diff --git a/docs/spec/distribution/transactions.md b/docs/spec/distribution/transactions.md index 47b11165a0..c8c1080ad4 100644 --- a/docs/spec/distribution/transactions.md +++ b/docs/spec/distribution/transactions.md @@ -1,16 +1,15 @@ # Transactions -## TxWithdrawDelegationRewardsAll +## MsgWithdrawDelegationRewardsAll When a delegator wishes to withdraw their rewards it must send -`TxWithdrawDelegationRewardsAll`. Note that parts of this transaction logic are also +`MsgWithdrawDelegationRewardsAll`. Note that parts of this transaction logic are also triggered each with any change in individual delegations, such as an unbond, redelegation, or delegation of additional tokens to a specific validator. ```golang -type TxWithdrawDelegationRewardsAll struct { - delegatorAddr sdk.AccAddress - withdrawAddr sdk.AccAddress // address to make the withdrawal to +type MsgWithdrawDelegationRewardsAll struct { + DelegatorAddr sdk.AccAddress } func WithdrawDelegationRewardsAll(delegatorAddr, withdrawAddr sdk.AccAddress) @@ -41,16 +40,15 @@ func GetDelegatorRewardsAll(delegatorAddr sdk.AccAddress, height int64) DecCoins return withdraw ``` -## TxWithdrawDelegationReward +## MsgWithdrawDelegationReward under special circumstances a delegator may wish to withdraw rewards from only a single validator. ```golang -type TxWithdrawDelegationReward struct { - delegatorAddr sdk.AccAddress - validatorAddr sdk.AccAddress - withdrawAddr sdk.AccAddress // address to make the withdrawal to +type MsgWithdrawDelegationReward struct { + DelegatorAddr sdk.AccAddress + ValidatorAddr sdk.ValAddress } func WithdrawDelegationReward(delegatorAddr, validatorAddr, withdrawAddr sdk.AccAddress) @@ -72,19 +70,18 @@ func WithdrawDelegationReward(delegatorAddr, validatorAddr, withdrawAddr sdk.Acc ``` -## TxWithdrawValidatorRewardsAll +## MsgWithdrawValidatorRewardsAll When a validator wishes to withdraw their rewards it must send -`TxWithdrawValidatorRewardsAll`. Note that parts of this transaction logic are also +`MsgWithdrawValidatorRewardsAll`. Note that parts of this transaction logic are also 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 fee, as well as any rewards earning on their self-delegation. ``` -type TxWithdrawValidatorRewardsAll struct { - operatorAddr sdk.AccAddress // validator address to withdraw from - withdrawAddr sdk.AccAddress // address to make the withdrawal to +type MsgWithdrawValidatorRewardsAll struct { + OperatorAddr sdk.ValAddress // validator address to withdraw from } func WithdrawValidatorRewardsAll(operatorAddr, withdrawAddr sdk.AccAddress) diff --git a/x/distribution/codec.go b/x/distribution/codec.go deleted file mode 100644 index e3bf8d49ae..0000000000 --- a/x/distribution/codec.go +++ /dev/null @@ -1,10 +0,0 @@ -package distribution - -import ( - "github.com/cosmos/cosmos-sdk/codec" -) - -// XXX TODO -// Register concrete types on codec codec -func RegisterCodec(cdc *codec.Codec) { -} diff --git a/x/distribution/handler.go b/x/distribution/handler.go index d2ef538fb1..1ae74ac78e 100644 --- a/x/distribution/handler.go +++ b/x/distribution/handler.go @@ -1 +1,84 @@ package distribution + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/distribution/keeper" + "github.com/cosmos/cosmos-sdk/x/distribution/tags" + "github.com/cosmos/cosmos-sdk/x/distribution/types" +) + +func NewHandler(k keeper.Keeper) sdk.Handler { + return func(ctx sdk.Context, msg sdk.Msg) sdk.Result { + // NOTE msg already has validate basic run + switch msg := msg.(type) { + case types.MsgModifyWithdrawAddress: + return handleMsgModifyWithdrawAddress(ctx, msg, k) + case types.MsgWithdrawDelegatorRewardsAll: + return handleMsgWithdrawDelegatorRewardsAll(ctx, msg, k) + case types.MsgWithdrawDelegatorReward: + return handleMsgWithdrawDelegatorReward(ctx, msg, k) + case types.MsgWithdrawValidatorRewardsAll: + return handleMsgWithdrawValidatorRewardsAll(ctx, msg, k) + default: + return sdk.ErrTxDecode("invalid message parse in distribution module").Result() + } + } +} + +//_____________________________________________________________________ + +// These functions assume everything has been authenticated, +// now we just perform action and save + +func handleMsgModifyWithdrawAddress(ctx sdk.Context, msg types.MsgModifyWithdrawAddress, k keeper.Keeper) sdk.Result { + + k.SetDelegatorWithdrawAddr(ctx, msg.DelegatorAddr, msg.WithdrawAddress) + + tags := sdk.NewTags( + tags.Action, tags.ActionModifyWithdrawAddress, + tags.Delegator, []byte(msg.DelegatorAddr.String()), + ) + return sdk.Result{ + Tags: tags, + } +} + +func handleMsgWithdrawDelegatorRewardsAll(ctx sdk.Context, msg types.MsgWithdrawDelegatorRewardsAll, k keeper.Keeper) sdk.Result { + + k.WithdrawDelegationRewardsAll(ctx, msg.DelegatorAddr) + + tags := sdk.NewTags( + tags.Action, tags.ActionWithdrawDelegatorRewardsAll, + tags.Delegator, []byte(msg.DelegatorAddr.String()), + ) + return sdk.Result{ + Tags: tags, + } +} + +func handleMsgWithdrawDelegatorReward(ctx sdk.Context, msg types.MsgWithdrawDelegatorReward, k keeper.Keeper) sdk.Result { + + k.WithdrawDelegationReward(ctx, msg.DelegatorAddr, msg.ValidatorAddr) + + tags := sdk.NewTags( + tags.Action, tags.ActionWithdrawDelegatorReward, + tags.Delegator, []byte(msg.DelegatorAddr.String()), + tags.Validator, []byte(msg.ValidatorAddr.String()), + ) + return sdk.Result{ + Tags: tags, + } +} + +func handleMsgWithdrawValidatorRewardsAll(ctx sdk.Context, msg types.MsgWithdrawValidatorRewardsAll, k keeper.Keeper) sdk.Result { + + k.WithdrawValidatorRewardsAll(ctx, msg.ValidatorAddr) + + tags := sdk.NewTags( + tags.Action, tags.ActionWithdrawValidatorRewardsAll, + tags.Validator, []byte(msg.ValidatorAddr.String()), + ) + return sdk.Result{ + Tags: tags, + } +} diff --git a/x/distribution/keeper/validator.go b/x/distribution/keeper/validator.go index ce73b22719..b8dece32cf 100644 --- a/x/distribution/keeper/validator.go +++ b/x/distribution/keeper/validator.go @@ -34,8 +34,7 @@ func (k Keeper) RemoveValidatorDistInfo(ctx sdk.Context, valAddr sdk.ValAddress) } // withdrawal all the validator rewards including the commission -func (k Keeper) WithdrawValidatorRewardsAll(ctx sdk.Context, - operatorAddr sdk.ValAddress) { +func (k Keeper) WithdrawValidatorRewardsAll(ctx sdk.Context, operatorAddr sdk.ValAddress) { // withdraw self-delegation height := ctx.BlockHeight() diff --git a/x/distribution/tags/tags.go b/x/distribution/tags/tags.go new file mode 100644 index 0000000000..dd55ba2369 --- /dev/null +++ b/x/distribution/tags/tags.go @@ -0,0 +1,17 @@ +// nolint +package tags + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" +) + +var ( + ActionModifyWithdrawAddress = []byte("modify-withdraw-address") + ActionWithdrawDelegatorRewardsAll = []byte("withdraw-delegator-rewards-all") + ActionWithdrawDelegatorReward = []byte("withdraw-delegator-reward") + ActionWithdrawValidatorRewardsAll = []byte("withdraw-validator-rewards-all") + + Action = sdk.TagAction + Validator = sdk.TagSrcValidator + Delegator = sdk.TagDelegator +) diff --git a/x/distribution/types/codec.go b/x/distribution/types/codec.go new file mode 100644 index 0000000000..84fee76a3a --- /dev/null +++ b/x/distribution/types/codec.go @@ -0,0 +1,24 @@ +package types + +import ( + "github.com/cosmos/cosmos-sdk/codec" +) + +// XXX TODO +// Register concrete types on codec codec +func RegisterCodec(cdc *codec.Codec) { + cdc.RegisterConcrete(MsgWithdrawDelegationRewardsAll{}, "cosmos-sdk/MsgWithdrawDelegationRewardsAll", nil) + cdc.RegisterConcrete(MsgWithdrawDelegationReward{}, "cosmos-sdk/MsgWithdrawDelegationReward", nil) + cdc.RegisterConcrete(MsgWithdrawValidatorRewardsAll{}, "cosmos-sdk/MsgWithdrawValidatorRewardsAll", nil) + cdc.RegisterConcrete(MsgModifyWithdrawAddress{}, "cosmos-sdk/MsgModifyWithdrawAddress", nil) +} + +// generic sealed codec to be used throughout sdk +var MsgCdc *codec.Codec + +func init() { + cdc := codec.New() + RegisterCodec(cdc) + codec.RegisterCrypto(cdc) + MsgCdc = cdc.Seal() +} diff --git a/x/distribution/types/msg.go b/x/distribution/types/msg.go new file mode 100644 index 0000000000..3808752f1d --- /dev/null +++ b/x/distribution/types/msg.go @@ -0,0 +1,175 @@ +//nolint +package types + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" +) + +// name to identify transaction types +const MsgType = "distr" + +// Verify interface at compile time +var _, _ sdk.Msg = &MsgModifyWithdrawAddress{}, &MsgWithdrawDelegatorRewardsAll{} +var _, _ sdk.Msg = &MsgWithdrawDelegationReward{}, &MsgWithdrawValidatorRewardsAll{} + +//______________________________________________________________________ + +// msg struct for changing the withdraw address for a delegator (or validator self-delegation) +type MsgModifyWithdrawAddress struct { + DelegatorAddr sdk.AccAddress `json:"delegator_addr"` + WithdrawAddr sdk.AccAddress `json:"delegator_addr"` +} + +func NewMsgModifyWithdrawAddress(delAddr, withdrawAddr sdk.AccAddress) MsgModifyWithdrawAddress { + return MsgModifyWithdrawAddress{ + DelegatorAddr: delAddr, + WithdrawAddr: withdrawAddr, + } +} + +func (msg MsgModifyWithdrawAddress) Type() string { return MsgType } +func (msg MsgModifyWithdrawAddress) Name() string { return "withdraw_delegation_rewards_all" } + +// Return address that must sign over msg.GetSignBytes() +func (msg MsgModifyWithdrawAddress) GetSigners() []sdk.AccAddress { + return []sdk.AccAddress{sdk.AccAddress(msg.DelegatorAddr)} +} + +// get the bytes for the message signer to sign on +func (msg MsgModifyWithdrawAddress) GetSignBytes() []byte { + b, err := MsgCdc.MarshalJSON(msg) + if err != nil { + panic(err) + } + return sdk.MustSortJSON(b) +} + +// quick validity check +func (msg MsgModifyWithdrawAddress) ValidateBasic() sdk.Error { + if msg.DelegatorAddr == nil { + return ErrNilDelegatorAddr(DefaultCodespace) + } + if msg.WithdrawAddr == nil { + return ErrNilWithdrawAddr(DefaultCodespace) + } + return nil +} + +//______________________________________________________________________ + +// msg struct for delegation withdraw for all of the delegator's delegations +type MsgWithdrawDelegatorRewardsAll struct { + DelegatorAddr sdk.AccAddress `json:"delegator_addr"` +} + +func NewMsgWithdrawDelegationRewardsAll(delAddr sdk.AccAddress) MsgWithdrawDelegatorRewardsAll { + return MsgWithdrawDelegatorRewardsAll{ + DelegatorAddr: delAddr, + } +} + +func (msg MsgWithdrawDelegatorRewardsAll) Type() string { return MsgType } +func (msg MsgWithdrawDelegatorRewardsAll) Name() string { return "withdraw_delegation_rewards_all" } + +// Return address that must sign over msg.GetSignBytes() +func (msg MsgWithdrawDelegatorRewardsAll) GetSigners() []sdk.AccAddress { + return []sdk.AccAddress{sdk.AccAddress(msg.DelegatorAddr)} +} + +// get the bytes for the message signer to sign on +func (msg MsgWithdrawDelegatorRewardsAll) GetSignBytes() []byte { + b, err := MsgCdc.MarshalJSON(msg) + if err != nil { + panic(err) + } + return sdk.MustSortJSON(b) +} + +// quick validity check +func (msg MsgWithdrawDelegatorRewardsAll) ValidateBasic() sdk.Error { + if msg.DelegatorAddr == nil { + return ErrNilDelegatorAddr(DefaultCodespace) + } + return nil +} + +//______________________________________________________________________ + +// msg struct for delegation withdraw from a single validator +type MsgWithdrawDelegationReward struct { + DelegatorAddr sdk.AccAddress `json:"delegator_addr"` + ValidatorAddr sdk.ValAddress `json:"validator_addr"` +} + +func NewMsgWithdrawDelegationReward(delAddr sdk.AccAddress, valAddr sdk.ValAddress) MsgWithdrawDelegationReward { + return MsgWithdrawDelegationReward{ + DelegatorAddr: delAddr, + ValidatorAddr: valAddr, + } +} + +func (msg MsgWithdrawDelegationReward) Type() string { return MsgType } +func (msg MsgWithdrawDelegationReward) Name() string { return "withdraw_delegation_reward" } + +// Return address that must sign over msg.GetSignBytes() +func (msg MsgWithdrawDelegationReward) GetSigners() []sdk.AccAddress { + return []sdk.AccAddress{sdk.AccAddress(msg.DelegatorAddr)} +} + +// get the bytes for the message signer to sign on +func (msg MsgWithdrawDelegationReward) GetSignBytes() []byte { + b, err := MsgCdc.MarshalJSON(msg) + if err != nil { + panic(err) + } + return sdk.MustSortJSON(b) +} + +// quick validity check +func (msg MsgWithdrawDelegationReward) ValidateBasic() sdk.Error { + if msg.DelegatorAddr == nil { + return ErrNilDelegatorAddr(DefaultCodespace) + } + if msg.ValidatorAddr == nil { + return ErrNilValidatorAddr(DefaultCodespace) + } + return nil +} + +//______________________________________________________________________ + +// msg struct for validator withdraw +type MsgWithdrawValidatorRewardsAll struct { + ValidatorAddr sdk.ValAddress `json:"validator_addr"` +} + +func NewMsgWithdrawValidatorRewardsAll(valAddr sdk.ValAddress) MsgWithdrawValidatorRewardsAll { + return MsgWithdrawValidatorRewardsAll{ + ValidatorAddr: valAddr, + } +} + +func (msg MsgWithdrawValidatorRewardsAll) Type() string { return MsgType } +func (msg MsgWithdrawValidatorRewardsAll) Name() string { return "withdraw_validator_rewards_all" } + +// Return address that must sign over msg.GetSignBytes() +func (msg MsgWithdrawValidatorRewardsAll) GetSigners() []sdk.AccAddress { + return []sdk.AccAddress{sdk.AccAddress(msg.ValidatorAddr.Bytes())} +} + +// get the bytes for the message signer to sign on +func (msg MsgWithdrawValidatorRewardsAll) GetSignBytes() []byte { + b, err := MsgCdc.MarshalJSON(msg) + if err != nil { + panic(err) + } + return sdk.MustSortJSON(b) +} + +// quick validity check +func (msg MsgWithdrawValidatorRewardsAll) ValidateBasic() sdk.Error { + if msg.ValidatorAddr == nil { + return ErrNilValidatorAddr(DefaultCodespace) + } + return nil +} diff --git a/x/stake/handler.go b/x/stake/handler.go index e7641393db..e1fb3edc02 100644 --- a/x/stake/handler.go +++ b/x/stake/handler.go @@ -87,6 +87,13 @@ func handleMsgCreateValidator(ctx sdk.Context, msg types.MsgCreateValidator, k k return err.Result() } + // call the hook if present + if k.validatorHooks != nil { + k.validatorHooks.OnValidatorCreated(ctx, validator.OperatorAddr) + accAddr := sdk.AccAddress{validator.OperatorAddr} + k.validatorHooks.OnDelegationCreated(ctx, accAddr, validator.OperatorAddr) + } + tags := sdk.NewTags( tags.Action, tags.ActionCreateValidator, tags.DstValidator, []byte(msg.ValidatorAddr.String()), @@ -146,6 +153,11 @@ func handleMsgDelegate(ctx sdk.Context, msg types.MsgDelegate, k keeper.Keeper) return err.Result() } + // call the hook if present + if k.validatorHooks != nil { + k.validatorHooks.OnDelegationCreated(ctx, msg.DelegatorAddr, validator.OperatorAddr) + } + tags := sdk.NewTags( tags.Action, tags.ActionDelegate, tags.Delegator, []byte(msg.DelegatorAddr.String()), diff --git a/x/stake/keeper/delegation.go b/x/stake/keeper/delegation.go index ad2ab86f02..78e16de828 100644 --- a/x/stake/keeper/delegation.go +++ b/x/stake/keeper/delegation.go @@ -84,6 +84,12 @@ func (k Keeper) SetDelegation(ctx sdk.Context, delegation types.Delegation) { // remove a delegation from store func (k Keeper) RemoveDelegation(ctx sdk.Context, delegation types.Delegation) { + + // call the hook if present + if k.hooks != nil { + k.hooks.OnDelegationRemoved(ctx, delegation.DelegatorAddr, delegation.ValidatorAddr) + } + store := ctx.KVStore(k.storeKey) store.Delete(GetDelegationKey(delegation.DelegatorAddr, delegation.ValidatorAddr)) } @@ -293,6 +299,11 @@ func (k Keeper) Delegate(ctx sdk.Context, delAddr sdk.AccAddress, bondAmt sdk.Co k.SetDelegation(ctx, delegation) k.UpdateValidator(ctx, validator) + // call the hook if present + if k.hooks != nil { + k.hooks.OnDelegationSharesModified(ctx, delegation.DelegatorAddr, validator.OperatorAddr) + } + return } @@ -351,6 +362,11 @@ func (k Keeper) unbond(ctx sdk.Context, delAddr sdk.AccAddress, valAddr sdk.ValA k.RemoveValidator(ctx, validator.OperatorAddr) } + // call the hook if present + if k.hooks != nil { + k.hooks.OnDelegationSharesModified(ctx, delegation.DelegatorAddr, validator.OperatorAddr) + } + return amount, nil } diff --git a/x/stake/keeper/keeper.go b/x/stake/keeper/keeper.go index b9db3cbfb1..cb76a9562a 100644 --- a/x/stake/keeper/keeper.go +++ b/x/stake/keeper/keeper.go @@ -22,12 +22,12 @@ type Keeper struct { func NewKeeper(cdc *codec.Codec, key, tkey sdk.StoreKey, ck bank.Keeper, codespace sdk.CodespaceType) Keeper { keeper := Keeper{ - storeKey: key, - storeTKey: tkey, - cdc: cdc, - bankKeeper: ck, - validatorHooks: nil, - codespace: codespace, + storeKey: key, + storeTKey: tkey, + cdc: cdc, + bankKeeper: ck, + hooks: nil, + codespace: codespace, } return keeper } @@ -37,7 +37,7 @@ func (k Keeper) WithHooks(sh sdk.StakingHooks) Keeper { if k.stakingHooks != nil { panic("cannot set validator hooks twice") } - k.validatorHooks = sh + k.hooks = sh return k } diff --git a/x/stake/keeper/validator.go b/x/stake/keeper/validator.go index 2e5191b2dd..27f2547fd9 100644 --- a/x/stake/keeper/validator.go +++ b/x/stake/keeper/validator.go @@ -623,8 +623,8 @@ func (k Keeper) beginUnbondingValidator(ctx sdk.Context, validator types.Validat store.Delete(GetValidatorsBondedIndexKey(validator.OperatorAddr)) // call the unbond hook if present - if k.validatorHooks != nil { - k.validatorHooks.OnValidatorBeginUnbonding(ctx, validator.ConsAddress()) + if k.hooks != nil { + k.hooks.OnValidatorBeginUnbonding(ctx, validator.ConsAddress()) } // return updated validator @@ -658,8 +658,8 @@ func (k Keeper) bondValidator(ctx sdk.Context, validator types.Validator) types. tstore.Set(GetTendermintUpdatesTKey(validator.OperatorAddr), bzABCI) // call the bond hook if present - if k.validatorHooks != nil { - k.validatorHooks.OnValidatorBonded(ctx, validator.ConsAddress()) + if k.hooks != nil { + k.hooks.OnValidatorBonded(ctx, validator.ConsAddress()) } // return updated validator @@ -670,8 +670,8 @@ func (k Keeper) bondValidator(ctx sdk.Context, validator types.Validator) types. func (k Keeper) RemoveValidator(ctx sdk.Context, address sdk.ValAddress) { // call the hook if present - if k.validatorHooks != nil { - k.validatorHooks.OnValidatorRemoved(ctx, validator.OperatorAddr) + if k.hooks != nil { + k.hooks.OnValidatorRemoved(ctx, validator.OperatorAddr) } // first retrieve the old validator record @@ -741,8 +741,8 @@ func (k Keeper) UpdateValidatorCommission(ctx sdk.Context, addr sdk.ValAddress, store := ctx.KVStore(k.storeKey) // call the hook if present - if k.validatorHooks != nil { - k.validatorHooks.OnValidatorCommissionChange(ctx, validator.OperatorAddr) + if k.hooks != nil { + k.hooks.OnValidatorCommissionChange(ctx, validator.OperatorAddr) } validator, found := k.GetValidator(addr) diff --git a/x/stake/types/codec.go b/x/stake/types/codec.go index aa4f64f8a2..4921cdf8e2 100644 --- a/x/stake/types/codec.go +++ b/x/stake/types/codec.go @@ -22,6 +22,5 @@ func init() { cdc := codec.New() RegisterCodec(cdc) codec.RegisterCrypto(cdc) - MsgCdc = cdc - //MsgCdc = cdc.Seal() //TODO use when upgraded to go-amino 0.9.10 + MsgCdc = cdc.Seal() }