Skip to content

Commit 60c97fd

Browse files
authored
[solana] Add frozen vester check in transfer_vesting (#263)
* Add frozen vester check in transfer_vesting * Add test for transfer_vesting with frozen account
1 parent 0b0cdfb commit 60c97fd

File tree

7 files changed

+86
-4
lines changed

7 files changed

+86
-4
lines changed

solana/Cargo.lock

+1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

solana/programs/staking/Cargo.toml

+1-1
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ anchor-lang = { version = "0.30.1", git = "https://github.com/coral-xyz/anchor/"
2929
] }
3030
anchor-spl = { version = "0.30.1", git = "https://github.com/coral-xyz/anchor/", rev = "06527e57c3e59683c36eb0a5c69ee669100b42e5"}
3131
anchor-attribute-error= { version = "0.30.1", git = "https://github.com/coral-xyz/anchor/", rev = "06527e57c3e59683c36eb0a5c69ee669100b42e5"}
32-
32+
spl-token = { version = "4.0", features = ["no-entrypoint"] }
3333
wasm-bindgen = "0.2.93"
3434
js-sys = { version = "0.3.70", optional = true }
3535
bincode = { version = "1.3.3", optional = true }

solana/programs/staking/src/contexts/transfer_vesting.rs

+6
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ use crate::{error::ErrorCode, error::VestingError};
1010
use anchor_lang::prelude::*;
1111
use anchor_spl::associated_token::AssociatedToken;
1212
use anchor_spl::token::{Mint, Token, TokenAccount};
13+
use spl_token::state::AccountState;
1314

1415
#[event_cpi]
1516
#[derive(Accounts)]
@@ -115,6 +116,11 @@ impl<'info> crate::contexts::TransferVesting<'info> {
115116
new_stake_account_metadata: None,
116117
};
117118

119+
// Check if vester_ta is frozen
120+
if self.vester_ta.state == AccountState::Frozen {
121+
return err!(VestingError::FrozenVesterAccount);
122+
}
123+
118124
// Self transfers are not allowed
119125
if self.new_vester_ta.owner.key() == self.vester_ta.owner.key() {
120126
return err!(VestingError::TransferVestToMyself);

solana/programs/staking/src/error.rs

+2
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,8 @@ pub enum VestingError {
105105
StakeAccountDelegatesMismatch,
106106
#[msg("Stake account delegation loop detected")]
107107
StakeAccountDelegationLoop,
108+
#[msg("Cannot transfer vesting from a frozen token account")]
109+
FrozenVesterAccount,
108110
}
109111

110112
#[error_code]

solana/tests/config.ts

+1-2
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,6 @@ describe("config", async () => {
4141
const whMintAccount = new Keypair();
4242
const whMintAuthority = new Keypair();
4343
const randomUser = new Keypair();
44-
const zeroPubkey = new PublicKey(0);
4544

4645
const vestingAdminKeypair = new Keypair();
4746
const vestingAdmin = vestingAdminKeypair.publicKey;
@@ -68,7 +67,7 @@ describe("config", async () => {
6867
program.provider,
6968
whMintAccount,
7069
whMintAuthority.publicKey,
71-
null,
70+
whMintAuthority.publicKey,
7271
WH_TOKEN_DECIMALS,
7372
);
7473

solana/tests/utils/before.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -399,7 +399,7 @@ export async function standardSetup(
399399
provider,
400400
whMintAccount,
401401
whMintAuthority.publicKey,
402-
null,
402+
whMintAuthority.publicKey,
403403
WH_TOKEN_DECIMALS,
404404
);
405405

solana/tests/vesting.ts

+74
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,10 @@ import {
1111
ASSOCIATED_TOKEN_PROGRAM_ID,
1212
createAssociatedTokenAccountIdempotentInstruction,
1313
createAssociatedTokenAccountInstruction,
14+
createFreezeAccountInstruction,
1415
createInitializeMintInstruction,
1516
createMintToInstruction,
17+
createThawAccountInstruction,
1618
createTransferCheckedInstruction,
1719
getAccount,
1820
getAssociatedTokenAddressSync,
@@ -3675,6 +3677,78 @@ describe("vesting", () => {
36753677
}
36763678
});
36773679

3680+
it("should fail to transfer vest if vester_ta is frozen", async () => {
3681+
let freezeTx = new Transaction();
3682+
freezeTx.add(
3683+
createFreezeAccountInstruction(
3684+
vester3Ta,
3685+
whMintAccount.publicKey,
3686+
whMintAuthority.publicKey,
3687+
)
3688+
);
3689+
await stakeConnection.provider.sendAndConfirm(freezeTx, [whMintAuthority]);
3690+
3691+
let vester3TaAccount = await getAccount(stakeConnection.provider.connection, vester3Ta);
3692+
assert.equal(vester3TaAccount.isFrozen, true, "vester3Ta should be frozen");
3693+
3694+
let stakeAccountMetadataAddress = await vester3StakeConnection.getStakeMetadataAddress(
3695+
vester3.publicKey
3696+
);
3697+
let newVester3StakeAccountMetadataAddress =
3698+
await newVester3StakeConnection.getStakeMetadataAddress(
3699+
newVester3.publicKey,
3700+
);
3701+
3702+
let newVester3Vest = PublicKey.findProgramAddressSync(
3703+
[
3704+
Buffer.from(wasm.Constants.VEST_SEED()),
3705+
config.toBuffer(),
3706+
newVester3Ta.toBuffer(),
3707+
FEW_LATER.toBuffer("le", 8),
3708+
],
3709+
stakeConnection.program.programId
3710+
)[0];
3711+
3712+
try {
3713+
await vesterStakeConnection.program.methods
3714+
.transferVesting()
3715+
.accounts({
3716+
...accounts,
3717+
vester: vester3.publicKey,
3718+
vest: vest3NowForTransfer,
3719+
vestingBalance: vesting3Balance,
3720+
delegateStakeAccountCheckpoints: null,
3721+
delegateStakeAccountMetadata: null,
3722+
stakeAccountMetadata: stakeAccountMetadataAddress,
3723+
newStakeAccountMetadata: newVester3StakeAccountMetadataAddress,
3724+
vesterTa: vester3Ta,
3725+
newVesterTa: newVester3Ta,
3726+
newVest: newVester3Vest,
3727+
newVestingBalance: newVesting3Balance,
3728+
globalConfig: vester3StakeConnection.configAddress,
3729+
})
3730+
.signers([vester3])
3731+
.rpc()
3732+
.then(confirm);
3733+
3734+
assert.fail("Expected error was not thrown");
3735+
} catch (e) {
3736+
assert(
3737+
(e as AnchorError).error?.errorCode?.code === "FrozenVesterAccount",
3738+
);
3739+
}
3740+
3741+
let thawTx = new Transaction();
3742+
thawTx.add(
3743+
createThawAccountInstruction(
3744+
vester3Ta,
3745+
whMintAccount.publicKey,
3746+
whMintAuthority.publicKey,
3747+
)
3748+
);
3749+
await stakeConnection.provider.sendAndConfirm(thawTx, [whMintAuthority]);
3750+
});
3751+
36783752
it("should fail to transfer vest if the sender hasn't delegated, but the recipient has", async () => {});
36793753

36803754
it("should successfully transfer a vest when both the sender and recipient haven't delegated", async () => {});

0 commit comments

Comments
 (0)