Skip to content

Commit ef2973f

Browse files
Tom LintonDanielVFFranck Chastagnol
authored
3pool multiple asset support and depositAll methods for strategies (#482)
* Add slippage check on deposit * Add slippage on withdraw * Checkpoint * Checkpoint * WIP * Fix min mint calculation and enable tests * Make maxSlippage constant and enable test for real * Add burn to mintable erc20 * WIP * Fix issues * Cleanup test * First pass at depositAll for 3pool * Add depositAll to other strategies * Switch reallocate to use depositAll * Add internal _deposit to avoid reentrancy check * Explicit init of depositAll variables * Update slither db * Repair withdrawAll * Update slither db * Use get_virtual_price for pricing Curve assets WIP Fix issues Fix tests * Resolve dangerous inequality * WIP * Remove burn * Revert change to VaultCore * Comment out test * Update slither db * Remove unneeded changes * Change operation ordering * Remove test that will no longer work * Update checkBalance to revert on unsupported assets * Add missing SafeMath * Remove unintentional change to deploy script * Update comment * Revert changes to hardhat.config.js * Fix typo * Update remove_liquidity arguments, small optimisations * Breakdown test functions with description * Add depositAll test to 3pool and some cleanup of tests * Make 3pool support all stablecoins in test fixture * Add withdrawAll test for 3pool * Remove reference to old strategies in test * Update debug task for 3pool * Add 3pool deploy script * Add calls for Rinkeby to deploy script * Improve reallocate * Remove unnecessary timeout, improve balance printing of reallocate * Upgrade slither db * Fix typo * Fix minWithdrawAmount in withdraw function and add require check * Rename deploy * Update slither db * Upgrade VaultAdmin, Comp and Aave strategy in deploy 14 * oops * Minor fix to reallocate task logging * Make casting more efficient * Pass 3pool coins in order to avoid lookup Co-authored-by: Daniel Von Fange <daniel@leancoder.com> Co-authored-by: Franck Chastagnol <franck@originprotocol.com>
1 parent 4030771 commit ef2973f

18 files changed

Lines changed: 676 additions & 464 deletions

contracts/contracts/interfaces/IStrategy.sol

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,18 @@ pragma solidity 0.5.11;
55
*/
66
interface IStrategy {
77
/**
8-
* @dev Deposit the given asset to Lending platform.
8+
* @dev Deposit the given asset to platform
99
* @param _asset asset address
1010
* @param _amount Amount to deposit
1111
*/
1212
function deposit(address _asset, uint256 _amount) external;
1313

14+
/**
15+
* @dev Deposit the entire balance of all supported assets in the Strategy
16+
* to the platform
17+
*/
18+
function depositAll() external;
19+
1420
/**
1521
* @dev Withdraw given asset from Lending platform
1622
*/

contracts/contracts/mocks/curve/MockCurvePool.sol

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,20 @@ contract MockCurvePool is ERC20 {
6666
IERC20(coins[uint256(_index)]).transfer(msg.sender, amount);
6767
}
6868

69-
function get_virtual_price() external returns (uint256) {
69+
function get_virtual_price() external view returns (uint256) {
7070
return 1 * 10**18;
7171
}
72+
73+
function remove_liquidity(uint256 _amount, uint256[3] memory _min_amounts)
74+
public
75+
{
76+
IERC20(lpToken).transferFrom(msg.sender, address(this), _amount);
77+
uint256 totalSupply = IERC20(lpToken).totalSupply();
78+
for (uint256 i = 0; i < 3; i++) {
79+
uint256 amount = _amount.div(totalSupply).mul(
80+
IERC20(coins[i]).balanceOf(address(this))
81+
);
82+
IERC20(coins[i]).transfer(msg.sender, amount);
83+
}
84+
}
7285
}

contracts/contracts/strategies/AaveStrategy.sol

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,13 +25,34 @@ contract AaveStrategy is InitializableAbstractStrategy {
2525
onlyVault
2626
nonReentrant
2727
{
28-
require(_amount > 0, "Must deposit something");
28+
_deposit(_asset, _amount);
29+
}
2930

31+
/**
32+
* @dev Deposit asset into Aave
33+
* @param _asset Address of asset to deposit
34+
* @param _amount Amount of asset to deposit
35+
* @return amountDeposited Amount of asset that was deposited
36+
*/
37+
function _deposit(address _asset, uint256 _amount) internal {
38+
require(_amount > 0, "Must deposit something");
3039
IAaveAToken aToken = _getATokenFor(_asset);
3140
emit Deposit(_asset, address(aToken), _amount);
3241
_getLendingPool().deposit(_asset, _amount, referralCode);
3342
}
3443

44+
/**
45+
* @dev Deposit the entire balance of any supported asset into Aave
46+
*/
47+
function depositAll() external onlyVault nonReentrant {
48+
for (uint256 i = 0; i < assetsMapped.length; i++) {
49+
uint256 balance = IERC20(assetsMapped[i]).balanceOf(address(this));
50+
if (balance > 0) {
51+
_deposit(assetsMapped[i], balance);
52+
}
53+
}
54+
}
55+
3556
/**
3657
* @dev Withdraw asset from Aave
3758
* @param _recipient Address to receive withdrawn asset

contracts/contracts/strategies/CompoundStrategy.sol

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,13 +41,34 @@ contract CompoundStrategy is InitializableAbstractStrategy {
4141
onlyVault
4242
nonReentrant
4343
{
44-
require(_amount > 0, "Must deposit something");
44+
_deposit(_asset, _amount);
45+
}
4546

47+
/**
48+
* @dev Deposit asset into Compound
49+
* @param _asset Address of asset to deposit
50+
* @param _amount Amount of asset to deposit
51+
* @return amountDeposited Amount of asset that was deposited
52+
*/
53+
function _deposit(address _asset, uint256 _amount) internal {
54+
require(_amount > 0, "Must deposit something");
4655
ICERC20 cToken = _getCTokenFor(_asset);
4756
emit Deposit(_asset, address(cToken), _amount);
4857
require(cToken.mint(_amount) == 0, "cToken mint failed");
4958
}
5059

60+
/**
61+
* @dev Deposit the entire balance of any supported asset into Compound
62+
*/
63+
function depositAll() external onlyVault nonReentrant {
64+
for (uint256 i = 0; i < assetsMapped.length; i++) {
65+
uint256 balance = IERC20(assetsMapped[i]).balanceOf(address(this));
66+
if (balance > 0) {
67+
_deposit(assetsMapped[i], balance);
68+
}
69+
}
70+
}
71+
5172
/**
5273
* @dev Withdraw asset from Compound
5374
* @param _recipient Address to receive withdrawn asset

contracts/contracts/strategies/ICurvePool.sol

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,11 @@ interface ICurvePool {
1515
uint256 _minAmount
1616
) external;
1717

18+
function remove_liquidity(
19+
uint256 _amount,
20+
uint256[3] calldata _minWithdrawAmounts
21+
) external;
22+
1823
function calc_withdraw_one_coin(uint256 _amount, int128 _index)
1924
external
2025
view

contracts/contracts/strategies/ThreePoolStrategy.sol

Lines changed: 114 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,6 @@ contract ThreePoolStrategy is InitializableAbstractStrategy {
2323

2424
address crvGaugeAddress;
2525
address crvMinterAddress;
26-
int128 poolCoinIndex = -1;
2726
uint256 constant maxSlippage = 1e16; // 1%, same as the Curve UI
2827

2928
/**
@@ -33,33 +32,32 @@ contract ThreePoolStrategy is InitializableAbstractStrategy {
3332
* @param _platformAddress Address of the Curve 3pool
3433
* @param _vaultAddress Address of the vault
3534
* @param _rewardTokenAddress Address of CRV
36-
* @param _asset Address of the supported asset
37-
* @param _pToken Correspond platform token address (i.e. 3Crv)
35+
* @param _assets Addresses of supported assets. MUST be passed in the same
36+
* order as returned by coins on the pool contract, i.e.
37+
* DAI, USDC, USDT
38+
* @param _pTokens Platform Token corresponding addresses
3839
* @param _crvGaugeAddress Address of the Curve DAO gauge for this pool
3940
* @param _crvMinterAddress Address of the CRV minter for rewards
4041
*/
4142
function initialize(
4243
address _platformAddress, // 3Pool address
4344
address _vaultAddress,
4445
address _rewardTokenAddress, // CRV
45-
address _asset,
46-
address _pToken,
46+
address[] calldata _assets,
47+
address[] calldata _pTokens,
4748
address _crvGaugeAddress,
4849
address _crvMinterAddress
4950
) external onlyGovernor initializer {
50-
ICurvePool threePool = ICurvePool(_platformAddress);
51-
for (int128 i = 0; i < 3; i++) {
52-
if (threePool.coins(uint256(i)) == _asset) poolCoinIndex = i;
53-
}
54-
require(poolCoinIndex != -1, "Invalid 3pool asset");
51+
// Should be set prior to abstract initialize call otherwise
52+
// abstractSetPToken calls will fail
5553
crvGaugeAddress = _crvGaugeAddress;
5654
crvMinterAddress = _crvMinterAddress;
5755
InitializableAbstractStrategy._initialize(
5856
_platformAddress,
5957
_vaultAddress,
6058
_rewardTokenAddress,
61-
_asset,
62-
_pToken
59+
_assets,
60+
_pTokens
6361
);
6462
}
6563

@@ -90,8 +88,9 @@ contract ThreePoolStrategy is InitializableAbstractStrategy {
9088
// 3Pool requires passing deposit amounts for all 3 assets, set to 0 for
9189
// all
9290
uint256[3] memory _amounts;
91+
uint256 poolCoinIndex = _getPoolCoinIndex(_asset);
9392
// Set the amount on the asset we want to deposit
94-
_amounts[uint256(poolCoinIndex)] = _amount;
93+
_amounts[poolCoinIndex] = _amount;
9594
ICurvePool curvePool = ICurvePool(platformAddress);
9695
uint256 assetDecimals = Helpers.getDecimals(_asset);
9796
uint256 depositValue = _amount
@@ -110,6 +109,47 @@ contract ThreePoolStrategy is InitializableAbstractStrategy {
110109
);
111110
}
112111

112+
/**
113+
* @dev Deposit the entire balance of any supported asset into the Curve 3pool
114+
*/
115+
function depositAll() external onlyVault nonReentrant {
116+
uint256[3] memory _amounts = [uint256(0), uint256(0), uint256(0)];
117+
uint256 depositValue = 0;
118+
ICurvePool curvePool = ICurvePool(platformAddress);
119+
120+
for (uint256 i = 0; i < assetsMapped.length; i++) {
121+
address assetAddress = assetsMapped[i];
122+
uint256 balance = IERC20(assetAddress).balanceOf(address(this));
123+
if (balance > 0) {
124+
uint256 poolCoinIndex = _getPoolCoinIndex(assetAddress);
125+
// Set the amount on the asset we want to deposit
126+
_amounts[poolCoinIndex] = balance;
127+
uint256 assetDecimals = Helpers.getDecimals(assetAddress);
128+
// Get value of deposit in Curve LP token to later determine
129+
// the minMintAmount argument for add_liquidity
130+
depositValue = depositValue.add(
131+
balance.scaleBy(int8(18 - assetDecimals)).divPrecisely(
132+
curvePool.get_virtual_price()
133+
)
134+
);
135+
emit Deposit(assetAddress, address(platformAddress), balance);
136+
}
137+
}
138+
139+
uint256 minMintAmount = depositValue.mulTruncate(
140+
uint256(1e18).sub(maxSlippage)
141+
);
142+
// Do the deposit to 3pool
143+
curvePool.add_liquidity(_amounts, minMintAmount);
144+
// Deposit into Gauge, the PToken is the same (3Crv) for all mapped
145+
// assets, so just get the address from the first one
146+
IERC20 pToken = IERC20(assetToPToken[assetsMapped[0]]);
147+
ICurveGauge(crvGaugeAddress).deposit(
148+
pToken.balanceOf(address(this)),
149+
address(this)
150+
);
151+
}
152+
113153
/**
114154
* @dev Withdraw asset from Curve 3Pool
115155
* @param _recipient Address to receive withdrawn asset
@@ -128,29 +168,43 @@ contract ThreePoolStrategy is InitializableAbstractStrategy {
128168

129169
// Calculate how much of the pool token we need to withdraw
130170
(uint256 contractPTokens, , uint256 totalPTokens) = _getTotalPTokens();
171+
172+
require(totalPTokens > 0, "Insufficient 3CRV balance");
173+
174+
uint256 poolCoinIndex = _getPoolCoinIndex(_asset);
131175
// Calculate the max amount of the asset we'd get if we withdrew all the
132176
// platform tokens
133177
ICurvePool curvePool = ICurvePool(platformAddress);
134178
uint256 maxAmount = curvePool.calc_withdraw_one_coin(
135179
totalPTokens,
136-
poolCoinIndex
180+
int128(poolCoinIndex)
137181
);
182+
138183
// Calculate how many platform tokens we need to withdraw the asset amount
139184
uint256 withdrawPTokens = totalPTokens.mul(_amount).div(maxAmount);
140185
if (contractPTokens < withdrawPTokens) {
141-
// Not enough of pool token exists on this contract, must be staked
142-
// in Gauge, unstake
143-
ICurveGauge(crvGaugeAddress).withdraw(withdrawPTokens);
186+
// Not enough of pool token exists on this contract, some must be
187+
// staked in Gauge, unstake difference
188+
ICurveGauge(crvGaugeAddress).withdraw(
189+
withdrawPTokens.sub(contractPTokens)
190+
);
144191
}
145-
uint256 minWithdrawAmount = withdrawPTokens.mulTruncate(
146-
uint256(1e18).sub(maxSlippage)
147-
);
192+
193+
// Calculate a minimum withdrawal amount
194+
uint256 assetDecimals = Helpers.getDecimals(_asset);
195+
// 3crv is 1e18, subtract slippage percentage and scale to asset
196+
// decimals
197+
uint256 minWithdrawAmount = withdrawPTokens
198+
.mulTruncate(uint256(1e18).sub(maxSlippage))
199+
.scaleBy(int8(assetDecimals - 18));
200+
148201
curvePool.remove_liquidity_one_coin(
149202
withdrawPTokens,
150-
poolCoinIndex,
203+
int128(poolCoinIndex),
151204
minWithdrawAmount
152205
);
153206
IERC20(_asset).safeTransfer(_recipient, _amount);
207+
154208
// Transfer any leftover dust back to the vault buffer.
155209
uint256 dust = IERC20(_asset).balanceOf(address(this));
156210
if (dust > 0) {
@@ -163,36 +217,41 @@ contract ThreePoolStrategy is InitializableAbstractStrategy {
163217
*/
164218
function withdrawAll() external onlyVaultOrGovernor nonReentrant {
165219
// Withdraw all from Gauge
166-
(, uint256 gaugePTokens, ) = _getTotalPTokens();
220+
(, uint256 gaugePTokens, uint256 totalPTokens) = _getTotalPTokens();
167221
ICurveGauge(crvGaugeAddress).withdraw(gaugePTokens);
168-
// Remove entire balance, 3pool strategies only support a single asset
169-
// so safe to use assetsMapped[0]
170-
IERC20 asset = IERC20(assetsMapped[0]);
171-
uint256 pTokenBalance = IERC20(assetToPToken[address(asset)]).balanceOf(
172-
address(this)
173-
);
174-
uint256 minWithdrawAmount = pTokenBalance.mulTruncate(
175-
uint256(1e18).sub(maxSlippage)
176-
);
177-
ICurvePool(platformAddress).remove_liquidity_one_coin(
178-
pTokenBalance,
179-
poolCoinIndex,
180-
minWithdrawAmount
181-
);
182-
// Transfer the asset out to Vault
183-
asset.safeTransfer(vaultAddress, asset.balanceOf(address(this)));
222+
uint256[3] memory minWithdrawAmounts = [
223+
uint256(0),
224+
uint256(0),
225+
uint256(0)
226+
];
227+
// Calculate min withdrawal amounts for each coin
228+
for (uint256 i = 0; i < assetsMapped.length; i++) {
229+
address assetAddress = assetsMapped[i];
230+
uint256 virtualBalance = checkBalance(assetAddress);
231+
uint256 poolCoinIndex = _getPoolCoinIndex(assetAddress);
232+
minWithdrawAmounts[poolCoinIndex] = virtualBalance.mulTruncate(
233+
uint256(1e18).sub(maxSlippage)
234+
);
235+
}
236+
// Remove liqudiity
237+
ICurvePool threePool = ICurvePool(platformAddress);
238+
threePool.remove_liquidity(totalPTokens, minWithdrawAmounts);
239+
// Transfer assets out ot Vault
240+
// Note that Curve will provide all 3 of the assets in 3pool even if
241+
// we have not set PToken addresses for all of them in this strategy
242+
for (uint256 i = 0; i < assetsMapped.length; i++) {
243+
IERC20 asset = IERC20(threePool.coins(i));
244+
asset.safeTransfer(vaultAddress, asset.balanceOf(address(this)));
245+
}
184246
}
185247

186248
/**
187249
* @dev Get the total asset value held in the platform
188-
* This includes any interest that was generated since depositing
189-
* We calculate this by calculating a what we would get if we withdrawAlld
190-
* the allocated percentage of this asset.
191250
* @param _asset Address of the asset
192251
* @return balance Total value of the asset in the platform
193252
*/
194253
function checkBalance(address _asset)
195-
external
254+
public
196255
view
197256
returns (uint256 balance)
198257
{
@@ -226,8 +285,9 @@ contract ThreePoolStrategy is InitializableAbstractStrategy {
226285
*/
227286
function safeApproveAllTokens() external {
228287
// This strategy is a special case since it only supports one asset
229-
address assetAddress = assetsMapped[0];
230-
_abstractSetPToken(assetAddress, assetToPToken[assetAddress]);
288+
for (uint256 i = 0; i < assetsMapped.length; i++) {
289+
_abstractSetPToken(assetsMapped[i], assetToPToken[assetsMapped[i]]);
290+
}
231291
}
232292

233293
/**
@@ -271,4 +331,14 @@ contract ThreePoolStrategy is InitializableAbstractStrategy {
271331
pToken.safeApprove(crvGaugeAddress, 0);
272332
pToken.safeApprove(crvGaugeAddress, uint256(-1));
273333
}
334+
335+
/**
336+
* @dev Get the index of the coin in 3pool
337+
*/
338+
function _getPoolCoinIndex(address _asset) internal view returns (uint256) {
339+
for (uint256 i = 0; i < 3; i++) {
340+
if (assetsMapped[i] == _asset) return i;
341+
}
342+
revert("Invalid 3pool asset");
343+
}
274344
}

0 commit comments

Comments
 (0)