diff --git a/contracts/.storage-layouts/GatewayActorModifiers.json b/contracts/.storage-layouts/GatewayActorModifiers.json index 9f79cde9e..d599af032 100644 --- a/contracts/.storage-layouts/GatewayActorModifiers.json +++ b/contracts/.storage-layouts/GatewayActorModifiers.json @@ -607,55 +607,55 @@ "type": "t_enum(IpcMsgKind)21376" }, { - "astId": 21385, + "astId": 21384, "contract": "contracts/lib/LibGatewayActorStorage.sol:GatewayActorModifiers", - "label": "to", - "offset": 0, - "slot": "1", - "type": "t_struct(IPCAddress)21658_storage" + "label": "localNonce", + "offset": 1, + "slot": "0", + "type": "t_uint64" }, { - "astId": 21389, + "astId": 21387, "contract": "contracts/lib/LibGatewayActorStorage.sol:GatewayActorModifiers", - "label": "from", - "offset": 0, - "slot": "5", - "type": "t_struct(IPCAddress)21658_storage" + "label": "originalNonce", + "offset": 9, + "slot": "0", + "type": "t_uint64" }, { - "astId": 21392, + "astId": 21390, "contract": "contracts/lib/LibGatewayActorStorage.sol:GatewayActorModifiers", - "label": "localNonce", + "label": "value", "offset": 0, - "slot": "9", - "type": "t_uint64" + "slot": "1", + "type": "t_uint256" }, { - "astId": 21395, + "astId": 21394, "contract": "contracts/lib/LibGatewayActorStorage.sol:GatewayActorModifiers", - "label": "value", + "label": "to", "offset": 0, - "slot": "10", - "type": "t_uint256" + "slot": "2", + "type": "t_struct(IPCAddress)21658_storage" }, { "astId": 21398, "contract": "contracts/lib/LibGatewayActorStorage.sol:GatewayActorModifiers", - "label": "message", + "label": "from", "offset": 0, - "slot": "11", - "type": "t_bytes_storage" + "slot": "6", + "type": "t_struct(IPCAddress)21658_storage" }, { "astId": 21401, "contract": "contracts/lib/LibGatewayActorStorage.sol:GatewayActorModifiers", - "label": "originalNonce", + "label": "message", "offset": 0, - "slot": "12", - "type": "t_uint64" + "slot": "10", + "type": "t_bytes_storage" } ], - "numberOfBytes": "416" + "numberOfBytes": "352" }, "t_struct(MaxPQ)19752_storage": { "encoding": "inplace", diff --git a/contracts/.storage-layouts/GatewayDiamond.json b/contracts/.storage-layouts/GatewayDiamond.json index 368d42df7..df4da1a34 100644 --- a/contracts/.storage-layouts/GatewayDiamond.json +++ b/contracts/.storage-layouts/GatewayDiamond.json @@ -607,55 +607,55 @@ "type": "t_enum(IpcMsgKind)21376" }, { - "astId": 21385, + "astId": 21384, "contract": "contracts/GatewayDiamond.sol:GatewayDiamond", - "label": "to", - "offset": 0, - "slot": "1", - "type": "t_struct(IPCAddress)21658_storage" + "label": "localNonce", + "offset": 1, + "slot": "0", + "type": "t_uint64" }, { - "astId": 21389, + "astId": 21387, "contract": "contracts/GatewayDiamond.sol:GatewayDiamond", - "label": "from", - "offset": 0, - "slot": "5", - "type": "t_struct(IPCAddress)21658_storage" + "label": "originalNonce", + "offset": 9, + "slot": "0", + "type": "t_uint64" }, { - "astId": 21392, + "astId": 21390, "contract": "contracts/GatewayDiamond.sol:GatewayDiamond", - "label": "localNonce", + "label": "value", "offset": 0, - "slot": "9", - "type": "t_uint64" + "slot": "1", + "type": "t_uint256" }, { - "astId": 21395, + "astId": 21394, "contract": "contracts/GatewayDiamond.sol:GatewayDiamond", - "label": "value", + "label": "to", "offset": 0, - "slot": "10", - "type": "t_uint256" + "slot": "2", + "type": "t_struct(IPCAddress)21658_storage" }, { "astId": 21398, "contract": "contracts/GatewayDiamond.sol:GatewayDiamond", - "label": "message", + "label": "from", "offset": 0, - "slot": "11", - "type": "t_bytes_storage" + "slot": "6", + "type": "t_struct(IPCAddress)21658_storage" }, { "astId": 21401, "contract": "contracts/GatewayDiamond.sol:GatewayDiamond", - "label": "originalNonce", + "label": "message", "offset": 0, - "slot": "12", - "type": "t_uint64" + "slot": "10", + "type": "t_bytes_storage" } ], - "numberOfBytes": "416" + "numberOfBytes": "352" }, "t_struct(MaxPQ)19752_storage": { "encoding": "inplace", diff --git a/contracts/.storage-layouts/SubnetActorDiamond.json b/contracts/.storage-layouts/SubnetActorDiamond.json index 673c708f7..499390156 100644 --- a/contracts/.storage-layouts/SubnetActorDiamond.json +++ b/contracts/.storage-layouts/SubnetActorDiamond.json @@ -403,55 +403,55 @@ "type": "t_enum(IpcMsgKind)21376" }, { - "astId": 21385, + "astId": 21384, "contract": "contracts/SubnetActorDiamond.sol:SubnetActorDiamond", - "label": "to", - "offset": 0, - "slot": "1", - "type": "t_struct(IPCAddress)21658_storage" + "label": "localNonce", + "offset": 1, + "slot": "0", + "type": "t_uint64" }, { - "astId": 21389, + "astId": 21387, "contract": "contracts/SubnetActorDiamond.sol:SubnetActorDiamond", - "label": "from", - "offset": 0, - "slot": "5", - "type": "t_struct(IPCAddress)21658_storage" + "label": "originalNonce", + "offset": 9, + "slot": "0", + "type": "t_uint64" }, { - "astId": 21392, + "astId": 21390, "contract": "contracts/SubnetActorDiamond.sol:SubnetActorDiamond", - "label": "localNonce", + "label": "value", "offset": 0, - "slot": "9", - "type": "t_uint64" + "slot": "1", + "type": "t_uint256" }, { - "astId": 21395, + "astId": 21394, "contract": "contracts/SubnetActorDiamond.sol:SubnetActorDiamond", - "label": "value", + "label": "to", "offset": 0, - "slot": "10", - "type": "t_uint256" + "slot": "2", + "type": "t_struct(IPCAddress)21658_storage" }, { "astId": 21398, "contract": "contracts/SubnetActorDiamond.sol:SubnetActorDiamond", - "label": "message", + "label": "from", "offset": 0, - "slot": "11", - "type": "t_bytes_storage" + "slot": "6", + "type": "t_struct(IPCAddress)21658_storage" }, { "astId": 21401, "contract": "contracts/SubnetActorDiamond.sol:SubnetActorDiamond", - "label": "originalNonce", + "label": "message", "offset": 0, - "slot": "12", - "type": "t_uint64" + "slot": "10", + "type": "t_bytes_storage" } ], - "numberOfBytes": "416" + "numberOfBytes": "352" }, "t_struct(MaxPQ)19752_storage": { "encoding": "inplace", diff --git a/contracts/.storage-layouts/SubnetActorModifiers.json b/contracts/.storage-layouts/SubnetActorModifiers.json index 37dd48fda..897adab58 100644 --- a/contracts/.storage-layouts/SubnetActorModifiers.json +++ b/contracts/.storage-layouts/SubnetActorModifiers.json @@ -403,55 +403,55 @@ "type": "t_enum(IpcMsgKind)21376" }, { - "astId": 21385, + "astId": 21384, "contract": "contracts/lib/LibSubnetActorStorage.sol:SubnetActorModifiers", - "label": "to", - "offset": 0, - "slot": "1", - "type": "t_struct(IPCAddress)21658_storage" + "label": "localNonce", + "offset": 1, + "slot": "0", + "type": "t_uint64" }, { - "astId": 21389, + "astId": 21387, "contract": "contracts/lib/LibSubnetActorStorage.sol:SubnetActorModifiers", - "label": "from", - "offset": 0, - "slot": "5", - "type": "t_struct(IPCAddress)21658_storage" + "label": "originalNonce", + "offset": 9, + "slot": "0", + "type": "t_uint64" }, { - "astId": 21392, + "astId": 21390, "contract": "contracts/lib/LibSubnetActorStorage.sol:SubnetActorModifiers", - "label": "localNonce", + "label": "value", "offset": 0, - "slot": "9", - "type": "t_uint64" + "slot": "1", + "type": "t_uint256" }, { - "astId": 21395, + "astId": 21394, "contract": "contracts/lib/LibSubnetActorStorage.sol:SubnetActorModifiers", - "label": "value", + "label": "to", "offset": 0, - "slot": "10", - "type": "t_uint256" + "slot": "2", + "type": "t_struct(IPCAddress)21658_storage" }, { "astId": 21398, "contract": "contracts/lib/LibSubnetActorStorage.sol:SubnetActorModifiers", - "label": "message", + "label": "from", "offset": 0, - "slot": "11", - "type": "t_bytes_storage" + "slot": "6", + "type": "t_struct(IPCAddress)21658_storage" }, { "astId": 21401, "contract": "contracts/lib/LibSubnetActorStorage.sol:SubnetActorModifiers", - "label": "originalNonce", + "label": "message", "offset": 0, - "slot": "12", - "type": "t_uint64" + "slot": "10", + "type": "t_bytes_storage" } ], - "numberOfBytes": "416" + "numberOfBytes": "352" }, "t_struct(MaxPQ)19752_storage": { "encoding": "inplace", diff --git a/contracts/contracts/structs/CrossNet.sol b/contracts/contracts/structs/CrossNet.sol index 857c75527..59be8dcaf 100644 --- a/contracts/contracts/structs/CrossNet.sol +++ b/contracts/contracts/structs/CrossNet.sol @@ -58,7 +58,6 @@ enum IpcMsgKind { /// @dev general-purpose cross-net transaction that call smart contracts. Call, /// @dev receipt from the execution of cross-net messages - /// (currently limited to `Transfer` messages) Result } @@ -66,34 +65,26 @@ enum IpcMsgKind { struct IpcEnvelope { /// @dev type of message being propagated. IpcMsgKind kind; - /// @dev destination of the message - /// It makes sense to extract from the encoded message - /// all shared fields required by all message, so they - /// can be inspected without having to decode the message. - IPCAddress to; - /// @dev address sending the message - IPCAddress from; /// @dev outgoing nonce for the envelope. /// This nonce is set by the gateway when committing the message for propagation. /// This nonce is changed on each network when the message is propagated, /// so it is unique for each network and prevents replay attacks. uint64 localNonce; - /// @dev Value being sent in the message. - /// For `Call` and `Result` kinds, the `value` field is synthetic and does not represent an actual token or native currency transfer. - /// Instead, it serves as metadata or an abstract representation of value to be interpreted by the target contract or receipt handler. - /// - /// For example, in a `Call` message, `value` might represent the intended payment amount for a service in a cross-network dApp, - /// allowing the receiving contract to process it as part of its logic, regardless of the actual token transfer mechanics. - /// Similarly, in a `Result` message, `value` might represent the outcome of a transaction, such as the total tokens minted or refunded. - /// - /// For `Transfer` messages, `value` represents the actual amount of native tokens being transferred across networks. - uint256 value; - /// @dev abi.encoded message - bytes message; /// @dev original nonce of the message from the source network. /// It is set once at the source network and remains unchanged during propagation. /// It is used to generate a unique tracing ID across networks, which is useful for debugging and auditing purposes. uint64 originalNonce; + /// @dev Value being sent in the message. + uint256 value; + /// @dev destination of the message + /// It makes sense to extract from the encoded message + /// all shared fields required by all message, so they + /// can be inspected without having to decode the message. + IPCAddress to; + /// @dev address sending the message + IPCAddress from; + /// @dev abi.encoded message + bytes message; /// @dev the gas limit is currently not used. // FIXME: currently not used. // uint256 gasLimit; diff --git a/contracts/test/helpers/SelectorLibrary.sol b/contracts/test/helpers/SelectorLibrary.sol index 740e9e41f..2ba79f835 100644 --- a/contracts/test/helpers/SelectorLibrary.sol +++ b/contracts/test/helpers/SelectorLibrary.sol @@ -41,14 +41,14 @@ library SelectorLibrary { if (keccak256(abi.encodePacked(facetName)) == keccak256(abi.encodePacked("GatewayMessengerFacet"))) { return abi.decode( - hex"000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000027f7999f400000000000000000000000000000000000000000000000000000000dcf7a9a000000000000000000000000000000000000000000000000000000000", + hex"000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000027f7999f4000000000000000000000000000000000000000000000000000000002c85ec2c00000000000000000000000000000000000000000000000000000000", (bytes4[]) ); } if (keccak256(abi.encodePacked(facetName)) == keccak256(abi.encodePacked("CheckpointingFacet"))) { return abi.decode( - hex"0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000453b4e7bf000000000000000000000000000000000000000000000000000000000add1e1f00000000000000000000000000000000000000000000000000000000d57d455700000000000000000000000000000000000000000000000000000000ac81837900000000000000000000000000000000000000000000000000000000", + hex"0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000453b4e7bf000000000000000000000000000000000000000000000000000000009db11d8c000000000000000000000000000000000000000000000000000000006326379f00000000000000000000000000000000000000000000000000000000ac81837900000000000000000000000000000000000000000000000000000000", (bytes4[]) ); } @@ -62,14 +62,14 @@ library SelectorLibrary { if (keccak256(abi.encodePacked(facetName)) == keccak256(abi.encodePacked("XnetMessagingFacet"))) { return abi.decode( - hex"00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000001b005393700000000000000000000000000000000000000000000000000000000", + hex"00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000001007cf1ec00000000000000000000000000000000000000000000000000000000", (bytes4[]) ); } if (keccak256(abi.encodePacked(facetName)) == keccak256(abi.encodePacked("SubnetActorGetterFacet"))) { return abi.decode( - hex"000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000223354c3e10000000000000000000000000000000000000000000000000000000035142c8c0000000000000000000000000000000000000000000000000000000006c46853000000000000000000000000000000000000000000000000000000004b27aa72000000000000000000000000000000000000000000000000000000004b0694e200000000000000000000000000000000000000000000000000000000b6797d3c000000000000000000000000000000000000000000000000000000008ef3f76100000000000000000000000000000000000000000000000000000000797196de00000000000000000000000000000000000000000000000000000000903e693000000000000000000000000000000000000000000000000000000000948628a900000000000000000000000000000000000000000000000000000000d92e8f12000000000000000000000000000000000000000000000000000000009de7025800000000000000000000000000000000000000000000000000000000c7cda762000000000000000000000000000000000000000000000000000000009754b29e0000000000000000000000000000000000000000000000000000000038a210b30000000000000000000000000000000000000000000000000000000080f76021000000000000000000000000000000000000000000000000000000005dd9147c00000000000000000000000000000000000000000000000000000000d6eb591000000000000000000000000000000000000000000000000000000000332a5ac9000000000000000000000000000000000000000000000000000000001597bf7e0000000000000000000000000000000000000000000000000000000052d182d1000000000000000000000000000000000000000000000000000000001904bb2e000000000000000000000000000000000000000000000000000000006ad04c7900000000000000000000000000000000000000000000000000000000cfca28240000000000000000000000000000000000000000000000000000000040550a1c00000000000000000000000000000000000000000000000000000000d081be03000000000000000000000000000000000000000000000000000000001f3a0e410000000000000000000000000000000000000000000000000000000072d0a0e000000000000000000000000000000000000000000000000000000000599c7bd1000000000000000000000000000000000000000000000000000000009e33bd0200000000000000000000000000000000000000000000000000000000c5ab224100000000000000000000000000000000000000000000000000000000f0cf6c9600000000000000000000000000000000000000000000000000000000ad81e4d60000000000000000000000000000000000000000000000000000000080875df700000000000000000000000000000000000000000000000000000000", + hex"000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000223354c3e10000000000000000000000000000000000000000000000000000000035142c8c0000000000000000000000000000000000000000000000000000000006c46853000000000000000000000000000000000000000000000000000000004b27aa72000000000000000000000000000000000000000000000000000000004b0694e200000000000000000000000000000000000000000000000000000000b6797d3c000000000000000000000000000000000000000000000000000000008ef3f761000000000000000000000000000000000000000000000000000000006b84e38300000000000000000000000000000000000000000000000000000000903e693000000000000000000000000000000000000000000000000000000000948628a900000000000000000000000000000000000000000000000000000000d92e8f12000000000000000000000000000000000000000000000000000000009de7025800000000000000000000000000000000000000000000000000000000c7cda762000000000000000000000000000000000000000000000000000000009754b29e0000000000000000000000000000000000000000000000000000000038a210b30000000000000000000000000000000000000000000000000000000080f76021000000000000000000000000000000000000000000000000000000005dd9147c00000000000000000000000000000000000000000000000000000000d6eb591000000000000000000000000000000000000000000000000000000000332a5ac9000000000000000000000000000000000000000000000000000000001597bf7e0000000000000000000000000000000000000000000000000000000052d182d1000000000000000000000000000000000000000000000000000000001904bb2e000000000000000000000000000000000000000000000000000000006ad04c7900000000000000000000000000000000000000000000000000000000cfca28240000000000000000000000000000000000000000000000000000000040550a1c00000000000000000000000000000000000000000000000000000000d081be03000000000000000000000000000000000000000000000000000000001f3a0e410000000000000000000000000000000000000000000000000000000072d0a0e000000000000000000000000000000000000000000000000000000000599c7bd1000000000000000000000000000000000000000000000000000000009e33bd0200000000000000000000000000000000000000000000000000000000c5ab224100000000000000000000000000000000000000000000000000000000f0cf6c9600000000000000000000000000000000000000000000000000000000ad81e4d60000000000000000000000000000000000000000000000000000000080875df700000000000000000000000000000000000000000000000000000000", (bytes4[]) ); } @@ -97,7 +97,7 @@ library SelectorLibrary { if (keccak256(abi.encodePacked(facetName)) == keccak256(abi.encodePacked("SubnetActorCheckpointingFacet"))) { return abi.decode( - hex"000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000026e029c3100000000000000000000000000000000000000000000000000000000cc2dc2b900000000000000000000000000000000000000000000000000000000", + hex"000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000025681656700000000000000000000000000000000000000000000000000000000cc2dc2b900000000000000000000000000000000000000000000000000000000", (bytes4[]) ); } diff --git a/contracts/test/integration/L2PlusXNet.t.sol b/contracts/test/integration/L2PlusXNet.t.sol index f896adba2..88e0281c9 100644 --- a/contracts/test/integration/L2PlusXNet.t.sol +++ b/contracts/test/integration/L2PlusXNet.t.sol @@ -148,7 +148,7 @@ contract L2PlusSubnetTest is Test, IntegrationTestBase, IIpcHandler { // there seems no auto way to derive the abi in string, check this before running tests, make sure // it's updated with the implementation new_topdown_message_topic = keccak256( - "NewTopDownMessage(address,(uint8,((uint64,address[]),(uint8,bytes)),((uint64,address[]),(uint8,bytes)),uint64,uint256,bytes,uint64),bytes32)" + "NewTopDownMessage(address,(uint8,uint64,uint64,uint256,((uint64,address[]),(uint8,bytes)),((uint64,address[]),(uint8,bytes)),bytes),bytes32)" ); // get some free money. diff --git a/docs/fendermint/general_cross_messages.md b/docs/fendermint/general_cross_messages.md new file mode 100644 index 000000000..159dc3a64 --- /dev/null +++ b/docs/fendermint/general_cross_messages.md @@ -0,0 +1,163 @@ +## Overview + +> :warning: **Caution** +> This document does not focus on simple transfers using the Transfer message kind (e.g., Gateway#release, an abstraction +> over Transfer). Instead, it covers the general message–building blocks implemented with the Call message kind, which enable +> communication across L2+ subnets and provide a foundation for higher-level communication abstractions. + +General cross-net messages enable the transmission of arbitrary messages between IPC subnets. They are particularly useful for transferring tokens (when supported, more on this later) or transmitting arbitrary data. This is not a feature that allows users to trigger messages from wallets; rather, it is a framework for cross-subnet contract-to-contract communication. + +Messages are transmitted across subnets using top-down and bottom-up mechanisms. For example, if a message is sent from a parent to a child subnet, the top-down mechanism is used, and vice versa. Similarly, messages between subnets not directly connected but sharing a common parent are routed automatically by the IPC system. + +## Limitations + +- Only contracts (not externally owned accounts, or EOAs) can invoke cross-net messages. +- Token transfers are only allowed if the source subnet, destination subnet, and all intermediate subnets share the same token. +- Since cross-net messages flow through the entire route between the source and destination subnets, multi-hop messages may take longer to be delivered. + +## Usage + +Messages are passed via the `Gateway` contract, which exposes the following method for triggering cross-net messages (only callable by another contract): + +```solidity +function sendContractXnetMessage( + IpcEnvelope calldata envelope +) external payable returns (IpcEnvelope memory committed); +``` + +The actual message is embedded within the IPC envelope as ABI-encoded bytes. + +### IPC Envelope Structure + +```solidity +struct IpcEnvelope { + /// @dev type of message being propagated. + IpcMsgKind kind; + /// @dev outgoing nonce for the envelope. + uint64 localNonce; + /// @dev original nonce of the message from the source network. + uint64 originalNonce; + /// @dev Value being sent in the message. + uint256 value; + /// @dev destination of the message + IPCAddress to; + /// @dev address sending the message + IPCAddress from; + /// @dev abi.encoded message + bytes message; +} + +``` + +### Message Types of Interest + +- **Call**: Represents a request sent to a destination contract. If the destination account is an externally owned account (EOA), the `Call` will fail and return an error in the `Result`. +- **Result**: Represents the response to a `Call` and `Transfer` messages, which could either be the result or an error report. + +### Contracts overview + +The source and destination contracts can be any contracts that implement the [IIpcHandler](../../contracts/sdk/interfaces/IIpcHandler.sol) interface. + +```solidity +function handleIpcMessage(IpcEnvelope calldata envelope) external payable returns (bytes memory ret); +``` + +The primary method for handling messages in a contract is the `handleIpcMessage` function from the IIpcHandler interface. The `handleIpcMessage` method is triggered either by the execution of messages included in a bottom-up checkpoint or by the execution of a finalized top-down finality message. Its return value is an ABI-encoded message, automatically added to the message field of the `Result` IpcEnvelope. If `handleIpcMessage` reverts, the error is propagated back as a Result with the `ActorErr` outcome type, but does not revert the caller. + +Although basic tasks such as decoding messages and handling errors must be implemented manually, you can simplify development by using the `IpcExchange` abstract contract from the [IPC SDK](../../contracts/sdk/IpcContract.sol). This contract provides convenience and ease of use for managing inter-process communication. + +### IPC SDK + +The IPC SDK simplifies development by providing an automatic implementation of the `handleIpcMessage` function and a convenient `performIpcCall` method. + +- **performIpcCall**: + This method calls `sendContractXnetMessage` on the Gateway and registers the message in an internal `inflightMsgs` map, indicating that a corresponding Result message is expected. +- **handleIpcMessage**: + When a message arrives, `handleIpcMessage` checks whether it is a `Call` or a `Result`. + For `Call` messages, it decodes the payload and invokes `_handleIpcCall`. + For `Result` messages, it verifies that the message corresponds to a tracked inflight message in the `inflightMsgs` map, then calls `_handleIpcResult`. If no matching entry is found, the process reverts with `UnrecognizedResult`. + +Both `_handleIpcCall` and `_handleIpcResult` must be implemented by the contract that extends the IPC SDK’s abstract `IpcExchange` contract. It is safe to revert within these functions: any revert will be returned in a `Result` message with the `ActorErr` outcome type, but will not revert the overall execution. + +When designing these handlers, ensure the contract remains within reasonable gas usage and block size constraints. For example, you can: + +- Offload heavy computations off-chain. +- Use “pull” over “push” patterns for transfers. +- Optimize Solidity code (e.g., minimize storage writes, use mappings). +- Break large tasks into smaller ones. + +Following these practices helps keep function calls within practical limits and ensures they can be included in a block successfully. + +The return value of `_handleIpcCall` becomes the return value of handleIpcMessage—an ABI-encoded message automatically added to the message field of the `Result` `IpcEnvelope`. + +```solidity +function _handleIpcCall( + IpcEnvelope memory envelope, + CallMsg memory callMsg +) internal virtual returns (bytes memory); +``` + +```solidity +function _handleIpcResult( + IpcEnvelope storage original, + IpcEnvelope memory result, + ResultMsg memory resultMsg +) internal virtual; +``` + +For an example, refer to [CrossMessengerCaller.sol](../../contracts/contracts/examples/CrossMessengerCaller.sol). + +## Results + +Results provide either error propagation or responses from the destination contract back to the caller. This creates a request-response mechanism where a `Call` or `Transfer` represents the request and a `Result` represents the response. + +The IPC envelope will have a `kind` of `Result`, and the message will follow the standardized schema below (see [CrossNet.sol](../../contracts/contracts/structs/CrossNet.sol)): + +```solidity +struct ResultMsg { + /// @dev ID of the envelope the result belongs to. + bytes32 id; + /// @dev Outcome of the call (success or type of error). + OutcomeType outcome; + /// @dev ABI-encoded return value or failure reason. + bytes ret; +} +``` + +### Outcome Types + +- **Ok**: Message was delivered successfully, and the `ret` field contains the response from the called contract. +- **SystemErr**: An error occurred during message transmission. The return value indicates the `InvalidXnetMessageReason` (see [IPCErrors.sol](../../contracts/contracts/errors/IPCErrors.sol)). +- **ActorErr**: A custom error returned by the called contract. + +### System Errors + +During message propagation, certain `SystemErr` errors can occur. These errors are returned in the `Result` message (via the `ret` field in `ResultMsg`) and then propagated back to the caller. The possible system errors are: + +- **Sender**: The message sender is an externally owned account (EOA), which is not permitted. +- **DstSubnet**: The destination subnet address is invalid or does not exist. +- **Nonce**: The message nonce does not match the expected nonce. +- **Value**: The transferred value is invalid (applicable only to Transfer messages, where value must not be zero). +- **Kind**: The message type (kind) is invalid. +- **ReflexiveSend**: The message’s source subnet matches its destination subnet (sending a message to itself). +- **NoRoute**: The message must travel upward, but there is no common parent subnet available. +- **IncompatibleSupplySource**: The supply source of the subnets is incompatible or mismatched. + +## How It Works + +1. A custom contract calls `sendContractXnetMessage` on the local `Gateway` with a message inside the IPC envelope (of kind `Call`). The envelope includes the destination subnet address, which is prefixed with the destination subnet path and is used for routing. +2. If the message passes validation, the gateway determines whether to send it upward (bottom-up) to a parent subnet or downward (top-down) to a child subnet. If it fails validation, the gateway rejects it. +3. If the message arrives at an intermediate subnet that is not the destination, it is stored in a postbox for further propagation. +4. Messages in the postbox are processed automatically. Based on their path, they are either sent to the parent via a bottom-up checkpoint or to the child subnet via a top-down message. +5. Steps 3 and 4 are repeated until the message either encounters an error or reaches the destination subnet. If an error occurs, a `Result` message is sent back to the source subnet. +6. Upon reaching the destination, the message is executed by invoking the handleIpcMessage method on the destination contract. A Result message is then sent back to the source subnet, which is possible because the IPC envelope includes the relayer’s address. +7. Steps 3 and 4 repeat until the `Result` message arrives at the source subnet, where it is executed on the original contract. + +## Debugging + +During message propagation, the following events are emitted by the subnet gateway, providing useful insights for debugging. Each event includes a unique message ID for cross-subnet tracking: + +- **`NewTopDownMessage`**: Emitted when a message is sent downward to a child subnet. +- **`QueuedBottomUpMessage`**: Emitted when a message is prepared for inclusion in a bottom-up checkpoint. +- **`MessageStoredInPostbox`**: Emitted when a message is received by an intermediate subnet and stored for further propagation. +- **`MessagePropagatedFromPostbox`**: Emitted when a message is sent from the postbox to the next subnet.