From 78bd850dea58753d608a897ec154b4a34f71aa59 Mon Sep 17 00:00:00 2001 From: tess3rac7 Date: Sun, 10 Jul 2022 13:50:41 -0400 Subject: [PATCH 1/4] Univ3 compiles. --- contracts/ReaperStrategyTarot.sol | 14 ++--- contracts/interfaces/IUniswapRouterV3.sol | 63 ----------------------- contracts/library/UniswapV3Utils.sol | 34 ++++++------ 3 files changed, 24 insertions(+), 87 deletions(-) delete mode 100644 contracts/interfaces/IUniswapRouterV3.sol diff --git a/contracts/ReaperStrategyTarot.sol b/contracts/ReaperStrategyTarot.sol index 7d6ed0c..ccfddc2 100644 --- a/contracts/ReaperStrategyTarot.sol +++ b/contracts/ReaperStrategyTarot.sol @@ -286,13 +286,13 @@ contract ReaperStrategyTarot is ReaperBaseStrategyv4 { path[0] = _from; path[1] = _to; IERC20Upgradeable(_from).safeIncreaseAllowance(UNI_ROUTER, _amount); - IUniswapV2Router02(UNI_ROUTER).swapExactTokensForTokensSupportingFeeOnTransferTokens( - _amount, - 0, - path, - address(this), - block.timestamp - ); + // IUniswapV2Router02(UNI_ROUTER).swapExactTokensForTokensSupportingFeeOnTransferTokens( + // _amount, + // 0, + // path, + // address(this), + // block.timestamp + // ); } /** diff --git a/contracts/interfaces/IUniswapRouterV3.sol b/contracts/interfaces/IUniswapRouterV3.sol deleted file mode 100644 index 106798c..0000000 --- a/contracts/interfaces/IUniswapRouterV3.sol +++ /dev/null @@ -1,63 +0,0 @@ -// SPDX-License-Identifier: MIT - -pragma solidity =0.7.6; - -interface IUniswapRouterV3 { - struct ExactInputSingleParams { - address tokenIn; - address tokenOut; - uint24 fee; - address recipient; - uint256 deadline; - uint256 amountIn; - uint256 amountOutMinimum; - uint160 sqrtPriceLimitX96; - } - - /// @notice Swaps `amountIn` of one token for as much as possible of another token - /// @param params The parameters necessary for the swap, encoded as `ExactInputSingleParams` in calldata - /// @return amountOut The amount of the received token - function exactInputSingle(ExactInputSingleParams calldata params) external payable returns (uint256 amountOut); - - struct ExactInputParams { - bytes path; - address recipient; - uint256 deadline; - uint256 amountIn; - uint256 amountOutMinimum; - } - - /// @notice Swaps `amountIn` of one token for as much as possible of another along the specified path - /// @param params The parameters necessary for the multi-hop swap, encoded as `ExactInputParams` in calldata - /// @return amountOut The amount of the received token - function exactInput(ExactInputParams calldata params) external payable returns (uint256 amountOut); - - struct ExactOutputSingleParams { - address tokenIn; - address tokenOut; - uint24 fee; - address recipient; - uint256 deadline; - uint256 amountOut; - uint256 amountInMaximum; - uint160 sqrtPriceLimitX96; - } - - /// @notice Swaps as little as possible of one token for `amountOut` of another token - /// @param params The parameters necessary for the swap, encoded as `ExactOutputSingleParams` in calldata - /// @return amountIn The amount of the input token - function exactOutputSingle(ExactOutputSingleParams calldata params) external payable returns (uint256 amountIn); - - struct ExactOutputParams { - bytes path; - address recipient; - uint256 deadline; - uint256 amountOut; - uint256 amountInMaximum; - } - - /// @notice Swaps as little as possible of one token for `amountOut` of another along the specified path (reversed) - /// @param params The parameters necessary for the multi-hop swap, encoded as `ExactOutputParams` in calldata - /// @return amountIn The amount of the input token - function exactOutput(ExactOutputParams calldata params) external payable returns (uint256 amountIn); -} \ No newline at end of file diff --git a/contracts/library/UniswapV3Utils.sol b/contracts/library/UniswapV3Utils.sol index e3b6139..5a7863f 100644 --- a/contracts/library/UniswapV3Utils.sol +++ b/contracts/library/UniswapV3Utils.sol @@ -1,12 +1,12 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.7.0; +pragma solidity ^0.8.0; -import "@uniswap/v3-periphery/contracts/libraries/Path.sol"; -import "../interfaces/IUniswapRouterV3.sol"; +import '@uniswap/v3-periphery/contracts/interfaces/ISwapRouter.sol'; +// import "@uniswap/v3-periphery/contracts/libraries/Path.sol"; library UniswapV3Utils { - using Path for bytes; + // using Path for bytes; // Swap along an encoded path using known amountIn function swap( @@ -14,14 +14,14 @@ library UniswapV3Utils { bytes memory _path, uint256 _amountIn ) internal returns (uint256 amountOut) { - IUniswapRouterV3.ExactInputParams memory params = IUniswapRouterV3.ExactInputParams({ + ISwapRouter.ExactInputParams memory params = ISwapRouter.ExactInputParams({ path: _path, recipient: address(this), deadline: block.timestamp, amountIn: _amountIn, amountOutMinimum: 0 }); - return IUniswapRouterV3(_router).exactInput(params); + return ISwapRouter(_router).exactInput(params); } // Swap along a token route using known fees and amountIn @@ -35,17 +35,17 @@ library UniswapV3Utils { } // Convert encoded path to token route - function pathToRoute(bytes memory _path) internal pure returns (address[] memory) { - uint256 numPools = _path.numPools(); - address[] memory route = new address[](numPools + 1); - for (uint256 i; i < numPools; i++) { - (address tokenA, address tokenB,) = _path.decodeFirstPool(); - route[i] = tokenA; - route[i + 1] = tokenB; - _path = _path.skipToken(); - } - return route; - } + // function pathToRoute(bytes memory _path) internal pure returns (address[] memory) { + // uint256 numPools = _path.numPools(); + // address[] memory route = new address[](numPools + 1); + // for (uint256 i; i < numPools; i++) { + // (address tokenA, address tokenB,) = _path.decodeFirstPool(); + // route[i] = tokenA; + // route[i + 1] = tokenB; + // _path = _path.skipToken(); + // } + // return route; + // } // Convert token route to encoded path // uint24 type for fees so path is packed tightly From 52dd9f4beadef12a9690243af8eb78ad3a26fe93 Mon Sep 17 00:00:00 2001 From: Magnus Brantheim Date: Sun, 10 Jul 2022 23:27:52 +0200 Subject: [PATCH 2/4] Add swap to dai using univ3 --- .openzeppelin/unknown-31337.json | 54 +++++++++++++++++----------- contracts/ReaperStrategyTarot.sol | 60 +++++++++++++++---------------- test/starter-test.js | 7 ++-- 3 files changed, 66 insertions(+), 55 deletions(-) diff --git a/.openzeppelin/unknown-31337.json b/.openzeppelin/unknown-31337.json index 974c749..59e6831 100644 --- a/.openzeppelin/unknown-31337.json +++ b/.openzeppelin/unknown-31337.json @@ -13,7 +13,7 @@ }, { "address": "0x5FC8d32690cc91D4c39d9d3abcBD16989F875707", - "txHash": "0x1b25bd0739cc1e0d1c850cfebd4273b79d2593c341e7e8a55df3259914468a3b", + "txHash": "0x33c7e6f75609eeb0038470ca7325cff1177aaa78f0c790249f1ce34acb5f653c", "kind": "uups" } ], @@ -927,9 +927,9 @@ } } }, - "08e568abb411d5c8caac9e482efebae786da16b3856a7277c4c7f7c78618d2b6": { + "64c9317176caa395b33386753ee6c32f44d1d5c4dddb99d9b853ea2ddbe07c0d": { "address": "0xDc64a140Aa3E981100a9becA4E685f962f0cF6C9", - "txHash": "0x799f1e3a8ca9299a2e8416665af5d650cdca69975d1e2fc52a22fbe632d00e39", + "txHash": "0x629e9e6157c8cba5605a958c529522777051ad10b87595b2c15b5b5cff9b0e52", "layout": { "storage": [ { @@ -1110,76 +1110,84 @@ "src": "contracts\\abstract\\ReaperBaseStrategyv4.sol:78" }, { - "label": "usedPools", + "label": "wantToDaiPath", "offset": 0, "slot": "312", + "type": "t_bytes_storage", + "contract": "ReaperStrategyTarot", + "src": "contracts\\ReaperStrategyTarot.sol:46" + }, + { + "label": "usedPools", + "offset": 0, + "slot": "313", "type": "t_struct(AddressSet)3096_storage", "contract": "ReaperStrategyTarot", - "src": "contracts\\ReaperStrategyTarot.sol:51" + "src": "contracts\\ReaperStrategyTarot.sol:59" }, { "label": "maxPools", "offset": 0, - "slot": "314", + "slot": "315", "type": "t_uint256", "contract": "ReaperStrategyTarot", - "src": "contracts\\ReaperStrategyTarot.sol:52" + "src": "contracts\\ReaperStrategyTarot.sol:60" }, { "label": "depositPool", "offset": 0, - "slot": "315", + "slot": "316", "type": "t_address", "contract": "ReaperStrategyTarot", - "src": "contracts\\ReaperStrategyTarot.sol:53" + "src": "contracts\\ReaperStrategyTarot.sol:61" }, { "label": "sharePriceSnapshot", "offset": 0, - "slot": "316", + "slot": "317", "type": "t_uint256", "contract": "ReaperStrategyTarot", - "src": "contracts\\ReaperStrategyTarot.sol:54" + "src": "contracts\\ReaperStrategyTarot.sol:62" }, { "label": "minProfitToChargeFees", "offset": 0, - "slot": "317", + "slot": "318", "type": "t_uint256", "contract": "ReaperStrategyTarot", - "src": "contracts\\ReaperStrategyTarot.sol:55" + "src": "contracts\\ReaperStrategyTarot.sol:63" }, { "label": "minWantToDepositOrWithdraw", "offset": 0, - "slot": "318", + "slot": "319", "type": "t_uint256", "contract": "ReaperStrategyTarot", - "src": "contracts\\ReaperStrategyTarot.sol:56" + "src": "contracts\\ReaperStrategyTarot.sol:64" }, { "label": "maxWantRemainingToRemovePool", "offset": 0, - "slot": "319", + "slot": "320", "type": "t_uint256", "contract": "ReaperStrategyTarot", - "src": "contracts\\ReaperStrategyTarot.sol:57" + "src": "contracts\\ReaperStrategyTarot.sol:65" }, { "label": "shouldHarvestOnDeposit", "offset": 0, - "slot": "320", + "slot": "321", "type": "t_bool", "contract": "ReaperStrategyTarot", - "src": "contracts\\ReaperStrategyTarot.sol:58" + "src": "contracts\\ReaperStrategyTarot.sol:66" }, { "label": "shouldHarvestOnWithdraw", "offset": 1, - "slot": "320", + "slot": "321", "type": "t_bool", "contract": "ReaperStrategyTarot", - "src": "contracts\\ReaperStrategyTarot.sol:59" + "src": "contracts\\ReaperStrategyTarot.sol:67" } ], "types": { @@ -1207,6 +1215,10 @@ "label": "bytes32", "numberOfBytes": "32" }, + "t_bytes_storage": { + "label": "bytes", + "numberOfBytes": "32" + }, "t_mapping(t_address,t_bool)": { "label": "mapping(address => bool)", "numberOfBytes": "32" diff --git a/contracts/ReaperStrategyTarot.sol b/contracts/ReaperStrategyTarot.sol index ccfddc2..54b2ec0 100644 --- a/contracts/ReaperStrategyTarot.sol +++ b/contracts/ReaperStrategyTarot.sol @@ -33,10 +33,15 @@ contract ReaperStrategyTarot is ReaperBaseStrategyv4 { /** * @dev Tokens Used: - * {WFTM} - Required for liquidity routing when doing swaps. - * {want} - Address of the token being lent + * {DAI} - Token for charging fees */ - address public constant WFTM = address(0x21be370D5312f44cB42ce377BC9b8a0cEF1A4C83); + address public constant DAI = address(0xDA10009cBd5D07dd0CeCc66161FC93D7c9000da1); + + /** + * @dev UniV3 paths: + * {wantToDaiPath} - Path for charging fees from profit + */ + bytes public wantToDaiPath; /** * @dev Tarot variables @@ -69,19 +74,21 @@ contract ReaperStrategyTarot is ReaperBaseStrategyv4 { address[] memory _feeRemitters, address[] memory _strategists, address[] memory _multisigRoles, - uint256 _initialPoolIndex, - address _want + address[] memory _wantToDaiRoute, + uint24[] memory _wantToDaiFee, + uint256 _initialPoolIndex ) public initializer { - __ReaperBaseStrategy_init(_vault, _want, _feeRemitters, _strategists, _multisigRoles); - sharePriceSnapshot = IVault(_vault).getPricePerFullShare(); + __ReaperBaseStrategy_init(_vault, _wantToDaiRoute[0], _feeRemitters, _strategists, _multisigRoles); + sharePriceSnapshot = IVault(_vault).getPricePerFullShare(); maxPools = 40; - minProfitToChargeFees = 1e16; + minProfitToChargeFees = 1e14; minWantToDepositOrWithdraw = 10; maxWantRemainingToRemovePool = 100; addUsedPool(_initialPoolIndex); depositPool = usedPools.at(0); // Guarantees depositPool is always a Tarot pool shouldHarvestOnDeposit = true; shouldHarvestOnWithdraw = true; + wantToDaiPath = UniswapV3Utils.routeToPath(_wantToDaiRoute, _wantToDaiFee); } function _adjustPosition(uint256 _debt) internal override { @@ -245,7 +252,6 @@ contract ReaperStrategyTarot is ReaperBaseStrategyv4 { uint256 profit = profitSinceHarvest(); if (profit >= minProfitToChargeFees) { uint256 fee = (profit * totalFee) / PERCENT_DIVISOR; - IERC20Upgradeable wftm = IERC20Upgradeable(WFTM); if (fee != 0) { uint256 wantBal = IERC20Upgradeable(want).balanceOf(address(this)); @@ -255,16 +261,18 @@ contract ReaperStrategyTarot is ReaperBaseStrategyv4 { fee = withdrawn + wantBal; } } - _swap(want, WFTM, fee); - uint256 wftmBalance = IERC20Upgradeable(WFTM).balanceOf(address(this)); - callerFee = (wftmBalance * callFee) / PERCENT_DIVISOR; - uint256 treasuryFeeToVault = (wftmBalance * treasuryFee) / PERCENT_DIVISOR; + _swapToDai(fee); + IERC20Upgradeable dai = IERC20Upgradeable(DAI); + uint256 daiBalance = dai.balanceOf(address(this)); + callerFee = (daiBalance * callFee) / PERCENT_DIVISOR; + uint256 treasuryFeeToVault = (daiBalance * treasuryFee) / PERCENT_DIVISOR; uint256 feeToStrategist = (treasuryFeeToVault * strategistFee) / PERCENT_DIVISOR; treasuryFeeToVault -= feeToStrategist; - wftm.safeTransfer(msg.sender, callerFee); - wftm.safeTransfer(treasury, treasuryFeeToVault); - wftm.safeTransfer(strategistRemitter, feeToStrategist); + + dai.safeTransfer(msg.sender, callerFee); + dai.safeTransfer(treasury, treasuryFeeToVault); + dai.safeTransfer(strategistRemitter, feeToStrategist); sharePriceSnapshot = IVault(vault).getPricePerFullShare(); } } @@ -273,26 +281,14 @@ contract ReaperStrategyTarot is ReaperBaseStrategyv4 { /** * @dev Helper function to swap tokens given {_from}, {_to} and {_amount} */ - function _swap( - address _from, - address _to, + function _swapToDai( uint256 _amount ) internal { - if (_from == _to || _amount == 0) { + if (_amount == 0) { return; } - - address[] memory path = new address[](2); - path[0] = _from; - path[1] = _to; - IERC20Upgradeable(_from).safeIncreaseAllowance(UNI_ROUTER, _amount); - // IUniswapV2Router02(UNI_ROUTER).swapExactTokensForTokensSupportingFeeOnTransferTokens( - // _amount, - // 0, - // path, - // address(this), - // block.timestamp - // ); + IERC20Upgradeable(want).safeIncreaseAllowance(UNI_ROUTER, _amount); + UniswapV3Utils.swap(UNI_ROUTER, wantToDaiPath, _amount); } /** diff --git a/test/starter-test.js b/test/starter-test.js index 8f941dc..d225478 100644 --- a/test/starter-test.js +++ b/test/starter-test.js @@ -56,6 +56,8 @@ describe('Vaults', function () { const daiAddress = '0xDA10009cBd5D07dd0CeCc66161FC93D7c9000da1'; const wantAddress = '0x4200000000000000000000000000000000000006'; + const wantToDaiPath = [wantAddress, daiAddress]; + const wantToDaiFee = [3000]; const wantHolderAddr = '0x428AB2BA90Eba0a4Be7aF34C9Ac451ab061AC010'; const strategistAddr = '0x1A20D7A31e5B3Bc5f02c8A146EF6f394502a10c4'; @@ -142,8 +144,9 @@ describe('Vaults', function () { [treasuryAddr, paymentSplitterAddress], [strategistAddr], [superAdminAddress, adminAddress, guardianAddress], + wantToDaiPath, + wantToDaiFee, poolIndex, - wantAddress, ], {kind: 'uups'}, ); @@ -258,7 +261,7 @@ describe('Vaults', function () { }); }); - xdescribe('Vault Tests', function () { + describe('Vault Tests', function () { it('should allow deposits and account for them correctly', async function () { const userBalance = await want.balanceOf(wantHolderAddr); const vaultBalance = await vault.totalAssets(); From 0f52282337dbe13af28c55fdc11ecac8d75b3ced Mon Sep 17 00:00:00 2001 From: Magnus Brantheim Date: Mon, 11 Jul 2022 11:32:31 +0200 Subject: [PATCH 3/4] Refactor to use USDC to charge fees, add minWantToSell to avoid reverts on swap --- .openzeppelin/unknown-31337.json | 34 +++++++++++-------- contracts/ReaperStrategyTarot.sol | 55 ++++++++++++++++++------------- hardhat.config.js | 13 ++------ test/starter-test.js | 18 +++++----- 4 files changed, 64 insertions(+), 56 deletions(-) diff --git a/.openzeppelin/unknown-31337.json b/.openzeppelin/unknown-31337.json index 59e6831..a332053 100644 --- a/.openzeppelin/unknown-31337.json +++ b/.openzeppelin/unknown-31337.json @@ -13,7 +13,7 @@ }, { "address": "0x5FC8d32690cc91D4c39d9d3abcBD16989F875707", - "txHash": "0x33c7e6f75609eeb0038470ca7325cff1177aaa78f0c790249f1ce34acb5f653c", + "txHash": "0x865a93f841d6c73187ddd877834c2c70d4282a05b7e970227c02c085a6ac4b25", "kind": "uups" } ], @@ -927,9 +927,9 @@ } } }, - "64c9317176caa395b33386753ee6c32f44d1d5c4dddb99d9b853ea2ddbe07c0d": { + "d589917e3a1c0e8f02bc1bda753219a3899683e50e7d2a7eb6cea546e952206a": { "address": "0xDc64a140Aa3E981100a9becA4E685f962f0cF6C9", - "txHash": "0x629e9e6157c8cba5605a958c529522777051ad10b87595b2c15b5b5cff9b0e52", + "txHash": "0x7234df4f543caa7d96e4cf6ed42869291cb235b5752e47870b2d5cc87646179b", "layout": { "storage": [ { @@ -1110,12 +1110,12 @@ "src": "contracts\\abstract\\ReaperBaseStrategyv4.sol:78" }, { - "label": "wantToDaiPath", + "label": "wantToUsdcRoute", "offset": 0, "slot": "312", "type": "t_bytes_storage", "contract": "ReaperStrategyTarot", - "src": "contracts\\ReaperStrategyTarot.sol:46" + "src": "contracts\\ReaperStrategyTarot.sol:44" }, { "label": "usedPools", @@ -1123,7 +1123,7 @@ "slot": "313", "type": "t_struct(AddressSet)3096_storage", "contract": "ReaperStrategyTarot", - "src": "contracts\\ReaperStrategyTarot.sol:59" + "src": "contracts\\ReaperStrategyTarot.sol:57" }, { "label": "maxPools", @@ -1131,7 +1131,7 @@ "slot": "315", "type": "t_uint256", "contract": "ReaperStrategyTarot", - "src": "contracts\\ReaperStrategyTarot.sol:60" + "src": "contracts\\ReaperStrategyTarot.sol:58" }, { "label": "depositPool", @@ -1139,7 +1139,7 @@ "slot": "316", "type": "t_address", "contract": "ReaperStrategyTarot", - "src": "contracts\\ReaperStrategyTarot.sol:61" + "src": "contracts\\ReaperStrategyTarot.sol:59" }, { "label": "sharePriceSnapshot", @@ -1147,7 +1147,7 @@ "slot": "317", "type": "t_uint256", "contract": "ReaperStrategyTarot", - "src": "contracts\\ReaperStrategyTarot.sol:62" + "src": "contracts\\ReaperStrategyTarot.sol:60" }, { "label": "minProfitToChargeFees", @@ -1155,7 +1155,7 @@ "slot": "318", "type": "t_uint256", "contract": "ReaperStrategyTarot", - "src": "contracts\\ReaperStrategyTarot.sol:63" + "src": "contracts\\ReaperStrategyTarot.sol:61" }, { "label": "minWantToDepositOrWithdraw", @@ -1163,7 +1163,7 @@ "slot": "319", "type": "t_uint256", "contract": "ReaperStrategyTarot", - "src": "contracts\\ReaperStrategyTarot.sol:64" + "src": "contracts\\ReaperStrategyTarot.sol:62" }, { "label": "maxWantRemainingToRemovePool", @@ -1171,7 +1171,7 @@ "slot": "320", "type": "t_uint256", "contract": "ReaperStrategyTarot", - "src": "contracts\\ReaperStrategyTarot.sol:65" + "src": "contracts\\ReaperStrategyTarot.sol:63" }, { "label": "shouldHarvestOnDeposit", @@ -1179,7 +1179,7 @@ "slot": "321", "type": "t_bool", "contract": "ReaperStrategyTarot", - "src": "contracts\\ReaperStrategyTarot.sol:66" + "src": "contracts\\ReaperStrategyTarot.sol:64" }, { "label": "shouldHarvestOnWithdraw", @@ -1187,6 +1187,14 @@ "slot": "321", "type": "t_bool", "contract": "ReaperStrategyTarot", + "src": "contracts\\ReaperStrategyTarot.sol:65" + }, + { + "label": "minWantToSell", + "offset": 0, + "slot": "322", + "type": "t_uint256", + "contract": "ReaperStrategyTarot", "src": "contracts\\ReaperStrategyTarot.sol:67" } ], diff --git a/contracts/ReaperStrategyTarot.sol b/contracts/ReaperStrategyTarot.sol index 54b2ec0..5b64eae 100644 --- a/contracts/ReaperStrategyTarot.sol +++ b/contracts/ReaperStrategyTarot.sol @@ -33,15 +33,15 @@ contract ReaperStrategyTarot is ReaperBaseStrategyv4 { /** * @dev Tokens Used: - * {DAI} - Token for charging fees + * {USDC} - Token for charging fees */ - address public constant DAI = address(0xDA10009cBd5D07dd0CeCc66161FC93D7c9000da1); + address public constant USDC = address(0x7F5c764cBc14f9669B88837ca1490cCa17c31607); /** - * @dev UniV3 paths: - * {wantToDaiPath} - Path for charging fees from profit + * @dev UniV3 routes: + * {wantToUsdcRoute} - Route for charging fees from profit */ - bytes public wantToDaiPath; + bytes public wantToUsdcRoute; /** * @dev Tarot variables @@ -64,6 +64,7 @@ contract ReaperStrategyTarot is ReaperBaseStrategyv4 { bool public shouldHarvestOnDeposit; bool public shouldHarvestOnWithdraw; uint256 public constant MAX_SLIPPAGE_TOLERANCE = 100; + uint256 public minWantToSell; /** * @dev Initializes the strategy. Sets parameters, saves routes, and gives allowances. @@ -74,21 +75,22 @@ contract ReaperStrategyTarot is ReaperBaseStrategyv4 { address[] memory _feeRemitters, address[] memory _strategists, address[] memory _multisigRoles, - address[] memory _wantToDaiRoute, + address[] memory _wantToUsdcRoute, uint24[] memory _wantToDaiFee, uint256 _initialPoolIndex ) public initializer { - __ReaperBaseStrategy_init(_vault, _wantToDaiRoute[0], _feeRemitters, _strategists, _multisigRoles); + __ReaperBaseStrategy_init(_vault, _wantToUsdcRoute[0], _feeRemitters, _strategists, _multisigRoles); sharePriceSnapshot = IVault(_vault).getPricePerFullShare(); maxPools = 40; - minProfitToChargeFees = 1e14; + minProfitToChargeFees = 1e9; minWantToDepositOrWithdraw = 10; maxWantRemainingToRemovePool = 100; + minWantToSell = 12 * 1e8; addUsedPool(_initialPoolIndex); depositPool = usedPools.at(0); // Guarantees depositPool is always a Tarot pool shouldHarvestOnDeposit = true; shouldHarvestOnWithdraw = true; - wantToDaiPath = UniswapV3Utils.routeToPath(_wantToDaiRoute, _wantToDaiFee); + wantToUsdcRoute = UniswapV3Utils.routeToPath(_wantToUsdcRoute, _wantToDaiFee); } function _adjustPosition(uint256 _debt) internal override { @@ -261,34 +263,33 @@ contract ReaperStrategyTarot is ReaperBaseStrategyv4 { fee = withdrawn + wantBal; } } - _swapToDai(fee); - IERC20Upgradeable dai = IERC20Upgradeable(DAI); - uint256 daiBalance = dai.balanceOf(address(this)); - callerFee = (daiBalance * callFee) / PERCENT_DIVISOR; - uint256 treasuryFeeToVault = (daiBalance * treasuryFee) / PERCENT_DIVISOR; + _swapToUsdc(fee); + IERC20Upgradeable usdc = IERC20Upgradeable(USDC); + uint256 usdcBalance = usdc.balanceOf(address(this)); + callerFee = (usdcBalance * callFee) / PERCENT_DIVISOR; + uint256 treasuryFeeToVault = (usdcBalance * treasuryFee) / PERCENT_DIVISOR; uint256 feeToStrategist = (treasuryFeeToVault * strategistFee) / PERCENT_DIVISOR; treasuryFeeToVault -= feeToStrategist; - dai.safeTransfer(msg.sender, callerFee); - dai.safeTransfer(treasury, treasuryFeeToVault); - dai.safeTransfer(strategistRemitter, feeToStrategist); + usdc.safeTransfer(msg.sender, callerFee); + usdc.safeTransfer(treasury, treasuryFeeToVault); + usdc.safeTransfer(strategistRemitter, feeToStrategist); sharePriceSnapshot = IVault(vault).getPricePerFullShare(); } } } /** - * @dev Helper function to swap tokens given {_from}, {_to} and {_amount} + * @dev Helper function to swap want to USDC */ - function _swapToDai( + function _swapToUsdc( uint256 _amount ) internal { - if (_amount == 0) { - return; + if (_amount >= minWantToSell) { + IERC20Upgradeable(want).safeIncreaseAllowance(UNI_ROUTER, _amount); + UniswapV3Utils.swap(UNI_ROUTER, wantToUsdcRoute, _amount); } - IERC20Upgradeable(want).safeIncreaseAllowance(UNI_ROUTER, _amount); - UniswapV3Utils.swap(UNI_ROUTER, wantToDaiPath, _amount); } /** @@ -551,4 +552,12 @@ contract ReaperStrategyTarot is ReaperBaseStrategyv4 { _atLeastRole(STRATEGIST); shouldHarvestOnWithdraw = _shouldHarvestOnWithdraw; } + + /** + * @dev Sets the minimum want that will be sold (too little causes revert from Uniswap) + */ + function setMinWantToSell(uint256 _minWantToSell) external { + _atLeastRole(STRATEGIST); + minWantToSell = _minWantToSell; + } } \ No newline at end of file diff --git a/hardhat.config.js b/hardhat.config.js index a4238cb..d7cda8f 100644 --- a/hardhat.config.js +++ b/hardhat.config.js @@ -9,7 +9,6 @@ require('solidity-coverage'); require('@openzeppelin/hardhat-upgrades'); const PRIVATE_KEY = process.env.DEPLOYER_PRIVATE_KEY; -const FTMSCAN_KEY = process.env.FTMSCAN_API_KEY; /** * @type import('hardhat/config').HardhatUserConfig @@ -51,18 +50,10 @@ module.exports = { }, networks: { mainnet: { - url: `https://rpc.ftm.tools`, - chainId: 250, + url: `https://mainnet.optimism.io`, + chainId: 10, accounts: [`0x${PRIVATE_KEY}`], }, - testnet: { - url: `https://rpcapi-tracing.testnet.fantom.network`, - chainId: 4002, - accounts: [`0x${PRIVATE_KEY}`], - }, - }, - etherscan: { - apiKey: FTMSCAN_KEY, }, mocha: { timeout: 1200000, diff --git a/test/starter-test.js b/test/starter-test.js index d225478..70db69d 100644 --- a/test/starter-test.js +++ b/test/starter-test.js @@ -44,7 +44,7 @@ describe('Vaults', function () { let Want; let want; - let dai; + let usdc; const treasuryAddr = '0x1E71AEE6081f62053123140aacC7a06021D77348'; const paymentSplitterAddress = '0x1E71AEE6081f62053123140aacC7a06021D77348'; @@ -54,10 +54,10 @@ describe('Vaults', function () { const guardianAddress = '0xf20E25f2AB644C8ecBFc992a6829478a85A98F2c'; const maintainerAddress = '0x81876677843D00a7D792E1617459aC2E93202576'; - const daiAddress = '0xDA10009cBd5D07dd0CeCc66161FC93D7c9000da1'; + const usdcAddress = '0x7F5c764cBc14f9669B88837ca1490cCa17c31607'; const wantAddress = '0x4200000000000000000000000000000000000006'; - const wantToDaiPath = [wantAddress, daiAddress]; - const wantToDaiFee = [3000]; + const wantToUsdcPath = [wantAddress, usdcAddress]; + const wantToUsdcFee = [500]; const wantHolderAddr = '0x428AB2BA90Eba0a4Be7aF34C9Ac451ab061AC010'; const strategistAddr = '0x1A20D7A31e5B3Bc5f02c8A146EF6f394502a10c4'; @@ -144,8 +144,8 @@ describe('Vaults', function () { [treasuryAddr, paymentSplitterAddress], [strategistAddr], [superAdminAddress, adminAddress, guardianAddress], - wantToDaiPath, - wantToDaiFee, + wantToUsdcPath, + wantToUsdcFee, poolIndex, ], {kind: 'uups'}, @@ -153,7 +153,7 @@ describe('Vaults', function () { await strategy.deployed(); await vault.addStrategy(strategy.address, 9000); want = await Want.attach(wantAddress); - dai = await Want.attach(daiAddress); + usdc = await Want.attach(usdcAddress); //approving LP token and vault share spend await want.connect(wantHolder).approve(vault.address, ethers.constants.MaxUint256); @@ -727,9 +727,9 @@ describe('Vaults', function () { const predictedCallerFee = await readOnlyStrat.callStatic.harvest(); console.log(`predicted caller fee ${ethers.utils.formatEther(predictedCallerFee)}`); - const daiBalBefore = await dai.balanceOf(owner.address); + const daiBalBefore = await usdc.balanceOf(owner.address); await strategy.harvest(); - const daiBalAfter = await dai.balanceOf(owner.address); + const daiBalAfter = await usdc.balanceOf(owner.address); const daiBalDifference = daiBalAfter.sub(daiBalBefore); console.log(`actual caller fee ${ethers.utils.formatEther(daiBalDifference)}`); }); From 2618287c316decac74de6ca9605459737c3882e4 Mon Sep 17 00:00:00 2001 From: Magnus Brantheim Date: Mon, 11 Jul 2022 11:48:06 +0200 Subject: [PATCH 4/4] Update scripts --- scripts/addKeepers.js | 30 +++++++++++++++--------------- scripts/addStrategy.js | 6 +++--- scripts/approve-vault.js | 4 ++-- scripts/deploy/1-vault.js | 16 +++++++++------- scripts/deploy/2-strategy.js | 29 ++++++++++++++++++----------- scripts/upgradeProxy.js | 5 ++--- 6 files changed, 49 insertions(+), 41 deletions(-) diff --git a/scripts/addKeepers.js b/scripts/addKeepers.js index d34dc0f..302c6aa 100644 --- a/scripts/addKeepers.js +++ b/scripts/addKeepers.js @@ -1,23 +1,23 @@ async function main() { - const strategyAddress = '0xCF266AF4b6688352fFD08BAeA9b58ff89ff09A3a'; + const strategyAddress = ''; const Strategy = await ethers.getContractFactory('ReaperStrategyGeist'); const strategy = Strategy.attach(strategyAddress); const keeperAddress = [ - // '0x55a078AFC2e20C8c20d1aa4420710d827Ee494d4', - // '0x5241F63D0C1f2970c45234a0F5b345036117E3C2', - // '0xf58d534290Ce9fc4Ea639B8b9eE238Fe83d2efA6', - // '0x5318250BD0b44D1740f47a5b6BE4F7fD5042682D', - // '0x33D6cB7E91C62Dd6980F16D61e0cfae082CaBFCA', - // '0x51263D56ec81B5e823e34d7665A1F505C327b014', - // '0x87A5AfC8cdDa71B5054C698366E97DB2F3C2BC2f', - // '0xe0268Aa6d55FfE1AA7A77587e56784e5b29004A2', - // '0x34Df14D42988e4Dc622e37dc318e70429336B6c5', - // '0x73C882796Ea481fe0A2B8DE499d95e60ff971663', - // '0x36a63324edFc157bE22CF63A6Bf1C3B49a0E72C0', - // '0x9a2AdcbFb972e0EC2946A342f46895702930064F', - // '0x7B540a4D24C906E5fB3d3EcD0Bb7B1aEd3823897', - // '0x8456a746e09A18F9187E5babEe6C60211CA728D1', + '0x55a078AFC2e20C8c20d1aa4420710d827Ee494d4', + '0x5241F63D0C1f2970c45234a0F5b345036117E3C2', + '0xf58d534290Ce9fc4Ea639B8b9eE238Fe83d2efA6', + '0x5318250BD0b44D1740f47a5b6BE4F7fD5042682D', + '0x33D6cB7E91C62Dd6980F16D61e0cfae082CaBFCA', + '0x51263D56ec81B5e823e34d7665A1F505C327b014', + '0x87A5AfC8cdDa71B5054C698366E97DB2F3C2BC2f', + '0xe0268Aa6d55FfE1AA7A77587e56784e5b29004A2', + '0x34Df14D42988e4Dc622e37dc318e70429336B6c5', + '0x73C882796Ea481fe0A2B8DE499d95e60ff971663', + '0x36a63324edFc157bE22CF63A6Bf1C3B49a0E72C0', + '0x9a2AdcbFb972e0EC2946A342f46895702930064F', + '0x7B540a4D24C906E5fB3d3EcD0Bb7B1aEd3823897', + '0x8456a746e09A18F9187E5babEe6C60211CA728D1', '0xd21E0fE4ba0379Ec8DF6263795c8120414Acd0A3', '0x3b410908e71ee04e7de2a87f8f9003afe6c1c7ce', ]; diff --git a/scripts/addStrategy.js b/scripts/addStrategy.js index d6cca69..485f051 100644 --- a/scripts/addStrategy.js +++ b/scripts/addStrategy.js @@ -1,10 +1,10 @@ async function main() { - const vaultAddress = '0xa9A9dB466685F977F9ECEe347958bcef90498177'; + const vaultAddress = ''; const Vault = await ethers.getContractFactory('ReaperVaultV2'); const vault = Vault.attach(vaultAddress); - const strategyAddress = '0xCF266AF4b6688352fFD08BAeA9b58ff89ff09A3a'; - const strategyAllocation = 9000; + const strategyAddress = ''; + const strategyAllocation = 9990; await vault.addStrategy(strategyAddress, strategyAllocation); console.log('Strategy added!'); } diff --git a/scripts/approve-vault.js b/scripts/approve-vault.js index 43c3a1a..7130671 100644 --- a/scripts/approve-vault.js +++ b/scripts/approve-vault.js @@ -1,7 +1,7 @@ async function main() { - const vaultAddress = '0xa6313302B3CeFF2727f19AAA30d7240d5B3CD9CD'; + const vaultAddress = ''; const ERC20 = await ethers.getContractFactory('@openzeppelin/contracts/token/ERC20/ERC20.sol:ERC20'); - const wantAddress = '0x21be370d5312f44cb42ce377bc9b8a0cef1a4c83'; + const wantAddress = ''; const want = await ERC20.attach(wantAddress); await want.approve(vaultAddress, ethers.utils.parseEther('1000')); console.log('want approved'); diff --git a/scripts/deploy/1-vault.js b/scripts/deploy/1-vault.js index d1272a8..04cc490 100644 --- a/scripts/deploy/1-vault.js +++ b/scripts/deploy/1-vault.js @@ -1,17 +1,19 @@ async function main() { const Vault = await ethers.getContractFactory('ReaperVaultV2'); - const wantAddress = '0x049d68029688eAbF473097a2fC38ef61633A3C7A'; - const tokenName = 'fUSDT Crypt'; - const tokenSymbol = 'rfUSDT'; - const tvlCap = ethers.utils.parseEther('1000'); + const wantAddress = '0x4200000000000000000000000000000000000006'; + const tokenName = 'ETH Crypt'; + const tokenSymbol = 'rfETH'; + const tvlCap = ethers.utils.parseEther('1'); const strategist1 = '0x1E71AEE6081f62053123140aacC7a06021D77348'; const strategist2 = '0x81876677843D00a7D792E1617459aC2E93202576'; const strategist3 = '0x1A20D7A31e5B3Bc5f02c8A146EF6f394502a10c4'; - const superAdmin = '0x04C710a1E8a738CDf7cAD3a52Ba77A784C35d8CE'; - const admin = '0x539eF36C804e4D735d8cAb69e8e441c12d4B88E0'; - const guardian = '0xf20E25f2AB644C8ecBFc992a6829478a85A98F2c'; + + const superAdmin = '0x9BC776dBb134Ef9D7014dB1823Cd755Ac5015203'; + const admin = '0xeb9C9b785aA7818B2EBC8f9842926c4B9f707e4B'; + const guardian = '0xb0C9D5851deF8A2Aac4A23031CA2610f8C3483F9'; + const strategists = [strategist1, strategist2, strategist3]; const multisigRoles = [superAdmin, admin, guardian]; diff --git a/scripts/deploy/2-strategy.js b/scripts/deploy/2-strategy.js index 6ed891c..115bae9 100644 --- a/scripts/deploy/2-strategy.js +++ b/scripts/deploy/2-strategy.js @@ -1,19 +1,26 @@ const hre = require('hardhat'); async function main() { - const vaultAddress = '0xa9A9dB466685F977F9ECEe347958bcef90498177'; + const vaultAddress = ''; const Strategy = await ethers.getContractFactory('ReaperStrategyGeist'); - const treasuryAddress = '0x0e7c5313E9BB80b654734d9b7aB1FB01468deE3b'; - const paymentSplitterAddress = '0x63cbd4134c2253041F370472c130e92daE4Ff174'; + + const treasuryAddress = '0xeb9C9b785aA7818B2EBC8f9842926c4B9f707e4B'; + const paymentSplitterAddress = '0x2b394b228908fb7DAcafF5F340f1b442a39B056C'; + const strategist1 = '0x1E71AEE6081f62053123140aacC7a06021D77348'; const strategist2 = '0x81876677843D00a7D792E1617459aC2E93202576'; const strategist3 = '0x1A20D7A31e5B3Bc5f02c8A146EF6f394502a10c4'; - const superAdmin = '0x04C710a1E8a738CDf7cAD3a52Ba77A784C35d8CE'; - const admin = '0x539eF36C804e4D735d8cAb69e8e441c12d4B88E0'; - const guardian = '0xf20E25f2AB644C8ecBFc992a6829478a85A98F2c'; - const gWant = '0x940f41f0ec9ba1a34cf001cc03347ac092f5f6b5'; - const targetLtv = 7800; + + const superAdmin = '0x9BC776dBb134Ef9D7014dB1823Cd755Ac5015203'; + const admin = '0xeb9C9b785aA7818B2EBC8f9842926c4B9f707e4B'; + const guardian = '0xb0C9D5851deF8A2Aac4A23031CA2610f8C3483F9'; + + const usdcAddress = '0x7F5c764cBc14f9669B88837ca1490cCa17c31607'; + const wantAddress = '0x4200000000000000000000000000000000000006'; + const wantToUsdcPath = [wantAddress, usdcAddress]; + const wantToUsdcFee = [500]; + const poolIndex = 5; const strategy = await hre.upgrades.deployProxy( Strategy, @@ -22,9 +29,9 @@ async function main() { [treasuryAddress, paymentSplitterAddress], [strategist1, strategist2, strategist3], [superAdmin, admin, guardian], - gWant, - targetLtv, - targetLtv + 40, + wantToUsdcPath, + wantToUsdcFee, + poolIndex, ], {kind: 'uups', timeout: 0}, ); diff --git a/scripts/upgradeProxy.js b/scripts/upgradeProxy.js index 524d128..e9dab6f 100644 --- a/scripts/upgradeProxy.js +++ b/scripts/upgradeProxy.js @@ -1,7 +1,6 @@ async function main() { - const stratFactory = await ethers.getContractFactory('ReaperAutoCompoundSolidexFarmer'); - const stratContract = await hre.upgrades.upgradeProxy('0x3630a380F320EA77284Ed03D09B4C73D1351C41e', stratFactory, { - call: {fn: 'postUpgradeLP0Allowance'}, + const stratFactory = await ethers.getContractFactory('ReaperStrategyTarot'); + await hre.upgrades.upgradeProxy('', stratFactory, { timeout: 0, }); console.log('Strategy upgraded!');