Compare commits
2 Commits
8af143c35a
...
c2e6ba17a6
Author | SHA1 | Date |
---|---|---|
Maximilian Schneider | c2e6ba17a6 | |
Maximilian Schneider | 411dad19a1 |
|
@ -22,8 +22,8 @@ and/or here: https://docs.solana.com/cli to get set up with node and rust as wel
|
|||
sudo apt-get install -y pkg-config build-essential python3-pip jq
|
||||
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
|
||||
source $HOME/.cargo/env
|
||||
rustup override set 1.58
|
||||
rustup component add rustfmt
|
||||
rustup default nightly
|
||||
rustup component add rust-src
|
||||
```
|
||||
|
||||
|
@ -35,7 +35,7 @@ rustup update
|
|||
|
||||
### get mango
|
||||
```
|
||||
VERSION=v1.6.4
|
||||
VERSION=v1.9.1
|
||||
sh -c "$(curl -sSfL https://release.solana.com/$VERSION/install)"
|
||||
sudo apt-get install -y libssl-dev libudev-dev
|
||||
cargo install spl-token-cli
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -16,14 +16,15 @@ clap = "3.0.0-beta.2"
|
|||
solana-client = "^1.6.4"
|
||||
solana-cli = "^1.6.4"
|
||||
solana-sdk = "^1.6.4"
|
||||
mango = { version = "*", path = "../program", features=["no-entrypoint"] }
|
||||
spl-token = { version = "^3.1.0", features=["no-entrypoint"] }
|
||||
serde_json = "1.0.60"
|
||||
chrono = "*"
|
||||
common = { version = "*", path = "../common" }
|
||||
serum_dex = { version = "^0.2", git = "https://github.com/project-serum/serum-dex.git", features=["no-entrypoint", "program"] }
|
||||
|
||||
flux-aggregator = { version = "^0.1", git = "https://github.com/blockworks-foundation/solana-flux-aggregator.git", features=["program", "no-entrypoint"] }
|
||||
arrayref = "^0.3.6"
|
||||
|
||||
fixed = { version = "^1.7.0" }
|
||||
|
||||
common = { version = "*", path = "../common" }
|
||||
mango = { version = "*", path = "../program", features=["no-entrypoint"] }
|
||||
|
||||
spl-token = { version = "^3.0.0", features=["no-entrypoint"] }
|
||||
serum_dex = { version = "0.4.0", rev="3104f424ee38a415418a1cdef67970771f832857", git = "https://github.com/blockworks-foundation/serum-dex.git", default-features=false, features = ["no-entrypoint", "program"] }
|
||||
flux-aggregator = { version = "^0.1", rev="ca6706d05218acc84d164ed5149fac7612d3aa2b", git = "https://github.com/blockworks-foundation/solana-flux-aggregator.git", features=["program", "no-entrypoint"] }
|
||||
pyth-client = {version = ">=0.5.0", features = ["no-entrypoint"]}
|
||||
|
|
|
@ -17,7 +17,7 @@ use solana_sdk::commitment_config::CommitmentConfig;
|
|||
use solana_sdk::program_pack::Pack;
|
||||
use solana_sdk::pubkey::Pubkey;
|
||||
use solana_sdk::signature::{Signer};
|
||||
use mango::instruction::{init_mango_group, init_margin_account, withdraw, borrow, deposit, settle_borrow, change_borrow_limit};
|
||||
use mango::instruction::{init_mango_group, init_margin_account, withdraw, borrow, deposit, settle_borrow, change_borrow_limit, switch_oracles};
|
||||
|
||||
#[derive(Clap, Debug)]
|
||||
pub struct Opts {
|
||||
|
@ -136,7 +136,19 @@ pub enum Command {
|
|||
token_symbol: String,
|
||||
#[clap(long)]
|
||||
borrow_limit: f64
|
||||
}
|
||||
},
|
||||
SwitchOracle {
|
||||
#[clap(long, short)]
|
||||
payer: String, // assumes for now payer is same as admin
|
||||
#[clap(long, short)]
|
||||
ids_path: String,
|
||||
#[clap(long)]
|
||||
mango_group_name: String,
|
||||
#[clap(long)]
|
||||
pair_name: String,
|
||||
#[clap(long)]
|
||||
oracle: String,
|
||||
},
|
||||
}
|
||||
|
||||
impl Opts {
|
||||
|
@ -669,6 +681,48 @@ pub fn start(opts: Opts) -> Result<()> {
|
|||
let signers = vec![&payer];
|
||||
send_instructions(&client, instructions, signers, &payer.pubkey())?;
|
||||
}
|
||||
Command::SwitchOracle {
|
||||
payer,
|
||||
ids_path,
|
||||
mango_group_name,
|
||||
pair_name,
|
||||
oracle,
|
||||
} => {
|
||||
println!("SwitchOracle");
|
||||
let payer = read_keypair_file(payer.as_str())?;
|
||||
let ids: Value = serde_json::from_reader(File::open(&ids_path)?)?;
|
||||
let cluster_name = opts.cluster.name();
|
||||
let cluster_ids = &ids[cluster_name];
|
||||
let cids = ClusterIds::load(cluster_ids);
|
||||
let mgids = cids.mango_groups[&mango_group_name].clone();
|
||||
// replace old oracle with new one when assembling instruction
|
||||
let old_oracle_pk = &cids.oracles[&pair_name];
|
||||
let new_oracle_pks: Vec<Pubkey> = mgids
|
||||
.oracle_pks
|
||||
.iter()
|
||||
.map(|o| {
|
||||
if o == old_oracle_pk {
|
||||
Pubkey::from_str(oracle.as_str()).unwrap()
|
||||
} else {
|
||||
*o
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
let instruction = switch_oracles(
|
||||
&cids.mango_program_id,
|
||||
&mgids.mango_group_pk,
|
||||
&payer.pubkey(),
|
||||
&new_oracle_pks.as_slice(),
|
||||
)?;
|
||||
let instructions = vec![instruction];
|
||||
let signers = vec![&payer];
|
||||
send_instructions(&client, instructions, signers, &payer.pubkey())?;
|
||||
// update ids
|
||||
// let oracle_pks = ids[cluster_name][mango_group_name].get_mut("oracle_pks").unwrap();
|
||||
// oracle_pks = new_oracle_pks.iter().map(|pk| pk.to_string()).collect() as Vec<String>;
|
||||
// let f = File::create(ids_path.as_str()).unwrap();
|
||||
// serde_json::to_writer_pretty(&f, &ids).unwrap();
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -6,15 +6,15 @@ edition = "2018"
|
|||
|
||||
|
||||
[dependencies]
|
||||
solana-sdk = { version = "1.5.11", features=["default"] }
|
||||
spl-token = { version = "3.0.0", features=["no-entrypoint"] }
|
||||
anyhow = "1.0.35"
|
||||
solana-client = "1.5.11"
|
||||
rand = "0.7.3"
|
||||
serde_json = "1.0.60"
|
||||
solana-sdk = "^1.7.10"
|
||||
spl-token = { version = "^3.2.0", features=["no-entrypoint"] }
|
||||
anyhow = "^1.0.43"
|
||||
solana-client = "^1.7.10"
|
||||
rand = "^0.7.0"
|
||||
serde_json = "^1.0.66"
|
||||
bs58 = "0.4.0"
|
||||
bincode = "1.3.1"
|
||||
bytemuck = "1.4.1"
|
||||
tiny-bip39 = "0.7.3"
|
||||
tiny-hderive = "0.2.1"
|
||||
ed25519-dalek = "1.0.0-pre.4"
|
||||
bincode = "^1.3.1"
|
||||
bytemuck = "^1.7.2"
|
||||
tiny-bip39 = "0.8.0"
|
||||
tiny-hderive = "0.3.0"
|
||||
ed25519-dalek = "^1.0.0"
|
||||
|
|
|
@ -57,7 +57,7 @@ impl std::fmt::Display for Cluster {
|
|||
impl Cluster {
|
||||
pub fn url(&self) -> &'static str {
|
||||
match self {
|
||||
Cluster::Devnet => "https://devnet.solana.com",
|
||||
Cluster::Devnet => "https://api.devnet.solana.com",
|
||||
Cluster::Testnet => "https://testnet.solana.com",
|
||||
// Cluster::Mainnet => "https://api.stakeconomy.com",
|
||||
Cluster::Mainnet => "https://api.mainnet-beta.solana.com",
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "mango"
|
||||
version = "0.2.6"
|
||||
version = "0.3.0"
|
||||
authors = ["blockworks"]
|
||||
edition = "2018"
|
||||
|
||||
|
@ -11,7 +11,6 @@ test-bpf = []
|
|||
|
||||
[dependencies]
|
||||
solana-program = "^1.6.4"
|
||||
spl-token = { version = "^3.0.0", features=["no-entrypoint"] }
|
||||
byteorder = "^1.3.4"
|
||||
arrayref = "^0.3.6"
|
||||
num_enum = "^0.5.1"
|
||||
|
@ -22,13 +21,15 @@ static_assertions = "^1.1.0"
|
|||
thiserror = "^1.0.24"
|
||||
serde = "^1.0.118"
|
||||
bincode = "^1.3.1"
|
||||
serum_dex = { version = "^0.2", git = "https://github.com/blockworks-foundation/serum-dex.git", features=["no-entrypoint", "program"] }
|
||||
|
||||
num-derive = "^0.3.3"
|
||||
flux-aggregator = { version = "^0.1", git = "https://github.com/blockworks-foundation/solana-flux-aggregator.git", features=["program", "no-entrypoint"] }
|
||||
fixed = { version = "^1.7.0", features=["serde"] }
|
||||
fixed-macro = "^1.1.1"
|
||||
|
||||
spl-token = { version = "^3.0.0", features=["no-entrypoint"] }
|
||||
serum_dex = { version = "0.4.0", rev="3104f424ee38a415418a1cdef67970771f832857", git = "https://github.com/blockworks-foundation/serum-dex.git", default-features=false, features = ["no-entrypoint", "program"] }
|
||||
flux-aggregator = { version = "^0.1", rev="ca6706d05218acc84d164ed5149fac7612d3aa2b", git = "https://github.com/blockworks-foundation/solana-flux-aggregator.git", features=["program", "no-entrypoint"] }
|
||||
pyth-client = {version = ">=0.5.0", features = ["no-entrypoint"]}
|
||||
|
||||
[dev-dependencies]
|
||||
solana-sdk = "^1.6.4"
|
||||
solana-program-test = "^1.6.4"
|
||||
|
|
|
@ -63,6 +63,8 @@ pub enum MangoErrorCode {
|
|||
InvalidMangoVault,
|
||||
#[error("MangoErrorCode::BeingLiquidated The margin account has restricted functionality while being liquidated")]
|
||||
BeingLiquidated,
|
||||
#[error("MangoErrorCode::OracleOffline")]
|
||||
OracleOffline,
|
||||
|
||||
#[error("MangoErrorCode::Default Check the source code for more info")]
|
||||
Default = u32::MAX_VALUE,
|
||||
|
@ -88,6 +90,14 @@ impl From<serum_dex::error::DexError> for MangoError {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
impl From<pyth_client::PythError> for MangoError {
|
||||
fn from(pyth_e: pyth_client::PythError) -> Self {
|
||||
let prog_e: ProgramError = pyth_e.into();
|
||||
prog_e.into()
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn check_assert(
|
||||
cond: bool,
|
||||
|
|
|
@ -36,7 +36,7 @@ pub enum MangoInstruction {
|
|||
/// 7+2*NUM_TOKENS..7+2*NUM_TOKENS+NUM_MARKETS `[]`
|
||||
/// spot_market_accs - MarketState account from serum dex for each of the spot markets
|
||||
/// 7+2*NUM_TOKENS+NUM_MARKETS..7+2*NUM_TOKENS+2*NUM_MARKETS `[]`
|
||||
/// oracle_accs - Solana Flux Aggregator accounts corresponding to each trading pair
|
||||
/// oracle_accs - Pyth Price / Solana Flux Aggregator accounts corresponding to each trading pair
|
||||
InitMangoGroup {
|
||||
signer_nonce: u64,
|
||||
maint_coll_ratio: U64F64,
|
||||
|
@ -83,7 +83,7 @@ pub enum MangoInstruction {
|
|||
/// 7. `[]` clock_acc - Clock sysvar account
|
||||
/// 8..8+NUM_MARKETS `[]` open_orders_accs - open orders for each of the spot market
|
||||
/// 8+NUM_MARKETS..8+2*NUM_MARKETS `[]`
|
||||
/// oracle_accs - flux aggregator feed accounts
|
||||
/// oracle_accs - Pyth Price / Solana Flux Aggregator accounts corresponding to each trading pair
|
||||
Withdraw {
|
||||
quantity: u64
|
||||
},
|
||||
|
@ -98,7 +98,7 @@ pub enum MangoInstruction {
|
|||
/// 3. `[]` clock_acc - Clock sysvar account
|
||||
/// 4..4+NUM_MARKETS `[]` open_orders_accs - open orders for each of the spot market
|
||||
/// 4+NUM_MARKETS..4+2*NUM_MARKETS `[]`
|
||||
/// oracle_accs - flux aggregator feed accounts
|
||||
/// oracle_accs - Pyth Price / Solana Flux Aggregator accounts corresponding to each trading pair
|
||||
Borrow {
|
||||
token_index: usize,
|
||||
quantity: u64
|
||||
|
@ -128,7 +128,7 @@ pub enum MangoInstruction {
|
|||
/// 4. `[]` clock_acc - Clock sysvar account
|
||||
/// 5..5+NUM_MARKETS `[]` open_orders_accs - open orders for each of the spot market
|
||||
/// 5+NUM_MARKETS..5+2*NUM_MARKETS `[]`
|
||||
/// oracle_accs - flux aggregator feed accounts
|
||||
/// oracle_accs - Pyth Price / Solana Flux Aggregator accounts corresponding to each trading pair
|
||||
/// 5+2*NUM_MARKETS..5+2*NUM_MARKETS+NUM_TOKENS `[writable]`
|
||||
/// vault_accs - MangoGroup vaults
|
||||
/// 5+2*NUM_MARKETS+NUM_TOKENS..5+2*NUM_MARKETS+2*NUM_TOKENS `[writable]`
|
||||
|
@ -197,7 +197,7 @@ pub enum MangoInstruction {
|
|||
/// 16. `[writable]` srm_vault_acc - MangoGroup's srm_vault used for fee reduction
|
||||
/// 17..17+NUM_MARKETS `[writable]` open_orders_accs - open orders for each of the spot market
|
||||
/// 17+NUM_MARKETS..17+2*NUM_MARKETS `[]`
|
||||
/// oracle_accs - flux aggregator feed accounts
|
||||
/// oracle_accs - Pyth Price / Solana Flux Aggregator accounts corresponding to each trading pair
|
||||
PlaceOrder {
|
||||
order: serum_dex::instruction::NewOrderInstructionV3
|
||||
},
|
||||
|
@ -297,7 +297,7 @@ pub enum MangoInstruction {
|
|||
/// 18. `[]` dex_signer_acc - signer for serum dex MarketState
|
||||
/// 19..19+NUM_MARKETS `[writable]` open_orders_accs - open orders for each of the spot market
|
||||
/// 19+NUM_MARKETS..19+2*NUM_MARKETS `[]`
|
||||
/// oracle_accs - flux aggregator feed accounts
|
||||
/// oracle_accs - Pyth Price / Solana Flux Aggregator accounts corresponding to each trading pair
|
||||
PlaceAndSettle {
|
||||
order: serum_dex::instruction::NewOrderInstructionV3
|
||||
},
|
||||
|
@ -324,7 +324,7 @@ pub enum MangoInstruction {
|
|||
/// 15. `[]` clock_acc - Clock sysvar account
|
||||
/// 16..16+NUM_MARKETS `[writable]` open_orders_accs - open orders for each of the spot market
|
||||
/// 16+NUM_MARKETS..16+2*NUM_MARKETS `[]`
|
||||
/// oracle_accs - flux aggregator feed accounts
|
||||
/// oracle_accs - Pyth Price / Solana Flux Aggregator accounts corresponding to each trading pair
|
||||
ForceCancelOrders {
|
||||
/// Max orders to cancel -- could be useful to lower this if running into compute limits
|
||||
/// Recommended: 5
|
||||
|
@ -347,12 +347,18 @@ pub enum MangoInstruction {
|
|||
/// 9. `[]` clock_acc - Clock sysvar account
|
||||
/// 10..10+NUM_MARKETS `[]` open_orders_accs - open orders for each of the spot market
|
||||
/// 10+NUM_MARKETS..10+2*NUM_MARKETS `[]`
|
||||
/// oracle_accs - flux aggregator feed accounts
|
||||
/// oracle_accs - Pyth Price / Solana Flux Aggregator accounts corresponding to each trading pair
|
||||
PartialLiquidate {
|
||||
/// Quantity of the token being deposited to repay borrows
|
||||
max_deposit: u64
|
||||
},
|
||||
|
||||
|
||||
/// Allows to switch the oracles to a new set
|
||||
/// 0. `[writable]` mango_group_acc - the data account to store mango group state vars
|
||||
/// 1. `[signer]` admin_acc - admin key that created the group
|
||||
/// 2..2+NUM_MARKETS `[]` oracle_accs - Pyth Price / Solana Flux Aggregator accounts corresponding to each trading pair
|
||||
SwitchOracles
|
||||
}
|
||||
|
||||
|
||||
|
@ -498,6 +504,9 @@ impl MangoInstruction {
|
|||
max_deposit: u64::from_le_bytes(*max_deposit)
|
||||
}
|
||||
}
|
||||
17 => {
|
||||
MangoInstruction::SwitchOracles
|
||||
}
|
||||
_ => { return None; }
|
||||
})
|
||||
}
|
||||
|
@ -1202,3 +1211,25 @@ pub fn partial_liquidate(
|
|||
data
|
||||
})
|
||||
}
|
||||
|
||||
pub fn switch_oracles(
|
||||
program_id: &Pubkey,
|
||||
mango_group_pk: &Pubkey,
|
||||
admin_pk: &Pubkey,
|
||||
oracle_pks: &[Pubkey],
|
||||
) -> Result<Instruction, ProgramError> {
|
||||
let mut accounts = vec![
|
||||
AccountMeta::new(*mango_group_pk, false),
|
||||
AccountMeta::new_readonly(*admin_pk, true),
|
||||
];
|
||||
accounts.extend(oracle_pks.iter().map(
|
||||
|pk| AccountMeta::new_readonly(*pk, false))
|
||||
);
|
||||
let instr = MangoInstruction::SwitchOracles {};
|
||||
let data = instr.pack();
|
||||
Ok(Instruction {
|
||||
program_id: *program_id,
|
||||
accounts,
|
||||
data
|
||||
})
|
||||
}
|
||||
|
|
|
@ -1,11 +1,13 @@
|
|||
use std::cmp;
|
||||
use std::cmp::min;
|
||||
use std::mem::size_of;
|
||||
use std::ops::Neg;
|
||||
|
||||
use arrayref::{array_ref, array_refs};
|
||||
use fixed::types::U64F64;
|
||||
use fixed_macro::types::U64F64;
|
||||
use flux_aggregator::borsh_state::InitBorshState;
|
||||
use pyth_client::PriceStatus;
|
||||
use serum_dex::matching::Side;
|
||||
use serum_dex::state::ToAlignedBytes;
|
||||
use solana_program::account_info::AccountInfo;
|
||||
|
@ -1433,6 +1435,51 @@ impl Processor {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
#[inline(never)]
|
||||
fn switch_oracles(
|
||||
program_id: &Pubkey,
|
||||
accounts: &[AccountInfo],
|
||||
) -> MangoResult<()> {
|
||||
const NUM_FIXED: usize = 2;
|
||||
let accounts = array_ref![accounts, 0, NUM_FIXED + NUM_MARKETS];
|
||||
let (fixed_accs, oracle_accs) = array_refs![accounts, NUM_FIXED, NUM_MARKETS];
|
||||
let [
|
||||
mango_group_acc,
|
||||
admin_acc,
|
||||
] = fixed_accs;
|
||||
|
||||
let mut mango_group = MangoGroup::load_mut_checked(
|
||||
mango_group_acc, program_id)?;
|
||||
check_eq!(admin_acc.key, &mango_group.admin, MangoErrorCode::InvalidGroupOwner)?;
|
||||
check!(admin_acc.is_signer, MangoErrorCode::SignerNecessary)?;
|
||||
|
||||
for i in 0..NUM_MARKETS {
|
||||
mango_group.oracles[i] = *oracle_accs[i].key;
|
||||
|
||||
// determine oracle type
|
||||
let borrowed = oracle_accs[i].data.borrow();
|
||||
let magic = u32::from_le_bytes(*array_ref![borrowed, 0, 4]);
|
||||
|
||||
// read oracle decimals
|
||||
let decimals = if magic == pyth_client::MAGIC {
|
||||
// detected pyth oracle
|
||||
let price_account = pyth_client::load_price(&borrowed)?;
|
||||
// usually expo is -8, verify anyways that it's within bounds
|
||||
check!(price_account.expo <= 0, MangoErrorCode::Default);
|
||||
check!(price_account.expo >= -255, MangoErrorCode::Default);
|
||||
price_account.expo.neg() as u8
|
||||
} else {
|
||||
// fall back to legacy flux aggregator
|
||||
let oracle = flux_aggregator::state::Aggregator::load_initialized(&oracle_accs[i])?;
|
||||
oracle.config.decimals
|
||||
};
|
||||
|
||||
mango_group.oracle_decimals[i] = decimals;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn process(
|
||||
program_id: &Pubkey,
|
||||
accounts: &[AccountInfo],
|
||||
|
@ -1544,6 +1591,10 @@ impl Processor {
|
|||
msg!("Mango: PartialLiquidate");
|
||||
Self::partial_liquidate(program_id, accounts, max_deposit)?;
|
||||
}
|
||||
MangoInstruction::SwitchOracles => {
|
||||
msg!("Mango: SwitchOracles");
|
||||
Self::switch_oracles(program_id, accounts)?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
@ -1675,16 +1726,27 @@ pub fn get_prices(
|
|||
for i in 0..NUM_MARKETS {
|
||||
check_eq_default!(&mango_group.oracles[i], oracle_accs[i].key)?;
|
||||
|
||||
// TODO store this info in MangoGroup, first make sure it cannot be changed by solink
|
||||
let base_adj = U64F64::from_num(10u64.pow(mango_group.mint_decimals[i] as u32));
|
||||
let quote_adj = U64F64::from_num(
|
||||
10u64.pow(quote_decimals.checked_sub(mango_group.oracle_decimals[i]).unwrap() as u32)
|
||||
);
|
||||
|
||||
let answer = flux_aggregator::read_median(&oracle_accs[i])?; // this is in USD cents
|
||||
// determine oracle type
|
||||
let borrowed = oracle_accs[i].data.borrow();
|
||||
let magic = u32::from_le_bytes(*array_ref![borrowed, 0, 4]);
|
||||
|
||||
let value = U64F64::from_num(answer.median);
|
||||
// read oracle value
|
||||
let value = if magic == pyth_client::MAGIC {
|
||||
// detected pyth oracle
|
||||
let price_account = pyth_client::load_price(&borrowed)?;
|
||||
check_eq!(price_account.get_current_price_status(), PriceStatus::Trading, MangoErrorCode::OracleOffline);
|
||||
U64F64::from_num(price_account.agg.price)
|
||||
} else {
|
||||
// fall back to legacy flux aggregator
|
||||
let answer = flux_aggregator::read_median(&oracle_accs[i])?; // this is in USD cents
|
||||
U64F64::from_num(answer.median)
|
||||
};
|
||||
|
||||
let base_adj = U64F64::from_num(10u64.pow(mango_group.mint_decimals[i] as u32));
|
||||
prices[i] = quote_adj
|
||||
.checked_div(base_adj).unwrap()
|
||||
.checked_mul(value).unwrap();
|
||||
|
|
Loading…
Reference in New Issue