Skip to content

Commit 3da4d0d

Browse files
committed
quote
1 parent d5c3fce commit 3da4d0d

File tree

4 files changed

+67
-4
lines changed

4 files changed

+67
-4
lines changed

core/src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,13 @@ mod instructions;
44
mod internal_utils;
55
mod keys;
66
mod pda;
7+
mod quote;
78
mod state;
89
mod typedefs;
910

1011
pub use instructions::*;
1112
pub use keys::*;
1213
pub use pda::*;
14+
pub use quote::*;
1315
pub use state::*;
1416
pub use typedefs::*;

core/src/quote.rs

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
/// Given the total lamports of a validator stake account (includes rent-exemption),
2+
/// return the maximum active stake lamports that the program will allow to be withdrawn from it.
3+
///
4+
/// Returns `None` on arithmetic failure.
5+
///
6+
/// When quoting with [`crate::ExchangeRate::quote_withdraw_stake`], make sure its returned
7+
/// amount does not exceed this amount for the largest validator stake account in the pool.
8+
#[inline]
9+
pub const fn max_withdraw_lamports(stake_acc_total_lamports: u64) -> Option<u64> {
10+
const MINIMUM_STAKE_ACCOUNT_BALANCE: u64 = 1_002_282_880;
11+
const TEN_SOL: u64 = 10_000_000_000;
12+
13+
let ten_pct_plus_10 = match (stake_acc_total_lamports / 10).checked_add(TEN_SOL) {
14+
Some(r) => r,
15+
None => return None,
16+
};
17+
let exhaustion = stake_acc_total_lamports.saturating_sub(MINIMUM_STAKE_ACCOUNT_BALANCE);
18+
Some(if ten_pct_plus_10 < exhaustion {
19+
ten_pct_plus_10
20+
} else {
21+
exhaustion
22+
})
23+
}

core/src/typedefs/exchange_rate.rs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,24 @@ pub struct ExchangeRate {
77
pub sol_balance: u64,
88
}
99

10+
impl ExchangeRate {
11+
/// Returns the staked lamports that will be split
12+
/// from the active stake account upon burning `stsol_amount` stSOL
13+
///
14+
/// Returns None on arithmetic failure
15+
#[inline]
16+
pub fn quote_withdraw_stake(&self, stsol_amount: u64) -> Option<u64> {
17+
if self.st_sol_supply == 0 {
18+
return None;
19+
}
20+
// unchecked mul: 2 u64s will not overflow u128
21+
// unchecked div: nonzero denom checked above
22+
let res = (u128::from(stsol_amount) * u128::from(self.sol_balance))
23+
/ u128::from(self.st_sol_supply);
24+
res.try_into().ok()
25+
}
26+
}
27+
1028
impl ExchangeRate {
1129
inherent_borsh_serde!();
1230
}

core/tests/tests/instructions/withdraw_v2.rs

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ use solana_account::Account;
66
use solana_instruction::Instruction;
77
use solana_pubkey::Pubkey;
88
use solido_legacy_core::{
9-
ValidatorList, WithdrawV2IxData, WithdrawV2IxKeysOwned, PROGRAM_ID, STAKE_PROGRAM,
9+
Lido, ValidatorList, WithdrawV2IxData, WithdrawV2IxKeysOwned, PROGRAM_ID, STAKE_PROGRAM,
1010
WITHDRAW_V2_IX_IS_SIGNER, WITHDRAW_V2_IX_IS_WRITER,
1111
};
1212

@@ -61,8 +61,12 @@ fn withdraw_v2_fixture() {
6161
const AMOUNT: u64 = 1_000_000_000;
6262

6363
let account = KeyedUiAccount::from_test_fixtures_file("validator-list");
64-
let data = account.account_data();
65-
let val_list = ValidatorList::deserialize(data.as_slice()).unwrap();
64+
let val_list_data = account.account_data();
65+
let val_list = ValidatorList::deserialize(val_list_data.as_slice()).unwrap();
66+
67+
let account = KeyedUiAccount::from_test_fixtures_file("lido");
68+
let lido = Lido::borsh_de(account.account_data().as_slice()).unwrap();
69+
let quoted_stake_lamports = lido.exchange_rate.quote_withdraw_stake(AMOUNT).unwrap();
6670

6771
let [user, burn_stsol_from, split_stake_to] = core::array::from_fn(|_i| Pubkey::new_unique());
6872
let mollusk = mollusk_lido_prog();
@@ -86,7 +90,23 @@ fn withdraw_v2_fixture() {
8690
])
8791
.collect();
8892

89-
let InstructionResult { raw_result, .. } = mollusk.process_instruction_chain(ixs, &accounts);
93+
let InstructionResult {
94+
raw_result,
95+
resulting_accounts,
96+
..
97+
} = mollusk.process_instruction_chain(ixs, &accounts);
9098

9199
raw_result.unwrap();
100+
101+
let split_stake_to_lamports = resulting_accounts
102+
.iter()
103+
.find(|(pk, _a)| *pk == split_stake_to)
104+
.unwrap()
105+
.1
106+
.lamports;
107+
108+
assert_eq!(
109+
split_stake_to_lamports,
110+
quoted_stake_lamports + STAKE_ACC_RENT_EXEMPT_LAMPORTS
111+
);
92112
}

0 commit comments

Comments
 (0)