Skip to content

Commit c337496

Browse files
authored
Merge pull request #85 from gnosis/feat/sp1-helios
feat: SP1 Helios Adapter
2 parents 32606b3 + 3aeec78 commit c337496

File tree

6 files changed

+415
-0
lines changed

6 files changed

+415
-0
lines changed
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
// SPDX-License-Identifier: LGPL-3.0-only
2+
pragma solidity ^0.8.20;
3+
4+
import { ISP1LightClient } from "./interfaces/ISP1LightClient.sol";
5+
import { SSZ } from "../Telepathy/libraries/SimpleSerialize.sol";
6+
import { Merkle } from "../Spectre/lib/Merkle.sol";
7+
import { Receipt } from "../Electron/lib/Receipt.sol";
8+
import { BlockHashAdapter } from "../BlockHashAdapter.sol";
9+
10+
contract SP1HeliosAdapter is BlockHashAdapter {
11+
string public constant PROVIDER = "sp1-helios";
12+
bytes32 internal constant MESSAGE_DISPATCHED_EVENT_SIG =
13+
0x218247aabc759e65b5bb92ccc074f9d62cd187259f2a0984c3c9cf91f67ff7cf; // keccak256("MessageDispatched(uint256,(uint256,uint256,uint256,address,address,bytes,address[],address[]))");
14+
15+
address public immutable SP1_HELIOS_ADDRESS;
16+
address public immutable SOURCE_YAHO;
17+
uint256 public immutable SOURCE_CHAIN_ID;
18+
19+
error HeaderNotAvailable();
20+
error InvalidBlockNumberProof();
21+
error InvalidBlockHashProof();
22+
error InvalidReceiptsRoot();
23+
error ErrorParseReceipt();
24+
error InvalidEventSignature();
25+
error InvalidEventSource();
26+
27+
constructor(address sp1HeliosAddress, uint256 sourceChainId, address sourceYaho) {
28+
SP1_HELIOS_ADDRESS = sp1HeliosAddress;
29+
SOURCE_CHAIN_ID = sourceChainId;
30+
SOURCE_YAHO = sourceYaho;
31+
}
32+
33+
function storeBlockHeader(
34+
uint256 slot,
35+
uint256 blockNumber,
36+
bytes32[] calldata blockNumberProof,
37+
bytes32 blockHash,
38+
bytes32[] calldata blockHashProof
39+
) external {
40+
bytes32 header = _getHeader(slot);
41+
42+
if (!SSZ.verifyBlockNumber(blockNumber, blockNumberProof, header)) {
43+
revert InvalidBlockNumberProof();
44+
}
45+
46+
if (!SSZ.verifyBlockHash(blockHash, blockHashProof, header)) {
47+
revert InvalidBlockHashProof();
48+
}
49+
50+
_storeHash(SOURCE_CHAIN_ID, blockNumber, blockHash);
51+
}
52+
53+
function verifyAndStoreDispatchedMessage(
54+
uint64 headerSlot,
55+
uint64 txSlot,
56+
bytes32[] memory receiptsRootProof,
57+
bytes32 receiptsRoot,
58+
bytes[] memory receiptProof,
59+
bytes memory txIndexRLPEncoded,
60+
uint256 logIndex
61+
) external {
62+
bytes32 header = _getHeader(headerSlot);
63+
64+
bool isValidReceiptsRoot = Merkle.verifyReceiptsRoot(
65+
receiptsRootProof,
66+
receiptsRoot,
67+
headerSlot,
68+
txSlot,
69+
header
70+
);
71+
if (!isValidReceiptsRoot) revert InvalidReceiptsRoot();
72+
73+
Receipt.ParsedReceipt memory parsedReceipt = Receipt.parseReceipt(
74+
receiptsRoot,
75+
receiptProof,
76+
txIndexRLPEncoded,
77+
logIndex
78+
);
79+
if (!parsedReceipt.isValid) revert ErrorParseReceipt();
80+
if (bytes32(parsedReceipt.topics[0]) != MESSAGE_DISPATCHED_EVENT_SIG) revert InvalidEventSignature();
81+
if (parsedReceipt.eventSource != SOURCE_YAHO) revert InvalidEventSource();
82+
83+
uint256 messageId = uint256(parsedReceipt.topics[1]);
84+
bytes32 messageHash = keccak256(parsedReceipt.data);
85+
86+
_storeHash(SOURCE_CHAIN_ID, messageId, messageHash);
87+
}
88+
89+
function _getHeader(uint256 slot) internal view returns (bytes32) {
90+
bytes32 header = ISP1LightClient(SP1_HELIOS_ADDRESS).headers(slot);
91+
if (header == bytes32(0)) {
92+
revert HeaderNotAvailable();
93+
}
94+
return header;
95+
}
96+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
// SPDX-License-Identifier: UNLICENSED
2+
pragma solidity ^0.8.20;
3+
4+
interface ISP1LightClient {
5+
function head() external view returns (uint256);
6+
function headers(uint256) external view returns (bytes32);
7+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
// SPDX-License-Identifier: UNLICENSED
2+
pragma solidity ^0.8.20;
3+
4+
/// @title MockSP1Helios
5+
/// @notice A mock Ethereum beacon chain light client, built with SP1 and Helios.
6+
contract MockSP1Helios {
7+
/// @notice The latest slot the light client has a finalized header for.
8+
uint256 public head = 0;
9+
10+
/// @notice Maps from a slot to a beacon block header root.
11+
mapping(uint256 => bytes32) public headers;
12+
13+
event HeadUpdate(uint256 indexed slot, bytes32 indexed root);
14+
15+
function setHeader(uint256 slot, bytes32 header) external {
16+
head = slot;
17+
headers[slot] = header;
18+
19+
emit HeadUpdate(slot, header);
20+
}
21+
}

packages/evm/tasks/deploy/adapters/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,3 +18,4 @@ import "./telepathy"
1818
import "./vea"
1919
import "./wormhole"
2020
import "./zetachain"
21+
import "./sp1helios"
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
import type { SignerWithAddress } from "@nomiclabs/hardhat-ethers/signers"
2+
import { task } from "hardhat/config"
3+
import type { TaskArguments } from "hardhat/types"
4+
5+
import type { SP1HeliosAdapter } from "../../../types/contracts/adapters/SP1Helios/SP1HeliosAdapter"
6+
import type { SP1HeliosAdapter__factory } from "../../../types/factories/contracts/adapters/SP1Helios/SP1HeliosAdapter__factory"
7+
import { verify } from "../index"
8+
9+
const MerklePatriciaAddresses = {
10+
10200: "0x777662E6A65411e0A425E59C496A7D1C0635A935",
11+
100: "0xff07C59F7D882D1799e1CABd1D17faaDE7694fe0",
12+
11155111: "0x1b19Dfd5e1986A0d524644F081AcB14d51159818",
13+
4201: "0xC82e50cc90C84DC492B4Beb6792DEeB496d52424",
14+
42: "0x10Da7e0e9eBc8BFE0021698F557F418889b9b4D2",
15+
}
16+
17+
task("deploy:SP1HeliosAdapter")
18+
.addParam("sp1helios")
19+
.addParam("sourceChainId")
20+
.addParam("sourceYaho")
21+
.addFlag("verify", "whether to verify the contract on Etherscan")
22+
.setAction(async function (taskArguments: TaskArguments, hre) {
23+
console.log("Deploying SP1HeliosAdapter...")
24+
const merklePatriciaAddress = MerklePatriciaAddresses[hre.network.config.chainId]
25+
if (!merklePatriciaAddress) {
26+
throw new Error("MerklePatricia Not Found")
27+
}
28+
const signers: SignerWithAddress[] = await hre.ethers.getSigners()
29+
const SP1HeliosAdapterFactory: SP1HeliosAdapter__factory = <SP1HeliosAdapter__factory>(
30+
await hre.ethers.getContractFactory("SP1HeliosAdapter", {
31+
libraries: {
32+
MerklePatricia: merklePatriciaAddress,
33+
},
34+
})
35+
)
36+
const constructorArguments = [
37+
taskArguments.sp1helios,
38+
taskArguments.sourceChainId,
39+
taskArguments.sourceYaho,
40+
] as const
41+
const SP1HeliosAdapter: SP1HeliosAdapter = <SP1HeliosAdapter>(
42+
await SP1HeliosAdapterFactory.connect(signers[0]).deploy(...constructorArguments)
43+
)
44+
await SP1HeliosAdapter.deployed()
45+
console.log("SP1HeliosAdapter deployed to:", SP1HeliosAdapter.address)
46+
if (taskArguments.verify) await verify(hre, SP1HeliosAdapter, constructorArguments)
47+
})

0 commit comments

Comments
 (0)