Merge pull request #13 from blockworks-foundation/mc/oracle-in-withdraw

basic stub oracle integration in tests and withdraw
This commit is contained in:
microwavedcola1 2022-03-01 06:38:13 +01:00 committed by GitHub
commit 2a698f3135
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 185 additions and 21 deletions

35
.cargo/audit.toml Normal file
View File

@ -0,0 +1,35 @@
# All of the options which can be passed via CLI arguments can also be
# permanently specified in this file.
# RUSTSEC-2020-0071 and RUSTSEC-2020-0159 are low severity vulnerable upstream Solana crates. Ignored for now.
[advisories]
ignore = ["RUSTSEC-2020-0071","RUSTSEC-2020-0159"] # advisory IDs to ignore e.g. ["RUSTSEC-2019-0001", ...]
informational_warnings = ["unmaintained"] # warn for categories of informational advisories
severity_threshold = "medium" # CVSS severity ("none", "low", "medium", "high", "critical")
# Advisory Database Configuration
[database]
path = "~/.cargo/advisory-db" # Path where advisory git repo will be cloned
url = "https://github.com/RustSec/advisory-db.git" # URL to git repo
fetch = true # Perform a `git fetch` before auditing (default: true)
stale = false # Allow stale advisory DB (i.e. no commits for 90 days, default: false)
# Output Configuration
[output]
deny = [] # exit on error if unmaintained dependencies are found
format = "terminal" # "terminal" (human readable report) or "json"
quiet = false # Only print information on error
show_tree = true # Show inverse dependency trees along with advisories (default: true)
# Target Configuration
[target]
# arch = "x86_64" # Ignore advisories for CPU architectures other than this one
# os = "linux" # Ignore advisories for operating systems other than this one
[packages]
source = "all" # "all", "public" or "local"
[yanked]
enabled = false # Warn for yanked crates in Cargo.lock (default: true)
update_index = true # Auto-update the crates.io index (default: true)

View File

@ -7,5 +7,7 @@ pub enum MangoError {
#[msg("")]
SomeError,
#[msg("")]
UnknownOracle,
UnexpectedOracle,
#[msg("")]
UnknownOracleType,
}

View File

@ -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<InitStubOracle>, price: I80F48) -> Result<()> {
pub fn create_stub_oracle(ctx: Context<CreateStubOracle>, price: I80F48) -> Result<()> {
let mut oracle = ctx.accounts.oracle.load_init()?;
oracle.price = price;
oracle.last_updated = Clock::get()?.unix_timestamp;

View File

@ -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;

View File

@ -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<SetStubOracle>, 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(())
}

View File

@ -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<Withdraw>, 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,19 @@ pub fn withdraw(ctx: Context<Withdraw>, 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)?;
require!(bank.oracle == oracle_ai.key(), MangoError::UnexpectedOracle);
let native_basis = position.native(&bank) * dummy_price;
let price = match oracle_type {
OracleType::Stub => {
AccountLoader::<'_, StubOracle>::try_from(oracle_ai)?
.load()?
.price
}
};
let native_basis = position.native(&bank) * price;
if native_basis.is_positive() {
assets += bank.init_asset_weight * native_basis;
} else {

View File

@ -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<InitStubOracle>, price: I80F48) -> Result<()> {
instructions::init_stub_oracle(ctx, price)
pub fn create_stub_oracle(ctx: Context<CreateStubOracle>, price: I80F48) -> Result<()> {
instructions::create_stub_oracle(ctx, price)
}
pub fn set_stub_oracle(ctx: Context<SetStubOracle>, price: I80F48) -> Result<()> {

View File

@ -23,5 +23,5 @@ pub fn determine_oracle_type(account: &AccountInfo) -> Result<OracleType> {
return Ok(OracleType::Stub);
}
Err(MangoError::UnknownOracle.into())
Err(MangoError::UnknownOracleType.into())
}

View File

@ -2,9 +2,12 @@ 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;
use std::str::FromStr;
use super::solana::SolanaCookie;
use mango_v4::state::*;
@ -77,6 +80,7 @@ pub struct WithdrawInstruction<'keypair> {
pub token_account: Pubkey,
pub banks: Vec<Pubkey>,
pub oracles: Vec<Pubkey>,
}
#[async_trait::async_trait(?Send)]
impl<'keypair> ClientInstruction for WithdrawInstruction<'keypair> {
@ -133,6 +137,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 +292,82 @@ impl<'keypair> ClientInstruction for RegisterTokenInstruction<'keypair> {
}
}
pub struct SetStubOracle<'keypair> {
pub mint: Pubkey,
pub payer: &'keypair Keypair,
pub price: &'static str,
}
#[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_str(self.price).unwrap(),
};
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,

View File

@ -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,28 @@ 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,
price: "1.0",
},
)
.await
.unwrap();
let register_token_accounts = send_tx(
solana,
RegisterTokenInstruction {
@ -119,6 +142,7 @@ async fn test_basic() -> Result<(), TransportError> {
owner,
token_account: payer_mint0_account,
banks: vec![bank],
oracles: vec![oracle],
},
)
.await