Skip to content
Open
Show file tree
Hide file tree
Changes from 6 commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
b8d8acd
Create initial state
nikeshnazareth Jul 22, 2025
a60d464
Create default message
nikeshnazareth Jul 22, 2025
d2aed97
Create message recipient
nikeshnazareth Jul 22, 2025
6b84dba
Create DepositRecipientScenarios
nikeshnazareth Jul 22, 2025
6205519
Create TipRecipientScenarios
nikeshnazareth Jul 22, 2025
c67ec39
Test claimDeposit path
nikeshnazareth Jul 22, 2025
9074f4d
add more scenarios
LeoPatOZ Jul 25, 2025
fa42d46
made scripts easier to work with
LeoPatOZ Jul 25, 2025
cc68261
reentrancy test
LeoPatOZ Jul 25, 2025
f0ee2db
remove uneeded counter
LeoPatOZ Jul 28, 2025
ffd4e33
fix scenarios
LeoPatOZ Jul 29, 2025
ac4471c
add more test scenarios
LeoPatOZ Jul 29, 2025
a3de214
better testing scenario
LeoPatOZ Jul 29, 2025
bd61c84
transient storage test
LeoPatOZ Jul 29, 2025
443b9e2
Merge branch 'main' into tests/message-relayer
LeoPatOZ Aug 1, 2025
f781997
update scenario
LeoPatOZ Aug 1, 2025
5347332
name
LeoPatOZ Aug 1, 2025
521fd25
Merge branch 'main' into tests/message-relayer
LeoPatOZ Aug 1, 2025
4608dc8
Create ifTxSucceeds modifier
nikeshnazareth Aug 5, 2025
28fb7bd
Remove InitialStateTest
nikeshnazareth Aug 5, 2025
d093a79
Use ifTxSucceeds so UserSetInvalidTipRecipient can inherit DepositRec…
nikeshnazareth Aug 5, 2025
0800551
Expand TipRecipientScenarios using the ifRelaySucceeds mechanism
nikeshnazareth Aug 5, 2025
0b1c6e7
Move shouldRevert checks to the InitialState contract
nikeshnazareth Aug 6, 2025
3b60423
Migrate FundAmountScenarios to the new structure
nikeshnazareth Aug 6, 2025
df1457e
Remove unused TIP_RECIPIENT_SLOT
nikeshnazareth Aug 6, 2025
9fb7b06
Test GasLimitScenarios
nikeshnazareth Aug 6, 2025
a0fed52
Migrate RelayRecipientScenarios to new structure
nikeshnazareth Aug 6, 2025
240c6d9
Create default scenarios for better encapsulation
nikeshnazareth Aug 6, 2025
4794e23
Fix InsufficientValue comment
nikeshnazareth Aug 6, 2025
7e30320
Run forge fmt
nikeshnazareth Aug 6, 2025
bac25c5
Increase OOG_INSIDE_RECIPIENT
nikeshnazareth Aug 6, 2025
d77e2ae
typo
LeoPatOZ Aug 6, 2025
0c38142
Merge branch 'main' into tests/message-relayer
nikeshnazareth Aug 6, 2025
9ff57d2
Set signalService to verify all signals
nikeshnazareth Aug 6, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 41 additions & 0 deletions test/MessageRelayer/DepositRecipientScenarios.t.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.28;

import {GenericRecipient} from "./GenericRecipient.t.sol";
import {InitialState} from "./InitialState.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 GenericRecipient.FunctionCalled();
_relayMessage();
}

function test_DepositRecipientIsNotMessageRelayer_relayMessage_shouldNotInvokeReceiveMessage() public {
vm.expectCall(address(messageRelayer), ethDeposit.data, 0);
_relayMessage();
}
}

abstract contract DepositRecipientIsMessageRelayer is InitialState {
function test_DepositRecipientIsMessageRelayer_relayMessage_shouldInvokeReceiveMessage() public {
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);
}
}
27 changes: 27 additions & 0 deletions test/MessageRelayer/GenericRecipient.t.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.28;

contract GenericRecipient {
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();
}
Comment on lines +23 to +52

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Fix syntax error in require statement.

The require statement with a custom error is using incorrect syntax. The require function expects a string message, not a custom error.

Apply this diff to fix the syntax error:

 function _simulateFunctionCall() internal {
-    require(callWillSucceed, CallFailed());
+    if (!callWillSucceed) {
+        revert CallFailed();
+    }
     emit FunctionCalled();
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
function _simulateFunctionCall() internal {
require(callWillSucceed, CallFailed());
emit FunctionCalled();
}
function _simulateFunctionCall() internal {
- require(callWillSucceed, CallFailed());
+ if (!callWillSucceed) {
+ revert CallFailed();
+ }
emit FunctionCalled();
}
🤖 Prompt for AI Agents
In test/MessageRelayer/GenericRecipient.t.sol around lines 23 to 26, the require
statement uses a custom error CallFailed() which is invalid syntax because
require expects a string message. Replace the custom error with a string message
describing the failure, for example require(callWillSucceed, "Call failed"), to
fix the syntax error.

}
70 changes: 70 additions & 0 deletions test/MessageRelayer/InitialState.t.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
// SPDX-License-Identifier: MIT
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 {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";

abstract contract InitialState is Test {
MessageRelayer messageRelayer;

// Default message parameters
IETHBridge.ETHDeposit ethDeposit;
uint256 height = 0;
bytes proof = "0x";
GenericRecipient to;
uint256 amount = 2 ether;
uint256 tip = 0.1 ether;
GenericRecipient relayerSelectedTipRecipient;
GenericRecipient userSelectedTipRecipient;
uint256 gasLimit = 0;
bytes data = "0x";

function setUp() public virtual {
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));

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, (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) {
return address(uint160(uint256(keccak256(abi.encode(_domainSeparator(), name)))));
}

function _domainSeparator() internal pure returns (bytes32) {
return keccak256("MessageRelayer");
}
}
49 changes: 49 additions & 0 deletions test/MessageRelayer/TipRecipientScenarios.t.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
// 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 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();
}
}
25 changes: 25 additions & 0 deletions test/mocks/MockSignalService.sol
Original file line number Diff line number Diff line change
@@ -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 {}
}