| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946 | const assert = require("assert");const anchor = require("@project-serum/anchor");const serumCmn = require("@project-serum/common");const TokenInstructions = require("@project-serum/serum").TokenInstructions;const utils = require("./utils");describe("Lockup and Registry", () => {  // Read the provider from the configured environmnet.  const provider = anchor.Provider.env();  // Configure the client to use the provider.  anchor.setProvider(provider);  const lockup = anchor.workspace.Lockup;  const registry = anchor.workspace.Registry;  let lockupAddress = null;  const WHITELIST_SIZE = 10;  let mint = null;  let god = null;  it("Sets up initial test state", async () => {    const [_mint, _god] = await serumCmn.createMintAndVault(      provider,      new anchor.BN(1000000)    );    mint = _mint;    god = _god;  });  it("Is initialized!", async () => {    await lockup.state.rpc.new({      accounts: {        authority: provider.wallet.publicKey,      },    });    lockupAddress = await lockup.state.address();    const lockupAccount = await lockup.state.fetch();    assert.ok(lockupAccount.authority.equals(provider.wallet.publicKey));    assert.ok(lockupAccount.whitelist.length === WHITELIST_SIZE);    lockupAccount.whitelist.forEach((e) => {      assert.ok(e.programId.equals(anchor.web3.PublicKey.default));    });  });  it("Deletes the default whitelisted addresses", async () => {    const defaultEntry = { programId: anchor.web3.PublicKey.default };    await lockup.state.rpc.whitelistDelete(defaultEntry, {      accounts: {        authority: provider.wallet.publicKey,      },    });  });  it("Sets a new authority", async () => {    const newAuthority = anchor.web3.Keypair.generate();    await lockup.state.rpc.setAuthority(newAuthority.publicKey, {      accounts: {        authority: provider.wallet.publicKey,      },    });    let lockupAccount = await lockup.state.fetch();    assert.ok(lockupAccount.authority.equals(newAuthority.publicKey));    await lockup.state.rpc.setAuthority(provider.wallet.publicKey, {      accounts: {        authority: newAuthority.publicKey,      },      signers: [newAuthority],    });    lockupAccount = await lockup.state.fetch();    assert.ok(lockupAccount.authority.equals(provider.wallet.publicKey));  });  const entries = [];  it("Adds to the whitelist", async () => {    const generateEntry = async () => {      let programId = anchor.web3.Keypair.generate().publicKey;      return {        programId,      };    };    for (let k = 0; k < WHITELIST_SIZE; k += 1) {      entries.push(await generateEntry());    }    const accounts = {      authority: provider.wallet.publicKey,    };    await lockup.state.rpc.whitelistAdd(entries[0], { accounts });    let lockupAccount = await lockup.state.fetch();    assert.ok(lockupAccount.whitelist.length === 1);    assert.deepEqual(lockupAccount.whitelist, [entries[0]]);    for (let k = 1; k < WHITELIST_SIZE; k += 1) {      await lockup.state.rpc.whitelistAdd(entries[k], { accounts });    }    lockupAccount = await lockup.state.fetch();    assert.deepEqual(lockupAccount.whitelist, entries);    await assert.rejects(      async () => {        const e = await generateEntry();        await lockup.state.rpc.whitelistAdd(e, { accounts });      },      (err) => {        assert.equal(err.code, 308);        assert.equal(err.msg, "Whitelist is full");        return true;      }    );  });  it("Removes from the whitelist", async () => {    await lockup.state.rpc.whitelistDelete(entries[0], {      accounts: {        authority: provider.wallet.publicKey,      },    });    let lockupAccount = await lockup.state.fetch();    assert.deepEqual(lockupAccount.whitelist, entries.slice(1));  });  const vesting = anchor.web3.Keypair.generate();  let vestingAccount = null;  let vestingSigner = null;  it("Creates a vesting account", async () => {    const startTs = new anchor.BN(Date.now() / 1000);    const endTs = new anchor.BN(startTs.toNumber() + 5);    const periodCount = new anchor.BN(2);    const beneficiary = provider.wallet.publicKey;    const depositAmount = new anchor.BN(100);    const vault = anchor.web3.Keypair.generate();    let [      _vestingSigner,      nonce,    ] = await anchor.web3.PublicKey.findProgramAddress(      [vesting.publicKey.toBuffer()],      lockup.programId    );    vestingSigner = _vestingSigner;    await lockup.rpc.createVesting(      beneficiary,      depositAmount,      nonce,      startTs,      endTs,      periodCount,      null, // Lock realizor is None.      {        accounts: {          vesting: vesting.publicKey,          vault: vault.publicKey,          depositor: god,          depositorAuthority: provider.wallet.publicKey,          tokenProgram: TokenInstructions.TOKEN_PROGRAM_ID,          rent: anchor.web3.SYSVAR_RENT_PUBKEY,          clock: anchor.web3.SYSVAR_CLOCK_PUBKEY,        },        signers: [vesting, vault],        instructions: [          await lockup.account.vesting.createInstruction(vesting),          ...(await serumCmn.createTokenAccountInstrs(            provider,            vault.publicKey,            mint,            vestingSigner          )),        ],      }    );    vestingAccount = await lockup.account.vesting.fetch(vesting.publicKey);    assert.ok(vestingAccount.beneficiary.equals(provider.wallet.publicKey));    assert.ok(vestingAccount.mint.equals(mint));    assert.ok(vestingAccount.grantor.equals(provider.wallet.publicKey));    assert.ok(vestingAccount.outstanding.eq(depositAmount));    assert.ok(vestingAccount.startBalance.eq(depositAmount));    assert.ok(vestingAccount.whitelistOwned.eq(new anchor.BN(0)));    assert.equal(vestingAccount.nonce, nonce);    assert.ok(vestingAccount.createdTs.gt(new anchor.BN(0)));    assert.ok(vestingAccount.startTs.eq(startTs));    assert.ok(vestingAccount.endTs.eq(endTs));    assert.ok(vestingAccount.realizor === null);  });  it("Fails to withdraw from a vesting account before vesting", async () => {    await assert.rejects(      async () => {        await lockup.rpc.withdraw(new anchor.BN(100), {          accounts: {            vesting: vesting.publicKey,            beneficiary: provider.wallet.publicKey,            token: god,            vault: vestingAccount.vault,            vestingSigner: vestingSigner,            tokenProgram: TokenInstructions.TOKEN_PROGRAM_ID,            clock: anchor.web3.SYSVAR_CLOCK_PUBKEY,          },        });      },      (err) => {        assert.equal(err.code, 307);        assert.equal(err.msg, "Insufficient withdrawal balance.");        return true;      }    );  });  it("Waits for a vesting period to pass", async () => {    await serumCmn.sleep(10 * 1000);  });  it("Withdraws from the vesting account", async () => {    const token = await serumCmn.createTokenAccount(      provider,      mint,      provider.wallet.publicKey    );    await lockup.rpc.withdraw(new anchor.BN(100), {      accounts: {        vesting: vesting.publicKey,        beneficiary: provider.wallet.publicKey,        token,        vault: vestingAccount.vault,        vestingSigner,        tokenProgram: TokenInstructions.TOKEN_PROGRAM_ID,        clock: anchor.web3.SYSVAR_CLOCK_PUBKEY,      },    });    vestingAccount = await lockup.account.vesting.fetch(vesting.publicKey);    assert.ok(vestingAccount.outstanding.eq(new anchor.BN(0)));    const vaultAccount = await serumCmn.getTokenAccount(      provider,      vestingAccount.vault    );    assert.ok(vaultAccount.amount.eq(new anchor.BN(0)));    const tokenAccount = await serumCmn.getTokenAccount(provider, token);    assert.ok(tokenAccount.amount.eq(new anchor.BN(100)));  });  const registrar = anchor.web3.Keypair.generate();  const rewardQ = anchor.web3.Keypair.generate();  const withdrawalTimelock = new anchor.BN(4);  const stakeRate = new anchor.BN(2);  const rewardQLen = 170;  let registrarAccount = null;  let registrarSigner = null;  let nonce = null;  let poolMint = null;  it("Creates registry genesis", async () => {    const [      _registrarSigner,      _nonce,    ] = await anchor.web3.PublicKey.findProgramAddress(      [registrar.publicKey.toBuffer()],      registry.programId    );    registrarSigner = _registrarSigner;    nonce = _nonce;    poolMint = await serumCmn.createMint(provider, registrarSigner);  });  it("Initializes registry's global state", async () => {    await registry.state.rpc.new({      accounts: { lockupProgram: lockup.programId },    });    const state = await registry.state.fetch();    assert.ok(state.lockupProgram.equals(lockup.programId));    // Should not allow a second initializatoin.    await assert.rejects(      async () => {        await registry.state.rpc.new(lockup.programId);      },      (err) => {        return true;      }    );  });  it("Initializes the registrar", async () => {    await registry.rpc.initialize(      mint,      provider.wallet.publicKey,      nonce,      withdrawalTimelock,      stakeRate,      rewardQLen,      {        accounts: {          registrar: registrar.publicKey,          poolMint,          rewardEventQ: rewardQ.publicKey,          rent: anchor.web3.SYSVAR_RENT_PUBKEY,        },        signers: [registrar, rewardQ],        instructions: [          await registry.account.registrar.createInstruction(registrar),          await registry.account.rewardQueue.createInstruction(rewardQ, 8250),        ],      }    );    registrarAccount = await registry.account.registrar.fetch(registrar.publicKey);    assert.ok(registrarAccount.authority.equals(provider.wallet.publicKey));    assert.equal(registrarAccount.nonce, nonce);    assert.ok(registrarAccount.mint.equals(mint));    assert.ok(registrarAccount.poolMint.equals(poolMint));    assert.ok(registrarAccount.stakeRate.eq(stakeRate));    assert.ok(registrarAccount.rewardEventQ.equals(rewardQ.publicKey));    assert.ok(registrarAccount.withdrawalTimelock.eq(withdrawalTimelock));  });  const member = anchor.web3.Keypair.generate();  let memberAccount = null;  let memberSigner = null;  let balances = null;  let balancesLocked = null;  it("Creates a member", async () => {    const [      _memberSigner,      nonce,    ] = await anchor.web3.PublicKey.findProgramAddress(      [registrar.publicKey.toBuffer(), member.publicKey.toBuffer()],      registry.programId    );    memberSigner = _memberSigner;    const [mainTx, _balances] = await utils.createBalanceSandbox(      provider,      registrarAccount,      memberSigner    );    const [lockedTx, _balancesLocked] = await utils.createBalanceSandbox(      provider,      registrarAccount,      memberSigner    );    balances = _balances;    balancesLocked = _balancesLocked;    const tx = registry.transaction.createMember(nonce, {      accounts: {        registrar: registrar.publicKey,        member: member.publicKey,        beneficiary: provider.wallet.publicKey,        memberSigner,        balances,        balancesLocked,        tokenProgram: TokenInstructions.TOKEN_PROGRAM_ID,        rent: anchor.web3.SYSVAR_RENT_PUBKEY,      },      instructions: [await registry.account.member.createInstruction(member)],    });    const signers = [member, provider.wallet.payer];    const allTxs = [mainTx, lockedTx, { tx, signers }];    let txSigs = await provider.sendAll(allTxs);    memberAccount = await registry.account.member.fetch(member.publicKey);    assert.ok(memberAccount.registrar.equals(registrar.publicKey));    assert.ok(memberAccount.beneficiary.equals(provider.wallet.publicKey));    assert.ok(memberAccount.metadata.equals(anchor.web3.PublicKey.default));    assert.equal(      JSON.stringify(memberAccount.balances),      JSON.stringify(balances)    );    assert.equal(      JSON.stringify(memberAccount.balancesLocked),      JSON.stringify(balancesLocked)    );    assert.ok(memberAccount.rewardsCursor === 0);    assert.ok(memberAccount.lastStakeTs.eq(new anchor.BN(0)));  });  it("Deposits (unlocked) to a member", async () => {    const depositAmount = new anchor.BN(120);    await registry.rpc.deposit(depositAmount, {      accounts: {        depositor: god,        depositorAuthority: provider.wallet.publicKey,        tokenProgram: TokenInstructions.TOKEN_PROGRAM_ID,        vault: memberAccount.balances.vault,        beneficiary: provider.wallet.publicKey,        member: member.publicKey,      },    });    const memberVault = await serumCmn.getTokenAccount(      provider,      memberAccount.balances.vault    );    assert.ok(memberVault.amount.eq(depositAmount));  });  it("Stakes to a member (unlocked)", async () => {    const stakeAmount = new anchor.BN(10);    await registry.rpc.stake(stakeAmount, false, {      accounts: {        // Stake instance.        registrar: registrar.publicKey,        rewardEventQ: rewardQ.publicKey,        poolMint,        // Member.        member: member.publicKey,        beneficiary: provider.wallet.publicKey,        balances,        balancesLocked,        // Program signers.        memberSigner,        registrarSigner,        // Misc.        clock: anchor.web3.SYSVAR_CLOCK_PUBKEY,        tokenProgram: TokenInstructions.TOKEN_PROGRAM_ID,      },    });    const vault = await serumCmn.getTokenAccount(      provider,      memberAccount.balances.vault    );    const vaultStake = await serumCmn.getTokenAccount(      provider,      memberAccount.balances.vaultStake    );    const spt = await serumCmn.getTokenAccount(      provider,      memberAccount.balances.spt    );    assert.ok(vault.amount.eq(new anchor.BN(100)));    assert.ok(vaultStake.amount.eq(new anchor.BN(20)));    assert.ok(spt.amount.eq(new anchor.BN(10)));  });  const unlockedVendor = anchor.web3.Keypair.generate();  const unlockedVendorVault = anchor.web3.Keypair.generate();  let unlockedVendorSigner = null;  it("Drops an unlocked reward", async () => {    const rewardKind = {      unlocked: {},    };    const rewardAmount = new anchor.BN(200);    const expiry = new anchor.BN(Date.now() / 1000 + 5);    const [      _vendorSigner,      nonce,    ] = await anchor.web3.PublicKey.findProgramAddress(      [registrar.publicKey.toBuffer(), unlockedVendor.publicKey.toBuffer()],      registry.programId    );    unlockedVendorSigner = _vendorSigner;    await registry.rpc.dropReward(      rewardKind,      rewardAmount,      expiry,      provider.wallet.publicKey,      nonce,      {        accounts: {          registrar: registrar.publicKey,          rewardEventQ: rewardQ.publicKey,          poolMint,          vendor: unlockedVendor.publicKey,          vendorVault: unlockedVendorVault.publicKey,          depositor: god,          depositorAuthority: provider.wallet.publicKey,          tokenProgram: TokenInstructions.TOKEN_PROGRAM_ID,          clock: anchor.web3.SYSVAR_CLOCK_PUBKEY,          rent: anchor.web3.SYSVAR_RENT_PUBKEY,        },        signers: [unlockedVendorVault, unlockedVendor],        instructions: [          ...(await serumCmn.createTokenAccountInstrs(            provider,            unlockedVendorVault.publicKey,            mint,            unlockedVendorSigner          )),          await registry.account.rewardVendor.createInstruction(unlockedVendor),        ],      }    );    const vendorAccount = await registry.account.rewardVendor.fetch(      unlockedVendor.publicKey    );    assert.ok(vendorAccount.registrar.equals(registrar.publicKey));    assert.ok(vendorAccount.vault.equals(unlockedVendorVault.publicKey));    assert.ok(vendorAccount.nonce === nonce);    assert.ok(vendorAccount.poolTokenSupply.eq(new anchor.BN(10)));    assert.ok(vendorAccount.expiryTs.eq(expiry));    assert.ok(vendorAccount.expiryReceiver.equals(provider.wallet.publicKey));    assert.ok(vendorAccount.total.eq(rewardAmount));    assert.ok(vendorAccount.expired === false);    assert.ok(vendorAccount.rewardEventQCursor === 0);    assert.deepEqual(vendorAccount.kind, rewardKind);    const rewardQAccount = await registry.account.rewardQueue.fetch(      rewardQ.publicKey    );    assert.ok(rewardQAccount.head === 1);    assert.ok(rewardQAccount.tail === 0);    const e = rewardQAccount.events[0];    assert.ok(e.vendor.equals(unlockedVendor.publicKey));    assert.equal(e.locked, false);  });  it("Collects an unlocked reward", async () => {    const token = await serumCmn.createTokenAccount(      provider,      mint,      provider.wallet.publicKey    );    await registry.rpc.claimReward({      accounts: {        to: token,        cmn: {          registrar: registrar.publicKey,          member: member.publicKey,          beneficiary: provider.wallet.publicKey,          balances,          balancesLocked,          vendor: unlockedVendor.publicKey,          vault: unlockedVendorVault.publicKey,          vendorSigner: unlockedVendorSigner,          tokenProgram: TokenInstructions.TOKEN_PROGRAM_ID,          clock: anchor.web3.SYSVAR_CLOCK_PUBKEY,        },      },    });    let tokenAccount = await serumCmn.getTokenAccount(provider, token);    assert.ok(tokenAccount.amount.eq(new anchor.BN(200)));    const memberAccount = await registry.account.member.fetch(member.publicKey);    assert.ok(memberAccount.rewardsCursor == 1);  });  const lockedVendor = anchor.web3.Keypair.generate();  const lockedVendorVault = anchor.web3.Keypair.generate();  let lockedVendorSigner = null;  let lockedRewardAmount = null;  let lockedRewardKind = null;  it("Drops a locked reward", async () => {    lockedRewardKind = {      locked: {        startTs: new anchor.BN(Date.now() / 1000),        endTs: new anchor.BN(Date.now() / 1000 + 6),        periodCount: new anchor.BN(2),      },    };    lockedRewardAmount = new anchor.BN(200);    const expiry = new anchor.BN(Date.now() / 1000 + 5);    const [      _vendorSigner,      nonce,    ] = await anchor.web3.PublicKey.findProgramAddress(      [registrar.publicKey.toBuffer(), lockedVendor.publicKey.toBuffer()],      registry.programId    );    lockedVendorSigner = _vendorSigner;    await registry.rpc.dropReward(      lockedRewardKind,      lockedRewardAmount,      expiry,      provider.wallet.publicKey,      nonce,      {        accounts: {          registrar: registrar.publicKey,          rewardEventQ: rewardQ.publicKey,          poolMint,          vendor: lockedVendor.publicKey,          vendorVault: lockedVendorVault.publicKey,          depositor: god,          depositorAuthority: provider.wallet.publicKey,          tokenProgram: TokenInstructions.TOKEN_PROGRAM_ID,          clock: anchor.web3.SYSVAR_CLOCK_PUBKEY,          rent: anchor.web3.SYSVAR_RENT_PUBKEY,        },        signers: [lockedVendorVault, lockedVendor],        instructions: [          ...(await serumCmn.createTokenAccountInstrs(            provider,            lockedVendorVault.publicKey,            mint,            lockedVendorSigner          )),          await registry.account.rewardVendor.createInstruction(lockedVendor),        ],      }    );    const vendorAccount = await registry.account.rewardVendor.fetch(      lockedVendor.publicKey    );    assert.ok(vendorAccount.registrar.equals(registrar.publicKey));    assert.ok(vendorAccount.vault.equals(lockedVendorVault.publicKey));    assert.ok(vendorAccount.nonce === nonce);    assert.ok(vendorAccount.poolTokenSupply.eq(new anchor.BN(10)));    assert.ok(vendorAccount.expiryTs.eq(expiry));    assert.ok(vendorAccount.expiryReceiver.equals(provider.wallet.publicKey));    assert.ok(vendorAccount.total.eq(lockedRewardAmount));    assert.ok(vendorAccount.expired === false);    assert.ok(vendorAccount.rewardEventQCursor === 1);    assert.equal(      JSON.stringify(vendorAccount.kind),      JSON.stringify(lockedRewardKind)    );    const rewardQAccount = await registry.account.rewardQueue.fetch(      rewardQ.publicKey    );    assert.ok(rewardQAccount.head === 2);    assert.ok(rewardQAccount.tail === 0);    const e = rewardQAccount.events[1];    assert.ok(e.vendor.equals(lockedVendor.publicKey));    assert.ok(e.locked === true);  });  let vendoredVesting = null;  let vendoredVestingVault = null;  let vendoredVestingSigner = null;  it("Claims a locked reward", async () => {    vendoredVesting = anchor.web3.Keypair.generate();    vendoredVestingVault = anchor.web3.Keypair.generate();    let [      _vendoredVestingSigner,      nonce,    ] = await anchor.web3.PublicKey.findProgramAddress(      [vendoredVesting.publicKey.toBuffer()],      lockup.programId    );    vendoredVestingSigner = _vendoredVestingSigner;    const remainingAccounts = lockup.instruction.createVesting      .accounts({        vesting: vendoredVesting.publicKey,        vault: vendoredVestingVault.publicKey,        depositor: lockedVendorVault.publicKey,        depositorAuthority: lockedVendorSigner,        tokenProgram: TokenInstructions.TOKEN_PROGRAM_ID,        rent: anchor.web3.SYSVAR_RENT_PUBKEY,        clock: anchor.web3.SYSVAR_CLOCK_PUBKEY,      })      // Change the signer status on the vendor signer since it's signed by the program, not the      // client.      .map((meta) =>        meta.pubkey === lockedVendorSigner ? { ...meta, isSigner: false } : meta      );    await registry.rpc.claimRewardLocked(nonce, {      accounts: {        registry: await registry.state.address(),        lockupProgram: lockup.programId,        cmn: {          registrar: registrar.publicKey,          member: member.publicKey,          beneficiary: provider.wallet.publicKey,          balances,          balancesLocked,          vendor: lockedVendor.publicKey,          vault: lockedVendorVault.publicKey,          vendorSigner: lockedVendorSigner,          tokenProgram: TokenInstructions.TOKEN_PROGRAM_ID,          clock: anchor.web3.SYSVAR_CLOCK_PUBKEY,        },      },      remainingAccounts,      signers: [vendoredVesting, vendoredVestingVault],      instructions: [        await lockup.account.vesting.createInstruction(vendoredVesting),        ...(await serumCmn.createTokenAccountInstrs(          provider,          vendoredVestingVault.publicKey,          mint,          vendoredVestingSigner        )),      ],    });    const lockupAccount = await lockup.account.vesting.fetch(      vendoredVesting.publicKey    );    assert.ok(lockupAccount.beneficiary.equals(provider.wallet.publicKey));    assert.ok(lockupAccount.mint.equals(mint));    assert.ok(lockupAccount.vault.equals(vendoredVestingVault.publicKey));    assert.ok(lockupAccount.outstanding.eq(lockedRewardAmount));    assert.ok(lockupAccount.startBalance.eq(lockedRewardAmount));    assert.ok(lockupAccount.endTs.eq(lockedRewardKind.locked.endTs));    assert.ok(      lockupAccount.periodCount.eq(lockedRewardKind.locked.periodCount)    );    assert.ok(lockupAccount.whitelistOwned.eq(new anchor.BN(0)));    assert.ok(lockupAccount.realizor.program.equals(registry.programId));    assert.ok(lockupAccount.realizor.metadata.equals(member.publicKey));  });  it("Waits for the lockup period to pass", async () => {    await serumCmn.sleep(10 * 1000);  });  it("Should fail to unlock an unrealized lockup reward", async () => {    const token = await serumCmn.createTokenAccount(      provider,      mint,      provider.wallet.publicKey    );    await assert.rejects(      async () => {        const withdrawAmount = new anchor.BN(10);        await lockup.rpc.withdraw(withdrawAmount, {          accounts: {            vesting: vendoredVesting.publicKey,            beneficiary: provider.wallet.publicKey,            token,            vault: vendoredVestingVault.publicKey,            vestingSigner: vendoredVestingSigner,            tokenProgram: TokenInstructions.TOKEN_PROGRAM_ID,            clock: anchor.web3.SYSVAR_CLOCK_PUBKEY,          },          // TODO: trait methods generated on the client. Until then, we need to manually          //       specify the account metas here.          remainingAccounts: [            { pubkey: registry.programId, isWritable: false, isSigner: false },            { pubkey: member.publicKey, isWritable: false, isSigner: false },            { pubkey: balances.spt, isWritable: false, isSigner: false },            { pubkey: balancesLocked.spt, isWritable: false, isSigner: false },          ],        });      },      (err) => {        // Solana doesn't propagate errors across CPI. So we receive the registry's error code,        // not the lockup's.        const errorCode = "custom program error: 0x140";        assert.ok(err.toString().split(errorCode).length === 2);        return true;      }    );  });  const pendingWithdrawal = anchor.web3.Keypair.generate();  it("Unstakes (unlocked)", async () => {    const unstakeAmount = new anchor.BN(10);    await registry.rpc.startUnstake(unstakeAmount, false, {      accounts: {        registrar: registrar.publicKey,        rewardEventQ: rewardQ.publicKey,        poolMint,        pendingWithdrawal: pendingWithdrawal.publicKey,        member: member.publicKey,        beneficiary: provider.wallet.publicKey,        balances,        balancesLocked,        memberSigner,        tokenProgram: TokenInstructions.TOKEN_PROGRAM_ID,        clock: anchor.web3.SYSVAR_CLOCK_PUBKEY,        rent: anchor.web3.SYSVAR_RENT_PUBKEY,      },      signers: [pendingWithdrawal],      instructions: [        await registry.account.pendingWithdrawal.createInstruction(          pendingWithdrawal        ),      ],    });    const vaultPw = await serumCmn.getTokenAccount(      provider,      memberAccount.balances.vaultPw    );    const vaultStake = await serumCmn.getTokenAccount(      provider,      memberAccount.balances.vaultStake    );    const spt = await serumCmn.getTokenAccount(      provider,      memberAccount.balances.spt    );    assert.ok(vaultPw.amount.eq(new anchor.BN(20)));    assert.ok(vaultStake.amount.eq(new anchor.BN(0)));    assert.ok(spt.amount.eq(new anchor.BN(0)));  });  const tryEndUnstake = async () => {    await registry.rpc.endUnstake({      accounts: {        registrar: registrar.publicKey,        member: member.publicKey,        beneficiary: provider.wallet.publicKey,        pendingWithdrawal: pendingWithdrawal.publicKey,        vault: balances.vault,        vaultPw: balances.vaultPw,        memberSigner,        clock: anchor.web3.SYSVAR_CLOCK_PUBKEY,        tokenProgram: TokenInstructions.TOKEN_PROGRAM_ID,      },    });  };  it("Fails to end unstaking before timelock", async () => {    await assert.rejects(      async () => {        await tryEndUnstake();      },      (err) => {        assert.equal(err.code, 309);        assert.equal(err.msg, "The unstake timelock has not yet expired.");        return true;      }    );  });  it("Waits for the unstake period to end", async () => {    await serumCmn.sleep(5000);  });  it("Unstake finalizes (unlocked)", async () => {    await tryEndUnstake();    const vault = await serumCmn.getTokenAccount(      provider,      memberAccount.balances.vault    );    const vaultPw = await serumCmn.getTokenAccount(      provider,      memberAccount.balances.vaultPw    );    assert.ok(vault.amount.eq(new anchor.BN(120)));    assert.ok(vaultPw.amount.eq(new anchor.BN(0)));  });  it("Withdraws deposits (unlocked)", async () => {    const token = await serumCmn.createTokenAccount(      provider,      mint,      provider.wallet.publicKey    );    const withdrawAmount = new anchor.BN(100);    await registry.rpc.withdraw(withdrawAmount, {      accounts: {        registrar: registrar.publicKey,        member: member.publicKey,        beneficiary: provider.wallet.publicKey,        vault: memberAccount.balances.vault,        memberSigner,        depositor: token,        tokenProgram: TokenInstructions.TOKEN_PROGRAM_ID,      },    });    const tokenAccount = await serumCmn.getTokenAccount(provider, token);    assert.ok(tokenAccount.amount.eq(withdrawAmount));  });  it("Should succesfully unlock a locked reward after unstaking", async () => {    const token = await serumCmn.createTokenAccount(      provider,      mint,      provider.wallet.publicKey    );    const withdrawAmount = new anchor.BN(7);    await lockup.rpc.withdraw(withdrawAmount, {      accounts: {        vesting: vendoredVesting.publicKey,        beneficiary: provider.wallet.publicKey,        token,        vault: vendoredVestingVault.publicKey,        vestingSigner: vendoredVestingSigner,        tokenProgram: TokenInstructions.TOKEN_PROGRAM_ID,        clock: anchor.web3.SYSVAR_CLOCK_PUBKEY,      },      // TODO: trait methods generated on the client. Until then, we need to manually      //       specify the account metas here.      remainingAccounts: [        { pubkey: registry.programId, isWritable: false, isSigner: false },        { pubkey: member.publicKey, isWritable: false, isSigner: false },        { pubkey: balances.spt, isWritable: false, isSigner: false },        { pubkey: balancesLocked.spt, isWritable: false, isSigner: false },      ],    });    const tokenAccount = await serumCmn.getTokenAccount(provider, token);    assert.ok(tokenAccount.amount.eq(withdrawAmount));  });});
 |