// SPDX-License-Identifier: MIT pragma solidity 0.8.23; import "./IHypMailbox.sol"; /** * @title MockHypMailbox — in-memory Mailbox used by the warp-route tests * @notice Simulates Hyperlane's interchain dispatch/handle flow inside a * single Hardhat test runner. Two mock-mailbox instances can be * paired (setSister) so that a dispatch on instance A triggers a * handle() call on instance B's enrolled recipient. * * @dev Only used in tests, never deployed to mainnet. Exposes: * - quoteDispatch → returns a configurable flat fee * - dispatch → forwards body to the sister Mailbox's * recipient.handle() with origin = this.localDomain * - localDomain → configurable per instance */ contract MockHypMailbox is IHypMailbox { uint32 public override localDomain; uint256 public fee = 0.001 ether; MockHypMailbox public sister; address public localRecipient; // The application contract on THIS Mailbox's chain that receives inbound messages. event Dispatched(uint32 indexed destination, bytes32 indexed recipient, bytes body); event Handled(uint32 indexed origin, bytes32 indexed sender, bytes body); constructor(uint32 _localDomain) { localDomain = _localDomain; } /// @notice Pair the two MockHypMailbox instances. /// @param _sister The OTHER Mailbox (on the other "chain"). /// @param _localRecipient The application contract on OUR side that /// should receive inbound .handle() calls. function setSister(MockHypMailbox _sister, address _localRecipient) external { sister = _sister; localRecipient = _localRecipient; } function setFee(uint256 _fee) external { fee = _fee; } function quoteDispatch(uint32, bytes32, bytes calldata) external view override returns (uint256) { return fee; } function dispatch( uint32 destinationDomain, bytes32 recipientAddress, bytes calldata messageBody ) external payable override returns (bytes32 messageId) { require(msg.value >= fee, "MockMailbox: insufficient fee"); emit Dispatched(destinationDomain, recipientAddress, messageBody); messageId = keccak256(abi.encode(localDomain, destinationDomain, recipientAddress, messageBody, block.timestamp)); // Delivery: msg.sender of dispatch is the application on OUR chain; // we pass its bytes32-padded address as the `sender` field that the // sister's recipient will check against its enrolled routers. The // sister Mailbox actually performs the call so that recipient.handle() // sees msg.sender == sister Mailbox (matching production behaviour). if (address(sister) != address(0)) { bytes32 ourSenderB32 = bytes32(uint256(uint160(msg.sender))); sister.deliverInbound(localDomain, ourSenderB32, messageBody); } } /// @dev Internal-cross-mailbox hook. Called by the SOURCE Mailbox; we /// forward to our locally-configured recipient so it sees /// msg.sender == us (the destination Mailbox), per production /// Hyperlane semantics. function deliverInbound(uint32 origin, bytes32 sender, bytes calldata body) external { require(msg.sender == address(sister), "MockMailbox: not from sister"); require(localRecipient != address(0), "MockMailbox: no local recipient"); IHypMessageRecipient(localRecipient).handle(origin, sender, body); emit Handled(origin, sender, body); } }