Skip to main content

单元测试 - EVM

单元测试在使用Foundry进行多链设置时可能会变得具有挑战性。因此,我们提供了一个轻量级的测试环境 MockHyperlaneEnvironment,让您能够在不需要分叉多个网络的情况下对您的跨链应用进行单元测试。

大多数多链应用将构建在我们的Mailbox合约之上。因此,我们使用 MockMailbox 将已部署的邮箱的细节抽象化,而我们的环境包含了同一链上的 originMailboxdestinationMailbox。在内部,我们通过将到达目标的消息存储在目标邮箱的 inboundMessages 映射中来模拟消息传递。我们通过将消息入队并使用 MockMailbox.processNextInboundMessage() 增加 inboundProcessedNonce 来模拟消息传递。

简单消息Forge测试的设置如下:

Sending a message

contract SimpleMessagingTest is Test {
// origin and destination domains (recommended to be the chainId)
uint32 origin = 1;
uint32 destination = 2;

// both mailboxes will on the same chain but different addresses
MockMailbox originMailbox;
MockMailbox destinationMailbox;

// contract which can receive messages
TestRecipient receiver;

function setUp() public {
originMailbox = new MockMailbox(origin);
destinationMailbox = new MockMailbox(destination);
originMailbox.addRemoteMailbox(destination, destinationMailbox);

receiver = new TestRecipient();
}

function testSendMessage() public {
string _message = "Aloha!";
originMailbox.dispatch(
destination,
TypeCasts.addressToBytes32(address(receiver)),
bytes(_message)
);
// simulating message delivery to the destinationMailbox
destinationMailbox.processNextInboundMessage();
assertEq(string(receiver.lastData()), _message);
}
}

Testing Router-based apps

假设您正在测试继承自RouterTestCrosschainApp

contract CrosschainAppTest is Test {
// origin and destination domains (recommended to be the chainId)
uint32 origin = 1;
uint32 destination = 2;

function setUp() public {
environment = new MockHyperlaneEnvironment(origin, destination);

// your cross-chain app
TestCrosschainApp originTelephone = new TestCrosschainApp(environment.mailboxes(origin));
TestCrosschainApp destinationTelephone = new TestCrosschainApp(environment.mailboxes(destination));

// assuming you're inheriting the Router pattern from https://github.com/hyperlane-xyz/hyperlane-monorepo/blob/main/solidity/contracts/client/Router.sol
originTelephone.enrollRemoteRouter(destinationTelephone);
destinationTelephone.enrollRemoteRouter(originTelephone);
}
}

调用processNextPendingMessage()processNextPendingMessageFromDestination()分别处理目标邮箱和源邮箱的入站消息。现在,您可以从源链到目标链以及从目标链到源链进行跨链调用了:

    function testRemoteTelephoneCallFromOrigin() public {
// check behavior on origin
vm.expectEmit(true, true, true, false);
emit TelephoneRinging(destination, TypeCasts.bytes32ToAddress(destinationTelephone), "Hello!"); // example event on origin
originTelephone.callRemote(destination, TypeCasts.bytes32ToAddress(destinationTelephone), "Hello!");

// simulating message delivery origin -> destination
environment.processNextPendingMessage();

// check behavior on destination
assertEq(destinationTelephone.latestMessage(originTelephone) == "Hello!");
}

function testRemoteTelephoneCallFromDestination() public {
// check behavior on destination
vm.expectEmit(true, true, true, false);
emit TelephoneRinging(origin, TypeCasts.bytes32ToAddress(originTelephone), "Howdy!"); // example event on destination
destinationTelephone.callRemote(origin, TypeCasts.bytes32ToAddress(originTelephone), "Howdy!");

// simulating message delivery destination -> origin
environment.processNextPendingMessageFromDestination();

// check behavior on origin
assertEq(originTelephone.latestMessage(destinationTelephone) == "Howdy!");
}

如果您想为您的应用程序使用自己的ISM,可以通过将其传递给Router的 initialize 方法来覆盖Mailbox提供的 defaultIsm,如下所示:

contract CrosschainAppTest is Test {
// origin and destination domains (recommended to be the chainId)
uint32 origin = 1;
uint32 destination = 2;

function setUp() public {
...

TestIgp igp = new TestIgp(); // example InterchainGasPaymaster passed as the hook

// deploy your own ISM contracts to verify messages between originTelephone and destinationTelephone
TelephoneISM originIsm = new TelephoneISM(); // local ISM for origin
TelephoneISM destinationIsm = new TelephoneISM(); // local ISM for destination


originTelephone.initialize(address(igp), address(originIsm), msg.sender);
originTelephone.initialize(address(igp), address(destinationIsm), msg.sender);

...
}
}
tip

您可以在此处找到我们单元测试设置的示例:InterchainAccountRouterTest