From 6c28740f5fc48b83bcc8f0bcbdfca6dd885506fc Mon Sep 17 00:00:00 2001 From: Kristaps Kalnins Date: Wed, 16 Feb 2022 20:12:37 +0200 Subject: [PATCH 1/5] Clean up API of locker creation helpers --- src/constants.ts | 10 +++++ src/wrappers/govern/govern.ts | 4 +- src/wrappers/govern/setup.ts | 73 +++++++++++++++++++++++-------- src/wrappers/lockedVoter/setup.ts | 42 ++++++------------ 4 files changed, 80 insertions(+), 49 deletions(-) diff --git a/src/constants.ts b/src/constants.ts index d05550a..a7b5add 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -93,3 +93,13 @@ export const DEFAULT_LOCKER_PARAMS: LockerParams = { maxStakeVoteMultiplier: 10, whitelistEnabled: false, }; + +/** + * The default number of signers required to execute a transaction. + */ +export const DEFAULT_GOVERNOR_SMART_WALLET_THRESHOLD = 2; + +/** + * The default maximum number of owners that may be on a smart wallet. + */ +export const DEFAULT_GOVERNOR_SMART_WALLET_MAX_OWNERS = 3; diff --git a/src/wrappers/govern/govern.ts b/src/wrappers/govern/govern.ts index c422dcc..063a17b 100644 --- a/src/wrappers/govern/govern.ts +++ b/src/wrappers/govern/govern.ts @@ -1,5 +1,5 @@ import { TransactionEnvelope } from "@saberhq/solana-contrib"; -import type { PublicKey } from "@solana/web3.js"; +import type { PublicKey, Signer } from "@solana/web3.js"; import { Keypair, SystemProgram } from "@solana/web3.js"; import type BN from "bn.js"; @@ -33,7 +33,7 @@ export class GovernWrapper { }: { electorate: PublicKey; smartWallet: PublicKey; - baseKP?: Keypair; + baseKP?: Signer; quorumVotes?: BN; votingDelay?: BN; votingPeriod?: BN; diff --git a/src/wrappers/govern/setup.ts b/src/wrappers/govern/setup.ts index 5c4dc8a..9a1bb22 100644 --- a/src/wrappers/govern/setup.ts +++ b/src/wrappers/govern/setup.ts @@ -1,43 +1,78 @@ import type { GokiSDK, SmartWalletWrapper } from "@gokiprotocol/client"; import type { TransactionEnvelope } from "@saberhq/solana-contrib"; -import type { PublicKey } from "@solana/web3.js"; +import type { PublicKey, Signer } from "@solana/web3.js"; import { Keypair } from "@solana/web3.js"; import BN from "bn.js"; import type { GovernanceParameters, TribecaSDK } from "../.."; -import { DEFAULT_GOVERNANCE_PARAMETERS } from "../.."; +import { + DEFAULT_GOVERNANCE_PARAMETERS, + DEFAULT_GOVERNOR_SMART_WALLET_MAX_OWNERS, + DEFAULT_GOVERNOR_SMART_WALLET_THRESHOLD, +} from "../.."; import type { GovernorWrapper } from ".."; import { findGovernorAddress } from ".."; /** - * Creates a Governor. - * @returns + * Parameters for the {@link createGovernorWithElectorate} function. */ -export const createGovernorWithElectorate = async ({ - createElectorate, - sdk, - gokiSDK, - owners = [sdk.provider.wallet.publicKey], - governanceParameters = DEFAULT_GOVERNANCE_PARAMETERS, - govBaseKP = Keypair.generate(), - smartWalletBaseKP = Keypair.generate(), -}: { +export interface CreateGovernorWithElectorateParams { + /** + * Function to create an electorate. + */ createElectorate: ( governor: PublicKey ) => Promise<{ key: PublicKey; tx: TransactionEnvelope }>; + /** + * Tribeca SDK. + */ sdk: TribecaSDK; + /** + * Goki SDK. + */ gokiSDK: GokiSDK; + /** + * Other signers on the governance smart wallet. + */ owners?: PublicKey[]; + /** + * Additional governance parameters. + */ governanceParameters?: Partial; /** * Base of the governor. */ - govBaseKP?: Keypair; + governorBaseKP?: Signer; /** * Base of the smart wallet. */ smartWalletBaseKP?: Keypair; -}): Promise<{ + + /** + * Number of signers required to execute a smart wallet transaction. This is useful for testing. + */ + threshold?: number; + /** + * Maximum number of owners on the smart wallet. + */ + maxOwners?: number; +} + +/** + * Creates a Governor. + * @returns + */ +export const createGovernorWithElectorate = async ({ + createElectorate, + sdk, + gokiSDK, + owners = [sdk.provider.wallet.publicKey], + governanceParameters = DEFAULT_GOVERNANCE_PARAMETERS, + governorBaseKP = Keypair.generate(), + smartWalletBaseKP = Keypair.generate(), + threshold = DEFAULT_GOVERNOR_SMART_WALLET_THRESHOLD, + maxOwners = DEFAULT_GOVERNOR_SMART_WALLET_MAX_OWNERS, +}: CreateGovernorWithElectorateParams): Promise<{ governorWrapper: GovernorWrapper; smartWalletWrapper: SmartWalletWrapper; electorate: PublicKey; @@ -46,7 +81,7 @@ export const createGovernorWithElectorate = async ({ tx: TransactionEnvelope; }[]; }> => { - const [governor] = await findGovernorAddress(govBaseKP.publicKey); + const [governor] = await findGovernorAddress(governorBaseKP.publicKey); const createTXs: { title: string; @@ -55,8 +90,8 @@ export const createGovernorWithElectorate = async ({ const { smartWalletWrapper, tx: tx1 } = await gokiSDK.newSmartWallet({ owners: [...owners, governor], - threshold: new BN(2), - numOwners: 3, + threshold: new BN(threshold), + numOwners: maxOwners, base: smartWalletBaseKP, }); createTXs.push({ @@ -71,7 +106,7 @@ export const createGovernorWithElectorate = async ({ const { wrapper: governorWrapper, tx: tx2 } = await sdk.govern.createGovernor( { ...governanceParameters, - baseKP: govBaseKP, + baseKP: governorBaseKP, electorate, smartWallet: smartWalletWrapper.key, } diff --git a/src/wrappers/lockedVoter/setup.ts b/src/wrappers/lockedVoter/setup.ts index 89120f5..f31cd04 100644 --- a/src/wrappers/lockedVoter/setup.ts +++ b/src/wrappers/lockedVoter/setup.ts @@ -1,21 +1,26 @@ -import type { GokiSDK, SmartWalletWrapper } from "@gokiprotocol/client"; +import type { SmartWalletWrapper } from "@gokiprotocol/client"; import type { TransactionEnvelope } from "@saberhq/solana-contrib"; import type { PublicKey } from "@solana/web3.js"; import { Keypair } from "@solana/web3.js"; -import type { - GovernanceParameters, - GovernorWrapper, - LockerParams, -} from "../.."; +import type { GovernorWrapper, LockerParams } from "../.."; import { DEFAULT_GOVERNANCE_PARAMETERS, DEFAULT_LOCKER_PARAMS, } from "../../constants"; -import type { TribecaSDK } from "../../sdk"; +import type { CreateGovernorWithElectorateParams } from "../govern/setup"; import { createGovernorWithElectorate } from "../govern/setup"; import { LockerWrapper } from "./locker"; +export interface CreateLockerParams extends CreateGovernorWithElectorateParams { + govTokenMint: PublicKey; + lockerParams?: Partial; + /** + * Base of the locker. + */ + lockerBaseKP?: Keypair; +} + /** * Creates a new Locker. * @returns @@ -30,26 +35,7 @@ export const createLocker = async ({ governorBaseKP = Keypair.generate(), lockerBaseKP = Keypair.generate(), smartWalletBaseKP = Keypair.generate(), -}: { - sdk: TribecaSDK; - gokiSDK: GokiSDK; - govTokenMint: PublicKey; - owners?: PublicKey[]; - governanceParameters?: Partial; - lockerParams?: Partial; - /** - * Base of the governor. - */ - governorBaseKP?: Keypair; - /** - * Base of the governor. - */ - lockerBaseKP?: Keypair; - /** - * Base of the smart wallet. - */ - smartWalletBaseKP?: Keypair; -}): Promise<{ +}: CreateLockerParams): Promise<{ governorWrapper: GovernorWrapper; smartWalletWrapper: SmartWalletWrapper; lockerWrapper: LockerWrapper; @@ -75,7 +61,7 @@ export const createLocker = async ({ gokiSDK, owners, governanceParameters, - govBaseKP: governorBaseKP, + governorBaseKP, smartWalletBaseKP, }); return { From 4af5106bd21cb8e041f6664ba409bc08aab058e5 Mon Sep 17 00:00:00 2001 From: Kristaps Kalnins Date: Wed, 16 Feb 2022 20:43:33 +0200 Subject: [PATCH 2/5] use the governor creation helpers in tests --- package.json | 4 +- src/constants.ts | 25 ++++++++-- src/sdk.ts | 36 +++++++++++++-- src/wrappers/govern/setup.ts | 64 ++++++++++++++------------ src/wrappers/lockedVoter/setup.ts | 30 ++++++------ tests/execute-proposal.spec.ts | 76 ++++++++++++++----------------- tests/govern.spec.ts | 28 +++++++----- tests/locked-voter.spec.ts | 45 +++++++++--------- tests/simple-voter.spec.ts | 57 ++++++++++++++--------- tests/workspace/workspace.ts | 56 +---------------------- yarn.lock | 14 +++--- 11 files changed, 222 insertions(+), 213 deletions(-) diff --git a/package.json b/package.json index 5ffe861..79c6b2e 100644 --- a/package.json +++ b/package.json @@ -43,11 +43,11 @@ "@types/chai": "^4.3.0", "@types/lodash": "^4.14.178", "@types/mocha": "^9.1.0", - "@types/node": "^16.11.24", + "@types/node": "^16.11.25", "@types/prettier": "^2.4.4", "@yarnpkg/doctor": "^3.1.1-rc.2", "bn.js": "^5.2.0", - "chai": "^4.3.4", + "chai": "=4.3.4", "eslint": "^8.9.0", "eslint-import-resolver-node": "^0.3.6", "eslint-plugin-import": "^2.25.4", diff --git a/src/constants.ts b/src/constants.ts index a7b5add..421b4ea 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -95,11 +95,28 @@ export const DEFAULT_LOCKER_PARAMS: LockerParams = { }; /** - * The default number of signers required to execute a transaction. + * Smart wallet creation parameters. */ -export const DEFAULT_GOVERNOR_SMART_WALLET_THRESHOLD = 2; +export interface SmartWalletParameters { + /** + * Number of signers required to execute a smart wallet transaction. This is useful for testing. + */ + threshold: number; + /** + * Maximum number of owners on the smart wallet. + */ + maxOwners: number; + /** + * Timelock delay. + */ + delay: number; +} /** - * The default maximum number of owners that may be on a smart wallet. + * Default parameters for the Governor smart wallet. */ -export const DEFAULT_GOVERNOR_SMART_WALLET_MAX_OWNERS = 3; +export const DEFAULT_GOVERNOR_SMART_WALLET_PARAMS: SmartWalletParameters = { + threshold: 2, + maxOwners: 3, + delay: 0, +}; diff --git a/src/sdk.ts b/src/sdk.ts index 245c067..da19c0b 100644 --- a/src/sdk.ts +++ b/src/sdk.ts @@ -1,3 +1,4 @@ +import { GokiSDK } from "@gokiprotocol/client"; import type { BN } from "@project-serum/anchor"; import { newProgramMap } from "@saberhq/anchor-contrib"; import type { AugmentedProvider, Provider } from "@saberhq/solana-contrib"; @@ -15,19 +16,33 @@ import { TRIBECA_IDLS, } from "./constants"; import type { LockerParams } from "./programs/lockedVoter"; -import { GovernWrapper } from "./wrappers"; +import type { CreateLockerParams } from "./wrappers"; +import { createLocker, GovernWrapper } from "./wrappers"; import { findLockerAddress } from "./wrappers/lockedVoter/pda"; import { findSimpleElectorateAddress } from "./wrappers/simpleVoter/pda"; import type { PendingElectorate } from "./wrappers/simpleVoter/types"; /** - * TribecaSDK. + * Tribeca protocol SDK. */ export class TribecaSDK { + /** + * The Goki SDK. + */ + readonly goki: GokiSDK; + constructor( + /** + * Provider. + */ readonly provider: AugmentedProvider, + /** + * Programs. + */ readonly programs: TribecaPrograms - ) {} + ) { + this.goki = GokiSDK.load({ provider }); + } /** * Creates a new instance of the SDK with the given keypair. @@ -58,6 +73,10 @@ export class TribecaSDK { return new GovernWrapper(this); } + /** + * Creates a new simple electorate. + * @returns + */ async createSimpleElectorate({ proposalThreshold, governor, @@ -97,6 +116,15 @@ export class TribecaSDK { }; } + /** + * Creates a new Locker and Governor. + * @param params + * @returns + */ + async createLockerAndGovernor(params: Omit) { + return await createLocker({ ...params, sdk: this }); + } + /** * Creates a Locker, which is an Electorate that supports vote locking. * @returns @@ -107,7 +135,7 @@ export class TribecaSDK { baseKP = Keypair.generate(), ...providedLockerParams }: { - baseKP?: Keypair; + baseKP?: Signer; governor: PublicKey; govTokenMint: PublicKey; } & Partial): Promise<{ diff --git a/src/wrappers/govern/setup.ts b/src/wrappers/govern/setup.ts index 9a1bb22..6362300 100644 --- a/src/wrappers/govern/setup.ts +++ b/src/wrappers/govern/setup.ts @@ -1,14 +1,17 @@ -import type { GokiSDK, SmartWalletWrapper } from "@gokiprotocol/client"; +import type { SmartWalletWrapper } from "@gokiprotocol/client"; import type { TransactionEnvelope } from "@saberhq/solana-contrib"; import type { PublicKey, Signer } from "@solana/web3.js"; import { Keypair } from "@solana/web3.js"; import BN from "bn.js"; -import type { GovernanceParameters, TribecaSDK } from "../.."; +import type { + GovernanceParameters, + SmartWalletParameters, + TribecaSDK, +} from "../.."; import { DEFAULT_GOVERNANCE_PARAMETERS, - DEFAULT_GOVERNOR_SMART_WALLET_MAX_OWNERS, - DEFAULT_GOVERNOR_SMART_WALLET_THRESHOLD, + DEFAULT_GOVERNOR_SMART_WALLET_PARAMS, } from "../.."; import type { GovernorWrapper } from ".."; import { findGovernorAddress } from ".."; @@ -22,17 +25,15 @@ export interface CreateGovernorWithElectorateParams { */ createElectorate: ( governor: PublicKey - ) => Promise<{ key: PublicKey; tx: TransactionEnvelope }>; + ) => Promise<{ key: PublicKey; tx?: TransactionEnvelope }>; /** * Tribeca SDK. */ sdk: TribecaSDK; /** - * Goki SDK. - */ - gokiSDK: GokiSDK; - /** - * Other signers on the governance smart wallet. + * Additional owners on the governance smart wallet. + * + * For the Tribeca Trinity, this should be an owner invoker and an "emergency DAO" Smart Wallet. */ owners?: PublicKey[]; /** @@ -47,15 +48,10 @@ export interface CreateGovernorWithElectorateParams { * Base of the smart wallet. */ smartWalletBaseKP?: Keypair; - - /** - * Number of signers required to execute a smart wallet transaction. This is useful for testing. - */ - threshold?: number; /** - * Maximum number of owners on the smart wallet. + * Additional smart wallet parameters. */ - maxOwners?: number; + smartWalletParameters?: Partial; } /** @@ -65,13 +61,11 @@ export interface CreateGovernorWithElectorateParams { export const createGovernorWithElectorate = async ({ createElectorate, sdk, - gokiSDK, owners = [sdk.provider.wallet.publicKey], governanceParameters = DEFAULT_GOVERNANCE_PARAMETERS, governorBaseKP = Keypair.generate(), smartWalletBaseKP = Keypair.generate(), - threshold = DEFAULT_GOVERNOR_SMART_WALLET_THRESHOLD, - maxOwners = DEFAULT_GOVERNOR_SMART_WALLET_MAX_OWNERS, + smartWalletParameters = DEFAULT_GOVERNOR_SMART_WALLET_PARAMS, }: CreateGovernorWithElectorateParams): Promise<{ governorWrapper: GovernorWrapper; smartWalletWrapper: SmartWalletWrapper; @@ -88,10 +82,22 @@ export const createGovernorWithElectorate = async ({ tx: TransactionEnvelope; }[] = []; - const { smartWalletWrapper, tx: tx1 } = await gokiSDK.newSmartWallet({ - owners: [...owners, governor], - threshold: new BN(threshold), - numOwners: maxOwners, + if (owners.find((owner) => owner.equals(governor))) { + throw new Error("governor should not be provided in owners list"); + } + + const allOwners = [...owners, governor]; + const smartWalletParams: SmartWalletParameters = { + ...DEFAULT_GOVERNOR_SMART_WALLET_PARAMS, + ...smartWalletParameters, + maxOwners: allOwners.length, + }; + + const { smartWalletWrapper, tx: tx1 } = await sdk.goki.newSmartWallet({ + owners: allOwners, + threshold: new BN(smartWalletParams.threshold), + numOwners: smartWalletParams.maxOwners, + delay: new BN(smartWalletParams.delay), base: smartWalletBaseKP, }); createTXs.push({ @@ -116,10 +122,12 @@ export const createGovernorWithElectorate = async ({ tx: tx2, }); - createTXs.push({ - title: "Create Electorate", - tx: createElectorateTX, - }); + if (createElectorateTX) { + createTXs.push({ + title: "Create Electorate", + tx: createElectorateTX, + }); + } return { governorWrapper, diff --git a/src/wrappers/lockedVoter/setup.ts b/src/wrappers/lockedVoter/setup.ts index f31cd04..1b44a15 100644 --- a/src/wrappers/lockedVoter/setup.ts +++ b/src/wrappers/lockedVoter/setup.ts @@ -1,24 +1,28 @@ import type { SmartWalletWrapper } from "@gokiprotocol/client"; import type { TransactionEnvelope } from "@saberhq/solana-contrib"; -import type { PublicKey } from "@solana/web3.js"; +import type { PublicKey, Signer } from "@solana/web3.js"; import { Keypair } from "@solana/web3.js"; import type { GovernorWrapper, LockerParams } from "../.."; -import { - DEFAULT_GOVERNANCE_PARAMETERS, - DEFAULT_LOCKER_PARAMS, -} from "../../constants"; +import { DEFAULT_LOCKER_PARAMS } from "../../constants"; import type { CreateGovernorWithElectorateParams } from "../govern/setup"; import { createGovernorWithElectorate } from "../govern/setup"; import { LockerWrapper } from "./locker"; -export interface CreateLockerParams extends CreateGovernorWithElectorateParams { +export interface CreateLockerParams + extends Omit { + /** + * Mint of the token staked for veTokens. + */ govTokenMint: PublicKey; + /** + * Parameters for the locker. + */ lockerParams?: Partial; /** * Base of the locker. */ - lockerBaseKP?: Keypair; + lockerBaseKP?: Signer; } /** @@ -27,14 +31,10 @@ export interface CreateLockerParams extends CreateGovernorWithElectorateParams { */ export const createLocker = async ({ sdk, - gokiSDK, govTokenMint, - owners = [sdk.provider.wallet.publicKey], - governanceParameters = DEFAULT_GOVERNANCE_PARAMETERS, lockerParams = DEFAULT_LOCKER_PARAMS, - governorBaseKP = Keypair.generate(), lockerBaseKP = Keypair.generate(), - smartWalletBaseKP = Keypair.generate(), + ...createGovernorParams }: CreateLockerParams): Promise<{ governorWrapper: GovernorWrapper; smartWalletWrapper: SmartWalletWrapper; @@ -58,11 +58,7 @@ export const createLocker = async ({ }; }, sdk, - gokiSDK, - owners, - governanceParameters, - governorBaseKP, - smartWalletBaseKP, + ...createGovernorParams, }); return { ...governor, diff --git a/tests/execute-proposal.spec.ts b/tests/execute-proposal.spec.ts index cea952e..2264fbd 100644 --- a/tests/execute-proposal.spec.ts +++ b/tests/execute-proposal.spec.ts @@ -1,6 +1,6 @@ import type { SmartWalletWrapper } from "@gokiprotocol/client"; -import { findTransactionAddress, GokiSDK } from "@gokiprotocol/client"; -import { expectTX } from "@saberhq/chai-solana"; +import { findTransactionAddress } from "@gokiprotocol/client"; +import { assertTXSuccess, expectTX } from "@saberhq/chai-solana"; import type { u64 } from "@saberhq/token-utils"; import { createMint, sleep } from "@saberhq/token-utils"; import type { PublicKey, Signer } from "@solana/web3.js"; @@ -10,24 +10,16 @@ import { expect } from "chai"; import type { GovernorWrapper } from "../src"; import { + createGovernorWithElectorate, DEFAULT_GOVERNANCE_PARAMETERS, - findGovernorAddress, - findSimpleElectorateAddress, SimpleVoterWrapper, VoteSide, } from "../src"; -import { - createUser, - INITIAL_MINT_AMOUNT, - makeSDK, - ONE, - ZERO, -} from "./workspace"; +import { createUser, INITIAL_MINT_AMOUNT, makeSDK, ZERO } from "./workspace"; describe("Execute proposal", () => { const sdk = makeSDK(); const { provider } = sdk; - const gokiSDK = GokiSDK.load({ provider }); let govTokenMint: PublicKey; let governorW: GovernorWrapper; @@ -38,40 +30,42 @@ describe("Execute proposal", () => { govTokenMint = await createMint(provider); const electorateBase = Keypair.generate(); - const [electorateKey] = await findSimpleElectorateAddress( - electorateBase.publicKey - ); const govBase = Keypair.generate(); - const [governor] = await findGovernorAddress(govBase.publicKey); - const owners = [provider.wallet.publicKey, governor]; - - const { smartWalletWrapper, tx: tx1 } = await gokiSDK.newSmartWallet({ - owners, - threshold: ONE, - numOwners: owners.length, - }); - await expectTX(tx1, "create smart wallet").to.be.fulfilled; - const { wrapper, tx: tx2 } = await sdk.govern.createGovernor({ - baseKP: govBase, - electorate: electorateKey, - smartWallet: smartWalletWrapper.key, - quorumVotes: INITIAL_MINT_AMOUNT, - votingDelay: ZERO, - votingPeriod: new BN(2), - }); - await expectTX(tx2, "create governor").to.be.fulfilled; + const { electorate, createTXs, governorWrapper, smartWalletWrapper } = + await createGovernorWithElectorate({ + createElectorate: async (governorKey) => { + const { electorate, tx: tx3 } = await sdk.createSimpleElectorate({ + baseKP: electorateBase, + proposalThreshold: INITIAL_MINT_AMOUNT, + governor: governorKey, + govTokenMint, + }); + return { + key: electorate, + tx: tx3, + }; + }, + sdk, + owners: [provider.wallet.publicKey], + governanceParameters: { + quorumVotes: INITIAL_MINT_AMOUNT, + votingDelay: ZERO, + votingPeriod: new BN(2), + }, + governorBaseKP: govBase, + smartWalletParameters: { + // threshold = 1 allows us to test the whitelist stuff + threshold: 1, + }, + }); - const { electorate, tx: tx3 } = await sdk.createSimpleElectorate({ - baseKP: electorateBase, - proposalThreshold: INITIAL_MINT_AMOUNT, - governor, - govTokenMint, - }); - await expectTX(tx3, "initialize electorate").to.be.fulfilled; + for (const { title, tx } of createTXs) { + await assertTXSuccess(tx, title); + } voterW = await SimpleVoterWrapper.load(sdk, electorate); - governorW = wrapper; + governorW = governorWrapper; smartWalletW = smartWalletWrapper; }); diff --git a/tests/govern.spec.ts b/tests/govern.spec.ts index 859a0d5..b73e9eb 100644 --- a/tests/govern.spec.ts +++ b/tests/govern.spec.ts @@ -1,5 +1,4 @@ -import { GokiSDK } from "@gokiprotocol/client"; -import { expectTX } from "@saberhq/chai-solana"; +import { assertTXSuccess, expectTX } from "@saberhq/chai-solana"; import type { SendTransactionError } from "@solana/web3.js"; import { Keypair, PublicKey } from "@solana/web3.js"; import type BN from "bn.js"; @@ -7,17 +6,20 @@ import { expect } from "chai"; import { zip } from "lodash"; import invariant from "tiny-invariant"; -import { DEFAULT_VOTE_DELAY, DEFAULT_VOTE_PERIOD } from "../src"; +import { + createGovernorWithElectorate, + DEFAULT_VOTE_DELAY, + DEFAULT_VOTE_PERIOD, +} from "../src"; import type { GovernorWrapper } from "../src/wrappers/govern/governor"; import { findGovernorAddress, findProposalAddress, } from "../src/wrappers/govern/pda"; -import { DUMMY_INSTRUCTIONS, makeSDK, setupGovernor, ZERO } from "./workspace"; +import { DUMMY_INSTRUCTIONS, makeSDK, ZERO } from "./workspace"; describe("Govern", () => { const sdk = makeSDK(); - const gokiSDK = GokiSDK.load({ provider: sdk.provider }); let governorW: GovernorWrapper; let smartWallet: PublicKey; @@ -25,12 +27,16 @@ describe("Govern", () => { before(async () => { const owners = [sdk.provider.wallet.publicKey]; const electorate = Keypair.generate().publicKey; - const { governorWrapper, smartWalletWrapper } = await setupGovernor({ - electorate, - sdk, - gokiSDK, - owners, - }); + const { governorWrapper, smartWalletWrapper, createTXs } = + await createGovernorWithElectorate({ + createElectorate: () => Promise.resolve({ key: electorate }), + sdk, + owners, + }); + + for (const { tx, title } of createTXs) { + await assertTXSuccess(tx, title); + } smartWallet = smartWalletWrapper.key; governorW = governorWrapper; diff --git a/tests/locked-voter.spec.ts b/tests/locked-voter.spec.ts index 62ca013..6c1f4a5 100644 --- a/tests/locked-voter.spec.ts +++ b/tests/locked-voter.spec.ts @@ -1,7 +1,10 @@ import type { SmartWalletWrapper } from "@gokiprotocol/client"; -import { GokiSDK } from "@gokiprotocol/client"; import { newProgram } from "@saberhq/anchor-contrib"; -import { assertTXThrows, expectTX } from "@saberhq/chai-solana"; +import { + assertTXSuccess, + assertTXThrows, + expectTX, +} from "@saberhq/chai-solana"; import { TransactionEnvelope } from "@saberhq/solana-contrib"; import { createMint, @@ -52,7 +55,6 @@ import { executeTransactionBySmartWallet, INITIAL_MINT_AMOUNT, makeSDK, - setupGovernor, WhitelistTesterJSON, ZERO, } from "./workspace"; @@ -67,7 +69,6 @@ const expectLockedSupply = async ( describe("Locked Voter", () => { const sdk = makeSDK(); - const gokiSDK = GokiSDK.load({ provider: sdk.provider }); let base: PublicKey; let govTokenMint: PublicKey; @@ -81,29 +82,27 @@ describe("Locked Voter", () => { const baseKP = Keypair.generate(); base = baseKP.publicKey; - const [lockerKey] = await findLockerAddress(base); const owners = [sdk.provider.wallet.publicKey]; - const { governorWrapper, smartWalletWrapper } = await setupGovernor({ - electorate: lockerKey, - sdk, - gokiSDK, - owners, - }); - const { locker, tx: tx1 } = await sdk.createLocker({ - baseKP, - proposalActivationMinVotes: INITIAL_MINT_AMOUNT, - governor: governorWrapper.governorKey, - govTokenMint, - }); - await expectTX(tx1, "initialize locker").to.be.fulfilled; + const { createTXs, lockerWrapper, smartWalletWrapper, governorWrapper } = + await sdk.createLockerAndGovernor({ + owners, + lockerBaseKP: baseKP, + govTokenMint, + lockerParams: { + proposalActivationMinVotes: INITIAL_MINT_AMOUNT, + }, + smartWalletParameters: { + threshold: 1, + }, + }); - lockerW = await LockerWrapper.load( - sdk, - locker, - governorWrapper.governorKey - ); + for (const { tx, title } of createTXs) { + await assertTXSuccess(tx, title); + } + + lockerW = lockerWrapper; governorW = governorWrapper; smartWalletW = smartWalletWrapper; }); diff --git a/tests/simple-voter.spec.ts b/tests/simple-voter.spec.ts index db1cda3..39fef53 100644 --- a/tests/simple-voter.spec.ts +++ b/tests/simple-voter.spec.ts @@ -1,7 +1,6 @@ import type { SmartWalletWrapper } from "@gokiprotocol/client"; -import { GokiSDK } from "@gokiprotocol/client"; import { BN } from "@project-serum/anchor"; -import { expectTX } from "@saberhq/chai-solana"; +import { assertTXSuccess, expectTX } from "@saberhq/chai-solana"; import { createMint, getATAAddress, @@ -14,7 +13,11 @@ import { expect } from "chai"; import invariant from "tiny-invariant"; import { DEFAULT_GOVERNANCE_PARAMETERS } from "../src"; -import { SimpleVoterWrapper, VoteSide } from "../src/wrappers"; +import { + createGovernorWithElectorate, + SimpleVoterWrapper, + VoteSide, +} from "../src/wrappers"; import type { GovernorWrapper } from "../src/wrappers/govern/governor"; import { findVoteAddress } from "../src/wrappers/govern/pda"; import { @@ -26,13 +29,11 @@ import { DUMMY_INSTRUCTIONS, INITIAL_MINT_AMOUNT, makeSDK, - setupGovernor, ZERO, } from "./workspace"; describe("Simple Voter", () => { const sdk = makeSDK(); - const gokiSDK = GokiSDK.load({ provider: sdk.provider }); let base: PublicKey; let govTokenMint: PublicKey; @@ -46,23 +47,37 @@ describe("Simple Voter", () => { const baseKP = Keypair.generate(); base = baseKP.publicKey; - const [electorateKey] = await findSimpleElectorateAddress(base); - - const owners = [sdk.provider.wallet.publicKey]; - const { governorWrapper, smartWalletWrapper } = await setupGovernor({ - electorate: electorateKey, - sdk, - gokiSDK, - owners, - }); - const { electorate, tx: tx1 } = await sdk.createSimpleElectorate({ - baseKP, - proposalThreshold: INITIAL_MINT_AMOUNT, - governor: governorWrapper.governorKey, - govTokenMint, - }); - await expectTX(tx1, "initialize electorate").to.be.fulfilled; + const { electorate, createTXs, governorWrapper, smartWalletWrapper } = + await createGovernorWithElectorate({ + createElectorate: async (governorKey) => { + const { electorate, tx: tx3 } = await sdk.createSimpleElectorate({ + baseKP, + proposalThreshold: INITIAL_MINT_AMOUNT, + governor: governorKey, + govTokenMint, + }); + return { + key: electorate, + tx: tx3, + }; + }, + sdk, + owners: [sdk.provider.wallet.publicKey], + governanceParameters: { + quorumVotes: INITIAL_MINT_AMOUNT, + votingDelay: ZERO, + votingPeriod: new BN(2), + }, + smartWalletParameters: { + // threshold = 1 allows us to test the whitelist stuff + threshold: 1, + }, + }); + + for (const { title, tx } of createTXs) { + await assertTXSuccess(tx, title); + } voterW = await SimpleVoterWrapper.load(sdk, electorate); governorW = governorWrapper; diff --git a/tests/workspace/workspace.ts b/tests/workspace/workspace.ts index 3fffdd1..a14fe5b 100644 --- a/tests/workspace/workspace.ts +++ b/tests/workspace/workspace.ts @@ -1,6 +1,4 @@ -import type { GokiSDK } from "@gokiprotocol/client"; import type { SmartWalletWrapper } from "@gokiprotocol/client/dist/cjs/wrappers/smartWallet"; -import type { Idl } from "@project-serum/anchor"; import * as anchor from "@project-serum/anchor"; import { chaiSolana, expectTX } from "@saberhq/chai-solana"; import type { Provider } from "@saberhq/solana-contrib"; @@ -21,12 +19,10 @@ import { LAMPORTS_PER_SOL, TransactionInstruction, } from "@solana/web3.js"; -import chai, { assert } from "chai"; +import chai from "chai"; import type { TribecaPrograms } from "../../src"; import { TribecaSDK } from "../../src"; -import type { GovernorWrapper } from "../../src/wrappers/govern/governor"; -import { findGovernorAddress } from "../../src/wrappers/govern/pda"; chai.use(chaiSolana); @@ -63,56 +59,6 @@ export const makeSDK = (): TribecaSDK => { }); }; -type IDLError = NonNullable[number]; - -export const assertError = (error: IDLError, other: IDLError): void => { - assert.strictEqual(error.code, other.code); - assert.strictEqual(error.msg, other.msg); -}; - -export const setupGovernor = async ({ - electorate, - sdk, - gokiSDK, - owners, - ...governorParams -}: { - electorate: PublicKey; - sdk: TribecaSDK; - gokiSDK: GokiSDK; - owners: PublicKey[]; - quorumVotes?: anchor.BN; - votingDelay?: anchor.BN; - votingPeriod?: anchor.BN; - smartWalletOwner?: PublicKey; -}): Promise<{ - governorWrapper: GovernorWrapper; - smartWalletWrapper: SmartWalletWrapper; -}> => { - const baseKP = Keypair.generate(); - const [governor] = await findGovernorAddress(baseKP.publicKey); - - const { smartWalletWrapper, tx: tx1 } = await gokiSDK.newSmartWallet({ - owners: [...owners, governor], - threshold: ONE, - numOwners: 3, - }); - await expectTX(tx1, "create smart wallet").to.be.fulfilled; - - const { wrapper, tx: tx2 } = await sdk.govern.createGovernor({ - baseKP, - electorate, - smartWallet: smartWalletWrapper.key, - ...governorParams, - }); - await expectTX(tx2, "create governor").to.be.fulfilled; - - return { - governorWrapper: wrapper, - smartWalletWrapper, - }; -}; - export const createUser = async ( provider: Provider, govTokenMint: PublicKey, diff --git a/yarn.lock b/yarn.lock index 6d57492..75a1f28 100644 --- a/yarn.lock +++ b/yarn.lock @@ -449,11 +449,11 @@ __metadata: "@types/chai": ^4.3.0 "@types/lodash": ^4.14.178 "@types/mocha": ^9.1.0 - "@types/node": ^16.11.24 + "@types/node": ^16.11.25 "@types/prettier": ^2.4.4 "@yarnpkg/doctor": ^3.1.1-rc.2 bn.js: ^5.2.0 - chai: ^4.3.4 + chai: =4.3.4 eslint: ^8.9.0 eslint-import-resolver-node: ^0.3.6 eslint-plugin-import: ^2.25.4 @@ -650,10 +650,10 @@ __metadata: languageName: node linkType: hard -"@types/node@npm:^16.11.24": - version: 16.11.24 - resolution: "@types/node@npm:16.11.24" - checksum: d4827d548c04ca55db30901ea248ce1a1f8d2958b3143a7e71fd86d3d7b804faebfa9b80b934c7c6030a82553323dfe70f01dd0000176561a2cb3fc9db06a7b3 +"@types/node@npm:^16.11.25": + version: 16.11.25 + resolution: "@types/node@npm:16.11.25" + checksum: 0b6e25a81364be89256ad1a36341e27b387e646d3186e270108a8bb7b6ecdfdf5ae037aa1c75a5117b8a7509c80093b75431cd5cfcfbc4d553b52e7db2ca272e languageName: node linkType: hard @@ -1858,7 +1858,7 @@ __metadata: languageName: node linkType: hard -"chai@npm:=4.3.4, chai@npm:^4.3.4": +"chai@npm:=4.3.4": version: 4.3.4 resolution: "chai@npm:4.3.4" dependencies: From 7ed045de1e3ea1de8dfc4f18e01284e4899bdd5e Mon Sep 17 00:00:00 2001 From: Kristaps Kalnins Date: Wed, 16 Feb 2022 20:48:13 +0200 Subject: [PATCH 3/5] simple voter cleanup --- src/sdk.ts | 2 +- src/wrappers/simpleVoter/setup.ts | 75 +++++++++++++++++++++++++++++++ tests/simple-voter.spec.ts | 56 ++++++++++------------- 3 files changed, 100 insertions(+), 33 deletions(-) create mode 100644 src/wrappers/simpleVoter/setup.ts diff --git a/src/sdk.ts b/src/sdk.ts index da19c0b..548765f 100644 --- a/src/sdk.ts +++ b/src/sdk.ts @@ -84,7 +84,7 @@ export class TribecaSDK { baseKP = Keypair.generate(), }: { proposalThreshold: BN; - baseKP?: Keypair; + baseKP?: Signer; governor: PublicKey; govTokenMint: PublicKey; }): Promise { diff --git a/src/wrappers/simpleVoter/setup.ts b/src/wrappers/simpleVoter/setup.ts new file mode 100644 index 0000000..508cdc5 --- /dev/null +++ b/src/wrappers/simpleVoter/setup.ts @@ -0,0 +1,75 @@ +import type { SmartWalletWrapper } from "@gokiprotocol/client"; +import type { TransactionEnvelope } from "@saberhq/solana-contrib"; +import type { PublicKey, Signer } from "@solana/web3.js"; +import { Keypair } from "@solana/web3.js"; +import type BN from "bn.js"; + +import type { GovernorWrapper } from "../.."; +import { DEFAULT_PROPOSAL_THRESHOLD } from "../.."; +import type { CreateGovernorWithElectorateParams } from "../govern/setup"; +import { createGovernorWithElectorate } from "../govern/setup"; +import { SimpleVoterWrapper } from "."; + +/** + * Creates a new simple electorate. + */ +export interface CreateSimpleElectorateParams + extends Omit { + /** + * Mint of the token staked for veTokens. + */ + govTokenMint: PublicKey; + /** + * Proposal threshold. + */ + proposalThreshold?: BN; + /** + * Base of the electorate. + */ + electorateBaseKP?: Signer; +} + +/** + * Creates a new Locker. + * @returns + */ +export const createSimpleElectorate = async ({ + sdk, + govTokenMint, + proposalThreshold = DEFAULT_PROPOSAL_THRESHOLD, + electorateBaseKP = Keypair.generate(), + ...createGovernorParams +}: CreateSimpleElectorateParams): Promise<{ + governorWrapper: GovernorWrapper; + smartWalletWrapper: SmartWalletWrapper; + simpleVoterWrapper: SimpleVoterWrapper; + createTXs: { + title: string; + tx: TransactionEnvelope; + }[]; +}> => { + const { electorate, ...governor } = await createGovernorWithElectorate({ + createElectorate: async (governorKey) => { + const { electorate, tx } = await sdk.createSimpleElectorate({ + baseKP: electorateBaseKP, + proposalThreshold, + governor: governorKey, + govTokenMint, + }); + return { + key: electorate, + tx, + }; + }, + sdk, + ...createGovernorParams, + }); + return { + ...governor, + simpleVoterWrapper: new SimpleVoterWrapper( + sdk, + electorate, + governor.governorWrapper.governorKey + ), + }; +}; diff --git a/tests/simple-voter.spec.ts b/tests/simple-voter.spec.ts index 39fef53..bc46f8e 100644 --- a/tests/simple-voter.spec.ts +++ b/tests/simple-voter.spec.ts @@ -13,17 +13,15 @@ import { expect } from "chai"; import invariant from "tiny-invariant"; import { DEFAULT_GOVERNANCE_PARAMETERS } from "../src"; -import { - createGovernorWithElectorate, - SimpleVoterWrapper, - VoteSide, -} from "../src/wrappers"; +import type { SimpleVoterWrapper } from "../src/wrappers"; +import { VoteSide } from "../src/wrappers"; import type { GovernorWrapper } from "../src/wrappers/govern/governor"; import { findVoteAddress } from "../src/wrappers/govern/pda"; import { findSimpleElectorateAddress, findTokenRecordAddress, } from "../src/wrappers/simpleVoter/pda"; +import { createSimpleElectorate } from "../src/wrappers/simpleVoter/setup"; import { createUser, DUMMY_INSTRUCTIONS, @@ -48,38 +46,32 @@ describe("Simple Voter", () => { const baseKP = Keypair.generate(); base = baseKP.publicKey; - const { electorate, createTXs, governorWrapper, smartWalletWrapper } = - await createGovernorWithElectorate({ - createElectorate: async (governorKey) => { - const { electorate, tx: tx3 } = await sdk.createSimpleElectorate({ - baseKP, - proposalThreshold: INITIAL_MINT_AMOUNT, - governor: governorKey, - govTokenMint, - }); - return { - key: electorate, - tx: tx3, - }; - }, - sdk, - owners: [sdk.provider.wallet.publicKey], - governanceParameters: { - quorumVotes: INITIAL_MINT_AMOUNT, - votingDelay: ZERO, - votingPeriod: new BN(2), - }, - smartWalletParameters: { - // threshold = 1 allows us to test the whitelist stuff - threshold: 1, - }, - }); + const { + simpleVoterWrapper, + createTXs, + governorWrapper, + smartWalletWrapper, + } = await createSimpleElectorate({ + sdk, + proposalThreshold: INITIAL_MINT_AMOUNT, + owners: [sdk.provider.wallet.publicKey], + govTokenMint, + governanceParameters: { + quorumVotes: INITIAL_MINT_AMOUNT, + votingDelay: ZERO, + votingPeriod: new BN(2), + }, + smartWalletParameters: { + // threshold = 1 allows us to test the whitelist stuff + threshold: 1, + }, + }); for (const { title, tx } of createTXs) { await assertTXSuccess(tx, title); } - voterW = await SimpleVoterWrapper.load(sdk, electorate); + voterW = simpleVoterWrapper; governorW = governorWrapper; smartWalletW = smartWalletWrapper; }); From 4dfbb462d30be7d1d042a880094f2b32007844dd Mon Sep 17 00:00:00 2001 From: Kristaps Kalnins Date: Mon, 28 Feb 2022 03:57:54 +0200 Subject: [PATCH 4/5] anchor solana version update --- Anchor.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Anchor.toml b/Anchor.toml index 1ad2b50..653190d 100644 --- a/Anchor.toml +++ b/Anchor.toml @@ -1,5 +1,5 @@ anchor_version = "0.22.0" -solana_version = "1.8.8" +solana_version = "1.8.16" [scripts] test = "yarn mocha -b" From c153ea3e268ad3a80804a1faa10f09eff27b5251 Mon Sep 17 00:00:00 2001 From: Kristaps Kalnins Date: Mon, 28 Feb 2022 04:27:43 +0200 Subject: [PATCH 5/5] fix base check --- tests/simple-voter.spec.ts | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/tests/simple-voter.spec.ts b/tests/simple-voter.spec.ts index bc46f8e..4b7fd93 100644 --- a/tests/simple-voter.spec.ts +++ b/tests/simple-voter.spec.ts @@ -53,6 +53,7 @@ describe("Simple Voter", () => { smartWalletWrapper, } = await createSimpleElectorate({ sdk, + electorateBaseKP: baseKP, proposalThreshold: INITIAL_MINT_AMOUNT, owners: [sdk.provider.wallet.publicKey], govTokenMint, @@ -96,14 +97,18 @@ describe("Simple Voter", () => { const electorateData = await voterW.fetchVoterMetadata(); const [expectedElectorate, bump] = await findSimpleElectorateAddress(base); - expect(electorate).eqAddress(expectedElectorate); + expect(electorate, "electorate").eqAddress(expectedElectorate); expect(electorateData.bump).equal(bump); expect(electorateData.proposalThreshold.toString()).eq( INITIAL_MINT_AMOUNT.toString() ); - expect(electorateData.base).eqAddress(base); - expect(electorateData.govTokenMint).eqAddress(govTokenMint); - expect(electorateData.governor).eqAddress(governorW.governorKey); + expect(electorateData.base, "base").eqAddress(base); + expect(electorateData.govTokenMint, "gov token mint").eqAddress( + govTokenMint + ); + expect(electorateData.governor, "governor").eqAddress( + governorW.governorKey + ); }); describe("Token Record", () => {