From e7869b26fe169e7593d297f4277a4c0d1aba3c9f Mon Sep 17 00:00:00 2001 From: cookiesanddudes Date: Sat, 29 Oct 2022 15:58:36 +0100 Subject: [PATCH 01/47] Convex adaptor initialization --- src/interfaces/external/IBooster.sol | 16 ++ src/interfaces/external/ICurvePool.sol | 15 ++ src/interfaces/external/IRewardPool.sol | 12 ++ src/modules/adaptors/Convex/ConvexAdaptor.sol | 143 ++++++++++++++++++ test/testAdaptors/Convex.t.sol | 93 ++++++++++++ 5 files changed, 279 insertions(+) create mode 100644 src/interfaces/external/IBooster.sol create mode 100644 src/interfaces/external/ICurvePool.sol create mode 100644 src/interfaces/external/IRewardPool.sol create mode 100644 src/modules/adaptors/Convex/ConvexAdaptor.sol create mode 100644 test/testAdaptors/Convex.t.sol diff --git a/src/interfaces/external/IBooster.sol b/src/interfaces/external/IBooster.sol new file mode 100644 index 00000000..ac45665e --- /dev/null +++ b/src/interfaces/external/IBooster.sol @@ -0,0 +1,16 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.16; + +// Convex IBooster interface +interface IBooster { + function owner() external view returns(address); + function setVoteDelegate(address _voteDelegate) external; + function vote(uint256 _voteId, address _votingAddress, bool _support) external returns(bool); + function voteGaugeWeight(address[] calldata _gauge, uint256[] calldata _weight ) external returns(bool); + function poolInfo(uint256 _pid) external returns(address _lptoken, address _token, address _gauge, address _crvRewards, address _stash, bool _shutdown); + + function withdrawTo(uint256 _pid, uint256 _amount, address _to) external returns(bool); + function withdraw(uint256 _pid, uint256 _amount) external returns(bool); + function deposit(uint256 _pid, uint256 _amount, bool _stake) external returns(bool); + function poolLength() external view returns (uint256); +} \ No newline at end of file diff --git a/src/interfaces/external/ICurvePool.sol b/src/interfaces/external/ICurvePool.sol new file mode 100644 index 00000000..4cf6fead --- /dev/null +++ b/src/interfaces/external/ICurvePool.sol @@ -0,0 +1,15 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.16; + +// ICurvePool interface +interface ICurvePool { + function coins(uint256) external view returns (address); + function balances(uint256) external view returns (address); + function get_virtual_price(uint256) external view returns (address); + + function add_liquidity(uint256[3] calldata amounts, uint256 min_mint_amount) external; + function exchange(int128 i, int128 j, uint256 dx, uint256 min_dy) external; + function remove_liquidity(uint256 _amount, uint256[3] calldata min_amounts) external; + function remove_liquidity_imbalance(uint256[3] calldata amounts, uint256 max_burn_amount) external; + function remove_liquidity_one_coin(uint256 _token_amount, int128 i, uint256 min_amount) external; +} \ No newline at end of file diff --git a/src/interfaces/external/IRewardPool.sol b/src/interfaces/external/IRewardPool.sol new file mode 100644 index 00000000..65f3eb45 --- /dev/null +++ b/src/interfaces/external/IRewardPool.sol @@ -0,0 +1,12 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.16; + +// Convex IRewardPool interface +interface IRewardPool { + function getReward() external returns(bool); + function getReward(address _account, bool _claimExtras) external returns(bool); + function withdrawAllAndUnwrap(bool claim) external; + function withdraw(uint256 amount, bool claim) external; + function stake(uint256 _amount) external; + function rewardPerToken() external view returns (uint256); +} \ No newline at end of file diff --git a/src/modules/adaptors/Convex/ConvexAdaptor.sol b/src/modules/adaptors/Convex/ConvexAdaptor.sol new file mode 100644 index 00000000..5c1480b4 --- /dev/null +++ b/src/modules/adaptors/Convex/ConvexAdaptor.sol @@ -0,0 +1,143 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity 0.8.16; + +import { BaseAdaptor, ERC20, SafeERC20, Cellar, PriceRouter, Registry, Math } from "src/modules/adaptors/BaseAdaptor.sol"; +import { SafeCast } from "@openzeppelin/contracts/utils/math/SafeCast.sol"; +import { Address } from "@openzeppelin/contracts/utils/Address.sol"; +import { IBooster } from "src/interfaces/external/IBooster.sol"; + +import { IRewardPool } from "src/interfaces/external/IRewardPool.sol"; +import { ICurvePool } from "src/interfaces/external/ICurvePool.sol"; + + +/** + * @title Convex V3 Adaptor + * @notice Allows Cellars to interact with Convex Positions. + * @author + */ +contract ConvexAdaptor is BaseAdaptor { + using SafeERC20 for ERC20; + using Math for uint256; + using SafeCast for uint256; + using Address for address; + + //==================== Adaptor Data Specification ==================== + // adaptorData = abi.encode(uint256 pid, ERC20 lpToken) + // Where: + // - pid is the pool id of the convex pool + // - lpToken is the lp token concerned by the pool + //==================================================================== + + //============================================ Global Functions =========================================== + /** + * @dev Identifier unique to this adaptor for a shared registry. + * Normally the identifier would just be the address of this contract, but this + * Identifier is needed during Cellar Delegate Call Operations, so getting the address + * of the adaptor is more difficult. + */ + function identifier() public pure override returns (bytes32) { + return keccak256(abi.encode("Convex Adaptor V 0.0")); + } + + /** + * @notice The Booster contract on Ethereum Mainnet. + */ + function booster() internal pure returns (IBooster) { + return IBooster(0xF403C135812408BFbE8713b5A23a04b3D48AAE31); + } + + /** + * @notice The Curve 3pool contract on Ethereum Mainnet. + */ + function curvePool() internal pure returns (ICurvePool) { + return ICurvePool(0xbEbc44782C7dB0a1A60Cb6fe97d0b483032FF1C7); + } + + /** + @notice Attempted to deposit into convex but failed + */ + error ConvexAdaptor_DepositFailed(); + + //============================================ Implement Base Functions =========================================== + /** + * @notice User deposits are NOT allowed into this position. + */ + function deposit( + uint256 amount, + bytes memory adaptorData, + bytes memory + ) public override { + (uint256 pid, ERC20 lpToken) = abi.decode(adaptorData, (uint256, ERC20)); + + lpToken.safeApprove(address(booster()), amount); + + // always assume we are staking + if(!(booster()).deposit(pid, amount, true)) { + revert ConvexAdaptor_DepositFailed(); + } + } + + /** + * @notice User withdraws are NOT allowed from this position. + */ + function withdraw( + uint256 amount, + address receiver, + bytes memory adaptorData, + bytes memory + ) public override { + // Run external receiver check. + _externalReceiverCheck(receiver); + + (uint256 pid, ) = abi.decode(adaptorData, (uint256, ERC20)); + + // withdraw from this address to the receiver in parameter + (booster()).withdrawTo(pid, amount, receiver); + } + + /** + * @notice User withdraws are not allowed so this position must return 0 for withdrawableFrom. + */ + function withdrawableFrom(bytes memory, bytes memory) public pure override returns (uint256) { + // TODO + return 0; + } + + /** + * @notice Calculates this positions LP tokens underlying worth in terms of `token0`. + */ + function balanceOf(bytes memory adaptorData) public view override returns (uint256) { + // TODO + (, ERC20 lpToken) = abi.decode(adaptorData, (uint256, ERC20)); + return lpToken.balanceOf(address(this)); + } + + /** + * @notice Returns `coins(0)` + */ + function assetOf(bytes memory adaptorData) public view override returns (ERC20) { + (, ERC20 lpToken) = abi.decode(adaptorData, (uint256, ERC20)); + return ERC20((curvePool()).coins(0)); + } + + //============================================ Strategist Functions =========================================== + + function claim(bytes memory adaptorData) public pure returns (uint256) { + // TODO + return 0; + } + + + //============================================ Helper Functions ============================================ + /** + * @notice Calculates the square root of the input. + */ + function _sqrt(uint256 _x) internal pure returns (uint256 y) { + uint256 z = (_x + 1) / 2; + y = _x; + while (z < y) { + y = z; + z = (_x / z + z) / 2; + } + } +} diff --git a/test/testAdaptors/Convex.t.sol b/test/testAdaptors/Convex.t.sol new file mode 100644 index 00000000..6d4bb17b --- /dev/null +++ b/test/testAdaptors/Convex.t.sol @@ -0,0 +1,93 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity 0.8.16; + +import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; +import { MockCellar, Cellar, ERC4626, ERC20 } from "src/mocks/MockCellar.sol"; +import { ConvexAdaptor } from "src/modules/adaptors/Convex/ConvexAdaptor.sol"; +import { BaseAdaptor } from "src/modules/adaptors/BaseAdaptor.sol"; +import { IBooster } from "src/interfaces/external/IBooster.sol"; +import { Registry } from "src/Registry.sol"; +import { PriceRouter } from "src/modules/price-router/PriceRouter.sol"; +import { Denominations } from "@chainlink/contracts/src/v0.8/Denominations.sol"; +import { ERC20Adaptor } from "src/modules/adaptors/ERC20Adaptor.sol"; +import { SwapRouter, IUniswapV2Router, IUniswapV3Router } from "src/modules/swap-router/SwapRouter.sol"; + + +import { Test, stdStorage, console, StdStorage, stdError } from "@forge-std/Test.sol"; +import { Math } from "src/utils/Math.sol"; + +contract CellarConvexTest is Test { + using SafeERC20 for ERC20; + using Math for uint256; + using stdStorage for StdStorage; + + ConvexAdaptor private convexAdaptor; + ERC20Adaptor private erc20Adaptor; + MockCellar private cellar; + PriceRouter private priceRouter; + Registry private registry; + SwapRouter private swapRouter; + + address private immutable strategist = vm.addr(0xBEEF); + + ERC20 private WETH = ERC20(0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2); + ERC20 private CVX = ERC20(0x4e3FBD56CD56c3e72c1403e103b45Db9da5B9D2B); + + ERC20 private LP3CRV = ERC20(0x6c3F90f043a72FA612cbac8115EE7e52BDe6E490); + ERC20 private USDC = ERC20(0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48); + ERC20 private DAI = ERC20(0x6B175474E89094C44Da98b954EedeAC495271d0F); + ERC20 private USDT = ERC20(0xdAC17F958D2ee523a2206206994597C13D831ec7); + + IBooster private booster = IBooster(0xF403C135812408BFbE8713b5A23a04b3D48AAE31); + + uint32 private lp3crvPosition; + + function setUp() external { + + convexAdaptor = new ConvexAdaptor(); + erc20Adaptor = new ERC20Adaptor(); + priceRouter = new PriceRouter(); + + registry = new Registry(address(this), address(swapRouter), address(priceRouter)); + + priceRouter.addAsset(USDC, 0, 0, false, 0); + priceRouter.addAsset(USDT, 0, 0, false, 0); + priceRouter.addAsset(DAI, 0, 0, false, 0); + + // Setup Cellar: + // Cellar positions array. + uint32[] memory positions = new uint32[](1); + + // Add adaptors and positions to the registry. + registry.trustAdaptor(address(convexAdaptor), 0, 0); + + lp3crvPosition = registry.trustPosition(address(convexAdaptor), false, abi.encode(uint256(9), address(DAI)), 0, 0); + + positions[0] = lp3crvPosition; + + bytes[] memory positionConfigs = new bytes[](1); + + cellar = new MockCellar(registry, DAI, positions, positionConfigs, "Convex Cellar", "CONVEX-CLR", strategist); + + vm.label(address(cellar), "cellar"); + vm.label(strategist, "strategist"); + + cellar.setupAdaptor(address(convexAdaptor)); + + USDC.safeApprove(address(cellar), type(uint256).max); + + // Manipulate test contracts storage so that minimum shareLockPeriod is zero blocks. + stdstore.target(address(cellar)).sig(cellar.shareLockPeriod.selector).checked_write(uint256(0)); + } + + function testDeposit() external { + uint256 amount = 100e18; + deal(address(DAI), address(this), amount); + assertEq( + DAI.balanceOf(address(this)), + amount, + "Should be able to deal some tokens" + ); + } + +} From 8a66a48590af52962de107bdea9c8635a7fca90f Mon Sep 17 00:00:00 2001 From: cookiesanddudes Date: Sat, 29 Oct 2022 16:07:19 +0100 Subject: [PATCH 02/47] rename test faucet --- test/testAdaptors/Convex.t.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/testAdaptors/Convex.t.sol b/test/testAdaptors/Convex.t.sol index 6d4bb17b..bf62d86f 100644 --- a/test/testAdaptors/Convex.t.sol +++ b/test/testAdaptors/Convex.t.sol @@ -80,7 +80,7 @@ contract CellarConvexTest is Test { stdstore.target(address(cellar)).sig(cellar.shareLockPeriod.selector).checked_write(uint256(0)); } - function testDeposit() external { + function testFaucet() external { uint256 amount = 100e18; deal(address(DAI), address(this), amount); assertEq( From f60172bc39e0dd4b8b730a42f0c9b3d60b935aa8 Mon Sep 17 00:00:00 2001 From: cookiesanddudes Date: Sat, 29 Oct 2022 16:26:06 +0100 Subject: [PATCH 03/47] Curve3PoolAdaptor --- src/modules/adaptors/Convex/ConvexAdaptor.sol | 2 +- .../adaptors/Convex/Curve3PoolAdaptor.sol | 135 ++++++++++++++++++ 2 files changed, 136 insertions(+), 1 deletion(-) create mode 100644 src/modules/adaptors/Convex/Curve3PoolAdaptor.sol diff --git a/src/modules/adaptors/Convex/ConvexAdaptor.sol b/src/modules/adaptors/Convex/ConvexAdaptor.sol index 5c1480b4..c23ef1f3 100644 --- a/src/modules/adaptors/Convex/ConvexAdaptor.sol +++ b/src/modules/adaptors/Convex/ConvexAdaptor.sol @@ -11,7 +11,7 @@ import { ICurvePool } from "src/interfaces/external/ICurvePool.sol"; /** - * @title Convex V3 Adaptor + * @title Convex Adaptor * @notice Allows Cellars to interact with Convex Positions. * @author */ diff --git a/src/modules/adaptors/Convex/Curve3PoolAdaptor.sol b/src/modules/adaptors/Convex/Curve3PoolAdaptor.sol new file mode 100644 index 00000000..f0b7a39a --- /dev/null +++ b/src/modules/adaptors/Convex/Curve3PoolAdaptor.sol @@ -0,0 +1,135 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity 0.8.16; + +import { BaseAdaptor, ERC20, SafeERC20, Cellar, PriceRouter, Registry, Math } from "src/modules/adaptors/BaseAdaptor.sol"; +import { SafeCast } from "@openzeppelin/contracts/utils/math/SafeCast.sol"; +import { Address } from "@openzeppelin/contracts/utils/Address.sol"; +import { IBooster } from "src/interfaces/external/IBooster.sol"; +import { ICurvePool } from "src/interfaces/external/ICurvePool.sol"; + + +/** + * @title Curve 3 Pool Adaptor + * @notice Allows Cellars to interact with Curve Positions. + * @author + */ +contract Curve3PoolAdaptor is BaseAdaptor { + using SafeERC20 for ERC20; + using Math for uint256; + using SafeCast for uint256; + using Address for address; + + //==================== Adaptor Data Specification ==================== + // adaptorData = abi.encode(ICurvePool curvePool, address lpToken) + // Where: + // - curvePool is the pool concerned by the position + // - lpToken is the lp generated by the pool (in old curve contracts, + // it is not available as a public method in the pool) + //==================================================================== + + //============================================ Global Functions =========================================== + /** + * @dev Identifier unique to this adaptor for a shared registry. + * Normally the identifier would just be the address of this contract, but this + * Identifier is needed during Cellar Delegate Call Operations, so getting the address + * of the adaptor is more difficult. + */ + function identifier() public pure override returns (bytes32) { + return keccak256(abi.encode("Curve 3Pool Adaptor V 0.0")); + } + + /** + @notice Attempted to deposit into Curve but failed + */ + error CurveAdaptor_DepositFailed(); + + //============================================ Implement Base Functions =========================================== + /** + * @notice User deposits are NOT allowed into this position. + */ + function deposit( + uint256, + bytes memory, + bytes memory + ) public pure override { + revert BaseAdaptor__UserDepositsNotAllowed(); + } + + /** + * @notice User withdraws are NOT allowed from this position. + */ + function withdraw( + uint256, + address, + bytes memory, + bytes memory + ) public pure override { + revert BaseAdaptor__UserWithdrawsNotAllowed(); + } + + + /** + * @notice User withdraws are not allowed so this position must return 0 for withdrawableFrom. + */ + function withdrawableFrom(bytes memory, bytes memory) public pure override returns (uint256) { + // TODO + return 0; + } + + /** + * @notice Calculates this positions LP tokens underlying worth in terms of `token0`. + */ + function balanceOf(bytes memory adaptorData) public view override returns (uint256) { + // TODO + (, ERC20 lpToken) = abi.decode(adaptorData, (uint256, ERC20)); + return lpToken.balanceOf(address(this)); + } + + /** + * @notice Returns `coins(0)` + */ + function assetOf(bytes memory adaptorData) public view override returns (ERC20) { + (, ERC20 lpToken) = abi.decode(adaptorData, (uint256, ERC20)); + return ERC20((curvePool()).coins(0)); + } + + //============================================ Strategist Functions =========================================== + + function claim(bytes memory adaptorData) public pure returns (uint256) { + // TODO + return 0; + } + + function openPosition(bytes memory adaptorData) public pure returns (uint256) { + // TODO + return 0; + } + + function closePosition(bytes memory adaptorData) public pure returns (uint256) { + // TODO + return 0; + } + + function takeFromPosition(bytes memory adaptorData) public pure returns (uint256) { + // TODO + return 0; + } + + function addToPosition(bytes memory adaptorData) public pure returns (uint256) { + // TODO + return 0; + } + + //============================================ Helper Functions ============================================ + /** + * @notice Calculates the square root of the input. + */ + function _sqrt(uint256 _x) internal pure returns (uint256 y) { + uint256 z = (_x + 1) / 2; + y = _x; + while (z < y) { + y = z; + z = (_x / z + z) / 2; + } + } +} From a282be7b3acf2badc07a6baa3d7d43a4fe9f8179 Mon Sep 17 00:00:00 2001 From: cookiesanddudes Date: Sat, 29 Oct 2022 16:39:39 +0100 Subject: [PATCH 04/47] change location of curve 3 pool adaptor --- .../adaptors/{Convex => Curve}/Curve3PoolAdaptor.sol | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) rename src/modules/adaptors/{Convex => Curve}/Curve3PoolAdaptor.sol (95%) diff --git a/src/modules/adaptors/Convex/Curve3PoolAdaptor.sol b/src/modules/adaptors/Curve/Curve3PoolAdaptor.sol similarity index 95% rename from src/modules/adaptors/Convex/Curve3PoolAdaptor.sol rename to src/modules/adaptors/Curve/Curve3PoolAdaptor.sol index f0b7a39a..8c06658f 100644 --- a/src/modules/adaptors/Convex/Curve3PoolAdaptor.sol +++ b/src/modules/adaptors/Curve/Curve3PoolAdaptor.sol @@ -89,17 +89,20 @@ contract Curve3PoolAdaptor is BaseAdaptor { * @notice Returns `coins(0)` */ function assetOf(bytes memory adaptorData) public view override returns (ERC20) { - (, ERC20 lpToken) = abi.decode(adaptorData, (uint256, ERC20)); - return ERC20((curvePool()).coins(0)); + (ICurvePool pool, ERC20 lpToken) = abi.decode(adaptorData, (ICurvePool, ERC20)); + return ERC20(pool.coins(0)); } //============================================ Strategist Functions =========================================== function claim(bytes memory adaptorData) public pure returns (uint256) { - // TODO + return 0; } + /** + * @notice Allows strategist to open up arbritray Curve positions. + */ function openPosition(bytes memory adaptorData) public pure returns (uint256) { // TODO return 0; From f36208720c8368cedfb415e94ada30a4d2b0ebda Mon Sep 17 00:00:00 2001 From: cookiesanddudes Date: Sat, 29 Oct 2022 16:41:53 +0100 Subject: [PATCH 05/47] add mock tests for curve pool --- test/testAdaptors/Curve3Pool.t.sol | 94 ++++++++++++++++++++++++++++++ 1 file changed, 94 insertions(+) create mode 100644 test/testAdaptors/Curve3Pool.t.sol diff --git a/test/testAdaptors/Curve3Pool.t.sol b/test/testAdaptors/Curve3Pool.t.sol new file mode 100644 index 00000000..90e98d53 --- /dev/null +++ b/test/testAdaptors/Curve3Pool.t.sol @@ -0,0 +1,94 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity 0.8.16; + +import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; +import { MockCellar, Cellar, ERC4626, ERC20 } from "src/mocks/MockCellar.sol"; +import { Curve3PoolAdaptor } from "src/modules/adaptors/Curve/Curve3PoolAdaptor.sol"; +import { BaseAdaptor } from "src/modules/adaptors/BaseAdaptor.sol"; +import { ICurvePool } from "src/interfaces/external/ICurvePool.sol"; +import { Registry } from "src/Registry.sol"; +import { PriceRouter } from "src/modules/price-router/PriceRouter.sol"; +import { Denominations } from "@chainlink/contracts/src/v0.8/Denominations.sol"; +import { ERC20Adaptor } from "src/modules/adaptors/ERC20Adaptor.sol"; +import { SwapRouter, IUniswapV2Router, IUniswapV3Router } from "src/modules/swap-router/SwapRouter.sol"; + + +import { Test, stdStorage, console, StdStorage, stdError } from "@forge-std/Test.sol"; +import { Math } from "src/utils/Math.sol"; + +contract Curve3PoolTest is Test { + using SafeERC20 for ERC20; + using Math for uint256; + using stdStorage for StdStorage; + + Curve3PoolAdaptor private curve3PoolAdaptor; + ERC20Adaptor private erc20Adaptor; + MockCellar private cellar; + PriceRouter private priceRouter; + Registry private registry; + SwapRouter private swapRouter; + + address private immutable strategist = vm.addr(0xBEEF); + + ERC20 private WETH = ERC20(0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2); + ERC20 private CVX = ERC20(0x4e3FBD56CD56c3e72c1403e103b45Db9da5B9D2B); + + ERC20 private LP3CRV = ERC20(0x6c3F90f043a72FA612cbac8115EE7e52BDe6E490); + ERC20 private USDC = ERC20(0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48); + ERC20 private DAI = ERC20(0x6B175474E89094C44Da98b954EedeAC495271d0F); + ERC20 private USDT = ERC20(0xdAC17F958D2ee523a2206206994597C13D831ec7); + + ICurvePool curve3Pool = ICurvePool(0xbEbc44782C7dB0a1A60Cb6fe97d0b483032FF1C7); + + uint32 private lp3crvPosition; + + function setUp() external { + + curve3PoolAdaptor = new Curve3PoolAdaptor(); + erc20Adaptor = new ERC20Adaptor(); + priceRouter = new PriceRouter(); + + registry = new Registry(address(this), address(swapRouter), address(priceRouter)); + + priceRouter.addAsset(USDC, 0, 0, false, 0); + priceRouter.addAsset(USDT, 0, 0, false, 0); + priceRouter.addAsset(DAI, 0, 0, false, 0); + + // Setup Cellar: + // Cellar positions array. + uint32[] memory positions = new uint32[](1); + + // Add adaptors and positions to the registry. + registry.trustAdaptor(address(curve3PoolAdaptor), 0, 0); + + // + lp3crvPosition = registry.trustPosition(address(curve3PoolAdaptor), false, abi.encode(ICurvePool(0xbEbc44782C7dB0a1A60Cb6fe97d0b483032FF1C7), address(0x6c3F90f043a72FA612cbac8115EE7e52BDe6E490)), 0, 0); + + positions[0] = lp3crvPosition; + + bytes[] memory positionConfigs = new bytes[](1); + + cellar = new MockCellar(registry, DAI, positions, positionConfigs, "Convex Cellar", "CONVEX-CLR", strategist); + + vm.label(address(cellar), "cellar"); + vm.label(strategist, "strategist"); + + cellar.setupAdaptor(address(curve3PoolAdaptor)); + + USDC.safeApprove(address(cellar), type(uint256).max); + + // Manipulate test contracts storage so that minimum shareLockPeriod is zero blocks. + stdstore.target(address(cellar)).sig(cellar.shareLockPeriod.selector).checked_write(uint256(0)); + } + + function testFaucet() external { + uint256 amount = 100e18; + deal(address(DAI), address(this), amount); + assertEq( + DAI.balanceOf(address(this)), + amount, + "Should be able to deal some tokens" + ); + } + +} From 2f024d72b867cb8d2d0576834337dc111d5ee0cd Mon Sep 17 00:00:00 2001 From: cookiesanddudes Date: Sat, 29 Oct 2022 17:35:46 +0100 Subject: [PATCH 06/47] new methods and test start --- src/interfaces/external/ICurvePool.sol | 4 +- .../adaptors/Curve/Curve3PoolAdaptor.sol | 21 ++++++---- test/testAdaptors/Curve3Pool.t.sol | 41 +++++++++++++++++++ 3 files changed, 57 insertions(+), 9 deletions(-) diff --git a/src/interfaces/external/ICurvePool.sol b/src/interfaces/external/ICurvePool.sol index 4cf6fead..f244fef2 100644 --- a/src/interfaces/external/ICurvePool.sol +++ b/src/interfaces/external/ICurvePool.sol @@ -7,9 +7,9 @@ interface ICurvePool { function balances(uint256) external view returns (address); function get_virtual_price(uint256) external view returns (address); - function add_liquidity(uint256[3] calldata amounts, uint256 min_mint_amount) external; + function add_liquidity(uint256[3] calldata amounts, uint256 min_mint_amount) external returns (uint256); function exchange(int128 i, int128 j, uint256 dx, uint256 min_dy) external; - function remove_liquidity(uint256 _amount, uint256[3] calldata min_amounts) external; + function remove_liquidity(uint256 _amount, uint256[3] calldata min_amounts) external returns (uint256[3] memory); function remove_liquidity_imbalance(uint256[3] calldata amounts, uint256 max_burn_amount) external; function remove_liquidity_one_coin(uint256 _token_amount, int128 i, uint256 min_amount) external; } \ No newline at end of file diff --git a/src/modules/adaptors/Curve/Curve3PoolAdaptor.sol b/src/modules/adaptors/Curve/Curve3PoolAdaptor.sol index 8c06658f..281fb8e4 100644 --- a/src/modules/adaptors/Curve/Curve3PoolAdaptor.sol +++ b/src/modules/adaptors/Curve/Curve3PoolAdaptor.sol @@ -89,7 +89,7 @@ contract Curve3PoolAdaptor is BaseAdaptor { * @notice Returns `coins(0)` */ function assetOf(bytes memory adaptorData) public view override returns (ERC20) { - (ICurvePool pool, ERC20 lpToken) = abi.decode(adaptorData, (ICurvePool, ERC20)); + (ICurvePool pool, ) = abi.decode(adaptorData, (ICurvePool, ERC20)); return ERC20(pool.coins(0)); } @@ -103,14 +103,21 @@ contract Curve3PoolAdaptor is BaseAdaptor { /** * @notice Allows strategist to open up arbritray Curve positions. */ - function openPosition(bytes memory adaptorData) public pure returns (uint256) { - // TODO - return 0; + function openPosition( + uint256[3] memory amounts, + uint256 minimumMintAmount, + ICurvePool pool + ) public returns (uint256) { + return pool.add_liquidity(amounts, minimumMintAmount); } - function closePosition(bytes memory adaptorData) public pure returns (uint256) { - // TODO - return 0; + function closePosition( + bytes memory adaptorData, + uint256 amount, + uint256[3] memory minimumAmounts + ) public returns (uint256[3] memory) { + (ICurvePool pool, ) = abi.decode(adaptorData, (ICurvePool, address)); + return pool.remove_liquidity(amount, minimumAmounts); } function takeFromPosition(bytes memory adaptorData) public pure returns (uint256) { diff --git a/test/testAdaptors/Curve3Pool.t.sol b/test/testAdaptors/Curve3Pool.t.sol index 90e98d53..73b9c83b 100644 --- a/test/testAdaptors/Curve3Pool.t.sol +++ b/test/testAdaptors/Curve3Pool.t.sol @@ -34,6 +34,8 @@ contract Curve3PoolTest is Test { ERC20 private CVX = ERC20(0x4e3FBD56CD56c3e72c1403e103b45Db9da5B9D2B); ERC20 private LP3CRV = ERC20(0x6c3F90f043a72FA612cbac8115EE7e52BDe6E490); + + ERC20 private USDC = ERC20(0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48); ERC20 private DAI = ERC20(0x6B175474E89094C44Da98b954EedeAC495271d0F); ERC20 private USDT = ERC20(0xdAC17F958D2ee523a2206206994597C13D831ec7); @@ -73,6 +75,10 @@ contract Curve3PoolTest is Test { vm.label(address(cellar), "cellar"); vm.label(strategist, "strategist"); + DAI.approve(address(cellar), type(uint256).max); + USDT.approve(address(cellar), type(uint72).max); + USDC.approve(address(cellar), type(uint72).max); + cellar.setupAdaptor(address(curve3PoolAdaptor)); USDC.safeApprove(address(cellar), type(uint256).max); @@ -91,4 +97,39 @@ contract Curve3PoolTest is Test { ); } + function testOpenPosition() external { + deal(address(DAI), address(this), 100e18); + deal(address(USDT), address(this), 100e6); + deal(address(USDT), address(this), 100e6); + + // Use `callOnAdaptor` to deposit LP into curve pool + Cellar.AdaptorCall[] memory data = new Cellar.AdaptorCall[](1); + bytes[] memory adaptorCalls = new bytes[](1); + adaptorCalls[0] = _createBytesDataToOpenPosition( + 100e18, + 100e6, + 100e6, + 0 + ); + + data[0] = Cellar.AdaptorCall({ adaptor: address(curve3PoolAdaptor), callData: adaptorCalls }); + cellar.callOnAdaptor(data); + + } + + function _createBytesDataToOpenPosition( + uint256 amount0, + uint256 amount1, + uint256 amount2, + uint256 minimumMintAmount + ) internal view returns (bytes memory) { + return + abi.encodeWithSelector( + Curve3PoolAdaptor.openPosition.selector, + [amount0, amount1,amount2], + minimumMintAmount, + curve3Pool + ); + } + } From a9cddbd55e766cb29de4ee249971bceda8b1fe2d Mon Sep 17 00:00:00 2001 From: federava Date: Sat, 29 Oct 2022 18:36:40 +0200 Subject: [PATCH 07/47] Curve3PoolAdaptor balanceOf --- src/modules/adaptors/Curve/Curve3PoolAdaptor.sol | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/modules/adaptors/Curve/Curve3PoolAdaptor.sol b/src/modules/adaptors/Curve/Curve3PoolAdaptor.sol index 8c06658f..e2db6606 100644 --- a/src/modules/adaptors/Curve/Curve3PoolAdaptor.sol +++ b/src/modules/adaptors/Curve/Curve3PoolAdaptor.sol @@ -80,9 +80,11 @@ contract Curve3PoolAdaptor is BaseAdaptor { * @notice Calculates this positions LP tokens underlying worth in terms of `token0`. */ function balanceOf(bytes memory adaptorData) public view override returns (uint256) { - // TODO - (, ERC20 lpToken) = abi.decode(adaptorData, (uint256, ERC20)); - return lpToken.balanceOf(address(this)); + (cellar, ICurvePool pool, ERC20 lpToken) = abi.decode(adaptorData, (address, ICurvePool, ERC20)); + + // Calculates amount of token0 is recieved when burning all LP tokens. + uint256 lpBalance = lpToken.balanceOf(cellar); + return pool.calc_withdraw_one_coin(lpBalance, 0); } /** From e6b4ece15bb2496e1cab00b34e3ae2f3ad628371 Mon Sep 17 00:00:00 2001 From: cookiesanddudes Date: Sat, 29 Oct 2022 17:43:19 +0100 Subject: [PATCH 08/47] add interface, fix balanceOf() --- src/interfaces/external/ICurvePool.sol | 1 + src/modules/adaptors/Curve/Curve3PoolAdaptor.sol | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/interfaces/external/ICurvePool.sol b/src/interfaces/external/ICurvePool.sol index f244fef2..560e3d77 100644 --- a/src/interfaces/external/ICurvePool.sol +++ b/src/interfaces/external/ICurvePool.sol @@ -12,4 +12,5 @@ interface ICurvePool { function remove_liquidity(uint256 _amount, uint256[3] calldata min_amounts) external returns (uint256[3] memory); function remove_liquidity_imbalance(uint256[3] calldata amounts, uint256 max_burn_amount) external; function remove_liquidity_one_coin(uint256 _token_amount, int128 i, uint256 min_amount) external; + function calc_withdraw_one_coin(uint256, int128) external view returns (uint256); } \ No newline at end of file diff --git a/src/modules/adaptors/Curve/Curve3PoolAdaptor.sol b/src/modules/adaptors/Curve/Curve3PoolAdaptor.sol index eca4e374..00c62128 100644 --- a/src/modules/adaptors/Curve/Curve3PoolAdaptor.sol +++ b/src/modules/adaptors/Curve/Curve3PoolAdaptor.sol @@ -80,10 +80,10 @@ contract Curve3PoolAdaptor is BaseAdaptor { * @notice Calculates this positions LP tokens underlying worth in terms of `token0`. */ function balanceOf(bytes memory adaptorData) public view override returns (uint256) { - (cellar, ICurvePool pool, ERC20 lpToken) = abi.decode(adaptorData, (address, ICurvePool, ERC20)); + (ICurvePool pool, ERC20 lpToken) = abi.decode(adaptorData, (ICurvePool, ERC20)); // Calculates amount of token0 is recieved when burning all LP tokens. - uint256 lpBalance = lpToken.balanceOf(cellar); + uint256 lpBalance = lpToken.balanceOf(msg.sender); return pool.calc_withdraw_one_coin(lpBalance, 0); } From 3ce7ac83db6c06cccca5879b5c0a8d7c2add890f Mon Sep 17 00:00:00 2001 From: cookiesanddudes Date: Sat, 29 Oct 2022 17:43:53 +0100 Subject: [PATCH 09/47] fix interface --- src/interfaces/external/ICurvePool.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/interfaces/external/ICurvePool.sol b/src/interfaces/external/ICurvePool.sol index 560e3d77..b9324259 100644 --- a/src/interfaces/external/ICurvePool.sol +++ b/src/interfaces/external/ICurvePool.sol @@ -12,5 +12,5 @@ interface ICurvePool { function remove_liquidity(uint256 _amount, uint256[3] calldata min_amounts) external returns (uint256[3] memory); function remove_liquidity_imbalance(uint256[3] calldata amounts, uint256 max_burn_amount) external; function remove_liquidity_one_coin(uint256 _token_amount, int128 i, uint256 min_amount) external; - function calc_withdraw_one_coin(uint256, int128) external view returns (uint256); + function calc_withdraw_one_coin(uint256 token_amount, int128 i) external view returns (uint256); } \ No newline at end of file From 90a64792005635cc1c6dc18e2f0952169fc787cf Mon Sep 17 00:00:00 2001 From: cookiesanddudes Date: Sat, 29 Oct 2022 18:07:12 +0100 Subject: [PATCH 10/47] updates on the adaptor --- .../adaptors/Curve/Curve3PoolAdaptor.sol | 15 +++++++++---- test/testAdaptors/Curve3Pool.t.sol | 22 +++++++++---------- 2 files changed, 22 insertions(+), 15 deletions(-) diff --git a/src/modules/adaptors/Curve/Curve3PoolAdaptor.sol b/src/modules/adaptors/Curve/Curve3PoolAdaptor.sol index 00c62128..97ee477a 100644 --- a/src/modules/adaptors/Curve/Curve3PoolAdaptor.sol +++ b/src/modules/adaptors/Curve/Curve3PoolAdaptor.sol @@ -88,7 +88,7 @@ contract Curve3PoolAdaptor is BaseAdaptor { } /** - * @notice Returns `coins(0)` + * @notice Returns `coins(0)` or token0 */ function assetOf(bytes memory adaptorData) public view override returns (ERC20) { (ICurvePool pool, ) = abi.decode(adaptorData, (ICurvePool, ERC20)); @@ -110,15 +110,22 @@ contract Curve3PoolAdaptor is BaseAdaptor { uint256 minimumMintAmount, ICurvePool pool ) public returns (uint256) { + ERC20 token0 = ERC20(pool.coins(0)); + ERC20 token1 = ERC20(pool.coins(1)); + ERC20 token2 = ERC20(pool.coins(2)); + + token0.safeApprove(address(pool), amounts[0]); + token1.safeApprove(address(pool), amounts[1]); + token2.safeApprove(address(pool), amounts[2]); + return pool.add_liquidity(amounts, minimumMintAmount); } function closePosition( - bytes memory adaptorData, uint256 amount, - uint256[3] memory minimumAmounts + uint256[3] memory minimumAmounts, + ICurvePool pool ) public returns (uint256[3] memory) { - (ICurvePool pool, ) = abi.decode(adaptorData, (ICurvePool, address)); return pool.remove_liquidity(amount, minimumAmounts); } diff --git a/test/testAdaptors/Curve3Pool.t.sol b/test/testAdaptors/Curve3Pool.t.sol index 73b9c83b..4908354a 100644 --- a/test/testAdaptors/Curve3Pool.t.sol +++ b/test/testAdaptors/Curve3Pool.t.sol @@ -35,7 +35,6 @@ contract Curve3PoolTest is Test { ERC20 private LP3CRV = ERC20(0x6c3F90f043a72FA612cbac8115EE7e52BDe6E490); - ERC20 private USDC = ERC20(0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48); ERC20 private DAI = ERC20(0x6B175474E89094C44Da98b954EedeAC495271d0F); ERC20 private USDT = ERC20(0xdAC17F958D2ee523a2206206994597C13D831ec7); @@ -75,13 +74,12 @@ contract Curve3PoolTest is Test { vm.label(address(cellar), "cellar"); vm.label(strategist, "strategist"); - DAI.approve(address(cellar), type(uint256).max); - USDT.approve(address(cellar), type(uint72).max); - USDC.approve(address(cellar), type(uint72).max); - cellar.setupAdaptor(address(curve3PoolAdaptor)); USDC.safeApprove(address(cellar), type(uint256).max); + DAI.safeApprove(address(cellar), type(uint256).max); + USDT.safeApprove(address(cellar), type(uint256).max); + // Manipulate test contracts storage so that minimum shareLockPeriod is zero blocks. stdstore.target(address(cellar)).sig(cellar.shareLockPeriod.selector).checked_write(uint256(0)); @@ -98,23 +96,25 @@ contract Curve3PoolTest is Test { } function testOpenPosition() external { - deal(address(DAI), address(this), 100e18); - deal(address(USDT), address(this), 100e6); - deal(address(USDT), address(this), 100e6); + deal(address(DAI), address(cellar), 10000000e18); + // deal(address(USDT), address(this), 10000000e18); + // deal(address(USDT), address(this), 10000000e18); + + // cellar.deposit(100e18, address(this)); + // Use `callOnAdaptor` to deposit LP into curve pool Cellar.AdaptorCall[] memory data = new Cellar.AdaptorCall[](1); bytes[] memory adaptorCalls = new bytes[](1); adaptorCalls[0] = _createBytesDataToOpenPosition( 100e18, - 100e6, - 100e6, + 0, + 0, 0 ); data[0] = Cellar.AdaptorCall({ adaptor: address(curve3PoolAdaptor), callData: adaptorCalls }); cellar.callOnAdaptor(data); - } function _createBytesDataToOpenPosition( From b00631778e3f235cb9617c1879a9d47fd4cbbca0 Mon Sep 17 00:00:00 2001 From: cookiesanddudes Date: Sat, 29 Oct 2022 18:25:50 +0100 Subject: [PATCH 11/47] fix balance of usdc --- test/testAdaptors/Curve3Pool.t.sol | 31 ++++++++++++++++++++++-------- 1 file changed, 23 insertions(+), 8 deletions(-) diff --git a/test/testAdaptors/Curve3Pool.t.sol b/test/testAdaptors/Curve3Pool.t.sol index 4908354a..f9ef7dd2 100644 --- a/test/testAdaptors/Curve3Pool.t.sol +++ b/test/testAdaptors/Curve3Pool.t.sol @@ -71,18 +71,32 @@ contract Curve3PoolTest is Test { cellar = new MockCellar(registry, DAI, positions, positionConfigs, "Convex Cellar", "CONVEX-CLR", strategist); + vm.label(address(curve3Pool), "curve pool"); + vm.label(address(this), "tester"); vm.label(address(cellar), "cellar"); vm.label(strategist, "strategist"); + vm.label(address(DAI), "dai token"); + vm.label(address(USDC), "usdc token"); + vm.label(address(USDT), "usdt token"); + + cellar.setupAdaptor(address(curve3PoolAdaptor)); - USDC.safeApprove(address(cellar), type(uint256).max); DAI.safeApprove(address(cellar), type(uint256).max); - USDT.safeApprove(address(cellar), type(uint256).max); + USDC.safeApprove(address(cellar), type(uint128).max); + USDT.safeApprove(address(cellar), type(uint128).max); // Manipulate test contracts storage so that minimum shareLockPeriod is zero blocks. stdstore.target(address(cellar)).sig(cellar.shareLockPeriod.selector).checked_write(uint256(0)); + + + console.log('This'); + console.logAddress(address(this)); + console.log('Cellar'); + console.logAddress(address(cellar)); + } function testFaucet() external { @@ -97,19 +111,20 @@ contract Curve3PoolTest is Test { function testOpenPosition() external { deal(address(DAI), address(cellar), 10000000e18); - // deal(address(USDT), address(this), 10000000e18); - // deal(address(USDT), address(this), 10000000e18); - - // cellar.deposit(100e18, address(this)); + deal(address(USDC), address(cellar), 10000000e18); + deal(address(USDT), address(cellar), 10000000e18); + console.log(DAI.balanceOf(address(cellar))); + console.log(USDC.balanceOf(address(cellar))); + console.log(USDT.balanceOf(address(cellar))); // Use `callOnAdaptor` to deposit LP into curve pool Cellar.AdaptorCall[] memory data = new Cellar.AdaptorCall[](1); bytes[] memory adaptorCalls = new bytes[](1); adaptorCalls[0] = _createBytesDataToOpenPosition( 100e18, - 0, - 0, + 100e6, + 100e6, 0 ); From 87c91f0086798bdb11fddb975d48837dd636310a Mon Sep 17 00:00:00 2001 From: cookiesanddudes Date: Sat, 29 Oct 2022 18:39:22 +0100 Subject: [PATCH 12/47] simplify test, allow deposits --- .../adaptors/Curve/Curve3PoolAdaptor.sol | 12 +++++----- test/testAdaptors/Curve3Pool.t.sol | 22 ++++++++----------- 2 files changed, 15 insertions(+), 19 deletions(-) diff --git a/src/modules/adaptors/Curve/Curve3PoolAdaptor.sol b/src/modules/adaptors/Curve/Curve3PoolAdaptor.sol index 97ee477a..988458d8 100644 --- a/src/modules/adaptors/Curve/Curve3PoolAdaptor.sol +++ b/src/modules/adaptors/Curve/Curve3PoolAdaptor.sol @@ -52,7 +52,7 @@ contract Curve3PoolAdaptor is BaseAdaptor { bytes memory, bytes memory ) public pure override { - revert BaseAdaptor__UserDepositsNotAllowed(); + // revert BaseAdaptor__UserDepositsNotAllowed(); } /** @@ -64,7 +64,7 @@ contract Curve3PoolAdaptor is BaseAdaptor { bytes memory, bytes memory ) public pure override { - revert BaseAdaptor__UserWithdrawsNotAllowed(); + // revert BaseAdaptor__UserWithdrawsNotAllowed(); } @@ -109,7 +109,7 @@ contract Curve3PoolAdaptor is BaseAdaptor { uint256[3] memory amounts, uint256 minimumMintAmount, ICurvePool pool - ) public returns (uint256) { + ) public { ERC20 token0 = ERC20(pool.coins(0)); ERC20 token1 = ERC20(pool.coins(1)); ERC20 token2 = ERC20(pool.coins(2)); @@ -118,15 +118,15 @@ contract Curve3PoolAdaptor is BaseAdaptor { token1.safeApprove(address(pool), amounts[1]); token2.safeApprove(address(pool), amounts[2]); - return pool.add_liquidity(amounts, minimumMintAmount); + pool.add_liquidity(amounts, minimumMintAmount); } function closePosition( uint256 amount, uint256[3] memory minimumAmounts, ICurvePool pool - ) public returns (uint256[3] memory) { - return pool.remove_liquidity(amount, minimumAmounts); + ) public { + pool.remove_liquidity(amount, minimumAmounts); } function takeFromPosition(bytes memory adaptorData) public pure returns (uint256) { diff --git a/test/testAdaptors/Curve3Pool.t.sol b/test/testAdaptors/Curve3Pool.t.sol index f9ef7dd2..641750ee 100644 --- a/test/testAdaptors/Curve3Pool.t.sol +++ b/test/testAdaptors/Curve3Pool.t.sol @@ -90,13 +90,6 @@ contract Curve3PoolTest is Test { // Manipulate test contracts storage so that minimum shareLockPeriod is zero blocks. stdstore.target(address(cellar)).sig(cellar.shareLockPeriod.selector).checked_write(uint256(0)); - - - console.log('This'); - console.logAddress(address(this)); - console.log('Cellar'); - console.logAddress(address(cellar)); - } function testFaucet() external { @@ -110,9 +103,12 @@ contract Curve3PoolTest is Test { } function testOpenPosition() external { - deal(address(DAI), address(cellar), 10000000e18); - deal(address(USDC), address(cellar), 10000000e18); - deal(address(USDT), address(cellar), 10000000e18); + deal(address(DAI), address(this), 100e18); + cellar.deposit(100e18, address(this)); + + // deal(address(DAI), address(cellar), 10000000e18); + // deal(address(USDC), address(cellar), 10000000e18); + // deal(address(USDT), address(cellar), 10000000e18); console.log(DAI.balanceOf(address(cellar))); console.log(USDC.balanceOf(address(cellar))); @@ -123,9 +119,9 @@ contract Curve3PoolTest is Test { bytes[] memory adaptorCalls = new bytes[](1); adaptorCalls[0] = _createBytesDataToOpenPosition( 100e18, - 100e6, - 100e6, - 0 + 0, + 0, + 1e18 ); data[0] = Cellar.AdaptorCall({ adaptor: address(curve3PoolAdaptor), callData: adaptorCalls }); From 938a96a6ffd995b3d6d0ca0ffc3a3d09a70442a8 Mon Sep 17 00:00:00 2001 From: cookiesanddudes Date: Sat, 29 Oct 2022 19:23:14 +0100 Subject: [PATCH 13/47] failed test --- .../adaptors/Curve/Curve3PoolAdaptor.sol | 4 +++- test/testAdaptors/Curve3Pool.t.sol | 21 ++++++++++++------- 2 files changed, 16 insertions(+), 9 deletions(-) diff --git a/src/modules/adaptors/Curve/Curve3PoolAdaptor.sol b/src/modules/adaptors/Curve/Curve3PoolAdaptor.sol index 988458d8..0659cb5a 100644 --- a/src/modules/adaptors/Curve/Curve3PoolAdaptor.sol +++ b/src/modules/adaptors/Curve/Curve3PoolAdaptor.sol @@ -109,7 +109,7 @@ contract Curve3PoolAdaptor is BaseAdaptor { uint256[3] memory amounts, uint256 minimumMintAmount, ICurvePool pool - ) public { + ) public returns (uint256){ ERC20 token0 = ERC20(pool.coins(0)); ERC20 token1 = ERC20(pool.coins(1)); ERC20 token2 = ERC20(pool.coins(2)); @@ -119,6 +119,8 @@ contract Curve3PoolAdaptor is BaseAdaptor { token2.safeApprove(address(pool), amounts[2]); pool.add_liquidity(amounts, minimumMintAmount); + + return 42; } function closePosition( diff --git a/test/testAdaptors/Curve3Pool.t.sol b/test/testAdaptors/Curve3Pool.t.sol index 641750ee..b8ea5f46 100644 --- a/test/testAdaptors/Curve3Pool.t.sol +++ b/test/testAdaptors/Curve3Pool.t.sol @@ -42,6 +42,8 @@ contract Curve3PoolTest is Test { ICurvePool curve3Pool = ICurvePool(0xbEbc44782C7dB0a1A60Cb6fe97d0b483032FF1C7); uint32 private lp3crvPosition; + uint32 private daiPosition; + function setUp() external { @@ -57,20 +59,25 @@ contract Curve3PoolTest is Test { // Setup Cellar: // Cellar positions array. - uint32[] memory positions = new uint32[](1); + uint32[] memory positions = new uint32[](2); // Add adaptors and positions to the registry. registry.trustAdaptor(address(curve3PoolAdaptor), 0, 0); + registry.trustAdaptor(address(erc20Adaptor), 0, 0); // lp3crvPosition = registry.trustPosition(address(curve3PoolAdaptor), false, abi.encode(ICurvePool(0xbEbc44782C7dB0a1A60Cb6fe97d0b483032FF1C7), address(0x6c3F90f043a72FA612cbac8115EE7e52BDe6E490)), 0, 0); + daiPosition = registry.trustPosition(address(erc20Adaptor), false, abi.encode(DAI), 0, 0); positions[0] = lp3crvPosition; + positions[1] = daiPosition; - bytes[] memory positionConfigs = new bytes[](1); + bytes[] memory positionConfigs = new bytes[](2); cellar = new MockCellar(registry, DAI, positions, positionConfigs, "Convex Cellar", "CONVEX-CLR", strategist); + // vm.prank(address(cellar)); + vm.label(address(curve3Pool), "curve pool"); vm.label(address(this), "tester"); vm.label(address(cellar), "cellar"); @@ -79,15 +86,12 @@ contract Curve3PoolTest is Test { vm.label(address(USDC), "usdc token"); vm.label(address(USDT), "usdt token"); - - cellar.setupAdaptor(address(curve3PoolAdaptor)); DAI.safeApprove(address(cellar), type(uint256).max); USDC.safeApprove(address(cellar), type(uint128).max); USDT.safeApprove(address(cellar), type(uint128).max); - // Manipulate test contracts storage so that minimum shareLockPeriod is zero blocks. stdstore.target(address(cellar)).sig(cellar.shareLockPeriod.selector).checked_write(uint256(0)); } @@ -103,10 +107,10 @@ contract Curve3PoolTest is Test { } function testOpenPosition() external { - deal(address(DAI), address(this), 100e18); - cellar.deposit(100e18, address(this)); + // deal(address(DAI), address(this), 100e18); + // cellar.deposit(100e18, address(this)); - // deal(address(DAI), address(cellar), 10000000e18); + deal(address(DAI), address(cellar), 10000000e18); // deal(address(USDC), address(cellar), 10000000e18); // deal(address(USDT), address(cellar), 10000000e18); @@ -125,6 +129,7 @@ contract Curve3PoolTest is Test { ); data[0] = Cellar.AdaptorCall({ adaptor: address(curve3PoolAdaptor), callData: adaptorCalls }); + cellar.callOnAdaptor(data); } From 6805c83bc6b21b0933966707cb84deb0fb08f804 Mon Sep 17 00:00:00 2001 From: cookiesanddudes Date: Sat, 29 Oct 2022 19:23:53 +0100 Subject: [PATCH 14/47] remove 42 --- src/modules/adaptors/Curve/Curve3PoolAdaptor.sol | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/modules/adaptors/Curve/Curve3PoolAdaptor.sol b/src/modules/adaptors/Curve/Curve3PoolAdaptor.sol index 0659cb5a..988458d8 100644 --- a/src/modules/adaptors/Curve/Curve3PoolAdaptor.sol +++ b/src/modules/adaptors/Curve/Curve3PoolAdaptor.sol @@ -109,7 +109,7 @@ contract Curve3PoolAdaptor is BaseAdaptor { uint256[3] memory amounts, uint256 minimumMintAmount, ICurvePool pool - ) public returns (uint256){ + ) public { ERC20 token0 = ERC20(pool.coins(0)); ERC20 token1 = ERC20(pool.coins(1)); ERC20 token2 = ERC20(pool.coins(2)); @@ -119,8 +119,6 @@ contract Curve3PoolAdaptor is BaseAdaptor { token2.safeApprove(address(pool), amounts[2]); pool.add_liquidity(amounts, minimumMintAmount); - - return 42; } function closePosition( From ecc8a76c261bcdb8240ffd1aa3226342d2b8ba00 Mon Sep 17 00:00:00 2001 From: cookiesanddudes Date: Sat, 29 Oct 2022 20:55:59 +0100 Subject: [PATCH 15/47] fix interfaces and test --- src/interfaces/external/ICurvePool.sol | 2 +- test/testAdaptors/Curve3Pool.t.sol | 22 +++++++++------------- 2 files changed, 10 insertions(+), 14 deletions(-) diff --git a/src/interfaces/external/ICurvePool.sol b/src/interfaces/external/ICurvePool.sol index b9324259..f7d03ebc 100644 --- a/src/interfaces/external/ICurvePool.sol +++ b/src/interfaces/external/ICurvePool.sol @@ -7,7 +7,7 @@ interface ICurvePool { function balances(uint256) external view returns (address); function get_virtual_price(uint256) external view returns (address); - function add_liquidity(uint256[3] calldata amounts, uint256 min_mint_amount) external returns (uint256); + function add_liquidity(uint256[3] calldata amounts, uint256 min_mint_amount) external; function exchange(int128 i, int128 j, uint256 dx, uint256 min_dy) external; function remove_liquidity(uint256 _amount, uint256[3] calldata min_amounts) external returns (uint256[3] memory); function remove_liquidity_imbalance(uint256[3] calldata amounts, uint256 max_burn_amount) external; diff --git a/test/testAdaptors/Curve3Pool.t.sol b/test/testAdaptors/Curve3Pool.t.sol index b8ea5f46..3d8ba364 100644 --- a/test/testAdaptors/Curve3Pool.t.sol +++ b/test/testAdaptors/Curve3Pool.t.sol @@ -53,9 +53,9 @@ contract Curve3PoolTest is Test { registry = new Registry(address(this), address(swapRouter), address(priceRouter)); + priceRouter.addAsset(DAI, 0, 0, false, 0); priceRouter.addAsset(USDC, 0, 0, false, 0); priceRouter.addAsset(USDT, 0, 0, false, 0); - priceRouter.addAsset(DAI, 0, 0, false, 0); // Setup Cellar: // Cellar positions array. @@ -75,10 +75,12 @@ contract Curve3PoolTest is Test { bytes[] memory positionConfigs = new bytes[](2); cellar = new MockCellar(registry, DAI, positions, positionConfigs, "Convex Cellar", "CONVEX-CLR", strategist); - + // vm.prank(address(cellar)); vm.label(address(curve3Pool), "curve pool"); + vm.label(address(curve3PoolAdaptor), "curve3PoolAdaptor"); + vm.label(address(this), "tester"); vm.label(address(cellar), "cellar"); vm.label(strategist, "strategist"); @@ -107,25 +109,19 @@ contract Curve3PoolTest is Test { } function testOpenPosition() external { - // deal(address(DAI), address(this), 100e18); - // cellar.deposit(100e18, address(this)); - - deal(address(DAI), address(cellar), 10000000e18); - // deal(address(USDC), address(cellar), 10000000e18); - // deal(address(USDT), address(cellar), 10000000e18); + // deal(address(DAI), address(this), 100_000e18); + // cellar.deposit(100_000e18, address(this)); - console.log(DAI.balanceOf(address(cellar))); - console.log(USDC.balanceOf(address(cellar))); - console.log(USDT.balanceOf(address(cellar))); + deal(address(DAI), address(cellar), 100_000e18); // Use `callOnAdaptor` to deposit LP into curve pool Cellar.AdaptorCall[] memory data = new Cellar.AdaptorCall[](1); bytes[] memory adaptorCalls = new bytes[](1); adaptorCalls[0] = _createBytesDataToOpenPosition( - 100e18, + 1e18, 0, 0, - 1e18 + 0 ); data[0] = Cellar.AdaptorCall({ adaptor: address(curve3PoolAdaptor), callData: adaptorCalls }); From e0231387cd70b70cbe78413357c9ad7b7f8b916b Mon Sep 17 00:00:00 2001 From: federava Date: Sat, 29 Oct 2022 22:03:22 +0200 Subject: [PATCH 16/47] fix: ICurvePool --- src/interfaces/external/ICurvePool.sol | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/interfaces/external/ICurvePool.sol b/src/interfaces/external/ICurvePool.sol index f7d03ebc..66d954d3 100644 --- a/src/interfaces/external/ICurvePool.sol +++ b/src/interfaces/external/ICurvePool.sol @@ -4,13 +4,13 @@ pragma solidity 0.8.16; // ICurvePool interface interface ICurvePool { function coins(uint256) external view returns (address); - function balances(uint256) external view returns (address); - function get_virtual_price(uint256) external view returns (address); + function balances(uint256) external view returns (uint256); + function get_virtual_price() external view returns (uint256); + function calc_withdraw_one_coin(uint256 token_amount, int128 i) external view returns (uint256); function add_liquidity(uint256[3] calldata amounts, uint256 min_mint_amount) external; function exchange(int128 i, int128 j, uint256 dx, uint256 min_dy) external; function remove_liquidity(uint256 _amount, uint256[3] calldata min_amounts) external returns (uint256[3] memory); function remove_liquidity_imbalance(uint256[3] calldata amounts, uint256 max_burn_amount) external; function remove_liquidity_one_coin(uint256 _token_amount, int128 i, uint256 min_amount) external; - function calc_withdraw_one_coin(uint256 token_amount, int128 i) external view returns (uint256); } \ No newline at end of file From a9c663eeb6132b518a00bd871f81ba6cda0fa2ab Mon Sep 17 00:00:00 2001 From: cookiesanddudes Date: Sat, 29 Oct 2022 21:19:27 +0100 Subject: [PATCH 17/47] added assertEq --- test/testAdaptors/Curve3Pool.t.sol | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/test/testAdaptors/Curve3Pool.t.sol b/test/testAdaptors/Curve3Pool.t.sol index 3d8ba364..2241bd17 100644 --- a/test/testAdaptors/Curve3Pool.t.sol +++ b/test/testAdaptors/Curve3Pool.t.sol @@ -33,12 +33,12 @@ contract Curve3PoolTest is Test { ERC20 private WETH = ERC20(0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2); ERC20 private CVX = ERC20(0x4e3FBD56CD56c3e72c1403e103b45Db9da5B9D2B); - ERC20 private LP3CRV = ERC20(0x6c3F90f043a72FA612cbac8115EE7e52BDe6E490); ERC20 private USDC = ERC20(0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48); ERC20 private DAI = ERC20(0x6B175474E89094C44Da98b954EedeAC495271d0F); ERC20 private USDT = ERC20(0xdAC17F958D2ee523a2206206994597C13D831ec7); + ERC20 private LP3CRV = ERC20(0x6c3F90f043a72FA612cbac8115EE7e52BDe6E490); ICurvePool curve3Pool = ICurvePool(0xbEbc44782C7dB0a1A60Cb6fe97d0b483032FF1C7); uint32 private lp3crvPosition; @@ -127,6 +127,14 @@ contract Curve3PoolTest is Test { data[0] = Cellar.AdaptorCall({ adaptor: address(curve3PoolAdaptor), callData: adaptorCalls }); cellar.callOnAdaptor(data); + + + uint256 lpBalance = LP3CRV.balanceOf(address(cellar)); + + // Assert balanceOf is bigger than 0 and equal to actual lp balance + vm.prank(address(cellar)); + assertGe(curve3PoolAdaptor.balanceOf(abi.encode(curve3Pool, LP3CRV)), 1e18-1e17); + } function _createBytesDataToOpenPosition( From 778b848d1d589b9f244bf54e1349678e624a2299 Mon Sep 17 00:00:00 2001 From: cookiesanddudes Date: Sat, 29 Oct 2022 21:23:26 +0100 Subject: [PATCH 18/47] added test condition --- test/testAdaptors/Curve3Pool.t.sol | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/test/testAdaptors/Curve3Pool.t.sol b/test/testAdaptors/Curve3Pool.t.sol index 2241bd17..0214a86d 100644 --- a/test/testAdaptors/Curve3Pool.t.sol +++ b/test/testAdaptors/Curve3Pool.t.sol @@ -131,10 +131,12 @@ contract Curve3PoolTest is Test { uint256 lpBalance = LP3CRV.balanceOf(address(cellar)); - // Assert balanceOf is bigger than 0 and equal to actual lp balance + // Assert balanceOf is bigger than 0.9 vm.prank(address(cellar)); assertGe(curve3PoolAdaptor.balanceOf(abi.encode(curve3Pool, LP3CRV)), 1e18-1e17); + // Assert LP is bigger than 0 + assertGe(lpBalance, 0); } function _createBytesDataToOpenPosition( From e12c57fb278b7fa1beb89ac5910607caba1c1c8a Mon Sep 17 00:00:00 2001 From: cookiesanddudes Date: Sat, 29 Oct 2022 21:57:35 +0100 Subject: [PATCH 19/47] remove position in one coin --- src/interfaces/external/ICurvePool.sol | 2 +- .../adaptors/Curve/Curve3PoolAdaptor.sol | 8 +- test/testAdaptors/Curve3Pool.t.sol | 121 ++++++++++++++++++ 3 files changed, 128 insertions(+), 3 deletions(-) diff --git a/src/interfaces/external/ICurvePool.sol b/src/interfaces/external/ICurvePool.sol index 66d954d3..39fee28a 100644 --- a/src/interfaces/external/ICurvePool.sol +++ b/src/interfaces/external/ICurvePool.sol @@ -10,7 +10,7 @@ interface ICurvePool { function add_liquidity(uint256[3] calldata amounts, uint256 min_mint_amount) external; function exchange(int128 i, int128 j, uint256 dx, uint256 min_dy) external; - function remove_liquidity(uint256 _amount, uint256[3] calldata min_amounts) external returns (uint256[3] memory); + function remove_liquidity(uint256 _amount, uint256[3] calldata min_amounts) external; function remove_liquidity_imbalance(uint256[3] calldata amounts, uint256 max_burn_amount) external; function remove_liquidity_one_coin(uint256 _token_amount, int128 i, uint256 min_amount) external; } \ No newline at end of file diff --git a/src/modules/adaptors/Curve/Curve3PoolAdaptor.sol b/src/modules/adaptors/Curve/Curve3PoolAdaptor.sol index 988458d8..fc46d1c0 100644 --- a/src/modules/adaptors/Curve/Curve3PoolAdaptor.sol +++ b/src/modules/adaptors/Curve/Curve3PoolAdaptor.sol @@ -84,6 +84,10 @@ contract Curve3PoolAdaptor is BaseAdaptor { // Calculates amount of token0 is recieved when burning all LP tokens. uint256 lpBalance = lpToken.balanceOf(msg.sender); + + if(lpBalance == 0) { + return 0; + } return pool.calc_withdraw_one_coin(lpBalance, 0); } @@ -123,10 +127,10 @@ contract Curve3PoolAdaptor is BaseAdaptor { function closePosition( uint256 amount, - uint256[3] memory minimumAmounts, + uint256 minimumAmounts, ICurvePool pool ) public { - pool.remove_liquidity(amount, minimumAmounts); + pool.remove_liquidity_one_coin(amount, 0, minimumAmounts); } function takeFromPosition(bytes memory adaptorData) public pure returns (uint256) { diff --git a/test/testAdaptors/Curve3Pool.t.sol b/test/testAdaptors/Curve3Pool.t.sol index 0214a86d..ea2ce179 100644 --- a/test/testAdaptors/Curve3Pool.t.sol +++ b/test/testAdaptors/Curve3Pool.t.sol @@ -139,6 +139,114 @@ contract Curve3PoolTest is Test { assertGe(lpBalance, 0); } + function testOpenDAIPosition() external { + deal(address(DAI), address(cellar), 100_000e18); + + // Use `callOnAdaptor` to deposit LP into curve pool + Cellar.AdaptorCall[] memory data = new Cellar.AdaptorCall[](1); + bytes[] memory adaptorCalls = new bytes[](1); + adaptorCalls[0] = _createBytesDataToOpenPosition( + 1e18, + 0, + 0, + 0 + ); + + data[0] = Cellar.AdaptorCall({ adaptor: address(curve3PoolAdaptor), callData: adaptorCalls }); + + cellar.callOnAdaptor(data); + + + uint256 lpBalance = LP3CRV.balanceOf(address(cellar)); + + // Assert balanceOf is bigger than 0.9 + vm.prank(address(cellar)); + assertGe(curve3PoolAdaptor.balanceOf(abi.encode(curve3Pool, LP3CRV)), 1e18-1e17); + + // Assert LP is bigger than 0 + assertGe(lpBalance, 0); + } + + + function testOpenDAIUSDCUSDTPosition() external { + deal(address(DAI), address(cellar), 100_000e18); + deal(address(USDC), address(cellar), 100_000e6); + deal(address(USDT), address(cellar), 100_000e6); + + // Use `callOnAdaptor` to deposit LP into curve pool + Cellar.AdaptorCall[] memory data = new Cellar.AdaptorCall[](1); + bytes[] memory adaptorCalls = new bytes[](1); + adaptorCalls[0] = _createBytesDataToOpenPosition( + 1e18, + 1e6, + 1e6, + 0 + ); + + data[0] = Cellar.AdaptorCall({ adaptor: address(curve3PoolAdaptor), callData: adaptorCalls }); + + cellar.callOnAdaptor(data); + + uint256 lpBalance = LP3CRV.balanceOf(address(cellar)); + + // Assert balanceOf is bigger than 0.9 + vm.prank(address(cellar)); + assertGe(curve3PoolAdaptor.balanceOf(abi.encode(curve3Pool, LP3CRV)), 1e18-1e17); + + // Assert LP is bigger than 0 + assertGe(lpBalance, 0); + } + + function testOpeningAndClosingPosition() external { + deal(address(DAI), address(cellar), 100_000e18); + deal(address(USDC), address(cellar), 100_000e6); + deal(address(USDT), address(cellar), 100_000e6); + + // Use `callOnAdaptor` to deposit LP into curve pool + Cellar.AdaptorCall[] memory data = new Cellar.AdaptorCall[](1); + bytes[] memory adaptorCalls = new bytes[](1); + adaptorCalls[0] = _createBytesDataToOpenPosition( + 1e18, + 1e6, + 1e6, + 0 + ); + + data[0] = Cellar.AdaptorCall({ adaptor: address(curve3PoolAdaptor), callData: adaptorCalls }); + + cellar.callOnAdaptor(data); + + uint256 lpBalance = LP3CRV.balanceOf(address(cellar)); + uint256 daiBalanceBefore = DAI.balanceOf(address(cellar)); + + // assert balanceOf is bigger than 0.9 + vm.prank(address(cellar)); + assertGe(curve3PoolAdaptor.balanceOf(abi.encode(curve3Pool, LP3CRV)), 1e18-1e17); + + // assert LP is bigger than 0 + assertGe(lpBalance, 0); + + // Now, close the position + adaptorCalls = new bytes[](1); + adaptorCalls[0] = _createBytesDataToClosePosition(lpBalance, 0); + + data[0] = Cellar.AdaptorCall({ adaptor: address(curve3PoolAdaptor), callData: adaptorCalls }); + cellar.callOnAdaptor(data); + + uint256 daiBalanceAfter = DAI.balanceOf(address(cellar)); + uint256 lpBalanceAfter = LP3CRV.balanceOf(address(cellar)); + + assertEq(lpBalanceAfter, 0); + + assertGe(daiBalanceAfter - daiBalanceBefore, 0); + + // assert adaptor balanceOf is zero as well + vm.prank(address(cellar)); + assertEq(curve3PoolAdaptor.balanceOf(abi.encode(curve3Pool, LP3CRV)), 0); + } + + + function _createBytesDataToOpenPosition( uint256 amount0, uint256 amount1, @@ -154,4 +262,17 @@ contract Curve3PoolTest is Test { ); } + function _createBytesDataToClosePosition( + uint256 amount, + uint256 minimumAmount + ) internal view returns (bytes memory) { + return + abi.encodeWithSelector( + Curve3PoolAdaptor.closePosition.selector, + amount, + minimumAmount, + curve3Pool + ); + } + } From c8cefcfb0d9be61623089762e5ba2950dd885451 Mon Sep 17 00:00:00 2001 From: cookiesanddudes Date: Sat, 29 Oct 2022 21:59:50 +0100 Subject: [PATCH 20/47] disallow deposits and withdrawals() --- .../adaptors/Curve/Curve3PoolAdaptor.sol | 21 ++----------------- 1 file changed, 2 insertions(+), 19 deletions(-) diff --git a/src/modules/adaptors/Curve/Curve3PoolAdaptor.sol b/src/modules/adaptors/Curve/Curve3PoolAdaptor.sol index fc46d1c0..e6d57665 100644 --- a/src/modules/adaptors/Curve/Curve3PoolAdaptor.sol +++ b/src/modules/adaptors/Curve/Curve3PoolAdaptor.sol @@ -52,7 +52,7 @@ contract Curve3PoolAdaptor is BaseAdaptor { bytes memory, bytes memory ) public pure override { - // revert BaseAdaptor__UserDepositsNotAllowed(); + revert BaseAdaptor__UserDepositsNotAllowed(); } /** @@ -64,7 +64,7 @@ contract Curve3PoolAdaptor is BaseAdaptor { bytes memory, bytes memory ) public pure override { - // revert BaseAdaptor__UserWithdrawsNotAllowed(); + revert BaseAdaptor__UserWithdrawsNotAllowed(); } @@ -101,11 +101,6 @@ contract Curve3PoolAdaptor is BaseAdaptor { //============================================ Strategist Functions =========================================== - function claim(bytes memory adaptorData) public pure returns (uint256) { - - return 0; - } - /** * @notice Allows strategist to open up arbritray Curve positions. */ @@ -143,16 +138,4 @@ contract Curve3PoolAdaptor is BaseAdaptor { return 0; } - //============================================ Helper Functions ============================================ - /** - * @notice Calculates the square root of the input. - */ - function _sqrt(uint256 _x) internal pure returns (uint256 y) { - uint256 z = (_x + 1) / 2; - y = _x; - while (z < y) { - y = z; - z = (_x / z + z) / 2; - } - } } From 359b187debe0e9ec7c5d06afd526e29baa554e48 Mon Sep 17 00:00:00 2001 From: cookiesanddudes Date: Sat, 29 Oct 2022 22:21:13 +0100 Subject: [PATCH 21/47] add close and take --- .../adaptors/Curve/Curve3PoolAdaptor.sol | 50 ++++++++++++++----- test/testAdaptors/Curve3Pool.t.sol | 25 +++++++--- 2 files changed, 57 insertions(+), 18 deletions(-) diff --git a/src/modules/adaptors/Curve/Curve3PoolAdaptor.sol b/src/modules/adaptors/Curve/Curve3PoolAdaptor.sol index e6d57665..6d7654ec 100644 --- a/src/modules/adaptors/Curve/Curve3PoolAdaptor.sol +++ b/src/modules/adaptors/Curve/Curve3PoolAdaptor.sol @@ -25,6 +25,8 @@ contract Curve3PoolAdaptor is BaseAdaptor { // - curvePool is the pool concerned by the position // - lpToken is the lp generated by the pool (in old curve contracts, // it is not available as a public method in the pool) + // + // Uses token0. In the case of curve3Pool it is DAI. //==================================================================== //============================================ Global Functions =========================================== @@ -38,11 +40,6 @@ contract Curve3PoolAdaptor is BaseAdaptor { return keccak256(abi.encode("Curve 3Pool Adaptor V 0.0")); } - /** - @notice Attempted to deposit into Curve but failed - */ - error CurveAdaptor_DepositFailed(); - //============================================ Implement Base Functions =========================================== /** * @notice User deposits are NOT allowed into this position. @@ -119,18 +116,47 @@ contract Curve3PoolAdaptor is BaseAdaptor { pool.add_liquidity(amounts, minimumMintAmount); } + + /** + * @notice Strategist attempted to remove all of a positions liquidity using `takeFromPosition`, + * but they need to use `closePosition`. + */ + error Curve3PoolAdaptor__CallClosePosition(); - function closePosition( + function takeFromPosition( uint256 amount, - uint256 minimumAmounts, - ICurvePool pool + uint256 minimumAmount, + ICurvePool pool, + ERC20 lpToken ) public { - pool.remove_liquidity_one_coin(amount, 0, minimumAmounts); + // we should not be closing a position here + if(lpToken.balanceOf(msg.sender) == amount) revert Curve3PoolAdaptor__CallClosePosition(); + _takeFromPosition(amount, minimumAmount, pool); } - function takeFromPosition(bytes memory adaptorData) public pure returns (uint256) { - // TODO - return 0; + function _takeFromPosition( + uint256 amount, + uint256 minimumAmount, + ICurvePool pool + ) internal { + pool.remove_liquidity_one_coin(amount, 0, minimumAmount); + } + + /** + * @notice Strategist attempted to remove all of a positions liquidity using `takeFromPosition`, + * but they need to use `closePosition`. + */ + error Curve3PoolAdaptor__PositionClosed(); + + function closePosition( + uint256 minimumAmount, + ICurvePool pool, + ERC20 lpToken + ) public returns (uint256) { + uint256 amountToWithdraw = lpToken.balanceOf(msg.sender); + + if(amountToWithdraw == 0) revert Curve3PoolAdaptor__PositionClosed(); + _takeFromPosition(amountToWithdraw, minimumAmount, pool); } function addToPosition(bytes memory adaptorData) public pure returns (uint256) { diff --git a/test/testAdaptors/Curve3Pool.t.sol b/test/testAdaptors/Curve3Pool.t.sol index ea2ce179..3d1b9e40 100644 --- a/test/testAdaptors/Curve3Pool.t.sol +++ b/test/testAdaptors/Curve3Pool.t.sol @@ -219,16 +219,18 @@ contract Curve3PoolTest is Test { uint256 lpBalance = LP3CRV.balanceOf(address(cellar)); uint256 daiBalanceBefore = DAI.balanceOf(address(cellar)); - // assert balanceOf is bigger than 0.9 + // assert balanceOf is bigger than 0 vm.prank(address(cellar)); - assertGe(curve3PoolAdaptor.balanceOf(abi.encode(curve3Pool, LP3CRV)), 1e18-1e17); + assertGe(curve3PoolAdaptor.balanceOf(abi.encode(curve3Pool, LP3CRV)), 0); // assert LP is bigger than 0 assertGe(lpBalance, 0); + console.log(lpBalance) + // Now, close the position adaptorCalls = new bytes[](1); - adaptorCalls[0] = _createBytesDataToClosePosition(lpBalance, 0); + adaptorCalls[0] = _createBytesDataToClosePosition(0); data[0] = Cellar.AdaptorCall({ adaptor: address(curve3PoolAdaptor), callData: adaptorCalls }); cellar.callOnAdaptor(data); @@ -246,7 +248,6 @@ contract Curve3PoolTest is Test { } - function _createBytesDataToOpenPosition( uint256 amount0, uint256 amount1, @@ -263,16 +264,28 @@ contract Curve3PoolTest is Test { } function _createBytesDataToClosePosition( - uint256 amount, uint256 minimumAmount ) internal view returns (bytes memory) { return abi.encodeWithSelector( Curve3PoolAdaptor.closePosition.selector, + minimumAmount, + curve3Pool, + LP3CRV + ); + } + + function _createBytesDataToTakeFromPosition( + uint256 amount, + uint256 minimumAmount + ) internal view returns (bytes memory) { + return + abi.encodeWithSelector( + Curve3PoolAdaptor.takeFromPosition.selector, amount, minimumAmount, curve3Pool - ); + ); } } From 0f203be983577db7130ff3b6e2b27e94f187ae03 Mon Sep 17 00:00:00 2001 From: federava Date: Sat, 29 Oct 2022 23:29:44 +0200 Subject: [PATCH 22/47] test: withdrawableFrom --- .../adaptors/Curve/Curve3PoolAdaptor.sol | 9 +++++--- test/testAdaptors/Curve3Pool.t.sol | 23 ++++++++++++++++++- 2 files changed, 28 insertions(+), 4 deletions(-) diff --git a/src/modules/adaptors/Curve/Curve3PoolAdaptor.sol b/src/modules/adaptors/Curve/Curve3PoolAdaptor.sol index 6d7654ec..a15c37ca 100644 --- a/src/modules/adaptors/Curve/Curve3PoolAdaptor.sol +++ b/src/modules/adaptors/Curve/Curve3PoolAdaptor.sol @@ -68,9 +68,12 @@ contract Curve3PoolAdaptor is BaseAdaptor { /** * @notice User withdraws are not allowed so this position must return 0 for withdrawableFrom. */ - function withdrawableFrom(bytes memory, bytes memory) public pure override returns (uint256) { - // TODO - return 0; + function withdrawableFrom(bytes memory adaptorData, bytes memory) public view override returns (uint256) { + (ICurvePool pool, ERC20 lpToken) = abi.decode(adaptorData, (ICurvePool, ERC20)); + + // Calculates amount of token0 is recieved when burning all LP tokens. + uint256 lpBalance = lpToken.balanceOf(msg.sender); + return pool.calc_withdraw_one_coin(lpBalance, 0); } /** diff --git a/test/testAdaptors/Curve3Pool.t.sol b/test/testAdaptors/Curve3Pool.t.sol index 3d1b9e40..d673b83d 100644 --- a/test/testAdaptors/Curve3Pool.t.sol +++ b/test/testAdaptors/Curve3Pool.t.sol @@ -226,7 +226,7 @@ contract Curve3PoolTest is Test { // assert LP is bigger than 0 assertGe(lpBalance, 0); - console.log(lpBalance) + console.log(lpBalance); // Now, close the position adaptorCalls = new bytes[](1); @@ -247,6 +247,27 @@ contract Curve3PoolTest is Test { assertEq(curve3PoolAdaptor.balanceOf(abi.encode(curve3Pool, LP3CRV)), 0); } + function testwithdrawableFrom() external { + deal(address(DAI), address(cellar), 100_000e18); + + // Use `callOnAdaptor` to deposit LP into curve pool + Cellar.AdaptorCall[] memory data = new Cellar.AdaptorCall[](1); + bytes[] memory adaptorCalls = new bytes[](1); + adaptorCalls[0] = _createBytesDataToOpenPosition( + 1e18, + 0, + 0, + 0 + ); + + data[0] = Cellar.AdaptorCall({ adaptor: address(curve3PoolAdaptor), callData: adaptorCalls }); + + cellar.callOnAdaptor(data); + + vm.prank(address(cellar)); + assertGe(curve3PoolAdaptor.withdrawableFrom(abi.encode(curve3Pool, LP3CRV), ""), 1e18-1e17); + } + function _createBytesDataToOpenPosition( uint256 amount0, From 375dd3e4a2b732df73af13ca2e49e952f7753b57 Mon Sep 17 00:00:00 2001 From: cookiesanddudes Date: Sat, 29 Oct 2022 22:34:28 +0100 Subject: [PATCH 23/47] add position and take position functions --- .../adaptors/Curve/Curve3PoolAdaptor.sol | 42 ++++++++++++++----- test/testAdaptors/Curve3Pool.t.sol | 16 ++++++- 2 files changed, 45 insertions(+), 13 deletions(-) diff --git a/src/modules/adaptors/Curve/Curve3PoolAdaptor.sol b/src/modules/adaptors/Curve/Curve3PoolAdaptor.sol index 6d7654ec..6bf75bf6 100644 --- a/src/modules/adaptors/Curve/Curve3PoolAdaptor.sol +++ b/src/modules/adaptors/Curve/Curve3PoolAdaptor.sol @@ -106,13 +106,17 @@ contract Curve3PoolAdaptor is BaseAdaptor { uint256 minimumMintAmount, ICurvePool pool ) public { - ERC20 token0 = ERC20(pool.coins(0)); - ERC20 token1 = ERC20(pool.coins(1)); - ERC20 token2 = ERC20(pool.coins(2)); + for(uint256 i; i Date: Sat, 29 Oct 2022 22:44:35 +0100 Subject: [PATCH 24/47] comment unimplemented function --- src/modules/adaptors/Convex/ConvexAdaptor.sol | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/modules/adaptors/Convex/ConvexAdaptor.sol b/src/modules/adaptors/Convex/ConvexAdaptor.sol index c23ef1f3..dee9e012 100644 --- a/src/modules/adaptors/Convex/ConvexAdaptor.sol +++ b/src/modules/adaptors/Convex/ConvexAdaptor.sol @@ -122,10 +122,10 @@ contract ConvexAdaptor is BaseAdaptor { //============================================ Strategist Functions =========================================== - function claim(bytes memory adaptorData) public pure returns (uint256) { - // TODO - return 0; - } + // function claim(bytes memory adaptorData) public pure returns (uint256) { + // // TODO + // return 0; + // } //============================================ Helper Functions ============================================ From a97ef8644c26d06daf563444eff0500004b1f899 Mon Sep 17 00:00:00 2001 From: cookiesanddudes Date: Sat, 29 Oct 2022 22:48:44 +0100 Subject: [PATCH 25/47] remove infinite for loop --- .../adaptors/Curve/Curve3PoolAdaptor.sol | 17 ++++++++--------- test/testAdaptors/Curve3Pool.t.sol | 4 ++-- 2 files changed, 10 insertions(+), 11 deletions(-) diff --git a/src/modules/adaptors/Curve/Curve3PoolAdaptor.sol b/src/modules/adaptors/Curve/Curve3PoolAdaptor.sol index 2b6df9e7..c1ac83bb 100644 --- a/src/modules/adaptors/Curve/Curve3PoolAdaptor.sol +++ b/src/modules/adaptors/Curve/Curve3PoolAdaptor.sol @@ -110,11 +110,10 @@ contract Curve3PoolAdaptor is BaseAdaptor { ICurvePool pool ) public { for(uint256 i; i Date: Sat, 29 Oct 2022 23:18:20 +0100 Subject: [PATCH 26/47] test adding and taking from curve --- test/testAdaptors/Curve3Pool.t.sol | 94 +++++++++++++++++++++++++----- 1 file changed, 81 insertions(+), 13 deletions(-) diff --git a/test/testAdaptors/Curve3Pool.t.sol b/test/testAdaptors/Curve3Pool.t.sol index edd6cfd6..e1d64d94 100644 --- a/test/testAdaptors/Curve3Pool.t.sol +++ b/test/testAdaptors/Curve3Pool.t.sol @@ -245,6 +245,70 @@ contract Curve3PoolTest is Test { assertEq(curve3PoolAdaptor.balanceOf(abi.encode(curve3Pool, LP3CRV)), 0); } +function testOpeningAddingAndTakingFromPosition() external { + deal(address(DAI), address(cellar), 100_000e18); + deal(address(USDC), address(cellar), 100_000e6); + deal(address(USDT), address(cellar), 100_000e6); + + // Use `callOnAdaptor` to deposit LP into curve pool + Cellar.AdaptorCall[] memory data = new Cellar.AdaptorCall[](1); + bytes[] memory adaptorCalls = new bytes[](1); + adaptorCalls[0] = _createBytesDataToOpenPosition( + 1e18, + 1e6, + 1e6, + 0 + ); + + data[0] = Cellar.AdaptorCall({ adaptor: address(curve3PoolAdaptor), callData: adaptorCalls }); + + cellar.callOnAdaptor(data); + + uint256 lpBalance = LP3CRV.balanceOf(address(cellar)); + + // assert balanceOf is bigger than 0 + vm.prank(address(cellar)); + assertGe(curve3PoolAdaptor.balanceOf(abi.encode(curve3Pool, LP3CRV)), 0); + + // assert LP is bigger than 0 + assertGe(lpBalance, 0); + + // Now, add to the position + adaptorCalls = new bytes[](1); + + adaptorCalls[0] = _createBytesDataToAddToPosition( + 10e18, + 10e6, + 10e6, + 0 + ); + + data[0] = Cellar.AdaptorCall({ adaptor: address(curve3PoolAdaptor), callData: adaptorCalls }); + cellar.callOnAdaptor(data); + + // check that amount has been added + vm.prank(address(cellar)); + assertGe(curve3PoolAdaptor.balanceOf(abi.encode(curve3Pool, LP3CRV)), 10e18); + + uint256 lpBalanceAfterAdd = LP3CRV.balanceOf(address(cellar)); + + assertGe(lpBalanceAfterAdd, 10e18); + + // Now, remove from the position + adaptorCalls[0] = _createBytesDataToTakeFromPosition( + 25e18, + 0 + ); + + data[0] = Cellar.AdaptorCall({ adaptor: address(curve3PoolAdaptor), callData: adaptorCalls }); + cellar.callOnAdaptor(data); + + // assert adaptor balanceOf is within range + vm.prank(address(cellar)); + assertGe(curve3PoolAdaptor.balanceOf(abi.encode(curve3Pool, LP3CRV)), 5e18); + assertLe(curve3PoolAdaptor.balanceOf(abi.encode(curve3Pool, LP3CRV)), 10e18); + } + function testwithdrawableFrom() external { deal(address(DAI), address(cellar), 100_000e18); @@ -282,6 +346,21 @@ contract Curve3PoolTest is Test { ); } + function _createBytesDataToAddToPosition( + uint256 amount0, + uint256 amount1, + uint256 amount2, + uint256 minimumAmount + ) internal view returns (bytes memory) { + return + abi.encodeWithSelector( + Curve3PoolAdaptor.addToPosition.selector, + [amount0, amount1, amount2], + minimumAmount, + curve3Pool + ); + } + function _createBytesDataToClosePosition( uint256 minimumAmount ) internal view returns (bytes memory) { @@ -303,22 +382,11 @@ contract Curve3PoolTest is Test { Curve3PoolAdaptor.takeFromPosition.selector, amount, minimumAmount, - curve3Pool + curve3Pool, + LP3CRV ); } - function _createBytesDataToAddToPosition( - uint256 amount, - uint256 minimumAmount - ) internal view returns (bytes memory) { - return - abi.encodeWithSelector( - Curve3PoolAdaptor.takeFromPosition.selector, - amount, - minimumAmount, - curve3Pool - ); - } } From 36e1329406135496824892d9827f354b2d8225c5 Mon Sep 17 00:00:00 2001 From: cookiesanddudes Date: Sat, 29 Oct 2022 23:34:24 +0100 Subject: [PATCH 27/47] convex setup --- src/modules/adaptors/Convex/ConvexAdaptor.sol | 72 ++++++++++--------- test/testAdaptors/Convex.t.sol | 5 +- 2 files changed, 44 insertions(+), 33 deletions(-) diff --git a/src/modules/adaptors/Convex/ConvexAdaptor.sol b/src/modules/adaptors/Convex/ConvexAdaptor.sol index dee9e012..c0f8e3ce 100644 --- a/src/modules/adaptors/Convex/ConvexAdaptor.sol +++ b/src/modules/adaptors/Convex/ConvexAdaptor.sol @@ -22,10 +22,11 @@ contract ConvexAdaptor is BaseAdaptor { using Address for address; //==================== Adaptor Data Specification ==================== - // adaptorData = abi.encode(uint256 pid, ERC20 lpToken) + // adaptorData = abi.encode(uint256 pid, ERC20 lpToken, ICurvePool pool) // Where: // - pid is the pool id of the convex pool // - lpToken is the lp token concerned by the pool + // - ICurvePool is the curve pool where the lp token was minted //==================================================================== //============================================ Global Functions =========================================== @@ -46,53 +47,34 @@ contract ConvexAdaptor is BaseAdaptor { return IBooster(0xF403C135812408BFbE8713b5A23a04b3D48AAE31); } - /** - * @notice The Curve 3pool contract on Ethereum Mainnet. - */ - function curvePool() internal pure returns (ICurvePool) { - return ICurvePool(0xbEbc44782C7dB0a1A60Cb6fe97d0b483032FF1C7); - } - /** @notice Attempted to deposit into convex but failed */ error ConvexAdaptor_DepositFailed(); //============================================ Implement Base Functions =========================================== + /** * @notice User deposits are NOT allowed into this position. */ function deposit( - uint256 amount, - bytes memory adaptorData, + uint256, + bytes memory, bytes memory - ) public override { - (uint256 pid, ERC20 lpToken) = abi.decode(adaptorData, (uint256, ERC20)); - - lpToken.safeApprove(address(booster()), amount); - - // always assume we are staking - if(!(booster()).deposit(pid, amount, true)) { - revert ConvexAdaptor_DepositFailed(); - } + ) public pure override { + revert BaseAdaptor__UserDepositsNotAllowed(); } /** * @notice User withdraws are NOT allowed from this position. */ function withdraw( - uint256 amount, - address receiver, - bytes memory adaptorData, + uint256, + address, + bytes memory, bytes memory - ) public override { - // Run external receiver check. - _externalReceiverCheck(receiver); - - (uint256 pid, ) = abi.decode(adaptorData, (uint256, ERC20)); - - // withdraw from this address to the receiver in parameter - (booster()).withdrawTo(pid, amount, receiver); + ) public pure override { + revert BaseAdaptor__UserWithdrawsNotAllowed(); } /** @@ -116,12 +98,38 @@ contract ConvexAdaptor is BaseAdaptor { * @notice Returns `coins(0)` */ function assetOf(bytes memory adaptorData) public view override returns (ERC20) { - (, ERC20 lpToken) = abi.decode(adaptorData, (uint256, ERC20)); - return ERC20((curvePool()).coins(0)); + (, , ICurvePool pool) = abi.decode(adaptorData, (uint256, ERC20, ICurvePool)); + return ERC20(pool.coins(0)); } //============================================ Strategist Functions =========================================== +/** + * @notice Open a position in convex + */ + function openPosition( + uint256 amount, + uint256 pid, + ERC20 lpToken + ) public { + lpToken.safeApprove(address(booster()), amount); + + // always assume we are staking + if(!(booster()).deposit(pid, amount, true)) { + revert ConvexAdaptor_DepositFailed(); + } + } + + /** + * @notice Close position in convex + */ + function closePosition( + uint256 pid, + uint256 amount + ) public { + (booster()).withdrawTo(pid, amount, msg.sender); + } + // function claim(bytes memory adaptorData) public pure returns (uint256) { // // TODO // return 0; diff --git a/test/testAdaptors/Convex.t.sol b/test/testAdaptors/Convex.t.sol index bf62d86f..79b88970 100644 --- a/test/testAdaptors/Convex.t.sol +++ b/test/testAdaptors/Convex.t.sol @@ -6,6 +6,8 @@ import { MockCellar, Cellar, ERC4626, ERC20 } from "src/mocks/MockCellar.sol"; import { ConvexAdaptor } from "src/modules/adaptors/Convex/ConvexAdaptor.sol"; import { BaseAdaptor } from "src/modules/adaptors/BaseAdaptor.sol"; import { IBooster } from "src/interfaces/external/IBooster.sol"; +import { ICurvePool } from "src/interfaces/external/ICurvePool.sol"; + import { Registry } from "src/Registry.sol"; import { PriceRouter } from "src/modules/price-router/PriceRouter.sol"; import { Denominations } from "@chainlink/contracts/src/v0.8/Denominations.sol"; @@ -39,6 +41,7 @@ contract CellarConvexTest is Test { ERC20 private USDT = ERC20(0xdAC17F958D2ee523a2206206994597C13D831ec7); IBooster private booster = IBooster(0xF403C135812408BFbE8713b5A23a04b3D48AAE31); + ICurvePool curve3Pool = ICurvePool(0xbEbc44782C7dB0a1A60Cb6fe97d0b483032FF1C7); uint32 private lp3crvPosition; @@ -61,7 +64,7 @@ contract CellarConvexTest is Test { // Add adaptors and positions to the registry. registry.trustAdaptor(address(convexAdaptor), 0, 0); - lp3crvPosition = registry.trustPosition(address(convexAdaptor), false, abi.encode(uint256(9), address(DAI)), 0, 0); + lp3crvPosition = registry.trustPosition(address(convexAdaptor), false, abi.encode(uint256(9), address(DAI), curve3Pool), 0, 0); positions[0] = lp3crvPosition; From b482e38d1a3e4606a4a73c711dff192ef369a466 Mon Sep 17 00:00:00 2001 From: cookiesanddudes Date: Sun, 30 Oct 2022 00:19:32 +0100 Subject: [PATCH 28/47] update tests --- test/testAdaptors/Curve3Pool.t.sol | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/test/testAdaptors/Curve3Pool.t.sol b/test/testAdaptors/Curve3Pool.t.sol index e1d64d94..549123f3 100644 --- a/test/testAdaptors/Curve3Pool.t.sol +++ b/test/testAdaptors/Curve3Pool.t.sol @@ -33,7 +33,6 @@ contract Curve3PoolTest is Test { ERC20 private WETH = ERC20(0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2); ERC20 private CVX = ERC20(0x4e3FBD56CD56c3e72c1403e103b45Db9da5B9D2B); - ERC20 private USDC = ERC20(0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48); ERC20 private DAI = ERC20(0x6B175474E89094C44Da98b954EedeAC495271d0F); ERC20 private USDT = ERC20(0xdAC17F958D2ee523a2206206994597C13D831ec7); @@ -44,7 +43,6 @@ contract Curve3PoolTest is Test { uint32 private lp3crvPosition; uint32 private daiPosition; - function setUp() external { curve3PoolAdaptor = new Curve3PoolAdaptor(); @@ -65,19 +63,16 @@ contract Curve3PoolTest is Test { registry.trustAdaptor(address(curve3PoolAdaptor), 0, 0); registry.trustAdaptor(address(erc20Adaptor), 0, 0); - // - lp3crvPosition = registry.trustPosition(address(curve3PoolAdaptor), false, abi.encode(ICurvePool(0xbEbc44782C7dB0a1A60Cb6fe97d0b483032FF1C7), address(0x6c3F90f043a72FA612cbac8115EE7e52BDe6E490)), 0, 0); daiPosition = registry.trustPosition(address(erc20Adaptor), false, abi.encode(DAI), 0, 0); + lp3crvPosition = registry.trustPosition(address(curve3PoolAdaptor), false, abi.encode(ICurvePool(0xbEbc44782C7dB0a1A60Cb6fe97d0b483032FF1C7), address(0x6c3F90f043a72FA612cbac8115EE7e52BDe6E490)), 0, 0); - positions[0] = lp3crvPosition; - positions[1] = daiPosition; + positions[0] = daiPosition; + positions[1] = lp3crvPosition; bytes[] memory positionConfigs = new bytes[](2); cellar = new MockCellar(registry, DAI, positions, positionConfigs, "Convex Cellar", "CONVEX-CLR", strategist); - // vm.prank(address(cellar)); - vm.label(address(curve3Pool), "curve pool"); vm.label(address(curve3PoolAdaptor), "curve3PoolAdaptor"); @@ -109,9 +104,6 @@ contract Curve3PoolTest is Test { } function testOpenPosition() external { - // deal(address(DAI), address(this), 100_000e18); - // cellar.deposit(100_000e18, address(this)); - deal(address(DAI), address(cellar), 100_000e18); // Use `callOnAdaptor` to deposit LP into curve pool From d9b8c829a40f14999d8cadb843d940b371fb40a5 Mon Sep 17 00:00:00 2001 From: cookiesanddudes Date: Sun, 30 Oct 2022 00:56:24 +0100 Subject: [PATCH 29/47] Convex strat --- src/interfaces/external/IBooster.sol | 3 +- src/interfaces/external/IRewardPool.sol | 4 +- src/modules/adaptors/Convex/ConvexAdaptor.sol | 41 +++++++++++-------- .../adaptors/Curve/Curve3PoolAdaptor.sol | 6 +-- 4 files changed, 30 insertions(+), 24 deletions(-) diff --git a/src/interfaces/external/IBooster.sol b/src/interfaces/external/IBooster.sol index ac45665e..4650357e 100644 --- a/src/interfaces/external/IBooster.sol +++ b/src/interfaces/external/IBooster.sol @@ -7,8 +7,7 @@ interface IBooster { function setVoteDelegate(address _voteDelegate) external; function vote(uint256 _voteId, address _votingAddress, bool _support) external returns(bool); function voteGaugeWeight(address[] calldata _gauge, uint256[] calldata _weight ) external returns(bool); - function poolInfo(uint256 _pid) external returns(address _lptoken, address _token, address _gauge, address _crvRewards, address _stash, bool _shutdown); - + function poolInfo(uint256 _pid) external view returns(address _lptoken, address _token, address _gauge, address _crvRewards, address _stash, bool _shutdown); function withdrawTo(uint256 _pid, uint256 _amount, address _to) external returns(bool); function withdraw(uint256 _pid, uint256 _amount) external returns(bool); function deposit(uint256 _pid, uint256 _amount, bool _stake) external returns(bool); diff --git a/src/interfaces/external/IRewardPool.sol b/src/interfaces/external/IRewardPool.sol index 65f3eb45..a430990d 100644 --- a/src/interfaces/external/IRewardPool.sol +++ b/src/interfaces/external/IRewardPool.sol @@ -1,8 +1,10 @@ // SPDX-License-Identifier: MIT pragma solidity 0.8.16; +import {IERC20} from '@openzeppelin/contracts/token/ERC20/IERC20.sol'; + // Convex IRewardPool interface -interface IRewardPool { +interface IRewardPool is IERC20 { function getReward() external returns(bool); function getReward(address _account, bool _claimExtras) external returns(bool); function withdrawAllAndUnwrap(bool claim) external; diff --git a/src/modules/adaptors/Convex/ConvexAdaptor.sol b/src/modules/adaptors/Convex/ConvexAdaptor.sol index c0f8e3ce..38ce4cfa 100644 --- a/src/modules/adaptors/Convex/ConvexAdaptor.sol +++ b/src/modules/adaptors/Convex/ConvexAdaptor.sol @@ -41,7 +41,7 @@ contract ConvexAdaptor is BaseAdaptor { } /** - * @notice The Booster contract on Ethereum Mainnet. + * @notice The Booster contract on Ethereum Mainnet where all deposits happen in Convex */ function booster() internal pure returns (IBooster) { return IBooster(0xF403C135812408BFbE8713b5A23a04b3D48AAE31); @@ -81,7 +81,6 @@ contract ConvexAdaptor is BaseAdaptor { * @notice User withdraws are not allowed so this position must return 0 for withdrawableFrom. */ function withdrawableFrom(bytes memory, bytes memory) public pure override returns (uint256) { - // TODO return 0; } @@ -89,9 +88,16 @@ contract ConvexAdaptor is BaseAdaptor { * @notice Calculates this positions LP tokens underlying worth in terms of `token0`. */ function balanceOf(bytes memory adaptorData) public view override returns (uint256) { - // TODO - (, ERC20 lpToken) = abi.decode(adaptorData, (uint256, ERC20)); - return lpToken.balanceOf(address(this)); + (uint256 pid, ERC20 lpToken, ICurvePool pool) = abi.decode(adaptorData, (uint256, ERC20, ICurvePool)); + + (,,,address rewardPool,,) = (booster()).poolInfo(pid); + + uint256 stakedBalance = IRewardPool(rewardPool).balanceOf(msg.sender); + + if(stakedBalance == 0) return 0; + + // returns how much do we get if were to withdraw the whole position from convex and curve + return pool.calc_withdraw_one_coin(stakedBalance, 0); } /** @@ -123,29 +129,28 @@ contract ConvexAdaptor is BaseAdaptor { /** * @notice Close position in convex */ - function closePosition( + function takeFromPosition( uint256 pid, uint256 amount ) public { (booster()).withdrawTo(pid, amount, msg.sender); } + /** + * @notice Close position in convex + */ + function closePosition( + uint256 pid, + uint256 amount + ) public { + (booster()).withdrawAll(pid, amount, msg.sender); + } + + // function claim(bytes memory adaptorData) public pure returns (uint256) { // // TODO // return 0; // } - //============================================ Helper Functions ============================================ - /** - * @notice Calculates the square root of the input. - */ - function _sqrt(uint256 _x) internal pure returns (uint256 y) { - uint256 z = (_x + 1) / 2; - y = _x; - while (z < y) { - y = z; - z = (_x / z + z) / 2; - } - } } diff --git a/src/modules/adaptors/Curve/Curve3PoolAdaptor.sol b/src/modules/adaptors/Curve/Curve3PoolAdaptor.sol index c1ac83bb..def76bcd 100644 --- a/src/modules/adaptors/Curve/Curve3PoolAdaptor.sol +++ b/src/modules/adaptors/Curve/Curve3PoolAdaptor.sol @@ -85,9 +85,9 @@ contract Curve3PoolAdaptor is BaseAdaptor { // Calculates amount of token0 is recieved when burning all LP tokens. uint256 lpBalance = lpToken.balanceOf(msg.sender); - if(lpBalance == 0) { - return 0; - } + // return 0 if lp balance is null + if(lpBalance == 0) return 0; + return pool.calc_withdraw_one_coin(lpBalance, 0); } From 5473a8efb42ed0354ee70fc2479c24ef5e22a824 Mon Sep 17 00:00:00 2001 From: federava Date: Sun, 30 Oct 2022 02:04:55 +0200 Subject: [PATCH 30/47] complete IBooster --- src/interfaces/external/IBooster.sol | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/src/interfaces/external/IBooster.sol b/src/interfaces/external/IBooster.sol index 4650357e..4e9ee400 100644 --- a/src/interfaces/external/IBooster.sol +++ b/src/interfaces/external/IBooster.sol @@ -4,12 +4,16 @@ pragma solidity 0.8.16; // Convex IBooster interface interface IBooster { function owner() external view returns(address); - function setVoteDelegate(address _voteDelegate) external; + function poolLength() external view returns (uint256); + function poolInfo(uint256 _pid) external view returns(address, address, address, address, address, bool); + + function deposit(uint256 _pid, uint256 _amount, bool _stake) external returns(bool); + function depositAll(uint256 _pid, bool _stake) external returns(bool); + function withdraw(uint256 _pid, uint256 _amount) external returns(bool); + function withdrawTo(uint256 _pid, uint256 _amount, address _to) external returns(bool); + function withdrawAll(uint256 _pid) external returns(bool); + function claimRewards(uint256 _pid, address _gauge) external returns(bool); function vote(uint256 _voteId, address _votingAddress, bool _support) external returns(bool); function voteGaugeWeight(address[] calldata _gauge, uint256[] calldata _weight ) external returns(bool); - function poolInfo(uint256 _pid) external view returns(address _lptoken, address _token, address _gauge, address _crvRewards, address _stash, bool _shutdown); - function withdrawTo(uint256 _pid, uint256 _amount, address _to) external returns(bool); - function withdraw(uint256 _pid, uint256 _amount) external returns(bool); - function deposit(uint256 _pid, uint256 _amount, bool _stake) external returns(bool); - function poolLength() external view returns (uint256); + function setVoteDelegate(address _voteDelegate) external; } \ No newline at end of file From 42304b37d31ee0a2a3394f17ed470e13ffdc595a Mon Sep 17 00:00:00 2001 From: federava Date: Sun, 30 Oct 2022 02:09:48 +0200 Subject: [PATCH 31/47] withdrawableFrom --- .../adaptors/Curve/Curve3PoolAdaptor.sol | 8 ++----- test/testAdaptors/Curve3Pool.t.sol | 21 ++++--------------- 2 files changed, 6 insertions(+), 23 deletions(-) diff --git a/src/modules/adaptors/Curve/Curve3PoolAdaptor.sol b/src/modules/adaptors/Curve/Curve3PoolAdaptor.sol index def76bcd..99333e00 100644 --- a/src/modules/adaptors/Curve/Curve3PoolAdaptor.sol +++ b/src/modules/adaptors/Curve/Curve3PoolAdaptor.sol @@ -68,12 +68,8 @@ contract Curve3PoolAdaptor is BaseAdaptor { /** * @notice User withdraws are not allowed so this position must return 0 for withdrawableFrom. */ - function withdrawableFrom(bytes memory adaptorData, bytes memory) public view override returns (uint256) { - (ICurvePool pool, ERC20 lpToken) = abi.decode(adaptorData, (ICurvePool, ERC20)); - - // Calculates amount of token0 is recieved when burning all LP tokens. - uint256 lpBalance = lpToken.balanceOf(msg.sender); - return pool.calc_withdraw_one_coin(lpBalance, 0); + function withdrawableFrom(bytes memory, bytes memory) public pure override returns (uint256) { + return 0; } /** diff --git a/test/testAdaptors/Curve3Pool.t.sol b/test/testAdaptors/Curve3Pool.t.sol index 549123f3..5685d46f 100644 --- a/test/testAdaptors/Curve3Pool.t.sol +++ b/test/testAdaptors/Curve3Pool.t.sol @@ -301,25 +301,12 @@ function testOpeningAddingAndTakingFromPosition() external { assertLe(curve3PoolAdaptor.balanceOf(abi.encode(curve3Pool, LP3CRV)), 10e18); } - function testwithdrawableFrom() external { - deal(address(DAI), address(cellar), 100_000e18); - - // Use `callOnAdaptor` to deposit LP into curve pool - Cellar.AdaptorCall[] memory data = new Cellar.AdaptorCall[](1); - bytes[] memory adaptorCalls = new bytes[](1); - adaptorCalls[0] = _createBytesDataToOpenPosition( - 1e18, - 0, + function testWithdrawableFromReturnsZero() external { + assertEq( + curve3PoolAdaptor.withdrawableFrom(abi.encode(0), abi.encode(0)), 0, - 0 + "`withdrawableFrom` should return 0." ); - - data[0] = Cellar.AdaptorCall({ adaptor: address(curve3PoolAdaptor), callData: adaptorCalls }); - - cellar.callOnAdaptor(data); - - vm.prank(address(cellar)); - assertGe(curve3PoolAdaptor.withdrawableFrom(abi.encode(curve3Pool, LP3CRV), ""), 1e18-1e17); } From c527277e30c47c1be3ae7796d2f55c5a8ec00bb0 Mon Sep 17 00:00:00 2001 From: cookiesanddudes Date: Sun, 30 Oct 2022 01:19:07 +0100 Subject: [PATCH 32/47] convex adaptor --- src/modules/adaptors/Convex/ConvexAdaptor.sol | 73 +++++++++++++++---- 1 file changed, 58 insertions(+), 15 deletions(-) diff --git a/src/modules/adaptors/Convex/ConvexAdaptor.sol b/src/modules/adaptors/Convex/ConvexAdaptor.sol index 38ce4cfa..2819826f 100644 --- a/src/modules/adaptors/Convex/ConvexAdaptor.sol +++ b/src/modules/adaptors/Convex/ConvexAdaptor.sol @@ -47,10 +47,6 @@ contract ConvexAdaptor is BaseAdaptor { return IBooster(0xF403C135812408BFbE8713b5A23a04b3D48AAE31); } - /** - @notice Attempted to deposit into convex but failed - */ - error ConvexAdaptor_DepositFailed(); //============================================ Implement Base Functions =========================================== @@ -88,9 +84,9 @@ contract ConvexAdaptor is BaseAdaptor { * @notice Calculates this positions LP tokens underlying worth in terms of `token0`. */ function balanceOf(bytes memory adaptorData) public view override returns (uint256) { - (uint256 pid, ERC20 lpToken, ICurvePool pool) = abi.decode(adaptorData, (uint256, ERC20, ICurvePool)); + (uint256 pid, , ICurvePool pool) = abi.decode(adaptorData, (uint256, ERC20, ICurvePool)); - (,,,address rewardPool,,) = (booster()).poolInfo(pid); + (, , ,address rewardPool, ,) = (booster()).poolInfo(pid); uint256 stakedBalance = IRewardPool(rewardPool).balanceOf(msg.sender); @@ -110,7 +106,12 @@ contract ConvexAdaptor is BaseAdaptor { //============================================ Strategist Functions =========================================== -/** + /** + @notice Attempted to deposit into convex but failed + */ + error ConvexAdaptor_DepositFailed(); + + /** * @notice Open a position in convex */ function openPosition( @@ -118,6 +119,25 @@ contract ConvexAdaptor is BaseAdaptor { uint256 pid, ERC20 lpToken ) public { + _addToPosition(amount, pid, lpToken); + } + + /** + * @notice Add to a position in convex + */ + function addToPosition( + uint256 amount, + uint256 pid, + ERC20 lpToken + ) public { + _addToPosition(amount, pid, lpToken); + } + + function _addToPosition( + uint256 amount, + uint256 pid, + ERC20 lpToken + ) internal { lpToken.safeApprove(address(booster()), amount); // always assume we are staking @@ -126,6 +146,11 @@ contract ConvexAdaptor is BaseAdaptor { } } + /** + * @notice Attempted to take from convex position but failed + */ + error ConvexAdaptor_TakeFromPositionFailed(); + /** * @notice Close position in convex */ @@ -133,24 +158,42 @@ contract ConvexAdaptor is BaseAdaptor { uint256 pid, uint256 amount ) public { - (booster()).withdrawTo(pid, amount, msg.sender); + if (!(booster()).withdraw(pid, amount)) { + revert ConvexAdaptor_TakeFromPositionFailed(); + } } + + /** + * @notice Attempted to take from convex position but failed + */ + error ConvexAdaptor_ClosePositionFailed(); + /** * @notice Close position in convex */ function closePosition( - uint256 pid, - uint256 amount + uint256 pid ) public { - (booster()).withdrawAll(pid, amount, msg.sender); + if (!booster().withdrawAll(pid)){ + revert ConvexAdaptor_ClosePositionFailed(); + } } + /** + * @notice Attempted to take from convex position but failed + */ + error ConvexAdaptor_CouldNotClaimRewards(); - // function claim(bytes memory adaptorData) public pure returns (uint256) { - // // TODO - // return 0; - // } + /** + * @notice Claims rewards and extras from convex + */ + function claimRewards(uint256 pid) public { + (, , ,address rewardPool, ,) = (booster()).poolInfo(pid); + if (!IRewardPool(rewardPool).getReward()){ + revert ConvexAdaptor_CouldNotClaimRewards(); + } + } } From 7c1b8e5edb7dc3e7f573319e4c1dd26fd96d21d5 Mon Sep 17 00:00:00 2001 From: federava Date: Sun, 30 Oct 2022 02:30:46 +0200 Subject: [PATCH 33/47] NatSpec Curve3PoolAdaptor --- .../adaptors/Curve/Curve3PoolAdaptor.sol | 38 +++++++++++++++++-- 1 file changed, 34 insertions(+), 4 deletions(-) diff --git a/src/modules/adaptors/Curve/Curve3PoolAdaptor.sol b/src/modules/adaptors/Curve/Curve3PoolAdaptor.sol index 99333e00..4106c1d8 100644 --- a/src/modules/adaptors/Curve/Curve3PoolAdaptor.sol +++ b/src/modules/adaptors/Curve/Curve3PoolAdaptor.sol @@ -64,7 +64,6 @@ contract Curve3PoolAdaptor is BaseAdaptor { revert BaseAdaptor__UserWithdrawsNotAllowed(); } - /** * @notice User withdraws are not allowed so this position must return 0 for withdrawableFrom. */ @@ -74,6 +73,8 @@ contract Curve3PoolAdaptor is BaseAdaptor { /** * @notice Calculates this positions LP tokens underlying worth in terms of `token0`. + * @notice Curve pools provide a calculation where the amount returned considers the swapping of token1 and token2 + * for token0 considering fees. */ function balanceOf(bytes memory adaptorData) public view override returns (uint256) { (ICurvePool pool, ERC20 lpToken) = abi.decode(adaptorData, (ICurvePool, ERC20)); @@ -96,9 +97,14 @@ contract Curve3PoolAdaptor is BaseAdaptor { } //============================================ Strategist Functions =========================================== - /** * @notice Allows strategist to open up arbritray Curve positions. + * @notice Allows to send any combination of token0, token1 and token2 which the pool will + * balance on deposit. + * @notice If minted lp tokens is less than minimumMintAmount function will revert. + * @param amounts token0, token1, and token2 amounts to be deposited. + * @param minimumMintAmount minting at least this amount of lp tokens. + * @param pool specifies the interface of the pool */ function openPosition( uint256[3] memory amounts, @@ -122,6 +128,11 @@ contract Curve3PoolAdaptor is BaseAdaptor { /** * @notice Strategist attempted to remove all of a positions liquidity using `takeFromPosition`, * but they need to use `closePosition`. + * @notice If receiving amount of token0 is less than minimumMintAmount function will revert. + * @param amount lp token amount to be burned. + * @param minimumMintAmount receiving at least this amount of token0. + * @param pool specifies the interface of the pool + * @param lpToken specifies the interface of the lp token */ error Curve3PoolAdaptor__CallClosePosition(); @@ -136,6 +147,13 @@ contract Curve3PoolAdaptor is BaseAdaptor { _takeFromPosition(amount, minimumAmount, pool); } + /** + * @notice Executes the removal of liquidity in one coin: token0. + * @notice If receiving amount of token0 is less than minimumMintAmount function will revert. + * @param amount lp token amount to be burned. + * @param minimumMintAmount receiving at least this amount of token0. + * @param pool specifies the interface of the pool + */ function _takeFromPosition( uint256 amount, uint256 minimumAmount, @@ -145,8 +163,11 @@ contract Curve3PoolAdaptor is BaseAdaptor { } /** - * @notice Strategist attempted to remove all of a positions liquidity using `takeFromPosition`, - * but they need to use `closePosition`. + * @notice Strategist use `closePosition` to remove all of a positions liquidity. + * @notice If receiving amount of token0 is less than minimumMintAmount function will revert. + * @param minimumMintAmount receiving at least this amount of token0. + * @param pool specifies the interface of the pool + * @param lpToken specifies the interface of the lp token */ error Curve3PoolAdaptor__PositionClosed(); @@ -161,6 +182,15 @@ contract Curve3PoolAdaptor is BaseAdaptor { _takeFromPosition(amountToWithdraw, minimumAmount, pool); } + /** + * @notice Allows strategist to add liquidity to a Curve position. + * @notice Allows to send any combination of token0, token1 and token2 which the pool will + * balance on deposit. + * @notice If minted lp tokens is less than minimumMintAmount function will revert. + * @param amounts token0, token1, and token2 amounts to be deposited. + * @param minimumMintAmount minting at least this amount of lp tokens. + * @param pool specifies the interface of the pool + */ function addToPosition( uint256[3] memory amounts, uint256 minimumMintAmount, From d8af9b760c322836db4518ca818eb12425363123 Mon Sep 17 00:00:00 2001 From: federava Date: Sun, 30 Oct 2022 02:11:28 +0100 Subject: [PATCH 34/47] test: revert Curve3Pool --- .../adaptors/Curve/Curve3PoolAdaptor.sol | 14 ++- test/testAdaptors/Curve3Pool.t.sol | 85 ++++++++++++++++--- 2 files changed, 80 insertions(+), 19 deletions(-) diff --git a/src/modules/adaptors/Curve/Curve3PoolAdaptor.sol b/src/modules/adaptors/Curve/Curve3PoolAdaptor.sol index 4106c1d8..b4fe386d 100644 --- a/src/modules/adaptors/Curve/Curve3PoolAdaptor.sol +++ b/src/modules/adaptors/Curve/Curve3PoolAdaptor.sol @@ -124,18 +124,17 @@ contract Curve3PoolAdaptor is BaseAdaptor { pool.add_liquidity(amounts, minimumMintAmount); } - + + error Curve3PoolAdaptor__CallClosePosition(); /** * @notice Strategist attempted to remove all of a positions liquidity using `takeFromPosition`, * but they need to use `closePosition`. * @notice If receiving amount of token0 is less than minimumMintAmount function will revert. * @param amount lp token amount to be burned. - * @param minimumMintAmount receiving at least this amount of token0. + * @param minimumAmount receiving at least this amount of token0. * @param pool specifies the interface of the pool * @param lpToken specifies the interface of the lp token */ - error Curve3PoolAdaptor__CallClosePosition(); - function takeFromPosition( uint256 amount, uint256 minimumAmount, @@ -151,7 +150,7 @@ contract Curve3PoolAdaptor is BaseAdaptor { * @notice Executes the removal of liquidity in one coin: token0. * @notice If receiving amount of token0 is less than minimumMintAmount function will revert. * @param amount lp token amount to be burned. - * @param minimumMintAmount receiving at least this amount of token0. + * @param minimumAmount receiving at least this amount of token0. * @param pool specifies the interface of the pool */ function _takeFromPosition( @@ -162,15 +161,14 @@ contract Curve3PoolAdaptor is BaseAdaptor { pool.remove_liquidity_one_coin(amount, 0, minimumAmount); } + error Curve3PoolAdaptor__PositionClosed(); /** * @notice Strategist use `closePosition` to remove all of a positions liquidity. * @notice If receiving amount of token0 is less than minimumMintAmount function will revert. - * @param minimumMintAmount receiving at least this amount of token0. + * @param minimumAmount receiving at least this amount of token0. * @param pool specifies the interface of the pool * @param lpToken specifies the interface of the lp token */ - error Curve3PoolAdaptor__PositionClosed(); - function closePosition( uint256 minimumAmount, ICurvePool pool, diff --git a/test/testAdaptors/Curve3Pool.t.sol b/test/testAdaptors/Curve3Pool.t.sol index 5685d46f..fdc2118a 100644 --- a/test/testAdaptors/Curve3Pool.t.sol +++ b/test/testAdaptors/Curve3Pool.t.sol @@ -93,16 +93,7 @@ contract Curve3PoolTest is Test { stdstore.target(address(cellar)).sig(cellar.shareLockPeriod.selector).checked_write(uint256(0)); } - function testFaucet() external { - uint256 amount = 100e18; - deal(address(DAI), address(this), amount); - assertEq( - DAI.balanceOf(address(this)), - amount, - "Should be able to deal some tokens" - ); - } - + // ========================================== POSITION MANAGEMENT TEST ========================================== function testOpenPosition() external { deal(address(DAI), address(cellar), 100_000e18); @@ -237,7 +228,7 @@ contract Curve3PoolTest is Test { assertEq(curve3PoolAdaptor.balanceOf(abi.encode(curve3Pool, LP3CRV)), 0); } -function testOpeningAddingAndTakingFromPosition() external { + function testOpeningAddingAndTakingFromPosition() external { deal(address(DAI), address(cellar), 100_000e18); deal(address(USDC), address(cellar), 100_000e6); deal(address(USDT), address(cellar), 100_000e6); @@ -309,6 +300,78 @@ function testOpeningAddingAndTakingFromPosition() external { ); } + // ========================================== REVERT TEST ========================================== + function testMinimalLPTokenMintUnreached() external { + deal(address(DAI), address(cellar), 100_000e18); + + // Use `callOnAdaptor` to deposit LP into curve pool + Cellar.AdaptorCall[] memory data = new Cellar.AdaptorCall[](1); + bytes[] memory adaptorCalls = new bytes[](1); + adaptorCalls[0] = _createBytesDataToOpenPosition( + 1e18, + 0, + 0, + 100e18 + ); + + data[0] = Cellar.AdaptorCall({ adaptor: address(curve3PoolAdaptor), callData: adaptorCalls }); + + vm.expectRevert( + bytes( + abi.encodePacked( + "Slippage screwed you" + ) + ) + ); + cellar.callOnAdaptor(data); + } + + + function testMinimalToken0WithdrawMintUnreached() external { + deal(address(DAI), address(cellar), 100_000e18); + deal(address(USDC), address(cellar), 100_000e6); + deal(address(USDT), address(cellar), 100_000e6); + + // Use `callOnAdaptor` to deposit LP into curve pool + Cellar.AdaptorCall[] memory data = new Cellar.AdaptorCall[](1); + bytes[] memory adaptorCalls = new bytes[](1); + adaptorCalls[0] = _createBytesDataToOpenPosition( + 1e18, + 1e6, + 1e6, + 0 + ); + + data[0] = Cellar.AdaptorCall({ adaptor: address(curve3PoolAdaptor), callData: adaptorCalls }); + + cellar.callOnAdaptor(data); + + uint256 lpBalance = LP3CRV.balanceOf(address(cellar)); + uint256 daiBalanceBefore = DAI.balanceOf(address(cellar)); + + // assert balanceOf is bigger than 0 + vm.prank(address(cellar)); + assertGe(curve3PoolAdaptor.balanceOf(abi.encode(curve3Pool, LP3CRV)), 0); + + // assert LP is bigger than 0 + assertGe(lpBalance, 0); + + // Now, close the position + adaptorCalls = new bytes[](1); + adaptorCalls[0] = _createBytesDataToClosePosition(100e18); + + data[0] = Cellar.AdaptorCall({ adaptor: address(curve3PoolAdaptor), callData: adaptorCalls }); + vm.expectRevert( + bytes( + abi.encodePacked( + "Not enough coins removed" + ) + ) + ); + cellar.callOnAdaptor(data); + } + + // ======================================= AUXILIAR FUNCTIONS ====================================== function _createBytesDataToOpenPosition( uint256 amount0, From 920f4c882b49043daa7efd0df572e88f6c62a12a Mon Sep 17 00:00:00 2001 From: cookiesanddudes Date: Sun, 30 Oct 2022 01:18:06 +0000 Subject: [PATCH 35/47] convex test start --- src/modules/adaptors/Convex/ConvexAdaptor.sol | 39 ++++-- test/testAdaptors/Convex.t.sol | 117 ++++++++++++++++-- 2 files changed, 140 insertions(+), 16 deletions(-) diff --git a/src/modules/adaptors/Convex/ConvexAdaptor.sol b/src/modules/adaptors/Convex/ConvexAdaptor.sol index 2819826f..56bc79d3 100644 --- a/src/modules/adaptors/Convex/ConvexAdaptor.sol +++ b/src/modules/adaptors/Convex/ConvexAdaptor.sol @@ -8,6 +8,7 @@ import { IBooster } from "src/interfaces/external/IBooster.sol"; import { IRewardPool } from "src/interfaces/external/IRewardPool.sol"; import { ICurvePool } from "src/interfaces/external/ICurvePool.sol"; +import { Test, stdStorage, console, StdStorage, stdError } from "@forge-std/Test.sol"; /** @@ -84,16 +85,38 @@ contract ConvexAdaptor is BaseAdaptor { * @notice Calculates this positions LP tokens underlying worth in terms of `token0`. */ function balanceOf(bytes memory adaptorData) public view override returns (uint256) { - (uint256 pid, , ICurvePool pool) = abi.decode(adaptorData, (uint256, ERC20, ICurvePool)); + (uint256 pid, ERC20 lpToken, ICurvePool pool) = abi.decode(adaptorData, (uint256, ERC20, ICurvePool)); (, , ,address rewardPool, ,) = (booster()).poolInfo(pid); uint256 stakedBalance = IRewardPool(rewardPool).balanceOf(msg.sender); - if(stakedBalance == 0) return 0; + uint256 lpBalance = lpToken.balanceOf(msg.sender); + + uint256 lpValue; + if(lpBalance != 0) { + lpValue = pool.calc_withdraw_one_coin(lpBalance, 0); + } + + // console.log('lpValue'); + // console.log(lpValue); + + // console.log('lpBalance'); + // console.log(lpBalance); + + if(stakedBalance == 0) return lpValue; + + uint256 stakedValue = pool.calc_withdraw_one_coin(stakedBalance, 0); + + // console.log('stakedBalance'); + // console.log(stakedBalance); + + // console.log('stakedValue'); + // console.log(stakedValue); // returns how much do we get if were to withdraw the whole position from convex and curve - return pool.calc_withdraw_one_coin(stakedBalance, 0); + // plus lp balance + return stakedValue + lpValue; } /** @@ -115,27 +138,27 @@ contract ConvexAdaptor is BaseAdaptor { * @notice Open a position in convex */ function openPosition( - uint256 amount, uint256 pid, + uint256 amount, ERC20 lpToken ) public { - _addToPosition(amount, pid, lpToken); + _addToPosition(pid, amount, lpToken); } /** * @notice Add to a position in convex */ function addToPosition( - uint256 amount, uint256 pid, + uint256 amount, ERC20 lpToken ) public { - _addToPosition(amount, pid, lpToken); + _addToPosition(pid, amount, lpToken); } function _addToPosition( - uint256 amount, uint256 pid, + uint256 amount, ERC20 lpToken ) internal { lpToken.safeApprove(address(booster()), amount); diff --git a/test/testAdaptors/Convex.t.sol b/test/testAdaptors/Convex.t.sol index 79b88970..d759e658 100644 --- a/test/testAdaptors/Convex.t.sol +++ b/test/testAdaptors/Convex.t.sol @@ -6,6 +6,8 @@ import { MockCellar, Cellar, ERC4626, ERC20 } from "src/mocks/MockCellar.sol"; import { ConvexAdaptor } from "src/modules/adaptors/Convex/ConvexAdaptor.sol"; import { BaseAdaptor } from "src/modules/adaptors/BaseAdaptor.sol"; import { IBooster } from "src/interfaces/external/IBooster.sol"; +import { IRewardPool } from "src/interfaces/external/IRewardPool.sol"; + import { ICurvePool } from "src/interfaces/external/ICurvePool.sol"; import { Registry } from "src/Registry.sol"; @@ -43,7 +45,12 @@ contract CellarConvexTest is Test { IBooster private booster = IBooster(0xF403C135812408BFbE8713b5A23a04b3D48AAE31); ICurvePool curve3Pool = ICurvePool(0xbEbc44782C7dB0a1A60Cb6fe97d0b483032FF1C7); + uint256 private constant PID_3CRV = 9; + + IRewardPool rewardPool; + uint32 private lp3crvPosition; + uint32 private daiPosition; function setUp() external { @@ -53,31 +60,48 @@ contract CellarConvexTest is Test { registry = new Registry(address(this), address(swapRouter), address(priceRouter)); - priceRouter.addAsset(USDC, 0, 0, false, 0); - priceRouter.addAsset(USDT, 0, 0, false, 0); priceRouter.addAsset(DAI, 0, 0, false, 0); + priceRouter.addAsset(USDT, 0, 0, false, 0); + priceRouter.addAsset(USDC, 0, 0, false, 0); // Setup Cellar: // Cellar positions array. - uint32[] memory positions = new uint32[](1); + uint32[] memory positions = new uint32[](2); // Add adaptors and positions to the registry. registry.trustAdaptor(address(convexAdaptor), 0, 0); + registry.trustAdaptor(address(erc20Adaptor), 0, 0); - lp3crvPosition = registry.trustPosition(address(convexAdaptor), false, abi.encode(uint256(9), address(DAI), curve3Pool), 0, 0); - - positions[0] = lp3crvPosition; + daiPosition = registry.trustPosition(address(erc20Adaptor), false, abi.encode(DAI), 0, 0); + lp3crvPosition = registry.trustPosition(address(convexAdaptor), false, abi.encode(PID_3CRV, address(DAI), curve3Pool), 0, 0); - bytes[] memory positionConfigs = new bytes[](1); + positions[0] = daiPosition; + positions[1] = lp3crvPosition; + + bytes[] memory positionConfigs = new bytes[](2); cellar = new MockCellar(registry, DAI, positions, positionConfigs, "Convex Cellar", "CONVEX-CLR", strategist); + vm.label(address(curve3Pool), "curve pool"); + vm.label(address(convexAdaptor), "convexAdaptor"); + vm.label(address(this), "tester"); vm.label(address(cellar), "cellar"); vm.label(strategist, "strategist"); + vm.label(address(DAI), "dai token"); + vm.label(address(USDC), "usdc token"); + vm.label(address(USDT), "usdt token"); cellar.setupAdaptor(address(convexAdaptor)); - USDC.safeApprove(address(cellar), type(uint256).max); + DAI.safeApprove(address(cellar), type(uint256).max); + USDC.safeApprove(address(cellar), type(uint128).max); + USDT.safeApprove(address(cellar), type(uint128).max); + + // get initialize reward pool + (, , ,address rp, ,) = booster.poolInfo(PID_3CRV); + rewardPool = IRewardPool(rp); + + console.logAddress(rp); // Manipulate test contracts storage so that minimum shareLockPeriod is zero blocks. stdstore.target(address(cellar)).sig(cellar.shareLockPeriod.selector).checked_write(uint256(0)); @@ -93,4 +117,81 @@ contract CellarConvexTest is Test { ); } + function testOpenPosition() external { + deal(address(LP3CRV), address(cellar), 100_000e18); + // deal(address(DAI), address(cellar), 1e18); + + vm.prank(address(cellar)); + console.log(convexAdaptor.balanceOf(abi.encode(PID_3CRV, LP3CRV, curve3Pool))); + + // Use `callOnAdaptor` to deposit LP into curve pool + Cellar.AdaptorCall[] memory data = new Cellar.AdaptorCall[](1); + bytes[] memory adaptorCalls = new bytes[](1); + adaptorCalls[0] = _createBytesDataToOpenPosition( + PID_3CRV, + 1e18, + LP3CRV + ); + + data[0] = Cellar.AdaptorCall({ adaptor: address(convexAdaptor), callData: adaptorCalls }); + + cellar.callOnAdaptor(data); + + + vm.prank(address(cellar)); + console.log(convexAdaptor.balanceOf(abi.encode(PID_3CRV, LP3CRV, curve3Pool))); + } + + function _createBytesDataToOpenPosition( + uint256 pid, + uint256 amount, + ERC20 lpToken + ) internal pure returns (bytes memory) { + return + abi.encodeWithSelector( + ConvexAdaptor.openPosition.selector, + pid, + amount, + lpToken + ); + } + + function _createBytesDataToAddToPosition( + uint256 pid, + uint256 amount, + ERC20 lpToken + ) internal pure returns (bytes memory) { + return + abi.encodeWithSelector( + ConvexAdaptor.addToPosition.selector, + pid, + amount, + lpToken + ); + } + + function _createBytesDataToClosePosition( + uint256 pid + ) internal pure returns (bytes memory) { + return + abi.encodeWithSelector( + ConvexAdaptor.closePosition.selector, + pid + ); + } + + function _createBytesDataToTakeFromPosition( + uint256 pid, + uint256 amount + ) internal pure returns (bytes memory) { + return + abi.encodeWithSelector( + ConvexAdaptor.takeFromPosition.selector, + pid, + amount + ); + } + + + } From cf2788b5e6b3be0ae595d40e31795db1ad43d615 Mon Sep 17 00:00:00 2001 From: cookiesanddudes Date: Sun, 30 Oct 2022 02:03:47 +0000 Subject: [PATCH 36/47] add convex test... working --- src/modules/adaptors/Convex/ConvexAdaptor.sol | 15 +--- test/testAdaptors/Convex.t.sol | 79 +++++++++++++------ test/testAdaptors/Curve3Pool.t.sol | 1 - 3 files changed, 56 insertions(+), 39 deletions(-) diff --git a/src/modules/adaptors/Convex/ConvexAdaptor.sol b/src/modules/adaptors/Convex/ConvexAdaptor.sol index 56bc79d3..dcebea92 100644 --- a/src/modules/adaptors/Convex/ConvexAdaptor.sol +++ b/src/modules/adaptors/Convex/ConvexAdaptor.sol @@ -83,6 +83,7 @@ contract ConvexAdaptor is BaseAdaptor { /** * @notice Calculates this positions LP tokens underlying worth in terms of `token0`. + * @dev Takes into account */ function balanceOf(bytes memory adaptorData) public view override returns (uint256) { (uint256 pid, ERC20 lpToken, ICurvePool pool) = abi.decode(adaptorData, (uint256, ERC20, ICurvePool)); @@ -98,24 +99,10 @@ contract ConvexAdaptor is BaseAdaptor { lpValue = pool.calc_withdraw_one_coin(lpBalance, 0); } - // console.log('lpValue'); - // console.log(lpValue); - - // console.log('lpBalance'); - // console.log(lpBalance); - if(stakedBalance == 0) return lpValue; uint256 stakedValue = pool.calc_withdraw_one_coin(stakedBalance, 0); - // console.log('stakedBalance'); - // console.log(stakedBalance); - - // console.log('stakedValue'); - // console.log(stakedValue); - - // returns how much do we get if were to withdraw the whole position from convex and curve - // plus lp balance return stakedValue + lpValue; } diff --git a/test/testAdaptors/Convex.t.sol b/test/testAdaptors/Convex.t.sol index d759e658..50f0e5ac 100644 --- a/test/testAdaptors/Convex.t.sol +++ b/test/testAdaptors/Convex.t.sol @@ -4,6 +4,8 @@ pragma solidity 0.8.16; import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; import { MockCellar, Cellar, ERC4626, ERC20 } from "src/mocks/MockCellar.sol"; import { ConvexAdaptor } from "src/modules/adaptors/Convex/ConvexAdaptor.sol"; +import { Curve3PoolAdaptor } from "src/modules/adaptors/Curve/Curve3PoolAdaptor.sol"; + import { BaseAdaptor } from "src/modules/adaptors/BaseAdaptor.sol"; import { IBooster } from "src/interfaces/external/IBooster.sol"; import { IRewardPool } from "src/interfaces/external/IRewardPool.sol"; @@ -26,6 +28,8 @@ contract CellarConvexTest is Test { using stdStorage for StdStorage; ConvexAdaptor private convexAdaptor; + Curve3PoolAdaptor private curve3PoolAdaptor; + ERC20Adaptor private erc20Adaptor; MockCellar private cellar; PriceRouter private priceRouter; @@ -51,10 +55,12 @@ contract CellarConvexTest is Test { uint32 private lp3crvPosition; uint32 private daiPosition; + uint32 private curvePosition; function setUp() external { convexAdaptor = new ConvexAdaptor(); + curve3PoolAdaptor = new Curve3PoolAdaptor(); erc20Adaptor = new ERC20Adaptor(); priceRouter = new PriceRouter(); @@ -66,19 +72,23 @@ contract CellarConvexTest is Test { // Setup Cellar: // Cellar positions array. - uint32[] memory positions = new uint32[](2); + uint32[] memory positions = new uint32[](3); // Add adaptors and positions to the registry. - registry.trustAdaptor(address(convexAdaptor), 0, 0); registry.trustAdaptor(address(erc20Adaptor), 0, 0); + registry.trustAdaptor(address(convexAdaptor), 0, 0); + registry.trustAdaptor(address(curve3PoolAdaptor), 0, 0); + daiPosition = registry.trustPosition(address(erc20Adaptor), false, abi.encode(DAI), 0, 0); lp3crvPosition = registry.trustPosition(address(convexAdaptor), false, abi.encode(PID_3CRV, address(DAI), curve3Pool), 0, 0); + curvePosition = registry.trustPosition(address(curve3PoolAdaptor), false, abi.encode(ICurvePool(0xbEbc44782C7dB0a1A60Cb6fe97d0b483032FF1C7), address(0x6c3F90f043a72FA612cbac8115EE7e52BDe6E490)), 0, 0); positions[0] = daiPosition; positions[1] = lp3crvPosition; + positions[2] = curvePosition; - bytes[] memory positionConfigs = new bytes[](2); + bytes[] memory positionConfigs = new bytes[](3); cellar = new MockCellar(registry, DAI, positions, positionConfigs, "Convex Cellar", "CONVEX-CLR", strategist); @@ -92,6 +102,7 @@ contract CellarConvexTest is Test { vm.label(address(USDT), "usdt token"); cellar.setupAdaptor(address(convexAdaptor)); + cellar.setupAdaptor(address(curve3PoolAdaptor)); DAI.safeApprove(address(cellar), type(uint256).max); USDC.safeApprove(address(cellar), type(uint128).max); @@ -101,35 +112,42 @@ contract CellarConvexTest is Test { (, , ,address rp, ,) = booster.poolInfo(PID_3CRV); rewardPool = IRewardPool(rp); - console.logAddress(rp); - // Manipulate test contracts storage so that minimum shareLockPeriod is zero blocks. stdstore.target(address(cellar)).sig(cellar.shareLockPeriod.selector).checked_write(uint256(0)); } - function testFaucet() external { - uint256 amount = 100e18; - deal(address(DAI), address(this), amount); - assertEq( - DAI.balanceOf(address(this)), - amount, - "Should be able to deal some tokens" - ); - } - + // opens position in curve and deposits LP into convex function testOpenPosition() external { - deal(address(LP3CRV), address(cellar), 100_000e18); - // deal(address(DAI), address(cellar), 1e18); + // first, mint dai into the cellar + deal(address(DAI), address(cellar), 100_000e18); - vm.prank(address(cellar)); - console.log(convexAdaptor.balanceOf(abi.encode(PID_3CRV, LP3CRV, curve3Pool))); + // then, deposit dai into curve through Curve adaptor // Use `callOnAdaptor` to deposit LP into curve pool Cellar.AdaptorCall[] memory data = new Cellar.AdaptorCall[](1); bytes[] memory adaptorCalls = new bytes[](1); + adaptorCalls[0] = _createBytesDataToOpenCurvePosition( + 10e18, + 0, + 0, + 0 + ); + + data[0] = Cellar.AdaptorCall({ adaptor: address(curve3PoolAdaptor), callData: adaptorCalls }); + + cellar.callOnAdaptor(data); + + + // vm.prank(address(cellar)); + // convexAdaptor.balanceOf(abi.encode(PID_3CRV, LP3CRV, curve3Pool)); + + // last, open position on convex using the freshly minted LP + // Use `callOnAdaptor` to deposit LP into convex pool + data = new Cellar.AdaptorCall[](1); + adaptorCalls = new bytes[](1); adaptorCalls[0] = _createBytesDataToOpenPosition( PID_3CRV, - 1e18, + 5e18, LP3CRV ); @@ -137,9 +155,10 @@ contract CellarConvexTest is Test { cellar.callOnAdaptor(data); - + // assert we have deposited 5e18 tokens into convex reward pool + assertEq(rewardPool.balanceOf(address(cellar)), 5e18); vm.prank(address(cellar)); - console.log(convexAdaptor.balanceOf(abi.encode(PID_3CRV, LP3CRV, curve3Pool))); + assertGe(convexAdaptor.balanceOf(abi.encode(PID_3CRV, LP3CRV, curve3Pool)), 0); } function _createBytesDataToOpenPosition( @@ -192,6 +211,18 @@ contract CellarConvexTest is Test { ); } - - + function _createBytesDataToOpenCurvePosition( + uint256 amount0, + uint256 amount1, + uint256 amount2, + uint256 minimumMintAmount + ) internal view returns (bytes memory) { + return + abi.encodeWithSelector( + Curve3PoolAdaptor.openPosition.selector, + [amount0, amount1,amount2], + minimumMintAmount, + curve3Pool + ); + } } diff --git a/test/testAdaptors/Curve3Pool.t.sol b/test/testAdaptors/Curve3Pool.t.sol index fdc2118a..f2eee9aa 100644 --- a/test/testAdaptors/Curve3Pool.t.sol +++ b/test/testAdaptors/Curve3Pool.t.sol @@ -347,7 +347,6 @@ contract Curve3PoolTest is Test { cellar.callOnAdaptor(data); uint256 lpBalance = LP3CRV.balanceOf(address(cellar)); - uint256 daiBalanceBefore = DAI.balanceOf(address(cellar)); // assert balanceOf is bigger than 0 vm.prank(address(cellar)); From bba8b2b1b7f138ec4895b9b11c6e14940533f26a Mon Sep 17 00:00:00 2001 From: cookiesanddudes Date: Sun, 30 Oct 2022 03:08:39 +0000 Subject: [PATCH 37/47] add open and close test --- src/modules/adaptors/Convex/ConvexAdaptor.sol | 15 ++-- test/testAdaptors/Convex.t.sol | 76 ++++++++++++++++++- 2 files changed, 79 insertions(+), 12 deletions(-) diff --git a/src/modules/adaptors/Convex/ConvexAdaptor.sol b/src/modules/adaptors/Convex/ConvexAdaptor.sol index dcebea92..5afafe7d 100644 --- a/src/modules/adaptors/Convex/ConvexAdaptor.sol +++ b/src/modules/adaptors/Convex/ConvexAdaptor.sol @@ -174,20 +174,17 @@ contract ConvexAdaptor is BaseAdaptor { } - /** - * @notice Attempted to take from convex position but failed - */ - error ConvexAdaptor_ClosePositionFailed(); - /** * @notice Close position in convex + * @dev does NOT claim rewards */ function closePosition( - uint256 pid + uint256 pid, + bool claim ) public { - if (!booster().withdrawAll(pid)){ - revert ConvexAdaptor_ClosePositionFailed(); - } + (, , ,address rewardPool, ,) = (booster()).poolInfo(pid); + + IRewardPool(rewardPool).withdrawAllAndUnwrap(claim); } /** diff --git a/test/testAdaptors/Convex.t.sol b/test/testAdaptors/Convex.t.sol index 50f0e5ac..cb6a2b2a 100644 --- a/test/testAdaptors/Convex.t.sol +++ b/test/testAdaptors/Convex.t.sol @@ -155,12 +155,78 @@ contract CellarConvexTest is Test { cellar.callOnAdaptor(data); - // assert we have deposited 5e18 tokens into convex reward pool + // assert we have deposited 5e18 tokens into convex reward assertEq(rewardPool.balanceOf(address(cellar)), 5e18); vm.prank(address(cellar)); assertGe(convexAdaptor.balanceOf(abi.encode(PID_3CRV, LP3CRV, curve3Pool)), 0); } + + // opens position in curve and deposits LP into convex + function testOpeningAndClosingPosition() external { + // first, mint dai into the cellar + deal(address(DAI), address(cellar), 100_000e18); + + + // then, deposit dai into curve through Curve adaptor + + // Use `callOnAdaptor` to deposit token into curve pool + Cellar.AdaptorCall[] memory data = new Cellar.AdaptorCall[](1); + bytes[] memory adaptorCalls = new bytes[](1); + adaptorCalls[0] = _createBytesDataToOpenCurvePosition( + 10e18, + 0, + 0, + 0 + ); + + data[0] = Cellar.AdaptorCall({ adaptor: address(curve3PoolAdaptor), callData: adaptorCalls }); + + cellar.callOnAdaptor(data); + + uint256 initialLPBalance = LP3CRV.balanceOf(address(cellar)); + // console.log(LP3CRV.balanceOf(address(cellar))); + + // vm.prank(address(cellar)); + // convexAdaptor.balanceOf(abi.encode(PID_3CRV, LP3CRV, curve3Pool)); + + // last, open position on convex using the freshly minted LP + // Use `callOnAdaptor` to deposit LP into convex pool + data = new Cellar.AdaptorCall[](1); + adaptorCalls = new bytes[](1); + adaptorCalls[0] = _createBytesDataToOpenPosition( + PID_3CRV, + 5e18, + LP3CRV + ); + + data[0] = Cellar.AdaptorCall({ adaptor: address(convexAdaptor), callData: adaptorCalls }); + + cellar.callOnAdaptor(data); + + // assert we have deposited 5e18 tokens into convex reward + assertEq(rewardPool.balanceOf(address(cellar)), 5e18); + assertLe(LP3CRV.balanceOf(address(cellar)), initialLPBalance); + + // now, close the position + data = new Cellar.AdaptorCall[](1); + adaptorCalls = new bytes[](1); + adaptorCalls[0] = _createBytesDataToClosePosition( + PID_3CRV, + true + ); + + data[0] = Cellar.AdaptorCall({ adaptor: address(convexAdaptor), callData: adaptorCalls }); + + cellar.callOnAdaptor(data); + + uint256 finalLPBalance = LP3CRV.balanceOf(address(cellar)); + + // check we recovered all of our initial LP + assertEq(initialLPBalance, finalLPBalance); + assertEq(rewardPool.balanceOf(address(cellar)), 0); + } + function _createBytesDataToOpenPosition( uint256 pid, uint256 amount, @@ -190,12 +256,14 @@ contract CellarConvexTest is Test { } function _createBytesDataToClosePosition( - uint256 pid + uint256 pid, + bool claimRewards ) internal pure returns (bytes memory) { return abi.encodeWithSelector( ConvexAdaptor.closePosition.selector, - pid + pid, + claimRewards ); } @@ -226,3 +294,5 @@ contract CellarConvexTest is Test { ); } } + + From 2a655ac8fe25d3c24e1df8732e0335d3adcd5ef5 Mon Sep 17 00:00:00 2001 From: cookiesanddudes Date: Sun, 30 Oct 2022 03:34:52 +0000 Subject: [PATCH 38/47] convex tests --- src/interfaces/external/IRewardPool.sol | 1 + src/modules/adaptors/Convex/ConvexAdaptor.sol | 14 +-- test/testAdaptors/Convex.t.sol | 107 ++++++++++++++++-- 3 files changed, 105 insertions(+), 17 deletions(-) diff --git a/src/interfaces/external/IRewardPool.sol b/src/interfaces/external/IRewardPool.sol index a430990d..b52c620a 100644 --- a/src/interfaces/external/IRewardPool.sol +++ b/src/interfaces/external/IRewardPool.sol @@ -9,6 +9,7 @@ interface IRewardPool is IERC20 { function getReward(address _account, bool _claimExtras) external returns(bool); function withdrawAllAndUnwrap(bool claim) external; function withdraw(uint256 amount, bool claim) external; + function withdrawAndUnwrap(uint256 amount, bool claim) external; function stake(uint256 _amount) external; function rewardPerToken() external view returns (uint256); } \ No newline at end of file diff --git a/src/modules/adaptors/Convex/ConvexAdaptor.sol b/src/modules/adaptors/Convex/ConvexAdaptor.sol index 5afafe7d..1417d2fa 100644 --- a/src/modules/adaptors/Convex/ConvexAdaptor.sol +++ b/src/modules/adaptors/Convex/ConvexAdaptor.sol @@ -156,21 +156,17 @@ contract ConvexAdaptor is BaseAdaptor { } } - /** - * @notice Attempted to take from convex position but failed - */ - error ConvexAdaptor_TakeFromPositionFailed(); - /** * @notice Close position in convex */ function takeFromPosition( uint256 pid, - uint256 amount + uint256 amount, + bool claim ) public { - if (!(booster()).withdraw(pid, amount)) { - revert ConvexAdaptor_TakeFromPositionFailed(); - } + (, , ,address rewardPool, ,) = (booster()).poolInfo(pid); + + IRewardPool(rewardPool).withdrawAndUnwrap(amount,claim); } diff --git a/test/testAdaptors/Convex.t.sol b/test/testAdaptors/Convex.t.sol index cb6a2b2a..f127381b 100644 --- a/test/testAdaptors/Convex.t.sol +++ b/test/testAdaptors/Convex.t.sol @@ -137,8 +137,6 @@ contract CellarConvexTest is Test { cellar.callOnAdaptor(data); - - // vm.prank(address(cellar)); // convexAdaptor.balanceOf(abi.encode(PID_3CRV, LP3CRV, curve3Pool)); // last, open position on convex using the freshly minted LP @@ -185,9 +183,9 @@ contract CellarConvexTest is Test { cellar.callOnAdaptor(data); uint256 initialLPBalance = LP3CRV.balanceOf(address(cellar)); - // console.log(LP3CRV.balanceOf(address(cellar))); + - // vm.prank(address(cellar)); + // convexAdaptor.balanceOf(abi.encode(PID_3CRV, LP3CRV, curve3Pool)); // last, open position on convex using the freshly minted LP @@ -227,6 +225,97 @@ contract CellarConvexTest is Test { assertEq(rewardPool.balanceOf(address(cellar)), 0); } + // opens position in curve and deposits LP into convex + function testAddAndTakeFromPositionAndClaimRewards() external { + // first, mint dai into the cellar + deal(address(DAI), address(cellar), 100_000e18); + + // then, deposit dai into curve through Curve adaptor + + // Use `callOnAdaptor` to deposit token into curve pool + Cellar.AdaptorCall[] memory data = new Cellar.AdaptorCall[](1); + bytes[] memory adaptorCalls = new bytes[](1); + adaptorCalls[0] = _createBytesDataToOpenCurvePosition( + 10e18, + 0, + 0, + 0 + ); + + data[0] = Cellar.AdaptorCall({ adaptor: address(curve3PoolAdaptor), callData: adaptorCalls }); + + cellar.callOnAdaptor(data); + + + // last, open position on convex using the freshly minted LP + // Use `callOnAdaptor` to deposit LP into convex pool + data = new Cellar.AdaptorCall[](1); + adaptorCalls = new bytes[](1); + adaptorCalls[0] = _createBytesDataToOpenPosition( + PID_3CRV, + 5e18, + LP3CRV + ); + + data[0] = Cellar.AdaptorCall({ adaptor: address(convexAdaptor), callData: adaptorCalls }); + + cellar.callOnAdaptor(data); + + // assert we have deposited 5e18 tokens into convex reward + assertEq(rewardPool.balanceOf(address(cellar)), 5e18); + uint256 intermediateLPBalance = LP3CRV.balanceOf(address(cellar)); + + // now, reduce the position + data = new Cellar.AdaptorCall[](1); + adaptorCalls = new bytes[](1); + adaptorCalls[0] = _createBytesDataToTakeFromPosition( + PID_3CRV, + 1e18, + true + ); + + data[0] = Cellar.AdaptorCall({ adaptor: address(convexAdaptor), callData: adaptorCalls }); + + cellar.callOnAdaptor(data); + + uint256 finalLPBalance = LP3CRV.balanceOf(address(cellar)); + + // check we recovered the 1e18 LP + assertEq(intermediateLPBalance, finalLPBalance - 1e18); + + // now, add to the position + data = new Cellar.AdaptorCall[](1); + adaptorCalls = new bytes[](1); + adaptorCalls[0] = _createBytesDataToAddToPosition( + PID_3CRV, + 2e18, + LP3CRV + ); + data[0] = Cellar.AdaptorCall({ adaptor: address(convexAdaptor), callData: adaptorCalls }); + + cellar.callOnAdaptor(data); + + // now, claim rewards + data = new Cellar.AdaptorCall[](1); + adaptorCalls = new bytes[](1); + adaptorCalls[0] = _createBytesDataToClaimRewards( + PID_3CRV + ); + data[0] = Cellar.AdaptorCall({ adaptor: address(convexAdaptor), callData: adaptorCalls }); + + cellar.callOnAdaptor(data); + } + + function _createBytesDataToClaimRewards( + uint256 pid + ) internal pure returns (bytes memory) { + return + abi.encodeWithSelector( + ConvexAdaptor.claimRewards.selector, + pid + ); + } + function _createBytesDataToOpenPosition( uint256 pid, uint256 amount, @@ -257,25 +346,27 @@ contract CellarConvexTest is Test { function _createBytesDataToClosePosition( uint256 pid, - bool claimRewards + bool claim ) internal pure returns (bytes memory) { return abi.encodeWithSelector( ConvexAdaptor.closePosition.selector, pid, - claimRewards + claim ); } function _createBytesDataToTakeFromPosition( uint256 pid, - uint256 amount + uint256 amount, + bool claim ) internal pure returns (bytes memory) { return abi.encodeWithSelector( ConvexAdaptor.takeFromPosition.selector, pid, - amount + amount, + claim ); } From ae3d3661c97d98f63382f2561100064990a4e71d Mon Sep 17 00:00:00 2001 From: cookiesanddudes Date: Sun, 30 Oct 2022 03:49:57 +0000 Subject: [PATCH 39/47] rewards! --- src/modules/adaptors/Convex/ConvexAdaptor.sol | 1 + test/testAdaptors/Convex.t.sol | 13 +++++++++++++ 2 files changed, 14 insertions(+) diff --git a/src/modules/adaptors/Convex/ConvexAdaptor.sol b/src/modules/adaptors/Convex/ConvexAdaptor.sol index 1417d2fa..b97ef889 100644 --- a/src/modules/adaptors/Convex/ConvexAdaptor.sol +++ b/src/modules/adaptors/Convex/ConvexAdaptor.sol @@ -190,6 +190,7 @@ contract ConvexAdaptor is BaseAdaptor { /** * @notice Claims rewards and extras from convex + * TODO: distribute these rewards to timelockERC20 adaptor in feat/timelockERC20 branch (out of scope for the hackathon) */ function claimRewards(uint256 pid) public { (, , ,address rewardPool, ,) = (booster()).poolInfo(pid); diff --git a/test/testAdaptors/Convex.t.sol b/test/testAdaptors/Convex.t.sol index f127381b..2770614c 100644 --- a/test/testAdaptors/Convex.t.sol +++ b/test/testAdaptors/Convex.t.sol @@ -40,6 +40,7 @@ contract CellarConvexTest is Test { ERC20 private WETH = ERC20(0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2); ERC20 private CVX = ERC20(0x4e3FBD56CD56c3e72c1403e103b45Db9da5B9D2B); + ERC20 private CRV = ERC20(0xD533a949740bb3306d119CC777fa900bA034cd52); ERC20 private LP3CRV = ERC20(0x6c3F90f043a72FA612cbac8115EE7e52BDe6E490); ERC20 private USDC = ERC20(0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48); @@ -283,6 +284,14 @@ contract CellarConvexTest is Test { // check we recovered the 1e18 LP assertEq(intermediateLPBalance, finalLPBalance - 1e18); + // time travel to accumulate rewards + vm.warp(block.timestamp + 2000); + vm.roll(block.number + 5); + + // Check that we are poor before claiming + assertEq(CRV.balanceOf(address(cellar)), 0); + assertEq(CVX.balanceOf(address(cellar)), 0); + // now, add to the position data = new Cellar.AdaptorCall[](1); adaptorCalls = new bytes[](1); @@ -304,6 +313,10 @@ contract CellarConvexTest is Test { data[0] = Cellar.AdaptorCall({ adaptor: address(convexAdaptor), callData: adaptorCalls }); cellar.callOnAdaptor(data); + + // Check that we got some juicy rewards + assertGe(CRV.balanceOf(address(cellar)), 0); + assertGe(CVX.balanceOf(address(cellar)), 0); } function _createBytesDataToClaimRewards( From d15ed495e4445218ac13c2240276f4bd10a8dc21 Mon Sep 17 00:00:00 2001 From: cookiesanddudes Date: Sun, 30 Oct 2022 03:51:21 +0000 Subject: [PATCH 40/47] fix comment --- src/modules/adaptors/Convex/ConvexAdaptor.sol | 1 - 1 file changed, 1 deletion(-) diff --git a/src/modules/adaptors/Convex/ConvexAdaptor.sol b/src/modules/adaptors/Convex/ConvexAdaptor.sol index b97ef889..7f41e2e9 100644 --- a/src/modules/adaptors/Convex/ConvexAdaptor.sol +++ b/src/modules/adaptors/Convex/ConvexAdaptor.sol @@ -172,7 +172,6 @@ contract ConvexAdaptor is BaseAdaptor { /** * @notice Close position in convex - * @dev does NOT claim rewards */ function closePosition( uint256 pid, From e12ad2433477688507e37d8ea56e9194e8c6a02b Mon Sep 17 00:00:00 2001 From: cookiesanddudes Date: Sun, 30 Oct 2022 03:58:36 +0000 Subject: [PATCH 41/47] adaptors --- src/modules/adaptors/Convex/ConvexAdaptor.sol | 13 +++++-------- src/modules/adaptors/Curve/Curve3PoolAdaptor.sol | 4 ++++ 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/src/modules/adaptors/Convex/ConvexAdaptor.sol b/src/modules/adaptors/Convex/ConvexAdaptor.sol index 7f41e2e9..70e1c276 100644 --- a/src/modules/adaptors/Convex/ConvexAdaptor.sol +++ b/src/modules/adaptors/Convex/ConvexAdaptor.sol @@ -8,7 +8,6 @@ import { IBooster } from "src/interfaces/external/IBooster.sol"; import { IRewardPool } from "src/interfaces/external/IRewardPool.sol"; import { ICurvePool } from "src/interfaces/external/ICurvePool.sol"; -import { Test, stdStorage, console, StdStorage, stdError } from "@forge-std/Test.sol"; /** @@ -83,15 +82,14 @@ contract ConvexAdaptor is BaseAdaptor { /** * @notice Calculates this positions LP tokens underlying worth in terms of `token0`. - * @dev Takes into account + * @dev Takes into account Cellar LP balance and also staked LP balance + * @dev The unit is the token0 of the curve pool where the LP was minted. See `assetOf()` */ function balanceOf(bytes memory adaptorData) public view override returns (uint256) { (uint256 pid, ERC20 lpToken, ICurvePool pool) = abi.decode(adaptorData, (uint256, ERC20, ICurvePool)); (, , ,address rewardPool, ,) = (booster()).poolInfo(pid); - - uint256 stakedBalance = IRewardPool(rewardPool).balanceOf(msg.sender); - + uint256 stakedLpBalance = IRewardPool(rewardPool).balanceOf(msg.sender); uint256 lpBalance = lpToken.balanceOf(msg.sender); uint256 lpValue; @@ -99,9 +97,8 @@ contract ConvexAdaptor is BaseAdaptor { lpValue = pool.calc_withdraw_one_coin(lpBalance, 0); } - if(stakedBalance == 0) return lpValue; - - uint256 stakedValue = pool.calc_withdraw_one_coin(stakedBalance, 0); + if(stakedLpBalance == 0) return lpValue; + uint256 stakedValue = pool.calc_withdraw_one_coin(stakedLpBalance, 0); return stakedValue + lpValue; } diff --git a/src/modules/adaptors/Curve/Curve3PoolAdaptor.sol b/src/modules/adaptors/Curve/Curve3PoolAdaptor.sol index b4fe386d..3ae6bb1a 100644 --- a/src/modules/adaptors/Curve/Curve3PoolAdaptor.sol +++ b/src/modules/adaptors/Curve/Curve3PoolAdaptor.sol @@ -161,7 +161,11 @@ contract Curve3PoolAdaptor is BaseAdaptor { pool.remove_liquidity_one_coin(amount, 0, minimumAmount); } + /** + * @notice Strategist attempted to withdraw an already closed position + */ error Curve3PoolAdaptor__PositionClosed(); + /** * @notice Strategist use `closePosition` to remove all of a positions liquidity. * @notice If receiving amount of token0 is less than minimumMintAmount function will revert. From 76f4ec4d8f7213633a6e54dceeafd0eb162b790e Mon Sep 17 00:00:00 2001 From: cookiesanddudes Date: Sun, 30 Oct 2022 04:00:32 +0000 Subject: [PATCH 42/47] linting --- src/modules/adaptors/Convex/ConvexAdaptor.sol | 1 - 1 file changed, 1 deletion(-) diff --git a/src/modules/adaptors/Convex/ConvexAdaptor.sol b/src/modules/adaptors/Convex/ConvexAdaptor.sol index 70e1c276..8ccad647 100644 --- a/src/modules/adaptors/Convex/ConvexAdaptor.sol +++ b/src/modules/adaptors/Convex/ConvexAdaptor.sol @@ -5,7 +5,6 @@ import { BaseAdaptor, ERC20, SafeERC20, Cellar, PriceRouter, Registry, Math } fr import { SafeCast } from "@openzeppelin/contracts/utils/math/SafeCast.sol"; import { Address } from "@openzeppelin/contracts/utils/Address.sol"; import { IBooster } from "src/interfaces/external/IBooster.sol"; - import { IRewardPool } from "src/interfaces/external/IRewardPool.sol"; import { ICurvePool } from "src/interfaces/external/ICurvePool.sol"; From f492862486e5d6402c8358a9a6117a079417a06a Mon Sep 17 00:00:00 2001 From: cookiesanddudes Date: Sun, 30 Oct 2022 04:31:46 +0000 Subject: [PATCH 43/47] comments --- src/modules/adaptors/Convex/ConvexAdaptor.sol | 37 ++++++++++++++++--- .../adaptors/Curve/Curve3PoolAdaptor.sol | 11 ++++-- 2 files changed, 38 insertions(+), 10 deletions(-) diff --git a/src/modules/adaptors/Convex/ConvexAdaptor.sol b/src/modules/adaptors/Convex/ConvexAdaptor.sol index 8ccad647..7d1abcbd 100644 --- a/src/modules/adaptors/Convex/ConvexAdaptor.sol +++ b/src/modules/adaptors/Convex/ConvexAdaptor.sol @@ -12,7 +12,7 @@ import { ICurvePool } from "src/interfaces/external/ICurvePool.sol"; /** * @title Convex Adaptor * @notice Allows Cellars to interact with Convex Positions. - * @author + * @author cookiesanddudes, federava */ contract ConvexAdaptor is BaseAdaptor { using SafeERC20 for ERC20; @@ -87,15 +87,20 @@ contract ConvexAdaptor is BaseAdaptor { function balanceOf(bytes memory adaptorData) public view override returns (uint256) { (uint256 pid, ERC20 lpToken, ICurvePool pool) = abi.decode(adaptorData, (uint256, ERC20, ICurvePool)); + // get reward pool where the LP are staked (, , ,address rewardPool, ,) = (booster()).poolInfo(pid); uint256 stakedLpBalance = IRewardPool(rewardPool).balanceOf(msg.sender); + + // get amount of LP owned uint256 lpBalance = lpToken.balanceOf(msg.sender); + // calculate lp owned value uint256 lpValue; if(lpBalance != 0) { lpValue = pool.calc_withdraw_one_coin(lpBalance, 0); } + // calculate stakedLp Value if(stakedLpBalance == 0) return lpValue; uint256 stakedValue = pool.calc_withdraw_one_coin(stakedLpBalance, 0); @@ -118,7 +123,10 @@ contract ConvexAdaptor is BaseAdaptor { error ConvexAdaptor_DepositFailed(); /** - * @notice Open a position in convex + * @notice Allows strategist to open a Convex position. + * @param pid convex pool id + * @param amount of LP to stake + * @param lpToken the corresponding LP token */ function openPosition( uint256 pid, @@ -129,7 +137,10 @@ contract ConvexAdaptor is BaseAdaptor { } /** - * @notice Add to a position in convex + * @notice Allows strategist to add liquidity to a Convex position. + * @param pid convex pool id + * @param amount of LP to stake + * @param lpToken the corresponding LP token */ function addToPosition( uint256 pid, @@ -153,7 +164,16 @@ contract ConvexAdaptor is BaseAdaptor { } /** - * @notice Close position in convex + * @notice Strategist attempted to remove all of a positions liquidity using `takeFromPosition`, + * but they need to use `closePosition`. + */ + error ConvexAdaptor__CallClosePosition(); + + /** + * @notice Allows strategist to remove liquidity from a position + * @param pid convex pool id + * @param amount of LP to stake + * @param claim true if rewards should be claimed when withdrawing */ function takeFromPosition( uint256 pid, @@ -162,12 +182,16 @@ contract ConvexAdaptor is BaseAdaptor { ) public { (, , ,address rewardPool, ,) = (booster()).poolInfo(pid); + if(IRewardPool(rewardPool).balanceOf(msg.sender) == amount) revert ConvexAdaptor__CallClosePosition(); + IRewardPool(rewardPool).withdrawAndUnwrap(amount,claim); } /** - * @notice Close position in convex + * @notice Allows strategist to close a position + * @param pid convex pool id + * @param claim true if rewards should be claimed when withdrawing */ function closePosition( uint256 pid, @@ -184,7 +208,8 @@ contract ConvexAdaptor is BaseAdaptor { error ConvexAdaptor_CouldNotClaimRewards(); /** - * @notice Claims rewards and extras from convex + * @notice Allows strategist to claim rewards and extras from convex + * @param pid convex pool id * TODO: distribute these rewards to timelockERC20 adaptor in feat/timelockERC20 branch (out of scope for the hackathon) */ function claimRewards(uint256 pid) public { diff --git a/src/modules/adaptors/Curve/Curve3PoolAdaptor.sol b/src/modules/adaptors/Curve/Curve3PoolAdaptor.sol index 3ae6bb1a..dfd2b35e 100644 --- a/src/modules/adaptors/Curve/Curve3PoolAdaptor.sol +++ b/src/modules/adaptors/Curve/Curve3PoolAdaptor.sol @@ -9,9 +9,9 @@ import { ICurvePool } from "src/interfaces/external/ICurvePool.sol"; /** - * @title Curve 3 Pool Adaptor - * @notice Allows Cellars to interact with Curve Positions. - * @author + * @title Curve 3Pool Adaptor + * @notice Allows Cellars to interact with 3Pool Curve Positions. + * @author cookiesanddudes, federava */ contract Curve3PoolAdaptor is BaseAdaptor { using SafeERC20 for ERC20; @@ -125,10 +125,13 @@ contract Curve3PoolAdaptor is BaseAdaptor { pool.add_liquidity(amounts, minimumMintAmount); } - error Curve3PoolAdaptor__CallClosePosition(); /** * @notice Strategist attempted to remove all of a positions liquidity using `takeFromPosition`, * but they need to use `closePosition`. + */ + error Curve3PoolAdaptor__CallClosePosition(); + + /** * @notice If receiving amount of token0 is less than minimumMintAmount function will revert. * @param amount lp token amount to be burned. * @param minimumAmount receiving at least this amount of token0. From af3079b9eb36b2a3f445bf18ec33d2d0e595cd2a Mon Sep 17 00:00:00 2001 From: federava Date: Sun, 30 Oct 2022 05:50:29 +0100 Subject: [PATCH 44/47] feature: Curve2Pool --- src/interfaces/external/ICurve2Pool.sol | 16 + .../adaptors/Curve/Curve2PoolAdaptor.sol | 213 +++++++++++ test/testAdaptors/Curve2Pool.t.sol | 362 ++++++++++++++++++ 3 files changed, 591 insertions(+) create mode 100644 src/interfaces/external/ICurve2Pool.sol create mode 100644 src/modules/adaptors/Curve/Curve2PoolAdaptor.sol create mode 100644 test/testAdaptors/Curve2Pool.t.sol diff --git a/src/interfaces/external/ICurve2Pool.sol b/src/interfaces/external/ICurve2Pool.sol new file mode 100644 index 00000000..c31c1977 --- /dev/null +++ b/src/interfaces/external/ICurve2Pool.sol @@ -0,0 +1,16 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.16; + +// ICurvePool interface +interface ICurvePool { + function coins(uint256) external view returns (address); + function balances(uint256) external view returns (uint256); + function get_virtual_price() external view returns (uint256); + function calc_withdraw_one_coin(uint256 token_amount, int128 i) external view returns (uint256); + + function add_liquidity(uint256[2] calldata amounts, uint256 min_mint_amount, address _receiver) external returns (uint256); + function exchange(int128 i, int128 j, uint256 dx, uint256 min_dy) external; + function remove_liquidity(uint256 _amount, uint256[3] calldata min_amounts) external; + function remove_liquidity_imbalance(uint256[2] calldata amounts, uint256 max_burn_amount) external; + function remove_liquidity_one_coin(uint256 _burn_amount, int128 i, uint256 _min_received, address _receiver) external returns (uint256); +} \ No newline at end of file diff --git a/src/modules/adaptors/Curve/Curve2PoolAdaptor.sol b/src/modules/adaptors/Curve/Curve2PoolAdaptor.sol new file mode 100644 index 00000000..f8d05c5b --- /dev/null +++ b/src/modules/adaptors/Curve/Curve2PoolAdaptor.sol @@ -0,0 +1,213 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity 0.8.16; + +import { BaseAdaptor, ERC20, SafeERC20, Cellar, PriceRouter, Registry, Math } from "src/modules/adaptors/BaseAdaptor.sol"; +import { SafeCast } from "@openzeppelin/contracts/utils/math/SafeCast.sol"; +import { Address } from "@openzeppelin/contracts/utils/Address.sol"; +import { IBooster } from "src/interfaces/external/IBooster.sol"; +import { ICurvePool } from "src/interfaces/external/ICurve2Pool.sol"; + + +/** + * @title Curve 2 Pool Adaptor + * @notice Allows Cellars to interact with Curve Positions. + * @author + */ +contract Curve2PoolAdaptor is BaseAdaptor { + using SafeERC20 for ERC20; + using Math for uint256; + using SafeCast for uint256; + using Address for address; + + //==================== Adaptor Data Specification ==================== + // adaptorData = abi.encode(ICurvePool curvePool, address lpToken) + // Where: + // - curvePool is the pool concerned by the position + // - lpToken is the lp generated by the pool (in old curve contracts, + // it is not available as a public method in the pool) + // + // Uses token0. In the case of curve3Pool it is DAI. + //==================================================================== + + //============================================ Global Functions =========================================== + /** + * @dev Identifier unique to this adaptor for a shared registry. + * Normally the identifier would just be the address of this contract, but this + * Identifier is needed during Cellar Delegate Call Operations, so getting the address + * of the adaptor is more difficult. + */ + function identifier() public pure override returns (bytes32) { + return keccak256(abi.encode("Curve 2Pool Adaptor V 0.0")); + } + + //============================================ Implement Base Functions =========================================== + /** + * @notice User deposits are NOT allowed into this position. + */ + function deposit( + uint256, + bytes memory, + bytes memory + ) public pure override { + revert BaseAdaptor__UserDepositsNotAllowed(); + } + + /** + * @notice User withdraws are NOT allowed from this position. + */ + function withdraw( + uint256, + address, + bytes memory, + bytes memory + ) public pure override { + revert BaseAdaptor__UserWithdrawsNotAllowed(); + } + + /** + * @notice User withdraws are not allowed so this position must return 0 for withdrawableFrom. + */ + function withdrawableFrom(bytes memory, bytes memory) public pure override returns (uint256) { + return 0; + } + + /** + * @notice Calculates this positions LP tokens underlying worth in terms of `token0`. + * @notice Curve pools provide a calculation where the amount returned considers the swapping of token1 and token2 + * for token0 considering fees. + */ + function balanceOf(bytes memory adaptorData) public view override returns (uint256) { + (ICurvePool pool, ERC20 lpToken) = abi.decode(adaptorData, (ICurvePool, ERC20)); + + // Calculates amount of token0 is recieved when burning all LP tokens. + uint256 lpBalance = lpToken.balanceOf(msg.sender); + + // return 0 if lp balance is null + if(lpBalance == 0) return 0; + + return pool.calc_withdraw_one_coin(lpBalance, 0); + } + + /** + * @notice Returns `coins(0)` or token0 + */ + function assetOf(bytes memory adaptorData) public view override returns (ERC20) { + (ICurvePool pool, ) = abi.decode(adaptorData, (ICurvePool, ERC20)); + return ERC20(pool.coins(0)); + } + + //============================================ Strategist Functions =========================================== + /** + * @notice Allows strategist to open up arbritray Curve positions. + * @notice Allows to send any combination of token0, token1 and token2 which the pool will + * balance on deposit. + * @notice If minted lp tokens is less than minimumMintAmount function will revert. + * @param amounts token0, token1, and token2 amounts to be deposited. + * @param minimumMintAmount minting at least this amount of lp tokens. + * @param pool specifies the interface of the pool + */ + function openPosition( + uint256[2] memory amounts, + uint256 minimumMintAmount, + ICurvePool pool + ) public { + for(uint256 i; i Date: Sun, 30 Oct 2022 04:56:43 +0000 Subject: [PATCH 45/47] add authors --- src/modules/adaptors/Curve/Curve2PoolAdaptor.sol | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/modules/adaptors/Curve/Curve2PoolAdaptor.sol b/src/modules/adaptors/Curve/Curve2PoolAdaptor.sol index f8d05c5b..eb337063 100644 --- a/src/modules/adaptors/Curve/Curve2PoolAdaptor.sol +++ b/src/modules/adaptors/Curve/Curve2PoolAdaptor.sol @@ -9,9 +9,9 @@ import { ICurvePool } from "src/interfaces/external/ICurve2Pool.sol"; /** - * @title Curve 2 Pool Adaptor - * @notice Allows Cellars to interact with Curve Positions. - * @author + * @title Curve 2Pool Adaptor + * @notice Allows Cellars to interact with 2Pool Curve Positions. + * @author cookiesanddudes, federava */ contract Curve2PoolAdaptor is BaseAdaptor { using SafeERC20 for ERC20; From d5991bc4ac3f9337f288e58f4e3a164c63df5bc4 Mon Sep 17 00:00:00 2001 From: cookiesanddudes Date: Sun, 30 Oct 2022 04:59:08 +0000 Subject: [PATCH 46/47] remove unused variables --- src/modules/adaptors/Curve/Curve2PoolAdaptor.sol | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/modules/adaptors/Curve/Curve2PoolAdaptor.sol b/src/modules/adaptors/Curve/Curve2PoolAdaptor.sol index eb337063..6110a3c9 100644 --- a/src/modules/adaptors/Curve/Curve2PoolAdaptor.sol +++ b/src/modules/adaptors/Curve/Curve2PoolAdaptor.sol @@ -122,7 +122,7 @@ contract Curve2PoolAdaptor is BaseAdaptor { } } - (uint256 mintAmount) = pool.add_liquidity(amounts, minimumMintAmount, address(this)); + pool.add_liquidity(amounts, minimumMintAmount, address(this)); } error Curve2PoolAdaptor__CallClosePosition(); @@ -158,7 +158,7 @@ contract Curve2PoolAdaptor is BaseAdaptor { uint256 minimumAmount, ICurvePool pool ) internal { - (uint256 mintAmount) = pool.remove_liquidity_one_coin(amount, 0, minimumAmount, address(this)); + pool.remove_liquidity_one_coin(amount, 0, minimumAmount, address(this)); } error Curve2PoolAdaptor__PositionClosed(); @@ -207,7 +207,7 @@ contract Curve2PoolAdaptor is BaseAdaptor { } } - (uint256 mintAmount) = pool.add_liquidity(amounts, minimumMintAmount, address(this)); + pool.add_liquidity(amounts, minimumMintAmount, address(this)); } } From f4d4ff48d4c393d086a830f15c5429f312c71d0e Mon Sep 17 00:00:00 2001 From: cookiesanddudes Date: Sun, 30 Oct 2022 05:15:49 +0000 Subject: [PATCH 47/47] final linting --- src/modules/adaptors/Convex/ConvexAdaptor.sol | 33 ++-- .../adaptors/Curve/Curve2PoolAdaptor.sol | 29 ++-- .../adaptors/Curve/Curve3PoolAdaptor.sol | 29 ++-- test/testAdaptors/Convex.t.sol | 149 +++++------------- test/testAdaptors/Curve2Pool.t.sol | 104 ++++-------- test/testAdaptors/Curve3Pool.t.sol | 142 +++++------------ 6 files changed, 156 insertions(+), 330 deletions(-) diff --git a/src/modules/adaptors/Convex/ConvexAdaptor.sol b/src/modules/adaptors/Convex/ConvexAdaptor.sol index 7d1abcbd..4618da85 100644 --- a/src/modules/adaptors/Convex/ConvexAdaptor.sol +++ b/src/modules/adaptors/Convex/ConvexAdaptor.sol @@ -8,7 +8,6 @@ import { IBooster } from "src/interfaces/external/IBooster.sol"; import { IRewardPool } from "src/interfaces/external/IRewardPool.sol"; import { ICurvePool } from "src/interfaces/external/ICurvePool.sol"; - /** * @title Convex Adaptor * @notice Allows Cellars to interact with Convex Positions. @@ -46,7 +45,6 @@ contract ConvexAdaptor is BaseAdaptor { return IBooster(0xF403C135812408BFbE8713b5A23a04b3D48AAE31); } - //============================================ Implement Base Functions =========================================== /** @@ -88,7 +86,7 @@ contract ConvexAdaptor is BaseAdaptor { (uint256 pid, ERC20 lpToken, ICurvePool pool) = abi.decode(adaptorData, (uint256, ERC20, ICurvePool)); // get reward pool where the LP are staked - (, , ,address rewardPool, ,) = (booster()).poolInfo(pid); + (, , , address rewardPool, , ) = (booster()).poolInfo(pid); uint256 stakedLpBalance = IRewardPool(rewardPool).balanceOf(msg.sender); // get amount of LP owned @@ -96,14 +94,14 @@ contract ConvexAdaptor is BaseAdaptor { // calculate lp owned value uint256 lpValue; - if(lpBalance != 0) { + if (lpBalance != 0) { lpValue = pool.calc_withdraw_one_coin(lpBalance, 0); } // calculate stakedLp Value - if(stakedLpBalance == 0) return lpValue; + if (stakedLpBalance == 0) return lpValue; uint256 stakedValue = pool.calc_withdraw_one_coin(stakedLpBalance, 0); - + return stakedValue + lpValue; } @@ -158,7 +156,7 @@ contract ConvexAdaptor is BaseAdaptor { lpToken.safeApprove(address(booster()), amount); // always assume we are staking - if(!(booster()).deposit(pid, amount, true)) { + if (!(booster()).deposit(pid, amount, true)) { revert ConvexAdaptor_DepositFailed(); } } @@ -170,7 +168,7 @@ contract ConvexAdaptor is BaseAdaptor { error ConvexAdaptor__CallClosePosition(); /** - * @notice Allows strategist to remove liquidity from a position + * @notice Allows strategist to remove liquidity from a position * @param pid convex pool id * @param amount of LP to stake * @param claim true if rewards should be claimed when withdrawing @@ -180,24 +178,20 @@ contract ConvexAdaptor is BaseAdaptor { uint256 amount, bool claim ) public { - (, , ,address rewardPool, ,) = (booster()).poolInfo(pid); + (, , , address rewardPool, , ) = (booster()).poolInfo(pid); - if(IRewardPool(rewardPool).balanceOf(msg.sender) == amount) revert ConvexAdaptor__CallClosePosition(); + if (IRewardPool(rewardPool).balanceOf(msg.sender) == amount) revert ConvexAdaptor__CallClosePosition(); - IRewardPool(rewardPool).withdrawAndUnwrap(amount,claim); + IRewardPool(rewardPool).withdrawAndUnwrap(amount, claim); } - /** * @notice Allows strategist to close a position * @param pid convex pool id * @param claim true if rewards should be claimed when withdrawing */ - function closePosition( - uint256 pid, - bool claim - ) public { - (, , ,address rewardPool, ,) = (booster()).poolInfo(pid); + function closePosition(uint256 pid, bool claim) public { + (, , , address rewardPool, , ) = (booster()).poolInfo(pid); IRewardPool(rewardPool).withdrawAllAndUnwrap(claim); } @@ -213,11 +207,10 @@ contract ConvexAdaptor is BaseAdaptor { * TODO: distribute these rewards to timelockERC20 adaptor in feat/timelockERC20 branch (out of scope for the hackathon) */ function claimRewards(uint256 pid) public { - (, , ,address rewardPool, ,) = (booster()).poolInfo(pid); + (, , , address rewardPool, , ) = (booster()).poolInfo(pid); - if (!IRewardPool(rewardPool).getReward()){ + if (!IRewardPool(rewardPool).getReward()) { revert ConvexAdaptor_CouldNotClaimRewards(); } } - } diff --git a/src/modules/adaptors/Curve/Curve2PoolAdaptor.sol b/src/modules/adaptors/Curve/Curve2PoolAdaptor.sol index 6110a3c9..57970dc4 100644 --- a/src/modules/adaptors/Curve/Curve2PoolAdaptor.sol +++ b/src/modules/adaptors/Curve/Curve2PoolAdaptor.sol @@ -7,7 +7,6 @@ import { Address } from "@openzeppelin/contracts/utils/Address.sol"; import { IBooster } from "src/interfaces/external/IBooster.sol"; import { ICurvePool } from "src/interfaces/external/ICurve2Pool.sol"; - /** * @title Curve 2Pool Adaptor * @notice Allows Cellars to interact with 2Pool Curve Positions. @@ -23,9 +22,9 @@ contract Curve2PoolAdaptor is BaseAdaptor { // adaptorData = abi.encode(ICurvePool curvePool, address lpToken) // Where: // - curvePool is the pool concerned by the position - // - lpToken is the lp generated by the pool (in old curve contracts, + // - lpToken is the lp generated by the pool (in old curve contracts, // it is not available as a public method in the pool) - // + // // Uses token0. In the case of curve3Pool it is DAI. //==================================================================== @@ -83,7 +82,7 @@ contract Curve2PoolAdaptor is BaseAdaptor { uint256 lpBalance = lpToken.balanceOf(msg.sender); // return 0 if lp balance is null - if(lpBalance == 0) return 0; + if (lpBalance == 0) return 0; return pool.calc_withdraw_one_coin(lpBalance, 0); } @@ -107,11 +106,11 @@ contract Curve2PoolAdaptor is BaseAdaptor { * @param pool specifies the interface of the pool */ function openPosition( - uint256[2] memory amounts, - uint256 minimumMintAmount, + uint256[2] memory amounts, + uint256 minimumMintAmount, ICurvePool pool ) public { - for(uint256 i; i