Skip to content

Commit f39ec85

Browse files
committed
Split OffchainAsyncOrder.settle.test.ts
1 parent 15e109b commit f39ec85

4 files changed

+994
-389
lines changed
Lines changed: 333 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,333 @@
1+
import { ethers } from 'ethers';
2+
import { bn, bootstrapMarkets, DEFAULT_SETTLEMENT_STRATEGY } from '../bootstrap';
3+
import { fastForwardTo } from '@synthetixio/core-utils/utils/hardhat/rpc';
4+
import { snapshotCheckpoint } from '@synthetixio/core-utils/utils/mocha/snapshot';
5+
import { SynthMarkets } from '@synthetixio/spot-market/test/common';
6+
import { calculateFillPrice, depositCollateral } from '../helpers';
7+
import assertBn from '@synthetixio/core-utils/utils/assertions/assert-bignumber';
8+
import assertEvent from '@synthetixio/core-utils/utils/assertions/assert-event';
9+
import assertRevert from '@synthetixio/core-utils/utils/assertions/assert-revert';
10+
import { getTxTime } from '@synthetixio/core-utils/src/utils/hardhat/rpc';
11+
import { wei } from '@synthetixio/wei';
12+
import { calcCurrentFundingVelocity } from '../helpers/funding-calcs';
13+
import { deepEqual } from 'assert/strict';
14+
15+
describe('Settle Offchain Async Order test', () => {
16+
const { systems, perpsMarkets, synthMarkets, provider, trader1, keeper } = bootstrapMarkets({
17+
synthMarkets: [
18+
{
19+
name: 'Bitcoin',
20+
token: 'snxBTC',
21+
buyPrice: bn(10_000),
22+
sellPrice: bn(10_000),
23+
},
24+
],
25+
perpsMarkets: [
26+
{
27+
requestedMarketId: 25,
28+
name: 'Ether',
29+
token: 'snxETH',
30+
price: bn(1000),
31+
fundingParams: { skewScale: bn(100_000), maxFundingVelocity: bn(10) },
32+
},
33+
],
34+
traderAccountIds: [2, 3],
35+
});
36+
let ethMarketId: ethers.BigNumber;
37+
let ethSettlementStrategyId: ethers.BigNumber;
38+
let btcSynth: SynthMarkets[number];
39+
40+
before('identify actors', async () => {
41+
ethMarketId = perpsMarkets()[0].marketId();
42+
ethSettlementStrategyId = perpsMarkets()[0].strategyId();
43+
btcSynth = synthMarkets()[0];
44+
});
45+
46+
before('set Pyth Benchmark Price data', async () => {
47+
const offChainPrice = bn(1000);
48+
49+
// set Pyth setBenchmarkPrice
50+
await systems().MockPythERC7412Wrapper.setBenchmarkPrice(offChainPrice);
51+
});
52+
53+
describe('failures before commiting orders', () => {
54+
describe('using settle', () => {
55+
it('reverts if account id is incorrect (not valid order)', async () => {
56+
await assertRevert(
57+
systems().PerpsMarket.connect(trader1()).settleOrder(1337),
58+
'OrderNotValid()'
59+
);
60+
});
61+
62+
it('reverts if order was not settled before (not valid order)', async () => {
63+
await assertRevert(
64+
systems().PerpsMarket.connect(trader1()).settleOrder(2),
65+
'OrderNotValid()'
66+
);
67+
});
68+
});
69+
});
70+
71+
const restoreToCommit = snapshotCheckpoint(provider);
72+
73+
const testCase = {
74+
name: 'only snxBTC',
75+
collateralData: {
76+
systems,
77+
trader: trader1,
78+
accountId: () => 2,
79+
collaterals: [
80+
{
81+
synthMarket: () => btcSynth,
82+
snxUSDAmount: () => bn(10_000),
83+
},
84+
],
85+
},
86+
};
87+
88+
describe(`Using ${testCase.name} as collateral`, () => {
89+
let tx: ethers.ContractTransaction;
90+
let startTime: number;
91+
92+
before(restoreToCommit);
93+
94+
before('add collateral', async () => {
95+
await depositCollateral(testCase.collateralData);
96+
});
97+
98+
before('commit the order', async () => {
99+
tx = await systems()
100+
.PerpsMarket.connect(trader1())
101+
.commitOrder({
102+
marketId: ethMarketId,
103+
accountId: 2,
104+
sizeDelta: bn(1),
105+
settlementStrategyId: 0,
106+
acceptablePrice: bn(1050), // 5% slippage
107+
referrer: ethers.constants.AddressZero,
108+
trackingCode: ethers.constants.HashZero,
109+
});
110+
startTime = await getTxTime(provider(), tx);
111+
});
112+
113+
const restoreBeforeSettle = snapshotCheckpoint(provider);
114+
115+
describe('attempts to settle before settlement time', () => {
116+
before(restoreBeforeSettle);
117+
118+
it('with settleOrder', async () => {
119+
await assertRevert(
120+
systems().PerpsMarket.connect(trader1()).settleOrder(2),
121+
'SettlementWindowNotOpen'
122+
);
123+
});
124+
});
125+
126+
describe('attempts to settle after settlement window', () => {
127+
before(restoreBeforeSettle);
128+
129+
before('fast forward to past settlement window', async () => {
130+
// fast forward to settlement
131+
await fastForwardTo(
132+
startTime +
133+
DEFAULT_SETTLEMENT_STRATEGY.settlementDelay +
134+
DEFAULT_SETTLEMENT_STRATEGY.settlementWindowDuration +
135+
1,
136+
provider()
137+
);
138+
});
139+
140+
it('with settleOrder', async () => {
141+
await assertRevert(
142+
systems().PerpsMarket.connect(keeper()).settleOrder(2),
143+
'SettlementWindowExpired'
144+
);
145+
});
146+
});
147+
148+
describe('attempts to settle with invalid pyth price data', () => {
149+
before(restoreBeforeSettle);
150+
151+
before('fast forward to settlement time', async () => {
152+
// fast forward to settlement
153+
await fastForwardTo(
154+
startTime + DEFAULT_SETTLEMENT_STRATEGY.settlementDelay + 1,
155+
provider()
156+
);
157+
});
158+
159+
it('reverts when there is no benchmark price', async () => {
160+
// set Pyth setBenchmarkPrice
161+
await systems().MockPythERC7412Wrapper.setAlwaysRevertFlag(true);
162+
163+
await assertRevert(
164+
systems().PerpsMarket.connect(keeper()).settleOrder(2),
165+
`OracleDataRequired("${DEFAULT_SETTLEMENT_STRATEGY.feedId}", ${startTime + 2})`
166+
);
167+
});
168+
});
169+
170+
describe('attempts to settle with not enough collateral', () => {
171+
// Note: This tests is not valid for the "only snxUSD" case
172+
before(restoreBeforeSettle);
173+
174+
before('fast forward to settlement time', async () => {
175+
// fast forward to settlement
176+
await fastForwardTo(
177+
startTime + DEFAULT_SETTLEMENT_STRATEGY.settlementDelay + 1,
178+
provider()
179+
);
180+
});
181+
182+
before('update collateral price', async () => {
183+
await btcSynth.sellAggregator().mockSetCurrentPrice(bn(0.1));
184+
});
185+
186+
it('reverts with invalid pyth price timestamp (after time)', async () => {
187+
const availableCollateral = wei(0.1);
188+
await assertRevert(
189+
systems().PerpsMarket.connect(keeper()).settleOrder(2),
190+
`InsufficientMargin("${availableCollateral.bn}", "${bn(5).toString()}")`
191+
);
192+
});
193+
});
194+
195+
describe('settle order', () => {
196+
before(restoreBeforeSettle);
197+
198+
before('fast forward to settlement time', async () => {
199+
// fast forward to settlement
200+
await fastForwardTo(
201+
startTime + DEFAULT_SETTLEMENT_STRATEGY.settlementDelay + 1,
202+
provider()
203+
);
204+
});
205+
206+
describe('disable settlement strategy', () => {
207+
before(async () => {
208+
await systems().PerpsMarket.setSettlementStrategyEnabled(
209+
ethMarketId,
210+
ethSettlementStrategyId,
211+
false
212+
);
213+
});
214+
215+
it('reverts with invalid settlement strategy', async () => {
216+
await assertRevert(
217+
systems().PerpsMarket.connect(trader1()).settleOrder(2),
218+
'InvalidSettlementStrategy'
219+
);
220+
});
221+
222+
after(async () => {
223+
await systems().PerpsMarket.setSettlementStrategyEnabled(
224+
ethMarketId,
225+
ethSettlementStrategyId,
226+
true
227+
);
228+
});
229+
});
230+
231+
describe('settle order', () => {
232+
let settleTx: ethers.ContractTransaction;
233+
234+
before('settle', async () => {
235+
settleTx = await systems().PerpsMarket.connect(keeper()).settleOrder(2);
236+
});
237+
238+
it('emits settle event', async () => {
239+
const accountId = 2;
240+
const fillPrice = calculateFillPrice(wei(0), wei(100_000), wei(1), wei(1000)).toBN();
241+
const sizeDelta = bn(1);
242+
const newPositionSize = bn(1);
243+
const totalFees = DEFAULT_SETTLEMENT_STRATEGY.settlementReward;
244+
const settlementReward = DEFAULT_SETTLEMENT_STRATEGY.settlementReward;
245+
const trackingCode = `"${ethers.constants.HashZero}"`;
246+
const msgSender = `"${await keeper().getAddress()}"`;
247+
const params = [
248+
ethMarketId,
249+
accountId,
250+
fillPrice,
251+
0,
252+
0,
253+
sizeDelta,
254+
newPositionSize,
255+
totalFees,
256+
0, // referral fees
257+
0, // collected fees
258+
settlementReward,
259+
trackingCode,
260+
msgSender,
261+
];
262+
await assertEvent(settleTx, `OrderSettled(${params.join(', ')})`, systems().PerpsMarket);
263+
});
264+
265+
it('emits market updated event', async () => {
266+
const price = bn(1000);
267+
const marketSize = bn(1);
268+
const marketSkew = bn(1);
269+
const sizeDelta = bn(1);
270+
const currentFundingRate = bn(0);
271+
const currentFundingVelocity = calcCurrentFundingVelocity({
272+
skew: wei(1),
273+
skewScale: wei(100_000),
274+
maxFundingVelocity: wei(10),
275+
});
276+
const params = [
277+
ethMarketId,
278+
price,
279+
marketSkew,
280+
marketSize,
281+
sizeDelta,
282+
currentFundingRate,
283+
currentFundingVelocity.toBN(), // Funding rates should be tested more thoroughly elsewhre
284+
0, // interest rate is 0 since no params were set
285+
];
286+
await assertEvent(settleTx, `MarketUpdated(${params.join(', ')})`, systems().PerpsMarket);
287+
});
288+
289+
it('emits collateral deducted events', async () => {
290+
let pendingTotalFees = DEFAULT_SETTLEMENT_STRATEGY.settlementReward;
291+
const accountId = 2;
292+
293+
for (let i = 0; i < testCase.collateralData.collaterals.length; i++) {
294+
const collateral = testCase.collateralData.collaterals[i];
295+
const synthMarket = collateral.synthMarket ? collateral.synthMarket().marketId() : 0;
296+
let deductedCollateralAmount: ethers.BigNumber = bn(0);
297+
if (synthMarket == 0) {
298+
deductedCollateralAmount = collateral.snxUSDAmount().lt(pendingTotalFees)
299+
? collateral.snxUSDAmount()
300+
: pendingTotalFees;
301+
} else {
302+
deductedCollateralAmount = pendingTotalFees.div(10_000);
303+
}
304+
pendingTotalFees = pendingTotalFees.sub(deductedCollateralAmount);
305+
306+
await assertEvent(
307+
settleTx,
308+
`CollateralDeducted(${accountId}, ${synthMarket}, ${deductedCollateralAmount})`,
309+
systems().PerpsMarket
310+
);
311+
}
312+
});
313+
314+
it('check position is live', async () => {
315+
const [pnl, funding, size] = await systems().PerpsMarket.getOpenPosition(2, ethMarketId);
316+
assertBn.equal(pnl, bn(-0.005));
317+
assertBn.equal(funding, bn(0));
318+
assertBn.equal(size, bn(1));
319+
});
320+
321+
it('check position size', async () => {
322+
const size = await systems().PerpsMarket.getOpenPositionSize(2, ethMarketId);
323+
assertBn.equal(size, bn(1));
324+
});
325+
326+
it('check account open position market ids', async () => {
327+
const positions = await systems().PerpsMarket.getAccountOpenPositions(2);
328+
deepEqual(positions, [ethMarketId]);
329+
});
330+
});
331+
});
332+
});
333+
});

0 commit comments

Comments
 (0)