From 3a011678e785d31502b264bd48347a8afc6ba2bc Mon Sep 17 00:00:00 2001 From: rigelrozanski Date: Thu, 22 Mar 2018 17:00:45 +0100 Subject: [PATCH] keeper tests/revisions --- docs/spec/README.md | 15 +- docs/spec/governance/governance.md | 4 +- docs/spec/governance/state.md | 4 +- docs/spec/staking/definitions and examples.md | 250 +++--- docs/spec/staking/old/spec.md | 14 +- docs/spec/staking/spec-technical.md | 763 +++++++----------- x/stake/keeper.go | 233 ++---- x/stake/keeper_keys.go | 51 ++ x/stake/keeper_test.go | 165 ++-- x/stake/pool.go | 78 +- x/stake/pool_test.go | 22 + x/stake/test_common.go | 4 +- x/stake/tick.go | 26 +- x/stake/tick_test.go | 46 +- x/stake/types.go | 26 +- 15 files changed, 758 insertions(+), 943 deletions(-) create mode 100644 x/stake/keeper_keys.go create mode 100644 x/stake/pool_test.go diff --git a/docs/spec/README.md b/docs/spec/README.md index 5f3942ff9b..6a507dc031 100644 --- a/docs/spec/README.md +++ b/docs/spec/README.md @@ -1,18 +1,13 @@ # Cosmos Hub Spec -This directory contains specifications for the application level components of -the Cosmos Hub. +This directory contains specifications for the application level components of the Cosmos Hub. NOTE: the specifications are not yet complete and very much a work in progress. -- [Basecoin](basecoin) - Cosmos SDK related specifications and transactions for - sending tokens. -- [Staking](staking) - Proof of Stake related specifications including bonding - and delegation transactions, inflation, fees, etc. -- [Governance](governance) - Governance related specifications including - proposals and voting. -- [Other](other) - Other components of the Cosmos Hub, including the reserve - pool, All in Bits vesting, etc. +- [Basecoin](basecoin) - Cosmos SDK related specifications and transactions for sending tokens. +- [Staking](staking) - Proof of Stake related specifications including bonding and delegation transactions, inflation, fees, etc. +- [Governance](governance) - Governance related specifications including proposals and voting. +- [Other](other) - Other components of the Cosmos Hub, including the reserve pool, All in Bits vesting, etc. The [specification for Tendermint](https://github.com/tendermint/tendermint/tree/develop/docs/specification/new-spec), i.e. the underlying blockchain, can be found elsewhere. diff --git a/docs/spec/governance/governance.md b/docs/spec/governance/governance.md index 9fe9635bd2..2e40e92dd4 100644 --- a/docs/spec/governance/governance.md +++ b/docs/spec/governance/governance.md @@ -147,8 +147,8 @@ type Procedure struct { MinDeposit int64 // Minimum deposit for a proposal to enter voting period. OptionSet []string // Options available to voters. {Yes, No, NoWithVeto, Abstain} ProposalTypes []string // Types available to submitters. {PlainTextProposal, SoftwareUpgradeProposal} - Threshold rational.Rat // Minimum propotion of Yes votes for proposal to pass. Initial value: 0.5 - Veto rational.Rat // Minimum value of Veto votes to Total votes ratio for proposal to be vetoed. Initial value: 1/3 + Threshold rational.Rational // Minimum propotion of Yes votes for proposal to pass. Initial value: 0.5 + Veto rational.Rational // Minimum value of Veto votes to Total votes ratio for proposal to be vetoed. Initial value: 1/3 MaxDepositPeriod int64 // Maximum period for Atom holders to deposit on a proposal. Initial value: 2 months GovernancePenalty int64 // Penalty if validator does not vote diff --git a/docs/spec/governance/state.md b/docs/spec/governance/state.md index 5457590d0d..b6f0d3a61c 100644 --- a/docs/spec/governance/state.md +++ b/docs/spec/governance/state.md @@ -15,8 +15,8 @@ type Procedure struct { MinDeposit int64 // Minimum deposit for a proposal to enter voting period. OptionSet []string // Options available to voters. {Yes, No, NoWithVeto, Abstain} ProposalTypes []string // Types available to submitters. {PlainTextProposal, SoftwareUpgradeProposal} - Threshold rational.Rat // Minimum propotion of Yes votes for proposal to pass. Initial value: 0.5 - Veto rational.Rat // Minimum value of Veto votes to Total votes ratio for proposal to be vetoed. Initial value: 1/3 + Threshold rational.Rational // Minimum propotion of Yes votes for proposal to pass. Initial value: 0.5 + Veto rational.Rational // Minimum value of Veto votes to Total votes ratio for proposal to be vetoed. Initial value: 1/3 MaxDepositPeriod int64 // Maximum period for Atom holders to deposit on a proposal. Initial value: 2 months GovernancePenalty int64 // Penalty if validator does not vote diff --git a/docs/spec/staking/definitions and examples.md b/docs/spec/staking/definitions and examples.md index ba4c1563e2..72dbc3a3d4 100644 --- a/docs/spec/staking/definitions and examples.md +++ b/docs/spec/staking/definitions and examples.md @@ -2,183 +2,113 @@ ## Basic Terms and Definitions -* Cosmsos Hub - a Tendermint-based Proof of Stake blockchain system -* Atom - native token of the Cosmsos Hub -* Atom holder - an entity that holds some amount of Atoms -* Candidate - an Atom holder that is actively involved in the Tendermint - blockchain protocol (running Tendermint Full Node (TODO: add link to Full - Node definition) and is competing with other candidates to be elected as a - validator (TODO: add link to Validator definition)) -* Validator - a candidate that is currently selected among a set of candidates - to be able to sign protocol messages in the Tendermint consensus protocol -* Delegator - an Atom holder that has bonded some of its Atoms by delegating - them to a validator (or a candidate) -* Bonding Atoms - a process of locking Atoms in a bond deposit (putting Atoms - under protocol control). Atoms are always bonded through a validator (or - candidate) process. Bonded atoms can be slashed (burned) in case a validator - process misbehaves (does not behave according to the protocol specification). - Atom holders can regain access to their bonded Atoms if they have not been - slashed by waiting an Unbonding period. -* Unbonding period - a period of time after which Atom holder gains access to - its bonded Atoms (they can be withdrawn to a user account) or they can be - re-delegated. -* Inflationary provisions - inflation is the process of increasing the Atom supply. - Atoms are periodically created on the Cosmos Hub and issued to bonded Atom holders. - The goal of inflation is to incentize most of the Atoms in existence to be bonded. -* Transaction fees - transaction fee is a fee that is included in a Cosmsos Hub - transaction. The fees are collected by the current validator set and - distributed among validators and delegators in proportion to their bonded - Atom share. -* Commission fee - a fee taken from the transaction fees by a validator for - their service +- Cosmsos Hub - a Tendermint-based Proof of Stake blockchain system +- Atom - native token of the Cosmsos Hub +- Atom holder - an entity that holds some amount of Atoms +- Candidate - an Atom holder that is actively involved in Tendermint blockchain protocol (running Tendermint Full Node +TODO: add link to Full Node definition) and is competing with other candidates to be elected as a Validator +(TODO: add link to Validator definition)) +- Validator - a candidate that is currently selected among a set of candidates to be able to sign protocol messages +in the Tendermint consensus protocol +- Delegator - an Atom holder that has bonded any of its Atoms by delegating them to a validator (or a candidate) +- Bonding Atoms - a process of locking Atoms in a bond deposit (putting Atoms under protocol control). +Atoms are always bonded through a validator (or candidate) process. Bonded atoms can be slashed (burned) in case a +validator process misbehaves (does not behave according to a protocol specification). Atom holder can regain access +to its bonded Atoms (if they are not slashed in the meantime), i.e., they can be moved to its account, +after Unbonding period has expired. +- Unbonding period - a period of time after which Atom holder gains access to its bonded Atoms (they can be withdrawn +to a user account) or they can re-delegate +- Inflationary provisions - inflation is a process of increasing Atom supply. Atoms are being created in the process of +(Cosmos Hub) blocks creation. Owners of bonded atoms are rewarded for securing network with inflationary provisions +proportional to it's bonded Atom share. +- Transaction fees - transaction fee is a fee that is included in the Cosmsos Hub transactions. The fees are collected +by the current validator set and distributed among validators and delegators in proportion to it's bonded Atom share. +- Commission fee - a fee taken from the transaction fees by a validator for it's service ## The pool and the share -At the core of the Staking module is the concept of a pool which denotes a -collection of Atoms contributed by different Atom holders. There are two global -pools in the Staking module: the bonded pool and unbonding pool. Bonded Atoms -are part of the global bonded pool. If a candidate or delegator wants to unbond -its Atoms, those Atoms are moved to the the unbonding pool for the duration of -the unbonding period. In the Staking module, a pool is a logical concept, i.e., -there is no pool data structure that would be responsible for managing pool -resources. Instead, it is managed in a distributed way. More precisely, at the -global level, for each pool, we track only the total amount of bonded or unbonded -Atoms and the current amount of issued shares. A share is a unit of Atom distribution -and the value of the share (share-to-atom exchange rate) changes during -system execution. The share-to-atom exchange rate can be computed as: +At the core of the Staking module is the concept of a pool which denotes collection of Atoms contributed by different +Atom holders. There are two global pools in the Staking module: bonded pool and unbonded pool. Bonded Atoms are part +of the global bonded pool. On the other side, if a candidate or delegator wants to unbond its Atoms, those Atoms are +kept in the unbonding pool for a duration of the unbonding period. In the Staking module, a pool is logical concept, +i.e., there is no pool data structure that would be responsible for managing pool resources. Instead, it is managed +in a distributed way. More precisely, at the global level, for each pool, we track only the total amount of +(bonded or unbonded) Atoms and a current amount of issued shares. A share is a unit of Atom distribution and the +value of the share (share-to-atom exchange rate) is changing during the system execution. The +share-to-atom exchange rate can be computed as: -`share-to-atom-exchange-rate = size of the pool / ammount of issued shares` +`share-to-atom-ex-rate = size of the pool / ammount of issued shares` -Then for each validator candidate (in a per candidate data structure) we keep track of -the amount of shares the candidate owns in a pool. At any point in time, -the exact amount of Atoms a candidate has in the pool can be computed as the -number of shares it owns multiplied with the current share-to-atom exchange rate: +Then for each candidate (in a per candidate data structure) we keep track of an amount of shares the candidate is owning +in a pool. At any point in time, the exact amount of Atoms a candidate has in the pool +can be computed as the number of shares it owns multiplied with the share-to-atom exchange rate: -`candidate-coins = candidate.Shares * share-to-atom-exchange-rate` +`candidate-coins = candidate.Shares * share-to-atom-ex-rate` -The benefit of such accounting of the pool resources is the fact that a -modification to the pool from bonding/unbonding/slashing/provisioning of -Atoms affects only global data (size of the pool and the number of shares) and -not the related validator/candidate data structure, i.e., the data structure of -other validators do not need to be modified. This has the advantage that -modifying global data is much cheaper computationally than modifying data of -every validator. Let's explain this further with several small examples: +The benefit of such accounting of the pool resources is the fact that a modification to the pool because of +bonding/unbonding/slashing/provisioning of atoms affects only global data (size of the pool and the number of shares) +and the related validator/candidate data structure, i.e., the data structure of other validators do not need to be +modified. Let's explain this further with several small examples: -We consider initially 4 validators p1, p2, p3 and p4, and that each validator -has bonded 10 Atoms to the bonded pool. Furthermore, let's assume that we have -issued initially 40 shares (note that the initial distribution of the shares, -i.e., share-to-atom exchange rate can be set to any meaningful value), i.e., -share-to-atom-ex-rate = 1 atom per share. Then at the global pool level we -have, the size of the pool is 40 Atoms, and the amount of issued shares is -equal to 40. And for each validator we store in their corresponding data -structure that each has 10 shares of the bonded pool. Now lets assume that the -validator p4 starts process of unbonding of 5 shares. Then the total size of -the pool is decreased and now it will be 35 shares and the amount of Atoms is -35 . Note that the only change in other data structures needed is reducing the -number of shares for a validator p4 from 10 to 5. +We consider initially 4 validators p1, p2, p3 and p4, and that each validator has bonded 10 Atoms +to a bonded pool. Furthermore, let's assume that we have issued initially 40 shares (note that the initial distribution +of the shares, i.e., share-to-atom exchange rate can be set to any meaningful value), i.e., +share-to-atom-ex-rate = 1 atom per share. Then at the global pool level we have, the size of the pool is 40 Atoms, and +the amount of issued shares is equal to 40. And for each validator we store in their corresponding data structure +that each has 10 shares of the bonded pool. Now lets assume that the validator p4 starts process of unbonding of 5 +shares. Then the total size of the pool is decreased and now it will be 35 shares and the amount of Atoms is 35. +Note that the only change in other data structures needed is reducing the number of shares for a validator p4 from 10 +to 5. -Let's consider now the case where a validator p1 wants to bond 15 more atoms to -the pool. Now the size of the pool is 50, and as the exchange rate hasn't -changed (1 share is still worth 1 Atom), we need to create more shares, i.e. we -now have 50 shares in the pool in total. Validators p2, p3 and p4 still have -(correspondingly) 10, 10 and 5 shares each worth of 1 atom per share, so we -don't need to modify anything in their corresponding data structures. But p1 -now has 25 shares, so we update the amount of shares owned by p1 in its -data structure. Note that apart from the size of the pool that is in Atoms, all -other data structures refer only to shares. +Let's consider now the case where a validator p1 wants to bond 15 more atoms to the pool. Now the size of the pool +is 50, and as the exchange rate hasn't changed (1 share is still worth 1 Atom), we need to create more shares, +i.e. we now have 50 shares in the pool in total. +Validators p2, p3 and p4 still have (correspondingly) 10, 10 and 5 shares each worth of 1 atom per share, so we +don't need to modify anything in their corresponding data structures. But p1 now has 25 shares, so we update the amount +of shares owned by the p1 in its data structure. Note that apart from the size of the pool that is in Atoms, all other +data structures refer only to shares. -Finally, let's consider what happens when new Atoms are created and added to -the pool due to inflation. Let's assume that the inflation rate is 10 percent -and that it is applied to the current state of the pool. This means that 5 -Atoms are created and added to the pool and that each validator now -proportionally increase it's Atom count. Let's analyse how this change is -reflected in the data structures. First, the size of the pool is increased and -is now 55 atoms. As a share of each validator in the pool hasn't changed, this -means that the total number of shares stay the same (50) and that the amount of -shares of each validator stays the same (correspondingly 25, 10, 10, 5). But -the exchange rate has changed and each share is now worth 55/50 Atoms per -share, so each validator has effectively increased amount of Atoms it has. So -validators now have (correspondingly) 55/2, 55/5, 55/5 and 55/10 Atoms. +Finally, let's consider what happens when new Atoms are created and added to the pool due to inflation. Let's assume that +the inflation rate is 10 percent and that it is applied to the current state of the pool. This means that 5 Atoms are +created and added to the pool and that each validator now proportionally increase it's Atom count. Let's analyse how this +change is reflected in the data structures. First, the size of the pool is increased and is now 55 atoms. As a share of +each validator in the pool hasn't changed, this means that the total number of shares stay the same (50) and that the +amount of shares of each validator stays the same (correspondingly 25, 10, 10, 5). But the exchange rate has changed and +each share is now worth 55/50 Atoms per share, so each validator has effectively increased amount of Atoms it has. +So validators now have (correspondingly) 55/2, 55/5, 55/5 and 55/10 Atoms. -The concepts of the pool and its shares is at the core of the accounting in the -Staking module. It is used for managing the global pools (such as bonding and -unbonding pool), but also for distribution of Atoms between validator and its -delegators (we will explain this in section X). +The concepts of the pool and its shares is at the core of the accounting in the Staking module. It is used for managing +the global pools (such as bonding and unbonding pool), but also for distribution of Atoms between validator and its +delegators (we will explain this in section X). #### Delegator shares -A candidate is, depending on it's status, contributing Atoms to either the -bonded or unbonding pool, and in return gets some amount of (global) pool -shares. Note that not all those Atoms (and respective shares) are owned by the -candidate as some Atoms could be delegated to a candidate. The mechanism for -distribution of Atoms (and shares) between a candidate and it's delegators is -based on a notion of delegator shares. More precisely, every candidate is -issuing (local) delegator shares (`Candidate.IssuedDelegatorShares`) that -represents some portion of global shares managed by the candidate -(`Candidate.GlobalStakeShares`). The principle behind managing delegator shares -is the same as described in [Section](#The pool and the share). We now -illustrate it with an example. +A candidate is, depending on it's status, contributing Atoms to either the bonded or unbonded pool, and in return gets +some amount of (global) pool shares. Note that not all those Atoms (and respective shares) are owned by the candidate +as some Atoms could be delegated to a candidate. The mechanism for distribution of Atoms (and shares) between a +candidate and it's delegators is based on a notion of delegator shares. More precisely, every candidate is issuing +(local) delegator shares (`Candidate.IssuedDelegatorShares`) that represents some portion of global shares +managed by the candidate (`Candidate.GlobalStakeShares`). The principle behind managing delegator shares is the same +as described in [Section](#The pool and the share). We now illustrate it with an example. -Let's consider 4 validators p1, p2, p3 and p4, and assume that each validator -has bonded 10 Atoms to the bonded pool. Furthermore, let's assume that we have -issued initially 40 global shares, i.e., that -`share-to-atom-exchange-rate = 1 atom per share`. So we will set -`GlobalState.BondedPool = 40` and `GlobalState.BondedShares = 40` and in the -Candidate data structure of each validator `Candidate.GlobalStakeShares = 10`. -Furthermore, each validator issued 10 delegator shares which are initially -owned by itself, i.e., `Candidate.IssuedDelegatorShares = 10`, where +Lets consider 4 validators p1, p2, p3 and p4, and assume that each validator has bonded 10 Atoms to a bonded pool. +Furthermore, lets assume that we have issued initially 40 global shares, i.e., that `share-to-atom-ex-rate = 1 atom per +share`. So we will `GlobalState.BondedPool = 40` and `GlobalState.BondedShares = 40` and in the Candidate data structure +of each validator `Candidate.GlobalStakeShares = 10`. Furthermore, each validator issued 10 delegator +shares which are initially owned by itself, i.e., `Candidate.IssuedDelegatorShares = 10`, where `delegator-share-to-global-share-ex-rate = 1 global share per delegator share`. -Now lets assume that a delegator d1 delegates 5 atoms to a validator p1 and -consider what are the updates we need to make to the data structures. First, -`GlobalState.BondedPool = 45` and `GlobalState.BondedShares = 45`. Then, for -validator p1 we have `Candidate.GlobalStakeShares = 15`, but we also need to -issue also additional delegator shares, i.e., -`Candidate.IssuedDelegatorShares = 15` as the delegator d1 now owns 5 delegator -shares of validator p1, where each delegator share is worth 1 global shares, -i.e, 1 Atom. Lets see now what happens after 5 new Atoms are created due to -inflation. In that case, we only need to update `GlobalState.BondedPool` which -is now equal to 50 Atoms as created Atoms are added to the bonded pool. Note -that the amount of global and delegator shares stay the same but they are now -worth more as share-to-atom-exchange-rate is now worth 50/45 Atoms per share. -Therefore, a delegator d1 now owns: +Now lets assume that a delegator d1 delegates 5 atoms to a validator p1 and consider what are the updates we need +to make to the data structures. First, `GlobalState.BondedPool = 45` and `GlobalState.BondedShares = 45`. Then, +for validator p1 we have `Candidate.GlobalStakeShares = 15`, but we also need to issue also additional delegator shares, +i.e., `Candidate.IssuedDelegatorShares = 15` as the delegator d1 now owns 5 delegator shares of validator p1, where +each delegator share is worth 1 global shares, i.e, 1 Atom. Lets see now what happens after 5 new Atoms are created due +to inflation. In that case, we only need to update `GlobalState.BondedPool` which is now equal to 50 Atoms as created +Atoms are added to the bonded pool. Note that the amount of global and delegator shares stay the same but they are now +worth more as share-to-atom-ex-rate is now worth 50/45 Atoms per share. Therefore, a delegator d1 now owns + +`delegatorCoins = 10 (delegator shares) * 1 (delegator-share-to-global-share-ex-rate) * 50/45 (share-to-atom-ex-rate) = 100/9 Atoms` + -`delegatorCoins = 5 (delegator shares) * 1 (delegator-share-to-global-share-ex-rate) * 50/45 (share-to-atom-ex-rate) = 5.55 Atoms` - -### Inflation provisions - -Validator provisions are minted on an hourly basis (the first block of a new -hour). The annual target of between 7% and 20%. The long-term target ratio of -bonded tokens to unbonded tokens is 67%. - -The target annual inflation rate is recalculated for each provisions cycle. The -inflation is also subject to a rate change (positive or negative) depending on -the distance from the desired ratio (67%). The maximum rate change possible is -defined to be 13% per year, however the annual inflation is capped as between -7% and 20%. - -```go -inflationRateChange(0) = 0 -GlobalState.Inflation(0) = 0.07 - -bondedRatio = GlobalState.BondedPool / GlobalState.TotalSupply -AnnualInflationRateChange = (1 - bondedRatio / 0.67) * 0.13 - -annualInflation += AnnualInflationRateChange - -if annualInflation > 0.20 then GlobalState.Inflation = 0.20 -if annualInflation < 0.07 then GlobalState.Inflation = 0.07 - -provisionTokensHourly = GlobalState.TotalSupply * GlobalState.Inflation / (365.25*24) -``` - -Because the validators hold a relative bonded share (`GlobalStakeShares`), when -more bonded tokens are added proportionally to all validators, the only term -which needs to be updated is the `GlobalState.BondedPool`. So for each -provisions cycle: - -```go -GlobalState.BondedPool += provisionTokensHourly -``` diff --git a/docs/spec/staking/old/spec.md b/docs/spec/staking/old/spec.md index bbec3a94fd..bd87ec0285 100644 --- a/docs/spec/staking/old/spec.md +++ b/docs/spec/staking/old/spec.md @@ -43,14 +43,14 @@ type Params struct { HoldBonded Address // account where all bonded coins are held HoldUnbonded Address // account where all delegated but unbonded coins are held - InflationRateChange rational.Rat // maximum annual change in inflation rate - InflationMax rational.Rat // maximum inflation rate - InflationMin rational.Rat // minimum inflation rate - GoalBonded rational.Rat // Goal of percent bonded atoms - ReserveTax rational.Rat // Tax collected on all fees + InflationRateChange rational.Rational // maximum annual change in inflation rate + InflationMax rational.Rational // maximum inflation rate + InflationMin rational.Rational // minimum inflation rate + GoalBonded rational.Rational // Goal of percent bonded atoms + ReserveTax rational.Rational // Tax collected on all fees - MaxValidators uint16 // maximum number of validators - BondDenom string // bondable coin denomination + MaxVals uint16 // maximum number of validators + AllowedBondDenom string // bondable coin denomination // gas costs for txs GasDeclareCandidacy int64 diff --git a/docs/spec/staking/spec-technical.md b/docs/spec/staking/spec-technical.md index 5f340877e7..e3a528d948 100644 --- a/docs/spec/staking/spec-technical.md +++ b/docs/spec/staking/spec-technical.md @@ -2,66 +2,54 @@ ## Overview -The Cosmos Hub is a Tendermint-based Proof of Stake blockchain system that -serves as a backbone of the Cosmos ecosystem. It is operated and secured by an -open and globally decentralized set of validators. Tendermint consensus is a -Byzantine fault-tolerant distributed protocol that involves all validators in -the process of exchanging protocol messages in the production of each block. To -avoid Nothing-at-Stake problem, a validator in Tendermint needs to lock up -coins in a bond deposit. Tendermint protocol messages are signed by the -validator's private key, and this is a basis for Tendermint strict -accountability that allows punishing misbehaving validators by slashing -(burning) their bonded Atoms. On the other hand, validators are rewarded for -their service of securing blockchain network by the inflationary provisions and -transactions fees. This incentives correct behavior of the validators and -provides the economic security of the network. - -The native token of the Cosmos Hub is called Atom; becoming a validator of the -Cosmos Hub requires holding Atoms. However, not all Atom holders are validators -of the Cosmos Hub. More precisely, there is a selection process that determines -the validator set as a subset of all validator candidates (Atom holders that -wants to become a validator). The other option for Atom holder is to delegate -their atoms to validators, i.e., being a delegator. A delegator is an Atom -holder that has bonded its Atoms by delegating it to a validator (or validator -candidate). By bonding Atoms to secure the network (and taking a risk of being -slashed in case of misbehaviour), a user is rewarded with inflationary -provisions and transaction fees proportional to the amount of its bonded Atoms. -The Cosmos Hub is designed to efficiently facilitate a small numbers of -validators (hundreds), and large numbers of delegators (tens of thousands). -More precisely, it is the role of the Staking module of the Cosmos Hub to -support various staking functionality including validator set selection, -delegating, bonding and withdrawing Atoms, and the distribution of inflationary -provisions and transaction fees. +The Cosmos Hub is a Tendermint-based Proof of Stake blockchain system that serves as a backbone of the Cosmos ecosystem. +It is operated and secured by an open and globally decentralized set of validators. Tendermint consensus is a +Byzantine fault-tolerant distributed protocol that involves all validators in the process of exchanging protocol +messages in the production of each block. To avoid Nothing-at-Stake problem, a validator in Tendermint needs to lock up +coins in a bond deposit. Tendermint protocol messages are signed by the validator's private key, and this is a basis for +Tendermint strict accountability that allows punishing misbehaving validators by slashing (burning) their bonded Atoms. +On the other hand, validators are for it's service of securing blockchain network rewarded by the inflationary +provisions and transactions fees. This incentivizes correct behavior of the validators and provide economic security +of the network. +The native token of the Cosmos Hub is called Atom; becoming a validator of the Cosmos Hub requires holding Atoms. +However, not all Atom holders are validators of the Cosmos Hub. More precisely, there is a selection process that +determines the validator set as a subset of all validator candidates (Atom holder that wants to +become a validator). The other option for Atom holder is to delegate their atoms to validators, i.e., +being a delegator. A delegator is an Atom holder that has bonded its Atoms by delegating it to a validator +(or validator candidate). By bonding Atoms to securing network (and taking a risk of being slashed in case the +validator misbehaves), a user is rewarded with inflationary provisions and transaction fees proportional to the amount +of its bonded Atoms. The Cosmos Hub is designed to efficiently facilitate a small numbers of validators (hundreds), and +large numbers of delegators (tens of thousands). More precisely, it is the role of the Staking module of the Cosmos Hub +to support various staking functionality including validator set selection; delegating, bonding and withdrawing Atoms; +and the distribution of inflationary provisions and transaction fees. + ## State The staking module persists the following information to the store: -* `GlobalState`, describing the global pools and the inflation related fields -* validator candidates (including current validators), indexed by public key and shares in the global pool -(bonded or unbonded depending on candidate status) -* delegator bonds (for each delegation to a candidate by a delegator), indexed by the delegator address and the candidate - public key -* the queue of unbonding delegations -* the queue of re-delegations +- `GlobalState`, describing the global pools and the inflation related fields +- `map[PubKey]Candidate`, a map of validator candidates (including current validators), indexed by public key +- `map[rational.Rat]Candidate`, an ordered map of validator candidates (including current validators), indexed by +shares in the global pool (bonded or unbonded depending on candidate status) +- `map[[]byte]DelegatorBond`, a map of DelegatorBonds (for each delegation to a candidate by a delegator), indexed by +the delegator address and the candidate public key +- `queue[QueueElemUnbondDelegation]`, a queue of unbonding delegations +- `queue[QueueElemReDelegate]`, a queue of re-delegations ### Global State -The GlobalState data structure contains total Atom supply, amount of Atoms in -the bonded pool, sum of all shares distributed for the bonded pool, amount of -Atoms in the unbonded pool, sum of all shares distributed for the unbonded -pool, a timestamp of the last processing of inflation, the current annual -inflation rate, a timestamp for the last comission accounting reset, the global -fee pool, a pool of reserve taxes collected for the governance use and an -adjustment factor for calculating global fee accum. `Params` is global data -structure that stores system parameters and defines overall functioning of the -module. - -``` go +GlobalState data structure contains total Atoms supply, amount of Atoms in the bonded pool, sum of all shares +distributed for the bonded pool, amount of Atoms in the unbonded pool, sum of all shares distributed for the +unbonded pool, a timestamp of the last processing of inflation, the current annual inflation rate, a timestamp +for the last comission accounting reset, the global fee pool, a pool of reserve taxes collected for the governance use +and an adjustment factor for calculating global feel accum (?). + +``` golang type GlobalState struct { TotalSupply int64 // total supply of Atoms BondedPool int64 // reserve of bonded tokens BondedShares rational.Rat // sum of all shares distributed for the BondedPool - UnbondedPool int64 // reserve of unbonding tokens held with candidates + UnbondedPool int64 // reserve of unbonded tokens held with candidates UnbondedShares rational.Rat // sum of all shares distributed for the UnbondedPool InflationLastTime int64 // timestamp of last processing of inflation Inflation rational.Rat // current annual inflation rate @@ -70,40 +58,19 @@ type GlobalState struct { ReservePool coin.Coins // pool of reserve taxes collected on all fees for governance use Adjustment rational.Rat // Adjustment factor for calculating global fee accum } - -type Params struct { - HoldBonded Address // account where all bonded coins are held - HoldUnbonding Address // account where all delegated but unbonding coins are held - - InflationRateChange rational.Rational // maximum annual change in inflation rate - InflationMax rational.Rational // maximum inflation rate - InflationMin rational.Rational // minimum inflation rate - GoalBonded rational.Rational // Goal of percent bonded atoms - ReserveTax rational.Rational // Tax collected on all fees - - MaxVals uint16 // maximum number of validators - AllowedBondDenom string // bondable coin denomination - - // gas costs for txs - GasDeclareCandidacy int64 - GasEditCandidacy int64 - GasDelegate int64 - GasRedelegate int64 - GasUnbond int64 -} ``` ### Candidate -The `Candidate` data structure holds the current state and some historical -actions of validators or candidate-validators. +The `Candidate` data structure holds the current state and some historical actions of +validators or candidate-validators. -``` go +``` golang type Candidate struct { Status CandidateStatus - ConsensusPubKey crypto.PubKey + PubKey crypto.PubKey GovernancePubKey crypto.PubKey - Owner crypto.Address + Owner Address GlobalStakeShares rational.Rat IssuedDelegatorShares rational.Rat RedelegatingShares rational.Rat @@ -116,115 +83,118 @@ type Candidate struct { Adjustment rational.Rat Description Description } +``` +CandidateStatus can be VyingUnbonded, VyingUnbonding, Bonded, KickUnbonding and KickUnbonded. + + +``` golang type Description struct { - Name string - DateBonded string - Identity string - Website string - Details string + Name string + DateBonded string + Identity string + Website string + Details string } ``` Candidate parameters are described: -* Status: it can be Bonded (active validator), Unbonding (validator candidate) - or Revoked -* ConsensusPubKey: candidate public key that is used strictly for participating in - consensus -* GovernancePubKey: public key used by the validator for governance voting -* Owner: Address that is allowed to unbond coins. -* GlobalStakeShares: Represents shares of `GlobalState.BondedPool` if - `Candidate.Status` is `Bonded`; or shares of `GlobalState.Unbondingt Pool` - otherwise -* IssuedDelegatorShares: Sum of all shares a candidate issued to delegators - (which includes the candidate's self-bond); a delegator share represents - their stake in the Candidate's `GlobalStakeShares` -* RedelegatingShares: The portion of `IssuedDelegatorShares` which are - currently re-delegating to a new validator -* VotingPower: Proportional to the amount of bonded tokens which the validator - has if `Candidate.Status` is `Bonded`; otherwise it is equal to `0` -* Commission: The commission rate of fees charged to any delegators -* CommissionMax: The maximum commission rate this candidate can charge each - day from the date `GlobalState.DateLastCommissionReset` -* CommissionChangeRate: The maximum daily increase of the candidate commission -* CommissionChangeToday: Counter for the amount of change to commission rate - which has occurred today, reset on the first block of each day (UTC time) -* ProposerRewardPool: reward pool for extra fees collected when this candidate - is the proposer of a block -* Adjustment factor used to passively calculate each validators entitled fees - from `GlobalState.FeePool` -* Description - * Name: moniker - * DateBonded: date determined which the validator was bonded - * Identity: optional field to provide a signature which verifies the - validators identity (ex. UPort or Keybase) - * Website: optional website link - * Details: optional details + - Status: signal that the candidate is either vying for validator status, + either unbonded or unbonding, an active validator, or a kicked validator + either unbonding or unbonded. + - PubKey: separated key from the owner of the candidate as is used strictly + for participating in consensus. + - Owner: Address where coins are bonded from and unbonded to + - GlobalStakeShares: Represents shares of `GlobalState.BondedPool` if + `Candidate.Status` is `Bonded`; or shares of `GlobalState.UnbondedPool` otherwise + - IssuedDelegatorShares: Sum of all shares a candidate issued to delegators (which + includes the candidate's self-bond); a delegator share represents their stake in + the Candidate's `GlobalStakeShares` + - RedelegatingShares: The portion of `IssuedDelegatorShares` which are + currently re-delegating to a new validator + - VotingPower: Proportional to the amount of bonded tokens which the validator + has if the candidate is a validator. + - Commission: The commission rate of fees charged to any delegators + - CommissionMax: The maximum commission rate this candidate can charge + each day from the date `GlobalState.DateLastCommissionReset` + - CommissionChangeRate: The maximum daily increase of the candidate commission + - CommissionChangeToday: Counter for the amount of change to commission rate + which has occurred today, reset on the first block of each day (UTC time) + - ProposerRewardPool: reward pool for extra fees collected when this candidate + is the proposer of a block + - Adjustment factor used to passively calculate each validators entitled fees + from `GlobalState.FeePool` + - Description + - Name: moniker + - DateBonded: date determined which the validator was bonded + - Identity: optional field to provide a signature which verifies the + validators identity (ex. UPort or Keybase) + - Website: optional website link + - Details: optional details ### DelegatorBond -Atom holders may delegate coins to candidates; under this circumstance their -funds are held in a `DelegatorBond` data structure. It is owned by one -delegator, and is associated with the shares for one candidate. The sender of -the transaction is the owner of the bond. +Atom holders may delegate coins to validators; under this circumstance their +funds are held in a `DelegatorBond` data structure. It is owned by one delegator, and is +associated with the shares for one validator. The sender of the transaction is +considered the owner of the bond. -``` go +``` golang type DelegatorBond struct { - Candidate crypto.PubKey - Shares rational.Rat + Candidate crypto.PubKey + Shares rational.Rat AdjustmentFeePool coin.Coins AdjustmentRewardPool coin.Coins } ``` Description: -* Candidate: the public key of the validator candidate: bonding too -* Shares: the number of delegator shares received from the validator candidate -* AdjustmentFeePool: Adjustment factor used to passively calculate each bonds - entitled fees from `GlobalState.FeePool` -* AdjustmentRewardPool: Adjustment factor used to passively calculate each - bonds entitled fees from `Candidate.ProposerRewardPool` - + - Candidate: the public key of the validator candidate: bonding too + - Shares: the number of delegator shares received from the validator candidate + - AdjustmentFeePool: Adjustment factor used to passively calculate each bonds + entitled fees from `GlobalState.FeePool` + - AdjustmentRewardPool: Adjustment factor used to passively calculate each + bonds entitled fees from `Candidate.ProposerRewardPool`` ### QueueElem -The Unbonding and re-delegation process is implemented using the ordered queue -data structure. All queue elements share a common structure: +Unbonding and re-delegation process is implemented using the ordered queue data structure. +All queue elements used share a common structure: -```golang +``` golang type QueueElem struct { - Candidate crypto.PubKey - InitTime int64 // when the element was added to the queue + Candidate crypto.PubKey + InitHeight int64 // when the queue was initiated } ``` -The queue is ordered so the next element to unbond/re-delegate is at the head. -Every tick the head of the queue is checked and if the unbonding period has -passed since `InitTime`, the final settlement of the unbonding is started or -re-delegation is executed, and the element is popped from the queue. Each -`QueueElem` is persisted in the store until it is popped from the queue. +The queue is ordered so the next to unbond/re-delegate is at the head. Every +tick the head of the queue is checked and if the unbonding period has passed +since `InitHeight`, the final settlement of the unbonding is started or re-delegation is executed, and the element is +pop from the queue. Each `QueueElem` is persisted in the store until it is popped from the queue. ### QueueElemUnbondDelegation -QueueElemUnbondDelegation structure is used in the unbonding queue. - -```golang +``` golang type QueueElemUnbondDelegation struct { - QueueElem - Payout Address // account to pay out to - Tokens coin.Coins // the value in Atoms of the amount of delegator shares which are unbonding - StartSlashRatio rational.Rat // candidate slash ratio + QueueElem + Payout Address // account to pay out to + Shares rational.Rat // amount of delegator shares which are unbonding + StartSlashRatio rational.Rat // candidate slash ratio at start of re-delegation } ``` +In the unbonding queue - the fraction of all historical slashings on +that validator are recorded (`StartSlashRatio`). When this queue reaches maturity +if that total slashing applied is greater on the validator then the +difference (amount that should have been slashed from the first validator) is +assigned to the amount being paid out. ### QueueElemReDelegate -QueueElemReDelegate structure is used in the re-delegation queue. - -```golang +``` golang type QueueElemReDelegate struct { - QueueElem - Payout Address // account to pay out to + QueueElem + Payout Address // account to pay out to Shares rational.Rat // amount of shares which are unbonding NewCandidate crypto.PubKey // validator to bond to after unbond } @@ -233,38 +203,31 @@ type QueueElemReDelegate struct { ### Transaction Overview Available Transactions: -* TxDeclareCandidacy -* TxEditCandidacy -* TxDelegate -* TxUnbond -* TxRedelegate -* TxLivelinessCheck -* TxProveLive + - TxDeclareCandidacy + - TxEditCandidacy + - TxLivelinessCheck + - TxProveLive + - TxDelegate + - TxUnbond + - TxRedelegate ## Transaction processing -In this section we describe the processing of the transactions and the -corresponding updates to the global state. In the following text we will use -`gs` to refer to the `GlobalState` data structure, `unbondDelegationQueue` is a -reference to the queue of unbond delegations, `reDelegationQueue` is the -reference for the queue of redelegations. We use `tx` to denote a -reference to a transaction that is being processed, and `sender` to denote the -address of the sender of the transaction. We use function -`loadCandidate(store, PubKey)` to obtain a Candidate structure from the store, -and `saveCandidate(store, candidate)` to save it. Similarly, we use -`loadDelegatorBond(store, sender, PubKey)` to load a delegator bond with the -key (sender and PubKey) from the store, and -`saveDelegatorBond(store, sender, bond)` to save it. -`removeDelegatorBond(store, sender, bond)` is used to remove the bond from the -store. +In this section we describe the processing of the transactions and the corresponding updates to the global state. +For the following text we will use gs to refer to the GlobalState data structure, candidateMap is a reference to the +map[PubKey]Candidate, delegatorBonds is a reference to map[[]byte]DelegatorBond, unbondDelegationQueue is a +reference to the queue[QueueElemUnbondDelegation] and redelegationQueue is the reference for the +queue[QueueElemReDelegate]. We use tx to denote reference to a transaction that is being processed. ### TxDeclareCandidacy -A validator candidacy is declared using the `TxDeclareCandidacy` transaction. +A validator candidacy can be declared using the `TxDeclareCandidacy` transaction. +During this transaction a self-delegation transaction is executed to bond +tokens which are sent in with the transaction (TODO: What does this mean?). -```golang +``` golang type TxDeclareCandidacy struct { - ConsensusPubKey crypto.PubKey + PubKey crypto.PubKey Amount coin.Coin GovernancePubKey crypto.PubKey Commission rational.Rat @@ -272,25 +235,28 @@ type TxDeclareCandidacy struct { CommissionMaxChange int64 Description Description } +``` +``` declareCandidacy(tx TxDeclareCandidacy): - candidate = getCandidate(store, msg.Address) - if candidate != nil return // candidate with that public key already exists + // create and save the empty candidate + candidate = loadCandidate(store, tx.PubKey) + if candidate != nil then return candidate = NewCandidate(tx.PubKey) candidate.Status = Unbonded candidate.Owner = sender - init candidate VotingPower, GlobalStakeShares, IssuedDelegatorShares, RedelegatingShares and Adjustment to rational.Zero + init candidate VotingPower, GlobalStakeShares, IssuedDelegatorShares,RedelegatingShares and Adjustment to rational.Zero init commision related fields based on the values from tx candidate.ProposerRewardPool = Coin(0) candidate.Description = tx.Description - setCandidate(store, candidate) + saveCandidate(store, candidate) - txDelegate = TxDelegate(tx.PubKey, tx.Amount) - return delegateWithCandidate(txDelegate, candidate) - -// see delegateWithCandidate function in [TxDelegate](TxDelegate) + // move coins from the sender account to a (self-bond) delegator account + // the candidate account and global shares are updated within here + txDelegate = TxDelegate{tx.BondUpdate} + return delegateWithCandidate(txDelegate, candidate) ``` ### TxEditCandidacy @@ -299,326 +265,221 @@ If either the `Description` (excluding `DateBonded` which is constant), `Commission`, or the `GovernancePubKey` need to be updated, the `TxEditCandidacy` transaction should be sent from the owner account: -```golang +``` golang type TxEditCandidacy struct { GovernancePubKey crypto.PubKey Commission int64 Description Description } +``` +``` editCandidacy(tx TxEditCandidacy): candidate = loadCandidate(store, tx.PubKey) - if candidate == nil or candidate.Status == Revoked return - - if tx.GovernancePubKey != nil candidate.GovernancePubKey = tx.GovernancePubKey - if tx.Commission >= 0 candidate.Commission = tx.Commission - if tx.Description != nil candidate.Description = tx.Description - + if candidate == nil or candidate.Status == Unbonded return + if tx.GovernancePubKey != nil then candidate.GovernancePubKey = tx.GovernancePubKey + if tx.Commission >= 0 then candidate.Commission = tx.Commission + if tx.Description != nil then candidate.Description = tx.Description saveCandidate(store, candidate) return -``` + ``` ### TxDelegate -Delegator bonds are created using the `TxDelegate` transaction. Within this -transaction the delegator provides an amount of coins, and in return receives -some amount of candidate's delegator shares that are assigned to -`DelegatorBond.Shares`. +All bonding, whether self-bonding or delegation, is done via `TxDelegate`. -```golang -type TxDelegate struct { - PubKey crypto.PubKey - Amount coin.Coin +Delegator bonds are created using the `TxDelegate` transaction. Within this transaction the delegator provides +an amount of coins, and in return receives some amount of candidate's delegator shares that are assigned to +`DelegatorBond.Shares`. The amount of created delegator shares depends on the candidate's +delegator-shares-to-atoms exchange rate and is computed as +`delegator-shares = delegator-coins / delegator-shares-to-atom-ex-rate`. + +``` golang +type TxDelegate struct { + PubKey crypto.PubKey + Amount coin.Coin } +``` +``` delegate(tx TxDelegate): candidate = loadCandidate(store, tx.PubKey) - if candidate == nil return + if candidate == nil then return return delegateWithCandidate(tx, candidate) delegateWithCandidate(tx TxDelegate, candidate Candidate): - if candidate.Status == Revoked return + if candidate.Status == Revoked then return - if candidate.Status == Bonded - poolAccount = params.HoldBonded - else - poolAccount = params.HoldUnbonded - - err = transfer(sender, poolAccount, tx.Amount) - if err != nil return - - bond = loadDelegatorBond(store, sender, tx.PubKey) - if bond == nil then bond = DelegatorBond(tx.PubKey, rational.Zero, Coin(0), Coin(0)) + if candidate.Status == Bonded then + poolAccount = address of the bonded pool + else + poolAccount = address of the unbonded pool - issuedDelegatorShares = addTokens(tx.Amount, candidate) - bond.Shares += issuedDelegatorShares - - saveCandidate(store, candidate) - saveDelegatorBond(store, sender, bond) - saveGlobalState(store, gs) - return + // Move coins from the delegator account to the bonded pool account + err = transfer(sender, poolAccount, tx.Amount) + if err != nil then return -addTokens(amount coin.Coin, candidate Candidate): - if candidate.Status == Bonded - gs.BondedPool += amount - issuedShares = amount / exchangeRate(gs.BondedShares, gs.BondedPool) - gs.BondedShares += issuedShares - else - gs.UnbondedPool += amount - issuedShares = amount / exchangeRate(gs.UnbondedShares, gs.UnbondedPool) - gs.UnbondedShares += issuedShares + // Get or create the delegator bond + bond = loadDelegatorBond(store, sender, tx.PubKey) + if bond == nil then + bond = DelegatorBond{tx.PubKey,rational.Zero, Coin(0), Coin(0)} - candidate.GlobalStakeShares += issuedShares - - if candidate.IssuedDelegatorShares.IsZero() + issuedDelegatorShares = candidate.addTokens(tx.Amount, gs) + bond.Shares = bond.Shares.Add(issuedDelegatorShares) + + saveCandidate(store, candidate) + + store.Set(GetDelegatorBondKey(sender, bond.PubKey), bond) + + saveGlobalState(store, gs) + return + +addTokens(amount int64, gs GlobalState, candidate Candidate): + + // get the exchange rate of global pool shares over delegator shares + if candidate.IssuedDelegatorShares.IsZero() then exRate = rational.One else - exRate = candidate.GlobalStakeShares / candidate.IssuedDelegatorShares + exRate = candiate.GlobalStakeShares.Quo(candidate.IssuedDelegatorShares) + + if candidate.Status == Bonded then + gs.BondedPool += amount + issuedShares = exchangeRate(gs.BondedShares, gs.BondedPool).Inv().Mul(amount) // (tokens/shares)^-1 * tokens + gs.BondedShares = gs.BondedShares.Add(issuedShares) + else + gs.UnbondedPool += amount + issuedShares = exchangeRate(gs.UnbondedShares, gs.UnbondedPool).Inv().Mul(amount) // (tokens/shares)^-1 * tokens + gs.UnbondedShares = gs.UnbondedShares.Add(issuedShares) - issuedDelegatorShares = issuedShares / exRate - candidate.IssuedDelegatorShares += issuedDelegatorShares - return issuedDelegatorShares + candidate.GlobalStakeShares = candidate.GlobalStakeShares.Add(issuedShares) + + issuedDelegatorShares = exRate.Mul(receivedGlobalShares) + candidate.IssuedDelegatorShares = candidate.IssuedDelegatorShares.Add(issuedDelegatorShares) + return exchangeRate(shares rational.Rat, tokenAmount int64): if shares.IsZero() then return rational.One - return tokenAmount / shares + return shares.Inv().Mul(tokenAmount) ``` ### TxUnbond - Delegator unbonding is defined with the following transaction: -```golang -type TxUnbond struct { - PubKey crypto.PubKey - Shares rational.Rat +``` golang +type TxUnbond struct { + PubKey crypto.PubKey + Shares rational.Rat } +``` -unbond(tx TxUnbond): - bond = loadDelegatorBond(store, sender, tx.PubKey) - if bond == nil return - if bond.Shares < tx.Shares return - - bond.Shares -= tx.Shares +``` +unbond(tx TxUnbond): - candidate = loadCandidate(store, tx.PubKey) + // get delegator bond + bond = loadDelegatorBond(store, sender, tx.PubKey) + if bond == nil then return + + // subtract bond tokens from delegator bond + if bond.Shares.LT(tx.Shares) return // bond shares < tx shares - revokeCandidacy = false - if bond.Shares.IsZero() - if sender == candidate.Owner and candidate.Status != Revoked then revokeCandidacy = true then removeDelegatorBond(store, sender, bond) - else + bond.Shares = bond.Shares.Sub(ts.Shares) + + candidate = loadCandidate(store, tx.PubKey) + if candidate == nil return + + revokeCandidacy = false + if bond.Shares.IsZero() { + // if the bond is the owner of the candidate then trigger a revoke candidacy + if sender.Equals(candidate.Owner) and candidate.Status != Revoked then + revokeCandidacy = true + + // remove the bond + removeDelegatorBond(store, sender, tx.PubKey) + else saveDelegatorBond(store, sender, bond) - if candidate.Status == Bonded - poolAccount = params.HoldBonded + // transfer coins back to account + if candidate.Status == Bonded then + poolAccount = address of the bonded pool else - poolAccount = params.HoldUnbonded + poolAccount = address of the unbonded pool - returnedCoins = removeShares(candidate, shares) - - unbondDelegationElem = QueueElemUnbondDelegation(tx.PubKey, currentHeight(), sender, returnedCoins, startSlashRatio) - unbondDelegationQueue.add(unbondDelegationElem) - - transfer(poolAccount, unbondingPoolAddress, returnCoins) - - if revokeCandidacy - if candidate.Status == Bonded then bondedToUnbondedPool(candidate) - candidate.Status = Revoked + returnCoins = candidate.removeShares(shares, gs) + // TODO: Shouldn't it be created a queue element in this case? + transfer(poolAccount, sender, returnCoins) - if candidate.IssuedDelegatorShares.IsZero() - removeCandidate(store, tx.PubKey) - else - saveCandidate(store, candidate) - - saveGlobalState(store, gs) - return - -removeShares(candidate Candidate, shares rational.Rat): - globalPoolSharesToRemove = delegatorShareExRate(candidate) * shares - - if candidate.Status == Bonded - gs.BondedShares -= globalPoolSharesToRemove - removedTokens = exchangeRate(gs.BondedShares, gs.BondedPool) * globalPoolSharesToRemove - gs.BondedPool -= removedTokens - else - gs.UnbondedShares -= globalPoolSharesToRemove - removedTokens = exchangeRate(gs.UnbondedShares, gs.UnbondedPool) * globalPoolSharesToRemove - gs.UnbondedPool -= removedTokens - - candidate.GlobalStakeShares -= removedTokens - candidate.IssuedDelegatorShares -= shares - return returnedCoins - -delegatorShareExRate(candidate Candidate): - if candidate.IssuedDelegatorShares.IsZero() then return rational.One - return candidate.GlobalStakeShares / candidate.IssuedDelegatorShares - -bondedToUnbondedPool(candidate Candidate): - removedTokens = exchangeRate(gs.BondedShares, gs.BondedPool) * candidate.GlobalStakeShares - gs.BondedShares -= candidate.GlobalStakeShares - gs.BondedPool -= removedTokens - - gs.UnbondedPool += removedTokens - issuedShares = removedTokens / exchangeRate(gs.UnbondedShares, gs.UnbondedPool) - gs.UnbondedShares += issuedShares - - candidate.GlobalStakeShares = issuedShares - candidate.Status = Unbonded - - return transfer(address of the bonded pool, address of the unbonded pool, removedTokens) -``` - -### TxRedelegate - -The re-delegation command allows delegators to switch validators while still -receiving equal reward to as if they had never unbonded. - -```golang -type TxRedelegate struct { - PubKeyFrom crypto.PubKey - PubKeyTo crypto.PubKey - Shares rational.Rat -} - -redelegate(tx TxRedelegate): - bond = loadDelegatorBond(store, sender, tx.PubKey) - if bond == nil then return - - if bond.Shares < tx.Shares return - candidate = loadCandidate(store, tx.PubKeyFrom) - if candidate == nil return - - candidate.RedelegatingShares += tx.Shares - reDelegationElem = QueueElemReDelegate(tx.PubKeyFrom, currentHeight(), sender, tx.Shares, tx.PubKeyTo) - redelegationQueue.add(reDelegationElem) - return -``` - -### TxLivelinessCheck - -Liveliness issues are calculated by keeping track of the block precommits in -the block header. A queue is persisted which contains the block headers from -all recent blocks for the duration of the unbonding period. A validator is -defined as having livliness issues if they have not been included in more than -33% of the blocks over: -* The most recent 24 Hours if they have >= 20% of global stake -* The most recent week if they have = 0% of global stake -* Linear interpolation of the above two scenarios - -Liveliness kicks are only checked when a `TxLivelinessCheck` transaction is -submitted. - -```golang -type TxLivelinessCheck struct { - PubKey crypto.PubKey - RewardAccount Addresss -} -``` - -If the `TxLivelinessCheck` is successful in kicking a validator, 5% of the -liveliness punishment is provided as a reward to `RewardAccount`. - -### TxProveLive - -If the validator was kicked for liveliness issues and is able to regain -liveliness then all delegators in the temporary unbonding pool which have not -transacted to move will be bonded back to the now-live validator and begin to -once again collect provisions and rewards. Regaining liveliness is demonstrated -by sending in a `TxProveLive` transaction: - -```golang -type TxProveLive struct { - PubKey crypto.PubKey -} -``` - -### End of block handling - -```golang -tick(ctx Context): - hrsPerYr = 8766 // as defined by a julian year of 365.25 days - - time = ctx.Time() - if time > gs.InflationLastTime + ProvisionTimeout - gs.InflationLastTime = time - gs.Inflation = nextInflation(hrsPerYr).Round(1000000000) + if revokeCandidacy then + // change the share types to unbonded if they were not already + if candidate.Status == Bonded then + // replace bonded shares with unbonded shares + tokens = gs.removeSharesBonded(candidate.GlobalStakeShares) + candidate.GlobalStakeShares = gs.addTokensUnbonded(tokens) + candidate.Status = Unbonded - provisions = gs.Inflation * (gs.TotalSupply / hrsPerYr) - - gs.BondedPool += provisions - gs.TotalSupply += provisions - - saveGlobalState(store, gs) - - if time > unbondDelegationQueue.head().InitTime + UnbondingPeriod - for each element elem in the unbondDelegationQueue where time > elem.InitTime + UnbondingPeriod do - transfer(unbondingQueueAddress, elem.Payout, elem.Tokens) - unbondDelegationQueue.remove(elem) - - if time > reDelegationQueue.head().InitTime + UnbondingPeriod - for each element elem in the unbondDelegationQueue where time > elem.InitTime + UnbondingPeriod do - candidate = getCandidate(store, elem.PubKey) - returnedCoins = removeShares(candidate, elem.Shares) - candidate.RedelegatingShares -= elem.Shares - delegateWithCandidate(TxDelegate(elem.NewCandidate, returnedCoins), candidate) - reDelegationQueue.remove(elem) - - return UpdateValidatorSet() + transfer(address of the bonded pool, address of the unbonded pool, tokens) + // lastly update the status + candidate.Status = Revoked -nextInflation(hrsPerYr rational.Rat): - if gs.TotalSupply > 0 - bondedRatio = gs.BondedPool / gs.TotalSupply - else - bondedRation = 0 - - inflationRateChangePerYear = (1 - bondedRatio / params.GoalBonded) * params.InflationRateChange - inflationRateChange = inflationRateChangePerYear / hrsPerYr + // deduct shares from the candidate and save + if candidate.GlobalStakeShares.IsZero() then + removeCandidate(store, tx.PubKey) + else + saveCandidate(store, candidate) - inflation = gs.Inflation + inflationRateChange - if inflation > params.InflationMax then inflation = params.InflationMax + saveGlobalState(store, gs) + return - if inflation < params.InflationMin then inflation = params.InflationMin - - return inflation +removeDelegatorBond(candidate Candidate): -UpdateValidatorSet(): - candidates = loadCandidates(store) + // first remove from the list of bonds + pks = loadDelegatorCandidates(store, sender) + for i, pk := range pks { + if candidate.Equals(pk) { + pks = append(pks[:i], pks[i+1:]...) + } + } + b := wire.BinaryBytes(pks) + store.Set(GetDelegatorBondsKey(delegator), b) - v1 = candidates.Validators() - v2 = updateVotingPower(candidates).Validators() - - change = v1.validatorsUpdated(v2) // determine all updated validators between two validator sets - return change - -updateVotingPower(candidates Candidates): - foreach candidate in candidates do - candidate.VotingPower = (candidate.IssuedDelegatorShares - candidate.RedelegatingShares) * delegatorShareExRate(candidate) - - candidates.Sort() - - foreach candidate in candidates do - if candidate is not in the first params.MaxVals - candidate.VotingPower = rational.Zero - if candidate.Status == Bonded then bondedToUnbondedPool(candidate Candidate) - - else if candidate.Status == UnBonded then unbondedToBondedPool(candidate) - - saveCandidate(store, c) - - return candidates - -unbondedToBondedPool(candidate Candidate): - removedTokens = exchangeRate(gs.UnbondedShares, gs.UnbondedPool) * candidate.GlobalStakeShares - gs.UnbondedShares -= candidate.GlobalStakeShares - gs.UnbondedPool -= removedTokens - - gs.BondedPool += removedTokens - issuedShares = removedTokens / exchangeRate(gs.BondedShares, gs.BondedPool) - gs.BondedShares += issuedShares - - candidate.GlobalStakeShares = issuedShares - candidate.Status = Bonded - - return transfer(address of the unbonded pool, address of the bonded pool, removedTokens) + // now remove the actual bond + store.Remove(GetDelegatorBondKey(delegator, candidate)) + //updateDelegatorBonds(store, delegator) +} +``` + +### Inflation provisions + +Validator provisions are minted on an hourly basis (the first block of a new +hour). The annual target of between 7% and 20%. The long-term target ratio of +bonded tokens to unbonded tokens is 67%. + +The target annual inflation rate is recalculated for each previsions cycle. The +inflation is also subject to a rate change (positive of negative) depending or +the distance from the desired ratio (67%). The maximum rate change possible is +defined to be 13% per year, however the annual inflation is capped as between +7% and 20%. + +``` +inflationRateChange(0) = 0 +GlobalState.Inflation(0) = 0.07 + +bondedRatio = GlobalState.BondedPool / GlobalState.TotalSupply +AnnualInflationRateChange = (1 - bondedRatio / 0.67) * 0.13 + +annualInflation += AnnualInflationRateChange + +if annualInflation > 0.20 then GlobalState.Inflation = 0.20 +if annualInflation < 0.07 then GlobalState.Inflation = 0.07 + +provisionTokensHourly = GlobalState.TotalSupply * GlobalState.Inflation / (365.25*24) +``` + +Because the validators hold a relative bonded share (`GlobalStakeShares`), when +more bonded tokens are added proportionally to all validators, the only term +which needs to be updated is the `GlobalState.BondedPool`. So for each previsions +cycle: + +``` +GlobalState.BondedPool += provisionTokensHourly ``` diff --git a/x/stake/keeper.go b/x/stake/keeper.go index d4b6c1b176..eaed55dbcb 100644 --- a/x/stake/keeper.go +++ b/x/stake/keeper.go @@ -6,63 +6,6 @@ import ( "github.com/cosmos/cosmos-sdk/x/bank" ) -//nolint -var ( - // Keys for store prefixes - CandidatesAddrKey = []byte{0x01} // key for all candidates' addresses - ParamKey = []byte{0x02} // key for global parameters relating to staking - GlobalStateKey = []byte{0x03} // key for global parameters relating to staking - - // Key prefixes - CandidateKeyPrefix = []byte{0x04} // prefix for each key to a candidate - ValidatorKeyPrefix = []byte{0x05} // prefix for each key to a candidate - ValidatorUpdatesKeyPrefix = []byte{0x06} // prefix for each key to a candidate - DelegatorBondKeyPrefix = []byte{0x07} // prefix for each key to a delegator's bond - DelegatorBondsKeyPrefix = []byte{0x08} // prefix for each key to a delegator's bond -) - -// XXX remove beggining word get from all these keys -// GetCandidateKey - get the key for the candidate with address -func GetCandidateKey(addr sdk.Address) []byte { - return append(CandidateKeyPrefix, addr.Bytes()...) -} - -// GetValidatorKey - get the key for the validator used in the power-store -func GetValidatorKey(addr sdk.Address, power sdk.Rat, cdc *wire.Codec) []byte { - b, _ := cdc.MarshalBinary(power) // TODO need to handle error here? - return append(ValidatorKeyPrefix, append(b, addr.Bytes()...)...) // TODO does this need prefix if its in its own store -} - -// GetValidatorUpdatesKey - get the key for the validator used in the power-store -func GetValidatorUpdatesKey(addr sdk.Address) []byte { - return append(ValidatorUpdatesKeyPrefix, addr.Bytes()...) // TODO does this need prefix if its in its own store -} - -// GetDelegatorBondKey - get the key for delegator bond with candidate -func GetDelegatorBondKey(delegatorAddr, candidateAddr sdk.Address, cdc *wire.Codec) []byte { - return append(GetDelegatorBondKeyPrefix(delegatorAddr, cdc), candidateAddr.Bytes()...) -} - -// GetDelegatorBondKeyPrefix - get the prefix for a delegator for all candidates -func GetDelegatorBondKeyPrefix(delegatorAddr sdk.Address, cdc *wire.Codec) []byte { - res, err := cdc.MarshalBinary(&delegatorAddr) - if err != nil { - panic(err) - } - return append(DelegatorBondKeyPrefix, res...) -} - -// GetDelegatorBondsKey - get the key for list of all the delegator's bonds -func GetDelegatorBondsKey(delegatorAddr sdk.Address, cdc *wire.Codec) []byte { - res, err := cdc.MarshalBinary(&delegatorAddr) - if err != nil { - panic(err) - } - return append(DelegatorBondsKeyPrefix, res...) -} - -//___________________________________________________________________________ - // keeper of the staking store type Keeper struct { storeKey sdk.StoreKey @@ -70,7 +13,7 @@ type Keeper struct { coinKeeper bank.CoinKeeper //just caches - gs GlobalState + gs Pool params Params } @@ -83,7 +26,8 @@ func NewKeeper(ctx sdk.Context, cdc *wire.Codec, key sdk.StoreKey, ck bank.CoinK return keeper } -//XXX load/save -> get/set +//_________________________________________________________________________ + func (k Keeper) getCandidate(ctx sdk.Context, addr sdk.Address) (candidate Candidate, found bool) { store := ctx.KVStore(k.storeKey) b := store.Get(GetCandidateKey(addr)) @@ -100,7 +44,6 @@ func (k Keeper) getCandidate(ctx sdk.Context, addr sdk.Address) (candidate Candi func (k Keeper) setCandidate(ctx sdk.Context, candidate Candidate) { store := ctx.KVStore(k.storeKey) - // XXX should only remove validator if we know candidate is a validator k.removeValidator(ctx, candidate.Address) validator := Validator{candidate.Address, candidate.VotingPower} k.updateValidator(ctx, validator) @@ -114,26 +57,34 @@ func (k Keeper) setCandidate(ctx sdk.Context, candidate Candidate) { func (k Keeper) removeCandidate(ctx sdk.Context, candidateAddr sdk.Address) { store := ctx.KVStore(k.storeKey) - - // XXX should only remove validator if we know candidate is a validator k.removeValidator(ctx, candidateAddr) store.Delete(GetCandidateKey(candidateAddr)) } -//___________________________________________________________________________ +func (k Keeper) getCandidates(ctx sdk.Context, maxRetrieve int16) (candidates Candidates) { + store := ctx.KVStore(k.storeKey) + iterator := store.Iterator(subspace(CandidateKeyPrefix)) -//func loadValidator(store sdk.KVStore, address sdk.Address, votingPower sdk.Rat) *Validator { -//b := store.Get(GetValidatorKey(address, votingPower)) -//if b == nil { -//return nil -//} -//validator := new(Validator) -//err := cdc.UnmarshalBinary(b, validator) -//if err != nil { -//panic(err) // This error should never occur big problem if does -//} -//return validator -//} + candidates = make([]Candidate, maxRetrieve) + i := 0 + for ; ; i++ { + if !iterator.Valid() || i > int(maxRetrieve-1) { + iterator.Close() + break + } + bz := iterator.Value() + var candidate Candidate + err := k.cdc.UnmarshalBinary(bz, &candidate) + if err != nil { + panic(err) + } + candidates[i] = candidate + iterator.Next() + } + return candidates[:i] // trim +} + +//___________________________________________________________________________ // updateValidator - update a validator and create accumulate any changes // in the changed validator substore @@ -155,6 +106,8 @@ func (k Keeper) updateValidator(ctx sdk.Context, validator Validator) { func (k Keeper) removeValidator(ctx sdk.Context, address sdk.Address) { store := ctx.KVStore(k.storeKey) + // XXX ensure that this record is a validator even? + //add validator with zero power to the validator updates b, err := k.cdc.MarshalBinary(Validator{address, sdk.ZeroRat}) if err != nil { @@ -193,7 +146,6 @@ func (k Keeper) getValidators(ctx sdk.Context, maxVal uint16) (validators []Vali validators[i] = val iterator.Next() } - return } @@ -229,45 +181,6 @@ func (k Keeper) clearValidatorUpdates(ctx sdk.Context, maxVal int) { iterator.Close() } -//--------------------------------------------------------------------- - -// getCandidates - get the active list of all candidates -func (k Keeper) getCandidates(ctx sdk.Context) (candidates Candidates) { - store := ctx.KVStore(k.storeKey) - iterator := store.Iterator(subspace(CandidateKeyPrefix)) - - for ; iterator.Valid(); iterator.Next() { - candidateBytes := iterator.Value() - var candidate Candidate - err := k.cdc.UnmarshalBinary(candidateBytes, &candidate) - if err != nil { - panic(err) - } - candidates = append(candidates, candidate) - } - iterator.Close() - return candidates -} - -//_____________________________________________________________________ - -// XXX use a store iterator here instead -//// load the pubkeys of all candidates a delegator is delegated too -//func (k Keeper) getDelegatorCandidates(ctx sdk.Context, delegator sdk.Address) (candidateAddrs []sdk.Address) { -//store := ctx.KVStore(k.storeKey) - -//candidateBytes := store.Get(GetDelegatorBondsKey(delegator, k.cdc)) -//if candidateBytes == nil { -//return nil -//} - -//err := k.cdc.UnmarshalBinary(candidateBytes, &candidateAddrs) -//if err != nil { -//panic(err) -//} -//return -//} - //_____________________________________________________________________ func (k Keeper) getDelegatorBond(ctx sdk.Context, @@ -288,20 +201,6 @@ func (k Keeper) getDelegatorBond(ctx sdk.Context, func (k Keeper) setDelegatorBond(ctx sdk.Context, bond DelegatorBond) { store := ctx.KVStore(k.storeKey) - - // XXX use store iterator - // if a new bond add to the list of bonds - //if k.getDelegatorBond(delegator, bond.Address) == nil { - //pks := k.getDelegatorCandidates(delegator) - //pks = append(pks, bond.Address) - //b, err := k.cdc.MarshalBinary(pks) - //if err != nil { - //panic(err) - //} - //store.Set(GetDelegatorBondsKey(delegator, k.cdc), b) - //} - - // now actually save the bond b, err := k.cdc.MarshalBinary(bond) if err != nil { panic(err) @@ -311,25 +210,32 @@ func (k Keeper) setDelegatorBond(ctx sdk.Context, bond DelegatorBond) { func (k Keeper) removeDelegatorBond(ctx sdk.Context, bond DelegatorBond) { store := ctx.KVStore(k.storeKey) - - // XXX use store iterator - // TODO use list queries on multistore to remove iterations here! - // first remove from the list of bonds - //addrs := k.getDelegatorCandidates(delegator) - //for i, addr := range addrs { - //if bytes.Equal(candidateAddr, addr) { - //addrs = append(addrs[:i], addrs[i+1:]...) - //} - //} - //b, err := k.cdc.MarshalBinary(addrs) - //if err != nil { - //panic(err) - //} - //store.Set(GetDelegatorBondsKey(delegator, k.cdc), b) - - // now remove the actual bond store.Delete(GetDelegatorBondKey(bond.DelegatorAddr, bond.CandidateAddr, k.cdc)) - //updateDelegatorBonds(store, delegator) //XXX remove? +} + +// load all bonds of a delegator +func (k Keeper) getDelegatorBonds(ctx sdk.Context, delegator sdk.Address, maxRetrieve int16) (bonds []DelegatorBond) { + store := ctx.KVStore(k.storeKey) + delegatorPrefixKey := GetDelegatorBondsKey(delegator, k.cdc) + iterator := store.Iterator(subspace(delegatorPrefixKey)) //smallest to largest + + bonds = make([]DelegatorBond, maxRetrieve) + i := 0 + for ; ; i++ { + if !iterator.Valid() || i > int(maxRetrieve-1) { + iterator.Close() + break + } + bondBytes := iterator.Value() + var bond DelegatorBond + err := k.cdc.UnmarshalBinary(bondBytes, &bond) + if err != nil { + panic(err) + } + bonds[i] = bond + iterator.Next() + } + return bonds[:i] // trim } //_______________________________________________________________________ @@ -349,7 +255,7 @@ func (k Keeper) getParams(ctx sdk.Context) (params Params) { err := k.cdc.UnmarshalBinary(b, ¶ms) if err != nil { - panic(err) // This error should never occur big problem if does + panic(err) } return } @@ -362,34 +268,3 @@ func (k Keeper) setParams(ctx sdk.Context, params Params) { store.Set(ParamKey, b) k.params = Params{} // clear the cache } - -//_______________________________________________________________________ - -// XXX nothing is this Keeper should return a pointer...!!!!!! -// load/save the global staking state -func (k Keeper) getGlobalState(ctx sdk.Context) (gs GlobalState) { - // check if cached before anything - if k.gs != (GlobalState{}) { - return k.gs - } - store := ctx.KVStore(k.storeKey) - b := store.Get(GlobalStateKey) - if b == nil { - return initialGlobalState() - } - err := k.cdc.UnmarshalBinary(b, &gs) - if err != nil { - panic(err) // This error should never occur big problem if does - } - return -} - -func (k Keeper) setGlobalState(ctx sdk.Context, gs GlobalState) { - store := ctx.KVStore(k.storeKey) - b, err := k.cdc.MarshalBinary(gs) - if err != nil { - panic(err) - } - store.Set(GlobalStateKey, b) - k.gs = GlobalState{} // clear the cache -} diff --git a/x/stake/keeper_keys.go b/x/stake/keeper_keys.go new file mode 100644 index 0000000000..c31d65f2c5 --- /dev/null +++ b/x/stake/keeper_keys.go @@ -0,0 +1,51 @@ +package stake + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/wire" +) + +//nolint +var ( + // Keys for store prefixes + CandidatesAddrKey = []byte{0x01} // key for all candidates' addresses + ParamKey = []byte{0x02} // key for global parameters relating to staking + PoolKey = []byte{0x03} // key for global parameters relating to staking + + // Key prefixes + CandidateKeyPrefix = []byte{0x04} // prefix for each key to a candidate + ValidatorKeyPrefix = []byte{0x05} // prefix for each key to a candidate + ValidatorUpdatesKeyPrefix = []byte{0x06} // prefix for each key to a candidate + DelegatorBondKeyPrefix = []byte{0x07} // prefix for each key to a delegator's bond +) + +// XXX remove beggining word get from all these keys +// GetCandidateKey - get the key for the candidate with address +func GetCandidateKey(addr sdk.Address) []byte { + return append(CandidateKeyPrefix, addr.Bytes()...) +} + +// GetValidatorKey - get the key for the validator used in the power-store +func GetValidatorKey(addr sdk.Address, power sdk.Rat, cdc *wire.Codec) []byte { + b, _ := cdc.MarshalBinary(power) // TODO need to handle error here? + return append(ValidatorKeyPrefix, append(b, addr.Bytes()...)...) // TODO does this need prefix if its in its own store +} + +// GetValidatorUpdatesKey - get the key for the validator used in the power-store +func GetValidatorUpdatesKey(addr sdk.Address) []byte { + return append(ValidatorUpdatesKeyPrefix, addr.Bytes()...) // TODO does this need prefix if its in its own store +} + +// GetDelegatorBondKey - get the key for delegator bond with candidate +func GetDelegatorBondKey(delegatorAddr, candidateAddr sdk.Address, cdc *wire.Codec) []byte { + return append(GetDelegatorBondsKey(delegatorAddr, cdc), candidateAddr.Bytes()...) +} + +// GetDelegatorBondKeyPrefix - get the prefix for a delegator for all candidates +func GetDelegatorBondsKey(delegatorAddr sdk.Address, cdc *wire.Codec) []byte { + res, err := cdc.MarshalBinary(&delegatorAddr) + if err != nil { + panic(err) + } + return append(DelegatorBondKeyPrefix, res...) +} diff --git a/x/stake/keeper_test.go b/x/stake/keeper_test.go index c7e00c534a..4b0a52d050 100644 --- a/x/stake/keeper_test.go +++ b/x/stake/keeper_test.go @@ -19,7 +19,7 @@ import ( //assert := assert.New(t) //store := initTestStore(t) //params := getParams(store) -//gs := getGlobalState(store) +//gs := getPool(store) //N := 5 //actors := newAddrs(N) @@ -27,19 +27,19 @@ import ( //// test a basic change in voting power //candidates[0].Assets = sdk.NewRat(500) -//candidates.updateVotingPower(store, gs, params) +//candidates.updateVotingPower(store, p, params) //assert.Equal(int64(500), candidates[0].VotingPower.Evaluate(), "%v", candidates[0]) //// test a swap in voting power //candidates[1].Assets = sdk.NewRat(600) -//candidates.updateVotingPower(store, gs, params) +//candidates.updateVotingPower(store, p, params) //assert.Equal(int64(600), candidates[0].VotingPower.Evaluate(), "%v", candidates[0]) //assert.Equal(int64(500), candidates[1].VotingPower.Evaluate(), "%v", candidates[1]) //// test the max validators term //params.MaxValidators = 4 //setParams(store, params) -//candidates.updateVotingPower(store, gs, params) +//candidates.updateVotingPower(store, p, params) //assert.Equal(int64(0), candidates[4].VotingPower.Evaluate(), "%v", candidates[4]) //} @@ -118,7 +118,7 @@ import ( //testRemove(t, vs1[1], changed[0]) //testRemove(t, vs1[2], changed[1]) -//// test many sdk of changes //vs2 = []Validator{v1, v3, v4, v5} //vs2[2].VotingPower = sdk.NewRat(11) //changed = vs1.validatorsUpdated(vs2) //require.Equal(4, len(changed), "%v", changed) // change 1, remove 1, add 2 //testRemove(t, vs1[1], changed[0]) //testChange(t, vs2[1], changed[1]) //testChange(t, vs2[2], changed[2]) //testChange(t, vs2[3], changed[3]) //} //func TestUpdateValidatorSet(t *testing.T) { //assert, require := assert.New(t), require.New(t) //store := initTestStore(t) //params := getParams(store) //gs := getGlobalState(store) //N := 5 +//// test many sdk of changes //vs2 = []Validator{v1, v3, v4, v5} //vs2[2].VotingPower = sdk.NewRat(11) //changed = vs1.validatorsUpdated(vs2) //require.Equal(4, len(changed), "%v", changed) // change 1, remove 1, add 2 //testRemove(t, vs1[1], changed[0]) //testChange(t, vs2[1], changed[1]) //testChange(t, vs2[2], changed[2]) //testChange(t, vs2[3], changed[3]) //} //func TestUpdateValidatorSet(t *testing.T) { //assert, require := assert.New(t), require.New(t) //store := initTestStore(t) //params := getParams(store) //gs := getPool(store) //N := 5 //actors := newAddrs(N) //candidates := candidatesFromActors(actors, []int64{400, 200, 100, 10, 1}) //for _, c := range candidates { @@ -126,14 +126,14 @@ import ( //} //// they should all already be validators -//change, err := UpdateValidatorSet(store, gs, params) +//change, err := UpdateValidatorSet(store, p, params) //require.Nil(err) //require.Equal(0, len(change), "%v", change) // change 1, remove 1, add 2 //// test the max value and test again //params.MaxValidators = 4 //setParams(store, params) -//change, err = UpdateValidatorSet(store, gs, params) +//change, err = UpdateValidatorSet(store, p, params) //require.Nil(err) //require.Equal(1, len(change), "%v", change) //testRemove(t, candidates[4].validator(), change[0]) @@ -149,7 +149,7 @@ import ( //for _, c := range candidates { //setCandidate(store, c) //} -//change, err = UpdateValidatorSet(store, gs, params) +//change, err = UpdateValidatorSet(store, p, params) //require.Nil(err) //require.Equal(5, len(change), "%v", change) // 3 changed, 1 added, 1 removed //candidates = getCandidates(store) @@ -170,25 +170,42 @@ import ( //assert.Equal(t, addrs[1], validators[1].Address) //} -func TestState(t *testing.T) { - ctx, _, keeper := createTestInput(t, nil, false, 0) +var ( + addrDel1 = addrs[0] + addrDel2 = addrs[1] + addrVal1 = addrs[2] + addrVal2 = addrs[3] + addrVal3 = addrs[4] + pk1 = crypto.GenPrivKeyEd25519().PubKey() + pk2 = crypto.GenPrivKeyEd25519().PubKey() + pk3 = crypto.GenPrivKeyEd25519().PubKey() - addrDel := sdk.Address([]byte("addressdelegator")) - addrVal := sdk.Address([]byte("addressvalidator")) - //pk := newPubKey("0B485CFC0EECC619440448436F8FC9DF40566F2369E72400281454CB552AFB57") - pk := crypto.GenPrivKeyEd25519().PubKey() - - //---------------------------------------------------------------------- - // Candidate checks - - // XXX expand to include both liabilities and assets use/test all candidate fields - candidate := Candidate{ - Address: addrVal, - PubKey: pk, + candidate1 = Candidate{ + Address: addrVal1, + PubKey: pk1, Assets: sdk.NewRat(9), Liabilities: sdk.NewRat(9), VotingPower: sdk.ZeroRat, } + candidate2 = Candidate{ + Address: addrVal2, + PubKey: pk2, + Assets: sdk.NewRat(9), + Liabilities: sdk.NewRat(9), + VotingPower: sdk.ZeroRat, + } + candidate3 = Candidate{ + Address: addrVal3, + PubKey: pk3, + Assets: sdk.NewRat(9), + Liabilities: sdk.NewRat(9), + VotingPower: sdk.ZeroRat, + } +) + +// XXX expand to include both liabilities and assets use/test all candidate1 fields +func TestCandidate(t *testing.T) { + ctx, _, keeper := createTestInput(t, nil, false, 0) candidatesEqual := func(c1, c2 Candidate) bool { return c1.Status == c2.Status && @@ -201,35 +218,40 @@ func TestState(t *testing.T) { } // check the empty keeper first - _, found := keeper.getCandidate(ctx, addrVal) + _, found := keeper.getCandidate(ctx, addrVal1) assert.False(t, found) - resAddrs := keeper.getCandidates(ctx) + resAddrs := keeper.getCandidates(ctx, 100) assert.Zero(t, len(resAddrs)) // set and retrieve a record - keeper.setCandidate(ctx, candidate) - resCand, found := keeper.getCandidate(ctx, addrVal) + keeper.setCandidate(ctx, candidate1) + resCand, found := keeper.getCandidate(ctx, addrVal1) assert.True(t, found) - assert.True(t, candidatesEqual(candidate, resCand), "%v \n %v", resCand, candidate) + assert.True(t, candidatesEqual(candidate1, resCand), "%v \n %v", resCand, candidate1) // modify a records, save, and retrieve - candidate.Liabilities = sdk.NewRat(99) - keeper.setCandidate(ctx, candidate) - resCand, found = keeper.getCandidate(ctx, addrVal) + candidate1.Liabilities = sdk.NewRat(99) + keeper.setCandidate(ctx, candidate1) + resCand, found = keeper.getCandidate(ctx, addrVal1) assert.True(t, found) - assert.True(t, candidatesEqual(candidate, resCand)) + assert.True(t, candidatesEqual(candidate1, resCand)) // also test that the address has been added to address list - resAddrs = keeper.getCandidates(ctx) + resAddrs = keeper.getCandidates(ctx, 100) require.Equal(t, 1, len(resAddrs)) - assert.Equal(t, addrVal, resAddrs[0].Address) + assert.Equal(t, addrVal1, resAddrs[0].Address) - //---------------------------------------------------------------------- - // Bond checks +} - bond := DelegatorBond{ - DelegatorAddr: addrDel, - CandidateAddr: addrVal, +func TestBond(t *testing.T) { + ctx, _, keeper := createTestInput(t, nil, false, 0) + + // first add a candidate1 to delegate too + keeper.setCandidate(ctx, candidate1) + + bond1to1 := DelegatorBond{ + DelegatorAddr: addrDel1, + CandidateAddr: addrVal1, Shares: sdk.NewRat(9), } @@ -239,36 +261,65 @@ func TestState(t *testing.T) { b1.Shares == b2.Shares } - //check the empty keeper first - _, found = keeper.getDelegatorBond(ctx, addrDel, addrVal) + // check the empty keeper first + _, found := keeper.getDelegatorBond(ctx, addrDel1, addrVal1) assert.False(t, found) - //Set and retrieve a record - keeper.setDelegatorBond(ctx, bond) - resBond, found := keeper.getDelegatorBond(ctx, addrDel, addrVal) + // set and retrieve a record + keeper.setDelegatorBond(ctx, bond1to1) + resBond, found := keeper.getDelegatorBond(ctx, addrDel1, addrVal1) assert.True(t, found) - assert.True(t, bondsEqual(bond, resBond)) + assert.True(t, bondsEqual(bond1to1, resBond)) - //modify a records, save, and retrieve - bond.Shares = sdk.NewRat(99) - keeper.setDelegatorBond(ctx, bond) - resBond, found = keeper.getDelegatorBond(ctx, addrDel, addrVal) + // modify a records, save, and retrieve + bond1to1.Shares = sdk.NewRat(99) + keeper.setDelegatorBond(ctx, bond1to1) + resBond, found = keeper.getDelegatorBond(ctx, addrDel1, addrVal1) assert.True(t, found) - assert.True(t, bondsEqual(bond, resBond)) + assert.True(t, bondsEqual(bond1to1, resBond)) - //---------------------------------------------------------------------- - // Param checks + // add some more records + keeper.setCandidate(ctx, candidate2) + keeper.setCandidate(ctx, candidate3) + bond1to2 := DelegatorBond{addrDel1, addrVal2, sdk.NewRat(9)} + bond1to3 := DelegatorBond{addrDel1, addrVal3, sdk.NewRat(9)} + bond2to1 := DelegatorBond{addrDel2, addrVal1, sdk.NewRat(9)} + bond2to2 := DelegatorBond{addrDel2, addrVal2, sdk.NewRat(9)} + bond2to3 := DelegatorBond{addrDel2, addrVal3, sdk.NewRat(9)} + keeper.setDelegatorBond(ctx, bond1to2) + keeper.setDelegatorBond(ctx, bond1to3) + keeper.setDelegatorBond(ctx, bond2to1) + keeper.setDelegatorBond(ctx, bond2to2) + keeper.setDelegatorBond(ctx, bond2to3) - keeper.setParams(ctx, defaultParams()) - params := defaultParams() + // test all bond retrieve capabilities + resBonds := keeper.getDelegatorBonds(ctx, addrDel1, 5) + require.Equal(t, 3, len(resBonds)) + assert.True(t, bondsEqual(bond1to1, resBonds[0])) + assert.True(t, bondsEqual(bond1to2, resBonds[1])) + assert.True(t, bondsEqual(bond1to3, resBonds[2])) + resBonds = keeper.getDelegatorBonds(ctx, addrDel1, 3) + require.Equal(t, 3, len(resBonds)) + resBonds = keeper.getDelegatorBonds(ctx, addrDel1, 2) + require.Equal(t, 2, len(resBonds)) + resBonds = keeper.getDelegatorBonds(ctx, addrDel2, 5) + require.Equal(t, 3, len(resBonds)) + assert.True(t, bondsEqual(bond2to1, resBonds[0])) + assert.True(t, bondsEqual(bond2to2, resBonds[1])) + assert.True(t, bondsEqual(bond2to3, resBonds[2])) +} + +func TestParams(t *testing.T) { + ctx, _, keeper := createTestInput(t, nil, false, 0) + expParams := defaultParams() //check that the empty keeper loads the default resParams := keeper.getParams(ctx) - assert.Equal(t, params, resParams) + assert.Equal(t, expParams, resParams) //modify a params, save, and retrieve - params.MaxValidators = 777 - keeper.setParams(ctx, params) + expParams.MaxValidators = 777 + keeper.setParams(ctx, expParams) resParams = keeper.getParams(ctx) - assert.Equal(t, params, resParams) + assert.Equal(t, expParams, resParams) } diff --git a/x/stake/pool.go b/x/stake/pool.go index 21b92ef816..7bf1682ae5 100644 --- a/x/stake/pool.go +++ b/x/stake/pool.go @@ -4,6 +4,36 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" ) +// load/save the global staking state +func (k Keeper) getPool(ctx sdk.Context) (gs Pool) { + // check if cached before anything + if k.gs != (Pool{}) { + return k.gs + } + store := ctx.KVStore(k.storeKey) + b := store.Get(PoolKey) + if b == nil { + return initialPool() + } + err := k.cdc.UnmarshalBinary(b, &gs) + if err != nil { + panic(err) // This error should never occur big problem if does + } + return +} + +func (k Keeper) setPool(ctx sdk.Context, p Pool) { + store := ctx.KVStore(k.storeKey) + b, err := k.cdc.MarshalBinary(p) + if err != nil { + panic(err) + } + store.Set(PoolKey, b) + k.gs = Pool{} // clear the cache +} + +//_______________________________________________________________________ + //TODO make these next two functions more efficient should be reading and writting to state ye know // move a candidates asset pool from bonded to unbonded pool @@ -29,38 +59,38 @@ func (k Keeper) unbondedToBondedPool(ctx sdk.Context, candidate Candidate) { //_______________________________________________________________________ func (k Keeper) addTokensBonded(ctx sdk.Context, amount int64) (issuedShares sdk.Rat) { - gs := k.getGlobalState(ctx) - issuedShares = gs.bondedShareExRate().Inv().Mul(sdk.NewRat(amount)) // (tokens/shares)^-1 * tokens - gs.BondedPool += amount - gs.BondedShares = gs.BondedShares.Add(issuedShares) - k.setGlobalState(ctx, gs) + p := k.getPool(ctx) + issuedShares = p.bondedShareExRate().Inv().Mul(sdk.NewRat(amount)) // (tokens/shares)^-1 * tokens + p.BondedPool += amount + p.BondedShares = p.BondedShares.Add(issuedShares) + k.setPool(ctx, p) return } func (k Keeper) removeSharesBonded(ctx sdk.Context, shares sdk.Rat) (removedTokens int64) { - gs := k.getGlobalState(ctx) - removedTokens = gs.bondedShareExRate().Mul(shares).Evaluate() // (tokens/shares) * shares - gs.BondedShares = gs.BondedShares.Sub(shares) - gs.BondedPool -= removedTokens - k.setGlobalState(ctx, gs) + p := k.getPool(ctx) + removedTokens = p.bondedShareExRate().Mul(shares).Evaluate() // (tokens/shares) * shares + p.BondedShares = p.BondedShares.Sub(shares) + p.BondedPool -= removedTokens + k.setPool(ctx, p) return } func (k Keeper) addTokensUnbonded(ctx sdk.Context, amount int64) (issuedShares sdk.Rat) { - gs := k.getGlobalState(ctx) - issuedShares = gs.unbondedShareExRate().Inv().Mul(sdk.NewRat(amount)) // (tokens/shares)^-1 * tokens - gs.UnbondedShares = gs.UnbondedShares.Add(issuedShares) - gs.UnbondedPool += amount - k.setGlobalState(ctx, gs) + p := k.getPool(ctx) + issuedShares = p.unbondedShareExRate().Inv().Mul(sdk.NewRat(amount)) // (tokens/shares)^-1 * tokens + p.UnbondedShares = p.UnbondedShares.Add(issuedShares) + p.UnbondedPool += amount + k.setPool(ctx, p) return } func (k Keeper) removeSharesUnbonded(ctx sdk.Context, shares sdk.Rat) (removedTokens int64) { - gs := k.getGlobalState(ctx) - removedTokens = gs.unbondedShareExRate().Mul(shares).Evaluate() // (tokens/shares) * shares - gs.UnbondedShares = gs.UnbondedShares.Sub(shares) - gs.UnbondedPool -= removedTokens - k.setGlobalState(ctx, gs) + p := k.getPool(ctx) + removedTokens = p.unbondedShareExRate().Mul(shares).Evaluate() // (tokens/shares) * shares + p.UnbondedShares = p.UnbondedShares.Sub(shares) + p.UnbondedPool -= removedTokens + k.setPool(ctx, p) return } @@ -69,7 +99,7 @@ func (k Keeper) removeSharesUnbonded(ctx sdk.Context, shares sdk.Rat) (removedTo // add tokens to a candidate func (k Keeper) candidateAddTokens(ctx sdk.Context, candidate Candidate, amount int64) (issuedDelegatorShares sdk.Rat) { - gs := k.getGlobalState(ctx) + p := k.getPool(ctx) exRate := candidate.delegatorShareExRate() var receivedGlobalShares sdk.Rat @@ -82,14 +112,14 @@ func (k Keeper) candidateAddTokens(ctx sdk.Context, candidate Candidate, amount issuedDelegatorShares = exRate.Mul(receivedGlobalShares) candidate.Liabilities = candidate.Liabilities.Add(issuedDelegatorShares) - k.setGlobalState(ctx, gs) // TODO cache GlobalState? + k.setPool(ctx, p) // TODO cache Pool? return } // remove shares from a candidate func (k Keeper) candidateRemoveShares(ctx sdk.Context, candidate Candidate, shares sdk.Rat) (createdCoins int64) { - gs := k.getGlobalState(ctx) + p := k.getPool(ctx) //exRate := candidate.delegatorShareExRate() //XXX make sure not used globalPoolSharesToRemove := candidate.delegatorShareExRate().Mul(shares) @@ -100,6 +130,6 @@ func (k Keeper) candidateRemoveShares(ctx sdk.Context, candidate Candidate, shar } candidate.Assets = candidate.Assets.Sub(globalPoolSharesToRemove) candidate.Liabilities = candidate.Liabilities.Sub(shares) - k.setGlobalState(ctx, gs) // TODO cache GlobalState? + k.setPool(ctx, p) // TODO cache Pool? return } diff --git a/x/stake/pool_test.go b/x/stake/pool_test.go new file mode 100644 index 0000000000..f95b833e96 --- /dev/null +++ b/x/stake/pool_test.go @@ -0,0 +1,22 @@ +package stake + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestPool(t *testing.T) { + ctx, _, keeper := createTestInput(t, nil, false, 0) + expPool := initialPool() + + //check that the empty keeper loads the default + resPool := keeper.getPool(ctx) + assert.Equal(t, expPool, resPool) + + //modify a params, save, and retrieve + expPool.TotalSupply = 777 + keeper.setPool(ctx, expPool) + resPool = keeper.getPool(ctx) + assert.Equal(t, expPool, resPool) +} diff --git a/x/stake/test_common.go b/x/stake/test_common.go index d3db53af43..3cdd0fb747 100644 --- a/x/stake/test_common.go +++ b/x/stake/test_common.go @@ -90,8 +90,8 @@ func createTestInput(t *testing.T, sender sdk.Address, isCheckTx bool, initCoins ck := bank.NewCoinKeeper(accountMapper) keeper := NewKeeper(ctx, cdc, keyStake, ck) - params := paramsNoInflation() - keeper.setParams(ctx, params) + //params := paramsNoInflation() + params := keeper.getParams(ctx) // fill all the addresses with some coins for _, addr := range addrs { diff --git a/x/stake/tick.go b/x/stake/tick.go index 9148dcf6ea..de0b52c0c3 100644 --- a/x/stake/tick.go +++ b/x/stake/tick.go @@ -10,14 +10,14 @@ func Tick(ctx sdk.Context, k Keeper) (change []*abci.Validator, err error) { // retrieve params params := k.getParams(ctx) - gs := k.getGlobalState(ctx) + p := k.getPool(ctx) height := ctx.BlockHeight() // Process Validator Provisions // XXX right now just process every 5 blocks, in new SDK make hourly - if gs.InflationLastTime+5 <= height { - gs.InflationLastTime = height - processProvisions(ctx, k, gs, params) + if p.InflationLastTime+5 <= height { + p.InflationLastTime = height + processProvisions(ctx, k, p, params) } newVals := k.getValidators(ctx, params.MaxValidators) @@ -29,28 +29,28 @@ func Tick(ctx sdk.Context, k Keeper) (change []*abci.Validator, err error) { var hrsPerYr = sdk.NewRat(8766) // as defined by a julian year of 365.25 days // process provisions for an hour period -func processProvisions(ctx sdk.Context, k Keeper, gs GlobalState, params Params) { +func processProvisions(ctx sdk.Context, k Keeper, p Pool, params Params) { - gs.Inflation = nextInflation(gs, params).Round(1000000000) + p.Inflation = nextInflation(p, params).Round(1000000000) // Because the validators hold a relative bonded share (`GlobalStakeShare`), when // more bonded tokens are added proportionally to all validators the only term // which needs to be updated is the `BondedPool`. So for each previsions cycle: - provisions := gs.Inflation.Mul(sdk.NewRat(gs.TotalSupply)).Quo(hrsPerYr).Evaluate() - gs.BondedPool += provisions - gs.TotalSupply += provisions + provisions := p.Inflation.Mul(sdk.NewRat(p.TotalSupply)).Quo(hrsPerYr).Evaluate() + p.BondedPool += provisions + p.TotalSupply += provisions // XXX XXX XXX XXX XXX XXX XXX XXX XXX // XXX Mint them to the hold account // XXX XXX XXX XXX XXX XXX XXX XXX XXX // save the params - k.setGlobalState(ctx, gs) + k.setPool(ctx, p) } // get the next inflation rate for the hour -func nextInflation(gs GlobalState, params Params) (inflation sdk.Rat) { +func nextInflation(p Pool, params Params) (inflation sdk.Rat) { // The target annual inflation rate is recalculated for each previsions cycle. The // inflation is also subject to a rate change (positive of negative) depending or @@ -59,11 +59,11 @@ func nextInflation(gs GlobalState, params Params) (inflation sdk.Rat) { // 7% and 20%. // (1 - bondedRatio/GoalBonded) * InflationRateChange - inflationRateChangePerYear := sdk.OneRat.Sub(gs.bondedRatio().Quo(params.GoalBonded)).Mul(params.InflationRateChange) + inflationRateChangePerYear := sdk.OneRat.Sub(p.bondedRatio().Quo(params.GoalBonded)).Mul(params.InflationRateChange) inflationRateChange := inflationRateChangePerYear.Quo(hrsPerYr) // increase the new annual inflation for this next cycle - inflation = gs.Inflation.Add(inflationRateChange) + inflation = p.Inflation.Add(inflationRateChange) if inflation.GT(params.InflationMax) { inflation = params.InflationMax } diff --git a/x/stake/tick_test.go b/x/stake/tick_test.go index 1d5cdaf746..4a1a9f6867 100644 --- a/x/stake/tick_test.go +++ b/x/stake/tick_test.go @@ -11,7 +11,7 @@ package stake //ctx, _, keeper := createTestInput(t, nil, false, 0) //params := defaultParams() //keeper.setParams(ctx, params) -//gs := keeper.getGlobalState(ctx) +//gs := keeper.getPool(ctx) //// Governing Mechanism: //// bondedRatio = BondedPool / TotalSupply @@ -42,7 +42,7 @@ package stake //{67, 100, sdk.NewRat(15, 100), sdk.ZeroRat}, //} //for _, tc := range tests { -//gs.BondedPool, gs.TotalSupply = tc.setBondedPool, tc.setTotalSupply +//gs.BondedPool, p.TotalSupply = tc.setBondedPool, tc.setTotalSupply //gs.Inflation = tc.setInflation //inflation := nextInflation(gs, params) @@ -57,7 +57,7 @@ package stake //ctx, _, keeper := createTestInput(t, nil, false, 0) //params := defaultParams() //keeper.setParams(ctx, params) -//gs := keeper.getGlobalState(ctx) +//gs := keeper.getPool(ctx) //// create some candidates some bonded, some unbonded //candidates := candidatesFromAddrsEmpty(addrs) @@ -75,42 +75,42 @@ package stake //var unbondedShares int64 = 400000000 //// initial bonded ratio ~ 27% -//assert.True(t, gs.bondedRatio().Equal(sdk.NewRat(bondedShares, totalSupply)), "%v", gs.bondedRatio()) +//assert.True(t, p.bondedRatio().Equal(sdk.NewRat(bondedShares, totalSupply)), "%v", p.bondedRatio()) //// Supplies -//assert.Equal(t, totalSupply, gs.TotalSupply) -//assert.Equal(t, bondedShares, gs.BondedPool) -//assert.Equal(t, unbondedShares, gs.UnbondedPool) +//assert.Equal(t, totalSupply, p.TotalSupply) +//assert.Equal(t, bondedShares, p.BondedPool) +//assert.Equal(t, unbondedShares, p.UnbondedPool) //// test the value of candidate shares -//assert.True(t, gs.bondedShareExRate().Equal(sdk.OneRat), "%v", gs.bondedShareExRate()) +//assert.True(t, p.bondedShareExRate().Equal(sdk.OneRat), "%v", p.bondedShareExRate()) -//initialSupply := gs.TotalSupply -//initialUnbonded := gs.TotalSupply - gs.BondedPool +//initialSupply := p.TotalSupply +//initialUnbonded := p.TotalSupply - p.BondedPool //// process the provisions a year //for hr := 0; hr < 8766; hr++ { //expInflation := nextInflation(gs, params).Round(1000000000) //expProvisions := (expInflation.Mul(sdk.NewRat(gs.TotalSupply)).Quo(hrsPerYr)).Evaluate() -//startBondedPool := gs.BondedPool -//startTotalSupply := gs.TotalSupply -//processProvisions(ctx, keeper, gs, params) -//assert.Equal(t, startBondedPool+expProvisions, gs.BondedPool) -//assert.Equal(t, startTotalSupply+expProvisions, gs.TotalSupply) +//startBondedPool := p.BondedPool +//startTotalSupply := p.TotalSupply +//processProvisions(ctx, keeper, p, params) +//assert.Equal(t, startBondedPool+expProvisions, p.BondedPool) +//assert.Equal(t, startTotalSupply+expProvisions, p.TotalSupply) //} -//assert.NotEqual(t, initialSupply, gs.TotalSupply) -//assert.Equal(t, initialUnbonded, gs.UnbondedPool) -////panic(fmt.Sprintf("debug total %v, bonded %v, diff %v\n", gs.TotalSupply, gs.BondedPool, gs.TotalSupply-gs.BondedPool)) +//assert.NotEqual(t, initialSupply, p.TotalSupply) +//assert.Equal(t, initialUnbonded, p.UnbondedPool) +////panic(fmt.Sprintf("debug total %v, bonded %v, diff %v\n", p.TotalSupply, p.BondedPool, p.TotalSupply-gs.BondedPool)) //// initial bonded ratio ~ 35% ~ 30% increase for bonded holders -//assert.True(t, gs.bondedRatio().Equal(sdk.NewRat(105906511, 305906511)), "%v", gs.bondedRatio()) +//assert.True(t, p.bondedRatio().Equal(sdk.NewRat(105906511, 305906511)), "%v", p.bondedRatio()) //// global supply -//assert.Equal(t, int64(611813022), gs.TotalSupply) -//assert.Equal(t, int64(211813022), gs.BondedPool) -//assert.Equal(t, unbondedShares, gs.UnbondedPool) +//assert.Equal(t, int64(611813022), p.TotalSupply) +//assert.Equal(t, int64(211813022), p.BondedPool) +//assert.Equal(t, unbondedShares, p.UnbondedPool) //// test the value of candidate shares -//assert.True(t, gs.bondedShareExRate().Mul(sdk.NewRat(bondedShares)).Equal(sdk.NewRat(211813022)), "%v", gs.bondedShareExRate()) +//assert.True(t, p.bondedShareExRate().Mul(sdk.NewRat(bondedShares)).Equal(sdk.NewRat(211813022)), "%v", p.bondedShareExRate()) //} diff --git a/x/stake/types.go b/x/stake/types.go index 8fd039da75..cdf8ece066 100644 --- a/x/stake/types.go +++ b/x/stake/types.go @@ -29,8 +29,8 @@ func defaultParams() Params { //_________________________________________________________________________ -// GlobalState - dynamic parameters of the current state -type GlobalState struct { +// Pool - dynamic parameters of the current state +type Pool struct { TotalSupply int64 `json:"total_supply"` // total supply of all tokens BondedShares sdk.Rat `json:"bonded_shares"` // sum of all shares distributed for the Bonded Pool UnbondedShares sdk.Rat `json:"unbonded_shares"` // sum of all shares distributed for the Unbonded Pool @@ -42,8 +42,8 @@ type GlobalState struct { // XXX define globalstate interface? -func initialGlobalState() GlobalState { - return GlobalState{ +func initialPool() Pool { + return Pool{ TotalSupply: 0, BondedShares: sdk.ZeroRat, UnbondedShares: sdk.ZeroRat, @@ -55,27 +55,27 @@ func initialGlobalState() GlobalState { } // get the bond ratio of the global state -func (gs GlobalState) bondedRatio() sdk.Rat { - if gs.TotalSupply > 0 { - return sdk.NewRat(gs.BondedPool, gs.TotalSupply) +func (p Pool) bondedRatio() sdk.Rat { + if p.TotalSupply > 0 { + return sdk.NewRat(p.BondedPool, p.TotalSupply) } return sdk.ZeroRat } // get the exchange rate of bonded token per issued share -func (gs GlobalState) bondedShareExRate() sdk.Rat { - if gs.BondedShares.IsZero() { +func (p Pool) bondedShareExRate() sdk.Rat { + if p.BondedShares.IsZero() { return sdk.OneRat } - return sdk.NewRat(gs.BondedPool).Quo(gs.BondedShares) + return sdk.NewRat(p.BondedPool).Quo(p.BondedShares) } // get the exchange rate of unbonded tokens held in candidates per issued share -func (gs GlobalState) unbondedShareExRate() sdk.Rat { - if gs.UnbondedShares.IsZero() { +func (p Pool) unbondedShareExRate() sdk.Rat { + if p.UnbondedShares.IsZero() { return sdk.OneRat } - return sdk.NewRat(gs.UnbondedPool).Quo(gs.UnbondedShares) + return sdk.NewRat(p.UnbondedPool).Quo(p.UnbondedShares) } //_______________________________________________________________________________________________________