-
Notifications
You must be signed in to change notification settings - Fork 2
/
CurveControllerV2Template.sol
207 lines (165 loc) · 7.47 KB
/
CurveControllerV2Template.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
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
// SPDX-License-Identifier: MIT
pragma solidity 0.6.11;
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/token/ERC20/SafeERC20.sol";
import "@openzeppelin/contracts/math/SafeMath.sol";
import "@openzeppelin/contracts/utils/Address.sol";
import "../../interfaces/curve/ICryptoSwapPool.sol";
import "../../interfaces/curve/IRegistry.sol";
import "../../interfaces/curve/IAddressProvider.sol";
import "../BaseController.sol";
/* solhint-disable func-name-mixedcase, var-name-mixedcase */
contract CurveControllerV2Template is BaseController {
event AddLiquidity(
address indexed provider,
uint256[N_COINS] token_amounts,
uint256 token_supply,
uint256 lp_token_amount
);
event RemoveLiquidity(
address indexed provider,
uint256[N_COINS] token_amounts,
uint256 token_supply,
uint256[N_COINS] amounts
);
event RemoveLiquidityOne(
address indexed provider,
uint256 token_amount,
uint256 coin_index,
uint256 coin_amount,
address coin_address
);
using SafeERC20 for IERC20;
using Address for address;
using SafeMath for uint256;
IAddressProvider public immutable addressProvider;
uint256 public constant N_COINS = 2;
constructor(
address _manager,
address _accessControl,
address _addressRegistry,
IAddressProvider _curveAddressProvider
) public BaseController(_manager, _accessControl, _addressRegistry) {
require(address(_curveAddressProvider) != address(0), "INVALID_CURVE_ADDRESS_PROVIDER");
addressProvider = _curveAddressProvider;
}
/// @notice Deploy liquidity to Curve pool
/// @dev Calls to external contract
/// @dev We trust sender to send a true curve poolAddress. If it's not the case it will fail in the add_liquidity part.
/// @param poolAddress Token addresses
/// @param amounts List of amounts of coins to deposit
/// @param poolAddress Minimum amount of LP tokens to mint from the deposit
function deploy(
address poolAddress,
uint256[N_COINS] calldata amounts,
uint256 minMintAmount
) external onlyManager onlyAddLiquidity {
address lpTokenAddress = _getLPToken(poolAddress);
uint256 amountsLength = amounts.length;
for (uint256 i = 0; i < amountsLength; ++i) {
if (amounts[i] > 0) {
address coin = ICryptoSwapPool(poolAddress).coins(i);
require(addressRegistry.checkAddress(coin, 0), "INVALID_COIN");
uint256 balance = IERC20(coin).balanceOf(address(this));
require(balance >= amounts[i], "INSUFFICIENT_BALANCE");
_approve(IERC20(coin), poolAddress, amounts[i]);
}
}
uint256 lpTokenBalanceBefore = IERC20(lpTokenAddress).balanceOf(address(this));
ICryptoSwapPool(poolAddress).add_liquidity(amounts, minMintAmount);
uint256 lpTokenBalanceAfter = IERC20(lpTokenAddress).balanceOf(address(this));
uint256 lpTokenAmount = lpTokenBalanceAfter.sub(lpTokenBalanceBefore);
require(lpTokenAmount >= minMintAmount, "LP_AMT_TOO_LOW");
emit AddLiquidity(msg.sender, amounts, IERC20(lpTokenAddress).totalSupply(), lpTokenAmount);
}
/// @notice Withdraw liquidity from Curve pool
/// @dev Calls to external contract
/// @dev We trust sender to send a true curve poolAddress. If it's not the case it will fail in the add_liquidity part.
/// @param poolAddress Token addresses
/// @param amount Quantity of LP tokens to burn in the withdrawal
/// @param minAmounts Minimum amounts of underlying coins to receive
function withdraw(
address poolAddress,
uint256 amount,
uint256[N_COINS] memory minAmounts
) external onlyManager onlyRemoveLiquidity {
address lpTokenAddress = _getLPTokenAndApprove(poolAddress, amount);
uint256 lpTokenBalanceBefore = IERC20(lpTokenAddress).balanceOf(address(this));
uint256[N_COINS] memory coinsBalancesBefore = _getCoinsBalances(poolAddress);
ICryptoSwapPool(poolAddress).remove_liquidity(amount, minAmounts);
uint256 lpTokenBalanceAfter = IERC20(lpTokenAddress).balanceOf(address(this));
uint256[N_COINS] memory coinsBalancesAfter = _getCoinsBalances(poolAddress);
_compareCoinsBalances(coinsBalancesBefore, coinsBalancesAfter, minAmounts);
require(lpTokenBalanceBefore.sub(amount) == lpTokenBalanceAfter, "LP_TOKEN_AMT_MISMATCH");
emit RemoveLiquidity(msg.sender, coinsBalancesAfter, IERC20(lpTokenAddress).totalSupply(), minAmounts);
}
/// @notice Withdraw liquidity from Curve pool
/// @dev Calls to external contract
/// @dev We trust sender to send a true curve poolAddress. If it's not the case it will fail in the add_liquidity part.
/// @param poolAddress token addresses
/// @param tokenAmount Amount of LP tokens to burn in the withdrawal
/// @param i Index value of the coin to withdraw
/// @param minAmount Minimum amount of coin to receive
function withdrawOneCoin(
address poolAddress,
uint256 tokenAmount,
uint256 i,
uint256 minAmount
) external onlyManager onlyRemoveLiquidity {
address lpTokenAddress = _getLPTokenAndApprove(poolAddress, tokenAmount);
address coin = ICryptoSwapPool(poolAddress).coins(uint256(i));
uint256 lpTokenBalanceBefore = IERC20(lpTokenAddress).balanceOf(address(this));
uint256 coinBalanceBefore = IERC20(coin).balanceOf(address(this));
ICryptoSwapPool(poolAddress).remove_liquidity_one_coin(tokenAmount, i, minAmount);
uint256 lpTokenBalanceAfter = IERC20(lpTokenAddress).balanceOf(address(this));
uint256 coinBalanceAfter = IERC20(coin).balanceOf(address(this));
require(coinBalanceBefore < coinBalanceAfter, "BALANCE_MUST_INCREASE");
require(lpTokenBalanceBefore.sub(tokenAmount) == lpTokenBalanceAfter, "LP_TOKEN_AMT_MISMATCH");
uint256 coin_amount = ICryptoSwapPool(poolAddress).calc_withdraw_one_coin(minAmount, i);
emit RemoveLiquidityOne(msg.sender, tokenAmount, i, coin_amount, coin);
}
function _getLPToken(address poolAddress) internal returns (address) {
require(poolAddress != address(0), "INVALID_POOL_ADDRESS");
address registryAddress = addressProvider.get_registry();
address lpTokenAddress = IRegistry(registryAddress).get_lp_token(poolAddress);
// If it's not registered in curve registry that should mean it's a factory pool (pool is also the LP Token)
// https://curve.readthedocs.io/factory-pools.html?highlight=factory%20pools%20differ#lp-tokens
if (lpTokenAddress == address(0)) {
lpTokenAddress = poolAddress;
}
require(addressRegistry.checkAddress(lpTokenAddress, 0), "INVALID_LP_TOKEN");
return lpTokenAddress;
}
function _getCoinsBalances(address poolAddress) internal returns (uint256[N_COINS] memory coinsBalances) {
for (uint256 i = 0; i < N_COINS; ++i) {
address coin = ICryptoSwapPool(poolAddress).coins(i);
uint256 balance = IERC20(coin).balanceOf(address(this));
coinsBalances[i] = balance;
}
return coinsBalances;
}
function _compareCoinsBalances(
uint256[N_COINS] memory balancesBefore,
uint256[N_COINS] memory balancesAfter,
uint256[N_COINS] memory amounts
) internal pure {
for (uint256 i = 0; i < N_COINS; ++i) {
uint256 minAmount = amounts[i];
require(balancesAfter[i].sub(balancesBefore[i]) >= minAmount, "INVALID_BALANCE_CHANGE");
}
}
function _getLPTokenAndApprove(address poolAddress, uint256 amount) internal returns (address) {
address lpTokenAddress = _getLPToken(poolAddress);
if (lpTokenAddress != poolAddress) {
_approve(IERC20(lpTokenAddress), poolAddress, amount);
}
return lpTokenAddress;
}
function _approve(IERC20 token, address spender, uint256 amount) internal {
uint256 currentAllowance = token.allowance(address(this), spender);
if (currentAllowance > 0) {
token.safeDecreaseAllowance(spender, currentAllowance);
}
token.safeIncreaseAllowance(spender, amount);
}
}