|
1 | 1 | // SPDX-License-Identifier: GPL-3.0-or-later |
2 | 2 | pragma solidity ^0.8.19; |
| 3 | + |
3 | 4 | import {Test} from "forge-std/Test.sol"; |
4 | 5 | import {StdInvariant} from "forge-std/StdInvariant.sol"; |
5 | 6 | import {SimpleETHContributionVault} from "src/safe-modules/SimpleETHContributionVault.sol"; |
6 | 7 | import {CommonBase} from "forge-std/Base.sol"; |
7 | 8 | import {StdCheats} from "forge-std/StdCheats.sol"; |
8 | 9 | import {StdUtils} from "forge-std/StdUtils.sol"; |
| 10 | +import {getETHValidatorData} from "../SimpleETHContributionVault.t.sol"; |
9 | 11 |
|
10 | 12 | contract MockDepositContract { |
11 | | - event Deposit ( |
12 | | - bytes[] pubkeys, |
13 | | - bytes[] withdrawal_credentials, |
14 | | - bytes[] signatures, |
15 | | - bytes32[] deposit_data_roots |
16 | | - ); |
17 | | - function depositValidator( |
18 | | - bytes[] calldata, |
19 | | - bytes[] calldata, |
20 | | - bytes[] calldata, |
21 | | - bytes32[] calldata |
22 | | - ) external payable {} |
| 13 | + uint256 public ghost_depositSum; |
| 14 | + |
| 15 | + event Deposit(bytes[] pubkeys, bytes[] withdrawal_credentials, bytes[] signatures, bytes32[] deposit_data_roots); |
| 16 | + |
| 17 | + function deposit(bytes[] calldata, bytes[] calldata, bytes[] calldata, bytes32[] calldata) external payable { |
| 18 | + ghost_depositSum += msg.value; |
| 19 | + } |
23 | 20 | } |
24 | 21 |
|
25 | | -contract SECVHandler is CommonBase, StdCheats, StdUtils { |
26 | | - SimpleETHContributionVault public contributionVault; |
| 22 | +contract SECVMock is SimpleETHContributionVault { |
| 23 | + constructor(address _safe, address eth2DepositContract) SimpleETHContributionVault(_safe, eth2DepositContract) {} |
| 24 | + |
| 25 | + /// @notice Mock depositValidator function |
| 26 | + function depositValidatorMock( |
| 27 | + bytes[] calldata pubkeys, |
| 28 | + bytes[] calldata withdrawal_credentials, |
| 29 | + bytes[] calldata signatures, |
| 30 | + bytes32[] calldata deposit_data_roots |
| 31 | + ) external payable { |
| 32 | + for (uint256 i = 0; i < 1;) { |
| 33 | + depositContract.deposit{value: msg.value}( |
| 34 | + pubkeys[i], withdrawal_credentials[i], signatures[i], deposit_data_roots[i] |
| 35 | + ); |
| 36 | + unchecked { |
| 37 | + i++; |
| 38 | + } |
| 39 | + } |
| 40 | + } |
| 41 | +} |
27 | 42 |
|
28 | | - uint256 public constant ETH_SUPPLY = 100_000 ether; |
| 43 | +contract SECVBoundedHandler is CommonBase, StdCheats, StdUtils { |
| 44 | + SECVMock public contributionVault; |
29 | 45 |
|
30 | | - uint256 public ghost_depositSum; |
31 | | - uint256 public ghost_rageQuitSum; |
| 46 | + uint256 public constant ETH_SUPPLY = 1_000_000 ether; |
32 | 47 |
|
33 | | - receive() external payable {} |
| 48 | + uint256 public ghost_depositSum; |
| 49 | + uint256 public ghost_rageQuitSum; |
34 | 50 |
|
35 | | - constructor(SimpleETHContributionVault vault) { |
36 | | - contributionVault = vault; |
37 | | - deal(address(this), ETH_SUPPLY); |
38 | | - } |
| 51 | + receive() external payable {} |
39 | 52 |
|
40 | | - function deposit(uint256 amount) external payable { |
41 | | - amount = bound(amount, 0, address(this).balance); |
42 | | - (bool _success,) = payable(contributionVault).call{value: amount}(""); |
43 | | - assert(_success); |
| 53 | + constructor(SECVMock vault) { |
| 54 | + contributionVault = vault; |
| 55 | + deal(address(this), ETH_SUPPLY); |
| 56 | + } |
44 | 57 |
|
45 | | - ghost_depositSum += amount; |
| 58 | + function deposit(uint256 amount) external payable { |
| 59 | + amount = bound(amount, 0, address(this).balance); |
| 60 | + (bool _success,) = payable(contributionVault).call{value: amount}(""); |
| 61 | + assert(_success); |
46 | 62 |
|
47 | | - } |
| 63 | + ghost_depositSum += amount; |
| 64 | + } |
48 | 65 |
|
49 | | - function rageQuit(uint256 amount) external payable { |
50 | | - amount = bound(amount, 0 , contributionVault.userBalances(address(this))); |
51 | | - contributionVault.rageQuit(address(this), amount); |
| 66 | + function rageQuit(uint256 amount) external payable { |
| 67 | + amount = bound(amount, 0, contributionVault.userBalances(address(this))); |
| 68 | + contributionVault.rageQuit(address(this), amount); |
52 | 69 |
|
53 | | - ghost_rageQuitSum += amount; |
54 | | - } |
| 70 | + ghost_rageQuitSum += amount; |
| 71 | + } |
| 72 | + |
| 73 | + function depositValidator(uint256 amount) external payable { |
| 74 | + amount = bound(amount, 0, address(contributionVault).balance); |
| 75 | + ( |
| 76 | + bytes[] memory pubkeys, |
| 77 | + bytes[] memory withdrawal_credentials, |
| 78 | + bytes[] memory signatures, |
| 79 | + bytes32[] memory deposit_data_roots |
| 80 | + ) = getETHValidatorData(); |
| 81 | + |
| 82 | + contributionVault.depositValidatorMock{value: amount}( |
| 83 | + pubkeys, withdrawal_credentials, signatures, deposit_data_roots |
| 84 | + ); |
| 85 | + } |
55 | 86 | } |
56 | 87 |
|
57 | 88 | contract SECVInvariant is Test { |
| 89 | + MockDepositContract public mockDepositContract; |
| 90 | + SECVMock public contributionVault; |
| 91 | + SECVBoundedHandler public handler; |
58 | 92 |
|
59 | | - MockDepositContract public mockDepositContract; |
60 | | - SimpleETHContributionVault public contributionVault; |
61 | | - SECVHandler public handler; |
62 | | - |
63 | | - address public safe; |
| 93 | + address public safe; |
64 | 94 |
|
65 | | - function setUp() public { |
66 | | - safe = makeAddr("safe"); |
| 95 | + function setUp() public { |
| 96 | + safe = makeAddr("safe"); |
67 | 97 |
|
68 | | - mockDepositContract = new MockDepositContract(); |
69 | | - contributionVault = new SimpleETHContributionVault( |
| 98 | + mockDepositContract = new MockDepositContract(); |
| 99 | + contributionVault = new SECVMock( |
70 | 100 | safe, |
71 | 101 | address(mockDepositContract) |
72 | 102 | ); |
73 | | - handler = new SECVHandler(contributionVault); |
| 103 | + handler = new SECVBoundedHandler(contributionVault); |
74 | 104 |
|
75 | | - targetContract(address(handler)); |
76 | | - } |
77 | | - |
78 | | - function invariant_balanceEqual() public { |
79 | | - assertEq( |
80 | | - handler.ETH_SUPPLY(), |
81 | | - address(handler).balance + contributionVault.userBalances(address(handler)) |
82 | | - ); |
83 | | - } |
| 105 | + targetContract(address(handler)); |
| 106 | + } |
84 | 107 |
|
85 | | - function invariant_vaultIsSolvent() public { |
86 | | - assertEq( |
87 | | - address(contributionVault).balance, |
88 | | - handler.ghost_depositSum() - handler.ghost_rageQuitSum() |
89 | | - ); |
90 | | - } |
| 108 | + /// @notice This invariant checks that the sum of balances in the handler, |
| 109 | + /// vault and mock deposit contract is equal |
| 110 | + function invariant_balanceEqual() public { |
| 111 | + assertEq( |
| 112 | + handler.ETH_SUPPLY(), |
| 113 | + address(handler).balance + contributionVault.userBalances(address(handler)) + address(mockDepositContract).balance |
| 114 | + ); |
| 115 | + } |
| 116 | + |
| 117 | + /// @notice This invariant checks that the vault is |
| 118 | + /// always solvent when a user ragequits. |
| 119 | + function invariant_vaultIsSolvent() public { |
| 120 | + assertEq( |
| 121 | + address(contributionVault).balance, |
| 122 | + handler.ghost_depositSum() + mockDepositContract.ghost_depositSum() - handler.ghost_rageQuitSum() |
| 123 | + ); |
| 124 | + } |
91 | 125 | } |
0 commit comments