Merge branch 'main' into feature/modify-order
This commit is contained in:
commit
8785d007bd
|
@ -21,7 +21,7 @@ on:
|
|||
|
||||
env:
|
||||
CARGO_TERM_COLOR: always
|
||||
SOLANA_VERSION: '1.13.3'
|
||||
SOLANA_VERSION: '1.14.9'
|
||||
RUST_TOOLCHAIN: '1.60.0'
|
||||
LOG_PROGRAM: 'm43thNJ58XCjL798ZSq6JGAG1BnWskhdq5or6kcnfsD'
|
||||
|
||||
|
|
|
@ -2,3 +2,6 @@
|
|||
path = anchor
|
||||
url = https://github.com/blockworks-foundation/anchor.git
|
||||
branch = v0.25.0-mangov4
|
||||
[submodule "switchboard-v2"]
|
||||
path = switchboard-v2
|
||||
url = https://github.com/blockworks-foundation/sbv2-solana.git
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -12,7 +12,8 @@ members = [
|
|||
[patch.crates-io]
|
||||
# for gzip encoded responses
|
||||
jsonrpc-core-client = { git = "https://github.com/ckamm/jsonrpc.git", branch = "ckamm/http-with-gzip" }
|
||||
# these patch anchor to use solana 1.10.35 in order to fix dependency conflicts in switchboard-v2
|
||||
# these patch anchor to use solana 1.14.9 in order to fix dependency conflicts in switchboard-v2
|
||||
# downstream applications may need to copy these patches to their own workspace
|
||||
anchor-spl = { path = "./anchor/spl/" }
|
||||
anchor-lang = { path = "./anchor/lang/" }
|
||||
switchboard-v2 = { path = "./switchboard-v2/rust/switchboard-v2/" }
|
||||
|
|
2
anchor
2
anchor
|
@ -1 +1 @@
|
|||
Subproject commit 3f8373770c51e105730b0bfd65208d3ba704c9ec
|
||||
Subproject commit 309c2c2f4cce7c0a13d307fab3c7e2985bff3fa5
|
|
@ -21,6 +21,6 @@ log = "0.4.0"
|
|||
mango-v4 = { path = "../programs/mango-v4", features = ["no-entrypoint", "client"] }
|
||||
pyth-sdk-solana = "0.1.0"
|
||||
serum_dex = { version = "0.5.6", git = "https://github.com/blockworks-foundation/serum-dex.git", default-features=false,features = ["no-entrypoint", "program"], branch = "ckamm/find_by_key" }
|
||||
solana-client = "~1.10.35"
|
||||
solana-sdk = "~1.10.35"
|
||||
solana-client = "~1.14.9"
|
||||
solana-sdk = "~1.14.9"
|
||||
tokio = { version = "1.14.1", features = ["rt-multi-thread", "time", "macros", "sync"] }
|
||||
|
|
|
@ -18,9 +18,9 @@ mango-v4 = { path = "../programs/mango-v4", features = ["no-entrypoint", "client
|
|||
pyth-sdk-solana = "0.1.0"
|
||||
serum_dex = { version = "0.5.6", git = "https://github.com/blockworks-foundation/serum-dex.git", default-features=false,features = ["no-entrypoint", "program"], branch = "ckamm/find_by_key" }
|
||||
shellexpand = "2.1.0"
|
||||
solana-account-decoder = "~1.10.35"
|
||||
solana-client = "~1.10.35"
|
||||
solana-sdk = "~1.10.35"
|
||||
solana-account-decoder = "~1.14.9"
|
||||
solana-client = "~1.14.9"
|
||||
solana-sdk = "~1.14.9"
|
||||
spl-associated-token-account = "1.0.3"
|
||||
thiserror = "1.0.31"
|
||||
log = "0.4"
|
||||
|
|
|
@ -65,15 +65,12 @@ impl AccountFetcher for RpcAccountFetcher {
|
|||
) -> anyhow::Result<Vec<(Pubkey, AccountSharedData)>> {
|
||||
use solana_account_decoder::UiAccountEncoding;
|
||||
use solana_client::rpc_config::{RpcAccountInfoConfig, RpcProgramAccountsConfig};
|
||||
use solana_client::rpc_filter::{
|
||||
Memcmp, MemcmpEncodedBytes, MemcmpEncoding, RpcFilterType,
|
||||
};
|
||||
use solana_client::rpc_filter::{Memcmp, RpcFilterType};
|
||||
let config = RpcProgramAccountsConfig {
|
||||
filters: Some(vec![RpcFilterType::Memcmp(Memcmp {
|
||||
offset: 0,
|
||||
bytes: MemcmpEncodedBytes::Bytes(discriminator.to_vec()),
|
||||
encoding: Some(MemcmpEncoding::Binary),
|
||||
})]),
|
||||
filters: Some(vec![RpcFilterType::Memcmp(Memcmp::new_raw_bytes(
|
||||
0,
|
||||
discriminator.to_vec(),
|
||||
))]),
|
||||
account_config: RpcAccountInfoConfig {
|
||||
encoding: Some(UiAccountEncoding::Base64),
|
||||
commitment: Some(self.rpc.commitment()),
|
||||
|
|
|
@ -5,7 +5,7 @@ use mango_v4::state::{Bank, MangoAccount, MangoAccountValue, MintInfo, PerpMarke
|
|||
|
||||
use solana_account_decoder::UiAccountEncoding;
|
||||
use solana_client::rpc_config::{RpcAccountInfoConfig, RpcProgramAccountsConfig};
|
||||
use solana_client::rpc_filter::{Memcmp, MemcmpEncodedBytes, RpcFilterType};
|
||||
use solana_client::rpc_filter::{Memcmp, RpcFilterType};
|
||||
use solana_sdk::pubkey::Pubkey;
|
||||
|
||||
pub fn fetch_mango_accounts(
|
||||
|
@ -15,21 +15,12 @@ pub fn fetch_mango_accounts(
|
|||
) -> Result<Vec<(Pubkey, MangoAccountValue)>, ClientError> {
|
||||
let config = RpcProgramAccountsConfig {
|
||||
filters: Some(vec![
|
||||
RpcFilterType::Memcmp(Memcmp {
|
||||
offset: 0,
|
||||
bytes: MemcmpEncodedBytes::Bytes(MangoAccount::discriminator().to_vec()),
|
||||
encoding: None,
|
||||
}),
|
||||
RpcFilterType::Memcmp(Memcmp {
|
||||
offset: 8,
|
||||
bytes: MemcmpEncodedBytes::Base58(group.to_string()),
|
||||
encoding: None,
|
||||
}),
|
||||
RpcFilterType::Memcmp(Memcmp {
|
||||
offset: 40,
|
||||
bytes: MemcmpEncodedBytes::Base58(owner.to_string()),
|
||||
encoding: None,
|
||||
}),
|
||||
RpcFilterType::Memcmp(Memcmp::new_base58_encoded(
|
||||
0,
|
||||
&MangoAccount::discriminator(),
|
||||
)),
|
||||
RpcFilterType::Memcmp(Memcmp::new_base58_encoded(8, &group.to_bytes())),
|
||||
RpcFilterType::Memcmp(Memcmp::new_base58_encoded(40, &owner.to_bytes())),
|
||||
]),
|
||||
account_config: RpcAccountInfoConfig {
|
||||
encoding: Some(UiAccountEncoding::Base64),
|
||||
|
@ -46,42 +37,38 @@ pub fn fetch_mango_accounts(
|
|||
}
|
||||
|
||||
pub fn fetch_banks(program: &Program, group: Pubkey) -> Result<Vec<(Pubkey, Bank)>, ClientError> {
|
||||
program.accounts::<Bank>(vec![RpcFilterType::Memcmp(Memcmp {
|
||||
offset: 8,
|
||||
bytes: MemcmpEncodedBytes::Base58(group.to_string()),
|
||||
encoding: None,
|
||||
})])
|
||||
program.accounts::<Bank>(vec![RpcFilterType::Memcmp(Memcmp::new_base58_encoded(
|
||||
8,
|
||||
&group.to_bytes(),
|
||||
))])
|
||||
}
|
||||
|
||||
pub fn fetch_mint_infos(
|
||||
program: &Program,
|
||||
group: Pubkey,
|
||||
) -> Result<Vec<(Pubkey, MintInfo)>, ClientError> {
|
||||
program.accounts::<MintInfo>(vec![RpcFilterType::Memcmp(Memcmp {
|
||||
offset: 8,
|
||||
bytes: MemcmpEncodedBytes::Base58(group.to_string()),
|
||||
encoding: None,
|
||||
})])
|
||||
program.accounts::<MintInfo>(vec![RpcFilterType::Memcmp(Memcmp::new_base58_encoded(
|
||||
8,
|
||||
&group.to_bytes(),
|
||||
))])
|
||||
}
|
||||
|
||||
pub fn fetch_serum3_markets(
|
||||
program: &Program,
|
||||
group: Pubkey,
|
||||
) -> Result<Vec<(Pubkey, Serum3Market)>, ClientError> {
|
||||
program.accounts::<Serum3Market>(vec![RpcFilterType::Memcmp(Memcmp {
|
||||
offset: 8,
|
||||
bytes: MemcmpEncodedBytes::Base58(group.to_string()),
|
||||
encoding: None,
|
||||
})])
|
||||
program.accounts::<Serum3Market>(vec![RpcFilterType::Memcmp(Memcmp::new_base58_encoded(
|
||||
8,
|
||||
&group.to_bytes(),
|
||||
))])
|
||||
}
|
||||
|
||||
pub fn fetch_perp_markets(
|
||||
program: &Program,
|
||||
group: Pubkey,
|
||||
) -> Result<Vec<(Pubkey, PerpMarket)>, ClientError> {
|
||||
program.accounts::<PerpMarket>(vec![RpcFilterType::Memcmp(Memcmp {
|
||||
offset: 8,
|
||||
bytes: MemcmpEncodedBytes::Base58(group.to_string()),
|
||||
encoding: None,
|
||||
})])
|
||||
program.accounts::<PerpMarket>(vec![RpcFilterType::Memcmp(Memcmp::new_base58_encoded(
|
||||
8,
|
||||
&group.to_bytes(),
|
||||
))])
|
||||
}
|
||||
|
|
13
idl-fixup.sh
13
idl-fixup.sh
|
@ -19,3 +19,16 @@ done
|
|||
# errors on enums that have tuple variants. This hack drops these from the idl.
|
||||
perl -0777 -pi -e 's/ *{\s*"name": "NodeRef(?<nested>(?:[^{}[\]]+|\{(?&nested)\}|\[(?&nested)\])*)\},\n//g' \
|
||||
target/idl/mango_v4.json target/types/mango_v4.ts;
|
||||
|
||||
# Reduce size of idl to be uploaded to chain
|
||||
cp target/idl/mango_v4.json target/idl/mango_v4_no_docs.json
|
||||
jq 'del(.types[]?.docs)' target/idl/mango_v4_no_docs.json \
|
||||
> target/idl/mango_v4_no_docs.json.bak && mv target/idl/mango_v4_no_docs.json.bak target/idl/mango_v4_no_docs.json
|
||||
jq 'del(.accounts[]?.type.fields[]?.docs)' target/idl/mango_v4_no_docs.json \
|
||||
> target/idl/mango_v4_no_docs.json.bak && mv target/idl/mango_v4_no_docs.json.bak target/idl/mango_v4_no_docs.json
|
||||
jq 'del(.instructions[]?.docs)' target/idl/mango_v4_no_docs.json \
|
||||
> target/idl/mango_v4_no_docs.json.bak && mv target/idl/mango_v4_no_docs.json.bak target/idl/mango_v4_no_docs.json
|
||||
jq 'del(.instructions[]?.accounts[]?.docs)' target/idl/mango_v4_no_docs.json \
|
||||
> target/idl/mango_v4_no_docs.json.bak && mv target/idl/mango_v4_no_docs.json.bak target/idl/mango_v4_no_docs.json
|
||||
jq 'del(.types[]?.type.fields[]?.docs)' target/idl/mango_v4_no_docs.json \
|
||||
> target/idl/mango_v4_no_docs.json.bak && mv target/idl/mango_v4_no_docs.json.bak target/idl/mango_v4_no_docs.json
|
||||
|
|
|
@ -22,6 +22,6 @@ log = "0.4.0"
|
|||
mango-v4 = { path = "../programs/mango-v4", features = ["no-entrypoint", "client"] }
|
||||
pyth-sdk-solana = "0.1.0"
|
||||
serum_dex = { version = "0.5.6", git = "https://github.com/blockworks-foundation/serum-dex.git", default-features=false,features = ["no-entrypoint", "program"], branch = "ckamm/find_by_key" }
|
||||
solana-client = "~1.10.35"
|
||||
solana-sdk = "~1.10.35"
|
||||
solana-client = "~1.14.9"
|
||||
solana-sdk = "~1.14.9"
|
||||
tokio = { version = "1.14.1", features = ["rt-multi-thread", "time", "macros", "sync"] }
|
||||
|
|
|
@ -36,11 +36,11 @@ serde_derive = "1.0.130"
|
|||
serde_json = "1.0.68"
|
||||
serum_dex = { version = "0.5.6", git = "https://github.com/blockworks-foundation/serum-dex.git", default-features=false,features = ["no-entrypoint", "program"], branch = "ckamm/find_by_key" }
|
||||
shellexpand = "2.1.0"
|
||||
solana-account-decoder = "~1.10.35"
|
||||
solana-client = "~1.10.35"
|
||||
solana-logger = "~1.10.35"
|
||||
solana-rpc = "~1.10.35"
|
||||
solana-sdk = "~1.10.35"
|
||||
solana-account-decoder = "~1.14.9"
|
||||
solana-client = "~1.14.9"
|
||||
solana-logger = "~1.14.9"
|
||||
solana-rpc = "~1.14.9"
|
||||
solana-sdk = "~1.14.9"
|
||||
tokio = { version = "1", features = ["full"] }
|
||||
tokio-stream = { version = "0.1.9"}
|
||||
tokio-tungstenite = "0.16.1"
|
||||
|
|
|
@ -3,12 +3,9 @@ use jsonrpc_core_client::transports::http;
|
|||
use solana_account_decoder::{UiAccount, UiAccountEncoding};
|
||||
use solana_client::{
|
||||
rpc_config::{RpcAccountInfoConfig, RpcContextConfig, RpcProgramAccountsConfig},
|
||||
rpc_response::{Response, RpcKeyedAccount},
|
||||
};
|
||||
use solana_rpc::{
|
||||
rpc::OptionalContext,
|
||||
rpc::{rpc_accounts::AccountsDataClient, rpc_minimal::MinimalClient},
|
||||
rpc_response::{OptionalContext, Response, RpcKeyedAccount},
|
||||
};
|
||||
use solana_rpc::rpc::{rpc_accounts::AccountsDataClient, rpc_minimal::MinimalClient};
|
||||
use solana_sdk::{account::AccountSharedData, commitment_config::CommitmentConfig, pubkey::Pubkey};
|
||||
|
||||
use anyhow::Context;
|
||||
|
|
|
@ -4,7 +4,7 @@ use jsonrpc_core_client::transports::ws;
|
|||
use solana_account_decoder::UiAccountEncoding;
|
||||
use solana_client::{
|
||||
rpc_config::{RpcAccountInfoConfig, RpcProgramAccountsConfig},
|
||||
rpc_filter::{Memcmp, MemcmpEncodedBytes, RpcFilterType},
|
||||
rpc_filter::{Memcmp, RpcFilterType},
|
||||
rpc_response::{Response, RpcKeyedAccount, RpcResponseContext},
|
||||
};
|
||||
use solana_rpc::rpc_pubsub::RpcSolPubSubClient;
|
||||
|
@ -78,17 +78,16 @@ async fn feed_data(
|
|||
// filter for only OpenOrders with v4 authority
|
||||
filters: Some(vec![
|
||||
RpcFilterType::DataSize(3228), // open orders size
|
||||
RpcFilterType::Memcmp(Memcmp {
|
||||
offset: 0,
|
||||
// "serum" + u64 that is Initialized (1) + OpenOrders (4)
|
||||
bytes: MemcmpEncodedBytes::Base58("AcUQf4PGf6fCHGwmpB".into()),
|
||||
encoding: None,
|
||||
}),
|
||||
RpcFilterType::Memcmp(Memcmp {
|
||||
offset: 45, // owner is the 4th field, after "serum" (header), account_flags: u64 and market: Pubkey
|
||||
bytes: MemcmpEncodedBytes::Bytes(config.open_orders_authority.to_bytes().into()),
|
||||
encoding: None,
|
||||
}),
|
||||
// "serum" + u64 that is Initialized (1) + OpenOrders (4)
|
||||
RpcFilterType::Memcmp(Memcmp::new_raw_bytes(
|
||||
// new_base58_encoded() does not work with old RPC nodes
|
||||
0,
|
||||
[0x73, 0x65, 0x72, 0x75, 0x6d, 5, 0, 0, 0, 0, 0, 0, 0].to_vec(),
|
||||
)),
|
||||
RpcFilterType::Memcmp(Memcmp::new_raw_bytes(
|
||||
45,
|
||||
config.open_orders_authority.to_bytes().to_vec(),
|
||||
)),
|
||||
]),
|
||||
with_context: Some(true),
|
||||
account_config: account_info_config.clone(),
|
||||
|
|
|
@ -19,7 +19,7 @@
|
|||
"build": "npm run build:esm; npm run build:cjs",
|
||||
"build:cjs": "tsc -p tsconfig.cjs.json",
|
||||
"build:esm": "tsc -p tsconfig.esm.json",
|
||||
"test": "ts-mocha ts/client/**/*.spec.ts --timeout 10000",
|
||||
"test": "ts-mocha ts/client/**/*.spec.ts --timeout 15000",
|
||||
"clean": "rm -rf dist",
|
||||
"example1-user": "ts-node ts/client/src/scripts/example1-user.ts",
|
||||
"example1-admin": "ts-node ts/client/src/scripts/example1-admin.ts",
|
||||
|
|
|
@ -35,18 +35,17 @@ num_enum = "0.5.1"
|
|||
pyth-sdk-solana = "0.1.0"
|
||||
serde = "^1.0"
|
||||
serum_dex = { version = "0.5.6", git = "https://github.com/blockworks-foundation/serum-dex.git", default-features=false,features = ["no-entrypoint", "program"], branch = "ckamm/find_by_key" }
|
||||
solana-address-lookup-table-program = "~1.10.35"
|
||||
solana-program = "~1.10.35"
|
||||
solana-sdk = { version = "~1.10.35", default-features = false, optional = true }
|
||||
solana-address-lookup-table-program = "~1.14.9"
|
||||
solana-program = "~1.14.9"
|
||||
solana-sdk = { version = "~1.14.9", default-features = false, optional = true }
|
||||
static_assertions = "1.1"
|
||||
switchboard-program = ">=0.2.0"
|
||||
switchboard-utils = ">=0.1.36"
|
||||
switchboard-v2 = "0.1.12"
|
||||
switchboard-v2 = "0.1.17"
|
||||
|
||||
[dev-dependencies]
|
||||
solana-sdk = { version = "~1.10.35", default-features = false }
|
||||
solana-program-test = "~1.10.35"
|
||||
solana-logger = "~1.10.35"
|
||||
solana-sdk = { version = "~1.14.9", default-features = false }
|
||||
solana-program-test = "~1.14.9"
|
||||
solana-logger = "~1.14.9"
|
||||
spl-token = { version = "^3.0.0", features = ["no-entrypoint"] }
|
||||
spl-associated-token-account = { version = "^1.0.3", features = ["no-entrypoint"] }
|
||||
bincode = "^1.3.1"
|
||||
|
|
|
@ -15,7 +15,7 @@ use super::*;
|
|||
const ONE_NATIVE_USDC_IN_USD: I80F48 = I80F48!(0.000001);
|
||||
|
||||
/// Information about prices for a bank or perp market.
|
||||
#[derive(Clone, AnchorDeserialize, AnchorSerialize)]
|
||||
#[derive(Clone, AnchorDeserialize, AnchorSerialize, Debug)]
|
||||
pub struct Prices {
|
||||
/// The current oracle price
|
||||
pub oracle: I80F48, // native/native
|
||||
|
@ -87,7 +87,7 @@ pub fn compute_health(
|
|||
Ok(new_health_cache(account, retriever)?.health(health_type))
|
||||
}
|
||||
|
||||
#[derive(Clone, AnchorDeserialize, AnchorSerialize)]
|
||||
#[derive(Clone, AnchorDeserialize, AnchorSerialize, Debug)]
|
||||
pub struct TokenInfo {
|
||||
pub token_index: TokenIndex,
|
||||
pub maint_asset_weight: I80F48,
|
||||
|
@ -129,7 +129,7 @@ impl TokenInfo {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, AnchorDeserialize, AnchorSerialize)]
|
||||
#[derive(Clone, AnchorDeserialize, AnchorSerialize, Debug)]
|
||||
pub struct Serum3Info {
|
||||
// reserved amounts as stored on the open orders
|
||||
pub reserved_base: I80F48,
|
||||
|
@ -208,7 +208,7 @@ struct Serum3Reserved {
|
|||
all_reserved_as_quote: I80F48,
|
||||
}
|
||||
|
||||
#[derive(Clone, AnchorDeserialize, AnchorSerialize)]
|
||||
#[derive(Clone, AnchorDeserialize, AnchorSerialize, Debug)]
|
||||
pub struct PerpInfo {
|
||||
pub perp_market_index: PerpMarketIndex,
|
||||
pub maint_asset_weight: I80F48,
|
||||
|
@ -313,7 +313,7 @@ impl PerpInfo {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, AnchorDeserialize, AnchorSerialize)]
|
||||
#[derive(Clone, AnchorDeserialize, AnchorSerialize, Debug)]
|
||||
pub struct HealthCache {
|
||||
pub(crate) token_infos: Vec<TokenInfo>,
|
||||
pub(crate) serum3_infos: Vec<Serum3Info>,
|
||||
|
|
|
@ -206,18 +206,36 @@ impl HealthCache {
|
|||
// - source_liab_weight * source_liab_price * a
|
||||
// + target_asset_weight * target_asset_price * price * a = 0.
|
||||
// where a is the source token native amount.
|
||||
// Note that this is just an estimate. Swapping can increase the amount that serum3
|
||||
// reserved contributions offset, moving the actual zero point further to the right.
|
||||
if point1_health <= 0 {
|
||||
return Ok(I80F48::ZERO);
|
||||
}
|
||||
let zero_health_amount = point1_amount - point1_health / final_health_slope;
|
||||
binary_search(
|
||||
point1_amount,
|
||||
point1_ratio,
|
||||
zero_health_amount,
|
||||
let zero_health_estimate = point1_amount - point1_health / final_health_slope;
|
||||
let right_bound = scan_right_until_less_than(
|
||||
zero_health_estimate,
|
||||
min_ratio,
|
||||
I80F48::from_num(0.1),
|
||||
health_ratio_after_swap,
|
||||
)?
|
||||
)?;
|
||||
if right_bound == zero_health_estimate {
|
||||
binary_search(
|
||||
point1_amount,
|
||||
point1_ratio,
|
||||
right_bound,
|
||||
min_ratio,
|
||||
I80F48::from_num(0.1),
|
||||
health_ratio_after_swap,
|
||||
)?
|
||||
} else {
|
||||
binary_search(
|
||||
zero_health_estimate,
|
||||
health_ratio_after_swap(zero_health_estimate)?,
|
||||
right_bound,
|
||||
min_ratio,
|
||||
I80F48::from_num(0.1),
|
||||
health_ratio_after_swap,
|
||||
)?
|
||||
}
|
||||
} else if point0_ratio >= min_ratio {
|
||||
// Must be between point0_amount and point1_amount.
|
||||
binary_search(
|
||||
|
@ -373,6 +391,25 @@ impl HealthCache {
|
|||
}
|
||||
}
|
||||
|
||||
fn scan_right_until_less_than(
|
||||
start: I80F48,
|
||||
target: I80F48,
|
||||
fun: impl Fn(I80F48) -> Result<I80F48>,
|
||||
) -> Result<I80F48> {
|
||||
let max_iterations = 20;
|
||||
let mut current = start;
|
||||
for _ in 0..max_iterations {
|
||||
let value = fun(current)?;
|
||||
if value <= target {
|
||||
return Ok(current);
|
||||
}
|
||||
current = current.max(I80F48::ONE) * I80F48::from(2);
|
||||
}
|
||||
Err(error_msg!(
|
||||
"could not find amount that lead to health ratio <= 0"
|
||||
))
|
||||
}
|
||||
|
||||
fn binary_search(
|
||||
mut left: I80F48,
|
||||
left_value: I80F48,
|
||||
|
@ -536,12 +573,11 @@ mod tests {
|
|||
.map(|c| c.health_ratio(HealthType::Init).to_num::<f64>())
|
||||
.unwrap_or(f64::MIN)
|
||||
};
|
||||
// With the binary search error, we can guarantee just +-1
|
||||
(
|
||||
source_amount.to_num(),
|
||||
ratio_for_amount(source_amount),
|
||||
ratio_for_amount(source_amount.saturating_sub(I80F48::ONE)),
|
||||
ratio_for_amount(source_amount.saturating_add(I80F48::ONE)),
|
||||
ratio_for_amount(source_amount - I80F48::ONE),
|
||||
ratio_for_amount(source_amount + I80F48::ONE),
|
||||
)
|
||||
};
|
||||
let check_max_swap_result = |c: &HealthCache,
|
||||
|
@ -556,8 +592,9 @@ mod tests {
|
|||
"checking {source} to {target} for price_factor: {price_factor}, target ratio {ratio}: actual ratios: {minus_ratio}/{actual_ratio}/{plus_ratio}, amount: {source_amount}",
|
||||
);
|
||||
assert!(actual_ratio >= ratio);
|
||||
assert!(minus_ratio < ratio || actual_ratio < minus_ratio);
|
||||
assert!(plus_ratio < ratio);
|
||||
// either we're within tolerance of the target, or swapping 1 more would
|
||||
// bring us below the target
|
||||
assert!(actual_ratio < ratio + 1.0 || plus_ratio < ratio);
|
||||
};
|
||||
|
||||
{
|
||||
|
@ -654,6 +691,34 @@ mod tests {
|
|||
// (tracking happens without decimals)
|
||||
assert!(find_max_swap_actual(&health_cache, 0, 1, 1.0, 1.0, banks).0 < 51.0);
|
||||
}
|
||||
|
||||
{
|
||||
// check with serum reserved
|
||||
println!("test 6");
|
||||
let mut health_cache = health_cache.clone();
|
||||
health_cache.serum3_infos = vec![Serum3Info {
|
||||
base_index: 1,
|
||||
quote_index: 0,
|
||||
market_index: 0,
|
||||
reserved_base: I80F48::from(30 / 3),
|
||||
reserved_quote: I80F48::from(30 / 2),
|
||||
}];
|
||||
adjust_by_usdc(&mut health_cache, 0, -20.0);
|
||||
adjust_by_usdc(&mut health_cache, 1, -40.0);
|
||||
adjust_by_usdc(&mut health_cache, 2, 120.0);
|
||||
|
||||
for price_factor in [0.9, 1.1] {
|
||||
for target in 1..100 {
|
||||
let target = target as f64;
|
||||
check_max_swap_result(&health_cache, 0, 1, target, price_factor, banks);
|
||||
check_max_swap_result(&health_cache, 1, 0, target, price_factor, banks);
|
||||
check_max_swap_result(&health_cache, 0, 2, target, price_factor, banks);
|
||||
check_max_swap_result(&health_cache, 1, 2, target, price_factor, banks);
|
||||
check_max_swap_result(&health_cache, 2, 0, target, price_factor, banks);
|
||||
check_max_swap_result(&health_cache, 2, 1, target, price_factor, banks);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
|
@ -20,4 +20,4 @@ test-bpf = []
|
|||
[dependencies]
|
||||
anchor-lang = { path = "../../anchor/lang" }
|
||||
anchor-spl = { path = "../../anchor/spl" }
|
||||
solana-program = "~1.10.35"
|
||||
solana-program = "~1.14.9"
|
||||
|
|
|
@ -22,7 +22,7 @@ solana --url https://mango.devnet.rpcpool.com program deploy --program-id $PROGR
|
|||
|
||||
# # publish idl
|
||||
cargo run -p anchor-cli -- idl upgrade --provider.cluster https://mango.devnet.rpcpool.com --provider.wallet $WALLET_WITH_FUNDS \
|
||||
--filepath target/idl/mango_v4.json $PROGRAM_ID
|
||||
--filepath target/idl/mango_v4_no_docs.json $PROGRAM_ID
|
||||
|
||||
|
||||
# build npm package
|
||||
|
|
|
@ -22,7 +22,7 @@ solana --url $MB_CLUSTER_URL program deploy --program-id $PROGRAM_ID \
|
|||
|
||||
# publish idl
|
||||
cargo run -p anchor-cli -- idl upgrade --provider.cluster $MB_CLUSTER_URL --provider.wallet $WALLET_WITH_FUNDS \
|
||||
--filepath target/idl/mango_v4.json $PROGRAM_ID
|
||||
--filepath target/idl/mango_v4_no_docs.json $PROGRAM_ID
|
||||
|
||||
|
||||
# build npm package
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
Subproject commit 97ed7b12de198f9dca2637af0ce8f5823cc3a0e3
|
|
@ -72,7 +72,6 @@ export class Bank implements BankForHealth {
|
|||
static from(
|
||||
publicKey: PublicKey,
|
||||
obj: {
|
||||
// TODO: rearrange fields to have same order as in bank.rs
|
||||
group: PublicKey;
|
||||
name: number[];
|
||||
mint: PublicKey;
|
||||
|
@ -198,7 +197,7 @@ export class Bank implements BankForHealth {
|
|||
public tokenIndex: TokenIndex,
|
||||
public mintDecimals: number,
|
||||
public bankNum: number,
|
||||
minVaultToDepositsRatio: number,
|
||||
public minVaultToDepositsRatio: number,
|
||||
netBorrowLimitWindowSizeTs: BN,
|
||||
lastNetBorrowsWindowStartTs: BN,
|
||||
public netBorrowLimitPerWindowQuote: BN,
|
||||
|
|
|
@ -500,7 +500,6 @@ export class Group {
|
|||
}
|
||||
|
||||
public getSerum3FeeRates(maker = true): number {
|
||||
// TODO: fetch msrm/srm vault balance
|
||||
const feeTier = getFeeTier(0, 0);
|
||||
const rates = getFeeRates(feeTier);
|
||||
return maker ? rates.maker : rates.taker;
|
||||
|
|
|
@ -14,6 +14,7 @@ function mockBankAndOracle(
|
|||
maintWeight: number,
|
||||
initWeight: number,
|
||||
price: number,
|
||||
stablePrice: number,
|
||||
): BankForHealth {
|
||||
return {
|
||||
tokenIndex,
|
||||
|
@ -22,7 +23,7 @@ function mockBankAndOracle(
|
|||
maintLiabWeight: I80F48.fromNumber(1 + maintWeight),
|
||||
initLiabWeight: I80F48.fromNumber(1 + initWeight),
|
||||
price: I80F48.fromNumber(price),
|
||||
stablePriceModel: { stablePrice: price } as StablePriceModel,
|
||||
stablePriceModel: { stablePrice: stablePrice } as StablePriceModel,
|
||||
scaledInitAssetWeight: () => I80F48.fromNumber(1 - initWeight),
|
||||
scaledInitLiabWeight: () => I80F48.fromNumber(1 + initWeight),
|
||||
};
|
||||
|
@ -57,12 +58,14 @@ describe('Health Cache', () => {
|
|||
0.1,
|
||||
0.2,
|
||||
1,
|
||||
1,
|
||||
);
|
||||
const targetBank: BankForHealth = mockBankAndOracle(
|
||||
4 as TokenIndex,
|
||||
0.3,
|
||||
0.5,
|
||||
5,
|
||||
5,
|
||||
);
|
||||
|
||||
const ti1 = TokenInfo.fromBank(sourceBank, I80F48.fromNumber(100));
|
||||
|
@ -146,18 +149,21 @@ describe('Health Cache', () => {
|
|||
0.1,
|
||||
0.2,
|
||||
1,
|
||||
1,
|
||||
);
|
||||
const bank2: BankForHealth = mockBankAndOracle(
|
||||
4 as TokenIndex,
|
||||
0.3,
|
||||
0.5,
|
||||
5,
|
||||
5,
|
||||
);
|
||||
const bank3: BankForHealth = mockBankAndOracle(
|
||||
5 as TokenIndex,
|
||||
0.3,
|
||||
0.5,
|
||||
10,
|
||||
10,
|
||||
);
|
||||
|
||||
const ti1 = TokenInfo.fromBank(bank1, I80F48.fromNumber(fixture.token1));
|
||||
|
@ -390,9 +396,9 @@ describe('Health Cache', () => {
|
|||
});
|
||||
|
||||
it('test_max_swap', (done) => {
|
||||
const b0 = mockBankAndOracle(0 as TokenIndex, 0.1, 0.1, 2);
|
||||
const b1 = mockBankAndOracle(1 as TokenIndex, 0.2, 0.2, 3);
|
||||
const b2 = mockBankAndOracle(2 as TokenIndex, 0.3, 0.3, 4);
|
||||
const b0 = mockBankAndOracle(0 as TokenIndex, 0.1, 0.1, 2, 2);
|
||||
const b1 = mockBankAndOracle(1 as TokenIndex, 0.2, 0.2, 3, 3);
|
||||
const b2 = mockBankAndOracle(2 as TokenIndex, 0.3, 0.3, 4, 4);
|
||||
const banks = [b0, b1, b2];
|
||||
const hc = new HealthCache(
|
||||
[
|
||||
|
@ -475,13 +481,13 @@ describe('Health Cache', () => {
|
|||
|
||||
for (const priceFactor of [0.1, 0.9, 1.1]) {
|
||||
for (const target of _.range(1, 100, 1)) {
|
||||
// checkMaxSwapResult(
|
||||
// clonedHc,
|
||||
// 0 as TokenIndex,
|
||||
// 1 as TokenIndex,
|
||||
// target,
|
||||
// priceFactor,
|
||||
// );
|
||||
checkMaxSwapResult(
|
||||
clonedHc,
|
||||
0 as TokenIndex,
|
||||
1 as TokenIndex,
|
||||
target,
|
||||
priceFactor,
|
||||
);
|
||||
checkMaxSwapResult(
|
||||
clonedHc,
|
||||
1 as TokenIndex,
|
||||
|
@ -489,13 +495,13 @@ describe('Health Cache', () => {
|
|||
target,
|
||||
priceFactor,
|
||||
);
|
||||
// checkMaxSwapResult(
|
||||
// clonedHc,
|
||||
// 0 as TokenIndex,
|
||||
// 2 as TokenIndex,
|
||||
// target,
|
||||
// priceFactor,
|
||||
// );
|
||||
checkMaxSwapResult(
|
||||
clonedHc,
|
||||
0 as TokenIndex,
|
||||
2 as TokenIndex,
|
||||
target,
|
||||
priceFactor,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -623,12 +629,90 @@ describe('Health Cache', () => {
|
|||
checkMaxSwapResult(clonedHc, 0 as TokenIndex, 1 as TokenIndex, 4, 1);
|
||||
}
|
||||
|
||||
// TODO test 5
|
||||
|
||||
{
|
||||
console.log(' - test 6');
|
||||
const clonedHc = _.cloneDeep(hc);
|
||||
clonedHc.serum3Infos = [
|
||||
new Serum3Info(
|
||||
I80F48.fromNumber(30 / 3),
|
||||
I80F48.fromNumber(30 / 2),
|
||||
1,
|
||||
0,
|
||||
0 as MarketIndex,
|
||||
),
|
||||
];
|
||||
|
||||
// adjust by usdc
|
||||
clonedHc.tokenInfos[0].balanceNative.iadd(
|
||||
I80F48.fromNumber(-20).div(clonedHc.tokenInfos[0].prices.oracle),
|
||||
);
|
||||
clonedHc.tokenInfos[1].balanceNative.iadd(
|
||||
I80F48.fromNumber(-40).div(clonedHc.tokenInfos[1].prices.oracle),
|
||||
);
|
||||
clonedHc.tokenInfos[2].balanceNative.iadd(
|
||||
I80F48.fromNumber(120).div(clonedHc.tokenInfos[2].prices.oracle),
|
||||
);
|
||||
|
||||
for (const priceFactor of [
|
||||
// 0.9,
|
||||
|
||||
1.1,
|
||||
]) {
|
||||
for (const target of _.range(1, 100, 1)) {
|
||||
checkMaxSwapResult(
|
||||
clonedHc,
|
||||
0 as TokenIndex,
|
||||
1 as TokenIndex,
|
||||
target,
|
||||
priceFactor,
|
||||
);
|
||||
checkMaxSwapResult(
|
||||
clonedHc,
|
||||
1 as TokenIndex,
|
||||
0 as TokenIndex,
|
||||
target,
|
||||
priceFactor,
|
||||
);
|
||||
checkMaxSwapResult(
|
||||
clonedHc,
|
||||
0 as TokenIndex,
|
||||
2 as TokenIndex,
|
||||
target,
|
||||
priceFactor,
|
||||
);
|
||||
checkMaxSwapResult(
|
||||
clonedHc,
|
||||
1 as TokenIndex,
|
||||
2 as TokenIndex,
|
||||
target,
|
||||
priceFactor,
|
||||
);
|
||||
checkMaxSwapResult(
|
||||
clonedHc,
|
||||
2 as TokenIndex,
|
||||
0 as TokenIndex,
|
||||
target,
|
||||
priceFactor,
|
||||
);
|
||||
checkMaxSwapResult(
|
||||
clonedHc,
|
||||
2 as TokenIndex,
|
||||
1 as TokenIndex,
|
||||
target,
|
||||
priceFactor,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
done();
|
||||
});
|
||||
|
||||
it('test_max_perp', (done) => {
|
||||
const baseLotSize = 100;
|
||||
const b0 = mockBankAndOracle(0 as TokenIndex, 0.0, 0.0, 1);
|
||||
const b0 = mockBankAndOracle(0 as TokenIndex, 0.0, 0.0, 1, 1);
|
||||
const p0 = mockPerpMarket(0, 0.3, 0.3, baseLotSize, 2);
|
||||
const hc = new HealthCache(
|
||||
[TokenInfo.fromBank(b0, I80F48.fromNumber(0))],
|
||||
|
|
|
@ -548,26 +548,47 @@ export class HealthCache {
|
|||
);
|
||||
}
|
||||
|
||||
private static scanRightUntilLessThan(
|
||||
start: I80F48,
|
||||
target: I80F48,
|
||||
fun: (amount: I80F48) => I80F48,
|
||||
): I80F48 {
|
||||
const maxIterations = 100;
|
||||
let current = start;
|
||||
// console.log(`scanRightUntilLessThan, start ${start.toLocaleString()}`);
|
||||
for (const key of Array(maxIterations).fill(0).keys()) {
|
||||
const value = fun(current);
|
||||
if (value.lt(target)) {
|
||||
return current;
|
||||
}
|
||||
// console.log(
|
||||
// ` - current ${current.toLocaleString()}, value ${value.toLocaleString()}, target ${target.toLocaleString()}`,
|
||||
// );
|
||||
current = current.max(ONE_I80F48()).mul(I80F48.fromNumber(2));
|
||||
}
|
||||
throw new Error('Could not find amount that led to health ratio <=0');
|
||||
}
|
||||
|
||||
private static binaryApproximationSearch(
|
||||
left: I80F48,
|
||||
leftRatio: I80F48,
|
||||
leftValue: I80F48,
|
||||
right: I80F48,
|
||||
rightRatio: I80F48,
|
||||
targetRatio: I80F48,
|
||||
targetValue: I80F48,
|
||||
minStep: I80F48,
|
||||
healthRatioAfterActionFn: (I80F48) => I80F48,
|
||||
fun: (I80F48) => I80F48,
|
||||
): I80F48 {
|
||||
const maxIterations = 20;
|
||||
const targetError = I80F48.fromNumber(0.1);
|
||||
const rightValue = fun(right);
|
||||
|
||||
if (
|
||||
(leftRatio.sub(targetRatio).isPos() &&
|
||||
rightRatio.sub(targetRatio).isPos()) ||
|
||||
(leftRatio.sub(targetRatio).isNeg() &&
|
||||
rightRatio.sub(targetRatio).isNeg())
|
||||
(leftValue.sub(targetValue).isPos() &&
|
||||
rightValue.sub(targetValue).isPos()) ||
|
||||
(leftValue.sub(targetValue).isNeg() &&
|
||||
rightValue.sub(targetValue).isNeg())
|
||||
) {
|
||||
throw new Error(
|
||||
`Internal error: left ${leftRatio.toNumber()} and right ${rightRatio.toNumber()} don't contain the target value ${targetRatio.toNumber()}, likely reason is the zeroAmount not been tight enough!`,
|
||||
`Internal error: left ${leftValue.toNumber()} and right ${rightValue.toNumber()} don't contain the target value ${targetValue.toNumber()}!`,
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -578,25 +599,16 @@ export class HealthCache {
|
|||
return left;
|
||||
}
|
||||
newAmount = left.add(right).mul(I80F48.fromNumber(0.5));
|
||||
const newAmountRatio = healthRatioAfterActionFn(newAmount);
|
||||
const error = newAmountRatio.sub(targetRatio);
|
||||
const newAmountRatio = fun(newAmount);
|
||||
const error = newAmountRatio.sub(targetValue);
|
||||
if (error.isPos() && error.lt(targetError)) {
|
||||
return newAmount;
|
||||
}
|
||||
if (newAmountRatio.gt(targetRatio) != rightRatio.gt(targetRatio)) {
|
||||
if (newAmountRatio.gt(targetValue) != rightValue.gt(targetValue)) {
|
||||
left = newAmount;
|
||||
leftRatio = newAmountRatio;
|
||||
} else {
|
||||
right = newAmount;
|
||||
rightRatio = newAmountRatio;
|
||||
}
|
||||
// console.log(
|
||||
// ` -- ${left.toNumber().toFixed(3)} (${leftRatio
|
||||
// .toNumber()
|
||||
// .toFixed(3)}) ${right.toNumber().toFixed(3)} (${rightRatio
|
||||
// .toNumber()
|
||||
// .toFixed(3)})`,
|
||||
// );
|
||||
}
|
||||
|
||||
console.error(
|
||||
|
@ -726,31 +738,63 @@ export class HealthCache {
|
|||
// search to the right of point1Amount: but how far?
|
||||
// At point1, source.balance < 0 and target.balance > 0, so use a simple estimation for
|
||||
// zero health: health - source_liab_weight * a + target_asset_weight * a * priceFactor = 0.
|
||||
// where a is the source token native amount.
|
||||
// Note that this is just an estimate. Swapping can increase the amount that serum3
|
||||
// reserved contributions offset, moving the actual zero point further to the right.
|
||||
if (point1Health.lte(ZERO_I80F48())) {
|
||||
return ZERO_I80F48();
|
||||
}
|
||||
const zeroHealthAmount = point1Amount.sub(
|
||||
point1Health.div(finalHealthSlope),
|
||||
const zeroHealthEstimate = point1Amount.sub(
|
||||
point1Health.sub(finalHealthSlope),
|
||||
);
|
||||
const zeroHealthRatio = healthRatioAfterSwap(zeroHealthAmount);
|
||||
const zeroHealth = healthAfterSwap(zeroHealthAmount);
|
||||
const zeroHealthEstimateRatio = healthRatioAfterSwap(zeroHealthEstimate);
|
||||
|
||||
amount = HealthCache.binaryApproximationSearch(
|
||||
point1Amount,
|
||||
point1Ratio,
|
||||
zeroHealthAmount,
|
||||
zeroHealthRatio,
|
||||
// console.log(`getMaxSourceForTokenSwap`);
|
||||
// console.log(` - finalHealthSlope ${finalHealthSlope.toLocaleString()}`);
|
||||
// console.log(` - minRatio ${minRatio.toLocaleString()}`);
|
||||
// console.log(` - point0Amount ${point0Amount.toLocaleString()}`);
|
||||
// console.log(` - point0Health ${point0Health.toLocaleString()}`);
|
||||
// console.log(` - point0Ratio ${point0Ratio.toLocaleString()}`);
|
||||
// console.log(` - point1Amount ${point1Amount.toLocaleString()}`);
|
||||
// console.log(` - point1Health ${point1Health.toLocaleString()}`);
|
||||
// console.log(` - point1Ratio ${point1Ratio.toLocaleString()}`);
|
||||
// console.log(
|
||||
// ` - zeroHealthEstimate ${zeroHealthEstimate.toLocaleString()}`,
|
||||
// );
|
||||
// console.log(
|
||||
// ` - zeroHealthEstimateRatio ${zeroHealthEstimateRatio.toLocaleString()}`,
|
||||
// );
|
||||
|
||||
const rightBound = HealthCache.scanRightUntilLessThan(
|
||||
zeroHealthEstimate,
|
||||
minRatio,
|
||||
ZERO_I80F48(),
|
||||
healthRatioAfterSwap,
|
||||
);
|
||||
if (rightBound.eq(zeroHealthEstimate)) {
|
||||
amount = HealthCache.binaryApproximationSearch(
|
||||
point1Amount,
|
||||
point1Ratio,
|
||||
rightBound,
|
||||
minRatio,
|
||||
ZERO_I80F48(),
|
||||
healthRatioAfterSwap,
|
||||
);
|
||||
} else {
|
||||
amount = HealthCache.binaryApproximationSearch(
|
||||
zeroHealthEstimate,
|
||||
healthRatioAfterSwap(zeroHealthEstimate),
|
||||
rightBound,
|
||||
minRatio,
|
||||
ZERO_I80F48(),
|
||||
healthRatioAfterSwap,
|
||||
);
|
||||
}
|
||||
} else if (point0Ratio.gte(minRatio)) {
|
||||
// Must be between point0Amount and point1Amount.
|
||||
amount = HealthCache.binaryApproximationSearch(
|
||||
point0Amount,
|
||||
point0Ratio,
|
||||
point1Amount,
|
||||
point1Ratio,
|
||||
minRatio,
|
||||
ZERO_I80F48(),
|
||||
healthRatioAfterSwap,
|
||||
|
@ -761,7 +805,6 @@ export class HealthCache {
|
|||
ZERO_I80F48(),
|
||||
initialRatio,
|
||||
point0Amount,
|
||||
point0Ratio,
|
||||
minRatio,
|
||||
ZERO_I80F48(),
|
||||
healthRatioAfterSwap,
|
||||
|
@ -880,7 +923,6 @@ export class HealthCache {
|
|||
initialAmount,
|
||||
initialRatio,
|
||||
zeroAmount,
|
||||
zeroAmountRatio,
|
||||
minRatio,
|
||||
ONE_I80F48(),
|
||||
healthRatioAfterPlacingOrder,
|
||||
|
@ -994,7 +1036,6 @@ export class HealthCache {
|
|||
case1Start,
|
||||
case1StartRatio,
|
||||
zeroHealthAmount,
|
||||
zeroHealthRatio,
|
||||
minRatio,
|
||||
ONE_I80F48(),
|
||||
healthRatioAfterTradeTrunc,
|
||||
|
@ -1005,7 +1046,6 @@ export class HealthCache {
|
|||
ZERO_I80F48(),
|
||||
initialRatio,
|
||||
case1Start,
|
||||
case1StartRatio,
|
||||
minRatio,
|
||||
ONE_I80F48(),
|
||||
healthRatioAfterTradeTrunc,
|
||||
|
|
|
@ -462,12 +462,33 @@ export class MangoAccount {
|
|||
const initHealthWithoutExistingPosition = initHealth.sub(
|
||||
existingPositionHealthContrib,
|
||||
);
|
||||
const maxBorrowNative = initHealthWithoutExistingPosition
|
||||
let maxBorrowNative = initHealthWithoutExistingPosition
|
||||
.div(tokenBank.initLiabWeight)
|
||||
.div(tokenBank.price);
|
||||
|
||||
// Cap maxBorrow to maintain minVaultToDepositsRatio on the bank
|
||||
const vaultAmount = group.vaultAmountsMap.get(tokenBank.vault.toBase58());
|
||||
if (!vaultAmount) {
|
||||
throw new Error(
|
||||
`No vault amount found for ${tokenBank.name} vault ${tokenBank.vault}!`,
|
||||
);
|
||||
}
|
||||
const vaultAmountAfterWithdrawingDeposits = I80F48.fromU64(vaultAmount).sub(
|
||||
existingTokenDeposits,
|
||||
);
|
||||
const expectedVaultMinAmount = tokenBank
|
||||
.nativeDeposits()
|
||||
.mul(I80F48.fromNumber(tokenBank.minVaultToDepositsRatio));
|
||||
if (vaultAmountAfterWithdrawingDeposits.gt(expectedVaultMinAmount)) {
|
||||
maxBorrowNative = maxBorrowNative.min(
|
||||
vaultAmountAfterWithdrawingDeposits.sub(expectedVaultMinAmount),
|
||||
);
|
||||
}
|
||||
|
||||
const maxBorrowNativeWithoutFees = maxBorrowNative.div(
|
||||
ONE_I80F48().add(tokenBank.loanOriginationFeeRate),
|
||||
);
|
||||
|
||||
return maxBorrowNativeWithoutFees.add(existingTokenDeposits);
|
||||
}
|
||||
|
||||
|
|
|
@ -69,7 +69,6 @@ export type MangoClientOptions = {
|
|||
prioritizationFee?: number;
|
||||
};
|
||||
|
||||
// TODO: replace ui values with native as input wherever possible
|
||||
export class MangoClient {
|
||||
private idsSource: IdsSource;
|
||||
private postSendTxCallback?: ({ txid }) => void;
|
||||
|
@ -2239,7 +2238,6 @@ export class MangoClient {
|
|||
group: Group,
|
||||
mintPk: PublicKey,
|
||||
): Promise<TransactionSignature> {
|
||||
// TODO: handle updating multiple banks
|
||||
const bank = group.getFirstBankByMint(mintPk);
|
||||
const mintInfo = group.mintInfosMapByMint.get(mintPk.toString())!;
|
||||
|
||||
|
@ -2444,9 +2442,6 @@ export class MangoClient {
|
|||
provider: Provider,
|
||||
groupName: string,
|
||||
): 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);
|
||||
const idl = IDL;
|
||||
|
||||
const id = Id.fromIdsByName(groupName);
|
||||
|
|
|
@ -97,6 +97,9 @@ async function debugUser(
|
|||
}
|
||||
|
||||
function getMaxSourceForTokenSwapWrapper(src, tgt): void {
|
||||
// Turn on for debugging specific pairs
|
||||
// if (src != 'DAI' || tgt != 'ETH') return;
|
||||
|
||||
const maxSourceUi = mangoAccount.getMaxSourceUiForTokenSwap(
|
||||
group,
|
||||
group.banksMapByName.get(src)![0].mint,
|
||||
|
|
|
@ -126,7 +126,6 @@ export async function buildVersionedTx(
|
|||
addressLookupTableAccounts: alts,
|
||||
});
|
||||
const vTx = new VersionedTransaction(message);
|
||||
// TODO: remove use of any when possible in future
|
||||
vTx.sign([
|
||||
((provider as AnchorProvider).wallet as any).payer as Signer,
|
||||
...additionalSigners,
|
||||
|
|
Loading…
Reference in New Issue