Merge remote-tracking branch 'origin/dev' into cj/avgentryprice

This commit is contained in:
Conj0iner 2022-08-06 22:32:59 +08:00
commit dc4a770835
55 changed files with 13027 additions and 2636 deletions

2
.eslintignore Normal file
View File

@ -0,0 +1,2 @@
ts/client/src/mango_v4.ts
ts/client/src/scripts

24
.eslintrc.json Normal file
View File

@ -0,0 +1,24 @@
{
"env": {
"browser": true,
"es2021": true,
"node": true
},
"extends": [
"eslint:recommended",
"plugin:@typescript-eslint/recommended",
"prettier"
],
"parser": "@typescript-eslint/parser",
"parserOptions": {
"ecmaVersion": 12,
"sourceType": "module"
},
"plugins": ["@typescript-eslint"],
"rules": {
"linebreak-style": ["error", "unix"],
"semi": ["error", "always"],
"@typescript-eslint/no-non-null-assertion": 0,
"@typescript-eslint/no-explicit-any": 0
}
}

1
.prettierignore Normal file
View File

@ -0,0 +1 @@
ts/client/src/mango_v4.ts

View File

@ -12,7 +12,7 @@ url = "https://anchor.projectserum.com"
[provider]
cluster = "localnet"
wallet = "/home/mc/.config/solana/id.json"
wallet = "~/.config/solana/id.json"
[scripts]
test = "yarn run ts-mocha -p ./tsconfig.json -t 1000000 anchor-tests/*.ts"

37
Cargo.lock generated
View File

@ -1260,17 +1260,24 @@ dependencies = [
"anchor-lang 0.25.0",
"anchor-spl 0.25.0",
"anyhow",
"base64 0.13.0",
"bincode",
"fixed",
"fixed-macro",
"itertools 0.10.3",
"log 0.4.17",
"mango-v4",
"pyth-sdk-solana",
"reqwest",
"serde",
"serde_json",
"serum_dex 0.4.0 (git+https://github.com/blockworks-foundation/serum-dex.git)",
"shellexpand",
"solana-account-decoder",
"solana-client",
"solana-sdk",
"thiserror",
"tokio",
]
[[package]]
@ -4581,9 +4588,9 @@ dependencies = [
[[package]]
name = "reqwest"
version = "0.11.10"
version = "0.11.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "46a1f7aa4f35e5e8b4160449f51afc758f0ce6454315a9fa7d0d113e958c41eb"
checksum = "b75aa69a3f06bbcc66ede33af2af253c6f7a86b1ca0033f60c580a27074fbf92"
dependencies = [
"async-compression",
"base64 0.13.0",
@ -4607,14 +4614,15 @@ dependencies = [
"percent-encoding 2.1.0",
"pin-project-lite",
"rustls",
"rustls-pemfile 0.3.0",
"rustls-pemfile 1.0.0",
"serde",
"serde_json",
"serde_urlencoded",
"tokio",
"tokio-native-tls",
"tokio-rustls",
"tokio-util 0.6.10",
"tokio-util 0.7.2",
"tower-service",
"url 2.2.2",
"wasm-bindgen",
"wasm-bindgen-futures",
@ -4762,15 +4770,6 @@ dependencies = [
"base64 0.13.0",
]
[[package]]
name = "rustls-pemfile"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1ee86d63972a7c661d1536fefe8c3c8407321c3df668891286de28abcd087360"
dependencies = [
"base64 0.13.0",
]
[[package]]
name = "rustls-pemfile"
version = "1.0.0"
@ -4947,9 +4946,9 @@ dependencies = [
[[package]]
name = "serde"
version = "1.0.137"
version = "1.0.141"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "61ea8d54c77f8315140a05f4c7237403bf38b72704d031543aa1d16abbf517d1"
checksum = "7af873f2c95b99fcb0bd0fe622a43e29514658873c8ceba88c4cb88833a22500"
dependencies = [
"serde_derive",
]
@ -4965,9 +4964,9 @@ dependencies = [
[[package]]
name = "serde_derive"
version = "1.0.137"
version = "1.0.141"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1f26faba0c3959972377d3b2d306ee9f71faee9714294e41bb777f83f88578be"
checksum = "75743a150d003dd863b51dc809bcad0d73f2102c53632f1e954e738192a3413f"
dependencies = [
"proc-macro2 1.0.39",
"quote 1.0.18",
@ -4987,9 +4986,9 @@ dependencies = [
[[package]]
name = "serde_json"
version = "1.0.81"
version = "1.0.82"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9b7ce2b32a1aed03c558dc61a5cd328f15aff2dbc17daad8fb8af04d2100e15c"
checksum = "82c2c1fdcd807d1098552c5b9a36e425e42e9fbd7c6a37a8425f390f781f7fa7"
dependencies = [
"itoa",
"ryu",

2
anchor

@ -1 +1 @@
Subproject commit 442c00634e847af84672727dc00b45a2c8d0a956
Subproject commit b52f23614601652a99ec6c27aec77bd327363b31

View File

@ -1,15 +1,10 @@
import * as anchor from '@project-serum/anchor';
import { Program, AnchorProvider } from '@project-serum/anchor';
import { MangoV4 } from '../target/types/mango_v4';
import * as spl from '@solana/spl-token';
import { AnchorProvider, Program } from '@project-serum/anchor';
import NodeWallet from '@project-serum/anchor/dist/cjs/nodewallet';
import { PublicKey, LAMPORTS_PER_SOL, Connection } from '@solana/web3.js';
import {
Group,
MangoClient,
StubOracle,
AccountSize,
} from '../ts/client/src/index';
import * as spl from '@solana/spl-token';
import { Connection, LAMPORTS_PER_SOL, PublicKey } from '@solana/web3.js';
import { MangoV4 } from '../target/types/mango_v4';
import { Group, MangoClient, StubOracle } from '../ts/client/src/index';
enum MINTS {
USDC = 'USDC',
@ -118,7 +113,7 @@ describe('mango-v4', () => {
// I think this is only for getting the serum market though?
envClient = await MangoClient.connect(envProvider, 'devnet', programId);
await envClient.groupCreate(groupNum, false, 1, insuranceMintPk);
group = await envClient.getGroupForAdmin(adminPk, groupNum);
group = await envClient.getGroupForCreator(adminPk, groupNum);
await envClient.stubOracleCreate(group, mintsMap['USDC']!.publicKey, 1.0);
usdcOracle = (
@ -215,49 +210,19 @@ describe('mango-v4', () => {
const mangoAccount = await client.getOrCreateMangoAccount(
group,
users[0].keypair.publicKey,
users[0].keypair,
0,
AccountSize.small,
'my_mango_account',
);
await mangoAccount.reload(client, group);
await client.tokenDeposit(
group,
mangoAccount,
'USDC',
100.5,
users[0].keypair,
);
await client.tokenDeposit(group, mangoAccount, 'USDC', 100.5);
await mangoAccount.reload(client, group);
await client.tokenDeposit(
group,
mangoAccount,
'BTC',
50.5,
users[0].keypair,
);
await client.tokenDeposit(group, mangoAccount, 'BTC', 50.5);
await mangoAccount.reload(client, group);
await client.tokenWithdraw(
group,
mangoAccount,
'USDC',
100,
false,
users[0].keypair,
);
await client.tokenWithdraw(group, mangoAccount, 'USDC', 100, false);
await mangoAccount.reload(client, group);
await client.tokenWithdraw(
group,
mangoAccount,
'BTC',
50,
false,
users[0].keypair,
);
await client.tokenWithdraw(group, mangoAccount, 'BTC', 50, false);
await mangoAccount.reload(client, group);
});
@ -284,59 +249,26 @@ describe('mango-v4', () => {
const mangoAccountA = await clientA.getOrCreateMangoAccount(
group,
users[0].keypair.publicKey,
users[0].keypair,
0,
AccountSize.small,
'my_mango_account',
);
await mangoAccountA.reload(clientA, group);
const mangoAccountB = await clientB.getOrCreateMangoAccount(
group,
users[1].keypair.publicKey,
users[1].keypair,
0,
AccountSize.small,
'my_mango_account',
);
await mangoAccountB.reload(clientB, group);
await envClient.stubOracleSet(group, btcOracle.publicKey, 100);
// Initialize liquidator
await clientA.tokenDeposit(
group,
mangoAccountA,
'USDC',
1000,
users[0].keypair,
);
await clientA.tokenDeposit(
group,
mangoAccountA,
'BTC',
100,
users[0].keypair,
);
await clientA.tokenDeposit(group, mangoAccountA, 'USDC', 1000);
await clientA.tokenDeposit(group, mangoAccountA, 'BTC', 100);
// Deposit collateral
await clientB.tokenDeposit(
group,
mangoAccountB,
'BTC',
100,
users[1].keypair,
);
await clientB.tokenDeposit(group, mangoAccountB, 'BTC', 100);
await mangoAccountB.reload(clientB, group);
// // Borrow
await clientB.tokenWithdraw(
group,
mangoAccountB,
'USDC',
200,
true,
users[1].keypair,
);
await clientB.tokenWithdraw(group, mangoAccountB, 'USDC', 200, true);
// // Set price so health is below maintanence
await envClient.stubOracleSet(group, btcOracle.publicKey, 1);
@ -346,10 +278,13 @@ describe('mango-v4', () => {
group,
mangoAccountA,
mangoAccountB,
users[0].keypair,
'BTC',
'USDC',
1000,
);
});
it('update index and rate', async () => {
envClient.updateIndexAndRate(group, 'USDC');
});
});

View File

@ -1,6 +1,8 @@
use clap::{Parser, Subcommand};
use clap::{Args, Parser, Subcommand};
use client::MangoClient;
use solana_sdk::pubkey::Pubkey;
use std::str::FromStr;
use std::sync::Arc;
#[derive(Parser, Debug, Clone)]
#[clap()]
@ -9,8 +11,83 @@ struct Cli {
command: Command,
}
#[derive(Args, Debug, Clone)]
struct Rpc {
#[clap(short, long, default_value = "m")]
url: String,
#[clap(short, long, default_value = "")]
fee_payer: String,
}
#[derive(Args, Debug, Clone)]
struct CreateAccount {
#[clap(short, long)]
group: String,
/// also pays for everything
#[clap(short, long)]
owner: String,
#[clap(short, long)]
account_num: Option<u32>,
#[clap(short, long, default_value = "")]
name: String,
#[clap(flatten)]
rpc: Rpc,
}
#[derive(Args, Debug, Clone)]
struct Deposit {
#[clap(long)]
account: String,
/// also pays for everything
#[clap(short, long)]
owner: String,
#[clap(short, long)]
mint: String,
#[clap(short, long)]
amount: u64,
#[clap(flatten)]
rpc: Rpc,
}
#[derive(Args, Debug, Clone)]
struct JupiterSwap {
#[clap(long)]
account: String,
/// also pays for everything
#[clap(short, long)]
owner: String,
#[clap(long)]
input_mint: String,
#[clap(long)]
output_mint: String,
#[clap(short, long)]
amount: u64,
#[clap(short, long)]
slippage: f64,
#[clap(flatten)]
rpc: Rpc,
}
#[derive(Subcommand, Debug, Clone)]
enum Command {
CreateAccount(CreateAccount),
Deposit(Deposit),
JupiterSwap(JupiterSwap),
GroupAddress {
#[clap(short, long)]
creator: String,
@ -26,9 +103,22 @@ enum Command {
owner: String,
#[clap(short, long, default_value = "0")]
num: u8,
num: u32,
},
}
impl Rpc {
fn client(&self, override_fee_payer: Option<&str>) -> anyhow::Result<client::Client> {
let fee_payer = client::keypair_from_cli(override_fee_payer.unwrap_or(&self.fee_payer));
Ok(client::Client {
cluster: anchor_client::Cluster::from_str(&self.url)?,
commitment: solana_sdk::commitment_config::CommitmentConfig::confirmed(),
fee_payer: Arc::new(fee_payer),
timeout: None,
})
}
}
fn main() -> Result<(), anyhow::Error> {
env_logger::init_from_env(
env_logger::Env::default().filter_or(env_logger::DEFAULT_FILTER_ENV, "info"),
@ -38,6 +128,57 @@ fn main() -> Result<(), anyhow::Error> {
let cli = Cli::parse();
match cli.command {
Command::CreateAccount(cmd) => {
let client = cmd.rpc.client(Some(&cmd.owner))?;
let group = client::pubkey_from_cli(&cmd.group);
let owner = client::keypair_from_cli(&cmd.owner);
let account_num = if let Some(num) = cmd.account_num {
num
} else {
// find free account_num
let accounts = MangoClient::find_accounts(&client, group, &owner)?;
if accounts.is_empty() {
0
} else {
accounts
.iter()
.map(|(_, account)| account.fixed.account_num)
.max()
.unwrap()
+ 1
}
};
let (account, txsig) = MangoClient::create_account(
&client,
group,
&owner,
&owner,
account_num,
&cmd.name,
)?;
println!("{}", account);
println!("{}", txsig);
}
Command::Deposit(cmd) => {
let client = cmd.rpc.client(Some(&cmd.owner))?;
let account = client::pubkey_from_cli(&cmd.account);
let owner = client::keypair_from_cli(&cmd.owner);
let mint = client::pubkey_from_cli(&cmd.mint);
let client = MangoClient::new_for_existing_account(client, account, owner)?;
let txsig = client.token_deposit(mint, cmd.amount)?;
println!("{}", txsig);
}
Command::JupiterSwap(cmd) => {
let client = cmd.rpc.client(Some(&cmd.owner))?;
let account = client::pubkey_from_cli(&cmd.account);
let owner = client::keypair_from_cli(&cmd.owner);
let input_mint = client::pubkey_from_cli(&cmd.input_mint);
let output_mint = client::pubkey_from_cli(&cmd.output_mint);
let client = MangoClient::new_for_existing_account(client, account, owner)?;
let txsig = client.jupiter_swap(input_mint, output_mint, cmd.amount, cmd.slippage)?;
println!("{}", txsig);
}
Command::GroupAddress { creator, num } => {
let creator = client::pubkey_from_cli(&creator);
println!("{}", MangoClient::group_for_admin(creator, num));

View File

@ -22,3 +22,10 @@ solana-account-decoder = "~1.10.29"
solana-client = "~1.10.29"
solana-sdk = "~1.10.29"
thiserror = "1.0.31"
log = "0.4"
reqwest = "0.11.11"
tokio = { version = "1", features = ["full"] }
serde = "1.0.141"
serde_json = "1.0.82"
base64 = "0.13.0"
bincode = "1.3.3"

View File

@ -11,17 +11,20 @@ use anchor_lang::Id;
use anchor_spl::associated_token::get_associated_token_address;
use anchor_spl::token::Token;
use bincode::Options;
use fixed::types::I80F48;
use itertools::Itertools;
use mango_v4::instructions::{Serum3OrderType, Serum3SelfTradeBehavior, Serum3Side};
use mango_v4::state::{AccountSize, Bank, Group, MangoAccountValue, Serum3MarketIndex, TokenIndex};
use solana_client::nonblocking::rpc_client::RpcClient as RpcClientAsync;
use solana_client::rpc_client::RpcClient;
use solana_sdk::signer::keypair;
use crate::account_fetcher::*;
use crate::context::{MangoGroupContext, Serum3MarketContext, TokenContext};
use crate::gpa::fetch_mango_accounts;
use crate::jupiter;
use crate::util::MyClone;
use anyhow::Context;
@ -36,14 +39,21 @@ pub struct Client {
pub cluster: Cluster,
pub fee_payer: Arc<Keypair>,
pub commitment: CommitmentConfig,
pub timeout: Option<Duration>,
}
impl Client {
pub fn new(cluster: Cluster, commitment: CommitmentConfig, fee_payer: &Keypair) -> Self {
pub fn new(
cluster: Cluster,
commitment: CommitmentConfig,
fee_payer: &Keypair,
timeout: Option<Duration>,
) -> Self {
Self {
cluster,
fee_payer: Arc::new(fee_payer.clone()),
commitment,
timeout,
}
}
@ -55,8 +65,22 @@ impl Client {
)
}
pub fn rpc_with_timeout(&self, timeout: Duration) -> RpcClient {
RpcClient::new_with_timeout_and_commitment(self.cluster.clone(), timeout, self.commitment)
pub fn rpc(&self) -> RpcClient {
let url = self.cluster.url().to_string();
if let Some(timeout) = self.timeout.as_ref() {
RpcClient::new_with_timeout_and_commitment(url, *timeout, self.commitment)
} else {
RpcClient::new_with_commitment(url, self.commitment)
}
}
pub fn rpc_async(&self) -> RpcClientAsync {
let url = self.cluster.url().to_string();
if let Some(timeout) = self.timeout.as_ref() {
RpcClientAsync::new_with_timeout_and_commitment(url, *timeout, self.commitment)
} else {
RpcClientAsync::new_with_commitment(url, self.commitment)
}
}
}
@ -72,6 +96,19 @@ pub struct MangoClient {
pub mango_account_address: Pubkey,
pub context: MangoGroupContext,
// Since MangoClient currently provides a blocking interface, we'd prefer to use reqwest::blocking::Client
// but that doesn't work inside async contexts. Hence we use the async reqwest Client instead and use
// a manual runtime to bridge into async code from both sync and async contexts.
// That doesn't work perfectly, see MangoClient::invoke().
pub http_client: reqwest::Client,
runtime: Option<tokio::runtime::Runtime>,
}
impl Drop for MangoClient {
fn drop(&mut self) {
self.runtime.take().expect("runtime").shutdown_background();
}
}
// TODO: add retry framework for sending tx and rpc calls
@ -88,11 +125,20 @@ impl MangoClient {
.0
}
pub fn find_accounts(
client: &Client,
group: Pubkey,
owner: &Keypair,
) -> anyhow::Result<Vec<(Pubkey, MangoAccountValue)>> {
let program = client.anchor_client().program(mango_v4::ID);
fetch_mango_accounts(&program, group, owner.pubkey()).map_err(Into::into)
}
pub fn find_or_create_account(
client: &Client,
group: Pubkey,
owner: Keypair,
payer: Keypair, // pays the SOL for the new account
owner: &Keypair,
payer: &Keypair, // pays the SOL for the new account
mango_account_name: &str,
) -> anyhow::Result<Pubkey> {
let program = client.anchor_client().program(mango_v4::ID);
@ -113,43 +159,7 @@ impl MangoClient {
Some(tuple) => tuple.1.fixed.account_num + 1,
None => 0u32,
};
program
.request()
.instruction(Instruction {
program_id: mango_v4::id(),
accounts: anchor_lang::ToAccountMetas::to_account_metas(
&mango_v4::accounts::AccountCreate {
group,
owner: owner.pubkey(),
account: {
Pubkey::find_program_address(
&[
group.as_ref(),
b"MangoAccount".as_ref(),
owner.pubkey().as_ref(),
&account_num.to_le_bytes(),
],
&mango_v4::id(),
)
.0
},
payer: payer.pubkey(),
system_program: System::id(),
},
None,
),
data: anchor_lang::InstructionData::data(
&mango_v4::instruction::AccountCreate {
account_num,
name: mango_account_name.to_owned(),
account_size: AccountSize::Small,
},
),
})
.signer(&owner)
.signer(&payer)
.send()
.map_err(prettify_client_error)
Self::create_account(client, group, owner, payer, account_num, mango_account_name)
.context("Failed to create account...")?;
}
let mango_account_tuples = fetch_mango_accounts(&program, group, owner.pubkey())?;
@ -160,13 +170,60 @@ impl MangoClient {
Ok(mango_account_tuples[index].0)
}
pub fn create_account(
client: &Client,
group: Pubkey,
owner: &Keypair,
payer: &Keypair, // pays the SOL for the new account
account_num: u32,
mango_account_name: &str,
) -> anyhow::Result<(Pubkey, Signature)> {
let program = client.anchor_client().program(mango_v4::ID);
let account = Pubkey::find_program_address(
&[
group.as_ref(),
b"MangoAccount".as_ref(),
owner.pubkey().as_ref(),
&account_num.to_le_bytes(),
],
&mango_v4::id(),
)
.0;
let txsig = program
.request()
.instruction(Instruction {
program_id: mango_v4::id(),
accounts: anchor_lang::ToAccountMetas::to_account_metas(
&mango_v4::accounts::AccountCreate {
group,
owner: owner.pubkey(),
account,
payer: payer.pubkey(),
system_program: System::id(),
},
None,
),
data: anchor_lang::InstructionData::data(&mango_v4::instruction::AccountCreate {
account_num,
name: mango_account_name.to_owned(),
account_size: AccountSize::Small,
}),
})
.signer(owner)
.signer(payer)
.send()
.map_err(prettify_client_error)?;
Ok((account, txsig))
}
/// Conveniently creates a RPC based client
pub fn new_for_existing_account(
client: Client,
account: Pubkey,
owner: Keypair,
) -> anyhow::Result<Self> {
let rpc = client.rpc_with_timeout(Duration::from_secs(60));
let rpc = client.rpc();
let account_fetcher = Arc::new(CachedAccountFetcher::new(RpcAccountFetcher { rpc }));
let mango_account = account_fetcher_fetch_mango_account(&*account_fetcher, account)?;
let group = mango_account.fixed.group;
@ -199,6 +256,15 @@ impl MangoClient {
owner,
mango_account_address: account,
context: group_context,
http_client: reqwest::Client::new(),
runtime: Some(
tokio::runtime::Builder::new_current_thread()
.thread_name("mango-client")
.enable_io()
.enable_time()
.build()
.unwrap(),
),
})
}
@ -229,13 +295,13 @@ impl MangoClient {
pub fn derive_health_check_remaining_account_metas(
&self,
affected_token: Option<TokenIndex>,
affected_tokens: Vec<TokenIndex>,
writable_banks: bool,
) -> anyhow::Result<Vec<AccountMeta>> {
let account = self.mango_account()?;
self.context.derive_health_check_remaining_account_metas(
&account,
affected_token,
affected_tokens,
writable_banks,
)
}
@ -273,12 +339,6 @@ impl MangoClient {
.chain(account.perp_iter_active_accounts())
.map(|&pa| self.context.perp_market_address(pa.market_index));
let to_account_meta = |pubkey| AccountMeta {
pubkey,
is_writable: false,
is_signer: false,
};
Ok(banks
.iter()
.map(|(pubkey, is_writable)| AccountMeta {
@ -286,18 +346,19 @@ impl MangoClient {
is_writable: *is_writable,
is_signer: false,
})
.chain(oracles.into_iter().map(to_account_meta))
.chain(perp_markets.map(to_account_meta))
.chain(serum_oos.map(to_account_meta))
.chain(oracles.into_iter().map(to_readonly_account_meta))
.chain(perp_markets.map(to_readonly_account_meta))
.chain(serum_oos.map(to_readonly_account_meta))
.collect())
}
pub fn token_deposit(&self, token_name: &str, amount: u64) -> anyhow::Result<Signature> {
let token_index = *self.context.token_indexes_by_name.get(token_name).unwrap();
let mint_info = self.context.mint_info(token_index);
pub fn token_deposit(&self, mint: Pubkey, amount: u64) -> anyhow::Result<Signature> {
let token = self.context.token_by_mint(&mint)?;
let token_index = token.token_index;
let mint_info = token.mint_info;
let health_check_metas =
self.derive_health_check_remaining_account_metas(Some(token_index), false)?;
self.derive_health_check_remaining_account_metas(vec![token_index], false)?;
self.program()
.request()
@ -430,7 +491,7 @@ impl MangoClient {
let account = self.mango_account()?;
let open_orders = account.serum3_find(s3.market_index).unwrap().open_orders;
let health_check_metas = self.derive_health_check_remaining_account_metas(None, false)?;
let health_check_metas = self.derive_health_check_remaining_account_metas(vec![], false)?;
// https://github.com/project-serum/serum-ts/blob/master/packages/serum/src/market.ts#L1306
let limit_price = {
@ -719,11 +780,7 @@ impl MangoClient {
.mint_info
.banks()
.iter()
.map(|bank_pubkey| AccountMeta {
pubkey: *bank_pubkey,
is_signer: false,
is_writable: true,
})
.map(|bank_pubkey| to_writable_account_meta(*bank_pubkey))
.collect::<Vec<_>>();
let health_remaining_ams = self
@ -772,6 +829,223 @@ impl MangoClient {
.send()
.map_err(prettify_client_error)
}
pub fn jupiter_swap(
&self,
input_mint: Pubkey,
output_mint: Pubkey,
source_amount: u64,
slippage: f64,
) -> anyhow::Result<Signature> {
self.invoke(self.jupiter_swap_async(input_mint, output_mint, source_amount, slippage))
}
// Not actually fully async, since it uses the blocking RPC client to send the actual tx
pub async fn jupiter_swap_async(
&self,
input_mint: Pubkey,
output_mint: Pubkey,
source_amount: u64,
slippage: f64,
) -> anyhow::Result<Signature> {
let source_token = self.context.token_by_mint(&input_mint)?;
let target_token = self.context.token_by_mint(&output_mint)?;
let quote = self
.http_client
.get("https://quote-api.jup.ag/v1/quote")
.query(&[
("inputMint", input_mint.to_string()),
("outputMint", output_mint.to_string()),
("amount", format!("{}", source_amount)),
("onlyDirectRoutes", "true".into()),
("filterTopNResult", "10".into()),
("slippage", format!("{}", slippage)),
])
.send()
.await
.context("quote request to jupiter")?
.json::<jupiter::QueryResult>()
.await
.context("receiving json response from jupiter quote request")?;
// Find the top route that doesn't involve Raydium (that has too many accounts)
let route = quote
.data
.iter()
.find(|route| {
!route
.market_infos
.iter()
.any(|mi| mi.label.contains("Raydium"))
})
.ok_or_else(|| {
anyhow::anyhow!(
"no route for swap. found {} routes, but none were usable",
quote.data.len()
)
})?;
let swap = self
.http_client
.post("https://quote-api.jup.ag/v1/swap")
.json(&jupiter::SwapRequest {
route: route.clone(),
user_public_key: self.owner.pubkey().to_string(),
wrap_unwrap_sol: false,
})
.send()
.await
.context("swap transaction request to jupiter")?
.json::<jupiter::SwapResponse>()
.await
.context("receiving json response from jupiter swap transaction request")?;
if swap.setup_transaction.is_some() || swap.cleanup_transaction.is_some() {
anyhow::bail!(
"chosen jupiter route requires setup or cleanup transactions, can't execute"
);
}
// TODO: deal with versioned transaction!
let jup_tx = bincode::options()
.with_fixint_encoding()
.reject_trailing_bytes()
.deserialize::<solana_sdk::transaction::Transaction>(
&base64::decode(&swap.swap_transaction)
.context("base64 decoding jupiter transaction")?,
)
.context("parsing jupiter transaction")?;
let jup_ixs = deserialize_instructions(&jup_tx.message)
.into_iter()
// TODO: possibly creating associated token accounts if they don't exist yet is good?!
// we could squeeze the FlashLoan instructions in the middle:
// - beginning AToken...
// - FlashLoanBegin
// - other JUP ix
// - FlashLoanEnd
// - ending AToken
.filter(|ix| {
ix.program_id
!= Pubkey::from_str("ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL").unwrap()
})
.collect::<Vec<_>>();
let bank_ams = [
source_token.mint_info.first_bank(),
target_token.mint_info.first_bank(),
]
.into_iter()
.map(to_writable_account_meta)
.collect::<Vec<_>>();
let vault_ams = [
source_token.mint_info.first_vault(),
target_token.mint_info.first_vault(),
]
.into_iter()
.map(to_writable_account_meta)
.collect::<Vec<_>>();
let token_ams = [source_token.mint_info.mint, target_token.mint_info.mint]
.into_iter()
.map(|mint| {
to_writable_account_meta(
anchor_spl::associated_token::get_associated_token_address(
&self.owner(),
&mint,
),
)
})
.collect::<Vec<_>>();
let loan_amounts = vec![source_amount, 0u64];
// This relies on the fact that health account banks will be identical to the first_bank above!
let health_ams = self
.derive_health_check_remaining_account_metas(
vec![source_token.token_index, target_token.token_index],
true,
)
.context("building health accounts")?;
let program = self.program();
let mut builder = program.request().instruction(Instruction {
program_id: mango_v4::id(),
accounts: {
let mut ams = anchor_lang::ToAccountMetas::to_account_metas(
&mango_v4::accounts::FlashLoanBegin {
group: self.group(),
token_program: Token::id(),
instructions: solana_sdk::sysvar::instructions::id(),
},
None,
);
ams.extend(bank_ams);
ams.extend(vault_ams.clone());
ams.extend(token_ams.clone());
ams
},
data: anchor_lang::InstructionData::data(&mango_v4::instruction::FlashLoanBegin {
loan_amounts,
}),
});
for ix in jup_ixs {
builder = builder.instruction(ix);
}
builder = builder.instruction(Instruction {
program_id: mango_v4::id(),
accounts: {
let mut ams = anchor_lang::ToAccountMetas::to_account_metas(
&mango_v4::accounts::FlashLoanEnd {
account: self.mango_account_address,
owner: self.owner(),
token_program: Token::id(),
},
None,
);
ams.extend(health_ams);
ams.extend(vault_ams);
ams.extend(token_ams);
ams
},
data: anchor_lang::InstructionData::data(&mango_v4::instruction::FlashLoanEnd {}),
});
let rpc = self.client.rpc_async();
builder
.signer(&self.owner)
.send_rpc_async(&rpc)
.await
.map_err(prettify_client_error)
}
fn invoke<T, F: std::future::Future<Output = T>>(&self, f: F) -> T {
// `block_on()` panics if called within an asynchronous execution context. Whereas
// `block_in_place()` only panics if called from a current_thread runtime, which is the
// lesser evil.
tokio::task::block_in_place(move || self.runtime.as_ref().expect("runtime").block_on(f))
}
}
fn deserialize_instructions(message: &solana_sdk::message::Message) -> Vec<Instruction> {
message
.instructions
.iter()
.map(|ci| solana_sdk::instruction::Instruction {
program_id: *ci.program_id(&message.account_keys),
accounts: ci
.accounts
.iter()
.map(|&index| AccountMeta {
pubkey: message.account_keys[index as usize],
is_signer: message.is_signer(index.into()),
is_writable: message.is_writable(index.into()),
})
.collect(),
data: ci.data.clone(),
})
.collect()
}
struct Serum3Data<'a> {
@ -835,3 +1109,19 @@ pub fn pubkey_from_cli(pubkey: &str) -> Pubkey {
Err(_) => keypair_from_cli(pubkey).pubkey(),
}
}
fn to_readonly_account_meta(pubkey: Pubkey) -> AccountMeta {
AccountMeta {
pubkey,
is_writable: false,
is_signer: false,
}
}
fn to_writable_account_meta(pubkey: Pubkey) -> AccountMeta {
AccountMeta {
pubkey,
is_writable: true,
is_signer: false,
}
}

View File

@ -9,6 +9,8 @@ use mango_v4::state::{
TokenIndex,
};
use fixed::types::I80F48;
use crate::gpa::*;
use solana_sdk::account::Account;
@ -16,13 +18,21 @@ use solana_sdk::instruction::AccountMeta;
use solana_sdk::signature::Keypair;
use solana_sdk::{commitment_config::CommitmentConfig, pubkey::Pubkey};
#[derive(Clone)]
pub struct TokenContext {
pub token_index: TokenIndex,
pub name: String,
pub mint_info: MintInfo,
pub mint_info_address: Pubkey,
pub decimals: u8,
}
impl TokenContext {
pub fn native_to_ui(&self, native: I80F48) -> f64 {
(native / I80F48::from(10u64.pow(self.decimals.into()))).to_num()
}
}
pub struct Serum3MarketContext {
pub address: Pubkey,
pub market: Serum3Market,
@ -68,6 +78,13 @@ impl MangoGroupContext {
self.tokens.get(&token_index).unwrap()
}
pub fn token_by_mint(&self, mint: &Pubkey) -> anyhow::Result<&TokenContext> {
self.tokens
.iter()
.find_map(|(_, tc)| (tc.mint_info.mint == *mint).then(|| tc))
.ok_or_else(|| anyhow::anyhow!("no token for mint {}", mint))
}
pub fn perp_market_address(&self, perp_market_index: PerpMarketIndex) -> Pubkey {
self.perp_markets.get(&perp_market_index).unwrap().address
}
@ -89,6 +106,7 @@ impl MangoGroupContext {
(
mi.token_index,
TokenContext {
token_index: mi.token_index,
name: String::new(),
mint_info: *mi,
mint_info_address: *pk,
@ -187,7 +205,7 @@ impl MangoGroupContext {
pub fn derive_health_check_remaining_account_metas(
&self,
account: &MangoAccountValue,
affected_token: Option<TokenIndex>,
affected_tokens: Vec<TokenIndex>,
writable_banks: bool,
) -> anyhow::Result<Vec<AccountMeta>> {
// figure out all the banks/oracles that need to be passed for the health check
@ -198,7 +216,7 @@ impl MangoGroupContext {
banks.push(mint_info.first_bank());
oracles.push(mint_info.oracle);
}
if let Some(affected_token_index) = affected_token {
for affected_token_index in affected_tokens {
if account
.token_iter_active()
.find(|p| p.token_index == affected_token_index)

61
client/src/jupiter.rs Normal file
View File

@ -0,0 +1,61 @@
use serde::{Deserialize, Serialize};
#[derive(Deserialize, Serialize, Debug, Clone)]
#[serde(rename_all = "camelCase")]
pub struct QueryResult {
pub data: Vec<QueryRoute>,
pub time_taken: f64,
pub context_slot: String,
}
#[derive(Deserialize, Serialize, Debug, Clone)]
#[serde(rename_all = "camelCase")]
pub struct QueryRoute {
pub in_amount: u64,
pub out_amount: u64,
pub amount: u64,
pub other_amount_threshold: u64,
pub out_amount_with_slippage: u64,
pub swap_mode: String,
pub price_impact_pct: f64,
pub market_infos: Vec<QueryMarketInfo>,
}
#[derive(Deserialize, Serialize, Debug, Clone)]
#[serde(rename_all = "camelCase")]
pub struct QueryMarketInfo {
pub id: String,
pub label: String,
pub input_mint: String,
pub output_mint: String,
pub in_amount: u64,
pub out_amount: u64,
pub lp_fee: QueryFee,
pub platform_fee: QueryFee,
pub not_enough_liquidity: bool,
pub price_impact_pct: f64,
}
#[derive(Deserialize, Serialize, Debug, Clone)]
#[serde(rename_all = "camelCase")]
pub struct QueryFee {
pub amount: u64,
pub mint: String,
pub pct: f64,
}
#[derive(Deserialize, Serialize, Debug, Clone)]
#[serde(rename_all = "camelCase")]
pub struct SwapRequest {
pub route: QueryRoute,
pub user_public_key: String,
pub wrap_unwrap_sol: bool,
}
#[derive(Deserialize, Serialize, Debug, Clone)]
#[serde(rename_all = "camelCase")]
pub struct SwapResponse {
pub setup_transaction: Option<String>,
pub swap_transaction: String,
pub cleanup_transaction: Option<String>,
}

View File

@ -9,4 +9,5 @@ mod chain_data_fetcher;
mod client;
mod context;
mod gpa;
mod jupiter;
mod util;

View File

@ -70,6 +70,7 @@ pub async fn loop_update_index_and_rate(mango_client: Arc<MangoClient>, token_in
program_id: mango_v4::id(),
accounts: anchor_lang::ToAccountMetas::to_account_metas(
&mango_v4::accounts::TokenUpdateIndexAndRate {
group: token.mint_info.group,
mint_info: token.mint_info_address,
oracle,
instructions: solana_program::sysvar::instructions::id(),
@ -240,6 +241,7 @@ pub async fn loop_update_funding(
program_id: mango_v4::id(),
accounts: anchor_lang::ToAccountMetas::to_account_metas(
&mango_v4::accounts::PerpUpdateFunding {
group: perp_market.group,
perp_market: pk,
asks: perp_market.asks,
bids: perp_market.bids,

View File

@ -2,6 +2,7 @@ mod crank;
mod taker;
use std::sync::Arc;
use std::time::Duration;
use anchor_client::Cluster;
@ -75,7 +76,7 @@ fn main() -> Result<(), anyhow::Error> {
};
let mango_client = Arc::new(MangoClient::new_for_existing_account(
Client::new(cluster, commitment, &owner),
Client::new(cluster, commitment, &owner, Some(Duration::from_secs(1))),
cli.mango_account,
owner,
)?);

View File

@ -114,7 +114,7 @@ fn ensure_deposit(mango_client: &Arc<MangoClient>) -> Result<(), anyhow::Error>
}
log::info!("Depositing {} {}", deposit_native, bank.name());
mango_client.token_deposit(bank.name(), desired_balance.to_num())?;
mango_client.token_deposit(bank.mint, desired_balance.to_num())?;
}
Ok(())

View File

@ -16,7 +16,7 @@ pub fn new_health_cache_(
let active_token_len = account.token_iter_active().count();
let active_perp_len = account.perp_iter_active_accounts().count();
let metas = context.derive_health_check_remaining_account_metas(account, None, false)?;
let metas = context.derive_health_check_remaining_account_metas(account, vec![], false)?;
let accounts = metas
.iter()
.map(|meta| {

View File

@ -99,14 +99,14 @@ async fn main() -> anyhow::Result<()> {
let rpc_timeout = Duration::from_secs(1);
let cluster = Cluster::Custom(rpc_url.clone(), ws_url.clone());
let commitment = CommitmentConfig::processed();
let client = Client::new(cluster.clone(), commitment, &liqor_owner);
let client = Client::new(cluster.clone(), commitment, &liqor_owner, Some(rpc_timeout));
// The representation of current on-chain account data
let chain_data = Arc::new(RwLock::new(chain_data::ChainData::new()));
// Reading accounts from chain_data
let account_fetcher = Arc::new(chain_data::AccountFetcher {
chain_data: chain_data.clone(),
rpc: client.rpc_with_timeout(rpc_timeout),
rpc: client.rpc(),
});
let mango_account = account_fetcher.fetch_fresh_mango_account(&cli.liqor_mango_account)?;

9814
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@ -23,13 +23,13 @@
"example1-user": "ts-node ts/client/src/scripts/example1-user.ts",
"example1-admin": "ts-node ts/client/src/scripts/example1-admin.ts",
"scratch": "ts-node ts/client/src/scripts/scratch/scratch.ts",
"format": "prettier --check .",
"lint": "eslint . --ext ts --ext tsx --ext js --quiet",
"format": "prettier --check ./ts",
"lint": "eslint ./ts --ext ts --ext tsx --ext js --quiet",
"typecheck": "tsc --noEmit --pretty",
"prepare": "yarn build",
"prebuild": "npm run clean",
"prepublishOnly": "npm run validate && npm run build",
"validate": "npm run typecheck && npm run test && npm run lint && npm run format-check"
"validate": "npm run typecheck && npm run lint && npm run format"
},
"devDependencies": {
"@jup-ag/core": "^1.0.0-beta.28",
@ -38,8 +38,8 @@
"@types/chai": "^4.3.0",
"@types/mocha": "^9.1.0",
"@types/node": "^14.14.37",
"@typescript-eslint/eslint-plugin": "^4.14.2",
"@typescript-eslint/parser": "^4.14.2",
"@typescript-eslint/eslint-plugin": "^5.32.0",
"@typescript-eslint/parser": "^5.32.0",
"chai": "^4.3.4",
"eslint": "^7.28.0",
"eslint-config-prettier": "^7.2.0",

View File

@ -8,7 +8,12 @@ use crate::state::*;
pub struct AccountClose<'info> {
pub group: AccountLoader<'info, Group>,
#[account(mut, has_one = group, has_one = owner, close = sol_destination)]
#[account(
mut,
has_one = group,
has_one = owner,
close = sol_destination
)]
pub account: AccountLoaderDynamic<'info, MangoAccount>,
pub owner: Signer<'info>,

View File

@ -10,6 +10,7 @@ use crate::state::{
use crate::util::checked_math as cm;
use anchor_lang::prelude::*;
use anchor_lang::solana_program::sysvar::instructions as tx_instructions;
use anchor_lang::Discriminator;
use anchor_spl::token::{self, Token, TokenAccount};
use fixed::types::I80F48;
@ -76,19 +77,20 @@ pub fn flash_loan_begin<'key, 'accounts, 'remaining, 'info>(
require_keys_eq!(bank.group, ctx.accounts.group.key());
require_keys_eq!(bank.vault, *vault_ai.key);
let vault = Account::<TokenAccount>::try_from(vault_ai)?;
let token_account = Account::<TokenAccount>::try_from(token_account_ai)?;
bank.flash_loan_approved_amount = *amount;
bank.flash_loan_vault_initial = token_account.amount;
bank.flash_loan_token_account_initial = token_account.amount;
// Transfer the loaned funds
if *amount > 0 {
// Provide a readable error message in case the vault doesn't have enough tokens
if token_account.amount < *amount {
if vault.amount < *amount {
return err!(MangoError::InsufficentBankVaultFunds).with_context(|| {
format!(
"bank vault {} does not have enough tokens, need {} but have {}",
vault_ai.key, amount, token_account.amount
vault_ai.key, amount, vault.amount
)
});
}
@ -141,11 +143,11 @@ pub fn flash_loan_begin<'key, 'accounts, 'remaining, 'info>(
// must be the FlashLoanEnd instruction
require!(
ix.data[0..8] == [178, 170, 2, 78, 240, 23, 190, 178],
ix.data[0..8] == crate::instruction::FlashLoanEnd::discriminator(),
MangoError::SomeError
);
// check that the same vaults are passed
// check that the same vaults and token accounts are passed
let begin_accounts = &ctx.remaining_accounts[num_loans..];
let end_accounts = &ix.accounts[ix.accounts.len() - 2 * num_loans..];
for (begin_account, end_account) in begin_accounts.iter().zip(end_accounts.iter()) {
@ -180,6 +182,7 @@ pub fn flash_loan_end<'key, 'accounts, 'remaining, 'info>(
ctx: Context<'key, 'accounts, 'remaining, 'info, FlashLoanEnd<'info>>,
) -> Result<()> {
let mut account = ctx.accounts.account.load_mut()?;
let group = account.fixed.group;
require!(!account.fixed.is_bankrupt(), MangoError::IsBankrupt);
@ -233,7 +236,7 @@ pub fn flash_loan_end<'key, 'accounts, 'remaining, 'info>(
// The Begin instruction only checks that End ends with the same vault accounts -
// but there could be an extra vault account in End, or a different bank could be
// used for the same vault.
require_neq!(bank.flash_loan_vault_initial, u64::MAX);
require_neq!(bank.flash_loan_token_account_initial, u64::MAX);
// Create the token position now, so we can compute the pre-health with fixed order health accounts
let (_, raw_token_index, _) = account.token_get_mut_or_create(bank.token_index)?;
@ -241,7 +244,7 @@ pub fn flash_loan_end<'key, 'accounts, 'remaining, 'info>(
// Transfer any excess over the inital balance of the token account back
// into the vault. Compute the total change in the vault balance.
let mut change = -I80F48::from(bank.flash_loan_approved_amount);
if token_account.amount > bank.flash_loan_vault_initial {
if token_account.amount > bank.flash_loan_token_account_initial {
let transfer_ctx = CpiContext::new(
ctx.accounts.token_program.to_account_info(),
token::Transfer {
@ -250,7 +253,7 @@ pub fn flash_loan_end<'key, 'accounts, 'remaining, 'info>(
authority: ctx.accounts.owner.to_account_info(),
},
);
let repay = token_account.amount - bank.flash_loan_vault_initial;
let repay = token_account.amount - bank.flash_loan_token_account_initial;
token::transfer(transfer_ctx, repay)?;
let repay = I80F48::from(repay);
@ -322,7 +325,7 @@ pub fn flash_loan_end<'key, 'accounts, 'remaining, 'info>(
}
bank.flash_loan_approved_amount = 0;
bank.flash_loan_vault_initial = u64::MAX;
bank.flash_loan_token_account_initial = u64::MAX;
token_loan_details.push(FlashLoanTokenDetail {
token_index: position.token_index,
@ -335,6 +338,7 @@ pub fn flash_loan_end<'key, 'accounts, 'remaining, 'info>(
});
emit!(TokenBalanceLog {
mango_group: group.key(),
mango_account: ctx.accounts.account.key(),
token_index: bank.token_index as u16,
indexed_position: position.indexed_position.to_bits(),
@ -345,6 +349,7 @@ pub fn flash_loan_end<'key, 'accounts, 'remaining, 'info>(
}
emit!(FlashLoanLog {
mango_group: group.key(),
mango_account: ctx.accounts.account.key(),
token_loan_details
});

View File

@ -157,6 +157,7 @@ pub fn liq_token_with_token(
);
emit!(LiquidateTokenAndTokenLog {
mango_group: ctx.accounts.group.key(),
liqee: ctx.accounts.liqee.key(),
liqor: ctx.accounts.liqor.key(),
asset_token_index,
@ -170,6 +171,7 @@ pub fn liq_token_with_token(
// liqee asset
emit!(TokenBalanceLog {
mango_group: ctx.accounts.group.key(),
mango_account: ctx.accounts.liqee.key(),
token_index: asset_token_index,
indexed_position: liqee_asset_position_indexed.to_bits(),
@ -179,6 +181,7 @@ pub fn liq_token_with_token(
});
// liqee liab
emit!(TokenBalanceLog {
mango_group: ctx.accounts.group.key(),
mango_account: ctx.accounts.liqee.key(),
token_index: liab_token_index,
indexed_position: liqee_liab_position_indexed.to_bits(),
@ -188,6 +191,7 @@ pub fn liq_token_with_token(
});
// liqor asset
emit!(TokenBalanceLog {
mango_group: ctx.accounts.group.key(),
mango_account: ctx.accounts.liqor.key(),
token_index: asset_token_index,
indexed_position: liqor_asset_position_indexed.to_bits(),
@ -197,6 +201,7 @@ pub fn liq_token_with_token(
});
// liqor liab
emit!(TokenBalanceLog {
mango_group: ctx.accounts.group.key(),
mango_account: ctx.accounts.liqor.key(),
token_index: liab_token_index,
indexed_position: liqor_liab_position_indexed.to_bits(),

View File

@ -61,6 +61,7 @@ pub fn perp_consume_events(ctx: Context<PerpConsumeEvents>, limit: usize) -> Res
fill,
)?;
emit_perp_balances(
ctx.accounts.group.key(),
fill.maker,
perp_market.perp_market_index as u64,
fill.price,
@ -101,6 +102,7 @@ pub fn perp_consume_events(ctx: Context<PerpConsumeEvents>, limit: usize) -> Res
fill,
)?;
emit_perp_balances(
ctx.accounts.group.key(),
fill.maker,
perp_market.perp_market_index as u64,
fill.price,
@ -110,6 +112,7 @@ pub fn perp_consume_events(ctx: Context<PerpConsumeEvents>, limit: usize) -> Res
&perp_market,
);
emit_perp_balances(
ctx.accounts.group.key(),
fill.taker,
perp_market.perp_market_index as u64,
fill.price,

View File

@ -1,15 +1,18 @@
use anchor_lang::prelude::*;
use crate::accounts_zerocopy::*;
use crate::state::{oracle_price, Book, BookSide, PerpMarket};
use crate::state::{oracle_price, Book, BookSide, Group, PerpMarket};
#[derive(Accounts)]
pub struct PerpUpdateFunding<'info> {
pub group: AccountLoader<'info, Group>, // Required for group metadata parsing
#[account(
mut,
has_one = bids,
has_one = asks,
has_one = oracle,
constraint = perp_market.load()?.group.key() == group.key(),
)]
pub perp_market: AccountLoader<'info, PerpMarket>,
#[account(mut)]

View File

@ -83,6 +83,7 @@ pub fn token_deposit(ctx: Context<TokenDeposit>, amount: u64) -> Result<()> {
cm!(amount_i80f48 * oracle_price * QUOTE_NATIVE_TO_UI).to_num::<f32>();
emit!(TokenBalanceLog {
mango_group: ctx.accounts.group.key(),
mango_account: ctx.accounts.account.key(),
token_index,
indexed_position: indexed_position.to_bits(),
@ -111,6 +112,7 @@ pub fn token_deposit(ctx: Context<TokenDeposit>, amount: u64) -> Result<()> {
}
emit!(DepositLog {
mango_group: ctx.accounts.group.key(),
mango_account: ctx.accounts.account.key(),
signer: ctx.accounts.token_authority.key(),
token_index,

View File

@ -109,7 +109,7 @@ pub fn token_edit(
// unchanged -
// dust
// flash_loan_vault_initial
// flash_loan_token_account_initial
// flash_loan_approved_amount
// token_index
// bump

View File

@ -131,7 +131,7 @@ pub fn token_register(
init_liab_weight: I80F48::from_num(init_liab_weight),
liquidation_fee: I80F48::from_num(liquidation_fee),
dust: I80F48::ZERO,
flash_loan_vault_initial: u64::MAX,
flash_loan_token_account_initial: u64::MAX,
flash_loan_approved_amount: 0,
token_index,
bump: *ctx.bumps.get("bank").ok_or(MangoError::SomeError)?,

View File

@ -103,7 +103,7 @@ pub fn token_register_trustless(
init_liab_weight: I80F48::from_num(1.5),
liquidation_fee: I80F48::from_num(0.125),
dust: I80F48::ZERO,
flash_loan_vault_initial: u64::MAX,
flash_loan_token_account_initial: u64::MAX,
flash_loan_approved_amount: 0,
token_index,
bump: *ctx.bumps.get("bank").ok_or(MangoError::SomeError)?,

View File

@ -5,16 +5,20 @@ use crate::logs::{UpdateIndexLog, UpdateRateLog};
use crate::state::HOUR;
use crate::{
accounts_zerocopy::{AccountInfoRef, LoadMutZeroCopyRef, LoadZeroCopyRef},
state::{oracle_price, Bank, MintInfo},
state::{oracle_price, Bank, Group, MintInfo},
};
use anchor_lang::solana_program::sysvar::instructions as tx_instructions;
use anchor_lang::Discriminator;
use checked_math as cm;
use fixed::types::I80F48;
#[derive(Accounts)]
pub struct TokenUpdateIndexAndRate<'info> {
pub group: AccountLoader<'info, Group>, // Required for group metadata parsing
#[account(
has_one = oracle
has_one = oracle,
constraint = mint_info.load()?.group.key() == group.key(),
)]
pub mint_info: AccountLoader<'info, MintInfo>,
@ -43,7 +47,8 @@ pub fn token_update_index_and_rate(ctx: Context<TokenUpdateIndexAndRate>) -> Res
// 2. we want to forbid cpi, since ix we would like to blacklist could just be called from cpi
require!(
ix.program_id == crate::id()
&& ix.data[0..8] == [131, 136, 194, 39, 11, 50, 10, 198], // token_update_index_and_rate
&& ix.data[0..8]
== crate::instruction::TokenUpdateIndexAndRate::discriminator(),
MangoError::SomeError
);

View File

@ -125,6 +125,7 @@ pub fn token_withdraw(ctx: Context<TokenWithdraw>, amount: u64, allow_borrow: bo
cm!(amount_i80f48 * oracle_price * QUOTE_NATIVE_TO_UI).to_num::<f32>();
emit!(TokenBalanceLog {
mango_group: ctx.accounts.group.key(),
mango_account: ctx.accounts.account.key(),
token_index,
indexed_position: indexed_position.to_bits(),
@ -151,6 +152,7 @@ pub fn token_withdraw(ctx: Context<TokenWithdraw>, amount: u64, allow_borrow: bo
}
emit!(WithdrawLog {
mango_group: ctx.accounts.group.key(),
mango_account: ctx.accounts.account.key(),
signer: ctx.accounts.owner.key(),
token_index,

View File

@ -4,6 +4,7 @@ use borsh::BorshSerialize;
/// Warning: This function needs 512+ bytes free on the stack
pub fn emit_perp_balances(
mango_group: Pubkey,
mango_account: Pubkey,
market_index: u64,
price: i64,
@ -11,6 +12,7 @@ pub fn emit_perp_balances(
pm: &PerpMarket,
) {
emit!(PerpBalanceLog {
mango_group,
mango_account,
market_index,
base_position: pp.base_position_lots,
@ -25,6 +27,7 @@ pub fn emit_perp_balances(
#[event]
pub struct PerpBalanceLog {
pub mango_group: Pubkey,
pub mango_account: Pubkey,
pub market_index: u64, // IDL doesn't support usize
pub base_position: i64,
@ -38,6 +41,7 @@ pub struct PerpBalanceLog {
#[event]
pub struct TokenBalanceLog {
pub mango_group: Pubkey,
pub mango_account: Pubkey,
pub token_index: u16, // IDL doesn't support usize
pub indexed_position: i128, // on client convert i128 to I80F48 easily by passing in the BN to I80F48 ctor
@ -46,14 +50,6 @@ pub struct TokenBalanceLog {
pub price: i128, // I80F48
}
#[event]
pub struct MarginTradeLog {
pub mango_account: Pubkey,
pub token_indexes: Vec<u16>,
pub pre_indexed_positions: Vec<i128>,
pub post_indexed_positions: Vec<i128>,
}
#[derive(AnchorSerialize, AnchorDeserialize)]
pub struct FlashLoanTokenDetail {
pub token_index: u16,
@ -67,12 +63,14 @@ pub struct FlashLoanTokenDetail {
#[event]
pub struct FlashLoanLog {
pub mango_group: Pubkey,
pub mango_account: Pubkey,
pub token_loan_details: Vec<FlashLoanTokenDetail>,
}
#[event]
pub struct WithdrawLog {
pub mango_group: Pubkey,
pub mango_account: Pubkey,
pub signer: Pubkey,
pub token_index: u16,
@ -82,6 +80,7 @@ pub struct WithdrawLog {
#[event]
pub struct DepositLog {
pub mango_group: Pubkey,
pub mango_account: Pubkey,
pub signer: Pubkey,
pub token_index: u16,
@ -147,6 +146,7 @@ pub struct UpdateRateLog {
#[event]
pub struct LiquidateTokenAndTokenLog {
pub mango_group: Pubkey,
pub liqee: Pubkey,
pub liqor: Pubkey,
pub asset_token_index: u16,

View File

@ -82,7 +82,7 @@ pub struct Bank {
// Collection of all fractions-of-native-tokens that got rounded away
pub dust: I80F48,
pub flash_loan_vault_initial: u64,
pub flash_loan_token_account_initial: u64,
pub flash_loan_approved_amount: u64,
// Index into TokenInfo on the group
@ -145,7 +145,10 @@ impl std::fmt::Debug for Bank {
"flash_loan_approved_amount",
&self.flash_loan_approved_amount,
)
.field("flash_loan_vault_initial", &self.flash_loan_vault_initial)
.field(
"flash_loan_token_account_initial",
&self.flash_loan_token_account_initial,
)
.field("reserved", &self.reserved)
.finish()
}
@ -185,7 +188,7 @@ impl Bank {
liquidation_fee: existing_bank.liquidation_fee,
dust: I80F48::ZERO,
flash_loan_approved_amount: 0,
flash_loan_vault_initial: u64::MAX,
flash_loan_token_account_initial: u64::MAX,
token_index: existing_bank.token_index,
bump: existing_bank.bump,
mint_decimals: existing_bank.mint_decimals,

View File

@ -2374,6 +2374,7 @@ impl ClientInstruction for PerpConsumeEventsInstruction {
}
pub struct PerpUpdateFundingInstruction {
pub group: Pubkey,
pub perp_market: Pubkey,
pub bids: Pubkey,
pub asks: Pubkey,
@ -2391,6 +2392,7 @@ impl ClientInstruction for PerpUpdateFundingInstruction {
let program_id = mango_v4::id();
let instruction = Self::Instruction {};
let accounts = Self::Accounts {
group: self.group,
perp_market: self.perp_market,
bids: self.bids,
asks: self.asks,
@ -2444,6 +2446,7 @@ impl ClientInstruction for TokenUpdateIndexAndRateInstruction {
let mint_info: MintInfo = loader.load(&self.mint_info).await.unwrap();
let accounts = Self::Accounts {
group: mint_info.group,
mint_info: self.mint_info,
oracle: mint_info.oracle,
instructions: solana_program::sysvar::instructions::id(),

View File

@ -106,4 +106,4 @@
"perpMarkets": []
}
]
}
}

View File

@ -44,8 +44,8 @@ export class I80F48 {
this.data = data;
}
static fromNumber(x: number): I80F48 {
let int_part = Math.trunc(x);
let v = new BN(int_part).iushln(48);
const int_part = Math.trunc(x);
const v = new BN(int_part).iushln(48);
v.iadd(new BN((x - int_part) * I80F48.MULTIPLIER_NUMBER));
return new I80F48(v);
}

View File

@ -63,7 +63,7 @@ export class Bank {
initLiabWeight: I80F48Dto;
liquidationFee: I80F48Dto;
dust: I80F48Dto;
flashLoanVaultInitial: BN;
flashLoanTokenAccountInitial: BN;
flashLoanApprovedAmount: BN;
tokenIndex: number;
mintDecimals: number;
@ -102,7 +102,7 @@ export class Bank {
obj.initLiabWeight,
obj.liquidationFee,
obj.dust,
obj.flashLoanVaultInitial,
obj.flashLoanTokenAccountInitial,
obj.flashLoanApprovedAmount,
obj.tokenIndex,
obj.mintDecimals,
@ -141,9 +141,9 @@ export class Bank {
maintLiabWeight: I80F48Dto,
initLiabWeight: I80F48Dto,
liquidationFee: I80F48Dto,
dust: Object,
flashLoanVaultInitial: Object,
flashLoanApprovedAmount: Object,
dust: I80F48Dto,
flashLoanTokenAccountInitial: BN,
flashLoanApprovedAmount: BN,
public tokenIndex: number,
public mintDecimals: number,
public bankNum: number,
@ -337,7 +337,7 @@ export class MintInfo {
}
toString(): string {
let res =
const res =
'mint ' +
this.mint.toBase58() +
'\n oracle ' +

View File

@ -207,7 +207,7 @@ export class Group {
price.data.slice(0, 8),
)
) {
let stubOracle = coder.decode('stubOracle', price.data);
const stubOracle = coder.decode('stubOracle', price.data);
banks[index].price = new I80F48(stubOracle.price.val);
} else {
banks[index].price = I80F48.fromNumber(

View File

@ -39,15 +39,18 @@ export class HealthCache {
public health(healthType: HealthType): I80F48 {
let health = ZERO_I80F48;
for (const tokenInfo of this.tokenInfos) {
let contrib = tokenInfo.healthContribution(healthType);
const contrib = tokenInfo.healthContribution(healthType);
health = health.add(contrib);
}
for (const serum3Info of this.serum3Infos) {
let contrib = serum3Info.healthContribution(healthType, this.tokenInfos);
const contrib = serum3Info.healthContribution(
healthType,
this.tokenInfos,
);
health = health.add(contrib);
}
for (const perpInfo of this.perpInfos) {
let contrib = perpInfo.healthContribution(healthType);
const contrib = perpInfo.healthContribution(healthType);
health = health.add(contrib);
}
return health;
@ -110,9 +113,9 @@ export class Serum3Info {
quoteIndex: number;
healthContribution(healthType: HealthType, tokenInfos: TokenInfo[]): I80F48 {
let baseInfo = tokenInfos[this.baseIndex];
let quoteInfo = tokenInfos[this.quoteIndex];
let reserved = this.reserved;
const baseInfo = tokenInfos[this.baseIndex];
const quoteInfo = tokenInfos[this.quoteIndex];
const reserved = this.reserved;
if (reserved.isZero()) {
return ZERO_I80F48;
@ -120,10 +123,10 @@ export class Serum3Info {
// How much the health would increase if the reserved balance were applied to the passed
// token info?
let computeHealthEffect = function (tokenInfo: TokenInfo) {
const computeHealthEffect = function (tokenInfo: TokenInfo) {
// This balance includes all possible reserved funds from markets that relate to the
// token, including this market itself: `reserved` is already included in `max_balance`.
let maxBalance = tokenInfo.balance.add(tokenInfo.serum3MaxReserved);
const maxBalance = tokenInfo.balance.add(tokenInfo.serum3MaxReserved);
// Assuming `reserved` was added to `max_balance` last (because that gives the smallest
// health effects): how much did health change because of it?
@ -139,13 +142,13 @@ export class Serum3Info {
liabPart = reserved.sub(maxBalance);
}
let assetWeight = tokenInfo.assetWeight(healthType);
let liabWeight = tokenInfo.liabWeight(healthType);
const assetWeight = tokenInfo.assetWeight(healthType);
const liabWeight = tokenInfo.liabWeight(healthType);
return assetWeight.mul(assetPart).add(liabWeight.mul(liabPart));
};
let reservedAsBase = computeHealthEffect(baseInfo);
let reservedAsQuote = computeHealthEffect(quoteInfo);
const reservedAsBase = computeHealthEffect(baseInfo);
const reservedAsQuote = computeHealthEffect(quoteInfo);
return reservedAsBase.min(reservedAsQuote);
}
}

View File

@ -28,7 +28,7 @@ export class MangoAccount {
netSettled: number;
headerVersion: number;
tokens: unknown;
serum3: Object;
serum3: unknown;
perps: unknown;
perpOpenOrders: unknown;
},
@ -50,7 +50,7 @@ export class MangoAccount {
obj.serum3 as Serum3PositionDto[],
obj.perps as PerpPositionDto[],
obj.perpOpenOrders as any,
{},
{} as any,
);
}
@ -71,7 +71,7 @@ export class MangoAccount {
serum3: Serum3PositionDto[],
perps: PerpPositionDto[],
perpOpenOrders: PerpPositionDto[],
public accountData: {},
public accountData: MangoAccountData,
) {
this.name = utf8.decode(new Uint8Array(name)).split('\x00')[0];
this.tokens = tokens.map((dto) => TokenPosition.from(dto));
@ -172,7 +172,7 @@ export class MangoAccount {
*/
getEquity(): I80F48 {
const equity = (this.accountData as MangoAccountData).equity;
let total_equity = equity.tokens.reduce(
const total_equity = equity.tokens.reduce(
(a, b) => a.add(b.value),
ZERO_I80F48,
);
@ -191,7 +191,7 @@ export class MangoAccount {
*/
getAssetsVal(): I80F48 {
const equity = (this.accountData as MangoAccountData).equity;
let total_equity = equity.tokens.reduce(
const total_equity = equity.tokens.reduce(
(a, b) => (b.value.gt(ZERO_I80F48) ? a.add(b.value) : a),
ZERO_I80F48,
);
@ -203,7 +203,7 @@ export class MangoAccount {
*/
getLiabsVal(): I80F48 {
const equity = (this.accountData as MangoAccountData).equity;
let total_equity = equity.tokens.reduce(
const total_equity = equity.tokens.reduce(
(a, b) => (b.value.lt(ZERO_I80F48) ? a.add(b.value) : a),
ZERO_I80F48,
);
@ -374,7 +374,7 @@ export class MangoAccount {
}
export class TokenPosition {
static TokenIndexUnset: number = 65535;
static TokenIndexUnset = 65535;
static from(dto: TokenPositionDto) {
return new TokenPosition(
I80F48.from(dto.indexedPosition),
@ -419,12 +419,12 @@ export class TokenPosition {
).toNumber();
}
public toString(group?: Group): String {
let extra: string = '';
public toString(group?: Group): string {
let extra = '';
if (group) {
let bank = group.findBank(this.tokenIndex);
const bank = group.findBank(this.tokenIndex);
if (bank) {
let native = this.native(bank);
const native = this.native(bank);
extra += ', native: ' + native.toNumber();
extra += ', ui: ' + this.ui(bank);
extra += ', tokenName: ' + bank.name;

View File

@ -1,10 +1,4 @@
import {
AnchorProvider,
BN,
Program,
Provider,
web3,
} from '@project-serum/anchor';
import { AnchorProvider, BN, Program, Provider } from '@project-serum/anchor';
import { getFeeRates, getFeeTier } from '@project-serum/serum';
import { Order } from '@project-serum/serum/lib/market';
import {
@ -472,20 +466,13 @@ export class MangoClient {
public async getOrCreateMangoAccount(
group: Group,
ownerPk: PublicKey,
payer: web3.Keypair,
accountNumber?: number,
accountSize?: AccountSize,
name?: string,
): Promise<MangoAccount> {
let mangoAccounts = await this.getMangoAccountsForOwner(group, ownerPk);
if (mangoAccounts.length === 0) {
await this.createMangoAccount(
group,
payer,
accountNumber ?? 0,
accountSize ?? AccountSize.small,
name ?? '',
);
await this.createMangoAccount(group, accountNumber, accountSize, name);
mangoAccounts = await this.getMangoAccountsForOwner(group, ownerPk);
}
return mangoAccounts[0];
@ -493,19 +480,21 @@ export class MangoClient {
public async createMangoAccount(
group: Group,
payer: web3.Keypair,
accountNumber: number,
accountSize: AccountSize,
accountNumber?: number,
accountSize?: AccountSize,
name?: string,
): Promise<TransactionSignature> {
return await this.program.methods
.accountCreate(accountNumber, accountSize, name ?? '')
.accountCreate(
accountNumber ?? 0,
accountSize ?? AccountSize.small,
name ?? '',
)
.accounts({
group: group.publicKey,
owner: (this.program.provider as AnchorProvider).wallet.publicKey,
payer: payer.publicKey,
payer: (this.program.provider as AnchorProvider).wallet.publicKey,
})
.signers([payer])
.rpc();
}
@ -631,7 +620,6 @@ export class MangoClient {
mangoAccount: MangoAccount,
tokenName: string,
amount: number,
signer: Signer,
) {
const bank = group.banksMap.get(tokenName)!;
@ -643,7 +631,7 @@ export class MangoClient {
let wrappedSolAccount: Keypair | undefined;
let preInstructions: TransactionInstruction[] = [];
let postInstructions: TransactionInstruction[] = [];
let additionalSigners: Signer[] = [];
const additionalSigners: Signer[] = [];
if (bank.mint.equals(WRAPPED_SOL_MINT)) {
wrappedSolAccount = new Keypair();
const lamports = Math.round(amount * LAMPORTS_PER_SOL) + 1e7;
@ -698,7 +686,7 @@ export class MangoClient {
)
.preInstructions(preInstructions)
.postInstructions(postInstructions)
.signers([signer].concat(additionalSigners))
.signers(additionalSigners)
.rpc({ skipPreflight: true });
}
@ -708,7 +696,6 @@ export class MangoClient {
tokenName: string,
amount: number,
allowBorrow: boolean,
signer: Signer,
) {
const bank = group.banksMap.get(tokenName)!;
@ -741,7 +728,6 @@ export class MangoClient {
({ pubkey: pk, isWritable: false, isSigner: false } as AccountMeta),
),
)
.signers([signer])
.rpc({ skipPreflight: true });
}
@ -751,7 +737,6 @@ export class MangoClient {
tokenName: string,
nativeAmount: number,
allowBorrow: boolean,
signer: Signer,
) {
const bank = group.banksMap.get(tokenName)!;
@ -784,7 +769,6 @@ export class MangoClient {
({ pubkey: pk, isWritable: false, isSigner: false } as AccountMeta),
),
)
.signers([signer])
.rpc({ skipPreflight: true });
}
@ -901,7 +885,7 @@ export class MangoClient {
): Promise<TransactionSignature> {
const serum3Market = group.serum3MarketsMap.get(serum3MarketName)!;
let openOrders = mangoAccount.serum3.find(
const openOrders = mangoAccount.serum3.find(
(account) => account.marketIndex === serum3Market.marketIndex,
)?.openOrders;
@ -1358,7 +1342,7 @@ export class MangoClient {
mangoAccount,
]);
let [nativePrice, nativeQuantity] = perpMarket.uiToNativePriceQuantity(
const [nativePrice, nativeQuantity] = perpMarket.uiToNativePriceQuantity(
price,
quantity,
);
@ -1437,7 +1421,7 @@ export class MangoClient {
/*
* Find or create associated token accounts
*/
let inputTokenAccountPk = await getAssociatedTokenAddress(
const inputTokenAccountPk = await getAssociatedTokenAddress(
inputBank.mint,
mangoAccount.owner,
);
@ -1445,7 +1429,7 @@ export class MangoClient {
await this.program.provider.connection.getAccountInfo(
inputTokenAccountPk,
);
let preInstructions = [];
const preInstructions = [];
if (!inputTokenAccExists) {
preInstructions.push(
Token.createAssociatedTokenAccountInstruction(
@ -1459,7 +1443,7 @@ export class MangoClient {
);
}
let outputTokenAccountPk = await getAssociatedTokenAddress(
const outputTokenAccountPk = await getAssociatedTokenAddress(
outputBank.mint,
mangoAccount.owner,
);
@ -1575,19 +1559,39 @@ export class MangoClient {
return this.program.provider.sendAndConfirm(tx);
}
async updateIndexAndRate(group: Group, tokenName: string) {
let bank = group.banksMap.get(tokenName)!;
let mintInfo = group.mintInfosMap.get(bank.tokenIndex)!;
await this.program.methods.tokenUpdateIndexAndRate().accounts({
'group': group.publicKey,
'mintInfo': mintInfo.publicKey,
'oracle': mintInfo.oracle,
'instructions': SYSVAR_INSTRUCTIONS_PUBKEY})
.remainingAccounts([
{
pubkey: bank.publicKey,
isWritable: true,
isSigner: false,
} as AccountMeta,
])
.rpc()
}
/// liquidations
async liqTokenWithToken(
group: Group,
liqor: MangoAccount,
liqee: MangoAccount,
liqorOwner: Signer,
assetTokenName: string,
liabTokenName: string,
maxLiabTransfer: number,
) {
let assetBank: Bank = group.banksMap.get(assetTokenName);
let liabBank: Bank = group.banksMap.get(liabTokenName);
const assetBank: Bank = group.banksMap.get(assetTokenName);
const liabBank: Bank = group.banksMap.get(liabTokenName);
const healthRemainingAccounts: PublicKey[] =
this.buildHealthRemainingAccounts(
@ -1617,10 +1621,9 @@ export class MangoClient {
group: group.publicKey,
liqor: liqor.publicKey,
liqee: liqee.publicKey,
liqorOwner: liqorOwner.publicKey,
liqorOwner: liqor.owner,
})
.remainingAccounts(parsedHealthAccounts)
.signers([liqorOwner])
.rpc();
}
@ -1634,7 +1637,7 @@ export class MangoClient {
// TODO: use IDL on chain or in repository? decide...
// Alternatively we could fetch IDL from chain.
// const idl = await Program.fetchIdl(MANGO_V4_ID, provider);
let idl = IDL;
const idl = IDL;
return new MangoClient(
new Program<MangoV4>(idl as MangoV4, programId, provider),
@ -1650,7 +1653,7 @@ export class MangoClient {
// TODO: use IDL on chain or in repository? decide...
// Alternatively we could fetch IDL from chain.
// const idl = await Program.fetchIdl(MANGO_V4_ID, provider);
let idl = IDL;
const idl = IDL;
const id = Id.fromIds(groupName);
@ -1706,7 +1709,7 @@ export class MangoClient {
}
}
const mintInfos = [...new Set(tokenIndices.sort())].map(
const mintInfos = [...new Set(tokenIndices)].map(
(tokenIndex) => group.mintInfosMap.get(tokenIndex)!,
);
healthRemainingAccounts.push(
@ -1742,7 +1745,7 @@ export class MangoClient {
const healthRemainingAccounts: PublicKey[] = [];
let tokenIndices = [];
for (let mangoAccount of mangoAccounts) {
for (const mangoAccount of mangoAccounts) {
tokenIndices.push(
...mangoAccount.tokens
.filter((token) => token.tokenIndex !== 65535)
@ -1765,14 +1768,14 @@ export class MangoClient {
healthRemainingAccounts.push(
...mintInfos.map((mintInfo) => mintInfo.oracle),
);
for (let mangoAccount of mangoAccounts) {
for (const mangoAccount of mangoAccounts) {
healthRemainingAccounts.push(
...mangoAccount.serum3
.filter((serum3Account) => serum3Account.marketIndex !== 65535)
.map((serum3Account) => serum3Account.openOrders),
);
}
for (let mangoAccount of mangoAccounts) {
for (const mangoAccount of mangoAccounts) {
healthRemainingAccounts.push(
...mangoAccount.perps
.filter((perp) => perp.marketIndex !== 65535)

View File

@ -50,7 +50,7 @@ export class Id {
}
static fromIds(name: string): Id {
let groupConfig = ids.groups.find((id) => id['name'] === name);
const groupConfig = ids.groups.find((id) => id['name'] === name);
return new Id(
groupConfig.cluster as Cluster,
name,

View File

@ -738,6 +738,11 @@ export type MangoV4 = {
{
"name": "tokenUpdateIndexAndRate",
"accounts": [
{
"name": "group",
"isMut": false,
"isSigner": false
},
{
"name": "mintInfo",
"isMut": false,
@ -2572,6 +2577,11 @@ export type MangoV4 = {
{
"name": "perpUpdateFunding",
"accounts": [
{
"name": "group",
"isMut": false,
"isSigner": false
},
{
"name": "perpMarket",
"isMut": true,
@ -2820,7 +2830,7 @@ export type MangoV4 = {
}
},
{
"name": "flashLoanVaultInitial",
"name": "flashLoanTokenAccountInitial",
"type": "u64"
},
{
@ -4440,6 +4450,11 @@ export type MangoV4 = {
{
"name": "PerpBalanceLog",
"fields": [
{
"name": "mangoGroup",
"type": "publicKey",
"index": false
},
{
"name": "mangoAccount",
"type": "publicKey",
@ -4490,6 +4505,11 @@ export type MangoV4 = {
{
"name": "TokenBalanceLog",
"fields": [
{
"name": "mangoGroup",
"type": "publicKey",
"index": false
},
{
"name": "mangoAccount",
"type": "publicKey",
@ -4523,39 +4543,13 @@ export type MangoV4 = {
]
},
{
"name": "MarginTradeLog",
"name": "FlashLoanLog",
"fields": [
{
"name": "mangoAccount",
"name": "mangoGroup",
"type": "publicKey",
"index": false
},
{
"name": "tokenIndexes",
"type": {
"vec": "u16"
},
"index": false
},
{
"name": "preIndexedPositions",
"type": {
"vec": "i128"
},
"index": false
},
{
"name": "postIndexedPositions",
"type": {
"vec": "i128"
},
"index": false
}
]
},
{
"name": "FlashLoanLog",
"fields": [
{
"name": "mangoAccount",
"type": "publicKey",
@ -4575,6 +4569,11 @@ export type MangoV4 = {
{
"name": "WithdrawLog",
"fields": [
{
"name": "mangoGroup",
"type": "publicKey",
"index": false
},
{
"name": "mangoAccount",
"type": "publicKey",
@ -4605,6 +4604,11 @@ export type MangoV4 = {
{
"name": "DepositLog",
"fields": [
{
"name": "mangoGroup",
"type": "publicKey",
"index": false
},
{
"name": "mangoAccount",
"type": "publicKey",
@ -4830,6 +4834,11 @@ export type MangoV4 = {
{
"name": "LiquidateTokenAndTokenLog",
"fields": [
{
"name": "mangoGroup",
"type": "publicKey",
"index": false
},
{
"name": "liqee",
"type": "publicKey",
@ -5742,6 +5751,11 @@ export const IDL: MangoV4 = {
{
"name": "tokenUpdateIndexAndRate",
"accounts": [
{
"name": "group",
"isMut": false,
"isSigner": false
},
{
"name": "mintInfo",
"isMut": false,
@ -7576,6 +7590,11 @@ export const IDL: MangoV4 = {
{
"name": "perpUpdateFunding",
"accounts": [
{
"name": "group",
"isMut": false,
"isSigner": false
},
{
"name": "perpMarket",
"isMut": true,
@ -7824,7 +7843,7 @@ export const IDL: MangoV4 = {
}
},
{
"name": "flashLoanVaultInitial",
"name": "flashLoanTokenAccountInitial",
"type": "u64"
},
{
@ -9444,6 +9463,11 @@ export const IDL: MangoV4 = {
{
"name": "PerpBalanceLog",
"fields": [
{
"name": "mangoGroup",
"type": "publicKey",
"index": false
},
{
"name": "mangoAccount",
"type": "publicKey",
@ -9494,6 +9518,11 @@ export const IDL: MangoV4 = {
{
"name": "TokenBalanceLog",
"fields": [
{
"name": "mangoGroup",
"type": "publicKey",
"index": false
},
{
"name": "mangoAccount",
"type": "publicKey",
@ -9527,39 +9556,13 @@ export const IDL: MangoV4 = {
]
},
{
"name": "MarginTradeLog",
"name": "FlashLoanLog",
"fields": [
{
"name": "mangoAccount",
"name": "mangoGroup",
"type": "publicKey",
"index": false
},
{
"name": "tokenIndexes",
"type": {
"vec": "u16"
},
"index": false
},
{
"name": "preIndexedPositions",
"type": {
"vec": "i128"
},
"index": false
},
{
"name": "postIndexedPositions",
"type": {
"vec": "i128"
},
"index": false
}
]
},
{
"name": "FlashLoanLog",
"fields": [
{
"name": "mangoAccount",
"type": "publicKey",
@ -9579,6 +9582,11 @@ export const IDL: MangoV4 = {
{
"name": "WithdrawLog",
"fields": [
{
"name": "mangoGroup",
"type": "publicKey",
"index": false
},
{
"name": "mangoAccount",
"type": "publicKey",
@ -9609,6 +9617,11 @@ export const IDL: MangoV4 = {
{
"name": "DepositLog",
"fields": [
{
"name": "mangoGroup",
"type": "publicKey",
"index": false
},
{
"name": "mangoAccount",
"type": "publicKey",
@ -9834,6 +9847,11 @@ export const IDL: MangoV4 = {
{
"name": "LiquidateTokenAndTokenLog",
"fields": [
{
"name": "mangoGroup",
"type": "publicKey",
"index": false
},
{
"name": "liqee",
"type": "publicKey",

View File

@ -1,7 +1,6 @@
import { AnchorProvider, Wallet } from '@project-serum/anchor';
import { Connection, Keypair } from '@solana/web3.js';
import fs from 'fs';
import { AccountSize } from '../accounts/mangoAccount';
import { MangoClient } from '../client';
import { MANGO_V4_ID } from '../constants';
@ -44,10 +43,6 @@ async function main() {
const mangoAccount = await client.getOrCreateMangoAccount(
group,
user.publicKey,
user,
0,
AccountSize.small,
'my_mango_account',
);
console.log(`...created/found mangoAccount ${mangoAccount.publicKey}`);
console.log(mangoAccount.toString());
@ -55,11 +50,11 @@ async function main() {
if (false) {
// deposit and withdraw
console.log(`Depositing...50 USDC`);
await client.tokenDeposit(group, mangoAccount, 'USDC', 50, user);
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, user);
await client.tokenDeposit(group, mangoAccount, 'BTC', 0.0005);
await mangoAccount.reload(client, group);
}
try {

View File

@ -92,11 +92,11 @@ async function main() {
if (true) {
// deposit
console.log(`...depositing 50 USDC`);
await client.tokenDeposit(group, mangoAccount, 'USDC', 50, user);
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, user);
await client.tokenDeposit(group, mangoAccount, 'BTC', 0.0005);
await mangoAccount.reload(client, group);
// serum3

View File

@ -1,7 +1,7 @@
import { AnchorProvider, Wallet } from '@project-serum/anchor';
import { Connection, Keypair, PublicKey } from '@solana/web3.js';
import fs from 'fs';
import { AccountSize, HealthType } from '../accounts/mangoAccount';
import { HealthType } from '../accounts/mangoAccount';
import { OrderType, Side } from '../accounts/perp';
import {
Serum3OrderType,
@ -50,7 +50,10 @@ async function main() {
JSON.parse(fs.readFileSync(process.env.ADMIN_KEYPAIR!, 'utf-8')),
),
);
const group = await client.getGroupForCreator(admin.publicKey, GROUP_NUM);
// const group = await client.getGroupForCreator(admin.publicKey, GROUP_NUM);
const group = await client.getGroup(
new PublicKey('FdynL6q7CNJMMiTZpfnYVkqQRYaoiBWgWkFYvvpx9uA8'),
);
console.log(group.toString());
// create + fetch account
@ -58,10 +61,6 @@ async function main() {
const mangoAccount = await client.getOrCreateMangoAccount(
group,
user.publicKey,
user,
0,
AccountSize.small,
'my_mango_account',
);
console.log(`...created/found mangoAccount ${mangoAccount.publicKey}`);
console.log(mangoAccount.toString());
@ -98,11 +97,15 @@ async function main() {
try {
console.log(`...depositing 50 USDC`);
await client.tokenDeposit(group, mangoAccount, 'USDC', 50, user);
await client.tokenDeposit(group, mangoAccount, 'USDC', 50);
await mangoAccount.reload(client, group);
console.log(`...withdrawing 1 USDC`);
await client.tokenWithdraw(group, mangoAccount, 'USDC', 1, true);
await mangoAccount.reload(client, group);
console.log(`...depositing 0.0005 BTC`);
await client.tokenDeposit(group, mangoAccount, 'BTC', 0.0005, user);
await client.tokenDeposit(group, mangoAccount, 'BTC', 0.0005);
await mangoAccount.reload(client, group);
} catch (error) {
console.log(error);

View File

@ -1,7 +1,7 @@
import { AnchorProvider, Wallet } from '@project-serum/anchor';
import { Connection, Keypair } from '@solana/web3.js';
import fs from 'fs';
import { AccountSize, HealthType } from '../accounts/mangoAccount';
import { HealthType } from '../accounts/mangoAccount';
import { MangoClient } from '../client';
import { MANGO_V4_ID } from '../constants';
import { toUiDecimals } from '../utils';
@ -43,9 +43,6 @@ async function main() {
const mangoAccount = await client.getOrCreateMangoAccount(
group,
user.publicKey,
0,
AccountSize.small,
'my_mango_account',
);
console.log(`...created/found mangoAccount ${mangoAccount.publicKey}`);
console.log(mangoAccount.toString());

View File

@ -101,7 +101,6 @@ async function main() {
group.findBank(token.tokenIndex)!.name,
token.native(group.findBank(token.tokenIndex)!).toNumber(),
false,
user,
);
}
} catch (error) {

View File

@ -45,21 +45,17 @@ async function main() {
const mangoAccount = await client.getOrCreateMangoAccount(
group,
user.publicKey,
user,
0,
AccountSize.small,
'my_mango_account',
);
console.log(`...created/found mangoAccount ${mangoAccount.publicKey}`);
console.log(mangoAccount.toString(group));
if (false) {
console.log(`...depositing 10 USDC`);
await client.tokenDeposit(group, mangoAccount, 'USDC', 10, user);
await client.tokenDeposit(group, mangoAccount, 'USDC', 10);
await mangoAccount.reload(client, group);
console.log(`...depositing 1 SOL`);
await client.tokenDeposit(group, mangoAccount, 'SOL', 1, user);
await client.tokenDeposit(group, mangoAccount, 'SOL', 1);
await mangoAccount.reload(client, group);
}

View File

@ -60,10 +60,6 @@ async function main() {
const mangoAccount = await client.getOrCreateMangoAccount(
group,
user.publicKey,
user,
0,
AccountSize.small,
'my_mango_account',
);
console.log(`...created/found mangoAccount ${mangoAccount.publicKey}`);
console.log(`start balance \n${mangoAccount.toString(group)}`);

View File

@ -40,10 +40,6 @@ async function main() {
const user1MangoAccount = await user1Client.getOrCreateMangoAccount(
group,
user1.publicKey,
user1,
0,
AccountSize.small,
'my_mango_account',
);
console.log(`...mangoAccount1 ${user1MangoAccount.publicKey}`);
@ -52,7 +48,7 @@ async function main() {
let amount = 0.001;
let token = 'BTC';
console.log(`Depositing...${amount} 'BTC'`);
await user1Client.tokenDeposit(group, user1MangoAccount, token, amount, user1);
await user1Client.tokenDeposit(group, user1MangoAccount, token, amount);
await user1MangoAccount.reload(user1Client, group);
console.log(`${user1MangoAccount.toString(group)}`);
@ -74,10 +70,6 @@ async function main() {
const user2MangoAccount = await user2Client.getOrCreateMangoAccount(
group,
user2.publicKey,
user2,
0,
AccountSize.small,
'my_mango_account',
);
console.log(`...mangoAccount2 ${user2MangoAccount.publicKey}`);
@ -98,7 +90,7 @@ async function main() {
/// user2 deposits some collateral and borrows BTC
amount = 1;
console.log(`Depositing...${amount} 'USDC'`);
await user2Client.tokenDeposit(group, user2MangoAccount, 'USDC', amount, user2);
await user2Client.tokenDeposit(group, user2MangoAccount, 'USDC', amount);
await user2MangoAccount.reload(user2Client, group);
console.log(`${user2MangoAccount.toString(group)}`);
@ -113,7 +105,6 @@ async function main() {
token,
amount,
true,
user2
);
await user2MangoAccount.reload(user2Client, group);
console.log(`${user2MangoAccount.toString(group)}`);

View File

@ -45,10 +45,6 @@ async function main() {
const mangoAccount = await client.getOrCreateMangoAccount(
group,
user.publicKey,
user,
0,
AccountSize.small,
'my_mango_account',
);
console.log(`...created/found mangoAccount ${mangoAccount.publicKey}`);

View File

@ -46,10 +46,6 @@ async function main() {
const mangoAccount = await client.getOrCreateMangoAccount(
group,
user.publicKey,
user,
0,
AccountSize.small,
'my_mango_account',
);
console.log(`...created/found mangoAccount ${mangoAccount.publicKey}`);

View File

@ -23,9 +23,9 @@ export function debugAccountMetas(ams: AccountMeta[]) {
export async function findOrCreate<T>(
entityName: string,
findMethod: Function,
findMethod: (...x: any) => any,
findArgs: any[],
createMethod: Function,
createMethod: (...x: any) => any,
createArgs: any[],
): Promise<T> {
let many: T[] = await findMethod(...findArgs);

4540
yarn.lock

File diff suppressed because it is too large Load Diff