LogoLogo
  • ⏩Introduction
    • Hyperlane Introduction
    • Getting started
    • Why Should You Use Hyperlane?
  • Permissionless Interoperability
    • Overview
    • Deploy Hyperlane
    • Warp Routes
      • Deploy a Warp Route
      • Deploy a UI for your Warp Route
    • Modular Rollup Interoperability
  • Build With Hyperlane
    • Quickstarts
      • Messaging
      • Accounts
      • Queries
      • hyperlane-quickstart repo
    • Guides
      • Finding my messages
      • Automatically pay for interchain gas
      • Manually pay for interchain gas
      • Choosing an interchain gas paymaster contract
      • Unit testing
      • Specifying an ISM
      • V2 migration guide
    • Explorer
      • Debugging messages
      • Configuring PI Chains
      • REST API
      • GraphQL API
    • Troubleshooting/Developer FAQ
    • Example apps
  • APIs and SDKs
    • Messaging API
      • Send
      • Receive
    • Accounts API
    • Queries API
    • Warp Route API
    • Interchain gas paymaster API
    • Hyperlane App Framework
      • Example usage
        • HelloWorld
        • Interchain Token
      • Solidity SDK
        • HyperlaneConnectionClient
        • Router
      • NodeJS SDK
        • RPC Providers
        • Deployment
        • Interchain testing
        • Quoting gas payments
        • App Abstraction
    • Hooks API
      • Contract addresses
  • Protocol
    • Overview
    • Mailbox
    • Interchain security modules
      • Interface
      • Multisig ISM
      • Routing ISM
      • Aggregation ISM
      • Optimistic ISM
      • Wormhole ISM
      • Hook ISM
      • CCIP-Read ISM
    • Interchain gas payments
    • Staking and slashing
    • Agents
      • Validators
      • Relayers
      • Watchtowers
    • Warp Routes
    • Implementation Guide
  • Operators
    • Validators
      • Guide
      • AWS setup
      • Monitoring and alerting
    • Relayers
      • Guide
      • Message filtering
    • Agent keys
      • Hexadecimal keys
      • AWS KMS keys
    • Agent configuration
      • Configuration reference
    • Running with docker compose
  • Resources
    • FAQ
    • Glossary
    • Contract addresses
      • Permissionless Deployment Contract Addresses
    • Domain identifiers
      • Permissionless Domain Identifiers
    • Default ISM settings
    • Coming Soon: Hyperlane v3
    • Token sources & faucets
    • Latencies
    • Github
    • Discord
    • Website
Powered by GitBook
On this page
  • MockMailbox source
  • TestRecipient source
  1. Build With Hyperlane
  2. Guides

Unit testing

Unit test your integration with mock Hyperlane contracts

You can use the MockMailbox contract to unit test your integration with the Hyperlane messaging API.

You simply need to deploy MockMailbox contracts in your test environment, and configure your application to send and receive messages using these contracts.

The MockMailbox contract reflects the asynchronous nature of interchain messaging. In order to deliver a dispatched message to its recipient, you must call MockMailbox.processNextPendingMessage(), as shown in the examples below.

// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.13;

import "forge-std/Test.sol";

import {MockMailbox} from "../src/mocks/MockMailbox.sol";
import {TestRecipient} from "../src/TestRecipient.sol";

contract MailboxTest is Test {
    uint32 constant originDomain = 1000;
    uint32 constant destinationDomain = 2000;
    MockMailbox originMailbox;
    MockMailbox destinationMailbox;

    function setUp() public {
        originMailbox = new MockMailbox(originDomain);
        destinationMailbox = new MockMailbox(destinationDomain);
        originMailbox.addRemoteMailbox(destinationDomain, destinationMailbox);
        destinationMailbox.addRemoteMailbox(originDomain, originMailbox);
    }

    function testExample() public {
        TestRecipient recipient = new TestRecipient();
        bytes memory data = "This is a test message";

        originMailbox.dispatch(destinationDomain, addressToBytes32(address(recipient)), data);
        destinationMailbox.processNextInboundMessage();

        assertEq(recipient.data(), data);
    }

    function addressToBytes32(address _addr) internal pure returns (bytes32) {
        return bytes32(uint256(uint160(_addr)));
    }
}
describe("Hyperlane", function() {
  it("should be able to send a message", async function () {
    const MockMailbox = await ethers.getContractFactory("MockMailbox")
    const TestRecipient = await ethers.getContractFactory("TestRecipient")

    const originDomain = 1000
    const originMailbox = await MockMailbox.deploy(originDomain)
    
    const destinationDomain = 2000
    const destinationMailbox = await MockMailbox.deploy(destinationDomain)
    
    await originMailbox.enrollRemoteMailbox(destinationDomain, destinationMailbox.address)
    await destinationMailbox.enrollRemoteMailbox(originDomain, originMailbox.address)

    const recipient = await TestRecipient.deploy()
    const data = ethers.utils.toUtf8Bytes("This is a test message")
    
    await originMailbox.dispatch(destinationDomain, addressToBytes32(recipient.address), data)
    await destinationMailbox.processNextPendingMessage()

    const dataReceived = await recipient.data()
    expect(dataReceived).to.eql(ethers.utils.hexlify(data))
  })
})

MockMailbox source

// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.0;

interface IMessageRecipient {
  function handle(
    uint32 _origin,
    bytes32 _sender,
    bytes calldata _message
  ) external;
}

contract MockMailbox {
    // Domain of chain on which the contract is deployed
    uint32 public immutable domain;
    uint32 public immutable version = 0;

    uint256 public outboundNonce = 0;
    uint256 public inboundUnprocessedNonce = 0;
    uint256 public inboundProcessedNonce = 0;
    mapping(uint32 => MockMailbox) public remoteMailboxes;
    mapping(uint256 => Message) public inboundMessages;

    struct Message {
        uint32 origin;
        address sender;
        address recipient;
        bytes body;
    }

    constructor(uint32 _domain) {
        domain = _domain;
    }

    function addRemoteMailbox(uint32 _domain, MockMailbox _mailbox) external {
        remoteMailboxes[_domain] = _mailbox;
    }

    function dispatch(
        uint32 _destinationDomain,
        bytes32 _recipientAddress,
        bytes calldata _messageBody
    ) external returns (bytes32) {
        MockMailbox _destinationMailbox = remoteMailboxes[_destinationDomain];
        require(
            address(_destinationMailbox) != address(0),
            "Missing remote mailbox"
        );
        _destinationMailbox.addInboundMessage(
            domain,
            msg.sender,
            bytes32ToAddress(_recipientAddress),
            _messageBody
        );
        outboundNonce++;
        return bytes32(0);
    }

    function addInboundMessage(
        uint32 _origin,
        address _sender,
        address _recipient,
        bytes calldata _body
    ) external {
        inboundMessages[inboundUnprocessedNonce] = Message(
            _origin,
            _sender,
            _recipient,
            _body
        );
        inboundUnprocessedNonce++;
    }

    function processNextInboundMessage() public {
        Message memory _message = inboundMessages[inboundProcessedNonce];
        IMessageRecipient(_message.recipient).handle(
            _message.origin,
            addressToBytes32(_message.sender),
            _message.body
        );
        inboundProcessedNonce++;
    }

    function bytes32ToAddress(bytes32 _buf) internal pure returns (address) {
        return address(uint160(uint256(_buf)));
    }
  
    function addressToBytes32(address _addr) internal pure returns (bytes32) {
        return bytes32(uint256(uint160(_addr)));
    }
}

TestRecipient source

// SPDX-License-Identifier: MIT OR Apache-2.0
pragma solidity >=0.8.0;

interface IMessageRecipient {
  function handle(
    uint32 _origin,
    bytes32 _sender,
    bytes calldata _message
  ) external;
}

contract TestRecipient is IMessageRecipient {
  bytes32 public sender;
  bytes public data;
  function handle(
    uint32,
    bytes32 _sender,
    bytes calldata _data
  ) external override {
    sender = _sender;
    data = _data;
  }
}
PreviousChoosing an interchain gas paymaster contractNextSpecifying an ISM

Last updated 2 years ago