Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: support l2 plus #1157

Open
wants to merge 20 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
356 changes: 182 additions & 174 deletions contracts/.storage-layouts/GatewayActorModifiers.json

Large diffs are not rendered by default.

354 changes: 181 additions & 173 deletions contracts/.storage-layouts/GatewayDiamond.json

Large diffs are not rendered by default.

278 changes: 139 additions & 139 deletions contracts/.storage-layouts/SubnetActorDiamond.json

Large diffs are not rendered by default.

280 changes: 140 additions & 140 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
1 change: 1 addition & 0 deletions contracts/contracts/errors/IPCErrors.sol
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ error InvalidFederationPayload();
error DuplicatedGenesisValidator();
error NotEnoughGenesisValidators();
error ValidatorPowerChangeDenied();
error UnroutableMessage(string reason);

enum InvalidXnetMessageReason {
Sender,
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
46 changes: 24 additions & 22 deletions contracts/contracts/gateway/GatewayMessengerFacet.sol
Original file line number Diff line number Diff line change
Expand Up @@ -4,21 +4,25 @@ 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} from "../structs/Subnet.sol";
import {InvalidXnetMessage, InvalidXnetMessageReason, CannotSendCrossMsgToItself, MethodNotAllowed, UnroutableMessage} from "../errors/IPCErrors.sol";
import {SubnetIDHelper} from "../lib/SubnetIDHelper.sol";
import {LibGateway} from "../lib/LibGateway.sol";
import {LibGateway, CrossMessageValidationOutcome} 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 {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;
karlem marked this conversation as resolved.
Show resolved Hide resolved
using CrossMsgHelper for IpcEnvelope;

/**
* @dev Sends a general-purpose cross-message from the local subnet to the destination subnet.
Expand All @@ -31,7 +35,7 @@ contract GatewayMessengerFacet is GatewayActorModifiers {
* @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 Down Expand Up @@ -62,8 +66,20 @@ contract GatewayMessengerFacet is GatewayActorModifiers {
nonce: 0 // nonce will be updated by LibGateway.commitCrossMessage
});

CrossMessageValidationOutcome outcome = committed.validateCrossMessage();

if (outcome != CrossMessageValidationOutcome.Valid) {
if (outcome == CrossMessageValidationOutcome.InvalidDstSubnet) {
revert InvalidXnetMessage(InvalidXnetMessageReason.DstSubnet);
} else if (outcome == CrossMessageValidationOutcome.CannotSendToItself) {
revert CannotSendCrossMsgToItself();
} else if (outcome == CrossMessageValidationOutcome.CommonParentNotExist) {
revert UnroutableMessage("no common parent");
}
}

// 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 +91,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();
}
}
5 changes: 5 additions & 0 deletions contracts/contracts/gateway/router/CheckpointingFacet.sol
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,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 @@ -41,6 +44,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
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);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

applyCrossMessages only handles topdown messages?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, it is only used to apply top-down messages locally after they have been finalized in Fendermint.

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 @@ -61,8 +61,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
24 changes: 23 additions & 1 deletion contracts/contracts/lib/AssetHelper.sol
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.23;

import {NotEnoughBalance} from "../errors/IPCErrors.sol";
import {NotEnoughBalance, InvalidSubnetActor} from "../errors/IPCErrors.sol";
import {Asset, AssetKind} from "../structs/Subnet.sol";
import {EMPTY_BYTES} from "../constants/Constants.sol";
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
Expand Down Expand Up @@ -145,6 +145,15 @@ library AssetHelper {
return (success, ret);
}

/// @notice Checks if the given address is a contract.
function isContract(address account) internal view returns (bool) {
uint256 size;
assembly {
size := extcodesize(account)
}
return size > 0;
}

/// @dev Adaptation from implementation `openzeppelin-contracts/utils/Address.sol`
/// that doesn't revert immediately in case of failure and merely notifies of the outcome.
function functionCallWithValue(
Expand All @@ -156,6 +165,10 @@ library AssetHelper {
revert NotEnoughBalance();
}

if (!isContract(target)) {
revert InvalidSubnetActor();
}

return target.call{value: value}(data);
}

Expand Down Expand Up @@ -196,6 +209,15 @@ library AssetHelper {
}
}

// @notice Gets the balance of the account.
function balanceOf(Asset memory asset, address holder) internal view returns (uint256 ret) {
if (asset.kind == AssetKind.Native) {
ret = holder.balance;
} else if (asset.kind == AssetKind.ERC20) {
ret = IERC20(asset.tokenAddress).balanceOf(holder);
}
}

// @notice Makes the asset available for spending by the given spender, without actually sending it.
// @return msgValue The amount of msg.value that needs to be sent along with the subsequent call that will _actually_ spend that asset.
// Will be 0 if the asset is a token, since no native coins are to be sent.
Expand Down
8 changes: 8 additions & 0 deletions contracts/contracts/lib/CrossMsgHelper.sol
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {IPCMsgType} from "../enums/IPCMsgType.sol";
import {SubnetID, IPCAddress} from "../structs/Subnet.sol";
import {SubnetIDHelper} from "../lib/SubnetIDHelper.sol";
import {FvmAddressHelper} from "../lib/FvmAddressHelper.sol";
import {LibGateway, CrossMessageValidationOutcome} from "../lib/LibGateway.sol";
import {FvmAddress} from "../structs/FvmAddress.sol";
import {FilAddress} from "fevmate/contracts/utils/FilAddress.sol";
import {Address} from "@openzeppelin/contracts/utils/Address.sol";
Expand Down Expand Up @@ -67,6 +68,7 @@ library CrossMsgHelper {
bytes memory ret
) public pure returns (IpcEnvelope memory) {
ResultMsg memory message = ResultMsg({id: toHash(crossMsg), outcome: outcome, ret: ret});

uint256 value = crossMsg.value;
if (outcome == OutcomeType.Ok) {
// if the message was executed successfully, the value stayed
Expand All @@ -84,6 +86,7 @@ library CrossMsgHelper {
});
}

// creates transfer message from the child subnet to the parent subnet
function createReleaseMsg(
SubnetID calldata subnet,
address signer,
Expand All @@ -98,6 +101,7 @@ library CrossMsgHelper {
);
}

// creates transfer message from the parent subnet to the child subnet
function createFundMsg(
SubnetID calldata subnet,
address signer,
Expand Down Expand Up @@ -199,4 +203,8 @@ library CrossMsgHelper {

return true;
}

function validateCrossMessage(IpcEnvelope memory crossMsg) internal view returns (CrossMessageValidationOutcome) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

caller why not just invoke LibGateway directly instead, it's internal anyways.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Raul has suggested a nice syntax sugar update. So I have added it after his comment.

return LibGateway.validateCrossMessage(crossMsg);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I still kind of feel CrossMessageValidationOutcome should not be exposed, unless different callers might handle the outcomes differently? Now every call of validateCrossMessage must handle the outcome, dont feel it's necessary though.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

unless different callers might handle the outcomes differently? - that's exactly the case :)

}
}
Loading
Loading