New ARM features before Ethena audit#208
Conversation
…hdraw from the lending market if not enough liquidity in the ARM
…the swap functions
* Using swap multiplier to calculate accrued fee * Removed Maths lib to get contract size down Removed lastAvailableAssets() function * Improved gas usage of _accrueSwapFee by removing if swapFeeMultiplier is zero * Fixed gap after storage variables were compacted * Removed outToken from _accrueSwapFee * Simplified _updateSwapFeeMultiplier * More gas savings Allow price liquidity cap to be unlimited * Moved state changes before token transfers * Update swap gas compare test
* WIP multi base assets * - Move reusable multi-base asset config and swap accounting into AbstractARM - Replace protocol-specific ARM redemption logic with generic asset adapters - removed legacy max-cap sentinel handling - Upgrade all ARMs to multi base assets - Added wstETH to the Lido ARM - Added weETH to EtherFi ARM * Updated AI context * Added wOETH to OETH ARM * Updated ARM contract dependencies * Updated Hardhat tasks * Proxied the adapters * Lido ARM upgrade checks * Added Ethena upgrade guard * Updated migration error * More cleanup of legacy immutables * fmt * Update EtherFi ABI * Update claimEthenaWithdrawals Hardhat task * Fixed fork tests * Fixed smoke tests * Fix invariant tests * Updated contract diagrams * Renamed the ARM adapter-facing methods to requestBaseAssetRedeem and claimBaseAssetRedeem * Used modifiers on adapters * Pro-rata losses to redeemers and remaining LPs (#223) * Pro-rata losses to redeemers and remaining LPs * Calculate swap fee against cross price rather than par value (#224)
- Gate migrateLegacyWithdrawQueue with reinitializer(2) - Run protocol-specific legacy queue checks through internal hooks - Use migration selector in ARM upgrade scripts - Remove obsolete external Ethena and Lido legacy check helpers
…ct against reentry
Refactored swap helpers to reduce contract size
| unchecked { | ||
| config.buyLiquidityRemaining = uint128(remaining - amountOut); | ||
| } | ||
| _ensureLiquidityAvailableForSwap(amountOut); |
There was a problem hiding this comment.
There is a small asymmetry here:
If side:
- Check against buyLiquidity
- Ensure there is enough tokens
Else side:
- Ensure there is enough tokens
- Check against buyLiquidity
* Custom errors for Operator and Admin functions * Fixed smoke tests * More smoke test fixes * moved error location
* Added pause and unpause * Fixed compile and tests * Using ContractPaused Error
|
|
||
| // 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. | ||
| require(totalAssets() > MIN_TOTAL_SUPPLY || reservedWithdrawLiquidity == 0, "ARM: insolvent"); |
There was a problem hiding this comment.
I'm wondering if we are really protecting from depositing when ARM is insolvent.
The attacker can still give 0.01 ETH to ARM (donation), which unblock this require, then deposit and get way more shares that expected.
I agree that this is still better than returning 0 though. But now that we have the pausing mechanism, I'm wondering if this is still relevant. WDYT?
| uint256 assetsAtClaim = request.shares > 0 ? convertToAssets(request.shares) : request.assets; | ||
| // Use the minimum of the asset value of the redeemed shares at request or claim. | ||
| assets = request.assets < assetsAtClaim ? request.assets : assetsAtClaim; |
There was a problem hiding this comment.
I'm not sure, but I think this line isn't necessary.
If request.shares == 0 then convertToAssets(request.shares) will return 0 too. And to have request.shares == 0 this implies that user call requestRedeem(0). Calling requestRedeem(0) force request.asset == 0 too.
So we can maybe have:
- uint256 assetsAtClaim = request.shares > 0 ? convertToAssets(request.shares) : request.assets;
- assets = request.assets < assetsAtClaim ? request.assets : assetsAtClaim;
+ assets = min(request.assets, convertToAssets(request.shares));I agree with Shahul that we should not allow requestRedeem(0) though.
* Added Natspec to Adapter contracts Moved modifiers to before functions * Fix formatting
* Support claiming legacy redeem requests * deposit, requestRedeem and claimRedeem now using custom errors * Change why legacy requests are detected * Format * Fix invariant test
…#235) * Added _checkNoLegacyWithdrawQueue to OriginARM * Use custom error in _checkNoLegacyWithdrawQueue * Added comment with 4-byte selector to each custom error * Format * Added missing comment of 4-byte selector to errors * Ethena and Origin ARMs to use LegacyWithdrawalsPending instead of their own custom error
…counter and derive each unstaker index by request position modulo the unstaker count (#245)
Summary
Adds the next ARM upgrade set ahead of the Ethena audit. This PR now includes active-market liquidity sourcing during swaps, discounted base-asset swap fees, per-price liquidity limits, multi-base-asset support, adapter-based redemption flows, updated loss/fee accounting, pause controls, reentrancy protection, and safer upgrade/migration handling.
Merged Changes
Key Changes
Withdraw From Active Market During Swaps
Swaps that need more liquidity than is currently held in the ARM can now pull the shortfall from the active ERC-4626 lending market.
This lets the ARM keep more liquidity deployed while still supporting swaps, provided the active market has enough withdrawable liquidity.
Discount-Based Swap Fees
Replaces the old performance fee on asset growth with fees accrued when the ARM buys base assets at a discount.
Fees are calculated using a swap fee multiplier and measured against the configured cross price, aligning fee accrual with the ARM's valuation price for each base asset.
Per-Price Liquidity Limits
Buy and sell liquidity limits are tracked per configured price and emitted in traderate updates.
Operators can cap how much liquidity can be consumed at the current buy/sell prices before prices are refreshed. Swap-side liquidity checks now cover both buy and sell paths.
Multi-Base-Asset ARM
Moves reusable multi-base logic into
AbstractARM, allowing a single ARM to support multiple base assets against one liquidity asset.Examples:
stETH,wstETHeETH,weETHsUSDeOETH,wOETHEach base asset has its own config:
Asset Adapter Redemption Flow
Protocol-specific redemption logic has moved out of the ARM and into per-asset adapters.
Adapters now own details such as:
The ARM tracks generic pending redeem accounting in liquidity-asset terms. Lido and EtherFi-style claim adapters also sweep adapter-held ETH/WETH when claiming, so donated balances do not remain stranded.
LP Loss Accounting
Updates LP redeem accounting so losses are shared pro-rata between redeemers and remaining LPs instead of allowing one side to absorb the full impact.
If assets per share falls after a redeem request is created, the claim uses the lower claim-time value.
Legacy Queue Migration And Claim Support
Adds a one-time, atomic legacy withdraw queue migration guarded by
reinitializer(2).Upgrade scripts call the migration selector directly, and protocol-specific legacy checks run through internal hooks instead of external helper functions.
Legacy LP redeem requests remain claimable after the upgrade. Additional safety checks prevent upgrades while unsupported legacy protocol withdrawal queues are still pending.
Pause / Unpause
Adds a shared ARM pause circuit breaker.
Reentrancy Protection
Adds
ReentrancyGuardUpgradeableprotection around liquidity-moving entrypoints, including swap entrypoints, deposits, LP redeem request/claim, and fee collection.This prevents nested callbacks from reusing stale pre-payout balances during liquidity-moving flows.
Ethena Adapter Storage And Queue Cleanup
Restores deprecated Ethena ARM storage slots for upgrade layout compatibility.
The Ethena adapter now tracks queued unstaker requests using a total request counter plus FIFO pending index, deriving each unstaker index from request position modulo the unstaker count instead of storing a pending index array.
Custom Errors and Contract Size Cleanup
Replaces admin/operator-only revert strings with custom errors across ownership, operator, pricing, fee, market, migration, and cap-manager paths.
Custom errors include selector documentation where required.
User-facing revert strings remain largely unchanged for swap, deposit, LP redeem, and adapter flows where preserving existing behavior is useful.
Additional refactors reduce ARM runtime size and keep the ARM implementations below the EIP-170 contract size limit.
Deployment, Task, and Test Updates
Includes updated deploy/upgrade scripts, ABIs, Hardhat tasks, diagrams, fork tests, smoke tests, invariant tests, and upgrade guard tests for the new architecture.
Testing
Coverage includes:
Gas comparison
Measured on mainnet fork block
24,846,066.Relative to current deployed: