feat(accounts): Add account Create,Execute,Query implementation (#17515)
Co-authored-by: unknown unknown <unknown@unknown>
This commit is contained in:
parent
147758fd17
commit
4a536b21e2
31
x/accounts/account_test.go
Normal file
31
x/accounts/account_test.go
Normal file
@ -0,0 +1,31 @@
|
||||
package accounts
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"google.golang.org/protobuf/types/known/emptypb"
|
||||
|
||||
"cosmossdk.io/x/accounts/internal/implementation"
|
||||
)
|
||||
|
||||
var _ implementation.Account = (*TestAccount)(nil)
|
||||
|
||||
type TestAccount struct{}
|
||||
|
||||
func (t TestAccount) RegisterInitHandler(builder *implementation.InitBuilder) {
|
||||
implementation.RegisterInitHandler(builder, func(_ context.Context, _ *emptypb.Empty) (*emptypb.Empty, error) {
|
||||
return &emptypb.Empty{}, nil
|
||||
})
|
||||
}
|
||||
|
||||
func (t TestAccount) RegisterExecuteHandlers(builder *implementation.ExecuteBuilder) {
|
||||
implementation.RegisterExecuteHandler(builder, func(_ context.Context, _ *emptypb.Empty) (*emptypb.Empty, error) {
|
||||
return &emptypb.Empty{}, nil
|
||||
})
|
||||
}
|
||||
|
||||
func (t TestAccount) RegisterQueryHandlers(builder *implementation.QueryBuilder) {
|
||||
implementation.RegisterQueryHandler(builder, func(_ context.Context, _ *emptypb.Empty) (*emptypb.Empty, error) {
|
||||
return &emptypb.Empty{}, nil
|
||||
})
|
||||
}
|
||||
165
x/accounts/keeper.go
Normal file
165
x/accounts/keeper.go
Normal file
@ -0,0 +1,165 @@
|
||||
package accounts
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/sha256"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"cosmossdk.io/collections"
|
||||
"cosmossdk.io/core/store"
|
||||
"cosmossdk.io/x/accounts/internal/implementation"
|
||||
)
|
||||
|
||||
var errAccountTypeNotFound = errors.New("account type not found")
|
||||
|
||||
var (
|
||||
// AccountTypeKeyPrefix is the prefix for the account type key.
|
||||
AccountTypeKeyPrefix = collections.NewPrefix(0)
|
||||
// AccountNumberKey is the key for the account number.
|
||||
AccountNumberKey = collections.NewPrefix(1)
|
||||
)
|
||||
|
||||
func NewKeeper(ss store.KVStoreService, accounts map[string]implementation.Account) (Keeper, error) {
|
||||
sb := collections.NewSchemaBuilder(ss)
|
||||
keeper := Keeper{
|
||||
storeService: ss,
|
||||
accounts: map[string]implementation.Implementation{},
|
||||
AccountNumber: collections.NewSequence(sb, AccountNumberKey, "account_number"),
|
||||
AccountsByType: collections.NewMap(sb, AccountTypeKeyPrefix, "accounts_by_type", collections.BytesKey, collections.StringValue),
|
||||
}
|
||||
|
||||
// make accounts implementation
|
||||
for typ, acc := range accounts {
|
||||
impl, err := implementation.NewImplementation(acc)
|
||||
if err != nil {
|
||||
return Keeper{}, err
|
||||
}
|
||||
keeper.accounts[typ] = impl
|
||||
}
|
||||
schema, err := sb.Build()
|
||||
if err != nil {
|
||||
return Keeper{}, err
|
||||
}
|
||||
keeper.Schema = schema
|
||||
return keeper, nil
|
||||
}
|
||||
|
||||
type Keeper struct {
|
||||
storeService store.KVStoreService
|
||||
|
||||
accounts map[string]implementation.Implementation
|
||||
|
||||
// Schema is the schema for the module.
|
||||
Schema collections.Schema
|
||||
// AccountNumber is the last global account number.
|
||||
AccountNumber collections.Sequence
|
||||
|
||||
// AccountsByType maps account address to their implementation.
|
||||
AccountsByType collections.Map[[]byte, string]
|
||||
}
|
||||
|
||||
// Create creates a new account of the given type.
|
||||
func (k Keeper) Create(
|
||||
ctx context.Context,
|
||||
accountType string,
|
||||
creator []byte,
|
||||
initRequest any,
|
||||
) (any, []byte, error) {
|
||||
impl, err := k.getImplementation(accountType)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
// make a new account address
|
||||
accountAddr, err := k.makeAddress(ctx)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
// make the context and init the account
|
||||
ctx = implementation.MakeAccountContext(ctx, k.storeService, accountAddr, creator)
|
||||
resp, err := impl.Init(ctx, initRequest)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
// map account address to account type
|
||||
if err := k.AccountsByType.Set(ctx, accountAddr, accountType); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
return resp, accountAddr, nil
|
||||
}
|
||||
|
||||
// Execute executes a state transition on the given account.
|
||||
func (k Keeper) Execute(
|
||||
ctx context.Context,
|
||||
accountAddr []byte,
|
||||
sender []byte,
|
||||
execRequest any,
|
||||
) (any, error) {
|
||||
// get account type
|
||||
accountType, err := k.AccountsByType.Get(ctx, accountAddr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// get account implementation
|
||||
impl, err := k.getImplementation(accountType)
|
||||
if err != nil {
|
||||
// this means the account was initialized with an implementation
|
||||
// that the chain does not know about, in theory should never happen,
|
||||
// as it might signal that the app-dev stopped supporting an account type.
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// make the context and execute the account state transition.
|
||||
ctx = implementation.MakeAccountContext(ctx, k.storeService, accountAddr, sender)
|
||||
return impl.Execute(ctx, execRequest)
|
||||
}
|
||||
|
||||
// Query queries the given account.
|
||||
func (k Keeper) Query(
|
||||
ctx context.Context,
|
||||
accountAddr []byte,
|
||||
queryRequest any,
|
||||
) (any, error) {
|
||||
// get account type
|
||||
accountType, err := k.AccountsByType.Get(ctx, accountAddr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// get account implementation
|
||||
impl, err := k.getImplementation(accountType)
|
||||
if err != nil {
|
||||
// this means the account was initialized with an implementation
|
||||
// that the chain does not know about, in theory should never happen,
|
||||
// as it might signal that the app-dev stopped supporting an account type.
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// make the context and execute the account state transition.
|
||||
ctx = implementation.MakeAccountContext(ctx, k.storeService, accountAddr, nil)
|
||||
return impl.Query(ctx, queryRequest)
|
||||
}
|
||||
|
||||
func (k Keeper) getImplementation(accountType string) (implementation.Implementation, error) {
|
||||
impl, ok := k.accounts[accountType]
|
||||
if !ok {
|
||||
return implementation.Implementation{}, fmt.Errorf("%w: %s", errAccountTypeNotFound, accountType)
|
||||
}
|
||||
return impl, nil
|
||||
}
|
||||
|
||||
func (k Keeper) makeAddress(ctx context.Context) ([]byte, error) {
|
||||
num, err := k.AccountNumber.Next(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// TODO: better address scheme, ref: https://github.com/cosmos/cosmos-sdk/issues/17516
|
||||
addr := sha256.Sum256(append([]byte("x/accounts"), binary.BigEndian.AppendUint64(nil, num)...))
|
||||
return addr[:], nil
|
||||
}
|
||||
95
x/accounts/keeper_test.go
Normal file
95
x/accounts/keeper_test.go
Normal file
@ -0,0 +1,95 @@
|
||||
package accounts
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
"google.golang.org/protobuf/types/known/emptypb"
|
||||
|
||||
"cosmossdk.io/collections"
|
||||
"cosmossdk.io/collections/colltest"
|
||||
"cosmossdk.io/x/accounts/internal/implementation"
|
||||
)
|
||||
|
||||
func newKeeper(t *testing.T, accounts map[string]implementation.Account) (Keeper, context.Context) {
|
||||
t.Helper()
|
||||
ss, ctx := colltest.MockStore()
|
||||
m, err := NewKeeper(ss, accounts)
|
||||
require.NoError(t, err)
|
||||
return m, ctx
|
||||
}
|
||||
|
||||
func TestKeeper_Create(t *testing.T) {
|
||||
m, ctx := newKeeper(t, map[string]implementation.Account{
|
||||
"test": TestAccount{},
|
||||
})
|
||||
|
||||
t.Run("ok", func(t *testing.T) {
|
||||
sender := []byte("sender")
|
||||
|
||||
resp, addr, err := m.Create(ctx, "test", sender, &emptypb.Empty{})
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, &emptypb.Empty{}, resp)
|
||||
require.NotNil(t, addr)
|
||||
|
||||
// ensure acc number was increased.
|
||||
num, err := m.AccountNumber.Peek(ctx)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, uint64(1), num)
|
||||
|
||||
// ensure account mapping
|
||||
accType, err := m.AccountsByType.Get(ctx, addr)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, "test", accType)
|
||||
})
|
||||
|
||||
t.Run("unknown account type", func(t *testing.T) {
|
||||
_, _, err := m.Create(ctx, "unknown", []byte("sender"), &emptypb.Empty{})
|
||||
require.ErrorIs(t, err, errAccountTypeNotFound)
|
||||
})
|
||||
}
|
||||
|
||||
func TestKeeper_Execute(t *testing.T) {
|
||||
m, ctx := newKeeper(t, map[string]implementation.Account{
|
||||
"test": TestAccount{},
|
||||
})
|
||||
|
||||
// create account
|
||||
sender := []byte("sender")
|
||||
_, accAddr, err := m.Create(ctx, "test", sender, &emptypb.Empty{})
|
||||
require.NoError(t, err)
|
||||
|
||||
t.Run("ok", func(t *testing.T) {
|
||||
resp, err := m.Execute(ctx, accAddr, sender, &emptypb.Empty{})
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, &emptypb.Empty{}, resp)
|
||||
})
|
||||
|
||||
t.Run("unknown account", func(t *testing.T) {
|
||||
_, err := m.Execute(ctx, []byte("unknown"), sender, &emptypb.Empty{})
|
||||
require.ErrorIs(t, err, collections.ErrNotFound)
|
||||
})
|
||||
}
|
||||
|
||||
func TestKeeper_Query(t *testing.T) {
|
||||
m, ctx := newKeeper(t, map[string]implementation.Account{
|
||||
"test": TestAccount{},
|
||||
})
|
||||
|
||||
// create account
|
||||
sender := []byte("sender")
|
||||
_, accAddr, err := m.Create(ctx, "test", sender, &emptypb.Empty{})
|
||||
require.NoError(t, err)
|
||||
|
||||
t.Run("ok", func(t *testing.T) {
|
||||
resp, err := m.Query(ctx, accAddr, &emptypb.Empty{})
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, &emptypb.Empty{}, resp)
|
||||
})
|
||||
|
||||
t.Run("unknown account", func(t *testing.T) {
|
||||
_, err := m.Query(ctx, []byte("unknown"), &emptypb.Empty{})
|
||||
require.ErrorIs(t, err, collections.ErrNotFound)
|
||||
})
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user