Skip to content

Commit

Permalink
integration: diva (#352) (#13)
Browse files Browse the repository at this point in the history
# Changes
- Add a `DivaBeaconOracle` contract where you can prove:
- Any of the fields of a
[validator](https://github.com/ethereum/consensus-specs/blob/dev/specs/phase0/beacon-chain.md#validator)
- The `balance` of a validator using the `balances` container in
`BeaconState`
- A deposit from a BLSPubkey using the `deposits` container in
`BeaconBlock`

# Notes
Lodestar currently has a [gindex
issue](ChainSafe/ssz#311) for fields in the
`Validator` container. As such, we need to calculate the gindices of
`Validator` fields manually.
  • Loading branch information
ratankaliani authored May 3, 2023
2 parents 0d324c8 + 67dbcc5 commit b6506d5
Show file tree
Hide file tree
Showing 13 changed files with 1,021 additions and 79 deletions.
4 changes: 2 additions & 2 deletions external/examples/oracle/NFTAirdrop.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down Expand Up @@ -175,4 +175,4 @@ contract NFTAirdropTest is Test {
vm.prank(address(oracle));
nftAirdrop.handleOracleResponse(1, responseData2, responseSuccess2);
}
}
}
200 changes: 200 additions & 0 deletions external/integrations/diva/DivaBeaconOracle.sol
Original file line number Diff line number Diff line change
@@ -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);
}
}
}
50 changes: 13 additions & 37 deletions external/integrations/eigenlayer/EigenLayerBeaconOracle.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand All @@ -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
);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -66,4 +66,4 @@ contract EigenLayerBeaconOracleProxy is

/// @notice Authorizes an upgrade for the implementation contract.
function _authorizeUpgrade(address newImplementation) internal override onlyTimelock {}
}
}
4 changes: 2 additions & 2 deletions external/integrations/gnosis/TelepathyValidator.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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 {}
Expand Down Expand Up @@ -165,4 +165,4 @@ contract TelepathyValidatorTest is Test {
telepathyValidator.toggleExecuteAffirmations();
assertEq(telepathyValidator.executeAffirmationsEnabled(), false);
}
}
}
Loading

0 comments on commit b6506d5

Please sign in to comment.