Skip to content

Commit

Permalink
remove l2 oracle storage key from request struct
Browse files Browse the repository at this point in the history
  • Loading branch information
jackchuma committed Dec 5, 2024
1 parent b41d80a commit a6710f0
Show file tree
Hide file tree
Showing 20 changed files with 158 additions and 212 deletions.
2 changes: 1 addition & 1 deletion contracts/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

.PHONY: test

ARBITRUM_REQUEST_HASH = 0x2beaf8ca6c94a43515767d5bb58ac039c6800fcc9b68ae24215941ccfb2866f2
ARBITRUM_REQUEST_HASH = 0x77f78ea01ff97a90675ede86106eb2ddb8f4db0cc7d575bef24272edabf51beb
OPTIMISM_REQUEST_HASH = 0xe38ad8c9e84178325f28799eb3aaae72551b2eea7920c43d88854edd350719f5
FULFILLER_ADDRESS = 0x23214A0864FC0014CAb6030267738F01AFfdd547
MOCK_VERIFIER_ADDRESS = 0x49E2cDC9e81825B6C718ae8244fe0D5b062F4874
Expand Down
1 change: 0 additions & 1 deletion contracts/script/actions/SubmitRequest.s.sol
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,6 @@ contract SubmitRequest is Script {
destinationChainId: 11155420,
inboxContract: 0x49E2cDC9e81825B6C718ae8244fe0D5b062F4874.addressToBytes32(), // RIP7755Inbox on Optimism Sepolia
l2Oracle: 0x218CD9489199F321E1177b56385d333c5B598629.addressToBytes32(), // Anchor State Registry on Sepolia
l2OracleStorageKey: 0xa6eef7e35abe7026729641147f7915573c7e97b47efa546f5f6e3230263bcb49, // Anchor State Registry storage slot
rewardAsset: 0x2e234DAe75C793f67A35089C9d99245E1C58470b.addressToBytes32(),
rewardAmount: 1 ether,
finalityDelaySeconds: 10,
Expand Down
1 change: 0 additions & 1 deletion contracts/script/actions/SubmitToInbox.s.sol
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,6 @@ contract SubmitToInbox is Script {
destinationChainId: 111112,
inboxContract: 0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512.addressToBytes32(),
l2Oracle: bytes32(0),
l2OracleStorageKey: bytes32(0),
rewardAsset: 0x000000000000000000000000f62849f9a0b5bf2913b396098f7c7019b51a820a,
rewardAmount: 1 ether,
finalityDelaySeconds: 10,
Expand Down
2 changes: 0 additions & 2 deletions contracts/src/RIP7755Structs.sol
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,6 @@ struct CrossChainRequest {
bytes32 inboxContract;
/// @dev The L1 address of the contract that should have L2 block info stored
bytes32 l2Oracle;
/// @dev The storage key at which we expect to find the L2 block info on the l2Oracle
bytes32 l2OracleStorageKey;
/// @dev The address of the ERC20 reward asset to be paid to whoever proves they filled this call
/// @dev Native asset specified as in ERC-7528 format
bytes32 rewardAsset;
Expand Down
19 changes: 8 additions & 11 deletions contracts/src/libraries/provers/ArbitrumProver.sol
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,10 @@ library ArbitrumProver {
struct Target {
/// @dev The address of the L1 contract to validate. Should be Arbitrum's Rollup contract
address l1Address;
/// @dev The storage key on L1 to validate
bytes32 l1StorageKey;
/// @dev The address of the L2 contract to validate.
address l2Address;
/// @dev The storage key on L2 to validate.
bytes32 l2StorageKey;
bytes l2StorageKey;
}

/// @notice Parameters needed for a full nested cross-L2 storage proof with Arbitrum as the destination chain
Expand All @@ -45,6 +43,9 @@ library ArbitrumProver {
/// @notice The storage slot offset of the `confirmData` field in an Arbitrum RBlock
uint256 private constant _ARBITRUM_RBLOCK_CONFIRMDATA_STORAGE_OFFSET = 2;

/// @notice The storage key on L1 to validate
bytes32 private constant _L1_STORAGE_KEY = 0x0000000000000000000000000000000000000000000000000000000000000076;

/// @notice This error is thrown when verification of the authenticity of the l2Oracle for the destination L2 chain
/// on Eth mainnet fails
error InvalidStateRoot();
Expand All @@ -69,13 +70,13 @@ library ArbitrumProver {
RIP7755Proof memory proofData = abi.decode(proof, (RIP7755Proof));

// Set the expected storage key and value for the destination L2 storage slot
proofData.dstL2AccountProofParams.storageKey = abi.encode(target.l2StorageKey);
proofData.dstL2AccountProofParams.storageKey = target.l2StorageKey;

// Derive the L1 storage key to use in the storage proof. For Arbitrum, we will use the storage slot containing
// the `confirmData` field in a posted RBlock
// See https://github.com/OffchainLabs/nitro-contracts/blob/main/src/rollup/Node.sol#L21 for the RBlock structure
// See https://github.com/OffchainLabs/nitro-contracts/blob/main/src/rollup/RollupCore.sol#L64 for the mapping location
proofData.dstL2StateRootProofParams.storageKey = _deriveL1StorageKey(proofData, target.l1StorageKey);
proofData.dstL2StateRootProofParams.storageKey = _deriveL1StorageKey(proofData);

// We first need to validate knowledge of the destination L2 chain's state root.
// StateValidator.validateState will accomplish each of the following 4 steps:
Expand Down Expand Up @@ -120,12 +121,8 @@ library ArbitrumProver {
}

/// @notice Derives the L1 storageKey using the supplied `nodeIndex` and the `confirmData` storage slot offset
function _deriveL1StorageKey(RIP7755Proof memory proofData, bytes32 l1StorageKey)
private
pure
returns (bytes memory)
{
uint256 startingStorageSlot = uint256(keccak256(abi.encode(proofData.nodeIndex, l1StorageKey)));
function _deriveL1StorageKey(RIP7755Proof memory proofData) private pure returns (bytes memory) {
uint256 startingStorageSlot = uint256(keccak256(abi.encode(proofData.nodeIndex, _L1_STORAGE_KEY)));
return abi.encodePacked(startingStorageSlot + _ARBITRUM_RBLOCK_CONFIRMDATA_STORAGE_OFFSET);
}
}
69 changes: 15 additions & 54 deletions contracts/src/libraries/provers/HashiProver.sol
Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.24;

import {RLPReader} from "optimism/packages/contracts-bedrock/src/libraries/rlp/RLPReader.sol";
import {StateValidator} from "../StateValidator.sol";
import {RIP7755Inbox} from "../../RIP7755Inbox.sol";
import {CrossChainRequest} from "../../RIP7755Structs.sol";
import {IShoyuBashi} from "../../interfaces/IShoyuBashi.sol";
import {BlockHeaders} from "../BlockHeaders.sol";
import {StateValidator} from "../StateValidator.sol";

/// @title HashiProver
///
Expand All @@ -14,19 +12,14 @@ import {IShoyuBashi} from "../../interfaces/IShoyuBashi.sol";
/// @notice This is a utility library for validating storage proofs using Hashi's block headers.
library HashiProver {
using StateValidator for address;
using RLPReader for RLPReader.RLPItem;
using RLPReader for bytes;

/// @notice The minimum block fields length
uint256 private constant MINIMUM_BLOCK_FIELDS_LENGTH = 9;
using BlockHeaders for bytes;

/// @notice The address and storage keys to validate on L1 and L2
struct Target {
/// @dev The address of the contract to validate. Should be Hashi's `RIP7755Inbox` contract
/// @dev The address of the destination contract to validate
address addr;
/// @dev The storage key on to validate. Should be the `RIP7755Inbox` storage slot containing the
/// `FulfillmentInfo` struct
bytes32 storageKey;
/// @dev The storage key on the destination contract to validate
bytes storageKey;
/// @dev The ID of the destination chain where the validation is expected to occur
uint256 destinationChainId;
/// @dev The address of the Shoyu Bashi contract
Expand All @@ -37,31 +30,21 @@ library HashiProver {
struct RIP7755Proof {
/// @dev The RLP-encoded block from which we want to retrieve its hash from Hashi
bytes rlpEncodedBlockHeader;
/// @dev Parameters needed to validate the authenticity of a specified storage location in `RIP7755Inbox` on
/// the destination chain
/// @dev Parameters needed to validate the authenticity of a specified storage location on the destination chain
StateValidator.AccountProofParameters dstAccountProofParams;
}

/// @notice This error is thrown when the number of bytes to convert into an uin256 is greather than 32
error BytesLengthExceeds32();

/// @notice This error is thrown when verification of proof.blockHash agaist the one stored in Hashi fails
error InvalidBlockHeader();

/// @notice This error is thrown when the block fields length is less than MINIMUM_BLOCK_FIELDS_LENGTH
error InvalidBlockFieldsLength();

/// @notice This error is thrown when verification of the authenticity of the `RIP7755Inbox` storage on the
/// destination chain fails
error InvalidStorage();

/// @notice Validates storage proofs and verifies fulfillment
///
/// @custom:reverts If RLP-encoded block header does not correspond to the block hash stored in Hashi
/// @custom:reverts If storage proof invalid.
/// @custom:reverts If fulfillmentInfo not found at verifyingContractStorageKey on request.verifyingContract
/// @custom:reverts If fulfillmentInfo.timestamp is less than request.finalityDelaySeconds from current destination
/// chain block timestamp.
/// @custom:reverts If the L2StorageRoot does not correspond to our validated L1 storage slot
///
/// @param proof The proof to validate
/// @param target The proof target on L1 and dst L2
Expand All @@ -71,45 +54,23 @@ library HashiProver {
function validate(bytes calldata proof, Target memory target) internal view returns (uint256, bytes memory) {
RIP7755Proof memory proofData = abi.decode(proof, (RIP7755Proof));

// Set the expected storage key for the `RIP7755Inbox`
proofData.dstAccountProofParams.storageKey = abi.encode(target.storageKey);
proofData.dstAccountProofParams.storageKey = target.storageKey;

(bytes32 stateRoot, uint256 blockNumber, uint256 timestamp) =
_extractStateRootBlockNumberAndTimestamp(proofData.rlpEncodedBlockHeader);
proofData.rlpEncodedBlockHeader.extractStateRootBlockNumberAndTimestamp();
bytes32 blockHeaderHash =
IShoyuBashi(target.shoyuBashi).getThresholdHash(target.destinationChainId, blockNumber);
if (blockHeaderHash != keccak256(proofData.rlpEncodedBlockHeader)) revert InvalidBlockHeader();

if (blockHeaderHash != proofData.rlpEncodedBlockHeader.toBlockHash()) {
revert InvalidBlockHeader();
}

bool validStorage = target.addr.validateAccountStorage(stateRoot, proofData.dstAccountProofParams);

if (!validStorage) {
revert InvalidStorage();
}

return (timestamp, proofData.dstAccountProofParams.storageValue);
}

/// @notice Extracts the stateRoot, blockNumber and timestamp from the RLP-encoded block headers array
///
/// @custom:reverts If the encoded block array has less than 9 elements
///
/// @dev The stateRoot should be the 4th element, the blockNumber the 9th and the timestamp should be the 12th element
function _extractStateRootBlockNumberAndTimestamp(bytes memory encodedBlockArray)
private
pure
returns (bytes32, uint256, uint256)
{
RLPReader.RLPItem[] memory blockFields = encodedBlockArray.readList();
if (blockFields.length < MINIMUM_BLOCK_FIELDS_LENGTH) revert InvalidBlockFieldsLength();
return (
bytes32(blockFields[3].readBytes()),
_bytesToUint256(blockFields[8].readBytes()),
uint256(bytes32(blockFields[11].readBytes()))
);
}

/// @notice Converts a sequence of bytes into an uint256
function _bytesToUint256(bytes memory b) private pure returns (uint256) {
if (b.length > 32) revert BytesLengthExceeds32();
return abi.decode(abi.encodePacked(new bytes(32 - b.length), b), (uint256));
}
}
12 changes: 7 additions & 5 deletions contracts/src/libraries/provers/OPStackProver.sol
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,10 @@ library OPStackProver {
struct Target {
/// @dev The address of the L1 contract to validate. Should be Optimism's AnchorStateRegistry contract
address l1Address;
/// @dev The storage key on L1 to validate
bytes32 l1StorageKey;
/// @dev The address of the L2 contract to validate.
address l2Address;
/// @dev The storage key on L2 to validate.
bytes32 l2StorageKey;
bytes l2StorageKey;
}

/// @notice Parameters needed for a full nested cross-L2 storage proof
Expand All @@ -40,6 +38,10 @@ library OPStackProver {
StateValidator.AccountProofParameters dstL2AccountProofParams;
}

/// @notice The storage key on L1 to validate
bytes private constant _L1_STORAGE_KEY =
abi.encode(0xa6eef7e35abe7026729641147f7915573c7e97b47efa546f5f6e3230263bcb49);

/// @notice This error is thrown when verification of the authenticity of the l2Oracle for the destination L2 chain
/// on Eth mainnet fails
error InvalidL1Storage();
Expand All @@ -66,9 +68,9 @@ library OPStackProver {
RIP7755Proof memory proofData = abi.decode(proof, (RIP7755Proof));

// Set the expected storage key for the L1 storage slot
proofData.dstL2StateRootProofParams.storageKey = abi.encode(target.l1StorageKey);
proofData.dstL2StateRootProofParams.storageKey = _L1_STORAGE_KEY;
// Set the expected storage key for the destination L2 storage slot
proofData.dstL2AccountProofParams.storageKey = abi.encode(target.l2StorageKey);
proofData.dstL2AccountProofParams.storageKey = target.l2StorageKey;

// We first need to validate knowledge of the destination L2 chain's state root.
// StateValidator.validateState will accomplish each of the following 4 steps:
Expand Down
3 changes: 1 addition & 2 deletions contracts/src/outboxes/RIP7755OutboxToArbitrum.sol
Original file line number Diff line number Diff line change
Expand Up @@ -39,9 +39,8 @@ contract RIP7755OutboxToArbitrum is RIP7755Outbox {
) internal view override {
ArbitrumProver.Target memory target = ArbitrumProver.Target({
l1Address: request.l2Oracle.bytes32ToAddress(),
l1StorageKey: request.l2OracleStorageKey,
l2Address: request.inboxContract.bytes32ToAddress(),
l2StorageKey: bytes32(inboxContractStorageKey)
l2StorageKey: inboxContractStorageKey
});
(uint256 l2Timestamp, bytes memory inboxContractStorageValue) = proof.validate(target);

Expand Down
2 changes: 1 addition & 1 deletion contracts/src/outboxes/RIP7755OutboxToHashi.sol
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ contract RIP7755OutboxToHashi is RIP7755Outbox {
(address shoyuBashi) = abi.decode(request.extraData[1], (address));
HashiProver.Target memory target = HashiProver.Target({
addr: request.inboxContract.bytes32ToAddress(),
storageKey: bytes32(inboxContractStorageKey),
storageKey: inboxContractStorageKey,
destinationChainId: request.destinationChainId,
shoyuBashi: shoyuBashi
});
Expand Down
7 changes: 1 addition & 6 deletions contracts/src/outboxes/RIP7755OutboxToOPStack.sol
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,8 @@ contract RIP7755OutboxToOPStack is RIP7755Outbox {
/// @notice Validates storage proofs and verifies fulfillment
///
/// @custom:reverts If storage proof invalid.
/// @custom:reverts If fulfillmentInfo not found at inboxContractStorageKey on request.inboxContract
/// @custom:reverts If fulfillmentInfo.timestamp is less than request.finalityDelaySeconds from current destination
/// chain block timestamp.
/// @custom:reverts If the L2StateRoot does not correspond to the validated L1 storage slot
///
/// @dev Implementation will vary by L2
///
/// @param inboxContractStorageKey The storage location of the data to verify on the destination chain
/// `RIP7755Inbox` contract
Expand All @@ -41,9 +37,8 @@ contract RIP7755OutboxToOPStack is RIP7755Outbox {
) internal view override {
OPStackProver.Target memory target = OPStackProver.Target({
l1Address: request.l2Oracle.bytes32ToAddress(),
l1StorageKey: request.l2OracleStorageKey,
l2Address: request.inboxContract.bytes32ToAddress(),
l2StorageKey: bytes32(inboxContractStorageKey)
l2StorageKey: inboxContractStorageKey
});
(uint256 l2Timestamp, bytes memory inboxContractStorageValue) = proof.validate(target);

Expand Down
1 change: 0 additions & 1 deletion contracts/test/ArbitrumProver.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -168,7 +168,6 @@ contract ArbitrumProverTest is Test {
destinationChainId: 421614, // arbitrum sepolia chain ID
inboxContract: 0x49E2cDC9e81825B6C718ae8244fe0D5b062F4874.addressToBytes32(), // RIP7755Inbox on Arbitrum Sepolia
l2Oracle: 0xd80810638dbDF9081b72C1B33c65375e807281C8.addressToBytes32(), // Arbitrum Rollup on Sepolia
l2OracleStorageKey: bytes32(uint256(118)), // Arbitrum Rollup _nodes storage slot
rewardAsset: address(mockErc20).addressToBytes32(),
rewardAmount: rewardAmount,
finalityDelaySeconds: 10,
Expand Down
5 changes: 2 additions & 3 deletions contracts/test/HashiProver.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ contract HashiProverTest is Test {
CrossChainRequest memory request = _initRequest(_REWARD_AMOUNT);
HashiProver.RIP7755Proof memory proof = _buildProof(validProof);

(, uint256 blockNumber, ) = proof.rlpEncodedBlockHeader.extractStateRootBlockNumberAndTimestamp();
(, uint256 blockNumber,) = proof.rlpEncodedBlockHeader.extractStateRootBlockNumberAndTimestamp();

bytes32 wrongBlockHeaderHash = bytes32(uint256(0));
shoyuBashi.setHash(HASHI_DOMAIN_DST_CHAIN_ID, blockNumber, wrongBlockHeaderHash);
Expand Down Expand Up @@ -113,7 +113,7 @@ contract HashiProverTest is Test {
});

bytes memory rlpEncodedBlockHeader = json.readBytes(".rlpEncodedBlockHeader");
(, uint256 blockNumber, ) = rlpEncodedBlockHeader.extractStateRootBlockNumberAndTimestamp();
(, uint256 blockNumber,) = rlpEncodedBlockHeader.extractStateRootBlockNumberAndTimestamp();

shoyuBashi.setHash(HASHI_DOMAIN_DST_CHAIN_ID, blockNumber, rlpEncodedBlockHeader.toBlockHash());

Expand All @@ -136,7 +136,6 @@ contract HashiProverTest is Test {
destinationChainId: HASHI_DOMAIN_DST_CHAIN_ID,
inboxContract: 0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512.addressToBytes32(), // RIP7755Inbox on Gnosis Chiado
l2Oracle: address(0).addressToBytes32(), // we don't use any L1 contract
l2OracleStorageKey: bytes32(0), // same as above
rewardAsset: address(mockErc20).addressToBytes32(),
rewardAmount: rewardAmount,
finalityDelaySeconds: 10,
Expand Down
1 change: 0 additions & 1 deletion contracts/test/OPStackProver.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -191,7 +191,6 @@ contract RIP7755OutboxOPStackValidatorTest is Test {
destinationChainId: 111112,
inboxContract: 0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512.addressToBytes32(), // RIP7755Inbox on mock Chain B
l2Oracle: 0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512.addressToBytes32(), // Anchor State Registry on mock L1
l2OracleStorageKey: 0xa6eef7e35abe7026729641147f7915573c7e97b47efa546f5f6e3230263bcb49, // Anchor State Registry storage slot
rewardAsset: 0x2e234DAe75C793f67A35089C9d99245E1C58470b.addressToBytes32(),
rewardAmount: rewardAmount,
finalityDelaySeconds: 10,
Expand Down
1 change: 0 additions & 1 deletion contracts/test/RIP7755Inbox.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -199,7 +199,6 @@ contract RIP7755InboxTest is Test {
destinationChainId: block.chainid,
inboxContract: address(inbox).addressToBytes32(),
l2Oracle: address(0).addressToBytes32(),
l2OracleStorageKey: bytes32(0),
rewardAsset: address(0).addressToBytes32(),
rewardAmount: 0,
finalityDelaySeconds: 0,
Expand Down
1 change: 0 additions & 1 deletion contracts/test/RIP7755Outbox.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -487,7 +487,6 @@ contract RIP7755OutboxTest is Test {
destinationChainId: 0,
inboxContract: address(0).addressToBytes32(),
l2Oracle: address(0).addressToBytes32(),
l2OracleStorageKey: bytes32(0),
rewardAsset: address(mockErc20).addressToBytes32(),
rewardAmount: rewardAmount,
finalityDelaySeconds: 10,
Expand Down
Loading

0 comments on commit a6710f0

Please sign in to comment.