Skip to content

Commit e40563e

Browse files
committed
isolatedPositionLiquidatePerpwithFill test
1 parent 7af9f65 commit e40563e

File tree

1 file changed

+338
-0
lines changed

1 file changed

+338
-0
lines changed
Lines changed: 338 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,338 @@
1+
import * as anchor from '@coral-xyz/anchor';
2+
import { Program } from '@coral-xyz/anchor';
3+
import {
4+
BASE_PRECISION,
5+
BN,
6+
EventSubscriber,
7+
isVariant,
8+
LIQUIDATION_PCT_PRECISION,
9+
OracleGuardRails,
10+
OracleSource,
11+
PositionDirection,
12+
PRICE_PRECISION,
13+
QUOTE_PRECISION,
14+
TestClient,
15+
Wallet,
16+
} from '../sdk/src';
17+
import { assert } from 'chai';
18+
19+
import { Keypair, LAMPORTS_PER_SOL, PublicKey } from '@solana/web3.js';
20+
21+
import {
22+
createUserWithUSDCAccount,
23+
initializeQuoteSpotMarket,
24+
mockOracleNoProgram,
25+
mockUSDCMint,
26+
mockUserUSDCAccount,
27+
setFeedPriceNoProgram,
28+
} from './testHelpers';
29+
import { OrderType, PERCENTAGE_PRECISION, PerpOperation } from '../sdk';
30+
import { startAnchor } from 'solana-bankrun';
31+
import { TestBulkAccountLoader } from '../sdk/src/accounts/testBulkAccountLoader';
32+
import { BankrunContextWrapper } from '../sdk/src/bankrun/bankrunConnection';
33+
34+
describe('liquidate perp (no open orders)', () => {
35+
const chProgram = anchor.workspace.Drift as Program;
36+
37+
let driftClient: TestClient;
38+
let eventSubscriber: EventSubscriber;
39+
40+
let bulkAccountLoader: TestBulkAccountLoader;
41+
42+
let bankrunContextWrapper: BankrunContextWrapper;
43+
44+
let usdcMint;
45+
let userUSDCAccount;
46+
47+
const liquidatorKeyPair = new Keypair();
48+
let liquidatorUSDCAccount: Keypair;
49+
let liquidatorDriftClient: TestClient;
50+
51+
let makerDriftClient: TestClient;
52+
let makerUSDCAccount: PublicKey;
53+
54+
// ammInvariant == k == x * y
55+
const mantissaSqrtScale = new BN(Math.sqrt(PRICE_PRECISION.toNumber()));
56+
const ammInitialQuoteAssetReserve = new anchor.BN(5 * 10 ** 13).mul(
57+
mantissaSqrtScale
58+
);
59+
const ammInitialBaseAssetReserve = new anchor.BN(5 * 10 ** 13).mul(
60+
mantissaSqrtScale
61+
);
62+
63+
const usdcAmount = new BN(10 * 10 ** 6);
64+
const makerUsdcAmount = new BN(1000 * 10 ** 6);
65+
66+
let oracle: PublicKey;
67+
68+
before(async () => {
69+
const context = await startAnchor('', [], []);
70+
71+
bankrunContextWrapper = new BankrunContextWrapper(context);
72+
73+
bulkAccountLoader = new TestBulkAccountLoader(
74+
bankrunContextWrapper.connection,
75+
'processed',
76+
1
77+
);
78+
79+
eventSubscriber = new EventSubscriber(
80+
bankrunContextWrapper.connection.toConnection(),
81+
//@ts-ignore
82+
chProgram
83+
);
84+
85+
await eventSubscriber.subscribe();
86+
87+
usdcMint = await mockUSDCMint(bankrunContextWrapper);
88+
userUSDCAccount = await mockUserUSDCAccount(
89+
usdcMint,
90+
usdcAmount,
91+
bankrunContextWrapper
92+
);
93+
94+
oracle = await mockOracleNoProgram(bankrunContextWrapper, 1);
95+
96+
driftClient = new TestClient({
97+
connection: bankrunContextWrapper.connection.toConnection(),
98+
wallet: bankrunContextWrapper.provider.wallet,
99+
programID: chProgram.programId,
100+
opts: {
101+
commitment: 'confirmed',
102+
},
103+
perpMarketIndexes: [0],
104+
spotMarketIndexes: [0],
105+
subAccountIds: [],
106+
oracleInfos: [
107+
{
108+
publicKey: oracle,
109+
source: OracleSource.PYTH,
110+
},
111+
],
112+
accountSubscription: {
113+
type: 'polling',
114+
accountLoader: bulkAccountLoader,
115+
},
116+
});
117+
118+
await driftClient.initialize(usdcMint.publicKey, true);
119+
await driftClient.subscribe();
120+
121+
await driftClient.updateInitialPctToLiquidate(
122+
LIQUIDATION_PCT_PRECISION.toNumber()
123+
);
124+
125+
await initializeQuoteSpotMarket(driftClient, usdcMint.publicKey);
126+
await driftClient.updatePerpAuctionDuration(new BN(0));
127+
128+
const oracleGuardRails: OracleGuardRails = {
129+
priceDivergence: {
130+
markOraclePercentDivergence: PERCENTAGE_PRECISION.muln(100),
131+
oracleTwap5MinPercentDivergence: PERCENTAGE_PRECISION.muln(100),
132+
},
133+
validity: {
134+
slotsBeforeStaleForAmm: new BN(100),
135+
slotsBeforeStaleForMargin: new BN(100),
136+
confidenceIntervalMaxSize: new BN(100000),
137+
tooVolatileRatio: new BN(11), // allow 11x change
138+
},
139+
};
140+
141+
await driftClient.updateOracleGuardRails(oracleGuardRails);
142+
143+
const periodicity = new BN(0);
144+
145+
await driftClient.initializePerpMarket(
146+
0,
147+
148+
oracle,
149+
ammInitialBaseAssetReserve,
150+
ammInitialQuoteAssetReserve,
151+
periodicity
152+
);
153+
154+
await driftClient.initializeUserAccountAndDepositCollateral(
155+
usdcAmount,
156+
userUSDCAccount.publicKey
157+
);
158+
159+
await driftClient.transferIsolatedPerpPositionDeposit(usdcAmount, 0);
160+
161+
await driftClient.openPosition(
162+
PositionDirection.LONG,
163+
new BN(175).mul(BASE_PRECISION).div(new BN(10)), // 17.5 SOL
164+
0,
165+
new BN(0)
166+
);
167+
168+
bankrunContextWrapper.fundKeypair(liquidatorKeyPair, LAMPORTS_PER_SOL);
169+
liquidatorUSDCAccount = await mockUserUSDCAccount(
170+
usdcMint,
171+
usdcAmount,
172+
bankrunContextWrapper,
173+
liquidatorKeyPair.publicKey
174+
);
175+
liquidatorDriftClient = new TestClient({
176+
connection: bankrunContextWrapper.connection.toConnection(),
177+
wallet: new Wallet(liquidatorKeyPair),
178+
programID: chProgram.programId,
179+
opts: {
180+
commitment: 'confirmed',
181+
},
182+
activeSubAccountId: 0,
183+
perpMarketIndexes: [0],
184+
spotMarketIndexes: [0],
185+
subAccountIds: [],
186+
oracleInfos: [
187+
{
188+
publicKey: oracle,
189+
source: OracleSource.PYTH,
190+
},
191+
],
192+
accountSubscription: {
193+
type: 'polling',
194+
accountLoader: bulkAccountLoader,
195+
},
196+
});
197+
await liquidatorDriftClient.subscribe();
198+
199+
await liquidatorDriftClient.initializeUserAccountAndDepositCollateral(
200+
usdcAmount,
201+
liquidatorUSDCAccount.publicKey
202+
);
203+
204+
[makerDriftClient, makerUSDCAccount] = await createUserWithUSDCAccount(
205+
bankrunContextWrapper,
206+
usdcMint,
207+
chProgram,
208+
makerUsdcAmount,
209+
[0],
210+
[0],
211+
[
212+
{
213+
publicKey: oracle,
214+
source: OracleSource.PYTH,
215+
},
216+
],
217+
bulkAccountLoader
218+
);
219+
220+
await makerDriftClient.deposit(makerUsdcAmount, 0, makerUSDCAccount);
221+
});
222+
223+
after(async () => {
224+
await driftClient.unsubscribe();
225+
await liquidatorDriftClient.unsubscribe();
226+
await makerDriftClient.unsubscribe();
227+
await eventSubscriber.unsubscribe();
228+
});
229+
230+
it('liquidate', async () => {
231+
await setFeedPriceNoProgram(bankrunContextWrapper, 0.1, oracle);
232+
await driftClient.updatePerpMarketPausedOperations(
233+
0,
234+
PerpOperation.AMM_FILL
235+
);
236+
237+
try {
238+
const failToPlaceTxSig = await driftClient.placePerpOrder({
239+
direction: PositionDirection.SHORT,
240+
baseAssetAmount: BASE_PRECISION,
241+
price: PRICE_PRECISION.divn(10),
242+
orderType: OrderType.LIMIT,
243+
reduceOnly: true,
244+
marketIndex: 0,
245+
});
246+
bankrunContextWrapper.connection.printTxLogs(failToPlaceTxSig);
247+
throw new Error('Expected placePerpOrder to throw an error');
248+
} catch (error) {
249+
if (
250+
error.message !==
251+
'Error processing Instruction 1: custom program error: 0x1773'
252+
) {
253+
throw new Error(`Unexpected error message: ${error.message}`);
254+
}
255+
}
256+
257+
await makerDriftClient.placePerpOrder({
258+
direction: PositionDirection.LONG,
259+
baseAssetAmount: new BN(175).mul(BASE_PRECISION),
260+
price: PRICE_PRECISION.divn(10),
261+
orderType: OrderType.LIMIT,
262+
marketIndex: 0,
263+
});
264+
265+
const makerInfos = [
266+
{
267+
maker: await makerDriftClient.getUserAccountPublicKey(),
268+
makerStats: makerDriftClient.getUserStatsAccountPublicKey(),
269+
makerUserAccount: makerDriftClient.getUserAccount(),
270+
},
271+
];
272+
273+
const txSig = await liquidatorDriftClient.liquidatePerpWithFill(
274+
await driftClient.getUserAccountPublicKey(),
275+
driftClient.getUserAccount(),
276+
0,
277+
makerInfos
278+
);
279+
280+
bankrunContextWrapper.connection.printTxLogs(txSig);
281+
282+
for (let i = 0; i < 32; i++) {
283+
assert(!isVariant(driftClient.getUserAccount().orders[i].status, 'open'));
284+
}
285+
286+
assert(
287+
liquidatorDriftClient
288+
.getUserAccount()
289+
.perpPositions[0].quoteAssetAmount.eq(new BN(175))
290+
);
291+
292+
assert(
293+
driftClient
294+
.getUserAccount()
295+
.perpPositions[0].baseAssetAmount.eq(new BN(0))
296+
);
297+
298+
assert(
299+
driftClient
300+
.getUserAccount()
301+
.perpPositions[0].quoteAssetAmount.eq(new BN(-15769403))
302+
);
303+
304+
assert(
305+
liquidatorDriftClient.getPerpMarketAccount(0).ifLiquidationFee === 10000
306+
);
307+
308+
assert(
309+
makerDriftClient
310+
.getUserAccount()
311+
.perpPositions[0].baseAssetAmount.eq(new BN(17500000000))
312+
);
313+
314+
assert(
315+
makerDriftClient
316+
.getUserAccount()
317+
.perpPositions[0].quoteAssetAmount.eq(new BN(-1749650))
318+
);
319+
320+
assert(
321+
liquidatorDriftClient.getPerpMarketAccount(0).ifLiquidationFee === 10000
322+
);
323+
324+
await makerDriftClient.liquidatePerpPnlForDeposit(
325+
await driftClient.getUserAccountPublicKey(),
326+
driftClient.getUserAccount(),
327+
0,
328+
0,
329+
QUOTE_PRECISION.muln(20)
330+
);
331+
332+
await makerDriftClient.resolvePerpBankruptcy(
333+
await driftClient.getUserAccountPublicKey(),
334+
driftClient.getUserAccount(),
335+
0
336+
);
337+
});
338+
});

0 commit comments

Comments
 (0)