From c1582fee46c0e6e3dbed8d9788b5d5b67c03fefe Mon Sep 17 00:00:00 2001 From: call-by Date: Wed, 17 Jul 2024 10:56:23 -0400 Subject: [PATCH] Revert "Fix/native restaking withdraw" --- .vscode/settings.json | 6 - package.json | 2 +- src/.solhint.json | 7 +- src/core/ClientGatewayLzReceiver.sol | 25 +- src/core/ExoCapsule.sol | 156 +++++------ src/core/NativeRestakingController.sol | 35 ++- src/interfaces/IExoCapsule.sol | 24 +- src/interfaces/INativeRestakingController.sol | 36 +-- src/libraries/BeaconChainProofs.sol | 191 ++++--------- src/libraries/WithdrawalContainer.sol | 3 - src/storage/ClientChainGatewayStorage.sol | 1 + src/storage/ExoCapsuleStorage.sol | 9 - test/foundry/DepositWithdrawPrinciple.t.sol | 140 +--------- test/foundry/ExocoreDeployer.t.sol | 61 ++--- .../test-data/full_withdrawal_proof.json | 159 ----------- .../test-data/partial_withdrawal_proof.json | 159 ----------- test/foundry/unit/ExoCapsule.t.sol | 252 +----------------- 17 files changed, 193 insertions(+), 1073 deletions(-) delete mode 100644 .vscode/settings.json delete mode 100644 test/foundry/test-data/full_withdrawal_proof.json delete mode 100644 test/foundry/test-data/partial_withdrawal_proof.json diff --git a/.vscode/settings.json b/.vscode/settings.json deleted file mode 100644 index dbff0926..00000000 --- a/.vscode/settings.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "[solidity]": { - "editor.defaultFormatter": "JuanBlanco.solidity" - }, - "solidity.formatter": "forge" -} diff --git a/package.json b/package.json index 2589c133..e0890ef9 100644 --- a/package.json +++ b/package.json @@ -96,7 +96,7 @@ "death": "^1.1.0", "debug": "^4.3.4", "decamelize": "^4.0.0", - "decimal.js": "10.4.3", + "decimal.js":"10.4.3", "deep-eql": "^4.1.3", "deep-extend": "^0.6.0", "deep-is": "^0.1.4", diff --git a/src/.solhint.json b/src/.solhint.json index c7c57638..fb01f2c8 100644 --- a/src/.solhint.json +++ b/src/.solhint.json @@ -1,9 +1,9 @@ { "extends": "solhint:recommended", "rules": { - "max-line-length": ["error", 128], + "max-line-length": ["error", 121], "compiler-version": ["error", "^0.8.0"], - "func-visibility": ["warn", { "ignoreConstructors": true }], + "func-visibility": ["warn", {"ignoreConstructors": true}], "no-inline-assembly": "off", "no-empty-blocks": "off", "no-unused-vars": "error", @@ -13,7 +13,6 @@ "max-states-count": "off", "reason-string": "off", "gas-custom-errors": "off", - "state-visibility": "error", - "no-complex-fallback": "off" + "state-visibility": "error" } } diff --git a/src/core/ClientGatewayLzReceiver.sol b/src/core/ClientGatewayLzReceiver.sol index f0813615..f945c52e 100644 --- a/src/core/ClientGatewayLzReceiver.sol +++ b/src/core/ClientGatewayLzReceiver.sol @@ -14,9 +14,6 @@ abstract contract ClientGatewayLzReceiver is PausableUpgradeable, OAppReceiverUp error DepositShouldNotFailOnExocore(address token, address depositor); error InvalidAddWhitelistTokensRequest(uint256 expectedLength, uint256 actualLength); - // Events - event WithdrawFailedOnExocore(address indexed token, address indexed withdrawer); - modifier onlyCalledFromThis() { require( msg.sender == address(this), @@ -114,24 +111,14 @@ abstract contract ClientGatewayLzReceiver is PausableUpgradeable, OAppReceiverUp bool success = (uint8(bytes1(responsePayload[0])) == 1); uint256 lastlyUpdatedPrincipalBalance = uint256(bytes32(responsePayload[1:33])); + if (success) { + IVault vault = _getVault(token); - if (!success) { - emit WithdrawFailedOnExocore(token, withdrawer); - } else { - if (token == VIRTUAL_STAKED_ETH_ADDRESS) { - IExoCapsule capsule = _getCapsule(withdrawer); - - capsule.updatePrincipalBalance(lastlyUpdatedPrincipalBalance); - capsule.updateWithdrawableBalance(unlockPrincipalAmount); - } else { - IVault vault = _getVault(token); - - vault.updatePrincipalBalance(withdrawer, lastlyUpdatedPrincipalBalance); - vault.updateWithdrawableBalance(withdrawer, unlockPrincipalAmount, 0); - } - - emit WithdrawPrincipalResult(success, token, withdrawer, unlockPrincipalAmount); + vault.updatePrincipalBalance(withdrawer, lastlyUpdatedPrincipalBalance); + vault.updateWithdrawableBalance(withdrawer, unlockPrincipalAmount, 0); } + + emit WithdrawPrincipalResult(success, token, withdrawer, unlockPrincipalAmount); } function afterReceiveWithdrawRewardResponse(bytes memory requestPayload, bytes calldata responsePayload) diff --git a/src/core/ExoCapsule.sol b/src/core/ExoCapsule.sol index 0a475153..7b5a9899 100644 --- a/src/core/ExoCapsule.sol +++ b/src/core/ExoCapsule.sol @@ -4,45 +4,27 @@ import {IExoCapsule} from "../interfaces/IExoCapsule.sol"; import {INativeRestakingController} from "../interfaces/INativeRestakingController.sol"; import {BeaconChainProofs} from "../libraries/BeaconChainProofs.sol"; -import {Endian} from "../libraries/Endian.sol"; import {ValidatorContainer} from "../libraries/ValidatorContainer.sol"; import {WithdrawalContainer} from "../libraries/WithdrawalContainer.sol"; import {ExoCapsuleStorage} from "../storage/ExoCapsuleStorage.sol"; import {IBeaconChainOracle} from "@beacon-oracle/contracts/src/IBeaconChainOracle.sol"; -import {ReentrancyGuardUpgradeable} from "@openzeppelin/contracts-upgradeable/security/ReentrancyGuardUpgradeable.sol"; +import {Initializable} from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; -contract ExoCapsule is ReentrancyGuardUpgradeable, ExoCapsuleStorage, IExoCapsule { +contract ExoCapsule is Initializable, ExoCapsuleStorage, IExoCapsule { using BeaconChainProofs for bytes32; - using Endian for bytes32; using ValidatorContainer for bytes32[]; using WithdrawalContainer for bytes32[]; event PrincipalBalanceUpdated(address, uint256); event WithdrawableBalanceUpdated(address, uint256); event WithdrawalSuccess(address, address, uint256); - /// @notice Emitted when a partial withdrawal claim is successfully redeemed - event PartialWithdrawalRedeemed( - bytes32 pubkey, uint256 withdrawalEpoch, address indexed recipient, uint64 partialWithdrawalAmountGwei - ); - /// @notice Emitted when an ETH validator is prove to have fully withdrawn from the beacon chain - event FullWithdrawalRedeemed( - bytes32 pubkey, uint64 withdrawalEpoch, address indexed recipient, uint64 withdrawalAmountGwei - ); - /// @notice Emitted when capsuleOwner enables restaking - event RestakingActivated(address indexed capsuleOwner); - /// @notice Emitted when ETH is received via the `receive` fallback - event NonBeaconChainETHReceived(uint256 amountReceived); - /// @notice Emitted when ETH that was previously received via the `receive` fallback is withdrawn - event NonBeaconChainETHWithdrawn(address indexed recipient, uint256 amountWithdrawn); error InvalidValidatorContainer(bytes32 pubkey); error InvalidWithdrawalContainer(uint64 validatorIndex); - error InvalidHistoricalSummaries(uint64 validatorIndex); error DoubleDepositedValidator(bytes32 pubkey); error StaleValidatorContainer(bytes32 pubkey, uint256 timestamp); - error WithdrawalAlreadyProven(bytes32 pubkey, uint256 timestamp); error UnregisteredValidator(bytes32 pubkey); error UnregisteredOrWithdrawnValidatorContainer(bytes32 pubkey); error FullyWithdrawnValidatorContainer(bytes32 pubkey); @@ -65,11 +47,6 @@ contract ExoCapsule is ReentrancyGuardUpgradeable, ExoCapsuleStorage, IExoCapsul _disableInitializers(); } - receive() external payable { - nonBeaconChainETHBalance += msg.value; - emit NonBeaconChainETHReceived(msg.value); - } - function initialize(address gateway_, address capsuleOwner_, address beaconOracle_) external initializer { require(gateway_ != address(0), "ExoCapsule: gateway address can not be empty"); require(capsuleOwner_ != address(0), "ExoCapsule: capsule owner address can not be empty"); @@ -78,14 +55,11 @@ contract ExoCapsule is ReentrancyGuardUpgradeable, ExoCapsuleStorage, IExoCapsul gateway = INativeRestakingController(gateway_); beaconOracle = IBeaconChainOracle(beaconOracle_); capsuleOwner = capsuleOwner_; - - emit RestakingActivated(capsuleOwner); } function verifyDepositProof(bytes32[] calldata validatorContainer, ValidatorContainerProof calldata proof) external onlyGateway - returns (uint256 depositAmount) { bytes32 validatorPubkey = validatorContainer.getPubkey(); bytes32 withdrawalCredentials = validatorContainer.getWithdrawalCredentials(); @@ -116,65 +90,66 @@ contract ExoCapsule is ReentrancyGuardUpgradeable, ExoCapsuleStorage, IExoCapsul validator.status = VALIDATOR_STATUS.REGISTERED; validator.validatorIndex = proof.validatorIndex; validator.mostRecentBalanceUpdateTimestamp = proof.beaconBlockTimestamp; - uint64 depositAmountGwei = validatorContainer.getEffectiveBalance(); - if (depositAmountGwei > MAX_RESTAKED_BALANCE_GWEI_PER_VALIDATOR) { - validator.restakedBalanceGwei = MAX_RESTAKED_BALANCE_GWEI_PER_VALIDATOR; - depositAmount = MAX_RESTAKED_BALANCE_GWEI_PER_VALIDATOR * GWEI_TO_WEI; - } else { - validator.restakedBalanceGwei = depositAmountGwei; - depositAmount = depositAmountGwei * GWEI_TO_WEI; - } + validator.restakedBalanceGwei = validatorContainer.getEffectiveBalance(); _capsuleValidatorsByIndex[proof.validatorIndex] = validatorPubkey; } - function verifyWithdrawalProof( + function verifyPartialWithdrawalProof( bytes32[] calldata validatorContainer, ValidatorContainerProof calldata validatorProof, bytes32[] calldata withdrawalContainer, - BeaconChainProofs.WithdrawalProof calldata withdrawalProof - ) external onlyGateway returns (bool partialWithdrawal, uint256 withdrawalAmount) { + WithdrawalContainerProof calldata withdrawalProof + ) external view onlyGateway { bytes32 validatorPubkey = validatorContainer.getPubkey(); - Validator storage validator = _capsuleValidators[validatorPubkey]; - uint64 withdrawalEpoch = withdrawalProof.slotRoot.getWithdrawalEpoch(); - partialWithdrawal = withdrawalEpoch < validatorContainer.getWithdrawableEpoch(); + uint64 withdrawableEpoch = validatorContainer.getWithdrawableEpoch(); + + bool partialWithdrawal = _timestampToEpoch(validatorProof.beaconBlockTimestamp) < withdrawableEpoch; if (!validatorContainer.verifyValidatorContainerBasic()) { revert InvalidValidatorContainer(validatorPubkey); } - if (validator.status == VALIDATOR_STATUS.UNREGISTERED) { - revert UnregisteredOrWithdrawnValidatorContainer(validatorPubkey); - } - if (provenWithdrawal[validatorPubkey][withdrawalProof.withdrawalIndex]) { - revert WithdrawalAlreadyProven(validatorPubkey, withdrawalProof.withdrawalIndex); + if (!partialWithdrawal) { + revert NotPartialWithdrawal(validatorPubkey); } - provenWithdrawal[validatorPubkey][withdrawalProof.withdrawalIndex] = true; + if (validatorProof.beaconBlockTimestamp != withdrawalProof.beaconBlockTimestamp) { + revert UnmatchedValidatorAndWithdrawal(validatorPubkey); + } _verifyValidatorContainer(validatorContainer, validatorProof); _verifyWithdrawalContainer(withdrawalContainer, withdrawalProof); + } + + function verifyFullWithdrawalProof( + bytes32[] calldata validatorContainer, + ValidatorContainerProof calldata validatorProof, + bytes32[] calldata withdrawalContainer, + WithdrawalContainerProof calldata withdrawalProof + ) external onlyGateway { + bytes32 validatorPubkey = validatorContainer.getPubkey(); + uint64 withdrawableEpoch = validatorContainer.getWithdrawableEpoch(); + + Validator storage validator = _capsuleValidators[validatorPubkey]; + bool fullyWithdrawal = _timestampToEpoch(validatorProof.beaconBlockTimestamp) > withdrawableEpoch; + + if (!validatorContainer.verifyValidatorContainerBasic()) { + revert InvalidValidatorContainer(validatorPubkey); + } + + if (!fullyWithdrawal) { + revert NotPartialWithdrawal(validatorPubkey); + } - uint64 withdrawalAmountGwei = withdrawalContainer.getAmount(); - - if (partialWithdrawal) { - // Immediately send ETH without sending request to Exocore side - emit PartialWithdrawalRedeemed(validatorPubkey, withdrawalEpoch, capsuleOwner, withdrawalAmountGwei); - _sendETH(capsuleOwner, withdrawalAmountGwei * GWEI_TO_WEI); - } else { - // Full withdrawal - validator.status = VALIDATOR_STATUS.WITHDRAWN; - validator.restakedBalanceGwei = 0; - // If over MAX_RESTAKED_BALANCE_GWEI_PER_VALIDATOR = 32 * 1e9, then send remaining amount immediately - emit FullWithdrawalRedeemed(validatorPubkey, withdrawalEpoch, capsuleOwner, withdrawalAmountGwei); - if (withdrawalAmountGwei > MAX_RESTAKED_BALANCE_GWEI_PER_VALIDATOR) { - uint256 amountToSend = (withdrawalAmountGwei - MAX_RESTAKED_BALANCE_GWEI_PER_VALIDATOR) * GWEI_TO_WEI; - _sendETH(capsuleOwner, amountToSend); - withdrawalAmount = MAX_RESTAKED_BALANCE_GWEI_PER_VALIDATOR * GWEI_TO_WEI; - } else { - withdrawalAmount = withdrawalAmountGwei * GWEI_TO_WEI; - } + if (validatorProof.beaconBlockTimestamp != withdrawalProof.beaconBlockTimestamp) { + revert UnmatchedValidatorAndWithdrawal(validatorPubkey); } + + _verifyValidatorContainer(validatorContainer, validatorProof); + _verifyWithdrawalContainer(withdrawalContainer, withdrawalProof); + + validator.status = VALIDATOR_STATUS.WITHDRAWN; } function withdraw(uint256 amount, address payable recipient) external onlyGateway { @@ -182,24 +157,14 @@ contract ExoCapsule is ReentrancyGuardUpgradeable, ExoCapsuleStorage, IExoCapsul require(amount > 0 && amount <= withdrawableBalance, "ExoCapsule: invalid withdrawal amount"); withdrawableBalance -= amount; - _sendETH(recipient, amount); + (bool sent,) = recipient.call{value: amount}(""); + if (!sent) { + revert WithdrawalFailure(capsuleOwner, recipient, amount); + } emit WithdrawalSuccess(capsuleOwner, recipient, amount); } - /// @notice Called by the capsule owner to withdraw the nonBeaconChainETHBalance - function withdrawNonBeaconChainETHBalance(address recipient, uint256 amountToWithdraw) external onlyGateway { - require( - amountToWithdraw <= nonBeaconChainETHBalance, - "ExoCapsule.withdrawNonBeaconChainETHBalance: amountToWithdraw is greater than nonBeaconChainETHBalance" - ); - require(recipient != address(0), "ExoCapsule: recipient address cannot be zero or empty"); - - nonBeaconChainETHBalance -= amountToWithdraw; - _sendETH(recipient, amountToWithdraw); - emit NonBeaconChainETHWithdrawn(recipient, amountToWithdraw); - } - function updatePrincipalBalance(uint256 lastlyUpdatedPrincipalBalance) external onlyGateway { principalBalance = lastlyUpdatedPrincipalBalance; @@ -249,14 +214,6 @@ contract ExoCapsule is ReentrancyGuardUpgradeable, ExoCapsuleStorage, IExoCapsul return validator; } - // slither-disable-next-line arbitrary-send-eth - function _sendETH(address recipient, uint256 amountWei) internal nonReentrant { - (bool sent,) = recipient.call{value: amountWei}(""); - if (!sent) { - revert WithdrawalFailure(capsuleOwner, recipient, amountWei); - } - } - function _verifyValidatorContainer(bytes32[] calldata validatorContainer, ValidatorContainerProof calldata proof) internal view @@ -275,13 +232,19 @@ contract ExoCapsule is ReentrancyGuardUpgradeable, ExoCapsuleStorage, IExoCapsul } } - function _verifyWithdrawalContainer( - bytes32[] calldata withdrawalContainer, - BeaconChainProofs.WithdrawalProof calldata proof - ) internal view { - // To-do check withdrawalContainer length is valid + function _verifyWithdrawalContainer(bytes32[] calldata withdrawalContainer, WithdrawalContainerProof calldata proof) + internal + view + { + bytes32 beaconBlockRoot = getBeaconBlockRoot(proof.beaconBlockTimestamp); bytes32 withdrawalContainerRoot = withdrawalContainer.merklelizeWithdrawalContainer(); - bool valid = withdrawalContainerRoot.isValidWithdrawalContainerRoot(proof); + bool valid = withdrawalContainerRoot.isValidWithdrawalContainerRoot( + proof.withdrawalContainerRootProof, + proof.withdrawalIndex, + beaconBlockRoot, + proof.executionPayloadRoot, + proof.executionPayloadRootProof + ); if (!valid) { revert InvalidWithdrawalContainer(withdrawalContainer.getValidatorIndex()); } @@ -294,8 +257,9 @@ contract ExoCapsule is ReentrancyGuardUpgradeable, ExoCapsuleStorage, IExoCapsul { uint64 atEpoch = _timestampToEpoch(atTimestamp); uint64 activationEpoch = validatorContainer.getActivationEpoch(); + uint64 exitEpoch = validatorContainer.getExitEpoch(); - return atEpoch >= activationEpoch; + return (atEpoch >= activationEpoch && atEpoch < exitEpoch); } function _isStaleProof(Validator storage validator, uint256 proofTimestamp) internal view returns (bool) { diff --git a/src/core/NativeRestakingController.sol b/src/core/NativeRestakingController.sol index 1fea7986..3de57ff2 100644 --- a/src/core/NativeRestakingController.sol +++ b/src/core/NativeRestakingController.sol @@ -2,9 +2,10 @@ pragma solidity ^0.8.19; import {IExoCapsule} from "../interfaces/IExoCapsule.sol"; import {INativeRestakingController} from "../interfaces/INativeRestakingController.sol"; -import {BeaconChainProofs} from "../libraries/BeaconChainProofs.sol"; + import {ValidatorContainer} from "../libraries/ValidatorContainer.sol"; import {BaseRestakingController} from "./BaseRestakingController.sol"; +import {ExoCapsule} from "./ExoCapsule.sol"; import {PausableUpgradeable} from "@openzeppelin/contracts-upgradeable/security/PausableUpgradeable.sol"; import {ReentrancyGuardUpgradeable} from "@openzeppelin/contracts-upgradeable/security/ReentrancyGuardUpgradeable.sol"; @@ -52,7 +53,7 @@ abstract contract NativeRestakingController is address(ownerToCapsule[msg.sender]) == address(0), "NativeRestakingController: message sender has already created the capsule" ); - IExoCapsule capsule = IExoCapsule( + ExoCapsule capsule = ExoCapsule( Create2.deploy( 0, bytes32(uint256(uint160(msg.sender))), @@ -75,7 +76,9 @@ abstract contract NativeRestakingController is IExoCapsule.ValidatorContainerProof calldata proof ) external payable whenNotPaused nonReentrant nativeRestakingEnabled { IExoCapsule capsule = _getCapsule(msg.sender); - uint256 depositValue = capsule.verifyDepositProof(validatorContainer, proof); + capsule.verifyDepositProof(validatorContainer, proof); + + uint256 depositValue = uint256(validatorContainer.getEffectiveBalance()) * GWEI_TO_WEI; bytes memory actionArgs = abi.encodePacked(bytes32(bytes20(VIRTUAL_STAKED_ETH_ADDRESS)), bytes32(bytes20(msg.sender)), depositValue); @@ -83,24 +86,18 @@ abstract contract NativeRestakingController is _processRequest(Action.REQUEST_DEPOSIT, actionArgs, encodedRequest); } - function processBeaconChainWithdrawal( + function processBeaconChainPartialWithdrawal( bytes32[] calldata validatorContainer, IExoCapsule.ValidatorContainerProof calldata validatorProof, bytes32[] calldata withdrawalContainer, - BeaconChainProofs.WithdrawalProof calldata withdrawalProof - ) external payable whenNotPaused nonReentrant nativeRestakingEnabled { - IExoCapsule capsule = _getCapsule(msg.sender); - (bool partialWithdrawal, uint256 withdrawalAmount) = - capsule.verifyWithdrawalProof(validatorContainer, validatorProof, withdrawalContainer, withdrawalProof); - if (!partialWithdrawal) { - // request full withdraw - bytes memory actionArgs = abi.encodePacked( - bytes32(bytes20(VIRTUAL_STAKED_ETH_ADDRESS)), bytes32(bytes20(msg.sender)), withdrawalAmount - ); - bytes memory encodedRequest = abi.encode(VIRTUAL_STAKED_ETH_ADDRESS, msg.sender, withdrawalAmount); - - _processRequest(Action.REQUEST_WITHDRAW_PRINCIPAL_FROM_EXOCORE, actionArgs, encodedRequest); - } - } + IExoCapsule.WithdrawalContainerProof calldata withdrawalProof + ) external payable whenNotPaused nonReentrant nativeRestakingEnabled {} + + function processBeaconChainFullWithdrawal( + bytes32[] calldata validatorContainer, + IExoCapsule.ValidatorContainerProof calldata validatorProof, + bytes32[] calldata withdrawalContainer, + IExoCapsule.WithdrawalContainerProof calldata withdrawalProof + ) external payable whenNotPaused nonReentrant nativeRestakingEnabled {} } diff --git a/src/interfaces/IExoCapsule.sol b/src/interfaces/IExoCapsule.sol index 545db228..4df5b24d 100644 --- a/src/interfaces/IExoCapsule.sol +++ b/src/interfaces/IExoCapsule.sol @@ -1,7 +1,5 @@ pragma solidity ^0.8.19; -import {BeaconChainProofs} from "../libraries/BeaconChainProofs.sol"; - interface IExoCapsule { /// @notice This struct contains the infos needed for validator container validity verification @@ -15,22 +13,28 @@ interface IExoCapsule { struct WithdrawalContainerProof { uint256 beaconBlockTimestamp; - bytes32 stateRoot; + bytes32 executionPayloadRoot; + bytes32[] executionPayloadRootProof; bytes32[] withdrawalContainerRootProof; + uint256 withdrawalIndex; } - function initialize(address gateway, address capsuleOwner, address beaconOracle) external; - function verifyDepositProof(bytes32[] calldata validatorContainer, ValidatorContainerProof calldata proof) - external - returns (uint256); + external; + + function verifyPartialWithdrawalProof( + bytes32[] calldata validatorContainer, + ValidatorContainerProof calldata validatorProof, + bytes32[] calldata withdrawalContainer, + WithdrawalContainerProof calldata withdrawalProof + ) external; - function verifyWithdrawalProof( + function verifyFullWithdrawalProof( bytes32[] calldata validatorContainer, ValidatorContainerProof calldata validatorProof, bytes32[] calldata withdrawalContainer, - BeaconChainProofs.WithdrawalProof calldata withdrawalProof - ) external returns (bool partialWithdrawal, uint256 withdrawalAmount); + WithdrawalContainerProof calldata withdrawalProof + ) external; function withdraw(uint256 amount, address payable recipient) external; diff --git a/src/interfaces/INativeRestakingController.sol b/src/interfaces/INativeRestakingController.sol index 34d83653..a4b1041e 100644 --- a/src/interfaces/INativeRestakingController.sol +++ b/src/interfaces/INativeRestakingController.sol @@ -1,6 +1,5 @@ pragma solidity ^0.8.19; -import {BeaconChainProofs} from "../libraries/BeaconChainProofs.sol"; import {IBaseRestakingController} from "./IBaseRestakingController.sol"; import {IExoCapsule} from "./IExoCapsule.sol"; @@ -27,11 +26,10 @@ interface INativeRestakingController is IBaseRestakingController { /** * @notice This is called to deposit ETH that is staked on Ethereum beacon chain to Exocore network to be restaked - * in future + * in the future. * @dev Before deposit, staker should have created the ExoCapsule that it owns and point the validator's withdrawal - * credentials - * to the ExoCapsule owned by staker. The effective balance of `validatorContainer` would be credited as deposited - * value by Exocore network. + * crendentials to the ExoCapsule owned by staker. The effective balance of `validatorContainer` would be credited + * as deposited value by Exocore network. * @ param */ function depositBeaconChainValidator( @@ -41,10 +39,9 @@ interface INativeRestakingController is IBaseRestakingController { /** * @notice When a beacon chain partial withdrawal to an ExoCapsule contract happens(the withdrawal time is less than - * validator's withdrawable_epoch), - * this function could be called with `validatorContainer`, `withdrawalContainer` and corresponding proofs to prove - * this partial withdrawal - * from beacon chain is done and unlock withdrawn ETH to be claimable for ExoCapsule owner. + * validator's withdrawable_epoch), this function could be called with `validatorContainer`, `withdrawalContainer` + * and corresponding proofs to prove this partial withdrawal from beacon chain is done and unlock withdrawn ETH to + * be claimable for ExoCapsule owner. * @param validatorContainer is the data structure included in `BeaconState` of `BeaconBlock` that contains beacon * chain validator information, * refer to: https://github.com/ethereum/consensus-specs/blob/dev/specs/phase0/beacon-chain.md#validator @@ -56,17 +53,20 @@ interface INativeRestakingController is IBaseRestakingController { * @param withdrawalProof is the merkle proof needed for verifying that `withdrawalContainer` is included in some * beacon block root. */ + function processBeaconChainPartialWithdrawal( + bytes32[] calldata validatorContainer, + IExoCapsule.ValidatorContainerProof calldata validatorProof, + bytes32[] calldata withdrawalContainer, + IExoCapsule.WithdrawalContainerProof calldata withdrawalProof + ) external payable; /** * @notice When a beacon chain full withdrawal to this capsule contract happens(the withdrawal time is euqal to or - * greater than - * validator's withdrawable_epoch), this function could be called with `validatorContainer`, `withdrawalContainer` - * and corresponding - * proofs to prove this full withdrawal from beacon chain is done, send withdrawal request to Exocore network to be - * processed. + * greater than validator's withdrawable_epoch), this function could be called with `validatorContainer`, + * `withdrawalContainer` and corresponding proofs to prove this full withdrawal from beacon chain is done, send + * withdrawal request to Exocore network to be processed. * After Exocore network finishs dealing with withdrawal request and sending back the response, ExoCapsule would - * unlock corresponding ETH - * in response to be cliamable for ExoCapsule owner. + * unlock corresponding ETH in response to be claimable for ExoCapsule owner. * @param validatorContainer is the data structure included in `BeaconState` of `BeaconBlock` that contains beacon * chain validator information, * refer to: https://github.com/ethereum/consensus-specs/blob/dev/specs/phase0/beacon-chain.md#validator @@ -78,11 +78,11 @@ interface INativeRestakingController is IBaseRestakingController { * @param withdrawalProof is the merkle proof needed for verifying that `withdrawalContainer` is included in some * beacon block root. */ - function processBeaconChainWithdrawal( + function processBeaconChainFullWithdrawal( bytes32[] calldata validatorContainer, IExoCapsule.ValidatorContainerProof calldata validatorProof, bytes32[] calldata withdrawalContainer, - BeaconChainProofs.WithdrawalProof calldata withdrawalProof + IExoCapsule.WithdrawalContainerProof calldata withdrawalProof ) external payable; } diff --git a/src/libraries/BeaconChainProofs.sol b/src/libraries/BeaconChainProofs.sol index ebf19978..21f00cd0 100644 --- a/src/libraries/BeaconChainProofs.sol +++ b/src/libraries/BeaconChainProofs.sol @@ -1,6 +1,5 @@ pragma solidity ^0.8.0; -import {Endian} from "../libraries/Endian.sol"; import {Merkle} from "./Merkle.sol"; // Utility library for parsing and PHASE0 beacon chain block headers @@ -10,7 +9,6 @@ import {Merkle} from "./Merkle.sol"; // Spec: https://github.com/ethereum/consensus-specs/blob/dev/specs/phase0/beacon-chain.md#beaconblockheader // BeaconState // Spec: https://github.com/ethereum/consensus-specs/blob/dev/specs/phase0/beacon-chain.md#beaconstate - library BeaconChainProofs { // constants are the number of fields and the heights of the different merkle trees used in merkleizing @@ -21,19 +19,6 @@ library BeaconChainProofs { uint256 internal constant BEACON_STATE_FIELD_TREE_HEIGHT = 5; - uint256 internal constant DENEB_FORK_TIMESTAMP = 1_710_338_135; - uint256 internal constant EXECUTION_PAYLOAD_HEADER_FIELD_TREE_HEIGHT_CAPELLA = 4; - uint256 internal constant EXECUTION_PAYLOAD_HEADER_FIELD_TREE_HEIGHT_DENEB = 5; // After deneb hard fork, it's - - // increased from 4 to 5 - // SLOTS_PER_HISTORICAL_ROOT = 2**13, so tree height is 13 - uint256 internal constant BLOCK_ROOTS_TREE_HEIGHT = 13; - - //Index of block_summary_root in historical_summary container - uint256 internal constant BLOCK_SUMMARY_ROOT_INDEX = 0; - //HISTORICAL_ROOTS_LIMIT = 2**24, so tree height is 24 - uint256 internal constant HISTORICAL_SUMMARIES_TREE_HEIGHT = 24; - uint256 internal constant VALIDATOR_TREE_HEIGHT = 40; // MAX_WITHDRAWALS_PER_PAYLOAD = 2**4, making tree height = 4 @@ -43,7 +28,6 @@ library BeaconChainProofs { // https://github.com/ethereum/consensus-specs/blob/dev/specs/capella/beacon-chain.md#beaconblockbody uint256 internal constant EXECUTION_PAYLOAD_INDEX = 9; - uint256 internal constant SLOT_INDEX = 0; // in beacon block header // https://github.com/ethereum/consensus-specs/blob/dev/specs/phase0/beacon-chain.md#beaconblockheader uint256 internal constant STATE_ROOT_INDEX = 3; @@ -51,10 +35,7 @@ library BeaconChainProofs { // in beacon state // https://github.com/ethereum/consensus-specs/blob/dev/specs/capella/beacon-chain.md#beaconstate uint256 internal constant VALIDATOR_TREE_ROOT_INDEX = 11; - uint256 internal constant HISTORICAL_SUMMARIES_INDEX = 27; - // in execution payload header - uint256 internal constant TIMESTAMP_INDEX = 9; //in execution payload uint256 internal constant WITHDRAWALS_INDEX = 14; @@ -73,19 +54,18 @@ library BeaconChainProofs { /// @notice This struct contains the merkle proofs and leaves needed to verify a partial/full withdrawal struct WithdrawalProof { - bytes32[] withdrawalContainerRootProof; - bytes32[] slotProof; - bytes32[] executionPayloadRootProof; - bytes32[] timestampProof; - bytes32[] historicalSummaryBlockRootProof; - uint256 blockRootIndex; - uint256 historicalSummaryIndex; - uint256 withdrawalIndex; + bytes withdrawalProof; + bytes slotProof; + bytes executionPayloadProof; + bytes timestampProof; + bytes historicalSummaryBlockRootProof; + uint64 blockRootIndex; + uint64 historicalSummaryIndex; + uint64 withdrawalIndex; bytes32 blockRoot; bytes32 slotRoot; bytes32 timestampRoot; bytes32 executionPayloadRoot; - bytes32 stateRoot; } /// @notice This struct contains the root and proof for verifying the state root against the oracle block root @@ -147,143 +127,64 @@ library BeaconChainProofs { }); } - function isValidWithdrawalContainerRoot(bytes32 withdrawalContainerRoot, WithdrawalProof calldata proof) - internal - view - returns (bool valid) - { - require(proof.blockRootIndex < 2 ** BLOCK_ROOTS_TREE_HEIGHT, "blockRootIndex too large"); - require(proof.withdrawalIndex < 2 ** WITHDRAWALS_TREE_HEIGHT, "withdrawalIndex too large"); - require( - proof.historicalSummaryIndex < 2 ** HISTORICAL_SUMMARIES_TREE_HEIGHT, "historicalSummaryIndex too large" + function isValidWithdrawalContainerRoot( + bytes32 withdrawalContainerRoot, + bytes32[] calldata withdrawalContainerRootProof, + uint256 withdrawalIndex, + bytes32 beaconBlockRoot, + bytes32 executionPayloadRoot, + bytes32[] calldata executionPayloadRootProof + ) internal view returns (bool valid) { + bool validExecutionPayloadRoot = + isValidExecutionPayloadRoot(executionPayloadRoot, beaconBlockRoot, executionPayloadRootProof); + bool validWCRootAgainstExecutionPayloadRoot = isValidWCRootAgainstExecutionPayloadRoot( + withdrawalContainerRoot, executionPayloadRoot, withdrawalContainerRootProof, withdrawalIndex ); - bool validExecutionPayloadRoot = isValidExecutionPayloadRoot(proof); - bool validHistoricalSummary = isValidHistoricalSummaryRoot(proof); - bool validWCRootAgainstExecutionPayloadRoot = - isValidWCRootAgainstExecutionPayloadRoot(proof, withdrawalContainerRoot); - if (validExecutionPayloadRoot && validHistoricalSummary && validWCRootAgainstExecutionPayloadRoot) { + if (validExecutionPayloadRoot && validWCRootAgainstExecutionPayloadRoot) { valid = true; } } - function isValidExecutionPayloadRoot(WithdrawalProof calldata withdrawalProof) internal pure returns (bool) { - uint256 withdrawalTimestamp = getWithdrawalTimestamp(withdrawalProof); - // Post deneb hard fork, executionPayloadHeader fields increased - uint256 executionPayloadHeaderFieldTreeHeight = withdrawalTimestamp < DENEB_FORK_TIMESTAMP - ? EXECUTION_PAYLOAD_HEADER_FIELD_TREE_HEIGHT_CAPELLA - : EXECUTION_PAYLOAD_HEADER_FIELD_TREE_HEIGHT_DENEB; - require( - withdrawalProof.withdrawalContainerRootProof.length - == executionPayloadHeaderFieldTreeHeight + WITHDRAWALS_TREE_HEIGHT + 1, - "wcRootProof has incorrect length" - ); - require( - withdrawalProof.executionPayloadRootProof.length - == BEACON_BLOCK_HEADER_FIELD_TREE_HEIGHT + BEACON_BLOCK_BODY_FIELD_TREE_HEIGHT, - "executionPayloadRootProof has incorrect length" - ); - require( - withdrawalProof.slotProof.length == BEACON_BLOCK_HEADER_FIELD_TREE_HEIGHT, "slotProof has incorrect length" - ); - require( - withdrawalProof.timestampProof.length == executionPayloadHeaderFieldTreeHeight, - "timestampProof has incorrect length" - ); - return true; - } - - function isValidWCRootAgainstExecutionPayloadRoot( - WithdrawalProof calldata withdrawalProof, - bytes32 withdrawalContainerRoot + function isValidExecutionPayloadRoot( + bytes32 executionPayloadRoot, + bytes32 beaconBlockRoot, + bytes32[] calldata executionPayloadRootProof ) internal view returns (bool) { - //Next we verify the slot against the blockRoot - require( - Merkle.verifyInclusionSha256({ - proof: withdrawalProof.slotProof, - root: withdrawalProof.blockRoot, - leaf: withdrawalProof.slotRoot, - index: SLOT_INDEX - }), - "Invalid slot merkle proof" - ); - - // Verify the executionPayloadRoot against the blockRoot - uint256 executionPayloadIndex = - (BODY_ROOT_INDEX << (BEACON_BLOCK_BODY_FIELD_TREE_HEIGHT)) | EXECUTION_PAYLOAD_INDEX; require( - Merkle.verifyInclusionSha256({ - proof: withdrawalProof.executionPayloadRootProof, - root: withdrawalProof.blockRoot, - leaf: withdrawalProof.executionPayloadRoot, - index: executionPayloadIndex - }), - "Invalid executionPayload proof" - ); - - // Verify the timestampRoot against the executionPayload root - require( - Merkle.verifyInclusionSha256({ - proof: withdrawalProof.timestampProof, - root: withdrawalProof.executionPayloadRoot, - leaf: withdrawalProof.timestampRoot, - index: TIMESTAMP_INDEX - }), - "Invalid timestamp proof" + executionPayloadRootProof.length + == BEACON_BLOCK_HEADER_FIELD_TREE_HEIGHT + BEACON_BLOCK_BODY_FIELD_TREE_HEIGHT, + "state root proof should have 3 nodes" ); - /** - * Next we verify the withdrawal fields against the executionPayloadRoot: - * First we compute the withdrawal_index, then we merkleize the - * withdrawalFields container to calculate the withdrawalRoot. - * - * Note: Merkleization of the withdrawals root tree uses MerkleizeWithMixin, i.e., the length of the array - * is hashed with the root of - * the array. Thus we shift the WITHDRAWALS_INDEX over by WITHDRAWALS_TREE_HEIGHT + 1 and not just - * WITHDRAWALS_TREE_HEIGHT. - */ - uint256 withdrawalIndex = - (WITHDRAWALS_INDEX << (WITHDRAWALS_TREE_HEIGHT + 1)) | uint256(withdrawalProof.withdrawalIndex); + uint256 leafIndex = (BODY_ROOT_INDEX << (BEACON_BLOCK_BODY_FIELD_TREE_HEIGHT)) | EXECUTION_PAYLOAD_INDEX; return Merkle.verifyInclusionSha256({ - proof: withdrawalProof.withdrawalContainerRootProof, - root: withdrawalProof.executionPayloadRoot, - leaf: withdrawalContainerRoot, - index: withdrawalIndex + proof: executionPayloadRootProof, + root: beaconBlockRoot, + leaf: executionPayloadRoot, + index: leafIndex }); } - function isValidHistoricalSummaryRoot(WithdrawalProof calldata withdrawalProof) internal view returns (bool) { + function isValidWCRootAgainstExecutionPayloadRoot( + bytes32 withdrawalContainerRoot, + bytes32 executionPayloadRoot, + bytes32[] calldata withdrawalContainerRootProof, + uint256 withdrawalIndex + ) internal view returns (bool) { require( - withdrawalProof.historicalSummaryBlockRootProof.length - == BEACON_STATE_FIELD_TREE_HEIGHT + (HISTORICAL_SUMMARIES_TREE_HEIGHT + 1) + 1 + (BLOCK_ROOTS_TREE_HEIGHT), - "historicalSummaryBlockRootProof has incorrect length" + withdrawalContainerRootProof.length == (VALIDATOR_TREE_HEIGHT + 1) + BEACON_STATE_FIELD_TREE_HEIGHT, + "validator container root proof should have 46 nodes" ); - uint256 historicalBlockHeaderIndex = ( - HISTORICAL_SUMMARIES_INDEX << ((HISTORICAL_SUMMARIES_TREE_HEIGHT + 1) + 1 + (BLOCK_ROOTS_TREE_HEIGHT)) - ) | (withdrawalProof.historicalSummaryIndex << (1 + (BLOCK_ROOTS_TREE_HEIGHT))) - | (BLOCK_SUMMARY_ROOT_INDEX << (BLOCK_ROOTS_TREE_HEIGHT)) | withdrawalProof.blockRootIndex; + uint256 leafIndex = (WITHDRAWALS_INDEX << (WITHDRAWALS_TREE_HEIGHT + 1)) | uint256(withdrawalIndex); return Merkle.verifyInclusionSha256({ - proof: withdrawalProof.historicalSummaryBlockRootProof, - root: withdrawalProof.stateRoot, - leaf: withdrawalProof.blockRoot, - index: historicalBlockHeaderIndex + proof: withdrawalContainerRootProof, + root: executionPayloadRoot, + leaf: withdrawalContainerRoot, + index: leafIndex }); } - /** - * @dev Retrieve the withdrawal timestamp - */ - - function getWithdrawalTimestamp(WithdrawalProof calldata withdrawalProof) internal pure returns (uint64) { - return Endian.fromLittleEndianUint64(withdrawalProof.timestampRoot); - } - - /** - * @dev Converts the withdrawal's slot to an epoch - */ - function getWithdrawalEpoch(bytes32 slotRoot) internal pure returns (uint64) { - return Endian.fromLittleEndianUint64(slotRoot) / SLOTS_PER_EPOCH; - } } diff --git a/src/libraries/WithdrawalContainer.sol b/src/libraries/WithdrawalContainer.sol index e6d26b83..10366bba 100644 --- a/src/libraries/WithdrawalContainer.sol +++ b/src/libraries/WithdrawalContainer.sol @@ -32,9 +32,6 @@ library WithdrawalContainer { return address(bytes20(withdrawalContainer[2])); } - /** - * @dev Retrieves a withdrawal's withdrawal amount (in gwei) - */ function getAmount(bytes32[] calldata withdrawalContainer) internal pure returns (uint64) { return withdrawalContainer[3].fromLittleEndianUint64(); } diff --git a/src/storage/ClientChainGatewayStorage.sol b/src/storage/ClientChainGatewayStorage.sol index a4b15338..dea39f85 100644 --- a/src/storage/ClientChainGatewayStorage.sol +++ b/src/storage/ClientChainGatewayStorage.sol @@ -24,6 +24,7 @@ contract ClientChainGatewayStorage is BootstrapStorage { // constant state variables uint256 internal constant TOKEN_ADDRESS_BYTES_LENGTH = 32; + uint256 internal constant GWEI_TO_WEI = 1e9; address internal constant VIRTUAL_STAKED_ETH_ADDRESS = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE; IETHPOSDeposit internal constant ETH_POS = IETHPOSDeposit(0x00000000219ab540356cBB839Cbe05303d7705Fa); // constants used for layerzero messaging diff --git a/src/storage/ExoCapsuleStorage.sol b/src/storage/ExoCapsuleStorage.sol index 283bb25b..d60c2410 100644 --- a/src/storage/ExoCapsuleStorage.sol +++ b/src/storage/ExoCapsuleStorage.sol @@ -29,24 +29,15 @@ contract ExoCapsuleStorage { address public constant BEACON_ROOTS_ADDRESS = 0x000F3df6D732807Ef1319fB7B8bB8522d0Beac02; uint256 public constant BEACON_CHAIN_GENESIS_TIME = 1_606_824_023; uint256 internal constant VERIFY_BALANCE_UPDATE_WINDOW_SECONDS = 4.5 hours; - uint256 public constant GWEI_TO_WEI = 1e9; - uint64 public constant MAX_RESTAKED_BALANCE_GWEI_PER_VALIDATOR = 32e9; uint256 public principalBalance; - /// @notice the amount of execution layer ETH in this contract that is staked in(i.e. withdrawn from the Beacon - /// Chain but not from Exocore) uint256 public withdrawableBalance; - /// @notice This variable tracks any ETH deposited into this contract via the `receive` fallback function - uint256 public nonBeaconChainETHBalance; address public capsuleOwner; INativeRestakingController public gateway; IBeaconChainOracle public beaconOracle; mapping(bytes32 pubkey => Validator validator) internal _capsuleValidators; mapping(uint256 index => bytes32 pubkey) internal _capsuleValidatorsByIndex; - /// @notice This is a mapping of validatorPubkeyHash to withdrawal index to whether or not they have proven a - /// withdrawal - mapping(bytes32 => mapping(uint256 => bool)) public provenWithdrawal; uint256[40] private __gap; diff --git a/test/foundry/DepositWithdrawPrinciple.t.sol b/test/foundry/DepositWithdrawPrinciple.t.sol index 44271540..283e4e9c 100644 --- a/test/foundry/DepositWithdrawPrinciple.t.sol +++ b/test/foundry/DepositWithdrawPrinciple.t.sol @@ -2,10 +2,7 @@ pragma solidity ^0.8.19; import "../../src/core/ExoCapsule.sol"; import "../../src/core/ExocoreGateway.sol"; - -import {IExoCapsule} from "../../src/interfaces/IExoCapsule.sol"; import {ILSTRestakingController} from "../../src/interfaces/ILSTRestakingController.sol"; - import "../../src/storage/GatewayStorage.sol"; import "./ExocoreDeployer.t.sol"; import "forge-std/Test.sol"; @@ -13,6 +10,7 @@ import "forge-std/Test.sol"; import "@layerzero-v2/protocol/contracts/libs/AddressCast.sol"; import "@layerzerolabs/lz-evm-protocol-v2/contracts/libs/GUID.sol"; import "@openzeppelin/contracts/utils/Create2.sol"; +import "forge-std/console.sol"; contract DepositWithdrawPrincipalTest is ExocoreDeployer { @@ -29,7 +27,6 @@ contract DepositWithdrawPrincipalTest is ExocoreDeployer { event StakedWithCapsule(address staker, address capsule); uint256 constant DEFAULT_ENDPOINT_CALL_GAS_LIMIT = 200_000; - uint64 public constant MAX_RESTAKED_BALANCE_GWEI_PER_VALIDATOR = 32e9; function test_LSTDepositWithdrawByLayerZero() public { Player memory depositor = players[0]; @@ -245,18 +242,16 @@ contract DepositWithdrawPrincipalTest is ExocoreDeployer { test_AddWhitelistTokens(); _testNativeDeposit(depositor, relayer, lastlyUpdatedPrincipalBalance); - lastlyUpdatedPrincipalBalance += 32 ether; - _testNativeWithdraw(depositor, relayer, lastlyUpdatedPrincipalBalance); } function _testNativeDeposit(Player memory depositor, Player memory relayer, uint256 lastlyUpdatedPrincipalBalance) internal { // before native stake and deposit, we simulate proper block environment states to make proof valid - _simulateBlockEnvironmentForNativeDeposit(); + _simulateBlockEnvironment(); // 1. firstly depositor should stake to beacon chain by depositing 32 ETH to ETHPOS contract - IExoCapsule expectedCapsule = IExoCapsule( + ExoCapsule expectedCapsule = ExoCapsule( Create2.computeAddress( bytes32(uint256(uint160(depositor.addr))), keccak256(abi.encodePacked(BEACON_PROXY_BYTECODE, abi.encode(address(capsuleBeacon), ""))), @@ -275,7 +270,7 @@ contract DepositWithdrawPrincipalTest is ExocoreDeployer { // because capsule address is expected to be compatible with validator container withdrawal credentails address capsuleAddress = _getCapsuleFromWithdrawalCredentials(_getWithdrawalCredentials(validatorContainer)); vm.etch(capsuleAddress, address(expectedCapsule).code); - capsule = ExoCapsule(payable(capsuleAddress)); + capsule = ExoCapsule(capsuleAddress); stdstore.target(capsuleAddress).sig("_beacon()").checked_write(address(capsuleBeacon)); assertEq(stdstore.target(capsuleAddress).sig("_beacon()").read_address(), address(capsuleBeacon)); @@ -295,11 +290,6 @@ contract DepositWithdrawPrincipalTest is ExocoreDeployer { /// client chain layerzero endpoint should emit the message packet including deposit payload. uint64 depositRequestNonce = 1; uint256 depositAmount = uint256(_getEffectiveBalance(validatorContainer)) * GWEI_TO_WEI; - // Cap to 32 ether - if (depositAmount >= 32 ether) { - depositAmount = 32 ether; - } - bytes memory depositRequestPayload = abi.encodePacked( GatewayStorage.Action.REQUEST_DEPOSIT, bytes32(bytes20(VIRTUAL_STAKED_ETH_ADDRESS)), @@ -317,6 +307,7 @@ contract DepositWithdrawPrincipalTest is ExocoreDeployer { depositRequestNonce, depositRequestPayload ); + /// client chain gateway should emit MessageSent event vm.expectEmit(true, true, true, true, address(clientGateway)); emit MessageSent( @@ -353,6 +344,7 @@ contract DepositWithdrawPrincipalTest is ExocoreDeployer { emit MessageSent( GatewayStorage.Action.RESPOND, depositResponseId, depositResponseNonce, depositResponseNativeFee ); + /// relayer catches the request message packet by listening to client chain event and feed it to Exocore network vm.startPrank(relayer.addr); exocoreLzEndpoint.lzReceive( @@ -383,10 +375,11 @@ contract DepositWithdrawPrincipalTest is ExocoreDeployer { vm.stopPrank(); } - function _simulateBlockEnvironmentForNativeDeposit() internal { + function _simulateBlockEnvironment() internal { /// we set the timestamp of proof to be exactly the timestamp that the validator container get activated on /// beacon chain - activationTimestamp = BEACON_CHAIN_GENESIS_TIME + _getActivationEpoch(validatorContainer) * SECONDS_PER_EPOCH; + uint256 activationTimestamp = + BEACON_CHAIN_GENESIS_TIME + _getActivationEpoch(validatorContainer) * SECONDS_PER_EPOCH; mockProofTimestamp = activationTimestamp; validatorProof.beaconBlockTimestamp = mockProofTimestamp; @@ -402,119 +395,4 @@ contract DepositWithdrawPrincipalTest is ExocoreDeployer { ); } - function _testNativeWithdraw(Player memory withdrawer, Player memory relayer, uint256 lastlyUpdatedPrincipalBalance) - internal - { - // before native withdraw, we simulate proper block environment states to make proof valid - _simulateBlockEnvironmentForNativeWithdraw(); - deal(address(capsule), 1 ether); // Deposit 1 ether to handle excess amount withdraw - - // 2. withdrawer will call clientGateway.processBeaconChainWithdrawal to withdraw from Exocore thru layerzero - - /// client chain layerzero endpoint should emit the message packet including deposit payload. - uint64 withdrawRequestNonce = 2; - uint64 withdrawalAmountGwei = _getWithdrawalAmount(withdrawalContainer); - uint256 withdrawalAmount; - if (withdrawalAmountGwei > MAX_RESTAKED_BALANCE_GWEI_PER_VALIDATOR) { - withdrawalAmount = MAX_RESTAKED_BALANCE_GWEI_PER_VALIDATOR * GWEI_TO_WEI; - } else { - withdrawalAmount = withdrawalAmountGwei * GWEI_TO_WEI; - } - bytes memory withdrawRequestPayload = abi.encodePacked( - GatewayStorage.Action.REQUEST_WITHDRAW_PRINCIPAL_FROM_EXOCORE, - bytes32(bytes20(VIRTUAL_STAKED_ETH_ADDRESS)), - bytes32(bytes20(withdrawer.addr)), - withdrawalAmount - ); - uint256 withdrawRequestNativeFee = clientGateway.quote(withdrawRequestPayload); - bytes32 withdrawRequestId = generateUID(withdrawRequestNonce, true); - - // client chain layerzero endpoint should emit the message packet including withdraw payload. - vm.expectEmit(true, true, true, true, address(clientChainLzEndpoint)); - emit NewPacket( - exocoreChainId, - address(clientGateway), - address(exocoreGateway).toBytes32(), - withdrawRequestNonce, - withdrawRequestPayload - ); - // client chain gateway should emit MessageSent event - vm.expectEmit(true, true, true, true, address(clientGateway)); - emit MessageSent( - GatewayStorage.Action.REQUEST_WITHDRAW_PRINCIPAL_FROM_EXOCORE, - withdrawRequestId, - withdrawRequestNonce, - withdrawRequestNativeFee - ); - - vm.startPrank(withdrawer.addr); - clientGateway.processBeaconChainWithdrawal{value: withdrawRequestNativeFee}( - validatorContainer, validatorProof, withdrawalContainer, withdrawalProof - ); - vm.stopPrank(); - - /// exocore gateway should return response message to exocore network layerzero endpoint - uint64 withdrawResponseNonce = 3; - lastlyUpdatedPrincipalBalance -= withdrawalAmount; - bytes memory withdrawResponsePayload = - abi.encodePacked(GatewayStorage.Action.RESPOND, withdrawRequestNonce, true, lastlyUpdatedPrincipalBalance); - uint256 withdrawResponseNativeFee = exocoreGateway.quote(clientChainId, withdrawResponsePayload); - bytes32 withdrawResponseId = generateUID(withdrawResponseNonce, false); - - // exocore gateway should return response message to exocore network layerzero endpoint - vm.expectEmit(true, true, true, true, address(exocoreLzEndpoint)); - emit NewPacket( - clientChainId, - address(exocoreGateway), - address(clientGateway).toBytes32(), - withdrawResponseNonce, - withdrawResponsePayload - ); - // exocore gateway should emit MessageSent event - vm.expectEmit(true, true, true, true, address(exocoreGateway)); - emit MessageSent( - GatewayStorage.Action.RESPOND, withdrawResponseId, withdrawResponseNonce, withdrawResponseNativeFee - ); - exocoreLzEndpoint.lzReceive( - Origin(clientChainId, address(clientGateway).toBytes32(), withdrawRequestNonce), - address(exocoreGateway), - withdrawRequestId, - withdrawRequestPayload, - bytes("") - ); - - // client chain gateway should execute the response hook and emit depositResult event - vm.expectEmit(true, true, true, true, address(clientGateway)); - emit WithdrawPrincipalResult(true, address(VIRTUAL_STAKED_ETH_ADDRESS), withdrawer.addr, withdrawalAmount); - clientChainLzEndpoint.lzReceive( - Origin(exocoreChainId, address(exocoreGateway).toBytes32(), withdrawResponseNonce), - address(clientGateway), - withdrawResponseId, - withdrawResponsePayload, - bytes("") - ); - } - - function _simulateBlockEnvironmentForNativeWithdraw() internal { - // load beacon chain validator container and proof from json file - string memory withdrawalInfo = vm.readFile("test/foundry/test-data/full_withdrawal_proof.json"); - _loadValidatorContainer(withdrawalInfo); - // load withdrawal proof - _loadWithdrawalContainer(withdrawalInfo); - - activationTimestamp = BEACON_CHAIN_GENESIS_TIME + _getActivationEpoch(validatorContainer) * SECONDS_PER_EPOCH; - mockProofTimestamp = activationTimestamp; - validatorProof.beaconBlockTimestamp = mockProofTimestamp; - - /// we set current block timestamp to be exactly one slot after the proof generation timestamp - mockCurrentBlockTimestamp = mockProofTimestamp + SECONDS_PER_SLOT; - vm.warp(mockCurrentBlockTimestamp); - - vm.mockCall( - address(beaconOracle), - abi.encodeWithSelector(beaconOracle.timestampToBlockRoot.selector, validatorProof.beaconBlockTimestamp), - abi.encode(beaconBlockRoot) - ); - } - } diff --git a/test/foundry/ExocoreDeployer.t.sol b/test/foundry/ExocoreDeployer.t.sol index 09fa9b9c..2e58013b 100644 --- a/test/foundry/ExocoreDeployer.t.sol +++ b/test/foundry/ExocoreDeployer.t.sol @@ -34,8 +34,6 @@ import {NonShortCircuitEndpointV2Mock} from "../mocks/NonShortCircuitEndpointV2M import "src/core/BeaconProxyBytecode.sol"; import "src/core/ExoCapsule.sol"; - -import "src/libraries/BeaconChainProofs.sol"; import "src/libraries/Endian.sol"; import "test/mocks/ETHPOSDepositMock.sol"; @@ -80,16 +78,10 @@ contract ExocoreDeployer is Test { hex"608060405260405161090e38038061090e83398101604081905261002291610460565b61002e82826000610035565b505061058a565b61003e83610100565b6040516001600160a01b038416907f1cf3b03a6cf19fa2baba4df148e9dcabedea7f8a5c07840e207e5c089be95d3e90600090a260008251118061007f5750805b156100fb576100f9836001600160a01b0316635c60da1b6040518163ffffffff1660e01b8152600401602060405180830381865afa1580156100c5573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906100e99190610520565b836102a360201b6100291760201c565b505b505050565b610113816102cf60201b6100551760201c565b6101725760405162461bcd60e51b815260206004820152602560248201527f455243313936373a206e657720626561636f6e206973206e6f74206120636f6e6044820152641d1c9858dd60da1b60648201526084015b60405180910390fd5b6101e6816001600160a01b0316635c60da1b6040518163ffffffff1660e01b8152600401602060405180830381865afa1580156101b3573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906101d79190610520565b6102cf60201b6100551760201c565b61024b5760405162461bcd60e51b815260206004820152603060248201527f455243313936373a20626561636f6e20696d706c656d656e746174696f6e206960448201526f1cc81b9bdd08184818dbdb9d1c9858dd60821b6064820152608401610169565b806102827fa3f0ad74e5423aebfd80d3ef4346578335a9a72aeaee59ff6cb3582b35133d5060001b6102de60201b6100641760201c565b80546001600160a01b0319166001600160a01b039290921691909117905550565b60606102c883836040518060600160405280602781526020016108e7602791396102e1565b9392505050565b6001600160a01b03163b151590565b90565b6060600080856001600160a01b0316856040516102fe919061053b565b600060405180830381855af49150503d8060008114610339576040519150601f19603f3d011682016040523d82523d6000602084013e61033e565b606091505b5090925090506103508683838761035a565b9695505050505050565b606083156103c65782516103bf576001600160a01b0385163b6103bf5760405162461bcd60e51b815260206004820152601d60248201527f416464726573733a2063616c6c20746f206e6f6e2d636f6e74726163740000006044820152606401610169565b50816103d0565b6103d083836103d8565b949350505050565b8151156103e85781518083602001fd5b8060405162461bcd60e51b81526004016101699190610557565b80516001600160a01b038116811461041957600080fd5b919050565b634e487b7160e01b600052604160045260246000fd5b60005b8381101561044f578181015183820152602001610437565b838111156100f95750506000910152565b6000806040838503121561047357600080fd5b61047c83610402565b60208401519092506001600160401b038082111561049957600080fd5b818501915085601f8301126104ad57600080fd5b8151818111156104bf576104bf61041e565b604051601f8201601f19908116603f011681019083821181831017156104e7576104e761041e565b8160405282815288602084870101111561050057600080fd5b610511836020830160208801610434565b80955050505050509250929050565b60006020828403121561053257600080fd5b6102c882610402565b6000825161054d818460208701610434565b9190910192915050565b6020815260008251806020840152610576816040850160208701610434565b601f01601f19169190910160400192915050565b61034e806105996000396000f3fe60806040523661001357610011610017565b005b6100115b610027610022610067565b610100565b565b606061004e83836040518060600160405280602781526020016102f260279139610124565b9392505050565b6001600160a01b03163b151590565b90565b600061009a7fa3f0ad74e5423aebfd80d3ef4346578335a9a72aeaee59ff6cb3582b35133d50546001600160a01b031690565b6001600160a01b0316635c60da1b6040518163ffffffff1660e01b8152600401602060405180830381865afa1580156100d7573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906100fb9190610249565b905090565b3660008037600080366000845af43d6000803e80801561011f573d6000f35b3d6000fd5b6060600080856001600160a01b03168560405161014191906102a2565b600060405180830381855af49150503d806000811461017c576040519150601f19603f3d011682016040523d82523d6000602084013e610181565b606091505b50915091506101928683838761019c565b9695505050505050565b6060831561020d578251610206576001600160a01b0385163b6102065760405162461bcd60e51b815260206004820152601d60248201527f416464726573733a2063616c6c20746f206e6f6e2d636f6e747261637400000060448201526064015b60405180910390fd5b5081610217565b610217838361021f565b949350505050565b81511561022f5781518083602001fd5b8060405162461bcd60e51b81526004016101fd91906102be565b60006020828403121561025b57600080fd5b81516001600160a01b038116811461004e57600080fd5b60005b8381101561028d578181015183820152602001610275565b8381111561029c576000848401525b50505050565b600082516102b4818460208701610272565b9190910192915050565b60208152600082518060208401526102dd816040850160208701610272565b601f01601f1916919091016040019291505056fe416464726573733a206c6f772d6c6576656c2064656c65676174652063616c6c206661696c6564a2646970667358221220d51e81d3bc5ed20a26aeb05dce7e825c503b2061aa78628027300c8d65b9d89a64736f6c634300080c0033416464726573733a206c6f772d6c6576656c2064656c65676174652063616c6c206661696c6564"; bytes32[] validatorContainer; - bytes32 beaconBlockRoot; // latest beacon block root + bytes32 beaconBlockRoot; IExoCapsule.ValidatorContainerProof validatorProof; - - bytes32[] withdrawalContainer; - BeaconChainProofs.WithdrawalProof withdrawalProof; - bytes32 withdrawBeaconBlockRoot; // block root for withdrawal proof - uint256 mockProofTimestamp; uint256 mockCurrentBlockTimestamp; - uint256 activationTimestamp; uint32 exocoreChainId = 2; uint32 clientChainId = 1; @@ -121,8 +113,9 @@ contract ExocoreDeployer is Test { vm.etch(CLAIM_REWARD_PRECOMPILE_ADDRESS, WithdrawRewardMockCode); // load beacon chain validator container and proof from json file - string memory validatorInfo = vm.readFile("test/foundry/test-data/validator_container_proof_302913.json"); - _loadValidatorContainer(validatorInfo); + _loadValidatorContainer(); + _loadValidatorProof(); + _loadBeaconBlockRoot(); vm.chainId(clientChainId); _deploy(); @@ -215,9 +208,15 @@ contract ExocoreDeployer is Test { vm.stopPrank(); } - function _loadValidatorContainer(string memory validatorInfo) internal { + function _loadValidatorContainer() internal { + string memory validatorInfo = vm.readFile("test/foundry/test-data/validator_container_proof_302913.json"); + validatorContainer = stdJson.readBytes32Array(validatorInfo, ".ValidatorFields"); require(validatorContainer.length > 0, "validator container should not be empty"); + } + + function _loadValidatorProof() internal { + string memory validatorInfo = vm.readFile("test/foundry/test-data/validator_container_proof_302913.json"); validatorProof.stateRoot = stdJson.readBytes32(validatorInfo, ".beaconStateRoot"); require(validatorProof.stateRoot != bytes32(0), "state root should not be empty"); @@ -229,41 +228,15 @@ contract ExocoreDeployer is Test { require(validatorProof.validatorContainerRootProof.length == 46, "validator root proof should have 46 nodes"); validatorProof.validatorIndex = stdJson.readUint(validatorInfo, ".validatorIndex"); require(validatorProof.validatorIndex != 0, "validator root index should not be 0"); + } + + function _loadBeaconBlockRoot() internal { + string memory validatorInfo = vm.readFile("test/foundry/test-data/validator_container_proof_302913.json"); beaconBlockRoot = stdJson.readBytes32(validatorInfo, ".latestBlockHeaderRoot"); require(beaconBlockRoot != bytes32(0), "beacon block root should not be empty"); } - function _loadWithdrawalContainer(string memory withdrawalInfo) internal { - withdrawalContainer = stdJson.readBytes32Array(withdrawalInfo, ".WithdrawalFields"); - require(withdrawalContainer.length > 0, "validator container should not be empty"); - - // bytes32 array proof data - withdrawalProof.withdrawalContainerRootProof = stdJson.readBytes32Array(withdrawalInfo, ".WithdrawalProof"); - withdrawalProof.slotProof = stdJson.readBytes32Array(withdrawalInfo, ".SlotProof"); - withdrawalProof.executionPayloadRootProof = stdJson.readBytes32Array(withdrawalInfo, ".ExecutionPayloadProof"); - withdrawalProof.timestampProof = stdJson.readBytes32Array(withdrawalInfo, ".TimestampProof"); - withdrawalProof.historicalSummaryBlockRootProof = - stdJson.readBytes32Array(withdrawalInfo, ".HistoricalSummaryProof"); - - // Index data - withdrawalProof.blockRootIndex = stdJson.readUint(withdrawalInfo, ".blockHeaderRootIndex"); - require(withdrawalProof.blockRootIndex != 0, "block header root index should not be 0"); - - withdrawalProof.historicalSummaryIndex = stdJson.readUint(withdrawalInfo, ".historicalSummaryIndex"); - require(withdrawalProof.historicalSummaryIndex != 0, "historical summary index should not be 0"); - - withdrawalProof.withdrawalIndex = stdJson.readUint(withdrawalInfo, ".withdrawalIndex"); - - // Root data - withdrawalProof.blockRoot = stdJson.readBytes32(withdrawalInfo, ".blockHeaderRoot"); - withdrawalProof.slotRoot = stdJson.readBytes32(withdrawalInfo, ".slotRoot"); - withdrawalProof.timestampRoot = stdJson.readBytes32(withdrawalInfo, ".timestampRoot"); - withdrawalProof.executionPayloadRoot = stdJson.readBytes32(withdrawalInfo, ".executionPayloadRoot"); - withdrawalProof.stateRoot = stdJson.readBytes32(withdrawalInfo, ".beaconStateRoot"); - require(withdrawalProof.stateRoot != bytes32(0), "state root should not be empty"); - } - function _deploy() internal { // prepare outside contracts like ERC20 token contract and layerzero endpoint contract restakeToken = new ERC20PresetFixedSupply("rest", "rest", 1e34, exocoreValidatorSet.addr); @@ -400,10 +373,6 @@ contract ExocoreDeployer is Test { return vc[2].fromLittleEndianUint64(); } - function _getWithdrawalAmount(bytes32[] storage wc) internal view returns (uint64) { - return wc[3].fromLittleEndianUint64(); - } - function generateUID(uint64 nonce, bool fromClientChainToExocore) internal view returns (bytes32 uid) { if (fromClientChainToExocore) { uid = GUID.generate( diff --git a/test/foundry/test-data/full_withdrawal_proof.json b/test/foundry/test-data/full_withdrawal_proof.json deleted file mode 100644 index bf4392ac..00000000 --- a/test/foundry/test-data/full_withdrawal_proof.json +++ /dev/null @@ -1,159 +0,0 @@ -{ - "slot": 6397852, - "validatorIndex": 302913, - "historicalSummaryIndex": 146, - "withdrawalIndex": 0, - "blockHeaderRootIndex": 8092, - "beaconStateRoot": "0xe562b064fa5f17412bf13cd3b650f9d84b912f127c3f4c09879864d51a8b4daf", - "slotRoot": "0x9c9f610000000000000000000000000000000000000000000000000000000000", - "timestampRoot": "0xb06fed6400000000000000000000000000000000000000000000000000000000", - "blockHeaderRoot": "0x8b036996f94e940c80c5c692fd0e25467a5d55f1cf92b7808f92090fc7be1d17", - "executionPayloadRoot": "0xe628472355543b53917635e60c1f924f111f7a3cd58f2d947e8631b9d9924cb1", - "latestBlockHeaderRoot": "0xa81fa0ec796b5f84e6435745245f6d24279a11a74e29666560355507c441332d", - "SlotProof": [ - "0x89c5010000000000000000000000000000000000000000000000000000000000", - "0xab4a015ca78ff722e478d047b19650dc6fc92a4270c6cd34401523d3d6a1d9f2", - "0xf4e65df697eb85f3ab176ac93b6ad4d96bd6b04bdddcc4f6c98f0fa94effc553" - ], - "WithdrawalProof": [ - "0xa3d843f57c18ee3dac0eb263e446fe5d0110059137807d3cae4a2e60ccca013f", - "0x87441da495942a4af734cbca4dbcf0b96b2d83137ce595c9f29495aae6a8d99e", - "0xae0dc609ecbfb26abc191227a76efb332aaea29725253756f2cad136ef5837a6", - "0x765bcd075991ecad96203020d1576fdb9b45b41dad3b5adde11263ab9f6f56b8", - "0x1000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x9d9b56c23faa6a8abf0a49105eb12bbdfdf198a9c6c616b8f24f6e91ad79de92", - "0xac5e32ea973e990d191039e91c7d9fd9830599b8208675f159c3df128228e729", - "0x38914949a92dc9e402aee96301b080f769f06d752a32acecaa0458cba66cf471" - ], - "WithdrawalCredentialProof": [ - "0x9e06c3582190fe488eac3f9f6c95622742f9afe3e038b39d2ca97ba6d5d0de4e", - "0x3eb11a14af12d8558cc14493938ffa0a1c6155349699c2b9245e76344d9922ee", - "0x81c959aeae7524f4f1d2d3d930ba504cbe86330619a221c9e2d9fb315e32a4d1", - "0x9b9adf5d31a74f30ae86ce7f7a8bfe89d2bdf2bd799d0c6896174b4f15878bf1", - "0x17d22cd18156b4bcbefbcfa3ed820c14cc5af90cb7c02c373cc476bc947ba4ac", - "0x22c1a00da80f2c5c8a11fdd629af774b9dde698305735d63b19aed6a70310537", - "0x949da2d82acf86e064a7022c5d5e69528ad6d3dd5b9cdf7fb9b736f0d925fc38", - "0x1920215f3c8c349a04f6d29263f495416e58d85c885b7d356dd4d335427d2748", - "0x7f12746ac9a3cc418594ab2c25838fdaf9ef43050a12f38f0c25ad7f976d889a", - "0x451a649946a59a90f56035d1eccdfcaa99ac8bb74b87c403653bdc5bc0055e2c", - "0x00ab86a6644a7694fa7bc0da3a8730404ea7e26da981b169316f7acdbbe8c79b", - "0x0d500027bb8983acbec0993a3d063f5a1f4b9a5b5893016bc9eec28e5633867e", - "0x2ba5cbed64a0202199a181c8612a8c5dad2512ad0ec6aa7f0c079392e16008ee", - "0xab8576644897391ddc0773ac95d072de29d05982f39201a5e0630b81563e91e9", - "0xc6e90f3f46f28faea3476837d2ec58ad5fa171c1f04446f2aa40aa433523ec74", - "0xb86e491b234c25dc5fa17b43c11ef04a7b3e89f99b2bb7d8daf87ee6dc6f3ae3", - "0xdb41e006a5111a4f2620a004e207d2a63fc5324d7f528409e779a34066a9b67f", - "0xe2356c743f98d89213868108ad08074ca35f685077e487077cef8a55917736c6", - "0xf7552771443e29ebcc7a4aad87e308783559a0b4ff696a0e49f81fb2736fe528", - "0x3d3aabf6c36de4242fef4b6e49441c24451ccf0e8e184a33bece69d3e3d40ac3", - "0xcddba7b592e3133393c16194fac7431abf2f5485ed711db282183c819e08ebaa", - "0x8a8d7fe3af8caa085a7639a832001457dfb9128a8061142ad0335629ff23ff9c", - "0xfeb3c337d7a51a6fbf00b9e34c52e1c9195c969bd4e7a0bfd51d5c5bed9c1167", - "0xe71f0aa83cc32edfbefa9f4d3e0174ca85182eec9f3a09f6a6c0df6377a510d7", - "0x31206fa80a50bb6abe29085058f16212212a60eec8f049fecb92d8c8e0a84bc0", - "0x21352bfecbeddde993839f614c3dac0a3ee37543f9b412b16199dc158e23b544", - "0x619e312724bb6d7c3153ed9de791d764a366b389af13c58bf8a8d90481a46765", - "0x7cdd2986268250628d0c10e385c58c6191e6fbe05191bcc04f133f2cea72c1c4", - "0x848930bd7ba8cac54661072113fb278869e07bb8587f91392933374d017bcbe1", - "0x8869ff2c22b28cc10510d9853292803328be4fb0e80495e8bb8d271f5b889636", - "0xb5fe28e79f1b850f8658246ce9b6a1e7b49fc06db7143e8fe0b4f2b0c5523a5c", - "0x985e929f70af28d0bdd1a90a808f977f597c7c778c489e98d3bd8910d31ac0f7", - "0xc6f67e02e6e4e1bdefb994c6098953f34636ba2b6ca20a4721d2b26a886722ff", - "0x1c9a7e5ff1cf48b4ad1582d3f4e4a1004f3b20d8c5a2b71387a4254ad933ebc5", - "0x2f075ae229646b6f6aed19a5e372cf295081401eb893ff599b3f9acc0c0d3e7d", - "0x328921deb59612076801e8cd61592107b5c67c79b846595cc6320c395b46362c", - "0xbfb909fdb236ad2411b4e4883810a074b840464689986c3f8a8091827e17c327", - "0x55d8fb3687ba3ba49f342c77f5a1f89bec83d811446e1a467139213d640b6a74", - "0xf7210d4f8e7e1039790e7bf4efa207555a10a6db1dd4b95da313aaa88b88fe76", - "0xad21b516cbc645ffe34ab5de1c8aef8cd4e7f8d2b51e8e1456adc7563cda206f", - "0x846b080000000000000000000000000000000000000000000000000000000000", - "0x5a6c050000000000000000000000000000000000000000000000000000000000", - "0x47314ebc95c63f3fd909af6442ed250d823b2ee8a6e48d8167d4dfdab96c8b5e", - "0xce3d1b002aa817e3718132b7ffe6cea677f81b1b3b690b8052732d8e1a70d06b", - "0x4715bf9a259680cd06827b30bddb27ad445506e9edeb72a3eab904d94dea816b", - "0xba25997a12576cc460c39cfc6cc87d7dc215b237a0f3c4dc8cb7473125b2e138" - ], - "TimestampProof": [ - "0x28a2c80000000000000000000000000000000000000000000000000000000000", - "0xa749df3368741198702798435eea361b1b1946aa9456587a2be377c8472ea2df", - "0xb1c03097a5a24f46cdb684df37e3ac0536a76428be1a6c6d6450378701ab1f3d", - "0x38914949a92dc9e402aee96301b080f769f06d752a32acecaa0458cba66cf471" - ], - "ExecutionPayloadProof": [ - "0xb6a435ffd17014d1dad214ba466aaa7fba5aa247945d2c29fd53e90d554f4474", - "0x336488033fe5f3ef4ccc12af07b9370b92e553e35ecb4a337a1b1c0e4afe1e0e", - "0xdb56114e00fdd4c1f85c892bf35ac9a89289aaecb1ebd0a96cde606a748b5d71", - "0x5ec9aaf0a3571602f4704d4471b9af564caf17e4d22a7c31017293cb95949053", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0xf5a5fd42d16a20302798ef6ed309979b43003d2320d9f0e8ea9831a92759fb4b", - "0xc1f7cb289e44e9f711d7d7c05b67b84b8c6dd0394b688922a490cfd3fe216db1" - ], - "ValidatorFields": [ - "0xe36689b7b39ee895a754ba878afac2aa5d83349143a8b23d371823dd9ed3435d", - "0x0100000000000000000000008e35f095545c56b07c942a4f3b055ef1ec4cb148", - "0x0040597307000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0xea65010000000000000000000000000000000000000000000000000000000000", - "0xf265010000000000000000000000000000000000000000000000000000000000", - "0xffffffffffffffff000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000" - ], - "WithdrawalFields": [ - "0x45cee50000000000000000000000000000000000000000000000000000000000", - "0x419f040000000000000000000000000000000000000000000000000000000000", - "0x59b0d71688da01057c08e4c1baa8faa629819c2a000000000000000000000000", - "0xe5015b7307000000000000000000000000000000000000000000000000000000" - ], - "StateRootAgainstLatestBlockHeaderProof": [ - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0xf5a5fd42d16a20302798ef6ed309979b43003d2320d9f0e8ea9831a92759fb4b", - "0xdb56114e00fdd4c1f85c892bf35ac9a89289aaecb1ebd0a96cde606a748b5d71" - ], - "HistoricalSummaryProof": [ - "0x050b5923fe2e470849a7d467e4cbed4803d3a46d516b84552567976960ff4ccc", - "0x6ab9b5fc357c793cc5339398016c28ea21f0be4d56a68b554a29766dd312eeeb", - "0xb8c4c9f1dec537f4c4652e6bf519bac768e85b2590b7683e392aab698b50d529", - "0x1cae4e7facb6883024359eb1e9212d36e68a106a7733dc1c099017a74e5f465a", - "0x616439c1379e10fc6ad2fa192a2e49c2d5a7155fdde95911f37fcfb75952fcb2", - "0x301ab0d5d5ada4bd2e859658fc31743b5a502b26bc9b8b162e5a161e21218048", - "0x9d2bc97fffd61659313009e67a4c729a10274f2af26914a53bc7af6717da211e", - "0x4bdcbe543f9ef7348855aac43d6b6286f9c0c7be53de8a1300bea1ba5ba0758e", - "0xb6631640d626ea9523ae619a42633072614326cc9220462dffdeb63e804ef05f", - "0xf19a76e33ca189a8682ece523c2afda138db575955b7af31a427c9b8adb41e15", - "0x221b43ad87d7410624842cad296fc48360b5bf4e835f6ff610db736774d2f2d3", - "0x297c51f4ff236db943bebeb35538e207c8de6330d26aa8138a9ca206f42154bf", - "0x129a0644f33b9ee4e9a36a11dd59d1dedc64012fbb7a79263d07f82d647ffba8", - "0x763794c2042b9c8381ac7c4d7f4d38b0abb38069b2810b522f87951f25d9d2be", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0xee0639a2ada97368e8b3493f9d2141e16c3cd9fe54e13691bb7a2c376c56c7c8", - "0xdb56114e00fdd4c1f85c892bf35ac9a89289aaecb1ebd0a96cde606a748b5d71", - "0xc78009fdf07fc56a11f122370658a353aaa542ed63e44c4bc15ff4cd105ab33c", - "0xba2bc704559c23541f0c9efa0b522454e8cd06cd504d0e45724709cf5672640f", - "0x9efde052aa15429fae05bad4d0b1d7c64da64d03d7a1854a588c2cb8430c0d30", - "0xd88ddfeed400a8755596b21942c1497e114c302e6118290f91e6772976041fa1", - "0xff857f4c17c9fb2e544e496685ebd8e2258c761e4636cfb031ba73a4430061c7", - "0x26846476fd5fc54a5d43385167c95144f2643f533cc85bb9d16b782f8d7db193", - "0x506d86582d252405b840018792cad2bf1259f1ef5aa5f887e13cb2f0094f51e1", - "0xffff0ad7e659772f9534c195c815efc4014ef1e1daed4404c06385d11192e92b", - "0x6cf04127db05441cd833107a52be852868890e4317e6a02ab47683aa75964220", - "0xb7d05f875f140027ef5118a2247bbb84ce8f2f0f1123623085daf7960c329f5f", - "0xdf6af5f5bbdb6be9ef8aa618e4bf8073960867171e29676f8b284dea6a08a85e", - "0xb58d900f5e182e3c50ef74969ea16c7726c549757cc23523c369587da7293784", - "0xd49a7502ffcfb0340b1d7885688500ca308161a7f96b62df9d083b71fcc8f2bb", - "0x8fe6b1689256c0d385f42f5bbe2027a22c1996e110ba97c171d3e5948de92beb", - "0x8d0d63c39ebade8509e0ae3c9c3876fb5fa112be18f905ecacfecb92057603ab", - "0x95eec8b2e541cad4e91de38385f2e046619f54496c2382cb6cacd5b98c26f5a4", - "0xf893e908917775b62bff23294dbbe3a1cd8e6cc1c35b4801887b646a6f81f17f", - "0xcddba7b592e3133393c16194fac7431abf2f5485ed711db282183c819e08ebaa", - "0x8a8d7fe3af8caa085a7639a832001457dfb9128a8061142ad0335629ff23ff9c", - "0xfeb3c337d7a51a6fbf00b9e34c52e1c9195c969bd4e7a0bfd51d5c5bed9c1167", - "0xe71f0aa83cc32edfbefa9f4d3e0174ca85182eec9f3a09f6a6c0df6377a510d7", - "0x9300000000000000000000000000000000000000000000000000000000000000", - "0xd9ed050000000000000000000000000000000000000000000000000000000000", - "0xd824a89a9d5dd329a069b394ddc6618c70ed784982061959ac77d58d48e9d7c8", - "0xdb56114e00fdd4c1f85c892bf35ac9a89289aaecb1ebd0a96cde606a748b5d71", - "0x8dc0b81ebe27fb91663a74713252d2eae5deb3b983374afc3c2d2f6b254128f1", - "0x09c7a863a253b4163a10916db2781eb35f58c016f0a24b3331ed84af3822c341" - ] -} diff --git a/test/foundry/test-data/partial_withdrawal_proof.json b/test/foundry/test-data/partial_withdrawal_proof.json deleted file mode 100644 index 0bca46c9..00000000 --- a/test/foundry/test-data/partial_withdrawal_proof.json +++ /dev/null @@ -1,159 +0,0 @@ -{ - "slot": 6397852, - "validatorIndex": 302913, - "historicalSummaryIndex": 146, - "withdrawalIndex": 0, - "blockHeaderRootIndex": 8092, - "beaconStateRoot": "0xc4ea7f435356d2de29784712dc1fb5597d7d36ec705ddebcbef8bdc4cb4ecaf0", - "slotRoot": "0x9c9f610000000000000000000000000000000000000000000000000000000000", - "timestampRoot": "0xb06fed6400000000000000000000000000000000000000000000000000000000", - "blockHeaderRoot": "0x8d9698a822c4f57d890f9465c046ae8799301ff01daca55fb2e8e347d547ec6d", - "executionPayloadRoot": "0xf6e0315572785590a6ee69adab31e5c1e9879264aeead5b7762a384ae7d938d0", - "latestBlockHeaderRoot": "0x34e6f6ae9370c1eacc38aad8c5a887983979b79a35c57f09570afd80a879fd69", - "SlotProof": [ - "0x89c5010000000000000000000000000000000000000000000000000000000000", - "0xab4a015ca78ff722e478d047b19650dc6fc92a4270c6cd34401523d3d6a1d9f2", - "0xe01b2b111680b6b01268c23ee3d7d4d145e98809dd5894cc8d1944bdc19e346e" - ], - "WithdrawalProof": [ - "0xa3d843f57c18ee3dac0eb263e446fe5d0110059137807d3cae4a2e60ccca013f", - "0x87441da495942a4af734cbca4dbcf0b96b2d83137ce595c9f29495aae6a8d99e", - "0xae0dc609ecbfb26abc191227a76efb332aaea29725253756f2cad136ef5837a6", - "0x765bcd075991ecad96203020d1576fdb9b45b41dad3b5adde11263ab9f6f56b8", - "0x1000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x9d9b56c23faa6a8abf0a49105eb12bbdfdf198a9c6c616b8f24f6e91ad79de92", - "0xac5e32ea973e990d191039e91c7d9fd9830599b8208675f159c3df128228e729", - "0x38914949a92dc9e402aee96301b080f769f06d752a32acecaa0458cba66cf471" - ], - "WithdrawalCredentialProof": [ - "0x9e06c3582190fe488eac3f9f6c95622742f9afe3e038b39d2ca97ba6d5d0de4e", - "0x3eb11a14af12d8558cc14493938ffa0a1c6155349699c2b9245e76344d9922ee", - "0x81c959aeae7524f4f1d2d3d930ba504cbe86330619a221c9e2d9fb315e32a4d1", - "0x9b9adf5d31a74f30ae86ce7f7a8bfe89d2bdf2bd799d0c6896174b4f15878bf1", - "0x17d22cd18156b4bcbefbcfa3ed820c14cc5af90cb7c02c373cc476bc947ba4ac", - "0x22c1a00da80f2c5c8a11fdd629af774b9dde698305735d63b19aed6a70310537", - "0x949da2d82acf86e064a7022c5d5e69528ad6d3dd5b9cdf7fb9b736f0d925fc38", - "0x1920215f3c8c349a04f6d29263f495416e58d85c885b7d356dd4d335427d2748", - "0x7f12746ac9a3cc418594ab2c25838fdaf9ef43050a12f38f0c25ad7f976d889a", - "0x451a649946a59a90f56035d1eccdfcaa99ac8bb74b87c403653bdc5bc0055e2c", - "0x00ab86a6644a7694fa7bc0da3a8730404ea7e26da981b169316f7acdbbe8c79b", - "0x0d500027bb8983acbec0993a3d063f5a1f4b9a5b5893016bc9eec28e5633867e", - "0x2ba5cbed64a0202199a181c8612a8c5dad2512ad0ec6aa7f0c079392e16008ee", - "0xab8576644897391ddc0773ac95d072de29d05982f39201a5e0630b81563e91e9", - "0xc6e90f3f46f28faea3476837d2ec58ad5fa171c1f04446f2aa40aa433523ec74", - "0xb86e491b234c25dc5fa17b43c11ef04a7b3e89f99b2bb7d8daf87ee6dc6f3ae3", - "0xdb41e006a5111a4f2620a004e207d2a63fc5324d7f528409e779a34066a9b67f", - "0xe2356c743f98d89213868108ad08074ca35f685077e487077cef8a55917736c6", - "0xf7552771443e29ebcc7a4aad87e308783559a0b4ff696a0e49f81fb2736fe528", - "0x3d3aabf6c36de4242fef4b6e49441c24451ccf0e8e184a33bece69d3e3d40ac3", - "0xcddba7b592e3133393c16194fac7431abf2f5485ed711db282183c819e08ebaa", - "0x8a8d7fe3af8caa085a7639a832001457dfb9128a8061142ad0335629ff23ff9c", - "0xfeb3c337d7a51a6fbf00b9e34c52e1c9195c969bd4e7a0bfd51d5c5bed9c1167", - "0xe71f0aa83cc32edfbefa9f4d3e0174ca85182eec9f3a09f6a6c0df6377a510d7", - "0x31206fa80a50bb6abe29085058f16212212a60eec8f049fecb92d8c8e0a84bc0", - "0x21352bfecbeddde993839f614c3dac0a3ee37543f9b412b16199dc158e23b544", - "0x619e312724bb6d7c3153ed9de791d764a366b389af13c58bf8a8d90481a46765", - "0x7cdd2986268250628d0c10e385c58c6191e6fbe05191bcc04f133f2cea72c1c4", - "0x848930bd7ba8cac54661072113fb278869e07bb8587f91392933374d017bcbe1", - "0x8869ff2c22b28cc10510d9853292803328be4fb0e80495e8bb8d271f5b889636", - "0xb5fe28e79f1b850f8658246ce9b6a1e7b49fc06db7143e8fe0b4f2b0c5523a5c", - "0x985e929f70af28d0bdd1a90a808f977f597c7c778c489e98d3bd8910d31ac0f7", - "0xc6f67e02e6e4e1bdefb994c6098953f34636ba2b6ca20a4721d2b26a886722ff", - "0x1c9a7e5ff1cf48b4ad1582d3f4e4a1004f3b20d8c5a2b71387a4254ad933ebc5", - "0x2f075ae229646b6f6aed19a5e372cf295081401eb893ff599b3f9acc0c0d3e7d", - "0x328921deb59612076801e8cd61592107b5c67c79b846595cc6320c395b46362c", - "0xbfb909fdb236ad2411b4e4883810a074b840464689986c3f8a8091827e17c327", - "0x55d8fb3687ba3ba49f342c77f5a1f89bec83d811446e1a467139213d640b6a74", - "0xf7210d4f8e7e1039790e7bf4efa207555a10a6db1dd4b95da313aaa88b88fe76", - "0xad21b516cbc645ffe34ab5de1c8aef8cd4e7f8d2b51e8e1456adc7563cda206f", - "0x846b080000000000000000000000000000000000000000000000000000000000", - "0x5a6c050000000000000000000000000000000000000000000000000000000000", - "0x47314ebc95c63f3fd909af6442ed250d823b2ee8a6e48d8167d4dfdab96c8b5e", - "0xce3d1b002aa817e3718132b7ffe6cea677f81b1b3b690b8052732d8e1a70d06b", - "0x4715bf9a259680cd06827b30bddb27ad445506e9edeb72a3eab904d94dea816b", - "0x673e299c82563e700195d7d66981bdf521b9609a3b6d22be78f069b359e553e5" - ], - "TimestampProof": [ - "0x28a2c80000000000000000000000000000000000000000000000000000000000", - "0xa749df3368741198702798435eea361b1b1946aa9456587a2be377c8472ea2df", - "0x568268c732e7471701bcaedcae302de5fe87b60cf9e6518696719f9732078b97", - "0x38914949a92dc9e402aee96301b080f769f06d752a32acecaa0458cba66cf471" - ], - "ExecutionPayloadProof": [ - "0xb6a435ffd17014d1dad214ba466aaa7fba5aa247945d2c29fd53e90d554f4474", - "0x336488033fe5f3ef4ccc12af07b9370b92e553e35ecb4a337a1b1c0e4afe1e0e", - "0xdb56114e00fdd4c1f85c892bf35ac9a89289aaecb1ebd0a96cde606a748b5d71", - "0x5ec9aaf0a3571602f4704d4471b9af564caf17e4d22a7c31017293cb95949053", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0xf5a5fd42d16a20302798ef6ed309979b43003d2320d9f0e8ea9831a92759fb4b", - "0xc1f7cb289e44e9f711d7d7c05b67b84b8c6dd0394b688922a490cfd3fe216db1" - ], - "ValidatorFields": [ - "0xe36689b7b39ee895a754ba878afac2aa5d83349143a8b23d371823dd9ed3435d", - "0x0100000000000000000000008e35f095545c56b07c942a4f3b055ef1ec4cb148", - "0x0040597307000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0xea65010000000000000000000000000000000000000000000000000000000000", - "0xf265010000000000000000000000000000000000000000000000000000000000", - "0xffffffffffffffff000000000000000000000000000000000000000000000000", - "0xffffffffffffffff000000000000000000000000000000000000000000000000" - ], - "WithdrawalFields": [ - "0x45cee50000000000000000000000000000000000000000000000000000000000", - "0x419f040000000000000000000000000000000000000000000000000000000000", - "0x59b0d71688da01057c08e4c1baa8faa629819c2a000000000000000000000000", - "0xbd56200000000000000000000000000000000000000000000000000000000000" - ], - "StateRootAgainstLatestBlockHeaderProof": [ - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0xf5a5fd42d16a20302798ef6ed309979b43003d2320d9f0e8ea9831a92759fb4b", - "0xdb56114e00fdd4c1f85c892bf35ac9a89289aaecb1ebd0a96cde606a748b5d71" - ], - "HistoricalSummaryProof": [ - "0x050b5923fe2e470849a7d467e4cbed4803d3a46d516b84552567976960ff4ccc", - "0x6ab9b5fc357c793cc5339398016c28ea21f0be4d56a68b554a29766dd312eeeb", - "0xb8c4c9f1dec537f4c4652e6bf519bac768e85b2590b7683e392aab698b50d529", - "0x1cae4e7facb6883024359eb1e9212d36e68a106a7733dc1c099017a74e5f465a", - "0x616439c1379e10fc6ad2fa192a2e49c2d5a7155fdde95911f37fcfb75952fcb2", - "0x301ab0d5d5ada4bd2e859658fc31743b5a502b26bc9b8b162e5a161e21218048", - "0x9d2bc97fffd61659313009e67a4c729a10274f2af26914a53bc7af6717da211e", - "0x4bdcbe543f9ef7348855aac43d6b6286f9c0c7be53de8a1300bea1ba5ba0758e", - "0xb6631640d626ea9523ae619a42633072614326cc9220462dffdeb63e804ef05f", - "0xf19a76e33ca189a8682ece523c2afda138db575955b7af31a427c9b8adb41e15", - "0x221b43ad87d7410624842cad296fc48360b5bf4e835f6ff610db736774d2f2d3", - "0x297c51f4ff236db943bebeb35538e207c8de6330d26aa8138a9ca206f42154bf", - "0x129a0644f33b9ee4e9a36a11dd59d1dedc64012fbb7a79263d07f82d647ffba8", - "0x763794c2042b9c8381ac7c4d7f4d38b0abb38069b2810b522f87951f25d9d2be", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0xee0639a2ada97368e8b3493f9d2141e16c3cd9fe54e13691bb7a2c376c56c7c8", - "0xdb56114e00fdd4c1f85c892bf35ac9a89289aaecb1ebd0a96cde606a748b5d71", - "0xc78009fdf07fc56a11f122370658a353aaa542ed63e44c4bc15ff4cd105ab33c", - "0xba2bc704559c23541f0c9efa0b522454e8cd06cd504d0e45724709cf5672640f", - "0x9efde052aa15429fae05bad4d0b1d7c64da64d03d7a1854a588c2cb8430c0d30", - "0xd88ddfeed400a8755596b21942c1497e114c302e6118290f91e6772976041fa1", - "0xff857f4c17c9fb2e544e496685ebd8e2258c761e4636cfb031ba73a4430061c7", - "0x26846476fd5fc54a5d43385167c95144f2643f533cc85bb9d16b782f8d7db193", - "0x506d86582d252405b840018792cad2bf1259f1ef5aa5f887e13cb2f0094f51e1", - "0xffff0ad7e659772f9534c195c815efc4014ef1e1daed4404c06385d11192e92b", - "0x6cf04127db05441cd833107a52be852868890e4317e6a02ab47683aa75964220", - "0xb7d05f875f140027ef5118a2247bbb84ce8f2f0f1123623085daf7960c329f5f", - "0xdf6af5f5bbdb6be9ef8aa618e4bf8073960867171e29676f8b284dea6a08a85e", - "0xb58d900f5e182e3c50ef74969ea16c7726c549757cc23523c369587da7293784", - "0xd49a7502ffcfb0340b1d7885688500ca308161a7f96b62df9d083b71fcc8f2bb", - "0x8fe6b1689256c0d385f42f5bbe2027a22c1996e110ba97c171d3e5948de92beb", - "0x8d0d63c39ebade8509e0ae3c9c3876fb5fa112be18f905ecacfecb92057603ab", - "0x95eec8b2e541cad4e91de38385f2e046619f54496c2382cb6cacd5b98c26f5a4", - "0xf893e908917775b62bff23294dbbe3a1cd8e6cc1c35b4801887b646a6f81f17f", - "0xcddba7b592e3133393c16194fac7431abf2f5485ed711db282183c819e08ebaa", - "0x8a8d7fe3af8caa085a7639a832001457dfb9128a8061142ad0335629ff23ff9c", - "0xfeb3c337d7a51a6fbf00b9e34c52e1c9195c969bd4e7a0bfd51d5c5bed9c1167", - "0xe71f0aa83cc32edfbefa9f4d3e0174ca85182eec9f3a09f6a6c0df6377a510d7", - "0x9300000000000000000000000000000000000000000000000000000000000000", - "0xd9ed050000000000000000000000000000000000000000000000000000000000", - "0xd824a89a9d5dd329a069b394ddc6618c70ed784982061959ac77d58d48e9d7c8", - "0xdb56114e00fdd4c1f85c892bf35ac9a89289aaecb1ebd0a96cde606a748b5d71", - "0x8dc0b81ebe27fb91663a74713252d2eae5deb3b983374afc3c2d2f6b254128f1", - "0xe237bc62b6b5269da5f4093c292d0f3bf2cf4d2eb93b4f366dba675c4df9cc62" - ] -} diff --git a/test/foundry/unit/ExoCapsule.t.sol b/test/foundry/unit/ExoCapsule.t.sol index 71884007..df7b07ac 100644 --- a/test/foundry/unit/ExoCapsule.t.sol +++ b/test/foundry/unit/ExoCapsule.t.sol @@ -5,6 +5,7 @@ import "@openzeppelin/contracts/proxy/transparent/ProxyAdmin.sol"; import "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; import "@openzeppelin/contracts/token/ERC20/presets/ERC20PresetFixedSupply.sol"; import "forge-std/Test.sol"; +import "forge-std/console.sol"; import "src/core/ExoCapsule.sol"; import "src/interfaces/IExoCapsule.sol"; @@ -13,7 +14,7 @@ import "src/libraries/BeaconChainProofs.sol"; import "src/libraries/Endian.sol"; import {ExoCapsuleStorage} from "src/storage/ExoCapsuleStorage.sol"; -contract DepositSetup is Test { +contract SetUp is Test { using stdStorage for StdStorage; using Endian for bytes32; @@ -76,7 +77,7 @@ contract DepositSetup is Test { address capsuleAddress = _getCapsuleFromWithdrawalCredentials(_getWithdrawalCredentials(validatorContainer)); vm.etch(capsuleAddress, address(phantomCapsule).code); - capsule = ExoCapsule(payable(capsuleAddress)); + capsule = ExoCapsule(capsuleAddress); assertEq(bytes32(capsule.capsuleWithdrawalCredentials()), _getWithdrawalCredentials(validatorContainer)); stdstore.target(capsuleAddress).sig("gateway()").checked_write(bytes32(uint256(uint160(address(this))))); @@ -114,7 +115,7 @@ contract DepositSetup is Test { } -contract VerifyDepositProof is DepositSetup { +contract VerifyDepositProof is SetUp { using BeaconChainProofs for bytes32; using stdStorage for StdStorage; @@ -295,248 +296,3 @@ contract VerifyDepositProof is DepositSetup { } } - -contract WithdrawalSetup is Test { - - using stdStorage for StdStorage; - using Endian for bytes32; - - bytes32[] validatorContainer; - /** - * struct ValidatorContainerProof { - * uint256 beaconBlockTimestamp; - * bytes32 stateRoot; - * bytes32[] stateRootProof; - * bytes32[] validatorContainerRootProof; - * uint256 validatorIndex; - * } - */ - IExoCapsule.ValidatorContainerProof validatorProof; - - bytes32[] withdrawalContainer; - BeaconChainProofs.WithdrawalProof withdrawalProof; - bytes32 beaconBlockRoot; // latest beacon block root - - ExoCapsule capsule; - IBeaconChainOracle beaconOracle; - address capsuleOwner; - - uint256 constant BEACON_CHAIN_GENESIS_TIME = 1_606_824_023; - /// @notice The number of slots each epoch in the beacon chain - uint64 internal constant SLOTS_PER_EPOCH = 32; - /// @notice The number of seconds in a slot in the beacon chain - uint64 internal constant SECONDS_PER_SLOT = 12; - /// @notice Number of seconds per epoch: 384 == 32 slots/epoch * 12 seconds/slot - uint64 internal constant SECONDS_PER_EPOCH = SLOTS_PER_EPOCH * SECONDS_PER_SLOT; - uint256 internal constant VERIFY_BALANCE_UPDATE_WINDOW_SECONDS = 4.5 hours; - - uint256 mockProofTimestamp; - uint256 mockCurrentBlockTimestamp; - uint256 activationTimestamp; - - function setUp() public { - string memory validatorInfo = vm.readFile("test/foundry/test-data/validator_container_proof_302913.json"); - _setValidatorContainer(validatorInfo); - - beaconOracle = IBeaconChainOracle(address(0x123)); - vm.etch(address(beaconOracle), bytes("aabb")); - - capsuleOwner = address(0x125); - - ExoCapsule phantomCapsule = new ExoCapsule(); - - address capsuleAddress = _getCapsuleFromWithdrawalCredentials(_getWithdrawalCredentials(validatorContainer)); - vm.etch(capsuleAddress, address(phantomCapsule).code); - capsule = ExoCapsule(payable(capsuleAddress)); - assertEq(bytes32(capsule.capsuleWithdrawalCredentials()), _getWithdrawalCredentials(validatorContainer)); - - stdstore.target(capsuleAddress).sig("gateway()").checked_write(bytes32(uint256(uint160(address(this))))); - - stdstore.target(capsuleAddress).sig("capsuleOwner()").checked_write(bytes32(uint256(uint160(capsuleOwner)))); - - stdstore.target(capsuleAddress).sig("beaconOracle()").checked_write( - bytes32(uint256(uint160(address(beaconOracle)))) - ); - - activationTimestamp = BEACON_CHAIN_GENESIS_TIME + _getActivationEpoch(validatorContainer) * SECONDS_PER_EPOCH; - mockProofTimestamp = activationTimestamp; - mockCurrentBlockTimestamp = mockProofTimestamp + SECONDS_PER_SLOT; - - vm.warp(mockCurrentBlockTimestamp); - validatorProof.beaconBlockTimestamp = mockProofTimestamp; - - vm.mockCall( - address(beaconOracle), - abi.encodeWithSelector(beaconOracle.timestampToBlockRoot.selector, mockProofTimestamp), - abi.encode(beaconBlockRoot) - ); - - uint256 depositAmount = capsule.verifyDepositProof(validatorContainer, validatorProof); - - ExoCapsuleStorage.Validator memory validator = - capsule.getRegisteredValidatorByPubkey(_getPubkey(validatorContainer)); - assertEq(uint8(validator.status), uint8(ExoCapsuleStorage.VALIDATOR_STATUS.REGISTERED)); - assertEq(validator.validatorIndex, validatorProof.validatorIndex); - assertEq(validator.mostRecentBalanceUpdateTimestamp, validatorProof.beaconBlockTimestamp); - assertEq(validator.restakedBalanceGwei, depositAmount / 1e9); - - vm.deal(address(capsule), 1 ether); // Deposit 1 ether to handle excess amount withdraw - } - - function _setValidatorContainer(string memory validatorInfo) internal { - validatorContainer = stdJson.readBytes32Array(validatorInfo, ".ValidatorFields"); - require(validatorContainer.length > 0, "validator container should not be empty"); - - validatorProof.stateRoot = stdJson.readBytes32(validatorInfo, ".beaconStateRoot"); - require(validatorProof.stateRoot != bytes32(0), "state root should not be empty"); - validatorProof.stateRootProof = - stdJson.readBytes32Array(validatorInfo, ".StateRootAgainstLatestBlockHeaderProof"); - require(validatorProof.stateRootProof.length == 3, "state root proof should have 3 nodes"); - validatorProof.validatorContainerRootProof = - stdJson.readBytes32Array(validatorInfo, ".WithdrawalCredentialProof"); - require(validatorProof.validatorContainerRootProof.length == 46, "validator root proof should have 46 nodes"); - validatorProof.validatorIndex = stdJson.readUint(validatorInfo, ".validatorIndex"); - require(validatorProof.validatorIndex != 0, "validator root index should not be 0"); - - beaconBlockRoot = stdJson.readBytes32(validatorInfo, ".latestBlockHeaderRoot"); - require(beaconBlockRoot != bytes32(0), "beacon block root should not be empty"); - } - - function _setWithdrawalContainer(string memory withdrawalInfo) internal { - withdrawalContainer = stdJson.readBytes32Array(withdrawalInfo, ".WithdrawalFields"); - require(withdrawalContainer.length > 0, "validator container should not be empty"); - - // bytes32 array proof data - withdrawalProof.withdrawalContainerRootProof = stdJson.readBytes32Array(withdrawalInfo, ".WithdrawalProof"); - withdrawalProof.slotProof = stdJson.readBytes32Array(withdrawalInfo, ".SlotProof"); - withdrawalProof.executionPayloadRootProof = stdJson.readBytes32Array(withdrawalInfo, ".ExecutionPayloadProof"); - withdrawalProof.timestampProof = stdJson.readBytes32Array(withdrawalInfo, ".TimestampProof"); - withdrawalProof.historicalSummaryBlockRootProof = - stdJson.readBytes32Array(withdrawalInfo, ".HistoricalSummaryProof"); - - // Index data - withdrawalProof.blockRootIndex = stdJson.readUint(withdrawalInfo, ".blockHeaderRootIndex"); - require(withdrawalProof.blockRootIndex != 0, "block header root index should not be 0"); - - withdrawalProof.historicalSummaryIndex = stdJson.readUint(withdrawalInfo, ".historicalSummaryIndex"); - require(withdrawalProof.historicalSummaryIndex != 0, "historical summary index should not be 0"); - - withdrawalProof.withdrawalIndex = stdJson.readUint(withdrawalInfo, ".withdrawalIndex"); - - // Root data - withdrawalProof.blockRoot = stdJson.readBytes32(withdrawalInfo, ".blockHeaderRoot"); - withdrawalProof.slotRoot = stdJson.readBytes32(withdrawalInfo, ".slotRoot"); - withdrawalProof.timestampRoot = stdJson.readBytes32(withdrawalInfo, ".timestampRoot"); - withdrawalProof.executionPayloadRoot = stdJson.readBytes32(withdrawalInfo, ".executionPayloadRoot"); - withdrawalProof.stateRoot = stdJson.readBytes32(withdrawalInfo, ".beaconStateRoot"); - require(withdrawalProof.stateRoot != bytes32(0), "state root should not be empty"); - } - - function _setTimeStamp() internal { - validatorProof.beaconBlockTimestamp = activationTimestamp + SECONDS_PER_SLOT; - mockCurrentBlockTimestamp = validatorProof.beaconBlockTimestamp + SECONDS_PER_SLOT; - vm.warp(mockCurrentBlockTimestamp); - vm.mockCall( - address(beaconOracle), - abi.encodeWithSelector(beaconOracle.timestampToBlockRoot.selector, validatorProof.beaconBlockTimestamp), - abi.encode(beaconBlockRoot) - ); - } - - function _getCapsuleFromWithdrawalCredentials(bytes32 withdrawalCredentials) internal pure returns (address) { - return address(bytes20(uint160(uint256(withdrawalCredentials)))); - } - - function _getPubkey(bytes32[] storage vc) internal view returns (bytes32) { - return vc[0]; - } - - function _getWithdrawalCredentials(bytes32[] storage vc) internal view returns (bytes32) { - return vc[1]; - } - - function _getEffectiveBalance(bytes32[] storage vc) internal view returns (uint64) { - return vc[2].fromLittleEndianUint64(); - } - - function _getActivationEpoch(bytes32[] storage vc) internal view returns (uint64) { - return vc[5].fromLittleEndianUint64(); - } - - function _getExitEpoch(bytes32[] storage vc) internal view returns (uint64) { - return vc[6].fromLittleEndianUint64(); - } - -} - -contract VerifyWithdrawalProof is WithdrawalSetup { - - using BeaconChainProofs for bytes32; - using stdStorage for StdStorage; - - function test_NonBeaconChainETHWithdraw() public { - assertEq(capsule.nonBeaconChainETHBalance(), 0); - address sender = vm.addr(1); - vm.startPrank(sender); - vm.deal(sender, 1 ether); - (bool sent,) = address(capsule).call{value: 0.5 ether}(""); - assertEq(sent, true); - assertEq(capsule.nonBeaconChainETHBalance(), 0.5 ether); - vm.stopPrank(); - - address recipient = vm.addr(2); - capsule.withdrawNonBeaconChainETHBalance(recipient, 0.2 ether); - assertEq(recipient.balance, 0.2 ether); - assertEq(capsule.nonBeaconChainETHBalance(), 0.3 ether); - - vm.expectRevert( - bytes( - "ExoCapsule.withdrawNonBeaconChainETHBalance: amountToWithdraw is greater than nonBeaconChainETHBalance" - ) - ); - capsule.withdrawNonBeaconChainETHBalance(recipient, 0.5 ether); - } - - function test_processFullWithdrawal_success() public setValidatorContainerAndTimestampForFullWithdrawal { - capsule.verifyWithdrawalProof(validatorContainer, validatorProof, withdrawalContainer, withdrawalProof); - } - - function test_processFullWithdrawal_revert_AlreadyProcessed() - public - setValidatorContainerAndTimestampForFullWithdrawal - { - capsule.verifyWithdrawalProof(validatorContainer, validatorProof, withdrawalContainer, withdrawalProof); - - vm.expectRevert( - abi.encodeWithSelector( - ExoCapsule.WithdrawalAlreadyProven.selector, - _getPubkey(validatorContainer), - withdrawalProof.withdrawalIndex - ) - ); - capsule.verifyWithdrawalProof(validatorContainer, validatorProof, withdrawalContainer, withdrawalProof); - } - - function test_processPartialWithdrawal_success() public setValidatorContainerAndTimestampForPartialWithdrawal { - capsule.verifyWithdrawalProof(validatorContainer, validatorProof, withdrawalContainer, withdrawalProof); - } - - modifier setValidatorContainerAndTimestampForFullWithdrawal() { - string memory withdrawalInfo = vm.readFile("test/foundry/test-data/full_withdrawal_proof.json"); - _setValidatorContainer(withdrawalInfo); - _setWithdrawalContainer(withdrawalInfo); - - _setTimeStamp(); - _; - } - - modifier setValidatorContainerAndTimestampForPartialWithdrawal() { - string memory withdrawalInfo = vm.readFile("test/foundry/test-data/partial_withdrawal_proof.json"); - _setValidatorContainer(withdrawalInfo); - _setWithdrawalContainer(withdrawalInfo); - - _setTimeStamp(); - _; - } - -}