From 936cb04c3b7adfb40bde72aa6aa69d897c9c990d Mon Sep 17 00:00:00 2001 From: sontrinh16 Date: Mon, 16 Dec 2024 13:33:52 +0700 Subject: [PATCH 1/4] add accounts integration tests and update integration branch service --- .../v2/accounts/base_account_test.go | 118 ++++++++ tests/integration/v2/accounts/bundler_test.go | 262 ++++++++++++++++++ tests/integration/v2/accounts/fixture_test.go | 211 ++++++++++++++ tests/integration/v2/accounts/wiring_test.go | 67 +++++ tests/integration/v2/services.go | 45 ++- x/accounts/keeper_account_abstraction.go | 3 + 6 files changed, 704 insertions(+), 2 deletions(-) create mode 100644 tests/integration/v2/accounts/base_account_test.go create mode 100644 tests/integration/v2/accounts/bundler_test.go create mode 100644 tests/integration/v2/accounts/fixture_test.go create mode 100644 tests/integration/v2/accounts/wiring_test.go diff --git a/tests/integration/v2/accounts/base_account_test.go b/tests/integration/v2/accounts/base_account_test.go new file mode 100644 index 000000000000..5f239d36c083 --- /dev/null +++ b/tests/integration/v2/accounts/base_account_test.go @@ -0,0 +1,118 @@ +package accounts + +import ( + "math/rand" + "testing" + + gogoproto "github.com/cosmos/gogoproto/proto" + gogoany "github.com/cosmos/gogoproto/types/any" + "github.com/stretchr/testify/require" + + "cosmossdk.io/simapp" + baseaccountv1 "cosmossdk.io/x/accounts/defaults/base/v1" + "cosmossdk.io/x/bank/testutil" + banktypes "cosmossdk.io/x/bank/types" + + codectypes "github.com/cosmos/cosmos-sdk/codec/types" + "github.com/cosmos/cosmos-sdk/crypto/keys/secp256k1" + cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" + "github.com/cosmos/cosmos-sdk/testutil/sims" + sdk "github.com/cosmos/cosmos-sdk/types" +) + +var ( + privKey = secp256k1.GenPrivKey() + accCreator = []byte("creator") +) + +func TestBaseAccount(t *testing.T) { + app := setupApp(t) + ak := app.AccountsKeeper + ctx := sdk.NewContext(app.CommitMultiStore(), false, app.Logger()) + + _, baseAccountAddr, err := ak.Init(ctx, "base", accCreator, &baseaccountv1.MsgInit{ + PubKey: toAnyPb(t, privKey.PubKey()), + }, nil, nil) + require.NoError(t, err) + + // fund base account! this will also cause an auth base account to be created + // by the bank module. + // TODO: fixed by letting x/auth rely on x/accounts for acc existence checks. + fundAccount(t, app, ctx, baseAccountAddr, "1000000stake") + + // now we make the account send a tx, public key not present. + // so we know it will default to x/accounts calling. + msg := &banktypes.MsgSend{ + FromAddress: bechify(t, app, baseAccountAddr), + ToAddress: bechify(t, app, []byte("random-addr")), + Amount: coins(t, "100stake"), + } + sendTx(t, ctx, app, baseAccountAddr, msg) +} + +func sendTx(t *testing.T, ctx sdk.Context, app *simapp.SimApp, sender []byte, msg sdk.Msg) { + t.Helper() + tx := sign(t, ctx, app, sender, privKey, msg) + _, _, err := app.SimDeliver(app.TxEncode, tx) + require.NoError(t, err) +} + +func sign(t *testing.T, ctx sdk.Context, app *simapp.SimApp, from sdk.AccAddress, privKey cryptotypes.PrivKey, msg sdk.Msg) sdk.Tx { + t.Helper() + r := rand.New(rand.NewSource(0)) + + accNum, err := app.AccountsKeeper.AccountByNumber.Get(ctx, from) + require.NoError(t, err) + accSeq, err := app.AccountsKeeper.Query(ctx, from, &baseaccountv1.QuerySequence{}) + require.NoError(t, err) + + tx, err := sims.GenSignedMockTx( + r, + app.TxConfig(), + []sdk.Msg{msg}, + coins(t, "100stake"), + 1_000_000, + app.ChainID(), + []uint64{accNum}, + []uint64{accSeq.(*baseaccountv1.QuerySequenceResponse).Sequence}, + privKey, + ) + + require.NoError(t, err) + return tx +} + +func bechify(t *testing.T, app *simapp.SimApp, addr []byte) string { + t.Helper() + bech32, err := app.AuthKeeper.AddressCodec().BytesToString(addr) + require.NoError(t, err) + return bech32 +} + +func fundAccount(t *testing.T, app *simapp.SimApp, ctx sdk.Context, addr sdk.AccAddress, amt string) { + t.Helper() + require.NoError(t, testutil.FundAccount(ctx, app.BankKeeper, addr, coins(t, amt))) +} + +func toAnyPb(t *testing.T, pm gogoproto.Message) *codectypes.Any { + t.Helper() + if gogoproto.MessageName(pm) == gogoproto.MessageName(&gogoany.Any{}) { + t.Fatal("no") + } + pb, err := codectypes.NewAnyWithValue(pm) + require.NoError(t, err) + return pb +} + +func coins(t *testing.T, s string) sdk.Coins { + t.Helper() + coins, err := sdk.ParseCoinsNormalized(s) + require.NoError(t, err) + return coins +} + +func setupApp(t *testing.T) *simapp.SimApp { + t.Helper() + app := simapp.Setup(t, false) + return app +} diff --git a/tests/integration/v2/accounts/bundler_test.go b/tests/integration/v2/accounts/bundler_test.go new file mode 100644 index 000000000000..2fb88983ddf6 --- /dev/null +++ b/tests/integration/v2/accounts/bundler_test.go @@ -0,0 +1,262 @@ +package accounts + +import ( + "context" + "fmt" + "testing" + + gogoproto "github.com/cosmos/gogoproto/proto" + "github.com/stretchr/testify/require" + + account_abstractionv1 "cosmossdk.io/x/accounts/interfaces/account_abstraction/v1" + banktypes "cosmossdk.io/x/bank/types" + + codectypes "github.com/cosmos/cosmos-sdk/codec/types" + sdk "github.com/cosmos/cosmos-sdk/types" + txtypes "github.com/cosmos/cosmos-sdk/types/tx" + signingtypes "github.com/cosmos/cosmos-sdk/types/tx/signing" +) + +func TestMsgServer_ExecuteBundle(t *testing.T) { + t.Run("bundle success", func(t *testing.T) { + f := initFixture(t, func(ctx context.Context, msg *account_abstractionv1.MsgAuthenticate) (*account_abstractionv1.MsgAuthenticateResponse, error) { + return &account_abstractionv1.MsgAuthenticateResponse{}, nil + }) + + recipient := f.mustAddr([]byte("recipient")) + feeAmt := sdk.NewInt64Coin("atom", 100) + sendAmt := sdk.NewInt64Coin("atom", 200) + + f.mint(f.mockAccountAddress, feeAmt, sendAmt) + + tx := makeTx(t, &banktypes.MsgSend{ + FromAddress: f.mustAddr(f.mockAccountAddress), + ToAddress: recipient, + Amount: sdk.NewCoins(sendAmt), + }, []byte("pass"), &account_abstractionv1.TxExtension{ + AuthenticationGasLimit: 2400, + BundlerPaymentMessages: []*codectypes.Any{wrapAny(t, &banktypes.MsgSend{ + FromAddress: f.mustAddr(f.mockAccountAddress), + ToAddress: f.bundler, + Amount: sdk.NewCoins(feeAmt), + })}, + BundlerPaymentGasLimit: 30000, + ExecutionGasLimit: 30000, + }) + + bundleResp := f.runBundle(tx) + require.Len(t, bundleResp.Responses, 1) + + txResp := bundleResp.Responses[0] + + require.Empty(t, txResp.Error) + require.NotZero(t, txResp.AuthenticationGasUsed) + require.NotZero(t, txResp.BundlerPaymentGasUsed) + require.NotZero(t, txResp.ExecutionGasUsed) + + // asses responses + require.Len(t, txResp.BundlerPaymentResponses, 1) + require.Equal(t, txResp.BundlerPaymentResponses[0].TypeUrl, "/cosmos.bank.v1beta1.MsgSendResponse") + + require.Len(t, txResp.ExecutionResponses, 1) + require.Equal(t, txResp.ExecutionResponses[0].TypeUrl, "/cosmos.bank.v1beta1.MsgSendResponse") + + // ensure sends have happened + require.Equal(t, f.balance(f.bundler, feeAmt.Denom), feeAmt) + require.Equal(t, f.balance(recipient, sendAmt.Denom), sendAmt) + }) + + t.Run("tx fails at auth step", func(t *testing.T) { + f := initFixture(t, func(ctx context.Context, msg *account_abstractionv1.MsgAuthenticate) (*account_abstractionv1.MsgAuthenticateResponse, error) { + return &account_abstractionv1.MsgAuthenticateResponse{}, fmt.Errorf("sentinel") + }) + recipient := f.mustAddr([]byte("recipient")) + feeAmt := sdk.NewInt64Coin("atom", 100) + sendAmt := sdk.NewInt64Coin("atom", 200) + f.mint(f.mockAccountAddress, feeAmt, sendAmt) + + tx := makeTx(t, &banktypes.MsgSend{ + FromAddress: f.mustAddr(f.mockAccountAddress), + ToAddress: recipient, + Amount: sdk.NewCoins(sendAmt), + }, []byte("pass"), &account_abstractionv1.TxExtension{ + AuthenticationGasLimit: 2400, + BundlerPaymentMessages: []*codectypes.Any{wrapAny(t, &banktypes.MsgSend{ + FromAddress: f.mustAddr(f.mockAccountAddress), + ToAddress: f.bundler, + Amount: sdk.NewCoins(feeAmt), + })}, + BundlerPaymentGasLimit: 30000, + ExecutionGasLimit: 30000, + }) + + bundleResp := f.runBundle(tx) + + require.Len(t, bundleResp.Responses, 1) + + txResp := bundleResp.Responses[0] + require.NotEmpty(t, txResp.Error) + require.Contains(t, txResp.Error, "sentinel") + require.NotZero(t, txResp.AuthenticationGasUsed) + require.Zero(t, txResp.BundlerPaymentGasUsed) + require.Zero(t, txResp.ExecutionGasUsed) + require.Empty(t, txResp.BundlerPaymentResponses) + require.Empty(t, txResp.ExecutionResponses) + + // ensure auth side effects are not persisted in case of failures + }) + + t.Run("tx fails at pay bundler step", func(t *testing.T) { + f := initFixture(t, func(ctx context.Context, msg *account_abstractionv1.MsgAuthenticate) (*account_abstractionv1.MsgAuthenticateResponse, error) { + return &account_abstractionv1.MsgAuthenticateResponse{}, nil + }) + + recipient := f.mustAddr([]byte("recipient")) + feeAmt := sdk.NewInt64Coin("atom", 100) + sendAmt := sdk.NewInt64Coin("atom", 200) + + f.mint(f.mockAccountAddress, feeAmt, sendAmt) + + tx := makeTx(t, &banktypes.MsgSend{ + FromAddress: f.mustAddr(f.mockAccountAddress), + ToAddress: recipient, + Amount: sdk.NewCoins(sendAmt), + }, []byte("pass"), &account_abstractionv1.TxExtension{ + AuthenticationGasLimit: 2400, + BundlerPaymentMessages: []*codectypes.Any{ + wrapAny(t, &banktypes.MsgSend{ + FromAddress: f.mustAddr(f.mockAccountAddress), + ToAddress: f.bundler, + Amount: sdk.NewCoins(feeAmt.AddAmount(feeAmt.Amount.AddRaw(100))), + }), + wrapAny(t, &banktypes.MsgSend{ + FromAddress: f.mustAddr(f.mockAccountAddress), + ToAddress: f.bundler, + Amount: sdk.NewCoins(feeAmt.AddAmount(feeAmt.Amount.AddRaw(30000))), + }), + }, + BundlerPaymentGasLimit: 30000, + ExecutionGasLimit: 30000, + }) + + bundleResp := f.runBundle(tx) + require.Len(t, bundleResp.Responses, 1) + + txResp := bundleResp.Responses[0] + + require.NotEmpty(t, txResp.Error) + require.Contains(t, txResp.Error, "bundler payment failed") + require.NotZero(t, txResp.AuthenticationGasUsed) + require.NotZero(t, txResp.BundlerPaymentGasUsed) + + require.Empty(t, txResp.BundlerPaymentResponses) + require.Zero(t, txResp.ExecutionGasUsed) + require.Empty(t, txResp.ExecutionResponses) + + // ensure bundler payment side effects are not persisted + require.True(t, f.balance(f.bundler, feeAmt.Denom).IsZero()) + }) + + t.Run("tx fails at execution step", func(t *testing.T) { + f := initFixture(t, func(ctx context.Context, msg *account_abstractionv1.MsgAuthenticate) (*account_abstractionv1.MsgAuthenticateResponse, error) { + return &account_abstractionv1.MsgAuthenticateResponse{}, nil + }) + + recipient := f.mustAddr([]byte("recipient")) + feeAmt := sdk.NewInt64Coin("atom", 100) + sendAmt := sdk.NewInt64Coin("atom", 40000) // this fails + + f.mint(f.mockAccountAddress, feeAmt) + + tx := makeTx(t, &banktypes.MsgSend{ + FromAddress: f.mustAddr(f.mockAccountAddress), + ToAddress: recipient, + Amount: sdk.NewCoins(sendAmt), + }, []byte("pass"), &account_abstractionv1.TxExtension{ + AuthenticationGasLimit: 2400, + BundlerPaymentMessages: []*codectypes.Any{ + wrapAny(t, &banktypes.MsgSend{ + FromAddress: f.mustAddr(f.mockAccountAddress), + ToAddress: f.bundler, + Amount: sdk.NewCoins(feeAmt), + }), + }, + BundlerPaymentGasLimit: 30000, + ExecutionGasLimit: 30000, + }) + + bundleResp := f.runBundle(tx) + require.Len(t, bundleResp.Responses, 1) + + txResp := bundleResp.Responses[0] + + require.NotEmpty(t, txResp.Error) + require.Contains(t, txResp.Error, "execution failed") + + require.NotZero(t, txResp.AuthenticationGasUsed) + + require.NotZero(t, txResp.BundlerPaymentGasUsed) + require.NotEmpty(t, txResp.BundlerPaymentResponses) + require.Equal(t, f.balance(f.bundler, feeAmt.Denom), feeAmt) // ensure bundler payment side effects are persisted + + require.NotZero(t, txResp.ExecutionGasUsed) + require.Empty(t, txResp.ExecutionResponses) + + // ensure execution side effects are not persisted + // aka recipient must not have money + require.True(t, f.balance(recipient, feeAmt.Denom).IsZero()) + }) +} + +func makeTx(t *testing.T, msg gogoproto.Message, sig []byte, xt *account_abstractionv1.TxExtension) []byte { + t.Helper() + anyMsg, err := codectypes.NewAnyWithValue(msg) + require.NoError(t, err) + + anyXt, err := codectypes.NewAnyWithValue(xt) + require.NoError(t, err) + + tx := &txtypes.Tx{ + Body: &txtypes.TxBody{ + Messages: []*codectypes.Any{anyMsg}, + Memo: "", + TimeoutHeight: 0, + Unordered: false, + TimeoutTimestamp: nil, + ExtensionOptions: []*codectypes.Any{anyXt}, + NonCriticalExtensionOptions: nil, + }, + AuthInfo: &txtypes.AuthInfo{ + SignerInfos: []*txtypes.SignerInfo{ + { + PublicKey: nil, + ModeInfo: &txtypes.ModeInfo{Sum: &txtypes.ModeInfo_Single_{Single: &txtypes.ModeInfo_Single{Mode: signingtypes.SignMode_SIGN_MODE_UNSPECIFIED}}}, + Sequence: 0, + }, + }, + Fee: nil, + }, + Signatures: [][]byte{sig}, + } + + bodyBytes, err := tx.Body.Marshal() + require.NoError(t, err) + + authInfoBytes, err := tx.AuthInfo.Marshal() + require.NoError(t, err) + + txRaw, err := (&txtypes.TxRaw{ + BodyBytes: bodyBytes, + AuthInfoBytes: authInfoBytes, + Signatures: tx.Signatures, + }).Marshal() + require.NoError(t, err) + return txRaw +} + +func wrapAny(t *testing.T, msg gogoproto.Message) *codectypes.Any { + t.Helper() + any, err := codectypes.NewAnyWithValue(msg) + require.NoError(t, err) + return any +} diff --git a/tests/integration/v2/accounts/fixture_test.go b/tests/integration/v2/accounts/fixture_test.go new file mode 100644 index 000000000000..134cab87ff4c --- /dev/null +++ b/tests/integration/v2/accounts/fixture_test.go @@ -0,0 +1,211 @@ +package accounts + +import ( + "context" + "testing" + + gogotypes "github.com/cosmos/gogoproto/types" + "github.com/stretchr/testify/require" + + "cosmossdk.io/core/router" + "cosmossdk.io/core/transaction" + "cosmossdk.io/depinject" + "cosmossdk.io/log" + "cosmossdk.io/runtime/v2" + "cosmossdk.io/x/accounts" + "cosmossdk.io/x/accounts/accountstd" + basedepinject "cosmossdk.io/x/accounts/defaults/base/depinject" + account_abstractionv1 "cosmossdk.io/x/accounts/interfaces/account_abstraction/v1" + accountsv1 "cosmossdk.io/x/accounts/v1" + "cosmossdk.io/x/bank" + bankkeeper "cosmossdk.io/x/bank/keeper" + banktypes "cosmossdk.io/x/bank/types" + minttypes "cosmossdk.io/x/mint/types" + + "github.com/cosmos/cosmos-sdk/codec" + codectestutil "github.com/cosmos/cosmos-sdk/codec/testutil" + "github.com/cosmos/cosmos-sdk/tests/integration/v2" + "github.com/cosmos/cosmos-sdk/testutil/configurator" + sdk "github.com/cosmos/cosmos-sdk/types" + moduletestutil "github.com/cosmos/cosmos-sdk/types/module/testutil" + "github.com/cosmos/cosmos-sdk/x/auth" + authkeeper "github.com/cosmos/cosmos-sdk/x/auth/keeper" +) + +var _ accountstd.Interface = (*mockAccount)(nil) + +type mockAccount struct { + authenticate authentiacteFunc +} + +func (m mockAccount) RegisterInitHandler(builder *accountstd.InitBuilder) { + accountstd.RegisterInitHandler(builder, func(ctx context.Context, req *gogotypes.Empty) (*gogotypes.Empty, error) { + return &gogotypes.Empty{}, nil + }) +} + +func (m mockAccount) RegisterExecuteHandlers(builder *accountstd.ExecuteBuilder) { + if m.authenticate == nil { + return + } + + accountstd.RegisterExecuteHandler(builder, m.authenticate) +} + +func (m mockAccount) RegisterQueryHandlers(_ *accountstd.QueryBuilder) {} + +func ProvideMockAccount(f authentiacteFunc) accountstd.DepinjectAccount { + return accountstd.DepinjectAccount{MakeAccount: func(_ accountstd.Dependencies) (string, accountstd.Interface, error) { + return "mock", mockAccount{f}, nil + }} +} + +type authentiacteFunc = func(ctx context.Context, msg *account_abstractionv1.MsgAuthenticate) (*account_abstractionv1.MsgAuthenticateResponse, error) + +type fixture struct { + t *testing.T + + app *integration.App + cdc codec.Codec + ctx context.Context + + authKeeper authkeeper.AccountKeeper + accountsKeeper accounts.Keeper + bankKeeper bankkeeper.Keeper + + mockAccountAddress []byte + bundler string +} + +func (f fixture) mustAddr(address []byte) string { + s, _ := f.authKeeper.AddressCodec().BytesToString(address) + return s +} + +func (f fixture) runBundle(txBytes ...[]byte) *accountsv1.MsgExecuteBundleResponse { + f.t.Helper() + + msgSrv := accounts.NewMsgServer(f.accountsKeeper) + + resp, err := msgSrv.ExecuteBundle(f.ctx, &accountsv1.MsgExecuteBundle{ + Bundler: f.bundler, + Txs: txBytes, + }) + require.NoError(f.t, err) + return resp +} + +func (f fixture) mint(address []byte, coins ...sdk.Coin) { + f.t.Helper() + for _, coin := range coins { + err := f.bankKeeper.MintCoins(f.ctx, minttypes.ModuleName, sdk.NewCoins(coin)) + require.NoError(f.t, err) + err = f.bankKeeper.SendCoinsFromModuleToAccount(f.ctx, minttypes.ModuleName, address, sdk.NewCoins(coin)) + require.NoError(f.t, err) + } +} + +func (f fixture) balance(recipient, denom string) sdk.Coin { + f.t.Helper() + balances, err := f.bankKeeper.Balance(f.ctx, &banktypes.QueryBalanceRequest{ + Address: recipient, + Denom: denom, + }) + require.NoError(f.t, err) + return *balances.Balance +} + +func initFixture(t *testing.T, f authentiacteFunc) *fixture { + t.Helper() + + fixture := &fixture{} + fixture.t = t + encodingCfg := moduletestutil.MakeTestEncodingConfig(codectestutil.CodecOptions{}, auth.AppModule{}, bank.AppModule{}, accounts.AppModule{}) + cdc := encodingCfg.Codec + + moduleConfigs := []configurator.ModuleOption{ + configurator.AccountsModule(), + configurator.AuthModule(), + configurator.BankModule(), + configurator.VestingModule(), + configurator.StakingModule(), + configurator.TxModule(), + configurator.ValidateModule(), + configurator.ConsensusModule(), + configurator.GenutilModule(), + } + + var err error + startupCfg := integration.DefaultStartUpConfig(t) + + msgRouterService := integration.NewRouterService() + fixture.registerMsgRouterService(msgRouterService) + + var routerFactory runtime.RouterServiceFactory = func(_ []byte) router.Service { + return msgRouterService + } + + queryRouterService := integration.NewRouterService() + fixture.registerQueryRouterService(queryRouterService) + + serviceBuilder := runtime.NewRouterBuilder(routerFactory, queryRouterService) + + startupCfg.BranchService = &integration.BranchService{} + startupCfg.RouterServiceBuilder = serviceBuilder + startupCfg.HeaderService = &integration.HeaderService{} + + fixture.app, err = integration.NewApp( + depinject.Configs(configurator.NewAppV2Config(moduleConfigs...), depinject.Provide( + // inject desired account types: + basedepinject.ProvideAccount, + + // provide base account options + basedepinject.ProvideSecp256K1PubKey, + + ProvideMockAccount, + ), depinject.Supply(log.NewNopLogger(), f)), + startupCfg, + &fixture.bankKeeper, &fixture.accountsKeeper, &fixture.authKeeper, &fixture.cdc) + require.NoError(t, err) + + fixture.ctx = fixture.app.StateLatestContext(t) + + // init account + _, addr, err := fixture.accountsKeeper.Init(fixture.ctx, "mock", []byte("system"), &gogotypes.Empty{}, nil, nil) + require.NoError(t, err) + + fixture.cdc = cdc + fixture.mockAccountAddress = addr + fixture.bundler = fixture.mustAddr([]byte("bundler")) + return fixture +} + +func (f *fixture) registerMsgRouterService(router *integration.RouterService) { + // register custom router service + bankSendHandler := func(ctx context.Context, req transaction.Msg) (transaction.Msg, error) { + msg, ok := req.(*banktypes.MsgSend) + if !ok { + return nil, integration.ErrInvalidMsgType + } + msgServer := bankkeeper.NewMsgServerImpl(f.bankKeeper) + resp, err := msgServer.Send(ctx, msg) + return resp, err + } + + router.RegisterHandler(bankSendHandler, "cosmos.bank.v1beta1.MsgSend") +} + +func (f *fixture) registerQueryRouterService(router *integration.RouterService) { + // register custom router service + queryHandler := func(ctx context.Context, msg transaction.Msg) (transaction.Msg, error) { + req, ok := msg.(*accountsv1.AccountNumberRequest) + if !ok { + return nil, integration.ErrInvalidMsgType + } + qs := accounts.NewQueryServer(f.accountsKeeper) + resp, err := qs.AccountNumber(ctx, req) + return resp, err + } + + router.RegisterHandler(queryHandler, "cosmos.accounts.v1.AccountNumberRequest") +} diff --git a/tests/integration/v2/accounts/wiring_test.go b/tests/integration/v2/accounts/wiring_test.go new file mode 100644 index 000000000000..3d0a8ed0b5b4 --- /dev/null +++ b/tests/integration/v2/accounts/wiring_test.go @@ -0,0 +1,67 @@ +package accounts + +import ( + "testing" + + "github.com/stretchr/testify/require" + + "cosmossdk.io/core/header" + storetypes "cosmossdk.io/store/types" + counterv1 "cosmossdk.io/x/accounts/testing/counter/v1" + "cosmossdk.io/x/bank/testutil" + + sdk "github.com/cosmos/cosmos-sdk/types" +) + +// TestDependencies aims to test wiring between different account components, +// inherited from the runtime, specifically: +// - address codec +// - binary codec +// - header service +// - gas service +// - funds +func TestDependencies(t *testing.T) { + app := setupApp(t) + ak := app.AccountsKeeper + ctx := sdk.NewContext(app.CommitMultiStore(), false, app.Logger()).WithHeaderInfo(header.Info{ChainID: "chain-id"}) + ctx = ctx.WithGasMeter(storetypes.NewGasMeter(500_000)) + + _, counterAddr, err := ak.Init(ctx, "counter", accCreator, &counterv1.MsgInit{ + InitialValue: 0, + }, nil, nil) + require.NoError(t, err) + // test dependencies + creatorInitFunds := sdk.NewCoins(sdk.NewInt64Coin("stake", 100_000)) + err = testutil.FundAccount(ctx, app.BankKeeper, accCreator, creatorInitFunds) + require.NoError(t, err) + sentFunds := sdk.NewCoins(sdk.NewInt64Coin("stake", 50_000)) + r, err := ak.Execute( + ctx, + counterAddr, + accCreator, + &counterv1.MsgTestDependencies{}, + sentFunds, + ) + require.NoError(t, err) + res := r.(*counterv1.MsgTestDependenciesResponse) + + // test gas + require.NotZero(t, res.BeforeGas) + require.NotZero(t, res.AfterGas) + require.Equal(t, int(uint64(10)), int(res.AfterGas-res.BeforeGas)) + + // test header service + require.Equal(t, ctx.HeaderInfo().ChainID, res.ChainId) + + // test address codec + wantAddr, err := app.AuthKeeper.AddressCodec().BytesToString(counterAddr) + require.NoError(t, err) + require.Equal(t, wantAddr, res.Address) + + // test funds + creatorFunds := app.BankKeeper.GetAllBalances(ctx, accCreator) + require.Equal(t, creatorInitFunds.Sub(sentFunds...), creatorFunds) + + accFunds := app.BankKeeper.GetAllBalances(ctx, counterAddr) + require.Equal(t, sentFunds, accFunds) +} diff --git a/tests/integration/v2/services.go b/tests/integration/v2/services.go index f608fd65fdb0..b7780ae4674f 100644 --- a/tests/integration/v2/services.go +++ b/tests/integration/v2/services.go @@ -21,6 +21,7 @@ import ( corestore "cosmossdk.io/core/store" "cosmossdk.io/core/transaction" "cosmossdk.io/server/v2/stf" + stfbranch "cosmossdk.io/server/v2/stf/branch" stfgas "cosmossdk.io/server/v2/stf/gas" ) @@ -244,15 +245,55 @@ func (bs *BranchService) ExecuteWithGasLimit( return 0, errors.New("context is not an integration context") } + originalGasMeter := iCtx.gasMeter + + iCtx.gasMeter = stfgas.DefaultGasMeter(gasLimit) + // execute branched, with predefined gas limit. - err = f(ctx) + err = bs.execute(ctx, iCtx, f) + // restore original context gasUsed = iCtx.gasMeter.Limit() - iCtx.gasMeter.Remaining() - _ = iCtx.gasMeter.Consume(gasUsed, "execute-with-gas-limit") + _ = originalGasMeter.Consume(gasUsed, "execute-with-gas-limit") + iCtx.gasMeter = stfgas.DefaultGasMeter(originalGasMeter.Remaining()) return gasUsed, err } +func (bs BranchService) execute(ctx context.Context, ictx *integrationContext, f func(ctx context.Context) error) error { + branchedState := stfbranch.DefaultNewWriterMap(ictx.state) + meteredBranchedState := stfgas.DefaultWrapWithGasMeter(ictx.gasMeter, branchedState) + + branchedCtx := &integrationContext{ + state: meteredBranchedState, + gasMeter: ictx.gasMeter, + header: ictx.header, + events: ictx.events, + } + + newCtx := context.WithValue(ctx, contextKey, branchedCtx) + + err := f(newCtx) + if err != nil { + return err + } + + err = applyStateChanges(ictx.state, branchedCtx.state) + if err != nil { + return err + } + + return nil +} + +func applyStateChanges(dst, src corestore.WriterMap) error { + changes, err := src.GetStateChanges() + if err != nil { + return err + } + return dst.ApplyStateChanges(changes) +} + // msgTypeURL returns the TypeURL of a proto message. func msgTypeURL(msg gogoproto.Message) string { return gogoproto.MessageName(msg) diff --git a/x/accounts/keeper_account_abstraction.go b/x/accounts/keeper_account_abstraction.go index e919bd84f675..9c7807779c08 100644 --- a/x/accounts/keeper_account_abstraction.go +++ b/x/accounts/keeper_account_abstraction.go @@ -82,9 +82,12 @@ func (k Keeper) executeBundledTx(ctx context.Context, bundler string, txBytes [] resp := new(v1.BundledTxResponse) // to execute a bundled tx the first step is authentication. signer := bundledTx.Signers[0] + fmt.Println(xt.AuthenticationGasLimit) authGasUsed, err := k.BranchService.ExecuteWithGasLimit(ctx, xt.AuthenticationGasLimit, func(ctx context.Context) error { return k.AuthenticateAccount(ctx, signer, bundler, protov2TxRawToProtoV1(bundledTx.TxRaw), protoV2TxToProtoV1(bundledTx.Tx), 0) }) + + fmt.Println(authGasUsed) resp.AuthenticationGasUsed = authGasUsed // set independently of outcome if err != nil { return resp, fmt.Errorf("%w: %w", ErrAuthentication, err) From 88cc287af2485ccd948d3327d8f8ec4fc20019b0 Mon Sep 17 00:00:00 2001 From: sontrinh16 Date: Mon, 16 Dec 2024 14:57:08 +0700 Subject: [PATCH 2/4] fix tests --- .../v2/accounts/base_account_test.go | 70 +++++++------------ tests/integration/v2/accounts/fixture_test.go | 8 +++ tests/integration/v2/accounts/wiring_test.go | 26 +++---- tests/integration/v2/services.go | 9 +++ x/accounts/keeper_account_abstraction.go | 2 - 5 files changed, 55 insertions(+), 60 deletions(-) diff --git a/tests/integration/v2/accounts/base_account_test.go b/tests/integration/v2/accounts/base_account_test.go index 5f239d36c083..4aa4610ce883 100644 --- a/tests/integration/v2/accounts/base_account_test.go +++ b/tests/integration/v2/accounts/base_account_test.go @@ -1,23 +1,25 @@ package accounts import ( - "math/rand" + "context" "testing" gogoproto "github.com/cosmos/gogoproto/proto" gogoany "github.com/cosmos/gogoproto/types/any" "github.com/stretchr/testify/require" - "cosmossdk.io/simapp" + "cosmossdk.io/x/accounts" baseaccountv1 "cosmossdk.io/x/accounts/defaults/base/v1" + bankkeeper "cosmossdk.io/x/bank/keeper" "cosmossdk.io/x/bank/testutil" banktypes "cosmossdk.io/x/bank/types" codectypes "github.com/cosmos/cosmos-sdk/codec/types" "github.com/cosmos/cosmos-sdk/crypto/keys/secp256k1" cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" - "github.com/cosmos/cosmos-sdk/testutil/sims" + "github.com/cosmos/cosmos-sdk/tests/integration/v2" sdk "github.com/cosmos/cosmos-sdk/types" + authkeeper "github.com/cosmos/cosmos-sdk/x/auth/keeper" ) var ( @@ -26,72 +28,54 @@ var ( ) func TestBaseAccount(t *testing.T) { - app := setupApp(t) - ak := app.AccountsKeeper - ctx := sdk.NewContext(app.CommitMultiStore(), false, app.Logger()) + f := initFixture(t, nil) + app := f.app + ctx := f.ctx - _, baseAccountAddr, err := ak.Init(ctx, "base", accCreator, &baseaccountv1.MsgInit{ + _, baseAccountAddr, err := f.accountsKeeper.Init(ctx, "base", accCreator, &baseaccountv1.MsgInit{ PubKey: toAnyPb(t, privKey.PubKey()), }, nil, nil) require.NoError(t, err) // fund base account! this will also cause an auth base account to be created // by the bank module. - // TODO: fixed by letting x/auth rely on x/accounts for acc existence checks. - fundAccount(t, app, ctx, baseAccountAddr, "1000000stake") + fundAccount(t, f.bankKeeper, ctx, baseAccountAddr, "1000000stake") // now we make the account send a tx, public key not present. // so we know it will default to x/accounts calling. msg := &banktypes.MsgSend{ - FromAddress: bechify(t, app, baseAccountAddr), - ToAddress: bechify(t, app, []byte("random-addr")), + FromAddress: bechify(t, f.authKeeper, baseAccountAddr), + ToAddress: bechify(t, f.authKeeper, []byte("random-addr")), Amount: coins(t, "100stake"), } - sendTx(t, ctx, app, baseAccountAddr, msg) + sendTx(t, ctx, app, f.accountsKeeper, baseAccountAddr, msg) } -func sendTx(t *testing.T, ctx sdk.Context, app *simapp.SimApp, sender []byte, msg sdk.Msg) { +func sendTx(t *testing.T, ctx context.Context, app *integration.App, ak accounts.Keeper, sender []byte, msg sdk.Msg) { t.Helper() - tx := sign(t, ctx, app, sender, privKey, msg) - _, _, err := app.SimDeliver(app.TxEncode, tx) + accNum, err := ak.AccountByNumber.Get(ctx, sender) require.NoError(t, err) -} - -func sign(t *testing.T, ctx sdk.Context, app *simapp.SimApp, from sdk.AccAddress, privKey cryptotypes.PrivKey, msg sdk.Msg) sdk.Tx { - t.Helper() - r := rand.New(rand.NewSource(0)) - accNum, err := app.AccountsKeeper.AccountByNumber.Get(ctx, from) - require.NoError(t, err) - accSeq, err := app.AccountsKeeper.Query(ctx, from, &baseaccountv1.QuerySequence{}) + accSeq, err := ak.Query(ctx, sender, &baseaccountv1.QuerySequence{}) require.NoError(t, err) - tx, err := sims.GenSignedMockTx( - r, - app.TxConfig(), - []sdk.Msg{msg}, - coins(t, "100stake"), - 1_000_000, - app.ChainID(), - []uint64{accNum}, - []uint64{accSeq.(*baseaccountv1.QuerySequenceResponse).Sequence}, - privKey, + app.SignCheckDeliver( + t, ctx, []sdk.Msg{msg}, "", []uint64{accNum}, []uint64{accSeq.(*baseaccountv1.QuerySequenceResponse).Sequence}, + []cryptotypes.PrivKey{privKey}, + "", ) - - require.NoError(t, err) - return tx } -func bechify(t *testing.T, app *simapp.SimApp, addr []byte) string { +func bechify(t *testing.T, ak authkeeper.AccountKeeper, addr []byte) string { t.Helper() - bech32, err := app.AuthKeeper.AddressCodec().BytesToString(addr) + bech32, err := ak.AddressCodec().BytesToString(addr) require.NoError(t, err) return bech32 } -func fundAccount(t *testing.T, app *simapp.SimApp, ctx sdk.Context, addr sdk.AccAddress, amt string) { +func fundAccount(t *testing.T, bk bankkeeper.Keeper, ctx context.Context, addr sdk.AccAddress, amt string) { t.Helper() - require.NoError(t, testutil.FundAccount(ctx, app.BankKeeper, addr, coins(t, amt))) + require.NoError(t, testutil.FundAccount(ctx, bk, addr, coins(t, amt))) } func toAnyPb(t *testing.T, pm gogoproto.Message) *codectypes.Any { @@ -110,9 +94,3 @@ func coins(t *testing.T, s string) sdk.Coins { require.NoError(t, err) return coins } - -func setupApp(t *testing.T) *simapp.SimApp { - t.Helper() - app := simapp.Setup(t, false) - return app -} diff --git a/tests/integration/v2/accounts/fixture_test.go b/tests/integration/v2/accounts/fixture_test.go index 134cab87ff4c..2a9a5c9aada7 100644 --- a/tests/integration/v2/accounts/fixture_test.go +++ b/tests/integration/v2/accounts/fixture_test.go @@ -16,11 +16,14 @@ import ( "cosmossdk.io/x/accounts/accountstd" basedepinject "cosmossdk.io/x/accounts/defaults/base/depinject" account_abstractionv1 "cosmossdk.io/x/accounts/interfaces/account_abstraction/v1" + counteraccount "cosmossdk.io/x/accounts/testing/counter" accountsv1 "cosmossdk.io/x/accounts/v1" "cosmossdk.io/x/bank" bankkeeper "cosmossdk.io/x/bank/keeper" banktypes "cosmossdk.io/x/bank/types" + _ "cosmossdk.io/x/consensus" // import as blank for app wiring minttypes "cosmossdk.io/x/mint/types" + _ "cosmossdk.io/x/staking" // import as blank for app wirings "github.com/cosmos/cosmos-sdk/codec" codectestutil "github.com/cosmos/cosmos-sdk/codec/testutil" @@ -30,6 +33,9 @@ import ( moduletestutil "github.com/cosmos/cosmos-sdk/types/module/testutil" "github.com/cosmos/cosmos-sdk/x/auth" authkeeper "github.com/cosmos/cosmos-sdk/x/auth/keeper" + _ "github.com/cosmos/cosmos-sdk/x/auth/tx/config" // import as blank for app wiring`` + _ "github.com/cosmos/cosmos-sdk/x/auth/vesting" // import as blank for app wiring + _ "github.com/cosmos/cosmos-sdk/x/genutil" // import as blank for app wiring ) var _ accountstd.Interface = (*mockAccount)(nil) @@ -153,6 +159,7 @@ func initFixture(t *testing.T, f authentiacteFunc) *fixture { startupCfg.BranchService = &integration.BranchService{} startupCfg.RouterServiceBuilder = serviceBuilder startupCfg.HeaderService = &integration.HeaderService{} + startupCfg.GasService = &integration.GasService{} fixture.app, err = integration.NewApp( depinject.Configs(configurator.NewAppV2Config(moduleConfigs...), depinject.Provide( @@ -163,6 +170,7 @@ func initFixture(t *testing.T, f authentiacteFunc) *fixture { basedepinject.ProvideSecp256K1PubKey, ProvideMockAccount, + counteraccount.ProvideAccount, ), depinject.Supply(log.NewNopLogger(), f)), startupCfg, &fixture.bankKeeper, &fixture.accountsKeeper, &fixture.authKeeper, &fixture.cdc) diff --git a/tests/integration/v2/accounts/wiring_test.go b/tests/integration/v2/accounts/wiring_test.go index 3d0a8ed0b5b4..649d990f8d05 100644 --- a/tests/integration/v2/accounts/wiring_test.go +++ b/tests/integration/v2/accounts/wiring_test.go @@ -6,10 +6,11 @@ import ( "github.com/stretchr/testify/require" "cosmossdk.io/core/header" - storetypes "cosmossdk.io/store/types" + stfgas "cosmossdk.io/server/v2/stf/gas" counterv1 "cosmossdk.io/x/accounts/testing/counter/v1" "cosmossdk.io/x/bank/testutil" + "github.com/cosmos/cosmos-sdk/tests/integration/v2" sdk "github.com/cosmos/cosmos-sdk/types" ) @@ -21,21 +22,21 @@ import ( // - gas service // - funds func TestDependencies(t *testing.T) { - app := setupApp(t) - ak := app.AccountsKeeper - ctx := sdk.NewContext(app.CommitMultiStore(), false, app.Logger()).WithHeaderInfo(header.Info{ChainID: "chain-id"}) - ctx = ctx.WithGasMeter(storetypes.NewGasMeter(500_000)) + f := initFixture(t, nil) + ctx := f.ctx + ctx = integration.SetHeaderInfo(ctx, header.Info{ChainID: "chain-id"}) + ctx = integration.SetGasMeter(ctx, stfgas.DefaultGasMeter(500_000)) - _, counterAddr, err := ak.Init(ctx, "counter", accCreator, &counterv1.MsgInit{ + _, counterAddr, err := f.accountsKeeper.Init(ctx, "counter", accCreator, &counterv1.MsgInit{ InitialValue: 0, }, nil, nil) require.NoError(t, err) // test dependencies creatorInitFunds := sdk.NewCoins(sdk.NewInt64Coin("stake", 100_000)) - err = testutil.FundAccount(ctx, app.BankKeeper, accCreator, creatorInitFunds) + err = testutil.FundAccount(ctx, f.bankKeeper, accCreator, creatorInitFunds) require.NoError(t, err) sentFunds := sdk.NewCoins(sdk.NewInt64Coin("stake", 50_000)) - r, err := ak.Execute( + r, err := f.accountsKeeper.Execute( ctx, counterAddr, accCreator, @@ -50,18 +51,19 @@ func TestDependencies(t *testing.T) { require.NotZero(t, res.AfterGas) require.Equal(t, int(uint64(10)), int(res.AfterGas-res.BeforeGas)) + headerInfo := integration.HeaderInfoFromContext(ctx) // test header service - require.Equal(t, ctx.HeaderInfo().ChainID, res.ChainId) + require.Equal(t, headerInfo.ChainID, res.ChainId) // test address codec - wantAddr, err := app.AuthKeeper.AddressCodec().BytesToString(counterAddr) + wantAddr, err := f.authKeeper.AddressCodec().BytesToString(counterAddr) require.NoError(t, err) require.Equal(t, wantAddr, res.Address) // test funds - creatorFunds := app.BankKeeper.GetAllBalances(ctx, accCreator) + creatorFunds := f.bankKeeper.GetAllBalances(ctx, accCreator) require.Equal(t, creatorInitFunds.Sub(sentFunds...), creatorFunds) - accFunds := app.BankKeeper.GetAllBalances(ctx, counterAddr) + accFunds := f.bankKeeper.GetAllBalances(ctx, counterAddr) require.Equal(t, sentFunds, accFunds) } diff --git a/tests/integration/v2/services.go b/tests/integration/v2/services.go index b7780ae4674f..f8c27ff12666 100644 --- a/tests/integration/v2/services.go +++ b/tests/integration/v2/services.go @@ -140,6 +140,15 @@ func GasMeterFactory(ctx context.Context) func() gas.Meter { } } +func SetGasMeter(ctx context.Context, meter gas.Meter) context.Context { + iCtx, ok := ctx.Value(contextKey).(*integrationContext) + if !ok { + return ctx + } + iCtx.gasMeter = meter + return context.WithValue(ctx, contextKey, iCtx) +} + func (s storeService) OpenKVStore(ctx context.Context) corestore.KVStore { const gasLimit = 100_000 iCtx, ok := ctx.Value(contextKey).(*integrationContext) diff --git a/x/accounts/keeper_account_abstraction.go b/x/accounts/keeper_account_abstraction.go index 9c7807779c08..05fabf0e4c71 100644 --- a/x/accounts/keeper_account_abstraction.go +++ b/x/accounts/keeper_account_abstraction.go @@ -82,12 +82,10 @@ func (k Keeper) executeBundledTx(ctx context.Context, bundler string, txBytes [] resp := new(v1.BundledTxResponse) // to execute a bundled tx the first step is authentication. signer := bundledTx.Signers[0] - fmt.Println(xt.AuthenticationGasLimit) authGasUsed, err := k.BranchService.ExecuteWithGasLimit(ctx, xt.AuthenticationGasLimit, func(ctx context.Context) error { return k.AuthenticateAccount(ctx, signer, bundler, protov2TxRawToProtoV1(bundledTx.TxRaw), protoV2TxToProtoV1(bundledTx.Tx), 0) }) - fmt.Println(authGasUsed) resp.AuthenticationGasUsed = authGasUsed // set independently of outcome if err != nil { return resp, fmt.Errorf("%w: %w", ErrAuthentication, err) From e97761319cb0c7b4c9b21aac4cde01acc7437733 Mon Sep 17 00:00:00 2001 From: sontrinh16 Date: Mon, 16 Dec 2024 14:59:08 +0700 Subject: [PATCH 3/4] delete v1 tests --- .../integration/accounts/base_account_test.go | 118 -------- tests/integration/accounts/bundler_test.go | 262 ------------------ tests/integration/accounts/fixture_test.go | 203 -------------- tests/integration/accounts/wiring_test.go | 67 ----- 4 files changed, 650 deletions(-) delete mode 100644 tests/integration/accounts/base_account_test.go delete mode 100644 tests/integration/accounts/bundler_test.go delete mode 100644 tests/integration/accounts/fixture_test.go delete mode 100644 tests/integration/accounts/wiring_test.go diff --git a/tests/integration/accounts/base_account_test.go b/tests/integration/accounts/base_account_test.go deleted file mode 100644 index 5f239d36c083..000000000000 --- a/tests/integration/accounts/base_account_test.go +++ /dev/null @@ -1,118 +0,0 @@ -package accounts - -import ( - "math/rand" - "testing" - - gogoproto "github.com/cosmos/gogoproto/proto" - gogoany "github.com/cosmos/gogoproto/types/any" - "github.com/stretchr/testify/require" - - "cosmossdk.io/simapp" - baseaccountv1 "cosmossdk.io/x/accounts/defaults/base/v1" - "cosmossdk.io/x/bank/testutil" - banktypes "cosmossdk.io/x/bank/types" - - codectypes "github.com/cosmos/cosmos-sdk/codec/types" - "github.com/cosmos/cosmos-sdk/crypto/keys/secp256k1" - cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" - "github.com/cosmos/cosmos-sdk/testutil/sims" - sdk "github.com/cosmos/cosmos-sdk/types" -) - -var ( - privKey = secp256k1.GenPrivKey() - accCreator = []byte("creator") -) - -func TestBaseAccount(t *testing.T) { - app := setupApp(t) - ak := app.AccountsKeeper - ctx := sdk.NewContext(app.CommitMultiStore(), false, app.Logger()) - - _, baseAccountAddr, err := ak.Init(ctx, "base", accCreator, &baseaccountv1.MsgInit{ - PubKey: toAnyPb(t, privKey.PubKey()), - }, nil, nil) - require.NoError(t, err) - - // fund base account! this will also cause an auth base account to be created - // by the bank module. - // TODO: fixed by letting x/auth rely on x/accounts for acc existence checks. - fundAccount(t, app, ctx, baseAccountAddr, "1000000stake") - - // now we make the account send a tx, public key not present. - // so we know it will default to x/accounts calling. - msg := &banktypes.MsgSend{ - FromAddress: bechify(t, app, baseAccountAddr), - ToAddress: bechify(t, app, []byte("random-addr")), - Amount: coins(t, "100stake"), - } - sendTx(t, ctx, app, baseAccountAddr, msg) -} - -func sendTx(t *testing.T, ctx sdk.Context, app *simapp.SimApp, sender []byte, msg sdk.Msg) { - t.Helper() - tx := sign(t, ctx, app, sender, privKey, msg) - _, _, err := app.SimDeliver(app.TxEncode, tx) - require.NoError(t, err) -} - -func sign(t *testing.T, ctx sdk.Context, app *simapp.SimApp, from sdk.AccAddress, privKey cryptotypes.PrivKey, msg sdk.Msg) sdk.Tx { - t.Helper() - r := rand.New(rand.NewSource(0)) - - accNum, err := app.AccountsKeeper.AccountByNumber.Get(ctx, from) - require.NoError(t, err) - accSeq, err := app.AccountsKeeper.Query(ctx, from, &baseaccountv1.QuerySequence{}) - require.NoError(t, err) - - tx, err := sims.GenSignedMockTx( - r, - app.TxConfig(), - []sdk.Msg{msg}, - coins(t, "100stake"), - 1_000_000, - app.ChainID(), - []uint64{accNum}, - []uint64{accSeq.(*baseaccountv1.QuerySequenceResponse).Sequence}, - privKey, - ) - - require.NoError(t, err) - return tx -} - -func bechify(t *testing.T, app *simapp.SimApp, addr []byte) string { - t.Helper() - bech32, err := app.AuthKeeper.AddressCodec().BytesToString(addr) - require.NoError(t, err) - return bech32 -} - -func fundAccount(t *testing.T, app *simapp.SimApp, ctx sdk.Context, addr sdk.AccAddress, amt string) { - t.Helper() - require.NoError(t, testutil.FundAccount(ctx, app.BankKeeper, addr, coins(t, amt))) -} - -func toAnyPb(t *testing.T, pm gogoproto.Message) *codectypes.Any { - t.Helper() - if gogoproto.MessageName(pm) == gogoproto.MessageName(&gogoany.Any{}) { - t.Fatal("no") - } - pb, err := codectypes.NewAnyWithValue(pm) - require.NoError(t, err) - return pb -} - -func coins(t *testing.T, s string) sdk.Coins { - t.Helper() - coins, err := sdk.ParseCoinsNormalized(s) - require.NoError(t, err) - return coins -} - -func setupApp(t *testing.T) *simapp.SimApp { - t.Helper() - app := simapp.Setup(t, false) - return app -} diff --git a/tests/integration/accounts/bundler_test.go b/tests/integration/accounts/bundler_test.go deleted file mode 100644 index 2fb88983ddf6..000000000000 --- a/tests/integration/accounts/bundler_test.go +++ /dev/null @@ -1,262 +0,0 @@ -package accounts - -import ( - "context" - "fmt" - "testing" - - gogoproto "github.com/cosmos/gogoproto/proto" - "github.com/stretchr/testify/require" - - account_abstractionv1 "cosmossdk.io/x/accounts/interfaces/account_abstraction/v1" - banktypes "cosmossdk.io/x/bank/types" - - codectypes "github.com/cosmos/cosmos-sdk/codec/types" - sdk "github.com/cosmos/cosmos-sdk/types" - txtypes "github.com/cosmos/cosmos-sdk/types/tx" - signingtypes "github.com/cosmos/cosmos-sdk/types/tx/signing" -) - -func TestMsgServer_ExecuteBundle(t *testing.T) { - t.Run("bundle success", func(t *testing.T) { - f := initFixture(t, func(ctx context.Context, msg *account_abstractionv1.MsgAuthenticate) (*account_abstractionv1.MsgAuthenticateResponse, error) { - return &account_abstractionv1.MsgAuthenticateResponse{}, nil - }) - - recipient := f.mustAddr([]byte("recipient")) - feeAmt := sdk.NewInt64Coin("atom", 100) - sendAmt := sdk.NewInt64Coin("atom", 200) - - f.mint(f.mockAccountAddress, feeAmt, sendAmt) - - tx := makeTx(t, &banktypes.MsgSend{ - FromAddress: f.mustAddr(f.mockAccountAddress), - ToAddress: recipient, - Amount: sdk.NewCoins(sendAmt), - }, []byte("pass"), &account_abstractionv1.TxExtension{ - AuthenticationGasLimit: 2400, - BundlerPaymentMessages: []*codectypes.Any{wrapAny(t, &banktypes.MsgSend{ - FromAddress: f.mustAddr(f.mockAccountAddress), - ToAddress: f.bundler, - Amount: sdk.NewCoins(feeAmt), - })}, - BundlerPaymentGasLimit: 30000, - ExecutionGasLimit: 30000, - }) - - bundleResp := f.runBundle(tx) - require.Len(t, bundleResp.Responses, 1) - - txResp := bundleResp.Responses[0] - - require.Empty(t, txResp.Error) - require.NotZero(t, txResp.AuthenticationGasUsed) - require.NotZero(t, txResp.BundlerPaymentGasUsed) - require.NotZero(t, txResp.ExecutionGasUsed) - - // asses responses - require.Len(t, txResp.BundlerPaymentResponses, 1) - require.Equal(t, txResp.BundlerPaymentResponses[0].TypeUrl, "/cosmos.bank.v1beta1.MsgSendResponse") - - require.Len(t, txResp.ExecutionResponses, 1) - require.Equal(t, txResp.ExecutionResponses[0].TypeUrl, "/cosmos.bank.v1beta1.MsgSendResponse") - - // ensure sends have happened - require.Equal(t, f.balance(f.bundler, feeAmt.Denom), feeAmt) - require.Equal(t, f.balance(recipient, sendAmt.Denom), sendAmt) - }) - - t.Run("tx fails at auth step", func(t *testing.T) { - f := initFixture(t, func(ctx context.Context, msg *account_abstractionv1.MsgAuthenticate) (*account_abstractionv1.MsgAuthenticateResponse, error) { - return &account_abstractionv1.MsgAuthenticateResponse{}, fmt.Errorf("sentinel") - }) - recipient := f.mustAddr([]byte("recipient")) - feeAmt := sdk.NewInt64Coin("atom", 100) - sendAmt := sdk.NewInt64Coin("atom", 200) - f.mint(f.mockAccountAddress, feeAmt, sendAmt) - - tx := makeTx(t, &banktypes.MsgSend{ - FromAddress: f.mustAddr(f.mockAccountAddress), - ToAddress: recipient, - Amount: sdk.NewCoins(sendAmt), - }, []byte("pass"), &account_abstractionv1.TxExtension{ - AuthenticationGasLimit: 2400, - BundlerPaymentMessages: []*codectypes.Any{wrapAny(t, &banktypes.MsgSend{ - FromAddress: f.mustAddr(f.mockAccountAddress), - ToAddress: f.bundler, - Amount: sdk.NewCoins(feeAmt), - })}, - BundlerPaymentGasLimit: 30000, - ExecutionGasLimit: 30000, - }) - - bundleResp := f.runBundle(tx) - - require.Len(t, bundleResp.Responses, 1) - - txResp := bundleResp.Responses[0] - require.NotEmpty(t, txResp.Error) - require.Contains(t, txResp.Error, "sentinel") - require.NotZero(t, txResp.AuthenticationGasUsed) - require.Zero(t, txResp.BundlerPaymentGasUsed) - require.Zero(t, txResp.ExecutionGasUsed) - require.Empty(t, txResp.BundlerPaymentResponses) - require.Empty(t, txResp.ExecutionResponses) - - // ensure auth side effects are not persisted in case of failures - }) - - t.Run("tx fails at pay bundler step", func(t *testing.T) { - f := initFixture(t, func(ctx context.Context, msg *account_abstractionv1.MsgAuthenticate) (*account_abstractionv1.MsgAuthenticateResponse, error) { - return &account_abstractionv1.MsgAuthenticateResponse{}, nil - }) - - recipient := f.mustAddr([]byte("recipient")) - feeAmt := sdk.NewInt64Coin("atom", 100) - sendAmt := sdk.NewInt64Coin("atom", 200) - - f.mint(f.mockAccountAddress, feeAmt, sendAmt) - - tx := makeTx(t, &banktypes.MsgSend{ - FromAddress: f.mustAddr(f.mockAccountAddress), - ToAddress: recipient, - Amount: sdk.NewCoins(sendAmt), - }, []byte("pass"), &account_abstractionv1.TxExtension{ - AuthenticationGasLimit: 2400, - BundlerPaymentMessages: []*codectypes.Any{ - wrapAny(t, &banktypes.MsgSend{ - FromAddress: f.mustAddr(f.mockAccountAddress), - ToAddress: f.bundler, - Amount: sdk.NewCoins(feeAmt.AddAmount(feeAmt.Amount.AddRaw(100))), - }), - wrapAny(t, &banktypes.MsgSend{ - FromAddress: f.mustAddr(f.mockAccountAddress), - ToAddress: f.bundler, - Amount: sdk.NewCoins(feeAmt.AddAmount(feeAmt.Amount.AddRaw(30000))), - }), - }, - BundlerPaymentGasLimit: 30000, - ExecutionGasLimit: 30000, - }) - - bundleResp := f.runBundle(tx) - require.Len(t, bundleResp.Responses, 1) - - txResp := bundleResp.Responses[0] - - require.NotEmpty(t, txResp.Error) - require.Contains(t, txResp.Error, "bundler payment failed") - require.NotZero(t, txResp.AuthenticationGasUsed) - require.NotZero(t, txResp.BundlerPaymentGasUsed) - - require.Empty(t, txResp.BundlerPaymentResponses) - require.Zero(t, txResp.ExecutionGasUsed) - require.Empty(t, txResp.ExecutionResponses) - - // ensure bundler payment side effects are not persisted - require.True(t, f.balance(f.bundler, feeAmt.Denom).IsZero()) - }) - - t.Run("tx fails at execution step", func(t *testing.T) { - f := initFixture(t, func(ctx context.Context, msg *account_abstractionv1.MsgAuthenticate) (*account_abstractionv1.MsgAuthenticateResponse, error) { - return &account_abstractionv1.MsgAuthenticateResponse{}, nil - }) - - recipient := f.mustAddr([]byte("recipient")) - feeAmt := sdk.NewInt64Coin("atom", 100) - sendAmt := sdk.NewInt64Coin("atom", 40000) // this fails - - f.mint(f.mockAccountAddress, feeAmt) - - tx := makeTx(t, &banktypes.MsgSend{ - FromAddress: f.mustAddr(f.mockAccountAddress), - ToAddress: recipient, - Amount: sdk.NewCoins(sendAmt), - }, []byte("pass"), &account_abstractionv1.TxExtension{ - AuthenticationGasLimit: 2400, - BundlerPaymentMessages: []*codectypes.Any{ - wrapAny(t, &banktypes.MsgSend{ - FromAddress: f.mustAddr(f.mockAccountAddress), - ToAddress: f.bundler, - Amount: sdk.NewCoins(feeAmt), - }), - }, - BundlerPaymentGasLimit: 30000, - ExecutionGasLimit: 30000, - }) - - bundleResp := f.runBundle(tx) - require.Len(t, bundleResp.Responses, 1) - - txResp := bundleResp.Responses[0] - - require.NotEmpty(t, txResp.Error) - require.Contains(t, txResp.Error, "execution failed") - - require.NotZero(t, txResp.AuthenticationGasUsed) - - require.NotZero(t, txResp.BundlerPaymentGasUsed) - require.NotEmpty(t, txResp.BundlerPaymentResponses) - require.Equal(t, f.balance(f.bundler, feeAmt.Denom), feeAmt) // ensure bundler payment side effects are persisted - - require.NotZero(t, txResp.ExecutionGasUsed) - require.Empty(t, txResp.ExecutionResponses) - - // ensure execution side effects are not persisted - // aka recipient must not have money - require.True(t, f.balance(recipient, feeAmt.Denom).IsZero()) - }) -} - -func makeTx(t *testing.T, msg gogoproto.Message, sig []byte, xt *account_abstractionv1.TxExtension) []byte { - t.Helper() - anyMsg, err := codectypes.NewAnyWithValue(msg) - require.NoError(t, err) - - anyXt, err := codectypes.NewAnyWithValue(xt) - require.NoError(t, err) - - tx := &txtypes.Tx{ - Body: &txtypes.TxBody{ - Messages: []*codectypes.Any{anyMsg}, - Memo: "", - TimeoutHeight: 0, - Unordered: false, - TimeoutTimestamp: nil, - ExtensionOptions: []*codectypes.Any{anyXt}, - NonCriticalExtensionOptions: nil, - }, - AuthInfo: &txtypes.AuthInfo{ - SignerInfos: []*txtypes.SignerInfo{ - { - PublicKey: nil, - ModeInfo: &txtypes.ModeInfo{Sum: &txtypes.ModeInfo_Single_{Single: &txtypes.ModeInfo_Single{Mode: signingtypes.SignMode_SIGN_MODE_UNSPECIFIED}}}, - Sequence: 0, - }, - }, - Fee: nil, - }, - Signatures: [][]byte{sig}, - } - - bodyBytes, err := tx.Body.Marshal() - require.NoError(t, err) - - authInfoBytes, err := tx.AuthInfo.Marshal() - require.NoError(t, err) - - txRaw, err := (&txtypes.TxRaw{ - BodyBytes: bodyBytes, - AuthInfoBytes: authInfoBytes, - Signatures: tx.Signatures, - }).Marshal() - require.NoError(t, err) - return txRaw -} - -func wrapAny(t *testing.T, msg gogoproto.Message) *codectypes.Any { - t.Helper() - any, err := codectypes.NewAnyWithValue(msg) - require.NoError(t, err) - return any -} diff --git a/tests/integration/accounts/fixture_test.go b/tests/integration/accounts/fixture_test.go deleted file mode 100644 index 0cf55d6d9022..000000000000 --- a/tests/integration/accounts/fixture_test.go +++ /dev/null @@ -1,203 +0,0 @@ -package accounts - -import ( - "context" - "testing" - - gogotypes "github.com/cosmos/gogoproto/types" - "github.com/stretchr/testify/require" - - "cosmossdk.io/core/appmodule" - "cosmossdk.io/log" - storetypes "cosmossdk.io/store/types" - "cosmossdk.io/x/accounts" - "cosmossdk.io/x/accounts/accountstd" - account_abstractionv1 "cosmossdk.io/x/accounts/interfaces/account_abstraction/v1" - accountsv1 "cosmossdk.io/x/accounts/v1" - "cosmossdk.io/x/bank" - bankkeeper "cosmossdk.io/x/bank/keeper" - banktypes "cosmossdk.io/x/bank/types" - minttypes "cosmossdk.io/x/mint/types" - txdecode "cosmossdk.io/x/tx/decode" - - "github.com/cosmos/cosmos-sdk/baseapp" - "github.com/cosmos/cosmos-sdk/codec" - addresscodec "github.com/cosmos/cosmos-sdk/codec/address" - codectestutil "github.com/cosmos/cosmos-sdk/codec/testutil" - "github.com/cosmos/cosmos-sdk/runtime" - "github.com/cosmos/cosmos-sdk/testutil/integration" - sdk "github.com/cosmos/cosmos-sdk/types" - moduletestutil "github.com/cosmos/cosmos-sdk/types/module/testutil" - "github.com/cosmos/cosmos-sdk/x/auth" - authkeeper "github.com/cosmos/cosmos-sdk/x/auth/keeper" - authsims "github.com/cosmos/cosmos-sdk/x/auth/simulation" - authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" -) - -var _ accountstd.Interface = (*mockAccount)(nil) - -type mockAccount struct { - authenticate func(ctx context.Context, msg *account_abstractionv1.MsgAuthenticate) (*account_abstractionv1.MsgAuthenticateResponse, error) -} - -func (m mockAccount) RegisterInitHandler(builder *accountstd.InitBuilder) { - accountstd.RegisterInitHandler(builder, func(ctx context.Context, req *gogotypes.Empty) (*gogotypes.Empty, error) { - return &gogotypes.Empty{}, nil - }) -} - -func (m mockAccount) RegisterExecuteHandlers(builder *accountstd.ExecuteBuilder) { - if m.authenticate == nil { - return - } - - accountstd.RegisterExecuteHandler(builder, m.authenticate) -} - -func (m mockAccount) RegisterQueryHandlers(_ *accountstd.QueryBuilder) {} - -type fixture struct { - t *testing.T - - app *integration.App - cdc codec.Codec - - authKeeper authkeeper.AccountKeeper - accountsKeeper accounts.Keeper - bankKeeper bankkeeper.Keeper - - mockAccountAddress []byte - bundler string -} - -func (f fixture) mustAddr(address []byte) string { - s, _ := f.authKeeper.AddressCodec().BytesToString(address) - return s -} - -func (f fixture) runBundle(txBytes ...[]byte) *accountsv1.MsgExecuteBundleResponse { - f.t.Helper() - - msgSrv := accounts.NewMsgServer(f.accountsKeeper) - - resp, err := msgSrv.ExecuteBundle(f.app.Context(), &accountsv1.MsgExecuteBundle{ - Bundler: f.bundler, - Txs: txBytes, - }) - require.NoError(f.t, err) - return resp -} - -func (f fixture) mint(address []byte, coins ...sdk.Coin) { - f.t.Helper() - for _, coin := range coins { - err := f.bankKeeper.MintCoins(f.app.Context(), minttypes.ModuleName, sdk.NewCoins(coin)) - require.NoError(f.t, err) - err = f.bankKeeper.SendCoinsFromModuleToAccount(f.app.Context(), minttypes.ModuleName, address, sdk.NewCoins(coin)) - require.NoError(f.t, err) - } -} - -func (f fixture) balance(recipient, denom string) sdk.Coin { - f.t.Helper() - balances, err := f.bankKeeper.Balance(f.app.Context(), &banktypes.QueryBalanceRequest{ - Address: recipient, - Denom: denom, - }) - require.NoError(f.t, err) - return *balances.Balance -} - -func initFixture(t *testing.T, f func(ctx context.Context, msg *account_abstractionv1.MsgAuthenticate) (*account_abstractionv1.MsgAuthenticateResponse, error)) *fixture { - t.Helper() - keys := storetypes.NewKVStoreKeys( - authtypes.StoreKey, banktypes.StoreKey, accounts.StoreKey, - ) - encodingCfg := moduletestutil.MakeTestEncodingConfig(codectestutil.CodecOptions{}, auth.AppModule{}, bank.AppModule{}, accounts.AppModule{}) - cdc := encodingCfg.Codec - - logger := log.NewTestLogger(t) - router := baseapp.NewMsgServiceRouter() - queryRouter := baseapp.NewGRPCQueryRouter() - - txDecoder, err := txdecode.NewDecoder(txdecode.Options{ - SigningContext: encodingCfg.TxConfig.SigningContext(), - ProtoCodec: encodingCfg.Codec, - }) - require.NoError(t, err) - - accountsKeeper, err := accounts.NewKeeper( - cdc, - runtime.NewEnvironment(runtime.NewKVStoreService(keys[accounts.StoreKey]), log.NewNopLogger(), runtime.EnvWithQueryRouterService(queryRouter), runtime.EnvWithMsgRouterService(router)), - addresscodec.NewBech32Codec("cosmos"), - cdc.InterfaceRegistry(), - txDecoder, - accountstd.AddAccount("mock", func(deps accountstd.Dependencies) (accountstd.Interface, error) { - return mockAccount{f}, nil - }), - ) - require.NoError(t, err) - accountsv1.RegisterQueryServer(queryRouter, accounts.NewQueryServer(accountsKeeper)) - - authority := authtypes.NewModuleAddress("gov") - - authKeeper := authkeeper.NewAccountKeeper( - runtime.NewEnvironment(runtime.NewKVStoreService(keys[authtypes.StoreKey]), log.NewNopLogger()), - cdc, - authtypes.ProtoBaseAccount, - accountsKeeper, - map[string][]string{minttypes.ModuleName: {authtypes.Minter}}, - addresscodec.NewBech32Codec(sdk.Bech32MainPrefix), - sdk.Bech32MainPrefix, - authority.String(), - ) - - blockedAddresses := map[string]bool{ - authKeeper.GetAuthority(): false, - } - bankKeeper := bankkeeper.NewBaseKeeper( - runtime.NewEnvironment(runtime.NewKVStoreService(keys[banktypes.StoreKey]), log.NewNopLogger()), - cdc, - authKeeper, - blockedAddresses, - authority.String(), - ) - - accountsModule := accounts.NewAppModule(cdc, accountsKeeper) - authModule := auth.NewAppModule(cdc, authKeeper, accountsKeeper, authsims.RandomGenesisAccounts, nil) - bankModule := bank.NewAppModule(cdc, bankKeeper, authKeeper) - - integrationApp := integration.NewIntegrationApp(logger, keys, cdc, - encodingCfg.InterfaceRegistry.SigningContext().AddressCodec(), - encodingCfg.InterfaceRegistry.SigningContext().ValidatorAddressCodec(), - map[string]appmodule.AppModule{ - accounts.ModuleName: accountsModule, - authtypes.ModuleName: authModule, - banktypes.ModuleName: bankModule, - }, router, queryRouter) - - authtypes.RegisterInterfaces(cdc.InterfaceRegistry()) - banktypes.RegisterInterfaces(cdc.InterfaceRegistry()) - - authtypes.RegisterMsgServer(integrationApp.MsgServiceRouter(), authkeeper.NewMsgServerImpl(authKeeper)) - authtypes.RegisterQueryServer(integrationApp.QueryHelper(), authkeeper.NewQueryServer(authKeeper)) - - banktypes.RegisterMsgServer(router, bankkeeper.NewMsgServerImpl(bankKeeper)) - - // init account - _, addr, err := accountsKeeper.Init(integrationApp.Context(), "mock", []byte("system"), &gogotypes.Empty{}, nil, nil) - require.NoError(t, err) - - fixture := &fixture{ - t: t, - app: integrationApp, - cdc: cdc, - authKeeper: authKeeper, - accountsKeeper: accountsKeeper, - bankKeeper: bankKeeper, - mockAccountAddress: addr, - bundler: "", - } - fixture.bundler = fixture.mustAddr([]byte("bundler")) - return fixture -} diff --git a/tests/integration/accounts/wiring_test.go b/tests/integration/accounts/wiring_test.go deleted file mode 100644 index 3d0a8ed0b5b4..000000000000 --- a/tests/integration/accounts/wiring_test.go +++ /dev/null @@ -1,67 +0,0 @@ -package accounts - -import ( - "testing" - - "github.com/stretchr/testify/require" - - "cosmossdk.io/core/header" - storetypes "cosmossdk.io/store/types" - counterv1 "cosmossdk.io/x/accounts/testing/counter/v1" - "cosmossdk.io/x/bank/testutil" - - sdk "github.com/cosmos/cosmos-sdk/types" -) - -// TestDependencies aims to test wiring between different account components, -// inherited from the runtime, specifically: -// - address codec -// - binary codec -// - header service -// - gas service -// - funds -func TestDependencies(t *testing.T) { - app := setupApp(t) - ak := app.AccountsKeeper - ctx := sdk.NewContext(app.CommitMultiStore(), false, app.Logger()).WithHeaderInfo(header.Info{ChainID: "chain-id"}) - ctx = ctx.WithGasMeter(storetypes.NewGasMeter(500_000)) - - _, counterAddr, err := ak.Init(ctx, "counter", accCreator, &counterv1.MsgInit{ - InitialValue: 0, - }, nil, nil) - require.NoError(t, err) - // test dependencies - creatorInitFunds := sdk.NewCoins(sdk.NewInt64Coin("stake", 100_000)) - err = testutil.FundAccount(ctx, app.BankKeeper, accCreator, creatorInitFunds) - require.NoError(t, err) - sentFunds := sdk.NewCoins(sdk.NewInt64Coin("stake", 50_000)) - r, err := ak.Execute( - ctx, - counterAddr, - accCreator, - &counterv1.MsgTestDependencies{}, - sentFunds, - ) - require.NoError(t, err) - res := r.(*counterv1.MsgTestDependenciesResponse) - - // test gas - require.NotZero(t, res.BeforeGas) - require.NotZero(t, res.AfterGas) - require.Equal(t, int(uint64(10)), int(res.AfterGas-res.BeforeGas)) - - // test header service - require.Equal(t, ctx.HeaderInfo().ChainID, res.ChainId) - - // test address codec - wantAddr, err := app.AuthKeeper.AddressCodec().BytesToString(counterAddr) - require.NoError(t, err) - require.Equal(t, wantAddr, res.Address) - - // test funds - creatorFunds := app.BankKeeper.GetAllBalances(ctx, accCreator) - require.Equal(t, creatorInitFunds.Sub(sentFunds...), creatorFunds) - - accFunds := app.BankKeeper.GetAllBalances(ctx, counterAddr) - require.Equal(t, sentFunds, accFunds) -} From 7756bf0c593e7acfb5a71349119f100914ca2303 Mon Sep 17 00:00:00 2001 From: sontrinh16 Date: Mon, 16 Dec 2024 15:01:48 +0700 Subject: [PATCH 4/4] minor --- x/accounts/keeper_account_abstraction.go | 1 - 1 file changed, 1 deletion(-) diff --git a/x/accounts/keeper_account_abstraction.go b/x/accounts/keeper_account_abstraction.go index 05fabf0e4c71..e919bd84f675 100644 --- a/x/accounts/keeper_account_abstraction.go +++ b/x/accounts/keeper_account_abstraction.go @@ -85,7 +85,6 @@ func (k Keeper) executeBundledTx(ctx context.Context, bundler string, txBytes [] authGasUsed, err := k.BranchService.ExecuteWithGasLimit(ctx, xt.AuthenticationGasLimit, func(ctx context.Context) error { return k.AuthenticateAccount(ctx, signer, bundler, protov2TxRawToProtoV1(bundledTx.TxRaw), protoV2TxToProtoV1(bundledTx.Tx), 0) }) - resp.AuthenticationGasUsed = authGasUsed // set independently of outcome if err != nil { return resp, fmt.Errorf("%w: %w", ErrAuthentication, err)