Add V2 Mailbox and Message contracts (#1185)
parent
57d84c91f6
commit
322c18e8d7
@ -0,0 +1,225 @@ |
||||
// SPDX-License-Identifier: MIT OR Apache-2.0 |
||||
pragma solidity >=0.8.0; |
||||
|
||||
// ============ Internal Imports ============ |
||||
import {Versioned} from "./upgrade/Versioned.sol"; |
||||
import {MerkleLib} from "./libs/Merkle.sol"; |
||||
import {MessageV2} from "./libs/MessageV2.sol"; |
||||
import {TypeCasts} from "./libs/TypeCasts.sol"; |
||||
import {IMessageRecipient} from "../interfaces/IMessageRecipient.sol"; |
||||
import {IInterchainSecurityModule, ISpecifiesInterchainSecurityModule} from "../interfaces/IInterchainSecurityModule.sol"; |
||||
import {IMailboxV2} from "../interfaces/IMailboxV2.sol"; |
||||
|
||||
// ============ External Imports ============ |
||||
import {Address} from "@openzeppelin/contracts/utils/Address.sol"; |
||||
import {OwnableUpgradeable} from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; |
||||
import {ReentrancyGuardUpgradeable} from "@openzeppelin/contracts-upgradeable/security/ReentrancyGuardUpgradeable.sol"; |
||||
|
||||
contract MailboxV2 is |
||||
IMailboxV2, |
||||
OwnableUpgradeable, |
||||
ReentrancyGuardUpgradeable, |
||||
Versioned |
||||
{ |
||||
// ============ Libraries ============ |
||||
|
||||
using MerkleLib for MerkleLib.Tree; |
||||
using MessageV2 for bytes; |
||||
using TypeCasts for bytes32; |
||||
using TypeCasts for address; |
||||
|
||||
// ============ Constants ============ |
||||
|
||||
// Maximum bytes per message = 2 KiB (somewhat arbitrarily set to begin) |
||||
uint256 public constant MAX_MESSAGE_BODY_BYTES = 2 * 2**10; |
||||
// Domain of chain on which the contract is deployed |
||||
uint32 public immutable localDomain; |
||||
|
||||
// ============ Public Storage ============ |
||||
|
||||
// The default ISM, used if the recipient fails to specify one. |
||||
IInterchainSecurityModule public defaultModule; |
||||
// An incremental merkle tree used to store outbound message IDs. |
||||
MerkleLib.Tree public tree; |
||||
// Mapping of message ID to whether or not that message has been delivered. |
||||
mapping(bytes32 => bool) public delivered; |
||||
|
||||
// ============ Upgrade Gap ============ |
||||
|
||||
// gap for upgrade safety |
||||
uint256[47] private __GAP; |
||||
|
||||
// ============ Events ============ |
||||
|
||||
/** |
||||
* @notice Emitted when the default ISM is updated |
||||
* @param module The new default ISM |
||||
*/ |
||||
event DefaultModuleSet(address indexed module); |
||||
|
||||
/** |
||||
* @notice Emitted when a new message is dispatched via Hyperlane |
||||
* @param messageId The unique message identifier |
||||
* @param message Raw bytes of message |
||||
*/ |
||||
event Dispatch(bytes32 indexed messageId, bytes message); |
||||
|
||||
/** |
||||
* @notice Emitted when a Hyperlane message is delivered |
||||
* @param messageId The unique message identifier |
||||
*/ |
||||
event Process(bytes32 indexed messageId); |
||||
|
||||
// ============ Constructor ============ |
||||
|
||||
// solhint-disable-next-line no-empty-blocks |
||||
constructor(uint32 _localDomain) { |
||||
localDomain = _localDomain; |
||||
} |
||||
|
||||
// ============ Initializer ============ |
||||
|
||||
function initialize(address _defaultModule) external initializer { |
||||
__ReentrancyGuard_init(); |
||||
__Ownable_init(); |
||||
_setDefaultModule(_defaultModule); |
||||
} |
||||
|
||||
// ============ External Functions ============ |
||||
|
||||
/** |
||||
* @notice Sets the default ISM for the Mailbox. |
||||
* @param _module The new default ISM. Must be a contract. |
||||
*/ |
||||
function setDefaultModule(address _module) external onlyOwner { |
||||
_setDefaultModule(_module); |
||||
} |
||||
|
||||
/** |
||||
* @notice Dispatches a message to the destination domain & recipient. |
||||
* @param _destinationDomain Domain of destination chain |
||||
* @param _recipientAddress Address of recipient on destination chain as bytes32 |
||||
* @param _messageBody Raw bytes content of message body |
||||
* @return The message ID inserted into the Mailbox's merkle tree |
||||
*/ |
||||
function dispatch( |
||||
uint32 _destinationDomain, |
||||
bytes32 _recipientAddress, |
||||
bytes calldata _messageBody |
||||
) external override returns (bytes32) { |
||||
require(_messageBody.length <= MAX_MESSAGE_BODY_BYTES, "msg too long"); |
||||
// Format the message into packed bytes. |
||||
bytes memory _message = MessageV2.formatMessage( |
||||
VERSION, |
||||
count(), |
||||
localDomain, |
||||
msg.sender.addressToBytes32(), |
||||
_destinationDomain, |
||||
_recipientAddress, |
||||
_messageBody |
||||
); |
||||
|
||||
// Insert the message ID into the merkle tree. |
||||
bytes32 _id = _message.id(); |
||||
tree.insert(_id); |
||||
emit Dispatch(_id, _message); |
||||
return _id; |
||||
} |
||||
|
||||
/** |
||||
* @notice Attempts to deliver `_message` to its recipient. Verifies |
||||
* `_message` via the recipient's ISM using the provided `_metadata`. |
||||
* @param _metadata Metadata used by the ISM to verify `_message`. |
||||
* @param _message Formatted Hyperlane message (refer to Message.sol). |
||||
*/ |
||||
function process(bytes calldata _metadata, bytes calldata _message) |
||||
external |
||||
override |
||||
nonReentrant |
||||
{ |
||||
// Check that the message was intended for this mailbox. |
||||
require(_message.version() == VERSION, "!version"); |
||||
require(_message.destination() == localDomain, "!destination"); |
||||
|
||||
// Check that the message hasn't already been delivered. |
||||
bytes32 _id = _message.id(); |
||||
require(delivered[_id] == false, "delivered"); |
||||
delivered[_id] = true; |
||||
|
||||
// Verify the message via the ISM. |
||||
IInterchainSecurityModule _ism = _recipientModule( |
||||
ISpecifiesInterchainSecurityModule(_message.recipientAddress()) |
||||
); |
||||
require(_ism.verify(_metadata, _message), "!module"); |
||||
|
||||
// Deliver the message to the recipient. |
||||
uint32 _origin = _message.origin(); |
||||
IMessageRecipient(_message.recipientAddress()).handle( |
||||
_origin, |
||||
_message.sender(), |
||||
_message.body() |
||||
); |
||||
emit Process(_id); |
||||
} |
||||
|
||||
// ============ Public Functions ============ |
||||
|
||||
/** |
||||
* @notice Calculates and returns tree's current root |
||||
*/ |
||||
function root() public view returns (bytes32) { |
||||
return tree.root(); |
||||
} |
||||
|
||||
/** |
||||
* @notice Returns the number of inserted leaves in the tree |
||||
*/ |
||||
function count() public view returns (uint256) { |
||||
return tree.count; |
||||
} |
||||
|
||||
/** |
||||
* @notice Returns a checkpoint representing the current merkle tree. |
||||
* @return root The root of the Outbox's merkle tree. |
||||
* @return index The index of the last element in the tree. |
||||
*/ |
||||
function latestCheckpoint() public view returns (bytes32, uint256) { |
||||
return (root(), count() - 1); |
||||
} |
||||
|
||||
// ============ Internal Functions ============ |
||||
|
||||
/** |
||||
* @notice Sets the default ISM for the Mailbox. |
||||
* @param _module The new default ISM. Must be a contract. |
||||
*/ |
||||
function _setDefaultModule(address _module) internal { |
||||
require(Address.isContract(_module), "!contract"); |
||||
defaultModule = IInterchainSecurityModule(_module); |
||||
emit DefaultModuleSet(_module); |
||||
} |
||||
|
||||
/** |
||||
* @notice Returns the ISM to use for the recipient, defaulting to the |
||||
* default ISM if none is specified. |
||||
* @param _recipient The message recipient whose ISM should be returned. |
||||
* @return The ISM to use for `_recipient`. |
||||
*/ |
||||
function _recipientModule(ISpecifiesInterchainSecurityModule _recipient) |
||||
internal |
||||
view |
||||
returns (IInterchainSecurityModule) |
||||
{ |
||||
// Use a default interchainSecurityModule if one is not specified by the |
||||
// recipient. |
||||
// This is useful for backwards compatibility and for convenience as |
||||
// recipients are not mandated to specify an ISM. |
||||
try _recipient.interchainSecurityModule() returns ( |
||||
IInterchainSecurityModule _val |
||||
) { |
||||
return _val; |
||||
} catch { |
||||
return defaultModule; |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,163 @@ |
||||
// SPDX-License-Identifier: MIT OR Apache-2.0 |
||||
pragma solidity >=0.8.0; |
||||
|
||||
import {TypeCasts} from "./TypeCasts.sol"; |
||||
|
||||
/** |
||||
* @title Hyperlane Message Library |
||||
* @notice Library for formatted messages used by Mailbox |
||||
**/ |
||||
library MessageV2 { |
||||
using TypeCasts for bytes32; |
||||
|
||||
uint256 private constant VERSION_OFFSET = 0; |
||||
uint256 private constant NONCE_OFFSET = 1; |
||||
uint256 private constant ORIGIN_OFFSET = 33; |
||||
uint256 private constant SENDER_OFFSET = 37; |
||||
uint256 private constant DESTINATION_OFFSET = 69; |
||||
uint256 private constant RECIPIENT_OFFSET = 73; |
||||
uint256 private constant BODY_OFFSET = 105; |
||||
|
||||
/** |
||||
* @notice Returns formatted (packed) Hyperlane message with provided fields |
||||
* @dev This function should only be used in memory message construction. |
||||
* @param _version The version of the origin and destination Mailboxes |
||||
* @param _nonce A nonce to uniquely identify the message on its origin chain |
||||
* @param _originDomain Domain of origin chain |
||||
* @param _sender Address of sender as bytes32 |
||||
* @param _destinationDomain Domain of destination chain |
||||
* @param _recipient Address of recipient on destination chain as bytes32 |
||||
* @param _messageBody Raw bytes of message body |
||||
* @return Formatted message |
||||
*/ |
||||
function formatMessage( |
||||
uint8 _version, |
||||
uint256 _nonce, |
||||
uint32 _originDomain, |
||||
bytes32 _sender, |
||||
uint32 _destinationDomain, |
||||
bytes32 _recipient, |
||||
bytes calldata _messageBody |
||||
) internal pure returns (bytes memory) { |
||||
return |
||||
abi.encodePacked( |
||||
_version, |
||||
_nonce, |
||||
_originDomain, |
||||
_sender, |
||||
_destinationDomain, |
||||
_recipient, |
||||
_messageBody |
||||
); |
||||
} |
||||
|
||||
/** |
||||
* @notice Returns the message ID. |
||||
* @param _message ABI encoded Hyperlane message. |
||||
* @return ID of `_message` |
||||
*/ |
||||
function id(bytes memory _message) internal pure returns (bytes32) { |
||||
return keccak256(_message); |
||||
} |
||||
|
||||
/** |
||||
* @notice Returns the message version. |
||||
* @param _message ABI encoded Hyperlane message. |
||||
* @return Version of `_message` |
||||
*/ |
||||
function version(bytes calldata _message) internal pure returns (uint8) { |
||||
return uint8(bytes1(_message[VERSION_OFFSET:NONCE_OFFSET])); |
||||
} |
||||
|
||||
/** |
||||
* @notice Returns the message nonce. |
||||
* @param _message ABI encoded Hyperlane message. |
||||
* @return Nonce of `_message` |
||||
*/ |
||||
function nonce(bytes calldata _message) internal pure returns (uint256) { |
||||
return uint256(bytes32(_message[NONCE_OFFSET:ORIGIN_OFFSET])); |
||||
} |
||||
|
||||
/** |
||||
* @notice Returns the message origin domain. |
||||
* @param _message ABI encoded Hyperlane message. |
||||
* @return Origin domain of `_message` |
||||
*/ |
||||
function origin(bytes calldata _message) internal pure returns (uint32) { |
||||
return uint32(bytes4(_message[ORIGIN_OFFSET:SENDER_OFFSET])); |
||||
} |
||||
|
||||
/** |
||||
* @notice Returns the message sender as bytes32. |
||||
* @param _message ABI encoded Hyperlane message. |
||||
* @return Sender of `_message` as bytes32 |
||||
*/ |
||||
function sender(bytes calldata _message) internal pure returns (bytes32) { |
||||
return bytes32(_message[SENDER_OFFSET:DESTINATION_OFFSET]); |
||||
} |
||||
|
||||
/** |
||||
* @notice Returns the message sender as address. |
||||
* @param _message ABI encoded Hyperlane message. |
||||
* @return Sender of `_message` as address |
||||
*/ |
||||
function senderAddress(bytes calldata _message) |
||||
internal |
||||
pure |
||||
returns (address) |
||||
{ |
||||
return sender(_message).bytes32ToAddress(); |
||||
} |
||||
|
||||
/** |
||||
* @notice Returns the message destination domain. |
||||
* @param _message ABI encoded Hyperlane message. |
||||
* @return Destination domain of `_message` |
||||
*/ |
||||
function destination(bytes calldata _message) |
||||
internal |
||||
pure |
||||
returns (uint32) |
||||
{ |
||||
return uint32(bytes4(_message[DESTINATION_OFFSET:RECIPIENT_OFFSET])); |
||||
} |
||||
|
||||
/** |
||||
* @notice Returns the message recipient as bytes32. |
||||
* @param _message ABI encoded Hyperlane message. |
||||
* @return Recipient of `_message` as bytes32 |
||||
*/ |
||||
function recipient(bytes calldata _message) |
||||
internal |
||||
pure |
||||
returns (bytes32) |
||||
{ |
||||
return bytes32(_message[RECIPIENT_OFFSET:BODY_OFFSET]); |
||||
} |
||||
|
||||
/** |
||||
* @notice Returns the message recipient as address. |
||||
* @param _message ABI encoded Hyperlane message. |
||||
* @return Recipient of `_message` as address |
||||
*/ |
||||
function recipientAddress(bytes calldata _message) |
||||
internal |
||||
pure |
||||
returns (address) |
||||
{ |
||||
return recipient(_message).bytes32ToAddress(); |
||||
} |
||||
|
||||
/** |
||||
* @notice Returns the message body. |
||||
* @param _message ABI encoded Hyperlane message. |
||||
* @return Body of `_message` |
||||
*/ |
||||
function body(bytes calldata _message) |
||||
internal |
||||
pure |
||||
returns (bytes calldata) |
||||
{ |
||||
return bytes(_message[BODY_OFFSET:]); |
||||
} |
||||
} |
@ -0,0 +1,76 @@ |
||||
// SPDX-License-Identifier: MIT OR Apache-2.0 |
||||
pragma solidity >=0.6.11; |
||||
|
||||
import {MessageV2} from "../libs/MessageV2.sol"; |
||||
|
||||
contract TestMessageV2 { |
||||
using MessageV2 for bytes; |
||||
|
||||
function version(bytes calldata _message) |
||||
external |
||||
pure |
||||
returns (uint32 _version) |
||||
{ |
||||
return _message.version(); |
||||
} |
||||
|
||||
function nonce(bytes calldata _message) |
||||
external |
||||
pure |
||||
returns (uint256 _nonce) |
||||
{ |
||||
return _message.nonce(); |
||||
} |
||||
|
||||
function body(bytes calldata _message) |
||||
external |
||||
pure |
||||
returns (bytes calldata _body) |
||||
{ |
||||
return _message.body(); |
||||
} |
||||
|
||||
function origin(bytes calldata _message) |
||||
external |
||||
pure |
||||
returns (uint32 _origin) |
||||
{ |
||||
return _message.origin(); |
||||
} |
||||
|
||||
function sender(bytes calldata _message) |
||||
external |
||||
pure |
||||
returns (bytes32 _sender) |
||||
{ |
||||
return _message.sender(); |
||||
} |
||||
|
||||
function destination(bytes calldata _message) |
||||
external |
||||
pure |
||||
returns (uint32 _destination) |
||||
{ |
||||
return _message.destination(); |
||||
} |
||||
|
||||
function recipient(bytes calldata _message) |
||||
external |
||||
pure |
||||
returns (bytes32 _recipient) |
||||
{ |
||||
return _message.recipient(); |
||||
} |
||||
|
||||
function recipientAddress(bytes calldata _message) |
||||
external |
||||
pure |
||||
returns (address _recipient) |
||||
{ |
||||
return _message.recipientAddress(); |
||||
} |
||||
|
||||
function id(bytes calldata _message) external pure returns (bytes32) { |
||||
return _message.id(); |
||||
} |
||||
} |
@ -0,0 +1,20 @@ |
||||
// SPDX-License-Identifier: MIT OR Apache-2.0 |
||||
pragma solidity >=0.8.0; |
||||
|
||||
import {IInterchainSecurityModule} from "../../interfaces/IInterchainSecurityModule.sol"; |
||||
|
||||
contract TestModule is IInterchainSecurityModule { |
||||
bool public accept; |
||||
|
||||
function setAccept(bool _val) external { |
||||
accept = _val; |
||||
} |
||||
|
||||
function verify(bytes calldata, bytes calldata) |
||||
external |
||||
view |
||||
returns (bool) |
||||
{ |
||||
return accept; |
||||
} |
||||
} |
@ -0,0 +1,16 @@ |
||||
// SPDX-License-Identifier: MIT OR Apache-2.0 |
||||
pragma solidity >=0.6.11; |
||||
|
||||
interface IInterchainSecurityModule { |
||||
// Called by the Mailbox to determine whether or not the message should be accepted. |
||||
function verify(bytes calldata _metadata, bytes calldata _message) |
||||
external |
||||
returns (bool); |
||||
} |
||||
|
||||
interface ISpecifiesInterchainSecurityModule { |
||||
function interchainSecurityModule() |
||||
external |
||||
view |
||||
returns (IInterchainSecurityModule); |
||||
} |
@ -0,0 +1,21 @@ |
||||
// SPDX-License-Identifier: MIT OR Apache-2.0 |
||||
pragma solidity >=0.8.0; |
||||
|
||||
interface IMailboxV2 { |
||||
function localDomain() external view returns (uint32); |
||||
|
||||
function dispatch( |
||||
uint32 _destinationDomain, |
||||
bytes32 _recipientAddress, |
||||
bytes calldata _messageBody |
||||
) external returns (bytes32); |
||||
|
||||
function process(bytes calldata _metadata, bytes calldata _message) |
||||
external; |
||||
|
||||
function count() external view returns (uint256); |
||||
|
||||
function root() external view returns (bytes32); |
||||
|
||||
function latestCheckpoint() external view returns (bytes32, uint256); |
||||
} |
@ -0,0 +1,229 @@ |
||||
import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers'; |
||||
import { expect } from 'chai'; |
||||
import { ethers } from 'hardhat'; |
||||
|
||||
import { utils } from '@hyperlane-xyz/utils'; |
||||
|
||||
import { |
||||
BadRecipient1__factory, |
||||
BadRecipient2__factory, |
||||
BadRecipient3__factory, |
||||
BadRecipient5__factory, |
||||
BadRecipient6__factory, |
||||
MailboxV2, |
||||
MailboxV2__factory, |
||||
TestModule, |
||||
TestModule__factory, |
||||
TestRecipient__factory, |
||||
} from '../types'; |
||||
|
||||
import { inferMessageValues } from './lib/mailboxes'; |
||||
|
||||
const originDomain = 1000; |
||||
const destDomain = 2000; |
||||
const ONLY_OWNER_REVERT_MSG = 'Ownable: caller is not the owner'; |
||||
|
||||
describe('Mailbox', async () => { |
||||
let mailbox: MailboxV2, |
||||
module: TestModule, |
||||
signer: SignerWithAddress, |
||||
nonOwner: SignerWithAddress; |
||||
|
||||
beforeEach(async () => { |
||||
[signer, nonOwner] = await ethers.getSigners(); |
||||
const moduleFactory = new TestModule__factory(signer); |
||||
module = await moduleFactory.deploy(); |
||||
const mailboxFactory = new MailboxV2__factory(signer); |
||||
mailbox = await mailboxFactory.deploy(originDomain); |
||||
await mailbox.initialize(module.address); |
||||
}); |
||||
|
||||
it('Cannot be initialized twice', async () => { |
||||
await expect(mailbox.initialize(module.address)).to.be.revertedWith( |
||||
'Initializable: contract is already initialized', |
||||
); |
||||
}); |
||||
|
||||
describe('#dispatch', () => { |
||||
let recipient: SignerWithAddress, message: string, id: string, body: string; |
||||
before(async () => { |
||||
[, recipient] = await ethers.getSigners(); |
||||
({ message, id, body } = await inferMessageValues( |
||||
mailbox, |
||||
signer.address, |
||||
destDomain, |
||||
recipient.address, |
||||
'message', |
||||
)); |
||||
}); |
||||
|
||||
it('Does not dispatch too large messages', async () => { |
||||
const longMessage = `0x${Buffer.alloc(3000).toString('hex')}`; |
||||
await expect( |
||||
mailbox.dispatch( |
||||
destDomain, |
||||
utils.addressToBytes32(recipient.address), |
||||
longMessage, |
||||
), |
||||
).to.be.revertedWith('msg too long'); |
||||
}); |
||||
|
||||
it('Dispatches a message', async () => { |
||||
// Send message with signer address as msg.sender
|
||||
await expect( |
||||
mailbox |
||||
.connect(signer) |
||||
.dispatch( |
||||
destDomain, |
||||
utils.addressToBytes32(recipient.address), |
||||
body, |
||||
), |
||||
) |
||||
.to.emit(mailbox, 'Dispatch') |
||||
.withArgs(id, message); |
||||
}); |
||||
|
||||
it('Returns the id of the dispatched message', async () => { |
||||
const actualId = await mailbox |
||||
.connect(signer) |
||||
.callStatic.dispatch( |
||||
destDomain, |
||||
utils.addressToBytes32(recipient.address), |
||||
body, |
||||
); |
||||
|
||||
expect(actualId).equals(id); |
||||
}); |
||||
}); |
||||
|
||||
describe('#process', () => { |
||||
const badRecipientFactories = [ |
||||
BadRecipient1__factory, |
||||
BadRecipient2__factory, |
||||
BadRecipient3__factory, |
||||
BadRecipient5__factory, |
||||
BadRecipient6__factory, |
||||
]; |
||||
let message: string, id: string, recipient: string; |
||||
|
||||
beforeEach(async () => { |
||||
await module.setAccept(true); |
||||
const recipientF = new TestRecipient__factory(signer); |
||||
recipient = utils.addressToBytes32((await recipientF.deploy()).address); |
||||
({ message, id } = await inferMessageValues( |
||||
mailbox, |
||||
signer.address, |
||||
originDomain, |
||||
recipient, |
||||
'message', |
||||
)); |
||||
}); |
||||
|
||||
it('processes a message', async () => { |
||||
await expect(mailbox.process('0x', message)).to.emit(mailbox, 'Process'); |
||||
expect(await mailbox.delivered(id)).to.be.true; |
||||
}); |
||||
|
||||
it('Rejects an already-processed message', async () => { |
||||
await expect(mailbox.process('0x', message)).to.emit(mailbox, 'Process'); |
||||
|
||||
// Try to process message again
|
||||
await expect(mailbox.process('0x', message)).to.be.revertedWith( |
||||
'delivered', |
||||
); |
||||
}); |
||||
|
||||
it('Fails to process message when rejected by module', async () => { |
||||
await module.setAccept(false); |
||||
await expect(mailbox.process('0x', message)).to.be.revertedWith( |
||||
'!module', |
||||
); |
||||
}); |
||||
|
||||
for (let i = 0; i < badRecipientFactories.length; i++) { |
||||
it(`Fails to process a message for a badly implemented recipient (${ |
||||
i + 1 |
||||
})`, async () => {
|
||||
const factory = new badRecipientFactories[i](signer); |
||||
const badRecipient = await factory.deploy(); |
||||
|
||||
({ message } = await inferMessageValues( |
||||
mailbox, |
||||
signer.address, |
||||
originDomain, |
||||
badRecipient.address, |
||||
'message', |
||||
)); |
||||
await expect(mailbox.process('0x', message)).to.be.reverted; |
||||
}); |
||||
} |
||||
|
||||
// TODO: Fails to process with wrong version..
|
||||
it('Fails to process message with wrong destination Domain', async () => { |
||||
({ message } = await inferMessageValues( |
||||
mailbox, |
||||
signer.address, |
||||
originDomain + 1, |
||||
recipient, |
||||
'message', |
||||
)); |
||||
|
||||
await expect(mailbox.process('0x', message)).to.be.revertedWith( |
||||
'!destination', |
||||
); |
||||
}); |
||||
|
||||
it('Fails to process message with wrong version', async () => { |
||||
const version = await mailbox.VERSION(); |
||||
({ message } = await inferMessageValues( |
||||
mailbox, |
||||
signer.address, |
||||
originDomain, |
||||
recipient, |
||||
'message', |
||||
version + 1, |
||||
)); |
||||
await expect(mailbox.process('0x', message)).to.be.revertedWith( |
||||
'!version', |
||||
); |
||||
}); |
||||
|
||||
it('Fails to process message sent to a non-existent contract address', async () => { |
||||
({ message } = await inferMessageValues( |
||||
mailbox, |
||||
signer.address, |
||||
originDomain, |
||||
'0x1234567890123456789012345678901234567890', // non-existent contract address
|
||||
'message', |
||||
)); |
||||
await expect(mailbox.process('0x', message)).to.be.reverted; |
||||
}); |
||||
}); |
||||
|
||||
describe('#setDefaultModule', async () => { |
||||
let newModule: TestModule; |
||||
before(async () => { |
||||
const moduleFactory = new TestModule__factory(signer); |
||||
newModule = await moduleFactory.deploy(); |
||||
}); |
||||
|
||||
it('Allows owner to update the default ISM', async () => { |
||||
await expect(mailbox.setDefaultModule(newModule.address)) |
||||
.to.emit(mailbox, 'DefaultModuleSet') |
||||
.withArgs(newModule.address); |
||||
expect(await mailbox.defaultModule()).to.equal(newModule.address); |
||||
}); |
||||
|
||||
it('Does not allow non-owner to update the default ISM', async () => { |
||||
await expect( |
||||
mailbox.connect(nonOwner).setDefaultModule(newModule.address), |
||||
).to.be.revertedWith(ONLY_OWNER_REVERT_MSG); |
||||
}); |
||||
|
||||
it('Reverts if the provided ISM is not a contract', async () => { |
||||
await expect(mailbox.setDefaultModule(signer.address)).to.be.revertedWith( |
||||
'!contract', |
||||
); |
||||
}); |
||||
}); |
||||
}); |
@ -0,0 +1,95 @@ |
||||
import { expect } from 'chai'; |
||||
import { ethers } from 'hardhat'; |
||||
|
||||
import { utils } from '@hyperlane-xyz/utils'; |
||||
|
||||
import { TestMessageV2, TestMessageV2__factory } from '../types'; |
||||
|
||||
const testCases = require('../../vectors/message.json'); |
||||
|
||||
const remoteDomain = 1000; |
||||
const localDomain = 2000; |
||||
const version = 0; |
||||
const nonce = 11; |
||||
|
||||
describe('MessageV2', async () => { |
||||
let messageLib: TestMessageV2; |
||||
|
||||
before(async () => { |
||||
const [signer] = await ethers.getSigners(); |
||||
|
||||
const Message = new TestMessageV2__factory(signer); |
||||
messageLib = await Message.deploy(); |
||||
}); |
||||
|
||||
it('Returns fields from a message', async () => { |
||||
const [sender, recipient] = await ethers.getSigners(); |
||||
const body = ethers.utils.formatBytes32String('message'); |
||||
|
||||
const message = utils.formatMessageV2( |
||||
version, |
||||
nonce, |
||||
remoteDomain, |
||||
sender.address, |
||||
localDomain, |
||||
recipient.address, |
||||
body, |
||||
); |
||||
|
||||
expect(await messageLib.version(message)).to.equal(version); |
||||
expect(await messageLib.nonce(message)).to.equal(nonce); |
||||
expect(await messageLib.origin(message)).to.equal(remoteDomain); |
||||
expect(await messageLib.sender(message)).to.equal( |
||||
utils.addressToBytes32(sender.address), |
||||
); |
||||
expect(await messageLib.destination(message)).to.equal(localDomain); |
||||
expect(await messageLib.recipient(message)).to.equal( |
||||
utils.addressToBytes32(recipient.address), |
||||
); |
||||
expect(await messageLib.recipientAddress(message)).to.equal( |
||||
recipient.address, |
||||
); |
||||
expect(await messageLib.body(message)).to.equal(body); |
||||
}); |
||||
|
||||
// TODO: Update rust output to new message format
|
||||
it.skip('Matches Rust-output HyperlaneMessage and leaf', async () => { |
||||
const origin = 1000; |
||||
const sender = '0x1111111111111111111111111111111111111111'; |
||||
const destination = 2000; |
||||
const recipient = '0x2222222222222222222222222222222222222222'; |
||||
const body = '0x1234'; |
||||
|
||||
const hyperlaneMessage = utils.formatMessageV2( |
||||
version, |
||||
nonce, |
||||
origin, |
||||
sender, |
||||
destination, |
||||
recipient, |
||||
body, |
||||
); |
||||
|
||||
const { |
||||
origin: testOrigin, |
||||
sender: testSender, |
||||
destination: testDestination, |
||||
recipient: testRecipient, |
||||
body: testBody, |
||||
messageHash, |
||||
} = testCases[0]; |
||||
|
||||
expect(await messageLib.origin(hyperlaneMessage)).to.equal(testOrigin); |
||||
expect(await messageLib.sender(hyperlaneMessage)).to.equal(testSender); |
||||
expect(await messageLib.destination(hyperlaneMessage)).to.equal( |
||||
testDestination, |
||||
); |
||||
expect(await messageLib.recipient(hyperlaneMessage)).to.equal( |
||||
testRecipient, |
||||
); |
||||
expect(await messageLib.body(hyperlaneMessage)).to.equal( |
||||
ethers.utils.hexlify(testBody), |
||||
); |
||||
expect(utils.messageIdV2(hyperlaneMessage)).to.equal(messageHash); |
||||
}); |
||||
}); |
Loading…
Reference in new issue