Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion evm/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
"license": "ISC",
"description": "",
"dependencies": {
"@hyperbridge/core": "^1.6.0",
"@hyperbridge/core": "file:../sdk/packages/core",
"@openzeppelin/contracts": "^5.4.0",
"@polytope-labs/solidity-merkle-trees": "^0.4.0",
"@uniswap/swap-router-contracts": "^1.3.1",
Expand Down
10 changes: 5 additions & 5 deletions evm/pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

21 changes: 19 additions & 2 deletions evm/src/consensus/BeefyV1.sol
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import {
} from "./Types.sol";
import {StateMachine} from "@hyperbridge/core/libraries/StateMachine.sol";
import {IConsensus, IntermediateState, StateCommitment} from "@hyperbridge/core/interfaces/IConsensus.sol";
import {IConsensusV2} from "@hyperbridge/core/interfaces/IConsensusV2.sol";

import {MerkleMultiProof} from "@polytope-labs/solidity-merkle-trees/src/MerkleMultiProof.sol";
import {MerkleMountainRange} from "@polytope-labs/solidity-merkle-trees/src/MerkleMountainRange.sol";
Expand All @@ -46,7 +47,7 @@ import {ERC165} from "@openzeppelin/contracts/utils/introspection/ERC165.sol";
* @notice This verifies secp256k1 signatures and authority set membership merkle proofs
* in order to confirm newly finalized states of the Hyperbridge blockchain.
*/
contract BeefyV1 is IConsensus, ERC165 {
contract BeefyV1 is IConsensus, IConsensusV2, ERC165 {
using HeaderImpl for Header;

// The PayloadId for the mmr root.
Expand Down Expand Up @@ -80,7 +81,23 @@ contract BeefyV1 is IConsensus, ERC165 {
* @dev See {IERC165-supportsInterface}.
*/
function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
return interfaceId == type(IConsensus).interfaceId || super.supportsInterface(interfaceId);
return interfaceId == type(IConsensus).interfaceId || interfaceId == type(IConsensusV2).interfaceId
|| super.supportsInterface(interfaceId);
}

function verify(bytes calldata previousState, bytes calldata proof)
external
pure
returns (bytes memory, IntermediateState[] memory, uint256)
{
BeefyConsensusState memory consensusState = abi.decode(previousState, (BeefyConsensusState));
(RelayChainProof memory relay, ParachainProof memory parachain) =
abi.decode(proof, (RelayChainProof, ParachainProof));

(BeefyConsensusState memory newState, IntermediateState[] memory intermediates) =
verifyConsensus(consensusState, BeefyConsensusProof(relay, parachain));

return (abi.encode(newState), intermediates, newState.nextAuthoritySet.id);
}

function verifyConsensus(bytes memory encodedState, bytes memory encodedProof)
Expand Down
21 changes: 19 additions & 2 deletions evm/src/consensus/BeefyV1FiatShamir.sol
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import {
} from "./Types.sol";

import {IConsensus, IntermediateState, StateCommitment} from "@hyperbridge/core/interfaces/IConsensus.sol";
import {IConsensusV2} from "@hyperbridge/core/interfaces/IConsensusV2.sol";

import {MerkleMultiProof} from "@polytope-labs/solidity-merkle-trees/src/MerkleMultiProof.sol";
import {MerkleMountainRange} from "@polytope-labs/solidity-merkle-trees/src/MerkleMountainRange.sol";
Expand Down Expand Up @@ -83,7 +84,7 @@ import {ERC165} from "@openzeppelin/contracts/utils/introspection/ERC165.sol";
* proportionally smaller merkle multi-proof. The bitmap verification and
* transcript construction add negligible overhead.
*/
contract BeefyV1FiatShamir is IConsensus, ERC165 {
contract BeefyV1FiatShamir is IConsensus, IConsensusV2, ERC165 {
using HeaderImpl for Header;
using Transcript for Transcript.State;

Expand Down Expand Up @@ -137,7 +138,23 @@ contract BeefyV1FiatShamir is IConsensus, ERC165 {
* @dev See {IERC165-supportsInterface}.
*/
function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
return interfaceId == type(IConsensus).interfaceId || super.supportsInterface(interfaceId);
return interfaceId == type(IConsensus).interfaceId || interfaceId == type(IConsensusV2).interfaceId
|| super.supportsInterface(interfaceId);
}

function verify(bytes calldata previousState, bytes calldata proof)
external
pure
returns (bytes memory, IntermediateState[] memory, uint256)
{
BeefyConsensusState memory consensusState = abi.decode(previousState, (BeefyConsensusState));
(RelayChainProof memory relay, ParachainProof memory parachain, uint256[4] memory signersBitmap) =
abi.decode(proof, (RelayChainProof, ParachainProof, uint256[4]));

(BeefyConsensusState memory newState, IntermediateState[] memory intermediates) =
verifyConsensus(consensusState, relay, parachain, signersBitmap);

return (abi.encode(newState), intermediates, newState.nextAuthoritySet.id);
}

/**
Expand Down
44 changes: 39 additions & 5 deletions evm/src/consensus/ConsensusRouter.sol
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
pragma solidity ^0.8.20;

import {IConsensus, IntermediateState} from "@hyperbridge/core/interfaces/IConsensus.sol";
import {IConsensusV2} from "@hyperbridge/core/interfaces/IConsensusV2.sol";
import {ERC165} from "@openzeppelin/contracts/utils/introspection/ERC165.sol";

/**
Expand All @@ -24,7 +25,7 @@ import {ERC165} from "@openzeppelin/contracts/utils/introspection/ERC165.sol";
* @notice Routes consensus verification to either SP1Beefy (ZK proof), BeefyV1 (naive proof),
* or BeefyV1FiatShamir (Fiat-Shamir sampled proof) based on the first byte of the proof.
*/
contract ConsensusRouter is IConsensus, ERC165 {
contract ConsensusRouter is IConsensus, IConsensusV2, ERC165 {
// Proof type enum
enum ProofType {
// 0x00 - BeefyV1 naive proof
Expand Down Expand Up @@ -60,7 +61,8 @@ contract ConsensusRouter is IConsensus, ERC165 {
* @dev See {IERC165-supportsInterface}.
*/
function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
return interfaceId == type(IConsensus).interfaceId || super.supportsInterface(interfaceId);
return interfaceId == type(IConsensus).interfaceId || interfaceId == type(IConsensusV2).interfaceId
|| super.supportsInterface(interfaceId);
}

/**
Expand Down Expand Up @@ -90,13 +92,45 @@ contract ConsensusRouter is IConsensus, ERC165 {

if (proofType == ProofType.ZK) {
// Route to SP1Beefy for ZK proof verification
return sp1Beefy.verifyConsensus(encodedState, actualProof);
return IConsensus(address(sp1Beefy)).verifyConsensus(encodedState, actualProof);
} else if (proofType == ProofType.Naive) {
// Route to BeefyV1 for naive proof verification
return beefyV1.verifyConsensus(encodedState, actualProof);
return IConsensus(address(beefyV1)).verifyConsensus(encodedState, actualProof);
} else if (proofType == ProofType.FiatShamir) {
// Route to BeefyV1FiatShamir for Fiat-Shamir sampled proof verification
return beefyV1FiatShamir.verifyConsensus(encodedState, actualProof);
return IConsensus(address(beefyV1FiatShamir)).verifyConsensus(encodedState, actualProof);
} else {
revert InvalidProofType(proofTypeByte);
}
}

/**
* @dev IConsensusV2 verify which routes to the appropriate verifier based on the first byte of the proof.
*/
function verify(bytes calldata previousState, bytes calldata encodedProof)
external
view
returns (bytes memory, IntermediateState[] memory, uint256)
{
if (encodedProof.length == 0) revert EmptyProof();

uint8 proofTypeByte = uint8(encodedProof[0]);

if (proofTypeByte > uint8(type(ProofType).max)) {
revert InvalidProofType(proofTypeByte);
}

ProofType proofType = ProofType(proofTypeByte);

// Strip the first byte
bytes calldata actualProof = encodedProof[1:];

if (proofType == ProofType.ZK) {
return IConsensusV2(address(sp1Beefy)).verify(previousState, actualProof);
} else if (proofType == ProofType.Naive) {
return IConsensusV2(address(beefyV1)).verify(previousState, actualProof);
} else if (proofType == ProofType.FiatShamir) {
return IConsensusV2(address(beefyV1FiatShamir)).verify(previousState, actualProof);
} else {
revert InvalidProofType(proofTypeByte);
}
Expand Down
29 changes: 26 additions & 3 deletions evm/src/consensus/SP1Beefy.sol
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@
// limitations under the License.
pragma solidity ^0.8.20;

import {IConsensus, IntermediateState} from "@hyperbridge/core/interfaces/IConsensus.sol";
import {IConsensus, IntermediateState, StateCommitment} from "@hyperbridge/core/interfaces/IConsensus.sol";
import {IConsensusV2} from "@hyperbridge/core/interfaces/IConsensusV2.sol";
import {ERC165} from "@openzeppelin/contracts/utils/introspection/ERC165.sol";
import {ISP1Verifier} from "@sp1-contracts/ISP1Verifier.sol";

Expand All @@ -28,7 +29,7 @@ import "./Types.sol";
* @notice Similar to the BeefyV1 client but delegates secp256k1 signature verification, authority set membership proof checks
* and mmr leaf to an SP1 program.
*/
contract SP1Beefy is IConsensus, ERC165 {
contract SP1Beefy is IConsensus, IConsensusV2, ERC165 {
using HeaderImpl for Header;

// SP1 verification key
Expand All @@ -55,7 +56,29 @@ contract SP1Beefy is IConsensus, ERC165 {
* @dev See {IERC165-supportsInterface}.
*/
function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
return interfaceId == type(IConsensus).interfaceId || super.supportsInterface(interfaceId);
return interfaceId == type(IConsensus).interfaceId || interfaceId == type(IConsensusV2).interfaceId
|| super.supportsInterface(interfaceId);
}

function verify(bytes calldata previousState, bytes calldata proof)
external
view
returns (bytes memory, IntermediateState[] memory, uint256)
{
BeefyConsensusState memory consensusState = abi.decode(previousState, (BeefyConsensusState));
(
MiniCommitment memory commitment,
PartialBeefyMmrLeaf memory leaf,
ParachainHeader[] memory headers,
bytes memory plonkProof
) = abi.decode(proof, (MiniCommitment, PartialBeefyMmrLeaf, ParachainHeader[], bytes));
SP1BeefyProof memory sp1Proof =
SP1BeefyProof({commitment: commitment, mmrLeaf: leaf, headers: headers, proof: plonkProof});

(BeefyConsensusState memory newState, IntermediateState[] memory intermediates) =
verifyConsensus(consensusState, sp1Proof);

return (abi.encode(newState), intermediates, newState.nextAuthoritySet.id);
}

/*
Expand Down
2 changes: 1 addition & 1 deletion evm/src/core/HandlerV1.sol
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ contract HandlerV1 is IHandler, ERC165, Context {
* @param host - `IsmpHost`
* @param proof - consensus proof
*/
function handleConsensus(IHost host, bytes calldata proof) external notFrozen(host) {
function handleConsensus(IHost host, bytes calldata proof) external virtual notFrozen(host) {
uint256 delay = block.timestamp - host.consensusUpdateTime();

if (delay >= host.unStakingPeriod()) revert ConsensusClientExpired();
Expand Down
115 changes: 115 additions & 0 deletions evm/src/core/HandlerV2.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
// Copyright (C) Polytope Labs Ltd.
// SPDX-License-Identifier: Apache-2.0

// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
pragma solidity ^0.8.17;

import {HandlerV1} from "./HandlerV1.sol";
import {IHandlerV2} from "@hyperbridge/core/interfaces/IHandlerV2.sol";
import {IConsensusV2} from "@hyperbridge/core/interfaces/IConsensusV2.sol";
import {IntermediateState, StateMachineHeight, StateCommitment} from "@hyperbridge/core/interfaces/IConsensus.sol";
import {IHandler} from "@hyperbridge/core/interfaces/IHandler.sol";
import {IHost} from "@hyperbridge/core/interfaces/IHost.sol";

/**
* @title The ISMP Message Handler V2.
* @author Polytope Labs (hello@polytope.technology)
*
* @notice Extends HandlerV1 with batch call support and IConsensusV2 integration.
* Relayers can bundle multiple handler operations into a single transaction via batchCall.
* Also tracks which relayer submitted the consensus proof for each authority set epoch.
*/
contract HandlerV2 is HandlerV1, IHandlerV2 {
// Maps authority set ID to the relayer that submitted the consensus proof for that epoch
mapping(uint256 => address) private _epochs;

// The current authority set epoch
uint256 private _currentEpoch;

// A call in the batch failed
error BatchCallFailed(uint256 index, bytes reason);

event NewEpoch(uint256 indexed authoritySetId, address indexed relayer);

/**
* @dev See {IERC165-supportsInterface}.
*/
function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
return interfaceId == type(IHandlerV2).interfaceId || super.supportsInterface(interfaceId);
}

/**
* @dev Process a batch of encoded handler calls in a single transaction.
* Uses delegatecall to self so msg.sender is preserved and storage writes
* happen in this contract's context. Atomic, any failure reverts the entire batch.
* @param calls - array of ABI-encoded handler function calls
*/
function batchCall(bytes[] memory calls) external {
uint256 len = calls.length;
for (uint256 i = 0; i < len; ++i) {
(bool success, bytes memory returnData) = address(this).delegatecall(calls[i]);
if (!success) {
revert BatchCallFailed(i, returnData);
}
}
}

/**
* @dev Handle incoming consensus messages using IConsensusV2.
* Verifies the proof, stores the new consensus state and intermediate states,
* and records the relayer for the new authority set epoch if one occurred.
* @param host - `IsmpHost`
* @param proof - consensus proof
*/
function handleConsensus(IHost host, bytes calldata proof) external override(HandlerV1, IHandler) notFrozen(host) {
uint256 delay = block.timestamp - host.consensusUpdateTime();
if (delay >= host.unStakingPeriod()) revert ConsensusClientExpired();

(bytes memory verifiedState, IntermediateState[] memory intermediates, uint256 nextAuthoritySetId) =
IConsensusV2(host.consensusClient()).verify(host.consensusState(), proof);
host.storeConsensusState(verifiedState);

uint256 intermediatesLen = intermediates.length;
for (uint256 i = 0; i < intermediatesLen; i++) {
IntermediateState memory intermediate = intermediates[i];
uint256 latestHeight = host.latestStateMachineHeight(intermediate.stateMachineId);
if (latestHeight != 0 && intermediate.height > latestHeight) {
StateMachineHeight memory stateMachineHeight =
StateMachineHeight({stateMachineId: intermediate.stateMachineId, height: intermediate.height});
host.storeStateMachineCommitment(stateMachineHeight, intermediate.commitment);
}
}

if (nextAuthoritySetId > _currentEpoch) {
_currentEpoch = nextAuthoritySetId;
_epochs[nextAuthoritySetId] = msg.sender;
emit NewEpoch(nextAuthoritySetId, msg.sender);
}
}

/**
* @dev Returns the relayer address for a given authority set ID.
* @param authoritySetId - the authority set / epoch ID
* @return the relayer address, or address(0) if not set
*/
function relayerOf(uint256 authoritySetId) external view returns (address) {
return _epochs[authoritySetId];
}

/**
* @dev Returns the current authority set epoch.
*/
function currentEpoch() external view returns (uint256) {
return _currentEpoch;
}
}
Loading