Socket
Socket
Sign inDemoInstall

@blockworks-foundation/mango-v4

Package Overview
Dependencies
Maintainers
7
Versions
239
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@blockworks-foundation/mango-v4 - npm Package Compare versions

Comparing version 0.16.11 to 0.16.12

dist/cjs/scripts/liqtest/liqtest-close-group.d.ts

45

CHANGELOG.md

@@ -7,4 +7,45 @@ # Mango v4 Program Change Log

### v0.16.0, 2023-5-
### v0.17.0, 2023-6-
- Configurable perp market settle token (#550)
This changes perp market margining to no longer assume all pnl is in USD
while settlement is in USDC. Instead, a configurable settle token is used for
pnl and settlement, defaulting to USDC.
There is no difference while the USDC price is forced to $1 and its init and liab
weights are 1. But with this patch, it becomes possible to change that.
For now it is not recommended to use a token other than USDC or USDT (or
another USD targeting stable token) for perp settlement.
The patch also updates all instructions dealing with the insurance vault
to be aware that the insurance fund is not in USD but in USDC and apply the
USDC price before payouts. To do this, the previous
PerpLiqNegativePnlOrBankruptcy was replaced by a new
PerpLiqNegativePnlOrBankruptcyV2 instruction.
- Allow reduce-only actions when init health is low (#592)
Previously when init health was negative, the program only allowed actions that
increased init health. Now it also accepts actions that keep init health the
same.
This is helpful for users because they now can place reducing limit orders on
the spot or perp orderbooks while their account has low health.
- Whitelist PerpPlaceOrderV2 and PerpPlaceOrderPeggedV2 for HealthRegions (#597)
- Improve logging of loans (#599, #603)
- Pyth oracle status is checked (#607)
- Fixes to the inactive fee buyback feature (#608)
- Fix token force close to respect the reduce-only flag (#613)
- Improve docs (#590, #594)
- Use workspace dependencies (#588)
## mainnet
### v0.16.0, 2023-5-19
Deployment: May 19, 2023 at 15:35:12 Central European Summer Time, https://explorer.solana.com/tx/22fEcghPGgAnYCZkfjTxTeKQwX5rzWSx3c5CV9TikJmaAKWCpubCZYBx5ZJJPeNG1xWUPWMw3ooDhFBRYCR3tKYU
- New event: PerpTakerTradeLog immediately logs your trade execution (#579, #584)

@@ -30,4 +71,2 @@

## mainnet
### v0.15.0, 2023-5-11

@@ -34,0 +73,0 @@

4

dist/cjs/scripts/archive/debug-user.js

@@ -42,5 +42,5 @@ "use strict";

console.log('mangoAccount.getAssetsValue() ' +
(0, utils_1.toUiDecimalsForQuote)(mangoAccount.getAssetsValue(group, mangoAccount_1.HealthType.init).toNumber()));
(0, utils_1.toUiDecimalsForQuote)(mangoAccount.getAssetsValue(group).toNumber()));
console.log('mangoAccount.getLiabsValue() ' +
(0, utils_1.toUiDecimalsForQuote)(mangoAccount.getLiabsValue(group, mangoAccount_1.HealthType.init).toNumber()));
(0, utils_1.toUiDecimalsForQuote)(mangoAccount.getLiabsValue(group).toNumber()));
async function getMaxWithdrawWithBorrowForTokenUiWrapper(token) {

@@ -47,0 +47,0 @@ console.log(`mangoAccount.getMaxWithdrawWithBorrowForTokenUi(group, ${token}) ` +

@@ -11,3 +11,2 @@ "use strict";

const fs_1 = __importDefault(require("fs"));
const mangoAccount_1 = require("../../src/accounts/mangoAccount");
const perp_1 = require("../../src/accounts/perp");

@@ -237,5 +236,5 @@ const client_1 = require("../../src/client");

console.log('...mangoAccount.getAssetsVal() ' +
(0, utils_1.toUiDecimalsForQuote)(mangoAccount.getAssetsValue(group, mangoAccount_1.HealthType.init).toNumber()));
(0, utils_1.toUiDecimalsForQuote)(mangoAccount.getAssetsValue(group).toNumber()));
console.log('...mangoAccount.getLiabsVal() ' +
(0, utils_1.toUiDecimalsForQuote)(mangoAccount.getLiabsValue(group, mangoAccount_1.HealthType.init).toNumber()));
(0, utils_1.toUiDecimalsForQuote)(mangoAccount.getLiabsValue(group).toNumber()));
console.log('...mangoAccount.getMaxWithdrawWithBorrowForToken(group, "SOL") ' +

@@ -242,0 +241,0 @@ (0, utils_1.toUiDecimalsForQuote)(mangoAccount

@@ -16,2 +16,3 @@ "use strict";

const GROUP_NUM = process.env.GROUP_NUM;
const CLUSTER = process.env.CLUSTER || 'mainnet-beta';
async function main() {

@@ -24,3 +25,3 @@ const options = anchor_1.AnchorProvider.defaultOptions();

const adminProvider = new anchor_1.AnchorProvider(connection, adminWallet, options);
const client = await client_1.MangoClient.connect(adminProvider, 'mainnet-beta', constants_1.MANGO_V4_ID['mainnet-beta'], {
const client = await client_1.MangoClient.connect(adminProvider, CLUSTER, constants_1.MANGO_V4_ID[CLUSTER], {
idsSource: 'get-program-accounts',

@@ -27,0 +28,0 @@ prioritizationFee: 5,

@@ -46,5 +46,5 @@ "use strict";

console.log('mangoAccount.getAssetsVal() ' +
(0, index_1.toUiDecimalsForQuote)(mangoAccount.getAssetsValue(group, mangoAccount_1.HealthType.init).toNumber()));
(0, index_1.toUiDecimalsForQuote)(mangoAccount.getAssetsValue(group).toNumber()));
console.log('mangoAccount.getLiabsVal() ' +
(0, index_1.toUiDecimalsForQuote)(mangoAccount.getLiabsValue(group, mangoAccount_1.HealthType.init).toNumber()));
(0, index_1.toUiDecimalsForQuote)(mangoAccount.getLiabsValue(group).toNumber()));
console.log("mangoAccount.getMaxWithdrawWithBorrowForToken(group, 'SOL') " +

@@ -51,0 +51,0 @@ (0, index_1.toUiDecimalsForQuote)((await mangoAccount.getMaxWithdrawWithBorrowForToken(group, group.banksMapByName.get('SOL')[0].mint)).toNumber()));

@@ -17,7 +17,10 @@ "use strict";

const GROUP_NUM = Number(process.env.GROUP_NUM || 200);
const CLUSTER = process.env.CLUSTER || 'mainnet-beta';
const MINTS = JSON.parse(process.env.MINTS || '').map((s) => new web3_js_1.PublicKey(s));
const SERUM_MARKETS = JSON.parse(process.env.SERUM_MARKETS || '').map((s) => new web3_js_1.PublicKey(s));
const MAINNET_MINTS = new Map([
['USDC', 'EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v'],
['ETH', '7vfCXTUXx5WJV5JADk17DUJ4ksgau7utNKj4b963voxs'],
['SOL', 'So11111111111111111111111111111111111111112'],
['MNGO', 'MangoCzJ36AjZyKwVj3VnYU4GTonjfVEnJmvvWaxLac'],
['USDC', MINTS[0]],
['ETH', MINTS[1]],
['SOL', MINTS[2]],
['MNGO', MINTS[3]],
]);

@@ -28,3 +31,3 @@ const STUB_PRICES = new Map([

['SOL', 0.015],
['MNGO', 0.02], // same price/decimals as SOL for convenience
['MNGO', 0.02],
]);

@@ -34,4 +37,4 @@ // External markets are matched with those in https://github.com/blockworks-foundation/mango-client-v3/blob/main/src/ids.json

const MAINNET_SERUM3_MARKETS = new Map([
['ETH/USDC', 'FZxi3yWkE5mMjyaZj6utmYL54QQYfMCKMcLaQZq4UwnA'],
['SOL/USDC', '8BnEgHoWFysVcuFFX7QztDmzuH8r5ZFvyP3sYwn1XTh6'],
['ETH/USDC', SERUM_MARKETS[0]],
['SOL/USDC', SERUM_MARKETS[1]],
]);

@@ -46,7 +49,7 @@ const MIN_VAULT_TO_DEPOSITS_RATIO = 0.2;

const connection = new web3_js_1.Connection(process.env.CLUSTER_URL, options);
const admin = web3_js_1.Keypair.fromSecretKey(Buffer.from(JSON.parse(fs_1.default.readFileSync(process.env.MANGO_MAINNET_PAYER_KEYPAIR, 'utf-8'))));
const admin = web3_js_1.Keypair.fromSecretKey(Buffer.from(JSON.parse(fs_1.default.readFileSync(process.env.PAYER_KEYPAIR, 'utf-8'))));
const adminWallet = new anchor_1.Wallet(admin);
console.log(`Admin ${adminWallet.publicKey.toBase58()}`);
const adminProvider = new anchor_1.AnchorProvider(connection, adminWallet, options);
const client = await client_1.MangoClient.connect(adminProvider, 'mainnet-beta', constants_1.MANGO_V4_ID['mainnet-beta'], {
const client = await client_1.MangoClient.connect(adminProvider, CLUSTER, constants_1.MANGO_V4_ID[CLUSTER], {
idsSource: 'get-program-accounts',

@@ -68,4 +71,4 @@ prioritizationFee: 100,

// stub oracles
let oracles = new Map();
for (let [name, mint] of MAINNET_MINTS) {
const oracles = new Map();
for (const [name, mint] of MAINNET_MINTS) {
console.log(`Creating stub oracle for ${name}...`);

@@ -98,6 +101,6 @@ const mintPk = new web3_js_1.PublicKey(mint);

console.log(`Registering USDC...`);
const usdcMainnetMint = new web3_js_1.PublicKey(MAINNET_MINTS.get('USDC'));
const usdcMainnetOracle = oracles.get('USDC');
const usdcMint = new web3_js_1.PublicKey(MAINNET_MINTS.get('USDC'));
const usdcOracle = oracles.get('USDC');
try {
await client.tokenRegister(group, usdcMainnetMint, usdcMainnetOracle, defaultOracleConfig, 0, 'USDC', defaultInterestRate, 0.0, 0.0001, 1, 1, 1, 1, 0, MIN_VAULT_TO_DEPOSITS_RATIO, NET_BORROWS_WINDOW_SIZE_TS, NET_BORROWS_LIMIT_NATIVE);
await client.tokenRegister(group, usdcMint, usdcOracle, defaultOracleConfig, 0, 'USDC', defaultInterestRate, 0.0, 0.0001, 1, 1, 1, 1, 0, MIN_VAULT_TO_DEPOSITS_RATIO, NET_BORROWS_WINDOW_SIZE_TS, NET_BORROWS_LIMIT_NATIVE);
await group.reloadAll(client);

@@ -110,6 +113,6 @@ }

console.log(`Registering ETH...`);
const ethMainnetMint = new web3_js_1.PublicKey(MAINNET_MINTS.get('ETH'));
const ethMainnetOracle = oracles.get('ETH');
const ethMint = new web3_js_1.PublicKey(MAINNET_MINTS.get('ETH'));
const ethOracle = oracles.get('ETH');
try {
await client.tokenRegister(group, ethMainnetMint, ethMainnetOracle, defaultOracleConfig, 1, 'ETH', defaultInterestRate, 0.0, 0.0001, 0.9, 0.8, 1.1, 1.2, 0.05, MIN_VAULT_TO_DEPOSITS_RATIO, NET_BORROWS_WINDOW_SIZE_TS, NET_BORROWS_LIMIT_NATIVE);
await client.tokenRegister(group, ethMint, ethOracle, defaultOracleConfig, 1, 'ETH', defaultInterestRate, 0.0, 0.0001, 0.9, 0.8, 1.1, 1.2, 0.05, MIN_VAULT_TO_DEPOSITS_RATIO, NET_BORROWS_WINDOW_SIZE_TS, NET_BORROWS_LIMIT_NATIVE);
await group.reloadAll(client);

@@ -122,6 +125,6 @@ }

console.log(`Registering SOL...`);
const solMainnetMint = new web3_js_1.PublicKey(MAINNET_MINTS.get('SOL'));
const solMainnetOracle = oracles.get('SOL');
const solMint = new web3_js_1.PublicKey(MAINNET_MINTS.get('SOL'));
const solOracle = oracles.get('SOL');
try {
await client.tokenRegister(group, solMainnetMint, solMainnetOracle, defaultOracleConfig, 2, // tokenIndex
await client.tokenRegister(group, solMint, solOracle, defaultOracleConfig, 2, // tokenIndex
'SOL', defaultInterestRate, 0.0, 0.0001, 0.9, 0.8, 1.1, 1.2, 0.05, MIN_VAULT_TO_DEPOSITS_RATIO, NET_BORROWS_WINDOW_SIZE_TS, NET_BORROWS_LIMIT_NATIVE);

@@ -145,5 +148,5 @@ await group.reloadAll(client);

console.log('Registering MNGO-PERP market...');
const mngoMainnetOracle = oracles.get('MNGO');
const mngoOracle = oracles.get('MNGO');
try {
await client.perpCreateMarket(group, mngoMainnetOracle, 0, 'MNGO-PERP', defaultOracleConfig, 6, 10, 100000, // base lots
await client.perpCreateMarket(group, mngoOracle, 0, 'MNGO-PERP', defaultOracleConfig, 6, 10, 100000, // base lots
0.9, 0.8, 1.1, 1.2, 0.0, 0.0, 0.05, -0.001, 0.002, 0, -0.1, 0.1, 10, false, 0, 0, 0, 0, -1.0, 2 * 60 * 60, 0.025);

@@ -181,5 +184,22 @@ }

}
async function extendTable(addresses) {
await group.reloadAll(client);
const alt = await client.program.provider.connection.getAddressLookupTable(group.addressLookupTables[0]);
addresses = addresses.filter((newAddress) => alt.value?.state.addresses &&
alt.value?.state.addresses.findIndex((addressInALt) => addressInALt.equals(newAddress)) === -1);
if (addresses.length === 0) {
return;
}
const extendIx = web3_js_1.AddressLookupTableProgram.extendLookupTable({
lookupTable: group.addressLookupTables[0],
payer: admin.publicKey,
authority: admin.publicKey,
addresses,
});
const sig = await client.sendAndConfirmTransaction([extendIx]);
console.log(`https://explorer.solana.com/tx/${sig}`);
}
// Extend using mango v4 relevant pub keys
try {
let bankAddresses = Array.from(group.banksMapByMint.values())
const bankAddresses = Array.from(group.banksMapByMint.values())
.flat()

@@ -191,6 +211,6 @@ .map((bank) => [bank.publicKey, bank.oracle, bank.vault])

.map((mintInfo) => mintInfo.publicKey));
let serum3MarketAddresses = Array.from(group.serum3MarketsMapByExternal.values())
const serum3MarketAddresses = Array.from(group.serum3MarketsMapByExternal.values())
.flat()
.map((serum3Market) => serum3Market.publicKey);
let serum3ExternalMarketAddresses = Array.from(group.serum3ExternalMarketsMap.values())
const serum3ExternalMarketAddresses = Array.from(group.serum3ExternalMarketsMap.values())
.flat()

@@ -203,3 +223,3 @@ .map((serum3ExternalMarket) => [

.flat();
let perpMarketAddresses = Array.from(group.perpMarketsMapByMarketIndex.values())
const perpMarketAddresses = Array.from(group.perpMarketsMapByMarketIndex.values())
.flat()

@@ -214,19 +234,2 @@ .map((perpMarket) => [

.flat();
async function extendTable(addresses) {
await group.reloadAll(client);
const alt = await client.program.provider.connection.getAddressLookupTable(group.addressLookupTables[0]);
addresses = addresses.filter((newAddress) => alt.value?.state.addresses &&
alt.value?.state.addresses.findIndex((addressInALt) => addressInALt.equals(newAddress)) === -1);
if (addresses.length === 0) {
return;
}
const extendIx = web3_js_1.AddressLookupTableProgram.extendLookupTable({
lookupTable: group.addressLookupTables[0],
payer: admin.publicKey,
authority: admin.publicKey,
addresses,
});
const sig = await client.sendAndConfirmTransaction([extendIx]);
console.log(`https://explorer.solana.com/tx/${sig}`);
}
console.log(`ALT: extending using mango v4 relevant public keys`);

@@ -233,0 +236,0 @@ await extendTable(bankAddresses);

@@ -19,2 +19,3 @@ "use strict";

const GROUP_NUM = Number(process.env.GROUP_NUM || 200);
const CLUSTER = process.env.CLUSTER || 'mainnet-beta';
// native prices

@@ -51,6 +52,6 @@ const PRICES = {

const connection = new web3_js_1.Connection(process.env.CLUSTER_URL, options);
const admin = web3_js_1.Keypair.fromSecretKey(Buffer.from(JSON.parse(fs_1.default.readFileSync(process.env.MANGO_MAINNET_PAYER_KEYPAIR, 'utf-8'))));
const admin = web3_js_1.Keypair.fromSecretKey(Buffer.from(JSON.parse(fs_1.default.readFileSync(process.env.PAYER_KEYPAIR, 'utf-8'))));
const userWallet = new anchor_1.Wallet(admin);
const userProvider = new anchor_1.AnchorProvider(connection, userWallet, options);
const client = await client_1.MangoClient.connect(userProvider, 'mainnet-beta', constants_1.MANGO_V4_ID['mainnet-beta'], {
const client = await client_1.MangoClient.connect(userProvider, CLUSTER, constants_1.MANGO_V4_ID[CLUSTER], {
idsSource: 'get-program-accounts',

@@ -91,5 +92,5 @@ prioritizationFee: 100,

console.log(`Creating mangoaccount...`);
let mangoAccount = await createMangoAccount(name);
const mangoAccount = await createMangoAccount(name);
console.log(`...created mangoAccount ${mangoAccount.publicKey} for ${name}`);
for (let [assetName, assetAmount] of assets) {
for (const [assetName, assetAmount] of assets) {
const assetMint = new web3_js_1.PublicKey(MAINNET_MINTS.get(assetName));

@@ -99,3 +100,3 @@ await client.tokenDepositNative(group, mangoAccount, assetMint, new anchor_1.BN(assetAmount));

}
for (let [liabName, liabAmount] of liabs) {
for (const [liabName, liabAmount] of liabs) {
const liabMint = new web3_js_1.PublicKey(MAINNET_MINTS.get(liabName));

@@ -123,3 +124,3 @@ // temporarily drop the borrowed token value, so the borrow goes through

console.log(`Creating mangoaccount...`);
let mangoAccount = await createMangoAccount(name);
const mangoAccount = await createMangoAccount(name);
console.log(`...created mangoAccount ${mangoAccount.publicKey} for ${name}`);

@@ -155,3 +156,3 @@ const market = group.getSerum3MarketByName('SOL/USDC');

console.log(`Creating mangoaccount...`);
let mangoAccount = await createMangoAccount(name);
const mangoAccount = await createMangoAccount(name);
console.log(`...created mangoAccount ${mangoAccount.publicKey} for ${name}`);

@@ -165,3 +166,3 @@ const collateralMint = new web3_js_1.PublicKey(MAINNET_MINTS.get('SOL'));

// placing this order decreases maint health by (0.9 - 1)*$0.06 = $-0.006
await client.perpPlaceOrder(group, mangoAccount, group.perpMarketsMapByName.get('MNGO-PERP')?.perpMarketIndex, perp_1.PerpOrderSide.bid, 0.001, // ui price that won't get hit
await client.perpPlaceOrder(group, mangoAccount, assertNotUndefined(group.perpMarketsMapByName.get('MNGO-PERP')?.perpMarketIndex), perp_1.PerpOrderSide.bid, 0.001, // ui price that won't get hit
3.0, // ui base quantity, 30 base lots, 3.0 MNGO, $0.06

@@ -179,3 +180,3 @@ 0.06, // ui quote quantity

console.log(`Creating mangoaccount...`);
let mangoAccount = await createMangoAccount(name);
const mangoAccount = await createMangoAccount(name);
console.log(`...created mangoAccount ${mangoAccount.publicKey} for ${name}`);

@@ -188,9 +189,9 @@ const collateralMint = new web3_js_1.PublicKey(MAINNET_MINTS.get('SOL'));

try {
await client.perpPlaceOrder(group, fundingAccount, group.perpMarketsMapByName.get('MNGO-PERP')?.perpMarketIndex, perp_1.PerpOrderSide.ask, 0.03, 1.1, // ui base quantity, 11 base lots, $0.022 value, gain $0.033
await client.perpPlaceOrder(group, fundingAccount, assertNotUndefined(group.perpMarketsMapByName.get('MNGO-PERP')?.perpMarketIndex), perp_1.PerpOrderSide.ask, 0.03, 1.1, // ui base quantity, 11 base lots, $0.022 value, gain $0.033
0.033, // ui quote quantity
4200, perp_1.PerpOrderType.limit, false, 0, 5);
await client.perpPlaceOrder(group, mangoAccount, group.perpMarketsMapByName.get('MNGO-PERP')?.perpMarketIndex, perp_1.PerpOrderSide.bid, 0.03, 1.1, // ui base quantity, 11 base lots, $0.022 value, cost $0.033
await client.perpPlaceOrder(group, mangoAccount, assertNotUndefined(group.perpMarketsMapByName.get('MNGO-PERP')?.perpMarketIndex), perp_1.PerpOrderSide.bid, 0.03, 1.1, // ui base quantity, 11 base lots, $0.022 value, cost $0.033
0.033, // ui quote quantity
4200, perp_1.PerpOrderType.market, false, 0, 5);
await client.perpConsumeAllEvents(group, group.perpMarketsMapByName.get('MNGO-PERP')?.perpMarketIndex);
await client.perpConsumeAllEvents(group, assertNotUndefined(group.perpMarketsMapByName.get('MNGO-PERP')?.perpMarketIndex));
}

@@ -205,3 +206,3 @@ finally {

console.log(`Creating mangoaccount...`);
let mangoAccount = await createMangoAccount(name);
const mangoAccount = await createMangoAccount(name);
console.log(`...created mangoAccount ${mangoAccount.publicKey} for ${name}`);

@@ -247,3 +248,3 @@ const perpMarket = group.perpMarketsMapByName.get('MNGO-PERP');

console.log(`Creating mangoaccount...`);
let mangoAccount = await createMangoAccount(name);
const mangoAccount = await createMangoAccount(name);
console.log(`...created mangoAccount ${mangoAccount.publicKey} for ${name}`);

@@ -284,2 +285,8 @@ const perpMarket = group.perpMarketsMapByName.get('MNGO-PERP');

}
function assertNotUndefined(value) {
if (value === undefined) {
throw new Error('Value was undefined');
}
return value;
}
main();

@@ -13,7 +13,8 @@ "use strict";

// This script tries to withdraw all positive balances for all accounts
// by MANGO_MAINNET_PAYER_KEYPAIR in the group.
// by PAYER_KEYPAIR in the group.
//
const GROUP_NUM = Number(process.env.GROUP_NUM || 200);
const CLUSTER = process.env.CLUSTER || 'mainnet-beta';
const CLUSTER_URL = process.env.CLUSTER_URL;
const MANGO_MAINNET_PAYER_KEYPAIR = process.env.MANGO_MAINNET_PAYER_KEYPAIR || '';
const PAYER_KEYPAIR = process.env.PAYER_KEYPAIR || '';
async function main() {

@@ -24,6 +25,6 @@ const options = anchor_1.AnchorProvider.defaultOptions();

const connection = new web3_js_1.Connection(CLUSTER_URL, options);
const admin = web3_js_1.Keypair.fromSecretKey(Buffer.from(JSON.parse(fs_1.default.readFileSync(MANGO_MAINNET_PAYER_KEYPAIR, 'utf-8'))));
const admin = web3_js_1.Keypair.fromSecretKey(Buffer.from(JSON.parse(fs_1.default.readFileSync(PAYER_KEYPAIR, 'utf-8'))));
const userWallet = new anchor_1.Wallet(admin);
const userProvider = new anchor_1.AnchorProvider(connection, userWallet, options);
const client = await client_1.MangoClient.connect(userProvider, 'mainnet-beta', constants_1.MANGO_V4_ID['mainnet-beta'], {
const client = await client_1.MangoClient.connect(userProvider, CLUSTER, constants_1.MANGO_V4_ID[CLUSTER], {
idsSource: 'get-program-accounts',

@@ -42,4 +43,4 @@ prioritizationFee: 100,

let accounts = await client.getMangoAccountsForOwner(group, admin.publicKey);
for (let account of accounts) {
for (let serumOrders of account.serum3Active()) {
for (const account of accounts) {
for (const serumOrders of account.serum3Active()) {
const serumMarket = group.getSerum3MarketByMarketIndex(serumOrders.marketIndex);

@@ -52,3 +53,3 @@ const serumExternal = serumMarket.serumMarketExternal;

}
for (let perpPosition of account.perpActive()) {
for (const perpPosition of account.perpActive()) {
const perpMarket = group.findPerpMarket(perpPosition.marketIndex);

@@ -60,3 +61,3 @@ console.log(`closing perp orders on: ${account} for market ${perpMarket.name}`);

accounts = await client.getMangoAccountsForOwner(group, admin.publicKey);
for (let account of accounts) {
for (const account of accounts) {
// close account

@@ -63,0 +64,0 @@ try {

@@ -34,3 +34,2 @@ "use strict";

const CLUSTER_URL = process.env.CLUSTER_URL_OVERRIDE || process.env.MB_CLUSTER_URL;
const PAYER_KEYPAIR = process.env.PAYER_KEYPAIR_OVERRIDE || process.env.MB_PAYER_KEYPAIR;
const GROUP_PK = process.env.GROUP_PK || '78b8f4cGCwmZ9ysPFMWLaLTkkaYnUjwMJYStWe5RTSSX';

@@ -37,0 +36,0 @@ const CLUSTER = process.env.CLUSTER_OVERRIDE || 'mainnet-beta';

@@ -37,2 +37,6 @@ /// <reference types="bn.js" />

scaledInitLiabWeight(price: I80F48): I80F48;
nativeDeposits(): I80F48;
nativeBorrows(): I80F48;
depositWeightScaleStartQuote: number;
borrowWeightScaleStartQuote: number;
}

@@ -39,0 +43,0 @@ export declare class Bank implements BankForHealth {

@@ -18,10 +18,23 @@ /// <reference types="bn.js" />

static fromDto(dto: any): HealthCache;
computeSerum3Reservations(healthType: HealthType): {
tokenMaxReserved: I80F48[];
computeSerum3Reservations(healthType: HealthType | undefined): {
tokenMaxReserved: TokenMaxReserved[];
serum3Reserved: Serum3Reserved[];
};
effectiveTokenBalances(healthType: HealthType | undefined): TokenBalance[];
effectiveTokenBalancesInternal(healthType: HealthType | undefined, ignoreNegativePerp: boolean): TokenBalance[];
healthSum(healthType: HealthType, tokenBalances: TokenBalance[]): I80F48;
health(healthType: HealthType): I80F48;
perpSettleHealth(): I80F48;
assets(healthType?: HealthType): I80F48;
liabs(healthType?: HealthType): I80F48;
perpMaxSettle(settleTokenIndex: TokenIndex): I80F48;
healthAssetsAndLiabsStableAssets(healthType: HealthType): {
assets: I80F48;
liabs: I80F48;
};
healthAssetsAndLiabsStableLiabs(healthType: HealthType): {
assets: I80F48;
liabs: I80F48;
};
healthAssetsAndLiabs(healthType: HealthType | undefined, stableAssets: boolean): {
assets: I80F48;
liabs: I80F48;
};
healthRatio(healthType: HealthType): I80F48;

@@ -43,3 +56,2 @@ findTokenInfoIndex(tokenIndex: TokenIndex): number;

simHealthRatioWithPerpOrderChanges(perpMarket: PerpMarket, existingPerpPosition: PerpPosition, side: PerpOrderSide, baseLots: BN, price: I80F48, healthType?: HealthType): I80F48;
logHealthCache(debug: string): void;
private static scanRightUntilLessThan;

@@ -72,11 +84,21 @@ private static findMaximum;

prices: Prices;
balanceNative: I80F48;
constructor(tokenIndex: TokenIndex, maintAssetWeight: I80F48, initAssetWeight: I80F48, initScaledAssetWeight: I80F48, maintLiabWeight: I80F48, initLiabWeight: I80F48, initScaledLiabWeight: I80F48, prices: Prices, balanceNative: I80F48);
balanceSpot: I80F48;
constructor(tokenIndex: TokenIndex, maintAssetWeight: I80F48, initAssetWeight: I80F48, initScaledAssetWeight: I80F48, maintLiabWeight: I80F48, initLiabWeight: I80F48, initScaledLiabWeight: I80F48, prices: Prices, balanceSpot: I80F48);
static fromDto(dto: TokenInfoDto): TokenInfo;
static fromBank(bank: BankForHealth, nativeBalance?: I80F48): TokenInfo;
assetWeight(healthType: HealthType): I80F48;
liabWeight(healthType: HealthType): I80F48;
healthContribution(healthType?: HealthType): I80F48;
toString(): string;
assetWeight(healthType: HealthType | undefined): I80F48;
assetWeightedPrice(healthType: HealthType | undefined): I80F48;
liabWeight(healthType: HealthType | undefined): I80F48;
liabWeightedPrice(healthType: HealthType | undefined): I80F48;
healthContribution(healthType: HealthType | undefined, balance: I80F48): I80F48;
toString(balance: I80F48): string;
}
declare class TokenBalance {
spotAndPerp: I80F48;
constructor(spotAndPerp: I80F48);
}
declare class TokenMaxReserved {
maxSerumReserved: I80F48;
constructor(maxSerumReserved: I80F48);
}
export declare class Serum3Reserved {

@@ -90,14 +112,15 @@ allReservedAsBase: I80F48;

reservedQuote: I80F48;
baseIndex: number;
quoteIndex: number;
baseInfoIndex: number;
quoteInfoIndex: number;
marketIndex: MarketIndex;
constructor(reservedBase: I80F48, reservedQuote: I80F48, baseIndex: number, quoteIndex: number, marketIndex: MarketIndex);
constructor(reservedBase: I80F48, reservedQuote: I80F48, baseInfoIndex: number, quoteInfoIndex: number, marketIndex: MarketIndex);
static fromDto(dto: Serum3InfoDto): Serum3Info;
static emptyFromSerum3Market(serum3Market: Serum3Market, baseEntryIndex: number, quoteEntryIndex: number): Serum3Info;
static fromOoModifyingTokenInfos(baseIndex: number, baseInfo: TokenInfo, quoteIndex: number, quoteInfo: TokenInfo, marketIndex: MarketIndex, oo: OpenOrders): Serum3Info;
healthContribution(healthType: HealthType | undefined, tokenInfos: TokenInfo[], tokenMaxReserved: I80F48[], marketReserved: Serum3Reserved): I80F48;
toString(tokenInfos: TokenInfo[], tokenMaxReserved: I80F48[], marketReserved: Serum3Reserved): string;
static fromOoModifyingTokenInfos(baseInfoIndex: number, baseInfo: TokenInfo, quoteInfoIndex: number, quoteInfo: TokenInfo, marketIndex: MarketIndex, oo: OpenOrders): Serum3Info;
healthContribution(healthType: HealthType | undefined, tokenInfos: TokenInfo[], tokenBalances: TokenBalance[], tokenMaxReserved: TokenMaxReserved[], marketReserved: Serum3Reserved): I80F48;
toString(tokenInfos: TokenInfo[], tokenBalances: TokenBalance[], tokenMaxReserved: TokenMaxReserved[], marketReserved: Serum3Reserved): string;
}
export declare class PerpInfo {
perpMarketIndex: number;
settleTokenIndex: TokenIndex;
maintBaseAssetWeight: I80F48;

@@ -114,9 +137,12 @@ initBaseAssetWeight: I80F48;

quote: I80F48;
prices: Prices;
basePrices: Prices;
hasOpenOrders: boolean;
constructor(perpMarketIndex: number, maintBaseAssetWeight: I80F48, initBaseAssetWeight: I80F48, maintBaseLiabWeight: I80F48, initBaseLiabWeight: I80F48, maintOverallAssetWeight: I80F48, initOverallAssetWeight: I80F48, baseLotSize: BN, baseLots: BN, bidsBaseLots: BN, asksBaseLots: BN, quote: I80F48, prices: Prices, hasOpenOrders: boolean);
constructor(perpMarketIndex: number, settleTokenIndex: TokenIndex, maintBaseAssetWeight: I80F48, initBaseAssetWeight: I80F48, maintBaseLiabWeight: I80F48, initBaseLiabWeight: I80F48, maintOverallAssetWeight: I80F48, initOverallAssetWeight: I80F48, baseLotSize: BN, baseLots: BN, bidsBaseLots: BN, asksBaseLots: BN, quote: I80F48, basePrices: Prices, hasOpenOrders: boolean);
static fromDto(dto: PerpInfoDto): PerpInfo;
static fromPerpPosition(perpMarket: PerpMarket, perpPosition: PerpPosition): PerpInfo;
healthContribution(healthType: HealthType | undefined): I80F48;
unweightedHealthContribution(healthType: HealthType | undefined): I80F48;
healthContribution(healthType: HealthType, settleToken: TokenInfo): I80F48;
healthUnsettledPnl(healthType: HealthType | undefined): I80F48;
weighHealthContributionSettle(unweighted: I80F48, healthType: HealthType, settleToken: TokenInfo): I80F48;
weighHealthContributionOverall(unweighted: I80F48, healthType: HealthType | undefined): I80F48;
unweightedHealthUnsettledPnl(healthType: HealthType | undefined): I80F48;
static emptyFromPerpMarket(perpMarket: PerpMarket): PerpInfo;

@@ -142,3 +168,3 @@ toString(): string;

};
balanceNative: I80F48Dto;
balanceSpot: I80F48Dto;
constructor(tokenIndex: number, maintAssetWeight: I80F48Dto, initAssetWeight: I80F48Dto, initScaledAssetWeight: I80F48Dto, maintLiabWeight: I80F48Dto, initLiabWeight: I80F48Dto, initScaledLiabWeight: I80F48Dto, prices: {

@@ -152,9 +178,10 @@ oracle: I80F48Dto;

reservedQuote: I80F48Dto;
baseIndex: number;
quoteIndex: number;
baseInfoIndex: number;
quoteInfoIndex: number;
marketIndex: number;
constructor(reservedBase: I80F48Dto, reservedQuote: I80F48Dto, baseIndex: number, quoteIndex: number);
constructor(reservedBase: I80F48Dto, reservedQuote: I80F48Dto, baseInfoIndex: number, quoteInfoIndex: number);
}
export declare class PerpInfoDto {
perpMarketIndex: number;
settleTokenIndex: number;
maintBaseAssetWeight: I80F48Dto;

@@ -177,1 +204,2 @@ initBaseAssetWeight: I80F48Dto;

}
export {};

@@ -14,3 +14,3 @@ "use strict";

const perp_1 = require("./perp");
function mockBankAndOracle(tokenIndex, maintWeight, initWeight, price, stablePrice) {
function mockBankAndOracle(tokenIndex, maintWeight, initWeight, price, stablePrice, deposits = 0, borrows = 0, borrowWeightScaleStartQuote = Number.MAX_SAFE_INTEGER, depositWeightScaleStartQuote = Number.MAX_SAFE_INTEGER) {
return {

@@ -24,9 +24,28 @@ tokenIndex,

stablePriceModel: { stablePrice: stablePrice },
scaledInitAssetWeight: () => I80F48_1.I80F48.fromNumber(1 - initWeight),
scaledInitLiabWeight: () => I80F48_1.I80F48.fromNumber(1 + initWeight),
scaledInitAssetWeight: (price) => {
const depositsQuote = I80F48_1.I80F48.fromNumber(deposits).mul(price);
if (depositWeightScaleStartQuote >= Number.MAX_SAFE_INTEGER ||
depositsQuote.lte(I80F48_1.I80F48.fromNumber(depositWeightScaleStartQuote))) {
return I80F48_1.I80F48.fromNumber(1 - initWeight);
}
return I80F48_1.I80F48.fromNumber(1 - initWeight).mul(I80F48_1.I80F48.fromNumber(depositWeightScaleStartQuote).div(depositsQuote));
},
scaledInitLiabWeight: (price) => {
const borrowsQuote = I80F48_1.I80F48.fromNumber(borrows).mul(price);
if (borrowWeightScaleStartQuote >= Number.MAX_SAFE_INTEGER ||
borrowsQuote.lte(I80F48_1.I80F48.fromNumber(borrowWeightScaleStartQuote))) {
return I80F48_1.I80F48.fromNumber(1 + initWeight);
}
return I80F48_1.I80F48.fromNumber(1 + initWeight).mul(borrowsQuote.div(I80F48_1.I80F48.fromNumber(borrowWeightScaleStartQuote)));
},
nativeDeposits: () => I80F48_1.I80F48.fromNumber(deposits),
nativeBorrows: () => I80F48_1.I80F48.fromNumber(borrows),
borrowWeightScaleStartQuote: borrowWeightScaleStartQuote,
depositWeightScaleStartQuote: depositWeightScaleStartQuote,
};
}
function mockPerpMarket(perpMarketIndex, maintBaseWeight, initBaseWeight, baseLotSize, price) {
function mockPerpMarket(perpMarketIndex, settleTokenIndex, maintBaseWeight, initBaseWeight, baseLotSize, price) {
return {
perpMarketIndex,
settleTokenIndex: settleTokenIndex,
maintBaseAssetWeight: I80F48_1.I80F48.fromNumber(1 - maintBaseWeight),

@@ -48,3 +67,3 @@ initBaseAssetWeight: I80F48_1.I80F48.fromNumber(1 - initBaseWeight),

it('test_health0', () => {
const sourceBank = mockBankAndOracle(1, 0.1, 0.2, 1, 1);
const sourceBank = mockBankAndOracle(0, 0.1, 0.2, 1, 1);
const targetBank = mockBankAndOracle(4, 0.3, 0.5, 5, 5);

@@ -60,13 +79,15 @@ const ti1 = healthCache_1.TokenInfo.fromBank(sourceBank, I80F48_1.I80F48.fromNumber(100));

});
const pM = mockPerpMarket(9, 0.1, 0.2, 10, targetBank.price.toNumber());
const pM = mockPerpMarket(9, 0, 0.1, 0.2, 10, targetBank.price.toNumber());
const pp = new mangoAccount_1.PerpPosition(pM.perpMarketIndex, 0, new anchor_1.BN(0), new anchor_1.BN(3), I80F48_1.I80F48.fromNumber(-310), new anchor_1.BN(0), (0, I80F48_1.ZERO_I80F48)(), (0, I80F48_1.ZERO_I80F48)(), new anchor_1.BN(7), new anchor_1.BN(11), new anchor_1.BN(1), new anchor_1.BN(2), 0, 0, new anchor_1.BN(0), new anchor_1.BN(0), new anchor_1.BN(0), 0, (0, I80F48_1.ZERO_I80F48)(), (0, I80F48_1.ZERO_I80F48)(), new anchor_1.BN(0), (0, I80F48_1.ZERO_I80F48)());
const pi1 = healthCache_1.PerpInfo.fromPerpPosition(pM, pp);
const hc = new healthCache_1.HealthCache([ti1, ti2], [si1], [pi1]);
// for bank1/oracle1, including open orders (scenario: bids execute)
const health1 = (100.0 + 1.0 + 2.0 + (20.0 + 15.0 * 5.0)) * 0.8;
// for bank1/oracle1
// including open orders (scenario: bids execute)
const serum1 = 1.0 + (20.0 + 15.0 * 5.0);
// and perp (scenario: bids execute)
const perp1 = (3.0 + 7.0 + 1.0) * 10.0 * 5.0 * 0.8 +
(-310.0 + 2.0 * 100.0 - 7.0 * 10.0 * 5.0);
const health1 = (100.0 + serum1 + perp1) * 0.8;
// for bank2/oracle2
const health2 = (-10.0 + 3.0) * 5.0 * 1.5;
// for perp (scenario: bids execute)
const health3 = (3.0 + 7.0 + 1.0) * 10.0 * 5.0 * 0.8 +
(-310.0 + 2.0 * 100.0 - 7.0 * 10.0 * 5.0);
const health = hc.health(mangoAccount_1.HealthType.init).toNumber();

@@ -76,9 +97,9 @@ console.log(` - health ${health

.padStart(10)}, case "test that includes all the side values (like referrer_rebates_accrued)"`);
(0, chai_1.expect)(health - (health1 + health2 + health3)).lessThan(0.0000001);
(0, chai_1.expect)(health - (health1 + health2)).lessThan(0.0000001);
});
it('test_health1', (done) => {
function testFixture(fixture) {
const bank1 = mockBankAndOracle(1, 0.1, 0.2, 1, 1);
const bank2 = mockBankAndOracle(4, 0.3, 0.5, 5, 5);
const bank3 = mockBankAndOracle(5, 0.3, 0.5, 10, 10);
const bank1 = mockBankAndOracle(0, 0.1, 0.2, 1, 1, fixture.bs1[0], fixture.bs1[0], fixture.bs1[1], fixture.bs1[1]);
const bank2 = mockBankAndOracle(4, 0.3, 0.5, 5, 5, fixture.bs2[0], fixture.bs2[0], fixture.bs2[1], fixture.bs2[1]);
const bank3 = mockBankAndOracle(5, 0.3, 0.5, 10, 10, fixture.bs3[0], fixture.bs3[0], fixture.bs3[1], fixture.bs3[1]);
const ti1 = healthCache_1.TokenInfo.fromBank(bank1, I80F48_1.I80F48.fromNumber(fixture.token1));

@@ -101,3 +122,3 @@ const ti2 = healthCache_1.TokenInfo.fromBank(bank2, I80F48_1.I80F48.fromNumber(fixture.token2));

});
const pM = mockPerpMarket(9, 0.1, 0.2, 10, bank2.price.toNumber());
const pM = mockPerpMarket(9, 0, 0.1, 0.2, 10, bank2.price.toNumber());
const pp = new mangoAccount_1.PerpPosition(pM.perpMarketIndex, 0, new anchor_1.BN(0), new anchor_1.BN(fixture.perp1[0]), I80F48_1.I80F48.fromNumber(fixture.perp1[1]), new anchor_1.BN(0), (0, I80F48_1.ZERO_I80F48)(), (0, I80F48_1.ZERO_I80F48)(), new anchor_1.BN(fixture.perp1[2]), new anchor_1.BN(fixture.perp1[3]), new anchor_1.BN(0), new anchor_1.BN(0), 0, 0, new anchor_1.BN(0), new anchor_1.BN(0), new anchor_1.BN(0), 0, (0, I80F48_1.ZERO_I80F48)(), (0, I80F48_1.ZERO_I80F48)(), new anchor_1.BN(0), (0, I80F48_1.ZERO_I80F48)());

@@ -119,2 +140,5 @@ const pi1 = healthCache_1.PerpInfo.fromPerpPosition(pM, pp);

token3: 0,
bs1: [0, Number.MAX_SAFE_INTEGER],
bs2: [0, Number.MAX_SAFE_INTEGER],
bs3: [0, Number.MAX_SAFE_INTEGER],
oo12: [20, 15],

@@ -124,9 +148,12 @@ oo13: [0, 0],

expectedHealth:
// for token1, including open orders (scenario: bids execute)
(100.0 + (20.0 + 15.0 * basePrice)) * 0.8 -
// for token1
0.8 *
(100.0 +
// including open orders (scenario: bids execute)
(20.0 + 15.0 * basePrice) +
// including perp (scenario: bids execute)
(3.0 + 7.0) * baseLotsToQuote * 0.8 +
(-131.0 - 7.0 * baseLotsToQuote)) -
// for token2
10.0 * basePrice * 1.5 +
// for perp (scenario: bids execute)
(3.0 + 7.0) * baseLotsToQuote * 0.8 +
(-131.0 - 7.0 * baseLotsToQuote),
10.0 * basePrice * 1.5,
});

@@ -138,2 +165,5 @@ testFixture({

token3: 0,
bs1: [0, Number.MAX_SAFE_INTEGER],
bs2: [0, Number.MAX_SAFE_INTEGER],
bs3: [0, Number.MAX_SAFE_INTEGER],
oo12: [20, 15],

@@ -144,8 +174,9 @@ oo13: [0, 0],

// for token1
-100.0 * 1.2 +
1.2 *
(-100.0 +
// for perp (scenario: asks execute)
(-10.0 - 11.0) * baseLotsToQuote * 1.2 +
(-131.0 + 11.0 * baseLotsToQuote)) +
// for token2, including open orders (scenario: asks execute)
(10.0 * basePrice + (20.0 + 15.0 * basePrice)) * 0.5 +
// for perp (scenario: asks execute)
(-10.0 - 11.0) * baseLotsToQuote * 1.2 +
(-131.0 + 11.0 * baseLotsToQuote),
(10.0 * basePrice + (20.0 + 15.0 * basePrice)) * 0.5,
});

@@ -157,6 +188,9 @@ testFixture({

token3: 0,
bs1: [0, Number.MAX_SAFE_INTEGER],
bs2: [0, Number.MAX_SAFE_INTEGER],
bs3: [0, Number.MAX_SAFE_INTEGER],
oo12: [0, 0],
oo13: [0, 0],
perp1: [-1, 100, 0, 0],
expectedHealth: 0.95 * (100.0 - 1.2 * 1.0 * baseLotsToQuote),
expectedHealth: 0.8 * 0.95 * (100.0 - 1.2 * 1.0 * baseLotsToQuote),
});

@@ -168,6 +202,9 @@ testFixture({

token3: 0,
bs1: [0, Number.MAX_SAFE_INTEGER],
bs2: [0, Number.MAX_SAFE_INTEGER],
bs3: [0, Number.MAX_SAFE_INTEGER],
oo12: [0, 0],
oo13: [0, 0],
perp1: [1, -100, 0, 0],
expectedHealth: -100.0 + 0.8 * 1.0 * baseLotsToQuote,
expectedHealth: 1.2 * (-100.0 + 0.8 * 1.0 * baseLotsToQuote),
});

@@ -179,6 +216,9 @@ testFixture({

token3: 0,
bs1: [0, Number.MAX_SAFE_INTEGER],
bs2: [0, Number.MAX_SAFE_INTEGER],
bs3: [0, Number.MAX_SAFE_INTEGER],
oo12: [0, 0],
oo13: [0, 0],
perp1: [10, 100, 0, 0],
expectedHealth: 0.95 * (100.0 + 0.8 * 10.0 * baseLotsToQuote),
expectedHealth: 0.8 * 0.95 * (100.0 + 0.8 * 10.0 * baseLotsToQuote),
});

@@ -190,6 +230,9 @@ testFixture({

token3: 0,
bs1: [0, Number.MAX_SAFE_INTEGER],
bs2: [0, Number.MAX_SAFE_INTEGER],
bs3: [0, Number.MAX_SAFE_INTEGER],
oo12: [0, 0],
oo13: [0, 0],
perp1: [30, -100, 0, 0],
expectedHealth: 0.95 * (-100.0 + 0.8 * 30.0 * baseLotsToQuote),
expectedHealth: 0.8 * 0.95 * (-100.0 + 0.8 * 30.0 * baseLotsToQuote),
});

@@ -201,2 +244,5 @@ testFixture({

token3: -10,
bs1: [0, Number.MAX_SAFE_INTEGER],
bs2: [0, Number.MAX_SAFE_INTEGER],
bs3: [0, Number.MAX_SAFE_INTEGER],
oo12: [1, 1],

@@ -220,2 +266,5 @@ oo13: [1, 1],

token3: -10,
bs1: [0, Number.MAX_SAFE_INTEGER],
bs2: [0, Number.MAX_SAFE_INTEGER],
bs3: [0, Number.MAX_SAFE_INTEGER],
oo12: [1, 1],

@@ -239,2 +288,5 @@ oo13: [1, 1],

token3: -1,
bs1: [0, Number.MAX_SAFE_INTEGER],
bs2: [0, Number.MAX_SAFE_INTEGER],
bs3: [0, Number.MAX_SAFE_INTEGER],
oo12: [0, 0],

@@ -257,2 +309,5 @@ oo13: [10, 1],

token3: -1,
bs1: [0, Number.MAX_SAFE_INTEGER],
bs2: [0, Number.MAX_SAFE_INTEGER],
bs3: [0, Number.MAX_SAFE_INTEGER],
oo12: [100, 0],

@@ -272,2 +327,66 @@ oo13: [10, 1],

});
testFixture({
name: '10, checking collateral limit',
token1: 100,
token2: 100,
token3: 100,
bs1: [100, 1000],
bs2: [1500, 5000],
bs3: [10000, 10000],
oo12: [0, 0],
oo13: [0, 0],
perp1: [0, 0, 0, 0],
expectedHealth:
// token1
0.8 * 100.0 +
// token2
0.5 * 100.0 * 5.0 * (5000.0 / (1500.0 * 5.0)) +
// token3
0.5 * 100.0 * 10.0 * (10000.0 / (10000.0 * 10.0)),
});
testFixture({
name: '11, checking borrow limit',
token1: -100,
token2: -100,
token3: -100,
bs1: [100, 1000],
bs2: [1500, 5000],
bs3: [10000, 10000],
oo12: [0, 0],
oo13: [0, 0],
perp1: [0, 0, 0, 0],
expectedHealth:
// token1
-1.2 * 100.0 -
// token2
1.5 * 100.0 * 5.0 * ((1500.0 * 5.0) / 5000.0) -
// token3
1.5 * 100.0 * 10.0 * ((10000.0 * 10.0) / 10000.0),
});
testFixture({
name: '12, positive perp health offsets token borrow',
token1: -100,
token2: 0,
token3: 0,
bs1: [0, Number.MAX_SAFE_INTEGER],
bs2: [0, Number.MAX_SAFE_INTEGER],
bs3: [0, Number.MAX_SAFE_INTEGER],
oo12: [0, 0],
oo13: [0, 0],
perp1: [1, 100, 0, 0],
expectedHealth: 0.8 * (-100.0 + 0.95 * (100.0 + 0.8 * 1.0 * baseLotsToQuote)),
});
testFixture({
name: '13, negative perp health offsets token deposit',
token1: 100,
token2: 0,
token3: 0,
bs1: [0, Number.MAX_SAFE_INTEGER],
bs2: [0, Number.MAX_SAFE_INTEGER],
bs3: [0, Number.MAX_SAFE_INTEGER],
oo12: [0, 0],
oo13: [0, 0],
perp1: [-1, -100, 0, 0],
expectedHealth: 1.2 * (100.0 - 100.0 - 1.2 * 1.0 * baseLotsToQuote),
});
done();

@@ -299,4 +418,4 @@ });

const clonedHcClone = (0, cloneDeep_1.default)(clonedHc);
clonedHc.tokenInfos[source].balanceNative.isub(amount);
clonedHc.tokenInfos[target].balanceNative.iadd(amount.mul(swapPrice));
clonedHc.tokenInfos[source].balanceSpot.isub(amount);
clonedHc.tokenInfos[target].balanceSpot.iadd(amount.mul(swapPrice));
return maxSwapFn(clonedHcClone);

@@ -336,3 +455,3 @@ }

const clonedHc = (0, cloneDeep_1.default)(hc);
clonedHc.tokenInfos[1].balanceNative.iadd(I80F48_1.I80F48.fromNumber(100).div(clonedHc.tokenInfos[1].prices.oracle));
clonedHc.tokenInfos[1].balanceSpot.iadd(I80F48_1.I80F48.fromNumber(100).div(clonedHc.tokenInfos[1].prices.oracle));
for (const priceFactor of [0.1, 0.9, 1.1]) {

@@ -354,4 +473,4 @@ for (const target of (0, range_1.default)(1, 100, 1)) {

// adjust by usdc
clonedHc.tokenInfos[0].balanceNative.iadd(I80F48_1.I80F48.fromNumber(-20).div(clonedHc.tokenInfos[0].prices.oracle));
clonedHc.tokenInfos[1].balanceNative.iadd(I80F48_1.I80F48.fromNumber(100).div(clonedHc.tokenInfos[1].prices.oracle));
clonedHc.tokenInfos[0].balanceSpot.iadd(I80F48_1.I80F48.fromNumber(-20).div(clonedHc.tokenInfos[0].prices.oracle));
clonedHc.tokenInfos[1].balanceSpot.iadd(I80F48_1.I80F48.fromNumber(100).div(clonedHc.tokenInfos[1].prices.oracle));
for (const priceFactor of [0.1, 0.9, 1.1]) {

@@ -370,4 +489,4 @@ for (const target of (0, range_1.default)(1, 100, 1)) {

// adjust by usdc
clonedHc.tokenInfos[0].balanceNative.iadd(I80F48_1.I80F48.fromNumber(-50).div(clonedHc.tokenInfos[0].prices.oracle));
clonedHc.tokenInfos[1].balanceNative.iadd(I80F48_1.I80F48.fromNumber(100).div(clonedHc.tokenInfos[1].prices.oracle));
clonedHc.tokenInfos[0].balanceSpot.iadd(I80F48_1.I80F48.fromNumber(-50).div(clonedHc.tokenInfos[0].prices.oracle));
clonedHc.tokenInfos[1].balanceSpot.iadd(I80F48_1.I80F48.fromNumber(100).div(clonedHc.tokenInfos[1].prices.oracle));
// possible even though the init ratio is <100

@@ -380,5 +499,5 @@ checkMaxSwapResult(clonedHc, 1, 0, 100, 1, maxSwapFn);

// adjust by usdc
clonedHc.tokenInfos[0].balanceNative.iadd(I80F48_1.I80F48.fromNumber(-30).div(clonedHc.tokenInfos[0].prices.oracle));
clonedHc.tokenInfos[1].balanceNative.iadd(I80F48_1.I80F48.fromNumber(100).div(clonedHc.tokenInfos[1].prices.oracle));
clonedHc.tokenInfos[2].balanceNative.iadd(I80F48_1.I80F48.fromNumber(-30).div(clonedHc.tokenInfos[2].prices.oracle));
clonedHc.tokenInfos[0].balanceSpot.iadd(I80F48_1.I80F48.fromNumber(-30).div(clonedHc.tokenInfos[0].prices.oracle));
clonedHc.tokenInfos[1].balanceSpot.iadd(I80F48_1.I80F48.fromNumber(100).div(clonedHc.tokenInfos[1].prices.oracle));
clonedHc.tokenInfos[2].balanceSpot.iadd(I80F48_1.I80F48.fromNumber(-30).div(clonedHc.tokenInfos[2].prices.oracle));
// swapping with a high ratio advises paying back all liabs

@@ -395,5 +514,5 @@ // and then swapping even more because increasing assets in 0 has better asset weight

// adjust by usdc
clonedHc.tokenInfos[0].balanceNative.iadd(I80F48_1.I80F48.fromNumber(100).div(clonedHc.tokenInfos[0].prices.oracle));
clonedHc.tokenInfos[1].balanceNative.iadd(I80F48_1.I80F48.fromNumber(-2).div(clonedHc.tokenInfos[1].prices.oracle));
clonedHc.tokenInfos[2].balanceNative.iadd(I80F48_1.I80F48.fromNumber(-65).div(clonedHc.tokenInfos[2].prices.oracle));
clonedHc.tokenInfos[0].balanceSpot.iadd(I80F48_1.I80F48.fromNumber(100).div(clonedHc.tokenInfos[0].prices.oracle));
clonedHc.tokenInfos[1].balanceSpot.iadd(I80F48_1.I80F48.fromNumber(-2).div(clonedHc.tokenInfos[1].prices.oracle));
clonedHc.tokenInfos[2].balanceSpot.iadd(I80F48_1.I80F48.fromNumber(-65).div(clonedHc.tokenInfos[2].prices.oracle));
const initRatio = clonedHc.healthRatio(mangoAccount_1.HealthType.init);

@@ -414,5 +533,5 @@ (0, chai_1.expect)(initRatio.toNumber()).greaterThan(3);

// adjust by usdc
clonedHc.tokenInfos[0].balanceNative.iadd(I80F48_1.I80F48.fromNumber(-20).div(clonedHc.tokenInfos[0].prices.oracle));
clonedHc.tokenInfos[1].balanceNative.iadd(I80F48_1.I80F48.fromNumber(-40).div(clonedHc.tokenInfos[1].prices.oracle));
clonedHc.tokenInfos[2].balanceNative.iadd(I80F48_1.I80F48.fromNumber(120).div(clonedHc.tokenInfos[2].prices.oracle));
clonedHc.tokenInfos[0].balanceSpot.iadd(I80F48_1.I80F48.fromNumber(-20).div(clonedHc.tokenInfos[0].prices.oracle));
clonedHc.tokenInfos[1].balanceSpot.iadd(I80F48_1.I80F48.fromNumber(-40).div(clonedHc.tokenInfos[1].prices.oracle));
clonedHc.tokenInfos[2].balanceSpot.iadd(I80F48_1.I80F48.fromNumber(120).div(clonedHc.tokenInfos[2].prices.oracle));
for (const priceFactor of [0.9, 1.1]) {

@@ -434,4 +553,4 @@ for (const target of (0, range_1.default)(1, 100, 1)) {

// adjust by usdc
clonedHc.tokenInfos[0].balanceNative.iadd(I80F48_1.I80F48.fromNumber(-20).div(clonedHc.tokenInfos[0].prices.oracle));
clonedHc.tokenInfos[1].balanceNative.iadd(I80F48_1.I80F48.fromNumber(20).div(clonedHc.tokenInfos[1].prices.oracle));
clonedHc.tokenInfos[0].balanceSpot.iadd(I80F48_1.I80F48.fromNumber(-20).div(clonedHc.tokenInfos[0].prices.oracle));
clonedHc.tokenInfos[1].balanceSpot.iadd(I80F48_1.I80F48.fromNumber(20).div(clonedHc.tokenInfos[1].prices.oracle));
(0, chai_1.expect)(clonedHc.health(mangoAccount_1.HealthType.init).toNumber() < 0);

@@ -449,4 +568,4 @@ for (const priceFactor of [0.9, 1.1]) {

// adjust by usdc
clonedHc.tokenInfos[0].balanceNative.iadd(I80F48_1.I80F48.fromNumber(-20).div(clonedHc.tokenInfos[0].prices.oracle));
clonedHc.tokenInfos[1].balanceNative.iadd(I80F48_1.I80F48.fromNumber(10).div(clonedHc.tokenInfos[1].prices.oracle));
clonedHc.tokenInfos[0].balanceSpot.iadd(I80F48_1.I80F48.fromNumber(-20).div(clonedHc.tokenInfos[0].prices.oracle));
clonedHc.tokenInfos[1].balanceSpot.iadd(I80F48_1.I80F48.fromNumber(10).div(clonedHc.tokenInfos[1].prices.oracle));
(0, chai_1.expect)(clonedHc.health(mangoAccount_1.HealthType.init).toNumber() < 0);

@@ -464,3 +583,3 @@ for (const priceFactor of [0.9, 1.1]) {

// adjust by usdc
clonedHc.tokenInfos[0].balanceNative.iadd(I80F48_1.I80F48.fromNumber(10).div(clonedHc.tokenInfos[0].prices.oracle));
clonedHc.tokenInfos[0].balanceSpot.iadd(I80F48_1.I80F48.fromNumber(10).div(clonedHc.tokenInfos[0].prices.oracle));
clonedHc.tokenInfos[1].initAssetWeight = (0, I80F48_1.ZERO_I80F48)();

@@ -480,4 +599,8 @@ (0, chai_1.expect)(findMaxSwapActual(clonedHc, 0, 1, 1, 1, maxSwapFn)[0] > 0);

const b0 = mockBankAndOracle(0, 0.0, 0.0, 1, 1);
const p0 = mockPerpMarket(0, 0.3, 0.3, baseLotSize, 2);
const hc = new healthCache_1.HealthCache([healthCache_1.TokenInfo.fromBank(b0, I80F48_1.I80F48.fromNumber(0))], [], [healthCache_1.PerpInfo.emptyFromPerpMarket(p0)]);
const b1 = mockBankAndOracle(1, 0.2, 0.2, 1.5, 1.5);
const p0 = mockPerpMarket(0, 1, 0.3, 0.3, baseLotSize, 2);
const hc = new healthCache_1.HealthCache([
healthCache_1.TokenInfo.fromBank(b0, I80F48_1.I80F48.fromNumber(0)),
healthCache_1.TokenInfo.fromBank(b1, I80F48_1.I80F48.fromNumber(0)),
], [], [healthCache_1.PerpInfo.emptyFromPerpMarket(p0)]);
(0, chai_1.expect)(hc.health(mangoAccount_1.HealthType.init).toNumber()).equals(0);

@@ -488,3 +611,3 @@ (0, chai_1.expect)(hc

function findMaxTrade(hc, side, ratio, priceFactor) {
const prices = hc.perpInfos[0].prices;
const prices = hc.perpInfos[0].basePrices;
const tradePrice = I80F48_1.I80F48.fromNumber(priceFactor).mul(prices.oracle);

@@ -514,16 +637,20 @@ const baseLots0 = hc

console.log(`checking for price_factor: ${priceFactor}, target ratio ${ratio}: actual ratio: ${actualRatio}, plus ratio: ${plusRatio}, base_lots: ${baseLots}`);
(0, chai_1.expect)(ratio).lessThan(actualRatio);
(0, chai_1.expect)(ratio).lessThanOrEqual(actualRatio);
(0, chai_1.expect)(plusRatio - 0.1).lessThanOrEqual(ratio);
}
// adjust token
hc.tokenInfos[0].balanceNative.iadd(I80F48_1.I80F48.fromNumber(3000));
for (const existing of [-5, 0, 3]) {
const hcClone = (0, cloneDeep_1.default)(hc);
hcClone.perpInfos[0].baseLots.iadd(new anchor_1.BN(existing));
hcClone.perpInfos[0].quote.isub(I80F48_1.I80F48.fromNumber(existing * baseLotSize * 2));
for (const side of [perp_1.PerpOrderSide.bid, perp_1.PerpOrderSide.ask]) {
console.log(`existing ${existing} ${side === perp_1.PerpOrderSide.bid ? 'bid' : 'ask'}`);
for (const priceFactor of [0.8, 1.0, 1.1]) {
for (const ratio of (0, range_1.default)(1, 101, 1)) {
checkMaxTrade(hcClone, side, ratio, priceFactor);
hc.tokenInfos[0].balanceSpot.iadd(I80F48_1.I80F48.fromNumber(3000));
for (const existingSettle of [-500, 0, 300]) {
const hcClone1 = (0, cloneDeep_1.default)(hc);
hcClone1.tokenInfos[1].balanceSpot.iadd(I80F48_1.I80F48.fromNumber(existingSettle));
for (const existing of [-5, 0, 3]) {
const hcClone2 = (0, cloneDeep_1.default)(hcClone1);
hcClone2.perpInfos[0].baseLots.iadd(new anchor_1.BN(existing));
hcClone2.perpInfos[0].quote.isub(I80F48_1.I80F48.fromNumber(existing * baseLotSize * 2));
for (const side of [perp_1.PerpOrderSide.bid, perp_1.PerpOrderSide.ask]) {
console.log(`lots ${existing}, settle ${existingSettle}, side ${side === perp_1.PerpOrderSide.bid ? 'bid' : 'ask'}`);
for (const priceFactor of [0.8, 1.0, 1.1]) {
for (const ratio of (0, range_1.default)(1, 101, 1)) {
checkMaxTrade(hcClone2, side, ratio, priceFactor);
}
}

@@ -545,2 +672,3 @@ }

});
// TODO: test_assets_and_borrows
});

@@ -117,3 +117,3 @@ /// <reference types="bn.js" />

getHealth(group: Group, healthType: HealthType): I80F48;
getPerpSettleHealth(group: Group): I80F48;
perpMaxSettle(group: Group, perpMarketSettleTokenIndex: TokenIndex): I80F48;
/**

@@ -146,3 +146,3 @@ * Health ratio, which is computed so `100 * (assets-liabs)/liabs`

*/
getAssetsValue(group: Group, healthType?: HealthType): I80F48;
getAssetsValue(group: Group): I80F48;
/**

@@ -152,3 +152,3 @@ * Sum of all negative assets.

*/
getLiabsValue(group: Group, healthType?: HealthType): I80F48;
getLiabsValue(group: Group): I80F48;
/**

@@ -173,2 +173,3 @@ * @returns Overall PNL, in native quote

* TODO: take into account net_borrow_limit and min_vault_to_deposits_ratio
* TODO: see max_borrow_for_health_fn
*/

@@ -175,0 +176,0 @@ getMaxWithdrawWithBorrowForToken(group: Group, mintPk: PublicKey): I80F48;

@@ -221,5 +221,5 @@ "use strict";

}
getPerpSettleHealth(group) {
perpMaxSettle(group, perpMarketSettleTokenIndex) {
const hc = healthCache_1.HealthCache.fromMangoAccount(group, this);
return hc.perpSettleHealth();
return hc.perpMaxSettle(perpMarketSettleTokenIndex);
}

@@ -262,7 +262,5 @@ /**

const quoteBank = group.getFirstBankByTokenIndex(sp.quoteTokenIndex);
// NOTE: referrerRebatesAccrued is not declared on oo class, but the layout
// is aware of it
tokensMap
.get(baseBank.tokenIndex)
.iadd(I80F48_1.I80F48.fromI64(oo.quoteTokenTotal.add(oo.referrerRebatesAccrued)).mul(quoteBank.price));
.iadd(I80F48_1.I80F48.fromI64(oo.quoteTokenTotal).mul(quoteBank.price));
}

@@ -284,5 +282,5 @@ const tokenEquity = Array.from(tokensMap.values()).reduce((a, b) => a.add(b), (0, I80F48_1.ZERO_I80F48)());

*/
getAssetsValue(group, healthType) {
getAssetsValue(group) {
const hc = healthCache_1.HealthCache.fromMangoAccount(group, this);
return hc.assets(healthType);
return hc.healthAssetsAndLiabs(undefined, false).assets;
}

@@ -293,5 +291,5 @@ /**

*/
getLiabsValue(group, healthType) {
getLiabsValue(group) {
const hc = healthCache_1.HealthCache.fromMangoAccount(group, this);
return hc.liabs(healthType);
return hc.healthAssetsAndLiabs(undefined, false).liabs;
}

@@ -345,2 +343,3 @@ /**

* TODO: take into account net_borrow_limit and min_vault_to_deposits_ratio
* TODO: see max_borrow_for_health_fn
*/

@@ -1079,6 +1078,6 @@ getMaxWithdrawWithBorrowForToken(group, mintPk) {

this.updateSettleLimit(perpMarket);
const perpSettleHealth = account.getPerpSettleHealth(group);
const perpMaxSettle = account.perpMaxSettle(group, perpMarket.settleTokenIndex);
const limitedUnsettled = this.applyPnlSettleLimit(this.getUnsettledPnl(perpMarket), perpMarket);
if (limitedUnsettled.lt((0, I80F48_1.ZERO_I80F48)())) {
return limitedUnsettled.max(perpSettleHealth.max((0, I80F48_1.ZERO_I80F48)()).neg());
return limitedUnsettled.max(perpMaxSettle.max((0, I80F48_1.ZERO_I80F48)()).neg());
}

@@ -1085,0 +1084,0 @@ return limitedUnsettled;

@@ -332,8 +332,8 @@ "use strict";

: (0, I80F48_1.ZERO_I80F48)();
const perpSettleHealth = acc.account.getPerpSettleHealth(group);
const perpMaxSettle = acc.account.perpMaxSettle(group, this.settleTokenIndex);
acc.settleablePnl =
// need positive settle health to settle against +ve pnl
perpSettleHealth.gt((0, I80F48_1.ZERO_I80F48)())
perpMaxSettle.gt((0, I80F48_1.ZERO_I80F48)())
? // can only settle min
acc.settleablePnl.max(perpSettleHealth.neg())
acc.settleablePnl.max(perpMaxSettle.neg())
: (0, I80F48_1.ZERO_I80F48)();

@@ -340,0 +340,0 @@ // If the ordering was unchanged `count` times we know we have the top `count` accounts

@@ -88,3 +88,2 @@ "use strict";

healthRatio: a.getHealthRatioUi(group, mangoAccount_1.HealthType.liquidationEnd),
liabs: (0, utils_1.toUiDecimalsForQuote)(a.getLiabsValue(group, mangoAccount_1.HealthType.liquidationEnd)),
};

@@ -232,3 +231,2 @@ });

healthRatio: a.getHealthRatioUi(group, mangoAccount_1.HealthType.liquidationEnd),
liabs: (0, utils_1.toUiDecimalsForQuote)(a.getLiabsValue(group, mangoAccount_1.HealthType.liquidationEnd)),
};

@@ -235,0 +233,0 @@ });

import { BN } from '@coral-xyz/anchor';
import cloneDeep from 'lodash/cloneDeep';
import { HUNDRED_I80F48, I80F48, MAX_I80F48, ONE_I80F48, ZERO_I80F48, } from '../numbers/I80F48';
import { I80F48, MAX_I80F48, ONE_I80F48, ZERO_I80F48, } from '../numbers/I80F48';
import { toNativeI80F48ForQuote } from '../utils';

@@ -31,2 +31,28 @@ import { HealthType } from './mangoAccount';

// warning: this code is copy pasta from rust, keep in sync with health.rs
function spotAmountTakenForHealthZero(health, startingSpot, assetWeightedPrice, liabWeightedPrice) {
if (health.lte(ZERO_I80F48())) {
return ZERO_I80F48();
}
let takenSpot = ZERO_I80F48();
if (startingSpot.gt(ZERO_I80F48())) {
if (assetWeightedPrice.gt(ZERO_I80F48())) {
const assetMax = health.div(assetWeightedPrice);
if (assetMax.lte(startingSpot)) {
return assetMax;
}
}
takenSpot = startingSpot;
health.isub(startingSpot.mul(assetWeightedPrice));
}
if (health.gt(ZERO_I80F48())) {
if (liabWeightedPrice.lte(ZERO_I80F48())) {
throw new Error('LiabWeightedPrice must be greater than 0!');
}
takenSpot.iadd(health.div(liabWeightedPrice));
}
return takenSpot;
}
function spotAmountGivenForHealthZero(health, startingSpot, assetWeightedPrice, liabWeightedPrice) {
return spotAmountTakenForHealthZero(health.neg(), startingSpot.neg(), liabWeightedPrice, assetWeightedPrice);
}
export class HealthCache {

@@ -52,13 +78,13 @@ tokenInfos;

// find the TokenInfos for the market's base and quote tokens
const baseIndex = tokenInfos.findIndex((tokenInfo) => tokenInfo.tokenIndex === serum3.baseTokenIndex);
const baseInfo = tokenInfos[baseIndex];
const baseInfoIndex = tokenInfos.findIndex((tokenInfo) => tokenInfo.tokenIndex === serum3.baseTokenIndex);
const baseInfo = tokenInfos[baseInfoIndex];
if (!baseInfo) {
throw new Error(`BaseInfo not found for market with marketIndex ${serum3.marketIndex}!`);
}
const quoteIndex = tokenInfos.findIndex((tokenInfo) => tokenInfo.tokenIndex === serum3.quoteTokenIndex);
const quoteInfo = tokenInfos[quoteIndex];
const quoteInfoIndex = tokenInfos.findIndex((tokenInfo) => tokenInfo.tokenIndex === serum3.quoteTokenIndex);
const quoteInfo = tokenInfos[quoteInfoIndex];
if (!quoteInfo) {
throw new Error(`QuoteInfo not found for market with marketIndex ${serum3.marketIndex}!`);
}
return Serum3Info.fromOoModifyingTokenInfos(baseIndex, baseInfo, quoteIndex, quoteInfo, serum3.marketIndex, oo);
return Serum3Info.fromOoModifyingTokenInfos(baseInfoIndex, baseInfo, quoteInfoIndex, quoteInfo, serum3.marketIndex, oo);
});

@@ -79,3 +105,3 @@ // health contribution from perp accounts

.fill(null)
.map((ignored) => ZERO_I80F48());
.map((ignored) => new TokenMaxReserved(ZERO_I80F48()));
// For each serum market, compute what happened if reserved_base was converted to quote

@@ -85,4 +111,4 @@ // or reserved_quote was converted to base.

for (const info of this.serum3Infos) {
const quote = this.tokenInfos[info.quoteIndex];
const base = this.tokenInfos[info.baseIndex];
const quote = this.tokenInfos[info.quoteInfoIndex];
const base = this.tokenInfos[info.baseInfoIndex];
const reservedBase = info.reservedBase;

@@ -96,6 +122,6 @@ const reservedQuote = info.reservedQuote;

const allReservedAsQuote = reservedQuote.add(reservedBase.mul(baseAsset).div(quoteLiab));
const baseMaxReserved = tokenMaxReserved[info.baseIndex];
baseMaxReserved.iadd(allReservedAsBase);
const quoteMaxReserved = tokenMaxReserved[info.quoteIndex];
quoteMaxReserved.iadd(allReservedAsQuote);
const baseMaxReserved = tokenMaxReserved[info.baseInfoIndex];
baseMaxReserved.maxSerumReserved.iadd(allReservedAsBase);
const quoteMaxReserved = tokenMaxReserved[info.quoteInfoIndex];
quoteMaxReserved.maxSerumReserved.iadd(allReservedAsQuote);
serum3Reserved.push(new Serum3Reserved(allReservedAsBase, allReservedAsQuote));

@@ -108,136 +134,130 @@ }

}
health(healthType) {
const health = ZERO_I80F48();
for (const tokenInfo of this.tokenInfos) {
const contrib = tokenInfo.healthContribution(healthType);
// console.log(` - ti ${contrib}`);
health.iadd(contrib);
effectiveTokenBalances(healthType) {
return this.effectiveTokenBalancesInternal(healthType, false);
}
effectiveTokenBalancesInternal(healthType, ignoreNegativePerp) {
const tokenBalances = new Array(this.tokenInfos.length)
.fill(null)
.map((ignored) => new TokenBalance(ZERO_I80F48()));
for (const perpInfo of this.perpInfos) {
const settleTokenIndex = this.findTokenInfoIndex(perpInfo.settleTokenIndex);
const perpSettleToken = tokenBalances[settleTokenIndex];
const healthUnsettled = perpInfo.healthUnsettledPnl(healthType);
if (!ignoreNegativePerp || healthUnsettled.gt(ZERO_I80F48())) {
perpSettleToken.spotAndPerp.iadd(healthUnsettled);
}
}
const res = this.computeSerum3Reservations(healthType);
for (const [index, serum3Info] of this.serum3Infos.entries()) {
const contrib = serum3Info.healthContribution(healthType, this.tokenInfos, res.tokenMaxReserved, res.serum3Reserved[index]);
// console.log(` - si ${contrib}`);
health.iadd(contrib);
for (const index of this.tokenInfos.keys()) {
const tokenInfo = this.tokenInfos[index];
const tokenBalance = tokenBalances[index];
tokenBalance.spotAndPerp.iadd(tokenInfo.balanceSpot);
}
for (const perpInfo of this.perpInfos) {
const contrib = perpInfo.healthContribution(healthType);
// console.log(` - pi ${contrib}`);
health.iadd(contrib);
}
return health;
return tokenBalances;
}
// Note: only considers positive perp pnl contributions, see program code for more reasoning
perpSettleHealth() {
healthSum(healthType, tokenBalances) {
const health = ZERO_I80F48();
for (const tokenInfo of this.tokenInfos) {
const contrib = tokenInfo.healthContribution(HealthType.maint);
for (const index of this.tokenInfos.keys()) {
const tokenInfo = this.tokenInfos[index];
const tokenBalance = tokenBalances[index];
const contrib = tokenInfo.healthContribution(healthType, tokenBalance.spotAndPerp);
// console.log(` - ti ${contrib}`);
health.iadd(contrib);
}
const res = this.computeSerum3Reservations(HealthType.maint);
const res = this.computeSerum3Reservations(healthType);
for (const [index, serum3Info] of this.serum3Infos.entries()) {
const contrib = serum3Info.healthContribution(HealthType.maint, this.tokenInfos, res.tokenMaxReserved, res.serum3Reserved[index]);
const contrib = serum3Info.healthContribution(healthType, this.tokenInfos, tokenBalances, res.tokenMaxReserved, res.serum3Reserved[index]);
// console.log(` - si ${contrib}`);
health.iadd(contrib);
}
for (const perpInfo of this.perpInfos) {
const positiveContrib = perpInfo
.healthContribution(HealthType.maint)
.max(ZERO_I80F48());
// console.log(` - pi ${positiveContrib}`);
health.iadd(positiveContrib);
}
return health;
}
// An undefined HealthType will use an asset and liab weight of 1
assets(healthType) {
const assets = ZERO_I80F48();
for (const tokenInfo of this.tokenInfos) {
const contrib = tokenInfo.healthContribution(healthType);
if (contrib.isPos()) {
assets.iadd(contrib);
}
}
const res = this.computeSerum3Reservations(HealthType.maint);
for (const [index, serum3Info] of this.serum3Infos.entries()) {
const contrib = serum3Info.healthContribution(healthType, this.tokenInfos, res.tokenMaxReserved, res.serum3Reserved[index]);
if (contrib.isPos()) {
assets.iadd(contrib);
}
}
for (const perpInfo of this.perpInfos) {
const contrib = perpInfo.healthContribution(healthType);
if (contrib.isPos()) {
assets.iadd(contrib);
}
}
return assets;
health(healthType) {
const tokenBalances = this.effectiveTokenBalancesInternal(healthType, false);
return this.healthSum(healthType, tokenBalances);
}
// An undefined HealthType will use an asset and liab weight of 1
liabs(healthType) {
const liabs = ZERO_I80F48();
perpMaxSettle(settleTokenIndex) {
const healthType = HealthType.maint;
const tokenBalances = this.effectiveTokenBalancesInternal(healthType, true);
const perpSettleHealth = this.healthSum(healthType, tokenBalances);
const tokenInfoIndex = this.findTokenInfoIndex(settleTokenIndex);
const tokenInfo = this.tokenInfos[tokenInfoIndex];
return spotAmountTakenForHealthZero(perpSettleHealth, tokenBalances[tokenInfoIndex].spotAndPerp, tokenInfo.assetWeightedPrice(healthType), tokenInfo.liabWeightedPrice(healthType));
}
healthAssetsAndLiabsStableAssets(healthType) {
return this.healthAssetsAndLiabs(healthType, true);
}
healthAssetsAndLiabsStableLiabs(healthType) {
return this.healthAssetsAndLiabs(healthType, false);
}
healthAssetsAndLiabs(healthType, stableAssets) {
const totalAssets = ZERO_I80F48();
const totalLiabs = ZERO_I80F48();
for (const tokenInfo of this.tokenInfos) {
const contrib = tokenInfo.healthContribution(healthType);
if (contrib.isNeg()) {
liabs.isub(contrib);
const assetBalance = ZERO_I80F48();
const liabBalance = ZERO_I80F48();
if (tokenInfo.balanceSpot.isPos()) {
assetBalance.iadd(tokenInfo.balanceSpot);
}
}
const res = this.computeSerum3Reservations(HealthType.maint);
for (const [index, serum3Info] of this.serum3Infos.entries()) {
const contrib = serum3Info.healthContribution(healthType, this.tokenInfos, res.tokenMaxReserved, res.serum3Reserved[index]);
if (contrib.isNeg()) {
liabs.isub(contrib);
else {
liabBalance.isub(tokenInfo.balanceSpot);
}
}
for (const perpInfo of this.perpInfos) {
const contrib = perpInfo.healthContribution(healthType);
if (contrib.isNeg()) {
liabs.isub(contrib);
for (const perpInfo of this.perpInfos) {
if (perpInfo.settleTokenIndex != tokenInfo.tokenIndex) {
continue;
}
const healthUnsettled = perpInfo.healthUnsettledPnl(healthType);
if (healthUnsettled.isPos()) {
assetBalance.iadd(healthUnsettled);
}
else {
liabBalance.isub(healthUnsettled);
}
}
}
return liabs;
}
healthRatio(healthType) {
const assets = ZERO_I80F48();
const liabs = ZERO_I80F48();
for (const tokenInfo of this.tokenInfos) {
const contrib = tokenInfo.healthContribution(healthType);
// console.log(` - ti contrib ${contrib.toLocaleString()}`);
if (contrib.isPos()) {
assets.iadd(contrib);
if (stableAssets) {
const assetWeightedPrice = tokenInfo.assetWeightedPrice(healthType);
const assets = assetBalance.mul(assetWeightedPrice);
totalAssets.iadd(assets);
if (assetBalance.gte(liabBalance)) {
totalLiabs.iadd(liabBalance.mul(assetWeightedPrice));
}
else {
const liabWeightedPrice = tokenInfo.liabWeightedPrice(healthType);
totalLiabs.iadd(assets.add(liabBalance.sub(assetBalance).mul(liabWeightedPrice)));
}
}
else {
liabs.isub(contrib);
const liabWeightedPrice = tokenInfo.liabWeightedPrice(healthType);
const liabs = liabBalance.mul(liabWeightedPrice);
totalLiabs.iadd(liabs);
if (assetBalance.gte(liabBalance)) {
const assetWeightedPrice = tokenInfo.assetWeightedPrice(healthType);
totalAssets.iadd(liabs.add(assetBalance.sub(liabBalance).mul(assetWeightedPrice)));
}
else {
totalAssets.iadd(assetBalance.mul(liabWeightedPrice));
}
}
}
const res = this.computeSerum3Reservations(HealthType.maint);
const tokenBalances = this.effectiveTokenBalances(healthType);
const res = this.computeSerum3Reservations(healthType);
for (const [index, serum3Info] of this.serum3Infos.entries()) {
const contrib = serum3Info.healthContribution(healthType, this.tokenInfos, res.tokenMaxReserved, res.serum3Reserved[index]);
// console.log(` - si contrib ${contrib.toLocaleString()}`);
const contrib = serum3Info.healthContribution(healthType, this.tokenInfos, tokenBalances, res.tokenMaxReserved, res.serum3Reserved[index]);
if (contrib.isPos()) {
assets.iadd(contrib);
totalAssets.iadd(contrib);
}
else {
liabs.isub(contrib);
totalLiabs.iadd(contrib);
}
}
for (const perpInfo of this.perpInfos) {
const contrib = perpInfo.healthContribution(healthType);
// console.log(` - pi contrib ${contrib.toLocaleString()}`);
if (contrib.isPos()) {
assets.iadd(contrib);
}
else {
liabs.isub(contrib);
}
return { assets: totalAssets, liabs: totalLiabs };
}
healthRatio(healthType) {
const res = this.healthAssetsAndLiabsStableLiabs(healthType);
const hundred = I80F48.fromNumber(100);
// console.log(`assets ${res.assets}`);
// console.log(`liabs ${res.liabs}`);
if (res.liabs.gt(I80F48.fromNumber(0.001))) {
return hundred.mul(res.assets.sub(res.liabs)).div(res.liabs);
}
// console.log(
// ` - assets ${assets.toLocaleString()}, liabs ${liabs.toLocaleString()}`,
// );
if (liabs.gt(I80F48.fromNumber(0.001))) {
return HUNDRED_I80F48().mul(assets.sub(liabs).div(liabs));
}
else {
return MAX_I80F48();
}
return MAX_I80F48();
}

@@ -261,3 +281,3 @@ findTokenInfoIndex(tokenIndex) {

// TODO: this will no longer work as easily because of the health weight changes
adjustedCache.tokenInfos[changeIndex].balanceNative.iadd(change.nativeTokenAmount);
adjustedCache.tokenInfos[changeIndex].balanceSpot.iadd(change.nativeTokenAmount);
}

@@ -285,4 +305,4 @@ // HealthCache.logHealthCache('afterChange', adjustedCache);

// Apply it to the tokens
baseEntry.balanceNative.iadd(freeBaseChange);
quoteEntry.balanceNative.iadd(freeQuoteChange);
baseEntry.balanceSpot.iadd(freeBaseChange);
quoteEntry.balanceSpot.iadd(freeQuoteChange);
// Apply it to the serum3 info

@@ -300,3 +320,3 @@ const index = this.getOrCreateSerum3InfoIndex(baseBank, quoteBank, serum3Market);

// Reduce token balance for quote
adjustedCache.tokenInfos[quoteIndex].balanceNative.isub(bidNativeQuoteAmount);
adjustedCache.tokenInfos[quoteIndex].balanceSpot.isub(bidNativeQuoteAmount);
// Increase reserved in Serum3Info for quote

@@ -312,3 +332,3 @@ adjustedCache.adjustSerum3Reserved(baseBank, quoteBank, serum3Market, ZERO_I80F48(), ZERO_I80F48(), bidNativeQuoteAmount, ZERO_I80F48());

// Reduce token balance for base
adjustedCache.tokenInfos[baseIndex].balanceNative.isub(askNativeBaseAmount);
adjustedCache.tokenInfos[baseIndex].balanceSpot.isub(askNativeBaseAmount);
// Increase reserved in Serum3Info for base

@@ -348,16 +368,2 @@ adjustedCache.adjustSerum3Reserved(baseBank, quoteBank, serum3Market, askNativeBaseAmount, ZERO_I80F48(), ZERO_I80F48(), ZERO_I80F48());

}
logHealthCache(debug) {
if (debug)
console.log(debug);
for (const token of this.tokenInfos) {
console.log(` ${token.toString()}`);
}
const res = this.computeSerum3Reservations(HealthType.maint);
for (const [index, serum3Info] of this.serum3Infos.entries()) {
console.log(` ${serum3Info.toString(this.tokenInfos, res.tokenMaxReserved, res.serum3Reserved[index])}`);
}
console.log(` assets ${this.assets(HealthType.init)}, liabs ${this.liabs(HealthType.init)}, `);
console.log(` health(HealthType.init) ${this.health(HealthType.init)}`);
console.log(` healthRatio(HealthType.init) ${this.healthRatio(HealthType.init)}`);
}
static scanRightUntilLessThan(start, target, fun) {

@@ -518,4 +524,7 @@ const maxIterations = 50;

const res = healthCacheClone.computeSerum3Reservations(HealthType.init);
const sourceReserved = res.tokenMaxReserved[sourceIndex];
const targetReserved = res.tokenMaxReserved[targetIndex];
const sourceReserved = res.tokenMaxReserved[sourceIndex].maxSerumReserved;
const targetReserved = res.tokenMaxReserved[targetIndex].maxSerumReserved;
const tokenBalances = healthCacheClone.effectiveTokenBalances(HealthType.init);
const sourceBalance = tokenBalances[sourceIndex].spotAndPerp;
const targetBalance = tokenBalances[targetIndex].spotAndPerp;
// If the price is sufficiently good, then health will just increase from swapping:

@@ -543,4 +552,4 @@ // once we've swapped enough, swapping x reduces health by x * source_liab_weight and

// and set the new weights on the tokenInfos
adjustedCache.tokenInfos[sourceIndex].balanceNative.isub(amount);
adjustedCache.tokenInfos[targetIndex].balanceNative.iadd(amount.mul(price));
adjustedCache.tokenInfos[sourceIndex].balanceSpot.isub(amount);
adjustedCache.tokenInfos[targetIndex].balanceSpot.iadd(amount.mul(price));
// adjustedCache.logHealthCache('afterSwap', adjustedCache);

@@ -560,6 +569,6 @@ return adjustedCache;

// The largest amount that the maximum could be at
const rightmost = source.balanceNative
const rightmost = sourceBalance
.abs()
.add(sourceReserved)
.max(target.balanceNative.abs().add(targetReserved).div(price));
.max(targetBalance.abs().add(targetReserved).div(price));
const [amountForMaxValue, maxValue] = HealthCache.findMaximum(ZERO_I80F48(), rightmost, I80F48.fromNumber(0.1), fnValueAfterSwap);

@@ -619,6 +628,6 @@ if (maxValue.lte(minFnValue)) {

if (side == Serum3Side.ask) {
const quoteBorrows = quote.balanceNative.lt(ZERO_I80F48())
? quote.balanceNative.abs().mul(quote.prices.liab(HealthType.init))
const quoteBorrows = quote.balanceSpot.lt(ZERO_I80F48())
? quote.balanceSpot.abs().mul(quote.prices.liab(HealthType.init))
: ZERO_I80F48();
const max = base.balanceNative
const max = base.balanceSpot
.mul(base.prices.asset(HealthType.init))

@@ -635,6 +644,6 @@ .max(quoteBorrows);

else {
const baseBorrows = base.balanceNative.lt(ZERO_I80F48())
? base.balanceNative.abs().mul(base.prices.liab(HealthType.init))
const baseBorrows = base.balanceSpot.lt(ZERO_I80F48())
? base.balanceSpot.abs().mul(base.prices.liab(HealthType.init))
: ZERO_I80F48();
const max = quote.balanceNative
const max = quote.balanceSpot
.mul(quote.prices.asset(HealthType.init))

@@ -663,4 +672,4 @@ .max(baseBorrows);

side === Serum3Side.ask
? adjustedCache.tokenInfos[baseIndex].balanceNative.isub(amount.div(base.prices.oracle))
: adjustedCache.tokenInfos[quoteIndex].balanceNative.isub(amount.div(quote.prices.oracle));
? adjustedCache.tokenInfos[baseIndex].balanceSpot.isub(amount.div(base.prices.oracle))
: adjustedCache.tokenInfos[quoteIndex].balanceSpot.isub(amount.div(quote.prices.oracle));
adjustedCache.adjustSerum3Reserved(baseBank, quoteBank, serum3Market, side === Serum3Side.ask

@@ -689,5 +698,6 @@ ? amount.div(base.prices.oracle)

const perpInfo = healthCacheClone.perpInfos[perpInfoIndex];
const prices = perpInfo.prices;
const prices = perpInfo.basePrices;
const baseLotSize = I80F48.fromI64(perpMarket.baseLotSize);
// If the price is sufficiently good then health will just increase from trading
const settleInfoIndex = this.findTokenInfoIndex(perpInfo.settleTokenIndex);
const settleInfo = this.tokenInfos[settleInfoIndex];
const finalHealthSlope = direction == 1

@@ -697,6 +707,10 @@ ? perpInfo.initBaseAssetWeight

.sub(price)
: price.sub(perpInfo.initBaseLiabWeight.mul(prices.liab(HealthType.init)));
: perpInfo.initBaseLiabWeight
.neg()
.mul(prices.liab(HealthType.init))
.add(price);
if (finalHealthSlope.gte(ZERO_I80F48())) {
return MAX_I80F48();
}
finalHealthSlope.imul(settleInfo.liabWeightedPrice(HealthType.init));
function cacheAfterTrade(baseLots) {

@@ -749,2 +763,6 @@ const adjustedCache = cloneDeep(healthCacheClone);

const startCache = cacheAfterTrade(new BN(case1Start.toNumber()));
startCache.perpInfos[perpInfoIndex].initOverallAssetWeight = ONE_I80F48();
const settleInfo = startCache.tokenInfos[settleInfoIndex];
settleInfo.initAssetWeight = settleInfo.initLiabWeight;
settleInfo.initScaledAssetWeight = settleInfo.initScaledLiabWeight;
const startHealth = startCache.health(HealthType.init);

@@ -754,12 +772,11 @@ if (startHealth.lte(ZERO_I80F48())) {

}
// The perp market's contribution to the health above may be capped. But we need to trade
// enough to fully reduce any positive-pnl buffer. Thus get the uncapped health:
const perpInfo = startCache.perpInfos[perpInfoIndex];
const startHealthUncapped = startHealth
.sub(perpInfo.healthContribution(HealthType.init))
.add(perpInfo.unweightedHealthContribution(HealthType.init));
const zeroHealthAmount = case1Start
.sub(startHealthUncapped.div(finalHealthSlope).div(baseLotSize))
.sub(startHealth.div(finalHealthSlope.mul(baseLotSize).mul(I80F48.fromNumber(0.99))))
.add(ONE_I80F48());
const zeroHealthRatio = healthRatioAfterTradeTrunc(zeroHealthAmount);
// console.log(`case1Start ${case1Start}`);
// console.log(`case1StartRatio ${case1StartRatio}`);
// console.log(`zeroHealthAmount ${zeroHealthAmount}`);
// console.log(`zeroHealthRatio ${zeroHealthRatio}`);
// console.log(`minRatio ${minRatio}`);
baseLots = HealthCache.binaryApproximationSearch(case1Start, case1StartRatio, zeroHealthAmount, zeroHealthRatio.max(minRatio), // workaround, originally minRatio

@@ -780,3 +797,3 @@ ONE_I80F48(), healthRatioAfterTradeTrunc);

const pi = hcClone.perpInfos[hcClone.findPerpInfoIndex(perpPosition.marketIndex)];
pi.prices.oracle = newPrice;
pi.basePrices.oracle = newPrice;
return hcClone.health(HealthType.maint);

@@ -829,4 +846,4 @@ }

prices;
balanceNative;
constructor(tokenIndex, maintAssetWeight, initAssetWeight, initScaledAssetWeight, maintLiabWeight, initLiabWeight, initScaledLiabWeight, prices, balanceNative) {
balanceSpot;
constructor(tokenIndex, maintAssetWeight, initAssetWeight, initScaledAssetWeight, maintLiabWeight, initLiabWeight, initScaledLiabWeight, prices, balanceSpot) {
this.tokenIndex = tokenIndex;

@@ -840,6 +857,6 @@ this.maintAssetWeight = maintAssetWeight;

this.prices = prices;
this.balanceNative = balanceNative;
this.balanceSpot = balanceSpot;
}
static fromDto(dto) {
return new TokenInfo(dto.tokenIndex, I80F48.from(dto.maintAssetWeight), I80F48.from(dto.initAssetWeight), I80F48.from(dto.initScaledAssetWeight), I80F48.from(dto.maintLiabWeight), I80F48.from(dto.initLiabWeight), I80F48.from(dto.initScaledLiabWeight), new Prices(I80F48.from(dto.prices.oracle), I80F48.from(dto.prices.stable)), I80F48.from(dto.balanceNative));
return new TokenInfo(dto.tokenIndex, I80F48.from(dto.maintAssetWeight), I80F48.from(dto.initAssetWeight), I80F48.from(dto.initScaledAssetWeight), I80F48.from(dto.maintLiabWeight), I80F48.from(dto.initLiabWeight), I80F48.from(dto.initScaledLiabWeight), new Prices(I80F48.from(dto.prices.oracle), I80F48.from(dto.prices.stable)), I80F48.from(dto.balanceSpot));
}

@@ -860,5 +877,10 @@ static fromBank(bank, nativeBalance) {

}
// healthType == HealthType.maint
return this.maintAssetWeight;
if (healthType == HealthType.maint) {
return this.maintAssetWeight;
}
return I80F48.fromNumber(1);
}
assetWeightedPrice(healthType) {
return this.assetWeight(healthType).mul(this.prices.asset(healthType));
}
liabWeight(healthType) {

@@ -871,24 +893,35 @@ if (healthType == HealthType.init) {

}
// healthType == HealthType.maint
return this.maintLiabWeight;
if (healthType == HealthType.maint) {
return this.maintLiabWeight;
}
return I80F48.fromNumber(1);
}
healthContribution(healthType) {
let weight, price;
liabWeightedPrice(healthType) {
return this.liabWeight(healthType).mul(this.prices.liab(healthType));
}
healthContribution(healthType, balance) {
if (healthType === undefined) {
return this.balanceNative.mul(this.prices.oracle);
return balance.mul(this.prices.oracle);
}
if (this.balanceNative.isNeg()) {
weight = this.liabWeight(healthType);
price = this.prices.liab(healthType);
}
else {
weight = this.assetWeight(healthType);
price = this.prices.asset(healthType);
}
return this.balanceNative.mul(weight).mul(price);
// console.log(`balance ${balance}`);
return balance.isNeg()
? balance.mul(this.liabWeightedPrice(healthType))
: balance.mul(this.assetWeightedPrice(healthType));
}
toString() {
return ` tokenIndex: ${this.tokenIndex}, balanceNative: ${this.balanceNative}, initHealth ${this.healthContribution(HealthType.init)}`;
toString(balance) {
return ` tokenIndex: ${this.tokenIndex}, balanceNative: ${this.balanceSpot}, initHealth ${this.healthContribution(HealthType.init, balance)}`;
}
}
class TokenBalance {
spotAndPerp;
constructor(spotAndPerp) {
this.spotAndPerp = spotAndPerp;
}
}
class TokenMaxReserved {
maxSerumReserved;
constructor(maxSerumReserved) {
this.maxSerumReserved = maxSerumReserved;
}
}
export class Serum3Reserved {

@@ -905,14 +938,14 @@ allReservedAsBase;

reservedQuote;
baseIndex;
quoteIndex;
baseInfoIndex;
quoteInfoIndex;
marketIndex;
constructor(reservedBase, reservedQuote, baseIndex, quoteIndex, marketIndex) {
constructor(reservedBase, reservedQuote, baseInfoIndex, quoteInfoIndex, marketIndex) {
this.reservedBase = reservedBase;
this.reservedQuote = reservedQuote;
this.baseIndex = baseIndex;
this.quoteIndex = quoteIndex;
this.baseInfoIndex = baseInfoIndex;
this.quoteInfoIndex = quoteInfoIndex;
this.marketIndex = marketIndex;
}
static fromDto(dto) {
return new Serum3Info(I80F48.from(dto.reservedBase), I80F48.from(dto.reservedQuote), dto.baseIndex, dto.quoteIndex, dto.marketIndex);
return new Serum3Info(I80F48.from(dto.reservedBase), I80F48.from(dto.reservedQuote), dto.baseInfoIndex, dto.quoteInfoIndex, dto.marketIndex);
}

@@ -922,17 +955,15 @@ static emptyFromSerum3Market(serum3Market, baseEntryIndex, quoteEntryIndex) {

}
static fromOoModifyingTokenInfos(baseIndex, baseInfo, quoteIndex, quoteInfo, marketIndex, oo) {
static fromOoModifyingTokenInfos(baseInfoIndex, baseInfo, quoteInfoIndex, quoteInfo, marketIndex, oo) {
// add the amounts that are freely settleable immediately to token balances
const baseFree = I80F48.fromI64(oo.baseTokenFree);
// NOTE: referrerRebatesAccrued is not declared on oo class, but the layout
// is aware of it
const quoteFree = I80F48.fromI64(oo.quoteTokenFree.add(oo.referrerRebatesAccrued));
baseInfo.balanceNative.iadd(baseFree);
quoteInfo.balanceNative.iadd(quoteFree);
const quoteFree = I80F48.fromI64(oo.quoteTokenFree);
baseInfo.balanceSpot.iadd(baseFree);
quoteInfo.balanceSpot.iadd(quoteFree);
// track the reserved amounts
const reservedBase = I80F48.fromI64(oo.baseTokenTotal.sub(oo.baseTokenFree));
const reservedQuote = I80F48.fromI64(oo.quoteTokenTotal.sub(oo.quoteTokenFree));
return new Serum3Info(reservedBase, reservedQuote, baseIndex, quoteIndex, marketIndex);
return new Serum3Info(reservedBase, reservedQuote, baseInfoIndex, quoteInfoIndex, marketIndex);
}
// An undefined HealthType will use an asset and liab weight of 1
healthContribution(healthType, tokenInfos, tokenMaxReserved, marketReserved) {
healthContribution(healthType, tokenInfos, tokenBalances, tokenMaxReserved, marketReserved) {
if (marketReserved.allReservedAsBase.isZero() ||

@@ -942,12 +973,12 @@ marketReserved.allReservedAsQuote.isZero()) {

}
const baseInfo = tokenInfos[this.baseIndex];
const quoteInfo = tokenInfos[this.quoteIndex];
const baseMaxReserved = tokenMaxReserved[this.baseIndex];
const quoteMaxReserved = tokenMaxReserved[this.quoteIndex];
const baseInfo = tokenInfos[this.baseInfoIndex];
const quoteInfo = tokenInfos[this.quoteInfoIndex];
const baseMaxReserved = tokenMaxReserved[this.baseInfoIndex];
const quoteMaxReserved = tokenMaxReserved[this.quoteInfoIndex];
// How much the health would increase if the reserved balance were applied to the passed
// token info?
const computeHealthEffect = function (tokenInfo, tokenMaxReserved, marketReserved) {
const computeHealthEffect = function (tokenInfo, balance, maxReserved, marketReserved) {
// This balance includes all possible reserved funds from markets that relate to the
// token, including this market itself: `tokenMaxReserved` is already included in `maxBalance`.
const maxBalance = tokenInfo.balanceNative.add(tokenMaxReserved);
const maxBalance = balance.spotAndPerp.add(maxReserved.maxSerumReserved);
// Assuming `reserved` was added to `max_balance` last (because that gives the smallest

@@ -982,4 +1013,4 @@ // health effects): how much did health change because of it?

};
const healthBase = computeHealthEffect(baseInfo, baseMaxReserved, marketReserved.allReservedAsBase);
const healthQuote = computeHealthEffect(quoteInfo, quoteMaxReserved, marketReserved.allReservedAsQuote);
const healthBase = computeHealthEffect(baseInfo, tokenBalances[this.baseInfoIndex], tokenMaxReserved[this.baseInfoIndex], marketReserved.allReservedAsBase);
const healthQuote = computeHealthEffect(quoteInfo, tokenBalances[this.quoteInfoIndex], tokenMaxReserved[this.quoteInfoIndex], marketReserved.allReservedAsQuote);
// console.log(` - healthBase ${healthBase.toLocaleString()}`);

@@ -989,4 +1020,4 @@ // console.log(` - healthQuote ${healthQuote.toLocaleString()}`);

}
toString(tokenInfos, tokenMaxReserved, marketReserved) {
return ` marketIndex: ${this.marketIndex}, baseIndex: ${this.baseIndex}, quoteIndex: ${this.quoteIndex}, reservedBase: ${this.reservedBase}, reservedQuote: ${this.reservedQuote}, initHealth ${this.healthContribution(HealthType.init, tokenInfos, tokenMaxReserved, marketReserved)}`;
toString(tokenInfos, tokenBalances, tokenMaxReserved, marketReserved) {
return ` marketIndex: ${this.marketIndex}, baseInfoIndex: ${this.baseInfoIndex}, quoteInfoIndex: ${this.quoteInfoIndex}, reservedBase: ${this.reservedBase}, reservedQuote: ${this.reservedQuote}, initHealth ${this.healthContribution(HealthType.init, tokenInfos, tokenBalances, tokenMaxReserved, marketReserved)}`;
}

@@ -996,2 +1027,3 @@ }

perpMarketIndex;
settleTokenIndex;
maintBaseAssetWeight;

@@ -1008,6 +1040,7 @@ initBaseAssetWeight;

quote;
prices;
basePrices;
hasOpenOrders;
constructor(perpMarketIndex, maintBaseAssetWeight, initBaseAssetWeight, maintBaseLiabWeight, initBaseLiabWeight, maintOverallAssetWeight, initOverallAssetWeight, baseLotSize, baseLots, bidsBaseLots, asksBaseLots, quote, prices, hasOpenOrders) {
constructor(perpMarketIndex, settleTokenIndex, maintBaseAssetWeight, initBaseAssetWeight, maintBaseLiabWeight, initBaseLiabWeight, maintOverallAssetWeight, initOverallAssetWeight, baseLotSize, baseLots, bidsBaseLots, asksBaseLots, quote, basePrices, hasOpenOrders) {
this.perpMarketIndex = perpMarketIndex;
this.settleTokenIndex = settleTokenIndex;
this.maintBaseAssetWeight = maintBaseAssetWeight;

@@ -1024,7 +1057,7 @@ this.initBaseAssetWeight = initBaseAssetWeight;

this.quote = quote;
this.prices = prices;
this.basePrices = basePrices;
this.hasOpenOrders = hasOpenOrders;
}
static fromDto(dto) {
return new PerpInfo(dto.perpMarketIndex, I80F48.from(dto.maintBaseAssetWeight), I80F48.from(dto.initBaseAssetWeight), I80F48.from(dto.maintBaseLiabWeight), I80F48.from(dto.initBaseLiabWeight), I80F48.from(dto.maintOverallAssetWeight), I80F48.from(dto.initOverallAssetWeight), dto.baseLotSize, dto.baseLots, dto.bidsBaseLots, dto.asksBaseLots, I80F48.from(dto.quote), new Prices(I80F48.from(dto.prices.oracle), I80F48.from(dto.prices.stable)), dto.hasOpenOrders);
return new PerpInfo(dto.perpMarketIndex, dto.settleTokenIndex, I80F48.from(dto.maintBaseAssetWeight), I80F48.from(dto.initBaseAssetWeight), I80F48.from(dto.maintBaseLiabWeight), I80F48.from(dto.initBaseLiabWeight), I80F48.from(dto.maintOverallAssetWeight), I80F48.from(dto.initOverallAssetWeight), dto.baseLotSize, dto.baseLots, dto.bidsBaseLots, dto.asksBaseLots, I80F48.from(dto.quote), new Prices(I80F48.from(dto.prices.oracle), I80F48.from(dto.prices.stable)), dto.hasOpenOrders);
}

@@ -1038,15 +1071,42 @@ static fromPerpPosition(perpMarket, perpPosition) {

.add(takerQuote);
return new PerpInfo(perpMarket.perpMarketIndex, perpMarket.maintBaseAssetWeight, perpMarket.initBaseAssetWeight, perpMarket.maintBaseLiabWeight, perpMarket.initBaseLiabWeight, perpMarket.maintOverallAssetWeight, perpMarket.initOverallAssetWeight, perpMarket.baseLotSize, baseLots, perpPosition.bidsBaseLots, perpPosition.asksBaseLots, quoteCurrent, new Prices(perpMarket.price, I80F48.fromNumber(perpMarket.stablePriceModel.stablePrice)), perpPosition.hasOpenOrders());
return new PerpInfo(perpMarket.perpMarketIndex, perpMarket.settleTokenIndex, perpMarket.maintBaseAssetWeight, perpMarket.initBaseAssetWeight, perpMarket.maintBaseLiabWeight, perpMarket.initBaseLiabWeight, perpMarket.maintOverallAssetWeight, perpMarket.initOverallAssetWeight, perpMarket.baseLotSize, baseLots, perpPosition.bidsBaseLots, perpPosition.asksBaseLots, quoteCurrent, new Prices(perpMarket.price, I80F48.fromNumber(perpMarket.stablePriceModel.stablePrice)), perpPosition.hasOpenOrders());
}
healthContribution(healthType) {
const contrib = this.unweightedHealthContribution(healthType);
if (contrib.gt(ZERO_I80F48())) {
const assetWeight = healthType == HealthType.init || healthType == HealthType.liquidationEnd
healthContribution(healthType, settleToken) {
const contrib = this.unweightedHealthUnsettledPnl(healthType);
return this.weighHealthContributionSettle(this.weighHealthContributionOverall(contrib, healthType), healthType, settleToken);
}
healthUnsettledPnl(healthType) {
const contrib = this.unweightedHealthUnsettledPnl(healthType);
return this.weighHealthContributionOverall(contrib, healthType);
}
weighHealthContributionSettle(unweighted, healthType, settleToken) {
if (this.settleTokenIndex !== settleToken.tokenIndex) {
throw new Error('Settle token index should match!');
}
if (unweighted.gt(ZERO_I80F48())) {
return (healthType == HealthType.init
? settleToken.initScaledAssetWeight
: healthType == HealthType.liquidationEnd
? settleToken.initAssetWeight
: settleToken.maintLiabWeight)
.mul(unweighted)
.mul(settleToken.prices.asset(healthType));
}
return (healthType == HealthType.init
? settleToken.initScaledLiabWeight
: healthType == HealthType.liquidationEnd
? settleToken.initLiabWeight
: settleToken.maintLiabWeight)
.mul(unweighted)
.mul(settleToken.prices.liab(healthType));
}
weighHealthContributionOverall(unweighted, healthType) {
if (unweighted.gt(ZERO_I80F48())) {
return (healthType == HealthType.init || healthType == HealthType.liquidationEnd
? this.initOverallAssetWeight
: this.maintOverallAssetWeight;
return assetWeight.mul(contrib);
: this.maintOverallAssetWeight).mul(unweighted);
}
return contrib;
return unweighted;
}
unweightedHealthContribution(healthType) {
unweightedHealthUnsettledPnl(healthType) {
function orderExecutionCase(pi, ordersBaseLots, orderPrice) {

@@ -1074,6 +1134,6 @@ const netBaseNative = I80F48.fromU64(pi.baseLots.add(ordersBaseLots).mul(pi.baseLotSize));

if (netBaseNative.isNeg()) {
basePrice = pi.prices.liab(healthType);
basePrice = pi.basePrices.liab(healthType);
}
else {
basePrice = pi.prices.asset(healthType);
basePrice = pi.basePrices.asset(healthType);
}

@@ -1088,4 +1148,4 @@ // Total value of the order-execution adjusted base position

// What is worse: Executing all bids at oracle_price.liab, or executing all asks at oracle_price.asset?
const bidsCase = orderExecutionCase(this, this.bidsBaseLots, this.prices.liab(healthType));
const asksCase = orderExecutionCase(this, this.asksBaseLots.neg(), this.prices.asset(healthType));
const bidsCase = orderExecutionCase(this, this.bidsBaseLots, this.basePrices.liab(healthType));
const asksCase = orderExecutionCase(this, this.asksBaseLots.neg(), this.basePrices.asset(healthType));
const worstCase = bidsCase.min(asksCase);

@@ -1095,6 +1155,6 @@ return this.quote.add(worstCase);

static emptyFromPerpMarket(perpMarket) {
return new PerpInfo(perpMarket.perpMarketIndex, perpMarket.maintBaseAssetWeight, perpMarket.initBaseAssetWeight, perpMarket.maintBaseLiabWeight, perpMarket.initBaseLiabWeight, perpMarket.maintOverallAssetWeight, perpMarket.initOverallAssetWeight, perpMarket.baseLotSize, new BN(0), new BN(0), new BN(0), ZERO_I80F48(), new Prices(perpMarket.price, I80F48.fromNumber(perpMarket.stablePriceModel.stablePrice)), false);
return new PerpInfo(perpMarket.perpMarketIndex, perpMarket.settleTokenIndex, perpMarket.maintBaseAssetWeight, perpMarket.initBaseAssetWeight, perpMarket.maintBaseLiabWeight, perpMarket.initBaseLiabWeight, perpMarket.maintOverallAssetWeight, perpMarket.initOverallAssetWeight, perpMarket.baseLotSize, new BN(0), new BN(0), new BN(0), ZERO_I80F48(), new Prices(perpMarket.price, I80F48.fromNumber(perpMarket.stablePriceModel.stablePrice)), false);
}
toString() {
return ` perpMarketIndex: ${this.perpMarketIndex}, base: ${this.baseLots}, quote: ${this.quote}, oraclePrice: ${this.prices.oracle}, uncapped health contribution ${this.unweightedHealthContribution(HealthType.init)}`;
return ` perpMarketIndex: ${this.perpMarketIndex}, base: ${this.baseLots}, quote: ${this.quote}, oraclePrice: ${this.basePrices.oracle}, uncapped health contribution ${this.unweightedHealthUnsettledPnl(HealthType.init)}`;
}

@@ -1116,3 +1176,3 @@ }

prices;
balanceNative;
balanceSpot;
constructor(tokenIndex, maintAssetWeight, initAssetWeight, initScaledAssetWeight, maintLiabWeight, initLiabWeight, initScaledLiabWeight, prices, balanceNative) {

@@ -1127,3 +1187,3 @@ this.tokenIndex = tokenIndex;

this.prices = prices;
this.balanceNative = balanceNative;
this.balanceSpot = balanceNative;
}

@@ -1134,10 +1194,10 @@ }

reservedQuote;
baseIndex;
quoteIndex;
baseInfoIndex;
quoteInfoIndex;
marketIndex;
constructor(reservedBase, reservedQuote, baseIndex, quoteIndex) {
constructor(reservedBase, reservedQuote, baseInfoIndex, quoteInfoIndex) {
this.reservedBase = reservedBase;
this.reservedQuote = reservedQuote;
this.baseIndex = baseIndex;
this.quoteIndex = quoteIndex;
this.baseInfoIndex = baseInfoIndex;
this.quoteInfoIndex = quoteInfoIndex;
}

@@ -1147,2 +1207,3 @@ }

perpMarketIndex;
settleTokenIndex;
maintBaseAssetWeight;

@@ -1149,0 +1210,0 @@ initBaseAssetWeight;

@@ -9,3 +9,3 @@ import { BN } from '@coral-xyz/anchor';

import { PerpOrderSide } from './perp';
function mockBankAndOracle(tokenIndex, maintWeight, initWeight, price, stablePrice) {
function mockBankAndOracle(tokenIndex, maintWeight, initWeight, price, stablePrice, deposits = 0, borrows = 0, borrowWeightScaleStartQuote = Number.MAX_SAFE_INTEGER, depositWeightScaleStartQuote = Number.MAX_SAFE_INTEGER) {
return {

@@ -19,9 +19,28 @@ tokenIndex,

stablePriceModel: { stablePrice: stablePrice },
scaledInitAssetWeight: () => I80F48.fromNumber(1 - initWeight),
scaledInitLiabWeight: () => I80F48.fromNumber(1 + initWeight),
scaledInitAssetWeight: (price) => {
const depositsQuote = I80F48.fromNumber(deposits).mul(price);
if (depositWeightScaleStartQuote >= Number.MAX_SAFE_INTEGER ||
depositsQuote.lte(I80F48.fromNumber(depositWeightScaleStartQuote))) {
return I80F48.fromNumber(1 - initWeight);
}
return I80F48.fromNumber(1 - initWeight).mul(I80F48.fromNumber(depositWeightScaleStartQuote).div(depositsQuote));
},
scaledInitLiabWeight: (price) => {
const borrowsQuote = I80F48.fromNumber(borrows).mul(price);
if (borrowWeightScaleStartQuote >= Number.MAX_SAFE_INTEGER ||
borrowsQuote.lte(I80F48.fromNumber(borrowWeightScaleStartQuote))) {
return I80F48.fromNumber(1 + initWeight);
}
return I80F48.fromNumber(1 + initWeight).mul(borrowsQuote.div(I80F48.fromNumber(borrowWeightScaleStartQuote)));
},
nativeDeposits: () => I80F48.fromNumber(deposits),
nativeBorrows: () => I80F48.fromNumber(borrows),
borrowWeightScaleStartQuote: borrowWeightScaleStartQuote,
depositWeightScaleStartQuote: depositWeightScaleStartQuote,
};
}
function mockPerpMarket(perpMarketIndex, maintBaseWeight, initBaseWeight, baseLotSize, price) {
function mockPerpMarket(perpMarketIndex, settleTokenIndex, maintBaseWeight, initBaseWeight, baseLotSize, price) {
return {
perpMarketIndex,
settleTokenIndex: settleTokenIndex,
maintBaseAssetWeight: I80F48.fromNumber(1 - maintBaseWeight),

@@ -43,3 +62,3 @@ initBaseAssetWeight: I80F48.fromNumber(1 - initBaseWeight),

it('test_health0', () => {
const sourceBank = mockBankAndOracle(1, 0.1, 0.2, 1, 1);
const sourceBank = mockBankAndOracle(0, 0.1, 0.2, 1, 1);
const targetBank = mockBankAndOracle(4, 0.3, 0.5, 5, 5);

@@ -55,13 +74,15 @@ const ti1 = TokenInfo.fromBank(sourceBank, I80F48.fromNumber(100));

});
const pM = mockPerpMarket(9, 0.1, 0.2, 10, targetBank.price.toNumber());
const pM = mockPerpMarket(9, 0, 0.1, 0.2, 10, targetBank.price.toNumber());
const pp = new PerpPosition(pM.perpMarketIndex, 0, new BN(0), new BN(3), I80F48.fromNumber(-310), new BN(0), ZERO_I80F48(), ZERO_I80F48(), new BN(7), new BN(11), new BN(1), new BN(2), 0, 0, new BN(0), new BN(0), new BN(0), 0, ZERO_I80F48(), ZERO_I80F48(), new BN(0), ZERO_I80F48());
const pi1 = PerpInfo.fromPerpPosition(pM, pp);
const hc = new HealthCache([ti1, ti2], [si1], [pi1]);
// for bank1/oracle1, including open orders (scenario: bids execute)
const health1 = (100.0 + 1.0 + 2.0 + (20.0 + 15.0 * 5.0)) * 0.8;
// for bank1/oracle1
// including open orders (scenario: bids execute)
const serum1 = 1.0 + (20.0 + 15.0 * 5.0);
// and perp (scenario: bids execute)
const perp1 = (3.0 + 7.0 + 1.0) * 10.0 * 5.0 * 0.8 +
(-310.0 + 2.0 * 100.0 - 7.0 * 10.0 * 5.0);
const health1 = (100.0 + serum1 + perp1) * 0.8;
// for bank2/oracle2
const health2 = (-10.0 + 3.0) * 5.0 * 1.5;
// for perp (scenario: bids execute)
const health3 = (3.0 + 7.0 + 1.0) * 10.0 * 5.0 * 0.8 +
(-310.0 + 2.0 * 100.0 - 7.0 * 10.0 * 5.0);
const health = hc.health(HealthType.init).toNumber();

@@ -71,9 +92,9 @@ console.log(` - health ${health

.padStart(10)}, case "test that includes all the side values (like referrer_rebates_accrued)"`);
expect(health - (health1 + health2 + health3)).lessThan(0.0000001);
expect(health - (health1 + health2)).lessThan(0.0000001);
});
it('test_health1', (done) => {
function testFixture(fixture) {
const bank1 = mockBankAndOracle(1, 0.1, 0.2, 1, 1);
const bank2 = mockBankAndOracle(4, 0.3, 0.5, 5, 5);
const bank3 = mockBankAndOracle(5, 0.3, 0.5, 10, 10);
const bank1 = mockBankAndOracle(0, 0.1, 0.2, 1, 1, fixture.bs1[0], fixture.bs1[0], fixture.bs1[1], fixture.bs1[1]);
const bank2 = mockBankAndOracle(4, 0.3, 0.5, 5, 5, fixture.bs2[0], fixture.bs2[0], fixture.bs2[1], fixture.bs2[1]);
const bank3 = mockBankAndOracle(5, 0.3, 0.5, 10, 10, fixture.bs3[0], fixture.bs3[0], fixture.bs3[1], fixture.bs3[1]);
const ti1 = TokenInfo.fromBank(bank1, I80F48.fromNumber(fixture.token1));

@@ -96,3 +117,3 @@ const ti2 = TokenInfo.fromBank(bank2, I80F48.fromNumber(fixture.token2));

});
const pM = mockPerpMarket(9, 0.1, 0.2, 10, bank2.price.toNumber());
const pM = mockPerpMarket(9, 0, 0.1, 0.2, 10, bank2.price.toNumber());
const pp = new PerpPosition(pM.perpMarketIndex, 0, new BN(0), new BN(fixture.perp1[0]), I80F48.fromNumber(fixture.perp1[1]), new BN(0), ZERO_I80F48(), ZERO_I80F48(), new BN(fixture.perp1[2]), new BN(fixture.perp1[3]), new BN(0), new BN(0), 0, 0, new BN(0), new BN(0), new BN(0), 0, ZERO_I80F48(), ZERO_I80F48(), new BN(0), ZERO_I80F48());

@@ -114,2 +135,5 @@ const pi1 = PerpInfo.fromPerpPosition(pM, pp);

token3: 0,
bs1: [0, Number.MAX_SAFE_INTEGER],
bs2: [0, Number.MAX_SAFE_INTEGER],
bs3: [0, Number.MAX_SAFE_INTEGER],
oo12: [20, 15],

@@ -119,9 +143,12 @@ oo13: [0, 0],

expectedHealth:
// for token1, including open orders (scenario: bids execute)
(100.0 + (20.0 + 15.0 * basePrice)) * 0.8 -
// for token1
0.8 *
(100.0 +
// including open orders (scenario: bids execute)
(20.0 + 15.0 * basePrice) +
// including perp (scenario: bids execute)
(3.0 + 7.0) * baseLotsToQuote * 0.8 +
(-131.0 - 7.0 * baseLotsToQuote)) -
// for token2
10.0 * basePrice * 1.5 +
// for perp (scenario: bids execute)
(3.0 + 7.0) * baseLotsToQuote * 0.8 +
(-131.0 - 7.0 * baseLotsToQuote),
10.0 * basePrice * 1.5,
});

@@ -133,2 +160,5 @@ testFixture({

token3: 0,
bs1: [0, Number.MAX_SAFE_INTEGER],
bs2: [0, Number.MAX_SAFE_INTEGER],
bs3: [0, Number.MAX_SAFE_INTEGER],
oo12: [20, 15],

@@ -139,8 +169,9 @@ oo13: [0, 0],

// for token1
-100.0 * 1.2 +
1.2 *
(-100.0 +
// for perp (scenario: asks execute)
(-10.0 - 11.0) * baseLotsToQuote * 1.2 +
(-131.0 + 11.0 * baseLotsToQuote)) +
// for token2, including open orders (scenario: asks execute)
(10.0 * basePrice + (20.0 + 15.0 * basePrice)) * 0.5 +
// for perp (scenario: asks execute)
(-10.0 - 11.0) * baseLotsToQuote * 1.2 +
(-131.0 + 11.0 * baseLotsToQuote),
(10.0 * basePrice + (20.0 + 15.0 * basePrice)) * 0.5,
});

@@ -152,6 +183,9 @@ testFixture({

token3: 0,
bs1: [0, Number.MAX_SAFE_INTEGER],
bs2: [0, Number.MAX_SAFE_INTEGER],
bs3: [0, Number.MAX_SAFE_INTEGER],
oo12: [0, 0],
oo13: [0, 0],
perp1: [-1, 100, 0, 0],
expectedHealth: 0.95 * (100.0 - 1.2 * 1.0 * baseLotsToQuote),
expectedHealth: 0.8 * 0.95 * (100.0 - 1.2 * 1.0 * baseLotsToQuote),
});

@@ -163,6 +197,9 @@ testFixture({

token3: 0,
bs1: [0, Number.MAX_SAFE_INTEGER],
bs2: [0, Number.MAX_SAFE_INTEGER],
bs3: [0, Number.MAX_SAFE_INTEGER],
oo12: [0, 0],
oo13: [0, 0],
perp1: [1, -100, 0, 0],
expectedHealth: -100.0 + 0.8 * 1.0 * baseLotsToQuote,
expectedHealth: 1.2 * (-100.0 + 0.8 * 1.0 * baseLotsToQuote),
});

@@ -174,6 +211,9 @@ testFixture({

token3: 0,
bs1: [0, Number.MAX_SAFE_INTEGER],
bs2: [0, Number.MAX_SAFE_INTEGER],
bs3: [0, Number.MAX_SAFE_INTEGER],
oo12: [0, 0],
oo13: [0, 0],
perp1: [10, 100, 0, 0],
expectedHealth: 0.95 * (100.0 + 0.8 * 10.0 * baseLotsToQuote),
expectedHealth: 0.8 * 0.95 * (100.0 + 0.8 * 10.0 * baseLotsToQuote),
});

@@ -185,6 +225,9 @@ testFixture({

token3: 0,
bs1: [0, Number.MAX_SAFE_INTEGER],
bs2: [0, Number.MAX_SAFE_INTEGER],
bs3: [0, Number.MAX_SAFE_INTEGER],
oo12: [0, 0],
oo13: [0, 0],
perp1: [30, -100, 0, 0],
expectedHealth: 0.95 * (-100.0 + 0.8 * 30.0 * baseLotsToQuote),
expectedHealth: 0.8 * 0.95 * (-100.0 + 0.8 * 30.0 * baseLotsToQuote),
});

@@ -196,2 +239,5 @@ testFixture({

token3: -10,
bs1: [0, Number.MAX_SAFE_INTEGER],
bs2: [0, Number.MAX_SAFE_INTEGER],
bs3: [0, Number.MAX_SAFE_INTEGER],
oo12: [1, 1],

@@ -215,2 +261,5 @@ oo13: [1, 1],

token3: -10,
bs1: [0, Number.MAX_SAFE_INTEGER],
bs2: [0, Number.MAX_SAFE_INTEGER],
bs3: [0, Number.MAX_SAFE_INTEGER],
oo12: [1, 1],

@@ -234,2 +283,5 @@ oo13: [1, 1],

token3: -1,
bs1: [0, Number.MAX_SAFE_INTEGER],
bs2: [0, Number.MAX_SAFE_INTEGER],
bs3: [0, Number.MAX_SAFE_INTEGER],
oo12: [0, 0],

@@ -252,2 +304,5 @@ oo13: [10, 1],

token3: -1,
bs1: [0, Number.MAX_SAFE_INTEGER],
bs2: [0, Number.MAX_SAFE_INTEGER],
bs3: [0, Number.MAX_SAFE_INTEGER],
oo12: [100, 0],

@@ -267,2 +322,66 @@ oo13: [10, 1],

});
testFixture({
name: '10, checking collateral limit',
token1: 100,
token2: 100,
token3: 100,
bs1: [100, 1000],
bs2: [1500, 5000],
bs3: [10000, 10000],
oo12: [0, 0],
oo13: [0, 0],
perp1: [0, 0, 0, 0],
expectedHealth:
// token1
0.8 * 100.0 +
// token2
0.5 * 100.0 * 5.0 * (5000.0 / (1500.0 * 5.0)) +
// token3
0.5 * 100.0 * 10.0 * (10000.0 / (10000.0 * 10.0)),
});
testFixture({
name: '11, checking borrow limit',
token1: -100,
token2: -100,
token3: -100,
bs1: [100, 1000],
bs2: [1500, 5000],
bs3: [10000, 10000],
oo12: [0, 0],
oo13: [0, 0],
perp1: [0, 0, 0, 0],
expectedHealth:
// token1
-1.2 * 100.0 -
// token2
1.5 * 100.0 * 5.0 * ((1500.0 * 5.0) / 5000.0) -
// token3
1.5 * 100.0 * 10.0 * ((10000.0 * 10.0) / 10000.0),
});
testFixture({
name: '12, positive perp health offsets token borrow',
token1: -100,
token2: 0,
token3: 0,
bs1: [0, Number.MAX_SAFE_INTEGER],
bs2: [0, Number.MAX_SAFE_INTEGER],
bs3: [0, Number.MAX_SAFE_INTEGER],
oo12: [0, 0],
oo13: [0, 0],
perp1: [1, 100, 0, 0],
expectedHealth: 0.8 * (-100.0 + 0.95 * (100.0 + 0.8 * 1.0 * baseLotsToQuote)),
});
testFixture({
name: '13, negative perp health offsets token deposit',
token1: 100,
token2: 0,
token3: 0,
bs1: [0, Number.MAX_SAFE_INTEGER],
bs2: [0, Number.MAX_SAFE_INTEGER],
bs3: [0, Number.MAX_SAFE_INTEGER],
oo12: [0, 0],
oo13: [0, 0],
perp1: [-1, -100, 0, 0],
expectedHealth: 1.2 * (100.0 - 100.0 - 1.2 * 1.0 * baseLotsToQuote),
});
done();

@@ -294,4 +413,4 @@ });

const clonedHcClone = cloneDeep(clonedHc);
clonedHc.tokenInfos[source].balanceNative.isub(amount);
clonedHc.tokenInfos[target].balanceNative.iadd(amount.mul(swapPrice));
clonedHc.tokenInfos[source].balanceSpot.isub(amount);
clonedHc.tokenInfos[target].balanceSpot.iadd(amount.mul(swapPrice));
return maxSwapFn(clonedHcClone);

@@ -331,3 +450,3 @@ }

const clonedHc = cloneDeep(hc);
clonedHc.tokenInfos[1].balanceNative.iadd(I80F48.fromNumber(100).div(clonedHc.tokenInfos[1].prices.oracle));
clonedHc.tokenInfos[1].balanceSpot.iadd(I80F48.fromNumber(100).div(clonedHc.tokenInfos[1].prices.oracle));
for (const priceFactor of [0.1, 0.9, 1.1]) {

@@ -349,4 +468,4 @@ for (const target of range(1, 100, 1)) {

// adjust by usdc
clonedHc.tokenInfos[0].balanceNative.iadd(I80F48.fromNumber(-20).div(clonedHc.tokenInfos[0].prices.oracle));
clonedHc.tokenInfos[1].balanceNative.iadd(I80F48.fromNumber(100).div(clonedHc.tokenInfos[1].prices.oracle));
clonedHc.tokenInfos[0].balanceSpot.iadd(I80F48.fromNumber(-20).div(clonedHc.tokenInfos[0].prices.oracle));
clonedHc.tokenInfos[1].balanceSpot.iadd(I80F48.fromNumber(100).div(clonedHc.tokenInfos[1].prices.oracle));
for (const priceFactor of [0.1, 0.9, 1.1]) {

@@ -365,4 +484,4 @@ for (const target of range(1, 100, 1)) {

// adjust by usdc
clonedHc.tokenInfos[0].balanceNative.iadd(I80F48.fromNumber(-50).div(clonedHc.tokenInfos[0].prices.oracle));
clonedHc.tokenInfos[1].balanceNative.iadd(I80F48.fromNumber(100).div(clonedHc.tokenInfos[1].prices.oracle));
clonedHc.tokenInfos[0].balanceSpot.iadd(I80F48.fromNumber(-50).div(clonedHc.tokenInfos[0].prices.oracle));
clonedHc.tokenInfos[1].balanceSpot.iadd(I80F48.fromNumber(100).div(clonedHc.tokenInfos[1].prices.oracle));
// possible even though the init ratio is <100

@@ -375,5 +494,5 @@ checkMaxSwapResult(clonedHc, 1, 0, 100, 1, maxSwapFn);

// adjust by usdc
clonedHc.tokenInfos[0].balanceNative.iadd(I80F48.fromNumber(-30).div(clonedHc.tokenInfos[0].prices.oracle));
clonedHc.tokenInfos[1].balanceNative.iadd(I80F48.fromNumber(100).div(clonedHc.tokenInfos[1].prices.oracle));
clonedHc.tokenInfos[2].balanceNative.iadd(I80F48.fromNumber(-30).div(clonedHc.tokenInfos[2].prices.oracle));
clonedHc.tokenInfos[0].balanceSpot.iadd(I80F48.fromNumber(-30).div(clonedHc.tokenInfos[0].prices.oracle));
clonedHc.tokenInfos[1].balanceSpot.iadd(I80F48.fromNumber(100).div(clonedHc.tokenInfos[1].prices.oracle));
clonedHc.tokenInfos[2].balanceSpot.iadd(I80F48.fromNumber(-30).div(clonedHc.tokenInfos[2].prices.oracle));
// swapping with a high ratio advises paying back all liabs

@@ -390,5 +509,5 @@ // and then swapping even more because increasing assets in 0 has better asset weight

// adjust by usdc
clonedHc.tokenInfos[0].balanceNative.iadd(I80F48.fromNumber(100).div(clonedHc.tokenInfos[0].prices.oracle));
clonedHc.tokenInfos[1].balanceNative.iadd(I80F48.fromNumber(-2).div(clonedHc.tokenInfos[1].prices.oracle));
clonedHc.tokenInfos[2].balanceNative.iadd(I80F48.fromNumber(-65).div(clonedHc.tokenInfos[2].prices.oracle));
clonedHc.tokenInfos[0].balanceSpot.iadd(I80F48.fromNumber(100).div(clonedHc.tokenInfos[0].prices.oracle));
clonedHc.tokenInfos[1].balanceSpot.iadd(I80F48.fromNumber(-2).div(clonedHc.tokenInfos[1].prices.oracle));
clonedHc.tokenInfos[2].balanceSpot.iadd(I80F48.fromNumber(-65).div(clonedHc.tokenInfos[2].prices.oracle));
const initRatio = clonedHc.healthRatio(HealthType.init);

@@ -409,5 +528,5 @@ expect(initRatio.toNumber()).greaterThan(3);

// 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));
clonedHc.tokenInfos[0].balanceSpot.iadd(I80F48.fromNumber(-20).div(clonedHc.tokenInfos[0].prices.oracle));
clonedHc.tokenInfos[1].balanceSpot.iadd(I80F48.fromNumber(-40).div(clonedHc.tokenInfos[1].prices.oracle));
clonedHc.tokenInfos[2].balanceSpot.iadd(I80F48.fromNumber(120).div(clonedHc.tokenInfos[2].prices.oracle));
for (const priceFactor of [0.9, 1.1]) {

@@ -429,4 +548,4 @@ for (const target of range(1, 100, 1)) {

// adjust by usdc
clonedHc.tokenInfos[0].balanceNative.iadd(I80F48.fromNumber(-20).div(clonedHc.tokenInfos[0].prices.oracle));
clonedHc.tokenInfos[1].balanceNative.iadd(I80F48.fromNumber(20).div(clonedHc.tokenInfos[1].prices.oracle));
clonedHc.tokenInfos[0].balanceSpot.iadd(I80F48.fromNumber(-20).div(clonedHc.tokenInfos[0].prices.oracle));
clonedHc.tokenInfos[1].balanceSpot.iadd(I80F48.fromNumber(20).div(clonedHc.tokenInfos[1].prices.oracle));
expect(clonedHc.health(HealthType.init).toNumber() < 0);

@@ -444,4 +563,4 @@ for (const priceFactor of [0.9, 1.1]) {

// adjust by usdc
clonedHc.tokenInfos[0].balanceNative.iadd(I80F48.fromNumber(-20).div(clonedHc.tokenInfos[0].prices.oracle));
clonedHc.tokenInfos[1].balanceNative.iadd(I80F48.fromNumber(10).div(clonedHc.tokenInfos[1].prices.oracle));
clonedHc.tokenInfos[0].balanceSpot.iadd(I80F48.fromNumber(-20).div(clonedHc.tokenInfos[0].prices.oracle));
clonedHc.tokenInfos[1].balanceSpot.iadd(I80F48.fromNumber(10).div(clonedHc.tokenInfos[1].prices.oracle));
expect(clonedHc.health(HealthType.init).toNumber() < 0);

@@ -459,3 +578,3 @@ for (const priceFactor of [0.9, 1.1]) {

// adjust by usdc
clonedHc.tokenInfos[0].balanceNative.iadd(I80F48.fromNumber(10).div(clonedHc.tokenInfos[0].prices.oracle));
clonedHc.tokenInfos[0].balanceSpot.iadd(I80F48.fromNumber(10).div(clonedHc.tokenInfos[0].prices.oracle));
clonedHc.tokenInfos[1].initAssetWeight = ZERO_I80F48();

@@ -475,4 +594,8 @@ expect(findMaxSwapActual(clonedHc, 0, 1, 1, 1, maxSwapFn)[0] > 0);

const b0 = mockBankAndOracle(0, 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))], [], [PerpInfo.emptyFromPerpMarket(p0)]);
const b1 = mockBankAndOracle(1, 0.2, 0.2, 1.5, 1.5);
const p0 = mockPerpMarket(0, 1, 0.3, 0.3, baseLotSize, 2);
const hc = new HealthCache([
TokenInfo.fromBank(b0, I80F48.fromNumber(0)),
TokenInfo.fromBank(b1, I80F48.fromNumber(0)),
], [], [PerpInfo.emptyFromPerpMarket(p0)]);
expect(hc.health(HealthType.init).toNumber()).equals(0);

@@ -483,3 +606,3 @@ expect(hc

function findMaxTrade(hc, side, ratio, priceFactor) {
const prices = hc.perpInfos[0].prices;
const prices = hc.perpInfos[0].basePrices;
const tradePrice = I80F48.fromNumber(priceFactor).mul(prices.oracle);

@@ -509,16 +632,20 @@ const baseLots0 = hc

console.log(`checking for price_factor: ${priceFactor}, target ratio ${ratio}: actual ratio: ${actualRatio}, plus ratio: ${plusRatio}, base_lots: ${baseLots}`);
expect(ratio).lessThan(actualRatio);
expect(ratio).lessThanOrEqual(actualRatio);
expect(plusRatio - 0.1).lessThanOrEqual(ratio);
}
// adjust token
hc.tokenInfos[0].balanceNative.iadd(I80F48.fromNumber(3000));
for (const existing of [-5, 0, 3]) {
const hcClone = cloneDeep(hc);
hcClone.perpInfos[0].baseLots.iadd(new BN(existing));
hcClone.perpInfos[0].quote.isub(I80F48.fromNumber(existing * baseLotSize * 2));
for (const side of [PerpOrderSide.bid, PerpOrderSide.ask]) {
console.log(`existing ${existing} ${side === PerpOrderSide.bid ? 'bid' : 'ask'}`);
for (const priceFactor of [0.8, 1.0, 1.1]) {
for (const ratio of range(1, 101, 1)) {
checkMaxTrade(hcClone, side, ratio, priceFactor);
hc.tokenInfos[0].balanceSpot.iadd(I80F48.fromNumber(3000));
for (const existingSettle of [-500, 0, 300]) {
const hcClone1 = cloneDeep(hc);
hcClone1.tokenInfos[1].balanceSpot.iadd(I80F48.fromNumber(existingSettle));
for (const existing of [-5, 0, 3]) {
const hcClone2 = cloneDeep(hcClone1);
hcClone2.perpInfos[0].baseLots.iadd(new BN(existing));
hcClone2.perpInfos[0].quote.isub(I80F48.fromNumber(existing * baseLotSize * 2));
for (const side of [PerpOrderSide.bid, PerpOrderSide.ask]) {
console.log(`lots ${existing}, settle ${existingSettle}, side ${side === PerpOrderSide.bid ? 'bid' : 'ask'}`);
for (const priceFactor of [0.8, 1.0, 1.1]) {
for (const ratio of range(1, 101, 1)) {
checkMaxTrade(hcClone2, side, ratio, priceFactor);
}
}

@@ -540,2 +667,3 @@ }

});
// TODO: test_assets_and_borrows
});

@@ -218,5 +218,5 @@ import { BN } from '@coral-xyz/anchor';

}
getPerpSettleHealth(group) {
perpMaxSettle(group, perpMarketSettleTokenIndex) {
const hc = HealthCache.fromMangoAccount(group, this);
return hc.perpSettleHealth();
return hc.perpMaxSettle(perpMarketSettleTokenIndex);
}

@@ -259,7 +259,5 @@ /**

const quoteBank = group.getFirstBankByTokenIndex(sp.quoteTokenIndex);
// NOTE: referrerRebatesAccrued is not declared on oo class, but the layout
// is aware of it
tokensMap
.get(baseBank.tokenIndex)
.iadd(I80F48.fromI64(oo.quoteTokenTotal.add(oo.referrerRebatesAccrued)).mul(quoteBank.price));
.iadd(I80F48.fromI64(oo.quoteTokenTotal).mul(quoteBank.price));
}

@@ -281,5 +279,5 @@ const tokenEquity = Array.from(tokensMap.values()).reduce((a, b) => a.add(b), ZERO_I80F48());

*/
getAssetsValue(group, healthType) {
getAssetsValue(group) {
const hc = HealthCache.fromMangoAccount(group, this);
return hc.assets(healthType);
return hc.healthAssetsAndLiabs(undefined, false).assets;
}

@@ -290,5 +288,5 @@ /**

*/
getLiabsValue(group, healthType) {
getLiabsValue(group) {
const hc = HealthCache.fromMangoAccount(group, this);
return hc.liabs(healthType);
return hc.healthAssetsAndLiabs(undefined, false).liabs;
}

@@ -342,2 +340,3 @@ /**

* TODO: take into account net_borrow_limit and min_vault_to_deposits_ratio
* TODO: see max_borrow_for_health_fn
*/

@@ -1071,6 +1070,6 @@ getMaxWithdrawWithBorrowForToken(group, mintPk) {

this.updateSettleLimit(perpMarket);
const perpSettleHealth = account.getPerpSettleHealth(group);
const perpMaxSettle = account.perpMaxSettle(group, perpMarket.settleTokenIndex);
const limitedUnsettled = this.applyPnlSettleLimit(this.getUnsettledPnl(perpMarket), perpMarket);
if (limitedUnsettled.lt(ZERO_I80F48())) {
return limitedUnsettled.max(perpSettleHealth.max(ZERO_I80F48()).neg());
return limitedUnsettled.max(perpMaxSettle.max(ZERO_I80F48()).neg());
}

@@ -1077,0 +1076,0 @@ return limitedUnsettled;

@@ -326,8 +326,8 @@ import { BN } from '@coral-xyz/anchor';

: ZERO_I80F48();
const perpSettleHealth = acc.account.getPerpSettleHealth(group);
const perpMaxSettle = acc.account.perpMaxSettle(group, this.settleTokenIndex);
acc.settleablePnl =
// need positive settle health to settle against +ve pnl
perpSettleHealth.gt(ZERO_I80F48())
perpMaxSettle.gt(ZERO_I80F48())
? // can only settle min
acc.settleablePnl.max(perpSettleHealth.neg())
acc.settleablePnl.max(perpMaxSettle.neg())
: ZERO_I80F48();

@@ -334,0 +334,0 @@ // If the ordering was unchanged `count` times we know we have the top `count` accounts

@@ -57,3 +57,2 @@ import { PublicKey } from '@solana/web3.js';

healthRatio: a.getHealthRatioUi(group, HealthType.liquidationEnd),
liabs: toUiDecimalsForQuote(a.getLiabsValue(group, HealthType.liquidationEnd)),
};

@@ -200,3 +199,2 @@ });

healthRatio: a.getHealthRatioUi(group, HealthType.liquidationEnd),
liabs: toUiDecimalsForQuote(a.getLiabsValue(group, HealthType.liquidationEnd)),
};

@@ -203,0 +201,0 @@ });

@@ -37,2 +37,6 @@ /// <reference types="bn.js" />

scaledInitLiabWeight(price: I80F48): I80F48;
nativeDeposits(): I80F48;
nativeBorrows(): I80F48;
depositWeightScaleStartQuote: number;
borrowWeightScaleStartQuote: number;
}

@@ -39,0 +43,0 @@ export declare class Bank implements BankForHealth {

@@ -18,10 +18,23 @@ /// <reference types="bn.js" />

static fromDto(dto: any): HealthCache;
computeSerum3Reservations(healthType: HealthType): {
tokenMaxReserved: I80F48[];
computeSerum3Reservations(healthType: HealthType | undefined): {
tokenMaxReserved: TokenMaxReserved[];
serum3Reserved: Serum3Reserved[];
};
effectiveTokenBalances(healthType: HealthType | undefined): TokenBalance[];
effectiveTokenBalancesInternal(healthType: HealthType | undefined, ignoreNegativePerp: boolean): TokenBalance[];
healthSum(healthType: HealthType, tokenBalances: TokenBalance[]): I80F48;
health(healthType: HealthType): I80F48;
perpSettleHealth(): I80F48;
assets(healthType?: HealthType): I80F48;
liabs(healthType?: HealthType): I80F48;
perpMaxSettle(settleTokenIndex: TokenIndex): I80F48;
healthAssetsAndLiabsStableAssets(healthType: HealthType): {
assets: I80F48;
liabs: I80F48;
};
healthAssetsAndLiabsStableLiabs(healthType: HealthType): {
assets: I80F48;
liabs: I80F48;
};
healthAssetsAndLiabs(healthType: HealthType | undefined, stableAssets: boolean): {
assets: I80F48;
liabs: I80F48;
};
healthRatio(healthType: HealthType): I80F48;

@@ -43,3 +56,2 @@ findTokenInfoIndex(tokenIndex: TokenIndex): number;

simHealthRatioWithPerpOrderChanges(perpMarket: PerpMarket, existingPerpPosition: PerpPosition, side: PerpOrderSide, baseLots: BN, price: I80F48, healthType?: HealthType): I80F48;
logHealthCache(debug: string): void;
private static scanRightUntilLessThan;

@@ -72,11 +84,21 @@ private static findMaximum;

prices: Prices;
balanceNative: I80F48;
constructor(tokenIndex: TokenIndex, maintAssetWeight: I80F48, initAssetWeight: I80F48, initScaledAssetWeight: I80F48, maintLiabWeight: I80F48, initLiabWeight: I80F48, initScaledLiabWeight: I80F48, prices: Prices, balanceNative: I80F48);
balanceSpot: I80F48;
constructor(tokenIndex: TokenIndex, maintAssetWeight: I80F48, initAssetWeight: I80F48, initScaledAssetWeight: I80F48, maintLiabWeight: I80F48, initLiabWeight: I80F48, initScaledLiabWeight: I80F48, prices: Prices, balanceSpot: I80F48);
static fromDto(dto: TokenInfoDto): TokenInfo;
static fromBank(bank: BankForHealth, nativeBalance?: I80F48): TokenInfo;
assetWeight(healthType: HealthType): I80F48;
liabWeight(healthType: HealthType): I80F48;
healthContribution(healthType?: HealthType): I80F48;
toString(): string;
assetWeight(healthType: HealthType | undefined): I80F48;
assetWeightedPrice(healthType: HealthType | undefined): I80F48;
liabWeight(healthType: HealthType | undefined): I80F48;
liabWeightedPrice(healthType: HealthType | undefined): I80F48;
healthContribution(healthType: HealthType | undefined, balance: I80F48): I80F48;
toString(balance: I80F48): string;
}
declare class TokenBalance {
spotAndPerp: I80F48;
constructor(spotAndPerp: I80F48);
}
declare class TokenMaxReserved {
maxSerumReserved: I80F48;
constructor(maxSerumReserved: I80F48);
}
export declare class Serum3Reserved {

@@ -90,14 +112,15 @@ allReservedAsBase: I80F48;

reservedQuote: I80F48;
baseIndex: number;
quoteIndex: number;
baseInfoIndex: number;
quoteInfoIndex: number;
marketIndex: MarketIndex;
constructor(reservedBase: I80F48, reservedQuote: I80F48, baseIndex: number, quoteIndex: number, marketIndex: MarketIndex);
constructor(reservedBase: I80F48, reservedQuote: I80F48, baseInfoIndex: number, quoteInfoIndex: number, marketIndex: MarketIndex);
static fromDto(dto: Serum3InfoDto): Serum3Info;
static emptyFromSerum3Market(serum3Market: Serum3Market, baseEntryIndex: number, quoteEntryIndex: number): Serum3Info;
static fromOoModifyingTokenInfos(baseIndex: number, baseInfo: TokenInfo, quoteIndex: number, quoteInfo: TokenInfo, marketIndex: MarketIndex, oo: OpenOrders): Serum3Info;
healthContribution(healthType: HealthType | undefined, tokenInfos: TokenInfo[], tokenMaxReserved: I80F48[], marketReserved: Serum3Reserved): I80F48;
toString(tokenInfos: TokenInfo[], tokenMaxReserved: I80F48[], marketReserved: Serum3Reserved): string;
static fromOoModifyingTokenInfos(baseInfoIndex: number, baseInfo: TokenInfo, quoteInfoIndex: number, quoteInfo: TokenInfo, marketIndex: MarketIndex, oo: OpenOrders): Serum3Info;
healthContribution(healthType: HealthType | undefined, tokenInfos: TokenInfo[], tokenBalances: TokenBalance[], tokenMaxReserved: TokenMaxReserved[], marketReserved: Serum3Reserved): I80F48;
toString(tokenInfos: TokenInfo[], tokenBalances: TokenBalance[], tokenMaxReserved: TokenMaxReserved[], marketReserved: Serum3Reserved): string;
}
export declare class PerpInfo {
perpMarketIndex: number;
settleTokenIndex: TokenIndex;
maintBaseAssetWeight: I80F48;

@@ -114,9 +137,12 @@ initBaseAssetWeight: I80F48;

quote: I80F48;
prices: Prices;
basePrices: Prices;
hasOpenOrders: boolean;
constructor(perpMarketIndex: number, maintBaseAssetWeight: I80F48, initBaseAssetWeight: I80F48, maintBaseLiabWeight: I80F48, initBaseLiabWeight: I80F48, maintOverallAssetWeight: I80F48, initOverallAssetWeight: I80F48, baseLotSize: BN, baseLots: BN, bidsBaseLots: BN, asksBaseLots: BN, quote: I80F48, prices: Prices, hasOpenOrders: boolean);
constructor(perpMarketIndex: number, settleTokenIndex: TokenIndex, maintBaseAssetWeight: I80F48, initBaseAssetWeight: I80F48, maintBaseLiabWeight: I80F48, initBaseLiabWeight: I80F48, maintOverallAssetWeight: I80F48, initOverallAssetWeight: I80F48, baseLotSize: BN, baseLots: BN, bidsBaseLots: BN, asksBaseLots: BN, quote: I80F48, basePrices: Prices, hasOpenOrders: boolean);
static fromDto(dto: PerpInfoDto): PerpInfo;
static fromPerpPosition(perpMarket: PerpMarket, perpPosition: PerpPosition): PerpInfo;
healthContribution(healthType: HealthType | undefined): I80F48;
unweightedHealthContribution(healthType: HealthType | undefined): I80F48;
healthContribution(healthType: HealthType, settleToken: TokenInfo): I80F48;
healthUnsettledPnl(healthType: HealthType | undefined): I80F48;
weighHealthContributionSettle(unweighted: I80F48, healthType: HealthType, settleToken: TokenInfo): I80F48;
weighHealthContributionOverall(unweighted: I80F48, healthType: HealthType | undefined): I80F48;
unweightedHealthUnsettledPnl(healthType: HealthType | undefined): I80F48;
static emptyFromPerpMarket(perpMarket: PerpMarket): PerpInfo;

@@ -142,3 +168,3 @@ toString(): string;

};
balanceNative: I80F48Dto;
balanceSpot: I80F48Dto;
constructor(tokenIndex: number, maintAssetWeight: I80F48Dto, initAssetWeight: I80F48Dto, initScaledAssetWeight: I80F48Dto, maintLiabWeight: I80F48Dto, initLiabWeight: I80F48Dto, initScaledLiabWeight: I80F48Dto, prices: {

@@ -152,9 +178,10 @@ oracle: I80F48Dto;

reservedQuote: I80F48Dto;
baseIndex: number;
quoteIndex: number;
baseInfoIndex: number;
quoteInfoIndex: number;
marketIndex: number;
constructor(reservedBase: I80F48Dto, reservedQuote: I80F48Dto, baseIndex: number, quoteIndex: number);
constructor(reservedBase: I80F48Dto, reservedQuote: I80F48Dto, baseInfoIndex: number, quoteInfoIndex: number);
}
export declare class PerpInfoDto {
perpMarketIndex: number;
settleTokenIndex: number;
maintBaseAssetWeight: I80F48Dto;

@@ -177,2 +204,3 @@ initBaseAssetWeight: I80F48Dto;

}
export {};
//# sourceMappingURL=healthCache.d.ts.map

@@ -117,3 +117,3 @@ /// <reference types="bn.js" />

getHealth(group: Group, healthType: HealthType): I80F48;
getPerpSettleHealth(group: Group): I80F48;
perpMaxSettle(group: Group, perpMarketSettleTokenIndex: TokenIndex): I80F48;
/**

@@ -146,3 +146,3 @@ * Health ratio, which is computed so `100 * (assets-liabs)/liabs`

*/
getAssetsValue(group: Group, healthType?: HealthType): I80F48;
getAssetsValue(group: Group): I80F48;
/**

@@ -152,3 +152,3 @@ * Sum of all negative assets.

*/
getLiabsValue(group: Group, healthType?: HealthType): I80F48;
getLiabsValue(group: Group): I80F48;
/**

@@ -173,2 +173,3 @@ * @returns Overall PNL, in native quote

* TODO: take into account net_borrow_limit and min_vault_to_deposits_ratio
* TODO: see max_borrow_for_health_fn
*/

@@ -175,0 +176,0 @@ getMaxWithdrawWithBorrowForToken(group: Group, mintPk: PublicKey): I80F48;

{
"name": "@blockworks-foundation/mango-v4",
"version": "0.16.11",
"version": "0.16.12",
"description": "Typescript Client for mango-v4 program.",

@@ -5,0 +5,0 @@ "repository": "https://github.com/blockworks-foundation/mango-v4",

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc