@drift-labs/sdk 2.145.0 → 2.146.0-alpha.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env +4 -0
- package/VERSION +1 -1
- package/lib/browser/accounts/grpcMultiUserAccountSubscriber.js +8 -1
- package/lib/browser/accounts/webSocketProgramAccountSubscriberV2.d.ts +99 -7
- package/lib/browser/accounts/webSocketProgramAccountSubscriberV2.js +435 -144
- package/lib/browser/adminClient.d.ts +5 -1
- package/lib/browser/adminClient.js +57 -23
- package/lib/browser/constants/numericConstants.d.ts +2 -0
- package/lib/browser/constants/numericConstants.js +5 -1
- package/lib/browser/constants/perpMarkets.js +0 -2
- package/lib/browser/decode/user.js +4 -0
- package/lib/browser/driftClient.d.ts +25 -10
- package/lib/browser/driftClient.js +238 -41
- package/lib/browser/driftClientConfig.d.ts +7 -2
- package/lib/browser/idl/drift.json +245 -22
- package/lib/browser/index.d.ts +4 -0
- package/lib/browser/index.js +9 -1
- package/lib/browser/marginCalculation.d.ts +86 -0
- package/lib/browser/marginCalculation.js +209 -0
- package/lib/browser/math/margin.d.ts +1 -1
- package/lib/browser/math/margin.js +8 -1
- package/lib/browser/math/position.d.ts +1 -0
- package/lib/browser/math/position.js +10 -2
- package/lib/browser/math/spotPosition.d.ts +1 -1
- package/lib/browser/math/spotPosition.js +3 -2
- package/lib/browser/math/superStake.d.ts +3 -2
- package/lib/browser/types.d.ts +13 -0
- package/lib/browser/types.js +12 -1
- package/lib/browser/user.d.ts +59 -11
- package/lib/browser/user.js +348 -43
- package/lib/node/accounts/grpcMultiUserAccountSubscriber.d.ts.map +1 -1
- package/lib/node/accounts/grpcMultiUserAccountSubscriber.js +8 -1
- package/lib/node/accounts/webSocketProgramAccountSubscriberV2.d.ts +99 -7
- package/lib/node/accounts/webSocketProgramAccountSubscriberV2.d.ts.map +1 -1
- package/lib/node/accounts/webSocketProgramAccountSubscriberV2.js +435 -144
- package/lib/node/adminClient.d.ts +5 -1
- package/lib/node/adminClient.d.ts.map +1 -1
- package/lib/node/adminClient.js +57 -23
- package/lib/node/constants/numericConstants.d.ts +2 -0
- package/lib/node/constants/numericConstants.d.ts.map +1 -1
- package/lib/node/constants/numericConstants.js +5 -1
- package/lib/node/constants/perpMarkets.d.ts.map +1 -1
- package/lib/node/constants/perpMarkets.js +0 -2
- package/lib/node/decode/user.d.ts.map +1 -1
- package/lib/node/decode/user.js +4 -0
- package/lib/node/driftClient.d.ts +25 -10
- package/lib/node/driftClient.d.ts.map +1 -1
- package/lib/node/driftClient.js +238 -41
- package/lib/node/driftClientConfig.d.ts +7 -2
- package/lib/node/driftClientConfig.d.ts.map +1 -1
- package/lib/node/idl/drift.json +245 -22
- package/lib/node/index.d.ts +4 -0
- package/lib/node/index.d.ts.map +1 -1
- package/lib/node/index.js +9 -1
- package/lib/node/marginCalculation.d.ts +87 -0
- package/lib/node/marginCalculation.d.ts.map +1 -0
- package/lib/node/marginCalculation.js +209 -0
- package/lib/node/math/margin.d.ts +1 -1
- package/lib/node/math/margin.d.ts.map +1 -1
- package/lib/node/math/margin.js +8 -1
- package/lib/node/math/position.d.ts +1 -0
- package/lib/node/math/position.d.ts.map +1 -1
- package/lib/node/math/position.js +10 -2
- package/lib/node/math/spotPosition.d.ts +1 -1
- package/lib/node/math/spotPosition.d.ts.map +1 -1
- package/lib/node/math/spotPosition.js +3 -2
- package/lib/node/math/superStake.d.ts +3 -2
- package/lib/node/math/superStake.d.ts.map +1 -1
- package/lib/node/types.d.ts +13 -0
- package/lib/node/types.d.ts.map +1 -1
- package/lib/node/types.js +12 -1
- package/lib/node/user.d.ts +59 -11
- package/lib/node/user.d.ts.map +1 -1
- package/lib/node/user.js +348 -43
- package/package.json +1 -1
- package/scripts/deposit-isolated-positions.ts +110 -0
- package/scripts/single-grpc-client-test.ts +71 -21
- package/scripts/withdraw-isolated-positions.ts +174 -0
- package/src/accounts/grpcMultiUserAccountSubscriber.ts +8 -1
- package/src/accounts/webSocketProgramAccountSubscriberV2.ts +566 -167
- package/src/adminClient.ts +74 -25
- package/src/constants/numericConstants.ts +5 -0
- package/src/constants/perpMarkets.ts +0 -3
- package/src/decode/user.ts +7 -1
- package/src/driftClient.ts +465 -52
- package/src/driftClientConfig.ts +15 -8
- package/src/idl/drift.json +246 -23
- package/src/index.ts +4 -0
- package/src/margin/README.md +143 -0
- package/src/marginCalculation.ts +306 -0
- package/src/math/margin.ts +13 -1
- package/src/math/position.ts +12 -2
- package/src/math/spotPosition.ts +6 -2
- package/src/types.ts +16 -0
- package/src/user.ts +623 -81
- package/tests/amm/test.ts +1 -1
- package/tests/dlob/helpers.ts +6 -3
- package/tests/user/getMarginCalculation.ts +405 -0
- package/tests/user/test.ts +0 -7
package/tests/amm/test.ts
CHANGED
package/tests/dlob/helpers.ts
CHANGED
|
@@ -44,6 +44,8 @@ export const mockPerpPosition: PerpPosition = {
|
|
|
44
44
|
lastBaseAssetAmountPerLp: new BN(0),
|
|
45
45
|
lastQuoteAssetAmountPerLp: new BN(0),
|
|
46
46
|
perLpBase: 0,
|
|
47
|
+
positionFlag: 0,
|
|
48
|
+
isolatedPositionScaledBalance: new BN(0),
|
|
47
49
|
maxMarginRatio: 1,
|
|
48
50
|
};
|
|
49
51
|
|
|
@@ -670,11 +672,12 @@ export class MockUserMap implements UserMapInterface {
|
|
|
670
672
|
wallet: new Wallet(new Keypair()),
|
|
671
673
|
programID: PublicKey.default,
|
|
672
674
|
});
|
|
675
|
+
this.eventEmitter = new EventEmitter();
|
|
673
676
|
}
|
|
674
677
|
|
|
675
|
-
public async subscribe(): Promise<void> {
|
|
678
|
+
public async subscribe(): Promise<void> {}
|
|
676
679
|
|
|
677
|
-
public async unsubscribe(): Promise<void> {
|
|
680
|
+
public async unsubscribe(): Promise<void> {}
|
|
678
681
|
|
|
679
682
|
public async addPubkey(userAccountPublicKey: PublicKey): Promise<void> {
|
|
680
683
|
const user = new User({
|
|
@@ -733,7 +736,7 @@ export class MockUserMap implements UserMapInterface {
|
|
|
733
736
|
);
|
|
734
737
|
}
|
|
735
738
|
|
|
736
|
-
public async updateWithOrderRecord(_record: OrderRecord): Promise<void> {
|
|
739
|
+
public async updateWithOrderRecord(_record: OrderRecord): Promise<void> {}
|
|
737
740
|
|
|
738
741
|
public values(): IterableIterator<User> {
|
|
739
742
|
return this.userMap.values();
|
|
@@ -0,0 +1,405 @@
|
|
|
1
|
+
import {
|
|
2
|
+
BN,
|
|
3
|
+
ZERO,
|
|
4
|
+
User,
|
|
5
|
+
UserAccount,
|
|
6
|
+
PublicKey,
|
|
7
|
+
PerpMarketAccount,
|
|
8
|
+
SpotMarketAccount,
|
|
9
|
+
PRICE_PRECISION,
|
|
10
|
+
OraclePriceData,
|
|
11
|
+
BASE_PRECISION,
|
|
12
|
+
QUOTE_PRECISION,
|
|
13
|
+
SPOT_MARKET_BALANCE_PRECISION,
|
|
14
|
+
SpotBalanceType,
|
|
15
|
+
OPEN_ORDER_MARGIN_REQUIREMENT,
|
|
16
|
+
SPOT_MARKET_WEIGHT_PRECISION,
|
|
17
|
+
PositionFlag,
|
|
18
|
+
} from '../../src';
|
|
19
|
+
import { MockUserMap, mockPerpMarkets, mockSpotMarkets } from '../dlob/helpers';
|
|
20
|
+
import { assert } from '../../src/assert/assert';
|
|
21
|
+
import { mockUserAccount as baseMockUserAccount } from './helpers';
|
|
22
|
+
import * as _ from 'lodash';
|
|
23
|
+
|
|
24
|
+
async function makeMockUser(
|
|
25
|
+
myMockPerpMarkets: Array<PerpMarketAccount>,
|
|
26
|
+
myMockSpotMarkets: Array<SpotMarketAccount>,
|
|
27
|
+
myMockUserAccount: UserAccount,
|
|
28
|
+
perpOraclePriceList: number[],
|
|
29
|
+
spotOraclePriceList: number[]
|
|
30
|
+
): Promise<User> {
|
|
31
|
+
const umap = new MockUserMap();
|
|
32
|
+
const mockUser: User = await umap.mustGet('1');
|
|
33
|
+
mockUser._isSubscribed = true;
|
|
34
|
+
mockUser.driftClient._isSubscribed = true;
|
|
35
|
+
mockUser.driftClient.accountSubscriber.isSubscribed = true;
|
|
36
|
+
|
|
37
|
+
const oraclePriceMap: Record<string, number> = {};
|
|
38
|
+
for (let i = 0; i < myMockPerpMarkets.length; i++) {
|
|
39
|
+
oraclePriceMap[myMockPerpMarkets[i].amm.oracle.toString()] =
|
|
40
|
+
perpOraclePriceList[i] ?? 1;
|
|
41
|
+
}
|
|
42
|
+
for (let i = 0; i < myMockSpotMarkets.length; i++) {
|
|
43
|
+
oraclePriceMap[myMockSpotMarkets[i].oracle.toString()] =
|
|
44
|
+
spotOraclePriceList[i] ?? 1;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function getMockUserAccount(): UserAccount {
|
|
48
|
+
return myMockUserAccount;
|
|
49
|
+
}
|
|
50
|
+
function getMockPerpMarket(marketIndex: number): PerpMarketAccount {
|
|
51
|
+
return myMockPerpMarkets[marketIndex];
|
|
52
|
+
}
|
|
53
|
+
function getMockSpotMarket(marketIndex: number): SpotMarketAccount {
|
|
54
|
+
return myMockSpotMarkets[marketIndex];
|
|
55
|
+
}
|
|
56
|
+
function getMockOracle(oracleKey: PublicKey) {
|
|
57
|
+
const data: OraclePriceData = {
|
|
58
|
+
price: new BN(
|
|
59
|
+
(oraclePriceMap[oracleKey.toString()] ?? 1) * PRICE_PRECISION.toNumber()
|
|
60
|
+
),
|
|
61
|
+
slot: new BN(0),
|
|
62
|
+
confidence: new BN(1),
|
|
63
|
+
hasSufficientNumberOfDataPoints: true,
|
|
64
|
+
};
|
|
65
|
+
return { data, slot: 0 };
|
|
66
|
+
}
|
|
67
|
+
function getOracleDataForPerpMarket(marketIndex: number) {
|
|
68
|
+
const oracle = getMockPerpMarket(marketIndex).amm.oracle;
|
|
69
|
+
return getMockOracle(oracle).data;
|
|
70
|
+
}
|
|
71
|
+
function getOracleDataForSpotMarket(marketIndex: number) {
|
|
72
|
+
const oracle = getMockSpotMarket(marketIndex).oracle;
|
|
73
|
+
return getMockOracle(oracle).data;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
mockUser.getUserAccount = getMockUserAccount;
|
|
77
|
+
mockUser.driftClient.getPerpMarketAccount = getMockPerpMarket as any;
|
|
78
|
+
mockUser.driftClient.getSpotMarketAccount = getMockSpotMarket as any;
|
|
79
|
+
mockUser.driftClient.getOraclePriceDataAndSlot = getMockOracle as any;
|
|
80
|
+
mockUser.driftClient.getOracleDataForPerpMarket =
|
|
81
|
+
getOracleDataForPerpMarket as any;
|
|
82
|
+
mockUser.driftClient.getOracleDataForSpotMarket =
|
|
83
|
+
getOracleDataForSpotMarket as any;
|
|
84
|
+
return mockUser;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
describe('getMarginCalculation snapshot', () => {
|
|
88
|
+
it('empty account returns zeroed snapshot', async () => {
|
|
89
|
+
const myMockPerpMarkets = _.cloneDeep(mockPerpMarkets);
|
|
90
|
+
const myMockSpotMarkets = _.cloneDeep(mockSpotMarkets);
|
|
91
|
+
const myMockUserAccount = _.cloneDeep(baseMockUserAccount);
|
|
92
|
+
|
|
93
|
+
const user: User = await makeMockUser(
|
|
94
|
+
myMockPerpMarkets,
|
|
95
|
+
myMockSpotMarkets,
|
|
96
|
+
myMockUserAccount,
|
|
97
|
+
[1, 1, 1, 1, 1, 1, 1, 1],
|
|
98
|
+
[1, 1, 1, 1, 1, 1, 1, 1]
|
|
99
|
+
);
|
|
100
|
+
|
|
101
|
+
const calc = user.getMarginCalculation('Initial');
|
|
102
|
+
assert(calc.totalCollateral.eq(ZERO));
|
|
103
|
+
assert(calc.marginRequirement.eq(ZERO));
|
|
104
|
+
assert(calc.numSpotLiabilities === 0);
|
|
105
|
+
assert(calc.numPerpLiabilities === 0);
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
it('quote deposit increases totalCollateral, no requirement', async () => {
|
|
109
|
+
const myMockPerpMarkets = _.cloneDeep(mockPerpMarkets);
|
|
110
|
+
const myMockSpotMarkets = _.cloneDeep(mockSpotMarkets);
|
|
111
|
+
const myMockUserAccount = _.cloneDeep(baseMockUserAccount);
|
|
112
|
+
|
|
113
|
+
myMockUserAccount.spotPositions[0].balanceType = SpotBalanceType.DEPOSIT;
|
|
114
|
+
myMockUserAccount.spotPositions[0].scaledBalance = new BN(
|
|
115
|
+
10000 * SPOT_MARKET_BALANCE_PRECISION.toNumber()
|
|
116
|
+
);
|
|
117
|
+
|
|
118
|
+
const user: User = await makeMockUser(
|
|
119
|
+
myMockPerpMarkets,
|
|
120
|
+
myMockSpotMarkets,
|
|
121
|
+
myMockUserAccount,
|
|
122
|
+
[1, 1, 1, 1, 1, 1, 1, 1],
|
|
123
|
+
[1, 1, 1, 1, 1, 1, 1, 1]
|
|
124
|
+
);
|
|
125
|
+
|
|
126
|
+
const calc = user.getMarginCalculation('Initial');
|
|
127
|
+
const expected = new BN('10000000000'); // $10k
|
|
128
|
+
assert(calc.totalCollateral.eq(expected));
|
|
129
|
+
assert(calc.marginRequirement.eq(ZERO));
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
it('quote borrow increases requirement and buffer applies', async () => {
|
|
133
|
+
const myMockPerpMarkets = _.cloneDeep(mockPerpMarkets);
|
|
134
|
+
const myMockSpotMarkets = _.cloneDeep(mockSpotMarkets);
|
|
135
|
+
const myMockUserAccount = _.cloneDeep(baseMockUserAccount);
|
|
136
|
+
|
|
137
|
+
// Borrow 100 quote
|
|
138
|
+
myMockUserAccount.spotPositions[0].balanceType = SpotBalanceType.BORROW;
|
|
139
|
+
myMockUserAccount.spotPositions[0].scaledBalance = new BN(
|
|
140
|
+
100 * SPOT_MARKET_BALANCE_PRECISION.toNumber()
|
|
141
|
+
);
|
|
142
|
+
|
|
143
|
+
const user: User = await makeMockUser(
|
|
144
|
+
myMockPerpMarkets,
|
|
145
|
+
myMockSpotMarkets,
|
|
146
|
+
myMockUserAccount,
|
|
147
|
+
[1, 1, 1, 1, 1, 1, 1, 1],
|
|
148
|
+
[1, 1, 1, 1, 1, 1, 1, 1]
|
|
149
|
+
);
|
|
150
|
+
|
|
151
|
+
const tenPercent = new BN(1000);
|
|
152
|
+
const calc = user.getMarginCalculation('Initial', {
|
|
153
|
+
liquidationBuffer: tenPercent,
|
|
154
|
+
});
|
|
155
|
+
const liability = new BN(100).mul(QUOTE_PRECISION); // $100
|
|
156
|
+
assert(calc.totalCollateral.eq(ZERO));
|
|
157
|
+
assert(calc.marginRequirement.eq(liability));
|
|
158
|
+
assert(
|
|
159
|
+
calc.marginRequirementPlusBuffer.eq(
|
|
160
|
+
liability.div(new BN(10)).add(calc.marginRequirement) // 10% of liability + margin requirement
|
|
161
|
+
)
|
|
162
|
+
);
|
|
163
|
+
assert(calc.numSpotLiabilities === 1);
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
it('non-quote spot open orders add IM', async () => {
|
|
167
|
+
const myMockPerpMarkets = _.cloneDeep(mockPerpMarkets);
|
|
168
|
+
const myMockSpotMarkets = _.cloneDeep(mockSpotMarkets);
|
|
169
|
+
const myMockUserAccount = _.cloneDeep(baseMockUserAccount);
|
|
170
|
+
|
|
171
|
+
// Market 1 (e.g., SOL) with 2 open orders
|
|
172
|
+
myMockUserAccount.spotPositions[1].marketIndex = 1;
|
|
173
|
+
myMockUserAccount.spotPositions[1].openOrders = 2;
|
|
174
|
+
|
|
175
|
+
const user: User = await makeMockUser(
|
|
176
|
+
myMockPerpMarkets,
|
|
177
|
+
myMockSpotMarkets,
|
|
178
|
+
myMockUserAccount,
|
|
179
|
+
[1, 1, 1, 1, 1, 1, 1, 1],
|
|
180
|
+
[1, 1, 1, 1, 1, 1, 1, 1]
|
|
181
|
+
);
|
|
182
|
+
|
|
183
|
+
const calc = user.getMarginCalculation('Initial');
|
|
184
|
+
const expectedIM = new BN(2).mul(OPEN_ORDER_MARGIN_REQUIREMENT);
|
|
185
|
+
assert(calc.marginRequirement.eq(expectedIM));
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
it('perp long liability reflects maintenance requirement', async () => {
|
|
189
|
+
const myMockPerpMarkets = _.cloneDeep(mockPerpMarkets);
|
|
190
|
+
const myMockSpotMarkets = _.cloneDeep(mockSpotMarkets);
|
|
191
|
+
const myMockUserAccount = _.cloneDeep(baseMockUserAccount);
|
|
192
|
+
|
|
193
|
+
// 20 base long, -$10 quote (liability)
|
|
194
|
+
myMockUserAccount.perpPositions[0].baseAssetAmount = new BN(20).mul(
|
|
195
|
+
BASE_PRECISION
|
|
196
|
+
);
|
|
197
|
+
myMockUserAccount.perpPositions[0].quoteAssetAmount = new BN(-10).mul(
|
|
198
|
+
QUOTE_PRECISION
|
|
199
|
+
);
|
|
200
|
+
|
|
201
|
+
const user: User = await makeMockUser(
|
|
202
|
+
myMockPerpMarkets,
|
|
203
|
+
myMockSpotMarkets,
|
|
204
|
+
myMockUserAccount,
|
|
205
|
+
[1, 1, 1, 1, 1, 1, 1, 1],
|
|
206
|
+
[1, 1, 1, 1, 1, 1, 1, 1]
|
|
207
|
+
);
|
|
208
|
+
|
|
209
|
+
const calc = user.getMarginCalculation('Maintenance');
|
|
210
|
+
// From existing liquidation test expectations: 2_000_000
|
|
211
|
+
assert(calc.marginRequirement.eq(new BN('2000000')));
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
it.only('maker position reducing: collateral equals maintenance requirement', async () => {
|
|
215
|
+
const myMockPerpMarkets = _.cloneDeep(mockPerpMarkets);
|
|
216
|
+
const myMockSpotMarkets = _.cloneDeep(mockSpotMarkets);
|
|
217
|
+
const myMockUserAccount = _.cloneDeep(baseMockUserAccount);
|
|
218
|
+
|
|
219
|
+
myMockUserAccount.perpPositions[0].baseAssetAmount = new BN(200000000).mul(
|
|
220
|
+
BASE_PRECISION
|
|
221
|
+
);
|
|
222
|
+
myMockUserAccount.perpPositions[0].quoteAssetAmount = new BN(
|
|
223
|
+
-180000000
|
|
224
|
+
).mul(QUOTE_PRECISION);
|
|
225
|
+
|
|
226
|
+
const user: User = await makeMockUser(
|
|
227
|
+
myMockPerpMarkets,
|
|
228
|
+
myMockSpotMarkets,
|
|
229
|
+
myMockUserAccount,
|
|
230
|
+
[1, 1, 1, 1, 1, 1, 1, 1],
|
|
231
|
+
[1, 1, 1, 1, 1, 1, 1, 1]
|
|
232
|
+
);
|
|
233
|
+
|
|
234
|
+
const calc = user.getMarginCalculation('Maintenance');
|
|
235
|
+
console.log('calc.marginRequirement', calc.marginRequirement.toString());
|
|
236
|
+
console.log('calc.totalCollateral', calc.totalCollateral.toString());
|
|
237
|
+
assert(calc.marginRequirement.eq(calc.totalCollateral));
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
it('maker reducing after simulated fill: collateral equals maintenance requirement', async () => {
|
|
241
|
+
const myMockPerpMarkets = _.cloneDeep(mockPerpMarkets);
|
|
242
|
+
const myMockSpotMarkets = _.cloneDeep(mockSpotMarkets);
|
|
243
|
+
|
|
244
|
+
// Build maker and taker accounts
|
|
245
|
+
const makerAccount = _.cloneDeep(baseMockUserAccount);
|
|
246
|
+
const takerAccount = _.cloneDeep(baseMockUserAccount);
|
|
247
|
+
|
|
248
|
+
// Oracle price = 1 for perp and spot
|
|
249
|
+
const perpOracles = [1, 1, 1, 1, 1, 1, 1, 1];
|
|
250
|
+
const spotOracles = [1, 1, 1, 1, 1, 1, 1, 1];
|
|
251
|
+
|
|
252
|
+
// Pre-fill: maker has 21 base long at entry 1 ($21 notional), taker flat
|
|
253
|
+
makerAccount.perpPositions[0].baseAssetAmount = new BN(21).mul(
|
|
254
|
+
BASE_PRECISION
|
|
255
|
+
);
|
|
256
|
+
makerAccount.perpPositions[0].quoteEntryAmount = new BN(-21).mul(
|
|
257
|
+
QUOTE_PRECISION
|
|
258
|
+
);
|
|
259
|
+
makerAccount.perpPositions[0].quoteBreakEvenAmount = new BN(-21).mul(
|
|
260
|
+
QUOTE_PRECISION
|
|
261
|
+
);
|
|
262
|
+
// Provide exactly $2 in quote collateral to equal 10% maintenance of 20 notional post-fill
|
|
263
|
+
makerAccount.spotPositions[0].balanceType = SpotBalanceType.DEPOSIT;
|
|
264
|
+
makerAccount.spotPositions[0].scaledBalance = new BN(2).mul(
|
|
265
|
+
SPOT_MARKET_BALANCE_PRECISION
|
|
266
|
+
);
|
|
267
|
+
|
|
268
|
+
// Simulate fill: maker sells 1 base to taker at price = oracle = 1
|
|
269
|
+
// Post-fill maker position: 20 base long with zero unrealized PnL
|
|
270
|
+
const maker: User = await makeMockUser(
|
|
271
|
+
myMockPerpMarkets,
|
|
272
|
+
myMockSpotMarkets,
|
|
273
|
+
makerAccount,
|
|
274
|
+
perpOracles,
|
|
275
|
+
spotOracles
|
|
276
|
+
);
|
|
277
|
+
const taker: User = await makeMockUser(
|
|
278
|
+
myMockPerpMarkets,
|
|
279
|
+
myMockSpotMarkets,
|
|
280
|
+
takerAccount,
|
|
281
|
+
perpOracles,
|
|
282
|
+
spotOracles
|
|
283
|
+
);
|
|
284
|
+
|
|
285
|
+
// Apply synthetic trade deltas to both user accounts
|
|
286
|
+
// Maker: base 21 -> 20; taker: base 0 -> 1. Use quote deltas consistent with price 1, fee 0
|
|
287
|
+
maker.getUserAccount().perpPositions[0].baseAssetAmount = new BN(20).mul(
|
|
288
|
+
BASE_PRECISION
|
|
289
|
+
);
|
|
290
|
+
maker.getUserAccount().perpPositions[0].quoteEntryAmount = new BN(-20).mul(
|
|
291
|
+
QUOTE_PRECISION
|
|
292
|
+
);
|
|
293
|
+
maker.getUserAccount().perpPositions[0].quoteBreakEvenAmount = new BN(
|
|
294
|
+
-20
|
|
295
|
+
).mul(QUOTE_PRECISION);
|
|
296
|
+
// Align quoteAssetAmount with base value so unrealized PnL = 0 at price 1
|
|
297
|
+
maker.getUserAccount().perpPositions[0].quoteAssetAmount = new BN(-20).mul(
|
|
298
|
+
QUOTE_PRECISION
|
|
299
|
+
);
|
|
300
|
+
|
|
301
|
+
taker.getUserAccount().perpPositions[0].baseAssetAmount = new BN(1).mul(
|
|
302
|
+
BASE_PRECISION
|
|
303
|
+
);
|
|
304
|
+
taker.getUserAccount().perpPositions[0].quoteEntryAmount = new BN(-1).mul(
|
|
305
|
+
QUOTE_PRECISION
|
|
306
|
+
);
|
|
307
|
+
taker.getUserAccount().perpPositions[0].quoteBreakEvenAmount = new BN(
|
|
308
|
+
-1
|
|
309
|
+
).mul(QUOTE_PRECISION);
|
|
310
|
+
// Also set taker's quoteAssetAmount consistently
|
|
311
|
+
taker.getUserAccount().perpPositions[0].quoteAssetAmount = new BN(-1).mul(
|
|
312
|
+
QUOTE_PRECISION
|
|
313
|
+
);
|
|
314
|
+
|
|
315
|
+
const makerCalc = maker.getMarginCalculation('Maintenance');
|
|
316
|
+
assert(makerCalc.marginRequirement.eq(makerCalc.totalCollateral));
|
|
317
|
+
assert(makerCalc.marginRequirement.gt(ZERO));
|
|
318
|
+
});
|
|
319
|
+
|
|
320
|
+
it('isolated position margin requirement (SDK parity)', async () => {
|
|
321
|
+
const myMockPerpMarkets = _.cloneDeep(mockPerpMarkets);
|
|
322
|
+
const myMockSpotMarkets = _.cloneDeep(mockSpotMarkets);
|
|
323
|
+
myMockSpotMarkets[0].oracle = new PublicKey(2);
|
|
324
|
+
myMockSpotMarkets[1].oracle = new PublicKey(5);
|
|
325
|
+
myMockPerpMarkets[0].amm.oracle = new PublicKey(5);
|
|
326
|
+
|
|
327
|
+
// Configure perp market 0 ratios to match on-chain test
|
|
328
|
+
myMockPerpMarkets[0].marginRatioInitial = 1000; // 10%
|
|
329
|
+
myMockPerpMarkets[0].marginRatioMaintenance = 500; // 5%
|
|
330
|
+
|
|
331
|
+
// Configure spot market 1 (e.g., SOL) weights to match on-chain test
|
|
332
|
+
myMockSpotMarkets[1].initialAssetWeight =
|
|
333
|
+
(SPOT_MARKET_WEIGHT_PRECISION.toNumber() * 8) / 10; // 0.8
|
|
334
|
+
myMockSpotMarkets[1].maintenanceAssetWeight =
|
|
335
|
+
(SPOT_MARKET_WEIGHT_PRECISION.toNumber() * 9) / 10; // 0.9
|
|
336
|
+
myMockSpotMarkets[1].initialLiabilityWeight =
|
|
337
|
+
(SPOT_MARKET_WEIGHT_PRECISION.toNumber() * 12) / 10; // 1.2
|
|
338
|
+
myMockSpotMarkets[1].maintenanceLiabilityWeight =
|
|
339
|
+
(SPOT_MARKET_WEIGHT_PRECISION.toNumber() * 11) / 10; // 1.1
|
|
340
|
+
|
|
341
|
+
// ---------- Cross margin only (spot positions) ----------
|
|
342
|
+
const crossAccount = _.cloneDeep(baseMockUserAccount);
|
|
343
|
+
// USDC deposit: $20,000
|
|
344
|
+
crossAccount.spotPositions[0].marketIndex = 0;
|
|
345
|
+
crossAccount.spotPositions[0].balanceType = SpotBalanceType.DEPOSIT;
|
|
346
|
+
crossAccount.spotPositions[0].scaledBalance = new BN(20000).mul(
|
|
347
|
+
SPOT_MARKET_BALANCE_PRECISION
|
|
348
|
+
);
|
|
349
|
+
// SOL borrow: 100 units
|
|
350
|
+
crossAccount.spotPositions[1].marketIndex = 1;
|
|
351
|
+
crossAccount.spotPositions[1].balanceType = SpotBalanceType.BORROW;
|
|
352
|
+
crossAccount.spotPositions[1].scaledBalance = new BN(100).mul(
|
|
353
|
+
SPOT_MARKET_BALANCE_PRECISION
|
|
354
|
+
);
|
|
355
|
+
// No perp exposure in cross calc
|
|
356
|
+
crossAccount.perpPositions[0].baseAssetAmount = new BN(
|
|
357
|
+
100 * BASE_PRECISION.toNumber()
|
|
358
|
+
);
|
|
359
|
+
crossAccount.perpPositions[0].quoteAssetAmount = new BN(
|
|
360
|
+
-11000 * QUOTE_PRECISION.toNumber()
|
|
361
|
+
);
|
|
362
|
+
crossAccount.perpPositions[0].positionFlag = PositionFlag.IsolatedPosition;
|
|
363
|
+
crossAccount.perpPositions[0].isolatedPositionScaledBalance = new BN(
|
|
364
|
+
100
|
|
365
|
+
).mul(SPOT_MARKET_BALANCE_PRECISION);
|
|
366
|
+
|
|
367
|
+
const userCross: User = await makeMockUser(
|
|
368
|
+
myMockPerpMarkets,
|
|
369
|
+
myMockSpotMarkets,
|
|
370
|
+
crossAccount,
|
|
371
|
+
[100, 1, 1, 1, 1, 1, 1, 1], // perp oracle for market 0 = 100
|
|
372
|
+
[1, 100, 1, 1, 1, 1, 1, 1] // spot oracle: usdc=1, sol=100
|
|
373
|
+
);
|
|
374
|
+
|
|
375
|
+
const crossCalc = userCross.getMarginCalculation('Initial');
|
|
376
|
+
const isolatedMarginCalc = crossCalc.isolatedMarginCalculations.get(0);
|
|
377
|
+
// Expect: cross MR from SOL borrow: 100 * $100 = $10,000 * 1.2 = $12,000
|
|
378
|
+
assert(crossCalc.marginRequirement.eq(new BN('12000000000')));
|
|
379
|
+
// Expect: cross total collateral from USDC deposit only = $20,000
|
|
380
|
+
assert(crossCalc.totalCollateral.eq(new BN('20000000000')));
|
|
381
|
+
// Meets cross margin requirement
|
|
382
|
+
assert(crossCalc.marginRequirement.lte(crossCalc.totalCollateral));
|
|
383
|
+
|
|
384
|
+
assert(isolatedMarginCalc?.marginRequirement.eq(new BN('1000000000')));
|
|
385
|
+
assert(isolatedMarginCalc?.totalCollateral.eq(new BN('-900000000')));
|
|
386
|
+
// With 10% buffer
|
|
387
|
+
const tenPct = new BN(1000);
|
|
388
|
+
const crossCalcBuf = userCross.getMarginCalculation('Initial', {
|
|
389
|
+
liquidationBuffer: tenPct,
|
|
390
|
+
});
|
|
391
|
+
assert(crossCalcBuf.marginRequirementPlusBuffer.eq(new BN('13000000000'))); // replicate 10% buffer
|
|
392
|
+
const crossTotalPlusBuffer = crossCalcBuf.totalCollateral.add(
|
|
393
|
+
crossCalcBuf.totalCollateralBuffer
|
|
394
|
+
);
|
|
395
|
+
assert(crossTotalPlusBuffer.eq(new BN('20000000000')));
|
|
396
|
+
|
|
397
|
+
const isoPosition = crossCalcBuf.isolatedMarginCalculations.get(0);
|
|
398
|
+
assert(isoPosition?.marginRequirementPlusBuffer.eq(new BN('2000000000')));
|
|
399
|
+
assert(
|
|
400
|
+
isoPosition?.totalCollateralBuffer
|
|
401
|
+
.add(isoPosition?.totalCollateral)
|
|
402
|
+
.eq(new BN('-1000000000'))
|
|
403
|
+
);
|
|
404
|
+
});
|
|
405
|
+
});
|
package/tests/user/test.ts
CHANGED
|
@@ -49,7 +49,6 @@ async function makeMockUser(
|
|
|
49
49
|
oraclePriceMap[myMockSpotMarkets[i].oracle.toString()] =
|
|
50
50
|
spotOraclePriceList[i];
|
|
51
51
|
}
|
|
52
|
-
// console.log(oraclePriceMap);
|
|
53
52
|
|
|
54
53
|
function getMockUserAccount(): UserAccount {
|
|
55
54
|
return myMockUserAccount;
|
|
@@ -61,12 +60,6 @@ async function makeMockUser(
|
|
|
61
60
|
return myMockSpotMarkets[marketIndex];
|
|
62
61
|
}
|
|
63
62
|
function getMockOracle(oracleKey: PublicKey) {
|
|
64
|
-
// console.log('oracleKey.toString():', oracleKey.toString());
|
|
65
|
-
// console.log(
|
|
66
|
-
// 'oraclePriceMap[oracleKey.toString()]:',
|
|
67
|
-
// oraclePriceMap[oracleKey.toString()]
|
|
68
|
-
// );
|
|
69
|
-
|
|
70
63
|
const QUOTE_ORACLE_PRICE_DATA: OraclePriceData = {
|
|
71
64
|
price: new BN(
|
|
72
65
|
oraclePriceMap[oracleKey.toString()] * PRICE_PRECISION.toNumber()
|