From b8d8acd9f8edb7ea5fb6f8e82354aabe29ba8381 Mon Sep 17 00:00:00 2001 From: Nikesh Nazareth Date: Tue, 22 Jul 2025 13:08:44 +1000 Subject: [PATCH 01/31] Create initial state The ETHBridge uses a mock signal service so any deposit is considered valid --- test/MessageRelayer/InitialState.t.sol | 29 ++++++++++++++++++++++++++ test/mocks/MockSignalService.sol | 25 ++++++++++++++++++++++ 2 files changed, 54 insertions(+) create mode 100644 test/MessageRelayer/InitialState.t.sol create mode 100644 test/mocks/MockSignalService.sol diff --git a/test/MessageRelayer/InitialState.t.sol b/test/MessageRelayer/InitialState.t.sol new file mode 100644 index 00000000..cd40ff79 --- /dev/null +++ b/test/MessageRelayer/InitialState.t.sol @@ -0,0 +1,29 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.28; + +import "forge-std/Test.sol"; + +import {ETHBridge} from "src/protocol/ETHBridge.sol"; + +import {MessageRelayer} from "src/protocol/taiko_alethia/MessageRelayer.sol"; +import {MockSignalService} from "test/mocks/MockSignalService.sol"; + +abstract contract InitialState is Test { + MessageRelayer messageRelayer; + + function setUp() public virtual { + MockSignalService signalService = new MockSignalService(); + address trustedCommitmentPublisher = _randomAddress("trustedCommitmentPublisher"); + address counterpart = _randomAddress("counterpart"); + ETHBridge bridge = new ETHBridge(address(signalService), trustedCommitmentPublisher, counterpart); + messageRelayer = new MessageRelayer(address(bridge)); + } + + function _randomAddress(string memory name) internal pure returns (address) { + return address(uint160(uint256(keccak256(abi.encode(_domainSeparator(), name))))); + } + + function _domainSeparator() internal pure returns (bytes32) { + return keccak256("MessageRelayer"); + } +} diff --git a/test/mocks/MockSignalService.sol b/test/mocks/MockSignalService.sol new file mode 100644 index 00000000..39f774b4 --- /dev/null +++ b/test/mocks/MockSignalService.sol @@ -0,0 +1,25 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.28; + +import {ISignalService} from "src/protocol/ISignalService.sol"; + +contract MockSignalService is ISignalService { + error NotImplemented(); + + function sendSignal(bytes32) external pure returns (bytes32) { + revert NotImplemented(); + } + + function isSignalStored(bytes32, address) external pure returns (bool) { + revert NotImplemented(); + } + + // verification always succeeds + function verifySignal( + uint256, /* height */ + address, /* commitmentPublisher */ + address, /* sender */ + bytes32, /* value */ + bytes memory /* proof */ + ) external view {} +} From a60d464b0260a34319bf2b19e132a7f217352864 Mon Sep 17 00:00:00 2001 From: Nikesh Nazareth Date: Tue, 22 Jul 2025 14:21:53 +1000 Subject: [PATCH 02/31] Create default message --- test/MessageRelayer/InitialState.t.sol | 32 ++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/test/MessageRelayer/InitialState.t.sol b/test/MessageRelayer/InitialState.t.sol index cd40ff79..2209387a 100644 --- a/test/MessageRelayer/InitialState.t.sol +++ b/test/MessageRelayer/InitialState.t.sol @@ -4,19 +4,51 @@ pragma solidity ^0.8.28; import "forge-std/Test.sol"; import {ETHBridge} from "src/protocol/ETHBridge.sol"; +import {IETHBridge} from "src/protocol/IETHBridge.sol"; +import {IMessageRelayer} from "src/protocol/IMessageRelayer.sol"; import {MessageRelayer} from "src/protocol/taiko_alethia/MessageRelayer.sol"; import {MockSignalService} from "test/mocks/MockSignalService.sol"; abstract contract InitialState is Test { MessageRelayer messageRelayer; + // Default message parameters + IETHBridge.ETHDeposit ethDeposit; + uint256 height = 0; + bytes proof = "0x"; + address to = _randomAddress("to"); + uint256 amount = 2 ether; + uint256 tip = 0.1 ether; + address relayerSelectedTipRecipient = _randomAddress("relayerSelectedTipRecipient"); + address userSelectedTipRecipient = _randomAddress("userSelectedTipRecipient"); + uint256 gasLimit = 0; + bytes data = "0x"; + function setUp() public virtual { MockSignalService signalService = new MockSignalService(); address trustedCommitmentPublisher = _randomAddress("trustedCommitmentPublisher"); address counterpart = _randomAddress("counterpart"); ETHBridge bridge = new ETHBridge(address(signalService), trustedCommitmentPublisher, counterpart); + vm.deal(address(bridge), amount); + messageRelayer = new MessageRelayer(address(bridge)); + + ethDeposit = IETHBridge.ETHDeposit({ + nonce: 0, + from: _randomAddress("from"), + to: address(messageRelayer), + amount: 2 ether, + data: "", + context: "", + canceler: address(0) + }); + _encodeReceiveCall(); + } + + function _encodeReceiveCall() internal { + ethDeposit.data = + abi.encodeCall(IMessageRelayer.receiveMessage, (to, tip, userSelectedTipRecipient, gasLimit, data)); } function _randomAddress(string memory name) internal pure returns (address) { From d2aed97b036514fc7a7a01107ce4fbbf6ed39b24 Mon Sep 17 00:00:00 2001 From: Nikesh Nazareth Date: Tue, 22 Jul 2025 14:55:08 +1000 Subject: [PATCH 03/31] Create message recipient --- test/MessageRelayer/InitialState.t.sol | 6 +++-- test/MessageRelayer/MessageRecipient.t.sol | 27 ++++++++++++++++++++++ 2 files changed, 31 insertions(+), 2 deletions(-) create mode 100644 test/MessageRelayer/MessageRecipient.t.sol diff --git a/test/MessageRelayer/InitialState.t.sol b/test/MessageRelayer/InitialState.t.sol index 2209387a..f98e6af4 100644 --- a/test/MessageRelayer/InitialState.t.sol +++ b/test/MessageRelayer/InitialState.t.sol @@ -6,6 +6,7 @@ import "forge-std/Test.sol"; import {ETHBridge} from "src/protocol/ETHBridge.sol"; import {IETHBridge} from "src/protocol/IETHBridge.sol"; +import {MessageRecipient} from "./MessageRecipient.t.sol"; import {IMessageRelayer} from "src/protocol/IMessageRelayer.sol"; import {MessageRelayer} from "src/protocol/taiko_alethia/MessageRelayer.sol"; import {MockSignalService} from "test/mocks/MockSignalService.sol"; @@ -17,7 +18,7 @@ abstract contract InitialState is Test { IETHBridge.ETHDeposit ethDeposit; uint256 height = 0; bytes proof = "0x"; - address to = _randomAddress("to"); + MessageRecipient to; uint256 amount = 2 ether; uint256 tip = 0.1 ether; address relayerSelectedTipRecipient = _randomAddress("relayerSelectedTipRecipient"); @@ -29,6 +30,7 @@ abstract contract InitialState is Test { MockSignalService signalService = new MockSignalService(); address trustedCommitmentPublisher = _randomAddress("trustedCommitmentPublisher"); address counterpart = _randomAddress("counterpart"); + to = new MessageRecipient(); ETHBridge bridge = new ETHBridge(address(signalService), trustedCommitmentPublisher, counterpart); vm.deal(address(bridge), amount); @@ -48,7 +50,7 @@ abstract contract InitialState is Test { function _encodeReceiveCall() internal { ethDeposit.data = - abi.encodeCall(IMessageRelayer.receiveMessage, (to, tip, userSelectedTipRecipient, gasLimit, data)); + abi.encodeCall(IMessageRelayer.receiveMessage, (address(to), tip, userSelectedTipRecipient, gasLimit, data)); } function _randomAddress(string memory name) internal pure returns (address) { diff --git a/test/MessageRelayer/MessageRecipient.t.sol b/test/MessageRelayer/MessageRecipient.t.sol new file mode 100644 index 00000000..f071af7c --- /dev/null +++ b/test/MessageRelayer/MessageRecipient.t.sol @@ -0,0 +1,27 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.28; + +contract MessageRecipient { + bool private callWillSucceed = true; + + error CallFailed(); + + event FunctionCalled(); + + function setSuccess(bool _callWillSucceed) external { + callWillSucceed = _callWillSucceed; + } + + fallback() external payable { + _simulateFunctionCall(); + } + + receive() external payable { + _simulateFunctionCall(); + } + + function _simulateFunctionCall() internal { + require(callWillSucceed, CallFailed()); + emit FunctionCalled(); + } +} From 6b84dba58b30e571bee8120f1b0b17ded4b7008d Mon Sep 17 00:00:00 2001 From: Nikesh Nazareth Date: Tue, 22 Jul 2025 15:19:38 +1000 Subject: [PATCH 04/31] Create DepositRecipientScenarios --- .../DepositRecipientScenarios.t.sol | 36 +++++++++++++++++++ 1 file changed, 36 insertions(+) create mode 100644 test/MessageRelayer/DepositRecipientScenarios.t.sol diff --git a/test/MessageRelayer/DepositRecipientScenarios.t.sol b/test/MessageRelayer/DepositRecipientScenarios.t.sol new file mode 100644 index 00000000..fcec2243 --- /dev/null +++ b/test/MessageRelayer/DepositRecipientScenarios.t.sol @@ -0,0 +1,36 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.28; + +import {InitialState} from "./InitialState.t.sol"; +import {MessageRecipient} from "./MessageRecipient.t.sol"; +import {IMessageRelayer} from "src/protocol/IMessageRelayer.sol"; + +// This is a concrete class because if we are not using the MessageRelayer, +// we do not need to investigate any other properties of the message +contract DepositRecipientIsNotMessageRelayer is InitialState { + function setUp() public override { + super.setUp(); + // bypass the relayer and send the message directly to the recipient + // do not bother changing the default message encoding (to a `receiveMessage` function) + // because the recipient handles any message + ethDeposit.to = address(to); + } + + function test_DepositRecipientIsNotMessageRelayer_relayMessage_shouldInvokeRecipient() public { + vm.expectEmit(); + emit MessageRecipient.FunctionCalled(); + messageRelayer.relayMessage(ethDeposit, height, proof, relayerSelectedTipRecipient); + } + + function test_DepositRecipientIsNotMessageRelayer_relayMessage_shouldNotInvokeReceiveMessage() public { + vm.expectCall(address(messageRelayer), ethDeposit.data, 0); + messageRelayer.relayMessage(ethDeposit, height, proof, relayerSelectedTipRecipient); + } +} + +abstract contract DepositRecipientIsMessageRelayer is InitialState { + function test_DepositRecipientIsMessageRelayer_relayMessage_shouldInvokeReceiveMessage() public { + vm.expectCall(address(messageRelayer), ethDeposit.data); + messageRelayer.relayMessage(ethDeposit, height, proof, relayerSelectedTipRecipient); + } +} From 620551929b5f0f1f891552f6908c0488002a0fed Mon Sep 17 00:00:00 2001 From: Nikesh Nazareth Date: Tue, 22 Jul 2025 16:25:33 +1000 Subject: [PATCH 05/31] Create TipRecipientScenarios --- .../DepositRecipientScenarios.t.sol | 10 ++-- ...Recipient.t.sol => GenericRecipient.t.sol} | 2 +- test/MessageRelayer/InitialState.t.sol | 21 +++++--- .../TipRecipientScenarios.t.sol | 48 +++++++++++++++++++ 4 files changed, 68 insertions(+), 13 deletions(-) rename test/MessageRelayer/{MessageRecipient.t.sol => GenericRecipient.t.sol} (95%) create mode 100644 test/MessageRelayer/TipRecipientScenarios.t.sol diff --git a/test/MessageRelayer/DepositRecipientScenarios.t.sol b/test/MessageRelayer/DepositRecipientScenarios.t.sol index fcec2243..9572fa89 100644 --- a/test/MessageRelayer/DepositRecipientScenarios.t.sol +++ b/test/MessageRelayer/DepositRecipientScenarios.t.sol @@ -1,8 +1,8 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.28; +import {GenericRecipient} from "./GenericRecipient.t.sol"; import {InitialState} from "./InitialState.t.sol"; -import {MessageRecipient} from "./MessageRecipient.t.sol"; import {IMessageRelayer} from "src/protocol/IMessageRelayer.sol"; // This is a concrete class because if we are not using the MessageRelayer, @@ -18,19 +18,19 @@ contract DepositRecipientIsNotMessageRelayer is InitialState { function test_DepositRecipientIsNotMessageRelayer_relayMessage_shouldInvokeRecipient() public { vm.expectEmit(); - emit MessageRecipient.FunctionCalled(); - messageRelayer.relayMessage(ethDeposit, height, proof, relayerSelectedTipRecipient); + emit GenericRecipient.FunctionCalled(); + _relayMessage(); } function test_DepositRecipientIsNotMessageRelayer_relayMessage_shouldNotInvokeReceiveMessage() public { vm.expectCall(address(messageRelayer), ethDeposit.data, 0); - messageRelayer.relayMessage(ethDeposit, height, proof, relayerSelectedTipRecipient); + _relayMessage(); } } abstract contract DepositRecipientIsMessageRelayer is InitialState { function test_DepositRecipientIsMessageRelayer_relayMessage_shouldInvokeReceiveMessage() public { vm.expectCall(address(messageRelayer), ethDeposit.data); - messageRelayer.relayMessage(ethDeposit, height, proof, relayerSelectedTipRecipient); + _relayMessage(); } } diff --git a/test/MessageRelayer/MessageRecipient.t.sol b/test/MessageRelayer/GenericRecipient.t.sol similarity index 95% rename from test/MessageRelayer/MessageRecipient.t.sol rename to test/MessageRelayer/GenericRecipient.t.sol index f071af7c..2472013b 100644 --- a/test/MessageRelayer/MessageRecipient.t.sol +++ b/test/MessageRelayer/GenericRecipient.t.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.28; -contract MessageRecipient { +contract GenericRecipient { bool private callWillSucceed = true; error CallFailed(); diff --git a/test/MessageRelayer/InitialState.t.sol b/test/MessageRelayer/InitialState.t.sol index f98e6af4..43554970 100644 --- a/test/MessageRelayer/InitialState.t.sol +++ b/test/MessageRelayer/InitialState.t.sol @@ -6,7 +6,7 @@ import "forge-std/Test.sol"; import {ETHBridge} from "src/protocol/ETHBridge.sol"; import {IETHBridge} from "src/protocol/IETHBridge.sol"; -import {MessageRecipient} from "./MessageRecipient.t.sol"; +import {GenericRecipient} from "./GenericRecipient.t.sol"; import {IMessageRelayer} from "src/protocol/IMessageRelayer.sol"; import {MessageRelayer} from "src/protocol/taiko_alethia/MessageRelayer.sol"; import {MockSignalService} from "test/mocks/MockSignalService.sol"; @@ -18,11 +18,11 @@ abstract contract InitialState is Test { IETHBridge.ETHDeposit ethDeposit; uint256 height = 0; bytes proof = "0x"; - MessageRecipient to; + GenericRecipient to; uint256 amount = 2 ether; uint256 tip = 0.1 ether; - address relayerSelectedTipRecipient = _randomAddress("relayerSelectedTipRecipient"); - address userSelectedTipRecipient = _randomAddress("userSelectedTipRecipient"); + GenericRecipient relayerSelectedTipRecipient; + GenericRecipient userSelectedTipRecipient; uint256 gasLimit = 0; bytes data = "0x"; @@ -30,7 +30,9 @@ abstract contract InitialState is Test { MockSignalService signalService = new MockSignalService(); address trustedCommitmentPublisher = _randomAddress("trustedCommitmentPublisher"); address counterpart = _randomAddress("counterpart"); - to = new MessageRecipient(); + to = new GenericRecipient(); + relayerSelectedTipRecipient = new GenericRecipient(); + userSelectedTipRecipient = new GenericRecipient(); ETHBridge bridge = new ETHBridge(address(signalService), trustedCommitmentPublisher, counterpart); vm.deal(address(bridge), amount); @@ -49,8 +51,13 @@ abstract contract InitialState is Test { } function _encodeReceiveCall() internal { - ethDeposit.data = - abi.encodeCall(IMessageRelayer.receiveMessage, (address(to), tip, userSelectedTipRecipient, gasLimit, data)); + ethDeposit.data = abi.encodeCall( + IMessageRelayer.receiveMessage, (address(to), tip, address(userSelectedTipRecipient), gasLimit, data) + ); + } + + function _relayMessage() internal { + messageRelayer.relayMessage(ethDeposit, height, proof, address(relayerSelectedTipRecipient)); } function _randomAddress(string memory name) internal pure returns (address) { diff --git a/test/MessageRelayer/TipRecipientScenarios.t.sol b/test/MessageRelayer/TipRecipientScenarios.t.sol new file mode 100644 index 00000000..b35a281c --- /dev/null +++ b/test/MessageRelayer/TipRecipientScenarios.t.sol @@ -0,0 +1,48 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.28; + +import {DepositRecipientIsMessageRelayer} from "./DepositRecipientScenarios.t.sol"; +import {GenericRecipient} from "./GenericRecipient.t.sol"; +import {IETHBridge} from "src/protocol/IETHBridge.sol"; +import {InitialState} from "./InitialState.t.sol"; + +contract UserSetValidTipRecipient is DepositRecipientIsMessageRelayer { + function test_UserSetValidTipRecipient_relayMessage_shouldTipUserSelectedRecipient() public { + uint256 balanceBefore = address(userSelectedTipRecipient).balance; + _relayMessage(); + assertEq(address(userSelectedTipRecipient).balance, balanceBefore + tip, "tip recipient balance mismatch"); + } + + function test_UserSetValidTipRecipient_relayMessage_shouldNotTipRelayerSelectedRecipient() public { + uint256 balanceBefore = address(relayerSelectedTipRecipient).balance; + _relayMessage(); + assertEq(address(relayerSelectedTipRecipient).balance, balanceBefore, "incorrect tip recipient paid"); + } +} + +contract UserSetZeroTipRecipient is DepositRecipientIsMessageRelayer { + function setUp() public override { + super.setUp(); + userSelectedTipRecipient = GenericRecipient(payable(0)); + _encodeReceiveCall(); + } + + function test_UserSetZeroTipRecipient_relayMessage_shouldTipRelayerSelectedRecipient() public { + uint256 balanceBefore = address(relayerSelectedTipRecipient).balance; + _relayMessage(); + assertEq(address(relayerSelectedTipRecipient).balance, balanceBefore + tip, "tip recipient balance mismatch"); + } +} + +// We bypass the `DepositRecipientIsMessageRelayer` because it seems vm.expectCall requires the call to succeed +contract UserSetInvalidTipRecipient is InitialState { + function setUp() public override { + super.setUp(); + userSelectedTipRecipient.setSuccess(false); + } + + function test_UserSetInvalidTipRecipient_relayMessage_shouldRevert() public { + vm.expectRevert(IETHBridge.FailedClaim.selector); + _relayMessage(); + } +} From c67ec39dba7ed47cd35cc03fe81597ce2e7824ad Mon Sep 17 00:00:00 2001 From: Nikesh Nazareth Date: Tue, 22 Jul 2025 16:46:39 +1000 Subject: [PATCH 06/31] Test claimDeposit path --- test/MessageRelayer/DepositRecipientScenarios.t.sol | 5 +++++ test/MessageRelayer/TipRecipientScenarios.t.sol | 3 ++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/test/MessageRelayer/DepositRecipientScenarios.t.sol b/test/MessageRelayer/DepositRecipientScenarios.t.sol index 9572fa89..b1dbc23b 100644 --- a/test/MessageRelayer/DepositRecipientScenarios.t.sol +++ b/test/MessageRelayer/DepositRecipientScenarios.t.sol @@ -33,4 +33,9 @@ abstract contract DepositRecipientIsMessageRelayer is InitialState { vm.expectCall(address(messageRelayer), ethDeposit.data); _relayMessage(); } + + function test_DepositRecipientIsMessageRelayer_claimDeposit_shouldInvokeReceiveMessage() public { + vm.expectCall(address(messageRelayer), ethDeposit.data); + messageRelayer.ethBridge().claimDeposit(ethDeposit, height, proof); + } } diff --git a/test/MessageRelayer/TipRecipientScenarios.t.sol b/test/MessageRelayer/TipRecipientScenarios.t.sol index b35a281c..3785e119 100644 --- a/test/MessageRelayer/TipRecipientScenarios.t.sol +++ b/test/MessageRelayer/TipRecipientScenarios.t.sol @@ -3,8 +3,9 @@ pragma solidity ^0.8.28; import {DepositRecipientIsMessageRelayer} from "./DepositRecipientScenarios.t.sol"; import {GenericRecipient} from "./GenericRecipient.t.sol"; -import {IETHBridge} from "src/protocol/IETHBridge.sol"; + import {InitialState} from "./InitialState.t.sol"; +import {IETHBridge} from "src/protocol/IETHBridge.sol"; contract UserSetValidTipRecipient is DepositRecipientIsMessageRelayer { function test_UserSetValidTipRecipient_relayMessage_shouldTipUserSelectedRecipient() public { From 9074f4dd23f00336df28e3a411eb3c10eefee79f Mon Sep 17 00:00:00 2001 From: Leo Date: Fri, 25 Jul 2025 18:33:03 +0400 Subject: [PATCH 07/31] add more scenarios --- src/protocol/IMessageRelayer.sol | 3 ++ src/protocol/taiko_alethia/MessageRelayer.sol | 1 + test/MessageRelayer/FundAmountScenarios.t.sol | 36 +++++++++++++++++++ test/MessageRelayer/GasLimitScenarios.t.sol | 35 ++++++++++++++++++ 4 files changed, 75 insertions(+) create mode 100644 test/MessageRelayer/FundAmountScenarios.t.sol create mode 100644 test/MessageRelayer/GasLimitScenarios.t.sol diff --git a/src/protocol/IMessageRelayer.sol b/src/protocol/IMessageRelayer.sol index 69263748..4a767af1 100644 --- a/src/protocol/IMessageRelayer.sol +++ b/src/protocol/IMessageRelayer.sol @@ -19,6 +19,9 @@ interface IMessageRelayer { /// @dev Message forwarding failed error MessageForwardingFailed(); + /// @dev Value sent is higher than tip amount + error InsufficientValue(); + /// @dev Tip transfer failed error TipTransferFailed(); diff --git a/src/protocol/taiko_alethia/MessageRelayer.sol b/src/protocol/taiko_alethia/MessageRelayer.sol index 8441c7fc..0aabf5a4 100644 --- a/src/protocol/taiko_alethia/MessageRelayer.sol +++ b/src/protocol/taiko_alethia/MessageRelayer.sol @@ -90,6 +90,7 @@ contract MessageRelayer is ReentrancyGuardTransient, IMessageRelayer { tipRecipient = TIP_RECIPIENT_SLOT.asAddress().tload(); } + require(msg.value >= tip, InsufficientValue()); uint256 valueToSend = msg.value - tip; bool forwardMessageSuccess; diff --git a/test/MessageRelayer/FundAmountScenarios.t.sol b/test/MessageRelayer/FundAmountScenarios.t.sol new file mode 100644 index 00000000..14c8d188 --- /dev/null +++ b/test/MessageRelayer/FundAmountScenarios.t.sol @@ -0,0 +1,36 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.28; + +import {DepositRecipientIsMessageRelayer} from "./DepositRecipientScenarios.t.sol"; +import {GenericRecipient} from "./GenericRecipient.t.sol"; + +import {InitialState} from "./InitialState.t.sol"; +import {IETHBridge} from "src/protocol/IETHBridge.sol"; + +contract VaryingFundAmount is DepositRecipientIsMessageRelayer { + function setUp() public override { + super.setUp(); + } + + function test_sentZeroAmount_relayMessage_shouldRevert() public zeroAmount { + vm.expectRevert(IETHBridge.FailedClaim.selector); + _relayMessage(); + } + + function test_setTipHigherThanAmount_relayMessage_shouldRevert() public amountTooLow { + vm.expectRevert(IETHBridge.FailedClaim.selector); + _relayMessage(); + } + + modifier zeroAmount() { + ethDeposit.amount = 0; + _encodeReceiveCall(); + _; + } + + modifier amountTooLow() { + ethDeposit.amount = tip - 1 wei; + _encodeReceiveCall(); + _; + } +} diff --git a/test/MessageRelayer/GasLimitScenarios.t.sol b/test/MessageRelayer/GasLimitScenarios.t.sol new file mode 100644 index 00000000..06d51b15 --- /dev/null +++ b/test/MessageRelayer/GasLimitScenarios.t.sol @@ -0,0 +1,35 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.28; + +import {DepositRecipientIsMessageRelayer} from "./DepositRecipientScenarios.t.sol"; +import {GenericRecipient} from "./GenericRecipient.t.sol"; + +import {InitialState} from "./InitialState.t.sol"; +import {IETHBridge} from "src/protocol/IETHBridge.sol"; + +contract UserSetGasLimit is DepositRecipientIsMessageRelayer { + function setUp() public override { + super.setUp(); + } + + function test_userSetHighGasLimit_relayMessage_shouldRevert() public gasLimitHigherThanValue { + vm.expectRevert(IETHBridge.FailedClaim.selector); + _relayMessage(); + } + + function test_userReasonableGasLimit_relayMessage() public whenGasLimitIsReasonable { + _relayMessage(); + } + + modifier gasLimitHigherThanValue() { + gasLimit = amount + 1 wei; + _encodeReceiveCall(); + _; + } + + modifier whenGasLimitIsReasonable() { + gasLimit = 100_000; + _encodeReceiveCall(); + _; + } +} From fa42d460a0e4394e2085181a8d2057ff6e510ca0 Mon Sep 17 00:00:00 2001 From: Leo Date: Fri, 25 Jul 2025 18:38:39 +0400 Subject: [PATCH 08/31] made scripts easier to work with made scripts easier to work with fmt --- Cargo.toml | 9 ++-- offchain/{utils.rs => lib.rs} | 0 offchain/sample_deposit_proof.rs | 50 ++++++++++++++---- offchain/sample_signal_proof.rs | 6 +-- offchain/{ => tmpl}/sample_deposit_proof.tmpl | 0 offchain/{ => tmpl}/sample_proof.tmpl | 0 test/ETHBridge/SampleDepositProof.t.sol | 52 +++++++++---------- test/SignalService/SampleProof.t.sol | 10 ++-- 8 files changed, 77 insertions(+), 50 deletions(-) rename offchain/{utils.rs => lib.rs} (100%) rename offchain/{ => tmpl}/sample_deposit_proof.tmpl (100%) rename offchain/{ => tmpl}/sample_proof.tmpl (100%) diff --git a/Cargo.toml b/Cargo.toml index ddbfb20f..09c51314 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,17 +1,16 @@ [package] - name = "minimal-rollup" version = "0.1.0" edition = "2021" +[lib] +name = "minimal_rollup" +path = "offchain/lib.rs" + [[bin]] name = "signal_slot" path = "offchain/signal_slot.rs" -[[bin]] -name = "utils" -path = "offchain/utils.rs" - [[bin]] name = "sample_signal_proof" path = "offchain/sample_signal_proof.rs" diff --git a/offchain/utils.rs b/offchain/lib.rs similarity index 100% rename from offchain/utils.rs rename to offchain/lib.rs diff --git a/offchain/sample_deposit_proof.rs b/offchain/sample_deposit_proof.rs index b16368cf..c8d4ae2d 100644 --- a/offchain/sample_deposit_proof.rs +++ b/offchain/sample_deposit_proof.rs @@ -1,15 +1,25 @@ +use alloy::primitives::utils::parse_units; +use alloy::sol_types::SolCall; use eyre::Result; mod signal_slot; use signal_slot::get_signal_slot; -mod utils; -use utils::{deploy_eth_bridge, deploy_signal_service, get_proofs, get_provider, SignalProof}; +use minimal_rollup::{ + deploy_eth_bridge, deploy_signal_service, get_proofs, get_provider, SignalProof, +}; -use alloy::hex::decode; +use alloy::hex::{self, decode}; use alloy::primitives::{Address, Bytes, FixedBytes, U256}; use std::fs; +use alloy::sol; + +sol! { + function somePayableFunction(uint256 someArg) external payable returns (uint256); + function someNonpayableFunction(uint256 someArg) external returns (uint256); +} + fn expand_vector(vec: Vec, name: &str) -> String { let mut expanded = String::new(); for (i, item) in vec.iter().enumerate() { @@ -20,7 +30,7 @@ fn expand_vector(vec: Vec, name: &str) -> String { return expanded; } -fn create_deposit_call( +pub fn create_deposit_call( proof: SignalProof, nonce: usize, signer: Address, @@ -66,14 +76,33 @@ fn deposit_specification() -> Vec { // This is an address on the destination chain, so it seems natural to use one generated there // In this case, the CrossChainDepositExists.sol test case defines _randomAddress("recipient"); let recipient = "0x99A270Be1AA5E97633177041859aEEB9a0670fAa"; + // Use both zero and non-zero amounts (in this case 4 ether) - let amounts = vec![0_u128, 4000000000000000000_u128]; + let amounts: Vec = vec![U256::ZERO, parse_units("4", "ether").unwrap().into()]; + // Use different calldata to try different functions and inputs + let valid_payable_function_call = somePayableFunctionCall { + someArg: U256::from(1234), + } + .abi_encode(); + let invalid_payable_function_call = somePayableFunctionCall { + someArg: U256::from(1235), + } + .abi_encode(); + let valid_nonpayable_function_call = someNonpayableFunctionCall { + someArg: U256::from(1234), + } + .abi_encode(); + + let valid_payable_encoded = hex::encode(valid_payable_function_call); + let invalid_payable_encoded = hex::encode(invalid_payable_function_call); + let valid_nonpayable_encoded = hex::encode(valid_nonpayable_function_call); + let calldata = vec![ - "", // empty - "9b28f6fb00000000000000000000000000000000000000000000000000000000000004d2", // (valid) call to somePayableFunction(1234) - "9b28f6fb00000000000000000000000000000000000000000000000000000000000004d3", // (invalid) call to somePayableFunction(1235) - "5932a71200000000000000000000000000000000000000000000000000000000000004d2", // (valid) call to `someNonPayableFunction(1234)` + "", + &valid_payable_encoded, + &invalid_payable_encoded, + &valid_nonpayable_encoded, ]; let zero_canceler = Address::ZERO; @@ -102,6 +131,7 @@ async fn main() -> Result<()> { let deposits = deposit_specification(); assert!(deposits.len() > 0, "No deposits to prove"); let mut ids: Vec> = vec![]; + // Perform all deposits for (_i, spec) in deposits.iter().enumerate() { let tx = eth_bridge @@ -150,7 +180,7 @@ async fn main() -> Result<()> { .as_str(); } - let template = fs::read_to_string("offchain/sample_deposit_proof.tmpl")?; + let template = fs::read_to_string("offchain/tmpl/sample_deposit_proof.tmpl")?; let formatted = template .replace( "{signal_service_address}", diff --git a/offchain/sample_signal_proof.rs b/offchain/sample_signal_proof.rs index ab7aa6de..db618470 100644 --- a/offchain/sample_signal_proof.rs +++ b/offchain/sample_signal_proof.rs @@ -3,8 +3,7 @@ use eyre::Result; mod signal_slot; use signal_slot::get_signal_slot; -mod utils; -use utils::{deploy_signal_service, get_proofs, get_provider}; +use minimal_rollup::{deploy_signal_service, get_proofs, get_provider}; use alloy::primitives::Bytes; use std::fs; @@ -32,7 +31,7 @@ async fn main() -> Result<()> { let proof = get_proofs(&provider, slot, &signal_service).await?; - let template = fs::read_to_string("offchain/sample_proof.tmpl")?; + let template = fs::read_to_string("offchain/tmpl/sample_proof.tmpl")?; let formatted = template .replace("{signal_service_address}", signal_service.address().to_string().as_str()) .replace("{block_hash}", proof.block_hash.to_string().as_str()) @@ -47,4 +46,3 @@ async fn main() -> Result<()> { println!("{}", formatted); Ok(()) } - diff --git a/offchain/sample_deposit_proof.tmpl b/offchain/tmpl/sample_deposit_proof.tmpl similarity index 100% rename from offchain/sample_deposit_proof.tmpl rename to offchain/tmpl/sample_deposit_proof.tmpl diff --git a/offchain/sample_proof.tmpl b/offchain/tmpl/sample_proof.tmpl similarity index 100% rename from offchain/sample_proof.tmpl rename to offchain/tmpl/sample_proof.tmpl diff --git a/test/ETHBridge/SampleDepositProof.t.sol b/test/ETHBridge/SampleDepositProof.t.sol index a98b4e34..5152c710 100644 --- a/test/ETHBridge/SampleDepositProof.t.sol +++ b/test/ETHBridge/SampleDepositProof.t.sol @@ -23,11 +23,11 @@ contract SampleDepositProof is ISampleDepositProof { // Populate proof 0 accountProof = new bytes[](3); accountProof[0] = - hex"f90151a0b91a8b7a7e9d3eab90afd81da3725030742f663c6ed8c26657bf00d842a9f4aaa01689b2a5203afd9ea0a0ca3765e4a538c7176e53eac1f8307a344ffc3c6176558080a04f9b3074be6c1048a52625a98244b225239aeb72c6fe9a8b84295c54c11f7711a0bba366e6a2f8ee0148ec2fb8c8f612f97c5c013c92997f187dec28eab359bf9980a0ad32814d04dc4f3d2d4837d098843cdb23d3b2ff2998d68bd3d567c28351f678a04b29efa44ecf50c19b34950cf1d0f05e00568bcc873120fbea9a4e8439de0962a0d0a1bfe5b45d2d863a794f016450a4caca04f3b599e8d1652afca8b752935fd880a0bf9b09e442e044778b354abbadb5ec049d7f5e8b585c3966d476c4fbc9a181d28080a09016cdf52377ca6d080a61a641119ebe8344e7efa3a347dbaf187d0bc706997ca0e5c557a0ce3894afeb44c37f3d24247f67dc76a174d8cacc360c1210eef60a7680"; + hex"f90151a0b91a8b7a7e9d3eab90afd81da3725030742f663c6ed8c26657bf00d842a9f4aaa01689b2a5203afd9ea0a0ca3765e4a538c7176e53eac1f8307a344ffc3c6176558080a02ed5a0d6485766183ba3d10dd7aa8c54b2b9fa6812a4daf2b769c741629e3a9aa023d456eaa93a2fcdb9831c9f6f6ead7e4627c2206f543fba0bad1fe7ec91211280a014ae85f7e6c07fb4f37e8c7cc10453d86ce8d9c8bb22c06137f424eaf2b8341ea04b29efa44ecf50c19b34950cf1d0f05e00568bcc873120fbea9a4e8439de0962a0d0a1bfe5b45d2d863a794f016450a4caca04f3b599e8d1652afca8b752935fd880a0bf9b09e442e044778b354abbadb5ec049d7f5e8b585c3966d476c4fbc9a181d28080a051bf87099e2e831acd6ab6bc1792f2cb28ed80cf38bd7c7b83dc6a62e527f6f1a0e5c557a0ce3894afeb44c37f3d24247f67dc76a174d8cacc360c1210eef60a7680"; accountProof[1] = - hex"f85180808080a0253a8805e504ff1d068e38ce687ee7719d6428467537c6e0a9e380bab6c1e7cc80808080a074ae0767a40fc6fff780050f46a50f6b39ca4edb7faa9669108157a1cd96f40980808080808080"; + hex"f85180808080a0fe75e385ee02ca1437bc5e16c42e59a4c6d5ebf6d25ff9ba50e67262ca9078b780808080a074ae0767a40fc6fff780050f46a50f6b39ca4edb7faa9669108157a1cd96f40980808080808080"; accountProof[2] = - hex"f869a020e659e60b21cc961f64ad47f20523c1d329d4bbda245ef3940a76dc89d0911bb846f8440180a03135b57fa5c3a632d77d047ed9beb7ad2fb10175e0aaca41fbe4de814ed565a5a04c6a820395819646f26663eb133c936140a62bc46c8d408aa765d28abac30b48"; + hex"f869a020e659e60b21cc961f64ad47f20523c1d329d4bbda245ef3940a76dc89d0911bb846f8440180a03135b57fa5c3a632d77d047ed9beb7ad2fb10175e0aaca41fbe4de814ed565a5a03e02a303e0a64f4588d05eaed8454840cdd10fe15cbfae680aa81923c5b8c9f0"; storageProof = new bytes[](2); storageProof[0] = hex"f8d180a0c3fd297a8560d746e4780fae27a416a5e164111f3fbdeafed15debeeb6285ffc808080a033bc0bdee3ae72377d806414ce91a661c7113f3b2f536fccf941284eca737bca80a008da7afef899990836d7474b37dba4be023feff40beef8598837f46dd285be8da03032f295e4a1ec28653b9412977b641edde37ab05622bd1e70296ff9d43981b980a0277ced4f9566e5d05fe2c8ffe2aa09e8d4d679218e0f545f35ae5c57d77257b280a06465f9f60adda0e80794baebb3431f108a436c0958858df32ea220d788a9e1e580808080"; @@ -49,11 +49,11 @@ contract SampleDepositProof is ISampleDepositProof { // Populate proof 1 accountProof = new bytes[](3); accountProof[0] = - hex"f90151a0b91a8b7a7e9d3eab90afd81da3725030742f663c6ed8c26657bf00d842a9f4aaa01689b2a5203afd9ea0a0ca3765e4a538c7176e53eac1f8307a344ffc3c6176558080a04f9b3074be6c1048a52625a98244b225239aeb72c6fe9a8b84295c54c11f7711a0bba366e6a2f8ee0148ec2fb8c8f612f97c5c013c92997f187dec28eab359bf9980a0ad32814d04dc4f3d2d4837d098843cdb23d3b2ff2998d68bd3d567c28351f678a04b29efa44ecf50c19b34950cf1d0f05e00568bcc873120fbea9a4e8439de0962a0d0a1bfe5b45d2d863a794f016450a4caca04f3b599e8d1652afca8b752935fd880a0bf9b09e442e044778b354abbadb5ec049d7f5e8b585c3966d476c4fbc9a181d28080a09016cdf52377ca6d080a61a641119ebe8344e7efa3a347dbaf187d0bc706997ca0e5c557a0ce3894afeb44c37f3d24247f67dc76a174d8cacc360c1210eef60a7680"; + hex"f90151a0b91a8b7a7e9d3eab90afd81da3725030742f663c6ed8c26657bf00d842a9f4aaa01689b2a5203afd9ea0a0ca3765e4a538c7176e53eac1f8307a344ffc3c6176558080a02ed5a0d6485766183ba3d10dd7aa8c54b2b9fa6812a4daf2b769c741629e3a9aa023d456eaa93a2fcdb9831c9f6f6ead7e4627c2206f543fba0bad1fe7ec91211280a014ae85f7e6c07fb4f37e8c7cc10453d86ce8d9c8bb22c06137f424eaf2b8341ea04b29efa44ecf50c19b34950cf1d0f05e00568bcc873120fbea9a4e8439de0962a0d0a1bfe5b45d2d863a794f016450a4caca04f3b599e8d1652afca8b752935fd880a0bf9b09e442e044778b354abbadb5ec049d7f5e8b585c3966d476c4fbc9a181d28080a051bf87099e2e831acd6ab6bc1792f2cb28ed80cf38bd7c7b83dc6a62e527f6f1a0e5c557a0ce3894afeb44c37f3d24247f67dc76a174d8cacc360c1210eef60a7680"; accountProof[1] = - hex"f85180808080a0253a8805e504ff1d068e38ce687ee7719d6428467537c6e0a9e380bab6c1e7cc80808080a074ae0767a40fc6fff780050f46a50f6b39ca4edb7faa9669108157a1cd96f40980808080808080"; + hex"f85180808080a0fe75e385ee02ca1437bc5e16c42e59a4c6d5ebf6d25ff9ba50e67262ca9078b780808080a074ae0767a40fc6fff780050f46a50f6b39ca4edb7faa9669108157a1cd96f40980808080808080"; accountProof[2] = - hex"f869a020e659e60b21cc961f64ad47f20523c1d329d4bbda245ef3940a76dc89d0911bb846f8440180a03135b57fa5c3a632d77d047ed9beb7ad2fb10175e0aaca41fbe4de814ed565a5a04c6a820395819646f26663eb133c936140a62bc46c8d408aa765d28abac30b48"; + hex"f869a020e659e60b21cc961f64ad47f20523c1d329d4bbda245ef3940a76dc89d0911bb846f8440180a03135b57fa5c3a632d77d047ed9beb7ad2fb10175e0aaca41fbe4de814ed565a5a03e02a303e0a64f4588d05eaed8454840cdd10fe15cbfae680aa81923c5b8c9f0"; storageProof = new bytes[](3); storageProof[0] = hex"f8d180a0c3fd297a8560d746e4780fae27a416a5e164111f3fbdeafed15debeeb6285ffc808080a033bc0bdee3ae72377d806414ce91a661c7113f3b2f536fccf941284eca737bca80a008da7afef899990836d7474b37dba4be023feff40beef8598837f46dd285be8da03032f295e4a1ec28653b9412977b641edde37ab05622bd1e70296ff9d43981b980a0277ced4f9566e5d05fe2c8ffe2aa09e8d4d679218e0f545f35ae5c57d77257b280a06465f9f60adda0e80794baebb3431f108a436c0958858df32ea220d788a9e1e580808080"; @@ -77,11 +77,11 @@ contract SampleDepositProof is ISampleDepositProof { // Populate proof 2 accountProof = new bytes[](3); accountProof[0] = - hex"f90151a0b91a8b7a7e9d3eab90afd81da3725030742f663c6ed8c26657bf00d842a9f4aaa01689b2a5203afd9ea0a0ca3765e4a538c7176e53eac1f8307a344ffc3c6176558080a04f9b3074be6c1048a52625a98244b225239aeb72c6fe9a8b84295c54c11f7711a0bba366e6a2f8ee0148ec2fb8c8f612f97c5c013c92997f187dec28eab359bf9980a0ad32814d04dc4f3d2d4837d098843cdb23d3b2ff2998d68bd3d567c28351f678a04b29efa44ecf50c19b34950cf1d0f05e00568bcc873120fbea9a4e8439de0962a0d0a1bfe5b45d2d863a794f016450a4caca04f3b599e8d1652afca8b752935fd880a0bf9b09e442e044778b354abbadb5ec049d7f5e8b585c3966d476c4fbc9a181d28080a09016cdf52377ca6d080a61a641119ebe8344e7efa3a347dbaf187d0bc706997ca0e5c557a0ce3894afeb44c37f3d24247f67dc76a174d8cacc360c1210eef60a7680"; + hex"f90151a0b91a8b7a7e9d3eab90afd81da3725030742f663c6ed8c26657bf00d842a9f4aaa01689b2a5203afd9ea0a0ca3765e4a538c7176e53eac1f8307a344ffc3c6176558080a02ed5a0d6485766183ba3d10dd7aa8c54b2b9fa6812a4daf2b769c741629e3a9aa023d456eaa93a2fcdb9831c9f6f6ead7e4627c2206f543fba0bad1fe7ec91211280a014ae85f7e6c07fb4f37e8c7cc10453d86ce8d9c8bb22c06137f424eaf2b8341ea04b29efa44ecf50c19b34950cf1d0f05e00568bcc873120fbea9a4e8439de0962a0d0a1bfe5b45d2d863a794f016450a4caca04f3b599e8d1652afca8b752935fd880a0bf9b09e442e044778b354abbadb5ec049d7f5e8b585c3966d476c4fbc9a181d28080a051bf87099e2e831acd6ab6bc1792f2cb28ed80cf38bd7c7b83dc6a62e527f6f1a0e5c557a0ce3894afeb44c37f3d24247f67dc76a174d8cacc360c1210eef60a7680"; accountProof[1] = - hex"f85180808080a0253a8805e504ff1d068e38ce687ee7719d6428467537c6e0a9e380bab6c1e7cc80808080a074ae0767a40fc6fff780050f46a50f6b39ca4edb7faa9669108157a1cd96f40980808080808080"; + hex"f85180808080a0fe75e385ee02ca1437bc5e16c42e59a4c6d5ebf6d25ff9ba50e67262ca9078b780808080a074ae0767a40fc6fff780050f46a50f6b39ca4edb7faa9669108157a1cd96f40980808080808080"; accountProof[2] = - hex"f869a020e659e60b21cc961f64ad47f20523c1d329d4bbda245ef3940a76dc89d0911bb846f8440180a03135b57fa5c3a632d77d047ed9beb7ad2fb10175e0aaca41fbe4de814ed565a5a04c6a820395819646f26663eb133c936140a62bc46c8d408aa765d28abac30b48"; + hex"f869a020e659e60b21cc961f64ad47f20523c1d329d4bbda245ef3940a76dc89d0911bb846f8440180a03135b57fa5c3a632d77d047ed9beb7ad2fb10175e0aaca41fbe4de814ed565a5a03e02a303e0a64f4588d05eaed8454840cdd10fe15cbfae680aa81923c5b8c9f0"; storageProof = new bytes[](2); storageProof[0] = hex"f8d180a0c3fd297a8560d746e4780fae27a416a5e164111f3fbdeafed15debeeb6285ffc808080a033bc0bdee3ae72377d806414ce91a661c7113f3b2f536fccf941284eca737bca80a008da7afef899990836d7474b37dba4be023feff40beef8598837f46dd285be8da03032f295e4a1ec28653b9412977b641edde37ab05622bd1e70296ff9d43981b980a0277ced4f9566e5d05fe2c8ffe2aa09e8d4d679218e0f545f35ae5c57d77257b280a06465f9f60adda0e80794baebb3431f108a436c0958858df32ea220d788a9e1e580808080"; @@ -103,11 +103,11 @@ contract SampleDepositProof is ISampleDepositProof { // Populate proof 3 accountProof = new bytes[](3); accountProof[0] = - hex"f90151a0b91a8b7a7e9d3eab90afd81da3725030742f663c6ed8c26657bf00d842a9f4aaa01689b2a5203afd9ea0a0ca3765e4a538c7176e53eac1f8307a344ffc3c6176558080a04f9b3074be6c1048a52625a98244b225239aeb72c6fe9a8b84295c54c11f7711a0bba366e6a2f8ee0148ec2fb8c8f612f97c5c013c92997f187dec28eab359bf9980a0ad32814d04dc4f3d2d4837d098843cdb23d3b2ff2998d68bd3d567c28351f678a04b29efa44ecf50c19b34950cf1d0f05e00568bcc873120fbea9a4e8439de0962a0d0a1bfe5b45d2d863a794f016450a4caca04f3b599e8d1652afca8b752935fd880a0bf9b09e442e044778b354abbadb5ec049d7f5e8b585c3966d476c4fbc9a181d28080a09016cdf52377ca6d080a61a641119ebe8344e7efa3a347dbaf187d0bc706997ca0e5c557a0ce3894afeb44c37f3d24247f67dc76a174d8cacc360c1210eef60a7680"; + hex"f90151a0b91a8b7a7e9d3eab90afd81da3725030742f663c6ed8c26657bf00d842a9f4aaa01689b2a5203afd9ea0a0ca3765e4a538c7176e53eac1f8307a344ffc3c6176558080a02ed5a0d6485766183ba3d10dd7aa8c54b2b9fa6812a4daf2b769c741629e3a9aa023d456eaa93a2fcdb9831c9f6f6ead7e4627c2206f543fba0bad1fe7ec91211280a014ae85f7e6c07fb4f37e8c7cc10453d86ce8d9c8bb22c06137f424eaf2b8341ea04b29efa44ecf50c19b34950cf1d0f05e00568bcc873120fbea9a4e8439de0962a0d0a1bfe5b45d2d863a794f016450a4caca04f3b599e8d1652afca8b752935fd880a0bf9b09e442e044778b354abbadb5ec049d7f5e8b585c3966d476c4fbc9a181d28080a051bf87099e2e831acd6ab6bc1792f2cb28ed80cf38bd7c7b83dc6a62e527f6f1a0e5c557a0ce3894afeb44c37f3d24247f67dc76a174d8cacc360c1210eef60a7680"; accountProof[1] = - hex"f85180808080a0253a8805e504ff1d068e38ce687ee7719d6428467537c6e0a9e380bab6c1e7cc80808080a074ae0767a40fc6fff780050f46a50f6b39ca4edb7faa9669108157a1cd96f40980808080808080"; + hex"f85180808080a0fe75e385ee02ca1437bc5e16c42e59a4c6d5ebf6d25ff9ba50e67262ca9078b780808080a074ae0767a40fc6fff780050f46a50f6b39ca4edb7faa9669108157a1cd96f40980808080808080"; accountProof[2] = - hex"f869a020e659e60b21cc961f64ad47f20523c1d329d4bbda245ef3940a76dc89d0911bb846f8440180a03135b57fa5c3a632d77d047ed9beb7ad2fb10175e0aaca41fbe4de814ed565a5a04c6a820395819646f26663eb133c936140a62bc46c8d408aa765d28abac30b48"; + hex"f869a020e659e60b21cc961f64ad47f20523c1d329d4bbda245ef3940a76dc89d0911bb846f8440180a03135b57fa5c3a632d77d047ed9beb7ad2fb10175e0aaca41fbe4de814ed565a5a03e02a303e0a64f4588d05eaed8454840cdd10fe15cbfae680aa81923c5b8c9f0"; storageProof = new bytes[](2); storageProof[0] = hex"f8d180a0c3fd297a8560d746e4780fae27a416a5e164111f3fbdeafed15debeeb6285ffc808080a033bc0bdee3ae72377d806414ce91a661c7113f3b2f536fccf941284eca737bca80a008da7afef899990836d7474b37dba4be023feff40beef8598837f46dd285be8da03032f295e4a1ec28653b9412977b641edde37ab05622bd1e70296ff9d43981b980a0277ced4f9566e5d05fe2c8ffe2aa09e8d4d679218e0f545f35ae5c57d77257b280a06465f9f60adda0e80794baebb3431f108a436c0958858df32ea220d788a9e1e580808080"; @@ -129,11 +129,11 @@ contract SampleDepositProof is ISampleDepositProof { // Populate proof 4 accountProof = new bytes[](3); accountProof[0] = - hex"f90151a0b91a8b7a7e9d3eab90afd81da3725030742f663c6ed8c26657bf00d842a9f4aaa01689b2a5203afd9ea0a0ca3765e4a538c7176e53eac1f8307a344ffc3c6176558080a04f9b3074be6c1048a52625a98244b225239aeb72c6fe9a8b84295c54c11f7711a0bba366e6a2f8ee0148ec2fb8c8f612f97c5c013c92997f187dec28eab359bf9980a0ad32814d04dc4f3d2d4837d098843cdb23d3b2ff2998d68bd3d567c28351f678a04b29efa44ecf50c19b34950cf1d0f05e00568bcc873120fbea9a4e8439de0962a0d0a1bfe5b45d2d863a794f016450a4caca04f3b599e8d1652afca8b752935fd880a0bf9b09e442e044778b354abbadb5ec049d7f5e8b585c3966d476c4fbc9a181d28080a09016cdf52377ca6d080a61a641119ebe8344e7efa3a347dbaf187d0bc706997ca0e5c557a0ce3894afeb44c37f3d24247f67dc76a174d8cacc360c1210eef60a7680"; + hex"f90151a0b91a8b7a7e9d3eab90afd81da3725030742f663c6ed8c26657bf00d842a9f4aaa01689b2a5203afd9ea0a0ca3765e4a538c7176e53eac1f8307a344ffc3c6176558080a02ed5a0d6485766183ba3d10dd7aa8c54b2b9fa6812a4daf2b769c741629e3a9aa023d456eaa93a2fcdb9831c9f6f6ead7e4627c2206f543fba0bad1fe7ec91211280a014ae85f7e6c07fb4f37e8c7cc10453d86ce8d9c8bb22c06137f424eaf2b8341ea04b29efa44ecf50c19b34950cf1d0f05e00568bcc873120fbea9a4e8439de0962a0d0a1bfe5b45d2d863a794f016450a4caca04f3b599e8d1652afca8b752935fd880a0bf9b09e442e044778b354abbadb5ec049d7f5e8b585c3966d476c4fbc9a181d28080a051bf87099e2e831acd6ab6bc1792f2cb28ed80cf38bd7c7b83dc6a62e527f6f1a0e5c557a0ce3894afeb44c37f3d24247f67dc76a174d8cacc360c1210eef60a7680"; accountProof[1] = - hex"f85180808080a0253a8805e504ff1d068e38ce687ee7719d6428467537c6e0a9e380bab6c1e7cc80808080a074ae0767a40fc6fff780050f46a50f6b39ca4edb7faa9669108157a1cd96f40980808080808080"; + hex"f85180808080a0fe75e385ee02ca1437bc5e16c42e59a4c6d5ebf6d25ff9ba50e67262ca9078b780808080a074ae0767a40fc6fff780050f46a50f6b39ca4edb7faa9669108157a1cd96f40980808080808080"; accountProof[2] = - hex"f869a020e659e60b21cc961f64ad47f20523c1d329d4bbda245ef3940a76dc89d0911bb846f8440180a03135b57fa5c3a632d77d047ed9beb7ad2fb10175e0aaca41fbe4de814ed565a5a04c6a820395819646f26663eb133c936140a62bc46c8d408aa765d28abac30b48"; + hex"f869a020e659e60b21cc961f64ad47f20523c1d329d4bbda245ef3940a76dc89d0911bb846f8440180a03135b57fa5c3a632d77d047ed9beb7ad2fb10175e0aaca41fbe4de814ed565a5a03e02a303e0a64f4588d05eaed8454840cdd10fe15cbfae680aa81923c5b8c9f0"; storageProof = new bytes[](2); storageProof[0] = hex"f8d180a0c3fd297a8560d746e4780fae27a416a5e164111f3fbdeafed15debeeb6285ffc808080a033bc0bdee3ae72377d806414ce91a661c7113f3b2f536fccf941284eca737bca80a008da7afef899990836d7474b37dba4be023feff40beef8598837f46dd285be8da03032f295e4a1ec28653b9412977b641edde37ab05622bd1e70296ff9d43981b980a0277ced4f9566e5d05fe2c8ffe2aa09e8d4d679218e0f545f35ae5c57d77257b280a06465f9f60adda0e80794baebb3431f108a436c0958858df32ea220d788a9e1e580808080"; @@ -155,11 +155,11 @@ contract SampleDepositProof is ISampleDepositProof { // Populate proof 5 accountProof = new bytes[](3); accountProof[0] = - hex"f90151a0b91a8b7a7e9d3eab90afd81da3725030742f663c6ed8c26657bf00d842a9f4aaa01689b2a5203afd9ea0a0ca3765e4a538c7176e53eac1f8307a344ffc3c6176558080a04f9b3074be6c1048a52625a98244b225239aeb72c6fe9a8b84295c54c11f7711a0bba366e6a2f8ee0148ec2fb8c8f612f97c5c013c92997f187dec28eab359bf9980a0ad32814d04dc4f3d2d4837d098843cdb23d3b2ff2998d68bd3d567c28351f678a04b29efa44ecf50c19b34950cf1d0f05e00568bcc873120fbea9a4e8439de0962a0d0a1bfe5b45d2d863a794f016450a4caca04f3b599e8d1652afca8b752935fd880a0bf9b09e442e044778b354abbadb5ec049d7f5e8b585c3966d476c4fbc9a181d28080a09016cdf52377ca6d080a61a641119ebe8344e7efa3a347dbaf187d0bc706997ca0e5c557a0ce3894afeb44c37f3d24247f67dc76a174d8cacc360c1210eef60a7680"; + hex"f90151a0b91a8b7a7e9d3eab90afd81da3725030742f663c6ed8c26657bf00d842a9f4aaa01689b2a5203afd9ea0a0ca3765e4a538c7176e53eac1f8307a344ffc3c6176558080a02ed5a0d6485766183ba3d10dd7aa8c54b2b9fa6812a4daf2b769c741629e3a9aa023d456eaa93a2fcdb9831c9f6f6ead7e4627c2206f543fba0bad1fe7ec91211280a014ae85f7e6c07fb4f37e8c7cc10453d86ce8d9c8bb22c06137f424eaf2b8341ea04b29efa44ecf50c19b34950cf1d0f05e00568bcc873120fbea9a4e8439de0962a0d0a1bfe5b45d2d863a794f016450a4caca04f3b599e8d1652afca8b752935fd880a0bf9b09e442e044778b354abbadb5ec049d7f5e8b585c3966d476c4fbc9a181d28080a051bf87099e2e831acd6ab6bc1792f2cb28ed80cf38bd7c7b83dc6a62e527f6f1a0e5c557a0ce3894afeb44c37f3d24247f67dc76a174d8cacc360c1210eef60a7680"; accountProof[1] = - hex"f85180808080a0253a8805e504ff1d068e38ce687ee7719d6428467537c6e0a9e380bab6c1e7cc80808080a074ae0767a40fc6fff780050f46a50f6b39ca4edb7faa9669108157a1cd96f40980808080808080"; + hex"f85180808080a0fe75e385ee02ca1437bc5e16c42e59a4c6d5ebf6d25ff9ba50e67262ca9078b780808080a074ae0767a40fc6fff780050f46a50f6b39ca4edb7faa9669108157a1cd96f40980808080808080"; accountProof[2] = - hex"f869a020e659e60b21cc961f64ad47f20523c1d329d4bbda245ef3940a76dc89d0911bb846f8440180a03135b57fa5c3a632d77d047ed9beb7ad2fb10175e0aaca41fbe4de814ed565a5a04c6a820395819646f26663eb133c936140a62bc46c8d408aa765d28abac30b48"; + hex"f869a020e659e60b21cc961f64ad47f20523c1d329d4bbda245ef3940a76dc89d0911bb846f8440180a03135b57fa5c3a632d77d047ed9beb7ad2fb10175e0aaca41fbe4de814ed565a5a03e02a303e0a64f4588d05eaed8454840cdd10fe15cbfae680aa81923c5b8c9f0"; storageProof = new bytes[](2); storageProof[0] = hex"f8d180a0c3fd297a8560d746e4780fae27a416a5e164111f3fbdeafed15debeeb6285ffc808080a033bc0bdee3ae72377d806414ce91a661c7113f3b2f536fccf941284eca737bca80a008da7afef899990836d7474b37dba4be023feff40beef8598837f46dd285be8da03032f295e4a1ec28653b9412977b641edde37ab05622bd1e70296ff9d43981b980a0277ced4f9566e5d05fe2c8ffe2aa09e8d4d679218e0f545f35ae5c57d77257b280a06465f9f60adda0e80794baebb3431f108a436c0958858df32ea220d788a9e1e580808080"; @@ -181,11 +181,11 @@ contract SampleDepositProof is ISampleDepositProof { // Populate proof 6 accountProof = new bytes[](3); accountProof[0] = - hex"f90151a0b91a8b7a7e9d3eab90afd81da3725030742f663c6ed8c26657bf00d842a9f4aaa01689b2a5203afd9ea0a0ca3765e4a538c7176e53eac1f8307a344ffc3c6176558080a04f9b3074be6c1048a52625a98244b225239aeb72c6fe9a8b84295c54c11f7711a0bba366e6a2f8ee0148ec2fb8c8f612f97c5c013c92997f187dec28eab359bf9980a0ad32814d04dc4f3d2d4837d098843cdb23d3b2ff2998d68bd3d567c28351f678a04b29efa44ecf50c19b34950cf1d0f05e00568bcc873120fbea9a4e8439de0962a0d0a1bfe5b45d2d863a794f016450a4caca04f3b599e8d1652afca8b752935fd880a0bf9b09e442e044778b354abbadb5ec049d7f5e8b585c3966d476c4fbc9a181d28080a09016cdf52377ca6d080a61a641119ebe8344e7efa3a347dbaf187d0bc706997ca0e5c557a0ce3894afeb44c37f3d24247f67dc76a174d8cacc360c1210eef60a7680"; + hex"f90151a0b91a8b7a7e9d3eab90afd81da3725030742f663c6ed8c26657bf00d842a9f4aaa01689b2a5203afd9ea0a0ca3765e4a538c7176e53eac1f8307a344ffc3c6176558080a02ed5a0d6485766183ba3d10dd7aa8c54b2b9fa6812a4daf2b769c741629e3a9aa023d456eaa93a2fcdb9831c9f6f6ead7e4627c2206f543fba0bad1fe7ec91211280a014ae85f7e6c07fb4f37e8c7cc10453d86ce8d9c8bb22c06137f424eaf2b8341ea04b29efa44ecf50c19b34950cf1d0f05e00568bcc873120fbea9a4e8439de0962a0d0a1bfe5b45d2d863a794f016450a4caca04f3b599e8d1652afca8b752935fd880a0bf9b09e442e044778b354abbadb5ec049d7f5e8b585c3966d476c4fbc9a181d28080a051bf87099e2e831acd6ab6bc1792f2cb28ed80cf38bd7c7b83dc6a62e527f6f1a0e5c557a0ce3894afeb44c37f3d24247f67dc76a174d8cacc360c1210eef60a7680"; accountProof[1] = - hex"f85180808080a0253a8805e504ff1d068e38ce687ee7719d6428467537c6e0a9e380bab6c1e7cc80808080a074ae0767a40fc6fff780050f46a50f6b39ca4edb7faa9669108157a1cd96f40980808080808080"; + hex"f85180808080a0fe75e385ee02ca1437bc5e16c42e59a4c6d5ebf6d25ff9ba50e67262ca9078b780808080a074ae0767a40fc6fff780050f46a50f6b39ca4edb7faa9669108157a1cd96f40980808080808080"; accountProof[2] = - hex"f869a020e659e60b21cc961f64ad47f20523c1d329d4bbda245ef3940a76dc89d0911bb846f8440180a03135b57fa5c3a632d77d047ed9beb7ad2fb10175e0aaca41fbe4de814ed565a5a04c6a820395819646f26663eb133c936140a62bc46c8d408aa765d28abac30b48"; + hex"f869a020e659e60b21cc961f64ad47f20523c1d329d4bbda245ef3940a76dc89d0911bb846f8440180a03135b57fa5c3a632d77d047ed9beb7ad2fb10175e0aaca41fbe4de814ed565a5a03e02a303e0a64f4588d05eaed8454840cdd10fe15cbfae680aa81923c5b8c9f0"; storageProof = new bytes[](3); storageProof[0] = hex"f8d180a0c3fd297a8560d746e4780fae27a416a5e164111f3fbdeafed15debeeb6285ffc808080a033bc0bdee3ae72377d806414ce91a661c7113f3b2f536fccf941284eca737bca80a008da7afef899990836d7474b37dba4be023feff40beef8598837f46dd285be8da03032f295e4a1ec28653b9412977b641edde37ab05622bd1e70296ff9d43981b980a0277ced4f9566e5d05fe2c8ffe2aa09e8d4d679218e0f545f35ae5c57d77257b280a06465f9f60adda0e80794baebb3431f108a436c0958858df32ea220d788a9e1e580808080"; @@ -209,11 +209,11 @@ contract SampleDepositProof is ISampleDepositProof { // Populate proof 7 accountProof = new bytes[](3); accountProof[0] = - hex"f90151a0b91a8b7a7e9d3eab90afd81da3725030742f663c6ed8c26657bf00d842a9f4aaa01689b2a5203afd9ea0a0ca3765e4a538c7176e53eac1f8307a344ffc3c6176558080a04f9b3074be6c1048a52625a98244b225239aeb72c6fe9a8b84295c54c11f7711a0bba366e6a2f8ee0148ec2fb8c8f612f97c5c013c92997f187dec28eab359bf9980a0ad32814d04dc4f3d2d4837d098843cdb23d3b2ff2998d68bd3d567c28351f678a04b29efa44ecf50c19b34950cf1d0f05e00568bcc873120fbea9a4e8439de0962a0d0a1bfe5b45d2d863a794f016450a4caca04f3b599e8d1652afca8b752935fd880a0bf9b09e442e044778b354abbadb5ec049d7f5e8b585c3966d476c4fbc9a181d28080a09016cdf52377ca6d080a61a641119ebe8344e7efa3a347dbaf187d0bc706997ca0e5c557a0ce3894afeb44c37f3d24247f67dc76a174d8cacc360c1210eef60a7680"; + hex"f90151a0b91a8b7a7e9d3eab90afd81da3725030742f663c6ed8c26657bf00d842a9f4aaa01689b2a5203afd9ea0a0ca3765e4a538c7176e53eac1f8307a344ffc3c6176558080a02ed5a0d6485766183ba3d10dd7aa8c54b2b9fa6812a4daf2b769c741629e3a9aa023d456eaa93a2fcdb9831c9f6f6ead7e4627c2206f543fba0bad1fe7ec91211280a014ae85f7e6c07fb4f37e8c7cc10453d86ce8d9c8bb22c06137f424eaf2b8341ea04b29efa44ecf50c19b34950cf1d0f05e00568bcc873120fbea9a4e8439de0962a0d0a1bfe5b45d2d863a794f016450a4caca04f3b599e8d1652afca8b752935fd880a0bf9b09e442e044778b354abbadb5ec049d7f5e8b585c3966d476c4fbc9a181d28080a051bf87099e2e831acd6ab6bc1792f2cb28ed80cf38bd7c7b83dc6a62e527f6f1a0e5c557a0ce3894afeb44c37f3d24247f67dc76a174d8cacc360c1210eef60a7680"; accountProof[1] = - hex"f85180808080a0253a8805e504ff1d068e38ce687ee7719d6428467537c6e0a9e380bab6c1e7cc80808080a074ae0767a40fc6fff780050f46a50f6b39ca4edb7faa9669108157a1cd96f40980808080808080"; + hex"f85180808080a0fe75e385ee02ca1437bc5e16c42e59a4c6d5ebf6d25ff9ba50e67262ca9078b780808080a074ae0767a40fc6fff780050f46a50f6b39ca4edb7faa9669108157a1cd96f40980808080808080"; accountProof[2] = - hex"f869a020e659e60b21cc961f64ad47f20523c1d329d4bbda245ef3940a76dc89d0911bb846f8440180a03135b57fa5c3a632d77d047ed9beb7ad2fb10175e0aaca41fbe4de814ed565a5a04c6a820395819646f26663eb133c936140a62bc46c8d408aa765d28abac30b48"; + hex"f869a020e659e60b21cc961f64ad47f20523c1d329d4bbda245ef3940a76dc89d0911bb846f8440180a03135b57fa5c3a632d77d047ed9beb7ad2fb10175e0aaca41fbe4de814ed565a5a03e02a303e0a64f4588d05eaed8454840cdd10fe15cbfae680aa81923c5b8c9f0"; storageProof = new bytes[](3); storageProof[0] = hex"f8d180a0c3fd297a8560d746e4780fae27a416a5e164111f3fbdeafed15debeeb6285ffc808080a033bc0bdee3ae72377d806414ce91a661c7113f3b2f536fccf941284eca737bca80a008da7afef899990836d7474b37dba4be023feff40beef8598837f46dd285be8da03032f295e4a1ec28653b9412977b641edde37ab05622bd1e70296ff9d43981b980a0277ced4f9566e5d05fe2c8ffe2aa09e8d4d679218e0f545f35ae5c57d77257b280a06465f9f60adda0e80794baebb3431f108a436c0958858df32ea220d788a9e1e580808080"; @@ -247,12 +247,12 @@ contract SampleDepositProof is ISampleDepositProof { /// @inheritdoc ISampleDepositProof function getStateRoot() public pure returns (bytes32) { - return bytes32(0xbf793a27cc4b534fdffda4c380f2cac2d70b3e1f663bdaaa826ede98f5b4790a); + return bytes32(0x6274fb32a5e8c592b0c85a10fe45bc935cd54f9d5c4f0d2b0903c79fcb0441a5); } /// @inheritdoc ISampleDepositProof function getBlockHash() public pure returns (bytes32) { - return bytes32(0xfa0e2ded914764f5f2bd4e6be751a6febe78f98b51d07aae71cf5b845276cfa7); + return bytes32(0x29fd06df4f9cc7383bef38eb0dd3091ee37bc59b24cd305dcf8e6dc078d21479); } /// @inheritdoc ISampleDepositProof diff --git a/test/SignalService/SampleProof.t.sol b/test/SignalService/SampleProof.t.sol index ea48ebb4..27b2068e 100644 --- a/test/SignalService/SampleProof.t.sol +++ b/test/SignalService/SampleProof.t.sol @@ -19,16 +19,16 @@ contract SampleProof is ISampleProof { signalProof = ISignalService.SignalProof({ accountProof: new bytes[](3), storageProof: new bytes[](1), - stateRoot: bytes32(0xa5d8725608e3d53dd7af5cad105ee1ff73a476255cb488d8ac7a19380618fae7), - blockHash: bytes32(0x20dd339fcac98e78ba268945313e1eba63bfa9c695b862a3440899e0a29f92ec) + stateRoot: bytes32(0x0c4473332e48e4afdd2d5f8abdbbbdc6fdc4b4acf9418e09cec5b13424a73c42), + blockHash: bytes32(0x9808a73c0e2de3265e88e6d45eb51922ee6096cbdb763295226d83c5db0376d7) }); signalProof.accountProof[0] = - hex"f90131a0b91a8b7a7e9d3eab90afd81da3725030742f663c6ed8c26657bf00d842a9f4aaa01689b2a5203afd9ea0a0ca3765e4a538c7176e53eac1f8307a344ffc3c6176558080a015b03aaf615a8c4fe56b96ecbb199a755d5de56f770648d8c1f10cd4673d4817a0533540b3b96b2a8549d5f7bfe3ad0100d9d3cf1df14daef68805e868255d626f8080a04b29efa44ecf50c19b34950cf1d0f05e00568bcc873120fbea9a4e8439de0962a0d0a1bfe5b45d2d863a794f016450a4caca04f3b599e8d1652afca8b752935fd880a0bf9b09e442e044778b354abbadb5ec049d7f5e8b585c3966d476c4fbc9a181d28080a04a8e98403c548519920cfc70c3e938c753b79e4c4ec8659fe7cd34fbb1f06b3da0e5c557a0ce3894afeb44c37f3d24247f67dc76a174d8cacc360c1210eef60a7680"; + hex"f90131a0b91a8b7a7e9d3eab90afd81da3725030742f663c6ed8c26657bf00d842a9f4aaa01689b2a5203afd9ea0a0ca3765e4a538c7176e53eac1f8307a344ffc3c6176558080a097bff13eeebad2cda5e5d75582a6cdb37619ddc67c76d2b7eb06dea2bf8e62c5a006fbfb86513f6f9e20c355094efb981e17ae76c3472ad5e6c5f4a46d256cd0e98080a04b29efa44ecf50c19b34950cf1d0f05e00568bcc873120fbea9a4e8439de0962a0d0a1bfe5b45d2d863a794f016450a4caca04f3b599e8d1652afca8b752935fd880a0bf9b09e442e044778b354abbadb5ec049d7f5e8b585c3966d476c4fbc9a181d28080a0d566c7264cb6d498c47d8fa6127e05996afedf32737a7718081ac0c583e6b559a0e5c557a0ce3894afeb44c37f3d24247f67dc76a174d8cacc360c1210eef60a7680"; signalProof.accountProof[1] = - hex"f85180808080a0cee4f950aa1b1110d20dc6dabcb25bb1ec9924d1f8388d551b63215381bd7f1180808080a074ae0767a40fc6fff780050f46a50f6b39ca4edb7faa9669108157a1cd96f40980808080808080"; + hex"f85180808080a025b14e1ba874b7c7b7cda60e9000747c9529cc103d4400132182dd5854adbd0d80808080a074ae0767a40fc6fff780050f46a50f6b39ca4edb7faa9669108157a1cd96f40980808080808080"; signalProof.accountProof[2] = - hex"f869a020e659e60b21cc961f64ad47f20523c1d329d4bbda245ef3940a76dc89d0911bb846f8440180a0f1c0f9507a3be8b6070650ae4ef4b2ca7c6325a3fac8694bd135680f94ca43cea0da147a8683b303efc72285d333a0683c61d218e18e8dbc84e4cbf5885d4a9229"; + hex"f869a020e659e60b21cc961f64ad47f20523c1d329d4bbda245ef3940a76dc89d0911bb846f8440180a0f1c0f9507a3be8b6070650ae4ef4b2ca7c6325a3fac8694bd135680f94ca43cea03e02a303e0a64f4588d05eaed8454840cdd10fe15cbfae680aa81923c5b8c9f0"; signalProof.storageProof[0] = hex"e3a120c503e3a6183486f40b26b20720d9fb44997221494d172cd9caf4192bc0b6980601"; } From cc68261f520c9df52a711ca933dcb27ee5541e86 Mon Sep 17 00:00:00 2001 From: Leo Date: Fri, 25 Jul 2025 19:32:05 +0400 Subject: [PATCH 09/31] reentrancy test --- .../DepositRecipientScenarios.t.sol | 2 ++ test/MessageRelayer/FundAmountScenarios.t.sol | 2 +- test/MessageRelayer/GenericRecipient.t.sol | 28 +++++++++++++++- test/MessageRelayer/InitialState.t.sol | 7 ++-- .../RelayRecipientScenarios.t.sol | 32 +++++++++++++++++++ 5 files changed, 66 insertions(+), 5 deletions(-) create mode 100644 test/MessageRelayer/RelayRecipientScenarios.t.sol diff --git a/test/MessageRelayer/DepositRecipientScenarios.t.sol b/test/MessageRelayer/DepositRecipientScenarios.t.sol index b1dbc23b..23debf32 100644 --- a/test/MessageRelayer/DepositRecipientScenarios.t.sol +++ b/test/MessageRelayer/DepositRecipientScenarios.t.sol @@ -3,6 +3,8 @@ pragma solidity ^0.8.28; import {GenericRecipient} from "./GenericRecipient.t.sol"; import {InitialState} from "./InitialState.t.sol"; + +import {IETHBridge} from "src/protocol/IETHBridge.sol"; import {IMessageRelayer} from "src/protocol/IMessageRelayer.sol"; // This is a concrete class because if we are not using the MessageRelayer, diff --git a/test/MessageRelayer/FundAmountScenarios.t.sol b/test/MessageRelayer/FundAmountScenarios.t.sol index 14c8d188..17720466 100644 --- a/test/MessageRelayer/FundAmountScenarios.t.sol +++ b/test/MessageRelayer/FundAmountScenarios.t.sol @@ -7,7 +7,7 @@ import {GenericRecipient} from "./GenericRecipient.t.sol"; import {InitialState} from "./InitialState.t.sol"; import {IETHBridge} from "src/protocol/IETHBridge.sol"; -contract VaryingFundAmount is DepositRecipientIsMessageRelayer { +contract VaryingFundAmounts is DepositRecipientIsMessageRelayer { function setUp() public override { super.setUp(); } diff --git a/test/MessageRelayer/GenericRecipient.t.sol b/test/MessageRelayer/GenericRecipient.t.sol index 2472013b..65a3227d 100644 --- a/test/MessageRelayer/GenericRecipient.t.sol +++ b/test/MessageRelayer/GenericRecipient.t.sol @@ -1,17 +1,36 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.28; -contract GenericRecipient { +import {IMessageRelayer} from "src/protocol/IMessageRelayer.sol"; + +interface IGenericRecipient { + function setSuccess(bool _callWillSucceed) external; + function setReentrancyAttack(bool _shouldAttack) external; +} + +contract GenericRecipient is IGenericRecipient { bool private callWillSucceed = true; + bool private shouldReenterAttack = false; + address private relayer; + uint256 private reentrancyCounter = 0; error CallFailed(); event FunctionCalled(); + event ReentrancyAttempt(uint256 counter); + + constructor(address _relayer) { + relayer = _relayer; + } function setSuccess(bool _callWillSucceed) external { callWillSucceed = _callWillSucceed; } + function setReentrancyAttack(bool _shouldAttack) external { + shouldReenterAttack = _shouldAttack; + } + fallback() external payable { _simulateFunctionCall(); } @@ -23,5 +42,12 @@ contract GenericRecipient { function _simulateFunctionCall() internal { require(callWillSucceed, CallFailed()); emit FunctionCalled(); + + if (shouldReenterAttack) { + reentrancyCounter++; + emit ReentrancyAttempt(reentrancyCounter); + + IMessageRelayer(relayer).receiveMessage(address(this), 0, address(this), 0, "0x"); + } } } diff --git a/test/MessageRelayer/InitialState.t.sol b/test/MessageRelayer/InitialState.t.sol index 43554970..7a4cf6a2 100644 --- a/test/MessageRelayer/InitialState.t.sol +++ b/test/MessageRelayer/InitialState.t.sol @@ -30,14 +30,15 @@ abstract contract InitialState is Test { MockSignalService signalService = new MockSignalService(); address trustedCommitmentPublisher = _randomAddress("trustedCommitmentPublisher"); address counterpart = _randomAddress("counterpart"); - to = new GenericRecipient(); - relayerSelectedTipRecipient = new GenericRecipient(); - userSelectedTipRecipient = new GenericRecipient(); ETHBridge bridge = new ETHBridge(address(signalService), trustedCommitmentPublisher, counterpart); vm.deal(address(bridge), amount); messageRelayer = new MessageRelayer(address(bridge)); + to = new GenericRecipient(address(messageRelayer)); + relayerSelectedTipRecipient = new GenericRecipient(address(messageRelayer)); + userSelectedTipRecipient = new GenericRecipient(address(messageRelayer)); + ethDeposit = IETHBridge.ETHDeposit({ nonce: 0, from: _randomAddress("from"), diff --git a/test/MessageRelayer/RelayRecipientScenarios.t.sol b/test/MessageRelayer/RelayRecipientScenarios.t.sol new file mode 100644 index 00000000..86baac80 --- /dev/null +++ b/test/MessageRelayer/RelayRecipientScenarios.t.sol @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.28; + +import {DepositRecipientIsMessageRelayer} from "./DepositRecipientScenarios.t.sol"; +import {GenericRecipient} from "./GenericRecipient.t.sol"; + +import {InitialState} from "./InitialState.t.sol"; +import {IETHBridge} from "src/protocol/IETHBridge.sol"; + +contract RelayRecipientRejectsDeposit is DepositRecipientIsMessageRelayer { + function setUp() public override { + super.setUp(); + to.setSuccess(false); + } + + function test_RelayRecipientRejectsDeposit_relayMessage_shouldRevert() public { + vm.expectRevert(IETHBridge.FailedClaim.selector); + _relayMessage(); + } +} + +contract RelayRecipientIsReentrant is DepositRecipientIsMessageRelayer { + function setUp() public override { + super.setUp(); + to.setReentrancyAttack(true); + } + + function test_RelayRecipientReentersReceiveMessage_relayMessage_shouldRevert() public { + vm.expectRevert(IETHBridge.FailedClaim.selector); + _relayMessage(); + } +} From f0ee2dbf09169df29a591bc36ae46ca3e11b8ff4 Mon Sep 17 00:00:00 2001 From: Leo Date: Mon, 28 Jul 2025 14:56:48 +0400 Subject: [PATCH 10/31] remove uneeded counter --- test/MessageRelayer/GenericRecipient.t.sol | 5 ----- 1 file changed, 5 deletions(-) diff --git a/test/MessageRelayer/GenericRecipient.t.sol b/test/MessageRelayer/GenericRecipient.t.sol index 65a3227d..f7fb7267 100644 --- a/test/MessageRelayer/GenericRecipient.t.sol +++ b/test/MessageRelayer/GenericRecipient.t.sol @@ -12,12 +12,10 @@ contract GenericRecipient is IGenericRecipient { bool private callWillSucceed = true; bool private shouldReenterAttack = false; address private relayer; - uint256 private reentrancyCounter = 0; error CallFailed(); event FunctionCalled(); - event ReentrancyAttempt(uint256 counter); constructor(address _relayer) { relayer = _relayer; @@ -44,9 +42,6 @@ contract GenericRecipient is IGenericRecipient { emit FunctionCalled(); if (shouldReenterAttack) { - reentrancyCounter++; - emit ReentrancyAttempt(reentrancyCounter); - IMessageRelayer(relayer).receiveMessage(address(this), 0, address(this), 0, "0x"); } } From ffd4e336d7d5afc61f502c0b2a65aaab982ff15c Mon Sep 17 00:00:00 2001 From: Leo Date: Tue, 29 Jul 2025 15:48:50 +0400 Subject: [PATCH 11/31] fix scenarios remove setup --- .../MessageRelayer/RelayRecipientScenarios.t.sol | 16 +++------------- 1 file changed, 3 insertions(+), 13 deletions(-) diff --git a/test/MessageRelayer/RelayRecipientScenarios.t.sol b/test/MessageRelayer/RelayRecipientScenarios.t.sol index 86baac80..79789bcb 100644 --- a/test/MessageRelayer/RelayRecipientScenarios.t.sol +++ b/test/MessageRelayer/RelayRecipientScenarios.t.sol @@ -7,25 +7,15 @@ import {GenericRecipient} from "./GenericRecipient.t.sol"; import {InitialState} from "./InitialState.t.sol"; import {IETHBridge} from "src/protocol/IETHBridge.sol"; -contract RelayRecipientRejectsDeposit is DepositRecipientIsMessageRelayer { - function setUp() public override { - super.setUp(); - to.setSuccess(false); - } - +contract RelayRecipientScenarios is DepositRecipientIsMessageRelayer { function test_RelayRecipientRejectsDeposit_relayMessage_shouldRevert() public { + to.setSuccess(false); vm.expectRevert(IETHBridge.FailedClaim.selector); _relayMessage(); } -} - -contract RelayRecipientIsReentrant is DepositRecipientIsMessageRelayer { - function setUp() public override { - super.setUp(); - to.setReentrancyAttack(true); - } function test_RelayRecipientReentersReceiveMessage_relayMessage_shouldRevert() public { + to.setReentrancyAttack(true); vm.expectRevert(IETHBridge.FailedClaim.selector); _relayMessage(); } From ac4471cb5d18fc3dabab2ffec85a75d1bba74701 Mon Sep 17 00:00:00 2001 From: Leo Date: Tue, 29 Jul 2025 16:10:15 +0400 Subject: [PATCH 12/31] add more test scenarios --- test/MessageRelayer/InitialState.t.sol | 3 ++- test/MessageRelayer/TipRecipientScenarios.t.sol | 16 ++++++++++++++++ 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/test/MessageRelayer/InitialState.t.sol b/test/MessageRelayer/InitialState.t.sol index 7a4cf6a2..bbca9a34 100644 --- a/test/MessageRelayer/InitialState.t.sol +++ b/test/MessageRelayer/InitialState.t.sol @@ -13,6 +13,7 @@ import {MockSignalService} from "test/mocks/MockSignalService.sol"; abstract contract InitialState is Test { MessageRelayer messageRelayer; + ETHBridge bridge; // Default message parameters IETHBridge.ETHDeposit ethDeposit; @@ -30,7 +31,7 @@ abstract contract InitialState is Test { MockSignalService signalService = new MockSignalService(); address trustedCommitmentPublisher = _randomAddress("trustedCommitmentPublisher"); address counterpart = _randomAddress("counterpart"); - ETHBridge bridge = new ETHBridge(address(signalService), trustedCommitmentPublisher, counterpart); + bridge = new ETHBridge(address(signalService), trustedCommitmentPublisher, counterpart); vm.deal(address(bridge), amount); messageRelayer = new MessageRelayer(address(bridge)); diff --git a/test/MessageRelayer/TipRecipientScenarios.t.sol b/test/MessageRelayer/TipRecipientScenarios.t.sol index 3785e119..88c9d0d8 100644 --- a/test/MessageRelayer/TipRecipientScenarios.t.sol +++ b/test/MessageRelayer/TipRecipientScenarios.t.sol @@ -19,6 +19,12 @@ contract UserSetValidTipRecipient is DepositRecipientIsMessageRelayer { _relayMessage(); assertEq(address(relayerSelectedTipRecipient).balance, balanceBefore, "incorrect tip recipient paid"); } + + function test_UserSetValidTipRecipient_claimDepositDirectly_shouldTipUserSelectedRecipient() public { + uint256 balanceBefore = address(userSelectedTipRecipient).balance; + bridge.claimDeposit(ethDeposit, height, proof); + assertEq(address(userSelectedTipRecipient).balance, balanceBefore + tip, "tip recipient balance mismatch"); + } } contract UserSetZeroTipRecipient is DepositRecipientIsMessageRelayer { @@ -33,6 +39,16 @@ contract UserSetZeroTipRecipient is DepositRecipientIsMessageRelayer { _relayMessage(); assertEq(address(relayerSelectedTipRecipient).balance, balanceBefore + tip, "tip recipient balance mismatch"); } + + function test_UserSetZeroTipRecipient_claimDepositDirectly_shouldNotTipRelayTipRecipient() public { + uint256 relayerTipRecipientBalanceBefore = address(relayerSelectedTipRecipient).balance; + bridge.claimDeposit(ethDeposit, height, proof); + assertEq( + address(relayerSelectedTipRecipient).balance, + relayerTipRecipientBalanceBefore, + "tip recipient balance mismatch" + ); + } } // We bypass the `DepositRecipientIsMessageRelayer` because it seems vm.expectCall requires the call to succeed From a3de21403b39576229f61734160cc09f3e1840e9 Mon Sep 17 00:00:00 2001 From: Leo Date: Tue, 29 Jul 2025 16:32:09 +0400 Subject: [PATCH 13/31] better testing scenario --- test/MessageRelayer/FundAmountScenarios.t.sol | 16 +++++++++++++++- test/MessageRelayer/GasLimitScenarios.t.sol | 3 +++ 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/test/MessageRelayer/FundAmountScenarios.t.sol b/test/MessageRelayer/FundAmountScenarios.t.sol index 17720466..5e0d13dd 100644 --- a/test/MessageRelayer/FundAmountScenarios.t.sol +++ b/test/MessageRelayer/FundAmountScenarios.t.sol @@ -3,6 +3,7 @@ pragma solidity ^0.8.28; import {DepositRecipientIsMessageRelayer} from "./DepositRecipientScenarios.t.sol"; import {GenericRecipient} from "./GenericRecipient.t.sol"; +import {IMessageRelayer} from "src/protocol/IMessageRelayer.sol"; import {InitialState} from "./InitialState.t.sol"; import {IETHBridge} from "src/protocol/IETHBridge.sol"; @@ -12,16 +13,29 @@ contract VaryingFundAmounts is DepositRecipientIsMessageRelayer { super.setUp(); } - function test_sentZeroAmount_relayMessage_shouldRevert() public zeroAmount { + function test_setZeroAmountWithTip_relayMessage_shouldRevert() public zeroAmount { vm.expectRevert(IETHBridge.FailedClaim.selector); _relayMessage(); } + function test_setZeroAmountWithoutTip_relayMessage_shouldSucceed() public zeroTipZeroAmount { + vm.expectEmit(); + emit IMessageRelayer.MessageForwarded(address(to), 0, data, address(userSelectedTipRecipient), 0); + _relayMessage(); + } + function test_setTipHigherThanAmount_relayMessage_shouldRevert() public amountTooLow { vm.expectRevert(IETHBridge.FailedClaim.selector); _relayMessage(); } + modifier zeroTipZeroAmount() { + tip = 0; + ethDeposit.amount = 0; + _encodeReceiveCall(); + _; + } + modifier zeroAmount() { ethDeposit.amount = 0; _encodeReceiveCall(); diff --git a/test/MessageRelayer/GasLimitScenarios.t.sol b/test/MessageRelayer/GasLimitScenarios.t.sol index 06d51b15..a8683542 100644 --- a/test/MessageRelayer/GasLimitScenarios.t.sol +++ b/test/MessageRelayer/GasLimitScenarios.t.sol @@ -6,6 +6,7 @@ import {GenericRecipient} from "./GenericRecipient.t.sol"; import {InitialState} from "./InitialState.t.sol"; import {IETHBridge} from "src/protocol/IETHBridge.sol"; +import {IMessageRelayer} from "src/protocol/IMessageRelayer.sol"; contract UserSetGasLimit is DepositRecipientIsMessageRelayer { function setUp() public override { @@ -18,6 +19,8 @@ contract UserSetGasLimit is DepositRecipientIsMessageRelayer { } function test_userReasonableGasLimit_relayMessage() public whenGasLimitIsReasonable { + vm.expectEmit(); + emit IMessageRelayer.MessageForwarded(address(to), amount - tip, data, address(userSelectedTipRecipient), tip); _relayMessage(); } From bd61c844017bfc2fbb6c3dc031dc84fa8bd9176d Mon Sep 17 00:00:00 2001 From: Leo Date: Tue, 29 Jul 2025 16:43:34 +0400 Subject: [PATCH 14/31] transient storage test --- test/MessageRelayer/InitialState.t.sol | 15 +++++++++++++++ test/MessageRelayer/TipRecipientScenarios.t.sol | 1 + 2 files changed, 16 insertions(+) diff --git a/test/MessageRelayer/InitialState.t.sol b/test/MessageRelayer/InitialState.t.sol index bbca9a34..f02f6652 100644 --- a/test/MessageRelayer/InitialState.t.sol +++ b/test/MessageRelayer/InitialState.t.sol @@ -27,6 +27,9 @@ abstract contract InitialState is Test { uint256 gasLimit = 0; bytes data = "0x"; + // keccak256("TIP_RECIPIENT_SLOT") + bytes32 constant TIP_RECIPIENT_SLOT = 0x833ce1785f54a5ca49991a09a7b058587309bf3687e5f20b7b66fa12132ef6f0; + function setUp() public virtual { MockSignalService signalService = new MockSignalService(); address trustedCommitmentPublisher = _randomAddress("trustedCommitmentPublisher"); @@ -70,3 +73,15 @@ abstract contract InitialState is Test { return keccak256("MessageRelayer"); } } + +contract InitialStateTest is InitialState { + function test_tipRecipientTransientStorage_isZero() public { + bytes32 value = vm.load(address(messageRelayer), TIP_RECIPIENT_SLOT); + + address storedAddress = address(uint160(uint256(value))); + + assertEq(storedAddress, address(0), "Initial transient slot should be empty"); + + assertEq(value, bytes32(0), "Initial transient slot bytes32 should be zero"); + } +} diff --git a/test/MessageRelayer/TipRecipientScenarios.t.sol b/test/MessageRelayer/TipRecipientScenarios.t.sol index 88c9d0d8..c3754faa 100644 --- a/test/MessageRelayer/TipRecipientScenarios.t.sol +++ b/test/MessageRelayer/TipRecipientScenarios.t.sol @@ -17,6 +17,7 @@ contract UserSetValidTipRecipient is DepositRecipientIsMessageRelayer { function test_UserSetValidTipRecipient_relayMessage_shouldNotTipRelayerSelectedRecipient() public { uint256 balanceBefore = address(relayerSelectedTipRecipient).balance; _relayMessage(); + assertEq(address(relayerSelectedTipRecipient).balance, balanceBefore, "incorrect tip recipient paid"); } From f781997dd0f198baefc4f3ae7354e762ba2b942c Mon Sep 17 00:00:00 2001 From: Leo Date: Fri, 1 Aug 2025 18:35:04 +0400 Subject: [PATCH 15/31] update scenario --- test/MessageRelayer/InitialState.t.sol | 14 +++-------- .../TipRecipientScenarios.t.sol | 23 ++++++++++--------- 2 files changed, 15 insertions(+), 22 deletions(-) diff --git a/test/MessageRelayer/InitialState.t.sol b/test/MessageRelayer/InitialState.t.sol index f02f6652..d4053c52 100644 --- a/test/MessageRelayer/InitialState.t.sol +++ b/test/MessageRelayer/InitialState.t.sol @@ -32,8 +32,8 @@ abstract contract InitialState is Test { function setUp() public virtual { MockSignalService signalService = new MockSignalService(); - address trustedCommitmentPublisher = _randomAddress("trustedCommitmentPublisher"); - address counterpart = _randomAddress("counterpart"); + address trustedCommitmentPublisher = makeAddr("trustedCommitmentPublisher"); + address counterpart = makeAddr("counterpart"); bridge = new ETHBridge(address(signalService), trustedCommitmentPublisher, counterpart); vm.deal(address(bridge), amount); @@ -45,7 +45,7 @@ abstract contract InitialState is Test { ethDeposit = IETHBridge.ETHDeposit({ nonce: 0, - from: _randomAddress("from"), + from: makeAddr("from"), to: address(messageRelayer), amount: 2 ether, data: "", @@ -64,14 +64,6 @@ abstract contract InitialState is Test { function _relayMessage() internal { messageRelayer.relayMessage(ethDeposit, height, proof, address(relayerSelectedTipRecipient)); } - - function _randomAddress(string memory name) internal pure returns (address) { - return address(uint160(uint256(keccak256(abi.encode(_domainSeparator(), name))))); - } - - function _domainSeparator() internal pure returns (bytes32) { - return keccak256("MessageRelayer"); - } } contract InitialStateTest is InitialState { diff --git a/test/MessageRelayer/TipRecipientScenarios.t.sol b/test/MessageRelayer/TipRecipientScenarios.t.sol index c3754faa..1d536912 100644 --- a/test/MessageRelayer/TipRecipientScenarios.t.sol +++ b/test/MessageRelayer/TipRecipientScenarios.t.sol @@ -7,7 +7,9 @@ import {GenericRecipient} from "./GenericRecipient.t.sol"; import {InitialState} from "./InitialState.t.sol"; import {IETHBridge} from "src/protocol/IETHBridge.sol"; -contract UserSetValidTipRecipient is DepositRecipientIsMessageRelayer { +import {console} from "forge-std/console.sol"; + +contract UserSetTipRecipientScenarios is DepositRecipientIsMessageRelayer { function test_UserSetValidTipRecipient_relayMessage_shouldTipUserSelectedRecipient() public { uint256 balanceBefore = address(userSelectedTipRecipient).balance; _relayMessage(); @@ -26,23 +28,16 @@ contract UserSetValidTipRecipient is DepositRecipientIsMessageRelayer { bridge.claimDeposit(ethDeposit, height, proof); assertEq(address(userSelectedTipRecipient).balance, balanceBefore + tip, "tip recipient balance mismatch"); } -} -contract UserSetZeroTipRecipient is DepositRecipientIsMessageRelayer { - function setUp() public override { - super.setUp(); - userSelectedTipRecipient = GenericRecipient(payable(0)); - _encodeReceiveCall(); - } - - function test_UserSetZeroTipRecipient_relayMessage_shouldTipRelayerSelectedRecipient() public { + function test_UserSetZeroTipRecipient_relayMessage_shouldTipRelayerSelectedRecipient() public zeroTipRecipient { uint256 balanceBefore = address(relayerSelectedTipRecipient).balance; _relayMessage(); assertEq(address(relayerSelectedTipRecipient).balance, balanceBefore + tip, "tip recipient balance mismatch"); } - function test_UserSetZeroTipRecipient_claimDepositDirectly_shouldNotTipRelayTipRecipient() public { + function test_UserSetZeroTipRecipient_claimDepositDirectly_shouldRevert() public zeroTipRecipient { uint256 relayerTipRecipientBalanceBefore = address(relayerSelectedTipRecipient).balance; + vm.expectRevert(IETHBridge.FailedClaim.selector); bridge.claimDeposit(ethDeposit, height, proof); assertEq( address(relayerSelectedTipRecipient).balance, @@ -50,6 +45,12 @@ contract UserSetZeroTipRecipient is DepositRecipientIsMessageRelayer { "tip recipient balance mismatch" ); } + + modifier zeroTipRecipient() { + userSelectedTipRecipient = GenericRecipient(payable(0)); + _encodeReceiveCall(); + _; + } } // We bypass the `DepositRecipientIsMessageRelayer` because it seems vm.expectCall requires the call to succeed From 5347332ba7c1f05290276e31a2468dfb53b54bf9 Mon Sep 17 00:00:00 2001 From: Leo Date: Fri, 1 Aug 2025 18:36:23 +0400 Subject: [PATCH 16/31] name --- test/MessageRelayer/FundAmountScenarios.t.sol | 6 +++--- test/MessageRelayer/GasLimitScenarios.t.sol | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/test/MessageRelayer/FundAmountScenarios.t.sol b/test/MessageRelayer/FundAmountScenarios.t.sol index 5e0d13dd..86d5059a 100644 --- a/test/MessageRelayer/FundAmountScenarios.t.sol +++ b/test/MessageRelayer/FundAmountScenarios.t.sol @@ -13,18 +13,18 @@ contract VaryingFundAmounts is DepositRecipientIsMessageRelayer { super.setUp(); } - function test_setZeroAmountWithTip_relayMessage_shouldRevert() public zeroAmount { + function test_SetZeroAmountWithTip_relayMessage_shouldRevert() public zeroAmount { vm.expectRevert(IETHBridge.FailedClaim.selector); _relayMessage(); } - function test_setZeroAmountWithoutTip_relayMessage_shouldSucceed() public zeroTipZeroAmount { + function test_SetZeroAmountWithoutTip_relayMessage_shouldSucceed() public zeroTipZeroAmount { vm.expectEmit(); emit IMessageRelayer.MessageForwarded(address(to), 0, data, address(userSelectedTipRecipient), 0); _relayMessage(); } - function test_setTipHigherThanAmount_relayMessage_shouldRevert() public amountTooLow { + function test_SetTipHigherThanAmount_relayMessage_shouldRevert() public amountTooLow { vm.expectRevert(IETHBridge.FailedClaim.selector); _relayMessage(); } diff --git a/test/MessageRelayer/GasLimitScenarios.t.sol b/test/MessageRelayer/GasLimitScenarios.t.sol index a8683542..47b01281 100644 --- a/test/MessageRelayer/GasLimitScenarios.t.sol +++ b/test/MessageRelayer/GasLimitScenarios.t.sol @@ -13,12 +13,12 @@ contract UserSetGasLimit is DepositRecipientIsMessageRelayer { super.setUp(); } - function test_userSetHighGasLimit_relayMessage_shouldRevert() public gasLimitHigherThanValue { + function test_UserSetHighGasLimit_relayMessage_shouldRevert() public gasLimitHigherThanValue { vm.expectRevert(IETHBridge.FailedClaim.selector); _relayMessage(); } - function test_userReasonableGasLimit_relayMessage() public whenGasLimitIsReasonable { + function test_UserReasonableGasLimit_relayMessage() public whenGasLimitIsReasonable { vm.expectEmit(); emit IMessageRelayer.MessageForwarded(address(to), amount - tip, data, address(userSelectedTipRecipient), tip); _relayMessage(); From 4608dc8202fb969f0d5fb7fa3e47fb033afe8b23 Mon Sep 17 00:00:00 2001 From: Nikesh Nazareth Date: Tue, 5 Aug 2025 15:22:38 +1000 Subject: [PATCH 17/31] Create ifTxSucceeds modifier --- test/MessageRelayer/InitialState.t.sol | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/test/MessageRelayer/InitialState.t.sol b/test/MessageRelayer/InitialState.t.sol index d4053c52..83740fd8 100644 --- a/test/MessageRelayer/InitialState.t.sol +++ b/test/MessageRelayer/InitialState.t.sol @@ -11,6 +11,14 @@ import {IMessageRelayer} from "src/protocol/IMessageRelayer.sol"; import {MessageRelayer} from "src/protocol/taiko_alethia/MessageRelayer.sol"; import {MockSignalService} from "test/mocks/MockSignalService.sol"; +// An explanation of the test structure: +// - we want to enumerate over several different configurations (eg. valid/invalid/zero tip recipients, +// sufficient/insufficient funds, eventual call succeeds/fails) +// - however, many of the tests are only relevant if the overall transaction succeeds, and this depends on settings +// defined in other files +// - ideally, we would only run the tests in the relevant scenario, but this would require less encapsulated logic +// - instead, the ifTxSucceeds modifier is used to turn irrelevant tests into no-ops + abstract contract InitialState is Test { MessageRelayer messageRelayer; ETHBridge bridge; @@ -27,6 +35,8 @@ abstract contract InitialState is Test { uint256 gasLimit = 0; bytes data = "0x"; + bool txShouldSucceed = true; + // keccak256("TIP_RECIPIENT_SLOT") bytes32 constant TIP_RECIPIENT_SLOT = 0x833ce1785f54a5ca49991a09a7b058587309bf3687e5f20b7b66fa12132ef6f0; @@ -64,6 +74,13 @@ abstract contract InitialState is Test { function _relayMessage() internal { messageRelayer.relayMessage(ethDeposit, height, proof, address(relayerSelectedTipRecipient)); } + + + modifier ifTxSucceeds() { + if (txShouldSucceed) { + _; + } + } } contract InitialStateTest is InitialState { From 28fb7bd66c6fa2bd2bfad42e745691d4490c302b Mon Sep 17 00:00:00 2001 From: Nikesh Nazareth Date: Tue, 5 Aug 2025 15:23:00 +1000 Subject: [PATCH 18/31] Remove InitialStateTest It was checking permanent storage instead of temporary storage --- test/MessageRelayer/InitialState.t.sol | 14 +------------- 1 file changed, 1 insertion(+), 13 deletions(-) diff --git a/test/MessageRelayer/InitialState.t.sol b/test/MessageRelayer/InitialState.t.sol index 83740fd8..070d4839 100644 --- a/test/MessageRelayer/InitialState.t.sol +++ b/test/MessageRelayer/InitialState.t.sol @@ -81,16 +81,4 @@ abstract contract InitialState is Test { _; } } -} - -contract InitialStateTest is InitialState { - function test_tipRecipientTransientStorage_isZero() public { - bytes32 value = vm.load(address(messageRelayer), TIP_RECIPIENT_SLOT); - - address storedAddress = address(uint160(uint256(value))); - - assertEq(storedAddress, address(0), "Initial transient slot should be empty"); - - assertEq(value, bytes32(0), "Initial transient slot bytes32 should be zero"); - } -} +} \ No newline at end of file From d093a793a533bbe3f283e02971943d54e265ac5f Mon Sep 17 00:00:00 2001 From: Nikesh Nazareth Date: Tue, 5 Aug 2025 15:40:02 +1000 Subject: [PATCH 19/31] Use ifTxSucceeds so UserSetInvalidTipRecipient can inherit DepositRecipientIsMessageRelayer --- test/MessageRelayer/DepositRecipientScenarios.t.sol | 4 ++-- test/MessageRelayer/InitialState.t.sol | 3 +-- test/MessageRelayer/TipRecipientScenarios.t.sol | 4 ++-- 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/test/MessageRelayer/DepositRecipientScenarios.t.sol b/test/MessageRelayer/DepositRecipientScenarios.t.sol index 23debf32..3f6482fa 100644 --- a/test/MessageRelayer/DepositRecipientScenarios.t.sol +++ b/test/MessageRelayer/DepositRecipientScenarios.t.sol @@ -31,12 +31,12 @@ contract DepositRecipientIsNotMessageRelayer is InitialState { } abstract contract DepositRecipientIsMessageRelayer is InitialState { - function test_DepositRecipientIsMessageRelayer_relayMessage_shouldInvokeReceiveMessage() public { + function test_DepositRecipientIsMessageRelayer_relayMessage_shouldInvokeReceiveMessage() public ifTxSucceeds { vm.expectCall(address(messageRelayer), ethDeposit.data); _relayMessage(); } - function test_DepositRecipientIsMessageRelayer_claimDeposit_shouldInvokeReceiveMessage() public { + function test_DepositRecipientIsMessageRelayer_claimDeposit_shouldInvokeReceiveMessage() public ifTxSucceeds { vm.expectCall(address(messageRelayer), ethDeposit.data); messageRelayer.ethBridge().claimDeposit(ethDeposit, height, proof); } diff --git a/test/MessageRelayer/InitialState.t.sol b/test/MessageRelayer/InitialState.t.sol index 070d4839..c78c836a 100644 --- a/test/MessageRelayer/InitialState.t.sol +++ b/test/MessageRelayer/InitialState.t.sol @@ -75,10 +75,9 @@ abstract contract InitialState is Test { messageRelayer.relayMessage(ethDeposit, height, proof, address(relayerSelectedTipRecipient)); } - modifier ifTxSucceeds() { if (txShouldSucceed) { _; } } -} \ No newline at end of file +} diff --git a/test/MessageRelayer/TipRecipientScenarios.t.sol b/test/MessageRelayer/TipRecipientScenarios.t.sol index 1d536912..8c7a7a50 100644 --- a/test/MessageRelayer/TipRecipientScenarios.t.sol +++ b/test/MessageRelayer/TipRecipientScenarios.t.sol @@ -53,11 +53,11 @@ contract UserSetTipRecipientScenarios is DepositRecipientIsMessageRelayer { } } -// We bypass the `DepositRecipientIsMessageRelayer` because it seems vm.expectCall requires the call to succeed -contract UserSetInvalidTipRecipient is InitialState { +contract UserSetInvalidTipRecipient is DepositRecipientIsMessageRelayer { function setUp() public override { super.setUp(); userSelectedTipRecipient.setSuccess(false); + txShouldSucceed = false; } function test_UserSetInvalidTipRecipient_relayMessage_shouldRevert() public { From 08005510e2ad9d6daeeaec55d842f8d523bedbec Mon Sep 17 00:00:00 2001 From: Nikesh Nazareth Date: Tue, 5 Aug 2025 23:24:02 +1000 Subject: [PATCH 20/31] Expand TipRecipientScenarios using the ifRelaySucceeds mechanism --- .../DepositRecipientScenarios.t.sol | 6 +- test/MessageRelayer/InitialState.t.sol | 20 ++- .../TipRecipientScenarios.t.sol | 123 +++++++++++++----- 3 files changed, 112 insertions(+), 37 deletions(-) diff --git a/test/MessageRelayer/DepositRecipientScenarios.t.sol b/test/MessageRelayer/DepositRecipientScenarios.t.sol index 3f6482fa..f87255d5 100644 --- a/test/MessageRelayer/DepositRecipientScenarios.t.sol +++ b/test/MessageRelayer/DepositRecipientScenarios.t.sol @@ -31,13 +31,13 @@ contract DepositRecipientIsNotMessageRelayer is InitialState { } abstract contract DepositRecipientIsMessageRelayer is InitialState { - function test_DepositRecipientIsMessageRelayer_relayMessage_shouldInvokeReceiveMessage() public ifTxSucceeds { + function test_DepositRecipientIsMessageRelayer_relayMessage_shouldInvokeReceiveMessage() public ifRelaySucceeds { vm.expectCall(address(messageRelayer), ethDeposit.data); _relayMessage(); } - function test_DepositRecipientIsMessageRelayer_claimDeposit_shouldInvokeReceiveMessage() public ifTxSucceeds { + function test_DepositRecipientIsMessageRelayer_claimDeposit_shouldInvokeReceiveMessage() public ifClaimSucceeds { vm.expectCall(address(messageRelayer), ethDeposit.data); - messageRelayer.ethBridge().claimDeposit(ethDeposit, height, proof); + _claimDeposit(); } } diff --git a/test/MessageRelayer/InitialState.t.sol b/test/MessageRelayer/InitialState.t.sol index c78c836a..4ee8652f 100644 --- a/test/MessageRelayer/InitialState.t.sol +++ b/test/MessageRelayer/InitialState.t.sol @@ -17,7 +17,7 @@ import {MockSignalService} from "test/mocks/MockSignalService.sol"; // - however, many of the tests are only relevant if the overall transaction succeeds, and this depends on settings // defined in other files // - ideally, we would only run the tests in the relevant scenario, but this would require less encapsulated logic -// - instead, the ifTxSucceeds modifier is used to turn irrelevant tests into no-ops +// - instead, the ifRelaySucceeds and ifClaimSucceeds modifiers are used to turn irrelevant tests into no-ops abstract contract InitialState is Test { MessageRelayer messageRelayer; @@ -35,7 +35,9 @@ abstract contract InitialState is Test { uint256 gasLimit = 0; bytes data = "0x"; - bool txShouldSucceed = true; + // `claimDeposit` may fail when `relayMessage` succeeds, so these are separate flags + bool relayShouldSucceed = true; + bool claimShouldSucceed = true; // keccak256("TIP_RECIPIENT_SLOT") bytes32 constant TIP_RECIPIENT_SLOT = 0x833ce1785f54a5ca49991a09a7b058587309bf3687e5f20b7b66fa12132ef6f0; @@ -75,8 +77,18 @@ abstract contract InitialState is Test { messageRelayer.relayMessage(ethDeposit, height, proof, address(relayerSelectedTipRecipient)); } - modifier ifTxSucceeds() { - if (txShouldSucceed) { + function _claimDeposit() internal { + bridge.claimDeposit(ethDeposit, height, proof); + } + + modifier ifRelaySucceeds() { + if (relayShouldSucceed) { + _; + } + } + + modifier ifClaimSucceeds() { + if (claimShouldSucceed) { _; } } diff --git a/test/MessageRelayer/TipRecipientScenarios.t.sol b/test/MessageRelayer/TipRecipientScenarios.t.sol index 8c7a7a50..f58621ce 100644 --- a/test/MessageRelayer/TipRecipientScenarios.t.sol +++ b/test/MessageRelayer/TipRecipientScenarios.t.sol @@ -9,59 +9,122 @@ import {IETHBridge} from "src/protocol/IETHBridge.sol"; import {console} from "forge-std/console.sol"; -contract UserSetTipRecipientScenarios is DepositRecipientIsMessageRelayer { - function test_UserSetValidTipRecipient_relayMessage_shouldTipUserSelectedRecipient() public { - uint256 balanceBefore = address(userSelectedTipRecipient).balance; +abstract contract TipRecipientScenarios is DepositRecipientIsMessageRelayer { + function test_TipRecipientScenarios_relayMessage_shouldTipCorrectRecipient() public ifRelaySucceeds { + (GenericRecipient correctRecipient,) = _recipients(); + uint256 balanceBefore = address(correctRecipient).balance; _relayMessage(); - assertEq(address(userSelectedTipRecipient).balance, balanceBefore + tip, "tip recipient balance mismatch"); + assertEq(address(correctRecipient).balance, balanceBefore + tip, "tip recipient balance mismatch"); } - function test_UserSetValidTipRecipient_relayMessage_shouldNotTipRelayerSelectedRecipient() public { - uint256 balanceBefore = address(relayerSelectedTipRecipient).balance; + function test_TipRecipientScenarios_relayMessage_shouldNotTipIncorrectRecipient() public ifRelaySucceeds { + (, GenericRecipient incorrectRecipient) = _recipients(); + uint256 balanceBefore = address(incorrectRecipient).balance; _relayMessage(); + assertEq(address(incorrectRecipient).balance, balanceBefore, "incorrect tip recipient paid"); + } - assertEq(address(relayerSelectedTipRecipient).balance, balanceBefore, "incorrect tip recipient paid"); + /// @param correctRecipient The user-selected recipient if it is set. The relayer-selected recipient otherwise. + /// @param incorrectRecipient The other recipient, which should not receive the tip. + /// @dev the only tested case where they are the same is when they are both zero (and the transaction reverts) + function _recipients() + internal + view + returns (GenericRecipient correctRecipient, GenericRecipient incorrectRecipient) + { + return userSelectedTipRecipient == GenericRecipient(payable(0)) + ? (relayerSelectedTipRecipient, userSelectedTipRecipient) + : (userSelectedTipRecipient, relayerSelectedTipRecipient); } +} + +// User-selected tip recipient scenarios - function test_UserSetValidTipRecipient_claimDepositDirectly_shouldTipUserSelectedRecipient() public { +abstract contract UserSetValidTipRecipient is TipRecipientScenarios { + function test_UserSetValidTipRecipient_claimDeposit_shouldTipUserSelectedRecipient() public ifClaimSucceeds { uint256 balanceBefore = address(userSelectedTipRecipient).balance; - bridge.claimDeposit(ethDeposit, height, proof); + _claimDeposit(); assertEq(address(userSelectedTipRecipient).balance, balanceBefore + tip, "tip recipient balance mismatch"); } +} - function test_UserSetZeroTipRecipient_relayMessage_shouldTipRelayerSelectedRecipient() public zeroTipRecipient { - uint256 balanceBefore = address(relayerSelectedTipRecipient).balance; - _relayMessage(); - assertEq(address(relayerSelectedTipRecipient).balance, balanceBefore + tip, "tip recipient balance mismatch"); +abstract contract UserSetZeroTipRecipient is TipRecipientScenarios { + function setUp() public virtual override { + super.setUp(); + userSelectedTipRecipient = GenericRecipient(payable(0)); + _encodeReceiveCall(); + claimShouldSucceed = false; } - function test_UserSetZeroTipRecipient_claimDepositDirectly_shouldRevert() public zeroTipRecipient { - uint256 relayerTipRecipientBalanceBefore = address(relayerSelectedTipRecipient).balance; + function test_UserSetZeroTipRecipient_claimDeposit_shouldRevert() public { vm.expectRevert(IETHBridge.FailedClaim.selector); - bridge.claimDeposit(ethDeposit, height, proof); - assertEq( - address(relayerSelectedTipRecipient).balance, - relayerTipRecipientBalanceBefore, - "tip recipient balance mismatch" - ); - } - - modifier zeroTipRecipient() { - userSelectedTipRecipient = GenericRecipient(payable(0)); - _encodeReceiveCall(); - _; + _claimDeposit(); } } -contract UserSetInvalidTipRecipient is DepositRecipientIsMessageRelayer { - function setUp() public override { +abstract contract UserSetInvalidTipRecipient is TipRecipientScenarios { + function setUp() public virtual override { super.setUp(); userSelectedTipRecipient.setSuccess(false); - txShouldSucceed = false; + relayShouldSucceed = false; + claimShouldSucceed = false; } function test_UserSetInvalidTipRecipient_relayMessage_shouldRevert() public { vm.expectRevert(IETHBridge.FailedClaim.selector); _relayMessage(); } + + function test_UserSetInvalidTipRecipient_claimDeposit_shouldRevert() public { + vm.expectRevert(IETHBridge.FailedClaim.selector); + _claimDeposit(); + } +} + +// Relayer-selected tip recipient scenarios + +abstract contract RelayerSetValidTipRecipient is TipRecipientScenarios {} + +abstract contract RelayerSetZeroTipRecipient is TipRecipientScenarios { + function setUp() public virtual override { + super.setUp(); + relayerSelectedTipRecipient = GenericRecipient(payable(0)); + } +} + +abstract contract RelayerSetInvalidTipRecipient is TipRecipientScenarios { + function setUp() public virtual override { + super.setUp(); + relayerSelectedTipRecipient.setSuccess(false); + } +} + +// Combined scenarios + +contract ValidUserTipRecipientOverrulesRelayer is UserSetValidTipRecipient, RelayerSetValidTipRecipient {} + +contract InvalidUserTipRecipientOverrulesRelayer is UserSetInvalidTipRecipient, RelayerSetValidTipRecipient { + function setUp() public override(InitialState, UserSetInvalidTipRecipient) { + super.setUp(); + } +} + +contract ValidRelayerTipRecipientUsed is UserSetZeroTipRecipient, RelayerSetValidTipRecipient { + function setUp() public override(InitialState, UserSetZeroTipRecipient) { + super.setUp(); + } +} + +contract InvalidRelayerTipRecipientUsed is UserSetZeroTipRecipient, RelayerSetInvalidTipRecipient { + function setUp() public override(UserSetZeroTipRecipient, RelayerSetInvalidTipRecipient) { + super.setUp(); + relayShouldSucceed = false; + } +} + +contract NoTipRecipientSet is UserSetZeroTipRecipient, RelayerSetZeroTipRecipient { + function setUp() public override(UserSetZeroTipRecipient, RelayerSetZeroTipRecipient) { + super.setUp(); + relayShouldSucceed = false; + } } From 0b1c6e76a6cd2c420d3cd3c30feb9055fe79c46d Mon Sep 17 00:00:00 2001 From: Nikesh Nazareth Date: Wed, 6 Aug 2025 12:15:09 +1000 Subject: [PATCH 21/31] Move shouldRevert checks to the InitialState contract This means we don't need to write an identical shouldRevert test everytime we clear the relayShouldSucceed or claimShouldSucceed flag --- test/MessageRelayer/InitialState.t.sol | 15 +++++++++++++++ test/MessageRelayer/TipRecipientScenarios.t.sol | 15 --------------- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/test/MessageRelayer/InitialState.t.sol b/test/MessageRelayer/InitialState.t.sol index 4ee8652f..c783fb47 100644 --- a/test/MessageRelayer/InitialState.t.sol +++ b/test/MessageRelayer/InitialState.t.sol @@ -18,6 +18,7 @@ import {MockSignalService} from "test/mocks/MockSignalService.sol"; // defined in other files // - ideally, we would only run the tests in the relevant scenario, but this would require less encapsulated logic // - instead, the ifRelaySucceeds and ifClaimSucceeds modifiers are used to turn irrelevant tests into no-ops +// - the tests in this file ensure the transaction reverts when it is expected to abstract contract InitialState is Test { MessageRelayer messageRelayer; @@ -67,6 +68,20 @@ abstract contract InitialState is Test { _encodeReceiveCall(); } + function test_InitialState_relayMessage_shouldRevertWhenExpected() public { + if (!relayShouldSucceed) { + vm.expectRevert(IETHBridge.FailedClaim.selector); + _relayMessage(); + } + } + + function test_InitialState_claimDeposit_shouldRevertWhenExpected() public { + if (!claimShouldSucceed) { + vm.expectRevert(IETHBridge.FailedClaim.selector); + _claimDeposit(); + } + } + function _encodeReceiveCall() internal { ethDeposit.data = abi.encodeCall( IMessageRelayer.receiveMessage, (address(to), tip, address(userSelectedTipRecipient), gasLimit, data) diff --git a/test/MessageRelayer/TipRecipientScenarios.t.sol b/test/MessageRelayer/TipRecipientScenarios.t.sol index f58621ce..c80d04bf 100644 --- a/test/MessageRelayer/TipRecipientScenarios.t.sol +++ b/test/MessageRelayer/TipRecipientScenarios.t.sol @@ -55,11 +55,6 @@ abstract contract UserSetZeroTipRecipient is TipRecipientScenarios { _encodeReceiveCall(); claimShouldSucceed = false; } - - function test_UserSetZeroTipRecipient_claimDeposit_shouldRevert() public { - vm.expectRevert(IETHBridge.FailedClaim.selector); - _claimDeposit(); - } } abstract contract UserSetInvalidTipRecipient is TipRecipientScenarios { @@ -69,16 +64,6 @@ abstract contract UserSetInvalidTipRecipient is TipRecipientScenarios { relayShouldSucceed = false; claimShouldSucceed = false; } - - function test_UserSetInvalidTipRecipient_relayMessage_shouldRevert() public { - vm.expectRevert(IETHBridge.FailedClaim.selector); - _relayMessage(); - } - - function test_UserSetInvalidTipRecipient_claimDeposit_shouldRevert() public { - vm.expectRevert(IETHBridge.FailedClaim.selector); - _claimDeposit(); - } } // Relayer-selected tip recipient scenarios From 3b604237efda18d5e53a382d6bead11a40b0fe26 Mon Sep 17 00:00:00 2001 From: Nikesh Nazareth Date: Wed, 6 Aug 2025 12:18:52 +1000 Subject: [PATCH 22/31] Migrate FundAmountScenarios to the new structure --- test/MessageRelayer/FundAmountScenarios.t.sol | 58 ++++++++++++------- 1 file changed, 37 insertions(+), 21 deletions(-) diff --git a/test/MessageRelayer/FundAmountScenarios.t.sol b/test/MessageRelayer/FundAmountScenarios.t.sol index 86d5059a..275fb898 100644 --- a/test/MessageRelayer/FundAmountScenarios.t.sol +++ b/test/MessageRelayer/FundAmountScenarios.t.sol @@ -1,50 +1,66 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.28; -import {DepositRecipientIsMessageRelayer} from "./DepositRecipientScenarios.t.sol"; import {GenericRecipient} from "./GenericRecipient.t.sol"; +import {ValidUserTipRecipientOverrulesRelayer} from "./TipRecipientScenarios.t.sol"; import {IMessageRelayer} from "src/protocol/IMessageRelayer.sol"; import {InitialState} from "./InitialState.t.sol"; import {IETHBridge} from "src/protocol/IETHBridge.sol"; -contract VaryingFundAmounts is DepositRecipientIsMessageRelayer { - function setUp() public override { - super.setUp(); +// Use ValidUserTipRecipientOverrulesRelayer as the default scenario. +// The particular tip arrangement should not affect these tests. +abstract contract FundAmountScenarios is ValidUserTipRecipientOverrulesRelayer { + function test_FundAmountScenarios_relayMessage_shouldInvokeRecipient() public ifRelaySucceeds { + vm.expectEmit(); + emit GenericRecipient.FunctionCalled(); + _relayMessage(); } - function test_SetZeroAmountWithTip_relayMessage_shouldRevert() public zeroAmount { - vm.expectRevert(IETHBridge.FailedClaim.selector); + function test_FundAmountScenarios_relayMessage_shouldNotRetainFundsInRelayer() public ifRelaySucceeds { + assertEq(address(messageRelayer).balance, 0, "relayer should not have funds"); _relayMessage(); + assertEq(address(messageRelayer).balance, 0, "relayer should not retain funds"); } - function test_SetZeroAmountWithoutTip_relayMessage_shouldSucceed() public zeroTipZeroAmount { - vm.expectEmit(); - emit IMessageRelayer.MessageForwarded(address(to), 0, data, address(userSelectedTipRecipient), 0); + function test_FundAmountScenarios_relayMessage_shouldSendAmountToRecipient() public ifRelaySucceeds { + uint256 balanceBefore = address(to).balance; + uint256 transferAmount = ethDeposit.amount - tip; _relayMessage(); + assertEq(address(to).balance, balanceBefore + transferAmount, "recipient balance mismatch"); } - function test_SetTipHigherThanAmount_relayMessage_shouldRevert() public amountTooLow { - vm.expectRevert(IETHBridge.FailedClaim.selector); - _relayMessage(); + function redundant_FundAmountScenarios_relayMessage_shouldSendTipToRecipient() public { + // This test (if it were implemented) would be redundant with the tip recipient scenarios + // It is included for completeness, so this file accounts for all the distributed funds } +} - modifier zeroTipZeroAmount() { - tip = 0; +contract AmountExceedsTip is FundAmountScenarios {} + +contract NoAmountNoTip is FundAmountScenarios { + function setUp() public override { + super.setUp(); ethDeposit.amount = 0; + tip = 0; _encodeReceiveCall(); - _; } +} - modifier zeroAmount() { +contract NoAmountNonzeroTip is FundAmountScenarios { + function setUp() public override { + super.setUp(); ethDeposit.amount = 0; - _encodeReceiveCall(); - _; + relayShouldSucceed = false; + claimShouldSucceed = false; } +} - modifier amountTooLow() { +contract AmountLessThanTip is FundAmountScenarios { + function setUp() public override { + super.setUp(); ethDeposit.amount = tip - 1 wei; - _encodeReceiveCall(); - _; + relayShouldSucceed = false; + claimShouldSucceed = false; } } From df1457e11ab82eff216300da7c31ba1d870f6145 Mon Sep 17 00:00:00 2001 From: Nikesh Nazareth Date: Wed, 6 Aug 2025 12:46:17 +1000 Subject: [PATCH 23/31] Remove unused TIP_RECIPIENT_SLOT --- test/MessageRelayer/InitialState.t.sol | 3 --- 1 file changed, 3 deletions(-) diff --git a/test/MessageRelayer/InitialState.t.sol b/test/MessageRelayer/InitialState.t.sol index c783fb47..456bc774 100644 --- a/test/MessageRelayer/InitialState.t.sol +++ b/test/MessageRelayer/InitialState.t.sol @@ -40,9 +40,6 @@ abstract contract InitialState is Test { bool relayShouldSucceed = true; bool claimShouldSucceed = true; - // keccak256("TIP_RECIPIENT_SLOT") - bytes32 constant TIP_RECIPIENT_SLOT = 0x833ce1785f54a5ca49991a09a7b058587309bf3687e5f20b7b66fa12132ef6f0; - function setUp() public virtual { MockSignalService signalService = new MockSignalService(); address trustedCommitmentPublisher = makeAddr("trustedCommitmentPublisher"); From 9fb7b0696e78114016b7a3176897b5f6b40594d5 Mon Sep 17 00:00:00 2001 From: Nikesh Nazareth Date: Wed, 6 Aug 2025 13:29:18 +1000 Subject: [PATCH 24/31] Test GasLimitScenarios --- test/MessageRelayer/FundAmountScenarios.t.sol | 2 +- test/MessageRelayer/GasLimitScenarios.t.sol | 54 +++++++++++++------ test/MessageRelayer/GenericRecipient.t.sol | 5 ++ test/MessageRelayer/InitialState.t.sol | 8 ++- 4 files changed, 49 insertions(+), 20 deletions(-) diff --git a/test/MessageRelayer/FundAmountScenarios.t.sol b/test/MessageRelayer/FundAmountScenarios.t.sol index 275fb898..400fc569 100644 --- a/test/MessageRelayer/FundAmountScenarios.t.sol +++ b/test/MessageRelayer/FundAmountScenarios.t.sol @@ -9,7 +9,7 @@ import {InitialState} from "./InitialState.t.sol"; import {IETHBridge} from "src/protocol/IETHBridge.sol"; // Use ValidUserTipRecipientOverrulesRelayer as the default scenario. -// The particular tip arrangement should not affect these tests. +// Any valid tip arrangement should suffice for these tests. abstract contract FundAmountScenarios is ValidUserTipRecipientOverrulesRelayer { function test_FundAmountScenarios_relayMessage_shouldInvokeRecipient() public ifRelaySucceeds { vm.expectEmit(); diff --git a/test/MessageRelayer/GasLimitScenarios.t.sol b/test/MessageRelayer/GasLimitScenarios.t.sol index 47b01281..382bfc5a 100644 --- a/test/MessageRelayer/GasLimitScenarios.t.sol +++ b/test/MessageRelayer/GasLimitScenarios.t.sol @@ -1,38 +1,58 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.28; -import {DepositRecipientIsMessageRelayer} from "./DepositRecipientScenarios.t.sol"; +import {AmountExceedsTip} from "./FundAmountScenarios.t.sol"; import {GenericRecipient} from "./GenericRecipient.t.sol"; import {InitialState} from "./InitialState.t.sol"; import {IETHBridge} from "src/protocol/IETHBridge.sol"; import {IMessageRelayer} from "src/protocol/IMessageRelayer.sol"; -contract UserSetGasLimit is DepositRecipientIsMessageRelayer { +// Found by experimentation +uint256 constant OOG_INSIDE_RECIPIENT = 60_000; + +// Use AmountExceedsTip as the default scenario. +// Any valid tip arrangement and funding amount should suffice for these tests. +abstract contract GasLimitScenarios is AmountExceedsTip {} + +contract NoGasLimit_SufficientGasProvided is GasLimitScenarios {} + +contract NoGasLimit_InsufficientGasProvided is GasLimitScenarios { function setUp() public override { super.setUp(); + gasProvidedWithCall = OOG_INSIDE_RECIPIENT; + relayShouldSucceed = false; + claimShouldSucceed = false; } +} - function test_UserSetHighGasLimit_relayMessage_shouldRevert() public gasLimitHigherThanValue { - vm.expectRevert(IETHBridge.FailedClaim.selector); - _relayMessage(); - } - - function test_UserReasonableGasLimit_relayMessage() public whenGasLimitIsReasonable { - vm.expectEmit(); - emit IMessageRelayer.MessageForwarded(address(to), amount - tip, data, address(userSelectedTipRecipient), tip); - _relayMessage(); +contract SufficientGasLimit_SufficientGasProvided is GasLimitScenarios { + function setUp() public override { + super.setUp(); + gasLimit = to.GAS_REQUIRED() + 100; + _encodeReceiveCall(); } +} - modifier gasLimitHigherThanValue() { - gasLimit = amount + 1 wei; +contract SufficientGasLimit_InsufficientGasProvided is GasLimitScenarios { + function setUp() public override { + super.setUp(); + gasLimit = to.GAS_REQUIRED() + 100; _encodeReceiveCall(); - _; + gasProvidedWithCall = OOG_INSIDE_RECIPIENT; + relayShouldSucceed = false; + claimShouldSucceed = false; } +} - modifier whenGasLimitIsReasonable() { - gasLimit = 100_000; +contract InsufficientGasLimit_SufficientGasProvided is GasLimitScenarios { + function setUp() public override { + super.setUp(); + // the amount forwarded to the recipient is slightly higher than gasLimit so deduct 150 as compensation + // TODO: understand why this is necessary + gasLimit = to.GAS_REQUIRED() - 150; _encodeReceiveCall(); - _; + relayShouldSucceed = false; + claimShouldSucceed = false; } } diff --git a/test/MessageRelayer/GenericRecipient.t.sol b/test/MessageRelayer/GenericRecipient.t.sol index f7fb7267..bb38070c 100644 --- a/test/MessageRelayer/GenericRecipient.t.sol +++ b/test/MessageRelayer/GenericRecipient.t.sol @@ -13,6 +13,9 @@ contract GenericRecipient is IGenericRecipient { bool private shouldReenterAttack = false; address private relayer; + // Consume a minimum amount of gas so we can test gas limits + uint256 public constant GAS_REQUIRED = 20_000; + error CallFailed(); event FunctionCalled(); @@ -39,6 +42,8 @@ contract GenericRecipient is IGenericRecipient { function _simulateFunctionCall() internal { require(callWillSucceed, CallFailed()); + require(gasleft() >= GAS_REQUIRED, "Insufficient gas"); + emit FunctionCalled(); if (shouldReenterAttack) { diff --git a/test/MessageRelayer/InitialState.t.sol b/test/MessageRelayer/InitialState.t.sol index 456bc774..0a011034 100644 --- a/test/MessageRelayer/InitialState.t.sol +++ b/test/MessageRelayer/InitialState.t.sol @@ -40,6 +40,8 @@ abstract contract InitialState is Test { bool relayShouldSucceed = true; bool claimShouldSucceed = true; + uint256 gasProvidedWithCall = 150_000; // covers a full relayMessage call with some overhead + function setUp() public virtual { MockSignalService signalService = new MockSignalService(); address trustedCommitmentPublisher = makeAddr("trustedCommitmentPublisher"); @@ -86,11 +88,13 @@ abstract contract InitialState is Test { } function _relayMessage() internal { - messageRelayer.relayMessage(ethDeposit, height, proof, address(relayerSelectedTipRecipient)); + messageRelayer.relayMessage{gas: gasProvidedWithCall}( + ethDeposit, height, proof, address(relayerSelectedTipRecipient) + ); } function _claimDeposit() internal { - bridge.claimDeposit(ethDeposit, height, proof); + bridge.claimDeposit{gas: gasProvidedWithCall}(ethDeposit, height, proof); } modifier ifRelaySucceeds() { From a0fed5246729f9e52d508734134d4595c63a9ab8 Mon Sep 17 00:00:00 2001 From: Nikesh Nazareth Date: Wed, 6 Aug 2025 13:41:40 +1000 Subject: [PATCH 25/31] Migrate RelayRecipientScenarios to new structure --- .../RelayRecipientScenarios.t.sol | 26 +++++++++++++------ 1 file changed, 18 insertions(+), 8 deletions(-) diff --git a/test/MessageRelayer/RelayRecipientScenarios.t.sol b/test/MessageRelayer/RelayRecipientScenarios.t.sol index 79789bcb..b33a69d5 100644 --- a/test/MessageRelayer/RelayRecipientScenarios.t.sol +++ b/test/MessageRelayer/RelayRecipientScenarios.t.sol @@ -1,22 +1,32 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.28; -import {DepositRecipientIsMessageRelayer} from "./DepositRecipientScenarios.t.sol"; +import {NoGasLimit_SufficientGasProvided} from "./GasLimitScenarios.t.sol"; import {GenericRecipient} from "./GenericRecipient.t.sol"; import {InitialState} from "./InitialState.t.sol"; import {IETHBridge} from "src/protocol/IETHBridge.sol"; -contract RelayRecipientScenarios is DepositRecipientIsMessageRelayer { - function test_RelayRecipientRejectsDeposit_relayMessage_shouldRevert() public { +// Use NoGasLimit_SufficientGasProvided as the default scenario. +// Any valid tip arrangement, funding amount and gas limit should suffice for these tests. +abstract contract RelayRecipientScenarios is NoGasLimit_SufficientGasProvided {} + +contract RelayRecipentAcceptsMessage is RelayRecipientScenarios {} + +contract RelayRecipientRejectsMessage is RelayRecipientScenarios { + function setUp() public override { + super.setUp(); to.setSuccess(false); - vm.expectRevert(IETHBridge.FailedClaim.selector); - _relayMessage(); + relayShouldSucceed = false; + claimShouldSucceed = false; } +} - function test_RelayRecipientReentersReceiveMessage_relayMessage_shouldRevert() public { +contract RelayRecipientReentersReceiveMessage is RelayRecipientScenarios { + function setUp() public override { + super.setUp(); to.setReentrancyAttack(true); - vm.expectRevert(IETHBridge.FailedClaim.selector); - _relayMessage(); + relayShouldSucceed = false; + claimShouldSucceed = false; } } From 240c6d9080970b720de8e2e8ce63b61fa42aac9a Mon Sep 17 00:00:00 2001 From: Nikesh Nazareth Date: Wed, 6 Aug 2025 13:56:12 +1000 Subject: [PATCH 26/31] Create default scenarios for better encapsulation Technically, the InitialState is a valid default scenario but I think it still makes sense structurally for each group of tests to be built on a previous one --- .../DepositRecipientScenarios.t.sol | 31 ++++++++++--------- test/MessageRelayer/FundAmountScenarios.t.sol | 9 +++--- test/MessageRelayer/GasLimitScenarios.t.sol | 9 +++--- .../RelayRecipientScenarios.t.sol | 9 +++--- .../TipRecipientScenarios.t.sol | 9 ++++-- 5 files changed, 38 insertions(+), 29 deletions(-) diff --git a/test/MessageRelayer/DepositRecipientScenarios.t.sol b/test/MessageRelayer/DepositRecipientScenarios.t.sol index f87255d5..e9aa47dd 100644 --- a/test/MessageRelayer/DepositRecipientScenarios.t.sol +++ b/test/MessageRelayer/DepositRecipientScenarios.t.sol @@ -7,9 +7,21 @@ import {InitialState} from "./InitialState.t.sol"; import {IETHBridge} from "src/protocol/IETHBridge.sol"; import {IMessageRelayer} from "src/protocol/IMessageRelayer.sol"; -// This is a concrete class because if we are not using the MessageRelayer, -// we do not need to investigate any other properties of the message -contract DepositRecipientIsNotMessageRelayer is InitialState { +abstract contract DepositRecipientScenarios is InitialState {} + +contract DepositRecipientIsMessageRelayer is DepositRecipientScenarios { + function test_DepositRecipientIsMessageRelayer_relayMessage_shouldInvokeReceiveMessage() public ifRelaySucceeds { + vm.expectCall(address(messageRelayer), ethDeposit.data); + _relayMessage(); + } + + function test_DepositRecipientIsMessageRelayer_claimDeposit_shouldInvokeReceiveMessage() public ifClaimSucceeds { + vm.expectCall(address(messageRelayer), ethDeposit.data); + _claimDeposit(); + } +} + +contract DepositRecipientIsNotMessageRelayer is DepositRecipientScenarios { function setUp() public override { super.setUp(); // bypass the relayer and send the message directly to the recipient @@ -30,14 +42,5 @@ contract DepositRecipientIsNotMessageRelayer is InitialState { } } -abstract contract DepositRecipientIsMessageRelayer is InitialState { - function test_DepositRecipientIsMessageRelayer_relayMessage_shouldInvokeReceiveMessage() public ifRelaySucceeds { - vm.expectCall(address(messageRelayer), ethDeposit.data); - _relayMessage(); - } - - function test_DepositRecipientIsMessageRelayer_claimDeposit_shouldInvokeReceiveMessage() public ifClaimSucceeds { - vm.expectCall(address(messageRelayer), ethDeposit.data); - _claimDeposit(); - } -} +// A valid scenario that can be used as a default scenario by unrelated tests. +abstract contract DefaultRecipientScenario is DepositRecipientScenarios {} \ No newline at end of file diff --git a/test/MessageRelayer/FundAmountScenarios.t.sol b/test/MessageRelayer/FundAmountScenarios.t.sol index 400fc569..48711324 100644 --- a/test/MessageRelayer/FundAmountScenarios.t.sol +++ b/test/MessageRelayer/FundAmountScenarios.t.sol @@ -2,15 +2,13 @@ pragma solidity ^0.8.28; import {GenericRecipient} from "./GenericRecipient.t.sol"; -import {ValidUserTipRecipientOverrulesRelayer} from "./TipRecipientScenarios.t.sol"; +import {DefaultTipRecipientScenario} from "./TipRecipientScenarios.t.sol"; import {IMessageRelayer} from "src/protocol/IMessageRelayer.sol"; import {InitialState} from "./InitialState.t.sol"; import {IETHBridge} from "src/protocol/IETHBridge.sol"; -// Use ValidUserTipRecipientOverrulesRelayer as the default scenario. -// Any valid tip arrangement should suffice for these tests. -abstract contract FundAmountScenarios is ValidUserTipRecipientOverrulesRelayer { +abstract contract FundAmountScenarios is DefaultTipRecipientScenario { function test_FundAmountScenarios_relayMessage_shouldInvokeRecipient() public ifRelaySucceeds { vm.expectEmit(); emit GenericRecipient.FunctionCalled(); @@ -64,3 +62,6 @@ contract AmountLessThanTip is FundAmountScenarios { claimShouldSucceed = false; } } + +// A valid scenario that can be used as a default scenario by unrelated tests. +abstract contract DefaultFundAmountScenario is AmountExceedsTip {} \ No newline at end of file diff --git a/test/MessageRelayer/GasLimitScenarios.t.sol b/test/MessageRelayer/GasLimitScenarios.t.sol index 382bfc5a..0c8460ea 100644 --- a/test/MessageRelayer/GasLimitScenarios.t.sol +++ b/test/MessageRelayer/GasLimitScenarios.t.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.28; -import {AmountExceedsTip} from "./FundAmountScenarios.t.sol"; +import {DefaultFundAmountScenario} from "./FundAmountScenarios.t.sol"; import {GenericRecipient} from "./GenericRecipient.t.sol"; import {InitialState} from "./InitialState.t.sol"; @@ -11,9 +11,7 @@ import {IMessageRelayer} from "src/protocol/IMessageRelayer.sol"; // Found by experimentation uint256 constant OOG_INSIDE_RECIPIENT = 60_000; -// Use AmountExceedsTip as the default scenario. -// Any valid tip arrangement and funding amount should suffice for these tests. -abstract contract GasLimitScenarios is AmountExceedsTip {} +abstract contract GasLimitScenarios is DefaultFundAmountScenario {} contract NoGasLimit_SufficientGasProvided is GasLimitScenarios {} @@ -56,3 +54,6 @@ contract InsufficientGasLimit_SufficientGasProvided is GasLimitScenarios { claimShouldSucceed = false; } } + +// A valid scenario that can be used as a default scenario by unrelated tests. +abstract contract DefaultGasLimitScenario is NoGasLimit_SufficientGasProvided {} \ No newline at end of file diff --git a/test/MessageRelayer/RelayRecipientScenarios.t.sol b/test/MessageRelayer/RelayRecipientScenarios.t.sol index b33a69d5..bca67bf3 100644 --- a/test/MessageRelayer/RelayRecipientScenarios.t.sol +++ b/test/MessageRelayer/RelayRecipientScenarios.t.sol @@ -1,15 +1,13 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.28; -import {NoGasLimit_SufficientGasProvided} from "./GasLimitScenarios.t.sol"; +import {DefaultGasLimitScenario} from "./GasLimitScenarios.t.sol"; import {GenericRecipient} from "./GenericRecipient.t.sol"; import {InitialState} from "./InitialState.t.sol"; import {IETHBridge} from "src/protocol/IETHBridge.sol"; -// Use NoGasLimit_SufficientGasProvided as the default scenario. -// Any valid tip arrangement, funding amount and gas limit should suffice for these tests. -abstract contract RelayRecipientScenarios is NoGasLimit_SufficientGasProvided {} +abstract contract RelayRecipientScenarios is DefaultGasLimitScenario {} contract RelayRecipentAcceptsMessage is RelayRecipientScenarios {} @@ -30,3 +28,6 @@ contract RelayRecipientReentersReceiveMessage is RelayRecipientScenarios { claimShouldSucceed = false; } } + +// A valid scenario that can be used as a default scenario by unrelated tests. +abstract contract DefaultRelayRecipientScenario is RelayRecipentAcceptsMessage {} \ No newline at end of file diff --git a/test/MessageRelayer/TipRecipientScenarios.t.sol b/test/MessageRelayer/TipRecipientScenarios.t.sol index c80d04bf..8face617 100644 --- a/test/MessageRelayer/TipRecipientScenarios.t.sol +++ b/test/MessageRelayer/TipRecipientScenarios.t.sol @@ -1,15 +1,14 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.28; -import {DepositRecipientIsMessageRelayer} from "./DepositRecipientScenarios.t.sol"; +import {DefaultRecipientScenario} from "./DepositRecipientScenarios.t.sol"; import {GenericRecipient} from "./GenericRecipient.t.sol"; import {InitialState} from "./InitialState.t.sol"; import {IETHBridge} from "src/protocol/IETHBridge.sol"; -import {console} from "forge-std/console.sol"; -abstract contract TipRecipientScenarios is DepositRecipientIsMessageRelayer { +abstract contract TipRecipientScenarios is DefaultRecipientScenario { function test_TipRecipientScenarios_relayMessage_shouldTipCorrectRecipient() public ifRelaySucceeds { (GenericRecipient correctRecipient,) = _recipients(); uint256 balanceBefore = address(correctRecipient).balance; @@ -113,3 +112,7 @@ contract NoTipRecipientSet is UserSetZeroTipRecipient, RelayerSetZeroTipRecipien relayShouldSucceed = false; } } + + +// A valid scenario that can be used as a default scenario by unrelated tests. +abstract contract DefaultTipRecipientScenario is ValidUserTipRecipientOverrulesRelayer {} From 4794e23478494eec652a3d80fa297f2fe45229b9 Mon Sep 17 00:00:00 2001 From: Nikesh Nazareth Date: Wed, 6 Aug 2025 13:59:23 +1000 Subject: [PATCH 27/31] Fix InsufficientValue comment --- src/protocol/IMessageRelayer.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/protocol/IMessageRelayer.sol b/src/protocol/IMessageRelayer.sol index f0166ddb..81ec1b79 100644 --- a/src/protocol/IMessageRelayer.sol +++ b/src/protocol/IMessageRelayer.sol @@ -19,7 +19,7 @@ interface IMessageRelayer { /// @dev Message forwarding failed error MessageForwardingFailed(); - /// @dev Value sent is higher than tip amount + /// @dev Value sent is lower than tip amount error InsufficientValue(); /// @dev Tip transfer failed From 7e303208fb0fc815cab7337802b3e50ceca73691 Mon Sep 17 00:00:00 2001 From: Nikesh Nazareth Date: Wed, 6 Aug 2025 14:00:52 +1000 Subject: [PATCH 28/31] Run forge fmt --- test/MessageRelayer/DepositRecipientScenarios.t.sol | 2 +- test/MessageRelayer/FundAmountScenarios.t.sol | 2 +- test/MessageRelayer/GasLimitScenarios.t.sol | 2 +- test/MessageRelayer/RelayRecipientScenarios.t.sol | 2 +- test/MessageRelayer/TipRecipientScenarios.t.sol | 2 -- 5 files changed, 4 insertions(+), 6 deletions(-) diff --git a/test/MessageRelayer/DepositRecipientScenarios.t.sol b/test/MessageRelayer/DepositRecipientScenarios.t.sol index e9aa47dd..bff2801b 100644 --- a/test/MessageRelayer/DepositRecipientScenarios.t.sol +++ b/test/MessageRelayer/DepositRecipientScenarios.t.sol @@ -43,4 +43,4 @@ contract DepositRecipientIsNotMessageRelayer is DepositRecipientScenarios { } // A valid scenario that can be used as a default scenario by unrelated tests. -abstract contract DefaultRecipientScenario is DepositRecipientScenarios {} \ No newline at end of file +abstract contract DefaultRecipientScenario is DepositRecipientScenarios {} diff --git a/test/MessageRelayer/FundAmountScenarios.t.sol b/test/MessageRelayer/FundAmountScenarios.t.sol index 48711324..8ec38a43 100644 --- a/test/MessageRelayer/FundAmountScenarios.t.sol +++ b/test/MessageRelayer/FundAmountScenarios.t.sol @@ -64,4 +64,4 @@ contract AmountLessThanTip is FundAmountScenarios { } // A valid scenario that can be used as a default scenario by unrelated tests. -abstract contract DefaultFundAmountScenario is AmountExceedsTip {} \ No newline at end of file +abstract contract DefaultFundAmountScenario is AmountExceedsTip {} diff --git a/test/MessageRelayer/GasLimitScenarios.t.sol b/test/MessageRelayer/GasLimitScenarios.t.sol index 0c8460ea..f7d6f096 100644 --- a/test/MessageRelayer/GasLimitScenarios.t.sol +++ b/test/MessageRelayer/GasLimitScenarios.t.sol @@ -56,4 +56,4 @@ contract InsufficientGasLimit_SufficientGasProvided is GasLimitScenarios { } // A valid scenario that can be used as a default scenario by unrelated tests. -abstract contract DefaultGasLimitScenario is NoGasLimit_SufficientGasProvided {} \ No newline at end of file +abstract contract DefaultGasLimitScenario is NoGasLimit_SufficientGasProvided {} diff --git a/test/MessageRelayer/RelayRecipientScenarios.t.sol b/test/MessageRelayer/RelayRecipientScenarios.t.sol index bca67bf3..ef4956d3 100644 --- a/test/MessageRelayer/RelayRecipientScenarios.t.sol +++ b/test/MessageRelayer/RelayRecipientScenarios.t.sol @@ -30,4 +30,4 @@ contract RelayRecipientReentersReceiveMessage is RelayRecipientScenarios { } // A valid scenario that can be used as a default scenario by unrelated tests. -abstract contract DefaultRelayRecipientScenario is RelayRecipentAcceptsMessage {} \ No newline at end of file +abstract contract DefaultRelayRecipientScenario is RelayRecipentAcceptsMessage {} diff --git a/test/MessageRelayer/TipRecipientScenarios.t.sol b/test/MessageRelayer/TipRecipientScenarios.t.sol index 8face617..9d2e58d6 100644 --- a/test/MessageRelayer/TipRecipientScenarios.t.sol +++ b/test/MessageRelayer/TipRecipientScenarios.t.sol @@ -7,7 +7,6 @@ import {GenericRecipient} from "./GenericRecipient.t.sol"; import {InitialState} from "./InitialState.t.sol"; import {IETHBridge} from "src/protocol/IETHBridge.sol"; - abstract contract TipRecipientScenarios is DefaultRecipientScenario { function test_TipRecipientScenarios_relayMessage_shouldTipCorrectRecipient() public ifRelaySucceeds { (GenericRecipient correctRecipient,) = _recipients(); @@ -113,6 +112,5 @@ contract NoTipRecipientSet is UserSetZeroTipRecipient, RelayerSetZeroTipRecipien } } - // A valid scenario that can be used as a default scenario by unrelated tests. abstract contract DefaultTipRecipientScenario is ValidUserTipRecipientOverrulesRelayer {} From bac25c5d12d8a1f2250ba05a75db29d1db729337 Mon Sep 17 00:00:00 2001 From: Nikesh Nazareth Date: Wed, 6 Aug 2025 14:04:19 +1000 Subject: [PATCH 29/31] Increase OOG_INSIDE_RECIPIENT The tests succeed locally but are failing too early in the CI. Use a slightly larger gas limit to try to account for both environments --- test/MessageRelayer/GasLimitScenarios.t.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/MessageRelayer/GasLimitScenarios.t.sol b/test/MessageRelayer/GasLimitScenarios.t.sol index f7d6f096..c55cc062 100644 --- a/test/MessageRelayer/GasLimitScenarios.t.sol +++ b/test/MessageRelayer/GasLimitScenarios.t.sol @@ -9,7 +9,7 @@ import {IETHBridge} from "src/protocol/IETHBridge.sol"; import {IMessageRelayer} from "src/protocol/IMessageRelayer.sol"; // Found by experimentation -uint256 constant OOG_INSIDE_RECIPIENT = 60_000; +uint256 constant OOG_INSIDE_RECIPIENT = 70_000; abstract contract GasLimitScenarios is DefaultFundAmountScenario {} From d77e2ae8af872bb27b4176335532bf6f4bf1980e Mon Sep 17 00:00:00 2001 From: Leo Date: Wed, 6 Aug 2025 11:29:14 +0200 Subject: [PATCH 30/31] typo --- test/MessageRelayer/RelayRecipientScenarios.t.sol | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/MessageRelayer/RelayRecipientScenarios.t.sol b/test/MessageRelayer/RelayRecipientScenarios.t.sol index ef4956d3..bb516603 100644 --- a/test/MessageRelayer/RelayRecipientScenarios.t.sol +++ b/test/MessageRelayer/RelayRecipientScenarios.t.sol @@ -9,7 +9,7 @@ import {IETHBridge} from "src/protocol/IETHBridge.sol"; abstract contract RelayRecipientScenarios is DefaultGasLimitScenario {} -contract RelayRecipentAcceptsMessage is RelayRecipientScenarios {} +contract RelayRecipientAcceptsMessage is RelayRecipientScenarios {} contract RelayRecipientRejectsMessage is RelayRecipientScenarios { function setUp() public override { @@ -30,4 +30,4 @@ contract RelayRecipientReentersReceiveMessage is RelayRecipientScenarios { } // A valid scenario that can be used as a default scenario by unrelated tests. -abstract contract DefaultRelayRecipientScenario is RelayRecipentAcceptsMessage {} +abstract contract DefaultRelayRecipientScenario is RelayRecipientAcceptsMessage {} From 9ff57d217cc203aba2830bdab73a4b2716252439 Mon Sep 17 00:00:00 2001 From: Nikesh Nazareth Date: Wed, 6 Aug 2025 20:08:41 +1000 Subject: [PATCH 31/31] Set signalService to verify all signals The MockSignalService merged from main (after the bridge PR) no longer verifies by default --- test/MessageRelayer/InitialState.t.sol | 1 + 1 file changed, 1 insertion(+) diff --git a/test/MessageRelayer/InitialState.t.sol b/test/MessageRelayer/InitialState.t.sol index 0a011034..6ff5cb0c 100644 --- a/test/MessageRelayer/InitialState.t.sol +++ b/test/MessageRelayer/InitialState.t.sol @@ -44,6 +44,7 @@ abstract contract InitialState is Test { function setUp() public virtual { MockSignalService signalService = new MockSignalService(); + signalService.setVerifyResult(true); address trustedCommitmentPublisher = makeAddr("trustedCommitmentPublisher"); address counterpart = makeAddr("counterpart"); bridge = new ETHBridge(address(signalService), trustedCommitmentPublisher, counterpart);