Skip to content

Instantly share code, notes, and snippets.

@3docSec

3docSec/poc.sh Secret

Last active June 15, 2024 12:25
Show Gist options
  • Save 3docSec/2ce73d7321dd957a0dc8ee5c379cbc45 to your computer and use it in GitHub Desktop.
Save 3docSec/2ce73d7321dd957a0dc8ee5c379cbc45 to your computer and use it in GitHub Desktop.
Amino JSON test
#!/usr/bin/env bash
POC_ROOT=$(pwd)
# start from a clean env
rm -rf 2024-05-canto cosmos-sdk
# clone the scope code + the canto SDK
git clone git@github.com:code-423n4/2024-05-canto.git
git clone git@github.com:cosmos/cosmos-sdk.git
# hack the cosmos SDK tests to be importable
pushd cosmos-sdk
git checkout v0.50.6
mv tests/integration/tx/internal tests/integration/tx/intern
popd
# modify the canto-main go.mod to point to the local checkout
pushd 2024-05-canto/canto-main
mv go.mod go.mod.old
sed '7i\
replace github.com/cosmos/cosmos-sdk/tests => ../../cosmos-sdk/tests
' go.mod.old > go.mod
# prepare the test
go get github.com/cosmos/cosmos-sdk/tests@v0.50.6
go get ./...
cat <<EOT >> app/amino_test.go
package app
import (
"context"
"fmt"
csrapi "github.com/Canto-Network/Canto/v7/api/canto/csr/v1"
erc20api "github.com/Canto-Network/Canto/v7/api/canto/erc20/v1"
inflationapi "github.com/Canto-Network/Canto/v7/api/canto/inflation/v1"
"github.com/Canto-Network/Canto/v7/x/coinswap"
coinswaptypes "github.com/Canto-Network/Canto/v7/x/coinswap/types"
"github.com/Canto-Network/Canto/v7/x/csr"
csrtypes "github.com/Canto-Network/Canto/v7/x/csr/types"
"github.com/Canto-Network/Canto/v7/x/epochs"
"github.com/Canto-Network/Canto/v7/x/erc20"
erc20types "github.com/Canto-Network/Canto/v7/x/erc20/types"
"github.com/Canto-Network/Canto/v7/x/govshuttle"
govshuttletypes "github.com/Canto-Network/Canto/v7/x/govshuttle/types"
"github.com/Canto-Network/Canto/v7/x/inflation"
inflationtypes "github.com/Canto-Network/Canto/v7/x/inflation/types"
"github.com/Canto-Network/Canto/v7/x/onboarding"
onboardingtypes "github.com/Canto-Network/Canto/v7/x/onboarding/types"
"github.com/cosmos/cosmos-proto/rapidproto"
"github.com/stretchr/testify/require"
"google.golang.org/protobuf/proto"
"google.golang.org/protobuf/reflect/protoregistry"
"google.golang.org/protobuf/types/known/anypb"
"pgregory.net/rapid"
"testing"
authapi "cosmossdk.io/api/cosmos/auth/v1beta1"
v1beta1 "cosmossdk.io/api/cosmos/base/v1beta1"
msgv1 "cosmossdk.io/api/cosmos/msg/v1"
txv1beta1 "cosmossdk.io/api/cosmos/tx/v1beta1"
"cosmossdk.io/x/tx/signing/aminojson"
signing_testutil "cosmossdk.io/x/tx/signing/testutil"
coinswapapi "github.com/Canto-Network/Canto/v7/api/canto/coinswap/v1"
govshuttleapi "github.com/Canto-Network/Canto/v7/api/canto/govshuttle/v1"
onboardingapi "github.com/Canto-Network/Canto/v7/api/canto/onboarding/v1"
"github.com/cosmos/cosmos-sdk/tests/integration/rapidgen"
"github.com/cosmos/cosmos-sdk/testutil/testdata"
"github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/types/bech32"
"github.com/cosmos/cosmos-sdk/types/module/testutil"
signingtypes "github.com/cosmos/cosmos-sdk/types/tx/signing"
"github.com/cosmos/cosmos-sdk/x/auth/migrations/legacytx"
"github.com/cosmos/cosmos-sdk/x/auth/signing"
"github.com/cosmos/cosmos-sdk/x/auth/tx"
)
// TestAminoJSON_Equivalence tests that x/tx/Encoder encoding is equivalent to the legacy Encoder encoding.
// A custom generator is used to generate random messages that are then encoded using both encoders. The custom
// generator only supports proto.Message (which implement the protoreflect API) so in order to test legacy gogo types
// we end up with a workflow as follows:
//
// 1. Generate a random protobuf proto.Message using the custom generator
// 2. Marshal the proto.Message to protobuf binary bytes
// 3. Unmarshal the protobuf bytes to a gogoproto.Message
// 4. Marshal the gogoproto.Message to amino JSON bytes
// 5. Marshal the proto.Message to amino JSON bytes
// 6. Compare the amino JSON bytes from steps 4 and 5
//
// In order for step 3 to work certain restrictions on the data generated in step 1 must be enforced and are described
// by the mutation of genOpts passed to the generator.
func TestAminoJSON_Equivalence(t *testing.T) {
encCfg := testutil.MakeTestEncodingConfig(
coinswap.AppModuleBasic{},
csr.AppModuleBasic{},
epochs.AppModuleBasic{},
erc20.AppModuleBasic{},
govshuttle.AppModuleBasic{},
inflation.AppModuleBasic{},
onboarding.AppModuleBasic{})
legacytx.RegressionTestingAminoCodec = encCfg.Amino
aj := aminojson.NewEncoder(aminojson.EncoderOptions{DoNotSortFields: true})
GenOpts := rapidproto.GeneratorOptions{
Resolver: protoregistry.GlobalTypes,
FieldMaps: []rapidproto.FieldMapper{rapidgen.GeneratorFieldMapper},
}
testedMsgs := []rapidgen.GeneratedType{
// coinswap
rapidgen.GenType(&coinswaptypes.MsgAddLiquidity{}, &coinswapapi.MsgAddLiquidity{}, GenOpts.WithDisallowNil()),
rapidgen.GenType(&coinswaptypes.MsgRemoveLiquidity{}, &coinswapapi.MsgRemoveLiquidity{}, GenOpts.WithDisallowNil()),
rapidgen.GenType(&coinswaptypes.MsgSwapOrder{}, &coinswapapi.MsgSwapOrder{}, GenOpts.WithDisallowNil()),
rapidgen.GenType(&coinswaptypes.MsgUpdateParams{}, &coinswapapi.MsgUpdateParams{}, GenOpts.WithDisallowNil()),
rapidgen.GenType(&coinswaptypes.Params{}, &coinswapapi.Params{}, GenOpts.WithDisallowNil()),
// csr
rapidgen.GenType(&csrtypes.MsgUpdateParams{}, &csrapi.MsgUpdateParams{}, GenOpts.WithDisallowNil()),
rapidgen.GenType(&csrtypes.Params{}, &csrapi.Params{}, GenOpts.WithDisallowNil()),
// erc20
rapidgen.GenType(&erc20types.MsgRegisterCoin{}, &erc20api.MsgRegisterCoin{}, GenOpts.WithDisallowNil()),
rapidgen.GenType(&erc20types.MsgRegisterERC20{}, &erc20api.MsgRegisterERC20{}, GenOpts.WithDisallowNil()),
rapidgen.GenType(&erc20types.MsgConvertERC20{}, &erc20api.MsgConvertERC20{}, GenOpts.WithDisallowNil()),
rapidgen.GenType(&erc20types.MsgConvertCoin{}, &erc20api.MsgConvertCoin{}, GenOpts.WithDisallowNil()),
rapidgen.GenType(&erc20types.MsgUpdateParams{}, &erc20api.MsgUpdateParams{}, GenOpts.WithDisallowNil()),
rapidgen.GenType(&erc20types.Params{}, &erc20api.Params{}, GenOpts.WithDisallowNil()),
// govshuttle
rapidgen.GenType(&govshuttletypes.LendingMarketProposal{}, &govshuttleapi.LendingMarketProposal{}, GenOpts.WithDisallowNil()),
rapidgen.GenType(&govshuttletypes.TreasuryProposal{}, &govshuttleapi.TreasuryProposal{}, GenOpts.WithDisallowNil()),
// inflation
rapidgen.GenType(&inflationtypes.MsgUpdateParams{}, &inflationapi.MsgUpdateParams{}, GenOpts.WithDisallowNil()),
rapidgen.GenType(&inflationtypes.Params{}, &inflationapi.Params{}, GenOpts.WithDisallowNil()),
// onboarding
rapidgen.GenType(&onboardingtypes.MsgUpdateParams{}, &onboardingapi.MsgUpdateParams{}, GenOpts.WithDisallowNil()),
rapidgen.GenType(&onboardingtypes.Params{}, &onboardingapi.Params{}, GenOpts.WithDisallowNil()),
}
for _, tt := range testedMsgs {
desc := tt.Pulsar.ProtoReflect().Descriptor()
name := string(desc.FullName())
t.Run(name, func(t *testing.T) {
gen := rapidproto.MessageGenerator(tt.Pulsar, tt.Opts)
fmt.Printf("testing %s\n", tt.Pulsar.ProtoReflect().Descriptor().FullName())
rapid.Check(t, func(t *rapid.T) {
// uncomment to debug; catch a panic and inspect application state
// defer func() {
// if r := recover(); r != nil {
// //fmt.Printf("Panic: %+v\n", r)
// t.FailNow()
// }
// }()
msg := gen.Draw(t, "msg")
postFixPulsarMessage(msg)
gogo := tt.Gogo
sanity := tt.Pulsar
protoBz, err := proto.Marshal(msg)
require.NoError(t, err)
err = proto.Unmarshal(protoBz, sanity)
require.NoError(t, err)
err = encCfg.Codec.Unmarshal(protoBz, gogo)
require.NoError(t, err)
legacyAminoJSON, err := encCfg.Amino.MarshalJSON(gogo)
require.NoError(t, err)
aminoJSON, err := aj.Marshal(msg)
require.NoError(t, err)
require.Equal(t, string(legacyAminoJSON), string(aminoJSON))
// test amino json signer handler equivalence
if !proto.HasExtension(desc.Options(), msgv1.E_Signer) {
// not signable
return
}
handlerOptions := signing_testutil.HandlerArgumentOptions{
ChainID: "test-chain",
Memo: "sometestmemo",
Msg: tt.Pulsar,
AccNum: 1,
AccSeq: 2,
SignerAddress: "signerAddress",
Fee: &txv1beta1.Fee{
Amount: []*v1beta1.Coin{{Denom: "uatom", Amount: "1000"}},
},
}
signerData, txData, err := signing_testutil.MakeHandlerArguments(handlerOptions)
require.NoError(t, err)
handler := aminojson.NewSignModeHandler(aminojson.SignModeHandlerOptions{})
signBz, err := handler.GetSignBytes(context.Background(), signerData, txData)
require.NoError(t, err)
legacyHandler := tx.NewSignModeLegacyAminoJSONHandler()
txBuilder := encCfg.TxConfig.NewTxBuilder()
require.NoError(t, txBuilder.SetMsgs([]types.Msg{tt.Gogo}...))
txBuilder.SetMemo(handlerOptions.Memo)
txBuilder.SetFeeAmount(types.Coins{types.NewInt64Coin("uatom", 1000)})
theTx := txBuilder.GetTx()
legacySigningData := signing.SignerData{
ChainID: handlerOptions.ChainID,
Address: handlerOptions.SignerAddress,
AccountNumber: handlerOptions.AccNum,
Sequence: handlerOptions.AccSeq,
}
legacySignBz, err := legacyHandler.GetSignBytes(signingtypes.SignMode_SIGN_MODE_LEGACY_AMINO_JSON,
legacySigningData, theTx)
require.NoError(t, err)
require.Equal(t, string(legacySignBz), string(signBz))
})
})
}
}
func newAny(t *testing.T, msg proto.Message) *anypb.Any {
bz, err := proto.Marshal(msg)
require.NoError(t, err)
typeName := fmt.Sprintf("/%s", msg.ProtoReflect().Descriptor().FullName())
return &anypb.Any{
TypeUrl: typeName,
Value: bz,
}
}
func postFixPulsarMessage(msg proto.Message) {
if m, ok := msg.(*authapi.ModuleAccount); ok {
if m.BaseAccount == nil {
m.BaseAccount = &authapi.BaseAccount{}
}
_, _, bz := testdata.KeyTestPubAddr()
// always set address to a valid bech32 address
text, _ := bech32.ConvertAndEncode("cosmos", bz)
m.BaseAccount.Address = text
// see negative test
if len(m.Permissions) == 0 {
m.Permissions = nil
}
}
if m, ok := msg.(*coinswapapi.MsgUpdateParams); ok {
m.Params.MaxStandardCoinPerPool = "10"
}
if m, ok := msg.(*coinswapapi.Params); ok {
m.MaxStandardCoinPerPool = "10"
}
}
EOT
# run the test
go test app/amino_test.go || true
# modify the ethermint-main go.mod to point to the local checkout
popd
pushd 2024-05-canto/ethermint-main
mv go.mod go.mod.old
sed '7i\
replace github.com/cosmos/cosmos-sdk/tests => ../../cosmos-sdk/tests
' go.mod.old > go.mod
# prepare the test
go get github.com/cosmos/cosmos-sdk/tests@v0.50.6
go get ./...
cat <<EOT >> app/amino_test.go
package app
import (
"context"
"fmt"
evmv1 "github.com/evmos/ethermint/api/ethermint/evm/v1"
feemarketv1 "github.com/evmos/ethermint/api/ethermint/feemarket/v1"
"github.com/evmos/ethermint/x/evm"
evmtypes "github.com/evmos/ethermint/x/evm/types"
"github.com/evmos/ethermint/x/feemarket"
feemarkettypes "github.com/evmos/ethermint/x/feemarket/types"
"github.com/cosmos/cosmos-proto/rapidproto"
"github.com/stretchr/testify/require"
"google.golang.org/protobuf/proto"
"google.golang.org/protobuf/reflect/protoregistry"
"google.golang.org/protobuf/types/known/anypb"
"pgregory.net/rapid"
"testing"
authapi "cosmossdk.io/api/cosmos/auth/v1beta1"
v1beta1 "cosmossdk.io/api/cosmos/base/v1beta1"
msgv1 "cosmossdk.io/api/cosmos/msg/v1"
txv1beta1 "cosmossdk.io/api/cosmos/tx/v1beta1"
"cosmossdk.io/x/tx/signing/aminojson"
signing_testutil "cosmossdk.io/x/tx/signing/testutil"
"github.com/cosmos/cosmos-sdk/tests/integration/rapidgen"
"github.com/cosmos/cosmos-sdk/testutil/testdata"
"github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/types/bech32"
"github.com/cosmos/cosmos-sdk/types/module/testutil"
signingtypes "github.com/cosmos/cosmos-sdk/types/tx/signing"
"github.com/cosmos/cosmos-sdk/x/auth/migrations/legacytx"
"github.com/cosmos/cosmos-sdk/x/auth/signing"
"github.com/cosmos/cosmos-sdk/x/auth/tx"
)
// TestAminoJSON_Equivalence tests that x/tx/Encoder encoding is equivalent to the legacy Encoder encoding.
// A custom generator is used to generate random messages that are then encoded using both encoders. The custom
// generator only supports proto.Message (which implement the protoreflect API) so in order to test legacy gogo types
// we end up with a workflow as follows:
//
// 1. Generate a random protobuf proto.Message using the custom generator
// 2. Marshal the proto.Message to protobuf binary bytes
// 3. Unmarshal the protobuf bytes to a gogoproto.Message
// 4. Marshal the gogoproto.Message to amino JSON bytes
// 5. Marshal the proto.Message to amino JSON bytes
// 6. Compare the amino JSON bytes from steps 4 and 5
//
// In order for step 3 to work certain restrictions on the data generated in step 1 must be enforced and are described
// by the mutation of genOpts passed to the generator.
func TestAminoJSON_Equivalence(t *testing.T) {
encCfg := testutil.MakeTestEncodingConfig(
evm.AppModuleBasic{},
feemarket.AppModuleBasic{})
legacytx.RegressionTestingAminoCodec = encCfg.Amino
aj := aminojson.NewEncoder(aminojson.EncoderOptions{DoNotSortFields: true})
GenOpts := rapidproto.GeneratorOptions{
Resolver: protoregistry.GlobalTypes,
FieldMaps: []rapidproto.FieldMapper{rapidgen.GeneratorFieldMapper},
}
testedMsgs := []rapidgen.GeneratedType{
// evm
rapidgen.GenType(&evmtypes.MsgEthereumTx{}, &evmv1.MsgEthereumTx{}, GenOpts.WithDisallowNil()),
rapidgen.GenType(&evmtypes.MsgUpdateParams{}, &evmv1.MsgUpdateParams{}, GenOpts.WithDisallowNil()),
rapidgen.GenType(&evmtypes.Params{}, &evmv1.Params{}, GenOpts.WithDisallowNil()),
// feemarket
rapidgen.GenType(&feemarkettypes.MsgUpdateParams{}, &feemarketv1.MsgUpdateParams{}, GenOpts.WithDisallowNil()),
rapidgen.GenType(&feemarkettypes.Params{}, &feemarketv1.Params{}, GenOpts.WithDisallowNil()),
}
for _, tt := range testedMsgs {
desc := tt.Pulsar.ProtoReflect().Descriptor()
name := string(desc.FullName())
t.Run(name, func(t *testing.T) {
gen := rapidproto.MessageGenerator(tt.Pulsar, tt.Opts)
fmt.Printf("testing %s\n", tt.Pulsar.ProtoReflect().Descriptor().FullName())
rapid.Check(t, func(t *rapid.T) {
// uncomment to debug; catch a panic and inspect application state
// defer func() {
// if r := recover(); r != nil {
// //fmt.Printf("Panic: %+v\n", r)
// t.FailNow()
// }
// }()
msg := gen.Draw(t, "msg")
postFixPulsarMessage(msg)
gogo := tt.Gogo
sanity := tt.Pulsar
protoBz, err := proto.Marshal(msg)
require.NoError(t, err)
err = proto.Unmarshal(protoBz, sanity)
require.NoError(t, err)
err = encCfg.Codec.Unmarshal(protoBz, gogo)
require.NoError(t, err)
legacyAminoJSON, err := encCfg.Amino.MarshalJSON(gogo)
require.NoError(t, err)
aminoJSON, err := aj.Marshal(msg)
require.NoError(t, err)
require.Equal(t, string(legacyAminoJSON), string(aminoJSON))
// test amino json signer handler equivalence
if !proto.HasExtension(desc.Options(), msgv1.E_Signer) {
// not signable
return
}
handlerOptions := signing_testutil.HandlerArgumentOptions{
ChainID: "test-chain",
Memo: "sometestmemo",
Msg: tt.Pulsar,
AccNum: 1,
AccSeq: 2,
SignerAddress: "signerAddress",
Fee: &txv1beta1.Fee{
Amount: []*v1beta1.Coin{{Denom: "uatom", Amount: "1000"}},
},
}
signerData, txData, err := signing_testutil.MakeHandlerArguments(handlerOptions)
require.NoError(t, err)
handler := aminojson.NewSignModeHandler(aminojson.SignModeHandlerOptions{})
signBz, err := handler.GetSignBytes(context.Background(), signerData, txData)
require.NoError(t, err)
legacyHandler := tx.NewSignModeLegacyAminoJSONHandler()
txBuilder := encCfg.TxConfig.NewTxBuilder()
require.NoError(t, txBuilder.SetMsgs([]types.Msg{tt.Gogo}...))
txBuilder.SetMemo(handlerOptions.Memo)
txBuilder.SetFeeAmount(types.Coins{types.NewInt64Coin("uatom", 1000)})
theTx := txBuilder.GetTx()
legacySigningData := signing.SignerData{
ChainID: handlerOptions.ChainID,
Address: handlerOptions.SignerAddress,
AccountNumber: handlerOptions.AccNum,
Sequence: handlerOptions.AccSeq,
}
legacySignBz, err := legacyHandler.GetSignBytes(signingtypes.SignMode_SIGN_MODE_LEGACY_AMINO_JSON,
legacySigningData, theTx)
require.NoError(t, err)
require.Equal(t, string(legacySignBz), string(signBz))
})
})
}
}
func newAny(t *testing.T, msg proto.Message) *anypb.Any {
bz, err := proto.Marshal(msg)
require.NoError(t, err)
typeName := fmt.Sprintf("/%s", msg.ProtoReflect().Descriptor().FullName())
return &anypb.Any{
TypeUrl: typeName,
Value: bz,
}
}
func postFixPulsarMessage(msg proto.Message) {
if m, ok := msg.(*authapi.ModuleAccount); ok {
if m.BaseAccount == nil {
m.BaseAccount = &authapi.BaseAccount{}
}
_, _, bz := testdata.KeyTestPubAddr()
// always set address to a valid bech32 address
text, _ := bech32.ConvertAndEncode("cosmos", bz)
m.BaseAccount.Address = text
// see negative test
if len(m.Permissions) == 0 {
m.Permissions = nil
}
}
if m, ok := msg.(*evmv1.MsgUpdateParams); ok {
m.Params.ChainConfig.CancunBlock = "10"
}
if m, ok := msg.(*evmv1.Params); ok {
m.ChainConfig.CancunBlock = "10"
}
if m, ok := msg.(*feemarketv1.MsgUpdateParams); ok {
m.Params.BaseFee = "10"
}
if m, ok := msg.(*feemarketv1.Params); ok {
m.BaseFee = "10"
}
}
EOT
# run the test
go test app/amino_test.go || true
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment