text: 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(new anchor.web3.PublicKey()));
      });
    });
  
    it("Deletes the default whitelisted addresses", async () => {
      const defaultEntry = { programId: new anchor.web3.PublicKey() };
      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, 108);
          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, 107);
          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(new anchor.web3.PublicKey()));
      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: 0x78";
          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, 109);
          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));
    });
  });
  