Merge pull request #2597 from derekpierre/staking-pool-docs

Staking Pool Docs
pull/2633/head
KPrasch 2021-04-09 14:24:58 -07:00 committed by GitHub
commit a93af0d82f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 163 additions and 2 deletions

View File

@ -0,0 +1,159 @@
.. _staking_contracts:
Staking Contracts
=================
Architecture
------------
NuCypher :doc:`Main Contracts</contracts_api/index>` enforce a 1:1 relationship between Staker and Worker roles. Even
though the ``StakingEscrow`` smart contract forces the Worker address to be an Ethereum account, the Staker address
can be that of a smart contract. Any staking contract must fulfil the requirements of the Main contracts, which
can be subsequently updated, including ABIs. Therefore, staking contracts must either be upgradeable or
have the ability to route to new methods in the Main contracts.
Development
-----------
To minimize development efforts, developers can use the :doc:`AbstractStakingContract </contracts_api/staking/AbstractStakingContract>`
smart contract API. It accesses Main Contracts through
:doc:`StakingInterface </contracts_api/staking/StakingInterface>` and
:doc:`StakingInterfaceRouter </contracts_api/staking/StakingInterfaceRouter>` so that developers do not need to access
the Main contracts directly or be concerned about upgraded or incompatible ABIs; instead, they can concentrate on
the core logic of their staking contract.
This feature makes it possible for the developer's staking smart contract to not be upgradeable. The
benefit of a non-upgradeable contract is that it reassures users that once the contract is deployed, it cannot
be modified.
Implementation
^^^^^^^^^^^^^^
Currently, there are two versions of ``AbstractStakingContract`` which are very similar except for how they
are initialized:
* ``AbstractStakingContract`` - the constructor is used to initialize values. It is a simplified version that uses
less gas but is not suitable to use with OpenZeppelin's ``Proxy`` contract because of the initialization logic in the
constructor
* ``InitializableStakingContract`` - the initialization logic was extracted from the constructor and
instead provided via an initialization method which allows it to be used with
OpenZeppelin's ``Proxy`` contract
These contracts can be inherited, and the following methods implemented:
* ``isFallbackAllowed()`` - defines who is allowed to call Staker methods. A simple example to allow only the owner:
.. code::
function isFallbackAllowed() public override view returns (bool) {
return msg.sender == owner();
}
* ``withdrawTokens(uint256 _value)`` - the process of withdrawal of NU from the contract
* ``withdrawETH()`` - the process of withdrawal of ETH from the contract
This implementation will result in two ABIs:
* The developer's staking contract implementation
* ``StakingInterface`` to access main contracts
Staking Pool Contract
---------------------
In an effort to reduce the friction associated with staking NU and running a Worker,
a :doc:`simple staking pool smart contract </contracts_api/staking/PoolingStakingContractV2>` is provided.
.. note::
While NuCypher had the staking pool contract `audited by MixBytes <https://github.com/mixbytes/audits_public/tree/master/NuCypher>`_,
there remains smart contract risk and there are no guarantees about the logic. There is also the risk of trust in
the *Owner* of the contract to not be malicious. See the `Risks`_ section below.
The staking pool smart contract organizes multiple NU holders into one large Staker which delegates to a
single Worker. It reduces overall Worker gas costs by combining multiple Stakers, each of whom would
have needed to run a Worker otherwise, into one large Staker that uses a single Worker to perform work on
behalf of the staking pool.
Each token holder can deposit any amount of NU into the pool and will be entitled to the pro-rata
share of the pool and proceeds without needing to maintain and run their own Worker. Token holders will pay a
percentage of NU staking rewards received to the owner of the Worker for running a node. For example, if a token holder
deposits 5% of the total NU deposited to the NU they are entitled to 5% of the staking rewards (after fees) and 5%
of the ETH policy fees received.
Contract Roles
^^^^^^^^^^^^^^
The pooling contract has several roles:
* *Owner* - controls the staking functions including all parameters of staking e.g. ``restake``, ``winddown``, etc.
.. important::
It is recommended to use a multisig, DAO or other decentralized governance mechanism.
* *Worker Owner* - the owner of the Worker that runs on behalf of the staking pool; only this address can
withdraw the worker's collected fee
* *Delegators* - the NU holders who deposit NU into the pooling contract
* *Administrator (Optional)* - oversees contract upgrades which allow modification to all contract logic
and behaviour; this role only applies when the contract is upgradeable and the OpenZeppelin's ``Proxy`` contract
is utilized
.. warning::
Be cautious of who is bestowed the Administrator role because even if the pooling contract was audited,
the Administrator can modify/upgrade the contract code after deployment. **It is recommended to use a
multisig, DAO or other decentralized governance mechanism.**
Worker Fees
^^^^^^^^^^^
The *Worker Owner* receives a percentage of NU staking rewards as a fee for running a Worker on behalf of the
staking pool. This percentage is configured during contract deployment. The implication here is that if the pool
does not generate any rewards then the *Worker Owner* will not receive any fee.
Contract Lifecycle
^^^^^^^^^^^^^^^^^^
* The *Owner* deploys the contract and initializes it by specifying the Worker fee percentage and the *Worker Owner*
address.
* Once deployed, deposits are enabled by default to start accepting deposits from *Delegators*.
* At any point before the *Owner* creates a stake, a Delegator can exit the pool and recoup their original deposit via
the ``withdrawAll`` function.
* After the intended deposits have been received, the *Owner* specifies staking parameters to create a stake: size,
duration, restaking, winddown etc., and bonds the stake to the Worker address.
* Once the *Owner* creates a stake, the ability to deposit into the pool is automatically disabled to prevent any
new deposits. Conditions for disabled deposits are enforced via the use of ``isDepositAllowed`` function checks
within the contract. Disabling deposits ensures that there is clear proportional ownership of the pool and its received
rewards i.e. if a *Delegator* provided 5% of the deposits, they will receive 5% of the proceeds from the staking pool
- staking rewards (after fees) and policy fees. This is a much simpler model for determining proportional ownership
than allowing deposits after staking has started and after staking rewards and policy fees have already been received.
* Once staking rewards and policy fees have been generated, the *Owner* can withdraw these from ``StakingEscrow``
to the pool; staking rewards via ``withdrawAsStaker``, and policy fees via ``withdrawPolicyFee``. *Delegators* can
determine the current value of their proportional share of rewards and fees via the ``getAvailableDelegatorReward``
and ``getAvailableDelegatorETH`` functions respectively. Their share of the proceeds can be withdrawn from the pool
via the ``withdrawTokens`` and ``withdrawETH`` contract functions. Note that this is only for staking rewards and
policy fees, **not** their original deposit. The original deposit can only be withdrawn once the stake has expired.
* Throughout this process, the *Worker Owner* can determine their Worker commission via the ``getAvailableWorkerReward``
function and retrieve it via the ``withdrawWorkerReward`` function.
* When the stake eventually becomes expired and the *Owner* withdraws the pool's escrowed NU from ``StakingEscrow`` via
``withdrawAsStaker``, then the withdrawn NU will be available for *Delegators* to withdraw including their
deposit, and proportional to their share.
* *Delegators* that want to withdraw their original deposit, NU rewards and ETH fees i.e. exit
the pool, they can do so via the ``withdrawAll`` function.
Risks
^^^^^
* The *Owner* / *Administrator* making the pooling contract Upgradeable in which case all logic can be modified.
* The *Owner* disabling ``winddown`` when creating the stake, and never turning it on, thereby keeping the locked
duration constant until ``winddown`` is enabled, potentially never.
* The *Owner* not running a Worker after creating the stake; by not running a node, the stake will be locked until the
work is performed.

View File

@ -1,8 +1,7 @@
Approaches to Upgradeable Contracts
===================================
Smart contracts in Ethereum are immutable...
In order to fix bugs and provide upgrade logic it is possible to change the contract (address) and save the original contract's storage values.
Smart contracts in Ethereum are immutable. In order to fix bugs and provide upgrade logic it is possible to change the contract (address) and save the original contract's storage values.
Approach A

View File

@ -133,6 +133,7 @@ Whitepapers
architecture/sub_stakes
architecture/slashing
architecture/service_fees
architecture/staking_contracts
.. toctree::
:maxdepth: 1

View File

@ -0,0 +1 @@
Document staking smart contract API and the base staking pool implementation (``PoolingStakingContractV2``).

View File

@ -40,6 +40,7 @@ CONTRACTS = {
'AbstractStakingContract',
'InitializableStakingContract',
'PoolingStakingContract',
'PoolingStakingContractV2',
'WorkLockPoolingContract']
}