|
3 | 3 | mod helpers;
|
4 | 4 |
|
5 | 5 | use helpers::*;
|
| 6 | +use solana_program::{ |
| 7 | + instruction::{AccountMeta, Instruction}, |
| 8 | + pubkey::Pubkey, |
| 9 | + sysvar, |
| 10 | +}; |
6 | 11 | use solana_program_test::*;
|
7 | 12 | use solana_sdk::{
|
8 | 13 | signature::{Keypair, Signer},
|
9 | 14 | transaction::Transaction,
|
10 | 15 | };
|
11 | 16 | use solend_program::{
|
12 |
| - instruction::refresh_reserve, |
| 17 | + instruction::{refresh_reserve, LendingInstruction}, |
13 | 18 | math::{Decimal, Rate, TryAdd, TryDiv, TryMul, TrySub},
|
14 | 19 | processor::process_instruction,
|
15 | 20 | state::SLOTS_PER_YEAR,
|
@@ -152,3 +157,162 @@ async fn test_success() {
|
152 | 157 | sol_reserve.liquidity.accumulated_protocol_fees_wads
|
153 | 158 | );
|
154 | 159 | }
|
| 160 | + |
| 161 | +#[tokio::test] |
| 162 | +async fn test_success_no_switchboard() { |
| 163 | + let mut test = ProgramTest::new( |
| 164 | + "solend_program", |
| 165 | + solend_program::id(), |
| 166 | + processor!(process_instruction), |
| 167 | + ); |
| 168 | + |
| 169 | + // limit to track compute unit increase |
| 170 | + test.set_compute_max_units(31_000); |
| 171 | + |
| 172 | + const SOL_RESERVE_LIQUIDITY_LAMPORTS: u64 = 100 * LAMPORTS_TO_SOL; |
| 173 | + const USDC_RESERVE_LIQUIDITY_FRACTIONAL: u64 = 100 * FRACTIONAL_TO_USDC; |
| 174 | + const BORROW_AMOUNT: u64 = 100; |
| 175 | + |
| 176 | + let user_accounts_owner = Keypair::new(); |
| 177 | + let lending_market = add_lending_market(&mut test); |
| 178 | + |
| 179 | + let mut reserve_config = test_reserve_config(); |
| 180 | + reserve_config.loan_to_value_ratio = 80; |
| 181 | + |
| 182 | + // Configure reserve to a fixed borrow rate of 1% |
| 183 | + const BORROW_RATE: u8 = 1; |
| 184 | + reserve_config.min_borrow_rate = BORROW_RATE; |
| 185 | + reserve_config.optimal_borrow_rate = BORROW_RATE; |
| 186 | + reserve_config.optimal_utilization_rate = 100; |
| 187 | + |
| 188 | + let usdc_mint = add_usdc_mint(&mut test); |
| 189 | + let usdc_oracle = add_usdc_oracle(&mut test); |
| 190 | + let usdc_test_reserve = add_reserve( |
| 191 | + &mut test, |
| 192 | + &lending_market, |
| 193 | + &usdc_oracle, |
| 194 | + &user_accounts_owner, |
| 195 | + AddReserveArgs { |
| 196 | + borrow_amount: BORROW_AMOUNT, |
| 197 | + liquidity_amount: USDC_RESERVE_LIQUIDITY_FRACTIONAL, |
| 198 | + liquidity_mint_decimals: usdc_mint.decimals, |
| 199 | + liquidity_mint_pubkey: usdc_mint.pubkey, |
| 200 | + config: reserve_config, |
| 201 | + slots_elapsed: 1, // elapsed from 1; clock.slot = 2 |
| 202 | + ..AddReserveArgs::default() |
| 203 | + }, |
| 204 | + ); |
| 205 | + |
| 206 | + let sol_oracle = add_sol_oracle(&mut test); |
| 207 | + let sol_test_reserve = add_reserve( |
| 208 | + &mut test, |
| 209 | + &lending_market, |
| 210 | + &sol_oracle, |
| 211 | + &user_accounts_owner, |
| 212 | + AddReserveArgs { |
| 213 | + borrow_amount: BORROW_AMOUNT, |
| 214 | + liquidity_amount: SOL_RESERVE_LIQUIDITY_LAMPORTS, |
| 215 | + liquidity_mint_decimals: 9, |
| 216 | + liquidity_mint_pubkey: spl_token::native_mint::id(), |
| 217 | + config: reserve_config, |
| 218 | + slots_elapsed: 1, // elapsed from 1; clock.slot = 2 |
| 219 | + ..AddReserveArgs::default() |
| 220 | + }, |
| 221 | + ); |
| 222 | + |
| 223 | + let mut test_context = test.start_with_context().await; |
| 224 | + test_context.warp_to_slot(3).unwrap(); // clock.slot = 3 |
| 225 | + |
| 226 | + let ProgramTestContext { |
| 227 | + mut banks_client, |
| 228 | + payer, |
| 229 | + last_blockhash: recent_blockhash, |
| 230 | + .. |
| 231 | + } = test_context; |
| 232 | + |
| 233 | + let mut transaction = Transaction::new_with_payer( |
| 234 | + &[ |
| 235 | + refresh_reserve_no_switchboard( |
| 236 | + solend_program::id(), |
| 237 | + usdc_test_reserve.pubkey, |
| 238 | + usdc_oracle.pyth_price_pubkey, |
| 239 | + false, |
| 240 | + ), |
| 241 | + refresh_reserve_no_switchboard( |
| 242 | + solend_program::id(), |
| 243 | + sol_test_reserve.pubkey, |
| 244 | + sol_oracle.pyth_price_pubkey, |
| 245 | + true, |
| 246 | + ), |
| 247 | + ], |
| 248 | + Some(&payer.pubkey()), |
| 249 | + ); |
| 250 | + |
| 251 | + transaction.sign(&[&payer], recent_blockhash); |
| 252 | + assert!(banks_client.process_transaction(transaction).await.is_ok()); |
| 253 | + |
| 254 | + let sol_reserve = sol_test_reserve.get_state(&mut banks_client).await; |
| 255 | + let usdc_reserve = usdc_test_reserve.get_state(&mut banks_client).await; |
| 256 | + |
| 257 | + let slot_rate = Rate::from_percent(BORROW_RATE) |
| 258 | + .try_div(SLOTS_PER_YEAR) |
| 259 | + .unwrap(); |
| 260 | + let compound_rate = Rate::one().try_add(slot_rate).unwrap(); |
| 261 | + let compound_borrow = Decimal::from(BORROW_AMOUNT).try_mul(compound_rate).unwrap(); |
| 262 | + let net_new_debt = compound_borrow |
| 263 | + .try_sub(Decimal::from(BORROW_AMOUNT)) |
| 264 | + .unwrap(); |
| 265 | + let protocol_take_rate = Rate::from_percent(usdc_reserve.config.protocol_take_rate); |
| 266 | + let delta_accumulated_protocol_fees = net_new_debt.try_mul(protocol_take_rate).unwrap(); |
| 267 | + |
| 268 | + assert_eq!( |
| 269 | + sol_reserve.liquidity.cumulative_borrow_rate_wads, |
| 270 | + compound_rate.into() |
| 271 | + ); |
| 272 | + assert_eq!( |
| 273 | + sol_reserve.liquidity.cumulative_borrow_rate_wads, |
| 274 | + usdc_reserve.liquidity.cumulative_borrow_rate_wads |
| 275 | + ); |
| 276 | + assert_eq!(sol_reserve.liquidity.borrowed_amount_wads, compound_borrow); |
| 277 | + assert_eq!( |
| 278 | + sol_reserve.liquidity.borrowed_amount_wads, |
| 279 | + usdc_reserve.liquidity.borrowed_amount_wads |
| 280 | + ); |
| 281 | + assert_eq!( |
| 282 | + sol_reserve.liquidity.market_price, |
| 283 | + sol_test_reserve.market_price |
| 284 | + ); |
| 285 | + assert_eq!( |
| 286 | + usdc_reserve.liquidity.market_price, |
| 287 | + usdc_test_reserve.market_price |
| 288 | + ); |
| 289 | + assert_eq!( |
| 290 | + delta_accumulated_protocol_fees, |
| 291 | + usdc_reserve.liquidity.accumulated_protocol_fees_wads |
| 292 | + ); |
| 293 | + assert_eq!( |
| 294 | + delta_accumulated_protocol_fees, |
| 295 | + sol_reserve.liquidity.accumulated_protocol_fees_wads |
| 296 | + ); |
| 297 | +} |
| 298 | + |
| 299 | +/// Creates a `RefreshReserve` instruction |
| 300 | +pub fn refresh_reserve_no_switchboard( |
| 301 | + program_id: Pubkey, |
| 302 | + reserve_pubkey: Pubkey, |
| 303 | + reserve_liquidity_pyth_oracle_pubkey: Pubkey, |
| 304 | + with_clock: bool, |
| 305 | +) -> Instruction { |
| 306 | + let mut accounts = vec![ |
| 307 | + AccountMeta::new(reserve_pubkey, false), |
| 308 | + AccountMeta::new_readonly(reserve_liquidity_pyth_oracle_pubkey, false), |
| 309 | + ]; |
| 310 | + if with_clock { |
| 311 | + accounts.push(AccountMeta::new_readonly(sysvar::clock::id(), false)) |
| 312 | + } |
| 313 | + Instruction { |
| 314 | + program_id, |
| 315 | + accounts, |
| 316 | + data: LendingInstruction::RefreshReserve.pack(), |
| 317 | + } |
| 318 | +} |
0 commit comments