From a7e1b4e95840b46caee060bca15a2d81fe9c6d93 Mon Sep 17 00:00:00 2001 From: Domen Grabec Date: Fri, 6 Mar 2026 19:09:46 +0100 Subject: [PATCH 1/5] prettier --- .../contracts/strategies/Generalized4626Strategy.sol | 6 ++++-- contracts/contracts/strategies/MorphoV2Strategy.sol | 1 - contracts/contracts/strategies/MorphoV2VaultUtils.sol | 10 +++++----- .../strategies/crosschain/CrossChainRemoteStrategy.sol | 6 +----- .../mainnet/179_upgrade_ousd_morpho_v2_strategy.js | 2 +- .../test/strategies/crosschain/cross-chain-strategy.js | 7 +++---- contracts/utils/morpho.js | 9 ++++----- 7 files changed, 18 insertions(+), 23 deletions(-) diff --git a/contracts/contracts/strategies/Generalized4626Strategy.sol b/contracts/contracts/strategies/Generalized4626Strategy.sol index b4793db7de..d451ff672e 100644 --- a/contracts/contracts/strategies/Generalized4626Strategy.sol +++ b/contracts/contracts/strategies/Generalized4626Strategy.sol @@ -5,7 +5,7 @@ pragma solidity ^0.8.0; * @title Generalized 4626 Strategy * @notice Investment strategy for ERC-4626 Tokenized Vaults * @dev This strategy should not be used for the Morpho V2 Vaults as those are not - * completley ERC-4626 compliant - they don't implement the maxWithdraw() and + * completley ERC-4626 compliant - they don't implement the maxWithdraw() and * maxRedeem() functions and rather return 0 when any of them is called. * @author Origin Protocol Inc */ @@ -145,7 +145,9 @@ contract Generalized4626Strategy is InitializableAbstractStrategy { nonReentrant { // @dev Don't use for Morpho V2 Vaults as below line will return 0 - uint256 sharesToRedeem = IERC4626(platformAddress).maxRedeem(address(this)); + uint256 sharesToRedeem = IERC4626(platformAddress).maxRedeem( + address(this) + ); uint256 assetAmount = 0; if (sharesToRedeem > 0) { diff --git a/contracts/contracts/strategies/MorphoV2Strategy.sol b/contracts/contracts/strategies/MorphoV2Strategy.sol index a963398e53..a7291d9145 100644 --- a/contracts/contracts/strategies/MorphoV2Strategy.sol +++ b/contracts/contracts/strategies/MorphoV2Strategy.sol @@ -12,7 +12,6 @@ import { IVaultV2 } from "../interfaces/morpho/IVaultV2.sol"; import { Math } from "@openzeppelin/contracts/utils/math/Math.sol"; contract MorphoV2Strategy is Generalized4626Strategy { - /** * @param _baseConfig Base strategy config with Morpho V2 Vault and * vaultAddress (OToken Vault contract), eg VaultProxy or OETHVaultProxy diff --git a/contracts/contracts/strategies/MorphoV2VaultUtils.sol b/contracts/contracts/strategies/MorphoV2VaultUtils.sol index 02fcad5bd7..98ad5ce94f 100644 --- a/contracts/contracts/strategies/MorphoV2VaultUtils.sol +++ b/contracts/contracts/strategies/MorphoV2VaultUtils.sol @@ -16,10 +16,11 @@ library MorphoV2VaultUtils { * 2) additional liquidity from the active adapter if it resolves to a Morpho V1 vault * and, when provided, matches the expected adapter */ - function maxWithdrawableAssets( - address platformAddress, - address assetToken - ) internal view returns (uint256 availableAssetLiquidity) { + function maxWithdrawableAssets(address platformAddress, address assetToken) + internal + view + returns (uint256 availableAssetLiquidity) + { availableAssetLiquidity = IERC20(assetToken).balanceOf(platformAddress); address liquidityAdapter = IVaultV2(platformAddress).liquidityAdapter(); @@ -34,5 +35,4 @@ library MorphoV2VaultUtils { revert IncompatibleAdapter(liquidityAdapter); } } - } diff --git a/contracts/contracts/strategies/crosschain/CrossChainRemoteStrategy.sol b/contracts/contracts/strategies/crosschain/CrossChainRemoteStrategy.sol index aa1c10e224..5e1a0c667e 100644 --- a/contracts/contracts/strategies/crosschain/CrossChainRemoteStrategy.sol +++ b/contracts/contracts/strategies/crosschain/CrossChainRemoteStrategy.sol @@ -153,11 +153,7 @@ contract CrossChainRemoteStrategy is ); if (amountToWithdraw > 0) { - _withdraw( - address(this), - usdcToken, - amountToWithdraw - ); + _withdraw(address(this), usdcToken, amountToWithdraw); } } diff --git a/contracts/deploy/mainnet/179_upgrade_ousd_morpho_v2_strategy.js b/contracts/deploy/mainnet/179_upgrade_ousd_morpho_v2_strategy.js index 810103568c..f0245f60dd 100644 --- a/contracts/deploy/mainnet/179_upgrade_ousd_morpho_v2_strategy.js +++ b/contracts/deploy/mainnet/179_upgrade_ousd_morpho_v2_strategy.js @@ -19,7 +19,7 @@ module.exports = deploymentWithGovernanceProposal( "MorphoV2Strategy", [ [addresses.mainnet.MorphoOUSDv2Vault, cVaultProxy.address], - addresses.mainnet.USDC + addresses.mainnet.USDC, ] ); diff --git a/contracts/test/strategies/crosschain/cross-chain-strategy.js b/contracts/test/strategies/crosschain/cross-chain-strategy.js index 4702d408bf..7f61f5d457 100644 --- a/contracts/test/strategies/crosschain/cross-chain-strategy.js +++ b/contracts/test/strategies/crosschain/cross-chain-strategy.js @@ -110,10 +110,9 @@ describe("ForkTest: CrossChainRemoteStrategy", function () { .connect(governor) .setLiquidityAdapter(morphoVault.address); - await expect(crossChainRemoteStrategy.connect(governor).withdrawAll()) - .to.be.revertedWithCustomError( - "IncompatibleAdapter(address)" - ); + await expect( + crossChainRemoteStrategy.connect(governor).withdrawAll() + ).to.be.revertedWithCustomError("IncompatibleAdapter(address)"); }); // Checks the diff in the total expected value in the vault diff --git a/contracts/utils/morpho.js b/contracts/utils/morpho.js index 2abb288a65..44b9a2db97 100644 --- a/contracts/utils/morpho.js +++ b/contracts/utils/morpho.js @@ -33,12 +33,11 @@ async function morphoWithdrawShortfall() { addresses.mainnet.USDC ); - const usdc = await ethers.getContractAt( - erc20Abi, - addresses.mainnet.USDC + const usdc = await ethers.getContractAt(erc20Abi, addresses.mainnet.USDC); + const vaultUSDCBalance = await usdc.balanceOf( + addresses.mainnet.MorphoOUSDv2Vault ); - const vaultUSDCBalance = await usdc.balanceOf(addresses.mainnet.MorphoOUSDv2Vault); - + maxWithdrawal = maxWithdrawal.add(vaultUSDCBalance); log( `Morpho OUSD v2 Strategy USDC balance: ${formatUnits( From 91ddc41724f4a244f8b9aa51d8362df5fd076c53 Mon Sep 17 00:00:00 2001 From: Domen Grabec Date: Fri, 6 Mar 2026 20:00:11 +0100 Subject: [PATCH 2/5] slither and prettier --- .../automation/CurvePoolBoosterBribesModule.sol | 16 +++++++++------- .../poolBooster/PoolBoosterFactoryMerkl.sol | 5 ++++- .../poolBooster/curve/CurvePoolBooster.sol | 2 +- 3 files changed, 14 insertions(+), 9 deletions(-) diff --git a/contracts/contracts/automation/CurvePoolBoosterBribesModule.sol b/contracts/contracts/automation/CurvePoolBoosterBribesModule.sol index ced22d9e2a..69eb401db5 100644 --- a/contracts/contracts/automation/CurvePoolBoosterBribesModule.sol +++ b/contracts/contracts/automation/CurvePoolBoosterBribesModule.sol @@ -60,6 +60,7 @@ contract CurvePoolBoosterBribesModule is AbstractSafeModule { ) AbstractSafeModule(_safeContract) { _grantRole(OPERATOR_ROLE, _operator); for (uint256 i = 0; i < _poolBoosters.length; i++) { + // slither-disable-next-line cache-array-length _addPoolBoosterAddress(_poolBoosters[i]); } _setBridgeFee(_bridgeFee); @@ -112,12 +113,12 @@ contract CurvePoolBoosterBribesModule is AbstractSafeModule { /// @dev Calls `manageCampaign` on each pool booster via the Safe. The Safe must hold /// enough ETH to cover `bridgeFee * poolBoosters.length`. function manageBribes() external onlyOperator { - uint256[] memory totalRewardAmounts = new uint256[]( - poolBoosters.length - ); - uint8[] memory extraDuration = new uint8[](poolBoosters.length); - uint256[] memory rewardsPerVote = new uint256[](poolBoosters.length); - for (uint256 i = 0; i < poolBoosters.length; i++) { + uint256 poolBoosterCount = poolBoosters.length; + + uint256[] memory totalRewardAmounts = new uint256[](poolBoosterCount); + uint8[] memory extraDuration = new uint8[](poolBoosterCount); + uint256[] memory rewardsPerVote = new uint256[](poolBoosterCount); + for (uint256 i = 0; i < poolBoosterCount; i++) { totalRewardAmounts[i] = type(uint256).max; // use all available rewards extraDuration[i] = 1; // extend by 1 period (week) rewardsPerVote[i] = 0; // no update to maxRewardPerVote @@ -166,8 +167,9 @@ contract CurvePoolBoosterBribesModule is AbstractSafeModule { /// @dev Reverts if the address is already in the poolBoosters array /// @param _pool Address to append to the poolBoosters array function _addPoolBoosterAddress(address _pool) internal { + uint256 poolBoosterCount = poolBoosters.length; require(_pool != address(0), "Zero address"); - for (uint256 j = 0; j < poolBoosters.length; j++) { + for (uint256 j = 0; j < poolBoosterCount; j++) { require(poolBoosters[j] != _pool, "Pool already added"); } poolBoosters.push(_pool); diff --git a/contracts/contracts/poolBooster/PoolBoosterFactoryMerkl.sol b/contracts/contracts/poolBooster/PoolBoosterFactoryMerkl.sol index a9c080470a..2f37c50193 100644 --- a/contracts/contracts/poolBooster/PoolBoosterFactoryMerkl.sol +++ b/contracts/contracts/poolBooster/PoolBoosterFactoryMerkl.sol @@ -21,7 +21,7 @@ contract PoolBoosterFactoryMerkl is AbstractPoolBoosterFactory { //////////////////////////////////////////////////// /// @notice Address of the UpgradeableBeacon - address public beacon; + address public immutable beacon; //////////////////////////////////////////////////// /// --- CONSTRUCTOR @@ -45,6 +45,7 @@ contract PoolBoosterFactoryMerkl is AbstractPoolBoosterFactory { /// --- CORE LOGIC //////////////////////////////////////////////////// + // slither-disable-start reentrancy-no-eth /// @notice Create a Pool Booster for Merkl using a BeaconProxy /// @param _ammPoolAddress Address of the AMM pool /// @param _initData Encoded call data for initializing the proxy @@ -75,6 +76,8 @@ contract PoolBoosterFactoryMerkl is AbstractPoolBoosterFactory { ); } + // slither-disable-end reentrancy-no-eth + /// @notice Calls bribe() on all pool boosters, skipping those in the exclusion list /// @param _exclusionList A list of pool booster addresses to skip function bribeAll(address[] memory _exclusionList) diff --git a/contracts/contracts/poolBooster/curve/CurvePoolBooster.sol b/contracts/contracts/poolBooster/curve/CurvePoolBooster.sol index e5117cd963..65ff60f53f 100644 --- a/contracts/contracts/poolBooster/curve/CurvePoolBooster.sol +++ b/contracts/contracts/poolBooster/curve/CurvePoolBooster.sol @@ -171,7 +171,7 @@ contract CurvePoolBooster is Initializable, Strategizable { ) external payable nonReentrant onlyGovernorOrStrategist { require(campaignId != 0, "Campaign not created"); - uint256 rewardAmount; + uint256 rewardAmount = 0; if (totalRewardAmount != 0) { uint256 amount = min( From 29a25d7021312230cee3e24b9f5c21e5ecc5ac0a Mon Sep 17 00:00:00 2001 From: Domen Grabec Date: Fri, 6 Mar 2026 20:03:27 +0100 Subject: [PATCH 3/5] minor change --- contracts/contracts/poolBooster/PoolBoosterFactoryMerkl.sol | 2 ++ 1 file changed, 2 insertions(+) diff --git a/contracts/contracts/poolBooster/PoolBoosterFactoryMerkl.sol b/contracts/contracts/poolBooster/PoolBoosterFactoryMerkl.sol index 2f37c50193..1af32dbee7 100644 --- a/contracts/contracts/poolBooster/PoolBoosterFactoryMerkl.sol +++ b/contracts/contracts/poolBooster/PoolBoosterFactoryMerkl.sol @@ -20,6 +20,8 @@ contract PoolBoosterFactoryMerkl is AbstractPoolBoosterFactory { /// --- STORAGE //////////////////////////////////////////////////// + address private deprecated_beacon; + /// @notice Address of the UpgradeableBeacon address public immutable beacon; From b894150eb60c358f8b06f1d3b78f1bf30422ff98 Mon Sep 17 00:00:00 2001 From: Domen Grabec Date: Mon, 9 Mar 2026 09:52:41 +0100 Subject: [PATCH 4/5] add comments and slither disables --- .../contracts/automation/CurvePoolBoosterBribesModule.sol | 2 +- contracts/contracts/harvest/AbstractHarvester.sol | 5 +++++ contracts/contracts/poolBooster/PoolBoosterFactoryMerkl.sol | 4 +++- contracts/contracts/zapper/AbstractOTokenZapper.sol | 4 ++++ contracts/contracts/zapper/OSonicZapper.sol | 4 ++++ 5 files changed, 17 insertions(+), 2 deletions(-) diff --git a/contracts/contracts/automation/CurvePoolBoosterBribesModule.sol b/contracts/contracts/automation/CurvePoolBoosterBribesModule.sol index 69eb401db5..401e84ba86 100644 --- a/contracts/contracts/automation/CurvePoolBoosterBribesModule.sol +++ b/contracts/contracts/automation/CurvePoolBoosterBribesModule.sol @@ -59,8 +59,8 @@ contract CurvePoolBoosterBribesModule is AbstractSafeModule { uint256 _additionalGasLimit ) AbstractSafeModule(_safeContract) { _grantRole(OPERATOR_ROLE, _operator); + // slither-disable-next-line cache-array-length for (uint256 i = 0; i < _poolBoosters.length; i++) { - // slither-disable-next-line cache-array-length _addPoolBoosterAddress(_poolBoosters[i]); } _setBridgeFee(_bridgeFee); diff --git a/contracts/contracts/harvest/AbstractHarvester.sol b/contracts/contracts/harvest/AbstractHarvester.sol index 287bbc03e2..1c55e8e41f 100644 --- a/contracts/contracts/harvest/AbstractHarvester.sol +++ b/contracts/contracts/harvest/AbstractHarvester.sol @@ -472,6 +472,9 @@ abstract contract AbstractHarvester is Governable { * @param _rewardTo Address where to send the share of harvest rewards to * @param _priceProvider Oracle to get prices of the swap token */ + // This function is called by the harvestAndSwap function, which is only called by + // functions that have the nonReentrant modifier. Therefore, this function is also non-reentrant. + // slither-disable-start reentrancy-eth,reentrancy-no-eth,reentrancy-benign,reentrancy-events,reentrancy-unlimited-gas,reentrancy-balance function _swap( address _swapToken, address _rewardTo, @@ -575,6 +578,8 @@ abstract contract AbstractHarvester is Governable { ); } + // slither-disable-end reentrancy-eth,reentrancy-no-eth,reentrancy-benign,reentrancy-events,reentrancy-unlimited-gas,reentrancy-balance + function _doSwap( SwapPlatform swapPlatform, address routerAddress, diff --git a/contracts/contracts/poolBooster/PoolBoosterFactoryMerkl.sol b/contracts/contracts/poolBooster/PoolBoosterFactoryMerkl.sol index 1af32dbee7..8fd353ed1c 100644 --- a/contracts/contracts/poolBooster/PoolBoosterFactoryMerkl.sol +++ b/contracts/contracts/poolBooster/PoolBoosterFactoryMerkl.sol @@ -20,8 +20,10 @@ contract PoolBoosterFactoryMerkl is AbstractPoolBoosterFactory { /// --- STORAGE //////////////////////////////////////////////////// + // Keep this mutable storage slot to preserve layout compatibility. + // slither-disable-next-line constable-states address private deprecated_beacon; - + /// @notice Address of the UpgradeableBeacon address public immutable beacon; diff --git a/contracts/contracts/zapper/AbstractOTokenZapper.sol b/contracts/contracts/zapper/AbstractOTokenZapper.sol index e095fa1b69..9098b3e4f4 100644 --- a/contracts/contracts/zapper/AbstractOTokenZapper.sol +++ b/contracts/contracts/zapper/AbstractOTokenZapper.sol @@ -68,6 +68,7 @@ abstract contract AbstractOTokenZapper { payable returns (uint256) { + // slither-disable-start reentrancy-balance uint256 balance = address(this).balance; emit Zap(msg.sender, ETH_MARKER, balance); @@ -83,6 +84,7 @@ abstract contract AbstractOTokenZapper { require(mintedWOToken >= minReceived, "Zapper: not enough minted"); + // slither-disable-end reentrancy-balance return mintedWOToken; } @@ -96,6 +98,7 @@ abstract contract AbstractOTokenZapper { uint256 wethAmount, uint256 minReceived ) external returns (uint256) { + // slither-disable-start reentrancy-balance // slither-disable-next-line unchecked-transfer unused-return weth.transferFrom(msg.sender, address(this), wethAmount); @@ -109,6 +112,7 @@ abstract contract AbstractOTokenZapper { require(mintedWOToken >= minReceived, "Zapper: not enough minted"); + // slither-disable-end reentrancy-balance return mintedWOToken; } diff --git a/contracts/contracts/zapper/OSonicZapper.sol b/contracts/contracts/zapper/OSonicZapper.sol index 8465e751da..bda4e83184 100644 --- a/contracts/contracts/zapper/OSonicZapper.sol +++ b/contracts/contracts/zapper/OSonicZapper.sol @@ -70,6 +70,7 @@ contract OSonicZapper { payable returns (uint256) { + // slither-disable-start reentrancy-balance uint256 balance = address(this).balance; emit Zap(msg.sender, ETH_MARKER, balance); @@ -85,6 +86,7 @@ contract OSonicZapper { require(mintedWOS >= minReceived, "Zapper: not enough minted"); + // slither-disable-end reentrancy-balance return mintedWOS; } @@ -98,6 +100,7 @@ contract OSonicZapper { external returns (uint256) { + // slither-disable-start reentrancy-balance // slither-disable-next-line unchecked-transfer unused-return wS.transferFrom(msg.sender, address(this), wSAmount); @@ -111,6 +114,7 @@ contract OSonicZapper { require(mintedWOS >= minReceived, "Zapper: not enough minted"); + // slither-disable-end reentrancy-balance return mintedWOS; } From ab5574b96a0db112da8658e33c26cabf850ce0f2 Mon Sep 17 00:00:00 2001 From: Domen Grabec Date: Mon, 9 Mar 2026 09:59:09 +0100 Subject: [PATCH 5/5] lint --- contracts/contracts/harvest/AbstractHarvester.sol | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/contracts/contracts/harvest/AbstractHarvester.sol b/contracts/contracts/harvest/AbstractHarvester.sol index 1c55e8e41f..df9d77d65b 100644 --- a/contracts/contracts/harvest/AbstractHarvester.sol +++ b/contracts/contracts/harvest/AbstractHarvester.sol @@ -474,7 +474,8 @@ abstract contract AbstractHarvester is Governable { */ // This function is called by the harvestAndSwap function, which is only called by // functions that have the nonReentrant modifier. Therefore, this function is also non-reentrant. - // slither-disable-start reentrancy-eth,reentrancy-no-eth,reentrancy-benign,reentrancy-events,reentrancy-unlimited-gas,reentrancy-balance + // slither-disable-start reentrancy-eth,reentrancy-no-eth,reentrancy-benign + // slither-disable-start reentrancy-events,reentrancy-unlimited-gas,reentrancy-balance function _swap( address _swapToken, address _rewardTo, @@ -578,7 +579,8 @@ abstract contract AbstractHarvester is Governable { ); } - // slither-disable-end reentrancy-eth,reentrancy-no-eth,reentrancy-benign,reentrancy-events,reentrancy-unlimited-gas,reentrancy-balance + // slither-disable-end reentrancy-events,reentrancy-unlimited-gas,reentrancy-balance + // slither-disable-end reentrancy-eth,reentrancy-no-eth,reentrancy-benign function _doSwap( SwapPlatform swapPlatform,