From ef49d57fdb65e6aa01518549345a908b4a5742db Mon Sep 17 00:00:00 2001 From: Ondra Chaloupka Date: Thu, 21 Dec 2023 14:47:03 +0100 Subject: [PATCH 1/6] [contract] disabling settlement instructions --- .../validator-bonds-sdk/generated/validator_bonds.ts | 10 ++++++++++ programs/validator-bonds/src/error.rs | 2 ++ .../src/instructions/settlement/claim_settlement.rs | 2 ++ .../src/instructions/settlement/close_settlement.rs | 2 ++ .../instructions/settlement/close_settlement_claim.rs | 2 ++ .../src/instructions/settlement/fund_settlement.rs | 2 ++ .../src/instructions/settlement/init_settlement.rs | 2 ++ .../validator-bonds/src/instructions/stake/reset.rs | 2 ++ 8 files changed, 24 insertions(+) diff --git a/packages/validator-bonds-sdk/generated/validator_bonds.ts b/packages/validator-bonds-sdk/generated/validator_bonds.ts index 19d62d4f..2c7e51f0 100644 --- a/packages/validator-bonds-sdk/generated/validator_bonds.ts +++ b/packages/validator-bonds-sdk/generated/validator_bonds.ts @@ -3088,6 +3088,11 @@ export type ValidatorBonds = { "code": 6044, "name": "StakeDelegationMismatch", "msg": "Delegation of provided stake account mismatches" + }, + { + "code": 6046, + "name": "NotYetImplemented", + "msg": "Not yet implemented" } ] }; @@ -6182,6 +6187,11 @@ export const IDL: ValidatorBonds = { "code": 6044, "name": "StakeDelegationMismatch", "msg": "Delegation of provided stake account mismatches" + }, + { + "code": 6046, + "name": "NotYetImplemented", + "msg": "Not yet implemented" } ] }; diff --git a/programs/validator-bonds/src/error.rs b/programs/validator-bonds/src/error.rs index 8497dc6f..a38a878c 100644 --- a/programs/validator-bonds/src/error.rs +++ b/programs/validator-bonds/src/error.rs @@ -136,4 +136,6 @@ pub enum ErrorCode { #[msg("Delegation of provided stake account mismatches")] StakeDelegationMismatch, // 6044 0x179c + #[msg("Not yet implemented")] + NotYetImplemented, // 6045 0x179d } diff --git a/programs/validator-bonds/src/instructions/settlement/claim_settlement.rs b/programs/validator-bonds/src/instructions/settlement/claim_settlement.rs index 6a5ab2f7..64cacbad 100644 --- a/programs/validator-bonds/src/instructions/settlement/claim_settlement.rs +++ b/programs/validator-bonds/src/instructions/settlement/claim_settlement.rs @@ -132,6 +132,8 @@ impl<'info> ClaimSettlement<'info> { }: ClaimSettlementArgs, settlement_claim_bump: u8, ) -> Result<()> { + require!(true == false, ErrorCode::NotYetImplemented); + if self.settlement.total_funds_claimed + amount > self.settlement.max_total_claim { return Err(error!(ErrorCode::ClaimAmountExceedsMaxTotalClaim) .with_values(("amount", amount)) diff --git a/programs/validator-bonds/src/instructions/settlement/close_settlement.rs b/programs/validator-bonds/src/instructions/settlement/close_settlement.rs index 56a57018..15daf52e 100644 --- a/programs/validator-bonds/src/instructions/settlement/close_settlement.rs +++ b/programs/validator-bonds/src/instructions/settlement/close_settlement.rs @@ -79,6 +79,8 @@ pub struct CloseSettlement<'info> { impl<'info> CloseSettlement<'info> { pub fn process(&mut self) -> Result<()> { + require!(true == false, ErrorCode::NotYetImplemented); + if self.settlement.split_rent_collector.is_some() { // stake account is managed by bonds program let stake_meta = check_stake_is_initialized_with_withdrawer_authority( diff --git a/programs/validator-bonds/src/instructions/settlement/close_settlement_claim.rs b/programs/validator-bonds/src/instructions/settlement/close_settlement_claim.rs index 35b5b911..c2c810ec 100644 --- a/programs/validator-bonds/src/instructions/settlement/close_settlement_claim.rs +++ b/programs/validator-bonds/src/instructions/settlement/close_settlement_claim.rs @@ -24,6 +24,8 @@ pub struct CloseSettlementClaim<'info> { impl<'info> CloseSettlementClaim<'info> { pub fn process(&mut self) -> Result<()> { + require!(true == false, ErrorCode::NotYetImplemented); + // The rule is that the settlement claim can be closed only when settlement does not exist // TODO: is there a nicer (more anchor native) way to verify the non-existence of the account? require_eq!( diff --git a/programs/validator-bonds/src/instructions/settlement/fund_settlement.rs b/programs/validator-bonds/src/instructions/settlement/fund_settlement.rs index 7bc70ca9..477d3b3a 100644 --- a/programs/validator-bonds/src/instructions/settlement/fund_settlement.rs +++ b/programs/validator-bonds/src/instructions/settlement/fund_settlement.rs @@ -121,6 +121,8 @@ pub struct FundSettlement<'info> { impl<'info> FundSettlement<'info> { pub fn process(&mut self) -> Result<()> { + require!(true == false, ErrorCode::NotYetImplemented); + if self.settlement.total_funded >= self.settlement.max_total_claim { msg!("Settlement is already fully funded"); return Ok(()); diff --git a/programs/validator-bonds/src/instructions/settlement/init_settlement.rs b/programs/validator-bonds/src/instructions/settlement/init_settlement.rs index 752eb4bf..9fe7f095 100644 --- a/programs/validator-bonds/src/instructions/settlement/init_settlement.rs +++ b/programs/validator-bonds/src/instructions/settlement/init_settlement.rs @@ -77,6 +77,8 @@ impl<'info> InitSettlement<'info> { }: InitSettlementArgs, settlement_bump: u8, ) -> Result<()> { + require!(true == false, ErrorCode::NotYetImplemented); + if settlement_total_claim == 0 || settlement_num_nodes == 0 { return Err(error!(ErrorCode::EmptySettlementMerkleTree) .with_values(("settlement_total_claim", settlement_total_claim)) diff --git a/programs/validator-bonds/src/instructions/stake/reset.rs b/programs/validator-bonds/src/instructions/stake/reset.rs index 0506c550..8f655899 100644 --- a/programs/validator-bonds/src/instructions/stake/reset.rs +++ b/programs/validator-bonds/src/instructions/stake/reset.rs @@ -71,6 +71,8 @@ pub struct ResetStake<'info> { impl<'info> ResetStake<'info> { pub fn process(&mut self) -> Result<()> { + require!(true == false, ErrorCode::NotYetImplemented); + // settlement account cannot exists require_eq!( self.settlement.lamports(), From acb11f9e966c19d04cc337363d13f14e81085167 Mon Sep 17 00:00:00 2001 From: Ondra Chaloupka Date: Thu, 21 Dec 2023 14:58:37 +0100 Subject: [PATCH 2/6] [contract] adding TODO on checking activation for funding --- .../src/instructions/settlement/fund_settlement.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/programs/validator-bonds/src/instructions/settlement/fund_settlement.rs b/programs/validator-bonds/src/instructions/settlement/fund_settlement.rs index 477d3b3a..18d3d2d4 100644 --- a/programs/validator-bonds/src/instructions/settlement/fund_settlement.rs +++ b/programs/validator-bonds/src/instructions/settlement/fund_settlement.rs @@ -144,6 +144,9 @@ impl<'info> FundSettlement<'info> { ErrorCode::StakeAccountAlreadyFunded, ); + // TODO: consider if missing to check the stake account is fully activated + // considering we don't care as it will be just deactivated(?) + let split_stake_rent_exempt = self.split_stake_account.to_account_info().lamports(); let stake_account_min_size = minimal_size_stake_account(&stake_meta, &self.config); From 3ff476fafe9a4df7b3875bd48472938a206faec9 Mon Sep 17 00:00:00 2001 From: Ondra Chaloupka Date: Thu, 21 Dec 2023 16:28:55 +0100 Subject: [PATCH 3/6] [contract] error naming typo; fix anchor error calls _with_values to use msg instead --- .../generated/validator_bonds.ts | 50 +++++++++------ programs/validator-bonds/src/checks.rs | 60 +++++++----------- programs/validator-bonds/src/error.rs | 20 +++--- programs/validator-bonds/src/events/bond.rs | 2 +- .../validator-bonds/src/events/withdraw.rs | 2 +- .../src/instructions/bond/fund_bond.rs | 8 +-- .../settlement/claim_settlement.rs | 40 ++++++++---- .../settlement/init_settlement.rs | 7 ++- .../src/instructions/stake/merge.rs | 63 ++++++++----------- .../withdraw/claim_withdraw_request.rs | 14 +++-- .../validator-bonds/src/utils/basis_points.rs | 9 ++- 11 files changed, 147 insertions(+), 128 deletions(-) diff --git a/packages/validator-bonds-sdk/generated/validator_bonds.ts b/packages/validator-bonds-sdk/generated/validator_bonds.ts index 2c7e51f0..f3924734 100644 --- a/packages/validator-bonds-sdk/generated/validator_bonds.ts +++ b/packages/validator-bonds-sdk/generated/validator_bonds.ts @@ -276,7 +276,7 @@ export type ValidatorBonds = { "isMut": false, "isSigner": false, "docs": [ - "authority that the provided stake account will be assigned to, authority owner is the program" + "new authority owner, it's the bonds program" ], "pda": { "seeds": [ @@ -296,7 +296,7 @@ export type ValidatorBonds = { }, { "name": "stakeAccount", - "isMut": false, + "isMut": true, "isSigner": false, "docs": [ "stake account to be deposited" @@ -2289,7 +2289,7 @@ export type ValidatorBonds = { ] }, { - "name": "DepositBondEvent", + "name": "FundBondEvent", "fields": [ { "name": "bond", @@ -2812,7 +2812,7 @@ export type ValidatorBonds = { ] }, { - "name": "WithdrawEvent", + "name": "ClaimWithdrawRequestEvent", "fields": [ { "name": "withdrawRequest", @@ -2926,18 +2926,18 @@ export type ValidatorBonds = { }, { "code": 6012, - "name": "HundrethBasisPointsOverflow", + "name": "HundredthBasisPointsOverflow", "msg": "Value of hundredth basis points is too big" }, { "code": 6013, - "name": "HundrethBasisPointsCalculation", - "msg": "Hundreth basis points calculation failure" + "name": "HundredthBasisPointsCalculation", + "msg": "Hundredth basis points calculation failure" }, { "code": 6014, - "name": "HundrethBasisPointsParse", - "msg": "Hundreth basis points failure to parse the value" + "name": "HundredthBasisPointsParse", + "msg": "Hundredth basis points failure to parse the value" }, { "code": 6015, @@ -3081,11 +3081,16 @@ export type ValidatorBonds = { }, { "code": 6043, + "name": "NonBondStakeAuthorities", + "msg": "One or both stake authorities does not belong to bonds program" + }, + { + "code": 6044, "name": "SettlementAuthorityMismatch", "msg": "Settlement stake account authority does not match with the provided stake account authority" }, { - "code": 6044, + "code": 6045, "name": "StakeDelegationMismatch", "msg": "Delegation of provided stake account mismatches" }, @@ -3375,7 +3380,7 @@ export const IDL: ValidatorBonds = { "isMut": false, "isSigner": false, "docs": [ - "authority that the provided stake account will be assigned to, authority owner is the program" + "new authority owner, it's the bonds program" ], "pda": { "seeds": [ @@ -3395,7 +3400,7 @@ export const IDL: ValidatorBonds = { }, { "name": "stakeAccount", - "isMut": false, + "isMut": true, "isSigner": false, "docs": [ "stake account to be deposited" @@ -5388,7 +5393,7 @@ export const IDL: ValidatorBonds = { ] }, { - "name": "DepositBondEvent", + "name": "FundBondEvent", "fields": [ { "name": "bond", @@ -5911,7 +5916,7 @@ export const IDL: ValidatorBonds = { ] }, { - "name": "WithdrawEvent", + "name": "ClaimWithdrawRequestEvent", "fields": [ { "name": "withdrawRequest", @@ -6025,18 +6030,18 @@ export const IDL: ValidatorBonds = { }, { "code": 6012, - "name": "HundrethBasisPointsOverflow", + "name": "HundredthBasisPointsOverflow", "msg": "Value of hundredth basis points is too big" }, { "code": 6013, - "name": "HundrethBasisPointsCalculation", - "msg": "Hundreth basis points calculation failure" + "name": "HundredthBasisPointsCalculation", + "msg": "Hundredth basis points calculation failure" }, { "code": 6014, - "name": "HundrethBasisPointsParse", - "msg": "Hundreth basis points failure to parse the value" + "name": "HundredthBasisPointsParse", + "msg": "Hundredth basis points failure to parse the value" }, { "code": 6015, @@ -6180,11 +6185,16 @@ export const IDL: ValidatorBonds = { }, { "code": 6043, + "name": "NonBondStakeAuthorities", + "msg": "One or both stake authorities does not belong to bonds program" + }, + { + "code": 6044, "name": "SettlementAuthorityMismatch", "msg": "Settlement stake account authority does not match with the provided stake account authority" }, { - "code": 6044, + "code": 6045, "name": "StakeDelegationMismatch", "msg": "Delegation of provided stake account mismatches" }, diff --git a/programs/validator-bonds/src/checks.rs b/programs/validator-bonds/src/checks.rs index 436574e9..d6a410fd 100644 --- a/programs/validator-bonds/src/checks.rs +++ b/programs/validator-bonds/src/checks.rs @@ -78,12 +78,11 @@ pub fn check_stake_valid_delegation( ); Ok(delegation) } else { - Err(error!(ErrorCode::StakeNotDelegated) - .with_values(( - "stake_account_state", - format!("{:?}", stake_account.deref()), - )) - .with_values(("validator_vote_account", validator_vote_account))) + msg!( + "Stake account is not delegated: {:?}", + stake_account.deref() + ); + err!(ErrorCode::StakeNotDelegated) } } @@ -93,22 +92,12 @@ pub fn check_stake_is_initialized_with_withdrawer_authority( stake_account_attribute_name: &str, ) -> Result { let stake_meta = stake_account.meta().ok_or( - error!(ErrorCode::UninitializedStake) - .with_account_name(stake_account_attribute_name) - .with_values(( - "stake_account_state", - format!("{:?}", stake_account.deref()), - )), + error!(ErrorCode::UninitializedStake).with_account_name(stake_account_attribute_name), )?; if stake_meta.authorized.withdrawer != *authority { return Err(error!(ErrorCode::InvalidStakeOwner) .with_account_name(stake_account_attribute_name) - .with_values(( - "stake_account_state", - format!("{:?}", stake_account.deref()), - )) - .with_values(("authority", authority)) - .with_values(("authorized_withdrawer", stake_meta.authorized.withdrawer))); + .with_pubkeys((stake_meta.authorized.withdrawer, *authority))); } Ok(stake_meta) } @@ -122,12 +111,10 @@ pub fn check_stake_is_not_locked( // TODO: consider working with custodian, would need to be passed from the caller // and on authorize withdrawer we would need to change the lockup to the new custodian if stake_lockup.is_in_force(clock, None) { - return Err(error!(ErrorCode::StakeLockedUp) - .with_account_name(stake_account_attribute_name) - .with_values(( - "stake_account_state", - format!("{:?}", stake_account.deref()), - ))); + msg!("Stake account is locked: {:?}", stake_account.deref()); + return Err( + error!(ErrorCode::StakeLockedUp).with_account_name(stake_account_attribute_name) + ); } } Ok(()) @@ -155,21 +142,22 @@ pub fn check_stake_exist_and_fully_activated( None, ); if activating + deactivating > 0 || effective == 0 { - return Err(error!(ErrorCode::NoStakeOrNotFullyActivated) - .with_values(( - "stake_account_state", - format!("{:?}", stake_account.deref()), - )) - .with_values(("effective", effective)) - .with_values(("activating", activating)) - .with_values(("deactivating", deactivating))); + msg!( + "Stake account is not activated: {:?}", + stake_account.deref() + ); + return Err(error!(ErrorCode::NoStakeOrNotFullyActivated).with_values(( + "effective/activating/deactivating", + format!("{}/{}/{}", effective, activating, deactivating), + ))); } Ok(stake) } else { - Err(error!(ErrorCode::StakeNotDelegated).with_values(( - "stake_account_state", - format!("{:?}", stake_account.deref()), - ))) + msg!( + "Stake account is not delegated: {:?}", + stake_account.deref() + ); + err!(ErrorCode::StakeNotDelegated) } } diff --git a/programs/validator-bonds/src/error.rs b/programs/validator-bonds/src/error.rs index a38a878c..e4c785f8 100644 --- a/programs/validator-bonds/src/error.rs +++ b/programs/validator-bonds/src/error.rs @@ -39,13 +39,13 @@ pub enum ErrorCode { InvalidWithdrawRequestAddress, // 6011 0x177b #[msg("Value of hundredth basis points is too big")] - HundrethBasisPointsOverflow, // 6012 0x177c + HundredthBasisPointsOverflow, // 6012 0x177c - #[msg("Hundreth basis points calculation failure")] - HundrethBasisPointsCalculation, // 6013 0x177d + #[msg("Hundredth basis points calculation failure")] + HundredthBasisPointsCalculation, // 6013 0x177d - #[msg("Hundreth basis points failure to parse the value")] - HundrethBasisPointsParse, // 6014 0x177e + #[msg("Hundredth basis points failure to parse the value")] + HundredthBasisPointsParse, // 6014 0x177e #[msg("Cannot deserialize validator vote account data")] FailedToDeserializeVoteAccount, // 6015 0x177f @@ -131,11 +131,15 @@ pub enum ErrorCode { #[msg("Stake account's staker does not match with the provided authority")] StakerAuthorityMismatch, // 6042 0x179a + #[msg("One or both stake authorities does not belong to bonds program")] + NonBondStakeAuthorities, // 6043 0x179b + #[msg("Settlement stake account authority does not match with the provided stake account authority")] - SettlementAuthorityMismatch, // 6043 0x179b + SettlementAuthorityMismatch, // 6044 0x179c #[msg("Delegation of provided stake account mismatches")] - StakeDelegationMismatch, // 6044 0x179c + StakeDelegationMismatch, // 6045 0x179d + #[msg("Not yet implemented")] - NotYetImplemented, // 6045 0x179d + NotYetImplemented, // 6046 0x179e } diff --git a/programs/validator-bonds/src/events/bond.rs b/programs/validator-bonds/src/events/bond.rs index 032c98c0..60018fd1 100644 --- a/programs/validator-bonds/src/events/bond.rs +++ b/programs/validator-bonds/src/events/bond.rs @@ -28,7 +28,7 @@ pub struct CloseBondEvent { } #[event] -pub struct DepositBondEvent { +pub struct FundBondEvent { pub bond: Pubkey, pub validator_vote: Pubkey, pub stake_account: Pubkey, diff --git a/programs/validator-bonds/src/events/withdraw.rs b/programs/validator-bonds/src/events/withdraw.rs index e710ffd9..2353b4d3 100644 --- a/programs/validator-bonds/src/events/withdraw.rs +++ b/programs/validator-bonds/src/events/withdraw.rs @@ -21,7 +21,7 @@ pub struct CancelWithdrawRequestEvent { } #[event] -pub struct WithdrawEvent { +pub struct ClaimWithdrawRequestEvent { pub withdraw_request: Pubkey, pub bond: Pubkey, pub validator_vote_account: Pubkey, diff --git a/programs/validator-bonds/src/instructions/bond/fund_bond.rs b/programs/validator-bonds/src/instructions/bond/fund_bond.rs index 17cda313..cc332518 100644 --- a/programs/validator-bonds/src/instructions/bond/fund_bond.rs +++ b/programs/validator-bonds/src/instructions/bond/fund_bond.rs @@ -3,7 +3,7 @@ use crate::checks::{ check_stake_is_not_locked, check_stake_valid_delegation, }; use crate::error::ErrorCode; -use crate::events::bond::DepositBondEvent; +use crate::events::bond::FundBondEvent; use crate::state::bond::Bond; use crate::state::config::Config; use anchor_lang::prelude::*; @@ -30,7 +30,7 @@ pub struct FundBond<'info> { bond: Account<'info, Bond>, /// CHECK: PDA - /// authority that the provided stake account will be assigned to, authority owner is the program + /// new authority owner, it's the bonds program #[account( seeds = [ b"bonds_authority", @@ -41,7 +41,7 @@ pub struct FundBond<'info> { bonds_withdrawer_authority: UncheckedAccount<'info>, /// stake account to be deposited - #[account()] + #[account(mut)] stake_account: Account<'info, StakeAccount>, /// authority signature permitting to change the stake_account authorities @@ -99,7 +99,7 @@ impl<'info> FundBond<'info> { None, )?; - emit!(DepositBondEvent { + emit!(FundBondEvent { bond: self.bond.key(), validator_vote: self.bond.validator_vote_account.key(), stake_account: self.stake_account.key(), diff --git a/programs/validator-bonds/src/instructions/settlement/claim_settlement.rs b/programs/validator-bonds/src/instructions/settlement/claim_settlement.rs index 64cacbad..54edaaa4 100644 --- a/programs/validator-bonds/src/instructions/settlement/claim_settlement.rs +++ b/programs/validator-bonds/src/instructions/settlement/claim_settlement.rs @@ -136,14 +136,27 @@ impl<'info> ClaimSettlement<'info> { if self.settlement.total_funds_claimed + amount > self.settlement.max_total_claim { return Err(error!(ErrorCode::ClaimAmountExceedsMaxTotalClaim) - .with_values(("amount", amount)) - .with_values(("max_total_claim", self.settlement.max_total_claim))); + .with_account_name("settlement") + .with_values(( + "total_funds_claimed + amount > max_total_claim", + format!( + "{} + {} <= {}", + self.settlement.total_funds_claimed, + amount, + self.settlement.max_total_claim + ), + ))); } if self.settlement.num_nodes_claimed + 1 > self.settlement.max_num_nodes { return Err(error!(ErrorCode::ClaimCountExceedsMaxNumNodes) - .with_values(("settlement", self.settlement.key())) - .with_values(("num_nodes_claimed", self.settlement.num_nodes_claimed)) - .with_values(("max_num_nodes", self.settlement.max_num_nodes))); + .with_account_name("settlement") + .with_values(( + "num_nodes_claimed + 1 > max_num_nodes", + format!( + "{} + 1 <= {}", + self.settlement.num_nodes_claimed, self.settlement.max_num_nodes + ), + ))); } // stake account is managed by bonds program @@ -170,11 +183,14 @@ impl<'info> ClaimSettlement<'info> { { return Err(error!(ErrorCode::ClaimingStakeAccountLamportsInsufficient) .with_account_name("stake_account") - .with_values(("stake_account_lamports", self.stake_account.get_lamports())) - .with_values(("claiming_amount", amount)) .with_values(( - "minimal_size_stake_account", - minimal_size_stake_account(&stake_meta, &self.config), + "stake_account_lamports < amount + minimal_size_stake_account", + format!( + "{} < {} + {}", + self.stake_account.get_lamports(), + amount, + minimal_size_stake_account(&stake_meta, &self.config) + ), ))); } @@ -182,9 +198,9 @@ impl<'info> ClaimSettlement<'info> { merkle_proof::tree_node(staker_authority, withdrawer_authority, vote_account, claim); if !merkle_proof::verify(proof, self.settlement.merkle_root, merkle_tree_node) { - return Err(error!(ErrorCode::ClaimSettlementProofFailed) - .with_values(("claiming_amount", amount)) - .with_values(("withdrawer_authority", withdrawer_authority.key()))); + msg!("Merkle proof verification failed. Merkle tree node: {:?}, staker_authority: {}, withdrawer_authority: {}, vote_account: {}, claim: {}", + merkle_tree_node, staker_authority, withdrawer_authority, vote_account, claim); + return err!(ErrorCode::ClaimSettlementProofFailed); } self.settlement_claim.set_inner(SettlementClaim { diff --git a/programs/validator-bonds/src/instructions/settlement/init_settlement.rs b/programs/validator-bonds/src/instructions/settlement/init_settlement.rs index 9fe7f095..4a3f3862 100644 --- a/programs/validator-bonds/src/instructions/settlement/init_settlement.rs +++ b/programs/validator-bonds/src/instructions/settlement/init_settlement.rs @@ -80,9 +80,10 @@ impl<'info> InitSettlement<'info> { require!(true == false, ErrorCode::NotYetImplemented); if settlement_total_claim == 0 || settlement_num_nodes == 0 { - return Err(error!(ErrorCode::EmptySettlementMerkleTree) - .with_values(("settlement_total_claim", settlement_total_claim)) - .with_values(("settlement_num_nodes", settlement_num_nodes))); + return Err(error!(ErrorCode::EmptySettlementMerkleTree).with_values(( + "settlement_total_claim, settlement_num_nodes", + format!("{}, {}", settlement_total_claim, settlement_num_nodes), + ))); } let (settlement_authority, settlement_authority_bump) = diff --git a/programs/validator-bonds/src/instructions/stake/merge.rs b/programs/validator-bonds/src/instructions/stake/merge.rs index bb51d7fd..6373d1bf 100644 --- a/programs/validator-bonds/src/instructions/stake/merge.rs +++ b/programs/validator-bonds/src/instructions/stake/merge.rs @@ -3,7 +3,6 @@ use crate::error::ErrorCode; use crate::events::stake::MergeEvent; use crate::state::config::{find_bonds_withdrawer_authority, Config}; use crate::state::settlement::find_settlement_authority; -use crate::ID; use anchor_lang::{ prelude::*, solana_program::{program::invoke_signed, stake::instruction::merge, sysvar::stake_history}, @@ -27,10 +26,8 @@ pub struct Merge<'info> { #[account(mut)] destination_stake: Account<'info, StakeAccount>, - /// CHECK: staker authority defined here has to be owned by program, then checked within the code - #[account( - owner = ID - )] + /// CHECK: checked within the code + #[account()] staker_authority: UncheckedAccount<'info>, /// CHECK: have no CPU budget to parse @@ -56,16 +53,16 @@ impl<'info> Merge<'info> { // staker authorities has to match each other, verification if it belongs to bond is down in switch statement if destination_meta.authorized.staker != self.staker_authority.key() { return Err(error!(ErrorCode::StakerAuthorityMismatch) + .with_account_name("destination_stake") .with_pubkeys(( destination_meta.authorized.staker, self.staker_authority.key(), - )) - .with_account_name("destination_stake")); + ))); } if source_meta.authorized.staker != self.staker_authority.key() { return Err(error!(ErrorCode::StakerAuthorityMismatch) - .with_pubkeys((source_meta.authorized.staker, self.staker_authority.key())) - .with_account_name("source_stake")); + .with_account_name("source_stake") + .with_pubkeys((source_meta.authorized.staker, self.staker_authority.key()))); } // withdrawer authorities must belongs to the bonds program (bonds program ownership) @@ -73,13 +70,13 @@ impl<'info> Merge<'info> { if source_meta.authorized.withdrawer != bonds_withdrawer_authority || destination_meta.authorized.withdrawer != bonds_withdrawer_authority { - return Err(error!(ErrorCode::StakerAuthorityMismatch) - .with_values(("bonds_withdrawer_authority", bonds_withdrawer_authority)) - .with_values(("source_stake_withdrawer", source_meta.authorized.withdrawer)) + return Err(error!(ErrorCode::NonBondStakeAuthorities) + .with_account_name("source_stake/destination_stake") .with_values(( - "destination_stake_withdrawer", - destination_meta.authorized.withdrawer, - ))); + "bonds_withdrawer_authority/source_stake_withdrawer/destination_stake_withdrawer", + format!("{}/{}/{}", bonds_withdrawer_authority, source_meta.authorized.withdrawer, destination_meta.authorized.withdrawer) + )) + ); } let destination_delegation = self.destination_stake.delegation(); @@ -90,23 +87,13 @@ impl<'info> Merge<'info> { || destination_delegation.unwrap().voter_pubkey != source_delegation.unwrap().voter_pubkey { + msg!( + "None or different stakes delegation, source: {:?}, destination: {:?}", + source_delegation, + destination_delegation + ); return Err(error!(ErrorCode::StakeDelegationMismatch) - .with_values(( - "destination_stake_delegation", - if let Some(dest) = destination_delegation { - dest.voter_pubkey.to_string() - } else { - "none".to_string() - }, - )) - .with_values(( - "source_stake_delegation", - if let Some(src) = source_delegation { - src.voter_pubkey.to_string() - } else { - "none".to_string() - }, - ))); + .with_account_name("source_stake/destination_stake")); } let (settlement_authority, settlement_bump) = find_settlement_authority(&settlement); @@ -145,11 +132,15 @@ impl<'info> Merge<'info> { ]], )? } else { - return Err(error!(ErrorCode::StakerAuthorityMismatch) - .with_account_name("staker_authority") - .with_values(("staker_authority", self.staker_authority.key())) - .with_values(("bonds_authority", bonds_withdrawer_authority)) - .with_values(("settlement_authority", settlement_authority))); + msg!( + "Staker authority mismatch, staker_authority: {}, bonds_withdrawer_authority: {}, settlement_authority: {}", + self.staker_authority.key(), + bonds_withdrawer_authority, + settlement_authority + ); + return Err( + error!(ErrorCode::StakerAuthorityMismatch).with_account_name("staker_authority") + ); }; emit!(MergeEvent { diff --git a/programs/validator-bonds/src/instructions/withdraw/claim_withdraw_request.rs b/programs/validator-bonds/src/instructions/withdraw/claim_withdraw_request.rs index 490b7dc8..78fa2ab8 100644 --- a/programs/validator-bonds/src/instructions/withdraw/claim_withdraw_request.rs +++ b/programs/validator-bonds/src/instructions/withdraw/claim_withdraw_request.rs @@ -4,7 +4,7 @@ use crate::checks::{ }; use crate::constants::BONDS_AUTHORITY_SEED; use crate::error::ErrorCode; -use crate::events::withdraw::WithdrawEvent; +use crate::events::withdraw::ClaimWithdrawRequestEvent; use crate::events::{SplitStakeData, U64ValueChange}; use crate::state::bond::Bond; use crate::state::config::Config; @@ -151,8 +151,14 @@ impl<'info> ClaimWithdrawRequest<'info> { { return Err(error!(ErrorCode::StakeAccountNotBigEnoughToSplit) .with_account_name("stake_account") - .with_values(("stake_account_lamports", self.stake_account.get_lamports())) - .with_values(("amount_to_fulfill_withdraw", amount_to_fulfill_withdraw))); + .with_values(("stake_account_lamports - amount_to_fulfill_withdraw < minimal_stake_size || amount_to_fulfill_withdraw < minimal_stake_size", + format!("{} - {} < {} || {} < {}", + self.stake_account.get_lamports(), + amount_to_fulfill_withdraw, + minimal_stake_size, + amount_to_fulfill_withdraw, + minimal_stake_size, + )))); } let withdraw_split_leftover = @@ -229,7 +235,7 @@ impl<'info> ClaimWithdrawRequest<'info> { self.stake_account.get_lamports(), ); - emit!(WithdrawEvent { + emit!(ClaimWithdrawRequestEvent { bond: self.bond.key(), validator_vote_account: self.validator_vote_account.key(), withdraw_request: self.withdraw_request.key(), diff --git a/programs/validator-bonds/src/utils/basis_points.rs b/programs/validator-bonds/src/utils/basis_points.rs index c5dfeb15..9039cbb6 100644 --- a/programs/validator-bonds/src/utils/basis_points.rs +++ b/programs/validator-bonds/src/utils/basis_points.rs @@ -33,7 +33,7 @@ impl HundredthBasisPoint { require_gte!( &Self::MAX_HUNDREDTH_BPS, &self, - ErrorCode::HundrethBasisPointsOverflow + ErrorCode::HundredthBasisPointsOverflow ); Ok(self) } @@ -63,8 +63,11 @@ impl TryFrom for HundredthBasisPoint { fn try_from(n: f64) -> Result { let hundredth_bps_i = (n * HundredthBasisPoint::DIVIDER as f64).floor() as i64; // 4.5% => 45000 bp_cents let hundredths_bps = u32::try_from(hundredth_bps_i).map_err(|_| { - error!(ErrorCode::HundrethBasisPointsCalculation) - .with_values(("hundredth_bps_i", hundredth_bps_i)) + msg!( + "failed to convert f64 to u32, hundredth_bps_i: {}", + hundredth_bps_i + ); + error!(ErrorCode::HundredthBasisPointsCalculation) })?; Ok(HundredthBasisPoint::from_hundredth_bp(hundredths_bps)?) } From e8b40cc2cf10fd0db961a205de9163ecd96d2b62 Mon Sep 17 00:00:00 2001 From: Ondra Chaloupka Date: Thu, 21 Dec 2023 16:51:33 +0100 Subject: [PATCH 4/6] adding notes from the previous PR --- packages/validator-bonds-sdk/src/api.ts | 3 +++ .../src/instructions/settlement/fund_settlement.rs | 2 ++ programs/validator-bonds/src/lib.rs | 2 +- 3 files changed, 6 insertions(+), 1 deletion(-) diff --git a/packages/validator-bonds-sdk/src/api.ts b/packages/validator-bonds-sdk/src/api.ts index db7d1f2b..2fd00f1e 100644 --- a/packages/validator-bonds-sdk/src/api.ts +++ b/packages/validator-bonds-sdk/src/api.ts @@ -2,6 +2,9 @@ import { ProgramAccount } from '@coral-xyz/anchor' import { PublicKey } from '@solana/web3.js' import { ValidatorBondsProgram, Config } from './sdk' +// TODO: +// - users can create arbitrary stake accounts (even with lockups), sdk must be prepared for that when showing total usable deposits + export async function getConfig( program: ValidatorBondsProgram, address: PublicKey diff --git a/programs/validator-bonds/src/instructions/settlement/fund_settlement.rs b/programs/validator-bonds/src/instructions/settlement/fund_settlement.rs index 18d3d2d4..a21b6145 100644 --- a/programs/validator-bonds/src/instructions/settlement/fund_settlement.rs +++ b/programs/validator-bonds/src/instructions/settlement/fund_settlement.rs @@ -153,6 +153,8 @@ impl<'info> FundSettlement<'info> { // note: we can over-fund the settlement when the stake account is in shape to not being possible to split it let amount_available = self.stake_account.get_lamports(); // amount needed: "amount + rent exempt + minimal stake size" -> ensuring stake account may exist + // NOTE: once deactivated the balance may drop only to "rent exempt" and "minimal stake size" is not needed anymore + // but we want to re-activate later the left-over at the stake account thus needed to be funded with plus minimal stake size let amount_needed = self.settlement.max_total_claim - self.settlement.total_funded + stake_account_min_size; // the left-over stake account has to be capable to exist after splitting diff --git a/programs/validator-bonds/src/lib.rs b/programs/validator-bonds/src/lib.rs index 22b30728..c4efe693 100644 --- a/programs/validator-bonds/src/lib.rs +++ b/programs/validator-bonds/src/lib.rs @@ -32,10 +32,10 @@ security_txt! { declare_id!("vBoNdEvzMrSai7is21XgVYik65mqtaKXuSdMBJ1xkW4"); // TODO: General TODOs: -// - verify that errors are used and error codes matches // - recheck all 'mut' definitions if they matches to what we need // - consider utility of zero_copy, if it can be used, what are benefits? // - consider using only PDA hash and not has_one at anchor constraints +// - readme with table of what stake_authority/withdraw_authority are at which stages fn check_context(ctx: &Context) -> Result<()> { if !check_id(ctx.program_id) { From 8585093b07797fea8f6f4b1eed4f4682e72e2de7 Mon Sep 17 00:00:00 2001 From: Ondra Chaloupka Date: Thu, 21 Dec 2023 16:54:20 +0100 Subject: [PATCH 5/6] update contract address to one we have keypair for --- Anchor.toml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Anchor.toml b/Anchor.toml index 2043156f..de6b4ff1 100644 --- a/Anchor.toml +++ b/Anchor.toml @@ -10,13 +10,13 @@ seeds = true skip-lint = false [programs.mainnet] -validator_bonds = "vbondsKbsC4QSLQQnn6ngZvkqfywn6KgEeQbkGSpk1V" +validator_bonds = "vBoNdEvzMrSai7is21XgVYik65mqtaKXuSdMBJ1xkW4" [programs.devnet] -validator_bonds = "vbondsKbsC4QSLQQnn6ngZvkqfywn6KgEeQbkGSpk1V" +validator_bonds = "vBoNdEvzMrSai7is21XgVYik65mqtaKXuSdMBJ1xkW4" [programs.localnet] -validator_bonds = "vbondsKbsC4QSLQQnn6ngZvkqfywn6KgEeQbkGSpk1V" +validator_bonds = "vBoNdEvzMrSai7is21XgVYik65mqtaKXuSdMBJ1xkW4" [registry] url = "https://api.apr.dev" From 75cf34efc579a4070db78bbc05e3a4def390333e Mon Sep 17 00:00:00 2001 From: Ondra Chaloupka Date: Thu, 21 Dec 2023 17:01:08 +0100 Subject: [PATCH 6/6] fix for test to run --- .../test-validator/initConfig.spec.ts | 17 ++++++++------- .../__tests__/test-validator/show.spec.ts | 21 ++++++++++--------- .../__tests__/test-validator/utils.ts | 19 +++++++++++++++++ packages/validator-bonds-cli/src/index.ts | 3 ++- .../__tests__/test-validator/utils.ts | 7 +++++-- 5 files changed, 46 insertions(+), 21 deletions(-) create mode 100644 packages/validator-bonds-cli/__tests__/test-validator/utils.ts diff --git a/packages/validator-bonds-cli/__tests__/test-validator/initConfig.spec.ts b/packages/validator-bonds-cli/__tests__/test-validator/initConfig.spec.ts index dfe5e919..0d2c16de 100644 --- a/packages/validator-bonds-cli/__tests__/test-validator/initConfig.spec.ts +++ b/packages/validator-bonds-cli/__tests__/test-validator/initConfig.spec.ts @@ -8,27 +8,28 @@ import { Transaction, } from '@solana/web3.js' import { - VALIDATOR_BONDS_PROGRAM_ID, + ValidatorBondsProgram, getConfig, - getProgram, } from '@marinade.finance/validator-bonds-sdk' +import { initTest } from './utils' beforeAll(() => { shellMatchers() }) describe('Init config account using CLI', () => { - const provider = AnchorProvider.env() - provider.opts.skipPreflight = true - const program = getProgram({ - connection: provider, - programId: VALIDATOR_BONDS_PROGRAM_ID, - }) + let provider: AnchorProvider + let program: ValidatorBondsProgram let configPath: string let configKeypair: Keypair let configCleanup: () => Promise + beforeAll(async () => { + shellMatchers() + ;({ provider, program } = await initTest()) + }) + beforeEach(async () => { // eslint-disable-next-line @typescript-eslint/no-extra-semi ;({ diff --git a/packages/validator-bonds-cli/__tests__/test-validator/show.spec.ts b/packages/validator-bonds-cli/__tests__/test-validator/show.spec.ts index b4def0c1..2e1d231e 100644 --- a/packages/validator-bonds-cli/__tests__/test-validator/show.spec.ts +++ b/packages/validator-bonds-cli/__tests__/test-validator/show.spec.ts @@ -2,28 +2,26 @@ import { AnchorProvider } from '@coral-xyz/anchor' import { shellMatchers } from '@marinade.finance/jest-utils' import YAML from 'yaml' import { - getProgram, initConfigInstruction, - VALIDATOR_BONDS_PROGRAM_ID, findBondsWithdrawerAuthority, + ValidatorBondsProgram, } from '@marinade.finance/validator-bonds-sdk' import { executeTxSimple } from '@marinade.finance/web3js-common' import { transaction } from '@marinade.finance/anchor-common' import { Keypair } from '@solana/web3.js' +import { initTest } from './utils' beforeAll(() => { shellMatchers() }) describe('Show command using CLI', () => { - const provider = AnchorProvider.env() - provider.opts.skipPreflight = true - provider.opts.commitment = 'confirmed' - const program = getProgram({ - connection: provider.connection, - wallet: provider.wallet, - opts: provider.opts, - programId: VALIDATOR_BONDS_PROGRAM_ID, + let provider: AnchorProvider + let program: ValidatorBondsProgram + + beforeAll(async () => { + shellMatchers() + ;({ provider, program } = await initTest()) }) it('show config', async () => { @@ -74,6 +72,7 @@ describe('Show command using CLI', () => { operatorAuthority: operatorAuthority.toBase58(), epochsToClaimSettlement: 101, withdrawLockupEpochs: 102, + minimumStakeLamports: 1000000000, bondsWithdrawerAuthorityBump, reserved: [512], }, @@ -111,6 +110,7 @@ describe('Show command using CLI', () => { operatorAuthority: operatorAuthority.toBase58(), epochsToClaimSettlement: 101, withdrawLockupEpochs: 102, + minimumStakeLamports: 1000000000, bondsWithdrawerAuthorityBump, reserved: [512], }, @@ -175,6 +175,7 @@ describe('Show command using CLI', () => { operatorAuthority: operatorAuthority.toBase58(), epochsToClaimSettlement: 101, withdrawLockupEpochs: 102, + minimumStakeLamports: 1000000000, bondsWithdrawerAuthorityBump, reserved: [512], }, diff --git a/packages/validator-bonds-cli/__tests__/test-validator/utils.ts b/packages/validator-bonds-cli/__tests__/test-validator/utils.ts new file mode 100644 index 00000000..edef5143 --- /dev/null +++ b/packages/validator-bonds-cli/__tests__/test-validator/utils.ts @@ -0,0 +1,19 @@ +import * as anchor from '@coral-xyz/anchor' +import { AnchorProvider } from '@coral-xyz/anchor' +import { + ValidatorBondsProgram, + getProgram, +} from '@marinade.finance/validator-bonds-sdk' + +export async function initTest(): Promise<{ + program: ValidatorBondsProgram + provider: AnchorProvider +}> { + if (process.env.ANCHOR_PROVIDER_URL?.includes('localhost')) { + // workaround to: https://github.com/coral-xyz/anchor/pull/2725 + process.env.ANCHOR_PROVIDER_URL = 'http://127.0.0.1:8899' + } + const provider = AnchorProvider.env() as anchor.AnchorProvider + provider.opts.skipPreflight = true + return { program: getProgram(provider), provider } +} diff --git a/packages/validator-bonds-cli/src/index.ts b/packages/validator-bonds-cli/src/index.ts index 38b0feea..1a6cc1f1 100644 --- a/packages/validator-bonds-cli/src/index.ts +++ b/packages/validator-bonds-cli/src/index.ts @@ -20,7 +20,8 @@ program .allowExcessArguments(false) .option( '-u, --cluster ', - 'solana cluster URL, accepts shortcuts (d/devnet, m/mainnet) (default: http://localhost:8899)' + 'solana cluster URL, accepts shortcuts (d/devnet, m/mainnet)', + 'http://127.0.0.1:8899' ) .option('-c ', 'alias for "-u, --cluster"') .option('--commitment ', 'Commitment', 'confirmed') diff --git a/packages/validator-bonds-sdk/__tests__/test-validator/utils.ts b/packages/validator-bonds-sdk/__tests__/test-validator/utils.ts index 2dc30ba8..c4628e3b 100644 --- a/packages/validator-bonds-sdk/__tests__/test-validator/utils.ts +++ b/packages/validator-bonds-sdk/__tests__/test-validator/utils.ts @@ -6,8 +6,11 @@ export async function initTest(): Promise<{ program: ValidatorBondsProgram provider: AnchorProvider }> { - anchor.setProvider(anchor.AnchorProvider.env()) - const provider = anchor.getProvider() as anchor.AnchorProvider + if (process.env.ANCHOR_PROVIDER_URL?.includes('localhost')) { + // workaround to: https://github.com/coral-xyz/anchor/pull/2725 + process.env.ANCHOR_PROVIDER_URL = 'http://127.0.0.1:8899' + } + const provider = AnchorProvider.env() as anchor.AnchorProvider provider.opts.skipPreflight = true return { program: getProgram(provider), provider } }