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

dapp staking v3 - part 4 #1053

Merged
merged 53 commits into from
Nov 14, 2023
Merged
Show file tree
Hide file tree
Changes from 48 commits
Commits
Show all changes
53 commits
Select commit Hold shift + click to select a range
0a7c519
EraRewardSpan
Dinonard Oct 11, 2023
e598d1b
Initial version of claim_staker_reward
Dinonard Oct 12, 2023
fff0656
Tests
Dinonard Oct 12, 2023
0daae3a
Test utils for claim-staker
Dinonard Oct 13, 2023
ebae663
Bug fixes, improvements
Dinonard Oct 13, 2023
c76ade9
Claim improvements & some tests
Dinonard Oct 13, 2023
096726e
Refactoring in progress
Dinonard Oct 16, 2023
39c6cf0
Refactoring continued
Dinonard Oct 16, 2023
e77327d
Refactoring progress
Dinonard Oct 17, 2023
025f0b7
Refactoring finished
Dinonard Oct 17, 2023
80a0e4f
Bonus rewards
Dinonard Oct 18, 2023
82c71ad
Docs & some minor changes
Dinonard Oct 18, 2023
582aef4
Comments, tests, improved coverage
Dinonard Oct 18, 2023
b25ccb1
Tier params & config init solution
Dinonard Oct 20, 2023
3d2d146
Tier reward calculation WIP
Dinonard Oct 20, 2023
894d10d
Tier assignemnt
Dinonard Oct 23, 2023
ff61cf1
Minor cleanup
Dinonard Oct 24, 2023
3ded046
Claim dapp rewards
Dinonard Oct 24, 2023
f2f8a5f
Claim dapp reward tests
Dinonard Oct 24, 2023
03d88f4
unstake from unregistered call
Dinonard Oct 24, 2023
ea3e23d
Extra traits
Dinonard Oct 24, 2023
7558682
fixes
Dinonard Oct 24, 2023
e0f19e1
Extra calls
Dinonard Oct 25, 2023
4926838
Refactoring
Dinonard Oct 25, 2023
d47bcf4
More refactoring, improvements, TODO solving
Dinonard Oct 25, 2023
0197c95
Local integration
Dinonard Oct 25, 2023
4ea8556
Genesis config
Dinonard Oct 25, 2023
589575b
Add forcing call
Dinonard Oct 25, 2023
1e80e09
try runtime build fix
Dinonard Oct 25, 2023
fbe4c4d
Minor changes
Dinonard Oct 26, 2023
12d01fd
Minor
Dinonard Oct 27, 2023
d725575
Formatting
Dinonard Oct 27, 2023
72d3983
Benchmarks INIT
Dinonard Oct 27, 2023
f0c14cc
Compiling benchmarks
Dinonard Oct 27, 2023
e2447b3
Fix
Dinonard Oct 30, 2023
4b9e03d
dapp tier calculation benchmark
Dinonard Oct 30, 2023
8982e10
Measured tier assignment
Dinonard Oct 30, 2023
ee50c2d
Decending rewards in benchmarks
Dinonard Oct 30, 2023
67de44c
Series refactoring & partial tests
Dinonard Oct 31, 2023
c586a6c
Comments, minor changes
Dinonard Oct 31, 2023
5cf60a1
Tests, improvements
Dinonard Oct 31, 2023
7a3c634
More tests, some minor refactoring
Dinonard Nov 2, 2023
c47d36e
Formatting
Dinonard Nov 2, 2023
d36839c
More benchmarks & experiments, refactoring
Dinonard Nov 2, 2023
d0d196e
Readme, docs
Dinonard Nov 2, 2023
9723d9f
Minor renaming, docs
Dinonard Nov 3, 2023
24b4b53
More docs
Dinonard Nov 3, 2023
408eaad
More docs
Dinonard Nov 3, 2023
fd65f20
Minor addition
Dinonard Nov 7, 2023
8fe1563
Review comment fixes & changes
Dinonard Nov 9, 2023
3b894ef
Minor change
Dinonard Nov 10, 2023
7cc82da
Review comments
Dinonard Nov 10, 2023
cc1efcc
Update frontier to make CI pass
Dinonard Nov 13, 2023
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 Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -274,6 +274,7 @@ pallet-block-reward = { path = "./pallets/block-reward", default-features = fals
pallet-collator-selection = { path = "./pallets/collator-selection", default-features = false }
pallet-custom-signatures = { path = "./pallets/custom-signatures", default-features = false }
pallet-dapps-staking = { path = "./pallets/dapps-staking", default-features = false }
pallet-dapp-staking-v3 = { path = "./pallets/dapp-staking-v3", default-features = false }
pallet-xc-asset-config = { path = "./pallets/xc-asset-config", default-features = false }
pallet-xvm = { path = "./pallets/xvm", default-features = false }
pallet-xcm = { path = "./pallets/pallet-xcm", default-features = false }
Expand Down
1 change: 1 addition & 0 deletions bin/collator/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@ shiden-runtime = { workspace = true, features = ["std"] }
# astar pallets dependencies
astar-primitives = { workspace = true }
pallet-block-reward = { workspace = true }
pallet-dapp-staking-v3 = { workspace = true }

# frame dependencies
frame-system = { workspace = true, features = ["std"] }
Expand Down
42 changes: 37 additions & 5 deletions bin/collator/src/local/chain_spec.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,17 +20,19 @@
use local_runtime::{
wasm_binary_unwrap, AccountId, AuraConfig, AuraId, BalancesConfig, BaseFeeConfig,
BlockRewardConfig, CouncilConfig, DemocracyConfig, EVMConfig, GenesisConfig, GrandpaConfig,
GrandpaId, Precompiles, Signature, SudoConfig, SystemConfig, TechnicalCommitteeConfig,
TreasuryConfig, VestingConfig,
BlockRewardConfig, CouncilConfig, DappStakingConfig, DemocracyConfig, EVMConfig, GenesisConfig,
GrandpaConfig, GrandpaId, Precompiles, Signature, SudoConfig, SystemConfig,
TechnicalCommitteeConfig, TreasuryConfig, VestingConfig, AST,
};
use sc_service::ChainType;
use sp_core::{crypto::Ss58Codec, sr25519, Pair, Public};
use sp_runtime::{
traits::{IdentifyAccount, Verify},
Perbill,
Perbill, Permill,
};

use pallet_dapp_staking_v3::TierThreshold;

type AccountPublic = <Signature as Verify>::Signer;

/// Specialized `ChainSpec` for Shiden Network.
Expand Down Expand Up @@ -112,7 +114,7 @@ fn testnet_genesis(
balances: endowed_accounts
.iter()
.cloned()
.map(|k| (k, 1_000_000_000_000_000_000_000_000_000))
.map(|k| (k, 1_000_000_000 * AST))
.collect(),
},
block_reward: BlockRewardConfig {
Expand Down Expand Up @@ -181,6 +183,36 @@ fn testnet_genesis(
},
democracy: DemocracyConfig::default(),
treasury: TreasuryConfig::default(),
dapp_staking: DappStakingConfig {
reward_portion: vec![
Permill::from_percent(40),
Permill::from_percent(30),
Permill::from_percent(20),
Permill::from_percent(10),
],
slot_distribution: vec![
Permill::from_percent(10),
Permill::from_percent(20),
Permill::from_percent(30),
Permill::from_percent(40),
],
tier_thresholds: vec![
TierThreshold::DynamicTvlAmount {
amount: 100 * AST,
minimum_amount: 80 * AST,
},
TierThreshold::DynamicTvlAmount {
amount: 50 * AST,
minimum_amount: 40 * AST,
},
TierThreshold::DynamicTvlAmount {
amount: 20 * AST,
minimum_amount: 20 * AST,
},
TierThreshold::FixedTvlAmount { amount: 10 * AST },
],
slots_per_tier: vec![10, 20, 30, 40],
},
}
}

Expand Down
11 changes: 11 additions & 0 deletions pallets/dapp-staking-v3/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ sp-std = { workspace = true }

astar-primitives = { workspace = true }

frame-benchmarking = { workspace = true, optional = true }

[dev-dependencies]
pallet-balances = { workspace = true }

Expand All @@ -42,4 +44,13 @@ std = [
"frame-system/std",
"pallet-balances/std",
"astar-primitives/std",
"frame-benchmarking/std",
]
runtime-benchmarks = [
"frame-benchmarking",
"frame-support/runtime-benchmarks",
"frame-system/runtime-benchmarks",
"sp-runtime/runtime-benchmarks",
"astar-primitives/runtime-benchmarks",
]
try-runtime = ["frame-support/try-runtime"]
167 changes: 167 additions & 0 deletions pallets/dapp-staking-v3/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
# dApp Staking v3

## Introduction

Astar and Shiden networks provide a unique way for developers to earn rewards by developing products that native token holdes decide to support.
Dinonard marked this conversation as resolved.
Show resolved Hide resolved

The principle is simple - stakers lock their tokens to _stake_ on a dApp, and if the dApp attracts enough support, it is rewarded in native currency, derived from the inflation.
In turn stakers are rewarded for locking & staking their tokens.

## Functionality Overview

### Eras

Eras are the basic _time unit_ in dApp staking and their length is measured in the number of blocks.

They are not expected to last long, e.g. current live networks era length is roughly 1 day (7200 blocks).
After an era ends, it's usually possible to claim rewards for it, if user or dApp are eligible.

### Periods

Periods are another _time unit_ in dApp staking. They are expected to be more lengthy than eras.

Each period consists of two subperiods:
* `Voting`
* `Build&Earn`

Each period is denoted by a number, which increments each time a new period begins.
Period beginning is marked by the `voting` subperiod, after which follows the `build&earn` period.

Stakes are **only** valid throughout a period. When new period starts, all stakes are reset to zero. This helps prevent projects remaining staked due to intertia.

#### Voting

When `Voting` starts, all _stakes_ are reset to **zero**.
Projects participating in dApp staking are expected to market themselves to (re)attract stakers.

Stakers must assess whether the project they want to stake on brings value to the ecosystem, and then `vote` for it.
Casting a vote, or staking, during the `Voting` subperiod makes the staker eligible for bonus rewards. so they are encouraged to participate.

`Voting` subperiod length is expressed in _standard_ era lengths, even though the entire voting period is treated as a single _voting era_.
E.g. if `voting` subperiod lasts for **10 eras**, and each era lasts for **100** blocks, total length of the `voting` subperiod will be **1000** blocks.

Neither stakers nor dApps earn rewards during this subperiod - no new rewards are generated after `voting` subperiod ends.

#### Build&Earn

`Build&Earn` subperiod consits of one or more eras, therefore its length is expressed in eras.

After each _era_ ends, eligible stakers and dApps can claim the rewards they earned. Rewards are **only** claimable for the finished eras.

It is still possible to _stake_ during this period, and stakers are encouraged to do so since this will increase the rewards they earn.
The only exemption is the **final era** of the `build&earn` subperiod - it's not possible to _stake_ then since the stake would be invalid anyhow (stake is only valid from the next era which would be in the next period).

### dApps & Smart Contracts

Protocol is called dApp staking, but internally it essentially works with smart contracts, or even more precise, smart contract addresses.

Throughout the code, when addressing a particular dApp, it's addressed as `smart contract`. Naming of the types & storage more closely follows `dApp` nomenclature.

#### Registration

Projects, or _dApps_, must be registered into protocol to participate.
Only a privileged `ManagerOrigin` can perform dApp registration.
The pallet itself does not make assumptions who the privileged origin is, and it can differ from runtime to runtime.

There is a limit of how many smart contracts can be registered at once. Once the limit is reached, any additional attempt to register a new contract will fail.

#### Reward Beneficiary & Ownership

When contract is registered, it is assigned a unique compact numeric Id - 16 bit unsigned integer. This is important for the inner workings of the pallet, and is not directly exposed to the users.

After a dApp has been registered, it is possible to modify reward beneficiary or even the owner of the dApp. The owner can perform reward delegation and can further transfer ownership.

#### Unregistration

dApp can be removed from the procotol by unregistering it.
This is a privileged action that only `ManagerOrigin` can perform.

After a dApp has been unregistered, it's no longer eligible to receive rewards.
It's still possible however to claim past unclaimed rewards.

Important to note that even if dApp has been unregistered, it still occupies a _slot_
in the dApp staking protocol and counts towards maximum number of registered dApps.
This will be improved in the future when dApp data will be cleaned up after the period ends.

### Stakers

#### Locking Tokens

In order for users to participate in dApp staking, the first step they need to take is lock some native currency. Reserved tokens cannot be locked, but tokens locked by another lock can be re-locked into dApp staking (double locked).
PierreOssun marked this conversation as resolved.
Show resolved Hide resolved

**NOTE:** Locked funds cannot be used for paying fees, or for transfer.

In order to participate, user must have a `MinimumLockedAmount` of native currency locked. This doesn't mean that they cannot lock _less_ in a single call, but total locked amount must always be equal or greater than `MinimumLockedAmount`.

In case amount specified for locking is greater than what user has available, only what's available will be locked.

#### Unlocking Tokens

User can at any time decide to unlock their tokens. However, it's not possible to unlock tokens which are staked, so user has to unstake them first.

Once _unlock_ is successfully executed, the tokens aren't immediately unlocked, but instead must undergo the unlocking process. Once unlocking process has finished, user can _claim_ their unlocked tokens into their free balance.

There is a limited number of `unlocking chunks` a user can have at any point in time. If limit is reached, user must claim existing unlocked chunks, or wait for them to be unlocked before claiming them to free up space for new chunks.

In case calling unlocking some amount would take the user below the `MinimumLockedAmount`, **everything** will be unlocked.

For users who decide they would rather re-lock their tokens then wait for the unlocking process to finish, there's an option to do so. All currently unlocking chunks are consumed, and added back into locked amount.

#### Staking Tokens

Locked tokens, which aren't being used for staking, can be used to stake on a dApp. This translates to _voting_ or _nominating_ a dApp to receive rewards derived from the inflation. User can stake on multiple dApps if they want to.

The staked amount **must be precise**, no adjustment will be made by the pallet in case a too large amount is specified.
PierreOssun marked this conversation as resolved.
Show resolved Hide resolved

The staked amount is only eligible for rewards from the next era - in other words, only the amount that has been staked for the entire era is eligible to receive rewards.

It is not possible to stake if there are unclaimed rewards from past eras. User must ensure to first claim their pending rewards, before staking. This is also beneficial to the users since it allows them to lock & stake the earned rewards as well.

User's stake on a contract must be equal or greater than the `MinimumStakeAmount`. This is similar to the minimum lock amount, but this limit is per contract.

Although user can stake on multiple smart contracts, the amount is limited. To be more precise, amount of database entries that can exist per user is limited.

The protocol keeps track of how much was staked by the user in `voting` and `build&earn` subperiod. This is important for the bonus reward calculation.

It is not possible to stake on a dApp that has been unregistered.
However, if dApp is unregistered after user has staked on it, user will keep earning
rewards for the staked amount.

#### Unstaking Tokens

User can at any time decide to unstake staked tokens. There's no _unstaking_ process associated with this action.

Unlike stake operation, which stakes from the _next_ era, unstake will reduce the staked amount for the current and next era if stake exists.

Same as with stake operation, it's not possible to unstake anything until unclaimed rewards have been claimed. User must ensure to first claim all rewards, before attempting to unstake. Unstake amount must also be precise as no adjustment will be done to the amount.

The amount unstaked will always first reduce the amount staked in the ongoing subperiod. E.g. if `voting` subperiod has stake of **100**, and `build&earn` subperiod has stake of **50**, calling unstake with amount **70** during `build&earn` subperiod will see `build&earn` stake amount reduced to **zero**, while `voting` stake will be reduced to **80**.

If unstake would reduce the staked amount below `MinimumStakeAmount`, everything is unstaked.

Once period finishes, all stakes are reset back to zero. This means that no unstake operation is needed after period ends to _unstake_ funds - it's done automatically.

If dApp has been unregistered, a special operation to unstake from unregistered contract must be used.

#### Claiming Staker Rewards

Stakers can claim rewards for passed eras during which they were staking. Even if multiple contracts were staked, claim reward call will claim rewards for all of them.

Only rewards for passed eras can be claimed. It is possible that a successful reward claim call will claim rewards for multiple eras. This can happen if staker hasn't claimed rewards in some time, and many eras have passed since then, accumulating pending rewards.

To achieve this, the pallet's underyling storage organizes **era reward information** into **spans**. A single span covers multiple eras, e.g. from **1** to **16**. In case user has staked during era 1, and hasn't claimed rewards until era 17, they will be eligible to claim 15 rewards in total (from era 2 to 16). All of this will be done in a single claim reward call.

In case unclaimed history has built up past one span, multiple reward claim calls will be needed to claim all of the rewards.

Rewards don't remain available forever, and if not claimed within some time period, they will be treated as expired. This will be a longer period, but will still exist.

Rewards are calculated using a simple formula: `staker_reward_pool * staker_staked_amount / total_staked_amount`.

#### Claim Bonus Reward

If staker staked on a dApp during the voting period, and didn't reduce their staked amount below what was staked at the end of the voting period, this makes them eligible for the bonus reward.

Bonus rewards need to be claimed per contract, unlike staker rewards.

Bonus reward is calculated using a simple formula: `bonus_reward_pool * staker_voting_period_stake / total_voting_period_stake`.

Loading
Loading