lockup: Cleanup
This commit is contained in:
parent
030037eeed
commit
e1d88669d3
|
@ -4,6 +4,8 @@
|
|||
pub mod client;
|
||||
#[macro_use]
|
||||
pub mod pack;
|
||||
#[cfg(feature = "program")]
|
||||
pub mod program;
|
||||
|
||||
// TODO: Import the shared_mem crate instead of hardcoding here.
|
||||
// The shared memory program is awaiting audit and so is not deployed
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
use serum_registry::error::RegistryError;
|
||||
use solana_sdk::account_info::AccountInfo;
|
||||
use solana_sdk::program_error::ProgramError;
|
||||
use spl_token::instruction as token_instruction;
|
||||
use std::convert::Into;
|
||||
|
||||
|
@ -10,7 +10,7 @@ pub fn invoke_token_transfer<'a, 'b>(
|
|||
tok_program_acc_info: &'a AccountInfo<'b>,
|
||||
signer_seeds: &[&[&[u8]]],
|
||||
amount: u64,
|
||||
) -> Result<(), RegistryError> {
|
||||
) -> Result<(), ProgramError> {
|
||||
let transfer_instr = token_instruction::transfer(
|
||||
&spl_token::ID,
|
||||
from_acc_info.key,
|
||||
|
@ -29,5 +29,4 @@ pub fn invoke_token_transfer<'a, 'b>(
|
|||
],
|
||||
signer_seeds,
|
||||
)
|
||||
.map_err(Into::into)
|
||||
}
|
|
@ -59,12 +59,12 @@ pub enum SubCommand {
|
|||
#[clap(short = 'a', long)]
|
||||
deposit_amount: u64,
|
||||
},
|
||||
/// Redeem a claimed token receipt for an amount of vested tokens.
|
||||
Redeem {
|
||||
/// The amount of vested tokens to redeem.
|
||||
/// Withdraw vested tokens.
|
||||
Withdraw {
|
||||
/// The amount of vested tokens to withdraw.
|
||||
#[clap(short, long)]
|
||||
amount: u64,
|
||||
/// Vesting account to redeem from.
|
||||
/// Vesting account to withdraw from.
|
||||
#[clap(short, long)]
|
||||
vesting: Pubkey,
|
||||
/// Token account to send the vested tokens to.
|
||||
|
@ -171,7 +171,7 @@ pub fn run(opts: Opts) -> Result<()> {
|
|||
println!("{:#?}", resp);
|
||||
Ok(())
|
||||
}
|
||||
SubCommand::Redeem {
|
||||
SubCommand::Withdraw {
|
||||
vesting,
|
||||
amount,
|
||||
token_account,
|
||||
|
@ -180,7 +180,7 @@ pub fn run(opts: Opts) -> Result<()> {
|
|||
let client = ctx.connect::<Client>(opts.cmd.pid)?;
|
||||
let vesting_account = client.vesting(&vesting)?;
|
||||
let safe = vesting_account.safe;
|
||||
let resp = client.redeem(RedeemRequest {
|
||||
let resp = client.withdraw(WithdrawRequest {
|
||||
beneficiary: &beneficiary,
|
||||
vesting,
|
||||
token_account,
|
||||
|
@ -207,7 +207,7 @@ fn account_cmd(ctx: &Context, pid: Pubkey, cmd: AccountsCommand) -> Result<()> {
|
|||
|
||||
let current_ts = client.rpc().get_block_time(client.rpc().get_slot()?)?;
|
||||
let amount = vault.available_for_withdrawal(current_ts);
|
||||
println!("Redeemable balance: {:?}", amount);
|
||||
println!("Withdrawable balance: {:?}", amount);
|
||||
println!("Whitelistable balance: {:?}", amount);
|
||||
|
||||
Ok(())
|
||||
|
|
|
@ -108,6 +108,7 @@ impl Client {
|
|||
safe,
|
||||
whitelist_program,
|
||||
mut relay_accounts,
|
||||
whitelist_program_vault,
|
||||
whitelist_program_vault_authority,
|
||||
delegate_amount,
|
||||
relay_data,
|
||||
|
@ -123,7 +124,6 @@ impl Client {
|
|||
AccountMeta::new_readonly(safe, false),
|
||||
AccountMeta::new_readonly(whitelist, false),
|
||||
AccountMeta::new_readonly(whitelist_program, false),
|
||||
AccountMeta::new_readonly(whitelist_program_vault_authority, false),
|
||||
// Below are relay accounts.
|
||||
AccountMeta::new(vault, false),
|
||||
AccountMeta::new_readonly(
|
||||
|
@ -131,6 +131,8 @@ impl Client {
|
|||
false,
|
||||
),
|
||||
AccountMeta::new_readonly(spl_token::ID, false),
|
||||
AccountMeta::new(whitelist_program_vault, false),
|
||||
AccountMeta::new_readonly(whitelist_program_vault_authority, false),
|
||||
];
|
||||
accounts.append(&mut relay_accounts);
|
||||
|
||||
|
@ -156,6 +158,7 @@ impl Client {
|
|||
vesting,
|
||||
safe,
|
||||
whitelist_program,
|
||||
whitelist_program_vault,
|
||||
whitelist_program_vault_authority,
|
||||
relay_data,
|
||||
mut relay_accounts,
|
||||
|
@ -180,6 +183,7 @@ impl Client {
|
|||
false,
|
||||
),
|
||||
AccountMeta::new_readonly(spl_token::ID, false),
|
||||
AccountMeta::new(whitelist_program_vault, false),
|
||||
AccountMeta::new_readonly(whitelist_program_vault_authority, false),
|
||||
];
|
||||
// Program specific relay.
|
||||
|
@ -195,8 +199,8 @@ impl Client {
|
|||
Ok(WhitelistDepositResponse { tx })
|
||||
}
|
||||
|
||||
pub fn redeem(&self, req: RedeemRequest) -> Result<RedeemResponse, ClientError> {
|
||||
let RedeemRequest {
|
||||
pub fn withdraw(&self, req: WithdrawRequest) -> Result<WithdrawResponse, ClientError> {
|
||||
let WithdrawRequest {
|
||||
beneficiary,
|
||||
vesting,
|
||||
token_account,
|
||||
|
@ -221,8 +225,8 @@ impl Client {
|
|||
let signers = [self.payer(), &beneficiary];
|
||||
let tx = self
|
||||
.inner
|
||||
.redeem_with_signers(&signers, &accounts, amount)?;
|
||||
Ok(RedeemResponse { tx })
|
||||
.withdraw_with_signers(&signers, &accounts, amount)?;
|
||||
Ok(WithdrawResponse { tx })
|
||||
}
|
||||
|
||||
pub fn set_authority(
|
||||
|
@ -281,7 +285,7 @@ impl Client {
|
|||
®istry_pid,
|
||||
)
|
||||
.map_err(|_| anyhow!("unable to create vault authority"))?;
|
||||
let vault = match is_mega {
|
||||
let whitelist_program_vault = match is_mega {
|
||||
false => r.vault,
|
||||
true => r.mega_vault,
|
||||
};
|
||||
|
@ -291,7 +295,6 @@ impl Client {
|
|||
AccountMeta::new(entity, false),
|
||||
AccountMeta::new_readonly(registrar, false),
|
||||
AccountMeta::new_readonly(solana_sdk::sysvar::clock::ID, false),
|
||||
AccountMeta::new(vault, false),
|
||||
];
|
||||
let (pool_accs, _) = r_client.common_pool_accounts(pool_program_id, registrar, false)?;
|
||||
relay_accounts.extend_from_slice(&pool_accs);
|
||||
|
@ -302,6 +305,7 @@ impl Client {
|
|||
safe,
|
||||
whitelist_program: registry_pid,
|
||||
relay_accounts,
|
||||
whitelist_program_vault,
|
||||
whitelist_program_vault_authority,
|
||||
delegate_amount: amount,
|
||||
relay_data,
|
||||
|
@ -346,7 +350,7 @@ impl Client {
|
|||
®istry_pid,
|
||||
)
|
||||
.map_err(|_| anyhow!("unable to create vault authority"))?;
|
||||
let vault = match is_mega {
|
||||
let whitelist_program_vault = match is_mega {
|
||||
false => r.vault,
|
||||
true => r.mega_vault,
|
||||
};
|
||||
|
@ -356,7 +360,6 @@ impl Client {
|
|||
AccountMeta::new(entity, false),
|
||||
AccountMeta::new_readonly(registrar, false),
|
||||
AccountMeta::new_readonly(solana_sdk::sysvar::clock::ID, false),
|
||||
AccountMeta::new(vault, false),
|
||||
];
|
||||
let (pool_accs, _) = r_client.common_pool_accounts(pool_program_id, registrar, is_mega)?;
|
||||
relay_accounts.extend_from_slice(&pool_accs);
|
||||
|
@ -366,6 +369,7 @@ impl Client {
|
|||
vesting,
|
||||
safe,
|
||||
whitelist_program: registry_pid,
|
||||
whitelist_program_vault,
|
||||
whitelist_program_vault_authority,
|
||||
relay_data,
|
||||
relay_accounts,
|
||||
|
@ -509,9 +513,10 @@ pub struct WhitelistWithdrawRequest<'a> {
|
|||
pub vesting: Pubkey,
|
||||
pub safe: Pubkey,
|
||||
pub whitelist_program: Pubkey,
|
||||
pub relay_accounts: Vec<AccountMeta>,
|
||||
pub whitelist_program_vault: Pubkey,
|
||||
pub whitelist_program_vault_authority: Pubkey,
|
||||
pub delegate_amount: u64,
|
||||
pub relay_accounts: Vec<AccountMeta>,
|
||||
pub relay_data: Vec<u8>,
|
||||
pub relay_signers: Vec<&'a Keypair>,
|
||||
}
|
||||
|
@ -526,6 +531,7 @@ pub struct WhitelistDepositRequest<'a> {
|
|||
pub vesting: Pubkey,
|
||||
pub safe: Pubkey,
|
||||
pub whitelist_program: Pubkey,
|
||||
pub whitelist_program_vault: Pubkey,
|
||||
pub whitelist_program_vault_authority: Pubkey,
|
||||
pub relay_accounts: Vec<AccountMeta>,
|
||||
pub relay_data: Vec<u8>,
|
||||
|
@ -537,7 +543,7 @@ pub struct WhitelistDepositResponse {
|
|||
pub tx: Signature,
|
||||
}
|
||||
|
||||
pub struct RedeemRequest<'a> {
|
||||
pub struct WithdrawRequest<'a> {
|
||||
pub beneficiary: &'a Keypair,
|
||||
pub vesting: Pubkey,
|
||||
pub token_account: Pubkey,
|
||||
|
@ -546,7 +552,7 @@ pub struct RedeemRequest<'a> {
|
|||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct RedeemResponse {
|
||||
pub struct WithdrawResponse {
|
||||
pub tx: Signature,
|
||||
}
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
use crate::access_control;
|
||||
use crate::common::access_control;
|
||||
use serum_common::pack::Pack;
|
||||
use serum_lockup::accounts::Vesting;
|
||||
use serum_lockup::error::LockupError;
|
||||
|
@ -6,6 +6,7 @@ use solana_program::info;
|
|||
use solana_sdk::account_info::{next_account_info, AccountInfo};
|
||||
use solana_sdk::pubkey::Pubkey;
|
||||
|
||||
// Convenience instruction for UI's.
|
||||
pub fn handler(_program_id: &Pubkey, accounts: &[AccountInfo]) -> Result<(), LockupError> {
|
||||
let acc_infos = &mut accounts.iter();
|
||||
|
||||
|
|
|
@ -48,14 +48,13 @@ pub fn whitelist<'a>(
|
|||
Ok(wl)
|
||||
}
|
||||
|
||||
/// Access control on any instruction mutating an existing Vesting account.
|
||||
pub fn vesting(
|
||||
program_id: &Pubkey,
|
||||
safe: &Pubkey,
|
||||
safe_acc_info: &AccountInfo,
|
||||
vesting_acc_info: &AccountInfo,
|
||||
vesting_acc_beneficiary_info: &AccountInfo,
|
||||
) -> Result<Vesting, LockupError> {
|
||||
let vesting = vesting_raw(program_id, safe, vesting_acc_info)?;
|
||||
let vesting = _vesting(program_id, safe_acc_info, vesting_acc_info)?;
|
||||
|
||||
if vesting.beneficiary != *vesting_acc_beneficiary_info.key {
|
||||
return Err(LockupErrorCode::Unauthorized)?;
|
||||
|
@ -64,9 +63,9 @@ pub fn vesting(
|
|||
Ok(vesting)
|
||||
}
|
||||
|
||||
pub fn vesting_raw(
|
||||
fn _vesting(
|
||||
program_id: &Pubkey,
|
||||
safe: &Pubkey,
|
||||
safe_acc_info: &AccountInfo,
|
||||
vesting_acc_info: &AccountInfo,
|
||||
) -> Result<Vesting, LockupError> {
|
||||
let mut data: &[u8] = &vesting_acc_info.try_borrow_data()?;
|
||||
|
@ -78,36 +77,20 @@ pub fn vesting_raw(
|
|||
if !vesting.initialized {
|
||||
return Err(LockupErrorCode::NotInitialized)?;
|
||||
}
|
||||
if vesting.safe != *safe {
|
||||
if vesting.safe != *safe_acc_info.key {
|
||||
return Err(LockupErrorCode::WrongSafe)?;
|
||||
}
|
||||
Ok(vesting)
|
||||
}
|
||||
|
||||
pub fn rent(acc_info: &AccountInfo) -> Result<Rent, LockupError> {
|
||||
if *acc_info.key != solana_sdk::sysvar::rent::id() {
|
||||
return Err(LockupErrorCode::InvalidRentSysvar)?;
|
||||
}
|
||||
Rent::from_account_info(acc_info).map_err(Into::into)
|
||||
}
|
||||
|
||||
pub fn clock(acc_info: &AccountInfo) -> Result<Clock, LockupError> {
|
||||
if *acc_info.key != solana_sdk::sysvar::clock::id() {
|
||||
return Err(LockupErrorCode::InvalidClockSysvar)?;
|
||||
}
|
||||
Clock::from_account_info(acc_info).map_err(Into::into)
|
||||
}
|
||||
|
||||
pub fn safe(acc_info: &AccountInfo, program_id: &Pubkey) -> Result<Safe, LockupError> {
|
||||
if acc_info.owner != program_id {
|
||||
return Err(LockupErrorCode::InvalidAccountOwner)?;
|
||||
}
|
||||
|
||||
let safe = Safe::unpack(&acc_info.try_borrow_data()?)?;
|
||||
if !safe.initialized {
|
||||
return Err(LockupErrorCode::NotInitialized)?;
|
||||
}
|
||||
|
||||
Ok(safe)
|
||||
}
|
||||
|
||||
|
@ -119,8 +102,10 @@ pub fn vault(
|
|||
safe_acc_info: &AccountInfo,
|
||||
program_id: &Pubkey,
|
||||
) -> Result<TokenAccount, LockupError> {
|
||||
let vesting = vesting_raw(program_id, safe_acc_info.key, vesting_acc_info)?;
|
||||
let vesting = _vesting(program_id, safe_acc_info, vesting_acc_info)?;
|
||||
|
||||
let vault = token(acc_info)?;
|
||||
|
||||
let va = vault_authority(
|
||||
program_id,
|
||||
vault_authority_acc_info,
|
||||
|
@ -132,14 +117,11 @@ pub fn vault(
|
|||
if va != vault.owner {
|
||||
return Err(LockupErrorCode::InvalidVault)?;
|
||||
}
|
||||
if va != *vault_authority_acc_info.key {
|
||||
return Err(LockupErrorCode::InvalidVault)?;
|
||||
}
|
||||
|
||||
Ok(vault)
|
||||
}
|
||||
|
||||
pub fn vault_authority(
|
||||
fn vault_authority(
|
||||
program_id: &Pubkey,
|
||||
vault_authority_acc_info: &AccountInfo,
|
||||
beneficiary_acc_info: &AccountInfo,
|
||||
|
@ -170,3 +152,17 @@ pub fn token(acc_info: &AccountInfo) -> Result<TokenAccount, LockupError> {
|
|||
|
||||
Ok(token)
|
||||
}
|
||||
|
||||
pub fn rent(acc_info: &AccountInfo) -> Result<Rent, LockupError> {
|
||||
if *acc_info.key != solana_sdk::sysvar::rent::id() {
|
||||
return Err(LockupErrorCode::InvalidRentSysvar)?;
|
||||
}
|
||||
Rent::from_account_info(acc_info).map_err(Into::into)
|
||||
}
|
||||
|
||||
pub fn clock(acc_info: &AccountInfo) -> Result<Clock, LockupError> {
|
||||
if *acc_info.key != solana_sdk::sysvar::clock::id() {
|
||||
return Err(LockupErrorCode::InvalidClockSysvar)?;
|
||||
}
|
||||
Clock::from_account_info(acc_info).map_err(Into::into)
|
||||
}
|
|
@ -0,0 +1,43 @@
|
|||
use serum_lockup::accounts::vault;
|
||||
use serum_lockup::accounts::Vesting;
|
||||
use solana_sdk::account_info::AccountInfo;
|
||||
use solana_sdk::entrypoint::ProgramResult;
|
||||
use solana_sdk::instruction::Instruction;
|
||||
use solana_sdk::pubkey::Pubkey;
|
||||
|
||||
pub mod access_control;
|
||||
|
||||
// Prepends the program's unique TAG identifier before making the signed
|
||||
// cross program invocation to a *trusted* program on the whitelist.
|
||||
//
|
||||
// The trusted program should perform three steps of validation:
|
||||
//
|
||||
// 1. Check for the TAG identifier in the first 8 bytes of the instruction data.
|
||||
// If present, then authentication must be done on the following two steps.
|
||||
// 2. Check accounts[1] is signed.
|
||||
// 3. Check accounts[1] is the correct program derived address for the vesting
|
||||
// account, i.e., signer seeds == [safe_address, beneficiary_address, nonce].
|
||||
//
|
||||
// If all of these hold, a program can trust the instruction was invoked
|
||||
// by a the lockup program on behalf of a vesting account.
|
||||
//
|
||||
// Importantly, it's the responsibility of the trusted program to maintain the
|
||||
// locked invariant preserved by this program and to return the funds at an
|
||||
// unspecified point in the future for unlocking. Any bug in the trusted program
|
||||
// can result in locked funds becoming unlocked, so take care when adding to the
|
||||
// whitelist.
|
||||
pub fn whitelist_cpi(
|
||||
mut instruction: Instruction,
|
||||
safe: &Pubkey,
|
||||
beneficiary_acc_info: &AccountInfo,
|
||||
vesting: &Vesting,
|
||||
accounts: &[AccountInfo],
|
||||
) -> ProgramResult {
|
||||
let mut data = serum_lockup::instruction::TAG.to_le_bytes().to_vec();
|
||||
data.extend(instruction.data);
|
||||
|
||||
instruction.data = data;
|
||||
|
||||
let signer_seeds = vault::signer_seeds(safe, beneficiary_acc_info.key, &vesting.nonce);
|
||||
solana_sdk::program::invoke_signed(&instruction, accounts, &[&signer_seeds])
|
||||
}
|
|
@ -1,5 +1,6 @@
|
|||
use crate::access_control;
|
||||
use crate::common::access_control;
|
||||
use serum_common::pack::Pack;
|
||||
use serum_common::program::invoke_token_transfer;
|
||||
use serum_lockup::accounts::{vault, Vesting};
|
||||
use serum_lockup::error::{LockupError, LockupErrorCode};
|
||||
use solana_program::info;
|
||||
|
@ -47,13 +48,13 @@ pub fn handler(
|
|||
|
||||
Vesting::unpack_unchecked_mut(
|
||||
&mut vesting_acc_info.try_borrow_mut_data()?,
|
||||
&mut |vesting_acc: &mut Vesting| {
|
||||
&mut |vesting: &mut Vesting| {
|
||||
state_transition(StateTransitionRequest {
|
||||
clock_ts,
|
||||
end_ts,
|
||||
period_count,
|
||||
deposit_amount,
|
||||
vesting_acc,
|
||||
vesting,
|
||||
beneficiary,
|
||||
safe_acc_info,
|
||||
depositor_acc_info,
|
||||
|
@ -94,8 +95,8 @@ fn access_control(req: AccessControlRequest) -> Result<AccessControlResponse, Lo
|
|||
}
|
||||
|
||||
// Account validation.
|
||||
let rent = access_control::rent(rent_acc_info)?;
|
||||
let _safe = access_control::safe(safe_acc_info, program_id)?;
|
||||
let rent = access_control::rent(rent_acc_info)?;
|
||||
let clock_ts = access_control::clock(&clock_acc_info)?.unix_timestamp;
|
||||
|
||||
// Initialize checks.
|
||||
|
@ -129,14 +130,14 @@ fn access_control(req: AccessControlRequest) -> Result<AccessControlResponse, Lo
|
|||
return Err(LockupErrorCode::InvalidDepositAmount)?;
|
||||
}
|
||||
}
|
||||
// Vault.
|
||||
// Vault owned by program.
|
||||
let vault = {
|
||||
let vault = access_control::token(vault_acc_info)?;
|
||||
let vault_authority = Pubkey::create_program_address(
|
||||
&vault::signer_seeds(safe_acc_info.key, &beneficiary, &nonce),
|
||||
program_id,
|
||||
)
|
||||
.map_err(|_| LockupErrorCode::InvalidVaultNonce)?;
|
||||
let vault = access_control::token(vault_acc_info)?;
|
||||
if vault.owner != vault_authority {
|
||||
return Err(LockupErrorCode::InvalidAccountOwner)?;
|
||||
}
|
||||
|
@ -157,7 +158,7 @@ fn state_transition(req: StateTransitionRequest) -> Result<(), LockupError> {
|
|||
end_ts,
|
||||
period_count,
|
||||
deposit_amount,
|
||||
vesting_acc,
|
||||
vesting,
|
||||
beneficiary,
|
||||
safe_acc_info,
|
||||
depositor_acc_info,
|
||||
|
@ -169,60 +170,45 @@ fn state_transition(req: StateTransitionRequest) -> Result<(), LockupError> {
|
|||
} = req;
|
||||
|
||||
// Initialize account.
|
||||
{
|
||||
vesting_acc.safe = safe_acc_info.key.clone();
|
||||
vesting_acc.beneficiary = beneficiary;
|
||||
vesting_acc.initialized = true;
|
||||
vesting_acc.mint = vault.mint;
|
||||
vesting_acc.vault = *vault_acc_info.key;
|
||||
vesting_acc.period_count = period_count;
|
||||
vesting_acc.start_balance = deposit_amount;
|
||||
vesting_acc.end_ts = end_ts;
|
||||
vesting_acc.start_ts = clock_ts;
|
||||
vesting_acc.balance = deposit_amount;
|
||||
vesting_acc.whitelist_owned = 0;
|
||||
vesting_acc.grantor = *depositor_authority_acc_info.key;
|
||||
vesting_acc.nonce = nonce;
|
||||
}
|
||||
vesting.safe = safe_acc_info.key.clone();
|
||||
vesting.beneficiary = beneficiary;
|
||||
vesting.initialized = true;
|
||||
vesting.mint = vault.mint;
|
||||
vesting.vault = *vault_acc_info.key;
|
||||
vesting.period_count = period_count;
|
||||
vesting.start_balance = deposit_amount;
|
||||
vesting.end_ts = end_ts;
|
||||
vesting.start_ts = clock_ts;
|
||||
vesting.outstanding = deposit_amount;
|
||||
vesting.whitelist_owned = 0;
|
||||
vesting.grantor = *depositor_authority_acc_info.key;
|
||||
vesting.nonce = nonce;
|
||||
|
||||
// Now transfer SPL funds from the depositor, to the
|
||||
// program-controlled vault.
|
||||
{
|
||||
let deposit_instruction = spl_token::instruction::transfer(
|
||||
&spl_token::ID,
|
||||
depositor_acc_info.key,
|
||||
vault_acc_info.key,
|
||||
depositor_authority_acc_info.key,
|
||||
&[],
|
||||
deposit_amount,
|
||||
)?;
|
||||
solana_sdk::program::invoke_signed(
|
||||
&deposit_instruction,
|
||||
&[
|
||||
depositor_acc_info.clone(),
|
||||
depositor_authority_acc_info.clone(),
|
||||
vault_acc_info.clone(),
|
||||
token_program_acc_info.clone(),
|
||||
],
|
||||
&[],
|
||||
)?;
|
||||
}
|
||||
// Transfer funds to vault.
|
||||
invoke_token_transfer(
|
||||
depositor_acc_info,
|
||||
vault_acc_info,
|
||||
depositor_authority_acc_info,
|
||||
token_program_acc_info,
|
||||
&[],
|
||||
deposit_amount,
|
||||
)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
struct AccessControlRequest<'a, 'b> {
|
||||
program_id: &'a Pubkey,
|
||||
beneficiary: Pubkey,
|
||||
end_ts: i64,
|
||||
period_count: u64,
|
||||
deposit_amount: u64,
|
||||
vesting_acc_info: &'a AccountInfo<'b>,
|
||||
safe_acc_info: &'a AccountInfo<'b>,
|
||||
depositor_authority_acc_info: &'a AccountInfo<'b>,
|
||||
vault_acc_info: &'a AccountInfo<'b>,
|
||||
rent_acc_info: &'a AccountInfo<'b>,
|
||||
clock_acc_info: &'a AccountInfo<'b>,
|
||||
program_id: &'a Pubkey,
|
||||
beneficiary: Pubkey,
|
||||
end_ts: i64,
|
||||
period_count: u64,
|
||||
deposit_amount: u64,
|
||||
nonce: u8,
|
||||
}
|
||||
|
||||
|
@ -232,17 +218,17 @@ struct AccessControlResponse {
|
|||
}
|
||||
|
||||
struct StateTransitionRequest<'a, 'b, 'c> {
|
||||
depositor_authority_acc_info: &'a AccountInfo<'b>,
|
||||
depositor_acc_info: &'a AccountInfo<'b>,
|
||||
token_program_acc_info: &'a AccountInfo<'b>,
|
||||
safe_acc_info: &'a AccountInfo<'b>,
|
||||
vault_acc_info: &'a AccountInfo<'b>,
|
||||
vesting: &'c mut Vesting,
|
||||
vault: TokenAccount,
|
||||
beneficiary: Pubkey,
|
||||
clock_ts: i64,
|
||||
end_ts: i64,
|
||||
period_count: u64,
|
||||
deposit_amount: u64,
|
||||
nonce: u8,
|
||||
vesting_acc: &'c mut Vesting,
|
||||
vault: TokenAccount,
|
||||
vault_acc_info: &'a AccountInfo<'b>,
|
||||
beneficiary: Pubkey,
|
||||
safe_acc_info: &'a AccountInfo<'b>,
|
||||
depositor_acc_info: &'a AccountInfo<'b>,
|
||||
depositor_authority_acc_info: &'a AccountInfo<'b>,
|
||||
token_program_acc_info: &'a AccountInfo<'b>,
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
use crate::access_control;
|
||||
use crate::common::access_control;
|
||||
use serum_common::pack::Pack;
|
||||
use serum_lockup::accounts::{Safe, Whitelist};
|
||||
use serum_lockup::error::{LockupError, LockupErrorCode};
|
||||
|
@ -31,11 +31,10 @@ pub fn handler(
|
|||
&mut safe_acc_info.try_borrow_mut_data()?,
|
||||
&mut |safe: &mut Safe| {
|
||||
state_transition(StateTransitionRequest {
|
||||
safe,
|
||||
safe_addr: safe_acc_info.key,
|
||||
whitelist: Whitelist::new(whitelist_acc_info.clone())?,
|
||||
whitelist_addr: whitelist_acc_info.key,
|
||||
authority,
|
||||
safe,
|
||||
safe_acc_info,
|
||||
whitelist_acc_info,
|
||||
})
|
||||
.map_err(Into::into)
|
||||
},
|
||||
|
@ -73,7 +72,7 @@ fn access_control(req: AccessControlRequest) -> Result<(), LockupError> {
|
|||
}
|
||||
}
|
||||
|
||||
// Whitelist (not yet set on Safe).
|
||||
// Whitelist (uninitialized).
|
||||
{
|
||||
if whitelist_acc_info.owner != program_id {
|
||||
return Err(LockupErrorCode::InvalidAccountOwner)?;
|
||||
|
@ -97,36 +96,33 @@ fn state_transition(req: StateTransitionRequest) -> Result<(), LockupError> {
|
|||
|
||||
let StateTransitionRequest {
|
||||
safe,
|
||||
safe_addr,
|
||||
safe_acc_info,
|
||||
authority,
|
||||
whitelist,
|
||||
whitelist_addr,
|
||||
whitelist_acc_info,
|
||||
} = req;
|
||||
|
||||
// Initialize Safe.
|
||||
safe.initialized = true;
|
||||
safe.authority = authority;
|
||||
safe.whitelist = *whitelist_addr;
|
||||
safe.whitelist = *whitelist_acc_info.key;
|
||||
|
||||
// Inittialize Whitelist.
|
||||
whitelist.set_safe(safe_addr)?;
|
||||
|
||||
info!("state-transition: success");
|
||||
let whitelist = Whitelist::new(whitelist_acc_info.clone())?;
|
||||
whitelist.set_safe(safe_acc_info.key)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
struct AccessControlRequest<'a, 'b> {
|
||||
program_id: &'a Pubkey,
|
||||
safe_acc_info: &'a AccountInfo<'b>,
|
||||
whitelist_acc_info: &'a AccountInfo<'b>,
|
||||
rent_acc_info: &'a AccountInfo<'b>,
|
||||
program_id: &'a Pubkey,
|
||||
}
|
||||
|
||||
struct StateTransitionRequest<'a, 'b> {
|
||||
safe_acc_info: &'a AccountInfo<'b>,
|
||||
whitelist_acc_info: &'a AccountInfo<'b>,
|
||||
safe: &'a mut Safe,
|
||||
safe_addr: &'a Pubkey,
|
||||
whitelist_addr: &'a Pubkey,
|
||||
whitelist: Whitelist<'b>,
|
||||
authority: Pubkey,
|
||||
}
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
//! Program entrypoint.
|
||||
|
||||
#![cfg_attr(feature = "strict", deny(warnings))]
|
||||
|
||||
use serum_common::pack::Pack;
|
||||
|
@ -9,16 +7,16 @@ use solana_sdk::account_info::AccountInfo;
|
|||
use solana_sdk::entrypoint::ProgramResult;
|
||||
use solana_sdk::pubkey::Pubkey;
|
||||
|
||||
pub(crate) mod access_control;
|
||||
mod available_for_withdrawal;
|
||||
mod common;
|
||||
mod create_vesting;
|
||||
mod initialize;
|
||||
mod redeem;
|
||||
mod set_authority;
|
||||
mod whitelist_add;
|
||||
mod whitelist_delete;
|
||||
mod whitelist_deposit;
|
||||
mod whitelist_withdraw;
|
||||
mod withdraw;
|
||||
|
||||
solana_sdk::entrypoint!(entry);
|
||||
fn entry(program_id: &Pubkey, accounts: &[AccountInfo], instruction_data: &[u8]) -> ProgramResult {
|
||||
|
@ -44,7 +42,7 @@ fn entry(program_id: &Pubkey, accounts: &[AccountInfo], instruction_data: &[u8])
|
|||
deposit_amount,
|
||||
nonce,
|
||||
),
|
||||
LockupInstruction::Redeem { amount } => redeem::handler(program_id, accounts, amount),
|
||||
LockupInstruction::Withdraw { amount } => withdraw::handler(program_id, accounts, amount),
|
||||
LockupInstruction::WhitelistWithdraw {
|
||||
amount,
|
||||
instruction_data,
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
use crate::access_control;
|
||||
use crate::common::access_control;
|
||||
use serum_common::pack::Pack;
|
||||
use serum_lockup::accounts::Safe;
|
||||
use serum_lockup::error::LockupError;
|
||||
|
@ -51,17 +51,9 @@ fn access_control(req: AccessControlRequest) -> Result<(), LockupError> {
|
|||
// Governance authorization.
|
||||
let _ = access_control::governance(program_id, safe_acc_info, safe_authority_acc_info)?;
|
||||
|
||||
info!("access-control: success");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
struct AccessControlRequest<'a, 'b> {
|
||||
program_id: &'a Pubkey,
|
||||
safe_acc_info: &'a AccountInfo<'b>,
|
||||
safe_authority_acc_info: &'a AccountInfo<'b>,
|
||||
}
|
||||
|
||||
fn state_transition(req: StateTransitionRequest) -> Result<(), LockupError> {
|
||||
info!("state-transition: set_authority");
|
||||
|
||||
|
@ -72,11 +64,15 @@ fn state_transition(req: StateTransitionRequest) -> Result<(), LockupError> {
|
|||
|
||||
safe_acc.authority = new_authority;
|
||||
|
||||
info!("state-transition: success");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
struct AccessControlRequest<'a, 'b> {
|
||||
program_id: &'a Pubkey,
|
||||
safe_acc_info: &'a AccountInfo<'b>,
|
||||
safe_authority_acc_info: &'a AccountInfo<'b>,
|
||||
}
|
||||
|
||||
struct StateTransitionRequest<'a> {
|
||||
safe_acc: &'a mut Safe,
|
||||
new_authority: Pubkey,
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
use crate::access_control;
|
||||
use crate::common::access_control;
|
||||
use serum_lockup::accounts::{Whitelist, WhitelistEntry};
|
||||
use serum_lockup::error::{LockupError, LockupErrorCode};
|
||||
use solana_program::info;
|
||||
|
@ -54,8 +54,6 @@ fn access_control(req: AccessControlRequest) -> Result<(), LockupError> {
|
|||
// Must be a valid derived address.
|
||||
let _ = wl_entry.derived_address()?;
|
||||
|
||||
info!("access-control: success");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
@ -71,8 +69,6 @@ fn state_transition(req: StateTransitionRequest) -> Result<(), LockupError> {
|
|||
.push(wl_entry)?
|
||||
.ok_or(LockupErrorCode::WhitelistFull)?;
|
||||
|
||||
info!("state-transition: success");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
use crate::access_control;
|
||||
use crate::common::access_control;
|
||||
use serum_lockup::accounts::{Whitelist, WhitelistEntry};
|
||||
use serum_lockup::error::{LockupError, LockupErrorCode};
|
||||
use solana_program::info;
|
||||
|
@ -50,8 +50,6 @@ fn access_control(req: AccessControlRequest) -> Result<(), LockupError> {
|
|||
let _ =
|
||||
access_control::whitelist(whitelist_acc_info.clone(), safe_acc_info, &safe, program_id)?;
|
||||
|
||||
info!("access-control: success");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
@ -67,8 +65,6 @@ fn state_transition(req: StateTransitionRequest) -> Result<(), LockupError> {
|
|||
.delete(wl_entry)?
|
||||
.ok_or(LockupErrorCode::WhitelistNotFound)?;
|
||||
|
||||
info!("state-transition: success");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
use crate::access_control;
|
||||
use crate::common::{access_control, whitelist_cpi};
|
||||
use serum_common::pack::Pack;
|
||||
use serum_lockup::accounts::{vault, Vesting};
|
||||
use serum_lockup::accounts::Vesting;
|
||||
use serum_lockup::error::{LockupError, LockupErrorCode};
|
||||
use solana_program::info;
|
||||
use solana_sdk::account_info::{next_account_info, AccountInfo};
|
||||
|
@ -8,11 +8,12 @@ use solana_sdk::instruction::{AccountMeta, Instruction};
|
|||
use solana_sdk::program_pack::Pack as TokenPack;
|
||||
use solana_sdk::pubkey::Pubkey;
|
||||
use std::convert::Into;
|
||||
use std::iter::Iterator;
|
||||
|
||||
pub fn handler(
|
||||
program_id: &Pubkey,
|
||||
accounts: &[AccountInfo],
|
||||
instruction_data: Vec<u8>,
|
||||
ref instruction_data: Vec<u8>,
|
||||
) -> Result<(), LockupError> {
|
||||
info!("handler: whitelist_deposit");
|
||||
|
||||
|
@ -30,10 +31,11 @@ pub fn handler(
|
|||
let vault_acc_info = next_account_info(acc_infos)?;
|
||||
let vault_auth_acc_info = next_account_info(acc_infos)?;
|
||||
let tok_prog_acc_info = next_account_info(acc_infos)?;
|
||||
let wl_prog_vault_acc_info = next_account_info(acc_infos)?;
|
||||
let wl_prog_vault_authority_acc_info = next_account_info(acc_infos)?;
|
||||
|
||||
// Program specific.
|
||||
let remaining_relay_accs: Vec<&AccountInfo> = acc_infos.collect();
|
||||
let remaining_relay_accs = acc_infos;
|
||||
|
||||
access_control(AccessControlRequest {
|
||||
program_id,
|
||||
|
@ -41,6 +43,7 @@ pub fn handler(
|
|||
vesting_acc_info,
|
||||
wl_acc_info,
|
||||
wl_prog_acc_info,
|
||||
wl_prog_vault_acc_info,
|
||||
wl_prog_vault_authority_acc_info,
|
||||
safe_acc_info,
|
||||
vault_acc_info,
|
||||
|
@ -52,17 +55,17 @@ pub fn handler(
|
|||
&mut |vesting: &mut Vesting| {
|
||||
state_transition(StateTransitionRequest {
|
||||
accounts,
|
||||
instruction_data: instruction_data.clone(),
|
||||
safe_acc: safe_acc_info.key,
|
||||
nonce: vesting.nonce,
|
||||
instruction_data,
|
||||
safe_acc_info,
|
||||
wl_prog_acc_info,
|
||||
wl_prog_vault_acc_info,
|
||||
wl_prog_vault_authority_acc_info,
|
||||
vault_acc_info,
|
||||
vault_auth_acc_info,
|
||||
tok_prog_acc_info,
|
||||
vesting,
|
||||
beneficiary_acc_info,
|
||||
remaining_relay_accs: remaining_relay_accs.clone(),
|
||||
remaining_relay_accs,
|
||||
})
|
||||
.map_err(Into::into)
|
||||
},
|
||||
|
@ -80,6 +83,7 @@ fn access_control(req: AccessControlRequest) -> Result<(), LockupError> {
|
|||
vesting_acc_info,
|
||||
wl_acc_info,
|
||||
wl_prog_acc_info,
|
||||
wl_prog_vault_acc_info,
|
||||
wl_prog_vault_authority_acc_info,
|
||||
safe_acc_info,
|
||||
vault_acc_info,
|
||||
|
@ -105,20 +109,25 @@ fn access_control(req: AccessControlRequest) -> Result<(), LockupError> {
|
|||
)?;
|
||||
let _vesting = access_control::vesting(
|
||||
program_id,
|
||||
safe_acc_info.key,
|
||||
safe_acc_info,
|
||||
vesting_acc_info,
|
||||
beneficiary_acc_info,
|
||||
)?;
|
||||
|
||||
// WhitelistDeposit checks.
|
||||
//
|
||||
// Is the given program on the whitelist?
|
||||
let entry = whitelist
|
||||
.get_derived(wl_prog_vault_authority_acc_info.key)?
|
||||
.ok_or(LockupErrorCode::WhitelistNotFound)?;
|
||||
if entry.program_id() != *wl_prog_acc_info.key {
|
||||
return Err(LockupErrorCode::WhitelistInvalidProgramId)?;
|
||||
}
|
||||
|
||||
info!("access-control: success");
|
||||
// Is the vault owned by this whitelisted authority?
|
||||
let wl_vault = access_control::token(wl_prog_vault_acc_info)?;
|
||||
if &wl_vault.owner != wl_prog_vault_authority_acc_info.key {
|
||||
return Err(LockupErrorCode::InvalidTokenAccountOwner)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
@ -130,18 +139,17 @@ fn state_transition(req: StateTransitionRequest) -> Result<(), LockupError> {
|
|||
vesting,
|
||||
instruction_data,
|
||||
accounts,
|
||||
nonce,
|
||||
safe_acc,
|
||||
safe_acc_info,
|
||||
vault_acc_info,
|
||||
vault_auth_acc_info,
|
||||
wl_prog_acc_info,
|
||||
wl_prog_vault_acc_info,
|
||||
wl_prog_vault_authority_acc_info,
|
||||
remaining_relay_accs,
|
||||
tok_prog_acc_info,
|
||||
beneficiary_acc_info,
|
||||
} = req;
|
||||
|
||||
// Check before balance.
|
||||
let before_amount = {
|
||||
let vault = spl_token::state::Account::unpack(&vault_acc_info.try_borrow_data()?)?;
|
||||
vault.amount
|
||||
|
@ -153,44 +161,47 @@ fn state_transition(req: StateTransitionRequest) -> Result<(), LockupError> {
|
|||
AccountMeta::new(*vault_acc_info.key, false),
|
||||
AccountMeta::new_readonly(*vault_auth_acc_info.key, true),
|
||||
AccountMeta::new_readonly(*tok_prog_acc_info.key, false),
|
||||
AccountMeta::new(*wl_prog_vault_acc_info.key, false),
|
||||
AccountMeta::new_readonly(*wl_prog_vault_authority_acc_info.key, false),
|
||||
];
|
||||
for a in remaining_relay_accs {
|
||||
meta_accounts.extend(remaining_relay_accs.map(|a| {
|
||||
if a.is_writable {
|
||||
meta_accounts.push(AccountMeta::new(*a.key, a.is_signer));
|
||||
AccountMeta::new(*a.key, a.is_signer)
|
||||
} else {
|
||||
meta_accounts.push(AccountMeta::new_readonly(*a.key, a.is_signer));
|
||||
AccountMeta::new_readonly(*a.key, a.is_signer)
|
||||
}
|
||||
}
|
||||
let mut data = serum_lockup::instruction::TAG.to_le_bytes().to_vec();
|
||||
data.extend(instruction_data);
|
||||
}));
|
||||
let relay_instruction = Instruction {
|
||||
program_id: *wl_prog_acc_info.key,
|
||||
accounts: meta_accounts,
|
||||
data,
|
||||
data: instruction_data.to_vec(),
|
||||
};
|
||||
let signer_seeds = vault::signer_seeds(safe_acc, beneficiary_acc_info.key, &nonce);
|
||||
solana_sdk::program::invoke_signed(&relay_instruction, &accounts[..], &[&signer_seeds])?;
|
||||
whitelist_cpi(
|
||||
relay_instruction,
|
||||
safe_acc_info.key,
|
||||
beneficiary_acc_info,
|
||||
vesting,
|
||||
accounts,
|
||||
)?;
|
||||
}
|
||||
|
||||
// Update vesting account with the deposit.
|
||||
{
|
||||
let after_amount = {
|
||||
let vault = spl_token::state::Account::unpack(&vault_acc_info.try_borrow_data()?)?;
|
||||
let deposit_amount = vault.amount - before_amount;
|
||||
vault.amount
|
||||
};
|
||||
|
||||
// Safety checks.
|
||||
//
|
||||
// Balance must go up.
|
||||
if deposit_amount <= 0 {
|
||||
return Err(LockupErrorCode::InsufficientDepositAmount)?;
|
||||
}
|
||||
// Cannot deposit more than withdrawn.
|
||||
if deposit_amount > vesting.whitelist_owned {
|
||||
return Err(LockupErrorCode::DepositOverflow)?;
|
||||
}
|
||||
|
||||
vesting.whitelist_owned -= deposit_amount;
|
||||
// Deposit safety checks.
|
||||
let deposit_amount = after_amount - before_amount;
|
||||
// Balance must go up.
|
||||
if deposit_amount <= 0 {
|
||||
return Err(LockupErrorCode::InsufficientDepositAmount)?;
|
||||
}
|
||||
// Cannot deposit more than withdrawn.
|
||||
if deposit_amount > vesting.whitelist_owned {
|
||||
return Err(LockupErrorCode::DepositOverflow)?;
|
||||
}
|
||||
// Book keeping.
|
||||
vesting.whitelist_owned -= deposit_amount;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
@ -199,6 +210,7 @@ struct AccessControlRequest<'a, 'b> {
|
|||
program_id: &'a Pubkey,
|
||||
wl_acc_info: &'a AccountInfo<'b>,
|
||||
wl_prog_acc_info: &'a AccountInfo<'b>,
|
||||
wl_prog_vault_acc_info: &'a AccountInfo<'b>,
|
||||
wl_prog_vault_authority_acc_info: &'a AccountInfo<'b>,
|
||||
beneficiary_acc_info: &'a AccountInfo<'b>,
|
||||
vesting_acc_info: &'a AccountInfo<'b>,
|
||||
|
@ -208,16 +220,16 @@ struct AccessControlRequest<'a, 'b> {
|
|||
}
|
||||
|
||||
struct StateTransitionRequest<'a, 'b, 'c> {
|
||||
instruction_data: Vec<u8>,
|
||||
vesting: &'c mut Vesting,
|
||||
remaining_relay_accs: &'c mut dyn Iterator<Item = &'a AccountInfo<'b>>,
|
||||
accounts: &'a [AccountInfo<'b>],
|
||||
nonce: u8,
|
||||
safe_acc: &'a Pubkey,
|
||||
safe_acc_info: &'a AccountInfo<'b>,
|
||||
vault_acc_info: &'a AccountInfo<'b>,
|
||||
vault_auth_acc_info: &'a AccountInfo<'b>,
|
||||
wl_prog_acc_info: &'a AccountInfo<'b>,
|
||||
wl_prog_vault_acc_info: &'a AccountInfo<'b>,
|
||||
wl_prog_vault_authority_acc_info: &'a AccountInfo<'b>,
|
||||
remaining_relay_accs: Vec<&'a AccountInfo<'b>>,
|
||||
tok_prog_acc_info: &'a AccountInfo<'b>,
|
||||
beneficiary_acc_info: &'a AccountInfo<'b>,
|
||||
instruction_data: &'c [u8],
|
||||
vesting: &'c mut Vesting,
|
||||
}
|
||||
|
|
|
@ -1,19 +1,20 @@
|
|||
use crate::access_control;
|
||||
use crate::common::{access_control, whitelist_cpi};
|
||||
use serum_common::pack::Pack;
|
||||
use serum_lockup::accounts::{vault, Vesting};
|
||||
use serum_lockup::accounts::Vesting;
|
||||
use serum_lockup::error::{LockupError, LockupErrorCode};
|
||||
use solana_program::info;
|
||||
use solana_sdk::account_info::{next_account_info, AccountInfo};
|
||||
use solana_sdk::instruction::{AccountMeta, Instruction};
|
||||
use solana_sdk::program_pack::Pack as TokenPack;
|
||||
use solana_sdk::pubkey::Pubkey;
|
||||
use spl_token::state::Account as TokenAccount;
|
||||
use std::convert::Into;
|
||||
|
||||
pub fn handler(
|
||||
program_id: &Pubkey,
|
||||
accounts: &[AccountInfo],
|
||||
amount: u64,
|
||||
instruction_data: Vec<u8>,
|
||||
ref instruction_data: Vec<u8>,
|
||||
) -> Result<(), LockupError> {
|
||||
info!("handler: whitelist_withdraw");
|
||||
|
||||
|
@ -24,7 +25,6 @@ pub fn handler(
|
|||
let safe_acc_info = next_account_info(acc_infos)?;
|
||||
let wl_acc_info = next_account_info(acc_infos)?;
|
||||
let wl_prog_acc_info = next_account_info(acc_infos)?;
|
||||
let wl_prog_vault_authority_acc_info = next_account_info(acc_infos)?;
|
||||
|
||||
// Below accounts are relayed.
|
||||
|
||||
|
@ -32,9 +32,11 @@ pub fn handler(
|
|||
let vault_acc_info = next_account_info(acc_infos)?;
|
||||
let vault_auth_acc_info = next_account_info(acc_infos)?;
|
||||
let tok_prog_acc_info = next_account_info(acc_infos)?;
|
||||
let wl_prog_vault_acc_info = next_account_info(acc_infos)?;
|
||||
let wl_prog_vault_authority_acc_info = next_account_info(acc_infos)?;
|
||||
|
||||
// Program specific.
|
||||
let remaining_relay_accs: Vec<&AccountInfo> = acc_infos.collect();
|
||||
let remaining_relay_accs = acc_infos;
|
||||
|
||||
access_control(AccessControlRequest {
|
||||
program_id,
|
||||
|
@ -42,6 +44,7 @@ pub fn handler(
|
|||
vesting_acc_info,
|
||||
wl_acc_info,
|
||||
wl_prog_acc_info,
|
||||
wl_prog_vault_acc_info,
|
||||
wl_prog_vault_authority_acc_info,
|
||||
safe_acc_info,
|
||||
vault_acc_info,
|
||||
|
@ -55,17 +58,17 @@ pub fn handler(
|
|||
state_transition(StateTransitionRequest {
|
||||
accounts,
|
||||
amount,
|
||||
instruction_data: instruction_data.clone(),
|
||||
safe_acc: safe_acc_info.key,
|
||||
nonce: vesting.nonce,
|
||||
instruction_data,
|
||||
safe_acc_info,
|
||||
wl_prog_acc_info,
|
||||
wl_prog_vault_acc_info,
|
||||
wl_prog_vault_authority_acc_info,
|
||||
vault_acc_info,
|
||||
vault_auth_acc_info,
|
||||
tok_prog_acc_info,
|
||||
vesting,
|
||||
beneficiary_acc_info,
|
||||
remaining_relay_accs: remaining_relay_accs.clone(),
|
||||
remaining_relay_accs,
|
||||
})
|
||||
.map_err(Into::into)
|
||||
},
|
||||
|
@ -82,6 +85,7 @@ fn access_control(req: AccessControlRequest) -> Result<(), LockupError> {
|
|||
beneficiary_acc_info,
|
||||
vesting_acc_info,
|
||||
wl_acc_info,
|
||||
wl_prog_vault_acc_info,
|
||||
wl_prog_acc_info,
|
||||
wl_prog_vault_authority_acc_info,
|
||||
safe_acc_info,
|
||||
|
@ -99,7 +103,7 @@ fn access_control(req: AccessControlRequest) -> Result<(), LockupError> {
|
|||
let safe = access_control::safe(safe_acc_info, program_id)?;
|
||||
let whitelist =
|
||||
access_control::whitelist(wl_acc_info.clone(), safe_acc_info, &safe, program_id)?;
|
||||
let _ = access_control::vault(
|
||||
let _vault = access_control::vault(
|
||||
vault_acc_info,
|
||||
vault_auth_acc_info,
|
||||
vesting_acc_info,
|
||||
|
@ -109,21 +113,29 @@ fn access_control(req: AccessControlRequest) -> Result<(), LockupError> {
|
|||
)?;
|
||||
let vesting = access_control::vesting(
|
||||
program_id,
|
||||
safe_acc_info.key,
|
||||
safe_acc_info,
|
||||
vesting_acc_info,
|
||||
beneficiary_acc_info,
|
||||
)?;
|
||||
|
||||
// WhitelistWithdraw checks.
|
||||
if amount > vesting.available_for_whitelist() {
|
||||
//
|
||||
// Do we have sufficient balance?
|
||||
if amount > vesting.balance() {
|
||||
return Err(LockupErrorCode::InsufficientWhitelistBalance)?;
|
||||
}
|
||||
// Is the given program on the whitelist?
|
||||
let entry = whitelist
|
||||
.get_derived(wl_prog_vault_authority_acc_info.key)?
|
||||
.ok_or(LockupErrorCode::WhitelistNotFound)?;
|
||||
if entry.program_id() != *wl_prog_acc_info.key {
|
||||
return Err(LockupErrorCode::WhitelistInvalidProgramId)?;
|
||||
}
|
||||
// Is the vault owned by this whitelisted authority?
|
||||
let wl_vault = access_control::token(wl_prog_vault_acc_info)?;
|
||||
if &wl_vault.owner != wl_prog_vault_authority_acc_info.key {
|
||||
return Err(LockupErrorCode::InvalidTokenAccountOwner)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
@ -137,68 +149,70 @@ fn state_transition(req: StateTransitionRequest) -> Result<(), LockupError> {
|
|||
beneficiary_acc_info,
|
||||
accounts,
|
||||
amount,
|
||||
nonce,
|
||||
safe_acc,
|
||||
safe_acc_info,
|
||||
vault_acc_info,
|
||||
wl_prog_acc_info,
|
||||
wl_prog_vault_authority_acc_info: _,
|
||||
wl_prog_vault_acc_info,
|
||||
wl_prog_vault_authority_acc_info,
|
||||
remaining_relay_accs,
|
||||
tok_prog_acc_info,
|
||||
vault_auth_acc_info,
|
||||
} = req;
|
||||
|
||||
let before_amount = {
|
||||
let vault = spl_token::state::Account::unpack(&vault_acc_info.try_borrow_data()?)?;
|
||||
let vault = TokenAccount::unpack(&vault_acc_info.try_borrow_data()?)?;
|
||||
vault.amount
|
||||
};
|
||||
|
||||
// Invoke relay.
|
||||
{
|
||||
let signer_seeds = vault::signer_seeds(safe_acc, beneficiary_acc_info.key, &nonce);
|
||||
let mut meta_accounts = vec![
|
||||
AccountMeta::new(*vault_acc_info.key, false),
|
||||
AccountMeta::new_readonly(*vault_auth_acc_info.key, true),
|
||||
AccountMeta::new_readonly(*tok_prog_acc_info.key, false),
|
||||
AccountMeta::new(*wl_prog_vault_acc_info.key, false),
|
||||
AccountMeta::new_readonly(*wl_prog_vault_authority_acc_info.key, false),
|
||||
];
|
||||
for a in remaining_relay_accs {
|
||||
meta_accounts.extend(remaining_relay_accs.map(|a| {
|
||||
if a.is_writable {
|
||||
meta_accounts.push(AccountMeta::new(*a.key, a.is_signer));
|
||||
AccountMeta::new(*a.key, a.is_signer)
|
||||
} else {
|
||||
meta_accounts.push(AccountMeta::new_readonly(*a.key, a.is_signer));
|
||||
AccountMeta::new_readonly(*a.key, a.is_signer)
|
||||
}
|
||||
}
|
||||
let mut data = serum_lockup::instruction::TAG.to_le_bytes().to_vec();
|
||||
data.extend(instruction_data);
|
||||
}));
|
||||
let relay_instruction = Instruction {
|
||||
program_id: *wl_prog_acc_info.key,
|
||||
accounts: meta_accounts,
|
||||
data,
|
||||
data: instruction_data.to_vec(),
|
||||
};
|
||||
|
||||
solana_sdk::program::invoke_signed(&relay_instruction, &accounts[..], &[&signer_seeds])?;
|
||||
whitelist_cpi(
|
||||
relay_instruction,
|
||||
safe_acc_info.key,
|
||||
beneficiary_acc_info,
|
||||
vesting,
|
||||
accounts,
|
||||
)?;
|
||||
}
|
||||
|
||||
// Check the amount transferred is valid. If not abort.
|
||||
let amount_transferred = {
|
||||
let after_amount = {
|
||||
let vault = spl_token::state::Account::unpack(&vault_acc_info.try_borrow_data()?)?;
|
||||
vault.amount
|
||||
};
|
||||
before_amount - after_amount
|
||||
let after_amount = {
|
||||
let vault = TokenAccount::unpack(&vault_acc_info.try_borrow_data()?)?;
|
||||
vault.amount
|
||||
};
|
||||
|
||||
// Withdrawal safety checks.
|
||||
let amount_transferred = before_amount - after_amount;
|
||||
// Is the amount transferred valid?
|
||||
if amount_transferred > amount {
|
||||
return Err(LockupErrorCode::InsufficientAmount)?;
|
||||
}
|
||||
|
||||
// Update vesting account.
|
||||
// Book keeping.
|
||||
vesting.whitelist_owned += amount_transferred;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
struct AccessControlRequest<'a, 'b> {
|
||||
program_id: &'a Pubkey,
|
||||
wl_prog_vault_acc_info: &'a AccountInfo<'b>,
|
||||
beneficiary_acc_info: &'a AccountInfo<'b>,
|
||||
vesting_acc_info: &'a AccountInfo<'b>,
|
||||
safe_acc_info: &'a AccountInfo<'b>,
|
||||
|
@ -207,21 +221,22 @@ struct AccessControlRequest<'a, 'b> {
|
|||
wl_acc_info: &'a AccountInfo<'b>,
|
||||
wl_prog_acc_info: &'a AccountInfo<'b>,
|
||||
wl_prog_vault_authority_acc_info: &'a AccountInfo<'b>,
|
||||
program_id: &'a Pubkey,
|
||||
amount: u64,
|
||||
}
|
||||
|
||||
struct StateTransitionRequest<'a, 'b, 'c> {
|
||||
instruction_data: Vec<u8>,
|
||||
vesting: &'c mut Vesting,
|
||||
remaining_relay_accs: &'c mut dyn Iterator<Item = &'a AccountInfo<'b>>,
|
||||
accounts: &'a [AccountInfo<'b>],
|
||||
amount: u64,
|
||||
nonce: u8,
|
||||
safe_acc: &'a Pubkey,
|
||||
vault_acc_info: &'a AccountInfo<'b>,
|
||||
wl_prog_acc_info: &'a AccountInfo<'b>,
|
||||
wl_prog_vault_acc_info: &'a AccountInfo<'b>,
|
||||
wl_prog_vault_authority_acc_info: &'a AccountInfo<'b>,
|
||||
remaining_relay_accs: Vec<&'a AccountInfo<'b>>,
|
||||
tok_prog_acc_info: &'a AccountInfo<'b>,
|
||||
vault_auth_acc_info: &'a AccountInfo<'b>,
|
||||
beneficiary_acc_info: &'a AccountInfo<'b>,
|
||||
safe_acc_info: &'a AccountInfo<'b>,
|
||||
instruction_data: &'c [u8],
|
||||
vesting: &'c mut Vesting,
|
||||
amount: u64,
|
||||
}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
use crate::access_control;
|
||||
use crate::common::access_control;
|
||||
use serum_common::pack::Pack;
|
||||
use serum_common::program::invoke_token_transfer;
|
||||
use serum_lockup::accounts::{vault, Vesting};
|
||||
use serum_lockup::error::{LockupError, LockupErrorCode};
|
||||
use solana_program::info;
|
||||
|
@ -12,13 +13,13 @@ pub fn handler(
|
|||
accounts: &[AccountInfo],
|
||||
amount: u64,
|
||||
) -> Result<(), LockupError> {
|
||||
info!("handler: redeem");
|
||||
info!("handler: withdraw");
|
||||
|
||||
let acc_infos = &mut accounts.iter();
|
||||
|
||||
let beneficiary_acc_info = next_account_info(acc_infos)?;
|
||||
let vesting_acc_info = next_account_info(acc_infos)?;
|
||||
let beneficiary_token_acc_info = next_account_info(acc_infos)?;
|
||||
let token_acc_info = next_account_info(acc_infos)?;
|
||||
let vault_acc_info = next_account_info(acc_infos)?;
|
||||
let vault_authority_acc_info = next_account_info(acc_infos)?;
|
||||
let safe_acc_info = next_account_info(acc_infos)?;
|
||||
|
@ -44,7 +45,7 @@ pub fn handler(
|
|||
vesting,
|
||||
vault_acc_info,
|
||||
vault_authority_acc_info,
|
||||
beneficiary_token_acc_info,
|
||||
token_acc_info,
|
||||
safe_acc_info,
|
||||
token_program_acc_info,
|
||||
beneficiary_acc_info,
|
||||
|
@ -56,7 +57,7 @@ pub fn handler(
|
|||
}
|
||||
|
||||
fn access_control(req: AccessControlRequest) -> Result<(), LockupError> {
|
||||
info!("access-control: redeem");
|
||||
info!("access-control: withdraw");
|
||||
|
||||
let AccessControlRequest {
|
||||
program_id,
|
||||
|
@ -69,20 +70,20 @@ fn access_control(req: AccessControlRequest) -> Result<(), LockupError> {
|
|||
clock_acc_info,
|
||||
} = req;
|
||||
|
||||
// Beneficiary authorization.
|
||||
// Authorization.
|
||||
if !beneficiary_acc_info.is_signer {
|
||||
return Err(LockupErrorCode::Unauthorized)?;
|
||||
}
|
||||
|
||||
// Account validation.
|
||||
let _ = access_control::safe(safe_acc_info, program_id)?;
|
||||
let _safe = access_control::safe(safe_acc_info, program_id)?;
|
||||
let vesting = access_control::vesting(
|
||||
program_id,
|
||||
safe_acc_info.key,
|
||||
safe_acc_info,
|
||||
vesting_acc_info,
|
||||
beneficiary_acc_info,
|
||||
)?;
|
||||
let _ = access_control::vault(
|
||||
let _vault = access_control::vault(
|
||||
vault_acc_info,
|
||||
vault_authority_acc_info,
|
||||
vesting_acc_info,
|
||||
|
@ -90,62 +91,44 @@ fn access_control(req: AccessControlRequest) -> Result<(), LockupError> {
|
|||
safe_acc_info,
|
||||
program_id,
|
||||
)?;
|
||||
let clock = access_control::clock(clock_acc_info)?;
|
||||
|
||||
// Redemption checks.
|
||||
{
|
||||
let clock = access_control::clock(clock_acc_info)?;
|
||||
if amount == 0 || amount > vesting.available_for_withdrawal(clock.unix_timestamp) {
|
||||
return Err(LockupErrorCode::InsufficientWithdrawalBalance)?;
|
||||
}
|
||||
// Withdrawal checks.
|
||||
if amount == 0 || amount > vesting.available_for_withdrawal(clock.unix_timestamp) {
|
||||
return Err(LockupErrorCode::InsufficientWithdrawalBalance)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn state_transition(req: StateTransitionRequest) -> Result<(), LockupError> {
|
||||
info!("state-transition: redeem");
|
||||
info!("state-transition: withdraw");
|
||||
|
||||
let StateTransitionRequest {
|
||||
vesting,
|
||||
amount,
|
||||
vault_acc_info,
|
||||
vault_authority_acc_info,
|
||||
beneficiary_token_acc_info,
|
||||
token_acc_info,
|
||||
safe_acc_info,
|
||||
token_program_acc_info,
|
||||
beneficiary_acc_info,
|
||||
} = req;
|
||||
|
||||
// Remove the withdrawn token from the vesting account.
|
||||
{
|
||||
vesting.deduct(amount);
|
||||
}
|
||||
|
||||
// Transfer token from the vault to the user address.
|
||||
{
|
||||
let withdraw_instruction = spl_token::instruction::transfer(
|
||||
&spl_token::ID,
|
||||
vault_acc_info.key,
|
||||
beneficiary_token_acc_info.key,
|
||||
&vault_authority_acc_info.key,
|
||||
&[],
|
||||
amount,
|
||||
)?;
|
||||
let signer_seeds =
|
||||
vault::signer_seeds(safe_acc_info.key, beneficiary_acc_info.key, &vesting.nonce);
|
||||
invoke_token_transfer(
|
||||
vault_acc_info,
|
||||
token_acc_info,
|
||||
vault_authority_acc_info,
|
||||
token_program_acc_info,
|
||||
&[&signer_seeds],
|
||||
amount,
|
||||
)?;
|
||||
|
||||
let signer_seeds =
|
||||
vault::signer_seeds(safe_acc_info.key, beneficiary_acc_info.key, &vesting.nonce);
|
||||
|
||||
solana_sdk::program::invoke_signed(
|
||||
&withdraw_instruction,
|
||||
&[
|
||||
vault_acc_info.clone(),
|
||||
beneficiary_token_acc_info.clone(),
|
||||
vault_authority_acc_info.clone(),
|
||||
token_program_acc_info.clone(),
|
||||
],
|
||||
&[&signer_seeds],
|
||||
)?;
|
||||
}
|
||||
// Update bookeeping.
|
||||
vesting.outstanding -= amount;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
@ -165,7 +148,7 @@ struct StateTransitionRequest<'a, 'b, 'c> {
|
|||
amount: u64,
|
||||
vesting: &'c mut Vesting,
|
||||
safe_acc_info: &'a AccountInfo<'b>,
|
||||
beneficiary_token_acc_info: &'a AccountInfo<'b>,
|
||||
token_acc_info: &'a AccountInfo<'b>,
|
||||
vault_acc_info: &'a AccountInfo<'b>,
|
||||
vault_authority_acc_info: &'a AccountInfo<'b>,
|
||||
token_program_acc_info: &'a AccountInfo<'b>,
|
|
@ -18,15 +18,15 @@ pub struct Vesting {
|
|||
/// The owner of this Vesting account. If not set, then the account
|
||||
/// is allocated but needs to be assigned.
|
||||
pub beneficiary: Pubkey,
|
||||
/// The mint of the SPL token the safe is storing, e.g., the SRM mint.
|
||||
/// The mint of the SPL token locked up.
|
||||
pub mint: Pubkey,
|
||||
/// Address of the token vault controlled by the Safe.
|
||||
/// Address of the account's token vault.
|
||||
pub vault: Pubkey,
|
||||
/// The owner of the token account funding this account.
|
||||
pub grantor: Pubkey,
|
||||
/// The outstanding SRM deposit backing this vesting account. All
|
||||
/// withdrawals/redemptions will deduct this balance.
|
||||
pub balance: u64,
|
||||
/// withdrawals will deduct this balance.
|
||||
pub outstanding: u64,
|
||||
/// The starting balance of this vesting account, i.e., how much was
|
||||
/// originally deposited.
|
||||
pub start_balance: u64,
|
||||
|
@ -40,38 +40,31 @@ pub struct Vesting {
|
|||
pub period_count: u64,
|
||||
/// The amount of tokens in custody of whitelisted programs.
|
||||
pub whitelist_owned: u64,
|
||||
///
|
||||
/// Signer nonce.
|
||||
pub nonce: u8,
|
||||
}
|
||||
|
||||
impl Vesting {
|
||||
/// Deducts the given amount from the vesting account upon
|
||||
/// withdrawal/redemption.
|
||||
pub fn deduct(&mut self, amount: u64) {
|
||||
self.balance -= amount;
|
||||
}
|
||||
|
||||
/// Returns the amount available for withdrawal as of the given ts.
|
||||
/// The amount for withdrawal is not necessarily the balance vested
|
||||
/// since funds can be sent to whitelisted programs. For this reason,
|
||||
/// take minimum of the availble balance vested and the available balance
|
||||
/// for sending to whitelisted programs.
|
||||
pub fn available_for_withdrawal(&self, current_ts: i64) -> u64 {
|
||||
std::cmp::min(
|
||||
self.balance_vested(current_ts),
|
||||
self.available_for_whitelist(),
|
||||
)
|
||||
std::cmp::min(self.outstanding_vested(current_ts), self.balance())
|
||||
}
|
||||
|
||||
/// Amount available for whitelisted programs to transfer.
|
||||
pub fn available_for_whitelist(&self) -> u64 {
|
||||
self.balance - self.whitelist_owned
|
||||
// The amount of funds left in the vault.
|
||||
pub fn balance(&self) -> u64 {
|
||||
self.outstanding.checked_sub(self.whitelist_owned).unwrap()
|
||||
}
|
||||
|
||||
// The amount vested that's available for withdrawal, if no funds were ever
|
||||
// sent to another program.
|
||||
fn balance_vested(&self, current_ts: i64) -> u64 {
|
||||
self.total_vested(current_ts) - self.withdrawn_amount()
|
||||
// The amount of outstanding locked tokens vested. Note that these
|
||||
// tokens might have been transferred to whitelisted programs.
|
||||
fn outstanding_vested(&self, current_ts: i64) -> u64 {
|
||||
self.total_vested(current_ts)
|
||||
.checked_sub(self.withdrawn_amount())
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
// Returns the amount withdrawn from this vesting account.
|
||||
fn withdrawn_amount(&self) -> u64 {
|
||||
self.start_balance.checked_sub(self.outstanding).unwrap()
|
||||
}
|
||||
|
||||
// Returns the total vested amount up to the given ts, assuming zero
|
||||
|
@ -82,38 +75,49 @@ impl Vesting {
|
|||
if current_ts >= self.end_ts {
|
||||
return self.start_balance;
|
||||
}
|
||||
self.linear_unlock(current_ts)
|
||||
self.linear_unlock(current_ts).unwrap()
|
||||
}
|
||||
|
||||
// Returns the amount withdrawn from this vesting account.
|
||||
fn withdrawn_amount(&self) -> u64 {
|
||||
self.start_balance - self.balance
|
||||
}
|
||||
fn linear_unlock(&self, current_ts: i64) -> Option<u64> {
|
||||
// LLVM doesn't support signed division.
|
||||
let current_ts = current_ts as u64;
|
||||
let start_ts = self.start_ts as u64;
|
||||
let end_ts = self.end_ts as u64;
|
||||
|
||||
fn linear_unlock(&self, current_ts: i64) -> u64 {
|
||||
let (end_ts, start_ts) = {
|
||||
// If we can't perfectly partition the vesting window,
|
||||
// push the start window back so that we can.
|
||||
//
|
||||
// This has the effect of making the first vesting period act as
|
||||
// a minor "cliff" that vests slightly more than the rest of the
|
||||
// periods.
|
||||
let overflow = (self.end_ts - self.start_ts) as u64 % self.period_count;
|
||||
if overflow != 0 {
|
||||
(self.end_ts, self.start_ts - overflow as i64)
|
||||
} else {
|
||||
(self.end_ts, self.start_ts)
|
||||
}
|
||||
};
|
||||
// If we can't perfectly partition the vesting window,
|
||||
// push the start of the window window back so that we can.
|
||||
//
|
||||
// This has the effect of making the first vesting period shorter
|
||||
// than the rest.
|
||||
let shifted_start_ts =
|
||||
start_ts.checked_sub(end_ts.checked_sub(start_ts)? % self.period_count)?;
|
||||
|
||||
let vested_period_count = {
|
||||
let period = (end_ts - start_ts) as u64 / self.period_count;
|
||||
let current_period_count = (current_ts - start_ts) as u64 / period;
|
||||
// Similarly, if we can't perfectly divide up the vesting rewards
|
||||
// then make the first period act as a cliff, earning slightly more than
|
||||
// subsequent periods.
|
||||
let reward_overflow = self.start_balance % self.period_count;
|
||||
|
||||
// Reward per period ignoring the overflow.
|
||||
let reward_per_period = (self.start_balance.checked_sub(reward_overflow)?)
|
||||
.checked_div(self.period_count)
|
||||
.unwrap();
|
||||
|
||||
// Number of vesting periods that have passed.
|
||||
let current_period = {
|
||||
let period_secs =
|
||||
(end_ts.checked_sub(shifted_start_ts)?).checked_div(self.period_count)?;
|
||||
let current_period_count =
|
||||
(current_ts.checked_sub(shifted_start_ts)?).checked_div(period_secs)?;
|
||||
std::cmp::min(current_period_count, self.period_count)
|
||||
};
|
||||
let reward_per_period = self.start_balance / self.period_count;
|
||||
|
||||
vested_period_count * reward_per_period
|
||||
if current_period == 0 {
|
||||
return Some(0);
|
||||
}
|
||||
|
||||
current_period
|
||||
.checked_mul(reward_per_period)?
|
||||
.checked_add(reward_overflow)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -132,20 +136,20 @@ mod tests {
|
|||
let beneficiary = Keypair::generate(&mut OsRng).pubkey();
|
||||
let initialized = true;
|
||||
let start_balance = 10;
|
||||
let balance = start_balance;
|
||||
let outstanding = start_balance;
|
||||
let start_ts = 11;
|
||||
let end_ts = 12;
|
||||
let period_count = 13;
|
||||
let whitelist_owned = 14;
|
||||
let grantor = Pubkey::new_rand();
|
||||
let mint = Pubkey::new_rand();
|
||||
let vault = Pubkey::new_rand();
|
||||
let grantor = Pubkey::new_unique();
|
||||
let mint = Pubkey::new_unique();
|
||||
let vault = Pubkey::new_unique();
|
||||
let nonce = 0;
|
||||
let vesting_acc = Vesting {
|
||||
safe,
|
||||
beneficiary,
|
||||
initialized,
|
||||
balance,
|
||||
outstanding,
|
||||
start_balance,
|
||||
start_ts,
|
||||
end_ts,
|
||||
|
@ -169,7 +173,7 @@ mod tests {
|
|||
assert_eq!(va.beneficiary, beneficiary);
|
||||
assert_eq!(va.initialized, initialized);
|
||||
assert_eq!(va.start_balance, start_balance);
|
||||
assert_eq!(va.balance, balance);
|
||||
assert_eq!(va.outstanding, outstanding);
|
||||
assert_eq!(va.start_ts, start_ts);
|
||||
assert_eq!(va.end_ts, end_ts);
|
||||
assert_eq!(va.period_count, period_count);
|
||||
|
@ -181,23 +185,23 @@ mod tests {
|
|||
fn available_for_withdrawal() {
|
||||
let safe = Keypair::generate(&mut OsRng).pubkey();
|
||||
let beneficiary = Keypair::generate(&mut OsRng).pubkey();
|
||||
let balance = 10;
|
||||
let outstanding = 10;
|
||||
let start_balance = 10;
|
||||
let start_ts = 10;
|
||||
let end_ts = 20;
|
||||
let period_count = 5;
|
||||
let initialized = true;
|
||||
let whitelist_owned = 0;
|
||||
let grantor = Pubkey::new_rand();
|
||||
let mint = Pubkey::new_rand();
|
||||
let vault = Pubkey::new_rand();
|
||||
let grantor = Pubkey::new_unique();
|
||||
let mint = Pubkey::new_unique();
|
||||
let vault = Pubkey::new_unique();
|
||||
let nonce = 0;
|
||||
let vesting_acc = Vesting {
|
||||
safe,
|
||||
beneficiary,
|
||||
initialized,
|
||||
whitelist_owned,
|
||||
balance,
|
||||
outstanding,
|
||||
start_balance,
|
||||
start_ts,
|
||||
end_ts,
|
||||
|
@ -217,6 +221,51 @@ mod tests {
|
|||
assert_eq!(10, vesting_acc.available_for_withdrawal(100));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn available_for_withdrawal_cliff() {
|
||||
let safe = Keypair::generate(&mut OsRng).pubkey();
|
||||
let beneficiary = Keypair::generate(&mut OsRng).pubkey();
|
||||
let outstanding = 11;
|
||||
let start_balance = 11;
|
||||
let start_ts = 10;
|
||||
let end_ts = 20;
|
||||
let period_count = 10;
|
||||
let initialized = true;
|
||||
let whitelist_owned = 0;
|
||||
let grantor = Pubkey::new_unique();
|
||||
let mint = Pubkey::new_unique();
|
||||
let vault = Pubkey::new_unique();
|
||||
let nonce = 0;
|
||||
let vesting_acc = Vesting {
|
||||
safe,
|
||||
beneficiary,
|
||||
initialized,
|
||||
whitelist_owned,
|
||||
outstanding,
|
||||
start_balance,
|
||||
start_ts,
|
||||
end_ts,
|
||||
period_count,
|
||||
grantor,
|
||||
mint,
|
||||
nonce,
|
||||
vault,
|
||||
};
|
||||
assert_eq!(0, vesting_acc.available_for_withdrawal(10));
|
||||
assert_eq!(2, vesting_acc.available_for_withdrawal(11));
|
||||
assert_eq!(3, vesting_acc.available_for_withdrawal(12));
|
||||
assert_eq!(4, vesting_acc.available_for_withdrawal(13));
|
||||
assert_eq!(5, vesting_acc.available_for_withdrawal(14));
|
||||
assert_eq!(6, vesting_acc.available_for_withdrawal(15));
|
||||
assert_eq!(7, vesting_acc.available_for_withdrawal(16));
|
||||
assert_eq!(8, vesting_acc.available_for_withdrawal(17));
|
||||
assert_eq!(9, vesting_acc.available_for_withdrawal(18));
|
||||
assert_eq!(10, vesting_acc.available_for_withdrawal(19));
|
||||
assert_eq!(11, vesting_acc.available_for_withdrawal(20));
|
||||
assert_eq!(11, vesting_acc.available_for_withdrawal(21));
|
||||
assert_eq!(11, vesting_acc.available_for_withdrawal(2100));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unpack_zeroes() {
|
||||
let og_size = Vesting::default().size().unwrap();
|
||||
|
@ -226,7 +275,7 @@ mod tests {
|
|||
assert_eq!(r.initialized, false);
|
||||
assert_eq!(r.safe, Pubkey::new(&[0; 32]));
|
||||
assert_eq!(r.beneficiary, Pubkey::new(&[0; 32]));
|
||||
assert_eq!(r.balance, 0);
|
||||
assert_eq!(r.outstanding, 0);
|
||||
assert_eq!(r.start_ts, 0);
|
||||
assert_eq!(r.end_ts, 0);
|
||||
assert_eq!(r.period_count, 0);
|
||||
|
|
|
@ -59,8 +59,7 @@ pub mod instruction {
|
|||
/// 5 `[]` Safe.
|
||||
/// 8. `[]` SPL token program.
|
||||
/// 9. `[]` Clock sysvar.
|
||||
// todo: rename
|
||||
Redeem { amount: u64 },
|
||||
Withdraw { amount: u64 },
|
||||
/// Accounts:
|
||||
///
|
||||
/// 0. `[signer]` Beneficiary.
|
||||
|
|
|
@ -116,7 +116,7 @@ fn lifecycle() {
|
|||
pass_time(client.rpc(), wait_ts);
|
||||
}
|
||||
|
||||
// Redeem 10 SRM.
|
||||
// Withdraw 10 SRM.
|
||||
//
|
||||
// Current state:
|
||||
//
|
||||
|
@ -134,22 +134,22 @@ fn lifecycle() {
|
|||
)
|
||||
.unwrap();
|
||||
|
||||
let redeem_amount = 10;
|
||||
let withdraw_amount = 10;
|
||||
|
||||
let _ = client
|
||||
.redeem(RedeemRequest {
|
||||
.withdraw(WithdrawRequest {
|
||||
beneficiary: &expected_beneficiary,
|
||||
vesting,
|
||||
token_account: bene_tok_acc.pubkey(),
|
||||
safe: safe_acc,
|
||||
amount: redeem_amount,
|
||||
amount: withdraw_amount,
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
// The SRM account should be increased.
|
||||
let bene_tok =
|
||||
rpc::account_token_unpacked::<TokenAccount>(client.rpc(), &bene_tok_acc.pubkey());
|
||||
assert_eq!(bene_tok.amount, redeem_amount);
|
||||
assert_eq!(bene_tok.amount, withdraw_amount);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -273,18 +273,20 @@ impl Client {
|
|||
pool_program_id,
|
||||
} = req;
|
||||
let vault = self.vault_for(®istrar, &depositor)?;
|
||||
let vault_acc = rpc::get_token_account::<TokenAccount>(self.inner.rpc(), &vault)?;
|
||||
let mut accounts = vec![
|
||||
// Whitelist relay interface,
|
||||
AccountMeta::new(depositor, false),
|
||||
AccountMeta::new(depositor_authority.pubkey(), true),
|
||||
AccountMeta::new_readonly(spl_token::ID, false),
|
||||
AccountMeta::new(vault, false),
|
||||
AccountMeta::new(vault_acc.owner, false),
|
||||
// Program specific.
|
||||
AccountMeta::new(member, false),
|
||||
AccountMeta::new_readonly(beneficiary.pubkey(), true),
|
||||
AccountMeta::new(entity, false),
|
||||
AccountMeta::new_readonly(registrar, false),
|
||||
AccountMeta::new_readonly(solana_sdk::sysvar::clock::ID, false),
|
||||
AccountMeta::new(vault, false),
|
||||
];
|
||||
let (pool_accs, _) = self.common_pool_accounts(pool_program_id, registrar, false)?;
|
||||
accounts.extend_from_slice(&pool_accs);
|
||||
|
@ -307,14 +309,15 @@ impl Client {
|
|||
amount,
|
||||
pool_program_id,
|
||||
} = req;
|
||||
let r = self.registrar(®istrar)?;
|
||||
let vault_acc = rpc::get_token_account::<TokenAccount>(self.inner.rpc(), &r.vault)?;
|
||||
let vault = self.vault_for(®istrar, &depositor)?;
|
||||
let r = self.registrar(®istrar)?;
|
||||
let vault_acc = rpc::get_token_account::<TokenAccount>(self.inner.rpc(), &vault)?;
|
||||
let mut accounts = vec![
|
||||
// Whitelist relay interface.
|
||||
AccountMeta::new(depositor, false),
|
||||
AccountMeta::new_readonly(beneficiary.pubkey(), true),
|
||||
AccountMeta::new_readonly(spl_token::ID, false),
|
||||
AccountMeta::new(vault, false),
|
||||
AccountMeta::new(vault_acc.owner, false),
|
||||
// Program specific.
|
||||
AccountMeta::new(member, false),
|
||||
|
@ -322,7 +325,6 @@ impl Client {
|
|||
AccountMeta::new(entity, false),
|
||||
AccountMeta::new_readonly(registrar, false),
|
||||
AccountMeta::new_readonly(solana_sdk::sysvar::clock::ID, false),
|
||||
AccountMeta::new(vault, false),
|
||||
];
|
||||
let is_mega = vault == r.mega_vault; // TODO: remove is_mega.
|
||||
let (pool_accs, _) = self.common_pool_accounts(pool_program_id, registrar, is_mega)?;
|
||||
|
|
|
@ -0,0 +1,2 @@
|
|||
pub mod entity;
|
||||
pub mod pool;
|
|
@ -1,7 +1,7 @@
|
|||
use crate::common::invoke_token_transfer;
|
||||
use crate::entity::{with_entity, EntityContext};
|
||||
use crate::pool::{pool_check, Pool, PoolConfig};
|
||||
use crate::common::entity::{with_entity, EntityContext};
|
||||
use crate::common::pool::{pool_check, Pool, PoolConfig};
|
||||
use serum_common::pack::Pack;
|
||||
use serum_common::program::invoke_token_transfer;
|
||||
use serum_registry::access_control;
|
||||
use serum_registry::accounts::{Entity, Member, Registrar};
|
||||
use serum_registry::error::{RegistryError, RegistryErrorCode};
|
||||
|
@ -26,6 +26,8 @@ pub fn handler(
|
|||
let depositor_acc_info = next_account_info(acc_infos)?;
|
||||
let depositor_authority_acc_info = next_account_info(acc_infos)?;
|
||||
let token_program_acc_info = next_account_info(acc_infos)?;
|
||||
let vault_acc_info = next_account_info(acc_infos)?;
|
||||
let vault_authority_acc_info = next_account_info(acc_infos)?;
|
||||
|
||||
// Program specfic.
|
||||
let member_acc_info = next_account_info(acc_infos)?;
|
||||
|
@ -33,7 +35,6 @@ pub fn handler(
|
|||
let entity_acc_info = next_account_info(acc_infos)?;
|
||||
let registrar_acc_info = next_account_info(acc_infos)?;
|
||||
let clock_acc_info = next_account_info(acc_infos)?;
|
||||
let vault_acc_info = next_account_info(acc_infos)?;
|
||||
|
||||
let pool = &Pool::parse_accounts(acc_infos, PoolConfig::GetBasket)?;
|
||||
|
||||
|
@ -54,6 +55,7 @@ pub fn handler(
|
|||
beneficiary_acc_info,
|
||||
entity_acc_info,
|
||||
vault_acc_info,
|
||||
vault_authority_acc_info,
|
||||
program_id,
|
||||
registrar_acc_info,
|
||||
registrar,
|
||||
|
@ -94,6 +96,7 @@ fn access_control(req: AccessControlRequest) -> Result<AccessControlResponse, Re
|
|||
beneficiary_acc_info,
|
||||
entity_acc_info,
|
||||
vault_acc_info,
|
||||
vault_authority_acc_info,
|
||||
registrar_acc_info,
|
||||
registrar,
|
||||
program_id,
|
||||
|
@ -116,13 +119,19 @@ fn access_control(req: AccessControlRequest) -> Result<AccessControlResponse, Re
|
|||
beneficiary_acc_info,
|
||||
program_id,
|
||||
)?;
|
||||
let _vault = access_control::vault(vault_acc_info, registrar_acc_info, registrar, program_id)?;
|
||||
let depositor = access_control::token(depositor_acc_info, depositor_authority_acc_info.key)?;
|
||||
let _vault = access_control::vault_authenticated(
|
||||
vault_acc_info,
|
||||
vault_authority_acc_info,
|
||||
registrar_acc_info,
|
||||
registrar,
|
||||
program_id,
|
||||
)?;
|
||||
pool_check(program_id, pool, registrar_acc_info, registrar, &member)?;
|
||||
|
||||
// Deposit specific.
|
||||
//
|
||||
// Authenticate the delegate boolean.
|
||||
let depositor = access_control::token(depositor_acc_info, depositor_authority_acc_info.key)?;
|
||||
if delegate != (depositor.owner == member.balances.delegate.owner) {
|
||||
return Err(RegistryErrorCode::DepositorOwnerDelegateMismatch)?;
|
||||
}
|
||||
|
@ -171,6 +180,7 @@ struct AccessControlRequest<'a, 'b, 'c> {
|
|||
entity_acc_info: &'a AccountInfo<'b>,
|
||||
vault_acc_info: &'a AccountInfo<'b>,
|
||||
registrar_acc_info: &'a AccountInfo<'b>,
|
||||
vault_authority_acc_info: &'a AccountInfo<'b>,
|
||||
program_id: &'a Pubkey,
|
||||
pool: &'c Pool<'a, 'b>,
|
||||
registrar: &'c Registrar,
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
use crate::common::invoke_token_transfer;
|
||||
use borsh::BorshDeserialize;
|
||||
use serum_common::pack::Pack;
|
||||
use serum_common::program::invoke_token_transfer;
|
||||
use serum_pool_schema::PoolState;
|
||||
use serum_registry::access_control;
|
||||
use serum_registry::accounts::reward_queue::Ring;
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
use crate::common::invoke_token_transfer;
|
||||
use serum_common::program::invoke_token_transfer;
|
||||
use serum_registry::accounts::reward_queue::Ring;
|
||||
use serum_registry::accounts::{RewardEvent, RewardEventQueue};
|
||||
use serum_registry::error::{RegistryError, RegistryErrorCode};
|
||||
|
|
|
@ -15,10 +15,8 @@ mod deposit;
|
|||
mod drop_locked_reward;
|
||||
mod drop_pool_reward;
|
||||
mod end_stake_withdrawal;
|
||||
mod entity;
|
||||
mod initialize;
|
||||
mod mark_generation;
|
||||
mod pool;
|
||||
mod slash;
|
||||
mod stake;
|
||||
mod start_stake_withdrawal;
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
use crate::pool::{pool_check_get_basket, Pool, PoolConfig};
|
||||
use crate::common::pool::{pool_check_get_basket, Pool, PoolConfig};
|
||||
use serum_common::pack::Pack;
|
||||
use serum_registry::access_control;
|
||||
use serum_registry::accounts::{Entity, Generation};
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
use crate::common::invoke_token_transfer;
|
||||
use crate::entity::{with_entity, EntityContext};
|
||||
use crate::pool::{pool_check, Pool, PoolConfig};
|
||||
use crate::common::entity::{with_entity, EntityContext};
|
||||
use crate::common::pool::{pool_check, Pool, PoolConfig};
|
||||
use serum_common::pack::Pack;
|
||||
use serum_common::program::invoke_token_transfer;
|
||||
use serum_registry::access_control;
|
||||
use serum_registry::accounts::{vault, Entity, Member, Registrar};
|
||||
use serum_registry::error::RegistryError;
|
||||
|
@ -121,7 +121,7 @@ fn access_control(req: AccessControlRequest) -> Result<(), RegistryError> {
|
|||
// Account validation.
|
||||
let _entity = access_control::entity(entity_acc_info, registrar_acc_info, program_id)?;
|
||||
let member = access_control::member_raw(member_acc_info, entity_acc_info, program_id)?;
|
||||
let _vault = access_control::vault_join(
|
||||
let _vault = access_control::vault_authenticated(
|
||||
vault_acc_info,
|
||||
vault_authority_acc_info,
|
||||
registrar_acc_info,
|
||||
|
@ -129,7 +129,7 @@ fn access_control(req: AccessControlRequest) -> Result<(), RegistryError> {
|
|||
program_id,
|
||||
)?;
|
||||
if let Some(mega_vault_acc_info) = mega_vault_acc_info {
|
||||
let _mega_vault = access_control::vault_join(
|
||||
let _mega_vault = access_control::vault_authenticated(
|
||||
mega_vault_acc_info,
|
||||
vault_authority_acc_info,
|
||||
registrar_acc_info,
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
use crate::entity::{with_entity, EntityContext};
|
||||
use crate::pool::{pool_check_create, Pool, PoolConfig};
|
||||
use crate::common::entity::{with_entity, EntityContext};
|
||||
use crate::common::pool::{pool_check_create, Pool, PoolConfig};
|
||||
use serum_common::pack::Pack;
|
||||
use serum_registry::access_control;
|
||||
use serum_registry::accounts::{Entity, Member, Registrar};
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
use crate::common::invoke_token_transfer;
|
||||
use crate::entity::{with_entity, EntityContext};
|
||||
use crate::pool::{pool_check, Pool, PoolConfig};
|
||||
use crate::common::entity::{with_entity, EntityContext};
|
||||
use crate::common::pool::{pool_check, Pool, PoolConfig};
|
||||
use serum_common::pack::Pack;
|
||||
use serum_common::program::invoke_token_transfer;
|
||||
use serum_registry::access_control;
|
||||
use serum_registry::accounts::entity::{EntityState, PoolPrices};
|
||||
use serum_registry::accounts::pending_withdrawal::PendingPayment;
|
||||
|
@ -158,7 +158,7 @@ fn access_control(req: AccessControlRequest) -> Result<AccessControlResponse, Re
|
|||
.map_or(Ok(None), |res| res.map(Some))?;
|
||||
|
||||
pool_check(program_id, pool, registrar_acc_info, registrar, &member)?;
|
||||
let _vault = access_control::vault_join(
|
||||
let _vault = access_control::vault_authenticated(
|
||||
vault_acc_info,
|
||||
vault_authority_acc_info,
|
||||
registrar_acc_info,
|
||||
|
@ -166,7 +166,7 @@ fn access_control(req: AccessControlRequest) -> Result<AccessControlResponse, Re
|
|||
program_id,
|
||||
)?;
|
||||
if let Some(mega_vault_acc_info) = mega_vault_acc_info {
|
||||
let _mega_vault = access_control::vault_join(
|
||||
let _mega_vault = access_control::vault_authenticated(
|
||||
mega_vault_acc_info,
|
||||
vault_authority_acc_info,
|
||||
registrar_acc_info,
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
use crate::pool::{pool_check, Pool, PoolConfig};
|
||||
use crate::common::pool::{pool_check, Pool, PoolConfig};
|
||||
use serum_common::pack::*;
|
||||
use serum_registry::access_control;
|
||||
use serum_registry::accounts::{Entity, Member, Registrar};
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
use crate::common::invoke_token_transfer;
|
||||
use crate::entity::{with_entity, EntityContext};
|
||||
use crate::pool::{pool_check, Pool, PoolConfig};
|
||||
use crate::common::entity::{with_entity, EntityContext};
|
||||
use crate::common::pool::{pool_check, Pool, PoolConfig};
|
||||
use serum_common::pack::Pack;
|
||||
use serum_common::program::invoke_token_transfer;
|
||||
use serum_registry::access_control;
|
||||
use serum_registry::accounts::{vault, Entity, Member, Registrar};
|
||||
use serum_registry::error::{RegistryError, RegistryErrorCode};
|
||||
|
@ -26,6 +26,7 @@ pub fn handler(
|
|||
let depositor_acc_info = next_account_info(acc_infos)?;
|
||||
let depositor_authority_acc_info = next_account_info(acc_infos)?;
|
||||
let token_program_acc_info = next_account_info(acc_infos)?;
|
||||
let vault_acc_info = next_account_info(acc_infos)?;
|
||||
let vault_authority_acc_info = next_account_info(acc_infos)?;
|
||||
|
||||
// Program specfic.
|
||||
|
@ -34,7 +35,6 @@ pub fn handler(
|
|||
let entity_acc_info = next_account_info(acc_infos)?;
|
||||
let registrar_acc_info = next_account_info(acc_infos)?;
|
||||
let clock_acc_info = next_account_info(acc_infos)?;
|
||||
let vault_acc_info = next_account_info(acc_infos)?;
|
||||
|
||||
let pool = &Pool::parse_accounts(acc_infos, PoolConfig::GetBasket)?;
|
||||
|
||||
|
@ -111,12 +111,6 @@ fn access_control(req: AccessControlRequest) -> Result<AccessControlResponse, Re
|
|||
return Err(RegistryErrorCode::Unauthorized)?;
|
||||
}
|
||||
if !depositor_authority_acc_info.is_signer {
|
||||
// This check is not strictly necessary. It is used to prevent people
|
||||
// from shooting themselves in the foot, i.e., withdrawing to a delegate
|
||||
// program without the delegate program signing.
|
||||
//
|
||||
// In the common case (i.e. withdrawing without a delegate program),
|
||||
// this authority will be the same as the beneficiary.
|
||||
return Err(RegistryErrorCode::Unauthorized)?;
|
||||
}
|
||||
|
||||
|
@ -127,25 +121,27 @@ fn access_control(req: AccessControlRequest) -> Result<AccessControlResponse, Re
|
|||
beneficiary_acc_info,
|
||||
program_id,
|
||||
)?;
|
||||
let _vault = access_control::vault_join(
|
||||
let _vault = access_control::vault_authenticated(
|
||||
vault_acc_info,
|
||||
vault_authority_acc_info,
|
||||
registrar_acc_info,
|
||||
®istrar,
|
||||
program_id,
|
||||
)?;
|
||||
let depositor = access_control::token(depositor_acc_info, depositor_authority_acc_info.key)?;
|
||||
pool_check(program_id, pool, registrar_acc_info, ®istrar, &member)?;
|
||||
|
||||
// Withdraw specific.
|
||||
//
|
||||
// Authenticate the delegate boolean.
|
||||
let depositor = access_control::token(depositor_acc_info, depositor_authority_acc_info.key)?;
|
||||
if delegate != (depositor.owner == member.balances.delegate.owner) {
|
||||
return Err(RegistryErrorCode::DepositorOwnerDelegateMismatch)?;
|
||||
}
|
||||
// Do we have enough funds for the withdrawal?
|
||||
let is_mega = registrar.is_mega(*vault_acc_info.key)?;
|
||||
if !member.can_withdraw(&pool.prices(), amount, is_mega, depositor.owner)? {
|
||||
return Err(RegistryErrorCode::InsufficientBalance)?;
|
||||
}
|
||||
// Authenticate the delegate boolean.
|
||||
if delegate != (depositor.owner == member.balances.delegate.owner) {
|
||||
return Err(RegistryErrorCode::DepositorOwnerDelegateMismatch)?;
|
||||
}
|
||||
|
||||
Ok(AccessControlResponse { depositor })
|
||||
}
|
||||
|
|
|
@ -214,7 +214,7 @@ pub fn generation_check(
|
|||
Ok(())
|
||||
}
|
||||
|
||||
pub fn vault_join(
|
||||
pub fn vault_authenticated(
|
||||
acc_info: &AccountInfo,
|
||||
vault_authority_acc_info: &AccountInfo,
|
||||
registrar_acc_info: &AccountInfo,
|
||||
|
|
Loading…
Reference in New Issue