Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
36 changes: 36 additions & 0 deletions x/tokenfactory/keeper/keeper.go
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
package keeper

import (
"context"
"fmt"

"cosmossdk.io/collections"
"cosmossdk.io/core/address"
corestore "cosmossdk.io/core/store"
"github.com/cosmos/cosmos-sdk/codec"
sdk "github.com/cosmos/cosmos-sdk/types"
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"

"github.com/you/nuahchain/x/tokenfactory/types"
)
Expand Down Expand Up @@ -66,3 +69,36 @@ func NewKeeper(
func (k Keeper) GetAuthority() []byte {
return k.authority
}

// FullDenom constructs the full denom path for a given owner and subdenom
func (k Keeper) FullDenom(owner, subdenom string) string {
return fmt.Sprintf("factory/%s/%s", owner, subdenom)
}

// GetDenom returns denom data from store
func (k Keeper) GetDenom(ctx context.Context, denom string) (types.Denom, bool) {
val, err := k.Denom.Get(ctx, denom)
if err != nil {
return types.Denom{}, false
}
return val, true
}

// SetDenom stores denom data
func (k Keeper) SetDenom(ctx context.Context, d types.Denom) {
if err := k.Denom.Set(ctx, d.Denom, d); err != nil {
panic(err)
}
}

// MustBeAdmin checks that caller is admin of denom
func (k Keeper) MustBeAdmin(ctx context.Context, denom string, caller sdk.AccAddress) error {
d, ok := k.GetDenom(ctx, denom)
if !ok {
return sdkerrors.ErrInvalidRequest.Wrap("denom does not exist")
}
if d.Owner != caller.String() {
return types.ErrNotAdmin
}
return nil
}
26 changes: 23 additions & 3 deletions x/tokenfactory/keeper/msg_server_burn.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,35 @@ import (
"context"

errorsmod "cosmossdk.io/errors"
sdk "github.com/cosmos/cosmos-sdk/types"

"github.com/you/nuahchain/x/tokenfactory/types"
)

func (k msgServer) Burn(ctx context.Context, msg *types.MsgBurn) (*types.MsgBurnResponse, error) {
if _, err := k.addressCodec.StringToBytes(msg.Owner); err != nil {
func (k msgServer) Burn(goCtx context.Context, msg *types.MsgBurn) (*types.MsgBurnResponse, error) {
admin, err := k.addressCodec.StringToBytes(msg.Owner)
if err != nil {
return nil, errorsmod.Wrap(err, "invalid authority address")
}

// TODO: Handle the message
ctx := sdk.UnwrapSDKContext(goCtx)
if err := k.MustBeAdmin(ctx, msg.Denom, admin); err != nil {
return nil, err
}

if msg.Amount <= 0 {
return nil, types.ErrInvalidAmount
}

coins := sdk.NewCoins(sdk.NewCoin(msg.Denom, sdk.NewInt(msg.Amount)))
adminAddr := sdk.AccAddress(admin)

if err := k.bankKeeper.SendCoinsFromAccountToModule(ctx, adminAddr, types.ModuleAccountName, coins); err != nil {
return nil, err
}
if err := k.bankKeeper.BurnCoins(ctx, types.ModuleAccountName, coins); err != nil {
return nil, err
}

return &types.MsgBurnResponse{}, nil
}
21 changes: 18 additions & 3 deletions x/tokenfactory/keeper/msg_server_change_admin.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,30 @@ import (
"context"

errorsmod "cosmossdk.io/errors"
sdk "github.com/cosmos/cosmos-sdk/types"

"github.com/you/nuahchain/x/tokenfactory/types"
)

func (k msgServer) ChangeAdmin(ctx context.Context, msg *types.MsgChangeAdmin) (*types.MsgChangeAdminResponse, error) {
if _, err := k.addressCodec.StringToBytes(msg.Owner); err != nil {
func (k msgServer) ChangeAdmin(goCtx context.Context, msg *types.MsgChangeAdmin) (*types.MsgChangeAdminResponse, error) {
admin, err := k.addressCodec.StringToBytes(msg.Owner)
if err != nil {
return nil, errorsmod.Wrap(err, "invalid authority address")
}

// TODO: Handle the message
ctx := sdk.UnwrapSDKContext(goCtx)
if err := k.MustBeAdmin(ctx, msg.Denom, admin); err != nil {
return nil, err
}

newAdmin, err := k.addressCodec.StringToBytes(msg.NewAdmin)
if err != nil {
return nil, errorsmod.Wrap(err, "invalid new admin address")
}

d, _ := k.GetDenom(ctx, msg.Denom)
d.Owner = sdk.AccAddress(newAdmin).String()
k.SetDenom(ctx, d)

return &types.MsgChangeAdminResponse{}, nil
}
40 changes: 23 additions & 17 deletions x/tokenfactory/keeper/msg_server_denom.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,26 +7,30 @@ import (

"cosmossdk.io/collections"
errorsmod "cosmossdk.io/errors"
sdk "github.com/cosmos/cosmos-sdk/types"
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"

"github.com/you/nuahchain/x/tokenfactory/types"
)

func (k msgServer) CreateDenom(ctx context.Context, msg *types.MsgCreateDenom) (*types.MsgCreateDenomResponse, error) {
func (k msgServer) CreateDenom(goCtx context.Context, msg *types.MsgCreateDenom) (*types.MsgCreateDenomResponse, error) {
if _, err := k.addressCodec.StringToBytes(msg.Owner); err != nil {
return nil, errorsmod.Wrap(sdkerrors.ErrInvalidAddress, fmt.Sprintf("invalid address: %s", err))
}

// Check if the value already exists
ok, err := k.Denom.Has(ctx, msg.Denom)
ctx := sdk.UnwrapSDKContext(goCtx)
full := k.FullDenom(msg.Owner, msg.Denom)

ok, err := k.Denom.Has(ctx, full)
if err != nil {
return nil, errorsmod.Wrap(sdkerrors.ErrLogic, err.Error())
} else if ok {
return nil, errorsmod.Wrap(sdkerrors.ErrInvalidRequest, "index already set")
return nil, types.ErrDenomExists
}

var denom = types.Denom{
denom := types.Denom{
Owner: msg.Owner,
Denom: msg.Denom,
Denom: full,
Description: msg.Description,
Ticker: msg.Ticker,
Precision: msg.Precision,
Expand All @@ -43,13 +47,15 @@ func (k msgServer) CreateDenom(ctx context.Context, msg *types.MsgCreateDenom) (
return &types.MsgCreateDenomResponse{}, nil
}

func (k msgServer) UpdateDenom(ctx context.Context, msg *types.MsgUpdateDenom) (*types.MsgUpdateDenomResponse, error) {
func (k msgServer) UpdateDenom(goCtx context.Context, msg *types.MsgUpdateDenom) (*types.MsgUpdateDenomResponse, error) {
if _, err := k.addressCodec.StringToBytes(msg.Owner); err != nil {
return nil, errorsmod.Wrap(sdkerrors.ErrInvalidAddress, fmt.Sprintf("invalid signer address: %s", err))
}

// Check if the value exists
val, err := k.Denom.Get(ctx, msg.Denom)
ctx := sdk.UnwrapSDKContext(goCtx)
full := k.FullDenom(msg.Owner, msg.Denom)

val, err := k.Denom.Get(ctx, full)
if err != nil {
if errors.Is(err, collections.ErrNotFound) {
return nil, errorsmod.Wrap(sdkerrors.ErrKeyNotFound, "index not set")
Expand All @@ -58,14 +64,13 @@ func (k msgServer) UpdateDenom(ctx context.Context, msg *types.MsgUpdateDenom) (
return nil, errorsmod.Wrap(sdkerrors.ErrLogic, err.Error())
}

// Checks if the msg owner is the same as the current owner
if msg.Owner != val.Owner {
return nil, errorsmod.Wrap(sdkerrors.ErrUnauthorized, "incorrect owner")
}

var denom = types.Denom{
denom := types.Denom{
Owner: msg.Owner,
Denom: msg.Denom,
Denom: full,
Description: msg.Description,
Ticker: msg.Ticker,
Precision: msg.Precision,
Expand All @@ -82,13 +87,15 @@ func (k msgServer) UpdateDenom(ctx context.Context, msg *types.MsgUpdateDenom) (
return &types.MsgUpdateDenomResponse{}, nil
}

func (k msgServer) DeleteDenom(ctx context.Context, msg *types.MsgDeleteDenom) (*types.MsgDeleteDenomResponse, error) {
func (k msgServer) DeleteDenom(goCtx context.Context, msg *types.MsgDeleteDenom) (*types.MsgDeleteDenomResponse, error) {
if _, err := k.addressCodec.StringToBytes(msg.Owner); err != nil {
return nil, errorsmod.Wrap(sdkerrors.ErrInvalidAddress, fmt.Sprintf("invalid signer address: %s", err))
}

// Check if the value exists
val, err := k.Denom.Get(ctx, msg.Denom)
ctx := sdk.UnwrapSDKContext(goCtx)
full := k.FullDenom(msg.Owner, msg.Denom)

val, err := k.Denom.Get(ctx, full)
if err != nil {
if errors.Is(err, collections.ErrNotFound) {
return nil, errorsmod.Wrap(sdkerrors.ErrKeyNotFound, "index not set")
Expand All @@ -97,12 +104,11 @@ func (k msgServer) DeleteDenom(ctx context.Context, msg *types.MsgDeleteDenom) (
return nil, errorsmod.Wrap(sdkerrors.ErrLogic, err.Error())
}

// Checks if the msg owner is the same as the current owner
if msg.Owner != val.Owner {
return nil, errorsmod.Wrap(sdkerrors.ErrUnauthorized, "incorrect owner")
}

if err := k.Denom.Remove(ctx, msg.Denom); err != nil {
if err := k.Denom.Remove(ctx, full); err != nil {
return nil, errorsmod.Wrap(sdkerrors.ErrLogic, "failed to remove denom")
}

Expand Down
7 changes: 4 additions & 3 deletions x/tokenfactory/keeper/msg_server_denom_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ func TestDenomMsgServerCreate(t *testing.T) {
}
_, err := srv.CreateDenom(f.ctx, expected)
require.NoError(t, err)
rst, err := f.keeper.Denom.Get(f.ctx, expected.Denom)
rst, err := f.keeper.Denom.Get(f.ctx, f.keeper.FullDenom(expected.Owner, expected.Denom))
require.NoError(t, err)
require.Equal(t, expected.Owner, rst.Owner)
}
Expand Down Expand Up @@ -85,7 +85,7 @@ func TestDenomMsgServerUpdate(t *testing.T) {
require.ErrorIs(t, err, tc.err)
} else {
require.NoError(t, err)
rst, err := f.keeper.Denom.Get(f.ctx, expected.Denom)
rst, err := f.keeper.Denom.Get(f.ctx, f.keeper.FullDenom(expected.Owner, expected.Denom))
require.NoError(t, err)
require.Equal(t, expected.Owner, rst.Owner)
}
Expand Down Expand Up @@ -148,7 +148,8 @@ func TestDenomMsgServerDelete(t *testing.T) {
require.ErrorIs(t, err, tc.err)
} else {
require.NoError(t, err)
found, err := f.keeper.Denom.Has(f.ctx, tc.request.Denom)
full := f.keeper.FullDenom(tc.request.Owner, tc.request.Denom)
found, err := f.keeper.Denom.Has(f.ctx, full)
require.NoError(t, err)
require.False(t, found)
}
Expand Down
31 changes: 28 additions & 3 deletions x/tokenfactory/keeper/msg_server_mint.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,40 @@ import (
"context"

errorsmod "cosmossdk.io/errors"
sdk "github.com/cosmos/cosmos-sdk/types"

"github.com/you/nuahchain/x/tokenfactory/types"
)

func (k msgServer) Mint(ctx context.Context, msg *types.MsgMint) (*types.MsgMintResponse, error) {
if _, err := k.addressCodec.StringToBytes(msg.Owner); err != nil {
func (k msgServer) Mint(goCtx context.Context, msg *types.MsgMint) (*types.MsgMintResponse, error) {
admin, err := k.addressCodec.StringToBytes(msg.Owner)
if err != nil {
return nil, errorsmod.Wrap(err, "invalid authority address")
}

// TODO: Handle the message
ctx := sdk.UnwrapSDKContext(goCtx)
if err := k.MustBeAdmin(ctx, msg.Denom, admin); err != nil {
return nil, err
}

if msg.Amount <= 0 {
return nil, types.ErrInvalidAmount
}

recipientAddr, err := k.addressCodec.StringToBytes(msg.Recipient)
if err != nil {
return nil, errorsmod.Wrap(err, "invalid recipient address")
}

coins := sdk.NewCoins(sdk.NewCoin(msg.Denom, sdk.NewInt(msg.Amount)))

if err := k.bankKeeper.MintCoins(ctx, types.ModuleAccountName, coins); err != nil {
return nil, err
}

if err := k.bankKeeper.SendCoinsFromModuleToAccount(ctx, types.ModuleAccountName, sdk.AccAddress(recipientAddr), coins); err != nil {
return nil, err
}

return &types.MsgMintResponse{}, nil
}
43 changes: 40 additions & 3 deletions x/tokenfactory/keeper/msg_server_set_denom_metadata.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,54 @@ package keeper

import (
"context"
"encoding/json"

errorsmod "cosmossdk.io/errors"
sdk "github.com/cosmos/cosmos-sdk/types"
banktypes "github.com/cosmos/cosmos-sdk/x/bank/types"

"github.com/you/nuahchain/x/tokenfactory/types"
)

func (k msgServer) SetDenomMetadata(ctx context.Context, msg *types.MsgSetDenomMetadata) (*types.MsgSetDenomMetadataResponse, error) {
if _, err := k.addressCodec.StringToBytes(msg.Owner); err != nil {
type denomUnitJSON struct {
Denom string `json:"denom"`
Exponent uint32 `json:"exponent"`
Aliases []string `json:"aliases"`
}

func (k msgServer) SetDenomMetadata(goCtx context.Context, msg *types.MsgSetDenomMetadata) (*types.MsgSetDenomMetadataResponse, error) {
admin, err := k.addressCodec.StringToBytes(msg.Owner)
if err != nil {
return nil, errorsmod.Wrap(err, "invalid authority address")
}

// TODO: Handle the message
ctx := sdk.UnwrapSDKContext(goCtx)
if err := k.MustBeAdmin(ctx, msg.Base, admin); err != nil {
return nil, err
}

var units []denomUnitJSON
if err := json.Unmarshal([]byte(msg.DenomUnits), &units); err != nil {
return nil, errorsmod.Wrap(err, "invalid denomUnits json")
}

bu := make([]*banktypes.DenomUnit, 0, len(units))
for _, u := range units {
bu = append(bu, &banktypes.DenomUnit{Denom: u.Denom, Exponent: u.Exponent, Aliases: u.Aliases})
}

md := banktypes.Metadata{
Base: msg.Base,
Name: msg.Name,
Symbol: msg.Symbol,
Display: msg.Display,
DenomUnits: bu,
Description: msg.Description,
}

if err := k.bankKeeper.SetDenomMetaData(ctx, md); err != nil {
return nil, err
}

return &types.MsgSetDenomMetadataResponse{}, nil
}
8 changes: 6 additions & 2 deletions x/tokenfactory/types/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,14 @@ package types
// DONTCOVER

import (
"cosmossdk.io/errors"
errorsmod "cosmossdk.io/errors"
)

// x/tokenfactory module sentinel errors
var (
ErrInvalidSigner = errors.Register(ModuleName, 1100, "expected gov account as only signer for proposal message")
ErrInvalidSigner = errorsmod.Register(ModuleName, 1100, "expected gov account as only signer for proposal message")
ErrDenomExists = errorsmod.Register(ModuleName, 2, "denom already exists")
ErrInvalidSubdenom = errorsmod.Register(ModuleName, 3, "invalid subdenom")
ErrNotAdmin = errorsmod.Register(ModuleName, 4, "caller is not denom admin")
ErrInvalidAmount = errorsmod.Register(ModuleName, 5, "invalid amount")
)
7 changes: 6 additions & 1 deletion x/tokenfactory/types/expected_keepers.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (

"cosmossdk.io/core/address"
sdk "github.com/cosmos/cosmos-sdk/types"
banktypes "github.com/cosmos/cosmos-sdk/x/bank/types"
)

// AuthKeeper defines the expected interface for the Auth module.
Expand All @@ -17,7 +18,11 @@ type AuthKeeper interface {
// BankKeeper defines the expected interface for the Bank module.
type BankKeeper interface {
SpendableCoins(context.Context, sdk.AccAddress) sdk.Coins
// Methods imported from bank should be defined here
MintCoins(context.Context, string, sdk.Coins) error
BurnCoins(context.Context, string, sdk.Coins) error
SendCoinsFromModuleToAccount(context.Context, string, sdk.AccAddress, sdk.Coins) error
SendCoinsFromAccountToModule(context.Context, sdk.AccAddress, string, sdk.Coins) error
SetDenomMetaData(context.Context, banktypes.Metadata) error
}

// ParamSubspace defines the expected Subspace interface for parameters.
Expand Down
Loading