diff --git a/.pending/breaking/sdk/3516-Remove-concept-of-shares-from-staking-unbonding-and-redelegation-UX b/.pending/breaking/sdk/3516-Remove-concept-of-shares-from-staking-unbonding-and-redelegation-UX new file mode 100644 index 000000000000..b8153c345a8b --- /dev/null +++ b/.pending/breaking/sdk/3516-Remove-concept-of-shares-from-staking-unbonding-and-redelegation-UX @@ -0,0 +1,2 @@ +#3516 Remove concept of shares from staking unbonding and redelegation UX; +replaced by direct coin amount. diff --git a/client/lcd/lcd_test.go b/client/lcd/lcd_test.go index f7b2dd076459..4d93dab34305 100644 --- a/client/lcd/lcd_test.go +++ b/client/lcd/lcd_test.go @@ -533,7 +533,7 @@ func TestBonding(t *testing.T) { // this test takes a long time to run, eventually this second validator // will get slashed, meaning that it's exchange rate is no-longer 1-to-1, // hence we utilize the exchange rate in the following test - delTokensAfterRedelegation := validator2.ShareTokens(delegatorDels[0].GetShares()) + delTokensAfterRedelegation := validator2.TokensFromShares(delegatorDels[0].GetShares()) require.Equal(t, rdTokens.ToDec(), delTokensAfterRedelegation) // verify balance after paying fees diff --git a/client/lcd/test_helpers.go b/client/lcd/test_helpers.go index 3360e0112256..ef871f5bf838 100644 --- a/client/lcd/test_helpers.go +++ b/client/lcd/test_helpers.go @@ -815,11 +815,11 @@ func doDelegate( from := acc.GetAddress().String() baseReq := rest.NewBaseReq(from, "", chainID, "", "", accnum, sequence, fees, nil, false) - msg := msgDelegationsInput{ + msg := stakingrest.DelegateRequest{ BaseReq: baseReq, DelegatorAddress: delAddr, ValidatorAddress: valAddr, - Delegation: sdk.NewCoin(sdk.DefaultBondDenom, amount), + Amount: sdk.NewCoin(sdk.DefaultBondDenom, amount), } req, err := cdc.MarshalJSON(msg) @@ -839,13 +839,6 @@ func doDelegate( return txResp } -type msgDelegationsInput struct { - BaseReq rest.BaseReq `json:"base_req"` - DelegatorAddress sdk.AccAddress `json:"delegator_address"` // in bech32 - ValidatorAddress sdk.ValAddress `json:"validator_address"` // in bech32 - Delegation sdk.Coin `json:"delegation"` -} - // POST /staking/delegators/{delegatorAddr}/delegations Submit delegation func doUndelegate( t *testing.T, port, name, pwd string, delAddr sdk.AccAddress, @@ -859,11 +852,11 @@ func doUndelegate( from := acc.GetAddress().String() baseReq := rest.NewBaseReq(from, "", chainID, "", "", accnum, sequence, fees, nil, false) - msg := msgUndelegateInput{ + msg := stakingrest.UndelegateRequest{ BaseReq: baseReq, DelegatorAddress: delAddr, ValidatorAddress: valAddr, - SharesAmount: amount.ToDec(), + Amount: sdk.NewCoin(sdk.DefaultBondDenom, amount), } req, err := cdc.MarshalJSON(msg) @@ -882,13 +875,6 @@ func doUndelegate( return txResp } -type msgUndelegateInput struct { - BaseReq rest.BaseReq `json:"base_req"` - DelegatorAddress sdk.AccAddress `json:"delegator_address"` // in bech32 - ValidatorAddress sdk.ValAddress `json:"validator_address"` // in bech32 - SharesAmount sdk.Dec `json:"shares"` -} - // POST /staking/delegators/{delegatorAddr}/delegations Submit delegation func doBeginRedelegation( t *testing.T, port, name, pwd string, delAddr sdk.AccAddress, valSrcAddr, @@ -902,12 +888,12 @@ func doBeginRedelegation( from := acc.GetAddress().String() baseReq := rest.NewBaseReq(from, "", chainID, "", "", accnum, sequence, fees, nil, false) - msg := stakingrest.MsgBeginRedelegateInput{ + msg := stakingrest.RedelegateRequest{ BaseReq: baseReq, DelegatorAddress: delAddr, ValidatorSrcAddress: valSrcAddr, ValidatorDstAddress: valDstAddr, - SharesAmount: amount.ToDec(), + Amount: sdk.NewCoin(sdk.DefaultBondDenom, amount), } req, err := cdc.MarshalJSON(msg) @@ -926,14 +912,6 @@ func doBeginRedelegation( return txResp } -type msgBeginRedelegateInput struct { - BaseReq rest.BaseReq `json:"base_req"` - DelegatorAddress sdk.AccAddress `json:"delegator_address"` // in bech32 - ValidatorSrcAddress sdk.ValAddress `json:"validator_src_address"` // in bech32 - ValidatorDstAddress sdk.ValAddress `json:"validator_dst_address"` // in bech32 - SharesAmount sdk.Dec `json:"shares"` -} - // GET /staking/delegators/{delegatorAddr}/delegations Get all delegations from a delegator func getDelegatorDelegations(t *testing.T, port string, delegatorAddr sdk.AccAddress) []staking.Delegation { res, body := Request(t, port, "GET", fmt.Sprintf("/staking/delegators/%s/delegations", delegatorAddr), nil) diff --git a/cmd/gaia/cli_test/cli_test.go b/cmd/gaia/cli_test/cli_test.go index 5bdb325f4a0a..078b1da32528 100644 --- a/cmd/gaia/cli_test/cli_test.go +++ b/cmd/gaia/cli_test/cli_test.go @@ -394,13 +394,13 @@ func TestGaiaCLICreateValidator(t *testing.T) { require.NotZero(t, validatorDelegations[0].Shares) // unbond a single share - unbondTokens := sdk.TokensFromTendermintPower(1) - success = f.TxStakingUnbond(keyBar, unbondTokens.String(), barVal, "-y") + unbondAmt := sdk.NewCoin(sdk.DefaultBondDenom, sdk.TokensFromTendermintPower(1)) + success = f.TxStakingUnbond(keyBar, unbondAmt.String(), barVal, "-y") require.True(t, success) tests.WaitForNextNBlocksTM(1, f.Port) // Ensure bonded staking is correct - remainingTokens := newValTokens.Sub(unbondTokens) + remainingTokens := newValTokens.Sub(unbondAmt.Amount) validator = f.QueryStakingValidator(barVal) require.Equal(t, remainingTokens, validator.Tokens) diff --git a/docs/_attic/WIP-lamborghini-distribution/transactions.md b/docs/_attic/WIP-lamborghini-distribution/transactions.md index 085f3b6e3a89..b0214118554a 100644 --- a/docs/_attic/WIP-lamborghini-distribution/transactions.md +++ b/docs/_attic/WIP-lamborghini-distribution/transactions.md @@ -76,7 +76,7 @@ When a validator wishes to withdraw their transaction fees it must send triggered each with any change in individual delegations, such as an unbond, redelegation, or delegation of additional tokens to a specific validator. This transaction withdraws the validators commission rewards, as well as any rewards -earning on their self-delegation. +earning on their self-delegation. ```golang type TxWithdrawValidator struct { diff --git a/docs/gaia/gaiacli.md b/docs/gaia/gaiacli.md index b3a1db42a2c1..f4c59ef83b46 100644 --- a/docs/gaia/gaiacli.md +++ b/docs/gaia/gaiacli.md @@ -376,12 +376,13 @@ gaiacli query staking delegations #### Unbond Tokens -If for any reason the validator misbehaves, or you just want to unbond a certain amount of tokens, use this following command. You can unbond a specific `shares-amount` (eg:`12.1`\) or a `shares-fraction` (eg:`0.25`) with the corresponding flags. +If for any reason the validator misbehaves, or you just want to unbond a certain +amount of tokens, use this following command. ```bash gaiacli tx staking unbond \ - --validator= \ - --shares-fraction=0.5 \ + \ + 10atom \ --from= \ --chain-id= ``` @@ -414,9 +415,9 @@ A redelegation is a type delegation that allows you to bond illiquid tokens from ```bash gaiacli tx staking redelegate \ - --addr-validator-source= \ - --addr-validator-dest= \ - --shares-fraction=50 \ + \ + \ + 10atom \ --from= \ --chain-id= ``` diff --git a/docs/translations/kr/gaia/gaiacli.md b/docs/translations/kr/gaia/gaiacli.md index 5d144510b698..a591318bc3b7 100755 --- a/docs/translations/kr/gaia/gaiacli.md +++ b/docs/translations/kr/gaia/gaiacli.md @@ -375,13 +375,12 @@ gaiacli query staking delegation 만약 특정 검증인이 악의적인 행동을 했거나 또는 본인이 개인적인 이유로 일부 토큰을 언본딩을 워하는 경우 다음 명령어를 통해 토큰을 언본딩 할 수 있습니다. 언본딩은 정확한 수량인 `shares-amount`(예시, `12.1`) 또는 언본딩을 원하는 물량의 비율인 `shares-fraction`(예시, `0.25`) 값으로 표현될 수 있습니다. - ```bash gaiacli tx staking unbond \ - --validator= \ - --shares-fraction=0.5 \ - --from= \ - --chain-id= + \ + 10atom \ + --from= \ + --chain-id= ``` 언본딩은 언본딩 기간이 끝나는 대로 완료됩니다. @@ -412,11 +411,11 @@ gaiacli query staking unbonding-delegations-from \ - --addr-validator-dest= \ - --shares-fraction=50 \ - --from= \ - --chain-id= + \ + \ + 10atom \ + --from= \ + --chain-id= ``` 위 예시와 같이 재위임될 토큰의 수량은 특정 수량(`shares-amount`) 또는 일정 비율(`shares-fraction`)로 표현될 수 있습니다. diff --git a/types/staking.go b/types/staking.go index 428979a18658..4decccb20315 100644 --- a/types/staking.go +++ b/types/staking.go @@ -61,20 +61,22 @@ func (b BondStatus) Equal(b2 BondStatus) bool { // validator for a delegated proof of stake system type Validator interface { - GetJailed() bool // whether the validator is jailed - GetMoniker() string // moniker of the validator - GetStatus() BondStatus // status of the validator - GetOperator() ValAddress // operator address to receive/return validators coins - GetConsPubKey() crypto.PubKey // validation consensus pubkey - GetConsAddr() ConsAddress // validation consensus address - GetTokens() Int // validation tokens - GetBondedTokens() Int // validator bonded tokens - GetTendermintPower() int64 // validation power in tendermint - GetCommission() Dec // validator commission rate - GetMinSelfDelegation() Int // validator minimum self delegation - GetDelegatorShares() Dec // total outstanding delegator shares - ShareTokens(Dec) Dec // token worth of provided delegator shares - ShareTokensTruncated(Dec) Dec // token worth of provided delegator shares, truncated + GetJailed() bool // whether the validator is jailed + GetMoniker() string // moniker of the validator + GetStatus() BondStatus // status of the validator + GetOperator() ValAddress // operator address to receive/return validators coins + GetConsPubKey() crypto.PubKey // validation consensus pubkey + GetConsAddr() ConsAddress // validation consensus address + GetTokens() Int // validation tokens + GetBondedTokens() Int // validator bonded tokens + GetTendermintPower() int64 // validation power in tendermint + GetCommission() Dec // validator commission rate + GetMinSelfDelegation() Int // validator minimum self delegation + GetDelegatorShares() Dec // total outstanding delegator shares + TokensFromShares(Dec) Dec // token worth of provided delegator shares + TokensFromSharesTruncated(Dec) Dec // token worth of provided delegator shares, truncated + SharesFromTokens(amt Int) (Dec, Error) // shares worth of delegator's bond + SharesFromTokensTruncated(amt Int) (Dec, Error) // truncated shares worth of delegator's bond } // validator which fulfills abci validator interface for use in Tendermint diff --git a/x/distribution/keeper/delegation.go b/x/distribution/keeper/delegation.go index 00e7669bbd90..f6d9bdc280a3 100644 --- a/x/distribution/keeper/delegation.go +++ b/x/distribution/keeper/delegation.go @@ -22,7 +22,7 @@ func (k Keeper) initializeDelegation(ctx sdk.Context, val sdk.ValAddress, del sd // calculate delegation stake in tokens // we don't store directly, so multiply delegation shares * (tokens per share) // note: necessary to truncate so we don't allow withdrawing more rewards than owed - stake := validator.ShareTokensTruncated(delegation.GetShares()) + stake := validator.TokensFromSharesTruncated(delegation.GetShares()) k.SetDelegatorStartingInfo(ctx, val, del, types.NewDelegatorStartingInfo(previousPeriod, stake, uint64(ctx.BlockHeight()))) } @@ -90,7 +90,7 @@ func (k Keeper) calculateDelegationRewards(ctx sdk.Context, val sdk.Validator, d // a stake sanity check - recalculated final stake should be less than or equal to current stake // here we cannot use Equals because stake is truncated when multiplied by slash fractions // we could only use equals if we had arbitrary-precision rationals - currentStake := val.ShareTokens(del.GetShares()) + currentStake := val.TokensFromShares(del.GetShares()) if stake.GT(currentStake) { panic(fmt.Sprintf("calculated final stake for delegator %s greater than current stake: %s, %s", del.GetDelegatorAddr(), stake, currentStake)) diff --git a/x/slashing/handler.go b/x/slashing/handler.go index fcc141a86c03..a710c6b6c478 100644 --- a/x/slashing/handler.go +++ b/x/slashing/handler.go @@ -31,7 +31,7 @@ func handleMsgUnjail(ctx sdk.Context, msg MsgUnjail, k Keeper) sdk.Result { return ErrMissingSelfDelegation(k.codespace).Result() } - if validator.ShareTokens(selfDel.GetShares()).TruncateInt().LT(validator.GetMinSelfDelegation()) { + if validator.TokensFromShares(selfDel.GetShares()).TruncateInt().LT(validator.GetMinSelfDelegation()) { return ErrSelfDelegationTooLowToUnjail(k.codespace).Result() } diff --git a/x/slashing/handler_test.go b/x/slashing/handler_test.go index 07cf45001aa0..0c0f2f26077b 100644 --- a/x/slashing/handler_test.go +++ b/x/slashing/handler_test.go @@ -51,7 +51,8 @@ func TestCannotUnjailUnlessMeetMinSelfDelegation(t *testing.T) { sdk.Coins{sdk.NewCoin(sk.GetParams(ctx).BondDenom, initCoins.Sub(amt))}, ) - undelegateMsg := staking.NewMsgUndelegate(sdk.AccAddress(addr), addr, sdk.OneDec()) + unbondAmt := sdk.NewCoin(sk.GetParams(ctx).BondDenom, sdk.OneInt()) + undelegateMsg := staking.NewMsgUndelegate(sdk.AccAddress(addr), addr, unbondAmt) got = staking.NewHandler(sk)(ctx, undelegateMsg) require.True(t, sk.Validator(ctx, addr).GetJailed()) @@ -92,10 +93,10 @@ func TestJailedValidatorDelegations(t *testing.T) { got = staking.NewHandler(stakingKeeper)(ctx, msgDelegate) require.True(t, got.IsOK(), "expected delegation to be ok, got %v", got) - unbondShares := bondAmount.ToDec() + unbondAmt := sdk.NewCoin(stakingKeeper.GetParams(ctx).BondDenom, bondAmount) // unbond validator total self-delegations (which should jail the validator) - msgUndelegate := staking.NewMsgUndelegate(sdk.AccAddress(valAddr), valAddr, unbondShares) + msgUndelegate := staking.NewMsgUndelegate(sdk.AccAddress(valAddr), valAddr, unbondAmt) got = staking.NewHandler(stakingKeeper)(ctx, msgUndelegate) require.True(t, got.IsOK(), "expected begin unbonding validator msg to be ok, got: %v", got) diff --git a/x/slashing/keeper_test.go b/x/slashing/keeper_test.go index 4dbc1e727d91..e7501e1af637 100644 --- a/x/slashing/keeper_test.go +++ b/x/slashing/keeper_test.go @@ -73,7 +73,10 @@ func TestHandleDoubleSign(t *testing.T) { // Should be able to unbond now del, _ := sk.GetDelegation(ctx, sdk.AccAddress(operatorAddr), operatorAddr) - msgUnbond := staking.NewMsgUndelegate(sdk.AccAddress(operatorAddr), operatorAddr, del.GetShares()) + validator, _ := sk.GetValidator(ctx, operatorAddr) + + totalBond := validator.TokensFromShares(del.GetShares()).TruncateInt() + msgUnbond := staking.NewMsgUndelegate(sdk.AccAddress(operatorAddr), operatorAddr, sdk.NewCoin(sk.GetParams(ctx).BondDenom, totalBond)) res = staking.NewHandler(sk)(ctx, msgUnbond) require.True(t, res.IsOK()) } diff --git a/x/staking/app_test.go b/x/staking/app_test.go index 8eea9daf9cae..da8c52ea5b51 100644 --- a/x/staking/app_test.go +++ b/x/staking/app_test.go @@ -157,7 +157,7 @@ func TestStakingMsgs(t *testing.T) { checkDelegation(t, mApp, keeper, addr2, sdk.ValAddress(addr1), true, bondTokens.ToDec()) // begin unbonding - beginUnbondingMsg := NewMsgUndelegate(addr2, sdk.ValAddress(addr1), bondTokens.ToDec()) + beginUnbondingMsg := NewMsgUndelegate(addr2, sdk.ValAddress(addr1), bondCoin) header = abci.Header{Height: mApp.LastBlockHeight() + 1} mock.SignCheckDeliver(t, mApp.Cdc, mApp.BaseApp, header, []sdk.Msg{beginUnbondingMsg}, []uint64{1}, []uint64{1}, true, true, priv2) diff --git a/x/staking/client/cli/tx.go b/x/staking/client/cli/tx.go index b5eb8defa182..0507fb7eaee3 100644 --- a/x/staking/client/cli/tx.go +++ b/x/staking/client/cli/tx.go @@ -153,7 +153,7 @@ func GetCmdRedelegate(storeName string, cdc *codec.Codec) *cobra.Command { Args: cobra.ExactArgs(3), Long: strings.TrimSpace(`Redelegate an amount of illiquid staking tokens from one validator to another: -$ gaiacli tx staking redelegate cosmosvaloper1gghjut3ccd8ay0zduzj64hwre2fxs9ldmqhffj cosmosvaloper1l2rsakp388kuv9k8qzq6lrm9taddae7fpx59wm 100 --from mykey +$ gaiacli tx staking redelegate cosmosvaloper1gghjut3ccd8ay0zduzj64hwre2fxs9ldmqhffj cosmosvaloper1l2rsakp388kuv9k8qzq6lrm9taddae7fpx59wm 100stake --from mykey `), RunE: func(cmd *cobra.Command, args []string) error { txBldr := authtxb.NewTxBuilderFromCLI().WithTxEncoder(auth.DefaultTxEncoder(cdc)) @@ -161,8 +161,6 @@ $ gaiacli tx staking redelegate cosmosvaloper1gghjut3ccd8ay0zduzj64hwre2fxs9ldmq WithCodec(cdc). WithAccountDecoder(cdc) - // var err error - delAddr := cliCtx.GetFromAddress() valSrcAddr, err := sdk.ValAddressFromBech32(args[0]) if err != nil { @@ -174,13 +172,12 @@ $ gaiacli tx staking redelegate cosmosvaloper1gghjut3ccd8ay0zduzj64hwre2fxs9ldmq return err } - // get the shares amount - sharesAmount, err := getShares(args[2], delAddr, valSrcAddr) + amount, err := sdk.ParseCoin(args[2]) if err != nil { return err } - msg := staking.NewMsgBeginRedelegate(delAddr, valSrcAddr, valDstAddr, sharesAmount) + msg := staking.NewMsgBeginRedelegate(delAddr, valSrcAddr, valDstAddr, amount) return utils.GenerateOrBroadcastMsgs(cliCtx, txBldr, []sdk.Msg{msg}, false) }, } @@ -194,7 +191,7 @@ func GetCmdUnbond(storeName string, cdc *codec.Codec) *cobra.Command { Args: cobra.ExactArgs(2), Long: strings.TrimSpace(`Unbond an amount of bonded shares from a validator: -$ gaiacli tx staking unbond cosmosvaloper1gghjut3ccd8ay0zduzj64hwre2fxs9ldmqhffj 100 --from mykey +$ gaiacli tx staking unbond cosmosvaloper1gghjut3ccd8ay0zduzj64hwre2fxs9ldmqhffj 100stake --from mykey `), RunE: func(cmd *cobra.Command, args []string) error { txBldr := authtxb.NewTxBuilderFromCLI().WithTxEncoder(auth.DefaultTxEncoder(cdc)) @@ -208,13 +205,12 @@ $ gaiacli tx staking unbond cosmosvaloper1gghjut3ccd8ay0zduzj64hwre2fxs9ldmqhffj return err } - // get the shares amount - sharesAmount, err := getShares(args[1], delAddr, valAddr) + amount, err := sdk.ParseCoin(args[1]) if err != nil { return err } - msg := staking.NewMsgUndelegate(delAddr, valAddr, sharesAmount) + msg := staking.NewMsgUndelegate(delAddr, valAddr, amount) return utils.GenerateOrBroadcastMsgs(cliCtx, txBldr, []sdk.Msg{msg}, false) }, } diff --git a/x/staking/client/cli/utils.go b/x/staking/client/cli/utils.go index 165a761c2b15..1cd897c27737 100644 --- a/x/staking/client/cli/utils.go +++ b/x/staking/client/cli/utils.go @@ -7,19 +7,6 @@ import ( "github.com/cosmos/cosmos-sdk/x/staking/types" ) -func getShares(sharesAmountStr string, delAddr sdk.AccAddress, valAddr sdk.ValAddress) (sharesAmount sdk.Dec, err error) { - sharesAmount, err = sdk.NewDecFromStr(sharesAmountStr) - if err != nil { - return sharesAmount, err - } - - if !sharesAmount.GT(sdk.ZeroDec()) { - return sharesAmount, errors.New("shares amount must be positive number (ex. 123, 1.23456789)") - } - - return -} - func buildCommissionMsg(rateStr, maxRateStr, maxChangeRateStr string) (commission types.CommissionMsg, err error) { if rateStr == "" || maxRateStr == "" || maxChangeRateStr == "" { return commission, errors.New("must specify all validator commission parameters") diff --git a/x/staking/client/rest/tx.go b/x/staking/client/rest/tx.go index f937dd333b54..f42a90c9d02a 100644 --- a/x/staking/client/rest/tx.go +++ b/x/staking/client/rest/tx.go @@ -31,35 +31,35 @@ func registerTxRoutes(cliCtx context.CLIContext, r *mux.Router, cdc *codec.Codec } type ( - // MsgBeginRedelegateInput defines the properties of a delegation request's body. - MsgDelegationsInput struct { + // DelegateRequest defines the properties of a delegation request's body. + DelegateRequest struct { BaseReq rest.BaseReq `json:"base_req"` DelegatorAddress sdk.AccAddress `json:"delegator_address"` // in bech32 ValidatorAddress sdk.ValAddress `json:"validator_address"` // in bech32 - Delegation sdk.Coin `json:"delegation"` + Amount sdk.Coin `json:"amount"` } - // MsgBeginRedelegateInput defines the properties of a redelegate request's body. - MsgBeginRedelegateInput struct { + // RedelegateRequest defines the properties of a redelegate request's body. + RedelegateRequest struct { BaseReq rest.BaseReq `json:"base_req"` DelegatorAddress sdk.AccAddress `json:"delegator_address"` // in bech32 ValidatorSrcAddress sdk.ValAddress `json:"validator_src_address"` // in bech32 ValidatorDstAddress sdk.ValAddress `json:"validator_dst_address"` // in bech32 - SharesAmount sdk.Dec `json:"shares"` + Amount sdk.Coin `json:"amount"` } - // MsgUndelegateInput defines the properties of a undelegate request's body. - MsgUndelegateInput struct { + // UndelegateRequest defines the properties of a undelegate request's body. + UndelegateRequest struct { BaseReq rest.BaseReq `json:"base_req"` DelegatorAddress sdk.AccAddress `json:"delegator_address"` // in bech32 ValidatorAddress sdk.ValAddress `json:"validator_address"` // in bech32 - SharesAmount sdk.Dec `json:"shares"` + Amount sdk.Coin `json:"amount"` } ) func postDelegationsHandlerFn(cdc *codec.Codec, kb keys.Keybase, cliCtx context.CLIContext) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { - var req MsgDelegationsInput + var req DelegateRequest if !rest.ReadRESTReq(w, r, cdc, &req) { return @@ -70,7 +70,7 @@ func postDelegationsHandlerFn(cdc *codec.Codec, kb keys.Keybase, cliCtx context. return } - msg := staking.NewMsgDelegate(req.DelegatorAddress, req.ValidatorAddress, req.Delegation) + msg := staking.NewMsgDelegate(req.DelegatorAddress, req.ValidatorAddress, req.Amount) if err := msg.ValidateBasic(); err != nil { rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) return @@ -93,7 +93,7 @@ func postDelegationsHandlerFn(cdc *codec.Codec, kb keys.Keybase, cliCtx context. func postRedelegationsHandlerFn(cdc *codec.Codec, kb keys.Keybase, cliCtx context.CLIContext) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { - var req MsgBeginRedelegateInput + var req RedelegateRequest if !rest.ReadRESTReq(w, r, cdc, &req) { return @@ -104,7 +104,7 @@ func postRedelegationsHandlerFn(cdc *codec.Codec, kb keys.Keybase, cliCtx contex return } - msg := staking.NewMsgBeginRedelegate(req.DelegatorAddress, req.ValidatorSrcAddress, req.ValidatorDstAddress, req.SharesAmount) + msg := staking.NewMsgBeginRedelegate(req.DelegatorAddress, req.ValidatorSrcAddress, req.ValidatorDstAddress, req.Amount) if err := msg.ValidateBasic(); err != nil { rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) return @@ -127,7 +127,7 @@ func postRedelegationsHandlerFn(cdc *codec.Codec, kb keys.Keybase, cliCtx contex func postUnbondingDelegationsHandlerFn(cdc *codec.Codec, kb keys.Keybase, cliCtx context.CLIContext) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { - var req MsgUndelegateInput + var req UndelegateRequest if !rest.ReadRESTReq(w, r, cdc, &req) { return @@ -138,7 +138,7 @@ func postUnbondingDelegationsHandlerFn(cdc *codec.Codec, kb keys.Keybase, cliCtx return } - msg := staking.NewMsgUndelegate(req.DelegatorAddress, req.ValidatorAddress, req.SharesAmount) + msg := staking.NewMsgUndelegate(req.DelegatorAddress, req.ValidatorAddress, req.Amount) if err := msg.ValidateBasic(); err != nil { rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) return diff --git a/x/staking/handler.go b/x/staking/handler.go index 2897317eea16..7da83cec2e5b 100644 --- a/x/staking/handler.go +++ b/x/staking/handler.go @@ -209,11 +209,11 @@ func handleMsgDelegate(ctx sdk.Context, msg types.MsgDelegate, k keeper.Keeper) return ErrNoValidatorFound(k.Codespace()).Result() } - if msg.Value.Denom != k.GetParams(ctx).BondDenom { + if msg.Amount.Denom != k.GetParams(ctx).BondDenom { return ErrBadDenom(k.Codespace()).Result() } - _, err := k.Delegate(ctx, msg.DelegatorAddress, msg.Value.Amount, validator, true) + _, err := k.Delegate(ctx, msg.DelegatorAddress, msg.Amount.Amount, validator, true) if err != nil { return err.Result() } @@ -229,7 +229,14 @@ func handleMsgDelegate(ctx sdk.Context, msg types.MsgDelegate, k keeper.Keeper) } func handleMsgUndelegate(ctx sdk.Context, msg types.MsgUndelegate, k keeper.Keeper) sdk.Result { - completionTime, err := k.Undelegate(ctx, msg.DelegatorAddress, msg.ValidatorAddress, msg.SharesAmount) + shares, err := k.ValidateUnbondAmount( + ctx, msg.DelegatorAddress, msg.ValidatorAddress, msg.Amount.Amount, + ) + if err != nil { + return err.Result() + } + + completionTime, err := k.Undelegate(ctx, msg.DelegatorAddress, msg.ValidatorAddress, shares) if err != nil { return err.Result() } @@ -245,8 +252,16 @@ func handleMsgUndelegate(ctx sdk.Context, msg types.MsgUndelegate, k keeper.Keep } func handleMsgBeginRedelegate(ctx sdk.Context, msg types.MsgBeginRedelegate, k keeper.Keeper) sdk.Result { - completionTime, err := k.BeginRedelegation(ctx, msg.DelegatorAddress, msg.ValidatorSrcAddress, - msg.ValidatorDstAddress, msg.SharesAmount) + shares, err := k.ValidateUnbondAmount( + ctx, msg.DelegatorAddress, msg.ValidatorSrcAddress, msg.Amount.Amount, + ) + if err != nil { + return err.Result() + } + + completionTime, err := k.BeginRedelegation( + ctx, msg.DelegatorAddress, msg.ValidatorSrcAddress, msg.ValidatorDstAddress, shares, + ) if err != nil { return err.Result() } diff --git a/x/staking/handler_test.go b/x/staking/handler_test.go index 73e8956372c8..19865aeba19b 100644 --- a/x/staking/handler_test.go +++ b/x/staking/handler_test.go @@ -71,6 +71,7 @@ func TestValidatorByPowerIndex(t *testing.T) { keeper.Slash(ctx, consAddr0, 0, initPower, sdk.NewDecWithPrec(5, 1)) keeper.Jail(ctx, consAddr0) keeper.ApplyAndReturnValidatorSetUpdates(ctx) + validator, found = keeper.GetValidator(ctx, validatorAddr) require.True(t, found) require.Equal(t, sdk.Unbonding, validator.Status) // ensure is unbonding @@ -91,14 +92,18 @@ func TestValidatorByPowerIndex(t *testing.T) { require.Equal(t, power2, power3) // unbond self-delegation - msgUndelegate := NewMsgUndelegate(sdk.AccAddress(validatorAddr), validatorAddr, initBond.ToDec()) + totalBond := validator.TokensFromShares(bond.GetShares()).TruncateInt() + unbondAmt := sdk.NewCoin(sdk.DefaultBondDenom, totalBond) + msgUndelegate := NewMsgUndelegate(sdk.AccAddress(validatorAddr), validatorAddr, unbondAmt) + got = handleMsgUndelegate(ctx, msgUndelegate, keeper) require.True(t, got.IsOK(), "expected msg to be ok, got %v", got) + var finishTime time.Time types.MsgCdc.MustUnmarshalBinaryLengthPrefixed(got.Data, &finishTime) + ctx = ctx.WithBlockTime(finishTime) EndBlocker(ctx, keeper) - EndBlocker(ctx, keeper) // verify that by power key nolonger exists @@ -215,8 +220,8 @@ func TestLegacyValidatorDelegations(t *testing.T) { require.Equal(t, bondAmount.MulRaw(2), validator.BondedTokens()) // unbond validator total self-delegations (which should jail the validator) - unbondShares := sdk.TokensFromTendermintPower(10) - msgUndelegate := NewMsgUndelegate(sdk.AccAddress(valAddr), valAddr, unbondShares.ToDec()) + unbondAmt := sdk.NewCoin(sdk.DefaultBondDenom, bondAmount) + msgUndelegate := NewMsgUndelegate(sdk.AccAddress(valAddr), valAddr, unbondAmt) got = handleMsgUndelegate(ctx, msgUndelegate, keeper) require.True(t, got.IsOK(), "expected begin unbonding validator msg to be ok, got %v", got) @@ -437,8 +442,8 @@ func TestIncrementsMsgUnbond(t *testing.T) { // just send the same msgUnbond multiple times // TODO use decimals here - unbondShares := sdk.NewDec(10) - msgUndelegate := NewMsgUndelegate(delegatorAddr, validatorAddr, unbondShares) + unbondAmt := sdk.NewCoin(sdk.DefaultBondDenom, sdk.NewInt(10)) + msgUndelegate := NewMsgUndelegate(delegatorAddr, validatorAddr, unbondAmt) numUnbonds := int64(5) for i := int64(0); i < numUnbonds; i++ { @@ -455,8 +460,8 @@ func TestIncrementsMsgUnbond(t *testing.T) { bond, found := keeper.GetDelegation(ctx, delegatorAddr, validatorAddr) require.True(t, found) - expBond := initBond.Sub(unbondShares.MulInt64(i + 1).RoundInt()) - expDelegatorShares := (initBond.MulRaw(2)).Sub(unbondShares.MulInt64(i + 1).RoundInt()) + expBond := initBond.Sub(unbondAmt.Amount.Mul(sdk.NewInt(i + 1))) + expDelegatorShares := initBond.MulRaw(2).Sub(unbondAmt.Amount.Mul(sdk.NewInt(i + 1))) expDelegatorAcc := initBond.Sub(expBond) gotBond := bond.Shares.RoundInt() @@ -482,28 +487,22 @@ func TestIncrementsMsgUnbond(t *testing.T) { sdk.TokensFromTendermintPower(1 << 31), initBond, } + for i, c := range errorCases { - unbondShares := c.ToDec() - msgUndelegate := NewMsgUndelegate(delegatorAddr, validatorAddr, unbondShares) + unbondAmt := sdk.NewCoin(sdk.DefaultBondDenom, c) + msgUndelegate := NewMsgUndelegate(delegatorAddr, validatorAddr, unbondAmt) got = handleMsgUndelegate(ctx, msgUndelegate, keeper) require.False(t, got.IsOK(), "expected unbond msg to fail, index: %v", i) } - leftBonded := initBond.Sub(unbondShares.MulInt64(numUnbonds).RoundInt()) + leftBonded := initBond.Sub(unbondAmt.Amount.Mul(sdk.NewInt(numUnbonds))) - // should be unable to unbond one more than we have - unbondShares = leftBonded.AddRaw(1).ToDec() - msgUndelegate = NewMsgUndelegate(delegatorAddr, validatorAddr, unbondShares) - got = handleMsgUndelegate(ctx, msgUndelegate, keeper) - require.False(t, got.IsOK(), - "got: %v\nmsgUnbond: %v\nshares: %v\nleftBonded: %v\n", got, msgUndelegate, unbondShares.String(), leftBonded) - - // should be able to unbond just what we have - unbondShares = leftBonded.ToDec() - msgUndelegate = NewMsgUndelegate(delegatorAddr, validatorAddr, unbondShares) + // should be able to unbond remaining + unbondAmt = sdk.NewCoin(sdk.DefaultBondDenom, leftBonded) + msgUndelegate = NewMsgUndelegate(delegatorAddr, validatorAddr, unbondAmt) got = handleMsgUndelegate(ctx, msgUndelegate, keeper) require.True(t, got.IsOK(), - "got: %v\nmsgUnbond: %v\nshares: %v\nleftBonded: %v\n", got, msgUndelegate, unbondShares, leftBonded) + "got: %v\nmsgUnbond: %v\nshares: %s\nleftBonded: %s\n", got.Log, msgUndelegate, unbondAmt, leftBonded) } func TestMultipleMsgCreateValidator(t *testing.T) { @@ -548,14 +547,18 @@ func TestMultipleMsgCreateValidator(t *testing.T) { for i, validatorAddr := range validatorAddrs { _, found := keeper.GetValidator(ctx, validatorAddr) require.True(t, found) - unbondingTokens := sdk.TokensFromTendermintPower(10) - msgUndelegate := NewMsgUndelegate(delegatorAddrs[i], validatorAddr, unbondingTokens.ToDec()) // remove delegation + + unbondAmt := sdk.NewCoin(sdk.DefaultBondDenom, sdk.TokensFromTendermintPower(10)) + msgUndelegate := NewMsgUndelegate(delegatorAddrs[i], validatorAddr, unbondAmt) // remove delegation got := handleMsgUndelegate(ctx, msgUndelegate, keeper) + require.True(t, got.IsOK(), "expected msg %d to be ok, got %v", i, got) var finishTime time.Time + // Jump to finishTime for unbonding period and remove from unbonding queue types.MsgCdc.MustUnmarshalBinaryLengthPrefixed(got.Data, &finishTime) ctx = ctx.WithBlockTime(finishTime) + EndBlocker(ctx, keeper) // Check that the validator is deleted from state @@ -576,7 +579,7 @@ func TestMultipleMsgDelegate(t *testing.T) { validatorAddr, delegatorAddrs := sdk.ValAddress(keep.Addrs[0]), keep.Addrs[1:] _ = setInstantUnbondPeriod(keeper, ctx) - //first make a validator + // first make a validator msgCreateValidator := NewTestMsgCreateValidator(validatorAddr, keep.PKs[0], sdk.NewInt(10)) got := handleMsgCreateValidator(ctx, msgCreateValidator, keeper) require.True(t, got.IsOK(), "expected msg to be ok, got %v", got) @@ -587,7 +590,7 @@ func TestMultipleMsgDelegate(t *testing.T) { got := handleMsgDelegate(ctx, msgDelegate, keeper) require.True(t, got.IsOK(), "expected msg %d to be ok, got %v", i, got) - //Check that the account is bonded + // check that the account is bonded bond, found := keeper.GetDelegation(ctx, delegatorAddr, validatorAddr) require.True(t, found) require.NotNil(t, bond, "expected delegatee bond %d to exist", bond) @@ -595,15 +598,19 @@ func TestMultipleMsgDelegate(t *testing.T) { // unbond them all for i, delegatorAddr := range delegatorAddrs { - msgUndelegate := NewMsgUndelegate(delegatorAddr, validatorAddr, sdk.NewDec(10)) + unbondAmt := sdk.NewCoin(sdk.DefaultBondDenom, sdk.NewInt(10)) + msgUndelegate := NewMsgUndelegate(delegatorAddr, validatorAddr, unbondAmt) + got := handleMsgUndelegate(ctx, msgUndelegate, keeper) require.True(t, got.IsOK(), "expected msg %d to be ok, got %v", i, got) + var finishTime time.Time types.MsgCdc.MustUnmarshalBinaryLengthPrefixed(got.Data, &finishTime) + ctx = ctx.WithBlockTime(finishTime) EndBlocker(ctx, keeper) - //Check that the account is unbonded + // check that the account is unbonded _, found := keeper.GetDelegation(ctx, delegatorAddr, validatorAddr) require.False(t, found) } @@ -625,11 +632,14 @@ func TestJailValidator(t *testing.T) { require.True(t, got.IsOK(), "expected ok, got %v", got) // unbond the validators bond portion - msgUndelegateValidator := NewMsgUndelegate(sdk.AccAddress(validatorAddr), validatorAddr, sdk.NewDec(10)) + unbondAmt := sdk.NewCoin(sdk.DefaultBondDenom, sdk.NewInt(10)) + msgUndelegateValidator := NewMsgUndelegate(sdk.AccAddress(validatorAddr), validatorAddr, unbondAmt) got = handleMsgUndelegate(ctx, msgUndelegateValidator, keeper) require.True(t, got.IsOK(), "expected no error: %v", got) + var finishTime time.Time types.MsgCdc.MustUnmarshalBinaryLengthPrefixed(got.Data, &finishTime) + ctx = ctx.WithBlockTime(finishTime) EndBlocker(ctx, keeper) @@ -638,10 +648,12 @@ func TestJailValidator(t *testing.T) { require.True(t, validator.Jailed, "%v", validator) // test that the delegator can still withdraw their bonds - msgUndelegateDelegator := NewMsgUndelegate(delegatorAddr, validatorAddr, sdk.NewDec(10)) + msgUndelegateDelegator := NewMsgUndelegate(delegatorAddr, validatorAddr, unbondAmt) + got = handleMsgUndelegate(ctx, msgUndelegateDelegator, keeper) require.True(t, got.IsOK(), "expected no error") types.MsgCdc.MustUnmarshalBinaryLengthPrefixed(got.Data, &finishTime) + ctx = ctx.WithBlockTime(finishTime) EndBlocker(ctx, keeper) @@ -674,14 +686,17 @@ func TestValidatorQueue(t *testing.T) { EndBlocker(ctx, keeper) // unbond the all self-delegation to put validator in unbonding state - msgUndelegateValidator := NewMsgUndelegate(sdk.AccAddress(validatorAddr), - validatorAddr, delTokens.ToDec()) + unbondAmt := sdk.NewCoin(sdk.DefaultBondDenom, delTokens) + msgUndelegateValidator := NewMsgUndelegate(sdk.AccAddress(validatorAddr), validatorAddr, unbondAmt) got = handleMsgUndelegate(ctx, msgUndelegateValidator, keeper) require.True(t, got.IsOK(), "expected no error: %v", got) + var finishTime time.Time types.MsgCdc.MustUnmarshalBinaryLengthPrefixed(got.Data, &finishTime) + ctx = ctx.WithBlockTime(finishTime) EndBlocker(ctx, keeper) + origHeader := ctx.BlockHeader() validator, found := keeper.GetValidator(ctx, validatorAddr) @@ -691,6 +706,7 @@ func TestValidatorQueue(t *testing.T) { // should still be unbonding at time 6 seconds later ctx = ctx.WithBlockTime(origHeader.Time.Add(time.Second * 6)) EndBlocker(ctx, keeper) + validator, found = keeper.GetValidator(ctx, validatorAddr) require.True(t, found) require.True(t, validator.GetStatus() == sdk.Unbonding, "%v", validator) @@ -698,6 +714,7 @@ func TestValidatorQueue(t *testing.T) { // should be in unbonded state at time 7 seconds later ctx = ctx.WithBlockTime(origHeader.Time.Add(time.Second * 7)) EndBlocker(ctx, keeper) + validator, found = keeper.GetValidator(ctx, validatorAddr) require.True(t, found) require.True(t, validator.GetStatus() == sdk.Unbonded, "%v", validator) @@ -721,11 +738,11 @@ func TestUnbondingPeriod(t *testing.T) { EndBlocker(ctx, keeper) // begin unbonding - unbondingTokens := sdk.TokensFromTendermintPower(10) - msgUndelegate := NewMsgUndelegate(sdk.AccAddress(validatorAddr), - validatorAddr, unbondingTokens.ToDec()) + unbondAmt := sdk.NewCoin(sdk.DefaultBondDenom, sdk.TokensFromTendermintPower(10)) + msgUndelegate := NewMsgUndelegate(sdk.AccAddress(validatorAddr), validatorAddr, unbondAmt) got = handleMsgUndelegate(ctx, msgUndelegate, keeper) require.True(t, got.IsOK(), "expected no error") + origHeader := ctx.BlockHeader() _, found := keeper.GetUnbondingDelegation(ctx, sdk.AccAddress(validatorAddr), validatorAddr) @@ -764,7 +781,8 @@ func TestUnbondingFromUnbondingValidator(t *testing.T) { require.True(t, got.IsOK(), "expected ok, got %v", got) // unbond the validators bond portion - msgUndelegateValidator := NewMsgUndelegate(sdk.AccAddress(validatorAddr), validatorAddr, sdk.NewDec(10)) + unbondAmt := sdk.NewCoin(sdk.DefaultBondDenom, sdk.NewInt(10)) + msgUndelegateValidator := NewMsgUndelegate(sdk.AccAddress(validatorAddr), validatorAddr, unbondAmt) got = handleMsgUndelegate(ctx, msgUndelegateValidator, keeper) require.True(t, got.IsOK(), "expected no error") @@ -774,7 +792,7 @@ func TestUnbondingFromUnbondingValidator(t *testing.T) { ctx = ctx.WithBlockTime(finishTime.Add(time.Second * -1)) // unbond the delegator from the validator - msgUndelegateDelegator := NewMsgUndelegate(delegatorAddr, validatorAddr, sdk.NewDec(10)) + msgUndelegateDelegator := NewMsgUndelegate(delegatorAddr, validatorAddr, unbondAmt) got = handleMsgUndelegate(ctx, msgUndelegateDelegator, keeper) require.True(t, got.IsOK(), "expected no error") @@ -820,7 +838,8 @@ func TestRedelegationPeriod(t *testing.T) { bal1 := AccMapper.GetAccount(ctx, sdk.AccAddress(validatorAddr)).GetCoins() // begin redelegate - msgBeginRedelegate := NewMsgBeginRedelegate(sdk.AccAddress(validatorAddr), validatorAddr, validatorAddr2, sdk.NewDec(10)) + redAmt := sdk.NewCoin(sdk.DefaultBondDenom, sdk.NewInt(10)) + msgBeginRedelegate := NewMsgBeginRedelegate(sdk.AccAddress(validatorAddr), validatorAddr, validatorAddr2, redAmt) got = handleMsgBeginRedelegate(ctx, msgBeginRedelegate, keeper) require.True(t, got.IsOK(), "expected no error, %v", got) @@ -873,12 +892,13 @@ func TestTransitiveRedelegation(t *testing.T) { require.True(t, got.IsOK(), "expected no error on runMsgCreateValidator") // begin redelegate - msgBeginRedelegate := NewMsgBeginRedelegate(sdk.AccAddress(validatorAddr), validatorAddr, validatorAddr2, sdk.NewDec(10)) + redAmt := sdk.NewCoin(sdk.DefaultBondDenom, sdk.NewInt(10)) + msgBeginRedelegate := NewMsgBeginRedelegate(sdk.AccAddress(validatorAddr), validatorAddr, validatorAddr2, redAmt) got = handleMsgBeginRedelegate(ctx, msgBeginRedelegate, keeper) require.True(t, got.IsOK(), "expected no error, %v", got) // cannot redelegation to next validator while first delegation exists - msgBeginRedelegate = NewMsgBeginRedelegate(sdk.AccAddress(validatorAddr), validatorAddr2, validatorAddr3, sdk.NewDec(10)) + msgBeginRedelegate = NewMsgBeginRedelegate(sdk.AccAddress(validatorAddr), validatorAddr2, validatorAddr3, redAmt) got = handleMsgBeginRedelegate(ctx, msgBeginRedelegate, keeper) require.True(t, !got.IsOK(), "expected an error, msg: %v", msgBeginRedelegate) @@ -915,8 +935,8 @@ func TestMultipleRedelegationAtSameTime(t *testing.T) { // begin a redelegate selfDelAddr := sdk.AccAddress(valAddr) // (the validator is it's own delegator) - msgBeginRedelegate := NewMsgBeginRedelegate(selfDelAddr, - valAddr, valAddr2, valTokens.QuoRaw(2).ToDec()) + redAmt := sdk.NewCoin(sdk.DefaultBondDenom, valTokens.QuoRaw(2)) + msgBeginRedelegate := NewMsgBeginRedelegate(selfDelAddr, valAddr, valAddr2, redAmt) got = handleMsgBeginRedelegate(ctx, msgBeginRedelegate, keeper) require.True(t, got.IsOK(), "expected no error, %v", got) @@ -967,8 +987,8 @@ func TestMultipleRedelegationAtUniqueTimes(t *testing.T) { // begin a redelegate selfDelAddr := sdk.AccAddress(valAddr) // (the validator is it's own delegator) - msgBeginRedelegate := NewMsgBeginRedelegate(selfDelAddr, - valAddr, valAddr2, valTokens.QuoRaw(2).ToDec()) + redAmt := sdk.NewCoin(sdk.DefaultBondDenom, valTokens.QuoRaw(2)) + msgBeginRedelegate := NewMsgBeginRedelegate(selfDelAddr, valAddr, valAddr2, redAmt) got = handleMsgBeginRedelegate(ctx, msgBeginRedelegate, keeper) require.True(t, got.IsOK(), "expected no error, %v", got) @@ -1016,7 +1036,8 @@ func TestMultipleUnbondingDelegationAtSameTime(t *testing.T) { // begin an unbonding delegation selfDelAddr := sdk.AccAddress(valAddr) // (the validator is it's own delegator) - msgUndelegate := NewMsgUndelegate(selfDelAddr, valAddr, valTokens.QuoRaw(2).ToDec()) + unbondAmt := sdk.NewCoin(sdk.DefaultBondDenom, valTokens.QuoRaw(2)) + msgUndelegate := NewMsgUndelegate(selfDelAddr, valAddr, unbondAmt) got = handleMsgUndelegate(ctx, msgUndelegate, keeper) require.True(t, got.IsOK(), "expected no error, %v", got) @@ -1062,7 +1083,8 @@ func TestMultipleUnbondingDelegationAtUniqueTimes(t *testing.T) { // begin an unbonding delegation selfDelAddr := sdk.AccAddress(valAddr) // (the validator is it's own delegator) - msgUndelegate := NewMsgUndelegate(selfDelAddr, valAddr, valTokens.QuoRaw(2).ToDec()) + unbondAmt := sdk.NewCoin(sdk.DefaultBondDenom, valTokens.QuoRaw(2)) + msgUndelegate := NewMsgUndelegate(selfDelAddr, valAddr, unbondAmt) got = handleMsgUndelegate(ctx, msgUndelegate, keeper) require.True(t, got.IsOK(), "expected no error, %v", got) @@ -1132,8 +1154,9 @@ func TestUnbondingWhenExcessValidators(t *testing.T) { keeper.ApplyAndReturnValidatorSetUpdates(ctx) require.Equal(t, 2, len(keeper.GetLastValidators(ctx))) - // unbond the valdator-2 - msgUndelegate := NewMsgUndelegate(sdk.AccAddress(validatorAddr2), validatorAddr2, valTokens2.ToDec()) + // unbond the validator-2 + unbondAmt := sdk.NewCoin(sdk.DefaultBondDenom, valTokens2) + msgUndelegate := NewMsgUndelegate(sdk.AccAddress(validatorAddr2), validatorAddr2, unbondAmt) got = handleMsgUndelegate(ctx, msgUndelegate, keeper) require.True(t, got.IsOK(), "expected no error on runMsgUndelegate") @@ -1177,21 +1200,21 @@ func TestBondUnbondRedelegateSlashTwice(t *testing.T) { ctx = ctx.WithBlockHeight(1) // begin unbonding 4 stake - ubdTokens := sdk.TokensFromTendermintPower(4) - msgUndelegate := NewMsgUndelegate(del, valA, ubdTokens.ToDec()) + unbondAmt := sdk.NewCoin(sdk.DefaultBondDenom, sdk.TokensFromTendermintPower(4)) + msgUndelegate := NewMsgUndelegate(del, valA, unbondAmt) got = handleMsgUndelegate(ctx, msgUndelegate, keeper) require.True(t, got.IsOK(), "expected no error on runMsgUndelegate") // begin redelegate 6 stake - rdTokens := sdk.TokensFromTendermintPower(6) - msgBeginRedelegate := NewMsgBeginRedelegate(del, valA, valB, rdTokens.ToDec()) + redAmt := sdk.NewCoin(sdk.DefaultBondDenom, sdk.TokensFromTendermintPower(6)) + msgBeginRedelegate := NewMsgBeginRedelegate(del, valA, valB, redAmt) got = handleMsgBeginRedelegate(ctx, msgBeginRedelegate, keeper) require.True(t, got.IsOK(), "expected no error on runMsgBeginRedelegate") // destination delegation should have 6 shares delegation, found := keeper.GetDelegation(ctx, del, valB) require.True(t, found) - require.Equal(t, rdTokens.ToDec(), delegation.Shares) + require.Equal(t, sdk.NewDecFromInt(redAmt.Amount), delegation.Shares) // must apply validator updates updates = keeper.ApplyAndReturnValidatorSetUpdates(ctx) @@ -1204,7 +1227,7 @@ func TestBondUnbondRedelegateSlashTwice(t *testing.T) { ubd, found := keeper.GetUnbondingDelegation(ctx, del, valA) require.True(t, found) require.Len(t, ubd.Entries, 1) - require.Equal(t, ubdTokens.QuoRaw(2), ubd.Entries[0].Balance) + require.Equal(t, unbondAmt.Amount.QuoRaw(2), ubd.Entries[0].Balance) // redelegation should have been slashed by half redelegation, found := keeper.GetRedelegation(ctx, del, valA, valB) @@ -1214,7 +1237,7 @@ func TestBondUnbondRedelegateSlashTwice(t *testing.T) { // destination delegation should have been slashed by half delegation, found = keeper.GetDelegation(ctx, del, valB) require.True(t, found) - require.Equal(t, rdTokens.QuoRaw(2).ToDec(), delegation.Shares) + require.Equal(t, sdk.NewDecFromInt(redAmt.Amount.QuoRaw(2)), delegation.Shares) // validator power should have been reduced by half validator, found := keeper.GetValidator(ctx, valA) @@ -1229,7 +1252,7 @@ func TestBondUnbondRedelegateSlashTwice(t *testing.T) { ubd, found = keeper.GetUnbondingDelegation(ctx, del, valA) require.True(t, found) require.Len(t, ubd.Entries, 1) - require.Equal(t, ubdTokens.QuoRaw(2), ubd.Entries[0].Balance) + require.Equal(t, unbondAmt.Amount.QuoRaw(2), ubd.Entries[0].Balance) // redelegation should be unchanged redelegation, found = keeper.GetRedelegation(ctx, del, valA, valB) @@ -1239,7 +1262,7 @@ func TestBondUnbondRedelegateSlashTwice(t *testing.T) { // destination delegation should be unchanged delegation, found = keeper.GetDelegation(ctx, del, valB) require.True(t, found) - require.Equal(t, rdTokens.QuoRaw(2).ToDec(), delegation.Shares) + require.Equal(t, sdk.NewDecFromInt(redAmt.Amount.QuoRaw(2)), delegation.Shares) // end blocker EndBlocker(ctx, keeper) diff --git a/x/staking/keeper/delegation.go b/x/staking/keeper/delegation.go index 86adf577cb8a..f13c2b40fda2 100644 --- a/x/staking/keeper/delegation.go +++ b/x/staking/keeper/delegation.go @@ -518,7 +518,7 @@ func (k Keeper) unbond(ctx sdk.Context, delAddr sdk.AccAddress, valAddr sdk.ValA // if the delegation is the operator of the validator and undelegating will decrease the validator's self delegation below their minimum // trigger a jail validator if isValidatorOperator && !validator.Jailed && - validator.ShareTokens(delegation.Shares).TruncateInt().LT(validator.MinSelfDelegation) { + validator.TokensFromShares(delegation.Shares).TruncateInt().LT(validator.MinSelfDelegation) { k.jailValidator(ctx, validator) validator = k.mustGetValidator(ctx, validator.OperatorAddress) @@ -726,3 +726,46 @@ func (k Keeper) CompleteRedelegation(ctx sdk.Context, delAddr sdk.AccAddress, return nil } + +// ValidateUnbondAmount validates that a given unbond or redelegation amount is +// valied based on upon the converted shares. If the amount is valid, the total +// amount of respective shares is returned, otherwise an error is returned. +func (k Keeper) ValidateUnbondAmount( + ctx sdk.Context, delAddr sdk.AccAddress, valAddr sdk.ValAddress, amt sdk.Int, +) (shares sdk.Dec, err sdk.Error) { + + validator, found := k.GetValidator(ctx, valAddr) + if !found { + return shares, types.ErrNoValidatorFound(k.Codespace()) + } + + del, found := k.GetDelegation(ctx, delAddr, valAddr) + if !found { + return shares, types.ErrNoDelegation(k.Codespace()) + } + + shares, err = validator.SharesFromTokens(amt) + if err != nil { + return shares, err + } + + sharesTruncated, err := validator.SharesFromTokensTruncated(amt) + if err != nil { + return shares, err + } + + delShares := del.GetShares() + if sharesTruncated.GT(delShares) { + return shares, types.ErrBadSharesAmount(k.Codespace()) + } + + // Cap the shares at the delegation's shares. Shares being greater could occur + // due to rounding, however we don't want to truncate the shares or take the + // minimum because we want to allow for the full withdraw of shares from a + // delegation. + if shares.GT(delShares) { + shares = delShares + } + + return shares, nil +} diff --git a/x/staking/simulation/msgs.go b/x/staking/simulation/msgs.go index 05241dbdccab..c7268afb21ab 100644 --- a/x/staking/simulation/msgs.go +++ b/x/staking/simulation/msgs.go @@ -148,24 +148,31 @@ func SimulateMsgUndelegate(m auth.AccountKeeper, k staking.Keeper) simulation.Op } delegation := delegations[r.Intn(len(delegations))] - numShares := simulation.RandomDecAmount(r, delegation.Shares) - if numShares.Equal(sdk.ZeroDec()) { + validator, found := k.GetValidator(ctx, delegation.GetValidatorAddr()) + if !found { return simulation.NoOpMsg(), nil, nil } - msg := staking.MsgUndelegate{ - DelegatorAddress: delegatorAddress, - ValidatorAddress: delegation.ValidatorAddress, - SharesAmount: numShares, + + totalBond := validator.TokensFromShares(delegation.GetShares()).TruncateInt() + unbondAmt := simulation.RandomAmount(r, totalBond) + if unbondAmt.Equal(sdk.ZeroInt()) { + return simulation.NoOpMsg(), nil, nil } + + msg := staking.NewMsgDelegate( + delegatorAddress, delegation.ValidatorAddress, sdk.NewCoin(k.GetParams(ctx).BondDenom, unbondAmt), + ) if msg.ValidateBasic() != nil { return simulation.NoOpMsg(), nil, fmt.Errorf("expected msg to pass ValidateBasic: %s, got error %v", msg.GetSignBytes(), msg.ValidateBasic()) } + ctx, write := ctx.CacheContext() ok := handler(ctx, msg).IsOK() if ok { write() } + opMsg = simulation.NewOperationMsg(msg, ok, "") return opMsg, nil, nil } @@ -195,20 +202,20 @@ func SimulateMsgBeginRedelegate(m auth.AccountKeeper, k staking.Keeper) simulati if amount.Equal(sdk.ZeroInt()) { return simulation.NoOpMsg(), nil, nil } - msg := staking.MsgBeginRedelegate{ - DelegatorAddress: delegatorAddress, - ValidatorSrcAddress: srcValidatorAddress, - ValidatorDstAddress: destValidatorAddress, - SharesAmount: amount.ToDec(), - } + + msg := staking.NewMsgBeginRedelegate( + delegatorAddress, srcValidatorAddress, destValidatorAddress, sdk.NewCoin(denom, amount), + ) if msg.ValidateBasic() != nil { return simulation.NoOpMsg(), nil, fmt.Errorf("expected msg to pass ValidateBasic: %s", msg.GetSignBytes()) } + ctx, write := ctx.CacheContext() ok := handler(ctx, msg).IsOK() if ok { write() } + opMsg = simulation.NewOperationMsg(msg, ok, "") return opMsg, nil, nil } diff --git a/x/staking/types/msg.go b/x/staking/types/msg.go index f9c2eea77f5b..9bf839fa2443 100644 --- a/x/staking/types/msg.go +++ b/x/staking/types/msg.go @@ -212,14 +212,14 @@ func (msg MsgEditValidator) ValidateBasic() sdk.Error { type MsgDelegate struct { DelegatorAddress sdk.AccAddress `json:"delegator_address"` ValidatorAddress sdk.ValAddress `json:"validator_address"` - Value sdk.Coin `json:"value"` + Amount sdk.Coin `json:"amount"` } -func NewMsgDelegate(delAddr sdk.AccAddress, valAddr sdk.ValAddress, value sdk.Coin) MsgDelegate { +func NewMsgDelegate(delAddr sdk.AccAddress, valAddr sdk.ValAddress, amount sdk.Coin) MsgDelegate { return MsgDelegate{ DelegatorAddress: delAddr, ValidatorAddress: valAddr, - Value: value, + Amount: amount, } } @@ -244,7 +244,7 @@ func (msg MsgDelegate) ValidateBasic() sdk.Error { if msg.ValidatorAddress.Empty() { return ErrNilValidatorAddr(DefaultCodespace) } - if msg.Value.Amount.LTE(sdk.ZeroInt()) { + if msg.Amount.Amount.LTE(sdk.ZeroInt()) { return ErrBadDelegationAmount(DefaultCodespace) } return nil @@ -257,17 +257,17 @@ type MsgBeginRedelegate struct { DelegatorAddress sdk.AccAddress `json:"delegator_address"` ValidatorSrcAddress sdk.ValAddress `json:"validator_src_address"` ValidatorDstAddress sdk.ValAddress `json:"validator_dst_address"` - SharesAmount sdk.Dec `json:"shares_amount"` + Amount sdk.Coin `json:"amount"` } func NewMsgBeginRedelegate(delAddr sdk.AccAddress, valSrcAddr, - valDstAddr sdk.ValAddress, sharesAmount sdk.Dec) MsgBeginRedelegate { + valDstAddr sdk.ValAddress, amount sdk.Coin) MsgBeginRedelegate { return MsgBeginRedelegate{ DelegatorAddress: delAddr, ValidatorSrcAddress: valSrcAddr, ValidatorDstAddress: valDstAddr, - SharesAmount: sharesAmount, + Amount: amount, } } @@ -295,7 +295,7 @@ func (msg MsgBeginRedelegate) ValidateBasic() sdk.Error { if msg.ValidatorDstAddress.Empty() { return ErrNilValidatorAddr(DefaultCodespace) } - if msg.SharesAmount.LTE(sdk.ZeroDec()) { + if msg.Amount.Amount.LTE(sdk.ZeroInt()) { return ErrBadSharesAmount(DefaultCodespace) } return nil @@ -305,14 +305,14 @@ func (msg MsgBeginRedelegate) ValidateBasic() sdk.Error { type MsgUndelegate struct { DelegatorAddress sdk.AccAddress `json:"delegator_address"` ValidatorAddress sdk.ValAddress `json:"validator_address"` - SharesAmount sdk.Dec `json:"shares_amount"` + Amount sdk.Coin `json:"amount"` } -func NewMsgUndelegate(delAddr sdk.AccAddress, valAddr sdk.ValAddress, sharesAmount sdk.Dec) MsgUndelegate { +func NewMsgUndelegate(delAddr sdk.AccAddress, valAddr sdk.ValAddress, amount sdk.Coin) MsgUndelegate { return MsgUndelegate{ DelegatorAddress: delAddr, ValidatorAddress: valAddr, - SharesAmount: sharesAmount, + Amount: amount, } } @@ -335,7 +335,7 @@ func (msg MsgUndelegate) ValidateBasic() sdk.Error { if msg.ValidatorAddress.Empty() { return ErrNilValidatorAddr(DefaultCodespace) } - if msg.SharesAmount.LTE(sdk.ZeroDec()) { + if msg.Amount.Amount.LTE(sdk.ZeroInt()) { return ErrBadSharesAmount(DefaultCodespace) } return nil diff --git a/x/staking/types/msg_test.go b/x/staking/types/msg_test.go index a2a60a44e709..da0cdae002b9 100644 --- a/x/staking/types/msg_test.go +++ b/x/staking/types/msg_test.go @@ -110,19 +110,18 @@ func TestMsgBeginRedelegate(t *testing.T) { delegatorAddr sdk.AccAddress validatorSrcAddr sdk.ValAddress validatorDstAddr sdk.ValAddress - sharesAmount sdk.Dec + amount sdk.Coin expectPass bool }{ - {"regular", sdk.AccAddress(addr1), addr2, addr3, sdk.NewDecWithPrec(1, 1), true}, - {"negative decimal", sdk.AccAddress(addr1), addr2, addr3, sdk.NewDecWithPrec(-1, 1), false}, - {"zero amount", sdk.AccAddress(addr1), addr2, addr3, sdk.ZeroDec(), false}, - {"empty delegator", sdk.AccAddress(emptyAddr), addr1, addr3, sdk.NewDecWithPrec(1, 1), false}, - {"empty source validator", sdk.AccAddress(addr1), emptyAddr, addr3, sdk.NewDecWithPrec(1, 1), false}, - {"empty destination validator", sdk.AccAddress(addr1), addr2, emptyAddr, sdk.NewDecWithPrec(1, 1), false}, + {"regular", sdk.AccAddress(addr1), addr2, addr3, sdk.NewInt64Coin(sdk.DefaultBondDenom, 1), true}, + {"zero amount", sdk.AccAddress(addr1), addr2, addr3, sdk.NewInt64Coin(sdk.DefaultBondDenom, 0), false}, + {"empty delegator", sdk.AccAddress(emptyAddr), addr1, addr3, sdk.NewInt64Coin(sdk.DefaultBondDenom, 1), false}, + {"empty source validator", sdk.AccAddress(addr1), emptyAddr, addr3, sdk.NewInt64Coin(sdk.DefaultBondDenom, 1), false}, + {"empty destination validator", sdk.AccAddress(addr1), addr2, emptyAddr, sdk.NewInt64Coin(sdk.DefaultBondDenom, 1), false}, } for _, tc := range tests { - msg := NewMsgBeginRedelegate(tc.delegatorAddr, tc.validatorSrcAddr, tc.validatorDstAddr, tc.sharesAmount) + msg := NewMsgBeginRedelegate(tc.delegatorAddr, tc.validatorSrcAddr, tc.validatorDstAddr, tc.amount) if tc.expectPass { require.Nil(t, msg.ValidateBasic(), "test: %v", tc.name) } else { @@ -137,18 +136,17 @@ func TestMsgUndelegate(t *testing.T) { name string delegatorAddr sdk.AccAddress validatorAddr sdk.ValAddress - sharesAmount sdk.Dec + amount sdk.Coin expectPass bool }{ - {"regular", sdk.AccAddress(addr1), addr2, sdk.NewDecWithPrec(1, 1), true}, - {"negative decimal", sdk.AccAddress(addr1), addr2, sdk.NewDecWithPrec(-1, 1), false}, - {"zero amount", sdk.AccAddress(addr1), addr2, sdk.ZeroDec(), false}, - {"empty delegator", sdk.AccAddress(emptyAddr), addr1, sdk.NewDecWithPrec(1, 1), false}, - {"empty validator", sdk.AccAddress(addr1), emptyAddr, sdk.NewDecWithPrec(1, 1), false}, + {"regular", sdk.AccAddress(addr1), addr2, sdk.NewInt64Coin(sdk.DefaultBondDenom, 1), true}, + {"zero amount", sdk.AccAddress(addr1), addr2, sdk.NewInt64Coin(sdk.DefaultBondDenom, 0), false}, + {"empty delegator", sdk.AccAddress(emptyAddr), addr1, sdk.NewInt64Coin(sdk.DefaultBondDenom, 1), false}, + {"empty validator", sdk.AccAddress(addr1), emptyAddr, sdk.NewInt64Coin(sdk.DefaultBondDenom, 1), false}, } for _, tc := range tests { - msg := NewMsgUndelegate(tc.delegatorAddr, tc.validatorAddr, tc.sharesAmount) + msg := NewMsgUndelegate(tc.delegatorAddr, tc.validatorAddr, tc.amount) if tc.expectPass { require.Nil(t, msg.ValidateBasic(), "test: %v", tc.name) } else { diff --git a/x/staking/types/validator.go b/x/staking/types/validator.go index 730d43073e29..b26c8af7186e 100644 --- a/x/staking/types/validator.go +++ b/x/staking/types/validator.go @@ -354,7 +354,12 @@ func (v Validator) AddTokensFromDel(pool Pool, amount sdk.Int) (Validator, Pool, // the first delegation to a validator sets the exchange rate to one issuedShares = amount.ToDec() } else { - issuedShares = v.DelegatorShares.MulInt(amount).QuoInt(v.Tokens) + shares, err := v.SharesFromTokens(amount) + if err != nil { + panic(err) + } + + issuedShares = shares } if v.Status == sdk.Bonded { @@ -384,7 +389,7 @@ func (v Validator) RemoveDelShares(pool Pool, delShares sdk.Dec) (Validator, Poo // leave excess tokens in the validator // however fully use all the delegator shares - issuedTokens = v.ShareTokens(delShares).TruncateInt() + issuedTokens = v.TokensFromShares(delShares).TruncateInt() v.Tokens = v.Tokens.Sub(issuedTokens) if v.Tokens.IsNegative() { panic("attempting to remove more tokens than available in validator") @@ -407,15 +412,35 @@ func (v Validator) InvalidExRate() bool { } // calculate the token worth of provided shares -func (v Validator) ShareTokens(shares sdk.Dec) sdk.Dec { +func (v Validator) TokensFromShares(shares sdk.Dec) sdk.Dec { return (shares.MulInt(v.Tokens)).Quo(v.DelegatorShares) } // calculate the token worth of provided shares, truncated -func (v Validator) ShareTokensTruncated(shares sdk.Dec) sdk.Dec { +func (v Validator) TokensFromSharesTruncated(shares sdk.Dec) sdk.Dec { return (shares.MulInt(v.Tokens)).QuoTruncate(v.DelegatorShares) } +// SharesFromTokens returns the shares of a delegation given a bond amount. It +// returns an error if the validator has no tokens. +func (v Validator) SharesFromTokens(amt sdk.Int) (sdk.Dec, sdk.Error) { + if v.Tokens.IsZero() { + return sdk.ZeroDec(), ErrInsufficientShares(DefaultCodespace) + } + + return v.GetDelegatorShares().MulInt(amt).QuoInt(v.GetTokens()), nil +} + +// SharesFromTokensTruncated returns the truncated shares of a delegation given +// a bond amount. It returns an error if the validator has no tokens. +func (v Validator) SharesFromTokensTruncated(amt sdk.Int) (sdk.Dec, sdk.Error) { + if v.Tokens.IsZero() { + return sdk.ZeroDec(), ErrInsufficientShares(DefaultCodespace) + } + + return v.GetDelegatorShares().MulInt(amt).QuoTruncate(v.GetTokens().ToDec()), nil +} + // get the bonded tokens which the validator holds func (v Validator) BondedTokens() sdk.Int { if v.Status == sdk.Bonded { diff --git a/x/staking/types/validator_test.go b/x/staking/types/validator_test.go index b8b1fb5c7f57..bd90427e7e3f 100644 --- a/x/staking/types/validator_test.go +++ b/x/staking/types/validator_test.go @@ -77,11 +77,11 @@ func TestShareTokens(t *testing.T) { Tokens: sdk.NewInt(100), DelegatorShares: sdk.NewDec(100), } - assert.True(sdk.DecEq(t, sdk.NewDec(50), validator.ShareTokens(sdk.NewDec(50)))) + assert.True(sdk.DecEq(t, sdk.NewDec(50), validator.TokensFromShares(sdk.NewDec(50)))) validator.Tokens = sdk.NewInt(50) - assert.True(sdk.DecEq(t, sdk.NewDec(25), validator.ShareTokens(sdk.NewDec(50)))) - assert.True(sdk.DecEq(t, sdk.NewDec(5), validator.ShareTokens(sdk.NewDec(10)))) + assert.True(sdk.DecEq(t, sdk.NewDec(25), validator.TokensFromShares(sdk.NewDec(50)))) + assert.True(sdk.DecEq(t, sdk.NewDec(5), validator.TokensFromShares(sdk.NewDec(10)))) } func TestRemoveTokens(t *testing.T) {