diff --git a/Cargo.lock b/Cargo.lock index 1766639..09ae57e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3331,6 +3331,7 @@ dependencies = [ "serde_json", "serum-common", "serum-context", + "serum-meta-entity", "serum-registry", "serum-registry-client", "solana-client", @@ -3349,7 +3350,6 @@ dependencies = [ "lazy_static", "serum-common", "serum-meta-entity", - "serum-pool-schema", "serum-registry", "solana-client", "solana-client-gen", diff --git a/lockup/client/src/lib.rs b/lockup/client/src/lib.rs index 5caeeec..99062d4 100644 --- a/lockup/client/src/lib.rs +++ b/lockup/client/src/lib.rs @@ -120,11 +120,11 @@ impl Client { let whitelist = s.whitelist; let mut accounts = vec![ AccountMeta::new_readonly(beneficiary.pubkey(), true), - AccountMeta::new(vesting, false), AccountMeta::new_readonly(safe, false), AccountMeta::new_readonly(whitelist, false), AccountMeta::new_readonly(whitelist_program, false), // Below are relay accounts. + AccountMeta::new(vesting, false), AccountMeta::new(vault, false), AccountMeta::new_readonly( self.vault_authority(safe, vesting, beneficiary.pubkey())?, @@ -170,13 +170,13 @@ impl Client { let whitelist = s.whitelist; let mut accounts = vec![ AccountMeta::new_readonly(beneficiary.pubkey(), true), - AccountMeta::new(vesting, false), AccountMeta::new_readonly(safe, false), AccountMeta::new_readonly(whitelist, false), AccountMeta::new_readonly(whitelist_program, false), // Below are relay accounts. // // Whitelist relay interface. + AccountMeta::new(vesting, false), AccountMeta::new(vault, false), AccountMeta::new_readonly( self.vault_authority(safe, vesting, beneficiary.pubkey())?, diff --git a/lockup/program/src/common/mod.rs b/lockup/program/src/common/mod.rs index a9145d9..51dd401 100644 --- a/lockup/program/src/common/mod.rs +++ b/lockup/program/src/common/mod.rs @@ -1,5 +1,4 @@ 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; @@ -11,9 +10,9 @@ pub fn whitelist_cpi( instruction: Instruction, safe: &Pubkey, beneficiary_acc_info: &AccountInfo, - vesting: &Vesting, + vesting_nonce: u8, accounts: &[AccountInfo], ) -> ProgramResult { - let signer_seeds = vault::signer_seeds(safe, beneficiary_acc_info.key, &vesting.nonce); + let signer_seeds = vault::signer_seeds(safe, beneficiary_acc_info.key, &vesting_nonce); solana_sdk::program::invoke_signed(&instruction, accounts, &[&signer_seeds]) } diff --git a/lockup/program/src/whitelist_deposit.rs b/lockup/program/src/whitelist_deposit.rs index 2660f6a..758adf2 100644 --- a/lockup/program/src/whitelist_deposit.rs +++ b/lockup/program/src/whitelist_deposit.rs @@ -7,7 +7,6 @@ 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 std::convert::Into; use std::iter::Iterator; pub fn handler( @@ -20,7 +19,6 @@ pub fn handler( 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 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)?; @@ -28,6 +26,7 @@ pub fn handler( // Below accounts are relayed. // Whitelist interface. + let vesting_acc_info = next_account_info(acc_infos)?; 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)?; @@ -37,7 +36,10 @@ pub fn handler( // Program specific. let remaining_relay_accs = acc_infos; - access_control(AccessControlRequest { + let AccessControlResponse { + vesting_nonce, + vesting_whitelist_owned, + } = access_control(AccessControlRequest { program_id, beneficiary_acc_info, vesting_acc_info, @@ -49,32 +51,27 @@ pub fn handler( vault_acc_info, vault_auth_acc_info, })?; - - Vesting::unpack_unchecked_mut( - &mut vesting_acc_info.try_borrow_mut_data()?, - &mut |vesting: &mut Vesting| { - state_transition(StateTransitionRequest { - accounts, - 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, - }) - .map_err(Into::into) - }, - )?; + state_transition(StateTransitionRequest { + accounts, + 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_nonce, + vesting_whitelist_owned, + vesting_acc_info, + beneficiary_acc_info, + remaining_relay_accs, + })?; Ok(()) } -fn access_control(req: AccessControlRequest) -> Result<(), LockupError> { +fn access_control(req: AccessControlRequest) -> Result { msg!("access-control: whitelist_deposit"); let AccessControlRequest { @@ -107,7 +104,7 @@ fn access_control(req: AccessControlRequest) -> Result<(), LockupError> { safe_acc_info, program_id, )?; - let _vesting = access_control::vesting( + let vesting = access_control::vesting( program_id, safe_acc_info, vesting_acc_info, @@ -129,14 +126,19 @@ fn access_control(req: AccessControlRequest) -> Result<(), LockupError> { return Err(LockupErrorCode::InvalidTokenAccountOwner)?; } - Ok(()) + Ok(AccessControlResponse { + vesting_nonce: vesting.nonce, + vesting_whitelist_owned: vesting.whitelist_owned, + }) } fn state_transition(req: StateTransitionRequest) -> Result<(), LockupError> { msg!("state-transition: whitelist_deposit"); let StateTransitionRequest { - vesting, + vesting_nonce, + vesting_whitelist_owned, + vesting_acc_info, instruction_data, accounts, safe_acc_info, @@ -158,6 +160,7 @@ fn state_transition(req: StateTransitionRequest) -> Result<(), LockupError> { // Invoke relay, signing with the program-derived-address. { let mut meta_accounts = vec![ + AccountMeta::new_readonly(*vesting_acc_info.key, false), 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), @@ -180,7 +183,7 @@ fn state_transition(req: StateTransitionRequest) -> Result<(), LockupError> { relay_instruction, safe_acc_info.key, beneficiary_acc_info, - vesting, + vesting_nonce, accounts, )?; } @@ -197,11 +200,19 @@ fn state_transition(req: StateTransitionRequest) -> Result<(), LockupError> { return Err(LockupErrorCode::InsufficientDepositAmount)?; } // Cannot deposit more than withdrawn. - if deposit_amount > vesting.whitelist_owned { + if deposit_amount > vesting_whitelist_owned { return Err(LockupErrorCode::DepositOverflow)?; } - // Book keeping. - vesting.whitelist_owned -= deposit_amount; + + Vesting::unpack_unchecked_mut( + &mut vesting_acc_info.try_borrow_mut_data()?, + &mut |vesting: &mut Vesting| { + // Book keeping. + vesting.whitelist_owned -= deposit_amount; + + Ok(()) + }, + )?; Ok(()) } @@ -219,6 +230,11 @@ struct AccessControlRequest<'a, 'b> { vault_auth_acc_info: &'a AccountInfo<'b>, } +struct AccessControlResponse { + vesting_nonce: u8, + vesting_whitelist_owned: u64, +} + struct StateTransitionRequest<'a, 'b, 'c> { remaining_relay_accs: &'c mut dyn Iterator>, accounts: &'a [AccountInfo<'b>], @@ -230,6 +246,8 @@ struct StateTransitionRequest<'a, 'b, 'c> { wl_prog_vault_authority_acc_info: &'a AccountInfo<'b>, tok_prog_acc_info: &'a AccountInfo<'b>, beneficiary_acc_info: &'a AccountInfo<'b>, + vesting_acc_info: &'a AccountInfo<'b>, instruction_data: &'c [u8], - vesting: &'c mut Vesting, + vesting_nonce: u8, + vesting_whitelist_owned: u64, } diff --git a/lockup/program/src/whitelist_withdraw.rs b/lockup/program/src/whitelist_withdraw.rs index 9c07b66..b09613b 100644 --- a/lockup/program/src/whitelist_withdraw.rs +++ b/lockup/program/src/whitelist_withdraw.rs @@ -8,7 +8,6 @@ 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, @@ -21,7 +20,6 @@ pub fn handler( 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 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)?; @@ -29,6 +27,7 @@ pub fn handler( // Below accounts are relayed. // Whitelist interface. + let vesting_acc_info = next_account_info(acc_infos)?; 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)?; @@ -38,7 +37,7 @@ pub fn handler( // Program specific. let remaining_relay_accs = acc_infos; - access_control(AccessControlRequest { + let AccessControlResponse { vesting_nonce } = access_control(AccessControlRequest { program_id, beneficiary_acc_info, vesting_acc_info, @@ -51,33 +50,27 @@ pub fn handler( vault_auth_acc_info, amount, })?; - - Vesting::unpack_unchecked_mut( - &mut vesting_acc_info.try_borrow_mut_data()?, - &mut |vesting: &mut Vesting| { - state_transition(StateTransitionRequest { - accounts, - amount, - 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, - }) - .map_err(Into::into) - }, - )?; + state_transition(StateTransitionRequest { + accounts, + amount, + vesting_nonce, + vesting_acc_info, + 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, + beneficiary_acc_info, + remaining_relay_accs, + })?; Ok(()) } -fn access_control(req: AccessControlRequest) -> Result<(), LockupError> { +fn access_control(req: AccessControlRequest) -> Result { msg!("access-control: whitelist_withdraw"); let AccessControlRequest { @@ -137,14 +130,17 @@ fn access_control(req: AccessControlRequest) -> Result<(), LockupError> { return Err(LockupErrorCode::InvalidTokenAccountOwner)?; } - Ok(()) + Ok(AccessControlResponse { + vesting_nonce: vesting.nonce, + }) } fn state_transition(req: StateTransitionRequest) -> Result<(), LockupError> { msg!("state-transition: whitelist_withdraw"); let StateTransitionRequest { - vesting, + vesting_acc_info, + vesting_nonce, instruction_data, beneficiary_acc_info, accounts, @@ -167,6 +163,7 @@ fn state_transition(req: StateTransitionRequest) -> Result<(), LockupError> { // Invoke relay. { let mut meta_accounts = vec![ + AccountMeta::new_readonly(*vesting_acc_info.key, false), 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), @@ -189,7 +186,7 @@ fn state_transition(req: StateTransitionRequest) -> Result<(), LockupError> { relay_instruction, safe_acc_info.key, beneficiary_acc_info, - vesting, + vesting_nonce, accounts, )?; } @@ -205,8 +202,14 @@ fn state_transition(req: StateTransitionRequest) -> Result<(), LockupError> { if amount_transferred > amount { return Err(LockupErrorCode::InsufficientAmount)?; } - // Book keeping. - vesting.whitelist_owned += amount_transferred; + + Vesting::unpack_unchecked_mut( + &mut vesting_acc_info.try_borrow_mut_data()?, + &mut |vesting: &mut Vesting| { + vesting.whitelist_owned += amount_transferred; + Ok(()) + }, + )?; Ok(()) } @@ -225,6 +228,10 @@ struct AccessControlRequest<'a, 'b> { amount: u64, } +struct AccessControlResponse { + vesting_nonce: u8, +} + struct StateTransitionRequest<'a, 'b, 'c> { remaining_relay_accs: &'c mut dyn Iterator>, accounts: &'a [AccountInfo<'b>], @@ -236,7 +243,8 @@ struct StateTransitionRequest<'a, 'b, 'c> { vault_auth_acc_info: &'a AccountInfo<'b>, beneficiary_acc_info: &'a AccountInfo<'b>, safe_acc_info: &'a AccountInfo<'b>, + vesting_acc_info: &'a AccountInfo<'b>, instruction_data: &'c [u8], - vesting: &'c mut Vesting, + vesting_nonce: u8, amount: u64, } diff --git a/registry/cli/Cargo.toml b/registry/cli/Cargo.toml index 48c0ef6..c3d81c2 100644 --- a/registry/cli/Cargo.toml +++ b/registry/cli/Cargo.toml @@ -10,6 +10,7 @@ serum-context = { path = "../../context" } serum-common = { path = "../../common" } serum-registry = { path = "../", features = ["client"] } serum-registry-client = { path = "../client" } +serum-meta-entity = { path = "../meta-entity", features = ["client"] } solana-client-gen = { path = "../../solana-client-gen" } spl-token = { version = "2.0.5", default-features = false } serde = { version = "1.0", features = ["derive"] } diff --git a/registry/cli/src/lib.rs b/registry/cli/src/lib.rs index f922cbb..54eb500 100644 --- a/registry/cli/src/lib.rs +++ b/registry/cli/src/lib.rs @@ -1,7 +1,8 @@ -use anyhow::{anyhow, Result}; +use anyhow::Result; use clap::Clap; use serum_common::client::rpc; use serum_context::Context; +use serum_meta_entity::accounts::Metadata; use serum_registry::accounts::{ Entity, LockedRewardVendor, Member, Registrar, UnlockedRewardVendor, }; @@ -20,9 +21,6 @@ pub enum Command { /// Seoncds until deactivation. #[clap(short = 't', long, default_value = "10000")] deactivation_timelock: i64, - /// SRM equivalent amount required for node activation. - #[clap(short, long, default_value = "10_000_000")] - reward_activation_threshold: u64, #[clap(short, long)] max_stake_per_entity: u64, #[clap(short, long)] @@ -30,10 +28,8 @@ pub enum Command { #[clap(short = 'b', long)] stake_rate_mega: u64, }, - /// Creates and registers a delegated staked node entity. + /// Creates a node entity, setting the active wallet as leader. CreateEntity { - #[clap(short, long)] - meta_entity_program_id: Pubkey, /// Registrar account address. #[clap(short, long)] registrar: Pubkey, @@ -42,21 +38,20 @@ pub enum Command { #[clap(short, long)] about: String, #[clap(short, long)] - image_url: String, + image_url: Option, }, - /// Joins an entity, creating an associated member account. - CreateMember { - /// Node entity to join with. + /// Updates an entity. Active wallet must be the node leader. + UpdateEntity { + #[clap(short, long)] + name: Option, + #[clap(short, long)] + about: Option, + #[clap(short, long)] + image_url: Option, #[clap(short, long)] entity: Pubkey, - /// Delegate of the member account [optional]. - #[clap(short, long)] - delegate: Option, - /// Registrar account address. - #[clap(short, long)] - registrar: Pubkey, }, - /// Sends all leftover funds from an expired unlocked reward vendor to a given + /// Sends all unclaimed funds from an expired unlocked reward vendor to a given /// account. ExpireUnlockedReward { /// The token account to send the leftover rewards to. @@ -67,7 +62,7 @@ pub enum Command { #[clap(short, long)] registrar: Pubkey, }, - /// Sends all leftover funds from an expired locked reward vendor to a given + /// Sends all unclaimed funds from an expired locked reward vendor to a given /// account. ExpireLockedReward { /// The token account to send the leftover rewards to. @@ -96,10 +91,9 @@ pub enum AccountsCommand { }, /// View a member of a node entity. Member { - /// Address of the stake account [optional]. If not provided, the - /// first derived stake address will be used for the configured wallet. + /// Address of the member stake account. #[clap(short, long)] - address: Option, + address: Pubkey, }, LockedVendor { #[clap(short, long)] @@ -115,11 +109,10 @@ pub fn run(ctx: Context, cmd: Command) -> Result<()> { let registry_pid = ctx.registry_pid; match cmd { - Command::Accounts(cmd) => account_cmd(&ctx, registry_pid, cmd), + Command::Accounts(cmd) => account_cmd(&ctx, cmd), Command::Init { withdrawal_timelock, deactivation_timelock, - reward_activation_threshold, max_stake_per_entity, stake_rate, stake_rate_mega, @@ -128,7 +121,6 @@ pub fn run(ctx: Context, cmd: Command) -> Result<()> { registry_pid, withdrawal_timelock, deactivation_timelock, - reward_activation_threshold, max_stake_per_entity, stake_rate, stake_rate_mega, @@ -138,21 +130,13 @@ pub fn run(ctx: Context, cmd: Command) -> Result<()> { name, about, image_url, - meta_entity_program_id, - } => create_entity_cmd( - &ctx, - registry_pid, - registrar, + } => create_entity_cmd(&ctx, registry_pid, registrar, name, about, image_url), + Command::UpdateEntity { name, about, image_url, - meta_entity_program_id, - ), - Command::CreateMember { entity, - delegate, - registrar, - } => create_member_cmd(&ctx, registry_pid, registrar, entity, delegate), + } => update_entity_cmd(&ctx, name, about, image_url, entity), Command::ExpireUnlockedReward { token, vendor, @@ -184,38 +168,13 @@ pub fn run(ctx: Context, cmd: Command) -> Result<()> { } } -fn create_member_cmd( - ctx: &Context, - registry_pid: Pubkey, - registrar: Pubkey, - entity: Pubkey, - delegate: Option, -) -> Result<()> { - let delegate = delegate.unwrap_or(Pubkey::new_from_array([0; 32])); - - let client = ctx.connect::(registry_pid)?; - - let CreateMemberResponse { tx, member } = client.create_member(CreateMemberRequest { - entity, - beneficiary: &ctx.wallet()?, - delegate, - registrar, - })?; - - println!("Confirmed transaction: {:?}", tx); - println!("Created node entity member with address: {:?}", member); - - Ok(()) -} - fn create_entity_cmd( ctx: &Context, registry_pid: Pubkey, registrar: Pubkey, name: String, about: String, - image_url: String, - meta_entity_program_id: Pubkey, + image_url: Option, ) -> Result<()> { let leader_kp = ctx.wallet()?; @@ -226,8 +185,8 @@ fn create_entity_cmd( metadata: Some(EntityMetadata { name, about, - image_url, - meta_entity_program_id, + image_url: image_url.unwrap_or("".to_string()), + meta_entity_program_id: ctx.meta_entity_pid, }), })?; @@ -236,7 +195,27 @@ fn create_entity_cmd( Ok(()) } -fn account_cmd(ctx: &Context, registry_pid: Pubkey, cmd: AccountsCommand) -> Result<()> { +fn update_entity_cmd( + ctx: &Context, + name: Option, + about: Option, + image_url: Option, + entity: Pubkey, +) -> Result<()> { + let client = ctx.connect::(ctx.registry_pid)?; + let resp = client.update_entity_metadata(UpdateEntityMetadataRequest { + name, + about, + image_url, + entity, + meta_entity_pid: ctx.meta_entity_pid, + })?; + println!("Transaction signature: {}", resp.tx.to_string()); + + Ok(()) +} + +fn account_cmd(ctx: &Context, cmd: AccountsCommand) -> Result<()> { let rpc_client = ctx.rpc_client(); match cmd { @@ -246,18 +225,11 @@ fn account_cmd(ctx: &Context, registry_pid: Pubkey, cmd: AccountsCommand) -> Res } AccountsCommand::Entity { address } => { let acc: Entity = rpc::get_account_unchecked(&rpc_client, &address)?; + let m: Metadata = rpc::get_account_unchecked(&rpc_client, &acc.metadata)?; println!("{:#?}", acc); + println!("{:#?}", m); } AccountsCommand::Member { address } => { - let address = match address { - Some(a) => a, - None => Pubkey::create_with_seed( - &ctx.wallet()?.pubkey(), - Client::member_seed(), - ®istry_pid, - ) - .map_err(|e| anyhow!("unable to derive stake address: {}", e.to_string()))?, - }; let acc: Member = rpc::get_account(&rpc_client, &address)?; println!("{:#?}", acc); } @@ -278,7 +250,6 @@ pub fn init( registry_pid: Pubkey, withdrawal_timelock: i64, deactivation_timelock: i64, - reward_activation_threshold: u64, max_stake_per_entity: u64, stake_rate: u64, stake_rate_mega: u64, @@ -297,7 +268,6 @@ pub fn init( deactivation_timelock, mint: ctx.srm_mint, mega_mint: ctx.msrm_mint, - reward_activation_threshold, max_stake_per_entity, stake_rate, stake_rate_mega, diff --git a/registry/client/Cargo.toml b/registry/client/Cargo.toml index 98f2747..53c58c3 100644 --- a/registry/client/Cargo.toml +++ b/registry/client/Cargo.toml @@ -15,6 +15,4 @@ borsh = { git = "https://github.com/project-serum/borsh", branch = "serum" } serum-registry = { path = "../", features = ["client"] } solana-client-gen = { path = "../../solana-client-gen" } serum-common = { path = "../../common" } -# todo: remove -serum-pool-schema = { path = "../../pool/schema" } -serum-meta-entity = { path = "../meta-entity" } +serum-meta-entity = { path = "../meta-entity", features = ["client"] } diff --git a/registry/client/src/lib.rs b/registry/client/src/lib.rs index d961b57..9231490 100644 --- a/registry/client/src/lib.rs +++ b/registry/client/src/lib.rs @@ -1,6 +1,7 @@ use serum_common::client::rpc; use serum_common::pack::*; use serum_meta_entity::accounts::mqueue::{MQueue, Ring as MQueueRing}; +use serum_meta_entity::client::Client as MetaEntityClient; use serum_registry::accounts::reward_queue::{RewardEventQueue, Ring}; use serum_registry::accounts::{ self, pending_withdrawal, vault, BalanceSandbox, Entity, LockedRewardVendor, Member, @@ -32,7 +33,6 @@ impl Client { withdrawal_timelock, deactivation_timelock, max_stake_per_entity, - reward_activation_threshold, mint, mega_mint, stake_rate, @@ -113,7 +113,6 @@ impl Client { nonce, withdrawal_timelock, deactivation_timelock, - reward_activation_threshold, max_stake_per_entity, stake_rate, stake_rate_mega, @@ -269,6 +268,37 @@ impl Client { Ok(UpdateEntityResponse { tx }) } + pub fn update_entity_metadata( + &self, + req: UpdateEntityMetadataRequest, + ) -> Result { + let UpdateEntityMetadataRequest { + name, + about, + image_url, + meta_entity_pid, + entity, + } = req; + + let entity = self.entity(&entity)?; + + let accounts = [ + AccountMeta::new(entity.metadata, false), + AccountMeta::new_readonly(self.payer().pubkey(), true), + ]; + + let client = MetaEntityClient::new( + meta_entity_pid, + Keypair::from_bytes(&self.payer().to_bytes()).expect("invalid payer"), + self.inner.url(), + Some(self.inner.options().clone()), + ); + client + .update(&accounts, name, about, image_url, None) + .map(|tx| UpdateEntityMetadataResponse { tx }) + .map_err(|err| ClientError::Any(anyhow::anyhow!("{}", err.to_string()))) + } + pub fn create_member( &self, req: CreateMemberRequest, @@ -529,10 +559,15 @@ impl Client { amount, } = req; + // Dummy account to pass into the instruction, since it conforms to the + // lockup program's whitelist withdraw/deposit interface. + let dummy_account_meta = AccountMeta::new_readonly(sysvar::clock::ID, false); + let vault = self.vault_for(&member, &depositor, false)?; let vault_acc = rpc::get_token_account::(self.rpc(), &vault)?; let accounts = vec![ // Whitelist relay interface, + dummy_account_meta, AccountMeta::new(depositor, false), AccountMeta::new(depositor_authority.pubkey(), true), AccountMeta::new_readonly(spl_token::ID, false), @@ -563,10 +598,16 @@ impl Client { registrar, amount, } = req; + + // Dummy account to pass into the instruction, since it conforms to the + // lockup program's whitelist withdraw/deposit interface. + let dummy_account_meta = AccountMeta::new_readonly(sysvar::clock::ID, false); + let vault = self.vault_for(&member, &depositor, false)?; let vault_acc = rpc::get_token_account::(self.rpc(), &vault)?; let accounts = vec![ // Whitelist relay interface. + dummy_account_meta, AccountMeta::new(depositor, false), AccountMeta::new_readonly(beneficiary.pubkey(), true), AccountMeta::new_readonly(spl_token::ID, false), @@ -846,11 +887,11 @@ impl Client { AccountMeta::new_readonly(solana_sdk::sysvar::clock::ID, false), AccountMeta::new_readonly(self.vault_authority(®istrar)?, false), AccountMeta::new_readonly(m.balances[0].owner, false), - AccountMeta::new_readonly(m.balances[0].vault_stake, false), - AccountMeta::new_readonly(m.balances[0].vault_stake_mega, false), + AccountMeta::new_readonly(m.balances[0].spt, false), + AccountMeta::new_readonly(m.balances[0].spt_mega, false), AccountMeta::new_readonly(m.balances[1].owner, false), - AccountMeta::new_readonly(m.balances[1].vault_stake, false), - AccountMeta::new_readonly(m.balances[1].vault_stake_mega, false), + AccountMeta::new_readonly(m.balances[1].spt, false), + AccountMeta::new_readonly(m.balances[1].spt_mega, false), ]; let tx = self .inner @@ -1115,16 +1156,23 @@ fn create_metadata_instructions( about: String, image_url: String, ) -> Vec { - let md = serum_meta_entity::accounts::Metadata { - initialized: false, - entity: Pubkey::new_from_array([0; 32]), - authority: *payer, - name: name.clone(), - about: about.clone(), - image_url: image_url.clone(), - chat: Pubkey::new_from_array([0; 32]), + let metadata_size = { + // 280 chars max. + let max_name = "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff" + .to_string(); + let max_about = max_name.clone(); + let max_image_url = max_name.clone(); + let md = serum_meta_entity::accounts::Metadata { + initialized: false, + entity: Pubkey::new_from_array([0; 32]), + authority: *payer, + name: max_name, + about: max_about, + image_url: max_image_url, + chat: Pubkey::new_from_array([0; 32]), + }; + md.size().unwrap() }; - let metadata_size = md.size().unwrap(); let lamports = client .get_minimum_balance_for_rent_exemption(metadata_size as usize) .unwrap(); @@ -1179,7 +1227,6 @@ pub struct InitializeRequest { pub withdrawal_timelock: i64, pub deactivation_timelock: i64, pub max_stake_per_entity: u64, - pub reward_activation_threshold: u64, pub mint: Pubkey, pub mega_mint: Pubkey, pub stake_rate: u64, @@ -1223,6 +1270,18 @@ pub struct UpdateEntityResponse { pub tx: Signature, } +pub struct UpdateEntityMetadataRequest { + pub name: Option, + pub about: Option, + pub image_url: Option, + pub meta_entity_pid: Pubkey, + pub entity: Pubkey, +} + +pub struct UpdateEntityMetadataResponse { + pub tx: Signature, +} + pub struct CreateMemberRequest<'a> { pub entity: Pubkey, pub delegate: Pubkey, diff --git a/registry/program/src/claim_locked_reward.rs b/registry/program/src/claim_locked_reward.rs index 94a42e5..c04dcab 100644 --- a/registry/program/src/claim_locked_reward.rs +++ b/registry/program/src/claim_locked_reward.rs @@ -39,7 +39,8 @@ pub fn handler( let clock_acc_info = next_account_info(acc_infos)?; let mut spt_acc_infos = vec![]; - while acc_infos.len() > 0 { + // 2: Main and locked balances. + for _ in 0..2 { spt_acc_infos.push(next_account_info(acc_infos)?); } @@ -205,10 +206,17 @@ fn state_transition(req: StateTransitionRequest) -> Result<(), RegistryError> { // Create vesting account with proportion of the reward. let spt_total = spts.iter().map(|a| a.amount).fold(0, |a, b| a + b); let amount = spt_total - .checked_div(vendor.pool_token_supply) - .unwrap() .checked_mul(vendor.total) + .unwrap() + .checked_div(vendor.pool_token_supply) .unwrap(); + + if amount <= 0 { + // Invariant violation. + msg!("Invalid reward calculation."); + return Err(RegistryErrorCode::Unknown)?; + } + // Lockup program requires the timestamp to be <= clock's timestamp. // So update if the time has already passed. let end_ts = { @@ -218,53 +226,52 @@ fn state_transition(req: StateTransitionRequest) -> Result<(), RegistryError> { vendor.end_ts } }; - if amount > 0 { - let ix = { - let instr = LockupInstruction::CreateVesting { - beneficiary: member.beneficiary, - end_ts, - period_count: vendor.period_count, - deposit_amount: amount, - nonce, - }; - let mut data = vec![0u8; instr.size()? as usize]; - LockupInstruction::pack(instr, &mut data)?; - Instruction { - program_id: *lockup_program_acc_info.key, - accounts: vec![ - AccountMeta::new(*vesting_acc_info.key, false), - AccountMeta::new(vendor.vault, false), - AccountMeta::new_readonly(*vendor_vault_authority_acc_info.key, true), - AccountMeta::new(*vesting_vault_acc_info.key, false), - AccountMeta::new_readonly(*safe_acc_info.key, false), - AccountMeta::new_readonly(spl_token::ID, false), - AccountMeta::new_readonly(sysvar::rent::ID, false), - AccountMeta::new_readonly(sysvar::clock::ID, false), - ], - data, - } + + let ix = { + let instr = LockupInstruction::CreateVesting { + beneficiary: member.beneficiary, + end_ts, + period_count: vendor.period_count, + deposit_amount: amount, + nonce, }; - let signer_seeds = &[ - registrar_acc_info.key.as_ref(), - vendor_acc_info.key.as_ref(), - &[vendor.nonce], - ]; - solana_sdk::program::invoke_signed( - &ix, - &[ - vesting_acc_info.clone(), - vendor_vault_acc_info.clone(), - vendor_vault_authority_acc_info.clone(), - vesting_vault_acc_info.clone(), - safe_acc_info.clone(), - token_program_acc_info.clone(), - rent_acc_info.clone(), - clock_acc_info.clone(), - lockup_program_acc_info.clone(), + let mut data = vec![0u8; instr.size()? as usize]; + LockupInstruction::pack(instr, &mut data)?; + Instruction { + program_id: *lockup_program_acc_info.key, + accounts: vec![ + AccountMeta::new(*vesting_acc_info.key, false), + AccountMeta::new(vendor.vault, false), + AccountMeta::new_readonly(*vendor_vault_authority_acc_info.key, true), + AccountMeta::new(*vesting_vault_acc_info.key, false), + AccountMeta::new_readonly(*safe_acc_info.key, false), + AccountMeta::new_readonly(spl_token::ID, false), + AccountMeta::new_readonly(sysvar::rent::ID, false), + AccountMeta::new_readonly(sysvar::clock::ID, false), ], - &[signer_seeds], - )?; - } + data, + } + }; + let signer_seeds = &[ + registrar_acc_info.key.as_ref(), + vendor_acc_info.key.as_ref(), + &[vendor.nonce], + ]; + solana_sdk::program::invoke_signed( + &ix, + &[ + vesting_acc_info.clone(), + vendor_vault_acc_info.clone(), + vendor_vault_authority_acc_info.clone(), + vesting_vault_acc_info.clone(), + safe_acc_info.clone(), + token_program_acc_info.clone(), + rent_acc_info.clone(), + clock_acc_info.clone(), + lockup_program_acc_info.clone(), + ], + &[signer_seeds], + )?; Ok(()) } diff --git a/registry/program/src/claim_unlocked_reward.rs b/registry/program/src/claim_unlocked_reward.rs index 5dc6efa..0def208 100644 --- a/registry/program/src/claim_unlocked_reward.rs +++ b/registry/program/src/claim_unlocked_reward.rs @@ -32,7 +32,8 @@ pub fn handler( let clock_acc_info = next_account_info(acc_infos)?; let mut spt_acc_infos = vec![]; - while acc_infos.len() > 0 { + // 2: Main and locked balances. + for _ in 0..2 { spt_acc_infos.push(next_account_info(acc_infos)?); } @@ -122,11 +123,11 @@ fn access_control(req: AccessControlRequest) -> Result Result<(), RegistryError> { // Transfer proportion of the reward to the user. let spt_total = spts.iter().map(|a| a.amount).fold(0, |a, b| a + b); let amount = spt_total - .checked_div(vendor.pool_token_supply) - .unwrap() .checked_mul(vendor.total) + .unwrap() + .checked_div(vendor.pool_token_supply) .unwrap(); + if amount <= 0 { + // Invariant violation. + msg!("Invalid reward calculation."); + return Err(RegistryErrorCode::Unknown)?; + } + let signer_seeds = &[ registrar_acc_info.key.as_ref(), vendor_acc_info.key.as_ref(), diff --git a/registry/program/src/deposit.rs b/registry/program/src/deposit.rs index 3257193..e110323 100644 --- a/registry/program/src/deposit.rs +++ b/registry/program/src/deposit.rs @@ -16,6 +16,7 @@ pub fn handler( let acc_infos = &mut accounts.iter(); // Lockup whitelist relay interface. + let _vesting_acc_info = next_account_info(acc_infos)?; 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)?; diff --git a/registry/program/src/initialize.rs b/registry/program/src/initialize.rs index 826ec8f..ccfa6fc 100644 --- a/registry/program/src/initialize.rs +++ b/registry/program/src/initialize.rs @@ -18,7 +18,6 @@ pub fn handler( nonce: u8, withdrawal_timelock: i64, deactivation_timelock: i64, - reward_activation_threshold: u64, max_stake_per_entity: u64, stake_rate: u64, stake_rate_mega: u64, @@ -56,7 +55,6 @@ pub fn handler( withdrawal_timelock, nonce, deactivation_timelock, - reward_activation_threshold, max_stake_per_entity, reward_event_q_acc_info, registrar_acc_info, @@ -137,7 +135,6 @@ fn state_transition(req: StateTransitionRequest) -> Result<(), RegistryError> { withdrawal_timelock, nonce, deactivation_timelock, - reward_activation_threshold, pool_mint_acc_info, pool_mint_mega_acc_info, max_stake_per_entity, @@ -155,7 +152,6 @@ fn state_transition(req: StateTransitionRequest) -> Result<(), RegistryError> { registrar.deactivation_timelock = deactivation_timelock; registrar.max_stake_per_entity = max_stake_per_entity; registrar.nonce = nonce; - registrar.reward_activation_threshold = reward_activation_threshold; registrar.pool_mint = *pool_mint_acc_info.key; registrar.pool_mint_mega = *pool_mint_mega_acc_info.key; registrar.reward_event_q = *reward_event_q_acc_info.key; @@ -187,7 +183,6 @@ struct StateTransitionRequest<'a, 'b, 'c> { pool_mint_mega_acc_info: &'a AccountInfo<'b>, registrar: &'c mut Registrar, authority: Pubkey, - reward_activation_threshold: u64, deactivation_timelock: i64, withdrawal_timelock: i64, max_stake_per_entity: u64, diff --git a/registry/program/src/lib.rs b/registry/program/src/lib.rs index 9286698..f21e2d6 100644 --- a/registry/program/src/lib.rs +++ b/registry/program/src/lib.rs @@ -40,7 +40,6 @@ fn entry(program_id: &Pubkey, accounts: &[AccountInfo], instruction_data: &[u8]) nonce, withdrawal_timelock, deactivation_timelock, - reward_activation_threshold, max_stake_per_entity, stake_rate, stake_rate_mega, @@ -53,7 +52,6 @@ fn entry(program_id: &Pubkey, accounts: &[AccountInfo], instruction_data: &[u8]) nonce, withdrawal_timelock, deactivation_timelock, - reward_activation_threshold, max_stake_per_entity, stake_rate, stake_rate_mega, @@ -62,7 +60,6 @@ fn entry(program_id: &Pubkey, accounts: &[AccountInfo], instruction_data: &[u8]) new_authority, withdrawal_timelock, deactivation_timelock, - reward_activation_threshold, max_stake_per_entity, } => update_registrar::handler( program_id, @@ -70,7 +67,6 @@ fn entry(program_id: &Pubkey, accounts: &[AccountInfo], instruction_data: &[u8]) new_authority, withdrawal_timelock, deactivation_timelock, - reward_activation_threshold, max_stake_per_entity, ), RegistryInstruction::CreateEntity { metadata } => { diff --git a/registry/program/src/switch_entity.rs b/registry/program/src/switch_entity.rs index 304c9be..6f9887b 100644 --- a/registry/program/src/switch_entity.rs +++ b/registry/program/src/switch_entity.rs @@ -26,9 +26,9 @@ pub fn handler(program_id: &Pubkey, accounts: &[AccountInfo]) -> Result<(), Regi while acc_infos.len() > 0 { asset_acc_infos.push(AssetAccInfos { owner_acc_info: next_account_info(acc_infos)?, - vault_stake_acc_info: next_account_info(acc_infos)?, - vault_stake_mega_acc_info: next_account_info(acc_infos)?, - }) + spt_acc_info: next_account_info(acc_infos)?, + spt_mega_acc_info: next_account_info(acc_infos)?, + }); } let AccessControlResponse { @@ -121,9 +121,9 @@ fn access_control(req: AccessControlRequest) -> Result Result Result Result<(), RegistryError> { // untouched. for a in assets { // Remove. - curr_entity.balances.spt_amount -= a.vault_stake.amount; - curr_entity.balances.spt_mega_amount -= a.vault_stake_mega.amount; + curr_entity.balances.spt_amount -= a.spt.amount; + curr_entity.balances.spt_mega_amount -= a.spt_mega.amount; // Add. - new_entity.balances.spt_amount += a.vault_stake.amount; - new_entity.balances.spt_mega_amount += a.vault_stake_mega.amount; + new_entity.balances.spt_amount += a.spt.amount; + new_entity.balances.spt_mega_amount += a.spt_mega.amount; } member.entity = *new_entity_acc_info.key; @@ -227,12 +224,12 @@ struct StateTransitionRequest<'a, 'b, 'c> { } struct Assets { - vault_stake: TokenAccount, - vault_stake_mega: TokenAccount, + spt: TokenAccount, + spt_mega: TokenAccount, } struct AssetAccInfos<'a, 'b> { owner_acc_info: &'a AccountInfo<'b>, - vault_stake_acc_info: &'a AccountInfo<'b>, - vault_stake_mega_acc_info: &'a AccountInfo<'b>, + spt_acc_info: &'a AccountInfo<'b>, + spt_mega_acc_info: &'a AccountInfo<'b>, } diff --git a/registry/program/src/update_registrar.rs b/registry/program/src/update_registrar.rs index ba56456..d1c40b7 100644 --- a/registry/program/src/update_registrar.rs +++ b/registry/program/src/update_registrar.rs @@ -13,7 +13,6 @@ pub fn handler( new_authority: Option, withdrawal_timelock: Option, deactivation_timelock: Option, - reward_activation_threshold: Option, max_stake_per_entity: Option, ) -> Result<(), RegistryError> { msg!("handler: initialize"); @@ -37,7 +36,6 @@ pub fn handler( new_authority, withdrawal_timelock, deactivation_timelock, - reward_activation_threshold, max_stake_per_entity, }) .map_err(Into::into) @@ -71,7 +69,6 @@ fn state_transition(req: StateTransitionRequest) -> Result<(), RegistryError> { new_authority, withdrawal_timelock, deactivation_timelock, - reward_activation_threshold, max_stake_per_entity, } = req; @@ -87,10 +84,6 @@ fn state_transition(req: StateTransitionRequest) -> Result<(), RegistryError> { registrar.deactivation_timelock = deactivation_timelock; } - if let Some(reward_activation_threshold) = reward_activation_threshold { - registrar.reward_activation_threshold = reward_activation_threshold; - } - if let Some(max_stake_per_entity) = max_stake_per_entity { registrar.max_stake_per_entity = max_stake_per_entity; } @@ -109,6 +102,5 @@ struct StateTransitionRequest<'a> { new_authority: Option, withdrawal_timelock: Option, deactivation_timelock: Option, - reward_activation_threshold: Option, max_stake_per_entity: Option, } diff --git a/registry/program/src/withdraw.rs b/registry/program/src/withdraw.rs index 48629d7..0dedb3c 100644 --- a/registry/program/src/withdraw.rs +++ b/registry/program/src/withdraw.rs @@ -17,6 +17,7 @@ pub fn handler( let acc_infos = &mut accounts.iter(); // Lockup whitelist relay interface. + let _vesting_acc_info = next_account_info(acc_infos)?; 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)?; diff --git a/registry/rewards/tests/lifecycle.rs b/registry/rewards/tests/lifecycle.rs index 12950d5..ec0e616 100644 --- a/registry/rewards/tests/lifecycle.rs +++ b/registry/rewards/tests/lifecycle.rs @@ -43,7 +43,6 @@ fn lifecycle() -> Result<()> { max_stake_per_entity: 1_000_000_000_000_000, stake_rate: 1, stake_rate_mega: 1, - reward_activation_threshold: 1, })?; // Create entity--and subsequently activate it so that it can receive diff --git a/registry/src/access_control.rs b/registry/src/access_control.rs index eac1deb..060c5db 100644 --- a/registry/src/access_control.rs +++ b/registry/src/access_control.rs @@ -242,6 +242,41 @@ pub fn member_vault_stake( Ok((member_vault, is_mega)) } +pub fn member_spt( + member: &Member, + member_spt_acc_info: &AccountInfo, + member_vault_authority_acc_info: &AccountInfo, + registrar_acc_info: &AccountInfo, + registrar: &Registrar, + program_id: &Pubkey, + balance_id: &Pubkey, +) -> Result<(TokenAccount, bool), RegistryError> { + let member_spt = vault_authenticated( + member_spt_acc_info, + member_vault_authority_acc_info, + registrar_acc_info, + ®istrar, + program_id, + )?; + + let b = member + .balances + .iter() + .filter(|b| &b.owner == balance_id) + .collect::>(); + let balances = b.first().ok_or(RegistryErrorCode::InvalidBalanceSandbox)?; + + let is_mega = { + if member_spt_acc_info.key != &balances.spt && member_spt_acc_info.key != &balances.spt_mega + { + return Err(RegistryErrorCode::InvalidSpt)?; + } + member_spt_acc_info.key == &balances.spt_mega + }; + + Ok((member_spt, is_mega)) +} + pub fn member_vault_pending_withdrawal( member: &Member, member_vault_acc_info: &AccountInfo, diff --git a/registry/src/accounts/registrar.rs b/registry/src/accounts/registrar.rs index 20db3d4..454f643 100644 --- a/registry/src/accounts/registrar.rs +++ b/registry/src/accounts/registrar.rs @@ -18,9 +18,6 @@ pub struct Registrar { pub authority: Pubkey, /// Nonce to derive the program-derived address owning the vaults. pub nonce: u8, - /// The amount of tokens that must be deposited to be eligible for rewards, - /// denominated in SRM. - pub reward_activation_threshold: u64, /// The maximum stake per node entity, denominated in SRM. pub max_stake_per_entity: u64, /// Number of seconds that must pass for a withdrawal to complete. diff --git a/registry/src/lib.rs b/registry/src/lib.rs index 7b49ca4..e3c41dc 100644 --- a/registry/src/lib.rs +++ b/registry/src/lib.rs @@ -30,7 +30,6 @@ pub mod instruction { nonce: u8, withdrawal_timelock: i64, deactivation_timelock: i64, - reward_activation_threshold: u64, max_stake_per_entity: u64, stake_rate: u64, stake_rate_mega: u64, @@ -43,7 +42,6 @@ pub mod instruction { new_authority: Option, withdrawal_timelock: Option, deactivation_timelock: Option, - reward_activation_threshold: Option, max_stake_per_entity: Option, }, /// Accounts: diff --git a/registry/tests/lifecycle.rs b/registry/tests/lifecycle.rs index bfc8ea7..9d5295b 100644 --- a/registry/tests/lifecycle.rs +++ b/registry/tests/lifecycle.rs @@ -33,7 +33,6 @@ fn lifecycle() { // Initialize the registrar. let withdrawal_timelock = 10; let deactivation_timelock = 10; - let reward_activation_threshold = 10; let max_stake_per_entity = 100_000_000_000_000; let registrar_authority = Keypair::generate(&mut OsRng); @@ -46,7 +45,6 @@ fn lifecycle() { deactivation_timelock, mint: srm_mint, mega_mint: msrm_mint, - reward_activation_threshold, max_stake_per_entity, stake_rate: 1, stake_rate_mega: 1, diff --git a/scripts/deploy-staking.sh b/scripts/deploy-staking.sh index 04b2a52..181b8f6 100755 --- a/scripts/deploy-staking.sh +++ b/scripts/deploy-staking.sh @@ -12,13 +12,18 @@ CLUSTER=l #CLUSTER=devnet DEACTIVATION_TIMELOCK=60 WITHDRAWAL_TIMELOCK=60 +# # 100_000_000 million SRM (6 decimals) +# MAX_STAKE_PER_ENTITY=100000000000000 +# # 1 SRM (6 decimals) to stake. +# STAKE_RATE=1000000 +# # 1 MSRM (0 decimals) to stake. +# STAKE_RATE_MEGA=1 -REWARD_ACTIVATION_THRESHOLD=1 CONFIG_FILE=~/.config/serum/cli/dev.yaml serum=$(pwd)/target/debug/serum @@ -90,7 +95,6 @@ EOM local rInit=$($serum --config $CONFIG_FILE \ registry init \ --deactivation-timelock $DEACTIVATION_TIMELOCK \ - --reward-activation-threshold $REWARD_ACTIVATION_THRESHOLD \ --withdrawal-timelock $WITHDRAWAL_TIMELOCK \ --max-stake-per-entity $MAX_STAKE_PER_ENTITY \ --stake-rate $STAKE_RATE \ @@ -101,8 +105,7 @@ EOM local reward_q=$(echo $rInit | jq .rewardEventQueue -r) local lInit=$($serum --config $CONFIG_FILE \ - lockup \ - initialize) + lockup initialize) local safe=$(echo $lInit | jq .safe -r) @@ -115,8 +118,7 @@ EOM --registrar $registrar \ --about "This the default entity all new members join." \ --image-url " " \ - --name "Default" \ - --meta-entity-program-id $meta_entity_pid) + --name "Default" ) local entity=$(echo $createEntity | jq .entity -r)