From 5216b9853d81d83d5196dd7ab99cebe7987e2b76 Mon Sep 17 00:00:00 2001 From: Volodymyr Lykhonis Date: Thu, 22 Feb 2024 22:50:54 +0100 Subject: [PATCH] Add anti inflation share protection --- src/pool/Vault.sol | 10 ++ test/pool/Vault.t.sol | 363 ++++++++++++++++++++---------------------- 2 files changed, 185 insertions(+), 188 deletions(-) diff --git a/src/pool/Vault.sol b/src/pool/Vault.sol index 9bdf9c2..12ab58c 100644 --- a/src/pool/Vault.sol +++ b/src/pool/Vault.sol @@ -12,6 +12,7 @@ contract Vault is OwnableUnset, ReentrancyGuardUpgradeable, PausableUpgradeable uint32 private constant _MIN_FEE = 0; // 0% uint32 private constant _MAX_FEE = 15_000; // 15% uint256 private constant _MAX_VALIDATORS_SUPPORTED = 1_000_000; + uint256 private constant _MINIMUM_REQUIRED_SHARES = 1e3; error InvalidAmount(uint256 amount); error WithdrawalFailed(address account, address beneficiary, uint256 amount); @@ -265,6 +266,15 @@ contract Vault is OwnableUnset, ReentrancyGuardUpgradeable, PausableUpgradeable revert DepositLimitExceeded(newTotalDeposits, depositLimit); } uint256 shares = _toShares(amount); + // burn minimum shares of first depositor to prevent share inflation and dust shares attacks. + if (totalShares == 0) { + if (shares < _MINIMUM_REQUIRED_SHARES) { + revert InvalidAmount(amount); + } + _shares[address(0)] = _MINIMUM_REQUIRED_SHARES; + totalShares += _MINIMUM_REQUIRED_SHARES; + shares -= _MINIMUM_REQUIRED_SHARES; + } _shares[beneficiary] += shares; totalShares += shares; totalUnstaked += amount; diff --git a/test/pool/Vault.t.sol b/test/pool/Vault.t.sol index 305cb20..a74a40b 100644 --- a/test/pool/Vault.t.sol +++ b/test/pool/Vault.t.sol @@ -9,6 +9,8 @@ import {Vault} from "../../src/pool/Vault.sol"; import {IDepositContract} from "../../src/pool/IDepositContract.sol"; contract VaultTest is Test { + uint256 private constant _MINIMUM_REQUIRED_SHARES = 1e3; + event Deposited(address indexed account, address indexed beneficiary, uint256 amount); event Withdrawn(address indexed account, address indexed beneficiary, uint256 amount); event WithdrawalRequested(address indexed account, address indexed beneficiary, uint256 amount); @@ -156,7 +158,7 @@ contract VaultTest is Test { } function test_DepositPartialValidator(uint256 amount) public { - vm.assume(amount > 0 && amount < 32 ether); + vm.assume(amount > _MINIMUM_REQUIRED_SHARES && amount < 32 ether); vm.prank(owner); vault.setDepositLimit(32 ether); @@ -172,8 +174,7 @@ contract VaultTest is Test { assertEq(0, vault.totalStaked()); assertEq(amount, vault.totalUnstaked()); assertEq(amount, vault.totalAssets()); - assertEq(amount, vault.totalShares()); - assertEq(amount, vault.sharesOf(beneficiary)); + assertEq(amount - vault.balanceOf(address(0)), vault.balanceOf(beneficiary)); assertEq(0, vault.totalValidatorsRegistered()); } @@ -194,8 +195,7 @@ contract VaultTest is Test { assertEq(0, vault.totalStaked()); assertEq(amount, vault.totalUnstaked()); assertEq(amount, vault.totalAssets()); - assertEq(amount, vault.totalShares()); - assertEq(amount, vault.sharesOf(beneficiary)); + assertEq(amount - vault.balanceOf(address(0)), vault.balanceOf(beneficiary)); assertEq(0, vault.totalValidatorsRegistered()); } @@ -215,8 +215,7 @@ contract VaultTest is Test { assertEq(0, vault.totalStaked()); assertEq(35 ether, vault.totalUnstaked()); assertEq(35 ether, vault.totalAssets()); - assertEq(35 ether, vault.totalShares()); - assertEq(35 ether, vault.sharesOf(beneficiary)); + assertEq(35 ether - vault.balanceOf(address(0)), vault.balanceOf(beneficiary)); assertEq(0, vault.totalValidatorsRegistered()); assertEq(35 ether, address(vault).balance); @@ -229,8 +228,7 @@ contract VaultTest is Test { assertEq(32 ether, vault.totalStaked()); assertEq(3 ether, vault.totalUnstaked()); assertEq(35 ether, vault.totalAssets()); - assertEq(35 ether, vault.totalShares()); - assertEq(35 ether, vault.sharesOf(beneficiary)); + assertEq(35 ether - vault.balanceOf(address(0)), vault.balanceOf(beneficiary)); assertEq(1, vault.totalValidatorsRegistered()); assertEq(3 ether, address(vault).balance); @@ -280,9 +278,8 @@ contract VaultTest is Test { assertEq(0, vault.totalStaked()); assertEq(20 ether, vault.totalUnstaked()); assertEq(20 ether, vault.totalAssets()); - assertEq(20 ether, vault.totalShares()); - assertEq(20 ether, vault.sharesOf(alice)); - assertEq(0 ether, vault.sharesOf(bob)); + assertEq(20 ether - vault.balanceOf(address(0)), vault.balanceOf(alice)); + assertEq(0 ether, vault.balanceOf(bob)); assertEq(0, vault.totalValidatorsRegistered()); assertEq(20 ether, address(vault).balance); @@ -292,9 +289,8 @@ contract VaultTest is Test { assertEq(0 ether, vault.totalStaked()); assertEq(50 ether, vault.totalUnstaked()); assertEq(50 ether, vault.totalAssets()); - assertEq(50 ether, vault.totalShares()); - assertEq(20 ether, vault.sharesOf(alice)); - assertEq(30 ether, vault.sharesOf(bob)); + assertEq(20 ether - vault.balanceOf(address(0)), vault.balanceOf(alice)); + assertEq(30 ether, vault.balanceOf(bob)); assertEq(0, vault.totalValidatorsRegistered()); assertEq(50 ether, address(vault).balance); @@ -307,9 +303,8 @@ contract VaultTest is Test { assertEq(32 ether, vault.totalStaked()); assertEq(18 ether, vault.totalUnstaked()); assertEq(50 ether, vault.totalAssets()); - assertEq(50 ether, vault.totalShares()); - assertEq(20 ether, vault.sharesOf(alice)); - assertEq(30 ether, vault.sharesOf(bob)); + assertEq(20 ether - vault.balanceOf(address(0)), vault.balanceOf(alice)); + assertEq(30 ether, vault.balanceOf(bob)); assertEq(1, vault.totalValidatorsRegistered()); assertEq(18 ether, address(vault).balance); @@ -333,8 +328,7 @@ contract VaultTest is Test { assertEq(0 ether, vault.totalStaked()); assertEq(40 ether, vault.totalUnstaked()); assertEq(40 ether, vault.totalAssets()); - assertEq(40 ether, vault.totalShares()); - assertEq(40 ether, vault.sharesOf(alice)); + assertEq(40 ether - vault.balanceOf(address(0)), vault.balanceOf(alice)); assertEq(0, vault.totalValidatorsRegistered()); assertEq(40 ether, address(vault).balance); @@ -346,8 +340,7 @@ contract VaultTest is Test { assertEq(0 ether, vault.totalStaked()); assertEq(35 ether, vault.totalUnstaked()); assertEq(35 ether, vault.totalAssets()); - assertEq(35 ether, vault.totalShares()); - assertEq(35 ether, vault.sharesOf(alice)); + assertEq(35 ether - vault.balanceOf(address(0)), vault.balanceOf(alice)); assertEq(0, vault.totalValidatorsRegistered()); assertEq(35 ether, address(vault).balance); assertEq(5 ether, alice.balance); @@ -369,8 +362,7 @@ contract VaultTest is Test { assertEq(0 ether, vault.totalStaked()); assertEq(40 ether, vault.totalUnstaked()); assertEq(40 ether, vault.totalAssets()); - assertEq(40 ether, vault.totalShares()); - assertEq(40 ether, vault.sharesOf(alice)); + assertEq(40 ether - vault.balanceOf(address(0)), vault.balanceOf(alice)); assertEq(0, vault.totalValidatorsRegistered()); assertEq(40 ether, address(vault).balance); } @@ -391,12 +383,14 @@ contract VaultTest is Test { assertEq(0 ether, vault.totalStaked()); assertEq(40 ether, vault.totalUnstaked()); assertEq(40 ether, vault.totalAssets()); - assertEq(40 ether, vault.totalShares()); - assertEq(40 ether, vault.sharesOf(alice)); + assertEq(40 ether - vault.balanceOf(address(0)), vault.balanceOf(alice)); - vm.prank(alice); - vm.expectRevert(abi.encodeWithSelector(Vault.InsufficientBalance.selector, 40 ether, 41 ether)); + vm.startPrank(alice); + vm.expectRevert( + abi.encodeWithSelector(Vault.InsufficientBalance.selector, 40 ether - vault.balanceOf(address(0)), 41 ether) + ); vault.withdraw(41 ether, alice); + vm.stopPrank(); } function test_WithdrawAndExitValidator() public { @@ -417,9 +411,7 @@ contract VaultTest is Test { assertEq(32 ether, vault.totalStaked()); assertEq(1 ether, vault.totalUnstaked()); assertEq(33 ether, vault.totalAssets()); - assertEq(33 ether, vault.totalShares()); - assertEq(33 ether, vault.sharesOf(alice)); - assertEq(33 ether, vault.balanceOf(alice)); + assertEq(33 ether - vault.balanceOf(address(0)), vault.balanceOf(alice)); assertEq(0, vault.totalPendingWithdrawal()); assertEq(0, vault.totalClaimable()); assertEq(0, vault.pendingBalanceOf(alice)); @@ -440,9 +432,7 @@ contract VaultTest is Test { assertEq(32 ether, vault.totalStaked()); assertEq(0 ether, vault.totalUnstaked()); assertEq(30 ether, vault.totalAssets()); - assertEq(30 ether, vault.totalShares()); - assertEq(30 ether, vault.sharesOf(alice)); - assertEq(30 ether, vault.balanceOf(alice)); + assertEq(30 ether - vault.balanceOf(address(0)), vault.balanceOf(alice)); assertEq(2 ether, vault.totalPendingWithdrawal()); assertEq(0 ether, vault.totalClaimable()); assertEq(2 ether, vault.pendingBalanceOf(alice)); @@ -456,7 +446,7 @@ contract VaultTest is Test { assertEq(32 ether, vault.totalStaked()); assertEq(0 ether, vault.totalUnstaked()); assertEq(30 ether, vault.totalAssets()); - assertEq(30 ether, vault.balanceOf(alice)); + assertEq(30 ether - vault.balanceOf(address(0)), vault.balanceOf(alice)); assertEq(2 ether, vault.totalPendingWithdrawal()); assertEq(0 ether, vault.totalClaimable()); assertEq(2 ether, vault.pendingBalanceOf(alice)); @@ -468,7 +458,7 @@ contract VaultTest is Test { assertEq(0 ether, vault.totalStaked()); assertEq(30 ether, vault.totalUnstaked()); assertEq(30 ether, vault.totalAssets()); - assertEq(30 ether, vault.balanceOf(alice)); + assertEq(30 ether - vault.balanceOf(address(0)), vault.balanceOf(alice)); assertEq(2 ether, vault.totalPendingWithdrawal()); assertEq(2 ether, vault.totalClaimable()); assertEq(2 ether, vault.pendingBalanceOf(alice)); @@ -551,9 +541,8 @@ contract VaultTest is Test { assertEq(0 ether, vault.totalStaked()); assertEq(79 ether, vault.totalUnstaked()); assertEq(79 ether, vault.totalAssets()); - assertEq(79 ether, vault.totalShares()); - assertEq(33 ether, vault.sharesOf(alice)); - assertEq(46 ether, vault.sharesOf(bob)); + assertEq(33 ether - vault.balanceOf(address(0)), vault.balanceOf(alice)); + assertEq(46 ether, vault.balanceOf(bob)); assertEq(0, vault.totalValidatorsRegistered()); assertEq(79 ether, address(vault).balance); @@ -566,10 +555,7 @@ contract VaultTest is Test { assertEq(64 ether, vault.totalStaked()); assertEq(15 ether, vault.totalUnstaked()); assertEq(79 ether, vault.totalAssets()); - assertEq(79 ether, vault.totalShares()); - assertEq(33 ether, vault.sharesOf(alice)); - assertEq(33 ether, vault.balanceOf(alice)); - assertEq(46 ether, vault.sharesOf(bob)); + assertEq(33 ether - vault.balanceOf(address(0)), vault.balanceOf(alice)); assertEq(46 ether, vault.balanceOf(bob)); assertEq(0, vault.totalFees()); assertEq(2, vault.totalValidatorsRegistered()); @@ -589,16 +575,14 @@ contract VaultTest is Test { assertEq(24.9 ether, vault.totalUnstaked()); assertEq(88.9 ether, vault.totalAssets()); assertEq(79 ether, vault.totalShares()); - assertEq(33 ether, vault.sharesOf(alice)); - assertEq(37.135443037974683544 ether, vault.balanceOf(alice)); - assertEq(46 ether, vault.sharesOf(bob)); + assertEq(37.135443037974682418 ether, vault.balanceOf(alice)); assertEq(51.764556962025316455 ether, vault.balanceOf(bob)); assertEq(1.1 ether, vault.totalFees()); assertEq(2, vault.totalValidatorsRegistered()); assertEq(26 ether, address(vault).balance); vm.prank(alice); - vault.withdraw(37.135443037974683544 ether, alice); + vault.withdraw(37.135443037974682418 ether, alice); vm.prank(bob); vault.withdraw(51.764556962025316455 ether, bob); @@ -628,7 +612,7 @@ contract VaultTest is Test { assertEq(0 ether, vault.totalStaked()); assertEq(32 ether, vault.totalUnstaked()); - assertEq(32 ether, vault.balanceOf(alice)); + assertEq(32 ether - vault.balanceOf(address(0)), vault.balanceOf(alice)); assertEq(0 ether, vault.pendingBalanceOf(alice)); assertEq(0 ether, vault.claimableBalanceOf(alice)); assertEq(0 ether, vault.totalFees()); @@ -640,7 +624,7 @@ contract VaultTest is Test { assertEq(32 ether, vault.totalStaked()); assertEq(0 ether, vault.totalUnstaked()); - assertEq(32 ether, vault.balanceOf(alice)); + assertEq(32 ether - vault.balanceOf(address(0)), vault.balanceOf(alice)); assertEq(0 ether, vault.pendingBalanceOf(alice)); assertEq(0 ether, vault.claimableBalanceOf(alice)); assertEq(0 ether, vault.totalFees()); @@ -652,7 +636,7 @@ contract VaultTest is Test { assertEq(32 ether, vault.totalStaked()); assertEq(0 ether, vault.totalUnstaked()); - assertEq(30 ether, vault.balanceOf(alice)); + assertEq(30 ether - vault.balanceOf(address(0)), vault.balanceOf(alice)); assertEq(2 ether, vault.pendingBalanceOf(alice)); assertEq(0 ether, vault.claimableBalanceOf(alice)); assertEq(0 ether, vault.totalFees()); @@ -671,7 +655,7 @@ contract VaultTest is Test { assertEq(0 ether, vault.totalStaked()); assertEq(30.9 ether, vault.totalUnstaked()); - assertEq(30.9 ether, vault.balanceOf(alice)); + assertEq(30.89999999999999897 ether, vault.balanceOf(alice)); assertEq(2 ether, vault.pendingBalanceOf(alice)); assertEq(2 ether, vault.claimableBalanceOf(alice)); assertEq(0.1 ether, vault.totalFees()); @@ -694,7 +678,7 @@ contract VaultTest is Test { assertEq(0 ether, vault.totalStaked()); assertEq(32 ether, vault.totalUnstaked()); - assertEq(32 ether, vault.balanceOf(alice)); + assertEq(32 ether - vault.balanceOf(address(0)), vault.balanceOf(alice)); assertEq(0 ether, vault.pendingBalanceOf(alice)); assertEq(0 ether, vault.claimableBalanceOf(alice)); assertEq(0 ether, vault.totalFees()); @@ -706,7 +690,7 @@ contract VaultTest is Test { assertEq(32 ether, vault.totalStaked()); assertEq(0 ether, vault.totalUnstaked()); - assertEq(32 ether, vault.balanceOf(alice)); + assertEq(32 ether - vault.balanceOf(address(0)), vault.balanceOf(alice)); assertEq(0 ether, vault.pendingBalanceOf(alice)); assertEq(0 ether, vault.claimableBalanceOf(alice)); assertEq(0 ether, vault.totalFees()); @@ -718,7 +702,7 @@ contract VaultTest is Test { assertEq(32 ether, vault.totalStaked()); assertEq(0 ether, vault.totalUnstaked()); - assertEq(30 ether, vault.balanceOf(alice)); + assertEq(30 ether - vault.balanceOf(address(0)), vault.balanceOf(alice)); assertEq(2 ether, vault.pendingBalanceOf(alice)); assertEq(0 ether, vault.claimableBalanceOf(alice)); assertEq(0 ether, vault.totalFees()); @@ -737,7 +721,7 @@ contract VaultTest is Test { assertEq(32 ether, vault.totalStaked()); assertEq(2.7 ether, vault.totalUnstaked()); - assertEq(32.7 ether, vault.balanceOf(alice)); + assertEq(32.69999999999999891 ether, vault.balanceOf(alice)); assertEq(2 ether, vault.pendingBalanceOf(alice)); assertEq(0 ether, vault.claimableBalanceOf(alice)); assertEq(0.3 ether, vault.totalFees()); @@ -764,9 +748,7 @@ contract VaultTest is Test { assertEq(0 ether, vault.totalStaked()); assertEq(26 ether, vault.totalUnstaked()); assertEq(26 ether, vault.totalShares()); - assertEq(10 ether, vault.sharesOf(alice)); - assertEq(10 ether, vault.balanceOf(alice)); - assertEq(16 ether, vault.sharesOf(bob)); + assertEq(10 ether - vault.balanceOf(address(0)), vault.balanceOf(alice)); assertEq(16 ether, vault.balanceOf(bob)); assertEq(26 ether, address(vault).balance); @@ -779,9 +761,7 @@ contract VaultTest is Test { assertEq(0 ether, vault.totalStaked()); assertEq(39 ether, vault.totalUnstaked()); assertEq(26 ether, vault.totalShares()); - assertEq(10 ether, vault.sharesOf(alice)); - assertEq(15 ether, vault.balanceOf(alice)); - assertEq(16 ether, vault.sharesOf(bob)); + assertEq(15 ether - vault.balanceOf(address(0)), vault.balanceOf(alice)); assertEq(24 ether, vault.balanceOf(bob)); assertEq(39 ether, address(vault).balance); @@ -791,10 +771,7 @@ contract VaultTest is Test { assertEq(0 ether, vault.totalStaked()); assertEq(54 ether, vault.totalUnstaked()); - assertEq(36 ether, vault.totalShares()); - assertEq(10 ether, vault.sharesOf(alice)); - assertEq(15 ether, vault.balanceOf(alice)); - assertEq(26 ether, vault.sharesOf(bob)); + assertEq(15 ether - vault.balanceOf(address(0)), vault.balanceOf(alice)); assertEq(39 ether, vault.balanceOf(bob)); assertEq(54 ether, address(vault).balance); } @@ -828,10 +805,7 @@ contract VaultTest is Test { assertEq(0 ether, vault.totalStaked()); assertEq(54 ether, vault.totalUnstaked()); assertEq(54 ether, vault.totalAssets()); - assertEq(36 ether, vault.totalShares()); - assertEq(10 ether, vault.sharesOf(alice)); - assertEq(15 ether, vault.balanceOf(alice)); - assertEq(26 ether, vault.sharesOf(bob)); + assertEq(15 ether - vault.balanceOf(address(0)), vault.balanceOf(alice)); assertEq(39 ether, vault.balanceOf(bob)); assertEq(54 ether, address(vault).balance); assertEq(0 ether, bob.balance); @@ -842,10 +816,7 @@ contract VaultTest is Test { assertEq(0 ether, vault.totalStaked()); assertEq(45 ether, vault.totalUnstaked()); assertEq(45 ether, vault.totalAssets()); - assertEq(30 ether, vault.totalShares()); - assertEq(10 ether, vault.sharesOf(alice)); - assertEq(15 ether, vault.balanceOf(alice)); - assertEq(20 ether, vault.sharesOf(bob)); + assertEq(15 ether - vault.balanceOf(address(0)), vault.balanceOf(alice)); assertEq(30 ether, vault.balanceOf(bob)); assertEq(45 ether, address(vault).balance); assertEq(9 ether, bob.balance); @@ -871,7 +842,7 @@ contract VaultTest is Test { assertEq(0 ether, address(vault).balance); assertEq(64 ether, vault.totalStaked()); assertEq(0 ether, vault.totalUnstaked()); - assertEq(64 ether, vault.balanceOf(alice)); + assertEq(64 ether - vault.balanceOf(address(0)), vault.balanceOf(alice)); vm.prank(alice); vault.withdraw(2 ether, alice); @@ -880,7 +851,7 @@ contract VaultTest is Test { assertEq(64 ether, vault.totalStaked()); assertEq(0 ether, vault.totalUnstaked()); assertEq(2 ether, vault.totalPendingWithdrawal()); - assertEq(62 ether, vault.balanceOf(alice)); + assertEq(62 ether - vault.balanceOf(address(0)), vault.balanceOf(alice)); assertEq(2 ether, vault.pendingBalanceOf(alice)); assertEq(0 ether, vault.claimableBalanceOf(alice)); assertEq(0 ether, vault.totalFees()); @@ -895,7 +866,7 @@ contract VaultTest is Test { assertEq(32 ether, vault.totalStaked()); assertEq(30 ether, vault.totalUnstaked()); assertEq(2 ether, vault.totalPendingWithdrawal()); - assertEq(62 ether, vault.balanceOf(alice)); + assertEq(62 ether - vault.balanceOf(address(0)), vault.balanceOf(alice)); assertEq(2 ether, vault.pendingBalanceOf(alice)); assertEq(2 ether, vault.claimableBalanceOf(alice)); assertEq(0 ether, vault.totalFees()); @@ -931,7 +902,7 @@ contract VaultTest is Test { assertEq(96 ether, vault.totalStaked()); assertEq(0 ether, vault.totalUnstaked()); assertEq(0 ether, vault.totalPendingWithdrawal()); - assertEq(64 ether, vault.balanceOf(alice)); + assertEq(64 ether - vault.balanceOf(address(0)), vault.balanceOf(alice)); assertEq(32 ether, vault.balanceOf(bob)); vm.prank(alice); @@ -942,7 +913,7 @@ contract VaultTest is Test { assertEq(0 ether, vault.totalUnstaked()); assertEq(6 ether, vault.totalPendingWithdrawal()); assertEq(0 ether, vault.claimableBalanceOf(alice)); - assertEq(58 ether, vault.balanceOf(alice)); + assertEq(58 ether - vault.balanceOf(address(0)), vault.balanceOf(alice)); assertEq(32 ether, vault.balanceOf(bob)); vm.prank(bob); @@ -953,7 +924,7 @@ contract VaultTest is Test { assertEq(28 ether, vault.totalUnstaked()); assertEq(6 ether, vault.totalPendingWithdrawal()); assertEq(0 ether, vault.claimableBalanceOf(alice)); - assertEq(58 ether, vault.balanceOf(alice)); + assertEq(58 ether - vault.balanceOf(address(0)), vault.balanceOf(alice)); assertEq(60 ether, vault.balanceOf(bob)); // simulate withdrawal @@ -965,7 +936,7 @@ contract VaultTest is Test { assertEq(6 ether, vault.totalPendingWithdrawal()); assertEq(0 ether, vault.claimableBalanceOf(alice)); assertEq(0 ether, vault.totalFees()); - assertEq(58 ether, vault.balanceOf(alice)); + assertEq(58 ether - vault.balanceOf(address(0)), vault.balanceOf(alice)); assertEq(60 ether, vault.balanceOf(bob)); vm.prank(oracle); @@ -979,7 +950,7 @@ contract VaultTest is Test { assertEq(6 ether, vault.totalPendingWithdrawal()); assertEq(6 ether, vault.claimableBalanceOf(alice)); assertEq(0 ether, vault.totalFees()); - assertEq(58 ether, vault.balanceOf(alice)); + assertEq(58 ether - vault.balanceOf(address(0)), vault.balanceOf(alice)); assertEq(60 ether, vault.balanceOf(bob)); } @@ -1002,7 +973,7 @@ contract VaultTest is Test { assertEq(0 ether, vault.totalUnstaked()); assertEq(0 ether, vault.totalPendingWithdrawal()); assertEq(0 ether, vault.totalClaimable()); - assertEq(32 ether, vault.balanceOf(alice)); + assertEq(32 ether - vault.balanceOf(address(0)), vault.balanceOf(alice)); assertEq(0 ether, vault.claimableBalanceOf(alice)); assertEq(0 ether, vault.totalFees()); @@ -1017,7 +988,7 @@ contract VaultTest is Test { assertEq(0 ether, vault.totalUnstaked()); assertEq(2 ether, vault.totalPendingWithdrawal()); assertEq(0 ether, vault.totalClaimable()); - assertEq(30 ether, vault.balanceOf(alice)); + assertEq(30 ether - vault.balanceOf(address(0)), vault.balanceOf(alice)); assertEq(0 ether, vault.claimableBalanceOf(alice)); assertEq(0 ether, vault.totalFees()); @@ -1030,7 +1001,7 @@ contract VaultTest is Test { assertEq(2 ether, vault.totalUnstaked()); assertEq(2 ether, vault.totalPendingWithdrawal()); assertEq(0 ether, vault.totalClaimable()); - assertEq(32 ether, vault.balanceOf(alice)); + assertEq(32 ether - vault.balanceOf(address(0)), vault.balanceOf(alice)); assertEq(0 ether, vault.claimableBalanceOf(alice)); assertEq(0 ether, vault.totalFees()); @@ -1046,7 +1017,7 @@ contract VaultTest is Test { assertEq(32 ether, vault.totalUnstaked()); assertEq(2 ether, vault.totalPendingWithdrawal()); assertEq(2 ether, vault.totalClaimable()); - assertEq(32 ether, vault.balanceOf(alice)); + assertEq(32 ether - vault.balanceOf(address(0)), vault.balanceOf(alice)); assertEq(2 ether, vault.claimableBalanceOf(alice)); assertEq(0 ether, vault.totalFees()); @@ -1079,7 +1050,7 @@ contract VaultTest is Test { assertEq(0 ether, vault.totalUnstaked()); assertEq(0 ether, vault.totalPendingWithdrawal()); assertEq(0 ether, vault.totalClaimable()); - assertEq(96 ether, vault.balanceOf(alice)); + assertEq(96 ether - vault.balanceOf(address(0)), vault.balanceOf(alice)); assertEq(0 ether, vault.pendingBalanceOf(alice)); assertEq(0 ether, vault.claimableBalanceOf(alice)); assertEq(0 ether, vault.totalFees()); @@ -1092,7 +1063,7 @@ contract VaultTest is Test { assertEq(0 ether, vault.totalUnstaked()); assertEq(66 ether, vault.totalPendingWithdrawal()); assertEq(0 ether, vault.totalClaimable()); - assertEq(30 ether, vault.balanceOf(alice)); + assertEq(30 ether - vault.balanceOf(address(0)), vault.balanceOf(alice)); assertEq(66 ether, vault.pendingBalanceOf(alice)); assertEq(0 ether, vault.claimableBalanceOf(alice)); assertEq(0 ether, vault.totalFees()); @@ -1105,7 +1076,7 @@ contract VaultTest is Test { assertEq(0 ether, vault.totalUnstaked()); assertEq(66 ether, vault.totalPendingWithdrawal()); assertEq(0 ether, vault.totalClaimable()); - assertEq(30 ether, vault.balanceOf(alice)); + assertEq(30 ether - vault.balanceOf(address(0)), vault.balanceOf(alice)); assertEq(66 ether, vault.pendingBalanceOf(alice)); assertEq(0 ether, vault.claimableBalanceOf(alice)); assertEq(0 ether, vault.totalFees()); @@ -1118,7 +1089,7 @@ contract VaultTest is Test { assertEq(0 ether, vault.totalUnstaked()); assertEq(66 ether, vault.totalPendingWithdrawal()); assertEq(32 ether, vault.totalClaimable()); - assertEq(30 ether, vault.balanceOf(alice)); + assertEq(30 ether - vault.balanceOf(address(0)), vault.balanceOf(alice)); assertEq(66 ether, vault.pendingBalanceOf(alice)); assertEq(32 ether, vault.claimableBalanceOf(alice)); assertEq(0 ether, vault.totalFees()); @@ -1134,7 +1105,7 @@ contract VaultTest is Test { assertEq(30 ether, vault.totalUnstaked()); assertEq(66 ether, vault.totalPendingWithdrawal()); assertEq(66 ether, vault.totalClaimable()); - assertEq(30 ether, vault.balanceOf(alice)); + assertEq(30 ether - vault.balanceOf(address(0)), vault.balanceOf(alice)); assertEq(66 ether, vault.pendingBalanceOf(alice)); assertEq(66 ether, vault.claimableBalanceOf(alice)); assertEq(0 ether, vault.totalFees()); @@ -1157,7 +1128,7 @@ contract VaultTest is Test { assertEq(33 ether, vault.totalUnstaked()); assertEq(33 ether, vault.totalAssets()); assertEq(33 ether, vault.totalShares()); - assertEq(33 ether, vault.sharesOf(alice)); + assertEq(33 ether - vault.balanceOf(address(0)), vault.balanceOf(alice)); assertEq(0, vault.totalValidatorsRegistered()); assertEq(33 ether, address(vault).balance); @@ -1168,8 +1139,7 @@ contract VaultTest is Test { assertEq(1 ether, vault.totalUnstaked()); assertEq(33 ether, vault.totalAssets()); assertEq(33 ether, vault.totalShares()); - assertEq(33 ether, vault.sharesOf(alice)); - assertEq(33 ether, vault.balanceOf(alice)); + assertEq(33 ether - vault.balanceOf(address(0)), vault.balanceOf(alice)); assertEq(0, vault.totalFees()); assertEq(1, vault.totalValidatorsRegistered()); assertEq(1 ether, address(vault).balance); @@ -1181,8 +1151,7 @@ contract VaultTest is Test { assertEq(0 ether, vault.totalUnstaked()); assertEq(31.5 ether, vault.totalAssets()); assertEq(31.5 ether, vault.totalShares()); - assertEq(31.5 ether, vault.sharesOf(alice)); - assertEq(31.5 ether, vault.balanceOf(alice)); + assertEq(31.5 ether - vault.balanceOf(address(0)), vault.balanceOf(alice)); assertEq(0, vault.totalFees()); assertEq(1, vault.totalValidatorsRegistered()); assertEq(0 ether, address(vault).balance); @@ -1205,9 +1174,7 @@ contract VaultTest is Test { assertEq(32 ether, vault.totalStaked()); assertEq(0.36 ether, vault.totalUnstaked()); assertEq(31.86 ether, vault.totalAssets()); - assertEq(31.5 ether, vault.totalShares()); - assertEq(31.5 ether, vault.sharesOf(alice)); - assertEq(31.86 ether, vault.balanceOf(alice)); + assertEq(31.86 ether - vault.balanceOf(address(0)) - 1, /* rounding error */ vault.balanceOf(alice)); assertEq(0.04 ether, vault.totalFees()); assertEq(1, vault.totalValidatorsRegistered()); assertEq(0.4 ether, address(vault).balance); @@ -1231,8 +1198,7 @@ contract VaultTest is Test { assertEq(32.13 ether, vault.totalUnstaked()); assertEq(32.13 ether, vault.totalAssets()); assertEq(31.5 ether, vault.totalShares()); - assertEq(31.5 ether, vault.sharesOf(alice)); - assertEq(32.13 ether, vault.balanceOf(alice)); + assertEq(32.13 ether - vault.balanceOf(address(0)), vault.balanceOf(alice)); assertEq(0.07 ether, vault.totalFees()); assertEq(1, vault.totalValidatorsRegistered()); assertEq(32.7 ether, address(vault).balance); @@ -1260,7 +1226,7 @@ contract VaultTest is Test { assertEq(33 ether, vault.totalUnstaked()); assertEq(33 ether, vault.totalAssets()); assertEq(33 ether, vault.totalShares()); - assertEq(33 ether, vault.sharesOf(alice)); + assertEq(33 ether - vault.balanceOf(address(0)), vault.balanceOf(alice)); assertEq(0, vault.totalValidatorsRegistered()); assertEq(33 ether, address(vault).balance); @@ -1271,123 +1237,77 @@ contract VaultTest is Test { assertEq(1 ether, vault.totalUnstaked()); assertEq(33 ether, vault.totalAssets()); assertEq(33 ether, vault.totalShares()); - assertEq(33 ether, vault.sharesOf(alice)); - assertEq(33 ether, vault.balanceOf(alice)); + assertEq(33 ether - vault.balanceOf(address(0)), vault.balanceOf(alice)); assertEq(0, vault.totalFees()); assertEq(1, vault.totalValidatorsRegistered()); assertEq(1 ether, address(vault).balance); - vm.prank(alice); - vault.withdraw(33 ether, alice); + vm.startPrank(alice); + vault.withdraw(33 ether - vault.balanceOf(address(0)), alice); + vm.stopPrank(); assertEq(32 ether, vault.totalStaked()); assertEq(0 ether, vault.totalUnstaked()); - assertEq(0 ether, vault.totalAssets()); - assertEq(0 ether, vault.totalShares()); - assertEq(0 ether, vault.sharesOf(alice)); + assertEq(vault.balanceOf(address(0)), vault.totalAssets()); + assertEq(vault.balanceOf(address(0)), vault.totalShares()); assertEq(0 ether, vault.balanceOf(alice)); assertEq(0, vault.totalFees()); assertEq(1, vault.totalValidatorsRegistered()); assertEq(0 ether, address(vault).balance); - assertEq(32 ether, vault.totalPendingWithdrawal()); - assertEq(32 ether, vault.pendingBalanceOf(alice)); + assertEq(32 ether - vault.balanceOf(address(0)), vault.totalPendingWithdrawal()); + assertEq(32 ether - vault.balanceOf(address(0)), vault.pendingBalanceOf(alice)); assertEq(0 ether, vault.claimableBalanceOf(alice)); assertEq(1 ether, alice.balance); // simulate rewards matching withdrawal vm.deal(address(vault), 32 ether); - vm.prank(oracle); + vm.startPrank(oracle); vm.expectEmit(); - emit Rebalanced(32 ether, 0 ether, 0 ether, 0 ether); + emit Rebalanced(32 ether, 0 ether, 0 ether, vault.balanceOf(address(0))); vault.rebalance(); + vm.stopPrank(); assertEq(0 ether, vault.totalStaked()); - assertEq(0 ether, vault.totalUnstaked()); - assertEq(0 ether, vault.totalAssets()); - assertEq(0 ether, vault.totalShares()); - assertEq(0 ether, vault.sharesOf(alice)); + assertEq(vault.balanceOf(address(0)), vault.totalUnstaked()); + assertEq(vault.balanceOf(address(0)), vault.totalAssets()); + assertEq(vault.balanceOf(address(0)), vault.totalShares()); assertEq(0 ether, vault.balanceOf(alice)); assertEq(0 ether, vault.totalFees()); assertEq(1, vault.totalValidatorsRegistered()); assertEq(32 ether, address(vault).balance); - assertEq(32 ether, vault.totalPendingWithdrawal()); - assertEq(32 ether, vault.pendingBalanceOf(alice)); - assertEq(32 ether, vault.claimableBalanceOf(alice)); + assertEq(32 ether - vault.balanceOf(address(0)), vault.totalPendingWithdrawal()); + assertEq(32 ether - vault.balanceOf(address(0)), vault.pendingBalanceOf(alice)); + assertEq(32 ether - vault.balanceOf(address(0)), vault.claimableBalanceOf(alice)); assertEq(1 ether, alice.balance); // simulate withdrawal vm.deal(address(vault), 64 ether); - vm.prank(oracle); + vm.startPrank(oracle); vm.expectEmit(); - emit RewardsDistributed(0 ether, 32 ether, 3.2 ether); + emit RewardsDistributed(vault.balanceOf(address(0)), 32 ether, 3.2 ether); vm.expectEmit(); - emit Rebalanced(0 ether, 0 ether, 0 ether, 28.8 ether); + emit Rebalanced(0 ether, vault.balanceOf(address(0)), 0 ether, 28.8 ether + vault.balanceOf(address(0))); vault.rebalance(); + vm.stopPrank(); assertEq(0 ether, vault.totalStaked()); - assertEq(28.8 ether, vault.totalUnstaked()); - assertEq(28.8 ether, vault.totalAssets()); - assertEq(0 ether, vault.totalShares()); - assertEq(0 ether, vault.sharesOf(alice)); + assertEq(28.8 ether + _MINIMUM_REQUIRED_SHARES, vault.totalUnstaked()); + assertEq(28.8 ether + _MINIMUM_REQUIRED_SHARES, vault.totalAssets()); assertEq(0 ether, vault.balanceOf(alice)); assertEq(3.2 ether, vault.totalFees()); assertEq(1, vault.totalValidatorsRegistered()); assertEq(64 ether, address(vault).balance); - assertEq(32 ether, vault.totalPendingWithdrawal()); - assertEq(32 ether, vault.pendingBalanceOf(alice)); - assertEq(32 ether, vault.claimableBalanceOf(alice)); + assertEq(32 ether - _MINIMUM_REQUIRED_SHARES, vault.totalPendingWithdrawal()); + assertEq(32 ether - _MINIMUM_REQUIRED_SHARES, vault.pendingBalanceOf(alice)); + assertEq(32 ether - _MINIMUM_REQUIRED_SHARES, vault.claimableBalanceOf(alice)); assertEq(1 ether, alice.balance); } - function test_FirstDepositSweepsRemainingRewards() public { - vm.startPrank(owner); - vault.setDepositLimit(100 ether); - vault.enableOracle(oracle, true); - vault.setFee(10_000); - vault.setFeeRecipient(feeRecipient); - vm.stopPrank(); - - address alice = vm.addr(100); - - // simulate rewards - vm.deal(address(vault), 1 ether); - - vm.prank(oracle); - vm.expectEmit(); - emit RewardsDistributed(0 ether, 1 ether, 0.1 ether); - vm.expectEmit(); - emit Rebalanced(0 ether, 0 ether, 0 ether, 0.9 ether); - vault.rebalance(); - - assertEq(0 ether, vault.totalStaked()); - assertEq(0.9 ether, vault.totalUnstaked()); - assertEq(0.9 ether, vault.totalAssets()); - assertEq(0 ether, vault.totalShares()); - assertEq(0 ether, vault.sharesOf(alice)); - assertEq(0 ether, vault.balanceOf(alice)); - assertEq(0.1 ether, vault.totalFees()); - assertEq(1 ether, address(vault).balance); - - // sweep remaining funds - vm.deal(alice, 1 ether); - vm.prank(alice); - vault.deposit{value: 1 ether}(alice); - - assertEq(0 ether, vault.totalStaked()); - assertEq(1.9 ether, vault.totalUnstaked()); - assertEq(1.9 ether, vault.totalAssets()); - assertEq(1 ether, vault.totalShares()); - assertEq(1 ether, vault.sharesOf(alice)); - assertEq(1.9 ether, vault.balanceOf(alice)); - assertEq(0.1 ether, vault.totalFees()); - assertEq(2 ether, address(vault).balance); - } - function test_AccountAfterWithdrawalAndDeposit() public { vm.startPrank(owner); vault.setDepositLimit(100 ether); @@ -1407,12 +1327,13 @@ contract VaultTest is Test { assertEq(0 ether, vault.totalUnstaked()); assertEq(0 ether, vault.totalPendingWithdrawal()); assertEq(0 ether, vault.totalClaimable()); - assertEq(32 ether, vault.balanceOf(alice)); + assertEq(32 ether - vault.balanceOf(address(0)), vault.balanceOf(alice)); assertEq(0 ether, vault.claimableBalanceOf(alice)); assertEq(0 ether, vault.totalFees()); - vm.prank(alice); - vault.withdraw(32 ether, alice); + vm.startPrank(alice); + vault.withdraw(32 ether - vault.balanceOf(address(0)), alice); + vm.stopPrank(); address bob = vm.addr(101); vm.deal(bob, 32 ether); @@ -1422,48 +1343,114 @@ contract VaultTest is Test { assertEq(32 ether, address(vault).balance); assertEq(32 ether, vault.totalStaked()); assertEq(32 ether, vault.totalUnstaked()); - assertEq(32 ether, vault.totalPendingWithdrawal()); + assertEq(32 ether - vault.balanceOf(address(0)), vault.totalPendingWithdrawal()); assertEq(0 ether, vault.totalClaimable()); assertEq(32 ether, vault.balanceOf(bob)); assertEq(0 ether, vault.balanceOf(alice)); - assertEq(32 ether, vault.pendingBalanceOf(alice)); + assertEq(32 ether - vault.balanceOf(address(0)), vault.pendingBalanceOf(alice)); assertEq(0 ether, vault.claimableBalanceOf(alice)); assertEq(0 ether, vault.totalFees()); - vm.prank(oracle); + vm.startPrank(oracle); vm.expectEmit(); emit Rebalanced(32 ether, 32 ether, 32 ether, 32 ether); vault.rebalance(); + vm.stopPrank(); assertEq(32 ether, address(vault).balance); assertEq(32 ether, vault.totalStaked()); assertEq(32 ether, vault.totalUnstaked()); - assertEq(32 ether, vault.totalPendingWithdrawal()); + assertEq(32 ether - vault.balanceOf(address(0)), vault.totalPendingWithdrawal()); assertEq(0 ether, vault.totalClaimable()); assertEq(32 ether, vault.balanceOf(bob)); assertEq(0 ether, vault.balanceOf(alice)); - assertEq(32 ether, vault.pendingBalanceOf(alice)); + assertEq(32 ether - vault.balanceOf(address(0)), vault.pendingBalanceOf(alice)); assertEq(0 ether, vault.claimableBalanceOf(alice)); assertEq(0 ether, vault.totalFees()); vm.deal(address(vault), 64 ether); - vm.prank(oracle); + vm.startPrank(oracle); vm.expectEmit(); - emit Rebalanced(32 ether, 32 ether, 0 ether, 32 ether); + emit Rebalanced(32 ether, 32 ether, 0 ether, 32 ether + vault.balanceOf(address(0))); vault.rebalance(); + vm.stopPrank(); assertEq(64 ether, address(vault).balance); assertEq(0 ether, vault.totalStaked()); - assertEq(32 ether, vault.totalUnstaked()); - assertEq(32 ether, vault.totalPendingWithdrawal()); - assertEq(32 ether, vault.totalClaimable()); + assertEq(32 ether + vault.balanceOf(address(0)), vault.totalUnstaked()); + assertEq(32 ether - vault.balanceOf(address(0)), vault.totalPendingWithdrawal()); + assertEq(32 ether - vault.balanceOf(address(0)), vault.totalClaimable()); assertEq(32 ether, vault.balanceOf(bob)); assertEq(0 ether, vault.balanceOf(alice)); - assertEq(32 ether, vault.pendingBalanceOf(alice)); - assertEq(32 ether, vault.claimableBalanceOf(alice)); + assertEq(32 ether - vault.balanceOf(address(0)), vault.pendingBalanceOf(alice)); + assertEq(32 ether - vault.balanceOf(address(0)), vault.claimableBalanceOf(alice)); assertEq(0 ether, vault.totalFees()); } + + function test_InflationAttack() public { + vm.startPrank(owner); + vault.setDepositLimit(1_000_000 ether); + vault.enableOracle(oracle, true); + vault.setFee(10_000); + vault.setFeeRecipient(feeRecipient); + vm.stopPrank(); + + address alice = vm.addr(100); + + assertEq(vault.totalAssets(), 0); + assertEq(vault.totalShares(), 0); + assertEq(vault.sharesOf(alice), 0); + assertEq(vault.balanceOf(alice), 0 ether); + + // Alice - Attacker simply deposits 100 wie + vm.deal(alice, 1_000_000 ether); + vm.prank(alice); + vault.deposit{value: _MINIMUM_REQUIRED_SHARES + 1 wei}(alice); + + assertNotEq(vault.totalAssets(), 0); + assertNotEq(vault.totalShares(), 0); + assertNotEq(vault.sharesOf(alice), 0); + assertNotEq(vault.balanceOf(alice), 0); + + // simulate rewards - reward injection + vm.deal(address(vault), 1 ether); + + // vault rebalance - reward accounting + vm.prank(oracle); + vault.rebalance(); + + uint256 withdraw_amount = vault.balanceOf(alice) - 2; + vm.prank(alice); + vault.withdraw(withdraw_amount, alice); + + assertNotEq(vault.totalAssets(), 0); + assertNotEq(vault.totalShares(), 0); + assertEq(vault.sharesOf(alice), 1); + assertNotEq(vault.balanceOf(alice), 0); + + for (uint256 i; i < 65; i++) { + vm.prank(alice); + // ignore reverts due deposit limits + try vault.deposit{value: vault.totalAssets() - 1}(alice) {} catch {} + } + + uint256 aliceShares = vault.sharesOf(alice); + uint256 aliceBalance = vault.balanceOf(alice); + + address bob = vm.addr(111); + + vm.deal(bob, 1 ether); + vm.prank(bob); + vault.deposit{value: 1 ether}(bob); + + assertEq(vault.sharesOf(alice), aliceShares); + assertTrue(aliceBalance <= vault.balanceOf(alice)); + assertNotEq(vault.sharesOf(bob), 0); + assertTrue( + vault.balanceOf(bob) >= 1 ether - 1e15 /* rounding error of 18 decimals - 3 of minimum shares amount */ + ); + } } contract MockDepositContract is IDepositContract {