diff --git a/.github/workflows/certora-prover.yml b/.github/workflows/certora-prover.yml
index f12e25c5a2..71d32962af 100644
--- a/.github/workflows/certora-prover.yml
+++ b/.github/workflows/certora-prover.yml
@@ -43,7 +43,7 @@ jobs:
distribution: temurin
java-version: '17'
- name: Install certora
- run: pip install certora-cli==4.13.1
+ run: pip install certora-cli
- name: Install solc
run: |
wget https://github.com/ethereum/solidity/releases/download/v0.8.12/solc-static-linux
diff --git a/README.md b/README.md
index e7dfc08802..82e56d0187 100644
--- a/README.md
+++ b/README.md
@@ -101,21 +101,19 @@ and/or
| Timelock | [Compound: `Timelock.sol`](https://github.com/compound-finance/compound-protocol/blob/a3214f67b73310d547e00fc578e8355911c9d376/contracts/Timelock.sol) | - | [`0xA6Db...0EAF`](https://etherscan.io/address/0xA6Db1A8C5a981d1536266D2a393c5F8dDb210EAF) | |
| Proxy Admin | [OpenZeppelin ProxyAdmin@4.7.1](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v4.7.1/contracts/proxy/transparent/ProxyAdmin.sol) | - | [`0x8b95...2444`](https://etherscan.io/address/0x8b9566AdA63B64d1E1dcF1418b43fd1433b72444) | |
-
-
-### M1 (Current Goerli Testnet Deployment)
+### M2 (Current Goerli Testnet Deployment)
| Name | Solidity | Proxy | Implementation | Notes |
| -------- | -------- | -------- | -------- | -------- |
-| StrategyManager | [`StrategyManager`](https://github.com/Layr-Labs/eigenlayer-contracts/blob/0139d6213927c0a7812578899ddd3dda58051928/src/contracts/core/StrategyManager.sol) | [`0x779...8E907`](https://goerli.etherscan.io/address/0x779d1b5315df083e3F9E94cB495983500bA8E907) | [`0x8676...0055`](https://goerli.etherscan.io/address/0x8676bb5f792ED407a237234Fe422aC6ed3540055) | Proxy: [OpenZeppelin TUP@4.7.1](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v4.7.1/contracts/proxy/transparent/TransparentUpgradeableProxy.sol) |
-| Strategy: stETH | [`StrategyBaseTVLLimits`](https://github.com/Layr-Labs/eigenlayer-contracts/blob/0139d6213927c0a7812578899ddd3dda58051928/src/contracts/strategies/StrategyBaseTVLLimits.sol) | [`0xB6...d14da`](https://goerli.etherscan.io/address/0xb613e78e2068d7489bb66419fb1cfa11275d14da) | [`0x81E9...F8ebA`](https://goerli.etherscan.io/address/0x81E94e16949AC397d508B5C2557a272faD2F8ebA) | Proxy: [OpenZeppelin TUP@4.7.1](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v4.7.1/contracts/proxy/transparent/TransparentUpgradeableProxy.sol) |
-| Strategy: rETH | [`StrategyBaseTVLLimits`](https://github.com/Layr-Labs/eigenlayer-contracts/blob/0139d6213927c0a7812578899ddd3dda58051928/src/contracts/strategies/StrategyBaseTVLLimits.sol) | [`0x8799...70b5`](https://goerli.etherscan.io/address/0x879944A8cB437a5f8061361f82A6d4EED59070b5) | [`0x81E9...F8ebA`](https://goerli.etherscan.io/address/0x81E94e16949AC397d508B5C2557a272faD2F8ebA) | Proxy: [OpenZeppelin TUP@4.7.1](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v4.7.1/contracts/proxy/transparent/TransparentUpgradeableProxy.sol) |
-| EigenPodManager | [`EigenPodManager`](https://github.com/Layr-Labs/eigenlayer-contracts/blob/0139d6213927c0a7812578899ddd3dda58051928/src/contracts/pods/EigenPodManager.sol) | [`0xa286b...df41`](https://goerli.etherscan.io/address/0xa286b84C96aF280a49Fe1F40B9627C2A2827df41) | [`0xdD09...901b`](https://goerli.etherscan.io/address/0xdD09d95bD25299EDBF4f33d76F84dBc77b0B901b) | Proxy: [OpenZeppelin TUP@4.7.1](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v4.7.1/contracts/proxy/transparent/TransparentUpgradeableProxy.sol) |
-| EigenPod (beacon) | [`EigenPod`](https://github.com/Layr-Labs/eigenlayer-contracts/blob/0139d6213927c0a7812578899ddd3dda58051928/src/contracts/pods/EigenPod.sol) | [`0x3093...C9a5`](https://goerli.etherscan.io/address/0x3093F3B560352F896F0e9567019902C9Aff8C9a5) | [`0x86bf...6CcA`](https://goerli.etherscan.io/address/0x86bf376E0C0c9c6D332E13422f35Aca75C106CcA) | - Beacon: [OpenZeppelin BeaconProxy@4.7.1](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v4.7.1/contracts/proxy/beacon/BeaconProxy.sol)
- Deployed pods use [UpgradableBeacon@4.7.1](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v4.7.1/contracts/proxy/beacon/UpgradeableBeacon.sol) |
-| DelayedWithdrawalRouter | [`DelayedWithdrawalRouter`](https://github.com/Layr-Labs/eigenlayer-contracts/blob/0139d6213927c0a7812578899ddd3dda58051928/src/contracts/pods/DelayedWithdrawalRouter.sol) | [`0x895...388f`](https://goerli.etherscan.io/address/0x89581561f1F98584F88b0d57c2180fb89225388f) | [`0x607...7fe`](https://goerli.etherscan.io/address/0x60700ade3Bf48C437a5D01b6Da8d7483cffa27fe) | Proxy: [OpenZeppelin TUP@4.7.1](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v4.7.1/contracts/proxy/transparent/TransparentUpgradeableProxy.sol) |
-| DelegationManager | [`DelegationManager`](https://github.com/Layr-Labs/eigenlayer-contracts/blob/0139d6213927c0a7812578899ddd3dda58051928/src/contracts/core/DelegationManager.sol) | [`0x1b7...Eb0a8`](https://goerli.etherscan.io/address/0x1b7b8F6b258f95Cf9596EabB9aa18B62940Eb0a8) | [`0x9b79...A99d`](https://goerli.etherscan.io/address/0x9b7980a32ceCe2Aa936DD2E43AF74af62581A99d) | Proxy: [OpenZeppelin TUP@4.7.1](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v4.7.1/contracts/proxy/transparent/TransparentUpgradeableProxy.sol) |
-| Slasher | [`Slasher`](https://github.com/Layr-Labs/eigenlayer-contracts/blob/0139d6213927c0a7812578899ddd3dda58051928/src/contracts/core/Slasher.sol) | [`0xD1...0C22`](https://goerli.etherscan.io/address/0xD11d60b669Ecf7bE10329726043B3ac07B380C22) | [`0x3865...8Be6`](https://goerli.etherscan.io/address/0x3865B5F5297f86c5295c7f818BAD1fA5286b8Be6) | Proxy: [OpenZeppelin TUP@4.7.1](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v4.7.1/contracts/proxy/transparent/TransparentUpgradeableProxy.sol) |
-| PauserRegistry | [`PauserRegistry`](https://github.com/Layr-Labs/eigenlayer-contracts/blob/0139d6213927c0a7812578899ddd3dda58051928/src/contracts/permissions/PauserRegistry.sol) | - | [`0x81E9...F8ebA`](https://goerli.etherscan.io/address/0x81E94e16949AC397d508B5C2557a272faD2F8ebA) | |
+| StrategyManager | [`StrategyManager`](https://github.com/Layr-Labs/eigenlayer-contracts/blob/goerli-m2-deployment/src/contracts/core/StrategyManager.sol) | [`0x779d...E907`](https://goerli.etherscan.io/address/0x779d1b5315df083e3F9E94cB495983500bA8E907) | [`0x8676...0055`](https://goerli.etherscan.io/address/0x8676bb5f792ED407a237234Fe422aC6ed3540055) | Proxy: [OpenZeppelin TUP@4.7.1](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v4.7.1/contracts/proxy/transparent/TransparentUpgradeableProxy.sol) |
+| Strategy: stETH | [`StrategyBaseTVLLimits`](https://github.com/Layr-Labs/eigenlayer-contracts/blob/goerli-m2-deployment/src/contracts/strategies/StrategyBaseTVLLimits.sol) | [`0xB613...14da`](https://goerli.etherscan.io/address/0xb613e78e2068d7489bb66419fb1cfa11275d14da) | [`0x81E9...8ebA`](https://goerli.etherscan.io/address/0x81E94e16949AC397d508B5C2557a272faD2F8ebA) | Proxy: [OpenZeppelin TUP@4.7.1](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v4.7.1/contracts/proxy/transparent/TransparentUpgradeableProxy.sol) |
+| Strategy: rETH | [`StrategyBaseTVLLimits`](https://github.com/Layr-Labs/eigenlayer-contracts/blob/goerli-m2-deployment/src/contracts/strategies/StrategyBaseTVLLimits.sol) | [`0x8799...70b5`](https://goerli.etherscan.io/address/0x879944A8cB437a5f8061361f82A6d4EED59070b5) | [`0x81E9...8ebA`](https://goerli.etherscan.io/address/0x81E94e16949AC397d508B5C2557a272faD2F8ebA) | Proxy: [OpenZeppelin TUP@4.7.1](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v4.7.1/contracts/proxy/transparent/TransparentUpgradeableProxy.sol) |
+| EigenPodManager | [`EigenPodManager`](https://github.com/Layr-Labs/eigenlayer-contracts/blob/goerli-m2-deployment/src/contracts/pods/EigenPodManager.sol) | [`0xa286...df41`](https://goerli.etherscan.io/address/0xa286b84C96aF280a49Fe1F40B9627C2A2827df41) | [`0xdD09...901b`](https://goerli.etherscan.io/address/0xdD09d95bD25299EDBF4f33d76F84dBc77b0B901b) | Proxy: [OpenZeppelin TUP@4.7.1](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v4.7.1/contracts/proxy/transparent/TransparentUpgradeableProxy.sol) |
+| EigenPod (beacon) | [`EigenPod`](https://github.com/Layr-Labs/eigenlayer-contracts/blob/goerli-m2-deployment/src/contracts/pods/EigenPod.sol) | [`0x3093...C9a5`](https://goerli.etherscan.io/address/0x3093F3B560352F896F0e9567019902C9Aff8C9a5) | [`0x86bf...6CcA`](https://goerli.etherscan.io/address/0x86bf376E0C0c9c6D332E13422f35Aca75C106CcA) | - Beacon: [OpenZeppelin BeaconProxy@4.7.1](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v4.7.1/contracts/proxy/beacon/BeaconProxy.sol)
- Deployed pods use [UpgradableBeacon@4.7.1](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v4.7.1/contracts/proxy/beacon/UpgradeableBeacon.sol) |
+| DelayedWithdrawalRouter | [`DelayedWithdrawalRouter`](https://github.com/Layr-Labs/eigenlayer-contracts/blob/goerli-m2-deployment/src/contracts/pods/DelayedWithdrawalRouter.sol) | [`0x8958...388f`](https://goerli.etherscan.io/address/0x89581561f1F98584F88b0d57c2180fb89225388f) | [`0x6070...27fe`](https://goerli.etherscan.io/address/0x60700ade3Bf48C437a5D01b6Da8d7483cffa27fe) | Proxy: [OpenZeppelin TUP@4.7.1](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v4.7.1/contracts/proxy/transparent/TransparentUpgradeableProxy.sol) |
+| DelegationManager | [`DelegationManager`](https://github.com/Layr-Labs/eigenlayer-contracts/blob/goerli-m2-deployment/src/contracts/core/DelegationManager.sol) | [`0x1b7b...b0a8`](https://goerli.etherscan.io/address/0x1b7b8F6b258f95Cf9596EabB9aa18B62940Eb0a8) | [`0x9b79...A99d`](https://goerli.etherscan.io/address/0x9b7980a32ceCe2Aa936DD2E43AF74af62581A99d) | Proxy: [OpenZeppelin TUP@4.7.1](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v4.7.1/contracts/proxy/transparent/TransparentUpgradeableProxy.sol) |
+| Slasher | [`Slasher`](https://github.com/Layr-Labs/eigenlayer-contracts/blob/goerli-m2-deployment/src/contracts/core/Slasher.sol) | [`0xD11d...0C22`](https://goerli.etherscan.io/address/0xD11d60b669Ecf7bE10329726043B3ac07B380C22) | [`0x3865...8Be6`](https://goerli.etherscan.io/address/0x3865B5F5297f86c5295c7f818BAD1fA5286b8Be6) | Proxy: [OpenZeppelin TUP@4.7.1](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v4.7.1/contracts/proxy/transparent/TransparentUpgradeableProxy.sol) |
+| PauserRegistry | [`PauserRegistry`](https://github.com/Layr-Labs/eigenlayer-contracts/blob/goerli-m2-deployment/src/contracts/permissions/PauserRegistry.sol) | - | [`0x2588...0010`](https://goerli.etherscan.io/address/0x2588f9299871a519883D92dcd5092B4A0Cf70010) | |
| Timelock | [Compound: `Timelock.sol`](https://github.com/compound-finance/compound-protocol/blob/a3214f67b73310d547e00fc578e8355911c9d376/contracts/Timelock.sol) | - | [`0xa7e7...796e`](https://goerli.etherscan.io/address/0xa7e72a0564ebf25fa082fc27020225edeaf1796e) | |
| Proxy Admin | [OpenZeppelin ProxyAdmin@4.7.1](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v4.7.1/contracts/proxy/transparent/ProxyAdmin.sol) | - | [`0x28ce...02e2`](https://goerli.etherscan.io/address/0x28ceac2ff82B2E00166e46636e2A4818C29902e2) | |
diff --git a/certora/harnesses/EigenPodHarness.sol b/certora/harnesses/EigenPodHarness.sol
new file mode 100644
index 0000000000..e645c1f4b1
--- /dev/null
+++ b/certora/harnesses/EigenPodHarness.sol
@@ -0,0 +1,40 @@
+// SPDX-License-Identifier: BUSL-1.1
+pragma solidity =0.8.12;
+
+import "../munged/pods/EigenPod.sol";
+
+contract EigenPodHarness is EigenPod {
+
+ constructor(
+ IETHPOSDeposit _ethPOS,
+ IDelayedWithdrawalRouter _delayedWithdrawalRouter,
+ IEigenPodManager _eigenPodManager,
+ uint64 _MAX_RESTAKED_BALANCE_GWEI_PER_VALIDATOR,
+ uint64 _GENESIS_TIME
+ )
+ EigenPod(_ethPOS, _delayedWithdrawalRouter, _eigenPodManager, _MAX_RESTAKED_BALANCE_GWEI_PER_VALIDATOR, _GENESIS_TIME) {}
+
+ function get_validatorIndex(bytes32 pubkeyHash) public view returns (uint64) {
+ return _validatorPubkeyHashToInfo[pubkeyHash].validatorIndex;
+ }
+
+ function get_restakedBalanceGwei(bytes32 pubkeyHash) public view returns (uint64) {
+ return _validatorPubkeyHashToInfo[pubkeyHash].restakedBalanceGwei;
+ }
+
+ function get_mostRecentBalanceUpdateTimestamp(bytes32 pubkeyHash) public view returns (uint64) {
+ return _validatorPubkeyHashToInfo[pubkeyHash].mostRecentBalanceUpdateTimestamp;
+ }
+
+ function get_podOwnerShares() public view returns (int256) {
+ return eigenPodManager.podOwnerShares(podOwner);
+ }
+
+ function get_withdrawableRestakedExecutionLayerGwei() public view returns (uint256) {
+ return withdrawableRestakedExecutionLayerGwei;
+ }
+
+ function get_ETH_Balance() public view returns (uint256) {
+ return address(this).balance;
+ }
+}
\ No newline at end of file
diff --git a/certora/harnesses/EigenPodManagerHarness.sol b/certora/harnesses/EigenPodManagerHarness.sol
index 62fd0cf667..6a43aad818 100644
--- a/certora/harnesses/EigenPodManagerHarness.sol
+++ b/certora/harnesses/EigenPodManagerHarness.sol
@@ -17,4 +17,8 @@ contract EigenPodManagerHarness is EigenPodManager {
function get_podOwnerShares(address podOwner) public view returns (int256) {
return podOwnerShares[podOwner];
}
+
+ function get_podByOwner(address podOwner) public view returns (IEigenPod) {
+ return ownerToPod[podOwner];
+ }
}
\ No newline at end of file
diff --git a/certora/scripts/core/verifyDelegationManager.sh b/certora/scripts/core/verifyDelegationManager.sh
index 69d1ab05a8..59cd344a01 100644
--- a/certora/scripts/core/verifyDelegationManager.sh
+++ b/certora/scripts/core/verifyDelegationManager.sh
@@ -13,6 +13,7 @@ certoraRun certora/harnesses/DelegationManagerHarness.sol \
--optimistic_loop \
--prover_args '-optimisticFallback true' \
--optimistic_hashing \
+ --parametric_contracts DelegationManagerHarness \
$RULE \
--loop_iter 2 \
--packages @openzeppelin=lib/openzeppelin-contracts @openzeppelin-upgrades=lib/openzeppelin-contracts-upgradeable \
diff --git a/certora/scripts/core/verifySlasher.sh b/certora/scripts/core/verifySlasher.sh
deleted file mode 100644
index f93d702741..0000000000
--- a/certora/scripts/core/verifySlasher.sh
+++ /dev/null
@@ -1,19 +0,0 @@
-if [[ "$2" ]]
-then
- RULE="--rule $2"
-fi
-
-solc-select use 0.8.12
-
-certoraRun certora/harnesses/SlasherHarness.sol \
- lib/openzeppelin-contracts/contracts/token/ERC20/ERC20.sol \
- certora/munged/pods/EigenPodManager.sol certora/munged/pods/EigenPod.sol certora/munged/strategies/StrategyBase.sol certora/munged/core/DelegationManager.sol \
- certora/munged/core/StrategyManager.sol certora/munged/permissions/PauserRegistry.sol \
- --verify SlasherHarness:certora/specs/core/Slasher.spec \
- --optimistic_loop \
- --prover_args '-optimisticFallback true -recursionErrorAsAssert false -recursionEntryLimit 2' \
- --loop_iter 2 \
- --link SlasherHarness:delegation=DelegationManager \
- $RULE \
- --packages @openzeppelin=lib/openzeppelin-contracts @openzeppelin-upgrades=lib/openzeppelin-contracts-upgradeable \
- --msg "Slasher $1 $2" \
diff --git a/certora/scripts/core/verifyStrategyManager.sh b/certora/scripts/core/verifyStrategyManager.sh
index bbcb553153..c8f5fbf976 100644
--- a/certora/scripts/core/verifyStrategyManager.sh
+++ b/certora/scripts/core/verifyStrategyManager.sh
@@ -14,6 +14,7 @@ certoraRun certora/harnesses/StrategyManagerHarness.sol \
--optimistic_loop \
--prover_args '-optimisticFallback true' \
--optimistic_hashing \
+ --parametric_contracts StrategyManagerHarness \
$RULE \
--loop_iter 2 \
--packages @openzeppelin=lib/openzeppelin-contracts @openzeppelin-upgrades=lib/openzeppelin-contracts-upgradeable \
diff --git a/certora/scripts/libraries/verifyStructuredLinkedList.sh b/certora/scripts/libraries/verifyStructuredLinkedList.sh
index 1d2d0ce8f6..d6d25bf35f 100644
--- a/certora/scripts/libraries/verifyStructuredLinkedList.sh
+++ b/certora/scripts/libraries/verifyStructuredLinkedList.sh
@@ -9,6 +9,7 @@ certoraRun certora/harnesses/StructuredLinkedListHarness.sol \
--verify StructuredLinkedListHarness:certora/specs/libraries/StructuredLinkedList.spec \
--optimistic_loop \
--prover_args '-optimisticFallback true' \
+ --parametric_contracts StructuredLinkedListHarness \
$RULE \
--rule_sanity \
--loop_iter 3 \
diff --git a/certora/scripts/pods/verifyEigenPod.sh b/certora/scripts/pods/verifyEigenPod.sh
new file mode 100644
index 0000000000..a9549d5a6a
--- /dev/null
+++ b/certora/scripts/pods/verifyEigenPod.sh
@@ -0,0 +1,23 @@
+if [[ "$2" ]]
+then
+ RULE="--rule $2"
+fi
+
+solc-select use 0.8.12
+
+certoraRun certora/harnesses/EigenPodHarness.sol \
+ certora/munged/core/DelegationManager.sol certora/munged/pods/EigenPodManager.sol \
+ certora/munged/core/Slasher.sol certora/munged/permissions/PauserRegistry.sol \
+ certora/munged/core/StrategyManager.sol \
+ certora/munged/strategies/StrategyBase.sol \
+ lib/openzeppelin-contracts/contracts/token/ERC20/ERC20.sol \
+ lib/openzeppelin-contracts/contracts/mocks/ERC1271WalletMock.sol \
+ --verify EigenPodHarness:certora/specs/pods/EigenPod.spec \
+ --optimistic_loop \
+ --prover_args '-recursionEntryLimit 3' \
+ --optimistic_hashing \
+ --parametric_contracts EigenPodHarness \
+ $RULE \
+ --loop_iter 1 \
+ --packages @openzeppelin=lib/openzeppelin-contracts @openzeppelin-upgrades=lib/openzeppelin-contracts-upgradeable \
+ --msg "EigenPod $1 $2" \
diff --git a/certora/scripts/pods/verifyEigenPodManager.sh b/certora/scripts/pods/verifyEigenPodManager.sh
index 6a504dce1e..2a623e9338 100644
--- a/certora/scripts/pods/verifyEigenPodManager.sh
+++ b/certora/scripts/pods/verifyEigenPodManager.sh
@@ -12,6 +12,7 @@ certoraRun certora/harnesses/EigenPodManagerHarness.sol \
--optimistic_loop \
--prover_args '-optimisticFallback true' \
--optimistic_hashing \
+ --parametric_contracts EigenPodManagerHarness \
$RULE \
--loop_iter 3 \
--packages @openzeppelin=lib/openzeppelin-contracts @openzeppelin-upgrades=lib/openzeppelin-contracts-upgradeable \
diff --git a/certora/scripts/strategies/verifyStrategyBase.sh b/certora/scripts/strategies/verifyStrategyBase.sh
index 27e864e8ba..220720f13a 100644
--- a/certora/scripts/strategies/verifyStrategyBase.sh
+++ b/certora/scripts/strategies/verifyStrategyBase.sh
@@ -16,5 +16,6 @@ certoraRun certora/munged/strategies/StrategyBase.sol \
--loop_iter 3 \
--packages @openzeppelin=lib/openzeppelin-contracts @openzeppelin-upgrades=lib/openzeppelin-contracts-upgradeable \
--link StrategyBase:strategyManager=StrategyManager \
+ --parametric_contracts StrategyBase \
$RULE \
--msg "StrategyBase $1 $2" \
\ No newline at end of file
diff --git a/certora/specs/pods/EigenPod.spec b/certora/specs/pods/EigenPod.spec
new file mode 100644
index 0000000000..019c2f0d1f
--- /dev/null
+++ b/certora/specs/pods/EigenPod.spec
@@ -0,0 +1,245 @@
+
+methods {
+ // Internal, NONDET-summarized EigenPod library functions
+ function _.verifyValidatorFields(bytes32, bytes32[] calldata, bytes calldata, uint40) internal => NONDET;
+ function _.verifyValidatorBalance(bytes32, bytes32, bytes calldata, uint40) internal => NONDET;
+ function _.verifyStateRootAgainstLatestBlockRoot(bytes32, bytes32, bytes calldata) internal => NONDET;
+ function _.verifyWithdrawal(bytes32, bytes32[] calldata, BeaconChainProofs.WithdrawalProof calldata) internal => NONDET;
+
+ // Internal, NONDET-summarized "send ETH" function -- unsound summary used to avoid HAVOC behavior
+ // when sending ETH using `Address.sendValue()`
+ function _._sendETH(address recipient, uint256 amountWei) internal => NONDET;
+
+ // summarize the deployment of EigenPods to avoid default, HAVOC behavior
+ function _.deploy(uint256, bytes32, bytes memory bytecode) internal => NONDET;
+
+ //// External Calls
+ // external calls to DelegationManager
+ function _.undelegate(address) external => DISPATCHER(true);
+ function _.decreaseDelegatedShares(address,address,uint256) external => DISPATCHER(true);
+ function _.increaseDelegatedShares(address,address,uint256) external => DISPATCHER(true);
+
+ // external calls from DelegationManager to ServiceManager
+ function _.updateStakes(address[]) external => NONDET;
+
+ // external calls to Slasher
+ function _.isFrozen(address) external => DISPATCHER(true);
+ function _.canWithdraw(address,uint32,uint256) external => DISPATCHER(true);
+ function _.recordStakeUpdate(address,uint32,uint32,uint256) external => NONDET;
+
+ // external calls to StrategyManager
+ function _.getDeposits(address) external => DISPATCHER(true);
+ function _.slasher() external => DISPATCHER(true);
+ function _.addShares(address,address,uint256) external => DISPATCHER(true);
+ function _.removeShares(address,address,uint256) external => DISPATCHER(true);
+ function _.withdrawSharesAsTokens(address, address, uint256, address) external => DISPATCHER(true);
+ function _.migrateQueuedWithdrawal(IStrategyManager.DeprecatedStruct_QueuedWithdrawal) external => NONDET;
+
+ // external calls to Strategy contracts
+ function _.deposit(address, uint256) external => NONDET;
+ function _.withdraw(address, address, uint256) external => NONDET;
+
+ // external calls to EigenPodManager
+ function _.addShares(address,uint256) external => DISPATCHER(true);
+ function _.removeShares(address,uint256) external => DISPATCHER(true);
+ function _.withdrawSharesAsTokens(address, address, uint256) external => DISPATCHER(true);
+ function _.podOwnerShares(address) external => DISPATCHER(true);
+
+ // external calls to EigenPod
+ function _.withdrawRestakedBeaconChainETH(address,uint256) external => DISPATCHER(true);
+ function _.stake(bytes, bytes, bytes32) external => DISPATCHER(true);
+ function _.initialize(address) external => DISPATCHER(true);
+
+ // external calls to ETH2Deposit contract
+ function _.deposit(bytes, bytes, bytes, bytes32) external => NONDET;
+
+ // external calls to DelayedWithdrawalRouter (from EigenPod)
+ function _.createDelayedWithdrawal(address, address) external => DISPATCHER(true);
+
+ // external calls to PauserRegistry
+ function _.isPauser(address) external => DISPATCHER(true);
+ function _.unpauser() external => DISPATCHER(true);
+
+ // external calls to ERC20 token
+ function _.transfer(address, uint256) external => DISPATCHER(true);
+ function _.transferFrom(address, address, uint256) external => DISPATCHER(true);
+ function _.approve(address, uint256) external => DISPATCHER(true);
+
+ // envfree functions
+ function MAX_RESTAKED_BALANCE_GWEI_PER_VALIDATOR() external returns (uint64) envfree;
+ function withdrawableRestakedExecutionLayerGwei() external returns (uint64) envfree;
+ function nonBeaconChainETHBalanceWei() external returns (uint256) envfree;
+ function eigenPodManager() external returns (address) envfree;
+ function podOwner() external returns (address) envfree;
+ function hasRestaked() external returns (bool) envfree;
+ function mostRecentWithdrawalTimestamp() external returns (uint64) envfree;
+ function validatorPubkeyHashToInfo(bytes32 validatorPubkeyHash) external returns (IEigenPod.ValidatorInfo) envfree;
+ function provenWithdrawal(bytes32 validatorPubkeyHash, uint64 slot) external returns (bool) envfree;
+ function validatorStatus(bytes32 pubkeyHash) external returns (IEigenPod.VALIDATOR_STATUS) envfree;
+ function delayedWithdrawalRouter() external returns (address) envfree;
+ function nonBeaconChainETHBalanceWei() external returns (uint256) envfree;
+
+ // harnessed functions
+ function get_validatorIndex(bytes32 pubkeyHash) external returns (uint64) envfree;
+ function get_restakedBalanceGwei(bytes32 pubkeyHash) external returns (uint64) envfree;
+ function get_mostRecentBalanceUpdateTimestamp(bytes32 pubkeyHash) external returns (uint64) envfree;
+ function get_podOwnerShares() external returns (int256) envfree;
+ function get_withdrawableRestakedExecutionLayerGwei() external returns (uint256) envfree;
+ function get_ETH_Balance() external returns (uint256) envfree;
+}
+
+// defines the allowed validator status transitions
+definition validatorStatusTransitionAllowed(IEigenPod.VALIDATOR_STATUS statusBefore, IEigenPod.VALIDATOR_STATUS statusAfter) returns bool =
+ (statusBefore == IEigenPod.VALIDATOR_STATUS.INACTIVE && statusAfter == IEigenPod.VALIDATOR_STATUS.ACTIVE)
+ || (statusBefore == IEigenPod.VALIDATOR_STATUS.ACTIVE && statusAfter == IEigenPod.VALIDATOR_STATUS.WITHDRAWN);
+
+// verifies that only the 2 allowed transitions of validator status occur
+rule validatorStatusTransitionsCorrect(bytes32 pubkeyHash) {
+ IEigenPod.VALIDATOR_STATUS statusBefore = validatorStatus(pubkeyHash);
+ method f;
+ env e;
+ calldataarg args;
+ f(e,args);
+ IEigenPod.VALIDATOR_STATUS statusAfter = validatorStatus(pubkeyHash);
+ assert(
+ (statusBefore == statusAfter)
+ || validatorStatusTransitionAllowed(statusBefore, statusAfter),
+ "disallowed validator status transition occurred"
+ );
+}
+
+// verifies that _validatorPubkeyHashToInfo[validatorPubkeyHash].mostRecentBalanceUpdateTimestamp can ONLY increase (or remain the same)
+rule mostRecentBalanceUpdateTimestampOnlyIncreases(bytes32 validatorPubkeyHash) {
+ IEigenPod.ValidatorInfo validatorInfoBefore = validatorPubkeyHashToInfo(validatorPubkeyHash);
+ method f;
+ env e;
+ calldataarg args;
+ f(e,args);
+ IEigenPod.ValidatorInfo validatorInfoAfter = validatorPubkeyHashToInfo(validatorPubkeyHash);
+ assert(validatorInfoAfter.mostRecentBalanceUpdateTimestamp >= validatorInfoBefore.mostRecentBalanceUpdateTimestamp,
+ "mostRecentBalanceUpdateTimestamp decreased");
+}
+
+// verifies that if a validator is marked as 'INACTIVE', then it has no other entries set in its ValidatorInfo
+invariant inactiveValidatorsHaveEmptyInfo(bytes32 pubkeyHash)
+ (validatorStatus(pubkeyHash) == IEigenPod.VALIDATOR_STATUS.INACTIVE) => (
+ get_validatorIndex(pubkeyHash) == 0
+ && get_restakedBalanceGwei(pubkeyHash) == 0
+ && get_mostRecentBalanceUpdateTimestamp(pubkeyHash) == 0);
+
+// verifies that _validatorPubkeyHashToInfo[validatorPubkeyHash].validatorIndex can be set initially but otherwise can't change
+// this can be understood as the only allowed transitions of index being of the form: 0 => anything (otherwise the index must stay the same)
+rule validatorIndexSetOnlyOnce(bytes32 pubkeyHash) {
+ requireInvariant inactiveValidatorsHaveEmptyInfo(pubkeyHash);
+ uint64 validatorIndexBefore = get_validatorIndex(pubkeyHash);
+ // perform arbitrary function call
+ method f;
+ env e;
+ calldataarg args;
+ f(e,args);
+ uint64 validatorIndexAfter = get_validatorIndex(pubkeyHash);
+ assert(validatorIndexBefore == 0 || validatorIndexAfter == validatorIndexBefore,
+ "validator index modified from nonzero value");
+}
+
+// verifies that once a validator has its status set to WITHDRAWN, its ‘restakedBalanceGwei’ is *and always remains* zero
+invariant withdrawnValidatorsHaveZeroRestakedGwei(bytes32 pubkeyHash)
+ (validatorStatus(pubkeyHash) == IEigenPod.VALIDATOR_STATUS.INACTIVE) =>
+ (get_restakedBalanceGwei(pubkeyHash) == 0);
+
+
+// // TODO: see if this draft rule can be salvaged
+// // draft rule to capture the following behavior (or at least most of it):
+// // The core invariant that ought to be maintained across the EPM and the EPs is that
+// // podOwnerShares[podOwner] + sum(sharesInQueuedWithdrawals) =
+// // sum(_validatorPubkeyHashToInfo[validatorPubkeyHash].restakedBalanceGwei) + withdrawableRestakedExecutionLayerGwei
+
+// // idea: if we ignore shares in queued withdrawals and rearrange, then we have:
+// // sum(_validatorPubkeyHashToInfo[validatorPubkeyHash].restakedBalanceGwei) =
+// // EigenPodManager.podOwnerShares(podOwner) - withdrawableRestakedExecutionLayerGwei
+// // we can track changes to the '_validatorPubkeyHashToInfo' mapping and check this with ghost variables
+
+// based on Certora's example here https://github.com/Certora/Tutorials/blob/michael/ethcc/EthCC/Ghosts/ghostTest.spec
+ghost mathint sumOfValidatorRestakedbalancesWei {
+ // NOTE: this commented out line is broken, as calling functions in axioms is currently disallowed, but this is what we'd run ideally.
+ // init_state axiom sumOfValidatorRestakedbalancesWei == to_mathint(get_podOwnerShares()) - to_mathint(get_withdrawableRestakedExecutionLayerGwei() * 1000000000);
+
+ // since both of these variables are zero at construction, just set the ghost to zero in the axiom
+ init_state axiom sumOfValidatorRestakedbalancesWei == 0;
+}
+
+hook Sstore _validatorPubkeyHashToInfo[KEY bytes32 validatorPubkeyHash].restakedBalanceGwei uint64 newValue (uint64 oldValue) STORAGE {
+ sumOfValidatorRestakedbalancesWei = (
+ sumOfValidatorRestakedbalancesWei +
+ to_mathint(newValue) * 1000000000 -
+ to_mathint(oldValue) * 1000000000
+ );
+}
+
+rule consistentAccounting() {
+ // fetch info before call
+ int256 podOwnerSharesBefore = get_podOwnerShares();
+ uint256 withdrawableRestakedExecutionLayerGweiBefore = get_withdrawableRestakedExecutionLayerGwei();
+ uint256 eigenPodBalanceBefore = get_ETH_Balance();
+ // filter down to valid pre-states
+ require(sumOfValidatorRestakedbalancesWei ==
+ to_mathint(podOwnerSharesBefore) - to_mathint(withdrawableRestakedExecutionLayerGweiBefore));
+
+ // perform arbitrary function call
+ method f;
+ env e;
+ calldataarg args;
+ f(e,args);
+
+ // fetch info after call
+ int256 podOwnerSharesAfter = get_podOwnerShares();
+ uint256 withdrawableRestakedExecutionLayerGweiAfter = get_withdrawableRestakedExecutionLayerGwei();
+ uint256 eigenPodBalanceAfter = get_ETH_Balance();
+ /**
+ * handling for weird, unrealistic edge case where calling `initialize` causes the pod owner to change, so the
+ * call to `get_podOwnerShares` queries the shares for a different address.
+ * calling `initialize` should *not* change user shares, so it is unrealistic to simulate it doing so.
+ */
+ if (f.selector == sig:initialize(address).selector) {
+ podOwnerSharesAfter = podOwnerSharesBefore;
+ }
+ // check post-state
+ // TODO: this check is still broken for `withdrawRestakedBeaconChainETH` since it does a low-level call to transfer the ETH, which triggers optimistic fallback dispatching
+ // special handling for one function
+ if (f.selector == sig:withdrawRestakedBeaconChainETH(address,uint256).selector) {
+ /* TODO: un-comment this once the dispatching is handled correctly
+ assert(sumOfValidatorRestakedbalancesWei ==
+ to_mathint(podOwnerSharesAfter) - to_mathint(withdrawableRestakedExecutionLayerGweiAfter)
+ // adjustment term for the ETH balance of the contract changing
+ + to_mathint(eigenPodBalanceBefore) - to_mathint(eigenPodBalanceAfter),
+ "invalid post-state");
+ */
+ // TODO: delete this once the above is salvaged (was added since CVL forbids empty blocks)
+ assert(true);
+ // outside of special case, we don't need the adjustment term
+ } else {
+ assert(sumOfValidatorRestakedbalancesWei ==
+ to_mathint(podOwnerSharesAfter) - to_mathint(withdrawableRestakedExecutionLayerGweiAfter),
+ "invalid post-state");
+ }
+}
+
+/*
+rule baseInvariant() {
+ int256 podOwnerSharesBefore = get_podOwnerShares();
+ // perform arbitrary function call
+ method f;
+ env e;
+ calldataarg args;
+ f(e,args);
+ int256 podOwnerSharesAfter = get_podOwnerShares();
+ mathint podOwnerSharesDelta = podOwnerSharesAfter - podOwnerSharesBefore;
+ assert(sumOfValidatorRestakedbalancesWei == podOwnerSharesDelta - to_mathint(get_withdrawableRestakedExecutionLayerGwei()),
+ "base invariant violated");
+}
+
+invariant consistentAccounting() {
+ sumOfValidatorRestakedbalancesWei ==
+ to_mathint(get_withdrawableRestakedExecutionLayerGwei()) - to_mathint(get_withdrawableRestakedExecutionLayerGwei());
+}
+*/
\ No newline at end of file
diff --git a/certora/specs/pods/EigenPodManager.spec b/certora/specs/pods/EigenPodManager.spec
index b7a2802fac..8d405c5fb5 100644
--- a/certora/specs/pods/EigenPodManager.spec
+++ b/certora/specs/pods/EigenPodManager.spec
@@ -35,11 +35,65 @@ methods {
function _.isPauser(address) external => DISPATCHER(true);
function _.unpauser() external => DISPATCHER(true);
- // TODO: envfree functions
+ // envfree functions
+ function ownerToPod(address podOwner) external returns (address) envfree;
+ function getPod(address podOwner) external returns (address) envfree;
+ function ethPOS() external returns (address) envfree;
+ function eigenPodBeacon() external returns (address) envfree;
+ function beaconChainOracle() external returns (address) envfree;
+ function getBlockRootAtTimestamp(uint64 timestamp) external returns (bytes32) envfree;
+ function strategyManager() external returns (address) envfree;
+ function slasher() external returns (address) envfree;
+ function hasPod(address podOwner) external returns (bool) envfree;
+ function numPods() external returns (uint256) envfree;
+ function maxPods() external returns (uint256) envfree;
+ function podOwnerShares(address podOwner) external returns (int256) envfree;
+ function beaconChainETHStrategy() external returns (address) envfree;
// harnessed functions
function get_podOwnerShares(address) external returns (int256) envfree;
+ function get_podByOwner(address) external returns (address) envfree;
}
+// verifies that podOwnerShares[podOwner] is never a non-whole Gwei amount
invariant podOwnerSharesAlwaysWholeGweiAmount(address podOwner)
- get_podOwnerShares(podOwner) % 1000000000 == 0;
\ No newline at end of file
+ get_podOwnerShares(podOwner) % 1000000000 == 0;
+
+// verifies that ownerToPod[podOwner] is set once (when podOwner deploys a pod), and can otherwise never be updated
+rule podAddressNeverChanges(address podOwner) {
+ address podAddressBefore = get_podByOwner(podOwner);
+ // perform arbitrary function call
+ method f;
+ env e;
+ calldataarg args;
+ f(e,args);
+ address podAddressAfter = get_podByOwner(podOwner);
+ assert(podAddressBefore == 0 || podAddressBefore == podAddressAfter,
+ "pod address changed after being set!");
+}
+
+// verifies that podOwnerShares[podOwner] can become negative (i.e. go from zero/positive to negative)
+// ONLY as a result of a call to `recordBeaconChainETHBalanceUpdate`
+rule limitationOnNegativeShares(address podOwner) {
+ int256 podOwnerSharesBefore = get_podOwnerShares(podOwner);
+ // perform arbitrary function call
+ method f;
+ env e;
+ calldataarg args;
+ f(e,args);
+ int256 podOwnerSharesAfter = get_podOwnerShares(podOwner);
+ if (podOwnerSharesAfter < 0) {
+ if (podOwnerSharesBefore >= 0) {
+ assert(f.selector == sig:recordBeaconChainETHBalanceUpdate(address, int256).selector,
+ "pod owner shares became negative from calling an unqualified function!");
+ } else {
+ assert(
+ (podOwnerSharesAfter >= podOwnerSharesBefore) ||
+ (f.selector == sig:recordBeaconChainETHBalanceUpdate(address, int256).selector),
+ "pod owner had negative shares decrease inappropriately"
+ );
+ }
+ }
+ // need this line to keep the prover happy :upside_down_face:
+ assert(true);
+}
diff --git a/docs/README.md b/docs/README.md
index 503a9df68d..de41946e10 100644
--- a/docs/README.md
+++ b/docs/README.md
@@ -1,6 +1,6 @@
## EigenLayer M2 Docs
-**EigenLayer M2 is a testnet-only release** that extends the functionality of EigenLayer M1 (which is live on both Goerli and mainnet).
+**EigenLayer M2** extends the functionality of EigenLayer M1 (which is live on both Goerli and mainnet). M2 is currently on the Goerli testnet, and will eventually be released on mainnet.
M1 enables very basic restaking: users that stake ETH natively or with a liquid staking token can opt-in to the M1 smart contracts, which currently support two basic operations: deposits and withdrawals.
@@ -10,7 +10,6 @@ M2 adds several features, the most important of which is the basic support neede
* Stakers can delegate their stake to a single operator
* Native ETH restaking is now fully featured, using beacon chain state proofs to validate withdrawal credentials, validator balances, and validator exits
* Proofs are supported by beacon chain headers provided by an oracle (See [`EigenPodManager` docs](./core/EigenPodManager.md) for more info)
-* TODO - multiquorums
### System Components
@@ -58,6 +57,6 @@ See full documentation in [`/core/DelegationManager.md`](./core/DelegationManage
| File | Type | Proxy? | Goerli |
| -------- | -------- | -------- | -------- |
-| [`Slasher.sol`](../src/contracts/core/Slasher.sol) | Singleton | Transparent proxy | TODO |
+| [`Slasher.sol`](../src/contracts/core/Slasher.sol) | - | - | - |
-The `Slasher` is deployed, but will remain completely paused during M2. Its design is not finalized.
\ No newline at end of file
+The `Slasher` is deployed, but will remain completely paused/unusable during M2. No contracts interact with it, and its design is not finalized.
\ No newline at end of file
diff --git a/docs/core/DelegationManager.md b/docs/core/DelegationManager.md
index 7f5ee2f079..f683685b54 100644
--- a/docs/core/DelegationManager.md
+++ b/docs/core/DelegationManager.md
@@ -19,7 +19,6 @@ This document organizes methods according to the following themes (click each to
* [Delegating to an Operator](#delegating-to-an-operator)
* [Undelegating and Withdrawing](#undelegating-and-withdrawing)
* [Accounting](#accounting)
-* [System Configuration](#system-configuration)
#### Important state variables
@@ -380,27 +379,4 @@ Called by the `EigenPodManager` when a Staker's shares decrease. This method is
* This method is a no-op if the Staker is not delegated to an Operator.
*Requirements*:
-* Caller MUST be either the `StrategyManager` or `EigenPodManager` (although the `StrategyManager` doesn't use this method)
-
----
-
-### System Configuration
-
-#### `setWithdrawalDelayBlocks`
-
-```solidity
-function setWithdrawalDelayBlocks(
- uint256 newWithdrawalDelayBlocks
-)
- external
- onlyOwner
-```
-
-Allows the `owner` to update the number of blocks that must pass before a withdrawal can be completed.
-
-*Effects*:
-* Updates `DelegationManager.withdrawalDelayBlocks`
-
-*Requirements*:
-* Caller MUST be the `owner`
-* `newWithdrawalDelayBlocks` MUST NOT be greater than `MAX_WITHDRAWAL_DELAY_BLOCKS` (50400)
+* Caller MUST be either the `StrategyManager` or `EigenPodManager` (although the `StrategyManager` doesn't use this method)
\ No newline at end of file
diff --git a/src/contracts/core/DelegationManager.sol b/src/contracts/core/DelegationManager.sol
index e47e687d65..fe9cde3d7a 100644
--- a/src/contracts/core/DelegationManager.sol
+++ b/src/contracts/core/DelegationManager.sol
@@ -83,18 +83,6 @@ contract DelegationManager is Initializable, OwnableUpgradeable, Pausable, Deleg
EXTERNAL FUNCTIONS
*******************************************************************************/
- /**
- * @notice Sets the address of the stakeRegistry
- * @param _stakeRegistry is the address of the StakeRegistry contract to call for stake updates when operator shares are changed
- * @dev Only callable once
- */
- function setStakeRegistry(IStakeRegistryStub _stakeRegistry) external onlyOwner {
- require(address(stakeRegistry) == address(0), "DelegationManager.setStakeRegistry: stakeRegistry already set");
- require(address(_stakeRegistry) != address(0), "DelegationManager.setStakeRegistry: stakeRegistry cannot be zero address");
- stakeRegistry = _stakeRegistry;
- emit StakeRegistrySet(_stakeRegistry);
- }
-
/**
* @notice Registers the caller as an operator in EigenLayer.
* @param registeringOperatorDetails is the `OperatorDetails` for the operator.
@@ -403,9 +391,6 @@ contract DelegationManager is Initializable, OwnableUpgradeable, Pausable, Deleg
// add strategy shares to delegate's shares
_increaseOperatorShares({operator: operator, staker: staker, strategy: strategy, shares: shares});
-
- // push the operator's new stake to the StakeRegistry
- _pushOperatorStakeUpdate(operator);
}
}
@@ -434,9 +419,6 @@ contract DelegationManager is Initializable, OwnableUpgradeable, Pausable, Deleg
strategy: strategy,
shares: shares
});
-
- // push the operator's new stake to the StakeRegistry
- _pushOperatorStakeUpdate(operator);
}
}
@@ -542,9 +524,6 @@ contract DelegationManager is Initializable, OwnableUpgradeable, Pausable, Deleg
unchecked { ++i; }
}
-
- // push the operator's new stake to the StakeRegistry
- _pushOperatorStakeUpdate(operator);
}
/**
@@ -624,9 +603,6 @@ contract DelegationManager is Initializable, OwnableUpgradeable, Pausable, Deleg
strategy: withdrawal.strategies[i],
shares: increaseInDelegateableShares
});
-
- // push the operator's new stake to the StakeRegistry
- _pushOperatorStakeUpdate(podOwnerOperator);
}
} else {
strategyManager.addShares(msg.sender, withdrawal.strategies[i], withdrawal.shares[i]);
@@ -643,8 +619,6 @@ contract DelegationManager is Initializable, OwnableUpgradeable, Pausable, Deleg
}
unchecked { ++i; }
}
- // push the operator's new stake to the StakeRegistry
- _pushOperatorStakeUpdate(currentOperator);
}
emit WithdrawalCompleted(withdrawalRoot);
@@ -663,16 +637,6 @@ contract DelegationManager is Initializable, OwnableUpgradeable, Pausable, Deleg
emit OperatorSharesDecreased(operator, staker, strategy, shares);
}
- function _pushOperatorStakeUpdate(address operator) internal {
- // if the stake registry has been set
- if (address(stakeRegistry) != address(0)) {
- address[] memory operators = new address[](1);
- operators[0] = operator;
- // update the operator's stake in the StakeRegistry
- stakeRegistry.updateStakes(operators);
- }
- }
-
/**
* @notice Removes `shares` in `strategies` from `staker` who is currently delegated to `operator` and queues a withdrawal to the `withdrawer`.
* @dev If the `operator` is indeed an operator, then the operator's delegated shares in the `strategies` are also decreased appropriately.
@@ -717,11 +681,6 @@ contract DelegationManager is Initializable, OwnableUpgradeable, Pausable, Deleg
unchecked { ++i; }
}
- // Push the operator's new stake to the StakeRegistry
- if (operator != address(0)) {
- _pushOperatorStakeUpdate(operator);
- }
-
// Create queue entry and increment withdrawal nonce
uint256 nonce = cumulativeWithdrawalsQueued[staker];
cumulativeWithdrawalsQueued[staker]++;
diff --git a/src/contracts/core/DelegationManagerStorage.sol b/src/contracts/core/DelegationManagerStorage.sol
index 789db18f0b..81a57d341c 100644
--- a/src/contracts/core/DelegationManagerStorage.sol
+++ b/src/contracts/core/DelegationManagerStorage.sol
@@ -89,8 +89,9 @@ abstract contract DelegationManagerStorage is IDelegationManager {
/// @dev This only increments (doesn't decrement), and is used to help ensure that otherwise identical withdrawals have unique hashes.
mapping(address => uint256) public cumulativeWithdrawalsQueued;
- /// @notice the address of the StakeRegistry contract to call for stake updates when operator shares are changed
- IStakeRegistryStub public stakeRegistry;
+ /// @notice Deprecated from an old Goerli release
+ /// See conversation here: https://github.com/Layr-Labs/eigenlayer-contracts/pull/365/files#r1417525270
+ address private __deprecated_stakeRegistry;
constructor(IStrategyManager _strategyManager, ISlasher _slasher, IEigenPodManager _eigenPodManager) {
strategyManager = _strategyManager;
diff --git a/src/contracts/core/Slasher.sol b/src/contracts/core/Slasher.sol
index a8ad44d213..b4ad83dd72 100644
--- a/src/contracts/core/Slasher.sol
+++ b/src/contracts/core/Slasher.sol
@@ -9,608 +9,94 @@ import "../permissions/Pausable.sol";
import "@openzeppelin-upgrades/contracts/access/OwnableUpgradeable.sol";
import "@openzeppelin-upgrades/contracts/proxy/utils/Initializable.sol";
+
/**
- * @title The primary 'slashing' contract for EigenLayer.
- * @author Layr Labs, Inc.
- * @notice Terms of Service: https://docs.eigenlayer.xyz/overview/terms-of-service
- * @notice This contract specifies details on slashing. The functionalities are:
- * - adding contracts who have permission to perform slashing,
- * - revoking permission for slashing from specified contracts,
- * - tracking historic stake updates to ensure that withdrawals can only be completed once no middlewares have slashing rights
- * over the funds being withdrawn
- */
+ * @notice This contract is not in use as of the Eigenlayer M2 release.
+ *
+ * Although many contracts reference it as an immutable variable, they do not
+ * interact with it and it is effectively dead code. The Slasher was originally
+ * deployed during Eigenlayer M1, but remained paused and unused for the duration
+ * of that release as well.
+ *
+ * Eventually, slashing design will be finalized and the Slasher will be finished
+ * and more fully incorporated into the core contracts. For now, you can ignore this
+ * file. If you really want to see what the deployed M1 version looks like, check
+ * out the `init-mainnet-deployment` branch under "releases".
+ *
+ * This contract is a stub that maintains its original interface for use in testing
+ * and deploy scripts. Otherwise, it does nothing.
+ */
contract Slasher is Initializable, OwnableUpgradeable, ISlasher, Pausable {
- using StructuredLinkedList for StructuredLinkedList.List;
-
- uint256 private constant HEAD = 0;
-
- uint8 internal constant PAUSED_OPT_INTO_SLASHING = 0;
- uint8 internal constant PAUSED_FIRST_STAKE_UPDATE = 1;
- uint8 internal constant PAUSED_NEW_FREEZING = 2;
-
- /// @notice The central StrategyManager contract of EigenLayer
- IStrategyManager public immutable strategyManager;
- /// @notice The DelegationManager contract of EigenLayer
- IDelegationManager public immutable delegation;
- // operator => whitelisted contract with slashing permissions => (the time before which the contract is allowed to slash the user, block it was last updated)
- mapping(address => mapping(address => MiddlewareDetails)) internal _whitelistedContractDetails;
- // staker => if their funds are 'frozen' and potentially subject to slashing or not
- mapping(address => bool) internal frozenStatus;
-
- uint32 internal constant MAX_CAN_SLASH_UNTIL = type(uint32).max;
-
- /**
- * operator => a linked list of the addresses of the whitelisted middleware with permission to slash the operator, i.e. which
- * the operator is serving. Sorted by the block at which they were last updated (content of updates below) in ascending order.
- * This means the 'HEAD' (i.e. start) of the linked list will have the stalest 'updateBlock' value.
- */
- mapping(address => StructuredLinkedList.List) internal _operatorToWhitelistedContractsByUpdate;
-
- /**
- * operator =>
- * [
- * (
- * the least recent update block of all of the middlewares it's serving/served,
- * latest time that the stake bonded at that update needed to serve until
- * )
- * ]
- */
- mapping(address => MiddlewareTimes[]) internal _operatorToMiddlewareTimes;
-
- constructor(IStrategyManager _strategyManager, IDelegationManager _delegation) {
- strategyManager = _strategyManager;
- delegation = _delegation;
- _disableInitializers();
- }
-
- /// @notice Ensures that the operator has opted into slashing by the caller, and that the caller has never revoked its slashing ability.
- modifier onlyRegisteredForService(address operator) {
- require(
- _whitelistedContractDetails[operator][msg.sender].contractCanSlashOperatorUntilBlock == MAX_CAN_SLASH_UNTIL,
- "Slasher.onlyRegisteredForService: Operator has not opted into slashing by caller"
- );
- _;
- }
+
+ constructor(IStrategyManager, IDelegationManager) {}
- // EXTERNAL FUNCTIONS
function initialize(
- address initialOwner,
- IPauserRegistry _pauserRegistry,
- uint256 initialPausedStatus
- ) external initializer {
- _initializePauser(_pauserRegistry, initialPausedStatus);
- _transferOwnership(initialOwner);
- }
+ address,
+ IPauserRegistry,
+ uint256
+ ) external {}
- /**
- * @notice Gives the `contractAddress` permission to slash the funds of the caller.
- * @dev Typically, this function must be called prior to registering for a middleware.
- */
- function optIntoSlashing(address contractAddress) external onlyWhenNotPaused(PAUSED_OPT_INTO_SLASHING) {
- require(delegation.isOperator(msg.sender), "Slasher.optIntoSlashing: msg.sender is not a registered operator");
- _optIntoSlashing(msg.sender, contractAddress);
- }
+ function optIntoSlashing(address) external {}
- /**
- * @notice Used for 'slashing' a certain operator.
- * @param toBeFrozen The operator to be frozen.
- * @dev Technically the operator is 'frozen' (hence the name of this function), and then subject to slashing pending a decision by a human-in-the-loop.
- * @dev The operator must have previously given the caller (which should be a contract) the ability to slash them, through a call to `optIntoSlashing`.
- */
- function freezeOperator(address toBeFrozen) external onlyWhenNotPaused(PAUSED_NEW_FREEZING) {
- require(
- canSlash(toBeFrozen, msg.sender),
- "Slasher.freezeOperator: msg.sender does not have permission to slash this operator"
- );
- _freezeOperator(toBeFrozen, msg.sender);
- }
+ function freezeOperator(address) external {}
- /**
- * @notice Removes the 'frozen' status from each of the `frozenAddresses`
- * @dev Callable only by the contract owner (i.e. governance).
- */
- function resetFrozenStatus(address[] calldata frozenAddresses) external onlyOwner {
- for (uint256 i = 0; i < frozenAddresses.length; ) {
- _resetFrozenStatus(frozenAddresses[i]);
- unchecked {
- ++i;
- }
- }
- }
+ function resetFrozenStatus(address[] calldata) external {}
- /**
- * @notice this function is a called by middlewares during an operator's registration to make sure the operator's stake at registration
- * is slashable until serveUntilBlock
- * @param operator the operator whose stake update is being recorded
- * @param serveUntilBlock the block until which the operator's stake at the current block is slashable
- * @dev adds the middleware's slashing contract to the operator's linked list
- */
- function recordFirstStakeUpdate(
- address operator,
- uint32 serveUntilBlock
- ) external onlyWhenNotPaused(PAUSED_FIRST_STAKE_UPDATE) onlyRegisteredForService(operator) {
- // update the 'stalest' stakes update time + latest 'serveUntil' time of the `operator`
- _recordUpdateAndAddToMiddlewareTimes(operator, uint32(block.number), serveUntilBlock);
+ function recordFirstStakeUpdate(address, uint32) external {}
- // Push the middleware to the end of the update list. This will fail if the caller *is* already in the list.
- require(
- _operatorToWhitelistedContractsByUpdate[operator].pushBack(_addressToUint(msg.sender)),
- "Slasher.recordFirstStakeUpdate: Appending middleware unsuccessful"
- );
- }
-
- /**
- * @notice this function is a called by middlewares during a stake update for an operator (perhaps to free pending withdrawals)
- * to make sure the operator's stake at updateBlock is slashable until serveUntilBlock
- * @param operator the operator whose stake update is being recorded
- * @param updateBlock the block for which the stake update is being recorded
- * @param serveUntilBlock the block until which the operator's stake at updateBlock is slashable
- * @param insertAfter the element of the operators linked list that the currently updating middleware should be inserted after
- * @dev insertAfter should be calculated offchain before making the transaction that calls this. this is subject to race conditions,
- * but it is anticipated to be rare and not detrimental.
- */
function recordStakeUpdate(
- address operator,
- uint32 updateBlock,
- uint32 serveUntilBlock,
- uint256 insertAfter
- ) external onlyRegisteredForService(operator) {
- // sanity check on input
- require(updateBlock <= block.number, "Slasher.recordStakeUpdate: cannot provide update for future block");
- // update the 'stalest' stakes update time + latest 'serveUntilBlock' of the `operator`
- _recordUpdateAndAddToMiddlewareTimes(operator, updateBlock, serveUntilBlock);
+ address,
+ uint32,
+ uint32,
+ uint256
+ ) external {}
- /**
- * Move the middleware to its correct update position, determined by `updateBlock` and indicated via `insertAfter`.
- * If the the middleware is the only one in the list, then no need to mutate the list
- */
- if (_operatorToWhitelistedContractsByUpdate[operator].sizeOf() != 1) {
- // Remove the caller (middleware) from the list. This will fail if the caller is *not* already in the list.
- require(
- _operatorToWhitelistedContractsByUpdate[operator].remove(_addressToUint(msg.sender)) != 0,
- "Slasher.recordStakeUpdate: Removing middleware unsuccessful"
- );
- // Run routine for updating the `operator`'s linked list of middlewares
- _updateMiddlewareList(operator, updateBlock, insertAfter);
- // if there is precisely one middleware in the list, then ensure that the caller is indeed the singular list entrant
- } else {
- require(
- _operatorToWhitelistedContractsByUpdate[operator].getHead() == _addressToUint(msg.sender),
- "Slasher.recordStakeUpdate: Caller is not the list entrant"
- );
- }
- }
+ function recordLastStakeUpdateAndRevokeSlashingAbility(address, uint32) external {}
- /**
- * @notice this function is a called by middlewares during an operator's deregistration to make sure the operator's stake at deregistration
- * is slashable until serveUntilBlock
- * @param operator the operator whose stake update is being recorded
- * @param serveUntilBlock the block until which the operator's stake at the current block is slashable
- * @dev removes the middleware's slashing contract to the operator's linked list and revokes the middleware's (i.e. caller's) ability to
- * slash `operator` once `serveUntilBlock` is reached
- */
- function recordLastStakeUpdateAndRevokeSlashingAbility(
- address operator,
- uint32 serveUntilBlock
- ) external onlyRegisteredForService(operator) {
- // update the 'stalest' stakes update time + latest 'serveUntilBlock' of the `operator`
- _recordUpdateAndAddToMiddlewareTimes(operator, uint32(block.number), serveUntilBlock);
- // remove the middleware from the list
- require(
- _operatorToWhitelistedContractsByUpdate[operator].remove(_addressToUint(msg.sender)) != 0,
- "Slasher.recordLastStakeUpdateAndRevokeSlashingAbility: Removing middleware unsuccessful"
- );
- // revoke the middleware's ability to slash `operator` after `serverUntil`
- _revokeSlashingAbility(operator, msg.sender, serveUntilBlock);
- }
+ function strategyManager() external view returns (IStrategyManager) {}
- // VIEW FUNCTIONS
+ function delegation() external view returns (IDelegationManager) {}
- /// @notice Returns the block until which `serviceContract` is allowed to slash the `operator`.
- function contractCanSlashOperatorUntilBlock(
- address operator,
- address serviceContract
- ) external view returns (uint32) {
- return _whitelistedContractDetails[operator][serviceContract].contractCanSlashOperatorUntilBlock;
- }
+ function isFrozen(address) external view returns (bool) {}
- /// @notice Returns the block at which the `serviceContract` last updated its view of the `operator`'s stake
- function latestUpdateBlock(address operator, address serviceContract) external view returns (uint32) {
- return _whitelistedContractDetails[operator][serviceContract].latestUpdateBlock;
- }
+ function canSlash(address, address) external view returns (bool) {}
- /*
- * @notice Returns `_whitelistedContractDetails[operator][serviceContract]`.
- * @dev A getter function like this appears to be necessary for returning a struct from storage in struct form, rather than as a tuple.
- */
- function whitelistedContractDetails(
- address operator,
- address serviceContract
- ) external view returns (MiddlewareDetails memory) {
- return _whitelistedContractDetails[operator][serviceContract];
- }
+ function contractCanSlashOperatorUntilBlock(
+ address,
+ address
+ ) external view returns (uint32) {}
- /**
- * @notice Used to determine whether `staker` is actively 'frozen'. If a staker is frozen, then they are potentially subject to
- * slashing of their funds, and cannot cannot deposit or withdraw from the strategyManager until the slashing process is completed
- * and the staker's status is reset (to 'unfrozen').
- * @param staker The staker of interest.
- * @return Returns 'true' if `staker` themselves has their status set to frozen, OR if the staker is delegated
- * to an operator who has their status set to frozen. Otherwise returns 'false'.
- */
- function isFrozen(address staker) external view returns (bool) {
- if (frozenStatus[staker]) {
- return true;
- } else if (delegation.isDelegated(staker)) {
- address operatorAddress = delegation.delegatedTo(staker);
- return (frozenStatus[operatorAddress]);
- } else {
- return false;
- }
- }
+ function latestUpdateBlock(address, address) external view returns (uint32) {}
- /// @notice Returns true if `slashingContract` is currently allowed to slash `toBeSlashed`.
- function canSlash(address toBeSlashed, address slashingContract) public view returns (bool) {
- if (
- block.number < _whitelistedContractDetails[toBeSlashed][slashingContract].contractCanSlashOperatorUntilBlock
- ) {
- return true;
- } else {
- return false;
- }
- }
+ function getCorrectValueForInsertAfter(address, uint32) external view returns (uint256) {}
- /**
- * @notice Returns 'true' if `operator` can currently complete a withdrawal started at the `withdrawalStartBlock`, with `middlewareTimesIndex` used
- * to specify the index of a `MiddlewareTimes` struct in the operator's list (i.e. an index in `_operatorToMiddlewareTimes[operator]`). The specified
- * struct is consulted as proof of the `operator`'s ability (or lack thereof) to complete the withdrawal.
- * This function will return 'false' if the operator cannot currently complete a withdrawal started at the `withdrawalStartBlock`, *or* in the event
- * that an incorrect `middlewareTimesIndex` is supplied, even if one or more correct inputs exist.
- * @param operator Either the operator who queued the withdrawal themselves, or if the withdrawing party is a staker who delegated to an operator,
- * this address is the operator *who the staker was delegated to* at the time of the `withdrawalStartBlock`.
- * @param withdrawalStartBlock The block number at which the withdrawal was initiated.
- * @param middlewareTimesIndex Indicates an index in `_operatorToMiddlewareTimes[operator]` to consult as proof of the `operator`'s ability to withdraw
- * @dev The correct `middlewareTimesIndex` input should be computable off-chain.
- */
function canWithdraw(
- address operator,
- uint32 withdrawalStartBlock,
- uint256 middlewareTimesIndex
- ) external view returns (bool) {
- // if the operator has never registered for a middleware, just return 'true'
- if (_operatorToMiddlewareTimes[operator].length == 0) {
- return true;
- }
+ address,
+ uint32,
+ uint256
+ ) external returns (bool) {}
- // pull the MiddlewareTimes struct at the `middlewareTimesIndex`th position in `_operatorToMiddlewareTimes[operator]`
- MiddlewareTimes memory update = _operatorToMiddlewareTimes[operator][middlewareTimesIndex];
-
- /**
- * Case-handling for if the operator is not registered for any middlewares (i.e. they previously registered but are no longer registered for any),
- * AND the withdrawal was initiated after the 'stalestUpdateBlock' of the MiddlewareTimes struct specified by the provided `middlewareTimesIndex`.
- * NOTE: we check the 2nd of these 2 conditions first for gas efficiency, to help avoid an extra SLOAD in all other cases.
- */
- if (
- withdrawalStartBlock >= update.stalestUpdateBlock &&
- _operatorToWhitelistedContractsByUpdate[operator].size == 0
- ) {
- /**
- * In this case, we just check against the 'latestServeUntilBlock' of the last MiddlewareTimes struct. This is because the operator not being registered
- * for any middlewares (i.e. `_operatorToWhitelistedContractsByUpdate.size == 0`) means no new MiddlewareTimes structs will be being pushed, *and* the operator
- * will not be undertaking any new obligations (so just checking against the last entry is OK, unlike when the operator is actively registered for >=1 middleware).
- */
- update = _operatorToMiddlewareTimes[operator][_operatorToMiddlewareTimes[operator].length - 1];
- return (uint32(block.number) > update.latestServeUntilBlock);
- }
-
- /**
- * Make sure the stalest update block at the time of the update is strictly after `withdrawalStartBlock` and ensure that the current time
- * is after the `latestServeUntilBlock` of the update. This assures us that this that all middlewares were updated after the withdrawal began, and
- * that the stake is no longer slashable.
- */
- return (withdrawalStartBlock < update.stalestUpdateBlock &&
- uint32(block.number) > update.latestServeUntilBlock);
- }
-
- /// @notice Getter function for fetching `_operatorToMiddlewareTimes[operator][arrayIndex]`.
function operatorToMiddlewareTimes(
- address operator,
- uint256 arrayIndex
- ) external view returns (MiddlewareTimes memory) {
- return _operatorToMiddlewareTimes[operator][arrayIndex];
- }
+ address,
+ uint256
+ ) external view returns (MiddlewareTimes memory) {}
- /// @notice Getter function for fetching `_operatorToMiddlewareTimes[operator].length`.
- function middlewareTimesLength(address operator) external view returns (uint256) {
- return _operatorToMiddlewareTimes[operator].length;
- }
+ function middlewareTimesLength(address) external view returns (uint256) {}
- /// @notice Getter function for fetching `_operatorToMiddlewareTimes[operator][index].stalestUpdateBlock`.
- function getMiddlewareTimesIndexStalestUpdateBlock(address operator, uint32 index) external view returns (uint32) {
- return _operatorToMiddlewareTimes[operator][index].stalestUpdateBlock;
- }
+ function getMiddlewareTimesIndexStalestUpdateBlock(address, uint32) external view returns (uint32) {}
- /// @notice Getter function for fetching `_operatorToMiddlewareTimes[operator][index].latestServeUntilBlock`.
- function getMiddlewareTimesIndexServeUntilBlock(address operator, uint32 index) external view returns (uint32) {
- return _operatorToMiddlewareTimes[operator][index].latestServeUntilBlock;
- }
+ function getMiddlewareTimesIndexServeUntilBlock(address, uint32) external view returns (uint32) {}
- /// @notice Getter function for fetching `_operatorToWhitelistedContractsByUpdate[operator].size`.
- function operatorWhitelistedContractsLinkedListSize(address operator) external view returns (uint256) {
- return _operatorToWhitelistedContractsByUpdate[operator].size;
- }
+ function operatorWhitelistedContractsLinkedListSize(address) external view returns (uint256) {}
- /// @notice Getter function for fetching a single node in the operator's linked list (`_operatorToWhitelistedContractsByUpdate[operator]`).
function operatorWhitelistedContractsLinkedListEntry(
- address operator,
- address node
- ) external view returns (bool, uint256, uint256) {
- return StructuredLinkedList.getNode(_operatorToWhitelistedContractsByUpdate[operator], _addressToUint(node));
- }
-
- /**
- * @notice A search routine for finding the correct input value of `insertAfter` to `recordStakeUpdate` / `_updateMiddlewareList`.
- * @dev Used within this contract only as a fallback in the case when an incorrect value of `insertAfter` is supplied as an input to `_updateMiddlewareList`.
- * @dev The return value should *either* be 'HEAD' (i.e. zero) in the event that the node being inserted in the linked list has an `updateBlock`
- * that is less than the HEAD of the list, *or* the return value should specify the last `node` in the linked list for which
- * `_whitelistedContractDetails[operator][node].latestUpdateBlock <= updateBlock`,
- * i.e. the node such that the *next* node either doesn't exist,
- * OR
- * `_whitelistedContractDetails[operator][nextNode].latestUpdateBlock > updateBlock`.
- */
- function getCorrectValueForInsertAfter(address operator, uint32 updateBlock) public view returns (uint256) {
- uint256 node = _operatorToWhitelistedContractsByUpdate[operator].getHead();
- /**
- * Special case:
- * If the node being inserted in the linked list has an `updateBlock` that is less than the HEAD of the list, then we set `insertAfter = HEAD`.
- * In _updateMiddlewareList(), the new node will be pushed to the front (HEAD) of the list.
- */
- if (_whitelistedContractDetails[operator][_uintToAddress(node)].latestUpdateBlock > updateBlock) {
- return HEAD;
- }
- /**
- * `node` being zero (i.e. equal to 'HEAD') indicates an empty/non-existent node, i.e. reaching the end of the linked list.
- * Since the linked list is ordered in ascending order of update blocks, we simply start from the head of the list and step through until
- * we find a the *last* `node` for which `_whitelistedContractDetails[operator][node].latestUpdateBlock <= updateBlock`, or
- * otherwise reach the end of the list.
- */
- (, uint256 nextNode) = _operatorToWhitelistedContractsByUpdate[operator].getNextNode(node);
- while (
- (nextNode != HEAD) &&
- (_whitelistedContractDetails[operator][_uintToAddress(node)].latestUpdateBlock <= updateBlock)
- ) {
- node = nextNode;
- (, nextNode) = _operatorToWhitelistedContractsByUpdate[operator].getNextNode(node);
- }
- return node;
- }
+ address,
+ address
+ ) external view returns (bool, uint256, uint256) {}
- /// @notice gets the node previous to the given node in the operators middleware update linked list
- /// @dev used in offchain libs for updating stakes
- function getPreviousWhitelistedContractByUpdate(
- address operator,
- uint256 node
- ) external view returns (bool, uint256) {
- return _operatorToWhitelistedContractsByUpdate[operator].getPreviousNode(node);
- }
-
- // INTERNAL FUNCTIONS
-
- function _optIntoSlashing(address operator, address contractAddress) internal {
- //allow the contract to slash anytime before a time VERY far in the future
- _whitelistedContractDetails[operator][contractAddress].contractCanSlashOperatorUntilBlock = MAX_CAN_SLASH_UNTIL;
- emit OptedIntoSlashing(operator, contractAddress);
- }
-
- function _revokeSlashingAbility(address operator, address contractAddress, uint32 serveUntilBlock) internal {
- require(
- serveUntilBlock != MAX_CAN_SLASH_UNTIL,
- "Slasher._revokeSlashingAbility: serveUntilBlock time must be limited"
- );
- // contractAddress can now only slash operator before `serveUntilBlock`
- _whitelistedContractDetails[operator][contractAddress].contractCanSlashOperatorUntilBlock = serveUntilBlock;
- emit SlashingAbilityRevoked(operator, contractAddress, serveUntilBlock);
- }
-
- function _freezeOperator(address toBeFrozen, address slashingContract) internal {
- if (!frozenStatus[toBeFrozen]) {
- frozenStatus[toBeFrozen] = true;
- emit OperatorFrozen(toBeFrozen, slashingContract);
- }
- }
-
- function _resetFrozenStatus(address previouslySlashedAddress) internal {
- if (frozenStatus[previouslySlashedAddress]) {
- frozenStatus[previouslySlashedAddress] = false;
- emit FrozenStatusReset(previouslySlashedAddress);
- }
- }
-
- /**
- * @notice records the most recent updateBlock for the currently updating middleware and appends an entry to the operator's list of
- * MiddlewareTimes if relevant information has updated
- * @param operator the entity whose stake update is being recorded
- * @param updateBlock the block number for which the currently updating middleware is updating the serveUntilBlock for
- * @param serveUntilBlock the block until which the operator's stake at updateBlock is slashable
- * @dev this function is only called during externally called stake updates by middleware contracts that can slash operator
- */
- function _recordUpdateAndAddToMiddlewareTimes(
- address operator,
- uint32 updateBlock,
- uint32 serveUntilBlock
- ) internal {
- // reject any stale update, i.e. one from before that of the most recent recorded update for the currently updating middleware
- require(
- _whitelistedContractDetails[operator][msg.sender].latestUpdateBlock <= updateBlock,
- "Slasher._recordUpdateAndAddToMiddlewareTimes: can't push a previous update"
- );
- _whitelistedContractDetails[operator][msg.sender].latestUpdateBlock = updateBlock;
- // get the latest recorded MiddlewareTimes, if the operator's list of MiddlwareTimes is non empty
- MiddlewareTimes memory curr;
- uint256 _operatorToMiddlewareTimesLength = _operatorToMiddlewareTimes[operator].length;
- if (_operatorToMiddlewareTimesLength != 0) {
- curr = _operatorToMiddlewareTimes[operator][_operatorToMiddlewareTimesLength - 1];
- }
- MiddlewareTimes memory next = curr;
- bool pushToMiddlewareTimes;
- // if the serve until is later than the latest recorded one, update it
- if (serveUntilBlock > curr.latestServeUntilBlock) {
- next.latestServeUntilBlock = serveUntilBlock;
- // mark that we need push next to middleware times array because it contains new information
- pushToMiddlewareTimes = true;
- }
-
- // If this is the very first middleware added to the operator's list of middleware, then we add an entry to _operatorToMiddlewareTimes
- if (_operatorToWhitelistedContractsByUpdate[operator].size == 0) {
- next.stalestUpdateBlock = updateBlock;
- pushToMiddlewareTimes = true;
- }
- // If the middleware is the first in the list, we will update the `stalestUpdateBlock` field in MiddlewareTimes
- else if (_operatorToWhitelistedContractsByUpdate[operator].getHead() == _addressToUint(msg.sender)) {
- // if the updated middleware was the earliest update, set it to the 2nd earliest update's update time
- (bool hasNext, uint256 nextNode) = _operatorToWhitelistedContractsByUpdate[operator].getNextNode(
- _addressToUint(msg.sender)
- );
-
- if (hasNext) {
- // get the next middleware's latest update block
- uint32 nextMiddlewaresLeastRecentUpdateBlock = _whitelistedContractDetails[operator][
- _uintToAddress(nextNode)
- ].latestUpdateBlock;
- if (nextMiddlewaresLeastRecentUpdateBlock < updateBlock) {
- // if there is a next node, then set the stalestUpdateBlock to its recorded value
- next.stalestUpdateBlock = nextMiddlewaresLeastRecentUpdateBlock;
- } else {
- //otherwise updateBlock is the least recent update as well
- next.stalestUpdateBlock = updateBlock;
- }
- } else {
- // otherwise this is the only middleware so right now is the stalestUpdateBlock
- next.stalestUpdateBlock = updateBlock;
- }
- // mark that we need to push `next` to middleware times array because it contains new information
- pushToMiddlewareTimes = true;
- }
-
- // if `next` has new information, then push it
- if (pushToMiddlewareTimes) {
- _operatorToMiddlewareTimes[operator].push(next);
- emit MiddlewareTimesAdded(
- operator,
- _operatorToMiddlewareTimes[operator].length - 1,
- next.stalestUpdateBlock,
- next.latestServeUntilBlock
- );
- }
- }
-
- /// @notice A routine for updating the `operator`'s linked list of middlewares, inside `recordStakeUpdate`.
- function _updateMiddlewareList(address operator, uint32 updateBlock, uint256 insertAfter) internal {
- /**
- * boolean used to track if the `insertAfter input to this function is incorrect. If it is, then `runFallbackRoutine` will
- * be flipped to 'true', and we will use `getCorrectValueForInsertAfter` to find the correct input. This routine helps solve
- * a race condition where the proper value of `insertAfter` changes while a transaction is pending.
- */
-
- bool runFallbackRoutine = false;
- // If this condition is met, then the `updateBlock` input should be after `insertAfter`'s latest updateBlock
- if (insertAfter != HEAD) {
- // Check that `insertAfter` exists. If not, we will use the fallback routine to find the correct value for `insertAfter`.
- if (!_operatorToWhitelistedContractsByUpdate[operator].nodeExists(insertAfter)) {
- runFallbackRoutine = true;
- }
-
- /**
- * Make sure `insertAfter` specifies a node for which the most recent updateBlock was *at or before* updateBlock.
- * Again, if not, we will use the fallback routine to find the correct value for `insertAfter`.
- */
- if (
- (!runFallbackRoutine) &&
- (_whitelistedContractDetails[operator][_uintToAddress(insertAfter)].latestUpdateBlock > updateBlock)
- ) {
- runFallbackRoutine = true;
- }
-
- // if we have not marked `runFallbackRoutine` as 'true' yet, then that means the `insertAfter` input was correct so far
- if (!runFallbackRoutine) {
- // Get `insertAfter`'s successor. `hasNext` will be false if `insertAfter` is the last node in the list
- (bool hasNext, uint256 nextNode) = _operatorToWhitelistedContractsByUpdate[operator].getNextNode(
- insertAfter
- );
- if (hasNext) {
- /**
- * Make sure the element after `insertAfter`'s most recent updateBlock was *strictly after* `updateBlock`.
- * Again, if not, we will use the fallback routine to find the correct value for `insertAfter`.
- */
- if (
- _whitelistedContractDetails[operator][_uintToAddress(nextNode)].latestUpdateBlock <= updateBlock
- ) {
- runFallbackRoutine = true;
- }
- }
- }
-
- // if we have not marked `runFallbackRoutine` as 'true' yet, then that means the `insertAfter` input was correct on all counts
- if (!runFallbackRoutine) {
- /**
- * Insert the caller (middleware) after `insertAfter`.
- * This will fail if `msg.sender` is already in the list, which they shouldn't be because they were removed from the list above.
- */
- require(
- _operatorToWhitelistedContractsByUpdate[operator].insertAfter(
- insertAfter,
- _addressToUint(msg.sender)
- ),
- "Slasher.recordStakeUpdate: Inserting middleware unsuccessful"
- );
- // in this case (runFallbackRoutine == true), we run a search routine to find the correct input value of `insertAfter` and then rerun this function
- } else {
- insertAfter = getCorrectValueForInsertAfter(operator, updateBlock);
- _updateMiddlewareList(operator, updateBlock, insertAfter);
- }
- // In this case (insertAfter == HEAD), the `updateBlock` input should be before every other middleware's latest updateBlock.
- } else {
- /**
- * Check that `updateBlock` is before any other middleware's latest updateBlock.
- * If not, use the fallback routine to find the correct value for `insertAfter`.
- */
- if (
- _whitelistedContractDetails[operator][
- _uintToAddress(_operatorToWhitelistedContractsByUpdate[operator].getHead())
- ].latestUpdateBlock <= updateBlock
- ) {
- runFallbackRoutine = true;
- }
- // if we have not marked `runFallbackRoutine` as 'true' yet, then that means the `insertAfter` input was correct on all counts
- if (!runFallbackRoutine) {
- /**
- * Insert the middleware at the start (i.e. HEAD) of the list.
- * This will fail if `msg.sender` is already in the list, which they shouldn't be because they were removed from the list above.
- */
- require(
- _operatorToWhitelistedContractsByUpdate[operator].pushFront(_addressToUint(msg.sender)),
- "Slasher.recordStakeUpdate: Preppending middleware unsuccessful"
- );
- // in this case (runFallbackRoutine == true), we run a search routine to find the correct input value of `insertAfter` and then rerun this function
- } else {
- insertAfter = getCorrectValueForInsertAfter(operator, updateBlock);
- _updateMiddlewareList(operator, updateBlock, insertAfter);
- }
- }
- }
-
- function _addressToUint(address addr) internal pure returns (uint256) {
- return uint256(uint160(addr));
- }
-
- function _uintToAddress(uint256 x) internal pure returns (address) {
- return address(uint160(x));
- }
+ function whitelistedContractDetails(
+ address,
+ address
+ ) external view returns (MiddlewareDetails memory) {}
- /**
- * @dev This empty reserved space is put in place to allow future versions to add new
- * variables without shifting down storage in the inheritance chain.
- * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps
- */
- uint256[46] private __gap;
-}
+}
\ No newline at end of file
diff --git a/src/contracts/interfaces/IDelegationManager.sol b/src/contracts/interfaces/IDelegationManager.sol
index f28b86fe8e..68b2bfcbf9 100644
--- a/src/contracts/interfaces/IDelegationManager.sol
+++ b/src/contracts/interfaces/IDelegationManager.sol
@@ -3,7 +3,6 @@ pragma solidity >=0.5.0;
import "./IStrategy.sol";
import "./ISignatureUtils.sol";
-import "./IStakeRegistryStub.sol";
import "./IStrategyManager.sol";
/**
@@ -70,9 +69,6 @@ interface IDelegationManager is ISignatureUtils {
uint256 expiry;
}
- /// @notice Emitted when the StakeRegistry is set
- event StakeRegistrySet(IStakeRegistryStub stakeRegistry);
-
/**
* Struct type used to specify an existing queued withdrawal. Rather than storing the entire struct, only a hash is stored.
* In functions that operate on existing queued withdrawals -- e.g. completeQueuedWithdrawal`, the data is resubmitted and the hash of the submitted
@@ -324,9 +320,6 @@ interface IDelegationManager is ISignatureUtils {
uint256 shares
) external;
- /// @notice the address of the StakeRegistry contract to call for stake updates when operator shares are changed
- function stakeRegistry() external view returns (IStakeRegistryStub);
-
/**
* @notice returns the address of the operator that `staker` is delegated to.
* @notice Mapping: staker => operator whom the staker is currently delegated to.
diff --git a/src/contracts/interfaces/IStakeRegistryStub.sol b/src/contracts/interfaces/IStakeRegistryStub.sol
deleted file mode 100644
index ad64a6785a..0000000000
--- a/src/contracts/interfaces/IStakeRegistryStub.sol
+++ /dev/null
@@ -1,13 +0,0 @@
-// SPDX-License-Identifier: BUSL-1.1
-pragma solidity >=0.5.0;
-
-import "./IStakeRegistryStub.sol";
-
-// @notice Stub interface to avoid circular-ish inheritance, where core contracts rely on middleware interfaces
-interface IStakeRegistryStub {
- /**
- * @notice Used for updating information on deposits of nodes.
- * @param operators are the addresses of the operators whose stake information is getting updated
- */
- function updateStakes(address[] memory operators) external;
-}
diff --git a/src/test/Delegation.t.sol b/src/test/Delegation.t.sol
index e6005c7d70..8f6964d928 100644
--- a/src/test/Delegation.t.sol
+++ b/src/test/Delegation.t.sol
@@ -12,7 +12,6 @@ contract DelegationTests is EigenLayerTestHelper {
uint32 serveUntil = 100;
address public registryCoordinator = address(uint160(uint256(keccak256("registryCoordinator"))));
- StakeRegistryStub public stakeRegistry;
uint8 defaultQuorumNumber = 0;
bytes32 defaultOperatorId = bytes32(uint256(0));
@@ -24,12 +23,6 @@ contract DelegationTests is EigenLayerTestHelper {
function setUp() public virtual override {
EigenLayerDeployer.setUp();
-
- initializeMiddlewares();
- }
-
- function initializeMiddlewares() public {
- stakeRegistry = new StakeRegistryStub();
}
/// @notice testing if an operator can register to themselves.
diff --git a/src/test/DepositWithdraw.t.sol b/src/test/DepositWithdraw.t.sol
index fc2813d5ea..28c3b5d048 100644
--- a/src/test/DepositWithdraw.t.sol
+++ b/src/test/DepositWithdraw.t.sol
@@ -18,126 +18,6 @@ contract DepositWithdrawTests is EigenLayerTestHelper {
return _testDepositWeth(getOperatorAddress(0), amountToDeposit);
}
- function testWithdrawalSequences() public {
- //use preexisting helper function to set up a withdrawal
- address middleware = address(0xdeadbeef);
- address middleware_2 = address(0x009849);
- address staker = getOperatorAddress(0);
- IDelegationManager.Withdrawal memory queuedWithdrawal;
-
- uint256 depositAmount = 1 ether;
- IStrategy strategy = wethStrat;
- IERC20 underlyingToken = weth;
- IStrategy[] memory strategyArray = new IStrategy[](1);
- strategyArray[0] = strategy;
- IERC20[] memory tokensArray = new IERC20[](1);
- tokensArray[0] = underlyingToken;
- {
- uint256[] memory shareAmounts = new uint256[](1);
- shareAmounts[0] = depositAmount - 1 gwei; //leave some shares behind so we don't get undelegation issues
- uint256[] memory strategyIndexes = new uint256[](1);
- strategyIndexes[0] = 0;
- address withdrawer = staker;
-
- {
- assertTrue(!delegation.isDelegated(staker), "_createQueuedWithdrawal: staker is already delegated");
- IDelegationManager.OperatorDetails memory operatorDetails = IDelegationManager.OperatorDetails({
- earningsReceiver: staker,
- delegationApprover: address(0),
- stakerOptOutWindowBlocks: 0
- });
- _testRegisterAsOperator(staker, operatorDetails);
- assertTrue(
- delegation.isDelegated(staker), "_createQueuedWithdrawal: staker isn't delegated when they should be"
- );
-
- //make deposit in WETH strategy
- uint256 amountDeposited = _testDepositWeth(staker, depositAmount);
- // We can't withdraw more than we deposit
- if (shareAmounts[0] > amountDeposited) {
- cheats.expectRevert("StrategyManager._removeShares: shareAmount too high");
- }
- }
-
-
- cheats.startPrank(staker);
- //opt in staker to restake for the two middlewares we are using
- slasher.optIntoSlashing(middleware);
- slasher.optIntoSlashing(middleware_2);
- cheats.stopPrank();
-
- cheats.startPrank(middleware);
- // first stake update with updateBlock = 1, serveUntilBlock = 5
-
- uint32 serveUntilBlock = 5;
- slasher.recordFirstStakeUpdate(staker, serveUntilBlock);
- cheats.stopPrank();
- //check middlewareTimes entry is correct
- require(slasher.getMiddlewareTimesIndexStalestUpdateBlock(staker, 0) == 1, "middleware updateBlock update incorrect");
- require(slasher.getMiddlewareTimesIndexServeUntilBlock(staker, 0) == 5, "middleware serveUntil update incorrect");
-
-
- cheats.startPrank(middleware_2);
- // first stake update with updateBlock = 1, serveUntilBlock = 6
- slasher.recordFirstStakeUpdate(staker, serveUntilBlock+1);
- cheats.stopPrank();
- //check middlewareTimes entry is correct
- require(slasher.getMiddlewareTimesIndexStalestUpdateBlock(staker, 1) == 1, "middleware updateBlock update incorrect");
- require(slasher.getMiddlewareTimesIndexServeUntilBlock(staker, 1) == 6, "middleware serveUntil update incorrect");
- //check old entry has not changed
- require(slasher.getMiddlewareTimesIndexStalestUpdateBlock(staker, 0) == 1, "middleware updateBlock update incorrect");
- require(slasher.getMiddlewareTimesIndexServeUntilBlock(staker, 0) == 5, "middleware serveUntil update incorrect");
-
- //move ahead a block before queuing the withdrawal
- cheats.roll(2);
- //cheats.startPrank(staker);
- //queue the withdrawal
- ( ,queuedWithdrawal) = _createOnlyQueuedWithdrawal(staker,
- true,
- depositAmount,
- strategyArray,
- tokensArray,
- shareAmounts,
- strategyIndexes,
- withdrawer
- );
-
- }
- //Because the staker has queued a withdrawal both currently staked middlewares must issued an update as required for the completion of the withdrawal
- //to be realistic we move ahead a block before updating middlewares
- cheats.roll(3);
-
- cheats.startPrank(middleware);
- // stake update with updateBlock = 3, newServeUntilBlock = 7
- uint32 newServeUntilBlock = 7;
- uint32 updateBlock = 3;
- uint256 insertAfter = 1;
- slasher.recordStakeUpdate(staker, updateBlock, newServeUntilBlock, insertAfter);
- cheats.stopPrank();
- //check middlewareTimes entry is correct
- require(slasher.getMiddlewareTimesIndexStalestUpdateBlock(staker, 2) == 1, "middleware updateBlock update incorrect");
- require(slasher.getMiddlewareTimesIndexServeUntilBlock(staker, 2) == 7, "middleware serveUntil update incorrect");
-
- cheats.startPrank(middleware_2);
- // stake update with updateBlock = 3, newServeUntilBlock = 10
- slasher.recordStakeUpdate(staker, updateBlock, newServeUntilBlock+3, insertAfter);
- cheats.stopPrank();
- //check middlewareTimes entry is correct
- require(slasher.getMiddlewareTimesIndexStalestUpdateBlock(staker, 3) == 3, "middleware updateBlock update incorrect");
- require(slasher.getMiddlewareTimesIndexServeUntilBlock(staker, 3) == 10, "middleware serveUntil update incorrect");
-
- cheats.startPrank(middleware);
- // stake update with updateBlock = 3, newServeUntilBlock = 7
- newServeUntilBlock = 7;
- updateBlock = 3;
- insertAfter = 2;
- slasher.recordStakeUpdate(staker, updateBlock, newServeUntilBlock, insertAfter);
- cheats.stopPrank();
- //check middlewareTimes entry is correct
- require(slasher.getMiddlewareTimesIndexStalestUpdateBlock(staker, 4) == 3, "middleware updateBlock update incorrect");
- require(slasher.getMiddlewareTimesIndexServeUntilBlock(staker, 4) == 10, "middleware serveUntil update incorrect");
- }
-
/// @notice deploys 'numStratsToAdd' strategies using '_testAddStrategy' and then deposits '1e18' to each of them from 'getOperatorAddress(0)'
/// @param numStratsToAdd is the number of strategies being added and deposited into
diff --git a/src/test/EigenLayerTestHelper.t.sol b/src/test/EigenLayerTestHelper.t.sol
index 5b9f4c040f..0b2387e60f 100644
--- a/src/test/EigenLayerTestHelper.t.sol
+++ b/src/test/EigenLayerTestHelper.t.sol
@@ -4,8 +4,6 @@ pragma solidity =0.8.12;
import "../test/EigenLayerDeployer.t.sol";
import "../contracts/interfaces/ISignatureUtils.sol";
-import "./mocks/StakeRegistryStub.sol";
-
contract EigenLayerTestHelper is EigenLayerDeployer {
uint8 durationToInit = 2;
uint256 public SECP256K1N_MODULUS = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141;
diff --git a/src/test/EigenPod.t.sol b/src/test/EigenPod.t.sol
index 198fc961c5..caec6be5e6 100644
--- a/src/test/EigenPod.t.sol
+++ b/src/test/EigenPod.t.sol
@@ -997,7 +997,6 @@ contract EigenPodTests is ProofParsing, EigenPodPausingConstants {
.validatorPubkeyHashToInfo(validatorPubkeyHash)
.restakedBalanceGwei;
- uint64 newValidatorBalance = _getValidatorUpdatedBalance();
int256 shareDiff = beaconChainETHBefore - eigenPodManager.podOwnerShares(podOwner);
assertTrue(
@@ -1042,10 +1041,10 @@ contract EigenPodTests is ProofParsing, EigenPodPausingConstants {
function testCreatePodIfItReturnsPodAddress() external {
cheats.startPrank(podOwner);
- address podAddress = eigenPodManager.createPod();
+ address _podAddress = eigenPodManager.createPod();
cheats.stopPrank();
IEigenPod pod = eigenPodManager.getPod(podOwner);
- require(podAddress == address(pod), "invalid pod address");
+ require(_podAddress == address(pod), "invalid pod address");
}
function testStakeOnEigenPodFromNonPodManagerAddress(address nonPodManager) external fuzzedAddress(nonPodManager) {
@@ -1390,13 +1389,13 @@ contract EigenPodTests is ProofParsing, EigenPodPausingConstants {
}
function test_validatorPubkeyToInfo() external {
- bytes memory pubkey = hex"93a0dd04ccddf3f1b419fdebf99481a2182c17d67cf14d32d6e50fc4bf8effc8db4a04b7c2f3a5975c1b9b74e2841888";
+ bytes memory _pubkey = hex"93a0dd04ccddf3f1b419fdebf99481a2182c17d67cf14d32d6e50fc4bf8effc8db4a04b7c2f3a5975c1b9b74e2841888";
setJSON("./src/test/test-data/withdrawal_credential_proof_302913.json");
_testDeployAndVerifyNewEigenPod(podOwner, signature, depositDataRoot);
IEigenPod pod = eigenPodManager.getPod(podOwner);
- IEigenPod.ValidatorInfo memory info1 = pod.validatorPubkeyToInfo(pubkey);
+ IEigenPod.ValidatorInfo memory info1 = pod.validatorPubkeyToInfo(_pubkey);
IEigenPod.ValidatorInfo memory info2 = pod.validatorPubkeyHashToInfo(getValidatorPubkeyHash());
require(info1.validatorIndex == info2.validatorIndex, "validatorIndex does not match");
@@ -1407,13 +1406,13 @@ contract EigenPodTests is ProofParsing, EigenPodPausingConstants {
}
function test_validatorStatus() external {
- bytes memory pubkey = hex"93a0dd04ccddf3f1b419fdebf99481a2182c17d67cf14d32d6e50fc4bf8effc8db4a04b7c2f3a5975c1b9b74e2841888";
+ bytes memory _pubkey = hex"93a0dd04ccddf3f1b419fdebf99481a2182c17d67cf14d32d6e50fc4bf8effc8db4a04b7c2f3a5975c1b9b74e2841888";
setJSON("./src/test/test-data/withdrawal_credential_proof_302913.json");
_testDeployAndVerifyNewEigenPod(podOwner, signature, depositDataRoot);
IEigenPod pod = eigenPodManager.getPod(podOwner);
- IEigenPod.VALIDATOR_STATUS status1 = pod.validatorStatus(pubkey);
+ IEigenPod.VALIDATOR_STATUS status1 = pod.validatorStatus(_pubkey);
IEigenPod.VALIDATOR_STATUS status2 = pod.validatorStatus(getValidatorPubkeyHash());
require(status1 == status2, "status does not match");
diff --git a/src/test/Slasher.t.sol b/src/test/Slasher.t.sol
deleted file mode 100644
index 561a971ccd..0000000000
--- a/src/test/Slasher.t.sol
+++ /dev/null
@@ -1,339 +0,0 @@
-// SPDX-License-Identifier: BUSL-1.1
-pragma solidity =0.8.12;
-
-import "./EigenLayerDeployer.t.sol";
-import "./EigenLayerTestHelper.t.sol";
-
-contract SlasherTests is EigenLayerTestHelper {
- ISlasher instance;
- uint256 constant HEAD = 0;
- address middleware = address(0xdeadbeef);
- address middleware_2 = address(0x009849);
- address middleware_3 = address(0x001000);
- address middleware_4 = address(0x002000);
-
- //performs basic deployment before each test
- function setUp() public override {
- super.setUp();
- }
-
- /**
- * @notice testing ownable permissions for slashing functions
- * addPermissionedContracts(), removePermissionedContracts()
- * and resetFrozenStatus().
- */
- function testOnlyOwnerFunctions(address incorrectCaller, address inputAddr)
- public
- fuzzedAddress(incorrectCaller)
- fuzzedAddress(inputAddr)
- {
- cheats.assume(incorrectCaller != slasher.owner());
- cheats.startPrank(incorrectCaller);
- address[] memory addressArray = new address[](1);
- addressArray[0] = inputAddr;
- cheats.expectRevert(bytes("Ownable: caller is not the owner"));
- slasher.resetFrozenStatus(addressArray);
- cheats.stopPrank();
- }
-
-
- function testRecursiveCallRevert() public {
- //Register and opt into slashing with operator
- cheats.startPrank(operator);
- IDelegationManager.OperatorDetails memory operatorDetails = IDelegationManager.OperatorDetails({
- earningsReceiver: operator,
- delegationApprover: address(0),
- stakerOptOutWindowBlocks: 0
- });
- string memory emptyStringForMetadataURI;
- delegation.registerAsOperator(operatorDetails, emptyStringForMetadataURI);
- slasher.optIntoSlashing(middleware);
- slasher.optIntoSlashing(middleware_2);
- slasher.optIntoSlashing(middleware_3);
- cheats.stopPrank();
-
- uint32 serveUntilBlock = uint32(block.number) + 1000;
- //these calls come from middlewares, we need more than 1 middleware to trigger the if clause on line 179
- // we need more than 2 middlewares to trigger the incorrect insertAfter being supplied by getCorrectValueForInsertAfter()
- cheats.startPrank(middleware);
- slasher.recordFirstStakeUpdate(operator, serveUntilBlock);
- cheats.stopPrank();
-
- cheats.startPrank(middleware_2);
- slasher.recordFirstStakeUpdate(operator, serveUntilBlock);
- cheats.stopPrank();
-
- cheats.startPrank(middleware_3);
- slasher.recordFirstStakeUpdate(operator, serveUntilBlock);
- cheats.stopPrank();
-
- cheats.startPrank(middleware);
-
- //we cannot add updateBlocks in the future so skip ahead to block 5.
- cheats.roll(5);
- //convert the middleware node address to a node number
- uint256 insertAfter = uint256(uint160(middleware));
-
- //Force fallbackRoutine to occur by specifying the wrong insertAfter. This then loops until we revert
- slasher.recordStakeUpdate(operator,5, serveUntilBlock, insertAfter);
-
-
- }
-
- function testRecordFirstStakeUpdate() public {
-
- //Register and opt into slashing with operator
- cheats.startPrank(operator);
- IDelegationManager.OperatorDetails memory operatorDetails = IDelegationManager.OperatorDetails({
- earningsReceiver: operator,
- delegationApprover: address(0),
- stakerOptOutWindowBlocks: 0
- });
- string memory emptyStringForMetadataURI;
- delegation.registerAsOperator(operatorDetails, emptyStringForMetadataURI);
- slasher.optIntoSlashing(middleware);
- slasher.optIntoSlashing(middleware_3);
- cheats.stopPrank();
-
- uint32 serveUntilBlock = uint32(block.number) + 1000;
-
- cheats.startPrank(middleware_2);
- //unapproved slasher calls should fail
- cheats.expectRevert("Slasher.onlyRegisteredForService: Operator has not opted into slashing by caller");
- slasher.recordFirstStakeUpdate(operator, serveUntilBlock);
- cheats.stopPrank();
-
- cheats.startPrank(middleware);
- //valid conditions should succeed
- slasher.recordFirstStakeUpdate(operator, serveUntilBlock);
-
- //repeated calls to FirstStakeUpdate from the same middleware should fail.
- cheats.expectRevert("Slasher.recordFirstStakeUpdate: Appending middleware unsuccessful");
- slasher.recordFirstStakeUpdate(operator, serveUntilBlock);
- cheats.stopPrank();
-
- cheats.startPrank(middleware_3);
- //sequential calls from different approved slashing middlewares should succeed
- slasher.recordFirstStakeUpdate(operator, serveUntilBlock);
- cheats.stopPrank();
-
- }
-
- function testRecordStakeUpdate() public {
- ///Register and opt into slashing with operator
- cheats.startPrank(operator);
- IDelegationManager.OperatorDetails memory operatorDetails = IDelegationManager.OperatorDetails({
- earningsReceiver: operator,
- delegationApprover: address(0),
- stakerOptOutWindowBlocks: 0
- });
- string memory emptyStringForMetadataURI;
- delegation.registerAsOperator(operatorDetails, emptyStringForMetadataURI);
- slasher.optIntoSlashing(middleware);
- slasher.optIntoSlashing(middleware_3);
- cheats.stopPrank();
-
- uint32 serveUntilBlock = uint32(block.number) + 1000;
- //convert the middleware node address to a node number
- uint256 insertAfter = uint256(uint160(middleware));
-
- cheats.startPrank(middleware);
-
- //calling before first stake update should fail
- cheats.expectRevert("Slasher.recordStakeUpdate: Removing middleware unsuccessful");
- slasher.recordStakeUpdate(operator,1, serveUntilBlock, insertAfter);
-
- //set up first stake update
- slasher.recordFirstStakeUpdate(operator, serveUntilBlock);
-
- cheats.expectRevert("Slasher.recordStakeUpdate: cannot provide update for future block");
- slasher.recordStakeUpdate(operator,5, serveUntilBlock, insertAfter);
- cheats.stopPrank();
-
- cheats.startPrank(middleware_2);
- //calling from unapproved middleware should fail
- cheats.expectRevert("Slasher.onlyRegisteredForService: Operator has not opted into slashing by caller");
- slasher.recordStakeUpdate(operator,1, serveUntilBlock, insertAfter);
- cheats.stopPrank();
-
-
- cheats.startPrank(middleware);
- //should succeed when given the correct settings
- cheats.roll(5);
- slasher.recordStakeUpdate(operator,3, serveUntilBlock, insertAfter);
- cheats.stopPrank();
- }
-
- function testOrderingRecordStakeUpdateVuln() public {
- ///Register and opt into slashing with operator
- cheats.startPrank(operator);
- IDelegationManager.OperatorDetails memory operatorDetails = IDelegationManager.OperatorDetails({
- earningsReceiver: operator,
- delegationApprover: address(0),
- stakerOptOutWindowBlocks: 0
- });
- string memory emptyStringForMetadataURI;
- delegation.registerAsOperator(operatorDetails, emptyStringForMetadataURI);
- slasher.optIntoSlashing(middleware);
- slasher.optIntoSlashing(middleware_3);
- cheats.stopPrank();
-
- uint32 serveUntilBlock = uint32(block.number) + 1000;
- //convert the middleware node address to a node number
- uint256 insertAfter = uint256(uint160(middleware));
-
- cheats.startPrank(middleware);
- //set up first stake update so sizeOf check on line179 is equal to 1
- //uint256 sizeOf = slasher.sizeOfOperatorList(operator);
- uint256 sizeOf = slasher.operatorWhitelistedContractsLinkedListSize(operator);
- slasher.recordFirstStakeUpdate(operator, serveUntilBlock);
- require(sizeOf + 1 == slasher.operatorWhitelistedContractsLinkedListSize(operator));
- cheats.stopPrank();
-
- //calling recordStakeUpdate() before recordFirstStakeUpdate() for middleware_3 should fail
- cheats.startPrank(middleware_3);
- sizeOf = slasher.operatorWhitelistedContractsLinkedListSize(operator);
- cheats.expectRevert("Slasher.recordStakeUpdate: Caller is not the list entrant");
- slasher.recordStakeUpdate(operator,1, serveUntilBlock, insertAfter);
- cheats.stopPrank();
-
- }
-
- function testOnlyRegisteredForService(address _slasher, uint32 _serveUntilBlock) public fuzzedAddress(_slasher) {
- cheats.prank(operator);
- IDelegationManager.OperatorDetails memory operatorDetails = IDelegationManager.OperatorDetails({
- earningsReceiver: operator,
- delegationApprover: address(0),
- stakerOptOutWindowBlocks: 0
- });
- string memory emptyStringForMetadataURI;
- delegation.registerAsOperator(operatorDetails, emptyStringForMetadataURI);
-
- //slasher cannot call stake update unless operator has oped in
- cheats.prank(_slasher);
- cheats.expectRevert("Slasher.onlyRegisteredForService: Operator has not opted into slashing by caller");
- slasher.recordFirstStakeUpdate(operator, _serveUntilBlock);
-
- cheats.prank(_slasher);
- cheats.expectRevert("Slasher.onlyRegisteredForService: Operator has not opted into slashing by caller");
- slasher.recordStakeUpdate(operator, 1,_serveUntilBlock,1);
-
- cheats.prank(_slasher);
- cheats.expectRevert("Slasher.onlyRegisteredForService: Operator has not opted into slashing by caller");
- slasher.recordLastStakeUpdateAndRevokeSlashingAbility(operator, _serveUntilBlock);
- }
-
- function testOptIn(address _operator, address _slasher) public fuzzedAddress(_slasher) fuzzedAddress(_operator) {
-
- //cannot opt in until registered as operator
- cheats.prank(_operator);
- cheats.expectRevert("Slasher.optIntoSlashing: msg.sender is not a registered operator");
- slasher.optIntoSlashing(_slasher);
-
- //can opt in after registered as operator
- cheats.startPrank(_operator);
- IDelegationManager.OperatorDetails memory operatorDetails = IDelegationManager.OperatorDetails({
- earningsReceiver: _operator,
- delegationApprover: address(0),
- stakerOptOutWindowBlocks: 0
- });
- string memory emptyStringForMetadataURI;
- delegation.registerAsOperator(operatorDetails, emptyStringForMetadataURI);
- slasher.optIntoSlashing(_slasher);
- cheats.stopPrank();
- }
-
- function testFreezeOperator() public {
- cheats.prank(operator);
- IDelegationManager.OperatorDetails memory operatorDetails = IDelegationManager.OperatorDetails({
- earningsReceiver: operator,
- delegationApprover: address(0),
- stakerOptOutWindowBlocks: 0
- });
- string memory emptyStringForMetadataURI;
- delegation.registerAsOperator(operatorDetails, emptyStringForMetadataURI);
-
- //cannot freeze until operator has oped in
- cheats.prank(middleware);
- cheats.expectRevert("Slasher.freezeOperator: msg.sender does not have permission to slash this operator");
- slasher.freezeOperator(operator);
-
- cheats.prank(operator);
- slasher.optIntoSlashing(middleware);
-
- //can freeze after operator has oped in
- cheats.prank(middleware);
- slasher.freezeOperator(operator);
-
- bool frozen = slasher.isFrozen(operator);
- require(frozen,"operator should be frozen");
- }
-
- function testResetFrozenOperator(address _attacker) public fuzzedAddress(_attacker) {
- cheats.assume(_attacker != slasher.owner());
-
- cheats.prank(operator);
- IDelegationManager.OperatorDetails memory operatorDetails = IDelegationManager.OperatorDetails({
- earningsReceiver: operator,
- delegationApprover: address(0),
- stakerOptOutWindowBlocks: 0
- });
- string memory emptyStringForMetadataURI;
- delegation.registerAsOperator(operatorDetails, emptyStringForMetadataURI);
-
- cheats.prank(operator);
- slasher.optIntoSlashing(middleware);
-
- cheats.prank(middleware);
- slasher.freezeOperator(operator);
-
- address[] memory frozenAddresses = new address[](1);
- frozenAddresses[0] = operator;
-
- //no other address can unfreeze
- cheats.prank(_attacker);
- cheats.expectRevert("Ownable: caller is not the owner");
- slasher.resetFrozenStatus(frozenAddresses);
-
- //owner can unfreeze
- cheats.prank(slasher.owner());
- slasher.resetFrozenStatus(frozenAddresses);
-
- bool frozen = slasher.isFrozen(operator);
- require(!frozen,"operator should be unfrozen");
- }
-
- function testRecordLastStakeUpdateAndRevokeSlashingAbility() public {
- ///Register and opt into slashing with operator
- cheats.startPrank(operator);
- IDelegationManager.OperatorDetails memory operatorDetails = IDelegationManager.OperatorDetails({
- earningsReceiver: operator,
- delegationApprover: address(0),
- stakerOptOutWindowBlocks: 0
- });
- string memory emptyStringForMetadataURI;
- delegation.registerAsOperator(operatorDetails, emptyStringForMetadataURI);
- slasher.optIntoSlashing(middleware);
- cheats.stopPrank();
-
- uint32 serveUntilBlock = 10;
-
- //stake update
- cheats.prank(middleware);
- slasher.recordFirstStakeUpdate(operator,serveUntilBlock);
-
- console.log("serveUntilBlock",slasher.getMiddlewareTimesIndexServeUntilBlock(operator,0));
- console.log("contractCanSlashOperatorUntil",slasher.contractCanSlashOperatorUntilBlock(operator,middleware));
-
- //middle can slash
- require(slasher.canSlash(operator,middleware),"middlewre should be able to slash");
-
- //revoke slashing
- cheats.prank(middleware);
- slasher.recordLastStakeUpdateAndRevokeSlashingAbility(operator,serveUntilBlock);
-
- cheats.roll(serveUntilBlock);
- //middleware can no longer slash
- require(!slasher.canSlash(operator,middleware),"middlewre should no longer be able to slash");
- }
-}
diff --git a/src/test/Withdrawals.t.sol b/src/test/Withdrawals.t.sol
index 6262d99b34..2f07da4e16 100644
--- a/src/test/Withdrawals.t.sol
+++ b/src/test/Withdrawals.t.sol
@@ -3,8 +3,6 @@ pragma solidity =0.8.12;
import "../test/EigenLayerTestHelper.t.sol";
-import "./mocks/StakeRegistryStub.sol";
-
contract WithdrawalTests is EigenLayerTestHelper {
// packed info used to help handle stack-too-deep errors
struct DataForTestWithdrawal {
@@ -15,16 +13,9 @@ contract WithdrawalTests is EigenLayerTestHelper {
}
bytes32 defaultOperatorId = bytes32(uint256(0));
- StakeRegistryStub public stakeRegistry;
function setUp() public virtual override {
EigenLayerDeployer.setUp();
-
- initializeMiddlewares();
- }
-
- function initializeMiddlewares() public {
- stakeRegistry = new StakeRegistryStub();
}
//This function helps with stack too deep issues with "testWithdrawal" test
@@ -41,8 +32,6 @@ contract WithdrawalTests is EigenLayerTestHelper {
cheats.assume(ethAmount >= 1 && ethAmount <= 1e18);
cheats.assume(eigenAmount >= 1 && eigenAmount <= 1e18);
- initializeMiddlewares();
-
if (RANDAO) {
_testWithdrawalAndDeregistration(operator, depositor, withdrawer, ethAmount, eigenAmount, withdrawAsTokens);
} else {
diff --git a/src/test/events/IDelegationManagerEvents.sol b/src/test/events/IDelegationManagerEvents.sol
index e82d9c458a..9c810aa4cb 100644
--- a/src/test/events/IDelegationManagerEvents.sol
+++ b/src/test/events/IDelegationManagerEvents.sol
@@ -2,12 +2,8 @@
pragma solidity =0.8.12;
import "src/contracts/interfaces/IDelegationManager.sol";
-import "src/test/mocks/StakeRegistryStub.sol";
interface IDelegationManagerEvents {
- /// @notice Emitted when the StakeRegistry is set
- event StakeRegistrySet(IStakeRegistryStub stakeRegistry);
-
// @notice Emitted when a new operator registers in EigenLayer and provides their OperatorDetails.
event OperatorRegistered(address indexed operator, IDelegationManager.OperatorDetails operatorDetails);
diff --git a/src/test/mocks/DelegationManagerMock.sol b/src/test/mocks/DelegationManagerMock.sol
index de9749993c..7b4097cd65 100644
--- a/src/test/mocks/DelegationManagerMock.sol
+++ b/src/test/mocks/DelegationManagerMock.sol
@@ -9,9 +9,6 @@ import "../../contracts/interfaces/IStrategyManager.sol";
contract DelegationManagerMock is IDelegationManager, Test {
mapping(address => bool) public isOperator;
mapping(address => mapping(IStrategy => uint256)) public operatorShares;
- IStakeRegistryStub public stakeRegistry;
-
- function setStakeRegistry(IStakeRegistryStub _stakeRegistry) external {}
function setIsOperator(address operator, bool _isOperatorReturnValue) external {
isOperator[operator] = _isOperatorReturnValue;
diff --git a/src/test/mocks/StakeRegistryStub.sol b/src/test/mocks/StakeRegistryStub.sol
deleted file mode 100644
index 1e0c3de622..0000000000
--- a/src/test/mocks/StakeRegistryStub.sol
+++ /dev/null
@@ -1,8 +0,0 @@
-// SPDX-License-Identifier: BUSL-1.1
-pragma solidity =0.8.12;
-
-import "../../contracts/interfaces/IStakeRegistryStub.sol";
-
-contract StakeRegistryStub is IStakeRegistryStub {
- function updateStakes(address[] memory) external {}
-}
diff --git a/src/test/unit/DelegationUnit.t.sol b/src/test/unit/DelegationUnit.t.sol
index 562380543c..cd9d7b1a79 100644
--- a/src/test/unit/DelegationUnit.t.sol
+++ b/src/test/unit/DelegationUnit.t.sol
@@ -8,7 +8,6 @@ import "src/contracts/core/DelegationManager.sol";
import "src/contracts/strategies/StrategyBase.sol";
import "src/test/events/IDelegationManagerEvents.sol";
-import "src/test/mocks/StakeRegistryStub.sol";
import "src/test/utils/EigenLayerUnitTestSetup.sol";
/**
@@ -27,7 +26,6 @@ contract DelegationManagerUnitTests is EigenLayerUnitTestSetup, IDelegationManag
StrategyBase strategyMock;
IERC20 mockToken;
uint256 mockTokenInitialSupply = 10e50;
- StakeRegistryStub stakeRegistryMock;
// Delegation signer
uint256 delegationSignerPrivateKey = uint256(0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80);
@@ -85,12 +83,6 @@ contract DelegationManagerUnitTests is EigenLayerUnitTestSetup, IDelegationManag
)
);
- // Deploy mock stake registry and set
- stakeRegistryMock = new StakeRegistryStub();
- cheats.expectEmit(true, true, true, true, address(delegationManager));
- emit StakeRegistrySet(stakeRegistryMock);
- delegationManager.setStakeRegistry(stakeRegistryMock);
-
// Deploy mock token and strategy
mockToken = new ERC20PresetFixedSupply("Mock Token", "MOCK", mockTokenInitialSupply, address(this));
strategyImplementation = new StrategyBase(strategyManagerMock);
@@ -310,12 +302,6 @@ contract DelegationManagerUnitTests_Initialization_Setters is DelegationManagerU
delegationManager.initialize(address(this), pauserRegistry, 0, initializedWithdrawalDelayBlocks);
}
- /// @notice Verifies that the stakeRegistry cannot be set after it has already been set
- function test_setStakeRegistry_revert_alreadySet() public {
- cheats.expectRevert("DelegationManager.setStakeRegistry: stakeRegistry already set");
- delegationManager.setStakeRegistry(stakeRegistryMock);
- }
-
function testFuzz_initialize_Revert_WhenWithdrawalDelayBlocksTooLarge(uint256 withdrawalDelayBlocks) public {
cheats.assume(withdrawalDelayBlocks > MAX_WITHDRAWAL_DELAY_BLOCKS);
// Deploy DelegationManager implmentation and proxy
diff --git a/src/test/unit/EigenPodUnit.t.sol b/src/test/unit/EigenPodUnit.t.sol
index b2cf50bee9..182562e047 100644
--- a/src/test/unit/EigenPodUnit.t.sol
+++ b/src/test/unit/EigenPodUnit.t.sol
@@ -357,7 +357,9 @@ contract EigenPodUnitTests_PodOwnerFunctions is EigenPodUnitTests, IEigenPodEven
function _seedPodWithETH(uint256 ethAmount) internal {
cheats.deal(address(this), ethAmount);
- address(eigenPod).call{value: ethAmount}("");
+ bool result;
+ bytes memory data;
+ (result, data) = address(eigenPod).call{value: ethAmount}("");
}
}
diff --git a/src/test/unit/SlasherUnit.t.sol b/src/test/unit/SlasherUnit.t.sol
deleted file mode 100644
index 897bac9884..0000000000
--- a/src/test/unit/SlasherUnit.t.sol
+++ /dev/null
@@ -1,799 +0,0 @@
-// SPDX-License-Identifier: BUSL-1.1
-pragma solidity =0.8.12;
-
-import "@openzeppelin/contracts/proxy/transparent/ProxyAdmin.sol";
-import "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol";
-
-import "forge-std/Test.sol";
-
-import "../../contracts/core/Slasher.sol";
-import "../../contracts/permissions/PauserRegistry.sol";
-import "../../contracts/strategies/StrategyBase.sol";
-
-import "../mocks/DelegationManagerMock.sol";
-import "../mocks/EigenPodManagerMock.sol";
-import "../mocks/StrategyManagerMock.sol";
-import "../mocks/Reenterer.sol";
-import "../mocks/Reverter.sol";
-
-import "../mocks/ERC20Mock.sol";
-
-import "src/test/utils/Utils.sol";
-
-contract SlasherUnitTests is Test, Utils {
-
- Vm cheats = Vm(HEVM_ADDRESS);
-
- uint256 private constant HEAD = 0;
-
- uint256 private constant _NULL = 0;
- uint256 private constant _HEAD = 0;
-
- bool private constant _PREV = false;
- bool private constant _NEXT = true;
-
- uint8 internal constant PAUSED_OPT_INTO_SLASHING = 0;
- uint8 internal constant PAUSED_FIRST_STAKE_UPDATE = 1;
- uint8 internal constant PAUSED_NEW_FREEZING = 2;
-
- uint32 internal constant MAX_CAN_SLASH_UNTIL = type(uint32).max;
-
- ProxyAdmin public proxyAdmin;
- PauserRegistry public pauserRegistry;
-
- Slasher public slasherImplementation;
- Slasher public slasher;
- StrategyManagerMock public strategyManagerMock;
- DelegationManagerMock public delegationManagerMock;
- EigenPodManagerMock public eigenPodManagerMock;
-
- Reenterer public reenterer;
-
- address public pauser = address(555);
- address public unpauser = address(999);
-
- address initialOwner = address(this);
-
- IERC20 public dummyToken;
- StrategyBase public dummyStrat;
-
- uint256[] public emptyUintArray;
-
- // used as transient storage to fix stack-too-deep errors
- uint32 contractCanSlashOperatorUntilBefore;
- uint256 linkedListLengthBefore;
- uint256 middlewareTimesLengthBefore;
- bool nodeExists;
- uint256 prevNode;
- uint256 nextNode;
- ISlasher.MiddlewareDetails middlewareDetailsBefore;
- ISlasher.MiddlewareDetails middlewareDetailsAfter;
-
- mapping(address => bool) public addressIsExcludedFromFuzzedInputs;
-
- modifier filterFuzzedAddressInputs(address fuzzedAddress) {
- cheats.assume(!addressIsExcludedFromFuzzedInputs[fuzzedAddress]);
- _;
- }
-
- function setUp() virtual public {
- proxyAdmin = new ProxyAdmin();
-
- address[] memory pausers = new address[](1);
- pausers[0] = pauser;
- pauserRegistry = new PauserRegistry(pausers, unpauser);
-
- delegationManagerMock = new DelegationManagerMock();
- eigenPodManagerMock = new EigenPodManagerMock();
- strategyManagerMock = new StrategyManagerMock();
- slasherImplementation = new Slasher(strategyManagerMock, delegationManagerMock);
- slasher = Slasher(
- address(
- new TransparentUpgradeableProxy(
- address(slasherImplementation),
- address(proxyAdmin),
- abi.encodeWithSelector(Slasher.initialize.selector, initialOwner, pauserRegistry, 0/*initialPausedStatus*/)
- )
- )
- );
- dummyToken = new ERC20Mock();
- dummyStrat = deployNewStrategy(dummyToken, strategyManagerMock, pauserRegistry, dummyAdmin);
-
- // excude the zero address and the proxyAdmin from fuzzed inputs
- addressIsExcludedFromFuzzedInputs[address(0)] = true;
- addressIsExcludedFromFuzzedInputs[address(proxyAdmin)] = true;
- }
-
- /**
- * Regression test for SigP's EGN2-01 issue, "Middleware can Deny Withdrawals by Revoking Slashing Prior to Queueing Withdrawal".
- * This test checks that a new queued withdrawal after total deregistration (i.e. queued *after* totally de-registering from all AVSs) can still eventually be completed.
- */
- function testCanCompleteNewQueuedWithdrawalAfterTotalDeregistration(
- address operator,
- address contractAddress,
- uint32 prevServeUntilBlock,
- uint32 serveUntilBlock,
- uint32 withdrawalStartBlock
- )
- public
- filterFuzzedAddressInputs(operator)
- filterFuzzedAddressInputs(contractAddress)
- {
- // filter out setting the `serveUntilBlock` time to the MAX (or one less than max), since the contract will revert in this instance (or our math with overflow, if 1 less).
- cheats.assume(prevServeUntilBlock <= MAX_CAN_SLASH_UNTIL - 1);
- cheats.assume(serveUntilBlock <= MAX_CAN_SLASH_UNTIL - 1);
-
- // simulate registering to and de-registering from an AVS
- testRecordFirstStakeUpdate(operator, contractAddress, prevServeUntilBlock);
- // perform the last stake update and revoke slashing ability, from the `contractAddress`
- cheats.startPrank(contractAddress);
- slasher.recordLastStakeUpdateAndRevokeSlashingAbility(operator, serveUntilBlock);
- cheats.stopPrank();
-
- uint256 middlewareTimesIndex = slasher.middlewareTimesLength(operator) - 1;
- ISlasher.MiddlewareTimes memory middlewareTimes = slasher.operatorToMiddlewareTimes(operator, middlewareTimesIndex);
-
- // emit log_named_uint("middlewareTimes.stalestUpdateBlock", middlewareTimes.stalestUpdateBlock);
- // emit log_named_uint("middlewareTimes.latestServeUntilBlock", middlewareTimes.latestServeUntilBlock);
-
- // uint256 operatorWhitelistedContractsLinkedListSize = slasher.operatorWhitelistedContractsLinkedListSize(operator);
- // emit log_named_uint("operatorWhitelistedContractsLinkedListSize", operatorWhitelistedContractsLinkedListSize);
-
- // filter fuzzed inputs
- // cheats.assume(withdrawalStartBlock >= block.number);
- cheats.assume(withdrawalStartBlock >= middlewareTimes.stalestUpdateBlock);
- cheats.roll(middlewareTimes.latestServeUntilBlock + 1);
-
- require(
- slasher.canWithdraw(operator, withdrawalStartBlock, middlewareTimesIndex),
- "operator cannot complete withdrawal when they should be able to"
- );
- }
-
- /**
- * Test related to SigP's EGN2-01 issue, "Middleware can Deny Withdrawals by Revoking Slashing Prior to Queueing Withdrawal", to ensure that the fix does not degrade performance.
- * This test checks that a *previous* queued withdrawal prior to total deregistration (i.e. queued *before* totally de-registering from all AVSs)
- * can still be withdrawn at the appropriate time, i.e. that a fix to EGN2-01 does not add any delay to existing withdrawals.
- */
- function testCanCompleteExistingQueuedWithdrawalAfterTotalDeregistration(
- address operator,
- address contractAddress,
- uint32 prevServeUntilBlock,
- uint32 serveUntilBlock
- )
- public
- filterFuzzedAddressInputs(operator)
- filterFuzzedAddressInputs(contractAddress)
- {
- // filter out setting the `serveUntilBlock` time to the MAX (or one less than max), since the contract will revert in this instance (or our math with overflow, if 1 less).
- cheats.assume(prevServeUntilBlock <= MAX_CAN_SLASH_UNTIL - 1);
- cheats.assume(serveUntilBlock <= MAX_CAN_SLASH_UNTIL - 1);
-
- // roll forward 2 blocks
- cheats.roll(block.number + 2);
- // make sure `withdrawalStartBlock` is in past
- uint32 withdrawalStartBlock = uint32(block.number) - 1;
-
- // simulate registering to and de-registering from an AVS
- testRecordFirstStakeUpdate(operator, contractAddress, prevServeUntilBlock);
-
- // perform the last stake update and revoke slashing ability, from the `contractAddress`
- cheats.startPrank(contractAddress);
- slasher.recordLastStakeUpdateAndRevokeSlashingAbility(operator, serveUntilBlock);
- cheats.stopPrank();
-
- uint256 operatorWhitelistedContractsLinkedListSize = slasher.operatorWhitelistedContractsLinkedListSize(operator);
- require(operatorWhitelistedContractsLinkedListSize == 0, "operatorWhitelistedContractsLinkedListSize != 0");
-
- uint256 middlewareTimesLength = slasher.middlewareTimesLength(operator);
- require(middlewareTimesLength >= 2, "middlewareTimesLength < 2");
- uint256 middlewareTimesIndex = middlewareTimesLength - 2;
-
- ISlasher.MiddlewareTimes memory olderMiddlewareTimes = slasher.operatorToMiddlewareTimes(operator, middlewareTimesIndex);
-
- cheats.roll(olderMiddlewareTimes.latestServeUntilBlock + 1);
-
- require(withdrawalStartBlock < olderMiddlewareTimes.stalestUpdateBlock, "withdrawalStartBlock >= olderMiddlewareTimes.stalestUpdateBlock");
-
- require(
- slasher.canWithdraw(operator, withdrawalStartBlock, middlewareTimesIndex),
- "operator cannot complete withdrawal when they should be able to"
- );
- }
-
- function testCannotReinitialize() public {
- cheats.expectRevert(bytes("Initializable: contract is already initialized"));
- slasher.initialize(initialOwner, pauserRegistry, 0);
- }
-
- function testOptIntoSlashing(address operator, address contractAddress)
- public
- filterFuzzedAddressInputs(operator)
- filterFuzzedAddressInputs(contractAddress)
- {
- delegationManagerMock.setIsOperator(operator, true);
-
- cheats.startPrank(operator);
- slasher.optIntoSlashing(contractAddress);
- cheats.stopPrank();
-
- assertEq(slasher.contractCanSlashOperatorUntilBlock(operator, contractAddress), MAX_CAN_SLASH_UNTIL);
- require(slasher.canSlash(operator, contractAddress), "contract was not properly granted slashing permission");
- }
-
- function testOptIntoSlashing_RevertsWhenPaused() public {
- address operator = address(this);
- address contractAddress = address(this);
-
- // pause opting into slashing
- cheats.startPrank(pauser);
- slasher.pause(2 ** PAUSED_OPT_INTO_SLASHING);
- cheats.stopPrank();
-
- cheats.startPrank(operator);
- cheats.expectRevert(bytes("Pausable: index is paused"));
- slasher.optIntoSlashing(contractAddress);
- cheats.stopPrank();
- }
-
- function testOptIntoSlashing_RevertsWhenCallerNotOperator(address notOperator) public filterFuzzedAddressInputs(notOperator) {
- require(!delegationManagerMock.isOperator(notOperator), "caller is an operator -- this is assumed false");
- address contractAddress = address(this);
-
- cheats.startPrank(notOperator);
- cheats.expectRevert(bytes("Slasher.optIntoSlashing: msg.sender is not a registered operator"));
- slasher.optIntoSlashing(contractAddress);
- cheats.stopPrank();
- }
-
- function testFreezeOperator(address toBeFrozen, address freezingContract) public
- filterFuzzedAddressInputs(toBeFrozen)
- filterFuzzedAddressInputs(freezingContract)
- {
- testOptIntoSlashing(toBeFrozen, freezingContract);
- cheats.startPrank(freezingContract);
- slasher.freezeOperator(toBeFrozen);
- cheats.stopPrank();
-
- require(slasher.isFrozen(toBeFrozen), "operator not properly frozen");
- }
-
- function testFreezeOperatorTwice(address toBeFrozen, address freezingContract) public {
- testFreezeOperator(toBeFrozen, freezingContract);
- testFreezeOperator(toBeFrozen, freezingContract);
- }
-
- function testFreezeOperator_RevertsWhenPaused(address toBeFrozen, address freezingContract) external
- filterFuzzedAddressInputs(toBeFrozen)
- filterFuzzedAddressInputs(freezingContract)
- {
- testOptIntoSlashing(toBeFrozen, freezingContract);
-
- // pause freezing
- cheats.startPrank(pauser);
- slasher.pause(2 ** PAUSED_NEW_FREEZING);
- cheats.stopPrank();
-
- cheats.startPrank(freezingContract);
- cheats.expectRevert(bytes("Pausable: index is paused"));
- slasher.freezeOperator(toBeFrozen);
- cheats.stopPrank();
- }
-
- function testFreezeOperator_WhenCallerDoesntHaveSlashingPermission(address toBeFrozen, address freezingContract) external
- filterFuzzedAddressInputs(toBeFrozen)
- filterFuzzedAddressInputs(freezingContract)
- {
- cheats.startPrank(freezingContract);
- cheats.expectRevert(bytes("Slasher.freezeOperator: msg.sender does not have permission to slash this operator"));
- slasher.freezeOperator(toBeFrozen);
- cheats.stopPrank();
- }
-
- function testResetFrozenStatus(uint8 numberOfOperators, uint256 pseudorandomInput) external {
- // sanity filtering
- cheats.assume(numberOfOperators <= 16);
-
- address contractAddress = address(this);
-
- address[] memory operatorAddresses = new address[](numberOfOperators);
- bool[] memory operatorFrozen = new bool[](numberOfOperators);
- for (uint256 i = 0; i < numberOfOperators; ++i) {
- address operatorAddress = address(uint160(8888 + i));
- operatorAddresses[i] = operatorAddress;
- testOptIntoSlashing(operatorAddress, contractAddress);
- bool freezeOperator = (pseudorandomInput % 2 == 0) ? false : true;
- pseudorandomInput = uint256(keccak256(abi.encodePacked(pseudorandomInput)));
- operatorFrozen[i] = freezeOperator;
- if (freezeOperator) {
- testFreezeOperator(operatorAddress, contractAddress);
- }
- }
-
- cheats.startPrank(slasher.owner());
- slasher.resetFrozenStatus(operatorAddresses);
- cheats.stopPrank();
-
- for (uint256 i = 0; i < numberOfOperators; ++i) {
- require(!slasher.isFrozen(operatorAddresses[i]), "operator frozen improperly (not unfrozen when should be)");
- }
- }
-
- function testResetFrozenStatus_RevertsWhenCalledByNotOwner(address notOwner) external filterFuzzedAddressInputs(notOwner) {
- // sanity filtering
- cheats.assume(notOwner != slasher.owner());
-
- address[] memory operatorAddresses = new address[](1);
-
- cheats.startPrank(notOwner);
- cheats.expectRevert(bytes("Ownable: caller is not the owner"));
- slasher.resetFrozenStatus(operatorAddresses);
- cheats.stopPrank();
- }
-
- function testRecordFirstStakeUpdate(address operator, address contractAddress, uint32 serveUntilBlock)
- public
- filterFuzzedAddressInputs(operator)
- filterFuzzedAddressInputs(contractAddress)
- {
- testOptIntoSlashing(operator, contractAddress);
- _testRecordFirstStakeUpdate(operator, contractAddress, serveUntilBlock);
- }
-
- // internal function corresponding to the bulk of the logic in `testRecordFirstStakeUpdate`, so we can reuse it elsewhere without calling `testOptIntoSlashing`
- function _testRecordFirstStakeUpdate(address operator, address contractAddress, uint32 serveUntilBlock)
- internal
- filterFuzzedAddressInputs(operator)
- filterFuzzedAddressInputs(contractAddress)
- {
-
- linkedListLengthBefore = slasher.operatorWhitelistedContractsLinkedListSize(operator);
- middlewareDetailsBefore = slasher.whitelistedContractDetails(operator, contractAddress);
- contractCanSlashOperatorUntilBefore = slasher.contractCanSlashOperatorUntilBlock(operator, contractAddress);
-
- ISlasher.MiddlewareTimes memory mostRecentMiddlewareTimesStructBefore;
- // fetch the most recent struct, if at least one exists (otherwise leave the struct uninitialized)
- middlewareTimesLengthBefore = slasher.middlewareTimesLength(operator);
- if (middlewareTimesLengthBefore != 0) {
- mostRecentMiddlewareTimesStructBefore = slasher.operatorToMiddlewareTimes(operator, middlewareTimesLengthBefore - 1);
- }
-
- cheats.startPrank(contractAddress);
- slasher.recordFirstStakeUpdate(operator, serveUntilBlock);
- cheats.stopPrank();
-
- ISlasher.MiddlewareTimes memory mostRecentMiddlewareTimesStructAfter = slasher.operatorToMiddlewareTimes(operator, slasher.middlewareTimesLength(operator) - 1);
-
- // check that linked list size increased appropriately
- require(slasher.operatorWhitelistedContractsLinkedListSize(operator) == linkedListLengthBefore + 1, "linked list length did not increase when it should!");
- // get the linked list entry for the `contractAddress`
- (nodeExists, prevNode, nextNode) = slasher.operatorWhitelistedContractsLinkedListEntry(operator, contractAddress);
- // verify that the node exists
- require(nodeExists, "node does not exist");
-
- // if the `serveUntilBlock` time is greater than the previous maximum, then an update must have been pushed and the `latestServeUntilBlock` must be equal to the `serveUntilBlock` input
- if (serveUntilBlock > mostRecentMiddlewareTimesStructBefore.latestServeUntilBlock) {
- require(slasher.middlewareTimesLength(operator) == middlewareTimesLengthBefore + 1, "MiddlewareTimes struct not pushed to array");
- require(mostRecentMiddlewareTimesStructAfter.latestServeUntilBlock == serveUntilBlock, "latestServeUntilBlock not updated correctly");
- } else {
- require(mostRecentMiddlewareTimesStructAfter.latestServeUntilBlock == mostRecentMiddlewareTimesStructBefore.latestServeUntilBlock, "latestServeUntilBlock updated incorrectly");
- }
- // if this is the first MiddlewareTimes struct in the array, then an update must have been pushed and the `stalestUpdateBlock` *must* be equal to the current block
- if (middlewareTimesLengthBefore == 0) {
- require(slasher.middlewareTimesLength(operator) == middlewareTimesLengthBefore + 1,
- "MiddlewareTimes struct not pushed to array");
- require(mostRecentMiddlewareTimesStructAfter.stalestUpdateBlock == block.number,
- "stalestUpdateBlock not updated correctly -- contractAddress is first list entry");
- // otherwise, we check if the `contractAddress` is the head of the list. If it *is*, then prevNode will be _HEAD, and...
- } else if (prevNode == _HEAD) {
- // if nextNode is _HEAD, then the this indicates that `contractAddress` is actually the only list entry
- if (nextNode != _HEAD) {
- // if nextNode is not the only list entry, then `latestUpdateBlock` should update to a more recent time (if applicable!)
- uint32 nextMiddlewaresLeastRecentUpdateBlock = slasher.whitelistedContractDetails(operator, _uintToAddress(nextNode)).latestUpdateBlock;
- uint32 newValue = (nextMiddlewaresLeastRecentUpdateBlock < block.number) ? nextMiddlewaresLeastRecentUpdateBlock: uint32(block.number);
- require(mostRecentMiddlewareTimesStructAfter.stalestUpdateBlock == newValue,
- "stalestUpdateBlock not updated correctly -- should have updated to newValue");
- } else {
- require(mostRecentMiddlewareTimesStructAfter.stalestUpdateBlock == block.number,
- "stalestUpdateBlock not updated correctly -- contractAddress is only list entry");
- }
- }
-
- middlewareDetailsAfter = slasher.whitelistedContractDetails(operator, contractAddress);
- require(middlewareDetailsAfter.latestUpdateBlock == block.number,
- "latestUpdateBlock not updated correctly");
- require(middlewareDetailsAfter.contractCanSlashOperatorUntilBlock == middlewareDetailsBefore.contractCanSlashOperatorUntilBlock,
- "contractCanSlashOperatorUntilBlock changed unexpectedly");
- // check that `contractCanSlashOperatorUntilBlock` did not change
- require(slasher.contractCanSlashOperatorUntilBlock(operator, contractAddress) == contractCanSlashOperatorUntilBefore, "contractCanSlashOperatorUntilBlock changed unexpectedly");
- }
-
- function testRecordFirstStakeUpdate_RevertsWhenPaused() external {
- address operator = address(this);
- address contractAddress = address(this);
- uint32 serveUntilBlock = 0;
- testOptIntoSlashing(operator, contractAddress);
-
- // pause first stake updates
- cheats.startPrank(pauser);
- slasher.pause(2 ** PAUSED_FIRST_STAKE_UPDATE);
- cheats.stopPrank();
-
- cheats.startPrank(contractAddress);
- cheats.expectRevert(bytes("Pausable: index is paused"));
- slasher.recordFirstStakeUpdate(operator, serveUntilBlock);
- cheats.stopPrank();
- }
-
- function testRecordFirstStakeUpdate_RevertsWhenCallerDoesntHaveSlashingPermission() external {
- address operator = address(this);
- address contractAddress = address(this);
- uint32 serveUntilBlock = 0;
-
- require(!slasher.canSlash(operator, contractAddress), "improper slashing permission has been given");
-
- cheats.startPrank(contractAddress);
- cheats.expectRevert(bytes("Slasher.onlyRegisteredForService: Operator has not opted into slashing by caller"));
- slasher.recordFirstStakeUpdate(operator, serveUntilBlock);
- cheats.stopPrank();
- }
-
- function testRecordFirstStakeUpdate_RevertsWhenCallerAlreadyInList() external {
- address operator = address(this);
- address contractAddress = address(this);
- uint32 serveUntilBlock = 0;
-
- testRecordFirstStakeUpdate(operator, contractAddress, serveUntilBlock);
-
- cheats.startPrank(contractAddress);
- cheats.expectRevert(bytes("Slasher.recordFirstStakeUpdate: Appending middleware unsuccessful"));
- slasher.recordFirstStakeUpdate(operator, serveUntilBlock);
- cheats.stopPrank();
- }
-
- function testRecordStakeUpdate(
- address operator,
- address contractAddress,
- uint32 prevServeUntilBlock,
- uint32 updateBlock,
- uint32 serveUntilBlock,
- uint256 insertAfter
- )
- public
- filterFuzzedAddressInputs(operator)
- filterFuzzedAddressInputs(contractAddress)
- {
- testRecordFirstStakeUpdate(operator, contractAddress, prevServeUntilBlock);
- _testRecordStakeUpdate(operator, contractAddress, updateBlock, serveUntilBlock, insertAfter);
- }
-
- // internal function corresponding to the bulk of the logic in `testRecordStakeUpdate`, so we can reuse it elsewhere without calling `testOptIntoSlashing`
- function _testRecordStakeUpdate(
- address operator,
- address contractAddress,
- uint32 updateBlock,
- uint32 serveUntilBlock,
- uint256 insertAfter
- )
- internal
- filterFuzzedAddressInputs(operator)
- filterFuzzedAddressInputs(contractAddress)
- {
- // filter out invalid fuzzed inputs. "cannot provide update for future block"
- cheats.assume(updateBlock <= block.number);
-
- linkedListLengthBefore = slasher.operatorWhitelistedContractsLinkedListSize(operator);
- middlewareDetailsBefore = slasher.whitelistedContractDetails(operator, contractAddress);
- contractCanSlashOperatorUntilBefore = slasher.contractCanSlashOperatorUntilBlock(operator, contractAddress);
-
- // filter out invalid fuzzed inputs. "can't push a previous update"
- cheats.assume(updateBlock >= middlewareDetailsBefore.latestUpdateBlock);
-
- // fetch the most recent struct
- middlewareTimesLengthBefore = slasher.middlewareTimesLength(operator);
- ISlasher.MiddlewareTimes memory mostRecentMiddlewareTimesStructBefore = slasher.operatorToMiddlewareTimes(operator, middlewareTimesLengthBefore - 1);
-
- cheats.startPrank(contractAddress);
- slasher.recordStakeUpdate(operator, updateBlock, serveUntilBlock, insertAfter);
- cheats.stopPrank();
-
- ISlasher.MiddlewareTimes memory mostRecentMiddlewareTimesStructAfter = slasher.operatorToMiddlewareTimes(operator, slasher.middlewareTimesLength(operator) - 1);
-
- // check that linked list size remained the same appropriately
- require(slasher.operatorWhitelistedContractsLinkedListSize(operator) == linkedListLengthBefore, "linked list length did increased inappropriately");
- // get the linked list entry for the `contractAddress`
- (nodeExists, prevNode, nextNode) = slasher.operatorWhitelistedContractsLinkedListEntry(operator, contractAddress);
- // verify that the node exists
- require(nodeExists, "node does not exist");
-
- // if the `serveUntilBlock` time is greater than the previous maximum, then an update must have been pushed and the `latestServeUntilBlock` must be equal to the `serveUntilBlock` input
- if (serveUntilBlock > mostRecentMiddlewareTimesStructBefore.latestServeUntilBlock) {
- require(slasher.middlewareTimesLength(operator) == middlewareTimesLengthBefore + 1, "MiddlewareTimes struct not pushed to array");
- require(mostRecentMiddlewareTimesStructAfter.latestServeUntilBlock == serveUntilBlock, "latestServeUntilBlock not updated correctly");
- } else {
- require(mostRecentMiddlewareTimesStructAfter.latestServeUntilBlock == mostRecentMiddlewareTimesStructBefore.latestServeUntilBlock, "latestServeUntilBlock updated incorrectly");
- }
- // if this is the first MiddlewareTimes struct in the array, then an update must have been pushed and the `stalestUpdateBlock` *must* be equal to the current block
- if (middlewareTimesLengthBefore == 0) {
- require(slasher.middlewareTimesLength(operator) == middlewareTimesLengthBefore + 1,
- "MiddlewareTimes struct not pushed to array");
- require(mostRecentMiddlewareTimesStructAfter.stalestUpdateBlock == updateBlock,
- "stalestUpdateBlock not updated correctly -- contractAddress is first list entry");
- // otherwise, we check if the `contractAddress` is the head of the list. If it *is*, then prevNode will be _HEAD, and...
- } else if (prevNode == _HEAD) {
- // if nextNode is _HEAD, then the this indicates that `contractAddress` is actually the only list entry
- if (nextNode != _HEAD) {
- // if nextNode is not the only list entry, then `latestUpdateBlock` should update to a more recent time (if applicable!)
- uint32 nextMiddlewaresLeastRecentUpdateBlock = slasher.whitelistedContractDetails(operator, _uintToAddress(nextNode)).latestUpdateBlock;
- uint32 newValue = (nextMiddlewaresLeastRecentUpdateBlock < updateBlock) ? nextMiddlewaresLeastRecentUpdateBlock: uint32(updateBlock);
- require(mostRecentMiddlewareTimesStructAfter.stalestUpdateBlock == newValue,
- "stalestUpdateBlock not updated correctly -- should have updated to newValue");
- } else {
- require(mostRecentMiddlewareTimesStructAfter.stalestUpdateBlock == updateBlock,
- "stalestUpdateBlock not updated correctly -- contractAddress is only list entry");
- }
- }
-
- middlewareDetailsAfter = slasher.whitelistedContractDetails(operator, contractAddress);
- require(middlewareDetailsAfter.latestUpdateBlock == updateBlock,
- "latestUpdateBlock not updated correctly");
- require(middlewareDetailsAfter.contractCanSlashOperatorUntilBlock == middlewareDetailsBefore.contractCanSlashOperatorUntilBlock,
- "contractCanSlashOperatorUntil changed unexpectedly");
- // check that `contractCanSlashOperatorUntilBlock` did not change
- require(slasher.contractCanSlashOperatorUntilBlock(operator, contractAddress) == contractCanSlashOperatorUntilBefore, "contractCanSlashOperatorUntilBlock changed unexpectedly");
- }
-
- function testRecordStakeUpdate_MultipleLinkedListEntries(
- address operator,
- address contractAddress,
- uint32 prevServeUntilBlock,
- uint32 updateBlock,
- uint32 serveUntilBlock,
- uint256 insertAfter
- )
- public
- filterFuzzedAddressInputs(operator)
- filterFuzzedAddressInputs(contractAddress)
- {
- address _contractAddress = address(this);
- cheats.assume(contractAddress != _contractAddress);
- testRecordFirstStakeUpdate(operator, _contractAddress, prevServeUntilBlock);
- testRecordStakeUpdate(operator, contractAddress, prevServeUntilBlock, updateBlock, serveUntilBlock, insertAfter);
- }
-
- function testRecordStakeUpdate_RevertsWhenCallerNotAlreadyInList(
- address operator,
- address contractAddress,
- uint32 serveUntilBlock,
- uint256 insertAfter
- )
- public
- filterFuzzedAddressInputs(operator)
- filterFuzzedAddressInputs(contractAddress)
- {
- uint32 updateBlock = 0;
-
- testOptIntoSlashing(operator, contractAddress);
-
- cheats.expectRevert(bytes("Slasher.recordStakeUpdate: Removing middleware unsuccessful"));
- cheats.startPrank(contractAddress);
- slasher.recordStakeUpdate(operator, updateBlock, serveUntilBlock, insertAfter);
- cheats.stopPrank();
- }
-
- function testRecordStakeUpdate_RevertsWhenCallerNotAlreadyInList_MultipleLinkedListEntries(
- address operator,
- address contractAddress,
- uint32 prevServeUntilBlock,
- uint32 serveUntilBlock,
- uint256 insertAfter
- )
- public
- filterFuzzedAddressInputs(operator)
- filterFuzzedAddressInputs(contractAddress)
- {
- address _contractAddress = address(this);
- uint32 updateBlock = 0;
-
- cheats.assume(contractAddress != _contractAddress);
-
- testRecordFirstStakeUpdate(operator, _contractAddress, prevServeUntilBlock);
- testOptIntoSlashing(operator, contractAddress);
-
- cheats.expectRevert(bytes("Slasher.recordStakeUpdate: Caller is not the list entrant"));
- cheats.startPrank(contractAddress);
- slasher.recordStakeUpdate(operator, updateBlock, serveUntilBlock, insertAfter);
- cheats.stopPrank();
- }
-
- function testRecordStakeUpdate_RevertsWhenCallerCannotSlash(
- address operator,
- address contractAddress,
- uint32 serveUntilBlock,
- uint256 insertAfter
- )
- public
- filterFuzzedAddressInputs(operator)
- filterFuzzedAddressInputs(contractAddress)
- {
- uint32 updateBlock = 0;
- cheats.expectRevert(bytes("Slasher.onlyRegisteredForService: Operator has not opted into slashing by caller"));
- cheats.startPrank(contractAddress);
- slasher.recordStakeUpdate(operator, updateBlock, serveUntilBlock, insertAfter);
- cheats.stopPrank();
- }
-
-
- function testRecordStakeUpdate_RevertsWhenUpdateBlockInFuture(
- address operator,
- address contractAddress,
- uint32 prevServeUntilBlock,
- uint32 updateBlock,
- uint32 serveUntilBlock,
- uint256 insertAfter
- )
- public
- filterFuzzedAddressInputs(operator)
- filterFuzzedAddressInputs(contractAddress)
- {
- // filter to appropriate fuzzed inputs (appropriate for causing reverts!)
- cheats.assume(updateBlock > block.number);
-
- testRecordFirstStakeUpdate(operator, contractAddress, prevServeUntilBlock);
-
- cheats.expectRevert(bytes("Slasher.recordStakeUpdate: cannot provide update for future block"));
- cheats.startPrank(contractAddress);
- slasher.recordStakeUpdate(operator, updateBlock, serveUntilBlock, insertAfter);
- cheats.stopPrank();
- }
-
- function testRecordLastStakeUpdateAndRevokeSlashingAbility(
- address operator,
- address contractAddress,
- uint32 prevServeUntilBlock,
- uint32 updateBlock,
- uint32 serveUntilBlock,
- uint256 insertAfter
- )
- public
- filterFuzzedAddressInputs(operator)
- filterFuzzedAddressInputs(contractAddress)
- {
- // filter out setting the `serveUntilBlock` time to the MAX, since the contract will revert in this instance.
- cheats.assume(serveUntilBlock != MAX_CAN_SLASH_UNTIL);
-
- testRecordStakeUpdate_MultipleLinkedListEntries(operator, contractAddress, prevServeUntilBlock, updateBlock, serveUntilBlock, insertAfter);
-
- linkedListLengthBefore = slasher.operatorWhitelistedContractsLinkedListSize(operator);
- middlewareDetailsBefore = slasher.whitelistedContractDetails(operator, contractAddress);
-
- ISlasher.MiddlewareTimes memory mostRecentMiddlewareTimesStructBefore;
- // fetch the most recent struct, if at least one exists (otherwise leave the struct uninitialized)
- middlewareTimesLengthBefore = slasher.middlewareTimesLength(operator);
- if (middlewareTimesLengthBefore != 0) {
- mostRecentMiddlewareTimesStructBefore = slasher.operatorToMiddlewareTimes(operator, middlewareTimesLengthBefore - 1);
- }
-
- // get the linked list entry for the `contractAddress`
- (nodeExists, prevNode, nextNode) = slasher.operatorWhitelistedContractsLinkedListEntry(operator, contractAddress);
- require(nodeExists, "node does not exist when it should");
-
- // perform the last stake update and revoke slashing ability, from the `contractAddress`
- cheats.startPrank(contractAddress);
- slasher.recordLastStakeUpdateAndRevokeSlashingAbility(operator, serveUntilBlock);
- cheats.stopPrank();
-
- ISlasher.MiddlewareTimes memory mostRecentMiddlewareTimesStructAfter = slasher.operatorToMiddlewareTimes(operator, slasher.middlewareTimesLength(operator) - 1);
-
- // check that linked list size decrease appropriately
- require(slasher.operatorWhitelistedContractsLinkedListSize(operator) == linkedListLengthBefore - 1, "linked list length did not decrease when it should!");
- // verify that the node no longer exists
- (nodeExists, /*prevNode*/, /*nextNode*/) = slasher.operatorWhitelistedContractsLinkedListEntry(operator, contractAddress);
- require(!nodeExists, "node exists when it should have been deleted");
-
- // if the `serveUntilBlock` time is greater than the previous maximum, then an update must have been pushed and the `latestServeUntilBlock` must be equal to the `serveUntilBlock` input
- if (serveUntilBlock > mostRecentMiddlewareTimesStructBefore.latestServeUntilBlock) {
- require(slasher.middlewareTimesLength(operator) == middlewareTimesLengthBefore + 1, "MiddlewareTimes struct not pushed to array");
- require(mostRecentMiddlewareTimesStructAfter.latestServeUntilBlock == serveUntilBlock, "latestServeUntilBlock not updated correctly");
- } else {
- require(mostRecentMiddlewareTimesStructAfter.latestServeUntilBlock == mostRecentMiddlewareTimesStructBefore.latestServeUntilBlock, "latestServeUntilBlock updated incorrectly");
- }
- // if this is the first MiddlewareTimes struct in the array, then an update must have been pushed and the `stalestUpdateBlock` *must* be equal to the current block
- if (middlewareTimesLengthBefore == 0) {
- require(slasher.middlewareTimesLength(operator) == middlewareTimesLengthBefore + 1,
- "MiddlewareTimes struct not pushed to array");
- require(mostRecentMiddlewareTimesStructAfter.stalestUpdateBlock == block.number,
- "stalestUpdateBlock not updated correctly -- contractAddress is first list entry");
- // otherwise, we check if the `contractAddress` is the head of the list. If it *is*, then prevNode will be _HEAD, and...
- } else if (prevNode == _HEAD) {
- // if nextNode is _HEAD, then the this indicates that `contractAddress` is actually the only list entry
- if (nextNode != _HEAD) {
- // if nextNode is not the only list entry, then `latestUpdateBlock` should update to a more recent time (if applicable!)
- uint32 nextMiddlewaresLeastRecentUpdateBlock = slasher.whitelistedContractDetails(operator, _uintToAddress(nextNode)).latestUpdateBlock;
- uint32 newValue = (nextMiddlewaresLeastRecentUpdateBlock < block.number) ? nextMiddlewaresLeastRecentUpdateBlock: uint32(block.number);
- require(mostRecentMiddlewareTimesStructAfter.stalestUpdateBlock == newValue,
- "stalestUpdateBlock not updated correctly -- should have updated to newValue");
- } else {
- require(mostRecentMiddlewareTimesStructAfter.stalestUpdateBlock == block.number,
- "stalestUpdateBlock not updated correctly -- contractAddress is only list entry");
- }
- }
-
- middlewareDetailsAfter = slasher.whitelistedContractDetails(operator, contractAddress);
- require(middlewareDetailsAfter.latestUpdateBlock == block.number,
- "latestUpdateBlock not updated correctly");
- // check that slashing ability was revoked after `serveUntilBlock`
- require(slasher.contractCanSlashOperatorUntilBlock(operator, contractAddress) == serveUntilBlock, "contractCanSlashOperatorUntil not set correctly");
- }
-
- function testRecordLastStakeUpdateAndRevokeSlashingAbility_RevertsWhenCallerCannotSlash(
- address operator,
- address contractAddress,
- uint32 serveUntilBlock
- )
- public
- filterFuzzedAddressInputs(operator)
- filterFuzzedAddressInputs(contractAddress)
- {
- // filter out setting the `serveUntilBlock` time to the MAX, since the contract will revert in this instance.
- cheats.assume(serveUntilBlock != MAX_CAN_SLASH_UNTIL);
-
- cheats.expectRevert(bytes("Slasher.onlyRegisteredForService: Operator has not opted into slashing by caller"));
- cheats.startPrank(contractAddress);
- slasher.recordLastStakeUpdateAndRevokeSlashingAbility(operator, serveUntilBlock);
- cheats.stopPrank();
- }
-
- function testRecordLastStakeUpdateAndRevokeSlashingAbility_RevertsWhenCallerNotAlreadyInList(
- address operator,
- address contractAddress,
- uint32 serveUntilBlock
- )
- public
- filterFuzzedAddressInputs(operator)
- filterFuzzedAddressInputs(contractAddress)
- {
- // filter out setting the `serveUntilBlock` time to the MAX, since the contract will revert in this instance.
- cheats.assume(serveUntilBlock != MAX_CAN_SLASH_UNTIL);
-
- testOptIntoSlashing(operator, contractAddress);
-
- cheats.expectRevert(bytes("Slasher.recordLastStakeUpdateAndRevokeSlashingAbility: Removing middleware unsuccessful"));
- cheats.startPrank(contractAddress);
- slasher.recordLastStakeUpdateAndRevokeSlashingAbility(operator, serveUntilBlock);
- cheats.stopPrank();
- }
-
- function testRecordLastStakeUpdateAndRevokeSlashingAbility_RevertsWhenServeUntilBlockInputIsMax(
- address operator,
- address contractAddress,
- uint32 prevServeUntilBlock,
- uint32 updateBlock,
- uint256 insertAfter
- )
- public
- filterFuzzedAddressInputs(operator)
- filterFuzzedAddressInputs(contractAddress)
- {
- uint32 serveUntilBlock = MAX_CAN_SLASH_UNTIL;
-
- testOptIntoSlashing(operator, contractAddress);
-
- _testRecordFirstStakeUpdate(operator, contractAddress, prevServeUntilBlock);
- _testRecordStakeUpdate(operator, contractAddress, updateBlock, prevServeUntilBlock, insertAfter);
-
- // perform the last stake update and revoke slashing ability, from the `contractAddress`
- cheats.startPrank(contractAddress);
- cheats.expectRevert(bytes("Slasher._revokeSlashingAbility: serveUntilBlock time must be limited"));
- slasher.recordLastStakeUpdateAndRevokeSlashingAbility(operator, serveUntilBlock);
- cheats.stopPrank();
- }
-
- function _addressToUint(address addr) internal pure returns(uint256) {
- return uint256(uint160(addr));
- }
-
- function _uintToAddress(uint256 x) internal pure returns(address) {
- return address(uint160(x));
- }
-}
\ No newline at end of file