Skip to content

Commit

Permalink
feat(accounts): Add TxCompat field –  implement Tx integration (part …
Browse files Browse the repository at this point in the history
…1) (#18969)

Co-authored-by: unknown unknown <unknown@unknown>
  • Loading branch information
testinginprod and unknown unknown authored Jan 8, 2024
1 parent 38f8aa3 commit 6848448
Show file tree
Hide file tree
Showing 9 changed files with 1,196 additions and 160 deletions.
860 changes: 764 additions & 96 deletions api/cosmos/accounts/v1/account_abstraction.pulsar.go

Large diffs are not rendered by default.

21 changes: 20 additions & 1 deletion proto/cosmos/accounts/v1/account_abstraction.proto
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ message UserOperation {
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;
// 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 +42,25 @@ message UserOperation {
// 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 {
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)
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

0 comments on commit 6848448

Please sign in to comment.