Skip to content

Commit

Permalink
add natspec and documentation (#54) (#56)
Browse files Browse the repository at this point in the history
* add natspec and documentation (#54)

* added dodoc, started rewriting README

* finished first pass on README

* updated .env.example

* move IStateReceiver to interfaces/

* added natspec to common/

* docs: add natspec for `ChildValidatorSet`

* docs: fix some natspec

* natspec for libs

* natspec on cvs for initialize

* natspec finished for root

* improving natspec, additional READMEs

* delete TODO

* fixes, recompile docs

* docs: clarify some `ChildValidatorSet` natspec

* fixes II

Co-authored-by: Zero Ekkusu <[email protected]>
Co-authored-by: gretzke <[email protected]>

* prettier

Co-authored-by: Zero Ekkusu <[email protected]>
Co-authored-by: gretzke <[email protected]>
  • Loading branch information
3 people authored Sep 16, 2022
1 parent e484f1b commit eeceb0d
Show file tree
Hide file tree
Showing 39 changed files with 1,532 additions and 376 deletions.
35 changes: 32 additions & 3 deletions .env.example
Original file line number Diff line number Diff line change
@@ -1,3 +1,32 @@
ETHERSCAN_API_KEY=ABC123ABC123ABC123ABC123ABC123ABC1
ROPSTEN_URL=https://eth-ropsten.alchemyapi.io/v2/<YOUR ALCHEMY KEY>
PRIVATE_KEY=0xabc123abc123abc123abc123abc123abc123abc123abc123abc123abc123abc1
# .example.env

# This file is designed to hold secrets like API and private keys without exposing them.
# Using the project locally without them is possible, but values will be needed here for
# deployment and other actions on public networks and testnets.

# In order to use this file, copy it and rename it .env

# Used to signal the gas reporter
# (this is done so that when the tests run on CI where there is no .env, the gas reporter
# won't run)
REPORT_GAS=true

# A private key is necessary for deploying and/or transacting on public networks
PRIVATE_KEY=

# The Hardhat config is configured to recognize 4 chains: a production root/child, and a
# test root/child. To run on any of them, put a URL to an RPC in the proper field. For
# example, if you are using Alchemy and need to fork Ethereum mainnet as the root chain,
# you would put the complete URL (including API key) in the ROOT_RPC.

# If you are running a local node, use a URL to localhost, for example:
# http://localhost:8545
ROOT_RPC=
ROOT_TEST_RPC=
CHILD_RPC=
CHILD_TEST_RPC=

# An Etherscan API key is needed for verifying contracts on Ethereum (testnets/mainnet),
# while a Polygonscan API key is needed for Polygon's chains.
ETHERSCAN_API_KEY=
POLYGONSCAN_API_KEY=
63 changes: 57 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ This repository contains the smart contract suite used in Polygon's POS v3 block
- [Check Test Coverage](#check-test-coverage)
- [Run Slither](#run-slither)
- [Continuous Integration](#continuous-integration)
- TODO: Scripts (deploy?)
- [Documentation](#documentation)

## Repo Architecture

Expand All @@ -42,13 +42,13 @@ There are a number of different contracts with different roles in the suite, as
│ libs/ "libraries used for specific applications"
├─ ModExp — "modular exponentiation (from Hubble Project, for BLS)"
├─ ValidatorQueue - "lib of operations for the validator queue"
├─ ValidatorStorage — "statistical binary tree lib for ordering validators"
├─ ValidatorStorage — "statistical red-black tree lib for ordering validators"
├─ WithdrawalQueue — "lib of operations for the rewards withdrawal queue"
│ mocks/ "mocks of various contracts for testing"
│ root/ "contracts that live on the root chain (Ethereum mainnet)"
├─ CheckpointManager - //TODO once root contracts are more settled
├─ RootValidatorSet - //TODO once root contracts are more settled
├─ StateSender - //TODO once root contracts are more settled
├─ CheckpointManager - "receives and executes messages from child"
├─ RootValidatorSet - "*LIKELY TO CHANGE* stores data from child about validators and epochs"
├─ StateSender - "sends messages to child"
```

### General Repo Layout
Expand Down Expand Up @@ -83,7 +83,39 @@ The `package-lock.json` is also provided to ensure the ability to install the sa

### Requirements

In order to work with this repo locally, you will need Node (preferably using [nvm](https://github.com/nvm-sh/nvm)) in order to work with the Hardhat part of the repo, and [Rust](https://www.rust-lang.org/tools/install) for the Foundry environment.
In order to work with this repo locally, you will need Node (preferably using [nvm](https://github.com/nvm-sh/nvm)) in order to work with the Hardhat part of the repo.

In addition, to work with Foundry, you will need to have it installed. The recommended method is to use their `foundryup` tool, which can be installed (and automatically install Foundry) using this command:

```bash
curl -L https://foundry.paradigm.xyz | bash
```

Note that this only works on Linux and Mac. For Windows, or if `foundryup` doesn't work, consult [their documentation](https://book.getfoundry.sh/getting-started/installation).

### Installation

**You do not need to clone this repo in order to interact with Polygon POS v3.**

If you would like to work with these contracts in a development environment, first clone the repo:

```bash
git clone [email protected]:maticnetwork/v3-contracts.git
```

If you have [nvm](https://github.com/nvm-sh/nvm) installed (recommended), you can run `nvm use #` to set your version of Node to the same as used in development and testing.

Install JS/TS (Hardhat) dependencies:

```bash
npm i
```

### General Repo Layout

This repo is a hybrid [Hardhat](https://hardhat.org) and [Foundry](https://getfoundry.sh/) environment. There are a number of add-ons, some of which we will detail here. Unlike standard Foundry environments, the contracts are located in `contracts/` (as opposed to `src/`) in order to conform with the general Hardhat project architecture. The Foundry/Solidity tests live in `test/forge/` whereas the Hardhat/Typescript tests are at the root level of `test/`. (For more details on the tests, see [Running Tests](#running-tests) in the [Using This Repo](#using-this-repo) section.)

Install Foundry libs:

In addition, to work with Foundry, you will need to have it installed. The recommended method is to use their `foundryup` tool, which can be installed (and automatically install Foundry) using this command:

Expand Down Expand Up @@ -121,6 +153,10 @@ forge install

There are a few things that should be done to set up the repo once you've cloned it and installed the dependencies and libraries. An important step for various parts of the repo to work properly is to set up a `.env` file. There is an `.example.env` file provided, copy it and rename the copy `.env`.

### Environment Setup

There are a few things that should be done to set up the repo once you've cloned it and installed the dependencies and libraries. An important step for various parts of the repo to work properly is to set up a `.env` file. There is an `.example.env` file provided, copy it and rename the copy `.env`.

The v3 contract set is meant to be deployed across two blockchains, which are called the root chain and child chain. In the case of Polygon POS v3 itself, Ethereum mainnet is the root chain, while Polygon POS v3 is the child chain. In order to give users the ability to work with these contracts on the chains of their choice, four networks are configured in Hardhat: `root`, `rootTest`, `child`, and `childTest`. To interact with whichever networks you would like to use as root and/or child, you will need to add a URL pointing to an RPC endpoint on the relevant chain in your `.env` file. This can be a RPC provider such as Ankr or Alchemy, in which case you would put the entire URL including the API key into the relevant line of the `.env`, or could be a local node, in which case you would put `https://localhost:<PORT_NUMBER>` (usually 8545).

A field for a private key is also provided in the `.env`. You will need to input this if you are interacting with any public networks (for example, deploying the contracts to a testnet).
Expand Down Expand Up @@ -165,6 +201,8 @@ forge test

Simple gas profiling is included in Foundry tests by default. For a more complete gas profile using Foundry, see [their documentation](https://book.getfoundry.sh/forge/gas-reports).

Simple gas profiling is included in Foundry tests by default. For a more complete gas profile using Foundry, see [their documentation](https://book.getfoundry.sh/forge/gas-reports).

### Linting

The linters run from inside the Hardhat/JS environment.
Expand Down Expand Up @@ -217,3 +255,16 @@ There is a CI script for Github Actions in `.github/workflows/`. Currently it ru
- both test suites (fails if any tests fail)
- coverage report (currently only HH)
- Slither

### Continuous Integration

There is a CI script for Github Actions in `.github/workflows/`. Currently it runs:

- linters
- both test suites (fails if any tests fail)
- coverage report (currently only HH)
- Slither

### Documentation

This repo makes use of [Dodoc](https://github.com/primitivefinance/primitive-dodoc), a Hardhat plugin from Primitive Finance which generates Markdown docs on contracts from their natspec. The docs are generated on every compile, and can be found in the `docs/` directory.
87 changes: 85 additions & 2 deletions contracts/child/ChildValidatorSet.sol
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,9 @@ contract ChildValidatorSet is System, Owned, ReentrancyGuardUpgradeable, IChildV
uint256 public minDelegation;

IBLS public bls;
/**
* @notice Message to sign for registration
*/
uint256[2] public message;

ValidatorTree private _validators;
Expand All @@ -52,8 +55,19 @@ contract ChildValidatorSet is System, Owned, ReentrancyGuardUpgradeable, IChildV
_;
}

/// @notice Initializer function for genesis contract, called by v3 client at genesis to set up the initial set.
/// @param governance Governance address to set as owner of the contract
/**
* @notice Initializer function for genesis contract, called by v3 client at genesis to set up the initial set.
* @dev only callable by client, can only be called once
* @param newEpochReward reward for a proposed epoch
* @param newMinStake minimum stake to become a validator
* @param newMinDelegation minimum amount to delegate to a validator
* @param validatorAddresses addresses of initial validators
* @param validatorPubkeys uint256[4] BLS public keys of initial validators
* @param validatorStakes amount staked per initial validator
* @param newBls address pf BLS contract/precompile
* @param newMessage message for BLS signing
* @param governance Governance address to set as owner of the contract
*/
function initialize(
uint256 newEpochReward,
uint256 newMinStake,
Expand Down Expand Up @@ -91,6 +105,9 @@ contract ChildValidatorSet is System, Owned, ReentrancyGuardUpgradeable, IChildV
message = newMessage;
}

/**
* @inheritdoc IChildValidatorSet
*/
function commitEpoch(
uint256 id,
Epoch calldata epoch,
Expand All @@ -115,18 +132,27 @@ contract ChildValidatorSet is System, Owned, ReentrancyGuardUpgradeable, IChildV
emit NewEpoch(id, epoch.startBlock, epoch.endBlock, epoch.epochRoot);
}

/**
* @inheritdoc IChildValidatorSet
*/
function addToWhitelist(address[] calldata whitelistAddreses) external onlyOwner {
for (uint256 i = 0; i < whitelistAddreses.length; i++) {
_addToWhitelist(whitelistAddreses[i]);
}
}

/**
* @inheritdoc IChildValidatorSet
*/
function removeFromWhitelist(address[] calldata whitelistAddreses) external onlyOwner {
for (uint256 i = 0; i < whitelistAddreses.length; i++) {
_removeFromWhitelist(whitelistAddreses[i]);
}
}

/**
* @inheritdoc IChildValidatorSet
*/
function register(uint256[2] calldata signature, uint256[4] calldata pubkey) external {
if (!whitelist[msg.sender]) revert Unauthorized("WHITELIST");

Expand All @@ -142,6 +168,9 @@ contract ChildValidatorSet is System, Owned, ReentrancyGuardUpgradeable, IChildV
emit NewValidator(msg.sender, pubkey);
}

/**
* @inheritdoc IChildValidatorSet
*/
function stake() external payable onlyValidator {
uint256 currentStake = _validators.stakeOf(msg.sender);
if (msg.value + currentStake < minStake) revert StakeRequirement({src: "stake", msg: "STAKE_TOO_LOW"});
Expand All @@ -150,6 +179,9 @@ contract ChildValidatorSet is System, Owned, ReentrancyGuardUpgradeable, IChildV
emit Staked(msg.sender, msg.value);
}

/**
* @inheritdoc IChildValidatorSet
*/
function unstake(uint256 amount) external {
int256 totalValidatorStake = int256(_validators.stakeOf(msg.sender)) + _queue.pendingStake(msg.sender);
int256 amountInt = amount.toInt256Safe();
Expand All @@ -168,6 +200,9 @@ contract ChildValidatorSet is System, Owned, ReentrancyGuardUpgradeable, IChildV
emit Unstaked(msg.sender, amount);
}

/**
* @inheritdoc IChildValidatorSet
*/
function delegate(address validator, bool restake) external payable {
RewardPool storage delegation = _validators.getDelegationPool(validator);
if (delegation.balanceOf(msg.sender) + msg.value < minDelegation)
Expand All @@ -176,6 +211,9 @@ contract ChildValidatorSet is System, Owned, ReentrancyGuardUpgradeable, IChildV
_delegate(msg.sender, validator, msg.value);
}

/**
* @inheritdoc IChildValidatorSet
*/
function undelegate(address validator, uint256 amount) external {
RewardPool storage delegation = _validators.getDelegationPool(validator);
uint256 delegatedAmount = delegation.balanceOf(msg.sender);
Expand All @@ -199,6 +237,9 @@ contract ChildValidatorSet is System, Owned, ReentrancyGuardUpgradeable, IChildV
emit Undelegated(msg.sender, validator, amount);
}

/**
* @inheritdoc IChildValidatorSet
*/
function claimValidatorReward() public {
Validator storage validator = _validators.get(msg.sender);
uint256 reward = validator.withdrawableRewards;
Expand All @@ -208,6 +249,9 @@ contract ChildValidatorSet is System, Owned, ReentrancyGuardUpgradeable, IChildV
emit ValidatorRewardClaimed(msg.sender, reward);
}

/**
* @inheritdoc IChildValidatorSet
*/
function claimDelegatorReward(address validator, bool restake) public {
RewardPool storage pool = _validators.getDelegationPool(validator);
uint256 reward = pool.claimRewards(msg.sender);
Expand All @@ -222,6 +266,9 @@ contract ChildValidatorSet is System, Owned, ReentrancyGuardUpgradeable, IChildV
emit DelegatorRewardClaimed(msg.sender, validator, restake, reward);
}

/**
* @inheritdoc IChildValidatorSet
*/
function withdraw(address to) external nonReentrant {
assert(to != address(0));
WithdrawalQueue storage queue = _withdrawals[msg.sender];
Expand All @@ -233,29 +280,47 @@ contract ChildValidatorSet is System, Owned, ReentrancyGuardUpgradeable, IChildV
require(success, "WITHDRAWAL_FAILED");
}

/**
* @inheritdoc IChildValidatorSet
*/
function setCommission(uint256 newCommission) external onlyValidator {
require(newCommission <= MAX_COMMISSION, "INVALID_COMMISSION");
Validator storage validator = _validators.get(msg.sender);
validator.commission = newCommission;
}

/**
* @inheritdoc IChildValidatorSet
*/
function getCurrentValidatorSet() external view returns (address[] memory) {
return sortedValidators(ACTIVE_VALIDATOR_SET_SIZE);
}

/**
* @inheritdoc IChildValidatorSet
*/
function getEpochByBlock(uint256 blockNumber) external view returns (Epoch memory) {
uint256 ret = epochEndBlocks.findUpperBound(blockNumber);
return epochs[ret + 1];
}

/**
* @inheritdoc IChildValidatorSet
*/
function getValidator(address validator) public view returns (Validator memory) {
return _validators.get(validator);
}

/**
* @inheritdoc IChildValidatorSet
*/
function delegationOf(address validator, address delegator) external view returns (uint256) {
return _validators.getDelegationPool(validator).balanceOf(delegator);
}

/**
* @inheritdoc IChildValidatorSet
*/
function sortedValidators(uint256 n) public view returns (address[] memory) {
uint256 length = n <= _validators.count ? n : _validators.count;
address[] memory validatorAddresses = new address[](length);
Expand All @@ -273,10 +338,16 @@ contract ChildValidatorSet is System, Owned, ReentrancyGuardUpgradeable, IChildV
return validatorAddresses;
}

/**
* @inheritdoc IChildValidatorSet
*/
function totalStake() external view returns (uint256) {
return _validators.totalStake;
}

/**
* @inheritdoc IChildValidatorSet
*/
function totalActiveStake() public view returns (uint256 activeStake) {
uint256 length = ACTIVE_VALIDATOR_SET_SIZE <= _validators.count ? ACTIVE_VALIDATOR_SET_SIZE : _validators.count;
if (length == 0) return 0;
Expand All @@ -290,18 +361,30 @@ contract ChildValidatorSet is System, Owned, ReentrancyGuardUpgradeable, IChildV
}
}

/**
* @inheritdoc IChildValidatorSet
*/
function withdrawable(address account) external view returns (uint256 amount) {
(amount, ) = _withdrawals[account].withdrawable(currentEpochId);
}

/**
* @inheritdoc IChildValidatorSet
*/
function pendingWithdrawals(address account) external view returns (uint256) {
return _withdrawals[account].pending(currentEpochId);
}

/**
* @inheritdoc IChildValidatorSet
*/
function getDelegatorReward(address validator, address delegator) external view returns (uint256) {
return _validators.getDelegationPool(validator).claimableRewards(delegator);
}

/**
* @inheritdoc IChildValidatorSet
*/
function getValidatorReward(address validator) external view returns (uint256) {
return getValidator(validator).withdrawableRewards;
}
Expand Down
2 changes: 1 addition & 1 deletion contracts/child/MRC20.sol
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ pragma solidity 0.8.17;

import "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol";
import "@openzeppelin/contracts/utils/Context.sol";
import "./IStateReceiver.sol";
import "../interfaces/IStateReceiver.sol";
import "./System.sol";

/**
Expand Down
Loading

0 comments on commit eeceb0d

Please sign in to comment.