-
Notifications
You must be signed in to change notification settings - Fork 4
/
Copy pathliquidator.ts
378 lines (322 loc) · 19.2 KB
/
liquidator.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
import { deployContract, logTxDetails } from "@tasks/utils/deploy-utils"
import { ONE_DAY, ZERO_ADDRESS } from "@utils/constants"
import { simpleToExactAmount } from "@utils/math"
import { encodeInitiateSwap } from "@utils/peripheral/cowswap"
import { formatUnits } from "ethers/lib/utils"
import { subtask, task, types } from "hardhat/config"
import { AssetProxy__factory, IERC20Metadata__factory, Liquidator__factory } from "types/generated"
import { getOrder, placeSellOrder } from "./peripheral/cowswapApi"
import { OneInchRouter } from "./peripheral/oneInchApi"
import { verifyEtherscan } from "./utils/etherscan"
import { logger } from "./utils/logger"
import { getChain, resolveAddress, resolveAssetToken } from "./utils/networkAddressFactory"
import { getSigner } from "./utils/signerFactory"
import type { Signer } from "ethers"
import type { HardhatRuntimeEnvironment } from "hardhat/types"
import type { AssetProxy, Liquidator } from "types/generated"
import type { CowSwapContext } from "./peripheral/cowswapApi"
import type { Chain } from "./utils"
const log = logger("task:liq")
const resolveMultipleAddress = async (chain: Chain, vaultsStr: string) =>
Promise.all(vaultsStr.split(",").map((vaultName) => resolveAddress(vaultName, chain)))
export async function deployLiquidator(
hre: HardhatRuntimeEnvironment,
signer: Signer,
nexusAddress: string,
syncSwapperAddress: string,
asyncSwapperAddress: string,
proxyAdmin: string,
proxy = true,
) {
const constructorArguments = [nexusAddress]
const liquidatorImpl = await deployContract<Liquidator>(new Liquidator__factory(signer), "Liquidator", constructorArguments)
await verifyEtherscan(hre, {
address: liquidatorImpl.address,
contract: "contracts/vault/liquidator/Liquidator.sol:Liquidator",
constructorArguments,
})
// Proxy
if (!proxy) {
return liquidatorImpl
}
const data = liquidatorImpl.interface.encodeFunctionData("initialize", [syncSwapperAddress, asyncSwapperAddress])
const proxyConstructorArguments = [liquidatorImpl.address, proxyAdmin, data]
const proxyContract = await deployContract<AssetProxy>(new AssetProxy__factory(signer), "AssetProxy", proxyConstructorArguments)
await verifyEtherscan(hre, {
address: proxyContract.address,
contract: "contracts/upgradability/Proxies.sol:AssetProxy",
constructorArguments: proxyConstructorArguments,
})
return Liquidator__factory.connect(proxyContract.address, signer)
}
subtask("liq-deploy", "Deploys a new Liquidator contract")
.addOptionalParam("syncSwapper", "Sync Swapper address override", undefined, types.string)
.addOptionalParam("asyncSwapper", "Async Swapper address override", "CowSwapDex", types.string)
.addOptionalParam("nexus", "Nexus address override", "Nexus", types.string)
.addOptionalParam("admin", "Proxy admin name or address override. eg DelayedProxyAdmin", "InstantProxyAdmin", types.string)
.addOptionalParam("proxy", "Deploy a proxy contract", true, types.boolean)
.addOptionalParam("speed", "Defender Relayer speed param: 'safeLow' | 'average' | 'fast' | 'fastest'", "fast", types.string)
.setAction(async (taskArgs, hre) => {
const { nexus, admin, syncSwapper, asyncSwapper, proxy, speed } = taskArgs
const chain = getChain(hre)
const signer = await getSigner(hre, speed)
const nexusAddress = resolveAddress(nexus, chain)
const syncSwapperAddress = syncSwapper ? resolveAddress(syncSwapper, chain) : ZERO_ADDRESS
const asyncSwapperAddress = resolveAddress(asyncSwapper, chain)
const proxyAdminAddress = resolveAddress(admin, chain)
return deployLiquidator(hre, signer, nexusAddress, syncSwapperAddress, asyncSwapperAddress, proxyAdminAddress, proxy)
})
task("liq-deploy").setAction(async (_, __, runSuper) => {
return runSuper()
})
subtask("liq-collect-rewards", "Collect rewards from vaults")
.addParam("vaults", "Vault names separated by ','.", undefined, types.string)
.addOptionalParam("liquidator", "Liquidator address override", "LiquidatorV2", types.string)
.addOptionalParam("speed", "Defender Relayer speed param: 'safeLow' | 'average' | 'fast' | 'fastest'", "fast", types.string)
.setAction(async (taskArgs, hre) => {
const { liquidator, speed, vaults } = taskArgs
const chain = getChain(hre)
const signer = await getSigner(hre, speed)
const liquidatorAddress = resolveAddress(liquidator, chain)
const vaultsAddress = await resolveMultipleAddress(chain, vaults)
const liquidatorContract = Liquidator__factory.connect(liquidatorAddress, signer)
const tx = await liquidatorContract.collectRewards(vaultsAddress)
await logTxDetails(tx, `liquidator.collectRewards(${vaultsAddress})`)
const receipt = await tx.wait()
const events = receipt.events?.find((e) => e.event === "CollectedRewards")
const totalRewards = {}
let i = 0
for (const rewards of events?.args?.rewards) {
let j = 0
for (const reward of rewards) {
const vaultToken = await resolveAssetToken(signer, chain, vaultsAddress[i])
const rewardToken = await resolveAssetToken(signer, chain, events?.args?.rewardTokens[i][j])
log(`Collected ${formatUnits(reward, rewardToken.decimals)} ${rewardToken.symbol} rewards from vault ${vaultToken.symbol}`)
totalRewards[rewardToken.symbol] = totalRewards[rewardToken.symbol] ? totalRewards[rewardToken.symbol].add(reward) : reward
j++
}
i++
}
for (const symbol in totalRewards) {
const rewardToken = await resolveAssetToken(signer, chain, symbol)
log(`Total Collected ${formatUnits(totalRewards[symbol], rewardToken.decimals)} ${symbol}`)
}
})
task("liq-collect-rewards").setAction(async (_, __, runSuper) => {
await runSuper()
})
subtask("liq-init-swap", "Initiate CowSwap swap of rewards to donate tokens")
.addParam("from", "Token symbol or address of the reward to sell", undefined, types.string)
.addParam("to", "Token symbol or address of the asset to buy so it can be donated back to the vault", undefined, types.string)
.addOptionalParam(
"receiver",
"Contract name or address of the contract or account to receive the tokens purchased",
"LiquidatorV2",
types.string,
)
.addOptionalParam("transfer", "Transfer sell tokens from liquidator?.", true, types.boolean)
.addOptionalParam("liquidator", "Liquidator address override", "LiquidatorV2", types.string)
.addOptionalParam("swapper", "Name or address to override the CowSwapDex contract", "CowSwapDex", types.string)
.addOptionalParam("readonly", "Quote swap but not initiate.", false, types.boolean)
.addOptionalParam("maxFee", "Max fee in from tokens", 0, types.int)
.addOptionalParam("speed", "Defender Relayer speed param: 'safeLow' | 'average' | 'fast' | 'fastest'", "fast", types.string)
.setAction(async (taskArgs, hre) => {
const { from, to, liquidator, maxFee, readonly, receiver, transfer, swapper, speed } = taskArgs
const chain = getChain(hre)
const signer = await getSigner(hre, speed)
const liquidatorAddress = resolveAddress(liquidator, chain)
const swapperAddress = resolveAddress(swapper, chain)
const sellToken = await resolveAssetToken(signer, chain, from)
const buyToken = await resolveAssetToken(signer, chain, to)
const receiverAddress = await resolveAddress(receiver, chain)
const liquidatorContract = Liquidator__factory.connect(liquidatorAddress, signer)
// Get Pending rewards
const { batch, rewards } = await liquidatorContract.pendingRewards(sellToken.address, buyToken.address)
log(`liquidator:\n batch: ${batch}\n rewards: ${formatUnits(rewards, sellToken.decimals)} ${sellToken.symbol}`)
// Place Sell Order on CoW Swap API
const context: CowSwapContext = {
trader: swapperAddress,
deadline: ONE_DAY,
chainId: chain,
}
const sellOrderParams = {
fromAsset: sellToken.address,
toAsset: buyToken.address,
fromAssetAmount: rewards,
receiver: receiverAddress,
}
log(`Sell ${formatUnits(rewards, sellToken.decimals)} ${sellToken.symbol} rewards`)
const sellOrder = await placeSellOrder(context, sellOrderParams)
log(`uid ${sellOrder.orderUid}`)
const feePercentage = sellOrder.fromAssetFeeAmount.mul(10000).div(rewards)
log(`fee ${formatUnits(sellOrder.fromAssetFeeAmount, sellToken.decimals)} ${formatUnits(feePercentage, 2)}%`)
log(`buy ${formatUnits(sellOrder.toAssetAmountAfterFee, buyToken.decimals)} ${buyToken.symbol}`)
const gasPrice = await hre.ethers.provider.getGasPrice()
log(`gas price ${formatUnits(gasPrice, "gwei")}`)
const maxFeeScaled = simpleToExactAmount(maxFee, sellToken.decimals)
if (maxFeeScaled.gt(0) && sellOrder.fromAssetFeeAmount.gt(maxFeeScaled)) {
throw Error(`Fee ${formatUnits(sellOrder.fromAssetFeeAmount, sellToken.decimals)} is greater than maxFee ${maxFee}`)
}
if (!readonly) {
// Initiate the order and sign
const data = encodeInitiateSwap(sellOrder.orderUid, transfer)
const tx = await liquidatorContract.initiateSwap(sellToken.address, buyToken.address, data)
await logTxDetails(tx, `liquidator initiateSwap of ${sellToken.symbol} to ${buyToken.symbol}`)
}
})
task("liq-init-swap").setAction(async (_, __, runSuper) => {
await runSuper()
})
subtask("liq-settle-swap", "Settle CoW Swap swap")
.addParam("uid", "The order unique identifier to settle", undefined, types.string)
.addOptionalParam("liquidator", "Liquidator address override", "LiquidatorV2", types.string)
.addOptionalParam("speed", "Defender Relayer speed param: 'safeLow' | 'average' | 'fast' | 'fastest'", "fast", types.string)
.setAction(async (taskArgs, hre) => {
const { liquidator, uid, speed } = taskArgs
const chain = getChain(hre)
const signer = await getSigner(hre, speed)
const liquidatorAddress = resolveAddress(liquidator, chain)
const liquidatorContract = Liquidator__factory.connect(liquidatorAddress, signer)
// Get the order details from cowswap API
const order = await getOrder(chain, uid)
if (order.status !== "fulfilled") {
throw new Error("Order has not been filled")
}
const fromAssetAddress = order.sellToken
const toAssetAddress = order.buyToken
const toAssetAmount = order.buyAmount
// Settle the order
const tx = await liquidatorContract.settleSwap(fromAssetAddress, toAssetAddress, toAssetAmount, [])
await logTxDetails(tx, `liquidator.settleSwap(${fromAssetAddress}, ${toAssetAddress}, ${toAssetAmount})`)
})
task("liq-settle-swap").setAction(async (_, __, runSuper) => {
await runSuper()
})
subtask("liq-sync-swap", "Swap rewards to donate tokens using 1Inch")
.addParam("from", "Token symbol or address of the reward to sell", undefined, types.string)
.addParam("to", "Token symbol or address of the asset to buy so they can be donated back to the vaults", undefined, types.string)
.addOptionalParam("liquidator", "Liquidator address override", "LiquidatorV2", types.string)
.addOptionalParam("speed", "Defender Relayer speed param: 'safeLow' | 'average' | 'fast' | 'fastest'", "fast", types.string)
.setAction(async (taskArgs, hre) => {
const { from, to, liquidator, speed } = taskArgs
const chain = getChain(hre)
const signer = await getSigner(hre, speed)
const liquidatorAddress = resolveAddress(liquidator, chain)
const fromAsset = await resolveAssetToken(signer, chain, from)
const toAssetAddress = await resolveAddress(to, chain)
const liquidatorContract = Liquidator__factory.connect(liquidatorAddress, signer)
// Get Pending rewards
const { batch, rewards } = await liquidatorContract.pendingRewards(fromAsset.address, toAssetAddress)
log(`liquidator:\n batch: ${batch}\n rewards: ${formatUnits(rewards, fromAsset.decimals)} ${from}`)
// Get quote
const router = new OneInchRouter(chain)
const minAssets = await router.getQuote({
fromTokenAddress: fromAsset.address,
toTokenAddress: toAssetAddress,
amount: rewards.toString(),
})
// TODO - investigate and encode swaps
// TODO - add slippage to minAssets
const { encodeOneInchSwap } = await import("@utils/peripheral/oneInch")
const data = encodeOneInchSwap(ZERO_ADDRESS, liquidatorAddress, "0x")
const tx = await liquidatorContract.swap(fromAsset.address, toAssetAddress, minAssets, data)
await logTxDetails(tx, `liquidator.swap(${fromAsset.address}, ${toAssetAddress}, ${minAssets})`)
})
task("liq-sync-swap").setAction(async (_, __, runSuper) => {
await runSuper()
})
subtask("liq-donate-tokens", "Donate purchased tokens to vaults")
.addParam("vaults", "Comma separated vault symbols or addresses", undefined, types.string)
.addParam("rewards", "Comma separated symbols or addresses of the reward tokens", undefined, types.string)
.addOptionalParam("purchase", "Symbol or address of the purchased token", "DAI", types.string)
.addOptionalParam("liquidator", "Liquidator address override", "LiquidatorV2", types.string)
.addOptionalParam("speed", "Defender Relayer speed param: 'safeLow' | 'average' | 'fast' | 'fastest'", "fast", types.string)
.setAction(async (taskArgs, hre) => {
const { liquidator, speed, rewards, purchase, vaults } = taskArgs
const chain = getChain(hre)
const signer = await getSigner(hre, speed)
const liquidatorAddress = resolveAddress(liquidator, chain)
const liquidatorContract = Liquidator__factory.connect(liquidatorAddress, signer)
const rewardAddresses = await resolveMultipleAddress(chain, rewards)
const vaultAddresses = await resolveMultipleAddress(chain, vaults)
const purchaseToken = await resolveAddress(purchase, chain)
const rewardTokens = []
const vaultTokens = []
const purchaseTokens = []
// For each vault
vaultAddresses.forEach((vaultAddress) => {
// For each reward token
rewardAddresses.forEach((rewardAddress) => {
rewardTokens.push(rewardAddress)
vaultTokens.push(vaultAddress)
purchaseTokens.push(purchaseToken)
})
})
log(`reward tokens [${rewardTokens.length}] ${rewardTokens}`)
log(`purchase tokens [${purchaseTokens.length}] ${purchaseTokens}`)
log(`vaults [${vaultTokens.length}] ${vaultTokens}`)
const tx = await liquidatorContract.donateTokens(rewardTokens, purchaseTokens, vaultTokens)
await logTxDetails(tx, `liquidator.donateTokens(${rewardTokens}, ${purchaseTokens}, ${vaultTokens})`)
})
task("liq-donate-tokens").setAction(async (_, __, runSuper) => {
await runSuper()
})
subtask("liq-set-async-swapper", "Governor sets a new async swapper on the Liquidator when using forked chains")
.addParam("swapper", "Contract address of the new async swapper", undefined, types.string)
.addOptionalParam("liquidator", "Liquidator address override", "LiquidatorV2", types.string)
.addOptionalParam("speed", "Defender Relayer speed param: 'safeLow' | 'average' | 'fast' | 'fastest'", "fast", types.string)
.setAction(async (taskArgs, hre) => {
const { liquidator, speed, swapper } = taskArgs
const chain = getChain(hre)
const signer = await getSigner(hre, speed)
const liquidatorAddress = resolveAddress(liquidator, chain)
const liquidatorContract = Liquidator__factory.connect(liquidatorAddress, signer)
const tx = await liquidatorContract.setAsyncSwapper(swapper)
await logTxDetails(tx, `liquidator.setAsyncSwapper(${swapper})`)
})
task("liq-set-async-swapper").setAction(async (_, __, runSuper) => {
await runSuper()
})
task("liq-rescue", "Governor rescues tokens from the Liquidator and sends it to governor")
.addParam("asset", "Token symbol or address of the asset to retrieve", undefined, types.string)
.addOptionalParam("amount", "Amount of tokens to rescue. Defaults to all assets.", undefined, types.float)
.addOptionalParam("liquidator", "Liquidator address override", "LiquidatorV2", types.string)
.addOptionalParam("speed", "Defender Relayer speed param: 'safeLow' | 'average' | 'fast' | 'fastest'", "fast", types.string)
.setAction(async (taskArgs, hre) => {
const { amount, asset, liquidator, speed } = taskArgs
const chain = getChain(hre)
const signer = await getSigner(hre, speed)
const assetAddress = resolveAddress(asset, chain)
const liquidatorAddress = resolveAddress(liquidator, chain)
const liquidatorContract = Liquidator__factory.connect(liquidatorAddress, signer)
const token = IERC20Metadata__factory.connect(assetAddress, signer)
const actualAssetAmount = await token.balanceOf(liquidator.address)
const rescueAmount = amount ? simpleToExactAmount(amount, await token.decimals()) : actualAssetAmount
await liquidatorContract.rescueToken(token.address, rescueAmount)
})
task("liq-approve", "Governor approves the async swapper to transfer tokens from the Liquidator")
.addParam("reward", "Token symbol or address of the reward token being approved", undefined, types.string)
.addOptionalParam("liquidator", "Liquidator address override", "LiquidatorV2", types.string)
.addOptionalParam("speed", "Defender Relayer speed param: 'safeLow' | 'average' | 'fast' | 'fastest'", "fast", types.string)
.setAction(async (taskArgs, hre) => {
const { reward, liquidator, speed } = taskArgs
const chain = getChain(hre)
const signer = await getSigner(hre, speed)
const rewardAddress = resolveAddress(reward, chain)
const liquidatorAddress = resolveAddress(liquidator, chain)
const liquidatorContract = Liquidator__factory.connect(liquidatorAddress, signer)
await liquidatorContract.approveAsyncSwapper(rewardAddress)
})
task("liq-revoke", "Governor revokes the async swapper to transfer tokens from the Liquidator")
.addParam("reward", "Token symbol or address of the reward token being approved", undefined, types.string)
.addOptionalParam("liquidator", "Liquidator address override", "LiquidatorV2", types.string)
.addOptionalParam("speed", "Defender Relayer speed param: 'safeLow' | 'average' | 'fast' | 'fastest'", "fast", types.string)
.setAction(async (taskArgs, hre) => {
const { reward, liquidator, speed } = taskArgs
const chain = getChain(hre)
const signer = await getSigner(hre, speed)
const rewardAddress = resolveAddress(reward, chain)
const liquidatorAddress = resolveAddress(liquidator, chain)
const liquidatorContract = Liquidator__factory.connect(liquidatorAddress, signer)
await liquidatorContract.revokeAsyncSwapper(rewardAddress)
})