diff --git a/solana/programs/staking/src/contexts/close_vesting_balance.rs b/solana/programs/staking/src/contexts/close_vesting_balance.rs new file mode 100644 index 00000000..94a8a22e --- /dev/null +++ b/solana/programs/staking/src/contexts/close_vesting_balance.rs @@ -0,0 +1,44 @@ +use crate::context::{VESTING_BALANCE_SEED, VESTING_CONFIG_SEED}; +use crate::error::VestingError; +use crate::state::{VestingBalance, VestingConfig}; +use anchor_lang::prelude::*; +use anchor_spl::associated_token::AssociatedToken; +use anchor_spl::token_interface::{Mint, TokenAccount, TokenInterface}; + +#[derive(Accounts)] +#[instruction()] +pub struct CloseVestingBalance<'info> { + #[account(mut)] + /// CHECK: This account is the original rent_payer for the vesting_balance account + rent_payer: Signer<'info>, + mint: InterfaceAccount<'info, Mint>, + #[account( + seeds = [VESTING_CONFIG_SEED.as_bytes(), mint.key().as_ref(), config.seed.to_le_bytes().as_ref()], + bump = config.bump + )] + config: Account<'info, VestingConfig>, + #[account( + mut, + has_one = rent_payer, + constraint = vesting_balance.total_vesting_balance == 0 @ VestingError::NotFullyVested, + seeds = [VESTING_BALANCE_SEED.as_bytes(), config.key().as_ref(), vester_ta.owner.key().as_ref()], + bump, + close = rent_payer, + )] + vesting_balance: Account<'info, VestingBalance>, + #[account( + associated_token::mint = mint, + associated_token::authority = vester_ta.owner, + associated_token::token_program = token_program + )] + vester_ta: InterfaceAccount<'info, TokenAccount>, + associated_token_program: Program<'info, AssociatedToken>, + token_program: Interface<'info, TokenInterface>, + system_program: Program<'info, System>, +} + +impl<'info> CloseVestingBalance<'info> { + pub fn close_vesting_balance(&mut self) -> Result<()> { + Ok(()) + } +} diff --git a/solana/programs/staking/src/contexts/create_vesting_balance.rs b/solana/programs/staking/src/contexts/create_vesting_balance.rs index 098f0d6e..d792c578 100644 --- a/solana/programs/staking/src/contexts/create_vesting_balance.rs +++ b/solana/programs/staking/src/contexts/create_vesting_balance.rs @@ -53,6 +53,7 @@ impl<'info> CreateVestingBalance<'info> { stake_account_metadata: Pubkey::default(), total_vesting_balance: 0, bump, + rent_payer: self.admin.key(), }); Ok(()) diff --git a/solana/programs/staking/src/contexts/mod.rs b/solana/programs/staking/src/contexts/mod.rs index 414ca91e..c8cc3515 100644 --- a/solana/programs/staking/src/contexts/mod.rs +++ b/solana/programs/staking/src/contexts/mod.rs @@ -15,8 +15,10 @@ pub use cancel_vesting::*; pub mod withdraw_surplus; pub use withdraw_surplus::*; +mod close_vesting_balance; mod create_vesting_balance; +pub use close_vesting_balance::*; pub use create_vesting_balance::*; pub mod transfer_vesting; diff --git a/solana/programs/staking/src/contexts/transfer_vesting.rs b/solana/programs/staking/src/contexts/transfer_vesting.rs index f28a8c18..20d45238 100644 --- a/solana/programs/staking/src/contexts/transfer_vesting.rs +++ b/solana/programs/staking/src/contexts/transfer_vesting.rs @@ -319,6 +319,11 @@ impl<'info> crate::contexts::TransferVesting<'info> { .checked_add(self.vest.amount) .ok_or(VestingError::Overflow)?, bump: new_vesting_balance_bump, + rent_payer: if self.new_vesting_balance.rent_payer == Pubkey::default() { + self.vester.key() + } else { + self.new_vesting_balance.rent_payer + }, }); self.vesting_balance.total_vesting_balance = self diff --git a/solana/programs/staking/src/lib.rs b/solana/programs/staking/src/lib.rs index a0048091..9a74cb9d 100644 --- a/solana/programs/staking/src/lib.rs +++ b/solana/programs/staking/src/lib.rs @@ -698,6 +698,11 @@ pub mod staking { .create_vesting_balance(ctx.bumps.vesting_balance) } + // Closes a vesting balance account + pub fn close_vesting_balance(ctx: Context) -> Result<()> { + ctx.accounts.close_vesting_balance() + } + // Finalize a Config, disabling any further creation or cancellation of Vesting accounts pub fn finalize_vesting_config(ctx: Context) -> Result<()> { ctx.accounts.finalize() diff --git a/solana/programs/staking/src/state/vesting_balance.rs b/solana/programs/staking/src/state/vesting_balance.rs index d86c747d..5c017a30 100644 --- a/solana/programs/staking/src/state/vesting_balance.rs +++ b/solana/programs/staking/src/state/vesting_balance.rs @@ -9,6 +9,7 @@ pub struct VestingBalance { pub total_vesting_balance: u64, pub bump: u8, pub stake_account_metadata: Pubkey, + pub rent_payer: Pubkey, } impl VestingBalance { @@ -21,6 +22,6 @@ pub mod tests { #[test] fn check_size() { - assert!(VestingBalance::LEN == 8 + 32 + 8 + 1 + 32); // 81 + assert!(VestingBalance::LEN == 8 + 32 + 8 + 1 + 32 + 32); // 113 } } diff --git a/solana/tests/vesting.ts b/solana/tests/vesting.ts index bd6d0345..c3853495 100644 --- a/solana/tests/vesting.ts +++ b/solana/tests/vesting.ts @@ -478,12 +478,13 @@ describe("vesting", () => { it("Airdrop", async () => { let tx = new Transaction(); tx.instructions = [ - ...[whMintAuthority, vester, vester2, vester3, fakeVestingAdmin].map((k) => - SystemProgram.transfer({ - fromPubkey: stakeConnection.provider.publicKey, - toPubkey: k.publicKey, - lamports: 10 * LAMPORTS_PER_SOL, - }), + ...[whMintAuthority, vester, vester2, vester3, fakeVestingAdmin].map( + (k) => + SystemProgram.transfer({ + fromPubkey: stakeConnection.provider.publicKey, + toPubkey: k.publicKey, + lamports: 10 * LAMPORTS_PER_SOL, + }), ), ]; await stakeConnection.provider.sendAndConfirm(tx, [ @@ -691,23 +692,25 @@ describe("vesting", () => { }); it("should fail to create vesting balance with invalid admin", async () => { - try { - await stakeConnection.program.methods - .createVestingBalance() - .accounts({ - ...accounts, - vestingBalance: vestingBalance, - admin: fakeVestingAdmin.publicKey, - }) - .signers([fakeVestingAdmin]) - .rpc() - .then(confirm); - - assert.fail("Expected error was not thrown"); - } catch (e) { - assert((e as AnchorError).error?.errorCode?.code === "InvalidVestingAdmin"); - } - fakeConfig = PublicKey.findProgramAddressSync( + try { + await stakeConnection.program.methods + .createVestingBalance() + .accounts({ + ...accounts, + vestingBalance: vestingBalance, + admin: fakeVestingAdmin.publicKey, + }) + .signers([fakeVestingAdmin]) + .rpc() + .then(confirm); + + assert.fail("Expected error was not thrown"); + } catch (e) { + assert( + (e as AnchorError).error?.errorCode?.code === "InvalidVestingAdmin", + ); + } + fakeConfig = PublicKey.findProgramAddressSync( [ Buffer.from(wasm.Constants.VESTING_CONFIG_SEED()), whMintAccount.publicKey.toBuffer(), @@ -746,6 +749,47 @@ describe("vesting", () => { .then(confirm); }); + it("Close and re-create vesting balance", async () => { + await stakeConnection.program.methods + .closeVestingBalance() + .accounts({ + ...accounts, + vestingBalance: vestingBalance, + rentPayer: whMintAuthority.publicKey, + }) + .signers([whMintAuthority]) + .rpc() + .then(confirm); + + await stakeConnection.program.methods + .createVestingBalance() + .accounts({ + ...accounts, + vestingBalance: vestingBalance, + }) + .signers([whMintAuthority]) + .rpc() + .then(confirm); + }); + + it("should fail to close vesting balance account with incorrect rent payer", async () => { + try { + await stakeConnection.program.methods + .closeVestingBalance() + .accounts({ + ...accounts, + vestingBalance: vestingBalance, + rentPayer: vester.publicKey, + }) + .signers([vester]) + .rpc() + .then(confirm); + assert.fail("Expected error was not thrown"); + } catch (e) { + assert((e as AnchorError).error?.errorCode?.code === "ConstraintHasOne"); + } + }); + it("Create another vesting balance", async () => { await stakeConnection.program.methods .createVestingBalance() @@ -803,6 +847,123 @@ describe("vesting", () => { .then(confirm); }); + it("Close and re-create another vesting balance", async () => { + await stakeConnection.program.methods + .closeVestingBalance() + .accounts({ + ...accounts, + vestingBalance: vesting2Balance, + vesterTa: vester2Ta, + rentPayer: whMintAuthority.publicKey, + }) + .signers([whMintAuthority]) + .rpc() + .then(confirm); + + await stakeConnection.program.methods + .closeVestingBalance() + .accounts({ + ...accounts, + vestingBalance: vesting3Balance, + vesterTa: vester3Ta, + rentPayer: whMintAuthority.publicKey, + }) + .signers([whMintAuthority]) + .rpc() + .then(confirm); + + await stakeConnection.program.methods + .closeVestingBalance() + .accounts({ + ...accounts, + vestingBalance: newVestingBalance, + vesterTa: newVesterTa, + rentPayer: whMintAuthority.publicKey, + }) + .signers([whMintAuthority]) + .rpc() + .then(confirm); + + await stakeConnection.program.methods + .closeVestingBalance() + .accounts({ + ...accounts, + vestingBalance: newVesting2Balance, + vesterTa: newVester2Ta, + rentPayer: whMintAuthority.publicKey, + }) + .signers([whMintAuthority]) + .rpc() + .then(confirm); + + await stakeConnection.program.methods + .closeVestingBalance() + .accounts({ + ...accounts, + config: config2, + vestingBalance: vestingBalance2, + rentPayer: whMintAuthority.publicKey, + }) + .signers([whMintAuthority]) + .rpc() + .then(confirm); + + await stakeConnection.program.methods + .createVestingBalance() + .accounts({ + ...accounts, + vestingBalance: vesting2Balance, + vesterTa: vester2Ta, + }) + .signers([whMintAuthority]) + .rpc() + .then(confirm); + + await stakeConnection.program.methods + .createVestingBalance() + .accounts({ + ...accounts, + vestingBalance: vesting3Balance, + vesterTa: vester3Ta, + }) + .signers([whMintAuthority]) + .rpc() + .then(confirm); + + await stakeConnection.program.methods + .createVestingBalance() + .accounts({ + ...accounts, + vestingBalance: newVestingBalance, + vesterTa: newVesterTa, + }) + .signers([whMintAuthority]) + .rpc() + .then(confirm); + + await stakeConnection.program.methods + .createVestingBalance() + .accounts({ + ...accounts, + vestingBalance: newVesting2Balance, + vesterTa: newVester2Ta, + }) + .signers([whMintAuthority]) + .rpc() + .then(confirm); + + await stakeConnection.program.methods + .createVestingBalance() + .accounts({ + ...accounts, + config: config2, + vestingBalance: vestingBalance2, + }) + .signers([whMintAuthority]) + .rpc() + .then(confirm); + }); + it("should fail to create vest with invalid admin", async () => { try { await stakeConnection.program.methods @@ -818,7 +979,9 @@ describe("vesting", () => { assert.fail("Expected error was not thrown"); } catch (e) { - assert((e as AnchorError).error?.errorCode?.code === "InvalidVestingAdmin"); + assert( + (e as AnchorError).error?.errorCode?.code === "InvalidVestingAdmin", + ); } }); @@ -1065,7 +1228,9 @@ describe("vesting", () => { assert.fail("Expected error was not thrown"); } catch (e) { - assert((e as AnchorError).error?.errorCode?.code === "InvalidVestingAdmin"); + assert( + (e as AnchorError).error?.errorCode?.code === "InvalidVestingAdmin", + ); } }); @@ -1230,9 +1395,7 @@ describe("vesting", () => { ); let delegateeStakeAccountMetadataAddress = - await stakeConnection.getStakeMetadataAddress( - vester.publicKey, - ); + await stakeConnection.getStakeMetadataAddress(vester.publicKey); let delegateeStakeAccountCheckpointsAddress = await stakeConnection.getStakeAccountCheckpointsAddressByMetadata( delegateeStakeAccountMetadataAddress, @@ -1302,9 +1465,7 @@ describe("vesting", () => { ); let delegateeStakeAccountMetadataAddress = - await vesterStakeConnection.getStakeMetadataAddress( - vester.publicKey, - ); + await vesterStakeConnection.getStakeMetadataAddress(vester.publicKey); let delegateeStakeAccountCheckpointsAddress = await vesterStakeConnection.getStakeAccountCheckpointsAddressByMetadata( delegateeStakeAccountMetadataAddress, @@ -1312,9 +1473,7 @@ describe("vesting", () => { ); let currentDelegateStakeAccountOwner = - await vesterStakeConnection.delegates( - vester.publicKey, - ); + await vesterStakeConnection.delegates(vester.publicKey); let currentDelegateStakeAccountMetadataAddress = await vesterStakeConnection.getStakeMetadataAddress( currentDelegateStakeAccountOwner, @@ -1412,9 +1571,7 @@ describe("vesting", () => { ); let delegateeStakeAccountMetadataAddress = - await stakeConnection.getStakeMetadataAddress( - vester.publicKey, - ); + await stakeConnection.getStakeMetadataAddress(vester.publicKey); let delegateeStakeAccountCheckpointsAddress = await stakeConnection.getStakeAccountCheckpointsAddressByMetadata( delegateeStakeAccountMetadataAddress, @@ -1484,17 +1641,14 @@ describe("vesting", () => { assert.fail("Expected error was not thrown"); } catch (e) { assert( - (e as AnchorError).error?.errorCode?.code === - "ErrorOfAccountParsing", + (e as AnchorError).error?.errorCode?.code === "ErrorOfAccountParsing", ); } }); it("should fail to claim without stakeAccountCheckpoints", async () => { let stakeAccountMetadataAddress = - await vesterStakeConnection.getStakeMetadataAddress( - vester.publicKey, - ); + await vesterStakeConnection.getStakeMetadataAddress(vester.publicKey); try { await stakeConnection.program.methods .claimVesting() @@ -1513,8 +1667,7 @@ describe("vesting", () => { assert.fail("Expected error was not thrown"); } catch (e) { assert( - (e as AnchorError).error?.errorCode?.code === - "ErrorOfAccountParsing", + (e as AnchorError).error?.errorCode?.code === "ErrorOfAccountParsing", ); } }); @@ -1556,9 +1709,7 @@ describe("vesting", () => { it("should fail to claim with incorrect stakeAccountCheckpoints ", async () => { let stakeAccountMetadataAddress = - await vesterStakeConnection.getStakeMetadataAddress( - vester.publicKey, - ); + await vesterStakeConnection.getStakeMetadataAddress(vester.publicKey); let incorrectStakeAccountMetadataAddress = await stakeConnection.getStakeMetadataAddress( @@ -1597,9 +1748,7 @@ describe("vesting", () => { it("fails to create a new checkpoints account if the existing checkpoints account is not fully loaded and is used as input", async () => { let stakeAccountMetadataAddress = - await vesterStakeConnection.getStakeMetadataAddress( - vester.publicKey, - ); + await vesterStakeConnection.getStakeMetadataAddress(vester.publicKey); let notFulledStakeAccountCheckpointsAddress = await vesterStakeConnection.getStakeAccountCheckpointsAddressByMetadata( @@ -1644,13 +1793,9 @@ describe("vesting", () => { it("should successfully claim staked vest", async () => { let stakeAccountMetadataAddress = - await stakeConnection.getStakeMetadataAddress( - vester.publicKey, - ); + await stakeConnection.getStakeMetadataAddress(vester.publicKey); let stakeAccountMetadataData = - await stakeConnection.fetchStakeAccountMetadata( - vester.publicKey, - ); + await stakeConnection.fetchStakeAccountMetadata(vester.publicKey); let delegateStakeAccountCheckpointsOwner = stakeAccountMetadataData.delegate; @@ -1681,9 +1826,7 @@ describe("vesting", () => { .then(confirm); let vesterStakeMetadata: StakeAccountMetadata = - await vesterStakeConnection.fetchStakeAccountMetadata( - vester.publicKey, - ); + await vesterStakeConnection.fetchStakeAccountMetadata(vester.publicKey); let vesterStakeCheckpoints: CheckpointAccount = await vesterStakeConnection.fetchCheckpointAccount( @@ -1702,13 +1845,9 @@ describe("vesting", () => { it("should fail to claim if checkpoints account is fulled", async () => { let stakeAccountMetadataAddress = - await vesterStakeConnection.getStakeMetadataAddress( - vester.publicKey, - ); + await vesterStakeConnection.getStakeMetadataAddress(vester.publicKey); let vesterStakeAccountMetadata = - await vesterStakeConnection.fetchStakeAccountMetadata( - vester.publicKey, - ); + await vesterStakeConnection.fetchStakeAccountMetadata(vester.publicKey); // there is only one checkpoint account assert.equal( vesterStakeAccountMetadata.stakeAccountCheckpointsLastIndex, @@ -1749,9 +1888,7 @@ describe("vesting", () => { ); vesterStakeAccountMetadata = - await vesterStakeConnection.fetchStakeAccountMetadata( - vester.publicKey, - ); + await vesterStakeConnection.fetchStakeAccountMetadata(vester.publicKey); // a new checkpoint account must be created assert.equal( vesterStakeAccountMetadata.stakeAccountCheckpointsLastIndex, @@ -1846,10 +1983,7 @@ describe("vesting", () => { let currentDelegate = await vesterStakeConnection.delegates( vester.publicKey, ); - assert.equal( - currentDelegate.toBase58(), - vester.publicKey.toBase58(), - ); + assert.equal(currentDelegate.toBase58(), vester.publicKey.toBase58()); let currentDelegateStakeAccountAddress = await vesterStakeConnection.getStakeMetadataAddress(currentDelegate); let currentDelegateStakeAccountCheckpointsAddress = @@ -1948,9 +2082,7 @@ describe("vesting", () => { it("should successfully create a new checkpoints account", async () => { let stakeAccountMetadataAddress = - await vesterStakeConnection.getStakeMetadataAddress( - vester.publicKey, - ); + await vesterStakeConnection.getStakeMetadataAddress(vester.publicKey); let currentStakeAccountCheckpointsAddress = await vesterStakeConnection.getStakeAccountCheckpointsAddressByMetadata( @@ -1977,9 +2109,7 @@ describe("vesting", () => { ); let vesterStakeMetadata: StakeAccountMetadata = - await vesterStakeConnection.fetchStakeAccountMetadata( - vester.publicKey, - ); + await vesterStakeConnection.fetchStakeAccountMetadata(vester.publicKey); assert.equal( vesterStakeMetadata.recordedVestingBalance.toString(), @@ -2103,9 +2233,8 @@ describe("vesting", () => { let vesterDelegateStakeAccountOwner = await vesterStakeConnection.delegates( vester.publicKey, ); - let newVesterDelegateStakeAccountOwner = await newVesterStakeConnection.delegates( - newVester.publicKey, - ); + let newVesterDelegateStakeAccountOwner = + await newVesterStakeConnection.delegates(newVester.publicKey); assert.notEqual( vesterDelegateStakeAccountOwner.toBase58(), newVesterDelegateStakeAccountOwner.toBase58(), @@ -2129,9 +2258,7 @@ describe("vesting", () => { ); let stakeAccountMetadataAddress = - await vesterStakeConnection.getStakeMetadataAddress( - vester.publicKey, - ); + await vesterStakeConnection.getStakeMetadataAddress(vester.publicKey); let newStakeAccountMetadataAddress = await newVesterStakeConnection.getStakeMetadataAddress( newVester.publicKey, @@ -2167,7 +2294,8 @@ describe("vesting", () => { assert.fail("Expected error was not thrown"); } catch (e) { assert( - (e as AnchorError).error?.errorCode?.code === "StakeAccountDelegatesMismatch", + (e as AnchorError).error?.errorCode?.code === + "StakeAccountDelegatesMismatch", ); } }); @@ -2184,9 +2312,8 @@ describe("vesting", () => { let vesterDelegateStakeAccountOwner = await vesterStakeConnection.delegates( vester.publicKey, ); - let newVesterDelegateStakeAccountOwner = await newVesterStakeConnection.delegates( - newVester.publicKey, - ); + let newVesterDelegateStakeAccountOwner = + await newVesterStakeConnection.delegates(newVester.publicKey); assert.equal( vesterDelegateStakeAccountOwner.toBase58(), newVesterDelegateStakeAccountOwner.toBase58(), @@ -2210,9 +2337,7 @@ describe("vesting", () => { ); let vesterStakeAccountMetadata = - await vesterStakeConnection.fetchStakeAccountMetadata( - vester.publicKey, - ); + await vesterStakeConnection.fetchStakeAccountMetadata(vester.publicKey); let newVesterStakeAccountMetadata = await newVesterStakeConnection.fetchStakeAccountMetadata( newVester.publicKey, @@ -2223,9 +2348,7 @@ describe("vesting", () => { ); let stakeAccountMetadataAddress = - await vesterStakeConnection.getStakeMetadataAddress( - vester.publicKey, - ); + await vesterStakeConnection.getStakeMetadataAddress(vester.publicKey); let newStakeAccountMetadataAddress = await newVesterStakeConnection.getStakeMetadataAddress( newVester.publicKey, @@ -2239,7 +2362,7 @@ describe("vesting", () => { ], stakeConnection.program.programId, )[0]; - + try { await vesterStakeConnection.program.methods .transferVesting() @@ -2260,7 +2383,8 @@ describe("vesting", () => { assert.fail("Expected error was not thrown"); } catch (e) { assert( - (e as AnchorError).error?.errorCode?.code === "StakeAccountDelegationLoop", + (e as AnchorError).error?.errorCode?.code === + "StakeAccountDelegationLoop", ); } @@ -2275,13 +2399,9 @@ describe("vesting", () => { it("should successfully claim staked vest with created checkpoint account", async () => { let stakeAccountMetadataAddress = - await stakeConnection.getStakeMetadataAddress( - vester.publicKey, - ); + await stakeConnection.getStakeMetadataAddress(vester.publicKey); let stakeAccountMetadataData = - await stakeConnection.fetchStakeAccountMetadata( - vester.publicKey, - ); + await stakeConnection.fetchStakeAccountMetadata(vester.publicKey); let delegateStakeAccountCheckpointsOwner = stakeAccountMetadataData.delegate; @@ -2312,9 +2432,7 @@ describe("vesting", () => { .then(confirm); let vesterStakeMetadata: StakeAccountMetadata = - await vesterStakeConnection.fetchStakeAccountMetadata( - vester.publicKey, - ); + await vesterStakeConnection.fetchStakeAccountMetadata(vester.publicKey); let vesterStakeCheckpoints: CheckpointAccount = await vesterStakeConnection.fetchCheckpointAccount( @@ -2334,9 +2452,7 @@ describe("vesting", () => { it("should successfully create a new checkpoints account after claim", async () => { let stakeAccountMetadataAddress = - await vesterStakeConnection.getStakeMetadataAddress( - vester.publicKey, - ); + await vesterStakeConnection.getStakeMetadataAddress(vester.publicKey); let currentStakeAccountCheckpointsAddress = await vesterStakeConnection.getStakeAccountCheckpointsAddressByMetadata( stakeAccountMetadataAddress, @@ -2346,9 +2462,7 @@ describe("vesting", () => { assert.equal(currentStakeAccountCheckpointsAddress, undefined); let vesterStakeAccountMetadata = - await vesterStakeConnection.fetchStakeAccountMetadata( - vester.publicKey, - ); + await vesterStakeConnection.fetchStakeAccountMetadata(vester.publicKey); // a new checkpoint account must be created assert.equal( vesterStakeAccountMetadata.stakeAccountCheckpointsLastIndex, @@ -2396,9 +2510,7 @@ describe("vesting", () => { .then(confirm); vesterStakeAccountMetadata = - await vesterStakeConnection.fetchStakeAccountMetadata( - vester.publicKey, - ); + await vesterStakeConnection.fetchStakeAccountMetadata(vester.publicKey); previousStakeAccountCheckpointsAddress = await vesterStakeConnection.getStakeAccountCheckpointsAddressByMetadata( @@ -2493,9 +2605,7 @@ describe("vesting", () => { it("should fail to transfer without stakeAccountMetadata", async () => { let stakeAccountMetadataAddress = - await vesterStakeConnection.getStakeMetadataAddress( - vester.publicKey, - ); + await vesterStakeConnection.getStakeMetadataAddress(vester.publicKey); let stakeAccountCheckpointsAddress = await vesterStakeConnection.getStakeAccountCheckpointsAddressByMetadata( stakeAccountMetadataAddress, @@ -2527,17 +2637,14 @@ describe("vesting", () => { assert.fail("Expected error was not thrown"); } catch (e) { assert( - (e as AnchorError).error?.errorCode?.code === - "ErrorOfAccountParsing", + (e as AnchorError).error?.errorCode?.code === "ErrorOfAccountParsing", ); } }); it("should fail to transfer vest to myself", async () => { let stakeAccountMetadataAddress = - await vesterStakeConnection.getStakeMetadataAddress( - vester.publicKey, - ); + await vesterStakeConnection.getStakeMetadataAddress(vester.publicKey); let stakeAccountCheckpointsAddress = await vesterStakeConnection.getStakeAccountCheckpointsAddressByMetadata( stakeAccountMetadataAddress, @@ -2575,9 +2682,7 @@ describe("vesting", () => { it("should successfully transfer vest to another vester", async () => { let stakeAccountMetadataAddress = - await vesterStakeConnection.getStakeMetadataAddress( - vester.publicKey, - ); + await vesterStakeConnection.getStakeMetadataAddress(vester.publicKey); let newStakeAccountMetadataAddress = await newVesterStakeConnection.getStakeMetadataAddress( newVester.publicKey, @@ -2622,18 +2727,21 @@ describe("vesting", () => { vester.publicKey, ); let vesterDelegateStakeAccountMetadataAddress = - await vesterStakeConnection.getStakeMetadataAddress(vesterDelegateStakeAccountOwner); + await vesterStakeConnection.getStakeMetadataAddress( + vesterDelegateStakeAccountOwner, + ); let vesterDelegateStakeAccountCheckpointsAddress = await vesterStakeConnection.getStakeAccountCheckpointsAddressByMetadata( vesterDelegateStakeAccountMetadataAddress, false, ); - let newVesterDelegateStakeAccountOwner = await newVesterStakeConnection.delegates( - newVester.publicKey, - ); + let newVesterDelegateStakeAccountOwner = + await newVesterStakeConnection.delegates(newVester.publicKey); let newVesterDelegateStakeAccountMetadataAddress = - await newVesterStakeConnection.getStakeMetadataAddress(newVesterDelegateStakeAccountOwner); + await newVesterStakeConnection.getStakeMetadataAddress( + newVesterDelegateStakeAccountOwner, + ); let newVesterDelegateStakeAccountCheckpointsAddress = await newVesterStakeConnection.getStakeAccountCheckpointsAddressByMetadata( newVesterDelegateStakeAccountMetadataAddress, @@ -2652,7 +2760,8 @@ describe("vesting", () => { .createCheckpoints() .accounts({ payer: accounts.vester, - stakeAccountCheckpoints: previousNewVesterDelegateStakeAccountCheckpointsAddress, + stakeAccountCheckpoints: + previousNewVesterDelegateStakeAccountCheckpointsAddress, stakeAccountMetadata: newVesterDelegateStakeAccountMetadataAddress, }) .signers([vester]) @@ -2669,7 +2778,10 @@ describe("vesting", () => { let tx = new Transaction(); tx.instructions = [ await vesterStakeConnection.program.methods - .delegate(newVesterDelegateStakeAccountOwner, vesterDelegateStakeAccountOwner) + .delegate( + newVesterDelegateStakeAccountOwner, + vesterDelegateStakeAccountOwner, + ) .accountsPartial({ payer: vester.publicKey, currentDelegateStakeAccountCheckpoints: @@ -2697,7 +2809,10 @@ describe("vesting", () => { }) .instruction(), await vesterStakeConnection.program.methods - .delegate(vesterDelegateStakeAccountOwner, newVesterDelegateStakeAccountOwner) + .delegate( + vesterDelegateStakeAccountOwner, + newVesterDelegateStakeAccountOwner, + ) .accountsPartial({ payer: vester.publicKey, currentDelegateStakeAccountCheckpoints: @@ -2731,9 +2846,7 @@ describe("vesting", () => { ); let vesterStakeMetadata: StakeAccountMetadata = - await vesterStakeConnection.fetchStakeAccountMetadata( - vester.publicKey, - ); + await vesterStakeConnection.fetchStakeAccountMetadata(vester.publicKey); let stakeAccountCheckpointsAddress = await vesterStakeConnection.getStakeAccountCheckpointsAddressByMetadata( stakeAccountMetadataAddress, @@ -2822,9 +2935,7 @@ describe("vesting", () => { it("should successfully transfer vest to another vester with votes delegated to another user", async () => { let vester2StakeAccountMetadataAddress = - await vester2StakeConnection.getStakeMetadataAddress( - vester2.publicKey, - ); + await vester2StakeConnection.getStakeMetadataAddress(vester2.publicKey); let vester2StakeAccountCheckpointsAddress = await vester2StakeConnection.getStakeAccountCheckpointsAddressByMetadata( vester2StakeAccountMetadataAddress, @@ -2928,9 +3039,7 @@ describe("vesting", () => { ); let vester2StakeMetadata: StakeAccountMetadata = - await vester2StakeConnection.fetchStakeAccountMetadata( - vester2.publicKey, - ); + await vester2StakeConnection.fetchStakeAccountMetadata(vester2.publicKey); let vester2StakeCheckpoints: CheckpointAccount = await vester2StakeConnection.fetchCheckpointAccount( vester2StakeAccountCheckpointsAddress, @@ -3144,9 +3253,7 @@ describe("vesting", () => { it("should successfully transfer vest to another vester with an existing vest of the same kind", async () => { let stakeAccountMetadataAddress = - await vesterStakeConnection.getStakeMetadataAddress( - vester.publicKey, - ); + await vesterStakeConnection.getStakeMetadataAddress(vester.publicKey); let stakeAccountCheckpointsAddress = await vesterStakeConnection.getStakeAccountCheckpointsAddressByMetadata( stakeAccountMetadataAddress, @@ -3178,18 +3285,21 @@ describe("vesting", () => { vester.publicKey, ); let vesterDelegateStakeAccountMetadataAddress = - await vesterStakeConnection.getStakeMetadataAddress(vesterDelegateStakeAccountOwner); + await vesterStakeConnection.getStakeMetadataAddress( + vesterDelegateStakeAccountOwner, + ); let vesterDelegateStakeAccountCheckpointsAddress = await vesterStakeConnection.getStakeAccountCheckpointsAddressByMetadata( vesterDelegateStakeAccountMetadataAddress, false, ); - let newVesterDelegateStakeAccountOwner = await newVesterStakeConnection.delegates( - newVester.publicKey, - ); + let newVesterDelegateStakeAccountOwner = + await newVesterStakeConnection.delegates(newVester.publicKey); let newVesterDelegateStakeAccountMetadataAddress = - await newVesterStakeConnection.getStakeMetadataAddress(newVesterDelegateStakeAccountOwner); + await newVesterStakeConnection.getStakeMetadataAddress( + newVesterDelegateStakeAccountOwner, + ); let newVesterDelegateStakeAccountCheckpointsAddress = await newVesterStakeConnection.getStakeAccountCheckpointsAddressByMetadata( newVesterDelegateStakeAccountMetadataAddress, @@ -3199,7 +3309,10 @@ describe("vesting", () => { let tx = new Transaction(); tx.instructions = [ await vesterStakeConnection.program.methods - .delegate(newVesterDelegateStakeAccountOwner, vesterDelegateStakeAccountOwner) + .delegate( + newVesterDelegateStakeAccountOwner, + vesterDelegateStakeAccountOwner, + ) .accountsPartial({ payer: vester.publicKey, currentDelegateStakeAccountCheckpoints: @@ -3227,7 +3340,10 @@ describe("vesting", () => { }) .instruction(), await vesterStakeConnection.program.methods - .delegate(vesterDelegateStakeAccountOwner, newVesterDelegateStakeAccountOwner) + .delegate( + vesterDelegateStakeAccountOwner, + newVesterDelegateStakeAccountOwner, + ) .accountsPartial({ payer: vester.publicKey, currentDelegateStakeAccountCheckpoints: @@ -3261,9 +3377,7 @@ describe("vesting", () => { ); let vesterStakeMetadata: StakeAccountMetadata = - await vesterStakeConnection.fetchStakeAccountMetadata( - vester.publicKey, - ); + await vesterStakeConnection.fetchStakeAccountMetadata(vester.publicKey); let vesterStakeCheckpoints: CheckpointAccount = await vesterStakeConnection.fetchCheckpointAccount( stakeAccountCheckpointsAddress, @@ -3428,6 +3542,10 @@ describe("vesting", () => { updatedNewVestingBalance.vester.toString("hex"), vesterWithoutAccount.publicKey.toString("hex"), ); + assert.equal( + updatedNewVestingBalance.rentPayer.toString("hex"), + newVester.publicKey.toString("hex"), + ); let newVesterStakeCheckpointsAfter: CheckpointAccount = await newVesterStakeConnection.fetchCheckpointAccount( @@ -3439,11 +3557,28 @@ describe("vesting", () => { ); }); + it("should fail to close vesting balance account when balance is not 0", async () => { + try { + await stakeConnection.program.methods + .closeVestingBalance() + .accounts({ + ...accounts, + rentPayer: newVester.publicKey, + vestingBalance: vestingBalanceWithoutAccount, + vesterTa: vesterTaWithoutAccount, + }) + .signers([newVester]) + .rpc() + .then(confirm); + assert.fail("Expected error was not thrown"); + } catch (e) { + assert((e as AnchorError).error?.errorCode?.code === "NotFullyVested"); + } + }); + it("should fail to transfer vest if newVestingBalance has stake account metadata and vestingBalance has no stake account metadata", async () => { let stakeAccountMetadataAddress = - await vester3StakeConnection.getStakeMetadataAddress( - vester3.publicKey, - ); + await vester3StakeConnection.getStakeMetadataAddress(vester3.publicKey); let stakeAccountCheckpointsAddress = await vester3StakeConnection.getStakeAccountCheckpointsAddressByMetadata( stakeAccountMetadataAddress, @@ -3495,12 +3630,7 @@ describe("vesting", () => { } }); - it("should fail to transfer vest if the sender hasn't delegated, but the recipient has", async () => { - - }); - - it("should successfully transfer a vest when both the sender and recipient haven't delegated", async () => { - - }); + it("should fail to transfer vest if the sender hasn't delegated, but the recipient has", async () => {}); + it("should successfully transfer a vest when both the sender and recipient haven't delegated", async () => {}); });