Skip to content

Commit

Permalink
added regression test for EIG14
Browse files Browse the repository at this point in the history
  • Loading branch information
Sidu28 committed Oct 6, 2023
1 parent 6dffb15 commit 3acc5ea
Show file tree
Hide file tree
Showing 5 changed files with 93 additions and 42 deletions.
3 changes: 3 additions & 0 deletions src/contracts/interfaces/IEigenPod.sol
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,9 @@ interface IEigenPod {
/// @notice the amount of execution layer ETH in this contract that is staked in EigenLayer (i.e. withdrawn from beaconchain but not EigenLayer),
function withdrawableRestakedExecutionLayerGwei() external view returns (uint64);

/// @notice any ETH deposited into the EigenPod contract via the `receive` fallback function
function nonBeaconChainETHBalanceWei() external view returns (uint256);

/// @notice Used to initialize the pointers to contracts crucial to the pod's functionality, in beacon proxy construction from EigenPodManager
function initialize(address owner) external;

Expand Down
1 change: 1 addition & 0 deletions src/contracts/pods/EigenPod.sol
Original file line number Diff line number Diff line change
Expand Up @@ -727,6 +727,7 @@ contract EigenPod is IEigenPod, Initializable, ReentrancyGuardUpgradeable, Eigen

function _processWithdrawalBeforeRestaking(address _podOwner) internal {
mostRecentWithdrawalTimestamp = uint32(block.timestamp);
nonBeaconChainETHBalanceWei = 0;
_sendETH_AsDelayedWithdrawal(_podOwner, address(this).balance);
}

Expand Down
92 changes: 50 additions & 42 deletions src/test/EigenPod.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -416,48 +416,7 @@ contract EigenPodTests is ProofParsing, EigenPodPausingConstants {
// 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/fullWithdrawalProof_Latest.json");
BeaconChainOracleMock(address(beaconChainOracle)).setOracleBlockRootAtTimestamp(getLatestBlockRoot());
uint64 restakedExecutionLayerGweiBefore = newPod.withdrawableRestakedExecutionLayerGwei();

withdrawalFields = getWithdrawalFields();
uint64 withdrawalAmountGwei = Endian.fromLittleEndianUint64(withdrawalFields[BeaconChainProofs.WITHDRAWAL_VALIDATOR_AMOUNT_INDEX]);

uint64 leftOverBalanceWEI = uint64(withdrawalAmountGwei - newPod.MAX_RESTAKED_BALANCE_GWEI_PER_VALIDATOR()) * uint64(GWEI_TO_WEI);
uint40 validatorIndex = uint40(Endian.fromLittleEndianUint64(withdrawalFields[BeaconChainProofs.WITHDRAWAL_VALIDATOR_INDEX_INDEX]));
cheats.deal(address(newPod), leftOverBalanceWEI);
emit log_named_uint("leftOverBalanceWEI", leftOverBalanceWEI);
emit log_named_uint("address(newPod)", address(newPod).balance);
emit log_named_uint("withdrawalAmountGwei", withdrawalAmountGwei);

uint256 delayedWithdrawalRouterContractBalanceBefore = address(delayedWithdrawalRouter).balance;
{
BeaconChainProofs.WithdrawalProof[] memory withdrawalProofsArray = new BeaconChainProofs.WithdrawalProof[](1);
withdrawalProofsArray[0] = _getWithdrawalProof();
bytes[] memory validatorFieldsProofArray = new bytes[](1);
validatorFieldsProofArray[0] = abi.encodePacked(getValidatorProof());
bytes32[][] memory validatorFieldsArray = new bytes32[][](1);
validatorFieldsArray[0] = getValidatorFields();
bytes32[][] memory withdrawalFieldsArray = new bytes32[][](1);
withdrawalFieldsArray[0] = withdrawalFields;

BeaconChainProofs.StateRootProof memory stateRootProofStruct = _getStateRootProof();


//cheats.expectEmit(true, true, true, true, address(newPod));
emit FullWithdrawalRedeemed(validatorIndex, _computeTimestampAtSlot(Endian.fromLittleEndianUint64(withdrawalProofsArray[0].slotRoot)), podOwner, withdrawalAmountGwei);
newPod.verifyAndProcessWithdrawals(0, stateRootProofStruct, withdrawalProofsArray, validatorFieldsProofArray, validatorFieldsArray, withdrawalFieldsArray);
}
require(newPod.withdrawableRestakedExecutionLayerGwei() - restakedExecutionLayerGweiBefore == newPod.MAX_RESTAKED_BALANCE_GWEI_PER_VALIDATOR(),
"restakedExecutionLayerGwei has not been incremented correctly");
require(address(delayedWithdrawalRouter).balance - delayedWithdrawalRouterContractBalanceBefore == leftOverBalanceWEI,
"pod delayed withdrawal balance hasn't been updated correctly");
require(newPod.validatorPubkeyHashToInfo(getValidatorPubkeyHash()).restakedBalanceGwei == 0, "balance not reset correctly");

cheats.roll(block.number + WITHDRAWAL_DELAY_BLOCKS + 1);
uint256 podOwnerBalanceBefore = address(podOwner).balance;
delayedWithdrawalRouter.claimDelayedWithdrawals(podOwner, 1);
require(address(podOwner).balance - podOwnerBalanceBefore == leftOverBalanceWEI, "Pod owner balance hasn't been updated correctly");
return newPod;
return _proveWithdrawalForPod(newPod);
}

/**
Expand Down Expand Up @@ -1210,6 +1169,51 @@ contract EigenPodTests is ProofParsing, EigenPodPausingConstants {
"EigenPod invariant violated: sharesInSM != withdrawableRestakedExecutionLayerGwei");
}

function _proveWithdrawalForPod(IEigenPod newPod) internal returns(IEigenPod) {
BeaconChainOracleMock(address(beaconChainOracle)).setOracleBlockRootAtTimestamp(getLatestBlockRoot());
uint64 restakedExecutionLayerGweiBefore = newPod.withdrawableRestakedExecutionLayerGwei();

withdrawalFields = getWithdrawalFields();
uint64 withdrawalAmountGwei = Endian.fromLittleEndianUint64(withdrawalFields[BeaconChainProofs.WITHDRAWAL_VALIDATOR_AMOUNT_INDEX]);

uint64 leftOverBalanceWEI = uint64(withdrawalAmountGwei - newPod.MAX_RESTAKED_BALANCE_GWEI_PER_VALIDATOR()) * uint64(GWEI_TO_WEI);
uint40 validatorIndex = uint40(Endian.fromLittleEndianUint64(withdrawalFields[BeaconChainProofs.WITHDRAWAL_VALIDATOR_INDEX_INDEX]));
cheats.deal(address(newPod), leftOverBalanceWEI);
emit log_named_uint("leftOverBalanceWEI", leftOverBalanceWEI);
emit log_named_uint("address(newPod)", address(newPod).balance);
emit log_named_uint("withdrawalAmountGwei", withdrawalAmountGwei);

uint256 delayedWithdrawalRouterContractBalanceBefore = address(delayedWithdrawalRouter).balance;
{
BeaconChainProofs.WithdrawalProof[] memory withdrawalProofsArray = new BeaconChainProofs.WithdrawalProof[](1);
withdrawalProofsArray[0] = _getWithdrawalProof();
bytes[] memory validatorFieldsProofArray = new bytes[](1);
validatorFieldsProofArray[0] = abi.encodePacked(getValidatorProof());
bytes32[][] memory validatorFieldsArray = new bytes32[][](1);
validatorFieldsArray[0] = getValidatorFields();
bytes32[][] memory withdrawalFieldsArray = new bytes32[][](1);
withdrawalFieldsArray[0] = withdrawalFields;

BeaconChainProofs.StateRootProof memory stateRootProofStruct = _getStateRootProof();


//cheats.expectEmit(true, true, true, true, address(newPod));
emit FullWithdrawalRedeemed(validatorIndex, _computeTimestampAtSlot(Endian.fromLittleEndianUint64(withdrawalProofsArray[0].slotRoot)), podOwner, withdrawalAmountGwei);
newPod.verifyAndProcessWithdrawals(0, stateRootProofStruct, withdrawalProofsArray, validatorFieldsProofArray, validatorFieldsArray, withdrawalFieldsArray);
}
require(newPod.withdrawableRestakedExecutionLayerGwei() - restakedExecutionLayerGweiBefore == newPod.MAX_RESTAKED_BALANCE_GWEI_PER_VALIDATOR(),
"restakedExecutionLayerGwei has not been incremented correctly");
require(address(delayedWithdrawalRouter).balance - delayedWithdrawalRouterContractBalanceBefore == leftOverBalanceWEI,
"pod delayed withdrawal balance hasn't been updated correctly");
require(newPod.validatorPubkeyHashToInfo(getValidatorPubkeyHash()).restakedBalanceGwei == 0, "balance not reset correctly");

cheats.roll(block.number + WITHDRAWAL_DELAY_BLOCKS + 1);
uint256 podOwnerBalanceBefore = address(podOwner).balance;
delayedWithdrawalRouter.claimDelayedWithdrawals(podOwner, 1);
require(address(podOwner).balance - podOwnerBalanceBefore == leftOverBalanceWEI, "Pod owner balance hasn't been updated correctly");

}

// simply tries to register 'sender' as a delegate, setting their 'DelegationTerms' contract in DelegationManager to 'dt'
// verifies that the storage of DelegationManager contract is updated appropriately
function _testRegisterAsOperator(address sender, IDelegationManager.OperatorDetails memory operatorDetails) internal {
Expand Down Expand Up @@ -1302,6 +1306,10 @@ contract EigenPodTests is ProofParsing, EigenPodPausingConstants {
eigenPodManager.stake{value: stakeAmount}(pubkey, _signature, _depositDataRoot);
cheats.stopPrank();

return _verifyWithdrawalCredentials(newPod, _podOwner);
}

function _verifyWithdrawalCredentials(IEigenPod newPod, address _podOwner) internal returns(IEigenPod) {
uint64 timestamp = 0;
// cheats.expectEmit(true, true, true, true, address(newPod));
// emit ValidatorRestaked(validatorIndex);
Expand Down
2 changes: 2 additions & 0 deletions src/test/mocks/EigenPodMock.sol
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ contract EigenPodMock is IEigenPod, Test {
/// @notice The max amount of eth, in gwei, that can be restaked per validator
function MAX_RESTAKED_BALANCE_GWEI_PER_VALIDATOR() external view returns(uint64) {}

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

/// @notice the amount of execution layer ETH in this contract that is staked in EigenLayer (i.e. withdrawn from beaconchain but not EigenLayer),
function withdrawableRestakedExecutionLayerGwei() external view returns(uint64) {}

Expand Down
37 changes: 37 additions & 0 deletions src/test/unit/EigenPodUnit.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -231,4 +231,41 @@ contract EigenPodUnitTests is EigenPodTests {
cheats.stopPrank();
}

function testPodReceiveFallBack(uint256 amountETH) external {
cheats.assume(amountETH > 0);
setJSON("./src/test/test-data/withdrawal_credential_proof_302913.json");
_testDeployAndVerifyNewEigenPod(podOwner, signature, depositDataRoot);
IEigenPod pod = eigenPodManager.getPod(podOwner);
cheats.deal(address(this), amountETH);

Address.sendValue(payable(address(pod)), amountETH);
}

/**
* This is a regression test for a bug (EIG-14) found by Hexens. Lets say podOwner sends 32 ETH to the EigenPod,
* the nonBeaconChainETHBalanceWei increases by 32 ETH. podOwner calls withdrawBeforeRestaking, which
* will simply send the entire ETH balance (32 ETH) to the owner. The owner activates restaking,
* creates a validator and verifies the withdrawal credentials, receiving 32 ETH in shares.
* They can exit the validator, the pod gets the 32ETH and they can call withdrawNonBeaconChainETHBalanceWei
* And simply withdraw the 32ETH because nonBeaconChainETHBalanceWei is 32ETH.
*/
function testValidatorBalanceCannotBeRemovedFromPodViaNonBeaconChainETHBalanceWei() external {
cheats.startPrank(podOwner);
IEigenPod newPod = eigenPodManager.getPod(podOwner);
cheats.expectEmit(true, true, true, true, address(newPod));
emit EigenPodStaked(pubkey);
eigenPodManager.stake{value: stakeAmount}(pubkey, signature, depositDataRoot);
cheats.stopPrank();

uint256 amount = 32 ether;

cheats.deal(address(this), amount);
Address.sendValue(payable(address(newPod)), amount);
require(newPod.nonBeaconChainETHBalanceWei() == amount, "nonBeaconChainETHBalanceWei should be 32 ETH");
//this is an M1 pod so hasRestaked should be false
require(newPod.hasRestaked() == false, "Pod should be restaked");
pod.activateRestaking();
require(newPod.nonBeaconChainETHBalanceWei() == 0, "nonBeaconChainETHBalanceWei should be 32 ETH");
}

}

0 comments on commit 3acc5ea

Please sign in to comment.