stake/tests/lockup.js

947 lines
29 KiB
JavaScript

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();
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 = new anchor.web3.Account();
await lockup.state.rpc.setAuthority(newAuthority.publicKey, {
accounts: {
authority: provider.wallet.publicKey,
},
});
let lockupAccount = await lockup.state();
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();
assert.ok(lockupAccount.authority.equals(provider.wallet.publicKey));
});
const entries = [];
it("Adds to the whitelist", async () => {
const generateEntry = async () => {
let programId = new anchor.web3.Account().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();
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();
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();
assert.deepEqual(lockupAccount.whitelist, entries.slice(1));
});
const vesting = new anchor.web3.Account();
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 = new anchor.web3.Account();
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(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(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 = new anchor.web3.Account();
const rewardQ = new anchor.web3.Account();
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();
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(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 = new anchor.web3.Account();
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(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 = new anchor.web3.Account();
const unlockedVendorVault = new anchor.web3.Account();
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(
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(
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(member.publicKey);
assert.ok(memberAccount.rewardsCursor == 1);
});
const lockedVendor = new anchor.web3.Account();
const lockedVendorVault = new anchor.web3.Account();
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(
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(
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 = new anchor.web3.Account();
vendoredVestingVault = new anchor.web3.Account();
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(
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 = new anchor.web3.Account();
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));
});
});