test(x/accounts): add tests for base account and cli (#20826)

This commit is contained in:
son trinh 2024-07-03 16:57:25 +07:00 committed by GitHub
parent 7b23e52b0b
commit 2718b7f6e0
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 535 additions and 1 deletions

View File

@ -27,6 +27,14 @@ func NewMockCometRPC(respQuery abci.QueryResponse) MockCometRPC {
return MockCometRPC{responseQuery: respQuery}
}
// NewMockCometRPCWithValue returns a mock CometBFT RPC implementation with value only.
// It is used for CLI testing.
func NewMockCometRPCWithValue(bz []byte) MockCometRPC {
return MockCometRPC{responseQuery: abci.QueryResponse{
Value: bz,
}}
}
func (MockCometRPC) BroadcastTxSync(context.Context, cmttypes.Tx) (*coretypes.ResultBroadcastTx, error) {
return &coretypes.ResultBroadcastTx{Code: 0}, nil
}

145
x/accounts/cli/cli_test.go Normal file
View File

@ -0,0 +1,145 @@
package cli_test
import (
"context"
"fmt"
"io"
"testing"
"github.com/gogo/protobuf/types"
"github.com/stretchr/testify/suite"
"cosmossdk.io/math"
"cosmossdk.io/x/accounts/cli"
v1 "cosmossdk.io/x/accounts/v1"
"cosmossdk.io/x/bank"
"github.com/cosmos/cosmos-sdk/client"
"github.com/cosmos/cosmos-sdk/client/flags"
addresscodec "github.com/cosmos/cosmos-sdk/codec/address"
codectestutil "github.com/cosmos/cosmos-sdk/codec/testutil"
"github.com/cosmos/cosmos-sdk/crypto/keyring"
svrcmd "github.com/cosmos/cosmos-sdk/server/cmd"
"github.com/cosmos/cosmos-sdk/testutil"
clitestutil "github.com/cosmos/cosmos-sdk/testutil/cli"
sdk "github.com/cosmos/cosmos-sdk/types"
testutilmod "github.com/cosmos/cosmos-sdk/types/module/testutil"
)
type CLITestSuite struct {
suite.Suite
kr keyring.Keyring
encCfg testutilmod.TestEncodingConfig
baseCtx client.Context
clientCtx client.Context
}
func TestCLITestSuite(t *testing.T) {
suite.Run(t, new(CLITestSuite))
}
func (s *CLITestSuite) SetupSuite() {
s.encCfg = testutilmod.MakeTestEncodingConfig(codectestutil.CodecOptions{}, bank.AppModule{})
s.kr = keyring.NewInMemory(s.encCfg.Codec)
s.baseCtx = client.Context{}.
WithKeyring(s.kr).
WithTxConfig(s.encCfg.TxConfig).
WithCodec(s.encCfg.Codec).
WithAccountRetriever(client.MockAccountRetriever{}).
WithOutput(io.Discard).
WithAddressCodec(addresscodec.NewBech32Codec("cosmos")).
WithValidatorAddressCodec(addresscodec.NewBech32Codec("cosmosvaloper")).
WithConsensusAddressCodec(addresscodec.NewBech32Codec("cosmosvalcons"))
}
func (s *CLITestSuite) TestTxInitCmd() {
accounts := testutil.CreateKeyringAccounts(s.T(), s.kr, 1)
accountStr := make([]string, len(accounts))
for i, acc := range accounts {
addrStr, err := s.baseCtx.AddressCodec.BytesToString(acc.Address)
s.Require().NoError(err)
accountStr[i] = addrStr
}
s.baseCtx = s.baseCtx.WithFromAddress(accounts[0].Address)
extraArgs := []string{
fmt.Sprintf("--%s=true", flags.FlagSkipConfirmation),
fmt.Sprintf("--%s=%s", flags.FlagBroadcastMode, flags.BroadcastSync),
fmt.Sprintf("--%s=%s", flags.FlagFees, sdk.NewCoins(sdk.NewCoin("photon", math.NewInt(10))).String()),
fmt.Sprintf("--%s=test-chain", flags.FlagChainID),
fmt.Sprintf("--%s=%s", flags.FlagFrom, accountStr[0]),
}
cmd := cli.GetTxInitCmd()
cmd.SetOutput(io.Discard)
ctxGen := func() client.Context {
bz, _ := s.encCfg.Codec.Marshal(&v1.SchemaResponse{
InitSchema: &v1.SchemaResponse_Handler{
Request: sdk.MsgTypeURL(&types.Empty{})[1:],
Response: sdk.MsgTypeURL(&types.Empty{})[1:],
},
})
c := clitestutil.NewMockCometRPCWithValue(bz)
return s.baseCtx.WithClient(c)
}
s.clientCtx = ctxGen()
testCases := []struct {
name string
accountType string
jsonMsg string
extraArgs []string
expectErrMsg string
}{
{
name: "valid json msg",
accountType: "test",
jsonMsg: `{}`,
extraArgs: extraArgs,
expectErrMsg: "",
},
{
name: "invalid json msg",
accountType: "test",
jsonMsg: `{"test": "jsonmsg"}`,
extraArgs: extraArgs,
expectErrMsg: "provided message is not valid",
},
{
name: "invalid sender",
accountType: "test",
jsonMsg: `{}`,
extraArgs: append(extraArgs, fmt.Sprintf("--%s=%s", flags.FlagFrom, "bar")),
expectErrMsg: "failed to convert address field to address",
},
}
for _, tc := range testCases {
tc := tc
s.Run(tc.name, func() {
ctx := svrcmd.CreateExecuteContext(context.Background())
var args []string
args = append(args, tc.accountType)
args = append(args, tc.jsonMsg)
args = append(args, tc.extraArgs...)
cmd.SetContext(ctx)
cmd.SetArgs(args)
out, err := clitestutil.ExecTestCLICmd(s.clientCtx, cmd, args)
if tc.expectErrMsg != "" {
s.Require().Error(err)
s.Require().Contains(out.String(), tc.expectErrMsg)
} else {
s.Require().NoError(err)
msg := &sdk.TxResponse{}
s.Require().NoError(s.clientCtx.Codec.UnmarshalJSON(out.Bytes(), msg), out.String())
}
})
}
}

View File

@ -0,0 +1,253 @@
package base
import (
"context"
"errors"
"testing"
"github.com/stretchr/testify/require"
"cosmossdk.io/core/store"
"cosmossdk.io/x/accounts/accountstd"
v1 "cosmossdk.io/x/accounts/defaults/base/v1"
aa_interface_v1 "cosmossdk.io/x/accounts/interfaces/account_abstraction/v1"
"cosmossdk.io/x/tx/signing"
codectypes "github.com/cosmos/cosmos-sdk/codec/types"
"github.com/cosmos/cosmos-sdk/crypto/keys/secp256k1"
"github.com/cosmos/cosmos-sdk/types/address"
"github.com/cosmos/cosmos-sdk/types/tx"
)
func setupBaseAccount(t *testing.T, ss store.KVStoreService) Account {
t.Helper()
deps := makeMockDependencies(ss)
handler := directHandler{}
createAccFn := NewAccount("base", signing.NewHandlerMap(handler))
_, acc, err := createAccFn(deps)
baseAcc := acc.(Account)
require.NoError(t, err)
return baseAcc
}
func TestInit(t *testing.T) {
ctx, ss := newMockContext(t)
baseAcc := setupBaseAccount(t, ss)
_, err := baseAcc.Init(ctx, &v1.MsgInit{
PubKey: secp256k1.GenPrivKey().PubKey().Bytes(),
})
require.NoError(t, err)
testcases := []struct {
name string
msg *v1.MsgInit
isExpErr bool
}{
{
"valid init",
&v1.MsgInit{
PubKey: secp256k1.GenPrivKey().PubKey().Bytes(),
},
false,
},
{
"invalid pubkey",
&v1.MsgInit{
PubKey: []byte("invalid_pk"),
},
true,
},
}
for _, tc := range testcases {
t.Run(tc.name, func(t *testing.T) {
_, err := baseAcc.Init(ctx, tc.msg)
if tc.isExpErr {
require.NotNil(t, err, tc.name)
return
}
require.NoError(t, err)
})
}
}
func TestSwapKey(t *testing.T) {
ctx, ss := newMockContext(t)
baseAcc := setupBaseAccount(t, ss)
_, err := baseAcc.Init(ctx, &v1.MsgInit{
PubKey: secp256k1.GenPrivKey().PubKey().Bytes(),
})
require.NoError(t, err)
testcases := []struct {
name string
genCtx func(ctx context.Context) context.Context
msg *v1.MsgSwapPubKey
isExpErr bool
expErr error
}{
{
"valid transaction",
func(ctx context.Context) context.Context {
return accountstd.SetSender(ctx, []byte("mock_base_account"))
},
&v1.MsgSwapPubKey{
NewPubKey: secp256k1.GenPrivKey().PubKey().Bytes(),
},
false,
nil,
},
{
"invalid transaction, sender is not self",
func(ctx context.Context) context.Context {
return accountstd.SetSender(ctx, []byte("sender"))
},
&v1.MsgSwapPubKey{
NewPubKey: secp256k1.GenPrivKey().PubKey().Bytes(),
},
true,
errors.New("unauthorized"),
},
{
"invalid transaction, invalid pubkey",
func(ctx context.Context) context.Context {
return accountstd.SetSender(ctx, []byte("mock_base_account"))
},
&v1.MsgSwapPubKey{
NewPubKey: []byte("invalid_pk"),
},
true,
nil,
},
}
for _, tc := range testcases {
t.Run(tc.name, func(t *testing.T) {
if tc.genCtx != nil {
ctx = tc.genCtx(ctx)
}
_, err := baseAcc.SwapPubKey(ctx, tc.msg)
if tc.isExpErr {
if tc.expErr != nil {
require.Equal(t, tc.expErr, err, tc.name)
} else {
require.NotNil(t, err, tc.name)
}
return
}
require.NoError(t, err)
})
}
}
func TestAuthenticate(t *testing.T) {
ctx, ss := newMockContext(t)
baseAcc := setupBaseAccount(t, ss)
privKey := secp256k1.GenPrivKey()
pkAny, err := codectypes.NewAnyWithValue(privKey.PubKey())
require.NoError(t, err)
_, err = baseAcc.Init(ctx, &v1.MsgInit{
PubKey: privKey.PubKey().Bytes(),
})
require.NoError(t, err)
ctx = accountstd.SetSender(ctx, address.Module("accounts"))
require.NoError(t, err)
transaction := tx.Tx{
Body: &tx.TxBody{},
AuthInfo: &tx.AuthInfo{
SignerInfos: []*tx.SignerInfo{
{
PublicKey: pkAny,
ModeInfo: &tx.ModeInfo{
Sum: &tx.ModeInfo_Single_{
Single: &tx.ModeInfo_Single{
Mode: 1,
},
},
},
Sequence: 0,
},
},
},
Signatures: [][]byte{},
}
bodyByte, err := transaction.Body.Marshal()
require.NoError(t, err)
authByte, err := transaction.AuthInfo.Marshal()
require.NoError(t, err)
txDoc := tx.SignDoc{
BodyBytes: bodyByte,
AuthInfoBytes: authByte,
ChainId: "test",
AccountNumber: 1,
}
signBytes, err := txDoc.Marshal()
require.NoError(t, err)
sig, err := privKey.Sign(signBytes)
require.NoError(t, err)
transaction.Signatures = append(transaction.Signatures, sig)
rawTx := tx.TxRaw{
BodyBytes: bodyByte,
AuthInfoBytes: authByte,
Signatures: transaction.Signatures,
}
_, err = baseAcc.Authenticate(ctx, &aa_interface_v1.MsgAuthenticate{
RawTx: &rawTx,
Tx: &transaction,
SignerIndex: 0,
})
require.NoError(t, err)
// testing with invalid signature
// update sequence number
transaction = tx.Tx{
Body: &tx.TxBody{},
AuthInfo: &tx.AuthInfo{
SignerInfos: []*tx.SignerInfo{
{
PublicKey: pkAny,
ModeInfo: &tx.ModeInfo{
Sum: &tx.ModeInfo_Single_{
Single: &tx.ModeInfo_Single{
Mode: 1,
},
},
},
Sequence: 1,
},
},
},
Signatures: [][]byte{},
}
authByte, err = transaction.AuthInfo.Marshal()
require.NoError(t, err)
txDoc.BodyBytes = []byte("invalid_msg")
txDoc.AuthInfoBytes = authByte
signBytes, err = txDoc.Marshal()
require.NoError(t, err)
invalidSig, err := privKey.Sign(signBytes)
require.NoError(t, err)
transaction.Signatures = append([][]byte{}, invalidSig)
rawTx.Signatures = transaction.Signatures
_, err = baseAcc.Authenticate(ctx, &aa_interface_v1.MsgAuthenticate{
RawTx: &rawTx,
Tx: &transaction,
SignerIndex: 0,
})
require.Equal(t, errors.New("signature verification failed"), err)
}

View File

@ -0,0 +1,122 @@
package base
import (
"context"
"testing"
gogoproto "github.com/cosmos/gogoproto/proto"
"github.com/stretchr/testify/require"
"google.golang.org/protobuf/runtime/protoiface"
signingv1beta1 "cosmossdk.io/api/cosmos/tx/signing/v1beta1"
"cosmossdk.io/collections"
"cosmossdk.io/core/appmodule/v2"
"cosmossdk.io/core/event"
"cosmossdk.io/core/header"
"cosmossdk.io/core/store"
"cosmossdk.io/x/accounts/accountstd"
accountsv1 "cosmossdk.io/x/accounts/v1"
"cosmossdk.io/x/tx/signing"
"github.com/cosmos/cosmos-sdk/codec"
"github.com/cosmos/cosmos-sdk/runtime"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/types/tx"
)
type ProtoMsg = protoiface.MessageV1
// mock statecodec
type mockStateCodec struct {
codec.Codec
}
var _ codec.Codec = mockStateCodec{}
func (c mockStateCodec) Marshal(m gogoproto.Message) ([]byte, error) {
// Size() check can catch the typed nil value.
if m == nil || gogoproto.Size(m) == 0 {
// return empty bytes instead of nil, because nil has special meaning in places like store.Set
return []byte{}, nil
}
return gogoproto.Marshal(m)
}
func (c mockStateCodec) Unmarshal(bz []byte, ptr gogoproto.Message) error {
err := gogoproto.Unmarshal(bz, ptr)
return err
}
// mock address codec
type addressCodec struct{}
func (a addressCodec) StringToBytes(text string) ([]byte, error) { return []byte(text), nil }
func (a addressCodec) BytesToString(bz []byte) (string, error) { return string(bz), nil }
func newMockContext(t *testing.T) (context.Context, store.KVStoreService) {
t.Helper()
return accountstd.NewMockContext(
0, []byte("mock_base_account"), []byte("sender"), nil, func(ctx context.Context, sender []byte, msg, msgResp ProtoMsg) error {
return nil
}, func(ctx context.Context, sender []byte, msg ProtoMsg) (ProtoMsg, error) {
return nil, nil
}, func(ctx context.Context, req, resp ProtoMsg) error {
_, ok := req.(*accountsv1.AccountNumberRequest)
require.True(t, ok)
gogoproto.Merge(resp.(gogoproto.Message), &accountsv1.AccountNumberResponse{
Number: 1,
})
return nil
},
)
}
func makeMockDependencies(storeservice store.KVStoreService) accountstd.Dependencies {
sb := collections.NewSchemaBuilder(storeservice)
return accountstd.Dependencies{
SchemaBuilder: sb,
AddressCodec: addressCodec{},
LegacyStateCodec: mockStateCodec{},
Environment: appmodule.Environment{
EventService: eventService{},
HeaderService: headerService{},
},
}
}
type headerService struct{}
func (h headerService) HeaderInfo(context.Context) header.Info {
return header.Info{
ChainID: "test",
}
}
type eventService struct{}
// EventManager implements event.Service.
func (eventService) EventManager(context.Context) event.Manager {
return runtime.EventService{Events: runtime.Events{EventManagerI: sdk.NewEventManager()}}
}
var _ signing.SignModeHandler = directHandler{}
type directHandler struct{}
func (s directHandler) Mode() signingv1beta1.SignMode {
return signingv1beta1.SignMode_SIGN_MODE_DIRECT
}
func (s directHandler) GetSignBytes(_ context.Context, signerData signing.SignerData, txData signing.TxData) ([]byte, error) {
txDoc := tx.SignDoc{
BodyBytes: txData.BodyBytes,
AuthInfoBytes: txData.AuthInfoBytes,
ChainId: signerData.ChainID,
AccountNumber: signerData.AccountNumber,
}
return txDoc.Marshal()
}

View File

@ -19,6 +19,8 @@ require (
google.golang.org/protobuf v1.34.2
)
require github.com/golang/mock v1.6.0 // indirect
require github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
require (
@ -76,7 +78,7 @@ require (
github.com/godbus/dbus v0.0.0-20190726142602-4481cbc300e2 // indirect
github.com/gofrs/uuid v4.4.0+incompatible // indirect
github.com/gogo/googleapis v1.4.1 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/gogo/protobuf v1.3.2
github.com/golang/glog v1.2.0 // indirect
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
github.com/golang/protobuf v1.5.4 // indirect

View File

@ -482,6 +482,7 @@ github.com/tidwall/btree v1.7.0/go.mod h1:twD9XRA5jj9VUQGELzDO4HPQTNJsoWWfYEL+EU
github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
github.com/zondax/hid v0.9.2 h1:WCJFnEDMiqGF64nlZz28E9qLVZ0KSJ7xpc5DLEyma2U=
github.com/zondax/hid v0.9.2/go.mod h1:l5wttcP0jwtdLjqjMMWFVEE7d1zO0jvSPA9OPZxWpEM=
github.com/zondax/ledger-go v0.14.3 h1:wEpJt2CEcBJ428md/5MgSLsXLBos98sBOyxNmCjfUCw=
@ -517,6 +518,7 @@ golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHl
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA=
golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@ -549,6 +551,7 @@ golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJ
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M=
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@ -611,6 +614,7 @@ golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtn
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d h1:vU5i/LfpvrRCpgM/VPfJLg5KjxD3E+hfT1SH+d9zLwg=
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=