From 5299e31ff8eb7fd19b6951e038410a848e1053bc Mon Sep 17 00:00:00 2001 From: microwavedcola1 Date: Mon, 28 Feb 2022 17:17:01 +0100 Subject: [PATCH] basic stub oracle integration in tests and withdraw Signed-off-by: microwavedcola1 --- ...t_stub_oracle.rs => create_stub_oracle.rs} | 4 +- programs/mango-v4/src/instructions/mod.rs | 4 +- .../src/instructions/set_stub_oracle.rs | 8 +- .../mango-v4/src/instructions/withdraw.rs | 32 +++++-- programs/mango-v4/src/lib.rs | 4 +- .../tests/program_test/mango_client.rs | 85 +++++++++++++++++++ programs/mango-v4/tests/test_basic.rs | 33 +++++++ 7 files changed, 151 insertions(+), 19 deletions(-) rename programs/mango-v4/src/instructions/{init_stub_oracle.rs => create_stub_oracle.rs} (84%) diff --git a/programs/mango-v4/src/instructions/init_stub_oracle.rs b/programs/mango-v4/src/instructions/create_stub_oracle.rs similarity index 84% rename from programs/mango-v4/src/instructions/init_stub_oracle.rs rename to programs/mango-v4/src/instructions/create_stub_oracle.rs index 8ced159d3..f5a808dbf 100644 --- a/programs/mango-v4/src/instructions/init_stub_oracle.rs +++ b/programs/mango-v4/src/instructions/create_stub_oracle.rs @@ -5,7 +5,7 @@ use fixed::types::I80F48; use crate::state::*; #[derive(Accounts)] -pub struct InitStubOracle<'info> { +pub struct CreateStubOracle<'info> { #[account( init, seeds = [b"stub_oracle".as_ref(), token_mint.key().as_ref()], @@ -23,7 +23,7 @@ pub struct InitStubOracle<'info> { pub system_program: Program<'info, System>, } -pub fn init_stub_oracle(ctx: Context, price: I80F48) -> Result<()> { +pub fn create_stub_oracle(ctx: Context, price: I80F48) -> Result<()> { let mut oracle = ctx.accounts.oracle.load_init()?; oracle.price = price; oracle.last_updated = Clock::get()?.unix_timestamp; diff --git a/programs/mango-v4/src/instructions/mod.rs b/programs/mango-v4/src/instructions/mod.rs index 8aa854ada..d320ceda1 100644 --- a/programs/mango-v4/src/instructions/mod.rs +++ b/programs/mango-v4/src/instructions/mod.rs @@ -1,15 +1,15 @@ pub use create_account::*; pub use create_group::*; +pub use create_stub_oracle::*; pub use deposit::*; -pub use init_stub_oracle::*; pub use register_token::*; pub use set_stub_oracle::*; pub use withdraw::*; mod create_account; mod create_group; +mod create_stub_oracle; mod deposit; -mod init_stub_oracle; mod register_token; mod set_stub_oracle; mod withdraw; diff --git a/programs/mango-v4/src/instructions/set_stub_oracle.rs b/programs/mango-v4/src/instructions/set_stub_oracle.rs index eb9185b97..865bb5d67 100644 --- a/programs/mango-v4/src/instructions/set_stub_oracle.rs +++ b/programs/mango-v4/src/instructions/set_stub_oracle.rs @@ -6,13 +6,13 @@ use crate::state::*; #[derive(Accounts)] pub struct SetStubOracle<'info> { #[account(mut)] - pub stub_oracle: AccountLoader<'info, StubOracle>, + pub oracle: AccountLoader<'info, StubOracle>, } pub fn set_stub_oracle(ctx: Context, price: I80F48) -> Result<()> { - let mut stub_oracle = ctx.accounts.stub_oracle.load_init()?; - stub_oracle.price = price; - stub_oracle.last_updated = Clock::get()?.unix_timestamp; + let mut oracle = ctx.accounts.oracle.load_mut()?; + oracle.price = price; + oracle.last_updated = Clock::get()?.unix_timestamp; Ok(()) } diff --git a/programs/mango-v4/src/instructions/withdraw.rs b/programs/mango-v4/src/instructions/withdraw.rs index b95bd0280..38386e959 100644 --- a/programs/mango-v4/src/instructions/withdraw.rs +++ b/programs/mango-v4/src/instructions/withdraw.rs @@ -48,6 +48,14 @@ impl<'info> Withdraw<'info> { } } +macro_rules! zip { + ($x: expr) => ($x); + ($x: expr, $($y: expr), +) => ( + $x.zip( + zip!($($y), +)) + ) +} + // TODO: It may make sense to have the token_index passed in from the outside. // That would save a lot of computation that needs to go into finding the // right index for the mint. @@ -101,17 +109,17 @@ pub fn withdraw(ctx: Context, amount: u64, allow_borrow: bool) -> Resu // let active_len = account.indexed_positions.iter_active().count(); require!( - ctx.remaining_accounts.len() == active_len, + ctx.remaining_accounts.len() == active_len * 2, // banks + oracles MangoError::SomeError ); let mut assets = I80F48::ZERO; let mut liabilities = I80F48::ZERO; // absolute value - for (position, bank_ai) in account - .indexed_positions - .iter_active() - .zip(ctx.remaining_accounts.iter()) - { + for (position, (bank_ai, oracle_ai)) in zip!( + account.indexed_positions.iter_active(), + ctx.remaining_accounts.iter(), + ctx.remaining_accounts.iter().skip(active_len) + ) { let bank_loader = AccountLoader::<'_, TokenBank>::try_from(bank_ai)?; let bank = bank_loader.load()?; @@ -122,11 +130,17 @@ pub fn withdraw(ctx: Context, amount: u64, allow_borrow: bool) -> Resu ); // converts the token value to the basis token value for health computations - // TODO: oracles // TODO: health basis token == USDC? - let dummy_price = I80F48::ONE; + let oracle_type = determine_oracle_type(oracle_ai)?; + let price = match oracle_type { + OracleType::Stub => { + AccountLoader::<'_, StubOracle>::try_from(oracle_ai)? + .load()? + .price + } + }; - let native_basis = position.native(&bank) * dummy_price; + let native_basis = position.native(&bank) * price; if native_basis.is_positive() { assets += bank.init_asset_weight * native_basis; } else { diff --git a/programs/mango-v4/src/lib.rs b/programs/mango-v4/src/lib.rs index 17f96db21..18b3c1114 100644 --- a/programs/mango-v4/src/lib.rs +++ b/programs/mango-v4/src/lib.rs @@ -52,8 +52,8 @@ pub mod mango_v4 { // because generic anchor clients won't know how to deal with it // and it's tricky to use in typescript generally // lets do an interface pass later - pub fn init_stub_oracle(ctx: Context, price: I80F48) -> Result<()> { - instructions::init_stub_oracle(ctx, price) + pub fn create_stub_oracle(ctx: Context, price: I80F48) -> Result<()> { + instructions::create_stub_oracle(ctx, price) } pub fn set_stub_oracle(ctx: Context, price: I80F48) -> Result<()> { diff --git a/programs/mango-v4/tests/program_test/mango_client.rs b/programs/mango-v4/tests/program_test/mango_client.rs index 11579585f..528ebed9b 100644 --- a/programs/mango-v4/tests/program_test/mango_client.rs +++ b/programs/mango-v4/tests/program_test/mango_client.rs @@ -2,6 +2,8 @@ use anchor_lang::prelude::*; use anchor_lang::solana_program::sysvar::{self, SysvarId}; use anchor_lang::Key; use anchor_spl::token::{Token, TokenAccount}; +use fixed::types::I80F48; +use solana_program::instruction::Instruction; use solana_sdk::instruction; use solana_sdk::signature::{Keypair, Signer}; use solana_sdk::transport::TransportError; @@ -77,6 +79,7 @@ pub struct WithdrawInstruction<'keypair> { pub token_account: Pubkey, pub banks: Vec, + pub oracles: Vec, } #[async_trait::async_trait(?Send)] impl<'keypair> ClientInstruction for WithdrawInstruction<'keypair> { @@ -133,6 +136,13 @@ impl<'keypair> ClientInstruction for WithdrawInstruction<'keypair> { is_writable: false, is_signer: false, })); + instruction + .accounts + .extend(self.oracles.iter().map(|&pubkey| AccountMeta { + pubkey, + is_writable: false, + is_signer: false, + })); (accounts, instruction) } @@ -281,6 +291,81 @@ impl<'keypair> ClientInstruction for RegisterTokenInstruction<'keypair> { } } +pub struct SetStubOracle<'keypair> { + pub mint: Pubkey, + pub payer: &'keypair Keypair, +} +#[async_trait::async_trait(?Send)] +impl<'keypair> ClientInstruction for SetStubOracle<'keypair> { + type Accounts = mango_v4::accounts::SetStubOracle; + type Instruction = mango_v4::instruction::SetStubOracle; + + async fn to_instruction( + &self, + loader: impl ClientAccountLoader + 'async_trait, + ) -> (Self::Accounts, Instruction) { + let program_id = mango_v4::id(); + let instruction = Self::Instruction { + price: I80F48::from_num(1.0), + }; + + let oracle = Pubkey::find_program_address( + &[b"stub_oracle".as_ref(), self.mint.as_ref()], + &program_id, + ) + .0; + + let accounts = Self::Accounts { oracle }; + + let instruction = make_instruction(program_id, &accounts, instruction); + (accounts, instruction) + } + + fn signers(&self) -> Vec<&Keypair> { + vec![] + } +} + +pub struct CreateStubOracle<'keypair> { + pub mint: Pubkey, + pub payer: &'keypair Keypair, +} +#[async_trait::async_trait(?Send)] +impl<'keypair> ClientInstruction for CreateStubOracle<'keypair> { + type Accounts = mango_v4::accounts::CreateStubOracle; + type Instruction = mango_v4::instruction::CreateStubOracle; + + async fn to_instruction( + &self, + loader: impl ClientAccountLoader + 'async_trait, + ) -> (Self::Accounts, Instruction) { + let program_id = mango_v4::id(); + let instruction = Self::Instruction { + price: I80F48::from_num(1.0), + }; + + let oracle = Pubkey::find_program_address( + &[b"stub_oracle".as_ref(), self.mint.as_ref()], + &program_id, + ) + .0; + + let accounts = Self::Accounts { + oracle, + token_mint: self.mint, + payer: self.payer.pubkey(), + system_program: System::id(), + }; + + let instruction = make_instruction(program_id, &accounts, instruction); + (accounts, instruction) + } + + fn signers(&self) -> Vec<&Keypair> { + vec![self.payer] + } +} + pub struct CreateGroupInstruction<'keypair> { pub admin: &'keypair Keypair, pub payer: &'keypair Keypair, diff --git a/programs/mango-v4/tests/test_basic.rs b/programs/mango-v4/tests/test_basic.rs index fbfd25582..bfbe9eed0 100644 --- a/programs/mango-v4/tests/test_basic.rs +++ b/programs/mango-v4/tests/test_basic.rs @@ -5,6 +5,7 @@ use solana_program::pubkey::Pubkey; use solana_program_test::*; use solana_sdk::instruction::Instruction; use solana_sdk::{signature::Keypair, signer::Signer, transport::TransportError}; +use std::cmp::min; use mango_v4::state::*; use program_test::*; @@ -48,6 +49,37 @@ async fn test_basic() -> Result<(), TransportError> { .unwrap() .account; + let create_stub_oracle_accounts = send_tx( + solana, + CreateStubOracle { + mint: mint0.pubkey, + payer, + }, + ) + .await + .unwrap(); + let oracle = create_stub_oracle_accounts.oracle; + + send_tx( + solana, + SetStubOracle { + mint: mint0.pubkey, + payer, + }, + ) + .await + .unwrap(); + + send_tx( + solana, + CreateStubOracle { + mint: mint0.pubkey, + payer, + }, + ) + .await + .unwrap(); + let register_token_accounts = send_tx( solana, RegisterTokenInstruction { @@ -119,6 +151,7 @@ async fn test_basic() -> Result<(), TransportError> { owner, token_account: payer_mint0_account, banks: vec![bank], + oracles: vec![oracle], }, ) .await