@paraswap/dex-lib 2.34.0 → 2.35.0
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/build/abi/api3-proxy.json +46 -0
- package/build/abi/api3-server-v1.json +463 -0
- package/build/abi/quick-perps/fast-price-events.json +70 -0
- package/build/abi/quick-perps/fast-price-feed.json +741 -0
- package/build/abi/quick-perps/reader.json +313 -0
- package/build/abi/quick-perps/vault-price-feed.json +323 -0
- package/build/abi/quick-perps/vault.json +1953 -0
- package/build/composed-event-subscriber.js +8 -2
- package/build/composed-event-subscriber.js.map +1 -1
- package/build/config.js +2 -2
- package/build/dex/index.js +2 -0
- package/build/dex/index.js.map +1 -1
- package/build/dex/quick-perps/config.d.ts +11 -0
- package/build/dex/quick-perps/config.js +27 -0
- package/build/dex/quick-perps/config.js.map +1 -0
- package/build/dex/quick-perps/fast-price-feed.d.ts +26 -0
- package/build/dex/quick-perps/fast-price-feed.js +176 -0
- package/build/dex/quick-perps/fast-price-feed.js.map +1 -0
- package/build/dex/quick-perps/pool.d.ts +21 -0
- package/build/dex/quick-perps/pool.js +210 -0
- package/build/dex/quick-perps/pool.js.map +1 -0
- package/build/dex/quick-perps/quick-perps.d.ts +52 -0
- package/build/dex/quick-perps/quick-perps.js +205 -0
- package/build/dex/quick-perps/quick-perps.js.map +1 -0
- package/build/dex/quick-perps/types.d.ts +98 -0
- package/build/dex/quick-perps/types.js +3 -0
- package/build/dex/quick-perps/types.js.map +1 -0
- package/build/dex/quick-perps/usdq.d.ts +15 -0
- package/build/dex/quick-perps/usdq.js +61 -0
- package/build/dex/quick-perps/usdq.js.map +1 -0
- package/build/dex/quick-perps/vault-price-feed.d.ts +43 -0
- package/build/dex/quick-perps/vault-price-feed.js +194 -0
- package/build/dex/quick-perps/vault-price-feed.js.map +1 -0
- package/build/dex/quick-perps/vault-utils.d.ts +8 -0
- package/build/dex/quick-perps/vault-utils.js +41 -0
- package/build/dex/quick-perps/vault-utils.js.map +1 -0
- package/build/dex/quick-perps/vault.d.ts +46 -0
- package/build/dex/quick-perps/vault.js +166 -0
- package/build/dex/quick-perps/vault.js.map +1 -0
- package/build/lib/api3-feed.d.ts +26 -0
- package/build/lib/api3-feed.js +82 -0
- package/build/lib/api3-feed.js.map +1 -0
- package/package.json +1 -1
- package/src/abi/api3-proxy.json +46 -0
- package/src/abi/api3-server-v1.json +463 -0
- package/src/abi/quick-perps/fast-price-events.json +70 -0
- package/src/abi/quick-perps/fast-price-feed.json +741 -0
- package/src/abi/quick-perps/reader.json +313 -0
- package/src/abi/quick-perps/vault-price-feed.json +323 -0
- package/src/abi/quick-perps/vault.json +1953 -0
- package/src/composed-event-subscriber.ts +16 -11
- package/src/config.ts +2 -2
- package/src/dex/index.ts +2 -0
- package/src/dex/quick-perps/config.ts +31 -0
- package/src/dex/quick-perps/fast-price-feed.ts +279 -0
- package/src/dex/quick-perps/pool.ts +372 -0
- package/src/dex/quick-perps/quick-perps-e2e.test.ts +65 -0
- package/src/dex/quick-perps/quick-perps-events.test.ts +101 -0
- package/src/dex/quick-perps/quick-perps-integration.test.ts +153 -0
- package/src/dex/quick-perps/quick-perps.ts +292 -0
- package/src/dex/quick-perps/types.ts +97 -0
- package/src/dex/quick-perps/usdq.ts +83 -0
- package/src/dex/quick-perps/vault-price-feed.ts +322 -0
- package/src/dex/quick-perps/vault-utils.ts +53 -0
- package/src/dex/quick-perps/vault.ts +290 -0
- package/src/lib/api3-feed.ts +140 -0
- package/tests/constants-e2e.ts +29 -0
- package/tests/tenderly-simulation.ts +46 -2
- package/tests/utils-e2e.ts +18 -10
|
@@ -0,0 +1,322 @@
|
|
|
1
|
+
import { Interface } from '@ethersproject/abi';
|
|
2
|
+
import { Address, MultiCallInput, MultiCallOutput } from '../../types';
|
|
3
|
+
import { VaultPriceFeedConfig } from './types';
|
|
4
|
+
import { FastPriceFeed } from './fast-price-feed';
|
|
5
|
+
import VaultPriceFeedAbi from '../../abi/quick-perps/vault-price-feed.json';
|
|
6
|
+
import { DeepReadonly } from 'ts-essentials';
|
|
7
|
+
import { Api3FeedSubscriber } from '../../lib/api3-feed';
|
|
8
|
+
|
|
9
|
+
export class VaultPriceFeed<State> {
|
|
10
|
+
BASIS_POINTS_DIVISOR = 10000n;
|
|
11
|
+
PRICE_PRECISION = 10n ** 30n;
|
|
12
|
+
ONE_USD = this.PRICE_PRECISION;
|
|
13
|
+
|
|
14
|
+
static interface = new Interface(VaultPriceFeedAbi);
|
|
15
|
+
|
|
16
|
+
protected isSecondaryPriceEnabled: boolean;
|
|
17
|
+
protected strictStableTokens: { [address: string]: boolean };
|
|
18
|
+
protected spreadBasisPoints: { [address: string]: bigint };
|
|
19
|
+
protected adjustmentBasisPoints: { [address: string]: bigint };
|
|
20
|
+
protected isAdjustmentAdditive: { [address: string]: boolean };
|
|
21
|
+
protected priceDecimals: { [address: string]: number };
|
|
22
|
+
protected maxStrictPriceDeviation: bigint;
|
|
23
|
+
|
|
24
|
+
constructor(
|
|
25
|
+
config: VaultPriceFeedConfig,
|
|
26
|
+
protected primaryPrices: { [token: string]: Api3FeedSubscriber<State> },
|
|
27
|
+
protected secondaryPrice: FastPriceFeed<State>,
|
|
28
|
+
) {
|
|
29
|
+
this.isSecondaryPriceEnabled = config.isSecondaryPriceEnabled;
|
|
30
|
+
this.strictStableTokens = config.strictStableTokens;
|
|
31
|
+
this.spreadBasisPoints = config.spreadBasisPoints;
|
|
32
|
+
this.adjustmentBasisPoints = config.adjustmentBasisPoints;
|
|
33
|
+
this.isAdjustmentAdditive = config.isAdjustmentAdditive;
|
|
34
|
+
this.priceDecimals = config.priceDecimals;
|
|
35
|
+
this.maxStrictPriceDeviation = config.maxStrictPriceDeviation;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
getPrice(
|
|
39
|
+
state: DeepReadonly<State>,
|
|
40
|
+
_token: Address,
|
|
41
|
+
_maximize: boolean,
|
|
42
|
+
_includeAmmPrice: boolean,
|
|
43
|
+
_useSwapPricing: boolean,
|
|
44
|
+
): bigint {
|
|
45
|
+
let price = this.getPriceV1(state, _token, _maximize, _includeAmmPrice);
|
|
46
|
+
|
|
47
|
+
const adjustmentBps = this.adjustmentBasisPoints[_token];
|
|
48
|
+
if (adjustmentBps > 0n) {
|
|
49
|
+
const isAdditive = this.isAdjustmentAdditive[_token];
|
|
50
|
+
if (isAdditive) {
|
|
51
|
+
price =
|
|
52
|
+
(price * (this.BASIS_POINTS_DIVISOR + adjustmentBps)) /
|
|
53
|
+
this.BASIS_POINTS_DIVISOR;
|
|
54
|
+
} else {
|
|
55
|
+
price =
|
|
56
|
+
(price * (this.BASIS_POINTS_DIVISOR - adjustmentBps)) /
|
|
57
|
+
this.BASIS_POINTS_DIVISOR;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return price;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
getPriceV1(
|
|
65
|
+
state: DeepReadonly<State>,
|
|
66
|
+
_token: Address,
|
|
67
|
+
_maximize: boolean,
|
|
68
|
+
_includeAmmPrice: boolean,
|
|
69
|
+
): bigint {
|
|
70
|
+
let price = this.getPrimaryPrice(state, _token, _maximize);
|
|
71
|
+
|
|
72
|
+
if (this.isSecondaryPriceEnabled) {
|
|
73
|
+
price = this.getSecondaryPrice(state, _token, price, _maximize);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
if (this.strictStableTokens[_token]) {
|
|
77
|
+
const delta =
|
|
78
|
+
price > this.ONE_USD ? price - this.ONE_USD : this.ONE_USD - price;
|
|
79
|
+
if (delta <= this.maxStrictPriceDeviation) {
|
|
80
|
+
return this.ONE_USD;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// if _maximize and price is e.g. 1.02, return 1.02
|
|
84
|
+
if (_maximize && price > this.ONE_USD) {
|
|
85
|
+
return price;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// if !_maximize and price is e.g. 0.98, return 0.98
|
|
89
|
+
if (!_maximize && price < this.ONE_USD) {
|
|
90
|
+
return price;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
return this.ONE_USD;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const _spreadBasisPoints = this.spreadBasisPoints[_token];
|
|
97
|
+
|
|
98
|
+
if (_maximize) {
|
|
99
|
+
return (
|
|
100
|
+
(price * (this.BASIS_POINTS_DIVISOR + _spreadBasisPoints)) /
|
|
101
|
+
this.BASIS_POINTS_DIVISOR
|
|
102
|
+
);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
return (
|
|
106
|
+
(price * (this.BASIS_POINTS_DIVISOR - _spreadBasisPoints)) /
|
|
107
|
+
this.BASIS_POINTS_DIVISOR
|
|
108
|
+
);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
getAmmPrice(state: DeepReadonly<State>, token: Address): bigint {
|
|
112
|
+
throw new Error(
|
|
113
|
+
'getAmmPrice implementation is not complete, developers should disable the dex or complete the implementation',
|
|
114
|
+
);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
getPrimaryPrice(
|
|
118
|
+
state: DeepReadonly<State>,
|
|
119
|
+
_token: Address,
|
|
120
|
+
_maximize: boolean,
|
|
121
|
+
): bigint {
|
|
122
|
+
// const priceFeedAddress = this.priceFeeds[_token];
|
|
123
|
+
// require(priceFeedAddress != address(0), "VaultPriceFeed: invalid price feed");
|
|
124
|
+
|
|
125
|
+
// if (chainlinkFlags != address(0)) {
|
|
126
|
+
// bool isRaised = IChainlinkFlags(chainlinkFlags).getFlag(FLAG_ARBITRUM_SEQ_OFFLINE);
|
|
127
|
+
// if (isRaised) {
|
|
128
|
+
// // If flag is raised we shouldn't perform any critical operations
|
|
129
|
+
// revert("Chainlink feeds are not being updated");
|
|
130
|
+
// }
|
|
131
|
+
// }
|
|
132
|
+
|
|
133
|
+
// IPriceFeed priceFeed = IPriceFeed(priceFeedAddress);
|
|
134
|
+
let price = 0n;
|
|
135
|
+
// const roundId = priceFeed.latestRound;
|
|
136
|
+
|
|
137
|
+
// for (let i = 0; i < this.priceSampleSpace; i++) {
|
|
138
|
+
// if (roundId <= i) {
|
|
139
|
+
// break;
|
|
140
|
+
// }
|
|
141
|
+
|
|
142
|
+
// if (i == 0) {
|
|
143
|
+
// const _p = priceFeed.latestAnswer();
|
|
144
|
+
// require(_p > 0, "VaultPriceFeed: invalid price");
|
|
145
|
+
// p = uint256(_p);
|
|
146
|
+
// } else {
|
|
147
|
+
// (, int256 _p, , ,) = priceFeed.getRoundData(roundId - i);
|
|
148
|
+
// require(_p > 0, "VaultPriceFeed: invalid price");
|
|
149
|
+
// p = uint256(_p);
|
|
150
|
+
// }
|
|
151
|
+
|
|
152
|
+
// if (price == 0n) {
|
|
153
|
+
// price = p;
|
|
154
|
+
// continue;
|
|
155
|
+
// }
|
|
156
|
+
|
|
157
|
+
// if (_maximize && p > price) {
|
|
158
|
+
// price = p;
|
|
159
|
+
// continue;
|
|
160
|
+
// }
|
|
161
|
+
|
|
162
|
+
// if (!_maximize && p < price) {
|
|
163
|
+
// price = p;
|
|
164
|
+
// }
|
|
165
|
+
// }
|
|
166
|
+
price = this.primaryPrices[_token].getLatestData(state);
|
|
167
|
+
|
|
168
|
+
// require(price > 0n, "VaultPriceFeed: could not fetch price");
|
|
169
|
+
if (price <= 0n) throw new Error('VaultPriceFeed: could not fetch price');
|
|
170
|
+
// normalize price precision
|
|
171
|
+
const _priceDecimals = this.priceDecimals[_token];
|
|
172
|
+
return (price * this.PRICE_PRECISION) / BigInt(10 ** _priceDecimals);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
getSecondaryPrice(
|
|
176
|
+
state: DeepReadonly<State>,
|
|
177
|
+
_token: Address,
|
|
178
|
+
_referencePrice: bigint,
|
|
179
|
+
_maximize: boolean,
|
|
180
|
+
): bigint {
|
|
181
|
+
return this.secondaryPrice.getPrice(
|
|
182
|
+
state,
|
|
183
|
+
_token,
|
|
184
|
+
_referencePrice,
|
|
185
|
+
_maximize,
|
|
186
|
+
);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
static getConfigMulticallInputs(
|
|
190
|
+
vaultPriceFeedAddress: Address,
|
|
191
|
+
tokenAddresses: Address[],
|
|
192
|
+
): MultiCallInput[] {
|
|
193
|
+
return [
|
|
194
|
+
{
|
|
195
|
+
target: vaultPriceFeedAddress,
|
|
196
|
+
callData: VaultPriceFeed.interface.encodeFunctionData(
|
|
197
|
+
'isSecondaryPriceEnabled',
|
|
198
|
+
),
|
|
199
|
+
},
|
|
200
|
+
...tokenAddresses.map(t => ({
|
|
201
|
+
target: vaultPriceFeedAddress,
|
|
202
|
+
callData: VaultPriceFeed.interface.encodeFunctionData(
|
|
203
|
+
'strictStableTokens',
|
|
204
|
+
[t],
|
|
205
|
+
),
|
|
206
|
+
})),
|
|
207
|
+
...tokenAddresses.map(t => ({
|
|
208
|
+
target: vaultPriceFeedAddress,
|
|
209
|
+
callData: VaultPriceFeed.interface.encodeFunctionData(
|
|
210
|
+
'spreadBasisPoints',
|
|
211
|
+
[t],
|
|
212
|
+
),
|
|
213
|
+
})),
|
|
214
|
+
...tokenAddresses.map(t => ({
|
|
215
|
+
target: vaultPriceFeedAddress,
|
|
216
|
+
callData: VaultPriceFeed.interface.encodeFunctionData(
|
|
217
|
+
'isAdjustmentAdditive',
|
|
218
|
+
[t],
|
|
219
|
+
),
|
|
220
|
+
})),
|
|
221
|
+
...tokenAddresses.map(t => ({
|
|
222
|
+
target: vaultPriceFeedAddress,
|
|
223
|
+
callData: VaultPriceFeed.interface.encodeFunctionData(
|
|
224
|
+
'adjustmentBasisPoints',
|
|
225
|
+
[t],
|
|
226
|
+
),
|
|
227
|
+
})),
|
|
228
|
+
...tokenAddresses.map(t => ({
|
|
229
|
+
target: vaultPriceFeedAddress,
|
|
230
|
+
callData: VaultPriceFeed.interface.encodeFunctionData('priceDecimals', [
|
|
231
|
+
t,
|
|
232
|
+
]),
|
|
233
|
+
})),
|
|
234
|
+
{
|
|
235
|
+
target: vaultPriceFeedAddress,
|
|
236
|
+
callData: VaultPriceFeed.interface.encodeFunctionData(
|
|
237
|
+
'maxStrictPriceDeviation',
|
|
238
|
+
),
|
|
239
|
+
},
|
|
240
|
+
];
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
static getConfig(
|
|
244
|
+
multicallOutputs: MultiCallOutput[],
|
|
245
|
+
tokenAddress: Address[],
|
|
246
|
+
): VaultPriceFeedConfig {
|
|
247
|
+
let i = 0;
|
|
248
|
+
return {
|
|
249
|
+
isSecondaryPriceEnabled: VaultPriceFeed.interface.decodeFunctionResult(
|
|
250
|
+
'isSecondaryPriceEnabled',
|
|
251
|
+
multicallOutputs[i++],
|
|
252
|
+
)[0],
|
|
253
|
+
strictStableTokens: tokenAddress.reduce(
|
|
254
|
+
(acc: { [address: string]: boolean }, t: Address) => {
|
|
255
|
+
acc[t] = VaultPriceFeed.interface.decodeFunctionResult(
|
|
256
|
+
'strictStableTokens',
|
|
257
|
+
multicallOutputs[i++],
|
|
258
|
+
)[0];
|
|
259
|
+
return acc;
|
|
260
|
+
},
|
|
261
|
+
{},
|
|
262
|
+
),
|
|
263
|
+
spreadBasisPoints: tokenAddress.reduce(
|
|
264
|
+
(acc: { [address: string]: bigint }, t: Address) => {
|
|
265
|
+
acc[t] = BigInt(
|
|
266
|
+
VaultPriceFeed.interface
|
|
267
|
+
.decodeFunctionResult(
|
|
268
|
+
'spreadBasisPoints',
|
|
269
|
+
multicallOutputs[i++],
|
|
270
|
+
)[0]
|
|
271
|
+
.toString(),
|
|
272
|
+
);
|
|
273
|
+
return acc;
|
|
274
|
+
},
|
|
275
|
+
{},
|
|
276
|
+
),
|
|
277
|
+
isAdjustmentAdditive: tokenAddress.reduce(
|
|
278
|
+
(acc: { [address: string]: boolean }, t: Address) => {
|
|
279
|
+
acc[t] = VaultPriceFeed.interface.decodeFunctionResult(
|
|
280
|
+
'isAdjustmentAdditive',
|
|
281
|
+
multicallOutputs[i++],
|
|
282
|
+
)[0];
|
|
283
|
+
return acc;
|
|
284
|
+
},
|
|
285
|
+
{},
|
|
286
|
+
),
|
|
287
|
+
adjustmentBasisPoints: tokenAddress.reduce(
|
|
288
|
+
(acc: { [address: string]: bigint }, t: Address) => {
|
|
289
|
+
acc[t] = BigInt(
|
|
290
|
+
VaultPriceFeed.interface
|
|
291
|
+
.decodeFunctionResult(
|
|
292
|
+
'adjustmentBasisPoints',
|
|
293
|
+
multicallOutputs[i++],
|
|
294
|
+
)[0]
|
|
295
|
+
.toString(),
|
|
296
|
+
);
|
|
297
|
+
return acc;
|
|
298
|
+
},
|
|
299
|
+
{},
|
|
300
|
+
),
|
|
301
|
+
priceDecimals: tokenAddress.reduce(
|
|
302
|
+
(acc: { [address: string]: number }, t: Address) => {
|
|
303
|
+
acc[t] = parseInt(
|
|
304
|
+
VaultPriceFeed.interface
|
|
305
|
+
.decodeFunctionResult('priceDecimals', multicallOutputs[i++])[0]
|
|
306
|
+
.toString(),
|
|
307
|
+
);
|
|
308
|
+
return acc;
|
|
309
|
+
},
|
|
310
|
+
{},
|
|
311
|
+
),
|
|
312
|
+
maxStrictPriceDeviation: BigInt(
|
|
313
|
+
VaultPriceFeed.interface
|
|
314
|
+
.decodeFunctionResult(
|
|
315
|
+
'maxStrictPriceDeviation',
|
|
316
|
+
multicallOutputs[i++],
|
|
317
|
+
)[0]
|
|
318
|
+
.toString(),
|
|
319
|
+
),
|
|
320
|
+
};
|
|
321
|
+
}
|
|
322
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { Vault } from './vault';
|
|
2
|
+
import { Address } from '../../types';
|
|
3
|
+
import { DeepReadonly } from 'ts-essentials';
|
|
4
|
+
|
|
5
|
+
export class VaultUtils<State> {
|
|
6
|
+
constructor(protected vault: Vault<State>) {}
|
|
7
|
+
|
|
8
|
+
getFeeBasisPoints(
|
|
9
|
+
state: DeepReadonly<State>,
|
|
10
|
+
_token: Address,
|
|
11
|
+
_usdqDelta: bigint,
|
|
12
|
+
_feeBasisPoints: bigint,
|
|
13
|
+
_taxBasisPoints: bigint,
|
|
14
|
+
_increment: boolean,
|
|
15
|
+
) {
|
|
16
|
+
if (!this.vault.hasDynamicFees) {
|
|
17
|
+
return _feeBasisPoints;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const initialAmount = this.vault.getUSDQAmount(state, _token);
|
|
21
|
+
let nextAmount = initialAmount + _usdqDelta;
|
|
22
|
+
if (!_increment) {
|
|
23
|
+
nextAmount = _usdqDelta > initialAmount ? 0n : initialAmount - _usdqDelta;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const targetAmount = this.vault.getTargetUsdqAmount(state, _token);
|
|
27
|
+
if (targetAmount == 0n) {
|
|
28
|
+
return _feeBasisPoints;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const initialDiff =
|
|
32
|
+
initialAmount > targetAmount
|
|
33
|
+
? initialAmount - targetAmount
|
|
34
|
+
: targetAmount - initialAmount;
|
|
35
|
+
const nextDiff =
|
|
36
|
+
nextAmount > targetAmount
|
|
37
|
+
? nextAmount - targetAmount
|
|
38
|
+
: targetAmount - nextAmount;
|
|
39
|
+
|
|
40
|
+
// action improves relative asset balance
|
|
41
|
+
if (nextDiff < initialDiff) {
|
|
42
|
+
const rebateBps = (_taxBasisPoints * initialDiff) / targetAmount;
|
|
43
|
+
return rebateBps > _feeBasisPoints ? 0n : _feeBasisPoints - rebateBps;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
let averageDiff = (initialDiff + nextDiff) / 2n;
|
|
47
|
+
if (averageDiff > targetAmount) {
|
|
48
|
+
averageDiff = targetAmount;
|
|
49
|
+
}
|
|
50
|
+
const taxBps = (_taxBasisPoints * averageDiff) / targetAmount;
|
|
51
|
+
return _feeBasisPoints + taxBps;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
@@ -0,0 +1,290 @@
|
|
|
1
|
+
import _ from 'lodash';
|
|
2
|
+
import { Interface } from '@ethersproject/abi';
|
|
3
|
+
import { AsyncOrSync, DeepReadonly } from 'ts-essentials';
|
|
4
|
+
import { PartialEventSubscriber } from '../../composed-event-subscriber';
|
|
5
|
+
import { Lens } from '../../lens';
|
|
6
|
+
import VaultABI from '../../abi/quick-perps/vault.json';
|
|
7
|
+
import { VaultUtils } from './vault-utils';
|
|
8
|
+
import {
|
|
9
|
+
VaultConfig,
|
|
10
|
+
VaultState,
|
|
11
|
+
FastPriceFeedConfig,
|
|
12
|
+
PoolState,
|
|
13
|
+
} from './types';
|
|
14
|
+
import { VaultPriceFeed } from './vault-price-feed';
|
|
15
|
+
import { USDQ } from './usdq';
|
|
16
|
+
import {
|
|
17
|
+
MultiCallInput,
|
|
18
|
+
MultiCallOutput,
|
|
19
|
+
Address,
|
|
20
|
+
Logger,
|
|
21
|
+
Log,
|
|
22
|
+
} from '../../types';
|
|
23
|
+
import { BlockHeader } from 'web3-eth';
|
|
24
|
+
|
|
25
|
+
export class Vault<State> extends PartialEventSubscriber<State, VaultState> {
|
|
26
|
+
static readonly interface: Interface = new Interface(VaultABI);
|
|
27
|
+
|
|
28
|
+
protected vaultUtils: VaultUtils<State>;
|
|
29
|
+
|
|
30
|
+
public tokenDecimals: { [address: string]: number };
|
|
31
|
+
public stableTokens: { [address: string]: boolean };
|
|
32
|
+
protected tokenWeights: { [address: string]: bigint };
|
|
33
|
+
public stableSwapFeeBasisPoints: bigint;
|
|
34
|
+
public swapFeeBasisPoints: bigint;
|
|
35
|
+
public stableTaxBasisPoints: bigint;
|
|
36
|
+
public taxBasisPoints: bigint;
|
|
37
|
+
public hasDynamicFees: bigint;
|
|
38
|
+
protected includeAmmPrice: boolean;
|
|
39
|
+
protected useSwapPricing: boolean;
|
|
40
|
+
protected totalTokenWeights: bigint;
|
|
41
|
+
|
|
42
|
+
constructor(
|
|
43
|
+
public readonly vaultAddress: Address,
|
|
44
|
+
protected tokenAddresses: Address[],
|
|
45
|
+
config: VaultConfig,
|
|
46
|
+
protected vaultPriceFeed: VaultPriceFeed<State>,
|
|
47
|
+
protected usdq: USDQ<State>,
|
|
48
|
+
lens: Lens<DeepReadonly<State>, DeepReadonly<VaultState>>,
|
|
49
|
+
logger: Logger,
|
|
50
|
+
) {
|
|
51
|
+
super([vaultAddress], lens, logger);
|
|
52
|
+
this.vaultUtils = new VaultUtils(this);
|
|
53
|
+
this.tokenDecimals = config.tokenDecimals;
|
|
54
|
+
this.stableTokens = config.stableTokens;
|
|
55
|
+
this.tokenWeights = config.tokenWeights;
|
|
56
|
+
this.stableSwapFeeBasisPoints = config.stableSwapFeeBasisPoints;
|
|
57
|
+
this.swapFeeBasisPoints = config.swapFeeBasisPoints;
|
|
58
|
+
this.stableTaxBasisPoints = config.stableTaxBasisPoints;
|
|
59
|
+
this.taxBasisPoints = config.taxBasisPoints;
|
|
60
|
+
this.hasDynamicFees = config.hasDynamicFees;
|
|
61
|
+
this.includeAmmPrice = config.includeAmmPrice;
|
|
62
|
+
this.useSwapPricing = config.useSwapPricing;
|
|
63
|
+
this.totalTokenWeights = config.totalTokenWeights;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
getMinPrice(state: DeepReadonly<State>, _token: Address): bigint {
|
|
67
|
+
return this.vaultPriceFeed.getPrice(
|
|
68
|
+
state,
|
|
69
|
+
_token,
|
|
70
|
+
false,
|
|
71
|
+
this.includeAmmPrice,
|
|
72
|
+
this.useSwapPricing,
|
|
73
|
+
);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
getMaxPrice(state: DeepReadonly<State>, _token: Address): bigint {
|
|
77
|
+
return this.vaultPriceFeed.getPrice(
|
|
78
|
+
state,
|
|
79
|
+
_token,
|
|
80
|
+
true,
|
|
81
|
+
this.includeAmmPrice,
|
|
82
|
+
this.useSwapPricing,
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
getFeeBasisPoints(
|
|
87
|
+
state: DeepReadonly<State>,
|
|
88
|
+
_token: Address,
|
|
89
|
+
_usdqDelta: bigint,
|
|
90
|
+
_feeBasisPoints: bigint,
|
|
91
|
+
_taxBasisPoints: bigint,
|
|
92
|
+
_increment: boolean,
|
|
93
|
+
): bigint {
|
|
94
|
+
return this.vaultUtils.getFeeBasisPoints(
|
|
95
|
+
state,
|
|
96
|
+
_token,
|
|
97
|
+
_usdqDelta,
|
|
98
|
+
_feeBasisPoints,
|
|
99
|
+
_taxBasisPoints,
|
|
100
|
+
_increment,
|
|
101
|
+
);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
getTargetUsdqAmount(state: DeepReadonly<State>, _token: Address): bigint {
|
|
105
|
+
const supply = this.usdq.getTotalSupply(state);
|
|
106
|
+
if (supply == 0n) {
|
|
107
|
+
return 0n;
|
|
108
|
+
}
|
|
109
|
+
const weight = this.tokenWeights[_token];
|
|
110
|
+
return (weight * supply) / this.totalTokenWeights;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
static getConfigMulticallInputs(
|
|
114
|
+
vaultAddress: Address,
|
|
115
|
+
tokenAddresses: Address[],
|
|
116
|
+
): MultiCallInput[] {
|
|
117
|
+
return [
|
|
118
|
+
...tokenAddresses.map(t => ({
|
|
119
|
+
target: vaultAddress,
|
|
120
|
+
callData: Vault.interface.encodeFunctionData('tokenDecimals', [t]),
|
|
121
|
+
})),
|
|
122
|
+
...tokenAddresses.map(t => ({
|
|
123
|
+
target: vaultAddress,
|
|
124
|
+
callData: Vault.interface.encodeFunctionData('stableTokens', [t]),
|
|
125
|
+
})),
|
|
126
|
+
...tokenAddresses.map(t => ({
|
|
127
|
+
target: vaultAddress,
|
|
128
|
+
callData: Vault.interface.encodeFunctionData('tokenWeights', [t]),
|
|
129
|
+
})),
|
|
130
|
+
...[
|
|
131
|
+
'stableSwapFeeBasisPoints',
|
|
132
|
+
'swapFeeBasisPoints',
|
|
133
|
+
'stableTaxBasisPoints',
|
|
134
|
+
'taxBasisPoints',
|
|
135
|
+
'hasDynamicFees',
|
|
136
|
+
'includeAmmPrice',
|
|
137
|
+
'useSwapPricing',
|
|
138
|
+
'totalTokenWeights',
|
|
139
|
+
].map(fn => ({
|
|
140
|
+
target: vaultAddress,
|
|
141
|
+
callData: Vault.interface.encodeFunctionData(fn),
|
|
142
|
+
})),
|
|
143
|
+
];
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
static getConfig(
|
|
147
|
+
multicallOutputs: MultiCallOutput[],
|
|
148
|
+
tokenAddresses: Address[],
|
|
149
|
+
): VaultConfig {
|
|
150
|
+
let i = 0;
|
|
151
|
+
return {
|
|
152
|
+
tokenDecimals: tokenAddresses.reduce(
|
|
153
|
+
(acc: { [address: string]: number }, t: Address) => {
|
|
154
|
+
acc[t] = parseInt(
|
|
155
|
+
Vault.interface
|
|
156
|
+
.decodeFunctionResult('tokenDecimals', multicallOutputs[i++])[0]
|
|
157
|
+
.toString(),
|
|
158
|
+
);
|
|
159
|
+
return acc;
|
|
160
|
+
},
|
|
161
|
+
{},
|
|
162
|
+
),
|
|
163
|
+
stableTokens: tokenAddresses.reduce(
|
|
164
|
+
(acc: { [address: string]: boolean }, t: Address) => {
|
|
165
|
+
acc[t] = Vault.interface.decodeFunctionResult(
|
|
166
|
+
'stableTokens',
|
|
167
|
+
multicallOutputs[i++],
|
|
168
|
+
)[0];
|
|
169
|
+
return acc;
|
|
170
|
+
},
|
|
171
|
+
{},
|
|
172
|
+
),
|
|
173
|
+
tokenWeights: tokenAddresses.reduce(
|
|
174
|
+
(acc: { [address: string]: bigint }, t: Address) => {
|
|
175
|
+
acc[t] = BigInt(
|
|
176
|
+
Vault.interface
|
|
177
|
+
.decodeFunctionResult('tokenWeights', multicallOutputs[i++])[0]
|
|
178
|
+
.toString(),
|
|
179
|
+
);
|
|
180
|
+
return acc;
|
|
181
|
+
},
|
|
182
|
+
{},
|
|
183
|
+
),
|
|
184
|
+
stableSwapFeeBasisPoints: BigInt(
|
|
185
|
+
Vault.interface
|
|
186
|
+
.decodeFunctionResult(
|
|
187
|
+
'stableSwapFeeBasisPoints',
|
|
188
|
+
multicallOutputs[i++],
|
|
189
|
+
)[0]
|
|
190
|
+
.toString(),
|
|
191
|
+
),
|
|
192
|
+
swapFeeBasisPoints: BigInt(
|
|
193
|
+
Vault.interface
|
|
194
|
+
.decodeFunctionResult('swapFeeBasisPoints', multicallOutputs[i++])[0]
|
|
195
|
+
.toString(),
|
|
196
|
+
),
|
|
197
|
+
stableTaxBasisPoints: BigInt(
|
|
198
|
+
Vault.interface
|
|
199
|
+
.decodeFunctionResult(
|
|
200
|
+
'stableTaxBasisPoints',
|
|
201
|
+
multicallOutputs[i++],
|
|
202
|
+
)[0]
|
|
203
|
+
.toString(),
|
|
204
|
+
),
|
|
205
|
+
taxBasisPoints: BigInt(
|
|
206
|
+
Vault.interface
|
|
207
|
+
.decodeFunctionResult('taxBasisPoints', multicallOutputs[i++])[0]
|
|
208
|
+
.toString(),
|
|
209
|
+
),
|
|
210
|
+
hasDynamicFees: Vault.interface.decodeFunctionResult(
|
|
211
|
+
'hasDynamicFees',
|
|
212
|
+
multicallOutputs[i++],
|
|
213
|
+
)[0],
|
|
214
|
+
includeAmmPrice: Vault.interface.decodeFunctionResult(
|
|
215
|
+
'includeAmmPrice',
|
|
216
|
+
multicallOutputs[i++],
|
|
217
|
+
)[0],
|
|
218
|
+
useSwapPricing: Vault.interface.decodeFunctionResult(
|
|
219
|
+
'useSwapPricing',
|
|
220
|
+
multicallOutputs[i++],
|
|
221
|
+
)[0],
|
|
222
|
+
totalTokenWeights: BigInt(
|
|
223
|
+
Vault.interface
|
|
224
|
+
.decodeFunctionResult('totalTokenWeights', multicallOutputs[i++])[0]
|
|
225
|
+
.toString(),
|
|
226
|
+
),
|
|
227
|
+
};
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
public getGenerateStateMultiCallInputs(): MultiCallInput[] {
|
|
231
|
+
return this.tokenAddresses.map((t: Address) => ({
|
|
232
|
+
target: this.vaultAddress,
|
|
233
|
+
callData: Vault.interface.encodeFunctionData('usdqAmounts', [t]),
|
|
234
|
+
}));
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
public generateState(
|
|
238
|
+
multicallOutputs: MultiCallOutput[],
|
|
239
|
+
blockNumber?: number | 'latest',
|
|
240
|
+
): DeepReadonly<VaultState> {
|
|
241
|
+
let vaultState: VaultState = {
|
|
242
|
+
usdqAmounts: {},
|
|
243
|
+
};
|
|
244
|
+
this.tokenAddresses.forEach(
|
|
245
|
+
(t: Address, i: number) =>
|
|
246
|
+
(vaultState.usdqAmounts[t] = BigInt(
|
|
247
|
+
Vault.interface
|
|
248
|
+
.decodeFunctionResult('usdqAmounts', multicallOutputs[i])[0]
|
|
249
|
+
.toString(),
|
|
250
|
+
)),
|
|
251
|
+
);
|
|
252
|
+
return vaultState;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
public getUSDQAmount(state: DeepReadonly<State>, token: Address): bigint {
|
|
256
|
+
return this.lens.get()(state).usdqAmounts[token];
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
public processLog(
|
|
260
|
+
state: DeepReadonly<VaultState>,
|
|
261
|
+
log: Readonly<Log>,
|
|
262
|
+
blockHeader: Readonly<BlockHeader>,
|
|
263
|
+
): AsyncOrSync<VaultState | null> {
|
|
264
|
+
try {
|
|
265
|
+
const parsed = Vault.interface.parseLog(log);
|
|
266
|
+
const _state: VaultState = _.cloneDeep(state);
|
|
267
|
+
switch (parsed.name) {
|
|
268
|
+
case 'IncreaseUsdqAmount': {
|
|
269
|
+
const tokenAddress = parsed.args.token.toLowerCase();
|
|
270
|
+
const amount = BigInt(parsed.args.amount.toString());
|
|
271
|
+
if (tokenAddress in state.usdqAmounts)
|
|
272
|
+
_state.usdqAmounts[tokenAddress] += amount;
|
|
273
|
+
return _state;
|
|
274
|
+
}
|
|
275
|
+
case 'DecreaseUsdqAmount': {
|
|
276
|
+
const tokenAddress = parsed.args.token.toLowerCase();
|
|
277
|
+
const amount = BigInt(parsed.args.amount.toString());
|
|
278
|
+
if (tokenAddress in state.usdqAmounts)
|
|
279
|
+
_state.usdqAmounts[tokenAddress] -= amount;
|
|
280
|
+
return _state;
|
|
281
|
+
}
|
|
282
|
+
default:
|
|
283
|
+
return null;
|
|
284
|
+
}
|
|
285
|
+
} catch (e) {
|
|
286
|
+
this.logger.error('Failed to parse log', e);
|
|
287
|
+
return null;
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
}
|