The home for Hyperlane core contracts, sdk packages, and other infrastructure
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
hyperlane-monorepo/solidity/test/isms/PolygonPosIsm.t.sol

368 lines
12 KiB

// SPDX-License-Identifier: MIT or Apache-2.0
pragma solidity ^0.8.13;
import {Test} from "forge-std/Test.sol";
import {LibBit} from "../../contracts/libs/LibBit.sol";
import {TypeCasts} from "../../contracts/libs/TypeCasts.sol";
import {StandardHookMetadata} from "../../contracts/hooks/libs/StandardHookMetadata.sol";
import {StandardHookMetadata} from "../../contracts/hooks/libs/StandardHookMetadata.sol";
import {AbstractMessageIdAuthorizedIsm} from "../../contracts/isms/hook/AbstractMessageIdAuthorizedIsm.sol";
import {TestMailbox} from "../../contracts/test/TestMailbox.sol";
import {Message} from "../../contracts/libs/Message.sol";
import {MessageUtils} from "./IsmTestUtils.sol";
import {PolygonPosIsm} from "../../contracts/isms/hook/PolygonPosIsm.sol";
import {PolygonPosHook} from "../../contracts/hooks/PolygonPosHook.sol";
import {TestRecipient} from "../../contracts/test/TestRecipient.sol";
import {NotCrossChainCall} from "@openzeppelin/contracts/crosschain/errors.sol";
interface IStateSender {
function counter() external view returns (uint256);
}
interface FxChild {
function onStateReceive(uint256 stateId, bytes calldata data) external;
}
contract PolygonPosIsmTest is Test {
using LibBit for uint256;
using TypeCasts for address;
using MessageUtils for bytes;
uint256 internal mainnetFork;
uint256 internal polygonPosFork;
address internal constant POLYGON_CROSSCHAIN_SYSTEM_ADDR =
0x0000000000000000000000000000000000001001;
address internal constant MUMBAI_FX_CHILD =
0xCf73231F28B7331BBe3124B907840A94851f9f11;
address internal constant GOERLI_CHECKPOINT_MANAGER =
0x2890bA17EfE978480615e330ecB65333b880928e;
address internal constant GOERLI_FX_ROOT =
0x3d1d3E34f7fB6D26245E6640E1c50710eFFf15bA;
address internal constant MAINNET_FX_CHILD =
0x8397259c983751DAf40400790063935a11afa28a;
address internal constant MAINNET_CHECKPOINT_MANAGER =
0x86E4Dc95c7FBdBf52e33D563BbDB00823894C287;
address internal constant MAINNET_FX_ROOT =
0xfe5e5D361b2ad62c541bAb87C45a0B9B018389a2;
address internal constant MAINNET_STATE_SENDER =
0x28e4F3a7f651294B9564800b2D01f35189A5bFbE;
uint8 internal constant POLYGON_POS_VERSION = 0;
uint8 internal constant HYPERLANE_VERSION = 1;
TestMailbox internal l1Mailbox;
PolygonPosIsm internal polygonPosISM;
PolygonPosHook internal polygonPosHook;
FxChild internal fxChild;
TestRecipient internal testRecipient;
bytes internal testMessage =
abi.encodePacked("Hello from the other chain!");
bytes internal testMetadata =
StandardHookMetadata.overrideRefundAddress(address(this));
bytes internal encodedMessage;
bytes32 internal messageId;
uint32 internal constant MAINNET_DOMAIN = 1;
uint32 internal constant POLYGON_POS_DOMAIN = 137;
event StateSynced(
uint256 indexed id,
address indexed contractAddress,
bytes data
);
event ReceivedMessage(bytes32 indexed messageId);
function setUp() public {
// block numbers to fork from, chain data is cached to ../../forge-cache/
mainnetFork = vm.createFork(vm.rpcUrl("mainnet"), 18_718_401);
polygonPosFork = vm.createFork(vm.rpcUrl("polygon"), 50_760_479);
testRecipient = new TestRecipient();
encodedMessage = _encodeTestMessage();
messageId = Message.id(encodedMessage);
}
///////////////////////////////////////////////////////////////////
/// SETUP ///
///////////////////////////////////////////////////////////////////
function deployPolygonPosHook() public {
vm.selectFork(mainnetFork);
l1Mailbox = new TestMailbox(MAINNET_DOMAIN);
polygonPosHook = new PolygonPosHook(
address(l1Mailbox),
POLYGON_POS_DOMAIN,
TypeCasts.addressToBytes32(address(polygonPosISM)),
MAINNET_CHECKPOINT_MANAGER,
MAINNET_FX_ROOT
);
polygonPosHook.setFxChildTunnel(address(polygonPosISM));
vm.makePersistent(address(polygonPosHook));
}
function deployPolygonPosIsm() public {
vm.selectFork(polygonPosFork);
fxChild = FxChild(MAINNET_FX_CHILD);
polygonPosISM = new PolygonPosIsm(MAINNET_FX_CHILD);
vm.makePersistent(address(polygonPosISM));
}
function deployAll() public {
deployPolygonPosIsm();
deployPolygonPosHook();
vm.selectFork(polygonPosFork);
polygonPosISM.setAuthorizedHook(
TypeCasts.addressToBytes32(address(polygonPosHook))
);
}
///////////////////////////////////////////////////////////////////
/// FORK TESTS ///
///////////////////////////////////////////////////////////////////
/* ============ hook.quoteDispatch ============ */
function testFork_quoteDispatch() public {
deployAll();
vm.selectFork(mainnetFork);
assertEq(polygonPosHook.quoteDispatch(testMetadata, encodedMessage), 0);
}
/* ============ hook.postDispatch ============ */
function testFork_postDispatch() public {
deployAll();
vm.selectFork(mainnetFork);
bytes memory encodedHookData = abi.encodeCall(
AbstractMessageIdAuthorizedIsm.verifyMessageId,
(messageId)
);
l1Mailbox.updateLatestDispatchedId(messageId);
IStateSender stateSender = IStateSender(MAINNET_STATE_SENDER);
vm.expectEmit(true, false, false, true);
emit StateSynced(
(stateSender.counter() + 1),
MAINNET_FX_CHILD,
abi.encode(
TypeCasts.addressToBytes32(address(polygonPosHook)),
TypeCasts.addressToBytes32(address(polygonPosISM)),
encodedHookData
)
);
polygonPosHook.postDispatch(testMetadata, encodedMessage);
}
function testFork_postDispatch_RevertWhen_ChainIDNotSupported() public {
deployAll();
vm.selectFork(mainnetFork);
bytes memory message = MessageUtils.formatMessage(
POLYGON_POS_VERSION,
uint32(0),
MAINNET_DOMAIN,
TypeCasts.addressToBytes32(address(this)),
11, // wrong domain
TypeCasts.addressToBytes32(address(testRecipient)),
testMessage
);
l1Mailbox.updateLatestDispatchedId(Message.id(message));
vm.expectRevert(
"AbstractMessageIdAuthHook: invalid destination domain"
);
polygonPosHook.postDispatch(testMetadata, message);
}
function testFork_postDispatch_RevertWhen_TooMuchValue() public {
deployAll();
vm.selectFork(mainnetFork);
// assign any value should revert
vm.deal(address(this), uint256(2 ** 255));
bytes memory excessValueMetadata = StandardHookMetadata
.overrideMsgValue(uint256(2 ** 255));
l1Mailbox.updateLatestDispatchedId(messageId);
vm.expectRevert(
"AbstractMessageIdAuthHook: msgValue must be less than 2 ** 255"
);
polygonPosHook.postDispatch(excessValueMetadata, encodedMessage);
}
function testFork_postDispatch_RevertWhen_NotLastDispatchedMessage()
public
{
deployAll();
vm.selectFork(mainnetFork);
vm.expectRevert(
"AbstractMessageIdAuthHook: message not latest dispatched"
);
polygonPosHook.postDispatch(testMetadata, encodedMessage);
}
/* ============ ISM.verifyMessageId ============ */
function testFork_verifyMessageId() public {
deployAll();
vm.selectFork(polygonPosFork);
bytes memory encodedHookData = abi.encodeCall(
AbstractMessageIdAuthorizedIsm.verifyMessageId,
(messageId)
);
vm.startPrank(POLYGON_CROSSCHAIN_SYSTEM_ADDR);
vm.expectEmit(true, false, false, false, address(polygonPosISM));
emit ReceivedMessage(messageId);
// FIX: expect other events
fxChild.onStateReceive(
0,
abi.encode(
TypeCasts.addressToBytes32(address(polygonPosHook)),
TypeCasts.addressToBytes32(address(polygonPosISM)),
encodedHookData
)
);
assertTrue(polygonPosISM.verifiedMessages(messageId).isBitSet(255));
vm.stopPrank();
}
function testFork_verifyMessageId_RevertWhen_NotAuthorized() public {
deployAll();
vm.selectFork(polygonPosFork);
// needs to be called by the fxchild on Polygon
vm.expectRevert(NotCrossChainCall.selector);
polygonPosISM.verifyMessageId(messageId);
vm.startPrank(MAINNET_FX_CHILD);
// needs to be called by the authorized hook contract on Ethereum
vm.expectRevert(
"AbstractMessageIdAuthorizedIsm: sender is not the hook"
);
polygonPosISM.verifyMessageId(messageId);
}
/* ============ ISM.verify ============ */
function testFork_verify() public {
deployAll();
vm.selectFork(polygonPosFork);
orchestrateRelayMessage(messageId);
bool verified = polygonPosISM.verify(new bytes(0), encodedMessage);
assertTrue(verified);
}
// sending over invalid message
function testFork_verify_RevertWhen_HyperlaneInvalidMessage() public {
deployAll();
orchestrateRelayMessage(messageId);
bytes memory invalidMessage = MessageUtils.formatMessage(
HYPERLANE_VERSION,
uint8(0),
MAINNET_DOMAIN,
TypeCasts.addressToBytes32(address(this)),
POLYGON_POS_DOMAIN,
TypeCasts.addressToBytes32(address(this)), // wrong recipient
testMessage
);
bool verified = polygonPosISM.verify(new bytes(0), invalidMessage);
assertFalse(verified);
}
// invalid messageID in postDispatch
function testFork_verify_RevertWhen_InvalidPolygonPosMessageID() public {
deployAll();
vm.selectFork(polygonPosFork);
bytes memory invalidMessage = MessageUtils.formatMessage(
HYPERLANE_VERSION,
uint8(0),
MAINNET_DOMAIN,
TypeCasts.addressToBytes32(address(this)),
POLYGON_POS_DOMAIN,
TypeCasts.addressToBytes32(address(this)),
testMessage
);
bytes32 _messageId = Message.id(invalidMessage);
orchestrateRelayMessage(_messageId);
bool verified = polygonPosISM.verify(new bytes(0), encodedMessage);
assertFalse(verified);
}
/* ============ helper functions ============ */
function _encodeTestMessage() internal view returns (bytes memory) {
return
MessageUtils.formatMessage(
HYPERLANE_VERSION,
uint32(0),
MAINNET_DOMAIN,
TypeCasts.addressToBytes32(address(this)),
POLYGON_POS_DOMAIN,
TypeCasts.addressToBytes32(address(testRecipient)),
testMessage
);
}
function orchestrateRelayMessage(bytes32 _messageId) internal {
vm.selectFork(polygonPosFork);
bytes memory encodedHookData = abi.encodeCall(
AbstractMessageIdAuthorizedIsm.verifyMessageId,
(_messageId)
);
vm.prank(POLYGON_CROSSCHAIN_SYSTEM_ADDR);
fxChild.onStateReceive(
0,
abi.encode(
TypeCasts.addressToBytes32(address(polygonPosHook)),
TypeCasts.addressToBytes32(address(polygonPosISM)),
encodedHookData
)
);
}
}