From b90a5b963836adf51d7bc31c54bc2468cb0bb0af Mon Sep 17 00:00:00 2001 From: 0xKoaj Date: Wed, 6 Nov 2024 16:48:24 -0300 Subject: [PATCH 1/5] feat: deploy with forta --- script/DeployFortaBase.sol | 130 ++++++++ script/DeployFortaPolygon.sol | 134 ++++++++ .../ERC4626DelayedWithdrawalAdapter.sol | 138 +++++++++ .../instances/beefy/BeefyStrategy.sol | 89 ++++++ .../instances/beefy/BeefyStrategyFactory.sol | 93 ++++++ .../erc4626/ERC4626DelayedStrategy.sol | 86 +++++ .../connector/ERC4626DelayedConnector.sol | 293 ++++++++++++++++++ 7 files changed, 963 insertions(+) create mode 100644 script/DeployFortaBase.sol create mode 100644 script/DeployFortaPolygon.sol create mode 100644 src/delayed-withdrawal-adapter/ERC4626DelayedWithdrawalAdapter.sol create mode 100644 src/strategies/instances/beefy/BeefyStrategy.sol create mode 100644 src/strategies/instances/beefy/BeefyStrategyFactory.sol create mode 100644 src/strategies/instances/erc4626/ERC4626DelayedStrategy.sol create mode 100644 src/strategies/layers/connector/ERC4626DelayedConnector.sol diff --git a/script/DeployFortaBase.sol b/script/DeployFortaBase.sol new file mode 100644 index 00000000..031043b9 --- /dev/null +++ b/script/DeployFortaBase.sol @@ -0,0 +1,130 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.13; + +import "forge-std/Script.sol"; +import "@balmy/earn-core/strategy-registry/EarnStrategyRegistry.sol"; +import "@balmy/earn-core/vault/FirewalledEarnVault.sol"; +import "@balmy/earn-core/nft-descriptor/EarnNFTDescriptor.sol"; +import "src/companion/FirewalledEarnVaultCompanion.sol"; + +import "src/global-registry/GlobalEarnRegistry.sol"; +import "src/fee-manager/FeeManager.sol"; +import "src/guardian-manager/GuardianManager.sol"; +import "src/liquidity-mining-manager/LiquidityMiningManager.sol"; +import "src/delayed-withdrawal-manager/DelayedWithdrawalManager.sol"; +import "src/tos-manager/TOSManager.sol"; + +import "src/strategies/instances/erc4626/ERC4626Strategy.sol"; +import "src/strategies/instances/erc4626/ERC4626StrategyFactory.sol"; + +import "src/strategies/instances/beefy/BeefyStrategy.sol"; +import "src/strategies/instances/beefy/BeefyStrategyFactory.sol"; + +import "@forta/firewall/ExternalFirewall.sol"; +import "@forta/firewall/FirewallAccess.sol"; +import "@forta/firewall/SecurityValidator.sol"; + +contract DeployFortaPolygon is Script { + function run() external { + uint256 deployerPrivateKey = vm.envUint("PRIVATE_KEY"); + address deployer = 0xB86dA339B88D9697fa3ACC55EDd378e002676E01; + address admin = vm.envAddress("GOVERNOR"); + vm.startBroadcast(deployerPrivateKey); + + EarnStrategyRegistry strategyRegistry = new EarnStrategyRegistry(); + EarnNFTDescriptor nftDescriptor = new EarnNFTDescriptor(); + address[] memory initialAdmins = new address[](2); + initialAdmins[0] = admin; + initialAdmins[1] = deployer; + + // FORTA + bytes32 attesterControllerId = bytes32("123"); + SecurityValidator validator = new SecurityValidator(address(this)); + FirewallAccess firewallAccess = new FirewallAccess(address(this)); + IExternalFirewall externalFirewall = + new ExternalFirewall(validator, ICheckpointHook(address(0)), attesterControllerId, firewallAccess); + FirewalledEarnVault vault = + new FirewalledEarnVault(strategyRegistry, admin, initialAdmins, nftDescriptor, externalFirewall); + + FirewalledEarnVaultCompanion companion = new FirewalledEarnVaultCompanion( + 0xED306e38BB930ec9646FF3D917B2e513a97530b1, + address(0), + admin, + IPermit2(0x000000000022D473030F116dDEE9F6B43aC78BA3), + externalFirewall + ); + + FeeManager feeManager = new FeeManager(admin, initialAdmins, initialAdmins, Fees(0, 0, 500, 1000)); + GuardianManager guardianManager = + new GuardianManager(strategyRegistry, admin, initialAdmins, initialAdmins, initialAdmins, initialAdmins); + DelayedWithdrawalManager delayedWithdrawalManager = new DelayedWithdrawalManager(vault); + + TOSManager tosManager = new TOSManager(strategyRegistry, admin, initialAdmins); + + LiquidityMiningManager liquidityMiningManager = new LiquidityMiningManager(strategyRegistry, admin, initialAdmins); + + GlobalEarnRegistry globalRegistry = new GlobalEarnRegistry(deployer); + globalRegistry.setAddress(keccak256("FEE_MANAGER"), address(feeManager)); + globalRegistry.setAddress(keccak256("GUARDIAN_MANAGER"), address(guardianManager)); + globalRegistry.setAddress(keccak256("LIQUIDITY_MINING_MANAGER"), address(liquidityMiningManager)); + globalRegistry.setAddress(keccak256("DELAYED_WITHDRAWAL_MANAGER"), address(delayedWithdrawalManager)); + globalRegistry.setAddress(keccak256("TOS_MANAGER"), address(tosManager)); + globalRegistry.transferOwnership(admin); + + ERC4626Strategy erc4626 = new ERC4626Strategy(); + ERC4626StrategyFactory erc4626Factory = new ERC4626StrategyFactory(erc4626); + + /* + - Morpho + - Stablecoin + - Reward: ETH + */ + (,StrategyId strategyIdMorpho) = erc4626Factory.cloneAndRegister( + vault.STRATEGY_REGISTRY(), + admin, + vault, + globalRegistry, + IERC4626(0xc1256Ae5FF1cf2719D4937adb3bbCCab2E00A2Ca), // Moonwell Flagship USDC + "", + "", + "", + "" + ); + + // Needs ETH to create the campaign + + liquidityMiningManager.setCampaign{value: 0.00001 ether}( + strategyIdMorpho, Token.NATIVE_TOKEN, 0.000000000001 ether, 10000000 + ); + + + + BeefyStrategy beefyStrategy = new BeefyStrategy(); + BeefyStrategyFactory beefyStrategyFactory = new BeefyStrategyFactory(beefyStrategy); + /* + - Beefy + - WETH + - TOS + */ + (,StrategyId strategyIdBeefy) = beefyStrategyFactory.cloneAndRegister( + vault.STRATEGY_REGISTRY(), + admin, + vault, + globalRegistry, + IBeefyVault(0x367A8DF45A165fD0A4405b4c45773d50E427322F), // WETH (superOETHb Market) + 0x4200000000000000000000000000000000000006, // WETH + "", + "", + "", + "" + ); + + bytes32 GROUP = keccak256("guardian_tos"); + tosManager.updateTOS( + GROUP, + "By selecting a Guardian, you acknowledge and accept the terms and conditions outlined in our Earn service's Terms of Use available at https://app.balmy.xyz/terms_of_use.pdf, including those related to the accuracy of data provided by third-party oracles and the actions taken by the Guardian in response to potential threats. Please note: Balmy does not guarantee the accuracy, completeness, or reliability of information from third-party yield providers. The Guardian operates on a best-effort basis to protect your funds in the event of a hack, and actions taken by the Guardian may impact the performance of your investment. Rescue fees may apply if funds are saved. Timing and decisions regarding redepositing or relocating funds are made in good faith, and Balmy is not liable for any financial losses resulting from these actions. Each Guardian may have its own specific terms of service, which will be presented to you before you engage with their service. By selecting a Guardian and proceeding, you agree to those terms. By signing this I acknowledge and agree to the above terms and conditions." + ); + tosManager.assignStrategyToGroup(strategyIdBeefy, GROUP); + vm.stopBroadcast(); + } +} diff --git a/script/DeployFortaPolygon.sol b/script/DeployFortaPolygon.sol new file mode 100644 index 00000000..df746b71 --- /dev/null +++ b/script/DeployFortaPolygon.sol @@ -0,0 +1,134 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.13; + +import "forge-std/Script.sol"; +import "@balmy/earn-core/strategy-registry/EarnStrategyRegistry.sol"; +import "@balmy/earn-core/vault/FirewalledEarnVault.sol"; +import "@balmy/earn-core/nft-descriptor/EarnNFTDescriptor.sol"; +import "src/companion/FirewalledEarnVaultCompanion.sol"; + +import "src/global-registry/GlobalEarnRegistry.sol"; +import "src/fee-manager/FeeManager.sol"; +import "src/guardian-manager/GuardianManager.sol"; +import "src/liquidity-mining-manager/LiquidityMiningManager.sol"; +import "src/delayed-withdrawal-manager/DelayedWithdrawalManager.sol"; +import "src/tos-manager/TOSManager.sol"; + +import "src/strategies/instances/erc4626/ERC4626DelayedStrategy.sol"; +import "src/delayed-withdrawal-adapter/ERC4626DelayedWithdrawalAdapter.sol"; + +import "src/strategies/instances/aave-v3/AaveV3Strategy.sol"; +import "src/strategies/instances/aave-v3/AaveV3StrategyFactory.sol"; + +import "@forta/firewall/ExternalFirewall.sol"; +import "@forta/firewall/FirewallAccess.sol"; +import "@forta/firewall/SecurityValidator.sol"; + +contract DeployFortaPolygon is Script { + function run() external { + uint256 deployerPrivateKey = vm.envUint("PRIVATE_KEY"); + address deployer = 0xB86dA339B88D9697fa3ACC55EDd378e002676E01; + address admin = vm.envAddress("GOVERNOR"); + vm.startBroadcast(deployerPrivateKey); + + EarnStrategyRegistry strategyRegistry = new EarnStrategyRegistry(); + EarnNFTDescriptor nftDescriptor = EarnNFTDescriptor(0xAe84114Aa7a651F765B24c74f3A0f8E64921C3D9); // Recycle NFT descriptor + address[] memory initialAdmins = new address[](2); + initialAdmins[0] = admin; + initialAdmins[1] = deployer; + + // FORTA + bytes32 attesterControllerId = bytes32("123"); + SecurityValidator validator = new SecurityValidator(address(this)); + FirewallAccess firewallAccess = new FirewallAccess(address(this)); + IExternalFirewall externalFirewall = + new ExternalFirewall(validator, ICheckpointHook(address(0)), attesterControllerId, firewallAccess); + FirewalledEarnVault vault = + new FirewalledEarnVault(strategyRegistry, admin, initialAdmins, nftDescriptor, externalFirewall); + + FirewalledEarnVaultCompanion companion = new FirewalledEarnVaultCompanion( + 0xED306e38BB930ec9646FF3D917B2e513a97530b1, + address(0), + admin, + IPermit2(0x000000000022D473030F116dDEE9F6B43aC78BA3), + externalFirewall + ); + + FeeManager feeManager = new FeeManager(admin, initialAdmins, initialAdmins, Fees(0, 0, 500, 1000)); + GuardianManager guardianManager = + new GuardianManager(strategyRegistry, admin, initialAdmins, initialAdmins, initialAdmins, initialAdmins); + DelayedWithdrawalManager delayedWithdrawalManager = new DelayedWithdrawalManager(vault); + + TOSManager tosManager = new TOSManager(strategyRegistry, admin, initialAdmins); + + LiquidityMiningManager liquidityMiningManager = new LiquidityMiningManager(strategyRegistry, admin, initialAdmins); + + GlobalEarnRegistry globalRegistry = new GlobalEarnRegistry(deployer); + globalRegistry.setAddress(keccak256("FEE_MANAGER"), address(feeManager)); + globalRegistry.setAddress(keccak256("GUARDIAN_MANAGER"), address(guardianManager)); + globalRegistry.setAddress(keccak256("LIQUIDITY_MINING_MANAGER"), address(liquidityMiningManager)); + globalRegistry.setAddress(keccak256("DELAYED_WITHDRAWAL_MANAGER"), address(delayedWithdrawalManager)); + globalRegistry.setAddress(keccak256("TOS_MANAGER"), address(tosManager)); + globalRegistry.transferOwnership(admin); + + /* + - Yearn + - WMATIC + - Delayed withdraw + - Rewards: Stablecoin + */ + + ERC4626DelayedWithdrawalAdapter delayedWithdrawalAdapter = new ERC4626DelayedWithdrawalAdapter( + globalRegistry, + 0x28F53bA70E5c8ce8D03b1FaD41E9dF11Bb646c36, // Yearn v3 WMATIC-A + 7200 // 7200 seconds are 2 hours + ); + + ERC4626DelayedStrategy delayedStrategy = new ERC4626DelayedStrategy( + globalRegistry, + vault, + 0x28F53bA70E5c8ce8D03b1FaD41E9dF11Bb646c36, + "Delayed Yearn v3 WMATIC-A", + delayedWithdrawalAdapter + ); + + vault.STRATEGY_REGISTRY().registerStrategy(deployer, delayedStrategy); + + // Needs DAI to create the campaign + /* + liquidityMiningManager.setCampaign( + delayedStrategy.strategyId(), 0x8f3Cf7ad23Cd3CaDbD9735AFf958023239c6A063, 1, 1000000 + ); + */ + + /* + - Aave v3 + - Stablecoin + - TOS + */ + AaveV3StrategyFactory aaveV3Factory = AaveV3StrategyFactory(0x5463cfE67361da69dffeBfB1e526de0d4bDCDE78); // Recycle Aave v3 factory + (, StrategyId strategyId) = aaveV3Factory.cloneAndRegister( + AaveV3StrategyImmutableData( + strategyRegistry, + admin, + vault, + globalRegistry, + IAToken(0x82E64f49Ed5EC1bC6e43DAD4FC8Af9bb3A2312EE), // aDai + IAaveV3Pool(0x794a61358D6845594F94dc1DB02A252b5b4814aD), // Aave v3 Pool + IAaveV3Rewards(0x929EC64c34a17401F460460D4B9390518E5B473e), // Aave v3 Rewards + "", + "", + "", + "" + ) + ); + + bytes32 GROUP = keccak256("guardian_tos"); + tosManager.updateTOS( + GROUP, + "By selecting a Guardian, you acknowledge and accept the terms and conditions outlined in our Earn service's Terms of Use available at https://app.balmy.xyz/terms_of_use.pdf, including those related to the accuracy of data provided by third-party oracles and the actions taken by the Guardian in response to potential threats. Please note: Balmy does not guarantee the accuracy, completeness, or reliability of information from third-party yield providers. The Guardian operates on a best-effort basis to protect your funds in the event of a hack, and actions taken by the Guardian may impact the performance of your investment. Rescue fees may apply if funds are saved. Timing and decisions regarding redepositing or relocating funds are made in good faith, and Balmy is not liable for any financial losses resulting from these actions. Each Guardian may have its own specific terms of service, which will be presented to you before you engage with their service. By selecting a Guardian and proceeding, you agree to those terms. By signing this I acknowledge and agree to the above terms and conditions." + ); + tosManager.assignStrategyToGroup(strategyId, GROUP); + vm.stopBroadcast(); + } +} diff --git a/src/delayed-withdrawal-adapter/ERC4626DelayedWithdrawalAdapter.sol b/src/delayed-withdrawal-adapter/ERC4626DelayedWithdrawalAdapter.sol new file mode 100644 index 00000000..2a198ac7 --- /dev/null +++ b/src/delayed-withdrawal-adapter/ERC4626DelayedWithdrawalAdapter.sol @@ -0,0 +1,138 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.22; + +import { + IDelayedWithdrawalAdapter, + IDelayedWithdrawalManager, + IEarnVault +} from "src/interfaces/IDelayedWithdrawalAdapter.sol"; +import { IEarnStrategy, StrategyId } from "@balmy/earn-core/interfaces/IEarnStrategy.sol"; +import { Math } from "@openzeppelin/contracts/utils/math/Math.sol"; +import { SafeERC20, IERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; +import { IGlobalEarnRegistry } from "src/interfaces/IGlobalEarnRegistry.sol"; +import { IERC4626, IERC20 } from "@openzeppelin/contracts/interfaces/IERC4626.sol"; + +struct Request { + uint256 amount; + uint256 deadline; +} + +contract ERC4626DelayedWithdrawalAdapter is IDelayedWithdrawalAdapter { + using Math for uint256; + using SafeERC20 for IERC20; + + /// @notice The id for the Delayed Withdrawal Manager + bytes32 public constant DELAYED_WITHDRAWAL_MANAGER = keccak256("DELAYED_WITHDRAWAL_MANAGER"); + + IGlobalEarnRegistry public immutable registry; + + address internal immutable _farmToken; + uint256 internal immutable _delay; + mapping(uint256 positionId => Request[] requestIds) internal _pendingWithdrawals; + + constructor(IGlobalEarnRegistry _registry, address farmToken_, uint256 delay_) { + registry = _registry; + _farmToken = farmToken_; + _delay = delay_; + } + + function supportsInterface(bytes4 interfaceId) external pure override returns (bool) { + return interfaceId == type(IDelayedWithdrawalAdapter).interfaceId; + } + + function estimatedPendingFunds(uint256 positionId, address) external view override returns (uint256 pendingAmount) { + Request[] memory requests = _pendingWithdrawals[positionId]; + // slither-disable-next-line incorrect-equality + if (requests.length == 0) { + return 0; + } + + for (uint256 i; i < requests.length; ++i) { + if (requests[i].deadline > block.timestamp) { + pendingAmount += IERC4626(_farmToken).previewRedeem(requests[i].amount); + } + } + } + + function withdrawableFunds(uint256 positionId, address) external view override returns (uint256 withdrawableAmount) { + Request[] memory requests = _pendingWithdrawals[positionId]; + // slither-disable-next-line incorrect-equality + if (requests.length == 0) { + return 0; + } + + for (uint256 i; i < requests.length; ++i) { + if (requests[i].deadline <= block.timestamp) { + withdrawableAmount += IERC4626(_farmToken).previewRedeem(requests[i].amount); + } + } + } + + function initiateDelayedWithdrawal(uint256 positionId, address, uint256 amount) external override { + IDelayedWithdrawalManager delayedWithdrawalManager = manager(); + IEarnVault vault_ = delayedWithdrawalManager.VAULT(); + (, IEarnStrategy strategy) = vault_.positionsStrategy(positionId); + if (msg.sender != address(strategy)) { + revert UnauthorizedPositionStrategy(); + } + Request[] storage requests = _pendingWithdrawals[positionId]; + bool needsToRegister = requests.length == 0; + requests.push(Request({ amount: amount, deadline: block.timestamp + _delay })); + if (needsToRegister) { + delayedWithdrawalManager.registerDelayedWithdraw(positionId, (IERC4626(_farmToken).asset())); + } + } + + // slither-disable-start assembly + function withdraw( + uint256 positionId, + address, + address recipient + ) + external + override + onlyManager + returns (uint256 withdrawn, uint256 stillPending) + { + Request[] memory requests = _pendingWithdrawals[positionId]; + if (requests.length == 0) { + return (0, 0); + } + uint256 numberOfRequestsPending; + uint256 farmTokenAmountToWithdraw; + for (uint256 i; i < requests.length; ++i) { + if (requests[i].deadline > block.timestamp) { + stillPending += IERC4626(_farmToken).previewRedeem(requests[i].amount); + if (numberOfRequestsPending != i) { + requests[numberOfRequestsPending] = requests[i]; + } + ++numberOfRequestsPending; + } else { + farmTokenAmountToWithdraw += requests[i].amount; + } + } + + if (numberOfRequestsPending != requests.length) { + // Resize the array + // solhint-disable-next-line no-inline-assembly + assembly { + mstore(requests, numberOfRequestsPending) + } + } + _pendingWithdrawals[positionId] = requests; + withdrawn = IERC4626(_farmToken).redeem(farmTokenAmountToWithdraw, recipient, address(this)); + } + + function manager() public view returns (IDelayedWithdrawalManager) { + return IDelayedWithdrawalManager(registry.getAddressOrFail(DELAYED_WITHDRAWAL_MANAGER)); + } + + function vault() public view override returns (IEarnVault) { + return manager().VAULT(); + } + + modifier onlyManager() { + if (msg.sender != address(manager())) revert UnauthorizedDelayedWithdrawalManager(); + _; + } +} diff --git a/src/strategies/instances/beefy/BeefyStrategy.sol b/src/strategies/instances/beefy/BeefyStrategy.sol new file mode 100644 index 00000000..0f16f742 --- /dev/null +++ b/src/strategies/instances/beefy/BeefyStrategy.sol @@ -0,0 +1,89 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.22; + +import { IERC4626, IERC20 } from "@openzeppelin/contracts/interfaces/IERC4626.sol"; +import { IEarnStrategy, StrategyId, IEarnVault } from "@balmy/earn-core/interfaces/IEarnStrategy.sol"; +import { Clone } from "@clones/Clone.sol"; +import { IGlobalEarnRegistry } from "../../../interfaces/IGlobalEarnRegistry.sol"; +import { BeefyConnector, IBeefyVault } from "../../layers/connector/BeefyConnector.sol"; +import { ExternalFees } from "../../layers/fees/ExternalFees.sol"; +import { ExternalGuardian } from "../../layers/guardian/ExternalGuardian.sol"; +import { ExternalTOSCreationValidation } from "../../layers/creation-validation/ExternalTOSCreationValidation.sol"; +import { ExternalLiquidityMining } from "../../layers/liquidity-mining/ExternalLiquidityMining.sol"; +import { BaseStrategy } from "../base/BaseStrategy.sol"; + +contract BeefyStrategy is + BaseStrategy, + Clone, + BeefyConnector, + ExternalLiquidityMining, + ExternalFees, + ExternalGuardian, + ExternalTOSCreationValidation +{ + /// @inheritdoc IEarnStrategy + string public description; + + // slither-disable-next-line reentrancy-benign + function init( + bytes calldata tosData, + bytes calldata guardianData, + bytes calldata feesData, + string calldata description_ + ) + external + initializer + { + _creationValidation_init(tosData); + _guardian_init(guardianData); + _fees_init(feesData); + _connector_init(); + description = description_; + } + + function strategyId() + public + view + override(ExternalLiquidityMining, ExternalFees, ExternalGuardian, ExternalTOSCreationValidation, BaseStrategy) + returns (StrategyId) + { + return BaseStrategy.strategyId(); + } + + // Immutable params: + // 1. Earn Vault (20B) + // 2. Global Registry (20B) + // 3. Beefy vault (20B) + // 4. Asset (20B) + + function _earnVault() internal pure override returns (IEarnVault) { + return IEarnVault(_getArgAddress(0)); + } + + function globalRegistry() + public + pure + override(ExternalLiquidityMining, ExternalFees, ExternalGuardian, ExternalTOSCreationValidation) + returns (IGlobalEarnRegistry) + { + return IGlobalEarnRegistry(_getArgAddress(20)); + } + + function beefyVault() public view virtual override returns (IBeefyVault) { + return IBeefyVault(_getArgAddress(40)); + } + + function _asset() internal pure override returns (IERC20) { + return IERC20(_getArgAddress(60)); + } + + // slither-disable-next-line naming-convention,dead-code + function _fees_underlying_asset() internal view override returns (address asset) { + return _connector_asset(); + } + + // slither-disable-next-line naming-convention,dead-code + function _guardian_rescueFee() internal view override returns (uint16) { + return _getFees().rescueFee; + } +} diff --git a/src/strategies/instances/beefy/BeefyStrategyFactory.sol b/src/strategies/instances/beefy/BeefyStrategyFactory.sol new file mode 100644 index 00000000..239b1873 --- /dev/null +++ b/src/strategies/instances/beefy/BeefyStrategyFactory.sol @@ -0,0 +1,93 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.22; + +import { StrategyId, IEarnVault, IEarnStrategyRegistry } from "@balmy/earn-core/interfaces/IEarnStrategy.sol"; +import { IEarnBalmyStrategy } from "../../../interfaces/IEarnBalmyStrategy.sol"; +import { IGlobalEarnRegistry } from "../../../interfaces/IGlobalEarnRegistry.sol"; +import { BaseStrategyFactory } from "../base/BaseStrategyFactory.sol"; +import { BeefyStrategy, IBeefyVault } from "./BeefyStrategy.sol"; + +contract BeefyStrategyFactory is BaseStrategyFactory { + constructor(BeefyStrategy implementation_) BaseStrategyFactory(implementation_) { } + + function cloneAndRegister( + IEarnStrategyRegistry strategyRegistry, + address owner, + IEarnVault earnVault, + IGlobalEarnRegistry globalRegistry, + IBeefyVault beefyVault, + address asset, + bytes calldata tosData, + bytes calldata guardianData, + bytes calldata feesData, + string calldata description + ) + external + returns (BeefyStrategy clone, StrategyId strategyId) + { + bytes memory immutableData = abi.encodePacked(earnVault, globalRegistry, beefyVault, asset); + (IEarnBalmyStrategy clone_, StrategyId stratId) = _cloneAndRegister(strategyRegistry, owner, immutableData); + clone = BeefyStrategy(payable(address(clone_))); + strategyId = stratId; + clone.init(tosData, guardianData, feesData, description); + } + + function clone2AndRegister( + IEarnStrategyRegistry strategyRegistry, + address owner, + IEarnVault earnVault, + IGlobalEarnRegistry globalRegistry, + IBeefyVault beefyVault, + address asset, + bytes calldata tosData, + bytes calldata guardianData, + bytes calldata feesData, + string calldata description + ) + external + returns (BeefyStrategy clone, StrategyId strategyId) + { + bytes memory immutableData = abi.encodePacked(earnVault, globalRegistry, beefyVault, asset); + (IEarnBalmyStrategy clone_, StrategyId stratId) = _clone2AndRegister(strategyRegistry, owner, immutableData); + clone = BeefyStrategy(payable(address(clone_))); + strategyId = stratId; + clone.init(tosData, guardianData, feesData, description); + } + + function clone3AndRegister( + IEarnStrategyRegistry strategyRegistry, + address owner, + IEarnVault earnVault, + IGlobalEarnRegistry globalRegistry, + IBeefyVault beefyVault, + address asset, + bytes32 salt, + bytes calldata tosData, + bytes calldata guardianData, + bytes calldata feesData, + string calldata description + ) + external + returns (BeefyStrategy clone, StrategyId strategyId) + { + bytes memory immutableData = abi.encodePacked(earnVault, globalRegistry, beefyVault, asset); + (IEarnBalmyStrategy clone_, StrategyId stratId) = _clone3AndRegister(strategyRegistry, owner, immutableData, salt); + clone = BeefyStrategy(payable(address(clone_))); + strategyId = stratId; + clone.init(tosData, guardianData, feesData, description); + } + + function addressOfClone2( + IEarnVault earnVault, + IGlobalEarnRegistry globalRegistry, + IBeefyVault beefyVault, + address asset + ) + external + view + returns (address clone) + { + bytes memory data = abi.encodePacked(earnVault, globalRegistry, beefyVault, asset); + return _addressOfClone2(data); + } +} diff --git a/src/strategies/instances/erc4626/ERC4626DelayedStrategy.sol b/src/strategies/instances/erc4626/ERC4626DelayedStrategy.sol new file mode 100644 index 00000000..e9875500 --- /dev/null +++ b/src/strategies/instances/erc4626/ERC4626DelayedStrategy.sol @@ -0,0 +1,86 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.22; + +import { + IEarnStrategy, StrategyId, IEarnVault, SpecialWithdrawalCode +} from "@balmy/earn-core/interfaces/IEarnStrategy.sol"; +import { IDelayedWithdrawalAdapter } from "src/delayed-withdrawal-manager/DelayedWithdrawalManager.sol"; +import { IGlobalEarnRegistry } from "src/interfaces/IGlobalEarnRegistry.sol"; +import { ExternalFees } from "../../layers/fees/ExternalFees.sol"; +import { ExternalTOSCreationValidation } from "../../layers/creation-validation/ExternalTOSCreationValidation.sol"; +import { ExternalLiquidityMining } from "../../layers/liquidity-mining/ExternalLiquidityMining.sol"; +import { BaseDelayedStrategy } from "../base/BaseDelayedStrategy.sol"; +import { IERC4626, IERC20 } from "@openzeppelin/contracts/interfaces/IERC4626.sol"; +import { ERC4626DelayedConnector } from "../../layers/connector/ERC4626DelayedConnector.sol"; + +contract ERC4626DelayedStrategy is + BaseDelayedStrategy, + ERC4626DelayedConnector, + ExternalLiquidityMining, + ExternalFees, + ExternalTOSCreationValidation +{ + /// @inheritdoc IEarnStrategy + string public description; + + // slither-disable-next-line naming-convention + IDelayedWithdrawalAdapter internal immutable __delayedWithdrawalAdapter; + IGlobalEarnRegistry internal immutable _globalRegistry; + IEarnVault internal immutable _vault; + address internal immutable _farmToken; + + constructor( + // General + IGlobalEarnRegistry globalRegistry_, + IEarnVault vault_, + address farmToken_, + string memory description_, + IDelayedWithdrawalAdapter delayedWithdrawalAdapter_ + ) { + _globalRegistry = globalRegistry_; + _vault = vault_; + _farmToken = farmToken_; + description = description_; + __delayedWithdrawalAdapter = delayedWithdrawalAdapter_; + maxApproveVault(); + } + + // slither-disable-next-line naming-convention,dead-code + function _fees_underlying_asset() internal view override returns (address asset) { + return _connector_asset(); + } + + function globalRegistry() + public + view + override(ExternalFees, ExternalLiquidityMining, ExternalTOSCreationValidation) + returns (IGlobalEarnRegistry) + { + return _globalRegistry; + } + + function strategyId() + public + view + override(BaseDelayedStrategy, ExternalFees, ExternalLiquidityMining, ExternalTOSCreationValidation) + returns (StrategyId) + { + return BaseDelayedStrategy.strategyId(); + } + + function _earnVault() internal view virtual override returns (IEarnVault) { + return _vault; + } + + function ERC4626Vault() public view virtual override returns (IERC4626) { + return IERC4626(_farmToken); + } + + function _asset() internal view virtual override returns (IERC20) { + return IERC20(ERC4626Vault().asset()); + } + + function _delayedWithdrawalAdapter() internal view virtual override returns (IDelayedWithdrawalAdapter) { + return __delayedWithdrawalAdapter; + } +} diff --git a/src/strategies/layers/connector/ERC4626DelayedConnector.sol b/src/strategies/layers/connector/ERC4626DelayedConnector.sol new file mode 100644 index 00000000..afefec81 --- /dev/null +++ b/src/strategies/layers/connector/ERC4626DelayedConnector.sol @@ -0,0 +1,293 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.22; + +import { IERC4626, IERC20 } from "@openzeppelin/contracts/interfaces/IERC4626.sol"; +import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; +import { Math } from "@openzeppelin/contracts/utils/math/Math.sol"; +import { Initializable } from "@openzeppelin/contracts/proxy/utils/Initializable.sol"; +import { StrategyId, IEarnStrategy, SpecialWithdrawalCode } from "@balmy/earn-core/interfaces/IEarnStrategy.sol"; +import { IDelayedWithdrawalAdapter } from "src/delayed-withdrawal-manager/DelayedWithdrawalManager.sol"; + +import { SpecialWithdrawal } from "@balmy/earn-core/types/SpecialWithdrawals.sol"; +import { BaseConnector } from "./base/BaseConnector.sol"; + +abstract contract ERC4626DelayedConnector is BaseConnector, Initializable { + using SafeERC20 for IERC20; + using SafeERC20 for IERC4626; + using Math for uint256; + + /// @notice Returns the address of the ERC4626 vault + // slither-disable-next-line naming-convention + function ERC4626Vault() public view virtual returns (IERC4626); + function _asset() internal view virtual returns (IERC20); + + function _delayedWithdrawalAdapter() internal view virtual returns (IDelayedWithdrawalAdapter); + + /// @notice Performs a max approve to the vault, so that we can deposit without any worries + function maxApproveVault() public { + _asset().forceApprove(address(ERC4626Vault()), type(uint256).max); + } + + // slither-disable-next-line naming-convention,dead-code + function _connector_init() internal { + maxApproveVault(); + } + + // slither-disable-next-line naming-convention,dead-code + function _connector_asset() internal view override returns (address) { + return address(_asset()); + } + + // slither-disable-next-line naming-convention,dead-code + function _connector_allTokens() internal view override returns (address[] memory tokens) { + tokens = new address[](1); + tokens[0] = _connector_asset(); + } + + // slither-disable-next-line naming-convention,dead-code + function _connector_supportedWithdrawals() + internal + view + virtual + override + returns (IEarnStrategy.WithdrawalType[] memory withdrawalTypes) + { + withdrawalTypes = new IEarnStrategy.WithdrawalType[](1); + withdrawalTypes[0] = IEarnStrategy.WithdrawalType.DELAYED; + } + + // slither-disable-next-line naming-convention,dead-code + function _connector_isDepositTokenSupported(address depositToken) internal view virtual override returns (bool) { + return depositToken == _connector_asset() || depositToken == address(ERC4626Vault()); + } + + // slither-disable-next-line naming-convention,dead-code + function _connector_supportedDepositTokens() internal view virtual override returns (address[] memory supported) { + supported = new address[](2); + supported[0] = _connector_asset(); + supported[1] = address(ERC4626Vault()); + } + + // slither-disable-next-line naming-convention,dead-code + function _connector_maxDeposit(address depositToken) internal view virtual override returns (uint256) { + IERC4626 vault = ERC4626Vault(); + if (depositToken == _connector_asset()) { + return vault.maxDeposit(address(this)); + } else if (depositToken == address(vault)) { + return type(uint256).max; + } else { + revert InvalidDepositToken(depositToken); + } + } + + // slither-disable-next-line naming-convention,dead-code + function _connector_totalBalances() + internal + view + virtual + override + returns (address[] memory tokens, uint256[] memory balances) + { + IERC4626 vault = ERC4626Vault(); + tokens = new address[](1); + tokens[0] = _connector_asset(); + balances = new uint256[](1); + balances[0] = vault.previewRedeem(vault.balanceOf(address(this))); + } + + // slither-disable-next-line naming-convention,dead-code + function _connector_isSpecialWithdrawalSupported(SpecialWithdrawalCode withdrawalCode) + internal + view + virtual + override + returns (bool) + { + return withdrawalCode == SpecialWithdrawal.WITHDRAW_ASSET_FARM_TOKEN_BY_AMOUNT + || withdrawalCode == SpecialWithdrawal.WITHDRAW_ASSET_FARM_TOKEN_BY_ASSET_AMOUNT; + } + + // slither-disable-next-line naming-convention,dead-code + function _connector_supportedSpecialWithdrawals() + internal + view + virtual + override + returns (SpecialWithdrawalCode[] memory codes) + { + codes = new SpecialWithdrawalCode[](2); + codes[0] = SpecialWithdrawal.WITHDRAW_ASSET_FARM_TOKEN_BY_AMOUNT; + codes[1] = SpecialWithdrawal.WITHDRAW_ASSET_FARM_TOKEN_BY_ASSET_AMOUNT; + } + + // slither-disable-next-line naming-convention,dead-code + function _connector_maxWithdraw() + internal + view + virtual + override + returns (address[] memory tokens, uint256[] memory withdrawable) + { + tokens = new address[](1); + tokens[0] = _connector_asset(); + withdrawable = new uint256[](1); + withdrawable[0] = ERC4626Vault().maxWithdraw(address(this)); + } + + // slither-disable-next-line naming-convention,dead-code + function _connector_delayedWithdrawalAdapter(address token) + internal + view + virtual + override + returns (IDelayedWithdrawalAdapter) + // solhint-disable-next-line no-empty-blocks + { + if (token == _connector_asset()) { + return _delayedWithdrawalAdapter(); + } + return IDelayedWithdrawalAdapter(address(0)); + } + + // slither-disable-next-line naming-convention,dead-code + function _connector_assetYieldCoefficient() internal view override returns (uint256 coefficient, uint256 multiplier) { + multiplier = 1e18; + IERC4626 vault = ERC4626Vault(); + uint256 shares = vault.totalSupply(); + if (shares == 0) { + return (multiplier, multiplier); + } + uint256 assets = vault.totalAssets(); + coefficient = assets.mulDiv(multiplier, shares, Math.Rounding.Floor); + } + + // slither-disable-next-line naming-convention,dead-code + function _connector_rewardEmissionsPerSecondPerAsset() + internal + pure + override + returns (uint256[] memory, uint256[] memory) + { + return (new uint256[](0), new uint256[](0)); + } + + // slither-disable-next-line naming-convention,dead-code + function _connector_totalAssetsInFarm() internal view override returns (uint256) { + return ERC4626Vault().totalAssets(); + } + + // slither-disable-next-line naming-convention,dead-code + function _connector_deposit( + address depositToken, + uint256 depositAmount + ) + internal + virtual + override + returns (uint256 assetsDeposited) + { + IERC4626 vault = ERC4626Vault(); + if (depositToken == _connector_asset()) { + uint256 shares = vault.deposit(depositAmount, address(this)); + // Note: there might be slippage or a deposit fee, so we will re-calculate the amount of assets deposited + // based on the amount of shares minted + return vault.previewRedeem(shares); + } else if (depositToken == address(vault)) { + return vault.previewRedeem(depositAmount); + } else { + revert InvalidDepositToken(depositToken); + } + } + + // slither-disable-next-line naming-convention,dead-code + function _connector_withdraw( + uint256 positionId, + address[] memory tokens, + uint256[] memory toWithdraw, + address + ) + internal + virtual + override + returns (IEarnStrategy.WithdrawalType[] memory withdrawalTypes) + { + IERC4626 vault = ERC4626Vault(); + // Note: we assume params are consistent and valid because they were validated by the EarnVault + uint256 shares = vault.previewWithdraw(toWithdraw[0]); + IERC20(address(vault)).safeTransfer(address(_connector_delayedWithdrawalAdapter(tokens[0])), shares); + + _connector_delayedWithdrawalAdapter(tokens[0]).initiateDelayedWithdrawal(positionId, tokens[0], shares); + + withdrawalTypes = _connector_supportedWithdrawals(); + } + + // slither-disable-next-line naming-convention,dead-code + function _connector_specialWithdraw( + uint256, + SpecialWithdrawalCode withdrawalCode, + uint256[] calldata toWithdraw, + bytes calldata, + address recipient + ) + internal + override + returns ( + uint256[] memory balanceChanges, + address[] memory actualWithdrawnTokens, + uint256[] memory actualWithdrawnAmounts, + bytes memory result + ) + { + IERC4626 vault = ERC4626Vault(); + balanceChanges = new uint256[](1); + actualWithdrawnTokens = new address[](1); + actualWithdrawnAmounts = new uint256[](1); + result = ""; + + if (withdrawalCode == SpecialWithdrawal.WITHDRAW_ASSET_FARM_TOKEN_BY_AMOUNT) { + uint256 shares = toWithdraw[0]; + uint256 assets = vault.previewRedeem(shares); + vault.safeTransfer(recipient, shares); + balanceChanges[0] = assets; + actualWithdrawnTokens[0] = address(vault); + actualWithdrawnAmounts[0] = shares; + } else if (withdrawalCode == SpecialWithdrawal.WITHDRAW_ASSET_FARM_TOKEN_BY_ASSET_AMOUNT) { + uint256 assets = toWithdraw[0]; + uint256 shares = vault.previewWithdraw(assets); + vault.safeTransfer(recipient, shares); + balanceChanges[0] = assets; + actualWithdrawnTokens[0] = address(vault); + actualWithdrawnAmounts[0] = shares; + } else { + revert InvalidSpecialWithdrawalCode(withdrawalCode); + } + } + + // slither-disable-next-line naming-convention,dead-code + function _connector_migrateToNewStrategy( + IEarnStrategy newStrategy, + bytes calldata + ) + internal + virtual + override + returns (bytes memory) + { + IERC4626 vault = ERC4626Vault(); + uint256 balance = vault.balanceOf(address(this)); + vault.safeTransfer(address(newStrategy), balance); + return abi.encode(balance); + } + + // slither-disable-next-line naming-convention,dead-code + function _connector_strategyRegistered( + StrategyId strategyId, + IEarnStrategy oldStrategy, + bytes calldata migrationData + ) + internal + virtual + override + // solhint-disable-next-line no-empty-blocks + { } +} From 17eacc1a6377ef6b07f4147159e93add3416b1f8 Mon Sep 17 00:00:00 2001 From: 0xKoaj Date: Wed, 6 Nov 2024 17:07:00 -0300 Subject: [PATCH 2/5] chore: fix lint --- script/DeployFortaBase.sol | 12 +++++------- script/DeployFortaPolygon.sol | 6 ++++-- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/script/DeployFortaBase.sol b/script/DeployFortaBase.sol index 031043b9..56745a35 100644 --- a/script/DeployFortaBase.sol +++ b/script/DeployFortaBase.sol @@ -79,7 +79,7 @@ contract DeployFortaPolygon is Script { - Stablecoin - Reward: ETH */ - (,StrategyId strategyIdMorpho) = erc4626Factory.cloneAndRegister( + (, StrategyId strategyIdMorpho) = erc4626Factory.cloneAndRegister( vault.STRATEGY_REGISTRY(), admin, vault, @@ -92,12 +92,10 @@ contract DeployFortaPolygon is Script { ); // Needs ETH to create the campaign - - liquidityMiningManager.setCampaign{value: 0.00001 ether}( - strategyIdMorpho, Token.NATIVE_TOKEN, 0.000000000001 ether, 10000000 - ); - + liquidityMiningManager.setCampaign{ value: 0.00001 ether }( + strategyIdMorpho, Token.NATIVE_TOKEN, 0.000000000001 ether, 10_000_000 + ); BeefyStrategy beefyStrategy = new BeefyStrategy(); BeefyStrategyFactory beefyStrategyFactory = new BeefyStrategyFactory(beefyStrategy); @@ -106,7 +104,7 @@ contract DeployFortaPolygon is Script { - WETH - TOS */ - (,StrategyId strategyIdBeefy) = beefyStrategyFactory.cloneAndRegister( + (, StrategyId strategyIdBeefy) = beefyStrategyFactory.cloneAndRegister( vault.STRATEGY_REGISTRY(), admin, vault, diff --git a/script/DeployFortaPolygon.sol b/script/DeployFortaPolygon.sol index df746b71..ac9d3217 100644 --- a/script/DeployFortaPolygon.sol +++ b/script/DeployFortaPolygon.sol @@ -32,7 +32,8 @@ contract DeployFortaPolygon is Script { vm.startBroadcast(deployerPrivateKey); EarnStrategyRegistry strategyRegistry = new EarnStrategyRegistry(); - EarnNFTDescriptor nftDescriptor = EarnNFTDescriptor(0xAe84114Aa7a651F765B24c74f3A0f8E64921C3D9); // Recycle NFT descriptor + EarnNFTDescriptor nftDescriptor = EarnNFTDescriptor(0xAe84114Aa7a651F765B24c74f3A0f8E64921C3D9); // Recycle NFT + // descriptor address[] memory initialAdmins = new address[](2); initialAdmins[0] = admin; initialAdmins[1] = deployer; @@ -106,7 +107,8 @@ contract DeployFortaPolygon is Script { - Stablecoin - TOS */ - AaveV3StrategyFactory aaveV3Factory = AaveV3StrategyFactory(0x5463cfE67361da69dffeBfB1e526de0d4bDCDE78); // Recycle Aave v3 factory + AaveV3StrategyFactory aaveV3Factory = AaveV3StrategyFactory(0x5463cfE67361da69dffeBfB1e526de0d4bDCDE78); // Recycle + // Aave v3 factory (, StrategyId strategyId) = aaveV3Factory.cloneAndRegister( AaveV3StrategyImmutableData( strategyRegistry, From 1fde8215579db22c17640c30ade1644fcefd1fcb Mon Sep 17 00:00:00 2001 From: 0xKoaj Date: Thu, 7 Nov 2024 16:27:18 -0300 Subject: [PATCH 3/5] feat: add validator and checkpoints to base --- script/DeployFortaBase.sol | 41 ++++++++++++++++++++++++++++++++------ 1 file changed, 35 insertions(+), 6 deletions(-) diff --git a/script/DeployFortaBase.sol b/script/DeployFortaBase.sol index 56745a35..77993d7a 100644 --- a/script/DeployFortaBase.sol +++ b/script/DeployFortaBase.sol @@ -23,6 +23,10 @@ import "src/strategies/instances/beefy/BeefyStrategyFactory.sol"; import "@forta/firewall/ExternalFirewall.sol"; import "@forta/firewall/FirewallAccess.sol"; import "@forta/firewall/SecurityValidator.sol"; +import "@forta/firewall/FirewallRouter.sol"; + +import "@forta/firewall/interfaces/ICheckpointHook.sol"; +import "@forta/firewall/interfaces/Checkpoint.sol"; contract DeployFortaPolygon is Script { function run() external { @@ -38,22 +42,47 @@ contract DeployFortaPolygon is Script { initialAdmins[1] = deployer; // FORTA - bytes32 attesterControllerId = bytes32("123"); - SecurityValidator validator = new SecurityValidator(address(this)); - FirewallAccess firewallAccess = new FirewallAccess(address(this)); - IExternalFirewall externalFirewall = + bytes32 attesterControllerId = bytes32("9999"); + ISecurityValidator validator = ISecurityValidator(0xc9b1AeD0895Dd647A82e35Cafff421B6CcFe690C); + FirewallAccess firewallAccess = new FirewallAccess(deployer); + ExternalFirewall externalFirewall = new ExternalFirewall(validator, ICheckpointHook(address(0)), attesterControllerId, firewallAccess); + + FirewallRouter firewallRouter = new FirewallRouter(externalFirewall, firewallAccess); FirewalledEarnVault vault = - new FirewalledEarnVault(strategyRegistry, admin, initialAdmins, nftDescriptor, externalFirewall); + new FirewalledEarnVault(strategyRegistry, admin, initialAdmins, nftDescriptor, firewallRouter); FirewalledEarnVaultCompanion companion = new FirewalledEarnVaultCompanion( 0xED306e38BB930ec9646FF3D917B2e513a97530b1, address(0), admin, IPermit2(0x000000000022D473030F116dDEE9F6B43aC78BA3), - externalFirewall + firewallRouter ); + /// will renounce later below + firewallAccess.grantRole(FIREWALL_ADMIN_ROLE, deployer); + firewallAccess.grantRole(PROTOCOL_ADMIN_ROLE, deployer); + firewallAccess.grantRole(ATTESTER_MANAGER_ROLE, deployer); + + /// let protected contract execute checkpoints on the external firewall + firewallAccess.grantRole(CHECKPOINT_EXECUTOR_ROLE, address(vault)); + firewallAccess.grantRole(CHECKPOINT_EXECUTOR_ROLE, address(companion)); + firewallAccess.grantRole(CHECKPOINT_EXECUTOR_ROLE, address(firewallRouter)); + + /// set the trusted attester: + /// this will be necessary when "foo()" receives an attested call later. + firewallAccess.grantRole(TRUSTED_ATTESTER_ROLE, deployer); + + Checkpoint memory checkpoint = + Checkpoint({ threshold: 0, refStart: 4, refEnd: 36, activation: Activation.AlwaysActive, trustedOrigin: false }); + + externalFirewall.setCheckpoint(vault.withdraw.selector, checkpoint); + externalFirewall.setCheckpoint(vault.specialWithdraw.selector, checkpoint); + + externalFirewall.setCheckpoint(companion.withdraw.selector, checkpoint); + externalFirewall.setCheckpoint(companion.specialWithdraw.selector, checkpoint); + FeeManager feeManager = new FeeManager(admin, initialAdmins, initialAdmins, Fees(0, 0, 500, 1000)); GuardianManager guardianManager = new GuardianManager(strategyRegistry, admin, initialAdmins, initialAdmins, initialAdmins, initialAdmins); From 20e3272b52535b78d11b7ab4d8144849cd4c6c1d Mon Sep 17 00:00:00 2001 From: 0xKoaj Date: Fri, 8 Nov 2024 13:32:22 -0300 Subject: [PATCH 4/5] feat: add validator and checkpoints to polygon --- script/DeployFortaPolygon.sol | 46 ++++++++++++++++++++++++++++------- 1 file changed, 37 insertions(+), 9 deletions(-) diff --git a/script/DeployFortaPolygon.sol b/script/DeployFortaPolygon.sol index ac9d3217..5943e5fb 100644 --- a/script/DeployFortaPolygon.sol +++ b/script/DeployFortaPolygon.sol @@ -23,6 +23,10 @@ import "src/strategies/instances/aave-v3/AaveV3StrategyFactory.sol"; import "@forta/firewall/ExternalFirewall.sol"; import "@forta/firewall/FirewallAccess.sol"; import "@forta/firewall/SecurityValidator.sol"; +import "@forta/firewall/FirewallRouter.sol"; + +import "@forta/firewall/interfaces/ICheckpointHook.sol"; +import "@forta/firewall/interfaces/Checkpoint.sol"; contract DeployFortaPolygon is Script { function run() external { @@ -39,22 +43,46 @@ contract DeployFortaPolygon is Script { initialAdmins[1] = deployer; // FORTA - bytes32 attesterControllerId = bytes32("123"); - SecurityValidator validator = new SecurityValidator(address(this)); - FirewallAccess firewallAccess = new FirewallAccess(address(this)); - IExternalFirewall externalFirewall = + bytes32 attesterControllerId = bytes32("9999"); + ISecurityValidator validator = ISecurityValidator(0xc9b1AeD0895Dd647A82e35Cafff421B6CcFe690C); + FirewallAccess firewallAccess = new FirewallAccess(deployer); + ExternalFirewall externalFirewall = new ExternalFirewall(validator, ICheckpointHook(address(0)), attesterControllerId, firewallAccess); + + FirewallRouter firewallRouter = new FirewallRouter(externalFirewall, firewallAccess); FirewalledEarnVault vault = - new FirewalledEarnVault(strategyRegistry, admin, initialAdmins, nftDescriptor, externalFirewall); + new FirewalledEarnVault(strategyRegistry, admin, initialAdmins, nftDescriptor, firewallRouter); FirewalledEarnVaultCompanion companion = new FirewalledEarnVaultCompanion( 0xED306e38BB930ec9646FF3D917B2e513a97530b1, address(0), admin, IPermit2(0x000000000022D473030F116dDEE9F6B43aC78BA3), - externalFirewall + firewallRouter ); + /// will renounce later below + firewallAccess.grantRole(FIREWALL_ADMIN_ROLE, deployer); + firewallAccess.grantRole(PROTOCOL_ADMIN_ROLE, deployer); + firewallAccess.grantRole(ATTESTER_MANAGER_ROLE, deployer); + + /// let protected contract execute checkpoints on the external firewall + firewallAccess.grantRole(CHECKPOINT_EXECUTOR_ROLE, address(vault)); + firewallAccess.grantRole(CHECKPOINT_EXECUTOR_ROLE, address(companion)); + firewallAccess.grantRole(CHECKPOINT_EXECUTOR_ROLE, address(firewallRouter)); + + /// set the trusted attester: + /// this will be necessary when "foo()" receives an attested call later. + firewallAccess.grantRole(TRUSTED_ATTESTER_ROLE, deployer); + + Checkpoint memory checkpoint = + Checkpoint({ threshold: 0, refStart: 4, refEnd: 36, activation: Activation.AlwaysActive, trustedOrigin: false }); + + externalFirewall.setCheckpoint(vault.withdraw.selector, checkpoint); + externalFirewall.setCheckpoint(vault.specialWithdraw.selector, checkpoint); + + externalFirewall.setCheckpoint(companion.withdraw.selector, checkpoint); + externalFirewall.setCheckpoint(companion.specialWithdraw.selector, checkpoint); FeeManager feeManager = new FeeManager(admin, initialAdmins, initialAdmins, Fees(0, 0, 500, 1000)); GuardianManager guardianManager = new GuardianManager(strategyRegistry, admin, initialAdmins, initialAdmins, initialAdmins, initialAdmins); @@ -98,10 +126,10 @@ contract DeployFortaPolygon is Script { // Needs DAI to create the campaign /* liquidityMiningManager.setCampaign( - delayedStrategy.strategyId(), 0x8f3Cf7ad23Cd3CaDbD9735AFf958023239c6A063, 1, 1000000 + delayedStrategy.strategyId(), 0x8f3Cf7ad23Cd3CaDbD9735AFf958023239c6A063, 1, 100000 ); - */ - + +*/ /* - Aave v3 - Stablecoin From 788292b9cdd911fb2baf7a47a6937207b3689375 Mon Sep 17 00:00:00 2001 From: 0xKoaj Date: Thu, 21 Nov 2024 11:04:29 -0300 Subject: [PATCH 5/5] refactor: some paths --- script/DeployFortaBase.sol | 10 +- script/DeployFortaPolygon.sol | 10 +- .../instances/beefy/BeefyStrategy.sol | 8 +- .../erc4626/ERC4626DelayedStrategy.sol | 6 +- .../instances/ERC4626DelayedStrategy.t.sol | 206 ++++++++++++++++++ 5 files changed, 224 insertions(+), 16 deletions(-) create mode 100644 test/integration/strategies/instances/ERC4626DelayedStrategy.t.sol diff --git a/script/DeployFortaBase.sol b/script/DeployFortaBase.sol index 77993d7a..5ba7ca59 100644 --- a/script/DeployFortaBase.sol +++ b/script/DeployFortaBase.sol @@ -8,12 +8,14 @@ import "@balmy/earn-core/nft-descriptor/EarnNFTDescriptor.sol"; import "src/companion/FirewalledEarnVaultCompanion.sol"; import "src/global-registry/GlobalEarnRegistry.sol"; -import "src/fee-manager/FeeManager.sol"; -import "src/guardian-manager/GuardianManager.sol"; -import "src/liquidity-mining-manager/LiquidityMiningManager.sol"; +import "src/strategies/layers/fees/external/FeeManager.sol"; +import "src/strategies/layers/guardian/external/GuardianManager.sol"; +import "src/strategies/layers/liquidity-mining/external/LiquidityMiningManager.sol"; import "src/delayed-withdrawal-manager/DelayedWithdrawalManager.sol"; -import "src/tos-manager/TOSManager.sol"; +import "src/strategies/layers/creation-validation/external/TOSManager.sol"; +import "src/strategies/instances/erc4626/ERC4626DelayedStrategy.sol"; +import "src/strategies/layers/connector/lido/ERC4626DelayedWithdrawalAdapter.sol"; import "src/strategies/instances/erc4626/ERC4626Strategy.sol"; import "src/strategies/instances/erc4626/ERC4626StrategyFactory.sol"; diff --git a/script/DeployFortaPolygon.sol b/script/DeployFortaPolygon.sol index 5943e5fb..9f17171e 100644 --- a/script/DeployFortaPolygon.sol +++ b/script/DeployFortaPolygon.sol @@ -8,14 +8,14 @@ import "@balmy/earn-core/nft-descriptor/EarnNFTDescriptor.sol"; import "src/companion/FirewalledEarnVaultCompanion.sol"; import "src/global-registry/GlobalEarnRegistry.sol"; -import "src/fee-manager/FeeManager.sol"; -import "src/guardian-manager/GuardianManager.sol"; -import "src/liquidity-mining-manager/LiquidityMiningManager.sol"; +import "src/strategies/layers/fees/external/FeeManager.sol"; +import "src/strategies/layers/guardian/external/GuardianManager.sol"; +import "src/strategies/layers/liquidity-mining/external/LiquidityMiningManager.sol"; import "src/delayed-withdrawal-manager/DelayedWithdrawalManager.sol"; -import "src/tos-manager/TOSManager.sol"; +import "src/strategies/layers/creation-validation/external/TOSManager.sol"; import "src/strategies/instances/erc4626/ERC4626DelayedStrategy.sol"; -import "src/delayed-withdrawal-adapter/ERC4626DelayedWithdrawalAdapter.sol"; +import "src/strategies/layers/connector/lido/ERC4626DelayedWithdrawalAdapter.sol"; import "src/strategies/instances/aave-v3/AaveV3Strategy.sol"; import "src/strategies/instances/aave-v3/AaveV3StrategyFactory.sol"; diff --git a/src/strategies/instances/beefy/BeefyStrategy.sol b/src/strategies/instances/beefy/BeefyStrategy.sol index 0f16f742..84a10879 100644 --- a/src/strategies/instances/beefy/BeefyStrategy.sol +++ b/src/strategies/instances/beefy/BeefyStrategy.sol @@ -6,10 +6,10 @@ import { IEarnStrategy, StrategyId, IEarnVault } from "@balmy/earn-core/interfac import { Clone } from "@clones/Clone.sol"; import { IGlobalEarnRegistry } from "../../../interfaces/IGlobalEarnRegistry.sol"; import { BeefyConnector, IBeefyVault } from "../../layers/connector/BeefyConnector.sol"; -import { ExternalFees } from "../../layers/fees/ExternalFees.sol"; -import { ExternalGuardian } from "../../layers/guardian/ExternalGuardian.sol"; -import { ExternalTOSCreationValidation } from "../../layers/creation-validation/ExternalTOSCreationValidation.sol"; -import { ExternalLiquidityMining } from "../../layers/liquidity-mining/ExternalLiquidityMining.sol"; +import { ExternalFees } from "../../layers/fees/external/ExternalFees.sol"; +import { ExternalGuardian } from "../../layers/guardian/external/ExternalGuardian.sol"; +import { ExternalTOSCreationValidation } from "../../layers/creation-validation/external/ExternalTOSCreationValidation.sol"; +import { ExternalLiquidityMining } from "../../layers/liquidity-mining/external/ExternalLiquidityMining.sol"; import { BaseStrategy } from "../base/BaseStrategy.sol"; contract BeefyStrategy is diff --git a/src/strategies/instances/erc4626/ERC4626DelayedStrategy.sol b/src/strategies/instances/erc4626/ERC4626DelayedStrategy.sol index e9875500..c58d093c 100644 --- a/src/strategies/instances/erc4626/ERC4626DelayedStrategy.sol +++ b/src/strategies/instances/erc4626/ERC4626DelayedStrategy.sol @@ -6,9 +6,9 @@ import { } from "@balmy/earn-core/interfaces/IEarnStrategy.sol"; import { IDelayedWithdrawalAdapter } from "src/delayed-withdrawal-manager/DelayedWithdrawalManager.sol"; import { IGlobalEarnRegistry } from "src/interfaces/IGlobalEarnRegistry.sol"; -import { ExternalFees } from "../../layers/fees/ExternalFees.sol"; -import { ExternalTOSCreationValidation } from "../../layers/creation-validation/ExternalTOSCreationValidation.sol"; -import { ExternalLiquidityMining } from "../../layers/liquidity-mining/ExternalLiquidityMining.sol"; +import { ExternalFees } from "src/strategies/layers/fees/external/ExternalFees.sol"; +import { ExternalTOSCreationValidation } from "src/strategies/layers/creation-validation/external/ExternalTOSCreationValidation.sol"; +import { ExternalLiquidityMining } from "src/strategies/layers/liquidity-mining/external/ExternalLiquidityMining.sol"; import { BaseDelayedStrategy } from "../base/BaseDelayedStrategy.sol"; import { IERC4626, IERC20 } from "@openzeppelin/contracts/interfaces/IERC4626.sol"; import { ERC4626DelayedConnector } from "../../layers/connector/ERC4626DelayedConnector.sol"; diff --git a/test/integration/strategies/instances/ERC4626DelayedStrategy.t.sol b/test/integration/strategies/instances/ERC4626DelayedStrategy.t.sol new file mode 100644 index 00000000..b8999d0a --- /dev/null +++ b/test/integration/strategies/instances/ERC4626DelayedStrategy.t.sol @@ -0,0 +1,206 @@ +import { IERC20 } from "@openzeppelin/contracts/interfaces/IERC20.sol"; +import { PRBTest } from "@prb/test/PRBTest.sol"; +import { StdUtils } from "forge-std/StdUtils.sol"; +import { StdCheats } from "forge-std/StdCheats.sol"; +import { ERC4626DelayedStrategy } from "src/strategies/instances/erc4626/ERC4626DelayedStrategy.sol"; +import { FirewalledEarnVault } from "@balmy/earn-core/vault/FirewalledEarnVault.sol"; +import { + StrategyId, + IEarnVault, + INFTPermissions, + IEarnStrategy, + SpecialWithdrawalCode +} from "@balmy/earn-core/interfaces/IEarnVault.sol"; +import { SpecialWithdrawalCode } from "@balmy/earn-core/types/SpecialWithdrawals.sol"; +import "@balmy/earn-core/strategy-registry/EarnStrategyRegistry.sol"; +import "@balmy/earn-core/vault/FirewalledEarnVault.sol"; +import "@balmy/earn-core/nft-descriptor/EarnNFTDescriptor.sol"; +import "src/companion/FirewalledEarnVaultCompanion.sol"; + +import "src/global-registry/GlobalEarnRegistry.sol"; +import "src/strategies/layers/fees/external/FeeManager.sol"; +import "src/strategies/layers/guardian/external/GuardianManager.sol"; +import "src/strategies/layers/liquidity-mining/external/LiquidityMiningManager.sol"; +import "src/delayed-withdrawal-manager/DelayedWithdrawalManager.sol"; +import "src/strategies/layers/creation-validation/external/TOSManager.sol"; + +import "src/strategies/instances/erc4626/ERC4626DelayedStrategy.sol"; +import "src/strategies/layers/connector/lido/ERC4626DelayedWithdrawalAdapter.sol"; + +import "src/strategies/instances/aave-v3/AaveV3Strategy.sol"; +import "src/strategies/instances/aave-v3/AaveV3StrategyFactory.sol"; + +import "@forta/firewall/ExternalFirewall.sol"; +import "@forta/firewall/FirewallAccess.sol"; +import "@forta/firewall/SecurityValidator.sol"; +import "@forta/firewall/FirewallRouter.sol"; + +import "@forta/firewall/interfaces/ICheckpointHook.sol"; +import "@forta/firewall/interfaces/Checkpoint.sol"; + +contract ERC4626DelayedStrategyTest is PRBTest, StdUtils, StdCheats { + function setUp() public { + vm.createSelectFork(vm.rpcUrl("polygon")); + } + + function test_withdraw_strategy() public { + ERC4626DelayedStrategy strategy = ERC4626DelayedStrategy(payable(0x98FFd2CdB5B6c653e680c22Ef13d7a195dB48ff3)); + address[] memory tokens = new address[](2); + tokens[0] = 0x83F20F44975D03b1b09e64809B757c47f942BEeA; + + uint256[] memory toWithdraw = new uint256[](1); + toWithdraw[0] = 5_000_000; + + vm.prank(0x58E5d76Fbbd7E1b51F0fC0F66B7734E108be0461); + strategy.withdraw({ positionId: 2, tokens: tokens, toWithdraw: toWithdraw, recipient: address(this) }); + } + + function test_withdraw_vault() public { + FirewalledEarnVault vault = FirewalledEarnVault(payable(0x58E5d76Fbbd7E1b51F0fC0F66B7734E108be0461)); + + uint256[] memory toWithdraw = new uint256[](1); + toWithdraw[0] = 5_000_000; + vm.prank(0xB86dA339B88D9697fa3ACC55EDd378e002676E01); + vault.specialWithdraw(2, SpecialWithdrawalCode.wrap(1), toWithdraw, "0x", address(this)); + } + + function test_withdraw_vault_2() public { + address deployer = 0xB86dA339B88D9697fa3ACC55EDd378e002676E01; + address admin = vm.envAddress("GOVERNOR"); + vm.startPrank(deployer); + EarnStrategyRegistry strategyRegistry = new EarnStrategyRegistry(); + EarnNFTDescriptor nftDescriptor = EarnNFTDescriptor(0xAe84114Aa7a651F765B24c74f3A0f8E64921C3D9); // Recycle NFT + // descriptor + address[] memory initialAdmins = new address[](2); + initialAdmins[0] = admin; + initialAdmins[1] = deployer; + + // FORTA + bytes32 attesterControllerId = bytes32("9999"); + ISecurityValidator validator = ISecurityValidator(0xc9b1AeD0895Dd647A82e35Cafff421B6CcFe690C); + FirewallAccess firewallAccess = new FirewallAccess(deployer); + ExternalFirewall externalFirewall = + new ExternalFirewall(validator, ICheckpointHook(address(0)), attesterControllerId, firewallAccess); + + FirewallRouter firewallRouter = new FirewallRouter(externalFirewall, firewallAccess); + FirewalledEarnVault vault = + new FirewalledEarnVault(strategyRegistry, admin, initialAdmins, nftDescriptor, firewallRouter); + + FirewalledEarnVaultCompanion companion = new FirewalledEarnVaultCompanion( + 0xED306e38BB930ec9646FF3D917B2e513a97530b1, + address(0), + admin, + IPermit2(0x000000000022D473030F116dDEE9F6B43aC78BA3), + firewallRouter + ); + + /// will renounce later below + firewallAccess.grantRole(FIREWALL_ADMIN_ROLE, deployer); + firewallAccess.grantRole(PROTOCOL_ADMIN_ROLE, deployer); + firewallAccess.grantRole(ATTESTER_MANAGER_ROLE, deployer); + + /// let protected contract execute checkpoints on the external firewall + firewallAccess.grantRole(CHECKPOINT_EXECUTOR_ROLE, address(vault)); + firewallAccess.grantRole(CHECKPOINT_EXECUTOR_ROLE, address(companion)); + firewallAccess.grantRole(CHECKPOINT_EXECUTOR_ROLE, address(firewallRouter)); + + /// set the trusted attester: + /// this will be necessary when "foo()" receives an attested call later. + firewallAccess.grantRole(TRUSTED_ATTESTER_ROLE, deployer); + + + FeeManager feeManager = new FeeManager(admin, initialAdmins, initialAdmins, Fees(0, 0, 500, 1000)); + GuardianManager guardianManager = + new GuardianManager(strategyRegistry, admin, initialAdmins, initialAdmins, initialAdmins, initialAdmins); + DelayedWithdrawalManager delayedWithdrawalManager = new DelayedWithdrawalManager(vault); + + TOSManager tosManager = new TOSManager(strategyRegistry, admin, initialAdmins); + + // LiquidityMiningManager liquidityMiningManager = new LiquidityMiningManager(strategyRegistry, admin, initialAdmins); + LiquidityMiningManager liquidityMiningManager = LiquidityMiningManager(0x64665eE43B54C1B08AE3198403462a2B7CC2c009); + GlobalEarnRegistry globalRegistry = new GlobalEarnRegistry(deployer); + globalRegistry.setAddress(keccak256("FEE_MANAGER"), address(feeManager)); + globalRegistry.setAddress(keccak256("GUARDIAN_MANAGER"), address(guardianManager)); + globalRegistry.setAddress(keccak256("LIQUIDITY_MINING_MANAGER"), address(liquidityMiningManager)); + globalRegistry.setAddress(keccak256("DELAYED_WITHDRAWAL_MANAGER"), address(delayedWithdrawalManager)); + globalRegistry.setAddress(keccak256("TOS_MANAGER"), address(tosManager)); + globalRegistry.transferOwnership(admin); + + + + ERC4626DelayedWithdrawalAdapter delayedWithdrawalAdapter = new ERC4626DelayedWithdrawalAdapter( + globalRegistry, + 0x0fEFEe13864c431717f5B2678607b6ce532a170C, // Yearn USDT CompoundV3 Lender (ysUSDT) + 7200 // 7200 seconds are 2 hours + ); + + ERC4626DelayedStrategy delayedStrategy = new ERC4626DelayedStrategy( + globalRegistry, + vault, + 0x0fEFEe13864c431717f5B2678607b6ce532a170C, + "Delayed Yearn USDT CompoundV3 Lender (ysUSDT)", + delayedWithdrawalAdapter + ); + + StrategyId strategyId = vault.STRATEGY_REGISTRY().registerStrategy(deployer, delayedStrategy); + liquidityMiningManager.setCampaign{ value: 0.00001 ether }( + strategyId, Token.NATIVE_TOKEN, 0.000000000001 ether, 10_000_000 + ); + + INFTPermissions.PermissionSet[] memory permissions = new INFTPermissions.PermissionSet[](1); + deal(0xc2132D05D31c914a87C6611C10748AEb04B58e8F, deployer, 1000000); + IERC20(0xc2132D05D31c914a87C6611C10748AEb04B58e8F).approve(address(vault), type(uint256).max); + vault.createPosition({ + strategyId: delayedStrategy.strategyId(), + depositToken: 0xc2132D05D31c914a87C6611C10748AEb04B58e8F, + depositAmount: 100, + owner: deployer, + permissions: permissions, + strategyValidationData: "", + misc: "" + }); + + vault.createPosition({ + strategyId: delayedStrategy.strategyId(), + depositToken: 0xc2132D05D31c914a87C6611C10748AEb04B58e8F, + depositAmount: 100, + owner: deployer, + permissions: permissions, + strategyValidationData: "", + misc: "" + }); + + vault.createPosition({ + strategyId: delayedStrategy.strategyId(), + depositToken: 0xc2132D05D31c914a87C6611C10748AEb04B58e8F, + depositAmount: 100, + owner: deployer, + permissions: permissions, + strategyValidationData: "", + misc: "" + }); + + vault.createPosition({ + strategyId: delayedStrategy.strategyId(), + depositToken: 0xc2132D05D31c914a87C6611C10748AEb04B58e8F, + depositAmount: 100, + owner: deployer, + permissions: permissions, + strategyValidationData: "", + misc: "" + }); + + uint256[] memory toWithdraw = new uint256[](1); + toWithdraw[0] = 10; + vault.specialWithdraw(2, SpecialWithdrawalCode.wrap(1), toWithdraw, "0x", address(this)); + /* + uint256[] memory toWithdraw = new uint256[](2); + toWithdraw[0] = 10; + toWithdraw[1] = type(uint256).max; + address[] memory tokens = new address[](2); + tokens[0] = 0xc2132D05D31c914a87C6611C10748AEb04B58e8F; + tokens[1] = 0x8f3Cf7ad23Cd3CaDbD9735AFf958023239c6A063; + vault.withdraw(2, tokens, toWithdraw, address(this)); + */ + } +}