Skip to content
This repository has been archived by the owner on Jan 11, 2024. It is now read-only.

Commit

Permalink
ERC20: enable a subnet's supply to be determined by an ERC20 token in…
Browse files Browse the repository at this point in the history
… the parent.

This commit introduces the concept of the "supply strategy". This configuration
object determines the kind of supply backing a subnet. It can be "native" or "ERC20".
In the latter case, the token address must be provided. We verify that the address
indeed corresponds to an ERC20 token.

This commit also adds a funcWithToken() external non-payable function to the GatewayManagerFacet.
This is analogous to fund(), but for token supply (where the amount must be passed as a parameter).

fund() and fundWithToken() validate that the expected supply kind is being used.
  • Loading branch information
raulk committed Dec 10, 2023
1 parent 1a6ed2a commit fe6ae46
Show file tree
Hide file tree
Showing 6 changed files with 78 additions and 8 deletions.
18 changes: 16 additions & 2 deletions src/SubnetActorDiamond.sol
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,12 @@ import {IDiamond} from "./interfaces/IDiamond.sol";
import {IDiamondCut} from "./interfaces/IDiamondCut.sol";
import {IDiamondLoupe} from "./interfaces/IDiamondLoupe.sol";
import {IERC165} from "./interfaces/IERC165.sol";
import {GatewayCannotBeZero, NotGateway, InvalidSubmissionPeriod, InvalidCollateral, InvalidMajorityPercentage, InvalidPowerScale} from "./errors/IPCErrors.sol";
import {GatewayCannotBeZero, NotGateway, InvalidSubmissionPeriod, InvalidCollateral, InvalidMajorityPercentage, InvalidPowerScale, InvalidERC20Address} from "./errors/IPCErrors.sol";
import {LibDiamond} from "./lib/LibDiamond.sol";
import {SubnetID} from "./structs/Subnet.sol";
import {SubnetID, SupplyKind, SupplyStrategy} from "./structs/Subnet.sol";
import {SubnetIDHelper} from "./lib/SubnetIDHelper.sol";
import {LibStaking} from "./lib/LibStaking.sol";
import {IERC20} from "openzeppelin-contracts/token/ERC20/IERC20.sol";

error FunctionNotFound(bytes4 _functionSelector);

Expand All @@ -32,6 +33,7 @@ contract SubnetActorDiamond {
uint256 minCrossMsgFee;
int8 powerScale;
bool permissioned;
SupplyStrategy supplyStrategy;
}

constructor(IDiamond.FacetCut[] memory _diamondCut, ConstructorParams memory params) {
Expand All @@ -51,6 +53,16 @@ contract SubnetActorDiamond {
if (params.powerScale > 18) {
revert InvalidPowerScale();
}
if (params.supplyStrategy.kind == SupplyKind.ERC20) {
if (params.supplyStrategy.tokenAddress == address(0)) {
revert InvalidERC20Address();
}
// We require that the ERC20 token exists beforehand.
// The call to balanceOf will revert if the supplied address does not exist, or if it's not an ERC20 contract.
// Ideally we'd be able to use ERC165 to check if the contract implements the ERC20 standard, but the latter does not support supportsInterface().
IERC20 token = IERC20(params.supplyStrategy.tokenAddress);
token.balanceOf(address(this));
}

LibDiamond.setContractOwner(msg.sender);
LibDiamond.diamondCut({_diamondCut: _diamondCut, _init: address(0), _calldata: new bytes(0)});
Expand Down Expand Up @@ -79,6 +91,8 @@ contract SubnetActorDiamond {
// The startConfiguration number is also 1 to match with nextConfigurationNumber, indicating we have
// empty validator change logs
s.changeSet.startConfigurationNumber = LibStaking.INITIAL_CONFIGURATION_NUMBER;
// Set the supply strategy.
s.supplyStrategy = params.supplyStrategy;
}

function _fallback() internal {
Expand Down
4 changes: 3 additions & 1 deletion src/errors/IPCErrors.sol
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ error InvalidCrossMsgDstSubnet();
error InvalidCrossMsgFromSubnet();
error InvalidCrossMsgNonce();
error InvalidCrossMsgValue();
error InvalidERC20Address();
error InvalidMajorityPercentage();
error InvalidPowerScale();
error InvalidRetentionHeight();
Expand Down Expand Up @@ -61,12 +62,13 @@ error PQEmpty();
error ParentFinalityAlreadyCommitted();
error PostboxNotExist();
error SignatureReplay();
error SubnetAlreadyBootstrapped();
error SubnetAlreadyKilled();
error SubnetNotActive();
error SubnetNotFound();
error UnexpectedSupplyStrategy();
error WithdrawExceedingCollateral();
error ZeroMembershipWeight();
error SubnetAlreadyBootstrapped();
error FacetCannotBeZero();
error WrongGateway();
error CannotFindSubnet();
Expand Down
42 changes: 39 additions & 3 deletions src/gateway/GatewayManagerFacet.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,22 +2,27 @@
pragma solidity 0.8.19;

import {GatewayActorModifiers} from "../lib/LibGatewayActorStorage.sol";
import {SubnetActorGetterFacet} from "../subnet/SubnetActorGetterFacet.sol";
import {BURNT_FUNDS_ACTOR} from "../constants/Constants.sol";
import {CrossMsg} from "../structs/Checkpoint.sol";
import {Status} from "../enums/Status.sol";
import {FvmAddress} from "../structs/FvmAddress.sol";
import {SubnetID, Subnet} from "../structs/Subnet.sol";
import {Membership} from "../structs/Subnet.sol";
import {AlreadyRegisteredSubnet, CannotReleaseZero, NotEnoughFunds, NotEnoughFundsToRelease, NotEnoughCollateral, NotEmptySubnetCircSupply, NotRegisteredSubnet, InvalidCrossMsgValue} from "../errors/IPCErrors.sol";
import {SubnetID, Subnet, SupplyStrategy} from "../structs/Subnet.sol";
import {Membership, SupplyKind} from "../structs/Subnet.sol";
import {AlreadyRegisteredSubnet, CannotReleaseZero, NotEnoughFunds, NotEnoughFundsToRelease, NotEnoughCollateral, NotEmptySubnetCircSupply, NotRegisteredSubnet, InvalidCrossMsgValue, UnexpectedSupplyStrategy} from "../errors/IPCErrors.sol";
import {LibGateway} from "../lib/LibGateway.sol";
import {SubnetIDHelper} from "../lib/SubnetIDHelper.sol";
import {CrossMsgHelper} from "../lib/CrossMsgHelper.sol";
import {FilAddress} from "fevmate/utils/FilAddress.sol";
import {ReentrancyGuard} from "../lib/LibReentrancyGuard.sol";
import {IERC20} from "openzeppelin-contracts/token/ERC20/IERC20.sol";
import {SafeERC20} from "openzeppelin-contracts/token/ERC20/utils/SafeERC20.sol";
import {Address} from "openzeppelin-contracts/utils/Address.sol";

contract GatewayManagerFacet is GatewayActorModifiers, ReentrancyGuard {
using FilAddress for address payable;
using SubnetIDHelper for SubnetID;
using SafeERC20 for IERC20;

/// @notice register a subnet in the gateway. It is called by a subnet when it reaches the threshold stake
/// @dev The subnet can optionally pass a genesis circulating supply that would be pre-allocated in the
Expand Down Expand Up @@ -142,6 +147,12 @@ contract GatewayManagerFacet is GatewayActorModifiers, ReentrancyGuard {
// prevent spamming if there's no value to fund.
revert InvalidCrossMsgValue();
}
// Validate that the supply strategy is native.
SupplyStrategy memory supplyStrategy = SubnetActorGetterFacet(subnetId.getActor()).supplyStrategy();
if (supplyStrategy.kind != SupplyKind.Native) {
revert UnexpectedSupplyStrategy();
}

CrossMsg memory crossMsg = CrossMsgHelper.createFundMsg({
subnet: subnetId,
signer: msg.sender,
Expand All @@ -154,6 +165,31 @@ contract GatewayManagerFacet is GatewayActorModifiers, ReentrancyGuard {
LibGateway.commitTopDownMsg(crossMsg);
}

function fundWithToken(SubnetID calldata subnetId, FvmAddress calldata to, uint256 amount) external nonReentrant {
// Check that the supply strategy is ERC20.
// There is no need to check whether the subnet exists. If it doesn't exist, the call to getter will revert.
// LibGateway.commitTopDownMsg will also revert if the subnet doesn't exist.
SupplyStrategy memory supplyStrategy = SubnetActorGetterFacet(subnetId.getActor()).supplyStrategy();
if (supplyStrategy.kind != SupplyKind.ERC20) {
revert UnexpectedSupplyStrategy();
}

// Transfer the specified token amount to the gateway to lock it.
IERC20(supplyStrategy.tokenAddress).safeTransferFrom({from: msg.sender, to: address(this), value: amount});

// Create the top-down message to mint the supply in the subnet.
CrossMsg memory crossMsg = CrossMsgHelper.createFundMsg({
subnet: subnetId,
signer: msg.sender,
to: to,
value: amount,
fee: 0 // injecting funds into a subnet should is free
});

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

/// @notice release() burns the received value and releases them from this subnet onto the parent by committing a bottom-up message.
///
/// @param to: the address to which to credit funds in the parent subnet.
Expand Down
4 changes: 3 additions & 1 deletion src/lib/LibSubnetActorStorage.sol
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import {ConsensusType} from "../enums/ConsensusType.sol";
import {NotGateway, SubnetAlreadyKilled} from "../errors/IPCErrors.sol";
import {FvmAddress} from "../structs/FvmAddress.sol";
import {BottomUpCheckpoint} from "../structs/Checkpoint.sol";
import {SubnetID, ValidatorSet, StakingChangeLog, StakingReleaseQueue, Validator} from "../structs/Subnet.sol";
import {SubnetID, ValidatorSet, StakingChangeLog, StakingReleaseQueue, SupplyStrategy, Validator} from "../structs/Subnet.sol";
import {Address} from "openzeppelin-contracts/utils/Address.sol";
import {EnumerableSet} from "openzeppelin-contracts/utils/structs/EnumerableSet.sol";

Expand Down Expand Up @@ -67,6 +67,8 @@ struct SubnetActorStorage {
mapping(address => string) bootstrapNodes;
/// @notice the list ov validators that announces bootstrap nodes
EnumerableSet.AddressSet bootstrapOwners;
/// @notice subnet supply strategy.
SupplyStrategy supplyStrategy;
}

library LibSubnetActorStorage {
Expand Down
11 changes: 11 additions & 0 deletions src/structs/Subnet.sol
Original file line number Diff line number Diff line change
Expand Up @@ -138,3 +138,14 @@ struct Membership {
Validator[] validators;
uint64 configurationNumber;
}

struct SupplyStrategy {
SupplyKind kind;
address tokenAddress;
}

/// @title Determines the type of supply used by the subnet.
enum SupplyKind {
Native,
ERC20
}
7 changes: 6 additions & 1 deletion src/subnet/SubnetActorGetterFacet.sol
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ pragma solidity 0.8.19;

import {ConsensusType} from "../enums/ConsensusType.sol";
import {BottomUpCheckpoint, CrossMsg} from "../structs/Checkpoint.sol";
import {SubnetID} from "../structs/Subnet.sol";
import {SubnetID, SupplyStrategy} from "../structs/Subnet.sol";
import {SubnetID, ValidatorInfo, Validator} from "../structs/Subnet.sol";
import {SubnetActorStorage} from "../lib/LibSubnetActorStorage.sol";
import {SubnetIDHelper} from "../lib/SubnetIDHelper.sol";
Expand Down Expand Up @@ -189,4 +189,9 @@ contract SubnetActorGetterFacet {
function getRelayerReward(address relayer) external view returns (uint256) {
return s.relayerRewards[relayer];
}

/// @notice Returns the supply strategy for the subnet.
function supplyStrategy() external view returns (SupplyStrategy memory supply) {
return s.supplyStrategy;
}
}

0 comments on commit fe6ae46

Please sign in to comment.