From 34778636fd7382c85f38fde57eacf574b3859672 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment?= Date: Fri, 3 Oct 2025 11:46:41 +0200 Subject: [PATCH 01/33] Implement EtherFiARM contract and associated interfaces; add tests for withdrawal functionality --- src/contracts/EtherFiARM.sol | 161 +++++++++++++++++++++ src/contracts/Interfaces.sol | 10 ++ src/contracts/utils/Addresses.sol | 8 + test/Base.sol | 8 + test/fork/EtherFiARM/RequestWithdraw.t.sol | 29 ++++ test/fork/EtherFiARM/shared/Shared.sol | 104 +++++++++++++ 6 files changed, 320 insertions(+) create mode 100644 src/contracts/EtherFiARM.sol create mode 100644 test/fork/EtherFiARM/RequestWithdraw.t.sol create mode 100644 test/fork/EtherFiARM/shared/Shared.sol diff --git a/src/contracts/EtherFiARM.sol b/src/contracts/EtherFiARM.sol new file mode 100644 index 00000000..087c5f47 --- /dev/null +++ b/src/contracts/EtherFiARM.sol @@ -0,0 +1,161 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity ^0.8.23; + +import {Initializable} from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; +import {IERC721Receiver} from "@openzeppelin/contracts/token/ERC721/IERC721Receiver.sol"; + +import {AbstractARM} from "./AbstractARM.sol"; +import {IERC20, IWETH, IEETHWithdrawal, IEETHWithdrawalNFT} from "./Interfaces.sol"; + +/** + * @title EtherFi (eETH) Automated Redemption Manager (ARM) + * @dev This implementation supports multiple Liquidity Providers (LPs) with single buy and sell prices. + * It also integrates to a CapManager contract that caps the amount of assets a liquidity provider + * can deposit and caps the ARM's total assets. + * A performance fee is also collected on increases in the ARM's total assets. + * @author Origin Protocol Inc + */ +contract EtherFiARM is Initializable, AbstractARM, IERC721Receiver { + /// @notice The address of the EtherFi eETH token + IERC20 public immutable eeth; + /// @notice The address of the Wrapped ETH (WETH) token + IWETH public immutable weth; + /// @notice The address of the EtherFi Withdrawal Queue contract + IEETHWithdrawal public immutable etherfiWithdrawalQueue; + /// @notice The address of the EtherFi Withdrawal NFT contract + IEETHWithdrawalNFT public immutable etherfiWithdrawalNFT; + + /// @notice The amount of eETH in the EtherFi Withdrawal Queue + uint256 public etherfiWithdrawalQueueAmount; + + /// @notice stores the requested amount for each EtherFi withdrawal + mapping(uint256 id => uint256 amount) public etherfiWithdrawalRequests; + + event RequestEtherFiWithdrawal(uint256 amount, uint256 requestId); + event ClaimEtherFiWithdrawals(uint256[] requestIds); + event RegisterEtherFiWithdrawalRequests(uint256[] requestIds, uint256 totalAmountRequested); + + /// @param _eeth The address of the eETH token + /// @param _weth The address of the WETH token + /// @param _etherfiWithdrawalQueue The address of the EtherFi's withdrawal queue contract + /// @param _claimDelay The delay in seconds before a user can claim a redeem from the request + /// @param _minSharesToRedeem The minimum amount of shares to redeem from the active lending market + /// @param _allocateThreshold The minimum amount of liquidity assets in excess of the ARM buffer before + /// the ARM can allocate to a active lending market. + constructor( + address _eeth, + address _weth, + address _etherfiWithdrawalQueue, + uint256 _claimDelay, + uint256 _minSharesToRedeem, + int256 _allocateThreshold, + address _etherfiWithdrawalNFT + ) AbstractARM(_weth, _eeth, _weth, _claimDelay, _minSharesToRedeem, _allocateThreshold) { + eeth = IERC20(_eeth); + weth = IWETH(_weth); + etherfiWithdrawalQueue = IEETHWithdrawal(_etherfiWithdrawalQueue); + etherfiWithdrawalNFT = IEETHWithdrawalNFT(_etherfiWithdrawalNFT); + + _disableInitializers(); + } + + /// @notice Initialize the storage variables stored in the proxy contract. + /// The deployer that calls initialize has to approve the ARM's proxy contract to transfer 1e12 WETH. + /// @param _name The name of the liquidity provider (LP) token. + /// @param _symbol The symbol of the liquidity provider (LP) token. + /// @param _operator The address of the account that can request and claim EtherFi withdrawals. + /// @param _fee The performance fee that is collected by the feeCollector measured in basis points (1/100th of a percent). + /// 10,000 = 100% performance fee + /// 1,500 = 15% performance fee + /// @param _feeCollector The account that can collect the performance fee + /// @param _capManager The address of the CapManager contract + function initialize( + string calldata _name, + string calldata _symbol, + address _operator, + uint256 _fee, + address _feeCollector, + address _capManager + ) external initializer { + _initARM(_operator, _name, _symbol, _fee, _feeCollector, _capManager); + + // Approve the EtherFi withdrawal queue contract. Used for redemption requests. + eeth.approve(address(etherfiWithdrawalQueue), type(uint256).max); + } + + /** + * @notice Request an eETH for ETH withdrawal. + * Reference: https://etherfi.gitbook.io/etherfi/contracts-and-integrations/how-to + */ + function requestEtherFiWithdrawal(uint256 amount) external onlyOperatorOrOwner returns (uint256 requestId) { + // Request the withdrawal from the EtherFi Withdrawal Queue. + requestId = etherfiWithdrawalQueue.requestWithdraw(address(this), amount); + + // Store the requested amount from storage + etherfiWithdrawalRequests[requestId] = amount; + + // Increase the Ether outstanding from the EtherFi Withdrawal Queue + etherfiWithdrawalQueueAmount += amount; + + // Emit event for the request + emit RequestEtherFiWithdrawal(amount, requestId); + } + + /** + * @notice Claim the ETH owed from the redemption requests and convert it to WETH. + * Before calling this method, caller should check on the request NFTs to ensure the withdrawal was processed. + * @param requestIds The request IDs of the withdrawal requests. + * Call `findCheckpointHints` on the EtherFi withdrawal queue contract to get the hint IDs. + */ + function claimEtherFiWithdrawals(uint256[] calldata requestIds) external { + // Claim the NFTs for ETH. + etherfiWithdrawalNFT.batchClaimWithdraw(requestIds); + + // Reduce the amount outstanding from the EtherFi Withdrawal Queue. + // The amount of ETH claimed from the EtherFi Withdrawal Queue can be less than the requested amount + // in the event of a mass slashing event of EtherFi validators. + uint256 totalAmountRequested = 0; + for (uint256 i = 0; i < requestIds.length; i++) { + // Read the requested amount from storage + uint256 requestAmount = etherfiWithdrawalRequests[requestIds[i]]; + + // Validate the request came from this EtherFi ARM contract and not + // transferred in from another account. + require(requestAmount > 0, "EtherFiARM: invalid request"); + + totalAmountRequested += requestAmount; + } + + // Store the reduced outstanding withdrawals from the EtherFi Withdrawal Queue + if (etherfiWithdrawalQueueAmount < totalAmountRequested) { + // This can happen if a EtherFi withdrawal request was transferred to the ARM contract + etherfiWithdrawalQueueAmount = 0; + } else { + etherfiWithdrawalQueueAmount -= totalAmountRequested; + } + + // Wrap all the received ETH to WETH. + weth.deposit{value: address(this).balance}(); + + emit ClaimEtherFiWithdrawals(requestIds); + } + + /** + * @dev Calculates the amount of eETH in the EtherFi Withdrawal Queue. + */ + function _externalWithdrawQueue() internal view override returns (uint256) { + return etherfiWithdrawalQueueAmount; + } + + /// @notice This payable method is necessary for receiving ETH claimed from the EtherFi withdrawal queue. + receive() external payable {} + + /// @notice To be able to receive the NFTs from the EtherFi withdrawal queue contract. + function onERC721Received(address operator, address from, uint256 tokenId, bytes calldata data) + external + override + returns (bytes4) + { + return IERC721Receiver.onERC721Received.selector; + } +} diff --git a/src/contracts/Interfaces.sol b/src/contracts/Interfaces.sol index b8b68dc3..afebba77 100644 --- a/src/contracts/Interfaces.sol +++ b/src/contracts/Interfaces.sol @@ -307,3 +307,13 @@ interface SiloIncentivesControllerGaugeLike { ); function owner() external view returns (address); } + +interface IEETHWithdrawal { + function requestWithdraw(address receipient, uint256 amount) external returns (uint256 requestId); +} + +interface IEETHWithdrawalNFT { + function finalizeRequests(uint256 requestId) external; + function claimWithdraw(uint256 requestId) external; + function batchClaimWithdraw(uint256[] calldata requestIds) external; +} diff --git a/src/contracts/utils/Addresses.sol b/src/contracts/utils/Addresses.sol index 1c385287..292cad75 100644 --- a/src/contracts/utils/Addresses.sol +++ b/src/contracts/utils/Addresses.sol @@ -26,6 +26,8 @@ library Mainnet { // Tokens address public constant OETH = 0x856c4Efb76C1D1AE02e20CEB03A2A6a08b0b8dC3; address public constant WETH = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2; + address public constant EETH = 0x35fA164735182de50811E8e2E824cFb9B6118ac2; + address public constant WEETH = 0xCd5fE23C85820F7B72D0926FC9b05b43E359b7ee; address public constant STETH = 0xae7ab96520DE3A18E5e111B5EaAb095312D7fE84; address public constant WSTETH = 0x7f39C581F595B53c5cb19bD0b3f8dA6c935E2Ca0; @@ -40,6 +42,10 @@ library Mainnet { address public constant LIDO_EL_VAULT = 0x388C818CA8B9251b393131C08a736A67ccB19297; address public constant LIDO_WITHDRAWAL_MANAGER = 0xB9D7934878B5FB9610B3fE8A5e441e8fad7E293f; + // EtherFi + address public constant ETHERFI_WITHDRAWAL = 0x308861A430be4cce5502d0A12724771Fc6DaF216; + address public constant ETHERFI_WITHDRAWAL_NFT = 0x7d5706f6ef3F89B3951E23e557CDFBC3239D4E2c; + // Morpho Market address public constant MORPHO_MARKET_MEVCAPITAL = 0x9a8bC3B04b7f3D87cfC09ba407dCED575f2d61D8; } @@ -117,6 +123,8 @@ contract AddressResolver { // Tokens resolver[MAINNET]["OETH"] = Mainnet.OETH; resolver[MAINNET]["WETH"] = Mainnet.WETH; + resolver[MAINNET]["EETH"] = Mainnet.EETH; + resolver[MAINNET]["WEETH"] = Mainnet.WEETH; resolver[MAINNET]["STETH"] = Mainnet.STETH; resolver[MAINNET]["WSTETH"] = Mainnet.WSTETH; diff --git a/test/Base.sol b/test/Base.sol index 214bd7b2..b49ebf7d 100644 --- a/test/Base.sol +++ b/test/Base.sol @@ -8,6 +8,7 @@ import {Test} from "forge-std/Test.sol"; import {Proxy} from "contracts/Proxy.sol"; import {OethARM} from "contracts/OethARM.sol"; import {LidoARM} from "contracts/LidoARM.sol"; +import {EtherFiARM} from "contracts/EtherFiARM.sol"; import {OriginARM} from "contracts/OriginARM.sol"; import {SonicHarvester} from "contracts/SonicHarvester.sol"; import {CapManager} from "contracts/CapManager.sol"; @@ -38,10 +39,12 @@ abstract contract Base_Test_ is Test { Proxy public proxy; Proxy public lpcProxy; Proxy public lidoProxy; + Proxy public etherfiProxy; Proxy public originARMProxy; Proxy public harvesterProxy; OethARM public oethARM; LidoARM public lidoARM; + EtherFiARM public etherfiARM; SonicHarvester public harvester; OriginARM public originARM; CapManager public capManager; @@ -53,6 +56,8 @@ abstract contract Base_Test_ is Test { IERC20 public wos; IERC20 public oeth; IERC20 public weth; + IERC20 public eeth; + IERC20 public weeth; IERC20 public steth; IERC20 public wsteth; IERC20 public badToken; @@ -99,6 +104,7 @@ abstract contract Base_Test_ is Test { _labelNotNull(address(proxy), "DEFAULT PROXY"); _labelNotNull(address(lpcProxy), "LPC PROXY"); _labelNotNull(address(lidoProxy), "LIDO ARM PROXY"); + _labelNotNull(address(etherfiProxy), "ETHERFI ARM PROXY"); _labelNotNull(address(oethARM), "OETH ARM"); _labelNotNull(address(lidoARM), "LIDO ARM"); _labelNotNull(address(originARM), "ORIGIN ARM"); @@ -109,6 +115,8 @@ abstract contract Base_Test_ is Test { _labelNotNull(address(os), "OS"); _labelNotNull(address(oeth), "OETH"); _labelNotNull(address(weth), "WETH"); + _labelNotNull(address(eeth), "EETH"); + _labelNotNull(address(weeth), "WEETH"); _labelNotNull(address(steth), "STETH"); _labelNotNull(address(wsteth), " WRAPPED STETH"); _labelNotNull(address(badToken), "BAD TOKEN"); diff --git a/test/fork/EtherFiARM/RequestWithdraw.t.sol b/test/fork/EtherFiARM/RequestWithdraw.t.sol new file mode 100644 index 00000000..62d1e507 --- /dev/null +++ b/test/fork/EtherFiARM/RequestWithdraw.t.sol @@ -0,0 +1,29 @@ +/// SPDX-License-Identifier: MIT +pragma solidity 0.8.23; + +// Test +import {Fork_Shared_Test} from "test/fork/EtherFiARM/shared/Shared.sol"; + +contract Fork_Concrete_EtherFiARM_RequestWithdraw_Test_ is Fork_Shared_Test { + function test() public { + // Fund the ARM with eETH from weETH + vm.prank(address(weeth)); + eeth.transfer(address(etherfiARM), 10 ether); + + // Request a withdrawal + vm.prank(operator); + uint256 requestId = etherfiARM.requestEtherFiWithdrawal(1 ether); + + // Process finalization on withdrawal queue + // We cheat a bit here, because we don't follow the full finalization process it could fail + // if there is not enough liquidity, but since the amount to claim is low, it should be fine + vm.prank(0x0EF8fa4760Db8f5Cd4d993f3e3416f30f942D705); + etherfiWithdrawalNFT.finalizeRequests(requestId); + + // Claim the withdrawal + uint256[] memory requestIdArray = new uint256[](1); + requestIdArray[0] = requestId; + vm.prank(operator); + etherfiARM.claimEtherFiWithdrawals(requestIdArray); + } +} diff --git a/test/fork/EtherFiARM/shared/Shared.sol b/test/fork/EtherFiARM/shared/Shared.sol new file mode 100644 index 00000000..7c6a1626 --- /dev/null +++ b/test/fork/EtherFiARM/shared/Shared.sol @@ -0,0 +1,104 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.23; + +// Test +import {Base_Test_} from "test/Base.sol"; + +// Contracts +import {Proxy} from "contracts/Proxy.sol"; +import {EtherFiARM} from "contracts/EtherFiARM.sol"; + +// Interfaces +import {Mainnet} from "src/contracts/utils/Addresses.sol"; +import {IERC20, IEETHWithdrawalNFT} from "contracts/Interfaces.sol"; + +abstract contract Fork_Shared_Test is Base_Test_ { + IEETHWithdrawalNFT public etherfiWithdrawalNFT; + + ////////////////////////////////////////////////////// + /// --- SETUP + ////////////////////////////////////////////////////// + function setUp() public virtual override { + super.setUp(); + + // Generate a fork + _createAndSelectFork(); + + // Deploy Mock contracts + _deployMockContracts(); + + // Generate addresses + _generateAddresses(); + + // Deploy contracts + _deployContracts(); + + // Label contracts + labelAll(); + } + + function _createAndSelectFork() internal { + // Check if the PROVIDER_URL is set. + require(vm.envExists("PROVIDER_URL"), "PROVIDER_URL not set"); + + // Create and select a fork. + if (vm.envExists("FORK_BLOCK_NUMBER_MAINNET")) { + vm.createSelectFork("mainnet", vm.envUint("FORK_BLOCK_NUMBER_MAINNET")); + } else { + vm.createSelectFork("mainnet"); + } + } + + function _deployMockContracts() internal { + eeth = IERC20(resolver.resolve("EETH")); + weth = IERC20(resolver.resolve("WETH")); + weeth = IERC20(resolver.resolve("WEETH")); + etherfiWithdrawalNFT = IEETHWithdrawalNFT(Mainnet.ETHERFI_WITHDRAWAL_NFT); + } + + function _generateAddresses() internal { + // Users and multisigs + alice = makeAddr("alice"); + bob = makeAddr("bob"); + deployer = makeAddr("DEPLOYER"); + operator = makeAddr("OPERATOR"); + governor = makeAddr("GOVERNOR"); + feeCollector = makeAddr("FEE_COLLECTOR"); + } + + function _deployContracts() internal { + vm.startPrank(deployer); + + // --- Deploy EtherFiARM Proxy --- + // Deploy Proxy contract for the EtherFiARM. + Proxy etherfiProxy = new Proxy(); + + // --- Deploy EtherFiARM implementation --- + // Deploy EtherFiARM implementation. + EtherFiARM etherfiImpl = new EtherFiARM( + address(eeth), address(weth), Mainnet.ETHERFI_WITHDRAWAL, 10 minutes, 0, 0, Mainnet.ETHERFI_WITHDRAWAL_NFT + ); + + // Deployer will need WETH to initialize the ARM. + deal(address(weth), deployer, 1e12); + weth.approve(address(etherfiProxy), type(uint256).max); + eeth.approve(address(etherfiProxy), type(uint256).max); + + // Initialize Proxy with EtherFiARM implementation. + bytes memory data = abi.encodeWithSignature( + "initialize(string,string,address,uint256,address,address)", + "EtherFi ARM", + "ARM-EETH", + operator, + 2000, // 20% performance fee + feeCollector, + address(lpcProxy) + ); + etherfiProxy.initialize(address(etherfiImpl), address(this), data); + + // Set the Proxy as the EtherFiARM. + etherfiARM = EtherFiARM(payable(address(etherfiProxy))); + + vm.stopPrank(); + } +} From 30b29bed03382ec240a3a97b01099425a23aed37 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment?= Date: Fri, 3 Oct 2025 15:59:05 +0200 Subject: [PATCH 02/33] Add EtherFi Redemption Manager integration to EtherFiARM contract and interfaces --- src/contracts/EtherFiARM.sol | 25 ++++++++++++++++++++-- src/contracts/Interfaces.sol | 6 ++++++ src/contracts/utils/Addresses.sol | 2 ++ test/fork/EtherFiARM/RequestWithdraw.t.sol | 16 +++++++++++++- test/fork/EtherFiARM/shared/Shared.sol | 13 +++++++++-- 5 files changed, 57 insertions(+), 5 deletions(-) diff --git a/src/contracts/EtherFiARM.sol b/src/contracts/EtherFiARM.sol index 087c5f47..b7cc7c30 100644 --- a/src/contracts/EtherFiARM.sol +++ b/src/contracts/EtherFiARM.sol @@ -5,7 +5,7 @@ import {Initializable} from "@openzeppelin/contracts-upgradeable/proxy/utils/Ini import {IERC721Receiver} from "@openzeppelin/contracts/token/ERC721/IERC721Receiver.sol"; import {AbstractARM} from "./AbstractARM.sol"; -import {IERC20, IWETH, IEETHWithdrawal, IEETHWithdrawalNFT} from "./Interfaces.sol"; +import {IERC20, IWETH, IEETHWithdrawal, IEETHWithdrawalNFT, IEETHRedemptionManager} from "./Interfaces.sol"; /** * @title EtherFi (eETH) Automated Redemption Manager (ARM) @@ -24,6 +24,8 @@ contract EtherFiARM is Initializable, AbstractARM, IERC721Receiver { IEETHWithdrawal public immutable etherfiWithdrawalQueue; /// @notice The address of the EtherFi Withdrawal NFT contract IEETHWithdrawalNFT public immutable etherfiWithdrawalNFT; + /// @notice The address of the EtherFi Redemption Manager contract + IEETHRedemptionManager public immutable etherfiRedemptionManager; /// @notice The amount of eETH in the EtherFi Withdrawal Queue uint256 public etherfiWithdrawalQueueAmount; @@ -49,12 +51,14 @@ contract EtherFiARM is Initializable, AbstractARM, IERC721Receiver { uint256 _claimDelay, uint256 _minSharesToRedeem, int256 _allocateThreshold, - address _etherfiWithdrawalNFT + address _etherfiWithdrawalNFT, + address _etherfiRedemptionManager ) AbstractARM(_weth, _eeth, _weth, _claimDelay, _minSharesToRedeem, _allocateThreshold) { eeth = IERC20(_eeth); weth = IWETH(_weth); etherfiWithdrawalQueue = IEETHWithdrawal(_etherfiWithdrawalQueue); etherfiWithdrawalNFT = IEETHWithdrawalNFT(_etherfiWithdrawalNFT); + etherfiRedemptionManager = IEETHRedemptionManager(_etherfiRedemptionManager); _disableInitializers(); } @@ -140,6 +144,23 @@ contract EtherFiARM is Initializable, AbstractARM, IERC721Receiver { emit ClaimEtherFiWithdrawals(requestIds); } + /** + * @notice Redeem instantly eETH for ETH via the EtherFi Redemption Manager. + * This method can only be called by the operator or the owner. + * 0.3% of fees are charged by the EtherFi Redemption Manager. + * @param amount The amount of eETH to redeem for ETH. + */ + function redeemEETH(uint256 amount) external onlyOperatorOrOwner { + // Approve the EtherFi Redemption Manager to spend eETH + eeth.approve(address(etherfiRedemptionManager), amount); + + // Redeem eETH for ETH via the EtherFi Redemption Manager + etherfiRedemptionManager.redeemEEth(amount, address(this)); + + // Wrap all the received ETH to WETH. + weth.deposit{value: address(this).balance}(); + } + /** * @dev Calculates the amount of eETH in the EtherFi Withdrawal Queue. */ diff --git a/src/contracts/Interfaces.sol b/src/contracts/Interfaces.sol index afebba77..95b7960d 100644 --- a/src/contracts/Interfaces.sol +++ b/src/contracts/Interfaces.sol @@ -317,3 +317,9 @@ interface IEETHWithdrawalNFT { function claimWithdraw(uint256 requestId) external; function batchClaimWithdraw(uint256[] calldata requestIds) external; } + +interface IEETHRedemptionManager { + function redeemEEth(uint256 amount, address receiver) external; + function redeemWeEth(uint256 amount, address receiver) external; + function canRedeem(uint256 amount) external view returns (bool); +} diff --git a/src/contracts/utils/Addresses.sol b/src/contracts/utils/Addresses.sol index 292cad75..abdb347a 100644 --- a/src/contracts/utils/Addresses.sol +++ b/src/contracts/utils/Addresses.sol @@ -44,7 +44,9 @@ library Mainnet { // EtherFi address public constant ETHERFI_WITHDRAWAL = 0x308861A430be4cce5502d0A12724771Fc6DaF216; + address public constant ETHERFI_LIQUIDITY_POOL = 0x308861A430be4cce5502d0A12724771Fc6DaF216; address public constant ETHERFI_WITHDRAWAL_NFT = 0x7d5706f6ef3F89B3951E23e557CDFBC3239D4E2c; + address public constant ETHERFI_REDEMPTION_MANAGER = 0xDadEf1fFBFeaAB4f68A9fD181395F68b4e4E7Ae0; // Morpho Market address public constant MORPHO_MARKET_MEVCAPITAL = 0x9a8bC3B04b7f3D87cfC09ba407dCED575f2d61D8; diff --git a/test/fork/EtherFiARM/RequestWithdraw.t.sol b/test/fork/EtherFiARM/RequestWithdraw.t.sol index 62d1e507..5cd273ef 100644 --- a/test/fork/EtherFiARM/RequestWithdraw.t.sol +++ b/test/fork/EtherFiARM/RequestWithdraw.t.sol @@ -2,10 +2,11 @@ pragma solidity 0.8.23; // Test +import {Mainnet} from "src/contracts/utils/Addresses.sol"; import {Fork_Shared_Test} from "test/fork/EtherFiARM/shared/Shared.sol"; contract Fork_Concrete_EtherFiARM_RequestWithdraw_Test_ is Fork_Shared_Test { - function test() public { + function test_DelayWithdraw() public { // Fund the ARM with eETH from weETH vm.prank(address(weeth)); eeth.transfer(address(etherfiARM), 10 ether); @@ -26,4 +27,17 @@ contract Fork_Concrete_EtherFiARM_RequestWithdraw_Test_ is Fork_Shared_Test { vm.prank(operator); etherfiARM.claimEtherFiWithdrawals(requestIdArray); } + + function test_DirectWithdraw() public { + // Fund the ARM with eETH from weETH + vm.prank(address(weeth)); + eeth.transfer(address(etherfiARM), 10 ether); + + // Overload the liquidity pool to ensure there is enough liquidity to redeem + deal(Mainnet.ETHERFI_LIQUIDITY_POOL, 1_000_000 ether); + + // Redeem eETH for WETH via the EtherFi Redemption Manager + vm.prank(operator); + etherfiARM.redeemEETH(1 ether); + } } diff --git a/test/fork/EtherFiARM/shared/Shared.sol b/test/fork/EtherFiARM/shared/Shared.sol index 7c6a1626..a7742032 100644 --- a/test/fork/EtherFiARM/shared/Shared.sol +++ b/test/fork/EtherFiARM/shared/Shared.sol @@ -10,10 +10,11 @@ import {EtherFiARM} from "contracts/EtherFiARM.sol"; // Interfaces import {Mainnet} from "src/contracts/utils/Addresses.sol"; -import {IERC20, IEETHWithdrawalNFT} from "contracts/Interfaces.sol"; +import {IERC20, IEETHWithdrawalNFT, IEETHRedemptionManager} from "contracts/Interfaces.sol"; abstract contract Fork_Shared_Test is Base_Test_ { IEETHWithdrawalNFT public etherfiWithdrawalNFT; + IEETHRedemptionManager public etherfiRedemptionManager; ////////////////////////////////////////////////////// /// --- SETUP @@ -54,6 +55,7 @@ abstract contract Fork_Shared_Test is Base_Test_ { weth = IERC20(resolver.resolve("WETH")); weeth = IERC20(resolver.resolve("WEETH")); etherfiWithdrawalNFT = IEETHWithdrawalNFT(Mainnet.ETHERFI_WITHDRAWAL_NFT); + etherfiRedemptionManager = IEETHRedemptionManager(Mainnet.ETHERFI_REDEMPTION_MANAGER); } function _generateAddresses() internal { @@ -76,7 +78,14 @@ abstract contract Fork_Shared_Test is Base_Test_ { // --- Deploy EtherFiARM implementation --- // Deploy EtherFiARM implementation. EtherFiARM etherfiImpl = new EtherFiARM( - address(eeth), address(weth), Mainnet.ETHERFI_WITHDRAWAL, 10 minutes, 0, 0, Mainnet.ETHERFI_WITHDRAWAL_NFT + address(eeth), + address(weth), + Mainnet.ETHERFI_WITHDRAWAL, + 10 minutes, + 0, + 0, + Mainnet.ETHERFI_WITHDRAWAL_NFT, + Mainnet.ETHERFI_REDEMPTION_MANAGER ); // Deployer will need WETH to initialize the ARM. From 15ec6c51a111c2cab715022a19267e9811884ad1 Mon Sep 17 00:00:00 2001 From: Nicholas Addison Date: Fri, 17 Oct 2025 18:15:33 +1100 Subject: [PATCH 03/33] Added asset() function for ERC-4626 compatibility --- src/contracts/AbstractARM.sol | 7 +++++++ test/smoke/LidoARMSmokeTest.t.sol | 1 + 2 files changed, 8 insertions(+) diff --git a/src/contracts/AbstractARM.sol b/src/contracts/AbstractARM.sol index 8a2d1146..85c2209c 100644 --- a/src/contracts/AbstractARM.sol +++ b/src/contracts/AbstractARM.sol @@ -675,6 +675,13 @@ abstract contract AbstractARM is OwnableOperable, ERC20Upgradeable { return newAvailableAssets - fees; } + /// @notice The liquidity asset used for deposits and redeems. eg WETH or wS + /// Used for compatibility with ERC-4626 + /// @return The address of the liquidity asset + function asset() public view returns (address) { + return liquidityAsset; + } + /// @dev Calculate the available assets which is the assets in the ARM, external withdrawal queue, /// and active lending market, less liquidity assets reserved for the ARM's withdrawal queue. /// This does not exclude any accrued performance fees. diff --git a/test/smoke/LidoARMSmokeTest.t.sol b/test/smoke/LidoARMSmokeTest.t.sol index 2b608597..62c83c01 100644 --- a/test/smoke/LidoARMSmokeTest.t.sol +++ b/test/smoke/LidoARMSmokeTest.t.sol @@ -51,6 +51,7 @@ contract Fork_LidoARM_Smoke_Test is AbstractSmokeTest { assertEq(address(lidoARM.steth()), Mainnet.STETH, "stETH"); assertEq(address(lidoARM.weth()), Mainnet.WETH, "WETH"); assertEq(lidoARM.liquidityAsset(), Mainnet.WETH, "liquidity asset"); + assertEq(lidoARM.asset(), Mainnet.WETH, "ERC-4626 asset"); assertEq(lidoARM.claimDelay(), 10 minutes, "claim delay"); assertEq(lidoARM.crossPrice(), 0.9999e36, "cross price"); From a6c6dcae2ca926e192fb4c8ee4816cef08004a2f Mon Sep 17 00:00:00 2001 From: Nicholas Addison Date: Fri, 17 Oct 2025 18:21:34 +1100 Subject: [PATCH 04/33] Format code --- src/contracts/pendle/PendleOriginARMSY.sol | 25 +++++++++++++++---- .../ClaimStETHWithdrawalForWETH.t.sol | 6 +---- .../LidoFixedPriceMultiLpARM/Deposit.t.sol | 8 +++--- test/fork/OriginARM/shared/ISilo.sol | 20 +++------------ test/invariants/LidoARM/TargetFunction.sol | 17 ++++--------- test/invariants/OriginARM/TargetFunction.sol | 4 ++- test/unit/OriginARM/ClaimRedeem.sol | 6 +---- 7 files changed, 38 insertions(+), 48 deletions(-) diff --git a/src/contracts/pendle/PendleOriginARMSY.sol b/src/contracts/pendle/PendleOriginARMSY.sol index 8a41a964..9e4a9231 100644 --- a/src/contracts/pendle/PendleOriginARMSY.sol +++ b/src/contracts/pendle/PendleOriginARMSY.sol @@ -25,7 +25,9 @@ contract PendleOriginARMSY is SYBaseV2 { internal virtual override - returns (uint256 /*amountSharesOut*/ ) + returns ( + uint256 /*amountSharesOut*/ + ) { if (tokenIn == yieldToken) { return amountDeposited; @@ -34,7 +36,12 @@ contract PendleOriginARMSY is SYBaseV2 { } } - function _redeem(address receiver, address, /*tokenOut*/ uint256 amountSharesToRedeem) + function _redeem( + address receiver, + address, + /*tokenOut*/ + uint256 amountSharesToRedeem + ) internal override returns (uint256) @@ -52,17 +59,25 @@ contract PendleOriginARMSY is SYBaseV2 { view virtual override - returns (uint256 /*amountSharesOut*/ ) + returns ( + uint256 /*amountSharesOut*/ + ) { if (tokenIn == yieldToken) return amountTokenToDeposit; else return IERC4626(yieldToken).previewDeposit(amountTokenToDeposit); } - function _previewRedeem(address, /*tokenOut*/ uint256 amountSharesToRedeem) + function _previewRedeem( + address, + /*tokenOut*/ + uint256 amountSharesToRedeem + ) internal pure override - returns (uint256 /*amountTokenOut*/ ) + returns ( + uint256 /*amountTokenOut*/ + ) { return amountSharesToRedeem; } diff --git a/test/fork/LidoFixedPriceMultiLpARM/ClaimStETHWithdrawalForWETH.t.sol b/test/fork/LidoFixedPriceMultiLpARM/ClaimStETHWithdrawalForWETH.t.sol index 96493087..5ea698e8 100644 --- a/test/fork/LidoFixedPriceMultiLpARM/ClaimStETHWithdrawalForWETH.t.sol +++ b/test/fork/LidoFixedPriceMultiLpARM/ClaimStETHWithdrawalForWETH.t.sol @@ -37,11 +37,7 @@ contract Fork_Concrete_LidoARM_RequestLidoWithdrawals_Test_ is Fork_Shared_Test_ ////////////////////////////////////////////////////// /// --- PASSING TESTS ////////////////////////////////////////////////////// - function test_ClaimLidoWithdrawals_EmptyList() - public - asOperator - requestLidoWithdrawalsOnLidoARM(new uint256[](0)) - { + function test_ClaimLidoWithdrawals_EmptyList() public asOperator requestLidoWithdrawalsOnLidoARM(new uint256[](0)) { assertEq(address(lidoARM).balance, 0); assertEq(lidoARM.lidoWithdrawalQueueAmount(), 0); diff --git a/test/fork/LidoFixedPriceMultiLpARM/Deposit.t.sol b/test/fork/LidoFixedPriceMultiLpARM/Deposit.t.sol index 9e5b8d3d..6c5c7e8d 100644 --- a/test/fork/LidoFixedPriceMultiLpARM/Deposit.t.sol +++ b/test/fork/LidoFixedPriceMultiLpARM/Deposit.t.sol @@ -733,10 +733,10 @@ contract Fork_Concrete_LidoARM_Deposit_Test_ is Fork_Shared_Test_ { uint256 expectedTotalSupplyBeforeDeposit = expectTotalAssetsBeforeSwap; uint256 expectTotalAssetsBeforeDeposit = expectTotalAssetsBeforeSwap - 1 - // steth in discounted to the cross price - + ((swapInAmount * 0.999e36) / 1e36) - // weth out discounted by the buy price - - ((swapInAmount * 0.998e36) / 1e36); + // steth in discounted to the cross price + + ((swapInAmount * 0.999e36) / 1e36) + // weth out discounted by the buy price + - ((swapInAmount * 0.998e36) / 1e36); assertEq(lidoARM.totalSupply(), expectedTotalSupplyBeforeDeposit, "total supply before deposit"); assertApproxEqAbs(lidoARM.totalAssets(), expectTotalAssetsBeforeDeposit, 3, "total assets before deposit"); assertEq(lidoARM.feesAccrued(), 0, "fees accrued before swap"); diff --git a/test/fork/OriginARM/shared/ISilo.sol b/test/fork/OriginARM/shared/ISilo.sol index a0713060..da24ea08 100644 --- a/test/fork/OriginARM/shared/ISilo.sol +++ b/test/fork/OriginARM/shared/ISilo.sol @@ -172,17 +172,11 @@ interface Silo { function maxDeposit(address) external pure returns (uint256 maxAssets); function maxFlashLoan(address _token) external view returns (uint256 maxLoan); function maxMint(address) external view returns (uint256 maxShares); - function maxRedeem(address _owner, ISilo.CollateralType _collateralType) - external - view - returns (uint256 maxShares); + function maxRedeem(address _owner, ISilo.CollateralType _collateralType) external view returns (uint256 maxShares); function maxRedeem(address _owner) external view returns (uint256 maxShares); function maxRepay(address _borrower) external view returns (uint256 assets); function maxRepayShares(address _borrower) external view returns (uint256 shares); - function maxWithdraw(address _owner, ISilo.CollateralType _collateralType) - external - view - returns (uint256 maxAssets); + function maxWithdraw(address _owner, ISilo.CollateralType _collateralType) external view returns (uint256 maxAssets); function maxWithdraw(address _owner) external view returns (uint256 maxAssets); function mint(uint256 _shares, address _receiver) external returns (uint256 assets); function mint(uint256 _shares, address _receiver, ISilo.CollateralType _collateralType) @@ -200,16 +194,10 @@ interface Silo { view returns (uint256 shares); function previewDeposit(uint256 _assets) external view returns (uint256 shares); - function previewMint(uint256 _shares, ISilo.CollateralType _collateralType) - external - view - returns (uint256 assets); + function previewMint(uint256 _shares, ISilo.CollateralType _collateralType) external view returns (uint256 assets); function previewMint(uint256 _shares) external view returns (uint256 assets); function previewRedeem(uint256 _shares) external view returns (uint256 assets); - function previewRedeem(uint256 _shares, ISilo.CollateralType _collateralType) - external - view - returns (uint256 assets); + function previewRedeem(uint256 _shares, ISilo.CollateralType _collateralType) external view returns (uint256 assets); function previewRepay(uint256 _assets) external view returns (uint256 shares); function previewRepayShares(uint256 _shares) external view returns (uint256 assets); function previewWithdraw(uint256 _assets) external view returns (uint256 shares); diff --git a/test/invariants/LidoARM/TargetFunction.sol b/test/invariants/LidoARM/TargetFunction.sol index dbbcb975..55a3dbc6 100644 --- a/test/invariants/LidoARM/TargetFunction.sol +++ b/test/invariants/LidoARM/TargetFunction.sol @@ -25,11 +25,7 @@ abstract contract TargetFunction is Properties { // Prank the user vm.prank(user); uint256[] memory amounts = lidoARM.swapExactTokensForTokens({ - amountIn: amount, - amountOutMin: 0, - path: path, - to: address(user), - deadline: block.timestamp + amountIn: amount, amountOutMin: 0, path: path, to: address(user), deadline: block.timestamp }); // Update ghost @@ -53,11 +49,7 @@ abstract contract TargetFunction is Properties { // Prank the user vm.prank(user); uint256[] memory amounts = lidoARM.swapTokensForExactTokens({ - amountOut: amount, - amountInMax: type(uint256).max, - path: path, - to: address(user), - deadline: block.timestamp + amountOut: amount, amountInMax: type(uint256).max, path: path, to: address(user), deadline: block.timestamp }); // Update ghost @@ -255,8 +247,9 @@ abstract contract TargetFunction is Properties { // Bound new cross price uint256 sell = priceScale ** 2 / lidoARM.traderate0(); uint256 buy = lidoARM.traderate1(); - newCrossPrice = - _bound(newCrossPrice, max(priceScale - lidoARM.MAX_CROSS_PRICE_DEVIATION(), buy) + 1, min(priceScale, sell)); + newCrossPrice = _bound( + newCrossPrice, max(priceScale - lidoARM.MAX_CROSS_PRICE_DEVIATION(), buy) + 1, min(priceScale, sell) + ); // Prank owner vm.prank(lidoARM.owner()); diff --git a/test/invariants/OriginARM/TargetFunction.sol b/test/invariants/OriginARM/TargetFunction.sol index 70f4d016..8111484b 100644 --- a/test/invariants/OriginARM/TargetFunction.sol +++ b/test/invariants/OriginARM/TargetFunction.sol @@ -215,7 +215,9 @@ abstract contract TargetFunction is Properties { if (originARM.crossPrice() > newCrossPrice) vm.assume(os.balanceOf(address(originARM)) <= MIN_TOTAL_SUPPLY); // Console log data - if (CONSOLE_LOG) console.log("setCrossPrice() \t From: %s | \t CrossP: %s", "Owner", faa(newCrossPrice / 1e18)); + if (CONSOLE_LOG) { + console.log("setCrossPrice() \t From: %s | \t CrossP: %s", "Owner", faa(newCrossPrice / 1e18)); + } // Main call vm.prank(governor); diff --git a/test/unit/OriginARM/ClaimRedeem.sol b/test/unit/OriginARM/ClaimRedeem.sol index f1332a36..8e01bb65 100644 --- a/test/unit/OriginARM/ClaimRedeem.sol +++ b/test/unit/OriginARM/ClaimRedeem.sol @@ -43,11 +43,7 @@ contract Unit_Concrete_OriginARM_ClaimRedeem_Test_ is Unit_Shared_Test { originARM.claimRedeem(0); } - function test_RevertWhen_ClaimRedeem_Because_AlreadyClaimed() - public - requestRedeemAll(alice) - timejump(CLAIM_DELAY) - { + function test_RevertWhen_ClaimRedeem_Because_AlreadyClaimed() public requestRedeemAll(alice) timejump(CLAIM_DELAY) { // Alice claims her redeem vm.prank(alice); originARM.claimRedeem(0); From 6938e5784ad148b480b63c49e37fdd5f51b7a4f5 Mon Sep 17 00:00:00 2001 From: Nicholas Addison Date: Fri, 17 Oct 2025 18:24:59 +1100 Subject: [PATCH 05/33] Changed asset() to be external rather than public --- src/contracts/AbstractARM.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/contracts/AbstractARM.sol b/src/contracts/AbstractARM.sol index 85c2209c..62727bec 100644 --- a/src/contracts/AbstractARM.sol +++ b/src/contracts/AbstractARM.sol @@ -678,7 +678,7 @@ abstract contract AbstractARM is OwnableOperable, ERC20Upgradeable { /// @notice The liquidity asset used for deposits and redeems. eg WETH or wS /// Used for compatibility with ERC-4626 /// @return The address of the liquidity asset - function asset() public view returns (address) { + function asset() external view virtual returns (address) { return liquidityAsset; } From 0eeda551880c1cca938ff16f436e36d21d0fbf61 Mon Sep 17 00:00:00 2001 From: Nicholas Addison Date: Mon, 20 Oct 2025 17:29:46 +1100 Subject: [PATCH 06/33] Show forge version in CI --- .github/workflows/main.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 9a05fe86..faff8564 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -24,6 +24,9 @@ jobs: - name: Install Foundry uses: foundry-rs/foundry-toolchain@v1 + - name: Show Forge version + run: forge --version + - name: Run Linter run: forge fmt --check From ae789bc3e882e11846b3d4e299b1d3271c205df8 Mon Sep 17 00:00:00 2001 From: Nicholas Addison Date: Tue, 21 Oct 2025 14:13:28 +1100 Subject: [PATCH 07/33] Removed : from remapping --- foundry.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/foundry.toml b/foundry.toml index cc0b1f82..8366e45b 100644 --- a/foundry.toml +++ b/foundry.toml @@ -19,7 +19,7 @@ ffi = true # - dependencies remappings (like "dependencies/@pendle-sy"), need to stay amongs first remappings. remappings = [ # Manage dependencies remappings inside dependencies folder (has to be first) - "dependencies/@pendle-sy-1.0.0-1.0.0/:@openzeppelin/contracts/=dependencies/@openzeppelin-contracts-4.9.3-4.9.3/contracts/", + "dependencies/@pendle-sy-1.0.0-1.0.0/@openzeppelin/contracts/=dependencies/@openzeppelin-contracts-4.9.3-4.9.3/contracts/", # Manage root folders remappings "contracts/=./src/contracts", "script/=./script", From 24a89718a3b36a1b81010712e6afa05b1bd22a6a Mon Sep 17 00:00:00 2001 From: Nicholas Addison Date: Wed, 22 Oct 2025 11:57:56 +1100 Subject: [PATCH 08/33] Reverted to Foundry version 1.3.6 --- Makefile | 2 +- foundry.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index 29989550..26e52aad 100644 --- a/Makefile +++ b/Makefile @@ -8,7 +8,7 @@ default: # Always keep Forge up to date install: - foundryup + foundryup -v 1.3.6 forge soldeer install yarn install diff --git a/foundry.toml b/foundry.toml index 8366e45b..cc0b1f82 100644 --- a/foundry.toml +++ b/foundry.toml @@ -19,7 +19,7 @@ ffi = true # - dependencies remappings (like "dependencies/@pendle-sy"), need to stay amongs first remappings. remappings = [ # Manage dependencies remappings inside dependencies folder (has to be first) - "dependencies/@pendle-sy-1.0.0-1.0.0/@openzeppelin/contracts/=dependencies/@openzeppelin-contracts-4.9.3-4.9.3/contracts/", + "dependencies/@pendle-sy-1.0.0-1.0.0/:@openzeppelin/contracts/=dependencies/@openzeppelin-contracts-4.9.3-4.9.3/contracts/", # Manage root folders remappings "contracts/=./src/contracts", "script/=./script", From f425ced5015181e06321057568359111ead9e236 Mon Sep 17 00:00:00 2001 From: Nicholas Addison Date: Wed, 22 Oct 2025 11:58:16 +1100 Subject: [PATCH 09/33] Revert formatting to older Foundry version --- src/contracts/pendle/PendleOriginARMSY.sol | 23 ++++--------------- .../ClaimStETHWithdrawalForWETH.t.sol | 6 ++++- .../LidoFixedPriceMultiLpARM/Deposit.t.sol | 8 +++---- test/fork/OriginARM/shared/ISilo.sol | 20 ++++++++++++---- test/invariants/LidoARM/TargetFunction.sol | 17 ++++++++++---- test/unit/OriginARM/ClaimRedeem.sol | 6 ++++- 6 files changed, 46 insertions(+), 34 deletions(-) diff --git a/src/contracts/pendle/PendleOriginARMSY.sol b/src/contracts/pendle/PendleOriginARMSY.sol index 9e4a9231..9dfe4a8a 100644 --- a/src/contracts/pendle/PendleOriginARMSY.sol +++ b/src/contracts/pendle/PendleOriginARMSY.sol @@ -25,9 +25,7 @@ contract PendleOriginARMSY is SYBaseV2 { internal virtual override - returns ( - uint256 /*amountSharesOut*/ - ) + returns (uint256 /*amountSharesOut*/ ) { if (tokenIn == yieldToken) { return amountDeposited; @@ -41,11 +39,7 @@ contract PendleOriginARMSY is SYBaseV2 { address, /*tokenOut*/ uint256 amountSharesToRedeem - ) - internal - override - returns (uint256) - { + ) internal override returns (uint256) { _transferOut(yieldToken, receiver, amountSharesToRedeem); return amountSharesToRedeem; } @@ -59,9 +53,7 @@ contract PendleOriginARMSY is SYBaseV2 { view virtual override - returns ( - uint256 /*amountSharesOut*/ - ) + returns (uint256 /*amountSharesOut*/ ) { if (tokenIn == yieldToken) return amountTokenToDeposit; else return IERC4626(yieldToken).previewDeposit(amountTokenToDeposit); @@ -71,14 +63,7 @@ contract PendleOriginARMSY is SYBaseV2 { address, /*tokenOut*/ uint256 amountSharesToRedeem - ) - internal - pure - override - returns ( - uint256 /*amountTokenOut*/ - ) - { + ) internal pure override returns (uint256 /*amountTokenOut*/ ) { return amountSharesToRedeem; } diff --git a/test/fork/LidoFixedPriceMultiLpARM/ClaimStETHWithdrawalForWETH.t.sol b/test/fork/LidoFixedPriceMultiLpARM/ClaimStETHWithdrawalForWETH.t.sol index 5ea698e8..96493087 100644 --- a/test/fork/LidoFixedPriceMultiLpARM/ClaimStETHWithdrawalForWETH.t.sol +++ b/test/fork/LidoFixedPriceMultiLpARM/ClaimStETHWithdrawalForWETH.t.sol @@ -37,7 +37,11 @@ contract Fork_Concrete_LidoARM_RequestLidoWithdrawals_Test_ is Fork_Shared_Test_ ////////////////////////////////////////////////////// /// --- PASSING TESTS ////////////////////////////////////////////////////// - function test_ClaimLidoWithdrawals_EmptyList() public asOperator requestLidoWithdrawalsOnLidoARM(new uint256[](0)) { + function test_ClaimLidoWithdrawals_EmptyList() + public + asOperator + requestLidoWithdrawalsOnLidoARM(new uint256[](0)) + { assertEq(address(lidoARM).balance, 0); assertEq(lidoARM.lidoWithdrawalQueueAmount(), 0); diff --git a/test/fork/LidoFixedPriceMultiLpARM/Deposit.t.sol b/test/fork/LidoFixedPriceMultiLpARM/Deposit.t.sol index 6c5c7e8d..9e5b8d3d 100644 --- a/test/fork/LidoFixedPriceMultiLpARM/Deposit.t.sol +++ b/test/fork/LidoFixedPriceMultiLpARM/Deposit.t.sol @@ -733,10 +733,10 @@ contract Fork_Concrete_LidoARM_Deposit_Test_ is Fork_Shared_Test_ { uint256 expectedTotalSupplyBeforeDeposit = expectTotalAssetsBeforeSwap; uint256 expectTotalAssetsBeforeDeposit = expectTotalAssetsBeforeSwap - 1 - // steth in discounted to the cross price - + ((swapInAmount * 0.999e36) / 1e36) - // weth out discounted by the buy price - - ((swapInAmount * 0.998e36) / 1e36); + // steth in discounted to the cross price + + ((swapInAmount * 0.999e36) / 1e36) + // weth out discounted by the buy price + - ((swapInAmount * 0.998e36) / 1e36); assertEq(lidoARM.totalSupply(), expectedTotalSupplyBeforeDeposit, "total supply before deposit"); assertApproxEqAbs(lidoARM.totalAssets(), expectTotalAssetsBeforeDeposit, 3, "total assets before deposit"); assertEq(lidoARM.feesAccrued(), 0, "fees accrued before swap"); diff --git a/test/fork/OriginARM/shared/ISilo.sol b/test/fork/OriginARM/shared/ISilo.sol index da24ea08..a0713060 100644 --- a/test/fork/OriginARM/shared/ISilo.sol +++ b/test/fork/OriginARM/shared/ISilo.sol @@ -172,11 +172,17 @@ interface Silo { function maxDeposit(address) external pure returns (uint256 maxAssets); function maxFlashLoan(address _token) external view returns (uint256 maxLoan); function maxMint(address) external view returns (uint256 maxShares); - function maxRedeem(address _owner, ISilo.CollateralType _collateralType) external view returns (uint256 maxShares); + function maxRedeem(address _owner, ISilo.CollateralType _collateralType) + external + view + returns (uint256 maxShares); function maxRedeem(address _owner) external view returns (uint256 maxShares); function maxRepay(address _borrower) external view returns (uint256 assets); function maxRepayShares(address _borrower) external view returns (uint256 shares); - function maxWithdraw(address _owner, ISilo.CollateralType _collateralType) external view returns (uint256 maxAssets); + function maxWithdraw(address _owner, ISilo.CollateralType _collateralType) + external + view + returns (uint256 maxAssets); function maxWithdraw(address _owner) external view returns (uint256 maxAssets); function mint(uint256 _shares, address _receiver) external returns (uint256 assets); function mint(uint256 _shares, address _receiver, ISilo.CollateralType _collateralType) @@ -194,10 +200,16 @@ interface Silo { view returns (uint256 shares); function previewDeposit(uint256 _assets) external view returns (uint256 shares); - function previewMint(uint256 _shares, ISilo.CollateralType _collateralType) external view returns (uint256 assets); + function previewMint(uint256 _shares, ISilo.CollateralType _collateralType) + external + view + returns (uint256 assets); function previewMint(uint256 _shares) external view returns (uint256 assets); function previewRedeem(uint256 _shares) external view returns (uint256 assets); - function previewRedeem(uint256 _shares, ISilo.CollateralType _collateralType) external view returns (uint256 assets); + function previewRedeem(uint256 _shares, ISilo.CollateralType _collateralType) + external + view + returns (uint256 assets); function previewRepay(uint256 _assets) external view returns (uint256 shares); function previewRepayShares(uint256 _shares) external view returns (uint256 assets); function previewWithdraw(uint256 _assets) external view returns (uint256 shares); diff --git a/test/invariants/LidoARM/TargetFunction.sol b/test/invariants/LidoARM/TargetFunction.sol index 55a3dbc6..dbbcb975 100644 --- a/test/invariants/LidoARM/TargetFunction.sol +++ b/test/invariants/LidoARM/TargetFunction.sol @@ -25,7 +25,11 @@ abstract contract TargetFunction is Properties { // Prank the user vm.prank(user); uint256[] memory amounts = lidoARM.swapExactTokensForTokens({ - amountIn: amount, amountOutMin: 0, path: path, to: address(user), deadline: block.timestamp + amountIn: amount, + amountOutMin: 0, + path: path, + to: address(user), + deadline: block.timestamp }); // Update ghost @@ -49,7 +53,11 @@ abstract contract TargetFunction is Properties { // Prank the user vm.prank(user); uint256[] memory amounts = lidoARM.swapTokensForExactTokens({ - amountOut: amount, amountInMax: type(uint256).max, path: path, to: address(user), deadline: block.timestamp + amountOut: amount, + amountInMax: type(uint256).max, + path: path, + to: address(user), + deadline: block.timestamp }); // Update ghost @@ -247,9 +255,8 @@ abstract contract TargetFunction is Properties { // Bound new cross price uint256 sell = priceScale ** 2 / lidoARM.traderate0(); uint256 buy = lidoARM.traderate1(); - newCrossPrice = _bound( - newCrossPrice, max(priceScale - lidoARM.MAX_CROSS_PRICE_DEVIATION(), buy) + 1, min(priceScale, sell) - ); + newCrossPrice = + _bound(newCrossPrice, max(priceScale - lidoARM.MAX_CROSS_PRICE_DEVIATION(), buy) + 1, min(priceScale, sell)); // Prank owner vm.prank(lidoARM.owner()); diff --git a/test/unit/OriginARM/ClaimRedeem.sol b/test/unit/OriginARM/ClaimRedeem.sol index 8e01bb65..f1332a36 100644 --- a/test/unit/OriginARM/ClaimRedeem.sol +++ b/test/unit/OriginARM/ClaimRedeem.sol @@ -43,7 +43,11 @@ contract Unit_Concrete_OriginARM_ClaimRedeem_Test_ is Unit_Shared_Test { originARM.claimRedeem(0); } - function test_RevertWhen_ClaimRedeem_Because_AlreadyClaimed() public requestRedeemAll(alice) timejump(CLAIM_DELAY) { + function test_RevertWhen_ClaimRedeem_Because_AlreadyClaimed() + public + requestRedeemAll(alice) + timejump(CLAIM_DELAY) + { // Alice claims her redeem vm.prank(alice); originARM.claimRedeem(0); From 5965428a697c1396ac89fd1e6e79a7a9e91c93fd Mon Sep 17 00:00:00 2001 From: Nicholas Addison Date: Wed, 22 Oct 2025 12:14:15 +1100 Subject: [PATCH 10/33] Added deploy of new LidoARM with the asset() function for better ERC-4626 compliance --- script/deploy/DeployManager.sol | 2 + .../mainnet/008_DeployPendleAdaptor.sol | 2 +- .../009_UpgradeLidoARMSetBufferScript.sol | 2 +- .../mainnet/010_UpgradeLidoARMAssetScript.sol | 50 +++++++++++++++++++ 4 files changed, 54 insertions(+), 2 deletions(-) create mode 100644 script/deploy/mainnet/010_UpgradeLidoARMAssetScript.sol diff --git a/script/deploy/DeployManager.sol b/script/deploy/DeployManager.sol index 03e672e2..053b1e8f 100644 --- a/script/deploy/DeployManager.sol +++ b/script/deploy/DeployManager.sol @@ -22,6 +22,7 @@ import {DeployPendleAdaptor} from "./mainnet/008_DeployPendleAdaptor.sol"; import {DeployPendleAdaptorSonic} from "./sonic/004_DeployPendleAdaptor.sol"; import {UpgradeLidoARMSetBufferScript} from "./mainnet/009_UpgradeLidoARMSetBufferScript.sol"; import {UpgradeOriginARMSetBufferScript} from "./sonic/005_UpgradeOriginARMSetBufferScript.sol"; +import {UpgradeLidoARMAssetScript} from "./mainnet/010_UpgradeLidoARMAssetScript.sol"; contract DeployManager is Script { using stdJson for string; @@ -80,6 +81,7 @@ contract DeployManager is Script { _runDeployFile(new UpgradeLidoARMMorphoScript()); _runDeployFile(new UpgradeLidoARMSetBufferScript()); _runDeployFile(new DeployPendleAdaptor()); + _runDeployFile(new UpgradeLidoARMAssetScript()); } else if (block.chainid == 17000) { // Holesky _runDeployFile(new DeployCoreHoleskyScript()); diff --git a/script/deploy/mainnet/008_DeployPendleAdaptor.sol b/script/deploy/mainnet/008_DeployPendleAdaptor.sol index dfb39f8a..00912eef 100644 --- a/script/deploy/mainnet/008_DeployPendleAdaptor.sol +++ b/script/deploy/mainnet/008_DeployPendleAdaptor.sol @@ -17,7 +17,7 @@ contract DeployPendleAdaptor is AbstractDeployScript { GovProposal public govProposal; string public constant override DEPLOY_NAME = "008_DeployPendleAdaptor"; - bool public constant override proposalExecuted = false; + bool public constant override proposalExecuted = true; function _execute() internal override { console.log("Deploy:", DEPLOY_NAME); diff --git a/script/deploy/mainnet/009_UpgradeLidoARMSetBufferScript.sol b/script/deploy/mainnet/009_UpgradeLidoARMSetBufferScript.sol index b03cb057..3a04f000 100644 --- a/script/deploy/mainnet/009_UpgradeLidoARMSetBufferScript.sol +++ b/script/deploy/mainnet/009_UpgradeLidoARMSetBufferScript.sol @@ -20,7 +20,7 @@ contract UpgradeLidoARMSetBufferScript is AbstractDeployScript { GovProposal public govProposal; string public constant override DEPLOY_NAME = "009_UpgradeLidoARMSetBufferScript"; - bool public constant override proposalExecuted = false; + bool public constant override proposalExecuted = true; Proxy morphoMarketProxy; LidoARM lidoARMImpl; diff --git a/script/deploy/mainnet/010_UpgradeLidoARMAssetScript.sol b/script/deploy/mainnet/010_UpgradeLidoARMAssetScript.sol new file mode 100644 index 00000000..6d83212c --- /dev/null +++ b/script/deploy/mainnet/010_UpgradeLidoARMAssetScript.sol @@ -0,0 +1,50 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.23; + +// Foundry imports +import {console} from "forge-std/console.sol"; + +// Contract imports +import {Proxy} from "contracts/Proxy.sol"; +import {LidoARM} from "contracts/LidoARM.sol"; +import {Mainnet} from "contracts/utils/Addresses.sol"; +import {MorphoMarket} from "contracts/markets/MorphoMarket.sol"; + +// Deployment imports +import {GovProposal, GovSixHelper} from "contracts/utils/GovSixHelper.sol"; +import {AbstractDeployScript} from "../AbstractDeployScript.sol"; + +contract UpgradeLidoARMAssetScript is AbstractDeployScript { + using GovSixHelper for GovProposal; + + GovProposal public govProposal; + + string public constant override DEPLOY_NAME = "010_UpgradeLidoARMAssetScript"; + bool public constant override proposalExecuted = false; + + Proxy morphoMarketProxy; + LidoARM lidoARMImpl; + MorphoMarket morphoMarket; + + function _execute() internal override { + console.log("Deploy:", DEPLOY_NAME); + console.log("------------"); + + // 1. Deploy new Lido implementation + uint256 claimDelay = tenderlyTestnet ? 1 minutes : 10 minutes; + lidoARMImpl = new LidoARM(Mainnet.STETH, Mainnet.WETH, Mainnet.LIDO_WITHDRAWAL, claimDelay, 1e7, 1e18); + _recordDeploy("LIDO_ARM_IMPL", address(lidoARMImpl)); + + console.log("Finished deploying", DEPLOY_NAME); + } + + function _buildGovernanceProposal() internal override { + govProposal.setDescription("Update Lido ARM to add asset() view function"); + + govProposal.action( + deployedContracts["LIDO_ARM"], "upgradeTo(address)", abi.encode(deployedContracts["LIDO_ARM_IMPL"]) + ); + + govProposal.simulate(); + } +} From e3c3f6855ee00968ef4427002752f8daa097d37b Mon Sep 17 00:00:00 2001 From: Nicholas Addison Date: Wed, 22 Oct 2025 12:20:25 +1100 Subject: [PATCH 11/33] Set Foundry version in CI --- .github/workflows/main.yml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index faff8564..d37f457b 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -23,6 +23,8 @@ jobs: - name: Install Foundry uses: foundry-rs/foundry-toolchain@v1 + with: + version: v1.3.6 - name: Show Forge version run: forge --version @@ -43,6 +45,11 @@ jobs: - name: Install Foundry uses: foundry-rs/foundry-toolchain@v1 + with: + version: v1.3.6 + + - name: Show Forge version + run: forge --version - name: Install Dependencies run: forge soldeer install && yarn install From fbd8104579eba68b1b73cb3f428e607dcfea50b1 Mon Sep 17 00:00:00 2001 From: Nicholas Addison Date: Wed, 22 Oct 2025 14:12:29 +1100 Subject: [PATCH 12/33] Changed to used authenticated Fly API --- src/js/utils/fly.js | 68 ++++++++++++++++++++++++--------------------- 1 file changed, 36 insertions(+), 32 deletions(-) diff --git a/src/js/utils/fly.js b/src/js/utils/fly.js index b26e7545..8ada2af6 100644 --- a/src/js/utils/fly.js +++ b/src/js/utils/fly.js @@ -1,5 +1,4 @@ const axios = require("axios"); -const fetch = require("node-fetch"); const { formatUnits, parseUnits, Interface } = require("ethers"); /// --- Note: --- @@ -7,7 +6,7 @@ const { formatUnits, parseUnits, Interface } = require("ethers"); /// --- const { resolveAddress } = require("./assets"); -const FlyTradeBaseURL = "https://api.fly.trade/aggregator"; +const FlyTradeBaseURL = "https://api.magpiefi.xyz/aggregator"; const log = require("./logger")("utils:fly"); @@ -91,45 +90,43 @@ const flyTradeQuote = async ({ }) => { const fromAsset = await resolveAddress(from); const toAsset = await resolveAddress(to); - const urlQuery = [ - `network=sonic`, - `fromTokenAddress=${fromAsset}`, - `toTokenAddress=${toAsset}`, - `sellAmount=${amount}`, - `fromAddress=${swapper}`, - `toAddress=${recipient}`, - `slippage=${slippage}`, - `liquiditySources=${liquiditySources}`, - `gasless=false`, - ].join("&"); + const params = { + network: "sonic", + fromTokenAddress: fromAsset, + toTokenAddress: toAsset, + sellAmount: amount, + fromAddress: swapper, + toAddress: recipient, + slippage, + liquiditySources, + gasless: false, + }; + + if (!process.env.FLY_API_KEY) { + throw new Error( + "The FLY_API_KEY environment variable must be set to call the Fly API", + ); + } try { - const response = await fetch(`${FlyTradeBaseURL}/quote?${urlQuery}`, { - method: "GET", + const response = await axios.get(`${FlyTradeBaseURL}/quote`, { + params, headers: { + apikey: process.env.FLY_API_KEY, "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36", }, }); - if (!response.ok || response.status !== 200) { - console.log("Fly.quote response:"); - console.log(response); - console.log(await response.text()); - throw new Error( - `Failed to get price quote from fly.quote: ${response.statusText}`, - ); - } - - const responseData = await response.json(); + const responseData = await response.data; - // log("FlyTrade quote response: ", responseData); + log("FlyTrade quote response data: ", responseData); const toAmount = parseUnits(responseData.amountOut, 18); const price = (amount * parseUnits("1", 36)) / toAmount; const fees = responseData.typedData.message.swapFee; const id = responseData.id; - const minAmountOut = responseData.typedData.messageamountOutMin; + const minAmountOut = responseData.typedData.message.amountOutMin; log(`Quote id : ${id}`); log(`${from}/${to} sell price: ${formatUnits(price, 4)}`); @@ -138,10 +135,12 @@ const flyTradeQuote = async ({ return { price, fromAsset, toAsset, minAmountOut, data, fees }; } catch (err) { if (err.response) { - console.error("Response data : ", err.response.data); - console.error("Response status: ", err.response.status); + console.error("Response data : ", err.response?.data); + console.error("Response status: ", err.response?.status); } - throw Error(`Call to FlyTrade quote API failed: ${err.message}`); + throw Error(`Call to FlyTrade quote API failed`, { + cause: err, + }); } }; @@ -156,6 +155,11 @@ const flyTradeTx = async ({ id }) => { try { const response = await axios.get(`${FlyTradeBaseURL}/transaction`, { params, + headers: { + apikey: process.env.FLY_API_KEY, + "User-Agent": + "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36", + }, }); // ------------------------------- ⚠️ ⚠️ ⚠️ ------------------------------- // @@ -163,7 +167,7 @@ const flyTradeTx = async ({ id }) => { // ------------------------------- ⚠️ ⚠️ ⚠️ ------------------------------- // console.log(`0x${response.data.data.slice(10)}`); - log("FlyTrade transaction response: ", response); + log("FlyTrade transaction response data: ", response.data); const iface = new Interface([ "function swapWithMagpieSignature(bytes) view returns (uint256)", @@ -181,7 +185,7 @@ const flyTradeTx = async ({ id }) => { console.error("Response data : ", err.response.data); console.error("Response status: ", err.response.status); } - throw Error(`Call to FlyTrade quote API failed: ${err.message}`); + throw Error(`Call to FlyTrade quote API failed`, { cause: err }); } }; From 31c082d5d3ed92901df1739035add101eedd5af3 Mon Sep 17 00:00:00 2001 From: Nicholas Addison Date: Wed, 22 Oct 2025 20:31:10 +1100 Subject: [PATCH 13/33] Refactor ZapperARM to make it more generic --- src/contracts/ZapperARM.sol | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/src/contracts/ZapperARM.sol b/src/contracts/ZapperARM.sol index f35f851f..56b76c92 100644 --- a/src/contracts/ZapperARM.sol +++ b/src/contracts/ZapperARM.sol @@ -11,33 +11,33 @@ import {ILiquidityProviderARM} from "./Interfaces.sol"; /** * @title Zapper contract for Automated Redemption Managers (ARMs) - * Converts S to wS and deposits it to an ARM to receive ARM LP shares. + * Converts native currency to wrapped currency, deposits it to an ARM and receives ARM LP shares. * @author Origin Protocol Inc */ contract ZapperARM is Ownable { - /// @notice The address of the wrapped S token (wS) - IWETH public immutable ws; + /// @notice The address of the wrapped token. eg WETH or wS + IWETH public immutable wrappedCurrency; event Zap(address indexed arm, address indexed sender, uint256 assets, uint256 shares); - constructor(address _ws) { - ws = IWETH(_ws); + constructor(address _wrappedCurrency) { + wrappedCurrency = IWETH(_wrappedCurrency); } - /// @notice Deposit S to OriginARM and receive ARM shares + /// @notice Convert native currency to wrapped currency, deposit it to an ARM and receive ARM shares /// @param arm The address of the ARM contract to deposit to /// @return shares The amount of ARM LP shares sent to the depositor function deposit(address arm) public payable returns (uint256 shares) { - // Wrap all S to wS - uint256 sBalance = address(this).balance; - ws.deposit{value: sBalance}(); + // Wrap all native currency sent + uint256 balance = address(this).balance; + wrappedCurrency.deposit{value: balance}(); - // Deposit all wS to the ARM - ws.approve(arm, sBalance); - shares = ILiquidityProviderARM(arm).deposit(sBalance, msg.sender); + // Deposit all wrapped currency to the ARM + wrappedCurrency.approve(arm, balance); + shares = ILiquidityProviderARM(arm).deposit(balance, msg.sender); // Emit event - emit Zap(arm, msg.sender, sBalance, shares); + emit Zap(arm, msg.sender, balance, shares); } /// @notice Rescue ERC20 tokens From 9d74e0108392701d0145fc0536c2400b795fcd65 Mon Sep 17 00:00:00 2001 From: Nicholas Addison Date: Wed, 22 Oct 2025 20:31:33 +1100 Subject: [PATCH 14/33] Add Ether.fi deploy script --- script/deploy/DeployManager.sol | 2 + .../mainnet/011_DeployEtherFiARMScript.sol | 117 ++++++++++++++++++ 2 files changed, 119 insertions(+) create mode 100644 script/deploy/mainnet/011_DeployEtherFiARMScript.sol diff --git a/script/deploy/DeployManager.sol b/script/deploy/DeployManager.sol index 053b1e8f..7aa9052a 100644 --- a/script/deploy/DeployManager.sol +++ b/script/deploy/DeployManager.sol @@ -23,6 +23,7 @@ import {DeployPendleAdaptorSonic} from "./sonic/004_DeployPendleAdaptor.sol"; import {UpgradeLidoARMSetBufferScript} from "./mainnet/009_UpgradeLidoARMSetBufferScript.sol"; import {UpgradeOriginARMSetBufferScript} from "./sonic/005_UpgradeOriginARMSetBufferScript.sol"; import {UpgradeLidoARMAssetScript} from "./mainnet/010_UpgradeLidoARMAssetScript.sol"; +import {DeployEtherFiARMScript} from "./mainnet/011_DeployEtherFiARMScript.sol"; contract DeployManager is Script { using stdJson for string; @@ -82,6 +83,7 @@ contract DeployManager is Script { _runDeployFile(new UpgradeLidoARMSetBufferScript()); _runDeployFile(new DeployPendleAdaptor()); _runDeployFile(new UpgradeLidoARMAssetScript()); + _runDeployFile(new DeployEtherFiARMScript()); } else if (block.chainid == 17000) { // Holesky _runDeployFile(new DeployCoreHoleskyScript()); diff --git a/script/deploy/mainnet/011_DeployEtherFiARMScript.sol b/script/deploy/mainnet/011_DeployEtherFiARMScript.sol new file mode 100644 index 00000000..ed938545 --- /dev/null +++ b/script/deploy/mainnet/011_DeployEtherFiARMScript.sol @@ -0,0 +1,117 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.23; + +// Foundry imports +import {console} from "forge-std/console.sol"; + +// Contract imports +import {IWETH} from "contracts/Interfaces.sol"; +import {Proxy} from "contracts/Proxy.sol"; +import {EtherFiARM} from "contracts/EtherFiARM.sol"; +import {CapManager} from "contracts/CapManager.sol"; +import {Mainnet} from "contracts/utils/Addresses.sol"; +import {MorphoMarket} from "contracts/markets/MorphoMarket.sol"; +import {ZapperARM} from "contracts/ZapperARM.sol"; + +// Deployment imports +import {GovProposal, GovSixHelper} from "contracts/utils/GovSixHelper.sol"; +import {AbstractDeployScript} from "../AbstractDeployScript.sol"; + +contract DeployEtherFiARMScript is AbstractDeployScript { + using GovSixHelper for GovProposal; + + GovProposal public govProposal; + + string public constant override DEPLOY_NAME = "011_DeployEtherFiARMScript"; + bool public constant override proposalExecuted = false; + + Proxy morphoMarketProxy; + EtherFiARM etherFiARMImpl; + MorphoMarket morphoMarket; + + function _execute() internal override { + console.log("Deploy:", DEPLOY_NAME); + console.log("------------"); + + // 1. Deploy new ARM proxy contract + Proxy armProxy = new Proxy(); + _recordDeploy("ETHER_FI_ARM", address(armProxy)); + + // 2. Deploy proxy for the CapManager + Proxy capManProxy = new Proxy(); + _recordDeploy("ETHER_FI_ARM_CAP_MAN", address(capManProxy)); + + // 3. Deploy CapManager implementation + CapManager capManagerImpl = new CapManager(address(armProxy)); + _recordDeploy("ETHER_FI_ARM_CAP_IMPL", address(capManagerImpl)); + + // 4. Initialize Proxy with CapManager implementation and set the owner to the deployer for now + bytes memory capManData = abi.encodeWithSignature("initialize(address)", Mainnet.ARM_RELAYER); + capManProxy.initialize(address(capManagerImpl), deployer, capManData); + CapManager capManager = CapManager(address(capManProxy)); + + // 4. Set total assets and liquidity provider caps + capManager.setTotalAssetsCap(25 ether); + capManager.setAccountCapEnabled(true); + address[] memory lpAccounts = new address[](1); + // TODO need to confirm which wallet Treasury will use + lpAccounts[0] = Mainnet.TREASURY; + capManager.setLiquidityProviderCaps(lpAccounts, 25 ether); + + // 5. Transfer ownership of CapManager to the mainnet 5/8 multisig + capManProxy.setOwner(Mainnet.GOV_MULTISIG); + + // 6. Deploy new Ether.Fi implementation + uint256 claimDelay = tenderlyTestnet ? 1 minutes : 10 minutes; + etherFiARMImpl = new EtherFiARM( + Mainnet.EETH, + Mainnet.WETH, + Mainnet.ETHERFI_WITHDRAWAL, + claimDelay, + 1e7, // minSharesToRedeem + 1e18, // allocateThreshold + Mainnet.ETHERFI_WITHDRAWAL_NFT, + Mainnet.ETHERFI_REDEMPTION_MANAGER + ); + _recordDeploy("ETHER_FI_ARM_IMPL", address(etherFiARMImpl)); + + // 7. Give the deployer a tiny amount of WETH for the initialization + // This can be skipped if the deployer already has WETH + IWETH(Mainnet.WETH).deposit{value: 1e13}(); + IWETH(Mainnet.WETH).approve(address(armProxy), 1e13); + + // 8. Initialize proxy, set the owner to TIMELOCK, set the operator to the ARM Relayer + bytes memory armData = abi.encodeWithSignature( + "initialize(string,string,address,uint256,address,address)", + "Ether.fi ARM", // name + "ARM-WETH-eETH", // symbol + Mainnet.ARM_RELAYER, // Operator + 2000, // 20% performance fee + Mainnet.BUYBACK_OPERATOR, // Fee collector + address(capManager) + ); + armProxy.initialize(address(etherFiARMImpl), Mainnet.TIMELOCK, armData); + + console.log("Initialized Ether.Fi ARM"); + + // 9. Deploy a Zapper that can work with different ARMs on mainnet + ZapperARM zapper = new ZapperARM(Mainnet.WETH); + zapper.setOwner(Mainnet.STRATEGIST); + _recordDeploy("ARM_ZAPPER", address(zapper)); + + console.log("Finished deploying", DEPLOY_NAME); + } + + function _buildGovernanceProposal() internal override { + govProposal.setDescription("Deploy the Ether.Fi ARM with a CapManager"); + + govProposal.action( + deployedContracts["ETHER_FI_ARM"], "upgradeTo(address)", abi.encode(deployedContracts["ETHER_FI_ARM_IMPL"]) + ); + + uint256 crossPrice = 0.9998 * 1e36; + govProposal.action(deployedContracts["ETHER_FI_ARM"], "setCrossPrice(uint256)", abi.encode(crossPrice)); + + govProposal.simulate(); + } +} From ca2a7b3497d453407f4436371dabf2a62dc706ee Mon Sep 17 00:00:00 2001 From: Nicholas Addison Date: Wed, 22 Oct 2025 20:31:51 +1100 Subject: [PATCH 15/33] Add Ether.fi smoke tests --- test/smoke/EtherFiARMSmokeTest.t.sol | 210 +++++++++++++++++++++++++++ 1 file changed, 210 insertions(+) create mode 100644 test/smoke/EtherFiARMSmokeTest.t.sol diff --git a/test/smoke/EtherFiARMSmokeTest.t.sol b/test/smoke/EtherFiARMSmokeTest.t.sol new file mode 100644 index 00000000..5104d4a3 --- /dev/null +++ b/test/smoke/EtherFiARMSmokeTest.t.sol @@ -0,0 +1,210 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.23; + +import {Test, console2} from "forge-std/Test.sol"; + +import {AbstractSmokeTest} from "./AbstractSmokeTest.sol"; + +import {IERC20, IEETHWithdrawal} from "contracts/Interfaces.sol"; +import {EtherFiARM} from "contracts/EtherFiARM.sol"; +import {CapManager} from "contracts/CapManager.sol"; +import {Proxy} from "contracts/Proxy.sol"; +import {Mainnet} from "contracts/utils/Addresses.sol"; +import {console} from "forge-std/console.sol"; + +contract Fork_EtherFiARM_Smoke_Test is AbstractSmokeTest { + IERC20 BAD_TOKEN = IERC20(makeAddr("bad token")); + + IERC20 weth; + IERC20 eeth; + Proxy armProxy; + EtherFiARM etherFiARM; + CapManager capManager; + address operator; + + function setUp() public { + weth = IERC20(resolver.resolve("WETH")); + eeth = IERC20(resolver.resolve("EETH")); + operator = resolver.resolve("OPERATOR"); + + vm.label(address(weth), "WETH"); + vm.label(address(eeth), "eETH"); + vm.label(address(operator), "OPERATOR"); + + armProxy = Proxy(payable(deployManager.getDeployment("ETHER_FI_ARM"))); + etherFiARM = EtherFiARM(payable(deployManager.getDeployment("ETHER_FI_ARM"))); + capManager = CapManager(deployManager.getDeployment("ETHER_FI_ARM_CAP_MAN")); + + // Only fuzz from this address. Big speedup on fork. + targetSender(address(this)); + } + + function test_initialConfig() external view { + assertEq(etherFiARM.name(), "Ether.fi ARM", "Name"); + assertEq(etherFiARM.symbol(), "ARM-WETH-eETH", "Symbol"); + assertEq(etherFiARM.owner(), Mainnet.TIMELOCK, "Owner"); + assertEq(etherFiARM.operator(), Mainnet.ARM_RELAYER, "Operator"); + assertEq(etherFiARM.feeCollector(), Mainnet.BUYBACK_OPERATOR, "Fee collector"); + assertEq((100 * uint256(etherFiARM.fee())) / etherFiARM.FEE_SCALE(), 20, "Performance fee as a percentage"); + // LidoLiquidityManager + assertEq(address(etherFiARM.etherfiWithdrawalQueue()), Mainnet.ETHERFI_WITHDRAWAL, "Ether.fi withdrawal queue"); + assertEq(address(etherFiARM.eeth()), Mainnet.EETH, "eETH"); + assertEq(address(etherFiARM.weth()), Mainnet.WETH, "WETH"); + assertEq(etherFiARM.liquidityAsset(), Mainnet.WETH, "liquidity asset"); + assertEq(etherFiARM.asset(), Mainnet.WETH, "ERC-4626 asset"); + assertEq(etherFiARM.claimDelay(), 10 minutes, "claim delay"); + assertEq(etherFiARM.crossPrice(), 0.9998e36, "cross price"); + + assertEq(capManager.accountCapEnabled(), true, "account cap enabled"); + assertEq(capManager.operator(), Mainnet.ARM_RELAYER, "Operator"); + assertEq(capManager.arm(), address(etherFiARM), "arm"); + } + + function test_swap_exact_eeth_for_weth() external { + // trader sells eETH and buys WETH, the ARM buys eETH as a + // 4 bps discount + _swapExactTokensForTokens(eeth, weth, 0.9996e36, 100 ether); + // 10 bps discount + _swapExactTokensForTokens(eeth, weth, 0.999e36, 1e15); + // 20 bps discount + _swapExactTokensForTokens(eeth, weth, 0.998e36, 1 ether); + } + + function test_swap_exact_weth_for_eeth() external { + // trader buys eETH and sells WETH, the ARM sells eETH at a + // 0.5 bps discount + _swapExactTokensForTokens(weth, eeth, 0.99995e36, 10 ether); + // 1 bps discount + _swapExactTokensForTokens(weth, eeth, 0.9999e36, 100 ether); + } + + function test_swapTokensForExactTokens() external { + // trader sells eETH and buys WETH, the ARM buys eETH at a + // 4 bps discount + _swapTokensForExactTokens(eeth, weth, 0.9996e36, 10 ether); + // 10 bps discount + _swapTokensForExactTokens(eeth, weth, 0.999e36, 100 ether); + // 50 bps discount + _swapTokensForExactTokens(eeth, weth, 0.995e36, 10 ether); + } + + function _swapExactTokensForTokens(IERC20 inToken, IERC20 outToken, uint256 price, uint256 amountIn) internal { + uint256 expectedOut; + if (inToken == weth) { + // Trader is buying eETH and selling WETH + // the ARM is selling eETH and buying WETH + deal(address(weth), address(this), 1_000_000 ether); + _dealEETH(address(etherFiARM), 1000 ether); + + expectedOut = amountIn * 1e36 / price; + + vm.prank(Mainnet.ARM_RELAYER); + etherFiARM.setPrices(price - 2e32, price); + } else { + // Trader is selling eETH and buying WETH + // the ARM is buying eETH and selling WETH + _dealEETH(address(this), 1000 ether); + deal(address(weth), address(etherFiARM), 1_000_000 ether); + + expectedOut = amountIn * price / 1e36; + + vm.prank(Mainnet.ARM_RELAYER); + uint256 sellPrice = price < 0.9997e36 ? 0.9999e36 : price + 2e32; + etherFiARM.setPrices(price, sellPrice); + } + // Approve the ARM to transfer the input token of the swap. + inToken.approve(address(etherFiARM), amountIn); + + uint256 startIn = inToken.balanceOf(address(this)); + uint256 startOut = outToken.balanceOf(address(this)); + + etherFiARM.swapExactTokensForTokens(inToken, outToken, amountIn, 0, address(this)); + + assertApproxEqAbs(inToken.balanceOf(address(this)), startIn - amountIn, 2, "In actual"); + assertApproxEqAbs(outToken.balanceOf(address(this)), startOut + expectedOut, 2, "Out actual"); + } + + function _swapTokensForExactTokens(IERC20 inToken, IERC20 outToken, uint256 price, uint256 amountOut) internal { + uint256 expectedIn; + if (inToken == weth) { + // Trader is buying eETH and selling WETH + // the ARM is selling eETH and buying WETH + deal(address(weth), address(this), 1_000_000 ether); + _dealEETH(address(etherFiARM), 1000 ether); + + expectedIn = amountOut * price / 1e36; + + vm.prank(Mainnet.ARM_RELAYER); + etherFiARM.setPrices(price - 2e32, price); + } else { + // Trader is selling eETH and buying WETH + // the ARM is buying eETH and selling WETH + _dealEETH(address(this), 1000 ether); + deal(address(weth), address(etherFiARM), 1_000_000 ether); + // _dealWETH(address(etherFiARM), 1000 ether); + + expectedIn = amountOut * 1e36 / price + 3; + + vm.prank(Mainnet.ARM_RELAYER); + uint256 sellPrice = price < 0.9997e36 ? 0.9999e36 : price + 2e32; + etherFiARM.setPrices(price, sellPrice); + } + // Approve the ARM to transfer the input token of the swap. + inToken.approve(address(etherFiARM), expectedIn + 10000); + console.log("Approved Lido ARM to spend %d", inToken.allowance(address(this), address(etherFiARM))); + console.log("In token balance: %d", inToken.balanceOf(address(this))); + + uint256 startIn = inToken.balanceOf(address(this)); + uint256 startOut = outToken.balanceOf(address(this)); + + etherFiARM.swapTokensForExactTokens(inToken, outToken, amountOut, 3 * amountOut, address(this)); + + assertApproxEqAbs(inToken.balanceOf(address(this)), startIn - expectedIn, 2, "In actual"); + assertApproxEqAbs(outToken.balanceOf(address(this)), startOut + amountOut, 2, "Out actual"); + } + + function test_proxy_unauthorizedAccess() external { + address RANDOM_ADDRESS = 0xfEEDBeef00000000000000000000000000000000; + vm.startPrank(RANDOM_ADDRESS); + + // Proxy's restricted methods. + vm.expectRevert("ARM: Only owner can call this function."); + armProxy.setOwner(RANDOM_ADDRESS); + + vm.expectRevert("ARM: Only owner can call this function."); + armProxy.initialize(address(this), address(this), ""); + + vm.expectRevert("ARM: Only owner can call this function."); + armProxy.upgradeTo(address(this)); + + vm.expectRevert("ARM: Only owner can call this function."); + armProxy.upgradeToAndCall(address(this), ""); + + // Implementation's restricted methods. + vm.expectRevert("ARM: Only owner can call this function."); + etherFiARM.setOwner(RANDOM_ADDRESS); + } + + // TODO replace _dealEETH with just deal + function _dealEETH(address to, uint256 amount) internal { + vm.prank(0x22162DbBa43fE0477cdC5234E248264eC7C6EA7c); + eeth.transfer(to, amount + 2); + // deal(address(eeth), to, amount); + } + + /* Operator Tests */ + + function test_setOperator() external { + vm.prank(Mainnet.TIMELOCK); + etherFiARM.setOperator(address(this)); + assertEq(etherFiARM.operator(), address(this)); + } + + function test_nonOwnerCannotSetOperator() external { + vm.expectRevert("ARM: Only owner can call this function."); + vm.prank(operator); + etherFiARM.setOperator(operator); + } + + error InvalidInitialization(); +} From e770cde9428c4eebac92414f3b555d191a7c2633 Mon Sep 17 00:00:00 2001 From: Nicholas Addison Date: Wed, 22 Oct 2025 20:32:43 +1100 Subject: [PATCH 16/33] Made onERC721Received a pure function --- src/contracts/EtherFiARM.sol | 1 + 1 file changed, 1 insertion(+) diff --git a/src/contracts/EtherFiARM.sol b/src/contracts/EtherFiARM.sol index b7cc7c30..353eabd7 100644 --- a/src/contracts/EtherFiARM.sol +++ b/src/contracts/EtherFiARM.sol @@ -174,6 +174,7 @@ contract EtherFiARM is Initializable, AbstractARM, IERC721Receiver { /// @notice To be able to receive the NFTs from the EtherFi withdrawal queue contract. function onERC721Received(address operator, address from, uint256 tokenId, bytes calldata data) external + pure override returns (bytes4) { From 6d76e40734eb6c8bfcb8f4b46bf53cfc8f03f8a2 Mon Sep 17 00:00:00 2001 From: Nicholas Addison Date: Wed, 22 Oct 2025 20:39:08 +1100 Subject: [PATCH 17/33] Increased the Treasury cap limit to 250 WETH --- script/deploy/mainnet/011_DeployEtherFiARMScript.sol | 4 ++-- test/smoke/EtherFiARMSmokeTest.t.sol | 2 ++ 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/script/deploy/mainnet/011_DeployEtherFiARMScript.sol b/script/deploy/mainnet/011_DeployEtherFiARMScript.sol index ed938545..cca7d855 100644 --- a/script/deploy/mainnet/011_DeployEtherFiARMScript.sol +++ b/script/deploy/mainnet/011_DeployEtherFiARMScript.sol @@ -51,12 +51,12 @@ contract DeployEtherFiARMScript is AbstractDeployScript { CapManager capManager = CapManager(address(capManProxy)); // 4. Set total assets and liquidity provider caps - capManager.setTotalAssetsCap(25 ether); + capManager.setTotalAssetsCap(250 ether); capManager.setAccountCapEnabled(true); address[] memory lpAccounts = new address[](1); // TODO need to confirm which wallet Treasury will use lpAccounts[0] = Mainnet.TREASURY; - capManager.setLiquidityProviderCaps(lpAccounts, 25 ether); + capManager.setLiquidityProviderCaps(lpAccounts, 250 ether); // 5. Transfer ownership of CapManager to the mainnet 5/8 multisig capManProxy.setOwner(Mainnet.GOV_MULTISIG); diff --git a/test/smoke/EtherFiARMSmokeTest.t.sol b/test/smoke/EtherFiARMSmokeTest.t.sol index 5104d4a3..1712a728 100644 --- a/test/smoke/EtherFiARMSmokeTest.t.sol +++ b/test/smoke/EtherFiARMSmokeTest.t.sol @@ -56,6 +56,8 @@ contract Fork_EtherFiARM_Smoke_Test is AbstractSmokeTest { assertEq(etherFiARM.crossPrice(), 0.9998e36, "cross price"); assertEq(capManager.accountCapEnabled(), true, "account cap enabled"); + assertEq(capManager.totalAssetsCap(), 250 ether, "total assets cap"); + assertEq(capManager.liquidityProviderCaps(Mainnet.TREASURY), 250 ether, "liquidity provider cap"); assertEq(capManager.operator(), Mainnet.ARM_RELAYER, "Operator"); assertEq(capManager.arm(), address(etherFiARM), "arm"); } From 8a161a4632ba1bbe0455526ed0d7f4dbe32944e7 Mon Sep 17 00:00:00 2001 From: Nicholas Addison Date: Thu, 23 Oct 2025 17:11:56 +1100 Subject: [PATCH 18/33] Updated Treasury address for the Ether.fi ARM --- script/deploy/mainnet/011_DeployEtherFiARMScript.sol | 2 +- src/contracts/utils/Addresses.sol | 1 + test/smoke/EtherFiARMSmokeTest.t.sol | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/script/deploy/mainnet/011_DeployEtherFiARMScript.sol b/script/deploy/mainnet/011_DeployEtherFiARMScript.sol index cca7d855..01cf5ccc 100644 --- a/script/deploy/mainnet/011_DeployEtherFiARMScript.sol +++ b/script/deploy/mainnet/011_DeployEtherFiARMScript.sol @@ -55,7 +55,7 @@ contract DeployEtherFiARMScript is AbstractDeployScript { capManager.setAccountCapEnabled(true); address[] memory lpAccounts = new address[](1); // TODO need to confirm which wallet Treasury will use - lpAccounts[0] = Mainnet.TREASURY; + lpAccounts[0] = Mainnet.TREASURY_LP; capManager.setLiquidityProviderCaps(lpAccounts, 250 ether); // 5. Transfer ownership of CapManager to the mainnet 5/8 multisig diff --git a/src/contracts/utils/Addresses.sol b/src/contracts/utils/Addresses.sol index abdb347a..2ef71d3c 100644 --- a/src/contracts/utils/Addresses.sol +++ b/src/contracts/utils/Addresses.sol @@ -14,6 +14,7 @@ library Mainnet { address public constant GOVERNOR_SIX = 0x1D3Fbd4d129Ddd2372EA85c5Fa00b2682081c9EC; address public constant STRATEGIST = 0x4FF1b9D9ba8558F5EAfCec096318eA0d8b541971; address public constant TREASURY = 0x70fCE97d671E81080CA3ab4cc7A59aAc2E117137; + address public constant TREASURY_LP = 0x6E3fddab68Bf1EBaf9daCF9F7907c7Bc0951D1dc; // Multisig and EOAs address public constant INITIAL_DEPLOYER = address(0x1001); diff --git a/test/smoke/EtherFiARMSmokeTest.t.sol b/test/smoke/EtherFiARMSmokeTest.t.sol index 1712a728..62916631 100644 --- a/test/smoke/EtherFiARMSmokeTest.t.sol +++ b/test/smoke/EtherFiARMSmokeTest.t.sol @@ -57,7 +57,7 @@ contract Fork_EtherFiARM_Smoke_Test is AbstractSmokeTest { assertEq(capManager.accountCapEnabled(), true, "account cap enabled"); assertEq(capManager.totalAssetsCap(), 250 ether, "total assets cap"); - assertEq(capManager.liquidityProviderCaps(Mainnet.TREASURY), 250 ether, "liquidity provider cap"); + assertEq(capManager.liquidityProviderCaps(Mainnet.TREASURY_LP), 250 ether, "liquidity provider cap"); assertEq(capManager.operator(), Mainnet.ARM_RELAYER, "Operator"); assertEq(capManager.arm(), address(etherFiARM), "arm"); } From f362ef6bbae4fc72d9adb45cefa34171aacd0be8 Mon Sep 17 00:00:00 2001 From: Nicholas Addison Date: Thu, 23 Oct 2025 18:56:11 +1100 Subject: [PATCH 19/33] Added requestEtherFiWithdrawal smoke tests --- test/smoke/EtherFiARMSmokeTest.t.sol | 26 +++++++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/test/smoke/EtherFiARMSmokeTest.t.sol b/test/smoke/EtherFiARMSmokeTest.t.sol index 62916631..20ff043f 100644 --- a/test/smoke/EtherFiARMSmokeTest.t.sol +++ b/test/smoke/EtherFiARMSmokeTest.t.sol @@ -208,5 +208,29 @@ contract Fork_EtherFiARM_Smoke_Test is AbstractSmokeTest { etherFiARM.setOperator(operator); } - error InvalidInitialization(); + function test_request_etherfi_withdrawal_operator() external { + // trader sells eETH and buys WETH, the ARM buys eETH as a 4 bps discount + _swapExactTokensForTokens(eeth, weth, 0.9996e36, 100 ether); + + // Expected events + vm.expectEmit(true, false, false, false, address(etherFiARM)); + emit EtherFiARM.RequestEtherFiWithdrawal(10 ether, 0); + + // Operator requests an Ether.fi withdrawal + vm.prank(Mainnet.ARM_RELAYER); + etherFiARM.requestEtherFiWithdrawal(10 ether); + } + + function test_request_etherfi_withdrawal_owner() external { + // trader sells eETH and buys WETH, the ARM buys eETH as a 4 bps discount + _swapExactTokensForTokens(eeth, weth, 0.9996e36, 100 ether); + + // Expected events + vm.expectEmit(true, false, false, false, address(etherFiARM)); + emit EtherFiARM.RequestEtherFiWithdrawal(10 ether, 0); + + // Owner requests an Ether.fi withdrawal + vm.prank(Mainnet.TIMELOCK); + etherFiARM.requestEtherFiWithdrawal(10 ether); + } } From d80af0637635dc87e06b2b0d9a5dd77a73a35782 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment?= Date: Thu, 23 Oct 2025 14:17:49 +0200 Subject: [PATCH 20/33] add smoke test for claim request and redeem. --- test/smoke/EtherFiARMSmokeTest.t.sol | 36 +++++++++++++++++++++++++++- 1 file changed, 35 insertions(+), 1 deletion(-) diff --git a/test/smoke/EtherFiARMSmokeTest.t.sol b/test/smoke/EtherFiARMSmokeTest.t.sol index 20ff043f..8049888d 100644 --- a/test/smoke/EtherFiARMSmokeTest.t.sol +++ b/test/smoke/EtherFiARMSmokeTest.t.sol @@ -5,7 +5,7 @@ import {Test, console2} from "forge-std/Test.sol"; import {AbstractSmokeTest} from "./AbstractSmokeTest.sol"; -import {IERC20, IEETHWithdrawal} from "contracts/Interfaces.sol"; +import {IERC20, IEETHWithdrawal, IEETHWithdrawalNFT} from "contracts/Interfaces.sol"; import {EtherFiARM} from "contracts/EtherFiARM.sol"; import {CapManager} from "contracts/CapManager.sol"; import {Proxy} from "contracts/Proxy.sol"; @@ -20,6 +20,7 @@ contract Fork_EtherFiARM_Smoke_Test is AbstractSmokeTest { Proxy armProxy; EtherFiARM etherFiARM; CapManager capManager; + IEETHWithdrawalNFT etherfiWithdrawalNFT; address operator; function setUp() public { @@ -34,6 +35,7 @@ contract Fork_EtherFiARM_Smoke_Test is AbstractSmokeTest { armProxy = Proxy(payable(deployManager.getDeployment("ETHER_FI_ARM"))); etherFiARM = EtherFiARM(payable(deployManager.getDeployment("ETHER_FI_ARM"))); capManager = CapManager(deployManager.getDeployment("ETHER_FI_ARM_CAP_MAN")); + etherfiWithdrawalNFT = IEETHWithdrawalNFT(Mainnet.ETHERFI_WITHDRAWAL_NFT); // Only fuzz from this address. Big speedup on fork. targetSender(address(this)); @@ -233,4 +235,36 @@ contract Fork_EtherFiARM_Smoke_Test is AbstractSmokeTest { vm.prank(Mainnet.TIMELOCK); etherFiARM.requestEtherFiWithdrawal(10 ether); } + + function test_claim_etherfi_request_with_delay() external { + // trader sells eETH and buys WETH, the ARM buys eETH as a 4 bps discount + _swapExactTokensForTokens(eeth, weth, 0.9996e36, 100 ether); + + // Owner requests an Ether.fi withdrawal + vm.prank(Mainnet.TIMELOCK); + uint256 requestId = etherFiARM.requestEtherFiWithdrawal(10 ether); + + // Process finalization on withdrawal queue + // We cheat a bit here, because we don't follow the full finalization process it could fail + // if there is not enough liquidity, but since the amount to claim is low, it should be fine + vm.prank(0x0EF8fa4760Db8f5Cd4d993f3e3416f30f942D705); + etherfiWithdrawalNFT.finalizeRequests(requestId); + + // Claim the withdrawal + uint256[] memory requestIdArray = new uint256[](1); + requestIdArray[0] = requestId; + etherFiARM.claimEtherFiWithdrawals(requestIdArray); + } + + function test_claim_etherfi_request_without_delay() external { + // trader sells eETH and buys WETH, the ARM buys eETH as a 4 bps discount + _swapExactTokensForTokens(eeth, weth, 0.9996e36, 100 ether); + + // Overload the liquidity pool to ensure there is enough liquidity to redeem + deal(Mainnet.ETHERFI_LIQUIDITY_POOL, 1_000_000 ether); + + // Redeem eETH for WETH via the EtherFi Redemption Manager + vm.prank(Mainnet.ARM_RELAYER); + etherFiARM.redeemEETH(1 ether); + } } From 9bd99ee733d9ebdb63300b3a749186bf3b581352 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment?= Date: Thu, 23 Oct 2025 15:54:24 +0200 Subject: [PATCH 21/33] add UpgradeOETHARMScript for deploying and upgrading OETH ARM implementation --- script/deploy/DeployManager.sol | 2 + .../mainnet/012_UpgradeOETHARMScript.sol | 63 +++++++++++++++++++ 2 files changed, 65 insertions(+) create mode 100644 script/deploy/mainnet/012_UpgradeOETHARMScript.sol diff --git a/script/deploy/DeployManager.sol b/script/deploy/DeployManager.sol index 7aa9052a..daf9e2f1 100644 --- a/script/deploy/DeployManager.sol +++ b/script/deploy/DeployManager.sol @@ -24,6 +24,7 @@ import {UpgradeLidoARMSetBufferScript} from "./mainnet/009_UpgradeLidoARMSetBuff import {UpgradeOriginARMSetBufferScript} from "./sonic/005_UpgradeOriginARMSetBufferScript.sol"; import {UpgradeLidoARMAssetScript} from "./mainnet/010_UpgradeLidoARMAssetScript.sol"; import {DeployEtherFiARMScript} from "./mainnet/011_DeployEtherFiARMScript.sol"; +import {UpgradeOETHARMScript} from "./mainnet/012_UpgradeOETHARMScript.sol"; contract DeployManager is Script { using stdJson for string; @@ -84,6 +85,7 @@ contract DeployManager is Script { _runDeployFile(new DeployPendleAdaptor()); _runDeployFile(new UpgradeLidoARMAssetScript()); _runDeployFile(new DeployEtherFiARMScript()); + _runDeployFile(new UpgradeOETHARMScript()); } else if (block.chainid == 17000) { // Holesky _runDeployFile(new DeployCoreHoleskyScript()); diff --git a/script/deploy/mainnet/012_UpgradeOETHARMScript.sol b/script/deploy/mainnet/012_UpgradeOETHARMScript.sol new file mode 100644 index 00000000..982245fe --- /dev/null +++ b/script/deploy/mainnet/012_UpgradeOETHARMScript.sol @@ -0,0 +1,63 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.23; + +// Foundry imports +import {console} from "forge-std/console.sol"; + +// Contract imports +import {Proxy} from "contracts/Proxy.sol"; +import {Mainnet} from "contracts/utils/Addresses.sol"; +import {OriginARM} from "contracts/OriginARM.sol"; + +// Deployment imports +import {GovProposal, GovSixHelper} from "contracts/utils/GovSixHelper.sol"; +import {AbstractDeployScript} from "../AbstractDeployScript.sol"; + +contract UpgradeOETHARMScript is AbstractDeployScript { + using GovSixHelper for GovProposal; + + GovProposal public govProposal; + + string public constant override DEPLOY_NAME = "012_UpgradeOETHARMScript"; + bool public constant override proposalExecuted = false; + + Proxy morphoMarketProxy; + OriginARM originARMImpl; + OriginARM oethARM; + + + function _execute() internal override { + console.log("Deploy:", DEPLOY_NAME); + console.log("------------"); + + // 1. Deploy new Origin implementation + uint256 claimDelay = tenderlyTestnet ? 1 minutes : 10 minutes; + originARMImpl = new OriginARM(Mainnet.OETH, Mainnet.WETH, Mainnet.OETH_VAULT, claimDelay, 1e7, 1e18); + _recordDeploy("OETH_ARM_IMPL", address(originARMImpl)); + + console.log("Finished deploying", DEPLOY_NAME); + } + + function _buildGovernanceProposal() internal override { + govProposal.setDescription("Update OETH ARM to use Origin ARM contract"); + + govProposal.action( + deployedContracts["OETH_ARM"], "upgradeTo(address)", abi.encode(deployedContracts["OETH_ARM_IMPL"]) + ); + + govProposal.simulate(); + } + + function _fork() internal override { + oethARM = OriginARM(deployedContracts["OETH_ARM"]); + + vm.prank(Mainnet.TIMELOCK); + oethARM.setOperator(Mainnet.STRATEGIST); + + vm.prank(Mainnet.STRATEGIST); + oethARM.setPrices(0.99975e36, 0.9999e36); + + vm.prank(Mainnet.TIMELOCK); + oethARM.setCrossPrice(1e36); + } +} From 3d38d1dbfdd2d3c06cd41f02bba44af80ea7d438 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment?= Date: Fri, 24 Oct 2025 07:29:08 +0200 Subject: [PATCH 22/33] fix gov proposal --- .../mainnet/012_UpgradeOETHARMScript.sol | 41 ++++++++++++------- 1 file changed, 26 insertions(+), 15 deletions(-) diff --git a/script/deploy/mainnet/012_UpgradeOETHARMScript.sol b/script/deploy/mainnet/012_UpgradeOETHARMScript.sol index 982245fe..62f8e782 100644 --- a/script/deploy/mainnet/012_UpgradeOETHARMScript.sol +++ b/script/deploy/mainnet/012_UpgradeOETHARMScript.sol @@ -25,7 +25,6 @@ contract UpgradeOETHARMScript is AbstractDeployScript { OriginARM originARMImpl; OriginARM oethARM; - function _execute() internal override { console.log("Deploy:", DEPLOY_NAME); console.log("------------"); @@ -39,25 +38,37 @@ contract UpgradeOETHARMScript is AbstractDeployScript { } function _buildGovernanceProposal() internal override { + // This is a cheat to remove. + // To upgrade the OETH ARM, the Timelock needs to have WETH and approve the ARM contract to pull WETH, + // to mint shares during initialization. So we simulate that by transferring WETH to the Timelock here. + // This can be removed once the Timelock has a WETH balance. + vm.prank(Mainnet.STRATEGIST); + (bool success,) = + Mainnet.WETH.call(abi.encodeWithSignature("transfer(address,uint256)", Mainnet.TIMELOCK, 1e12)); + require(success, "WETH transfer failed"); + govProposal.setDescription("Update OETH ARM to use Origin ARM contract"); + // 1. Timelock needs to approve the OETH ARM to pull WETH for initialization. + govProposal.action(Mainnet.WETH, "approve(address,uint256)", abi.encode(deployedContracts["OETH_ARM"], 1e12)); + + // 2. Upgrade the OETH ARM implementation, and initialize. + bytes memory initializeData = abi.encodeWithSelector( + OriginARM.initialize.selector, + "Origin ARM", + "ARM-WETH-OETH", + Mainnet.ARM_RELAYER, + 2000, // 20% performance fee + Mainnet.ARM_BUYBACK, + address(0) + ); + govProposal.action( - deployedContracts["OETH_ARM"], "upgradeTo(address)", abi.encode(deployedContracts["OETH_ARM_IMPL"]) + deployedContracts["OETH_ARM"], + "upgradeToAndCall(address,bytes)", + abi.encode(deployedContracts["OETH_ARM_IMPL"], initializeData) ); govProposal.simulate(); } - - function _fork() internal override { - oethARM = OriginARM(deployedContracts["OETH_ARM"]); - - vm.prank(Mainnet.TIMELOCK); - oethARM.setOperator(Mainnet.STRATEGIST); - - vm.prank(Mainnet.STRATEGIST); - oethARM.setPrices(0.99975e36, 0.9999e36); - - vm.prank(Mainnet.TIMELOCK); - oethARM.setCrossPrice(1e36); - } } From 3166a00cbacafad57df560fecc9efa0523e1ddb7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment?= Date: Fri, 24 Oct 2025 08:31:40 +0200 Subject: [PATCH 23/33] remove cheat for gov proposal --- script/deploy/mainnet/012_UpgradeOETHARMScript.sol | 9 --------- 1 file changed, 9 deletions(-) diff --git a/script/deploy/mainnet/012_UpgradeOETHARMScript.sol b/script/deploy/mainnet/012_UpgradeOETHARMScript.sol index 62f8e782..a5dc930c 100644 --- a/script/deploy/mainnet/012_UpgradeOETHARMScript.sol +++ b/script/deploy/mainnet/012_UpgradeOETHARMScript.sol @@ -38,15 +38,6 @@ contract UpgradeOETHARMScript is AbstractDeployScript { } function _buildGovernanceProposal() internal override { - // This is a cheat to remove. - // To upgrade the OETH ARM, the Timelock needs to have WETH and approve the ARM contract to pull WETH, - // to mint shares during initialization. So we simulate that by transferring WETH to the Timelock here. - // This can be removed once the Timelock has a WETH balance. - vm.prank(Mainnet.STRATEGIST); - (bool success,) = - Mainnet.WETH.call(abi.encodeWithSignature("transfer(address,uint256)", Mainnet.TIMELOCK, 1e12)); - require(success, "WETH transfer failed"); - govProposal.setDescription("Update OETH ARM to use Origin ARM contract"); // 1. Timelock needs to approve the OETH ARM to pull WETH for initialization. From a62325f406e296e672e28af779670da30c7e639e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment?= Date: Fri, 24 Oct 2025 08:31:50 +0200 Subject: [PATCH 24/33] wip smoke test --- src/contracts/utils/Addresses.sol | 1 + test/smoke/OethARMSmokeTest.t.sol | 186 ++++++++++++++++++++++++------ 2 files changed, 152 insertions(+), 35 deletions(-) diff --git a/src/contracts/utils/Addresses.sol b/src/contracts/utils/Addresses.sol index 2ef71d3c..0baaba2f 100644 --- a/src/contracts/utils/Addresses.sol +++ b/src/contracts/utils/Addresses.sol @@ -30,6 +30,7 @@ library Mainnet { address public constant EETH = 0x35fA164735182de50811E8e2E824cFb9B6118ac2; address public constant WEETH = 0xCd5fE23C85820F7B72D0926FC9b05b43E359b7ee; address public constant STETH = 0xae7ab96520DE3A18E5e111B5EaAb095312D7fE84; + address public constant WOETH = 0xDcEe70654261AF21C44c093C300eD3Bb97b78192; address public constant WSTETH = 0x7f39C581F595B53c5cb19bD0b3f8dA6c935E2Ca0; // Contracts diff --git a/test/smoke/OethARMSmokeTest.t.sol b/test/smoke/OethARMSmokeTest.t.sol index ed15f0d6..a021a840 100644 --- a/test/smoke/OethARMSmokeTest.t.sol +++ b/test/smoke/OethARMSmokeTest.t.sol @@ -6,17 +6,17 @@ import {Test, console2} from "forge-std/Test.sol"; import {AbstractSmokeTest} from "./AbstractSmokeTest.sol"; import {IERC20} from "contracts/Interfaces.sol"; -import {OethARM} from "contracts/OethARM.sol"; import {Proxy} from "contracts/Proxy.sol"; import {Mainnet} from "contracts/utils/Addresses.sol"; +import {OriginARM} from "contracts/OriginARM.sol"; -contract Fork_OethARM_Smoke_Test is AbstractSmokeTest { +contract Fork_OriginARM_Smoke_Test is AbstractSmokeTest { IERC20 BAD_TOKEN = IERC20(makeAddr("bad token")); IERC20 weth; IERC20 oeth; Proxy proxy; - OethARM oethARM; + OriginARM oethARM; address operator; function setUp() public { @@ -29,7 +29,7 @@ contract Fork_OethARM_Smoke_Test is AbstractSmokeTest { vm.label(address(operator), "OPERATOR"); proxy = Proxy(payable(deployManager.getDeployment("OETH_ARM"))); - oethARM = OethARM(deployManager.getDeployment("OETH_ARM")); + oethARM = OriginARM(deployManager.getDeployment("OETH_ARM")); _dealWETH(address(oethARM), 100 ether); _dealOETH(address(oethARM), 100 ether); @@ -38,52 +38,176 @@ contract Fork_OethARM_Smoke_Test is AbstractSmokeTest { targetSender(address(this)); } - function test_swapExactTokensForTokens() external { - _swapExactTokensForTokens(oeth, weth, 10 ether, 10 ether); + //////////////////////////////////////////////////// + /// --- HELPERS + //////////////////////////////////////////////////// + function _dealWETH(address to, uint256 amount) internal { + deal(address(weth), to, amount); + } + + // Helper functions to deal tokens from whales, because oeth is rebasing, so deal() doesn't work + function _dealOETH(address to, uint256 amount) internal { + vm.prank(Mainnet.WOETH); + oeth.transfer(to, amount); } - function test_swapTokensForExactTokens() external { - _swapTokensForExactTokens(oeth, weth, 10 ether, 10 ether); + //////////////////////////////////////////////////// + /// --- INITIAL CONFIG + //////////////////////////////////////////////////// + function test_initialConfig() external view { + // Ownership and fees + assertEq(oethARM.name(), "Origin ARM", "Name"); + assertEq(oethARM.symbol(), "ARM-WETH-OETH", "Symbol"); + assertEq(oethARM.owner(), Mainnet.TIMELOCK, "Owner"); + assertEq(oethARM.operator(), Mainnet.ARM_RELAYER, "Operator"); + assertEq(oethARM.feeCollector(), Mainnet.ARM_BUYBACK, "Fee collector"); + assertEq((100 * uint256(oethARM.fee())) / oethARM.FEE_SCALE(), 20, "Performance fee as a percentage"); + + // Assets + assertEq(address(oethARM.token0()), address(weth), "token0"); + assertEq(address(oethARM.token1()), address(oeth), "token1"); + assertEq(oethARM.liquidityAsset(), Mainnet.WETH, "liquidity asset"); + assertEq(oethARM.baseAsset(), Mainnet.OETH, "base asset"); + assertEq(oethARM.asset(), Mainnet.WETH, "ERC-4626 asset"); + + // Prices + assertNotEq(oethARM.crossPrice(), 0, "cross price"); + assertNotEq(oethARM.traderate0(), 0, "traderate0"); + assertNotEq(oethARM.traderate1(), 0, "traderate1"); + + // Redemption + assertEq(address(oethARM.vault()), Mainnet.OETH_VAULT, "OETH Vault"); + assertEq(oethARM.claimDelay(), 10 minutes, "claim delay"); } - function _swapExactTokensForTokens(IERC20 inToken, IERC20 outToken, uint256 amountIn, uint256 expectedOut) - internal - { + //////////////////////////////////////////////////// + /// --- SWAP TESTS + //////////////////////////////////////////////////// + + function test_swap_exact_oeth_for_weth() external { + // trader sells OETH and buys WETH, the ARM buys OETH as a + // 4 bps discount + _swapExactTokensForTokens(oeth, weth, 0.9996e36, 100 ether); + // 10 bps discount + _swapExactTokensForTokens(oeth, weth, 0.999e36, 1e15); + // 20 bps discount + _swapExactTokensForTokens(oeth, weth, 0.998e36, 1 ether); + } + + function test_swap_exact_weth_for_oeth() external { + // For this test, we need to set the cross price to 0.9999e36, which requires + // moving out all OETH from the ARM. + vm.startPrank(address(oethARM)); + oeth.transfer(address(this), oeth.balanceOf(address(oethARM))); + vm.stopPrank(); + vm.prank(Mainnet.TIMELOCK); + oethARM.setCrossPrice(0.9999e36); + + // trader buys OETH and sells WETH, the ARM sells OETH at a + // 0.5 bps discount + _swapExactTokensForTokens(weth, oeth, 0.99995e36, 10 ether); + // 1 bps discount + _swapExactTokensForTokens(weth, oeth, 0.9999e36, 100 ether); + } + + function _swapExactTokensForTokens(IERC20 inToken, IERC20 outToken, uint256 price, uint256 amountIn) internal { + uint256 expectedOut; if (inToken == weth) { - _dealWETH(address(this), amountIn + 1000); + // Trader is buying stETH and selling WETH + // the ARM is selling stETH and buying WETH + deal(address(weth), address(this), 1_000_000 ether); + _dealOETH(address(oethARM), 1000 ether); + + expectedOut = amountIn * 1e36 / price; + + vm.prank(Mainnet.ARM_RELAYER); + oethARM.setPrices(price - 2e32, price); } else { - _dealOETH(address(this), amountIn + 1000); + // Trader is selling stETH and buying WETH + // the ARM is buying stETH and selling WETH + _dealOETH(address(this), 1000 ether); + deal(address(weth), address(oethARM), 1_000_000 ether); + + expectedOut = amountIn * price / 1e36; + + vm.prank(Mainnet.ARM_RELAYER); + oethARM.setPrices(price, 1e36); } // Approve the ARM to transfer the input token of the swap. inToken.approve(address(oethARM), amountIn); uint256 startIn = inToken.balanceOf(address(this)); uint256 startOut = outToken.balanceOf(address(this)); + oethARM.swapExactTokensForTokens(inToken, outToken, amountIn, 0, address(this)); - assertEq(inToken.balanceOf(address(this)), startIn - amountIn, "In actual"); - assertEq(outToken.balanceOf(address(this)), startOut + expectedOut, "Out actual"); + + assertApproxEqAbs(inToken.balanceOf(address(this)), startIn - amountIn, 2, "In actual"); + assertApproxEqAbs(outToken.balanceOf(address(this)), startOut + expectedOut, 2, "Out actual"); + } + + function test_swap_oeth_for_exact_weth() external { + // trader sells OETH and buys WETH, the ARM buys OETH at a + // 4 bps discount + _swapTokensForExactTokens(oeth, weth, 0.9996e36, 10 ether); + // 10 bps discount + _swapTokensForExactTokens(oeth, weth, 0.999e36, 100 ether); + // 50 bps discount + _swapTokensForExactTokens(oeth, weth, 0.995e36, 10 ether); } - function _swapTokensForExactTokens(IERC20 inToken, IERC20 outToken, uint256 amountIn, uint256 expectedOut) - internal - { + function test_swap_weth_for_exact_oeth() external { + // For this test, we need to set the cross price to 0.9999e36, which requires + // moving out all OETH from the ARM. + vm.startPrank(address(oethARM)); + oeth.transfer(address(this), oeth.balanceOf(address(oethARM))); + vm.stopPrank(); + vm.prank(Mainnet.TIMELOCK); + oethARM.setCrossPrice(0.9999e36); + + // trader buys OETH and sells WETH, the ARM sells OETH at a + // 0.5 bps discount + _swapTokensForExactTokens(weth, oeth, 0.99995e36, 10 ether); + // 1 bps discount + _swapTokensForExactTokens(weth, oeth, 0.9999e36, 100 ether); + } + + function _swapTokensForExactTokens(IERC20 inToken, IERC20 outToken, uint256 price, uint256 amountOut) internal { + uint256 expectedIn; if (inToken == weth) { - _dealWETH(address(this), amountIn + 1000); + // Trader is buying stETH and selling WETH + // the ARM is selling stETH and buying WETH + deal(address(weth), address(this), 1_000_000 ether); + _dealOETH(address(oethARM), 1000 ether); + + expectedIn = amountOut * price / 1e36; + + vm.prank(Mainnet.ARM_RELAYER); + oethARM.setPrices(price - 2e32, price); } else { - _dealOETH(address(this), amountIn + 1000); + // Trader is selling stETH and buying WETH + // the ARM is buying stETH and selling WETH + _dealOETH(address(this), 1000 ether); + deal(address(weth), address(oethARM), 1_000_000 ether); + + expectedIn = amountOut * 1e36 / price + 3; + + vm.prank(Mainnet.ARM_RELAYER); + oethARM.setPrices(price, 1e36); } // Approve the ARM to transfer the input token of the swap. - inToken.approve(address(oethARM), amountIn); + inToken.approve(address(oethARM), expectedIn + 10000); uint256 startIn = inToken.balanceOf(address(this)); + uint256 startOut = outToken.balanceOf(address(this)); + + oethARM.swapTokensForExactTokens(inToken, outToken, amountOut, 3 * amountOut, address(this)); - oethARM.swapTokensForExactTokens(inToken, outToken, expectedOut, 3 * expectedOut, address(this)); - assertEq(inToken.balanceOf(address(this)), startIn - amountIn, "In actual"); - assertEq(outToken.balanceOf(address(this)), expectedOut, "Out actual"); + assertApproxEqAbs(inToken.balanceOf(address(this)), startIn - expectedIn, 3, "In actual"); + assertApproxEqAbs(outToken.balanceOf(address(this)), startOut + amountOut, 3, "Out actual"); } function test_unauthorizedAccess() external { - address RANDOM_ADDRESS = 0xfEEDBeef00000000000000000000000000000000; + address RANDOM_ADDRESS = vm.randomAddress(); vm.startPrank(RANDOM_ADDRESS); // Proxy's restricted methods. @@ -148,7 +272,7 @@ contract Fork_OethARM_Smoke_Test is AbstractSmokeTest { oethARM.swapTokensForExactTokens(oeth, oeth, 10 ether, 10 ether, address(this)); } - function test_collectTokens() external { + /*function test_collectTokens() external { vm.startPrank(Mainnet.TIMELOCK); oethARM.transferToken(address(weth), address(this), weth.balanceOf(address(oethARM))); @@ -161,15 +285,7 @@ contract Fork_OethARM_Smoke_Test is AbstractSmokeTest { vm.stopPrank(); } - - function _dealOETH(address to, uint256 amount) internal { - vm.prank(0xDcEe70654261AF21C44c093C300eD3Bb97b78192); - oeth.transfer(to, amount); - } - - function _dealWETH(address to, uint256 amount) internal { - deal(address(weth), to, amount); - } + */ /* Operator Tests */ From e317063f921843610b1c276243afaf302e8f04ab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment?= Date: Fri, 24 Oct 2025 09:18:35 +0200 Subject: [PATCH 25/33] transfer out oeth and weth before initialization --- .../mainnet/012_UpgradeOETHARMScript.sol | 22 +++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/script/deploy/mainnet/012_UpgradeOETHARMScript.sol b/script/deploy/mainnet/012_UpgradeOETHARMScript.sol index a5dc930c..ad26be9d 100644 --- a/script/deploy/mainnet/012_UpgradeOETHARMScript.sol +++ b/script/deploy/mainnet/012_UpgradeOETHARMScript.sol @@ -8,6 +8,7 @@ import {console} from "forge-std/console.sol"; import {Proxy} from "contracts/Proxy.sol"; import {Mainnet} from "contracts/utils/Addresses.sol"; import {OriginARM} from "contracts/OriginARM.sol"; +import {IERC20} from "contracts/Interfaces.sol"; // Deployment imports import {GovProposal, GovSixHelper} from "contracts/utils/GovSixHelper.sol"; @@ -40,10 +41,27 @@ contract UpgradeOETHARMScript is AbstractDeployScript { function _buildGovernanceProposal() internal override { govProposal.setDescription("Update OETH ARM to use Origin ARM contract"); - // 1. Timelock needs to approve the OETH ARM to pull WETH for initialization. + + // 1. Transfer OETH out of the existing OETH ARM, to have a clean assets per share ratio. + uint256 balanceOETH = IERC20(Mainnet.OETH).balanceOf(deployedContracts["OETH_ARM"]); + govProposal.action( + deployedContracts["OETH_ARM"], + "transferToken(address,address,uint256)", + abi.encode(Mainnet.OETH, Mainnet.TREASURY_LP, balanceOETH) + ); + + // 2. Transfer WETH out of the existing OETH ARM, to have a clean assets per share ratio. + uint256 balanceWETH = IERC20(Mainnet.WETH).balanceOf(deployedContracts["OETH_ARM"]); + govProposal.action( + deployedContracts["OETH_ARM"], + "transferToken(address,address,uint256)", + abi.encode(Mainnet.WETH, Mainnet.TREASURY_LP, balanceWETH) + ); + + // 3. Timelock needs to approve the OETH ARM to pull WETH for initialization. govProposal.action(Mainnet.WETH, "approve(address,uint256)", abi.encode(deployedContracts["OETH_ARM"], 1e12)); - // 2. Upgrade the OETH ARM implementation, and initialize. + // 4. Upgrade the OETH ARM implementation, and initialize. bytes memory initializeData = abi.encodeWithSelector( OriginARM.initialize.selector, "Origin ARM", From abd30ca7e40285b9e79c815f8741441249f2a485 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment?= Date: Fri, 24 Oct 2025 09:19:11 +0200 Subject: [PATCH 26/33] add more smoke tests --- test/smoke/OethARMSmokeTest.t.sol | 97 ++++++++++++------------------- 1 file changed, 37 insertions(+), 60 deletions(-) diff --git a/test/smoke/OethARMSmokeTest.t.sol b/test/smoke/OethARMSmokeTest.t.sol index a021a840..43c7b9d7 100644 --- a/test/smoke/OethARMSmokeTest.t.sol +++ b/test/smoke/OethARMSmokeTest.t.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.23; -import {Test, console2} from "forge-std/Test.sol"; +import {Test, console} from "forge-std/Test.sol"; import {AbstractSmokeTest} from "./AbstractSmokeTest.sol"; @@ -206,98 +206,75 @@ contract Fork_OriginARM_Smoke_Test is AbstractSmokeTest { assertApproxEqAbs(outToken.balanceOf(address(this)), startOut + amountOut, 3, "Out actual"); } - function test_unauthorizedAccess() external { - address RANDOM_ADDRESS = vm.randomAddress(); - vm.startPrank(RANDOM_ADDRESS); - - // Proxy's restricted methods. - vm.expectRevert("ARM: Only owner can call this function."); - proxy.setOwner(RANDOM_ADDRESS); - - vm.expectRevert("ARM: Only owner can call this function."); - proxy.initialize(address(this), address(this), ""); - - vm.expectRevert("ARM: Only owner can call this function."); - proxy.upgradeTo(address(this)); - - vm.expectRevert("ARM: Only owner can call this function."); - proxy.upgradeToAndCall(address(this), ""); - - // Implementation's restricted methods. - vm.expectRevert("ARM: Only owner can call this function."); - oethARM.setOwner(RANDOM_ADDRESS); - } - function test_wrongInTokenExactIn() external { - vm.expectRevert("ARM: Invalid swap"); + vm.expectRevert("ARM: Invalid in token"); oethARM.swapExactTokensForTokens(BAD_TOKEN, oeth, 10 ether, 0, address(this)); - vm.expectRevert("ARM: Invalid swap"); + vm.expectRevert("ARM: Invalid in token"); oethARM.swapExactTokensForTokens(BAD_TOKEN, weth, 10 ether, 0, address(this)); - vm.expectRevert("ARM: Invalid swap"); - oethARM.swapExactTokensForTokens(weth, weth, 10 ether, 0, address(this)); - vm.expectRevert("ARM: Invalid swap"); - oethARM.swapExactTokensForTokens(oeth, oeth, 10 ether, 0, address(this)); } function test_wrongOutTokenExactIn() external { - vm.expectRevert("ARM: Invalid swap"); + vm.expectRevert("ARM: Invalid out token"); oethARM.swapTokensForExactTokens(weth, BAD_TOKEN, 10 ether, 10 ether, address(this)); - vm.expectRevert("ARM: Invalid swap"); + vm.expectRevert("ARM: Invalid out token"); oethARM.swapTokensForExactTokens(oeth, BAD_TOKEN, 10 ether, 10 ether, address(this)); - vm.expectRevert("ARM: Invalid swap"); + vm.expectRevert("ARM: Invalid out token"); oethARM.swapTokensForExactTokens(weth, weth, 10 ether, 10 ether, address(this)); - vm.expectRevert("ARM: Invalid swap"); + vm.expectRevert("ARM: Invalid out token"); oethARM.swapTokensForExactTokens(oeth, oeth, 10 ether, 10 ether, address(this)); } function test_wrongInTokenExactOut() external { - vm.expectRevert("ARM: Invalid swap"); + vm.expectRevert("ARM: Invalid in token"); oethARM.swapTokensForExactTokens(BAD_TOKEN, oeth, 10 ether, 10 ether, address(this)); - vm.expectRevert("ARM: Invalid swap"); + vm.expectRevert("ARM: Invalid in token"); oethARM.swapTokensForExactTokens(BAD_TOKEN, weth, 10 ether, 10 ether, address(this)); - vm.expectRevert("ARM: Invalid swap"); - oethARM.swapTokensForExactTokens(weth, weth, 10 ether, 10 ether, address(this)); - vm.expectRevert("ARM: Invalid swap"); - oethARM.swapTokensForExactTokens(oeth, oeth, 10 ether, 10 ether, address(this)); } function test_wrongOutTokenExactOut() external { - vm.expectRevert("ARM: Invalid swap"); + vm.expectRevert("ARM: Invalid out token"); oethARM.swapTokensForExactTokens(weth, BAD_TOKEN, 10 ether, 10 ether, address(this)); - vm.expectRevert("ARM: Invalid swap"); + vm.expectRevert("ARM: Invalid out token"); oethARM.swapTokensForExactTokens(oeth, BAD_TOKEN, 10 ether, 10 ether, address(this)); - vm.expectRevert("ARM: Invalid swap"); + vm.expectRevert("ARM: Invalid out token"); oethARM.swapTokensForExactTokens(weth, weth, 10 ether, 10 ether, address(this)); - vm.expectRevert("ARM: Invalid swap"); + vm.expectRevert("ARM: Invalid out token"); oethARM.swapTokensForExactTokens(oeth, oeth, 10 ether, 10 ether, address(this)); } - /*function test_collectTokens() external { - vm.startPrank(Mainnet.TIMELOCK); + //////////////////////////////////////////////////// + /// --- AUTHORIZATION + //////////////////////////////////////////////////// + function test_unauthorizedAccess() external { + address RANDOM_ADDRESS = vm.randomAddress(); + vm.startPrank(RANDOM_ADDRESS); - oethARM.transferToken(address(weth), address(this), weth.balanceOf(address(oethARM))); - assertGt(weth.balanceOf(address(this)), 50 ether); - assertEq(weth.balanceOf(address(oethARM)), 0); + // Proxy's restricted methods. + vm.expectRevert("ARM: Only owner can call this function."); + proxy.setOwner(RANDOM_ADDRESS); - oethARM.transferToken(address(oeth), address(this), oeth.balanceOf(address(oethARM))); - assertGt(oeth.balanceOf(address(this)), 50 ether); - assertLt(oeth.balanceOf(address(oethARM)), 3); + vm.expectRevert("ARM: Only owner can call this function."); + proxy.initialize(address(this), address(this), ""); + + vm.expectRevert("ARM: Only owner can call this function."); + proxy.upgradeTo(address(this)); + + vm.expectRevert("ARM: Only owner can call this function."); + proxy.upgradeToAndCall(address(this), ""); + // Implementation's restricted methods. + vm.expectRevert("ARM: Only owner can call this function."); + oethARM.setOwner(RANDOM_ADDRESS); vm.stopPrank(); - } - */ - /* Operator Tests */ + vm.expectRevert("ARM: Only owner can call this function."); + vm.prank(operator); + oethARM.setOperator(operator); + } function test_setOperator() external { vm.prank(Mainnet.TIMELOCK); oethARM.setOperator(address(this)); assertEq(oethARM.operator(), address(this)); } - - function test_nonOwnerCannotSetOperator() external { - vm.expectRevert("ARM: Only owner can call this function."); - vm.prank(operator); - oethARM.setOperator(operator); - } } From b4586249861dc9cc564f5b45ff3ca6e2036391a6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment?= Date: Fri, 24 Oct 2025 09:48:55 +0200 Subject: [PATCH 27/33] finalize smoke tests --- test/smoke/OethARMSmokeTest.t.sol | 43 ++++++++++++++++++++++++++++++- 1 file changed, 42 insertions(+), 1 deletion(-) diff --git a/test/smoke/OethARMSmokeTest.t.sol b/test/smoke/OethARMSmokeTest.t.sol index 43c7b9d7..720ab5a2 100644 --- a/test/smoke/OethARMSmokeTest.t.sol +++ b/test/smoke/OethARMSmokeTest.t.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.23; -import {Test, console} from "forge-std/Test.sol"; +import {Test} from "forge-std/Test.sol"; import {AbstractSmokeTest} from "./AbstractSmokeTest.sol"; @@ -277,4 +277,45 @@ contract Fork_OriginARM_Smoke_Test is AbstractSmokeTest { oethARM.setOperator(address(this)); assertEq(oethARM.operator(), address(this)); } + + //////////////////////////////////////////////////// + /// --- VAULT WITHDRAWALS + //////////////////////////////////////////////////// + function test_request_origin_withdrawal() external { + _dealOETH(address(oethARM), 10 ether); + vm.prank(Mainnet.ARM_RELAYER); + uint256 requestId = oethARM.requestOriginWithdrawal(10 ether); + assertNotEq(requestId, 0); + } + + function test_claim_origin_withdrawal() external { + // Cheat section + // Deal OETH to the ARM, in order to have some asset to withdraw + _dealOETH(address(oethARM), 10 ether); + // Deal WETH to this test account to mint OETH in the Vault, directly increasing + // the Vault's liquidity doesn't work because of the "Backing supply liquidity error" check + _dealWETH(address(this), 10_000 ether); + (bool success,) = + Mainnet.WETH.call(abi.encodeWithSignature("approve(address,uint256)", Mainnet.OETH_VAULT, 10_000 ether)); + require(success, "Approve failed"); + (success,) = Mainnet.OETH_VAULT.call( + abi.encodeWithSignature("mint(address,uint256,uint256)", Mainnet.WETH, 10_000 ether, 0) + ); + require(success, "Mint failed"); + // End cheat section + + // Request a withdrawal + vm.prank(Mainnet.ARM_RELAYER); + uint256 requestId = oethARM.requestOriginWithdrawal(10 ether); + + // Fast forward time by 1 day to pass the claim delay + vm.warp(block.timestamp + 1 days); + + // Claim the withdrawal + uint256[] memory requestIds = new uint256[](1); + requestIds[0] = requestId; + + uint256 amountClaimed = oethARM.claimOriginWithdrawals(requestIds); + assertEq(amountClaimed, 10 ether); + } } From 63053155eeeed236f2f5fbab11d446ab28bee94b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment?= Date: Fri, 24 Oct 2025 11:20:33 +0200 Subject: [PATCH 28/33] fmt --- script/deploy/mainnet/012_UpgradeOETHARMScript.sol | 1 - 1 file changed, 1 deletion(-) diff --git a/script/deploy/mainnet/012_UpgradeOETHARMScript.sol b/script/deploy/mainnet/012_UpgradeOETHARMScript.sol index ad26be9d..282085df 100644 --- a/script/deploy/mainnet/012_UpgradeOETHARMScript.sol +++ b/script/deploy/mainnet/012_UpgradeOETHARMScript.sol @@ -41,7 +41,6 @@ contract UpgradeOETHARMScript is AbstractDeployScript { function _buildGovernanceProposal() internal override { govProposal.setDescription("Update OETH ARM to use Origin ARM contract"); - // 1. Transfer OETH out of the existing OETH ARM, to have a clean assets per share ratio. uint256 balanceOETH = IERC20(Mainnet.OETH).balanceOf(deployedContracts["OETH_ARM"]); govProposal.action( From 2cea3a8b6767159f3f82b8354dbbb3796c403944 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment?= <55331875+clement-ux@users.noreply.github.com> Date: Thu, 30 Oct 2025 00:55:24 +0100 Subject: [PATCH 29/33] Remove old ARM (#135) * remove old OETH ARM * fix: correct contract names to use consistent LidoARM casing * remove unused import * fix failing test * fix failing test * generate puml files --- Makefile | 2 +- README.md | 7 +- docs/OEthARMHierarchy.svg | 113 --------------- docs/OEthARMSquashed.svg | 70 --------- docs/OEthARMStorage.svg | 57 -------- docs/OriginARMHierarchy.svg | 60 ++++++++ docs/OriginARMPublicSquashed.svg | 108 ++++++++++++++ docs/OriginARMSquashed.svg | 135 ++++++++++++++++++ docs/generate.sh | 16 ++- docs/plantuml/originContracts.png | Bin 0 -> 26302 bytes docs/plantuml/originContracts.puml | 34 +++++ foundry.toml | 2 +- script/deploy/DeployManager.sol | 8 -- .../deploy/holesky/001_DeployCoreScript.sol | 33 ----- script/deploy/holesky/002_UpgradeScript.sol | 38 ----- .../deploy/mainnet/001_DeployCoreScript.sol | 53 ------- script/deploy/mainnet/002_UpgradeScript.sol | 39 ----- .../005_RegisterLidoWithdrawalsScript.sol | 1 - .../deploy/sonic/001_DeployOriginARMProxy.sol | 1 - src/contracts/Interfaces.sol | 94 ------------ src/contracts/OethARM.sol | 35 ----- src/contracts/OethLiquidityManager.sol | 62 -------- src/contracts/OwnerLP.sol | 14 -- src/contracts/PeggedARM.sol | 49 ------- test/Base.sol | 5 - test/fork/Harvester/Swap.sol | 1 - .../ClaimRedeem.t.sol | 0 .../ClaimStETHWithdrawalForWETH.t.sol | 0 .../CollectFees.t.sol | 0 .../Constructor.t.sol | 1 + .../Deposit.t.sol | 0 test/fork/LidoARM/Proxy.t.sol | 79 ++++++++++ .../RequestRedeem.t.sol | 0 .../RequestStETHWithdrawalForETH.t.sol | 0 .../SetCrossPrice.t.sol | 0 .../Setters.t.sol | 3 +- .../SwapExactTokensForTokens.t.sol | 0 .../SwapTokensForExactTokens.t.sol | 0 .../TotalAssets.t.sol | 4 - test/fork/OethARM/Ownable.t.sol | 49 ------- test/fork/OethARM/Proxy.t.sol | 69 --------- .../OethARM/SwapExactTokensForTokens.t.sol | 132 ----------------- .../OethARM/SwapTokensForExactTokens.t.sol | 131 ----------------- test/fork/OethARM/Transfer.t.sol | 57 -------- test/fork/OethARM/Withdraw.t.sol | 105 -------------- test/fork/shared/Shared.sol | 19 +-- test/fork/utils/Modifiers.sol | 7 - test/invariants/LidoARM/FuzzerFoundry.sol | 2 +- test/invariants/LidoARM/Unit.sol | 6 +- test/smoke/EtherFiARMSmokeTest.t.sol | 4 +- test/smoke/LidoARMSmokeTest.t.sol | 2 - test/smoke/OethARMSmokeTest.t.sol | 118 +++++++-------- 52 files changed, 498 insertions(+), 1327 deletions(-) delete mode 100644 docs/OEthARMHierarchy.svg delete mode 100644 docs/OEthARMSquashed.svg delete mode 100644 docs/OEthARMStorage.svg create mode 100644 docs/OriginARMHierarchy.svg create mode 100644 docs/OriginARMPublicSquashed.svg create mode 100644 docs/OriginARMSquashed.svg create mode 100644 docs/plantuml/originContracts.png create mode 100644 docs/plantuml/originContracts.puml delete mode 100644 script/deploy/holesky/001_DeployCoreScript.sol delete mode 100644 script/deploy/holesky/002_UpgradeScript.sol delete mode 100644 script/deploy/mainnet/001_DeployCoreScript.sol delete mode 100644 script/deploy/mainnet/002_UpgradeScript.sol delete mode 100644 src/contracts/OethARM.sol delete mode 100644 src/contracts/OethLiquidityManager.sol delete mode 100644 src/contracts/OwnerLP.sol delete mode 100644 src/contracts/PeggedARM.sol rename test/fork/{LidoFixedPriceMultiLpARM => LidoARM}/ClaimRedeem.t.sol (100%) rename test/fork/{LidoFixedPriceMultiLpARM => LidoARM}/ClaimStETHWithdrawalForWETH.t.sol (100%) rename test/fork/{LidoFixedPriceMultiLpARM => LidoARM}/CollectFees.t.sol (100%) rename test/fork/{LidoFixedPriceMultiLpARM => LidoARM}/Constructor.t.sol (95%) rename test/fork/{LidoFixedPriceMultiLpARM => LidoARM}/Deposit.t.sol (100%) create mode 100644 test/fork/LidoARM/Proxy.t.sol rename test/fork/{LidoFixedPriceMultiLpARM => LidoARM}/RequestRedeem.t.sol (100%) rename test/fork/{LidoFixedPriceMultiLpARM => LidoARM}/RequestStETHWithdrawalForETH.t.sol (100%) rename test/fork/{LidoFixedPriceMultiLpARM => LidoARM}/SetCrossPrice.t.sol (100%) rename test/fork/{LidoFixedPriceMultiLpARM => LidoARM}/Setters.t.sol (99%) rename test/fork/{LidoFixedPriceMultiLpARM => LidoARM}/SwapExactTokensForTokens.t.sol (100%) rename test/fork/{LidoFixedPriceMultiLpARM => LidoARM}/SwapTokensForExactTokens.t.sol (100%) rename test/fork/{LidoFixedPriceMultiLpARM => LidoARM}/TotalAssets.t.sol (98%) delete mode 100644 test/fork/OethARM/Ownable.t.sol delete mode 100644 test/fork/OethARM/Proxy.t.sol delete mode 100644 test/fork/OethARM/SwapExactTokensForTokens.t.sol delete mode 100644 test/fork/OethARM/SwapTokensForExactTokens.t.sol delete mode 100644 test/fork/OethARM/Transfer.t.sol delete mode 100644 test/fork/OethARM/Withdraw.t.sol diff --git a/Makefile b/Makefile index 26e52aad..754d9de2 100644 --- a/Makefile +++ b/Makefile @@ -42,7 +42,7 @@ test-all: @$(MAKE) test-std test-invariant-lido: - @FOUNDRY_INVARIANT_FAIL_ON_REVERT=false FOUNDRY_MATCH_CONTRACT=FuzzerFoundry_OethARM $(MAKE) test-std + @FOUNDRY_INVARIANT_FAIL_ON_REVERT=false FOUNDRY_MATCH_CONTRACT=FuzzerFoundry_LidoARM $(MAKE) test-std test-invariant-origin: @FOUNDRY_INVARIANT_FAIL_ON_REVERT=true FOUNDRY_MATCH_CONTRACT=FuzzerFoundry_OriginARM $(MAKE) test-std diff --git a/README.md b/README.md index c51142bb..b53f5b7a 100644 --- a/README.md +++ b/README.md @@ -239,9 +239,10 @@ If the verification doesn't work with the deployment, it can be done separately For example ``` -# Verify OethARM -forge verify-contract 0xd8fF298eAed581f74ab845Af62C48aCF85B2f05e OethARM \ - --constructor-args $(cast abi-encode "constructor(address,address,address)" 0x856c4Efb76C1D1AE02e20CEB03A2A6a08b0b8dC3 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2 0x39254033945AA2E4809Cc2977E7087BEE48bd7Ab ) +# Verify LidoARM +forge verify-contract 0xeC6FdCc3904F8dD6a9cbbBCC41B741df5963B42E LidoARM \ + --constructor-args $(cast abi-encode "constructor(address,address,address,uint256,uint256,int256)" 0xae7ab96520DE3A18E5e111B5EaAb095312D7fE84 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2 0x889edC2eDab5f40e902b864aD4d7AdE8E412F9B1 600 0 0 ) + # Verify Proxy forge verify-contract 0x6bac785889A4127dB0e0CeFEE88E0a9F1Aaf3cC7 Proxy diff --git a/docs/OEthARMHierarchy.svg b/docs/OEthARMHierarchy.svg deleted file mode 100644 index 89dbae31..00000000 --- a/docs/OEthARMHierarchy.svg +++ /dev/null @@ -1,113 +0,0 @@ - - - - - - -UmlClassDiagram - - - -0 - -<<Abstract>> -AbstractARM -../src/contracts/AbstractARM.sol - - - -22 - -OwnableOperable -../src/contracts/OwnableOperable.sol - - - -0->22 - - - - - -19 - -OethARM -../src/contracts/OethARM.sol - - - -20 - -OethLiquidityManager -../src/contracts/OethLiquidityManager.sol - - - -19->20 - - - - - -23 - -<<Abstract>> -OwnerLP -../src/contracts/OwnerLP.sol - - - -19->23 - - - - - -24 - -<<Abstract>> -PeggedARM -../src/contracts/PeggedARM.sol - - - -19->24 - - - - - -20->22 - - - - - -21 - -Ownable -../src/contracts/Ownable.sol - - - -22->21 - - - - - -23->21 - - - - - -24->0 - - - - - diff --git a/docs/OEthARMSquashed.svg b/docs/OEthARMSquashed.svg deleted file mode 100644 index 62cc57f9..00000000 --- a/docs/OEthARMSquashed.svg +++ /dev/null @@ -1,70 +0,0 @@ - - - - - - -UmlClassDiagram - - - -19 - -OethARM -../src/contracts/OethARM.sol - -Private: -   _gap: uint256[49] <<OwnableOperable>> -   _gap: uint256[50] <<AbstractARM>> -Internal: -   OWNER_SLOT: bytes32 <<Ownable>> -Public: -   operator: address <<OwnableOperable>> -   token0: IERC20 <<AbstractARM>> -   token1: IERC20 <<AbstractARM>> -   bothDirections: bool <<PeggedARM>> -   oeth: address <<OethLiquidityManager>> -   oethVault: address <<OethLiquidityManager>> - -Internal: -    _owner(): (ownerOut: address) <<Ownable>> -    _setOwner(newOwner: address) <<Ownable>> -    _onlyOwner() <<Ownable>> -    _initOwnableOperable(_operator: address) <<OwnableOperable>> -    _setOperator(newOperator: address) <<OwnableOperable>> -    _swapExactTokensForTokens(inToken: IERC20, outToken: IERC20, amountIn: uint256, to: address): (amountOut: uint256) <<PeggedARM>> -    _swapTokensForExactTokens(inToken: IERC20, outToken: IERC20, amountOut: uint256, to: address): (amountIn: uint256) <<PeggedARM>> -    _inDeadline(deadline: uint256) <<AbstractARM>> -    _transferAsset(asset: address, to: address, amount: uint256) <<AbstractARM>> -    _transferAssetFrom(asset: address, from: address, to: address, amount: uint256) <<AbstractARM>> -    _swap(inToken: IERC20, outToken: IERC20, amount: uint256, to: address): uint256 <<PeggedARM>> -    _approvals() <<OethLiquidityManager>> -External: -    owner(): address <<Ownable>> -    setOwner(newOwner: address) <<onlyOwner>> <<Ownable>> -    transferToken(token: address, to: address, amount: uint256) <<onlyOwner>> <<OwnerLP>> -    setOperator(newOperator: address) <<onlyOwner>> <<OwnableOperable>> -    swapExactTokensForTokens(inToken: IERC20, outToken: IERC20, amountIn: uint256, amountOutMin: uint256, to: address) <<AbstractARM>> -    swapExactTokensForTokens(amountIn: uint256, amountOutMin: uint256, path: address[], to: address, deadline: uint256): (amounts: uint256[]) <<AbstractARM>> -    swapTokensForExactTokens(inToken: IERC20, outToken: IERC20, amountOut: uint256, amountInMax: uint256, to: address) <<AbstractARM>> -    swapTokensForExactTokens(amountOut: uint256, amountInMax: uint256, path: address[], to: address, deadline: uint256): (amounts: uint256[]) <<AbstractARM>> -    approvals() <<onlyOwner>> <<OethLiquidityManager>> -    requestWithdrawal(amount: uint256): (requestId: uint256, queued: uint256) <<onlyOperatorOrOwner>> <<OethLiquidityManager>> -    claimWithdrawal(requestId: uint256) <<onlyOperatorOrOwner>> <<OethLiquidityManager>> -    claimWithdrawals(requestIds: uint256[]) <<onlyOperatorOrOwner>> <<OethLiquidityManager>> -    initialize(_operator: address) <<initializer>> <<OethARM>> -Public: -    <<event>> AdminChanged(previousAdmin: address, newAdmin: address) <<Ownable>> -    <<event>> OperatorChanged(newAdmin: address) <<OwnableOperable>> -    <<modifier>> onlyOwner() <<Ownable>> -    <<modifier>> onlyOperatorOrOwner() <<OwnableOperable>> -    constructor() <<Ownable>> -    constructor(_oeth: address, _oethVault: address) <<OethLiquidityManager>> -    constructor(_bothDirections: bool) <<PeggedARM>> -    constructor(_oeth: address, _weth: address, _oethVault: address) <<OethARM>> - - - diff --git a/docs/OEthARMStorage.svg b/docs/OEthARMStorage.svg deleted file mode 100644 index 05a02ce2..00000000 --- a/docs/OEthARMStorage.svg +++ /dev/null @@ -1,57 +0,0 @@ - - - - - - -StorageDiagram - - - -1 - -OethARM <<Contract>> - -slot - -0 - -1-50 - -51 - -52-101 - -0x360..bbd - -0xb53..104 - -type: <inherited contract>.variable (bytes) - -unallocated (30) - -bool: Initializable.initializing (1) - -bool: Initializable.initialized (1) - -uint256[50]: Initializable.gap (1600) - -unallocated (12) - -address: OwnableOperable.operator (20) - -uint256[50]: OwnableOperable._gap (1600) - -unallocated (12) - -address: eip1967.proxy.implementation (20) - -unallocated (12) - -address: eip1967.proxy.admin (20) - - - diff --git a/docs/OriginARMHierarchy.svg b/docs/OriginARMHierarchy.svg new file mode 100644 index 00000000..3d21b159 --- /dev/null +++ b/docs/OriginARMHierarchy.svg @@ -0,0 +1,60 @@ + + + + + + +UmlClassDiagram + + + +0 + +<<Abstract>> +AbstractARM +../src/contracts/AbstractARM.sol + + + +29 + +OwnableOperable +../src/contracts/OwnableOperable.sol + + + +0->29 + + + + + +27 + +OriginARM +../src/contracts/OriginARM.sol + + + +27->0 + + + + + +28 + +Ownable +../src/contracts/Ownable.sol + + + +29->28 + + + + + diff --git a/docs/OriginARMPublicSquashed.svg b/docs/OriginARMPublicSquashed.svg new file mode 100644 index 00000000..b65e6e4d --- /dev/null +++ b/docs/OriginARMPublicSquashed.svg @@ -0,0 +1,108 @@ + + + + + + +UmlClassDiagram + + + +27 + +OriginARM +../src/contracts/OriginARM.sol + +Public: +   operator: address <<OwnableOperable>> +   MAX_CROSS_PRICE_DEVIATION: uint256 <<AbstractARM>> +   PRICE_SCALE: uint256 <<AbstractARM>> +   FEE_SCALE: uint256 <<AbstractARM>> +   minSharesToRedeem: uint256 <<AbstractARM>> +   allocateThreshold: int256 <<AbstractARM>> +   liquidityAsset: address <<AbstractARM>> +   baseAsset: address <<AbstractARM>> +   token0: IERC20 <<AbstractARM>> +   token1: IERC20 <<AbstractARM>> +   claimDelay: uint256 <<AbstractARM>> +   traderate0: uint256 <<AbstractARM>> +   traderate1: uint256 <<AbstractARM>> +   crossPrice: uint256 <<AbstractARM>> +   withdrawsQueued: uint128 <<AbstractARM>> +   withdrawsClaimed: uint128 <<AbstractARM>> +   nextWithdrawalIndex: uint256 <<AbstractARM>> +   withdrawalRequests: mapping(uint256=>WithdrawalRequest) <<AbstractARM>> +   fee: uint16 <<AbstractARM>> +   lastAvailableAssets: int128 <<AbstractARM>> +   feeCollector: address <<AbstractARM>> +   capManager: address <<AbstractARM>> +   activeMarket: address <<AbstractARM>> +   supportedMarkets: mapping(address=>bool) <<AbstractARM>> +   armBuffer: uint256 <<AbstractARM>> +   vault: address <<OriginARM>> +   vaultWithdrawalAmount: uint256 <<OriginARM>> + +External: +    setOwner(newOwner: address) <<onlyOwner>> <<Ownable>> +    setOperator(newOperator: address) <<onlyOwner>> <<OwnableOperable>> +    swapExactTokensForTokens(inToken: IERC20, outToken: IERC20, amountIn: uint256, amountOutMin: uint256, to: address) <<AbstractARM>> +    swapExactTokensForTokens(amountIn: uint256, amountOutMin: uint256, path: address[], to: address, deadline: uint256): (amounts: uint256[]) <<AbstractARM>> +    swapTokensForExactTokens(inToken: IERC20, outToken: IERC20, amountOut: uint256, amountInMax: uint256, to: address) <<AbstractARM>> +    swapTokensForExactTokens(amountOut: uint256, amountInMax: uint256, path: address[], to: address, deadline: uint256): (amounts: uint256[]) <<AbstractARM>> +    getReserves(): (reserve0: uint256, reserve1: uint256) <<AbstractARM>> +    setPrices(buyT1: uint256, sellT1: uint256) <<onlyOperatorOrOwner>> <<AbstractARM>> +    setCrossPrice(newCrossPrice: uint256) <<onlyOwner>> <<AbstractARM>> +    previewDeposit(assets: uint256): (shares: uint256) <<AbstractARM>> +    deposit(assets: uint256): (shares: uint256) <<AbstractARM>> +    deposit(assets: uint256, receiver: address): (shares: uint256) <<AbstractARM>> +    previewRedeem(shares: uint256): (assets: uint256) <<AbstractARM>> +    requestRedeem(shares: uint256): (requestId: uint256, assets: uint256) <<AbstractARM>> +    claimRedeem(requestId: uint256): (assets: uint256) <<AbstractARM>> +    asset(): address <<AbstractARM>> +    setFee(_fee: uint256) <<onlyOwner>> <<AbstractARM>> +    setFeeCollector(_feeCollector: address) <<onlyOwner>> <<AbstractARM>> +    feesAccrued(): (fees: uint256) <<AbstractARM>> +    addMarkets(_markets: address[]) <<onlyOwner>> <<AbstractARM>> +    removeMarket(_market: address) <<onlyOwner>> <<AbstractARM>> +    setActiveMarket(_market: address) <<onlyOperatorOrOwner>> <<AbstractARM>> +    allocate(): (liquidityDelta: int256) <<AbstractARM>> +    setCapManager(_capManager: address) <<onlyOwner>> <<AbstractARM>> +    setARMBuffer(_armBuffer: uint256) <<onlyOperatorOrOwner>> <<AbstractARM>> +    initialize(_name: string, _symbol: string, _operator: address, _fee: uint256, _feeCollector: address, _capManager: address) <<initializer>> <<OriginARM>> +    requestOriginWithdrawal(amount: uint256): (requestId: uint256) <<onlyOperatorOrOwner>> <<OriginARM>> +    claimOriginWithdrawals(requestIds: uint256[]): (amountClaimed: uint256) <<OriginARM>> +Public: +    <<event>> AdminChanged(previousAdmin: address, newAdmin: address) <<Ownable>> +    <<event>> OperatorChanged(newAdmin: address) <<OwnableOperable>> +    <<event>> TraderateChanged(traderate0: uint256, traderate1: uint256) <<AbstractARM>> +    <<event>> CrossPriceUpdated(crossPrice: uint256) <<AbstractARM>> +    <<event>> Deposit(owner: address, assets: uint256, shares: uint256) <<AbstractARM>> +    <<event>> RedeemRequested(withdrawer: address, requestId: uint256, assets: uint256, queued: uint256, claimTimestamp: uint256) <<AbstractARM>> +    <<event>> RedeemClaimed(withdrawer: address, requestId: uint256, assets: uint256) <<AbstractARM>> +    <<event>> FeeCollected(feeCollector: address, fee: uint256) <<AbstractARM>> +    <<event>> FeeUpdated(fee: uint256) <<AbstractARM>> +    <<event>> FeeCollectorUpdated(newFeeCollector: address) <<AbstractARM>> +    <<event>> CapManagerUpdated(capManager: address) <<AbstractARM>> +    <<event>> ActiveMarketUpdated(market: address) <<AbstractARM>> +    <<event>> MarketAdded(market: address) <<AbstractARM>> +    <<event>> MarketRemoved(market: address) <<AbstractARM>> +    <<event>> ARMBufferUpdated(armBuffer: uint256) <<AbstractARM>> +    <<event>> Allocated(market: address, assets: int256) <<AbstractARM>> +    <<event>> RequestOriginWithdrawal(amount: uint256, requestId: uint256) <<OriginARM>> +    <<event>> ClaimOriginWithdrawals(requestIds: uint256[], amountClaimed: uint256) <<OriginARM>> +    <<modifier>> onlyOwner() <<Ownable>> +    <<modifier>> onlyOperatorOrOwner() <<OwnableOperable>> +    constructor() <<Ownable>> +    owner(): address <<Ownable>> +    constructor(_otoken: address, _liquidityAsset: address, _vault: address, _claimDelay: uint256, _minSharesToRedeem: uint256, _allocateThreshold: int256) <<OriginARM>> +    claimable(): (claimableAmount: uint256) <<AbstractARM>> +    totalAssets(): uint256 <<AbstractARM>> +    convertToShares(assets: uint256): (shares: uint256) <<AbstractARM>> +    convertToAssets(shares: uint256): (assets: uint256) <<AbstractARM>> +    collectFees(): (fees: uint256) <<AbstractARM>> + + + diff --git a/docs/OriginARMSquashed.svg b/docs/OriginARMSquashed.svg new file mode 100644 index 00000000..8540d3e3 --- /dev/null +++ b/docs/OriginARMSquashed.svg @@ -0,0 +1,135 @@ + + + + + + +UmlClassDiagram + + + +27 + +OriginARM +../src/contracts/OriginARM.sol + +Private: +   _gap: uint256[49] <<OwnableOperable>> +   _gap: uint256[38] <<AbstractARM>> +Internal: +   OWNER_SLOT: bytes32 <<Ownable>> +   MIN_TOTAL_SUPPLY: uint256 <<AbstractARM>> +   DEAD_ACCOUNT: address <<AbstractARM>> +Public: +   operator: address <<OwnableOperable>> +   MAX_CROSS_PRICE_DEVIATION: uint256 <<AbstractARM>> +   PRICE_SCALE: uint256 <<AbstractARM>> +   FEE_SCALE: uint256 <<AbstractARM>> +   minSharesToRedeem: uint256 <<AbstractARM>> +   allocateThreshold: int256 <<AbstractARM>> +   liquidityAsset: address <<AbstractARM>> +   baseAsset: address <<AbstractARM>> +   token0: IERC20 <<AbstractARM>> +   token1: IERC20 <<AbstractARM>> +   claimDelay: uint256 <<AbstractARM>> +   traderate0: uint256 <<AbstractARM>> +   traderate1: uint256 <<AbstractARM>> +   crossPrice: uint256 <<AbstractARM>> +   withdrawsQueued: uint128 <<AbstractARM>> +   withdrawsClaimed: uint128 <<AbstractARM>> +   nextWithdrawalIndex: uint256 <<AbstractARM>> +   withdrawalRequests: mapping(uint256=>WithdrawalRequest) <<AbstractARM>> +   fee: uint16 <<AbstractARM>> +   lastAvailableAssets: int128 <<AbstractARM>> +   feeCollector: address <<AbstractARM>> +   capManager: address <<AbstractARM>> +   activeMarket: address <<AbstractARM>> +   supportedMarkets: mapping(address=>bool) <<AbstractARM>> +   armBuffer: uint256 <<AbstractARM>> +   vault: address <<OriginARM>> +   vaultWithdrawalAmount: uint256 <<OriginARM>> + +Internal: +    _owner(): (ownerOut: address) <<Ownable>> +    _setOwner(newOwner: address) <<Ownable>> +    _onlyOwner() <<Ownable>> +    _initOwnableOperable(_operator: address) <<OwnableOperable>> +    _setOperator(newOperator: address) <<OwnableOperable>> +    _initARM(_operator: address, _name: string, _symbol: string, _fee: uint256, _feeCollector: address, _capManager: address) <<AbstractARM>> +    _inDeadline(deadline: uint256) <<AbstractARM>> +    _transferAsset(asset: address, to: address, amount: uint256) <<AbstractARM>> +    _transferAssetFrom(asset: address, from: address, to: address, amount: uint256) <<AbstractARM>> +    _swapExactTokensForTokens(inToken: IERC20, outToken: IERC20, amountIn: uint256, to: address): (amountOut: uint256) <<AbstractARM>> +    _swapTokensForExactTokens(inToken: IERC20, outToken: IERC20, amountOut: uint256, to: address): (amountIn: uint256) <<AbstractARM>> +    _deposit(assets: uint256, receiver: address): (shares: uint256) <<AbstractARM>> +    _requireLiquidityAvailable(amount: uint256) <<AbstractARM>> +    _availableAssets(): (availableAssets: uint256, outstandingWithdrawals: uint256) <<AbstractARM>> +    _externalWithdrawQueue(): uint256 <<OriginARM>> +    _setFee(_fee: uint256) <<AbstractARM>> +    _setFeeCollector(_feeCollector: address) <<AbstractARM>> +    _feesAccrued(): (fees: uint256, newAvailableAssets: uint256) <<AbstractARM>> +    _allocate(): (liquidityDelta: int256) <<AbstractARM>> +External: +    setOwner(newOwner: address) <<onlyOwner>> <<Ownable>> +    setOperator(newOperator: address) <<onlyOwner>> <<OwnableOperable>> +    swapExactTokensForTokens(inToken: IERC20, outToken: IERC20, amountIn: uint256, amountOutMin: uint256, to: address) <<AbstractARM>> +    swapExactTokensForTokens(amountIn: uint256, amountOutMin: uint256, path: address[], to: address, deadline: uint256): (amounts: uint256[]) <<AbstractARM>> +    swapTokensForExactTokens(inToken: IERC20, outToken: IERC20, amountOut: uint256, amountInMax: uint256, to: address) <<AbstractARM>> +    swapTokensForExactTokens(amountOut: uint256, amountInMax: uint256, path: address[], to: address, deadline: uint256): (amounts: uint256[]) <<AbstractARM>> +    getReserves(): (reserve0: uint256, reserve1: uint256) <<AbstractARM>> +    setPrices(buyT1: uint256, sellT1: uint256) <<onlyOperatorOrOwner>> <<AbstractARM>> +    setCrossPrice(newCrossPrice: uint256) <<onlyOwner>> <<AbstractARM>> +    previewDeposit(assets: uint256): (shares: uint256) <<AbstractARM>> +    deposit(assets: uint256): (shares: uint256) <<AbstractARM>> +    deposit(assets: uint256, receiver: address): (shares: uint256) <<AbstractARM>> +    previewRedeem(shares: uint256): (assets: uint256) <<AbstractARM>> +    requestRedeem(shares: uint256): (requestId: uint256, assets: uint256) <<AbstractARM>> +    claimRedeem(requestId: uint256): (assets: uint256) <<AbstractARM>> +    asset(): address <<AbstractARM>> +    setFee(_fee: uint256) <<onlyOwner>> <<AbstractARM>> +    setFeeCollector(_feeCollector: address) <<onlyOwner>> <<AbstractARM>> +    feesAccrued(): (fees: uint256) <<AbstractARM>> +    addMarkets(_markets: address[]) <<onlyOwner>> <<AbstractARM>> +    removeMarket(_market: address) <<onlyOwner>> <<AbstractARM>> +    setActiveMarket(_market: address) <<onlyOperatorOrOwner>> <<AbstractARM>> +    allocate(): (liquidityDelta: int256) <<AbstractARM>> +    setCapManager(_capManager: address) <<onlyOwner>> <<AbstractARM>> +    setARMBuffer(_armBuffer: uint256) <<onlyOperatorOrOwner>> <<AbstractARM>> +    initialize(_name: string, _symbol: string, _operator: address, _fee: uint256, _feeCollector: address, _capManager: address) <<initializer>> <<OriginARM>> +    requestOriginWithdrawal(amount: uint256): (requestId: uint256) <<onlyOperatorOrOwner>> <<OriginARM>> +    claimOriginWithdrawals(requestIds: uint256[]): (amountClaimed: uint256) <<OriginARM>> +Public: +    <<event>> AdminChanged(previousAdmin: address, newAdmin: address) <<Ownable>> +    <<event>> OperatorChanged(newAdmin: address) <<OwnableOperable>> +    <<event>> TraderateChanged(traderate0: uint256, traderate1: uint256) <<AbstractARM>> +    <<event>> CrossPriceUpdated(crossPrice: uint256) <<AbstractARM>> +    <<event>> Deposit(owner: address, assets: uint256, shares: uint256) <<AbstractARM>> +    <<event>> RedeemRequested(withdrawer: address, requestId: uint256, assets: uint256, queued: uint256, claimTimestamp: uint256) <<AbstractARM>> +    <<event>> RedeemClaimed(withdrawer: address, requestId: uint256, assets: uint256) <<AbstractARM>> +    <<event>> FeeCollected(feeCollector: address, fee: uint256) <<AbstractARM>> +    <<event>> FeeUpdated(fee: uint256) <<AbstractARM>> +    <<event>> FeeCollectorUpdated(newFeeCollector: address) <<AbstractARM>> +    <<event>> CapManagerUpdated(capManager: address) <<AbstractARM>> +    <<event>> ActiveMarketUpdated(market: address) <<AbstractARM>> +    <<event>> MarketAdded(market: address) <<AbstractARM>> +    <<event>> MarketRemoved(market: address) <<AbstractARM>> +    <<event>> ARMBufferUpdated(armBuffer: uint256) <<AbstractARM>> +    <<event>> Allocated(market: address, assets: int256) <<AbstractARM>> +    <<event>> RequestOriginWithdrawal(amount: uint256, requestId: uint256) <<OriginARM>> +    <<event>> ClaimOriginWithdrawals(requestIds: uint256[], amountClaimed: uint256) <<OriginARM>> +    <<modifier>> onlyOwner() <<Ownable>> +    <<modifier>> onlyOperatorOrOwner() <<OwnableOperable>> +    constructor() <<Ownable>> +    owner(): address <<Ownable>> +    constructor(_otoken: address, _liquidityAsset: address, _vault: address, _claimDelay: uint256, _minSharesToRedeem: uint256, _allocateThreshold: int256) <<OriginARM>> +    claimable(): (claimableAmount: uint256) <<AbstractARM>> +    totalAssets(): uint256 <<AbstractARM>> +    convertToShares(assets: uint256): (shares: uint256) <<AbstractARM>> +    convertToAssets(shares: uint256): (assets: uint256) <<AbstractARM>> +    collectFees(): (fees: uint256) <<AbstractARM>> + + + diff --git a/docs/generate.sh b/docs/generate.sh index 3c607ab1..c51df7d4 100644 --- a/docs/generate.sh +++ b/docs/generate.sh @@ -6,13 +6,6 @@ sol2uml storage ../src/contracts -c Proxy -o ProxyStorage.svg \ -sn eip1967.proxy.implementation,eip1967.proxy.admin \ -st address,address -sol2uml ../src/contracts -v -hv -hf -he -hs -hl -hi -b OethARM -o OethARMHierarchy.svg -sol2uml ../src/contracts -s -d 0 -b OethARM -o OethARMSquashed.svg -sol2uml storage ../src/contracts -c OethARM -o OethARMStorage.svg \ - -sn eip1967.proxy.implementation,eip1967.proxy.admin \ - -st address,address \ - --hideExpand gap,_gap - sol2uml ../src/contracts -v -hv -hf -he -hs -hl -hi -b LidoARM -o LidoARMHierarchy.svg sol2uml ../src/contracts -s -d 0 -b LidoARM -o LidoARMSquashed.svg sol2uml ../src/contracts -hp -s -d 0 -b LidoARM -o LidoARMPublicSquashed.svg @@ -21,6 +14,15 @@ sol2uml storage ../src/contracts,../lib -c LidoARM -o LidoARMStorage.svg \ -st address,address \ --hideExpand gap,_gap +sol2uml ../src/contracts -v -hv -hf -he -hs -hl -hi -b OriginARM -o OriginARMHierarchy.svg +sol2uml ../src/contracts -s -d 0 -b OriginARM -o OriginARMSquashed.svg +sol2uml ../src/contracts -hp -s -d 0 -b OriginARM -o OriginARMPublicSquashed.svg +sol2uml storage ../src/contracts,../lib -c OriginARM -o OriginARMStorage.svg \ + -sn eip1967.proxy.implementation,eip1967.proxy.admin \ + -st address,address \ + --hideExpand gap,_gap + + sol2uml ../src/contracts -v -hv -hf -he -hs -hl -hi -b CapManager -o CapManagerHierarchy.svg sol2uml ../src/contracts -s -d 0 -b CapManager -o CapManagerSquashed.svg diff --git a/docs/plantuml/originContracts.png b/docs/plantuml/originContracts.png new file mode 100644 index 0000000000000000000000000000000000000000..e3e31351452a2716d5359f99e3ab28bca6c44d80 GIT binary patch literal 26302 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 #$originColor { +} + +object "OriginARM" as arm <><> #$originColor { + shares: ARM-oETH-WETH + assets: oETH, WETH +} + +object "OETHVault" as oethVault <><> #$thirdPartyColor { + assets: oETH, WETH +} + +zap <..> arm +arm ..> oethVault + +@enduml \ No newline at end of file diff --git a/foundry.toml b/foundry.toml index cc0b1f82..3ad84e5e 100644 --- a/foundry.toml +++ b/foundry.toml @@ -6,7 +6,7 @@ verbosity = 3 sender = "0x0165C55EF814dEFdd658532A48Bd17B2c8356322" tx_origin = "0x0165C55EF814dEFdd658532A48Bd17B2c8356322" auto_detect_remappings = false -gas_reports = ["OethARM", "Proxy", "LidoARM", "OriginARM"] +gas_reports = ["Proxy", "LidoARM", "OriginARM"] fs_permissions = [{ access = "read-write", path = "./build" }] extra_output_files = ["metadata"] ignored_warnings_from = ["src/contracts/Proxy.sol"] diff --git a/script/deploy/DeployManager.sol b/script/deploy/DeployManager.sol index daf9e2f1..f83458b9 100644 --- a/script/deploy/DeployManager.sol +++ b/script/deploy/DeployManager.sol @@ -6,14 +6,10 @@ import {VmSafe} from "forge-std/Vm.sol"; import {stdJson} from "forge-std/StdJson.sol"; import {AbstractDeployScript} from "./AbstractDeployScript.sol"; -import {DeployCoreMainnetScript} from "./mainnet/001_DeployCoreScript.sol"; -import {UpgradeMainnetScript} from "./mainnet/002_UpgradeScript.sol"; import {UpgradeLidoARMMainnetScript} from "./mainnet/003_UpgradeLidoARMScript.sol"; import {UpdateCrossPriceMainnetScript} from "./mainnet/004_UpdateCrossPriceScript.sol"; import {RegisterLidoWithdrawalsScript} from "./mainnet/005_RegisterLidoWithdrawalsScript.sol"; import {ChangeFeeCollectorScript} from "./mainnet/006_ChangeFeeCollector.sol"; -import {DeployCoreHoleskyScript} from "./holesky/001_DeployCoreScript.sol"; -import {UpgradeHoleskyScript} from "./holesky/002_UpgradeScript.sol"; import {DeployOriginARMProxyScript} from "./sonic/001_DeployOriginARMProxy.sol"; import {DeployOriginARMScript} from "./sonic/002_DeployOriginARM.sol"; import {UpgradeOriginARMScript} from "./sonic/003_UpgradeOriginARM.sol"; @@ -74,8 +70,6 @@ contract DeployManager is Script { function run() external { if (block.chainid == 1 || block.chainid == 31337) { // TODO: Use vm.readDir to recursively build this? - _runDeployFile(new DeployCoreMainnetScript()); - _runDeployFile(new UpgradeMainnetScript(getDeployment("OETH_ARM"))); _runDeployFile(new UpgradeLidoARMMainnetScript()); _runDeployFile(new UpdateCrossPriceMainnetScript()); _runDeployFile(new RegisterLidoWithdrawalsScript()); @@ -88,8 +82,6 @@ contract DeployManager is Script { _runDeployFile(new UpgradeOETHARMScript()); } else if (block.chainid == 17000) { // Holesky - _runDeployFile(new DeployCoreHoleskyScript()); - _runDeployFile(new UpgradeHoleskyScript(getDeployment("OETH_ARM"))); } else if (block.chainid == 146) { // Sonic console.log("Deploying Origin ARM"); diff --git a/script/deploy/holesky/001_DeployCoreScript.sol b/script/deploy/holesky/001_DeployCoreScript.sol deleted file mode 100644 index 8650642e..00000000 --- a/script/deploy/holesky/001_DeployCoreScript.sol +++ /dev/null @@ -1,33 +0,0 @@ -// SPDX-License-Identifier: MIT - -pragma solidity 0.8.23; - -import "forge-std/console.sol"; -import {Vm} from "forge-std/Vm.sol"; - -import {OethARM} from "contracts/OethARM.sol"; -import {Proxy} from "contracts/Proxy.sol"; -import {Holesky} from "contracts/utils/Addresses.sol"; -import {AbstractDeployScript} from "../AbstractDeployScript.sol"; - -contract DeployCoreHoleskyScript is AbstractDeployScript { - string public constant override DEPLOY_NAME = "001_CoreHolesky"; - bool public constant override proposalExecuted = false; - - function _execute() internal override { - console.log("Deploy:", DEPLOY_NAME); - console.log("------------"); - - // 1. Deploy proxy contracts - Proxy proxy = new Proxy(); - _recordDeploy("OETH_ARM", address(proxy)); - - // 2. Deploy implementation - OethARM implementation = new OethARM(Holesky.OETH, Holesky.WETH, Holesky.OETH_VAULT); - _recordDeploy("OETH_ARM_IMPL", address(implementation)); - - // 3. Initialize proxy, set the owner and operator to the RELAYER and approve the OETH Vault to transfer OETH - bytes memory data = abi.encodeWithSignature("initialize(address)", Holesky.RELAYER); - proxy.initialize(address(implementation), Holesky.RELAYER, data); - } -} diff --git a/script/deploy/holesky/002_UpgradeScript.sol b/script/deploy/holesky/002_UpgradeScript.sol deleted file mode 100644 index 30dd8b2f..00000000 --- a/script/deploy/holesky/002_UpgradeScript.sol +++ /dev/null @@ -1,38 +0,0 @@ -// SPDX-License-Identifier: MIT - -pragma solidity 0.8.23; - -import "forge-std/console.sol"; -import {Vm} from "forge-std/Vm.sol"; - -import {OethARM} from "contracts/OethARM.sol"; -import {Proxy} from "contracts/Proxy.sol"; -import {Holesky} from "contracts/utils/Addresses.sol"; -import {AbstractDeployScript} from "../AbstractDeployScript.sol"; - -contract UpgradeHoleskyScript is AbstractDeployScript { - string public constant override DEPLOY_NAME = "002_UpgradeHolesky"; - bool public constant override proposalExecuted = false; - - address newImpl; - Proxy internal proxy; - - constructor(address _proxy) { - proxy = Proxy(payable(_proxy)); - } - - function _execute() internal override { - console.log("Deploy:", DEPLOY_NAME); - console.log("------------"); - - // 1. Deploy new implementation - newImpl = address(new OethARM(Holesky.OETH, Holesky.WETH, Holesky.OETH_VAULT)); - _recordDeploy("OETH_ARM_IMPL", newImpl); - } - - function _fork() internal override { - // Upgrade the proxy - vm.prank(Holesky.RELAYER); - proxy.upgradeTo(newImpl); - } -} diff --git a/script/deploy/mainnet/001_DeployCoreScript.sol b/script/deploy/mainnet/001_DeployCoreScript.sol deleted file mode 100644 index 2b97c617..00000000 --- a/script/deploy/mainnet/001_DeployCoreScript.sol +++ /dev/null @@ -1,53 +0,0 @@ -// SPDX-License-Identifier: MIT - -pragma solidity 0.8.23; - -import "forge-std/console.sol"; -import {Vm} from "forge-std/Vm.sol"; - -import {OethARM} from "contracts/OethARM.sol"; -import {Proxy} from "contracts/Proxy.sol"; -import {Mainnet} from "contracts/utils/Addresses.sol"; -import {GovProposal, GovSixHelper} from "contracts/utils/GovSixHelper.sol"; -import {AbstractDeployScript} from "../AbstractDeployScript.sol"; - -contract DeployCoreMainnetScript is AbstractDeployScript { - using GovSixHelper for GovProposal; - - GovProposal public govProposal; - - string public constant override DEPLOY_NAME = "001_CoreMainnet"; - bool public constant override proposalExecuted = true; - - function _execute() internal override { - console.log("Deploy:", DEPLOY_NAME); - console.log("------------"); - - // 1. Deploy proxy contracts - Proxy proxy = new Proxy(); - _recordDeploy("OETH_ARM", address(proxy)); - - // 2. Deploy implementation - OethARM implementation = new OethARM(Mainnet.OETH, Mainnet.WETH, Mainnet.OETH_VAULT); - _recordDeploy("OETH_ARM_IMPL", address(implementation)); - - // 3. Initialize proxy, set the owner to TIMELOCK, set the operator to the OETH Relayer and approve the OETH Vault to transfer OETH - bytes memory data = abi.encodeWithSignature("initialize(address)", Mainnet.OETH_RELAYER); - proxy.initialize(address(implementation), Mainnet.TIMELOCK, data); - } - - function _buildGovernanceProposal() internal override { - // govProposal.setDescription("Setup OETH ARM Contract"); - - // NOTE: This has already been done during deployment - // but doing this here to test governance flow. - - // Set operator - // govProposal.action(deployedContracts["OETH_ARM"], "initialize(address)", abi.encode(Mainnet.OETH_RELAYER)); - } - - function _fork() internal override { - // Simulate on fork - // govProposal.simulate(); - } -} diff --git a/script/deploy/mainnet/002_UpgradeScript.sol b/script/deploy/mainnet/002_UpgradeScript.sol deleted file mode 100644 index f9804f80..00000000 --- a/script/deploy/mainnet/002_UpgradeScript.sol +++ /dev/null @@ -1,39 +0,0 @@ -// SPDX-License-Identifier: MIT - -pragma solidity 0.8.23; - -import "forge-std/console.sol"; -import {Vm} from "forge-std/Vm.sol"; - -import {OethARM} from "contracts/OethARM.sol"; -import {Proxy} from "contracts/Proxy.sol"; -import {Mainnet} from "contracts/utils/Addresses.sol"; -import {AbstractDeployScript} from "../AbstractDeployScript.sol"; - -contract UpgradeMainnetScript is AbstractDeployScript { - string public constant override DEPLOY_NAME = "002_UpgradeMainnet"; - bool public constant override proposalExecuted = true; - - address newImpl; - Proxy internal proxy; - - constructor(address _proxy) { - proxy = Proxy(payable(_proxy)); - } - - function _execute() internal override { - console.log("Deploy:", DEPLOY_NAME); - console.log("------------"); - - // 1. Deploy new implementation - newImpl = address(new OethARM(Mainnet.OETH, Mainnet.WETH, Mainnet.OETH_VAULT)); - _recordDeploy("OETH_ARM_IMPL", newImpl); - } - - function _fork() internal override { - // Upgrade the proxy - vm.prank(Mainnet.TIMELOCK); - proxy.upgradeTo(newImpl); - console.log("OethARM upgraded"); - } -} diff --git a/script/deploy/mainnet/005_RegisterLidoWithdrawalsScript.sol b/script/deploy/mainnet/005_RegisterLidoWithdrawalsScript.sol index 7fd9fafb..0a16afb1 100644 --- a/script/deploy/mainnet/005_RegisterLidoWithdrawalsScript.sol +++ b/script/deploy/mainnet/005_RegisterLidoWithdrawalsScript.sol @@ -3,7 +3,6 @@ pragma solidity 0.8.23; import "forge-std/console.sol"; -import {Vm} from "forge-std/Vm.sol"; import {LidoARM} from "contracts/LidoARM.sol"; import {Mainnet} from "contracts/utils/Addresses.sol"; diff --git a/script/deploy/sonic/001_DeployOriginARMProxy.sol b/script/deploy/sonic/001_DeployOriginARMProxy.sol index b2a453c9..63a84203 100644 --- a/script/deploy/sonic/001_DeployOriginARMProxy.sol +++ b/script/deploy/sonic/001_DeployOriginARMProxy.sol @@ -3,7 +3,6 @@ pragma solidity 0.8.23; import "forge-std/console.sol"; -import {Vm} from "forge-std/Vm.sol"; import {Proxy} from "contracts/Proxy.sol"; import {AbstractDeployScript} from "../AbstractDeployScript.sol"; diff --git a/src/contracts/Interfaces.sol b/src/contracts/Interfaces.sol index 95b7960d..951f3542 100644 --- a/src/contracts/Interfaces.sol +++ b/src/contracts/Interfaces.sol @@ -13,100 +13,6 @@ interface IERC20 { event Transfer(address indexed from, address indexed to, uint256 value); } -interface IOethARM { - function token0() external returns (address); - function token1() external returns (address); - function owner() external returns (address); - - /** - * @notice Swaps an exact amount of input tokens for as many output tokens as possible. - * msg.sender should have already given the ARM contract an allowance of - * at least amountIn on the input token. - * - * @param inToken Input token. - * @param outToken Output token. - * @param amountIn The amount of input tokens to send. - * @param amountOutMin The minimum amount of output tokens that must be received for the transaction not to revert. - * @param to Recipient of the output tokens. - */ - function swapExactTokensForTokens( - IERC20 inToken, - IERC20 outToken, - uint256 amountIn, - uint256 amountOutMin, - address to - ) external; - - /** - * @notice Uniswap V2 Router compatible interface. Swaps an exact amount of - * input tokens for as many output tokens as possible. - * msg.sender should have already given the ARM contract an allowance of - * at least amountIn on the input token. - * - * @param amountIn The amount of input tokens to send. - * @param amountOutMin The minimum amount of output tokens that must be received for the transaction not to revert. - * @param path The input and output token addresses. - * @param to Recipient of the output tokens. - * @param deadline Unix timestamp after which the transaction will revert. - * @return amounts The input and output token amounts. - */ - function swapExactTokensForTokens( - uint256 amountIn, - uint256 amountOutMin, - address[] calldata path, - address to, - uint256 deadline - ) external returns (uint256[] memory amounts); - - /** - * @notice Receive an exact amount of output tokens for as few input tokens as possible. - * msg.sender should have already given the router an allowance of - * at least amountInMax on the input token. - * - * @param inToken Input token. - * @param outToken Output token. - * @param amountOut The amount of output tokens to receive. - * @param amountInMax The maximum amount of input tokens that can be required before the transaction reverts. - * @param to Recipient of the output tokens. - */ - function swapTokensForExactTokens( - IERC20 inToken, - IERC20 outToken, - uint256 amountOut, - uint256 amountInMax, - address to - ) external; - - /** - * @notice Uniswap V2 Router compatible interface. Receive an exact amount of - * output tokens for as few input tokens as possible. - * msg.sender should have already given the router an allowance of - * at least amountInMax on the input token. - * - * @param amountOut The amount of output tokens to receive. - * @param amountInMax The maximum amount of input tokens that can be required before the transaction reverts. - * @param path The input and output token addresses. - * @param to Recipient of the output tokens. - * @param deadline Unix timestamp after which the transaction will revert. - * @return amounts The input and output token amounts. - */ - function swapTokensForExactTokens( - uint256 amountOut, - uint256 amountInMax, - address[] calldata path, - address to, - uint256 deadline - ) external returns (uint256[] memory amounts); - - function setOwner(address newOwner) external; - function transferToken(address token, address to, uint256 amount) external; - - // From OethLiquidityManager - function requestWithdrawal(uint256 amount) external returns (uint256 requestId, uint256 queued); - function claimWithdrawal(uint256 requestId) external; - function claimWithdrawals(uint256[] calldata requestIds) external; -} - interface ILiquidityProviderARM is IERC20 { function previewDeposit(uint256 assets) external returns (uint256 shares); function deposit(uint256 assets) external returns (uint256 shares); diff --git a/src/contracts/OethARM.sol b/src/contracts/OethARM.sol deleted file mode 100644 index 027448ff..00000000 --- a/src/contracts/OethARM.sol +++ /dev/null @@ -1,35 +0,0 @@ -// SPDX-License-Identifier: BUSL-1.1 -pragma solidity ^0.8.23; - -import {Initializable} from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; - -import {AbstractARM} from "./AbstractARM.sol"; -import {PeggedARM} from "./PeggedARM.sol"; -import {OwnerLP} from "./OwnerLP.sol"; -import {OethLiquidityManager} from "./OethLiquidityManager.sol"; - -/** - * @title Origin Ether (OETH) Automated Redemption Manager (ARM) - * @author Origin Protocol Inc - */ -contract OethARM is Initializable, OwnerLP, PeggedARM, OethLiquidityManager { - /// @param _oeth The address of the OETH token that is being swapped into this contract. - /// @param _weth The address of the WETH token that is being swapped out of this contract. - /// @param _oethVault The address of the OETH Vault proxy. - constructor(address _oeth, address _weth, address _oethVault) - AbstractARM(_oeth, _weth, _weth, 10 minutes, 0, 0) - PeggedARM(false) - OethLiquidityManager(_oeth, _oethVault) - {} - - /// @notice Initialize the contract. - /// @param _operator The address of the account that can request and claim OETH withdrawals from the OETH Vault. - function initialize(address _operator) external initializer { - _setOperator(_operator); - _approvals(); - } - - function _externalWithdrawQueue() internal view override returns (uint256 assets) { - // TODO track OETH sent to the OETH Vault's withdrawal queue - } -} diff --git a/src/contracts/OethLiquidityManager.sol b/src/contracts/OethLiquidityManager.sol deleted file mode 100644 index 21517ca8..00000000 --- a/src/contracts/OethLiquidityManager.sol +++ /dev/null @@ -1,62 +0,0 @@ -// SPDX-License-Identifier: BUSL-1.1 -pragma solidity ^0.8.23; - -import {OwnableOperable} from "./OwnableOperable.sol"; -import {IERC20, IOriginVault} from "./Interfaces.sol"; - -/** - * @title Manages OETH liquidity against the OETH Vault. - * @author Origin Protocol Inc - */ -contract OethLiquidityManager is OwnableOperable { - address public immutable oeth; - address public immutable oethVault; - - /// @param _oeth The address of the OETH token. - /// @param _oethVault The address of the OETH Vault proxy. - constructor(address _oeth, address _oethVault) { - oeth = _oeth; - oethVault = _oethVault; - } - - /** - * @notice Approve the OETH Vault to transfer OETH from this cont4act. - */ - function approvals() external onlyOwner { - _approvals(); - } - - function _approvals() internal { - IERC20(oeth).approve(oethVault, type(uint256).max); - } - - /** - * @notice Request withdrawal of WETH from the OETH Vault. - * @param amount The amount of OETH to burn and WETH to withdraw. - */ - function requestWithdrawal(uint256 amount) - external - onlyOperatorOrOwner - returns (uint256 requestId, uint256 queued) - { - return IOriginVault(oethVault).requestWithdrawal(amount); - } - - /** - * @notice Claim previously requested withdrawal of WETH from the OETH Vault. - * The Vault's claimable WETH needs to be greater than or equal to the queued amount of the request. - * @param requestId The ID of the OETH Vault's withdrawal request. - */ - function claimWithdrawal(uint256 requestId) external onlyOperatorOrOwner { - IOriginVault(oethVault).claimWithdrawal(requestId); - } - - /** - * @notice Claim multiple previously requested withdrawals of WETH from the OETH Vault. - * The Vault's claimable WETH needs to be greater than or equal to the queued amount of the request. - * @param requestIds List of request IDs from the OETH Vault's withdrawal requests. - */ - function claimWithdrawals(uint256[] memory requestIds) external onlyOperatorOrOwner { - IOriginVault(oethVault).claimWithdrawals(requestIds); - } -} diff --git a/src/contracts/OwnerLP.sol b/src/contracts/OwnerLP.sol deleted file mode 100644 index 6778172a..00000000 --- a/src/contracts/OwnerLP.sol +++ /dev/null @@ -1,14 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.23; - -import {Ownable} from "./Ownable.sol"; -import {IERC20} from "./Interfaces.sol"; - -abstract contract OwnerLP is Ownable { - /** - * @notice Owner can transfer out any ERC20 token. - */ - function transferToken(address token, address to, uint256 amount) external onlyOwner { - IERC20(token).transfer(to, amount); - } -} diff --git a/src/contracts/PeggedARM.sol b/src/contracts/PeggedARM.sol deleted file mode 100644 index 9a2f59ae..00000000 --- a/src/contracts/PeggedARM.sol +++ /dev/null @@ -1,49 +0,0 @@ -// SPDX-License-Identifier: BUSL-1.1 -pragma solidity ^0.8.23; - -import {AbstractARM} from "./AbstractARM.sol"; -import {IERC20} from "./Interfaces.sol"; - -abstract contract PeggedARM is AbstractARM { - /// @notice If true, the ARM contract can swap in both directions between token0 and token1. - bool public immutable bothDirections; - - constructor(bool _bothDirections) { - bothDirections = _bothDirections; - } - - function _swapExactTokensForTokens(IERC20 inToken, IERC20 outToken, uint256 amountIn, address to) - internal - override - returns (uint256 amountOut) - { - return _swap(inToken, outToken, amountIn, to); - } - - function _swapTokensForExactTokens(IERC20 inToken, IERC20 outToken, uint256 amountOut, address to) - internal - override - returns (uint256 amountIn) - { - return _swap(inToken, outToken, amountOut, to); - } - - function _swap(IERC20 inToken, IERC20 outToken, uint256 amount, address to) internal returns (uint256) { - if (bothDirections) { - require( - inToken == token0 && outToken == token1 || inToken == token1 && outToken == token0, "ARM: Invalid swap" - ); - } else { - require(inToken == token0 && outToken == token1, "ARM: Invalid swap"); - } - - // Transfer the input tokens from the caller to this ARM contract - require(inToken.transferFrom(msg.sender, address(this), amount), "failed transfer in"); - - // Transfer the same amount of output tokens to the recipient - require(outToken.transfer(to, amount), "failed transfer out"); - - // 1:1 swaps so the exact amount is returned as the calculated amount - return amount; - } -} diff --git a/test/Base.sol b/test/Base.sol index b49ebf7d..0c7e2dd2 100644 --- a/test/Base.sol +++ b/test/Base.sol @@ -6,7 +6,6 @@ import {Test} from "forge-std/Test.sol"; // Contracts import {Proxy} from "contracts/Proxy.sol"; -import {OethARM} from "contracts/OethARM.sol"; import {LidoARM} from "contracts/LidoARM.sol"; import {EtherFiARM} from "contracts/EtherFiARM.sol"; import {OriginARM} from "contracts/OriginARM.sol"; @@ -36,13 +35,11 @@ abstract contract Base_Test_ is Test { ////////////////////////////////////////////////////// /// --- CONTRACTS ////////////////////////////////////////////////////// - Proxy public proxy; Proxy public lpcProxy; Proxy public lidoProxy; Proxy public etherfiProxy; Proxy public originARMProxy; Proxy public harvesterProxy; - OethARM public oethARM; LidoARM public lidoARM; EtherFiARM public etherfiARM; SonicHarvester public harvester; @@ -101,11 +98,9 @@ abstract contract Base_Test_ is Test { /// @notice Better if called once all contract have been depoyed. function labelAll() public virtual { // Contracts - _labelNotNull(address(proxy), "DEFAULT PROXY"); _labelNotNull(address(lpcProxy), "LPC PROXY"); _labelNotNull(address(lidoProxy), "LIDO ARM PROXY"); _labelNotNull(address(etherfiProxy), "ETHERFI ARM PROXY"); - _labelNotNull(address(oethARM), "OETH ARM"); _labelNotNull(address(lidoARM), "LIDO ARM"); _labelNotNull(address(originARM), "ORIGIN ARM"); _labelNotNull(address(capManager), "CAP MANAGER"); diff --git a/test/fork/Harvester/Swap.sol b/test/fork/Harvester/Swap.sol index eb833ac1..4bba23f8 100644 --- a/test/fork/Harvester/Swap.sol +++ b/test/fork/Harvester/Swap.sol @@ -5,7 +5,6 @@ import {Sonic} from "contracts/utils/Addresses.sol"; import {SonicHarvester} from "contracts/SonicHarvester.sol"; import {Fork_Shared_Test} from "test/fork/Harvester/shared/Shared.sol"; -import {console} from "forge-std/console.sol"; contract Fork_Concrete_Harvester_Swap_Test_ is Fork_Shared_Test { address public constant OS_WHALE = 0x9F0dF7799f6FDAd409300080cfF680f5A23df4b1; diff --git a/test/fork/LidoFixedPriceMultiLpARM/ClaimRedeem.t.sol b/test/fork/LidoARM/ClaimRedeem.t.sol similarity index 100% rename from test/fork/LidoFixedPriceMultiLpARM/ClaimRedeem.t.sol rename to test/fork/LidoARM/ClaimRedeem.t.sol diff --git a/test/fork/LidoFixedPriceMultiLpARM/ClaimStETHWithdrawalForWETH.t.sol b/test/fork/LidoARM/ClaimStETHWithdrawalForWETH.t.sol similarity index 100% rename from test/fork/LidoFixedPriceMultiLpARM/ClaimStETHWithdrawalForWETH.t.sol rename to test/fork/LidoARM/ClaimStETHWithdrawalForWETH.t.sol diff --git a/test/fork/LidoFixedPriceMultiLpARM/CollectFees.t.sol b/test/fork/LidoARM/CollectFees.t.sol similarity index 100% rename from test/fork/LidoFixedPriceMultiLpARM/CollectFees.t.sol rename to test/fork/LidoARM/CollectFees.t.sol diff --git a/test/fork/LidoFixedPriceMultiLpARM/Constructor.t.sol b/test/fork/LidoARM/Constructor.t.sol similarity index 95% rename from test/fork/LidoFixedPriceMultiLpARM/Constructor.t.sol rename to test/fork/LidoARM/Constructor.t.sol index 03462efb..934d62df 100644 --- a/test/fork/LidoFixedPriceMultiLpARM/Constructor.t.sol +++ b/test/fork/LidoARM/Constructor.t.sol @@ -2,6 +2,7 @@ pragma solidity 0.8.23; // Test imports +import {Mainnet} from "src/contracts/utils/Addresses.sol"; import {Fork_Shared_Test_} from "test/fork/shared/Shared.sol"; contract Fork_Concrete_LidoARM_Constructor_Test is Fork_Shared_Test_ { diff --git a/test/fork/LidoFixedPriceMultiLpARM/Deposit.t.sol b/test/fork/LidoARM/Deposit.t.sol similarity index 100% rename from test/fork/LidoFixedPriceMultiLpARM/Deposit.t.sol rename to test/fork/LidoARM/Deposit.t.sol diff --git a/test/fork/LidoARM/Proxy.t.sol b/test/fork/LidoARM/Proxy.t.sol new file mode 100644 index 00000000..6c238c8e --- /dev/null +++ b/test/fork/LidoARM/Proxy.t.sol @@ -0,0 +1,79 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.23; + +// Contracts +import {LidoARM} from "contracts/LidoARM.sol"; + +// Test imports +import {Fork_Shared_Test_} from "test/fork/shared/Shared.sol"; + +// Utils +import {Mainnet} from "contracts/utils/Addresses.sol"; + +/// @notice The purpose of this contract is to test the `Proxy` contract. +contract Fork_Concrete_LidoARM_Proxy_Test_ is Fork_Shared_Test_ { + ////////////////////////////////////////////////////// + /// --- SETUP + ////////////////////////////////////////////////////// + function setUp() public virtual override { + super.setUp(); + lidoProxy.setOwner(Mainnet.TIMELOCK); + } + + ////////////////////////////////////////////////////// + /// --- REVERTING TESTS + ////////////////////////////////////////////////////// + function test_RevertWhen_UnauthorizedAccess() public { + vm.startPrank(address(0x123)); + vm.expectRevert("ARM: Only owner can call this function."); + lidoProxy.setOwner(deployer); + + vm.expectRevert("ARM: Only owner can call this function."); + lidoProxy.initialize(address(this), address(this), ""); + + vm.expectRevert("ARM: Only owner can call this function."); + lidoProxy.upgradeTo(address(this)); + + vm.expectRevert("ARM: Only owner can call this function."); + lidoProxy.upgradeToAndCall(address(this), ""); + vm.stopPrank(); + } + + ////////////////////////////////////////////////////// + /// --- PASSING TESTS + ////////////////////////////////////////////////////// + function test_Upgrade() public asLidoARMOwner { + address owner = Mainnet.TIMELOCK; + + // Deploy new implementation + LidoARM newImplementation = new LidoARM(Mainnet.STETH, Mainnet.WETH, Mainnet.OETH_VAULT, 10 minutes, 0, 0); + lidoProxy.upgradeTo(address(newImplementation)); + assertEq(lidoProxy.implementation(), address(newImplementation)); + + // Ensure ownership was preserved. + assertEq(lidoProxy.owner(), owner); + assertEq(lidoARM.owner(), owner); + + // Ensure the storage was preserved through the upgrade. + assertEq(address(lidoARM.token0()), Mainnet.WETH); + assertEq(address(lidoARM.token1()), Mainnet.STETH); + } + + function test_UpgradeAndCall() public asLidoARMOwner { + address owner = Mainnet.TIMELOCK; + + // Deploy new implementation + LidoARM newImplementation = new LidoARM(Mainnet.STETH, Mainnet.WETH, Mainnet.OETH_VAULT, 10 minutes, 0, 0); + bytes memory data = abi.encodeWithSignature("setOperator(address)", address(0x123)); + + lidoProxy.upgradeToAndCall(address(newImplementation), data); + assertEq(lidoProxy.implementation(), address(newImplementation)); + + // Ensure ownership was preserved. + assertEq(lidoProxy.owner(), owner); + assertEq(lidoARM.owner(), owner); + + // Ensure the post upgrade code was run + assertEq(lidoARM.operator(), address(0x123)); + } +} diff --git a/test/fork/LidoFixedPriceMultiLpARM/RequestRedeem.t.sol b/test/fork/LidoARM/RequestRedeem.t.sol similarity index 100% rename from test/fork/LidoFixedPriceMultiLpARM/RequestRedeem.t.sol rename to test/fork/LidoARM/RequestRedeem.t.sol diff --git a/test/fork/LidoFixedPriceMultiLpARM/RequestStETHWithdrawalForETH.t.sol b/test/fork/LidoARM/RequestStETHWithdrawalForETH.t.sol similarity index 100% rename from test/fork/LidoFixedPriceMultiLpARM/RequestStETHWithdrawalForETH.t.sol rename to test/fork/LidoARM/RequestStETHWithdrawalForETH.t.sol diff --git a/test/fork/LidoFixedPriceMultiLpARM/SetCrossPrice.t.sol b/test/fork/LidoARM/SetCrossPrice.t.sol similarity index 100% rename from test/fork/LidoFixedPriceMultiLpARM/SetCrossPrice.t.sol rename to test/fork/LidoARM/SetCrossPrice.t.sol diff --git a/test/fork/LidoFixedPriceMultiLpARM/Setters.t.sol b/test/fork/LidoARM/Setters.t.sol similarity index 99% rename from test/fork/LidoFixedPriceMultiLpARM/Setters.t.sol rename to test/fork/LidoARM/Setters.t.sol index 9de35b6f..9b625227 100644 --- a/test/fork/LidoFixedPriceMultiLpARM/Setters.t.sol +++ b/test/fork/LidoARM/Setters.t.sol @@ -5,11 +5,10 @@ pragma solidity 0.8.23; import {Fork_Shared_Test_} from "test/fork/shared/Shared.sol"; // Contracts -import {IERC20} from "contracts/Interfaces.sol"; import {AbstractARM} from "contracts/AbstractARM.sol"; import {CapManager} from "contracts/CapManager.sol"; -contract Fork_Concrete_lidoARM_Setters_Test_ is Fork_Shared_Test_ { +contract Fork_Concrete_LidoARM_Setters_Test_ is Fork_Shared_Test_ { address[] testProviders; ////////////////////////////////////////////////////// diff --git a/test/fork/LidoFixedPriceMultiLpARM/SwapExactTokensForTokens.t.sol b/test/fork/LidoARM/SwapExactTokensForTokens.t.sol similarity index 100% rename from test/fork/LidoFixedPriceMultiLpARM/SwapExactTokensForTokens.t.sol rename to test/fork/LidoARM/SwapExactTokensForTokens.t.sol diff --git a/test/fork/LidoFixedPriceMultiLpARM/SwapTokensForExactTokens.t.sol b/test/fork/LidoARM/SwapTokensForExactTokens.t.sol similarity index 100% rename from test/fork/LidoFixedPriceMultiLpARM/SwapTokensForExactTokens.t.sol rename to test/fork/LidoARM/SwapTokensForExactTokens.t.sol diff --git a/test/fork/LidoFixedPriceMultiLpARM/TotalAssets.t.sol b/test/fork/LidoARM/TotalAssets.t.sol similarity index 98% rename from test/fork/LidoFixedPriceMultiLpARM/TotalAssets.t.sol rename to test/fork/LidoARM/TotalAssets.t.sol index 84078e1b..f77ec6fb 100644 --- a/test/fork/LidoFixedPriceMultiLpARM/TotalAssets.t.sol +++ b/test/fork/LidoARM/TotalAssets.t.sol @@ -7,10 +7,6 @@ import {stdError} from "forge-std/StdError.sol"; // Test imports import {Fork_Shared_Test_} from "test/fork/shared/Shared.sol"; -// Contracts -import {IERC20} from "contracts/Interfaces.sol"; -import {AbstractARM} from "contracts/AbstractARM.sol"; - contract Fork_Concrete_LidoARM_TotalAssets_Test_ is Fork_Shared_Test_ { ////////////////////////////////////////////////////// /// --- SETUP diff --git a/test/fork/OethARM/Ownable.t.sol b/test/fork/OethARM/Ownable.t.sol deleted file mode 100644 index 02240846..00000000 --- a/test/fork/OethARM/Ownable.t.sol +++ /dev/null @@ -1,49 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity 0.8.23; - -// Test imports -import {Fork_Shared_Test_} from "test/fork/shared/Shared.sol"; - -// Utils -import {Mainnet} from "contracts/utils/Addresses.sol"; - -/// @notice The purpose of this contract is to test the `Ownable` contract. -contract Fork_Concrete_OethARM_Ownable_Test_ is Fork_Shared_Test_ { - ////////////////////////////////////////////////////// - /// --- REVERTING TESTS - ////////////////////////////////////////////////////// - function test_RevertWhen_SetOperator_Because_NotOwner() public { - vm.expectRevert("ARM: Only owner can call this function."); - vm.prank(alice); - oethARM.setOperator(deployer); - } - - function test_RevertWhen_SetOwner_Because_NotOwner() public { - vm.expectRevert("ARM: Only owner can call this function."); - vm.prank(alice); - oethARM.setOwner(deployer); - } - - ////////////////////////////////////////////////////// - /// --- PASSING TESTS - ////////////////////////////////////////////////////// - function test_SetOperator() public asOwner { - // Assertions before - assertEq(oethARM.operator(), operator); - - oethARM.setOperator(alice); - - // Assertions after - assertEq(oethARM.operator(), alice); - } - - function test_SetOwner() public asOwner { - // Assertions before - assertEq(oethARM.owner(), Mainnet.TIMELOCK); - - oethARM.setOwner(alice); - - // Assertions after - assertEq(oethARM.owner(), alice); - } -} diff --git a/test/fork/OethARM/Proxy.t.sol b/test/fork/OethARM/Proxy.t.sol deleted file mode 100644 index e55f2512..00000000 --- a/test/fork/OethARM/Proxy.t.sol +++ /dev/null @@ -1,69 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity 0.8.23; - -// Contracts -import {OethARM} from "contracts/OethARM.sol"; - -// Test imports -import {Fork_Shared_Test_} from "test/fork/shared/Shared.sol"; - -// Utils -import {Mainnet} from "contracts/utils/Addresses.sol"; - -/// @notice The purpose of this contract is to test the `Proxy` contract. -contract Fork_Concrete_OethARM_Proxy_Test_ is Fork_Shared_Test_ { - ////////////////////////////////////////////////////// - /// --- REVERTING TESTS - ////////////////////////////////////////////////////// - function test_RevertWhen_UnauthorizedAccess() public { - vm.expectRevert("ARM: Only owner can call this function."); - proxy.setOwner(deployer); - - vm.expectRevert("ARM: Only owner can call this function."); - proxy.initialize(address(this), address(this), ""); - - vm.expectRevert("ARM: Only owner can call this function."); - proxy.upgradeTo(address(this)); - - vm.expectRevert("ARM: Only owner can call this function."); - proxy.upgradeToAndCall(address(this), ""); - } - - ////////////////////////////////////////////////////// - /// --- PASSING TESTS - ////////////////////////////////////////////////////// - function test_Upgrade() public asOwner { - address owner = Mainnet.TIMELOCK; - - // Deploy new implementation - OethARM newImplementation = new OethARM(Mainnet.OETH, Mainnet.WETH, Mainnet.OETH_VAULT); - proxy.upgradeTo(address(newImplementation)); - assertEq(proxy.implementation(), address(newImplementation)); - - // Ensure ownership was preserved. - assertEq(proxy.owner(), owner); - assertEq(oethARM.owner(), owner); - - // Ensure the storage was preserved through the upgrade. - assertEq(address(oethARM.token0()), Mainnet.OETH); - assertEq(address(oethARM.token1()), Mainnet.WETH); - } - - function test_UpgradeAndCall() public asOwner { - address owner = Mainnet.TIMELOCK; - - // Deploy new implementation - OethARM newImplementation = new OethARM(Mainnet.OETH, Mainnet.WETH, Mainnet.OETH_VAULT); - bytes memory data = abi.encodeWithSignature("setOperator(address)", address(0x123)); - - proxy.upgradeToAndCall(address(newImplementation), data); - assertEq(proxy.implementation(), address(newImplementation)); - - // Ensure ownership was preserved. - assertEq(proxy.owner(), owner); - assertEq(oethARM.owner(), owner); - - // Ensure the post upgrade code was run - assertEq(oethARM.operator(), address(0x123)); - } -} diff --git a/test/fork/OethARM/SwapExactTokensForTokens.t.sol b/test/fork/OethARM/SwapExactTokensForTokens.t.sol deleted file mode 100644 index 111afa2a..00000000 --- a/test/fork/OethARM/SwapExactTokensForTokens.t.sol +++ /dev/null @@ -1,132 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity 0.8.23; - -// Test imports -import {Fork_Shared_Test_} from "test/fork/shared/Shared.sol"; - -// Interfaces -import {IERC20} from "contracts/Interfaces.sol"; - -/// @notice The purpose of this contract is to test the `swapExactTokensForTokens` function in the `OethARM` contract. -contract Fork_Concrete_OethARM_SwapExactTokensForTokens_Test_ is Fork_Shared_Test_ { - address[] path; - - ////////////////////////////////////////////////////// - /// --- SETUP - ////////////////////////////////////////////////////// - function setUp() public override { - super.setUp(); - - path = new address[](2); - path[0] = address(oeth); - path[1] = address(weth); - - // Deal tokens - deal(address(oeth), address(this), 100 ether); - deal(address(weth), address(oethARM), 100 ether); - deal(address(oeth), address(oethARM), 100 ether); - - // Approve tokens - oeth.approve(address(oethARM), type(uint256).max); - } - - ////////////////////////////////////////////////////// - /// --- REVERTING TESTS - ////////////////////////////////////////////////////// - function test_RevertWhen_SwapExactTokensForTokens_Simple_Because_InsufficientOutputAmount() public { - vm.expectRevert("ARM: Insufficient output amount"); - oethARM.swapExactTokensForTokens(oeth, weth, 10 ether, 11 ether, address(this)); - } - - function test_RevertWhen_SwapExactTokensForTokens_Simple_Because_InvalidSwap_TokenIn() public { - vm.expectRevert("ARM: Invalid swap"); - oethARM.swapExactTokensForTokens(weth, weth, 10 ether, 10 ether, address(this)); - } - - function test_RevertWhen_SwapExactTokensForTokens_Simple_Because_InvalidSwap_TokenOut() public { - vm.expectRevert("ARM: Invalid swap"); - oethARM.swapExactTokensForTokens(oeth, oeth, 10 ether, 10 ether, address(this)); - } - - function test_RevertWhen_SwapExactTokensForTokens_Complex_Because_InsuficientOutputAmount() public { - vm.expectRevert("ARM: Insufficient output amount"); - oethARM.swapExactTokensForTokens(10 ether, 11 ether, path, address(this), block.timestamp + 10); - } - - function test_RevertWhen_SwapExactTokensForTokens_Complex_Because_InvalidPathLength() public { - vm.expectRevert("ARM: Invalid path length"); - oethARM.swapExactTokensForTokens(10 ether, 10 ether, new address[](3), address(this), 0); - } - - function test_RevertWhen_SwapExactTokensForTokens_Complex_Because_DeadlineExpired() public { - vm.expectRevert("ARM: Deadline expired"); - oethARM.swapExactTokensForTokens(10 ether, 10 ether, path, address(this), 0); - } - - function test_RevertWhen_SwapExactTokensForTokens_Complex_Because_InvalidSwap_TokenIn() public { - path[0] = address(weth); - path[1] = address(weth); - vm.expectRevert("ARM: Invalid swap"); - oethARM.swapExactTokensForTokens(10 ether, 10 ether, path, address(this), block.timestamp + 10); - } - - function test_RevertWhen_SwapExactTokensForTokens_Complex_Because_InvalidSwap_TokenOut() public { - path[0] = address(oeth); - path[1] = address(oeth); - vm.expectRevert("ARM: Invalid swap"); - oethARM.swapExactTokensForTokens(10 ether, 10 ether, path, address(this), block.timestamp + 10); - } - - ////////////////////////////////////////////////////// - /// --- PASSING TESTS - ////////////////////////////////////////////////////// - function test_SwapExactTokensForTokens_Simple() public { - // Assertions before - assertEq(weth.balanceOf(address(this)), 0 ether, "WETH balance user"); - assertEq(oeth.balanceOf(address(this)), 100 ether, "OETH balance user"); - assertEq(weth.balanceOf(address(oethARM)), 100 ether, "OETH balance ARM"); - assertEq(weth.balanceOf(address(oethARM)), 100 ether, "WETH balance ARM"); - - // Expected events - vm.expectEmit({emitter: address(oeth)}); - emit IERC20.Transfer(address(this), address(oethARM), 10 ether); - vm.expectEmit({emitter: address(weth)}); - emit IERC20.Transfer(address(oethARM), address(this), 10 ether); - // Main call - oethARM.swapExactTokensForTokens(oeth, weth, 10 ether, 10 ether, address(this)); - - // Assertions after - assertEq(weth.balanceOf(address(this)), 10 ether, "WETH balance user"); - assertEq(oeth.balanceOf(address(this)), 90 ether, "OETH balance"); - assertEq(weth.balanceOf(address(oethARM)), 90 ether, "WETH balance ARM"); - assertEq(oeth.balanceOf(address(oethARM)), 110 ether, "OETH balance ARM"); - } - - function test_SwapExactTokensForTokens_Complex() public { - // Assertions before - assertEq(weth.balanceOf(address(this)), 0 ether, "WETH balance user"); - assertEq(oeth.balanceOf(address(this)), 100 ether, "OETH balance user"); - assertEq(weth.balanceOf(address(oethARM)), 100 ether, "OETH balance ARM"); - assertEq(weth.balanceOf(address(oethARM)), 100 ether, "WETH balance ARM"); - - path[0] = address(oeth); - path[1] = address(weth); - - // Expected events - vm.expectEmit({emitter: address(oeth)}); - emit IERC20.Transfer(address(this), address(oethARM), 10 ether); - vm.expectEmit({emitter: address(weth)}); - emit IERC20.Transfer(address(oethARM), address(this), 10 ether); - // Main call - uint256[] memory amounts = - oethARM.swapExactTokensForTokens(10 ether, 10 ether, path, address(this), block.timestamp + 1000); - - // Assertions after - assertEq(amounts[0], 10 ether, "Amounts[0]"); - assertEq(amounts[1], 10 ether, "Amounts[1]"); - assertEq(weth.balanceOf(address(this)), 10 ether, "WETH balance user"); - assertEq(oeth.balanceOf(address(this)), 90 ether, "OETH balance"); - assertEq(weth.balanceOf(address(oethARM)), 90 ether, "WETH balance ARM"); - assertEq(oeth.balanceOf(address(oethARM)), 110 ether, "OETH balance ARM"); - } -} diff --git a/test/fork/OethARM/SwapTokensForExactTokens.t.sol b/test/fork/OethARM/SwapTokensForExactTokens.t.sol deleted file mode 100644 index 3e012379..00000000 --- a/test/fork/OethARM/SwapTokensForExactTokens.t.sol +++ /dev/null @@ -1,131 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity 0.8.23; - -// Test imports -import {Fork_Shared_Test_} from "test/fork/shared/Shared.sol"; - -// Interfaces -import {IERC20} from "contracts/Interfaces.sol"; - -/// @notice The purpose of this contract is to test the `swapTokensForExactTokens` function in the `OethARM` contract. -contract Fork_Concrete_OethARM_SwapTokensForExactTokens_Test_ is Fork_Shared_Test_ { - address[] path; - - ////////////////////////////////////////////////////// - /// --- SETUP - ////////////////////////////////////////////////////// - function setUp() public override { - super.setUp(); - - path = new address[](2); - path[0] = address(oeth); - path[1] = address(weth); - - // Deal tokens - deal(address(oeth), address(this), 100 ether); - deal(address(weth), address(oethARM), 100 ether); - deal(address(oeth), address(oethARM), 100 ether); - - // Approve tokens - oeth.approve(address(oethARM), type(uint256).max); - } - - ////////////////////////////////////////////////////// - /// --- REVERTING TESTS - ////////////////////////////////////////////////////// - function test_RevertWhen_SwapTokensForExactTokens_Simple_Because_InsufficientOutputAmount() public { - vm.expectRevert("ARM: Excess input amount"); - oethARM.swapTokensForExactTokens(oeth, weth, 10 ether, 9 ether, address(this)); - } - - function test_RevertWhen_SwapTokensForExactTokens_Simple_Because_InvalidSwap_TokenIn() public { - vm.expectRevert("ARM: Invalid swap"); - oethARM.swapTokensForExactTokens(weth, weth, 10 ether, 10 ether, address(this)); - } - - function test_RevertWhen_SwapTokensForExactTokens_Simple_Because_InvalidSwap_TokenOut() public { - vm.expectRevert("ARM: Invalid swap"); - oethARM.swapTokensForExactTokens(oeth, oeth, 10 ether, 10 ether, address(this)); - } - - function test_RevertWhen_SwapTokensForExactTokens_Complex_Because_InsufficientOutputAmount() public { - vm.expectRevert("ARM: Excess input amount"); - oethARM.swapTokensForExactTokens(10 ether, 9 ether, path, address(this), block.timestamp + 10); - } - - function test_RevertWhen_SwapTokensForExactTokens_Complex_Because_InvalidPathLength() public { - vm.expectRevert("ARM: Invalid path length"); - oethARM.swapTokensForExactTokens(10 ether, 10 ether, new address[](3), address(this), block.timestamp + 10); - } - - function test_RevertWhen_SwapTokensForExactTokens_Complex_Because_DeadlineExpired() public { - vm.expectRevert("ARM: Deadline expired"); - oethARM.swapTokensForExactTokens(10 ether, 10 ether, path, address(this), block.timestamp - 1); - } - - function test_RevertWhen_SwapTokensForExactTokens_Complex_Because_InvalidSwap_TokenIn() public { - path[0] = address(weth); - path[1] = address(weth); - vm.expectRevert("ARM: Invalid swap"); - oethARM.swapTokensForExactTokens(10 ether, 10 ether, path, address(this), block.timestamp + 10); - } - - function test_RevertWhen_SwapTokensForExactTokens_Complex_Because_InvalidSwap_TokenOut() public { - path[0] = address(oeth); - path[1] = address(oeth); - vm.expectRevert("ARM: Invalid swap"); - oethARM.swapTokensForExactTokens(10 ether, 10 ether, path, address(this), block.timestamp + 10); - } - - ////////////////////////////////////////////////////// - /// --- PASSING TESTS - ////////////////////////////////////////////////////// - function test_SwapTokensForExactTokens_Simple() public { - // Assertions before - assertEq(weth.balanceOf(address(this)), 0 ether, "WETH balance user"); - assertEq(oeth.balanceOf(address(this)), 100 ether, "OETH balance user"); - assertEq(weth.balanceOf(address(oethARM)), 100 ether, "OETH balance ARM"); - assertEq(weth.balanceOf(address(oethARM)), 100 ether, "WETH balance ARM"); - - // Expected events - vm.expectEmit({emitter: address(oeth)}); - emit IERC20.Transfer(address(this), address(oethARM), 10 ether); - vm.expectEmit({emitter: address(weth)}); - emit IERC20.Transfer(address(oethARM), address(this), 10 ether); - // Main call - oethARM.swapTokensForExactTokens(oeth, weth, 10 ether, 10 ether, address(this)); - - // Assertions after - assertEq(weth.balanceOf(address(this)), 10 ether, "WETH balance user"); - assertEq(oeth.balanceOf(address(this)), 90 ether, "OETH balance"); - assertEq(weth.balanceOf(address(oethARM)), 90 ether, "WETH balance ARM"); - assertEq(oeth.balanceOf(address(oethARM)), 110 ether, "OETH balance ARM"); - } - - function test_SwapTokensForExactTokens_Complex() public { - // Assertions before - assertEq(weth.balanceOf(address(this)), 0 ether, "WETH balance user"); - assertEq(oeth.balanceOf(address(this)), 100 ether, "OETH balance user"); - assertEq(weth.balanceOf(address(oethARM)), 100 ether, "OETH balance ARM"); - assertEq(weth.balanceOf(address(oethARM)), 100 ether, "WETH balance ARM"); - - path[0] = address(oeth); - path[1] = address(weth); - // Expected events - vm.expectEmit({emitter: address(oeth)}); - emit IERC20.Transfer(address(this), address(oethARM), 10 ether); - vm.expectEmit({emitter: address(weth)}); - emit IERC20.Transfer(address(oethARM), address(this), 10 ether); - // Main call - uint256[] memory amounts = - oethARM.swapTokensForExactTokens(10 ether, 10 ether, path, address(this), block.timestamp + 1000); - - // Assertions after - assertEq(amounts[0], 10 ether, "Amounts[0]"); - assertEq(amounts[1], 10 ether, "Amounts[1]"); - assertEq(weth.balanceOf(address(this)), 10 ether, "WETH balance user"); - assertEq(oeth.balanceOf(address(this)), 90 ether, "OETH balance"); - assertEq(weth.balanceOf(address(oethARM)), 90 ether, "WETH balance ARM"); - assertEq(oeth.balanceOf(address(oethARM)), 110 ether, "OETH balance ARM"); - } -} diff --git a/test/fork/OethARM/Transfer.t.sol b/test/fork/OethARM/Transfer.t.sol deleted file mode 100644 index 90832e33..00000000 --- a/test/fork/OethARM/Transfer.t.sol +++ /dev/null @@ -1,57 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity 0.8.23; - -// Test imports -import {Fork_Shared_Test_} from "test/fork/shared/Shared.sol"; - -// Interfaces -import {IERC20} from "contracts/Interfaces.sol"; - -/// @notice The purpose of this contract is to test the `transferToken` and `transferEth` functions in the `OethARM` contract. -contract Fork_Concrete_OethARM_Transfer_Test_ is Fork_Shared_Test_ { - bool public shouldRevertOnReceive; - - ////////////////////////////////////////////////////// - /// --- SETUP - ////////////////////////////////////////////////////// - - function setUp() public override { - super.setUp(); - - // Deal tokens - deal(address(oethARM), 100 ether); - deal(address(weth), address(oethARM), 100 ether); - } - - ////////////////////////////////////////////////////// - /// --- REVERTING TESTS - ////////////////////////////////////////////////////// - function test_RevertWhen_TransferToken_Because_NotOwner() public { - vm.expectRevert("ARM: Only owner can call this function."); - oethARM.transferToken(address(0), address(0), 0); - } - - ////////////////////////////////////////////////////// - /// --- PASSING TESTS - ////////////////////////////////////////////////////// - function test_TransferToken() public asOwner { - // Assertions before - assertEq(weth.balanceOf(address(this)), 0); - assertEq(weth.balanceOf(address(oethARM)), 100 ether); - - vm.expectEmit({emitter: address(weth)}); - emit IERC20.Transfer(address(oethARM), address(this), 10 ether); - oethARM.transferToken(address(weth), address(this), 10 ether); - - // Assertions after - assertEq(weth.balanceOf(address(this)), 10 ether); - assertEq(weth.balanceOf(address(oethARM)), 90 ether); - } - - ////////////////////////////////////////////////////// - /// --- RECEIVE - ////////////////////////////////////////////////////// - receive() external payable { - if (shouldRevertOnReceive) revert(); - } -} diff --git a/test/fork/OethARM/Withdraw.t.sol b/test/fork/OethARM/Withdraw.t.sol deleted file mode 100644 index f63728ee..00000000 --- a/test/fork/OethARM/Withdraw.t.sol +++ /dev/null @@ -1,105 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity 0.8.23; - -// Test imports -import {Fork_Shared_Test_} from "test/fork/shared/Shared.sol"; - -// Interfaces -import {IERC20} from "contracts/Interfaces.sol"; - -/// @notice The purpose of this contract is to test the `requestWithdrawal`, -/// `claimWithdrawal` and `claimWithdrawals` functions in the `OethARM` contract. -contract Fork_Concrete_OethARM_Withdraw_Test_ is Fork_Shared_Test_ { - ////////////////////////////////////////////////////// - /// --- SETUP - ////////////////////////////////////////////////////// - function setUp() public override { - super.setUp(); - - // Deal tokens - deal(address(oeth), address(oethARM), 10 ether); - deal(address(weth), address(vault), 10 ether); - - // Remove solvency check - vm.prank(vault.governor()); - vault.setMaxSupplyDiff(0); - } - - ////////////////////////////////////////////////////// - /// --- REVERTING TESTS - ////////////////////////////////////////////////////// - function test_RevertWhen_RequestWithdraw() public { - vm.expectRevert("ARM: Only operator or owner can call this function."); - oethARM.requestWithdrawal(1 ether); - } - - function test_RevertWhen_ClaimWithdraw() public { - vm.expectRevert("ARM: Only operator or owner can call this function."); - oethARM.claimWithdrawal(0); - } - - function test_RevertWhen_ClaimWithdraws() public { - vm.expectRevert("ARM: Only operator or owner can call this function."); - oethARM.claimWithdrawals(new uint256[](0)); - } - - ////////////////////////////////////////////////////// - /// --- PASSING TESTS - ////////////////////////////////////////////////////// - function test_RequestWithdraw() public asOwner mockCallDripperCollect { - (uint128 queuedBefore,,, uint128 nextWithdrawalIndex) = vault.withdrawalQueueMetadata(); - vm.expectEmit({emitter: address(oeth)}); - emit IERC20.Transfer(address(oethARM), address(0), 1 ether); - (uint256 requestId, uint256 queued) = oethARM.requestWithdrawal(1 ether); - - // Assertions after - assertEq(requestId, nextWithdrawalIndex, "Request ID"); - assertEq(queued, queuedBefore + 1 ether, "Queued amount should be 1 ether"); - assertEq(oeth.balanceOf(address(oethARM)), 9 ether, "OETH balance should be 99 ether"); - } - - function test_ClaimWithdraw_() public asOwner mockCallDripperCollect { - // First request withdrawal - (uint256 requestId,) = oethARM.requestWithdrawal(1 ether); - - // Add more liquidity to facilitate withdrawal - (uint128 queued,, uint128 claimed,) = vault.withdrawalQueueMetadata(); - deal(address(weth), address(vault), queued - claimed + 1 ether); - - // Add liquidity to the withdrawal queue - vault.addWithdrawalQueueLiquidity(); - - // Skip delay - skip(10 minutes); - - // Then claim withdrawal - oethARM.claimWithdrawal(requestId); - - // Assertions after - assertEq(weth.balanceOf(address(oethARM)), 1 ether, "WETH balance should be 1 ether"); - } - - function test_ClaimWithdraws() public asOwner mockCallDripperCollect { - (,,, uint128 nextWithdrawalIndex) = vault.withdrawalQueueMetadata(); - - // First request withdrawal - oethARM.requestWithdrawal(1 ether); - oethARM.requestWithdrawal(1 ether); - - // Add more liquidity to facilitate withdrawal - (uint128 queued,, uint128 claimed,) = vault.withdrawalQueueMetadata(); - deal(address(weth), address(vault), queued - claimed + 2 ether); - - // Skip withdrawal queue delay - skip(10 minutes); - - uint256[] memory requestIds = new uint256[](2); - requestIds[0] = nextWithdrawalIndex; - requestIds[1] = nextWithdrawalIndex + 1; - // Then claim withdrawal - oethARM.claimWithdrawals(requestIds); - - // Assertions after - assertEq(weth.balanceOf(address(oethARM)), 2 ether, "WETH balance should be 1 ether"); - } -} diff --git a/test/fork/shared/Shared.sol b/test/fork/shared/Shared.sol index b583f31a..707dde99 100644 --- a/test/fork/shared/Shared.sol +++ b/test/fork/shared/Shared.sol @@ -9,7 +9,6 @@ import {Modifiers} from "test/fork/utils/Modifiers.sol"; // Contracts import {Proxy} from "contracts/Proxy.sol"; -import {OethARM} from "contracts/OethARM.sol"; import {LidoARM} from "contracts/LidoARM.sol"; import {CapManager} from "contracts/CapManager.sol"; import {ZapperLidoARM} from "contracts/ZapperLidoARM.sol"; @@ -20,7 +19,6 @@ import {IOriginVault} from "contracts/Interfaces.sol"; // Utils import {Mainnet} from "contracts/utils/Addresses.sol"; -import {AddressResolver} from "contracts/utils/Addresses.sol"; /// @notice This contract should inherit (directly or indirectly) from `Base_Test_`. /// It should be used to setup the FORK test ONLY! @@ -106,27 +104,14 @@ abstract contract Fork_Shared_Test_ is Modifiers { function _deployContracts() internal { // --- Deploy all proxies --- - proxy = new Proxy(); lpcProxy = new Proxy(); lidoProxy = new Proxy(); - - // --- Deploy OethARM implementation --- - // Deploy OethARM implementation. - address implementation = address(new OethARM(address(oeth), address(weth), address(vault))); - vm.label(implementation, "OETH ARM IMPLEMENTATION"); - - // Initialize Proxy with OethARM implementation. - bytes memory data = abi.encodeWithSignature("initialize(address)", operator); - proxy.initialize(implementation, governor, data); - - // Set the Proxy as the OethARM. - oethARM = OethARM(address(proxy)); - // --- Deploy CapManager implementation --- // Deploy CapManager implementation. CapManager capManagerImpl = new CapManager(address(lidoProxy)); // Initialize Proxy with CapManager implementation. + bytes memory data = abi.encodeWithSignature("initialize(address)", operator); lpcProxy.initialize(address(capManagerImpl), address(this), data); // Set the Proxy as the CapManager. @@ -176,8 +161,6 @@ abstract contract Fork_Shared_Test_ is Modifiers { vm.label(address(steth), "stETH"); vm.label(address(badToken), "BAD TOKEN"); vm.label(address(vault), "OETH VAULT"); - vm.label(address(oethARM), "OETH ARM"); - vm.label(address(proxy), "OETH ARM PROXY"); vm.label(address(lidoARM), "LIDO ARM"); vm.label(address(lidoProxy), "LIDO ARM PROXY"); vm.label(address(capManager), "LIQUIDITY PROVIDER CONTROLLER"); diff --git a/test/fork/utils/Modifiers.sol b/test/fork/utils/Modifiers.sol index f55933d4..753aac74 100644 --- a/test/fork/utils/Modifiers.sol +++ b/test/fork/utils/Modifiers.sol @@ -21,13 +21,6 @@ abstract contract Modifiers is Helpers { vm.stopPrank(); } - /// @notice Impersonate the owner of the OethARM contract. - modifier asOwner() { - vm.startPrank(oethARM.owner()); - _; - vm.stopPrank(); - } - /// @notice Impersonate the governor of the vault. modifier asGovernor() { vm.startPrank(vault.governor()); diff --git a/test/invariants/LidoARM/FuzzerFoundry.sol b/test/invariants/LidoARM/FuzzerFoundry.sol index df7815ca..f5c6a1a4 100644 --- a/test/invariants/LidoARM/FuzzerFoundry.sol +++ b/test/invariants/LidoARM/FuzzerFoundry.sol @@ -4,7 +4,7 @@ pragma solidity 0.8.23; // Test imports import {TargetFunction} from "test/invariants/LidoARM/TargetFunction.sol"; -contract FuzzerFoundry_OethARM is TargetFunction { +contract FuzzerFoundry_LidoARM is TargetFunction { uint256 private constant NUM_LPS = 4; uint256 private constant NUM_SWAPS = 3; uint256 private constant MAX_WETH_PER_USERS = 1_000_000 ether; diff --git a/test/invariants/LidoARM/Unit.sol b/test/invariants/LidoARM/Unit.sol index 65497401..ba3cbcbd 100644 --- a/test/invariants/LidoARM/Unit.sol +++ b/test/invariants/LidoARM/Unit.sol @@ -2,13 +2,13 @@ pragma solidity 0.8.23; import {Test} from "forge-std/Test.sol"; -import {FuzzerFoundry_OethARM} from "test/invariants/LidoARM/FuzzerFoundry.sol"; +import {FuzzerFoundry_LidoARM} from "test/invariants/LidoARM/FuzzerFoundry.sol"; contract Unit is Test { - FuzzerFoundry_OethARM f; + FuzzerFoundry_LidoARM f; function setUp() public { - f = new FuzzerFoundry_OethARM(); + f = new FuzzerFoundry_LidoARM(); f.setUp(); } diff --git a/test/smoke/EtherFiARMSmokeTest.t.sol b/test/smoke/EtherFiARMSmokeTest.t.sol index 8049888d..e57003ed 100644 --- a/test/smoke/EtherFiARMSmokeTest.t.sol +++ b/test/smoke/EtherFiARMSmokeTest.t.sol @@ -1,11 +1,9 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.23; -import {Test, console2} from "forge-std/Test.sol"; - import {AbstractSmokeTest} from "./AbstractSmokeTest.sol"; -import {IERC20, IEETHWithdrawal, IEETHWithdrawalNFT} from "contracts/Interfaces.sol"; +import {IERC20, IEETHWithdrawalNFT} from "contracts/Interfaces.sol"; import {EtherFiARM} from "contracts/EtherFiARM.sol"; import {CapManager} from "contracts/CapManager.sol"; import {Proxy} from "contracts/Proxy.sol"; diff --git a/test/smoke/LidoARMSmokeTest.t.sol b/test/smoke/LidoARMSmokeTest.t.sol index 62c83c01..45a9dfda 100644 --- a/test/smoke/LidoARMSmokeTest.t.sol +++ b/test/smoke/LidoARMSmokeTest.t.sol @@ -1,8 +1,6 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.23; -import {Test, console2} from "forge-std/Test.sol"; - import {AbstractSmokeTest} from "./AbstractSmokeTest.sol"; import {IERC20, IStETHWithdrawal} from "contracts/Interfaces.sol"; diff --git a/test/smoke/OethARMSmokeTest.t.sol b/test/smoke/OethARMSmokeTest.t.sol index 720ab5a2..24ea53e9 100644 --- a/test/smoke/OethARMSmokeTest.t.sol +++ b/test/smoke/OethARMSmokeTest.t.sol @@ -16,7 +16,7 @@ contract Fork_OriginARM_Smoke_Test is AbstractSmokeTest { IERC20 weth; IERC20 oeth; Proxy proxy; - OriginARM oethARM; + OriginARM originARM; address operator; function setUp() public { @@ -29,10 +29,10 @@ contract Fork_OriginARM_Smoke_Test is AbstractSmokeTest { vm.label(address(operator), "OPERATOR"); proxy = Proxy(payable(deployManager.getDeployment("OETH_ARM"))); - oethARM = OriginARM(deployManager.getDeployment("OETH_ARM")); + originARM = OriginARM(deployManager.getDeployment("OETH_ARM")); - _dealWETH(address(oethARM), 100 ether); - _dealOETH(address(oethARM), 100 ether); + _dealWETH(address(originARM), 100 ether); + _dealOETH(address(originARM), 100 ether); // Only fuzz from this address. Big speedup on fork. targetSender(address(this)); @@ -56,28 +56,28 @@ contract Fork_OriginARM_Smoke_Test is AbstractSmokeTest { //////////////////////////////////////////////////// function test_initialConfig() external view { // Ownership and fees - assertEq(oethARM.name(), "Origin ARM", "Name"); - assertEq(oethARM.symbol(), "ARM-WETH-OETH", "Symbol"); - assertEq(oethARM.owner(), Mainnet.TIMELOCK, "Owner"); - assertEq(oethARM.operator(), Mainnet.ARM_RELAYER, "Operator"); - assertEq(oethARM.feeCollector(), Mainnet.ARM_BUYBACK, "Fee collector"); - assertEq((100 * uint256(oethARM.fee())) / oethARM.FEE_SCALE(), 20, "Performance fee as a percentage"); + assertEq(originARM.name(), "Origin ARM", "Name"); + assertEq(originARM.symbol(), "ARM-WETH-OETH", "Symbol"); + assertEq(originARM.owner(), Mainnet.TIMELOCK, "Owner"); + assertEq(originARM.operator(), Mainnet.ARM_RELAYER, "Operator"); + assertEq(originARM.feeCollector(), Mainnet.ARM_BUYBACK, "Fee collector"); + assertEq((100 * uint256(originARM.fee())) / originARM.FEE_SCALE(), 20, "Performance fee as a percentage"); // Assets - assertEq(address(oethARM.token0()), address(weth), "token0"); - assertEq(address(oethARM.token1()), address(oeth), "token1"); - assertEq(oethARM.liquidityAsset(), Mainnet.WETH, "liquidity asset"); - assertEq(oethARM.baseAsset(), Mainnet.OETH, "base asset"); - assertEq(oethARM.asset(), Mainnet.WETH, "ERC-4626 asset"); + assertEq(address(originARM.token0()), address(weth), "token0"); + assertEq(address(originARM.token1()), address(oeth), "token1"); + assertEq(originARM.liquidityAsset(), Mainnet.WETH, "liquidity asset"); + assertEq(originARM.baseAsset(), Mainnet.OETH, "base asset"); + assertEq(originARM.asset(), Mainnet.WETH, "ERC-4626 asset"); // Prices - assertNotEq(oethARM.crossPrice(), 0, "cross price"); - assertNotEq(oethARM.traderate0(), 0, "traderate0"); - assertNotEq(oethARM.traderate1(), 0, "traderate1"); + assertNotEq(originARM.crossPrice(), 0, "cross price"); + assertNotEq(originARM.traderate0(), 0, "traderate0"); + assertNotEq(originARM.traderate1(), 0, "traderate1"); // Redemption - assertEq(address(oethARM.vault()), Mainnet.OETH_VAULT, "OETH Vault"); - assertEq(oethARM.claimDelay(), 10 minutes, "claim delay"); + assertEq(address(originARM.vault()), Mainnet.OETH_VAULT, "OETH Vault"); + assertEq(originARM.claimDelay(), 10 minutes, "claim delay"); } //////////////////////////////////////////////////// @@ -97,11 +97,11 @@ contract Fork_OriginARM_Smoke_Test is AbstractSmokeTest { function test_swap_exact_weth_for_oeth() external { // For this test, we need to set the cross price to 0.9999e36, which requires // moving out all OETH from the ARM. - vm.startPrank(address(oethARM)); - oeth.transfer(address(this), oeth.balanceOf(address(oethARM))); + vm.startPrank(address(originARM)); + oeth.transfer(address(this), oeth.balanceOf(address(originARM))); vm.stopPrank(); vm.prank(Mainnet.TIMELOCK); - oethARM.setCrossPrice(0.9999e36); + originARM.setCrossPrice(0.9999e36); // trader buys OETH and sells WETH, the ARM sells OETH at a // 0.5 bps discount @@ -116,30 +116,30 @@ contract Fork_OriginARM_Smoke_Test is AbstractSmokeTest { // Trader is buying stETH and selling WETH // the ARM is selling stETH and buying WETH deal(address(weth), address(this), 1_000_000 ether); - _dealOETH(address(oethARM), 1000 ether); + _dealOETH(address(originARM), 1000 ether); expectedOut = amountIn * 1e36 / price; vm.prank(Mainnet.ARM_RELAYER); - oethARM.setPrices(price - 2e32, price); + originARM.setPrices(price - 2e32, price); } else { // Trader is selling stETH and buying WETH // the ARM is buying stETH and selling WETH _dealOETH(address(this), 1000 ether); - deal(address(weth), address(oethARM), 1_000_000 ether); + deal(address(weth), address(originARM), 1_000_000 ether); expectedOut = amountIn * price / 1e36; vm.prank(Mainnet.ARM_RELAYER); - oethARM.setPrices(price, 1e36); + originARM.setPrices(price, 1e36); } // Approve the ARM to transfer the input token of the swap. - inToken.approve(address(oethARM), amountIn); + inToken.approve(address(originARM), amountIn); uint256 startIn = inToken.balanceOf(address(this)); uint256 startOut = outToken.balanceOf(address(this)); - oethARM.swapExactTokensForTokens(inToken, outToken, amountIn, 0, address(this)); + originARM.swapExactTokensForTokens(inToken, outToken, amountIn, 0, address(this)); assertApproxEqAbs(inToken.balanceOf(address(this)), startIn - amountIn, 2, "In actual"); assertApproxEqAbs(outToken.balanceOf(address(this)), startOut + expectedOut, 2, "Out actual"); @@ -158,11 +158,11 @@ contract Fork_OriginARM_Smoke_Test is AbstractSmokeTest { function test_swap_weth_for_exact_oeth() external { // For this test, we need to set the cross price to 0.9999e36, which requires // moving out all OETH from the ARM. - vm.startPrank(address(oethARM)); - oeth.transfer(address(this), oeth.balanceOf(address(oethARM))); + vm.startPrank(address(originARM)); + oeth.transfer(address(this), oeth.balanceOf(address(originARM))); vm.stopPrank(); vm.prank(Mainnet.TIMELOCK); - oethARM.setCrossPrice(0.9999e36); + originARM.setCrossPrice(0.9999e36); // trader buys OETH and sells WETH, the ARM sells OETH at a // 0.5 bps discount @@ -177,30 +177,30 @@ contract Fork_OriginARM_Smoke_Test is AbstractSmokeTest { // Trader is buying stETH and selling WETH // the ARM is selling stETH and buying WETH deal(address(weth), address(this), 1_000_000 ether); - _dealOETH(address(oethARM), 1000 ether); + _dealOETH(address(originARM), 1000 ether); expectedIn = amountOut * price / 1e36; vm.prank(Mainnet.ARM_RELAYER); - oethARM.setPrices(price - 2e32, price); + originARM.setPrices(price - 2e32, price); } else { // Trader is selling stETH and buying WETH // the ARM is buying stETH and selling WETH _dealOETH(address(this), 1000 ether); - deal(address(weth), address(oethARM), 1_000_000 ether); + deal(address(weth), address(originARM), 1_000_000 ether); expectedIn = amountOut * 1e36 / price + 3; vm.prank(Mainnet.ARM_RELAYER); - oethARM.setPrices(price, 1e36); + originARM.setPrices(price, 1e36); } // Approve the ARM to transfer the input token of the swap. - inToken.approve(address(oethARM), expectedIn + 10000); + inToken.approve(address(originARM), expectedIn + 10000); uint256 startIn = inToken.balanceOf(address(this)); uint256 startOut = outToken.balanceOf(address(this)); - oethARM.swapTokensForExactTokens(inToken, outToken, amountOut, 3 * amountOut, address(this)); + originARM.swapTokensForExactTokens(inToken, outToken, amountOut, 3 * amountOut, address(this)); assertApproxEqAbs(inToken.balanceOf(address(this)), startIn - expectedIn, 3, "In actual"); assertApproxEqAbs(outToken.balanceOf(address(this)), startOut + amountOut, 3, "Out actual"); @@ -208,38 +208,38 @@ contract Fork_OriginARM_Smoke_Test is AbstractSmokeTest { function test_wrongInTokenExactIn() external { vm.expectRevert("ARM: Invalid in token"); - oethARM.swapExactTokensForTokens(BAD_TOKEN, oeth, 10 ether, 0, address(this)); + originARM.swapExactTokensForTokens(BAD_TOKEN, oeth, 10 ether, 0, address(this)); vm.expectRevert("ARM: Invalid in token"); - oethARM.swapExactTokensForTokens(BAD_TOKEN, weth, 10 ether, 0, address(this)); + originARM.swapExactTokensForTokens(BAD_TOKEN, weth, 10 ether, 0, address(this)); } function test_wrongOutTokenExactIn() external { vm.expectRevert("ARM: Invalid out token"); - oethARM.swapTokensForExactTokens(weth, BAD_TOKEN, 10 ether, 10 ether, address(this)); + originARM.swapTokensForExactTokens(weth, BAD_TOKEN, 10 ether, 10 ether, address(this)); vm.expectRevert("ARM: Invalid out token"); - oethARM.swapTokensForExactTokens(oeth, BAD_TOKEN, 10 ether, 10 ether, address(this)); + originARM.swapTokensForExactTokens(oeth, BAD_TOKEN, 10 ether, 10 ether, address(this)); vm.expectRevert("ARM: Invalid out token"); - oethARM.swapTokensForExactTokens(weth, weth, 10 ether, 10 ether, address(this)); + originARM.swapTokensForExactTokens(weth, weth, 10 ether, 10 ether, address(this)); vm.expectRevert("ARM: Invalid out token"); - oethARM.swapTokensForExactTokens(oeth, oeth, 10 ether, 10 ether, address(this)); + originARM.swapTokensForExactTokens(oeth, oeth, 10 ether, 10 ether, address(this)); } function test_wrongInTokenExactOut() external { vm.expectRevert("ARM: Invalid in token"); - oethARM.swapTokensForExactTokens(BAD_TOKEN, oeth, 10 ether, 10 ether, address(this)); + originARM.swapTokensForExactTokens(BAD_TOKEN, oeth, 10 ether, 10 ether, address(this)); vm.expectRevert("ARM: Invalid in token"); - oethARM.swapTokensForExactTokens(BAD_TOKEN, weth, 10 ether, 10 ether, address(this)); + originARM.swapTokensForExactTokens(BAD_TOKEN, weth, 10 ether, 10 ether, address(this)); } function test_wrongOutTokenExactOut() external { vm.expectRevert("ARM: Invalid out token"); - oethARM.swapTokensForExactTokens(weth, BAD_TOKEN, 10 ether, 10 ether, address(this)); + originARM.swapTokensForExactTokens(weth, BAD_TOKEN, 10 ether, 10 ether, address(this)); vm.expectRevert("ARM: Invalid out token"); - oethARM.swapTokensForExactTokens(oeth, BAD_TOKEN, 10 ether, 10 ether, address(this)); + originARM.swapTokensForExactTokens(oeth, BAD_TOKEN, 10 ether, 10 ether, address(this)); vm.expectRevert("ARM: Invalid out token"); - oethARM.swapTokensForExactTokens(weth, weth, 10 ether, 10 ether, address(this)); + originARM.swapTokensForExactTokens(weth, weth, 10 ether, 10 ether, address(this)); vm.expectRevert("ARM: Invalid out token"); - oethARM.swapTokensForExactTokens(oeth, oeth, 10 ether, 10 ether, address(this)); + originARM.swapTokensForExactTokens(oeth, oeth, 10 ether, 10 ether, address(this)); } //////////////////////////////////////////////////// @@ -264,34 +264,34 @@ contract Fork_OriginARM_Smoke_Test is AbstractSmokeTest { // Implementation's restricted methods. vm.expectRevert("ARM: Only owner can call this function."); - oethARM.setOwner(RANDOM_ADDRESS); + originARM.setOwner(RANDOM_ADDRESS); vm.stopPrank(); vm.expectRevert("ARM: Only owner can call this function."); vm.prank(operator); - oethARM.setOperator(operator); + originARM.setOperator(operator); } function test_setOperator() external { vm.prank(Mainnet.TIMELOCK); - oethARM.setOperator(address(this)); - assertEq(oethARM.operator(), address(this)); + originARM.setOperator(address(this)); + assertEq(originARM.operator(), address(this)); } //////////////////////////////////////////////////// /// --- VAULT WITHDRAWALS //////////////////////////////////////////////////// function test_request_origin_withdrawal() external { - _dealOETH(address(oethARM), 10 ether); + _dealOETH(address(originARM), 10 ether); vm.prank(Mainnet.ARM_RELAYER); - uint256 requestId = oethARM.requestOriginWithdrawal(10 ether); + uint256 requestId = originARM.requestOriginWithdrawal(10 ether); assertNotEq(requestId, 0); } function test_claim_origin_withdrawal() external { // Cheat section // Deal OETH to the ARM, in order to have some asset to withdraw - _dealOETH(address(oethARM), 10 ether); + _dealOETH(address(originARM), 10 ether); // Deal WETH to this test account to mint OETH in the Vault, directly increasing // the Vault's liquidity doesn't work because of the "Backing supply liquidity error" check _dealWETH(address(this), 10_000 ether); @@ -306,7 +306,7 @@ contract Fork_OriginARM_Smoke_Test is AbstractSmokeTest { // Request a withdrawal vm.prank(Mainnet.ARM_RELAYER); - uint256 requestId = oethARM.requestOriginWithdrawal(10 ether); + uint256 requestId = originARM.requestOriginWithdrawal(10 ether); // Fast forward time by 1 day to pass the claim delay vm.warp(block.timestamp + 1 days); @@ -315,7 +315,7 @@ contract Fork_OriginARM_Smoke_Test is AbstractSmokeTest { uint256[] memory requestIds = new uint256[](1); requestIds[0] = requestId; - uint256 amountClaimed = oethARM.claimOriginWithdrawals(requestIds); + uint256 amountClaimed = originARM.claimOriginWithdrawals(requestIds); assertEq(amountClaimed, 10 ether); } } From b0a36f989c9b28bbf0013381d3e1546dbfb73fe3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment?= Date: Thu, 30 Oct 2025 16:19:13 +0100 Subject: [PATCH 30/33] fmt --- test/smoke/OethARMSmokeTest.t.sol | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/test/smoke/OethARMSmokeTest.t.sol b/test/smoke/OethARMSmokeTest.t.sol index 24ea53e9..dcc19b2d 100644 --- a/test/smoke/OethARMSmokeTest.t.sol +++ b/test/smoke/OethARMSmokeTest.t.sol @@ -298,9 +298,8 @@ contract Fork_OriginARM_Smoke_Test is AbstractSmokeTest { (bool success,) = Mainnet.WETH.call(abi.encodeWithSignature("approve(address,uint256)", Mainnet.OETH_VAULT, 10_000 ether)); require(success, "Approve failed"); - (success,) = Mainnet.OETH_VAULT.call( - abi.encodeWithSignature("mint(address,uint256,uint256)", Mainnet.WETH, 10_000 ether, 0) - ); + (success,) = Mainnet.OETH_VAULT + .call(abi.encodeWithSignature("mint(address,uint256,uint256)", Mainnet.WETH, 10_000 ether, 0)); require(success, "Mint failed"); // End cheat section From 5c7c17b7089f26f836f0cb66dccb9d8ca7d3b1be Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment?= Date: Thu, 30 Oct 2025 16:53:27 +0100 Subject: [PATCH 31/33] refactor: rename test contract for clarity and remove unused import --- test/fork/LidoARM/ClaimStETHWithdrawalForWETH.t.sol | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/fork/LidoARM/ClaimStETHWithdrawalForWETH.t.sol b/test/fork/LidoARM/ClaimStETHWithdrawalForWETH.t.sol index 5ea698e8..00dff276 100644 --- a/test/fork/LidoARM/ClaimStETHWithdrawalForWETH.t.sol +++ b/test/fork/LidoARM/ClaimStETHWithdrawalForWETH.t.sol @@ -5,11 +5,11 @@ pragma solidity 0.8.23; import {Fork_Shared_Test_} from "test/fork/shared/Shared.sol"; // Contracts -import {IERC20, IStETHWithdrawal} from "contracts/Interfaces.sol"; +import {IStETHWithdrawal} from "contracts/Interfaces.sol"; import {LidoARM} from "contracts/LidoARM.sol"; import {Mainnet} from "contracts/utils/Addresses.sol"; -contract Fork_Concrete_LidoARM_RequestLidoWithdrawals_Test_ is Fork_Shared_Test_ { +contract Fork_Concrete_LidoARM_ClaimLidoWithdrawals_Test_ is Fork_Shared_Test_ { uint256[] amounts0; uint256[] amounts1; uint256[] amounts2; From 7b2b41a4d2e354ef2ee75eebc80296f6aa7d8459 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment?= Date: Thu, 30 Oct 2025 16:57:20 +0100 Subject: [PATCH 32/33] try something --- foundry.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/foundry.toml b/foundry.toml index 85c172c9..3d6cf01b 100644 --- a/foundry.toml +++ b/foundry.toml @@ -2,7 +2,7 @@ src = "src" out = "out" libs = ["dependencies"] -verbosity = 3 +verbosity = 5 sender = "0x0165C55EF814dEFdd658532A48Bd17B2c8356322" tx_origin = "0x0165C55EF814dEFdd658532A48Bd17B2c8356322" auto_detect_remappings = false From 1aa7a23a0fa20f910821739e5b0a4dd556a70579 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment?= Date: Thu, 30 Oct 2025 17:02:36 +0100 Subject: [PATCH 33/33] try something --- Makefile | 2 +- foundry.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index cbea41af..36e08d01 100644 --- a/Makefile +++ b/Makefile @@ -27,7 +27,7 @@ snapshot: # Tests test-std: - forge test --summary --fail-fast --show-progress + forge test --summary --fail-fast --show-progress -vvv test: @FOUNDRY_NO_MATCH_CONTRACT=Fuzzer $(MAKE) test-std diff --git a/foundry.toml b/foundry.toml index 3d6cf01b..85c172c9 100644 --- a/foundry.toml +++ b/foundry.toml @@ -2,7 +2,7 @@ src = "src" out = "out" libs = ["dependencies"] -verbosity = 5 +verbosity = 3 sender = "0x0165C55EF814dEFdd658532A48Bd17B2c8356322" tx_origin = "0x0165C55EF814dEFdd658532A48Bd17B2c8356322" auto_detect_remappings = false