From 5e8db393ed736e6e3ed47c61825d4ddaaa49e396 Mon Sep 17 00:00:00 2001 From: Nicholas Addison Date: Thu, 26 Feb 2026 16:55:14 +1100 Subject: [PATCH 1/7] Do not allow deposits if the ARM can not meet all its withdrawal obligations --- src/contracts/AbstractARM.sol | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/contracts/AbstractARM.sol b/src/contracts/AbstractARM.sol index 1e4151b2..cc2c0c5c 100644 --- a/src/contracts/AbstractARM.sol +++ b/src/contracts/AbstractARM.sol @@ -550,6 +550,9 @@ abstract contract AbstractARM is OwnableOperable, ERC20Upgradeable { /// @dev Internal logic for depositing liquidity assets in exchange for liquidity provider (LP) shares. function _deposit(uint256 assets, address receiver) internal returns (uint256 shares) { + // Do not allow deposits if the ARM can not meet all its withdrawal obligations. + require(totalAssets() > MIN_TOTAL_SUPPLY || withdrawsQueued == withdrawsClaimed, "ARM: insolvent"); + // Calculate the amount of shares to mint after the performance fees have been accrued // which reduces the available assets, and before new assets are deposited. shares = convertToShares(assets); @@ -717,6 +720,11 @@ abstract contract AbstractARM is OwnableOperable, ERC20Upgradeable { // total assets should only go up from the initial deposit amount that is burnt // but in case of something unforeseen, return at least MIN_TOTAL_SUPPLY. + // An example scenario that will return MIN_TOTAL_SUPPLY is: + // First LP deposits and then requests a redeem of all their ARM shares. + // While waiting to claim their request, the ARM suffer a loss of assets. eg lending market loss. + // When they claim their request, the newAvailableAssets will be zero as + // the ARM assets will be less than the outstanding withdrawal request that was calculated before the loss. if (fees + MIN_TOTAL_SUPPLY >= newAvailableAssets) return MIN_TOTAL_SUPPLY; // Remove the performance fee from the available assets From a485c4e5f5efc97f072b0fe1795a37a01f2fa41a Mon Sep 17 00:00:00 2001 From: Nicholas Addison Date: Thu, 26 Feb 2026 17:36:05 +1100 Subject: [PATCH 2/7] Added solvency tests --- test/fork/LidoARM/Deposit.t.sol | 16 +++++++++++++ test/unit/OriginARM/Deposit.sol | 40 +++++++++++++++++++++++++++++++++ 2 files changed, 56 insertions(+) diff --git a/test/fork/LidoARM/Deposit.t.sol b/test/fork/LidoARM/Deposit.t.sol index 6c5c7e8d..add65a8a 100644 --- a/test/fork/LidoARM/Deposit.t.sol +++ b/test/fork/LidoARM/Deposit.t.sol @@ -97,6 +97,22 @@ contract Fork_Concrete_LidoARM_Deposit_Test_ is Fork_Shared_Test_ { lidoARM.deposit((DEFAULT_AMOUNT / 2) - MIN_TOTAL_SUPPLY + 1); // This should revert! } + function test_RevertWhen_Deposit_Because_Insolvent() + public + setTotalAssetsCap(DEFAULT_AMOUNT + MIN_TOTAL_SUPPLY) + setLiquidityProviderCap(address(this), DEFAULT_AMOUNT) + depositInLidoARM(address(this), DEFAULT_AMOUNT) + requestRedeemFromLidoARM(address(this), DEFAULT_AMOUNT) + { + // Drain all WETH → rawTotal (0) < outstanding (DEFAULT_AMOUNT) → insolvent + deal(address(weth), address(lidoARM), 0); + + assertEq(lidoARM.totalAssets(), MIN_TOTAL_SUPPLY, "totalAssets should be floored at MIN_TOTAL_SUPPLY"); + + vm.expectRevert("ARM: insolvent"); + lidoARM.deposit(DEFAULT_AMOUNT); + } + ////////////////////////////////////////////////////// /// --- PASSING TESTS ////////////////////////////////////////////////////// diff --git a/test/unit/OriginARM/Deposit.sol b/test/unit/OriginARM/Deposit.sol index 1a461eef..b048789e 100644 --- a/test/unit/OriginARM/Deposit.sol +++ b/test/unit/OriginARM/Deposit.sol @@ -228,6 +228,46 @@ contract Unit_Concrete_OriginARM_Deposit_Test_ is Unit_Shared_Test { ); } + /// @notice Deposit reverts when the ARM is insolvent (totalAssets floored to MIN_TOTAL_SUPPLY) + /// and there are outstanding withdrawal requests (withdrawsQueued > withdrawsClaimed). + function test_RevertWhen_Deposit_Because_Insolvent() + public + deposit(alice, DEFAULT_AMOUNT) + requestRedeemAll(alice) + { + // Drain all WETH → rawTotal (0) < outstanding (DEFAULT_AMOUNT) → insolvent + deal(address(weth), address(originARM), 0); + + assertEq(originARM.totalAssets(), MIN_TOTAL_SUPPLY, "totalAssets should be floored at MIN_TOTAL_SUPPLY"); + assertGt(originARM.withdrawsQueued(), originARM.withdrawsClaimed(), "should have outstanding requests"); + + vm.expectRevert("ARM: insolvent"); + vm.prank(alice); + originARM.deposit(DEFAULT_AMOUNT); + } + + /// @notice Deposit is allowed when there are outstanding requests but the ARM remains solvent. + /// Documents the totalAssets() > MIN_TOTAL_SUPPLY branch of the insolvent guard. + /// Alice deposits 2x and redeems 50%, leaving LP equity = MIN_TOTAL_SUPPLY + DEFAULT_AMOUNT. + function test_Deposit_When_SolventWithOutstandingRequests() + public + deposit(alice, DEFAULT_AMOUNT * 2) + requestRedeem(alice, 5e17) // 50% of alice's shares → DEFAULT_AMOUNT queued + { + // rawTotal = MIN_TOTAL_SUPPLY + 2*DEFAULT_AMOUNT, outstanding = DEFAULT_AMOUNT + // totalAssets() = MIN_TOTAL_SUPPLY + DEFAULT_AMOUNT > MIN_TOTAL_SUPPLY → solvent + assertGt(originARM.totalAssets(), MIN_TOTAL_SUPPLY, "should be solvent with LP equity"); + assertGt(originARM.withdrawsQueued(), originARM.withdrawsClaimed(), "should have outstanding requests"); + + deal(address(weth), bob, DEFAULT_AMOUNT); + vm.startPrank(bob); + weth.approve(address(originARM), DEFAULT_AMOUNT); + originARM.deposit(DEFAULT_AMOUNT); + vm.stopPrank(); + + assertGt(originARM.balanceOf(bob), 0, "bob should have received shares"); + } + function test_Deposit_ForSomeoneElse() public { // Expected values uint256 expectedShares = originARM.convertToShares(DEFAULT_AMOUNT); From f21bcb3bc29930318aec4ab34ec4f6d11d9c1b91 Mon Sep 17 00:00:00 2001 From: Nicholas Addison Date: Thu, 26 Feb 2026 17:50:30 +1100 Subject: [PATCH 3/7] Added deploy script to upgrade the ARMs --- .../mainnet/020_UpgradeLidoARMScript.s.sol | 32 ++++++++++++++++ .../mainnet/021_UpgradeEtherFiARMScript.s.sol | 37 +++++++++++++++++++ .../mainnet/022_UpgradeEthenaARMScript.s.sol | 29 +++++++++++++++ .../mainnet/023_UpgradeOETHARMScript.s.sol | 32 ++++++++++++++++ 4 files changed, 130 insertions(+) create mode 100644 script/deploy/mainnet/020_UpgradeLidoARMScript.s.sol create mode 100644 script/deploy/mainnet/021_UpgradeEtherFiARMScript.s.sol create mode 100644 script/deploy/mainnet/022_UpgradeEthenaARMScript.s.sol create mode 100644 script/deploy/mainnet/023_UpgradeOETHARMScript.s.sol diff --git a/script/deploy/mainnet/020_UpgradeLidoARMScript.s.sol b/script/deploy/mainnet/020_UpgradeLidoARMScript.s.sol new file mode 100644 index 00000000..08a4b2fc --- /dev/null +++ b/script/deploy/mainnet/020_UpgradeLidoARMScript.s.sol @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.23; + +// Contract +import {LidoARM} from "contracts/LidoARM.sol"; +import {Mainnet} from "contracts/utils/Addresses.sol"; + +// Deployment +import {AbstractDeployScript} from "script/deploy/helpers/AbstractDeployScript.s.sol"; +import {GovHelper, GovProposal} from "script/deploy/helpers/GovHelper.sol"; + +contract $020_UpgradeLidoARMScript is AbstractDeployScript("020_UpgradeLidoARMScript") { + using GovHelper for GovProposal; + + function _execute() internal override { + // 1. Deploy new LidoARM implementation + uint256 claimDelay = 10 minutes; + uint256 minSharesToRedeem = 1e7; + int256 allocateThreshold = 1e18; + LidoARM lidoARMImpl = + new LidoARM(Mainnet.STETH, Mainnet.WETH, Mainnet.LIDO_WITHDRAWAL, claimDelay, minSharesToRedeem, allocateThreshold); + _recordDeployment("LIDO_ARM_IMPL", address(lidoARMImpl)); + } + + function _buildGovernanceProposal() internal override { + govProposal.setDescription("Upgrade Lido ARM to restrict deposits during insolvency"); + + govProposal.action( + resolver.resolve("LIDO_ARM"), "upgradeTo(address)", abi.encode(resolver.resolve("LIDO_ARM_IMPL")) + ); + } +} diff --git a/script/deploy/mainnet/021_UpgradeEtherFiARMScript.s.sol b/script/deploy/mainnet/021_UpgradeEtherFiARMScript.s.sol new file mode 100644 index 00000000..eb026bea --- /dev/null +++ b/script/deploy/mainnet/021_UpgradeEtherFiARMScript.s.sol @@ -0,0 +1,37 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.23; + +// Contract +import {Proxy} from "contracts/Proxy.sol"; +import {EtherFiARM} from "contracts/EtherFiARM.sol"; +import {Mainnet} from "contracts/utils/Addresses.sol"; + +// Deployment +import {AbstractDeployScript} from "script/deploy/helpers/AbstractDeployScript.s.sol"; + +contract $021_UpgradeEtherFiARMScript is AbstractDeployScript("021_UpgradeEtherFiARMScript") { + EtherFiARM etherFiARMImpl; + + function _execute() internal override { + // 1. Deploy new EtherFiARM implementation + uint256 claimDelay = 10 minutes; + uint256 minSharesToRedeem = 1e7; + int256 allocateThreshold = 1e18; + etherFiARMImpl = new EtherFiARM( + Mainnet.EETH, + Mainnet.WETH, + Mainnet.ETHERFI_WITHDRAWAL, + claimDelay, + minSharesToRedeem, + allocateThreshold, + Mainnet.ETHERFI_WITHDRAWAL_NFT + ); + _recordDeployment("ETHERFI_ARM_IMPL", address(etherFiARMImpl)); + } + + function _fork() internal override { + vm.startPrank(Proxy(payable(resolver.resolve("ETHER_FI_ARM"))).owner()); + Proxy(payable(resolver.resolve("ETHER_FI_ARM"))).upgradeTo(address(etherFiARMImpl)); + vm.stopPrank(); + } +} diff --git a/script/deploy/mainnet/022_UpgradeEthenaARMScript.s.sol b/script/deploy/mainnet/022_UpgradeEthenaARMScript.s.sol new file mode 100644 index 00000000..964f886b --- /dev/null +++ b/script/deploy/mainnet/022_UpgradeEthenaARMScript.s.sol @@ -0,0 +1,29 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.23; + +// Contract +import {Proxy} from "contracts/Proxy.sol"; +import {EthenaARM} from "contracts/EthenaARM.sol"; +import {Mainnet} from "contracts/utils/Addresses.sol"; + +// Deployment +import {AbstractDeployScript} from "script/deploy/helpers/AbstractDeployScript.s.sol"; + +contract $022_UpgradeEthenaARMScript is AbstractDeployScript("022_UpgradeEthenaARMScript") { + EthenaARM armImpl; + + function _execute() internal override { + // 1. Deploy new EthenaARM implementation + uint256 claimDelay = 10 minutes; + uint256 minSharesToRedeem = 1e18; + int256 allocateThreshold = 100e18; + armImpl = new EthenaARM(Mainnet.USDE, Mainnet.SUSDE, claimDelay, minSharesToRedeem, allocateThreshold); + _recordDeployment("ETHENA_ARM_IMPL", address(armImpl)); + } + + function _fork() internal override { + vm.startPrank(Proxy(payable(resolver.resolve("ETHENA_ARM"))).owner()); + Proxy(payable(resolver.resolve("ETHENA_ARM"))).upgradeTo(address(armImpl)); + vm.stopPrank(); + } +} diff --git a/script/deploy/mainnet/023_UpgradeOETHARMScript.s.sol b/script/deploy/mainnet/023_UpgradeOETHARMScript.s.sol new file mode 100644 index 00000000..104fd712 --- /dev/null +++ b/script/deploy/mainnet/023_UpgradeOETHARMScript.s.sol @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.23; + +// Contract +import {OriginARM} from "contracts/OriginARM.sol"; +import {Mainnet} from "contracts/utils/Addresses.sol"; + +// Deployment +import {AbstractDeployScript} from "script/deploy/helpers/AbstractDeployScript.s.sol"; +import {GovHelper, GovProposal} from "script/deploy/helpers/GovHelper.sol"; + +contract $023_UpgradeOETHARMScript is AbstractDeployScript("023_UpgradeOETHARMScript") { + using GovHelper for GovProposal; + + function _execute() internal override { + // 1. Deploy new OriginARM implementation + uint256 claimDelay = 10 minutes; + uint256 minSharesToRedeem = 1e7; + int256 allocateThreshold = 1e18; + OriginARM originARMImpl = + new OriginARM(Mainnet.OETH, Mainnet.WETH, Mainnet.OETH_VAULT, claimDelay, minSharesToRedeem, allocateThreshold); + _recordDeployment("OETH_ARM_IMPL", address(originARMImpl)); + } + + function _buildGovernanceProposal() internal override { + govProposal.setDescription("Upgrade OETH ARM to restrict deposits during insolvency"); + + govProposal.action( + resolver.resolve("OETH_ARM"), "upgradeTo(address)", abi.encode(resolver.resolve("OETH_ARM_IMPL")) + ); + } +} From 6e5212969e1a5784429c8af151f51f0c56a4ebc5 Mon Sep 17 00:00:00 2001 From: Nicholas Addison Date: Thu, 26 Feb 2026 17:56:53 +1100 Subject: [PATCH 4/7] Added test for Immunefi #67167 --- test/unit/OriginARM/Deposit.sol | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/test/unit/OriginARM/Deposit.sol b/test/unit/OriginARM/Deposit.sol index b048789e..88c052e9 100644 --- a/test/unit/OriginARM/Deposit.sol +++ b/test/unit/OriginARM/Deposit.sol @@ -246,6 +246,35 @@ contract Unit_Concrete_OriginARM_Deposit_Test_ is Unit_Shared_Test { originARM.deposit(DEFAULT_AMOUNT); } + /// @notice Attacker deposit is blocked when the ARM is insolvent due to a partial WETH loss. + /// Scenario (Immunefi #67167): + /// 1. Alice deposits and immediately requests a full redeem. + /// 2. While Alice waits to claim, the ARM suffers a 10% loss (e.g., lending market slashing). + /// 3. An attacker tries to deposit to dilute Alice's claim — blocked by the insolvent guard. + /// Without the guard, the attacker would acquire nearly all shares at the floored price and + /// capture Alice's remaining WETH when Alice's claim pays min(request.assets, convertToAssets). + function test_RevertWhen_Deposit_Because_Insolvent_WithSmallLoss() + public + deposit(alice, DEFAULT_AMOUNT) + requestRedeemAll(alice) + { + // Simulate a 10% loss on Alice's deposit (e.g., lending market slashing). + // rawTotal = MIN_TOTAL_SUPPLY + 0.9 * DEFAULT_AMOUNT < outstanding = DEFAULT_AMOUNT → insolvent + uint256 wethAfterLoss = MIN_TOTAL_SUPPLY + DEFAULT_AMOUNT * 9 / 10; + deal(address(weth), address(originARM), wethAfterLoss); + + assertEq(originARM.totalAssets(), MIN_TOTAL_SUPPLY, "totalAssets should be floored at MIN_TOTAL_SUPPLY"); + assertGt(originARM.withdrawsQueued(), originARM.withdrawsClaimed(), "should have outstanding requests"); + + // Attacker (bob) attempts to deposit to dilute Alice's claim — must be blocked + deal(address(weth), bob, DEFAULT_AMOUNT); + vm.startPrank(bob); + weth.approve(address(originARM), DEFAULT_AMOUNT); + vm.expectRevert("ARM: insolvent"); + originARM.deposit(DEFAULT_AMOUNT); + vm.stopPrank(); + } + /// @notice Deposit is allowed when there are outstanding requests but the ARM remains solvent. /// Documents the totalAssets() > MIN_TOTAL_SUPPLY branch of the insolvent guard. /// Alice deposits 2x and redeems 50%, leaving LP equity = MIN_TOTAL_SUPPLY + DEFAULT_AMOUNT. From 70f05595a6b62c7bef75b80c2773a66601d7772c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment?= Date: Thu, 26 Feb 2026 13:07:01 +0100 Subject: [PATCH 5/7] Renumber and rename deposit-restrict deploy scripts to avoid index overlap with 020_UpgradeEthenaARMScript --- ...MScript.s.sol => 021_UpgradeLidoARMDepositScript.s.sol} | 7 ++++--- ...ript.s.sol => 022_UpgradeEtherFiARMDepositScript.s.sol} | 2 +- ...cript.s.sol => 023_UpgradeEthenaARMDepositScript.s.sol} | 2 +- ...MScript.s.sol => 024_UpgradeOETHARMDepositScript.s.sol} | 7 ++++--- 4 files changed, 10 insertions(+), 8 deletions(-) rename script/deploy/mainnet/{020_UpgradeLidoARMScript.s.sol => 021_UpgradeLidoARMDepositScript.s.sol} (78%) rename script/deploy/mainnet/{021_UpgradeEtherFiARMScript.s.sol => 022_UpgradeEtherFiARMDepositScript.s.sol} (91%) rename script/deploy/mainnet/{022_UpgradeEthenaARMScript.s.sol => 023_UpgradeEthenaARMDepositScript.s.sol} (90%) rename script/deploy/mainnet/{023_UpgradeOETHARMScript.s.sol => 024_UpgradeOETHARMDepositScript.s.sol} (78%) diff --git a/script/deploy/mainnet/020_UpgradeLidoARMScript.s.sol b/script/deploy/mainnet/021_UpgradeLidoARMDepositScript.s.sol similarity index 78% rename from script/deploy/mainnet/020_UpgradeLidoARMScript.s.sol rename to script/deploy/mainnet/021_UpgradeLidoARMDepositScript.s.sol index 08a4b2fc..bf4dd4cc 100644 --- a/script/deploy/mainnet/020_UpgradeLidoARMScript.s.sol +++ b/script/deploy/mainnet/021_UpgradeLidoARMDepositScript.s.sol @@ -9,7 +9,7 @@ import {Mainnet} from "contracts/utils/Addresses.sol"; import {AbstractDeployScript} from "script/deploy/helpers/AbstractDeployScript.s.sol"; import {GovHelper, GovProposal} from "script/deploy/helpers/GovHelper.sol"; -contract $020_UpgradeLidoARMScript is AbstractDeployScript("020_UpgradeLidoARMScript") { +contract $021_UpgradeLidoARMDepositScript is AbstractDeployScript("021_UpgradeLidoARMDepositScript") { using GovHelper for GovProposal; function _execute() internal override { @@ -17,8 +17,9 @@ contract $020_UpgradeLidoARMScript is AbstractDeployScript("020_UpgradeLidoARMSc uint256 claimDelay = 10 minutes; uint256 minSharesToRedeem = 1e7; int256 allocateThreshold = 1e18; - LidoARM lidoARMImpl = - new LidoARM(Mainnet.STETH, Mainnet.WETH, Mainnet.LIDO_WITHDRAWAL, claimDelay, minSharesToRedeem, allocateThreshold); + LidoARM lidoARMImpl = new LidoARM( + Mainnet.STETH, Mainnet.WETH, Mainnet.LIDO_WITHDRAWAL, claimDelay, minSharesToRedeem, allocateThreshold + ); _recordDeployment("LIDO_ARM_IMPL", address(lidoARMImpl)); } diff --git a/script/deploy/mainnet/021_UpgradeEtherFiARMScript.s.sol b/script/deploy/mainnet/022_UpgradeEtherFiARMDepositScript.s.sol similarity index 91% rename from script/deploy/mainnet/021_UpgradeEtherFiARMScript.s.sol rename to script/deploy/mainnet/022_UpgradeEtherFiARMDepositScript.s.sol index eb026bea..914e066e 100644 --- a/script/deploy/mainnet/021_UpgradeEtherFiARMScript.s.sol +++ b/script/deploy/mainnet/022_UpgradeEtherFiARMDepositScript.s.sol @@ -9,7 +9,7 @@ import {Mainnet} from "contracts/utils/Addresses.sol"; // Deployment import {AbstractDeployScript} from "script/deploy/helpers/AbstractDeployScript.s.sol"; -contract $021_UpgradeEtherFiARMScript is AbstractDeployScript("021_UpgradeEtherFiARMScript") { +contract $022_UpgradeEtherFiARMDepositScript is AbstractDeployScript("022_UpgradeEtherFiARMDepositScript") { EtherFiARM etherFiARMImpl; function _execute() internal override { diff --git a/script/deploy/mainnet/022_UpgradeEthenaARMScript.s.sol b/script/deploy/mainnet/023_UpgradeEthenaARMDepositScript.s.sol similarity index 90% rename from script/deploy/mainnet/022_UpgradeEthenaARMScript.s.sol rename to script/deploy/mainnet/023_UpgradeEthenaARMDepositScript.s.sol index 964f886b..d43bf338 100644 --- a/script/deploy/mainnet/022_UpgradeEthenaARMScript.s.sol +++ b/script/deploy/mainnet/023_UpgradeEthenaARMDepositScript.s.sol @@ -9,7 +9,7 @@ import {Mainnet} from "contracts/utils/Addresses.sol"; // Deployment import {AbstractDeployScript} from "script/deploy/helpers/AbstractDeployScript.s.sol"; -contract $022_UpgradeEthenaARMScript is AbstractDeployScript("022_UpgradeEthenaARMScript") { +contract $023_UpgradeEthenaARMDepositScript is AbstractDeployScript("023_UpgradeEthenaARMDepositScript") { EthenaARM armImpl; function _execute() internal override { diff --git a/script/deploy/mainnet/023_UpgradeOETHARMScript.s.sol b/script/deploy/mainnet/024_UpgradeOETHARMDepositScript.s.sol similarity index 78% rename from script/deploy/mainnet/023_UpgradeOETHARMScript.s.sol rename to script/deploy/mainnet/024_UpgradeOETHARMDepositScript.s.sol index 104fd712..117cfe9e 100644 --- a/script/deploy/mainnet/023_UpgradeOETHARMScript.s.sol +++ b/script/deploy/mainnet/024_UpgradeOETHARMDepositScript.s.sol @@ -9,7 +9,7 @@ import {Mainnet} from "contracts/utils/Addresses.sol"; import {AbstractDeployScript} from "script/deploy/helpers/AbstractDeployScript.s.sol"; import {GovHelper, GovProposal} from "script/deploy/helpers/GovHelper.sol"; -contract $023_UpgradeOETHARMScript is AbstractDeployScript("023_UpgradeOETHARMScript") { +contract $024_UpgradeOETHARMDepositScript is AbstractDeployScript("024_UpgradeOETHARMDepositScript") { using GovHelper for GovProposal; function _execute() internal override { @@ -17,8 +17,9 @@ contract $023_UpgradeOETHARMScript is AbstractDeployScript("023_UpgradeOETHARMSc uint256 claimDelay = 10 minutes; uint256 minSharesToRedeem = 1e7; int256 allocateThreshold = 1e18; - OriginARM originARMImpl = - new OriginARM(Mainnet.OETH, Mainnet.WETH, Mainnet.OETH_VAULT, claimDelay, minSharesToRedeem, allocateThreshold); + OriginARM originARMImpl = new OriginARM( + Mainnet.OETH, Mainnet.WETH, Mainnet.OETH_VAULT, claimDelay, minSharesToRedeem, allocateThreshold + ); _recordDeployment("OETH_ARM_IMPL", address(originARMImpl)); } From 1de343e91952841be53bea2fbb40bd282b559cb0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment?= Date: Thu, 26 Feb 2026 13:09:07 +0100 Subject: [PATCH 6/7] fmt --- test/unit/OriginARM/Deposit.sol | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/test/unit/OriginARM/Deposit.sol b/test/unit/OriginARM/Deposit.sol index 88c052e9..54f28bc4 100644 --- a/test/unit/OriginARM/Deposit.sol +++ b/test/unit/OriginARM/Deposit.sol @@ -230,11 +230,7 @@ contract Unit_Concrete_OriginARM_Deposit_Test_ is Unit_Shared_Test { /// @notice Deposit reverts when the ARM is insolvent (totalAssets floored to MIN_TOTAL_SUPPLY) /// and there are outstanding withdrawal requests (withdrawsQueued > withdrawsClaimed). - function test_RevertWhen_Deposit_Because_Insolvent() - public - deposit(alice, DEFAULT_AMOUNT) - requestRedeemAll(alice) - { + function test_RevertWhen_Deposit_Because_Insolvent() public deposit(alice, DEFAULT_AMOUNT) requestRedeemAll(alice) { // Drain all WETH → rawTotal (0) < outstanding (DEFAULT_AMOUNT) → insolvent deal(address(weth), address(originARM), 0); @@ -282,6 +278,7 @@ contract Unit_Concrete_OriginARM_Deposit_Test_ is Unit_Shared_Test { public deposit(alice, DEFAULT_AMOUNT * 2) requestRedeem(alice, 5e17) // 50% of alice's shares → DEFAULT_AMOUNT queued + { // rawTotal = MIN_TOTAL_SUPPLY + 2*DEFAULT_AMOUNT, outstanding = DEFAULT_AMOUNT // totalAssets() = MIN_TOTAL_SUPPLY + DEFAULT_AMOUNT > MIN_TOTAL_SUPPLY → solvent From 2a7cb4111faa70aee71b2bccf59e8e9354e8b3ba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment?= Date: Thu, 26 Feb 2026 14:19:49 +0100 Subject: [PATCH 7/7] Add assumptions for asset thresholds in target deposit functions --- test/invariants/EthenaARM/TargetFunctions.sol | 1 + test/invariants/OriginARM/TargetFunction.sol | 2 ++ 2 files changed, 3 insertions(+) diff --git a/test/invariants/EthenaARM/TargetFunctions.sol b/test/invariants/EthenaARM/TargetFunctions.sol index bc2187b6..a293b4e8 100644 --- a/test/invariants/EthenaARM/TargetFunctions.sol +++ b/test/invariants/EthenaARM/TargetFunctions.sol @@ -67,6 +67,7 @@ abstract contract TargetFunctions is Setup, StdUtils { // ║ ✦✦✦ ETHENA ARM ✦✦✦ ║ // ╚══════════════════════════════════════════════════════════════════════════════╝ function targetARMDeposit(uint88 amount, uint256 randomAddressIndex) external ensureExchangeRateIncrease { + vm.assume(arm.totalAssets() > 1e12 || arm.withdrawsQueued() == arm.withdrawsClaimed()); // Select a random user from makers address user = makers[randomAddressIndex % MAKERS_COUNT]; diff --git a/test/invariants/OriginARM/TargetFunction.sol b/test/invariants/OriginARM/TargetFunction.sol index 36eb66a9..92ecd62a 100644 --- a/test/invariants/OriginARM/TargetFunction.sol +++ b/test/invariants/OriginARM/TargetFunction.sol @@ -58,6 +58,8 @@ abstract contract TargetFunction is Properties { using MathComparisons for uint256; function handler_deposit(uint8 seed, uint88 amount) public { + vm.assume(originARM.totalAssets() > 1e12 || originARM.withdrawsQueued() == originARM.withdrawsClaimed()); + // Get a random user from the list of lps address user = getRandomLPs(seed);