@paraswap/dex-lib 3.4.2-balancer-v2-single-swap.0 → 3.4.2
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/README.md +377 -377
- package/build/config.js +9 -5
- package/build/config.js.map +1 -1
- package/build/dex/algebra/algebra.js +25 -25
- package/build/dex/balancer-v1/balancer-v1.js +34 -34
- package/build/dex/balancer-v2/balancer-v2.d.ts +1 -1
- package/build/dex/balancer-v2/balancer-v2.js +65 -78
- package/build/dex/balancer-v2/balancer-v2.js.map +1 -1
- package/build/dex/camelot/camelot.js +27 -27
- package/build/dex/kyberdmm/kyberdmm.js +27 -27
- package/build/dex/pancakeswap-v3/pancakeswap-v3.js +25 -25
- package/build/dex/solidly/solidly.js +27 -27
- package/build/dex/solidly-v3/solidly-v3.js +25 -25
- package/build/dex/swaap-v2/config.js +15 -0
- package/build/dex/swaap-v2/config.js.map +1 -1
- package/build/dex/uniswap-v2/uniswap-v2.js +26 -26
- package/build/dex/uniswap-v3/uniswap-v3.js +25 -25
- package/funding.json +5 -5
- package/package.json +1 -1
- package/src/config.ts +500 -496
- package/src/constants.ts +67 -67
- package/src/dex/aave-v2/aave-v2.ts +409 -409
- package/src/dex/aave-v3/aave-v3.ts +320 -320
- package/src/dex/algebra/algebra.ts +1180 -1180
- package/src/dex/angle-staked-stable/angle-staked-stable.ts +347 -347
- package/src/dex/augustus-rfq.ts +180 -180
- package/src/dex/bProtocol/bProtocol.ts +108 -108
- package/src/dex/balancer-v1/balancer-v1.ts +572 -572
- package/src/dex/balancer-v2/balancer-v2-e2e.test.ts +1699 -1699
- package/src/dex/balancer-v2/balancer-v2.ts +1669 -1701
- package/src/dex/bancor/bancor.ts +128 -128
- package/src/dex/camelot/camelot.ts +873 -873
- package/src/dex/compound/compound.ts +107 -107
- package/src/dex/curve-v1/config.ts +1179 -1179
- package/src/dex/curve-v1/curve-v1-e2e.test.ts +507 -507
- package/src/dex/curve-v1/curve-v1.ts +1315 -1315
- package/src/dex/curve-v1-factory/config.ts +1018 -1018
- package/src/dex/curve-v1-factory/curve-v1-factory.ts +1288 -1288
- package/src/dex/curve-v2/curve-v2.ts +440 -440
- package/src/dex/dexalot/dexalot.ts +1100 -1100
- package/src/dex/dodo-v1/dodo-v1.ts +132 -132
- package/src/dex/dodo-v2.ts +266 -266
- package/src/dex/etherfi.ts +239 -239
- package/src/dex/generic-rfq/generic-rfq.ts +431 -431
- package/src/dex/gmx/gmx.ts +321 -321
- package/src/dex/hashflow/hashflow.ts +1135 -1135
- package/src/dex/idex.ts +254 -254
- package/src/dex/index.ts +357 -357
- package/src/dex/jarvis-v6/jarvis-v6.ts +509 -509
- package/src/dex/jarvis.ts +262 -262
- package/src/dex/kyberdmm/kyberdmm.ts +649 -649
- package/src/dex/lido/lido.ts +110 -110
- package/src/dex/maker-psm/maker-psm.ts +592 -592
- package/src/dex/maverick-v1/maverick-v1.ts +537 -537
- package/src/dex/nerve/config.ts +635 -635
- package/src/dex/nerve/nerve-e2e.test.ts +731 -731
- package/src/dex/nerve/nerve-events.test.ts +911 -911
- package/src/dex/nerve/nerve.ts +467 -467
- package/src/dex/onebit/onebit.ts +113 -113
- package/src/dex/pancakeswap-v3/pancakeswap-v3.ts +1150 -1150
- package/src/dex/paraswap-limit-orders/config.ts +60 -60
- package/src/dex/paraswap-limit-orders/paraswap-limit-orders-e2e.test.ts +137 -137
- package/src/dex/paraswap-limit-orders/paraswap-limit-orders-integration.test.ts +328 -328
- package/src/dex/paraswap-limit-orders/paraswap-limit-orders.ts +631 -631
- package/src/dex/polygon-migrator/polygon-migrator.ts +194 -194
- package/src/dex/quick-perps/quick-perps.ts +337 -337
- package/src/dex/quickswap/config.ts +16 -16
- package/src/dex/quickswap/quickswap-e2e.test.ts +384 -384
- package/src/dex/smoothy/smoothy.ts +110 -110
- package/src/dex/solidly/config.ts +225 -225
- package/src/dex/solidly/solidly-e2e.test.ts +1421 -1421
- package/src/dex/solidly/solidly-integration.test.ts +1753 -1753
- package/src/dex/solidly/solidly.ts +687 -687
- package/src/dex/solidly-v3/solidly-v3.ts +968 -968
- package/src/dex/spark/spark.ts +284 -284
- package/src/dex/stable-pool/stable-pool.ts +129 -129
- package/src/dex/swaap-v2/config.ts +15 -0
- package/src/dex/swaap-v2/swaap-v2-e2e.test.ts +233 -17
- package/src/dex/swaap-v2/swaap-v2-integration.test.ts +1 -1
- package/src/dex/swaap-v2/swaap-v2.ts +1056 -1056
- package/src/dex/swell/swell.ts +312 -312
- package/src/dex/synthetix/synthetix.ts +420 -420
- package/src/dex/trader-joe-v2.1/trader-joe-v2.1.ts +180 -180
- package/src/dex/uniswap-v2/config.ts +637 -637
- package/src/dex/uniswap-v2/constants.ts +58 -58
- package/src/dex/uniswap-v2/uniswap-v2-e2e-avalanche.test.ts +1257 -1257
- package/src/dex/uniswap-v2/uniswap-v2-e2e-base.test.ts +42 -42
- package/src/dex/uniswap-v2/uniswap-v2-e2e-bsc.test.ts +1295 -1295
- package/src/dex/uniswap-v2/uniswap-v2-e2e-mainnet.test.ts +1834 -1834
- package/src/dex/uniswap-v2/uniswap-v2-e2e-polygon.test.ts +871 -871
- package/src/dex/uniswap-v2/uniswap-v2.ts +1117 -1117
- package/src/dex/uniswap-v3/uniswap-v3.ts +1501 -1501
- package/src/dex/uniswap-v3.ts +242 -242
- package/src/dex/weth/config.ts +73 -73
- package/src/dex/weth/weth.ts +230 -230
- package/src/dex/wombat/wombat.ts +467 -467
- package/src/dex/woo-fi-v2/woo-fi-v2.ts +529 -529
- package/src/dex/wsteth/wsteth.ts +304 -304
- package/src/dex/zerox/index.ts +328 -328
- package/src/executor/Executor01BytecodeBuilder.ts +474 -474
- package/src/executor/Executor02BytecodeBuilder.ts +1392 -1392
- package/src/executor/ExecutorBytecodeBuilder.ts +390 -390
- package/src/executor/ExecutorDetector.ts +131 -131
- package/src/executor/constants.ts +66 -66
- package/src/executor/executor01-bytecode-builder-e2e.test.ts +1478 -1478
- package/src/executor/utils.ts +62 -62
- package/src/router/simpleswap.ts +366 -366
- package/src/types.ts +342 -342
- package/tests/constants-e2e.ts +1632 -1622
- package/tests/generate-new-limit-order.ts +107 -107
|
@@ -1,592 +1,592 @@
|
|
|
1
|
-
import { Interface } from '@ethersproject/abi';
|
|
2
|
-
import { DeepReadonly } from 'ts-essentials';
|
|
3
|
-
import { Contract } from 'web3-eth-contract';
|
|
4
|
-
import {
|
|
5
|
-
Token,
|
|
6
|
-
Address,
|
|
7
|
-
ExchangePrices,
|
|
8
|
-
PoolPrices,
|
|
9
|
-
Log,
|
|
10
|
-
AdapterExchangeParam,
|
|
11
|
-
SimpleExchangeParam,
|
|
12
|
-
PoolLiquidity,
|
|
13
|
-
Logger,
|
|
14
|
-
NumberAsString,
|
|
15
|
-
DexExchangeParam,
|
|
16
|
-
} from '../../types';
|
|
17
|
-
import { SwapSide, Network } from '../../constants';
|
|
18
|
-
import * as CALLDATA_GAS_COST from '../../calldata-gas-cost';
|
|
19
|
-
import { StatefulEventSubscriber } from '../../stateful-event-subscriber';
|
|
20
|
-
import { getDexKeysWithNetwork, getBigIntPow } from '../../utils';
|
|
21
|
-
import { IDex } from '../../dex/idex';
|
|
22
|
-
import { IDexHelper } from '../../dex-helper/idex-helper';
|
|
23
|
-
import { MakerPsmData, PoolState, PoolConfig } from './types';
|
|
24
|
-
import { SimpleExchange } from '../simple-exchange';
|
|
25
|
-
import { MakerPsmConfig, Adapters } from './config';
|
|
26
|
-
import PsmABI from '../../abi/maker-psm/psm.json';
|
|
27
|
-
import VatABI from '../../abi/maker-psm/vat.json';
|
|
28
|
-
import { BI_POWS } from '../../bigint-constants';
|
|
29
|
-
import { SpecialDex } from '../../executor/types';
|
|
30
|
-
import { hexConcat, hexZeroPad, hexlify } from '@ethersproject/bytes';
|
|
31
|
-
import { ZEROS_12_BYTES } from '../../executor/constants';
|
|
32
|
-
|
|
33
|
-
const vatInterface = new Interface(VatABI);
|
|
34
|
-
const psmInterface = new Interface(PsmABI);
|
|
35
|
-
const WAD = BI_POWS[18];
|
|
36
|
-
|
|
37
|
-
const bigIntify = (b: any) => BigInt(b.toString());
|
|
38
|
-
const ceilDiv = (a: bigint, b: bigint) => (a + b - 1n) / b;
|
|
39
|
-
|
|
40
|
-
async function getOnChainState(
|
|
41
|
-
multiContract: Contract,
|
|
42
|
-
poolConfigs: PoolConfig[],
|
|
43
|
-
vatAddress: Address,
|
|
44
|
-
blockNumber: number | 'latest',
|
|
45
|
-
): Promise<PoolState[]> {
|
|
46
|
-
const callData = poolConfigs
|
|
47
|
-
.map(c => [
|
|
48
|
-
{
|
|
49
|
-
target: c.psmAddress,
|
|
50
|
-
callData: psmInterface.encodeFunctionData('tin', []),
|
|
51
|
-
},
|
|
52
|
-
{
|
|
53
|
-
target: c.psmAddress,
|
|
54
|
-
callData: psmInterface.encodeFunctionData('tout', []),
|
|
55
|
-
},
|
|
56
|
-
{
|
|
57
|
-
target: vatAddress,
|
|
58
|
-
callData: vatInterface.encodeFunctionData('ilks', [c.identifier]),
|
|
59
|
-
},
|
|
60
|
-
])
|
|
61
|
-
.flat();
|
|
62
|
-
|
|
63
|
-
const res = await multiContract.methods
|
|
64
|
-
.aggregate(callData)
|
|
65
|
-
.call({}, blockNumber);
|
|
66
|
-
|
|
67
|
-
let i = 0;
|
|
68
|
-
return poolConfigs.map(c => {
|
|
69
|
-
const tin = bigIntify(
|
|
70
|
-
psmInterface.decodeFunctionResult('tin', res.returnData[i++])[0],
|
|
71
|
-
);
|
|
72
|
-
const tout = bigIntify(
|
|
73
|
-
psmInterface.decodeFunctionResult('tout', res.returnData[i++])[0],
|
|
74
|
-
);
|
|
75
|
-
const ilks = vatInterface.decodeFunctionResult('ilks', res.returnData[i++]);
|
|
76
|
-
const Art = bigIntify(ilks.Art);
|
|
77
|
-
const line = bigIntify(ilks.line);
|
|
78
|
-
const rate = bigIntify(ilks.rate);
|
|
79
|
-
return {
|
|
80
|
-
tin,
|
|
81
|
-
tout,
|
|
82
|
-
Art,
|
|
83
|
-
line,
|
|
84
|
-
rate,
|
|
85
|
-
};
|
|
86
|
-
});
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
export class MakerPsmEventPool extends StatefulEventSubscriber<PoolState> {
|
|
90
|
-
handlers: {
|
|
91
|
-
[event: string]: (event: any, pool: PoolState, log: Log) => PoolState;
|
|
92
|
-
} = {};
|
|
93
|
-
|
|
94
|
-
logDecoder: (log: Log) => any;
|
|
95
|
-
|
|
96
|
-
to18ConversionFactor: bigint;
|
|
97
|
-
bytes32Tout =
|
|
98
|
-
'0x746f757400000000000000000000000000000000000000000000000000000000'; // bytes32('tout')
|
|
99
|
-
bytes32Tin =
|
|
100
|
-
'0x74696e0000000000000000000000000000000000000000000000000000000000'; // bytes32('tin')
|
|
101
|
-
|
|
102
|
-
constructor(
|
|
103
|
-
parentName: string,
|
|
104
|
-
protected network: number,
|
|
105
|
-
protected dexHelper: IDexHelper,
|
|
106
|
-
logger: Logger,
|
|
107
|
-
public poolConfig: PoolConfig,
|
|
108
|
-
protected vatAddress: Address,
|
|
109
|
-
) {
|
|
110
|
-
super(parentName, poolConfig.identifier, dexHelper, logger);
|
|
111
|
-
|
|
112
|
-
this.logDecoder = (log: Log) => psmInterface.parseLog(log);
|
|
113
|
-
this.addressesSubscribed = [poolConfig.psmAddress];
|
|
114
|
-
this.to18ConversionFactor = getBigIntPow(18 - poolConfig.gem.decimals);
|
|
115
|
-
|
|
116
|
-
// Add handlers
|
|
117
|
-
this.handlers['File'] = this.handleFile.bind(this);
|
|
118
|
-
this.handlers['SellGem'] = this.handleSellGem.bind(this);
|
|
119
|
-
this.handlers['BuyGem'] = this.handleBuyGem.bind(this);
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
handleFile(event: any, pool: PoolState, log: Log): PoolState {
|
|
123
|
-
if (event.args.what === this.bytes32Tin) {
|
|
124
|
-
pool.tin = bigIntify(event.args.data);
|
|
125
|
-
} else if (event.args.what === this.bytes32Tout) {
|
|
126
|
-
pool.tout = bigIntify(event.args.data);
|
|
127
|
-
}
|
|
128
|
-
return pool;
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
handleSellGem(event: any, pool: PoolState, log: Log): PoolState {
|
|
132
|
-
pool.Art += bigIntify(event.args.value) * this.to18ConversionFactor;
|
|
133
|
-
return pool;
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
handleBuyGem(event: any, pool: PoolState, log: Log): PoolState {
|
|
137
|
-
pool.Art -= bigIntify(event.args.value) * this.to18ConversionFactor;
|
|
138
|
-
return pool;
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
getIdentifier(): string {
|
|
142
|
-
return `${this.parentName}_${this.poolConfig.psmAddress}`.toLowerCase();
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
/**
|
|
146
|
-
* The function is called every time any of the subscribed
|
|
147
|
-
* addresses release log. The function accepts the current
|
|
148
|
-
* state, updates the state according to the log, and returns
|
|
149
|
-
* the updated state.
|
|
150
|
-
* @param state - Current state of event subscriber
|
|
151
|
-
* @param log - Log released by one of the subscribed addresses
|
|
152
|
-
* @returns Updates state of the event subscriber after the log
|
|
153
|
-
*/
|
|
154
|
-
protected processLog(
|
|
155
|
-
state: DeepReadonly<PoolState>,
|
|
156
|
-
log: Readonly<Log>,
|
|
157
|
-
): DeepReadonly<PoolState> | null {
|
|
158
|
-
try {
|
|
159
|
-
const event = this.logDecoder(log);
|
|
160
|
-
if (event.name in this.handlers) {
|
|
161
|
-
return this.handlers[event.name](event, state, log);
|
|
162
|
-
}
|
|
163
|
-
return state;
|
|
164
|
-
} catch (e) {
|
|
165
|
-
this.logger.error(
|
|
166
|
-
`Error_${this.parentName}_processLog could not parse the log with topic ${log.topics}:`,
|
|
167
|
-
e,
|
|
168
|
-
);
|
|
169
|
-
return null;
|
|
170
|
-
}
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
/**
|
|
174
|
-
* The function generates state using on-chain calls. This
|
|
175
|
-
* function is called to regenerate state if the event based
|
|
176
|
-
* system fails to fetch events and the local state is no
|
|
177
|
-
* more correct.
|
|
178
|
-
* @param blockNumber - Blocknumber for which the state should
|
|
179
|
-
* should be generated
|
|
180
|
-
* @returns state of the event subscriber at blocknumber
|
|
181
|
-
*/
|
|
182
|
-
async generateState(blockNumber: number): Promise<Readonly<PoolState>> {
|
|
183
|
-
return (
|
|
184
|
-
await getOnChainState(
|
|
185
|
-
this.dexHelper.multiContract,
|
|
186
|
-
[this.poolConfig],
|
|
187
|
-
this.vatAddress,
|
|
188
|
-
blockNumber,
|
|
189
|
-
)
|
|
190
|
-
)[0];
|
|
191
|
-
}
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
export class MakerPsm extends SimpleExchange implements IDex<MakerPsmData> {
|
|
195
|
-
protected eventPools: { [gemAddress: string]: MakerPsmEventPool };
|
|
196
|
-
|
|
197
|
-
// warning: There is limit on swap
|
|
198
|
-
readonly hasConstantPriceLargeAmounts = true;
|
|
199
|
-
readonly isFeeOnTransferSupported = false;
|
|
200
|
-
|
|
201
|
-
public static dexKeysWithNetwork: { key: string; networks: Network[] }[] =
|
|
202
|
-
getDexKeysWithNetwork(MakerPsmConfig);
|
|
203
|
-
|
|
204
|
-
logger: Logger;
|
|
205
|
-
|
|
206
|
-
constructor(
|
|
207
|
-
protected network: Network,
|
|
208
|
-
dexKey: string,
|
|
209
|
-
protected dexHelper: IDexHelper,
|
|
210
|
-
protected adapters = Adapters[network],
|
|
211
|
-
protected dai: Token = MakerPsmConfig[dexKey][network].dai,
|
|
212
|
-
protected vatAddress: Address = MakerPsmConfig[dexKey][network].vatAddress,
|
|
213
|
-
protected poolConfigs: PoolConfig[] = MakerPsmConfig[dexKey][network].pools,
|
|
214
|
-
) {
|
|
215
|
-
super(dexHelper, dexKey);
|
|
216
|
-
this.logger = dexHelper.getLogger(dexKey);
|
|
217
|
-
this.eventPools = {};
|
|
218
|
-
poolConfigs.forEach(
|
|
219
|
-
p =>
|
|
220
|
-
(this.eventPools[p.gem.address.toLowerCase()] = new MakerPsmEventPool(
|
|
221
|
-
dexKey,
|
|
222
|
-
network,
|
|
223
|
-
dexHelper,
|
|
224
|
-
this.logger,
|
|
225
|
-
p,
|
|
226
|
-
this.vatAddress,
|
|
227
|
-
)),
|
|
228
|
-
);
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
async initializePricing(blockNumber: number) {
|
|
232
|
-
const poolStates = await getOnChainState(
|
|
233
|
-
this.dexHelper.multiContract,
|
|
234
|
-
this.poolConfigs,
|
|
235
|
-
this.vatAddress,
|
|
236
|
-
blockNumber,
|
|
237
|
-
);
|
|
238
|
-
await Promise.all(
|
|
239
|
-
this.poolConfigs.map(async (p, i) => {
|
|
240
|
-
const eventPool = this.eventPools[p.gem.address.toLowerCase()];
|
|
241
|
-
await eventPool.initialize(blockNumber, {
|
|
242
|
-
state: poolStates[i],
|
|
243
|
-
});
|
|
244
|
-
}),
|
|
245
|
-
);
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
// Returns the list of contract adapters (name and index)
|
|
249
|
-
// for a buy/sell. Return null if there are no adapters.
|
|
250
|
-
getAdapters(side: SwapSide): { name: string; index: number }[] | null {
|
|
251
|
-
return this.adapters[side];
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
getEventPool(srcToken: Token, destToken: Token): MakerPsmEventPool | null {
|
|
255
|
-
const srcAddress = srcToken.address.toLowerCase();
|
|
256
|
-
const destAddress = destToken.address.toLowerCase();
|
|
257
|
-
return (
|
|
258
|
-
(srcAddress === this.dai.address && this.eventPools[destAddress]) ||
|
|
259
|
-
(destAddress === this.dai.address && this.eventPools[srcAddress]) ||
|
|
260
|
-
null
|
|
261
|
-
);
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
// Returns list of pool identifiers that can be used
|
|
265
|
-
// for a given swap. poolIdentifiers must be unique
|
|
266
|
-
// across DEXes.
|
|
267
|
-
async getPoolIdentifiers(
|
|
268
|
-
srcToken: Token,
|
|
269
|
-
destToken: Token,
|
|
270
|
-
side: SwapSide,
|
|
271
|
-
blockNumber: number,
|
|
272
|
-
): Promise<string[]> {
|
|
273
|
-
const eventPool = this.getEventPool(srcToken, destToken);
|
|
274
|
-
if (!eventPool) return [];
|
|
275
|
-
return [eventPool.getIdentifier()];
|
|
276
|
-
}
|
|
277
|
-
|
|
278
|
-
async getPoolState(
|
|
279
|
-
pool: MakerPsmEventPool,
|
|
280
|
-
blockNumber: number,
|
|
281
|
-
): Promise<PoolState> {
|
|
282
|
-
const eventState = pool.getState(blockNumber);
|
|
283
|
-
if (eventState) return eventState;
|
|
284
|
-
const onChainState = await pool.generateState(blockNumber);
|
|
285
|
-
pool.setState(onChainState, blockNumber);
|
|
286
|
-
return onChainState;
|
|
287
|
-
}
|
|
288
|
-
|
|
289
|
-
computePrices(
|
|
290
|
-
isSrcDai: boolean,
|
|
291
|
-
to18ConversionFactor: bigint,
|
|
292
|
-
side: SwapSide,
|
|
293
|
-
amounts: bigint[],
|
|
294
|
-
poolState: PoolState,
|
|
295
|
-
): bigint[] {
|
|
296
|
-
const sellGemCheck = (dart: bigint) =>
|
|
297
|
-
(dart + poolState.Art) * poolState.rate <= poolState.line;
|
|
298
|
-
const buyGemCheck = (dart: bigint) => dart <= poolState.Art;
|
|
299
|
-
|
|
300
|
-
return amounts.map(a => {
|
|
301
|
-
if (side === SwapSide.SELL) {
|
|
302
|
-
if (isSrcDai) {
|
|
303
|
-
const gemAmt18 = (a * WAD) / (WAD + poolState.tout);
|
|
304
|
-
if (buyGemCheck(gemAmt18)) return gemAmt18 / to18ConversionFactor;
|
|
305
|
-
} else {
|
|
306
|
-
const gemAmt18 = to18ConversionFactor * a;
|
|
307
|
-
if (sellGemCheck(gemAmt18))
|
|
308
|
-
return gemAmt18 - (gemAmt18 * poolState.tin) / WAD;
|
|
309
|
-
}
|
|
310
|
-
} else {
|
|
311
|
-
if (isSrcDai) {
|
|
312
|
-
const gemAmt18 = to18ConversionFactor * a;
|
|
313
|
-
if (buyGemCheck(gemAmt18))
|
|
314
|
-
return gemAmt18 + (gemAmt18 * poolState.tout) / WAD;
|
|
315
|
-
} else {
|
|
316
|
-
const gemAmt18 = (a * WAD) / (WAD - poolState.tin);
|
|
317
|
-
if (sellGemCheck(gemAmt18)) return gemAmt18 / to18ConversionFactor;
|
|
318
|
-
}
|
|
319
|
-
}
|
|
320
|
-
return 0n;
|
|
321
|
-
});
|
|
322
|
-
}
|
|
323
|
-
|
|
324
|
-
// Returns pool prices for amounts.
|
|
325
|
-
// If limitPools is defined only pools in limitPools
|
|
326
|
-
// should be used. If limitPools is undefined then
|
|
327
|
-
// any pools can be used.
|
|
328
|
-
async getPricesVolume(
|
|
329
|
-
srcToken: Token,
|
|
330
|
-
destToken: Token,
|
|
331
|
-
amounts: bigint[],
|
|
332
|
-
side: SwapSide,
|
|
333
|
-
blockNumber: number,
|
|
334
|
-
limitPools?: string[],
|
|
335
|
-
): Promise<null | ExchangePrices<MakerPsmData>> {
|
|
336
|
-
const eventPool = this.getEventPool(srcToken, destToken);
|
|
337
|
-
if (!eventPool) return null;
|
|
338
|
-
|
|
339
|
-
const poolIdentifier = eventPool.getIdentifier();
|
|
340
|
-
if (limitPools && !limitPools.includes(poolIdentifier)) return null;
|
|
341
|
-
|
|
342
|
-
const poolState = await this.getPoolState(eventPool, blockNumber);
|
|
343
|
-
|
|
344
|
-
const unitVolume = getBigIntPow(
|
|
345
|
-
(side === SwapSide.SELL ? srcToken : destToken).decimals,
|
|
346
|
-
);
|
|
347
|
-
|
|
348
|
-
const isSrcDai = srcToken.address.toLowerCase() === this.dai.address;
|
|
349
|
-
const gem = isSrcDai ? destToken : srcToken;
|
|
350
|
-
const toll =
|
|
351
|
-
(side === SwapSide.SELL && isSrcDai) ||
|
|
352
|
-
(side === SwapSide.BUY && !isSrcDai)
|
|
353
|
-
? poolState.tout
|
|
354
|
-
: poolState.tin;
|
|
355
|
-
|
|
356
|
-
const [unit, ...prices] = this.computePrices(
|
|
357
|
-
isSrcDai,
|
|
358
|
-
eventPool.to18ConversionFactor,
|
|
359
|
-
side,
|
|
360
|
-
[unitVolume, ...amounts],
|
|
361
|
-
poolState,
|
|
362
|
-
);
|
|
363
|
-
|
|
364
|
-
return [
|
|
365
|
-
{
|
|
366
|
-
prices,
|
|
367
|
-
unit,
|
|
368
|
-
data: {
|
|
369
|
-
toll: toll.toString(),
|
|
370
|
-
psmAddress: eventPool.poolConfig.psmAddress,
|
|
371
|
-
gemJoinAddress: eventPool.poolConfig.gemJoinAddress,
|
|
372
|
-
gemDecimals: gem.decimals,
|
|
373
|
-
},
|
|
374
|
-
poolAddresses: [eventPool.poolConfig.psmAddress],
|
|
375
|
-
exchange: this.dexKey,
|
|
376
|
-
gasCost: 100 * 1000, //TODO: simulate and fix the gas cost
|
|
377
|
-
poolIdentifier,
|
|
378
|
-
},
|
|
379
|
-
];
|
|
380
|
-
}
|
|
381
|
-
|
|
382
|
-
// Returns estimated gas cost of calldata for this DEX in multiSwap
|
|
383
|
-
getCalldataGasCost(poolPrices: PoolPrices<MakerPsmData>): number | number[] {
|
|
384
|
-
return (
|
|
385
|
-
CALLDATA_GAS_COST.DEX_OVERHEAD +
|
|
386
|
-
CALLDATA_GAS_COST.LENGTH_SMALL +
|
|
387
|
-
CALLDATA_GAS_COST.ADDRESS +
|
|
388
|
-
// TODO: pools have toll as zero currently but may change
|
|
389
|
-
CALLDATA_GAS_COST.ZERO +
|
|
390
|
-
// Either 1 (18 decimals) or 1e12 = 0xe8d4a51000 (6 decimals)
|
|
391
|
-
CALLDATA_GAS_COST.wordNonZeroBytes(
|
|
392
|
-
poolPrices.data.gemDecimals === 6 ? 4 : 1,
|
|
393
|
-
)
|
|
394
|
-
);
|
|
395
|
-
}
|
|
396
|
-
|
|
397
|
-
getPsmParams(
|
|
398
|
-
srcToken: string,
|
|
399
|
-
srcAmount: string,
|
|
400
|
-
destAmount: string,
|
|
401
|
-
data: MakerPsmData,
|
|
402
|
-
side: SwapSide,
|
|
403
|
-
): { isGemSell: boolean; gemAmount: string } {
|
|
404
|
-
const isSrcDai = srcToken.toLowerCase() === this.dai.address;
|
|
405
|
-
const to18ConversionFactor = getBigIntPow(18 - data.gemDecimals);
|
|
406
|
-
if (side === SwapSide.SELL) {
|
|
407
|
-
if (isSrcDai) {
|
|
408
|
-
const gemAmt18 = (BigInt(srcAmount) * WAD) / (WAD + BigInt(data.toll));
|
|
409
|
-
return {
|
|
410
|
-
isGemSell: false,
|
|
411
|
-
gemAmount: (gemAmt18 / to18ConversionFactor).toString(),
|
|
412
|
-
};
|
|
413
|
-
} else {
|
|
414
|
-
return { isGemSell: true, gemAmount: srcAmount };
|
|
415
|
-
}
|
|
416
|
-
} else {
|
|
417
|
-
if (isSrcDai) {
|
|
418
|
-
return { isGemSell: false, gemAmount: destAmount };
|
|
419
|
-
} else {
|
|
420
|
-
const gemAmt = ceilDiv(
|
|
421
|
-
BigInt(destAmount) * WAD,
|
|
422
|
-
(WAD - BigInt(data.toll)) * to18ConversionFactor,
|
|
423
|
-
);
|
|
424
|
-
return {
|
|
425
|
-
isGemSell: true,
|
|
426
|
-
gemAmount: gemAmt.toString(),
|
|
427
|
-
};
|
|
428
|
-
}
|
|
429
|
-
}
|
|
430
|
-
}
|
|
431
|
-
|
|
432
|
-
// Encode params required by the exchange adapter
|
|
433
|
-
// Used for multiSwap, buy & megaSwap
|
|
434
|
-
getAdapterParam(
|
|
435
|
-
srcToken: string,
|
|
436
|
-
destToken: string,
|
|
437
|
-
srcAmount: string,
|
|
438
|
-
destAmount: string,
|
|
439
|
-
data: MakerPsmData,
|
|
440
|
-
side: SwapSide,
|
|
441
|
-
): AdapterExchangeParam {
|
|
442
|
-
const to18ConversionFactor = getBigIntPow(18 - data.gemDecimals);
|
|
443
|
-
const payload = this.abiCoder.encodeParameter(
|
|
444
|
-
{
|
|
445
|
-
ParentStruct: {
|
|
446
|
-
gemJoinAddress: 'address',
|
|
447
|
-
toll: 'uint256',
|
|
448
|
-
to18ConversionFactor: 'uint256',
|
|
449
|
-
},
|
|
450
|
-
},
|
|
451
|
-
{
|
|
452
|
-
gemJoinAddress: data.gemJoinAddress,
|
|
453
|
-
toll: data.toll,
|
|
454
|
-
to18ConversionFactor,
|
|
455
|
-
},
|
|
456
|
-
);
|
|
457
|
-
|
|
458
|
-
return {
|
|
459
|
-
targetExchange: data.psmAddress,
|
|
460
|
-
networkFee: '0',
|
|
461
|
-
payload,
|
|
462
|
-
};
|
|
463
|
-
}
|
|
464
|
-
|
|
465
|
-
// Encode call data used by simpleSwap like routers
|
|
466
|
-
// Used for simpleSwap & simpleBuy
|
|
467
|
-
async getSimpleParam(
|
|
468
|
-
srcToken: string,
|
|
469
|
-
destToken: string,
|
|
470
|
-
srcAmount: string,
|
|
471
|
-
destAmount: string,
|
|
472
|
-
data: MakerPsmData,
|
|
473
|
-
side: SwapSide,
|
|
474
|
-
): Promise<SimpleExchangeParam> {
|
|
475
|
-
const { isGemSell, gemAmount } = this.getPsmParams(
|
|
476
|
-
srcToken,
|
|
477
|
-
srcAmount,
|
|
478
|
-
destAmount,
|
|
479
|
-
data,
|
|
480
|
-
side,
|
|
481
|
-
);
|
|
482
|
-
|
|
483
|
-
const swapData = psmInterface.encodeFunctionData(
|
|
484
|
-
isGemSell ? 'sellGem' : 'buyGem',
|
|
485
|
-
[this.augustusAddress, gemAmount],
|
|
486
|
-
);
|
|
487
|
-
|
|
488
|
-
return this.buildSimpleParamWithoutWETHConversion(
|
|
489
|
-
srcToken,
|
|
490
|
-
srcAmount,
|
|
491
|
-
destToken,
|
|
492
|
-
destAmount,
|
|
493
|
-
swapData,
|
|
494
|
-
data.psmAddress,
|
|
495
|
-
isGemSell ? data.gemJoinAddress : data.psmAddress,
|
|
496
|
-
);
|
|
497
|
-
}
|
|
498
|
-
|
|
499
|
-
getDexParam(
|
|
500
|
-
srcToken: Address,
|
|
501
|
-
destToken: Address,
|
|
502
|
-
srcAmount: NumberAsString,
|
|
503
|
-
destAmount: NumberAsString,
|
|
504
|
-
recipient: Address,
|
|
505
|
-
data: MakerPsmData,
|
|
506
|
-
side: SwapSide,
|
|
507
|
-
): DexExchangeParam {
|
|
508
|
-
const { isGemSell, gemAmount } = this.getPsmParams(
|
|
509
|
-
srcToken,
|
|
510
|
-
srcAmount,
|
|
511
|
-
destAmount,
|
|
512
|
-
data,
|
|
513
|
-
side,
|
|
514
|
-
);
|
|
515
|
-
|
|
516
|
-
let exchangeData = psmInterface.encodeFunctionData(
|
|
517
|
-
isGemSell ? 'sellGem' : 'buyGem',
|
|
518
|
-
[recipient, gemAmount],
|
|
519
|
-
);
|
|
520
|
-
|
|
521
|
-
// append toll and to18ConversionFactor & set specialDexFlag = SWAP_ON_MAKER_PSM to
|
|
522
|
-
// - `buyGem` on Ex1 & Ex2
|
|
523
|
-
// - `sellGem` on Ex3
|
|
524
|
-
let specialDexFlag = SpecialDex.DEFAULT;
|
|
525
|
-
if (
|
|
526
|
-
(side === SwapSide.SELL && !isGemSell) ||
|
|
527
|
-
(side === SwapSide.BUY && isGemSell)
|
|
528
|
-
) {
|
|
529
|
-
exchangeData = hexConcat([
|
|
530
|
-
exchangeData,
|
|
531
|
-
hexZeroPad(hexlify(BigInt(data.toll)), 32),
|
|
532
|
-
hexZeroPad(hexlify(getBigIntPow(18 - data.gemDecimals)), 32),
|
|
533
|
-
]);
|
|
534
|
-
specialDexFlag = SpecialDex.SWAP_ON_MAKER_PSM;
|
|
535
|
-
}
|
|
536
|
-
|
|
537
|
-
return {
|
|
538
|
-
needWrapNative: this.needWrapNative,
|
|
539
|
-
dexFuncHasRecipient: true,
|
|
540
|
-
exchangeData,
|
|
541
|
-
targetExchange: data.psmAddress,
|
|
542
|
-
specialDexFlag,
|
|
543
|
-
spender: isGemSell ? data.gemJoinAddress : data.psmAddress,
|
|
544
|
-
returnAmountPos: undefined,
|
|
545
|
-
};
|
|
546
|
-
}
|
|
547
|
-
// Returns list of top pools based on liquidity. Max
|
|
548
|
-
// limit number pools should be returned.
|
|
549
|
-
async getTopPoolsForToken(
|
|
550
|
-
tokenAddress: Address,
|
|
551
|
-
limit: number,
|
|
552
|
-
): Promise<PoolLiquidity[]> {
|
|
553
|
-
const _tokenAddress = tokenAddress.toLowerCase();
|
|
554
|
-
// Liquidity depends on the swapping side hence we simply use the min
|
|
555
|
-
// Its always in terms of stable coin hence liquidityUSD = liquidity
|
|
556
|
-
const minLiq = (poolState: PoolState) => {
|
|
557
|
-
const buyLimit = poolState.Art;
|
|
558
|
-
const sellLimit =
|
|
559
|
-
(poolState.line - poolState.Art * poolState.rate) / poolState.rate;
|
|
560
|
-
return (
|
|
561
|
-
2 *
|
|
562
|
-
parseInt(
|
|
563
|
-
(
|
|
564
|
-
(buyLimit > sellLimit ? sellLimit : buyLimit) / BI_POWS[18]
|
|
565
|
-
).toString(),
|
|
566
|
-
)
|
|
567
|
-
);
|
|
568
|
-
};
|
|
569
|
-
|
|
570
|
-
const isDai = _tokenAddress === this.dai.address;
|
|
571
|
-
|
|
572
|
-
const validPoolConfigs = isDai
|
|
573
|
-
? this.poolConfigs
|
|
574
|
-
: this.eventPools[_tokenAddress]
|
|
575
|
-
? [this.eventPools[_tokenAddress].poolConfig]
|
|
576
|
-
: [];
|
|
577
|
-
if (!validPoolConfigs.length) return [];
|
|
578
|
-
|
|
579
|
-
const poolStates = await getOnChainState(
|
|
580
|
-
this.dexHelper.multiContract,
|
|
581
|
-
validPoolConfigs,
|
|
582
|
-
this.vatAddress,
|
|
583
|
-
'latest',
|
|
584
|
-
);
|
|
585
|
-
return validPoolConfigs.map((p, i) => ({
|
|
586
|
-
exchange: this.dexKey,
|
|
587
|
-
address: p.psmAddress,
|
|
588
|
-
liquidityUSD: minLiq(poolStates[i]),
|
|
589
|
-
connectorTokens: [isDai ? p.gem : this.dai],
|
|
590
|
-
}));
|
|
591
|
-
}
|
|
592
|
-
}
|
|
1
|
+
import { Interface } from '@ethersproject/abi';
|
|
2
|
+
import { DeepReadonly } from 'ts-essentials';
|
|
3
|
+
import { Contract } from 'web3-eth-contract';
|
|
4
|
+
import {
|
|
5
|
+
Token,
|
|
6
|
+
Address,
|
|
7
|
+
ExchangePrices,
|
|
8
|
+
PoolPrices,
|
|
9
|
+
Log,
|
|
10
|
+
AdapterExchangeParam,
|
|
11
|
+
SimpleExchangeParam,
|
|
12
|
+
PoolLiquidity,
|
|
13
|
+
Logger,
|
|
14
|
+
NumberAsString,
|
|
15
|
+
DexExchangeParam,
|
|
16
|
+
} from '../../types';
|
|
17
|
+
import { SwapSide, Network } from '../../constants';
|
|
18
|
+
import * as CALLDATA_GAS_COST from '../../calldata-gas-cost';
|
|
19
|
+
import { StatefulEventSubscriber } from '../../stateful-event-subscriber';
|
|
20
|
+
import { getDexKeysWithNetwork, getBigIntPow } from '../../utils';
|
|
21
|
+
import { IDex } from '../../dex/idex';
|
|
22
|
+
import { IDexHelper } from '../../dex-helper/idex-helper';
|
|
23
|
+
import { MakerPsmData, PoolState, PoolConfig } from './types';
|
|
24
|
+
import { SimpleExchange } from '../simple-exchange';
|
|
25
|
+
import { MakerPsmConfig, Adapters } from './config';
|
|
26
|
+
import PsmABI from '../../abi/maker-psm/psm.json';
|
|
27
|
+
import VatABI from '../../abi/maker-psm/vat.json';
|
|
28
|
+
import { BI_POWS } from '../../bigint-constants';
|
|
29
|
+
import { SpecialDex } from '../../executor/types';
|
|
30
|
+
import { hexConcat, hexZeroPad, hexlify } from '@ethersproject/bytes';
|
|
31
|
+
import { ZEROS_12_BYTES } from '../../executor/constants';
|
|
32
|
+
|
|
33
|
+
const vatInterface = new Interface(VatABI);
|
|
34
|
+
const psmInterface = new Interface(PsmABI);
|
|
35
|
+
const WAD = BI_POWS[18];
|
|
36
|
+
|
|
37
|
+
const bigIntify = (b: any) => BigInt(b.toString());
|
|
38
|
+
const ceilDiv = (a: bigint, b: bigint) => (a + b - 1n) / b;
|
|
39
|
+
|
|
40
|
+
async function getOnChainState(
|
|
41
|
+
multiContract: Contract,
|
|
42
|
+
poolConfigs: PoolConfig[],
|
|
43
|
+
vatAddress: Address,
|
|
44
|
+
blockNumber: number | 'latest',
|
|
45
|
+
): Promise<PoolState[]> {
|
|
46
|
+
const callData = poolConfigs
|
|
47
|
+
.map(c => [
|
|
48
|
+
{
|
|
49
|
+
target: c.psmAddress,
|
|
50
|
+
callData: psmInterface.encodeFunctionData('tin', []),
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
target: c.psmAddress,
|
|
54
|
+
callData: psmInterface.encodeFunctionData('tout', []),
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
target: vatAddress,
|
|
58
|
+
callData: vatInterface.encodeFunctionData('ilks', [c.identifier]),
|
|
59
|
+
},
|
|
60
|
+
])
|
|
61
|
+
.flat();
|
|
62
|
+
|
|
63
|
+
const res = await multiContract.methods
|
|
64
|
+
.aggregate(callData)
|
|
65
|
+
.call({}, blockNumber);
|
|
66
|
+
|
|
67
|
+
let i = 0;
|
|
68
|
+
return poolConfigs.map(c => {
|
|
69
|
+
const tin = bigIntify(
|
|
70
|
+
psmInterface.decodeFunctionResult('tin', res.returnData[i++])[0],
|
|
71
|
+
);
|
|
72
|
+
const tout = bigIntify(
|
|
73
|
+
psmInterface.decodeFunctionResult('tout', res.returnData[i++])[0],
|
|
74
|
+
);
|
|
75
|
+
const ilks = vatInterface.decodeFunctionResult('ilks', res.returnData[i++]);
|
|
76
|
+
const Art = bigIntify(ilks.Art);
|
|
77
|
+
const line = bigIntify(ilks.line);
|
|
78
|
+
const rate = bigIntify(ilks.rate);
|
|
79
|
+
return {
|
|
80
|
+
tin,
|
|
81
|
+
tout,
|
|
82
|
+
Art,
|
|
83
|
+
line,
|
|
84
|
+
rate,
|
|
85
|
+
};
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export class MakerPsmEventPool extends StatefulEventSubscriber<PoolState> {
|
|
90
|
+
handlers: {
|
|
91
|
+
[event: string]: (event: any, pool: PoolState, log: Log) => PoolState;
|
|
92
|
+
} = {};
|
|
93
|
+
|
|
94
|
+
logDecoder: (log: Log) => any;
|
|
95
|
+
|
|
96
|
+
to18ConversionFactor: bigint;
|
|
97
|
+
bytes32Tout =
|
|
98
|
+
'0x746f757400000000000000000000000000000000000000000000000000000000'; // bytes32('tout')
|
|
99
|
+
bytes32Tin =
|
|
100
|
+
'0x74696e0000000000000000000000000000000000000000000000000000000000'; // bytes32('tin')
|
|
101
|
+
|
|
102
|
+
constructor(
|
|
103
|
+
parentName: string,
|
|
104
|
+
protected network: number,
|
|
105
|
+
protected dexHelper: IDexHelper,
|
|
106
|
+
logger: Logger,
|
|
107
|
+
public poolConfig: PoolConfig,
|
|
108
|
+
protected vatAddress: Address,
|
|
109
|
+
) {
|
|
110
|
+
super(parentName, poolConfig.identifier, dexHelper, logger);
|
|
111
|
+
|
|
112
|
+
this.logDecoder = (log: Log) => psmInterface.parseLog(log);
|
|
113
|
+
this.addressesSubscribed = [poolConfig.psmAddress];
|
|
114
|
+
this.to18ConversionFactor = getBigIntPow(18 - poolConfig.gem.decimals);
|
|
115
|
+
|
|
116
|
+
// Add handlers
|
|
117
|
+
this.handlers['File'] = this.handleFile.bind(this);
|
|
118
|
+
this.handlers['SellGem'] = this.handleSellGem.bind(this);
|
|
119
|
+
this.handlers['BuyGem'] = this.handleBuyGem.bind(this);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
handleFile(event: any, pool: PoolState, log: Log): PoolState {
|
|
123
|
+
if (event.args.what === this.bytes32Tin) {
|
|
124
|
+
pool.tin = bigIntify(event.args.data);
|
|
125
|
+
} else if (event.args.what === this.bytes32Tout) {
|
|
126
|
+
pool.tout = bigIntify(event.args.data);
|
|
127
|
+
}
|
|
128
|
+
return pool;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
handleSellGem(event: any, pool: PoolState, log: Log): PoolState {
|
|
132
|
+
pool.Art += bigIntify(event.args.value) * this.to18ConversionFactor;
|
|
133
|
+
return pool;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
handleBuyGem(event: any, pool: PoolState, log: Log): PoolState {
|
|
137
|
+
pool.Art -= bigIntify(event.args.value) * this.to18ConversionFactor;
|
|
138
|
+
return pool;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
getIdentifier(): string {
|
|
142
|
+
return `${this.parentName}_${this.poolConfig.psmAddress}`.toLowerCase();
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* The function is called every time any of the subscribed
|
|
147
|
+
* addresses release log. The function accepts the current
|
|
148
|
+
* state, updates the state according to the log, and returns
|
|
149
|
+
* the updated state.
|
|
150
|
+
* @param state - Current state of event subscriber
|
|
151
|
+
* @param log - Log released by one of the subscribed addresses
|
|
152
|
+
* @returns Updates state of the event subscriber after the log
|
|
153
|
+
*/
|
|
154
|
+
protected processLog(
|
|
155
|
+
state: DeepReadonly<PoolState>,
|
|
156
|
+
log: Readonly<Log>,
|
|
157
|
+
): DeepReadonly<PoolState> | null {
|
|
158
|
+
try {
|
|
159
|
+
const event = this.logDecoder(log);
|
|
160
|
+
if (event.name in this.handlers) {
|
|
161
|
+
return this.handlers[event.name](event, state, log);
|
|
162
|
+
}
|
|
163
|
+
return state;
|
|
164
|
+
} catch (e) {
|
|
165
|
+
this.logger.error(
|
|
166
|
+
`Error_${this.parentName}_processLog could not parse the log with topic ${log.topics}:`,
|
|
167
|
+
e,
|
|
168
|
+
);
|
|
169
|
+
return null;
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* The function generates state using on-chain calls. This
|
|
175
|
+
* function is called to regenerate state if the event based
|
|
176
|
+
* system fails to fetch events and the local state is no
|
|
177
|
+
* more correct.
|
|
178
|
+
* @param blockNumber - Blocknumber for which the state should
|
|
179
|
+
* should be generated
|
|
180
|
+
* @returns state of the event subscriber at blocknumber
|
|
181
|
+
*/
|
|
182
|
+
async generateState(blockNumber: number): Promise<Readonly<PoolState>> {
|
|
183
|
+
return (
|
|
184
|
+
await getOnChainState(
|
|
185
|
+
this.dexHelper.multiContract,
|
|
186
|
+
[this.poolConfig],
|
|
187
|
+
this.vatAddress,
|
|
188
|
+
blockNumber,
|
|
189
|
+
)
|
|
190
|
+
)[0];
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
export class MakerPsm extends SimpleExchange implements IDex<MakerPsmData> {
|
|
195
|
+
protected eventPools: { [gemAddress: string]: MakerPsmEventPool };
|
|
196
|
+
|
|
197
|
+
// warning: There is limit on swap
|
|
198
|
+
readonly hasConstantPriceLargeAmounts = true;
|
|
199
|
+
readonly isFeeOnTransferSupported = false;
|
|
200
|
+
|
|
201
|
+
public static dexKeysWithNetwork: { key: string; networks: Network[] }[] =
|
|
202
|
+
getDexKeysWithNetwork(MakerPsmConfig);
|
|
203
|
+
|
|
204
|
+
logger: Logger;
|
|
205
|
+
|
|
206
|
+
constructor(
|
|
207
|
+
protected network: Network,
|
|
208
|
+
dexKey: string,
|
|
209
|
+
protected dexHelper: IDexHelper,
|
|
210
|
+
protected adapters = Adapters[network],
|
|
211
|
+
protected dai: Token = MakerPsmConfig[dexKey][network].dai,
|
|
212
|
+
protected vatAddress: Address = MakerPsmConfig[dexKey][network].vatAddress,
|
|
213
|
+
protected poolConfigs: PoolConfig[] = MakerPsmConfig[dexKey][network].pools,
|
|
214
|
+
) {
|
|
215
|
+
super(dexHelper, dexKey);
|
|
216
|
+
this.logger = dexHelper.getLogger(dexKey);
|
|
217
|
+
this.eventPools = {};
|
|
218
|
+
poolConfigs.forEach(
|
|
219
|
+
p =>
|
|
220
|
+
(this.eventPools[p.gem.address.toLowerCase()] = new MakerPsmEventPool(
|
|
221
|
+
dexKey,
|
|
222
|
+
network,
|
|
223
|
+
dexHelper,
|
|
224
|
+
this.logger,
|
|
225
|
+
p,
|
|
226
|
+
this.vatAddress,
|
|
227
|
+
)),
|
|
228
|
+
);
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
async initializePricing(blockNumber: number) {
|
|
232
|
+
const poolStates = await getOnChainState(
|
|
233
|
+
this.dexHelper.multiContract,
|
|
234
|
+
this.poolConfigs,
|
|
235
|
+
this.vatAddress,
|
|
236
|
+
blockNumber,
|
|
237
|
+
);
|
|
238
|
+
await Promise.all(
|
|
239
|
+
this.poolConfigs.map(async (p, i) => {
|
|
240
|
+
const eventPool = this.eventPools[p.gem.address.toLowerCase()];
|
|
241
|
+
await eventPool.initialize(blockNumber, {
|
|
242
|
+
state: poolStates[i],
|
|
243
|
+
});
|
|
244
|
+
}),
|
|
245
|
+
);
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
// Returns the list of contract adapters (name and index)
|
|
249
|
+
// for a buy/sell. Return null if there are no adapters.
|
|
250
|
+
getAdapters(side: SwapSide): { name: string; index: number }[] | null {
|
|
251
|
+
return this.adapters[side];
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
getEventPool(srcToken: Token, destToken: Token): MakerPsmEventPool | null {
|
|
255
|
+
const srcAddress = srcToken.address.toLowerCase();
|
|
256
|
+
const destAddress = destToken.address.toLowerCase();
|
|
257
|
+
return (
|
|
258
|
+
(srcAddress === this.dai.address && this.eventPools[destAddress]) ||
|
|
259
|
+
(destAddress === this.dai.address && this.eventPools[srcAddress]) ||
|
|
260
|
+
null
|
|
261
|
+
);
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
// Returns list of pool identifiers that can be used
|
|
265
|
+
// for a given swap. poolIdentifiers must be unique
|
|
266
|
+
// across DEXes.
|
|
267
|
+
async getPoolIdentifiers(
|
|
268
|
+
srcToken: Token,
|
|
269
|
+
destToken: Token,
|
|
270
|
+
side: SwapSide,
|
|
271
|
+
blockNumber: number,
|
|
272
|
+
): Promise<string[]> {
|
|
273
|
+
const eventPool = this.getEventPool(srcToken, destToken);
|
|
274
|
+
if (!eventPool) return [];
|
|
275
|
+
return [eventPool.getIdentifier()];
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
async getPoolState(
|
|
279
|
+
pool: MakerPsmEventPool,
|
|
280
|
+
blockNumber: number,
|
|
281
|
+
): Promise<PoolState> {
|
|
282
|
+
const eventState = pool.getState(blockNumber);
|
|
283
|
+
if (eventState) return eventState;
|
|
284
|
+
const onChainState = await pool.generateState(blockNumber);
|
|
285
|
+
pool.setState(onChainState, blockNumber);
|
|
286
|
+
return onChainState;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
computePrices(
|
|
290
|
+
isSrcDai: boolean,
|
|
291
|
+
to18ConversionFactor: bigint,
|
|
292
|
+
side: SwapSide,
|
|
293
|
+
amounts: bigint[],
|
|
294
|
+
poolState: PoolState,
|
|
295
|
+
): bigint[] {
|
|
296
|
+
const sellGemCheck = (dart: bigint) =>
|
|
297
|
+
(dart + poolState.Art) * poolState.rate <= poolState.line;
|
|
298
|
+
const buyGemCheck = (dart: bigint) => dart <= poolState.Art;
|
|
299
|
+
|
|
300
|
+
return amounts.map(a => {
|
|
301
|
+
if (side === SwapSide.SELL) {
|
|
302
|
+
if (isSrcDai) {
|
|
303
|
+
const gemAmt18 = (a * WAD) / (WAD + poolState.tout);
|
|
304
|
+
if (buyGemCheck(gemAmt18)) return gemAmt18 / to18ConversionFactor;
|
|
305
|
+
} else {
|
|
306
|
+
const gemAmt18 = to18ConversionFactor * a;
|
|
307
|
+
if (sellGemCheck(gemAmt18))
|
|
308
|
+
return gemAmt18 - (gemAmt18 * poolState.tin) / WAD;
|
|
309
|
+
}
|
|
310
|
+
} else {
|
|
311
|
+
if (isSrcDai) {
|
|
312
|
+
const gemAmt18 = to18ConversionFactor * a;
|
|
313
|
+
if (buyGemCheck(gemAmt18))
|
|
314
|
+
return gemAmt18 + (gemAmt18 * poolState.tout) / WAD;
|
|
315
|
+
} else {
|
|
316
|
+
const gemAmt18 = (a * WAD) / (WAD - poolState.tin);
|
|
317
|
+
if (sellGemCheck(gemAmt18)) return gemAmt18 / to18ConversionFactor;
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
return 0n;
|
|
321
|
+
});
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
// Returns pool prices for amounts.
|
|
325
|
+
// If limitPools is defined only pools in limitPools
|
|
326
|
+
// should be used. If limitPools is undefined then
|
|
327
|
+
// any pools can be used.
|
|
328
|
+
async getPricesVolume(
|
|
329
|
+
srcToken: Token,
|
|
330
|
+
destToken: Token,
|
|
331
|
+
amounts: bigint[],
|
|
332
|
+
side: SwapSide,
|
|
333
|
+
blockNumber: number,
|
|
334
|
+
limitPools?: string[],
|
|
335
|
+
): Promise<null | ExchangePrices<MakerPsmData>> {
|
|
336
|
+
const eventPool = this.getEventPool(srcToken, destToken);
|
|
337
|
+
if (!eventPool) return null;
|
|
338
|
+
|
|
339
|
+
const poolIdentifier = eventPool.getIdentifier();
|
|
340
|
+
if (limitPools && !limitPools.includes(poolIdentifier)) return null;
|
|
341
|
+
|
|
342
|
+
const poolState = await this.getPoolState(eventPool, blockNumber);
|
|
343
|
+
|
|
344
|
+
const unitVolume = getBigIntPow(
|
|
345
|
+
(side === SwapSide.SELL ? srcToken : destToken).decimals,
|
|
346
|
+
);
|
|
347
|
+
|
|
348
|
+
const isSrcDai = srcToken.address.toLowerCase() === this.dai.address;
|
|
349
|
+
const gem = isSrcDai ? destToken : srcToken;
|
|
350
|
+
const toll =
|
|
351
|
+
(side === SwapSide.SELL && isSrcDai) ||
|
|
352
|
+
(side === SwapSide.BUY && !isSrcDai)
|
|
353
|
+
? poolState.tout
|
|
354
|
+
: poolState.tin;
|
|
355
|
+
|
|
356
|
+
const [unit, ...prices] = this.computePrices(
|
|
357
|
+
isSrcDai,
|
|
358
|
+
eventPool.to18ConversionFactor,
|
|
359
|
+
side,
|
|
360
|
+
[unitVolume, ...amounts],
|
|
361
|
+
poolState,
|
|
362
|
+
);
|
|
363
|
+
|
|
364
|
+
return [
|
|
365
|
+
{
|
|
366
|
+
prices,
|
|
367
|
+
unit,
|
|
368
|
+
data: {
|
|
369
|
+
toll: toll.toString(),
|
|
370
|
+
psmAddress: eventPool.poolConfig.psmAddress,
|
|
371
|
+
gemJoinAddress: eventPool.poolConfig.gemJoinAddress,
|
|
372
|
+
gemDecimals: gem.decimals,
|
|
373
|
+
},
|
|
374
|
+
poolAddresses: [eventPool.poolConfig.psmAddress],
|
|
375
|
+
exchange: this.dexKey,
|
|
376
|
+
gasCost: 100 * 1000, //TODO: simulate and fix the gas cost
|
|
377
|
+
poolIdentifier,
|
|
378
|
+
},
|
|
379
|
+
];
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
// Returns estimated gas cost of calldata for this DEX in multiSwap
|
|
383
|
+
getCalldataGasCost(poolPrices: PoolPrices<MakerPsmData>): number | number[] {
|
|
384
|
+
return (
|
|
385
|
+
CALLDATA_GAS_COST.DEX_OVERHEAD +
|
|
386
|
+
CALLDATA_GAS_COST.LENGTH_SMALL +
|
|
387
|
+
CALLDATA_GAS_COST.ADDRESS +
|
|
388
|
+
// TODO: pools have toll as zero currently but may change
|
|
389
|
+
CALLDATA_GAS_COST.ZERO +
|
|
390
|
+
// Either 1 (18 decimals) or 1e12 = 0xe8d4a51000 (6 decimals)
|
|
391
|
+
CALLDATA_GAS_COST.wordNonZeroBytes(
|
|
392
|
+
poolPrices.data.gemDecimals === 6 ? 4 : 1,
|
|
393
|
+
)
|
|
394
|
+
);
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
getPsmParams(
|
|
398
|
+
srcToken: string,
|
|
399
|
+
srcAmount: string,
|
|
400
|
+
destAmount: string,
|
|
401
|
+
data: MakerPsmData,
|
|
402
|
+
side: SwapSide,
|
|
403
|
+
): { isGemSell: boolean; gemAmount: string } {
|
|
404
|
+
const isSrcDai = srcToken.toLowerCase() === this.dai.address;
|
|
405
|
+
const to18ConversionFactor = getBigIntPow(18 - data.gemDecimals);
|
|
406
|
+
if (side === SwapSide.SELL) {
|
|
407
|
+
if (isSrcDai) {
|
|
408
|
+
const gemAmt18 = (BigInt(srcAmount) * WAD) / (WAD + BigInt(data.toll));
|
|
409
|
+
return {
|
|
410
|
+
isGemSell: false,
|
|
411
|
+
gemAmount: (gemAmt18 / to18ConversionFactor).toString(),
|
|
412
|
+
};
|
|
413
|
+
} else {
|
|
414
|
+
return { isGemSell: true, gemAmount: srcAmount };
|
|
415
|
+
}
|
|
416
|
+
} else {
|
|
417
|
+
if (isSrcDai) {
|
|
418
|
+
return { isGemSell: false, gemAmount: destAmount };
|
|
419
|
+
} else {
|
|
420
|
+
const gemAmt = ceilDiv(
|
|
421
|
+
BigInt(destAmount) * WAD,
|
|
422
|
+
(WAD - BigInt(data.toll)) * to18ConversionFactor,
|
|
423
|
+
);
|
|
424
|
+
return {
|
|
425
|
+
isGemSell: true,
|
|
426
|
+
gemAmount: gemAmt.toString(),
|
|
427
|
+
};
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
// Encode params required by the exchange adapter
|
|
433
|
+
// Used for multiSwap, buy & megaSwap
|
|
434
|
+
getAdapterParam(
|
|
435
|
+
srcToken: string,
|
|
436
|
+
destToken: string,
|
|
437
|
+
srcAmount: string,
|
|
438
|
+
destAmount: string,
|
|
439
|
+
data: MakerPsmData,
|
|
440
|
+
side: SwapSide,
|
|
441
|
+
): AdapterExchangeParam {
|
|
442
|
+
const to18ConversionFactor = getBigIntPow(18 - data.gemDecimals);
|
|
443
|
+
const payload = this.abiCoder.encodeParameter(
|
|
444
|
+
{
|
|
445
|
+
ParentStruct: {
|
|
446
|
+
gemJoinAddress: 'address',
|
|
447
|
+
toll: 'uint256',
|
|
448
|
+
to18ConversionFactor: 'uint256',
|
|
449
|
+
},
|
|
450
|
+
},
|
|
451
|
+
{
|
|
452
|
+
gemJoinAddress: data.gemJoinAddress,
|
|
453
|
+
toll: data.toll,
|
|
454
|
+
to18ConversionFactor,
|
|
455
|
+
},
|
|
456
|
+
);
|
|
457
|
+
|
|
458
|
+
return {
|
|
459
|
+
targetExchange: data.psmAddress,
|
|
460
|
+
networkFee: '0',
|
|
461
|
+
payload,
|
|
462
|
+
};
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
// Encode call data used by simpleSwap like routers
|
|
466
|
+
// Used for simpleSwap & simpleBuy
|
|
467
|
+
async getSimpleParam(
|
|
468
|
+
srcToken: string,
|
|
469
|
+
destToken: string,
|
|
470
|
+
srcAmount: string,
|
|
471
|
+
destAmount: string,
|
|
472
|
+
data: MakerPsmData,
|
|
473
|
+
side: SwapSide,
|
|
474
|
+
): Promise<SimpleExchangeParam> {
|
|
475
|
+
const { isGemSell, gemAmount } = this.getPsmParams(
|
|
476
|
+
srcToken,
|
|
477
|
+
srcAmount,
|
|
478
|
+
destAmount,
|
|
479
|
+
data,
|
|
480
|
+
side,
|
|
481
|
+
);
|
|
482
|
+
|
|
483
|
+
const swapData = psmInterface.encodeFunctionData(
|
|
484
|
+
isGemSell ? 'sellGem' : 'buyGem',
|
|
485
|
+
[this.augustusAddress, gemAmount],
|
|
486
|
+
);
|
|
487
|
+
|
|
488
|
+
return this.buildSimpleParamWithoutWETHConversion(
|
|
489
|
+
srcToken,
|
|
490
|
+
srcAmount,
|
|
491
|
+
destToken,
|
|
492
|
+
destAmount,
|
|
493
|
+
swapData,
|
|
494
|
+
data.psmAddress,
|
|
495
|
+
isGemSell ? data.gemJoinAddress : data.psmAddress,
|
|
496
|
+
);
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
getDexParam(
|
|
500
|
+
srcToken: Address,
|
|
501
|
+
destToken: Address,
|
|
502
|
+
srcAmount: NumberAsString,
|
|
503
|
+
destAmount: NumberAsString,
|
|
504
|
+
recipient: Address,
|
|
505
|
+
data: MakerPsmData,
|
|
506
|
+
side: SwapSide,
|
|
507
|
+
): DexExchangeParam {
|
|
508
|
+
const { isGemSell, gemAmount } = this.getPsmParams(
|
|
509
|
+
srcToken,
|
|
510
|
+
srcAmount,
|
|
511
|
+
destAmount,
|
|
512
|
+
data,
|
|
513
|
+
side,
|
|
514
|
+
);
|
|
515
|
+
|
|
516
|
+
let exchangeData = psmInterface.encodeFunctionData(
|
|
517
|
+
isGemSell ? 'sellGem' : 'buyGem',
|
|
518
|
+
[recipient, gemAmount],
|
|
519
|
+
);
|
|
520
|
+
|
|
521
|
+
// append toll and to18ConversionFactor & set specialDexFlag = SWAP_ON_MAKER_PSM to
|
|
522
|
+
// - `buyGem` on Ex1 & Ex2
|
|
523
|
+
// - `sellGem` on Ex3
|
|
524
|
+
let specialDexFlag = SpecialDex.DEFAULT;
|
|
525
|
+
if (
|
|
526
|
+
(side === SwapSide.SELL && !isGemSell) ||
|
|
527
|
+
(side === SwapSide.BUY && isGemSell)
|
|
528
|
+
) {
|
|
529
|
+
exchangeData = hexConcat([
|
|
530
|
+
exchangeData,
|
|
531
|
+
hexZeroPad(hexlify(BigInt(data.toll)), 32),
|
|
532
|
+
hexZeroPad(hexlify(getBigIntPow(18 - data.gemDecimals)), 32),
|
|
533
|
+
]);
|
|
534
|
+
specialDexFlag = SpecialDex.SWAP_ON_MAKER_PSM;
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
return {
|
|
538
|
+
needWrapNative: this.needWrapNative,
|
|
539
|
+
dexFuncHasRecipient: true,
|
|
540
|
+
exchangeData,
|
|
541
|
+
targetExchange: data.psmAddress,
|
|
542
|
+
specialDexFlag,
|
|
543
|
+
spender: isGemSell ? data.gemJoinAddress : data.psmAddress,
|
|
544
|
+
returnAmountPos: undefined,
|
|
545
|
+
};
|
|
546
|
+
}
|
|
547
|
+
// Returns list of top pools based on liquidity. Max
|
|
548
|
+
// limit number pools should be returned.
|
|
549
|
+
async getTopPoolsForToken(
|
|
550
|
+
tokenAddress: Address,
|
|
551
|
+
limit: number,
|
|
552
|
+
): Promise<PoolLiquidity[]> {
|
|
553
|
+
const _tokenAddress = tokenAddress.toLowerCase();
|
|
554
|
+
// Liquidity depends on the swapping side hence we simply use the min
|
|
555
|
+
// Its always in terms of stable coin hence liquidityUSD = liquidity
|
|
556
|
+
const minLiq = (poolState: PoolState) => {
|
|
557
|
+
const buyLimit = poolState.Art;
|
|
558
|
+
const sellLimit =
|
|
559
|
+
(poolState.line - poolState.Art * poolState.rate) / poolState.rate;
|
|
560
|
+
return (
|
|
561
|
+
2 *
|
|
562
|
+
parseInt(
|
|
563
|
+
(
|
|
564
|
+
(buyLimit > sellLimit ? sellLimit : buyLimit) / BI_POWS[18]
|
|
565
|
+
).toString(),
|
|
566
|
+
)
|
|
567
|
+
);
|
|
568
|
+
};
|
|
569
|
+
|
|
570
|
+
const isDai = _tokenAddress === this.dai.address;
|
|
571
|
+
|
|
572
|
+
const validPoolConfigs = isDai
|
|
573
|
+
? this.poolConfigs
|
|
574
|
+
: this.eventPools[_tokenAddress]
|
|
575
|
+
? [this.eventPools[_tokenAddress].poolConfig]
|
|
576
|
+
: [];
|
|
577
|
+
if (!validPoolConfigs.length) return [];
|
|
578
|
+
|
|
579
|
+
const poolStates = await getOnChainState(
|
|
580
|
+
this.dexHelper.multiContract,
|
|
581
|
+
validPoolConfigs,
|
|
582
|
+
this.vatAddress,
|
|
583
|
+
'latest',
|
|
584
|
+
);
|
|
585
|
+
return validPoolConfigs.map((p, i) => ({
|
|
586
|
+
exchange: this.dexKey,
|
|
587
|
+
address: p.psmAddress,
|
|
588
|
+
liquidityUSD: minLiq(poolStates[i]),
|
|
589
|
+
connectorTokens: [isDai ? p.gem : this.dai],
|
|
590
|
+
}));
|
|
591
|
+
}
|
|
592
|
+
}
|