margin trade test
Signed-off-by: microwavedcola1 <microwavedcola@gmail.com>
This commit is contained in:
parent
425e22a086
commit
dcacadbcbf
|
@ -8,4 +8,5 @@ node_modules
|
|||
|
||||
.idea
|
||||
|
||||
programs/margin-trade/src/lib-expanded.rs
|
||||
programs/mango-v4/src/lib-expanded.rs
|
||||
|
|
|
@ -1494,6 +1494,7 @@ dependencies = [
|
|||
"fixed",
|
||||
"fixed-macro",
|
||||
"log",
|
||||
"margin-trade",
|
||||
"pyth-client",
|
||||
"serde",
|
||||
"solana-logger",
|
||||
|
@ -1505,6 +1506,15 @@ dependencies = [
|
|||
"static_assertions",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "margin-trade"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anchor-lang",
|
||||
"anchor-spl",
|
||||
"solana-program",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "matches"
|
||||
version = "0.1.9"
|
||||
|
|
|
@ -27,9 +27,9 @@ bytemuck = "^1.7.2"
|
|||
fixed = { version = "=1.11.0", features = ["serde", "borsh"] }
|
||||
fixed-macro = "^1.1.1"
|
||||
pyth-client = {version = "0.5.0", features = ["no-entrypoint"]}
|
||||
static_assertions = "1.1"
|
||||
solana-program = "1.9.5"
|
||||
serde = "^1.0"
|
||||
solana-program = "1.9.5"
|
||||
static_assertions = "1.1"
|
||||
|
||||
|
||||
[dev-dependencies]
|
||||
|
@ -43,3 +43,4 @@ log = "0.4.14"
|
|||
env_logger = "0.9.0"
|
||||
base64 = "0.13.0"
|
||||
async-trait = "0.1.52"
|
||||
margin-trade = { path = "../margin-trade", features = ["cpi"] }
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
use crate::error::MangoError;
|
||||
use crate::state::{compute_health, MangoAccount, MangoGroup};
|
||||
use crate::util::to_account_meta;
|
||||
use crate::{group_seeds, Mango};
|
||||
use crate::state::{compute_health, MangoAccount, MangoGroup, TokenBank};
|
||||
use crate::{group_seeds, util, Mango};
|
||||
use anchor_lang::prelude::*;
|
||||
use anchor_spl::token::TokenAccount;
|
||||
use solana_program::instruction::Instruction;
|
||||
use std::cell::{Ref, RefMut};
|
||||
|
||||
#[derive(Accounts)]
|
||||
pub struct MarginTrade<'info> {
|
||||
|
@ -20,18 +21,44 @@ pub struct MarginTrade<'info> {
|
|||
}
|
||||
|
||||
/// reference https://github.com/blockworks-foundation/mango-v3/blob/mc/flash_loan/program/src/processor.rs#L5323
|
||||
pub fn margin_trade(ctx: Context<MarginTrade>, cpi_data: Vec<u8>) -> Result<()> {
|
||||
pub fn margin_trade<'key, 'accounts, 'remaining, 'info>(
|
||||
ctx: Context<'key, 'accounts, 'remaining, 'info, MarginTrade<'info>>,
|
||||
cpi_data: Vec<u8>,
|
||||
) -> Result<()> {
|
||||
let group = ctx.accounts.group.load()?;
|
||||
let mut account = ctx.accounts.account.load_mut()?;
|
||||
let active_len = account.indexed_positions.iter_active().count();
|
||||
|
||||
// remaining_accounts layout is expected as follows
|
||||
// * active_len number of banks
|
||||
// * active_len number of oracles
|
||||
// * cpi_program
|
||||
// * cpi_accounts
|
||||
|
||||
let banks = &ctx.remaining_accounts[0..active_len];
|
||||
let oracles = &ctx.remaining_accounts[active_len..active_len * 2];
|
||||
let cpi_ais = &ctx.remaining_accounts[active_len * 2..];
|
||||
|
||||
let cpi_program_id = *ctx.remaining_accounts[active_len * 2].key;
|
||||
|
||||
// prepare for cpi
|
||||
let (cpi_ais, cpi_ams) = {
|
||||
// we also need the group
|
||||
let mut cpi_ais = [ctx.accounts.group.to_account_info()].to_vec();
|
||||
// skip banks, oracles and cpi program from the remaining_accounts
|
||||
let mut remaining_cpi_ais = ctx.remaining_accounts[active_len * 2 + 1..].to_vec();
|
||||
cpi_ais.append(&mut remaining_cpi_ais);
|
||||
|
||||
let mut cpi_ams = cpi_ais.to_account_metas(Option::None);
|
||||
// we want group to be the signer, so that loans can be taken from the token vaults
|
||||
cpi_ams[0].is_signer = true;
|
||||
|
||||
(cpi_ais, cpi_ams)
|
||||
};
|
||||
|
||||
// since we are using group signer seeds to invoke cpi,
|
||||
// assert that none of the cpi accounts is the mango program to prevent that invoker doesn't
|
||||
// abuse this ix to do unwanted changes
|
||||
for cpi_ai in cpi_ais {
|
||||
for cpi_ai in &cpi_ais {
|
||||
require!(
|
||||
cpi_ai.key() != Mango::id(),
|
||||
MangoError::InvalidMarginTradeTargetCpiProgram
|
||||
|
@ -39,25 +66,85 @@ pub fn margin_trade(ctx: Context<MarginTrade>, cpi_data: Vec<u8>) -> Result<()>
|
|||
}
|
||||
|
||||
// compute pre cpi health
|
||||
let pre_cpi_health = compute_health(&mut account, &banks, &oracles).unwrap();
|
||||
let pre_cpi_health = compute_health(&mut account, &banks, &oracles)?;
|
||||
require!(pre_cpi_health > 0, MangoError::HealthMustBePositive);
|
||||
msg!("pre_cpi_health {:?}", pre_cpi_health);
|
||||
|
||||
// prepare and invoke cpi
|
||||
let cpi_ix = Instruction {
|
||||
program_id: *ctx.remaining_accounts[active_len].key,
|
||||
program_id: cpi_program_id,
|
||||
data: cpi_data,
|
||||
accounts: cpi_ais
|
||||
.iter()
|
||||
.skip(1)
|
||||
.map(|cpi_ai| to_account_meta(cpi_ai))
|
||||
.collect(),
|
||||
accounts: cpi_ams,
|
||||
};
|
||||
let group_seeds = group_seeds!(group);
|
||||
let pre_cpi_token_vault_amounts = get_pre_cpi_token_amounts(&ctx, &cpi_ais);
|
||||
solana_program::program::invoke_signed(&cpi_ix, &cpi_ais, &[group_seeds])?;
|
||||
adjust_for_post_cpi_token_amounts(
|
||||
&ctx,
|
||||
&cpi_ais,
|
||||
pre_cpi_token_vault_amounts,
|
||||
group,
|
||||
&mut banks.to_vec(),
|
||||
&mut account,
|
||||
)?;
|
||||
|
||||
// compute post cpi health
|
||||
let post_cpi_health = compute_health(&mut account, &banks, &oracles).unwrap();
|
||||
let post_cpi_health = compute_health(&account, &banks, &oracles)?;
|
||||
require!(post_cpi_health > 0, MangoError::HealthMustBePositive);
|
||||
msg!("post_cpi_health {:?}", post_cpi_health);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn get_pre_cpi_token_amounts(ctx: &Context<MarginTrade>, cpi_ais: &Vec<AccountInfo>) -> Vec<u64> {
|
||||
let mut mango_vault_token_account_amounts = vec![];
|
||||
for maybe_token_account in cpi_ais
|
||||
.iter()
|
||||
.filter(|ai| ai.owner == &TokenAccount::owner())
|
||||
{
|
||||
let maybe_mango_vault_token_account =
|
||||
Account::<TokenAccount>::try_from(maybe_token_account).unwrap();
|
||||
if maybe_mango_vault_token_account.owner == ctx.accounts.group.key() {
|
||||
mango_vault_token_account_amounts.push(maybe_mango_vault_token_account.amount)
|
||||
}
|
||||
}
|
||||
mango_vault_token_account_amounts
|
||||
}
|
||||
|
||||
/// withdraws from bank, on users behalf, if he hasn't returned back entire loan amount
|
||||
fn adjust_for_post_cpi_token_amounts(
|
||||
ctx: &Context<MarginTrade>,
|
||||
cpi_ais: &Vec<AccountInfo>,
|
||||
pre_cpi_token_vault_amounts: Vec<u64>,
|
||||
group: Ref<MangoGroup>,
|
||||
banks: &mut Vec<AccountInfo>,
|
||||
account: &mut RefMut<MangoAccount>,
|
||||
) -> Result<()> {
|
||||
let x = cpi_ais
|
||||
.iter()
|
||||
.filter(|ai| ai.owner == &TokenAccount::owner());
|
||||
|
||||
for (maybe_token_account, (pre_cpi_token_vault_amount, bank_ai)) in
|
||||
util::zip!(x, pre_cpi_token_vault_amounts.iter(), banks.iter())
|
||||
{
|
||||
let maybe_mango_vault_token_account =
|
||||
Account::<TokenAccount>::try_from(maybe_token_account).unwrap();
|
||||
if maybe_mango_vault_token_account.owner == ctx.accounts.group.key() {
|
||||
let still_loaned_amount =
|
||||
pre_cpi_token_vault_amount - maybe_mango_vault_token_account.amount;
|
||||
if still_loaned_amount <= 0 {
|
||||
continue;
|
||||
}
|
||||
|
||||
let token_index = group
|
||||
.tokens
|
||||
.index_for_mint(&maybe_mango_vault_token_account.mint)?;
|
||||
let mut position = *account.indexed_positions.get_mut_or_create(token_index)?;
|
||||
let bank_loader = AccountLoader::<'_, TokenBank>::try_from(bank_ai)?;
|
||||
let mut bank = bank_loader.load_mut()?;
|
||||
// todo: this doesnt work since bank is not mut in the tests atm
|
||||
bank.withdraw(&mut position, still_loaned_amount);
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
pub use self::margin_trade::*;
|
||||
pub use create_account::*;
|
||||
pub use create_group::*;
|
||||
pub use create_stub_oracle::*;
|
||||
pub use deposit::*;
|
||||
pub use margin_trade::*;
|
||||
pub use register_token::*;
|
||||
pub use set_stub_oracle::*;
|
||||
pub use withdraw::*;
|
||||
|
|
|
@ -1,5 +1,8 @@
|
|||
use fixed::types::I80F48;
|
||||
|
||||
#[macro_use]
|
||||
pub mod util;
|
||||
|
||||
extern crate static_assertions;
|
||||
|
||||
use anchor_lang::prelude::*;
|
||||
|
@ -10,7 +13,6 @@ pub mod address_lookup_table;
|
|||
pub mod error;
|
||||
pub mod instructions;
|
||||
pub mod state;
|
||||
pub mod util;
|
||||
|
||||
declare_id!("Fg6PaFpoGXkYsidMpWTK6W2BeZ7FEfcYkg476zPFsLnS");
|
||||
|
||||
|
@ -65,7 +67,10 @@ pub mod mango_v4 {
|
|||
instructions::withdraw(ctx, amount, allow_borrow)
|
||||
}
|
||||
|
||||
pub fn margin_trade(ctx: Context<MarginTrade>, cpi_data: Vec<u8>) -> Result<()> {
|
||||
pub fn margin_trade<'key, 'accounts, 'remaining, 'info>(
|
||||
ctx: Context<'key, 'accounts, 'remaining, 'info, MarginTrade<'info>>,
|
||||
cpi_data: Vec<u8>,
|
||||
) -> Result<()> {
|
||||
instructions::margin_trade(ctx, cpi_data)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,23 +6,16 @@ use pyth_client::load_price;
|
|||
|
||||
use crate::error::MangoError;
|
||||
use crate::state::{determine_oracle_type, MangoAccount, OracleType, StubOracle, TokenBank};
|
||||
|
||||
macro_rules! zip {
|
||||
($x: expr) => ($x);
|
||||
($x: expr, $($y: expr), +) => (
|
||||
$x.zip(
|
||||
zip!($($y), +))
|
||||
)
|
||||
}
|
||||
use crate::util;
|
||||
|
||||
pub fn compute_health(
|
||||
account: &mut RefMut<MangoAccount>,
|
||||
account: &RefMut<MangoAccount>,
|
||||
banks: &[AccountInfo],
|
||||
oracles: &[AccountInfo],
|
||||
) -> Result<I80F48> {
|
||||
let mut assets = I80F48::ZERO;
|
||||
let mut liabilities = I80F48::ZERO; // absolute value
|
||||
for (position, (bank_ai, oracle_ai)) in zip!(
|
||||
for (position, (bank_ai, oracle_ai)) in util::zip!(
|
||||
account.indexed_positions.iter_active(),
|
||||
banks.iter(),
|
||||
oracles.iter()
|
||||
|
|
|
@ -1,10 +1,12 @@
|
|||
use solana_program::account_info::AccountInfo;
|
||||
use solana_program::instruction::AccountMeta;
|
||||
#![macro_use]
|
||||
|
||||
pub fn to_account_meta(account_info: &AccountInfo) -> AccountMeta {
|
||||
if account_info.is_writable {
|
||||
AccountMeta::new(*account_info.key, account_info.is_signer)
|
||||
} else {
|
||||
AccountMeta::new_readonly(*account_info.key, account_info.is_signer)
|
||||
}
|
||||
#[macro_export]
|
||||
macro_rules! zip {
|
||||
($x: expr) => ($x);
|
||||
($x: expr, $($y: expr), +) => (
|
||||
$x.zip(
|
||||
zip!($($y), +))
|
||||
)
|
||||
}
|
||||
|
||||
pub(crate) use zip;
|
||||
|
|
|
@ -130,6 +130,89 @@ async fn derive_health_check_remaining_account_metas(
|
|||
// ClientInstruction impl
|
||||
//
|
||||
|
||||
pub struct MarginTradeInstruction<'keypair> {
|
||||
pub account: Pubkey,
|
||||
pub owner: &'keypair Keypair,
|
||||
pub mango_token_vault: Pubkey,
|
||||
pub mango_group: Pubkey,
|
||||
pub margin_trade_program_id: Pubkey,
|
||||
pub loan_token_account: Pubkey,
|
||||
pub loan_token_account_owner: Pubkey,
|
||||
pub margin_trade_program_ix_cpi_data: Vec<u8>,
|
||||
}
|
||||
#[async_trait::async_trait(?Send)]
|
||||
impl<'keypair> ClientInstruction for MarginTradeInstruction<'keypair> {
|
||||
type Accounts = mango_v4::accounts::MarginTrade;
|
||||
type Instruction = mango_v4::instruction::MarginTrade;
|
||||
async fn to_instruction(
|
||||
&self,
|
||||
account_loader: impl ClientAccountLoader + 'async_trait,
|
||||
) -> (Self::Accounts, instruction::Instruction) {
|
||||
let program_id = mango_v4::id();
|
||||
let instruction = Self::Instruction {
|
||||
cpi_data: self.margin_trade_program_ix_cpi_data.clone(),
|
||||
};
|
||||
|
||||
let account: MangoAccount = account_loader.load(&self.account).await.unwrap();
|
||||
let lookup_table = account_loader
|
||||
.load_bytes(&account.address_lookup_table)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let accounts = Self::Accounts {
|
||||
group: account.group,
|
||||
account: self.account,
|
||||
owner: self.owner.pubkey(),
|
||||
};
|
||||
|
||||
let banks_and_oracles = mango_v4::address_lookup_table::addresses(&lookup_table);
|
||||
|
||||
let mut instruction = make_instruction(program_id, &accounts, instruction);
|
||||
instruction.accounts.push(AccountMeta {
|
||||
pubkey: banks_and_oracles[0],
|
||||
// since user doesnt return all loan after margin trade, we want to mark withdrawal from the bank
|
||||
is_writable: true,
|
||||
is_signer: false,
|
||||
});
|
||||
instruction.accounts.push(AccountMeta {
|
||||
pubkey: banks_and_oracles[1],
|
||||
is_writable: false,
|
||||
is_signer: false,
|
||||
});
|
||||
instruction.accounts.push(AccountMeta {
|
||||
pubkey: self.margin_trade_program_id,
|
||||
is_writable: false,
|
||||
is_signer: false,
|
||||
});
|
||||
instruction.accounts.push(AccountMeta {
|
||||
pubkey: self.mango_token_vault,
|
||||
is_writable: true,
|
||||
is_signer: false,
|
||||
});
|
||||
instruction.accounts.push(AccountMeta {
|
||||
pubkey: self.loan_token_account,
|
||||
is_writable: true,
|
||||
is_signer: false,
|
||||
});
|
||||
instruction.accounts.push(AccountMeta {
|
||||
pubkey: self.loan_token_account_owner,
|
||||
is_writable: false,
|
||||
is_signer: false,
|
||||
});
|
||||
instruction.accounts.push(AccountMeta {
|
||||
pubkey: spl_token::ID,
|
||||
is_writable: false,
|
||||
is_signer: false,
|
||||
});
|
||||
|
||||
(accounts, instruction)
|
||||
}
|
||||
|
||||
fn signers(&self) -> Vec<&Keypair> {
|
||||
vec![self.owner]
|
||||
}
|
||||
}
|
||||
|
||||
pub struct WithdrawInstruction<'keypair> {
|
||||
pub amount: u64,
|
||||
pub allow_borrow: bool,
|
||||
|
|
|
@ -78,7 +78,18 @@ pub struct TestContext {
|
|||
}
|
||||
|
||||
impl TestContext {
|
||||
pub async fn new() -> Self {
|
||||
pub async fn new(
|
||||
test_opt: Option<ProgramTest>,
|
||||
margin_trade_program_id: Option<&Pubkey>,
|
||||
margin_trade_token_account: Option<&Keypair>,
|
||||
mtta_owner: Option<&Pubkey>,
|
||||
) -> Self {
|
||||
let mut test = if test_opt.is_some() {
|
||||
test_opt.unwrap()
|
||||
} else {
|
||||
ProgramTest::new("mango_v4", mango_v4::id(), processor!(mango_v4::entry))
|
||||
};
|
||||
|
||||
// We need to intercept logs to capture program log output
|
||||
let log_filter = "solana_rbpf=trace,\
|
||||
solana_runtime::message_processor=debug,\
|
||||
|
@ -94,11 +105,8 @@ impl TestContext {
|
|||
program_log: program_log_capture.clone(),
|
||||
}));
|
||||
|
||||
let program_id = mango_v4::id();
|
||||
|
||||
let mut test = ProgramTest::new("mango_v4", program_id, processor!(mango_v4::entry));
|
||||
// intentionally set to half the limit, to catch potential problems early
|
||||
test.set_compute_max_units(100000);
|
||||
test.set_compute_max_units(200000);
|
||||
|
||||
// Setup the environment
|
||||
|
||||
|
@ -156,6 +164,28 @@ impl TestContext {
|
|||
}
|
||||
let quote_index = mints.len() - 1;
|
||||
|
||||
// margin trade
|
||||
if margin_trade_program_id.is_some() {
|
||||
test.add_program(
|
||||
"margin_trade",
|
||||
*margin_trade_program_id.unwrap(),
|
||||
std::option::Option::None,
|
||||
);
|
||||
test.add_packable_account(
|
||||
margin_trade_token_account.unwrap().pubkey(),
|
||||
u32::MAX as u64,
|
||||
&Account {
|
||||
mint: mints[0].pubkey,
|
||||
owner: *mtta_owner.unwrap(),
|
||||
amount: 10,
|
||||
state: AccountState::Initialized,
|
||||
is_native: COption::None,
|
||||
..Account::default()
|
||||
},
|
||||
&spl_token::id(),
|
||||
);
|
||||
}
|
||||
|
||||
// Users
|
||||
let num_users = 4;
|
||||
let mut users = Vec::new();
|
||||
|
|
|
@ -1,8 +1,12 @@
|
|||
#![cfg(feature = "test-bpf")]
|
||||
|
||||
use anchor_lang::InstructionData;
|
||||
use fixed::types::I80F48;
|
||||
use solana_program::pubkey::Pubkey;
|
||||
use solana_program_test::*;
|
||||
use solana_sdk::signature::Signer;
|
||||
use solana_sdk::{signature::Keypair, transport::TransportError};
|
||||
use std::str::FromStr;
|
||||
|
||||
use mango_v4::state::*;
|
||||
use program_test::*;
|
||||
|
@ -13,7 +17,18 @@ mod program_test;
|
|||
// that they work in principle. It should be split up / renamed.
|
||||
#[tokio::test]
|
||||
async fn test_basic() -> Result<(), TransportError> {
|
||||
let context = TestContext::new().await;
|
||||
let margin_trade_program_id =
|
||||
Pubkey::from_str("J83w4HKfqxwcq3BEMMkPFSppX3gqekLyLJBexebFVkix").unwrap();
|
||||
let margin_trade_token_account = Keypair::new();
|
||||
let (mtta_owner, mtta_bump_seeds) =
|
||||
Pubkey::find_program_address(&[b"margintrade"], &margin_trade_program_id);
|
||||
let context = TestContext::new(
|
||||
Option::None,
|
||||
Some(&margin_trade_program_id),
|
||||
Some(&margin_trade_token_account),
|
||||
Some(&mtta_owner),
|
||||
)
|
||||
.await;
|
||||
let solana = &context.solana.clone();
|
||||
|
||||
let admin = &Keypair::new();
|
||||
|
@ -125,6 +140,35 @@ async fn test_basic() -> Result<(), TransportError> {
|
|||
);
|
||||
}
|
||||
|
||||
//
|
||||
// TEST: Margin trade
|
||||
//
|
||||
{
|
||||
send_tx(
|
||||
solana,
|
||||
MarginTradeInstruction {
|
||||
account,
|
||||
owner,
|
||||
mango_token_vault: vault,
|
||||
mango_group: group,
|
||||
margin_trade_program_id,
|
||||
loan_token_account: margin_trade_token_account.pubkey(),
|
||||
loan_token_account_owner: mtta_owner,
|
||||
margin_trade_program_ix_cpi_data: {
|
||||
let ix = margin_trade::instruction::MarginTrade {
|
||||
amount_from: 2,
|
||||
amount_to: 1,
|
||||
loan_token_account_owner_bump_seeds: mtta_bump_seeds,
|
||||
};
|
||||
ix.data()
|
||||
},
|
||||
},
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
}
|
||||
let margin_trade_loan = 1;
|
||||
|
||||
//
|
||||
// TEST: Withdraw funds
|
||||
//
|
||||
|
@ -145,7 +189,10 @@ async fn test_basic() -> Result<(), TransportError> {
|
|||
.await
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(solana.token_account_balance(vault).await, withdraw_amount);
|
||||
assert_eq!(
|
||||
solana.token_account_balance(vault).await,
|
||||
withdraw_amount - margin_trade_loan
|
||||
);
|
||||
assert_eq!(
|
||||
solana.token_account_balance(payer_mint0_account).await,
|
||||
start_balance + withdraw_amount
|
||||
|
|
|
@ -0,0 +1,23 @@
|
|||
[package]
|
||||
name = "margin-trade"
|
||||
version = "0.1.0"
|
||||
description = "Created with Anchor"
|
||||
edition = "2021"
|
||||
|
||||
[lib]
|
||||
crate-type = ["cdylib", "lib"]
|
||||
name = "margin_trade"
|
||||
doctest = false
|
||||
|
||||
[features]
|
||||
no-entrypoint = []
|
||||
no-idl = []
|
||||
no-log-ix-name = []
|
||||
cpi = ["no-entrypoint"]
|
||||
default = []
|
||||
test-bpf = []
|
||||
|
||||
[dependencies]
|
||||
anchor-lang = { version = "0.22.0", features = [] }
|
||||
anchor-spl = "0.22.0"
|
||||
solana-program = "1.9.5"
|
|
@ -0,0 +1,2 @@
|
|||
[target.bpfel-unknown-unknown.dependencies.std]
|
||||
features = []
|
|
@ -0,0 +1,94 @@
|
|||
use anchor_lang::prelude::*;
|
||||
use anchor_spl::token;
|
||||
use anchor_spl::token::{Token, TokenAccount, Transfer};
|
||||
|
||||
declare_id!("J83w4HKfqxwcq3BEMMkPFSppX3gqekLyLJBexebFVkix");
|
||||
|
||||
#[program]
|
||||
pub mod margin_trade {
|
||||
use super::*;
|
||||
|
||||
pub fn margin_trade(
|
||||
ctx: Context<MarginTradeCtx>,
|
||||
amount_from: u64,
|
||||
loan_token_account_owner_bump_seeds: u8,
|
||||
amount_to: u64,
|
||||
) -> Result<()> {
|
||||
msg!(
|
||||
"taking amount({}) loan from mango for mint {:?}",
|
||||
amount_from,
|
||||
ctx.accounts.mango_token_vault.mint
|
||||
);
|
||||
token::transfer(ctx.accounts.transfer_from_mango_vault_ctx(), amount_from)?;
|
||||
|
||||
msg!("TODO: do something with the loan");
|
||||
|
||||
msg!(
|
||||
"transferring amount({}) loan back to mango for mint {:?}",
|
||||
amount_to,
|
||||
ctx.accounts.loan_token_account.mint
|
||||
);
|
||||
let seeds = &[
|
||||
b"margintrade".as_ref(),
|
||||
&[loan_token_account_owner_bump_seeds],
|
||||
];
|
||||
token::transfer(
|
||||
ctx.accounts
|
||||
.transfer_back_to_mango_vault_ctx()
|
||||
.with_signer(&[seeds]),
|
||||
amount_to,
|
||||
)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct MarginTrade;
|
||||
|
||||
impl anchor_lang::Id for MarginTrade {
|
||||
fn id() -> Pubkey {
|
||||
ID
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Accounts)]
|
||||
|
||||
pub struct MarginTradeCtx<'info> {
|
||||
pub mango_group: Signer<'info>,
|
||||
|
||||
#[account(mut)]
|
||||
pub mango_token_vault: Account<'info, TokenAccount>,
|
||||
|
||||
#[account(mut)]
|
||||
pub loan_token_account: Account<'info, TokenAccount>,
|
||||
|
||||
// todo: can we do better than UncheckedAccount?
|
||||
pub loan_token_account_owner: UncheckedAccount<'info>,
|
||||
|
||||
pub token_program: Program<'info, Token>,
|
||||
}
|
||||
|
||||
impl<'info> MarginTradeCtx<'info> {
|
||||
pub fn transfer_from_mango_vault_ctx(&self) -> CpiContext<'_, '_, '_, 'info, Transfer<'info>> {
|
||||
let program = self.token_program.to_account_info();
|
||||
let accounts = Transfer {
|
||||
from: self.mango_token_vault.to_account_info(),
|
||||
to: self.loan_token_account.to_account_info(),
|
||||
authority: self.mango_group.to_account_info(),
|
||||
};
|
||||
CpiContext::new(program, accounts)
|
||||
}
|
||||
|
||||
pub fn transfer_back_to_mango_vault_ctx(
|
||||
&self,
|
||||
) -> CpiContext<'_, '_, '_, 'info, Transfer<'info>> {
|
||||
let program = self.token_program.to_account_info();
|
||||
let accounts = Transfer {
|
||||
from: self.loan_token_account.to_account_info(),
|
||||
to: self.mango_token_vault.to_account_info(),
|
||||
authority: self.loan_token_account_owner.to_account_info(),
|
||||
};
|
||||
CpiContext::new(program, accounts)
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue