Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Mc/force close #1006

Merged
merged 3 commits into from
Sep 4, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 14 additions & 4 deletions ts/client/scripts/force-close-serum3-market.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,27 +46,37 @@ async function forceCloseSerum3Market(): Promise<void> {
);

for (let a of mangoAccounts) {
console.log(`mango account ${a.publicKey}`);
// Cancel all orders and confirm that all have been cancelled
for (const _ of range(0, 10)) {
console.log(a.getSerum3OoAccount(MARKET_INDEX).freeSlotBits.zeroBits());
const sig = await client.serum3LiqForceCancelOrders(
group,
a,
serum3Market.serumMarketExternal,
10,
);
console.log(
` serum3LiqForceCancelOrders for ${
a.publicKey
}, sig https://explorer.solana.com/tx/${sig.signature}?cluster=${
` - serum3LiqForceCancelOrders https://explorer.solana.com/tx/${sig.signature}?cluster=${
CLUSTER == 'devnet' ? 'devnet' : ''
}`,
);

a = await a.reload(client);
if (a.getSerum3OoAccount(MARKET_INDEX).freeSlotBits.zeroBits() === 0) {
break;
}
}

const sig2 = await client.serum3CloseOpenOrders(
group,
a,
serum3Market.serumMarketExternal,
);
console.log(
` - serum3CloseOpenOrders https://explorer.solana.com/tx/${sig2.signature}?cluster=${
CLUSTER == 'devnet' ? 'devnet' : ''
}`,
);
}
}

Expand Down
116 changes: 39 additions & 77 deletions ts/client/scripts/force-close-token-borrows.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,10 @@ import { AnchorProvider, Wallet } from '@coral-xyz/anchor';
import { Cluster, Connection, Keypair, PublicKey } from '@solana/web3.js';
import fs from 'fs';
import { TokenIndex } from '../src/accounts/bank';
import { HealthType } from '../src/accounts/mangoAccount';
import { MangoClient } from '../src/client';
import { MANGO_V4_ID } from '../src/constants';
import {
fetchJupiterTransaction,
fetchRoutes,
prepareMangoRouterInstructions,
} from '../src/router';
import { toNative, toUiDecimals } from '../src/utils';
import { ONE_I80F48 } from '../src/numbers/I80F48';

const CLUSTER: Cluster =
(process.env.CLUSTER_OVERRIDE as Cluster) || 'mainnet-beta';
Expand Down Expand Up @@ -56,96 +52,62 @@ async function forceCloseTokenBorrows(): Promise<void> {
);
}

const usdcBank = group.getFirstBankByTokenIndex(0 as TokenIndex);
// Get all mango accounts with borrows for given token
const mangoAccountsWithBorrows = (
await client.getAllMangoAccounts(group)
).filter((a) => a.getTokenBalanceUi(forceCloseTokenBank) < 0);

if (
forceCloseTokenBank.uiBorrows() >=
liqor.getTokenBalanceUi(forceCloseTokenBank)
) {
throw new Error(
`Ensure that liqor has enough deposits to cover borrows! forceCloseTokenBank.uiBorrows() ${forceCloseTokenBank.uiBorrows()}, liqor.getTokenBalanceUi(forceCloseTokenBank) ${liqor.getTokenBalanceUi(forceCloseTokenBank)}`,
);
}

console.log(`${liqor.toString(group, true)}`);

for (const liqee of mangoAccountsWithBorrows) {
liqor = await liqor.reload(client);
// Liqor can only liquidate borrow using deposits, since borrows are in reduce only
// Swap usdc worth token borrow (sub existing position), account for slippage using liquidation fee
// MAX_LIAB_TRANSFER guards against trying to swap to a very large amount
const amount =
Math.min(
liqee.getTokenBorrowsUi(forceCloseTokenBank) -
liqor.getTokenBalanceUi(forceCloseTokenBank),
MAX_LIAB_TRANSFER,
) *
forceCloseTokenBank.uiPrice *
(1 + forceCloseTokenBank.liquidationFee.toNumber());

console.log(
`liqor balance ${liqor.getTokenBalanceUi(
forceCloseTokenBank,
)}, liqee balance ${liqee.getTokenBalanceUi(
forceCloseTokenBank,
)}, liqor will swap further amount of $${toUiDecimals(
amount,
usdcBank.mintDecimals,
)} to ${forceCloseTokenBank.name}`,
);
const sortedByContribution = liqee
.getHealthContributionPerAssetUi(group, HealthType.init)
.filter((a) => {
const potentialAssetBank = group.getFirstBankByName(a.asset);

const amountBn = toNative(
Math.min(amount, 99999999999), // Jupiter API can't handle amounts larger than 99999999999
usdcBank.mintDecimals,
);
const { bestRoute } = await fetchRoutes(
usdcBank.mint,
forceCloseTokenBank.mint,
amountBn.toString(),
forceCloseTokenBank.liquidationFee.toNumber() * 100,
'ExactIn',
'0',
liqor.owner,
);
if (!bestRoute) {
await new Promise((r) => setTimeout(r, 500));
continue;
}
const [ixs, alts] =
bestRoute.routerName === 'Mango'
? await prepareMangoRouterInstructions(
bestRoute,
usdcBank.mint,
forceCloseTokenBank.mint,
user.publicKey,
)
: await fetchJupiterTransaction(
client.connection,
bestRoute,
user.publicKey,
0,
usdcBank.mint,
forceCloseTokenBank.mint,
const feeFactorTotal = ONE_I80F48()
.add(forceCloseTokenBank.liquidationFee)
.add(forceCloseTokenBank.platformLiquidationFee)
.mul(
ONE_I80F48()
.add(potentialAssetBank.liquidationFee)
.add(potentialAssetBank.platformLiquidationFee),
);
const sig = await client.marginTrade({
group: group,
mangoAccount: liqor,
inputMintPk: usdcBank.mint,
amountIn: amount,
outputMintPk: forceCloseTokenBank.mint,
userDefinedInstructions: ixs,
userDefinedAlts: alts,
flashLoanType: { swap: {} },
sequenceCheck: false,
});

return (
potentialAssetBank.reduceOnly != 2 &&
forceCloseTokenBank.initLiabWeight.gte(
potentialAssetBank.initLiabWeight.mul(feeFactorTotal),
)
);
})
.sort((a, b) => {
return a.contribution - b.contribution;
});
const assetBank = group.getFirstBankByName(sortedByContribution[0].asset);

console.log(
` - marginTrade, sig https://explorer.solana.com/tx/${sig}?cluster=${
CLUSTER == 'devnet' ? 'devnet' : ''
}`,
`${liqee.publicKey.toString()}, balance ${liqee.getTokenBalanceUi(forceCloseTokenBank)}, asset ${assetBank.name}, contribution ${sortedByContribution[0].contribution}`,
);

await client.tokenForceCloseBorrowsWithToken(
const sig = await client.tokenForceCloseBorrowsWithToken(
group,
liqor,
liqee,
usdcBank.tokenIndex,
assetBank.tokenIndex,
forceCloseTokenBank.tokenIndex,
);
console.log(` - sig ${sig.signature}`);
}
}

Expand Down
33 changes: 8 additions & 25 deletions ts/client/scripts/force-withdraw-token.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,43 +65,26 @@ async function forceWithdrawTokens(): Promise<void> {

for (const mangoAccount of mangoAccounts) {
console.log(
`${mangoAccount.getTokenBalanceUi(forceWithdrawBank)} for ${
mangoAccount.publicKey
}`,
`${mangoAccount.publicKey} ${forceWithdrawBank.name} balance ${mangoAccount.getTokenBalanceUi(forceWithdrawBank)}`,
);

try {
const sig = await client.serum3LiqForceCancelOrders(
group,
mangoAccount,
serum3Market.serumMarketExternal,
);
console.log(
` serum3LiqForceCancelOrders for ${mangoAccount.publicKey}, owner ${
mangoAccount.owner
}, sig https://explorer.solana.com/tx/${sig.signature}?cluster=${
CLUSTER == 'devnet' ? 'devnet' : ''
}`,
);
} catch (error) {
console.log(error);
}

await client
.tokenForceWithdraw(group, mangoAccount, TOKEN_INDEX)
.then((sig) => {
console.log(
` tokenForceWithdraw for ${mangoAccount.publicKey}, owner ${
mangoAccount.owner
}, sig https://explorer.solana.com/tx/${sig.signature}?cluster=${
` - tokenForceWithdraw https://explorer.solana.com/tx/${sig.signature}?cluster=${
CLUSTER == 'devnet' ? 'devnet' : ''
}`,
);
});
}

await group.reloadAll(client);
console.log(forceWithdrawBank.uiDeposits());
const groupFresh = await client.getGroup(new PublicKey(GROUP_PK));
const forceWithdrawBankFresh =
groupFresh.getFirstBankByTokenIndex(TOKEN_INDEX);
console.log(
`Final ${forceWithdrawBankFresh.name} deposits ${forceWithdrawBankFresh.uiDeposits()}`,
);
}

forceWithdrawTokens();
6 changes: 6 additions & 0 deletions ts/client/src/accounts/group.ts
Original file line number Diff line number Diff line change
Expand Up @@ -598,6 +598,12 @@ export class Group {
return banks[0];
}

public getFirstBankByName(name: string): Bank {
const banks = this.banksMapByName.get(name);
if (!banks) throw new Error(`No bank found for name ${name}!`);
return banks[0];
}

public getFirstBankByOracle(oraclePk: PublicKey): Bank {
const banks = this.banksMapByOracle.get(oraclePk.toString());
if (!banks) throw new Error(`No bank found for oracle ${oraclePk}!`);
Expand Down
Loading