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

Staking Contract #27

Closed
wants to merge 5 commits into from
Closed

Staking Contract #27

wants to merge 5 commits into from

Conversation

ilblackdragon
Copy link
Member

@ilblackdragon ilblackdragon commented Jan 13, 2020

  • Proposal Name: staking-contract
  • Start Date: 2020-01-12
  • NEP PR: nearprotocol/neps#0000
  • Issue(s): link to relevant issues in relevant repos (not required).

Summary

Provides standard and reference internal design for staking contract with delegation.

Motivation

In NEAR contracts and accounts are the same object. This allows to have a contract that stakes NEAR with it's balance.
This is useful to implement delegation, custom reward dispersion, liquid staking and various derivatives on stake and more.

Guide-level explanation

The simplest example of staking contract would be a delegation contract.

Let's define actors:

  • The staking pool contract account staking-pool. A key-less account with the contract that pools funds.
  • The owner of the staking contract owner. Owner runs the validator node on behalf of the staking pool account.
  • A delegator user1. The account who wants to stake their fund to the pool.

The owner can setup such contract and validate on behalf of this contract in their node.
Any other user can send their tokens to the contract, which will be pooled together and increase the total stake.
These users would accrue rewards (subtracted fees set by the owner).
Then they can unstake and withdraw their balance after some unlocking period.

More complex example can also issue token for deposited user's stake. This stake is the right to withdraw underlaying balance and rewards. This provides staking liquidity and allows to trade staked tokens.

Reference-level explanation

Staking contract guarantees:

  • The contract can't lose or lock tokens of users.
  • If a user deposited X, the user should be able to withdraw at least X.
  • If a user successfully staked X, the user can unstake at least X.
  • The contract should lock unstaked funds for longer than 4 epochs.

Staking Contract spec

Next interface is implemented by staking contract:

/******************/
/* Change methods */
/******************/

/// Call to distribute rewards after the new epoch. It's automatically called before every
/// changing action. But it's not called before the view methods, so one might need to call
/// `ping` before calling view methods to get recent results.
pub fn ping(&mut self);

/// Deposits the attached amount into the inner account of the predecessor.
#[payable]
pub fn deposit(&mut self);

/// Withdraws the non staked balance for given account.
/// It's only allowed if the `unstake` action was not performed in the recent 4 epochs.
pub fn withdraw(&mut self, amount: U128);

/// Stakes the given amount from the inner account of the predecessor.
/// The inner account should have enough unstaked balance.
pub fn stake(&mut self, amount: U128);

/// Unstakes the given amount from the inner account of the predecessor.
/// The inner account should have enough staked balance.
/// The new total unstaked balance will be available for withdrawal in 4 epochs.
pub fn unstake(&mut self, amount: U128);

/****************/
/* View methods */
/****************/

/// Returns the unstaked balance of the given account.
pub fn get_account_unstaked_balance(&self, account_id: AccountId) -> U128;

/// Returns the staked balance of the given account.
pub fn get_account_staked_balance(&self, account_id: AccountId) -> U128;

/// Returns the total balance of the given account (including staked and unstaked balances).
pub fn get_account_total_balance(&self, account_id: AccountId) -> U128;

/// Returns `true` if the given account can withdraw unstaked tokens in the current epoch.
pub fn is_account_unstaked_balance_available(&self, account_id: AccountId) -> bool;

/// Returns the total staking balance.
pub fn get_total_staked_balance(&self) -> U128;

User path

A simple path for a user who wants to pool their funds can be the following:

Delegate money

To deposit and stake 100 NEAR.

near call staking-pool deposit '{}' --accountId user1 --amount 100
near call staking-pool stake '{"amount": "100000000000000000000000000"}' --accountId user1

Wait for a week, so the pool accumulate some rewards.

Update internal state of the staking pool (optional)
near call staking-pool ping '{}' --accountId user1
See current balance
# User1 total balance
near view staking-pool get_account_total_balance '{"account_id": "user1"}'
Unstake some rewards and withdraw them

Get the current staked balance

# User1 staked balance
near view staking-pool get_account_staked_balance '{"account_id": "user1"}'

Let's say user1 accumulated 0.6 NEAR and the total is 100.6 NEAR. The user decides to withdraw 0.5 NEAR.
First the user has to unstake 0.5 NEAR.

near call staking-pool unstake '{"amount": "500000000000000000000000"}' --accountId user1

Let's check the unstaked balance.

# User1 unstaked balance
near view staking-pool get_account_unstaked_balance '{"account_id": "user1"}'

Wait for 4 epochs. Check if the balance is liquid and can be withdrawn now.

# Whether @user1 can withdraw
near view staking-pool is_account_unstaked_balance_available '{"account_id": "user1"}'

Now withdraw 0.5 NEAR back to the user1 account.

near call staking-pool withdraw '{"amount": "500000000000000000000000"}' --accountId user1

Drawbacks

General drawback of staking finance, is leverage of the stake. Someone can try to create leverage and buy more stake to control bigger position of the network and do bigger harm. The issue is that this happening and addressing this on chain gives us great visibility.

Rationale and alternatives

There a number of applications around DeFi and staking that are going to happened soon.
Currently they require a separate set of multi-sig holders on a different chain. This creates new vectors of attack in addition to reducing visibility of security on chain.

Because in NEAR contracts can stake, we can relatively easily add the support for any generic staking financial contracts.

Unresolved questions

Future possibilities

@ilblackdragon
Copy link
Member Author

@evgenykuzyakov thoughts?

Copy link
Contributor

@evgenykuzyakov evgenykuzyakov left a comment

Choose a reason for hiding this comment

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

I'm against having any callback towards contracts that are called based on the time/block/some event. It has a lot of security implications as well as complexity involved with this.
Security:

  • priorities of such callbacks
  • handling them securely

Complexity:

  • gas price uncertainty
  • limited use-case for staking. Not general enough.

Alternative solution is to design the contract that is unaware of the rewards or events happening with the account balance/locked.

It's possible to issue token (similar to uniswap pooling) that gives shares towards the contract balance. Once the account balance has changed, we can recalculate your share towards this balance.
It will also account for the storage/account_name rent.

text/0000-staking-contract.md Outdated Show resolved Hide resolved
text/0000-staking-contract.md Outdated Show resolved Hide resolved
@ilblackdragon
Copy link
Member Author

Discussion offline:

  • Uniswap suggestion by @evgenykuzyakov doesn't really work, as there is changing balance happening due to rewards.
  • Instead an alternative where ping() call required to the staking contract every epoch. This allows to capture what rewards were added and distribute them to the users.
  • Additional things required from runtime is epoch_id - way to determine current epoch and if epoch changed.

@ilblackdragon
Copy link
Member Author

Longer discussion happened in CW: https://commonwealth.im/near/proposal/discussion/357-riskless-delegation-aka-tezos-delegation re:delegation with outcome to continue with this approach for all delegation needs.

Will refresh this with latest approach.

Co-Authored-By: Evgeny Kuzyakov <ek@nearprotocol.com>
@evgenykuzyakov
Copy link
Contributor

The latest staking design looks better. We'll need to incorporate voting for staking pools

@render
Copy link

render bot commented May 8, 2020

@render
Copy link

render bot commented May 8, 2020

Your Render PR Server at https://nomicon-pr-27.onrender.com is now live!

View it on your dashboard at https://dashboard.render.com/static/srv-bqq8lj08atn30p5p2900.

Copy link
Collaborator

@bowenwang1996 bowenwang1996 left a comment

Choose a reason for hiding this comment

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

It doesn't mention anything about voting

@evgenykuzyakov
Copy link
Contributor

It doesn't mention anything about voting

It doesn't have to. The spec just defines the interface and invariants with guarantees. It doesn't need to mention particular implementation, e.g. shares or inner implementation or extra methods, like initialization, etc.

@ilblackdragon
Copy link
Member Author

See https://github.com/near/core-contracts/tree/master/staking-pool to detailed design and implementation.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants