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.
367 lines
12 KiB
367 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
|
|
)
|
|
);
|
|
}
|
|
}
|
|
|