Skip to content

Commit

Permalink
fix: implement Amino serialization for x/authz and x/feegrant (cosmos…
Browse files Browse the repository at this point in the history
…#11224)

* fix: Add RegisterLegacyAminoCodec for authz/feegrant

* add module name

* Fix GetSignByes, add tests

* removed module names from registered messages to match other modules

* added interfaces and concrete types registration

* unseal amino instances to allow external grant and authorization registration

* fixed messages tests

* allow to register external types into authz modulecdc

* use legacy.Cdc instead of ModuleCdc inside x/authz

* move the legacy.Cdc initialization outside init function

* added serialization docs

* Update docs/core/encoding.md

Co-authored-by: Amaury <[email protected]>

* added the Ledger specification

* fixed tests

Co-authored-by: Amaury M <[email protected]>

(cherry picked from commit 81cfc6c)
  • Loading branch information
RiccardoM committed Nov 14, 2022
1 parent 730f766 commit 1fc135e
Show file tree
Hide file tree
Showing 20 changed files with 222 additions and 12 deletions.
3 changes: 1 addition & 2 deletions codec/legacy/codec.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,9 @@ import (
// has all Tendermint crypto and evidence types registered.
//
// TODO: Deprecated - remove this global.
var Cdc *codec.LegacyAmino
var Cdc = codec.NewLegacyAmino()

func init() {
Cdc = codec.NewLegacyAmino()
cryptocodec.RegisterCrypto(Cdc)
codec.RegisterEvidences(Cdc)
}
Expand Down
18 changes: 18 additions & 0 deletions docs/core/encoding.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,24 @@ Note, there are length-prefixed variants of the above functionality and this is
typically used for when the data needs to be streamed or grouped together
(e.g. `ResponseDeliverTx.Data`)

#### Authz authorizations

Since the `MsgExec` message type can contain different messages instances, it is important that developers
add the following code inside the `init` method of their module's `codec.go` file:

```go
import "github.com/cosmos/cosmos-sdk/codec/legacy"

init() {
// Register all Amino interfaces and concrete types on the global Amino codec so that this can later be
// used to properly serialize x/authz MsgExec instances
RegisterLegacyAminoCodec(legacy.Cdc)
}
```

This will allow the `x/authz` module to properly serialize and de-serializes `MsgExec` instances using Amino,
which is required when signing this kind of messages using a Ledger.

### Gogoproto

Modules are encouraged to utilize Protobuf encoding for their respective types. In the SDK, we use the [Gogoproto](https://github.com/gogo/protobuf) specific implementation of the Protobuf spec that offers speed and DX improvements compared to the official [Google protobuf implementation](https://github.com/protocolbuffers/protobuf).
Expand Down
5 changes: 5 additions & 0 deletions x/auth/types/codec.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package types

import (
"github.com/cosmos/cosmos-sdk/codec"
"github.com/cosmos/cosmos-sdk/codec/legacy"
"github.com/cosmos/cosmos-sdk/codec/types"
cryptocodec "github.com/cosmos/cosmos-sdk/crypto/codec"
"github.com/cosmos/cosmos-sdk/x/auth/legacy/legacytx"
Expand Down Expand Up @@ -45,4 +46,8 @@ var (
func init() {
RegisterLegacyAminoCodec(amino)
cryptocodec.RegisterCrypto(amino)

// Register all Amino interfaces and concrete types on the global Amino codec so that this can later be
// used to properly serialize x/authz MsgExec instances
RegisterLegacyAminoCodec(legacy.Cdc)
}
18 changes: 18 additions & 0 deletions x/authz/codec.go
Original file line number Diff line number Diff line change
@@ -1,11 +1,24 @@
package authz

import (
"github.com/cosmos/cosmos-sdk/codec"
"github.com/cosmos/cosmos-sdk/codec/legacy"
types "github.com/cosmos/cosmos-sdk/codec/types"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/types/msgservice"
)

// RegisterLegacyAminoCodec registers the necessary x/authz interfaces and concrete types
// on the provided LegacyAmino codec. These types are used for Amino JSON serialization.
func RegisterLegacyAminoCodec(cdc *codec.LegacyAmino) {
cdc.RegisterConcrete(&MsgGrant{}, "cosmos-sdk/MsgGrant", nil)
cdc.RegisterConcrete(&MsgRevoke{}, "cosmos-sdk/MsgRevoke", nil)
cdc.RegisterConcrete(&MsgExec{}, "cosmos-sdk/MsgExec", nil)

cdc.RegisterInterface((*Authorization)(nil), nil)
cdc.RegisterConcrete(&GenericAuthorization{}, "cosmos-sdk/GenericAuthorization", nil)
}

// RegisterInterfaces registers the interfaces types with the interface registry
func RegisterInterfaces(registry types.InterfaceRegistry) {
registry.RegisterImplementations((*sdk.Msg)(nil),
Expand All @@ -22,3 +35,8 @@ func RegisterInterfaces(registry types.InterfaceRegistry) {

msgservice.RegisterMsgServiceDesc(registry, MsgServiceDesc())
}
func init() {
// Register all Amino interfaces and concrete types on the global Amino codec so that this can later be
// used to properly serialize MsgGrant and MsgExec instances
RegisterLegacyAminoCodec(legacy.Cdc)
}
4 changes: 3 additions & 1 deletion x/authz/module/module.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,9 @@ func (am AppModule) RegisterServices(cfg module.Configurator) {
}

// RegisterLegacyAminoCodec registers the authz module's types for the given codec.
func (AppModuleBasic) RegisterLegacyAminoCodec(cdc *codec.LegacyAmino) {}
func (AppModuleBasic) RegisterLegacyAminoCodec(cdc *codec.LegacyAmino) {
authz.RegisterLegacyAminoCodec(cdc)
}

// RegisterInterfaces registers the authz module's interface types
func (AppModuleBasic) RegisterInterfaces(registry cdctypes.InterfaceRegistry) {
Expand Down
2 changes: 1 addition & 1 deletion x/authz/msgs.go
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
package authz

import (
"github.com/cosmos/cosmos-sdk/codec/legacy"
"time"

"github.com/gogo/protobuf/proto"

"github.com/cosmos/cosmos-sdk/codec/legacy"
cdctypes "github.com/cosmos/cosmos-sdk/codec/types"
sdk "github.com/cosmos/cosmos-sdk/types"
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
Expand Down
64 changes: 64 additions & 0 deletions x/authz/msgs_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package authz_test

import (
"github.com/cosmos/cosmos-sdk/x/auth/legacy/legacytx"
stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types"
"testing"
"time"

Expand Down Expand Up @@ -116,3 +118,65 @@ func TestMsgGrantGetAuthorization(t *testing.T) {
m.SetAuthorization(&g)
require.Equal(m.GetAuthorization(), &g)
}

func TestAminoJSON(t *testing.T) {
tx := legacytx.StdTx{}
var msg legacytx.LegacyMsg
someDate := time.Date(1, 1, 1, 1, 1, 1, 1, time.UTC)
msgSend := banktypes.MsgSend{FromAddress: "cosmos1ghi", ToAddress: "cosmos1jkl"}
typeURL := sdk.MsgTypeURL(&msgSend)
msgSendAny, err := cdctypes.NewAnyWithValue(&msgSend)
require.NoError(t, err)
grant, err := authz.NewGrant(authz.NewGenericAuthorization(typeURL), someDate.Add(time.Hour))
require.NoError(t, err)
sendGrant, err := authz.NewGrant(banktypes.NewSendAuthorization(sdk.NewCoins(sdk.NewCoin("stake", sdk.NewInt(1000)))), someDate.Add(time.Hour))
require.NoError(t, err)
valAddr, err := sdk.ValAddressFromBech32("cosmosvaloper1xcy3els9ua75kdm783c3qu0rfa2eples6eavqq")
require.NoError(t, err)
stakingAuth, err := stakingtypes.NewStakeAuthorization([]sdk.ValAddress{valAddr}, nil, stakingtypes.AuthorizationType_AUTHORIZATION_TYPE_DELEGATE, &sdk.Coin{Denom: "stake", Amount: sdk.NewInt(1000)})
require.NoError(t, err)
delegateGrant, err := authz.NewGrant(stakingAuth, someDate.Add(time.Hour))
require.NoError(t, err)

// Amino JSON encoding has changed in authz since v0.46.
// Before, it was outputting something like:
// `{"account_number":"1","chain_id":"foo","fee":{"amount":[],"gas":"0"},"memo":"memo","msgs":[{"grant":{"authorization":{"msg":"/cosmos.bank.v1beta1.MsgSend"},"expiration":"0001-01-01T02:01:01.000000001Z"},"grantee":"cosmos1def","granter":"cosmos1abc"}],"sequence":"1","timeout_height":"1"}`
//
// This was a bug. Now, it's as below, See how there's `type` & `value` fields.
// ref: https://github.com/cosmos/cosmos-sdk/issues/11190
// ref: https://github.com/cosmos/cosmjs/issues/1026
msg = &authz.MsgGrant{Granter: "cosmos1abc", Grantee: "cosmos1def", Grant: grant}
tx.Msgs = []sdk.Msg{msg}
require.Equal(t,
`{"account_number":"1","chain_id":"foo","fee":{"amount":[],"gas":"0"},"memo":"memo","msgs":[{"type":"cosmos-sdk/MsgGrant","value":{"grant":{"authorization":{"type":"cosmos-sdk/GenericAuthorization","value":{"msg":"/cosmos.bank.v1beta1.MsgSend"}},"expiration":"0001-01-01T02:01:01.000000001Z"},"grantee":"cosmos1def","granter":"cosmos1abc"}}],"sequence":"1","timeout_height":"1"}`,
string(legacytx.StdSignBytes("foo", 1, 1, 1, legacytx.StdFee{}, []sdk.Msg{msg}, "memo")),
)

msg = &authz.MsgGrant{Granter: "cosmos1abc", Grantee: "cosmos1def", Grant: sendGrant}
tx.Msgs = []sdk.Msg{msg}
require.Equal(t,
`{"account_number":"1","chain_id":"foo","fee":{"amount":[],"gas":"0"},"memo":"memo","msgs":[{"type":"cosmos-sdk/MsgGrant","value":{"grant":{"authorization":{"type":"cosmos-sdk/SendAuthorization","value":{"spend_limit":[{"amount":"1000","denom":"stake"}]}},"expiration":"0001-01-01T02:01:01.000000001Z"},"grantee":"cosmos1def","granter":"cosmos1abc"}}],"sequence":"1","timeout_height":"1"}`,
string(legacytx.StdSignBytes("foo", 1, 1, 1, legacytx.StdFee{}, []sdk.Msg{msg}, "memo")),
)

msg = &authz.MsgGrant{Granter: "cosmos1abc", Grantee: "cosmos1def", Grant: delegateGrant}
tx.Msgs = []sdk.Msg{msg}
require.Equal(t,
`{"account_number":"1","chain_id":"foo","fee":{"amount":[],"gas":"0"},"memo":"memo","msgs":[{"type":"cosmos-sdk/MsgGrant","value":{"grant":{"authorization":{"type":"cosmos-sdk/StakeAuthorization","value":{"Validators":{"type":"cosmos-sdk/StakeAuthorization/AllowList","value":{"allow_list":{"address":["cosmosvaloper1xcy3els9ua75kdm783c3qu0rfa2eples6eavqq"]}}},"authorization_type":1,"max_tokens":{"amount":"1000","denom":"stake"}}},"expiration":"0001-01-01T02:01:01.000000001Z"},"grantee":"cosmos1def","granter":"cosmos1abc"}}],"sequence":"1","timeout_height":"1"}`,
string(legacytx.StdSignBytes("foo", 1, 1, 1, legacytx.StdFee{}, []sdk.Msg{msg}, "memo")),
)

msg = &authz.MsgRevoke{Granter: "cosmos1abc", Grantee: "cosmos1def", MsgTypeUrl: typeURL}
tx.Msgs = []sdk.Msg{msg}
require.Equal(t,
`{"account_number":"1","chain_id":"foo","fee":{"amount":[],"gas":"0"},"memo":"memo","msgs":[{"type":"cosmos-sdk/MsgRevoke","value":{"grantee":"cosmos1def","granter":"cosmos1abc","msg_type_url":"/cosmos.bank.v1beta1.MsgSend"}}],"sequence":"1","timeout_height":"1"}`,
string(legacytx.StdSignBytes("foo", 1, 1, 1, legacytx.StdFee{}, []sdk.Msg{msg}, "memo")),
)

msg = &authz.MsgExec{Grantee: "cosmos1def", Msgs: []*cdctypes.Any{msgSendAny}}
tx.Msgs = []sdk.Msg{msg}
require.Equal(t,
`{"account_number":"1","chain_id":"foo","fee":{"amount":[],"gas":"0"},"memo":"memo","msgs":[{"type":"cosmos-sdk/MsgExec","value":{"grantee":"cosmos1def","msgs":[{"type":"cosmos-sdk/MsgSend","value":{"amount":[],"from_address":"cosmos1ghi","to_address":"cosmos1jkl"}}]}}],"sequence":"1","timeout_height":"1"}`,
string(legacytx.StdSignBytes("foo", 1, 1, 1, legacytx.StdFee{}, []sdk.Msg{msg}, "memo")),
)
}
6 changes: 3 additions & 3 deletions x/authz/simulation/operations_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ func (suite *SimTestSuite) TestSimulateGrant() {
suite.Require().NoError(err)

var msg authz.MsgGrant
suite.app.AppCodec().UnmarshalJSON(operationMsg.Msg, &msg)
suite.app.LegacyAmino().UnmarshalJSON(operationMsg.Msg, &msg)
suite.Require().True(operationMsg.OK)
suite.Require().Equal(granter.Address.String(), msg.Granter)
suite.Require().Equal(grantee.Address.String(), msg.Grantee)
Expand Down Expand Up @@ -143,7 +143,7 @@ func (suite *SimTestSuite) TestSimulateRevoke() {
suite.Require().NoError(err)

var msg authz.MsgRevoke
suite.app.AppCodec().UnmarshalJSON(operationMsg.Msg, &msg)
suite.app.LegacyAmino().UnmarshalJSON(operationMsg.Msg, &msg)

suite.Require().True(operationMsg.OK)
suite.Require().Equal(granter.Address.String(), msg.Granter)
Expand Down Expand Up @@ -178,7 +178,7 @@ func (suite *SimTestSuite) TestSimulateExec() {

var msg authz.MsgExec

suite.app.AppCodec().UnmarshalJSON(operationMsg.Msg, &msg)
suite.app.LegacyAmino().UnmarshalJSON(operationMsg.Msg, &msg)

suite.Require().True(operationMsg.OK)
suite.Require().Equal(grantee.Address.String(), msg.Grantee)
Expand Down
6 changes: 6 additions & 0 deletions x/bank/types/codec.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package types

import (
"github.com/cosmos/cosmos-sdk/codec"
"github.com/cosmos/cosmos-sdk/codec/legacy"
"github.com/cosmos/cosmos-sdk/codec/types"
cryptocodec "github.com/cosmos/cosmos-sdk/crypto/codec"
sdk "github.com/cosmos/cosmos-sdk/types"
Expand All @@ -14,6 +15,7 @@ import (
func RegisterLegacyAminoCodec(cdc *codec.LegacyAmino) {
cdc.RegisterConcrete(&MsgSend{}, "cosmos-sdk/MsgSend", nil)
cdc.RegisterConcrete(&MsgMultiSend{}, "cosmos-sdk/MsgMultiSend", nil)
cdc.RegisterConcrete(&SendAuthorization{}, "cosmos-sdk/SendAuthorization", nil)
}

func RegisterInterfaces(registry types.InterfaceRegistry) {
Expand Down Expand Up @@ -45,4 +47,8 @@ func init() {
RegisterLegacyAminoCodec(amino)
cryptocodec.RegisterCrypto(amino)
amino.Seal()

// Register all Amino interfaces and concrete types on the global Amino codec so that this can later be
// used to properly serialize x/authz MsgExec instances
RegisterLegacyAminoCodec(legacy.Cdc)
}
5 changes: 5 additions & 0 deletions x/crisis/types/codec.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package types

import (
"github.com/cosmos/cosmos-sdk/codec"
"github.com/cosmos/cosmos-sdk/codec/legacy"
codectypes "github.com/cosmos/cosmos-sdk/codec/types"
cryptocodec "github.com/cosmos/cosmos-sdk/crypto/codec"
sdk "github.com/cosmos/cosmos-sdk/types"
Expand Down Expand Up @@ -38,4 +39,8 @@ func init() {
RegisterLegacyAminoCodec(amino)
cryptocodec.RegisterCrypto(amino)
amino.Seal()

// Register all Amino interfaces and concrete types on the global Amino codec so that this can later be
// used to properly serialize x/authz MsgExec instances
RegisterLegacyAminoCodec(legacy.Cdc)
}
5 changes: 5 additions & 0 deletions x/distribution/types/codec.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package types

import (
"github.com/cosmos/cosmos-sdk/codec"
"github.com/cosmos/cosmos-sdk/codec/legacy"
"github.com/cosmos/cosmos-sdk/codec/types"
cryptocodec "github.com/cosmos/cosmos-sdk/crypto/codec"
sdk "github.com/cosmos/cosmos-sdk/types"
Expand Down Expand Up @@ -51,4 +52,8 @@ func init() {
RegisterLegacyAminoCodec(amino)
cryptocodec.RegisterCrypto(amino)
amino.Seal()

// Register all Amino interfaces and concrete types on the global Amino codec so that this can later be
// used to properly serialize x/authz MsgExec instances
RegisterLegacyAminoCodec(legacy.Cdc)
}
5 changes: 5 additions & 0 deletions x/evidence/types/codec.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package types

import (
"github.com/cosmos/cosmos-sdk/codec"
"github.com/cosmos/cosmos-sdk/codec/legacy"
"github.com/cosmos/cosmos-sdk/codec/types"
cryptocodec "github.com/cosmos/cosmos-sdk/crypto/codec"
sdk "github.com/cosmos/cosmos-sdk/types"
Expand Down Expand Up @@ -44,4 +45,8 @@ func init() {
RegisterLegacyAminoCodec(amino)
cryptocodec.RegisterCrypto(amino)
amino.Seal()

// Register all Amino interfaces and concrete types on the global Amino codec so that this can later be
// used to properly serialize x/authz MsgExec instances
RegisterLegacyAminoCodec(legacy.Cdc)
}
34 changes: 34 additions & 0 deletions x/feegrant/codec.go
Original file line number Diff line number Diff line change
@@ -1,11 +1,25 @@
package feegrant

import (
"github.com/cosmos/cosmos-sdk/codec"
"github.com/cosmos/cosmos-sdk/codec/legacy"
"github.com/cosmos/cosmos-sdk/codec/types"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/types/msgservice"
)

// RegisterLegacyAminoCodec registers the necessary x/feegrant interfaces and concrete types
// on the provided LegacyAmino codec. These types are used for Amino JSON serialization.
func RegisterLegacyAminoCodec(cdc *codec.LegacyAmino) {
cdc.RegisterConcrete(&MsgGrantAllowance{}, "cosmos-sdk/MsgGrantAllowance", nil)
cdc.RegisterConcrete(&MsgRevokeAllowance{}, "cosmos-sdk/MsgRevokeAllowance", nil)

cdc.RegisterInterface((*FeeAllowanceI)(nil), nil)
cdc.RegisterConcrete(&BasicAllowance{}, "cosmos-sdk/BasicAllowance", nil)
cdc.RegisterConcrete(&PeriodicAllowance{}, "cosmos-sdk/PeriodicAllowance", nil)
cdc.RegisterConcrete(&AllowedMsgAllowance{}, "cosmos-sdk/AllowedMsgAllowance", nil)
}

// RegisterInterfaces registers the interfaces types with the interface registry
func RegisterInterfaces(registry types.InterfaceRegistry) {
registry.RegisterImplementations((*sdk.Msg)(nil),
Expand All @@ -23,3 +37,23 @@ func RegisterInterfaces(registry types.InterfaceRegistry) {

msgservice.RegisterMsgServiceDesc(registry, &_Msg_serviceDesc)
}

var (
amino = codec.NewLegacyAmino()

// ModuleCdc references the global x/feegrant module codec. Note, the codec should
// ONLY be used in certain instances of tests and for JSON encoding as Amino is
// still used for that purpose.
//
// The actual codec used for serialization should be provided to x/feegrant and
// defined at the application level.
ModuleCdc = codec.NewAminoCodec(amino)
)

func init() {
RegisterLegacyAminoCodec(amino)

// Register all Amino interfaces and concrete types on the global Amino codec so that this can later be
// used to properly serialize x/authz MsgExec instances
RegisterLegacyAminoCodec(legacy.Cdc)
}
1 change: 1 addition & 0 deletions x/feegrant/module/module.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ func (am AppModule) RegisterServices(cfg module.Configurator) {

// RegisterLegacyAminoCodec registers the feegrant module's types for the given codec.
func (AppModuleBasic) RegisterLegacyAminoCodec(cdc *codec.LegacyAmino) {
feegrant.RegisterLegacyAminoCodec(cdc)
}

// RegisterInterfaces registers the feegrant module's interface types
Expand Down
5 changes: 2 additions & 3 deletions x/feegrant/msgs.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ package feegrant
import (
"github.com/gogo/protobuf/proto"

"github.com/cosmos/cosmos-sdk/codec/legacy"
"github.com/cosmos/cosmos-sdk/codec/types"
sdk "github.com/cosmos/cosmos-sdk/types"
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
Expand Down Expand Up @@ -78,7 +77,7 @@ func (msg MsgGrantAllowance) Route() string {

// GetSignBytes implements the LegacyMsg.GetSignBytes method.
func (msg MsgGrantAllowance) GetSignBytes() []byte {
return sdk.MustSortJSON(legacy.Cdc.MustMarshalJSON(&msg))
return sdk.MustSortJSON(ModuleCdc.MustMarshalJSON(&msg))
}

// GetFeeAllowanceI returns unpacked FeeAllowance
Expand Down Expand Up @@ -142,5 +141,5 @@ func (msg MsgRevokeAllowance) Route() string {

// GetSignBytes implements the LegacyMsg.GetSignBytes method.
func (msg MsgRevokeAllowance) GetSignBytes() []byte {
return sdk.MustSortJSON(legacy.Cdc.MustMarshalJSON(&msg))
return sdk.MustSortJSON(ModuleCdc.MustMarshalJSON(&msg))
}
Loading

0 comments on commit 1fc135e

Please sign in to comment.