generated from PaulRBerg/hardhat-template
-
Notifications
You must be signed in to change notification settings - Fork 52
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge branch 'feat/v0.2.0' into docs/README
- Loading branch information
Showing
5 changed files
with
420 additions
and
0 deletions.
There are no files selected for viewing
81 changes: 81 additions & 0 deletions
81
packages/evm/contracts/adapters/Spectre/SpectreAdapter.sol
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
} | ||
} |
6 changes: 6 additions & 0 deletions
6
packages/evm/contracts/adapters/Spectre/interfaces/ISpectre.sol
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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
10
packages/evm/contracts/adapters/Spectre/mock/MockSpectre.sol
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
} | ||
} |
Oops, something went wrong.