diff --git a/packages/foundry/src/interfaces/external/IRewarder.sol b/packages/foundry/src/interfaces/external/IRewarder.sol new file mode 100644 index 00000000..903f26ed --- /dev/null +++ b/packages/foundry/src/interfaces/external/IRewarder.sol @@ -0,0 +1,10 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.15; + +interface IRewarder { + function pendingTokens( + uint256 _pid, + address _user, + uint256 + ) external returns (address[] memory rewardTokens, uint256[] memory rewardAmounts); +} diff --git a/packages/foundry/src/interfaces/external/curve/ICurveMetapool.sol b/packages/foundry/src/interfaces/external/curve/ICurveMetapool.sol new file mode 100644 index 00000000..c16279c5 --- /dev/null +++ b/packages/foundry/src/interfaces/external/curve/ICurveMetapool.sol @@ -0,0 +1,71 @@ +// SPDX-License-Identifier: GPL-3.0 +// Docgen-SOLC: 0.8.15 + +pragma solidity ^0.8.15; + +interface ICurveMetapool { + function get_virtual_price() external view returns (uint256); + + function add_liquidity(uint256[2] calldata amounts, uint256 min_mint_amounts) external returns (uint256); + + function add_liquidity( + uint256[2] calldata _amounts, + uint256 _min_mint_amounts, + address _receiver + ) external returns (uint256); + + function add_liquidity(uint256[3] calldata amounts, uint256 min_mint_amounts) external returns (uint256); + + function add_liquidity( + uint256[3] calldata _amounts, + uint256 _min_mint_amounts, + address _receiver + ) external returns (uint256); + + function add_liquidity(uint256[4] calldata amounts, uint256 min_mint_amounts) external; + + function add_liquidity( + uint256[4] calldata _amounts, + uint256 _min_mint_amounts, + address _receiver + ) external returns (uint256); + + function remove_liquidity_one_coin( + uint256 amount, + int128 i, + uint256 min_underlying_amount + ) external; + + function remove_liquidity_one_coin( + uint256 amount, + int128 i, + uint256 min_underlying_amount, + bool donate_dust + ) external; + + function calc_withdraw_one_coin(uint256 _token_amount, int128 i) external view returns (uint256); + + //Some pools use exchange (sUsd,3crv)... + function exchange( + uint256 i, + uint256 j, + uint256 dx, + uint256 min_dy + ) external returns (uint256); + + //...And some others use exchange_underlying (mim,3crv) + function exchange_underlying( + int128 i, + int128 j, + uint256 dx, + uint256 min_dy + ) external returns (uint256); + + function lp_price() external view returns (uint256); + + function price_oracle() external view returns (uint256); + + function balances(uint256 i) external view returns (uint256); + + function remove_liquidity(uint256 amount, uint256[4] memory min_amounts) external; +} diff --git a/packages/foundry/src/interfaces/external/stargate/IStargateRewarder.sol b/packages/foundry/src/interfaces/external/stargate/IStargateRewarder.sol new file mode 100644 index 00000000..39636524 --- /dev/null +++ b/packages/foundry/src/interfaces/external/stargate/IStargateRewarder.sol @@ -0,0 +1,8 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.15; + +interface IStargateRewarder { + function deposit(uint256 _pid, uint256 _amount) external; + + function pendingStargate(uint256 _pid, address _user) external view returns (uint256); +} diff --git a/packages/foundry/src/interfaces/external/stargate/IStargateRouter.sol b/packages/foundry/src/interfaces/external/stargate/IStargateRouter.sol new file mode 100644 index 00000000..79e99172 --- /dev/null +++ b/packages/foundry/src/interfaces/external/stargate/IStargateRouter.sol @@ -0,0 +1,10 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.15; + +interface IStargateRouter { + function addLiquidity( + uint256 _pid, + uint256 _amount, + address _to + ) external; +} diff --git a/packages/foundry/src/interfaces/external/sushi/IMiniChefV2.sol b/packages/foundry/src/interfaces/external/sushi/IMiniChefV2.sol new file mode 100644 index 00000000..cad26f3d --- /dev/null +++ b/packages/foundry/src/interfaces/external/sushi/IMiniChefV2.sol @@ -0,0 +1,47 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.15; + +interface IMiniChefV2 { + struct UserInfo { + uint256 amount; + uint256 rewardDebt; + } + + struct PoolInfo { + uint128 accSushiPerShare; + uint64 lastRewardTime; + uint64 allocPoint; + } + + function poolLength() external view returns (uint256); + + function updatePool(uint256 pid) external returns (IMiniChefV2.PoolInfo memory); + + function userInfo(uint256 _pid, address _user) external view returns (uint256, uint256); + + function deposit( + uint256 pid, + uint256 amount, + address to + ) external; + + function withdraw( + uint256 pid, + uint256 amount, + address to + ) external; + + function harvest(uint256 pid, address to) external; + + function pendingSushi(uint256 _pid, address _user) external returns (uint256); + + function rewarder(uint256 _pid) external returns (address); + + function withdrawAndHarvest( + uint256 pid, + uint256 amount, + address to + ) external; + + function emergencyWithdraw(uint256 pid, address to) external; +} diff --git a/packages/foundry/src/interfaces/external/uni/IUniswapV2Pair.sol b/packages/foundry/src/interfaces/external/uni/IUniswapV2Pair.sol new file mode 100644 index 00000000..492ce59b --- /dev/null +++ b/packages/foundry/src/interfaces/external/uni/IUniswapV2Pair.sol @@ -0,0 +1,98 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.15; + +interface IUniswapV2Pair { + event Approval(address indexed owner, address indexed spender, uint256 value); + event Transfer(address indexed from, address indexed to, uint256 value); + + function name() external pure returns (string memory); + + function symbol() external pure returns (string memory); + + function decimals() external pure returns (uint8); + + function totalSupply() external view returns (uint256); + + function balanceOf(address owner) external view returns (uint256); + + function allowance(address owner, address spender) external view returns (uint256); + + function approve(address spender, uint256 value) external returns (bool); + + function transfer(address to, uint256 value) external returns (bool); + + function transferFrom( + address from, + address to, + uint256 value + ) external returns (bool); + + function DOMAIN_SEPARATOR() external view returns (bytes32); + + function PERMIT_TYPEHASH() external pure returns (bytes32); + + function nonces(address owner) external view returns (uint256); + + function permit( + address owner, + address spender, + uint256 value, + uint256 deadline, + uint8 v, + bytes32 r, + bytes32 s + ) external; + + event Mint(address indexed sender, uint256 amount0, uint256 amount1); + event Burn(address indexed sender, uint256 amount0, uint256 amount1, address indexed to); + event Swap( + address indexed sender, + uint256 amount0In, + uint256 amount1In, + uint256 amount0Out, + uint256 amount1Out, + address indexed to + ); + event Sync(uint112 reserve0, uint112 reserve1); + + function MINIMUM_LIQUIDITY() external pure returns (uint256); + + function factory() external view returns (address); + + function token0() external view returns (address); + + function token1() external view returns (address); + + function getReserves() + external + view + returns ( + uint112 reserve0, + uint112 reserve1, + uint32 blockTimestampLast + ); + + function price0CumulativeLast() external view returns (uint256); + + function price1CumulativeLast() external view returns (uint256); + + function kLast() external view returns (uint256); + + function mint(address to) external returns (uint256 liquidity); + + function burn(address to) external returns (uint256 amount0, uint256 amount1); + + function swap( + uint256 amount0Out, + uint256 amount1Out, + address to, + bytes calldata data + ) external; + + function skim(address to) external; + + function sync() external; + + function initialize(address, address) external; +} diff --git a/packages/foundry/src/interfaces/modules/IUniswapV2Module.sol b/packages/foundry/src/interfaces/modules/IUniswapV2Module.sol new file mode 100644 index 00000000..d596f60d --- /dev/null +++ b/packages/foundry/src/interfaces/modules/IUniswapV2Module.sol @@ -0,0 +1,16 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.15; + +interface IUniswapV2Module { + function swap( + address _router, + address[] memory _route, + uint256 _amount + ) external; + + function addLiquidity( + address _router, + address _lpToken0, + address _lpToken1 + ) external; +} diff --git a/packages/foundry/src/vault/adapter/abstracts/WithRewards.sol b/packages/foundry/src/vault/adapter/abstracts/WithRewards.sol index e3b8b30a..b2099420 100644 --- a/packages/foundry/src/vault/adapter/abstracts/WithRewards.sol +++ b/packages/foundry/src/vault/adapter/abstracts/WithRewards.sol @@ -12,7 +12,7 @@ import { IAdapter } from "../../../interfaces/vault/IAdapter.sol"; contract WithRewards is EIP165, OnlyStrategy { function rewardTokens() external view virtual returns (address[] memory) {} - function claim() public virtual onlyStrategy {} + function claim() public virtual {} /*////////////////////////////////////////////////////////////// EIP-165 LOGIC diff --git a/packages/foundry/src/vault/adapter/convex/ConvexAdapter.sol b/packages/foundry/src/vault/adapter/convex/ConvexAdapter.sol index ed0ea8bb..74a43f96 100644 --- a/packages/foundry/src/vault/adapter/convex/ConvexAdapter.sol +++ b/packages/foundry/src/vault/adapter/convex/ConvexAdapter.sol @@ -125,7 +125,7 @@ contract ConvexAdapter is AdapterBase, WithRewards { error MiningNotActive(); /// @notice Claim liquidity mining rewards given that it's active - function claim() public override onlyStrategy { + function claim() public override { convexRewards.getReward(address(this), true); } diff --git a/packages/foundry/src/vault/strategy/Pool2SingleAssetCompounder.sol b/packages/foundry/src/vault/strategy/Pool2SingleAssetCompounder.sol index cd529a72..70f72020 100644 --- a/packages/foundry/src/vault/strategy/Pool2SingleAssetCompounder.sol +++ b/packages/foundry/src/vault/strategy/Pool2SingleAssetCompounder.sol @@ -65,3 +65,22 @@ contract Pool2SingleAssetCompounder is StrategyBase { IAdapter(address(this)).strategyDeposit(ERC20(asset).balanceOf(address(this)), 0); } } + + +// asset -> asset +// asset -> curve pool2 +// asset -> curve pool3 +// asset -> curve custom pool -- how many? +// asset -> uniV2 +// asset -> uniV3 +// asset -> velo? +// asset -> balancer -- how many? +// split asset to mitigate slippage + + +// Use 1Inch Router +// 1. Get Spot price +// 2. Calc Min +// 3. Trade using router +// 4. Potentially LP +// Trade to usdc first? and than trade all together to asset? Or just each reward on their own \ No newline at end of file diff --git a/packages/foundry/src/vault/strategy/autocompouding/CompounderStrategyBase.sol b/packages/foundry/src/vault/strategy/autocompouding/CompounderStrategyBase.sol new file mode 100644 index 00000000..04b7894a --- /dev/null +++ b/packages/foundry/src/vault/strategy/autocompouding/CompounderStrategyBase.sol @@ -0,0 +1,197 @@ +// SPDX-License-Identifier: GPL-3.0 +// Docgen-SOLC: 0.8.15 + +pragma solidity ^0.8.15; + +import { IWithRewards } from "../../../interfaces/vault/IWithRewards.sol"; +import { IEIP165 } from "../../../interfaces/IEIP165.sol"; +import { MathUpgradeable as Math } from "openzeppelin-contracts-upgradeable/utils/math/MathUpgradeable.sol"; +import { ERC4626Upgradeable as ERC4626, ERC20Upgradeable as ERC20 } from "openzeppelin-contracts-upgradeable/token/ERC20/extensions/ERC4626Upgradeable.sol"; +import { IUniswapRouterV2 } from "../../../interfaces/external/uni/IUniswapRouterV2.sol"; +import { IAdapter } from "../../../interfaces/vault/IAdapter.sol"; +import { IWithRewards } from "../../../interfaces/vault/IWithRewards.sol"; + +contract CompounderStrategyBase { + // Native token on chain + address public native; + + // Protocol contracts info (masterchefs, poolIds, etc.) + address[] public protocolAddresses; + uint256[] public protocolUints; + + // Vault and Strategist + address public vault; + address public strategist; + + // Swapping + address[] public tradeModules; + address[] public routers; + /** + * @dev rewardToken index must match respective index in rewardRoutes and pendingRewards. + * @dev Routes follow this pattern: [rewardToken, ...hops, native] + */ + address[][] public rewardsToNativeRoutes; + + // Data management + bool public isVaultFunctional; + uint256 public lastHarvest; + address[] public rewardTokens; + uint256[] public pendingRewards; + + // Events + event Harvest(); + + // Errors + error InvalidRoute(); + error FunctionNotImplemented(bytes4 sig); + + function verifyAdapterSelectorCompatibility(bytes4[8] memory sigs) public { + uint8 len = uint8(sigs.length); + for (uint8 i; i < len; i++) { + if (sigs[i].length == 0) return; + if (!IEIP165(address(this)).supportsInterface(sigs[i])) revert FunctionNotImplemented(sigs[i]); + } + } + + function verifyAdapterCompatibility(bytes memory data) public virtual {} + + /*////////////////////////////////////////////////////////////// + SETUP + //////////////////////////////////////////////////////////////*/ + + // Give allowances necessary for deposit, withdraw, lpToken swaps, and addLiquidity. + function _giveAllowances() internal virtual {} + + // Give allowances for rewardToken swaps. + function giveRewardAllowances() public { + address swapRouter = routers[0]; + + uint256 len = rewardTokens.length; + for (uint256 i; i < len; ++i) { + ERC20(rewardTokens[i]).approve(swapRouter, type(uint256).max); + } + } + + // Give initial allowances for setup. + function _giveInitialAllowances() internal { + _giveAllowances(); + giveRewardAllowances(); + } + + /*////////////////////////////////////////////////////////////// + HARVEST LOGIC + //////////////////////////////////////////////////////////////*/ + + // Harvest rewards. + function harvest() public virtual { + if (_rewardsCheck() == true && _rewardRoutesCheck() == true) { + _compound(); + } + + lastHarvest = block.timestamp; + + if (isVaultFunctional == false) isVaultFunctional = true; + + emit Harvest(); + } + + // Logic to claim rewards, swap rewards to native, charge fees, swap native to deposit token, add liquidity (if necessary), and re-deposit. + function _compound() internal virtual {} + + // Swap all rewards to native token. + function _swapRewardsToNative() internal virtual { + uint256 len = rewardsToNativeRoutes.length; + for (uint256 i; i < len; ++i) { + address reward = rewardsToNativeRoutes[i][0]; + address[] memory rewardRoute = rewardsToNativeRoutes[i]; + uint256 rewardAmount = ERC20(reward).balanceOf(address(this)); + if (rewardAmount > 0) { + _swapRewardsToNative(rewardRoute, rewardAmount); + } + } + } + + function _swapRewardsToNative(address[] memory _rewardRoute, uint256 rewardAmount) internal virtual {} + + // Claim rewards from underlying protocol. + function _claimRewards() internal virtual { + IWithRewards(address(this)).claim(); + } + + // Deposit assetToken or lpPair into underlying protocol. + function _deposit() internal virtual { + uint256 assets = 1e18; + uint256 shares = 1e18; + + IAdapter(address(this)).strategyDeposit(assets, shares); + } + + /*////////////////////////////////////////////////////////////// + REWARDS AND ROUTES + //////////////////////////////////////////////////////////////*/ + + // Return available rewards for all rewardTokens. + function rewardsAvailable() public virtual returns (uint256[] memory) {} + + // Set rewards tokens according to rewardToNativeRoutes. + function _setRewardTokens(address[][] memory _rewardsToNativeRoutes) internal virtual { + uint256 len = _rewardsToNativeRoutes.length; + for (uint256 i; i < len; ++i) { + if (_rewardsToNativeRoutes[i][_rewardsToNativeRoutes.length - 1] != native) revert InvalidRoute(); + rewardTokens[i] = _rewardsToNativeRoutes[i][0]; + } + } + + // Check to see that at least 1 reward is available. + function _rewardsCheck() internal virtual returns (bool) { + pendingRewards = rewardsAvailable(); + + uint256 len = pendingRewards.length; + for (uint256 i; i < len; ++i) { + if (pendingRewards[i] > 0) { + return true; + } + } + + return false; + } + + // Check to make sure all rewardTokens have correct respective routes. + function _rewardRoutesCheck() internal view virtual returns (bool) { + uint256 len = rewardTokens.length; + + for (uint256 i; i < len; ++i) { + if ( + rewardTokens[i] != rewardsToNativeRoutes[i][0] || + native != rewardsToNativeRoutes[i][rewardsToNativeRoutes.length - 1] + ) return false; + } + + return true; + } + + // Set rewardRoute at index. + function setRewardsToNativeRoute(uint256 rewardIndex, address[] calldata route) public virtual { + rewardsToNativeRoutes[rewardIndex] = route; + + if (isVaultFunctional == true) isVaultFunctional = false; + } + + // Get rewardRoute at index. + function getRewardsToNativeRoute(uint256 rewardIndex) public view virtual returns (address[] memory) { + return rewardsToNativeRoutes[rewardIndex]; + } + + /*////////////////////////////////////////////////////////////// + ACCOUNTING LOGIC + //////////////////////////////////////////////////////////////*/ + + // Calculate the total underlaying 'want' held by the strat. + function balanceOf() public view virtual returns (uint256) {} + + // Calculates how much 'want' this contract holds. + function balanceOfWant() public view virtual returns (uint256) {} + + // Calculates how much 'want' the strategy has working in the farm. + function balanceOfPool() public view virtual returns (uint256) {} +} diff --git a/packages/foundry/src/vault/strategy/autocompouding/liquidityPair/LiquidityPairBase.sol b/packages/foundry/src/vault/strategy/autocompouding/liquidityPair/LiquidityPairBase.sol new file mode 100644 index 00000000..16bed389 --- /dev/null +++ b/packages/foundry/src/vault/strategy/autocompouding/liquidityPair/LiquidityPairBase.sol @@ -0,0 +1,91 @@ +// SPDX-License-Identifier: GPL-3.0 +// Docgen-SOLC: 0.8.15 + +pragma solidity ^0.8.15; + +import { IWithRewards } from "../../../../interfaces/vault/IWithRewards.sol"; +import { IEIP165 } from "../../../../interfaces/IEIP165.sol"; +import { MathUpgradeable as Math } from "openzeppelin-contracts-upgradeable/utils/math/MathUpgradeable.sol"; +import { ERC4626Upgradeable as ERC4626, ERC20Upgradeable as ERC20 } from "openzeppelin-contracts-upgradeable/token/ERC20/extensions/ERC4626Upgradeable.sol"; + +import { CompounderStrategyBase } from "../CompounderStrategyBase.sol"; + +import { IUniswapV2Pair } from "../../../../interfaces/external/uni/IUniswapV2Pair.sol"; +import { IUniswapRouterV2 } from "../../../../interfaces/external/uni/IUniswapRouterV2.sol"; +import { IAdapter } from "../../../../interfaces/vault/IAdapter.sol"; +import { IWithRewards } from "../../../../interfaces/vault/IWithRewards.sol"; + +contract LiquidityPairBase is CompounderStrategyBase { + using Math for uint256; + + // Tokens used + address public lpPair; + address public lpToken0; + address public lpToken1; + + // Routes + address[] public nativeToLp0Route; + address[] public nativeToLp1Route; + + /*////////////////////////////////////////////////////////////// + SETUP + //////////////////////////////////////////////////////////////*/ + + // Setup for routes and allowances in constructor. + function _setUp( + address[][] memory _rewardsToNativeRoutes, + address[] memory _nativeToLp0Route, + address[] memory _nativeToLp1Route + ) internal virtual { + lpToken0 = IUniswapV2Pair(lpPair).token0(); + if (_nativeToLp0Route[0] != native) revert InvalidRoute(); + if (_nativeToLp0Route[_nativeToLp0Route.length - 1] != lpToken0) revert InvalidRoute(); + nativeToLp0Route = _nativeToLp0Route; + + lpToken1 = IUniswapV2Pair(lpPair).token0(); + if (_nativeToLp1Route[0] != native) revert InvalidRoute(); + if (_nativeToLp1Route[_nativeToLp1Route.length - 1] != lpToken1) revert InvalidRoute(); + nativeToLp1Route = _nativeToLp1Route; + + _setRewardTokens(_rewardsToNativeRoutes); + + _giveInitialAllowances(); + } + + /*////////////////////////////////////////////////////////////// + ROUTES + //////////////////////////////////////////////////////////////*/ + + // Set nativeToLp0Route. + function setNativeToLp0TokenRoute(address[] calldata route) public virtual { + nativeToLp0Route = route; + + if (isVaultFunctional == true) isVaultFunctional = false; + } + + // Set nativeToLp1Route at index. + function setNativeToLp1TokenRoute(address[] calldata route) public virtual { + nativeToLp1Route = route; + + if (isVaultFunctional == true) isVaultFunctional = false; + } + + /*////////////////////////////////////////////////////////////// + COMPOUND LOGIC + //////////////////////////////////////////////////////////////*/ + + // Logic to claim rewards, swap rewards to native, charge fees, swap native to lpTokens, add liquidity, and re-deposit. + function _compound() internal virtual override { + _claimRewards(); + _swapRewardsToNative(); + _swapNativeToLpTokens(); + _addLiquidity(); + _deposit(); + } + + // Swap native tokens for lpTokens. + function _swapNativeToLpTokens() internal virtual {} + + // Use lpTokens to create lpPair. + function _addLiquidity() internal virtual {} +} diff --git a/packages/foundry/src/vault/strategy/autocompouding/liquidityPair/protocols/sushi/MiniChefV2Compounder.sol b/packages/foundry/src/vault/strategy/autocompouding/liquidityPair/protocols/sushi/MiniChefV2Compounder.sol new file mode 100644 index 00000000..63b6217e --- /dev/null +++ b/packages/foundry/src/vault/strategy/autocompouding/liquidityPair/protocols/sushi/MiniChefV2Compounder.sol @@ -0,0 +1,118 @@ +// SPDX-License-Identifier: GPL-3.0 +// Docgen-SOLC: 0.8.15 + +pragma solidity ^0.8.15; +import { LiquidityPairBase, ERC20 } from "../../LiquidityPairBase.sol"; +import { IUniswapRouterV2 } from "../../../../../../interfaces/external/uni/IUniswapRouterV2.sol"; +import { IUniswapV2Pair } from "../../../../../../interfaces/external/uni/IUniswapV2Pair.sol"; +import { IMiniChefV2 } from "../../../../../../interfaces/external/sushi/IMiniChefV2.sol"; +import { IRewarder } from "../../../../../../interfaces/external/IRewarder.sol"; +import { IUniswapV2Module } from "../../../../../../interfaces/modules/IUniswapV2Module.sol"; + +contract LiquidityPairCompounder is LiquidityPairBase { + constructor( + address _native, + address _lpPair, + address[] memory _tradeModules, + address[] memory _routers, + address _vault, + address _strategist, + address[] memory _protocolAddresses, + uint256[] memory _protocolUints, + address[][] memory _rewardsToNativeRoutes, + address[] memory _nativeToLp0Route, + address[] memory _nativeToLp1Route + ) public { + native = _native; + lpPair = _lpPair; + + tradeModules = _tradeModules; + routers = _routers; + + vault = _vault; + strategist = _strategist; + + protocolAddresses = _protocolAddresses; + protocolUints = _protocolUints; + + rewardsToNativeRoutes = _rewardsToNativeRoutes; + + _setUp(rewardsToNativeRoutes, nativeToLp0Route, nativeToLp1Route); + } + + /*////////////////////////////////////////////////////////////// + SETUP + //////////////////////////////////////////////////////////////*/ + + // Give allowances for protocol deposit and rewardToken swaps. + function _giveAllowances() internal override { + address uniV2Router = routers[0]; + address chef = protocolAddresses[0]; + + ERC20(lpPair).approve(chef, type(uint256).max); + ERC20(native).approve(uniV2Router, type(uint256).max); + ERC20(lpToken0).approve(uniV2Router, type(uint256).max); + ERC20(lpToken1).approve(uniV2Router, type(uint256).max); + } + + /*////////////////////////////////////////////////////////////// + HARVEST LOGIC + //////////////////////////////////////////////////////////////*/ + + // Swap all rewards to native token. + function _swapRewardsToNative(address[] memory _rewardRoute, uint256 _rewardAmount) internal override { + IUniswapV2Module uniV2Module = IUniswapV2Module(tradeModules[0]); + address uniV2Router = routers[0]; + + uniV2Module.swap(uniV2Router, _rewardRoute, _rewardAmount); + } + + // Swap native tokens for lpTokens. + function _swapNativeToLpTokens() internal override { + IUniswapV2Module uniV2Module = IUniswapV2Module(tradeModules[0]); + address uniV2Router = routers[0]; + + uniV2Module.swap(uniV2Router, nativeToLp0Route, ERC20(native).balanceOf(address(this)) / 2); + uniV2Module.swap(uniV2Router, nativeToLp1Route, ERC20(native).balanceOf(address(this))); + } + + // Use lpTokens to create lpPair. + function _addLiquidity() internal override { + IUniswapV2Module uniV2Module = IUniswapV2Module(tradeModules[0]); + address uniV2Router = routers[0]; + + uniV2Module.addLiquidity(uniV2Router, lpToken0, lpToken1); + } + + // Return available rewards for all rewardTokens. + function rewardsAvailable() public override returns (uint256[] memory) { + address chef = protocolAddresses[0]; + uint256 pid = protocolUints[0]; + + pendingRewards.push(IMiniChefV2(chef).pendingSushi(pid, address(this))); + + address rewarder = IMiniChefV2(chef).rewarder(pid); + + (, uint256[] memory rewardAmounts) = IRewarder(rewarder).pendingTokens(pid, address(this), 0); + + uint256 len = rewardAmounts.length; + for (uint256 i; i < len; ++i) { + pendingRewards.push(rewardAmounts[i]); + } + + return pendingRewards; + } + + /*////////////////////////////////////////////////////////////// + ACCOUNTING LOGIC + //////////////////////////////////////////////////////////////*/ + + // Calculate the total underlaying 'want' held by the strat. + function balanceOf() public view override returns (uint256) {} + + // Calculates how much 'want' this contract holds. + function balanceOfWant() public view override returns (uint256) {} + + // Calculates how much 'want' the strategy has working in the farm. + function balanceOfPool() public view override returns (uint256) {} +} diff --git a/packages/foundry/src/vault/strategy/autocompouding/singleStake/SingleStakeBase.sol b/packages/foundry/src/vault/strategy/autocompouding/singleStake/SingleStakeBase.sol new file mode 100644 index 00000000..99f52a55 --- /dev/null +++ b/packages/foundry/src/vault/strategy/autocompouding/singleStake/SingleStakeBase.sol @@ -0,0 +1,72 @@ +// SPDX-License-Identifier: GPL-3.0 +// Docgen-SOLC: 0.8.15 + +pragma solidity ^0.8.15; + +import { IWithRewards } from "../../../../interfaces/vault/IWithRewards.sol"; +import { IAdapter } from "../../../../interfaces/vault/IAdapter.sol"; + +import { IEIP165 } from "../../../../interfaces/IEIP165.sol"; +import { MathUpgradeable as Math } from "openzeppelin-contracts-upgradeable/utils/math/MathUpgradeable.sol"; +import { ERC4626Upgradeable as ERC4626, ERC20Upgradeable as ERC20 } from "openzeppelin-contracts-upgradeable/token/ERC20/extensions/ERC4626Upgradeable.sol"; + +import { CompounderStrategyBase } from "../CompounderStrategyBase.sol"; + +import { IUniswapRouterV2 } from "../../../../interfaces/external/uni/IUniswapRouterV2.sol"; + +contract SingleStakeBase is CompounderStrategyBase { + using Math for uint256; + + // Tokens used + address public assetToken; + + // Routes + address[] public nativeToAssetTokenRoute; + + /*////////////////////////////////////////////////////////////// + SETUP + //////////////////////////////////////////////////////////////*/ + + // Setup for routes and allowances in constructor. + function _setUp( + address[][] memory _rewardToNativeRoutes, + address[] memory _nativeToAssetTokenRoute, + address _assetToken + ) internal virtual { + if (_nativeToAssetTokenRoute[0] != native) revert InvalidRoute(); + + assetToken = _assetToken; + + if (_nativeToAssetTokenRoute[_nativeToAssetTokenRoute.length - 1] != _assetToken) revert InvalidRoute(); + + _setRewardTokens(_rewardToNativeRoutes); + + _giveInitialAllowances(); + } + + /*////////////////////////////////////////////////////////////// + ROUTES + //////////////////////////////////////////////////////////////*/ + + // Set nativeToLp0Route. + function setNativeToAssetTokenRoute(address[] calldata route) public virtual { + nativeToAssetTokenRoute = route; + + if (isVaultFunctional == true) isVaultFunctional = false; + } + + /*////////////////////////////////////////////////////////////// + COMPOUND LOGIC + //////////////////////////////////////////////////////////////*/ + + // Logic to claim rewards, swap rewards to native, charge fees, swap native to lpTokens, add liquidity, and re-deposit. + function _compound() internal virtual override { + _claimRewards(); + _swapRewardsToNative(); + _swapNativeToAssetToken(); + _deposit(); + } + + // Swap native tokens for assetToken + function _swapNativeToAssetToken() internal virtual {} +} diff --git a/packages/foundry/src/vault/strategy/autocompouding/singleStake/protocols/stargate/StargateCompounder.sol b/packages/foundry/src/vault/strategy/autocompouding/singleStake/protocols/stargate/StargateCompounder.sol new file mode 100644 index 00000000..5e80a0ab --- /dev/null +++ b/packages/foundry/src/vault/strategy/autocompouding/singleStake/protocols/stargate/StargateCompounder.sol @@ -0,0 +1,99 @@ +// SPDX-License-Identifier: GPL-3.0 +// Docgen-SOLC: 0.8.15 + +pragma solidity ^0.8.15; +import { SingleStakeBase, ERC20 } from "../../SingleStakeBase.sol"; +import { IUniswapRouterV2 } from "../../../../../../interfaces/external/uni/IUniswapRouterV2.sol"; +import { IUniswapV2Pair } from "../../../../../../interfaces/external/uni/IUniswapV2Pair.sol"; +import { IStargateRewarder } from "../../../../../../interfaces/external/stargate/IStargateRewarder.sol"; +import { IStargateRouter } from "../../../../../../interfaces/external/stargate/IStargateRouter.sol"; +import { IRewarder } from "../../../../../../interfaces/external/IRewarder.sol"; +import { IUniswapV2Module } from "../../../../../../interfaces/modules/IUniswapV2Module.sol"; + +contract StargateCompounder is SingleStakeBase { + constructor( + address _native, + address _assetToken, + address[] memory _tradeModules, + address[] memory _routers, + address _vault, + address _strategist, + address[] memory _protocolAddresses, + uint256[] memory _protocolUints, + address[][] memory _rewardsToNativeRoutes, + address[] memory _nativeToAssetTokenRoute + ) public { + native = _native; + + tradeModules = _tradeModules; + routers = _routers; + + vault = _vault; + strategist = _strategist; + + protocolAddresses = _protocolAddresses; + protocolUints = _protocolUints; + + rewardsToNativeRoutes = _rewardsToNativeRoutes; + nativeToAssetTokenRoute = _nativeToAssetTokenRoute; + + _setUp(rewardsToNativeRoutes, nativeToAssetTokenRoute, _assetToken); + } + + /*////////////////////////////////////////////////////////////// + SETUP + //////////////////////////////////////////////////////////////*/ + + // Give allowances for protocol deposit and rewardToken swaps. + function _giveAllowances() internal override { + address stargateRewarder = protocolAddresses[0]; + address stargateRouter = protocolAddresses[1]; + address stargateWrappedAsset = protocolAddresses[2]; + + ERC20(assetToken).approve(stargateRouter, type(uint256).max); + ERC20(stargateWrappedAsset).approve(stargateRewarder, type(uint256).max); + } + + /*////////////////////////////////////////////////////////////// + COMPOUND LOGIC + //////////////////////////////////////////////////////////////*/ + + // Swap all rewards to native token + function _swapRewardsToNative(address[] memory _rewardRoute, uint256 _rewardAmount) internal override { + IUniswapV2Module uniV2Module = IUniswapV2Module(tradeModules[0]); + address uniV2Router = routers[0]; + + uniV2Module.swap(uniV2Router, _rewardRoute, _rewardAmount); + } + + // Swap native tokens for lpTokens + function _swapNativeToAssetToken() internal override { + IUniswapV2Module uniV2Module = IUniswapV2Module(tradeModules[0]); + address uniV2Router = routers[0]; + + uniV2Module.swap(uniV2Router, nativeToAssetTokenRoute, ERC20(native).balanceOf(address(this))); + } + + // Return available rewards for all rewardTokens. + function rewardsAvailable() public override returns (uint256[] memory) { + IStargateRewarder stargateRewarder = IStargateRewarder(protocolAddresses[0]); + uint256 pid = protocolUints[0]; + + pendingRewards.push(stargateRewarder.pendingStargate(pid, address(this))); + + return pendingRewards; + } + + /*////////////////////////////////////////////////////////////// + ACCOUNTING LOGIC + //////////////////////////////////////////////////////////////*/ + + // Calculate the total underlaying 'want' held by the strat. + function balanceOf() public view override returns (uint256) {} + + // Calculates how much 'want' this contract holds. + function balanceOfWant() public view override returns (uint256) {} + + // Calculates how much 'want' the strategy has working in the farm. + function balanceOfPool() public view override returns (uint256) {} +} diff --git a/packages/foundry/src/vault/strategy/compounder/BaseToAssetCompounder.sol b/packages/foundry/src/vault/strategy/compounder/BaseToAssetCompounder.sol new file mode 100644 index 00000000..a7433187 --- /dev/null +++ b/packages/foundry/src/vault/strategy/compounder/BaseToAssetCompounder.sol @@ -0,0 +1,164 @@ +// SPDX-License-Identifier: GPL-3.0 +// Docgen-SOLC: 0.8.15 + +pragma solidity ^0.8.15; + +import { IWithRewards } from "../../../interfaces/vault/IWithRewards.sol"; +import { IEIP165 } from "../../../interfaces/IEIP165.sol"; +import { MathUpgradeable as Math } from "openzeppelin-contracts-upgradeable/utils/math/MathUpgradeable.sol"; +import { ERC4626Upgradeable as ERC4626, ERC20Upgradeable as ERC20, IERC20Upgradeable as IERC20 } from "openzeppelin-contracts-upgradeable/token/ERC20/extensions/ERC4626Upgradeable.sol"; +import { IUniswapRouterV2 } from "../../../interfaces/external/uni/IUniswapRouterV2.sol"; +import { IAdapter } from "../../../interfaces/vault/IAdapter.sol"; +import { IWithRewards } from "../../../interfaces/vault/IWithRewards.sol"; +import { StrategyBase } from "../StrategyBase.sol"; + +interface IRouter { + function trade( + address[] memory route, + uint256 amount, + uint256 minAmount + ) external returns (uint256); +} + +contract BaseToAssetCompounder is StrategyBase { + // Events + event Harvest(); + + // Errors + error InvalidRoute(); + error InvalidConfig(); + + function verifyAdapterCompatibility(bytes memory data) public override { + ( + address baseAsset, + address toBaseAssetRouter, + address toAssetRouter, + address[4][5] memory toBaseAssetRoutes, + address[4] memory toAssetRoute, + uint8[5] memory toBaseAssetLen, + uint8 toAssetLen, + uint256[5] memory minTradeAmounts + ) = abi.decode( + IAdapter(address(this)).strategyConfig(), + (address, address, address, address[4][5], address[4], uint8[5], uint8, uint256[5]) + ); + + address asset = IAdapter(address(this)).asset(); + + address[] memory rewardTokens = IWithRewards(address(this)).rewardTokens(); + + uint256 len = rewardTokens.length; + for (uint256 i; i < len; i++) { + if (toBaseAssetRoutes[i][0] != rewardTokens[i]) revert InvalidConfig(); + if (toBaseAssetRoutes[i][toBaseAssetLen[i] - 1] != baseAsset) revert InvalidConfig(); + } + + if (toAssetRoute[0] != baseAsset) revert InvalidConfig(); + if (toAssetRoute[toAssetLen - 1] != asset) revert InvalidConfig(); + } + + function setUp(bytes memory data) public override { + ( + address baseAsset, + address toBaseAssetRouter, + address toAssetRouter, + address[4][5] memory toBaseAssetRoutes, + address[4] memory toAssetRoute, + uint8[5] memory toBaseAssetLen, + uint8 toAssetLen, + uint256[5] memory minTradeAmounts + ) = abi.decode( + IAdapter(address(this)).strategyConfig(), + (address, address, address, address[4][5], address[4], uint8[5], uint8, uint256[5]) + ); + + // Approve all rewardsToken for trading + address[] memory rewardTokens = IWithRewards(address(this)).rewardTokens(); + uint256 len = rewardTokens.length; + for (uint256 i = 0; i < len; i++) { + IERC20(rewardTokens[i]).approve(toBaseAssetRouter, type(uint256).max); + } + IERC20(baseAsset).approve(toAssetRouter, type(uint256).max); + } + + /*////////////////////////////////////////////////////////////// + HARVEST LOGIC + //////////////////////////////////////////////////////////////*/ + + // Harvest rewards. + function harvest() public override { + ( + address baseAsset, + address toBaseAssetRouter, + address toAssetRouter, + address[4][5] memory toBaseAssetRoutes, + address[4] memory toAssetRoute, + uint8[5] memory toBaseAssetLen, + uint8 toAssetLen, + uint256[5] memory minTradeAmounts + ) = abi.decode( + IAdapter(address(this)).strategyConfig(), + (address, address, address, address[4][5], address[4], uint8[5], uint8, uint256[5]) + ); + address asset = IAdapter(address(this)).asset(); + uint256 balBefore = IERC20(asset).balanceOf(address(this)); + + IWithRewards(address(this)).claim(); + + _swapRewardsToBase(toBaseAssetRouter, toBaseAssetRoutes, toBaseAssetLen, minTradeAmounts); + + _swapBaseToAsset(toAssetRouter, baseAsset, toAssetRoute, toAssetLen); + + uint256 balAfter = IERC20(asset).balanceOf(address(this)); + + IAdapter(address(this)).strategyDeposit(balAfter - balBefore, 0); + + emit Harvest(); + } + + // Swap all rewards to base asset. + function _swapRewardsToBase( + address toBaseAssetRouter, + address[4][5] memory toBaseAssetRoutes, + uint8[5] memory toBaseAssetLen, + uint256[5] memory minTradeAmounts + ) internal virtual { + address[] memory rewardTokens = IWithRewards(address(this)).rewardTokens(); + + uint256 len = rewardTokens.length; + for (uint256 i = 0; i < len; i++) { + uint256 rewardAmount = IERC20(rewardTokens[i]).balanceOf(address(this)); + + if (rewardAmount > minTradeAmounts[i]) { + // clean up trade route + address[] memory route = new address[](toBaseAssetLen[i]); + for (uint256 idx; idx < toBaseAssetLen[i]; idx++) { + route[idx] = toBaseAssetRoutes[i][idx]; + } + + // route, amount, minOut + IRouter(toBaseAssetRouter).trade(route, rewardAmount, 0); + } + } + } + + function _swapBaseToAsset( + address toAssetRouter, + address baseAsset, + address[4] memory toAssetRoute, + uint8 toAssetLen + ) internal virtual { + uint256 amount = IERC20(baseAsset).balanceOf(address(this)); + + if (amount > 0) { + // clean up trade route + address[] memory route = new address[](toAssetLen); + for (uint256 i; i < toAssetLen; i++) { + route[i] = toAssetRoute[i]; + } + + // route, amount, minOut + IRouter(toAssetRouter).trade(route, amount, 0); + } + } +} diff --git a/packages/foundry/src/vault/strategy/compounder/BaseToLpCompounder.sol b/packages/foundry/src/vault/strategy/compounder/BaseToLpCompounder.sol new file mode 100644 index 00000000..a29c9cf2 --- /dev/null +++ b/packages/foundry/src/vault/strategy/compounder/BaseToLpCompounder.sol @@ -0,0 +1,201 @@ +// SPDX-License-Identifier: GPL-3.0 +// Docgen-SOLC: 0.8.15 + +pragma solidity ^0.8.15; + +import { IWithRewards } from "../../../interfaces/vault/IWithRewards.sol"; +import { IEIP165 } from "../../../interfaces/IEIP165.sol"; +import { MathUpgradeable as Math } from "openzeppelin-contracts-upgradeable/utils/math/MathUpgradeable.sol"; +import { ERC4626Upgradeable as ERC4626, ERC20Upgradeable as ERC20, IERC20Upgradeable as IERC20 } from "openzeppelin-contracts-upgradeable/token/ERC20/extensions/ERC4626Upgradeable.sol"; +import { IUniswapRouterV2 } from "../../../interfaces/external/uni/IUniswapRouterV2.sol"; +import { IAdapter } from "../../../interfaces/vault/IAdapter.sol"; +import { IWithRewards } from "../../../interfaces/vault/IWithRewards.sol"; +import { StrategyBase } from "../StrategyBase.sol"; + +interface IRouter { + function trade( + address[] memory route, + uint256 amount, + uint256 minAmount + ) external returns (uint256); +} + +interface IPool { + function addLiquidity( + address lpToken, + address[2] memory underlyingTokens, + uint256 bal0, + uint256 bal1 + ) external returns (uint256); + + function underlyingTokens(address lpToken) external returns (address[2] memory); +} + +contract BaseToLpCompounder is StrategyBase { + // Events + event Harvest(); + + // Errors + error InvalidRoute(); + error InvalidConfig(); + + function verifyAdapterCompatibility(bytes memory data) public override { + ( + address baseAsset, + address toBaseAssetRouter, + address toAssetRouter, + address pool, + address[4][5] memory toBaseAssetRoutes, + address[4][2] memory toAssetRoute, + uint8[5] memory toBaseAssetLen, + uint8[2] memory toAssetLen, + uint256[5] memory minTradeAmounts + ) = abi.decode( + IAdapter(address(this)).strategyConfig(), + (address, address, address, address, address[4][5], address[4][2], uint8[5], uint8[2], uint256[5]) + ); + + address asset = IAdapter(address(this)).asset(); + + address[] memory rewardTokens = IWithRewards(address(this)).rewardTokens(); + + uint256 len = rewardTokens.length; + for (uint256 i; i < len; i++) { + if (toBaseAssetRoutes[i][0] != rewardTokens[i]) revert InvalidConfig(); + if (toBaseAssetRoutes[i][toBaseAssetLen[i] - 1] != baseAsset) revert InvalidConfig(); + } + + address[2] memory underlyingTokens = IPool(pool).underlyingTokens(IAdapter(address(this)).asset()); + + if (toAssetRoute[0][0] != baseAsset) revert InvalidConfig(); + if (toAssetRoute[1][0] != baseAsset) revert InvalidConfig(); + if (toAssetRoute[0][toAssetLen[0]] != underlyingTokens[0]) revert InvalidConfig(); + if (toAssetRoute[1][toAssetLen[1]] != underlyingTokens[1]) revert InvalidConfig(); + } + + function setUp(bytes memory data) public override { + ( + address baseAsset, + address toBaseAssetRouter, + address toAssetRouter, + address pool, + address[4][5] memory toBaseAssetRoutes, + address[4][2] memory toAssetRoute, + uint8[5] memory toBaseAssetLen, + uint8[2] memory toAssetLen, + uint256[5] memory minTradeAmounts + ) = abi.decode( + IAdapter(address(this)).strategyConfig(), + (address, address, address, address, address[4][5], address[4][2], uint8[5], uint8[2], uint256[5]) + ); + + // Approve all rewardsToken for trading + address[] memory rewardTokens = IWithRewards(address(this)).rewardTokens(); + uint256 len = rewardTokens.length; + for (uint256 i = 0; i < len; i++) { + IERC20(rewardTokens[i]).approve(toBaseAssetRouter, type(uint256).max); + } + IERC20(baseAsset).approve(toAssetRouter, type(uint256).max); + + address[2] memory underlyingTokens = IPool(pool).underlyingTokens(IAdapter(address(this)).asset()); + IERC20(underlyingTokens[0]).approve(pool, type(uint256).max); + IERC20(underlyingTokens[1]).approve(pool, type(uint256).max); + } + + /*////////////////////////////////////////////////////////////// + HARVEST LOGIC + //////////////////////////////////////////////////////////////*/ + + // Harvest rewards. + function harvest() public override { + ( + address baseAsset, + address toBaseAssetRouter, + address toAssetRouter, + address pool, + address[4][5] memory toBaseAssetRoutes, + address[4][2] memory toAssetRoute, + uint8[5] memory toBaseAssetLen, + uint8[2] memory toAssetLen, + uint256[5] memory minTradeAmounts + ) = abi.decode( + IAdapter(address(this)).strategyConfig(), + (address, address, address, address, address[4][5], address[4][2], uint8[5], uint8[2], uint256[5]) + ); + + address asset = IAdapter(address(this)).asset(); + uint256 balBefore = IERC20(asset).balanceOf(address(this)); + + IWithRewards(address(this)).claim(); + + _swapRewardsToBase(toBaseAssetRouter, toBaseAssetRoutes, toBaseAssetLen, minTradeAmounts); + + uint256 baseBal = IERC20(baseAsset).balanceOf(address(this)); + uint256 allocation = baseBal / 2; + + _swapBaseToAsset(toAssetRouter, baseAsset, toAssetRoute[0], toAssetLen[0], allocation); + _swapBaseToAsset(toAssetRouter, baseAsset, toAssetRoute[1], toAssetLen[1], allocation); + + _addLiquidity(pool, asset); + + uint256 balAfter = IERC20(asset).balanceOf(address(this)); + + IAdapter(address(this)).strategyDeposit(balAfter - balBefore, 0); + + emit Harvest(); + } + + // Swap all rewards to base asset. + function _swapRewardsToBase( + address toBaseAssetRouter, + address[4][5] memory toBaseAssetRoutes, + uint8[5] memory toBaseAssetLen, + uint256[5] memory minTradeAmounts + ) internal virtual { + address[] memory rewardTokens = IWithRewards(address(this)).rewardTokens(); + + uint256 len = rewardTokens.length; + for (uint256 i = 0; i < len; i++) { + uint256 rewardAmount = IERC20(rewardTokens[i]).balanceOf(address(this)); + + if (rewardAmount > minTradeAmounts[i]) { + // clean up trade route + address[] memory route = new address[](toBaseAssetLen[i]); + for (uint256 idx; idx < toBaseAssetLen[i]; idx++) { + route[idx] = toBaseAssetRoutes[i][idx]; + } + + // route, amount, minOut + IRouter(toBaseAssetRouter).trade(route, rewardAmount, 0); + } + } + } + + function _swapBaseToAsset( + address toAssetRouter, + address baseAsset, + address[4] memory toAssetRoute, + uint8 toAssetLen, + uint256 allocation + ) internal virtual { + if (allocation > 0) { + // clean up trade route + address[] memory route = new address[](toAssetLen); + for (uint256 i; i < toAssetLen; i++) { + route[i] = toAssetRoute[i]; + } + + // route, amount, minOut + IRouter(toAssetRouter).trade(route, allocation, 0); + } + } + + function _addLiquidity(address pool, address asset) internal { + address[2] memory underlyingTokens = IPool(pool).underlyingTokens(asset); + + uint256 bal0 = IERC20(underlyingTokens[0]).balanceOf(address(this)); + uint256 bal1 = IERC20(underlyingTokens[0]).balanceOf(address(this)); + + IPool(pool).addLiquidity(asset, underlyingTokens, bal0, bal1); + } +} diff --git a/packages/foundry/src/vault/strategy/compounder/pool/CurveMetapool.sol b/packages/foundry/src/vault/strategy/compounder/pool/CurveMetapool.sol new file mode 100644 index 00000000..250e8510 --- /dev/null +++ b/packages/foundry/src/vault/strategy/compounder/pool/CurveMetapool.sol @@ -0,0 +1,29 @@ +// SPDX-License-Identifier: GPL-3.0 +// Docgen-SOLC: 0.8.15 + +pragma solidity ^0.8.15; + +import { ICurveMetapool } from "../../../../interfaces/external/curve/ICurveMetapool.sol"; +import { IERC20 } from "openzeppelin-contracts/token/ERC20/IERC20.sol"; +import { SafeERC20 } from "openzeppelin-contracts/token/ERC20/utils/SafeERC20.sol"; + +contract CurveMetapool { + using SafeERC20 for IERC20; + + function addLiquidity( + address router, + address lpToken, + address[2] memory underlyingTokens, + uint256 bal0, + uint256 bal1 + ) external returns (uint256) { + IERC20(underlyingTokens[0]).transferFrom(msg.sender, address(this), bal0); + IERC20(underlyingTokens[1]).transferFrom(msg.sender, address(this), bal1); + + ICurveMetapool(router).addLiquidity([bal0, 0], 0); + + uint256 bal = IERC20(lpToken).balanceOf(address(this)); + IERC20(lpToken).safeTransfer(msg.sender, bal); + return bal; + } +} diff --git a/packages/foundry/src/vault/strategy/compounder/pool/UniV2Pool.sol b/packages/foundry/src/vault/strategy/compounder/pool/UniV2Pool.sol new file mode 100644 index 00000000..ccdb6c80 --- /dev/null +++ b/packages/foundry/src/vault/strategy/compounder/pool/UniV2Pool.sol @@ -0,0 +1,38 @@ +// SPDX-License-Identifier: GPL-3.0 +// Docgen-SOLC: 0.8.15 + +pragma solidity ^0.8.15; + +import { IUniswapRouterV2 } from "../../../../interfaces/external/uni/IUniswapRouterV2.sol"; +import { IERC20 } from "openzeppelin-contracts/token/ERC20/IERC20.sol"; +import { SafeERC20 } from "openzeppelin-contracts/token/ERC20/utils/SafeERC20.sol"; + +contract UniV2Pool { + using SafeERC20 for IERC20; + + function addLiquidity( + address router, + address lpToken, + address[2] memory underlyingTokens, + uint256 bal0, + uint256 bal1 + ) external returns (uint256) { + IERC20(underlyingTokens[0]).transferFrom(msg.sender, address(this), bal0); + IERC20(underlyingTokens[1]).transferFrom(msg.sender, address(this), bal1); + + IUniswapRouterV2(router).addLiquidity( + underlyingTokens[0], + underlyingTokens[1], + bal0, + bal1, + 0, + 0, + address(this), + block.timestamp + ); + + uint256 bal = IERC20(lpToken).balanceOf(address(this)); + IERC20(lpToken).safeTransfer(msg.sender, bal); + return bal; + } +} diff --git a/packages/foundry/src/vault/strategy/compounder/router/UniV2Router.sol b/packages/foundry/src/vault/strategy/compounder/router/UniV2Router.sol new file mode 100644 index 00000000..9f1de946 --- /dev/null +++ b/packages/foundry/src/vault/strategy/compounder/router/UniV2Router.sol @@ -0,0 +1,38 @@ +// SPDX-License-Identifier: GPL-3.0 +// Docgen-SOLC: 0.8.15 + +pragma solidity ^0.8.15; + +import { IUniswapRouterV2 } from "../../../../interfaces/external/uni/IUniswapRouterV2.sol"; +import { IERC20 } from "openzeppelin-contracts/token/ERC20/IERC20.sol"; +import { SafeERC20 } from "openzeppelin-contracts/token/ERC20/utils/SafeERC20.sol"; + +contract UniV2Router { + using SafeERC20 for IERC20; + + address router; + + constructor(address _router) { + router = _router; + } + + function trade( + address[] memory route, + uint256 amount, + uint256 minAmount + ) external returns (uint256) { + IERC20(route[0]).transferFrom(msg.sender, address(this), amount); + + uint256[] memory amountsOut = IUniswapRouterV2(router).swapExactTokensForTokens( + amount, + minAmount, + route, + msg.sender, + block.timestamp + ); + + uint256 len = route.length; + IERC20(route[len - 1]).safeTransfer(msg.sender, amountsOut[len - 1]); + return amountsOut[len - 1]; + } +} diff --git a/packages/foundry/src/vault/strategy/trade/1InchTradeModule.sol b/packages/foundry/src/vault/strategy/trade/1InchTradeModule.sol new file mode 100644 index 00000000..64f461be --- /dev/null +++ b/packages/foundry/src/vault/strategy/trade/1InchTradeModule.sol @@ -0,0 +1,116 @@ +// SPDX-License-Identifier: GPL-3.0 +// Docgen-SOLC: 0.8.15 + +pragma solidity ^0.8.15; +import { IUniswapRouterV2 } from "../../../interfaces/external/uni/IUniswapRouterV2.sol"; +import { IERC20 } from "openzeppelin-contracts/token/ERC20/IERC20.sol"; + +contract UniV2TradeModule { + function getAmountOut( + uint256 amountIn, + address[] calldata tradePath, + address router + ) external view returns (uint256) { + uint256 len = tradePath.length; + uint256[] memory amountsOut = IUniswapRouterV2(router).getAmountsOut(amountIn, tradePath); + return amountsOut[len - 1]; + } + + function trade( + uint256 amountIn, + uint256 amountOut, + address[] calldata tradePath, + address router + ) external returns (uint256) { + IERC20(tradePath[0]).transferFrom(msg.sender, address(this), amountIn); + + uint256[] memory amountsOut = IUniswapRouterV2(router).swapExactTokensForTokens( + amountIn, + amountOut, + tradePath, + msg.sender, + block.timestamp + ); + + uint256 len = tradePath.length; + IERC20(tradePath[len - 1]).transfer(msg.sender, amountsOut[len - 1]); + return amountsOut[len - 1]; + } +} +/* +struct Order { + uint256 salt; + address makerAsset; + address takerAsset; + address maker; + address receiver; + address allowedSender; // equals to Zero address on public orders + uint256 makingAmount; + uint256 takingAmount; + uint256 offsets; + // bytes makerAssetData; + // bytes takerAssetData; + // bytes getMakingAmount; // this.staticcall(abi.encodePacked(bytes, swapTakerAmount)) => (swapMakerAmount) + // bytes getTakingAmount; // this.staticcall(abi.encodePacked(bytes, swapMakerAmount)) => (swapTakerAmount) + // bytes predicate; // this.staticcall(bytes) => (bool) + // bytes permit; // On first fill: permit.1.call(abi.encodePacked(permit.selector, permit.2)) + // bytes preInteraction; + // bytes postInteraction; + bytes interactions; // concat(makerAssetData, takerAssetData, getMakingAmount, getTakingAmount, predicate, permit, preIntercation, postInteraction) + } + */ + +/* +struct SwapDescription { + IERC20 srcToken; + IERC20 dstToken; + address payable srcReceiver; + address payable dstReceiver; + uint256 amount; + uint256 minReturnAmount; + uint256 flags; + } + */ + + /* + /// @notice Performs a swap, delegating all calls encoded in `data` to `executor`. See tests for usage examples + /// @dev router keeps 1 wei of every token on the contract balance for gas optimisations reasons. This affects first swap of every token by leaving 1 wei on the contract. + /// @param executor Aggregation executor that executes calls described in `data` + /// @param desc Swap description + /// @param permit Should contain valid permit that can be used in `IERC20Permit.permit` calls. + /// @param data Encoded calls that `caller` should execute in between of swaps + /// @return returnAmount Resulting token amount + /// @return spentAmount Source token amount + function swap( + IAggregationExecutor executor, + SwapDescription calldata desc, + bytes calldata permit, + bytes calldata data + ) + external + payable + returns ( + uint256 returnAmount, + uint256 spentAmount + ) + */ + + /* + /// @notice Swap token0 for token1, or token1 for token0 + /// @dev The caller of this method receives a callback in the form of IUniswapV3SwapCallback#uniswapV3SwapCallback + /// @param recipient The address to receive the output of the swap + /// @param zeroForOne The direction of the swap, true for token0 to token1, false for token1 to token0 + /// @param amountSpecified The amount of the swap, which implicitly configures the swap as exact input (positive), or exact output (negative) + /// @param sqrtPriceLimitX96 The Q64.96 sqrt price limit. If zero for one, the price cannot be less than this + /// value after the swap. If one for zero, the price cannot be greater than this value after the swap + /// @param data Any data to be passed through to the callback + /// @return amount0 The delta of the balance of token0 of the pool, exact when negative, minimum when positive + /// @return amount1 The delta of the balance of token1 of the pool, exact when negative, minimum when positive + function swap( + address recipient, + bool zeroForOne, + int256 amountSpecified, + uint160 sqrtPriceLimitX96, + bytes calldata data + ) external returns (int256 amount0, int256 amount1); + */ \ No newline at end of file diff --git a/packages/foundry/src/vault/strategy/trade/CompounderStrategy.sol b/packages/foundry/src/vault/strategy/trade/CompounderStrategy.sol new file mode 100644 index 00000000..9049372c --- /dev/null +++ b/packages/foundry/src/vault/strategy/trade/CompounderStrategy.sol @@ -0,0 +1,50 @@ +// SPDX-License-Identifier: GPL-3.0 +// Docgen-SOLC: 0.8.15 +pragma solidity ^0.8.15; + +import { ERC4626Upgradeable as ERC4626, ERC20Upgradeable as ERC20 } from "openzeppelin-contracts-upgradeable/token/ERC20/extensions/ERC4626Upgradeable.sol"; +import { IUniswapRouterV2 } from "../../../interfaces/external/uni/IUniswapRouterV2.sol"; +import { IAdapter } from "../../../interfaces/vault/IAdapter.sol"; +import { IWithRewards } from "../../../interfaces/vault/IWithRewards.sol"; +import { StrategyBase } from "../StrategyBase.sol"; +import { IERC20 } from "openzeppelin-contracts/token/ERC20/IERC20.sol"; +import { ITradeModule, Trade } from "./ITradeModule.sol"; + +contract Pool2SingleAssetCompounder is StrategyBase { + error NoValidTradePath(); + + /// @notice claim all token rewards and trade them for the underlying asset + // function harvest() public override { + // (address tradeModule, address router, uint256 minTradeAmount, uint256 slippage, address[] tradePath) = abi.decode( + // IAdapter(address(this)).strategyConfig(), + // ((address, address, uint256, uint256, address[])) + // ); + + // for (uint256 i = 0; i < len; i++) { + // uint256 bal = ERC20(tradePath[0]).balanceOf(address(this)); + // if (bal > minTradeAmount) { + // // TODO amountOut currently doesnt account for oracle manipulation before the trade. It assumes a fair price + // uint256 amountOut = ITradeModule(tradeModule).getAmountOut(bal, tradePath, router); + // ITradeModule(tradeModule).trade(bal, (amountOut * (1e18 - slippage)) / 1e18, tradePath, router); + // } + // } + // IAdapter(address(this)).strategyDeposit(ERC20(asset).balanceOf(address(this)), 0); + // } +} + +// asset -> asset +// asset -> curve pool2 +// asset -> curve pool3 +// asset -> curve custom pool -- how many? +// asset -> uniV2 +// asset -> uniV3 +// asset -> velo? +// asset -> balancer -- how many? +// split asset to mitigate slippage + +// Use 1Inch Router +// 1. Get Spot price +// 2. Calc Min +// 3. Trade using router +// 4. Potentially LP +// Trade to usdc first? and than trade all together to asset? Or just each reward on their own diff --git a/packages/foundry/src/vault/strategy/trade/ITradeModule.sol b/packages/foundry/src/vault/strategy/trade/ITradeModule.sol new file mode 100644 index 00000000..0de5ddbe --- /dev/null +++ b/packages/foundry/src/vault/strategy/trade/ITradeModule.sol @@ -0,0 +1,26 @@ +// SPDX-License-Identifier: GPL-3.0 +// Docgen-SOLC: 0.8.15 +pragma solidity ^0.8.15; + +struct Trade { + address tradeModule; + address router; + uint256 minTradeAmount; + uint256 slippage; + address[] tradePath; +} + +interface ITradeModule { + function getAmountOut( + uint256 amountIn, + address[] calldata tradePath, + address router + ) external view returns (uint256); + + function trade( + uint256 amountIn, + uint256 amountOut, + address[] calldata tradePath, + address router + ) external returns (uint256); +} \ No newline at end of file diff --git a/packages/foundry/src/vault/strategy/trade/Pool2Depositor.sol b/packages/foundry/src/vault/strategy/trade/Pool2Depositor.sol new file mode 100644 index 00000000..fcff9dc7 --- /dev/null +++ b/packages/foundry/src/vault/strategy/trade/Pool2Depositor.sol @@ -0,0 +1,59 @@ +// SPDX-License-Identifier: GPL-3.0 +// Docgen-SOLC: 0.8.15 + +pragma solidity ^0.8.15; +import { IUniswapRouterV2 } from "../../../interfaces/external/uni/IUniswapRouterV2.sol"; +import { IUniswapV2Pair } from "../../../interfaces/external/uni/IUniswapV2Pair.sol"; +import { IERC20 } from "openzeppelin-contracts/token/ERC20/IERC20.sol"; + +contract UniV2TradeModule { + function getAmountOut( + uint256 amountIn, + address[] calldata tradePath, + address router + ) external view returns (uint256) { + uint256 len = tradePath.length; + uint256[] memory amountsOut = IUniswapRouterV2(router).getAmountsOut(amountIn, tradePath); + return amountsOut[len - 1]; + } + + function deposit( + address asset, + uint256 amountIn, + address[] calldata tradePath, + address router + ) external returns (uint256) { + IERC20(tradePath[0]).transferFrom(msg.sender, address(this), amountIn); + + // A - only one token + // B - both token but unbalanced + // C - both token but balanced + + // 1. Check pair assets + // 2. Check pair balance and ratio + // 3. Compare balances and ratio + // 4. Trade larger balance for smaller balance to achieve ratio ---- this will change the ratio + // 5. Deposit to pool + + // address token0 = IUniswapV2Pair(asset).token0(); + // address token1 = IUniswapV2Pair(asset).token1(); + + // uint256 bal0 = IERC20(token0).balanceOf(msg.sender); + // uint256 bal1 = IERC20(token1).balanceOf(msg.sender); + + // (uint112 reserve0, uint112 reserve1, uint32 blockTimestampLast) = IUniswapV2Pair(asset).getReserves(); + + // uint256 amountOut = IUniswapRouterV2(router).addLiquidity( + // token0, + // token1, + // amountADesired, + // amountBDesired, + // amountAMin, + // amountBMin, + // msg.sender, + // block.timestamp + // ); + // IERC20(asset).transfer(msg.sender, amountOut); + // return amountOut; + } +} diff --git a/packages/foundry/src/vault/strategy/trade/SimplePool2Depositor.sol b/packages/foundry/src/vault/strategy/trade/SimplePool2Depositor.sol new file mode 100644 index 00000000..75e04638 --- /dev/null +++ b/packages/foundry/src/vault/strategy/trade/SimplePool2Depositor.sol @@ -0,0 +1,51 @@ +// SPDX-License-Identifier: GPL-3.0 +// Docgen-SOLC: 0.8.15 +pragma solidity ^0.8.15; + +import { IUniswapRouterV2 } from "../../../interfaces/external/uni/IUniswapRouterV2.sol"; +import { IUniswapV2Pair } from "../../../interfaces/external/uni/IUniswapV2Pair.sol"; +import { IERC20 } from "openzeppelin-contracts/token/ERC20/IERC20.sol"; +import { ITradeModule } from "./ITradeModule.sol"; + +contract SimplePool2Depositor { + function getAllocation( + address tradeModule, + uint256 allocation, + address[] calldata tradePath, + address router, + uint256 slippage + ) internal returns (uint256 amountOut, uint256 bal) { + amountOut = ITradeModule(tradeModule).getAmountOut(allocation, tradePath, router); + bal = ITradeModule(tradeModule).trade(allocation, (amountOut * (1e18 - slippage)) / 1e18, tradePath, router); + } + + function deposit( + address asset, + uint256 amountIn, + address[][2] calldata tradePaths, + address router, + address tradeModule, + uint256 slippage + ) external returns (uint256) { + address token0 = IUniswapV2Pair(asset).token0(); + address token1 = IUniswapV2Pair(asset).token1(); + uint256 allocation = amountIn / 2; + + (uint256 amountOut0, uint256 bal0) = getAllocation(tradeModule, allocation, tradePaths[0], router, slippage); + + (uint256 amountOut1, uint256 bal1) = getAllocation(tradeModule, allocation, tradePaths[1], router, slippage); + + (, , uint256 amountOut) = IUniswapRouterV2(router).addLiquidity( + token0, + token1, + bal0, + bal1, + (bal0 * (1e18 - 1e14)) / 1e18, + (bal1 * (1e18 - 1e14)) / 1e18, + msg.sender, + block.timestamp + ); + IERC20(asset).transfer(msg.sender, amountOut); + return amountOut; + } +} diff --git a/packages/foundry/src/vault/strategy/trade/UniV2TradeModule.sol b/packages/foundry/src/vault/strategy/trade/UniV2TradeModule.sol new file mode 100644 index 00000000..9d9f9001 --- /dev/null +++ b/packages/foundry/src/vault/strategy/trade/UniV2TradeModule.sol @@ -0,0 +1,71 @@ +// SPDX-License-Identifier: GPL-3.0 +// Docgen-SOLC: 0.8.15 + +pragma solidity ^0.8.15; +import { IUniswapRouterV2 } from "../../../interfaces/external/uni/IUniswapRouterV2.sol"; +import { IERC20 } from "openzeppelin-contracts/token/ERC20/IERC20.sol"; + +contract UniV2TradeModule { + function getAmountOut( + uint256 amountIn, + address[] calldata tradePath, + address router + ) external view returns (uint256) { + uint256 len = tradePath.length; + uint256[] memory amountsOut = IUniswapRouterV2(router).getAmountsOut(amountIn, tradePath); + return amountsOut[len - 1]; + } + + function trade( + uint256 amountIn, + uint256 amountOut, + address[] calldata tradePath, + address router + ) external returns (uint256) { + IERC20(tradePath[0]).transferFrom(msg.sender, address(this), amountIn); + + uint256[] memory amountsOut = IUniswapRouterV2(router).swapExactTokensForTokens( + amountIn, + amountOut, + tradePath, + msg.sender, + block.timestamp + ); + + uint256 len = tradePath.length; + IERC20(tradePath[len - 1]).transfer(msg.sender, amountsOut[len - 1]); + return amountsOut[len - 1]; + } + + // Swap compatible with UniswapV2 interface. + function swap( + address _router, + address[] memory _route, + uint256 _amount + ) external { + IUniswapRouterV2(_router).swapExactTokensForTokens(_amount, 0, _route, address(this), block.timestamp + 60); + } + + // Add liquidity to UniswapV2-compatible protocol. + function addLiquidity( + address _router, + address _lpToken0, + address _lpToken1 + ) external { + uint256 lpToken0Amount = IERC20(_lpToken0).balanceOf(address(this)); + uint256 lpToken1Amount = IERC20(_lpToken1).balanceOf(address(this)); + + if (lpToken0Amount > 0 && lpToken1Amount > 0) { + IUniswapRouterV2(_router).addLiquidity( + _lpToken0, + _lpToken1, + lpToken0Amount, + lpToken1Amount, + 0, + 0, + address(this), + block.timestamp + 60 + ); + } + } +} diff --git a/packages/foundry/test/utils/mocks/MockStrategyClaimer.sol b/packages/foundry/test/utils/mocks/MockStrategyClaimer.sol new file mode 100644 index 00000000..edf1a19a --- /dev/null +++ b/packages/foundry/test/utils/mocks/MockStrategyClaimer.sol @@ -0,0 +1,28 @@ +pragma solidity ^0.8.15; + +import { IWithRewards } from "../../../src/interfaces/vault/IWithRewards.sol"; + + +contract MockStrategyClaimer { + event SelectorsVerified(); + event AdapterVerified(); + event StrategySetup(); + event StrategyExecuted(); + + function verifyAdapterSelectorCompatibility(bytes4[8] memory) public { + emit SelectorsVerified(); + } + + function verifyAdapterCompatibility(bytes memory) public { + emit AdapterVerified(); + } + + function setUp(bytes memory) public { + emit StrategySetup(); + } + + function harvest() public { + IWithRewards(address(this)).claim(); + emit StrategyExecuted(); + } +} diff --git a/packages/foundry/test/vault/integration/aaveV2/AaveV2Adapter.t.sol b/packages/foundry/test/vault/integration/adapter/aaveV2/AaveV2Adapter.t.sol similarity index 97% rename from packages/foundry/test/vault/integration/aaveV2/AaveV2Adapter.t.sol rename to packages/foundry/test/vault/integration/adapter/aaveV2/AaveV2Adapter.t.sol index a07bf6eb..7a0ce315 100644 --- a/packages/foundry/test/vault/integration/aaveV2/AaveV2Adapter.t.sol +++ b/packages/foundry/test/vault/integration/adapter/aaveV2/AaveV2Adapter.t.sol @@ -5,7 +5,7 @@ pragma solidity ^0.8.15; import { Test } from "forge-std/Test.sol"; -import { AaveV2Adapter, SafeERC20, IERC20, IERC20Metadata, Math, ILendingPool, IAaveMining, IAToken, IProtocolDataProvider, DataTypes } from "../../../../src/vault/adapter/aave/aaveV2/AaveV2Adapter.sol"; +import { AaveV2Adapter, SafeERC20, IERC20, IERC20Metadata, Math, ILendingPool, IAaveMining, IAToken, IProtocolDataProvider, DataTypes } from "../../../../../src/vault/adapter/aave/aaveV2/AaveV2Adapter.sol"; import { AaveV2TestConfigStorage, AaveV2TestConfig } from "./AaveV2TestConfigStorage.sol"; import { AbstractAdapterTest, ITestConfigStorage, IAdapter } from "../abstract/AbstractAdapterTest.sol"; diff --git a/packages/foundry/test/vault/integration/aaveV2/AaveV2TestConfigStorage.sol b/packages/foundry/test/vault/integration/adapter/aaveV2/AaveV2TestConfigStorage.sol similarity index 100% rename from packages/foundry/test/vault/integration/aaveV2/AaveV2TestConfigStorage.sol rename to packages/foundry/test/vault/integration/adapter/aaveV2/AaveV2TestConfigStorage.sol diff --git a/packages/foundry/test/vault/integration/aaveV3/AaveV3Adapter.t.sol b/packages/foundry/test/vault/integration/adapter/aaveV3/AaveV3Adapter.t.sol similarity index 97% rename from packages/foundry/test/vault/integration/aaveV3/AaveV3Adapter.t.sol rename to packages/foundry/test/vault/integration/adapter/aaveV3/AaveV3Adapter.t.sol index 5ebf7e6f..d84af36d 100644 --- a/packages/foundry/test/vault/integration/aaveV3/AaveV3Adapter.t.sol +++ b/packages/foundry/test/vault/integration/adapter/aaveV3/AaveV3Adapter.t.sol @@ -5,7 +5,7 @@ pragma solidity ^0.8.15; import { Test } from "forge-std/Test.sol"; -import { AaveV3Adapter, SafeERC20, IERC20, IERC20Metadata, Math, ILendingPool, IAaveIncentives, IAToken, IProtocolDataProvider, DataTypes } from "../../../../src/vault/adapter/aave/aaveV3/AaveV3Adapter.sol"; +import { AaveV3Adapter, SafeERC20, IERC20, IERC20Metadata, Math, ILendingPool, IAaveIncentives, IAToken, IProtocolDataProvider, DataTypes } from "../../../../../src/vault/adapter/aave/aaveV3/AaveV3Adapter.sol"; import { AaveV3TestConfigStorage, AaveV3TestConfig } from "./AaveV3TestConfigStorage.sol"; import { AbstractAdapterTest, ITestConfigStorage, IAdapter } from "../abstract/AbstractAdapterTest.sol"; diff --git a/packages/foundry/test/vault/integration/aaveV3/AaveV3TestConfigStorage.sol b/packages/foundry/test/vault/integration/adapter/aaveV3/AaveV3TestConfigStorage.sol similarity index 100% rename from packages/foundry/test/vault/integration/aaveV3/AaveV3TestConfigStorage.sol rename to packages/foundry/test/vault/integration/adapter/aaveV3/AaveV3TestConfigStorage.sol diff --git a/packages/foundry/test/vault/integration/abstract/AbstractAdapterTest.sol b/packages/foundry/test/vault/integration/adapter/abstract/AbstractAdapterTest.sol similarity index 98% rename from packages/foundry/test/vault/integration/abstract/AbstractAdapterTest.sol rename to packages/foundry/test/vault/integration/adapter/abstract/AbstractAdapterTest.sol index 10306295..e0443ba4 100644 --- a/packages/foundry/test/vault/integration/abstract/AbstractAdapterTest.sol +++ b/packages/foundry/test/vault/integration/adapter/abstract/AbstractAdapterTest.sol @@ -6,11 +6,11 @@ pragma solidity ^0.8.15; import { Test } from "forge-std/Test.sol"; import { PropertyTest } from "./PropertyTest.prop.sol"; -import { IAdapter, IERC4626 } from "../../../../src/interfaces/vault/IAdapter.sol"; -import { IStrategy } from "../../../../src/interfaces/vault/IStrategy.sol"; +import { IAdapter, IERC4626 } from "../../../../../src/interfaces/vault/IAdapter.sol"; +import { IStrategy } from "../../../../../src/interfaces/vault/IStrategy.sol"; import { IERC20Upgradeable as IERC20, IERC20MetadataUpgradeable as IERC20Metadata } from "openzeppelin-contracts-upgradeable/token/ERC20/extensions/IERC20MetadataUpgradeable.sol"; import { ITestConfigStorage } from "./ITestConfigStorage.sol"; -import { MockStrategy } from "../../../utils/mocks/MockStrategy.sol"; +import { MockStrategy } from "../../../../utils/mocks/MockStrategy.sol"; import { Math } from "openzeppelin-contracts/utils/math/Math.sol"; import { Clones } from "openzeppelin-contracts/proxy/Clones.sol"; @@ -558,13 +558,13 @@ contract AbstractAdapterTest is PropertyTest { //////////////////////////////////////////////////////////////*/ // OPTIONAL - function testClaim() public virtual {} + function test__claim() public virtual {} /*////////////////////////////////////////////////////////////// PERMIT //////////////////////////////////////////////////////////////*/ - function testPermit() public { + function test__permit() public { uint256 privateKey = 0xBEEF; address owner = vm.addr(privateKey); diff --git a/packages/foundry/test/vault/integration/abstract/ITestConfigStorage.sol b/packages/foundry/test/vault/integration/adapter/abstract/ITestConfigStorage.sol similarity index 100% rename from packages/foundry/test/vault/integration/abstract/ITestConfigStorage.sol rename to packages/foundry/test/vault/integration/adapter/abstract/ITestConfigStorage.sol diff --git a/packages/foundry/test/vault/integration/abstract/PropertyTest.prop.sol b/packages/foundry/test/vault/integration/adapter/abstract/PropertyTest.prop.sol similarity index 99% rename from packages/foundry/test/vault/integration/abstract/PropertyTest.prop.sol rename to packages/foundry/test/vault/integration/adapter/abstract/PropertyTest.prop.sol index b4550b03..f8bcf504 100644 --- a/packages/foundry/test/vault/integration/abstract/PropertyTest.prop.sol +++ b/packages/foundry/test/vault/integration/adapter/abstract/PropertyTest.prop.sol @@ -3,7 +3,7 @@ pragma solidity ^0.8.15; -import { EnhancedTest } from "../../../utils/EnhancedTest.sol"; +import { EnhancedTest } from "../../../../utils/EnhancedTest.sol"; import { IERC4626Upgradeable as IERC4626, IERC20Upgradeable as IERC20 } from "openzeppelin-contracts-upgradeable/interfaces/IERC4626Upgradeable.sol"; contract PropertyTest is EnhancedTest { diff --git a/packages/foundry/test/vault/integration/beefy/BeefyAdapter.t.sol b/packages/foundry/test/vault/integration/adapter/beefy/BeefyAdapter.t.sol similarity index 96% rename from packages/foundry/test/vault/integration/beefy/BeefyAdapter.t.sol rename to packages/foundry/test/vault/integration/adapter/beefy/BeefyAdapter.t.sol index 148e1560..51ba524b 100644 --- a/packages/foundry/test/vault/integration/beefy/BeefyAdapter.t.sol +++ b/packages/foundry/test/vault/integration/adapter/beefy/BeefyAdapter.t.sol @@ -5,11 +5,11 @@ pragma solidity ^0.8.15; import { Test } from "forge-std/Test.sol"; -import { BeefyAdapter, SafeERC20, IERC20, IERC20Metadata, IBeefyVault, IBeefyBooster, IBeefyBalanceCheck } from "../../../../src/vault/adapter/beefy/BeefyAdapter.sol"; +import { BeefyAdapter, SafeERC20, IERC20, IERC20Metadata, IBeefyVault, IBeefyBooster, IBeefyBalanceCheck } from "../../../../../src/vault/adapter/beefy/BeefyAdapter.sol"; import { BeefyTestConfigStorage, BeefyTestConfig } from "./BeefyTestConfigStorage.sol"; import { AbstractAdapterTest, ITestConfigStorage, IAdapter, Math } from "../abstract/AbstractAdapterTest.sol"; -import { IPermissionRegistry, Permission } from "../../../../src/interfaces/vault/IPermissionRegistry.sol"; -import { PermissionRegistry } from "../../../../src/vault/PermissionRegistry.sol"; +import { IPermissionRegistry, Permission } from "../../../../../src/interfaces/vault/IPermissionRegistry.sol"; +import { PermissionRegistry } from "../../../../../src/vault/PermissionRegistry.sol"; contract BeefyAdapterTest is AbstractAdapterTest { using Math for uint256; diff --git a/packages/foundry/test/vault/integration/beefy/BeefyTestConfigStorage.sol b/packages/foundry/test/vault/integration/adapter/beefy/BeefyTestConfigStorage.sol similarity index 100% rename from packages/foundry/test/vault/integration/beefy/BeefyTestConfigStorage.sol rename to packages/foundry/test/vault/integration/adapter/beefy/BeefyTestConfigStorage.sol diff --git a/packages/foundry/test/vault/integration/compoundV2/CompoundV2Adapter.t.sol b/packages/foundry/test/vault/integration/adapter/compoundV2/CompoundV2Adapter.t.sol similarity index 98% rename from packages/foundry/test/vault/integration/compoundV2/CompoundV2Adapter.t.sol rename to packages/foundry/test/vault/integration/adapter/compoundV2/CompoundV2Adapter.t.sol index 36fcb8b8..20b47f23 100644 --- a/packages/foundry/test/vault/integration/compoundV2/CompoundV2Adapter.t.sol +++ b/packages/foundry/test/vault/integration/adapter/compoundV2/CompoundV2Adapter.t.sol @@ -5,7 +5,7 @@ pragma solidity ^0.8.15; import { Test } from "forge-std/Test.sol"; -import { CompoundV2Adapter, SafeERC20, IERC20, IERC20Metadata, Math, ICToken, IComptroller } from "../../../../src/vault/adapter/compound/compoundV2/CompoundV2Adapter.sol"; +import { CompoundV2Adapter, SafeERC20, IERC20, IERC20Metadata, Math, ICToken, IComptroller } from "../../../../../src/vault/adapter/compound/compoundV2/CompoundV2Adapter.sol"; import { CompoundV2TestConfigStorage, CompoundV2TestConfig } from "./CompoundV2TestConfigStorage.sol"; import { AbstractAdapterTest, ITestConfigStorage, IAdapter } from "../abstract/AbstractAdapterTest.sol"; diff --git a/packages/foundry/test/vault/integration/compoundV2/CompoundV2TestConfigStorage.sol b/packages/foundry/test/vault/integration/adapter/compoundV2/CompoundV2TestConfigStorage.sol similarity index 100% rename from packages/foundry/test/vault/integration/compoundV2/CompoundV2TestConfigStorage.sol rename to packages/foundry/test/vault/integration/adapter/compoundV2/CompoundV2TestConfigStorage.sol diff --git a/packages/foundry/test/vault/integration/convex/ConvexAdapter.t.sol b/packages/foundry/test/vault/integration/adapter/convex/ConvexAdapter.t.sol similarity index 72% rename from packages/foundry/test/vault/integration/convex/ConvexAdapter.t.sol rename to packages/foundry/test/vault/integration/adapter/convex/ConvexAdapter.t.sol index 4a8ea268..6f982402 100644 --- a/packages/foundry/test/vault/integration/convex/ConvexAdapter.t.sol +++ b/packages/foundry/test/vault/integration/adapter/convex/ConvexAdapter.t.sol @@ -3,9 +3,11 @@ pragma solidity ^0.8.15; import { Test } from "forge-std/Test.sol"; -import { ConvexAdapter, SafeERC20, IERC20, IERC20Metadata, Math, IConvexBooster, IConvexRewards } from "../../../../src/vault/adapter/convex/ConvexAdapter.sol"; +import { ConvexAdapter, SafeERC20, IERC20, IERC20Metadata, Math, IConvexBooster, IConvexRewards, IWithRewards } from "../../../../../src/vault/adapter/convex/ConvexAdapter.sol"; import { ConvexTestConfigStorage, ConvexTestConfig } from "./ConvexTestConfigStorage.sol"; import { AbstractAdapterTest, ITestConfigStorage, IAdapter } from "../abstract/AbstractAdapterTest.sol"; +import { Clones } from "openzeppelin-contracts/proxy/Clones.sol"; +import { MockStrategyClaimer } from "../../../../utils/mocks/MockStrategyClaimer.sol"; contract ConvexAdapterTest is AbstractAdapterTest { using Math for uint256; @@ -88,4 +90,35 @@ contract ConvexAdapterTest is AbstractAdapterTest { assertEq(asset.allowance(address(adapter), address(convexBooster)), type(uint256).max, "allowance"); } + + /*////////////////////////////////////////////////////////////// + CLAIM + //////////////////////////////////////////////////////////////*/ + + // OPTIONAL + function test__claim() public override { + uint256 forkId = vm.createSelectFork(vm.rpcUrl("mainnet"), 15717986); + vm.selectFork(forkId); + + testConfigStorage = ITestConfigStorage(address(new ConvexTestConfigStorage())); + + _setUpTest(testConfigStorage.getTestConfig(0)); + + adapter = IAdapter(Clones.clone(address(new ConvexAdapter()))); + adapter.initialize( + abi.encode(asset, address(this), strategy, 0, sigs, ""), //address(new MockStrategyClaimer()) + externalRegistry, + testConfigStorage.getTestConfig(0) + ); + + _mintFor(defaultAmount * 10000, bob); + + vm.prank(bob); + adapter.deposit(defaultAmount * 10000, bob); + + vm.warp(100 days); + + vm.prank(bob); + IWithRewards(address(adapter)).claim(); + } } diff --git a/packages/foundry/test/vault/integration/convex/ConvexTestConfigStorage.sol b/packages/foundry/test/vault/integration/adapter/convex/ConvexTestConfigStorage.sol similarity index 88% rename from packages/foundry/test/vault/integration/convex/ConvexTestConfigStorage.sol rename to packages/foundry/test/vault/integration/adapter/convex/ConvexTestConfigStorage.sol index 2114bd4d..c3fdd5f2 100644 --- a/packages/foundry/test/vault/integration/convex/ConvexTestConfigStorage.sol +++ b/packages/foundry/test/vault/integration/adapter/convex/ConvexTestConfigStorage.sol @@ -13,8 +13,8 @@ contract ConvexTestConfigStorage is ITestConfigStorage { ConvexTestConfig[] internal testConfigs; constructor() { - // Mainnet - wETH - testConfigs.push(ConvexTestConfig(61)); + // Mainnet - MIM + 3CRV + testConfigs.push(ConvexTestConfig(40)); } function getTestConfig(uint256 i) public view returns (bytes memory) { diff --git a/packages/foundry/test/vault/integration/yearn/YearnAdapter.t.sol b/packages/foundry/test/vault/integration/adapter/yearn/YearnAdapter.t.sol similarity index 98% rename from packages/foundry/test/vault/integration/yearn/YearnAdapter.t.sol rename to packages/foundry/test/vault/integration/adapter/yearn/YearnAdapter.t.sol index 2292ac4e..3b620310 100644 --- a/packages/foundry/test/vault/integration/yearn/YearnAdapter.t.sol +++ b/packages/foundry/test/vault/integration/adapter/yearn/YearnAdapter.t.sol @@ -5,7 +5,7 @@ pragma solidity ^0.8.15; import { Test } from "forge-std/Test.sol"; -import { YearnAdapter, SafeERC20, IERC20, IERC20Metadata, Math, VaultAPI, IYearnRegistry } from "../../../../src/vault/adapter/yearn/YearnAdapter.sol"; +import { YearnAdapter, SafeERC20, IERC20, IERC20Metadata, Math, VaultAPI, IYearnRegistry } from "../../../../../src/vault/adapter/yearn/YearnAdapter.sol"; import { YearnTestConfigStorage, YearnTestConfig } from "./YearnTestConfigStorage.sol"; import { AbstractAdapterTest, ITestConfigStorage, IAdapter } from "../abstract/AbstractAdapterTest.sol"; diff --git a/packages/foundry/test/vault/integration/yearn/YearnTestConfigStorage.sol b/packages/foundry/test/vault/integration/adapter/yearn/YearnTestConfigStorage.sol similarity index 100% rename from packages/foundry/test/vault/integration/yearn/YearnTestConfigStorage.sol rename to packages/foundry/test/vault/integration/adapter/yearn/YearnTestConfigStorage.sol diff --git a/packages/foundry/test/vault/integration/strategies/compounder/BaseToAssetCompounder.t.sol b/packages/foundry/test/vault/integration/strategies/compounder/BaseToAssetCompounder.t.sol new file mode 100644 index 00000000..0fad96ce --- /dev/null +++ b/packages/foundry/test/vault/integration/strategies/compounder/BaseToAssetCompounder.t.sol @@ -0,0 +1,13 @@ +// SPDX-License-Identifier: GPL-3.0 +// Docgen-SOLC: 0.8.15 + +pragma solidity ^0.8.15; + +import { Test } from "forge-std/Test.sol"; + + +contract BaseToAssetCompounderTest is Test { + function setUp() public { + + } +} \ No newline at end of file diff --git a/packages/hardhat/script/benGov/AddBeneficiariesToRegistry.s.sol b/packages/hardhat/script/benGov/AddBeneficiariesToRegistry.s.sol deleted file mode 100644 index db5a4293..00000000 --- a/packages/hardhat/script/benGov/AddBeneficiariesToRegistry.s.sol +++ /dev/null @@ -1,102 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only -// Docgen-SOLC: 0.8.0 -pragma solidity ^0.8.0; - -import { Script } from "forge-std/Script.sol"; -import { BeneficiaryRegistry } from "../../contracts/core/dao/BeneficiaryRegistry.sol"; - -bytes32 constant DEFAULT_REGION = 0xf2208c967df089f60420785795c0a9ba8896b0f6f1867fa7f1f12ad6f79c1a18; // World - -/// @notice Deploys all contracts needed for beneficiary governance and sets the admin functions -/// Beneficiaries: -/// 0 - Nomination Proposal -/// No Takedowns so far - -/// 2,3 - Monthly Election -/// 4,5 - Quarterly Election -/// 6,7 - Yearly Election -/// 8-18 - Unused -contract AddBeneficiariesToRegistry is Script { - BeneficiaryRegistry internal beneficiaryRegistry = BeneficiaryRegistry(0x4d52E3Ff3B68E8824903e7a5743DD43586aEAF69); - - string[19] internal cids = [ - "QmSzq4gKUk9LhNNCFBVDbkkz26umqu63jx2dXNABBKcgJ9", - "QmNY4QEKhnreDZHW3yiphXn765im5y8RCGrySKzRoXDPTB", - "QmZn2p7xrRxqVGhKAtqjMZSY1Z6pW7A6RD7DiK2xt4nYTB", - "QmUW6peidPvHtbH4P7VNUpPdfaJqv7nmURbpgMcnzUbTtY", - "QmVAur87rJCyGh2ja1Pg4smAqD6MzXtuTXxWkeCkKYrf3n", - "QmNu5LeMyvMkP2sTBZhesXp352ihJUmGTPVeRp42Y9caXJ", - "QmQ3WQK41BozEznephMEh4cwGsRsFuXjFQRM6himtu5kHq", - "QmRnnDnHnF8uv1oour3oU9s1GmKNggzdEqMJueZViAvSAa", - "QmdoFZ6DL49SVmXDBv1zpkCngvr6bU7SUgxLbkh6ZM9L2V", - "QmchtjJsFDWNzLj93gPND1WhYUUGKeznNR6ATUeuaW71uq", - "QmYJ8KGfKo5iG8EbxZxvNoWiNuEMM4Hx5ok4paZ1F27G1w", - "QmTf7c3MaHsBR5rdT5CN3m3yZCuf1XkGxhV81GhJKxempA", - "QmfQ2Ffxw5P6NoKkQvkRoN31mAd65Dp2UBfPMdgzCDCDDx", - "QmVNQeMrLpvnizudP9kZquFSb28CmH9fHddEpdX7wyADxF", - "QmTiSvXRwdGFvPMPtKMurh4FJnzq3bZzws63UuErirSMFq", - "QmSLS4TKBM2M9tXv5CAdFqUQJ9wR8UjjyhtrUxtbRrXE9M", - "QmaisZuRUE1ndZGSzmvg4Uxr7nsi2ciQ1z9VoDCnpGRADt", - "Qmc4qtSHikeYvy4WScJWXtUeX2g3Qf6fC4xoJaU3fugQwu", - "QmcCmaXA3E8nzcBrTejHHjT8svKh6cSTwhKbPNuRyM6uH5" - ]; - - function run() external { - uint256 deployerPrivateKey = vm.envUint("PRIVATE_KEY"); - vm.startBroadcast(deployerPrivateKey); - - beneficiaryRegistry.addBeneficiary( - 0x8626f6940E2eb28930eFb4CeF49B2d1F2C9C1199, - DEFAULT_REGION, - "QmcCmaXA3E8nzcBrTejHHjT8svKh6cSTwhKbPNuRyM6uH5" - ); - beneficiaryRegistry.addBeneficiary( - 0x2546BcD3c84621e976D8185a91A922aE77ECEc30, - DEFAULT_REGION, - "QmSLS4TKBM2M9tXv5CAdFqUQJ9wR8UjjyhtrUxtbRrXE9M" - ); - beneficiaryRegistry.addBeneficiary( - 0xbDA5747bFD65F08deb54cb465eB87D40e51B197E, - DEFAULT_REGION, - "QmaisZuRUE1ndZGSzmvg4Uxr7nsi2ciQ1z9VoDCnpGRADt" - ); - - // for (uint256 i = 9; i < 14; i++) { - // beneficiaryRegistry.addBeneficiary(vm.addr(i + 1), DEFAULT_REGION, cids[i]); - // } - vm.stopBroadcast(); - } -} - -contract RevokeBeneficiariesToRegistry is Script { - BeneficiaryRegistry internal beneficiaryRegistry = BeneficiaryRegistry(0xc9e244f134824fbd070858ab7A82B0e0B298cD5d); - - address[10] internal addresses = [ - 0x0000000000000000000000000000000000000000, - 0x6813Eb9362372EEF6200f3b1dbC3f819671cBA69, - 0x1efF47bc3a10a45D4B230B5d10E37751FE6AA718, - 0xe1AB8145F7E55DC933d51a18c793F901A3A0b276, - 0xE57bFE9F44b819898F47BF37E5AF72a0783e1141, - 0xd41c057fd1c78805AAC12B0A94a405c0461A6FBb, - 0xF1F6619B38A98d6De0800F1DefC0a6399eB6d30C, - 0xF7Edc8FA1eCc32967F827C9043FcAe6ba73afA5c, - 0x4CCeBa2d7D2B4fdcE4304d3e09a1fea9fbEb1528, - 0x3DA8D322CB2435dA26E9C9fEE670f9fB7Fe74E49 - ]; - - address[3] internal addx = [ - 0x7E5F4552091A69125d5DfCb7b8C2659029395Bdf, - 0x2B5AD5c4795c026514f8317c7a215E218DcCD6cF, - 0x6813Eb9362372EEF6200f3b1dbC3f819671cBA69 - ]; - - function run() external { - uint256 deployerPrivateKey = vm.envUint("PRIVATE_KEY"); - vm.startBroadcast(deployerPrivateKey); - - for (uint256 i; i < 3; i++) { - beneficiaryRegistry.revokeBeneficiary(addx[i]); - } - vm.stopBroadcast(); - } -} diff --git a/packages/hardhat/script/benGov/AddNominationProposal.s.sol b/packages/hardhat/script/benGov/AddNominationProposal.s.sol deleted file mode 100644 index a52a1d2c..00000000 --- a/packages/hardhat/script/benGov/AddNominationProposal.s.sol +++ /dev/null @@ -1,62 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only -// Docgen-SOLC: 0.8.0 -pragma solidity ^0.8.0; - -import { Script } from "forge-std/Script.sol"; -import { BeneficiaryGovernance } from "../../contracts/core/dao/BeneficiaryGovernance.sol"; -import { ParticipationReward } from "../../contracts/core/utils/ParticipationReward.sol"; -import { ERC20PresetMinterPauser } from "@openzeppelin/contracts/token/ERC20/presets/ERC20PresetMinterPauser.sol"; - -bytes32 constant DEFAULT_REGION = 0xf2208c967df089f60420785795c0a9ba8896b0f6f1867fa7f1f12ad6f79c1a18; // World - -/// @notice Deploys all contracts needed for beneficiary governance and sets the admin functions -/// Beneficiaries: -/// 0 - Nomination Proposal -/// No Takedowns so far -/// 2,3 - Monthly Election -/// 4,5 - Quarterly Election -/// 6,7 - Yearly Election -/// 8-18 - Unused -contract AddProposal is Script { - BeneficiaryGovernance internal beneficiaryGovernance = - BeneficiaryGovernance(0x1a46F07635d330883eAFdeA6946F95bd21cBf3Bb); // you must enter the new address from deployment here - ERC20PresetMinterPauser internal pop = ERC20PresetMinterPauser(0xc731FB77aFE6EfBE621c06b13478327c681aD334); // you must enter the new address from deployment here - ParticipationReward internal participationReward = ParticipationReward(0xE8d27AbeF7BD4aC929BCAAC050A443dA1cFb4d03); - string[19] internal cids = [ - "QmSzq4gKUk9LhNNCFBVDbkkz26umqu63jx2dXNABBKcgJ9", - "QmNY4QEKhnreDZHW3yiphXn765im5y8RCGrySKzRoXDPTB", - "QmZn2p7xrRxqVGhKAtqjMZSY1Z6pW7A6RD7DiK2xt4nYTB", - "QmUW6peidPvHtbH4P7VNUpPdfaJqv7nmURbpgMcnzUbTtY", - "QmVAur87rJCyGh2ja1Pg4smAqD6MzXtuTXxWkeCkKYrf3n", - "QmNu5LeMyvMkP2sTBZhesXp352ihJUmGTPVeRp42Y9caXJ", - "QmQ3WQK41BozEznephMEh4cwGsRsFuXjFQRM6himtu5kHq", - "QmRnnDnHnF8uv1oour3oU9s1GmKNggzdEqMJueZViAvSAa", - "QmdoFZ6DL49SVmXDBv1zpkCngvr6bU7SUgxLbkh6ZM9L2V", - "QmchtjJsFDWNzLj93gPND1WhYUUGKeznNR6ATUeuaW71uq", - "QmYJ8KGfKo5iG8EbxZxvNoWiNuEMM4Hx5ok4paZ1F27G1w", - "QmTf7c3MaHsBR5rdT5CN3m3yZCuf1XkGxhV81GhJKxempA", - "QmfQ2Ffxw5P6NoKkQvkRoN31mAd65Dp2UBfPMdgzCDCDDx", - "QmVNQeMrLpvnizudP9kZquFSb28CmH9fHddEpdX7wyADxF", - "QmTiSvXRwdGFvPMPtKMurh4FJnzq3bZzws63UuErirSMFq", - "QmSLS4TKBM2M9tXv5CAdFqUQJ9wR8UjjyhtrUxtbRrXE9M", - "QmaisZuRUE1ndZGSzmvg4Uxr7nsi2ciQ1z9VoDCnpGRADt", - "Qmc4qtSHikeYvy4WScJWXtUeX2g3Qf6fC4xoJaU3fugQwu", - "QmcCmaXA3E8nzcBrTejHHjT8svKh6cSTwhKbPNuRyM6uH5" - ]; - - function run() external { - uint256 deployerPrivateKey = vm.envUint("PRIVATE_KEY"); - vm.startBroadcast(deployerPrivateKey); - pop.mint(0xaD5459EBbA9110B0a77ab2c3A7C3F300bBc0bd04, 200000000000000000000000000000000); // put your address here - pop.approve(address(beneficiaryGovernance), 200000000000000000000000000000000); - for (uint256 i; i < 6; i++) { - beneficiaryGovernance.createProposal( - vm.addr(i + 1), - DEFAULT_REGION, - cids[i], - BeneficiaryGovernance.ProposalType.BeneficiaryNominationProposal - ); - } - vm.stopBroadcast(); - } -} diff --git a/packages/hardhat/script/benGov/AddTakedownProposal.s.sol b/packages/hardhat/script/benGov/AddTakedownProposal.s.sol deleted file mode 100644 index 2d15260a..00000000 --- a/packages/hardhat/script/benGov/AddTakedownProposal.s.sol +++ /dev/null @@ -1,56 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only -// Docgen-SOLC: 0.8.0 -pragma solidity ^0.8.0; - -import { Script } from "forge-std/Script.sol"; -import { BeneficiaryGovernance } from "../../contracts/core/dao/BeneficiaryGovernance.sol"; - -bytes32 constant DEFAULT_REGION = 0xf2208c967df089f60420785795c0a9ba8896b0f6f1867fa7f1f12ad6f79c1a18; // World - -/// @notice Deploys all contracts needed for beneficiary governance and sets the admin functions -/// Beneficiaries: -/// 0 - Nomination Proposal -/// No Takedowns so far -/// 2,3 - Monthly Election -/// 4,5 - Quarterly Election -/// 6,7 - Yearly Election -/// 8-18 - Unused -contract AddProposal is Script { - BeneficiaryGovernance internal beneficiaryGovernance = - BeneficiaryGovernance(0xD8bE3e1D20C4620eCF7e5196056f9662C0e40E2B); - - string[19] internal cids = [ - "0x453686cb1d8c953ded406253aac5c37da2488f7c41c323cbe41be9d1e6936136", - "0x02ec2d6316d641dd3bcd5bc8b9c5e1693b4bb18b7b1e45fa237f4d3356d76c06", - "0xa9f111b356ed948342c3458b5422ce85831c32cec8a1ec5aafc6d7be29df2aa2", - "0x5b91dfed7bbf22163dc420883d9dea83245cb63322921ee9d0dd26f0580a4ab5", - "0x65831e033489d630441ba7c1a95206ed20914a9d70f03255a2e247a04c11df11", - "0x084e6f61ebc62f6f7830fd038567d3254eba8a50bf99ce12efc91ead08c76b99", - "0x19531b897392c00859b8fd8a6203646ac3b000a217f45806d23caac22e31a31c", - "0x3344a7be91be7304967d2657d3722ba25eddf3c0c87b546fe3b93b5eac05f127", - "0xe5afcdc556dec784675714313ccbdc6ded2d124559d2ee7c3882e0b2f3b7fac2", - "0xd574aad0b3ab7d4d57a32474bcf697904196a2aa1d1abca0ab4d9c459b3421d8", - "0x93ef576314edf05313724ab46fceb4ffec1a1187790813b5ba6917b3ed837912", - "0x4f054cc99fd70f608beeb51c93e2d7234990b643c77214a3b4f2a4bf8cbb0197", - "0xfd73c7c373e600eeab92276fb7da703dc0b98d70e003d260dd7491705647bcb7", - "0x6875113530fac1aaa15a8432826efeb8430d799ca3c20e1f7ded5ea93d333f5c", - "0x4fdfe2d6eb6a11d5fa67e89bf453c1e50aa9689d445803a1d42e55d93035eb84", - "0x3b6071fb19f07ac422472bb042b2827e5c715ad2a36f8b50e9b4df4299560178", - "0xb7fd8f6704c234984e2a630652a552e8100de0e8ffb06ad12b817851863b6dff", - "0xcbf6f690f0d6aab08193341217f157b32c5df7681fd9f8953a8448fee531b6c4", - "0xcdfebccf1e220187d76b7bd97b5e0b31ab76e54ad00126cd2936da0511cb92fc" - ]; - - function run() external { - vm.startBroadcast(); - for (uint256 i = 1; i < 2; i++) { - beneficiaryGovernance.createProposal( - vm.addr(i + 1), - DEFAULT_REGION, - cids[i], - BeneficiaryGovernance.ProposalType.BeneficiaryTakedownProposal - ); - } - vm.stopBroadcast(); - } -} diff --git a/packages/hardhat/script/benGov/BeneficiaryGovernanceDeployment.s.sol b/packages/hardhat/script/benGov/BeneficiaryGovernanceDeployment.s.sol deleted file mode 100644 index 0de7afd8..00000000 --- a/packages/hardhat/script/benGov/BeneficiaryGovernanceDeployment.s.sol +++ /dev/null @@ -1,100 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only -// Docgen-SOLC: 0.8.0 -pragma solidity ^0.8.0; - -import { Script } from "forge-std/Script.sol"; -import { ERC20PresetMinterPauser } from "@openzeppelin/contracts/token/ERC20/presets/ERC20PresetMinterPauser.sol"; -import { MockERC20PresetMinterPauser } from "../../contracts/mocks/MockERC20PresetMinterPauser.sol"; -import { ACLRegistry, IACLRegistry } from "../../contracts/core/utils/ACLRegistry.sol"; -import { ContractRegistry, IContractRegistry } from "../../contracts/core/utils/ContractRegistry.sol"; -import { KeeperIncentiveV2 } from "../../contracts/core/utils/KeeperIncentiveV2.sol"; -import { ParticipationReward } from "../../contracts/core/utils/ParticipationReward.sol"; -import { RewardsManager } from "../../contracts/core/utils/RewardsManager.sol"; -import { IUniswapV2Router02 } from "@uniswap/v2-periphery/contracts/interfaces/IUniswapV2Router02.sol"; - -import { GovStaking } from "../../contracts/core/dao/GovStaking.sol"; -import { BeneficiaryGovernance } from "../../contracts/core/dao/BeneficiaryGovernance.sol"; -import { BeneficiaryRegistry } from "../../contracts/core/dao/BeneficiaryRegistry.sol"; -import { GrantElections } from "../../contracts/core/dao/GrantElections.sol"; -import { BeneficiaryVaults } from "../../contracts/core/dao/BeneficiaryVaults.sol"; -import { Region } from "../../contracts/core/utils/Region.sol"; - -/// @notice Deploys all contracts needed for beneficiary governance and sets the admin functions -/// Beneficiaries: -/// 0 - Nomination Proposal -/// No Takedowns so far -/// 2,3 - Monthly Election -/// 4,5 - Quarterly Election -/// 6,7 - Yearly Election -/// 8-18 - Unused -contract DeployBenGov is Script { - address internal yourAddress = 0xaD5459EBbA9110B0a77ab2c3A7C3F300bBc0bd04; // enter your address here - ACLRegistry internal aclRegistry; - ContractRegistry internal contractRegistry; - MockERC20PresetMinterPauser internal pop; - KeeperIncentiveV2 internal keeperIncentive; - ParticipationReward internal participationReward; - GovStaking internal staking; - RewardsManager internal rewardsManager; - BeneficiaryGovernance internal beneficiaryGovernance; - BeneficiaryRegistry internal beneficiaryRegistry; - GrantElections internal grantElections; - BeneficiaryVaults internal beneficiaryVault; - Region internal region; - - function run() external returns (address) { - uint256 deployerPrivateKey = vm.envUint("PRIVATE_KEY"); - vm.startBroadcast(deployerPrivateKey); - // Deploying core contracts - aclRegistry = new ACLRegistry(); - aclRegistry.grantRole(keccak256("DAO"), yourAddress); // replace with your signer - aclRegistry.grantRole(keccak256("INCENTIVE_MANAGER_ROLE"), yourAddress); // replace with your signer - aclRegistry.grantRole(keccak256("BeneficiaryGovernance"), yourAddress); // replace with your signer - - contractRegistry = new ContractRegistry(IACLRegistry(address(aclRegistry))); - - pop = new MockERC20PresetMinterPauser("Popcorn", "POP"); - contractRegistry.addContract(keccak256("POP"), address(pop), "1"); - - keeperIncentive = new KeeperIncentiveV2(IContractRegistry(contractRegistry), 0, 0); - contractRegistry.addContract(keccak256("KeeperIncentive"), address(keeperIncentive), "1"); - - // Deploying Additional util contracts - participationReward = new ParticipationReward(IContractRegistry(contractRegistry)); - contractRegistry.addContract(keccak256("ParticipationReward"), address(participationReward), "1"); - - rewardsManager = new RewardsManager( - IContractRegistry(contractRegistry), - IUniswapV2Router02(0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D) - ); - contractRegistry.addContract(keccak256("RewardsManager"), address(rewardsManager), "1"); - aclRegistry.grantRole(keccak256("RewardsManager"), address(rewardsManager)); - keeperIncentive.createIncentive(address(rewardsManager), 0, true, true, address(pop), 1, 0); - keeperIncentive.createIncentive(address(rewardsManager), 0, true, true, address(pop), 1, 0); - - GovStaking staking = new GovStaking(IContractRegistry(contractRegistry)); - contractRegistry.addContract(keccak256("GovStaking"), address(staking), "1"); - - // Deploying Beneficiary Governance contracts - beneficiaryGovernance = new BeneficiaryGovernance(IContractRegistry(contractRegistry)); - contractRegistry.addContract(keccak256("BeneficiaryGovernance"), address(beneficiaryGovernance), "1"); - aclRegistry.grantRole(keccak256("BeneficiaryGovernance"), address(beneficiaryGovernance)); - participationReward.addControllerContract(keccak256("BeneficiaryGovernance"), address(beneficiaryGovernance)); - - beneficiaryRegistry = new BeneficiaryRegistry(IContractRegistry(contractRegistry)); - contractRegistry.addContract(keccak256("BeneficiaryRegistry"), address(beneficiaryRegistry), "1"); - - grantElections = new GrantElections(IContractRegistry(contractRegistry)); - contractRegistry.addContract(keccak256("GrantElections"), address(grantElections), "1"); - aclRegistry.grantRole(keccak256("BeneficiaryGovernance"), address(grantElections)); - participationReward.addControllerContract("GrantElections", address(grantElections)); - - beneficiaryVault = new BeneficiaryVaults(IContractRegistry(contractRegistry)); - - region = new Region(address(beneficiaryVault), IContractRegistry(contractRegistry)); - contractRegistry.addContract(keccak256("Region"), address(region), "1"); - - vm.stopBroadcast(); - return address(pop); - } -} diff --git a/packages/hardhat/script/benGov/CreateFinalizedYearlyElection.s.sol b/packages/hardhat/script/benGov/CreateFinalizedYearlyElection.s.sol deleted file mode 100644 index f930b2cb..00000000 --- a/packages/hardhat/script/benGov/CreateFinalizedYearlyElection.s.sol +++ /dev/null @@ -1,55 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only -// Docgen-SOLC: 0.8.0 -pragma solidity ^0.8.0; - -import { Script } from "forge-std/Script.sol"; -import { GrantElections } from "../../contracts/core/dao/GrantElections.sol"; - -bytes32 constant DEFAULT_REGION = 0xf2208c967df089f60420785795c0a9ba8896b0f6f1867fa7f1f12ad6f79c1a18; // World -uint256 constant DAYS = 60 * 60 * 24; - -/// @notice Deploys all contracts needed for beneficiary governance and sets the admin functions -/// Beneficiaries: -/// 0 - Nomination Proposal -/// No Takedowns so far -/// 2,3 - Monthly Election -/// 4,5 - Quarterly Election -/// 6,7 - Yearly Election -/// 8-18 - Unused -contract CreateElection is Script { - GrantElections internal grantElection = GrantElections(0x3df8bB3119f37eb93921e1afFe8872bC95a5b013); - - GrantElections.ElectionTerm ELECTION_TERM = GrantElections.ElectionTerm.Yearly; - address[] votedBeneficiaries; - uint256[] votes; - - function run() external { - vm.startBroadcast(); - // TODO set config in coordination with Fan correct for testing - grantElection.setConfiguration( - ELECTION_TERM, - 2, // ranking cutoff - 5, // awardees cutoff - false, // VRF - 10 * 60, // Registration period - default - 10 * 60, // Voting period - default - 1 * DAYS, // cooldown period - default - 0, // bondAmount - false, // bond required - 0, // finalization incentive - true, // enabled - GrantElections.ShareType.EqualWeight - ); - grantElection.initialize(ELECTION_TERM, DEFAULT_REGION); - - for (uint256 i = 2; i < 7; i++) { - votedBeneficiaries.push(vm.addr(i + 1)); - votes.push(10 ether); - } - for (uint256 i = 2; i < 12; i++) { - grantElection.registerForElection(vm.addr(i + 1), uint256(ELECTION_TERM)); - grantElection.vote(votedBeneficiaries, votes, uint256(ELECTION_TERM)); - } - vm.stopBroadcast(); - } -} diff --git a/packages/hardhat/script/benGov/CreateOpenQuarterlyElection.s.sol b/packages/hardhat/script/benGov/CreateOpenQuarterlyElection.s.sol deleted file mode 100644 index d2785c66..00000000 --- a/packages/hardhat/script/benGov/CreateOpenQuarterlyElection.s.sol +++ /dev/null @@ -1,56 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only -// Docgen-SOLC: 0.8.0 -pragma solidity ^0.8.0; - -import { Script } from "forge-std/Script.sol"; -import { GrantElections } from "../../contracts/core/dao/GrantElections.sol"; - -bytes32 constant DEFAULT_REGION = 0xf2208c967df089f60420785795c0a9ba8896b0f6f1867fa7f1f12ad6f79c1a18; // World -uint256 constant DAYS = 60 * 60 * 24; - -/// @notice Deploys all contracts needed for beneficiary governance and sets the admin functions -/// Beneficiaries: -/// 0 - Nomination Proposal -/// No Takedowns so far -/// 2,3 - Monthly Election -/// 4,5 - Quarterly Election -/// 6,7 - Yearly Election -/// 8-18 - Unused -contract CreateElection is Script { - GrantElections internal grantElection = GrantElections(0xF8C417F7a4593a561970358375Fc4b2F6F890E9c); - - GrantElections.ElectionTerm ELECTION_TERM = GrantElections.ElectionTerm.Quarterly; - address[] votedBeneficiaries; - uint256[] votes; - - function run() external { - vm.startBroadcast(); - // TODO set config in coordination with Fan correct for testing - grantElection.setConfiguration( - ELECTION_TERM, - 2, // ranking cutoff - 5, // awardees cutoff - false, // VRF - 30 * 60, // Registration period - default - 1 * DAYS, // Voting period - default - 15, // cooldown period - default - 0, // bondAmount - false, // bond required - 0, // finalization incentive - true, // enabled - GrantElections.ShareType.EqualWeight - ); - grantElection.initialize(ELECTION_TERM, DEFAULT_REGION); - - for (uint256 i = 2; i < 7; i++) { - votedBeneficiaries.push(vm.addr(i + 1)); - votes.push(10 ether); - } - for (uint256 i = 2; i < 12; i++) { - grantElection.registerForElection(vm.addr(i + 1), uint256(ELECTION_TERM)); - grantElection.vote(votedBeneficiaries, votes, uint256(ELECTION_TERM)); - } - - vm.stopBroadcast(); - } -} diff --git a/packages/hardhat/script/benGov/CreateRegistrationMonthlyElection.s.sol b/packages/hardhat/script/benGov/CreateRegistrationMonthlyElection.s.sol deleted file mode 100644 index 7c9d51d0..00000000 --- a/packages/hardhat/script/benGov/CreateRegistrationMonthlyElection.s.sol +++ /dev/null @@ -1,46 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only -// Docgen-SOLC: 0.8.0 -pragma solidity ^0.8.0; - -import { Script } from "forge-std/Script.sol"; -import { GrantElections } from "../../contracts/core/dao/GrantElections.sol"; - -bytes32 constant DEFAULT_REGION = 0xf2208c967df089f60420785795c0a9ba8896b0f6f1867fa7f1f12ad6f79c1a18; // World -uint256 constant DAYS = 60 * 60 * 24; - -/// @notice Deploys all contracts needed for beneficiary governance and sets the admin functions -/// Beneficiaries: -/// 0 - Nomination Proposal -/// No Takedowns so far -/// 2,3 - Monthly Election -/// 4,5 - Quarterly Election -/// 6,7 - Yearly Election -/// 8-18 - Unused -contract CreateElection is Script { - GrantElections internal grantElection = GrantElections(0xF8C417F7a4593a561970358375Fc4b2F6F890E9c); - GrantElections.ElectionTerm ELECTION_TERM = GrantElections.ElectionTerm.Monthly; - - function run() external { - vm.startBroadcast(); - // TODO set config in coordination with Fan correct for testing - grantElection.setConfiguration( - ELECTION_TERM, - 2, // ranking cutoff - 5, // awardees cutoff - false, // VRF - 1 * DAYS, // Registration period - default - 15, // Voting period - default - 15, // cooldown period - default - 0, // bondAmount - false, // bond required - 0, // finalization incentive - true, // enabled - GrantElections.ShareType.EqualWeight - ); - grantElection.initialize(ELECTION_TERM, DEFAULT_REGION); - for (uint256 i = 2; i < 11; i++) { - grantElection.registerForElection(vm.addr(i + 1), uint256(ELECTION_TERM)); - } - vm.stopBroadcast(); - } -} diff --git a/packages/hardhat/script/benGov/RemoveBeneficiariesFromRegistry.s.sol b/packages/hardhat/script/benGov/RemoveBeneficiariesFromRegistry.s.sol deleted file mode 100644 index dbfaa3a2..00000000 --- a/packages/hardhat/script/benGov/RemoveBeneficiariesFromRegistry.s.sol +++ /dev/null @@ -1,28 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only -// Docgen-SOLC: 0.8.0 -pragma solidity ^0.8.0; - -import { Script } from "forge-std/Script.sol"; -import { BeneficiaryRegistry } from "../../contracts/core/dao/BeneficiaryRegistry.sol"; - -bytes32 constant DEFAULT_REGION = 0xf2208c967df089f60420785795c0a9ba8896b0f6f1867fa7f1f12ad6f79c1a18; // World - -/// @notice Deploys all contracts needed for beneficiary governance and sets the admin functions -/// Beneficiaries: -/// 0 - Nomination Proposal -/// No Takedowns so far -/// 2,3 - Monthly Election -/// 4,5 - Quarterly Election -/// 6,7 - Yearly Election -/// 8-18 - Unused -contract RemoveBeneficiariesFromRegistry is Script { - BeneficiaryRegistry internal beneficiaryRegistry = BeneficiaryRegistry(0x408Ad169a738E5c4E9dBd2847bc0fD1FF9A8AcBe); - - function run() external { - vm.startBroadcast(); - for (uint256 i; i < 10; i++) { - beneficiaryRegistry.revokeBeneficiary(vm.addr(i + 1)); - } - vm.stopBroadcast(); - } -} diff --git a/packages/hardhat/script/benGov/readme.MD b/packages/hardhat/script/benGov/readme.MD deleted file mode 100644 index eb65b7c6..00000000 --- a/packages/hardhat/script/benGov/readme.MD +++ /dev/null @@ -1,15 +0,0 @@ -Deployment script for Goerli: - -(Note, you will have to update the addresses on these scripts before you run them) - -forge script ./script/benGov/BeneficiaryGovernanceDeployment.s.sol:DeployBenGov -vvv --rpc-url --broadcast --verify - - - -Adding nomination proposals and eligible beneficiaries: - -forge script ./script/benGov/AddNominationProposal.s.sol:AddProposal --rpc-url --chain-id 5 --sender --broadcast - - -forge script ./script/benGov/AddBeneficiariesToRegistry.s.sol:AddBeneficiariesToRegistry --rpc-url --chain-id 5 --sender --broadcast - diff --git a/packages/hardhat/test/forge/batchSlippageSimulation/AbstractSimulateBatchSlippage.t.sol b/packages/hardhat/test/forge/batchSlippageSimulation/AbstractSimulateBatchSlippage.t.sol deleted file mode 100644 index 6ed62db4..00000000 --- a/packages/hardhat/test/forge/batchSlippageSimulation/AbstractSimulateBatchSlippage.t.sol +++ /dev/null @@ -1,97 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0-or-later -pragma solidity ^0.8.0; - -import { Test } from "forge-std/Test.sol"; -import "solmate/src/tokens/ERC20.sol"; - -import "../../../contracts/core/interfaces/IBatchContract.sol"; -import "../../../contracts/externals/interfaces/IBasicIssuanceModule.sol"; -import "../../../contracts/externals/interfaces/ISetToken.sol"; - -//TODO butter needs to be updated so that `setSlippage()` doesnt block any inputs above 2% -abstract contract AbstractSimulateBatchSlippage is Test { - address public constant DAO = 0x92a1cB552d0e177f3A135B4c87A4160C8f2a485f; - IBasicIssuanceModule public basicIssuanceModule = IBasicIssuanceModule(0xd8EF3cACe8b4907117a45B0b125c68560532F94D); - IBatchContract public batchContract; - - // Override with the address of the used batchContract - function setUp() public virtual { - //batchContract = IBatchContract(0x42189f909e1EFA409A4509070dDBc31A592422A8); - } - - function test__simulateMintSlippage() external { - _runSimulation(true); - } - - function test__simulateRedeemSlippage() external { - _runSimulation(false); - } - - function _runSimulation(bool isMint) internal { - bytes32 batchId; - Batch memory currentBatch; - uint256 sourceBatchValue; - uint256 targetBatchValue; - uint256 loss; - - vm.startPrank(DAO); - batchContract.setSlippage(10000, 10000); - - emit log_named_uint("blockNumber", block.number); - emit log_named_uint("blockTime", block.timestamp); - - if (isMint) { - batchId = batchContract.currentMintBatchId(); - currentBatch = batchContract.getBatch(batchId); - - sourceBatchValue = _getUnderlyingBatchValue(address(currentBatch.sourceToken), currentBatch.sourceTokenBalance); - emit log_named_uint("sourceBatchValue", sourceBatchValue); - - batchContract.batchMint(); - - currentBatch = batchContract.getBatch(batchId); - targetBatchValue = _getSetBatchValue(address(currentBatch.targetToken), currentBatch.targetTokenBalance); - emit log_named_uint("targetBatchValue", targetBatchValue); - } else { - batchId = batchContract.currentRedeemBatchId(); - currentBatch = batchContract.getBatch(batchId); - - sourceBatchValue = _getSetBatchValue(address(currentBatch.sourceToken), currentBatch.sourceTokenBalance); - emit log_named_uint("sourceBatchValue", sourceBatchValue); - - batchContract.batchRedeem(); - - currentBatch = batchContract.getBatch(batchId); - targetBatchValue = _getUnderlyingBatchValue(address(currentBatch.targetToken), currentBatch.targetTokenBalance); - emit log_named_uint("targetBatchValue", targetBatchValue); - } - loss = sourceBatchValue - targetBatchValue; - - emit log_named_uint("loss", loss); - - // Divide by 1e18 to get proper slippage - emit log_named_uint("slippage", (loss * 1e18) / sourceBatchValue); - - vm.stopPrank(); - } - - // Override with price calculation depending on what kind of batchContract is used - function _getUnderlyingBatchValue(address underlyingToken, uint256 batchBalance) - internal - view - virtual - returns (uint256 value) - { - // raise balances that are denominated in anything less than 18 decimals - // uint256 decimalDiff = 18 - ERC20(underlyingToken).decimals(); - // value = decimalDiff == 0 ? batchBalance : batchBalance * (10**decimalDiff); - } - - // previous batchContract.currentMintBatchId - function _getSetBatchValue(address setToken, uint256 batchBalance) internal view returns (uint256 value) { - (address[] memory tokenAddresses, uint256[] memory quantities) = basicIssuanceModule - .getRequiredComponentUnitsForIssue(ISetToken(setToken), 1e18); - - value = (batchBalance * batchContract.valueOfComponents(tokenAddresses, quantities)) / 1e18; - } -} diff --git a/packages/hardhat/test/forge/batchSlippageSimulation/SimulateThreeXBatchSlippage.t.sol b/packages/hardhat/test/forge/batchSlippageSimulation/SimulateThreeXBatchSlippage.t.sol deleted file mode 100644 index 3e5d4c7b..00000000 --- a/packages/hardhat/test/forge/batchSlippageSimulation/SimulateThreeXBatchSlippage.t.sol +++ /dev/null @@ -1,22 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0-or-later -pragma solidity ^0.8.0; - -import "solmate/src/tokens/ERC20.sol"; -import "./AbstractSimulateBatchSlippage.t.sol"; - -contract SimulateThreeXBatchSlippage is AbstractSimulateBatchSlippage { - function setUp() public override { - batchContract = IBatchContract(0x42189f909e1EFA409A4509070dDBc31A592422A8); - } - - function _getUnderlyingBatchValue(address underlyingToken, uint256 batchBalance) - internal - view - override - returns (uint256 value) - { - // raise balances that are denominated in anything less than 18 decimals - uint256 decimalDiff = 18 - ERC20(underlyingToken).decimals(); - value = decimalDiff == 0 ? batchBalance : batchBalance * (10**decimalDiff); - } -} diff --git a/packages/hardhat/test/forge/fork/ButterFeeConverter.t.sol b/packages/hardhat/test/forge/fork/ButterFeeConverter.t.sol deleted file mode 100644 index 9aa74a85..00000000 --- a/packages/hardhat/test/forge/fork/ButterFeeConverter.t.sol +++ /dev/null @@ -1,260 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0-or-later -pragma solidity ^0.8.0; - -import { Test } from "forge-std/Test.sol"; -import "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol"; -import "@uniswap/v2-periphery/contracts/interfaces/IUniswapV2Router02.sol"; - -import "../../../contracts/core/defi/butter/ButterFeeConverter.sol"; -import "../../../contracts/core/utils/RewardsManager.sol"; -import "../../../contracts/core/utils/KeeperIncentiveV2.sol"; -import "../../../contracts/core/interfaces/IACLRegistry.sol"; -import "../../../contracts/core/interfaces/IContractRegistry.sol"; -import "../../../contracts/core/interfaces/IButterBatchProcessing.sol"; - -import "../../../contracts/externals/interfaces/Curve3Pool.sol"; -import "../../../contracts/externals/interfaces/ISetToken.sol"; -import "../../../contracts/externals/interfaces/IStreamingFeeModule.sol"; - -// @dev fork block 15008113 -contract ButterFeeConverterTest is Test { - ISetToken internal butter; - IStreamingFeeModule internal streamingFeeModule; - Curve3Pool internal threePool; - IACLRegistry internal aclRegistry; - IContractRegistry internal contractRegistry; - IERC20Metadata internal threeCrv; - ButterFeeConverter internal converter; - IButterBatchProcessing internal butterBatchProcessing; - KeeperIncentiveV2 internal keeperIncentive; - - IERC20Metadata internal preferredStable; - - address constant rewardsManager = address(42); - - address constant BTR_TOKEN = address(0x109d2034e97eC88f50BEeBC778b5A5650F98c124); - address constant SET_STREAMING_FEE_MODULE = address(0x08f866c74205617B6F3903EF481798EcED10cDEC); - address constant THREE_POOL = address(0xbEbc44782C7dB0a1A60Cb6fe97d0b483032FF1C7); - address constant THREE_CRV = address(0x6c3F90f043a72FA612cbac8115EE7e52BDe6E490); - address constant UNISWAP_ROUTER = address(0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D); - - address constant ACL_REGISTRY = address(0x8A41aAa4B467ea545DDDc5759cE3D35984F093f4); - address constant CONTRACT_REGISTRY = address(0x85831b53AFb86889c20aF38e654d871D8b0B7eC3); - - address constant ADMIN = address(0x92a1cB552d0e177f3A135B4c87A4160C8f2a485f); - - event FeeTransferredToRewardsManager( - address indexed keeper, - int128 preferredStableCoinIndex, - uint256 threeCrvAmount, - uint256 keeperTip, - uint256 stableCoinAmount - ); - event PreferredStableCoinIndexUpdated(int128 oldIndex, int128 newIndex); - event MaxSlippageUpdated(uint256 oldSlippage, uint256 newSlippage); - event KeeperTipUpdated(uint256 oldKeeperTip, uint256 newKeeperTip); - - function setUp() public { - uint256 forkId = vm.createSelectFork(vm.rpcUrl("FORKING_RPC_URL"), 15008113); - vm.selectFork(forkId); - - butter = ISetToken(BTR_TOKEN); - streamingFeeModule = IStreamingFeeModule(SET_STREAMING_FEE_MODULE); - aclRegistry = IACLRegistry(ACL_REGISTRY); - contractRegistry = IContractRegistry(CONTRACT_REGISTRY); - threePool = Curve3Pool(THREE_POOL); - threeCrv = IERC20Metadata(THREE_CRV); - butterBatchProcessing = IButterBatchProcessing(contractRegistry.getContract(keccak256("ButterBatchProcessing"))); - - converter = new ButterFeeConverter( - butter, - streamingFeeModule, - contractRegistry, - threePool, - threeCrv, - int128(1), - 250 - ); - keeperIncentive = new KeeperIncentiveV2(contractRegistry, 0, 0); - - preferredStable = IERC20Metadata(threePool.coins(1)); - - vm.startPrank(ADMIN); - - aclRegistry.grantRole(keccak256("ApprovedContract"), address(converter)); - aclRegistry.grantRole(keccak256("INCENTIVE_MANAGER_ROLE"), address(ADMIN)); - - streamingFeeModule.updateFeeRecipient(butter, address(converter)); - butterBatchProcessing.setRedemptionFee(10, address(converter)); - - contractRegistry.addContract(keccak256("RewardsManager"), rewardsManager, keccak256("1")); - contractRegistry.updateContract(keccak256("KeeperIncentive"), address(keeperIncentive), keccak256("2")); - - keeperIncentive.createIncentive(address(converter), 0, true, true, address(threeCrv), 1, 0); - - vm.stopPrank(); - - // Note: This warp seems to hugely increase the gas costs of the Butter batch redeem step... - vm.warp(block.timestamp + 365 * 1 days); - } - - function test_has_contract_name() public { - assertEq(converter.contractName(), keccak256("ButterFeeConverter")); - } - - function test_has_streaming_fee_module() public { - assertEq(address(converter.streamingFeeModule()), address(streamingFeeModule)); - } - - function test_has_btr_token() public { - assertEq(address(converter.butter()), address(butter)); - } - - function test_has_three_pool() public { - assertEq(address(converter.threePool()), address(threePool)); - } - - function test_has_three_crv() public { - assertEq(address(converter.threeCRV()), address(threeCrv)); - } - - function test_has_preferred_coin() public { - assertEq(converter.preferredStableCoinIndex(), 1); - } - - function test_set_preferred_coin() public { - assertEq(converter.preferredStableCoinIndex(), 1); - - vm.expectEmit(false, false, false, true, address(converter)); - emit PreferredStableCoinIndexUpdated(1, 0); - - vm.prank(ADMIN); - converter.setPreferredStableCoinIndex(int128(0)); - - assertEq(converter.preferredStableCoinIndex(), 0); - - vm.expectEmit(false, false, false, true, address(converter)); - emit PreferredStableCoinIndexUpdated(0, 2); - - vm.prank(ADMIN); - converter.setPreferredStableCoinIndex(int128(2)); - - assertEq(converter.preferredStableCoinIndex(), 2); - } - - function test_reverts_on_invalid_index() public { - vm.prank(ADMIN); - vm.expectRevert("Invalid index"); - converter.setPreferredStableCoinIndex(-1); - - vm.prank(ADMIN); - vm.expectRevert("Invalid index"); - converter.setPreferredStableCoinIndex(3); - } - - function test_set_preferred_coin_reverts_on_invalid_role() public { - vm.expectRevert("you dont have the right role"); - converter.setPreferredStableCoinIndex(2); - } - - function test_set_max_slippage() public { - assertEq(converter.maxSlippage(), 50); - - vm.expectEmit(false, false, false, true, address(converter)); - emit MaxSlippageUpdated(50, 1000); - - vm.prank(ADMIN); - converter.setMaxSlippage(1000); - - assertEq(converter.maxSlippage(), 1000); - } - - function test_reverts_on_invalid_slippage() public { - vm.prank(ADMIN); - vm.expectRevert("Invalid slippage"); - converter.setMaxSlippage(10001); - } - - function test_set_max_slippage_reverts_on_invalid_role() public { - vm.expectRevert("you dont have the right role"); - converter.setMaxSlippage(1000); - } - - function test_set_keeper_tip() public { - assertEq(converter.keeperTip(), 250); - - vm.expectEmit(false, false, false, true, address(converter)); - emit KeeperTipUpdated(250, 500); - - vm.prank(ADMIN); - converter.setKeeperTip(500); - - assertEq(converter.keeperTip(), 500); - } - - function test_reverts_on_invalid_tip() public { - vm.prank(ADMIN); - vm.expectRevert("Invalid tip"); - converter.setKeeperTip(10001); - } - - function test_set_keeper_reverts_on_invalid_role() public { - vm.expectRevert("you dont have the right role"); - converter.setKeeperTip(1000); - } - - function test_accrue_fee_usdc() public { - assertEq(preferredStable.balanceOf(address(rewardsManager)), 0); - - vm.expectEmit(true, false, false, true, address(converter)); - emit FeeTransferredToRewardsManager(address(this), 1, 11723638014446496341294, 299212623, 11669292332); - - converter.accrueFee(); - - assertEq(preferredStable.balanceOf(address(rewardsManager)), 11669292332); - } - - function test_accrue_fee_dai() public { - vm.prank(ADMIN); - converter.setPreferredStableCoinIndex(int128(0)); - preferredStable = IERC20Metadata(threePool.coins(0)); - - assertEq(preferredStable.balanceOf(address(rewardsManager)), 0); - - vm.expectEmit(true, false, false, true, address(converter)); - emit FeeTransferredToRewardsManager( - address(this), - 0, - 11723638014446496341294, - 299208332027310994108, - 11669124949065128770215 - ); - - converter.accrueFee(); - - assertEq(preferredStable.balanceOf(address(rewardsManager)), 11669124949065128770215); - } - - function test_accrue_fee_usdt() public { - assertFalse(keeperIncentive.hasClaimableBalance(address(this))); - - vm.prank(ADMIN); - converter.setPreferredStableCoinIndex(int128(2)); - preferredStable = IERC20Metadata(threePool.coins(2)); - - assertEq(preferredStable.balanceOf(address(rewardsManager)), 0); - - vm.expectEmit(true, false, false, true, address(converter)); - emit FeeTransferredToRewardsManager(address(this), 2, 11723638014446496341294, 299590511, 11684029951); - - converter.accrueFee(); - - assertTrue(keeperIncentive.hasClaimableBalance(address(this))); - assertEq(preferredStable.balanceOf(address(rewardsManager)), 11684029951); - } - - function test_scale_value() public { - assertEq(converter.scaleValue(1e36, 18), 1e18); - assertEq(converter.scaleValue(1e36, 6), 1e6); - } -} diff --git a/packages/hardhat/test/forge/fork/FeeEscrow.t.sol b/packages/hardhat/test/forge/fork/FeeEscrow.t.sol deleted file mode 100644 index 74b64bf2..00000000 --- a/packages/hardhat/test/forge/fork/FeeEscrow.t.sol +++ /dev/null @@ -1,164 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0-or-later -pragma solidity ^0.8.0; - -import "forge-std/Test.sol"; - -import "../../../contracts/core/utils/FeeEscrow.sol"; -import "../../../contracts/mocks/MockERC20.sol"; - -// forge test --fork-url $FORKING_RPC_URL --match-contract FeeEscrowTest - -contract FeeEscrowTest is Test { - /* FeeEscrow events */ - event BurnerAddressAdded(address indexed feeToken, address indexed burnerAddress); - event BurnerAddressRemoved(address indexed feeToken); - event BurnerAddressChanged(address indexed feeToken, address indexed burnerAddress); - event FeesBurned(address indexed feeToken, uint256 burnAmount); - - FeeEscrow public feeEscrow; - MockERC20 public feeToken; - address public burnerAddress = address(1234); - address public burnerAddress2 = address(5678); - - function setUp() public { - feeEscrow = new FeeEscrow(); - feeToken = new MockERC20("FeeToken", "FT", 18); - - vm.label(address(feeEscrow), "FeeEscrow"); - vm.label(address(this), "FeeEscrowTest"); - vm.label(address(feeToken), "FeeToken"); - vm.label(burnerAddress, "BurnerAddress"); - vm.label(burnerAddress2, "BurnerAddress2"); - - feeToken.mint(address(this), 1e18); - } - - /* ========== MUTATIVE FUNCTIONS TESTS ========== */ - - /* Adding a burner address to FeeEscrow */ - - function test__addBurnerAddressNoFeeTokenReverts() public { - vm.expectRevert("need to specify fee token"); - feeEscrow.addBurnerAddress(address(0), burnerAddress); - } - - function test__addBurnerAddressNoBurnerAddressReverts() public { - vm.expectRevert("need to specify burner address"); - feeEscrow.addBurnerAddress(address(feeToken), address(0)); - } - - function test__addBurnerAddress() public { - feeEscrow.addBurnerAddress(address(feeToken), burnerAddress); - address feeEscrowBurnerAddress = feeEscrow.getBurnerAddress(address(feeToken)); - assertEq(feeEscrowBurnerAddress, burnerAddress); - assertEq(feeEscrowBurnerAddress, address(1234)); - assertEq(feeEscrow.feeTokenToBurnerAddress(address(feeToken)), burnerAddress); - } - - function test__addBurnerAddressEvent() public { - vm.expectEmit(true, true, false, true, address(feeEscrow)); - emit BurnerAddressAdded(address(feeToken), burnerAddress); - feeEscrow.addBurnerAddress(address(feeToken), burnerAddress); - } - - /* Removing a burner address from FeeEscrow */ - - function test__removeBurnerAddressNoFeeTokenReverts() public { - vm.expectRevert("need to specify fee token"); - feeEscrow.removeBurnerAddress(address(0)); - } - - function test__removeBurnerAddress() public { - feeEscrow.addBurnerAddress(address(feeToken), burnerAddress); - assertEq(feeEscrow.getBurnerAddress(address(feeToken)), burnerAddress); - feeEscrow.removeBurnerAddress(address(feeToken)); - vm.expectRevert("burner address not registered"); - feeEscrow.getBurnerAddress(address(feeToken)); - assertEq(feeEscrow.feeTokenToBurnerAddress(address(feeToken)), address(0)); - } - - function test__removeBurnerAddressEvent() public { - feeEscrow.addBurnerAddress(address(feeToken), burnerAddress); - assertEq(feeEscrow.getBurnerAddress(address(feeToken)), burnerAddress); - vm.expectEmit(true, false, false, true, address(feeEscrow)); - emit BurnerAddressRemoved(address(feeToken)); - feeEscrow.removeBurnerAddress(address(feeToken)); - } - - /* Changing a burner address in FeeEscrow */ - - function test__changeBurnerAddressNoFeeTokenReverts() public { - feeEscrow.addBurnerAddress(address(feeToken), burnerAddress); - assertEq(feeEscrow.getBurnerAddress(address(feeToken)), burnerAddress); - vm.expectRevert("need to specify fee token"); - feeEscrow.changeBurnerAddress(address(0), burnerAddress2); - assertEq(feeEscrow.getBurnerAddress(address(feeToken)), burnerAddress); - } - - function test__changeBurnerAddressNoBurnerAddressReverts() public { - feeEscrow.addBurnerAddress(address(feeToken), burnerAddress); - assertEq(feeEscrow.getBurnerAddress(address(feeToken)), burnerAddress); - vm.expectRevert("need to specify burner address"); - feeEscrow.changeBurnerAddress(address(feeToken), address(0)); - assertEq(feeEscrow.getBurnerAddress(address(feeToken)), burnerAddress); - } - - function test__changeBurnerAddressNoBurnerAddressRegisteredReverts() public { - vm.expectRevert("no burner address registered"); - feeEscrow.changeBurnerAddress(address(feeToken), burnerAddress2); - assertEq(feeEscrow.feeTokenToBurnerAddress(address(feeToken)), address(0)); - } - - function test__changeBurnerAddress() public { - feeEscrow.addBurnerAddress(address(feeToken), burnerAddress); - assertEq(feeEscrow.getBurnerAddress(address(feeToken)), burnerAddress); - feeEscrow.changeBurnerAddress(address(feeToken), burnerAddress2); - assertTrue(feeEscrow.getBurnerAddress(address(feeToken)) != burnerAddress); - assertEq(feeEscrow.getBurnerAddress(address(feeToken)), burnerAddress2); - assertEq(feeEscrow.getBurnerAddress(address(feeToken)), address(5678)); - assertEq(feeEscrow.feeTokenToBurnerAddress(address(feeToken)), burnerAddress2); - } - - function test__changeBurnerAddressEvent() public { - feeEscrow.addBurnerAddress(address(feeToken), burnerAddress); - assertEq(feeEscrow.getBurnerAddress(address(feeToken)), burnerAddress); - vm.expectEmit(true, true, false, true, address(feeEscrow)); - emit BurnerAddressChanged(address(feeToken), burnerAddress2); - feeEscrow.changeBurnerAddress(address(feeToken), burnerAddress2); - assertEq(feeEscrow.getBurnerAddress(address(feeToken)), burnerAddress2); - } - - /* Transferring fees to burner address from FeeEscrow */ - - function test__transferFeesNoBurnerAddressRegisteredReverts() public { - vm.expectRevert("no burner address registered"); - feeEscrow.transferFees(address(feeToken), 1e18); - } - - function test__transferFeesNoBurnAmountReverts() public { - feeEscrow.addBurnerAddress(address(feeToken), burnerAddress); - assertEq(feeEscrow.getBurnerAddress(address(feeToken)), burnerAddress); - vm.expectRevert("burn amount must be > 0"); - feeEscrow.transferFees(address(feeToken), 0); - } - - function test__transferFees() public { - feeEscrow.addBurnerAddress(address(feeToken), burnerAddress); - assertEq(feeEscrow.getBurnerAddress(address(feeToken)), burnerAddress); - uint256 burnAmount = 1e18; - feeToken.approve(address(feeEscrow), burnAmount); - feeEscrow.transferFees(address(feeToken), burnAmount); - assertEq(feeToken.balanceOf(burnerAddress), burnAmount); - assertEq(feeToken.balanceOf(address(this)), 0); - } - - function test__transferFeesEvent() public { - feeEscrow.addBurnerAddress(address(feeToken), burnerAddress); - uint256 burnAmount = 1e18; - feeToken.approve(address(feeEscrow), burnAmount); - vm.expectEmit(true, false, false, true, address(feeEscrow)); - emit FeesBurned(address(feeToken), burnAmount); - feeEscrow.transferFees(address(feeToken), burnAmount); - assertEq(feeToken.balanceOf(burnerAddress), burnAmount); - } -}