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

Commit

Permalink
Split SubnetActor
Browse files Browse the repository at this point in the history
  • Loading branch information
dnkolegov committed Dec 21, 2023
1 parent 903ade6 commit 8430b3e
Show file tree
Hide file tree
Showing 18 changed files with 550 additions and 288 deletions.
16 changes: 16 additions & 0 deletions scripts/deploy-sa-diamond.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,11 +30,27 @@ async function deploySubnetActorDiamond(
LibStaking: libs['LibStaking'],
}

const rewarderFacetLibs: Libraries = {
LibStaking: libs['LibStaking'],
}

const pauserFacetLibs: Libraries = {
LibDiamond: libs['LibDiamond'],
LibPausable: libs['LibPausable'],
}

const checkpointerFacetLibs: Libraries = {
LibStaking: libs['LibStaking'],
}

const facets = [
{ name: 'DiamondLoupeFacet', libs: {} },
{ name: 'DiamondCutFacet', libs: {} },
{ name: 'SubnetActorGetterFacet', libs: getterFacetLibs },
{ name: 'SubnetActorManagerFacet', libs: managerFacetLibs },
{ name: 'SubnetActorRewardFacet', libs: rewarderFacetLibs },
{ name: 'SubnetActorCheckpointingFacet', libs: checkpointerFacetLibs },
{ name: 'SubnetActorPauseFacet', libs: pauserFacetLibs },
]
// The `facetCuts` variable is the FacetCut[] that contains the functions to add during diamond deployment
const facetCuts = []
Expand Down
5 changes: 4 additions & 1 deletion scripts/python/build_selector_library.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,14 +81,17 @@ def main():
'src/gateway/router/XnetMessagingFacet.sol',
'src/subnet/SubnetActorGetterFacet.sol',
'src/subnet/SubnetActorManagerFacet.sol',
'src/subnet/SubnetActorPauseFacet.sol',
'src/subnet/SubnetActorRewardFacet.sol',
'src/subnet/SubnetActorCheckpointingFacet.sol',
'src/subnetregistry/RegisterSubnetFacet.sol',
'src/subnetregistry/SubnetGetterFacet.sol',
'test/helpers/ERC20PresetFixedSupply.sol',
'test/helpers/NumberContractFacetEight.sol',
'test/helpers/NumberContractFacetSeven.sol',
'test/helpers/SelectorLibrary.sol',
'test/helpers/TestUtils.sol',
'test/mocks/SubnetActorManagerFacetMock.sol',
'test/mocks/SubnetActorMock.sol',
]

for filepath in filepaths_to_target:
Expand Down
4 changes: 2 additions & 2 deletions src/gateway/router/BottomUpRouterFacet.sol
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// SPDX-License-Identifier: MIT OR Apache-2.0
pragma solidity 0.8.19;

import {ISubnetActor} from "../../interfaces/ISubnetActor.sol";
import {IRelayerRewardDistributor} from "../../interfaces/ISubnetActor.sol";
import {GatewayActorModifiers} from "../../lib/LibGatewayActorStorage.sol";
import {BottomUpMsgBatch} from "../../structs/CrossNet.sol";
import {LibGateway} from "../../lib/LibGateway.sol";
Expand Down Expand Up @@ -78,7 +78,7 @@ contract BottomUpRouterFacet is GatewayActorModifiers {
Address.functionCallWithValue({
target: msg.sender,
data: abi.encodeCall(
ISubnetActor.distributeRewardToRelayers,
IRelayerRewardDistributor.distributeRewardToRelayers,
(block.number, totalFee, QuorumObjKind.Checkpoint)
),
value: totalFee
Expand Down
4 changes: 2 additions & 2 deletions src/gateway/router/CheckpointingFacet.sol
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import {Status} from "../../enums/Status.sol";
import {Subnet} from "../../structs/Subnet.sol";
import {QuorumObjKind} from "../../structs/Quorum.sol";
import {Address} from "openzeppelin-contracts/utils/Address.sol";
import {ISubnetActor} from "../../interfaces/ISubnetActor.sol";
import {IRelayerRewardDistributor} from "../../interfaces/ISubnetActor.sol";

import {InvalidBatchSource, NotEnoughBalance, MaxMsgsPerBatchExceeded, BatchWithNoMessages, InvalidCheckpointSource, InvalidCrossMsgNonce, InvalidCrossMsgDstSubnet, CheckpointAlreadyExists} from "../../errors/IPCErrors.sol";
import {NotRegisteredSubnet, SubnetNotActive, SubnetNotFound, InvalidSubnet, CheckpointNotCreated} from "../../errors/IPCErrors.sol";
Expand Down Expand Up @@ -47,7 +47,7 @@ contract CheckpointingFacet is GatewayActorModifiers {
Address.functionCallWithValue({
target: msg.sender,
data: abi.encodeCall(
ISubnetActor.distributeRewardToRelayers,
IRelayerRewardDistributor.distributeRewardToRelayers,
(checkpoint.blockHeight, 0, QuorumObjKind.Checkpoint)
),
value: 0
Expand Down
51 changes: 50 additions & 1 deletion src/interfaces/ISubnetActor.sol
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import {BottomUpCheckpoint} from "../structs/CrossNet.sol";
import {QuorumObjKind} from "../structs/Quorum.sol";

/// @title Subnet Actor interface
/// @author LimeChain team
interface ISubnetActor {
/// Called by peers looking to join a subnet.
///
Expand Down Expand Up @@ -49,3 +48,53 @@ interface ISubnetActor {
/// The reword includes the fixed reward for a relayer defined in the contract and `amount` of fees from the cross-messages.
function distributeRewardToRelayers(uint256 height, uint256 amount, QuorumObjKind kind) external payable;
}

interface ISubnetActorXX {
/// Called by peers looking to join a subnet.
///
/// It implements the basic logic to onboard new peers to the subnet.
function join(bytes calldata metadata) external payable;

/// Called by peers looking to leave a subnet.
function leave() external;

/// Method that allows a validator to increase their stake
function stake() external payable;

/// Method that allows to pre-fund an address in the subnet before it bootstraps.
function preFund() external payable;

/// Method that allows to recover initial balance for an address from a subnet that hasn't bootstrapped yet.
function preRelease(uint256 amount) external;

/// Method that allows a validator to unstake their collateral from a subnet
function unstake(uint256 amount) external;

/// Unregister the subnet from the hierarchy, making it no longer discoverable.
function kill() external;

/// Validator claims released collateral
function claim() external;

/// Relayer claims a reward
function claimRewardForRelayer() external;

/// Executes the checkpoint if it is valid.
/// It triggers the commitment of the checkpoint, the execution of related cross-net messages,
/// and any other side-effects that need to be triggered by the checkpoint such as relayer reward book keeping.
function submitCheckpoint(
BottomUpCheckpoint calldata checkpoint,
address[] calldata signatories,
bytes[] calldata signatures
) external;

/// reward the relayers for processing checkpoint at height `height`.
/// The reword includes the fixed reward for a relayer defined in the contract and `amount` of fees from the cross-messages.
function distributeRewardToRelayers(uint256 height, uint256 amount, QuorumObjKind kind) external payable;
}

interface IRelayerRewardDistributor {
/// reward the relayers for processing checkpoint at height `height`.
/// The reword includes the fixed reward for a relayer defined in the contract and `amount` of fees from the cross-messages.
function distributeRewardToRelayers(uint256 height, uint256 amount, QuorumObjKind kind) external payable;
}
12 changes: 6 additions & 6 deletions src/lib/LibPausable.sol
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ abstract contract Pausable {
event Paused(address account);

/**
* @dev Emitted when the pause is lifted by `account`.
* @dev Emitted when the unpause is lifted by `account`.
*/
event Unpaused(address account);

Expand Down Expand Up @@ -46,7 +46,7 @@ abstract contract Pausable {
* @dev Throws if the contract is paused.
*/
function _requireNotPaused() internal view virtual {
if (paused()) {
if (_paused()) {
revert EnforcedPause();
}
}
Expand All @@ -55,13 +55,13 @@ abstract contract Pausable {
* @dev Throws if the contract is not paused.
*/
function _requirePaused() internal view virtual {
if (!paused()) {
if (!_paused()) {
revert ExpectedPause();
}
}

/// @notice sets if to pause the contract
function paused() public view returns(bool) {
/// @notice returns true if the contract is paused
function _paused() internal view returns(bool) {
PausableStorage storage s = pausableStorage();
return s.paused;
}
Expand All @@ -76,7 +76,7 @@ abstract contract Pausable {
function _pause() internal whenNotPaused {
PausableStorage storage s = pausableStorage();
s.paused = true;
emit Unpaused(msg.sender);
emit Paused(msg.sender);
}

/**
Expand Down
159 changes: 159 additions & 0 deletions src/subnet/SubnetActorCheckpointingFacet.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
// SPDX-License-Identifier: MIT OR Apache-2.0
pragma solidity 0.8.19;

import {InvalidBatchEpoch, MaxMsgsPerBatchExceeded, BatchWithNoMessages, InvalidSignatureErr, InvalidCheckpointEpoch} from "../errors/IPCErrors.sol";
import {IGateway} from "../interfaces/IGateway.sol";
import {BottomUpCheckpoint, BottomUpMsgBatch, BottomUpMsgBatchInfo} from "../structs/CrossNet.sol";
import {Validator, ValidatorSet} from "../structs/Subnet.sol";
import {LibDiamond} from "../lib/LibDiamond.sol";
import {MultisignatureChecker} from "../lib/LibMultisignatureChecker.sol";
import {ReentrancyGuard} from "../lib/LibReentrancyGuard.sol";
import {SubnetActorModifiers} from "../lib/LibSubnetActorStorage.sol";
import {LibValidatorSet, LibStaking} from "../lib/LibStaking.sol";
import {EnumerableSet} from "openzeppelin-contracts/utils/structs/EnumerableSet.sol";
import {Address} from "openzeppelin-contracts/utils/Address.sol";
import {LibSubnetActor} from "../lib/LibSubnetActor.sol";
import {Pausable} from "../lib/LibPausable.sol";

contract SubnetActorCheckpointingFacet is SubnetActorModifiers, ReentrancyGuard, Pausable {
using EnumerableSet for EnumerableSet.AddressSet;
using LibValidatorSet for ValidatorSet;
using Address for address payable;

/// @notice Submits a checkpoint commitment for execution.
/// @dev It triggers the commitment of the checkpoint and any other side-effects that
/// need to be triggered by the checkpoint such as relayer reward book keeping.
/// @param checkpoint The executed bottom-up checkpoint.
/// @param signatories The addresses of validators signing the checkpoint.
/// @param signatures The signatures of validators on the checkpoint.
function submitCheckpoint(
BottomUpCheckpoint calldata checkpoint,
address[] calldata signatories,
bytes[] calldata signatures
) external whenNotPaused {
// the checkpoint height must be equal to the last bottom-up checkpoint height or
// the next one
if (
checkpoint.blockHeight != s.lastBottomUpCheckpointHeight + s.bottomUpCheckPeriod &&
checkpoint.blockHeight != s.lastBottomUpCheckpointHeight
) {
revert InvalidCheckpointEpoch();
}
bytes32 checkpointHash = keccak256(abi.encode(checkpoint));

if (checkpoint.blockHeight == s.lastBottomUpCheckpointHeight + s.bottomUpCheckPeriod) {
// validate signatures and quorum threshold, revert if validation fails
validateActiveQuorumSignatures({signatories: signatories, hash: checkpointHash, signatures: signatures});

// If the checkpoint height is the next expected height then this is a new checkpoint which must be executed
// in the Gateway Actor, the checkpoint and the relayer must be stored, last bottom-up checkpoint updated.
s.committedCheckpoints[checkpoint.blockHeight] = checkpoint;

// slither-disable-next-line unused-return
s.relayerRewards.checkpointRewarded[checkpoint.blockHeight].add(msg.sender);

s.lastBottomUpCheckpointHeight = checkpoint.blockHeight;

// Commit in gateway to distribute rewards
IGateway(s.ipcGatewayAddr).commitCheckpoint(checkpoint);

// confirming the changes in membership in the child
LibStaking.confirmChange(checkpoint.nextConfigurationNumber);
} else if (checkpoint.blockHeight == s.lastBottomUpCheckpointHeight) {
// If the checkpoint height is equal to the last checkpoint height, then this is a repeated submission.
// We should store the relayer, but not to execute checkpoint again.
// In this case, we do not verify the signatures for this checkpoint again,
// but we add the relayer to the list of all relayers for this checkpoint to be rewarded later.
// The reason for comparing hashes instead of verifying signatures is the following:
// once the checkpoint is executed, the active validator set changes
// and can only be used to validate the next checkpoint, not another instance of the last one.
bytes32 lastCheckpointHash = keccak256(abi.encode(s.committedCheckpoints[checkpoint.blockHeight]));
if (checkpointHash == lastCheckpointHash) {
// slither-disable-next-line unused-return
s.relayerRewards.checkpointRewarded[checkpoint.blockHeight].add(msg.sender);
}
}
}

/// @notice Submits a batch of bottom-up messages for execution.
/// @dev It triggers the execution of a cross-net message batch.
/// @param batch The batch of bottom-up messages.
/// @param signatories The addresses of validators signing the batch.
/// @param signatures The signatures of validators on the batch.
function submitBottomUpMsgBatch(
BottomUpMsgBatch calldata batch,
address[] calldata signatories,
bytes[] calldata signatures
) external {
// forbid the submission of batches from the past
if (batch.blockHeight < s.lastBottomUpBatch.blockHeight) {
revert InvalidBatchEpoch();
}
if (batch.msgs.length > s.maxMsgsPerBottomUpBatch) {
revert MaxMsgsPerBatchExceeded();
}
// if the batch height is not max, we only supoprt batch submission in period epochs
if (batch.msgs.length != s.maxMsgsPerBottomUpBatch && batch.blockHeight % s.bottomUpMsgBatchPeriod != 0) {
revert InvalidBatchEpoch();
}
if (batch.msgs.length == 0) {
revert BatchWithNoMessages();
}

bytes32 batchHash = keccak256(abi.encode(batch));

if (batch.blockHeight == s.lastBottomUpBatch.blockHeight) {
// If the batch info is equal to the last batch info, then this is a repeated submission.
// We should store the relayer, but not to execute batch again following the same reward logic
// used for checkpoints.
if (batchHash == s.lastBottomUpBatch.hash) {
// slither-disable-next-line unused-return
s.relayerRewards.batchRewarded[batch.blockHeight].add(msg.sender);
}
} else {
// validate signatures and quorum threshold, revert if validation fails
validateActiveQuorumSignatures({signatories: signatories, hash: batchHash, signatures: signatures});

// If the checkpoint height is the next expected height then this is a new batch,
// and should be forwarded to the gateway for execution.
s.lastBottomUpBatch = BottomUpMsgBatchInfo({blockHeight: batch.blockHeight, hash: batchHash});

// slither-disable-next-line unused-return
s.relayerRewards.batchRewarded[batch.blockHeight].add(msg.sender);

// Execute messages.
IGateway(s.ipcGatewayAddr).execBottomUpMsgBatch(batch);
}
}

/// @notice Checks whether the signatures are valid for the provided signatories and hash within the current validator set.
/// Reverts otherwise.
/// @dev Signatories in `signatories` and their signatures in `signatures` must be provided in the same order.
/// Having it public allows external users to perform sanity-check verification if needed.
/// @param signatories The addresses of the signatories.
/// @param hash The hash of the checkpoint.
/// @param signatures The packed signatures of the checkpoint.
function validateActiveQuorumSignatures(
address[] memory signatories,
bytes32 hash,
bytes[] memory signatures
) public view {
// This call reverts if at least one of the signatories (validator) is not in the active validator set.
uint256[] memory collaterals = s.validatorSet.getTotalPowerOfValidators(signatories);
uint256 activeCollateral = s.validatorSet.getTotalActivePower();

uint256 threshold = (activeCollateral * s.majorityPercentage) / 100;

(bool valid, MultisignatureChecker.Error err) = MultisignatureChecker.isValidWeightedMultiSignature({
signatories: signatories,
weights: collaterals,
threshold: threshold,
hash: hash,
signatures: signatures
});

if (!valid) {
revert InvalidSignatureErr(uint8(err));
}
}
}
Loading

0 comments on commit 8430b3e

Please sign in to comment.