edit tokens, perp markets, mango accounts, allow delegate to perform certain operations (#94)

Signed-off-by: microwavedcola1 <microwavedcola@gmail.com>
This commit is contained in:
microwavedcola1 2022-07-05 19:31:47 +02:00 committed by GitHub
parent dc4aee885b
commit d74cc78a84
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
39 changed files with 1681 additions and 166 deletions

View File

@ -9,6 +9,7 @@ pub struct CloseAccount<'info> {
#[account(
mut,
// note: should never be the delegate
has_one = owner,
has_one = group,
close = sol_destination

View File

@ -17,7 +17,6 @@ pub struct CreateAccount<'info> {
space = 8 + std::mem::size_of::<MangoAccount>(),
)]
pub account: AccountLoader<'info, MangoAccount>,
pub owner: Signer<'info>,
#[account(mut)]

View File

@ -0,0 +1,62 @@
use crate::error::MangoError;
use anchor_lang::prelude::*;
use crate::state::*;
use crate::util::fill32_from_str;
#[derive(Accounts)]
pub struct EditAccount<'info> {
pub group: AccountLoader<'info, Group>,
#[account(
mut,
// Note: should never be the delegate
has_one = owner,
has_one = group,
)]
pub account: AccountLoader<'info, MangoAccount>,
pub owner: Signer<'info>,
}
pub fn edit_account(
ctx: Context<EditAccount>,
name_opt: Option<String>,
// note: can also be used to unset by using the default pubkey here as a param
delegate_opt: Option<Pubkey>,
) -> Result<()> {
require!(
name_opt.is_some() || delegate_opt.is_some(),
MangoError::SomeError
);
let mut account = ctx.accounts.account.load_mut()?;
// msg!("old account {:#?}", account);
// note: unchanged fields are inline, and match exact definition in create_account
// please maintain, and don't remove, makes it easy to reason about which support modification by owner
if let Some(name) = name_opt {
account.name = fill32_from_str(name)?;
}
// unchanged -
// owner
// account_num
// bump
if let Some(delegate) = delegate_opt {
account.delegate = delegate;
}
// unchanged -
// tokens
// serum3
// perps
// being_liquidated
// is_bankrupt
// msg!("new account {:#?}", account);
Ok(())
}

View File

@ -32,7 +32,7 @@ pub struct FlashLoan<'info> {
#[account(
mut,
has_one = group,
has_one = owner,
constraint = account.load()?.is_owner_or_delegate(owner.key()),
)]
pub account: AccountLoader<'info, MangoAccount>,

View File

@ -37,11 +37,12 @@ pub struct FlashLoan2End<'info> {
pub group: AccountLoader<'info, Group>,
#[account(
mut,
has_one = owner,
has_one = group,
constraint = account.load()?.is_owner_or_delegate(owner.key()),
)]
pub account: AccountLoader<'info, MangoAccount>,
pub owner: Signer<'info>,
pub token_program: Program<'info, Token>,
}

View File

@ -40,10 +40,11 @@ pub struct FlashLoan3Begin<'info> {
pub struct FlashLoan3End<'info> {
#[account(
mut,
has_one = owner,
constraint = account.load()?.is_owner_or_delegate(owner.key()),
)]
pub account: AccountLoader<'info, MangoAccount>,
pub owner: Signer<'info>,
pub token_program: Program<'info, Token>,
}

View File

@ -15,9 +15,9 @@ pub struct LiqTokenWithToken<'info> {
#[account(
mut,
has_one = group,
constraint = liqor.load()?.owner == liqor_owner.key() || liqor.load()?.delegate == liqor_owner.key(),
)]
pub liqor: AccountLoader<'info, MangoAccount>,
#[account(address = liqor.load()?.owner)]
pub liqor_owner: Signer<'info>,
#[account(

View File

@ -6,6 +6,7 @@ pub use compute_account_data::*;
pub use create_account::*;
pub use create_group::*;
pub use create_stub_oracle::*;
pub use edit_account::*;
pub use flash_loan::*;
pub use flash_loan2::*;
pub use flash_loan3::*;
@ -17,6 +18,7 @@ pub use perp_cancel_order_by_client_order_id::*;
pub use perp_close_market::*;
pub use perp_consume_events::*;
pub use perp_create_market::*;
pub use perp_edit_market::*;
pub use perp_place_order::*;
pub use perp_update_funding::*;
pub use serum3_cancel_all_orders::*;
@ -32,6 +34,7 @@ pub use set_stub_oracle::*;
pub use token_add_bank::*;
pub use token_deposit::*;
pub use token_deregister::*;
pub use token_edit::*;
pub use token_register::*;
pub use token_withdraw::*;
pub use update_index::*;
@ -44,6 +47,7 @@ mod compute_account_data;
mod create_account;
mod create_group;
mod create_stub_oracle;
mod edit_account;
mod flash_loan;
mod flash_loan2;
mod flash_loan3;
@ -55,6 +59,7 @@ mod perp_cancel_order_by_client_order_id;
mod perp_close_market;
mod perp_consume_events;
mod perp_create_market;
mod perp_edit_market;
mod perp_place_order;
mod perp_update_funding;
mod serum3_cancel_all_orders;
@ -70,6 +75,7 @@ mod set_stub_oracle;
mod token_add_bank;
mod token_deposit;
mod token_deregister;
mod token_edit;
mod token_register;
mod token_withdraw;
mod update_index;

View File

@ -10,9 +10,10 @@ pub struct PerpCancelAllOrders<'info> {
#[account(
mut,
has_one = group,
has_one = owner,
constraint = account.load()?.is_owner_or_delegate(owner.key()),
)]
pub account: AccountLoader<'info, MangoAccount>,
pub owner: Signer<'info>,
#[account(
mut,
@ -25,8 +26,6 @@ pub struct PerpCancelAllOrders<'info> {
pub asks: AccountLoader<'info, BookSide>,
#[account(mut)]
pub bids: AccountLoader<'info, BookSide>,
pub owner: Signer<'info>,
}
pub fn perp_cancel_all_orders(ctx: Context<PerpCancelAllOrders>, limit: u8) -> Result<()> {

View File

@ -10,9 +10,10 @@ pub struct PerpCancelAllOrdersBySide<'info> {
#[account(
mut,
has_one = group,
has_one = owner,
constraint = account.load()?.is_owner_or_delegate(owner.key()),
)]
pub account: AccountLoader<'info, MangoAccount>,
pub owner: Signer<'info>,
#[account(
mut,
@ -25,8 +26,6 @@ pub struct PerpCancelAllOrdersBySide<'info> {
pub asks: AccountLoader<'info, BookSide>,
#[account(mut)]
pub bids: AccountLoader<'info, BookSide>,
pub owner: Signer<'info>,
}
pub fn perp_cancel_all_orders_by_side(

View File

@ -10,9 +10,10 @@ pub struct PerpCancelOrder<'info> {
#[account(
mut,
has_one = group,
has_one = owner,
constraint = account.load()?.is_owner_or_delegate(owner.key()),
)]
pub account: AccountLoader<'info, MangoAccount>,
pub owner: Signer<'info>,
#[account(
mut,
@ -25,8 +26,6 @@ pub struct PerpCancelOrder<'info> {
pub asks: AccountLoader<'info, BookSide>,
#[account(mut)]
pub bids: AccountLoader<'info, BookSide>,
pub owner: Signer<'info>,
}
pub fn perp_cancel_order(ctx: Context<PerpCancelOrder>, order_id: i128) -> Result<()> {

View File

@ -10,9 +10,10 @@ pub struct PerpCancelOrderByClientOrderId<'info> {
#[account(
mut,
has_one = group,
has_one = owner,
constraint = account.load()?.is_owner_or_delegate(owner.key()),
)]
pub account: AccountLoader<'info, MangoAccount>,
pub owner: Signer<'info>,
#[account(
mut,
@ -25,8 +26,6 @@ pub struct PerpCancelOrderByClientOrderId<'info> {
pub asks: AccountLoader<'info, BookSide>,
#[account(mut)]
pub bids: AccountLoader<'info, BookSide>,
pub owner: Signer<'info>,
}
pub fn perp_cancel_order_by_client_order_id(

View File

@ -0,0 +1,118 @@
use crate::state::*;
use anchor_lang::prelude::*;
use fixed::types::I80F48;
#[derive(Accounts)]
pub struct PerpEditMarket<'info> {
#[account(
has_one = admin,
)]
pub group: AccountLoader<'info, Group>,
pub admin: Signer<'info>,
#[account(
mut,
has_one = group
)]
pub perp_market: AccountLoader<'info, PerpMarket>,
}
#[allow(clippy::too_many_arguments)]
pub fn perp_edit_market(
ctx: Context<PerpEditMarket>,
oracle_opt: Option<Pubkey>,
oracle_config_opt: Option<OracleConfig>,
base_token_index_opt: Option<TokenIndex>,
base_token_decimals_opt: Option<u8>,
maint_asset_weight_opt: Option<f32>,
init_asset_weight_opt: Option<f32>,
maint_liab_weight_opt: Option<f32>,
init_liab_weight_opt: Option<f32>,
liquidation_fee_opt: Option<f32>,
maker_fee_opt: Option<f32>,
taker_fee_opt: Option<f32>,
min_funding_opt: Option<f32>,
max_funding_opt: Option<f32>,
impact_quantity_opt: Option<i64>,
) -> Result<()> {
let mut perp_market = ctx.accounts.perp_market.load_mut()?;
// note: unchanged fields are inline, and match exact definition in perp_register_market
// please maintain, and don't remove, makes it easy to reason about which support admin modification
// unchanged -
// name
// group
if let Some(oracle) = oracle_opt {
perp_market.oracle = oracle;
}
if let Some(oracle_config) = oracle_config_opt {
perp_market.oracle_config = oracle_config;
};
// unchanged -
// bids
// asks
// event_queue
// quote_lot_size
// base_lot_size
if let Some(maint_asset_weight) = maint_asset_weight_opt {
perp_market.maint_asset_weight = I80F48::from_num(maint_asset_weight);
}
if let Some(init_asset_weight) = init_asset_weight_opt {
perp_market.init_asset_weight = I80F48::from_num(init_asset_weight);
}
if let Some(maint_liab_weight) = maint_liab_weight_opt {
perp_market.maint_liab_weight = I80F48::from_num(maint_liab_weight);
}
if let Some(init_liab_weight) = init_liab_weight_opt {
perp_market.init_liab_weight = I80F48::from_num(init_liab_weight);
}
if let Some(liquidation_fee) = liquidation_fee_opt {
perp_market.liquidation_fee = I80F48::from_num(liquidation_fee);
}
if let Some(maker_fee) = maker_fee_opt {
perp_market.maker_fee = I80F48::from_num(maker_fee);
}
if let Some(taker_fee) = taker_fee_opt {
perp_market.taker_fee = I80F48::from_num(taker_fee);
}
if let Some(min_funding) = min_funding_opt {
perp_market.min_funding = I80F48::from_num(min_funding);
}
if let Some(max_funding) = max_funding_opt {
perp_market.max_funding = I80F48::from_num(max_funding);
}
if let Some(impact_quantity) = impact_quantity_opt {
perp_market.impact_quantity = impact_quantity;
}
// unchanged -
// long_funding
// short_funding
// funding_last_updated
// open_interest
// seq_num
// fees_accrued
// bump
if let Some(base_token_decimals) = base_token_decimals_opt {
perp_market.base_token_decimals = base_token_decimals;
}
// unchanged -
// perp_market_index
if let Some(base_token_index) = base_token_index_opt {
perp_market.base_token_index = base_token_index;
}
// unchanged -
// quote_token_index
Ok(())
}

View File

@ -14,9 +14,10 @@ pub struct PerpPlaceOrder<'info> {
#[account(
mut,
has_one = group,
has_one = owner,
constraint = account.load()?.is_owner_or_delegate(owner.key()),
)]
pub account: AccountLoader<'info, MangoAccount>,
pub owner: Signer<'info>,
#[account(
mut,
@ -36,8 +37,6 @@ pub struct PerpPlaceOrder<'info> {
/// CHECK: The oracle can be one of several different account types and the pubkey is checked above
pub oracle: UncheckedAccount<'info>,
pub owner: Signer<'info>,
}
// TODO

View File

@ -10,7 +10,7 @@ pub struct Serum3CancelAllOrders<'info> {
#[account(
mut,
has_one = group,
has_one = owner,
constraint = account.load()?.is_owner_or_delegate(owner.key()),
)]
pub account: AccountLoader<'info, MangoAccount>,
pub owner: Signer<'info>,

View File

@ -17,7 +17,7 @@ pub struct Serum3CancelOrder<'info> {
#[account(
mut,
has_one = group,
has_one = owner,
constraint = account.load()?.is_owner_or_delegate(owner.key()),
)]
pub account: AccountLoader<'info, MangoAccount>,
pub owner: Signer<'info>,

View File

@ -10,7 +10,7 @@ pub struct Serum3CloseOpenOrders<'info> {
#[account(
mut,
has_one = group,
has_one = owner,
constraint = account.load()?.is_owner_or_delegate(owner.key()),
)]
pub account: AccountLoader<'info, MangoAccount>,
pub owner: Signer<'info>,

View File

@ -10,9 +10,10 @@ pub struct Serum3CreateOpenOrders<'info> {
#[account(
mut,
has_one = group,
has_one = owner,
constraint = account.load()?.is_owner_or_delegate(owner.key()),
)]
pub account: AccountLoader<'info, MangoAccount>,
pub owner: Signer<'info>,
#[account(
has_one = group,
@ -38,8 +39,6 @@ pub struct Serum3CreateOpenOrders<'info> {
/// CHECK: Newly created by serum cpi call
pub open_orders: UncheckedAccount<'info>,
pub owner: Signer<'info>,
#[account(mut)]
pub payer: Signer<'info>,

View File

@ -86,7 +86,7 @@ pub struct Serum3PlaceOrder<'info> {
#[account(
mut,
has_one = group,
has_one = owner,
constraint = account.load()?.is_owner_or_delegate(owner.key()),
)]
pub account: AccountLoader<'info, MangoAccount>,
pub owner: Signer<'info>,

View File

@ -18,7 +18,7 @@ pub struct Serum3SettleFunds<'info> {
#[account(
mut,
has_one = group,
has_one = owner,
constraint = account.load()?.is_owner_or_delegate(owner.key()),
)]
pub account: AccountLoader<'info, MangoAccount>,
pub owner: Signer<'info>,

View File

@ -0,0 +1,121 @@
use anchor_lang::prelude::*;
use fixed::types::I80F48;
use super::InterestRateParams;
use crate::accounts_zerocopy::LoadMutZeroCopyRef;
use crate::state::*;
#[derive(Accounts)]
#[instruction(token_index: TokenIndex, bank_num: u64)]
pub struct TokenEdit<'info> {
#[account(
has_one = admin,
)]
pub group: AccountLoader<'info, Group>,
pub admin: Signer<'info>,
#[account(
has_one = group
)]
pub mint_info: AccountLoader<'info, MintInfo>,
}
#[allow(unused_variables)]
#[allow(clippy::too_many_arguments)]
pub fn token_edit(
ctx: Context<TokenEdit>,
bank_num: u64,
oracle_opt: Option<Pubkey>,
oracle_config_opt: Option<OracleConfig>,
interest_rate_params_opt: Option<InterestRateParams>,
loan_fee_rate_opt: Option<f32>,
loan_origination_fee_rate_opt: Option<f32>,
maint_asset_weight_opt: Option<f32>,
init_asset_weight_opt: Option<f32>,
maint_liab_weight_opt: Option<f32>,
init_liab_weight_opt: Option<f32>,
liquidation_fee_opt: Option<f32>,
) -> Result<()> {
ctx.accounts
.mint_info
.load()?
.verify_banks_ais(ctx.remaining_accounts)?;
for ai in ctx.remaining_accounts.iter() {
let mut bank = ai.load_mut::<Bank>()?;
// note: unchanged fields are inline, and match exact definition in register_token
// please maintain, and don't remove, makes it easy to reason about which support admin modification
// unchanged -
// name
// group
// mint
// vault
if let Some(oracle) = oracle_opt {
bank.oracle = oracle;
}
if let Some(oracle_config) = oracle_config_opt {
bank.oracle_config = oracle_config;
};
// unchanged -
// deposit_index
// borrow_index
// cached_indexed_total_deposits
// cached_indexed_total_borrows
// indexed_deposits
// indexed_borrows
// last_updated
if let Some(ref interest_rate_params) = interest_rate_params_opt {
// TODO: add a require! verifying relation between the parameters
bank.util0 = I80F48::from_num(interest_rate_params.util0);
bank.rate0 = I80F48::from_num(interest_rate_params.rate0);
bank.util1 = I80F48::from_num(interest_rate_params.util1);
bank.rate1 = I80F48::from_num(interest_rate_params.rate1);
bank.max_rate = I80F48::from_num(interest_rate_params.max_rate);
}
// unchanged -
// collected_fees_native
if let Some(loan_origination_fee_rate) = loan_origination_fee_rate_opt {
bank.loan_origination_fee_rate = I80F48::from_num(loan_origination_fee_rate);
}
if let Some(loan_fee_rate) = loan_fee_rate_opt {
bank.loan_fee_rate = I80F48::from_num(loan_fee_rate);
}
if let Some(maint_asset_weight) = maint_asset_weight_opt {
bank.maint_asset_weight = I80F48::from_num(maint_asset_weight);
}
if let Some(init_asset_weight) = init_asset_weight_opt {
bank.init_asset_weight = I80F48::from_num(init_asset_weight);
}
if let Some(maint_liab_weight) = maint_liab_weight_opt {
bank.maint_liab_weight = I80F48::from_num(maint_liab_weight);
}
if let Some(init_liab_weight) = init_liab_weight_opt {
bank.init_liab_weight = I80F48::from_num(init_liab_weight);
}
if let Some(liquidation_fee) = liquidation_fee_opt {
bank.liquidation_fee = I80F48::from_num(liquidation_fee);
}
// unchanged -
// dust
// flash_loan_vault_initial
// flash_loan_approved_amount
// token_index
// bump
// mint_decimals
// bank_num
// reserved
}
Ok(())
}

View File

@ -16,6 +16,7 @@ pub struct TokenWithdraw<'info> {
#[account(
mut,
has_one = group,
// note: should never be the delegate
has_one = owner,
)]
pub account: AccountLoader<'info, MangoAccount>,

View File

@ -3,7 +3,6 @@ use anchor_lang::prelude::*;
use crate::logs::UpdateIndexLog;
use crate::{
accounts_zerocopy::{LoadMutZeroCopyRef, LoadZeroCopyRef},
error::MangoError,
state::{Bank, MintInfo},
};
use checked_math as cm;
@ -14,21 +13,14 @@ pub struct UpdateIndex<'info> {
}
pub fn update_index(ctx: Context<UpdateIndex>) -> Result<()> {
let mint_info = ctx.accounts.mint_info.load()?;
let total_banks = mint_info
.banks
.iter()
.filter(|bank| *bank != &Pubkey::default())
.count();
require_eq!(total_banks, ctx.remaining_accounts.len());
let all_banks = ctx.remaining_accounts;
check_banks(all_banks, &mint_info)?;
ctx.accounts
.mint_info
.load()?
.verify_banks_ais(ctx.remaining_accounts)?;
let mut indexed_total_deposits = I80F48::ZERO;
let mut indexed_total_borrows = I80F48::ZERO;
for ai in all_banks.iter() {
for ai in ctx.remaining_accounts.iter() {
let bank = ai.load::<Bank>()?;
indexed_total_deposits = cm!(indexed_total_deposits + bank.indexed_deposits);
indexed_total_borrows = cm!(indexed_total_borrows + bank.indexed_borrows);
@ -36,7 +28,7 @@ pub fn update_index(ctx: Context<UpdateIndex>) -> Result<()> {
let now_ts = Clock::get()?.unix_timestamp;
let (diff_ts, deposit_index, borrow_index) = {
let mut some_bank = all_banks[0].load_mut::<Bank>()?;
let mut some_bank = ctx.remaining_accounts[0].load_mut::<Bank>()?;
// TODO: should we enforce a minimum window between 2 update_index ix calls?
let diff_ts = I80F48::from_num(now_ts - some_bank.last_updated);
@ -53,7 +45,7 @@ pub fn update_index(ctx: Context<UpdateIndex>) -> Result<()> {
msg!("deposit_index {}", deposit_index);
msg!("borrow_index {}", borrow_index);
for ai in all_banks.iter() {
for ai in ctx.remaining_accounts.iter() {
let mut bank = ai.load_mut::<Bank>()?;
bank.cached_indexed_total_deposits = indexed_total_deposits;
@ -77,22 +69,3 @@ pub fn update_index(ctx: Context<UpdateIndex>) -> Result<()> {
Ok(())
}
fn check_banks(all_banks: &[AccountInfo], mint_info: &MintInfo) -> Result<()> {
for (idx, ai) in all_banks.iter().enumerate() {
match ai.load::<Bank>() {
Ok(bank) => {
if mint_info.token_index != bank.token_index
|| mint_info.group != bank.group
// todo: just below check should be enough, above 2 checks are superfluous and defensive
|| mint_info.banks[idx] != ai.key()
{
return Err(error!(MangoError::SomeError));
}
}
Err(error) => return Err(error),
}
}
Ok(())
}

View File

@ -71,6 +71,37 @@ pub mod mango_v4 {
)
}
#[allow(clippy::too_many_arguments)]
pub fn token_edit(
ctx: Context<TokenEdit>,
bank_num: u64,
oracle_opt: Option<Pubkey>,
oracle_config_opt: Option<OracleConfig>,
interest_rate_params_opt: Option<InterestRateParams>,
loan_fee_rate_opt: Option<f32>,
loan_origination_fee_rate_opt: Option<f32>,
maint_asset_weight_opt: Option<f32>,
init_asset_weight_opt: Option<f32>,
maint_liab_weight_opt: Option<f32>,
init_liab_weight_opt: Option<f32>,
liquidation_fee_opt: Option<f32>,
) -> Result<()> {
instructions::token_edit(
ctx,
bank_num,
oracle_opt,
oracle_config_opt,
interest_rate_params_opt,
loan_fee_rate_opt,
loan_origination_fee_rate_opt,
maint_asset_weight_opt,
init_asset_weight_opt,
maint_liab_weight_opt,
init_liab_weight_opt,
liquidation_fee_opt,
)
}
#[allow(clippy::too_many_arguments)]
pub fn token_add_bank(
ctx: Context<TokenAddBank>,
@ -99,7 +130,13 @@ pub mod mango_v4 {
instructions::create_account(ctx, account_num, name)
}
// TODO set delegate
pub fn edit_account(
ctx: Context<EditAccount>,
name_opt: Option<String>,
delegate_opt: Option<Pubkey>,
) -> Result<()> {
instructions::edit_account(ctx, name_opt, delegate_opt)
}
pub fn close_account(ctx: Context<CloseAccount>) -> Result<()> {
instructions::close_account(ctx)
@ -182,6 +219,11 @@ pub mod mango_v4 {
instructions::serum3_register_market(ctx, market_index, name)
}
// note:
// pub fn serum3_edit_market - doesn't exist since a mango serum3 market only contains the properties
// registered base and quote token pairs, and serum3 external market its pointing to, and none of them
// should be edited once set on creation
pub fn serum3_deregister_market(ctx: Context<Serum3DeregisterMarket>) -> Result<()> {
instructions::serum3_deregister_market(ctx)
}
@ -309,6 +351,43 @@ pub mod mango_v4 {
)
}
#[allow(clippy::too_many_arguments)]
pub fn perp_edit_market(
ctx: Context<PerpEditMarket>,
oracle_opt: Option<Pubkey>,
oracle_config_opt: Option<OracleConfig>,
base_token_index_opt: Option<TokenIndex>,
base_token_decimals_opt: Option<u8>,
maint_asset_weight_opt: Option<f32>,
init_asset_weight_opt: Option<f32>,
maint_liab_weight_opt: Option<f32>,
init_liab_weight_opt: Option<f32>,
liquidation_fee_opt: Option<f32>,
maker_fee_opt: Option<f32>,
taker_fee_opt: Option<f32>,
min_funding_opt: Option<f32>,
max_funding_opt: Option<f32>,
impact_quantity_opt: Option<i64>,
) -> Result<()> {
instructions::perp_edit_market(
ctx,
oracle_opt,
oracle_config_opt,
base_token_index_opt,
base_token_decimals_opt,
maint_asset_weight_opt,
init_asset_weight_opt,
maint_liab_weight_opt,
init_liab_weight_opt,
liquidation_fee_opt,
maker_fee_opt,
taker_fee_opt,
min_funding_opt,
max_funding_opt,
impact_quantity_opt,
)
}
pub fn perp_close_market(ctx: Context<PerpCloseMarket>) -> Result<()> {
instructions::perp_close_market(ctx)
}

View File

@ -6,6 +6,7 @@ use std::mem::size_of;
pub type TokenIndex = u16;
#[account(zero_copy)]
#[derive(Debug)]
pub struct Group {
// Relying on Anchor's discriminator be sufficient for our versioning needs?
// pub meta_data: MetaData,

View File

@ -770,6 +770,10 @@ impl MangoAccount {
.unwrap()
.trim_matches(char::from(0))
}
pub fn is_owner_or_delegate(&self, ix_signer: Pubkey) -> bool {
self.owner == ix_signer || self.delegate == ix_signer
}
}
impl Default for MangoAccount {

View File

@ -2,7 +2,9 @@ use anchor_lang::prelude::*;
use static_assertions::const_assert_eq;
use std::mem::size_of;
use super::TokenIndex;
use crate::{accounts_zerocopy::LoadZeroCopyRef, error::MangoError};
use super::{Bank, TokenIndex};
pub const MAX_BANKS: usize = 6;
@ -44,4 +46,30 @@ impl MintInfo {
pub fn first_vault(&self) -> Pubkey {
self.vaults[0]
}
pub fn verify_banks_ais(&self, all_bank_ais: &[AccountInfo]) -> Result<()> {
let total_banks = self
.banks
.iter()
.filter(|bank| *bank != &Pubkey::default())
.count();
require_eq!(total_banks, all_bank_ais.len());
for (idx, ai) in all_bank_ais.iter().enumerate() {
match ai.load::<Bank>() {
Ok(bank) => {
if self.token_index != bank.token_index
|| self.group != bank.group
// todo: just below check should be enough, above 2 checks are superfluous and defensive
|| self.banks[idx] != ai.key()
{
return Err(error!(MangoError::SomeError));
}
}
Err(error) => return Err(error),
}
}
Ok(())
}
}

View File

@ -14,6 +14,7 @@ use super::{Book, OracleConfig};
pub type PerpMarketIndex = u16;
#[account(zero_copy)]
#[derive(Debug)]
pub struct PerpMarket {
pub name: [u8; 16],

View File

@ -1296,6 +1296,53 @@ impl<'keypair> ClientInstruction for CreateAccountInstruction<'keypair> {
}
}
pub struct EditAccountInstruction<'keypair> {
pub account_num: u8,
pub group: Pubkey,
pub owner: &'keypair Keypair,
pub name: String,
pub delegate: Pubkey,
}
#[async_trait::async_trait(?Send)]
impl<'keypair> ClientInstruction for EditAccountInstruction<'keypair> {
type Accounts = mango_v4::accounts::EditAccount;
type Instruction = mango_v4::instruction::EditAccount;
async fn to_instruction(
&self,
_account_loader: impl ClientAccountLoader + 'async_trait,
) -> (Self::Accounts, instruction::Instruction) {
let program_id = mango_v4::id();
let instruction = mango_v4::instruction::EditAccount {
name_opt: Option::from(self.name.to_string()),
delegate_opt: Option::from(self.delegate),
};
let account = Pubkey::find_program_address(
&[
self.group.as_ref(),
b"MangoAccount".as_ref(),
self.owner.pubkey().as_ref(),
&self.account_num.to_le_bytes(),
],
&program_id,
)
.0;
let accounts = mango_v4::accounts::EditAccount {
group: self.group,
account,
owner: self.owner.pubkey(),
};
let instruction = make_instruction(program_id, &accounts, instruction);
(accounts, instruction)
}
fn signers(&self) -> Vec<&Keypair> {
vec![self.owner]
}
}
pub struct CloseAccountInstruction<'keypair> {
pub group: Pubkey,
pub account: Pubkey,

View File

@ -0,0 +1,149 @@
#![cfg(feature = "test-bpf")]
use fixed::types::I80F48;
use solana_program_test::*;
use solana_sdk::{signature::Keypair, signature::Signer, transport::TransportError};
use mango_v4::state::*;
use program_test::*;
mod program_test;
#[tokio::test]
async fn test_delegate() -> Result<(), TransportError> {
let context = TestContext::new().await;
let solana = &context.solana.clone();
let admin = &Keypair::new();
let owner = &context.users[0].key;
let payer = &context.users[1].key;
let delegate = &context.users[1].key;
let mints = &context.mints[0..1];
let payer_mint0_account = context.users[1].token_accounts[0];
//
// SETUP: Create a group, register a token (mint0), create an account
//
let mango_setup::GroupWithTokens { group, tokens } = mango_setup::GroupWithTokensConfig {
admin,
payer,
mints,
}
.create(solana)
.await;
let bank = tokens[0].bank;
let account = send_tx(
solana,
CreateAccountInstruction {
account_num: 0,
group,
owner,
payer,
},
)
.await
.unwrap()
.account;
// deposit
send_tx(
solana,
TokenDepositInstruction {
amount: 100,
account,
token_account: payer_mint0_account,
token_authority: payer,
},
)
.await
.unwrap();
//
// TEST: Edit account - Set delegate
//
{
send_tx(
solana,
EditAccountInstruction {
delegate: delegate.pubkey(),
account_num: 0,
group,
owner,
name: "new_name".to_owned(),
},
)
.await
.unwrap();
}
//
// TEST: Edit account as delegate - should fail
//
{
let res = send_tx(
solana,
EditAccountInstruction {
delegate: delegate.pubkey(),
account_num: 0,
group,
owner: delegate,
name: "new_name".to_owned(),
},
)
.await;
assert!(res.is_err());
}
//
// TEST: Withdraw funds as delegate should fail
//
{
let withdraw_amount = 50;
let res = send_tx(
solana,
TokenWithdrawInstruction {
amount: withdraw_amount,
allow_borrow: true,
account,
owner: delegate,
token_account: payer_mint0_account,
},
)
.await;
assert!(res.is_err());
}
//
// TEST: Close account as delegate should fail
//
{
let bank_data: Bank = solana.get_account(bank).await;
send_tx(
solana,
TokenWithdrawInstruction {
amount: bank_data.native_deposits().to_num(),
allow_borrow: false,
account,
owner,
token_account: payer_mint0_account,
},
)
.await
.unwrap();
let res = send_tx(
solana,
CloseAccountInstruction {
group,
account,
owner: delegate,
sol_destination: payer.pubkey(),
},
)
.await;
assert!(res.is_err());
}
Ok(())
}

View File

@ -20,7 +20,7 @@ cp -v ./target/types/mango_v4.ts ./ts/client/src/mango_v4.ts
if [[ -z "${NO_DEPLOY}" ]]; then
# publish program
solana --url https://mango.devnet.rpcpool.com program deploy --program-id $PROGRAM_ID \
-k $WALLET_WITH_FUNDS target/deploy/mango_v4.so
-k $WALLET_WITH_FUNDS target/deploy/mango_v4.so --skip-fee-check
# # publish idl
# anchor idl upgrade --provider.cluster https://mango.devnet.rpcpool.com --provider.wallet $WALLET_WITH_FUNDS \

View File

@ -141,9 +141,39 @@ export class Bank {
}
toString(): string {
return `Bank ${
this.tokenIndex
} deposit index - ${this.depositIndex.toNumber()}, borrow index - ${this.borrowIndex.toNumber()}`;
return (
'Bank ' +
'\n token index -' +
this.tokenIndex +
'\n deposit index -' +
this.depositIndex.toNumber() +
'\n borrow index -' +
this.borrowIndex.toNumber() +
'\n cachedIndexedTotalDeposits -' +
this.cachedIndexedTotalDeposits.toNumber() +
'\n cachedIndexedTotalBorrows -' +
this.cachedIndexedTotalBorrows.toNumber() +
'\n maxRate -' +
this.maxRate.toNumber() +
'\n util0 -' +
this.util0.toNumber() +
'\n rate0 -' +
this.rate0.toNumber() +
'\n util1 -' +
this.util1.toNumber() +
'\n rate1 -' +
this.rate1.toNumber() +
'\n maintAssetWeight -' +
this.maintAssetWeight.toNumber() +
'\n initAssetWeight -' +
this.initAssetWeight.toNumber() +
'\n maintLiabWeight -' +
this.maintLiabWeight.toNumber() +
'\n initLiabWeight -' +
this.initLiabWeight.toNumber() +
'\n liquidationFee -' +
this.liquidationFee.toNumber()
);
}
nativeDeposits(): I80F48 {

View File

@ -234,6 +234,7 @@ export class MangoAccount {
let res = 'MangoAccount';
res = res + '\n pk: ' + this.publicKey.toString();
res = res + '\n name: ' + this.name;
res = res + '\n delegate: ' + this.delegate;
res =
this.tokensActive().length > 0

View File

@ -126,6 +126,28 @@ export class PerpMarket {
const quoteUnit = Math.pow(10, QUOTE_DECIMALS);
return new BN(uiQuote * quoteUnit).div(this.quoteLotSize);
}
toString(): string {
return (
'PerpMarket ' +
'\n perpMarketIndex -' +
this.perpMarketIndex +
'\n maintAssetWeight -' +
this.maintAssetWeight.toNumber() +
'\n initAssetWeight -' +
this.initAssetWeight.toNumber() +
'\n maintLiabWeight -' +
this.maintLiabWeight.toNumber() +
'\n initLiabWeight -' +
this.initLiabWeight.toNumber() +
'\n liquidationFee -' +
this.liquidationFee.toNumber() +
'\n makerFee -' +
this.makerFee.toNumber() +
'\n takerFee -' +
this.takerFee.toNumber()
);
}
}
export class Side {

View File

@ -151,7 +151,6 @@ export class MangoClient {
initLiabWeight: number,
liquidationFee: number,
): Promise<TransactionSignature> {
const bn = I80F48.fromNumber(oracleConfFilter).getData();
return await this.program.methods
.tokenRegister(
tokenIndex,
@ -182,6 +181,60 @@ export class MangoClient {
.rpc();
}
public async tokenEdit(
group: Group,
tokenName: string,
oracle: PublicKey,
oracleConfFilter: number,
util0: number,
rate0: number,
util1: number,
rate1: number,
maxRate: number,
loanFeeRate: number,
loanOriginationFeeRate: number,
maintAssetWeight: number,
initAssetWeight: number,
maintLiabWeight: number,
initLiabWeight: number,
liquidationFee: number,
): Promise<TransactionSignature> {
const bank = group.banksMap.get(tokenName)!;
const mintInfo = group.mintInfosMap.get(bank.tokenIndex)!;
return await this.program.methods
.tokenEdit(
new BN(0),
oracle,
{
confFilter: {
val: I80F48.fromNumber(oracleConfFilter).getData(),
},
} as any, // future: nested custom types dont typecheck, fix if possible?
{ util0, rate0, util1, rate1, maxRate },
loanFeeRate,
loanOriginationFeeRate,
maintAssetWeight,
initAssetWeight,
maintLiabWeight,
initLiabWeight,
liquidationFee,
)
.accounts({
group: group.publicKey,
admin: (this.program.provider as AnchorProvider).wallet.publicKey,
mintInfo: mintInfo.publicKey,
})
.remainingAccounts([
{
pubkey: bank.publicKey,
isWritable: true,
isSigner: false,
} as AccountMeta,
])
.rpc({ skipPreflight: true });
}
public async tokenDeregister(
group: Group,
tokenName: string,
@ -388,6 +441,20 @@ export class MangoClient {
.rpc();
}
public async editMangoAccount(
group: Group,
name?: string,
delegate?: PublicKey,
): Promise<TransactionSignature> {
return await this.program.methods
.editAccount(name, delegate)
.accounts({
group: group.publicKey,
owner: (this.program.provider as AnchorProvider).wallet.publicKey,
})
.rpc();
}
public async getMangoAccount(mangoAccount: MangoAccount) {
return MangoAccount.from(
mangoAccount.publicKey,
@ -1048,6 +1115,55 @@ export class MangoClient {
.rpc();
}
async perpEditMarket(
group: Group,
perpMarketName: string,
oracle: PublicKey,
oracleConfFilter: number,
baseTokenIndex: number,
baseTokenDecimals: number,
maintAssetWeight: number,
initAssetWeight: number,
maintLiabWeight: number,
initLiabWeight: number,
liquidationFee: number,
makerFee: number,
takerFee: number,
minFunding: number,
maxFunding: number,
impactQuantity: number,
): Promise<TransactionSignature> {
const perpMarket = group.perpMarketsMap.get(perpMarketName)!;
return await this.program.methods
.perpEditMarket(
oracle,
{
confFilter: {
val: I80F48.fromNumber(oracleConfFilter).getData(),
},
} as any, // future: nested custom types dont typecheck, fix if possible?
baseTokenIndex,
baseTokenDecimals,
maintAssetWeight,
initAssetWeight,
maintLiabWeight,
initLiabWeight,
liquidationFee,
makerFee,
takerFee,
minFunding,
maxFunding,
new BN(impactQuantity),
)
.accounts({
group: group.publicKey,
admin: (this.program.provider as AnchorProvider).wallet.publicKey,
perpMarket: perpMarket.publicKey,
})
.rpc();
}
async perpCloseMarket(
group: Group,
perpMarketName: string,

View File

@ -264,6 +264,96 @@ export type MangoV4 = {
}
]
},
{
"name": "tokenEdit",
"accounts": [
{
"name": "group",
"isMut": false,
"isSigner": false
},
{
"name": "admin",
"isMut": false,
"isSigner": true
},
{
"name": "mintInfo",
"isMut": false,
"isSigner": false
}
],
"args": [
{
"name": "bankNum",
"type": "u64"
},
{
"name": "oracleOpt",
"type": {
"option": "publicKey"
}
},
{
"name": "oracleConfigOpt",
"type": {
"option": {
"defined": "OracleConfig"
}
}
},
{
"name": "interestRateParamsOpt",
"type": {
"option": {
"defined": "InterestRateParams"
}
}
},
{
"name": "loanFeeRateOpt",
"type": {
"option": "f32"
}
},
{
"name": "loanOriginationFeeRateOpt",
"type": {
"option": "f32"
}
},
{
"name": "maintAssetWeightOpt",
"type": {
"option": "f32"
}
},
{
"name": "initAssetWeightOpt",
"type": {
"option": "f32"
}
},
{
"name": "maintLiabWeightOpt",
"type": {
"option": "f32"
}
},
{
"name": "initLiabWeightOpt",
"type": {
"option": "f32"
}
},
{
"name": "liquidationFeeOpt",
"type": {
"option": "f32"
}
}
]
},
{
"name": "tokenAddBank",
"accounts": [
@ -518,6 +608,40 @@ export type MangoV4 = {
}
]
},
{
"name": "editAccount",
"accounts": [
{
"name": "group",
"isMut": false,
"isSigner": false
},
{
"name": "account",
"isMut": true,
"isSigner": false
},
{
"name": "owner",
"isMut": false,
"isSigner": true
}
],
"args": [
{
"name": "nameOpt",
"type": {
"option": "string"
}
},
{
"name": "delegateOpt",
"type": {
"option": "publicKey"
}
}
]
},
{
"name": "closeAccount",
"accounts": [
@ -1046,6 +1170,11 @@ export type MangoV4 = {
"isMut": true,
"isSigner": false
},
{
"name": "owner",
"isMut": false,
"isSigner": true
},
{
"name": "serumMarket",
"isMut": false,
@ -1085,11 +1214,6 @@ export type MangoV4 = {
]
}
},
{
"name": "owner",
"isMut": false,
"isSigner": true
},
{
"name": "payer",
"isMut": true,
@ -1787,6 +1911,114 @@ export type MangoV4 = {
}
]
},
{
"name": "perpEditMarket",
"accounts": [
{
"name": "group",
"isMut": false,
"isSigner": false
},
{
"name": "admin",
"isMut": false,
"isSigner": true
},
{
"name": "perpMarket",
"isMut": true,
"isSigner": false
}
],
"args": [
{
"name": "oracleOpt",
"type": {
"option": "publicKey"
}
},
{
"name": "oracleConfigOpt",
"type": {
"option": {
"defined": "OracleConfig"
}
}
},
{
"name": "baseTokenIndexOpt",
"type": {
"option": "u16"
}
},
{
"name": "baseTokenDecimalsOpt",
"type": {
"option": "u8"
}
},
{
"name": "maintAssetWeightOpt",
"type": {
"option": "f32"
}
},
{
"name": "initAssetWeightOpt",
"type": {
"option": "f32"
}
},
{
"name": "maintLiabWeightOpt",
"type": {
"option": "f32"
}
},
{
"name": "initLiabWeightOpt",
"type": {
"option": "f32"
}
},
{
"name": "liquidationFeeOpt",
"type": {
"option": "f32"
}
},
{
"name": "makerFeeOpt",
"type": {
"option": "f32"
}
},
{
"name": "takerFeeOpt",
"type": {
"option": "f32"
}
},
{
"name": "minFundingOpt",
"type": {
"option": "f32"
}
},
{
"name": "maxFundingOpt",
"type": {
"option": "f32"
}
},
{
"name": "impactQuantityOpt",
"type": {
"option": "i64"
}
}
]
},
{
"name": "perpCloseMarket",
"accounts": [
@ -1846,6 +2078,11 @@ export type MangoV4 = {
"isMut": true,
"isSigner": false
},
{
"name": "owner",
"isMut": false,
"isSigner": true
},
{
"name": "perpMarket",
"isMut": true,
@ -1870,11 +2107,6 @@ export type MangoV4 = {
"name": "oracle",
"isMut": false,
"isSigner": false
},
{
"name": "owner",
"isMut": false,
"isSigner": true
}
],
"args": [
@ -1929,6 +2161,11 @@ export type MangoV4 = {
"isMut": true,
"isSigner": false
},
{
"name": "owner",
"isMut": false,
"isSigner": true
},
{
"name": "perpMarket",
"isMut": true,
@ -1943,11 +2180,6 @@ export type MangoV4 = {
"name": "bids",
"isMut": true,
"isSigner": false
},
{
"name": "owner",
"isMut": false,
"isSigner": true
}
],
"args": [
@ -1970,6 +2202,11 @@ export type MangoV4 = {
"isMut": true,
"isSigner": false
},
{
"name": "owner",
"isMut": false,
"isSigner": true
},
{
"name": "perpMarket",
"isMut": true,
@ -1984,11 +2221,6 @@ export type MangoV4 = {
"name": "bids",
"isMut": true,
"isSigner": false
},
{
"name": "owner",
"isMut": false,
"isSigner": true
}
],
"args": [
@ -2011,6 +2243,11 @@ export type MangoV4 = {
"isMut": true,
"isSigner": false
},
{
"name": "owner",
"isMut": false,
"isSigner": true
},
{
"name": "perpMarket",
"isMut": true,
@ -2025,11 +2262,6 @@ export type MangoV4 = {
"name": "bids",
"isMut": true,
"isSigner": false
},
{
"name": "owner",
"isMut": false,
"isSigner": true
}
],
"args": [
@ -2052,6 +2284,11 @@ export type MangoV4 = {
"isMut": true,
"isSigner": false
},
{
"name": "owner",
"isMut": false,
"isSigner": true
},
{
"name": "perpMarket",
"isMut": true,
@ -2066,11 +2303,6 @@ export type MangoV4 = {
"name": "bids",
"isMut": true,
"isSigner": false
},
{
"name": "owner",
"isMut": false,
"isSigner": true
}
],
"args": [
@ -4414,6 +4646,96 @@ export const IDL: MangoV4 = {
}
]
},
{
"name": "tokenEdit",
"accounts": [
{
"name": "group",
"isMut": false,
"isSigner": false
},
{
"name": "admin",
"isMut": false,
"isSigner": true
},
{
"name": "mintInfo",
"isMut": false,
"isSigner": false
}
],
"args": [
{
"name": "bankNum",
"type": "u64"
},
{
"name": "oracleOpt",
"type": {
"option": "publicKey"
}
},
{
"name": "oracleConfigOpt",
"type": {
"option": {
"defined": "OracleConfig"
}
}
},
{
"name": "interestRateParamsOpt",
"type": {
"option": {
"defined": "InterestRateParams"
}
}
},
{
"name": "loanFeeRateOpt",
"type": {
"option": "f32"
}
},
{
"name": "loanOriginationFeeRateOpt",
"type": {
"option": "f32"
}
},
{
"name": "maintAssetWeightOpt",
"type": {
"option": "f32"
}
},
{
"name": "initAssetWeightOpt",
"type": {
"option": "f32"
}
},
{
"name": "maintLiabWeightOpt",
"type": {
"option": "f32"
}
},
{
"name": "initLiabWeightOpt",
"type": {
"option": "f32"
}
},
{
"name": "liquidationFeeOpt",
"type": {
"option": "f32"
}
}
]
},
{
"name": "tokenAddBank",
"accounts": [
@ -4668,6 +4990,40 @@ export const IDL: MangoV4 = {
}
]
},
{
"name": "editAccount",
"accounts": [
{
"name": "group",
"isMut": false,
"isSigner": false
},
{
"name": "account",
"isMut": true,
"isSigner": false
},
{
"name": "owner",
"isMut": false,
"isSigner": true
}
],
"args": [
{
"name": "nameOpt",
"type": {
"option": "string"
}
},
{
"name": "delegateOpt",
"type": {
"option": "publicKey"
}
}
]
},
{
"name": "closeAccount",
"accounts": [
@ -5196,6 +5552,11 @@ export const IDL: MangoV4 = {
"isMut": true,
"isSigner": false
},
{
"name": "owner",
"isMut": false,
"isSigner": true
},
{
"name": "serumMarket",
"isMut": false,
@ -5235,11 +5596,6 @@ export const IDL: MangoV4 = {
]
}
},
{
"name": "owner",
"isMut": false,
"isSigner": true
},
{
"name": "payer",
"isMut": true,
@ -5937,6 +6293,114 @@ export const IDL: MangoV4 = {
}
]
},
{
"name": "perpEditMarket",
"accounts": [
{
"name": "group",
"isMut": false,
"isSigner": false
},
{
"name": "admin",
"isMut": false,
"isSigner": true
},
{
"name": "perpMarket",
"isMut": true,
"isSigner": false
}
],
"args": [
{
"name": "oracleOpt",
"type": {
"option": "publicKey"
}
},
{
"name": "oracleConfigOpt",
"type": {
"option": {
"defined": "OracleConfig"
}
}
},
{
"name": "baseTokenIndexOpt",
"type": {
"option": "u16"
}
},
{
"name": "baseTokenDecimalsOpt",
"type": {
"option": "u8"
}
},
{
"name": "maintAssetWeightOpt",
"type": {
"option": "f32"
}
},
{
"name": "initAssetWeightOpt",
"type": {
"option": "f32"
}
},
{
"name": "maintLiabWeightOpt",
"type": {
"option": "f32"
}
},
{
"name": "initLiabWeightOpt",
"type": {
"option": "f32"
}
},
{
"name": "liquidationFeeOpt",
"type": {
"option": "f32"
}
},
{
"name": "makerFeeOpt",
"type": {
"option": "f32"
}
},
{
"name": "takerFeeOpt",
"type": {
"option": "f32"
}
},
{
"name": "minFundingOpt",
"type": {
"option": "f32"
}
},
{
"name": "maxFundingOpt",
"type": {
"option": "f32"
}
},
{
"name": "impactQuantityOpt",
"type": {
"option": "i64"
}
}
]
},
{
"name": "perpCloseMarket",
"accounts": [
@ -5996,6 +6460,11 @@ export const IDL: MangoV4 = {
"isMut": true,
"isSigner": false
},
{
"name": "owner",
"isMut": false,
"isSigner": true
},
{
"name": "perpMarket",
"isMut": true,
@ -6020,11 +6489,6 @@ export const IDL: MangoV4 = {
"name": "oracle",
"isMut": false,
"isSigner": false
},
{
"name": "owner",
"isMut": false,
"isSigner": true
}
],
"args": [
@ -6079,6 +6543,11 @@ export const IDL: MangoV4 = {
"isMut": true,
"isSigner": false
},
{
"name": "owner",
"isMut": false,
"isSigner": true
},
{
"name": "perpMarket",
"isMut": true,
@ -6093,11 +6562,6 @@ export const IDL: MangoV4 = {
"name": "bids",
"isMut": true,
"isSigner": false
},
{
"name": "owner",
"isMut": false,
"isSigner": true
}
],
"args": [
@ -6120,6 +6584,11 @@ export const IDL: MangoV4 = {
"isMut": true,
"isSigner": false
},
{
"name": "owner",
"isMut": false,
"isSigner": true
},
{
"name": "perpMarket",
"isMut": true,
@ -6134,11 +6603,6 @@ export const IDL: MangoV4 = {
"name": "bids",
"isMut": true,
"isSigner": false
},
{
"name": "owner",
"isMut": false,
"isSigner": true
}
],
"args": [
@ -6161,6 +6625,11 @@ export const IDL: MangoV4 = {
"isMut": true,
"isSigner": false
},
{
"name": "owner",
"isMut": false,
"isSigner": true
},
{
"name": "perpMarket",
"isMut": true,
@ -6175,11 +6644,6 @@ export const IDL: MangoV4 = {
"name": "bids",
"isMut": true,
"isSigner": false
},
{
"name": "owner",
"isMut": false,
"isSigner": true
}
],
"args": [
@ -6202,6 +6666,11 @@ export const IDL: MangoV4 = {
"isMut": true,
"isSigner": false
},
{
"name": "owner",
"isMut": false,
"isSigner": true
},
{
"name": "perpMarket",
"isMut": true,
@ -6216,11 +6685,6 @@ export const IDL: MangoV4 = {
"name": "bids",
"isMut": true,
"isSigner": false
},
{
"name": "owner",
"isMut": false,
"isSigner": true
}
],
"args": [

View File

@ -255,6 +255,116 @@ async function main() {
);
console.log(`...created perp market ${perpMarkets[0].publicKey}`);
//
// edit
//
console.log(`Editing USDC...`);
try {
let sig = await client.tokenEdit(
group,
'USDC',
btcDevnetOracle,
0.1,
0.3,
0.08,
0.81,
0.91,
0.75,
0.0007,
1.7,
0.9,
0.7,
1.3,
1.5,
0.04,
);
console.log(`https://explorer.solana.com/tx/${sig}?cluster=devnet`);
await group.reloadAll(client);
console.log(group.banksMap.get('USDC').toString());
} catch (error) {
throw error;
}
console.log(`Resetting USDC...`);
try {
let sig = await client.tokenEdit(
group,
'USDC',
usdcDevnetOracle.publicKey,
0.1,
0.4,
0.07,
0.8,
0.9,
1.5,
0.0005,
1.5,
0.8,
0.6,
1.2,
1.4,
0.02,
);
console.log(`https://explorer.solana.com/tx/${sig}?cluster=devnet`);
await group.reloadAll(client);
console.log(group.banksMap.get('USDC').toString());
} catch (error) {
throw error;
}
console.log(`Editing perp market...`);
try {
let sig = await client.perpEditMarket(
group,
'BTC-PERP',
btcDevnetOracle,
0.2,
1,
6,
0.9,
0.9,
1.035,
1.06,
0.013,
0.0003,
0.1,
0.07,
0.07,
1001,
);
console.log(`https://explorer.solana.com/tx/${sig}?cluster=devnet`);
await group.reloadAll(client);
console.log(group.perpMarketsMap.get('BTC-PERP').toString());
} catch (error) {
console.log(error);
}
console.log(`Resetting perp market...`);
try {
let sig = await client.perpEditMarket(
group,
'BTC-PERP',
btcDevnetOracle,
0.1,
0,
6,
1,
0.95,
1.025,
1.05,
0.012,
0.0002,
0.0,
0.05,
0.05,
100,
);
console.log(`https://explorer.solana.com/tx/${sig}?cluster=devnet`);
await group.reloadAll(client);
console.log(group.perpMarketsMap.get('BTC-PERP').toString());
} catch (error) {
console.log(error);
}
process.exit();
}

View File

@ -0,0 +1,171 @@
import { AnchorProvider, Wallet } from '@project-serum/anchor';
import { Connection, Keypair } from '@solana/web3.js';
import fs from 'fs';
import { OrderType, Side } from '../accounts/perp';
import {
Serum3OrderType,
Serum3SelfTradeBehavior,
Serum3Side,
} from '../accounts/serum3';
import { MangoClient } from '../client';
import { MANGO_V4_ID } from '../constants';
//
// An example for users based on high level api i.e. the client
// Create
// process.env.USER_KEYPAIR - mango account owner keypair path
// process.env.ADMIN_KEYPAIR - group admin keypair path (useful for automatically finding the group)
//
// This script deposits some tokens, places some serum orders, cancels them, places some perp orders
//
async function main() {
const options = AnchorProvider.defaultOptions();
const connection = new Connection(
'https://mango.devnet.rpcpool.com',
options,
);
// mango account owner
const user = Keypair.fromSecretKey(
Buffer.from(
JSON.parse(fs.readFileSync(process.env.USER_KEYPAIR!, 'utf-8')),
),
);
const userWallet = new Wallet(user);
const userProvider = new AnchorProvider(connection, userWallet, options);
const client = await MangoClient.connect(
userProvider,
'devnet',
MANGO_V4_ID['devnet'],
);
console.log(`User ${userWallet.publicKey.toBase58()}`);
// delegate
const delegate = Keypair.fromSecretKey(
Buffer.from(JSON.parse(fs.readFileSync(process.env.DELEGATE!, 'utf-8'))),
);
const delegateWallet = new Wallet(delegate);
const delegateProvider = new AnchorProvider(
connection,
delegateWallet,
options,
);
// Note: simply create a client with delegate and use this client to execute ixs
const delegateClient = await MangoClient.connect(
delegateProvider,
'devnet',
MANGO_V4_ID['devnet'],
);
console.log(`Delegate ${delegateWallet.publicKey.toBase58()}`);
// fetch group
const admin = Keypair.fromSecretKey(
Buffer.from(
JSON.parse(fs.readFileSync(process.env.ADMIN_KEYPAIR!, 'utf-8')),
),
);
const group = await delegateClient.getGroupForAdmin(admin.publicKey, 0);
console.log(group.toString());
// fetch mango account using owners pubkey
console.log(`Fetching mangoaccount...`);
const mangoAccount = (
await delegateClient.getMangoAccountForOwner(group, user.publicKey)
)[0];
console.log(`...created/found mangoAccount ${mangoAccount.publicKey}`);
console.log(mangoAccount.toString());
if (true) {
// set delegate, and change name
console.log(`...changing mango account name, and setting a delegate`);
await client.editMangoAccount(
group,
'my_changed_name',
delegate.publicKey,
);
await mangoAccount.reload(client, group);
console.log(mangoAccount.toString());
}
if (true) {
// deposit
console.log(`...depositing 50 USDC`);
await client.tokenDeposit(group, mangoAccount, 'USDC', 50);
await mangoAccount.reload(client, group);
console.log(`...depositing 0.0005 BTC`);
await client.tokenDeposit(group, mangoAccount, 'BTC', 0.0005);
await mangoAccount.reload(client, group);
// serum3
console.log(`...placing serum3 bid`);
await delegateClient.serum3PlaceOrder(
group,
mangoAccount,
'BTC/USDC',
Serum3Side.bid,
20,
0.0001,
Serum3SelfTradeBehavior.decrementTake,
Serum3OrderType.limit,
Date.now(),
10,
);
await mangoAccount.reload(delegateClient, group);
console.log(`...current own orders on OB`);
let orders = await delegateClient.getSerum3Orders(
group,
'BTC/USDC',
);
for (const order of orders) {
console.log(
` - order orderId ${order.orderId}, ${order.side}, ${order.price}, ${order.size}`,
);
console.log(` - cancelling order with ${order.orderId}`);
await delegateClient.serum3CancelOrder(
group,
mangoAccount,
'BTC/USDC',
order.side === 'buy' ? Serum3Side.bid : Serum3Side.ask,
order.orderId,
);
}
console.log(`...settling funds`);
await delegateClient.serum3SettleFunds(
group,
mangoAccount,
'BTC/USDC',
);
}
if (true) {
// perps
console.log(`...placing perp bid`);
try {
await delegateClient.perpPlaceOrder(
group,
mangoAccount,
'BTC-PERP',
Side.bid,
30000,
0.000001,
30000 * 0.000001,
Math.floor(Math.random() * 99999),
OrderType.limit,
0,
1,
);
} catch (error) {
console.log(error);
}
}
process.exit();
}
main();

View File

@ -1,5 +1,5 @@
import { AnchorProvider, Wallet } from '@project-serum/anchor';
import { Connection, Keypair } from '@solana/web3.js';
import { Connection, Keypair, PublicKey } from '@solana/web3.js';
import fs from 'fs';
import { OrderType, Side } from '../accounts/perp';
import {
@ -61,32 +61,47 @@ async function main() {
console.log(`...created/found mangoAccount ${mangoAccount.publicKey}`);
console.log(mangoAccount.toString());
await mangoAccount.reloadAccountData(client, group);
if (true) {
// deposit and withdraw
console.log(`Depositing...50 USDC`);
await client.tokenDeposit(group, mangoAccount, 'USDC', 50);
await mangoAccount.reload(client, group);
console.log(`Depositing...0.0005 BTC`);
await client.tokenDeposit(group, mangoAccount, 'BTC', 0.0005);
await mangoAccount.reload(client, group);
console.log(`Withdrawing...0.1 ORCA`);
await client.tokenWithdraw2(
group,
mangoAccount,
'ORCA',
0.1 * Math.pow(10, group.banksMap.get('ORCA').mintDecimals),
true,
// set delegate, and change name
console.log(`...changing mango account name, and setting a delegate`);
const randomKey = new PublicKey(
'4ZkS7ZZkxfsC3GtvvsHP3DFcUeByU9zzZELS4r8HCELo',
);
await client.editMangoAccount(group, 'my_changed_name', randomKey);
await mangoAccount.reload(client, group);
console.log(mangoAccount.toString());
console.log(`...resetting mango account name, and re-setting a delegate`);
await client.editMangoAccount(group, 'my_mango_account', PublicKey.default);
await mangoAccount.reload(client, group);
console.log(mangoAccount.toString());
}
if (true) {
// deposit and withdraw
console.log(`...depositing 50 USDC`);
await client.tokenDeposit(group, mangoAccount, 'USDC', 50);
await mangoAccount.reload(client, group);
console.log(`...depositing 0.0005 BTC`);
await client.tokenDeposit(group, mangoAccount, 'BTC', 0.0005);
await mangoAccount.reload(client, group);
// witdrawing fails if no (other) user has deposited ORCA in the group
// console.log(`Withdrawing...0.1 ORCA`);
// await client.tokenWithdraw2(
// group,
// mangoAccount,
// 'ORCA',
// 0.1 * Math.pow(10, group.banksMap.get('ORCA').mintDecimals),
// true,
// );
// await mangoAccount.reload(client, group);
// console.log(mangoAccount.toString());
// serum3
console.log(
`Placing serum3 bid which would not be settled since its relatively low then midprice...`,
`...placing serum3 bid which would not be settled since its relatively low then midprice`,
);
await client.serum3PlaceOrder(
group,
@ -103,7 +118,7 @@ async function main() {
);
await mangoAccount.reload(client, group);
console.log(`Placing serum3 bid way above midprice...`);
console.log(`...placing serum3 bid way above midprice`);
await client.serum3PlaceOrder(
group,
mangoAccount,
@ -119,7 +134,7 @@ async function main() {
);
await mangoAccount.reload(client, group);
console.log(`Placing serum3 ask way below midprice...`);
console.log(`...placing serum3 ask way below midprice`);
await client.serum3PlaceOrder(
group,
mangoAccount,
@ -134,7 +149,7 @@ async function main() {
10,
);
console.log(`Current own orders on OB...`);
console.log(`...current own orders on OB`);
let orders = await client.getSerum3Orders(
group,
@ -142,9 +157,9 @@ async function main() {
);
for (const order of orders) {
console.log(
` - Order orderId ${order.orderId}, ${order.side}, ${order.price}, ${order.size}`,
` - order orderId ${order.orderId}, ${order.side}, ${order.price}, ${order.size}`,
);
console.log(` - Cancelling order with ${order.orderId}`);
console.log(` - cancelling order with ${order.orderId}`);
await client.serum3CancelOrder(
group,
mangoAccount,
@ -155,7 +170,7 @@ async function main() {
);
}
console.log(`Current own orders on OB...`);
console.log(`...current own orders on OB`);
orders = await client.getSerum3Orders(
group,
@ -165,7 +180,7 @@ async function main() {
console.log(order);
}
console.log(`Settling funds...`);
console.log(`...settling funds`);
await client.serum3SettleFunds(
group,
mangoAccount,
@ -177,23 +192,23 @@ async function main() {
if (true) {
await mangoAccount.reload(client, group);
console.log(
'mangoAccount.getEquity() ' +
'...mangoAccount.getEquity() ' +
toUiDecimals(mangoAccount.getEquity().toNumber()),
);
console.log(
'mangoAccount.getCollateralValue() ' +
'...mangoAccount.getCollateralValue() ' +
toUiDecimals(mangoAccount.getCollateralValue().toNumber()),
);
console.log(
'mangoAccount.getAssetsVal() ' +
'...mangoAccount.getAssetsVal() ' +
toUiDecimals(mangoAccount.getAssetsVal().toNumber()),
);
console.log(
'mangoAccount.getLiabsVal() ' +
'...mangoAccount.getLiabsVal() ' +
toUiDecimals(mangoAccount.getLiabsVal().toNumber()),
);
console.log(
"mangoAccount.getMaxWithdrawWithBorrowForToken(group, 'SOL') " +
'...mangoAccount.getMaxWithdrawWithBorrowForToken(group, "SOL") ' +
toUiDecimals(
(
await mangoAccount.getMaxWithdrawWithBorrowForToken(group, 'SOL')
@ -201,7 +216,7 @@ async function main() {
),
);
console.log(
"mangoAccount.getSerum3MarketMarginAvailable(group, 'BTC/USDC') " +
"...mangoAccount.getSerum3MarketMarginAvailable(group, 'BTC/USDC') " +
toUiDecimals(
mangoAccount
.getSerum3MarketMarginAvailable(group, 'BTC/USDC')
@ -209,7 +224,7 @@ async function main() {
),
);
console.log(
"mangoAccount.getPerpMarketMarginAvailable(group, 'BTC-PERP') " +
"...mangoAccount.getPerpMarketMarginAvailable(group, 'BTC-PERP') " +
toUiDecimals(
mangoAccount
.getPerpMarketMarginAvailable(group, 'BTC-PERP')
@ -220,7 +235,7 @@ async function main() {
if (true) {
// perps
console.log(`Placing perp bid...`);
console.log(`...placing perp bid`);
try {
await client.perpPlaceOrder(
group,
@ -239,7 +254,7 @@ async function main() {
console.log(error);
}
console.log(`Placing perp ask...`);
console.log(`...placing perp ask`);
await client.perpPlaceOrder(
group,
mangoAccount,
@ -257,7 +272,7 @@ async function main() {
while (true) {
// TODO: quotePositionNative might be buggy on program side, investigate...
console.log(
`Waiting for self trade to consume (note: make sure keeper crank is running)...`,
`...waiting for self trade to consume (note: make sure keeper crank is running)`,
);
await mangoAccount.reload(client, group);
console.log(mangoAccount.toString());