Skip to content

Commit

Permalink
Merge branch 'feat/v0.2.0' into docs/README
Browse files Browse the repository at this point in the history
  • Loading branch information
zengzengzenghuy committed Sep 24, 2024
2 parents b4d322f + eeb5cb2 commit 001f5cd
Show file tree
Hide file tree
Showing 5 changed files with 420 additions and 0 deletions.
81 changes: 81 additions & 0 deletions packages/evm/contracts/adapters/Spectre/SpectreAdapter.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
// SPDX-License-Identifier: LGPL-3.0-only
pragma solidity ^0.8.20;

import { Merkle } from "./lib/Merkle.sol";
import { Receipt } from "../Electron/lib/Receipt.sol";
import { BlockHashAdapter } from "../BlockHashAdapter.sol";
import { AccessControl } from "@openzeppelin/contracts/access/AccessControl.sol";
import { ISpectre } from "./interfaces/ISpectre.sol";

contract SpectreAdapter is AccessControl, BlockHashAdapter {
string public constant PROVIDER = "spectre";

// keccak256("MessageDispatched(uint256,(uint256,uint256,uint256,address,address,bytes,address[],address[]))")
bytes32 internal constant MESSAGE_DISPATCHED_EVENT_SIG =
0x218247aabc759e65b5bb92ccc074f9d62cd187259f2a0984c3c9cf91f67ff7cf;

address public immutable SOURCE_YAHO;
uint256 public immutable SOURCE_CHAIN_ID;
address public spectreAddress;

error Unauthorized();
error InvalidEventSource();
error InvalidReceiptsRoot();
error ErrorParseReceipt();
error InvalidEventSignature();
error BlockHeaderRootMissing();

constructor(address initialSpectreAddress, uint256 sourceChainId, address sourceYaho) {
_setupRole(DEFAULT_ADMIN_ROLE, msg.sender);
SOURCE_CHAIN_ID = sourceChainId;
SOURCE_YAHO = sourceYaho;
spectreAddress = initialSpectreAddress;
}

modifier onlyAdmin() {
if (!hasRole(DEFAULT_ADMIN_ROLE, msg.sender)) revert Unauthorized();
_;
}

function changeSpectreAddress(address newSpectreAddress) external onlyAdmin {
spectreAddress = newSpectreAddress;
}

function verifyAndStoreDispatchedMessage(
uint64 srcSlot,
uint64 txSlot,
bytes32[] memory receiptsRootProof,
bytes32 receiptsRoot,
bytes[] memory receiptProof,
bytes memory txIndexRLPEncoded,
uint256 logIndex
) external {
bytes32 blockHeaderRoot = ISpectre(spectreAddress).blockHeaderRoots(srcSlot);
if (blockHeaderRoot == bytes32(0)) revert BlockHeaderRootMissing();

bool isValidReceiptsRoot = Merkle.verifyReceiptsRoot(
receiptsRootProof,
receiptsRoot,
srcSlot,
txSlot,
blockHeaderRoot
);
if (!isValidReceiptsRoot) revert InvalidReceiptsRoot();

Receipt.ParsedReceipt memory parsedReceipt = Receipt.parseReceipt(
receiptsRoot,
receiptProof,
txIndexRLPEncoded,
logIndex
);

if (!parsedReceipt.isValid) revert ErrorParseReceipt();
if (bytes32(parsedReceipt.topics[0]) != MESSAGE_DISPATCHED_EVENT_SIG) revert InvalidEventSignature();
if (parsedReceipt.eventSource != SOURCE_YAHO) revert InvalidEventSource();

uint256 messageId = uint256(parsedReceipt.topics[1]);
bytes32 messageHash = keccak256(parsedReceipt.data);

_storeHash(SOURCE_CHAIN_ID, messageId, messageHash);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.20;

interface ISpectre {
function blockHeaderRoots(uint256 slot) external view returns (bytes32);
}
118 changes: 118 additions & 0 deletions packages/evm/contracts/adapters/Spectre/lib/Merkle.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
// SPDX-License-Identifier: LGPL-3.0-only
pragma solidity ^0.8.20;

library Merkle {
uint256 internal constant SLOTS_PER_HISTORICAL_ROOT = 8192;
// BeaconState -> BlockRoots
uint256 internal constant BLOCK_ROOTS_GINDEX = 37;
// BeaconBlock -> BeaconState
uint256 internal constant STATE_ROOT_GINDEX = 11;
// BeaconBlock -> BeaconBody -> ExecutionPayload -> ReceiptsRoot
uint256 internal constant RECEIPT_ROOT_GINDEX = 6435;

function restoreMerkleRoot(
bytes32[] memory branch,
bytes32 leaf,
uint256 index
) internal pure returns (bytes32 root) {
require(index < 2 ** branch.length, "invalid leaf index");

bytes32 combineHash = leaf;
uint256 curIndex = index;
for (uint256 i = 0; i < branch.length; ) {
if (curIndex % 2 == 0) combineHash = sha256(bytes.concat(combineHash, branch[i]));
else combineHash = sha256(bytes.concat(branch[i], combineHash));

curIndex /= 2;

unchecked {
i++;
}
}

root = combineHash;
}

function verifyReceiptsRoot(
bytes32[] memory receiptsRootBranch,
bytes32 receiptsRoot,
uint64 lcSlot,
uint64 txSlot,
bytes32 headerRoot
) internal pure returns (bool) {
uint256 index;
if (txSlot == lcSlot) {
index = RECEIPT_ROOT_GINDEX;
} else if (lcSlot - txSlot <= SLOTS_PER_HISTORICAL_ROOT) {
uint256[] memory blockRootsGindex = new uint256[](2);
blockRootsGindex[0] = BLOCK_ROOTS_GINDEX;
blockRootsGindex[1] = calculateArrayGindex(txSlot % SLOTS_PER_HISTORICAL_ROOT);
uint256[] memory receiptGindexes = new uint256[](3);
receiptGindexes[0] = STATE_ROOT_GINDEX;
receiptGindexes[1] = concatGindices(blockRootsGindex);
receiptGindexes[2] = RECEIPT_ROOT_GINDEX;

// BeaconBlock -> BeaconState -> HistoricalRoots -> BeaconBlock -> BeaconBody -> ExecutionPayload -> ReceiptsRoot
index = concatGindices(receiptGindexes);
} else if (lcSlot - txSlot > SLOTS_PER_HISTORICAL_ROOT) {
revert("txSlot lags by >8192 blocks. Not supported.");
} else {
revert("txSlot can't be greater than lightclient slot");
}

bytes32 computedRoot = restoreMerkleRoot(receiptsRootBranch, receiptsRoot, calculateIndex(index));
return computedRoot == headerRoot;
}

function concatGindices(uint256[] memory gindices) public pure returns (uint256) {
uint256 result = 1; // Start with binary "1"
for (uint i = 0; i < gindices.length; i++) {
uint256 gindex = gindices[i];
uint256 gindexWithoutLeadingOne = gindex & ((1 << (bitLength(gindex) - 1)) - 1);
result = (result << (bitLength(gindex) - 1)) | gindexWithoutLeadingOne;
}
return result;
}

function bitLength(uint256 number) internal pure returns (uint256) {
if (number == 0) {
return 0;
}
uint256 length = 0;
while (number > 0) {
length++;
number >>= 1;
}
return length;
}

function calculateArrayGindex(uint256 elementIndex) internal pure returns (uint256) {
uint256 gindex = 1;
uint256 depth = 0;
while ((1 << depth) < SLOTS_PER_HISTORICAL_ROOT) {
depth++;
}

for (uint256 d = 0; d < depth; d++) {
gindex = (gindex << 1) | ((elementIndex >> (depth - d - 1)) & 1);
}
return gindex;
}

function calculateIndex(uint256 gindex) internal pure returns (uint256 index) {
uint256 depth = floorLog2(gindex);
index = gindex % (2 ** depth);
}

function floorLog2(uint256 x) internal pure returns (uint256) {
require(x > 0, "Input must be greater than zero");
uint256 result = 0;

while (x > 1) {
x >>= 1;
result++;
}

return result;
}
}
10 changes: 10 additions & 0 deletions packages/evm/contracts/adapters/Spectre/mock/MockSpectre.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
// SPDX-License-Identifier: LGPL-3.0-only
pragma solidity ^0.8.20;

contract MockSpectre {
mapping(uint256 => bytes32) public blockHeaderRoots;

function setRoot(uint256 slot, bytes32 root) external {
blockHeaderRoots[slot] = root;
}
}
Loading

0 comments on commit 001f5cd

Please sign in to comment.