Skip to content

Commit

Permalink
Deneb Mainnet Patch (#395)
Browse files Browse the repository at this point in the history
* init commit

* updated testFullWithdrawalFlow to deneb spec

* added two proof paths

* added both capella and deneb testS

* added testFullWithdrawalFlowCapellaWithdrawalAgainstDenebRoot

* added event

* fixed storage gap

* uncommented testsg

* fix: remove line

* fixed tesst

* added a setter in the EPM for deneForkTimetamp

* tests still broken

* cleanup

* added modifier

* fixing tests

* tests working

* added tests

* comments

* fixed failing test

* fix flaky test

* removed modifier

---------

Co-authored-by: gpsanant <[email protected]>
  • Loading branch information
Sidu28 and gpsanant authored Feb 1, 2024
1 parent 0d1008b commit 842737e
Show file tree
Hide file tree
Showing 16 changed files with 535 additions and 32 deletions.
16 changes: 16 additions & 0 deletions src/contracts/interfaces/IEigenPodManager.sol
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@ interface IEigenPodManager is IPausable {
bytes32 withdrawalRoot
);

event DenebForkTimestampUpdated(uint64 newValue);

/**
* @notice Creates an EigenPod for the sender.
* @dev Function will revert if the `msg.sender` already has an EigenPod.
Expand Down Expand Up @@ -146,4 +148,18 @@ interface IEigenPodManager is IPausable {
* @dev Reverts if `shares` is not a whole Gwei amount
*/
function withdrawSharesAsTokens(address podOwner, address destination, uint256 shares) external;

/**
* @notice the deneb hard fork timestamp used to determine which proof path to use for proving a withdrawal
*/
function denebForkTimestamp() external view returns (uint64);

/**
* setting the deneb hard fork timestamp by the eigenPodManager owner
* @dev this function is designed to be called twice. Once, it is set to type(uint64).max
* prior to the actual deneb fork timestamp being set, and then the second time it is set
* to the actual deneb fork timestamp.
*/
function setDenebForkTimestamp(uint64 newDenebForkTimestamp) external;

}
13 changes: 9 additions & 4 deletions src/contracts/libraries/BeaconChainProofs.sol
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,9 @@ library BeaconChainProofs {
uint256 internal constant VALIDATOR_FIELD_TREE_HEIGHT = 3;

uint256 internal constant NUM_EXECUTION_PAYLOAD_HEADER_FIELDS = 15;
uint256 internal constant EXECUTION_PAYLOAD_HEADER_FIELD_TREE_HEIGHT = 4;
//Note: changed in the deneb hard fork from 4->5
uint256 internal constant EXECUTION_PAYLOAD_HEADER_FIELD_TREE_HEIGHT_DENEB = 5;
uint256 internal constant EXECUTION_PAYLOAD_HEADER_FIELD_TREE_HEIGHT_CAPELLA = 4;

uint256 internal constant NUM_EXECUTION_PAYLOAD_FIELDS = 15;
uint256 internal constant EXECUTION_PAYLOAD_FIELD_TREE_HEIGHT = 4;
Expand Down Expand Up @@ -211,7 +213,8 @@ library BeaconChainProofs {
function verifyWithdrawal(
bytes32 beaconStateRoot,
bytes32[] calldata withdrawalFields,
WithdrawalProof calldata withdrawalProof
WithdrawalProof calldata withdrawalProof,
uint64 denebForkTimestamp
) internal view {
require(
withdrawalFields.length == 2 ** WITHDRAWAL_FIELD_TREE_HEIGHT,
Expand All @@ -232,9 +235,11 @@ library BeaconChainProofs {
"BeaconChainProofs.verifyWithdrawal: historicalSummaryIndex is too large"
);

//Note: post deneb hard fork, the exection payload header fields increased, adding an extra level to the tree height
uint256 executionPayloadHeaderFieldTreeHeight = (getWithdrawalTimestamp(withdrawalProof) < denebForkTimestamp) ? EXECUTION_PAYLOAD_HEADER_FIELD_TREE_HEIGHT_CAPELLA : EXECUTION_PAYLOAD_HEADER_FIELD_TREE_HEIGHT_DENEB;
require(
withdrawalProof.withdrawalProof.length ==
32 * (EXECUTION_PAYLOAD_HEADER_FIELD_TREE_HEIGHT + WITHDRAWALS_TREE_HEIGHT + 1),
32 * (executionPayloadHeaderFieldTreeHeight + WITHDRAWALS_TREE_HEIGHT + 1),
"BeaconChainProofs.verifyWithdrawal: withdrawalProof has incorrect length"
);
require(
Expand All @@ -247,7 +252,7 @@ library BeaconChainProofs {
"BeaconChainProofs.verifyWithdrawal: slotProof has incorrect length"
);
require(
withdrawalProof.timestampProof.length == 32 * (EXECUTION_PAYLOAD_HEADER_FIELD_TREE_HEIGHT),
withdrawalProof.timestampProof.length == 32 * (executionPayloadHeaderFieldTreeHeight),
"BeaconChainProofs.verifyWithdrawal: timestampProof has incorrect length"
);

Expand Down
3 changes: 2 additions & 1 deletion src/contracts/pods/EigenPod.sol
Original file line number Diff line number Diff line change
Expand Up @@ -607,7 +607,8 @@ contract EigenPod is IEigenPod, Initializable, ReentrancyGuardUpgradeable, Eigen
BeaconChainProofs.verifyWithdrawal({
beaconStateRoot: beaconStateRoot,
withdrawalFields: withdrawalFields,
withdrawalProof: withdrawalProof
withdrawalProof: withdrawalProof,
denebForkTimestamp: eigenPodManager.denebForkTimestamp()
});

uint40 validatorIndex = withdrawalFields.getValidatorIndex();
Expand Down
22 changes: 22 additions & 0 deletions src/contracts/pods/EigenPodManager.sol
Original file line number Diff line number Diff line change
Expand Up @@ -241,6 +241,28 @@ contract EigenPodManager is
_updateBeaconChainOracle(newBeaconChainOracle);
}

/**
* @notice Sets the timestamp of the Deneb fork.
* @param newDenebForkTimestamp is the new timestamp of the Deneb fork
*/
function setDenebForkTimestamp(uint64 newDenebForkTimestamp) external onlyOwner {

/**
* @notice Modifier to ensure that the deneb fork timestamp is not set
* @dev Note that this function is only ever meant to be called twice. First, it will be called to set the timestamp
* to type(uint64).max, and then it will be called to set the timestamp to the actual timestamp
*/
require(newDenebForkTimestamp != 0, "EigenPodManager.denebForkEnabled: cannot set newDenebForkTimestamp to 0");
if(newDenebForkTimestamp == type(uint64).max){
require(denebForkTimestamp == 0, "EigenPodManager.denebForkEnabled: denebForkTimestamp must not be set yet");
} else {
require(denebForkTimestamp ==type(uint64).max, "EigenPodManager.denebForkEnabled: Deneb fork timestamp cannot be set");
}

denebForkTimestamp = newDenebForkTimestamp;
emit DenebForkTimestampUpdated(denebForkTimestamp);
}

// INTERNAL FUNCTIONS

function _deployPod() internal returns (IEigenPod) {
Expand Down
4 changes: 3 additions & 1 deletion src/contracts/pods/EigenPodManagerStorage.sol
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,8 @@ abstract contract EigenPodManagerStorage is IEigenPodManager {
*/
mapping(address => int256) public podOwnerShares;

uint64 public denebForkTimestamp;

constructor(
IETHPOSDeposit _ethPOS,
IBeacon _eigenPodBeacon,
Expand All @@ -83,5 +85,5 @@ abstract contract EigenPodManagerStorage is IEigenPodManager {
* variables without shifting down storage in the inheritance chain.
* See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps
*/
uint256[45] private __gap;
uint256[44] private __gap;
}
53 changes: 47 additions & 6 deletions src/test/EigenPod.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ contract EigenPodTests is ProofParsing, EigenPodPausingConstants {


uint256 internal constant GWEI_TO_WEI = 1e9;
uint64 public constant DENEB_FORK_TIMESTAMP_GOERLI = 1705473120;


bytes pubkey =
hex"88347ed1c492eedc97fc8c506a35d44d81f27a0c7a1c661b35913cfd15256c0cccbd34a83341f505c7de2983292f2cab";
Expand All @@ -24,6 +26,8 @@ contract EigenPodTests is ProofParsing, EigenPodPausingConstants {

address podOwner = address(42000094993494);

bool public IS_DENEB;

Vm cheats = Vm(HEVM_ADDRESS);
DelegationManager public delegation;
IStrategyManager public strategyManager;
Expand Down Expand Up @@ -255,6 +259,8 @@ contract EigenPodTests is ProofParsing, EigenPodPausingConstants {
)
);

eigenPodManager.setDenebForkTimestamp(type(uint64).max);

cheats.deal(address(podOwner), 5 * stakeAmount);

fuzzedAddressMapping[address(0)] = true;
Expand Down Expand Up @@ -502,6 +508,37 @@ contract EigenPodTests is ProofParsing, EigenPodPausingConstants {
}

/// @notice This test is to ensure the full withdrawal flow works
function testFullWithdrawalFlowDeneb() public returns (IEigenPod) {
eigenPodManager.setDenebForkTimestamp(DENEB_FORK_TIMESTAMP_GOERLI);
IS_DENEB = true;
//this call is to ensure that validator 302913 has proven their withdrawalcreds
// ./solidityProofGen -newBalance=32000115173 "ValidatorFieldsProof" 302913 true "data/withdrawal_proof_goerli/goerli_block_header_6399998.json" "data/withdrawal_proof_goerli/goerli_slot_6399998.json" "withdrawal_credential_proof_302913.json"
setJSON("./src/test/test-data/withdrawal_credential_proof_302913.json");
_testDeployAndVerifyNewEigenPod(podOwner, signature, depositDataRoot);
IEigenPod newPod = eigenPodManager.getPod(podOwner);

//Deneb: ./solidityProofGen/solidityProofGen "WithdrawalFieldsProof" 302913 271 8191 true false "data/deneb_goerli_block_header_7431952.json" "data/deneb_goerli_slot_7431952.json" "data/deneb_goerli_slot_7421952.json" "data/deneb_goerli_block_header_7421951.json" "data/deneb_goerli_block_7421951.json" "fullWithdrawalProof_Latest.json" false false
// To get block header: curl -H "Accept: application/json" 'https://eigenlayer.spiceai.io/goerli/beacon/eth/v1/beacon/headers/6399000?api_key\="343035|f6ebfef661524745abb4f1fd908a76e8"' > block_header_6399000.json
// To get block: curl -H "Accept: application/json" 'https://eigenlayer.spiceai.io/goerli/beacon/eth/v2/beacon/blocks/6399000?api_key\="343035|f6ebfef661524745abb4f1fd908a76e8"' > block_6399000.json
setJSON("./src/test/test-data/fullWithdrawalDeneb.json");
return _proveWithdrawalForPod(newPod);
}

function testFullWithdrawalFlowCapellaWithdrawalAgainstDenebRoot() public returns (IEigenPod) {
IS_DENEB = false;
//this call is to ensure that validator 302913 has proven their withdrawalcreds
// ./solidityProofGen/solidityProofGen "WithdrawalFieldsProof" 302913 146 8092 true false "data/deneb_goerli_block_header_7431952.json" "data/deneb_goerli_slot_7431952.json" "data/goerli_slot_6397952.json" "data/goerli_block_header_6397852.json" "data/goerli_block_6397852.json" "fullWithdrawalProof_CapellaAgainstDeneb.json" false true
setJSON("./src/test/test-data/withdrawal_credential_proof_302913.json");
_testDeployAndVerifyNewEigenPod(podOwner, signature, depositDataRoot);
IEigenPod newPod = eigenPodManager.getPod(podOwner);

//Deneb: ./solidityProofGen/solidityProofGen "WithdrawalFieldsProof" 302913 271 8191 true false "data/deneb_goerli_block_header_7431952.json" "data/deneb_goerli_slot_7431952.json" "data/deneb_goerli_slot_7421952.json" "data/deneb_goerli_block_header_7421951.json" "data/deneb_goerli_block_7421951.json" "fullWithdrawalProof_Latest.json" false
// To get block header: curl -H "Accept: application/json" 'https://eigenlayer.spiceai.io/goerli/beacon/eth/v1/beacon/headers/6399000?api_key\="343035|f6ebfef661524745abb4f1fd908a76e8"' > block_header_6399000.json
// To get block: curl -H "Accept: application/json" 'https://eigenlayer.spiceai.io/goerli/beacon/eth/v2/beacon/blocks/6399000?api_key\="343035|f6ebfef661524745abb4f1fd908a76e8"' > block_6399000.json
setJSON("./src/test/test-data/fullWithdrawalCapellaAgainstDenebRoot.json");
return _proveWithdrawalForPod(newPod);
}

function testFullWithdrawalFlow() public returns (IEigenPod) {
//this call is to ensure that validator 302913 has proven their withdrawalcreds
// ./solidityProofGen -newBalance=32000115173 "ValidatorFieldsProof" 302913 true "data/withdrawal_proof_goerli/goerli_block_header_6399998.json" "data/withdrawal_proof_goerli/goerli_slot_6399998.json" "withdrawal_credential_proof_302913.json"
Expand Down Expand Up @@ -835,7 +872,6 @@ contract EigenPodTests is ProofParsing, EigenPodPausingConstants {
emit log("hello");

IEigenPod newPod = _testDeployAndVerifyNewEigenPod(podOwner, signature, depositDataRoot);
emit log("hello");
//./solidityProofGen "WithdrawalFieldsProof" 302913 146 8092 true false "data/withdrawal_proof_goerli/goerli_block_header_6399998.json" "data/withdrawal_proof_goerli/goerli_slot_6399998.json" "data/withdrawal_proof_goerli/goerli_slot_6397852.json" "data/withdrawal_proof_goerli/goerli_block_header_6397852.json" "data/withdrawal_proof_goerli/goerli_block_6397852.json" "fullWithdrawalProof_Latest.json" false
// To get block header: curl -H "Accept: application/json" 'https://eigenlayer.spiceai.io/goerli/beacon/eth/v1/beacon/headers/6399000?api_key\="343035|f6ebfef661524745abb4f1fd908a76e8"' > block_header_6399000.json
// To get block: curl -H "Accept: application/json" 'https://eigenlayer.spiceai.io/goerli/beacon/eth/v2/beacon/blocks/6399000?api_key\="343035|f6ebfef661524745abb4f1fd908a76e8"' > block_6399000.json
Expand Down Expand Up @@ -1475,7 +1511,7 @@ contract EigenPodTests is ProofParsing, EigenPodPausingConstants {
uint64 withdrawalAmountGwei = Endian.fromLittleEndianUint64(
withdrawalFields[BeaconChainProofs.WITHDRAWAL_VALIDATOR_AMOUNT_INDEX]
);

emit log_named_uint("withdrawalAmountGwei", withdrawalAmountGwei);
uint64 leftOverBalanceWEI = uint64(withdrawalAmountGwei - newPod.MAX_RESTAKED_BALANCE_GWEI_PER_VALIDATOR()) *
uint64(GWEI_TO_WEI);
cheats.deal(address(newPod), leftOverBalanceWEI);
Expand Down Expand Up @@ -1727,18 +1763,22 @@ contract EigenPodTests is ProofParsing, EigenPodPausingConstants {
eigenPodManager.stake{value: stakeAmount}(pubkey, signature, depositDataRoot);
cheats.stopPrank();

if(!IS_DENEB){
emit log("NOT DENEB");
}
bytes memory withdrawalProof = IS_DENEB ? abi.encodePacked(getWithdrawalProofDeneb()) : abi.encodePacked(getWithdrawalProofCapella());
bytes memory timestampProof = IS_DENEB ? abi.encodePacked(getTimestampProofDeneb()) : abi.encodePacked(getTimestampProofCapella());
{
bytes32 blockRoot = getBlockRoot();
bytes32 slotRoot = getSlotRoot();
bytes32 timestampRoot = getTimestampRoot();
bytes32 executionPayloadRoot = getExecutionPayloadRoot();

return
BeaconChainProofs.WithdrawalProof(
abi.encodePacked(getWithdrawalProof()),
abi.encodePacked(withdrawalProof),
abi.encodePacked(getSlotProof()),
abi.encodePacked(getExecutionPayloadProof()),
abi.encodePacked(getTimestampProof()),
abi.encodePacked(timestampProof),
abi.encodePacked(getHistoricalSummaryProof()),
uint64(getBlockRootIndex()),
uint64(getHistoricalSummaryIndex()),
Expand All @@ -1750,6 +1790,7 @@ contract EigenPodTests is ProofParsing, EigenPodPausingConstants {
);
}
}


function _setOracleBlockRoot() internal {
bytes32 latestBlockRoot = getLatestBlockRoot();
Expand Down Expand Up @@ -1778,7 +1819,7 @@ contract Relayer is Test {
bytes32[] calldata withdrawalFields,
BeaconChainProofs.WithdrawalProof calldata proofs
) public view {
BeaconChainProofs.verifyWithdrawal(beaconStateRoot, withdrawalFields, proofs);
BeaconChainProofs.verifyWithdrawal(beaconStateRoot, withdrawalFields, proofs, type(uint64).max);
}
}

Expand Down
4 changes: 4 additions & 0 deletions src/test/events/IEigenPodManagerEvents.sol
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@ interface IEigenPodManagerEvents {
/// @notice Emitted to notify the update of the beaconChainOracle address
event BeaconOracleUpdated(address indexed newOracleAddress);

/// @notice Emitted to notify that the denebForkTimestamp has been set
event DenebForkTimestampUpdated(uint64 denebForkTimestamp);


/// @notice Emitted to notify the deployment of an EigenPod
event PodDeployed(address indexed eigenPod, address indexed podOwner);

Expand Down
5 changes: 5 additions & 0 deletions src/test/integration/IntegrationDeployer.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -272,6 +272,11 @@ abstract contract IntegrationDeployer is Test, IUserDeployer {

// Create mock beacon chain / proof gen interface
beaconChain = new BeaconChainMock(timeMachine, beaconChainOracle);



//set deneb fork timestamp
eigenPodManager.setDenebForkTimestamp(type(uint64).max);
}

/// @dev Deploy a strategy and its underlying token, push to global lists of tokens/strategies, and whitelist
Expand Down
21 changes: 15 additions & 6 deletions src/test/integration/mocks/BeaconChainMock.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -694,19 +694,28 @@ contract BeaconChainMock is Test {
(BeaconChainProofs.VALIDATOR_TREE_HEIGHT + 1) + BeaconChainProofs.BEACON_STATE_FIELD_TREE_HEIGHT
);

uint immutable WITHDRAWAL_PROOF_LEN = 32 * (
BeaconChainProofs.EXECUTION_PAYLOAD_HEADER_FIELD_TREE_HEIGHT +
uint immutable WITHDRAWAL_PROOF_LEN_CAPELLA = 32 * (
BeaconChainProofs.EXECUTION_PAYLOAD_HEADER_FIELD_TREE_HEIGHT_CAPELLA +
BeaconChainProofs.WITHDRAWALS_TREE_HEIGHT + 1
);

uint immutable WITHDRAWAL_PROOF_LEN_DENEB= 32 * (
BeaconChainProofs.EXECUTION_PAYLOAD_HEADER_FIELD_TREE_HEIGHT_DENEB +
BeaconChainProofs.WITHDRAWALS_TREE_HEIGHT + 1
);

uint immutable EXECPAYLOAD_PROOF_LEN = 32 * (
BeaconChainProofs.BEACON_BLOCK_HEADER_FIELD_TREE_HEIGHT +
BeaconChainProofs.BEACON_BLOCK_BODY_FIELD_TREE_HEIGHT
);
uint immutable SLOT_PROOF_LEN = 32 * (
BeaconChainProofs.BEACON_BLOCK_HEADER_FIELD_TREE_HEIGHT
);
uint immutable TIMESTAMP_PROOF_LEN = 32 * (
BeaconChainProofs.EXECUTION_PAYLOAD_HEADER_FIELD_TREE_HEIGHT
uint immutable TIMESTAMP_PROOF_LEN_CAPELLA = 32 * (
BeaconChainProofs.EXECUTION_PAYLOAD_HEADER_FIELD_TREE_HEIGHT_CAPELLA
);
uint immutable TIMESTAMP_PROOF_LEN_DENEB = 32 * (
BeaconChainProofs.EXECUTION_PAYLOAD_HEADER_FIELD_TREE_HEIGHT_DENEB
);
uint immutable HISTSUMMARY_PROOF_LEN = 32 * (
BeaconChainProofs.BEACON_STATE_FIELD_TREE_HEIGHT +
Expand All @@ -725,10 +734,10 @@ contract BeaconChainMock is Test {
uint64 oracleTimestamp
) internal view returns (BeaconChainProofs.WithdrawalProof memory) {
return BeaconChainProofs.WithdrawalProof({
withdrawalProof: new bytes(WITHDRAWAL_PROOF_LEN),
withdrawalProof: new bytes(WITHDRAWAL_PROOF_LEN_CAPELLA),
slotProof: new bytes(SLOT_PROOF_LEN),
executionPayloadProof: new bytes(EXECPAYLOAD_PROOF_LEN),
timestampProof: new bytes(TIMESTAMP_PROOF_LEN),
timestampProof: new bytes(TIMESTAMP_PROOF_LEN_CAPELLA),
historicalSummaryBlockRootProof: new bytes(HISTSUMMARY_PROOF_LEN),
blockRootIndex: 0,
historicalSummaryIndex: 0,
Expand Down
7 changes: 7 additions & 0 deletions src/test/mocks/EigenPodManagerMock.sol
Original file line number Diff line number Diff line change
Expand Up @@ -85,4 +85,11 @@ contract EigenPodManagerMock is IEigenPodManager, Test {
function numPods() external view returns (uint256) {}

function maxPods() external view returns (uint256) {}


function denebForkTimestamp() external view returns (uint64){
return type(uint64).max;
}

function setDenebForkTimestamp(uint64 timestamp) external{}
}
Loading

0 comments on commit 842737e

Please sign in to comment.