Staking Contracts

Architecture

NuCypher Main Contracts 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 AbstractStakingContract smart contract API. It accesses Main Contracts through StakingInterface and 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:

    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 simple staking pool smart contract is provided.

Note

While NuCypher had the staking pool contract audited by MixBytes, 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.