From cea17e9da56127a487b5e26d2e31fd2d9ee1d94e Mon Sep 17 00:00:00 2001 From: Reece Williams Date: Sat, 10 Feb 2024 14:25:20 -0600 Subject: [PATCH] feat: Ability to SudoMint tokens (optional) --- app/app.go | 2 ++ x/tokenfactory/keeper/bankactions.go | 12 +++++---- x/tokenfactory/keeper/keeper.go | 16 ++++++++---- x/tokenfactory/keeper/keeper_test.go | 4 +++ x/tokenfactory/keeper/msg_server.go | 30 ++++++++++++--------- x/tokenfactory/keeper/msg_server_test.go | 33 +++++++++++++++++++----- x/tokenfactory/types/capabilities.go | 3 +++ 7 files changed, 71 insertions(+), 29 deletions(-) diff --git a/app/app.go b/app/app.go index 0c2ec89..56d7e3f 100644 --- a/app/app.go +++ b/app/app.go @@ -151,6 +151,7 @@ var ( tokenfactorytypes.EnableBurnFrom, tokenfactorytypes.EnableForceTransfer, tokenfactorytypes.EnableSetMetadata, + tokenfactorytypes.EnableSudoMint, } ) @@ -550,6 +551,7 @@ func NewApp( app.BankKeeper, app.DistrKeeper, tokenFactoryCapabilities, + tokenfactorykeeper.DefaultIsSudoAdminFunc, govModAddress, ) wasmOpts = append(wasmOpts, bindings.RegisterCustomPlugins(app.BankKeeper, &app.TokenFactoryKeeper)...) diff --git a/x/tokenfactory/keeper/bankactions.go b/x/tokenfactory/keeper/bankactions.go index c3de57b..7fbc1f9 100644 --- a/x/tokenfactory/keeper/bankactions.go +++ b/x/tokenfactory/keeper/bankactions.go @@ -8,14 +8,16 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" ) -func (k Keeper) mintTo(ctx sdk.Context, amount sdk.Coin, mintTo string) error { +func (k Keeper) mintTo(ctx sdk.Context, amount sdk.Coin, mintTo string, isSudo bool) error { // verify that denom is an x/tokenfactory denom - _, _, err := types.DeconstructDenom(amount.Denom) - if err != nil { - return err + if !isSudo { + _, _, err := types.DeconstructDenom(amount.Denom) + if err != nil { + return err + } } - err = k.bankKeeper.MintCoins(ctx, types.ModuleName, sdk.NewCoins(amount)) + err := k.bankKeeper.MintCoins(ctx, types.ModuleName, sdk.NewCoins(amount)) if err != nil { return err } diff --git a/x/tokenfactory/keeper/keeper.go b/x/tokenfactory/keeper/keeper.go index 11455fd..99954a0 100644 --- a/x/tokenfactory/keeper/keeper.go +++ b/x/tokenfactory/keeper/keeper.go @@ -15,6 +15,9 @@ import ( ) type ( + // IsAdmin is a function signature that checks if an address is an admin. + IsSudoAdmin func(ctx context.Context, addr string) bool + Keeper struct { cdc codec.BinaryCodec storeKey store.StoreKey @@ -28,6 +31,8 @@ type ( // the address capable of executing a MsgUpdateParams message. Typically, this // should be the x/gov module account. authority string + + IsSudoAdminFunc IsSudoAdmin } ) @@ -39,6 +44,8 @@ func NewKeeper( bankKeeper types.BankKeeper, communityPoolKeeper types.CommunityPoolKeeper, enabledCapabilities []string, + // use DefaultIsSudoAdminFunc if you don't have a custom one + isSudoAdminFunc IsSudoAdmin, authority string, ) Keeper { return Keeper{ @@ -52,14 +59,13 @@ func NewKeeper( authority: authority, enabledCapabilities: enabledCapabilities, - } -} -func DefaultIsAdminFunc(ctx context.Context, addr string) bool { - return false + IsSudoAdminFunc: isSudoAdminFunc, + } } -func DefaultExtraSudoAllowedCheckFunc(ctx context.Context) bool { +// DefaultIsSudoAdminFunc returns false for all addresses. +func DefaultIsSudoAdminFunc(ctx context.Context, addr string) bool { return false } diff --git a/x/tokenfactory/keeper/keeper_test.go b/x/tokenfactory/keeper/keeper_test.go index 9772e02..a8f0a75 100644 --- a/x/tokenfactory/keeper/keeper_test.go +++ b/x/tokenfactory/keeper/keeper_test.go @@ -46,3 +46,7 @@ func (suite *KeeperTestSuite) CreateDefaultDenom() { res, _ := suite.msgServer.CreateDenom(suite.Ctx, types.NewMsgCreateDenom(suite.TestAccs[0].String(), "bitcoin")) suite.defaultDenom = res.GetNewTokenDenom() } + +func (suite *KeeperTestSuite) OverrideMsgServer(newKeeper keeper.Keeper) { + suite.msgServer = keeper.NewMsgServerImpl(newKeeper) +} diff --git a/x/tokenfactory/keeper/msg_server.go b/x/tokenfactory/keeper/msg_server.go index 6264e99..0c97760 100644 --- a/x/tokenfactory/keeper/msg_server.go +++ b/x/tokenfactory/keeper/msg_server.go @@ -52,26 +52,32 @@ func (server msgServer) Mint(goCtx context.Context, msg *types.MsgMint) (*types. var err error ctx := sdk.UnwrapSDKContext(goCtx) - // Standard User Verification - _, denomExists := server.bankKeeper.GetDenomMetaData(ctx, msg.Amount.Denom) - if !denomExists { - return nil, types.ErrDenomDoesNotExist.Wrapf("denom: %s", msg.Amount.Denom) - } + sudoEnabled := types.IsCapabilityEnabled(server.Keeper.enabledCapabilities, types.EnableSudoMint) + senderIsSudoAble := server.IsSudoAdminFunc(goCtx, msg.Sender) + isSudo := sudoEnabled && senderIsSudoAble - authorityMetadata, err := server.Keeper.GetAuthorityMetadata(ctx, msg.Amount.GetDenom()) - if err != nil { - return nil, err - } + if !isSudo { + // Standard user verification if they are not a Sudo admin + _, denomExists := server.bankKeeper.GetDenomMetaData(ctx, msg.Amount.Denom) + if !denomExists { + return nil, types.ErrDenomDoesNotExist.Wrapf("denom: %s", msg.Amount.Denom) + } - if msg.Sender != authorityMetadata.GetAdmin() { - return nil, types.ErrUnauthorized + authorityMetadata, err := server.Keeper.GetAuthorityMetadata(ctx, msg.Amount.GetDenom()) + if err != nil { + return nil, err + } + + if msg.Sender != authorityMetadata.GetAdmin() { + return nil, types.ErrUnauthorized + } } if msg.MintToAddress == "" { msg.MintToAddress = msg.Sender } - err = server.Keeper.mintTo(ctx, msg.Amount, msg.MintToAddress) + err = server.Keeper.mintTo(ctx, msg.Amount, msg.MintToAddress, isSudo) if err != nil { return nil, err } diff --git a/x/tokenfactory/keeper/msg_server_test.go b/x/tokenfactory/keeper/msg_server_test.go index 7ba9bdb..c2bd989 100644 --- a/x/tokenfactory/keeper/msg_server_test.go +++ b/x/tokenfactory/keeper/msg_server_test.go @@ -1,6 +1,7 @@ package keeper_test import ( + "context" "fmt" "github.com/reecepbcups/tokenfactory/x/tokenfactory/types" @@ -21,8 +22,8 @@ func (suite *KeeperTestSuite) TestMintDenomMsg() { amount int64 mintDenom string admin string + sudoer string expectedMessageEvents int // the valid case should emit >= 1 - inflation sdkmath.LegacyDec }{ { desc: "denom does not exist", @@ -37,19 +38,37 @@ func (suite *KeeperTestSuite) TestMintDenomMsg() { admin: suite.TestAccs[0].String(), expectedMessageEvents: 1, }, + // Sudo Mints + { + desc: "successful sudo mint executed by an allowed sudoer", + amount: 10, + mintDenom: "unique", + admin: suite.TestAccs[0].String(), + sudoer: suite.TestAccs[0].String(), // this user can sudo mint + expectedMessageEvents: 1, + }, + { + desc: "invalid sudo mint from a non admin", + amount: 10, + mintDenom: "unique", + admin: suite.TestAccs[0].String(), + sudoer: "nope", + }, } { suite.Run(fmt.Sprintf("Case %s", tc.desc), func() { ctx := suite.Ctx.WithEventManager(sdk.NewEventManager()) suite.Require().Equal(0, len(ctx.EventManager().Events())) - // set minter values - minter, err := suite.App.MintKeeper.Minter.Get(ctx) - suite.Require().NoError(err) - minter.Inflation = tc.inflation - suite.Require().NoError(suite.App.MintKeeper.Minter.Set(ctx, minter)) + // Override the default IsSudoAdminFunc for testing + suite.App.TokenFactoryKeeper.IsSudoAdminFunc = func(ctx context.Context, addr string) bool { + return tc.sudoer == addr + } + + suite.OverrideMsgServer(suite.App.TokenFactoryKeeper) // Test mint message - suite.msgServer.Mint(ctx, types.NewMsgMint(tc.admin, sdk.NewInt64Coin(tc.mintDenom, 10))) //nolint:errcheck + suite.msgServer.Mint(ctx, types.NewMsgMint(tc.admin, sdk.NewInt64Coin(tc.mintDenom, tc.amount))) //nolint:errcheck + // Ensure current number and type of event is emitted suite.AssertEventEmitted(ctx, types.TypeMsgMint, tc.expectedMessageEvents) }) diff --git a/x/tokenfactory/types/capabilities.go b/x/tokenfactory/types/capabilities.go index bda91c9..f879cdf 100644 --- a/x/tokenfactory/types/capabilities.go +++ b/x/tokenfactory/types/capabilities.go @@ -4,6 +4,9 @@ const ( EnableSetMetadata = "enable_metadata" EnableForceTransfer = "enable_force_transfer" EnableBurnFrom = "enable_burn_from" + // Allows addresses of your choosing to mint tokens based on specific conditions. + // via the IsSudoAdminFunc + EnableSudoMint = "enable_admin_sudo_mint" ) func IsCapabilityEnabled(enabledCapabilities []string, capability string) bool {