parent
c8c85dfbc8
commit
80d88c3a4c
|
@ -0,0 +1,103 @@
|
|||
// nolint
|
||||
package stake
|
||||
|
||||
import (
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
)
|
||||
|
||||
type CodeType = sdk.CodeType
|
||||
|
||||
const (
|
||||
// Gaia errors reserve 200 ~ 299.
|
||||
CodeInvalidValidator CodeType = 201
|
||||
CodeInvalidCandidate CodeType = 202
|
||||
CodeInvalidBond CodeType = 203
|
||||
CodeInvalidInput CodeType = 204
|
||||
CodeUnauthorized CodeType = sdk.CodeUnauthorized
|
||||
CodeInternal CodeType = sdk.CodeInternal
|
||||
CodeUnknownRequest CodeType = sdk.CodeUnknownRequest
|
||||
)
|
||||
|
||||
// NOTE: Don't stringer this, we'll put better messages in later.
|
||||
func codeToDefaultMsg(code CodeType) string {
|
||||
switch code {
|
||||
case CodeInvalidValidator:
|
||||
return "Invalid Validator"
|
||||
case CodeInvalidCandidate:
|
||||
return "Invalid Candidate"
|
||||
case CodeInvalidBond:
|
||||
return "Invalid Bond"
|
||||
case CodeInvalidInput:
|
||||
return "Invalid Input"
|
||||
case CodeUnauthorized:
|
||||
return "Unauthorized"
|
||||
case CodeInternal:
|
||||
return "Internal Error"
|
||||
case CodeUnknownRequest:
|
||||
return "Unknown request"
|
||||
default:
|
||||
return sdk.CodeToDefaultMsg(code)
|
||||
}
|
||||
}
|
||||
|
||||
//----------------------------------------
|
||||
// Error constructors
|
||||
|
||||
func ErrCandidateEmpty() error {
|
||||
return newError(CodeInvalidValidator, "Cannot bond to an empty candidate")
|
||||
}
|
||||
func ErrBadBondingDenom() error {
|
||||
return newError(CodeInvalidValidator, "Invalid coin denomination")
|
||||
}
|
||||
func ErrBadBondingAmount() error {
|
||||
return newError(CodeInvalidValidator, "Amount must be > 0")
|
||||
}
|
||||
func ErrNoBondingAcct() error {
|
||||
return newError(CodeInvalidValidator, "No bond account for this (address, validator) pair")
|
||||
}
|
||||
func ErrCommissionNegative() error {
|
||||
return newError(CodeInvalidValidator, "Commission must be positive")
|
||||
}
|
||||
func ErrCommissionHuge() error {
|
||||
return newError(CodeInvalidValidator, "Commission cannot be more than 100%")
|
||||
}
|
||||
func ErrBadValidatorAddr() error {
|
||||
return newError(CodeInvalidValidator, "Validator does not exist for that address")
|
||||
}
|
||||
func ErrCandidateExistsAddr() error {
|
||||
return newError(CodeInvalidValidator, "Candidate already exist, cannot re-declare candidacy")
|
||||
}
|
||||
func ErrMissingSignature() error {
|
||||
return newError(CodeInvalidValidator, "Missing signature")
|
||||
}
|
||||
func ErrBondNotNominated() error {
|
||||
return newError(CodeInvalidValidator, "Cannot bond to non-nominated account")
|
||||
}
|
||||
func ErrNoCandidateForAddress() error {
|
||||
return newError(CodeInvalidValidator, "Validator does not exist for that address")
|
||||
}
|
||||
func ErrNoDelegatorForAddress() error {
|
||||
return newError(CodeInvalidValidator, "Delegator does not contain validator bond")
|
||||
}
|
||||
func ErrInsufficientFunds() error {
|
||||
return newError(CodeInvalidValidator, "Insufficient bond shares")
|
||||
}
|
||||
func ErrBadRemoveValidator() error {
|
||||
return newError(CodeInvalidValidator, "Error removing validator")
|
||||
}
|
||||
|
||||
//----------------------------------------
|
||||
|
||||
// TODO group with code from x/bank/errors.go
|
||||
|
||||
func msgOrDefaultMsg(msg string, code CodeType) string {
|
||||
if msg != "" {
|
||||
return msg
|
||||
}
|
||||
return codeToDefaultMsg(code)
|
||||
}
|
||||
|
||||
func newError(code CodeType, msg string) sdk.Error {
|
||||
msg = msgOrDefaultMsg(msg, code)
|
||||
return sdk.NewError(code, msg)
|
||||
}
|
|
@ -0,0 +1,520 @@
|
|||
package stake
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
|
||||
"github.com/spf13/viper"
|
||||
"github.com/tendermint/tmlibs/log"
|
||||
"github.com/tendermint/tmlibs/rational"
|
||||
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
"github.com/cosmos/cosmos-sdk/x/auth"
|
||||
coin "github.com/cosmos/cosmos-sdk/x/bank" // XXX fix
|
||||
)
|
||||
|
||||
// nolint
|
||||
const stakingModuleName = "stake"
|
||||
|
||||
// Name is the name of the modules.
|
||||
func Name() string {
|
||||
return stakingModuleName
|
||||
}
|
||||
|
||||
//_______________________________________________________________________
|
||||
|
||||
// DelegatedProofOfStake - interface to enforce delegation stake
|
||||
type delegatedProofOfStake interface {
|
||||
declareCandidacy(TxDeclareCandidacy) error
|
||||
editCandidacy(TxEditCandidacy) error
|
||||
delegate(TxDelegate) error
|
||||
unbond(TxUnbond) error
|
||||
}
|
||||
|
||||
type coinSend interface {
|
||||
transferFn(sender, receiver sdk.Actor, coins coin.Coins) error
|
||||
}
|
||||
|
||||
//_______________________________________________________________________
|
||||
|
||||
// Handler - the transaction processing handler
|
||||
type Handler struct {
|
||||
}
|
||||
|
||||
// NewHandler returns a new Handler with the default Params
|
||||
func NewHandler() Handler {
|
||||
return Handler{}
|
||||
}
|
||||
|
||||
// Name - return stake namespace
|
||||
func (Handler) Name() string {
|
||||
return stakingModuleName
|
||||
}
|
||||
|
||||
// InitState - set genesis parameters for staking
|
||||
func (h Handler) InitState(l log.Logger, store types.KVStore,
|
||||
module, key, value string, cb sdk.InitStater) (log string, err error) {
|
||||
return "", h.initState(module, key, value, store)
|
||||
}
|
||||
|
||||
// separated for testing
|
||||
func (Handler) initState(module, key, value string, store types.KVStore) error {
|
||||
if module != stakingModuleName {
|
||||
return sdk.ErrUnknownModule(module)
|
||||
}
|
||||
|
||||
params := loadParams(store)
|
||||
switch key {
|
||||
case "allowed_bond_denom":
|
||||
params.AllowedBondDenom = value
|
||||
case "max_vals",
|
||||
"gas_bond",
|
||||
"gas_unbond":
|
||||
|
||||
// TODO: enforce non-negative integers in input
|
||||
i, err := strconv.Atoi(value)
|
||||
if err != nil {
|
||||
return fmt.Errorf("input must be integer, Error: %v", err.Error())
|
||||
}
|
||||
|
||||
switch key {
|
||||
case "max_vals":
|
||||
params.MaxVals = uint16(i)
|
||||
case "gas_bond":
|
||||
params.GasDelegate = int64(i)
|
||||
case "gas_unbound":
|
||||
params.GasUnbond = int64(i)
|
||||
}
|
||||
default:
|
||||
return sdk.ErrUnknownKey(key)
|
||||
}
|
||||
|
||||
saveParams(store, params)
|
||||
return nil
|
||||
}
|
||||
|
||||
// CheckTx checks if the tx is properly structured
|
||||
func (h Handler) CheckTx(ctx sdk.Context, store types.KVStore,
|
||||
tx sdk.Tx, _ sdk.Checker) (res sdk.CheckResult, err error) {
|
||||
|
||||
err = tx.ValidateBasic()
|
||||
if err != nil {
|
||||
return res, err
|
||||
}
|
||||
|
||||
// get the sender
|
||||
sender, err := getTxSender(ctx)
|
||||
if err != nil {
|
||||
return res, err
|
||||
}
|
||||
|
||||
params := loadParams(store)
|
||||
|
||||
// create the new checker object to
|
||||
checker := check{
|
||||
store: store,
|
||||
sender: sender,
|
||||
}
|
||||
|
||||
// return the fee for each tx type
|
||||
switch txInner := tx.Unwrap().(type) {
|
||||
case TxDeclareCandidacy:
|
||||
return sdk.NewCheck(params.GasDeclareCandidacy, ""),
|
||||
checker.declareCandidacy(txInner)
|
||||
case TxEditCandidacy:
|
||||
return sdk.NewCheck(params.GasEditCandidacy, ""),
|
||||
checker.editCandidacy(txInner)
|
||||
case TxDelegate:
|
||||
return sdk.NewCheck(params.GasDelegate, ""),
|
||||
checker.delegate(txInner)
|
||||
case TxUnbond:
|
||||
return sdk.NewCheck(params.GasUnbond, ""),
|
||||
checker.unbond(txInner)
|
||||
}
|
||||
|
||||
return res, sdk.ErrUnknownTxType(tx)
|
||||
}
|
||||
|
||||
// DeliverTx executes the tx if valid
|
||||
func (h Handler) DeliverTx(ctx sdk.Context, store types.KVStore,
|
||||
tx sdk.Tx, dispatch sdk.Deliver) (res sdk.DeliverResult, err error) {
|
||||
|
||||
// TODO: remove redundancy
|
||||
// also we don't need to check the res - gas is already deducted in sdk
|
||||
_, err = h.CheckTx(ctx, store, tx, nil)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
sender, err := getTxSender(ctx)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
params := loadParams(store)
|
||||
deliverer := deliver{
|
||||
store: store,
|
||||
sender: sender,
|
||||
params: params,
|
||||
transfer: coinSender{
|
||||
store: store,
|
||||
dispatch: dispatch,
|
||||
ctx: ctx,
|
||||
}.transferFn,
|
||||
}
|
||||
|
||||
// Run the transaction
|
||||
switch _tx := tx.Unwrap().(type) {
|
||||
case TxDeclareCandidacy:
|
||||
res.GasUsed = params.GasDeclareCandidacy
|
||||
return res, deliverer.declareCandidacy(_tx)
|
||||
case TxEditCandidacy:
|
||||
res.GasUsed = params.GasEditCandidacy
|
||||
return res, deliverer.editCandidacy(_tx)
|
||||
case TxDelegate:
|
||||
res.GasUsed = params.GasDelegate
|
||||
return res, deliverer.delegate(_tx)
|
||||
case TxUnbond:
|
||||
//context with hold account permissions
|
||||
params := loadParams(store)
|
||||
res.GasUsed = params.GasUnbond
|
||||
ctx2 := ctx.WithPermissions(params.HoldBonded)
|
||||
deliverer.transfer = coinSender{
|
||||
store: store,
|
||||
dispatch: dispatch,
|
||||
ctx: ctx2,
|
||||
}.transferFn
|
||||
return res, deliverer.unbond(_tx)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// get the sender from the ctx and ensure it matches the tx pubkey
|
||||
func getTxSender(ctx sdk.Context) (sender sdk.Actor, err error) {
|
||||
senders := ctx.GetPermissions("", auth.NameSigs)
|
||||
if len(senders) != 1 {
|
||||
return sender, ErrMissingSignature()
|
||||
}
|
||||
return senders[0], nil
|
||||
}
|
||||
|
||||
//_______________________________________________________________________
|
||||
|
||||
type coinSender struct {
|
||||
store types.KVStore
|
||||
dispatch sdk.Deliver
|
||||
ctx sdk.Context
|
||||
}
|
||||
|
||||
var _ coinSend = coinSender{} // enforce interface at compile time
|
||||
|
||||
func (c coinSender) transferFn(sender, receiver sdk.Actor, coins coin.Coins) error {
|
||||
send := coin.NewSendOneTx(sender, receiver, coins)
|
||||
|
||||
// If the deduction fails (too high), abort the command
|
||||
_, err := c.dispatch.DeliverTx(c.ctx, c.store, send)
|
||||
return err
|
||||
}
|
||||
|
||||
//_____________________________________________________________________
|
||||
|
||||
type check struct {
|
||||
store types.KVStore
|
||||
sender sdk.Actor
|
||||
}
|
||||
|
||||
var _ delegatedProofOfStake = check{} // enforce interface at compile time
|
||||
|
||||
func (c check) declareCandidacy(tx TxDeclareCandidacy) error {
|
||||
|
||||
// check to see if the pubkey or sender has been registered before
|
||||
candidate := loadCandidate(c.store, tx.PubKey)
|
||||
if candidate != nil {
|
||||
return fmt.Errorf("cannot bond to pubkey which is already declared candidacy"+
|
||||
" PubKey %v already registered with %v candidate address",
|
||||
candidate.PubKey, candidate.Owner)
|
||||
}
|
||||
|
||||
return checkDenom(tx.BondUpdate, c.store)
|
||||
}
|
||||
|
||||
func (c check) editCandidacy(tx TxEditCandidacy) error {
|
||||
|
||||
// candidate must already be registered
|
||||
candidate := loadCandidate(c.store, tx.PubKey)
|
||||
if candidate == nil { // does PubKey exist
|
||||
return fmt.Errorf("cannot delegate to non-existant PubKey %v", tx.PubKey)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c check) delegate(tx TxDelegate) error {
|
||||
|
||||
candidate := loadCandidate(c.store, tx.PubKey)
|
||||
if candidate == nil { // does PubKey exist
|
||||
return fmt.Errorf("cannot delegate to non-existant PubKey %v", tx.PubKey)
|
||||
}
|
||||
return checkDenom(tx.BondUpdate, c.store)
|
||||
}
|
||||
|
||||
func (c check) unbond(tx TxUnbond) error {
|
||||
|
||||
// check if bond has any shares in it unbond
|
||||
bond := loadDelegatorBond(c.store, c.sender, tx.PubKey)
|
||||
sharesStr := viper.GetString(tx.Shares)
|
||||
if bond.Shares.LT(rational.Zero) { // bond shares < tx shares
|
||||
return fmt.Errorf("no shares in account to unbond")
|
||||
}
|
||||
|
||||
// if shares set to maximum shares then we're good
|
||||
if sharesStr == "MAX" {
|
||||
return nil
|
||||
}
|
||||
|
||||
// test getting rational number from decimal provided
|
||||
shares, err := rational.NewFromDecimal(sharesStr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// test that there are enough shares to unbond
|
||||
if bond.Shares.LT(shares) {
|
||||
return fmt.Errorf("not enough bond shares to unbond, have %v, trying to unbond %v",
|
||||
bond.Shares, tx.Shares)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func checkDenom(tx BondUpdate, store types.KVStore) error {
|
||||
if tx.Bond.Denom != loadParams(store).AllowedBondDenom {
|
||||
return fmt.Errorf("Invalid coin denomination")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
//_____________________________________________________________________
|
||||
|
||||
type deliver struct {
|
||||
store types.KVStore
|
||||
sender sdk.Actor
|
||||
params Params
|
||||
gs *GlobalState
|
||||
transfer transferFn
|
||||
}
|
||||
|
||||
type transferFn func(sender, receiver sdk.Actor, coins coin.Coins) error
|
||||
|
||||
var _ delegatedProofOfStake = deliver{} // enforce interface at compile time
|
||||
|
||||
//_____________________________________________________________________
|
||||
// deliver helper functions
|
||||
|
||||
// TODO move from deliver with new SDK should only be dependant on store to send coins in NEW SDK
|
||||
|
||||
// move a candidates asset pool from bonded to unbonded pool
|
||||
func (d deliver) bondedToUnbondedPool(candidate *Candidate) error {
|
||||
|
||||
// replace bonded shares with unbonded shares
|
||||
tokens := d.gs.removeSharesBonded(candidate.Assets)
|
||||
candidate.Assets = d.gs.addTokensUnbonded(tokens)
|
||||
candidate.Status = Unbonded
|
||||
|
||||
return d.transfer(d.params.HoldBonded, d.params.HoldUnbonded,
|
||||
coin.Coins{{d.params.AllowedBondDenom, tokens}})
|
||||
}
|
||||
|
||||
// move a candidates asset pool from unbonded to bonded pool
|
||||
func (d deliver) unbondedToBondedPool(candidate *Candidate) error {
|
||||
|
||||
// replace bonded shares with unbonded shares
|
||||
tokens := d.gs.removeSharesUnbonded(candidate.Assets)
|
||||
candidate.Assets = d.gs.addTokensBonded(tokens)
|
||||
candidate.Status = Bonded
|
||||
|
||||
return d.transfer(d.params.HoldUnbonded, d.params.HoldBonded,
|
||||
coin.Coins{{d.params.AllowedBondDenom, tokens}})
|
||||
}
|
||||
|
||||
//_____________________________________________________________________
|
||||
|
||||
// These functions assume everything has been authenticated,
|
||||
// now we just perform action and save
|
||||
func (d deliver) declareCandidacy(tx TxDeclareCandidacy) error {
|
||||
|
||||
// create and save the empty candidate
|
||||
bond := loadCandidate(d.store, tx.PubKey)
|
||||
if bond != nil {
|
||||
return ErrCandidateExistsAddr()
|
||||
}
|
||||
candidate := NewCandidate(tx.PubKey, d.sender, tx.Description)
|
||||
saveCandidate(d.store, candidate)
|
||||
|
||||
// move coins from the d.sender account to a (self-bond) delegator account
|
||||
// the candidate account and global shares are updated within here
|
||||
txDelegate := TxDelegate{tx.BondUpdate}
|
||||
return d.delegateWithCandidate(txDelegate, candidate)
|
||||
}
|
||||
|
||||
func (d deliver) editCandidacy(tx TxEditCandidacy) error {
|
||||
|
||||
// Get the pubKey bond account
|
||||
candidate := loadCandidate(d.store, tx.PubKey)
|
||||
if candidate == nil {
|
||||
return ErrBondNotNominated()
|
||||
}
|
||||
if candidate.Status == Unbonded { //candidate has been withdrawn
|
||||
return ErrBondNotNominated()
|
||||
}
|
||||
|
||||
//check and edit any of the editable terms
|
||||
if tx.Description.Moniker != "" {
|
||||
candidate.Description.Moniker = tx.Description.Moniker
|
||||
}
|
||||
if tx.Description.Identity != "" {
|
||||
candidate.Description.Identity = tx.Description.Identity
|
||||
}
|
||||
if tx.Description.Website != "" {
|
||||
candidate.Description.Website = tx.Description.Website
|
||||
}
|
||||
if tx.Description.Details != "" {
|
||||
candidate.Description.Details = tx.Description.Details
|
||||
}
|
||||
|
||||
saveCandidate(d.store, candidate)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d deliver) delegate(tx TxDelegate) error {
|
||||
// Get the pubKey bond account
|
||||
candidate := loadCandidate(d.store, tx.PubKey)
|
||||
if candidate == nil {
|
||||
return ErrBondNotNominated()
|
||||
}
|
||||
return d.delegateWithCandidate(tx, candidate)
|
||||
}
|
||||
|
||||
func (d deliver) delegateWithCandidate(tx TxDelegate, candidate *Candidate) error {
|
||||
|
||||
if candidate.Status == Revoked { //candidate has been withdrawn
|
||||
return ErrBondNotNominated()
|
||||
}
|
||||
|
||||
var poolAccount sdk.Actor
|
||||
if candidate.Status == Bonded {
|
||||
poolAccount = d.params.HoldBonded
|
||||
} else {
|
||||
poolAccount = d.params.HoldUnbonded
|
||||
}
|
||||
|
||||
// TODO maybe refactor into GlobalState.addBondedTokens(), maybe with new SDK
|
||||
// Move coins from the delegator account to the bonded pool account
|
||||
err := d.transfer(d.sender, poolAccount, coin.Coins{tx.Bond})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Get or create the delegator bond
|
||||
bond := loadDelegatorBond(d.store, d.sender, tx.PubKey)
|
||||
if bond == nil {
|
||||
bond = &DelegatorBond{
|
||||
PubKey: tx.PubKey,
|
||||
Shares: rational.Zero,
|
||||
}
|
||||
}
|
||||
|
||||
// Account new shares, save
|
||||
bond.Shares = bond.Shares.Add(candidate.addTokens(tx.Bond.Amount, d.gs))
|
||||
saveCandidate(d.store, candidate)
|
||||
saveDelegatorBond(d.store, d.sender, bond)
|
||||
saveGlobalState(d.store, d.gs)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d deliver) unbond(tx TxUnbond) error {
|
||||
|
||||
// get delegator bond
|
||||
bond := loadDelegatorBond(d.store, d.sender, tx.PubKey)
|
||||
if bond == nil {
|
||||
return ErrNoDelegatorForAddress()
|
||||
}
|
||||
|
||||
// retrieve the amount of bonds to remove (TODO remove redundancy already serialized)
|
||||
var shares rational.Rat
|
||||
if tx.Shares == "MAX" {
|
||||
shares = bond.Shares
|
||||
} else {
|
||||
var err error
|
||||
shares, err = rational.NewFromDecimal(tx.Shares)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// subtract bond tokens from delegator bond
|
||||
if bond.Shares.LT(shares) { // bond shares < tx shares
|
||||
return ErrInsufficientFunds()
|
||||
}
|
||||
bond.Shares = bond.Shares.Sub(shares)
|
||||
|
||||
// get pubKey candidate
|
||||
candidate := loadCandidate(d.store, tx.PubKey)
|
||||
if candidate == nil {
|
||||
return ErrNoCandidateForAddress()
|
||||
}
|
||||
|
||||
revokeCandidacy := false
|
||||
if bond.Shares.IsZero() {
|
||||
|
||||
// if the bond is the owner of the candidate then
|
||||
// trigger a revoke candidacy
|
||||
if d.sender.Equals(candidate.Owner) &&
|
||||
candidate.Status != Revoked {
|
||||
revokeCandidacy = true
|
||||
}
|
||||
|
||||
// remove the bond
|
||||
removeDelegatorBond(d.store, d.sender, tx.PubKey)
|
||||
} else {
|
||||
saveDelegatorBond(d.store, d.sender, bond)
|
||||
}
|
||||
|
||||
// transfer coins back to account
|
||||
var poolAccount sdk.Actor
|
||||
if candidate.Status == Bonded {
|
||||
poolAccount = d.params.HoldBonded
|
||||
} else {
|
||||
poolAccount = d.params.HoldUnbonded
|
||||
}
|
||||
|
||||
returnCoins := candidate.removeShares(shares, d.gs)
|
||||
err := d.transfer(poolAccount, d.sender,
|
||||
coin.Coins{{d.params.AllowedBondDenom, returnCoins}})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// lastly if an revoke candidate if necessary
|
||||
if revokeCandidacy {
|
||||
|
||||
// change the share types to unbonded if they were not already
|
||||
if candidate.Status == Bonded {
|
||||
err = d.bondedToUnbondedPool(candidate)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// lastly update the status
|
||||
candidate.Status = Revoked
|
||||
}
|
||||
|
||||
// deduct shares from the candidate and save
|
||||
if candidate.Liabilities.IsZero() {
|
||||
removeCandidate(d.store, tx.PubKey)
|
||||
} else {
|
||||
saveCandidate(d.store, candidate)
|
||||
}
|
||||
|
||||
saveGlobalState(d.store, d.gs)
|
||||
return nil
|
||||
}
|
|
@ -0,0 +1,328 @@
|
|||
package stake
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
crypto "github.com/tendermint/go-crypto"
|
||||
"github.com/tendermint/tmlibs/rational"
|
||||
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
coin "github.com/cosmos/cosmos-sdk/x/bank" // XXX fix
|
||||
)
|
||||
|
||||
//______________________________________________________________________
|
||||
|
||||
// dummy transfer functions, represents store operations on account balances
|
||||
|
||||
type testCoinSender struct {
|
||||
store map[string]int64
|
||||
}
|
||||
|
||||
var _ coinSend = testCoinSender{} // enforce interface at compile time
|
||||
|
||||
func (c testCoinSender) transferFn(sender, receiver sdk.Actor, coins coin.Coins) error {
|
||||
c.store[string(sender.Address)] -= coins[0].Amount
|
||||
c.store[string(receiver.Address)] += coins[0].Amount
|
||||
return nil
|
||||
}
|
||||
|
||||
//______________________________________________________________________
|
||||
|
||||
func initAccounts(n int, amount int64) ([]sdk.Actor, map[string]int64) {
|
||||
accStore := map[string]int64{}
|
||||
senders := newActors(n)
|
||||
for _, sender := range senders {
|
||||
accStore[string(sender.Address)] = amount
|
||||
}
|
||||
return senders, accStore
|
||||
}
|
||||
|
||||
func newTxDeclareCandidacy(amt int64, pubKey crypto.PubKey) TxDeclareCandidacy {
|
||||
return TxDeclareCandidacy{
|
||||
BondUpdate{
|
||||
PubKey: pubKey,
|
||||
Bond: coin.Coin{"fermion", amt},
|
||||
},
|
||||
Description{},
|
||||
}
|
||||
}
|
||||
|
||||
func newTxDelegate(amt int64, pubKey crypto.PubKey) TxDelegate {
|
||||
return TxDelegate{BondUpdate{
|
||||
PubKey: pubKey,
|
||||
Bond: coin.Coin{"fermion", amt},
|
||||
}}
|
||||
}
|
||||
|
||||
func newTxUnbond(shares string, pubKey crypto.PubKey) TxUnbond {
|
||||
return TxUnbond{
|
||||
PubKey: pubKey,
|
||||
Shares: shares,
|
||||
}
|
||||
}
|
||||
|
||||
func paramsNoInflation() Params {
|
||||
return Params{
|
||||
HoldBonded: sdk.NewActor(stakingModuleName, []byte("77777777777777777777777777777777")),
|
||||
HoldUnbonded: sdk.NewActor(stakingModuleName, []byte("88888888888888888888888888888888")),
|
||||
InflationRateChange: rational.Zero,
|
||||
InflationMax: rational.Zero,
|
||||
InflationMin: rational.Zero,
|
||||
GoalBonded: rational.New(67, 100),
|
||||
MaxVals: 100,
|
||||
AllowedBondDenom: "fermion",
|
||||
GasDeclareCandidacy: 20,
|
||||
GasEditCandidacy: 20,
|
||||
GasDelegate: 20,
|
||||
GasUnbond: 20,
|
||||
}
|
||||
}
|
||||
|
||||
func newDeliver(t, sender sdk.Actor, accStore map[string]int64) deliver {
|
||||
store := initTestStore()
|
||||
params := paramsNoInflation()
|
||||
saveParams(store, params)
|
||||
return deliver{
|
||||
store: store,
|
||||
sender: sender,
|
||||
params: params,
|
||||
gs: loadGlobalState(store),
|
||||
transfer: testCoinSender{accStore}.transferFn,
|
||||
}
|
||||
}
|
||||
|
||||
func TestDuplicatesTxDeclareCandidacy(t *testing.T) {
|
||||
senders, accStore := initAccounts(2, 1000) // for accounts
|
||||
|
||||
deliverer := newDeliver(t, senders[0], accStore)
|
||||
checker := check{
|
||||
store: deliverer.store,
|
||||
sender: senders[0],
|
||||
}
|
||||
|
||||
txDeclareCandidacy := newTxDeclareCandidacy(10, pks[0])
|
||||
got := deliverer.declareCandidacy(txDeclareCandidacy)
|
||||
assert.NoError(t, got, "expected no error on runTxDeclareCandidacy")
|
||||
|
||||
// one sender can bond to two different pubKeys
|
||||
txDeclareCandidacy.PubKey = pks[1]
|
||||
err := checker.declareCandidacy(txDeclareCandidacy)
|
||||
assert.Nil(t, err, "didn't expected error on checkTx")
|
||||
|
||||
// two senders cant bond to the same pubkey
|
||||
checker.sender = senders[1]
|
||||
txDeclareCandidacy.PubKey = pks[0]
|
||||
err = checker.declareCandidacy(txDeclareCandidacy)
|
||||
assert.NotNil(t, err, "expected error on checkTx")
|
||||
}
|
||||
|
||||
func TestIncrementsTxDelegate(t *testing.T) {
|
||||
initSender := int64(1000)
|
||||
senders, accStore := initAccounts(1, initSender) // for accounts
|
||||
deliverer := newDeliver(t, senders[0], accStore)
|
||||
|
||||
// first declare candidacy
|
||||
bondAmount := int64(10)
|
||||
txDeclareCandidacy := newTxDeclareCandidacy(bondAmount, pks[0])
|
||||
got := deliverer.declareCandidacy(txDeclareCandidacy)
|
||||
assert.NoError(t, got, "expected declare candidacy tx to be ok, got %v", got)
|
||||
expectedBond := bondAmount // 1 since we send 1 at the start of loop,
|
||||
|
||||
// just send the same txbond multiple times
|
||||
holder := deliverer.params.HoldUnbonded // XXX this should be HoldBonded, new SDK updates
|
||||
txDelegate := newTxDelegate(bondAmount, pks[0])
|
||||
for i := 0; i < 5; i++ {
|
||||
got := deliverer.delegate(txDelegate)
|
||||
assert.NoError(t, got, "expected tx %d to be ok, got %v", i, got)
|
||||
|
||||
//Check that the accounts and the bond account have the appropriate values
|
||||
candidates := loadCandidates(deliverer.store)
|
||||
expectedBond += bondAmount
|
||||
expectedSender := initSender - expectedBond
|
||||
gotBonded := candidates[0].Liabilities.Evaluate()
|
||||
gotHolder := accStore[string(holder.Address)]
|
||||
gotSender := accStore[string(deliverer.sender.Address)]
|
||||
assert.Equal(t, expectedBond, gotBonded, "i: %v, %v, %v", i, expectedBond, gotBonded)
|
||||
assert.Equal(t, expectedBond, gotHolder, "i: %v, %v, %v", i, expectedBond, gotHolder)
|
||||
assert.Equal(t, expectedSender, gotSender, "i: %v, %v, %v", i, expectedSender, gotSender)
|
||||
}
|
||||
}
|
||||
|
||||
func TestIncrementsTxUnbond(t *testing.T) {
|
||||
initSender := int64(0)
|
||||
senders, accStore := initAccounts(1, initSender) // for accounts
|
||||
deliverer := newDeliver(t, senders[0], accStore)
|
||||
|
||||
// set initial bond
|
||||
initBond := int64(1000)
|
||||
accStore[string(deliverer.sender.Address)] = initBond
|
||||
got := deliverer.declareCandidacy(newTxDeclareCandidacy(initBond, pks[0]))
|
||||
assert.NoError(t, got, "expected initial bond tx to be ok, got %v", got)
|
||||
|
||||
// just send the same txunbond multiple times
|
||||
holder := deliverer.params.HoldUnbonded // XXX new SDK, this should be HoldBonded
|
||||
|
||||
// XXX use decimals here
|
||||
unbondShares, unbondSharesStr := int64(10), "10"
|
||||
txUndelegate := newTxUnbond(unbondSharesStr, pks[0])
|
||||
nUnbonds := 5
|
||||
for i := 0; i < nUnbonds; i++ {
|
||||
got := deliverer.unbond(txUndelegate)
|
||||
assert.NoError(t, got, "expected tx %d to be ok, got %v", i, got)
|
||||
|
||||
//Check that the accounts and the bond account have the appropriate values
|
||||
candidates := loadCandidates(deliverer.store)
|
||||
expectedBond := initBond - int64(i+1)*unbondShares // +1 since we send 1 at the start of loop
|
||||
expectedSender := initSender + (initBond - expectedBond)
|
||||
gotBonded := candidates[0].Liabilities.Evaluate()
|
||||
gotHolder := accStore[string(holder.Address)]
|
||||
gotSender := accStore[string(deliverer.sender.Address)]
|
||||
|
||||
assert.Equal(t, expectedBond, gotBonded, "%v, %v", expectedBond, gotBonded)
|
||||
assert.Equal(t, expectedBond, gotHolder, "%v, %v", expectedBond, gotHolder)
|
||||
assert.Equal(t, expectedSender, gotSender, "%v, %v", expectedSender, gotSender)
|
||||
}
|
||||
|
||||
// these are more than we have bonded now
|
||||
errorCases := []int64{
|
||||
//1<<64 - 1, // more than int64
|
||||
//1<<63 + 1, // more than int64
|
||||
1<<63 - 1,
|
||||
1 << 31,
|
||||
initBond,
|
||||
}
|
||||
for _, c := range errorCases {
|
||||
unbondShares := strconv.Itoa(int(c))
|
||||
txUndelegate := newTxUnbond(unbondShares, pks[0])
|
||||
got = deliverer.unbond(txUndelegate)
|
||||
assert.Error(t, got, "expected unbond tx to fail")
|
||||
}
|
||||
|
||||
leftBonded := initBond - unbondShares*int64(nUnbonds)
|
||||
|
||||
// should be unable to unbond one more than we have
|
||||
txUndelegate = newTxUnbond(strconv.Itoa(int(leftBonded)+1), pks[0])
|
||||
got = deliverer.unbond(txUndelegate)
|
||||
assert.Error(t, got, "expected unbond tx to fail")
|
||||
|
||||
// should be able to unbond just what we have
|
||||
txUndelegate = newTxUnbond(strconv.Itoa(int(leftBonded)), pks[0])
|
||||
got = deliverer.unbond(txUndelegate)
|
||||
assert.NoError(t, got, "expected unbond tx to pass")
|
||||
}
|
||||
|
||||
func TestMultipleTxDeclareCandidacy(t *testing.T) {
|
||||
initSender := int64(1000)
|
||||
senders, accStore := initAccounts(3, initSender)
|
||||
pubKeys := []crypto.PubKey{pks[0], pks[1], pks[2]}
|
||||
deliverer := newDeliver(t, senders[0], accStore)
|
||||
|
||||
// bond them all
|
||||
for i, sender := range senders {
|
||||
txDeclareCandidacy := newTxDeclareCandidacy(10, pubKeys[i])
|
||||
deliverer.sender = sender
|
||||
got := deliverer.declareCandidacy(txDeclareCandidacy)
|
||||
assert.NoError(t, got, "expected tx %d to be ok, got %v", i, got)
|
||||
|
||||
//Check that the account is bonded
|
||||
candidates := loadCandidates(deliverer.store)
|
||||
val := candidates[i]
|
||||
balanceGot, balanceExpd := accStore[string(val.Owner.Address)], initSender-10
|
||||
assert.Equal(t, i+1, len(candidates), "expected %d candidates got %d, candidates: %v", i+1, len(candidates), candidates)
|
||||
assert.Equal(t, 10, int(val.Liabilities.Evaluate()), "expected %d shares, got %d", 10, val.Liabilities)
|
||||
assert.Equal(t, balanceExpd, balanceGot, "expected account to have %d, got %d", balanceExpd, balanceGot)
|
||||
}
|
||||
|
||||
// unbond them all
|
||||
for i, sender := range senders {
|
||||
candidatePre := loadCandidate(deliverer.store, pubKeys[i])
|
||||
txUndelegate := newTxUnbond("10", pubKeys[i])
|
||||
deliverer.sender = sender
|
||||
got := deliverer.unbond(txUndelegate)
|
||||
assert.NoError(t, got, "expected tx %d to be ok, got %v", i, got)
|
||||
|
||||
//Check that the account is unbonded
|
||||
candidates := loadCandidates(deliverer.store)
|
||||
assert.Equal(t, len(senders)-(i+1), len(candidates), "expected %d candidates got %d", len(senders)-(i+1), len(candidates))
|
||||
|
||||
candidatePost := loadCandidate(deliverer.store, pubKeys[i])
|
||||
balanceGot, balanceExpd := accStore[string(candidatePre.Owner.Address)], initSender
|
||||
assert.Nil(t, candidatePost, "expected nil candidate retrieve, got %d", 0, candidatePost)
|
||||
assert.Equal(t, balanceExpd, balanceGot, "expected account to have %d, got %d", balanceExpd, balanceGot)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMultipleTxDelegate(t *testing.T) {
|
||||
accounts, accStore := initAccounts(3, 1000)
|
||||
sender, delegators := accounts[0], accounts[1:]
|
||||
deliverer := newDeliver(t, sender, accStore)
|
||||
|
||||
//first make a candidate
|
||||
txDeclareCandidacy := newTxDeclareCandidacy(10, pks[0])
|
||||
got := deliverer.declareCandidacy(txDeclareCandidacy)
|
||||
require.NoError(t, got, "expected tx to be ok, got %v", got)
|
||||
|
||||
// delegate multiple parties
|
||||
for i, delegator := range delegators {
|
||||
txDelegate := newTxDelegate(10, pks[0])
|
||||
deliverer.sender = delegator
|
||||
got := deliverer.delegate(txDelegate)
|
||||
require.NoError(t, got, "expected tx %d to be ok, got %v", i, got)
|
||||
|
||||
//Check that the account is bonded
|
||||
bond := loadDelegatorBond(deliverer.store, delegator, pks[0])
|
||||
assert.NotNil(t, bond, "expected delegatee bond %d to exist", bond)
|
||||
}
|
||||
|
||||
// unbond them all
|
||||
for i, delegator := range delegators {
|
||||
txUndelegate := newTxUnbond("10", pks[0])
|
||||
deliverer.sender = delegator
|
||||
got := deliverer.unbond(txUndelegate)
|
||||
require.NoError(t, got, "expected tx %d to be ok, got %v", i, got)
|
||||
|
||||
//Check that the account is unbonded
|
||||
bond := loadDelegatorBond(deliverer.store, delegator, pks[0])
|
||||
assert.Nil(t, bond, "expected delegatee bond %d to be nil", bond)
|
||||
}
|
||||
}
|
||||
|
||||
func TestVoidCandidacy(t *testing.T) {
|
||||
accounts, accStore := initAccounts(2, 1000) // for accounts
|
||||
sender, delegator := accounts[0], accounts[1]
|
||||
deliverer := newDeliver(t, sender, accStore)
|
||||
|
||||
// create the candidate
|
||||
txDeclareCandidacy := newTxDeclareCandidacy(10, pks[0])
|
||||
got := deliverer.declareCandidacy(txDeclareCandidacy)
|
||||
require.NoError(t, got, "expected no error on runTxDeclareCandidacy")
|
||||
|
||||
// bond a delegator
|
||||
txDelegate := newTxDelegate(10, pks[0])
|
||||
deliverer.sender = delegator
|
||||
got = deliverer.delegate(txDelegate)
|
||||
require.NoError(t, got, "expected ok, got %v", got)
|
||||
|
||||
// unbond the candidates bond portion
|
||||
txUndelegate := newTxUnbond("10", pks[0])
|
||||
deliverer.sender = sender
|
||||
got = deliverer.unbond(txUndelegate)
|
||||
require.NoError(t, got, "expected no error on runTxDeclareCandidacy")
|
||||
|
||||
// test that this pubkey cannot yet be bonded too
|
||||
deliverer.sender = delegator
|
||||
got = deliverer.delegate(txDelegate)
|
||||
assert.Error(t, got, "expected error, got %v", got)
|
||||
|
||||
// test that the delegator can still withdraw their bonds
|
||||
got = deliverer.unbond(txUndelegate)
|
||||
require.NoError(t, got, "expected no error on runTxDeclareCandidacy")
|
||||
|
||||
// verify that the pubkey can now be reused
|
||||
got = deliverer.declareCandidacy(txDeclareCandidacy)
|
||||
assert.NoError(t, got, "expected ok, got %v", got)
|
||||
}
|
|
@ -0,0 +1,74 @@
|
|||
package stake
|
||||
|
||||
import (
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
|
||||
abci "github.com/tendermint/abci/types"
|
||||
"github.com/tendermint/tmlibs/rational"
|
||||
)
|
||||
|
||||
// Tick - called at the end of every block
|
||||
func Tick(ctx sdk.Context, store types.KVStore) (change []*abci.Validator, err error) {
|
||||
|
||||
// retrieve params
|
||||
params := loadParams(store)
|
||||
gs := loadGlobalState(store)
|
||||
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(store, gs, params)
|
||||
}
|
||||
|
||||
return UpdateValidatorSet(store, gs, params)
|
||||
}
|
||||
|
||||
var hrsPerYr = rational.New(8766) // as defined by a julian year of 365.25 days
|
||||
|
||||
// process provisions for an hour period
|
||||
func processProvisions(store types.KVStore, gs *GlobalState, params Params) {
|
||||
|
||||
gs.Inflation = nextInflation(gs, 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(rational.New(gs.TotalSupply)).Quo(hrsPerYr).Evaluate()
|
||||
gs.BondedPool += provisions
|
||||
gs.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
|
||||
saveGlobalState(store, gs)
|
||||
}
|
||||
|
||||
// get the next inflation rate for the hour
|
||||
func nextInflation(gs *GlobalState, params Params) (inflation rational.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
|
||||
// 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%.
|
||||
|
||||
// (1 - bondedRatio/GoalBonded) * InflationRateChange
|
||||
inflationRateChangePerYear := rational.One.Sub(gs.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)
|
||||
if inflation.GT(params.InflationMax) {
|
||||
inflation = params.InflationMax
|
||||
}
|
||||
if inflation.LT(params.InflationMin) {
|
||||
inflation = params.InflationMin
|
||||
}
|
||||
|
||||
return
|
||||
}
|
|
@ -0,0 +1,116 @@
|
|||
package stake
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/tendermint/tmlibs/rational"
|
||||
)
|
||||
|
||||
func TestGetInflation(t *testing.T) {
|
||||
store := initTestStore(t)
|
||||
params := loadParams(store)
|
||||
gs := loadGlobalState(store)
|
||||
|
||||
// Governing Mechanism:
|
||||
// bondedRatio = BondedPool / TotalSupply
|
||||
// inflationRateChangePerYear = (1- bondedRatio/ GoalBonded) * MaxInflationRateChange
|
||||
|
||||
tests := []struct {
|
||||
setBondedPool, setTotalSupply int64
|
||||
setInflation, expectedChange rational.Rat
|
||||
}{
|
||||
// with 0% bonded atom supply the inflation should increase by InflationRateChange
|
||||
{0, 0, rational.New(7, 100), params.InflationRateChange.Quo(hrsPerYr)},
|
||||
|
||||
// 100% bonded, starting at 20% inflation and being reduced
|
||||
{1, 1, rational.New(20, 100), rational.One.Sub(rational.One.Quo(params.GoalBonded)).Mul(params.InflationRateChange).Quo(hrsPerYr)},
|
||||
|
||||
// 50% bonded, starting at 10% inflation and being increased
|
||||
{1, 2, rational.New(10, 100), rational.One.Sub(rational.New(1, 2).Quo(params.GoalBonded)).Mul(params.InflationRateChange).Quo(hrsPerYr)},
|
||||
|
||||
// test 7% minimum stop (testing with 100% bonded)
|
||||
{1, 1, rational.New(7, 100), rational.Zero},
|
||||
{1, 1, rational.New(70001, 1000000), rational.New(-1, 1000000)},
|
||||
|
||||
// test 20% maximum stop (testing with 0% bonded)
|
||||
{0, 0, rational.New(20, 100), rational.Zero},
|
||||
{0, 0, rational.New(199999, 1000000), rational.New(1, 1000000)},
|
||||
|
||||
// perfect balance shouldn't change inflation
|
||||
{67, 100, rational.New(15, 100), rational.Zero},
|
||||
}
|
||||
for _, tc := range tests {
|
||||
gs.BondedPool, gs.TotalSupply = tc.setBondedPool, tc.setTotalSupply
|
||||
gs.Inflation = tc.setInflation
|
||||
|
||||
inflation := nextInflation(gs, params)
|
||||
diffInflation := inflation.Sub(tc.setInflation)
|
||||
|
||||
assert.True(t, diffInflation.Equal(tc.expectedChange),
|
||||
"%v, %v", diffInflation, tc.expectedChange)
|
||||
}
|
||||
}
|
||||
|
||||
func TestProcessProvisions(t *testing.T) {
|
||||
store := initTestStore(t)
|
||||
params := loadParams(store)
|
||||
gs := loadGlobalState(store)
|
||||
|
||||
// create some candidates some bonded, some unbonded
|
||||
n := 10
|
||||
actors := newActors(n)
|
||||
candidates := candidatesFromActorsEmpty(actors)
|
||||
for i, candidate := range candidates {
|
||||
if i < 5 {
|
||||
candidate.Status = Bonded
|
||||
}
|
||||
mintedTokens := int64((i + 1) * 10000000)
|
||||
gs.TotalSupply += mintedTokens
|
||||
candidate.addTokens(mintedTokens, gs)
|
||||
saveCandidate(store, candidate)
|
||||
}
|
||||
var totalSupply int64 = 550000000
|
||||
var bondedShares int64 = 150000000
|
||||
var unbondedShares int64 = 400000000
|
||||
|
||||
// initial bonded ratio ~ 27%
|
||||
assert.True(t, gs.bondedRatio().Equal(rational.New(bondedShares, totalSupply)), "%v", gs.bondedRatio())
|
||||
|
||||
// Supplies
|
||||
assert.Equal(t, totalSupply, gs.TotalSupply)
|
||||
assert.Equal(t, bondedShares, gs.BondedPool)
|
||||
assert.Equal(t, unbondedShares, gs.UnbondedPool)
|
||||
|
||||
// test the value of candidate shares
|
||||
assert.True(t, gs.bondedShareExRate().Equal(rational.One), "%v", gs.bondedShareExRate())
|
||||
|
||||
initialSupply := gs.TotalSupply
|
||||
initialUnbonded := gs.TotalSupply - gs.BondedPool
|
||||
|
||||
// process the provisions a year
|
||||
for hr := 0; hr < 8766; hr++ {
|
||||
expInflation := nextInflation(gs, params).Round(1000000000)
|
||||
expProvisions := (expInflation.Mul(rational.New(gs.TotalSupply)).Quo(hrsPerYr)).Evaluate()
|
||||
startBondedPool := gs.BondedPool
|
||||
startTotalSupply := gs.TotalSupply
|
||||
processProvisions(store, gs, params)
|
||||
assert.Equal(t, startBondedPool+expProvisions, gs.BondedPool)
|
||||
assert.Equal(t, startTotalSupply+expProvisions, gs.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))
|
||||
|
||||
// initial bonded ratio ~ 35% ~ 30% increase for bonded holders
|
||||
assert.True(t, gs.bondedRatio().Equal(rational.New(105906511, 305906511)), "%v", gs.bondedRatio())
|
||||
|
||||
// global supply
|
||||
assert.Equal(t, int64(611813022), gs.TotalSupply)
|
||||
assert.Equal(t, int64(211813022), gs.BondedPool)
|
||||
assert.Equal(t, unbondedShares, gs.UnbondedPool)
|
||||
|
||||
// test the value of candidate shares
|
||||
assert.True(t, gs.bondedShareExRate().Mul(rational.New(bondedShares)).Equal(rational.New(211813022)), "%v", gs.bondedShareExRate())
|
||||
|
||||
}
|
Loading…
Reference in New Issue