registry: Expire reward drops (#61)
This commit is contained in:
parent
aa1833abc5
commit
7e11fa3d7b
|
@ -2,7 +2,9 @@ use anyhow::{anyhow, Result};
|
||||||
use clap::Clap;
|
use clap::Clap;
|
||||||
use serum_common::client::rpc;
|
use serum_common::client::rpc;
|
||||||
use serum_context::Context;
|
use serum_context::Context;
|
||||||
use serum_registry::accounts::{Entity, Member, Registrar};
|
use serum_registry::accounts::{
|
||||||
|
Entity, LockedRewardVendor, Member, Registrar, UnlockedRewardVendor,
|
||||||
|
};
|
||||||
use serum_registry_client::*;
|
use serum_registry_client::*;
|
||||||
use solana_client_gen::prelude::*;
|
use solana_client_gen::prelude::*;
|
||||||
|
|
||||||
|
@ -54,6 +56,28 @@ pub enum Command {
|
||||||
#[clap(short, long)]
|
#[clap(short, long)]
|
||||||
registrar: Pubkey,
|
registrar: Pubkey,
|
||||||
},
|
},
|
||||||
|
/// Sends all leftover funds from an expired unlocked reward vendor to a given
|
||||||
|
/// account.
|
||||||
|
ExpireUnlockedReward {
|
||||||
|
/// The token account to send the leftover rewards to.
|
||||||
|
#[clap(long)]
|
||||||
|
token: Pubkey,
|
||||||
|
#[clap(long)]
|
||||||
|
vendor: Pubkey,
|
||||||
|
#[clap(short, long)]
|
||||||
|
registrar: Pubkey,
|
||||||
|
},
|
||||||
|
/// Sends all leftover funds from an expired locked reward vendor to a given
|
||||||
|
/// account.
|
||||||
|
ExpireLockedReward {
|
||||||
|
/// The token account to send the leftover rewards to.
|
||||||
|
#[clap(long)]
|
||||||
|
token: Pubkey,
|
||||||
|
#[clap(long)]
|
||||||
|
vendor: Pubkey,
|
||||||
|
#[clap(short, long)]
|
||||||
|
registrar: Pubkey,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clap)]
|
#[derive(Debug, Clap)]
|
||||||
|
@ -77,6 +101,14 @@ pub enum AccountsCommand {
|
||||||
#[clap(short, long)]
|
#[clap(short, long)]
|
||||||
address: Option<Pubkey>,
|
address: Option<Pubkey>,
|
||||||
},
|
},
|
||||||
|
LockedVendor {
|
||||||
|
#[clap(short, long)]
|
||||||
|
address: Pubkey,
|
||||||
|
},
|
||||||
|
UnlockedVendor {
|
||||||
|
#[clap(short, long)]
|
||||||
|
address: Pubkey,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn run(ctx: Context, cmd: Command) -> Result<()> {
|
pub fn run(ctx: Context, cmd: Command) -> Result<()> {
|
||||||
|
@ -121,6 +153,34 @@ pub fn run(ctx: Context, cmd: Command) -> Result<()> {
|
||||||
delegate,
|
delegate,
|
||||||
registrar,
|
registrar,
|
||||||
} => create_member_cmd(&ctx, registry_pid, registrar, entity, delegate),
|
} => create_member_cmd(&ctx, registry_pid, registrar, entity, delegate),
|
||||||
|
Command::ExpireUnlockedReward {
|
||||||
|
token,
|
||||||
|
vendor,
|
||||||
|
registrar,
|
||||||
|
} => {
|
||||||
|
let client = ctx.connect::<Client>(registry_pid)?;
|
||||||
|
let resp = client.expire_unlocked_reward(ExpireUnlockedRewardRequest {
|
||||||
|
token,
|
||||||
|
vendor,
|
||||||
|
registrar,
|
||||||
|
})?;
|
||||||
|
println!("Transaction executed: {:?}", resp.tx);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
Command::ExpireLockedReward {
|
||||||
|
token,
|
||||||
|
vendor,
|
||||||
|
registrar,
|
||||||
|
} => {
|
||||||
|
let client = ctx.connect::<Client>(registry_pid)?;
|
||||||
|
let resp = client.expire_locked_reward(ExpireLockedRewardRequest {
|
||||||
|
token,
|
||||||
|
vendor,
|
||||||
|
registrar,
|
||||||
|
})?;
|
||||||
|
println!("Transaction executed: {:?}", resp.tx);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -201,6 +261,14 @@ fn account_cmd(ctx: &Context, registry_pid: Pubkey, cmd: AccountsCommand) -> Res
|
||||||
let acc: Member = rpc::get_account(&rpc_client, &address)?;
|
let acc: Member = rpc::get_account(&rpc_client, &address)?;
|
||||||
println!("{:#?}", acc);
|
println!("{:#?}", acc);
|
||||||
}
|
}
|
||||||
|
AccountsCommand::LockedVendor { address } => {
|
||||||
|
let acc: LockedRewardVendor = rpc::get_account(&rpc_client, &address)?;
|
||||||
|
println!("{:#?}", acc);
|
||||||
|
}
|
||||||
|
AccountsCommand::UnlockedVendor { address } => {
|
||||||
|
let acc: UnlockedRewardVendor = rpc::get_account(&rpc_client, &address)?;
|
||||||
|
println!("{:#?}", acc);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,7 +3,8 @@ use serum_common::pack::*;
|
||||||
use serum_meta_entity::accounts::mqueue::{MQueue, Ring as MQueueRing};
|
use serum_meta_entity::accounts::mqueue::{MQueue, Ring as MQueueRing};
|
||||||
use serum_registry::accounts::reward_queue::{RewardEventQueue, Ring};
|
use serum_registry::accounts::reward_queue::{RewardEventQueue, Ring};
|
||||||
use serum_registry::accounts::{
|
use serum_registry::accounts::{
|
||||||
self, pending_withdrawal, vault, BalanceSandbox, Entity, Member, PendingWithdrawal, Registrar,
|
self, pending_withdrawal, vault, BalanceSandbox, Entity, LockedRewardVendor, Member,
|
||||||
|
PendingWithdrawal, Registrar, UnlockedRewardVendor,
|
||||||
};
|
};
|
||||||
use serum_registry::client::{Client as InnerClient, ClientError as InnerClientError};
|
use serum_registry::client::{Client as InnerClient, ClientError as InnerClientError};
|
||||||
use solana_client_gen::prelude::*;
|
use solana_client_gen::prelude::*;
|
||||||
|
@ -11,6 +12,7 @@ use solana_client_gen::solana_sdk::instruction::AccountMeta;
|
||||||
use solana_client_gen::solana_sdk::pubkey::Pubkey;
|
use solana_client_gen::solana_sdk::pubkey::Pubkey;
|
||||||
use solana_client_gen::solana_sdk::signature::Signature;
|
use solana_client_gen::solana_sdk::signature::Signature;
|
||||||
use solana_client_gen::solana_sdk::signature::{Keypair, Signer};
|
use solana_client_gen::solana_sdk::signature::{Keypair, Signer};
|
||||||
|
use solana_client_gen::solana_sdk::sysvar;
|
||||||
use spl_token::state::Account as TokenAccount;
|
use spl_token::state::Account as TokenAccount;
|
||||||
use std::convert::Into;
|
use std::convert::Into;
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
@ -855,6 +857,64 @@ impl Client {
|
||||||
.switch_entity_with_signers(&[self.payer(), beneficiary], &accs)?;
|
.switch_entity_with_signers(&[self.payer(), beneficiary], &accs)?;
|
||||||
Ok(SwitchEntityResponse { tx })
|
Ok(SwitchEntityResponse { tx })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn expire_unlocked_reward(
|
||||||
|
&self,
|
||||||
|
req: ExpireUnlockedRewardRequest,
|
||||||
|
) -> Result<ExpireUnlockedRewardResponse, ClientError> {
|
||||||
|
let ExpireUnlockedRewardRequest {
|
||||||
|
token,
|
||||||
|
vendor,
|
||||||
|
registrar,
|
||||||
|
} = req;
|
||||||
|
let vendor_acc = self.unlocked_vendor(&vendor)?;
|
||||||
|
let vendor_va = Pubkey::create_program_address(
|
||||||
|
&[registrar.as_ref(), vendor.as_ref(), &[vendor_acc.nonce]],
|
||||||
|
self.program(),
|
||||||
|
)
|
||||||
|
.map_err(|_| ClientError::Any(anyhow::anyhow!("invalid vendor vault authority")))?;
|
||||||
|
let accs = vec![
|
||||||
|
AccountMeta::new_readonly(self.payer().pubkey(), true),
|
||||||
|
AccountMeta::new(token, false),
|
||||||
|
AccountMeta::new(vendor, false),
|
||||||
|
AccountMeta::new(vendor_acc.vault, false),
|
||||||
|
AccountMeta::new_readonly(vendor_va, false),
|
||||||
|
AccountMeta::new_readonly(registrar, false),
|
||||||
|
AccountMeta::new_readonly(spl_token::ID, false),
|
||||||
|
AccountMeta::new_readonly(sysvar::clock::ID, false),
|
||||||
|
];
|
||||||
|
let tx = self.inner.expire_unlocked_reward(&accs)?;
|
||||||
|
Ok(ExpireUnlockedRewardResponse { tx })
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn expire_locked_reward(
|
||||||
|
&self,
|
||||||
|
req: ExpireLockedRewardRequest,
|
||||||
|
) -> Result<ExpireLockedRewardResponse, ClientError> {
|
||||||
|
let ExpireLockedRewardRequest {
|
||||||
|
token,
|
||||||
|
vendor,
|
||||||
|
registrar,
|
||||||
|
} = req;
|
||||||
|
let vendor_acc = self.locked_vendor(&vendor)?;
|
||||||
|
let vendor_va = Pubkey::create_program_address(
|
||||||
|
&[registrar.as_ref(), vendor.as_ref(), &[vendor_acc.nonce]],
|
||||||
|
self.program(),
|
||||||
|
)
|
||||||
|
.map_err(|_| ClientError::Any(anyhow::anyhow!("invalid vendor vault authority")))?;
|
||||||
|
let accs = vec![
|
||||||
|
AccountMeta::new_readonly(self.payer().pubkey(), true),
|
||||||
|
AccountMeta::new(token, false),
|
||||||
|
AccountMeta::new(vendor, false),
|
||||||
|
AccountMeta::new(vendor_acc.vault, false),
|
||||||
|
AccountMeta::new_readonly(vendor_va, false),
|
||||||
|
AccountMeta::new_readonly(registrar, false),
|
||||||
|
AccountMeta::new_readonly(spl_token::ID, false),
|
||||||
|
AccountMeta::new_readonly(sysvar::clock::ID, false),
|
||||||
|
];
|
||||||
|
let tx = self.inner.expire_locked_reward(&accs)?;
|
||||||
|
Ok(ExpireLockedRewardResponse { tx })
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Account accessors.
|
// Account accessors.
|
||||||
|
@ -1008,6 +1068,14 @@ impl Client {
|
||||||
pub fn pending_withdrawal(&self, pw: &Pubkey) -> Result<PendingWithdrawal, ClientError> {
|
pub fn pending_withdrawal(&self, pw: &Pubkey) -> Result<PendingWithdrawal, ClientError> {
|
||||||
rpc::get_account::<PendingWithdrawal>(self.rpc(), pw).map_err(Into::into)
|
rpc::get_account::<PendingWithdrawal>(self.rpc(), pw).map_err(Into::into)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn unlocked_vendor(&self, v: &Pubkey) -> Result<UnlockedRewardVendor, ClientError> {
|
||||||
|
rpc::get_account::<UnlockedRewardVendor>(self.rpc(), v).map_err(Into::into)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn locked_vendor(&self, v: &Pubkey) -> Result<LockedRewardVendor, ClientError> {
|
||||||
|
rpc::get_account::<LockedRewardVendor>(self.rpc(), v).map_err(Into::into)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct ProgramAccount<T> {
|
pub struct ProgramAccount<T> {
|
||||||
|
@ -1247,6 +1315,26 @@ pub struct SwitchEntityResponse {
|
||||||
pub tx: Signature,
|
pub tx: Signature,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub struct ExpireUnlockedRewardRequest {
|
||||||
|
pub token: Pubkey,
|
||||||
|
pub vendor: Pubkey,
|
||||||
|
pub registrar: Pubkey,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct ExpireUnlockedRewardResponse {
|
||||||
|
pub tx: Signature,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct ExpireLockedRewardRequest {
|
||||||
|
pub token: Pubkey,
|
||||||
|
pub vendor: Pubkey,
|
||||||
|
pub registrar: Pubkey,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct ExpireLockedRewardResponse {
|
||||||
|
pub tx: Signature,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Error)]
|
#[derive(Debug, Error)]
|
||||||
pub enum ClientError {
|
pub enum ClientError {
|
||||||
#[error("Client error {0}")]
|
#[error("Client error {0}")]
|
||||||
|
|
|
@ -194,6 +194,14 @@ fn state_transition(req: StateTransitionRequest) -> Result<(), RegistryError> {
|
||||||
clock,
|
clock,
|
||||||
} = req;
|
} = req;
|
||||||
|
|
||||||
|
// Move member rewards cursor.
|
||||||
|
member.rewards_cursor = cursor + 1;
|
||||||
|
|
||||||
|
if vendor.expired {
|
||||||
|
msg!("Vendor expired. Reward not collected");
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
// Create vesting account with proportion of the reward.
|
// Create vesting account with proportion of the reward.
|
||||||
let spt_total = spts.iter().map(|a| a.amount).fold(0, |a, b| a + b);
|
let spt_total = spts.iter().map(|a| a.amount).fold(0, |a, b| a + b);
|
||||||
let amount = spt_total
|
let amount = spt_total
|
||||||
|
@ -258,9 +266,6 @@ fn state_transition(req: StateTransitionRequest) -> Result<(), RegistryError> {
|
||||||
)?;
|
)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Move member rewards cursor.
|
|
||||||
member.rewards_cursor = cursor + 1;
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -176,6 +176,14 @@ fn state_transition(req: StateTransitionRequest) -> Result<(), RegistryError> {
|
||||||
spts,
|
spts,
|
||||||
} = req;
|
} = req;
|
||||||
|
|
||||||
|
// Move member rewards cursor.
|
||||||
|
member.rewards_cursor = cursor + 1;
|
||||||
|
|
||||||
|
if vendor.expired {
|
||||||
|
msg!("Vendor expired. Reward not collected");
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
// Transfer proportion of the reward to the user.
|
// Transfer proportion of the reward to the user.
|
||||||
let spt_total = spts.iter().map(|a| a.amount).fold(0, |a, b| a + b);
|
let spt_total = spts.iter().map(|a| a.amount).fold(0, |a, b| a + b);
|
||||||
let amount = spt_total
|
let amount = spt_total
|
||||||
|
@ -198,9 +206,6 @@ fn state_transition(req: StateTransitionRequest) -> Result<(), RegistryError> {
|
||||||
amount,
|
amount,
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
// Move member rewards cursor.
|
|
||||||
member.rewards_cursor = cursor + 1;
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,156 @@
|
||||||
|
use serum_common::pack::Pack;
|
||||||
|
use serum_common::program::invoke_token_transfer;
|
||||||
|
use serum_registry::access_control;
|
||||||
|
use serum_registry::accounts::LockedRewardVendor;
|
||||||
|
use serum_registry::error::{RegistryError, RegistryErrorCode};
|
||||||
|
use solana_program::msg;
|
||||||
|
use solana_sdk::account_info::{next_account_info, AccountInfo};
|
||||||
|
use solana_sdk::pubkey::Pubkey;
|
||||||
|
use spl_token::state::Account as TokenAccount;
|
||||||
|
|
||||||
|
#[inline(never)]
|
||||||
|
pub fn handler(program_id: &Pubkey, accounts: &[AccountInfo]) -> Result<(), RegistryError> {
|
||||||
|
msg!("handler: expire_locked_reward");
|
||||||
|
|
||||||
|
let acc_infos = &mut accounts.iter();
|
||||||
|
|
||||||
|
let expiry_receiver_acc_info = next_account_info(acc_infos)?;
|
||||||
|
let token_acc_info = next_account_info(acc_infos)?;
|
||||||
|
let vendor_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 registrar_acc_info = next_account_info(acc_infos)?;
|
||||||
|
let token_program_acc_info = next_account_info(acc_infos)?;
|
||||||
|
let clock_acc_info = next_account_info(acc_infos)?;
|
||||||
|
|
||||||
|
let AccessControlResponse { ref vault } = access_control(AccessControlRequest {
|
||||||
|
program_id,
|
||||||
|
registrar_acc_info,
|
||||||
|
vendor_acc_info,
|
||||||
|
vault_acc_info,
|
||||||
|
token_acc_info,
|
||||||
|
expiry_receiver_acc_info,
|
||||||
|
clock_acc_info,
|
||||||
|
})?;
|
||||||
|
|
||||||
|
LockedRewardVendor::unpack_mut(
|
||||||
|
&mut vendor_acc_info.try_borrow_mut_data()?,
|
||||||
|
&mut |vendor: &mut LockedRewardVendor| {
|
||||||
|
state_transition(StateTransitionRequest {
|
||||||
|
vendor,
|
||||||
|
vault,
|
||||||
|
registrar_acc_info,
|
||||||
|
vendor_acc_info,
|
||||||
|
vault_acc_info,
|
||||||
|
vault_authority_acc_info,
|
||||||
|
token_acc_info,
|
||||||
|
token_program_acc_info,
|
||||||
|
})
|
||||||
|
.map_err(Into::into)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.map_err(Into::into)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn access_control(req: AccessControlRequest) -> Result<AccessControlResponse, RegistryError> {
|
||||||
|
msg!("access-control: expire_locked_reward");
|
||||||
|
|
||||||
|
let AccessControlRequest {
|
||||||
|
program_id,
|
||||||
|
expiry_receiver_acc_info,
|
||||||
|
registrar_acc_info,
|
||||||
|
vault_acc_info,
|
||||||
|
vendor_acc_info,
|
||||||
|
token_acc_info,
|
||||||
|
clock_acc_info,
|
||||||
|
} = req;
|
||||||
|
|
||||||
|
// Authorization.
|
||||||
|
if !expiry_receiver_acc_info.is_signer {
|
||||||
|
return Err(RegistryErrorCode::Unauthorized)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Account validation.
|
||||||
|
let _registrar = access_control::registrar(registrar_acc_info, program_id)?;
|
||||||
|
let vendor =
|
||||||
|
access_control::locked_reward_vendor(vendor_acc_info, registrar_acc_info, program_id)?;
|
||||||
|
let vault = access_control::token_account(vault_acc_info)?;
|
||||||
|
let token = access_control::token_account(token_acc_info)?;
|
||||||
|
let clock = access_control::clock(clock_acc_info)?;
|
||||||
|
|
||||||
|
if vendor.expired {
|
||||||
|
return Err(RegistryErrorCode::VendorAlreadyExpired)?;
|
||||||
|
}
|
||||||
|
if &vendor.vault != vault_acc_info.key {
|
||||||
|
return Err(RegistryErrorCode::InvalidVault)?;
|
||||||
|
}
|
||||||
|
if &vendor.expiry_receiver != expiry_receiver_acc_info.key {
|
||||||
|
return Err(RegistryErrorCode::InvalidVault)?;
|
||||||
|
}
|
||||||
|
if &token.owner != expiry_receiver_acc_info.key {
|
||||||
|
return Err(RegistryErrorCode::InvalidAccountOwner)?;
|
||||||
|
}
|
||||||
|
if clock.unix_timestamp <= vendor.expiry_ts {
|
||||||
|
return Err(RegistryErrorCode::VendorNotExpired)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(AccessControlResponse { vault })
|
||||||
|
}
|
||||||
|
|
||||||
|
fn state_transition(req: StateTransitionRequest) -> Result<(), RegistryError> {
|
||||||
|
msg!("state-transition: expire_locked_reward");
|
||||||
|
|
||||||
|
let StateTransitionRequest {
|
||||||
|
vendor,
|
||||||
|
vault,
|
||||||
|
token_acc_info,
|
||||||
|
vendor_acc_info,
|
||||||
|
vault_acc_info,
|
||||||
|
vault_authority_acc_info,
|
||||||
|
registrar_acc_info,
|
||||||
|
token_program_acc_info,
|
||||||
|
} = req;
|
||||||
|
|
||||||
|
let signer_seeds = &[
|
||||||
|
registrar_acc_info.key.as_ref(),
|
||||||
|
vendor_acc_info.key.as_ref(),
|
||||||
|
&[vendor.nonce],
|
||||||
|
];
|
||||||
|
invoke_token_transfer(
|
||||||
|
vault_acc_info,
|
||||||
|
token_acc_info,
|
||||||
|
vault_authority_acc_info,
|
||||||
|
token_program_acc_info,
|
||||||
|
&[signer_seeds],
|
||||||
|
vault.amount,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
vendor.expired = true;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
struct AccessControlRequest<'a, 'b> {
|
||||||
|
program_id: &'a Pubkey,
|
||||||
|
expiry_receiver_acc_info: &'a AccountInfo<'b>,
|
||||||
|
registrar_acc_info: &'a AccountInfo<'b>,
|
||||||
|
vendor_acc_info: &'a AccountInfo<'b>,
|
||||||
|
vault_acc_info: &'a AccountInfo<'b>,
|
||||||
|
token_acc_info: &'a AccountInfo<'b>,
|
||||||
|
clock_acc_info: &'a AccountInfo<'b>,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct AccessControlResponse {
|
||||||
|
vault: TokenAccount,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct StateTransitionRequest<'a, 'b, 'c> {
|
||||||
|
vendor: &'c mut LockedRewardVendor,
|
||||||
|
vault: &'c TokenAccount,
|
||||||
|
registrar_acc_info: &'a AccountInfo<'b>,
|
||||||
|
vendor_acc_info: &'a AccountInfo<'b>,
|
||||||
|
vault_authority_acc_info: &'a AccountInfo<'b>,
|
||||||
|
vault_acc_info: &'a AccountInfo<'b>,
|
||||||
|
token_program_acc_info: &'a AccountInfo<'b>,
|
||||||
|
token_acc_info: &'a AccountInfo<'b>,
|
||||||
|
}
|
|
@ -0,0 +1,156 @@
|
||||||
|
use serum_common::pack::Pack;
|
||||||
|
use serum_common::program::invoke_token_transfer;
|
||||||
|
use serum_registry::access_control;
|
||||||
|
use serum_registry::accounts::UnlockedRewardVendor;
|
||||||
|
use serum_registry::error::{RegistryError, RegistryErrorCode};
|
||||||
|
use solana_program::msg;
|
||||||
|
use solana_sdk::account_info::{next_account_info, AccountInfo};
|
||||||
|
use solana_sdk::pubkey::Pubkey;
|
||||||
|
use spl_token::state::Account as TokenAccount;
|
||||||
|
|
||||||
|
#[inline(never)]
|
||||||
|
pub fn handler(program_id: &Pubkey, accounts: &[AccountInfo]) -> Result<(), RegistryError> {
|
||||||
|
msg!("handler: expire_unlocked_reward");
|
||||||
|
|
||||||
|
let acc_infos = &mut accounts.iter();
|
||||||
|
|
||||||
|
let expiry_receiver_acc_info = next_account_info(acc_infos)?;
|
||||||
|
let token_acc_info = next_account_info(acc_infos)?;
|
||||||
|
let vendor_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 registrar_acc_info = next_account_info(acc_infos)?;
|
||||||
|
let token_program_acc_info = next_account_info(acc_infos)?;
|
||||||
|
let clock_acc_info = next_account_info(acc_infos)?;
|
||||||
|
|
||||||
|
let AccessControlResponse { ref vault } = access_control(AccessControlRequest {
|
||||||
|
program_id,
|
||||||
|
registrar_acc_info,
|
||||||
|
vendor_acc_info,
|
||||||
|
vault_acc_info,
|
||||||
|
token_acc_info,
|
||||||
|
expiry_receiver_acc_info,
|
||||||
|
clock_acc_info,
|
||||||
|
})?;
|
||||||
|
|
||||||
|
UnlockedRewardVendor::unpack_mut(
|
||||||
|
&mut vendor_acc_info.try_borrow_mut_data()?,
|
||||||
|
&mut |vendor: &mut UnlockedRewardVendor| {
|
||||||
|
state_transition(StateTransitionRequest {
|
||||||
|
vendor,
|
||||||
|
vault,
|
||||||
|
registrar_acc_info,
|
||||||
|
vendor_acc_info,
|
||||||
|
vault_acc_info,
|
||||||
|
vault_authority_acc_info,
|
||||||
|
token_acc_info,
|
||||||
|
token_program_acc_info,
|
||||||
|
})
|
||||||
|
.map_err(Into::into)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.map_err(Into::into)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn access_control(req: AccessControlRequest) -> Result<AccessControlResponse, RegistryError> {
|
||||||
|
msg!("access-control: expire_unlocked_reward");
|
||||||
|
|
||||||
|
let AccessControlRequest {
|
||||||
|
program_id,
|
||||||
|
expiry_receiver_acc_info,
|
||||||
|
registrar_acc_info,
|
||||||
|
vault_acc_info,
|
||||||
|
vendor_acc_info,
|
||||||
|
token_acc_info,
|
||||||
|
clock_acc_info,
|
||||||
|
} = req;
|
||||||
|
|
||||||
|
// Authorization.
|
||||||
|
if !expiry_receiver_acc_info.is_signer {
|
||||||
|
return Err(RegistryErrorCode::Unauthorized)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Account validation.
|
||||||
|
let _registrar = access_control::registrar(registrar_acc_info, program_id)?;
|
||||||
|
let vendor =
|
||||||
|
access_control::unlocked_reward_vendor(vendor_acc_info, registrar_acc_info, program_id)?;
|
||||||
|
let vault = access_control::token_account(vault_acc_info)?;
|
||||||
|
let token = access_control::token_account(token_acc_info)?;
|
||||||
|
let clock = access_control::clock(clock_acc_info)?;
|
||||||
|
|
||||||
|
if vendor.expired {
|
||||||
|
return Err(RegistryErrorCode::VendorAlreadyExpired)?;
|
||||||
|
}
|
||||||
|
if &vendor.vault != vault_acc_info.key {
|
||||||
|
return Err(RegistryErrorCode::InvalidVault)?;
|
||||||
|
}
|
||||||
|
if &vendor.expiry_receiver != expiry_receiver_acc_info.key {
|
||||||
|
return Err(RegistryErrorCode::InvalidVault)?;
|
||||||
|
}
|
||||||
|
if &token.owner != expiry_receiver_acc_info.key {
|
||||||
|
return Err(RegistryErrorCode::InvalidAccountOwner)?;
|
||||||
|
}
|
||||||
|
if clock.unix_timestamp <= vendor.expiry_ts {
|
||||||
|
return Err(RegistryErrorCode::VendorNotExpired)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(AccessControlResponse { vault })
|
||||||
|
}
|
||||||
|
|
||||||
|
fn state_transition(req: StateTransitionRequest) -> Result<(), RegistryError> {
|
||||||
|
msg!("state-transition: expire_unlocked_reward");
|
||||||
|
|
||||||
|
let StateTransitionRequest {
|
||||||
|
vendor,
|
||||||
|
vault,
|
||||||
|
token_acc_info,
|
||||||
|
vendor_acc_info,
|
||||||
|
vault_acc_info,
|
||||||
|
vault_authority_acc_info,
|
||||||
|
registrar_acc_info,
|
||||||
|
token_program_acc_info,
|
||||||
|
} = req;
|
||||||
|
|
||||||
|
let signer_seeds = &[
|
||||||
|
registrar_acc_info.key.as_ref(),
|
||||||
|
vendor_acc_info.key.as_ref(),
|
||||||
|
&[vendor.nonce],
|
||||||
|
];
|
||||||
|
invoke_token_transfer(
|
||||||
|
vault_acc_info,
|
||||||
|
token_acc_info,
|
||||||
|
vault_authority_acc_info,
|
||||||
|
token_program_acc_info,
|
||||||
|
&[signer_seeds],
|
||||||
|
vault.amount,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
vendor.expired = true;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
struct AccessControlRequest<'a, 'b> {
|
||||||
|
program_id: &'a Pubkey,
|
||||||
|
expiry_receiver_acc_info: &'a AccountInfo<'b>,
|
||||||
|
registrar_acc_info: &'a AccountInfo<'b>,
|
||||||
|
vendor_acc_info: &'a AccountInfo<'b>,
|
||||||
|
vault_acc_info: &'a AccountInfo<'b>,
|
||||||
|
token_acc_info: &'a AccountInfo<'b>,
|
||||||
|
clock_acc_info: &'a AccountInfo<'b>,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct AccessControlResponse {
|
||||||
|
vault: TokenAccount,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct StateTransitionRequest<'a, 'b, 'c> {
|
||||||
|
vendor: &'c mut UnlockedRewardVendor,
|
||||||
|
vault: &'c TokenAccount,
|
||||||
|
registrar_acc_info: &'a AccountInfo<'b>,
|
||||||
|
vendor_acc_info: &'a AccountInfo<'b>,
|
||||||
|
vault_authority_acc_info: &'a AccountInfo<'b>,
|
||||||
|
vault_acc_info: &'a AccountInfo<'b>,
|
||||||
|
token_program_acc_info: &'a AccountInfo<'b>,
|
||||||
|
token_acc_info: &'a AccountInfo<'b>,
|
||||||
|
}
|
|
@ -16,6 +16,8 @@ mod deposit;
|
||||||
mod drop_locked_reward;
|
mod drop_locked_reward;
|
||||||
mod drop_unlocked_reward;
|
mod drop_unlocked_reward;
|
||||||
mod end_stake_withdrawal;
|
mod end_stake_withdrawal;
|
||||||
|
mod expire_locked_reward;
|
||||||
|
mod expire_unlocked_reward;
|
||||||
mod initialize;
|
mod initialize;
|
||||||
mod stake;
|
mod stake;
|
||||||
mod start_stake_withdrawal;
|
mod start_stake_withdrawal;
|
||||||
|
@ -129,6 +131,12 @@ fn entry(program_id: &Pubkey, accounts: &[AccountInfo], instruction_data: &[u8])
|
||||||
RegistryInstruction::ClaimUnlockedReward { cursor } => {
|
RegistryInstruction::ClaimUnlockedReward { cursor } => {
|
||||||
claim_unlocked_reward::handler(program_id, accounts, cursor)
|
claim_unlocked_reward::handler(program_id, accounts, cursor)
|
||||||
}
|
}
|
||||||
|
RegistryInstruction::ExpireUnlockedReward => {
|
||||||
|
expire_unlocked_reward::handler(program_id, accounts)
|
||||||
|
}
|
||||||
|
RegistryInstruction::ExpireLockedReward => {
|
||||||
|
expire_locked_reward::handler(program_id, accounts)
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
result?;
|
result?;
|
||||||
|
|
|
@ -12,10 +12,6 @@ pub struct LockedRewardVendor {
|
||||||
pub nonce: u8,
|
pub nonce: u8,
|
||||||
pub pool: Pubkey,
|
pub pool: Pubkey,
|
||||||
pub pool_token_supply: u64,
|
pub pool_token_supply: u64,
|
||||||
// The position of the reward event associated with this vendor.
|
|
||||||
// Used to perform access control on member accounts attempting
|
|
||||||
// to claim the reward. Reject any member who's cursor is greater
|
|
||||||
// than this cursor.
|
|
||||||
pub reward_event_q_cursor: u32,
|
pub reward_event_q_cursor: u32,
|
||||||
pub start_ts: i64,
|
pub start_ts: i64,
|
||||||
pub end_ts: i64,
|
pub end_ts: i64,
|
||||||
|
@ -23,6 +19,7 @@ pub struct LockedRewardVendor {
|
||||||
pub expiry_receiver: Pubkey,
|
pub expiry_receiver: Pubkey,
|
||||||
pub total: u64,
|
pub total: u64,
|
||||||
pub period_count: u64,
|
pub period_count: u64,
|
||||||
|
pub expired: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl LockedRewardVendor {
|
impl LockedRewardVendor {
|
||||||
|
|
|
@ -17,6 +17,7 @@ pub struct UnlockedRewardVendor {
|
||||||
pub expiry_ts: i64,
|
pub expiry_ts: i64,
|
||||||
pub expiry_receiver: Pubkey,
|
pub expiry_receiver: Pubkey,
|
||||||
pub total: u64,
|
pub total: u64,
|
||||||
|
pub expired: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl UnlockedRewardVendor {
|
impl UnlockedRewardVendor {
|
||||||
|
|
|
@ -80,6 +80,8 @@ pub enum RegistryErrorCode {
|
||||||
InvalidSpt = 66,
|
InvalidSpt = 66,
|
||||||
InvalidPendingWithdrawalVault = 67,
|
InvalidPendingWithdrawalVault = 67,
|
||||||
InvalidStakeVault = 68,
|
InvalidStakeVault = 68,
|
||||||
|
VendorAlreadyExpired = 69,
|
||||||
|
VendorNotExpired = 70,
|
||||||
Unknown = 1000,
|
Unknown = 1000,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -183,6 +183,20 @@ pub mod instruction {
|
||||||
///
|
///
|
||||||
///
|
///
|
||||||
ClaimUnlockedReward { cursor: u32 },
|
ClaimUnlockedReward { cursor: u32 },
|
||||||
|
/// Accounts:
|
||||||
|
///
|
||||||
|
/// 0. `[signer]` Expiry receiver.
|
||||||
|
/// 1. `[writable]` Token account to send leftover rewards to.
|
||||||
|
/// 2. `[writable]` Vendor.
|
||||||
|
/// 3. `[writable]` Vendor vault.
|
||||||
|
/// 4. `[]` Vendor vault authority.
|
||||||
|
/// 5. `[]` Registrar.
|
||||||
|
/// 6. `[]` Token program.
|
||||||
|
/// 7. `[]` Clock sysvar.
|
||||||
|
ExpireUnlockedReward,
|
||||||
|
/// Same as ExpireUnlockedReward, but with a LockedRewardVendor
|
||||||
|
/// account.
|
||||||
|
ExpireLockedReward,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -21,8 +21,17 @@ STAKE_RATE_MEGA=1
|
||||||
REWARD_ACTIVATION_THRESHOLD=1
|
REWARD_ACTIVATION_THRESHOLD=1
|
||||||
|
|
||||||
CONFIG_FILE=~/.config/serum/cli/dev.yaml
|
CONFIG_FILE=~/.config/serum/cli/dev.yaml
|
||||||
|
serum=$(pwd)/target/debug/serum
|
||||||
|
|
||||||
main() {
|
main() {
|
||||||
|
#
|
||||||
|
# Check the CLI is built or installed.
|
||||||
|
#
|
||||||
|
if ! command -v $serum &> /dev/null
|
||||||
|
then
|
||||||
|
echo "Serum CLI not installed"
|
||||||
|
exit
|
||||||
|
fi
|
||||||
#
|
#
|
||||||
# Build all programs.
|
# Build all programs.
|
||||||
#
|
#
|
||||||
|
@ -47,7 +56,7 @@ main() {
|
||||||
#
|
#
|
||||||
# Generate genesis state.
|
# Generate genesis state.
|
||||||
#
|
#
|
||||||
local genesis=$(cargo run -p serum-cli -- dev init-mint)
|
local genesis=$($serum dev init-mint)
|
||||||
|
|
||||||
local srm_mint=$(echo $genesis | jq .srmMint -r)
|
local srm_mint=$(echo $genesis | jq .srmMint -r)
|
||||||
local msrm_mint=$(echo $genesis | jq .msrmMint -r)
|
local msrm_mint=$(echo $genesis | jq .msrmMint -r)
|
||||||
|
@ -78,7 +87,7 @@ EOM
|
||||||
#
|
#
|
||||||
# Now intialize all the accounts.
|
# Now intialize all the accounts.
|
||||||
#
|
#
|
||||||
local rInit=$(cargo run -p serum-cli -- --config $CONFIG_FILE \
|
local rInit=$($serum --config $CONFIG_FILE \
|
||||||
registry init \
|
registry init \
|
||||||
--deactivation-timelock $DEACTIVATION_TIMELOCK \
|
--deactivation-timelock $DEACTIVATION_TIMELOCK \
|
||||||
--reward-activation-threshold $REWARD_ACTIVATION_THRESHOLD \
|
--reward-activation-threshold $REWARD_ACTIVATION_THRESHOLD \
|
||||||
|
@ -91,7 +100,7 @@ EOM
|
||||||
local registrar_nonce=$(echo $rInit | jq .nonce -r)
|
local registrar_nonce=$(echo $rInit | jq .nonce -r)
|
||||||
local reward_q=$(echo $rInit | jq .rewardEventQueue -r)
|
local reward_q=$(echo $rInit | jq .rewardEventQueue -r)
|
||||||
|
|
||||||
local lInit=$(cargo run -p serum-cli -- --config $CONFIG_FILE \
|
local lInit=$($serum --config $CONFIG_FILE \
|
||||||
lockup \
|
lockup \
|
||||||
initialize)
|
initialize)
|
||||||
|
|
||||||
|
@ -101,7 +110,7 @@ EOM
|
||||||
# Initialize a node entity. Hack until we separate joining entities
|
# Initialize a node entity. Hack until we separate joining entities
|
||||||
# from creating member accounts.
|
# from creating member accounts.
|
||||||
#
|
#
|
||||||
local createEntity=$(cargo run -p serum-cli -- --config $CONFIG_FILE \
|
local createEntity=$($serum --config $CONFIG_FILE \
|
||||||
registry create-entity \
|
registry create-entity \
|
||||||
--registrar $registrar \
|
--registrar $registrar \
|
||||||
--about "This the default entity all new members join." \
|
--about "This the default entity all new members join." \
|
||||||
|
@ -114,7 +123,7 @@ EOM
|
||||||
#
|
#
|
||||||
# Add the registry to the lockup program whitelist.
|
# Add the registry to the lockup program whitelist.
|
||||||
#
|
#
|
||||||
cargo run -p serum-cli -- --config $CONFIG_FILE \
|
$serum --config $CONFIG_FILE \
|
||||||
lockup gov \
|
lockup gov \
|
||||||
--safe $safe \
|
--safe $safe \
|
||||||
whitelist-add \
|
whitelist-add \
|
||||||
|
|
Loading…
Reference in New Issue