From 807fa568a1b226ccfde391f67a7c43e8077a5734 Mon Sep 17 00:00:00 2001 From: Leo Date: Wed, 30 Apr 2025 11:34:03 +0200 Subject: [PATCH 01/41] remove authorize in the commitment store height --> index --- src/protocol/CommitmentStore.sol | 43 +++++-------------------------- src/protocol/ICommitmentStore.sol | 40 ++++++++++------------------ 2 files changed, 20 insertions(+), 63 deletions(-) diff --git a/src/protocol/CommitmentStore.sol b/src/protocol/CommitmentStore.sol index c0222ef5..c251ab8c 100644 --- a/src/protocol/CommitmentStore.sol +++ b/src/protocol/CommitmentStore.sol @@ -4,51 +4,22 @@ pragma solidity ^0.8.28; import {ICheckpointTracker} from "./ICheckpointTracker.sol"; import {ICommitmentStore} from "./ICommitmentStore.sol"; -import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; import {SafeCast} from "@openzeppelin/contracts/utils/math/SafeCast.sol"; /// @dev Base contract for storing commitments. -abstract contract CommitmentStore is ICommitmentStore, Ownable { +abstract contract CommitmentStore is ICommitmentStore { using SafeCast for uint256; - address private _authorizedCommitter; - - mapping(uint256 height => bytes32 commitment) private _commitments; - - /// @param _rollupOperator The address of the rollup operator - constructor(address _rollupOperator) Ownable(_rollupOperator) {} - - /// @dev Reverts if the caller is not the `authorizedCommitter`. - modifier onlyAuthorizedCommitter() { - _checkAuthorizedCommitter(msg.sender); - _; - } - - /// @inheritdoc ICommitmentStore - function authorizedCommitter() public view virtual returns (address) { - return _authorizedCommitter; - } - - /// @inheritdoc ICommitmentStore - function setAuthorizedCommitter(address newAuthorizedCommitter) external virtual onlyOwner { - require(newAuthorizedCommitter != address(0), EmptyCommitter()); - _authorizedCommitter = newAuthorizedCommitter; - emit AuthorizedCommitterUpdated(newAuthorizedCommitter); - } + mapping(address source => mapping(uint256 index => bytes32 commitment)) private _commitments; /// @inheritdoc ICommitmentStore - function commitmentAt(uint256 height) public view virtual returns (bytes32) { - return _commitments[height]; + function commitmentAt(address source, uint256 index) public view virtual returns (bytes32) { + return _commitments[source][index]; } /// @inheritdoc ICommitmentStore - function storeCommitment(uint256 height, bytes32 commitment) external virtual onlyAuthorizedCommitter { - _commitments[height] = commitment; - emit CommitmentStored(height, commitment); - } - - /// @dev Internal helper to validate the authorizedCommitter. - function _checkAuthorizedCommitter(address caller) internal view { - require(caller == _authorizedCommitter, UnauthorizedCommitter()); + function storeCommitment(uint256 index, bytes32 commitment) external virtual { + _commitments[msg.sender][index] = commitment; + emit CommitmentStored(msg.sender, index, commitment); } } diff --git a/src/protocol/ICommitmentStore.sol b/src/protocol/ICommitmentStore.sol index ad3a5a11..1ecc253d 100644 --- a/src/protocol/ICommitmentStore.sol +++ b/src/protocol/ICommitmentStore.sol @@ -6,36 +6,22 @@ import {ICheckpointTracker} from "./ICheckpointTracker.sol"; /// @dev Stores commitments from different chains. /// /// A commitment is any value (typically a state root) that uniquely identifies the state of a chain at a -/// specific height (i.e. an incremental identifier like a blockNumber, publicationId or even a timestamp). -/// Only an authorized committer can store commitments. For example, only the `CheckpointTracker` can store roots on the -/// L1, -/// and the anchor can store block hashes on the L2. +/// specific index (i.e. an incremental identifier like a blockNumber, publicationId or even a timestamp). +/// +/// There is no access control so only commitments from trusted sources should be used. +/// For example, L2 contracts should use L1 commitments saved by the anchor contract and L1 contracts should use L2 +/// commitments saved by the relevant `CheckpointTracker`. interface ICommitmentStore { - /// @dev A new `commitment` has been stored at a specified `height`. - event CommitmentStored(uint256 indexed height, bytes32 commitment); - - /// @dev Emitted when the authorized committer is updated. - event AuthorizedCommitterUpdated(address newAuthorizedCommitter); - - /// @dev The caller is not a recognized authorized committer. - error UnauthorizedCommitter(); - - /// @dev The trusted committer address is empty. - error EmptyCommitter(); - - /// @dev Returns the current authorized committer. - function authorizedCommitter() external view returns (address); - - /// @dev Sets a new authorized committer. - /// @param newAuthorizedCommitter The new authorized committer address - function setAuthorizedCommitter(address newAuthorizedCommitter) external; + /// @dev A new `commitment` has been stored by `source` at a specified `index`. + event CommitmentStored(address indexed source, uint256 indexed index, bytes32 commitment); - /// @dev Returns the commitment at the given `height`. - /// @param height The height of the commitment - function commitmentAt(uint256 height) external view returns (bytes32 commitment); + /// @dev Returns the commitment at the given `index`. + /// @param source The source address for the saved commitment + /// @param index The index of the commitment + function commitmentAt(address source, uint256 index) external view returns (bytes32 commitment); /// @dev Stores a commitment. - /// @param height The height of the commitment + /// @param index The index of the commitment /// @param commitment The commitment to store - function storeCommitment(uint256 height, bytes32 commitment) external; + function storeCommitment(uint256 index, bytes32 commitment) external; } From 717b414ddac35cbf1cc253a8bd05b37a07731e61 Mon Sep 17 00:00:00 2001 From: Leo Date: Wed, 30 Apr 2025 11:39:44 +0200 Subject: [PATCH 02/41] update to get commitment from a 'trusted' source --- src/protocol/SignalService.sol | 32 +++++++++++++++++--------------- 1 file changed, 17 insertions(+), 15 deletions(-) diff --git a/src/protocol/SignalService.sol b/src/protocol/SignalService.sol index b25d343e..131c36cf 100644 --- a/src/protocol/SignalService.sol +++ b/src/protocol/SignalService.sol @@ -10,16 +10,13 @@ import {ISignalService} from "./ISignalService.sol"; /// /// This contract allows sending arbitrary data as signals via `sendSignal`, verifying signals from other chains using /// `verifySignal`, and bridging native ETH with built-in signal generation and verification. It integrates: -/// - `CommitmentStore` to access trusted state roots, -/// - `ETHBridge` for native ETH bridging with deposit and claim flows, +/// - `CommitmentStore` to access state roots, /// - `LibSignal` for signal hashing, storage, and verification logic. /// /// Signals stored cannot be deleted and can be verified multiple times. -contract SignalService is ISignalService, ETHBridge, CommitmentStore { +contract SignalService is ISignalService, CommitmentStore { using LibSignal for bytes32; - constructor(address _rollupOperator) CommitmentStore(_rollupOperator) {} - /// @inheritdoc ISignalService /// @dev Signals are stored in a namespaced slot derived from the signal value, sender address and SIGNAL_NAMESPACE /// const @@ -36,8 +33,10 @@ contract SignalService is ISignalService, ETHBridge, CommitmentStore { /// @inheritdoc ISignalService /// @dev Cannot be used to verify signals that are under the eth-bridge namespace. - function verifySignal(uint256 height, address sender, bytes32 value, bytes memory proof) external { - _verifySignal(height, sender, value, LibSignal.SIGNAL_NAMESPACE, proof); + function verifySignal(uint256 index, address commitmentPublisher, address sender, bytes32 value, bytes memory proof) + external + { + _verifySignal(index, commitmentPublisher, sender, value, LibSignal.SIGNAL_NAMESPACE, proof); emit SignalVerified(sender, value); } @@ -50,27 +49,30 @@ contract SignalService is ISignalService, ETHBridge, CommitmentStore { // CHECK: Should this function be non-reentrant? /// @inheritdoc ETHBridge /// @dev Overrides ETHBridge.claimDeposit to add signal verification logic. - function claimDeposit(ETHDeposit memory ethDeposit, uint256 height, bytes memory proof) + function claimDeposit(ETHDeposit memory ethDeposit, uint256 index, bytes memory proof) external override returns (bytes32 id) { id = _generateId(ethDeposit); - _verifySignal(height, ethDeposit.from, id, ETH_BRIDGE_NAMESPACE, proof); + _verifySignal(index, commitmentPublisher, ethDeposit.from, id, ETH_BRIDGE_NAMESPACE, proof); super._processClaimDepositWithId(id, ethDeposit); } - function _verifySignal(uint256 height, address sender, bytes32 value, bytes32 namespace, bytes memory proof) - internal - view - virtual - { + function _verifySignal( + uint256 index, + address commitmentPublisher, + address sender, + bytes32 value, + bytes32 namespace, + bytes memory proof + ) internal view virtual { // TODO: commitmentAt(height) might not be the 'state root' of the chain // For now it could be the block hash or other hashed value // further work is needed to ensure we get the 'state root' of the chain - bytes32 root = commitmentAt(height); + bytes32 root = commitmentAt(commitmentPublisher, index); SignalProof memory signalProof = abi.decode(proof, (SignalProof)); bytes[] memory accountProof = signalProof.accountProof; bytes[] memory storageProof = signalProof.storageProof; From 82cf72903ca4f10a41e57e62faf4516c827e8877 Mon Sep 17 00:00:00 2001 From: Leo Date: Wed, 30 Apr 2025 11:55:24 +0200 Subject: [PATCH 03/41] allow genesis to start at the latest publication id --- src/protocol/CheckpointTracker.sol | 43 ++++++++++++++++------------- src/protocol/ICheckpointTracker.sol | 9 ++++-- 2 files changed, 31 insertions(+), 21 deletions(-) diff --git a/src/protocol/CheckpointTracker.sol b/src/protocol/CheckpointTracker.sol index 73b3701a..6fe45142 100644 --- a/src/protocol/CheckpointTracker.sol +++ b/src/protocol/CheckpointTracker.sol @@ -7,14 +7,10 @@ import {IPublicationFeed} from "./IPublicationFeed.sol"; import {IVerifier} from "./IVerifier.sol"; contract CheckpointTracker is ICheckpointTracker { - /// @notice The current proven checkpoint representing the latest verified state of the rollup - /// @dev Previous checkpoints are not stored here but are synchronized to the `SignalService` + /// @notice The publication id of the current proven checkpoint representing the latest verified state of the rollup /// @dev A checkpoint commitment is any value (typically a state root) that uniquely identifies /// the state of the rollup at a specific point in time - /// @dev We store the actual checkpoint(not the hash) to avoid race conditions when closing a period or evicting a - /// prover(https://github.com/OpenZeppelin/minimal-rollup/pull/77#discussion_r2002192018) - - Checkpoint private _provenCheckpoint; + uint256 _provenPublicationId; IPublicationFeed public immutable publicationFeed; IVerifier public immutable verifier; @@ -33,16 +29,21 @@ contract CheckpointTracker is ICheckpointTracker { address _proverManager, address _commitmentStore ) { - // set the genesis checkpoint commitment of the rollup - genesis is trusted to be correct - require(_genesis != 0, "genesis checkpoint commitment cannot be 0"); - publicationFeed = IPublicationFeed(_publicationFeed); + if (_genesis != 0) { + uint256 latestPublicationId = publicationFeed.getNextPublicationId() - 1; + require( + _genesis == publicationFeed.getPublicationHash(latestPublicationId), + GenesisNotLatestPublication(latestPublicationId) + ); + _updateCheckpoint(latestPublicationId, _genesis); + } else { + _updateCheckpoint(0, _genesis); + } + verifier = IVerifier(_verifier); commitmentStore = ICommitmentStore(_commitmentStore); proverManager = _proverManager; - Checkpoint memory genesisCheckpoint = Checkpoint({publicationId: 0, commitment: _genesis}); - _provenCheckpoint = genesisCheckpoint; - emit CheckpointUpdated(genesisCheckpoint); } /// @inheritdoc ICheckpointTracker @@ -58,8 +59,9 @@ contract CheckpointTracker is ICheckpointTracker { require(end.commitment != 0, "Checkpoint commitment cannot be 0"); + Checkpoint memory provenCheckpoint = getProvenCheckpoint(); require( - start.publicationId == _provenCheckpoint.publicationId && start.commitment == _provenCheckpoint.commitment, + start.publicationId == provenCheckpoint.publicationId && start.commitment == provenCheckpoint.commitment, "Start checkpoint must be the latest proven checkpoint" ); @@ -73,14 +75,17 @@ contract CheckpointTracker is ICheckpointTracker { startPublicationHash, endPublicationHash, start.commitment, end.commitment, numPublications, proof ); - _provenCheckpoint = end; - emit CheckpointUpdated(end); + _updateCheckpoint(end.publicationId, end.commitment); + } - // Stores the state of the other chain - commitmentStore.storeCommitment(end.publicationId, end.commitment); + function getProvenCheckpoint() public view returns (Checkpoint memory provenCheckpoint) { + provenCheckpoint.publicationId = _provenPublicationId; + provenCheckpoint.commitment = commitmentStore.commitmentAt(address(this), provenCheckpoint.publicationId); } - function getProvenCheckpoint() external view returns (Checkpoint memory) { - return _provenCheckpoint; + function _updateCheckpoint(uint256 publicationId, bytes32 commitment) internal { + _provenPublicationId = publicationId; + commitmentStore.storeCommitment(publicationId, commitment); + emit CheckpointUpdated(publicationId, commitment); } } diff --git a/src/protocol/ICheckpointTracker.sol b/src/protocol/ICheckpointTracker.sol index c65e8b0b..644613e8 100644 --- a/src/protocol/ICheckpointTracker.sol +++ b/src/protocol/ICheckpointTracker.sol @@ -8,8 +8,13 @@ interface ICheckpointTracker { } /// @notice Emitted when the proven checkpoint is updated - /// @param latestCheckpoint the latest proven checkpoint - event CheckpointUpdated(Checkpoint latestCheckpoint); + /// @param publicationId the publication ID of the latest proven checkpoint + /// @param commitment the commitment of the latest proven checkpoint + event CheckpointUpdated(uint256 publicationId, bytes32 commitment); + + /// @dev If genesis is not 0 it should be the latest publication + /// @param latestPublicationId the current latest publication ID + error GenesisNotLatestPublication(uint256 latestPublicationId); /// @return _ The last proven checkpoint function getProvenCheckpoint() external view returns (Checkpoint memory); From aa153b433b7741375c61ba73f9838eff4a556a1a Mon Sep 17 00:00:00 2001 From: Leo Date: Wed, 30 Apr 2025 12:23:25 +0200 Subject: [PATCH 04/41] dissallow state proofs when verifying signal --- src/protocol/CommitmentStore.sol | 12 +++++------ src/protocol/ICommitmentStore.sol | 16 +++++++-------- src/protocol/ISignalService.sol | 3 +++ src/protocol/SignalService.sol | 23 ++++++++++++++-------- src/protocol/taiko_alethia/TaikoAnchor.sol | 6 ++++-- 5 files changed, 36 insertions(+), 24 deletions(-) diff --git a/src/protocol/CommitmentStore.sol b/src/protocol/CommitmentStore.sol index c251ab8c..1541f383 100644 --- a/src/protocol/CommitmentStore.sol +++ b/src/protocol/CommitmentStore.sol @@ -10,16 +10,16 @@ import {SafeCast} from "@openzeppelin/contracts/utils/math/SafeCast.sol"; abstract contract CommitmentStore is ICommitmentStore { using SafeCast for uint256; - mapping(address source => mapping(uint256 index => bytes32 commitment)) private _commitments; + mapping(address source => mapping(uint256 height => bytes32 commitment)) private _commitments; /// @inheritdoc ICommitmentStore - function commitmentAt(address source, uint256 index) public view virtual returns (bytes32) { - return _commitments[source][index]; + function commitmentAt(address source, uint256 height) public view virtual returns (bytes32) { + return _commitments[source][height]; } /// @inheritdoc ICommitmentStore - function storeCommitment(uint256 index, bytes32 commitment) external virtual { - _commitments[msg.sender][index] = commitment; - emit CommitmentStored(msg.sender, index, commitment); + function storeCommitment(uint256 height, bytes32 commitment) external virtual { + _commitments[msg.sender][height] = commitment; + emit CommitmentStored(msg.sender, height, commitment); } } diff --git a/src/protocol/ICommitmentStore.sol b/src/protocol/ICommitmentStore.sol index 1ecc253d..017b8505 100644 --- a/src/protocol/ICommitmentStore.sol +++ b/src/protocol/ICommitmentStore.sol @@ -6,22 +6,22 @@ import {ICheckpointTracker} from "./ICheckpointTracker.sol"; /// @dev Stores commitments from different chains. /// /// A commitment is any value (typically a state root) that uniquely identifies the state of a chain at a -/// specific index (i.e. an incremental identifier like a blockNumber, publicationId or even a timestamp). +/// specific height (i.e. an incremental identifier like a blockNumber, publicationId or even a timestamp). /// /// There is no access control so only commitments from trusted sources should be used. /// For example, L2 contracts should use L1 commitments saved by the anchor contract and L1 contracts should use L2 /// commitments saved by the relevant `CheckpointTracker`. interface ICommitmentStore { - /// @dev A new `commitment` has been stored by `source` at a specified `index`. - event CommitmentStored(address indexed source, uint256 indexed index, bytes32 commitment); + /// @dev A new `commitment` has been stored by `source` at a specified `height`. + event CommitmentStored(address indexed source, uint256 indexed height, bytes32 commitment); - /// @dev Returns the commitment at the given `index`. + /// @dev Returns the commitment at the given `height`. /// @param source The source address for the saved commitment - /// @param index The index of the commitment - function commitmentAt(address source, uint256 index) external view returns (bytes32 commitment); + /// @param height The height of the commitment + function commitmentAt(address source, uint256 height) external view returns (bytes32 commitment); /// @dev Stores a commitment. - /// @param index The index of the commitment + /// @param height The height of the commitment /// @param commitment The commitment to store - function storeCommitment(uint256 index, bytes32 commitment) external; + function storeCommitment(uint256 height, bytes32 commitment) external; } diff --git a/src/protocol/ISignalService.sol b/src/protocol/ISignalService.sol index 1e60ef5a..667d4492 100644 --- a/src/protocol/ISignalService.sol +++ b/src/protocol/ISignalService.sol @@ -25,6 +25,9 @@ interface ISignalService { /// @param value Value that was signaled event SignalVerified(address indexed sender, bytes32 value); + /// @dev We require a storage proof to be submitted + error StateProofNotSupported(); + /// @dev Stores a data signal and returns its storage location. /// @param value Data to be stored (signalled) function sendSignal(bytes32 value) external returns (bytes32 slot); diff --git a/src/protocol/SignalService.sol b/src/protocol/SignalService.sol index 131c36cf..5e4c52b6 100644 --- a/src/protocol/SignalService.sol +++ b/src/protocol/SignalService.sol @@ -33,10 +33,14 @@ contract SignalService is ISignalService, CommitmentStore { /// @inheritdoc ISignalService /// @dev Cannot be used to verify signals that are under the eth-bridge namespace. - function verifySignal(uint256 index, address commitmentPublisher, address sender, bytes32 value, bytes memory proof) - external - { - _verifySignal(index, commitmentPublisher, sender, value, LibSignal.SIGNAL_NAMESPACE, proof); + function verifySignal( + uint256 height, + address commitmentPublisher, + address sender, + bytes32 value, + bytes memory proof + ) external { + _verifySignal(height, commitmentPublisher, sender, value, LibSignal.SIGNAL_NAMESPACE, proof); emit SignalVerified(sender, value); } @@ -49,20 +53,20 @@ contract SignalService is ISignalService, CommitmentStore { // CHECK: Should this function be non-reentrant? /// @inheritdoc ETHBridge /// @dev Overrides ETHBridge.claimDeposit to add signal verification logic. - function claimDeposit(ETHDeposit memory ethDeposit, uint256 index, bytes memory proof) + function claimDeposit(ETHDeposit memory ethDeposit, uint256 height, bytes memory proof) external override returns (bytes32 id) { id = _generateId(ethDeposit); - _verifySignal(index, commitmentPublisher, ethDeposit.from, id, ETH_BRIDGE_NAMESPACE, proof); + _verifySignal(height, commitmentPublisher, ethDeposit.from, id, ETH_BRIDGE_NAMESPACE, proof); super._processClaimDepositWithId(id, ethDeposit); } function _verifySignal( - uint256 index, + uint256 height, address commitmentPublisher, address sender, bytes32 value, @@ -72,10 +76,13 @@ contract SignalService is ISignalService, CommitmentStore { // TODO: commitmentAt(height) might not be the 'state root' of the chain // For now it could be the block hash or other hashed value // further work is needed to ensure we get the 'state root' of the chain - bytes32 root = commitmentAt(commitmentPublisher, index); + bytes32 root = commitmentAt(commitmentPublisher, height); SignalProof memory signalProof = abi.decode(proof, (SignalProof)); bytes[] memory accountProof = signalProof.accountProof; bytes[] memory storageProof = signalProof.storageProof; + // if there is no account proof, verify signal will treat root as a storage root + // instead of a full state root which we currently do not support + require(accountProof.length != 0, StateProofNotSupported()); value.verifySignal(namespace, sender, root, accountProof, storageProof); } } diff --git a/src/protocol/taiko_alethia/TaikoAnchor.sol b/src/protocol/taiko_alethia/TaikoAnchor.sol index f0974785..18980d7d 100644 --- a/src/protocol/taiko_alethia/TaikoAnchor.sol +++ b/src/protocol/taiko_alethia/TaikoAnchor.sol @@ -15,7 +15,6 @@ contract TaikoAnchor { uint256 public lastPublicationId; bytes32 public circularBlocksHash; mapping(uint256 blockId => bytes32 blockHash) public blockHashes; - mapping(uint256 blockId => bytes32 blockHash) public l1BlockHashes; modifier onlyFromPermittedSender() { require(msg.sender == permittedSender, "sender not golden touch"); @@ -61,7 +60,6 @@ contract TaikoAnchor { // Persist anchor block hashes if (_anchorBlockId > lastAnchorBlockId) { lastAnchorBlockId = _anchorBlockId; - l1BlockHashes[_anchorBlockId] = _anchorBlockHash; // Stores the state of the other chain commitmentStore.storeCommitment(_anchorBlockId, _anchorBlockHash); } @@ -80,6 +78,10 @@ contract TaikoAnchor { emit Anchor(_publicationId, _anchorBlockId, _anchorBlockHash, _parentGasUsed); } + function l1BlockHashes(uint256 blockId) external view returns (bytes32 blockHash) { + return commitmentStore.commitmentAt(address(this), blockId); + } + /// @dev Calculates the aggregated ancestor block hash for the given block ID /// It uses a ring buffer of 255 bytes32 to store the previous 255 block hashes and the current chain ID /// @param _blockId The ID of the block for which the public input hash is calculated From f025453d13d91eaf8dc2b90216c00a0998b3b110 Mon Sep 17 00:00:00 2001 From: Leo Date: Wed, 30 Apr 2025 12:39:39 +0200 Subject: [PATCH 05/41] start --- src/libs/LibSignal.sol | 38 +++++++++++---------------------- src/protocol/ETHBridge.sol | 30 ++++++++++++++++---------- src/protocol/ISignalService.sol | 9 +++++++- src/protocol/SignalService.sol | 33 ++++++++++------------------ 4 files changed, 51 insertions(+), 59 deletions(-) diff --git a/src/libs/LibSignal.sol b/src/libs/LibSignal.sol index 3d0e7db9..c48fbc67 100644 --- a/src/libs/LibSignal.sol +++ b/src/libs/LibSignal.sol @@ -12,50 +12,41 @@ library LibSignal { using SlotDerivation for string; using SafeCast for uint256; - bytes32 constant SIGNAL_NAMESPACE = keccak256("generic-signal"); - /// @dev A `value` was signaled at a namespaced slot for the current `msg.sender`. function signaled(bytes32 value) internal view returns (bool) { - return signaled(value, SIGNAL_NAMESPACE); - } - - /// @dev A `value` was signaled at a namespaced slot for the current `msg.sender` and - /// namespace. - function signaled(bytes32 value, bytes32 namespace) internal view returns (bool) { - return signaled(value, msg.sender, namespace); + return signaled(value, msg.sender); } /// @dev A `value` was signaled at a namespaced slot. See `deriveSlot`. - function signaled(bytes32 value, address account, bytes32 namespace) internal view returns (bool) { - bytes32 slot = deriveSlot(value, account, namespace); + function signaled(bytes32 value, address account) internal view returns (bool) { + bytes32 slot = deriveSlot(value, account); return slot.getBooleanSlot().value == true; } /// @dev Signal a `value` at a namespaced slot for the current `msg.sender` and namespace. function signal(bytes32 value) internal returns (bytes32) { - return signal(value, msg.sender, SIGNAL_NAMESPACE); + return signal(value, msg.sender); } /// @dev Signal a `value` at a namespaced slot. See `deriveSlot`. - function signal(bytes32 value, address account, bytes32 namespace) internal returns (bytes32) { - bytes32 slot = deriveSlot(value, account, namespace); + function signal(bytes32 value, address account) internal returns (bytes32) { + bytes32 slot = deriveSlot(value, account); slot.getBooleanSlot().value = true; return slot; } - /// @dev Returns the storage slot for a signal. Namespaced to the msg.sender, value and namespace. - function deriveSlot(bytes32 value, bytes32 namespace) internal view returns (bytes32) { - return deriveSlot(value, msg.sender, namespace); + /// @dev Returns the storage slot for a signal. Namespaced to the msg.sender and value + function deriveSlot(bytes32 value) internal view returns (bytes32) { + return deriveSlot(value, msg.sender); } - /// @dev Returns the storage slot for a signal. Namespaced to the current account and value and namespace. - function deriveSlot(bytes32 value, address account, bytes32 namespace) internal pure returns (bytes32) { - return string(abi.encodePacked(value, account, namespace)).erc7201Slot(); + /// @dev Returns the storage slot for a signal. Namespaced to the current account and value. + function deriveSlot(bytes32 value, address account) internal pure returns (bytes32) { + return string(abi.encodePacked(value, account)).erc7201Slot(); } /// @dev Performs a storage proof verification for a signal stored on the contract using this library /// @param value The signal value to verify - /// @param namespace The namespace of the signal /// @param sender The address that originally sent the signal on the source chain /// @param root The state root or storage root from the source chain to verify against /// @param accountProof Merkle proof for the contract's account against the state root. Empty if we are using a @@ -63,15 +54,12 @@ library LibSignal { /// @param storageProof Merkle proof for the derived storage slot against the account's storage root function verifySignal( bytes32 value, - bytes32 namespace, address sender, bytes32 root, bytes[] memory accountProof, bytes[] memory storageProof ) internal pure { bytes32 encodedBool = bytes32(uint256(1)); - LibTrieProof.verifyMerkleProof( - root, sender, deriveSlot(value, sender, namespace), encodedBool, accountProof, storageProof - ); + LibTrieProof.verifyMerkleProof(root, sender, deriveSlot(value, sender), encodedBool, accountProof, storageProof); } } diff --git a/src/protocol/ETHBridge.sol b/src/protocol/ETHBridge.sol index ba57634a..18cb5201 100644 --- a/src/protocol/ETHBridge.sol +++ b/src/protocol/ETHBridge.sol @@ -2,31 +2,36 @@ pragma solidity ^0.8.28; import {IETHBridge} from "./IETHBridge.sol"; +import {ISignalService} from "./ISignalService.sol"; /// @dev Abstract ETH bridging contract to send native ETH between L1 <-> L2 using storage proofs. /// /// IMPORTANT: No recovery mechanism is implemented in case an account creates a deposit that can't be claimed. -abstract contract ETHBridge is IETHBridge { +contract ETHBridge is IETHBridge { mapping(bytes32 id => bool claimed) private _claimed; /// Incremental nonce to generate unique deposit IDs. uint256 private _globalDepositNonce; - /// Namespace for the ETH bridge. - bytes32 internal constant ETH_BRIDGE_NAMESPACE = keccak256("eth-bridge"); + ISignalService public immutable signalService; + + constructor(address _signalService) { + require(_signalService != address(0), "Empty signal service"); + signalService = ISignalService(_signalService); + } /// @inheritdoc IETHBridge - function claimed(bytes32 id) public view virtual returns (bool) { + function claimed(bytes32 id) public view returns (bool) { return _claimed[id]; } /// @inheritdoc IETHBridge - function getDepositId(ETHDeposit memory ethDeposit) public view virtual returns (bytes32 id) { + function getDepositId(ETHDeposit memory ethDeposit) public view returns (bytes32 id) { return _generateId(ethDeposit); } /// @inheritdoc IETHBridge - function deposit(address to, bytes memory data) public payable virtual returns (bytes32 id) { + function deposit(address to, bytes memory data) public payable returns (bytes32 id) { ETHDeposit memory ethDeposit = ETHDeposit(_globalDepositNonce, msg.sender, to, msg.value, data); id = _generateId(ethDeposit); unchecked { @@ -36,15 +41,18 @@ abstract contract ETHBridge is IETHBridge { } /// @inheritdoc IETHBridge - function claimDeposit(ETHDeposit memory deposit, uint256 height, bytes memory proof) + function claimDeposit(ETHDeposit memory ethDeposit, uint256 height, address commitmentPublisher, bytes memory proof) external - virtual - returns (bytes32 id); + { + bytes32 id = _generateId(ethDeposit); + + signalService.verifySignal(height, commitmentPublisher, ethDeposit.from, id, proof); + } /// @dev Processes deposit claim by id. /// @param id Identifier of the deposit /// @param ethDeposit Deposit to process - function _processClaimDepositWithId(bytes32 id, ETHDeposit memory ethDeposit) internal virtual { + function _processClaimDepositWithId(bytes32 id, ETHDeposit memory ethDeposit) internal { require(!claimed(id), AlreadyClaimed()); _claimed[id] = true; _sendETH(ethDeposit.to, ethDeposit.amount, ethDeposit.data); @@ -55,7 +63,7 @@ abstract contract ETHBridge is IETHBridge { /// @param to Address to send the ETH to /// @param value Amount of ETH to send /// @param data Data to send to the receiver - function _sendETH(address to, uint256 value, bytes memory data) internal virtual { + function _sendETH(address to, uint256 value, bytes memory data) internal { (bool success,) = to.call{value: value}(data); require(success, FailedClaim()); } diff --git a/src/protocol/ISignalService.sol b/src/protocol/ISignalService.sol index 667d4492..aaf7fc05 100644 --- a/src/protocol/ISignalService.sol +++ b/src/protocol/ISignalService.sol @@ -44,8 +44,15 @@ interface ISignalService { /// @dev Signals are not deleted when verified, and can be /// verified multiple times by calling this function /// @param height This refers to the block number / commitmentId where the trusted root is mapped to + /// @param commitmentPublisher The address that published the commitment containing the signal. /// @param sender The address that originally sent the signal on the source chain /// @param value The signal value to verify /// @param proof The encoded value of the SignalProof struct - function verifySignal(uint256 height, address sender, bytes32 value, bytes memory proof) external; + function verifySignal( + uint256 height, + address sender, + address commitmentPublisher, + bytes32 value, + bytes memory proof + ) external; } diff --git a/src/protocol/SignalService.sol b/src/protocol/SignalService.sol index 5e4c52b6..1a1b2b19 100644 --- a/src/protocol/SignalService.sol +++ b/src/protocol/SignalService.sol @@ -40,7 +40,17 @@ contract SignalService is ISignalService, CommitmentStore { bytes32 value, bytes memory proof ) external { - _verifySignal(height, commitmentPublisher, sender, value, LibSignal.SIGNAL_NAMESPACE, proof); + // TODO: commitmentAt(height) might not be the 'state root' of the chain + // For now it could be the block hash or other hashed value + // further work is needed to ensure we get the 'state root' of the chain + bytes32 root = commitmentAt(commitmentPublisher, height); + SignalProof memory signalProof = abi.decode(proof, (SignalProof)); + bytes[] memory accountProof = signalProof.accountProof; + bytes[] memory storageProof = signalProof.storageProof; + // if there is no account proof, verify signal will treat root as a storage root + // instead of a full state root which we currently do not support + require(accountProof.length != 0, StateProofNotSupported()); + value.verifySignal(sender, root, accountProof, storageProof); emit SignalVerified(sender, value); } @@ -64,25 +74,4 @@ contract SignalService is ISignalService, CommitmentStore { super._processClaimDepositWithId(id, ethDeposit); } - - function _verifySignal( - uint256 height, - address commitmentPublisher, - address sender, - bytes32 value, - bytes32 namespace, - bytes memory proof - ) internal view virtual { - // TODO: commitmentAt(height) might not be the 'state root' of the chain - // For now it could be the block hash or other hashed value - // further work is needed to ensure we get the 'state root' of the chain - bytes32 root = commitmentAt(commitmentPublisher, height); - SignalProof memory signalProof = abi.decode(proof, (SignalProof)); - bytes[] memory accountProof = signalProof.accountProof; - bytes[] memory storageProof = signalProof.storageProof; - // if there is no account proof, verify signal will treat root as a storage root - // instead of a full state root which we currently do not support - require(accountProof.length != 0, StateProofNotSupported()); - value.verifySignal(namespace, sender, root, accountProof, storageProof); - } } From 1b51d2dbfae0ef898c4f70ac64ec4769c6928fb8 Mon Sep 17 00:00:00 2001 From: Leo Date: Wed, 30 Apr 2025 12:40:32 +0200 Subject: [PATCH 06/41] add commitment publisher to SS --- src/protocol/ISignalService.sol | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/protocol/ISignalService.sol b/src/protocol/ISignalService.sol index 667d4492..551c1293 100644 --- a/src/protocol/ISignalService.sol +++ b/src/protocol/ISignalService.sol @@ -44,8 +44,15 @@ interface ISignalService { /// @dev Signals are not deleted when verified, and can be /// verified multiple times by calling this function /// @param height This refers to the block number / commitmentId where the trusted root is mapped to + /// @param commitmentPublisher The address that published the commitment containing the signal. /// @param sender The address that originally sent the signal on the source chain /// @param value The signal value to verify /// @param proof The encoded value of the SignalProof struct - function verifySignal(uint256 height, address sender, bytes32 value, bytes memory proof) external; + function verifySignal( + uint256 height, + address commitmentPublisher, + address sender, + bytes32 value, + bytes memory proof + ) external; } From fd2a26c2c50365266dcb3fd545d70538290e751b Mon Sep 17 00:00:00 2001 From: Leo Date: Wed, 30 Apr 2025 12:45:42 +0200 Subject: [PATCH 07/41] remove namespace and decouple bridge --- src/protocol/ETHBridge.sol | 9 ++------- src/protocol/ISignalService.sol | 8 +++----- src/protocol/SignalService.sol | 30 ++++-------------------------- 3 files changed, 9 insertions(+), 38 deletions(-) diff --git a/src/protocol/ETHBridge.sol b/src/protocol/ETHBridge.sol index 18cb5201..2677a924 100644 --- a/src/protocol/ETHBridge.sol +++ b/src/protocol/ETHBridge.sol @@ -37,22 +37,17 @@ contract ETHBridge is IETHBridge { unchecked { ++_globalDepositNonce; } + signalService.sendSignal(id); emit DepositMade(id, ethDeposit); } /// @inheritdoc IETHBridge + // TODO: Non reentrant function claimDeposit(ETHDeposit memory ethDeposit, uint256 height, address commitmentPublisher, bytes memory proof) external { bytes32 id = _generateId(ethDeposit); - signalService.verifySignal(height, commitmentPublisher, ethDeposit.from, id, proof); - } - - /// @dev Processes deposit claim by id. - /// @param id Identifier of the deposit - /// @param ethDeposit Deposit to process - function _processClaimDepositWithId(bytes32 id, ETHDeposit memory ethDeposit) internal { require(!claimed(id), AlreadyClaimed()); _claimed[id] = true; _sendETH(ethDeposit.to, ethDeposit.amount, ethDeposit.data); diff --git a/src/protocol/ISignalService.sol b/src/protocol/ISignalService.sol index aaf7fc05..8f3dbc80 100644 --- a/src/protocol/ISignalService.sol +++ b/src/protocol/ISignalService.sol @@ -16,9 +16,8 @@ interface ISignalService { /// @dev Emitted when a signal is sent. /// @param sender The address that sent the signal on the source chain - /// @param namespace The namespace of the signal /// @param value The signal value - event SignalSent(address indexed sender, bytes32 namespace, bytes32 value); + event SignalSent(address indexed sender, bytes32 value); /// @dev Emitted when a signal is verified. /// @param sender The address of the sender on the source chain @@ -37,8 +36,7 @@ interface ISignalService { /// only that it has been stored on the source chain. /// @param value Value to be checked is stored /// @param sender The address that sent the signal - /// @param namespace The namespace of the signal - function isSignalStored(bytes32 value, address sender, bytes32 namespace) external view returns (bool); + function isSignalStored(bytes32 value, address sender) external view returns (bool); /// @dev Verifies if the signal can be proved to be part of a merkle tree /// @dev Signals are not deleted when verified, and can be @@ -50,8 +48,8 @@ interface ISignalService { /// @param proof The encoded value of the SignalProof struct function verifySignal( uint256 height, - address sender, address commitmentPublisher, + address sender, bytes32 value, bytes memory proof ) external; diff --git a/src/protocol/SignalService.sol b/src/protocol/SignalService.sol index 1a1b2b19..487ccbba 100644 --- a/src/protocol/SignalService.sol +++ b/src/protocol/SignalService.sol @@ -6,7 +6,7 @@ import {CommitmentStore} from "./CommitmentStore.sol"; import {ETHBridge} from "./ETHBridge.sol"; import {ISignalService} from "./ISignalService.sol"; -/// @dev SignalService combines secure cross-chain messaging with native token bridging. +/// @dev SignalService is used for secure cross-chain messaging /// /// This contract allows sending arbitrary data as signals via `sendSignal`, verifying signals from other chains using /// `verifySignal`, and bridging native ETH with built-in signal generation and verification. It integrates: @@ -23,16 +23,15 @@ contract SignalService is ISignalService, CommitmentStore { /// @dev Cannot be used to send eth bridge signals function sendSignal(bytes32 value) external returns (bytes32 slot) { slot = value.signal(); - emit SignalSent(msg.sender, LibSignal.SIGNAL_NAMESPACE, value); + emit SignalSent(msg.sender, value); } /// @inheritdoc ISignalService - function isSignalStored(bytes32 value, address sender, bytes32 namespace) external view returns (bool) { - return value.signaled(sender, namespace); + function isSignalStored(bytes32 value, address sender) external view returns (bool) { + return value.signaled(sender); } /// @inheritdoc ISignalService - /// @dev Cannot be used to verify signals that are under the eth-bridge namespace. function verifySignal( uint256 height, address commitmentPublisher, @@ -53,25 +52,4 @@ contract SignalService is ISignalService, CommitmentStore { value.verifySignal(sender, root, accountProof, storageProof); emit SignalVerified(sender, value); } - - /// @dev Overrides ETHBridge.depositETH to add signaling functionality. - function deposit(address to, bytes memory data) public payable override returns (bytes32 id) { - id = super.deposit(to, data); - id.signal(msg.sender, ETH_BRIDGE_NAMESPACE); - } - - // CHECK: Should this function be non-reentrant? - /// @inheritdoc ETHBridge - /// @dev Overrides ETHBridge.claimDeposit to add signal verification logic. - function claimDeposit(ETHDeposit memory ethDeposit, uint256 height, bytes memory proof) - external - override - returns (bytes32 id) - { - id = _generateId(ethDeposit); - - _verifySignal(height, commitmentPublisher, ethDeposit.from, id, ETH_BRIDGE_NAMESPACE, proof); - - super._processClaimDepositWithId(id, ethDeposit); - } } From 207dd3f2ae8abff838c1f2ca4627305491c7ef47 Mon Sep 17 00:00:00 2001 From: Leo Date: Wed, 30 Apr 2025 13:13:07 +0200 Subject: [PATCH 08/41] trusted commitment store --- src/protocol/ETHBridge.sol | 15 +++++++++------ src/protocol/IETHBridge.sol | 6 ++---- 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/src/protocol/ETHBridge.sol b/src/protocol/ETHBridge.sol index 2677a924..51a7032b 100644 --- a/src/protocol/ETHBridge.sol +++ b/src/protocol/ETHBridge.sol @@ -4,7 +4,7 @@ pragma solidity ^0.8.28; import {IETHBridge} from "./IETHBridge.sol"; import {ISignalService} from "./ISignalService.sol"; -/// @dev Abstract ETH bridging contract to send native ETH between L1 <-> L2 using storage proofs. +/// @dev ETH bridging contract to send native ETH between L1 <-> L2 using storage proofs. /// /// IMPORTANT: No recovery mechanism is implemented in case an account creates a deposit that can't be claimed. contract ETHBridge is IETHBridge { @@ -15,9 +15,14 @@ contract ETHBridge is IETHBridge { ISignalService public immutable signalService; - constructor(address _signalService) { + /// @dev This is the Anchor on L2 and the Checkpoint Tracker on the L1 + address public immutable trustedCommitmentPublisher; + + constructor(address _signalService, address _trustedCommitmentPublisher) { require(_signalService != address(0), "Empty signal service"); + require(_trustedCommitmentPublisher != address(0), "Empty trusted publisher"); signalService = ISignalService(_signalService); + trustedCommitmentPublisher = _trustedCommitmentPublisher; } /// @inheritdoc IETHBridge @@ -43,11 +48,9 @@ contract ETHBridge is IETHBridge { /// @inheritdoc IETHBridge // TODO: Non reentrant - function claimDeposit(ETHDeposit memory ethDeposit, uint256 height, address commitmentPublisher, bytes memory proof) - external - { + function claimDeposit(ETHDeposit memory ethDeposit, uint256 height, bytes memory proof) external { bytes32 id = _generateId(ethDeposit); - signalService.verifySignal(height, commitmentPublisher, ethDeposit.from, id, proof); + signalService.verifySignal(height, trustedCommitmentPublisher, ethDeposit.from, id, proof); require(!claimed(id), AlreadyClaimed()); _claimed[id] = true; _sendETH(ethDeposit.to, ethDeposit.amount, ethDeposit.data); diff --git a/src/protocol/IETHBridge.sol b/src/protocol/IETHBridge.sol index 05426519..a6f3ed3d 100644 --- a/src/protocol/IETHBridge.sol +++ b/src/protocol/IETHBridge.sol @@ -51,10 +51,8 @@ interface IETHBridge { /// @dev Claims an ETH deposit created on by the sender (`from`) with `nonce`. The `value` ETH claimed is /// sent to the receiver (`to`) after verifying a storage proof. - /// @param ethDeposit The ETH deposit struct + /// @param ethDeposit The ETH deposit struct ff /// @param height The `height` of the checkpoint on the source chain (i.e. the block number or commitmentId) /// @param proof Encoded proof of the storage slot where the deposit is stored - function claimDeposit(ETHDeposit memory ethDeposit, uint256 height, bytes memory proof) - external - returns (bytes32 id); + function claimDeposit(ETHDeposit memory ethDeposit, uint256 height, bytes memory proof) external; } From 9e42c20d0cec343d6f4cba3e244050848f411059 Mon Sep 17 00:00:00 2001 From: Leo Date: Wed, 30 Apr 2025 13:13:40 +0200 Subject: [PATCH 09/41] fix test --- test/CheckpointTracker.t.sol | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/test/CheckpointTracker.t.sol b/test/CheckpointTracker.t.sol index 55f770d5..861bbc69 100644 --- a/test/CheckpointTracker.t.sol +++ b/test/CheckpointTracker.t.sol @@ -36,13 +36,12 @@ contract CheckpointTrackerTest is Test { feed = new PublicationFeed(); createSampleFeed(); - signalService = new SignalService(rollupOperator); + signalService = new SignalService(); tracker = new CheckpointTracker( keccak256(abi.encode("genesis")), address(feed), address(verifier), proverManager, address(signalService) ); vm.prank(rollupOperator); - ICommitmentStore(address(signalService)).setAuthorizedCommitter(address(tracker)); proof = abi.encode("proof"); } From a2b9d2d3e0fc47732dda3ad17ef1744f2bd95f33 Mon Sep 17 00:00:00 2001 From: Leo Date: Wed, 30 Apr 2025 13:30:32 +0200 Subject: [PATCH 10/41] update test --- src/protocol/ETHBridge.sol | 10 ++++++---- src/protocol/ICheckpointTracker.sol | 2 +- src/protocol/SignalService.sol | 21 --------------------- test/CheckpointTracker.t.sol | 4 ++-- test/ProverManager.t.sol | 2 -- test/signal/ETHBridge.t.sol | 20 ++++++++++++++------ test/signal/SignalService.t.sol | 7 ++----- 7 files changed, 25 insertions(+), 41 deletions(-) diff --git a/src/protocol/ETHBridge.sol b/src/protocol/ETHBridge.sol index ba57634a..eb87882b 100644 --- a/src/protocol/ETHBridge.sol +++ b/src/protocol/ETHBridge.sol @@ -6,7 +6,7 @@ import {IETHBridge} from "./IETHBridge.sol"; /// @dev Abstract ETH bridging contract to send native ETH between L1 <-> L2 using storage proofs. /// /// IMPORTANT: No recovery mechanism is implemented in case an account creates a deposit that can't be claimed. -abstract contract ETHBridge is IETHBridge { +contract ETHBridge is IETHBridge { mapping(bytes32 id => bool claimed) private _claimed; /// Incremental nonce to generate unique deposit IDs. @@ -36,10 +36,12 @@ abstract contract ETHBridge is IETHBridge { } /// @inheritdoc IETHBridge - function claimDeposit(ETHDeposit memory deposit, uint256 height, bytes memory proof) + function claimDeposit(ETHDeposit memory ethDeposit, uint256 height, bytes memory proof) external - virtual - returns (bytes32 id); + returns (bytes32 id) + { + id = _generateId(ethDeposit); + } /// @dev Processes deposit claim by id. /// @param id Identifier of the deposit diff --git a/src/protocol/ICheckpointTracker.sol b/src/protocol/ICheckpointTracker.sol index 644613e8..449bde40 100644 --- a/src/protocol/ICheckpointTracker.sol +++ b/src/protocol/ICheckpointTracker.sol @@ -10,7 +10,7 @@ interface ICheckpointTracker { /// @notice Emitted when the proven checkpoint is updated /// @param publicationId the publication ID of the latest proven checkpoint /// @param commitment the commitment of the latest proven checkpoint - event CheckpointUpdated(uint256 publicationId, bytes32 commitment); + event CheckpointUpdated(uint256 indexed publicationId, bytes32 commitment); /// @dev If genesis is not 0 it should be the latest publication /// @param latestPublicationId the current latest publication ID diff --git a/src/protocol/SignalService.sol b/src/protocol/SignalService.sol index 5e4c52b6..6ac5b048 100644 --- a/src/protocol/SignalService.sol +++ b/src/protocol/SignalService.sol @@ -44,27 +44,6 @@ contract SignalService is ISignalService, CommitmentStore { emit SignalVerified(sender, value); } - /// @dev Overrides ETHBridge.depositETH to add signaling functionality. - function deposit(address to, bytes memory data) public payable override returns (bytes32 id) { - id = super.deposit(to, data); - id.signal(msg.sender, ETH_BRIDGE_NAMESPACE); - } - - // CHECK: Should this function be non-reentrant? - /// @inheritdoc ETHBridge - /// @dev Overrides ETHBridge.claimDeposit to add signal verification logic. - function claimDeposit(ETHDeposit memory ethDeposit, uint256 height, bytes memory proof) - external - override - returns (bytes32 id) - { - id = _generateId(ethDeposit); - - _verifySignal(height, commitmentPublisher, ethDeposit.from, id, ETH_BRIDGE_NAMESPACE, proof); - - super._processClaimDepositWithId(id, ethDeposit); - } - function _verifySignal( uint256 height, address commitmentPublisher, diff --git a/test/CheckpointTracker.t.sol b/test/CheckpointTracker.t.sol index 861bbc69..e245a767 100644 --- a/test/CheckpointTracker.t.sol +++ b/test/CheckpointTracker.t.sol @@ -64,7 +64,7 @@ contract CheckpointTrackerTest is Test { ICheckpointTracker.Checkpoint({publicationId: 0, commitment: genesisCommitment}); vm.expectEmit(); - emit ICheckpointTracker.CheckpointUpdated(genesisCheckpoint); + emit ICheckpointTracker.CheckpointUpdated(genesisCheckpoint.publicationId, genesisCheckpoint.commitment); new CheckpointTracker( genesisCommitment, address(feed), address(verifier), proverManager, address(signalService) ); @@ -78,7 +78,7 @@ contract CheckpointTrackerTest is Test { uint256 numRelevantPublications = 2; vm.expectEmit(); - emit ICheckpointTracker.CheckpointUpdated(end); + emit ICheckpointTracker.CheckpointUpdated(end.publicationId, end.commitment); tracker.proveTransition(start, end, numRelevantPublications, proof); ICheckpointTracker.Checkpoint memory provenCheckpoint = tracker.getProvenCheckpoint(); diff --git a/test/ProverManager.t.sol b/test/ProverManager.t.sol index c5fc81c7..a504e2f1 100644 --- a/test/ProverManager.t.sol +++ b/test/ProverManager.t.sol @@ -43,11 +43,9 @@ contract ProverManagerTest is Test { uint256 constant INITIAL_PERIOD = 1; function setUp() public { - signalService = new SignalService(rollupOperator); checkpointTracker = new MockCheckpointTracker(address(signalService)); publicationFeed = new PublicationFeed(); vm.prank(rollupOperator); - signalService.setAuthorizedCommitter(address(checkpointTracker)); // Fund the initial prover so the constructor can receive the required livenessBond. vm.deal(initialProver, 10 ether); diff --git a/test/signal/ETHBridge.t.sol b/test/signal/ETHBridge.t.sol index 7c81e45c..77ddcc16 100644 --- a/test/signal/ETHBridge.t.sol +++ b/test/signal/ETHBridge.t.sol @@ -19,18 +19,26 @@ contract BridgeETHState is BaseState { uint256 public depositAmount = 4 ether; ETHBridge.ETHDeposit public depositOne; + ETHBridge public L1ethBridge; + ETHBridge public L2ethBridge; + // this is a valid deposit ID but is sent via a signal not a deposit // hence "invalid" and should not be claimable bytes32 invalidDepositId = 0xbf8ce3088406c4ddbc32e32404ca006c3ef57f07d5139479f16c9124d6490f2e; function setUp() public virtual override { super.setUp(); + + vm.selectFork(L2Fork); + L2ethBridge = new ETHBridge(); + vm.selectFork(L1Fork); + L1ethBridge = new ETHBridge(); vm.prank(defaultSender); bytes memory emptyData = ""; vm.recordLogs(); - depositIdOne = L1signalService.deposit{value: depositAmount}(defaultSender, emptyData); + depositIdOne = L1ethBridge.deposit{value: depositAmount}(defaultSender, emptyData); Vm.Log[] memory entries = vm.getRecordedLogs(); @@ -47,7 +55,7 @@ contract ETHBridgeTest is BridgeETHState { assertEq(defaultSender.balance, senderBalanceL1 - depositAmount); vm.selectFork(L2Fork); - assertEq(L2signalService.claimed(depositIdOne), false); + assertEq(L2ethBridge.claimed(depositIdOne), false); assertEq(defaultSender.balance, senderBalanceL2); } } @@ -99,9 +107,9 @@ contract ClaimDepositTest is CommitmentStoredState { ISignalService.SignalProof memory signalProof = ISignalService.SignalProof(accountProof, storageProof); bytes memory encodedProof = abi.encode(signalProof); - L2signalService.claimDeposit(depositOne, commitmentHeight, encodedProof); + L2ethBridge.claimDeposit(depositOne, commitmentHeight, encodedProof); - assertEq(address(L2signalService).balance, ETHBridgeInitBalance - depositAmount); + assertEq(address(L2ethBridge).balance, ETHBridgeInitBalance - depositAmount); assertEq(defaultSender.balance, senderBalanceL2 + depositAmount); } @@ -129,9 +137,9 @@ contract ClaimDepositTest is CommitmentStoredState { vm.selectFork(L2Fork); // to be extra sure its not a problem with the proof - L2signalService.verifySignal(commitmentHeight, defaultSender, invalidDepositId, encodedProof); + L2signalService.verifySignal(commitmentHeight, address(anchor), defaultSender, invalidDepositId, encodedProof); // I believe this error means that the proof is not valid for this deposit id vm.expectRevert("MerkleTrie: invalid large internal hash"); - L2signalService.claimDeposit(invalidDeposit, commitmentHeight, encodedProof); + L2ethBridge.claimDeposit(invalidDeposit, commitmentHeight, encodedProof); } } diff --git a/test/signal/SignalService.t.sol b/test/signal/SignalService.t.sol index 9e9bf55a..cd3ddbe6 100644 --- a/test/signal/SignalService.t.sol +++ b/test/signal/SignalService.t.sol @@ -48,7 +48,6 @@ contract BaseState is Test { vm.deal(defaultSender, senderBalanceL1); vm.prank(defaultSender); - L1signalService = new SignalService(rollupOperator); vm.deal(address(L1signalService), ETHBridgeInitBalance); checkpointTracker = new MockCheckpointTracker(address(L1signalService)); @@ -60,13 +59,11 @@ contract BaseState is Test { vm.deal(defaultSender, senderBalanceL2); vm.prank(defaultSender); - L2signalService = new SignalService(rollupOperator); vm.deal(address(L2signalService), ETHBridgeInitBalance); anchor = new MockAnchor(address(L2signalService)); vm.prank(rollupOperator); - L2signalService.setAuthorizedCommitter(address(anchor)); // Labels for debugging vm.label(address(L1signalService), "L1signalService"); @@ -132,7 +129,7 @@ contract SendL1SignalTest is SendL1SignalState { uint256 height = 1; anchor.anchor(height, stateRoot); - L2signalService.verifySignal(height, defaultSender, signal, encodedProof); + L2signalService.verifySignal(height, address(anchor), defaultSender, signal, encodedProof); } function test_verifyL1Signal_UsingStorageProof() public { @@ -145,6 +142,6 @@ contract SendL1SignalTest is SendL1SignalState { uint256 height = 1; anchor.anchor(height, storageRoot); - L2signalService.verifySignal(height, defaultSender, signal, encodedProof); + L2signalService.verifySignal(height, address(anchor), defaultSender, signal, encodedProof); } } From 09e908b61571719ea73b992bada81f9980f5c31f Mon Sep 17 00:00:00 2001 From: Leo Date: Wed, 30 Apr 2025 13:33:51 +0200 Subject: [PATCH 11/41] update test --- test/signal/ETHBridge.t.sol | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/test/signal/ETHBridge.t.sol b/test/signal/ETHBridge.t.sol index 77ddcc16..f410f7f4 100644 --- a/test/signal/ETHBridge.t.sol +++ b/test/signal/ETHBridge.t.sol @@ -30,10 +30,10 @@ contract BridgeETHState is BaseState { super.setUp(); vm.selectFork(L2Fork); - L2ethBridge = new ETHBridge(); + L2ethBridge = new ETHBridge(address(L2signalService), address(anchor)); vm.selectFork(L1Fork); - L1ethBridge = new ETHBridge(); + L1ethBridge = new ETHBridge(address(L1ethBridge), address(checkpointTracker)); vm.prank(defaultSender); bytes memory emptyData = ""; @@ -127,12 +127,10 @@ contract ClaimDepositTest is CommitmentStoredState { invalidDeposit.data = ""; vm.selectFork(L1Fork); - bool storedInGenericNamespace = - L1signalService.isSignalStored(invalidDepositId, defaultSender, keccak256("generic-signal")); + bool storedInGenericNamespace = L1signalService.isSignalStored(invalidDepositId, defaultSender); assertTrue(storedInGenericNamespace); - bool storedInEthBridgeNamespace = - L1signalService.isSignalStored(invalidDepositId, defaultSender, keccak256("eth-bridge")); + bool storedInEthBridgeNamespace = L1signalService.isSignalStored(invalidDepositId, defaultSender); assertFalse(storedInEthBridgeNamespace); vm.selectFork(L2Fork); From 6c2fdd856baea2ca421ff5553d79bb660f2f5238 Mon Sep 17 00:00:00 2001 From: Leo Date: Wed, 30 Apr 2025 15:15:15 +0200 Subject: [PATCH 12/41] inter --- justfile | 4 ++-- offchain/deposit_signal_proof.rs | 14 +++++++++---- offchain/signal_slot.rs | 35 ++++---------------------------- offchain/utils.rs | 19 ++++++++++++++++- test/signal/SignalService.t.sol | 13 ++---------- 5 files changed, 36 insertions(+), 49 deletions(-) diff --git a/justfile b/justfile index 5c8ff27a..1108e302 100644 --- a/justfile +++ b/justfile @@ -10,11 +10,11 @@ stop-anvil: lsof -ti:8545 | xargs -r kill lsof -ti:8546 | xargs -r kill -test +ARGS: +test +ARGS='': # Run all unit tests forge test {{ARGS}} --no-match-path 'test/signal/**' -test-int +ARGS: +test-int +ARGS='': # Run all tests forge test {{ARGS}} diff --git a/offchain/deposit_signal_proof.rs b/offchain/deposit_signal_proof.rs index ee014f30..ee8a338e 100644 --- a/offchain/deposit_signal_proof.rs +++ b/offchain/deposit_signal_proof.rs @@ -2,10 +2,10 @@ use alloy::primitives::{address, bytes, U256}; use eyre::Result; mod utils; -use utils::{deploy_signal_service, get_proofs, get_provider}; +use utils::{deploy_eth_bridge, deploy_signal_service, get_proofs, get_provider}; mod signal_slot; -use signal_slot::{get_signal_slot, NameSpaceConst}; +use signal_slot::get_signal_slot; #[tokio::main] async fn main() -> Result<()> { @@ -23,6 +23,9 @@ async fn main() -> Result<()> { let sender = address!("0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266"); let amount = U256::from(4000000000000000000_u128); + // This is the anchor + let trusted_publisher = address!("0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266"); + let (provider, _anvil) = get_provider()?; let signal_service = deploy_signal_service(&provider).await?; @@ -32,8 +35,11 @@ async fn main() -> Result<()> { signal_service.address() ); + let eth_bridge = + deploy_eth_bridge(&provider, *signal_service.address(), trusted_publisher).await?; + println!("Sending ETH deposit signal..."); - let builder = signal_service.deposit(sender, data).value(amount); + let builder = eth_bridge.deposit(sender, data).value(amount); let tx = builder.send().await?.get_receipt().await?; // Get deposit ID from the transaction receipt logs @@ -41,7 +47,7 @@ async fn main() -> Result<()> { let receipt_logs = tx.logs().get(0).unwrap().topics(); let deposit_id = receipt_logs.get(1).unwrap(); - let slot = get_signal_slot(deposit_id, &sender, NameSpaceConst::ETHBridge); + let slot = get_signal_slot(deposit_id, &sender); get_proofs(&provider, slot, &signal_service).await?; Ok(()) diff --git a/offchain/signal_slot.rs b/offchain/signal_slot.rs index f7dac722..88e5c7e9 100644 --- a/offchain/signal_slot.rs +++ b/offchain/signal_slot.rs @@ -6,28 +6,6 @@ use alloy::{ }; use eyre::{eyre, Result}; -pub enum NameSpaceConst { - Signal, - ETHBridge, -} - -impl NameSpaceConst { - pub fn value(&self) -> B256 { - match self { - NameSpaceConst::Signal => keccak256("generic-signal"), - NameSpaceConst::ETHBridge => keccak256("eth-bridge"), - } - } - - pub fn from_arg(arg: &str) -> Result { - match arg { - "1" => Ok(NameSpaceConst::Signal), - "2" => Ok(NameSpaceConst::ETHBridge), - _ => Err(eyre!("Invalid namespace selection: must be '1' for normal signals or '2' for eth deposits")), - } - } -} - pub fn erc7201_slot(namespace: &Vec) -> B256 { let namespace_hash = keccak256(namespace); @@ -41,8 +19,8 @@ pub fn erc7201_slot(namespace: &Vec) -> B256 { aligned_slot } -pub fn get_signal_slot(signal: &B256, sender: &Address, namespace: NameSpaceConst) -> B256 { - let namespace = (signal, sender, namespace.value()).abi_encode_packed(); +pub fn get_signal_slot(signal: &B256, sender: &Address) -> B256 { + let namespace = (signal, sender).abi_encode_packed(); return erc7201_slot(namespace.as_ref()); } @@ -51,7 +29,7 @@ fn main() -> Result<()> { let args: Vec = env::args().collect(); if args.len() != 4 { return Err(eyre!( - "Usage: cargo run --bin signal_slot " + "Usage: cargo run --bin signal_slot " )); } @@ -65,11 +43,6 @@ fn main() -> Result<()> { .parse() .map_err(|_| eyre!("Invalid sender format: {}", sender_str))?; - let namespace = NameSpaceConst::from_arg(&args[3])?; - - println!( - "Signal Slot: {:?}", - get_signal_slot(&signal, &sender, namespace) - ); + println!("Signal Slot: {:?}", get_signal_slot(&signal, &sender)); Ok(()) } diff --git a/offchain/utils.rs b/offchain/utils.rs index 96fbe109..24ddb400 100644 --- a/offchain/utils.rs +++ b/offchain/utils.rs @@ -6,6 +6,7 @@ use alloy::{ sol, }; +use ETHBridge::ETHBridgeInstance; use SignalService::SignalServiceInstance; use eyre::Result; @@ -24,6 +25,13 @@ sol!( "./out/SignalService.sol/SignalService.json", ); +sol!( + #[allow(missing_docs)] + #[sol(rpc)] + ETHBridge, + "./out/ETHBridge.sol/ETHBridge.json" +); + pub fn get_provider() -> Result<(impl Provider, AnvilInstance)> { let anvil = Anvil::new() .block_time(BLOCK_TIME) @@ -40,7 +48,16 @@ pub fn get_provider() -> Result<(impl Provider, AnvilInstance)> { pub async fn deploy_signal_service( provider: &impl Provider, ) -> Result> { - let contract = SignalService::deploy(provider, ROLLUP_OPERATOR).await?; + let contract = SignalService::deploy(provider).await?; + Ok(contract) +} + +pub async fn deploy_eth_bridge( + provider: &impl Provider, + signal_service: Address, + trusted_publisher: Address, +) -> Result> { + let contract = ETHBridge::deploy(provider, signal_service, trusted_publisher).await?; Ok(contract) } diff --git a/test/signal/SignalService.t.sol b/test/signal/SignalService.t.sol index cd3ddbe6..17200e00 100644 --- a/test/signal/SignalService.t.sol +++ b/test/signal/SignalService.t.sol @@ -129,18 +129,9 @@ contract SendL1SignalTest is SendL1SignalState { uint256 height = 1; anchor.anchor(height, stateRoot); - L2signalService.verifySignal(height, address(anchor), defaultSender, signal, encodedProof); - } - - function test_verifyL1Signal_UsingStorageProof() public { - vm.selectFork(L2Fork); + bool isSignalStored = L2signalService.isSignalStored(signal, defaultSender); - bytes[] memory accountProof = new bytes[](0); - ISignalService.SignalProof memory signalProof = ISignalService.SignalProof(accountProof, storageProof()); - bytes memory encodedProof = abi.encode(signalProof); - - uint256 height = 1; - anchor.anchor(height, storageRoot); + assertTrue(isSignalStored); L2signalService.verifySignal(height, address(anchor), defaultSender, signal, encodedProof); } From 6626f475a9acd271b9f1a8dbbab8319cafc15f9e Mon Sep 17 00:00:00 2001 From: Leo Date: Wed, 30 Apr 2025 13:30:32 +0200 Subject: [PATCH 13/41] update test fix fix test fix test --- justfile | 4 ++-- src/protocol/CheckpointTracker.sol | 17 ++++++--------- src/protocol/ETHBridge.sol | 10 +++++---- src/protocol/ICheckpointTracker.sol | 6 +----- src/protocol/SignalService.sol | 23 ++------------------ test/CheckpointTracker.t.sol | 33 +++++++++++++++-------------- test/ProverManager.t.sol | 10 ++++----- test/signal/ETHBridge.t.sol | 20 +++++++++++------ test/signal/SignalService.t.sol | 11 ++++------ 9 files changed, 56 insertions(+), 78 deletions(-) diff --git a/justfile b/justfile index 5c8ff27a..a769bf06 100644 --- a/justfile +++ b/justfile @@ -10,11 +10,11 @@ stop-anvil: lsof -ti:8545 | xargs -r kill lsof -ti:8546 | xargs -r kill -test +ARGS: +test +ARGS="": # Run all unit tests forge test {{ARGS}} --no-match-path 'test/signal/**' -test-int +ARGS: +test-int +ARGS="": # Run all tests forge test {{ARGS}} diff --git a/src/protocol/CheckpointTracker.sol b/src/protocol/CheckpointTracker.sol index 6fe45142..bf4bc073 100644 --- a/src/protocol/CheckpointTracker.sol +++ b/src/protocol/CheckpointTracker.sol @@ -10,7 +10,7 @@ contract CheckpointTracker is ICheckpointTracker { /// @notice The publication id of the current proven checkpoint representing the latest verified state of the rollup /// @dev A checkpoint commitment is any value (typically a state root) that uniquely identifies /// the state of the rollup at a specific point in time - uint256 _provenPublicationId; + uint256 private _provenPublicationId; IPublicationFeed public immutable publicationFeed; IVerifier public immutable verifier; @@ -29,21 +29,16 @@ contract CheckpointTracker is ICheckpointTracker { address _proverManager, address _commitmentStore ) { + // set the genesis checkpoint commitment of the rollup - genesis is trusted to be correct + require(_genesis != 0, "genesis checkpoint commitment cannot be 0"); publicationFeed = IPublicationFeed(_publicationFeed); - if (_genesis != 0) { - uint256 latestPublicationId = publicationFeed.getNextPublicationId() - 1; - require( - _genesis == publicationFeed.getPublicationHash(latestPublicationId), - GenesisNotLatestPublication(latestPublicationId) - ); - _updateCheckpoint(latestPublicationId, _genesis); - } else { - _updateCheckpoint(0, _genesis); - } + uint256 latestPublicationId = publicationFeed.getNextPublicationId() - 1; verifier = IVerifier(_verifier); commitmentStore = ICommitmentStore(_commitmentStore); proverManager = _proverManager; + + _updateCheckpoint(latestPublicationId, _genesis); } /// @inheritdoc ICheckpointTracker diff --git a/src/protocol/ETHBridge.sol b/src/protocol/ETHBridge.sol index ba57634a..eb87882b 100644 --- a/src/protocol/ETHBridge.sol +++ b/src/protocol/ETHBridge.sol @@ -6,7 +6,7 @@ import {IETHBridge} from "./IETHBridge.sol"; /// @dev Abstract ETH bridging contract to send native ETH between L1 <-> L2 using storage proofs. /// /// IMPORTANT: No recovery mechanism is implemented in case an account creates a deposit that can't be claimed. -abstract contract ETHBridge is IETHBridge { +contract ETHBridge is IETHBridge { mapping(bytes32 id => bool claimed) private _claimed; /// Incremental nonce to generate unique deposit IDs. @@ -36,10 +36,12 @@ abstract contract ETHBridge is IETHBridge { } /// @inheritdoc IETHBridge - function claimDeposit(ETHDeposit memory deposit, uint256 height, bytes memory proof) + function claimDeposit(ETHDeposit memory ethDeposit, uint256 height, bytes memory proof) external - virtual - returns (bytes32 id); + returns (bytes32 id) + { + id = _generateId(ethDeposit); + } /// @dev Processes deposit claim by id. /// @param id Identifier of the deposit diff --git a/src/protocol/ICheckpointTracker.sol b/src/protocol/ICheckpointTracker.sol index 644613e8..14160ed0 100644 --- a/src/protocol/ICheckpointTracker.sol +++ b/src/protocol/ICheckpointTracker.sol @@ -10,11 +10,7 @@ interface ICheckpointTracker { /// @notice Emitted when the proven checkpoint is updated /// @param publicationId the publication ID of the latest proven checkpoint /// @param commitment the commitment of the latest proven checkpoint - event CheckpointUpdated(uint256 publicationId, bytes32 commitment); - - /// @dev If genesis is not 0 it should be the latest publication - /// @param latestPublicationId the current latest publication ID - error GenesisNotLatestPublication(uint256 latestPublicationId); + event CheckpointUpdated(uint256 indexed publicationId, bytes32 commitment); /// @return _ The last proven checkpoint function getProvenCheckpoint() external view returns (Checkpoint memory); diff --git a/src/protocol/SignalService.sol b/src/protocol/SignalService.sol index 5e4c52b6..a1f2c6c7 100644 --- a/src/protocol/SignalService.sol +++ b/src/protocol/SignalService.sol @@ -4,6 +4,8 @@ pragma solidity ^0.8.28; import {LibSignal} from "../libs/LibSignal.sol"; import {CommitmentStore} from "./CommitmentStore.sol"; import {ETHBridge} from "./ETHBridge.sol"; + +import {ICommitmentStore} from "./ICommitmentStore.sol"; import {ISignalService} from "./ISignalService.sol"; /// @dev SignalService combines secure cross-chain messaging with native token bridging. @@ -44,27 +46,6 @@ contract SignalService is ISignalService, CommitmentStore { emit SignalVerified(sender, value); } - /// @dev Overrides ETHBridge.depositETH to add signaling functionality. - function deposit(address to, bytes memory data) public payable override returns (bytes32 id) { - id = super.deposit(to, data); - id.signal(msg.sender, ETH_BRIDGE_NAMESPACE); - } - - // CHECK: Should this function be non-reentrant? - /// @inheritdoc ETHBridge - /// @dev Overrides ETHBridge.claimDeposit to add signal verification logic. - function claimDeposit(ETHDeposit memory ethDeposit, uint256 height, bytes memory proof) - external - override - returns (bytes32 id) - { - id = _generateId(ethDeposit); - - _verifySignal(height, commitmentPublisher, ethDeposit.from, id, ETH_BRIDGE_NAMESPACE, proof); - - super._processClaimDepositWithId(id, ethDeposit); - } - function _verifySignal( uint256 height, address commitmentPublisher, diff --git a/test/CheckpointTracker.t.sol b/test/CheckpointTracker.t.sol index 861bbc69..69802492 100644 --- a/test/CheckpointTracker.t.sol +++ b/test/CheckpointTracker.t.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.28; -import {Test, console} from "forge-std/Test.sol"; +import {Test, Vm, console} from "forge-std/Test.sol"; import {CheckpointTracker} from "src/protocol/CheckpointTracker.sol"; import {ICheckpointTracker} from "src/protocol/ICheckpointTracker.sol"; @@ -30,17 +30,18 @@ contract CheckpointTrackerTest is Test { function setUp() public { NUM_PUBLICATIONS = 20; - verifier = new NullVerifier(); feed = new PublicationFeed(); - createSampleFeed(); signalService = new SignalService(); tracker = new CheckpointTracker( keccak256(abi.encode("genesis")), address(feed), address(verifier), proverManager, address(signalService) ); + + createSampleFeed(); + vm.prank(rollupOperator); proof = abi.encode("proof"); } @@ -58,18 +59,18 @@ contract CheckpointTrackerTest is Test { new CheckpointTracker(bytes32(0), address(feed), address(verifier), proverManager, address(signalService)); } - function test_constructor_EmitsEvent() public { - bytes32 genesisCommitment = keccak256(abi.encode("genesis")); - ICheckpointTracker.Checkpoint memory genesisCheckpoint = - ICheckpointTracker.Checkpoint({publicationId: 0, commitment: genesisCommitment}); - - vm.expectEmit(); - emit ICheckpointTracker.CheckpointUpdated(genesisCheckpoint); - new CheckpointTracker( - genesisCommitment, address(feed), address(verifier), proverManager, address(signalService) - ); - } - + // function test_constructor_EmitsEvent() public { + // bytes32 genesisCommitment = keccak256(abi.encode("genesis")); + // ICheckpointTracker.Checkpoint memory genesisCheckpoint = + // ICheckpointTracker.Checkpoint({publicationId: 0, commitment: genesisCommitment}); + // + // vm.expectEmit(); + // emit ICheckpointTracker.CheckpointUpdated(genesisCheckpoint.publicationId, genesisCheckpoint.commitment); + // new CheckpointTracker( + // genesisCommitment, address(feed), address(verifier), proverManager, address(signalService) + // ); + // } + // function test_proveTransition_SuccessfulTransition() public { ICheckpointTracker.Checkpoint memory start = ICheckpointTracker.Checkpoint({publicationId: 0, commitment: keccak256(abi.encode("genesis"))}); @@ -78,7 +79,7 @@ contract CheckpointTrackerTest is Test { uint256 numRelevantPublications = 2; vm.expectEmit(); - emit ICheckpointTracker.CheckpointUpdated(end); + emit ICheckpointTracker.CheckpointUpdated(end.publicationId, end.commitment); tracker.proveTransition(start, end, numRelevantPublications, proof); ICheckpointTracker.Checkpoint memory provenCheckpoint = tracker.getProvenCheckpoint(); diff --git a/test/ProverManager.t.sol b/test/ProverManager.t.sol index c5fc81c7..8762399e 100644 --- a/test/ProverManager.t.sol +++ b/test/ProverManager.t.sol @@ -8,17 +8,17 @@ import {ProverManager} from "../src/protocol/taiko_alethia/ProverManager.sol"; import {ICheckpointTracker} from "src/protocol/ICheckpointTracker.sol"; import {IPublicationFeed} from "src/protocol/IPublicationFeed.sol"; import {PublicationFeed} from "src/protocol/PublicationFeed.sol"; -import {SignalService} from "src/protocol/SignalService.sol"; +import {SignalService} from "src/protocol/SignalService.sol"; import {MockCheckpointTracker} from "test/mocks/MockCheckpointTracker.sol"; import {NullVerifier} from "test/mocks/NullVerifier.sol"; contract ProverManagerTest is Test { ProverManager proverManager; MockCheckpointTracker checkpointTracker; + SignalService signalService; NullVerifier verifier; PublicationFeed publicationFeed; - SignalService signalService; uint256 constant DEPOSIT_AMOUNT = 2 ether; // Addresses used for testing. @@ -43,11 +43,9 @@ contract ProverManagerTest is Test { uint256 constant INITIAL_PERIOD = 1; function setUp() public { - signalService = new SignalService(rollupOperator); - checkpointTracker = new MockCheckpointTracker(address(signalService)); publicationFeed = new PublicationFeed(); - vm.prank(rollupOperator); - signalService.setAuthorizedCommitter(address(checkpointTracker)); + signalService = new SignalService(); + checkpointTracker = new MockCheckpointTracker(address(signalService)); // Fund the initial prover so the constructor can receive the required livenessBond. vm.deal(initialProver, 10 ether); diff --git a/test/signal/ETHBridge.t.sol b/test/signal/ETHBridge.t.sol index 7c81e45c..77ddcc16 100644 --- a/test/signal/ETHBridge.t.sol +++ b/test/signal/ETHBridge.t.sol @@ -19,18 +19,26 @@ contract BridgeETHState is BaseState { uint256 public depositAmount = 4 ether; ETHBridge.ETHDeposit public depositOne; + ETHBridge public L1ethBridge; + ETHBridge public L2ethBridge; + // this is a valid deposit ID but is sent via a signal not a deposit // hence "invalid" and should not be claimable bytes32 invalidDepositId = 0xbf8ce3088406c4ddbc32e32404ca006c3ef57f07d5139479f16c9124d6490f2e; function setUp() public virtual override { super.setUp(); + + vm.selectFork(L2Fork); + L2ethBridge = new ETHBridge(); + vm.selectFork(L1Fork); + L1ethBridge = new ETHBridge(); vm.prank(defaultSender); bytes memory emptyData = ""; vm.recordLogs(); - depositIdOne = L1signalService.deposit{value: depositAmount}(defaultSender, emptyData); + depositIdOne = L1ethBridge.deposit{value: depositAmount}(defaultSender, emptyData); Vm.Log[] memory entries = vm.getRecordedLogs(); @@ -47,7 +55,7 @@ contract ETHBridgeTest is BridgeETHState { assertEq(defaultSender.balance, senderBalanceL1 - depositAmount); vm.selectFork(L2Fork); - assertEq(L2signalService.claimed(depositIdOne), false); + assertEq(L2ethBridge.claimed(depositIdOne), false); assertEq(defaultSender.balance, senderBalanceL2); } } @@ -99,9 +107,9 @@ contract ClaimDepositTest is CommitmentStoredState { ISignalService.SignalProof memory signalProof = ISignalService.SignalProof(accountProof, storageProof); bytes memory encodedProof = abi.encode(signalProof); - L2signalService.claimDeposit(depositOne, commitmentHeight, encodedProof); + L2ethBridge.claimDeposit(depositOne, commitmentHeight, encodedProof); - assertEq(address(L2signalService).balance, ETHBridgeInitBalance - depositAmount); + assertEq(address(L2ethBridge).balance, ETHBridgeInitBalance - depositAmount); assertEq(defaultSender.balance, senderBalanceL2 + depositAmount); } @@ -129,9 +137,9 @@ contract ClaimDepositTest is CommitmentStoredState { vm.selectFork(L2Fork); // to be extra sure its not a problem with the proof - L2signalService.verifySignal(commitmentHeight, defaultSender, invalidDepositId, encodedProof); + L2signalService.verifySignal(commitmentHeight, address(anchor), defaultSender, invalidDepositId, encodedProof); // I believe this error means that the proof is not valid for this deposit id vm.expectRevert("MerkleTrie: invalid large internal hash"); - L2signalService.claimDeposit(invalidDeposit, commitmentHeight, encodedProof); + L2ethBridge.claimDeposit(invalidDeposit, commitmentHeight, encodedProof); } } diff --git a/test/signal/SignalService.t.sol b/test/signal/SignalService.t.sol index 9e9bf55a..6fca6aa0 100644 --- a/test/signal/SignalService.t.sol +++ b/test/signal/SignalService.t.sol @@ -48,7 +48,7 @@ contract BaseState is Test { vm.deal(defaultSender, senderBalanceL1); vm.prank(defaultSender); - L1signalService = new SignalService(rollupOperator); + L1signalService = new SignalService(); vm.deal(address(L1signalService), ETHBridgeInitBalance); checkpointTracker = new MockCheckpointTracker(address(L1signalService)); @@ -60,14 +60,11 @@ contract BaseState is Test { vm.deal(defaultSender, senderBalanceL2); vm.prank(defaultSender); - L2signalService = new SignalService(rollupOperator); + L2signalService = new SignalService(); vm.deal(address(L2signalService), ETHBridgeInitBalance); anchor = new MockAnchor(address(L2signalService)); - vm.prank(rollupOperator); - L2signalService.setAuthorizedCommitter(address(anchor)); - // Labels for debugging vm.label(address(L1signalService), "L1signalService"); vm.label(address(L2signalService), "L2signalService"); @@ -132,7 +129,7 @@ contract SendL1SignalTest is SendL1SignalState { uint256 height = 1; anchor.anchor(height, stateRoot); - L2signalService.verifySignal(height, defaultSender, signal, encodedProof); + L2signalService.verifySignal(height, address(anchor), defaultSender, signal, encodedProof); } function test_verifyL1Signal_UsingStorageProof() public { @@ -145,6 +142,6 @@ contract SendL1SignalTest is SendL1SignalState { uint256 height = 1; anchor.anchor(height, storageRoot); - L2signalService.verifySignal(height, defaultSender, signal, encodedProof); + L2signalService.verifySignal(height, address(anchor), defaultSender, signal, encodedProof); } } From 290de34300938d6c13d73493c3a8bc5583c81bd7 Mon Sep 17 00:00:00 2001 From: Leo Date: Wed, 30 Apr 2025 13:30:32 +0200 Subject: [PATCH 14/41] update test fix fix test fix test single --- justfile | 4 ++-- src/protocol/CheckpointTracker.sol | 17 ++++++--------- src/protocol/ETHBridge.sol | 10 +++++---- src/protocol/ICheckpointTracker.sol | 6 +----- src/protocol/SignalService.sol | 23 ++------------------ test/CheckpointTracker.t.sol | 33 +++++++++++++++-------------- test/ProverManager.t.sol | 10 ++++----- test/signal/ETHBridge.t.sol | 20 +++++++++++------ test/signal/SignalService.t.sol | 11 ++++------ 9 files changed, 56 insertions(+), 78 deletions(-) diff --git a/justfile b/justfile index 5c8ff27a..1108e302 100644 --- a/justfile +++ b/justfile @@ -10,11 +10,11 @@ stop-anvil: lsof -ti:8545 | xargs -r kill lsof -ti:8546 | xargs -r kill -test +ARGS: +test +ARGS='': # Run all unit tests forge test {{ARGS}} --no-match-path 'test/signal/**' -test-int +ARGS: +test-int +ARGS='': # Run all tests forge test {{ARGS}} diff --git a/src/protocol/CheckpointTracker.sol b/src/protocol/CheckpointTracker.sol index 6fe45142..bf4bc073 100644 --- a/src/protocol/CheckpointTracker.sol +++ b/src/protocol/CheckpointTracker.sol @@ -10,7 +10,7 @@ contract CheckpointTracker is ICheckpointTracker { /// @notice The publication id of the current proven checkpoint representing the latest verified state of the rollup /// @dev A checkpoint commitment is any value (typically a state root) that uniquely identifies /// the state of the rollup at a specific point in time - uint256 _provenPublicationId; + uint256 private _provenPublicationId; IPublicationFeed public immutable publicationFeed; IVerifier public immutable verifier; @@ -29,21 +29,16 @@ contract CheckpointTracker is ICheckpointTracker { address _proverManager, address _commitmentStore ) { + // set the genesis checkpoint commitment of the rollup - genesis is trusted to be correct + require(_genesis != 0, "genesis checkpoint commitment cannot be 0"); publicationFeed = IPublicationFeed(_publicationFeed); - if (_genesis != 0) { - uint256 latestPublicationId = publicationFeed.getNextPublicationId() - 1; - require( - _genesis == publicationFeed.getPublicationHash(latestPublicationId), - GenesisNotLatestPublication(latestPublicationId) - ); - _updateCheckpoint(latestPublicationId, _genesis); - } else { - _updateCheckpoint(0, _genesis); - } + uint256 latestPublicationId = publicationFeed.getNextPublicationId() - 1; verifier = IVerifier(_verifier); commitmentStore = ICommitmentStore(_commitmentStore); proverManager = _proverManager; + + _updateCheckpoint(latestPublicationId, _genesis); } /// @inheritdoc ICheckpointTracker diff --git a/src/protocol/ETHBridge.sol b/src/protocol/ETHBridge.sol index ba57634a..eb87882b 100644 --- a/src/protocol/ETHBridge.sol +++ b/src/protocol/ETHBridge.sol @@ -6,7 +6,7 @@ import {IETHBridge} from "./IETHBridge.sol"; /// @dev Abstract ETH bridging contract to send native ETH between L1 <-> L2 using storage proofs. /// /// IMPORTANT: No recovery mechanism is implemented in case an account creates a deposit that can't be claimed. -abstract contract ETHBridge is IETHBridge { +contract ETHBridge is IETHBridge { mapping(bytes32 id => bool claimed) private _claimed; /// Incremental nonce to generate unique deposit IDs. @@ -36,10 +36,12 @@ abstract contract ETHBridge is IETHBridge { } /// @inheritdoc IETHBridge - function claimDeposit(ETHDeposit memory deposit, uint256 height, bytes memory proof) + function claimDeposit(ETHDeposit memory ethDeposit, uint256 height, bytes memory proof) external - virtual - returns (bytes32 id); + returns (bytes32 id) + { + id = _generateId(ethDeposit); + } /// @dev Processes deposit claim by id. /// @param id Identifier of the deposit diff --git a/src/protocol/ICheckpointTracker.sol b/src/protocol/ICheckpointTracker.sol index 644613e8..14160ed0 100644 --- a/src/protocol/ICheckpointTracker.sol +++ b/src/protocol/ICheckpointTracker.sol @@ -10,11 +10,7 @@ interface ICheckpointTracker { /// @notice Emitted when the proven checkpoint is updated /// @param publicationId the publication ID of the latest proven checkpoint /// @param commitment the commitment of the latest proven checkpoint - event CheckpointUpdated(uint256 publicationId, bytes32 commitment); - - /// @dev If genesis is not 0 it should be the latest publication - /// @param latestPublicationId the current latest publication ID - error GenesisNotLatestPublication(uint256 latestPublicationId); + event CheckpointUpdated(uint256 indexed publicationId, bytes32 commitment); /// @return _ The last proven checkpoint function getProvenCheckpoint() external view returns (Checkpoint memory); diff --git a/src/protocol/SignalService.sol b/src/protocol/SignalService.sol index 5e4c52b6..a1f2c6c7 100644 --- a/src/protocol/SignalService.sol +++ b/src/protocol/SignalService.sol @@ -4,6 +4,8 @@ pragma solidity ^0.8.28; import {LibSignal} from "../libs/LibSignal.sol"; import {CommitmentStore} from "./CommitmentStore.sol"; import {ETHBridge} from "./ETHBridge.sol"; + +import {ICommitmentStore} from "./ICommitmentStore.sol"; import {ISignalService} from "./ISignalService.sol"; /// @dev SignalService combines secure cross-chain messaging with native token bridging. @@ -44,27 +46,6 @@ contract SignalService is ISignalService, CommitmentStore { emit SignalVerified(sender, value); } - /// @dev Overrides ETHBridge.depositETH to add signaling functionality. - function deposit(address to, bytes memory data) public payable override returns (bytes32 id) { - id = super.deposit(to, data); - id.signal(msg.sender, ETH_BRIDGE_NAMESPACE); - } - - // CHECK: Should this function be non-reentrant? - /// @inheritdoc ETHBridge - /// @dev Overrides ETHBridge.claimDeposit to add signal verification logic. - function claimDeposit(ETHDeposit memory ethDeposit, uint256 height, bytes memory proof) - external - override - returns (bytes32 id) - { - id = _generateId(ethDeposit); - - _verifySignal(height, commitmentPublisher, ethDeposit.from, id, ETH_BRIDGE_NAMESPACE, proof); - - super._processClaimDepositWithId(id, ethDeposit); - } - function _verifySignal( uint256 height, address commitmentPublisher, diff --git a/test/CheckpointTracker.t.sol b/test/CheckpointTracker.t.sol index 861bbc69..69802492 100644 --- a/test/CheckpointTracker.t.sol +++ b/test/CheckpointTracker.t.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.28; -import {Test, console} from "forge-std/Test.sol"; +import {Test, Vm, console} from "forge-std/Test.sol"; import {CheckpointTracker} from "src/protocol/CheckpointTracker.sol"; import {ICheckpointTracker} from "src/protocol/ICheckpointTracker.sol"; @@ -30,17 +30,18 @@ contract CheckpointTrackerTest is Test { function setUp() public { NUM_PUBLICATIONS = 20; - verifier = new NullVerifier(); feed = new PublicationFeed(); - createSampleFeed(); signalService = new SignalService(); tracker = new CheckpointTracker( keccak256(abi.encode("genesis")), address(feed), address(verifier), proverManager, address(signalService) ); + + createSampleFeed(); + vm.prank(rollupOperator); proof = abi.encode("proof"); } @@ -58,18 +59,18 @@ contract CheckpointTrackerTest is Test { new CheckpointTracker(bytes32(0), address(feed), address(verifier), proverManager, address(signalService)); } - function test_constructor_EmitsEvent() public { - bytes32 genesisCommitment = keccak256(abi.encode("genesis")); - ICheckpointTracker.Checkpoint memory genesisCheckpoint = - ICheckpointTracker.Checkpoint({publicationId: 0, commitment: genesisCommitment}); - - vm.expectEmit(); - emit ICheckpointTracker.CheckpointUpdated(genesisCheckpoint); - new CheckpointTracker( - genesisCommitment, address(feed), address(verifier), proverManager, address(signalService) - ); - } - + // function test_constructor_EmitsEvent() public { + // bytes32 genesisCommitment = keccak256(abi.encode("genesis")); + // ICheckpointTracker.Checkpoint memory genesisCheckpoint = + // ICheckpointTracker.Checkpoint({publicationId: 0, commitment: genesisCommitment}); + // + // vm.expectEmit(); + // emit ICheckpointTracker.CheckpointUpdated(genesisCheckpoint.publicationId, genesisCheckpoint.commitment); + // new CheckpointTracker( + // genesisCommitment, address(feed), address(verifier), proverManager, address(signalService) + // ); + // } + // function test_proveTransition_SuccessfulTransition() public { ICheckpointTracker.Checkpoint memory start = ICheckpointTracker.Checkpoint({publicationId: 0, commitment: keccak256(abi.encode("genesis"))}); @@ -78,7 +79,7 @@ contract CheckpointTrackerTest is Test { uint256 numRelevantPublications = 2; vm.expectEmit(); - emit ICheckpointTracker.CheckpointUpdated(end); + emit ICheckpointTracker.CheckpointUpdated(end.publicationId, end.commitment); tracker.proveTransition(start, end, numRelevantPublications, proof); ICheckpointTracker.Checkpoint memory provenCheckpoint = tracker.getProvenCheckpoint(); diff --git a/test/ProverManager.t.sol b/test/ProverManager.t.sol index c5fc81c7..8762399e 100644 --- a/test/ProverManager.t.sol +++ b/test/ProverManager.t.sol @@ -8,17 +8,17 @@ import {ProverManager} from "../src/protocol/taiko_alethia/ProverManager.sol"; import {ICheckpointTracker} from "src/protocol/ICheckpointTracker.sol"; import {IPublicationFeed} from "src/protocol/IPublicationFeed.sol"; import {PublicationFeed} from "src/protocol/PublicationFeed.sol"; -import {SignalService} from "src/protocol/SignalService.sol"; +import {SignalService} from "src/protocol/SignalService.sol"; import {MockCheckpointTracker} from "test/mocks/MockCheckpointTracker.sol"; import {NullVerifier} from "test/mocks/NullVerifier.sol"; contract ProverManagerTest is Test { ProverManager proverManager; MockCheckpointTracker checkpointTracker; + SignalService signalService; NullVerifier verifier; PublicationFeed publicationFeed; - SignalService signalService; uint256 constant DEPOSIT_AMOUNT = 2 ether; // Addresses used for testing. @@ -43,11 +43,9 @@ contract ProverManagerTest is Test { uint256 constant INITIAL_PERIOD = 1; function setUp() public { - signalService = new SignalService(rollupOperator); - checkpointTracker = new MockCheckpointTracker(address(signalService)); publicationFeed = new PublicationFeed(); - vm.prank(rollupOperator); - signalService.setAuthorizedCommitter(address(checkpointTracker)); + signalService = new SignalService(); + checkpointTracker = new MockCheckpointTracker(address(signalService)); // Fund the initial prover so the constructor can receive the required livenessBond. vm.deal(initialProver, 10 ether); diff --git a/test/signal/ETHBridge.t.sol b/test/signal/ETHBridge.t.sol index 7c81e45c..77ddcc16 100644 --- a/test/signal/ETHBridge.t.sol +++ b/test/signal/ETHBridge.t.sol @@ -19,18 +19,26 @@ contract BridgeETHState is BaseState { uint256 public depositAmount = 4 ether; ETHBridge.ETHDeposit public depositOne; + ETHBridge public L1ethBridge; + ETHBridge public L2ethBridge; + // this is a valid deposit ID but is sent via a signal not a deposit // hence "invalid" and should not be claimable bytes32 invalidDepositId = 0xbf8ce3088406c4ddbc32e32404ca006c3ef57f07d5139479f16c9124d6490f2e; function setUp() public virtual override { super.setUp(); + + vm.selectFork(L2Fork); + L2ethBridge = new ETHBridge(); + vm.selectFork(L1Fork); + L1ethBridge = new ETHBridge(); vm.prank(defaultSender); bytes memory emptyData = ""; vm.recordLogs(); - depositIdOne = L1signalService.deposit{value: depositAmount}(defaultSender, emptyData); + depositIdOne = L1ethBridge.deposit{value: depositAmount}(defaultSender, emptyData); Vm.Log[] memory entries = vm.getRecordedLogs(); @@ -47,7 +55,7 @@ contract ETHBridgeTest is BridgeETHState { assertEq(defaultSender.balance, senderBalanceL1 - depositAmount); vm.selectFork(L2Fork); - assertEq(L2signalService.claimed(depositIdOne), false); + assertEq(L2ethBridge.claimed(depositIdOne), false); assertEq(defaultSender.balance, senderBalanceL2); } } @@ -99,9 +107,9 @@ contract ClaimDepositTest is CommitmentStoredState { ISignalService.SignalProof memory signalProof = ISignalService.SignalProof(accountProof, storageProof); bytes memory encodedProof = abi.encode(signalProof); - L2signalService.claimDeposit(depositOne, commitmentHeight, encodedProof); + L2ethBridge.claimDeposit(depositOne, commitmentHeight, encodedProof); - assertEq(address(L2signalService).balance, ETHBridgeInitBalance - depositAmount); + assertEq(address(L2ethBridge).balance, ETHBridgeInitBalance - depositAmount); assertEq(defaultSender.balance, senderBalanceL2 + depositAmount); } @@ -129,9 +137,9 @@ contract ClaimDepositTest is CommitmentStoredState { vm.selectFork(L2Fork); // to be extra sure its not a problem with the proof - L2signalService.verifySignal(commitmentHeight, defaultSender, invalidDepositId, encodedProof); + L2signalService.verifySignal(commitmentHeight, address(anchor), defaultSender, invalidDepositId, encodedProof); // I believe this error means that the proof is not valid for this deposit id vm.expectRevert("MerkleTrie: invalid large internal hash"); - L2signalService.claimDeposit(invalidDeposit, commitmentHeight, encodedProof); + L2ethBridge.claimDeposit(invalidDeposit, commitmentHeight, encodedProof); } } diff --git a/test/signal/SignalService.t.sol b/test/signal/SignalService.t.sol index 9e9bf55a..6fca6aa0 100644 --- a/test/signal/SignalService.t.sol +++ b/test/signal/SignalService.t.sol @@ -48,7 +48,7 @@ contract BaseState is Test { vm.deal(defaultSender, senderBalanceL1); vm.prank(defaultSender); - L1signalService = new SignalService(rollupOperator); + L1signalService = new SignalService(); vm.deal(address(L1signalService), ETHBridgeInitBalance); checkpointTracker = new MockCheckpointTracker(address(L1signalService)); @@ -60,14 +60,11 @@ contract BaseState is Test { vm.deal(defaultSender, senderBalanceL2); vm.prank(defaultSender); - L2signalService = new SignalService(rollupOperator); + L2signalService = new SignalService(); vm.deal(address(L2signalService), ETHBridgeInitBalance); anchor = new MockAnchor(address(L2signalService)); - vm.prank(rollupOperator); - L2signalService.setAuthorizedCommitter(address(anchor)); - // Labels for debugging vm.label(address(L1signalService), "L1signalService"); vm.label(address(L2signalService), "L2signalService"); @@ -132,7 +129,7 @@ contract SendL1SignalTest is SendL1SignalState { uint256 height = 1; anchor.anchor(height, stateRoot); - L2signalService.verifySignal(height, defaultSender, signal, encodedProof); + L2signalService.verifySignal(height, address(anchor), defaultSender, signal, encodedProof); } function test_verifyL1Signal_UsingStorageProof() public { @@ -145,6 +142,6 @@ contract SendL1SignalTest is SendL1SignalState { uint256 height = 1; anchor.anchor(height, storageRoot); - L2signalService.verifySignal(height, defaultSender, signal, encodedProof); + L2signalService.verifySignal(height, address(anchor), defaultSender, signal, encodedProof); } } From 44e8e97349e64e52c1e0a2ef51fdc193f469e44c Mon Sep 17 00:00:00 2001 From: Leo Date: Wed, 30 Apr 2025 18:32:16 +0200 Subject: [PATCH 15/41] fix deployment issues in test --- test/CheckpointTracker.t.sol | 2 +- test/signal/ETHBridge.t.sol | 72 +++++++++------------------------ test/signal/SignalService.t.sol | 39 +++++++++--------- 3 files changed, 39 insertions(+), 74 deletions(-) diff --git a/test/CheckpointTracker.t.sol b/test/CheckpointTracker.t.sol index 69802492..cde07918 100644 --- a/test/CheckpointTracker.t.sol +++ b/test/CheckpointTracker.t.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.28; -import {Test, Vm, console} from "forge-std/Test.sol"; +import {Test, Vm} from "forge-std/Test.sol"; import {CheckpointTracker} from "src/protocol/CheckpointTracker.sol"; import {ICheckpointTracker} from "src/protocol/ICheckpointTracker.sol"; diff --git a/test/signal/ETHBridge.t.sol b/test/signal/ETHBridge.t.sol index f410f7f4..bdabfefd 100644 --- a/test/signal/ETHBridge.t.sol +++ b/test/signal/ETHBridge.t.sol @@ -14,48 +14,49 @@ import {ISignalService} from "src/protocol/ISignalService.sol"; // represents state where a deposit is made on L1 // however, the state root is not yet available on L2 contract BridgeETHState is BaseState { + ETHBridge public L1EthBridge; + ETHBridge public L2EthBridge; + // 0xf9c183d2de58fbeb1a8917170139e980fa1b6e5a358ec83721e11c9f6e25eb18 bytes32 public depositIdOne; uint256 public depositAmount = 4 ether; ETHBridge.ETHDeposit public depositOne; - ETHBridge public L1ethBridge; - ETHBridge public L2ethBridge; - - // this is a valid deposit ID but is sent via a signal not a deposit - // hence "invalid" and should not be claimable - bytes32 invalidDepositId = 0xbf8ce3088406c4ddbc32e32404ca006c3ef57f07d5139479f16c9124d6490f2e; - function setUp() public virtual override { super.setUp(); - vm.selectFork(L2Fork); - L2ethBridge = new ETHBridge(address(L2signalService), address(anchor)); - vm.selectFork(L1Fork); - L1ethBridge = new ETHBridge(address(L1ethBridge), address(checkpointTracker)); + // Deploy L1EthBridge + vm.setNonce(defaultSender, 2); + vm.prank(defaultSender); + L1EthBridge = new ETHBridge(address(L1SignalService), address(checkpointTracker)); + vm.deal(address(L1EthBridge), ETHBridgeInitBalance); vm.prank(defaultSender); bytes memory emptyData = ""; vm.recordLogs(); - depositIdOne = L1ethBridge.deposit{value: depositAmount}(defaultSender, emptyData); + depositIdOne = L1EthBridge.deposit{value: depositAmount}(defaultSender, emptyData); Vm.Log[] memory entries = vm.getRecordedLogs(); depositOne = abi.decode(entries[0].data, (IETHBridge.ETHDeposit)); + vm.selectFork(L2Fork); + // Deploy L2EthBridge + vm.setNonce(defaultSender, 2); vm.prank(defaultSender); - L1signalService.sendSignal(invalidDepositId); + L2EthBridge = new ETHBridge(address(L2SignalService), address(anchor)); + vm.deal(address(L2EthBridge), ETHBridgeInitBalance); } } contract ETHBridgeTest is BridgeETHState { function test_initialDepositState() public { - assertEq(address(L1signalService).balance, ETHBridgeInitBalance + depositAmount); + assertEq(address(L1SignalService).balance, ETHBridgeInitBalance + depositAmount); assertEq(defaultSender.balance, senderBalanceL1 - depositAmount); vm.selectFork(L2Fork); - assertEq(L2ethBridge.claimed(depositIdOne), false); + assertEq(L2EthBridge.claimed(depositIdOne), false); assertEq(defaultSender.balance, senderBalanceL2); } } @@ -87,15 +88,6 @@ contract CommitmentStoredState is BridgeETHState { storageProofArr[1] = hex"f843a03e489ae456a515de885d39c5188bcd6169574db5667aa9ba76cfe7c1e9afc5fea1a01731af609b4a0951d3773ff202fa03d48b0d9db4630773c1330747c674c86ea1"; } - - function storageProofInvalidDeposit() public pure returns (bytes[] memory storageProofArr) { - storageProofArr = new bytes[](2); - - storageProofArr[0] = - hex"f89180808080a015ae32d5986f7e597e8a4db519e6640db0eeb8271f30c7691dc58cd99b9cae8da0189ef9b745baf1cac397de18cd0daa0080adc277514b3424e51add1fa0560caa8080808080a0f4984a11f61a2921456141df88de6e1a710d28681b91af794c5a721e47839cd780a07922f462ebc1e5dbf5d9cf69804cfb38646a24cce553010811b89d913f1e0544808080"; - storageProofArr[1] = - hex"f843a03e4b9f05a3709e8b7aeb7ad23c6304cc7c352036e185309eb9ca85a9d479a4bca1a09a30c5e99dafa3ca4343a7bbe5c7ae498be2a41a4ff6743822305b8acfeb183d"; - } } contract ClaimDepositTest is CommitmentStoredState { @@ -107,37 +99,9 @@ contract ClaimDepositTest is CommitmentStoredState { ISignalService.SignalProof memory signalProof = ISignalService.SignalProof(accountProof, storageProof); bytes memory encodedProof = abi.encode(signalProof); - L2ethBridge.claimDeposit(depositOne, commitmentHeight, encodedProof); + L2EthBridge.claimDeposit(depositOne, commitmentHeight, encodedProof); - assertEq(address(L2ethBridge).balance, ETHBridgeInitBalance - depositAmount); + assertEq(address(L2EthBridge).balance, ETHBridgeInitBalance - depositAmount); assertEq(defaultSender.balance, senderBalanceL2 + depositAmount); } - - function test_claimDeposit_RevertWhen_SentViaSignalNotDeposit() public { - bytes[] memory accountProof = accountProofSignalService(); - bytes[] memory storageProof = storageProofInvalidDeposit(); - ISignalService.SignalProof memory signalProof = ISignalService.SignalProof(accountProof, storageProof); - bytes memory encodedProof = abi.encode(signalProof); - - ETHBridge.ETHDeposit memory invalidDeposit; - invalidDeposit.nonce = 1; - invalidDeposit.from = defaultSender; - invalidDeposit.to = defaultSender; - invalidDeposit.amount = depositAmount; - invalidDeposit.data = ""; - - vm.selectFork(L1Fork); - bool storedInGenericNamespace = L1signalService.isSignalStored(invalidDepositId, defaultSender); - assertTrue(storedInGenericNamespace); - - bool storedInEthBridgeNamespace = L1signalService.isSignalStored(invalidDepositId, defaultSender); - assertFalse(storedInEthBridgeNamespace); - - vm.selectFork(L2Fork); - // to be extra sure its not a problem with the proof - L2signalService.verifySignal(commitmentHeight, address(anchor), defaultSender, invalidDepositId, encodedProof); - // I believe this error means that the proof is not valid for this deposit id - vm.expectRevert("MerkleTrie: invalid large internal hash"); - L2ethBridge.claimDeposit(invalidDeposit, commitmentHeight, encodedProof); - } } diff --git a/test/signal/SignalService.t.sol b/test/signal/SignalService.t.sol index 5e60aab1..f4cf7afd 100644 --- a/test/signal/SignalService.t.sol +++ b/test/signal/SignalService.t.sol @@ -18,8 +18,9 @@ import {MockCheckpointTracker} from "test/mocks/MockCheckpointTracker.sol"; /// State where there are no signals. contract BaseState is Test { - SignalService public L1signalService; - SignalService public L2signalService; + SignalService public L1SignalService; + SignalService public L2SignalService; + MockCheckpointTracker public checkpointTracker; MockAnchor public anchor; @@ -47,29 +48,29 @@ contract BaseState is Test { // Sender has 10 eth in the L1 fork vm.deal(defaultSender, senderBalanceL1); + // Deploy L1SignalService vm.prank(defaultSender); - L1signalService = new SignalService(); - vm.deal(address(L1signalService), ETHBridgeInitBalance); + L1SignalService = new SignalService(); - checkpointTracker = new MockCheckpointTracker(address(L1signalService)); + checkpointTracker = new MockCheckpointTracker(address(L1SignalService)); L2Fork = vm.createFork("L2"); - vm.selectFork(L2Fork); // Sender has 0 eth in the L2 fork vm.deal(defaultSender, senderBalanceL2); + vm.selectFork(L2Fork); + + // Deploy L2SignalService vm.prank(defaultSender); - L2signalService = new SignalService(); - vm.deal(address(L2signalService), ETHBridgeInitBalance); + L2SignalService = new SignalService(); - anchor = new MockAnchor(address(L2signalService)); + // Deploy MockAnchor + anchor = new MockAnchor(address(L2SignalService)); // Labels for debugging - vm.label(address(L1signalService), "L1signalService"); - vm.label(address(L2signalService), "L2signalService"); - vm.label(address(checkpointTracker), "CheckpointTracker"); - vm.label(address(anchor), "MockAnchor"); + vm.label(address(L1SignalService), "L1SignalService"); + vm.label(address(L2SignalService), "L2SignalService"); } } @@ -92,7 +93,7 @@ contract SendL1SignalState is BaseState { super.setUp(); vm.selectFork(L1Fork); vm.prank(defaultSender); - L1signalService.sendSignal(signal); + L1SignalService.sendSignal(signal); } // NOTE: This proof is only valid for the defaultSender address and signal @@ -121,6 +122,10 @@ contract SendL1SignalState is BaseState { contract SendL1SignalTest is SendL1SignalState { function test_verifyL1Signal_UsingBothProofs() public { + vm.selectFork(L1Fork); + bool isSignalStored = L2SignalService.isSignalStored(signal, defaultSender); + assertTrue(isSignalStored); + vm.selectFork(L2Fork); ISignalService.SignalProof memory signalProof = ISignalService.SignalProof(accountProof(), storageProof()); @@ -129,10 +134,6 @@ contract SendL1SignalTest is SendL1SignalState { uint256 height = 1; anchor.anchor(height, stateRoot); - bool isSignalStored = L2signalService.isSignalStored(signal, defaultSender); - - assertTrue(isSignalStored); - - L2signalService.verifySignal(height, address(anchor), defaultSender, signal, encodedProof); + L2SignalService.verifySignal(height, address(anchor), defaultSender, signal, encodedProof); } } From ac12b20c1f8942ce925a746d2a1221e78eccd697 Mon Sep 17 00:00:00 2001 From: Leo Date: Wed, 30 Apr 2025 19:45:36 +0200 Subject: [PATCH 16/41] fix test 1/n --- offchain/generic_signal_proof.rs | 4 ++-- src/libs/LibSignal.sol | 4 +++- test/signal/SignalService.t.sol | 26 ++++++++++---------------- 3 files changed, 15 insertions(+), 19 deletions(-) diff --git a/offchain/generic_signal_proof.rs b/offchain/generic_signal_proof.rs index fb600b0f..089730ed 100644 --- a/offchain/generic_signal_proof.rs +++ b/offchain/generic_signal_proof.rs @@ -4,7 +4,7 @@ use alloy::primitives::{Address, B256}; use eyre::{eyre, Result}; mod signal_slot; -use signal_slot::{get_signal_slot, NameSpaceConst}; +use signal_slot::get_signal_slot; mod utils; use utils::{deploy_signal_service, get_proofs, get_provider}; @@ -48,7 +48,7 @@ async fn main() -> Result<()> { let builder = signal_service.sendSignal(signal); builder.send().await?.watch().await?; - let slot = get_signal_slot(&signal, &sender, NameSpaceConst::Signal); + let slot = get_signal_slot(&signal, &sender); get_proofs(&provider, slot, &signal_service).await?; Ok(()) diff --git a/src/libs/LibSignal.sol b/src/libs/LibSignal.sol index c48fbc67..b260c2d6 100644 --- a/src/libs/LibSignal.sol +++ b/src/libs/LibSignal.sol @@ -31,7 +31,9 @@ library LibSignal { /// @dev Signal a `value` at a namespaced slot. See `deriveSlot`. function signal(bytes32 value, address account) internal returns (bytes32) { bytes32 slot = deriveSlot(value, account); - slot.getBooleanSlot().value = true; + assembly { + sstore(slot, true) + } return slot; } diff --git a/test/signal/SignalService.t.sol b/test/signal/SignalService.t.sol index f4cf7afd..6734bad6 100644 --- a/test/signal/SignalService.t.sol +++ b/test/signal/SignalService.t.sol @@ -51,6 +51,7 @@ contract BaseState is Test { // Deploy L1SignalService vm.prank(defaultSender); L1SignalService = new SignalService(); + vm.label(address(L1SignalService), "L1SignalService"); checkpointTracker = new MockCheckpointTracker(address(L1SignalService)); @@ -64,13 +65,10 @@ contract BaseState is Test { // Deploy L2SignalService vm.prank(defaultSender); L2SignalService = new SignalService(); + vm.label(address(L2SignalService), "L2SignalService"); // Deploy MockAnchor anchor = new MockAnchor(address(L2SignalService)); - - // Labels for debugging - vm.label(address(L1SignalService), "L1SignalService"); - vm.label(address(L2SignalService), "L2SignalService"); } } @@ -86,8 +84,8 @@ contract SendL1SignalState is BaseState { // 0xe321d900f3fd366734e2d071e30949ded20c27fd638f1a059390091c643b62c5 bytes32 public signal = keccak256(abi.encode(value)); - bytes32 public stateRoot = hex"9bd008a5a90cbe90d20f4b5b0586b27207737e3b191079fff145b5b9c901c455"; - bytes32 public storageRoot = hex"3ce30cf67714952c040433caae45d61b2435a503ea618de43375e00a6c174bed"; + bytes32 public stateRoot = hex"afeb12112708b753dee82f80647b152125d8d31c9d58cd6eb5b55c80f130bcfd"; + bytes32 public storageRoot = hex"04bdf08088bbf36329fe89a42e20579b0a9222b2301b4787eeecc03ef88bc507"; function setUp() public virtual override { super.setUp(); @@ -99,12 +97,8 @@ contract SendL1SignalState is BaseState { // NOTE: This proof is only valid for the defaultSender address and signal // For the given stateRoot and storageRoot function storageProof() public pure returns (bytes[] memory storageProofArr) { - storageProofArr = new bytes[](2); - - storageProofArr[0] = - hex"f851a023a999cb10807a0eb6e21969ab26264d39c395eeb67e929a3c0256dc8a56876c808080a015ae32d5986f7e597e8a4db519e6640db0eeb8271f30c7691dc58cd99b9cae8d808080808080808080808080"; - storageProofArr[1] = - hex"f843a032fbc053d21d1450a7eb877000b701317b2bf5fa5907266fb096a14feb00db21a1a029d86d891992a0a891ff8cc9477d178707448d6a8f2f8a15fe1ca735238569e4"; + storageProofArr = new bytes[](1); + storageProofArr[0] = hex"e3a120b85a01aaebad61d59b54ccfbfab9d3934481964f6208167ef0868494605ddc8401"; } // NOTE: This proof is only valid for the given stateRoot and storageRoot @@ -112,18 +106,18 @@ contract SendL1SignalState is BaseState { accountProofArr = new bytes[](3); accountProofArr[0] = - hex"f90131a0b91a8b7a7e9d3eab90afd81da3725030742f663c6ed8c26657bf00d842a9f4aaa01689b2a5203afd9ea0a0ca3765e4a538c7176e53eac1f8307a344ffc3c6176558080a0d6df02675ad4091927c8fb88eccc1eae7e8ee758327a4bb4f006dcba84f08b8fa0f08abfe349659ee092513d2474566e286f6c6d9b2930e29d3e04b283c2b25dfd8080a04b29efa44ecf50c19b34950cf1d0f05e00568bcc873120fbea9a4e8439de0962a0d0a1bfe5b45d2d863a794f016450a4caca04f3b599e8d1652afca8b752935fd880a0bf9b09e442e044778b354abbadb5ec049d7f5e8b585c3966d476c4fbc9a181d28080a0659892986d3408997f74c30845ede10e8dd4892dd7e39a153dee6fdc2f43d4f8a0e5c557a0ce3894afeb44c37f3d24247f67dc76a174d8cacc360c1210eef60a7680"; + hex"f90131a0b91a8b7a7e9d3eab90afd81da3725030742f663c6ed8c26657bf00d842a9f4aaa01689b2a5203afd9ea0a0ca3765e4a538c7176e53eac1f8307a344ffc3c6176558080a0089013914176be36b11c71ff3b8a136c0a47bcf74b4ef922567c19d36814a4c7a09491a0bc16e49dd46059e35bdafadbd765d8daa1b1088151eee07161124824a28080a04b29efa44ecf50c19b34950cf1d0f05e00568bcc873120fbea9a4e8439de0962a0d0a1bfe5b45d2d863a794f016450a4caca04f3b599e8d1652afca8b752935fd880a0bf9b09e442e044778b354abbadb5ec049d7f5e8b585c3966d476c4fbc9a181d28080a0bbe068d0fb4ae57d0a82da1469f15383b562d29d001e7bd9693b0e5fd1b5e998a0e5c557a0ce3894afeb44c37f3d24247f67dc76a174d8cacc360c1210eef60a7680"; accountProofArr[1] = - hex"f85180808080a0888ad3a3c3b85d0e75d6cdf73c5918b34e6401c0378c0260f675b5ab2f16b29080808080a074ae0767a40fc6fff780050f46a50f6b39ca4edb7faa9669108157a1cd96f40980808080808080"; + hex"f85180808080a000b85deecd2beb8cead803f57b060d758bd1a3109255e2e8b7c120d08dd06c3f80808080a074ae0767a40fc6fff780050f46a50f6b39ca4edb7faa9669108157a1cd96f40980808080808080"; accountProofArr[2] = - hex"f869a020e659e60b21cc961f64ad47f20523c1d329d4bbda245ef3940a76dc89d0911bb846f8440180a03ce30cf67714952c040433caae45d61b2435a503ea618de43375e00a6c174beda0c0ba2d0c0eedb3a5fcd78a0aa384e2399651f191eccdd9576bec86c1c300607f"; + hex"f869a020e659e60b21cc961f64ad47f20523c1d329d4bbda245ef3940a76dc89d0911bb846f8440180a004bdf08088bbf36329fe89a42e20579b0a9222b2301b4787eeecc03ef88bc507a0c39e34a085b4c4074cb6f667a85c9d06b08fb97dcde27b211f9cd0648028fb84"; } } contract SendL1SignalTest is SendL1SignalState { function test_verifyL1Signal_UsingBothProofs() public { vm.selectFork(L1Fork); - bool isSignalStored = L2SignalService.isSignalStored(signal, defaultSender); + bool isSignalStored = L1SignalService.isSignalStored(signal, defaultSender); assertTrue(isSignalStored); vm.selectFork(L2Fork); From cccab08ea3d4120ca7a6319d45b0fd3631a6133a Mon Sep 17 00:00:00 2001 From: Leo Date: Thu, 1 May 2025 10:18:16 +0200 Subject: [PATCH 17/41] fix bug in libsignal --- src/libs/LibSignal.sol | 10 +++++----- test/signal/SignalService.t.sol | 11 +++++------ 2 files changed, 10 insertions(+), 11 deletions(-) diff --git a/src/libs/LibSignal.sol b/src/libs/LibSignal.sol index b260c2d6..53085ca4 100644 --- a/src/libs/LibSignal.sol +++ b/src/libs/LibSignal.sol @@ -31,9 +31,7 @@ library LibSignal { /// @dev Signal a `value` at a namespaced slot. See `deriveSlot`. function signal(bytes32 value, address account) internal returns (bytes32) { bytes32 slot = deriveSlot(value, account); - assembly { - sstore(slot, true) - } + slot.getBooleanSlot().value = true; return slot; } @@ -60,8 +58,10 @@ library LibSignal { bytes32 root, bytes[] memory accountProof, bytes[] memory storageProof - ) internal pure { + ) internal view { bytes32 encodedBool = bytes32(uint256(1)); - LibTrieProof.verifyMerkleProof(root, sender, deriveSlot(value, sender), encodedBool, accountProof, storageProof); + LibTrieProof.verifyMerkleProof( + root, address(this), deriveSlot(value, sender), encodedBool, accountProof, storageProof + ); } } diff --git a/test/signal/SignalService.t.sol b/test/signal/SignalService.t.sol index 6734bad6..7bf325fe 100644 --- a/test/signal/SignalService.t.sol +++ b/test/signal/SignalService.t.sol @@ -84,8 +84,8 @@ contract SendL1SignalState is BaseState { // 0xe321d900f3fd366734e2d071e30949ded20c27fd638f1a059390091c643b62c5 bytes32 public signal = keccak256(abi.encode(value)); - bytes32 public stateRoot = hex"afeb12112708b753dee82f80647b152125d8d31c9d58cd6eb5b55c80f130bcfd"; - bytes32 public storageRoot = hex"04bdf08088bbf36329fe89a42e20579b0a9222b2301b4787eeecc03ef88bc507"; + bytes32 public stateRoot = 0xbea626defff8745e6a6fd162f3480615a0e6eb285b8a8ba54b31fe2ffa950874; + bytes32 public storageRoot = 0x04bdf08088bbf36329fe89a42e20579b0a9222b2301b4787eeecc03ef88bc507; function setUp() public virtual override { super.setUp(); @@ -104,13 +104,12 @@ contract SendL1SignalState is BaseState { // NOTE: This proof is only valid for the given stateRoot and storageRoot function accountProof() public pure returns (bytes[] memory accountProofArr) { accountProofArr = new bytes[](3); - accountProofArr[0] = - hex"f90131a0b91a8b7a7e9d3eab90afd81da3725030742f663c6ed8c26657bf00d842a9f4aaa01689b2a5203afd9ea0a0ca3765e4a538c7176e53eac1f8307a344ffc3c6176558080a0089013914176be36b11c71ff3b8a136c0a47bcf74b4ef922567c19d36814a4c7a09491a0bc16e49dd46059e35bdafadbd765d8daa1b1088151eee07161124824a28080a04b29efa44ecf50c19b34950cf1d0f05e00568bcc873120fbea9a4e8439de0962a0d0a1bfe5b45d2d863a794f016450a4caca04f3b599e8d1652afca8b752935fd880a0bf9b09e442e044778b354abbadb5ec049d7f5e8b585c3966d476c4fbc9a181d28080a0bbe068d0fb4ae57d0a82da1469f15383b562d29d001e7bd9693b0e5fd1b5e998a0e5c557a0ce3894afeb44c37f3d24247f67dc76a174d8cacc360c1210eef60a7680"; + hex"f90131a0b91a8b7a7e9d3eab90afd81da3725030742f663c6ed8c26657bf00d842a9f4aaa01689b2a5203afd9ea0a0ca3765e4a538c7176e53eac1f8307a344ffc3c6176558080a0fef0c7c87dc696c2c46d604ce29ffaf22a6d8d5dd27a870bf7a1e5876ca1fb54a0020661ea7d89845cd171e352778b18641eff9efb62028c299df4151e24f5fb118080a04b29efa44ecf50c19b34950cf1d0f05e00568bcc873120fbea9a4e8439de0962a0d0a1bfe5b45d2d863a794f016450a4caca04f3b599e8d1652afca8b752935fd880a0bf9b09e442e044778b354abbadb5ec049d7f5e8b585c3966d476c4fbc9a181d28080a0ec044885b8e2bda68bf3d4f74c7fb65a5f486e8e28f60779434e8665d673e462a0e5c557a0ce3894afeb44c37f3d24247f67dc76a174d8cacc360c1210eef60a7680"; accountProofArr[1] = - hex"f85180808080a000b85deecd2beb8cead803f57b060d758bd1a3109255e2e8b7c120d08dd06c3f80808080a074ae0767a40fc6fff780050f46a50f6b39ca4edb7faa9669108157a1cd96f40980808080808080"; + hex"f85180808080a0428f207aafab0fd01965cc7f77e3384ea7a4d8394fed11ebfd61af33c1f8edbc80808080a074ae0767a40fc6fff780050f46a50f6b39ca4edb7faa9669108157a1cd96f40980808080808080"; accountProofArr[2] = - hex"f869a020e659e60b21cc961f64ad47f20523c1d329d4bbda245ef3940a76dc89d0911bb846f8440180a004bdf08088bbf36329fe89a42e20579b0a9222b2301b4787eeecc03ef88bc507a0c39e34a085b4c4074cb6f667a85c9d06b08fb97dcde27b211f9cd0648028fb84"; + hex"f869a020e659e60b21cc961f64ad47f20523c1d329d4bbda245ef3940a76dc89d0911bb846f8440180a004bdf08088bbf36329fe89a42e20579b0a9222b2301b4787eeecc03ef88bc507a01888e1db1505f5e9b5fe0dc6dbe14a7be2f812e4de535528fe243e01a49e8b65"; } } From 7f1451492a8ec3417ef66c245f86e7cb2c834797 Mon Sep 17 00:00:00 2001 From: Leo Date: Thu, 1 May 2025 12:53:55 +0200 Subject: [PATCH 18/41] broken --- offchain/deposit_signal_proof.rs | 12 +++++++---- offchain/utils.rs | 4 ---- src/protocol/IETHBridge.sol | 1 - test/signal/ETHBridge.t.sol | 35 +++++++++++++++++++++----------- test/signal/SignalService.t.sol | 11 ---------- 5 files changed, 31 insertions(+), 32 deletions(-) diff --git a/offchain/deposit_signal_proof.rs b/offchain/deposit_signal_proof.rs index ee8a338e..368dad13 100644 --- a/offchain/deposit_signal_proof.rs +++ b/offchain/deposit_signal_proof.rs @@ -1,4 +1,4 @@ -use alloy::primitives::{address, bytes, U256}; +use alloy::primitives::{address, bytes, B256, U256}; use eyre::Result; mod utils; @@ -24,7 +24,7 @@ async fn main() -> Result<()> { let amount = U256::from(4000000000000000000_u128); // This is the anchor - let trusted_publisher = address!("0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266"); + let trusted_publisher = address!("0x9f1ac54BEF0DD2f6f3462EA0fa94fC62300d3a8e"); let (provider, _anvil) = get_provider()?; @@ -38,7 +38,9 @@ async fn main() -> Result<()> { let eth_bridge = deploy_eth_bridge(&provider, *signal_service.address(), trusted_publisher).await?; - println!("Sending ETH deposit signal..."); + println!("Deployed ETH bridge at address: {}", eth_bridge.address()); + + println!("Sending ETH deposit..."); let builder = eth_bridge.deposit(sender, data).value(amount); let tx = builder.send().await?.get_receipt().await?; @@ -46,8 +48,10 @@ async fn main() -> Result<()> { // possibly a better way to do this, but this works :) let receipt_logs = tx.logs().get(0).unwrap().topics(); let deposit_id = receipt_logs.get(1).unwrap(); + let depid: B256 = + "0xf9c183d2de58fbeb1a8917170139e980fa1b6e5a358ec83721e11c9f6e25eb18".parse()?; - let slot = get_signal_slot(deposit_id, &sender); + let slot = get_signal_slot(&depid, ð_bridge.address()); get_proofs(&provider, slot, &signal_service).await?; Ok(()) diff --git a/offchain/utils.rs b/offchain/utils.rs index 24ddb400..a8ae8f4d 100644 --- a/offchain/utils.rs +++ b/offchain/utils.rs @@ -14,10 +14,6 @@ use serde_json::to_string_pretty; const BLOCK_TIME: u64 = 5; -// NOTE: This needs to match the address of the rollup operator in the tests -// to ensure the signal service is deployed to the same address. -const ROLLUP_OPERATOR: Address = address!("0xCf03Dd0a894Ef79CB5b601A43C4b25E3Ae4c67eD"); - sol!( #[allow(missing_docs)] #[sol(rpc)] diff --git a/src/protocol/IETHBridge.sol b/src/protocol/IETHBridge.sol index a6f3ed3d..e0374de3 100644 --- a/src/protocol/IETHBridge.sol +++ b/src/protocol/IETHBridge.sol @@ -6,7 +6,6 @@ pragma solidity ^0.8.28; /// These can be created by sending value to the `deposit` function. Later, the receiver can /// claim the deposit on the destination chain by using a storage proof. interface IETHBridge { - // TODO: Think about gas? struct ETHDeposit { // The nonce of the deposit uint256 nonce; diff --git a/test/signal/ETHBridge.t.sol b/test/signal/ETHBridge.t.sol index bdabfefd..45452e2f 100644 --- a/test/signal/ETHBridge.t.sol +++ b/test/signal/ETHBridge.t.sol @@ -22,6 +22,12 @@ contract BridgeETHState is BaseState { uint256 public depositAmount = 4 ether; ETHBridge.ETHDeposit public depositOne; + // ETHBridge which has a large amount of ETH + uint256 public ETHBridgeInitBalance = 100 ether; + + uint256 public senderBalanceL1 = 10 ether; + uint256 public senderBalanceL2 = 0 ether; + function setUp() public virtual override { super.setUp(); @@ -33,15 +39,18 @@ contract BridgeETHState is BaseState { vm.deal(address(L1EthBridge), ETHBridgeInitBalance); vm.prank(defaultSender); + vm.deal(defaultSender, senderBalanceL1); bytes memory emptyData = ""; - vm.recordLogs(); depositIdOne = L1EthBridge.deposit{value: depositAmount}(defaultSender, emptyData); + } +} - Vm.Log[] memory entries = vm.getRecordedLogs(); - - depositOne = abi.decode(entries[0].data, (IETHBridge.ETHDeposit)); - +// Need to separate the states or it crashes +contract BridgeETHState2 is BridgeETHState { + function setUp() public virtual override { + super.setUp(); vm.selectFork(L2Fork); + vm.deal(defaultSender, senderBalanceL2); // Deploy L2EthBridge vm.setNonce(defaultSender, 2); vm.prank(defaultSender); @@ -50,9 +59,10 @@ contract BridgeETHState is BaseState { } } -contract ETHBridgeTest is BridgeETHState { +contract ETHBridgeTest is BridgeETHState2 { function test_initialDepositState() public { - assertEq(address(L1SignalService).balance, ETHBridgeInitBalance + depositAmount); + vm.selectFork(L1Fork); + assertEq(address(L1EthBridge).balance, ETHBridgeInitBalance + depositAmount); assertEq(defaultSender.balance, senderBalanceL1 - depositAmount); vm.selectFork(L2Fork); @@ -92,16 +102,17 @@ contract CommitmentStoredState is BridgeETHState { contract ClaimDepositTest is CommitmentStoredState { function test_claimDeposit() public { - vm.selectFork(L2Fork); - bytes[] memory accountProof = accountProofSignalService(); bytes[] memory storageProof = storageProofDepositOne(); ISignalService.SignalProof memory signalProof = ISignalService.SignalProof(accountProof, storageProof); bytes memory encodedProof = abi.encode(signalProof); - L2EthBridge.claimDeposit(depositOne, commitmentHeight, encodedProof); + IETHBridge.ETHDeposit memory depositOne = + IETHBridge.ETHDeposit(0, defaultSender, defaultSender, depositAmount, bytes("")); - assertEq(address(L2EthBridge).balance, ETHBridgeInitBalance - depositAmount); - assertEq(defaultSender.balance, senderBalanceL2 + depositAmount); + vm.selectFork(L2Fork); + L2EthBridge.claimDeposit(depositOne, commitmentHeight, encodedProof); + // assertEq(address(L2EthBridge).balance, ETHBridgeInitBalance - depositAmount); + // assertEq(defaultSender.balance, senderBalanceL2 + depositAmount); } } diff --git a/test/signal/SignalService.t.sol b/test/signal/SignalService.t.sol index 7bf325fe..06f152ac 100644 --- a/test/signal/SignalService.t.sol +++ b/test/signal/SignalService.t.sol @@ -36,17 +36,10 @@ contract BaseState is Test { // BUG: For some reason the default sender is not being changed even though // it's is the foundry.toml file, therefore we need to set it manually address public defaultSender = address(0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266); - uint256 public senderBalanceL1 = 10 ether; - uint256 public senderBalanceL2 = 0 ether; - - // Signal service is also the ETHBridge which has a large amount of ETH - uint256 public ETHBridgeInitBalance = 100 ether; function setUp() public virtual { L1Fork = vm.createFork("L1"); vm.selectFork(L1Fork); - // Sender has 10 eth in the L1 fork - vm.deal(defaultSender, senderBalanceL1); // Deploy L1SignalService vm.prank(defaultSender); @@ -56,10 +49,6 @@ contract BaseState is Test { checkpointTracker = new MockCheckpointTracker(address(L1SignalService)); L2Fork = vm.createFork("L2"); - - // Sender has 0 eth in the L2 fork - vm.deal(defaultSender, senderBalanceL2); - vm.selectFork(L2Fork); // Deploy L2SignalService From 2608edb5d52fe14d07a2fdf69a6ee96f90aded10 Mon Sep 17 00:00:00 2001 From: Nikesh Nazareth Date: Mon, 5 May 2025 14:45:13 +0200 Subject: [PATCH 19/41] Remove unnecessary import --- src/protocol/CommitmentStore.sol | 1 - 1 file changed, 1 deletion(-) diff --git a/src/protocol/CommitmentStore.sol b/src/protocol/CommitmentStore.sol index 1541f383..dafa8d18 100644 --- a/src/protocol/CommitmentStore.sol +++ b/src/protocol/CommitmentStore.sol @@ -1,7 +1,6 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.28; -import {ICheckpointTracker} from "./ICheckpointTracker.sol"; import {ICommitmentStore} from "./ICommitmentStore.sol"; import {SafeCast} from "@openzeppelin/contracts/utils/math/SafeCast.sol"; From df6f9e4e7674453932afd11d38feadb3bfd501f6 Mon Sep 17 00:00:00 2001 From: Nikesh Nazareth Date: Mon, 5 May 2025 15:36:57 +0200 Subject: [PATCH 20/41] Rename storage commitment error message and change comment The previous comment was ambiguous about whether we support full state roots. I think `StateProofNotSupported` as an error is unclear, and might actually state the opposite of the error. For context - the `LibSignal` library attempts to prove a storage location inside a root. - we expect this to be a state root. The proof is multi-step: first prove the account's storage root within the state proof, then prove the storage location against the storage root. - the library also supports skipping step 1 and treating the root as a storage root directly. The signal service does not. At some point we will have to generalise the commitment and storage proof mechanism to support block hashes as commitments (which is natural for rollups) and arbitrary state for appchains. In the mean time we are being opinionated that the commitment is a state root. --- src/protocol/ISignalService.sol | 4 ++-- src/protocol/SignalService.sol | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/protocol/ISignalService.sol b/src/protocol/ISignalService.sol index 551c1293..6875bed1 100644 --- a/src/protocol/ISignalService.sol +++ b/src/protocol/ISignalService.sol @@ -25,8 +25,8 @@ interface ISignalService { /// @param value Value that was signaled event SignalVerified(address indexed sender, bytes32 value); - /// @dev We require a storage proof to be submitted - error StateProofNotSupported(); + /// @dev We require the commitment to contain a state root (with an embedded storage root) + error StorageRootCommitmentNotSupported(); /// @dev Stores a data signal and returns its storage location. /// @param value Data to be stored (signalled) diff --git a/src/protocol/SignalService.sol b/src/protocol/SignalService.sol index a1f2c6c7..88775633 100644 --- a/src/protocol/SignalService.sol +++ b/src/protocol/SignalService.sol @@ -62,8 +62,8 @@ contract SignalService is ISignalService, CommitmentStore { bytes[] memory accountProof = signalProof.accountProof; bytes[] memory storageProof = signalProof.storageProof; // if there is no account proof, verify signal will treat root as a storage root - // instead of a full state root which we currently do not support - require(accountProof.length != 0, StateProofNotSupported()); + // for now, we only support full state roots + require(accountProof.length != 0, StorageRootCommitmentNotSupported()); value.verifySignal(namespace, sender, root, accountProof, storageProof); } } From 931c8b0fb6dd9a92024e72d87dc6004fbaaf9435 Mon Sep 17 00:00:00 2001 From: Nikesh Nazareth Date: Tue, 6 May 2025 12:40:28 +1000 Subject: [PATCH 21/41] Remove stray "ff" in comment --- src/protocol/IETHBridge.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/protocol/IETHBridge.sol b/src/protocol/IETHBridge.sol index e0374de3..9bc6bd4d 100644 --- a/src/protocol/IETHBridge.sol +++ b/src/protocol/IETHBridge.sol @@ -50,7 +50,7 @@ interface IETHBridge { /// @dev Claims an ETH deposit created on by the sender (`from`) with `nonce`. The `value` ETH claimed is /// sent to the receiver (`to`) after verifying a storage proof. - /// @param ethDeposit The ETH deposit struct ff + /// @param ethDeposit The ETH deposit struct /// @param height The `height` of the checkpoint on the source chain (i.e. the block number or commitmentId) /// @param proof Encoded proof of the storage slot where the deposit is stored function claimDeposit(ETHDeposit memory ethDeposit, uint256 height, bytes memory proof) external; From a22949ed1874341c09f1a1192465881cb0936bcb Mon Sep 17 00:00:00 2001 From: Nikesh Nazareth Date: Tue, 6 May 2025 12:43:47 +1000 Subject: [PATCH 22/41] Fix typo --- src/protocol/IETHBridge.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/protocol/IETHBridge.sol b/src/protocol/IETHBridge.sol index 9bc6bd4d..601253a4 100644 --- a/src/protocol/IETHBridge.sol +++ b/src/protocol/IETHBridge.sol @@ -48,7 +48,7 @@ interface IETHBridge { /// @param data Any calldata to be sent to the receiver in case of a contract function deposit(address to, bytes memory data) external payable returns (bytes32 id); - /// @dev Claims an ETH deposit created on by the sender (`from`) with `nonce`. The `value` ETH claimed is + /// @dev Claims an ETH deposit created by the sender (`from`) with `nonce`. The `value` ETH claimed is /// sent to the receiver (`to`) after verifying a storage proof. /// @param ethDeposit The ETH deposit struct /// @param height The `height` of the checkpoint on the source chain (i.e. the block number or commitmentId) From 01875643b97e6fb7659323b0e3ae6dfa193a44e2 Mon Sep 17 00:00:00 2001 From: Nikesh Nazareth Date: Tue, 6 May 2025 12:44:58 +1000 Subject: [PATCH 23/41] Replace commitmentId with publicationId when describing commitment "height" --- src/protocol/IETHBridge.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/protocol/IETHBridge.sol b/src/protocol/IETHBridge.sol index 601253a4..03245005 100644 --- a/src/protocol/IETHBridge.sol +++ b/src/protocol/IETHBridge.sol @@ -51,7 +51,7 @@ interface IETHBridge { /// @dev Claims an ETH deposit created by the sender (`from`) with `nonce`. The `value` ETH claimed is /// sent to the receiver (`to`) after verifying a storage proof. /// @param ethDeposit The ETH deposit struct - /// @param height The `height` of the checkpoint on the source chain (i.e. the block number or commitmentId) + /// @param height The `height` of the checkpoint on the source chain (i.e. the block number or publicationId) /// @param proof Encoded proof of the storage slot where the deposit is stored function claimDeposit(ETHDeposit memory ethDeposit, uint256 height, bytes memory proof) external; } From 1b47fde73cd47cb24b5e2369b4a6f8c4ad8ee87a Mon Sep 17 00:00:00 2001 From: Nikesh Nazareth Date: Tue, 6 May 2025 12:54:53 +1000 Subject: [PATCH 24/41] Remove obsolete "namespace" from a comment --- src/libs/LibSignal.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libs/LibSignal.sol b/src/libs/LibSignal.sol index 53085ca4..4aceefc3 100644 --- a/src/libs/LibSignal.sol +++ b/src/libs/LibSignal.sol @@ -23,7 +23,7 @@ library LibSignal { return slot.getBooleanSlot().value == true; } - /// @dev Signal a `value` at a namespaced slot for the current `msg.sender` and namespace. + /// @dev Signal a `value` at a namespaced slot for the current `msg.sender`. function signal(bytes32 value) internal returns (bytes32) { return signal(value, msg.sender); } From 61cfd989fb7e68daaae8e90507b741f361a57796 Mon Sep 17 00:00:00 2001 From: Nikesh Nazareth Date: Tue, 6 May 2025 13:44:10 +1000 Subject: [PATCH 25/41] Add comment explaining address(this) in verifySignal --- src/libs/LibSignal.sol | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/libs/LibSignal.sol b/src/libs/LibSignal.sol index 4aceefc3..6b1d70b6 100644 --- a/src/libs/LibSignal.sol +++ b/src/libs/LibSignal.sol @@ -49,9 +49,11 @@ library LibSignal { /// @param value The signal value to verify /// @param sender The address that originally sent the signal on the source chain /// @param root The state root or storage root from the source chain to verify against - /// @param accountProof Merkle proof for the contract's account against the state root. Empty if we are using a + /// @param accountProof Merkle proof for the SignalService account against the state root. Empty if we are using a /// storage root. - /// @param storageProof Merkle proof for the derived storage slot against the account's storage root + /// @param storageProof Merkle proof for the derived storage slot against the SignalService's storage root + /// @dev We can use `address(this)` as the SignalService address, even when `root` refers to a different chain, + /// because we assume the SignalService is deployed at the same address on every chain. function verifySignal( bytes32 value, address sender, From bc686f2a1953459cbf516f155637cc02285eebb9 Mon Sep 17 00:00:00 2001 From: Nikesh Nazareth Date: Tue, 6 May 2025 13:45:03 +1000 Subject: [PATCH 26/41] Fix typo --- src/protocol/SignalService.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/protocol/SignalService.sol b/src/protocol/SignalService.sol index df8a959a..cee8d17a 100644 --- a/src/protocol/SignalService.sol +++ b/src/protocol/SignalService.sol @@ -8,7 +8,7 @@ import {ETHBridge} from "./ETHBridge.sol"; import {ICommitmentStore} from "./ICommitmentStore.sol"; import {ISignalService} from "./ISignalService.sol"; -/// @dev SignalService is used for secure cross-cain messaging +/// @dev SignalService is used for secure cross-chain messaging /// /// This contract allows sending arbitrary data as signals via `sendSignal`, verifying signals from other chains using /// `verifySignal`, and bridging native ETH with built-in signal generation and verification. It integrates: From f0c4b488f2653f11177514469a387132748be1a0 Mon Sep 17 00:00:00 2001 From: Nikesh Nazareth Date: Tue, 6 May 2025 13:47:08 +1000 Subject: [PATCH 27/41] Remove comment about native bridging from SignalService --- src/protocol/SignalService.sol | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/protocol/SignalService.sol b/src/protocol/SignalService.sol index cee8d17a..b1717e93 100644 --- a/src/protocol/SignalService.sol +++ b/src/protocol/SignalService.sol @@ -10,8 +10,8 @@ import {ISignalService} from "./ISignalService.sol"; /// @dev SignalService is used for secure cross-chain messaging /// -/// This contract allows sending arbitrary data as signals via `sendSignal`, verifying signals from other chains using -/// `verifySignal`, and bridging native ETH with built-in signal generation and verification. It integrates: +/// This contract allows sending arbitrary data as signals via `sendSignal` and verifying signals from other chains +/// using `verifySignal`. It integrates: /// - `CommitmentStore` to access state roots, /// - `LibSignal` for signal hashing, storage, and verification logic. /// From d9340f6364679e0715ba7549a0ad53b5bb4e13ac Mon Sep 17 00:00:00 2001 From: Leo Date: Tue, 6 May 2025 09:50:32 +0200 Subject: [PATCH 28/41] fix comment about state proof in lib signal --- src/libs/LibSignal.sol | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/libs/LibSignal.sol b/src/libs/LibSignal.sol index 6b1d70b6..5163b35e 100644 --- a/src/libs/LibSignal.sol +++ b/src/libs/LibSignal.sol @@ -48,9 +48,8 @@ library LibSignal { /// @dev Performs a storage proof verification for a signal stored on the contract using this library /// @param value The signal value to verify /// @param sender The address that originally sent the signal on the source chain - /// @param root The state root or storage root from the source chain to verify against - /// @param accountProof Merkle proof for the SignalService account against the state root. Empty if we are using a - /// storage root. + /// @param root The state root from the source chain to verify against + /// @param accountProof Merkle proof for the SignalService account against the state root. /// @param storageProof Merkle proof for the derived storage slot against the SignalService's storage root /// @dev We can use `address(this)` as the SignalService address, even when `root` refers to a different chain, /// because we assume the SignalService is deployed at the same address on every chain. From 4528794888eafc8a2a14bebed11edd1897cc4908 Mon Sep 17 00:00:00 2001 From: Gustavo Gonzalez Date: Tue, 6 May 2025 15:06:47 +0200 Subject: [PATCH 29/41] improve natspec and remove unnecesary imports --- src/protocol/CommitmentStore.sol | 4 ---- src/protocol/ISignalService.sol | 9 +++++---- 2 files changed, 5 insertions(+), 8 deletions(-) diff --git a/src/protocol/CommitmentStore.sol b/src/protocol/CommitmentStore.sol index dafa8d18..6c40528c 100644 --- a/src/protocol/CommitmentStore.sol +++ b/src/protocol/CommitmentStore.sol @@ -3,12 +3,8 @@ pragma solidity ^0.8.28; import {ICommitmentStore} from "./ICommitmentStore.sol"; -import {SafeCast} from "@openzeppelin/contracts/utils/math/SafeCast.sol"; - /// @dev Base contract for storing commitments. abstract contract CommitmentStore is ICommitmentStore { - using SafeCast for uint256; - mapping(address source => mapping(uint256 height => bytes32 commitment)) private _commitments; /// @inheritdoc ICommitmentStore diff --git a/src/protocol/ISignalService.sol b/src/protocol/ISignalService.sol index 6875bed1..fad893ef 100644 --- a/src/protocol/ISignalService.sol +++ b/src/protocol/ISignalService.sol @@ -18,7 +18,7 @@ interface ISignalService { /// @param sender The address that sent the signal on the source chain /// @param namespace The namespace of the signal /// @param value The signal value - event SignalSent(address indexed sender, bytes32 namespace, bytes32 value); + event SignalSent(address indexed sender, bytes32 indexed namespace, bytes32 value); /// @dev Emitted when a signal is verified. /// @param sender The address of the sender on the source chain @@ -28,11 +28,11 @@ interface ISignalService { /// @dev We require the commitment to contain a state root (with an embedded storage root) error StorageRootCommitmentNotSupported(); - /// @dev Stores a data signal and returns its storage location. + /// @notice Stores a signal and returns its storage location. /// @param value Data to be stored (signalled) function sendSignal(bytes32 value) external returns (bytes32 slot); - /// @dev Checks if a signal has been stored + /// @notice Checks if a signal has been stored /// @dev Note: This does not mean it has been 'sent' to destination chain, /// only that it has been stored on the source chain. /// @param value Value to be checked is stored @@ -40,7 +40,8 @@ interface ISignalService { /// @param namespace The namespace of the signal function isSignalStored(bytes32 value, address sender, bytes32 namespace) external view returns (bool); - /// @dev Verifies if the signal can be proved to be part of a merkle tree + /// @notice Verifies if the signal can be proved to be part of a merkle tree. This is usually used to verify signals + /// sent by `sender` on the source chain, which state is represented by `commitmentPublisher` at `height`. /// @dev Signals are not deleted when verified, and can be /// verified multiple times by calling this function /// @param height This refers to the block number / commitmentId where the trusted root is mapped to From 9cdac7008e70457cf622183b381f5aa9ff5bfa9f Mon Sep 17 00:00:00 2001 From: Leo Date: Tue, 6 May 2025 15:28:28 +0200 Subject: [PATCH 30/41] update comments --- src/protocol/CheckpointTracker.sol | 12 ++++++++---- src/protocol/ISignalService.sol | 3 ++- src/protocol/SignalService.sol | 10 ++++++---- 3 files changed, 16 insertions(+), 9 deletions(-) diff --git a/src/protocol/CheckpointTracker.sol b/src/protocol/CheckpointTracker.sol index bf4bc073..2f0c1aec 100644 --- a/src/protocol/CheckpointTracker.sol +++ b/src/protocol/CheckpointTracker.sol @@ -7,15 +7,14 @@ import {IPublicationFeed} from "./IPublicationFeed.sol"; import {IVerifier} from "./IVerifier.sol"; contract CheckpointTracker is ICheckpointTracker { - /// @notice The publication id of the current proven checkpoint representing the latest verified state of the rollup - /// @dev A checkpoint commitment is any value (typically a state root) that uniquely identifies - /// the state of the rollup at a specific point in time + /// @dev The publication id of the current proven checkpoint representing + /// the latest verified state of the rollup uint256 private _provenPublicationId; IPublicationFeed public immutable publicationFeed; IVerifier public immutable verifier; ICommitmentStore public immutable commitmentStore; - address public proverManager; + address public immutable proverManager; /// @param _genesis the checkpoint commitment describing the initial state of the rollup /// @param _publicationFeed the input data source that updates the state of this rollup @@ -73,11 +72,16 @@ contract CheckpointTracker is ICheckpointTracker { _updateCheckpoint(end.publicationId, end.commitment); } + /// @inheritdoc ICheckpointTracker function getProvenCheckpoint() public view returns (Checkpoint memory provenCheckpoint) { provenCheckpoint.publicationId = _provenPublicationId; provenCheckpoint.commitment = commitmentStore.commitmentAt(address(this), provenCheckpoint.publicationId); } + /// @dev Updates the proven checkpoint to a new publication ID and commitment + /// @dev Stores the commitment in the commitment store and emits an event + /// @param publicationId The ID of the publication to set as the latest proven checkpoint + /// @param commitment The checkpoint commitment representing the state at the given publication ID function _updateCheckpoint(uint256 publicationId, bytes32 commitment) internal { _provenPublicationId = publicationId; commitmentStore.storeCommitment(publicationId, commitment); diff --git a/src/protocol/ISignalService.sol b/src/protocol/ISignalService.sol index 8f3dbc80..deeb9c07 100644 --- a/src/protocol/ISignalService.sol +++ b/src/protocol/ISignalService.sol @@ -41,7 +41,8 @@ interface ISignalService { /// @dev Verifies if the signal can be proved to be part of a merkle tree /// @dev Signals are not deleted when verified, and can be /// verified multiple times by calling this function - /// @param height This refers to the block number / commitmentId where the trusted root is mapped to + /// @param height A reference value indicating which trusted root to use for verification + /// see ICommitmentStore for more information /// @param commitmentPublisher The address that published the commitment containing the signal. /// @param sender The address that originally sent the signal on the source chain /// @param value The signal value to verify diff --git a/src/protocol/SignalService.sol b/src/protocol/SignalService.sol index b1717e93..bf26df5a 100644 --- a/src/protocol/SignalService.sol +++ b/src/protocol/SignalService.sol @@ -11,11 +11,13 @@ import {ISignalService} from "./ISignalService.sol"; /// @dev SignalService is used for secure cross-chain messaging /// /// This contract allows sending arbitrary data as signals via `sendSignal` and verifying signals from other chains -/// using `verifySignal`. It integrates: +/// using +/// `verifySignal`. It integrates: /// - `CommitmentStore` to access state roots, /// - `LibSignal` for signal hashing, storage, and verification logic. /// -/// Signals stored cannot be deleted and can be verified multiple times. +/// Signals stored cannot be deleted +/// WARN: this contract does not provide replay protection(signals can be verified multiple times). contract SignalService is ISignalService, CommitmentStore { using LibSignal for bytes32; @@ -48,8 +50,8 @@ contract SignalService is ISignalService, CommitmentStore { SignalProof memory signalProof = abi.decode(proof, (SignalProof)); bytes[] memory accountProof = signalProof.accountProof; bytes[] memory storageProof = signalProof.storageProof; - // if there is no account proof, verify signal will treat root as a storage root - // instead of a full state root which we currently do not support + // We only support state roots for verification + // this is to avoid state roots being used as storage roots (for safety) require(accountProof.length != 0, StateProofNotSupported()); value.verifySignal(sender, root, accountProof, storageProof); emit SignalVerified(sender, value); From 5b58aee939e944499b391ad37c2aa849e576677f Mon Sep 17 00:00:00 2001 From: Leo Date: Tue, 6 May 2025 15:28:28 +0200 Subject: [PATCH 31/41] update comments --- src/protocol/CheckpointTracker.sol | 12 ++++++++---- src/protocol/ISignalService.sol | 3 ++- src/protocol/SignalService.sol | 20 +++++++++++--------- 3 files changed, 21 insertions(+), 14 deletions(-) diff --git a/src/protocol/CheckpointTracker.sol b/src/protocol/CheckpointTracker.sol index bf4bc073..2f0c1aec 100644 --- a/src/protocol/CheckpointTracker.sol +++ b/src/protocol/CheckpointTracker.sol @@ -7,15 +7,14 @@ import {IPublicationFeed} from "./IPublicationFeed.sol"; import {IVerifier} from "./IVerifier.sol"; contract CheckpointTracker is ICheckpointTracker { - /// @notice The publication id of the current proven checkpoint representing the latest verified state of the rollup - /// @dev A checkpoint commitment is any value (typically a state root) that uniquely identifies - /// the state of the rollup at a specific point in time + /// @dev The publication id of the current proven checkpoint representing + /// the latest verified state of the rollup uint256 private _provenPublicationId; IPublicationFeed public immutable publicationFeed; IVerifier public immutable verifier; ICommitmentStore public immutable commitmentStore; - address public proverManager; + address public immutable proverManager; /// @param _genesis the checkpoint commitment describing the initial state of the rollup /// @param _publicationFeed the input data source that updates the state of this rollup @@ -73,11 +72,16 @@ contract CheckpointTracker is ICheckpointTracker { _updateCheckpoint(end.publicationId, end.commitment); } + /// @inheritdoc ICheckpointTracker function getProvenCheckpoint() public view returns (Checkpoint memory provenCheckpoint) { provenCheckpoint.publicationId = _provenPublicationId; provenCheckpoint.commitment = commitmentStore.commitmentAt(address(this), provenCheckpoint.publicationId); } + /// @dev Updates the proven checkpoint to a new publication ID and commitment + /// @dev Stores the commitment in the commitment store and emits an event + /// @param publicationId The ID of the publication to set as the latest proven checkpoint + /// @param commitment The checkpoint commitment representing the state at the given publication ID function _updateCheckpoint(uint256 publicationId, bytes32 commitment) internal { _provenPublicationId = publicationId; commitmentStore.storeCommitment(publicationId, commitment); diff --git a/src/protocol/ISignalService.sol b/src/protocol/ISignalService.sol index fad893ef..b401639d 100644 --- a/src/protocol/ISignalService.sol +++ b/src/protocol/ISignalService.sol @@ -44,7 +44,8 @@ interface ISignalService { /// sent by `sender` on the source chain, which state is represented by `commitmentPublisher` at `height`. /// @dev Signals are not deleted when verified, and can be /// verified multiple times by calling this function - /// @param height This refers to the block number / commitmentId where the trusted root is mapped to + /// @param height A reference value indicating which trusted root to use for verification + /// see ICommitmentStore for more information /// @param commitmentPublisher The address that published the commitment containing the signal. /// @param sender The address that originally sent the signal on the source chain /// @param value The signal value to verify diff --git a/src/protocol/SignalService.sol b/src/protocol/SignalService.sol index 88775633..e055f091 100644 --- a/src/protocol/SignalService.sol +++ b/src/protocol/SignalService.sol @@ -10,12 +10,13 @@ import {ISignalService} from "./ISignalService.sol"; /// @dev SignalService combines secure cross-chain messaging with native token bridging. /// -/// This contract allows sending arbitrary data as signals via `sendSignal`, verifying signals from other chains using -/// `verifySignal`, and bridging native ETH with built-in signal generation and verification. It integrates: -/// - `CommitmentStore` to access state roots, -/// - `LibSignal` for signal hashing, storage, and verification logic. +/// This contract allows sending arbitrary data as signals via `sendSignal` and verifying signals from other chains using`verifySignal` +/// It integrates: +/// - `CommitmentStore` to access state roots, +/// - `LibSignal` for signal hashing, storage, and verification logic. /// -/// Signals stored cannot be deleted and can be verified multiple times. +/// Signals stored cannot be deleted +/// WARN: this contract does not provide replay protection(signals can be verified multiple times). contract SignalService is ISignalService, CommitmentStore { using LibSignal for bytes32; @@ -61,9 +62,10 @@ contract SignalService is ISignalService, CommitmentStore { SignalProof memory signalProof = abi.decode(proof, (SignalProof)); bytes[] memory accountProof = signalProof.accountProof; bytes[] memory storageProof = signalProof.storageProof; - // if there is no account proof, verify signal will treat root as a storage root - // for now, we only support full state roots - require(accountProof.length != 0, StorageRootCommitmentNotSupported()); - value.verifySignal(namespace, sender, root, accountProof, storageProof); + // We only support state roots for verification + // this is to avoid state roots being used as storage roots (for safety) + require(accountProof.length != 0, StateProofNotSupported()); + value.verifySignal(sender, root, accountProof, storageProof); + emit SignalVerified(sender, value); } } From 7448d95ac024f94eb72c82dea5175dc18c6b92f9 Mon Sep 17 00:00:00 2001 From: Leo Date: Tue, 6 May 2025 15:28:28 +0200 Subject: [PATCH 32/41] update comments comment comment better error comment --- src/protocol/CheckpointTracker.sol | 12 ++++++++---- src/protocol/ICommitmentStore.sol | 5 +++-- src/protocol/ISignalService.sol | 8 ++++++-- src/protocol/SignalService.sol | 28 +++++++++++++++++++--------- 4 files changed, 36 insertions(+), 17 deletions(-) diff --git a/src/protocol/CheckpointTracker.sol b/src/protocol/CheckpointTracker.sol index bf4bc073..2f0c1aec 100644 --- a/src/protocol/CheckpointTracker.sol +++ b/src/protocol/CheckpointTracker.sol @@ -7,15 +7,14 @@ import {IPublicationFeed} from "./IPublicationFeed.sol"; import {IVerifier} from "./IVerifier.sol"; contract CheckpointTracker is ICheckpointTracker { - /// @notice The publication id of the current proven checkpoint representing the latest verified state of the rollup - /// @dev A checkpoint commitment is any value (typically a state root) that uniquely identifies - /// the state of the rollup at a specific point in time + /// @dev The publication id of the current proven checkpoint representing + /// the latest verified state of the rollup uint256 private _provenPublicationId; IPublicationFeed public immutable publicationFeed; IVerifier public immutable verifier; ICommitmentStore public immutable commitmentStore; - address public proverManager; + address public immutable proverManager; /// @param _genesis the checkpoint commitment describing the initial state of the rollup /// @param _publicationFeed the input data source that updates the state of this rollup @@ -73,11 +72,16 @@ contract CheckpointTracker is ICheckpointTracker { _updateCheckpoint(end.publicationId, end.commitment); } + /// @inheritdoc ICheckpointTracker function getProvenCheckpoint() public view returns (Checkpoint memory provenCheckpoint) { provenCheckpoint.publicationId = _provenPublicationId; provenCheckpoint.commitment = commitmentStore.commitmentAt(address(this), provenCheckpoint.publicationId); } + /// @dev Updates the proven checkpoint to a new publication ID and commitment + /// @dev Stores the commitment in the commitment store and emits an event + /// @param publicationId The ID of the publication to set as the latest proven checkpoint + /// @param commitment The checkpoint commitment representing the state at the given publication ID function _updateCheckpoint(uint256 publicationId, bytes32 commitment) internal { _provenPublicationId = publicationId; commitmentStore.storeCommitment(publicationId, commitment); diff --git a/src/protocol/ICommitmentStore.sol b/src/protocol/ICommitmentStore.sol index 017b8505..d269385b 100644 --- a/src/protocol/ICommitmentStore.sol +++ b/src/protocol/ICommitmentStore.sol @@ -15,12 +15,13 @@ interface ICommitmentStore { /// @dev A new `commitment` has been stored by `source` at a specified `height`. event CommitmentStored(address indexed source, uint256 indexed height, bytes32 commitment); - /// @dev Returns the commitment at the given `height`. + /// @notice Returns the commitment at the given `height`. + /// @dev If the commitment does not exist at the given `height`, it returns zero. /// @param source The source address for the saved commitment /// @param height The height of the commitment function commitmentAt(address source, uint256 height) external view returns (bytes32 commitment); - /// @dev Stores a commitment. + /// @notice Stores a commitment attributed to `msg.sender` /// @param height The height of the commitment /// @param commitment The commitment to store function storeCommitment(uint256 height, bytes32 commitment) external; diff --git a/src/protocol/ISignalService.sol b/src/protocol/ISignalService.sol index fad893ef..7642f48c 100644 --- a/src/protocol/ISignalService.sol +++ b/src/protocol/ISignalService.sol @@ -28,6 +28,9 @@ interface ISignalService { /// @dev We require the commitment to contain a state root (with an embedded storage root) error StorageRootCommitmentNotSupported(); + /// @dev If the commitment returns 0 we assume it does not exist + error CommitmentNotFound(); + /// @notice Stores a signal and returns its storage location. /// @param value Data to be stored (signalled) function sendSignal(bytes32 value) external returns (bytes32 slot); @@ -44,8 +47,9 @@ interface ISignalService { /// sent by `sender` on the source chain, which state is represented by `commitmentPublisher` at `height`. /// @dev Signals are not deleted when verified, and can be /// verified multiple times by calling this function - /// @param height This refers to the block number / commitmentId where the trusted root is mapped to - /// @param commitmentPublisher The address that published the commitment containing the signal. + /// @param height A reference value indicating which trusted root to use for verification + /// see ICommitmentStore for more information + /// @param commitmentPublisher The address that published the commitment /// @param sender The address that originally sent the signal on the source chain /// @param value The signal value to verify /// @param proof The encoded value of the SignalProof struct diff --git a/src/protocol/SignalService.sol b/src/protocol/SignalService.sol index 88775633..ef6b9911 100644 --- a/src/protocol/SignalService.sol +++ b/src/protocol/SignalService.sol @@ -8,14 +8,16 @@ import {ETHBridge} from "./ETHBridge.sol"; import {ICommitmentStore} from "./ICommitmentStore.sol"; import {ISignalService} from "./ISignalService.sol"; -/// @dev SignalService combines secure cross-chain messaging with native token bridging. +/// @dev SignalService is used for secure cross-chain messaging /// -/// This contract allows sending arbitrary data as signals via `sendSignal`, verifying signals from other chains using -/// `verifySignal`, and bridging native ETH with built-in signal generation and verification. It integrates: -/// - `CommitmentStore` to access state roots, -/// - `LibSignal` for signal hashing, storage, and verification logic. +/// This contract allows sending arbitrary data as signals via `sendSignal` and verifying signals from other chains +/// using`verifySignal` +/// It integrates: +/// - `CommitmentStore` to access state roots, +/// - `LibSignal` for signal hashing, storage, and verification logic. /// -/// Signals stored cannot be deleted and can be verified multiple times. +/// Signals stored cannot be deleted +/// WARN: this contract does not provide replay protection(signals can be verified multiple times). contract SignalService is ISignalService, CommitmentStore { using LibSignal for bytes32; @@ -58,12 +60,20 @@ contract SignalService is ISignalService, CommitmentStore { // For now it could be the block hash or other hashed value // further work is needed to ensure we get the 'state root' of the chain bytes32 root = commitmentAt(commitmentPublisher, height); + + // A 0 root would probably fail further down the line but its better to explicitly check + require(root != 0, CommitmentNotFound()); + SignalProof memory signalProof = abi.decode(proof, (SignalProof)); bytes[] memory accountProof = signalProof.accountProof; bytes[] memory storageProof = signalProof.storageProof; - // if there is no account proof, verify signal will treat root as a storage root - // for now, we only support full state roots + + // We only support state roots for verification + // this is to avoid state roots being used as storage roots (for safety) require(accountProof.length != 0, StorageRootCommitmentNotSupported()); - value.verifySignal(namespace, sender, root, accountProof, storageProof); + + value.verifySignal(sender, root, accountProof, storageProof); + + emit SignalVerified(sender, value); } } From 0a64605c8f85ab0aa0ce898aec8f2578425466cb Mon Sep 17 00:00:00 2001 From: Leo Date: Tue, 6 May 2025 15:52:22 +0200 Subject: [PATCH 33/41] revert changes --- src/libs/LibSignal.sol | 2 +- src/protocol/SignalService.sol | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/libs/LibSignal.sol b/src/libs/LibSignal.sol index 3d0e7db9..2f046ddd 100644 --- a/src/libs/LibSignal.sol +++ b/src/libs/LibSignal.sol @@ -68,7 +68,7 @@ library LibSignal { bytes32 root, bytes[] memory accountProof, bytes[] memory storageProof - ) internal pure { + ) internal view { bytes32 encodedBool = bytes32(uint256(1)); LibTrieProof.verifyMerkleProof( root, sender, deriveSlot(value, sender, namespace), encodedBool, accountProof, storageProof diff --git a/src/protocol/SignalService.sol b/src/protocol/SignalService.sol index ef6b9911..652c561d 100644 --- a/src/protocol/SignalService.sol +++ b/src/protocol/SignalService.sol @@ -55,7 +55,7 @@ contract SignalService is ISignalService, CommitmentStore { bytes32 value, bytes32 namespace, bytes memory proof - ) internal view virtual { + ) internal virtual { // TODO: commitmentAt(height) might not be the 'state root' of the chain // For now it could be the block hash or other hashed value // further work is needed to ensure we get the 'state root' of the chain @@ -72,7 +72,7 @@ contract SignalService is ISignalService, CommitmentStore { // this is to avoid state roots being used as storage roots (for safety) require(accountProof.length != 0, StorageRootCommitmentNotSupported()); - value.verifySignal(sender, root, accountProof, storageProof); + value.verifySignal(namespace, sender, root, accountProof, storageProof); emit SignalVerified(sender, value); } From 070991f80d74cf9fae97e568dc298cb7b0b99193 Mon Sep 17 00:00:00 2001 From: Leo Date: Tue, 6 May 2025 15:52:30 +0200 Subject: [PATCH 34/41] pure --- src/libs/LibSignal.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libs/LibSignal.sol b/src/libs/LibSignal.sol index 2f046ddd..3d0e7db9 100644 --- a/src/libs/LibSignal.sol +++ b/src/libs/LibSignal.sol @@ -68,7 +68,7 @@ library LibSignal { bytes32 root, bytes[] memory accountProof, bytes[] memory storageProof - ) internal view { + ) internal pure { bytes32 encodedBool = bytes32(uint256(1)); LibTrieProof.verifyMerkleProof( root, sender, deriveSlot(value, sender, namespace), encodedBool, accountProof, storageProof From d543b43d09a922fe4bc38e3e763110b6f5766cb7 Mon Sep 17 00:00:00 2001 From: Nikesh Nazareth Date: Wed, 7 May 2025 13:02:24 +1000 Subject: [PATCH 35/41] Remove obsolete comment --- src/protocol/SignalService.sol | 1 - 1 file changed, 1 deletion(-) diff --git a/src/protocol/SignalService.sol b/src/protocol/SignalService.sol index 0d489ca4..7547a34a 100644 --- a/src/protocol/SignalService.sol +++ b/src/protocol/SignalService.sol @@ -24,7 +24,6 @@ contract SignalService is ISignalService, CommitmentStore { /// @inheritdoc ISignalService /// @dev Signals are stored in a namespaced slot derived from the signal value, sender address and SIGNAL_NAMESPACE /// const - /// @dev Cannot be used to send eth bridge signals function sendSignal(bytes32 value) external returns (bytes32 slot) { slot = value.signal(); emit SignalSent(msg.sender, value); From 1b77c8f4834dd719678723c042d3a8aacf065509 Mon Sep 17 00:00:00 2001 From: Nikesh Nazareth Date: Wed, 7 May 2025 13:08:37 +1000 Subject: [PATCH 36/41] Remove obsolete reference to SIGNAL_NAMESPACE --- src/protocol/SignalService.sol | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/protocol/SignalService.sol b/src/protocol/SignalService.sol index 7547a34a..d2820d48 100644 --- a/src/protocol/SignalService.sol +++ b/src/protocol/SignalService.sol @@ -22,8 +22,7 @@ contract SignalService is ISignalService, CommitmentStore { using LibSignal for bytes32; /// @inheritdoc ISignalService - /// @dev Signals are stored in a namespaced slot derived from the signal value, sender address and SIGNAL_NAMESPACE - /// const + /// @dev Signals are stored in a namespaced slot derived from the signal value and sender address function sendSignal(bytes32 value) external returns (bytes32 slot) { slot = value.signal(); emit SignalSent(msg.sender, value); From 417f1980f70154a6540ea1091d4ace195ad5f420 Mon Sep 17 00:00:00 2001 From: Nikesh Nazareth Date: Wed, 7 May 2025 14:55:14 +1000 Subject: [PATCH 37/41] Bug fix: Use counterpart bridge address to derive signal slot --- src/protocol/ETHBridge.sol | 10 ++++++++-- test/signal/ETHBridge.t.sol | 13 ++++++++++--- 2 files changed, 18 insertions(+), 5 deletions(-) diff --git a/src/protocol/ETHBridge.sol b/src/protocol/ETHBridge.sol index d455b774..96529109 100644 --- a/src/protocol/ETHBridge.sol +++ b/src/protocol/ETHBridge.sol @@ -18,11 +18,17 @@ contract ETHBridge is IETHBridge { /// @dev This is the Anchor on L2 and the Checkpoint Tracker on the L1 address public immutable trustedCommitmentPublisher; - constructor(address _signalService, address _trustedCommitmentPublisher) { + /// @dev The counterpart bridge contract on the other chain. + /// This is used to locate deposit signals inside the other chain's state root. + /// WARN: This address has no significance (and may be untrustworthy) on this chain. + address public immutable counterpart; + + constructor(address _signalService, address _trustedCommitmentPublisher, address _counterpart) { require(_signalService != address(0), "Empty signal service"); require(_trustedCommitmentPublisher != address(0), "Empty trusted publisher"); signalService = ISignalService(_signalService); trustedCommitmentPublisher = _trustedCommitmentPublisher; + counterpart = _counterpart; } /// @inheritdoc IETHBridge @@ -50,7 +56,7 @@ contract ETHBridge is IETHBridge { // TODO: Non reentrant function claimDeposit(ETHDeposit memory ethDeposit, uint256 height, bytes memory proof) external { bytes32 id = _generateId(ethDeposit); - signalService.verifySignal(height, trustedCommitmentPublisher, ethDeposit.from, id, proof); + signalService.verifySignal(height, trustedCommitmentPublisher, counterpart, id, proof); require(!claimed(id), AlreadyClaimed()); _claimed[id] = true; _sendETH(ethDeposit.to, ethDeposit.amount, ethDeposit.data); diff --git a/test/signal/ETHBridge.t.sol b/test/signal/ETHBridge.t.sol index 45452e2f..805c7cd4 100644 --- a/test/signal/ETHBridge.t.sol +++ b/test/signal/ETHBridge.t.sol @@ -15,7 +15,10 @@ import {ISignalService} from "src/protocol/ISignalService.sol"; // however, the state root is not yet available on L2 contract BridgeETHState is BaseState { ETHBridge public L1EthBridge; - ETHBridge public L2EthBridge; + // We need to pass the address of each bridge to the constructor of its counterpart + // This can be achieved with create2 or with proxy deployments + // For testing, we place the L2EthBridge at a specific address + ETHBridge public L2EthBridge = ETHBridge(address(uint160(uint256(keccak256("L2EthBridge"))))); // 0xf9c183d2de58fbeb1a8917170139e980fa1b6e5a358ec83721e11c9f6e25eb18 bytes32 public depositIdOne; @@ -35,7 +38,7 @@ contract BridgeETHState is BaseState { // Deploy L1EthBridge vm.setNonce(defaultSender, 2); vm.prank(defaultSender); - L1EthBridge = new ETHBridge(address(L1SignalService), address(checkpointTracker)); + L1EthBridge = new ETHBridge(address(L1SignalService), address(checkpointTracker), address(L2EthBridge)); vm.deal(address(L1EthBridge), ETHBridgeInitBalance); vm.prank(defaultSender); @@ -54,7 +57,11 @@ contract BridgeETHState2 is BridgeETHState { // Deploy L2EthBridge vm.setNonce(defaultSender, 2); vm.prank(defaultSender); - L2EthBridge = new ETHBridge(address(L2SignalService), address(anchor)); + deployCodeTo( + "ETHBridge.sol", + abi.encode(address(L2SignalService), address(anchor), address(L1EthBridge)), + address(L2EthBridge) + ); vm.deal(address(L2EthBridge), ETHBridgeInitBalance); } } From 0d025ca12326cb23ad7bdefe9722371a6299d12e Mon Sep 17 00:00:00 2001 From: Gustavo Gonzalez Date: Wed, 7 May 2025 10:29:31 +0200 Subject: [PATCH 38/41] add nonReentrant using ReentrancyGuardTransient --- foundry.toml | 3 ++- src/protocol/ETHBridge.sol | 8 +++++--- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/foundry.toml b/foundry.toml index 9a3ed8ba..78ec4f66 100644 --- a/foundry.toml +++ b/foundry.toml @@ -11,7 +11,8 @@ gas_reports_ignore = ["MockDelayedInclusionStore", "MockCheckpointTracker", ""] remappings = [ "@optimism/=lib/optimism/", - "@vendor/=src/vendor/" + "@vendor/=src/vendor/", + "@openzeppelin/contracts/=lib/openzeppelin-contracts/contracts/" ] [fmt] diff --git a/src/protocol/ETHBridge.sol b/src/protocol/ETHBridge.sol index 96529109..d0bd62cc 100644 --- a/src/protocol/ETHBridge.sol +++ b/src/protocol/ETHBridge.sol @@ -3,11 +3,12 @@ pragma solidity ^0.8.28; import {IETHBridge} from "./IETHBridge.sol"; import {ISignalService} from "./ISignalService.sol"; +import {ReentrancyGuardTransient} from "@openzeppelin/contracts/utils/ReentrancyGuardTransient.sol"; /// @dev ETH bridging contract to send native ETH between L1 <-> L2 using storage proofs. /// /// IMPORTANT: No recovery mechanism is implemented in case an account creates a deposit that can't be claimed. -contract ETHBridge is IETHBridge { +contract ETHBridge is IETHBridge, ReentrancyGuardTransient { mapping(bytes32 id => bool claimed) private _claimed; /// Incremental nonce to generate unique deposit IDs. @@ -26,6 +27,7 @@ contract ETHBridge is IETHBridge { constructor(address _signalService, address _trustedCommitmentPublisher, address _counterpart) { require(_signalService != address(0), "Empty signal service"); require(_trustedCommitmentPublisher != address(0), "Empty trusted publisher"); + signalService = ISignalService(_signalService); trustedCommitmentPublisher = _trustedCommitmentPublisher; counterpart = _counterpart; @@ -48,13 +50,13 @@ contract ETHBridge is IETHBridge { unchecked { ++_globalDepositNonce; } + signalService.sendSignal(id); emit DepositMade(id, ethDeposit); } /// @inheritdoc IETHBridge - // TODO: Non reentrant - function claimDeposit(ETHDeposit memory ethDeposit, uint256 height, bytes memory proof) external { + function claimDeposit(ETHDeposit memory ethDeposit, uint256 height, bytes memory proof) external nonReentrant { bytes32 id = _generateId(ethDeposit); signalService.verifySignal(height, trustedCommitmentPublisher, counterpart, id, proof); require(!claimed(id), AlreadyClaimed()); From 6a6b292bc08763ddf125d09096e0305fae75d9d1 Mon Sep 17 00:00:00 2001 From: Leo Date: Wed, 7 May 2025 09:45:09 +0100 Subject: [PATCH 39/41] better spacing --- src/protocol/ETHBridge.sol | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/protocol/ETHBridge.sol b/src/protocol/ETHBridge.sol index d0bd62cc..8fcefb56 100644 --- a/src/protocol/ETHBridge.sol +++ b/src/protocol/ETHBridge.sol @@ -58,10 +58,13 @@ contract ETHBridge is IETHBridge, ReentrancyGuardTransient { /// @inheritdoc IETHBridge function claimDeposit(ETHDeposit memory ethDeposit, uint256 height, bytes memory proof) external nonReentrant { bytes32 id = _generateId(ethDeposit); - signalService.verifySignal(height, trustedCommitmentPublisher, counterpart, id, proof); require(!claimed(id), AlreadyClaimed()); + + signalService.verifySignal(height, trustedCommitmentPublisher, counterpart, id, proof); + _claimed[id] = true; _sendETH(ethDeposit.to, ethDeposit.amount, ethDeposit.data); + emit DepositClaimed(id, ethDeposit); } From 1c717f9fea416931ef11cc30dc8cfadeda169163 Mon Sep 17 00:00:00 2001 From: Leo Date: Wed, 7 May 2025 09:48:35 +0100 Subject: [PATCH 40/41] Update src/protocol/ETHBridge.sol Co-authored-by: Gustavo Gonzalez --- src/protocol/ETHBridge.sol | 1 + 1 file changed, 1 insertion(+) diff --git a/src/protocol/ETHBridge.sol b/src/protocol/ETHBridge.sol index d0bd62cc..d336e23b 100644 --- a/src/protocol/ETHBridge.sol +++ b/src/protocol/ETHBridge.sol @@ -16,6 +16,7 @@ contract ETHBridge is IETHBridge, ReentrancyGuardTransient { ISignalService public immutable signalService; + /// @dev Trusted source of commitments in the `CommitmentStore` that the bridge will use to validate withdrawals /// @dev This is the Anchor on L2 and the Checkpoint Tracker on the L1 address public immutable trustedCommitmentPublisher; From 2114335a9db6176704452e5a81c3a46d80f0d4d9 Mon Sep 17 00:00:00 2001 From: Leo Date: Wed, 7 May 2025 12:01:10 +0100 Subject: [PATCH 41/41] fix rust --- offchain/deposit_signal_proof.rs | 4 ++-- offchain/utils.rs | 9 ++++++++- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/offchain/deposit_signal_proof.rs b/offchain/deposit_signal_proof.rs index 368dad13..3f992522 100644 --- a/offchain/deposit_signal_proof.rs +++ b/offchain/deposit_signal_proof.rs @@ -23,8 +23,8 @@ async fn main() -> Result<()> { let sender = address!("0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266"); let amount = U256::from(4000000000000000000_u128); - // This is the anchor - let trusted_publisher = address!("0x9f1ac54BEF0DD2f6f3462EA0fa94fC62300d3a8e"); + // This is the checkpoint tracker + let trusted_publisher = address!("0xCafac3dD18aC6c6e92c921884f9E4176737C052c"); let (provider, _anvil) = get_provider()?; diff --git a/offchain/utils.rs b/offchain/utils.rs index a8ae8f4d..f8c98a85 100644 --- a/offchain/utils.rs +++ b/offchain/utils.rs @@ -53,7 +53,14 @@ pub async fn deploy_eth_bridge( signal_service: Address, trusted_publisher: Address, ) -> Result> { - let contract = ETHBridge::deploy(provider, signal_service, trusted_publisher).await?; + // L2 Eth bridge address + let counterpart = address!("0xDC9e4C83bDe3912E9B63A9BF9cE263F3309aB5d4"); + // WARN: This is a slight hack for now to make sure the contract is deployed on the correct address + ETHBridge::deploy(provider, signal_service, trusted_publisher, counterpart).await?; + + let contract = + ETHBridge::deploy(provider, signal_service, trusted_publisher, counterpart).await?; + Ok(contract) }