Skip to content

Commit

Permalink
feat: support l2 plus (#1157)
Browse files Browse the repository at this point in the history
Co-authored-by: cryptoAtwill <[email protected]>
Co-authored-by: cryptoAtwill <[email protected]>
Co-authored-by: raulk <[email protected]>
  • Loading branch information
4 people authored Jan 14, 2025
1 parent dafebd9 commit 967829f
Show file tree
Hide file tree
Showing 53 changed files with 3,363 additions and 1,170 deletions.
478 changes: 284 additions & 194 deletions contracts/.storage-layouts/GatewayActorModifiers.json

Large diffs are not rendered by default.

478 changes: 284 additions & 194 deletions contracts/.storage-layouts/GatewayDiamond.json

Large diffs are not rendered by default.

386 changes: 234 additions & 152 deletions contracts/.storage-layouts/SubnetActorDiamond.json

Large diffs are not rendered by default.

386 changes: 234 additions & 152 deletions contracts/.storage-layouts/SubnetActorModifiers.json

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions contracts/contracts/GatewayDiamond.sol
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,9 @@ import {BATCH_PERIOD, MAX_MSGS_PER_BATCH} from "./structs/CrossNet.sol";

error FunctionNotFound(bytes4 _functionSelector);

bool constant FEATURE_MULTILEVEL_CROSSMSG = false;
bool constant FEATURE_MULTILEVEL_CROSSMSG = true;
bool constant FEATURE_GENERAL_PUPRPOSE_CROSSMSG = true;
uint8 constant FEATURE_SUBNET_DEPTH = 2;
uint8 constant FEATURE_SUBNET_DEPTH = 10;

contract GatewayDiamond {
GatewayActorStorage internal s;
Expand Down
6 changes: 4 additions & 2 deletions contracts/contracts/errors/IPCErrors.sol
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ error AlreadyRegisteredSubnet();
error AlreadyInSet();
error CannotConfirmFutureChanges();
error CannotReleaseZero();
error CannotSendCrossMsgToItself();
error CheckpointAlreadyExists();
error BatchAlreadyExists();
error MaxMsgsPerBatchExceeded();
Expand Down Expand Up @@ -90,7 +89,10 @@ enum InvalidXnetMessageReason {
DstSubnet,
Nonce,
Value,
Kind
Kind,
ReflexiveSend,
NoRoute,
IncompatibleSupplySource
}

string constant ERR_PERMISSIONED_AND_BOOTSTRAPPED = "Method not allowed if permissioned is enabled and subnet bootstrapped";
Expand Down
72 changes: 72 additions & 0 deletions contracts/contracts/examples/CrossMessengerCaller.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
// SPDX-License-Identifier: MIT OR Apache-2.0
pragma solidity ^0.8.23;

import {FvmAddress} from "../structs/FvmAddress.sol";
import {SubnetID, IPCAddress} from "../structs/Subnet.sol";
import {IpcEnvelope, IpcMsgKind, CallMsg, ResultMsg} from "../structs/CrossNet.sol";
import {IGateway} from "../interfaces/IGateway.sol";
import {SubnetIDHelper} from "../lib/SubnetIDHelper.sol";
import {FvmAddressHelper} from "../lib/FvmAddressHelper.sol";
import {EMPTY_BYTES, METHOD_SEND} from "../constants/Constants.sol";
import {IpcExchange} from "../../sdk/IpcContract.sol";

interface ISubnetGetter {
function ipcGatewayAddr() external view returns (address);
function getParent() external view returns (SubnetID memory);
}

/// This is a simple example contract to invoke cross messages between subnets from different levels
contract CrossMessengerCaller is IpcExchange {
event CallReceived(IPCAddress from, CallMsg msg);
event ResultReceived(IpcEnvelope original, ResultMsg result);

uint256 public callsReceived;
uint256 public resultsReceived;

constructor(address gatewayAddr_) IpcExchange(gatewayAddr_) {
callsReceived = 0;
resultsReceived = 0;
}

function _handleIpcCall(
IpcEnvelope memory envelope,
CallMsg memory callMsg
) internal override returns (bytes memory) {
emit CallReceived(envelope.from, callMsg);
callsReceived += 1;
return EMPTY_BYTES;
}

function _handleIpcResult(
IpcEnvelope storage original,
IpcEnvelope memory,
ResultMsg memory resultMsg
) internal override {
resultsReceived += 1;
emit ResultReceived(original, resultMsg);
}

/// @dev Invoke a cross net send fund message from the current subnet to the target subnet
function invokeSendMessage(SubnetID calldata targetSubnet, address recipient, uint256 value) external {
IPCAddress memory to = IPCAddress({subnetId: targetSubnet, rawAddress: FvmAddressHelper.from(recipient)});
CallMsg memory message = CallMsg({method: abi.encodePacked(METHOD_SEND), params: EMPTY_BYTES});
invokeCrossMessage(to, message, value);
}

function invokeCrossMessage(IPCAddress memory to, CallMsg memory callMsg, uint256 value) internal {
// "sendContractXnetMessage" will handle the `from`
IPCAddress memory from;

IpcEnvelope memory envelope = IpcEnvelope({
kind: IpcMsgKind.Call,
from: from,
to: to,
value: value,
message: abi.encode(callMsg),
originalNonce: 0,
localNonce: 0
});

IGateway(gatewayAddr).sendContractXnetMessage(envelope);
}
}
4 changes: 4 additions & 0 deletions contracts/contracts/gateway/GatewayGetterFacet.sol
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,10 @@ contract GatewayGetterFacet {
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;
Expand Down
8 changes: 4 additions & 4 deletions contracts/contracts/gateway/GatewayManagerFacet.sol
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,7 @@ contract GatewayManagerFacet is GatewayActorModifiers, ReentrancyGuard {
revert InvalidXnetMessage(InvalidXnetMessageReason.Value);
}
// slither-disable-next-line unused-return
(bool registered, ) = LibGateway.getSubnet(subnetId);
(bool registered, Subnet storage subnet) = LibGateway.getSubnet(subnetId);
if (!registered) {
revert NotRegisteredSubnet();
}
Expand All @@ -158,7 +158,7 @@ contract GatewayManagerFacet is GatewayActorModifiers, ReentrancyGuard {
});

// commit top-down message.
LibGateway.commitTopDownMsg(crossMsg);
LibGateway.commitTopDownMsg(subnet, crossMsg);
}

/// @notice Sends funds to a specified subnet receiver using ERC20 tokens.
Expand All @@ -174,7 +174,7 @@ contract GatewayManagerFacet is GatewayActorModifiers, ReentrancyGuard {
revert InvalidXnetMessage(InvalidXnetMessageReason.Value);
}
// slither-disable-next-line unused-return
(bool registered, ) = LibGateway.getSubnet(subnetId);
(bool registered, Subnet storage subnet) = LibGateway.getSubnet(subnetId);
if (!registered) {
revert NotRegisteredSubnet();
}
Expand All @@ -199,7 +199,7 @@ contract GatewayManagerFacet is GatewayActorModifiers, ReentrancyGuard {
});

// Commit top-down message.
LibGateway.commitTopDownMsg(crossMsg);
LibGateway.commitTopDownMsg(subnet, crossMsg);
}

/// @notice release() burns the received value locally in subnet and commits a bottom-up message to release the assets in the parent.
Expand Down
64 changes: 31 additions & 33 deletions contracts/contracts/gateway/GatewayMessengerFacet.sol
Original file line number Diff line number Diff line change
Expand Up @@ -4,34 +4,41 @@ pragma solidity ^0.8.23;
import {GatewayActorModifiers} from "../lib/LibGatewayActorStorage.sol";
import {IpcEnvelope, CallMsg, IpcMsgKind} from "../structs/CrossNet.sol";
import {IPCMsgType} from "../enums/IPCMsgType.sol";
import {SubnetID, AssetKind, IPCAddress} from "../structs/Subnet.sol";
import {InvalidXnetMessage, InvalidXnetMessageReason, CannotSendCrossMsgToItself, MethodNotAllowed} from "../errors/IPCErrors.sol";
import {Subnet, SubnetID, AssetKind, IPCAddress, Asset} from "../structs/Subnet.sol";
import {InvalidXnetMessage, InvalidXnetMessageReason, MethodNotAllowed} from "../errors/IPCErrors.sol";
import {SubnetIDHelper} from "../lib/SubnetIDHelper.sol";
import {LibGateway} from "../lib/LibGateway.sol";
import {FilAddress} from "fevmate/contracts/utils/FilAddress.sol";
import {AssetHelper} from "../lib/AssetHelper.sol";
import {CrossMsgHelper} from "../lib/CrossMsgHelper.sol";
import {FvmAddressHelper} from "../lib/FvmAddressHelper.sol";
import {ISubnetActor} from "../interfaces/ISubnetActor.sol";

import {EnumerableSet} from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol";

string constant ERR_GENERAL_CROSS_MSG_DISABLED = "Support for general-purpose cross-net messages is disabled";
string constant ERR_MULTILEVEL_CROSS_MSG_DISABLED = "Support for multi-level cross-net messages is disabled";

contract GatewayMessengerFacet is GatewayActorModifiers {
using FilAddress for address payable;
using SubnetIDHelper for SubnetID;
using EnumerableSet for EnumerableSet.Bytes32Set;
using CrossMsgHelper for IpcEnvelope;
using AssetHelper for Asset;

/**
* @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: 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.
* @param envelope - the original envelope, which will be validated, stamped, and committed during the send.
* @return committed envelope.
*/
function sendContractXnetMessage(
IpcEnvelope calldata envelope
IpcEnvelope memory envelope
) external payable returns (IpcEnvelope memory committed) {
if (!s.generalPurposeCrossMsg) {
revert MethodNotAllowed(ERR_GENERAL_CROSS_MSG_DISABLED);
Expand All @@ -42,28 +49,33 @@ 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);
}

// Will revert if the message won't deserialize into a CallMsg.
abi.decode(envelope.message, (CallMsg));

committed = IpcEnvelope({
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
// nonce and originalNonce will be updated by LibGateway.commitValidatedCrossMessage
originalNonce: 0,
localNonce: 0
});

(bool valid, InvalidXnetMessageReason reason, IPCMsgType applyType) = committed.validateCrossMessage();
if (!valid) {
revert InvalidXnetMessage(reason);
}

if (applyType == IPCMsgType.TopDown) {
(, SubnetID memory nextHop) = committed.to.subnetId.down(s.networkName);
// lock funds on the current subnet gateway for the next hop
ISubnetActor(nextHop.getActor()).supplySource().lock(envelope.value);
}

// Commit xnet message for dispatch.
bool shouldBurn = LibGateway.commitCrossMessage(committed);
bool shouldBurn = LibGateway.commitValidatedCrossMessage(committed);

// Apply side effects, such as burning funds.
LibGateway.crossMsgSideEffects({v: committed.value, shouldBurn: shouldBurn});
Expand All @@ -75,23 +87,9 @@ contract GatewayMessengerFacet is GatewayActorModifiers {
}

/**
* @dev propagates the populated cross net message for the given cid
* @param msgCid - the cid of the cross-net message
* @dev Propagates all the populated cross-net messages from the postbox.
*/
function propagate(bytes32 msgCid) external payable {
if (!s.multiLevelCrossMsg) {
revert MethodNotAllowed(ERR_MULTILEVEL_CROSS_MSG_DISABLED);
}

IpcEnvelope storage crossMsg = s.postbox[msgCid];

bool shouldBurn = LibGateway.commitCrossMessage(crossMsg);
// We must delete the message first to prevent potential re-entrancies,
// and as the message is deleted and we don't have a reference to the object
// anymore, we need to pull the data from the message to trigger the side-effects.
uint256 v = crossMsg.value;
delete s.postbox[msgCid];

LibGateway.crossMsgSideEffects({v: v, shouldBurn: shouldBurn});
function propagateAll() external payable {
LibGateway.propagateAllPostboxMessages();
}
}
11 changes: 9 additions & 2 deletions contracts/contracts/gateway/router/CheckpointingFacet.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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";

import {ActivityRollupRecorded, FullActivityRollup} from "../../structs/Activity.sol";
Expand All @@ -23,6 +23,9 @@ contract CheckpointingFacet is GatewayActorModifiers {
using SubnetIDHelper for SubnetID;
using CrossMsgHelper for IpcEnvelope;

/// @dev Emitted when a checkpoint is committed to gateway.
event CheckpointCommitted(address indexed subnet, uint256 subnetHeight);

/// @notice submit a verified checkpoint in the gateway to trigger side-effects.
/// @dev this method is called by the corresponding subnet actor.
/// Called from a subnet actor if the checkpoint is cryptographically valid.
Expand All @@ -43,6 +46,8 @@ contract CheckpointingFacet is GatewayActorModifiers {
LibGateway.checkMsgLength(checkpoint.msgs);

execBottomUpMsgs(checkpoint.msgs, subnet);

emit CheckpointCommitted({subnet: checkpoint.subnetID.getAddress(), subnetHeight: checkpoint.blockHeight});
}

/// @notice creates a new bottom-up checkpoint
Expand Down Expand Up @@ -129,7 +134,9 @@ contract CheckpointingFacet is GatewayActorModifiers {
uint256 crossMsgLength = msgs.length;

for (uint256 i; i < crossMsgLength; ) {
totalValue += msgs[i].value;
if (msgs[i].kind != IpcMsgKind.Call) {
totalValue += msgs[i].value;
}
unchecked {
++i;
}
Expand Down
3 changes: 2 additions & 1 deletion contracts/contracts/gateway/router/XnetMessagingFacet.sol
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ contract XnetMessagingFacet is GatewayActorModifiers {
/// @dev It requires the caller to be the system actor.
/// @param crossMsgs The array of cross-network messages to be applied.
function applyCrossMessages(IpcEnvelope[] calldata crossMsgs) external systemActorOnly {
LibGateway.applyMessages(s.networkName.getParentSubnet(), crossMsgs);
LibGateway.applyTopDownMessages(s.networkName.getParentSubnet(), crossMsgs);
LibGateway.propagateAllPostboxMessages();
}
}
4 changes: 2 additions & 2 deletions contracts/contracts/interfaces/IGateway.sol
Original file line number Diff line number Diff line change
Expand Up @@ -62,8 +62,8 @@ interface IGateway {
IpcEnvelope calldata envelope
) external payable returns (IpcEnvelope memory committed);

/// @notice Propagates the stored postbox item for the given cid
function propagate(bytes32 msgCid) external payable;
/// @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;
Expand Down
9 changes: 9 additions & 0 deletions contracts/contracts/interfaces/ISubnetActor.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
// SPDX-License-Identifier: MIT OR Apache-2.0
pragma solidity ^0.8.23;

import {Asset} from "../structs/Subnet.sol";

/// @title Subnet actor interface
interface ISubnetActor {
function supplySource() external view returns (Asset memory);
}
Loading

0 comments on commit 967829f

Please sign in to comment.