From 67dbcc5a1f56e8821d66c1ba3b110d973df46622 Mon Sep 17 00:00:00 2001 From: Ratan Kaliani Date: Tue, 2 May 2023 16:42:28 -0700 Subject: [PATCH] integration: diva (#352) Creates a Diva Beacon Oracle contract that can prove: Any of the fields of a validator. The balance of a validator using the balances container in BeaconState. A deposit from a BLSPubkey using the deposits container in BeaconBlock. Note: Lodestar currently has a gindex issue for fields in the Validator container. As such, we need to calculate the gindices of Validator fields manually. GitOrigin-RevId: 26db3160591809821b4785bce105ac980c0db6bd --- external/examples/oracle/NFTAirdrop.t.sol | 4 +- .../integrations/diva/DivaBeaconOracle.sol | 200 ++++++++++ .../eigenlayer/EigenLayerBeaconOracle.sol | 50 +-- .../EigenLayerBeaconOracleProxy.sol | 2 +- .../gnosis/TelepathyValidator.t.sol | 4 +- .../libraries/BeaconOracleHelper.sol | 180 +++++++++ test/integrations/diva/DivaBeaconOracle.t.sol | 361 ++++++++++++++++++ .../diva/fixtures/diva_6250752.json | 114 ++++++ .../diva/fixtures/diva_deposit_6308974.json | 23 ++ .../diva/fixtures/diva_status_6308974.json | 90 +++++ .../EigenLayerBeaconOracleTest.t.sol | 72 ++-- .../fixtures/eigenlayer_6211232.json} | 0 .../fixtures/eigenlayer_6250752.json} | 0 13 files changed, 1021 insertions(+), 79 deletions(-) create mode 100644 external/integrations/diva/DivaBeaconOracle.sol create mode 100644 external/integrations/libraries/BeaconOracleHelper.sol create mode 100644 test/integrations/diva/DivaBeaconOracle.t.sol create mode 100644 test/integrations/diva/fixtures/diva_6250752.json create mode 100644 test/integrations/diva/fixtures/diva_deposit_6308974.json create mode 100644 test/integrations/diva/fixtures/diva_status_6308974.json rename test/integrations/{ => eigenlayer}/EigenLayerBeaconOracleTest.t.sol (72%) rename test/integrations/{fixtures/valid_6211232.json => eigenlayer/fixtures/eigenlayer_6211232.json} (100%) rename test/integrations/{fixtures/valid_6250752.json => eigenlayer/fixtures/eigenlayer_6250752.json} (100%) diff --git a/external/examples/oracle/NFTAirdrop.t.sol b/external/examples/oracle/NFTAirdrop.t.sol index da009c7..d4c23be 100644 --- a/external/examples/oracle/NFTAirdrop.t.sol +++ b/external/examples/oracle/NFTAirdrop.t.sol @@ -2,7 +2,7 @@ pragma solidity ^0.8.16; import "forge-std/Test.sol"; import "forge-std/console.sol"; -import {NFTAirdrop} from "examples/oracle/NFTAirdrop.sol"; +import {NFTAirdrop} from "external/examples/oracle/NFTAirdrop.sol"; import {MockTelepathy} from "src/amb/mocks/MockTelepathy.sol"; import {TelepathyOracle, RequestData} from "src/oracle/TelepathyOracle.sol"; import {TelepathyOracleFulfiller} from "src/oracle/TelepathyOracleFulfiller.sol"; @@ -175,4 +175,4 @@ contract NFTAirdropTest is Test { vm.prank(address(oracle)); nftAirdrop.handleOracleResponse(1, responseData2, responseSuccess2); } -} +} \ No newline at end of file diff --git a/external/integrations/diva/DivaBeaconOracle.sol b/external/integrations/diva/DivaBeaconOracle.sol new file mode 100644 index 0000000..bb5d5c1 --- /dev/null +++ b/external/integrations/diva/DivaBeaconOracle.sol @@ -0,0 +1,200 @@ +pragma solidity 0.8.16; + +import {ILightClient} from "src/lightclient/interfaces/ILightClient.sol"; +import {SSZ} from "src/libraries/SimpleSerialize.sol"; +import {BeaconOracleHelper} from "external/integrations/libraries/BeaconOracleHelper.sol"; + +contract DivaBeaconOracle { + ILightClient lightclient; + + event BeaconOracleUpdate(uint256 validatorIndex); + + error InvalidBeaconStateRootProof(); + error InvalidValidatorProof(uint256 validatorIndex); + error InvalidDepositProof(bytes32 validatorPubkeyHash); + error InvalidBalanceProof(uint256 validatorIndex); + error InvalidLightClientAddress(); + error InvalidValidatorFieldProof(BeaconOracleHelper.ValidatorField field, uint256 validatorIndex); + + constructor(address _lightClient) { + if (_lightClient == address(0)) { + revert InvalidLightClientAddress(); + } + lightclient = ILightClient(_lightClient); + } + + /// @notice Mapping from SHA-256 hash of padded pubkey to the slot of the latest proved deposit + mapping(bytes32 => uint256) public depositedSlots; + /// @notice Mapping from validator index to latest proved balance + mapping(uint256 => uint256) public validatorBalances; + /// @notice Mapping from validator index to validator struct + mapping(uint256 => BeaconOracleHelper.Validator) public validatorState; + + /// @notice Prove pubkey against deposit in beacon block + function proveDeposit( + uint256 _slot, + bytes32 _pubkeyHash, + // Index of deposit in deposit tree (MAX_LENGTH = 16) + uint256 _depositIndex, + bytes32[] calldata _depositedPubkeyProof + ) external { + bytes32 blockHeaderRoot = ILightClient(lightclient).headers(_slot); + + if ( + !BeaconOracleHelper._verifyValidatorDeposited( + _depositIndex, _pubkeyHash, _depositedPubkeyProof, blockHeaderRoot + ) + ) { + revert InvalidDepositProof(_pubkeyHash); + } + + depositedSlots[_pubkeyHash] = _slot; + } + + /// @notice Prove pubkey, withdrawal credentials + function proveValidatorField( + BeaconOracleHelper.BeaconStateRootProofInfo calldata _beaconStateRootProofInfo, + BeaconOracleHelper.ValidatorProofInfo calldata _validatorProofInfo, + // Prove fields that are bytes32 + bytes32 _validatorFieldLeaf, + bytes32[] calldata _validatorFieldProof, + BeaconOracleHelper.ValidatorField _field + ) external { + _verifyValidator(_beaconStateRootProofInfo, _validatorProofInfo); + if ( + !BeaconOracleHelper._verifyValidatorField( + _validatorProofInfo.validatorRoot, + _validatorFieldLeaf, + _validatorFieldProof, + _field) + ) { + revert InvalidValidatorFieldProof(_field, _validatorProofInfo.validatorIndex); + } + BeaconOracleHelper.Validator storage validator = validatorState[_validatorProofInfo.validatorIndex]; + if (_field == BeaconOracleHelper.ValidatorField.Pubkey) { + validator.pubkey = _validatorFieldLeaf; + } else if (_field == BeaconOracleHelper.ValidatorField.WithdrawalCredentials) { + validator.withdrawalCredentials = _validatorFieldLeaf; + } + } + + /// @notice Prove slashed & status epochs + function proveValidatorField( + BeaconOracleHelper.BeaconStateRootProofInfo calldata _beaconStateRootProofInfo, + BeaconOracleHelper.ValidatorProofInfo calldata _validatorProofInfo, + // Prove fields that are uint256 or bool + uint256 _validatorFieldLeaf, + bytes32[] calldata _validatorFieldProof, + BeaconOracleHelper.ValidatorField _field + ) external { + _verifyValidator(_beaconStateRootProofInfo, _validatorProofInfo); + if ( + !BeaconOracleHelper._verifyValidatorField( + _validatorProofInfo.validatorRoot, + SSZ.toLittleEndian(_validatorFieldLeaf), + _validatorFieldProof, + _field) + ) { + revert InvalidValidatorFieldProof(_field, _validatorProofInfo.validatorIndex); + } + + BeaconOracleHelper.Validator memory validator = validatorState[_validatorProofInfo.validatorIndex]; + if (_field == BeaconOracleHelper.ValidatorField.Slashed) { + validator.slashed = _validatorFieldLeaf == 1; + } else if (_field == BeaconOracleHelper.ValidatorField.ActivationEligibilityEpoch) { + validator.activationEligibilityEpoch = _validatorFieldLeaf; + } else if (_field == BeaconOracleHelper.ValidatorField.ActivationEpoch) { + validator.activationEpoch = _validatorFieldLeaf; + } else if (_field == BeaconOracleHelper.ValidatorField.ExitEpoch) { + validator.exitEpoch = _validatorFieldLeaf; + } else if (_field == BeaconOracleHelper.ValidatorField.WithdrawableEpoch) { + validator.withdrawableEpoch = _validatorFieldLeaf; + } + validatorState[_validatorProofInfo.validatorIndex] = validator; + } + + /// @notice Proves the balance of a validator against balances array + function proveValidatorBalance( + BeaconOracleHelper.BeaconStateRootProofInfo calldata _beaconStateRootProofInfo, + BeaconOracleHelper.ValidatorProofInfo calldata _validatorProofInfo, + // Combined balances of 4 validators packed into same gindex + bytes32 _combinedBalance, + bytes32[] calldata _balanceProof + ) external { + _verifyValidator(_beaconStateRootProofInfo, _validatorProofInfo); + if ( + !BeaconOracleHelper._verifyValidatorBalance( + _balanceProof, + _validatorProofInfo.validatorIndex, + _combinedBalance, + _beaconStateRootProofInfo.beaconStateRoot + ) + ) { + revert InvalidBalanceProof(_validatorProofInfo.validatorIndex); + } + + validatorBalances[_validatorProofInfo.validatorIndex] = BeaconOracleHelper + ._getBalanceFromCombinedBalance(_validatorProofInfo.validatorIndex, _combinedBalance); + + emit BeaconOracleUpdate(_validatorProofInfo.validatorIndex); + } + + function getWithdrawalCredentials(uint256 _validatorIndex) external view returns (bytes32) { + return validatorState[_validatorIndex].withdrawalCredentials; + } + + function getPubkey(uint256 _validatorIndex) external view returns (bytes32) { + return validatorState[_validatorIndex].pubkey; + } + + function getSlashed(uint256 _validatorIndex) external view returns (bool) { + return validatorState[_validatorIndex].slashed; + } + + function getActivationEligibilityEpoch(uint256 _validatorIndex) + external + view + returns (uint256) + { + return validatorState[_validatorIndex].activationEligibilityEpoch; + } + + function getActivationEpoch(uint256 _validatorIndex) external view returns (uint256) { + return validatorState[_validatorIndex].activationEpoch; + } + + function getExitEpoch(uint256 _validatorIndex) external view returns (uint256) { + return validatorState[_validatorIndex].exitEpoch; + } + + function getWithdrawableEpoch(uint256 _validatorIndex) external view returns (uint256) { + return validatorState[_validatorIndex].withdrawableEpoch; + } + + function getBalance(uint256 _validatorIndex) external view returns (uint256) { + return validatorBalances[_validatorIndex]; + } + + function getDepositStatus(bytes32 _validatorPubkeyHash) external view returns (uint256) { + return depositedSlots[_validatorPubkeyHash]; + } + + function _verifyValidator( + BeaconOracleHelper.BeaconStateRootProofInfo calldata _beaconStateRootProofInfo, + BeaconOracleHelper.ValidatorProofInfo calldata _validatorProofInfo + ) internal view { + bytes32 blockHeaderRoot = ILightClient(lightclient).headers(_beaconStateRootProofInfo.slot); + + if (!BeaconOracleHelper._verifyBeaconStateRoot(_beaconStateRootProofInfo, blockHeaderRoot)) { + revert InvalidBeaconStateRootProof(); + } + + if ( + !BeaconOracleHelper._verifyValidatorRoot( + _validatorProofInfo, _beaconStateRootProofInfo.beaconStateRoot + ) + ) { + revert InvalidValidatorProof(_validatorProofInfo.validatorIndex); + } + } +} \ No newline at end of file diff --git a/external/integrations/eigenlayer/EigenLayerBeaconOracle.sol b/external/integrations/eigenlayer/EigenLayerBeaconOracle.sol index b4021d5..dc512d4 100644 --- a/external/integrations/eigenlayer/EigenLayerBeaconOracle.sol +++ b/external/integrations/eigenlayer/EigenLayerBeaconOracle.sol @@ -3,15 +3,13 @@ pragma solidity 0.8.16; import {ILightClient} from "src/lightclient/interfaces/ILightClient.sol"; import {SSZ} from "src/libraries/SimpleSerialize.sol"; import {ILightClientUpdater} from "external/integrations/eigenlayer/ILightClientUpdater.sol"; +import {BeaconOracleHelper} from "external/integrations/libraries/BeaconOracleHelper.sol"; import {EigenLayerBeaconOracleStorage} from "external/integrations/eigenlayer/EigenLayerBeaconOracleStorage.sol"; import {ReentrancyGuardUpgradeable} from "openzeppelin-contracts-upgradeable/security/ReentrancyGuardUpgradeable.sol"; contract EigenLayerBeaconOracle is ILightClientUpdater, EigenLayerBeaconOracleStorage { - uint256 internal constant EXECUTION_PAYLOAD_BLOCK_NUMBER_INDEX = 3222; - uint256 internal constant BEACON_STATE_ROOT_INDEX = 11; - event BeaconStateOracleUpdate(uint256 slot, uint256 blockNumber, bytes32 stateRoot); error InvalidBlockNumberProof(); @@ -31,57 +29,35 @@ contract EigenLayerBeaconOracle is ILightClientUpdater, EigenLayerBeaconOracleSt } function fulfillRequest( - uint256 _slot, + BeaconOracleHelper.BeaconStateRootProofInfo calldata _beaconStateRootProofInfo, uint256 _blockNumber, - bytes32[] calldata _blockNumberProof, - bytes32 _beaconStateRoot, - bytes32[] calldata _beaconStateRootProof + bytes32[] calldata _blockNumberProof ) external onlyWhitelistedUpdater { - if (_slot <= head) { + if (_beaconStateRootProofInfo.slot <= head) { revert SlotNumberTooLow(); } - bytes32 blockHeaderRoot = ILightClient(lightclient).headers(_slot); + bytes32 blockHeaderRoot = ILightClient(lightclient).headers(_beaconStateRootProofInfo.slot); // Verify block number against block header root - if (!verifyBlockNumber(_blockNumber, _blockNumberProof, blockHeaderRoot)) { + if (!BeaconOracleHelper._verifyBlockNumber(_blockNumber, _blockNumberProof, blockHeaderRoot)) + { revert InvalidBlockNumberProof(); } // Verify beacon state root against block header root - if (!verifyBeaconStateRoot(_beaconStateRoot, _beaconStateRootProof, blockHeaderRoot)) { + if (!BeaconOracleHelper._verifyBeaconStateRoot(_beaconStateRootProofInfo, blockHeaderRoot)) { revert InvalidBeaconStateRootProof(); } // Store the header root - blockNumberToStateRoot[_blockNumber] = _beaconStateRoot; + blockNumberToStateRoot[_blockNumber] = _beaconStateRootProofInfo.beaconStateRoot; // Require that the slot number is greater than the previous slot number - head = _slot; - - emit BeaconStateOracleUpdate(_slot, _blockNumber, _beaconStateRoot); - } - - function verifyBlockNumber( - uint256 _blockNumber, - bytes32[] memory _blockNumberProof, - bytes32 _blockHeaderRoot - ) internal pure returns (bool) { - return SSZ.isValidMerkleBranch( - SSZ.toLittleEndian(_blockNumber), - EXECUTION_PAYLOAD_BLOCK_NUMBER_INDEX, - _blockNumberProof, - _blockHeaderRoot - ); - } + head = _beaconStateRootProofInfo.slot; - function verifyBeaconStateRoot( - bytes32 _beaconStateRoot, - bytes32[] memory _beaconStateRootProof, - bytes32 _blockHeaderRoot - ) internal pure returns (bool) { - return SSZ.isValidMerkleBranch( - _beaconStateRoot, BEACON_STATE_ROOT_INDEX, _beaconStateRootProof, _blockHeaderRoot + emit BeaconStateOracleUpdate( + _beaconStateRootProofInfo.slot, _blockNumber, _beaconStateRootProofInfo.beaconStateRoot ); } -} +} \ No newline at end of file diff --git a/external/integrations/eigenlayer/EigenLayerBeaconOracleProxy.sol b/external/integrations/eigenlayer/EigenLayerBeaconOracleProxy.sol index 8db0843..363b0df 100644 --- a/external/integrations/eigenlayer/EigenLayerBeaconOracleProxy.sol +++ b/external/integrations/eigenlayer/EigenLayerBeaconOracleProxy.sol @@ -66,4 +66,4 @@ contract EigenLayerBeaconOracleProxy is /// @notice Authorizes an upgrade for the implementation contract. function _authorizeUpgrade(address newImplementation) internal override onlyTimelock {} -} +} \ No newline at end of file diff --git a/external/integrations/gnosis/TelepathyValidator.t.sol b/external/integrations/gnosis/TelepathyValidator.t.sol index f4aff6f..5718464 100644 --- a/external/integrations/gnosis/TelepathyValidator.t.sol +++ b/external/integrations/gnosis/TelepathyValidator.t.sol @@ -6,7 +6,7 @@ import {MockTelepathy} from "src/amb/mocks/MockTelepathy.sol"; import {TelepathyPubSub} from "src/pubsub/TelepathyPubSub.sol"; import {Subscription} from "src/pubsub/interfaces/IPubSub.sol"; import {TelepathyHandler} from "src/amb/interfaces/TelepathyHandler.sol"; -import {TelepathyValidator} from "examples/pubsub/gnosis/TelepathyValidator.sol"; +import {TelepathyValidator} from "external/integrations/gnosis/TelepathyValidator.sol"; import {UUPSProxy} from "src/libraries/Proxy.sol"; interface IForeignAMB {} @@ -165,4 +165,4 @@ contract TelepathyValidatorTest is Test { telepathyValidator.toggleExecuteAffirmations(); assertEq(telepathyValidator.executeAffirmationsEnabled(), false); } -} +} \ No newline at end of file diff --git a/external/integrations/libraries/BeaconOracleHelper.sol b/external/integrations/libraries/BeaconOracleHelper.sol new file mode 100644 index 0000000..69c44ec --- /dev/null +++ b/external/integrations/libraries/BeaconOracleHelper.sol @@ -0,0 +1,180 @@ +pragma solidity 0.8.16; + +import {SSZ} from "src/libraries/SimpleSerialize.sol"; + +library BeaconOracleHelper { + /// @notice Beacon block constants + uint256 internal constant BEACON_STATE_ROOT_INDEX = 11; + uint256 internal constant BASE_DEPOSIT_INDEX = 6336; + uint256 internal constant EXECUTION_PAYLOAD_BLOCK_NUMBER_INDEX = 3222; + + /// @notice Validator proof constants + uint256 internal constant BASE_VALIDATOR_INDEX = 94557999988736; + uint256 internal constant VALIDATOR_FIELDS_LENGTH = 8; + uint256 internal constant PUBKEY_INDEX = 0; + uint256 internal constant WITHDRAWAL_CREDENTIALS_INDEX = 1; + uint256 internal constant EFFECTIVE_BALANCE_INDEX = 2; + uint256 internal constant SLASHED_INDEX = 3; + uint256 internal constant ACTIVATION_ELIGIBILITY_EPOCH_INDEX = 4; + uint256 internal constant ACTIVATION_EPOCH_INDEX = 5; + uint256 internal constant EXIT_EPOCH_INDEX = 6; + uint256 internal constant WITHDRAWABLE_EPOCH_INDEX = 7; + + /// @notice Balance constants + uint256 internal constant BASE_BALANCE_INDEX = 24189255811072; + + struct BeaconStateRootProofInfo { + uint256 slot; + bytes32 beaconStateRoot; + bytes32[] beaconStateRootProof; + } + + struct ValidatorProofInfo { + uint256 validatorIndex; + bytes32 validatorRoot; + bytes32[] validatorProof; + } + + struct Validator { + bytes32 pubkey; + bytes32 withdrawalCredentials; + // Not to be confused with the validator's balance (effective balance capped at 32ETH) + uint256 effectiveBalance; + bool slashed; + uint256 activationEligibilityEpoch; + uint256 activationEpoch; + uint256 exitEpoch; + uint256 withdrawableEpoch; + } + + enum ValidatorField { + Pubkey, + WithdrawalCredentials, + // Not to be confused with the validator's balance (effective balance capped at 32ETH) + EffectiveBalance, + Slashed, + ActivationEligibilityEpoch, + ActivationEpoch, + ExitEpoch, + WithdrawableEpoch + } + + function _verifyBlockNumber( + uint256 _blockNumber, + bytes32[] memory _blockNumberProof, + bytes32 _blockHeaderRoot + ) internal pure returns (bool) { + return SSZ.isValidMerkleBranch( + SSZ.toLittleEndian(_blockNumber), + EXECUTION_PAYLOAD_BLOCK_NUMBER_INDEX, + _blockNumberProof, + _blockHeaderRoot + ); + } + + function _verifyBeaconStateRoot(BeaconStateRootProofInfo calldata _beaconStateRootProofInfo, bytes32 _blockHeaderRoot) + internal + pure + returns (bool) + { + return SSZ.isValidMerkleBranch( + _beaconStateRootProofInfo.beaconStateRoot, + BEACON_STATE_ROOT_INDEX, + _beaconStateRootProofInfo.beaconStateRootProof, + _blockHeaderRoot + ); + } + + function _verifyValidatorRoot( + ValidatorProofInfo calldata _validatorProofInfo, + bytes32 _beaconStateRoot + ) internal pure returns (bool) { + return SSZ.isValidMerkleBranch( + _validatorProofInfo.validatorRoot, + BASE_VALIDATOR_INDEX + _validatorProofInfo.validatorIndex, + _validatorProofInfo.validatorProof, + _beaconStateRoot + ); + } + + /// @notice Proves the gindex for the specified pubkey at _depositIndex + function _verifyValidatorDeposited( + uint256 _depositIndex, + bytes32 _pubkeyHash, + bytes32[] memory _depositedPubkeyProof, + bytes32 _blockHeaderRoot + ) internal pure returns (bool) { + return SSZ.isValidMerkleBranch( + _pubkeyHash, + ((((BASE_DEPOSIT_INDEX + _depositIndex) * 2) + 1) * 4) + 0, + _depositedPubkeyProof, + _blockHeaderRoot + ); + } + + function _verifyValidatorBalance( + bytes32[] memory _balanceProof, + uint256 _validatorIndex, + bytes32 _combinedBalance, + bytes32 _beaconStateRoot + ) internal pure returns (bool) { + return SSZ.isValidMerkleBranch( + _combinedBalance, BASE_BALANCE_INDEX + _validatorIndex, _balanceProof, _beaconStateRoot + ); + } + + /// @notice Proves a validator field against the validator root + function _verifyValidatorField( + bytes32 _validatorRoot, + bytes32 _leaf, + bytes32[] memory _validatorFieldProof, + ValidatorField _field + ) internal pure returns (bool) { + return SSZ.isValidMerkleBranch( + _leaf, + _getFieldGIndex(_field), + _validatorFieldProof, + _validatorRoot + ); + } + + /// @notice Validator balances are stored in an array of 4 64-bit integers, we extract the validator's balance + function _getBalanceFromCombinedBalance(uint256 _validatorIndex, bytes32 _combinedBalance) + internal + pure + returns (uint256) + { + uint256 modBalance = _validatorIndex % 4; + + bytes32 mask = bytes32(0xFFFFFFFFFFFFFFFF << ((3 - modBalance) * 64)); + bytes32 leBytes = (_combinedBalance & mask) << (modBalance * 64); + uint256 result = 0; + for (uint256 i = 0; i < leBytes.length; i++) { + result += uint256(uint8(leBytes[i])) * 2 ** (8 * i); + } + return result; + } + + /// @notice Returns the gindex for a validator field + function _getFieldGIndex(ValidatorField _field) + internal + pure + returns (uint256) + { + if (_field == ValidatorField.Pubkey) { + return VALIDATOR_FIELDS_LENGTH + PUBKEY_INDEX; + } else if (_field == ValidatorField.WithdrawalCredentials) { + return VALIDATOR_FIELDS_LENGTH + WITHDRAWAL_CREDENTIALS_INDEX; + } else if (_field == ValidatorField.Slashed) { + return VALIDATOR_FIELDS_LENGTH + SLASHED_INDEX; + } else if (_field == ValidatorField.ActivationEligibilityEpoch) { + return VALIDATOR_FIELDS_LENGTH + ACTIVATION_ELIGIBILITY_EPOCH_INDEX; + } else if (_field == ValidatorField.ActivationEpoch) { + return VALIDATOR_FIELDS_LENGTH + ACTIVATION_EPOCH_INDEX; + } else if (_field == ValidatorField.ExitEpoch) { + return VALIDATOR_FIELDS_LENGTH + EXIT_EPOCH_INDEX; + } else { + return VALIDATOR_FIELDS_LENGTH + WITHDRAWABLE_EPOCH_INDEX; + } + } +} \ No newline at end of file diff --git a/test/integrations/diva/DivaBeaconOracle.t.sol b/test/integrations/diva/DivaBeaconOracle.t.sol new file mode 100644 index 0000000..f390f58 --- /dev/null +++ b/test/integrations/diva/DivaBeaconOracle.t.sol @@ -0,0 +1,361 @@ +pragma solidity 0.8.16; + +import "forge-std/Vm.sol"; +import "forge-std/console.sol"; +import "forge-std/Test.sol"; + +import {SSZ} from "src/libraries/SimpleSerialize.sol"; +import {LightClient, LightClientStep, LightClientRotate} from "src/lightclient/LightClient.sol"; +import {LightClientFixture} from "test/lightclient/LightClientFixture.sol"; +import {Strings} from "openzeppelin-contracts/utils/Strings.sol"; +import {DivaBeaconOracle} from "external/integrations/diva/DivaBeaconOracle.sol"; +import {BeaconOracleHelper} from "external/integrations/libraries/BeaconOracleHelper.sol"; +import {UUPSProxy} from "src/libraries/Proxy.sol"; + +import {LightClientMock} from "src/lightclient/LightClientMock.sol"; + +struct DepositFixture { + uint256 depositIndex; + bytes32 headerRoot; + bytes32 pubkeyHash; + bytes32[] pubkeyProof; + uint256 slot; +} + +struct StatusFixture { + uint256 activationEpoch; + bytes32[] activationProof; + bytes32 beaconStateRoot; + bytes32[] beaconStateRootProof; + bytes32 blockHeaderRoot; + uint256 exitEpoch; + bytes32[] exitProof; + uint256 pendingEpoch; + bytes32[] pendingProof; + uint256 slashed; + bytes32[] slashedProof; + uint256 slot; + uint256 validatorIndex; + bytes32[] validatorProof; + bytes32 validatorRoot; + bytes32 withdrawalCredentials; + bytes32[] withdrawalCredentialsProof; +} + +struct BeaconOracleUpdateFixture { + bytes32[] balanceProof; + bytes32 beaconStateRoot; + bytes32[] beaconStateRootProof; + bytes32 combinedBalance; + bytes32 headerRoot; + uint256 slot; + uint256 validatorBalance; + uint256 validatorIndex; + bytes32[] validatorProof; + bytes32 validatorRoot; + bytes32 withdrawalCredentials; + bytes32[] withdrawalCredentialsProof; +} + +contract TestErrors { + error InvalidBeaconStateRootProof(); + + error InvalidValidatorProof(uint256 validatorIndex); + + error InvalidDepositProof(bytes32 validatorPubkeyHash); + error InvalidBalanceProof(uint256 validatorIndex); + error InvalidValidatorFieldProof( + BeaconOracleHelper.ValidatorField field, uint256 validatorIndex + ); +} + +contract TestEvents { + event BeaconOracleUpdate(uint256 validatorIndex); +} + +contract DivaBeaconOracleTest is Test, TestEvents, TestErrors { + uint32 constant SOURCE_CHAIN_ID = 1; + uint16 constant FINALITY_THRESHOLD = 350; + + address public oracleOperator; + BeaconOracleUpdateFixture fixture; + DepositFixture depositFixture; + StatusFixture statusFixture; + DivaBeaconOracle oracle; + + function setUp() public { + // read all fixtures from entire directory + + LightClientMock lightClient = new LightClientMock(); + + oracleOperator = makeAddr("operator"); + + oracle = new DivaBeaconOracle(address(lightClient)); + + string memory root = vm.projectRoot(); + string memory filename = "diva_6250752"; + string memory path = + string.concat(root, "/test/integrations/diva/fixtures/", filename, ".json"); + string memory file = vm.readFile(path); + bytes memory parsed = vm.parseJson(file); + fixture = abi.decode(parsed, (BeaconOracleUpdateFixture)); + + filename = "diva_deposit_6308974"; + path = string.concat(root, "/test/integrations/diva/fixtures/", filename, ".json"); + file = vm.readFile(path); + parsed = vm.parseJson(file); + depositFixture = abi.decode(parsed, (DepositFixture)); + + filename = "diva_status_6308974"; + path = string.concat(root, "/test/integrations/diva/fixtures/", filename, ".json"); + file = vm.readFile(path); + parsed = vm.parseJson(file); + statusFixture = abi.decode(parsed, (StatusFixture)); + + lightClient.setHeader(fixture.slot, fixture.headerRoot); + lightClient.setHeader(depositFixture.slot, depositFixture.headerRoot); + } + + function test_ProveDeposit() public { + oracle.proveDeposit( + depositFixture.slot, + depositFixture.pubkeyHash, + depositFixture.depositIndex, + depositFixture.pubkeyProof + ); + + uint256 slotDeposited = oracle.getDepositStatus(depositFixture.pubkeyHash); + assertEq(slotDeposited, depositFixture.slot); + } + + function test_ProveValidatorField() public { + BeaconOracleHelper.BeaconStateRootProofInfo memory beaconStateRootProofInfo = + BeaconOracleHelper.BeaconStateRootProofInfo({ + slot: statusFixture.slot, + beaconStateRoot: statusFixture.beaconStateRoot, + beaconStateRootProof: statusFixture.beaconStateRootProof + }); + + BeaconOracleHelper.ValidatorProofInfo memory validatorProofInfo = BeaconOracleHelper + .ValidatorProofInfo({ + validatorIndex: statusFixture.validatorIndex, + validatorRoot: statusFixture.validatorRoot, + validatorProof: statusFixture.validatorProof + }); + + oracle.proveValidatorField( + beaconStateRootProofInfo, + validatorProofInfo, + statusFixture.withdrawalCredentials, + statusFixture.withdrawalCredentialsProof, + BeaconOracleHelper.ValidatorField.WithdrawalCredentials + ); + + bytes32 withdrawalCredentials = + oracle.getWithdrawalCredentials(validatorProofInfo.validatorIndex); + + assertEq(withdrawalCredentials, statusFixture.withdrawalCredentials); + + oracle.proveValidatorField( + beaconStateRootProofInfo, + validatorProofInfo, + statusFixture.pendingEpoch, + statusFixture.pendingProof, + BeaconOracleHelper.ValidatorField.ActivationEligibilityEpoch + ); + uint256 pendingEpoch = oracle.getActivationEligibilityEpoch(statusFixture.validatorIndex); + assertEq(pendingEpoch, statusFixture.pendingEpoch); + + oracle.proveValidatorField( + beaconStateRootProofInfo, + validatorProofInfo, + statusFixture.activationEpoch, + statusFixture.activationProof, + BeaconOracleHelper.ValidatorField.ActivationEpoch + ); + uint256 activationEpoch = oracle.getActivationEpoch(statusFixture.validatorIndex); + assertEq(activationEpoch, statusFixture.activationEpoch); + + oracle.proveValidatorField( + beaconStateRootProofInfo, + validatorProofInfo, + statusFixture.exitEpoch, + statusFixture.exitProof, + BeaconOracleHelper.ValidatorField.ExitEpoch + ); + uint256 exitEpoch = oracle.getExitEpoch(statusFixture.validatorIndex); + assertEq(exitEpoch, statusFixture.exitEpoch); + + oracle.proveValidatorField( + beaconStateRootProofInfo, + validatorProofInfo, + statusFixture.slashed, + statusFixture.slashedProof, + BeaconOracleHelper.ValidatorField.Slashed + ); + bool slashed = oracle.getSlashed(statusFixture.validatorIndex); + assertEq(slashed, statusFixture.slashed == 1); + } + + function test_ProveValidatorBalance() public { + BeaconOracleHelper.BeaconStateRootProofInfo memory beaconStateRootProofInfo = + BeaconOracleHelper.BeaconStateRootProofInfo({ + slot: fixture.slot, + beaconStateRoot: fixture.beaconStateRoot, + beaconStateRootProof: fixture.beaconStateRootProof + }); + + BeaconOracleHelper.ValidatorProofInfo memory validatorProofInfo = BeaconOracleHelper + .ValidatorProofInfo({ + validatorIndex: fixture.validatorIndex, + validatorRoot: fixture.validatorRoot, + validatorProof: fixture.validatorProof + }); + + oracle.proveValidatorBalance( + beaconStateRootProofInfo, + validatorProofInfo, + fixture.combinedBalance, + fixture.balanceProof + ); + + uint256 balance = oracle.getBalance(validatorProofInfo.validatorIndex); + assertEq(balance, fixture.validatorBalance); + } + + // TESTING REVERTS + function test_RevertInProveDeposit() public { + vm.expectRevert( + abi.encodeWithSelector(InvalidDepositProof.selector, depositFixture.pubkeyHash) + ); + oracle.proveDeposit( + depositFixture.slot, + depositFixture.pubkeyHash, + // Incorrect deposit index + 16, + depositFixture.pubkeyProof + ); + } + + function test_RevertInProveValidatorBalance() public { + BeaconOracleHelper.BeaconStateRootProofInfo memory beaconStateRootProofInfo = + BeaconOracleHelper.BeaconStateRootProofInfo({ + slot: fixture.slot, + beaconStateRoot: fixture.beaconStateRoot, + beaconStateRootProof: fixture.beaconStateRootProof + }); + + BeaconOracleHelper.ValidatorProofInfo memory validatorProofInfo = BeaconOracleHelper + .ValidatorProofInfo({ + validatorIndex: fixture.validatorIndex, + validatorRoot: fixture.validatorRoot, + validatorProof: fixture.validatorProof + }); + + vm.expectRevert( + abi.encodeWithSelector(InvalidBalanceProof.selector, validatorProofInfo.validatorIndex) + ); + oracle.proveValidatorBalance( + // Incorrect combined balance + beaconStateRootProofInfo, + validatorProofInfo, + "0x0", + fixture.balanceProof + ); + } + + function test_RevertsInProveValidatorStatus() public { + BeaconOracleHelper.BeaconStateRootProofInfo memory beaconStateRootProofInfo = + BeaconOracleHelper.BeaconStateRootProofInfo({ + slot: statusFixture.slot, + beaconStateRoot: statusFixture.beaconStateRoot, + beaconStateRootProof: statusFixture.beaconStateRootProof + }); + + BeaconOracleHelper.ValidatorProofInfo memory validatorProofInfo = BeaconOracleHelper + .ValidatorProofInfo({ + validatorIndex: statusFixture.validatorIndex, + validatorRoot: statusFixture.validatorRoot, + validatorProof: statusFixture.validatorProof + }); + + vm.expectRevert( + abi.encodeWithSelector( + InvalidValidatorFieldProof.selector, + BeaconOracleHelper.ValidatorField.WithdrawalCredentials, + validatorProofInfo.validatorIndex + ) + ); + oracle.proveValidatorField( + beaconStateRootProofInfo, + validatorProofInfo, + // Incorrect withdrawal credentials + "0x0", + statusFixture.withdrawalCredentialsProof, + BeaconOracleHelper.ValidatorField.WithdrawalCredentials + ); + + vm.expectRevert( + abi.encodeWithSelector( + InvalidValidatorFieldProof.selector, + BeaconOracleHelper.ValidatorField.ActivationEligibilityEpoch, + validatorProofInfo.validatorIndex + ) + ); + oracle.proveValidatorField( + beaconStateRootProofInfo, + validatorProofInfo, + // Incorrect leaf + uint256(0), + statusFixture.pendingProof, + BeaconOracleHelper.ValidatorField.ActivationEligibilityEpoch + ); + + vm.expectRevert( + abi.encodeWithSelector( + InvalidValidatorFieldProof.selector, + BeaconOracleHelper.ValidatorField.ActivationEpoch, + validatorProofInfo.validatorIndex + ) + ); + oracle.proveValidatorField( + beaconStateRootProofInfo, + validatorProofInfo, + // Incorrect leaf + uint256(0), + statusFixture.activationProof, + BeaconOracleHelper.ValidatorField.ActivationEpoch + ); + + vm.expectRevert( + abi.encodeWithSelector( + InvalidValidatorFieldProof.selector, + BeaconOracleHelper.ValidatorField.ExitEpoch, + validatorProofInfo.validatorIndex + ) + ); + oracle.proveValidatorField( + beaconStateRootProofInfo, + validatorProofInfo, + // Incorrect leaf + uint256(0), + statusFixture.exitProof, + BeaconOracleHelper.ValidatorField.ExitEpoch + ); + + vm.expectRevert( + abi.encodeWithSelector( + InvalidValidatorFieldProof.selector, + BeaconOracleHelper.ValidatorField.Slashed, + validatorProofInfo.validatorIndex + ) + ); + oracle.proveValidatorField( + beaconStateRootProofInfo, + validatorProofInfo, // Validator IS slashed + uint256(0), + statusFixture.slashedProof, + BeaconOracleHelper.ValidatorField.Slashed + ); + } +} diff --git a/test/integrations/diva/fixtures/diva_6250752.json b/test/integrations/diva/fixtures/diva_6250752.json new file mode 100644 index 0000000..e0a95d8 --- /dev/null +++ b/test/integrations/diva/fixtures/diva_6250752.json @@ -0,0 +1,114 @@ +{ + "balanceProof": [ + "0x6926de7307000000b825c47e08000000007a9e8908000000dabde36f08000000", + "0x53f010d9db161cd524313d66ca0d09ded8fb933abbc0b21bbe0a7b0dd5b8bf61", + "0xa53a373194eed0c81f4cd57e1268c8e435bcf228a765d597a0dd7ffe5192acf0", + "0xb93b727323a5a7fe01d24555f5d8ac33c24b90f0483f211e26963263c9a5e4dc", + "0x2a90ac2feebb9abf0496888b281ad7be8b7624169611bd20d1e9a51c8db4be0f", + "0xcc05034f1d99452b487131582e5703faa8e702454b18b987c6acefb64f7de0db", + "0x011fa9dbb2ad5f1d8616d533d2f91a989590f9a36a2e8280cdc07975ee67394e", + "0x39101b28cb4599a0e14c0cb501c3073bdf505758ea67a70982d8a55f3f832b32", + "0xe5000f66c3696c91f3d0c08047c6f700d269532b27be361307eeb8ba1606776a", + "0x0458405a08a935fb8b32a4e89edb01ab4124e8d43ca4ce2ba32b1fbb00709bb6", + "0x83d2ebeffd8aa6f319d882cb2151e1aa767cab2f6a97e961acbb5eeb1d8f8f62", + "0x8521b6362c8b0c67ac42147ca3ecb6f3941406e6aee5a3d93666881b4aacc24e", + "0x672053ca49e0ed29bca02230a11c70a69e942c1acdddbddbc0a3ca61fd7cc1a7", + "0x02b901879f9fe85b22cf369369df9dc9829e9e332faa3c1b552101ac01e879b6", + "0x5619f4bd427901e545ebb1ea0490bfc604a28c95d1b7771782a86a7d975e03ff", + "0xbf035e86ecf4e41cfc43a696661ac0dfbd715cff52c851a66d803daab2c47eb7", + "0x81a947f516b9b15a7c353e7b8b96b7dbfc62ffcb5e7d655f2ae6b265f73917ba", + "0x5671b6a591735411bead659b5e6ef7f7e87187bf2676845f20fae6fce8adaac0", + "0x95eec8b2e541cad4e91de38385f2e046619f54496c2382cb6cacd5b98c26f5a4", + "0xf893e908917775b62bff23294dbbe3a1cd8e6cc1c35b4801887b646a6f81f17f", + "0xcddba7b592e3133393c16194fac7431abf2f5485ed711db282183c819e08ebaa", + "0x8a8d7fe3af8caa085a7639a832001457dfb9128a8061142ad0335629ff23ff9c", + "0xfeb3c337d7a51a6fbf00b9e34c52e1c9195c969bd4e7a0bfd51d5c5bed9c1167", + "0xe71f0aa83cc32edfbefa9f4d3e0174ca85182eec9f3a09f6a6c0df6377a510d7", + "0x31206fa80a50bb6abe29085058f16212212a60eec8f049fecb92d8c8e0a84bc0", + "0x21352bfecbeddde993839f614c3dac0a3ee37543f9b412b16199dc158e23b544", + "0x619e312724bb6d7c3153ed9de791d764a366b389af13c58bf8a8d90481a46765", + "0x7cdd2986268250628d0c10e385c58c6191e6fbe05191bcc04f133f2cea72c1c4", + "0x848930bd7ba8cac54661072113fb278869e07bb8587f91392933374d017bcbe1", + "0x8869ff2c22b28cc10510d9853292803328be4fb0e80495e8bb8d271f5b889636", + "0xb5fe28e79f1b850f8658246ce9b6a1e7b49fc06db7143e8fe0b4f2b0c5523a5c", + "0x985e929f70af28d0bdd1a90a808f977f597c7c778c489e98d3bd8910d31ac0f7", + "0xc6f67e02e6e4e1bdefb994c6098953f34636ba2b6ca20a4721d2b26a886722ff", + "0x1c9a7e5ff1cf48b4ad1582d3f4e4a1004f3b20d8c5a2b71387a4254ad933ebc5", + "0x2f075ae229646b6f6aed19a5e372cf295081401eb893ff599b3f9acc0c0d3e7d", + "0x328921deb59612076801e8cd61592107b5c67c79b846595cc6320c395b46362c", + "0xbfb909fdb236ad2411b4e4883810a074b840464689986c3f8a8091827e17c327", + "0x55d8fb3687ba3ba49f342c77f5a1f89bec83d811446e1a467139213d640b6a74", + "0xa4e0080000000000000000000000000000000000000000000000000000000000", + "0xfcef8c1615e15fb4544a05f04d5ae6fee030a9584bbb8f0cd4734246288b6618", + "0xbae3b7168b9e902a4b07e690cb405e1a6beccec0b90f4af9b1b975ec22894d25", + "0x1a680e24ad0d1051d1046946ce69cf6ba34d999a7bd9d4c413c37b7b1061dead", + "0x2c018d680ba57153b0840755be06713923a353639911eb4e208045fb84c7b279", + "0x5ff848fbbacb155b456c1cd4cedc8abebc2feb7c5b7d44a2d3e1a9df82963484" + ], + "beaconStateRoot": "0x85dba4ad816adaede3fc4747743a27962d864b3dcb384796ca61380356ccea8d", + "beaconStateRootProof": [ + "0x05b9d8071c8ef680565c0b816bc812ec7bb5f6f69077b0851cf9062729c6a187", + "0x99f1e326d3c33fbe8edbad00a217a349e2732b4e80facbbfb5743d47c0e095ae", + "0x15f66e9071eaf727cd17c395c479d10d86bfd014e049e8f826d5368799115652" + ], + "combinedBalance": "0x9b11de73070000004825de73070000006147de7307000000f0fedd7307000000", + "headerRoot": "0x289d7c5c0b00bbdec82351c0f98f445ccfd36edeac607f3653bd2b9afc8d54df", + "slot": 6250752, + "validatorBalance": 32008704411, + "validatorIndex": 0, + "validatorProof": [ + "0x3ac0c6d7ace25c3594164196734b3573a56bc13af66633d85d21d6a23c81b7ee", + "0xe7fd6bf696c14bb217a06714cc32383e94eb00571823c6fd930972b4419bdba9", + "0xe13cecc8ade6560cc1331b2fde50ba8e6fded06a58f3c663ddf6ad99b4cdc3f6", + "0xb62392a4ff10941b060ba484e9278421d22783ac33a07868de0bdf46c140a3ce", + "0x8854619c6e80d4a73c9752ecb11b87aad66ebb314d819a5f678a68b368dfe3b1", + "0x4865f7c038aaaa29defacb8346140b5f80f8fef08bb68d02282b8c3acd36dde4", + "0x45b6e10f0cab5eb6301dce63bf54fadccde9aa8cecc4331c80fe22d423b5a37c", + "0x507026804186fb5faea30c7229e6b3976d4034c30f23f0edf54a23cde8a01269", + "0xb436314256a15099ee270675ee8a894139fdc085d3d562a427afce3f228d58b2", + "0xce52bff0b2d7721fe73178c803b218892fe1f53ee553983e9e405e96b04a034b", + "0x9f092d2e75ddfdeed2a5950202b88d3cbec368d618124ed77464109fa375808a", + "0x66de5055971d1ae51df85cdadeea0025216d03469f79157117611e53efca6b1f", + "0xd395d631b230aed3321ff9dadde03f5280c771fe2d03c292fa396254292ea228", + "0x2c1d94977cba454deff44d34f7950c963b700e53db174c31613d967f3538d61b", + "0xbd3bd5f59edfb87bf0eef0e7a540a3570461fddb236fc60f43ef41f942595bb1", + "0xeab642b663be18910761d10ea218d5c58a8a2becbffe3603f33bfc38404378bf", + "0x8fabe278b53d57c0da42d901c9803f7d3fb0ebc27dac126cd3db19be7d989429", + "0x92465f5a5ca98bf99b08b6faa3f548f7edcb1f48453e036c5a5fa360f28b9498", + "0x395773ad0f20b868d2ba2ad495288fc7ece51de3057d5394d4ffae63b36eb37c", + "0xf99d1f98d1d6893911ee642e4e24fcd648f829ca7b0c9985cda10581ef0e1aac", + "0xcddba7b592e3133393c16194fac7431abf2f5485ed711db282183c819e08ebaa", + "0x8a8d7fe3af8caa085a7639a832001457dfb9128a8061142ad0335629ff23ff9c", + "0xfeb3c337d7a51a6fbf00b9e34c52e1c9195c969bd4e7a0bfd51d5c5bed9c1167", + "0xe71f0aa83cc32edfbefa9f4d3e0174ca85182eec9f3a09f6a6c0df6377a510d7", + "0x31206fa80a50bb6abe29085058f16212212a60eec8f049fecb92d8c8e0a84bc0", + "0x21352bfecbeddde993839f614c3dac0a3ee37543f9b412b16199dc158e23b544", + "0x619e312724bb6d7c3153ed9de791d764a366b389af13c58bf8a8d90481a46765", + "0x7cdd2986268250628d0c10e385c58c6191e6fbe05191bcc04f133f2cea72c1c4", + "0x848930bd7ba8cac54661072113fb278869e07bb8587f91392933374d017bcbe1", + "0x8869ff2c22b28cc10510d9853292803328be4fb0e80495e8bb8d271f5b889636", + "0xb5fe28e79f1b850f8658246ce9b6a1e7b49fc06db7143e8fe0b4f2b0c5523a5c", + "0x985e929f70af28d0bdd1a90a808f977f597c7c778c489e98d3bd8910d31ac0f7", + "0xc6f67e02e6e4e1bdefb994c6098953f34636ba2b6ca20a4721d2b26a886722ff", + "0x1c9a7e5ff1cf48b4ad1582d3f4e4a1004f3b20d8c5a2b71387a4254ad933ebc5", + "0x2f075ae229646b6f6aed19a5e372cf295081401eb893ff599b3f9acc0c0d3e7d", + "0x328921deb59612076801e8cd61592107b5c67c79b846595cc6320c395b46362c", + "0xbfb909fdb236ad2411b4e4883810a074b840464689986c3f8a8091827e17c327", + "0x55d8fb3687ba3ba49f342c77f5a1f89bec83d811446e1a467139213d640b6a74", + "0xf7210d4f8e7e1039790e7bf4efa207555a10a6db1dd4b95da313aaa88b88fe76", + "0xad21b516cbc645ffe34ab5de1c8aef8cd4e7f8d2b51e8e1456adc7563cda206f", + "0xa4e0080000000000000000000000000000000000000000000000000000000000", + "0xb71a090000000000000000000000000000000000000000000000000000000000", + "0xbcd1ed9aa5ce692c8c92ad92084cb8ab013f49776e682654ada8f5ff08964ba9", + "0xe6bac2ac57b28f30db1118d1a3f324d4d2e1f0d4ae9778b6a186f399a74c6235", + "0x2c018d680ba57153b0840755be06713923a353639911eb4e208045fb84c7b279", + "0x5ff848fbbacb155b456c1cd4cedc8abebc2feb7c5b7d44a2d3e1a9df82963484" + ], + "validatorRoot": "0x2baf4065b5d6246410518c7981e5507ce82d46d87f8099df52c396c3b62b0fd5", + "withdrawalCredentials": "0x0100000000000000000000000d369bb49efa5100fd3b86a9f828c55da04d2d50", + "withdrawalCredentialsProof": [ + "0xb9c940e5baf4e3fd869acf2b380c8fd36ab34d3365daf2022bcec5a6848d7b0b", + "0x19327cb9763c96e00332bde93bdbb1032c4b796dda73e515c8c5f7ede9a419be", + "0xbcd42b1f092780448fb0131cd25a24c9d25e4b3b610774ae9aa8d3e437e811fe" + ] +} \ No newline at end of file diff --git a/test/integrations/diva/fixtures/diva_deposit_6308974.json b/test/integrations/diva/fixtures/diva_deposit_6308974.json new file mode 100644 index 0000000..16cad57 --- /dev/null +++ b/test/integrations/diva/fixtures/diva_deposit_6308974.json @@ -0,0 +1,23 @@ +{ + "depositIndex": 0, + "headerRoot": "0x8ae7501389bf5039ecae495ef97e96ea75e4cef982c58ff348fc80dcf3b8ac58", + "pubkeyHash": "0xa4e77f77152b8d18f0ddf12f4186f06e681eda3afa1639b784d8acb81f10a68d", + "pubkeyProof": [ + "0x0100000000000000000000004a93ab8b0103cc8bb2a712ef0241748ba0cad9ff", + "0xfb930008e535309e229529006273c43c93caf3c403dbe38347979a3480c5913c", + "0xaa3428e719175fbec375fcf914a297403e4c78dde3692070982e55a0fb1d9ee2", + "0x044488d632d197e23911a12fce2599d7e288946a197d3fa3c03112f350972e23", + "0x2fae92a292147846a7b9208112c518f1fe624acf9f1f2b79deb7d2184d4dadaa", + "0x0f9b7ebafb9639e6e6f1945c100ce58a51a9e8520e5d08627fa1d406bdb73a93", + "0xebccfe6ae52ce0028a1fd21026a55ba29172d21a8149ea304e463b0142c95406", + "0x1000000000000000000000000000000000000000000000000000000000000000", + "0x792930bbd5baac43bcc798ee49aa8185ef76bb3b44ba62b91d86ae569e4bb535", + "0x0078dc18c92af3ecee78c7b4d569f455a33497362ca87d72e3444216a2266402", + "0xef3bf7405b3440b68a288064dcf786cadc5ca426f204fffef1196c002b4ac404", + "0x4467cb4d42ad8f8b9eca62ddc7666cfb8e1d0bb365e5f1245f109c6b1e5f50ad", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0xf5a5fd42d16a20302798ef6ed309979b43003d2320d9f0e8ea9831a92759fb4b", + "0x86ba29fd0d93db71f9f08cd6a60a61f40fa8df59f2f791a57a37958950610ce4" + ], + "slot": 6308974 +} \ No newline at end of file diff --git a/test/integrations/diva/fixtures/diva_status_6308974.json b/test/integrations/diva/fixtures/diva_status_6308974.json new file mode 100644 index 0000000..b7c35b4 --- /dev/null +++ b/test/integrations/diva/fixtures/diva_status_6308974.json @@ -0,0 +1,90 @@ +{ + "activationEpoch": 187922, + "activationProof": [ + "0xe3dd020000000000000000000000000000000000000000000000000000000000", + "0x5cf048c20dd53a17e59d180c60778d248b19abca811cb394fb2fcce4e227710e", + "0x47891983f24ed0551b3e09fefee0acfe1e78dc99a37ef91dcfdef884a5d6ef1a" + ], + "beaconStateRoot": "0x766019cc1fca2aeaa99ae6a3e0ff7dda8473d7fd095b4a6f72e704c587f99661", + "beaconStateRootProof": [ + "0xb1637a7ea1e1365d98e7b5b4db621621dbbff0e98112f26ad90ff504316fb880", + "0x2485de40900a4bf80424454e6f4ec00f583d7c7cd1a8e45bcb8d4444154ec1ab", + "0x4aef3572fc492c52e5d0ab4f48eb6a2e81da232efca34925ef84cf9dff2dd996" + ], + "blockHeaderRoot": "0x8ae7501389bf5039ecae495ef97e96ea75e4cef982c58ff348fc80dcf3b8ac58", + "exitEpoch": 191952, + "exitProof": [ + "0xcb0d030000000000000000000000000000000000000000000000000000000000", + "0x5ff9c45bc015ad7d04a2df91cc6de67a7a637edb1e9922df8ffa4672dc746ed7", + "0x47891983f24ed0551b3e09fefee0acfe1e78dc99a37ef91dcfdef884a5d6ef1a" + ], + "pendingEpoch": 187875, + "pendingProof": [ + "0x12de020000000000000000000000000000000000000000000000000000000000", + "0x5cf048c20dd53a17e59d180c60778d248b19abca811cb394fb2fcce4e227710e", + "0x47891983f24ed0551b3e09fefee0acfe1e78dc99a37ef91dcfdef884a5d6ef1a" + ], + "slashed": 1, + "slashedProof": [ + "0x0076be3707000000000000000000000000000000000000000000000000000000", + "0x3a6d5408e53f4d2974e46ac457ec2f194aee59c5fd437dfe61909bc8bb5b68c2", + "0xfe4b358084e38c3da7ab7404ee57116181bc4276959932aa55295667ecfa2319" + ], + "slot": 6308974, + "validatorIndex": 552061, + "validatorProof": [ + "0xa7ccaae4d9d211b9b32a11315e55b264ac724641b0ca2c1fc54b8486c528680c", + "0xb330cab869b4dc4e911dd11e42f69e65873759cb4ed28057ff9fe7d9320d1605", + "0x75fad6f3d105cfd72264ee91b48a75dd17c88e7375f9be7359b18ad3d16e00f9", + "0x14d31b5b753062eb17270cc6f8c145fc09e709f2dedbc1b04aac0803cbe1ca9a", + "0x0d969937f41f1cf6475ec5d21440e438851a91244ec760c496bb1971294d4a4a", + "0x1bc7f7003c81462a989f16c4975d5b0d33672c83921d71753e5727d3453d182e", + "0xd331b3c293ff2e5d959d2a34e0316786dda05786621a8683f9e719945c7c080d", + "0x10dd07c8c699b9afae42d5021f0dcf5d029eb3778ad0509efe25ef24b845a387", + "0x08550ae4e3e1834620fefd8dbb4757516cf175b0b724defc2dfe5e64a761a86e", + "0xd75d5eef83ac3c6e9b372e247a935d4c1f2728a37cb0eac874c046450eb89d73", + "0xca97fd3b3923647b22a857c2038bee539ea4f0cc7dae641f812ecfaedb7f12ab", + "0x0a516636581dafb42eb47ffb4a1e51d5831aed131aed81991e83c0bf25b6e7a3", + "0x2c945494941bc0312b2b0fdfc6233aa48973aba11c7c4ae1bab97e4293caa6e7", + "0x7d46a548c90c16e4de67a23d2562210615e6582fce948e8e87edf1008fdaac9a", + "0x31402f52478379eb9a5bc8775eb1a3da8e486f616b56048a29318821b06505a5", + "0x6ded377be5d8e08d315a82246f32b70bff388e275e2c6e2cd40499775f7ecbbe", + "0x8326fcc67b6d2b1b155bdec5bb60e339a593532eb7c5dc7766d97c50c93a1b9b", + "0x8d0d63c39ebade8509e0ae3c9c3876fb5fa112be18f905ecacfecb92057603ab", + "0x95eec8b2e541cad4e91de38385f2e046619f54496c2382cb6cacd5b98c26f5a4", + "0x5bffaf698f77d14d5c4a2ef3560558e36e18ef769f84e409d08c8cae8676a61f", + "0xcddba7b592e3133393c16194fac7431abf2f5485ed711db282183c819e08ebaa", + "0x8a8d7fe3af8caa085a7639a832001457dfb9128a8061142ad0335629ff23ff9c", + "0xfeb3c337d7a51a6fbf00b9e34c52e1c9195c969bd4e7a0bfd51d5c5bed9c1167", + "0xe71f0aa83cc32edfbefa9f4d3e0174ca85182eec9f3a09f6a6c0df6377a510d7", + "0x31206fa80a50bb6abe29085058f16212212a60eec8f049fecb92d8c8e0a84bc0", + "0x21352bfecbeddde993839f614c3dac0a3ee37543f9b412b16199dc158e23b544", + "0x619e312724bb6d7c3153ed9de791d764a366b389af13c58bf8a8d90481a46765", + "0x7cdd2986268250628d0c10e385c58c6191e6fbe05191bcc04f133f2cea72c1c4", + "0x848930bd7ba8cac54661072113fb278869e07bb8587f91392933374d017bcbe1", + "0x8869ff2c22b28cc10510d9853292803328be4fb0e80495e8bb8d271f5b889636", + "0xb5fe28e79f1b850f8658246ce9b6a1e7b49fc06db7143e8fe0b4f2b0c5523a5c", + "0x985e929f70af28d0bdd1a90a808f977f597c7c778c489e98d3bd8910d31ac0f7", + "0xc6f67e02e6e4e1bdefb994c6098953f34636ba2b6ca20a4721d2b26a886722ff", + "0x1c9a7e5ff1cf48b4ad1582d3f4e4a1004f3b20d8c5a2b71387a4254ad933ebc5", + "0x2f075ae229646b6f6aed19a5e372cf295081401eb893ff599b3f9acc0c0d3e7d", + "0x328921deb59612076801e8cd61592107b5c67c79b846595cc6320c395b46362c", + "0xbfb909fdb236ad2411b4e4883810a074b840464689986c3f8a8091827e17c327", + "0x55d8fb3687ba3ba49f342c77f5a1f89bec83d811446e1a467139213d640b6a74", + "0xf7210d4f8e7e1039790e7bf4efa207555a10a6db1dd4b95da313aaa88b88fe76", + "0xad21b516cbc645ffe34ab5de1c8aef8cd4e7f8d2b51e8e1456adc7563cda206f", + "0xce3e090000000000000000000000000000000000000000000000000000000000", + "0xc37e090000000000000000000000000000000000000000000000000000000000", + "0x3daa923362e5bdbbd73a28ea3262141119d02fa70f19441c853a97496a32c449", + "0x68f148b7f277d8ba158726f1a090cf6ed85ea0618e73cfd053d678af5510c3c6", + "0xe408b322725d69aebcb81340e76cfbaa5dc222665dec168c2aea14eb4f3178c4", + "0xa894e30d6c93b0932f901917f5cff45989da3ab1313f5a5d611e8feaa791ce05" + ], + "validatorRoot": "0xde38f30765be9598ca3507dfb2299978fc58492b158be96ddce87e58b9b436d5", + "withdrawalCredentials": "0x010000000000000000000000687a9414b0225092d4a8b859fe813f1f637d3383", + "withdrawalCredentialsProof": [ + "0xb78686e4f36aad2970532775f21c7ddd46a6d27780bb8d859fae3dfe9980e32b", + "0x17be127147a22d151027dfd8e6e7f03939fed868669dc56a345eae8479a86a0f", + "0xfe4b358084e38c3da7ab7404ee57116181bc4276959932aa55295667ecfa2319" + ] +} \ No newline at end of file diff --git a/test/integrations/EigenLayerBeaconOracleTest.t.sol b/test/integrations/eigenlayer/EigenLayerBeaconOracleTest.t.sol similarity index 72% rename from test/integrations/EigenLayerBeaconOracleTest.t.sol rename to test/integrations/eigenlayer/EigenLayerBeaconOracleTest.t.sol index 2b81889..f88f8c6 100644 --- a/test/integrations/EigenLayerBeaconOracleTest.t.sol +++ b/test/integrations/eigenlayer/EigenLayerBeaconOracleTest.t.sol @@ -8,6 +8,7 @@ import {SSZ} from "src/libraries/SimpleSerialize.sol"; import {LightClient, LightClientStep, LightClientRotate} from "src/lightclient/LightClient.sol"; import {LightClientFixture} from "test/lightclient/LightClientFixture.sol"; import {Strings} from "openzeppelin-contracts/utils/Strings.sol"; +import {BeaconOracleHelper} from "external/integrations/libraries/BeaconOracleHelper.sol"; import {EigenLayerBeaconOracle} from "external/integrations/eigenlayer/EigenLayerBeaconOracle.sol"; import {EigenLayerBeaconOracleProxy} from "external/integrations/eigenlayer/EigenLayerBeaconOracleProxy.sol"; @@ -43,7 +44,9 @@ contract EigenLayerBeaconOracleTest is Test, TestEvents, TestErrors { address public oracleOperator; address public guardian; BeaconOracleUpdateFixture fixture; + BeaconOracleHelper.BeaconStateRootProofInfo beaconStateRootProofInfo; BeaconOracleUpdateFixture fixture2; + BeaconOracleHelper.BeaconStateRootProofInfo beaconStateRootProofInfo2; function isWhitelisted(address _oracleUpdater) public view returns (bool) { return oracle.whitelistedOracleUpdaters(_oracleUpdater); @@ -71,21 +74,34 @@ contract EigenLayerBeaconOracleTest is Test, TestEvents, TestErrors { EigenLayerBeaconOracleProxy(address(proxy)).updateWhitelist(oracleOperator, true); string memory root = vm.projectRoot(); - string memory filename = "valid_6211232"; - string memory path = string.concat(root, "/test/integrations/fixtures/", filename, ".json"); + string memory filename = "eigenlayer_6211232"; + string memory path = + string.concat(root, "/test/integrations/eigenlayer/fixtures/", filename, ".json"); string memory file = vm.readFile(path); bytes memory parsed = vm.parseJson(file); fixture = abi.decode(parsed, (BeaconOracleUpdateFixture)); lightClient.setHeader(fixture.slot, fixture.headerRoot); - string memory filename2 = "valid_6250752"; + string memory filename2 = "eigenlayer_6250752"; string memory path2 = - string.concat(root, "/test/integrations/fixtures/", filename2, ".json"); + string.concat(root, "/test/integrations/eigenlayer/fixtures/", filename2, ".json"); string memory file2 = vm.readFile(path2); bytes memory parsed2 = vm.parseJson(file2); fixture2 = abi.decode(parsed2, (BeaconOracleUpdateFixture)); + beaconStateRootProofInfo = BeaconOracleHelper.BeaconStateRootProofInfo({ + slot: fixture.slot, + beaconStateRoot: fixture.beaconStateRoot, + beaconStateRootProof: fixture.beaconStateRootProof + }); + + beaconStateRootProofInfo2 = BeaconOracleHelper.BeaconStateRootProofInfo({ + slot: fixture2.slot, + beaconStateRoot: fixture2.beaconStateRoot, + beaconStateRootProof: fixture2.beaconStateRootProof + }); + lightClient.setHeader(fixture2.slot, fixture2.headerRoot); vm.warp(9999999999999); @@ -98,11 +114,7 @@ contract EigenLayerBeaconOracleTest is Test, TestEvents, TestErrors { emit BeaconStateOracleUpdate(fixture.slot, fixture.blockNumber, fixture.beaconStateRoot); oracle.fulfillRequest( - fixture.slot, - fixture.blockNumber, - fixture.blockNumberProof, - fixture.beaconStateRoot, - fixture.beaconStateRootProof + beaconStateRootProofInfo, fixture.blockNumber, fixture.blockNumberProof ); bytes32 beaconStateRoot = oracle.blockNumberToStateRoot(fixture.blockNumber); @@ -120,33 +132,21 @@ contract EigenLayerBeaconOracleTest is Test, TestEvents, TestErrors { vm.prank(notOperator); vm.expectRevert(abi.encodeWithSelector(InvalidUpdater.selector, notOperator)); oracle.fulfillRequest( - fixture.slot, - fixture.blockNumber, - fixture.blockNumberProof, - fixture.beaconStateRoot, - fixture.beaconStateRootProof + beaconStateRootProofInfo, fixture.blockNumber, fixture.blockNumberProof ); } function test_RevertSlotTooLow() public { vm.prank(oracleOperator); oracle.fulfillRequest( - fixture2.slot, - fixture2.blockNumber, - fixture2.blockNumberProof, - fixture2.beaconStateRoot, - fixture2.beaconStateRootProof + beaconStateRootProofInfo2, fixture2.blockNumber, fixture2.blockNumberProof ); vm.prank(oracleOperator); // Slot number is lower than previous slot vm.expectRevert(abi.encodeWithSelector(SlotNumberTooLow.selector)); oracle.fulfillRequest( - fixture.slot, - fixture.blockNumber, - fixture.blockNumberProof, - fixture.beaconStateRoot, - fixture.beaconStateRootProof + beaconStateRootProofInfo, fixture.blockNumber, fixture.blockNumberProof ); } @@ -154,25 +154,23 @@ contract EigenLayerBeaconOracleTest is Test, TestEvents, TestErrors { vm.prank(oracleOperator); vm.expectRevert(abi.encodeWithSelector(InvalidBlockNumberProof.selector)); oracle.fulfillRequest( - fixture.slot, - fixture.blockNumber, + beaconStateRootProofInfo, + fixture2.blockNumber, // Invalid blockNumberProof - fixture2.blockNumberProof, - fixture.beaconStateRoot, - fixture.beaconStateRootProof + fixture2.blockNumberProof ); } function test_RevertInvalidBeaconStateRootProof() public { + BeaconOracleHelper.BeaconStateRootProofInfo memory invalidBaseProof = BeaconOracleHelper + .BeaconStateRootProofInfo({ + slot: fixture.slot, + beaconStateRoot: fixture.beaconStateRoot, + // Invalid beaconStateRootProof + beaconStateRootProof: fixture2.beaconStateRootProof + }); vm.prank(oracleOperator); vm.expectRevert(abi.encodeWithSelector(InvalidBeaconStateRootProof.selector)); - oracle.fulfillRequest( - fixture.slot, - fixture.blockNumber, - fixture.blockNumberProof, - fixture.beaconStateRoot, - // Invalid beaconStateRootProof - fixture2.beaconStateRootProof - ); + oracle.fulfillRequest(invalidBaseProof, fixture.blockNumber, fixture.blockNumberProof); } } diff --git a/test/integrations/fixtures/valid_6211232.json b/test/integrations/eigenlayer/fixtures/eigenlayer_6211232.json similarity index 100% rename from test/integrations/fixtures/valid_6211232.json rename to test/integrations/eigenlayer/fixtures/eigenlayer_6211232.json diff --git a/test/integrations/fixtures/valid_6250752.json b/test/integrations/eigenlayer/fixtures/eigenlayer_6250752.json similarity index 100% rename from test/integrations/fixtures/valid_6250752.json rename to test/integrations/eigenlayer/fixtures/eigenlayer_6250752.json