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

Contract redesign #931

Draft
wants to merge 6 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from 3 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
4 changes: 4 additions & 0 deletions contracts/src/enums/ConsensusType.sol
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,7 @@ pragma solidity ^0.8.23;
enum ConsensusType {
Fendermint
}

enum Consensus {
ProofOfPower
Copy link
Contributor

Choose a reason for hiding this comment

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

What exactly is ProofOfPower?

}
4 changes: 3 additions & 1 deletion contracts/src/errors/IPCErrors.sol
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ pragma solidity ^0.8.23;
error AddressShouldBeValidator();
error AlreadyRegisteredSubnet();
error AlreadyInSet();
error AlreadyInitialized();
error CannotConfirmFutureChanges();
error CannotReleaseZero();
error CannotSendCrossMsgToItself();
Expand All @@ -15,6 +16,7 @@ error CheckpointNotCreated();
error BottomUpCheckpointAlreadySubmitted();
error BatchNotCreated();
error CollateralIsZero();
error DuplicatedGenesisValidator();
error EmptyAddress();
error FailedAddIncompleteQuorum();
error FailedAddSignatory();
Expand Down Expand Up @@ -73,11 +75,11 @@ error SubnetAlreadyBootstrapped();
error SubnetNotBootstrapped();
error FacetCannotBeZero();
error WrongGateway();
error WrongSubnet();
error CannotFindSubnet();
error UnknownSubnet();
error MethodNotAllowed(string reason);
error InvalidFederationPayload();
error DuplicatedGenesisValidator();
error NotEnoughGenesisValidators();

enum InvalidXnetMessageReason {
Expand Down
14 changes: 14 additions & 0 deletions contracts/src/interfaces/IGenesis.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
// SPDX-License-Identifier: MIT OR Apache-2.0
pragma solidity ^0.8.23;

/// @notice A interface that indicated the implementing facet contains a or multiple genesis settings.
interface IGenesisComponent {
/// @notice Returns the id of the component
function id() external view returns(bytes4);

/// @notice Returns the actual bytes of the genesis
function genesis() external view returns(bytes memory);

/// @notice Checks if the component is bootstrapped
function bootstrapped() external view returns(bool);
Comment on lines +12 to +13
Copy link
Contributor

Choose a reason for hiding this comment

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

This is better placed in a Lifecycle facet and generalized as a stage() method that returns the lifecycle stage of a subnet.

}
8 changes: 8 additions & 0 deletions contracts/src/interfaces/ISubnet.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
// SPDX-License-Identifier: MIT OR Apache-2.0
pragma solidity ^0.8.23;

/// @title Subnet interface
interface ISubnet {
/// @notice Checks if the subnet is now bootstrapped
function bootstrapped() external view returns(bool);
}
Copy link
Contributor

Choose a reason for hiding this comment

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

Could you elaborate on what responsibility/scope this interface would hold going forward?

31 changes: 31 additions & 0 deletions contracts/src/lib/LibGenesis.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
// SPDX-License-Identifier: MIT OR Apache-2.0
pragma solidity ^0.8.23;

import {EnumerableMap} from "openzeppelin-contracts/utils/structs/EnumerableMap.sol";
import {IGenesisComponent} from "../interfaces/IGenesis.sol";

struct SubnetGenesis {
/// @notice The total circulation supply of the subnet
uint256 circSupply;
/// @notice The genesis balances of the address
EnumerableMap.AddressToUintMap balances;
}

/// @title Lib Subnet Genesis
/// @notice Handles the subnet genesis states and util functions
library LibSubnetGenesis {
using EnumerableMap for EnumerableMap.AddressToUintMap;

/// @notice Deposit into the genesis balance of the address
function deposit(SubnetGenesis storage self, address addr, uint256 amount) internal {
Comment on lines +19 to +20
Copy link
Contributor

Choose a reason for hiding this comment

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

This OOP-style pattern is nice, since LibSubnetGenesis can be associated with the SubnetGenesis type via using to write code like this:

using LibSubnetGenesis for SubnetGenesis;

genesis.deposit(addr, amount);

(bool exists, uint256 existingAmount) = self.balances.tryGet(addr);

if (exists) {
self.balances.set(addr, existingAmount + amount);
} else {
self.balances.set(addr, amount);
}

self.circSupply += amount;
Comment on lines +23 to +29
Copy link
Contributor

Choose a reason for hiding this comment

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

This can be simplified by doing the addition to circSupply sooner, and using an early return in the if. But it's probably clearer this way.

}
}
180 changes: 180 additions & 0 deletions contracts/src/lib/power/LibMaxPQ.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,180 @@
// SPDX-License-Identifier: MIT OR Apache-2.0
pragma solidity ^0.8.23;

import {ProofOfPower, LibPowerQuery} from "./LibPower.sol";
import {PQ, LibPQ} from "./LibPQ.sol";

struct MaxPQ {
PQ inner;
}

/// The max index priority queue for staking. The same implementation as LibMinPQ, just order compare
/// is reversed.
library LibMaxPQ {
using LibPQ for PQ;
using LibPowerQuery for ProofOfPower;

function getSize(MaxPQ storage self) internal view returns (uint16) {
return self.inner.size;
}

function getAddress(MaxPQ storage self, uint16 i) internal view returns (address) {
return self.inner.posToAddress[i];
}

function contains(MaxPQ storage self, address validator) internal view returns (bool) {
return self.inner.contains(validator);
}

/// @notice Insert the validator address into this PQ.
/// NOTE that caller should ensure the valdiator is not already in the queue.
function insert(MaxPQ storage self, ProofOfPower storage proofOrPower, address validator) internal {
uint16 size = self.inner.size + 1;

self.inner.addressToPos[validator] = size;
self.inner.posToAddress[size] = validator;

self.inner.size = size;

uint256 power = proofOrPower.getConfirmedPower(validator);
swim({self: self, proofOrPower: proofOrPower, pos: size, value: power});
}

/// @notice Pop the maximum value in the priority queue.
/// NOTE that caller should ensure the queue is not empty!
function pop(MaxPQ storage self, ProofOfPower storage proofOrPower) internal {
self.inner.requireNotEmpty();

uint16 size = self.inner.size;

self.inner.exchange(1, size);

self.inner.size = size - 1;
self.inner.del(size);

uint256 power = self.inner.getPower(proofOrPower, 1);
sink({self: self, proofOrPower: proofOrPower, pos: 1, value: power});
}

/// @notice Reheapify the heap when the validator is deleted.
/// NOTE that caller should ensure the queue is not empty.
function deleteReheapify(MaxPQ storage self, ProofOfPower storage proofOrPower, address validator) internal {
uint16 pos = self.inner.getPosOrRevert(validator);
uint16 size = self.inner.size;

self.inner.exchange(pos, size);

// remove the item
self.inner.size = size - 1;
self.inner.del(size);

if (size == pos) {
return;
}

// swim pos up in case exchanged index is smaller
uint256 power = self.inner.getPower(proofOrPower, pos);
swim({self: self, proofOrPower: proofOrPower, pos: pos, value: power});

// sink pos down in case updated pos is larger
power = self.inner.getPower(proofOrPower, pos);
sink({self: self, proofOrPower: proofOrPower, pos: pos, value: power});
}

/// @notice Reheapify the heap when the collateral of a key has increased.
/// NOTE that caller should ensure the queue is not empty.
function increaseReheapify(MaxPQ storage self, ProofOfPower storage proofOrPower, address validator) internal {
uint16 pos = self.inner.getPosOrRevert(validator);
uint256 power = proofOrPower.getConfirmedPower(validator);
swim({self: self, proofOrPower: proofOrPower, pos: pos, value: power});
}

/// @notice Reheapify the heap when the collateral of a key has decreased.
/// NOTE that caller should ensure the queue is not empty.
function decreaseReheapify(MaxPQ storage self, ProofOfPower storage proofOrPower, address validator) internal {
uint16 pos = self.inner.getPosOrRevert(validator);
uint256 power = proofOrPower.getConfirmedPower(validator);
sink({self: self, proofOrPower: proofOrPower, pos: pos, value: power});
}

/// @notice Get the maximum value in the priority queue.
/// NOTE that caller should ensure the queue is not empty!
function max(MaxPQ storage self, ProofOfPower storage proofOrPower) internal view returns (address, uint256) {
self.inner.requireNotEmpty();

address addr = self.inner.posToAddress[1];
uint256 power = proofOrPower.getConfirmedPower(addr);
return (addr, power);
}

/***************************************************************************
* Heap internal helper functions, should not be called by external functions
****************************************************************************/
function swim(MaxPQ storage self, ProofOfPower storage proofOrPower, uint16 pos, uint256 value) internal {
uint16 parentPos;
uint256 parentPower;

while (pos > 1) {
parentPos = pos >> 1; // parentPos = pos / 2
parentPower = self.inner.getPower(proofOrPower, parentPos);

// Parent power is not smaller than that of the current child, and the heap condition met.
if (!firstValueSmaller(parentPower, value)) {
break;
}

self.inner.exchange(parentPos, pos);
pos = parentPos;
}
}

function sink(MaxPQ storage self, ProofOfPower storage proofOrPower, uint16 pos, uint256 value) internal {
uint16 childPos = pos << 1; // childPos = pos * 2
uint256 childPower;

uint16 size = self.inner.size;

while (childPos <= size) {
if (childPos < size) {
// select the max of the two children
(childPos, childPower) = largerPosition({
self: self,
proofOrPower: proofOrPower,
pos1: childPos,
pos2: childPos + 1
});
} else {
childPower = self.inner.getPower(proofOrPower, childPos);
}

// parent, current idx, is not more than its two children, min heap condition is met.
if (!firstValueSmaller(value, childPower)) {
break;
}

self.inner.exchange(childPos, pos);
pos = childPos;
childPos = pos << 1;
}
}

/// @notice Get the larger index of pos1 and pos2.
function largerPosition(
MaxPQ storage self,
ProofOfPower storage proofOrPower,
uint16 pos1,
uint16 pos2
) internal view returns (uint16, uint256) {
uint256 power1 = self.inner.getPower(proofOrPower, pos1);
uint256 power2 = self.inner.getPower(proofOrPower, pos2);

if (firstValueSmaller(power1, power2)) {
return (pos2, power2);
}
return (pos1, power1);
}

function firstValueSmaller(uint256 v1, uint256 v2) internal pure returns (bool) {
return v1 < v2;
}
}
Loading