From 6ab7712ecfb927b8a1f54554fc84660d18b7f9c2 Mon Sep 17 00:00:00 2001 From: LePremierHomme <57456510+LePremierHomme@users.noreply.github.com> Date: Sun, 18 Jan 2026 17:25:05 +0200 Subject: [PATCH] Remove non-direct messaging --- contracts/contracts/errors/IPCErrors.sol | 4 +- .../contracts/gateway/GatewayGetterFacet.sol | 10 - .../gateway/GatewayMessengerFacet.sol | 7 - .../gateway/router/XnetMessagingFacet.sol | 1 - contracts/contracts/interfaces/IGateway.sol | 3 - contracts/contracts/lib/LibGateway.sol | 172 +---------- .../contracts/lib/LibGatewayActorStorage.sol | 6 - .../subnet/SubnetActorCheckpointingFacet.sol | 3 - contracts/test/helpers/SelectorLibrary.sol | 4 +- .../test/integration/GatewayDiamond.t.sol | 6 +- contracts/test/integration/L2PlusSubnet.t.sol | 290 +++--------------- contracts/test/integration/L2PlusXNet.t.sol | 5 + 12 files changed, 71 insertions(+), 440 deletions(-) diff --git a/contracts/contracts/errors/IPCErrors.sol b/contracts/contracts/errors/IPCErrors.sol index e1ac2063c3..7bf4ce56e5 100644 --- a/contracts/contracts/errors/IPCErrors.sol +++ b/contracts/contracts/errors/IPCErrors.sol @@ -56,7 +56,6 @@ error OldConfigurationNumber(); error PQDoesNotContainAddress(); error PQEmpty(); error ParentFinalityAlreadyCommitted(); -error PostboxNotExist(); error SignatureReplay(); error SubnetAlreadyKilled(); error SubnetNotActive(); @@ -91,7 +90,8 @@ enum InvalidXnetMessageReason { Kind, ReflexiveSend, NoRoute, - IncompatibleSupplySource + IncompatibleSupplySource, + NonDirect } string constant ERR_PERMISSIONED_AND_BOOTSTRAPPED = "Method not allowed if permissioned is enabled and subnet bootstrapped"; diff --git a/contracts/contracts/gateway/GatewayGetterFacet.sol b/contracts/contracts/gateway/GatewayGetterFacet.sol index a8e9593605..cbc4d5d11f 100644 --- a/contracts/contracts/gateway/GatewayGetterFacet.sol +++ b/contracts/contracts/gateway/GatewayGetterFacet.sol @@ -120,16 +120,6 @@ contract GatewayGetterFacet { return s.appliedTopDownNonce; } - /// @notice Returns the storable message and its wrapped status from the postbox by a given identifier. - /// @param id The unique identifier of the message in the postbox. - function postbox(bytes32 id) external view returns (IpcEnvelope memory storableMsg) { - return (s.postbox[id]); - } - - function postboxMsgs() external view returns (bytes32[] memory) { - return (s.postboxKeys.values()); - } - /// @notice Returns the majority percentage required for certain consensus or decision-making processes. function majorityPercentage() external view returns (uint64) { return s.majorityPercentage; diff --git a/contracts/contracts/gateway/GatewayMessengerFacet.sol b/contracts/contracts/gateway/GatewayMessengerFacet.sol index a658c3f40f..42ff2584b9 100644 --- a/contracts/contracts/gateway/GatewayMessengerFacet.sol +++ b/contracts/contracts/gateway/GatewayMessengerFacet.sol @@ -85,11 +85,4 @@ contract GatewayMessengerFacet is GatewayActorModifiers { // which passes the struct by reference. return committed; } - - /** - * @dev Propagates all the populated cross-net messages from the postbox. - */ - function propagateAll() external payable { - LibGateway.propagateAllPostboxMessages(); - } } diff --git a/contracts/contracts/gateway/router/XnetMessagingFacet.sol b/contracts/contracts/gateway/router/XnetMessagingFacet.sol index 9251b76aab..ef479558aa 100644 --- a/contracts/contracts/gateway/router/XnetMessagingFacet.sol +++ b/contracts/contracts/gateway/router/XnetMessagingFacet.sol @@ -28,6 +28,5 @@ contract XnetMessagingFacet is GatewayActorModifiers { /// @param crossMsgs The array of cross-network messages to be applied. function applyCrossMessages(IpcEnvelope[] calldata crossMsgs) external systemActorOnly { LibGateway.applyTopDownMessages(s.networkName.getParentSubnet(), crossMsgs); - LibGateway.propagateAllPostboxMessages(); } } diff --git a/contracts/contracts/interfaces/IGateway.sol b/contracts/contracts/interfaces/IGateway.sol index ff43d9ffee..d474706af6 100644 --- a/contracts/contracts/interfaces/IGateway.sol +++ b/contracts/contracts/interfaces/IGateway.sol @@ -62,9 +62,6 @@ interface IGateway { IpcEnvelope calldata envelope ) external payable returns (IpcEnvelope memory committed); - /// @notice Propagates all the stored messages to destination subnet - function propagateAll() external payable; - /// @notice commit the ipc parent finality into storage function commitParentFinality(ParentFinality calldata finality) external; } diff --git a/contracts/contracts/lib/LibGateway.sol b/contracts/contracts/lib/LibGateway.sol index 0d217c4532..32221aefbe 100644 --- a/contracts/contracts/lib/LibGateway.sol +++ b/contracts/contracts/lib/LibGateway.sol @@ -36,10 +36,6 @@ library LibGateway { event QueuedBottomUpMessage(bytes32 indexed id); /// @dev event emitted when there is a new bottom-up message batch to be signed. event NewBottomUpMsgBatch(uint256 indexed epoch); - /// @dev event emmitted when a message is stored in the postbox - to be propagated further. - event MessageStoredInPostbox(bytes32 indexed id); - /// @dev event emmitted when a message is propagated further from the postbox. - event MessagePropagatedFromPostbox(bytes32 id); /// @notice returns the bottom-up batch function getBottomUpMsgBatch(uint256 epoch) internal view returns (bool exists, BottomUpMsgBatch storage batch) { @@ -288,7 +284,7 @@ library LibGateway { } } - /// @notice executes a cross message if its destination is the current network, otherwise adds it to the postbox to be propagated further + /// @notice executes a cross message, assuming its destination is the current network. /// This function assumes that the relevant funds have been already minted or burnt /// when the top-down or bottom-up messages have been queued for execution. /// This function is not expected to revert. If a controlled failure happens, a new @@ -355,28 +351,8 @@ library LibGateway { supplySource = AssetHelper.native(); } - // If the crossnet destination is NOT the current network (network where the gateway is running), - // we add it to the postbox for further propagation. - // Even if we send for propagation, the execution of every message - // should increase the appliedNonce to allow the execution of the next message - // of the batch (this is way we have this after the nonce logic). if (!crossMsg.to.subnetId.equals(s.networkName)) { - (bool valid, InvalidXnetMessageReason reason) = validateCrossMessage(crossMsg); - if (!valid) { - sendReceipt( - crossMsg, - OutcomeType.SystemErr, - abi.encodeWithSelector(InvalidXnetMessage.selector, reason) - ); - return; - } - - bytes32 cid = crossMsg.toHash(); - s.postboxKeys.add(cid); - s.postbox[cid] = crossMsg; - - emit MessageStoredInPostbox({id: crossMsg.toTracingId()}); - return; + revert("Only direct messaging supported"); } // execute the message and get the receipt. @@ -440,11 +416,8 @@ library LibGateway { SubnetID memory to = crossMessage.to.subnetId; IPCMsgType applyType = crossMessage.applyType(s.networkName); - bool isLCA = to.commonParent(crossMessage.from.subnetId).equals(s.networkName); - // If the directionality is top-down, or if we're inverting the direction - // because we're the LCA, commit a top-down message. - if (applyType == IPCMsgType.TopDown || isLCA) { + if (applyType == IPCMsgType.TopDown) { (, SubnetID memory subnetId) = to.down(s.networkName); (, Subnet storage subnet) = getSubnet(subnetId); LibGateway.commitTopDownMsg(subnet, crossMessage); @@ -471,52 +444,6 @@ library LibGateway { } } - /// Checks if the incoming and outgoing subnet supply sources can be mapped. - /// Caller should make sure the incoming/outgoing subnets and current subnet are immediate parent/child subnets. - function checkSubnetsSupplyCompatible( - bool isLCA, - IPCMsgType applyType, - SubnetID memory incoming, - SubnetID memory outgoing, - SubnetID memory current - ) internal view returns(bool) { - if (isLCA) { - // now, it's pivoting @ LCA (i.e. upwards => downwards) - // if incoming bottom up subnet and outgoing target subnet have the same - // asset, we will allow it. This is because if they are using the - // same asset, then the asset can be mapped in both subnets. - - (, SubnetID memory incDown) = incoming.down(current); - (, SubnetID memory outDown) = outgoing.down(current); - - Asset memory incAsset = ISubnetActor(incDown.getActor()).supplySource(); - Asset memory outAsset = ISubnetActor(outDown.getActor()).supplySource(); - - return incAsset.equals(outAsset); - } - - if (applyType == IPCMsgType.BottomUp) { - // The child subnet has supply source native, this is the same as - // the current subnet's native source, the mapping makes sense, propagate up. - (, SubnetID memory incDown) = incoming.down(current); - return incDown.getActor().hasSupplyOfKind(AssetKind.Native); - } - - // Topdown handling - - // The incoming subnet's supply source will be mapped to native coin in the - // next child subnet. If the down subnet has native, then the mapping makes - // sense. - (, SubnetID memory down) = outgoing.down(current); - return down.getActor().hasSupplyOfKind(AssetKind.Native); - } - - /// @notice Validates a cross message before committing it. - function validateCrossMessage(IpcEnvelope memory envelope) internal view returns (bool, InvalidXnetMessageReason) { - (bool valid, InvalidXnetMessageReason reason, ) = checkCrossMessage(envelope); - return (valid, reason); - } - /// @notice Validates a cross message and returns the applyType if the message is valid function checkCrossMessage(IpcEnvelope memory envelope) internal view returns (bool valid, InvalidXnetMessageReason reason, IPCMsgType applyType) { SubnetID memory toSubnetId = envelope.to.subnetId; @@ -526,101 +453,22 @@ library LibGateway { GatewayActorStorage storage s = LibGatewayActorStorage.appStorage(); SubnetID memory currentNetwork = s.networkName; + applyType = envelope.applyType(currentNetwork); // We cannot send a cross message to the same subnet. if (toSubnetId.equals(currentNetwork)) { return (false, InvalidXnetMessageReason.ReflexiveSend, applyType); } - // Lowest common ancestor subnet - bool isLCA = toSubnetId.commonParent(envelope.from.subnetId).equals(currentNetwork); - applyType = envelope.applyType(currentNetwork); - - // If the directionality is top-down, or if we're inverting the direction - // else we need to check if the common parent exists. - if (applyType == IPCMsgType.TopDown || isLCA) { - (bool foundChildSubnetId, SubnetID memory childSubnetId) = toSubnetId.down(currentNetwork); - if (!foundChildSubnetId) { - return (false, InvalidXnetMessageReason.DstSubnet, applyType); - } - - (bool foundSubnet,) = LibGateway.getSubnet(childSubnetId); - if (!foundSubnet) { - return (false, InvalidXnetMessageReason.DstSubnet, applyType); - } - } else { - SubnetID memory commonParent = toSubnetId.commonParent(currentNetwork); - if (commonParent.isEmpty()) { - return (false, InvalidXnetMessageReason.NoRoute, applyType); - } - } - - // starting/ending subnet, no need check supply sources - if (envelope.from.subnetId.equals(currentNetwork) || envelope.to.subnetId.equals(currentNetwork)) { - return (true, reason, applyType); - } - - bool supplySourcesCompatible = checkSubnetsSupplyCompatible({ - isLCA: isLCA, - applyType: applyType, - incoming: envelope.from.subnetId, - outgoing: envelope.to.subnetId, - current: currentNetwork - }); + // Allow only direct parent-child relationships, by checking route length difference. + bool isDirect = (applyType == IPCMsgType.TopDown) + ? toSubnetId.route.length == envelope.from.subnetId.route.length + 1 + : envelope.from.subnetId.route.length == toSubnetId.route.length + 1; - if (!supplySourcesCompatible) { - return (false, InvalidXnetMessageReason.IncompatibleSupplySource, applyType); + if (!isDirect) { + return (false, InvalidXnetMessageReason.NonDirect, applyType); } return (true, reason, applyType); } - - /** - * @dev Propagates all the populated cross-net messages from the postbox. - */ - function propagateAllPostboxMessages() internal { - GatewayActorStorage storage s = LibGatewayActorStorage.appStorage(); - - uint256 keysLength = s.postboxKeys.length(); - - bytes32[] memory values = s.postboxKeys.values(); - - for (uint256 i = 0; i < keysLength; ) { - bytes32 msgCid = values[i]; - LibGateway.propagatePostboxMessage(msgCid); - - unchecked { - ++i; - } - } - } - - /** - * @dev Propagates the populated cross-net message for the given `msgCid`. - * @param msgCid - the cid of the cross-net message - */ - function propagatePostboxMessage(bytes32 msgCid) internal { - GatewayActorStorage storage s = LibGatewayActorStorage.appStorage(); - IpcEnvelope storage crossMsg = s.postbox[msgCid]; - - if (crossMsg.isEmpty()) { - revert("Message not found in postbox"); - } - - bool shouldBurn = LibGateway.commitValidatedCrossMessage(crossMsg); - - // Cache value before deletion to avoid re-entrancy - uint256 v = crossMsg.value; - bytes32 deterministicId = crossMsg.toTracingId(); - - // Remove the message to prevent re-entrancy and clean up state - delete s.postbox[msgCid]; - s.postboxKeys.remove(msgCid); - - // Execute side effects - LibGateway.crossMsgSideEffects({v: v, shouldBurn: shouldBurn}); - - emit MessagePropagatedFromPostbox({id: deterministicId}); - } - } diff --git a/contracts/contracts/lib/LibGatewayActorStorage.sol b/contracts/contracts/lib/LibGatewayActorStorage.sol index 1afe951765..5464067d3d 100644 --- a/contracts/contracts/lib/LibGatewayActorStorage.sol +++ b/contracts/contracts/lib/LibGatewayActorStorage.sol @@ -55,12 +55,6 @@ struct GatewayActorStorage { mapping(bytes32 => Subnet) subnets; /// @notice The parent finalities. Key is the block number, value is the finality struct. mapping(uint256 => ParentFinality) finalitiesMap; - /// @notice Postbox keeps track of all the cross-net messages triggered by - /// an actor that need to be propagated further through the hierarchy. - /// cross-net message id => CrossMsg - mapping(bytes32 => IpcEnvelope) postbox; - /// @notice Keys of the envelopes in the postbox. Useful to iterate through them - EnumerableSet.Bytes32Set postboxKeys; /// @notice A mapping of block numbers to bottom-up cross-messages // slither-disable-next-line uninitialized-state mapping(uint256 => BottomUpMsgBatch) bottomUpMsgBatches; diff --git a/contracts/contracts/subnet/SubnetActorCheckpointingFacet.sol b/contracts/contracts/subnet/SubnetActorCheckpointingFacet.sol index 7e6f926536..125ed608e9 100644 --- a/contracts/contracts/subnet/SubnetActorCheckpointingFacet.sol +++ b/contracts/contracts/subnet/SubnetActorCheckpointingFacet.sol @@ -125,9 +125,6 @@ contract SubnetActorCheckpointingFacet is ISubnetActorCheckpointing, ReentrancyG } IGateway(LibSubnetActorStorage.appStorage().ipcGatewayAddr).execBottomUpMsgBatch(msgs); - - // Propagate cross messages from checkpoint to other subnets - IGateway(LibSubnetActorStorage.appStorage().ipcGatewayAddr).propagateAll(); } function ensureValidHeight(uint64 blockHeight, uint64 lastHeight) internal view { diff --git a/contracts/test/helpers/SelectorLibrary.sol b/contracts/test/helpers/SelectorLibrary.sol index f75ccf662c..7b7eeeb6ea 100644 --- a/contracts/test/helpers/SelectorLibrary.sol +++ b/contracts/test/helpers/SelectorLibrary.sol @@ -27,7 +27,7 @@ library SelectorLibrary { if (keccak256(abi.encodePacked(facetName)) == keccak256(abi.encodePacked("GatewayGetterFacet"))) { return abi.decode( - hex"000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000188789f83b0000000000000000000000000000000000000000000000000000000006c4685300000000000000000000000000000000000000000000000000000000dd81b5cf0000000000000000000000000000000000000000000000000000000041b6a2e80000000000000000000000000000000000000000000000000000000038d6693200000000000000000000000000000000000000000000000000000000444ead5100000000000000000000000000000000000000000000000000000000544dddff000000000000000000000000000000000000000000000000000000006ad21bb000000000000000000000000000000000000000000000000000000000b1ba49b000000000000000000000000000000000000000000000000000000000f3229131000000000000000000000000000000000000000000000000000000000338150f0000000000000000000000000000000000000000000000000000000094074b03000000000000000000000000000000000000000000000000000000007edeac9200000000000000000000000000000000000000000000000000000000c66c66a1000000000000000000000000000000000000000000000000000000003594c3c1000000000000000000000000000000000000000000000000000000009d3070b50000000000000000000000000000000000000000000000000000000042398a9a00000000000000000000000000000000000000000000000000000000fa34a400000000000000000000000000000000000000000000000000000000005d02968500000000000000000000000000000000000000000000000000000000599c7bd1000000000000000000000000000000000000000000000000000000008cfd78e7000000000000000000000000000000000000000000000000000000007474d79f0000000000000000000000000000000000000000000000000000000002e30f9a00000000000000000000000000000000000000000000000000000000a2b6715800000000000000000000000000000000000000000000000000000000", + hex"000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000168789f83b0000000000000000000000000000000000000000000000000000000006c4685300000000000000000000000000000000000000000000000000000000dd81b5cf0000000000000000000000000000000000000000000000000000000041b6a2e80000000000000000000000000000000000000000000000000000000038d6693200000000000000000000000000000000000000000000000000000000444ead5100000000000000000000000000000000000000000000000000000000544dddff000000000000000000000000000000000000000000000000000000006ad21bb000000000000000000000000000000000000000000000000000000000b1ba49b000000000000000000000000000000000000000000000000000000000f3229131000000000000000000000000000000000000000000000000000000000338150f0000000000000000000000000000000000000000000000000000000094074b03000000000000000000000000000000000000000000000000000000007edeac9200000000000000000000000000000000000000000000000000000000c66c66a1000000000000000000000000000000000000000000000000000000003594c3c1000000000000000000000000000000000000000000000000000000009d3070b50000000000000000000000000000000000000000000000000000000042398a9a00000000000000000000000000000000000000000000000000000000fa34a400000000000000000000000000000000000000000000000000000000005d02968500000000000000000000000000000000000000000000000000000000599c7bd10000000000000000000000000000000000000000000000000000000002e30f9a00000000000000000000000000000000000000000000000000000000a2b6715800000000000000000000000000000000000000000000000000000000", (bytes4[]) ); } @@ -41,7 +41,7 @@ library SelectorLibrary { if (keccak256(abi.encodePacked(facetName)) == keccak256(abi.encodePacked("GatewayMessengerFacet"))) { return abi.decode( - hex"000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000027f7999f4000000000000000000000000000000000000000000000000000000002c85ec2c00000000000000000000000000000000000000000000000000000000", + hex"000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000012c85ec2c00000000000000000000000000000000000000000000000000000000", (bytes4[]) ); } diff --git a/contracts/test/integration/GatewayDiamond.t.sol b/contracts/test/integration/GatewayDiamond.t.sol index 2d16bdee55..1feea106c6 100644 --- a/contracts/test/integration/GatewayDiamond.t.sol +++ b/contracts/test/integration/GatewayDiamond.t.sol @@ -84,10 +84,6 @@ contract GatewayActorDiamondTest is Test, IntegrationTestBase, SubnetWithNativeT gatewayDiamond.getter().majorityPercentage() == DEFAULT_MAJORITY_PERCENTAGE, "unexpected majorityPercentage" ); - - IpcEnvelope memory storableMsg = gatewayDiamond.getter().postbox(0); - IpcEnvelope memory msg1; - require(msg1.toHash() == storableMsg.toHash(), "unexpected hash"); } function testGatewayDiamond_NewGatewayWithDefaultParams() public view { @@ -557,7 +553,7 @@ contract GatewayActorDiamondTest is Test, IntegrationTestBase, SubnetWithNativeT } function testGatewayDiamond_SendCrossMessage_Fails_Fuzz(uint256 fee) public { - vm.assume(fee < DEFAULT_CROSS_MSG_FEE); + vm.assume(fee > 0 && fee < DEFAULT_CROSS_MSG_FEE); address caller = CHILD_NETWORK_ADDRESS; vm.deal(caller, DEFAULT_COLLATERAL_AMOUNT + DEFAULT_CROSS_MSG_FEE + 2); diff --git a/contracts/test/integration/L2PlusSubnet.t.sol b/contracts/test/integration/L2PlusSubnet.t.sol index 1987cd690d..38fe558d8c 100644 --- a/contracts/test/integration/L2PlusSubnet.t.sol +++ b/contracts/test/integration/L2PlusSubnet.t.sol @@ -229,7 +229,7 @@ contract L2PlusSubnetTest is Test, IntegrationTestBase { Params memory params = Params({ root: rootNetwork, subnet: tokenL2Subnet, - subnetL3: nativeL3SubnetsWithTokenParent[0], + subnetL3: tokenL3SubnetsWithTokenParent[0], caller: caller, callerAddr: address(caller), recipientAddr: address(new MockIpcContractPayable()), @@ -251,7 +251,38 @@ contract L2PlusSubnetTest is Test, IntegrationTestBase { sendCrossMessageFromSiblingToSiblingWithOkResult(rootNetwork, tokenL2Subnet, tokenL3SubnetsWithTokenParent); } - // Error scenarios + function testL2PlusSubnet_RejectNonDirectMessage() public { + TestSubnetDefinition memory nativeL3Subnet = nativeL3Subnets[0]; + MockIpcContractResult caller = new MockIpcContractResult(); + address callerAddr = address(caller); + address recipientAddr = address(new MockIpcContractPayable()); + + registerSubnet(nativeL2Subnet.subnetActorAddr, rootNetwork.gateway); + registerSubnet(nativeL3Subnet.subnetActorAddr, nativeL2Subnet.gateway); + + // L3 -> L1 + IpcEnvelope memory l3_to_l1 = TestUtils.newXnetCallMsg( + IPCAddress({subnetId: nativeL3Subnet.id, rawAddress: FvmAddressHelper.from(callerAddr)}), + IPCAddress({subnetId: rootNetwork.id, rawAddress: FvmAddressHelper.from(recipientAddr)}), + 0 ether, + 0 + ); + vm.expectRevert(abi.encodeWithSelector(InvalidXnetMessage.selector, InvalidXnetMessageReason.NonDirect)); + vm.prank(callerAddr); + nativeL3Subnets[0].gateway.messenger().sendContractXnetMessage{value: 0}(l3_to_l1); + + // L1 -> L3 + IpcEnvelope memory l1_to_l3 = TestUtils.newXnetCallMsg( + IPCAddress({subnetId: rootNetwork.id, rawAddress: FvmAddressHelper.from(recipientAddr)}), + IPCAddress({subnetId: nativeL3Subnet.id, rawAddress: FvmAddressHelper.from(callerAddr)}), + 0 ether, + 0 + ); + vm.expectRevert(abi.encodeWithSelector(InvalidXnetMessage.selector, InvalidXnetMessageReason.NonDirect)); + vm.prank(callerAddr); + rootNetwork.gateway.messenger().sendContractXnetMessage{value: 0}(l1_to_l3); + } + function testL2PlusSubnet_Native_SendCrossMessageFromChildToNonExistingActorError() public { MockIpcContractResult caller = new MockIpcContractResult(); Params memory params = Params({ @@ -314,7 +345,7 @@ contract L2PlusSubnetTest is Test, IntegrationTestBase { Params memory params = Params({ root: rootNetwork, subnet: tokenL2Subnet, - subnetL3: nativeL3SubnetsWithTokenParent[0], + subnetL3: tokenL3SubnetsWithTokenParent[0], caller: caller, callerAddr: address(caller), recipientAddr: 0x53c82507aA03B1a6e695000c302674ef1ecb880B, @@ -328,135 +359,6 @@ contract L2PlusSubnetTest is Test, IntegrationTestBase { sendCrossMessageFromParentToChildWithResult(params); } - function testL2PlusSubnet_ParentToChildTopDownNoncePropagatedCorrectly() public { - MockIpcContractResult caller = new MockIpcContractResult(); - Params memory params = Params({ - root: rootNetwork, - subnet: nativeL2Subnet, - subnetL3: nativeL3Subnets[0], - caller: caller, - callerAddr: address(caller), - recipientAddr: address(new MockIpcContractPayable()), - amount: 3, - expectedOutcome: OutcomeType.Ok, - expectedRet: abi.encode(EMPTY_BYTES), - callerAmount: 1 ether, - fundAmount: 100000 - }); - - // register L2 into root network - registerSubnet(params.subnet.subnetActorAddr, params.root.gateway); - // register L3 into L2 subnet - registerSubnet(params.subnetL3.subnetActorAddr, params.subnet.gateway); - - vm.deal(params.callerAddr, params.callerAmount); - - IpcEnvelope memory fundCrossMessage = CrossMsgHelper.createFundMsg({ - subnet: params.subnet.id, - signer: params.callerAddr, - to: FvmAddressHelper.from(params.callerAddr), - value: params.amount - }); - - // 0 is default but we set it explicitly here to make it clear - fundCrossMessage.localNonce = 0; - - vm.prank(params.callerAddr); - vm.expectEmit(true, true, true, true, params.root.gatewayAddr); - emit LibGateway.NewTopDownMessage({ - subnet: params.subnet.subnetActorAddr, - message: fundCrossMessage, - id: fundCrossMessage.toTracingId() - }); - - params.root.gateway.manager().fund{value: params.amount}( - params.subnet.id, - FvmAddressHelper.from(params.callerAddr) - ); - - IpcEnvelope memory callCrossMessage = TestUtils.newXnetCallMsg( - IPCAddress({subnetId: params.root.id, rawAddress: FvmAddressHelper.from(params.callerAddr)}), - IPCAddress({subnetId: params.subnetL3.id, rawAddress: FvmAddressHelper.from(params.recipientAddr)}), - params.amount, - 1 - ); - - // send the cross message from the root network to the L3 subnet - vm.prank(params.callerAddr); - vm.expectEmit(true, true, true, true, params.root.gatewayAddr); - emit LibGateway.NewTopDownMessage({ - subnet: params.subnet.subnetActorAddr, - message: callCrossMessage, - id: callCrossMessage.toTracingId() - }); - - params.root.gateway.messenger().sendContractXnetMessage{value: params.amount}(callCrossMessage); - (, uint64 rootTopDownNonce) = params.root.gateway.getter().getTopDownNonce(params.subnet.id); - assertEq(rootTopDownNonce, 2, "wrong root top down nonce"); - - IpcEnvelope[] memory msgsForL2 = new IpcEnvelope[](2); - msgsForL2[0] = fundCrossMessage; - msgsForL2[1] = callCrossMessage; - - // the expected nonce for the top down message for L3 subnet is 0 because no previous message was sent - // from L2 to L3 - msgsForL2[1].localNonce = 0; - vm.prank(FilAddress.SYSTEM_ACTOR); - vm.expectEmit(true, true, true, true, params.subnet.gatewayAddr); - emit LibGateway.NewTopDownMessage({ - subnet: params.subnetL3.subnetActorAddr, - message: callCrossMessage, - id: callCrossMessage.toTracingId() - }); - - // nonce needs to be 1 because of the fund message. - msgsForL2[1].localNonce = 1; - params.subnet.gateway.xnetMessenger().applyCrossMessages(msgsForL2); - - uint64 subnetAppliedTopDownNonce = params.subnet.gateway.getter().appliedTopDownNonce(); - assertEq(subnetAppliedTopDownNonce, 2, "wrong L2 subnet applied top down nonce"); - - IpcEnvelope[] memory msgsForL3 = new IpcEnvelope[](1); - msgsForL3[0] = callCrossMessage; - - vm.prank(FilAddress.SYSTEM_ACTOR); - // nonce is zero because this is a first message touching the L3 subnet - msgsForL3[0].localNonce = 0; - params.subnetL3.gateway.xnetMessenger().applyCrossMessages(msgsForL3); - - uint64 subnetL3AppliedTopDownNonce = params.subnetL3.gateway.getter().appliedTopDownNonce(); - assertEq(subnetL3AppliedTopDownNonce, 1, "wrong L3 subnet applied top down nonce"); - - // now fund from L2 to L3 to check to nonce propagation - vm.deal(params.callerAddr, params.callerAmount); - - IpcEnvelope memory fundCrossMessageL3 = CrossMsgHelper.createFundMsg({ - subnet: params.subnetL3.id, - signer: params.callerAddr, - to: FvmAddressHelper.from(params.callerAddr), - value: params.amount - }); - - // nonce should be 1 because this is the first cross message from L1 to L3 - fundCrossMessageL3.localNonce = 1; - - vm.prank(params.callerAddr); - vm.expectEmit(true, true, true, true, params.subnet.gatewayAddr); - emit LibGateway.NewTopDownMessage({ - subnet: params.subnetL3.subnetActorAddr, - message: fundCrossMessageL3, - id: fundCrossMessageL3.toTracingId() - }); - - params.subnet.gateway.manager().fund{value: params.amount}( - params.subnetL3.id, - FvmAddressHelper.from(params.callerAddr) - ); - - uint64 subnetL3AppliedTopDownNonceAfterFund = params.subnetL3.gateway.getter().appliedTopDownNonce(); - assertEq(subnetL3AppliedTopDownNonceAfterFund, 1, "wrong L3 subnet applied top down nonce"); - } - function fundSubnet( GatewayDiamond gateway, TestSubnetDefinition memory subnet, @@ -494,7 +396,7 @@ contract L2PlusSubnetTest is Test, IntegrationTestBase { // create the xnet message on the subnet L3 - it's local gateway IpcEnvelope memory crossMessage = TestUtils.newXnetCallMsg( IPCAddress({subnetId: params.subnetL3.id, rawAddress: FvmAddressHelper.from(params.callerAddr)}), - IPCAddress({subnetId: params.root.id, rawAddress: FvmAddressHelper.from(params.recipientAddr)}), + IPCAddress({subnetId: params.subnet.id, rawAddress: FvmAddressHelper.from(params.recipientAddr)}), params.amount, 0 ); @@ -509,36 +411,9 @@ contract L2PlusSubnetTest is Test, IntegrationTestBase { _msgs[0] = crossMessage; execBottomUpMsgBatch(_checkpoint, _msgs, params.subnetL3.subnetActor); - // create checkpoint in L2 and submit it to the root network (L2 subnet actor) - vm.recordLogs(); - (BottomUpCheckpoint memory checkpoint, IpcEnvelope[] memory l2_batch) = XnetUtil - .callCreateBottomUpCheckpointFromChildSubnet(params.subnet.id, params.subnet.gateway); - // expected result top down message from root to L2. This is a response to the xnet call. - IpcEnvelope memory resultMessage = crossMessage.createResultMsg(params.expectedOutcome, params.expectedRet); - resultMessage.localNonce = 1; - - submitBottomUpCheckpointAndExpectTopDownMessageEvent( - checkpoint, - l2_batch, - params.subnet.subnetActor, - resultMessage, - params.subnet.subnetActorAddr, - params.root.gatewayAddr - ); - - // apply the result message in the L2 subnet and expect another top down message to be emitted IpcEnvelope[] memory msgs = new IpcEnvelope[](1); - msgs[0] = cloneIpcEnvelopeWithDifferentNonce(resultMessage, 0); - - commitParentFinality(params.subnet.gatewayAddr); - executeTopDownMsgsAndExpectTopDownMessageEvent( - msgs, - params.subnet.gateway, - resultMessage, - params.subnetL3.subnetActorAddr, - params.subnet.gatewayAddr - ); + msgs[0] = crossMessage.createResultMsg(params.expectedOutcome, params.expectedRet); // apply the result and check it was propagated to the correct actor commitParentFinality(params.subnetL3.gatewayAddr); @@ -559,9 +434,10 @@ contract L2PlusSubnetTest is Test, IntegrationTestBase { vm.prank(params.callerAddr); fundSubnet(params.root.gateway, params.subnet, params.callerAddr, params.fundAmount); + fundSubnet(params.subnet.gateway, params.subnetL3, params.callerAddr, params.fundAmount); IpcEnvelope memory crossMessage = TestUtils.newXnetCallMsg( - IPCAddress({subnetId: params.root.id, rawAddress: FvmAddressHelper.from(params.callerAddr)}), + IPCAddress({subnetId: params.subnet.id, rawAddress: FvmAddressHelper.from(params.callerAddr)}), IPCAddress({subnetId: params.subnetL3.id, rawAddress: FvmAddressHelper.from(params.recipientAddr)}), params.amount, 0 @@ -574,37 +450,29 @@ contract L2PlusSubnetTest is Test, IntegrationTestBase { IERC20(subnetSupply.tokenAddress).transfer(params.callerAddr, params.amount); // increase allowance so that send xnet msg will make it vm.prank(params.callerAddr); - IERC20(subnetSupply.tokenAddress).approve(address(params.root.gatewayAddr), params.amount); + IERC20(subnetSupply.tokenAddress).approve(address(params.subnet.gatewayAddr), params.amount); + } + Asset memory subnetL3Supply = params.subnetL3.subnetActor.getter().supplySource(); + if (subnetL3Supply.kind == AssetKind.ERC20) { + // increase callerAddr's L3 token balance + IERC20(subnetL3Supply.tokenAddress).transfer(params.callerAddr, params.amount); + // L2 gateway needs to lock L3's tokens, so approve L2 gateway for L3's token + vm.prank(params.callerAddr); + IERC20(subnetL3Supply.tokenAddress).approve(address(params.subnet.gatewayAddr), params.amount); } - crossMessage.localNonce = 1; - // send the cross message from the root network to the L3 subnet + // send the cross message from the L2 subnet to the L3 subnet vm.prank(params.callerAddr); - vm.expectEmit(true, true, true, true, params.root.gatewayAddr); - emit LibGateway.NewTopDownMessage({ - subnet: params.subnet.subnetActorAddr, - message: crossMessage, - id: crossMessage.toTracingId() - }); - crossMessage.localNonce = 0; if (subnetSupply.kind == AssetKind.ERC20) { - params.root.gateway.messenger().sendContractXnetMessage(crossMessage); + params.subnet.gateway.messenger().sendContractXnetMessage(crossMessage); } else { - params.root.gateway.messenger().sendContractXnetMessage{value: params.amount}(crossMessage); + params.subnet.gateway.messenger().sendContractXnetMessage{value: params.amount}(crossMessage); } IpcEnvelope[] memory msgs = new IpcEnvelope[](1); msgs[0] = crossMessage; - // propagate the message from the L2 to the L3 subnet - executeTopDownMsgsAndExpectTopDownMessageEvent( - msgs, - params.subnet.gateway, - crossMessage, - params.subnetL3.subnetActorAddr, - params.subnet.gatewayAddr - ); // apply the cross message in the L3 subnet executeTopDownMsgs(msgs, params.subnetL3.gateway); // submit checkoint so the result message can be propagated to L2 @@ -614,13 +482,6 @@ contract L2PlusSubnetTest is Test, IntegrationTestBase { submitBottomUpCheckpoint(l3_checkpoint, params.subnetL3.subnetActor); execBottomUpMsgBatch(l3_checkpoint, l3_batch, params.subnetL3.subnetActor); - // submit checkoint so the result message can be propagated to root network - vm.recordLogs(); - (BottomUpCheckpoint memory l2_checkpoint, IpcEnvelope[] memory l2_batch) = XnetUtil - .callCreateBottomUpCheckpointFromChildSubnet(params.subnet.id, params.subnet.gateway); - submitBottomUpCheckpoint(l2_checkpoint, params.subnet.subnetActor); - execBottomUpMsgBatch(l2_checkpoint, l2_batch, params.subnet.subnetActor); - assertTrue(params.caller.hasResult(), "missing result"); assertTrue(params.caller.result().outcome == params.expectedOutcome, "wrong result outcome"); assertTrue(keccak256(params.caller.result().ret) == keccak256(params.expectedRet), "wrong result outcome"); @@ -659,58 +520,9 @@ contract L2PlusSubnetTest is Test, IntegrationTestBase { 0 ); + vm.expectRevert(abi.encodeWithSelector(InvalidXnetMessage.selector, InvalidXnetMessageReason.NonDirect)); vm.prank(callerAddr); subnetL3s[0].gateway.messenger().sendContractXnetMessage{value: amount}(crossMessage); - - // submit the checkpoint from L3-0 to L2 - vm.recordLogs(); - (BottomUpCheckpoint memory checkpoint, ) = XnetUtil.callCreateBottomUpCheckpointFromChildSubnet( - subnetL3s[0].id, - subnetL3s[0].gateway - ); - - // submit the checkpoint in L2 produces top down message to L3-1 - submitBottomUpCheckpointAndExpectTopDownMessageEvent( - checkpoint, - getBottomUpBatchRecordedFromLogs(vm.getRecordedLogs()), - subnetL3s[0].subnetActor, - crossMessage, - subnetL3s[1].subnetActorAddr, - subnet.gatewayAddr - ); - - // mimics the execution of the top down messages in the L3-1 subnet - IpcEnvelope[] memory msgs = new IpcEnvelope[](1); - msgs[0] = crossMessage; - - executeTopDownMsgs(msgs, subnetL3s[1].gateway); - - // submit the checkpoint from L3-1 to L2 for result propagation - vm.recordLogs(); - (checkpoint, ) = XnetUtil.callCreateBottomUpCheckpointFromChildSubnet(subnetL3s[1].id, subnetL3s[1].gateway); - - // expected result top down message from L2 to L3. This is a response to the xnet call. - IpcEnvelope memory resultMessage = crossMessage.createResultMsg(OutcomeType.Ok, abi.encode(EMPTY_BYTES)); - resultMessage.localNonce = 1; - - // submit the checkpoint in L2 produces top down message to L3-1 - submitBottomUpCheckpointAndExpectTopDownMessageEvent( - checkpoint, - getBottomUpBatchRecordedFromLogs(vm.getRecordedLogs()), - subnetL3s[1].subnetActor, - resultMessage, - subnetL3s[0].subnetActorAddr, - subnet.gatewayAddr - ); - - // apply the result message in the L3-1 subnet - resultMessage.localNonce = 0; - IpcEnvelope[] memory resultMsgs = new IpcEnvelope[](1); - resultMsgs[0] = resultMessage; - - executeTopDownMsgs(resultMsgs, subnetL3s[0].gateway); - assertTrue(caller.hasResult(), "missing result"); - assertTrue(caller.result().outcome == OutcomeType.Ok, "wrong result outcome"); } function commitParentFinality(address gateway) internal { diff --git a/contracts/test/integration/L2PlusXNet.t.sol b/contracts/test/integration/L2PlusXNet.t.sol index db2c58d928..e3bdd0d819 100644 --- a/contracts/test/integration/L2PlusXNet.t.sol +++ b/contracts/test/integration/L2PlusXNet.t.sol @@ -438,6 +438,7 @@ contract L2PlusSubnetTest is Test, IntegrationTestBase, IIpcHandler { // testing Native L1 => ERC20 L2 => ERC20 L3, this supply source is not allowed function test_N1E2E3_rejects() public { + vm.skip(true); // TODO: re-enable once non-direct messaging is allowed SubnetID memory l1SubnetID = initL1(); address erc20_1 = address(new ERC20PresetFixedSupply("TestToken1", "TT", 21_000_000 ether, address(this))); @@ -491,6 +492,8 @@ contract L2PlusSubnetTest is Test, IntegrationTestBase, IIpcHandler { // testing Native L1 => ERC20 L2 => Native L3 function test_N1E2N3_works() public { + vm.skip(true); // TODO: re-enable once non-direct messaging is allowed + SubnetID memory l1SubnetID = initL1(); address erc20 = address(new ERC20PresetFixedSupply("TestToken", "TT", 21_000_000 ether, address(this))); @@ -530,6 +533,8 @@ contract L2PlusSubnetTest is Test, IntegrationTestBase, IIpcHandler { // testing Native L3 => ERC20 L2 => Native L1 => ERC20 L2' => Native L3' function test_N3E2N1E2N3_works() public { + vm.skip(true); // TODO: re-enable once non-direct messaging is allowed + SubnetID memory l1SubnetID = initL1(); address erc20 = address(new ERC20PresetFixedSupply("TestToken", "TT", 21_000_000 ether, address(this)));