Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(accounts): Add TxCompat field –  implement Tx integration (part 1) #18969

Merged
merged 4 commits into from
Jan 8, 2024
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
880 changes: 776 additions & 104 deletions api/cosmos/accounts/v1/account_abstraction.pulsar.go

Large diffs are not rendered by default.

23 changes: 22 additions & 1 deletion proto/cosmos/accounts/v1/account_abstraction.proto
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@

import "google/protobuf/any.proto";

import "cosmos/tx/signing/v1beta1/signing.proto";

Check failure on line 9 in proto/cosmos/accounts/v1/account_abstraction.proto

View workflow job for this annotation

GitHub Actions / lint

Import "cosmos/tx/signing/v1beta1/signing.proto" is unused.

// UserOperation defines the type used to define a state transition that
// an account wants to make.
message UserOperation {
Expand All @@ -17,7 +19,7 @@
string authentication_method = 2;
// authentication_data defines the authentication data associated with the authentication method.
// It is the account implementer duty to assess that the UserOperation is properly signed.
bytes authentication_data = 3;
google.protobuf.Any authentication_data = 3;

Check failure on line 22 in proto/cosmos/accounts/v1/account_abstraction.proto

View workflow job for this annotation

GitHub Actions / break-check

Field "3" on message "UserOperation" changed type from "bytes" to "message".
// authentication_gas_limit expresses the gas limit to be used for the authentication part of the
// UserOperation.
uint64 authentication_gas_limit = 4;
Expand All @@ -42,6 +44,25 @@
// execution_gas_limit defines the gas limit to be used for the execution of the UserOperation's
// execution messages.
uint64 execution_gas_limit = 8;

// tx_compat is populated only when the operation is composed from a raw tx.
// In fact if a TX comes and the sender of the TX is an abstracted account,
// we convert the TX into a user operation, and try to authenticate using the
// x/accounts authenticate method. If a bundler tries to send a UserOperation
// with a populated tx_compat, the operation will immediately yield a failure.
TxCompat tx_compat = 9;
}

// TxCompat provides compatibility for x/accounts abstracted account with the cosmos-sdk's Txs.
// In fact TxCompat contains fields coming from the Tx in raw and decoded format. The Raw format
// is mainly needed for proper sig verification.
message TxCompat {
// auth_info_bytes contains the auth info bytes of the tx.
// Must not be modified.
bytes auth_info_bytes = 1;
// body_bytes contains the body bytes of the tx.
// must not be modified.
bytes body_bytes = 2;
}

// UserOperationResponse defines the response of a UserOperation.
Expand Down
2 changes: 1 addition & 1 deletion simapp/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ require (
google.golang.org/protobuf v1.32.0
)

require cosmossdk.io/x/accounts v0.0.0-20231013072015-ec9bcc41ef9c
require cosmossdk.io/x/accounts v0.0.0-20240104091155-b729e981f130

require (
cosmossdk.io/x/auth v0.0.0-00010101000000-000000000000
Expand Down
45 changes: 34 additions & 11 deletions tests/e2e/accounts/account_abstraction_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,13 +59,28 @@ func TestAccountAbstraction(t *testing.T) {
aliceAddrStr, err := app.AuthKeeper.AddressCodec().BytesToString(aliceAddr)
require.NoError(t, err)

t.Run("fail - tx compat in bundle is not allowed", func(t *testing.T) {
resp := ak.ExecuteUserOperation(ctx, bundlerAddrStr, &accountsv1.UserOperation{
Sender: aaAddrStr,
AuthenticationMethod: "secp256k1",
AuthenticationData: mockSignature,
ExecutionMessages: intoAny(t, &banktypes.MsgSend{
FromAddress: aaAddrStr,
ToAddress: bundlerAddrStr,
Amount: coins(t, "1stake"), // the sender is the AA, so it has the coins and wants to pay the bundler for the gas
}),
TxCompat: &accountsv1.TxCompat{},
})
require.Contains(t, resp.Error, accounts.ErrDisallowedTxCompatInBundle.Error())
})

t.Run("ok - pay bundler and exec not implemented", func(t *testing.T) {
// we simulate executing an user operation in an abstracted account
// which only implements the authentication.
resp := ak.ExecuteUserOperation(ctx, bundlerAddrStr, &accountsv1.UserOperation{
Sender: aaAddrStr,
AuthenticationMethod: "secp256k1",
AuthenticationData: []byte("signature"),
AuthenticationData: mockSignature,
AuthenticationGasLimit: 10000,
BundlerPaymentMessages: intoAny(t, &banktypes.MsgSend{
FromAddress: aaAddrStr,
Expand All @@ -78,7 +93,7 @@ func TestAccountAbstraction(t *testing.T) {
ToAddress: aliceAddrStr,
Amount: coins(t, "2000stake"), // as the real action the sender wants to send coins to alice
}),
ExecutionGasLimit: 36000,
ExecutionGasLimit: 38000,
})
require.Empty(t, resp.Error) // no error
require.Len(t, resp.BundlerPaymentResponses, 1)
Expand All @@ -97,7 +112,7 @@ func TestAccountAbstraction(t *testing.T) {
resp := ak.ExecuteUserOperation(ctx, bundlerAddrStr, &accountsv1.UserOperation{
Sender: aaAddrStr,
AuthenticationMethod: "secp256k1",
AuthenticationData: []byte("signature"),
AuthenticationData: mockSignature,
AuthenticationGasLimit: 10000,
BundlerPaymentMessages: intoAny(t, &banktypes.MsgSend{
FromAddress: bundlerAddrStr, // abstracted account tries to send money from bundler to itself.
Expand All @@ -124,7 +139,7 @@ func TestAccountAbstraction(t *testing.T) {
resp := ak.ExecuteUserOperation(ctx, bundlerAddrStr, &accountsv1.UserOperation{
Sender: aaAddrStr,
AuthenticationMethod: "secp256k1",
AuthenticationData: []byte("signature"),
AuthenticationData: mockSignature,
AuthenticationGasLimit: 10000,
BundlerPaymentMessages: intoAny(t, &banktypes.MsgSend{
FromAddress: aaAddrStr,
Expand Down Expand Up @@ -153,7 +168,7 @@ func TestAccountAbstraction(t *testing.T) {
resp := ak.ExecuteUserOperation(ctx, bundlerAddrStr, &accountsv1.UserOperation{
Sender: aaAddrStr,
AuthenticationMethod: "invalid",
AuthenticationData: []byte("signature"),
AuthenticationData: mockSignature,
AuthenticationGasLimit: 10000,
BundlerPaymentMessages: intoAny(t, &banktypes.MsgSend{
FromAddress: aaAddrStr,
Expand Down Expand Up @@ -183,7 +198,7 @@ func TestAccountAbstraction(t *testing.T) {
resp := ak.ExecuteUserOperation(ctx, bundlerAddrStr, &accountsv1.UserOperation{
Sender: aaAddrStr,
AuthenticationMethod: "secp256k1",
AuthenticationData: []byte("signature"),
AuthenticationData: mockSignature,
AuthenticationGasLimit: 10000,
BundlerPaymentMessages: intoAny(t, &banktypes.MsgSend{
FromAddress: aaAddrStr,
Expand Down Expand Up @@ -212,7 +227,7 @@ func TestAccountAbstraction(t *testing.T) {
resp := ak.ExecuteUserOperation(ctx, bundlerAddrStr, &accountsv1.UserOperation{
Sender: aaAddrStr,
AuthenticationMethod: "secp256k1",
AuthenticationData: []byte("signature"),
AuthenticationData: mockSignature,
AuthenticationGasLimit: 10000,
BundlerPaymentMessages: intoAny(t, &banktypes.MsgSend{
FromAddress: aaAddrStr,
Expand Down Expand Up @@ -241,7 +256,7 @@ func TestAccountAbstraction(t *testing.T) {
resp := ak.ExecuteUserOperation(ctx, bundlerAddrStr, &accountsv1.UserOperation{
Sender: aaFullAddrStr,
AuthenticationMethod: "secp256k1",
AuthenticationData: []byte("signature"),
AuthenticationData: mockSignature,
AuthenticationGasLimit: 10000,
BundlerPaymentMessages: intoAny(t, &banktypes.MsgSend{
FromAddress: aaFullAddrStr,
Expand All @@ -265,7 +280,7 @@ func TestAccountAbstraction(t *testing.T) {
resp := ak.ExecuteUserOperation(ctx, bundlerAddrStr, &accountsv1.UserOperation{
Sender: aaFullAddrStr,
AuthenticationMethod: "secp256k1",
AuthenticationData: []byte("signature"),
AuthenticationData: mockSignature,
AuthenticationGasLimit: 10000,
BundlerPaymentMessages: nil,
BundlerPaymentGasLimit: 50000,
Expand Down Expand Up @@ -294,7 +309,7 @@ func TestAccountAbstraction(t *testing.T) {
resp := ak.ExecuteUserOperation(ctx, bundlerAddrStr, &accountsv1.UserOperation{
Sender: aaFullAddrStr,
AuthenticationMethod: "secp256k1",
AuthenticationData: []byte("signature"),
AuthenticationData: mockSignature,
AuthenticationGasLimit: 10000,
BundlerPaymentMessages: intoAny(t, &nft.MsgSend{
ClassId: "omega-rare",
Expand All @@ -308,7 +323,7 @@ func TestAccountAbstraction(t *testing.T) {
ToAddress: aliceAddrStr,
Amount: coins(t, "2000stake"),
}),
ExecutionGasLimit: 36000,
ExecutionGasLimit: 38000,
})
require.Empty(t, resp.Error) // no error
})
Expand Down Expand Up @@ -336,3 +351,11 @@ func balanceIs(t *testing.T, ctx context.Context, app *simapp.SimApp, addr sdk.A
balance := app.BankKeeper.GetAllBalances(ctx, addr)
require.Equal(t, s, balance.String())
}

var mockSignature = &codectypes.Any{TypeUrl: "signature", Value: []byte("signature")}

func setupApp(t *testing.T) *simapp.SimApp {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

t.Helper()
app := simapp.Setup(t, false)
return app
}
2 changes: 1 addition & 1 deletion tests/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ require (
)

require (
cosmossdk.io/x/accounts v0.0.0-20231013072015-ec9bcc41ef9c
cosmossdk.io/x/accounts v0.0.0-20240104091155-b729e981f130
cosmossdk.io/x/auth v0.0.0-00010101000000-000000000000
cosmossdk.io/x/authz v0.0.0-00010101000000-000000000000
cosmossdk.io/x/bank v0.0.0-00010101000000-000000000000
Expand Down
2 changes: 1 addition & 1 deletion tests/starship/tests/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ require (
cosmossdk.io/depinject v1.0.0-alpha.4 // indirect
cosmossdk.io/errors v1.0.0 // indirect
cosmossdk.io/store v1.0.1 // indirect
cosmossdk.io/x/accounts v0.0.0-20231013072015-ec9bcc41ef9c // indirect
cosmossdk.io/x/accounts v0.0.0-20240104091155-b729e981f130 // indirect
cosmossdk.io/x/authz v0.0.0-00010101000000-000000000000 // indirect
cosmossdk.io/x/circuit v0.0.0-20230613133644-0a778132a60f // indirect
cosmossdk.io/x/distribution v0.0.0-20230925135524-a1bc045b3190 // indirect
Expand Down
10 changes: 10 additions & 0 deletions x/accounts/keeper_account_abstraction.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@ var (
ErrBundlerPayment = errors.New("bundler payment failed")
// ErrExecution is returned when the execution fails.
ErrExecution = errors.New("execution failed")
// ErrDisallowedTxCompatInBundle is returned when the tx compat
// is populated in a bundle.
ErrDisallowedTxCompatInBundle = errors.New("tx compat field populated in bundle")
)

// ExecuteUserOperation handles the execution of an abstracted account UserOperation.
Expand All @@ -25,6 +28,13 @@ func (k Keeper) ExecuteUserOperation(
bundler string,
op *v1.UserOperation,
) *v1.UserOperationResponse {
// TxCompat field must not be allowed in a UserOperation sent from a bundle.
// Only the runtime can populate this field when an abstracted account sends
// a tx (not from a bundle) and this is converted into a UserOperation.
if op.TxCompat != nil {
return &v1.UserOperationResponse{Error: ErrDisallowedTxCompatInBundle.Error()}
}

resp := &v1.UserOperationResponse{}

// authenticate
Expand Down
15 changes: 11 additions & 4 deletions x/accounts/msg_server.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,6 @@ package accounts
import (
"context"

"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"

"cosmossdk.io/core/event"
"cosmossdk.io/x/accounts/internal/implementation"
v1 "cosmossdk.io/x/accounts/v1"
Expand Down Expand Up @@ -103,5 +100,15 @@ func (m msgServer) Execute(ctx context.Context, execute *v1.MsgExecute) (*v1.Msg
}

func (m msgServer) ExecuteBundle(ctx context.Context, req *v1.MsgExecuteBundle) (*v1.MsgExecuteBundleResponse, error) {
return nil, status.Error(codes.Unimplemented, "not implemented")
_, err := m.k.addressCodec.StringToBytes(req.Bundler)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

NOTE: this is not related to the PR but I just saw the MsgServer logic was missing (probably got nuked in a merge).

if err != nil {
return nil, err
}

resp := &v1.MsgExecuteBundleResponse{Responses: make([]*v1.UserOperationResponse, len(req.Operations))}
for i, op := range req.Operations {
resp.Responses[i] = m.k.ExecuteUserOperation(ctx, req.Bundler, op)
}

return resp, nil
}
Loading
Loading