@@ -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