Add V2 Mailbox and Message contracts (#1185)

v2-2
Asa Oines 2 years ago committed by GitHub
parent 57d84c91f6
commit 322c18e8d7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 23
      .github/workflows/node.yml
  2. 2
      .github/workflows/rust.yml
  3. 225
      solidity/contracts/MailboxV2.sol
  4. 163
      solidity/contracts/libs/MessageV2.sol
  5. 76
      solidity/contracts/test/TestMessageV2.sol
  6. 20
      solidity/contracts/test/TestModule.sol
  7. 16
      solidity/interfaces/IInterchainSecurityModule.sol
  8. 21
      solidity/interfaces/IMailboxV2.sol
  9. 32
      solidity/test/lib/mailboxes.ts
  10. 229
      solidity/test/mailboxv2.test.ts
  11. 95
      solidity/test/messagev2.test.ts
  12. 32
      typescript/utils/src/utils.ts

@ -1,11 +1,11 @@
name: node name: node
on: on:
# Triggers the workflow on push or pull request events but only for the main branch # Triggers the workflow on push or pull request events but only for the main and v2 branches
push: push:
branches: [main] branches: [main, v2]
pull_request: pull_request:
branches: [main] branches: [main, v2]
# Allows you to run this workflow manually from the Actions tab # Allows you to run this workflow manually from the Actions tab
workflow_dispatch: workflow_dispatch:
@ -24,14 +24,13 @@ jobs:
# Check out the lockfile from main, reinstall, and then # Check out the lockfile from main, reinstall, and then
# verify the lockfile matches what was committed. # verify the lockfile matches what was committed.
run: | run: |
yarn install yarn install
CHANGES=$(git status -s) CHANGES=$(git status -s)
if [[ ! -z $CHANGES ]]; then if [[ ! -z $CHANGES ]]; then
echo "Changes found: $CHANGES" echo "Changes found: $CHANGES"
git diff git diff
exit 1 exit 1
fi fi
yarn-build: yarn-build:
runs-on: ubuntu-latest runs-on: ubuntu-latest
@ -95,7 +94,7 @@ jobs:
test-sol: test-sol:
env: env:
ETHERSCAN_API_KEY: "" ETHERSCAN_API_KEY: ''
runs-on: ubuntu-latest runs-on: ubuntu-latest
needs: [yarn-build] needs: [yarn-build]

@ -2,7 +2,7 @@ name: rust
on: on:
push: push:
branches: [main] branches: [main, v2]
pull_request: pull_request:
paths: paths:
- 'rust/**' - 'rust/**'

@ -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);
}

@ -3,7 +3,7 @@ import { ethers } from 'ethers';
import { utils } from '@hyperlane-xyz/utils'; import { utils } from '@hyperlane-xyz/utils';
import { TestOutbox } from '../../types'; import { MailboxV2, TestOutbox } from '../../types';
import { DispatchEvent } from '../../types/contracts/Outbox'; import { DispatchEvent } from '../../types/contracts/Outbox';
export const dispatchMessage = async ( export const dispatchMessage = async (
@ -55,3 +55,33 @@ export interface MerkleProof {
index: number; index: number;
message: string; message: string;
} }
export const inferMessageValues = async (
mailbox: MailboxV2,
sender: string,
destination: number,
recipient: string,
messageStr: string,
version?: number,
) => {
const body = utils.ensure0x(
Buffer.from(ethers.utils.toUtf8Bytes(messageStr)).toString('hex'),
);
const nonce = await mailbox.count();
const localDomain = await mailbox.localDomain();
const message = utils.formatMessageV2(
version ?? (await mailbox.VERSION()),
nonce,
localDomain,
sender,
destination,
recipient,
body,
);
const id = utils.messageIdV2(message);
return {
message,
id,
body,
};
};

@ -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);
});
});

@ -1,4 +1,4 @@
import { ethers, utils } from 'ethers'; import { BigNumber, ethers, utils } from 'ethers';
import { Checkpoint } from './types'; import { Checkpoint } from './types';
import { Address, Domain, HexString, ParsedMessage } from './types'; import { Address, Domain, HexString, ParsedMessage } from './types';
@ -55,6 +55,36 @@ export const formatMessage = (
); );
}; };
export const formatMessageV2 = (
version: number | BigNumber,
nonce: number | BigNumber,
originDomain: Domain,
senderAddr: Address,
destinationDomain: Domain,
recipientAddr: Address,
body: HexString,
): string => {
senderAddr = addressToBytes32(senderAddr);
recipientAddr = addressToBytes32(recipientAddr);
return ethers.utils.solidityPack(
['uint8', 'uint256', 'uint32', 'bytes32', 'uint32', 'bytes32', 'bytes'],
[
version,
nonce,
originDomain,
senderAddr,
destinationDomain,
recipientAddr,
body,
],
);
};
export function messageIdV2(message: HexString): string {
return ethers.utils.solidityKeccak256(['bytes'], [message]);
}
/** /**
* Parse a serialized Abacus message from raw bytes. * Parse a serialized Abacus message from raw bytes.
* *

Loading…
Cancel
Save