diff --git a/src/contracts/AbstractARM.sol b/src/contracts/AbstractARM.sol index 2f7ddf81..bf4865bb 100644 --- a/src/contracts/AbstractARM.sol +++ b/src/contracts/AbstractARM.sol @@ -679,6 +679,7 @@ abstract contract AbstractARM is OwnableOperable, ERC20Upgradeable { function _deposit(uint256 assets, address receiver) internal returns (uint256 shares) { require(totalAssets() > MIN_TOTAL_SUPPLY || reservedWithdrawLiquidity == 0, "ARM: insolvent"); shares = convertToShares(assets); + require(shares != 0, "ARM: zero shares"); // Transfer liquidity from the depositor before minting LP shares. IERC20(liquidityAsset).transferFrom(msg.sender, address(this), assets); diff --git a/test/invariants/LidoARM/TargetFunction.sol b/test/invariants/LidoARM/TargetFunction.sol index 1756b87a..23c213de 100644 --- a/test/invariants/LidoARM/TargetFunction.sol +++ b/test/invariants/LidoARM/TargetFunction.sol @@ -93,8 +93,12 @@ abstract contract TargetFunction is Properties { // Select a random user address user = lps[account % lps.length]; - amount = uint80(_bound(amount, 0, min(weth.balanceOf(user), type(uint80).max))); - vm.assume(amount > 0); + uint256 totalSupply = lidoARM.totalSupply(); + uint256 totalAssets = lidoARM.totalAssets(); + uint256 minAmount = totalAssets / totalSupply + 1; + uint256 maxAmount = min(weth.balanceOf(user), type(uint80).max); + vm.assume(minAmount <= maxAmount); + amount = uint80(_bound(amount, minAmount, maxAmount)); // Cache preview deposit uint256 expectedShares = lidoARM.previewDeposit(amount); diff --git a/test/invariants/OriginARM/TargetFunction.sol b/test/invariants/OriginARM/TargetFunction.sol index 1a933920..eb920b2f 100644 --- a/test/invariants/OriginARM/TargetFunction.sol +++ b/test/invariants/OriginARM/TargetFunction.sol @@ -86,6 +86,11 @@ abstract contract TargetFunction is Properties { if (CONSOLE_LOG) console.log("deposit() \t\t From: %s | \t Amount: %s", name(user), faa(amount)); // Expected amount of shares + uint256 totalSupply = originARM.totalSupply(); + uint256 totalAssets = originARM.totalAssets(); + uint256 minAmount = totalAssets / totalSupply + 1; + vm.assume(minAmount <= type(uint88).max); + amount = uint88(_bound(amount, minAmount, type(uint88).max)); uint256 previewDeposit = originARM.previewDeposit(amount); // Main call diff --git a/test/unit/OriginARM/Deposit.sol b/test/unit/OriginARM/Deposit.sol index 604d59dc..c05fddee 100644 --- a/test/unit/OriginARM/Deposit.sol +++ b/test/unit/OriginARM/Deposit.sol @@ -228,6 +228,18 @@ contract Unit_Concrete_OriginARM_Deposit_Test_ is Unit_Shared_Test { originARM.deposit(DEFAULT_AMOUNT); } + function test_RevertWhen_Deposit_Because_ZeroShares() public { + // Donating 1 wei increases assets per share enough for a 1 wei deposit to round down to 0 shares. + deal(address(weth), address(originARM), weth.balanceOf(address(originARM)) + 1); + assertEq(originARM.convertToShares(1), 0, "deposit should round to zero shares"); + + deal(address(weth), alice, 1); + + vm.expectRevert("ARM: zero shares"); + vm.prank(alice); + originARM.deposit(1); + } + /// @notice Deposits remain priced from gross assets when escrowed redeem shares share a partial loss. function test_Deposit_When_OutstandingRequestSharesShareSmallLoss() public