Merge remote-tracking branch 'origin/deploy' into dev
This commit is contained in:
commit
6f85dfa42d
|
@ -113,6 +113,14 @@ pub struct Cli {
|
|||
#[clap(long, env, value_parser, value_delimiter = ',')]
|
||||
pub(crate) rebalance_skip_tokens: Option<Vec<u16>>,
|
||||
|
||||
/// query jupiter for direct routes to and from these tokens
|
||||
///
|
||||
/// These alternate routes will only be used when the main USDC-based one does not
|
||||
/// work or does not fit in a transaction. A direct route to/from USDC is always also an alternative.
|
||||
/// The alternate route with the lowest price impact will be used.
|
||||
#[clap(long, env, value_parser, value_delimiter = ',')]
|
||||
pub(crate) rebalance_alternate_jupiter_route_tokens: Option<Vec<u16>>,
|
||||
|
||||
/// When closing borrows, the rebalancer can't close token positions exactly.
|
||||
/// Instead it purchases too much and then gets rid of the excess in a second step.
|
||||
/// If this is 0.05, then it'll swap borrow_value * (1 + 0.05) quote token into borrow token.
|
||||
|
|
|
@ -242,9 +242,13 @@ async fn main() -> anyhow::Result<()> {
|
|||
borrow_settle_excess: (1f64 + cli.rebalance_borrow_settle_excess).max(1f64),
|
||||
refresh_timeout: Duration::from_secs(cli.rebalance_refresh_timeout_secs),
|
||||
jupiter_version: cli.jupiter_version.into(),
|
||||
skip_tokens: cli.rebalance_skip_tokens.unwrap_or(Vec::new()),
|
||||
skip_tokens: cli.rebalance_skip_tokens.unwrap_or_default(),
|
||||
alternate_jupiter_route_tokens: cli
|
||||
.rebalance_alternate_jupiter_route_tokens
|
||||
.unwrap_or_default(),
|
||||
allow_withdraws: signer_is_owner,
|
||||
};
|
||||
rebalance_config.validate(&mango_client.context);
|
||||
|
||||
let rebalancer = Arc::new(rebalance::Rebalancer {
|
||||
mango_client: mango_client.clone(),
|
||||
|
|
|
@ -5,7 +5,7 @@ use mango_v4::state::{
|
|||
PlaceOrderType, Side, TokenIndex, QUOTE_TOKEN_INDEX,
|
||||
};
|
||||
use mango_v4_client::{
|
||||
chain_data, jupiter, perp_pnl, MangoClient, PerpMarketContext, TokenContext,
|
||||
chain_data, jupiter, perp_pnl, MangoClient, MangoGroupContext, PerpMarketContext, TokenContext,
|
||||
TransactionBuilder, TransactionSize,
|
||||
};
|
||||
|
||||
|
@ -28,9 +28,22 @@ pub struct Config {
|
|||
pub refresh_timeout: Duration,
|
||||
pub jupiter_version: jupiter::Version,
|
||||
pub skip_tokens: Vec<TokenIndex>,
|
||||
pub alternate_jupiter_route_tokens: Vec<TokenIndex>,
|
||||
pub allow_withdraws: bool,
|
||||
}
|
||||
|
||||
impl Config {
|
||||
// panics on failure
|
||||
pub fn validate(&self, context: &MangoGroupContext) {
|
||||
self.skip_tokens.iter().for_each(|&ti| {
|
||||
context.token(ti);
|
||||
});
|
||||
self.alternate_jupiter_route_tokens.iter().for_each(|&ti| {
|
||||
context.token(ti);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
fn token_bank(
|
||||
token: &TokenContext,
|
||||
account_fetcher: &chain_data::AccountFetcher,
|
||||
|
@ -106,7 +119,7 @@ impl Rebalancer {
|
|||
/// Grab three possible routes:
|
||||
/// 1. USDC -> output (complex routes)
|
||||
/// 2. USDC -> output (direct route only)
|
||||
/// 3. SOL -> output (direct route only)
|
||||
/// 3. alternate_jupiter_route_tokens -> output (direct route only)
|
||||
/// Use 1. if it fits into a tx. Otherwise use the better of 2./3.
|
||||
async fn token_swap_buy(
|
||||
&self,
|
||||
|
@ -114,16 +127,7 @@ impl Rebalancer {
|
|||
in_amount_quote: u64,
|
||||
) -> anyhow::Result<(Signature, jupiter::Quote)> {
|
||||
let quote_token = self.mango_client.context.token(QUOTE_TOKEN_INDEX);
|
||||
let sol_token = self.mango_client.context.token(
|
||||
*self
|
||||
.mango_client
|
||||
.context
|
||||
.token_indexes_by_name
|
||||
.get("SOL") // TODO: better use mint
|
||||
.unwrap(),
|
||||
);
|
||||
let quote_mint = quote_token.mint;
|
||||
let sol_mint = sol_token.mint;
|
||||
let jupiter_version = self.config.jupiter_version;
|
||||
|
||||
let full_route_job = self.jupiter_quote(
|
||||
|
@ -140,18 +144,21 @@ impl Rebalancer {
|
|||
true,
|
||||
jupiter_version,
|
||||
);
|
||||
let mut jobs = vec![full_route_job, direct_quote_route_job];
|
||||
|
||||
// For the SOL -> output route we need to adjust the in amount by the SOL price
|
||||
let sol_price = self
|
||||
.account_fetcher
|
||||
.fetch_bank_price(&sol_token.first_bank())?;
|
||||
let in_amount_sol = (I80F48::from(in_amount_quote) / sol_price)
|
||||
.ceil()
|
||||
.to_num::<u64>();
|
||||
let direct_sol_route_job =
|
||||
self.jupiter_quote(sol_mint, output_mint, in_amount_sol, true, jupiter_version);
|
||||
|
||||
let jobs = vec![full_route_job, direct_quote_route_job, direct_sol_route_job];
|
||||
for in_token_index in &self.config.alternate_jupiter_route_tokens {
|
||||
let in_token = self.mango_client.context.token(*in_token_index);
|
||||
// For the alternate output routes we need to adjust the in amount by the token price
|
||||
let in_price = self
|
||||
.account_fetcher
|
||||
.fetch_bank_price(&in_token.first_bank())?;
|
||||
let in_amount = (I80F48::from(in_amount_quote) / in_price)
|
||||
.ceil()
|
||||
.to_num::<u64>();
|
||||
let direct_route_job =
|
||||
self.jupiter_quote(in_token.mint, output_mint, in_amount, true, jupiter_version);
|
||||
jobs.push(direct_route_job);
|
||||
}
|
||||
|
||||
let mut results = futures::future::join_all(jobs).await;
|
||||
let full_route = results.remove(0)?;
|
||||
|
@ -173,7 +180,7 @@ impl Rebalancer {
|
|||
/// Grab three possible routes:
|
||||
/// 1. input -> USDC (complex routes)
|
||||
/// 2. input -> USDC (direct route only)
|
||||
/// 3. input -> SOL (direct route only)
|
||||
/// 3. input -> alternate_jupiter_route_tokens (direct route only)
|
||||
/// Use 1. if it fits into a tx. Otherwise use the better of 2./3.
|
||||
async fn token_swap_sell(
|
||||
&self,
|
||||
|
@ -181,26 +188,21 @@ impl Rebalancer {
|
|||
in_amount: u64,
|
||||
) -> anyhow::Result<(Signature, jupiter::Quote)> {
|
||||
let quote_token = self.mango_client.context.token(QUOTE_TOKEN_INDEX);
|
||||
let sol_token = self.mango_client.context.token(
|
||||
*self
|
||||
.mango_client
|
||||
.context
|
||||
.token_indexes_by_name
|
||||
.get("SOL") // TODO: better use mint
|
||||
.unwrap(),
|
||||
);
|
||||
let quote_mint = quote_token.mint;
|
||||
let sol_mint = sol_token.mint;
|
||||
let jupiter_version = self.config.jupiter_version;
|
||||
|
||||
let full_route_job =
|
||||
self.jupiter_quote(input_mint, quote_mint, in_amount, false, jupiter_version);
|
||||
let direct_quote_route_job =
|
||||
self.jupiter_quote(input_mint, quote_mint, in_amount, true, jupiter_version);
|
||||
let direct_sol_route_job =
|
||||
self.jupiter_quote(input_mint, sol_mint, in_amount, true, jupiter_version);
|
||||
let mut jobs = vec![full_route_job, direct_quote_route_job];
|
||||
|
||||
let jobs = vec![full_route_job, direct_quote_route_job, direct_sol_route_job];
|
||||
for out_token_index in &self.config.alternate_jupiter_route_tokens {
|
||||
let out_token = self.mango_client.context.token(*out_token_index);
|
||||
let direct_route_job =
|
||||
self.jupiter_quote(input_mint, out_token.mint, in_amount, true, jupiter_version);
|
||||
jobs.push(direct_route_job);
|
||||
}
|
||||
|
||||
let mut results = futures::future::join_all(jobs).await;
|
||||
let full_route = results.remove(0)?;
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@blockworks-foundation/mango-v4",
|
||||
"version": "0.21.31",
|
||||
"version": "0.23.1",
|
||||
"description": "Typescript Client for mango-v4 program.",
|
||||
"repository": "https://github.com/blockworks-foundation/mango-v4",
|
||||
"author": {
|
||||
|
|
|
@ -32,7 +32,11 @@ borsh = { version = "0.10.3", features = ["const-generics"] }
|
|||
bytemuck = { version = "^1.7.2", features = ["min_const_generics"] }
|
||||
default-env = "0.1.1"
|
||||
derivative = "2.2.0"
|
||||
fixed = { workspace = true, features = ["serde", "borsh", "debug-assert-in-release"] }
|
||||
fixed = { workspace = true, features = [
|
||||
"serde",
|
||||
"borsh",
|
||||
"debug-assert-in-release",
|
||||
] }
|
||||
num_enum = "0.5.1"
|
||||
pyth-sdk-solana = { workspace = true }
|
||||
serde = "^1.0"
|
||||
|
@ -48,7 +52,9 @@ switchboard-program = "0.2"
|
|||
switchboard-v2 = { package = "switchboard-solana", version = "0.28" }
|
||||
|
||||
|
||||
openbook-v2 = { git = "https://github.com/openbook-dex/openbook-v2.git", features = ["no-entrypoint"] }
|
||||
openbook-v2 = { git = "https://github.com/openbook-dex/openbook-v2.git", features = [
|
||||
"no-entrypoint",
|
||||
] }
|
||||
|
||||
|
||||
[dev-dependencies]
|
||||
|
@ -56,7 +62,9 @@ solana-sdk = { workspace = true, default-features = false }
|
|||
solana-program-test = { workspace = true }
|
||||
solana-logger = { workspace = true }
|
||||
spl-token = { version = "^3.0.0", features = ["no-entrypoint"] }
|
||||
spl-associated-token-account = { version = "^1.0.3", features = ["no-entrypoint"] }
|
||||
spl-associated-token-account = { version = "^1.0.3", features = [
|
||||
"no-entrypoint",
|
||||
] }
|
||||
bincode = "^1.3.1"
|
||||
log = "0.4.14"
|
||||
env_logger = "0.9.0"
|
||||
|
@ -69,4 +77,4 @@ num = "0.4.0"
|
|||
regex = "1"
|
||||
serde_json = "1"
|
||||
bs58 = "0.5"
|
||||
anyhow = "1"
|
||||
anyhow = "1"
|
||||
|
|
|
@ -73,6 +73,7 @@ async function main(): Promise<void> {
|
|||
group,
|
||||
usdcDevnetMint,
|
||||
usdcDevnetOracle.publicKey,
|
||||
PublicKey.default,
|
||||
0, // tokenIndex
|
||||
'USDC',
|
||||
{
|
||||
|
@ -101,6 +102,7 @@ async function main(): Promise<void> {
|
|||
group,
|
||||
solDevnetMint,
|
||||
solDevnetOracle,
|
||||
PublicKey.default,
|
||||
4, // tokenIndex
|
||||
'SOL',
|
||||
{
|
||||
|
@ -130,6 +132,7 @@ async function main(): Promise<void> {
|
|||
group,
|
||||
usdtDevnetMint,
|
||||
usdcDevnetOracle.publicKey,
|
||||
PublicKey.default,
|
||||
5, // tokenIndex
|
||||
'USDT',
|
||||
{
|
||||
|
@ -163,6 +166,7 @@ async function main(): Promise<void> {
|
|||
group.getFirstBankByMint(insuranceMint),
|
||||
0,
|
||||
'SOL/USDC',
|
||||
0,
|
||||
);
|
||||
await group.reloadAll(client);
|
||||
const serum3Market = group.getSerum3MarketByExternalMarket(
|
||||
|
@ -211,6 +215,7 @@ async function main(): Promise<void> {
|
|||
1.0,
|
||||
2 * 60 * 60,
|
||||
0.025,
|
||||
0,
|
||||
);
|
||||
await group.reloadAll(client);
|
||||
const perpMarket = group.getPerpMarketByMarketIndex(
|
||||
|
|
|
@ -1,16 +1,9 @@
|
|||
import { AnchorProvider, Wallet } from '@coral-xyz/anchor';
|
||||
import { Cluster, Connection, Keypair, PublicKey } from '@solana/web3.js';
|
||||
import copy from 'fast-copy';
|
||||
import { cpuUsage } from 'process';
|
||||
import { Group } from '../../src/accounts/group';
|
||||
import { HealthCache } from '../../src/accounts/healthCache';
|
||||
import { HealthType, MangoAccount } from '../../src/accounts/mangoAccount';
|
||||
import { PerpMarket } from '../../src/accounts/perp';
|
||||
import { Serum3Market } from '../../src/accounts/serum3';
|
||||
import { MangoAccount } from '../../src/accounts/mangoAccount';
|
||||
import { MangoClient } from '../../src/client';
|
||||
import { MANGO_V4_ID } from '../../src/constants';
|
||||
import { ZERO_I80F48 } from '../../src/numbers/I80F48';
|
||||
import { toUiDecimalsForQuote } from '../../src/utils';
|
||||
|
||||
const CLUSTER_URL =
|
||||
process.env.CLUSTER_URL_OVERRIDE || process.env.MB_CLUSTER_URL;
|
||||
|
@ -27,56 +20,56 @@ async function debugUser(
|
|||
group: Group,
|
||||
mangoAccount: MangoAccount,
|
||||
): Promise<void> {
|
||||
console.log(mangoAccount.toString(group));
|
||||
// console.log(mangoAccount.toString(group));
|
||||
|
||||
await mangoAccount.reload(client);
|
||||
|
||||
console.log(
|
||||
'mangoAccount.getEquity() ' +
|
||||
toUiDecimalsForQuote(mangoAccount.getEquity(group)!.toNumber()),
|
||||
);
|
||||
console.log(
|
||||
'mangoAccount.getHealth(HealthType.init) ' +
|
||||
toUiDecimalsForQuote(
|
||||
mangoAccount.getHealth(group, HealthType.init)!.toNumber(),
|
||||
),
|
||||
);
|
||||
console.log(
|
||||
'HealthCache.fromMangoAccount(group,mangoAccount).health(HealthType.init) ' +
|
||||
toUiDecimalsForQuote(
|
||||
HealthCache.fromMangoAccount(group, mangoAccount)
|
||||
.health(HealthType.init)
|
||||
.toNumber(),
|
||||
),
|
||||
);
|
||||
console.log(
|
||||
'mangoAccount.getHealthRatio(HealthType.init) ' +
|
||||
mangoAccount.getHealthRatio(group, HealthType.init)!.toNumber(),
|
||||
);
|
||||
console.log(
|
||||
'mangoAccount.getHealthRatioUi(HealthType.init) ' +
|
||||
mangoAccount.getHealthRatioUi(group, HealthType.init),
|
||||
);
|
||||
console.log(
|
||||
'mangoAccount.getHealthRatio(HealthType.maint) ' +
|
||||
mangoAccount.getHealthRatio(group, HealthType.maint)!.toNumber(),
|
||||
);
|
||||
console.log(
|
||||
'mangoAccount.getHealthRatioUi(HealthType.maint) ' +
|
||||
mangoAccount.getHealthRatioUi(group, HealthType.maint),
|
||||
);
|
||||
console.log(
|
||||
'mangoAccount.getCollateralValue() ' +
|
||||
toUiDecimalsForQuote(mangoAccount.getCollateralValue(group)!.toNumber()),
|
||||
);
|
||||
console.log(
|
||||
'mangoAccount.getAssetsValue() ' +
|
||||
toUiDecimalsForQuote(mangoAccount.getAssetsValue(group)!.toNumber()),
|
||||
);
|
||||
console.log(
|
||||
'mangoAccount.getLiabsValue() ' +
|
||||
toUiDecimalsForQuote(mangoAccount.getLiabsValue(group)!.toNumber()),
|
||||
);
|
||||
// console.log(
|
||||
// 'mangoAccount.getEquity() ' +
|
||||
// toUiDecimalsForQuote(mangoAccount.getEquity(group)!.toNumber()),
|
||||
// );
|
||||
// console.log(
|
||||
// 'mangoAccount.getHealth(HealthType.init) ' +
|
||||
// toUiDecimalsForQuote(
|
||||
// mangoAccount.getHealth(group, HealthType.init)!.toNumber(),
|
||||
// ),
|
||||
// );
|
||||
// console.log(
|
||||
// 'HealthCache.fromMangoAccount(group,mangoAccount).health(HealthType.init) ' +
|
||||
// toUiDecimalsForQuote(
|
||||
// HealthCache.fromMangoAccount(group, mangoAccount)
|
||||
// .health(HealthType.init)
|
||||
// .toNumber(),
|
||||
// ),
|
||||
// );
|
||||
// console.log(
|
||||
// 'mangoAccount.getHealthRatio(HealthType.init) ' +
|
||||
// mangoAccount.getHealthRatio(group, HealthType.init)!.toNumber(),
|
||||
// );
|
||||
// console.log(
|
||||
// 'mangoAccount.getHealthRatioUi(HealthType.init) ' +
|
||||
// mangoAccount.getHealthRatioUi(group, HealthType.init),
|
||||
// );
|
||||
// console.log(
|
||||
// 'mangoAccount.getHealthRatio(HealthType.maint) ' +
|
||||
// mangoAccount.getHealthRatio(group, HealthType.maint)!.toNumber(),
|
||||
// );
|
||||
// console.log(
|
||||
// 'mangoAccount.getHealthRatioUi(HealthType.maint) ' +
|
||||
// mangoAccount.getHealthRatioUi(group, HealthType.maint),
|
||||
// );
|
||||
// console.log(
|
||||
// 'mangoAccount.getCollateralValue() ' +
|
||||
// toUiDecimalsForQuote(mangoAccount.getCollateralValue(group)!.toNumber()),
|
||||
// );
|
||||
// console.log(
|
||||
// 'mangoAccount.getAssetsValue() ' +
|
||||
// toUiDecimalsForQuote(mangoAccount.getAssetsValue(group)!.toNumber()),
|
||||
// );
|
||||
// console.log(
|
||||
// 'mangoAccount.getLiabsValue() ' +
|
||||
// toUiDecimalsForQuote(mangoAccount.getLiabsValue(group)!.toNumber()),
|
||||
// );
|
||||
|
||||
async function getMaxWithdrawWithBorrowForTokenUiWrapper(
|
||||
token,
|
||||
|
@ -93,178 +86,178 @@ async function debugUser(
|
|||
await getMaxWithdrawWithBorrowForTokenUiWrapper(srcToken);
|
||||
}
|
||||
|
||||
function getMaxSourceForTokenSwapWrapper(src, tgt): void {
|
||||
// Turn on for debugging specific pairs
|
||||
// if (src != 'USDC' || tgt != 'MNGO') return;
|
||||
// function getMaxSourceForTokenSwapWrapper(src, tgt): void {
|
||||
// // Turn on for debugging specific pairs
|
||||
// // if (src != 'USDC' || tgt != 'MNGO') return;
|
||||
|
||||
let maxSourceUi;
|
||||
try {
|
||||
maxSourceUi = mangoAccount.getMaxSourceUiForTokenSwap(
|
||||
group,
|
||||
group.banksMapByName.get(src)![0].mint,
|
||||
group.banksMapByName.get(tgt)![0].mint,
|
||||
);
|
||||
} catch (error) {
|
||||
console.log(`Error for ${src}->${tgt}, ` + error.toString());
|
||||
}
|
||||
// let maxSourceUi;
|
||||
// try {
|
||||
// maxSourceUi = mangoAccount.getMaxSourceUiForTokenSwap(
|
||||
// group,
|
||||
// group.banksMapByName.get(src)![0].mint,
|
||||
// group.banksMapByName.get(tgt)![0].mint,
|
||||
// );
|
||||
// } catch (error) {
|
||||
// console.log(`Error for ${src}->${tgt}, ` + error.toString());
|
||||
// }
|
||||
|
||||
const maxTargetUi =
|
||||
maxSourceUi *
|
||||
(group.banksMapByName.get(src)![0].uiPrice /
|
||||
group.banksMapByName.get(tgt)![0].uiPrice);
|
||||
// const maxTargetUi =
|
||||
// maxSourceUi *
|
||||
// (group.banksMapByName.get(src)![0].uiPrice /
|
||||
// group.banksMapByName.get(tgt)![0].uiPrice);
|
||||
|
||||
const sim = mangoAccount.simHealthRatioWithTokenPositionUiChanges(group, [
|
||||
{
|
||||
mintPk: group.banksMapByName.get(src)![0].mint,
|
||||
uiTokenAmount: -maxSourceUi,
|
||||
},
|
||||
{
|
||||
mintPk: group.banksMapByName.get(tgt)![0].mint,
|
||||
uiTokenAmount: maxTargetUi,
|
||||
},
|
||||
]);
|
||||
console.log(
|
||||
`getMaxSourceForTokenSwap ${src.padEnd(4)} ${tgt.padEnd(4)} ` +
|
||||
maxSourceUi.toFixed(3).padStart(10) +
|
||||
`, health ratio after (${sim.toFixed(3).padStart(10)})`,
|
||||
);
|
||||
}
|
||||
for (const srcToken of Array.from(group.banksMapByName.keys()).sort()) {
|
||||
for (const tgtToken of Array.from(group.banksMapByName.keys()).sort()) {
|
||||
getMaxSourceForTokenSwapWrapper(srcToken, tgtToken);
|
||||
}
|
||||
}
|
||||
// const sim = mangoAccount.simHealthRatioWithTokenPositionUiChanges(group, [
|
||||
// {
|
||||
// mintPk: group.banksMapByName.get(src)![0].mint,
|
||||
// uiTokenAmount: -maxSourceUi,
|
||||
// },
|
||||
// {
|
||||
// mintPk: group.banksMapByName.get(tgt)![0].mint,
|
||||
// uiTokenAmount: maxTargetUi,
|
||||
// },
|
||||
// ]);
|
||||
// console.log(
|
||||
// `getMaxSourceForTokenSwap ${src.padEnd(4)} ${tgt.padEnd(4)} ` +
|
||||
// maxSourceUi.toFixed(3).padStart(10) +
|
||||
// `, health ratio after (${sim.toFixed(3).padStart(10)})`,
|
||||
// );
|
||||
// }
|
||||
// for (const srcToken of Array.from(group.banksMapByName.keys()).sort()) {
|
||||
// for (const tgtToken of Array.from(group.banksMapByName.keys()).sort()) {
|
||||
// getMaxSourceForTokenSwapWrapper(srcToken, tgtToken);
|
||||
// }
|
||||
// }
|
||||
|
||||
function getMaxForPerpWrapper(perpMarket: PerpMarket): void {
|
||||
const maxQuoteUi = mangoAccount.getMaxQuoteForPerpBidUi(
|
||||
group,
|
||||
perpMarket.perpMarketIndex,
|
||||
);
|
||||
const simMaxQuote = mangoAccount.simHealthRatioWithPerpBidUiChanges(
|
||||
group,
|
||||
perpMarket.perpMarketIndex,
|
||||
maxQuoteUi / perpMarket.uiPrice,
|
||||
);
|
||||
const maxBaseUi = mangoAccount.getMaxBaseForPerpAskUi(
|
||||
group,
|
||||
perpMarket.perpMarketIndex,
|
||||
);
|
||||
const simMaxBase = mangoAccount.simHealthRatioWithPerpAskUiChanges(
|
||||
group,
|
||||
perpMarket.perpMarketIndex,
|
||||
maxBaseUi,
|
||||
);
|
||||
console.log(
|
||||
`getMaxPerp ${perpMarket.name.padStart(
|
||||
10,
|
||||
)} getMaxQuoteForPerpBidUi ${maxQuoteUi
|
||||
.toFixed(3)
|
||||
.padStart(10)} health ratio after (${simMaxQuote
|
||||
.toFixed(3)
|
||||
.padStart(10)}), getMaxBaseForPerpAskUi ${maxBaseUi
|
||||
.toFixed(3)
|
||||
.padStart(10)} health ratio after (${simMaxBase
|
||||
.toFixed(3)
|
||||
.padStart(10)})`,
|
||||
);
|
||||
}
|
||||
for (const perpMarket of Array.from(
|
||||
group.perpMarketsMapByMarketIndex.values(),
|
||||
)) {
|
||||
getMaxForPerpWrapper(perpMarket);
|
||||
}
|
||||
// function getMaxForPerpWrapper(perpMarket: PerpMarket): void {
|
||||
// const maxQuoteUi = mangoAccount.getMaxQuoteForPerpBidUi(
|
||||
// group,
|
||||
// perpMarket.perpMarketIndex,
|
||||
// );
|
||||
// const simMaxQuote = mangoAccount.simHealthRatioWithPerpBidUiChanges(
|
||||
// group,
|
||||
// perpMarket.perpMarketIndex,
|
||||
// maxQuoteUi / perpMarket.uiPrice,
|
||||
// );
|
||||
// const maxBaseUi = mangoAccount.getMaxBaseForPerpAskUi(
|
||||
// group,
|
||||
// perpMarket.perpMarketIndex,
|
||||
// );
|
||||
// const simMaxBase = mangoAccount.simHealthRatioWithPerpAskUiChanges(
|
||||
// group,
|
||||
// perpMarket.perpMarketIndex,
|
||||
// maxBaseUi,
|
||||
// );
|
||||
// console.log(
|
||||
// `getMaxPerp ${perpMarket.name.padStart(
|
||||
// 10,
|
||||
// )} getMaxQuoteForPerpBidUi ${maxQuoteUi
|
||||
// .toFixed(3)
|
||||
// .padStart(10)} health ratio after (${simMaxQuote
|
||||
// .toFixed(3)
|
||||
// .padStart(10)}), getMaxBaseForPerpAskUi ${maxBaseUi
|
||||
// .toFixed(3)
|
||||
// .padStart(10)} health ratio after (${simMaxBase
|
||||
// .toFixed(3)
|
||||
// .padStart(10)})`,
|
||||
// );
|
||||
// }
|
||||
// for (const perpMarket of Array.from(
|
||||
// group.perpMarketsMapByMarketIndex.values(),
|
||||
// )) {
|
||||
// getMaxForPerpWrapper(perpMarket);
|
||||
// }
|
||||
|
||||
function getMaxForSerum3Wrapper(serum3Market: Serum3Market): void {
|
||||
console.log(
|
||||
`getMaxQuoteForSerum3BidUi ${serum3Market.name} ` +
|
||||
mangoAccount.getMaxQuoteForSerum3BidUi(
|
||||
group,
|
||||
serum3Market.serumMarketExternal,
|
||||
),
|
||||
);
|
||||
console.log(
|
||||
`- simHealthRatioWithSerum3BidUiChanges ${serum3Market.name} ` +
|
||||
mangoAccount.simHealthRatioWithSerum3BidUiChanges(
|
||||
group,
|
||||
mangoAccount.getMaxQuoteForSerum3BidUi(
|
||||
group,
|
||||
serum3Market.serumMarketExternal,
|
||||
),
|
||||
serum3Market.serumMarketExternal,
|
||||
HealthType.init,
|
||||
),
|
||||
);
|
||||
console.log(
|
||||
`getMaxBaseForSerum3AskUi ${serum3Market.name} ` +
|
||||
mangoAccount.getMaxBaseForSerum3AskUi(
|
||||
group,
|
||||
serum3Market.serumMarketExternal,
|
||||
),
|
||||
);
|
||||
console.log(
|
||||
`- simHealthRatioWithSerum3BidUiChanges ${serum3Market.name} ` +
|
||||
mangoAccount.simHealthRatioWithSerum3AskUiChanges(
|
||||
group,
|
||||
mangoAccount.getMaxBaseForSerum3AskUi(
|
||||
group,
|
||||
serum3Market.serumMarketExternal,
|
||||
),
|
||||
serum3Market.serumMarketExternal,
|
||||
HealthType.init,
|
||||
),
|
||||
);
|
||||
}
|
||||
for (const serum3Market of Array.from(
|
||||
group.serum3MarketsMapByExternal.values(),
|
||||
)) {
|
||||
getMaxForSerum3Wrapper(serum3Market);
|
||||
}
|
||||
// function getMaxForSerum3Wrapper(serum3Market: Serum3Market): void {
|
||||
// console.log(
|
||||
// `getMaxQuoteForSerum3BidUi ${serum3Market.name} ` +
|
||||
// mangoAccount.getMaxQuoteForSerum3BidUi(
|
||||
// group,
|
||||
// serum3Market.serumMarketExternal,
|
||||
// ),
|
||||
// );
|
||||
// console.log(
|
||||
// `- simHealthRatioWithSerum3BidUiChanges ${serum3Market.name} ` +
|
||||
// mangoAccount.simHealthRatioWithSerum3BidUiChanges(
|
||||
// group,
|
||||
// mangoAccount.getMaxQuoteForSerum3BidUi(
|
||||
// group,
|
||||
// serum3Market.serumMarketExternal,
|
||||
// ),
|
||||
// serum3Market.serumMarketExternal,
|
||||
// HealthType.init,
|
||||
// ),
|
||||
// );
|
||||
// console.log(
|
||||
// `getMaxBaseForSerum3AskUi ${serum3Market.name} ` +
|
||||
// mangoAccount.getMaxBaseForSerum3AskUi(
|
||||
// group,
|
||||
// serum3Market.serumMarketExternal,
|
||||
// ),
|
||||
// );
|
||||
// console.log(
|
||||
// `- simHealthRatioWithSerum3BidUiChanges ${serum3Market.name} ` +
|
||||
// mangoAccount.simHealthRatioWithSerum3AskUiChanges(
|
||||
// group,
|
||||
// mangoAccount.getMaxBaseForSerum3AskUi(
|
||||
// group,
|
||||
// serum3Market.serumMarketExternal,
|
||||
// ),
|
||||
// serum3Market.serumMarketExternal,
|
||||
// HealthType.init,
|
||||
// ),
|
||||
// );
|
||||
// }
|
||||
// for (const serum3Market of Array.from(
|
||||
// group.serum3MarketsMapByExternal.values(),
|
||||
// )) {
|
||||
// getMaxForSerum3Wrapper(serum3Market);
|
||||
// }
|
||||
|
||||
// Liquidation price for perp positions
|
||||
for (const pp of mangoAccount.perpActive()) {
|
||||
const pm = group.getPerpMarketByMarketIndex(pp.marketIndex);
|
||||
const health = toUiDecimalsForQuote(
|
||||
mangoAccount.getHealth(group, HealthType.maint),
|
||||
);
|
||||
// // Liquidation price for perp positions
|
||||
// for (const pp of mangoAccount.perpActive()) {
|
||||
// const pm = group.getPerpMarketByMarketIndex(pp.marketIndex);
|
||||
// const health = toUiDecimalsForQuote(
|
||||
// mangoAccount.getHealth(group, HealthType.maint),
|
||||
// );
|
||||
|
||||
if (
|
||||
// pp.getNotionalValueUi(pm) > 1000 &&
|
||||
// !(pp.getNotionalValueUi(pm) < health && pp.getBasePosition(pm).isPos())
|
||||
// eslint-disable-next-line no-constant-condition
|
||||
true
|
||||
) {
|
||||
const then = Date.now();
|
||||
const startUsage = cpuUsage();
|
||||
// if (
|
||||
// // pp.getNotionalValueUi(pm) > 1000 &&
|
||||
// // !(pp.getNotionalValueUi(pm) < health && pp.getBasePosition(pm).isPos())
|
||||
// // eslint-disable-next-line no-constant-condition
|
||||
// true
|
||||
// ) {
|
||||
// const then = Date.now();
|
||||
// const startUsage = cpuUsage();
|
||||
|
||||
const lp = await pp.getLiquidationPrice(group, mangoAccount);
|
||||
if (lp == null || lp.lt(ZERO_I80F48())) {
|
||||
continue;
|
||||
}
|
||||
const lpUi = group
|
||||
.getPerpMarketByMarketIndex(pp.marketIndex)
|
||||
.priceNativeToUi(lp.toNumber());
|
||||
// const lp = await pp.getLiquidationPrice(group, mangoAccount);
|
||||
// if (lp == null || lp.lt(ZERO_I80F48())) {
|
||||
// continue;
|
||||
// }
|
||||
// const lpUi = group
|
||||
// .getPerpMarketByMarketIndex(pp.marketIndex)
|
||||
// .priceNativeToUi(lp.toNumber());
|
||||
|
||||
const gClone: Group = copy(group);
|
||||
gClone.getPerpMarketByMarketIndex(pm.perpMarketIndex)._price = lp;
|
||||
// const gClone: Group = copy(group);
|
||||
// gClone.getPerpMarketByMarketIndex(pm.perpMarketIndex)._price = lp;
|
||||
|
||||
const simHealth = toUiDecimalsForQuote(
|
||||
mangoAccount.getHealth(gClone, HealthType.maint),
|
||||
);
|
||||
// const simHealth = toUiDecimalsForQuote(
|
||||
// mangoAccount.getHealth(gClone, HealthType.maint),
|
||||
// );
|
||||
|
||||
const now = Date.now();
|
||||
const endUsage = cpuUsage(startUsage);
|
||||
// const now = Date.now();
|
||||
// const endUsage = cpuUsage(startUsage);
|
||||
|
||||
console.log(
|
||||
` - ${pm.name}, health: ${health.toLocaleString()}, side: ${
|
||||
pp.getBasePosition(pm).isPos() ? 'LONG' : 'SHORT'
|
||||
}, notional: ${pp
|
||||
.getNotionalValueUi(pm)
|
||||
.toLocaleString()}, liq price: ${lpUi.toLocaleString()}, sim health: ${simHealth.toLocaleString()}, time ${
|
||||
now - then
|
||||
}ms, cpu usage ${(endUsage['user'] / 1000).toLocaleString()}ms`,
|
||||
);
|
||||
}
|
||||
}
|
||||
// console.log(
|
||||
// ` - ${pm.name}, health: ${health.toLocaleString()}, side: ${
|
||||
// pp.getBasePosition(pm).isPos() ? 'LONG' : 'SHORT'
|
||||
// }, notional: ${pp
|
||||
// .getNotionalValueUi(pm)
|
||||
// .toLocaleString()}, liq price: ${lpUi.toLocaleString()}, sim health: ${simHealth.toLocaleString()}, time ${
|
||||
// now - then
|
||||
// }ms, cpu usage ${(endUsage['user'] / 1000).toLocaleString()}ms`,
|
||||
// );
|
||||
// }
|
||||
// }
|
||||
}
|
||||
|
||||
async function main(): Promise<void> {
|
||||
|
|
|
@ -94,6 +94,7 @@ async function main() {
|
|||
group,
|
||||
usdcDevnetMint,
|
||||
usdcDevnetOracle.publicKey,
|
||||
PublicKey.default,
|
||||
0, // tokenIndex
|
||||
'USDC',
|
||||
{
|
||||
|
@ -124,6 +125,7 @@ async function main() {
|
|||
group,
|
||||
solDevnetMint,
|
||||
solDevnetOracle,
|
||||
PublicKey.default,
|
||||
1, // tokenIndex
|
||||
'SOL',
|
||||
{
|
||||
|
@ -218,6 +220,7 @@ async function main() {
|
|||
1.0,
|
||||
2 * 60 * 60,
|
||||
0.025,
|
||||
0,
|
||||
);
|
||||
await group.reloadAll(client);
|
||||
const perpMarket = group.getPerpMarketByMarketIndex(0 as PerpMarketIndex);
|
||||
|
|
|
@ -206,6 +206,7 @@ async function registerTokens() {
|
|||
group,
|
||||
usdcMainnetMint,
|
||||
usdcMainnetOracle.publicKey,
|
||||
PublicKey.default,
|
||||
0,
|
||||
'USDC',
|
||||
{
|
||||
|
@ -226,6 +227,7 @@ async function registerTokens() {
|
|||
group,
|
||||
usdtMainnetMint,
|
||||
usdtMainnetOracle,
|
||||
PublicKey.default,
|
||||
1,
|
||||
'USDT',
|
||||
{
|
||||
|
@ -246,6 +248,7 @@ async function registerTokens() {
|
|||
group,
|
||||
daiMainnetMint,
|
||||
daiMainnetOracle,
|
||||
PublicKey.default,
|
||||
2,
|
||||
'DAI',
|
||||
{
|
||||
|
@ -266,6 +269,7 @@ async function registerTokens() {
|
|||
group,
|
||||
ethMainnetMint,
|
||||
ethMainnetOracle,
|
||||
PublicKey.default,
|
||||
3,
|
||||
'ETH',
|
||||
{
|
||||
|
@ -286,6 +290,7 @@ async function registerTokens() {
|
|||
group,
|
||||
solMainnetMint,
|
||||
solMainnetOracle,
|
||||
PublicKey.default,
|
||||
4,
|
||||
'SOL',
|
||||
{
|
||||
|
@ -306,6 +311,7 @@ async function registerTokens() {
|
|||
group,
|
||||
msolMainnetMint,
|
||||
msolMainnetOracle,
|
||||
PublicKey.default,
|
||||
5,
|
||||
'MSOL',
|
||||
{
|
||||
|
@ -450,6 +456,7 @@ async function registerPerpMarkets() {
|
|||
1.0,
|
||||
2 * 60 * 60,
|
||||
0.025,
|
||||
0,
|
||||
);
|
||||
|
||||
await client.perpCreateMarket(
|
||||
|
@ -482,6 +489,7 @@ async function registerPerpMarkets() {
|
|||
1.0,
|
||||
2 * 60 * 60,
|
||||
0.2, // 20% positive pnl liquidation fee?
|
||||
0,
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -60,31 +60,31 @@ async function buildClient(): Promise<MangoClient> {
|
|||
);
|
||||
}
|
||||
|
||||
async function groupEdit(): Promise<void> {
|
||||
const client = await buildClient();
|
||||
const group = await client.getGroup(new PublicKey(GROUP_PK));
|
||||
const ix = await client.program.methods
|
||||
.groupEdit(
|
||||
null, // admin
|
||||
null, // fastListingAdmin
|
||||
null, // securityAdmin
|
||||
null, // testing
|
||||
null, // version
|
||||
null, // depositLimitQuote
|
||||
null, // feesPayWithMngo
|
||||
null, // feesMngoBonusRate
|
||||
null, // feesSwapMangoAccount
|
||||
6, // feesMngoTokenIndex
|
||||
null, // feesExpiryInterval
|
||||
5, // allowedFastListingsPerInterval
|
||||
)
|
||||
.accounts({
|
||||
group: group.publicKey,
|
||||
admin: group.admin,
|
||||
})
|
||||
.instruction();
|
||||
console.log(serializeInstructionToBase64(ix));
|
||||
}
|
||||
// async function groupEdit(): Promise<void> {
|
||||
// const client = await buildClient();
|
||||
// const group = await client.getGroup(new PublicKey(GROUP_PK));
|
||||
// const ix = await client.program.methods
|
||||
// .groupEdit(
|
||||
// null, // admin
|
||||
// null, // fastListingAdmin
|
||||
// null, // securityAdmin
|
||||
// null, // testing
|
||||
// null, // version
|
||||
// null, // depositLimitQuote
|
||||
// null, // feesPayWithMngo
|
||||
// null, // feesMngoBonusRate
|
||||
// null, // feesSwapMangoAccount
|
||||
// 6, // feesMngoTokenIndex
|
||||
// null, // feesExpiryInterval
|
||||
// 5, // allowedFastListingsPerInterval
|
||||
// )
|
||||
// .accounts({
|
||||
// group: group.publicKey,
|
||||
// admin: group.admin,
|
||||
// })
|
||||
// .instruction();
|
||||
// console.log(serializeInstructionToBase64(ix));
|
||||
// }
|
||||
|
||||
// async function tokenRegister(): Promise<void> {
|
||||
// const client = await buildClient();
|
||||
|
@ -265,6 +265,7 @@ async function perpCreate(): Promise<void> {
|
|||
1,
|
||||
new BN(60 * 60),
|
||||
percentageToDecimal(10),
|
||||
0,
|
||||
)
|
||||
.accounts({
|
||||
group: group.publicKey,
|
||||
|
@ -358,6 +359,7 @@ async function perpEdit(): Promise<void> {
|
|||
params.positivePnlLiquidationFee,
|
||||
params.name,
|
||||
params.forceClose,
|
||||
params.platformLiquidationFee,
|
||||
)
|
||||
.accounts({
|
||||
group: group.publicKey,
|
||||
|
@ -466,7 +468,7 @@ async function idlSetAuthority(): Promise<void> {
|
|||
|
||||
async function main(): Promise<void> {
|
||||
try {
|
||||
await groupEdit();
|
||||
// await groupEdit();
|
||||
// await tokenRegister();
|
||||
// await tokenEdit();
|
||||
// await perpCreate();
|
||||
|
|
|
@ -18,9 +18,12 @@ import {
|
|||
Transaction,
|
||||
TransactionInstruction,
|
||||
} from '@solana/web3.js';
|
||||
import chunk from 'lodash/chunk';
|
||||
import { chunk } from 'lodash';
|
||||
import { updateVoterWeightRecord } from './updateVoteWeightRecord';
|
||||
import { VsrClient } from './voteStakeRegistryClient';
|
||||
import { createComputeBudgetIx } from '../../src/utils/rpc';
|
||||
import { sendSignAndConfirmTransactions } from '@blockworks-foundation/mangolana/lib/transactions';
|
||||
import { SequenceType } from '@blockworks-foundation/mangolana/lib/globalTypes';
|
||||
|
||||
export const MANGO_MINT = 'MangoCzJ36AjZyKwVj3VnYU4GTonjfVEnJmvvWaxLac';
|
||||
export const MANGO_REALM_PK = new PublicKey(
|
||||
|
@ -98,23 +101,27 @@ export const createProposal = async (
|
|||
|
||||
const insertInstructions: TransactionInstruction[] = [];
|
||||
for (const i in proposalInstructions) {
|
||||
const instruction = getInstructionDataFromBase64(
|
||||
serializeInstructionToBase64(proposalInstructions[i]),
|
||||
);
|
||||
await withInsertTransaction(
|
||||
insertInstructions,
|
||||
MANGO_GOVERNANCE_PROGRAM,
|
||||
programVersion,
|
||||
governance,
|
||||
proposalAddress,
|
||||
tokenOwnerRecord.pubkey,
|
||||
governanceAuthority,
|
||||
Number(i),
|
||||
0,
|
||||
0,
|
||||
[instruction],
|
||||
payer,
|
||||
);
|
||||
try {
|
||||
const instruction = getInstructionDataFromBase64(
|
||||
serializeInstructionToBase64(proposalInstructions[i]),
|
||||
);
|
||||
await withInsertTransaction(
|
||||
insertInstructions,
|
||||
MANGO_GOVERNANCE_PROGRAM,
|
||||
programVersion,
|
||||
governance,
|
||||
proposalAddress,
|
||||
tokenOwnerRecord.pubkey,
|
||||
governanceAuthority,
|
||||
Number(i),
|
||||
0,
|
||||
0,
|
||||
[instruction],
|
||||
payer,
|
||||
);
|
||||
} catch (e) {
|
||||
console.log(e, '@@@@@@@');
|
||||
}
|
||||
}
|
||||
if (signOff) {
|
||||
const signatoryRecordAddress = await getSignatoryRecordAddress(
|
||||
|
@ -137,28 +144,29 @@ export const createProposal = async (
|
|||
|
||||
const txChunks = chunk([...instructions, ...insertInstructions], 2);
|
||||
|
||||
const transactions: Transaction[] = [];
|
||||
const latestBlockhash = await connection.getLatestBlockhash('confirmed');
|
||||
for (const chunk of txChunks) {
|
||||
const tx = new Transaction();
|
||||
tx.add(...chunk);
|
||||
tx.lastValidBlockHeight = latestBlockhash.lastValidBlockHeight;
|
||||
tx.recentBlockhash = latestBlockhash.blockhash;
|
||||
tx.feePayer = payer;
|
||||
transactions.push(tx);
|
||||
}
|
||||
await sendSignAndConfirmTransactions({
|
||||
connection,
|
||||
wallet,
|
||||
transactionInstructions: txChunks.map((txChunk) => ({
|
||||
instructionsSet: [
|
||||
{
|
||||
signers: [],
|
||||
transactionInstruction: createComputeBudgetIx(80000),
|
||||
},
|
||||
...txChunk.map((tx) => ({
|
||||
signers: [],
|
||||
transactionInstruction: tx,
|
||||
})),
|
||||
],
|
||||
sequenceType: SequenceType.Sequential,
|
||||
})),
|
||||
config: {
|
||||
maxRetries: 5,
|
||||
autoRetry: true,
|
||||
maxTxesInBatch: 20,
|
||||
logFlowInfo: true,
|
||||
},
|
||||
});
|
||||
|
||||
const signedTransactions = await wallet.signAllTransactions(transactions);
|
||||
for (const tx of signedTransactions) {
|
||||
const rawTransaction = tx.serialize();
|
||||
const address = await connection.sendRawTransaction(rawTransaction, {
|
||||
skipPreflight: true,
|
||||
});
|
||||
await connection.confirmTransaction({
|
||||
blockhash: latestBlockhash.blockhash,
|
||||
lastValidBlockHeight: latestBlockhash.lastValidBlockHeight,
|
||||
signature: address,
|
||||
});
|
||||
}
|
||||
return proposalAddress;
|
||||
};
|
||||
|
|
|
@ -202,7 +202,7 @@ async function main(): Promise<void> {
|
|||
|
||||
const genericBanks = ['MNGO', 'MSOL'];
|
||||
let nextTokenIndex = 3;
|
||||
for (let name of genericBanks) {
|
||||
for (const name of genericBanks) {
|
||||
console.log(`Registering ${name}...`);
|
||||
const mint = new PublicKey(MAINNET_MINTS.get(name)!);
|
||||
const oracle = oracles.get(name);
|
||||
|
@ -239,7 +239,7 @@ async function main(): Promise<void> {
|
|||
}
|
||||
|
||||
let nextSerumMarketIndex = 0;
|
||||
for (let [name, mint] of MAINNET_MINTS) {
|
||||
for (const [name, mint] of MAINNET_MINTS) {
|
||||
if (name == 'USDC') {
|
||||
continue;
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { AnchorProvider, BN, Wallet } from '@coral-xyz/anchor';
|
||||
import { Cluster, Connection, Keypair, PublicKey } from '@solana/web3.js';
|
||||
import * as splToken from '@solana/spl-token';
|
||||
import { Cluster, Connection, Keypair, PublicKey } from '@solana/web3.js';
|
||||
import fs from 'fs';
|
||||
import { Bank } from '../../src/accounts/bank';
|
||||
import {
|
||||
|
@ -280,6 +280,7 @@ async function main() {
|
|||
group,
|
||||
newMint,
|
||||
newOracle.publicKey,
|
||||
PublicKey.default,
|
||||
newTokenIndex,
|
||||
'TMP',
|
||||
{
|
||||
|
|
|
@ -143,6 +143,7 @@ async function registerTokens(): Promise<void> {
|
|||
group,
|
||||
usdcMainnetMint,
|
||||
usdcMainnetOracle,
|
||||
PublicKey.default,
|
||||
0,
|
||||
'USDC',
|
||||
defaultTokenParams,
|
||||
|
|
|
@ -0,0 +1,140 @@
|
|||
// import { createComputeBudgetIx } from '@blockworks-foundation/mango-v4';
|
||||
// import { PublicKey, chunk } from '@metaplex-foundation/js';
|
||||
// import { bs58 } from '@project-serum/anchor/dist/cjs/utils/bytes';
|
||||
// import { Connection, Keypair, Transaction } from '@solana/web3.js';
|
||||
// import {
|
||||
// SwitchboardProgram,
|
||||
// QueueAccount,
|
||||
// CrankAccount,
|
||||
// AggregatorAccount,
|
||||
// } from '@switchboard-xyz/solana.js';
|
||||
// import { MANGO_DAO_WALLET } from './governance/constants';
|
||||
// import { OracleJob } from '@switchboard-xyz/common';
|
||||
// import { Wallet } from '@coral-xyz/anchor';
|
||||
// import { awaitTransactionSignatureConfirmation } from '@blockworks-foundation/mangolana/lib/transactions';
|
||||
|
||||
// const newOracleName = 'NOS/USD';
|
||||
// const oldOraclePk = '2FGoL9PNhNGpduRKLsTa4teRaX3vfarXAc1an2KyXxQm';
|
||||
|
||||
// const SWITCHBOARD_PERMISSIONLESS_QUE =
|
||||
// '5JYwqvKkqp35w8Nq3ba4z1WYUeJQ1rB36V8XvaGp6zn1';
|
||||
// const SWITCHBOARD_PERMISSIONLESS_CRANK =
|
||||
// 'BKtF8yyQsj3Ft6jb2nkfpEKzARZVdGgdEPs6mFmZNmbA';
|
||||
|
||||
// async function run() {
|
||||
// const WALLET = new Wallet(Keypair.fromSecretKey(bs58.decode('')));
|
||||
// const connection = new Connection('https://api.mngo.cloud/lite-rpc/v1/');
|
||||
// const program = await SwitchboardProgram.load(connection);
|
||||
// const payer = WALLET.publicKey;
|
||||
|
||||
// const [[queueAccount], [crankAccount]] = await Promise.all([
|
||||
// QueueAccount.load(program, SWITCHBOARD_PERMISSIONLESS_QUE),
|
||||
// CrankAccount.load(program, SWITCHBOARD_PERMISSIONLESS_CRANK),
|
||||
// ]);
|
||||
|
||||
// const [aggregatorAccountOld, aggregatorAccountDataOld] =
|
||||
// await AggregatorAccount.load(program, new PublicKey(oldOraclePk));
|
||||
|
||||
// const jobs = await aggregatorAccountOld.loadJobs(aggregatorAccountDataOld);
|
||||
// const newJobs: string[] = [];
|
||||
// for (const job of jobs) {
|
||||
// const jobYaml = job.job.toYaml();
|
||||
// const remove_after = jobYaml.indexOf('- multiplyTask');
|
||||
// let result = jobYaml.substring(0, remove_after);
|
||||
// result =
|
||||
// result +
|
||||
// `- conditionalTask:
|
||||
// attempt:
|
||||
// - multiplyTask:
|
||||
// job:
|
||||
// tasks:
|
||||
// - oracleTask:
|
||||
// pythAddress: Gnt27xtC473ZT2Mw5u8wZ68Z3gULkSTb5DuxJy7eJotD
|
||||
// pythAllowedConfidenceInterval: 10
|
||||
// onFailure:
|
||||
// - multiplyTask:
|
||||
// job:
|
||||
// tasks:
|
||||
// - oracleTask:
|
||||
// switchboardAddress: FwYfsmj5x8YZXtQBNo2Cz8TE7WRCMFqA6UTffK4xQKMH`;
|
||||
// newJobs.push(result);
|
||||
// }
|
||||
|
||||
// const [aggregatorAccountNew, txArray1] =
|
||||
// await queueAccount.createFeedInstructions(payer, {
|
||||
// name: newOracleName,
|
||||
// batchSize: aggregatorAccountDataOld.oracleRequestBatchSize,
|
||||
// minRequiredOracleResults: aggregatorAccountDataOld.minOracleResults,
|
||||
// minRequiredJobResults: aggregatorAccountDataOld.minJobResults,
|
||||
// minUpdateDelaySeconds: aggregatorAccountDataOld.minUpdateDelaySeconds,
|
||||
// forceReportPeriod: 60 * 60,
|
||||
// withdrawAuthority: MANGO_DAO_WALLET,
|
||||
// authority: payer,
|
||||
// crankDataBuffer: crankAccount.dataBuffer?.publicKey,
|
||||
// crankPubkey: crankAccount.publicKey,
|
||||
// fundAmount: 0.1,
|
||||
// slidingWindow: true,
|
||||
// disableCrank: false,
|
||||
// maxPriorityFeeMultiplier: 5,
|
||||
// priorityFeeBumpPeriod: 10,
|
||||
// priorityFeeBump: 1000,
|
||||
// basePriorityFee: 1000,
|
||||
// jobs: [
|
||||
// ...newJobs.map((x) => ({
|
||||
// weight: 1,
|
||||
// data: OracleJob.encodeDelimited(OracleJob.fromYaml(x)).finish(),
|
||||
// })),
|
||||
// ],
|
||||
// });
|
||||
|
||||
// const lockTx = aggregatorAccountNew.lockInstruction(payer, {});
|
||||
// const transferAuthIx = aggregatorAccountNew.setAuthorityInstruction(payer, {
|
||||
// newAuthority: MANGO_DAO_WALLET,
|
||||
// });
|
||||
// const latestBlockhash = await connection.getLatestBlockhash('processed');
|
||||
|
||||
// const txChunks = chunk([...txArray1, lockTx, transferAuthIx], 1);
|
||||
|
||||
// const transactions: Transaction[] = [];
|
||||
|
||||
// for (const chunkIndex in txChunks) {
|
||||
// const chunk = txChunks[chunkIndex];
|
||||
// const tx = new Transaction();
|
||||
// const singers = [...chunk.flatMap((x) => x.signers)];
|
||||
// tx.add(createComputeBudgetIx(800000));
|
||||
// tx.add(...chunk.flatMap((x) => x.ixns));
|
||||
// tx.lastValidBlockHeight = latestBlockhash.lastValidBlockHeight;
|
||||
// tx.recentBlockhash = latestBlockhash.blockhash;
|
||||
// tx.feePayer = payer;
|
||||
// if (singers.length) {
|
||||
// tx.sign(...singers);
|
||||
// }
|
||||
// transactions.push(tx);
|
||||
// }
|
||||
// const signedTxes = await WALLET.signAllTransactions(transactions);
|
||||
|
||||
// for (const signed of signedTxes) {
|
||||
// const rawTransaction = signed.serialize();
|
||||
// const signature = await connection.sendRawTransaction(rawTransaction, {
|
||||
// skipPreflight: true,
|
||||
// });
|
||||
// await awaitTransactionSignatureConfirmation({
|
||||
// txid: signature,
|
||||
// confirmLevel: 'processed',
|
||||
// connection: connection,
|
||||
// timeoutStrategy: {
|
||||
// block: latestBlockhash,
|
||||
// },
|
||||
// config: {
|
||||
// logFlowInfo: true,
|
||||
// },
|
||||
// });
|
||||
// }
|
||||
|
||||
// console.log(aggregatorAccountNew.publicKey.toBase58(), '@@@@@');
|
||||
// }
|
||||
// try {
|
||||
// run();
|
||||
// } catch (e) {
|
||||
// console.log(e);
|
||||
// }
|
|
@ -27,7 +27,6 @@ import { MangoAccount } from '../src/accounts/mangoAccount';
|
|||
import { MangoClient } from '../src/client';
|
||||
import { NullTokenEditParams } from '../src/clientIxParamBuilder';
|
||||
import { MANGO_V4_MAIN_GROUP as MANGO_V4_PRIMARY_GROUP } from '../src/constants';
|
||||
import { I80F48 } from '../src/numbers/I80F48';
|
||||
import {
|
||||
LiqorPriceImpact,
|
||||
buildGroupGrid,
|
||||
|
@ -190,8 +189,7 @@ async function updateTokenParams(): Promise<void> {
|
|||
const builder = Builder(NullTokenEditParams);
|
||||
let change = false;
|
||||
|
||||
// eslint-disable-next-line no-constant-condition
|
||||
if (true) {
|
||||
try {
|
||||
const tier = Object.values(LISTING_PRESETS).find((x) =>
|
||||
x.initLiabWeight.toFixed(1) === '1.8'
|
||||
? x.initLiabWeight.toFixed(1) ===
|
||||
|
@ -200,27 +198,75 @@ async function updateTokenParams(): Promise<void> {
|
|||
: x.initLiabWeight.toFixed(1) ===
|
||||
bank?.initLiabWeight.toNumber().toFixed(1),
|
||||
);
|
||||
if (!tier) {
|
||||
console.log(`${bank.name}, no tier found`);
|
||||
} else {
|
||||
console.log(
|
||||
`${bank.name.padStart(10)}, ${bank.loanFeeRate
|
||||
.mul(I80F48.fromNumber(100))
|
||||
.toFixed(2)}, ${bank.loanOriginationFeeRate
|
||||
.mul(I80F48.fromNumber(100))
|
||||
.toFixed(2)}, ${tier?.preset_name.padStart(5)}, ${(
|
||||
tier.loanFeeRate * 100
|
||||
).toFixed(2)}, ${(tier!.loanOriginationFeeRate * 100).toFixed(2)}`,
|
||||
);
|
||||
|
||||
builder.loanFeeRate(tier!.loanFeeRate);
|
||||
builder.loanOriginationFeeRate(tier!.loanOriginationFeeRate);
|
||||
builder.flashLoanSwapFeeRate(tier!.loanOriginationFeeRate);
|
||||
change = true;
|
||||
// eslint-disable-next-line no-constant-condition
|
||||
if (true) {
|
||||
if (
|
||||
bank.uiBorrows() == 0 &&
|
||||
bank.reduceOnly == 2 &&
|
||||
bank.initAssetWeight.toNumber() == 0 &&
|
||||
bank.maintAssetWeight.toNumber() == 0
|
||||
) {
|
||||
builder.disableAssetLiquidation(true);
|
||||
builder.oracleConfig({
|
||||
confFilter: 1000,
|
||||
maxStalenessSlots: -1,
|
||||
});
|
||||
change = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
// // eslint-disable-next-line no-constant-condition
|
||||
// if (true) {
|
||||
// if (bank.uiBorrows() == 0 && bank.reduceOnly == 1) {
|
||||
// builder.disableAssetLiquidation(true);
|
||||
// builder.forceWithdraw(true);
|
||||
// change = true;
|
||||
// }
|
||||
// }
|
||||
|
||||
// // eslint-disable-next-line no-constant-condition
|
||||
// if (true) {
|
||||
// if (!tier) {
|
||||
// console.log(`${bank.name}, no tier found`);
|
||||
// } else if (tier.preset_name != 'C') {
|
||||
// if (tier.preset_name.includes('A')) {
|
||||
// builder.liquidationFee(bank.liquidationFee.toNumber() * 0.2);
|
||||
// builder.platformLiquidationFee(
|
||||
// bank.liquidationFee.toNumber() * 0.8,
|
||||
// );
|
||||
// } else if (tier.preset_name.includes('B')) {
|
||||
// builder.liquidationFee(bank.liquidationFee.toNumber() * 0.4);
|
||||
// builder.platformLiquidationFee(
|
||||
// bank.liquidationFee.toNumber() * 0.6,
|
||||
// );
|
||||
// }
|
||||
// change = true;
|
||||
// }
|
||||
// }
|
||||
|
||||
// eslint-disable-next-line no-constant-condition
|
||||
// if (true) {
|
||||
// if (!tier) {
|
||||
// console.log(`${bank.name}, no tier found`);
|
||||
// } else {
|
||||
// console.log(
|
||||
// `${bank.name.padStart(10)}, ${bank.loanFeeRate
|
||||
// .mul(I80F48.fromNumber(100))
|
||||
// .toFixed(2)}, ${bank.loanOriginationFeeRate
|
||||
// .mul(I80F48.fromNumber(100))
|
||||
// .toFixed(2)}, ${tier?.preset_name.padStart(5)}, ${(
|
||||
// tier.loanFeeRate * 100
|
||||
// ).toFixed(2)}, ${(tier!.loanOriginationFeeRate * 100).toFixed(2)}`,
|
||||
// );
|
||||
|
||||
// builder.loanFeeRate(tier!.loanFeeRate);
|
||||
// builder.loanOriginationFeeRate(tier!.loanOriginationFeeRate);
|
||||
// builder.flashLoanSwapFeeRate(tier!.loanOriginationFeeRate);
|
||||
// change = true;
|
||||
// }
|
||||
// }
|
||||
|
||||
// formulas are sourced from here
|
||||
// https://www.notion.so/mango-markets/Mango-v4-Risk-parameter-recommendations-d309cdf5faac4aeea7560356e68532ab
|
||||
|
||||
|
@ -230,175 +276,176 @@ async function updateTokenParams(): Promise<void> {
|
|||
// 4 * priceImpact.target_amount,
|
||||
// );
|
||||
|
||||
if (!bank.areBorrowsReduceOnly()) {
|
||||
// eslint-disable-next-line no-constant-condition
|
||||
if (false) {
|
||||
// Net borrow limits
|
||||
const netBorrowLimitPerWindowQuote = Math.max(
|
||||
10_000,
|
||||
Math.min(bank.uiDeposits() * bank.uiPrice, 300_000) / 3 +
|
||||
Math.max(0, bank.uiDeposits() * bank.uiPrice - 300_000) / 5,
|
||||
// eslint-disable-next-line no-constant-condition
|
||||
if (false) {
|
||||
// Net borrow limits
|
||||
const netBorrowLimitPerWindowQuote = Math.max(
|
||||
10_000,
|
||||
Math.min(bank.uiDeposits() * bank.uiPrice, 300_000) / 3 +
|
||||
Math.max(0, bank.uiDeposits() * bank.uiPrice - 300_000) / 5,
|
||||
);
|
||||
builder.netBorrowLimitPerWindowQuote(
|
||||
toNativeI80F48ForQuote(netBorrowLimitPerWindowQuote).toNumber(),
|
||||
);
|
||||
change = true;
|
||||
if (
|
||||
netBorrowLimitPerWindowQuote !=
|
||||
toUiDecimalsForQuote(bank.netBorrowLimitPerWindowQuote)
|
||||
) {
|
||||
console.log(
|
||||
`${
|
||||
bank.name
|
||||
} new - ${netBorrowLimitPerWindowQuote.toLocaleString()}, old - ${toUiDecimalsForQuote(
|
||||
bank.netBorrowLimitPerWindowQuote,
|
||||
).toLocaleString()}`,
|
||||
);
|
||||
builder.netBorrowLimitPerWindowQuote(
|
||||
toNativeI80F48ForQuote(netBorrowLimitPerWindowQuote).toNumber(),
|
||||
);
|
||||
change = true;
|
||||
if (
|
||||
netBorrowLimitPerWindowQuote !=
|
||||
toUiDecimalsForQuote(bank.netBorrowLimitPerWindowQuote)
|
||||
) {
|
||||
console.log(
|
||||
`${
|
||||
bank.name
|
||||
} new - ${netBorrowLimitPerWindowQuote.toLocaleString()}, old - ${toUiDecimalsForQuote(
|
||||
bank.netBorrowLimitPerWindowQuote,
|
||||
).toLocaleString()}`,
|
||||
}
|
||||
}
|
||||
|
||||
// Deposit limits
|
||||
// eslint-disable-next-line no-constant-condition
|
||||
if (false) {
|
||||
if (bank.maintAssetWeight.toNumber() > 0) {
|
||||
{
|
||||
// Find asset's largest batch in $ we would need to liquidate, batches are extreme points of a range of price drop,
|
||||
// range is constrained by leverage provided
|
||||
// i.e. how much volatility we expect
|
||||
const r = findLargestAssetBatchUi(
|
||||
pisForLiqor,
|
||||
bank.name,
|
||||
Math.round(bank.maintAssetWeight.toNumber() * 100),
|
||||
100 - Math.round(bank.maintAssetWeight.toNumber() * 100),
|
||||
stepSize,
|
||||
);
|
||||
|
||||
const maxLiqBatchQuoteUi = r[0];
|
||||
const maxLiqBatchUi = r[1];
|
||||
|
||||
const sellImpact = getPriceImpactForBank(
|
||||
midPriceImpacts,
|
||||
bank,
|
||||
(bank.liquidationFee.toNumber() * 100) / 2,
|
||||
);
|
||||
|
||||
// Deposit limit = sell impact - largest batch
|
||||
const allowedNewDepositsQuoteUi =
|
||||
sellImpact.target_amount - maxLiqBatchQuoteUi;
|
||||
const allowedNewDepositsUi =
|
||||
sellImpact.target_amount / bank.uiPrice -
|
||||
maxLiqBatchQuoteUi / bank.uiPrice;
|
||||
|
||||
const depositLimitUi = bank.uiDeposits() + allowedNewDepositsUi;
|
||||
|
||||
// LOG
|
||||
// console.log(
|
||||
// `${bank.name.padStart(20)} ${maxLiqBatchUi
|
||||
// .toLocaleString()
|
||||
// .padStart(15)} ${maxLiqBatchQuoteUi
|
||||
// .toLocaleString()
|
||||
// .padStart(15)}$ ${sellImpact.target_amount
|
||||
// .toLocaleString()
|
||||
// .padStart(12)}$ ${sellImpact.avg_price_impact_percent
|
||||
// .toLocaleString()
|
||||
// .padStart(12)}% ${allowedNewDepositsUi
|
||||
// .toLocaleString()
|
||||
// .padStart(20)}${allowedNewDepositsQuoteUi
|
||||
// .toLocaleString()
|
||||
// .padStart(20)}$ ${bank
|
||||
// .uiDeposits()
|
||||
// .toLocaleString()
|
||||
// .padStart(12)} ${(bank.uiDeposits() * bank.uiPrice)
|
||||
// .toLocaleString()
|
||||
// .padStart(12)}$ ${depositLimitUi
|
||||
// .toLocaleString()
|
||||
// .padStart(12)}`,
|
||||
// );
|
||||
|
||||
builder.depositLimit(toNative(depositLimitUi, bank.mintDecimals));
|
||||
change = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Deposit limits
|
||||
// eslint-disable-next-line no-constant-condition
|
||||
if (false) {
|
||||
if (bank.maintAssetWeight.toNumber() > 0) {
|
||||
{
|
||||
// Find asset's largest batch in $ we would need to liquidate, batches are extreme points of a range of price drop,
|
||||
// range is constrained by leverage provided
|
||||
// i.e. how much volatility we expect
|
||||
const r = findLargestAssetBatchUi(
|
||||
pisForLiqor,
|
||||
bank.name,
|
||||
Math.round(bank.maintAssetWeight.toNumber() * 100),
|
||||
100 - Math.round(bank.maintAssetWeight.toNumber() * 100),
|
||||
stepSize,
|
||||
);
|
||||
const params = builder.build();
|
||||
console.log(
|
||||
`${bank.name}, ${params.disableAssetLiquidation} ${params.oracleConfig?.maxStalenessSlots} ${params.oracleConfig?.confFilter}`,
|
||||
);
|
||||
|
||||
const maxLiqBatchQuoteUi = r[0];
|
||||
const maxLiqBatchUi = r[1];
|
||||
const ix = await client.program.methods
|
||||
.tokenEdit(
|
||||
params.oracle,
|
||||
params.oracleConfig,
|
||||
params.groupInsuranceFund,
|
||||
params.interestRateParams,
|
||||
params.loanFeeRate,
|
||||
params.loanOriginationFeeRate,
|
||||
params.maintAssetWeight,
|
||||
params.initAssetWeight,
|
||||
params.maintLiabWeight,
|
||||
params.initLiabWeight,
|
||||
params.liquidationFee,
|
||||
params.stablePriceDelayIntervalSeconds,
|
||||
params.stablePriceDelayGrowthLimit,
|
||||
params.stablePriceGrowthLimit,
|
||||
params.minVaultToDepositsRatio,
|
||||
params.netBorrowLimitPerWindowQuote !== null
|
||||
? new BN(params.netBorrowLimitPerWindowQuote)
|
||||
: null,
|
||||
params.netBorrowLimitWindowSizeTs !== null
|
||||
? new BN(params.netBorrowLimitWindowSizeTs)
|
||||
: null,
|
||||
params.borrowWeightScaleStartQuote,
|
||||
params.depositWeightScaleStartQuote,
|
||||
params.resetStablePrice ?? false,
|
||||
params.resetNetBorrowLimit ?? false,
|
||||
params.reduceOnly,
|
||||
params.name,
|
||||
params.forceClose,
|
||||
params.tokenConditionalSwapTakerFeeRate,
|
||||
params.tokenConditionalSwapMakerFeeRate,
|
||||
params.flashLoanSwapFeeRate,
|
||||
params.interestCurveScaling,
|
||||
params.interestTargetUtilization,
|
||||
params.maintWeightShiftStart,
|
||||
params.maintWeightShiftEnd,
|
||||
params.maintWeightShiftAssetTarget,
|
||||
params.maintWeightShiftLiabTarget,
|
||||
params.maintWeightShiftAbort ?? false,
|
||||
false, // setFallbackOracle, unused
|
||||
params.depositLimit,
|
||||
params.zeroUtilRate,
|
||||
params.platformLiquidationFee,
|
||||
params.disableAssetLiquidation,
|
||||
params.collateralFeePerDay,
|
||||
params.forceWithdraw,
|
||||
)
|
||||
.accounts({
|
||||
group: group.publicKey,
|
||||
oracle: bank.oracle,
|
||||
admin: group.admin,
|
||||
mintInfo: group.mintInfosMapByTokenIndex.get(bank.tokenIndex)
|
||||
?.publicKey,
|
||||
fallbackOracle: PublicKey.default,
|
||||
})
|
||||
.remainingAccounts([
|
||||
{
|
||||
pubkey: bank.publicKey,
|
||||
isWritable: true,
|
||||
isSigner: false,
|
||||
} as AccountMeta,
|
||||
])
|
||||
.instruction();
|
||||
|
||||
const sellImpact = getPriceImpactForBank(
|
||||
midPriceImpacts,
|
||||
bank,
|
||||
(bank.liquidationFee.toNumber() * 100) / 2,
|
||||
);
|
||||
const tx = new Transaction({ feePayer: wallet.publicKey }).add(ix);
|
||||
const simulated = await client.connection.simulateTransaction(tx);
|
||||
|
||||
// Deposit limit = sell impact - largest batch
|
||||
const allowedNewDepositsQuoteUi =
|
||||
sellImpact.target_amount - maxLiqBatchQuoteUi;
|
||||
const allowedNewDepositsUi =
|
||||
sellImpact.target_amount / bank.uiPrice -
|
||||
maxLiqBatchQuoteUi / bank.uiPrice;
|
||||
if (simulated.value.err) {
|
||||
console.log('error', simulated.value.logs);
|
||||
throw simulated.value.logs;
|
||||
}
|
||||
|
||||
const depositLimitUi = bank.uiDeposits() + allowedNewDepositsUi;
|
||||
|
||||
// LOG
|
||||
// console.log(
|
||||
// `${bank.name.padStart(20)} ${maxLiqBatchUi
|
||||
// .toLocaleString()
|
||||
// .padStart(15)} ${maxLiqBatchQuoteUi
|
||||
// .toLocaleString()
|
||||
// .padStart(15)}$ ${sellImpact.target_amount
|
||||
// .toLocaleString()
|
||||
// .padStart(12)}$ ${sellImpact.avg_price_impact_percent
|
||||
// .toLocaleString()
|
||||
// .padStart(12)}% ${allowedNewDepositsUi
|
||||
// .toLocaleString()
|
||||
// .padStart(20)}${allowedNewDepositsQuoteUi
|
||||
// .toLocaleString()
|
||||
// .padStart(20)}$ ${bank
|
||||
// .uiDeposits()
|
||||
// .toLocaleString()
|
||||
// .padStart(12)} ${(bank.uiDeposits() * bank.uiPrice)
|
||||
// .toLocaleString()
|
||||
// .padStart(12)}$ ${depositLimitUi
|
||||
// .toLocaleString()
|
||||
// .padStart(12)}`,
|
||||
// );
|
||||
|
||||
builder.depositLimit(
|
||||
toNative(depositLimitUi, bank.mintDecimals),
|
||||
);
|
||||
change = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const params = builder.build();
|
||||
console.log(bank.name);
|
||||
console.log(params.loanFeeRate);
|
||||
console.log(params.loanOriginationFeeRate);
|
||||
console.log(params.flashLoanSwapFeeRate);
|
||||
|
||||
const ix = await client.program.methods
|
||||
.tokenEdit(
|
||||
params.oracle,
|
||||
params.oracleConfig,
|
||||
params.groupInsuranceFund,
|
||||
params.interestRateParams,
|
||||
params.loanFeeRate,
|
||||
params.loanOriginationFeeRate,
|
||||
params.maintAssetWeight,
|
||||
params.initAssetWeight,
|
||||
params.maintLiabWeight,
|
||||
params.initLiabWeight,
|
||||
params.liquidationFee,
|
||||
params.stablePriceDelayIntervalSeconds,
|
||||
params.stablePriceDelayGrowthLimit,
|
||||
params.stablePriceGrowthLimit,
|
||||
params.minVaultToDepositsRatio,
|
||||
params.netBorrowLimitPerWindowQuote !== null
|
||||
? new BN(params.netBorrowLimitPerWindowQuote)
|
||||
: null,
|
||||
params.netBorrowLimitWindowSizeTs !== null
|
||||
? new BN(params.netBorrowLimitWindowSizeTs)
|
||||
: null,
|
||||
params.borrowWeightScaleStartQuote,
|
||||
params.depositWeightScaleStartQuote,
|
||||
params.resetStablePrice ?? false,
|
||||
params.resetNetBorrowLimit ?? false,
|
||||
params.reduceOnly,
|
||||
params.name,
|
||||
params.forceClose,
|
||||
params.tokenConditionalSwapTakerFeeRate,
|
||||
params.tokenConditionalSwapMakerFeeRate,
|
||||
params.flashLoanSwapFeeRate,
|
||||
params.interestCurveScaling,
|
||||
params.interestTargetUtilization,
|
||||
params.maintWeightShiftStart,
|
||||
params.maintWeightShiftEnd,
|
||||
params.maintWeightShiftAssetTarget,
|
||||
params.maintWeightShiftLiabTarget,
|
||||
params.maintWeightShiftAbort ?? false,
|
||||
false, // setFallbackOracle, unused
|
||||
params.depositLimit,
|
||||
)
|
||||
.accounts({
|
||||
group: group.publicKey,
|
||||
oracle: bank.oracle,
|
||||
admin: group.admin,
|
||||
mintInfo: group.mintInfosMapByTokenIndex.get(bank.tokenIndex)
|
||||
?.publicKey,
|
||||
})
|
||||
.remainingAccounts([
|
||||
{
|
||||
pubkey: bank.publicKey,
|
||||
isWritable: true,
|
||||
isSigner: false,
|
||||
} as AccountMeta,
|
||||
])
|
||||
.instruction();
|
||||
|
||||
const tx = new Transaction({ feePayer: wallet.publicKey }).add(ix);
|
||||
const simulated = await client.connection.simulateTransaction(tx);
|
||||
|
||||
if (simulated.value.err) {
|
||||
console.log('error', simulated.value.logs);
|
||||
throw simulated.value.logs;
|
||||
}
|
||||
|
||||
if (change) {
|
||||
instructions.push(ix);
|
||||
}
|
||||
if (change) {
|
||||
instructions.push(ix);
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(`....Skipping ${bank.name}, ${error}`);
|
||||
|
@ -433,7 +480,7 @@ async function updateTokenParams(): Promise<void> {
|
|||
tokenOwnerRecord,
|
||||
PROPOSAL_TITLE
|
||||
? PROPOSAL_TITLE
|
||||
: 'Update loan fee, loan origination fee, and flash loan fees in mango-v4',
|
||||
: 'Disable asset liquidation for C tier tokens in mango-v4, part 2',
|
||||
PROPOSAL_LINK ?? '',
|
||||
Object.values(proposals).length,
|
||||
instructions,
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
import { BN } from '@coral-xyz/anchor';
|
||||
import { utf8 } from '@coral-xyz/anchor/dist/cjs/utils/bytes';
|
||||
import { PublicKey } from '@solana/web3.js';
|
||||
import { format } from 'path';
|
||||
import { I80F48, I80F48Dto, ONE_I80F48, ZERO_I80F48 } from '../numbers/I80F48';
|
||||
import { As, toUiDecimals } from '../utils';
|
||||
import { OracleProvider, isOracleStaleOrUnconfident } from './oracle';
|
||||
|
@ -686,6 +685,7 @@ export class MintInfo {
|
|||
vaults: PublicKey[];
|
||||
oracle: PublicKey;
|
||||
registrationTime: BN;
|
||||
fallbackOracle: PublicKey;
|
||||
groupInsuranceFund: number;
|
||||
},
|
||||
): MintInfo {
|
||||
|
@ -698,6 +698,7 @@ export class MintInfo {
|
|||
obj.vaults,
|
||||
obj.oracle,
|
||||
obj.registrationTime,
|
||||
obj.fallbackOracle,
|
||||
obj.groupInsuranceFund == 1,
|
||||
);
|
||||
}
|
||||
|
@ -711,6 +712,7 @@ export class MintInfo {
|
|||
public vaults: PublicKey[],
|
||||
public oracle: PublicKey,
|
||||
public registrationTime: BN,
|
||||
public fallbackOracle: PublicKey,
|
||||
public groupInsuranceFund: boolean,
|
||||
) {}
|
||||
|
||||
|
|
|
@ -811,7 +811,7 @@ export class HealthCache {
|
|||
}
|
||||
}
|
||||
|
||||
private static binaryApproximationSearch(
|
||||
static binaryApproximationSearch(
|
||||
left: I80F48,
|
||||
leftValue: I80F48,
|
||||
right: I80F48,
|
||||
|
@ -830,10 +830,8 @@ export class HealthCache {
|
|||
// );
|
||||
|
||||
if (
|
||||
(leftValue.sub(targetValue).isPos() &&
|
||||
rightValue.sub(targetValue).isPos()) ||
|
||||
(leftValue.sub(targetValue).isNeg() &&
|
||||
rightValue.sub(targetValue).isNeg())
|
||||
(leftValue.gt(targetValue) && rightValue.gt(targetValue)) ||
|
||||
(leftValue.lt(targetValue) && rightValue.lt(targetValue))
|
||||
) {
|
||||
throw new Error(
|
||||
`Internal error: left ${leftValue.toNumber()} and right ${rightValue.toNumber()} don't contain the target value ${targetValue.toNumber()}!`,
|
||||
|
|
|
@ -1,10 +1,18 @@
|
|||
import { PublicKey } from '@solana/web3.js';
|
||||
import { MangoAccount } from './mangoAccount';
|
||||
import {
|
||||
HealthType,
|
||||
MangoAccount,
|
||||
TokenPosition,
|
||||
TokenPositionDto,
|
||||
} from './mangoAccount';
|
||||
import BN from 'bn.js';
|
||||
import { Bank } from './bank';
|
||||
import { toNative, toUiDecimals } from '../utils';
|
||||
import { Bank, TokenIndex } from './bank';
|
||||
import { deepClone, toNative, toUiDecimals } from '../utils';
|
||||
import { expect } from 'chai';
|
||||
import { I80F48 } from '../numbers/I80F48';
|
||||
import { I80F48, I80F48Dto, ONE_I80F48, ZERO_I80F48 } from '../numbers/I80F48';
|
||||
import { Group } from './group';
|
||||
import { HealthCache } from './healthCache';
|
||||
import { assert } from 'console';
|
||||
|
||||
describe('Mango Account', () => {
|
||||
const mangoAccount = new MangoAccount(
|
||||
|
@ -79,3 +87,238 @@ describe('Mango Account', () => {
|
|||
done();
|
||||
});
|
||||
});
|
||||
|
||||
describe('maxWithdraw', () => {
|
||||
const protoAccount = new MangoAccount(
|
||||
PublicKey.default,
|
||||
PublicKey.default,
|
||||
PublicKey.default,
|
||||
[],
|
||||
PublicKey.default,
|
||||
0,
|
||||
false,
|
||||
false,
|
||||
new BN(0),
|
||||
new BN(0),
|
||||
new BN(0),
|
||||
new BN(0),
|
||||
new BN(0),
|
||||
new BN(0),
|
||||
new BN(0),
|
||||
0,
|
||||
0,
|
||||
[],
|
||||
[],
|
||||
[],
|
||||
[],
|
||||
[],
|
||||
new Map(),
|
||||
);
|
||||
protoAccount.tokens.push(
|
||||
new TokenPosition(ZERO_I80F48(), 0 as TokenIndex, 0, ZERO_I80F48(), 0, 0),
|
||||
);
|
||||
protoAccount.tokens.push(
|
||||
new TokenPosition(ZERO_I80F48(), 1 as TokenIndex, 0, ZERO_I80F48(), 0, 0),
|
||||
);
|
||||
|
||||
const protoBank = {
|
||||
vault: PublicKey.default,
|
||||
mint: PublicKey.default,
|
||||
tokenIndex: 0,
|
||||
price: ONE_I80F48(),
|
||||
getAssetPrice() {
|
||||
return this.price;
|
||||
},
|
||||
getLiabPrice() {
|
||||
return this.price;
|
||||
},
|
||||
stablePriceModel: { stablePrice: ONE_I80F48() },
|
||||
initAssetWeight: I80F48.fromNumber(0.8),
|
||||
initLiabWeight: I80F48.fromNumber(1.2),
|
||||
maintWeights() {
|
||||
return [I80F48.fromNumber(0.9), I80F48.fromNumber(1.1)];
|
||||
},
|
||||
scaledInitAssetWeight(price) {
|
||||
return this.initAssetWeight;
|
||||
},
|
||||
scaledInitLiabWeight(price) {
|
||||
return this.initLiabWeight;
|
||||
},
|
||||
loanOriginationFeeRate: I80F48.fromNumber(0.001),
|
||||
minVaultToDepositsRatio: I80F48.fromNumber(0.1),
|
||||
depositIndex: I80F48.fromNumber(1000000),
|
||||
borrowIndex: I80F48.fromNumber(1000000),
|
||||
indexedDeposits: I80F48.fromNumber(0),
|
||||
indexedBorrows: I80F48.fromNumber(0),
|
||||
nativeDeposits() {
|
||||
return this.depositIndex.mul(this.indexedDeposits);
|
||||
},
|
||||
nativeBorrows() {
|
||||
return this.borrowIndex.mul(this.indexedBorrows);
|
||||
},
|
||||
areBorrowsReduceOnly() {
|
||||
return false;
|
||||
},
|
||||
} as any as Bank;
|
||||
|
||||
function makeGroup(bank0, bank1, vaultAmount) {
|
||||
return {
|
||||
getFirstBankByMint(mint) {
|
||||
if (mint.equals(bank0.mint)) {
|
||||
return bank0;
|
||||
} else if (mint.equals(bank1.mint)) {
|
||||
return bank1;
|
||||
}
|
||||
},
|
||||
getFirstBankByTokenIndex(tokenIndex) {
|
||||
return [bank0, bank1][tokenIndex];
|
||||
},
|
||||
getFirstBankForPerpSettlement() {
|
||||
return bank0;
|
||||
},
|
||||
vaultAmountsMap: new Map<string, BN>([
|
||||
[bank0.vault.toBase58(), new BN(vaultAmount)],
|
||||
]),
|
||||
} as any as Group;
|
||||
}
|
||||
|
||||
function setup(vaultAmount): [Group, Bank, Bank, MangoAccount] {
|
||||
const account = deepClone<MangoAccount>(protoAccount);
|
||||
const bank0 = deepClone(protoBank);
|
||||
const bank1 = deepClone(protoBank);
|
||||
bank1.tokenIndex = 1 as TokenIndex;
|
||||
bank1.mint = PublicKey.unique();
|
||||
bank1.initAssetWeight = ONE_I80F48();
|
||||
bank1.initLiabWeight = ONE_I80F48();
|
||||
const group = makeGroup(bank0, bank1, vaultAmount);
|
||||
return [group, bank0, bank1, account];
|
||||
}
|
||||
|
||||
function deposit(bank, account, amount) {
|
||||
const amountV = I80F48.fromNumber(amount);
|
||||
const indexedAmount = amountV.div(bank.depositIndex);
|
||||
if (indexedAmount.mul(bank.depositIndex).lt(amountV)) {
|
||||
const delta = new I80F48(new BN(1));
|
||||
indexedAmount.iadd(delta);
|
||||
}
|
||||
bank.indexedDeposits.iadd(indexedAmount);
|
||||
const tp = account.tokens[bank.tokenIndex];
|
||||
assert(!tp.indexedPosition.isNeg());
|
||||
tp.indexedPosition.iadd(indexedAmount);
|
||||
}
|
||||
|
||||
function borrow(bank, account, amount) {
|
||||
const indexedAmount = I80F48.fromNumber(amount).div(bank.borrowIndex);
|
||||
bank.indexedBorrows.iadd(indexedAmount);
|
||||
const tp = account.tokens[bank.tokenIndex];
|
||||
assert(!tp.indexedPosition.isPos());
|
||||
tp.indexedPosition.isub(indexedAmount);
|
||||
}
|
||||
|
||||
function maxWithdraw(group, account) {
|
||||
return account
|
||||
.getMaxWithdrawWithBorrowForToken(group, PublicKey.default)
|
||||
.toNumber();
|
||||
}
|
||||
|
||||
it('full withdraw', (done) => {
|
||||
const [group, bank0, bank1, account] = setup(1000000);
|
||||
deposit(bank0, account, 100);
|
||||
expect(maxWithdraw(group, account)).equal(100);
|
||||
done();
|
||||
});
|
||||
|
||||
it('full withdraw limited vault', (done) => {
|
||||
const [group, bank0, bank1, account] = setup(90);
|
||||
deposit(bank0, account, 100);
|
||||
expect(maxWithdraw(group, account)).equal(90);
|
||||
done();
|
||||
});
|
||||
|
||||
it('full withdraw limited utilization', (done) => {
|
||||
const [group, bank0, bank1, account] = setup(1000000);
|
||||
const other = deepClone(account);
|
||||
deposit(bank0, account, 100);
|
||||
borrow(bank0, other, 50);
|
||||
expect(maxWithdraw(group, account)).equal(50);
|
||||
done();
|
||||
});
|
||||
|
||||
it('withdraw limited health', (done) => {
|
||||
const [group, bank0, bank1, account] = setup(1000000);
|
||||
deposit(bank0, account, 100);
|
||||
borrow(bank1, account, 50);
|
||||
expect(maxWithdraw(group, account)).equal(Math.floor(100 - 50 / 0.8));
|
||||
done();
|
||||
});
|
||||
|
||||
it('pure borrow', (done) => {
|
||||
const [group, bank0, bank1, account] = setup(1000000);
|
||||
const other = deepClone(account);
|
||||
deposit(bank0, other, 1000); // so there's something to borrow
|
||||
deposit(bank1, account, 100);
|
||||
expect(maxWithdraw(group, account)).equal(Math.floor(100 / 1.2));
|
||||
done();
|
||||
});
|
||||
|
||||
it('pure borrow limited utilization', (done) => {
|
||||
const [group, bank0, bank1, account] = setup(1000000);
|
||||
const other = deepClone(account);
|
||||
deposit(bank0, other, 50);
|
||||
deposit(bank1, account, 100);
|
||||
expect(maxWithdraw(group, account)).equal(44); // due to origination fees!
|
||||
|
||||
bank0.loanOriginationFeeRate = ZERO_I80F48();
|
||||
expect(maxWithdraw(group, account)).equal(45);
|
||||
|
||||
done();
|
||||
});
|
||||
|
||||
it('withdraw and borrow', (done) => {
|
||||
const [group, bank0, bank1, account] = setup(1000000);
|
||||
const other = deepClone(account);
|
||||
deposit(bank0, account, 100);
|
||||
deposit(bank1, account, 100);
|
||||
deposit(bank0, other, 10000);
|
||||
expect(maxWithdraw(group, account)).equal(100 + Math.floor(100 / 1.2));
|
||||
done();
|
||||
});
|
||||
|
||||
it('withdraw limited health and scaling', (done) => {
|
||||
const [group, bank0, bank1, account] = setup(1000000);
|
||||
bank0.scaledInitAssetWeight = function (price) {
|
||||
const startScale = I80F48.fromNumber(50);
|
||||
if (this.nativeDeposits().gt(startScale)) {
|
||||
return this.initAssetWeight.div(this.nativeDeposits().div(startScale));
|
||||
}
|
||||
return this.initAssetWeight;
|
||||
};
|
||||
const other = deepClone(account);
|
||||
deposit(bank0, other, 100);
|
||||
deposit(bank0, account, 200);
|
||||
borrow(bank1, account, 20);
|
||||
// initial account health = 200 * 0.8 * 50 / 300 - 20 = 6.66
|
||||
// zero account health = 100 * 0.8 * 50 / 200 - 20 = 0
|
||||
// so can withdraw 100
|
||||
expect(maxWithdraw(group, account)).equal(100);
|
||||
done();
|
||||
});
|
||||
|
||||
it('borrow limited health and scaling', (done) => {
|
||||
const [group, bank0, bank1, account] = setup(1000000);
|
||||
bank0.scaledInitLiabWeight = function (price) {
|
||||
const startScale = I80F48.fromNumber(50);
|
||||
if (this.nativeBorrows().gt(startScale)) {
|
||||
return this.initLiabWeight.mul(this.nativeBorrows().div(startScale));
|
||||
}
|
||||
return this.initLiabWeight;
|
||||
};
|
||||
const other = deepClone(account);
|
||||
deposit(bank0, other, 100);
|
||||
deposit(bank1, account, 100);
|
||||
// -64*1.2*64/50+100 = 1.69
|
||||
// -65*1.2*65/50+100 = -1.4
|
||||
expect(maxWithdraw(group, account)).equal(64);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
|
|
@ -4,9 +4,16 @@ import { OpenOrders, Order, Orderbook } from '@project-serum/serum/lib/market';
|
|||
import { AccountInfo, PublicKey } from '@solana/web3.js';
|
||||
import { MangoClient } from '../client';
|
||||
import { OPENBOOK_PROGRAM_ID, RUST_I64_MAX, RUST_I64_MIN } from '../constants';
|
||||
import { I80F48, I80F48Dto, ONE_I80F48, ZERO_I80F48 } from '../numbers/I80F48';
|
||||
import {
|
||||
I80F48,
|
||||
I80F48Dto,
|
||||
MAX_I80F48,
|
||||
ONE_I80F48,
|
||||
ZERO_I80F48,
|
||||
} from '../numbers/I80F48';
|
||||
import {
|
||||
U64_MAX_BN,
|
||||
deepClone,
|
||||
roundTo5,
|
||||
toNativeI80F48,
|
||||
toUiDecimals,
|
||||
|
@ -536,67 +543,126 @@ export class MangoAccount {
|
|||
mintPk: PublicKey,
|
||||
): I80F48 {
|
||||
const tokenBank: Bank = group.getFirstBankByMint(mintPk);
|
||||
const initHealth = this.getHealth(group, HealthType.init);
|
||||
const loanOriginationFactor = ONE_I80F48().add(
|
||||
tokenBank.loanOriginationFeeRate,
|
||||
);
|
||||
const maxBorrowUtilization = I80F48.fromNumber(
|
||||
1 - tokenBank.minVaultToDepositsRatio,
|
||||
);
|
||||
const tp = this.getToken(tokenBank.tokenIndex);
|
||||
const healthCache = HealthCache.fromMangoAccount(group, this);
|
||||
const tokenInfoIndex = healthCache.getOrCreateTokenInfoIndex(tokenBank);
|
||||
const initHealth = healthCache.health(HealthType.init);
|
||||
|
||||
// Case 1:
|
||||
// Cannot withdraw if init health is below 0
|
||||
if (initHealth.lte(ZERO_I80F48())) {
|
||||
return ZERO_I80F48();
|
||||
}
|
||||
|
||||
// Deposits need special treatment since they would neither count towards liabilities
|
||||
// nor would be charged loanOriginationFeeRate when withdrawn
|
||||
// Step 1: Since withdraws can change the asset weight scaling and borrows will
|
||||
// change the liab weight scaling, we use a binary search to find something
|
||||
// close to the true maximum value.
|
||||
// To do that, we first get an upper bound that the search can start with.
|
||||
|
||||
const tp = this.getToken(tokenBank.tokenIndex);
|
||||
const existingTokenDeposits = tp ? tp.deposits(tokenBank) : ZERO_I80F48();
|
||||
let existingPositionHealthContrib = ZERO_I80F48();
|
||||
if (existingTokenDeposits.gt(ZERO_I80F48())) {
|
||||
existingPositionHealthContrib = existingTokenDeposits
|
||||
.mul(tokenBank.getAssetPrice())
|
||||
.imul(tokenBank.scaledInitAssetWeight(tokenBank.getAssetPrice()));
|
||||
}
|
||||
|
||||
// Case 2: token deposits have higher contribution than initHealth,
|
||||
// can withdraw without borrowing until initHealth reaches 0
|
||||
if (existingPositionHealthContrib.gt(initHealth)) {
|
||||
const withdrawAbleExistingPositionHealthContrib = initHealth;
|
||||
return withdrawAbleExistingPositionHealthContrib
|
||||
.div(tokenBank.scaledInitAssetWeight(tokenBank.getAssetPrice()))
|
||||
.div(tokenBank.getAssetPrice());
|
||||
}
|
||||
|
||||
// Case 3: withdraw = withdraw existing deposits + borrows until initHealth reaches 0
|
||||
const initHealthWithoutExistingPosition = initHealth.sub(
|
||||
existingPositionHealthContrib,
|
||||
const lowerBoundBorrowHealthFactor = tokenBank
|
||||
.getLiabPrice()
|
||||
.mul(tokenBank.scaledInitLiabWeight(tokenBank.getLiabPrice()));
|
||||
const upperBound = existingTokenDeposits.add(
|
||||
initHealth.div(lowerBoundBorrowHealthFactor),
|
||||
);
|
||||
let maxBorrowNative = initHealthWithoutExistingPosition
|
||||
.div(tokenBank.scaledInitLiabWeight(tokenBank.price))
|
||||
.div(tokenBank.price);
|
||||
|
||||
// Cap maxBorrow to maintain minVaultToDepositsRatio on the bank
|
||||
// Step 2: Find the maximum withdraw amount
|
||||
|
||||
const mutTokenBank = deepClone<Bank>(tokenBank);
|
||||
const mutHealthCache = deepClone<HealthCache>(healthCache);
|
||||
const invalidHealthValue = MAX_I80F48().div(I80F48.fromNumber(2)).neg();
|
||||
function healthAfterWithdraw(amount: I80F48): I80F48 {
|
||||
const withdrawOfDepositsAmount = amount.min(existingTokenDeposits);
|
||||
const borrowAmount = amount.sub(withdrawOfDepositsAmount);
|
||||
// Take care of loan origination fee
|
||||
const borrowCost = borrowAmount.mul(loanOriginationFactor);
|
||||
|
||||
// Update the account's token position
|
||||
const mutTi = mutHealthCache.tokenInfos[tokenInfoIndex];
|
||||
const startTi = healthCache.tokenInfos[tokenInfoIndex];
|
||||
mutTi.balanceSpot = startTi.balanceSpot
|
||||
.sub(withdrawOfDepositsAmount)
|
||||
.sub(borrowCost);
|
||||
|
||||
// Update the bank and the scaled weights
|
||||
mutTokenBank.indexedDeposits = tokenBank.indexedDeposits.sub(
|
||||
withdrawOfDepositsAmount.div(tokenBank.depositIndex),
|
||||
);
|
||||
mutTokenBank.indexedBorrows = tokenBank.indexedBorrows.add(
|
||||
borrowCost.div(tokenBank.borrowIndex),
|
||||
);
|
||||
if (mutTokenBank.nativeBorrows().gt(mutTokenBank.nativeDeposits())) {
|
||||
return invalidHealthValue;
|
||||
}
|
||||
if (borrowAmount.isPos()) {
|
||||
if (
|
||||
mutTokenBank
|
||||
.nativeBorrows()
|
||||
.gt(mutTokenBank.nativeDeposits().mul(maxBorrowUtilization))
|
||||
) {
|
||||
return invalidHealthValue;
|
||||
}
|
||||
}
|
||||
mutTi.initScaledAssetWeight = mutTokenBank.scaledInitAssetWeight(
|
||||
tokenBank.getAssetPrice(),
|
||||
);
|
||||
mutTi.initScaledLiabWeight = mutTokenBank.scaledInitLiabWeight(
|
||||
tokenBank.getLiabPrice(),
|
||||
);
|
||||
|
||||
return mutHealthCache.health(HealthType.init);
|
||||
}
|
||||
|
||||
// Withdrawing one token will change health by at least this much.
|
||||
// We use this to define a good stopping criterion for the search.
|
||||
const minHealthChangePerNative = tokenBank
|
||||
.getAssetPrice()
|
||||
.mul(tokenBank.scaledInitAssetWeight(tokenBank.getAssetPrice()));
|
||||
|
||||
let amount = HealthCache.binaryApproximationSearch(
|
||||
ZERO_I80F48(),
|
||||
initHealth,
|
||||
upperBound,
|
||||
I80F48.fromNumber(0.5).mul(minHealthChangePerNative).min(initHealth),
|
||||
I80F48.fromNumber(0.5),
|
||||
healthAfterWithdraw,
|
||||
{
|
||||
maxIterations: 100,
|
||||
targetError: I80F48.fromNumber(0.2)
|
||||
.mul(minHealthChangePerNative)
|
||||
.toNumber(),
|
||||
},
|
||||
);
|
||||
|
||||
// Step 3: Only full tokens can be withdrawn, do the rounding and
|
||||
// check if withdrawing one-native more would also be fine
|
||||
amount = amount.floor();
|
||||
const amountPlusOne = amount.add(ONE_I80F48());
|
||||
if (!healthAfterWithdraw(amountPlusOne).isNeg()) {
|
||||
amount = amountPlusOne;
|
||||
}
|
||||
|
||||
// Step 4: No borrows on no-borrow tokens
|
||||
if (tokenBank.areBorrowsReduceOnly()) {
|
||||
amount = amount.min(existingTokenDeposits);
|
||||
}
|
||||
|
||||
// Step 5: also limit by vault funds
|
||||
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 vaultLimit = I80F48.fromU64(vaultAmount);
|
||||
|
||||
const maxBorrowNativeWithoutFees = maxBorrowNative.div(
|
||||
ONE_I80F48().add(tokenBank.loanOriginationFeeRate),
|
||||
);
|
||||
|
||||
return maxBorrowNativeWithoutFees.add(existingTokenDeposits);
|
||||
return amount.min(vaultLimit).max(ZERO_I80F48());
|
||||
}
|
||||
|
||||
public getMaxWithdrawWithBorrowForTokenUi(
|
||||
|
|
|
@ -229,9 +229,26 @@ export class MangoClient {
|
|||
ixs: TransactionInstruction[],
|
||||
opts: SendTransactionOpts = {},
|
||||
): Promise<MangoSignatureStatus> {
|
||||
const alts =
|
||||
opts?.alts && opts?.alts?.length
|
||||
? opts.alts
|
||||
: group.addressLookupTablesList;
|
||||
|
||||
const uniqueAccountsCount = [
|
||||
...new Set([
|
||||
...ixs.flatMap((x) => x.keys.map((x) => x.pubkey.toBase58())),
|
||||
...ixs.flatMap((x) => x.programId.toBase58()),
|
||||
...alts.map((x) => x.key.toBase58()),
|
||||
]),
|
||||
].length;
|
||||
|
||||
if (uniqueAccountsCount > 64) {
|
||||
throw new Error(`Max accounts limit exceeded`);
|
||||
}
|
||||
|
||||
return await this.sendAndConfirmTransaction(ixs, {
|
||||
alts: group.addressLookupTablesList,
|
||||
...opts,
|
||||
alts: alts,
|
||||
});
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue