2023-02-21 23:36:59 -08:00
|
|
|
import * as anchor from '@coral-xyz/anchor';
|
|
|
|
import { AnchorProvider, BN, Program } from '@coral-xyz/anchor';
|
|
|
|
import NodeWallet from '@coral-xyz/anchor/dist/cjs/nodewallet';
|
2022-08-04 08:35:05 -07:00
|
|
|
import * as spl from '@solana/spl-token';
|
2022-10-08 03:48:13 -07:00
|
|
|
import {
|
|
|
|
Connection,
|
|
|
|
LAMPORTS_PER_SOL,
|
|
|
|
PublicKey,
|
|
|
|
Keypair,
|
|
|
|
} from '@solana/web3.js';
|
2022-08-04 08:35:05 -07:00
|
|
|
import { MangoV4 } from '../target/types/mango_v4';
|
2022-10-08 03:48:13 -07:00
|
|
|
import ProgramKeypair from '../target/deploy/mango_v4-keypair.json';
|
|
|
|
import {
|
|
|
|
Group,
|
|
|
|
MangoAccount,
|
|
|
|
MangoClient,
|
|
|
|
StubOracle,
|
|
|
|
U64_MAX_BN,
|
|
|
|
} from '../ts/client/src/index';
|
|
|
|
import { assert } from 'chai';
|
|
|
|
import {
|
|
|
|
PerpMarketIndex,
|
|
|
|
PerpOrderSide,
|
|
|
|
PerpOrderType,
|
|
|
|
} from '../ts/client/src/accounts/perp';
|
2022-08-03 09:05:16 -07:00
|
|
|
|
|
|
|
enum MINTS {
|
|
|
|
USDC = 'USDC',
|
|
|
|
BTC = 'BTC',
|
|
|
|
}
|
2022-10-08 03:48:13 -07:00
|
|
|
const NUM_USERS = 4;
|
2022-12-08 01:23:12 -08:00
|
|
|
const PROGRAM_ID = '4MangoMjqJ2firMokCjjGgoK8d4MXcrgL7XJaL3w6fVg';
|
2022-10-08 03:48:13 -07:00
|
|
|
|
|
|
|
interface TestUser {
|
|
|
|
keypair: anchor.web3.Keypair;
|
|
|
|
tokenAccounts: spl.AccountInfo[];
|
|
|
|
mangoAccount: MangoAccount;
|
|
|
|
client: MangoClient;
|
|
|
|
}
|
2022-08-03 09:05:16 -07:00
|
|
|
|
|
|
|
async function createMints(
|
|
|
|
program: anchor.Program<MangoV4>,
|
|
|
|
payer: anchor.web3.Keypair,
|
|
|
|
admin,
|
2022-11-04 07:35:40 -07:00
|
|
|
): Promise<Partial<Record<keyof typeof MINTS, spl.Token>>> {
|
|
|
|
const mints: spl.Token[] = [];
|
2022-08-03 09:05:16 -07:00
|
|
|
for (let i = 0; i < 2; i++) {
|
|
|
|
mints.push(
|
|
|
|
await spl.Token.createMint(
|
|
|
|
program.provider.connection,
|
|
|
|
payer,
|
|
|
|
admin.publicKey,
|
|
|
|
admin.publicKey,
|
|
|
|
6,
|
|
|
|
spl.TOKEN_PROGRAM_ID,
|
|
|
|
),
|
|
|
|
);
|
|
|
|
}
|
2022-11-04 07:35:40 -07:00
|
|
|
const mintsMap = {
|
2022-08-03 09:05:16 -07:00
|
|
|
USDC: mints[0],
|
|
|
|
BTC: mints[1],
|
|
|
|
};
|
|
|
|
|
|
|
|
return mintsMap;
|
|
|
|
}
|
|
|
|
|
|
|
|
async function createUsers(
|
|
|
|
mintsMap: Partial<Record<keyof typeof MINTS, spl.Token>>,
|
|
|
|
payer: anchor.web3.Keypair,
|
|
|
|
provider: anchor.Provider,
|
2022-10-08 03:48:13 -07:00
|
|
|
group: Group,
|
|
|
|
connection: Connection,
|
|
|
|
programId: PublicKey,
|
2022-11-04 07:35:40 -07:00
|
|
|
): Promise<TestUser[]> {
|
|
|
|
const users: TestUser[] = [];
|
2022-08-03 09:05:16 -07:00
|
|
|
for (let i = 0; i < NUM_USERS; i++) {
|
2022-11-04 07:35:40 -07:00
|
|
|
const user = anchor.web3.Keypair.generate();
|
2022-08-03 09:05:16 -07:00
|
|
|
|
|
|
|
await provider.connection.requestAirdrop(
|
|
|
|
user.publicKey,
|
|
|
|
LAMPORTS_PER_SOL * 1000,
|
|
|
|
);
|
|
|
|
|
2022-11-04 07:35:40 -07:00
|
|
|
const tokenAccounts: spl.AccountInfo[] = [];
|
|
|
|
for (const mintKey in mintsMap) {
|
|
|
|
const mint: spl.Token = mintsMap[mintKey];
|
|
|
|
const tokenAccount = await mint.getOrCreateAssociatedAccountInfo(
|
2022-08-03 09:05:16 -07:00
|
|
|
user.publicKey,
|
|
|
|
);
|
|
|
|
await mint.mintTo(tokenAccount.address, payer, [], 1_000_000_000_000_000);
|
|
|
|
tokenAccounts.push(tokenAccount);
|
|
|
|
}
|
2022-10-08 03:48:13 -07:00
|
|
|
|
|
|
|
const client = await MangoClient.connect(
|
|
|
|
new anchor.AnchorProvider(
|
|
|
|
connection,
|
|
|
|
new NodeWallet(user),
|
|
|
|
AnchorProvider.defaultOptions(),
|
|
|
|
),
|
|
|
|
'devnet',
|
|
|
|
programId,
|
2022-11-04 07:35:40 -07:00
|
|
|
{ idsSource: 'get-program-accounts' },
|
2022-10-08 03:48:13 -07:00
|
|
|
);
|
|
|
|
|
2022-11-04 07:35:40 -07:00
|
|
|
const mangoAccount = await client.getOrCreateMangoAccount(group);
|
2022-10-08 03:48:13 -07:00
|
|
|
await mangoAccount!.reload(client);
|
|
|
|
|
2022-08-03 09:05:16 -07:00
|
|
|
console.log('created user ' + i);
|
2022-10-08 03:48:13 -07:00
|
|
|
users.push({
|
|
|
|
keypair: user,
|
|
|
|
tokenAccounts: tokenAccounts,
|
|
|
|
client,
|
|
|
|
mangoAccount: mangoAccount!,
|
|
|
|
});
|
2022-08-03 09:05:16 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
return users;
|
|
|
|
}
|
|
|
|
|
|
|
|
describe('mango-v4', () => {
|
2022-11-04 07:35:40 -07:00
|
|
|
const programId = new PublicKey(PROGRAM_ID);
|
2022-08-03 09:05:16 -07:00
|
|
|
// Configure the client to use the local cluster.
|
|
|
|
const envProvider = anchor.AnchorProvider.env();
|
|
|
|
anchor.setProvider(envProvider);
|
2022-11-04 07:35:40 -07:00
|
|
|
const envProviderWallet = envProvider.wallet;
|
|
|
|
const envProviderPayer = (envProviderWallet as NodeWallet).payer;
|
2022-08-03 09:05:16 -07:00
|
|
|
|
|
|
|
const options = AnchorProvider.defaultOptions();
|
|
|
|
const connection = new Connection(
|
|
|
|
process.env.ANCHOR_PROVIDER_URL!,
|
|
|
|
options.commitment,
|
|
|
|
);
|
|
|
|
|
|
|
|
const program = anchor.workspace.MangoV4 as Program<MangoV4>;
|
2022-10-08 03:48:13 -07:00
|
|
|
let users: TestUser[] = [];
|
2022-08-03 09:05:16 -07:00
|
|
|
|
|
|
|
let mintsMap: Partial<Record<keyof typeof MINTS, spl.Token>>;
|
|
|
|
|
|
|
|
let group: Group;
|
|
|
|
let usdcOracle: StubOracle;
|
|
|
|
let btcOracle: StubOracle;
|
|
|
|
let envClient: MangoClient;
|
|
|
|
|
|
|
|
it('Initialize group and users', async () => {
|
|
|
|
console.log(`provider ${envProviderWallet.publicKey.toString()}`);
|
2022-08-17 23:48:45 -07:00
|
|
|
console.log(`program id ${programId.toString()}`);
|
2022-08-03 09:05:16 -07:00
|
|
|
|
|
|
|
mintsMap = await createMints(program, envProviderPayer, envProviderWallet);
|
|
|
|
|
|
|
|
const groupNum = 0;
|
|
|
|
const insuranceMintPk = mintsMap['USDC']!.publicKey;
|
|
|
|
const adminPk = envProviderWallet.publicKey;
|
|
|
|
|
|
|
|
// Passing devnet as the cluster here - client cannot accept localnet
|
|
|
|
// I think this is only for getting the serum market though?
|
2022-11-04 07:35:40 -07:00
|
|
|
envClient = await MangoClient.connect(envProvider, 'devnet', programId, {
|
|
|
|
idsSource: 'get-program-accounts',
|
|
|
|
});
|
2022-10-08 03:48:13 -07:00
|
|
|
await envClient.groupCreate(groupNum, true, 1, insuranceMintPk);
|
2022-08-04 08:35:05 -07:00
|
|
|
group = await envClient.getGroupForCreator(adminPk, groupNum);
|
2022-08-03 09:05:16 -07:00
|
|
|
|
2022-10-08 03:48:13 -07:00
|
|
|
users = await createUsers(
|
|
|
|
mintsMap,
|
|
|
|
envProviderPayer,
|
|
|
|
envProvider,
|
|
|
|
group,
|
|
|
|
connection,
|
|
|
|
programId,
|
|
|
|
);
|
|
|
|
|
|
|
|
assert.strictEqual(group.groupNum, groupNum);
|
|
|
|
assert.deepEqual(group.admin, adminPk);
|
|
|
|
});
|
|
|
|
|
|
|
|
it('Create stub oracles and register tokens', async () => {
|
2022-08-03 09:05:16 -07:00
|
|
|
await envClient.stubOracleCreate(group, mintsMap['USDC']!.publicKey, 1.0);
|
|
|
|
usdcOracle = (
|
|
|
|
await envClient.getStubOracle(group, mintsMap['USDC']!.publicKey)
|
|
|
|
)[0];
|
|
|
|
await envClient.tokenRegister(
|
|
|
|
group,
|
|
|
|
mintsMap['USDC']!.publicKey,
|
|
|
|
usdcOracle.publicKey,
|
|
|
|
0.1,
|
|
|
|
0, // tokenIndex
|
|
|
|
'USDC',
|
|
|
|
0.01,
|
|
|
|
0.4,
|
|
|
|
0.07,
|
|
|
|
0.8,
|
|
|
|
0.9,
|
|
|
|
1.5,
|
|
|
|
0.0005,
|
|
|
|
1.5,
|
|
|
|
0.8,
|
|
|
|
0.6,
|
|
|
|
1.2,
|
|
|
|
1.4,
|
|
|
|
0.02,
|
|
|
|
);
|
|
|
|
await group.reloadAll(envClient);
|
|
|
|
await envClient.stubOracleCreate(group, mintsMap['BTC']!.publicKey, 100.0);
|
|
|
|
btcOracle = (
|
|
|
|
await envClient.getStubOracle(group, mintsMap['BTC']!.publicKey)
|
|
|
|
)[0];
|
|
|
|
|
|
|
|
await envClient.tokenRegister(
|
|
|
|
group,
|
|
|
|
mintsMap['BTC']!.publicKey,
|
|
|
|
btcOracle.publicKey,
|
|
|
|
0.1,
|
|
|
|
1, // tokenIndex
|
|
|
|
'BTC',
|
|
|
|
0.01,
|
|
|
|
0.4,
|
|
|
|
0.07,
|
|
|
|
0.8,
|
|
|
|
0.9,
|
|
|
|
0.88,
|
|
|
|
0.0005,
|
|
|
|
1.5,
|
|
|
|
0.8,
|
|
|
|
0.6,
|
|
|
|
1.2,
|
|
|
|
1.4,
|
|
|
|
0.02,
|
|
|
|
);
|
|
|
|
await group.reloadAll(envClient);
|
|
|
|
|
2022-10-08 03:48:13 -07:00
|
|
|
const banks = await envClient.getBanksForGroup(group);
|
|
|
|
assert.equal(banks.length, 2, 'Two banks present');
|
|
|
|
|
|
|
|
assert.equal(banks[0].name, 'USDC', 'USDC bank present');
|
|
|
|
assert.equal(banks[0].tokenIndex, 0, 'USDC bank token index set');
|
|
|
|
assert.equal(banks[0].uiDeposits(), 0, 'USDC bank has zero deposits');
|
|
|
|
assert.equal(banks[0].uiBorrows(), 0, 'USDC bank has zero borrows');
|
|
|
|
|
|
|
|
assert.equal(banks[1].name, 'BTC', 'BTC bank present');
|
|
|
|
assert.equal(banks[1].tokenIndex, 1, 'BTC bank token index set');
|
|
|
|
assert.equal(banks[1].uiDeposits(), 0, 'BTC bank has zero deposits');
|
|
|
|
assert.equal(banks[1].uiBorrows(), 0, 'BTC bank has zero borrows');
|
|
|
|
});
|
|
|
|
|
|
|
|
it('Create perp market', async () => {
|
2022-08-03 09:05:16 -07:00
|
|
|
await envClient.perpCreateMarket(
|
|
|
|
group,
|
|
|
|
btcOracle.publicKey,
|
|
|
|
0,
|
|
|
|
'BTC-PERP',
|
|
|
|
0.1,
|
|
|
|
6,
|
|
|
|
10,
|
|
|
|
100,
|
|
|
|
0.975,
|
|
|
|
0.95,
|
|
|
|
1.025,
|
|
|
|
1.05,
|
|
|
|
0.012,
|
|
|
|
0.0002,
|
|
|
|
0.0,
|
2022-09-29 03:59:55 -07:00
|
|
|
0,
|
2022-08-03 09:05:16 -07:00
|
|
|
0.05,
|
|
|
|
0.05,
|
|
|
|
100,
|
2022-09-29 03:59:55 -07:00
|
|
|
true,
|
|
|
|
true,
|
2022-10-08 03:48:13 -07:00
|
|
|
1_000_000,
|
2022-09-29 03:59:55 -07:00
|
|
|
0,
|
|
|
|
0,
|
|
|
|
0,
|
2022-10-08 03:48:13 -07:00
|
|
|
);
|
|
|
|
await group.reloadAll(envClient);
|
|
|
|
|
|
|
|
const perps = await envClient.perpGetMarkets(group);
|
|
|
|
assert.equal(perps.length, 1, 'One perp market present');
|
|
|
|
assert.equal(perps[0].name, 'BTC-PERP', 'Name is correct');
|
|
|
|
assert.equal(perps[0].perpMarketIndex, 0, 'Index is correct');
|
|
|
|
assert.equal(
|
|
|
|
perps[0].oracle.toBase58(),
|
|
|
|
btcOracle.publicKey.toBase58(),
|
|
|
|
'Oracle is correct',
|
|
|
|
);
|
|
|
|
});
|
|
|
|
|
|
|
|
it('Edit perp market', async () => {
|
|
|
|
let btcPerp = (await envClient.perpGetMarkets(group))[0];
|
|
|
|
|
|
|
|
assert.closeTo(
|
|
|
|
btcPerp.makerFee.toNumber(),
|
|
|
|
0.0002,
|
|
|
|
0.00001,
|
|
|
|
'Maker fee before',
|
|
|
|
);
|
|
|
|
assert.closeTo(
|
|
|
|
btcPerp.takerFee.toNumber(),
|
|
|
|
0.0,
|
|
|
|
0.00001,
|
|
|
|
'Taker fee before',
|
|
|
|
);
|
|
|
|
|
|
|
|
await envClient.perpEditMarket(
|
|
|
|
group,
|
|
|
|
btcPerp.perpMarketIndex,
|
|
|
|
btcPerp.oracle,
|
|
|
|
0.1,
|
|
|
|
btcPerp.baseDecimals,
|
|
|
|
btcPerp.maintAssetWeight.toNumber(),
|
|
|
|
btcPerp.initAssetWeight.toNumber(),
|
|
|
|
btcPerp.maintLiabWeight.toNumber(),
|
|
|
|
btcPerp.initLiabWeight.toNumber(),
|
|
|
|
btcPerp.liquidationFee.toNumber(),
|
|
|
|
0.0004, // Changed
|
|
|
|
0.0005, // Changed
|
|
|
|
0.0,
|
|
|
|
btcPerp.minFunding.toNumber(),
|
|
|
|
btcPerp.maxFunding.toNumber(),
|
|
|
|
btcPerp.impactQuantity.toNumber(),
|
|
|
|
true,
|
|
|
|
true,
|
|
|
|
1_000_000,
|
|
|
|
0,
|
2022-09-29 05:13:28 -07:00
|
|
|
0,
|
2022-08-03 09:05:16 -07:00
|
|
|
);
|
2022-10-08 03:48:13 -07:00
|
|
|
|
2022-08-03 09:05:16 -07:00
|
|
|
await group.reloadAll(envClient);
|
2022-10-08 03:48:13 -07:00
|
|
|
|
|
|
|
btcPerp = (await envClient.perpGetMarkets(group))[0];
|
|
|
|
|
|
|
|
assert.closeTo(
|
|
|
|
btcPerp.makerFee.toNumber(),
|
|
|
|
0.0004,
|
|
|
|
0.00001,
|
|
|
|
'Maker fee changed',
|
|
|
|
);
|
|
|
|
assert.closeTo(
|
|
|
|
btcPerp.takerFee.toNumber(),
|
|
|
|
0.0005,
|
|
|
|
0.00001,
|
|
|
|
'Taker fee changed',
|
|
|
|
);
|
2022-08-03 09:05:16 -07:00
|
|
|
});
|
|
|
|
|
2022-10-08 03:48:13 -07:00
|
|
|
it('Deposit & Withdraw tokens', async () => {
|
2022-08-03 09:05:16 -07:00
|
|
|
const client = await MangoClient.connect(
|
|
|
|
new anchor.AnchorProvider(
|
|
|
|
connection,
|
|
|
|
new NodeWallet(users[0].keypair),
|
|
|
|
options,
|
|
|
|
),
|
|
|
|
'devnet',
|
|
|
|
programId,
|
2022-11-04 07:35:40 -07:00
|
|
|
{ idsSource: 'get-program-accounts' },
|
2022-08-03 09:05:16 -07:00
|
|
|
);
|
|
|
|
|
2022-11-04 07:35:40 -07:00
|
|
|
const mangoAccount = await client.getOrCreateMangoAccount(group);
|
2022-10-08 03:48:13 -07:00
|
|
|
await mangoAccount!.reload(client);
|
2022-08-03 09:05:16 -07:00
|
|
|
|
2022-08-17 23:48:45 -07:00
|
|
|
await client.tokenDeposit(
|
|
|
|
group,
|
2022-10-08 03:48:13 -07:00
|
|
|
mangoAccount!,
|
2022-08-17 23:48:45 -07:00
|
|
|
mintsMap.USDC!.publicKey,
|
|
|
|
100.5,
|
|
|
|
);
|
2022-10-08 03:48:13 -07:00
|
|
|
await mangoAccount!.reload(client);
|
2022-08-03 09:05:16 -07:00
|
|
|
|
2022-08-17 23:48:45 -07:00
|
|
|
await client.tokenDeposit(
|
|
|
|
group,
|
2022-10-08 03:48:13 -07:00
|
|
|
mangoAccount!,
|
2022-08-17 23:48:45 -07:00
|
|
|
mintsMap.BTC!.publicKey,
|
|
|
|
50.5,
|
|
|
|
);
|
2022-10-08 03:48:13 -07:00
|
|
|
await mangoAccount!.reload(client);
|
|
|
|
|
2022-11-04 07:35:40 -07:00
|
|
|
const banks = await envClient.getBanksForGroup(group);
|
2022-10-08 03:48:13 -07:00
|
|
|
assert.equal(
|
|
|
|
mangoAccount!.getTokenBalanceUi(banks[0]),
|
|
|
|
100.5,
|
|
|
|
'USDC balance reflects deposit',
|
|
|
|
);
|
|
|
|
assert.equal(
|
|
|
|
mangoAccount!.getTokenBalanceUi(banks[1]),
|
|
|
|
50.5,
|
|
|
|
'BTC balance reflects deposit',
|
|
|
|
);
|
2022-08-03 09:05:16 -07:00
|
|
|
|
2022-08-17 23:48:45 -07:00
|
|
|
await client.tokenWithdraw(
|
|
|
|
group,
|
2022-10-08 03:48:13 -07:00
|
|
|
mangoAccount!,
|
2022-08-17 23:48:45 -07:00
|
|
|
mintsMap.USDC!.publicKey,
|
|
|
|
100,
|
|
|
|
false,
|
|
|
|
);
|
2022-10-08 03:48:13 -07:00
|
|
|
await mangoAccount!.reload(client);
|
2022-08-03 09:05:16 -07:00
|
|
|
|
2022-08-17 23:48:45 -07:00
|
|
|
await client.tokenWithdraw(
|
|
|
|
group,
|
2022-10-08 03:48:13 -07:00
|
|
|
mangoAccount!,
|
2022-08-17 23:48:45 -07:00
|
|
|
mintsMap.BTC!.publicKey,
|
|
|
|
50,
|
|
|
|
false,
|
|
|
|
);
|
2022-10-08 03:48:13 -07:00
|
|
|
await mangoAccount!.reload(client);
|
|
|
|
|
|
|
|
assert.equal(
|
|
|
|
mangoAccount!.getTokenBalanceUi(banks[0]),
|
|
|
|
0.5,
|
|
|
|
'USDC balance reflects withdrawal',
|
|
|
|
);
|
|
|
|
assert.equal(
|
|
|
|
mangoAccount!.getTokenBalanceUi(banks[1]),
|
|
|
|
0.5,
|
|
|
|
'BTC balance reflects withdrawal',
|
|
|
|
);
|
2022-08-03 09:05:16 -07:00
|
|
|
});
|
|
|
|
|
2022-10-08 03:48:13 -07:00
|
|
|
it('Place, cancel & match perp orders', async () => {
|
|
|
|
const makerClient = users[2].client;
|
|
|
|
const takerClient = users[3].client;
|
|
|
|
|
|
|
|
const makerAccount = users[2].mangoAccount;
|
|
|
|
const takerAccount = users[3].mangoAccount;
|
|
|
|
await makerAccount.reload(makerClient);
|
|
|
|
await takerAccount.reload(takerClient);
|
|
|
|
|
|
|
|
// Expand each account to allow for perps
|
|
|
|
await makerClient.expandMangoAccount(group, makerAccount, 8, 8, 8, 8);
|
|
|
|
await takerClient.expandMangoAccount(group, takerAccount, 8, 8, 8, 8);
|
|
|
|
|
|
|
|
// Give each client some cash
|
|
|
|
await makerClient.tokenDeposit(
|
|
|
|
group,
|
|
|
|
makerAccount,
|
|
|
|
mintsMap.USDC!.publicKey,
|
|
|
|
1000,
|
2022-08-03 09:05:16 -07:00
|
|
|
);
|
2022-10-08 03:48:13 -07:00
|
|
|
await makerAccount.reload(makerClient);
|
|
|
|
|
|
|
|
await takerClient.tokenDeposit(
|
|
|
|
group,
|
|
|
|
takerAccount,
|
|
|
|
mintsMap.USDC!.publicKey,
|
|
|
|
1000,
|
2022-08-03 09:05:16 -07:00
|
|
|
);
|
2022-10-08 03:48:13 -07:00
|
|
|
await takerAccount.reload(takerClient);
|
|
|
|
|
|
|
|
// Set price
|
|
|
|
await envClient.stubOracleSet(group, btcOracle.publicKey, 100);
|
|
|
|
|
|
|
|
btcOracle = (
|
|
|
|
await envClient.getStubOracle(group, mintsMap['BTC']!.publicKey)
|
|
|
|
)[0];
|
2022-08-03 09:05:16 -07:00
|
|
|
|
2022-10-08 03:48:13 -07:00
|
|
|
assert.equal(
|
|
|
|
btcOracle.price.toNumber(),
|
|
|
|
100.0,
|
|
|
|
'Oracle price has been set',
|
|
|
|
);
|
|
|
|
|
|
|
|
// Maker places passive order
|
|
|
|
await makerClient.perpPlaceOrder(
|
2022-08-03 09:05:16 -07:00
|
|
|
group,
|
2022-10-08 03:48:13 -07:00
|
|
|
makerAccount,
|
|
|
|
0 as PerpMarketIndex,
|
|
|
|
PerpOrderSide.bid,
|
|
|
|
99.0,
|
|
|
|
2.0,
|
|
|
|
100_000,
|
|
|
|
0,
|
|
|
|
PerpOrderType.limit,
|
|
|
|
0,
|
|
|
|
20,
|
|
|
|
);
|
|
|
|
|
|
|
|
await makerClient.perpConsumeAllEvents(group, 0 as PerpMarketIndex);
|
|
|
|
|
|
|
|
await makerAccount.reload(makerClient);
|
|
|
|
let makerOrders = await makerAccount.loadPerpOpenOrdersForMarket(
|
|
|
|
makerClient,
|
|
|
|
group,
|
|
|
|
0 as PerpMarketIndex,
|
|
|
|
);
|
|
|
|
assert.equal(makerOrders.length, 1, 'Maker has one open order');
|
|
|
|
assert.equal(makerOrders[0].uiPrice, 99.0, 'Price correct');
|
|
|
|
assert.equal(makerOrders[0].uiSize, 2.0, 'Size correct');
|
|
|
|
assert.equal(makerOrders[0].side, PerpOrderSide.bid, 'Side correct');
|
|
|
|
|
|
|
|
// Partial fill by taker
|
|
|
|
await takerClient.perpPlaceOrder(
|
|
|
|
group,
|
|
|
|
takerAccount,
|
|
|
|
0 as PerpMarketIndex,
|
|
|
|
PerpOrderSide.ask,
|
|
|
|
98.0,
|
|
|
|
1.0,
|
|
|
|
100_000,
|
|
|
|
0,
|
|
|
|
PerpOrderType.limit,
|
|
|
|
0,
|
|
|
|
20,
|
|
|
|
);
|
|
|
|
|
|
|
|
await takerClient.perpConsumeAllEvents(group, 0 as PerpMarketIndex);
|
|
|
|
await takerAccount.reload(takerClient);
|
|
|
|
await makerAccount.reload(takerClient);
|
|
|
|
|
|
|
|
makerOrders = await makerAccount.loadPerpOpenOrdersForMarket(
|
|
|
|
makerClient,
|
|
|
|
group,
|
|
|
|
0 as PerpMarketIndex,
|
|
|
|
);
|
|
|
|
assert.equal(makerOrders.length, 1, 'Maker still has one open order');
|
|
|
|
assert.equal(makerOrders[0].uiSize, 1.0, 'Size reduced');
|
|
|
|
|
2022-11-04 07:35:40 -07:00
|
|
|
const makerPerps = makerAccount.perpActive();
|
2022-10-08 03:48:13 -07:00
|
|
|
assert.equal(makerPerps.length, 1, 'Maker has perp position');
|
|
|
|
assert.equal(makerPerps[0].marketIndex, 0, 'Market index matches');
|
2022-11-04 07:35:40 -07:00
|
|
|
assert.isTrue(
|
|
|
|
makerPerps[0].basePositionLots.eq(new anchor.BN(10000)),
|
2022-10-08 03:48:13 -07:00
|
|
|
'base position correct',
|
|
|
|
);
|
|
|
|
|
2022-11-04 07:35:40 -07:00
|
|
|
const takerPerps = takerAccount.perpActive();
|
2022-10-08 03:48:13 -07:00
|
|
|
assert.equal(takerPerps.length, 1, 'Taker has perp position');
|
|
|
|
assert.equal(takerPerps[0].marketIndex, 0, 'Market index matches');
|
2022-11-04 07:35:40 -07:00
|
|
|
assert.isTrue(
|
|
|
|
takerPerps[0].basePositionLots.eq(new anchor.BN(-10000)),
|
2022-10-08 03:48:13 -07:00
|
|
|
'base position correct',
|
|
|
|
);
|
|
|
|
|
|
|
|
// Cancel remaining order
|
|
|
|
await makerClient.perpCancelAllOrders(
|
|
|
|
group,
|
|
|
|
makerAccount,
|
|
|
|
0 as PerpMarketIndex,
|
|
|
|
20,
|
|
|
|
);
|
|
|
|
makerOrders = await makerAccount.loadPerpOpenOrdersForMarket(
|
|
|
|
makerClient,
|
|
|
|
group,
|
|
|
|
0 as PerpMarketIndex,
|
|
|
|
);
|
|
|
|
assert.equal(makerOrders.length, 0, 'Maker orders have been canceled');
|
|
|
|
});
|
|
|
|
|
|
|
|
it('Test perp settle pnl & settle fees', async () => {
|
|
|
|
const settlerClient = users[1].client;
|
|
|
|
const makerClient = users[2].client;
|
|
|
|
const takerClient = users[3].client;
|
|
|
|
|
|
|
|
const settlerAccount = users[1].mangoAccount;
|
|
|
|
const makerAccount = users[2].mangoAccount;
|
|
|
|
const takerAccount = users[3].mangoAccount;
|
|
|
|
|
|
|
|
await settlerAccount.reload(settlerClient);
|
|
|
|
await makerAccount.reload(makerClient);
|
|
|
|
await takerAccount.reload(takerClient);
|
|
|
|
|
|
|
|
// Set higher price
|
|
|
|
await envClient.stubOracleSet(group, btcOracle.publicKey, 200);
|
|
|
|
|
|
|
|
btcOracle = (
|
|
|
|
await envClient.getStubOracle(group, mintsMap['BTC']!.publicKey)
|
|
|
|
)[0];
|
|
|
|
|
|
|
|
assert.equal(
|
|
|
|
btcOracle.price.toNumber(),
|
|
|
|
200.0,
|
|
|
|
'Oracle price has been set',
|
|
|
|
);
|
|
|
|
|
|
|
|
const banks = await envClient.getBanksForGroup(group);
|
|
|
|
await makerAccount.reload(makerClient);
|
|
|
|
await takerAccount.reload(takerClient);
|
|
|
|
|
|
|
|
assert.equal(
|
|
|
|
makerAccount.getTokenBalanceUi(banks[0]),
|
|
|
|
1000,
|
|
|
|
'Maker balance before',
|
|
|
|
);
|
|
|
|
assert.equal(
|
|
|
|
takerAccount.getTokenBalanceUi(banks[0]),
|
|
|
|
1000,
|
|
|
|
'Taker balance before',
|
|
|
|
);
|
|
|
|
|
|
|
|
// Settle the PnL. Taker account is short, so it is unprofitable
|
|
|
|
await settlerClient.perpSettlePnl(
|
|
|
|
group,
|
|
|
|
makerAccount,
|
|
|
|
takerAccount,
|
|
|
|
settlerAccount,
|
|
|
|
0 as PerpMarketIndex,
|
|
|
|
);
|
|
|
|
|
|
|
|
await makerAccount.reload(makerClient);
|
|
|
|
await takerAccount.reload(takerClient);
|
|
|
|
await settlerAccount.reload(settlerClient);
|
|
|
|
|
|
|
|
assert.closeTo(
|
|
|
|
makerAccount.getTokenBalanceUi(banks[0]),
|
|
|
|
1100,
|
|
|
|
1,
|
|
|
|
'Maker balance has profit',
|
|
|
|
);
|
|
|
|
assert.closeTo(
|
|
|
|
takerAccount.getTokenBalanceUi(banks[0]),
|
|
|
|
900,
|
|
|
|
1,
|
|
|
|
'Taker balance has loss',
|
|
|
|
);
|
|
|
|
assert.closeTo(
|
|
|
|
settlerAccount.getTokenBalanceUi(banks[0]),
|
|
|
|
1,
|
|
|
|
0.001,
|
|
|
|
'Settler balance has extra fee',
|
|
|
|
);
|
|
|
|
|
|
|
|
// Set even higher price
|
|
|
|
await envClient.stubOracleSet(group, btcOracle.publicKey, 250);
|
|
|
|
|
|
|
|
// Settle the fees
|
|
|
|
let btcPerp = (await envClient.perpGetMarkets(group))[0];
|
|
|
|
assert.closeTo(
|
|
|
|
btcPerp.feesAccrued.toNumber(),
|
|
|
|
89100,
|
|
|
|
0.1,
|
|
|
|
'Fees accrued by contract',
|
|
|
|
);
|
|
|
|
assert.equal(
|
|
|
|
btcPerp.feesSettled.toNumber(),
|
|
|
|
0,
|
|
|
|
'Fees settled by contract is 0',
|
2022-08-03 09:05:16 -07:00
|
|
|
);
|
|
|
|
|
2022-10-08 03:48:13 -07:00
|
|
|
await envClient.perpSettleFees(
|
2022-08-03 09:05:16 -07:00
|
|
|
group,
|
2022-10-08 03:48:13 -07:00
|
|
|
takerAccount,
|
|
|
|
0 as PerpMarketIndex,
|
|
|
|
U64_MAX_BN,
|
2022-08-03 09:05:16 -07:00
|
|
|
);
|
2022-10-08 03:48:13 -07:00
|
|
|
|
|
|
|
btcPerp = (await envClient.perpGetMarkets(group))[0];
|
|
|
|
assert.equal(
|
|
|
|
btcPerp.feesAccrued.toNumber(),
|
|
|
|
0,
|
|
|
|
'Fees accrued by contract is 0',
|
|
|
|
);
|
|
|
|
assert.closeTo(
|
|
|
|
btcPerp.feesSettled.toNumber(),
|
|
|
|
89100,
|
|
|
|
0.1,
|
|
|
|
'Fees accrued by contract have been settled',
|
|
|
|
);
|
|
|
|
|
|
|
|
await takerAccount.reload(takerClient);
|
|
|
|
assert.closeTo(
|
|
|
|
takerAccount.getTokenBalanceUi(banks[0]),
|
|
|
|
898.95,
|
|
|
|
0.01,
|
|
|
|
'Taker balance has more loss',
|
|
|
|
);
|
|
|
|
});
|
|
|
|
|
|
|
|
it('liquidate token with token', async () => {
|
|
|
|
const clientA = users[0].client;
|
|
|
|
const clientB = users[1].client;
|
|
|
|
|
|
|
|
const mangoAccountA = users[0].mangoAccount;
|
|
|
|
const mangoAccountB = users[1].mangoAccount;
|
|
|
|
await mangoAccountA!.reload(clientA);
|
|
|
|
await mangoAccountB!.reload(clientB);
|
2022-08-03 09:05:16 -07:00
|
|
|
|
|
|
|
await envClient.stubOracleSet(group, btcOracle.publicKey, 100);
|
|
|
|
|
|
|
|
// Initialize liquidator
|
2022-08-17 23:48:45 -07:00
|
|
|
await clientA.tokenDeposit(
|
|
|
|
group,
|
2022-10-08 03:48:13 -07:00
|
|
|
mangoAccountA!,
|
2022-08-17 23:48:45 -07:00
|
|
|
mintsMap.USDC!.publicKey,
|
|
|
|
1000,
|
|
|
|
);
|
|
|
|
await clientA.tokenDeposit(
|
|
|
|
group,
|
2022-10-08 03:48:13 -07:00
|
|
|
mangoAccountA!,
|
2022-08-17 23:48:45 -07:00
|
|
|
mintsMap.BTC!.publicKey,
|
|
|
|
100,
|
|
|
|
);
|
2022-08-03 09:05:16 -07:00
|
|
|
|
|
|
|
// Deposit collateral
|
2022-08-17 23:48:45 -07:00
|
|
|
await clientB.tokenDeposit(
|
|
|
|
group,
|
2022-10-08 03:48:13 -07:00
|
|
|
mangoAccountB!,
|
2022-08-17 23:48:45 -07:00
|
|
|
mintsMap.BTC!.publicKey,
|
|
|
|
100,
|
|
|
|
);
|
2022-10-08 03:48:13 -07:00
|
|
|
await mangoAccountB!.reload(clientB);
|
|
|
|
|
|
|
|
// Borrow
|
2022-08-17 23:48:45 -07:00
|
|
|
await clientB.tokenWithdraw(
|
|
|
|
group,
|
2022-10-08 03:48:13 -07:00
|
|
|
mangoAccountB!,
|
2022-08-17 23:48:45 -07:00
|
|
|
mintsMap.USDC!.publicKey,
|
|
|
|
200,
|
|
|
|
true,
|
|
|
|
);
|
2022-10-08 03:48:13 -07:00
|
|
|
|
2023-11-22 23:59:19 -08:00
|
|
|
// Set price so health is below maintenance
|
2022-08-03 09:05:16 -07:00
|
|
|
await envClient.stubOracleSet(group, btcOracle.publicKey, 1);
|
|
|
|
|
2022-10-08 03:48:13 -07:00
|
|
|
await mangoAccountB!.reload(clientB);
|
2022-08-03 09:05:16 -07:00
|
|
|
|
|
|
|
await clientA.liqTokenWithToken(
|
|
|
|
group,
|
2022-10-08 03:48:13 -07:00
|
|
|
mangoAccountA!,
|
|
|
|
mangoAccountB!,
|
2022-08-17 23:48:45 -07:00
|
|
|
mintsMap.BTC!.publicKey,
|
|
|
|
mintsMap.USDC!.publicKey,
|
2022-08-03 09:05:16 -07:00
|
|
|
1000,
|
|
|
|
);
|
|
|
|
});
|
2022-08-05 10:11:44 -07:00
|
|
|
|
|
|
|
it('update index and rate', async () => {
|
2022-10-08 03:48:13 -07:00
|
|
|
let bank = (await envClient.getBanksForGroup(group))[0];
|
|
|
|
const lastUpdated = bank.indexLastUpdated;
|
|
|
|
|
|
|
|
await envClient.updateIndexAndRate(group, mintsMap.USDC!.publicKey);
|
|
|
|
|
|
|
|
bank = (await envClient.getBanksForGroup(group))[0];
|
|
|
|
assert.isTrue(
|
|
|
|
bank.indexLastUpdated > lastUpdated,
|
|
|
|
'Index timestamp updated',
|
|
|
|
);
|
2022-08-05 10:11:44 -07:00
|
|
|
});
|
2022-11-17 23:58:56 -08:00
|
|
|
|
|
|
|
it('calculates entry and break even price correctly', async () => {
|
|
|
|
const { client: clientA, mangoAccount: accountA } = users[2];
|
|
|
|
const { client: clientB, mangoAccount: accountB } = users[3];
|
|
|
|
|
|
|
|
await accountA!.reload(clientA);
|
|
|
|
await accountB!.reload(clientB);
|
|
|
|
|
|
|
|
const btcPerp = (await envClient.perpGetMarkets(group))[0];
|
|
|
|
const positionA = accountA.getPerpPosition(btcPerp.perpMarketIndex)!;
|
|
|
|
const positionB = accountB.getPerpPosition(btcPerp.perpMarketIndex)!;
|
|
|
|
|
2023-02-21 23:36:59 -08:00
|
|
|
assert.equal(positionA.getBasePositionUi(btcPerp), 1, 'Position is long');
|
|
|
|
assert.equal(positionB.getBasePositionUi(btcPerp), -1, 'Position is short');
|
2022-11-17 23:58:56 -08:00
|
|
|
|
|
|
|
assert.isTrue(
|
2023-02-21 23:36:59 -08:00
|
|
|
positionA.getEntryPrice(btcPerp).eq(new BN(99.0)),
|
|
|
|
'long entry price matches',
|
2022-11-17 23:58:56 -08:00
|
|
|
);
|
|
|
|
assert.isTrue(
|
2023-02-21 23:36:59 -08:00
|
|
|
positionB.getEntryPrice(btcPerp).eq(new BN(99.0)),
|
|
|
|
'short entry price matches',
|
2022-11-17 23:58:56 -08:00
|
|
|
);
|
|
|
|
|
|
|
|
assert.isTrue(
|
2023-02-21 23:36:59 -08:00
|
|
|
positionA.getBreakEvenPrice(btcPerp).eq(new BN(99.0)),
|
|
|
|
'long break even price matches',
|
2022-11-17 23:58:56 -08:00
|
|
|
);
|
|
|
|
assert.isTrue(
|
2023-02-21 23:36:59 -08:00
|
|
|
positionB.getBreakEvenPrice(btcPerp).eq(new BN(99.0)),
|
|
|
|
'short break even price matches',
|
2022-11-17 23:58:56 -08:00
|
|
|
);
|
|
|
|
});
|
2022-08-03 09:05:16 -07:00
|
|
|
});
|