-
Notifications
You must be signed in to change notification settings - Fork 0
/
LiquidatorAgent.sol
165 lines (134 loc) · 6.18 KB
/
LiquidatorAgent.sol
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
// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.19;
import "forge-std/console2.sol";
import { SD59x18, wrap, convert, uMAX_SD59x18 } from "prb-math/SD59x18.sol";
import { ERC20PermitMock } from "pt-v5-vault-test/contracts/mock/ERC20PermitMock.sol";
import { ILiquidationPair } from "pt-v5-liquidator-interfaces/ILiquidationPair.sol";
import { FixedLiquidationPair } from "fixed-liquidator/FixedLiquidationPair.sol";
import { FixedLiquidationRouter } from "fixed-liquidator/FixedLiquidationRouter.sol";
import { PrizePool } from "pt-v5-prize-pool/PrizePool.sol";
import { SingleChainEnvironment } from "../environment/SingleChainEnvironment.sol";
import { Config } from "../utils/Config.sol";
import { Utils } from "../utils/Utils.sol";
contract LiquidatorAgent is Utils {
string liquidatorCsvFile = string.concat(vm.projectRoot(), "/data/liquidatorOut.csv");
string liquidatorCsvColumns =
"Draw ID, Timestamp, Elapsed Time, Elapsed Percent, Availability, Amount In, Amount Out, Exchange Rate, Market Exchange Rate, Profit, Efficiency, Remaining Yield";
SingleChainEnvironment public env;
PrizePool public prizePool;
ERC20PermitMock public prizeToken;
FixedLiquidationRouter public router;
string liquidatorCsv;
uint public burnedPool;
mapping(uint24 drawId => uint256 totalBurnedPool) public totalBurnedPoolPerDraw;
mapping(uint24 drawId => uint256 amountIn) public totalAmountInPerDraw;
mapping(uint24 drawId => uint256 amountOut) public totalAmountOutPerDraw;
mapping(uint24 drawId => uint256 amountIn) public feeBurnerAmountInPerDraw;
mapping(uint24 drawId => uint256 amountOut) public feeBurnerAmountOutPerDraw;
constructor(SingleChainEnvironment _env) {
env = _env;
prizePool = env.prizePool();
prizeToken = env.prizeToken();
router = env.router();
initOutputFileCsv(liquidatorCsvFile, liquidatorCsvColumns);
prizeToken.approve(address(router), type(uint256).max);
}
function check(SD59x18 wethUsdValue, SD59x18 poolUsdValue) public {
uint256 amountOut;
uint256 amountIn;
SD59x18 profit;
uint24 openDrawId = prizePool.getOpenDrawId();
(amountOut, amountIn, profit) = checkLiquidationPair(wethUsdValue, poolUsdValue, wethUsdValue, env.pair());
if (profit.gt(wrap(0))) {
totalAmountInPerDraw[openDrawId] += amountIn;
totalAmountOutPerDraw[openDrawId] += amountOut;
}
(amountOut, amountIn, profit) = checkLiquidationPair(poolUsdValue, wethUsdValue, wethUsdValue, env.feeBurnerPair());
if (profit.gt(wrap(0))) {
feeBurnerAmountInPerDraw[openDrawId] += amountIn;
feeBurnerAmountOutPerDraw[openDrawId] += amountOut;
}
}
function checkLiquidationPair(SD59x18 tokenInValueUsd, SD59x18 tokenOutValueUsd, SD59x18 ethValueUsd, ILiquidationPair pair) public returns (uint256 amountOut, uint256 amountIn, SD59x18 profit) {
uint256 maxAmountOut = pair.maxAmountOut();
(amountOut, amountIn, profit) = _findBestProfit(tokenInValueUsd, tokenOutValueUsd, ethValueUsd, pair);
// if (isFeeBurner(pair) && maxAmountOut > 0) {
// }
// console2.log("checkLiquidationPair amountOut %e", amountOut);
// console2.log("checkLiquidationPair amountIn %e", amountIn);
if (profit.gt(wrap(0))) {
ERC20PermitMock tokenIn = ERC20PermitMock(pair.tokenIn());
tokenIn.mint(address(this), amountIn);
tokenIn.approve(address(router), amountIn);
router.swapExactAmountOut(
FixedLiquidationPair(address(pair)),
address(this),
amountOut,
uint256(uMAX_SD59x18 / 1e18), // NOTE: uMAX_SD59x18/1e18 for DaLiquidator
block.timestamp + 10
);
if (isFeeBurner(pair)) {
burnedPool += amountIn;
}
uint256 elapsedSinceDrawEnded;
// uint256 elapsedSinceDrawEnded = block.timestamp -
// prizePool.drawClosesAt(prizePool.getLastAwardedDrawId());
// SD59x18 efficiency = convert(int256(amountIn)).div(convert(int256(amountOutInPrizeTokens)));
// uint256 efficiencyPercent = uint256(convert(efficiency.mul(convert(100))));
uint256[] memory logs = new uint256[](12);
logs[0] = prizePool.getLastAwardedDrawId();
logs[1] = block.timestamp;
logs[2] = elapsedSinceDrawEnded;
logs[3] = (elapsedSinceDrawEnded * 100) / 1 days;
logs[4] = maxAmountOut;
logs[5] = amountIn;
logs[6] = amountOut;
logs[7] = amountIn / amountOut;
logs[8] = 0;
logs[9] = 0; //convert(profit);
logs[10] = 0;
logs[11] = pair.maxAmountOut();
logUint256ToCsv(liquidatorCsvFile, logs);
}
}
function _findBestProfit(
SD59x18 tokenInValueUsd,
SD59x18 tokenOutValueUsd,
SD59x18 ethValueUsd,
ILiquidationPair pair
) internal returns (uint256 actualAmountOut, uint256 actualAmountIn, SD59x18 profit) {
uint256 maxAmountOut = pair.maxAmountOut();
uint256 liquidationSearchDensity = env.config().liquidator().liquidationPairSearchDensity;
for (uint i = 1; i <= liquidationSearchDensity; i++) {
uint256 amountOut = (maxAmountOut/liquidationSearchDensity) * i;
if (amountOut == 0) {
continue;
}
uint256 amountIn = pair.computeExactAmountIn(amountOut);
SD59x18 swapProfit = computeProfit(tokenInValueUsd, tokenOutValueUsd, ethValueUsd, amountIn, amountOut);
if (swapProfit.gt(profit)) {
profit = swapProfit;
actualAmountOut = amountOut;
actualAmountIn = amountIn;
}
}
}
function computeProfit(
SD59x18 tokenInValueUsd,
SD59x18 tokenOutValueUsd,
SD59x18 ethValueUsd,
uint256 amountIn,
uint256 amountOut
) public view returns (SD59x18) {
SD59x18 amountOutInUsd = tokenOutValueUsd.mul(convert(int256(amountOut)));
if (amountIn > 57896044618658097711785492504343953926634992332820282019728) {
return wrap(0);
}
SD59x18 capitalCost = tokenInValueUsd.mul(convert(int256(amountIn)));//.add(convert(int256(env.config().gas().liquidationCostInUsd)));
SD59x18 cost = capitalCost.add(convert(int256(env.config().gas().liquidationCostInUsd)));
return cost.lt(amountOutInUsd) ? amountOutInUsd.sub(cost) : wrap(0);
}
function isFeeBurner(ILiquidationPair pair) public view returns (bool) {
return address(pair) == address(env.feeBurnerPair());
}
}