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

Commit

Permalink
Feature/subnet lifecycle (#17)
Browse files Browse the repository at this point in the history
feat: SA lifecycle methods and corresponding GW method implementations
  • Loading branch information
mikirov authored Mar 16, 2023
1 parent 1905cfc commit 46afe66
Show file tree
Hide file tree
Showing 24 changed files with 1,941 additions and 173 deletions.
3 changes: 3 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
[submodule "lib/forge-std"]
path = lib/forge-std
url = https://github.com/foundry-rs/forge-std
[submodule "lib/openzeppelin-contracts"]
path = lib/openzeppelin-contracts
url = https://github.com/OpenZeppelin/openzeppelin-contracts
1 change: 1 addition & 0 deletions lib/fevmate
Submodule fevmate added at 5ef8d1
1 change: 1 addition & 0 deletions lib/openzeppelin-contracts
Submodule openzeppelin-contracts added at adb861
1 change: 1 addition & 0 deletions remappings.txt
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
ds-test/=lib/forge-std/lib/ds-test/src/
forge-std/=lib/forge-std/src/
openzeppelin-contracts/=lib/openzeppelin-contracts/contracts/
12 changes: 0 additions & 12 deletions script/Counter.s.sol

This file was deleted.

238 changes: 192 additions & 46 deletions src/Gateway.sol
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,27 @@ import "./structs/Checkpoint.sol";
import "./structs/Postbox.sol";
import "./enums/Status.sol";
import "./interfaces/IGateway.sol";
import "./interfaces/ISubnetActor.sol";
import "./lib/SubnetIDHelper.sol";
import "./lib/CheckpointMappingHelper.sol";
import "./lib/CheckpointHelper.sol";
import "openzeppelin-contracts/security/ReentrancyGuard.sol";
import "openzeppelin-contracts/utils/Address.sol";

/// @title Gateway Contract
/// @author LimeChain team
contract Gateway is IGateway, ReentrancyGuard {
using Address for address payable;
using SubnetIDHelper for SubnetID;
using CheckpointHelper for Checkpoint;
using CheckpointMappingHelper for mapping(int64 => Checkpoint);

contract Gateway is IGateway {
int64 constant DEFAULT_CHECKPOINT_PERIOD = 10;
uint64 constant MIN_COLLATERAL_AMOUNT = 1 ether;
uint64 constant MAX_NONCE = type(uint64).max;

/// @notice ID of the current network
SubnetID public networkName;
SubnetID private networkName;

/// @notice Number of active subnets spawned from this one
uint64 public totalSubnets;
Expand All @@ -22,7 +35,7 @@ contract Gateway is IGateway {

/// @notice List of subnets
/// SubnetID => Subnet
mapping(bytes => Subnet) public subnets;
mapping(bytes32 => Subnet) public subnets;

/// @notice Checkpoint period in number of epochs for the subnet
int64 public checkPeriod;
Expand All @@ -38,7 +51,7 @@ contract Gateway is IGateway {
/// @notice Postbox keeps track for an EOA of all the cross-net messages triggered by
/// an actor that need to be propagated further through the hierarchy.
/// postbox id => PostBoxItem
mapping(uint256 => PostBoxItem) public postbox;
mapping(uint64 => PostBoxItem) private postbox;

/// @notice Latest nonce of a cross message sent from subnet.
uint64 public nonce;
Expand All @@ -58,69 +71,202 @@ contract Gateway is IGateway {
uint64 public appliedBottomUpNonce;
uint64 public appliedTopDownNonce;

constructor(string memory _networkName, int64 _checkpointPeriod) {
networkName = SubnetID(_networkName, address(0));
/// epoch => SubnetID => [childIndex, exists(0 - no, 1 - yes)]

This comment has been minimized.

Copy link
@adlrocha

adlrocha Apr 4, 2023

Contributor

What are these mappings for? A brief comment describing what is this for would be really helpful 🙏

mapping(int64 => mapping(bytes32 => uint256[2])) internal children;
/// epoch => SubnetID => check => exists
mapping(int64 => mapping(bytes32 => mapping(bytes32 => bool)))
internal checks;

constructor(address[] memory path, int64 checkpointPeriod) {
networkName = SubnetID(path);
minStake = MIN_COLLATERAL_AMOUNT;
checkPeriod = _checkpointPeriod > DEFAULT_CHECKPOINT_PERIOD
? _checkpointPeriod
checkPeriod = checkpointPeriod > DEFAULT_CHECKPOINT_PERIOD
? checkpointPeriod
: DEFAULT_CHECKPOINT_PERIOD;
appliedBottomUpNonce = MAX_NONCE;
}

function register() external {
revert("MethodNotImplemented");
function getNetworkName() external view returns (SubnetID memory) {
return networkName;
}

function addStake() external {
revert("MethodNotImplemented");
}
function register() external payable {
require(
msg.value >= minStake,
"call to register doesn't include enough funds"
);

function releaseStake(uint amount) external {
revert("MethodNotImplemented");
}
(bool registered, Subnet storage subnet) = getSubnet(msg.sender);

function kill() external {
revert("MethodNotImplemented");
}
require(registered == false, "subnet is already registered");

function commitChildCheck(bytes memory checkpoint) external {
revert("MethodNotImplemented");
}
subnet.id = networkName.createSubnetId(msg.sender);
subnet.stake = msg.value;
subnet.status = Status.Active;
subnet.nonce = 0;
subnet.circSupply = 0;

function fund(bytes memory subnetId) external {
revert("MethodNotImplemented");
totalSubnets += 1;
}

function release() external {
revert("MethodNotImplemented");
}
function addStake() external payable {
require(msg.value > 0, "no stake to add");

(bool registered, Subnet storage subnet) = getSubnet(msg.sender);

require(registered, "subnet is not registered");

function sendCross(
bytes memory toSubnetId,
bytes memory crossMsg
) external {
revert("MethodNotImplemented");
subnet.stake += msg.value;
}

function applyMessage(bytes memory crossMsg) external {
revert("MethodNotImplemented");
function releaseStake(uint amount) external nonReentrant {
require(amount > 0, "no funds to release in params");

(bool registered, Subnet storage subnet) = getSubnet(msg.sender);

require(registered, "subnet is not registered");
require(
subnet.stake >= amount,
"subnet actor not allowed to release so many funds"
);
require(
address(this).balance >= amount,
"something went really wrong! the actor doesn't have enough balance to release"
);

subnet.stake -= amount;

if (subnet.stake < minStake) {
subnet.status = Status.Inactive;
}

payable(subnet.id.getActor()).sendValue(amount);
}

function whitelistPropagator(
uint256 postboxId,
address[] memory owners
) external {
revert("MethodNotImplemented");
function kill() external {
(bool registered, Subnet storage subnet) = getSubnet(msg.sender);

require(registered, "subnet is not registered");
require(
address(this).balance >= subnet.stake,
"something went really wrong! the actor doesn't have enough balance to release"
);
require(
subnet.circSupply == 0,
"cannot kill a subnet that still holds user funds in its circ. supply"
);

uint256 stake = subnet.stake;

totalSubnets -= 1;

delete subnets[subnet.id.toHash()];

payable(msg.sender).sendValue(stake);
}

function propagate(uint256 postboxId) external {
revert("MethodNotImplemented");
function commitChildCheck(
Checkpoint calldata commit
) external returns (uint fee) {
require(
commit.data.source.getActor() == msg.sender,
"source in checkpoint doesn't belong to subnet"
);

(bool registered, Subnet storage subnet) = getSubnet(msg.sender);

require(registered, "subnet is not registered");

require(
subnet.status == Status.Active,
"can't commit checkpoint for an inactive subnet"
);

require(
subnet.prevCheckpoint.data.epoch <= commit.data.epoch,
"checkpoint being committed belongs to the past"
);
if (commit.data.prevHash != bytes32(0)) {
require(
subnet.prevCheckpoint.toHash() == commit.data.prevHash,
"previous checkpoint not consistent with previous one"
);
}

// cross message
if (commit.hasCrossMsgMeta()) {
if (commit.data.crossMsgs.msgs.length > 0) {
bottomUpMsgMeta[bottomUpNonce] = commit.data.crossMsgs;
bottomUpMsgMeta[bottomUpNonce].nonce = bottomUpNonce;
bottomUpNonce += 1;
}

require(
subnet.circSupply >= commit.data.crossMsgs.value,
"wtf! we can't release funds below circ, supply. something went really wrong"
);

subnet.circSupply -= commit.data.crossMsgs.value;
fee = commit.data.crossMsgs.fee;
}

(
bool checkpointExists,
int64 currentEpoch,
Checkpoint storage checkpoint
) = checkpoints.getCheckpointPerEpoch(block.number, checkPeriod);

// create checkpoint if not exists
if (checkpointExists == false) {
checkpoint.data.source = networkName;
checkpoint.data.epoch = currentEpoch;
}

bytes32 commitSource = commit.data.source.toHash();
bytes32 commitData = commit.toHash();

uint[2] memory child = children[currentEpoch][commitSource];
uint childIndex = child[0]; // index at checkpoint.data.children for the given subnet
bool childExists = child[1] == 1; // 0 - no, 1 - yes
bool childCheckExists = checks[currentEpoch][commitSource][commitData];

require(
childCheckExists == false,
"child checkpoint being committed already exists"
);

if (childExists == false) {
checkpoint.data.children.push(
ChildCheck({
source: commit.data.source,
checks: new bytes32[](0)
})
);
childIndex = checkpoint.data.children.length - 1;
}

checkpoint.data.children[childIndex].checks.push(commitData);

children[currentEpoch][commitSource][0] = childIndex;
children[currentEpoch][commitSource][1] = 1;
checks[currentEpoch][commitSource][commitData] = true;

subnet.prevCheckpoint = commit;

if (fee > 0) {
payable(msg.sender).functionCallWithValue(
abi.encodeWithSignature("reward()"),
fee
);
}
}

function commitCrossMessage(
bytes memory crossMessage,
uint256 feeAmount
) external {
revert("MethodNotImplemented");
function getSubnet(
address actor
) internal view returns (bool found, Subnet storage subnet) {
SubnetID memory subnetId = networkName.createSubnetId(actor);

subnet = subnets[subnetId.toHash()];
found = subnet.id.route.length != 0;
}
}
Loading

0 comments on commit 46afe66

Please sign in to comment.