From fce3b2dc701364e6f0f79d006f48e981b7c0ac45 Mon Sep 17 00:00:00 2001 From: ftperez11 Date: Sun, 12 Mar 2023 22:46:21 -0400 Subject: [PATCH 1/8] saving work first commit for hop adapter and inerface --- .../src/vault/adapter/hop/HopAdapter.sol | 114 ++++++++++++++++++ .../foundry/src/vault/adapter/hop/IHop.sol | 20 +++ .../vault/integration/hop/HopAdapter.t.sol | 83 +++++++++++++ .../integration/hop/HoptestConfigStorage.sol | 23 ++++ 4 files changed, 240 insertions(+) create mode 100644 packages/foundry/src/vault/adapter/hop/HopAdapter.sol create mode 100644 packages/foundry/src/vault/adapter/hop/IHop.sol create mode 100644 packages/foundry/test/vault/integration/hop/HopAdapter.t.sol create mode 100644 packages/foundry/test/vault/integration/hop/HoptestConfigStorage.sol diff --git a/packages/foundry/src/vault/adapter/hop/HopAdapter.sol b/packages/foundry/src/vault/adapter/hop/HopAdapter.sol new file mode 100644 index 00000000..de80793f --- /dev/null +++ b/packages/foundry/src/vault/adapter/hop/HopAdapter.sol @@ -0,0 +1,114 @@ +// SPDX-License-Identifier: GPL-3.0 +// Docgen-SOLC: 0.8.15 + +pragma solidity ^0.8.15; + +import { AdapterBase, IERC20, IERC20Metadata, SafeERC20, ERC20, Math, IStrategy, IAdapter } from "../abstracts/AdapterBase.sol"; +import { WithRewards, IWithRewards } from "../abstracts/WithRewards.sol"; +import { IRewardPool } from "./IHop.sol"; + +/** + * @title Hop Protocol Adapter + * @notice ERC4626 wrapper for Hop Vaults. + * + * An ERC4626 compliant Wrapper for https://github.com/sushiswap/sushiswap/blob/archieve/canary/contracts/MasterChefV2.sol. + * Allows wrapping Hop Vaults. + */ +contract HopAdapter is AdapterBase, WithRewards { + using SafeERC20 for IERC20; + using Math for uint256; + + string internal _name; + string internal _symbol; + + // @notice The Reward interface + IRewardPool public rewardPool; + + // @notice The address of the reward token + address public rewardsToken; + + // @notice The pool ID + uint256 public pid; + + /** + * @notice Initialize a new Hop Adapter. + * @param adapterInitData Encoded data for the base adapter initialization. + * @dev `_pid` - The poolId for lpToken. + * @dev This function is called by the factory contract when deploying a new vault. + */ + + function initialize( + bytes memory adapterInitData, + address registry, + bytes memory masterchefInitData + ) external initializer { + (uint256 _pid, address _rewardsToken) = abi.decode(masterchefInitData, (uint256, address)); + + rewardPool = IRewardPool(registry); + + __AdapterBase_init(adapterInitData); + + pid = _pid; + rewardsToken = _rewardsToken; + IMasterChef.PoolInfo memory pool = masterChef.poolInfo(_pid); + + _name = string.concat("Popcorn Hop", IERC20Metadata(asset()).name(), " Adapter"); + _symbol = string.concat("popB-", IERC20Metadata(asset()).symbol()); + + IERC20(pool.lpToken).approve(address(masterChef), type(uint256).max); + } +2 + function name() public view override(IERC20Metadata, ERC20) returns (string memory) { + return _name; + } + + function symbol() public view override(IERC20Metadata, ERC20) returns (string memory) { + return _symbol; + } + + /*////////////////////////////////////////////////////////////// + ACCOUNTING LOGIC + //////////////////////////////////////////////////////////////*/ + + /// @notice Calculates the total amount of underlying tokens the Vault holds. + /// @return The total amount of underlying tokens the Vault holds. + + function _totalAssets() internal view override returns (uint256) { + IMasterChef.UserInfo memory user = masterChef.userInfo(pid, address(this)); + return user.amount; + } + + /*////////////////////////////////////////////////////////////// + INTERNAL HOOKS LOGIC + //////////////////////////////////////////////////////////////*/ + + function _protocolDeposit(uint256 amount, uint256) internal virtual override { + masterChef.deposit(pid, amount); + } + + function _protocolWithdraw(uint256 amount, uint256) internal virtual override { + masterChef.withdraw(pid, amount); + } + + /*////////////////////////////////////////////////////////////// + STRATEGY LOGIC + //////////////////////////////////////////////////////////////*/ + /// @notice Claim rewards from the masterChef + function claim() public override onlyStrategy { + masterChef.deposit(pid, 0); + } + + /// @notice The token rewarded + function rewardTokens() external view override returns (address[] memory) { + address[] memory _rewardTokens = new address[](1); + _rewardTokens[0] = rewardsToken; + } + + /*////////////////////////////////////////////////////////////// + EIP-165 LOGIC + //////////////////////////////////////////////////////////////*/ + + function supportsInterface(bytes4 interfaceId) public pure override(WithRewards, AdapterBase) returns (bool) { + return interfaceId == type(IWithRewards).interfaceId || interfaceId == type(IAdapter).interfaceId; + } +} \ No newline at end of file diff --git a/packages/foundry/src/vault/adapter/hop/IHop.sol b/packages/foundry/src/vault/adapter/hop/IHop.sol new file mode 100644 index 00000000..93aed12c --- /dev/null +++ b/packages/foundry/src/vault/adapter/hop/IHop.sol @@ -0,0 +1,20 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity ^0.8.10; + +interface IRewardPool { + function deposit(uint256 amount) external; + + function stake(uint256 amount) external; + + function withdraw(uint256 amount) external; + + function earned(address account) external view returns (uint256); + + function getReward() external; + + function balanceOf(address account) external view returns (uint256); + + function stakingToken() external view returns (address); + + function rewardsToken() external view returns (address); +} diff --git a/packages/foundry/test/vault/integration/hop/HopAdapter.t.sol b/packages/foundry/test/vault/integration/hop/HopAdapter.t.sol new file mode 100644 index 00000000..622194b2 --- /dev/null +++ b/packages/foundry/test/vault/integration/hop/HopAdapter.t.sol @@ -0,0 +1,83 @@ +// SPDX-License-Identifier: GPL-3.0 +pragma solidity ^0.8.15; + +import { Test } from "forge-std/Test.sol"; + +import { HopAdapter, SafeERC20, IERC20, IERC20Metadata, Math, IRewardPool } from "../../../../src/vault/adapter/sushi/HopAdapter.sol"; +import { HopTestConfigStorage, HopTestConfig } from "./HopTestConfigStorage.sol"; +import { AbstractAdapterTest, ITestConfigStorage, IAdapter } from "../abstract/AbstractAdapterTest.sol"; + +contract HopAdapterTest is AbstractAdapterTest { + using Math for uint256; + + //This is an erc token https://etherscan.io/address/0xc5102fe9359fd9a28f877a67e36b0f050d81a3cc#writeContract + IRewardPool public rewardPool = IRewardPool(0xc5102fE9359FD9a28f877a67E36B0F050d81a3CC); + + address public want; + + function setUp() public { + uint256 forkId = vm.createSelectFork(vm.rpcUrl("mainnet")); + vm.selectFork(forkId); + + testConfigStorage = ITestConfigStorage(address(new HopTestConfigStorage())); + + _setUpTest(testConfigStorage.getTestConfig(0)); + } + + function overrideSetup(bytes memory testConfig) public override { + _setUpTest(testConfig); + } + + function _setUpTest(bytes memory testConfig) internal { + address _want = abi.decode(testConfig, (uint256, address)); + + want = _want; + + setUpBaseTest(IERC20(want), address(new HopAdapter()), address(rewardPool), 10, "MasterChef", true); + + vm.label(address(rewardPool), "hop"); + vm.label(address(asset), "asset"); + vm.label(address(this), "test"); + + adapter.initialize(abi.encode(asset, address(this), strategy, 0, sigs, ""), externalRegistry, testConfig); + } + + /*////////////////////////////////////////////////////////////// + HELPER + //////////////////////////////////////////////////////////////*/ + + // Verify that totalAssets returns the expected amount + function verify_totalAssets() public override { + deal(address(asset), bob, defaultAmount); + vm.startPrank(bob); + asset.approve(address(adapter), defaultAmount); + adapter.deposit(defaultAmount, bob); + vm.stopPrank(); + + assertEq( + adapter.totalAssets(), + adapter.convertToAssets(adapter.totalSupply()), + string.concat("totalSupply converted != totalAssets", baseTestId) + ); + } + + /*////////////////////////////////////////////////////////////// + INITIALIZATION + //////////////////////////////////////////////////////////////*/ + + function verify_adapterInit() public override { + assertEq(adapter.asset(), address(asset), "asset"); + assertEq( + IERC20Metadata(address(adapter)).symbol(), + string.concat("popB-", IERC20Metadata(address(asset)).symbol()), + "symbol" + ); + assertEq( + IERC20Metadata(address(adapter)).symbol(), + string.concat("popB-", IERC20Metadata(address(asset)).symbol()), + "symbol" + ); + + assertEq(asset.allowance(address(adapter), address(masterChef)), type(uint256).max, "allowance"); + } +} diff --git a/packages/foundry/test/vault/integration/hop/HoptestConfigStorage.sol b/packages/foundry/test/vault/integration/hop/HoptestConfigStorage.sol new file mode 100644 index 00000000..f40623f5 --- /dev/null +++ b/packages/foundry/test/vault/integration/hop/HoptestConfigStorage.sol @@ -0,0 +1,23 @@ +pragma solidity ^0.8.15; + +import { ITestConfigStorage } from "../abstract/ITestConfigStorage.sol"; + +struct HopTestConfig { + address want; +} + +contract HopTestConfigStorage is ITestConfigStorage { + HopTestConfig[] internal testConfigs; + + constructor() { + testConfigs.push(HopTestConfig(0xc5102fE9359FD9a28f877a67E36B0F050d81a3CC)); + } + + function getTestConfig(uint256 i) public view returns (bytes memory) { + return abi.encode(testConfigs[i].want); + } + + function getTestConfigLength() public view returns (uint256) { + return testConfigs.length; + } +} From e96a2c64b3306ead1b985cb7d253a8db5e4010eb Mon Sep 17 00:00:00 2001 From: ftperez11 Date: Tue, 28 Mar 2023 23:35:22 -0400 Subject: [PATCH 2/8] saving work --- .../src/vault/adapter/hop/HopAdapter.sol | 65 +++++++++++-------- .../foundry/src/vault/adapter/hop/IHop.sol | 62 ++++++++++++++++-- .../vault/integration/hop/HopAdapter.t.sol | 22 +++---- ...igStorage.sol => HopTestConfigStorage.sol} | 0 4 files changed, 104 insertions(+), 45 deletions(-) rename packages/foundry/test/vault/integration/hop/{HoptestConfigStorage.sol => HopTestConfigStorage.sol} (100%) diff --git a/packages/foundry/src/vault/adapter/hop/HopAdapter.sol b/packages/foundry/src/vault/adapter/hop/HopAdapter.sol index de80793f..53eec907 100644 --- a/packages/foundry/src/vault/adapter/hop/HopAdapter.sol +++ b/packages/foundry/src/vault/adapter/hop/HopAdapter.sol @@ -5,13 +5,13 @@ pragma solidity ^0.8.15; import { AdapterBase, IERC20, IERC20Metadata, SafeERC20, ERC20, Math, IStrategy, IAdapter } from "../abstracts/AdapterBase.sol"; import { WithRewards, IWithRewards } from "../abstracts/WithRewards.sol"; -import { IRewardPool } from "./IHop.sol"; +import { ILiquidityPool, IStakingRewards } from "./IHop.sol"; /** * @title Hop Protocol Adapter * @notice ERC4626 wrapper for Hop Vaults. * - * An ERC4626 compliant Wrapper for https://github.com/sushiswap/sushiswap/blob/archieve/canary/contracts/MasterChefV2.sol. + * An ERC4626 compliant Wrapper for https://github.com/hop-exchange/contracts/blob/28ec0f1a8df497a102c0a3e779a68a81bf69b9ad/contracts/saddle/Swap.sol. * Allows wrapping Hop Vaults. */ contract HopAdapter is AdapterBase, WithRewards { @@ -22,42 +22,41 @@ contract HopAdapter is AdapterBase, WithRewards { string internal _symbol; // @notice The Reward interface - IRewardPool public rewardPool; + IStakingRewards public stakingRewards; - // @notice The address of the reward token - address public rewardsToken; + /// @notice The Hop protocol swap contract + ILiquidityPool public liquidityPool; - // @notice The pool ID - uint256 public pid; + // @notice The address of the LP token + address public LPToken; /** * @notice Initialize a new Hop Adapter. * @param adapterInitData Encoded data for the base adapter initialization. - * @dev `_pid` - The poolId for lpToken. * @dev This function is called by the factory contract when deploying a new vault. */ function initialize( bytes memory adapterInitData, address registry, - bytes memory masterchefInitData + bytes memory hopInitData ) external initializer { - (uint256 _pid, address _rewardsToken) = abi.decode(masterchefInitData, (uint256, address)); + __AdapterBase_init(adapterInitData); - rewardPool = IRewardPool(registry); + (address _liquidityPool, address _stakingRewards) = abi.decode(hopInitData, (address, address)); - __AdapterBase_init(adapterInitData); + liquidityPool = ILiquidityPool(registry); + stakingRewards = IStakingRewards(_stakingRewards); - pid = _pid; - rewardsToken = _rewardsToken; - IMasterChef.PoolInfo memory pool = masterChef.poolInfo(_pid); + ILiquidityPool.Swap memory swapStorage = liquidityPool.swapStorage(); + LPToken = swapStorage.lpToken; _name = string.concat("Popcorn Hop", IERC20Metadata(asset()).name(), " Adapter"); _symbol = string.concat("popB-", IERC20Metadata(asset()).symbol()); - IERC20(pool.lpToken).approve(address(masterChef), type(uint256).max); + IERC20(LPToken).approve(address(liquidityPool), type(uint256).max); } -2 + function name() public view override(IERC20Metadata, ERC20) returns (string memory) { return _name; } @@ -74,34 +73,46 @@ contract HopAdapter is AdapterBase, WithRewards { /// @return The total amount of underlying tokens the Vault holds. function _totalAssets() internal view override returns (uint256) { - IMasterChef.UserInfo memory user = masterChef.userInfo(pid, address(this)); - return user.amount; + return paused() ? IERC20(asset()).balanceOf(address(this)) : stakingRewards.balanceOf(address(this)); } /*////////////////////////////////////////////////////////////// INTERNAL HOOKS LOGIC //////////////////////////////////////////////////////////////*/ - function _protocolDeposit(uint256 amount, uint256) internal virtual override { - masterChef.deposit(pid, amount); + function _protocolDeposit( + uint256[] calldata amounts, + uint256 minToMint, + uint256 deadline + ) internal virtual { + liquidityPool.addLiquidity(amounts, minToMint, deadline); } - function _protocolWithdraw(uint256 amount, uint256) internal virtual override { - masterChef.withdraw(pid, amount); + function _protocolWithdraw( + uint256 amount, + uint256[] calldata minAmounts, + uint256 deadline + ) internal virtual { + liquidityPool.removeLiquidity(amount, minAmounts, deadline); } /*////////////////////////////////////////////////////////////// STRATEGY LOGIC //////////////////////////////////////////////////////////////*/ - /// @notice Claim rewards from the masterChef + /// @notice Claim rewards from Hop function claim() public override onlyStrategy { - masterChef.deposit(pid, 0); + stakingRewards.getReward(); } /// @notice The token rewarded function rewardTokens() external view override returns (address[] memory) { address[] memory _rewardTokens = new address[](1); - _rewardTokens[0] = rewardsToken; + _rewardTokens[0] = LPToken; + // return _rewardTokens; + } + + function getLPTokenAdress() external view returns (address) { + return LPToken; } /*////////////////////////////////////////////////////////////// @@ -111,4 +122,4 @@ contract HopAdapter is AdapterBase, WithRewards { function supportsInterface(bytes4 interfaceId) public pure override(WithRewards, AdapterBase) returns (bool) { return interfaceId == type(IWithRewards).interfaceId || interfaceId == type(IAdapter).interfaceId; } -} \ No newline at end of file +} diff --git a/packages/foundry/src/vault/adapter/hop/IHop.sol b/packages/foundry/src/vault/adapter/hop/IHop.sol index 93aed12c..147e0946 100644 --- a/packages/foundry/src/vault/adapter/hop/IHop.sol +++ b/packages/foundry/src/vault/adapter/hop/IHop.sol @@ -1,20 +1,68 @@ // SPDX-License-Identifier: AGPL-3.0-only pragma solidity ^0.8.10; -interface IRewardPool { - function deposit(uint256 amount) external; +// this is the LP token +// https://github.com/hop-exchange/contracts/blob/28ec0f1a8df497a102c0a3e779a68a81bf69b9ad/contracts/saddle/LPToken.sol - function stake(uint256 amount) external; +interface ILiquidityPool { + struct Swap { + uint256 initialA; + uint256 futureA; + uint256 initialATime; + uint256 futureATime; + uint256 swapFee; + uint256 adminFee; + uint256 defaultWithdrawFee; + address lpToken; + // LPToken lpToken; + // IERC20[] pooledTokens; + uint256[] tokenPrecisionMultipliers; + uint256[] balances; + // mapping(address => uint256) depositTimestamp; + // mapping(address => uint256) withdrawFeeMultiplier; + } - function withdraw(uint256 amount) external; + function addLiquidity( + uint256[] calldata amounts, + uint256 minToMint, + uint256 deadline + ) external returns (uint256); + + function removeLiquidity( + uint256 amount, + uint256[] calldata minAmounts, + uint256 deadline + ) external returns (uint256[] memory); + + function getVirtualPrice() external view returns (uint256); + + function swapStorage() external view returns (ILiquidityPool.Swap memory); +} + +// this is lp token on arbitrum https://arbiscan.io/address/0xb0cabfe930642ad3e7decdc741884d8c3f7ebc70#writeContract + +// they are using this contract https://github.com/Synthetixio/synthetix/blob/develop/contracts/StakingRewards.sol +interface IStakingRewards { + // Views + function lastTimeRewardApplicable() external view returns (uint256); + + function rewardPerToken() external view returns (uint256); function earned(address account) external view returns (uint256); - function getReward() external; + function getRewardForDuration() external view returns (uint256); + + function totalSupply() external view returns (uint256); function balanceOf(address account) external view returns (uint256); - function stakingToken() external view returns (address); + // Mutative + + function stake(uint256 amount) external; + + function withdraw(uint256 amount) external; + + function getReward() external; - function rewardsToken() external view returns (address); + function exit() external; } diff --git a/packages/foundry/test/vault/integration/hop/HopAdapter.t.sol b/packages/foundry/test/vault/integration/hop/HopAdapter.t.sol index 622194b2..5e6f88d6 100644 --- a/packages/foundry/test/vault/integration/hop/HopAdapter.t.sol +++ b/packages/foundry/test/vault/integration/hop/HopAdapter.t.sol @@ -3,17 +3,17 @@ pragma solidity ^0.8.15; import { Test } from "forge-std/Test.sol"; -import { HopAdapter, SafeERC20, IERC20, IERC20Metadata, Math, IRewardPool } from "../../../../src/vault/adapter/sushi/HopAdapter.sol"; +import { HopAdapter, SafeERC20, IERC20, IERC20Metadata, Math, ILiquidityPool, IStakingRewards } from "../../../../src/vault/adapter/hop/HopAdapter.sol"; import { HopTestConfigStorage, HopTestConfig } from "./HopTestConfigStorage.sol"; import { AbstractAdapterTest, ITestConfigStorage, IAdapter } from "../abstract/AbstractAdapterTest.sol"; contract HopAdapterTest is AbstractAdapterTest { using Math for uint256; - //This is an erc token https://etherscan.io/address/0xc5102fe9359fd9a28f877a67e36b0f050d81a3cc#writeContract - IRewardPool public rewardPool = IRewardPool(0xc5102fE9359FD9a28f877a67E36B0F050d81a3CC); - - address public want; + // Note: using the hop liquidity pool contract + // https://optimistic.etherscan.io/address/0xaa30d6bba6285d0585722e2440ff89e23ef68864#writeContract + ILiquidityPool public liquidityPool = ILiquidityPool(0xaa30D6bba6285d0585722e2440Ff89E23EF68864); + IStakingRewards public stakingRewards = IStakingRewards(0x3f27c540ADaE3a9E8c875C61e3B970b559d7F65d); function setUp() public { uint256 forkId = vm.createSelectFork(vm.rpcUrl("mainnet")); @@ -29,13 +29,13 @@ contract HopAdapterTest is AbstractAdapterTest { } function _setUpTest(bytes memory testConfig) internal { - address _want = abi.decode(testConfig, (uint256, address)); - - want = _want; + (address _liquidityPool, address _stakingRewards) = abi.decode(testConfig, (address, address)); + liquidityPool = ILiquidityPool(_liquidityPool); + stakingRewards = IStakingRewards(_stakingRewards); - setUpBaseTest(IERC20(want), address(new HopAdapter()), address(rewardPool), 10, "MasterChef", true); + setUpBaseTest(asset, address(new HopAdapter()), address(liquidityPool), 10, "Hop", true); - vm.label(address(rewardPool), "hop"); + vm.label(address(liquidityPool), "hop"); vm.label(address(asset), "asset"); vm.label(address(this), "test"); @@ -78,6 +78,6 @@ contract HopAdapterTest is AbstractAdapterTest { "symbol" ); - assertEq(asset.allowance(address(adapter), address(masterChef)), type(uint256).max, "allowance"); + assertEq(asset.allowance(address(adapter), address(liquidityPool)), type(uint256).max, "allowance"); } } diff --git a/packages/foundry/test/vault/integration/hop/HoptestConfigStorage.sol b/packages/foundry/test/vault/integration/hop/HopTestConfigStorage.sol similarity index 100% rename from packages/foundry/test/vault/integration/hop/HoptestConfigStorage.sol rename to packages/foundry/test/vault/integration/hop/HopTestConfigStorage.sol From 3e281cdb900fc4fdf80012251a219b1fc870ceb7 Mon Sep 17 00:00:00 2001 From: ftperez11 Date: Wed, 29 Mar 2023 00:18:49 -0400 Subject: [PATCH 3/8] test config file updates --- .../test/vault/integration/hop/HopTestConfigStorage.sol | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/packages/foundry/test/vault/integration/hop/HopTestConfigStorage.sol b/packages/foundry/test/vault/integration/hop/HopTestConfigStorage.sol index f40623f5..b33cdc10 100644 --- a/packages/foundry/test/vault/integration/hop/HopTestConfigStorage.sol +++ b/packages/foundry/test/vault/integration/hop/HopTestConfigStorage.sol @@ -3,18 +3,21 @@ pragma solidity ^0.8.15; import { ITestConfigStorage } from "../abstract/ITestConfigStorage.sol"; struct HopTestConfig { - address want; + address liquidityPool; + address stakingRewards; } contract HopTestConfigStorage is ITestConfigStorage { HopTestConfig[] internal testConfigs; constructor() { - testConfigs.push(HopTestConfig(0xc5102fE9359FD9a28f877a67E36B0F050d81a3CC)); + testConfigs.push( + HopTestConfig(0xaa30D6bba6285d0585722e2440Ff89E23EF68864, 0x3f27c540ADaE3a9E8c875C61e3B970b559d7F65d) + ); } function getTestConfig(uint256 i) public view returns (bytes memory) { - return abi.encode(testConfigs[i].want); + return abi.encode(testConfigs[i].liquidityPool, testConfigs[i].stakingRewards); } function getTestConfigLength() public view returns (uint256) { From e2cdf8adf9ad1f0322ae419bd8e5d8d9cab0aac0 Mon Sep 17 00:00:00 2001 From: ftperez11 Date: Fri, 31 Mar 2023 13:38:04 -0400 Subject: [PATCH 4/8] saving work failing on setup --- .../src/vault/adapter/hop/HopAdapter.sol | 34 ++++++++++++------- .../foundry/src/vault/adapter/hop/IHop.sol | 13 +++---- .../vault/integration/hop/HopAdapter.t.sol | 15 ++++++-- .../integration/hop/HopTestConfigStorage.sol | 2 +- 4 files changed, 40 insertions(+), 24 deletions(-) diff --git a/packages/foundry/src/vault/adapter/hop/HopAdapter.sol b/packages/foundry/src/vault/adapter/hop/HopAdapter.sol index 53eec907..8ce9bcb3 100644 --- a/packages/foundry/src/vault/adapter/hop/HopAdapter.sol +++ b/packages/foundry/src/vault/adapter/hop/HopAdapter.sol @@ -21,13 +21,18 @@ contract HopAdapter is AdapterBase, WithRewards { string internal _name; string internal _symbol; + uint256[] amounts; + uint256[] minAmounts; + uint256 deadline; + uint256 minToMint; + // @notice The Reward interface IStakingRewards public stakingRewards; /// @notice The Hop protocol swap contract ILiquidityPool public liquidityPool; - // @notice The address of the LP token + // @notice The address of the LP token //need to stake this address public LPToken; /** @@ -48,13 +53,23 @@ contract HopAdapter is AdapterBase, WithRewards { liquidityPool = ILiquidityPool(registry); stakingRewards = IStakingRewards(_stakingRewards); + require(asset() == liquidityPool.getToken(0), "Asset tokens do not match"); + ILiquidityPool.Swap memory swapStorage = liquidityPool.swapStorage(); + LPToken = swapStorage.lpToken; + require(LPToken == stakingRewards.stakingToken(), "Staking tokens do not match"); + + deadline = block.timestamp; + minToMint = liquidityPool.getVirtualPrice(); + _name = string.concat("Popcorn Hop", IERC20Metadata(asset()).name(), " Adapter"); _symbol = string.concat("popB-", IERC20Metadata(asset()).symbol()); + IERC20(asset()).approve(address(liquidityPool), type(uint256).max); IERC20(LPToken).approve(address(liquidityPool), type(uint256).max); + IERC20(LPToken).approve(address(stakingRewards), type(uint256).max); } function name() public view override(IERC20Metadata, ERC20) returns (string memory) { @@ -80,20 +95,16 @@ contract HopAdapter is AdapterBase, WithRewards { INTERNAL HOOKS LOGIC //////////////////////////////////////////////////////////////*/ - function _protocolDeposit( - uint256[] calldata amounts, - uint256 minToMint, - uint256 deadline - ) internal virtual { + function _protocolDeposit(uint256 amount, uint256) internal virtual override { + amounts = [amount, 0]; liquidityPool.addLiquidity(amounts, minToMint, deadline); + stakingRewards.stake(amount); } - function _protocolWithdraw( - uint256 amount, - uint256[] calldata minAmounts, - uint256 deadline - ) internal virtual { + function _protocolWithdraw(uint256 amount, uint256) internal virtual override { + minAmounts = [amount, 0]; liquidityPool.removeLiquidity(amount, minAmounts, deadline); + stakingRewards.withdraw(amount); } /*////////////////////////////////////////////////////////////// @@ -108,7 +119,6 @@ contract HopAdapter is AdapterBase, WithRewards { function rewardTokens() external view override returns (address[] memory) { address[] memory _rewardTokens = new address[](1); _rewardTokens[0] = LPToken; - // return _rewardTokens; } function getLPTokenAdress() external view returns (address) { diff --git a/packages/foundry/src/vault/adapter/hop/IHop.sol b/packages/foundry/src/vault/adapter/hop/IHop.sol index 147e0946..58df30f6 100644 --- a/packages/foundry/src/vault/adapter/hop/IHop.sol +++ b/packages/foundry/src/vault/adapter/hop/IHop.sol @@ -1,9 +1,6 @@ // SPDX-License-Identifier: AGPL-3.0-only pragma solidity ^0.8.10; -// this is the LP token -// https://github.com/hop-exchange/contracts/blob/28ec0f1a8df497a102c0a3e779a68a81bf69b9ad/contracts/saddle/LPToken.sol - interface ILiquidityPool { struct Swap { uint256 initialA; @@ -14,8 +11,7 @@ interface ILiquidityPool { uint256 adminFee; uint256 defaultWithdrawFee; address lpToken; - // LPToken lpToken; - // IERC20[] pooledTokens; + address[] pooledTokens; uint256[] tokenPrecisionMultipliers; uint256[] balances; // mapping(address => uint256) depositTimestamp; @@ -36,12 +32,11 @@ interface ILiquidityPool { function getVirtualPrice() external view returns (uint256); + function getToken(uint8) external view returns (address); + function swapStorage() external view returns (ILiquidityPool.Swap memory); } -// this is lp token on arbitrum https://arbiscan.io/address/0xb0cabfe930642ad3e7decdc741884d8c3f7ebc70#writeContract - -// they are using this contract https://github.com/Synthetixio/synthetix/blob/develop/contracts/StakingRewards.sol interface IStakingRewards { // Views function lastTimeRewardApplicable() external view returns (uint256); @@ -56,6 +51,8 @@ interface IStakingRewards { function balanceOf(address account) external view returns (uint256); + function stakingToken() external view returns (address); + // Mutative function stake(uint256 amount) external; diff --git a/packages/foundry/test/vault/integration/hop/HopAdapter.t.sol b/packages/foundry/test/vault/integration/hop/HopAdapter.t.sol index 5e6f88d6..39c12948 100644 --- a/packages/foundry/test/vault/integration/hop/HopAdapter.t.sol +++ b/packages/foundry/test/vault/integration/hop/HopAdapter.t.sol @@ -13,7 +13,8 @@ contract HopAdapterTest is AbstractAdapterTest { // Note: using the hop liquidity pool contract // https://optimistic.etherscan.io/address/0xaa30d6bba6285d0585722e2440ff89e23ef68864#writeContract ILiquidityPool public liquidityPool = ILiquidityPool(0xaa30D6bba6285d0585722e2440Ff89E23EF68864); - IStakingRewards public stakingRewards = IStakingRewards(0x3f27c540ADaE3a9E8c875C61e3B970b559d7F65d); + IStakingRewards public stakingRewards = IStakingRewards(0xfD49C7EE330fE060ca66feE33d49206eB96F146D); + address public LPToken; function setUp() public { uint256 forkId = vm.createSelectFork(vm.rpcUrl("mainnet")); @@ -31,11 +32,19 @@ contract HopAdapterTest is AbstractAdapterTest { function _setUpTest(bytes memory testConfig) internal { (address _liquidityPool, address _stakingRewards) = abi.decode(testConfig, (address, address)); liquidityPool = ILiquidityPool(_liquidityPool); + stakingRewards = IStakingRewards(_stakingRewards); + address asset = liquidityPool.getToken(0); + + ILiquidityPool.Swap memory swapStorage = liquidityPool.swapStorage(); + + LPToken = swapStorage.lpToken; - setUpBaseTest(asset, address(new HopAdapter()), address(liquidityPool), 10, "Hop", true); + setUpBaseTest(IERC20(asset), address(new HopAdapter()), address(liquidityPool), 10, "Hop", true); - vm.label(address(liquidityPool), "hop"); + vm.label(address(liquidityPool), "hopLiquidityPool"); + vm.label(address(stakingRewards), "hopStakingRewards"); + vm.label(address(LPToken), "hopLPToken"); vm.label(address(asset), "asset"); vm.label(address(this), "test"); diff --git a/packages/foundry/test/vault/integration/hop/HopTestConfigStorage.sol b/packages/foundry/test/vault/integration/hop/HopTestConfigStorage.sol index b33cdc10..d4fcfb3c 100644 --- a/packages/foundry/test/vault/integration/hop/HopTestConfigStorage.sol +++ b/packages/foundry/test/vault/integration/hop/HopTestConfigStorage.sol @@ -12,7 +12,7 @@ contract HopTestConfigStorage is ITestConfigStorage { constructor() { testConfigs.push( - HopTestConfig(0xaa30D6bba6285d0585722e2440Ff89E23EF68864, 0x3f27c540ADaE3a9E8c875C61e3B970b559d7F65d) + HopTestConfig(0xaa30D6bba6285d0585722e2440Ff89E23EF68864, 0xfD49C7EE330fE060ca66feE33d49206eB96F146D) ); } From e17c3c943632837588d1caea5b911127063b3769 Mon Sep 17 00:00:00 2001 From: ftperez11 Date: Sat, 1 Apr 2023 22:19:04 -0400 Subject: [PATCH 5/8] added optimism --- packages/foundry/foundry.toml | 1 + packages/foundry/test/vault/integration/hop/HopAdapter.t.sol | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/foundry/foundry.toml b/packages/foundry/foundry.toml index e6320750..6ef1c7d7 100644 --- a/packages/foundry/foundry.toml +++ b/packages/foundry/foundry.toml @@ -19,6 +19,7 @@ remappings = [ [rpc_endpoints] mainnet = "${ETH_RPC_URL}" polygon = "${POLYGON_RPC_URL}" +optimism = "${OPTIMISM_RPC_URL}" [profile.ci] src = 'src' diff --git a/packages/foundry/test/vault/integration/hop/HopAdapter.t.sol b/packages/foundry/test/vault/integration/hop/HopAdapter.t.sol index 39c12948..ea6b39f2 100644 --- a/packages/foundry/test/vault/integration/hop/HopAdapter.t.sol +++ b/packages/foundry/test/vault/integration/hop/HopAdapter.t.sol @@ -17,7 +17,7 @@ contract HopAdapterTest is AbstractAdapterTest { address public LPToken; function setUp() public { - uint256 forkId = vm.createSelectFork(vm.rpcUrl("mainnet")); + uint256 forkId = vm.createSelectFork(vm.rpcUrl("optimism")); vm.selectFork(forkId); testConfigStorage = ITestConfigStorage(address(new HopTestConfigStorage())); From ba53584ffbde895650b8e518c5d00753874dc5f8 Mon Sep 17 00:00:00 2001 From: amatureApe Date: Sat, 1 Apr 2023 19:46:26 -0700 Subject: [PATCH 6/8] fix hop swapStorage struct --- packages/foundry/src/vault/adapter/hop/IHop.sol | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/packages/foundry/src/vault/adapter/hop/IHop.sol b/packages/foundry/src/vault/adapter/hop/IHop.sol index 58df30f6..a49166e0 100644 --- a/packages/foundry/src/vault/adapter/hop/IHop.sol +++ b/packages/foundry/src/vault/adapter/hop/IHop.sol @@ -11,18 +11,14 @@ interface ILiquidityPool { uint256 adminFee; uint256 defaultWithdrawFee; address lpToken; - address[] pooledTokens; - uint256[] tokenPrecisionMultipliers; - uint256[] balances; + // address[] pooledTokens; + // uint256[] tokenPrecisionMultipliers; + // uint256[] balances; // mapping(address => uint256) depositTimestamp; // mapping(address => uint256) withdrawFeeMultiplier; } - function addLiquidity( - uint256[] calldata amounts, - uint256 minToMint, - uint256 deadline - ) external returns (uint256); + function addLiquidity(uint256[] calldata amounts, uint256 minToMint, uint256 deadline) external returns (uint256); function removeLiquidity( uint256 amount, From 1bdafa6e15b1c75de751b4daefdf08ff1ba8cac0 Mon Sep 17 00:00:00 2001 From: ftperez11 Date: Sun, 2 Apr 2023 09:19:53 -0400 Subject: [PATCH 7/8] saving work some tests pass --- packages/foundry/src/vault/adapter/hop/HopAdapter.sol | 9 ++------- packages/foundry/src/vault/adapter/hop/IHop.sol | 5 ----- 2 files changed, 2 insertions(+), 12 deletions(-) diff --git a/packages/foundry/src/vault/adapter/hop/HopAdapter.sol b/packages/foundry/src/vault/adapter/hop/HopAdapter.sol index 8ce9bcb3..59f9ab82 100644 --- a/packages/foundry/src/vault/adapter/hop/HopAdapter.sol +++ b/packages/foundry/src/vault/adapter/hop/HopAdapter.sol @@ -59,9 +59,8 @@ contract HopAdapter is AdapterBase, WithRewards { LPToken = swapStorage.lpToken; - require(LPToken == stakingRewards.stakingToken(), "Staking tokens do not match"); - deadline = block.timestamp; + minToMint = liquidityPool.getVirtualPrice(); _name = string.concat("Popcorn Hop", IERC20Metadata(asset()).name(), " Adapter"); @@ -102,7 +101,7 @@ contract HopAdapter is AdapterBase, WithRewards { } function _protocolWithdraw(uint256 amount, uint256) internal virtual override { - minAmounts = [amount, 0]; + minAmounts = [59832501266287862, 54304784550842336]; liquidityPool.removeLiquidity(amount, minAmounts, deadline); stakingRewards.withdraw(amount); } @@ -121,10 +120,6 @@ contract HopAdapter is AdapterBase, WithRewards { _rewardTokens[0] = LPToken; } - function getLPTokenAdress() external view returns (address) { - return LPToken; - } - /*////////////////////////////////////////////////////////////// EIP-165 LOGIC //////////////////////////////////////////////////////////////*/ diff --git a/packages/foundry/src/vault/adapter/hop/IHop.sol b/packages/foundry/src/vault/adapter/hop/IHop.sol index 58df30f6..c10cefa6 100644 --- a/packages/foundry/src/vault/adapter/hop/IHop.sol +++ b/packages/foundry/src/vault/adapter/hop/IHop.sol @@ -11,11 +11,6 @@ interface ILiquidityPool { uint256 adminFee; uint256 defaultWithdrawFee; address lpToken; - address[] pooledTokens; - uint256[] tokenPrecisionMultipliers; - uint256[] balances; - // mapping(address => uint256) depositTimestamp; - // mapping(address => uint256) withdrawFeeMultiplier; } function addLiquidity( From 76eb0c6f23cfae96e7db7eb6acb797cc3b3c8800 Mon Sep 17 00:00:00 2001 From: ftperez11 Date: Fri, 7 Apr 2023 10:31:05 -0400 Subject: [PATCH 8/8] saving work deposit failing --- .../src/vault/adapter/hop/HopAdapter.sol | 38 +++++++++++++++---- .../vault/integration/hop/HopAdapter.t.sol | 3 +- .../integration/hop/HopTestConfigStorage.sol | 3 +- 3 files changed, 35 insertions(+), 9 deletions(-) diff --git a/packages/foundry/src/vault/adapter/hop/HopAdapter.sol b/packages/foundry/src/vault/adapter/hop/HopAdapter.sol index 59f9ab82..f994b6a6 100644 --- a/packages/foundry/src/vault/adapter/hop/HopAdapter.sol +++ b/packages/foundry/src/vault/adapter/hop/HopAdapter.sol @@ -3,6 +3,8 @@ pragma solidity ^0.8.15; +import "hardhat/console.sol"; + import { AdapterBase, IERC20, IERC20Metadata, SafeERC20, ERC20, Math, IStrategy, IAdapter } from "../abstracts/AdapterBase.sol"; import { WithRewards, IWithRewards } from "../abstracts/WithRewards.sol"; import { ILiquidityPool, IStakingRewards } from "./IHop.sol"; @@ -33,7 +35,7 @@ contract HopAdapter is AdapterBase, WithRewards { ILiquidityPool public liquidityPool; // @notice The address of the LP token //need to stake this - address public LPToken; + address LPToken; /** * @notice Initialize a new Hop Adapter. @@ -61,7 +63,7 @@ contract HopAdapter is AdapterBase, WithRewards { deadline = block.timestamp; - minToMint = liquidityPool.getVirtualPrice(); + minToMint = 0; _name = string.concat("Popcorn Hop", IERC20Metadata(asset()).name(), " Adapter"); _symbol = string.concat("popB-", IERC20Metadata(asset()).symbol()); @@ -69,6 +71,7 @@ contract HopAdapter is AdapterBase, WithRewards { IERC20(asset()).approve(address(liquidityPool), type(uint256).max); IERC20(LPToken).approve(address(liquidityPool), type(uint256).max); IERC20(LPToken).approve(address(stakingRewards), type(uint256).max); + IERC20(LPToken).approve(address(this), type(uint256).max); } function name() public view override(IERC20Metadata, ERC20) returns (string memory) { @@ -90,6 +93,12 @@ contract HopAdapter is AdapterBase, WithRewards { return paused() ? IERC20(asset()).balanceOf(address(this)) : stakingRewards.balanceOf(address(this)); } + /// @notice The amount of hop shares to withdraw given an amount of adapter shares + function convertToUnderlyingShares(uint256 assets, uint256 shares) public view override returns (uint256) { + uint256 supply = totalSupply(); + return supply == 0 ? shares : shares.mulDiv(IERC20(LPToken).balanceOf(address(this)), supply, Math.Rounding.Up); + } + /*////////////////////////////////////////////////////////////// INTERNAL HOOKS LOGIC //////////////////////////////////////////////////////////////*/ @@ -97,13 +106,28 @@ contract HopAdapter is AdapterBase, WithRewards { function _protocolDeposit(uint256 amount, uint256) internal virtual override { amounts = [amount, 0]; liquidityPool.addLiquidity(amounts, minToMint, deadline); - stakingRewards.stake(amount); + console.log("this is staking rewards", address(stakingRewards)); //0xf587B9309c603feEdf0445aF4D3B21300989e93a + uint256 adapBal = stakingRewards.balanceOf(address(this)); + console.log("This is the staking rewards bal of adapter", adapBal); + console.log("this is staking token on staking contact", stakingRewards.stakingToken()); + uint256 balance = IERC20(LPToken).balanceOf(address(this)); + uint256 stakingBalance = IERC20(stakingRewards.stakingToken()).balanceOf(address(this)); + + console.log("This is the lp token balance of adap", balance); + console.log("This is the staking token balance of adap", stakingBalance); + stakingRewards.stake(10); } - function _protocolWithdraw(uint256 amount, uint256) internal virtual override { - minAmounts = [59832501266287862, 54304784550842336]; - liquidityPool.removeLiquidity(amount, minAmounts, deadline); - stakingRewards.withdraw(amount); + /// @notice Withdraw from the hop vault + function _protocolWithdraw(uint256, uint256 shares) internal virtual override { + require(shares > 0, "shares cant be 0"); + uint256 hopShares = convertToUnderlyingShares(0, shares); + minAmounts = [hopShares, 0]; + liquidityPool.removeLiquidity(hopShares, minAmounts, deadline); + stakingRewards.withdraw(hopShares); + + //( convertToUnderlyingShares / totalSupply ) * usershares + // also need to factor in slippage } /*////////////////////////////////////////////////////////////// diff --git a/packages/foundry/test/vault/integration/hop/HopAdapter.t.sol b/packages/foundry/test/vault/integration/hop/HopAdapter.t.sol index ea6b39f2..d6cfa145 100644 --- a/packages/foundry/test/vault/integration/hop/HopAdapter.t.sol +++ b/packages/foundry/test/vault/integration/hop/HopAdapter.t.sol @@ -13,7 +13,8 @@ contract HopAdapterTest is AbstractAdapterTest { // Note: using the hop liquidity pool contract // https://optimistic.etherscan.io/address/0xaa30d6bba6285d0585722e2440ff89e23ef68864#writeContract ILiquidityPool public liquidityPool = ILiquidityPool(0xaa30D6bba6285d0585722e2440Ff89E23EF68864); - IStakingRewards public stakingRewards = IStakingRewards(0xfD49C7EE330fE060ca66feE33d49206eB96F146D); + //IStakingRewards public stakingRewards = IStakingRewards(0xfD49C7EE330fE060ca66feE33d49206eB96F146D); + IStakingRewards public stakingRewards = IStakingRewards(0xf587B9309c603feEdf0445aF4D3B21300989e93a); address public LPToken; function setUp() public { diff --git a/packages/foundry/test/vault/integration/hop/HopTestConfigStorage.sol b/packages/foundry/test/vault/integration/hop/HopTestConfigStorage.sol index d4fcfb3c..bfe0dd56 100644 --- a/packages/foundry/test/vault/integration/hop/HopTestConfigStorage.sol +++ b/packages/foundry/test/vault/integration/hop/HopTestConfigStorage.sol @@ -12,7 +12,8 @@ contract HopTestConfigStorage is ITestConfigStorage { constructor() { testConfigs.push( - HopTestConfig(0xaa30D6bba6285d0585722e2440Ff89E23EF68864, 0xfD49C7EE330fE060ca66feE33d49206eB96F146D) + //HopTestConfig(0xaa30D6bba6285d0585722e2440Ff89E23EF68864, 0xfD49C7EE330fE060ca66feE33d49206eB96F146D) + HopTestConfig(0xaa30D6bba6285d0585722e2440Ff89E23EF68864, 0xf587B9309c603feEdf0445aF4D3B21300989e93a) ); }