diff --git a/contracts/contracts/gateway/GatewayMessengerFacet.sol b/contracts/contracts/gateway/GatewayMessengerFacet.sol index cc2a39d87..4bf23ff80 100644 --- a/contracts/contracts/gateway/GatewayMessengerFacet.sol +++ b/contracts/contracts/gateway/GatewayMessengerFacet.sol @@ -25,15 +25,16 @@ contract GatewayMessengerFacet is GatewayActorModifiers { using CrossMsgHelper for IpcEnvelope; /** - * @dev Sends a general-purpose cross-message from the local subnet to the destination subnet. - * Any value in msg.value will be forwarded in the call. - * - * IMPORTANT: Only smart contracts are allowed to trigger these cross-net messages. User wallets can send funds - * from their address to the destination subnet and then run the transaction in the destination normally. - * - * @param envelope - the original envelope, which will be validated, stamped and committed during the send. - * @return committed envelope. - */ + * @dev Sends a general-purpose cross-message from the local subnet to the destination subnet. + * IMPORTANT: Native tokens via msg.value are treated as a contribution toward gas costs associated with message propagation. + * There is no strict enforcement of the exact gas cost, and any msg.value provided will be accepted. + * + * IMPORTANT: Only smart contracts are allowed to trigger these cross-net messages. User wallets can send funds + * from their address to the destination subnet and then run the transaction in the destination normally. + * + * @param envelope - the original envelope, which will be validated, stamped, and committed during the send. + * @return committed envelope. + */ function sendContractXnetMessage( IpcEnvelope memory envelope ) external payable returns (IpcEnvelope memory committed) { @@ -46,10 +47,6 @@ contract GatewayMessengerFacet is GatewayActorModifiers { revert InvalidXnetMessage(InvalidXnetMessageReason.Sender); } - if (envelope.value != msg.value) { - revert InvalidXnetMessage(InvalidXnetMessageReason.Value); - } - if (envelope.kind != IpcMsgKind.Call) { revert InvalidXnetMessage(InvalidXnetMessageReason.Kind); } @@ -61,7 +58,7 @@ contract GatewayMessengerFacet is GatewayActorModifiers { kind: IpcMsgKind.Call, from: IPCAddress({subnetId: s.networkName, rawAddress: FvmAddressHelper.from(msg.sender)}), to: envelope.to, - value: msg.value, + value: envelope.value, message: envelope.message, nonce: 0 // nonce will be updated by LibGateway.commitCrossMessage }); diff --git a/contracts/contracts/gateway/router/CheckpointingFacet.sol b/contracts/contracts/gateway/router/CheckpointingFacet.sol index 74666a501..7997063bb 100644 --- a/contracts/contracts/gateway/router/CheckpointingFacet.sol +++ b/contracts/contracts/gateway/router/CheckpointingFacet.sol @@ -14,7 +14,7 @@ import {NotRegisteredSubnet, SubnetNotActive, SubnetNotFound, InvalidSubnet, Che import {BatchNotCreated, InvalidBatchEpoch, BatchAlreadyExists, NotEnoughSubnetCircSupply, InvalidCheckpointEpoch} from "../../errors/IPCErrors.sol"; import {CrossMsgHelper} from "../../lib/CrossMsgHelper.sol"; -import {IpcEnvelope, SubnetID} from "../../structs/CrossNet.sol"; +import {IpcEnvelope, SubnetID, IpcMsgKind} from "../../structs/CrossNet.sol"; import {SubnetIDHelper} from "../../lib/SubnetIDHelper.sol"; contract CheckpointingFacet is GatewayActorModifiers { @@ -127,7 +127,9 @@ contract CheckpointingFacet is GatewayActorModifiers { uint256 crossMsgLength = msgs.length; for (uint256 i; i < crossMsgLength; ) { - totalValue += msgs[i].value; + if (msgs[i].kind == IpcMsgKind.Transfer) { + totalValue += msgs[i].value; + } unchecked { ++i; } @@ -138,7 +140,7 @@ contract CheckpointingFacet is GatewayActorModifiers { if (subnet.circSupply < totalAmount) { revert NotEnoughSubnetCircSupply(); } - + subnet.circSupply -= totalAmount; // execute cross-messages diff --git a/contracts/contracts/lib/LibGateway.sol b/contracts/contracts/lib/LibGateway.sol index a86e84f67..9a882caa6 100644 --- a/contracts/contracts/lib/LibGateway.sol +++ b/contracts/contracts/lib/LibGateway.sol @@ -250,7 +250,9 @@ library LibGateway { crossMessage.nonce = topDownNonce; subnet.topDownNonce = topDownNonce + 1; - subnet.circSupply += crossMessage.value; + if (crossMessage.kind == IpcMsgKind.Transfer) { + subnet.circSupply += crossMessage.value; + } emit NewTopDownMessage({subnet: subnet.id.getAddress(), message: crossMessage, id: crossMessage.toDeterministicHash()}); } diff --git a/contracts/contracts/structs/CrossNet.sol b/contracts/contracts/structs/CrossNet.sol index 368554b60..5c19680bb 100644 --- a/contracts/contracts/structs/CrossNet.sol +++ b/contracts/contracts/structs/CrossNet.sol @@ -73,10 +73,15 @@ struct IpcEnvelope { /// @dev outgoing nonce for the envelope. /// This nonce is set by the gateway when committing the message for propagation uint64 nonce; - /// @dev value being sent in the message. - /// If we want receipts to return value, and all messages to be able - /// to handle different supply sources we can expose the value - /// as a common field. + /// @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; diff --git a/contracts/test/integration/GatewayDiamond.t.sol b/contracts/test/integration/GatewayDiamond.t.sol index ad039514f..d534b0602 100644 --- a/contracts/test/integration/GatewayDiamond.t.sol +++ b/contracts/test/integration/GatewayDiamond.t.sol @@ -538,28 +538,6 @@ contract GatewayActorDiamondTest is Test, IntegrationTestBase, SubnetWithNativeT gatewayDiamond.manager().kill(); } - function testGatewayDiamond_SendCrossMessage_Fails_NoFunds() public { - address caller = address(new MockIpcContract()); - vm.startPrank(caller); - vm.deal(caller, DEFAULT_COLLATERAL_AMOUNT + DEFAULT_CROSS_MSG_FEE + 2); - registerSubnet(DEFAULT_COLLATERAL_AMOUNT, caller); - - SubnetID memory destinationSubnet = gatewayDiamond.getter().getNetworkName().createSubnetId(caller); - - vm.expectRevert(abi.encodeWithSelector(InvalidXnetMessage.selector, InvalidXnetMessageReason.Value)); - gatewayDiamond.messenger().sendContractXnetMessage{value: DEFAULT_CROSS_MSG_FEE}( - TestUtils.newXnetCallMsg( - IPCAddress({ - subnetId: SubnetID({root: ROOTNET_CHAINID, route: new address[](0)}), - rawAddress: FvmAddressHelper.from(caller) - }), - IPCAddress({subnetId: destinationSubnet, rawAddress: FvmAddressHelper.from(caller)}), - 1, - 0 - ) - ); - } - function testGatewayDiamond_SendCrossMessage_Fails_Fuzz(uint256 fee) public { vm.assume(fee < DEFAULT_CROSS_MSG_FEE); @@ -878,27 +856,6 @@ contract GatewayActorDiamondTest is Test, IntegrationTestBase, SubnetWithNativeT ); } - function testGatewayDiamond_SendCrossMessage_Fails_Failes_InvalidCrossMsgValue() public { - address caller = address(new MockIpcContract()); - vm.startPrank(caller); - vm.deal(caller, DEFAULT_COLLATERAL_AMOUNT + DEFAULT_CROSS_MSG_FEE + 2); - registerSubnet(DEFAULT_COLLATERAL_AMOUNT, caller); - SubnetID memory destinationSubnet = gatewayDiamond.getter().getNetworkName().createSubnetId(caller); - - vm.expectRevert(abi.encodeWithSelector(InvalidXnetMessage.selector, InvalidXnetMessageReason.Value)); - gatewayDiamond.messenger().sendContractXnetMessage{value: DEFAULT_CROSS_MSG_FEE}( - TestUtils.newXnetCallMsg( - IPCAddress({ - subnetId: SubnetID({root: ROOTNET_CHAINID, route: new address[](0)}), - rawAddress: FvmAddressHelper.from(caller) - }), - IPCAddress({subnetId: destinationSubnet, rawAddress: FvmAddressHelper.from(caller)}), - 5, - 0 - ) - ); - } - // TODO: this is no longer possible because EOA cannot be subnet // function testGatewayDiamond_SendCrossMessage_Fails_EoACaller() public { // address caller = vm.addr(100); @@ -1215,7 +1172,8 @@ contract GatewayActorDiamondTest is Test, IntegrationTestBase, SubnetWithNativeT gatewayDiamond.checkpointer().commitCheckpoint(checkpoint); (, subnetInfo) = gatewayDiamond.getter().getSubnet(subnetId); - require(subnetInfo.circSupply == DEFAULT_COLLATERAL_AMOUNT - 10 * amount, "unexpected circulating supply"); + // cross net messages with Call kind does not affect the circulating supply + require(subnetInfo.circSupply == DEFAULT_COLLATERAL_AMOUNT, "unexpected circulating supply"); } function testGatewayDiamond_listIncompleteCheckpoints() public {