From 92712f74b0d76fd1dd2f098b5a9a3de8122f45bf Mon Sep 17 00:00:00 2001 From: faultytolly <137398096+faultytolly@users.noreply.github.com> Date: Mon, 2 Dec 2024 16:12:19 +0100 Subject: [PATCH] feat: (tokenfactory): Wire module (#158) Co-authored-by: Faulty Tolly <@faulttolerance.net> Co-authored-by: Sergi Rene --- app/app.go | 100 +- app/apptesting/apptesting.go | 239 ++ app/test_helpers.go | 66 + x/tokenfactory/README.md | 155 ++ x/tokenfactory/bindings/custom_msg_test.go | 278 ++ x/tokenfactory/bindings/custom_query_test.go | 76 + x/tokenfactory/bindings/helpers_test.go | 94 + x/tokenfactory/bindings/message_plugin.go | 319 +++ x/tokenfactory/bindings/queries.go | 57 + x/tokenfactory/bindings/query_plugin.go | 122 + .../bindings/testdata/download_releases.sh | 16 + .../bindings/testdata/token_reflect.wasm | Bin 0 -> 285428 bytes x/tokenfactory/bindings/testdata/version.txt | 1 + x/tokenfactory/bindings/types/msg.go | 60 + x/tokenfactory/bindings/types/query.go | 59 + x/tokenfactory/bindings/types/types.go | 37 + x/tokenfactory/bindings/validate_msg_test.go | 402 +++ .../bindings/validate_queries_test.go | 116 + x/tokenfactory/bindings/wasm.go | 29 + x/tokenfactory/client/cli/query.go | 122 + x/tokenfactory/client/cli/tx.go | 188 ++ x/tokenfactory/keeper/admins.go | 49 + x/tokenfactory/keeper/admins_test.go | 403 +++ x/tokenfactory/keeper/bankactions.go | 40 + x/tokenfactory/keeper/createdenom.go | 86 + x/tokenfactory/keeper/createdenom_test.go | 163 ++ x/tokenfactory/keeper/creators.go | 27 + x/tokenfactory/keeper/genesis.go | 60 + x/tokenfactory/keeper/genesis_test.go | 62 + x/tokenfactory/keeper/grpc_query.go | 35 + x/tokenfactory/keeper/keeper.go | 82 + x/tokenfactory/keeper/keeper_test.go | 63 + x/tokenfactory/keeper/msg_server.go | 191 ++ x/tokenfactory/keeper/msg_server_test.go | 247 ++ x/tokenfactory/keeper/params.go | 18 + x/tokenfactory/module.go | 225 ++ x/tokenfactory/simulation/genesis.go | 18 + x/tokenfactory/simulation/operations.go | 406 +++ x/tokenfactory/simulation/params.go | 24 + x/tokenfactory/testhelpers/consts.go | 8 + x/tokenfactory/testhelpers/suite.go | 67 + x/tokenfactory/types/authorityMetadata.go | 15 + x/tokenfactory/types/authorityMetadata.pb.go | 351 +++ x/tokenfactory/types/authzcodec/codec.go | 24 + x/tokenfactory/types/codec.go | 48 + x/tokenfactory/types/denoms.go | 68 + x/tokenfactory/types/denoms_test.go | 132 + x/tokenfactory/types/errors.go | 22 + x/tokenfactory/types/events.go | 18 + x/tokenfactory/types/expected_keepers.go | 36 + x/tokenfactory/types/genesis.go | 51 + x/tokenfactory/types/genesis.pb.go | 649 +++++ x/tokenfactory/types/genesis_test.go | 139 + x/tokenfactory/types/keys.go | 49 + x/tokenfactory/types/msgs.go | 246 ++ x/tokenfactory/types/msgs_test.go | 452 ++++ x/tokenfactory/types/params.go | 62 + x/tokenfactory/types/params.pb.go | 342 +++ x/tokenfactory/types/query.pb.go | 1332 ++++++++++ x/tokenfactory/types/query.pb.gw.go | 355 +++ x/tokenfactory/types/tx.pb.go | 2238 +++++++++++++++++ 61 files changed, 11401 insertions(+), 38 deletions(-) create mode 100644 app/apptesting/apptesting.go create mode 100644 x/tokenfactory/README.md create mode 100644 x/tokenfactory/bindings/custom_msg_test.go create mode 100644 x/tokenfactory/bindings/custom_query_test.go create mode 100644 x/tokenfactory/bindings/helpers_test.go create mode 100644 x/tokenfactory/bindings/message_plugin.go create mode 100644 x/tokenfactory/bindings/queries.go create mode 100644 x/tokenfactory/bindings/query_plugin.go create mode 100755 x/tokenfactory/bindings/testdata/download_releases.sh create mode 100644 x/tokenfactory/bindings/testdata/token_reflect.wasm create mode 100644 x/tokenfactory/bindings/testdata/version.txt create mode 100644 x/tokenfactory/bindings/types/msg.go create mode 100644 x/tokenfactory/bindings/types/query.go create mode 100644 x/tokenfactory/bindings/types/types.go create mode 100644 x/tokenfactory/bindings/validate_msg_test.go create mode 100644 x/tokenfactory/bindings/validate_queries_test.go create mode 100644 x/tokenfactory/bindings/wasm.go create mode 100644 x/tokenfactory/client/cli/query.go create mode 100644 x/tokenfactory/client/cli/tx.go create mode 100644 x/tokenfactory/keeper/admins.go create mode 100644 x/tokenfactory/keeper/admins_test.go create mode 100644 x/tokenfactory/keeper/bankactions.go create mode 100644 x/tokenfactory/keeper/createdenom.go create mode 100644 x/tokenfactory/keeper/createdenom_test.go create mode 100644 x/tokenfactory/keeper/creators.go create mode 100644 x/tokenfactory/keeper/genesis.go create mode 100644 x/tokenfactory/keeper/genesis_test.go create mode 100644 x/tokenfactory/keeper/grpc_query.go create mode 100644 x/tokenfactory/keeper/keeper.go create mode 100644 x/tokenfactory/keeper/keeper_test.go create mode 100644 x/tokenfactory/keeper/msg_server.go create mode 100644 x/tokenfactory/keeper/msg_server_test.go create mode 100644 x/tokenfactory/keeper/params.go create mode 100644 x/tokenfactory/module.go create mode 100644 x/tokenfactory/simulation/genesis.go create mode 100644 x/tokenfactory/simulation/operations.go create mode 100644 x/tokenfactory/simulation/params.go create mode 100644 x/tokenfactory/testhelpers/consts.go create mode 100644 x/tokenfactory/testhelpers/suite.go create mode 100644 x/tokenfactory/types/authorityMetadata.go create mode 100644 x/tokenfactory/types/authorityMetadata.pb.go create mode 100644 x/tokenfactory/types/authzcodec/codec.go create mode 100644 x/tokenfactory/types/codec.go create mode 100644 x/tokenfactory/types/denoms.go create mode 100644 x/tokenfactory/types/denoms_test.go create mode 100644 x/tokenfactory/types/errors.go create mode 100644 x/tokenfactory/types/events.go create mode 100644 x/tokenfactory/types/expected_keepers.go create mode 100644 x/tokenfactory/types/genesis.go create mode 100644 x/tokenfactory/types/genesis.pb.go create mode 100644 x/tokenfactory/types/genesis_test.go create mode 100644 x/tokenfactory/types/keys.go create mode 100644 x/tokenfactory/types/msgs.go create mode 100644 x/tokenfactory/types/msgs_test.go create mode 100644 x/tokenfactory/types/params.go create mode 100644 x/tokenfactory/types/params.pb.go create mode 100644 x/tokenfactory/types/query.pb.go create mode 100644 x/tokenfactory/types/query.pb.gw.go create mode 100644 x/tokenfactory/types/tx.pb.go diff --git a/app/app.go b/app/app.go index da0bcb3..1ca9334 100644 --- a/app/app.go +++ b/app/app.go @@ -101,6 +101,11 @@ import ( timeupgradekeeper "github.com/dymensionxyz/dymension-rdk/x/timeupgrade/keeper" timeupgradetypes "github.com/dymensionxyz/dymension-rdk/x/timeupgrade/types" + "github.com/dymensionxyz/rollapp-wasm/x/tokenfactory" + "github.com/dymensionxyz/rollapp-wasm/x/tokenfactory/bindings" + tokenfactorykeeper "github.com/dymensionxyz/rollapp-wasm/x/tokenfactory/keeper" + tokenfactorytypes "github.com/dymensionxyz/rollapp-wasm/x/tokenfactory/types" + ibctransfer "github.com/cosmos/ibc-go/v6/modules/apps/transfer" ibctransferkeeper "github.com/cosmos/ibc-go/v6/modules/apps/transfer/keeper" ibctransfertypes "github.com/cosmos/ibc-go/v6/modules/apps/transfer/types" @@ -169,7 +174,7 @@ var ( ibchost.StoreKey, upgradetypes.StoreKey, epochstypes.StoreKey, hubgentypes.StoreKey, ibctransfertypes.StoreKey, capabilitytypes.StoreKey, - wasmtypes.StoreKey, gaslesstypes.StoreKey, + wasmtypes.StoreKey, tokenfactorytypes.StoreKey, gaslesstypes.StoreKey, hubtypes.StoreKey, callbackTypes.StoreKey, cwerrorsTypes.StoreKey, @@ -219,6 +224,7 @@ var ( ibctransfer.AppModuleBasic{}, vesting.AppModuleBasic{}, hubgenesis.AppModuleBasic{}, + tokenfactory.NewAppModuleBasic(), wasm.AppModuleBasic{}, hub.AppModuleBasic{}, timeupgrade.AppModuleBasic{}, @@ -242,6 +248,7 @@ var ( gaslesstypes.ModuleName: nil, callbackTypes.ModuleName: nil, rollappparamstypes.ModuleName: nil, + tokenfactorytypes.ModuleName: {authtypes.Minter, authtypes.Burner}, } // module accounts that are allowed to receive tokens @@ -283,27 +290,28 @@ type App struct { memKeys map[string]*storetypes.MemoryStoreKey // keepers - AccountKeeper authkeeper.AccountKeeper - AuthzKeeper authzkeeper.Keeper - BankKeeper bankkeeper.Keeper - CapabilityKeeper *capabilitykeeper.Keeper - StakingKeeper stakingkeeper.Keeper - SequencersKeeper seqkeeper.Keeper - MintKeeper mintkeeper.Keeper - EpochsKeeper epochskeeper.Keeper - DistrKeeper distrkeeper.Keeper - GovKeeper govkeeper.Keeper - HubGenesisKeeper hubgenkeeper.Keeper - UpgradeKeeper upgradekeeper.Keeper - ParamsKeeper paramskeeper.Keeper - IBCKeeper *ibckeeper.Keeper // IBC Keeper must be a pointer in the app, so we can SetRouter on it correctly - TransferKeeper ibctransferkeeper.Keeper - WasmKeeper wasmkeeper.Keeper - FeeGrantKeeper feegrantkeeper.Keeper - GaslessKeeper gaslesskeeper.Keeper - CallbackKeeper callbackKeeper.Keeper - CWErrorsKeeper cwerrorsKeeper.Keeper - TimeUpgradeKeeper timeupgradekeeper.Keeper + AccountKeeper authkeeper.AccountKeeper + AuthzKeeper authzkeeper.Keeper + BankKeeper bankkeeper.BaseKeeper + CapabilityKeeper *capabilitykeeper.Keeper + StakingKeeper stakingkeeper.Keeper + SequencersKeeper seqkeeper.Keeper + MintKeeper mintkeeper.Keeper + EpochsKeeper epochskeeper.Keeper + DistrKeeper distrkeeper.Keeper + GovKeeper govkeeper.Keeper + HubGenesisKeeper hubgenkeeper.Keeper + UpgradeKeeper upgradekeeper.Keeper + ParamsKeeper paramskeeper.Keeper + IBCKeeper *ibckeeper.Keeper // IBC Keeper must be a pointer in the app, so we can SetRouter on it correctly + TransferKeeper ibctransferkeeper.Keeper + WasmKeeper wasmkeeper.Keeper + TokenFactoryKeeper tokenfactorykeeper.Keeper + FeeGrantKeeper feegrantkeeper.Keeper + GaslessKeeper gaslesskeeper.Keeper + CallbackKeeper callbackKeeper.Keeper + CWErrorsKeeper cwerrorsKeeper.Keeper + TimeUpgradeKeeper timeupgradekeeper.Keeper // make scoped keepers public for test purposes ScopedIBCKeeper capabilitykeeper.ScopedKeeper @@ -446,6 +454,17 @@ func NewRollapp( ), ) + app.SequencersKeeper = *seqkeeper.NewKeeper( + appCodec, + keys[seqtypes.StoreKey], + app.GetSubspace(seqtypes.ModuleName), + authtypes.NewModuleAddress(seqtypes.ModuleName).String(), + app.AccountKeeper, + app.RollappParamsKeeper, + app.UpgradeKeeper, + []seqkeeper.AccountBumpFilterFunc{}, + ) + app.DistrKeeper = distrkeeper.NewKeeper( appCodec, keys[distrtypes.StoreKey], @@ -476,17 +495,6 @@ func NewRollapp( app.GetSubspace(rollappparamstypes.ModuleName), ) - app.SequencersKeeper = *seqkeeper.NewKeeper( - appCodec, - keys[seqtypes.StoreKey], - app.GetSubspace(seqtypes.ModuleName), - authtypes.NewModuleAddress(seqtypes.ModuleName).String(), - app.AccountKeeper, - app.RollappParamsKeeper, - app.UpgradeKeeper, - []seqkeeper.AccountBumpFilterFunc{}, - ) - app.IBCKeeper = ibckeeper.NewKeeper( appCodec, keys[ibchost.StoreKey], app.GetSubspace(ibchost.ModuleName), &app.SequencersKeeper, app.UpgradeKeeper, scopedIBCKeeper, ) @@ -503,6 +511,11 @@ func NewRollapp( AddRoute(upgradetypes.RouterKey, upgrade.NewSoftwareUpgradeProposalHandler(app.UpgradeKeeper)). AddRoute(ibcclienttypes.RouterKey, ibcclient.NewClientProposalHandler(app.IBCKeeper.ClientKeeper)) + // The gov proposal types can be individually enabled + if len(enabledProposals) != 0 { + govRouter.AddRoute(wasm.RouterKey, wasm.NewWasmProposalHandler(app.WasmKeeper, enabledProposals)) + } + govConfig := govtypes.DefaultConfig() /* Example of setting gov params: @@ -533,6 +546,15 @@ func NewRollapp( keys[hubtypes.StoreKey], ) + tokenFactoryKeeper := tokenfactorykeeper.NewKeeper( + keys[tokenfactorytypes.StoreKey], + app.GetSubspace(tokenfactorytypes.ModuleName), + app.AccountKeeper, + app.BankKeeper, + app.DistrKeeper, + ) + app.TokenFactoryKeeper = tokenFactoryKeeper + var ics4Wrapper ibcporttypes.ICS4Wrapper // The IBC tranfer submit is wrapped with the following middlewares: // - denom metadata middleware @@ -604,16 +626,13 @@ func NewRollapp( panic(fmt.Sprintf("error while reading wasm config: %s", err)) } - // The gov proposal types can be individually enabled - if len(enabledProposals) != 0 { - govRouter.AddRoute(wasm.RouterKey, wasm.NewWasmProposalHandler(app.WasmKeeper, enabledProposals)) - } - // Include the x/cwerrors query to stargate queries wasmOpts = append(wasmOpts, wasmkeeper.WithQueryPlugins(&wasmkeeper.QueryPlugins{ Stargate: wasmkeeper.AcceptListStargateQuerier(getAcceptedStargateQueries(), app.GRPCQueryRouter(), appCodec), })) + wasmOpts = append(bindings.RegisterCustomPlugins(&app.BankKeeper, &app.TokenFactoryKeeper), wasmOpts...) + // The last arguments can contain custom message handlers, and custom query handlers, // if we want to allow any custom callbacks availableCapabilities := strings.Join(AllCapabilities(), ",") @@ -682,6 +701,7 @@ func NewRollapp( ibc.NewAppModule(app.IBCKeeper), ibctransfer.NewAppModule(app.TransferKeeper), upgrade.NewAppModule(app.UpgradeKeeper), + tokenfactory.NewAppModule(app.TokenFactoryKeeper, app.AccountKeeper, app.BankKeeper), hubgenesis.NewAppModule(appCodec, app.HubGenesisKeeper), hub.NewAppModule(appCodec, app.HubKeeper), timeupgrade.NewAppModule(app.TimeUpgradeKeeper, app.UpgradeKeeper), @@ -720,6 +740,7 @@ func NewRollapp( hubgentypes.ModuleName, hubtypes.ModuleName, wasm.ModuleName, + tokenfactorytypes.ModuleName, callbackTypes.ModuleName, cwerrorsTypes.ModuleName, // does not have begin blocker rollappparamstypes.ModuleName, @@ -752,6 +773,7 @@ func NewRollapp( callbackTypes.ModuleName, cwerrorsTypes.ModuleName, rollappparamstypes.ModuleName, + tokenfactorytypes.ModuleName, } app.mm.SetOrderEndBlockers(endBlockersList...) @@ -783,6 +805,7 @@ func NewRollapp( gaslesstypes.ModuleName, hubgentypes.ModuleName, hubtypes.ModuleName, + tokenfactorytypes.ModuleName, wasm.ModuleName, callbackTypes.ModuleName, cwerrorsTypes.ModuleName, @@ -1147,6 +1170,7 @@ func initParamsKeeper(appCodec codec.BinaryCodec, legacyAmino *codec.LegacyAmino paramsKeeper.Subspace(gaslesstypes.ModuleName) paramsKeeper.Subspace(wasmtypes.ModuleName) + paramsKeeper.Subspace(tokenfactorytypes.ModuleName) paramsKeeper.Subspace(callbackTypes.ModuleName) paramsKeeper.Subspace(cwerrorsTypes.ModuleName) paramsKeeper.Subspace(rollappparamstypes.ModuleName) diff --git a/app/apptesting/apptesting.go b/app/apptesting/apptesting.go new file mode 100644 index 0000000..1d278ee --- /dev/null +++ b/app/apptesting/apptesting.go @@ -0,0 +1,239 @@ +package apptesting + +import ( + "fmt" + "time" + + "github.com/cosmos/cosmos-sdk/baseapp" + "github.com/cosmos/cosmos-sdk/client" + "github.com/cosmos/cosmos-sdk/store/rootmulti" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/types/tx/signing" + authsigning "github.com/cosmos/cosmos-sdk/x/auth/signing" + "github.com/cosmos/cosmos-sdk/x/bank/testutil" + distrtypes "github.com/cosmos/cosmos-sdk/x/distribution/types" + stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" + "github.com/stretchr/testify/suite" + abci "github.com/tendermint/tendermint/abci/types" + "github.com/tendermint/tendermint/crypto/ed25519" + "github.com/tendermint/tendermint/libs/log" + tmtypes "github.com/tendermint/tendermint/proto/tendermint/types" + dbm "github.com/tendermint/tm-db" + + minttypes "github.com/cosmos/cosmos-sdk/x/mint/types" + "github.com/dymensionxyz/rollapp-wasm/app" +) + +type KeeperTestHelper struct { + suite.Suite + + App *app.App + Ctx sdk.Context + QueryHelper *baseapp.QueryServiceTestHelper + TestAccs []sdk.AccAddress +} + +// Setup sets up basic environment for suite (App, Ctx, and test accounts) +func (s *KeeperTestHelper) Setup() { + s.App = app.Setup(false) + s.Ctx = s.App.BaseApp.NewContext(false, tmtypes.Header{Height: 1, ChainID: "osmosis-1", Time: time.Now().UTC()}) + s.QueryHelper = &baseapp.QueryServiceTestHelper{ + GRPCQueryRouter: s.App.GRPCQueryRouter(), + Ctx: s.Ctx, + } + s.TestAccs = CreateRandomAccounts(3) +} + +func (s *KeeperTestHelper) SetupTestForInitGenesis() { + // Setting to True, leads to init genesis not running + s.App = app.Setup(true) + s.Ctx = s.App.BaseApp.NewContext(true, tmtypes.Header{}) +} + +// CreateTestContext creates a test context. +func (s *KeeperTestHelper) CreateTestContext() sdk.Context { + ctx, _ := s.CreateTestContextWithMultiStore() + return ctx +} + +// CreateTestContextWithMultiStore creates a test context and returns it together with multi store. +func (s *KeeperTestHelper) CreateTestContextWithMultiStore() (sdk.Context, sdk.CommitMultiStore) { + db := dbm.NewMemDB() + logger := log.NewNopLogger() + + ms := rootmulti.NewStore(db, logger) + + return sdk.NewContext(ms, tmtypes.Header{}, false, logger), ms +} + +// CreateTestContext creates a test context. +func (s *KeeperTestHelper) Commit() { + oldHeight := s.Ctx.BlockHeight() + oldHeader := s.Ctx.BlockHeader() + s.App.Commit() + newHeader := tmtypes.Header{Height: oldHeight + 1, ChainID: oldHeader.ChainID, Time: oldHeader.Time.Add(time.Second)} + s.App.BeginBlock(abci.RequestBeginBlock{Header: newHeader}) + s.Ctx = s.App.NewContext(false, newHeader) +} + +// FundAcc funds target address with specified amount. +func (s *KeeperTestHelper) FundAcc(acc sdk.AccAddress, amounts sdk.Coins) { + err := testutil.FundAccount(s.App.BankKeeper, s.Ctx, acc, amounts) + s.Require().NoError(err) +} + +// FundModuleAcc funds target modules with specified amount. +func (s *KeeperTestHelper) FundModuleAcc(moduleName string, amounts sdk.Coins) { + err := testutil.FundModuleAccount(s.App.BankKeeper, s.Ctx, moduleName, amounts) + s.Require().NoError(err) +} + +func (s *KeeperTestHelper) MintCoins(coins sdk.Coins) { + err := s.App.BankKeeper.MintCoins(s.Ctx, minttypes.ModuleName, coins) + s.Require().NoError(err) +} + +// SetupValidator sets up a validator and returns the ValAddress. +func (s *KeeperTestHelper) SetupValidator(bondStatus stakingtypes.BondStatus) sdk.ValAddress { + /* + valPub := secp256k1.GenPrivKey().PubKey() + valAddr := sdk.ValAddress(valPub.Address()) + bondDenom := s.App.StakingKeeper.GetParams(s.Ctx).BondDenom + selfBond := sdk.NewCoins(sdk.Coin{Amount: sdk.NewInt(100), Denom: bondDenom}) + + s.FundAcc(sdk.AccAddress(valAddr), selfBond) + + stakingHandler := staking.NewHandler(s.App.StakingKeeper) + stakingCoin := sdk.NewCoin(sdk.DefaultBondDenom, selfBond[0].Amount) + ZeroCommission := stakingtypes.NewCommissionRates(sdk.ZeroDec(), sdk.ZeroDec(), sdk.ZeroDec()) + msg, err := stakingtypes.NewMsgCreateValidator(valAddr, valPub, stakingCoin, stakingtypes.Description{}, ZeroCommission, sdk.OneInt()) + s.Require().NoError(err) + res, err := stakingHandler(s.Ctx, msg) + s.Require().NoError(err) + s.Require().NotNil(res) + + val, found := s.App.StakingKeeper.GetValidator(s.Ctx, valAddr) + s.Require().True(found) + + val = val.UpdateStatus(bondStatus) + s.App.StakingKeeper.SetValidator(s.Ctx, val) + + return valAddr + */ + + return sdk.ValAddress("cookies") +} + +// BeginNewBlock starts a new block. +func (s *KeeperTestHelper) BeginNewBlock() { + var valAddr []byte + + validators := s.App.StakingKeeper.GetAllValidators(s.Ctx) + if len(validators) >= 1 { + valAddrFancy, err := validators[0].GetConsAddr() + s.Require().NoError(err) + valAddr = valAddrFancy.Bytes() + } else { + valAddrFancy := s.SetupValidator(stakingtypes.Bonded) + validator, _ := s.App.StakingKeeper.GetValidator(s.Ctx, valAddrFancy) + valAddr2, _ := validator.GetConsAddr() + valAddr = valAddr2.Bytes() + } + + s.BeginNewBlockWithProposer(valAddr) +} + +// BeginNewBlockWithProposer begins a new block with a proposer. +func (s *KeeperTestHelper) BeginNewBlockWithProposer(proposer sdk.ValAddress) { + validator, found := s.App.StakingKeeper.GetValidator(s.Ctx, proposer) + s.Assert().True(found) + + valConsAddr, err := validator.GetConsAddr() + s.Require().NoError(err) + + valAddr := valConsAddr.Bytes() + + newBlockTime := s.Ctx.BlockTime().Add(5 * time.Second) + + header := tmtypes.Header{Height: s.Ctx.BlockHeight() + 1, Time: newBlockTime} + newCtx := s.Ctx.WithBlockTime(newBlockTime).WithBlockHeight(s.Ctx.BlockHeight() + 1) + s.Ctx = newCtx + lastCommitInfo := abci.LastCommitInfo{ + Votes: []abci.VoteInfo{{ + Validator: abci.Validator{Address: valAddr, Power: 1000}, + SignedLastBlock: true, + }}, + } + reqBeginBlock := abci.RequestBeginBlock{Header: header, LastCommitInfo: lastCommitInfo} + + fmt.Println("beginning block ", s.Ctx.BlockHeight()) + s.App.BeginBlocker(s.Ctx, reqBeginBlock) +} + +// EndBlock ends the block. +func (s *KeeperTestHelper) EndBlock() { + reqEndBlock := abci.RequestEndBlock{Height: s.Ctx.BlockHeight()} + s.App.EndBlocker(s.Ctx, reqEndBlock) +} + +// AllocateRewardsToValidator allocates reward tokens to a distribution module then allocates rewards to the validator address. +func (s *KeeperTestHelper) AllocateRewardsToValidator(valAddr sdk.ValAddress, rewardAmt sdk.Int) { + validator, found := s.App.StakingKeeper.GetValidator(s.Ctx, valAddr) + s.Require().True(found) + + // allocate reward tokens to distribution module + coins := sdk.Coins{sdk.NewCoin(sdk.DefaultBondDenom, rewardAmt)} + err := testutil.FundModuleAccount(s.App.BankKeeper, s.Ctx, distrtypes.ModuleName, coins) + s.Require().NoError(err) + + // allocate rewards to validator + s.Ctx = s.Ctx.WithBlockHeight(s.Ctx.BlockHeight() + 1) + decTokens := sdk.DecCoins{{Denom: sdk.DefaultBondDenom, Amount: sdk.NewDec(20000)}} + s.App.DistrKeeper.AllocateTokensToValidator(s.Ctx, validator, decTokens) +} + +// BuildTx builds a transaction. +func (s *KeeperTestHelper) BuildTx( + txBuilder client.TxBuilder, + msgs []sdk.Msg, + sigV2 signing.SignatureV2, + memo string, txFee sdk.Coins, + gasLimit uint64, +) authsigning.Tx { + err := txBuilder.SetMsgs(msgs[0]) + s.Require().NoError(err) + + err = txBuilder.SetSignatures(sigV2) + s.Require().NoError(err) + + txBuilder.SetMemo(memo) + txBuilder.SetFeeAmount(txFee) + txBuilder.SetGasLimit(gasLimit) + + return txBuilder.GetTx() +} + +// CreateRandomAccounts is a function return a list of randomly generated AccAddresses +func CreateRandomAccounts(numAccts int) []sdk.AccAddress { + testAddrs := make([]sdk.AccAddress, numAccts) + for i := 0; i < numAccts; i++ { + pk := ed25519.GenPrivKey().PubKey() + testAddrs[i] = sdk.AccAddress(pk.Address()) + } + + return testAddrs +} + +// AssertEventEmitted asserts that ctx's event manager has emitted the given number of events +// of the given type. +func (s *KeeperTestHelper) AssertEventEmitted(ctx sdk.Context, eventTypeExpected string, numEventsExpected int) { + allEvents := ctx.EventManager().Events() + // filter out other events + actualEvents := make([]sdk.Event, 0) + for _, event := range allEvents { + if event.Type == eventTypeExpected { + actualEvents = append(actualEvents, event) + } + } + s.Equal(numEventsExpected, len(actualEvents)) +} diff --git a/app/test_helpers.go b/app/test_helpers.go index d09c9f5..aab4e42 100644 --- a/app/test_helpers.go +++ b/app/test_helpers.go @@ -6,6 +6,7 @@ import ( "testing" "time" + "github.com/CosmWasm/wasmd/x/wasm" appcodec "github.com/cosmos/cosmos-sdk/codec" codectypes "github.com/cosmos/cosmos-sdk/codec/types" "github.com/cosmos/cosmos-sdk/crypto/codec" @@ -20,6 +21,7 @@ import ( rollappparamstypes "github.com/dymensionxyz/dymension-rdk/x/rollappparams/types" "github.com/tendermint/tendermint/crypto/encoding" "github.com/tendermint/tendermint/libs/log" + "github.com/tendermint/tendermint/proto/tendermint/crypto" tmproto "github.com/tendermint/tendermint/proto/tendermint/types" types2 "github.com/tendermint/tendermint/types" dbm "github.com/tendermint/tm-db" @@ -238,3 +240,67 @@ func addDenomToBankModule(appCodec appcodec.Codec, genesisState GenesisState, de return genesisState } + +func Setup(isCheckTx bool, opts ...wasm.Option) *App { + db := dbm.NewMemDB() + app := NewRollapp(log.NewNopLogger(), db, nil, true, map[int64]bool{}, DefaultNodeHome, 5, MakeEncodingConfig(), wasm.EnableAllProposals, EmptyBaseAppOptions{}, opts) + + if !isCheckTx { + genesisState := NewDefaultGenesisState(app.appCodec) + + // make hub genesis happy by adding denom meta + bankGenesis := new(banktypes.GenesisState) + app.appCodec.MustUnmarshalJSON(genesisState[banktypes.ModuleName], bankGenesis) + bankGenesis.DenomMetadata = append(bankGenesis.DenomMetadata, banktypes.Metadata{ + Description: "stake", + DenomUnits: []*banktypes.DenomUnit{ + { + Denom: "stake", + Exponent: 6, + Aliases: nil, + }, + }, + Base: "stake", + Display: "stake", + Name: "stake", + Symbol: "stake", + URI: "", + URIHash: "", + }) + genesisState[banktypes.ModuleName] = app.AppCodec().MustMarshalJSON(bankGenesis) + + app.InitChain( + abci.RequestInitChain{ + Time: time.Now(), + ChainId: "cookies", + ConsensusParams: DefaultConsensusParams, + Validators: []abci.ValidatorUpdate{ + { + PubKey: crypto.PublicKey{}, + Power: 50, + }, + }, + AppStateBytes: marshalGenesis(genesisState), + InitialHeight: 0, + GenesisChecksum: "cookies", + }, + ) + } + return app +} + +// EmptyBaseAppOptions is a stub implementing AppOptions +type EmptyBaseAppOptions struct{} + +// Get implements AppOptions +func (ao EmptyBaseAppOptions) Get(o string) interface{} { + return nil +} + +func marshalGenesis(genesisState GenesisState) json.RawMessage { + stateBytes, err := json.MarshalIndent(genesisState, "", " ") + if err != nil { + panic(err) + } + return stateBytes +} diff --git a/x/tokenfactory/README.md b/x/tokenfactory/README.md new file mode 100644 index 0000000..7d6644e --- /dev/null +++ b/x/tokenfactory/README.md @@ -0,0 +1,155 @@ +# Token Factory + +The tokenfactory module allows any account to create a new token with +the name `factory/{creator address}/{subdenom}`. Because tokens are +namespaced by creator address, this allows token minting to be +permissionless, due to not needing to resolve name collisions. A single +account can create multiple denoms, by providing a unique subdenom for each +created denom. Once a denom is created, the original creator is given +"admin" privileges over the asset. This allows them to: + +- Mint their denom to any account +- Burn their denom from any account +- Create a transfer of their denom between any two accounts +- Change the admin. In the future, more admin capabilities may be added. Admins + can choose to share admin privileges with other accounts using the authz + module. The `ChangeAdmin` functionality, allows changing the master admin + account, or even setting it to `""`, meaning no account has admin privileges + of the asset. + +## Messages + +### CreateDenom + +Creates a denom of `factory/{creator address}/{subdenom}` given the denom creator +address and the subdenom. Subdenoms can contain `[a-zA-Z0-9./]`. + +```go +message MsgCreateDenom { + string sender = 1 [ (gogoproto.moretags) = "yaml:\"sender\"" ]; + string subdenom = 2 [ (gogoproto.moretags) = "yaml:\"subdenom\"" ]; +} +``` + +**State Modifications:** + +- Fund community pool with the denom creation fee from the creator address, set + in `Params`. +- Set `DenomMetaData` via bank keeper. +- Set `AuthorityMetadata` for the given denom to store the admin for the created + denom `factory/{creator address}/{subdenom}`. Admin is automatically set as the + Msg sender. +- Add denom to the `CreatorPrefixStore`, where a state of denoms created per + creator is kept. + +### Mint + +Minting of a specific denom is only allowed for the current admin. +Note, the current admin is defaulted to the creator of the denom. + +```go +message MsgMint { + string sender = 1 [ (gogoproto.moretags) = "yaml:\"sender\"" ]; + cosmos.base.v1beta1.Coin amount = 2 [ + (gogoproto.moretags) = "yaml:\"amount\"", + (gogoproto.nullable) = false + ]; +} +``` + +**State Modifications:** + +- Safety check the following + - Check that the denom minting is created via `tokenfactory` module + - Check that the sender of the message is the admin of the denom +- Mint designated amount of tokens for the denom via `bank` module + +### Burn + +Burning of a specific denom is only allowed for the current admin. +Note, the current admin is defaulted to the creator of the denom. + +```go +message MsgBurn { + string sender = 1 [ (gogoproto.moretags) = "yaml:\"sender\"" ]; + cosmos.base.v1beta1.Coin amount = 2 [ + (gogoproto.moretags) = "yaml:\"amount\"", + (gogoproto.nullable) = false + ]; +} +``` + +**State Modifications:** + +- Saftey check the following + - Check that the denom minting is created via `tokenfactory` module + - Check that the sender of the message is the admin of the denom +- Burn designated amount of tokens for the denom via `bank` module + +### ChangeAdmin + +Change the admin of a denom. Note, this is only allowed to be called by the current admin of the denom. + +```go +message MsgChangeAdmin { + string sender = 1 [ (gogoproto.moretags) = "yaml:\"sender\"" ]; + string denom = 2 [ (gogoproto.moretags) = "yaml:\"denom\"" ]; + string newAdmin = 3 [ (gogoproto.moretags) = "yaml:\"new_admin\"" ]; +} +``` + +### SetDenomMetadata + +Setting of metadata for a specific denom is only allowed for the admin of the denom. +It allows the overwriting of the denom metadata in the bank module. + +```go +message MsgChangeAdmin { + string sender = 1 [ (gogoproto.moretags) = "yaml:\"sender\"" ]; + cosmos.bank.v1beta1.Metadata metadata = 2 [ (gogoproto.moretags) = "yaml:\"metadata\"", (gogoproto.nullable) = false ]; +} +``` + +**State Modifications:** + +- Check that sender of the message is the admin of denom +- Modify `AuthorityMetadata` state entry to change the admin of the denom + +## Expectations from the chain + +The chain's bech32 prefix for addresses can be at most 16 characters long. + +This comes from denoms having a 128 byte maximum length, enforced from the SDK, +and us setting longest_subdenom to be 44 bytes. + +A token factory token's denom is: `factory/{creator address}/{subdenom}` + +Splitting up into sub-components, this has: + +- `len(factory) = 7` +- `2 * len("/") = 2` +- `len(longest_subdenom)` +- `len(creator_address) = len(bech32(longest_addr_length, chain_addr_prefix))`. + +Longest addr length at the moment is `32 bytes`. Due to SDK error correction +settings, this means `len(bech32(32, chain_addr_prefix)) = len(chain_addr_prefix) + 1 + 58`. +Adding this all, we have a total length constraint of `128 = 7 + 2 + len(longest_subdenom) + len(longest_chain_addr_prefix) + 1 + 58`. +Therefore `len(longest_subdenom) + len(longest_chain_addr_prefix) = 128 - (7 + 2 + 1 + 58) = 60`. + +The choice between how we standardized the split these 60 bytes between maxes +from longest_subdenom and longest_chain_addr_prefix is somewhat arbitrary. +Considerations going into this: + +- Per [BIP-0173](https://github.com/bitcoin/bips/blob/master/bip-0173.mediawiki#bech32) + the technically longest HRP for a 32 byte address ('data field') is 31 bytes. + (Comes from encode(data) = 59 bytes, and max length = 90 bytes) +- subdenom should be at least 32 bytes so hashes can go into it +- longer subdenoms are very helpful for creating human readable denoms +- chain addresses should prefer being smaller. The longest HRP in cosmos to date is 11 bytes. (`persistence`) + +For explicitness, its currently set to `len(longest_subdenom) = 44` and `len(longest_chain_addr_prefix) = 16`. + +Please note, if the SDK increases the maximum length of a denom from 128 bytes, +these caps should increase. + +So please don't make code rely on these max lengths for parsing. diff --git a/x/tokenfactory/bindings/custom_msg_test.go b/x/tokenfactory/bindings/custom_msg_test.go new file mode 100644 index 0000000..94823d4 --- /dev/null +++ b/x/tokenfactory/bindings/custom_msg_test.go @@ -0,0 +1,278 @@ +package bindings_test + +/* + +import ( + "encoding/json" + "fmt" + "testing" + + "github.com/stretchr/testify/require" + + "github.com/CosmWasm/wasmd/x/wasm/keeper" + wasmvmtypes "github.com/CosmWasm/wasmvm/types" + sdk "github.com/cosmos/cosmos-sdk/types" + + "github.com/dymensionxyz/rollapp-wasm/app" + bindings "github.com/dymensionxyz/rollapp-wasm/x/tokenfactory/bindings/types" + "github.com/dymensionxyz/rollapp-wasm/x/tokenfactory/types" +) + +func TestCreateDenomMsg(t *testing.T) { + creator := RandomAccountAddress() + osmosis, ctx := SetupCustomApp(t, creator) + + lucky := RandomAccountAddress() + reflect := instantiateReflectContract(t, ctx, osmosis, lucky) + require.NotEmpty(t, reflect) + + // Fund reflect contract with 100 base denom creation fees + reflectAmount := sdk.NewCoins(sdk.NewCoin(types.DefaultParams().DenomCreationFee[0].Denom, types.DefaultParams().DenomCreationFee[0].Amount.MulRaw(100))) + fundAccount(t, ctx, osmosis, reflect, reflectAmount) + + msg := bindings.TokenMsg{CreateDenom: &bindings.CreateDenom{ + Subdenom: "SUN", + }} + err := executeCustom(t, ctx, osmosis, reflect, lucky, msg, sdk.Coin{}) + require.NoError(t, err) + + // query the denom and see if it matches + query := bindings.TokenQuery{ + FullDenom: &bindings.FullDenom{ + CreatorAddr: reflect.String(), + Subdenom: "SUN", + }, + } + resp := bindings.FullDenomResponse{} + queryCustom(t, ctx, osmosis, reflect, query, &resp) + + require.Equal(t, resp.Denom, fmt.Sprintf("factory/%s/SUN", reflect.String())) +} + +func TestMintMsg(t *testing.T) { + creator := RandomAccountAddress() + osmosis, ctx := SetupCustomApp(t, creator) + + lucky := RandomAccountAddress() + reflect := instantiateReflectContract(t, ctx, osmosis, lucky) + require.NotEmpty(t, reflect) + + // Fund reflect contract with 100 base denom creation fees + reflectAmount := sdk.NewCoins(sdk.NewCoin(types.DefaultParams().DenomCreationFee[0].Denom, types.DefaultParams().DenomCreationFee[0].Amount.MulRaw(100))) + fundAccount(t, ctx, osmosis, reflect, reflectAmount) + + // lucky was broke + balances := osmosis.BankKeeper.GetAllBalances(ctx, lucky) + require.Empty(t, balances) + + // Create denom for minting + msg := bindings.TokenMsg{CreateDenom: &bindings.CreateDenom{ + Subdenom: "SUN", + }} + err := executeCustom(t, ctx, osmosis, reflect, lucky, msg, sdk.Coin{}) + require.NoError(t, err) + sunDenom := fmt.Sprintf("factory/%s/%s", reflect.String(), msg.CreateDenom.Subdenom) + + amount, ok := sdk.NewIntFromString("808010808") + require.True(t, ok) + msg = bindings.TokenMsg{MintTokens: &bindings.MintTokens{ + Denom: sunDenom, + Amount: amount, + MintToAddress: lucky.String(), + }} + err = executeCustom(t, ctx, osmosis, reflect, lucky, msg, sdk.Coin{}) + require.NoError(t, err) + + balances = osmosis.BankKeeper.GetAllBalances(ctx, lucky) + require.Len(t, balances, 1) + coin := balances[0] + require.Equal(t, amount, coin.Amount) + require.Contains(t, coin.Denom, "factory/") + + // query the denom and see if it matches + query := bindings.TokenQuery{ + FullDenom: &bindings.FullDenom{ + CreatorAddr: reflect.String(), + Subdenom: "SUN", + }, + } + resp := bindings.FullDenomResponse{} + queryCustom(t, ctx, osmosis, reflect, query, &resp) + + require.Equal(t, resp.Denom, coin.Denom) + + // mint the same denom again + err = executeCustom(t, ctx, osmosis, reflect, lucky, msg, sdk.Coin{}) + require.NoError(t, err) + + balances = osmosis.BankKeeper.GetAllBalances(ctx, lucky) + require.Len(t, balances, 1) + coin = balances[0] + require.Equal(t, amount.MulRaw(2), coin.Amount) + require.Contains(t, coin.Denom, "factory/") + + // query the denom and see if it matches + query = bindings.TokenQuery{ + FullDenom: &bindings.FullDenom{ + CreatorAddr: reflect.String(), + Subdenom: "SUN", + }, + } + resp = bindings.FullDenomResponse{} + queryCustom(t, ctx, osmosis, reflect, query, &resp) + + require.Equal(t, resp.Denom, coin.Denom) + + // now mint another amount / denom + // create it first + msg = bindings.TokenMsg{CreateDenom: &bindings.CreateDenom{ + Subdenom: "MOON", + }} + err = executeCustom(t, ctx, osmosis, reflect, lucky, msg, sdk.Coin{}) + require.NoError(t, err) + moonDenom := fmt.Sprintf("factory/%s/%s", reflect.String(), msg.CreateDenom.Subdenom) + + amount = amount.SubRaw(1) + msg = bindings.TokenMsg{MintTokens: &bindings.MintTokens{ + Denom: moonDenom, + Amount: amount, + MintToAddress: lucky.String(), + }} + err = executeCustom(t, ctx, osmosis, reflect, lucky, msg, sdk.Coin{}) + require.NoError(t, err) + + balances = osmosis.BankKeeper.GetAllBalances(ctx, lucky) + require.Len(t, balances, 2) + coin = balances[0] + require.Equal(t, amount, coin.Amount) + require.Contains(t, coin.Denom, "factory/") + + // query the denom and see if it matches + query = bindings.TokenQuery{ + FullDenom: &bindings.FullDenom{ + CreatorAddr: reflect.String(), + Subdenom: "MOON", + }, + } + resp = bindings.FullDenomResponse{} + queryCustom(t, ctx, osmosis, reflect, query, &resp) + + require.Equal(t, resp.Denom, coin.Denom) + + // and check the first denom is unchanged + coin = balances[1] + require.Equal(t, amount.AddRaw(1).MulRaw(2), coin.Amount) + require.Contains(t, coin.Denom, "factory/") + + // query the denom and see if it matches + query = bindings.TokenQuery{ + FullDenom: &bindings.FullDenom{ + CreatorAddr: reflect.String(), + Subdenom: "SUN", + }, + } + resp = bindings.FullDenomResponse{} + queryCustom(t, ctx, osmosis, reflect, query, &resp) + + require.Equal(t, resp.Denom, coin.Denom) +} + +func TestBurnMsg(t *testing.T) { + creator := RandomAccountAddress() + osmosis, ctx := SetupCustomApp(t, creator) + + lucky := RandomAccountAddress() + reflect := instantiateReflectContract(t, ctx, osmosis, lucky) + require.NotEmpty(t, reflect) + + // Fund reflect contract with 100 base denom creation fees + reflectAmount := sdk.NewCoins(sdk.NewCoin(types.DefaultParams().DenomCreationFee[0].Denom, types.DefaultParams().DenomCreationFee[0].Amount.MulRaw(100))) + fundAccount(t, ctx, osmosis, reflect, reflectAmount) + + // lucky was broke + balances := osmosis.BankKeeper.GetAllBalances(ctx, lucky) + require.Empty(t, balances) + + // Create denom for minting + msg := bindings.TokenMsg{CreateDenom: &bindings.CreateDenom{ + Subdenom: "SUN", + }} + err := executeCustom(t, ctx, osmosis, reflect, lucky, msg, sdk.Coin{}) + require.NoError(t, err) + sunDenom := fmt.Sprintf("factory/%s/%s", reflect.String(), msg.CreateDenom.Subdenom) + + amount, ok := sdk.NewIntFromString("808010808") + require.True(t, ok) + + msg = bindings.TokenMsg{MintTokens: &bindings.MintTokens{ + Denom: sunDenom, + Amount: amount, + MintToAddress: lucky.String(), + }} + err = executeCustom(t, ctx, osmosis, reflect, lucky, msg, sdk.Coin{}) + require.NoError(t, err) + + // can't burn from different address + msg = bindings.TokenMsg{BurnTokens: &bindings.BurnTokens{ + Denom: sunDenom, + Amount: amount, + BurnFromAddress: lucky.String(), + }} + err = executeCustom(t, ctx, osmosis, reflect, lucky, msg, sdk.Coin{}) + require.Error(t, err) + + // lucky needs to send balance to reflect contract to burn it + luckyBalance := osmosis.BankKeeper.GetAllBalances(ctx, lucky) + err = osmosis.BankKeeper.SendCoins(ctx, lucky, reflect, luckyBalance) + require.NoError(t, err) + + msg = bindings.TokenMsg{BurnTokens: &bindings.BurnTokens{ + Denom: sunDenom, + Amount: amount, + BurnFromAddress: reflect.String(), + }} + err = executeCustom(t, ctx, osmosis, reflect, lucky, msg, sdk.Coin{}) + require.NoError(t, err) +} + +type ReflectExec struct { + ReflectMsg *ReflectMsgs `json:"reflect_msg,omitempty"` + ReflectSubMsg *ReflectSubMsgs `json:"reflect_sub_msg,omitempty"` +} + +type ReflectMsgs struct { + Msgs []wasmvmtypes.CosmosMsg `json:"msgs"` +} + +type ReflectSubMsgs struct { + Msgs []wasmvmtypes.SubMsg `json:"msgs"` +} + +func executeCustom(t *testing.T, ctx sdk.Context, osmosis *app.TokenApp, contract sdk.AccAddress, sender sdk.AccAddress, msg bindings.TokenMsg, funds sdk.Coin) error { + wrapped := bindings.TokenFactoryMsg{ + Token: &msg, + } + customBz, err := json.Marshal(wrapped) + require.NoError(t, err) + + reflectMsg := ReflectExec{ + ReflectMsg: &ReflectMsgs{ + Msgs: []wasmvmtypes.CosmosMsg{{ + Custom: customBz, + }}, + }, + } + reflectBz, err := json.Marshal(reflectMsg) + require.NoError(t, err) + + // no funds sent if amount is 0 + var coins sdk.Coins + if !funds.Amount.IsNil() { + coins = sdk.Coins{funds} + } + + contractKeeper := keeper.NewDefaultPermissionKeeper(osmosis.WasmKeeper) + _, err = contractKeeper.Execute(ctx, contract, sender, reflectBz, coins) + return err +} +*/ diff --git a/x/tokenfactory/bindings/custom_query_test.go b/x/tokenfactory/bindings/custom_query_test.go new file mode 100644 index 0000000..03208f3 --- /dev/null +++ b/x/tokenfactory/bindings/custom_query_test.go @@ -0,0 +1,76 @@ +package bindings_test + +/* +import ( + "encoding/json" + "fmt" + "testing" + + "github.com/stretchr/testify/require" + + wasmvmtypes "github.com/CosmWasm/wasmvm/types" + sdk "github.com/cosmos/cosmos-sdk/types" + + "github.com/dymensionxyz/rollapp-wasm/app" + bindings "github.com/dymensionxyz/rollapp-wasm/x/tokenfactory/bindings/types" +) + +func TestQueryFullDenom(t *testing.T) { + actor := RandomAccountAddress() + tokenz, ctx := SetupCustomApp(t, actor) + + reflect := instantiateReflectContract(t, ctx, tokenz, actor) + require.NotEmpty(t, reflect) + + // query full denom + query := bindings.TokenQuery{ + FullDenom: &bindings.FullDenom{ + CreatorAddr: reflect.String(), + Subdenom: "ustart", + }, + } + resp := bindings.FullDenomResponse{} + queryCustom(t, ctx, tokenz, reflect, query, &resp) + + expected := fmt.Sprintf("factory/%s/ustart", reflect.String()) + require.EqualValues(t, expected, resp.Denom) +} + +type ReflectQuery struct { + Chain *ChainRequest `json:"chain,omitempty"` +} + +type ChainRequest struct { + Request wasmvmtypes.QueryRequest `json:"request"` +} + +type ChainResponse struct { + Data []byte `json:"data"` +} + +func queryCustom(t *testing.T, ctx sdk.Context, tokenz *app.TokenApp, contract sdk.AccAddress, request bindings.TokenQuery, response interface{}) { + wrapped := bindings.TokenFactoryQuery{ + Token: &request, + } + msgBz, err := json.Marshal(wrapped) + require.NoError(t, err) + fmt.Println(string(msgBz)) + + query := ReflectQuery{ + Chain: &ChainRequest{ + Request: wasmvmtypes.QueryRequest{Custom: msgBz}, + }, + } + queryBz, err := json.Marshal(query) + require.NoError(t, err) + fmt.Println(string(queryBz)) + + resBz, err := tokenz.WasmKeeper.QuerySmart(ctx, contract, queryBz) + require.NoError(t, err) + var resp ChainResponse + err = json.Unmarshal(resBz, &resp) + require.NoError(t, err) + err = json.Unmarshal(resp.Data, response) + require.NoError(t, err) +} +*/ diff --git a/x/tokenfactory/bindings/helpers_test.go b/x/tokenfactory/bindings/helpers_test.go new file mode 100644 index 0000000..357995a --- /dev/null +++ b/x/tokenfactory/bindings/helpers_test.go @@ -0,0 +1,94 @@ +package bindings_test + +/* +import ( + "os" + "testing" + "time" + + "github.com/stretchr/testify/require" + + "github.com/tendermint/tendermint/crypto" + "github.com/tendermint/tendermint/crypto/ed25519" + tmproto "github.com/tendermint/tendermint/proto/tendermint/types" + + "github.com/cosmos/cosmos-sdk/simapp" + sdk "github.com/cosmos/cosmos-sdk/types" + + "github.com/CosmWasm/wasmd/x/wasm/keeper" + "github.com/dymensionxyz/rollapp-wasm/app" +) + +func CreateTestInput() (*app.TokenApp, sdk.Context) { + osmosis := app.Setup(false) + ctx := osmosis.BaseApp.NewContext(false, tmproto.Header{Height: 1, ChainID: "osmosis-1", Time: time.Now().UTC()}) + return osmosis, ctx +} + +func FundAccount(t *testing.T, ctx sdk.Context, osmosis *app.TokenApp, acct sdk.AccAddress) { + err := simapp.FundAccount(osmosis.BankKeeper, ctx, acct, sdk.NewCoins( + sdk.NewCoin("uosmo", sdk.NewInt(10000000000)), + )) + require.NoError(t, err) +} + +// we need to make this deterministic (same every test run), as content might affect gas costs +func keyPubAddr() (crypto.PrivKey, crypto.PubKey, sdk.AccAddress) { + key := ed25519.GenPrivKey() + pub := key.PubKey() + addr := sdk.AccAddress(pub.Address()) + return key, pub, addr +} + +func RandomAccountAddress() sdk.AccAddress { + _, _, addr := keyPubAddr() + return addr +} + +func RandomBech32AccountAddress() string { + return RandomAccountAddress().String() +} + +func storeReflectCode(t *testing.T, ctx sdk.Context, tokenz *app.TokenApp, addr sdk.AccAddress) uint64 { + wasmCode, err := os.ReadFile("./testdata/token_reflect.wasm") + require.NoError(t, err) + + contractKeeper := keeper.NewDefaultPermissionKeeper(tokenz.WasmKeeper) + codeID, _, err := contractKeeper.Create(ctx, addr, wasmCode, nil) + require.NoError(t, err) + + return codeID +} + +func instantiateReflectContract(t *testing.T, ctx sdk.Context, tokenz *app.TokenApp, funder sdk.AccAddress) sdk.AccAddress { + initMsgBz := []byte("{}") + contractKeeper := keeper.NewDefaultPermissionKeeper(tokenz.WasmKeeper) + codeID := uint64(1) + addr, _, err := contractKeeper.Instantiate(ctx, codeID, funder, funder, initMsgBz, "demo contract", nil) + require.NoError(t, err) + + return addr +} + +func fundAccount(t *testing.T, ctx sdk.Context, tokenz *app.TokenApp, addr sdk.AccAddress, coins sdk.Coins) { + err := simapp.FundAccount( + tokenz.BankKeeper, + ctx, + addr, + coins, + ) + require.NoError(t, err) +} + +func SetupCustomApp(t *testing.T, addr sdk.AccAddress) (*app.TokenApp, sdk.Context) { + tokenz, ctx := CreateTestInput() + wasmKeeper := tokenz.WasmKeeper + + storeReflectCode(t, ctx, tokenz, addr) + + cInfo := wasmKeeper.GetCodeInfo(ctx, 1) + require.NotNil(t, cInfo) + + return tokenz, ctx +} +*/ diff --git a/x/tokenfactory/bindings/message_plugin.go b/x/tokenfactory/bindings/message_plugin.go new file mode 100644 index 0000000..1009507 --- /dev/null +++ b/x/tokenfactory/bindings/message_plugin.go @@ -0,0 +1,319 @@ +package bindings + +import ( + "encoding/json" + + wasmkeeper "github.com/CosmWasm/wasmd/x/wasm/keeper" + wasmvmtypes "github.com/CosmWasm/wasmvm/types" + sdk "github.com/cosmos/cosmos-sdk/types" + sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" + bankkeeper "github.com/cosmos/cosmos-sdk/x/bank/keeper" + banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" + + bindingstypes "github.com/dymensionxyz/rollapp-wasm/x/tokenfactory/bindings/types" + tokenfactorykeeper "github.com/dymensionxyz/rollapp-wasm/x/tokenfactory/keeper" + tokenfactorytypes "github.com/dymensionxyz/rollapp-wasm/x/tokenfactory/types" +) + +// CustomMessageDecorator returns decorator for custom CosmWasm bindings messages +func CustomMessageDecorator(bank *bankkeeper.BaseKeeper, tokenFactory *tokenfactorykeeper.Keeper) func(wasmkeeper.Messenger) wasmkeeper.Messenger { + return func(old wasmkeeper.Messenger) wasmkeeper.Messenger { + return &CustomMessenger{ + wrapped: old, + bank: bank, + tokenFactory: tokenFactory, + } + } +} + +type CustomMessenger struct { + wrapped wasmkeeper.Messenger + bank *bankkeeper.BaseKeeper + tokenFactory *tokenfactorykeeper.Keeper +} + +var _ wasmkeeper.Messenger = (*CustomMessenger)(nil) + +// DispatchMsg executes on the contractMsg. +func (m *CustomMessenger) DispatchMsg(ctx sdk.Context, contractAddr sdk.AccAddress, contractIBCPortID string, msg wasmvmtypes.CosmosMsg) ([]sdk.Event, [][]byte, error) { + if msg.Custom != nil { + // only handle the happy path where this is really creating / minting / swapping ... + // leave everything else for the wrapped version + var contractMsg bindingstypes.TokenFactoryMsg + if err := json.Unmarshal(msg.Custom, &contractMsg); err != nil { + return nil, nil, sdkerrors.Wrap(err, "token factory msg") + } + if contractMsg.Token == nil { + return nil, nil, sdkerrors.Wrap(sdkerrors.ErrUnknownRequest, "nil token field") + } + tokenMsg := contractMsg.Token + + if tokenMsg.CreateDenom != nil { + return m.createDenom(ctx, contractAddr, tokenMsg.CreateDenom) + } + if tokenMsg.MintTokens != nil { + return m.mintTokens(ctx, contractAddr, tokenMsg.MintTokens) + } + if tokenMsg.ChangeAdmin != nil { + return m.changeAdmin(ctx, contractAddr, tokenMsg.ChangeAdmin) + } + if tokenMsg.BurnTokens != nil { + return m.burnTokens(ctx, contractAddr, tokenMsg.BurnTokens) + } + if tokenMsg.SetMetadata != nil { + return m.setMetadata(ctx, contractAddr, tokenMsg.SetMetadata) + } + } + return m.wrapped.DispatchMsg(ctx, contractAddr, contractIBCPortID, msg) +} + +// createDenom creates a new token denom +func (m *CustomMessenger) createDenom(ctx sdk.Context, contractAddr sdk.AccAddress, createDenom *bindingstypes.CreateDenom) ([]sdk.Event, [][]byte, error) { + bz, err := PerformCreateDenom(m.tokenFactory, m.bank, ctx, contractAddr, createDenom) + if err != nil { + return nil, nil, sdkerrors.Wrap(err, "perform create denom") + } + // TODO: double check how this is all encoded to the contract + return nil, [][]byte{bz}, nil +} + +// PerformCreateDenom is used with createDenom to create a token denom; validates the msgCreateDenom. +func PerformCreateDenom(f *tokenfactorykeeper.Keeper, b *bankkeeper.BaseKeeper, ctx sdk.Context, contractAddr sdk.AccAddress, createDenom *bindingstypes.CreateDenom) ([]byte, error) { + if createDenom == nil { + return nil, wasmvmtypes.InvalidRequest{Err: "create denom null create denom"} + } + + msgServer := tokenfactorykeeper.NewMsgServerImpl(*f) + + msgCreateDenom := tokenfactorytypes.NewMsgCreateDenom(contractAddr.String(), createDenom.Subdenom) + + if err := msgCreateDenom.ValidateBasic(); err != nil { + return nil, sdkerrors.Wrap(err, "failed validating MsgCreateDenom") + } + + // Create denom + resp, err := msgServer.CreateDenom( + sdk.WrapSDKContext(ctx), + msgCreateDenom, + ) + if err != nil { + return nil, sdkerrors.Wrap(err, "creating denom") + } + + if createDenom.Metadata != nil { + newDenom := resp.NewTokenDenom + err := PerformSetMetadata(f, b, ctx, contractAddr, newDenom, *createDenom.Metadata) + if err != nil { + return nil, sdkerrors.Wrap(err, "setting metadata") + } + } + + return resp.Marshal() +} + +// mintTokens mints tokens of a specified denom to an address. +func (m *CustomMessenger) mintTokens(ctx sdk.Context, contractAddr sdk.AccAddress, mint *bindingstypes.MintTokens) ([]sdk.Event, [][]byte, error) { + err := PerformMint(m.tokenFactory, m.bank, ctx, contractAddr, mint) + if err != nil { + return nil, nil, sdkerrors.Wrap(err, "perform mint") + } + return nil, nil, nil +} + +// PerformMint used with mintTokens to validate the mint message and mint through token factory. +func PerformMint(f *tokenfactorykeeper.Keeper, b *bankkeeper.BaseKeeper, ctx sdk.Context, contractAddr sdk.AccAddress, mint *bindingstypes.MintTokens) error { + if mint == nil { + return wasmvmtypes.InvalidRequest{Err: "mint token null mint"} + } + rcpt, err := parseAddress(mint.MintToAddress) + if err != nil { + return err + } + + coin := sdk.Coin{Denom: mint.Denom, Amount: mint.Amount} + sdkMsg := tokenfactorytypes.NewMsgMint(contractAddr.String(), coin) + if err = sdkMsg.ValidateBasic(); err != nil { + return err + } + + // Mint through token factory / message server + msgServer := tokenfactorykeeper.NewMsgServerImpl(*f) + _, err = msgServer.Mint(sdk.WrapSDKContext(ctx), sdkMsg) + if err != nil { + return sdkerrors.Wrap(err, "minting coins from message") + } + err = b.SendCoins(ctx, contractAddr, rcpt, sdk.NewCoins(coin)) + if err != nil { + return sdkerrors.Wrap(err, "sending newly minted coins from message") + } + return nil +} + +// changeAdmin changes the admin. +func (m *CustomMessenger) changeAdmin(ctx sdk.Context, contractAddr sdk.AccAddress, changeAdmin *bindingstypes.ChangeAdmin) ([]sdk.Event, [][]byte, error) { + err := ChangeAdmin(m.tokenFactory, ctx, contractAddr, changeAdmin) + if err != nil { + return nil, nil, sdkerrors.Wrap(err, "failed to change admin") + } + return nil, nil, nil +} + +// ChangeAdmin is used with changeAdmin to validate changeAdmin messages and to dispatch. +func ChangeAdmin(f *tokenfactorykeeper.Keeper, ctx sdk.Context, contractAddr sdk.AccAddress, changeAdmin *bindingstypes.ChangeAdmin) error { + if changeAdmin == nil { + return wasmvmtypes.InvalidRequest{Err: "changeAdmin is nil"} + } + newAdminAddr, err := parseAddress(changeAdmin.NewAdminAddress) + if err != nil { + return err + } + + changeAdminMsg := tokenfactorytypes.NewMsgChangeAdmin(contractAddr.String(), changeAdmin.Denom, newAdminAddr.String()) + if err := changeAdminMsg.ValidateBasic(); err != nil { + return err + } + + msgServer := tokenfactorykeeper.NewMsgServerImpl(*f) + _, err = msgServer.ChangeAdmin(sdk.WrapSDKContext(ctx), changeAdminMsg) + if err != nil { + return sdkerrors.Wrap(err, "failed changing admin from message") + } + return nil +} + +// burnTokens burns tokens. +func (m *CustomMessenger) burnTokens(ctx sdk.Context, contractAddr sdk.AccAddress, burn *bindingstypes.BurnTokens) ([]sdk.Event, [][]byte, error) { + err := PerformBurn(m.tokenFactory, ctx, contractAddr, burn) + if err != nil { + return nil, nil, sdkerrors.Wrap(err, "perform burn") + } + return nil, nil, nil +} + +// PerformBurn performs token burning after validating tokenBurn message. +func PerformBurn(f *tokenfactorykeeper.Keeper, ctx sdk.Context, contractAddr sdk.AccAddress, burn *bindingstypes.BurnTokens) error { + if burn == nil { + return wasmvmtypes.InvalidRequest{Err: "burn token null mint"} + } + if burn.BurnFromAddress != "" && burn.BurnFromAddress != contractAddr.String() { + return wasmvmtypes.InvalidRequest{Err: "BurnFromAddress must be \"\""} + } + + coin := sdk.Coin{Denom: burn.Denom, Amount: burn.Amount} + sdkMsg := tokenfactorytypes.NewMsgBurn(contractAddr.String(), coin) + if err := sdkMsg.ValidateBasic(); err != nil { + return err + } + + // Burn through token factory / message server + msgServer := tokenfactorykeeper.NewMsgServerImpl(*f) + _, err := msgServer.Burn(sdk.WrapSDKContext(ctx), sdkMsg) + if err != nil { + return sdkerrors.Wrap(err, "burning coins from message") + } + return nil +} + +// createDenom creates a new token denom +func (m *CustomMessenger) setMetadata(ctx sdk.Context, contractAddr sdk.AccAddress, setMetadata *bindingstypes.SetMetadata) ([]sdk.Event, [][]byte, error) { + err := PerformSetMetadata(m.tokenFactory, m.bank, ctx, contractAddr, setMetadata.Denom, setMetadata.Metadata) + if err != nil { + return nil, nil, sdkerrors.Wrap(err, "perform create denom") + } + return nil, nil, nil +} + +// PerformSetMetadata is used with setMetadata to add new metadata +// It also is called inside CreateDenom if optional metadata field is set +func PerformSetMetadata(f *tokenfactorykeeper.Keeper, b *bankkeeper.BaseKeeper, ctx sdk.Context, contractAddr sdk.AccAddress, denom string, metadata bindingstypes.Metadata) error { + // ensure contract address is admin of denom + auth, err := f.GetAuthorityMetadata(ctx, denom) + if err != nil { + return err + } + if auth.Admin != contractAddr.String() { + return wasmvmtypes.InvalidRequest{Err: "only admin can set metadata"} + } + + // ensure we are setting proper denom metadata (bank uses Base field, fill it if missing) + if metadata.Base == "" { + metadata.Base = denom + } else if metadata.Base != denom { + // this is the key that we set + return wasmvmtypes.InvalidRequest{Err: "Base must be the same as denom"} + } + + // Create and validate the metadata + bankMetadata := WasmMetadataToSdk(metadata) + if err := bankMetadata.Validate(); err != nil { + return err + } + + b.SetDenomMetaData(ctx, bankMetadata) + return nil +} + +// GetFullDenom is a function, not method, so the message_plugin can use it +func GetFullDenom(contract string, subDenom string) (string, error) { + // Address validation + if _, err := parseAddress(contract); err != nil { + return "", err + } + fullDenom, err := tokenfactorytypes.GetTokenDenom(contract, subDenom) + if err != nil { + return "", sdkerrors.Wrap(err, "validate sub-denom") + } + + return fullDenom, nil +} + +// parseAddress parses address from bech32 string and verifies its format. +func parseAddress(addr string) (sdk.AccAddress, error) { + parsed, err := sdk.AccAddressFromBech32(addr) + if err != nil { + return nil, sdkerrors.Wrap(err, "address from bech32") + } + err = sdk.VerifyAddressFormat(parsed) + if err != nil { + return nil, sdkerrors.Wrap(err, "verify address format") + } + return parsed, nil +} + +func WasmMetadataToSdk(metadata bindingstypes.Metadata) banktypes.Metadata { + denoms := []*banktypes.DenomUnit{} + for _, unit := range metadata.DenomUnits { + denoms = append(denoms, &banktypes.DenomUnit{ + Denom: unit.Denom, + Exponent: unit.Exponent, + Aliases: unit.Aliases, + }) + } + return banktypes.Metadata{ + Description: metadata.Description, + Display: metadata.Display, + Base: metadata.Base, + Name: metadata.Name, + Symbol: metadata.Symbol, + DenomUnits: denoms, + } +} + +func SdkMetadataToWasm(metadata banktypes.Metadata) *bindingstypes.Metadata { + denoms := []bindingstypes.DenomUnit{} + for _, unit := range metadata.DenomUnits { + denoms = append(denoms, bindingstypes.DenomUnit{ + Denom: unit.Denom, + Exponent: unit.Exponent, + Aliases: unit.Aliases, + }) + } + return &bindingstypes.Metadata{ + Description: metadata.Description, + Display: metadata.Display, + Base: metadata.Base, + Name: metadata.Name, + Symbol: metadata.Symbol, + DenomUnits: denoms, + } +} diff --git a/x/tokenfactory/bindings/queries.go b/x/tokenfactory/bindings/queries.go new file mode 100644 index 0000000..1495513 --- /dev/null +++ b/x/tokenfactory/bindings/queries.go @@ -0,0 +1,57 @@ +package bindings + +import ( + "fmt" + + sdk "github.com/cosmos/cosmos-sdk/types" + bankkeeper "github.com/cosmos/cosmos-sdk/x/bank/keeper" + + bindingstypes "github.com/dymensionxyz/rollapp-wasm/x/tokenfactory/bindings/types" + tokenfactorykeeper "github.com/dymensionxyz/rollapp-wasm/x/tokenfactory/keeper" +) + +type QueryPlugin struct { + bankKeeper *bankkeeper.BaseKeeper + tokenFactoryKeeper *tokenfactorykeeper.Keeper +} + +// NewQueryPlugin returns a reference to a new QueryPlugin. +func NewQueryPlugin(b *bankkeeper.BaseKeeper, tfk *tokenfactorykeeper.Keeper) *QueryPlugin { + return &QueryPlugin{ + bankKeeper: b, + tokenFactoryKeeper: tfk, + } +} + +// GetDenomAdmin is a query to get denom admin. +func (qp QueryPlugin) GetDenomAdmin(ctx sdk.Context, denom string) (*bindingstypes.AdminResponse, error) { + metadata, err := qp.tokenFactoryKeeper.GetAuthorityMetadata(ctx, denom) + if err != nil { + return nil, fmt.Errorf("failed to get admin for denom: %s", denom) + } + return &bindingstypes.AdminResponse{Admin: metadata.Admin}, nil +} + +func (qp QueryPlugin) GetDenomsByCreator(ctx sdk.Context, creator string) (*bindingstypes.DenomsByCreatorResponse, error) { + // TODO: validate creator address + denoms := qp.tokenFactoryKeeper.GetDenomsFromCreator(ctx, creator) + return &bindingstypes.DenomsByCreatorResponse{Denoms: denoms}, nil +} + +func (qp QueryPlugin) GetMetadata(ctx sdk.Context, denom string) (*bindingstypes.MetadataResponse, error) { + metadata, found := qp.bankKeeper.GetDenomMetaData(ctx, denom) + var parsed *bindingstypes.Metadata + if found { + parsed = SdkMetadataToWasm(metadata) + } + return &bindingstypes.MetadataResponse{Metadata: parsed}, nil +} + +func (qp QueryPlugin) GetParams(ctx sdk.Context) (*bindingstypes.ParamsResponse, error) { + params := qp.tokenFactoryKeeper.GetParams(ctx) + return &bindingstypes.ParamsResponse{ + Params: bindingstypes.Params{ + DenomCreationFee: ConvertSdkCoinsToWasmCoins(params.DenomCreationFee), + }, + }, nil +} diff --git a/x/tokenfactory/bindings/query_plugin.go b/x/tokenfactory/bindings/query_plugin.go new file mode 100644 index 0000000..720acd1 --- /dev/null +++ b/x/tokenfactory/bindings/query_plugin.go @@ -0,0 +1,122 @@ +package bindings + +import ( + "encoding/json" + "fmt" + + wasmvmtypes "github.com/CosmWasm/wasmvm/types" + sdk "github.com/cosmos/cosmos-sdk/types" + sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" + + bindingstypes "github.com/dymensionxyz/rollapp-wasm/x/tokenfactory/bindings/types" +) + +// CustomQuerier dispatches custom CosmWasm bindings queries. +func CustomQuerier(qp *QueryPlugin) func(ctx sdk.Context, request json.RawMessage) ([]byte, error) { + return func(ctx sdk.Context, request json.RawMessage) ([]byte, error) { + var contractQuery bindingstypes.TokenFactoryQuery + if err := json.Unmarshal(request, &contractQuery); err != nil { + return nil, sdkerrors.Wrap(err, "osmosis query") + } + if contractQuery.Token == nil { + return nil, sdkerrors.Wrap(sdkerrors.ErrUnknownRequest, "nil token field") + } + tokenQuery := contractQuery.Token + + switch { + case tokenQuery.FullDenom != nil: + creator := tokenQuery.FullDenom.CreatorAddr + subdenom := tokenQuery.FullDenom.Subdenom + + fullDenom, err := GetFullDenom(creator, subdenom) + if err != nil { + return nil, sdkerrors.Wrap(err, "osmo full denom query") + } + + res := bindingstypes.FullDenomResponse{ + Denom: fullDenom, + } + + bz, err := json.Marshal(res) + if err != nil { + return nil, sdkerrors.Wrap(err, "failed to marshal FullDenomResponse") + } + + return bz, nil + + case tokenQuery.Admin != nil: + res, err := qp.GetDenomAdmin(ctx, tokenQuery.Admin.Denom) + if err != nil { + return nil, err + } + + bz, err := json.Marshal(res) + if err != nil { + return nil, fmt.Errorf("failed to JSON marshal AdminResponse: %w", err) + } + + return bz, nil + + case tokenQuery.Metadata != nil: + res, err := qp.GetMetadata(ctx, tokenQuery.Metadata.Denom) + if err != nil { + return nil, err + } + + bz, err := json.Marshal(res) + if err != nil { + return nil, fmt.Errorf("failed to JSON marshal MetadataResponse: %w", err) + } + + return bz, nil + + case tokenQuery.DenomsByCreator != nil: + res, err := qp.GetDenomsByCreator(ctx, tokenQuery.DenomsByCreator.Creator) + if err != nil { + return nil, err + } + + bz, err := json.Marshal(res) + if err != nil { + return nil, fmt.Errorf("failed to JSON marshal DenomsByCreatorResponse: %w", err) + } + + return bz, nil + + case tokenQuery.Params != nil: + res, err := qp.GetParams(ctx) + if err != nil { + return nil, err + } + + bz, err := json.Marshal(res) + if err != nil { + return nil, fmt.Errorf("failed to JSON marshal ParamsResponse: %w", err) + } + + return bz, nil + + default: + return nil, wasmvmtypes.UnsupportedRequest{Kind: "unknown token query variant"} + } + } +} + +// ConvertSdkCoinsToWasmCoins converts sdk type coins to wasm vm type coins +func ConvertSdkCoinsToWasmCoins(coins []sdk.Coin) wasmvmtypes.Coins { + var toSend wasmvmtypes.Coins + for _, coin := range coins { + c := ConvertSdkCoinToWasmCoin(coin) + toSend = append(toSend, c) + } + return toSend +} + +// ConvertSdkCoinToWasmCoin converts a sdk type coin to a wasm vm type coin +func ConvertSdkCoinToWasmCoin(coin sdk.Coin) wasmvmtypes.Coin { + return wasmvmtypes.Coin{ + Denom: coin.Denom, + // Note: gamm tokens have 18 decimal places, so 10^22 is common, no longer in u64 range + Amount: coin.Amount.String(), + } +} diff --git a/x/tokenfactory/bindings/testdata/download_releases.sh b/x/tokenfactory/bindings/testdata/download_releases.sh new file mode 100755 index 0000000..161a33f --- /dev/null +++ b/x/tokenfactory/bindings/testdata/download_releases.sh @@ -0,0 +1,16 @@ +#!/bin/bash +set -o errexit -o nounset -o pipefail +command -v shellcheck > /dev/null && shellcheck "$0" + +if [ $# -ne 1 ]; then + echo "Usage: ./download_releases.sh RELEASE_TAG" + exit 1 +fi + +tag="$1" +url="https://github.com/CosmWasm/token-bindings/releases/download/$tag/token_reflect.wasm" +echo "Downloading $url ..." +wget -O "token_reflect.wasm" "$url" + +rm -f version.txt +echo "$tag" >version.txt \ No newline at end of file diff --git a/x/tokenfactory/bindings/testdata/token_reflect.wasm b/x/tokenfactory/bindings/testdata/token_reflect.wasm new file mode 100644 index 0000000000000000000000000000000000000000..0d742e10fc20d98e5432e413032502d8ed98b5f8 GIT binary patch literal 285428 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

90R9hsOfjf>BydfNz7?O=Pv+=5e@NQ?&k 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 O%Q}Cm?Oi3vD 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 0 { + require.Error(t, err) + actualErrMsg := err.Error() + require.Equal(t, spec.expErrMsg, actualErrMsg) + return + } + require.NoError(t, err) + }) + } +} + +func TestMint(t *testing.T) { + creator := RandomAccountAddress() + tokenz, ctx := SetupCustomApp(t, creator) + + // Fund actor with 100 base denom creation fees + tokenCreationFeeAmt := sdk.NewCoins(sdk.NewCoin(types.DefaultParams().DenomCreationFee[0].Denom, types.DefaultParams().DenomCreationFee[0].Amount.MulRaw(100))) + fundAccount(t, ctx, tokenz, creator, tokenCreationFeeAmt) + + // Create denoms for valid mint tests + validDenom := bindings.CreateDenom{ + Subdenom: "MOON", + } + _, err := wasmbinding.PerformCreateDenom(&tokenz.TokenFactoryKeeper, &tokenz.BankKeeper, ctx, creator, &validDenom) + require.NoError(t, err) + + emptyDenom := bindings.CreateDenom{ + Subdenom: "", + } + _, err = wasmbinding.PerformCreateDenom(&tokenz.TokenFactoryKeeper, &tokenz.BankKeeper, ctx, creator, &emptyDenom) + require.NoError(t, err) + + validDenomStr := fmt.Sprintf("factory/%s/%s", creator.String(), validDenom.Subdenom) + emptyDenomStr := fmt.Sprintf("factory/%s/%s", creator.String(), emptyDenom.Subdenom) + + lucky := RandomAccountAddress() + + // lucky was broke + balances := tokenz.BankKeeper.GetAllBalances(ctx, lucky) + require.Empty(t, balances) + + amount, ok := sdk.NewIntFromString("8080") + require.True(t, ok) + + specs := map[string]struct { + mint *bindings.MintTokens + expErr bool + }{ + "valid mint": { + mint: &bindings.MintTokens{ + Denom: validDenomStr, + Amount: amount, + MintToAddress: lucky.String(), + }, + }, + "empty sub-denom": { + mint: &bindings.MintTokens{ + Denom: emptyDenomStr, + Amount: amount, + MintToAddress: lucky.String(), + }, + expErr: false, + }, + "nonexistent sub-denom": { + mint: &bindings.MintTokens{ + Denom: fmt.Sprintf("factory/%s/%s", creator.String(), "SUN"), + Amount: amount, + MintToAddress: lucky.String(), + }, + expErr: true, + }, + "invalid sub-denom": { + mint: &bindings.MintTokens{ + Denom: "sub-denom_2", + Amount: amount, + MintToAddress: lucky.String(), + }, + expErr: true, + }, + "zero amount": { + mint: &bindings.MintTokens{ + Denom: validDenomStr, + Amount: sdk.ZeroInt(), + MintToAddress: lucky.String(), + }, + expErr: true, + }, + "negative amount": { + mint: &bindings.MintTokens{ + Denom: validDenomStr, + Amount: amount.Neg(), + MintToAddress: lucky.String(), + }, + expErr: true, + }, + "empty recipient": { + mint: &bindings.MintTokens{ + Denom: validDenomStr, + Amount: amount, + MintToAddress: "", + }, + expErr: true, + }, + "invalid recipient": { + mint: &bindings.MintTokens{ + Denom: validDenomStr, + Amount: amount, + MintToAddress: "invalid", + }, + expErr: true, + }, + "null mint": { + mint: nil, + expErr: true, + }, + } + for name, spec := range specs { + t.Run(name, func(t *testing.T) { + // when + gotErr := wasmbinding.PerformMint(&tokenz.TokenFactoryKeeper, &tokenz.BankKeeper, ctx, creator, spec.mint) + // then + if spec.expErr { + require.Error(t, gotErr) + return + } + require.NoError(t, gotErr) + }) + } +} + +func TestBurn(t *testing.T) { + creator := RandomAccountAddress() + tokenz, ctx := SetupCustomApp(t, creator) + + // Fund actor with 100 base denom creation fees + tokenCreationFeeAmt := sdk.NewCoins(sdk.NewCoin(types.DefaultParams().DenomCreationFee[0].Denom, types.DefaultParams().DenomCreationFee[0].Amount.MulRaw(100))) + fundAccount(t, ctx, tokenz, creator, tokenCreationFeeAmt) + + // Create denoms for valid burn tests + validDenom := bindings.CreateDenom{ + Subdenom: "MOON", + } + _, err := wasmbinding.PerformCreateDenom(&tokenz.TokenFactoryKeeper, &tokenz.BankKeeper, ctx, creator, &validDenom) + require.NoError(t, err) + + emptyDenom := bindings.CreateDenom{ + Subdenom: "", + } + _, err = wasmbinding.PerformCreateDenom(&tokenz.TokenFactoryKeeper, &tokenz.BankKeeper, ctx, creator, &emptyDenom) + require.NoError(t, err) + + lucky := RandomAccountAddress() + + // lucky was broke + balances := tokenz.BankKeeper.GetAllBalances(ctx, lucky) + require.Empty(t, balances) + + validDenomStr := fmt.Sprintf("factory/%s/%s", creator.String(), validDenom.Subdenom) + emptyDenomStr := fmt.Sprintf("factory/%s/%s", creator.String(), emptyDenom.Subdenom) + mintAmount, ok := sdk.NewIntFromString("8080") + require.True(t, ok) + + specs := map[string]struct { + burn *bindings.BurnTokens + expErr bool + }{ + "valid burn": { + burn: &bindings.BurnTokens{ + Denom: validDenomStr, + Amount: mintAmount, + BurnFromAddress: creator.String(), + }, + expErr: false, + }, + "non admin address": { + burn: &bindings.BurnTokens{ + Denom: validDenomStr, + Amount: mintAmount, + BurnFromAddress: lucky.String(), + }, + expErr: true, + }, + "empty sub-denom": { + burn: &bindings.BurnTokens{ + Denom: emptyDenomStr, + Amount: mintAmount, + BurnFromAddress: creator.String(), + }, + expErr: false, + }, + "invalid sub-denom": { + burn: &bindings.BurnTokens{ + Denom: "sub-denom_2", + Amount: mintAmount, + BurnFromAddress: creator.String(), + }, + expErr: true, + }, + "non-minted denom": { + burn: &bindings.BurnTokens{ + Denom: fmt.Sprintf("factory/%s/%s", creator.String(), "SUN"), + Amount: mintAmount, + BurnFromAddress: creator.String(), + }, + expErr: true, + }, + "zero amount": { + burn: &bindings.BurnTokens{ + Denom: validDenomStr, + Amount: sdk.ZeroInt(), + BurnFromAddress: creator.String(), + }, + expErr: true, + }, + "negative amount": { + burn: nil, + expErr: true, + }, + "null burn": { + burn: &bindings.BurnTokens{ + Denom: validDenomStr, + Amount: mintAmount.Neg(), + BurnFromAddress: creator.String(), + }, + expErr: true, + }, + } + + for name, spec := range specs { + t.Run(name, func(t *testing.T) { + // Mint valid denom str and empty denom string for burn test + mintBinding := &bindings.MintTokens{ + Denom: validDenomStr, + Amount: mintAmount, + MintToAddress: creator.String(), + } + err := wasmbinding.PerformMint(&tokenz.TokenFactoryKeeper, &tokenz.BankKeeper, ctx, creator, mintBinding) + require.NoError(t, err) + + emptyDenomMintBinding := &bindings.MintTokens{ + Denom: emptyDenomStr, + Amount: mintAmount, + MintToAddress: creator.String(), + } + err = wasmbinding.PerformMint(&tokenz.TokenFactoryKeeper, &tokenz.BankKeeper, ctx, creator, emptyDenomMintBinding) + require.NoError(t, err) + + // when + gotErr := wasmbinding.PerformBurn(&tokenz.TokenFactoryKeeper, ctx, creator, spec.burn) + // then + if spec.expErr { + require.Error(t, gotErr) + return + } + require.NoError(t, gotErr) + }) + } +} +*/ diff --git a/x/tokenfactory/bindings/validate_queries_test.go b/x/tokenfactory/bindings/validate_queries_test.go new file mode 100644 index 0000000..9c27f82 --- /dev/null +++ b/x/tokenfactory/bindings/validate_queries_test.go @@ -0,0 +1,116 @@ +package bindings_test + +/*import ( + "fmt" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + sdk "github.com/cosmos/cosmos-sdk/types" + + wasmbinding "github.com/dymensionxyz/rollapp-wasm/x/tokenfactory/bindings" +) + +func TestFullDenom(t *testing.T) { + actor := RandomAccountAddress() + + specs := map[string]struct { + addr string + subdenom string + expFullDenom string + expErr bool + }{ + "valid address": { + addr: actor.String(), + subdenom: "subDenom1", + expFullDenom: fmt.Sprintf("factory/%s/subDenom1", actor.String()), + }, + "empty address": { + addr: "", + subdenom: "subDenom1", + expErr: true, + }, + "invalid address": { + addr: "invalid", + subdenom: "subDenom1", + expErr: true, + }, + "empty sub-denom": { + addr: actor.String(), + subdenom: "", + expFullDenom: fmt.Sprintf("factory/%s/", actor.String()), + }, + "invalid sub-denom (contains underscore)": { + addr: actor.String(), + subdenom: "sub_denom", + expErr: true, + }, + } + for name, spec := range specs { + t.Run(name, func(t *testing.T) { + // when + gotFullDenom, gotErr := wasmbinding.GetFullDenom(spec.addr, spec.subdenom) + // then + if spec.expErr { + require.Error(t, gotErr) + return + } + require.NoError(t, gotErr) + assert.Equal(t, spec.expFullDenom, gotFullDenom, "exp %s but got %s", spec.expFullDenom, gotFullDenom) + }) + } +} + +func TestDenomAdmin(t *testing.T) { + addr := RandomAccountAddress() + app, ctx := SetupCustomApp(t, addr) + + // set token creation fee to zero to make testing easier + tfParams := app.TokenFactoryKeeper.GetParams(ctx) + tfParams.DenomCreationFee = sdk.NewCoins() + app.TokenFactoryKeeper.SetParams(ctx, tfParams) + + // create a subdenom via the token factory + admin := sdk.AccAddress([]byte("addr1_______________")) + tfDenom, err := app.TokenFactoryKeeper.CreateDenom(ctx, admin.String(), "subdenom") + require.NoError(t, err) + require.NotEmpty(t, tfDenom) + + queryPlugin := wasmbinding.NewQueryPlugin(&app.BankKeeper, &app.TokenFactoryKeeper) + + testCases := []struct { + name string + denom string + expectErr bool + expectAdmin string + }{ + { + name: "valid token factory denom", + denom: tfDenom, + expectAdmin: admin.String(), + }, + { + name: "invalid token factory denom", + denom: "uosmo", + expectErr: false, + expectAdmin: "", + }, + } + + for _, tc := range testCases { + tc := tc + + t.Run(tc.name, func(t *testing.T) { + resp, err := queryPlugin.GetDenomAdmin(ctx, tc.denom) + if tc.expectErr { + require.Error(t, err) + } else { + require.NoError(t, err) + require.NotNil(t, resp) + require.Equal(t, tc.expectAdmin, resp.Admin) + } + }) + } +} +*/ diff --git a/x/tokenfactory/bindings/wasm.go b/x/tokenfactory/bindings/wasm.go new file mode 100644 index 0000000..9b41187 --- /dev/null +++ b/x/tokenfactory/bindings/wasm.go @@ -0,0 +1,29 @@ +package bindings + +import ( + "github.com/CosmWasm/wasmd/x/wasm" + + wasmkeeper "github.com/CosmWasm/wasmd/x/wasm/keeper" + + bankkeeper "github.com/cosmos/cosmos-sdk/x/bank/keeper" + tokenfactorykeeper "github.com/dymensionxyz/rollapp-wasm/x/tokenfactory/keeper" +) + +func RegisterCustomPlugins( + bank *bankkeeper.BaseKeeper, + tokenFactory *tokenfactorykeeper.Keeper, +) []wasmkeeper.Option { + wasmQueryPlugin := NewQueryPlugin(bank, tokenFactory) + + queryPluginOpt := wasmkeeper.WithQueryPlugins(&wasmkeeper.QueryPlugins{ + Custom: CustomQuerier(wasmQueryPlugin), + }) + messengerDecoratorOpt := wasmkeeper.WithMessageHandlerDecorator( + CustomMessageDecorator(bank, tokenFactory), + ) + + return []wasm.Option{ + queryPluginOpt, + messengerDecoratorOpt, + } +} diff --git a/x/tokenfactory/client/cli/query.go b/x/tokenfactory/client/cli/query.go new file mode 100644 index 0000000..bb2f55d --- /dev/null +++ b/x/tokenfactory/client/cli/query.go @@ -0,0 +1,122 @@ +package cli + +import ( + "fmt" + + // "strings" + + "github.com/spf13/cobra" + + "github.com/cosmos/cosmos-sdk/client" + "github.com/cosmos/cosmos-sdk/client/flags" + + // "github.com/cosmos/cosmos-sdk/client/flags" + // sdk "github.com/cosmos/cosmos-sdk/types" + + "github.com/dymensionxyz/rollapp-wasm/x/tokenfactory/types" +) + +// GetQueryCmd returns the cli query commands for this module +func GetQueryCmd() *cobra.Command { + // Group tokenfactory queries under a subcommand + cmd := &cobra.Command{ + Use: types.ModuleName, + Short: fmt.Sprintf("Querying commands for the %s module", types.ModuleName), + DisableFlagParsing: true, + SuggestionsMinimumDistance: 2, + RunE: client.ValidateCmd, + } + + cmd.AddCommand( + GetParams(), + GetCmdDenomAuthorityMetadata(), + GetCmdDenomsFromCreator(), + ) + + return cmd +} + +// GetParams returns the params for the module +func GetParams() *cobra.Command { + cmd := &cobra.Command{ + Use: "params [flags]", + Short: "Get the params for the x/tokenfactory module", + Args: cobra.ExactArgs(0), + RunE: func(cmd *cobra.Command, args []string) error { + clientCtx, err := client.GetClientQueryContext(cmd) + if err != nil { + return err + } + queryClient := types.NewQueryClient(clientCtx) + + res, err := queryClient.Params(cmd.Context(), &types.QueryParamsRequest{}) + if err != nil { + return err + } + + return clientCtx.PrintProto(res) + }, + } + + flags.AddQueryFlagsToCmd(cmd) + + return cmd +} + +// GetCmdDenomAuthorityMetadata returns the authority metadata for a queried denom +func GetCmdDenomAuthorityMetadata() *cobra.Command { + cmd := &cobra.Command{ + Use: "denom-authority-metadata [denom] [flags]", + Short: "Get the authority metadata for a specific denom", + Args: cobra.ExactArgs(1), + RunE: func(cmd *cobra.Command, args []string) error { + clientCtx, err := client.GetClientQueryContext(cmd) + if err != nil { + return err + } + queryClient := types.NewQueryClient(clientCtx) + + res, err := queryClient.DenomAuthorityMetadata(cmd.Context(), &types.QueryDenomAuthorityMetadataRequest{ + Denom: args[0], + }) + if err != nil { + return err + } + + return clientCtx.PrintProto(res) + }, + } + + flags.AddQueryFlagsToCmd(cmd) + + return cmd +} + +// GetCmdDenomsFromCreator a command to get a list of all tokens created by a specific creator address +func GetCmdDenomsFromCreator() *cobra.Command { + cmd := &cobra.Command{ + Use: "denoms-from-creator [creator address] [flags]", + Short: "Returns a list of all tokens created by a specific creator address", + Args: cobra.ExactArgs(1), + RunE: func(cmd *cobra.Command, args []string) error { + clientCtx, err := client.GetClientQueryContext(cmd) + if err != nil { + return err + } + queryClient := types.NewQueryClient(clientCtx) + + res, err := queryClient.DenomsFromCreator(cmd.Context(), &types.QueryDenomsFromCreatorRequest{ + Creator: args[0], + }) + if err != nil { + return err + } + + return clientCtx.PrintProto(res) + }, + } + + flags.AddQueryFlagsToCmd(cmd) + + return cmd +} diff --git a/x/tokenfactory/client/cli/tx.go b/x/tokenfactory/client/cli/tx.go new file mode 100644 index 0000000..e877232 --- /dev/null +++ b/x/tokenfactory/client/cli/tx.go @@ -0,0 +1,188 @@ +package cli + +import ( + "fmt" + + "github.com/spf13/cobra" + + "github.com/cosmos/cosmos-sdk/client" + "github.com/cosmos/cosmos-sdk/client/flags" + "github.com/cosmos/cosmos-sdk/client/tx" + sdk "github.com/cosmos/cosmos-sdk/types" + + "github.com/dymensionxyz/rollapp-wasm/x/tokenfactory/types" +) + +// GetTxCmd returns the transaction commands for this module +func GetTxCmd() *cobra.Command { + cmd := &cobra.Command{ + Use: types.ModuleName, + Short: fmt.Sprintf("%s transactions subcommands", types.ModuleName), + DisableFlagParsing: true, + SuggestionsMinimumDistance: 2, + RunE: client.ValidateCmd, + } + + cmd.AddCommand( + NewCreateDenomCmd(), + NewMintCmd(), + NewBurnCmd(), + // NewForceTransferCmd(), + NewChangeAdminCmd(), + ) + + return cmd +} + +// NewCreateDenomCmd broadcast MsgCreateDenom +func NewCreateDenomCmd() *cobra.Command { + cmd := &cobra.Command{ + Use: "create-denom [subdenom] [flags]", + Short: "create a new denom from an account", + Args: cobra.ExactArgs(1), + RunE: func(cmd *cobra.Command, args []string) error { + clientCtx, err := client.GetClientTxContext(cmd) + if err != nil { + return err + } + + txf := tx.NewFactoryCLI(clientCtx, cmd.Flags()).WithTxConfig(clientCtx.TxConfig).WithAccountRetriever(clientCtx.AccountRetriever) + + msg := types.NewMsgCreateDenom( + clientCtx.GetFromAddress().String(), + args[0], + ) + + return tx.GenerateOrBroadcastTxWithFactory(clientCtx, txf, msg) + }, + } + + flags.AddTxFlagsToCmd(cmd) + return cmd +} + +// NewMintCmd broadcast MsgMint +func NewMintCmd() *cobra.Command { + cmd := &cobra.Command{ + Use: "mint [amount] [flags]", + Short: "Mint a denom to an address. Must have admin authority to do so.", + Args: cobra.ExactArgs(1), + RunE: func(cmd *cobra.Command, args []string) error { + clientCtx, err := client.GetClientTxContext(cmd) + if err != nil { + return err + } + + txf := tx.NewFactoryCLI(clientCtx, cmd.Flags()).WithTxConfig(clientCtx.TxConfig).WithAccountRetriever(clientCtx.AccountRetriever) + + amount, err := sdk.ParseCoinNormalized(args[0]) + if err != nil { + return err + } + + msg := types.NewMsgMint( + clientCtx.GetFromAddress().String(), + amount, + ) + + return tx.GenerateOrBroadcastTxWithFactory(clientCtx, txf, msg) + }, + } + + flags.AddTxFlagsToCmd(cmd) + return cmd +} + +// NewBurnCmd broadcast MsgBurn +func NewBurnCmd() *cobra.Command { + cmd := &cobra.Command{ + Use: "burn [amount] [flags]", + Short: "Burn tokens from an address. Must have admin authority to do so.", + Args: cobra.ExactArgs(1), + RunE: func(cmd *cobra.Command, args []string) error { + clientCtx, err := client.GetClientTxContext(cmd) + if err != nil { + return err + } + + txf := tx.NewFactoryCLI(clientCtx, cmd.Flags()).WithTxConfig(clientCtx.TxConfig).WithAccountRetriever(clientCtx.AccountRetriever) + + amount, err := sdk.ParseCoinNormalized(args[0]) + if err != nil { + return err + } + + msg := types.NewMsgBurn( + clientCtx.GetFromAddress().String(), + amount, + ) + + return tx.GenerateOrBroadcastTxWithFactory(clientCtx, txf, msg) + }, + } + + flags.AddTxFlagsToCmd(cmd) + return cmd +} + +// // NewForceTransferCmd broadcast MsgForceTransfer +// func NewForceTransferCmd() *cobra.Command { +// cmd := &cobra.Command{ +// Use: "force-transfer [amount] [transfer-from-address] [transfer-to-address] [flags]", +// Short: "Force transfer tokens from one address to another address. Must have admin authority to do so.", +// Args: cobra.ExactArgs(3), +// RunE: func(cmd *cobra.Command, args []string) error { +// clientCtx, err := client.GetClientTxContext(cmd) +// if err != nil { +// return err +// } + +// txf := tx.NewFactoryCLI(clientCtx, cmd.Flags()).WithTxConfig(clientCtx.TxConfig).WithAccountRetriever(clientCtx.AccountRetriever) + +// amount, err := sdk.ParseCoinNormalized(args[0]) +// if err != nil { +// return err +// } + +// msg := types.NewMsgForceTransfer( +// clientCtx.GetFromAddress().String(), +// amount, +// args[1], +// args[2], +// ) + +// return tx.GenerateOrBroadcastTxWithFactory(clientCtx, txf, msg) +// }, +// } + +// flags.AddTxFlagsToCmd(cmd) +// return cmd +// } + +// NewChangeAdminCmd broadcast MsgChangeAdmin +func NewChangeAdminCmd() *cobra.Command { + cmd := &cobra.Command{ + Use: "change-admin [denom] [new-admin-address] [flags]", + Short: "Changes the admin address for a factory-created denom. Must have admin authority to do so.", + Args: cobra.ExactArgs(2), + RunE: func(cmd *cobra.Command, args []string) error { + clientCtx, err := client.GetClientTxContext(cmd) + if err != nil { + return err + } + + txf := tx.NewFactoryCLI(clientCtx, cmd.Flags()).WithTxConfig(clientCtx.TxConfig).WithAccountRetriever(clientCtx.AccountRetriever) + + msg := types.NewMsgChangeAdmin( + clientCtx.GetFromAddress().String(), + args[0], + args[1], + ) + + return tx.GenerateOrBroadcastTxWithFactory(clientCtx, txf, msg) + }, + } + + flags.AddTxFlagsToCmd(cmd) + return cmd +} diff --git a/x/tokenfactory/keeper/admins.go b/x/tokenfactory/keeper/admins.go new file mode 100644 index 0000000..d6e9ab7 --- /dev/null +++ b/x/tokenfactory/keeper/admins.go @@ -0,0 +1,49 @@ +package keeper + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/gogo/protobuf/proto" + + "github.com/dymensionxyz/rollapp-wasm/x/tokenfactory/types" +) + +// GetAuthorityMetadata returns the authority metadata for a specific denom +func (k Keeper) GetAuthorityMetadata(ctx sdk.Context, denom string) (types.DenomAuthorityMetadata, error) { + bz := k.GetDenomPrefixStore(ctx, denom).Get([]byte(types.DenomAuthorityMetadataKey)) + + metadata := types.DenomAuthorityMetadata{} + err := proto.Unmarshal(bz, &metadata) + if err != nil { + return types.DenomAuthorityMetadata{}, err + } + return metadata, nil +} + +// setAuthorityMetadata stores authority metadata for a specific denom +func (k Keeper) setAuthorityMetadata(ctx sdk.Context, denom string, metadata types.DenomAuthorityMetadata) error { + err := metadata.Validate() + if err != nil { + return err + } + + store := k.GetDenomPrefixStore(ctx, denom) + + bz, err := proto.Marshal(&metadata) + if err != nil { + return err + } + + store.Set([]byte(types.DenomAuthorityMetadataKey), bz) + return nil +} + +func (k Keeper) setAdmin(ctx sdk.Context, denom string, admin string) error { + metadata, err := k.GetAuthorityMetadata(ctx, denom) + if err != nil { + return err + } + + metadata.Admin = admin + + return k.setAuthorityMetadata(ctx, denom, metadata) +} diff --git a/x/tokenfactory/keeper/admins_test.go b/x/tokenfactory/keeper/admins_test.go new file mode 100644 index 0000000..ee6aef4 --- /dev/null +++ b/x/tokenfactory/keeper/admins_test.go @@ -0,0 +1,403 @@ +package keeper_test + +import ( + "fmt" + + sdk "github.com/cosmos/cosmos-sdk/types" + banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" + + "github.com/dymensionxyz/rollapp-wasm/x/tokenfactory/types" +) + +func (suite *KeeperTestSuite) TestAdminMsgs() { + addr0bal := int64(0) + addr1bal := int64(0) + + bankKeeper := suite.App.BankKeeper + + suite.CreateDefaultDenom() + // Make sure that the admin is set correctly + queryRes, err := suite.queryClient.DenomAuthorityMetadata(suite.Ctx.Context(), &types.QueryDenomAuthorityMetadataRequest{ + Denom: suite.defaultDenom, + }) + suite.Require().NoError(err) + suite.Require().Equal(suite.TestAccs[0].String(), queryRes.AuthorityMetadata.Admin) + + // Test minting to admins own account + _, err = suite.msgServer.Mint(sdk.WrapSDKContext(suite.Ctx), types.NewMsgMint(suite.TestAccs[0].String(), sdk.NewInt64Coin(suite.defaultDenom, 10))) + addr0bal += 10 + suite.Require().NoError(err) + suite.Require().True(bankKeeper.GetBalance(suite.Ctx, suite.TestAccs[0], suite.defaultDenom).Amount.Int64() == addr0bal, bankKeeper.GetBalance(suite.Ctx, suite.TestAccs[0], suite.defaultDenom)) + + // // Test force transferring + // _, err = suite.msgServer.ForceTransfer(sdk.WrapSDKContext(suite.Ctx), types.NewMsgForceTransfer(suite.TestAccs[0].String(), sdk.NewInt64Coin(denom, 5), suite.TestAccs[1].String(), suite.TestAccs[0].String())) + // suite.Require().NoError(err) + // suite.Require().True(bankKeeper.GetBalance(suite.Ctx, suite.TestAccs[0], denom).IsEqual(sdk.NewInt64Coin(denom, 15))) + // suite.Require().True(bankKeeper.GetBalance(suite.Ctx, suite.TestAccs[1], denom).IsEqual(sdk.NewInt64Coin(denom, 5))) + + // Test burning from own account + _, err = suite.msgServer.Burn(sdk.WrapSDKContext(suite.Ctx), types.NewMsgBurn(suite.TestAccs[0].String(), sdk.NewInt64Coin(suite.defaultDenom, 5))) + suite.Require().NoError(err) + suite.Require().True(bankKeeper.GetBalance(suite.Ctx, suite.TestAccs[1], suite.defaultDenom).Amount.Int64() == addr1bal) + + // Test Change Admin + _, err = suite.msgServer.ChangeAdmin(sdk.WrapSDKContext(suite.Ctx), types.NewMsgChangeAdmin(suite.TestAccs[0].String(), suite.defaultDenom, suite.TestAccs[1].String())) + suite.Require().NoError(err) + queryRes, err = suite.queryClient.DenomAuthorityMetadata(suite.Ctx.Context(), &types.QueryDenomAuthorityMetadataRequest{ + Denom: suite.defaultDenom, + }) + suite.Require().NoError(err) + suite.Require().Equal(suite.TestAccs[1].String(), queryRes.AuthorityMetadata.Admin) + + // Make sure old admin can no longer do actions + _, err = suite.msgServer.Burn(sdk.WrapSDKContext(suite.Ctx), types.NewMsgBurn(suite.TestAccs[0].String(), sdk.NewInt64Coin(suite.defaultDenom, 5))) + suite.Require().Error(err) + + // Make sure the new admin works + _, err = suite.msgServer.Mint(sdk.WrapSDKContext(suite.Ctx), types.NewMsgMint(suite.TestAccs[1].String(), sdk.NewInt64Coin(suite.defaultDenom, 5))) + addr1bal += 5 + suite.Require().NoError(err) + suite.Require().True(bankKeeper.GetBalance(suite.Ctx, suite.TestAccs[1], suite.defaultDenom).Amount.Int64() == addr1bal) + + // Try setting admin to empty + _, err = suite.msgServer.ChangeAdmin(sdk.WrapSDKContext(suite.Ctx), types.NewMsgChangeAdmin(suite.TestAccs[1].String(), suite.defaultDenom, "")) + suite.Require().NoError(err) + queryRes, err = suite.queryClient.DenomAuthorityMetadata(suite.Ctx.Context(), &types.QueryDenomAuthorityMetadataRequest{ + Denom: suite.defaultDenom, + }) + suite.Require().NoError(err) + suite.Require().Equal("", queryRes.AuthorityMetadata.Admin) +} + +// TestMintDenom ensures the following properties of the MintMessage: +// * Noone can mint tokens for a denom that doesn't exist +// * Only the admin of a denom can mint tokens for it +// * The admin of a denom can mint tokens for it +func (suite *KeeperTestSuite) TestMintDenom() { + var addr0bal int64 + + // Create a denom + suite.CreateDefaultDenom() + + for _, tc := range []struct { + desc string + amount int64 + mintDenom string + admin string + valid bool + }{ + { + desc: "denom does not exist", + amount: 10, + mintDenom: "factory/osmo1t7egva48prqmzl59x5ngv4zx0dtrwewc9m7z44/evmos", + admin: suite.TestAccs[0].String(), + valid: false, + }, + { + desc: "mint is not by the admin", + amount: 10, + mintDenom: suite.defaultDenom, + admin: suite.TestAccs[1].String(), + valid: false, + }, + { + desc: "success case", + amount: 10, + mintDenom: suite.defaultDenom, + admin: suite.TestAccs[0].String(), + valid: true, + }, + } { + suite.Run(fmt.Sprintf("Case %s", tc.desc), func() { + // Test minting to admins own account + bankKeeper := suite.App.BankKeeper + _, err := suite.msgServer.Mint(sdk.WrapSDKContext(suite.Ctx), types.NewMsgMint(tc.admin, sdk.NewInt64Coin(tc.mintDenom, 10))) + if tc.valid { + addr0bal += 10 + suite.Require().NoError(err) + suite.Require().Equal(bankKeeper.GetBalance(suite.Ctx, suite.TestAccs[0], suite.defaultDenom).Amount.Int64(), addr0bal, bankKeeper.GetBalance(suite.Ctx, suite.TestAccs[0], suite.defaultDenom)) + } else { + suite.Require().Error(err) + } + }) + } +} + +func (suite *KeeperTestSuite) TestBurnDenom() { + var addr0bal int64 + + // Create a denom. + suite.CreateDefaultDenom() + + // mint 10 default token for testAcc[0] + suite.msgServer.Mint(sdk.WrapSDKContext(suite.Ctx), types.NewMsgMint(suite.TestAccs[0].String(), sdk.NewInt64Coin(suite.defaultDenom, 10))) //nolint:errcheck + addr0bal += 10 + + for _, tc := range []struct { + desc string + amount int64 + burnDenom string + admin string + valid bool + }{ + { + desc: "denom does not exist", + amount: 10, + burnDenom: "factory/osmo1t7egva48prqmzl59x5ngv4zx0dtrwewc9m7z44/evmos", + admin: suite.TestAccs[0].String(), + valid: false, + }, + { + desc: "burn is not by the admin", + amount: 10, + burnDenom: suite.defaultDenom, + admin: suite.TestAccs[1].String(), + valid: false, + }, + { + desc: "burn amount is bigger than minted amount", + amount: 1000, + burnDenom: suite.defaultDenom, + admin: suite.TestAccs[1].String(), + valid: false, + }, + { + desc: "success case", + amount: 10, + burnDenom: suite.defaultDenom, + admin: suite.TestAccs[0].String(), + valid: true, + }, + } { + suite.Run(fmt.Sprintf("Case %s", tc.desc), func() { + // Test minting to admins own account + bankKeeper := suite.App.BankKeeper + _, err := suite.msgServer.Burn(sdk.WrapSDKContext(suite.Ctx), types.NewMsgBurn(tc.admin, sdk.NewInt64Coin(tc.burnDenom, 10))) + if tc.valid { + addr0bal -= 10 + suite.Require().NoError(err) + suite.Require().True(bankKeeper.GetBalance(suite.Ctx, suite.TestAccs[0], suite.defaultDenom).Amount.Int64() == addr0bal, bankKeeper.GetBalance(suite.Ctx, suite.TestAccs[0], suite.defaultDenom)) + } else { + suite.Require().Error(err) + suite.Require().True(bankKeeper.GetBalance(suite.Ctx, suite.TestAccs[0], suite.defaultDenom).Amount.Int64() == addr0bal, bankKeeper.GetBalance(suite.Ctx, suite.TestAccs[0], suite.defaultDenom)) + } + }) + } +} + +func (suite *KeeperTestSuite) TestChangeAdminDenom() { + for _, tc := range []struct { + desc string + msgChangeAdmin func(denom string) *types.MsgChangeAdmin + expectedChangeAdminPass bool + expectedAdminIndex int + msgMint func(denom string) *types.MsgMint + expectedMintPass bool + }{ + { + desc: "creator admin can't mint after setting to '' ", + msgChangeAdmin: func(denom string) *types.MsgChangeAdmin { + return types.NewMsgChangeAdmin(suite.TestAccs[0].String(), denom, "") + }, + expectedChangeAdminPass: true, + expectedAdminIndex: -1, + msgMint: func(denom string) *types.MsgMint { + return types.NewMsgMint(suite.TestAccs[0].String(), sdk.NewInt64Coin(denom, 5)) + }, + expectedMintPass: false, + }, + { + desc: "non-admins can't change the existing admin", + msgChangeAdmin: func(denom string) *types.MsgChangeAdmin { + return types.NewMsgChangeAdmin(suite.TestAccs[1].String(), denom, suite.TestAccs[2].String()) + }, + expectedChangeAdminPass: false, + expectedAdminIndex: 0, + }, + { + desc: "success change admin", + msgChangeAdmin: func(denom string) *types.MsgChangeAdmin { + return types.NewMsgChangeAdmin(suite.TestAccs[0].String(), denom, suite.TestAccs[1].String()) + }, + expectedAdminIndex: 1, + expectedChangeAdminPass: true, + msgMint: func(denom string) *types.MsgMint { + return types.NewMsgMint(suite.TestAccs[1].String(), sdk.NewInt64Coin(denom, 5)) + }, + expectedMintPass: true, + }, + } { + suite.Run(fmt.Sprintf("Case %s", tc.desc), func() { + // setup test + suite.SetupTest() + + // Create a denom and mint + res, err := suite.msgServer.CreateDenom(sdk.WrapSDKContext(suite.Ctx), types.NewMsgCreateDenom(suite.TestAccs[0].String(), "bitcoin")) + suite.Require().NoError(err) + + testDenom := res.GetNewTokenDenom() + + _, err = suite.msgServer.Mint(sdk.WrapSDKContext(suite.Ctx), types.NewMsgMint(suite.TestAccs[0].String(), sdk.NewInt64Coin(testDenom, 10))) + suite.Require().NoError(err) + + _, err = suite.msgServer.ChangeAdmin(sdk.WrapSDKContext(suite.Ctx), tc.msgChangeAdmin(testDenom)) + if tc.expectedChangeAdminPass { + suite.Require().NoError(err) + } else { + suite.Require().Error(err) + } + + queryRes, err := suite.queryClient.DenomAuthorityMetadata(suite.Ctx.Context(), &types.QueryDenomAuthorityMetadataRequest{ + Denom: testDenom, + }) + suite.Require().NoError(err) + + // expectedAdminIndex with negative value is assumed as admin with value of "" + const emptyStringAdminIndexFlag = -1 + if tc.expectedAdminIndex == emptyStringAdminIndexFlag { + suite.Require().Equal("", queryRes.AuthorityMetadata.Admin) + } else { + suite.Require().Equal(suite.TestAccs[tc.expectedAdminIndex].String(), queryRes.AuthorityMetadata.Admin) + } + + // we test mint to test if admin authority is performed properly after admin change. + if tc.msgMint != nil { + _, err := suite.msgServer.Mint(sdk.WrapSDKContext(suite.Ctx), tc.msgMint(testDenom)) + if tc.expectedMintPass { + suite.Require().NoError(err) + } else { + suite.Require().Error(err) + } + } + }) + } +} + +func (suite *KeeperTestSuite) TestSetDenomMetaData() { + // setup test + suite.SetupTest() + suite.CreateDefaultDenom() + + for _, tc := range []struct { + desc string + msgSetDenomMetadata types.MsgSetDenomMetadata + expectedPass bool + }{ + { + desc: "successful set denom metadata", + msgSetDenomMetadata: *types.NewMsgSetDenomMetadata(suite.TestAccs[0].String(), banktypes.Metadata{ + Description: "yeehaw", + DenomUnits: []*banktypes.DenomUnit{ + { + Denom: suite.defaultDenom, + Exponent: 0, + }, + { + Denom: "uosmo", + Exponent: 6, + }, + }, + Base: suite.defaultDenom, + Display: "uosmo", + Name: "OSMO", + Symbol: "OSMO", + }), + expectedPass: true, + }, + { + desc: "non existent factory denom name", + msgSetDenomMetadata: *types.NewMsgSetDenomMetadata(suite.TestAccs[0].String(), banktypes.Metadata{ + Description: "yeehaw", + DenomUnits: []*banktypes.DenomUnit{ + { + Denom: fmt.Sprintf("factory/%s/litecoin", suite.TestAccs[0].String()), + Exponent: 0, + }, + { + Denom: "uosmo", + Exponent: 6, + }, + }, + Base: fmt.Sprintf("factory/%s/litecoin", suite.TestAccs[0].String()), + Display: "uosmo", + Name: "OSMO", + Symbol: "OSMO", + }), + expectedPass: false, + }, + { + desc: "non-factory denom", + msgSetDenomMetadata: *types.NewMsgSetDenomMetadata(suite.TestAccs[0].String(), banktypes.Metadata{ + Description: "yeehaw", + DenomUnits: []*banktypes.DenomUnit{ + { + Denom: "uosmo", + Exponent: 0, + }, + { + Denom: "uosmoo", + Exponent: 6, + }, + }, + Base: "uosmo", + Display: "uosmoo", + Name: "OSMO", + Symbol: "OSMO", + }), + expectedPass: false, + }, + { + desc: "wrong admin", + msgSetDenomMetadata: *types.NewMsgSetDenomMetadata(suite.TestAccs[1].String(), banktypes.Metadata{ + Description: "yeehaw", + DenomUnits: []*banktypes.DenomUnit{ + { + Denom: suite.defaultDenom, + Exponent: 0, + }, + { + Denom: "uosmo", + Exponent: 6, + }, + }, + Base: suite.defaultDenom, + Display: "uosmo", + Name: "OSMO", + Symbol: "OSMO", + }), + expectedPass: false, + }, + { + desc: "invalid metadata (missing display denom unit)", + msgSetDenomMetadata: *types.NewMsgSetDenomMetadata(suite.TestAccs[0].String(), banktypes.Metadata{ + Description: "yeehaw", + DenomUnits: []*banktypes.DenomUnit{ + { + Denom: suite.defaultDenom, + Exponent: 0, + }, + }, + Base: suite.defaultDenom, + Display: "uosmo", + Name: "OSMO", + Symbol: "OSMO", + }), + expectedPass: false, + }, + } { + suite.Run(fmt.Sprintf("Case %s", tc.desc), func() { + bankKeeper := suite.App.BankKeeper + res, err := suite.msgServer.SetDenomMetadata(sdk.WrapSDKContext(suite.Ctx), &tc.msgSetDenomMetadata) + if tc.expectedPass { + suite.Require().NoError(err) + suite.Require().NotNil(res) + + md, found := bankKeeper.GetDenomMetaData(suite.Ctx, suite.defaultDenom) + suite.Require().True(found) + suite.Require().Equal(tc.msgSetDenomMetadata.Metadata.Name, md.Name) + } else { + suite.Require().Error(err) + } + }) + } +} diff --git a/x/tokenfactory/keeper/bankactions.go b/x/tokenfactory/keeper/bankactions.go new file mode 100644 index 0000000..352f51b --- /dev/null +++ b/x/tokenfactory/keeper/bankactions.go @@ -0,0 +1,40 @@ +package keeper + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" + + "github.com/dymensionxyz/rollapp-wasm/x/tokenfactory/types" +) + +func (k Keeper) mintTo(ctx sdk.Context, amount sdk.Coin, mintTo string) error { + err := k.bankKeeper.MintCoins(ctx, types.ModuleName, sdk.NewCoins(amount)) + if err != nil { + return err + } + + addr, err := sdk.AccAddressFromBech32(mintTo) + if err != nil { + return err + } + + return k.bankKeeper.SendCoinsFromModuleToAccount(ctx, types.ModuleName, + addr, + sdk.NewCoins(amount)) +} + +func (k Keeper) burnFrom(ctx sdk.Context, amount sdk.Coin, burnFrom string) error { + addr, err := sdk.AccAddressFromBech32(burnFrom) + if err != nil { + return err + } + + err = k.bankKeeper.SendCoinsFromAccountToModule(ctx, + addr, + types.ModuleName, + sdk.NewCoins(amount)) + if err != nil { + return err + } + + return k.bankKeeper.BurnCoins(ctx, types.ModuleName, sdk.NewCoins(amount)) +} diff --git a/x/tokenfactory/keeper/createdenom.go b/x/tokenfactory/keeper/createdenom.go new file mode 100644 index 0000000..e4f2e3c --- /dev/null +++ b/x/tokenfactory/keeper/createdenom.go @@ -0,0 +1,86 @@ +package keeper + +import ( + "fmt" + + sdk "github.com/cosmos/cosmos-sdk/types" + banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" + + "github.com/dymensionxyz/rollapp-wasm/x/tokenfactory/types" +) + +// ConvertToBaseToken converts a fee amount in a whitelisted fee token to the base fee token amount +func (k Keeper) CreateDenom(ctx sdk.Context, creatorAddr string, subdenom string) (newTokenDenom string, err error) { + denom, err := k.validateCreateDenom(ctx, creatorAddr, subdenom) + if err != nil { + return "", err + } + + err = k.chargeForCreateDenom(ctx, creatorAddr, subdenom) + if err != nil { + return "", err + } + + err = k.createDenomAfterValidation(ctx, creatorAddr, denom) + return denom, err +} + +// Runs CreateDenom logic after the charge and all denom validation has been handled. +// Made into a second function for genesis initialization. +func (k Keeper) createDenomAfterValidation(ctx sdk.Context, creatorAddr string, denom string) (err error) { + denomMetaData := banktypes.Metadata{ + DenomUnits: []*banktypes.DenomUnit{{ + Denom: denom, + Exponent: 0, + }}, + Base: denom, + } + + k.bankKeeper.SetDenomMetaData(ctx, denomMetaData) + + authorityMetadata := types.DenomAuthorityMetadata{ + Admin: creatorAddr, + } + err = k.setAuthorityMetadata(ctx, denom, authorityMetadata) + if err != nil { + return err + } + + k.addDenomFromCreator(ctx, creatorAddr, denom) + return nil +} + +func (k Keeper) validateCreateDenom(ctx sdk.Context, creatorAddr string, subdenom string) (newTokenDenom string, err error) { + // Temporary check until IBC bug is sorted out + if k.bankKeeper.HasSupply(ctx, subdenom) { + return "", fmt.Errorf("temporary error until IBC bug is sorted out, " + + "can't create subdenoms that are the same as a native denom") + } + + denom, err := types.GetTokenDenom(creatorAddr, subdenom) + if err != nil { + return "", err + } + + _, found := k.bankKeeper.GetDenomMetaData(ctx, denom) + if found { + return "", types.ErrDenomExists + } + + return denom, nil +} + +func (k Keeper) chargeForCreateDenom(ctx sdk.Context, creatorAddr string, subdenom string) (err error) { + // Send creation fee to community pool + creationFee := k.GetParams(ctx).DenomCreationFee + accAddr, err := sdk.AccAddressFromBech32(creatorAddr) + if err != nil { + return err + } + if creationFee != nil { + if err := k.communityPoolKeeper.FundCommunityPool(ctx, creationFee, accAddr); err != nil { + return err + } + } + return nil +} diff --git a/x/tokenfactory/keeper/createdenom_test.go b/x/tokenfactory/keeper/createdenom_test.go new file mode 100644 index 0000000..3d5aabb --- /dev/null +++ b/x/tokenfactory/keeper/createdenom_test.go @@ -0,0 +1,163 @@ +package keeper_test + +import ( + "fmt" + + sdk "github.com/cosmos/cosmos-sdk/types" + + "github.com/dymensionxyz/rollapp-wasm/x/tokenfactory/testhelpers" + "github.com/dymensionxyz/rollapp-wasm/x/tokenfactory/types" +) + +func (suite *KeeperTestSuite) TestMsgCreateDenom() { + var ( + tokenFactoryKeeper = suite.App.TokenFactoryKeeper + bankKeeper = suite.App.BankKeeper + denomCreationFee = tokenFactoryKeeper.GetParams(suite.Ctx).DenomCreationFee + ) + + // Get balance of acc 0 before creating a denom + preCreateBalance := bankKeeper.GetBalance(suite.Ctx, suite.TestAccs[0], denomCreationFee[0].Denom) + + // Creating a denom should work + res, err := suite.msgServer.CreateDenom(sdk.WrapSDKContext(suite.Ctx), types.NewMsgCreateDenom(suite.TestAccs[0].String(), "bitcoin")) + suite.Require().NoError(err) + suite.Require().NotEmpty(res.GetNewTokenDenom()) + + // Make sure that the admin is set correctly + queryRes, err := suite.queryClient.DenomAuthorityMetadata(suite.Ctx.Context(), &types.QueryDenomAuthorityMetadataRequest{ + Denom: res.GetNewTokenDenom(), + }) + suite.Require().NoError(err) + suite.Require().Equal(suite.TestAccs[0].String(), queryRes.AuthorityMetadata.Admin) + + // Make sure that creation fee was deducted + postCreateBalance := bankKeeper.GetBalance(suite.Ctx, suite.TestAccs[0], tokenFactoryKeeper.GetParams(suite.Ctx).DenomCreationFee[0].Denom) + suite.Require().True(preCreateBalance.Sub(postCreateBalance).IsEqual(denomCreationFee[0])) + + // Make sure that a second version of the same denom can't be recreated + _, err = suite.msgServer.CreateDenom(sdk.WrapSDKContext(suite.Ctx), types.NewMsgCreateDenom(suite.TestAccs[0].String(), "bitcoin")) + suite.Require().Error(err) + + // Creating a second denom should work + res, err = suite.msgServer.CreateDenom(sdk.WrapSDKContext(suite.Ctx), types.NewMsgCreateDenom(suite.TestAccs[0].String(), "litecoin")) + suite.Require().NoError(err) + suite.Require().NotEmpty(res.GetNewTokenDenom()) + + // Try querying all the denoms created by suite.TestAccs[0] + queryRes2, err := suite.queryClient.DenomsFromCreator(suite.Ctx.Context(), &types.QueryDenomsFromCreatorRequest{ + Creator: suite.TestAccs[0].String(), + }) + suite.Require().NoError(err) + suite.Require().Len(queryRes2.Denoms, 2) + + // Make sure that a second account can create a denom with the same subdenom + res, err = suite.msgServer.CreateDenom(sdk.WrapSDKContext(suite.Ctx), types.NewMsgCreateDenom(suite.TestAccs[1].String(), "bitcoin")) + suite.Require().NoError(err) + suite.Require().NotEmpty(res.GetNewTokenDenom()) + + // Make sure that an address with a "/" in it can't create denoms + _, err = suite.msgServer.CreateDenom(sdk.WrapSDKContext(suite.Ctx), types.NewMsgCreateDenom("osmosis.eth/creator", "bitcoin")) + suite.Require().Error(err) +} + +func (suite *KeeperTestSuite) TestCreateDenom() { + var ( + primaryDenom = types.DefaultParams().DenomCreationFee[0].Denom + secondaryDenom = testhelpers.SecondaryDenom + defaultDenomCreationFee = types.Params{DenomCreationFee: sdk.NewCoins(sdk.NewCoin(primaryDenom, sdk.NewInt(50000000)))} + twoDenomCreationFee = types.Params{DenomCreationFee: sdk.NewCoins(sdk.NewCoin(primaryDenom, sdk.NewInt(50000000)), sdk.NewCoin(secondaryDenom, sdk.NewInt(50000000)))} + nilCreationFee = types.Params{DenomCreationFee: nil} + largeCreationFee = types.Params{DenomCreationFee: sdk.NewCoins(sdk.NewCoin(primaryDenom, sdk.NewInt(5000000000)))} + ) + + for _, tc := range []struct { + desc string + denomCreationFee types.Params + setup func() + subdenom string + valid bool + }{ + { + desc: "subdenom too long", + denomCreationFee: defaultDenomCreationFee, + subdenom: "assadsadsadasdasdsadsadsadsadsadsadsklkadaskkkdasdasedskhanhassyeunganassfnlksdflksafjlkasd", + valid: false, + }, + { + desc: "subdenom and creator pair already exists", + denomCreationFee: defaultDenomCreationFee, + setup: func() { + _, err := suite.msgServer.CreateDenom(sdk.WrapSDKContext(suite.Ctx), types.NewMsgCreateDenom(suite.TestAccs[0].String(), "bitcoin")) + suite.Require().NoError(err) + }, + subdenom: "bitcoin", + valid: false, + }, + { + desc: "success case: defaultDenomCreationFee", + denomCreationFee: defaultDenomCreationFee, + subdenom: "evmos", + valid: true, + }, + { + desc: "success case: twoDenomCreationFee", + denomCreationFee: twoDenomCreationFee, + subdenom: "catcoin", + valid: true, + }, + { + desc: "success case: nilCreationFee", + denomCreationFee: nilCreationFee, + subdenom: "czcoin", + valid: true, + }, + { + desc: "account doesn't have enough to pay for denom creation fee", + denomCreationFee: largeCreationFee, + subdenom: "tooexpensive", + valid: false, + }, + { + desc: "subdenom having invalid characters", + denomCreationFee: defaultDenomCreationFee, + subdenom: "bit/***/ //&&&/coin", + valid: false, + }, + } { + suite.SetupTest() + suite.Run(fmt.Sprintf("Case %s", tc.desc), func() { + if tc.setup != nil { + tc.setup() + } + tokenFactoryKeeper := suite.App.TokenFactoryKeeper + bankKeeper := suite.App.BankKeeper + // Set denom creation fee in params + tokenFactoryKeeper.SetParams(suite.Ctx, tc.denomCreationFee) + denomCreationFee := tokenFactoryKeeper.GetParams(suite.Ctx).DenomCreationFee + suite.Require().Equal(tc.denomCreationFee.DenomCreationFee, denomCreationFee) + + // note balance, create a tokenfactory denom, then note balance again + preCreateBalance := bankKeeper.GetAllBalances(suite.Ctx, suite.TestAccs[0]) + res, err := suite.msgServer.CreateDenom(sdk.WrapSDKContext(suite.Ctx), types.NewMsgCreateDenom(suite.TestAccs[0].String(), tc.subdenom)) + postCreateBalance := bankKeeper.GetAllBalances(suite.Ctx, suite.TestAccs[0]) + if tc.valid { + suite.Require().NoError(err) + suite.Require().True(preCreateBalance.Sub(postCreateBalance...).IsEqual(denomCreationFee)) + + // Make sure that the admin is set correctly + queryRes, err := suite.queryClient.DenomAuthorityMetadata(suite.Ctx.Context(), &types.QueryDenomAuthorityMetadataRequest{ + Denom: res.GetNewTokenDenom(), + }) + + suite.Require().NoError(err) + suite.Require().Equal(suite.TestAccs[0].String(), queryRes.AuthorityMetadata.Admin) + + } else { + suite.Require().Error(err) + // Ensure we don't charge if we expect an error + suite.Require().True(preCreateBalance.IsEqual(postCreateBalance)) + } + }) + } +} diff --git a/x/tokenfactory/keeper/creators.go b/x/tokenfactory/keeper/creators.go new file mode 100644 index 0000000..570d54b --- /dev/null +++ b/x/tokenfactory/keeper/creators.go @@ -0,0 +1,27 @@ +package keeper + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" +) + +func (k Keeper) addDenomFromCreator(ctx sdk.Context, creator, denom string) { + store := k.GetCreatorPrefixStore(ctx, creator) + store.Set([]byte(denom), []byte(denom)) +} + +func (k Keeper) GetDenomsFromCreator(ctx sdk.Context, creator string) []string { + store := k.GetCreatorPrefixStore(ctx, creator) + + iterator := store.Iterator(nil, nil) + defer iterator.Close() + + denoms := []string{} + for ; iterator.Valid(); iterator.Next() { + denoms = append(denoms, string(iterator.Key())) + } + return denoms +} + +func (k Keeper) GetAllDenomsIterator(ctx sdk.Context) sdk.Iterator { + return k.GetCreatorsPrefixStore(ctx).Iterator(nil, nil) +} diff --git a/x/tokenfactory/keeper/genesis.go b/x/tokenfactory/keeper/genesis.go new file mode 100644 index 0000000..583e8ec --- /dev/null +++ b/x/tokenfactory/keeper/genesis.go @@ -0,0 +1,60 @@ +package keeper + +import ( + "fmt" + "strings" + + sdk "github.com/cosmos/cosmos-sdk/types" + + "github.com/dymensionxyz/rollapp-wasm/x/tokenfactory/types" +) + +// InitGenesis initializes the tokenfactory module's state from a provided genesis +// state. +func (k Keeper) InitGenesis(ctx sdk.Context, genState types.GenesisState) { + k.CreateModuleAccount(ctx) + + if genState.Params.DenomCreationFee == nil { + genState.Params.DenomCreationFee = sdk.NewCoins() + } + k.SetParams(ctx, genState.Params) + + for _, genDenom := range genState.GetFactoryDenoms() { + if strings.HasPrefix(genDenom.Denom, "ibc") { + panic(fmt.Errorf("IBC denoms are not allowed in denom metadata")) + } + err := k.createDenomAfterValidation(ctx, genDenom.AuthorityMetadata.Admin, genDenom.GetDenom()) + if err != nil { + panic(err) + } + err = k.setAuthorityMetadata(ctx, genDenom.GetDenom(), genDenom.GetAuthorityMetadata()) + if err != nil { + panic(err) + } + } +} + +// ExportGenesis returns the tokenfactory module's exported genesis. +func (k Keeper) ExportGenesis(ctx sdk.Context) *types.GenesisState { + var genDenoms []types.GenesisDenom + iterator := k.GetAllDenomsIterator(ctx) + defer iterator.Close() + for ; iterator.Valid(); iterator.Next() { + denom := string(iterator.Value()) + + authorityMetadata, err := k.GetAuthorityMetadata(ctx, denom) + if err != nil { + panic(err) + } + + genDenoms = append(genDenoms, types.GenesisDenom{ + Denom: denom, + AuthorityMetadata: authorityMetadata, + }) + } + + return &types.GenesisState{ + FactoryDenoms: genDenoms, + Params: k.GetParams(ctx), + } +} diff --git a/x/tokenfactory/keeper/genesis_test.go b/x/tokenfactory/keeper/genesis_test.go new file mode 100644 index 0000000..bb5d7e2 --- /dev/null +++ b/x/tokenfactory/keeper/genesis_test.go @@ -0,0 +1,62 @@ +package keeper_test + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" + banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" + + "github.com/dymensionxyz/rollapp-wasm/x/tokenfactory/types" +) + +func (suite *KeeperTestSuite) TestGenesis() { + genesisState := types.GenesisState{ + FactoryDenoms: []types.GenesisDenom{ + { + Denom: "factory/cosmos1t7egva48prqmzl59x5ngv4zx0dtrwewcdqdjr8/diff-admin", + AuthorityMetadata: types.DenomAuthorityMetadata{ + Admin: "cosmos15czt5nhlnvayqq37xun9s9yus0d6y26dx74r5p", + }, + }, + { + Denom: "factory/cosmos1t7egva48prqmzl59x5ngv4zx0dtrwewcdqdjr8/bitcoin", + AuthorityMetadata: types.DenomAuthorityMetadata{ + Admin: "cosmos1t7egva48prqmzl59x5ngv4zx0dtrwewcdqdjr8", + }, + }, + { + Denom: "factory/cosmos1t7egva48prqmzl59x5ngv4zx0dtrwewcdqdjr8/litecoin", + AuthorityMetadata: types.DenomAuthorityMetadata{ + Admin: "cosmos1t7egva48prqmzl59x5ngv4zx0dtrwewcdqdjr8", + }, + }, + }, + } + + suite.SetupTestForInitGenesis() + app := suite.App + + // Test both with bank denom metadata set, and not set. + for i, denom := range genesisState.FactoryDenoms { + // hacky, sets bank metadata to exist if i != 0, to cover both cases. + if i != 0 { + app.BankKeeper.SetDenomMetaData(suite.Ctx, banktypes.Metadata{Base: denom.GetDenom()}) + } + } + + // check before initGenesis that the module account is nil + tokenfactoryModuleAccount := app.AccountKeeper.GetAccount(suite.Ctx, app.AccountKeeper.GetModuleAddress(types.ModuleName)) + suite.Require().Nil(tokenfactoryModuleAccount) + + app.TokenFactoryKeeper.SetParams(suite.Ctx, types.Params{DenomCreationFee: sdk.Coins{sdk.NewInt64Coin("uosmo", 100)}}) + app.TokenFactoryKeeper.InitGenesis(suite.Ctx, genesisState) + + // check that the module account is now initialized + tokenfactoryModuleAccount = app.AccountKeeper.GetAccount(suite.Ctx, app.AccountKeeper.GetModuleAddress(types.ModuleName)) + suite.Require().NotNil(tokenfactoryModuleAccount) + + exportedGenesis := app.TokenFactoryKeeper.ExportGenesis(suite.Ctx) + suite.Require().NotNil(exportedGenesis) + suite.Require().Equal( + string(app.AppCodec().MustMarshalJSON(&genesisState)), + string(app.AppCodec().MustMarshalJSON(exportedGenesis)), + ) +} diff --git a/x/tokenfactory/keeper/grpc_query.go b/x/tokenfactory/keeper/grpc_query.go new file mode 100644 index 0000000..5ad26d6 --- /dev/null +++ b/x/tokenfactory/keeper/grpc_query.go @@ -0,0 +1,35 @@ +package keeper + +import ( + "context" + + sdk "github.com/cosmos/cosmos-sdk/types" + + "github.com/dymensionxyz/rollapp-wasm/x/tokenfactory/types" +) + +var _ types.QueryServer = Keeper{} + +func (k Keeper) Params(ctx context.Context, req *types.QueryParamsRequest) (*types.QueryParamsResponse, error) { + sdkCtx := sdk.UnwrapSDKContext(ctx) + params := k.GetParams(sdkCtx) + + return &types.QueryParamsResponse{Params: params}, nil +} + +func (k Keeper) DenomAuthorityMetadata(ctx context.Context, req *types.QueryDenomAuthorityMetadataRequest) (*types.QueryDenomAuthorityMetadataResponse, error) { + sdkCtx := sdk.UnwrapSDKContext(ctx) + + authorityMetadata, err := k.GetAuthorityMetadata(sdkCtx, req.GetDenom()) + if err != nil { + return nil, err + } + + return &types.QueryDenomAuthorityMetadataResponse{AuthorityMetadata: authorityMetadata}, nil +} + +func (k Keeper) DenomsFromCreator(ctx context.Context, req *types.QueryDenomsFromCreatorRequest) (*types.QueryDenomsFromCreatorResponse, error) { + sdkCtx := sdk.UnwrapSDKContext(ctx) + denoms := k.GetDenomsFromCreator(sdkCtx, req.GetCreator()) + return &types.QueryDenomsFromCreatorResponse{Denoms: denoms}, nil +} diff --git a/x/tokenfactory/keeper/keeper.go b/x/tokenfactory/keeper/keeper.go new file mode 100644 index 0000000..8416caf --- /dev/null +++ b/x/tokenfactory/keeper/keeper.go @@ -0,0 +1,82 @@ +package keeper + +import ( + "fmt" + + "github.com/tendermint/tendermint/libs/log" + + "github.com/cosmos/cosmos-sdk/store/prefix" + storetypes "github.com/cosmos/cosmos-sdk/store/types" + sdk "github.com/cosmos/cosmos-sdk/types" + + "github.com/dymensionxyz/rollapp-wasm/x/tokenfactory/types" + + authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" + paramtypes "github.com/cosmos/cosmos-sdk/x/params/types" +) + +type ( + Keeper struct { + storeKey storetypes.StoreKey + + paramSpace paramtypes.Subspace + + accountKeeper types.AccountKeeper + bankKeeper types.BankKeeper + communityPoolKeeper types.CommunityPoolKeeper + } +) + +// NewKeeper returns a new instance of the x/tokenfactory keeper +func NewKeeper( + storeKey storetypes.StoreKey, + paramSpace paramtypes.Subspace, + accountKeeper types.AccountKeeper, + bankKeeper types.BankKeeper, + communityPoolKeeper types.CommunityPoolKeeper, +) Keeper { + if !paramSpace.HasKeyTable() { + paramSpace = paramSpace.WithKeyTable(types.ParamKeyTable()) + } + + return Keeper{ + storeKey: storeKey, + paramSpace: paramSpace, + + accountKeeper: accountKeeper, + bankKeeper: bankKeeper, + communityPoolKeeper: communityPoolKeeper, + } +} + +// Logger returns a logger for the x/tokenfactory module +func (k Keeper) Logger(ctx sdk.Context) log.Logger { + return ctx.Logger().With("module", fmt.Sprintf("x/%s", types.ModuleName)) +} + +// GetDenomPrefixStore returns the substore for a specific denom +func (k Keeper) GetDenomPrefixStore(ctx sdk.Context, denom string) sdk.KVStore { + store := ctx.KVStore(k.storeKey) + return prefix.NewStore(store, types.GetDenomPrefixStore(denom)) +} + +// GetCreatorPrefixStore returns the substore for a specific creator address +func (k Keeper) GetCreatorPrefixStore(ctx sdk.Context, creator string) sdk.KVStore { + store := ctx.KVStore(k.storeKey) + return prefix.NewStore(store, types.GetCreatorPrefix(creator)) +} + +// GetCreatorsPrefixStore returns the substore that contains a list of creators +func (k Keeper) GetCreatorsPrefixStore(ctx sdk.Context) sdk.KVStore { + store := ctx.KVStore(k.storeKey) + return prefix.NewStore(store, types.GetCreatorsPrefix()) +} + +// CreateModuleAccount creates a module account with minting and burning capabilities +// This account isn't intended to store any coins, +// it purely mints and burns them on behalf of the admin of respective denoms, +// and sends to the relevant address. +func (k Keeper) CreateModuleAccount(ctx sdk.Context) { + moduleAcc := authtypes.NewEmptyModuleAccount(types.ModuleName, authtypes.Minter, authtypes.Burner) + k.accountKeeper.SetModuleAccount(ctx, moduleAcc) +} diff --git a/x/tokenfactory/keeper/keeper_test.go b/x/tokenfactory/keeper/keeper_test.go new file mode 100644 index 0000000..35e7be9 --- /dev/null +++ b/x/tokenfactory/keeper/keeper_test.go @@ -0,0 +1,63 @@ +package keeper_test + +import ( + "testing" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/dymensionxyz/rollapp-wasm/app/apptesting" + "github.com/dymensionxyz/rollapp-wasm/x/tokenfactory/keeper" + "github.com/dymensionxyz/rollapp-wasm/x/tokenfactory/testhelpers" + "github.com/dymensionxyz/rollapp-wasm/x/tokenfactory/types" + "github.com/stretchr/testify/suite" + tmproto "github.com/tendermint/tendermint/proto/tendermint/types" +) + +type KeeperTestSuite struct { + apptesting.KeeperTestHelper + + queryClient types.QueryClient + msgServer types.MsgServer + // defaultDenom is on the suite, as it depends on the creator test address. + defaultDenom string +} + +func TestKeeperTestSuite(t *testing.T) { + suite.Run(t, new(KeeperTestSuite)) +} + +func (suite *KeeperTestSuite) SetupTest() { + suite.Setup() + // Fund every TestAcc with two denoms, one of which is the denom creation fee + fundAccsAmount := sdk.NewCoins(sdk.NewCoin(types.DefaultParams().DenomCreationFee[0].Denom, types.DefaultParams().DenomCreationFee[0].Amount.MulRaw(100)), sdk.NewCoin(testhelpers.SecondaryDenom, testhelpers.SecondaryAmount)) + for _, acc := range suite.TestAccs { + suite.FundAcc(acc, fundAccsAmount) + } + + suite.queryClient = types.NewQueryClient(suite.QueryHelper) + suite.msgServer = keeper.NewMsgServerImpl(suite.App.TokenFactoryKeeper) +} + +func (suite *KeeperTestSuite) CreateDefaultDenom() { + res, _ := suite.msgServer.CreateDenom(sdk.WrapSDKContext(suite.Ctx), types.NewMsgCreateDenom(suite.TestAccs[0].String(), "bitcoin")) + suite.defaultDenom = res.GetNewTokenDenom() +} + +func (suite *KeeperTestSuite) TestCreateModuleAccount() { + app := suite.App + + // remove module account + tokenfactoryModuleAccount := app.AccountKeeper.GetAccount(suite.Ctx, app.AccountKeeper.GetModuleAddress(types.ModuleName)) + app.AccountKeeper.RemoveAccount(suite.Ctx, tokenfactoryModuleAccount) + + // ensure module account was removed + suite.Ctx = app.BaseApp.NewContext(false, tmproto.Header{}) + tokenfactoryModuleAccount = app.AccountKeeper.GetAccount(suite.Ctx, app.AccountKeeper.GetModuleAddress(types.ModuleName)) + suite.Require().Nil(tokenfactoryModuleAccount) + + // create module account + app.TokenFactoryKeeper.CreateModuleAccount(suite.Ctx) + + // check that the module account is now initialized + tokenfactoryModuleAccount = app.AccountKeeper.GetAccount(suite.Ctx, app.AccountKeeper.GetModuleAddress(types.ModuleName)) + suite.Require().NotNil(tokenfactoryModuleAccount) +} diff --git a/x/tokenfactory/keeper/msg_server.go b/x/tokenfactory/keeper/msg_server.go new file mode 100644 index 0000000..40f911a --- /dev/null +++ b/x/tokenfactory/keeper/msg_server.go @@ -0,0 +1,191 @@ +package keeper + +import ( + "context" + + sdk "github.com/cosmos/cosmos-sdk/types" + + "github.com/dymensionxyz/rollapp-wasm/x/tokenfactory/types" +) + +type msgServer struct { + Keeper +} + +// NewMsgServerImpl returns an implementation of the MsgServer interface +// for the provided Keeper. +func NewMsgServerImpl(keeper Keeper) types.MsgServer { + return &msgServer{Keeper: keeper} +} + +var _ types.MsgServer = msgServer{} + +func (server msgServer) CreateDenom(goCtx context.Context, msg *types.MsgCreateDenom) (*types.MsgCreateDenomResponse, error) { + ctx := sdk.UnwrapSDKContext(goCtx) + + denom, err := server.Keeper.CreateDenom(ctx, msg.Sender, msg.Subdenom) + if err != nil { + return nil, err + } + + ctx.EventManager().EmitEvents(sdk.Events{ + sdk.NewEvent( + types.TypeMsgCreateDenom, + sdk.NewAttribute(types.AttributeCreator, msg.Sender), + sdk.NewAttribute(types.AttributeNewTokenDenom, denom), + ), + }) + + return &types.MsgCreateDenomResponse{ + NewTokenDenom: denom, + }, nil +} + +func (server msgServer) Mint(goCtx context.Context, msg *types.MsgMint) (*types.MsgMintResponse, error) { + ctx := sdk.UnwrapSDKContext(goCtx) + + // pay some extra gas cost to give a better error here. + _, denomExists := server.bankKeeper.GetDenomMetaData(ctx, msg.Amount.Denom) + if !denomExists { + return nil, types.ErrDenomDoesNotExist.Wrapf("denom: %s", msg.Amount.Denom) + } + + authorityMetadata, err := server.Keeper.GetAuthorityMetadata(ctx, msg.Amount.GetDenom()) + if err != nil { + return nil, err + } + + if msg.Sender != authorityMetadata.GetAdmin() { + return nil, types.ErrUnauthorized + } + + err = server.Keeper.mintTo(ctx, msg.Amount, msg.Sender) + if err != nil { + return nil, err + } + + ctx.EventManager().EmitEvents(sdk.Events{ + sdk.NewEvent( + types.TypeMsgMint, + sdk.NewAttribute(types.AttributeMintToAddress, msg.Sender), + sdk.NewAttribute(types.AttributeAmount, msg.Amount.String()), + ), + }) + + return &types.MsgMintResponse{}, nil +} + +func (server msgServer) Burn(goCtx context.Context, msg *types.MsgBurn) (*types.MsgBurnResponse, error) { + ctx := sdk.UnwrapSDKContext(goCtx) + + authorityMetadata, err := server.Keeper.GetAuthorityMetadata(ctx, msg.Amount.GetDenom()) + if err != nil { + return nil, err + } + + if msg.Sender != authorityMetadata.GetAdmin() { + return nil, types.ErrUnauthorized + } + + err = server.Keeper.burnFrom(ctx, msg.Amount, msg.Sender) + if err != nil { + return nil, err + } + + ctx.EventManager().EmitEvents(sdk.Events{ + sdk.NewEvent( + types.TypeMsgBurn, + sdk.NewAttribute(types.AttributeBurnFromAddress, msg.Sender), + sdk.NewAttribute(types.AttributeAmount, msg.Amount.String()), + ), + }) + + return &types.MsgBurnResponse{}, nil +} + +// func (server msgServer) ForceTransfer(goCtx context.Context, msg *types.MsgForceTransfer) (*types.MsgForceTransferResponse, error) { +// ctx := sdk.UnwrapSDKContext(goCtx) + +// authorityMetadata, err := server.Keeper.GetAuthorityMetadata(ctx, msg.Amount.GetDenom()) +// if err != nil { +// return nil, err +// } + +// if msg.Sender != authorityMetadata.GetAdmin() { +// return nil, types.ErrUnauthorized +// } + +// err = server.Keeper.forceTransfer(ctx, msg.Amount, msg.TransferFromAddress, msg.TransferToAddress) +// if err != nil { +// return nil, err +// } + +// ctx.EventManager().EmitEvents(sdk.Events{ +// sdk.NewEvent( +// types.TypeMsgForceTransfer, +// sdk.NewAttribute(types.AttributeTransferFromAddress, msg.TransferFromAddress), +// sdk.NewAttribute(types.AttributeTransferToAddress, msg.TransferToAddress), +// sdk.NewAttribute(types.AttributeAmount, msg.Amount.String()), +// ), +// }) + +// return &types.MsgForceTransferResponse{}, nil +// } + +func (server msgServer) ChangeAdmin(goCtx context.Context, msg *types.MsgChangeAdmin) (*types.MsgChangeAdminResponse, error) { + ctx := sdk.UnwrapSDKContext(goCtx) + + authorityMetadata, err := server.Keeper.GetAuthorityMetadata(ctx, msg.Denom) + if err != nil { + return nil, err + } + + if msg.Sender != authorityMetadata.GetAdmin() { + return nil, types.ErrUnauthorized + } + + err = server.Keeper.setAdmin(ctx, msg.Denom, msg.NewAdmin) + if err != nil { + return nil, err + } + ctx.EventManager().EmitEvents(sdk.Events{ + sdk.NewEvent( + types.TypeMsgChangeAdmin, + sdk.NewAttribute(types.AttributeDenom, msg.GetDenom()), + sdk.NewAttribute(types.AttributeNewAdmin, msg.NewAdmin), + ), + }) + + return &types.MsgChangeAdminResponse{}, nil +} + +func (server msgServer) SetDenomMetadata(goCtx context.Context, msg *types.MsgSetDenomMetadata) (*types.MsgSetDenomMetadataResponse, error) { + ctx := sdk.UnwrapSDKContext(goCtx) + + // Defense in depth validation of metadata + err := msg.Metadata.Validate() + if err != nil { + return nil, err + } + + authorityMetadata, err := server.Keeper.GetAuthorityMetadata(ctx, msg.Metadata.Base) + if err != nil { + return nil, err + } + + if msg.Sender != authorityMetadata.GetAdmin() { + return nil, types.ErrUnauthorized + } + + server.Keeper.bankKeeper.SetDenomMetaData(ctx, msg.Metadata) + + ctx.EventManager().EmitEvents(sdk.Events{ + sdk.NewEvent( + types.TypeMsgSetDenomMetadata, + sdk.NewAttribute(types.AttributeDenom, msg.Metadata.Base), + sdk.NewAttribute(types.AttributeDenomMetadata, msg.Metadata.String()), + ), + }) + + return &types.MsgSetDenomMetadataResponse{}, nil +} diff --git a/x/tokenfactory/keeper/msg_server_test.go b/x/tokenfactory/keeper/msg_server_test.go new file mode 100644 index 0000000..9bc59bb --- /dev/null +++ b/x/tokenfactory/keeper/msg_server_test.go @@ -0,0 +1,247 @@ +package keeper_test + +import ( + "fmt" + + "github.com/dymensionxyz/rollapp-wasm/x/tokenfactory/types" + + sdk "github.com/cosmos/cosmos-sdk/types" + banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" +) + +// TestMintDenomMsg tests TypeMsgMint message is emitted on a successful mint +func (suite *KeeperTestSuite) TestMintDenomMsg() { + // Create a denom + suite.CreateDefaultDenom() + + for _, tc := range []struct { + desc string + amount int64 + mintDenom string + admin string + valid bool + expectedMessageEvents int + }{ + { + desc: "denom does not exist", + amount: 10, + mintDenom: "factory/osmo1t7egva48prqmzl59x5ngv4zx0dtrwewc9m7z44/evmos", + admin: suite.TestAccs[0].String(), + valid: false, + }, + { + desc: "success case", + amount: 10, + mintDenom: suite.defaultDenom, + admin: suite.TestAccs[0].String(), + valid: true, + expectedMessageEvents: 1, + }, + } { + suite.Run(fmt.Sprintf("Case %s", tc.desc), func() { + ctx := suite.Ctx.WithEventManager(sdk.NewEventManager()) + suite.Require().Equal(0, len(ctx.EventManager().Events())) + // Test mint message + suite.msgServer.Mint(sdk.WrapSDKContext(ctx), types.NewMsgMint(tc.admin, sdk.NewInt64Coin(tc.mintDenom, 10))) //nolint:errcheck + // Ensure current number and type of event is emitted + suite.AssertEventEmitted(ctx, types.TypeMsgMint, tc.expectedMessageEvents) + }) + } +} + +// TestBurnDenomMsg tests TypeMsgBurn message is emitted on a successful burn +func (suite *KeeperTestSuite) TestBurnDenomMsg() { + // Create a denom. + suite.CreateDefaultDenom() + // mint 10 default token for testAcc[0] + suite.msgServer.Mint(sdk.WrapSDKContext(suite.Ctx), types.NewMsgMint(suite.TestAccs[0].String(), sdk.NewInt64Coin(suite.defaultDenom, 10))) //nolint:errcheck + + for _, tc := range []struct { + desc string + amount int64 + burnDenom string + admin string + valid bool + expectedMessageEvents int + }{ + { + desc: "denom does not exist", + burnDenom: "factory/osmo1t7egva48prqmzl59x5ngv4zx0dtrwewc9m7z44/evmos", + admin: suite.TestAccs[0].String(), + valid: false, + }, + { + desc: "success case", + burnDenom: suite.defaultDenom, + admin: suite.TestAccs[0].String(), + valid: true, + expectedMessageEvents: 1, + }, + } { + suite.Run(fmt.Sprintf("Case %s", tc.desc), func() { + ctx := suite.Ctx.WithEventManager(sdk.NewEventManager()) + suite.Require().Equal(0, len(ctx.EventManager().Events())) + // Test burn message + suite.msgServer.Burn(sdk.WrapSDKContext(ctx), types.NewMsgBurn(tc.admin, sdk.NewInt64Coin(tc.burnDenom, 10))) //nolint:errcheck + // Ensure current number and type of event is emitted + suite.AssertEventEmitted(ctx, types.TypeMsgBurn, tc.expectedMessageEvents) + }) + } +} + +// TestCreateDenomMsg tests TypeMsgCreateDenom message is emitted on a successful denom creation +func (suite *KeeperTestSuite) TestCreateDenomMsg() { + defaultDenomCreationFee := types.Params{DenomCreationFee: sdk.NewCoins(sdk.NewCoin(sdk.DefaultBondDenom, sdk.NewInt(50000000)))} + for _, tc := range []struct { + desc string + denomCreationFee types.Params + subdenom string + valid bool + expectedMessageEvents int + }{ + { + desc: "subdenom too long", + denomCreationFee: defaultDenomCreationFee, + subdenom: "assadsadsadasdasdsadsadsadsadsadsadsklkadaskkkdasdasedskhanhassyeunganassfnlksdflksafjlkasd", + valid: false, + }, + { + desc: "success case: defaultDenomCreationFee", + denomCreationFee: defaultDenomCreationFee, + subdenom: "evmos", + valid: true, + expectedMessageEvents: 1, + }, + } { + suite.SetupTest() + suite.Run(fmt.Sprintf("Case %s", tc.desc), func() { + tokenFactoryKeeper := suite.App.TokenFactoryKeeper + ctx := suite.Ctx.WithEventManager(sdk.NewEventManager()) + suite.Require().Equal(0, len(ctx.EventManager().Events())) + // Set denom creation fee in params + tokenFactoryKeeper.SetParams(suite.Ctx, tc.denomCreationFee) + // Test create denom message + suite.msgServer.CreateDenom(sdk.WrapSDKContext(ctx), types.NewMsgCreateDenom(suite.TestAccs[0].String(), tc.subdenom)) //nolint:errcheck + // Ensure current number and type of event is emitted + suite.AssertEventEmitted(ctx, types.TypeMsgCreateDenom, tc.expectedMessageEvents) + }) + } +} + +// TestChangeAdminDenomMsg tests TypeMsgChangeAdmin message is emitted on a successful admin change +func (suite *KeeperTestSuite) TestChangeAdminDenomMsg() { + for _, tc := range []struct { + desc string + msgChangeAdmin func(denom string) *types.MsgChangeAdmin + expectedChangeAdminPass bool + expectedAdminIndex int + msgMint func(denom string) *types.MsgMint + expectedMintPass bool + expectedMessageEvents int + }{ + { + desc: "non-admins can't change the existing admin", + msgChangeAdmin: func(denom string) *types.MsgChangeAdmin { + return types.NewMsgChangeAdmin(suite.TestAccs[1].String(), denom, suite.TestAccs[2].String()) + }, + expectedChangeAdminPass: false, + expectedAdminIndex: 0, + }, + { + desc: "success change admin", + msgChangeAdmin: func(denom string) *types.MsgChangeAdmin { + return types.NewMsgChangeAdmin(suite.TestAccs[0].String(), denom, suite.TestAccs[1].String()) + }, + expectedAdminIndex: 1, + expectedChangeAdminPass: true, + expectedMessageEvents: 1, + msgMint: func(denom string) *types.MsgMint { + return types.NewMsgMint(suite.TestAccs[1].String(), sdk.NewInt64Coin(denom, 5)) + }, + expectedMintPass: true, + }, + } { + suite.Run(fmt.Sprintf("Case %s", tc.desc), func() { + // setup test + suite.SetupTest() + ctx := suite.Ctx.WithEventManager(sdk.NewEventManager()) + suite.Require().Equal(0, len(ctx.EventManager().Events())) + // Create a denom and mint + res, err := suite.msgServer.CreateDenom(sdk.WrapSDKContext(ctx), types.NewMsgCreateDenom(suite.TestAccs[0].String(), "bitcoin")) + suite.Require().NoError(err) + testDenom := res.GetNewTokenDenom() + suite.msgServer.Mint(sdk.WrapSDKContext(ctx), types.NewMsgMint(suite.TestAccs[0].String(), sdk.NewInt64Coin(testDenom, 10))) //nolint:errcheck + // Test change admin message + suite.msgServer.ChangeAdmin(sdk.WrapSDKContext(ctx), tc.msgChangeAdmin(testDenom)) //nolint:errcheck + // Ensure current number and type of event is emitted + suite.AssertEventEmitted(ctx, types.TypeMsgChangeAdmin, tc.expectedMessageEvents) + }) + } +} + +// TestSetDenomMetaDataMsg tests TypeMsgSetDenomMetadata message is emitted on a successful denom metadata change +func (suite *KeeperTestSuite) TestSetDenomMetaDataMsg() { + // setup test + suite.SetupTest() + suite.CreateDefaultDenom() + + for _, tc := range []struct { + desc string + msgSetDenomMetadata types.MsgSetDenomMetadata + expectedPass bool + expectedMessageEvents int + }{ + { + desc: "successful set denom metadata", + msgSetDenomMetadata: *types.NewMsgSetDenomMetadata(suite.TestAccs[0].String(), banktypes.Metadata{ + Description: "yeehaw", + DenomUnits: []*banktypes.DenomUnit{ + { + Denom: suite.defaultDenom, + Exponent: 0, + }, + { + Denom: "uosmo", + Exponent: 6, + }, + }, + Base: suite.defaultDenom, + Display: "uosmo", + Name: "OSMO", + Symbol: "OSMO", + }), + expectedPass: true, + expectedMessageEvents: 1, + }, + { + desc: "non existent factory denom name", + msgSetDenomMetadata: *types.NewMsgSetDenomMetadata(suite.TestAccs[0].String(), banktypes.Metadata{ + Description: "yeehaw", + DenomUnits: []*banktypes.DenomUnit{ + { + Denom: fmt.Sprintf("factory/%s/litecoin", suite.TestAccs[0].String()), + Exponent: 0, + }, + { + Denom: "uosmo", + Exponent: 6, + }, + }, + Base: fmt.Sprintf("factory/%s/litecoin", suite.TestAccs[0].String()), + Display: "uosmo", + Name: "OSMO", + Symbol: "OSMO", + }), + expectedPass: false, + }, + } { + suite.Run(fmt.Sprintf("Case %s", tc.desc), func() { + ctx := suite.Ctx.WithEventManager(sdk.NewEventManager()) + suite.Require().Equal(0, len(ctx.EventManager().Events())) + // Test set denom metadata message + suite.msgServer.SetDenomMetadata(sdk.WrapSDKContext(ctx), &tc.msgSetDenomMetadata) //nolint:errcheck + // Ensure current number and type of event is emitted + suite.AssertEventEmitted(ctx, types.TypeMsgSetDenomMetadata, tc.expectedMessageEvents) + }) + } +} diff --git a/x/tokenfactory/keeper/params.go b/x/tokenfactory/keeper/params.go new file mode 100644 index 0000000..8fe99d9 --- /dev/null +++ b/x/tokenfactory/keeper/params.go @@ -0,0 +1,18 @@ +package keeper + +import ( + "github.com/dymensionxyz/rollapp-wasm/x/tokenfactory/types" + + sdk "github.com/cosmos/cosmos-sdk/types" +) + +// GetParams returns the total set params. +func (k Keeper) GetParams(ctx sdk.Context) (params types.Params) { + k.paramSpace.GetParamSet(ctx, ¶ms) + return params +} + +// SetParams sets the total set of params. +func (k Keeper) SetParams(ctx sdk.Context, params types.Params) { + k.paramSpace.SetParamSet(ctx, ¶ms) +} diff --git a/x/tokenfactory/module.go b/x/tokenfactory/module.go new file mode 100644 index 0000000..c6ba35f --- /dev/null +++ b/x/tokenfactory/module.go @@ -0,0 +1,225 @@ +/* +The tokenfactory module allows any account to create a new token with +the name `factory/{creator address}/{subdenom}`. + +- Mint and burn user denom to and form any account +- Create a transfer of their denom between any two accounts +- Change the admin. In the future, more admin capabilities may be added. +*/ +package tokenfactory + +import ( + "context" + "encoding/json" + "fmt" + "github.com/cosmos/cosmos-sdk/client" + "github.com/cosmos/cosmos-sdk/codec" + cdctypes "github.com/cosmos/cosmos-sdk/codec/types" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/types/module" + "github.com/gorilla/mux" + "github.com/grpc-ecosystem/grpc-gateway/runtime" + "github.com/spf13/cobra" + abci "github.com/tendermint/tendermint/abci/types" + + "github.com/dymensionxyz/rollapp-wasm/x/tokenfactory/client/cli" + "github.com/dymensionxyz/rollapp-wasm/x/tokenfactory/keeper" + "github.com/dymensionxyz/rollapp-wasm/x/tokenfactory/types" +) + +var ( + _ module.AppModule = AppModule{} + _ module.AppModuleBasic = AppModuleBasic{} +) + +// ---------------------------------------------------------------------------- +// AppModuleBasic +// ---------------------------------------------------------------------------- + +// AppModuleBasic implements the AppModuleBasic interface for the capability module. +type AppModuleBasic struct{} + +func NewAppModuleBasic() AppModuleBasic { + return AppModuleBasic{} +} + +// Name returns the x/tokenfactory module's name. +func (AppModuleBasic) Name() string { + return types.ModuleName +} + +func (AppModuleBasic) RegisterLegacyAminoCodec(cdc *codec.LegacyAmino) { + types.RegisterCodec(cdc) +} + +// RegisterInterfaces registers the module's interface types +func (a AppModuleBasic) RegisterInterfaces(reg cdctypes.InterfaceRegistry) { + types.RegisterInterfaces(reg) +} + +// DefaultGenesis returns the x/tokenfactory module's default genesis state. +func (AppModuleBasic) DefaultGenesis(cdc codec.JSONCodec) json.RawMessage { + return cdc.MustMarshalJSON(types.DefaultGenesis()) +} + +// ValidateGenesis performs genesis state validation for the x/tokenfactory module. +func (AppModuleBasic) ValidateGenesis(cdc codec.JSONCodec, config client.TxEncodingConfig, bz json.RawMessage) error { + var genState types.GenesisState + if err := cdc.UnmarshalJSON(bz, &genState); err != nil { + return fmt.Errorf("failed to unmarshal %s genesis state: %w", types.ModuleName, err) + } + + return genState.Validate() +} + +// RegisterRESTRoutes registers the capability module's REST service handlers. +func (AppModuleBasic) RegisterRESTRoutes(clientCtx client.Context, rtr *mux.Router) { +} + +// RegisterGRPCGatewayRoutes registers the gRPC Gateway routes for the module. +func (AppModuleBasic) RegisterGRPCGatewayRoutes(clientCtx client.Context, mux *runtime.ServeMux) { + types.RegisterQueryHandlerClient(context.Background(), mux, types.NewQueryClient(clientCtx)) //nolint:errcheck +} + +// GetTxCmd returns the x/tokenfactory module's root tx command. +func (a AppModuleBasic) GetTxCmd() *cobra.Command { + return cli.GetTxCmd() +} + +// GetQueryCmd returns the x/tokenfactory module's root query command. +func (AppModuleBasic) GetQueryCmd() *cobra.Command { + return cli.GetQueryCmd() +} + +// ---------------------------------------------------------------------------- +// AppModule +// ---------------------------------------------------------------------------- + +// AppModule implements the AppModule interface for the capability module. +type AppModule struct { + AppModuleBasic + + keeper keeper.Keeper + accountKeeper types.AccountKeeper + bankKeeper types.BankKeeper +} + +func NewAppModule( + keeper keeper.Keeper, + accountKeeper types.AccountKeeper, + bankKeeper types.BankKeeper, +) AppModule { + return AppModule{ + AppModuleBasic: NewAppModuleBasic(), + keeper: keeper, + accountKeeper: accountKeeper, + bankKeeper: bankKeeper, + } +} + +// Name returns the x/tokenfactory module's name. +func (am AppModule) Name() string { + return am.AppModuleBasic.Name() +} + +// Route returns the x/tokenfactory module's message routing key. +func (am AppModule) Route() sdk.Route { + return sdk.Route{} +} + +// QuerierRoute returns the x/tokenfactory module's query routing key. +func (AppModule) QuerierRoute() string { return types.QuerierRoute } + +// LegacyQuerierHandler returns the x/tokenfactory module's Querier. +func (am AppModule) LegacyQuerierHandler(legacyQuerierCdc *codec.LegacyAmino) sdk.Querier { + return nil +} + +// RegisterServices registers a GRPC query service to respond to the +// module-specific GRPC queries. +func (am AppModule) RegisterServices(cfg module.Configurator) { + types.RegisterMsgServer(cfg.MsgServer(), keeper.NewMsgServerImpl(am.keeper)) + types.RegisterQueryServer(cfg.QueryServer(), am.keeper) +} + +// RegisterInvariants registers the x/tokenfactory module's invariants. +func (am AppModule) RegisterInvariants(_ sdk.InvariantRegistry) {} + +// InitGenesis performs the x/tokenfactory module's genesis initialization. It +// returns no validator updates. +func (am AppModule) InitGenesis(ctx sdk.Context, cdc codec.JSONCodec, gs json.RawMessage) []abci.ValidatorUpdate { + var genState types.GenesisState + cdc.MustUnmarshalJSON(gs, &genState) + + am.keeper.InitGenesis(ctx, genState) + + return []abci.ValidatorUpdate{} +} + +// ExportGenesis returns the x/tokenfactory module's exported genesis state as raw +// JSON bytes. +func (am AppModule) ExportGenesis(ctx sdk.Context, cdc codec.JSONCodec) json.RawMessage { + genState := am.keeper.ExportGenesis(ctx) + return cdc.MustMarshalJSON(genState) +} + +// ConsensusVersion implements ConsensusVersion. +func (AppModule) ConsensusVersion() uint64 { return 1 } + +// BeginBlock executes all ABCI BeginBlock logic respective to the tokenfactory module. +func (am AppModule) BeginBlock(_ sdk.Context, _ abci.RequestBeginBlock) {} + +// EndBlock executes all ABCI EndBlock logic respective to the tokenfactory module. It +// returns no validator updates. +func (am AppModule) EndBlock(_ sdk.Context, _ abci.RequestEndBlock) []abci.ValidatorUpdate { + return []abci.ValidatorUpdate{} +} + +// ___________________________________________________________________________ + +// AppModuleSimulationV2 functions + +// // GenerateGenesisState creates a randomized GenState of the tokenfactory module. +// func (am AppModule) SimulatorGenesisState(simState *module.SimulationState, s *simtypes.SimCtx) { +// tfDefaultGen := types.DefaultGenesis() +// tfDefaultGen.Params.DenomCreationFee = sdk.NewCoins(sdk.NewCoin("stake", sdk.NewInt(10000000))) +// tfDefaultGenJson := simState.Cdc.MustMarshalJSON(tfDefaultGen) +// simState.GenState[types.ModuleName] = tfDefaultGenJson +// } + +// // WeightedOperations returns the all the lockup module operations with their respective weights. +// func (am AppModule) Actions() []simtypes.Action { +// return []simtypes.Action{ +// simtypes.NewMsgBasedAction("create token factory token", am.keeper, simulation.RandomMsgCreateDenom), +// simtypes.NewMsgBasedAction("mint token factory token", am.keeper, simulation.RandomMsgMintDenom), +// simtypes.NewMsgBasedAction("burn token factory token", am.keeper, simulation.RandomMsgBurnDenom), +// simtypes.NewMsgBasedAction("change admin token factory token", am.keeper, simulation.RandomMsgChangeAdmin), +// } +// } + +// ____________________________________________________________________________ + +// AppModuleSimulation functions +/*func (AppModule) GenerateGenesisState(simState *module.SimulationState) { + simulation.RandomizedGenState(simState) +} + +// GenerateGenesisState creates a randomized GenState of the bank module. +func (am AppModule) ProposalContents(simState module.SimulationState) []simtypes.WeightedProposalContent { + return nil +} + +// RandomizedParams creates randomized bank param changes for the simulator. +func (am AppModule) RandomizedParams(r *rand.Rand) []simtypes.ParamChange { + return simulation.ParamChanges(r) +} + +// RegisterStoreDecoder registers a decoder for supply module's types +func (am AppModule) RegisterStoreDecoder(sdr sdk.StoreDecoderRegistry) { +} + +// WeightedOperations returns the all the gov module operations with their respective weights. +func (am AppModule) WeightedOperations(simState module.SimulationState) []simtypes.WeightedOperation { + return simulation.WeightedOperations(&simState, am.keeper, am.accountKeeper, am.bankKeeper) +} +*/ diff --git a/x/tokenfactory/simulation/genesis.go b/x/tokenfactory/simulation/genesis.go new file mode 100644 index 0000000..dfd1df6 --- /dev/null +++ b/x/tokenfactory/simulation/genesis.go @@ -0,0 +1,18 @@ +package simulation + +/*func RandDenomCreationFeeParam(r *rand.Rand) sdk.Coins { + amount := r.Int63n(10_000_000) + return sdk.NewCoins(sdk.NewCoin("stake", sdk.NewInt(amount))) +} + +func RandomizedGenState(simstate *module.SimulationState) { + tfGenesis := types.DefaultGenesis() + + _, err := simstate.Cdc.MarshalJSON(tfGenesis) + if err != nil { + panic(err) + } + + simstate.GenState[types.ModuleName] = simstate.Cdc.MustMarshalJSON(tfGenesis) +} +*/ diff --git a/x/tokenfactory/simulation/operations.go b/x/tokenfactory/simulation/operations.go new file mode 100644 index 0000000..9b6c4de --- /dev/null +++ b/x/tokenfactory/simulation/operations.go @@ -0,0 +1,406 @@ +package simulation + +/*import ( + "math/rand" + + "github.com/cosmos/cosmos-sdk/baseapp" + simappparams "github.com/cosmos/cosmos-sdk/simapp/params" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/types/module" + simtypes "github.com/cosmos/cosmos-sdk/types/simulation" + banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" + "github.com/cosmos/cosmos-sdk/x/simulation" + "github.com/dymensionxyz/rollapp-wasm/app/params" + "github.com/dymensionxyz/rollapp-wasm/x/tokenfactory/types" +) + +// Simulation operation weights constants +// +//nolint:gosec +const ( + OpWeightMsgCreateDenom = "op_weight_msg_create_denom" + OpWeightMsgMint = "op_weight_msg_mint" + OpWeightMsgBurn = "op_weight_msg_burn" + OpWeightMsgChangeAdmin = "op_weight_msg_change_admin" + OpWeightMsgSetDenomMetadata = "op_weight_msg_set_denom_metadata" +) + +type TokenfactoryKeeper interface { + GetParams(ctx sdk.Context) (params types.Params) + GetAuthorityMetadata(ctx sdk.Context, denom string) (types.DenomAuthorityMetadata, error) + GetAllDenomsIterator(ctx sdk.Context) sdk.Iterator + GetDenomsFromCreator(ctx sdk.Context, creator string) []string +} + +type BankKeeper interface { + simulation.BankKeeper + GetAllBalances(ctx sdk.Context, addr sdk.AccAddress) sdk.Coins + GetBalance(ctx sdk.Context, addr sdk.AccAddress, denom string) sdk.Coin +} + +func WeightedOperations( + simstate *module.SimulationState, + tfKeeper TokenfactoryKeeper, + ak types.AccountKeeper, + bk BankKeeper, +) simulation.WeightedOperations { + + var ( + weightMsgCreateDenom int + weightMsgMint int + weightMsgBurn int + weightMsgChangeAdmin int + weightMsgSetDenomMetadata int + ) + + simstate.AppParams.GetOrGenerate(simstate.Cdc, OpWeightMsgCreateDenom, &weightMsgCreateDenom, nil, + func(_ *rand.Rand) { + weightMsgCreateDenom = params.DefaultWeightMsgCreateDenom + }, + ) + simstate.AppParams.GetOrGenerate(simstate.Cdc, OpWeightMsgMint, &weightMsgMint, nil, + func(_ *rand.Rand) { + weightMsgMint = params.DefaultWeightMsgMint + }, + ) + simstate.AppParams.GetOrGenerate(simstate.Cdc, OpWeightMsgBurn, &weightMsgBurn, nil, + func(_ *rand.Rand) { + weightMsgBurn = params.DefaultWeightMsgBurn + }, + ) + simstate.AppParams.GetOrGenerate(simstate.Cdc, OpWeightMsgChangeAdmin, &weightMsgChangeAdmin, nil, + func(_ *rand.Rand) { + weightMsgChangeAdmin = params.DefaultWeightMsgChangeAdmin + }, + ) + simstate.AppParams.GetOrGenerate(simstate.Cdc, OpWeightMsgSetDenomMetadata, &weightMsgSetDenomMetadata, nil, + func(_ *rand.Rand) { + weightMsgSetDenomMetadata = params.DefaultWeightMsgSetDenomMetadata + }, + ) + + return simulation.WeightedOperations{ + simulation.NewWeightedOperation( + weightMsgCreateDenom, + SimulateMsgCreateDenom( + tfKeeper, + ak, + bk, + ), + ), + simulation.NewWeightedOperation( + weightMsgMint, + SimulateMsgMint( + tfKeeper, + ak, + bk, + DefaultSimulationDenomSelector, + ), + ), + simulation.NewWeightedOperation( + weightMsgBurn, + SimulateMsgBurn( + tfKeeper, + ak, + bk, + DefaultSimulationDenomSelector, + ), + ), + simulation.NewWeightedOperation( + weightMsgChangeAdmin, + SimulateMsgChangeAdmin( + tfKeeper, + ak, + bk, + DefaultSimulationDenomSelector, + ), + ), + simulation.NewWeightedOperation( + weightMsgSetDenomMetadata, + SimulateMsgSetDenomMetadata( + tfKeeper, + ak, + bk, + DefaultSimulationDenomSelector, + ), + ), + } +} + +type DenomSelector = func(*rand.Rand, sdk.Context, TokenfactoryKeeper, string) (string, bool) + +func DefaultSimulationDenomSelector(r *rand.Rand, ctx sdk.Context, tfKeeper TokenfactoryKeeper, creator string) (string, bool) { + denoms := tfKeeper.GetDenomsFromCreator(ctx, creator) + if len(denoms) == 0 { + return "", false + } + randPos := r.Intn(len(denoms)) + + return denoms[randPos], true +} + +func SimulateMsgSetDenomMetadata( + tfKeeper TokenfactoryKeeper, + ak types.AccountKeeper, + bk BankKeeper, + denomSelector DenomSelector, +) simtypes.Operation { + return func( + r *rand.Rand, + app *baseapp.BaseApp, + ctx sdk.Context, + accs []simtypes.Account, + chainID string, + ) (simtypes.OperationMsg, []simtypes.FutureOperation, error) { + // Get create denom account + createdDenomAccount, _ := simtypes.RandomAcc(r, accs) + + // Get demon + denom, hasDenom := denomSelector(r, ctx, tfKeeper, createdDenomAccount.Address.String()) + if !hasDenom { + return simtypes.NoOpMsg(types.ModuleName, types.MsgSetDenomMetadata{}.Type(), "sim account have no denom created"), nil, nil + } + + // Get admin of the denom + authData, err := tfKeeper.GetAuthorityMetadata(ctx, denom) + if err != nil { + return simtypes.NoOpMsg(types.ModuleName, types.MsgSetDenomMetadata{}.Type(), "err authority metadata"), nil, err + } + adminAccount, found := simtypes.FindAccount(accs, sdk.MustAccAddressFromBech32(authData.Admin)) + if !found { + return simtypes.NoOpMsg(types.ModuleName, types.MsgSetDenomMetadata{}.Type(), "admin account not found"), nil, nil + } + + metadata := banktypes.Metadata{ + Description: simtypes.RandStringOfLength(r, 10), + DenomUnits: []*banktypes.DenomUnit{{ + Denom: denom, + Exponent: 0, + }}, + Base: denom, + Display: denom, + Name: simtypes.RandStringOfLength(r, 10), + Symbol: simtypes.RandStringOfLength(r, 10), + } + + msg := types.MsgSetDenomMetadata{ + Sender: adminAccount.Address.String(), + Metadata: metadata, + } + + txCtx := BuildOperationInput(r, app, ctx, &msg, adminAccount, ak, bk, nil) + return simulation.GenAndDeliverTxWithRandFees(txCtx) + } +} + +func SimulateMsgChangeAdmin( + tfKeeper TokenfactoryKeeper, + ak types.AccountKeeper, + bk BankKeeper, + denomSelector DenomSelector, +) simtypes.Operation { + return func( + r *rand.Rand, + app *baseapp.BaseApp, + ctx sdk.Context, + accs []simtypes.Account, + chainID string, + ) (simtypes.OperationMsg, []simtypes.FutureOperation, error) { + // Get create denom account + createdDenomAccount, _ := simtypes.RandomAcc(r, accs) + + // Get demon + denom, hasDenom := denomSelector(r, ctx, tfKeeper, createdDenomAccount.Address.String()) + if !hasDenom { + return simtypes.NoOpMsg(types.ModuleName, types.MsgChangeAdmin{}.Type(), "sim account have no denom created"), nil, nil + } + + // Get admin of the denom + authData, err := tfKeeper.GetAuthorityMetadata(ctx, denom) + if err != nil { + return simtypes.NoOpMsg(types.ModuleName, types.MsgChangeAdmin{}.Type(), "err authority metadata"), nil, err + } + curAdminAccount, found := simtypes.FindAccount(accs, sdk.MustAccAddressFromBech32(authData.Admin)) + if !found { + return simtypes.NoOpMsg(types.ModuleName, types.MsgChangeAdmin{}.Type(), "admin account not found"), nil, nil + } + + // Rand new admin account + newAdmin, _ := simtypes.RandomAcc(r, accs) + if newAdmin.Address.String() == curAdminAccount.Address.String() { + return simtypes.NoOpMsg(types.ModuleName, types.MsgChangeAdmin{}.Type(), "new admin cannot be the same as current admin"), nil, nil + } + + // Create msg + msg := types.MsgChangeAdmin{ + Sender: curAdminAccount.Address.String(), + Denom: denom, + NewAdmin: newAdmin.Address.String(), + } + + txCtx := BuildOperationInput(r, app, ctx, &msg, curAdminAccount, ak, bk, nil) + return simulation.GenAndDeliverTxWithRandFees(txCtx) + } +} + +func SimulateMsgBurn( + tfKeeper TokenfactoryKeeper, + ak types.AccountKeeper, + bk BankKeeper, + denomSelector DenomSelector, +) simtypes.Operation { + return func( + r *rand.Rand, + app *baseapp.BaseApp, + ctx sdk.Context, + accs []simtypes.Account, + chainID string, + ) (simtypes.OperationMsg, []simtypes.FutureOperation, error) { + // Get create denom account + createdDenomAccount, _ := simtypes.RandomAcc(r, accs) + + // Get demon + denom, hasDenom := denomSelector(r, ctx, tfKeeper, createdDenomAccount.Address.String()) + if !hasDenom { + return simtypes.NoOpMsg(types.ModuleName, types.MsgBurn{}.Type(), "sim account have no denom created"), nil, nil + } + + // Get admin of the denom + authData, err := tfKeeper.GetAuthorityMetadata(ctx, denom) + if err != nil { + return simtypes.NoOpMsg(types.ModuleName, types.MsgBurn{}.Type(), "err authority metadata"), nil, err + } + adminAccount, found := simtypes.FindAccount(accs, sdk.MustAccAddressFromBech32(authData.Admin)) + if !found { + return simtypes.NoOpMsg(types.ModuleName, types.MsgBurn{}.Type(), "admin account not found"), nil, nil + } + + // Check if admin account balance = 0 + accountBalance := bk.GetBalance(ctx, adminAccount.Address, denom) + if accountBalance.Amount.LTE(sdk.ZeroInt()) { + return simtypes.NoOpMsg(types.ModuleName, types.MsgBurn{}.Type(), "sim account have no balance"), nil, nil + } + + // Rand burn amount + amount, _ := simtypes.RandPositiveInt(r, accountBalance.Amount) + burnAmount := sdk.NewCoin(denom, amount) + + // Create msg + msg := types.MsgBurn{ + Sender: adminAccount.Address.String(), + Amount: burnAmount, + } + + txCtx := BuildOperationInput(r, app, ctx, &msg, adminAccount, ak, bk, sdk.NewCoins(burnAmount)) + return simulation.GenAndDeliverTxWithRandFees(txCtx) + } +} + +// Simulate msg mint denom +func SimulateMsgMint( + tfKeeper TokenfactoryKeeper, + ak types.AccountKeeper, + bk BankKeeper, + denomSelector DenomSelector, +) simtypes.Operation { + return func( + r *rand.Rand, + app *baseapp.BaseApp, + ctx sdk.Context, + accs []simtypes.Account, + chainID string, + ) (simtypes.OperationMsg, []simtypes.FutureOperation, error) { + // Get create denom account + createdDenomAccount, _ := simtypes.RandomAcc(r, accs) + + // Get demon + denom, hasDenom := denomSelector(r, ctx, tfKeeper, createdDenomAccount.Address.String()) + if !hasDenom { + return simtypes.NoOpMsg(types.ModuleName, types.MsgMint{}.Type(), "sim account have no denom created"), nil, nil + } + + // Get admin of the denom + authData, err := tfKeeper.GetAuthorityMetadata(ctx, denom) + if err != nil { + return simtypes.NoOpMsg(types.ModuleName, types.MsgMint{}.Type(), "err authority metadata"), nil, err + } + adminAccount, found := simtypes.FindAccount(accs, sdk.MustAccAddressFromBech32(authData.Admin)) + if !found { + return simtypes.NoOpMsg(types.ModuleName, types.MsgMint{}.Type(), "admin account not found"), nil, nil + } + + // Rand mint amount + mintAmount, _ := simtypes.RandPositiveInt(r, sdk.NewIntFromUint64(100_000_000)) + + // Create msg mint + msg := types.MsgMint{ + Sender: adminAccount.Address.String(), + Amount: sdk.NewCoin(denom, mintAmount), + } + + txCtx := BuildOperationInput(r, app, ctx, &msg, adminAccount, ak, bk, nil) + return simulation.GenAndDeliverTxWithRandFees(txCtx) + } +} + +// Simulate msg create denom +func SimulateMsgCreateDenom(tfKeeper TokenfactoryKeeper, ak types.AccountKeeper, bk BankKeeper) simtypes.Operation { + return func( + r *rand.Rand, + app *baseapp.BaseApp, + ctx sdk.Context, + accs []simtypes.Account, + chainID string, + ) (simtypes.OperationMsg, []simtypes.FutureOperation, error) { + // Get sims account + simAccount, _ := simtypes.RandomAcc(r, accs) + + // Check if sims account enough create fee + createFee := tfKeeper.GetParams(ctx).DenomCreationFee + balances := bk.GetAllBalances(ctx, simAccount.Address) + _, hasNeg := balances.SafeSub(createFee) + if hasNeg { + return simtypes.NoOpMsg(types.ModuleName, types.MsgCreateDenom{}.Type(), "Creator not enough creation fee"), nil, nil + } + + // Create msg create denom + msg := types.MsgCreateDenom{ + Sender: simAccount.Address.String(), + Subdenom: simtypes.RandStringOfLength(r, 10), + } + + txCtx := BuildOperationInput(r, app, ctx, &msg, simAccount, ak, bk, createFee) + return simulation.GenAndDeliverTxWithRandFees(txCtx) + } +} + +// BuildOperationInput helper to build object +func BuildOperationInput( + r *rand.Rand, + app *baseapp.BaseApp, + ctx sdk.Context, + msg interface { + sdk.Msg + Type() string + }, + simAccount simtypes.Account, + ak types.AccountKeeper, + bk BankKeeper, + deposit sdk.Coins, +) simulation.OperationInput { + return simulation.OperationInput{ + R: r, + App: app, + TxGen: simappparams.MakeTestEncodingConfig().TxConfig, + Cdc: nil, + Msg: msg, + MsgType: msg.Type(), + Context: ctx, + SimAccount: simAccount, + AccountKeeper: ak, + Bankkeeper: bk, + ModuleName: types.ModuleName, + CoinsSpentInMsg: deposit, + } +} +*/ diff --git a/x/tokenfactory/simulation/params.go b/x/tokenfactory/simulation/params.go new file mode 100644 index 0000000..47cc91b --- /dev/null +++ b/x/tokenfactory/simulation/params.go @@ -0,0 +1,24 @@ +package simulation + +/*import ( + "fmt" + "math/rand" + + simtypes "github.com/cosmos/cosmos-sdk/types/simulation" + "github.com/cosmos/cosmos-sdk/x/simulation" + "github.com/dymensionxyz/rollapp-wasm/x/tokenfactory/types" +) + +func ParamChanges(r *rand.Rand) []simtypes.ParamChange { + return []simtypes.ParamChange{ + simulation.NewSimParamChange( + types.ModuleName, + string(types.KeyDenomCreationFee), + func(r *rand.Rand) string { + amount := RandDenomCreationFeeParam(r) + return fmt.Sprintf("[{\"denom\":\"%v\",\"amount\":\"%v\"}]", amount[0].Denom, amount[0].Amount) + }, + ), + } +} +*/ diff --git a/x/tokenfactory/testhelpers/consts.go b/x/tokenfactory/testhelpers/consts.go new file mode 100644 index 0000000..d7804a3 --- /dev/null +++ b/x/tokenfactory/testhelpers/consts.go @@ -0,0 +1,8 @@ +package testhelpers + +import sdk "github.com/cosmos/cosmos-sdk/types" + +var ( + SecondaryDenom = "uion" + SecondaryAmount = sdk.NewInt(100000000) +) diff --git a/x/tokenfactory/testhelpers/suite.go b/x/tokenfactory/testhelpers/suite.go new file mode 100644 index 0000000..109e905 --- /dev/null +++ b/x/tokenfactory/testhelpers/suite.go @@ -0,0 +1,67 @@ +package testhelpers + +/*import ( + "encoding/json" + "testing" + "time" + + "github.com/cosmos/cosmos-sdk/x/authz" + "github.com/stretchr/testify/require" + + "github.com/cosmos/cosmos-sdk/codec" + cdctypes "github.com/cosmos/cosmos-sdk/codec/types" + cryptocodec "github.com/cosmos/cosmos-sdk/crypto/codec" + sdk "github.com/cosmos/cosmos-sdk/types" +) + +var ( + Amino = codec.NewLegacyAmino() + AuthzModuleCdc = codec.NewAminoCodec(Amino) +) + +func init() { + cryptocodec.RegisterCrypto(Amino) + codec.RegisterEvidences(Amino) + sdk.RegisterLegacyAminoCodec(Amino) +} + +func TestMessageAuthzSerialization(t *testing.T, msg sdk.Msg) { + someDate := time.Date(1, 1, 1, 1, 1, 1, 1, time.UTC) + const ( + mockGranter string = "cosmos1abc" + mockGrantee string = "cosmos1xyz" + ) + + var ( + mockMsgGrant authz.MsgGrant + mockMsgRevoke authz.MsgRevoke + mockMsgExec authz.MsgExec + ) + + // Authz: Grant Msg + typeURL := sdk.MsgTypeURL(msg) + later := someDate.Add(time.Hour) + grant, err := authz.NewGrant(authz.NewGenericAuthorization(typeURL), later) + require.NoError(t, err) + + msgGrant := authz.MsgGrant{Granter: mockGranter, Grantee: mockGrantee, Grant: grant} + msgGrantBytes := json.RawMessage(sdk.MustSortJSON(AuthzModuleCdc.MustMarshalJSON(&msgGrant))) + err = AuthzModuleCdc.UnmarshalJSON(msgGrantBytes, &mockMsgGrant) + require.NoError(t, err) + + // Authz: Revoke Msg + msgRevoke := authz.MsgRevoke{Granter: mockGranter, Grantee: mockGrantee, MsgTypeUrl: typeURL} + msgRevokeByte := json.RawMessage(sdk.MustSortJSON(AuthzModuleCdc.MustMarshalJSON(&msgRevoke))) + err = AuthzModuleCdc.UnmarshalJSON(msgRevokeByte, &mockMsgRevoke) + require.NoError(t, err) + + // Authz: Exec Msg + msgAny, err := cdctypes.NewAnyWithValue(msg) + require.NoError(t, err) + msgExec := authz.MsgExec{Grantee: mockGrantee, Msgs: []*cdctypes.Any{msgAny}} + execMsgByte := json.RawMessage(sdk.MustSortJSON(AuthzModuleCdc.MustMarshalJSON(&msgExec))) + err = AuthzModuleCdc.UnmarshalJSON(execMsgByte, &mockMsgExec) + require.NoError(t, err) + require.Equal(t, msgExec.Msgs[0].Value, mockMsgExec.Msgs[0].Value) +} +*/ diff --git a/x/tokenfactory/types/authorityMetadata.go b/x/tokenfactory/types/authorityMetadata.go new file mode 100644 index 0000000..b45bffc --- /dev/null +++ b/x/tokenfactory/types/authorityMetadata.go @@ -0,0 +1,15 @@ +package types + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" +) + +func (metadata DenomAuthorityMetadata) Validate() error { + if metadata.Admin != "" { + _, err := sdk.AccAddressFromBech32(metadata.Admin) + if err != nil { + return err + } + } + return nil +} diff --git a/x/tokenfactory/types/authorityMetadata.pb.go b/x/tokenfactory/types/authorityMetadata.pb.go new file mode 100644 index 0000000..9241dde --- /dev/null +++ b/x/tokenfactory/types/authorityMetadata.pb.go @@ -0,0 +1,351 @@ +// Code generated by protoc-gen-gogo. DO NOT EDIT. +// source: osmosis/tokenfactory/v1beta1/authorityMetadata.proto + +package types + +import ( + fmt "fmt" + _ "github.com/cosmos/cosmos-sdk/types" + _ "github.com/gogo/protobuf/gogoproto" + proto "github.com/gogo/protobuf/proto" + io "io" + math "math" + math_bits "math/bits" +) + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the proto package it is being compiled against. +// A compilation error at this line likely means your copy of the +// proto package needs to be updated. +const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package + +// DenomAuthorityMetadata specifies metadata for addresses that have specific +// capabilities over a token factory denom. Right now there is only one Admin +// permission, but is planned to be extended to the future. +type DenomAuthorityMetadata struct { + // Can be empty for no admin, or a valid osmosis address + Admin string `protobuf:"bytes,1,opt,name=admin,proto3" json:"admin,omitempty" yaml:"admin"` +} + +func (m *DenomAuthorityMetadata) Reset() { *m = DenomAuthorityMetadata{} } +func (m *DenomAuthorityMetadata) String() string { return proto.CompactTextString(m) } +func (*DenomAuthorityMetadata) ProtoMessage() {} +func (*DenomAuthorityMetadata) Descriptor() ([]byte, []int) { + return fileDescriptor_99435de88ae175f7, []int{0} +} +func (m *DenomAuthorityMetadata) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *DenomAuthorityMetadata) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_DenomAuthorityMetadata.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *DenomAuthorityMetadata) XXX_Merge(src proto.Message) { + xxx_messageInfo_DenomAuthorityMetadata.Merge(m, src) +} +func (m *DenomAuthorityMetadata) XXX_Size() int { + return m.Size() +} +func (m *DenomAuthorityMetadata) XXX_DiscardUnknown() { + xxx_messageInfo_DenomAuthorityMetadata.DiscardUnknown(m) +} + +var xxx_messageInfo_DenomAuthorityMetadata proto.InternalMessageInfo + +func (m *DenomAuthorityMetadata) GetAdmin() string { + if m != nil { + return m.Admin + } + return "" +} + +func init() { + proto.RegisterType((*DenomAuthorityMetadata)(nil), "osmosis.tokenfactory.v1beta1.DenomAuthorityMetadata") +} + +func init() { + proto.RegisterFile("osmosis/tokenfactory/v1beta1/authorityMetadata.proto", fileDescriptor_99435de88ae175f7) +} + +var fileDescriptor_99435de88ae175f7 = []byte{ + // 239 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0x32, 0xc9, 0x2f, 0xce, 0xcd, + 0x2f, 0xce, 0x2c, 0xd6, 0x2f, 0xc9, 0xcf, 0x4e, 0xcd, 0x4b, 0x4b, 0x4c, 0x2e, 0xc9, 0x2f, 0xaa, + 0xd4, 0x2f, 0x33, 0x4c, 0x4a, 0x2d, 0x49, 0x34, 0xd4, 0x4f, 0x2c, 0x2d, 0xc9, 0xc8, 0x2f, 0xca, + 0x2c, 0xa9, 0xf4, 0x4d, 0x2d, 0x49, 0x4c, 0x49, 0x2c, 0x49, 0xd4, 0x2b, 0x28, 0xca, 0x2f, 0xc9, + 0x17, 0x92, 0x81, 0xea, 0xd2, 0x43, 0xd6, 0xa5, 0x07, 0xd5, 0x25, 0x25, 0x92, 0x9e, 0x9f, 0x9e, + 0x0f, 0x56, 0xa8, 0x0f, 0x62, 0x41, 0xf4, 0x48, 0xc9, 0x25, 0x83, 0x35, 0xe9, 0x27, 0x25, 0x16, + 0xa7, 0xc2, 0x2d, 0x48, 0xce, 0xcf, 0xcc, 0x83, 0xc8, 0x2b, 0xb9, 0x71, 0x89, 0xb9, 0xa4, 0xe6, + 0xe5, 0xe7, 0x3a, 0xa2, 0xdb, 0x29, 0xa4, 0xc6, 0xc5, 0x9a, 0x98, 0x92, 0x9b, 0x99, 0x27, 0xc1, + 0xa8, 0xc0, 0xa8, 0xc1, 0xe9, 0x24, 0xf0, 0xe9, 0x9e, 0x3c, 0x4f, 0x65, 0x62, 0x6e, 0x8e, 0x95, + 0x12, 0x58, 0x58, 0x29, 0x08, 0x22, 0x6d, 0xc5, 0xf2, 0x62, 0x81, 0x3c, 0xa3, 0x53, 0xd0, 0x89, + 0x47, 0x72, 0x8c, 0x17, 0x1e, 0xc9, 0x31, 0x3e, 0x78, 0x24, 0xc7, 0x38, 0xe1, 0xb1, 0x1c, 0xc3, + 0x85, 0xc7, 0x72, 0x0c, 0x37, 0x1e, 0xcb, 0x31, 0x44, 0x59, 0xa4, 0x67, 0x96, 0x64, 0x94, 0x26, + 0xe9, 0x25, 0xe7, 0xe7, 0xea, 0x43, 0x3d, 0xa0, 0x9b, 0x93, 0x98, 0x54, 0x0c, 0xe3, 0xe8, 0x97, + 0x19, 0x1a, 0xe9, 0x57, 0xa0, 0x86, 0x44, 0x49, 0x65, 0x41, 0x6a, 0x71, 0x12, 0x1b, 0xd8, 0x89, + 0xc6, 0x80, 0x00, 0x00, 0x00, 0xff, 0xff, 0x05, 0xcf, 0xa3, 0x03, 0x2e, 0x01, 0x00, 0x00, +} + +func (this *DenomAuthorityMetadata) Equal(that interface{}) bool { + if that == nil { + return this == nil + } + + that1, ok := that.(*DenomAuthorityMetadata) + if !ok { + that2, ok := that.(DenomAuthorityMetadata) + if ok { + that1 = &that2 + } else { + return false + } + } + if that1 == nil { + return this == nil + } else if this == nil { + return false + } + if this.Admin != that1.Admin { + return false + } + return true +} +func (m *DenomAuthorityMetadata) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *DenomAuthorityMetadata) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *DenomAuthorityMetadata) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.Admin) > 0 { + i -= len(m.Admin) + copy(dAtA[i:], m.Admin) + i = encodeVarintAuthorityMetadata(dAtA, i, uint64(len(m.Admin))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func encodeVarintAuthorityMetadata(dAtA []byte, offset int, v uint64) int { + offset -= sovAuthorityMetadata(v) + base := offset + for v >= 1<<7 { + dAtA[offset] = uint8(v&0x7f | 0x80) + v >>= 7 + offset++ + } + dAtA[offset] = uint8(v) + return base +} +func (m *DenomAuthorityMetadata) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.Admin) + if l > 0 { + n += 1 + l + sovAuthorityMetadata(uint64(l)) + } + return n +} + +func sovAuthorityMetadata(x uint64) (n int) { + return (math_bits.Len64(x|1) + 6) / 7 +} +func sozAuthorityMetadata(x uint64) (n int) { + return sovAuthorityMetadata(uint64((x << 1) ^ uint64((int64(x) >> 63)))) +} +func (m *DenomAuthorityMetadata) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowAuthorityMetadata + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: DenomAuthorityMetadata: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: DenomAuthorityMetadata: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Admin", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowAuthorityMetadata + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthAuthorityMetadata + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthAuthorityMetadata + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Admin = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipAuthorityMetadata(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthAuthorityMetadata + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func skipAuthorityMetadata(dAtA []byte) (n int, err error) { + l := len(dAtA) + iNdEx := 0 + depth := 0 + for iNdEx < l { + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowAuthorityMetadata + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + wireType := int(wire & 0x7) + switch wireType { + case 0: + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowAuthorityMetadata + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + iNdEx++ + if dAtA[iNdEx-1] < 0x80 { + break + } + } + case 1: + iNdEx += 8 + case 2: + var length int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowAuthorityMetadata + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + length |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if length < 0 { + return 0, ErrInvalidLengthAuthorityMetadata + } + iNdEx += length + case 3: + depth++ + case 4: + if depth == 0 { + return 0, ErrUnexpectedEndOfGroupAuthorityMetadata + } + depth-- + case 5: + iNdEx += 4 + default: + return 0, fmt.Errorf("proto: illegal wireType %d", wireType) + } + if iNdEx < 0 { + return 0, ErrInvalidLengthAuthorityMetadata + } + if depth == 0 { + return iNdEx, nil + } + } + return 0, io.ErrUnexpectedEOF +} + +var ( + ErrInvalidLengthAuthorityMetadata = fmt.Errorf("proto: negative length found during unmarshaling") + ErrIntOverflowAuthorityMetadata = fmt.Errorf("proto: integer overflow") + ErrUnexpectedEndOfGroupAuthorityMetadata = fmt.Errorf("proto: unexpected end of group") +) diff --git a/x/tokenfactory/types/authzcodec/codec.go b/x/tokenfactory/types/authzcodec/codec.go new file mode 100644 index 0000000..366e337 --- /dev/null +++ b/x/tokenfactory/types/authzcodec/codec.go @@ -0,0 +1,24 @@ +package authzcodec + +// Note: this file is a copy from authz/codec in 0.46 so we can be compatible with 0.45 + +import ( + "github.com/cosmos/cosmos-sdk/codec" + cryptocodec "github.com/cosmos/cosmos-sdk/crypto/codec" + sdk "github.com/cosmos/cosmos-sdk/types" +) + +var ( + Amino = codec.NewLegacyAmino() + ModuleCdc = codec.NewAminoCodec(Amino) +) + +func init() { + // Register all Amino interfaces and concrete types on the authz Amino codec so that this can later be + // used to properly serialize MsgGrant and MsgExec instances + sdk.RegisterLegacyAminoCodec(Amino) + cryptocodec.RegisterCrypto(Amino) + codec.RegisterEvidences(Amino) + + Amino.Seal() +} diff --git a/x/tokenfactory/types/codec.go b/x/tokenfactory/types/codec.go new file mode 100644 index 0000000..cb76899 --- /dev/null +++ b/x/tokenfactory/types/codec.go @@ -0,0 +1,48 @@ +package types + +import ( + "github.com/cosmos/cosmos-sdk/codec" + cdctypes "github.com/cosmos/cosmos-sdk/codec/types" + cryptocodec "github.com/cosmos/cosmos-sdk/crypto/codec" + sdk "github.com/cosmos/cosmos-sdk/types" + + // this line is used by starport scaffolding # 1 + "github.com/cosmos/cosmos-sdk/types/msgservice" +) + +func RegisterCodec(cdc *codec.LegacyAmino) { + cdc.RegisterConcrete(&MsgCreateDenom{}, "osmosis/tokenfactory/create-denom", nil) + cdc.RegisterConcrete(&MsgMint{}, "osmosis/tokenfactory/mint", nil) + cdc.RegisterConcrete(&MsgBurn{}, "osmosis/tokenfactory/burn", nil) + // cdc.RegisterConcrete(&MsgForceTransfer{}, "osmosis/tokenfactory/force-transfer", nil) + cdc.RegisterConcrete(&MsgChangeAdmin{}, "osmosis/tokenfactory/change-admin", nil) +} + +func RegisterInterfaces(registry cdctypes.InterfaceRegistry) { + registry.RegisterImplementations( + (*sdk.Msg)(nil), + &MsgCreateDenom{}, + &MsgMint{}, + &MsgBurn{}, + // &MsgForceTransfer{}, + &MsgChangeAdmin{}, + ) + msgservice.RegisterMsgServiceDesc(registry, &_Msg_serviceDesc) +} + +var ( + amino = codec.NewLegacyAmino() + ModuleCdc = codec.NewProtoCodec(cdctypes.NewInterfaceRegistry()) +) + +func init() { + RegisterCodec(amino) + // Register all Amino interfaces and concrete types on the authz Amino codec so that this can later be + // used to properly serialize MsgGrant and MsgExec instances + // Note: these 3 are inlines from authz/codec in 0.46 so we can be compatible with 0.45 + sdk.RegisterLegacyAminoCodec(amino) + cryptocodec.RegisterCrypto(amino) + codec.RegisterEvidences(amino) + + amino.Seal() +} diff --git a/x/tokenfactory/types/denoms.go b/x/tokenfactory/types/denoms.go new file mode 100644 index 0000000..7a9f2f9 --- /dev/null +++ b/x/tokenfactory/types/denoms.go @@ -0,0 +1,68 @@ +package types + +import ( + "strings" + + sdk "github.com/cosmos/cosmos-sdk/types" + sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" +) + +const ( + ModuleDenomPrefix = "factory" + // See the TokenFactory readme for a derivation of these. + // TL;DR, MaxSubdenomLength + MaxHrpLength = 60 comes from SDK max denom length = 128 + // and the structure of tokenfactory denoms. + MaxSubdenomLength = 44 + MaxHrpLength = 16 + // MaxCreatorLength = 59 + MaxHrpLength + MaxCreatorLength = 59 + MaxHrpLength +) + +// GetTokenDenom constructs a denom string for tokens created by tokenfactory +// based on an input creator address and a subdenom +// The denom constructed is factory/{creator}/{subdenom} +func GetTokenDenom(creator, subdenom string) (string, error) { + if len(subdenom) > MaxSubdenomLength { + return "", ErrSubdenomTooLong + } + if len(creator) > MaxCreatorLength { + return "", ErrCreatorTooLong + } + if strings.Contains(creator, "/") { + return "", ErrInvalidCreator + } + denom := strings.Join([]string{ModuleDenomPrefix, creator, subdenom}, "/") + return denom, sdk.ValidateDenom(denom) +} + +// DeconstructDenom takes a token denom string and verifies that it is a valid +// denom of the tokenfactory module, and is of the form `factory/{creator}/{subdenom}` +// If valid, it returns the creator address and subdenom +func DeconstructDenom(denom string) (creator string, subdenom string, err error) { + err = sdk.ValidateDenom(denom) + if err != nil { + return "", "", err + } + + strParts := strings.Split(denom, "/") + if len(strParts) < 3 { + return "", "", sdkerrors.Wrapf(ErrInvalidDenom, "not enough parts of denom %s", denom) + } + + if strParts[0] != ModuleDenomPrefix { + return "", "", sdkerrors.Wrapf(ErrInvalidDenom, "denom prefix is incorrect. Is: %s. Should be: %s", strParts[0], ModuleDenomPrefix) + } + + creator = strParts[1] + creatorAddr, err := sdk.AccAddressFromBech32(creator) + if err != nil { + return "", "", sdkerrors.Wrapf(ErrInvalidDenom, "Invalid creator address (%s)", err) + } + + // Handle the case where a denom has a slash in its subdenom. For example, + // when we did the split, we'd turn factory/accaddr/atomderivative/sikka into ["factory", "accaddr", "atomderivative", "sikka"] + // So we have to join [2:] with a "/" as the delimiter to get back the correct subdenom which should be "atomderivative/sikka" + subdenom = strings.Join(strParts[2:], "/") + + return creatorAddr.String(), subdenom, nil +} diff --git a/x/tokenfactory/types/denoms_test.go b/x/tokenfactory/types/denoms_test.go new file mode 100644 index 0000000..a619238 --- /dev/null +++ b/x/tokenfactory/types/denoms_test.go @@ -0,0 +1,132 @@ +package types_test + +import ( + "testing" + + "github.com/stretchr/testify/require" + + "github.com/dymensionxyz/rollapp-wasm/x/tokenfactory/types" +) + +func TestDeconstructDenom(t *testing.T) { + // Note: this seems to be used in osmosis to add some more checks (only 20 or 32 byte addresses), + // which is good, but not required for these tests as they make code less reuable + // appparams.SetAddressPrefixes() + + for _, tc := range []struct { + desc string + denom string + expectedSubdenom string + err error + }{ + { + desc: "empty is invalid", + denom: "", + err: types.ErrInvalidDenom, + }, + { + desc: "normal", + denom: "factory/cosmos1t7egva48prqmzl59x5ngv4zx0dtrwewcdqdjr8/bitcoin", + expectedSubdenom: "bitcoin", + }, + { + desc: "multiple slashes in subdenom", + denom: "factory/cosmos1t7egva48prqmzl59x5ngv4zx0dtrwewcdqdjr8/bitcoin/1", + expectedSubdenom: "bitcoin/1", + }, + { + desc: "no subdenom", + denom: "factory/cosmos1t7egva48prqmzl59x5ngv4zx0dtrwewcdqdjr8/", + expectedSubdenom: "", + }, + { + desc: "incorrect prefix", + denom: "ibc/cosmos1t7egva48prqmzl59x5ngv4zx0dtrwewcdqdjr8/bitcoin", + err: types.ErrInvalidDenom, + }, + { + desc: "subdenom of only slashes", + denom: "factory/cosmos1t7egva48prqmzl59x5ngv4zx0dtrwewcdqdjr8/////", + expectedSubdenom: "////", + }, + { + desc: "too long name", + denom: "factory/cosmos1t7egva48prqmzl59x5ngv4zx0dtrwewcdqdjr8/adsfadsfadsfadsfadsfadsfadsfadsfadsfadsfadsfadsfadsfadsfadsfadsfadsfadsfadsfadsfadsfadsfadsfadsfadsfadsfadsfadsfadsfadsfadsfadsfadsfadsfadsfadsfadsfadsfadsfadsfadsf", + err: types.ErrInvalidDenom, + }, + } { + t.Run(tc.desc, func(t *testing.T) { + expectedCreator := "cosmos1t7egva48prqmzl59x5ngv4zx0dtrwewcdqdjr8" + creator, subdenom, err := types.DeconstructDenom(tc.denom) + if tc.err != nil { + require.ErrorContains(t, err, tc.err.Error()) + } else { + require.NoError(t, err) + require.Equal(t, expectedCreator, creator) + require.Equal(t, tc.expectedSubdenom, subdenom) + } + }) + } +} + +func TestGetTokenDenom(t *testing.T) { + // appparams.SetAddressPrefixes() + for _, tc := range []struct { + desc string + creator string + subdenom string + valid bool + }{ + { + desc: "normal", + creator: "cosmos1t7egva48prqmzl59x5ngv4zx0dtrwewcdqdjr8", + subdenom: "bitcoin", + valid: true, + }, + { + desc: "multiple slashes in subdenom", + creator: "cosmos1t7egva48prqmzl59x5ngv4zx0dtrwewcdqdjr8", + subdenom: "bitcoin/1", + valid: true, + }, + { + desc: "no subdenom", + creator: "cosmos1t7egva48prqmzl59x5ngv4zx0dtrwewcdqdjr8", + subdenom: "", + valid: true, + }, + { + desc: "subdenom of only slashes", + creator: "cosmos1t7egva48prqmzl59x5ngv4zx0dtrwewcdqdjr8", + subdenom: "/////", + valid: true, + }, + { + desc: "too long name", + creator: "cosmos1t7egva48prqmzl59x5ngv4zx0dtrwewcdqdjr8", + subdenom: "adsfadsfadsfadsfadsfadsfadsfadsfadsfadsfadsfadsfadsfadsfadsfadsfadsfadsfadsfadsfadsfadsfadsfadsfadsfadsfadsfadsfadsfadsfadsfadsfadsfadsfadsfadsfadsfadsfadsfadsfadsf", + valid: false, + }, + { + desc: "subdenom is exactly max length", + creator: "cosmos1t7egva48prqmzl59x5ngv4zx0dtrwewcdqdjr8", + subdenom: "bitcoinfsadfsdfeadfsafwefsefsefsdfsdafasefsf", + valid: true, + }, + { + desc: "creator is exactly max length", + creator: "cosmos1t7egva48prqmzl59x5ngv4zx0dtrwewcdqdjr8jhgjhgkhjklhkjhkjhgjhgjgjghelu", + subdenom: "bitcoin", + valid: true, + }, + } { + t.Run(tc.desc, func(t *testing.T) { + _, err := types.GetTokenDenom(tc.creator, tc.subdenom) + if tc.valid { + require.NoError(t, err) + } else { + require.Error(t, err) + } + }) + } +} diff --git a/x/tokenfactory/types/errors.go b/x/tokenfactory/types/errors.go new file mode 100644 index 0000000..06991f1 --- /dev/null +++ b/x/tokenfactory/types/errors.go @@ -0,0 +1,22 @@ +package types + +// DONTCOVER + +import ( + fmt "fmt" + + sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" +) + +// x/tokenfactory module sentinel errors +var ( + ErrDenomExists = sdkerrors.Register(ModuleName, 2, "attempting to create a denom that already exists (has bank metadata)") + ErrUnauthorized = sdkerrors.Register(ModuleName, 3, "unauthorized account") + ErrInvalidDenom = sdkerrors.Register(ModuleName, 4, "invalid denom") + ErrInvalidCreator = sdkerrors.Register(ModuleName, 5, "invalid creator") + ErrInvalidAuthorityMetadata = sdkerrors.Register(ModuleName, 6, "invalid authority metadata") + ErrInvalidGenesis = sdkerrors.Register(ModuleName, 7, "invalid genesis") + ErrSubdenomTooLong = sdkerrors.Register(ModuleName, 8, fmt.Sprintf("subdenom too long, max length is %d bytes", MaxSubdenomLength)) + ErrCreatorTooLong = sdkerrors.Register(ModuleName, 9, fmt.Sprintf("creator too long, max length is %d bytes", MaxCreatorLength)) + ErrDenomDoesNotExist = sdkerrors.Register(ModuleName, 10, "denom does not exist") +) diff --git a/x/tokenfactory/types/events.go b/x/tokenfactory/types/events.go new file mode 100644 index 0000000..602e06f --- /dev/null +++ b/x/tokenfactory/types/events.go @@ -0,0 +1,18 @@ +package types + +// event types +// +//nolint:gosec +const ( + AttributeAmount = "amount" + AttributeCreator = "creator" + AttributeSubdenom = "subdenom" + AttributeNewTokenDenom = "new_token_denom" + AttributeMintToAddress = "mint_to_address" + AttributeBurnFromAddress = "burn_from_address" + AttributeTransferFromAddress = "transfer_from_address" + AttributeTransferToAddress = "transfer_to_address" + AttributeDenom = "denom" + AttributeNewAdmin = "new_admin" + AttributeDenomMetadata = "denom_metadata" +) diff --git a/x/tokenfactory/types/expected_keepers.go b/x/tokenfactory/types/expected_keepers.go new file mode 100644 index 0000000..15f377e --- /dev/null +++ b/x/tokenfactory/types/expected_keepers.go @@ -0,0 +1,36 @@ +package types + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" + authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" + banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" +) + +type BankKeeper interface { + // Methods imported from bank should be defined here + GetDenomMetaData(ctx sdk.Context, denom string) (banktypes.Metadata, bool) + SetDenomMetaData(ctx sdk.Context, denomMetaData banktypes.Metadata) + + HasSupply(ctx sdk.Context, denom string) bool + + SendCoinsFromModuleToAccount(ctx sdk.Context, senderModule string, recipientAddr sdk.AccAddress, amt sdk.Coins) error + SendCoinsFromAccountToModule(ctx sdk.Context, senderAddr sdk.AccAddress, recipientModule string, amt sdk.Coins) error + MintCoins(ctx sdk.Context, moduleName string, amt sdk.Coins) error + BurnCoins(ctx sdk.Context, moduleName string, amt sdk.Coins) error + + SendCoins(ctx sdk.Context, fromAddr sdk.AccAddress, toAddr sdk.AccAddress, amt sdk.Coins) error + HasBalance(ctx sdk.Context, addr sdk.AccAddress, amt sdk.Coin) bool + GetAllBalances(ctx sdk.Context, addr sdk.AccAddress) sdk.Coins + SpendableCoins(ctx sdk.Context, addr sdk.AccAddress) sdk.Coins + GetBalance(ctx sdk.Context, addr sdk.AccAddress, denom string) sdk.Coin +} + +type AccountKeeper interface { + SetModuleAccount(ctx sdk.Context, macc authtypes.ModuleAccountI) + GetAccount(ctx sdk.Context, addr sdk.AccAddress) authtypes.AccountI +} + +// CommunityPoolKeeper defines the contract needed to be fulfilled for community pool interactions. +type CommunityPoolKeeper interface { + FundCommunityPool(ctx sdk.Context, amount sdk.Coins, sender sdk.AccAddress) error +} diff --git a/x/tokenfactory/types/genesis.go b/x/tokenfactory/types/genesis.go new file mode 100644 index 0000000..b1ba181 --- /dev/null +++ b/x/tokenfactory/types/genesis.go @@ -0,0 +1,51 @@ +package types + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" + sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" +) + +// this line is used by starport scaffolding # genesis/types/import + +// DefaultIndex is the default capability global index +const DefaultIndex uint64 = 1 + +// DefaultGenesis returns the default Capability genesis state +func DefaultGenesis() *GenesisState { + return &GenesisState{ + Params: DefaultParams(), + FactoryDenoms: []GenesisDenom{}, + } +} + +// Validate performs basic genesis state validation returning an error upon any +// failure. +func (gs GenesisState) Validate() error { + err := gs.Params.Validate() + if err != nil { + return err + } + + seenDenoms := map[string]bool{} + + for _, denom := range gs.GetFactoryDenoms() { + if seenDenoms[denom.GetDenom()] { + return sdkerrors.Wrapf(ErrInvalidGenesis, "duplicate denom: %s", denom.GetDenom()) + } + seenDenoms[denom.GetDenom()] = true + + _, _, err := DeconstructDenom(denom.GetDenom()) + if err != nil { + return err + } + + if denom.AuthorityMetadata.Admin != "" { + _, err = sdk.AccAddressFromBech32(denom.AuthorityMetadata.Admin) + if err != nil { + return sdkerrors.Wrapf(ErrInvalidAuthorityMetadata, "Invalid admin address (%s)", err) + } + } + } + + return nil +} diff --git a/x/tokenfactory/types/genesis.pb.go b/x/tokenfactory/types/genesis.pb.go new file mode 100644 index 0000000..c067319 --- /dev/null +++ b/x/tokenfactory/types/genesis.pb.go @@ -0,0 +1,649 @@ +// Code generated by protoc-gen-gogo. DO NOT EDIT. +// source: osmosis/tokenfactory/v1beta1/genesis.proto + +package types + +import ( + fmt "fmt" + _ "github.com/gogo/protobuf/gogoproto" + proto "github.com/gogo/protobuf/proto" + io "io" + math "math" + math_bits "math/bits" +) + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the proto package it is being compiled against. +// A compilation error at this line likely means your copy of the +// proto package needs to be updated. +const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package + +// GenesisState defines the tokenfactory module's genesis state. +type GenesisState struct { + // params defines the paramaters of the module. + Params Params `protobuf:"bytes,1,opt,name=params,proto3" json:"params"` + FactoryDenoms []GenesisDenom `protobuf:"bytes,2,rep,name=factory_denoms,json=factoryDenoms,proto3" json:"factory_denoms" yaml:"factory_denoms"` +} + +func (m *GenesisState) Reset() { *m = GenesisState{} } +func (m *GenesisState) String() string { return proto.CompactTextString(m) } +func (*GenesisState) ProtoMessage() {} +func (*GenesisState) Descriptor() ([]byte, []int) { + return fileDescriptor_5749c3f71850298b, []int{0} +} +func (m *GenesisState) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *GenesisState) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_GenesisState.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *GenesisState) XXX_Merge(src proto.Message) { + xxx_messageInfo_GenesisState.Merge(m, src) +} +func (m *GenesisState) XXX_Size() int { + return m.Size() +} +func (m *GenesisState) XXX_DiscardUnknown() { + xxx_messageInfo_GenesisState.DiscardUnknown(m) +} + +var xxx_messageInfo_GenesisState proto.InternalMessageInfo + +func (m *GenesisState) GetParams() Params { + if m != nil { + return m.Params + } + return Params{} +} + +func (m *GenesisState) GetFactoryDenoms() []GenesisDenom { + if m != nil { + return m.FactoryDenoms + } + return nil +} + +// GenesisDenom defines a tokenfactory denom that is defined within genesis +// state. The structure contains DenomAuthorityMetadata which defines the +// denom's admin. +type GenesisDenom struct { + Denom string `protobuf:"bytes,1,opt,name=denom,proto3" json:"denom,omitempty" yaml:"denom"` + AuthorityMetadata DenomAuthorityMetadata `protobuf:"bytes,2,opt,name=authority_metadata,json=authorityMetadata,proto3" json:"authority_metadata" yaml:"authority_metadata"` +} + +func (m *GenesisDenom) Reset() { *m = GenesisDenom{} } +func (m *GenesisDenom) String() string { return proto.CompactTextString(m) } +func (*GenesisDenom) ProtoMessage() {} +func (*GenesisDenom) Descriptor() ([]byte, []int) { + return fileDescriptor_5749c3f71850298b, []int{1} +} +func (m *GenesisDenom) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *GenesisDenom) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_GenesisDenom.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *GenesisDenom) XXX_Merge(src proto.Message) { + xxx_messageInfo_GenesisDenom.Merge(m, src) +} +func (m *GenesisDenom) XXX_Size() int { + return m.Size() +} +func (m *GenesisDenom) XXX_DiscardUnknown() { + xxx_messageInfo_GenesisDenom.DiscardUnknown(m) +} + +var xxx_messageInfo_GenesisDenom proto.InternalMessageInfo + +func (m *GenesisDenom) GetDenom() string { + if m != nil { + return m.Denom + } + return "" +} + +func (m *GenesisDenom) GetAuthorityMetadata() DenomAuthorityMetadata { + if m != nil { + return m.AuthorityMetadata + } + return DenomAuthorityMetadata{} +} + +func init() { + proto.RegisterType((*GenesisState)(nil), "osmosis.tokenfactory.v1beta1.GenesisState") + proto.RegisterType((*GenesisDenom)(nil), "osmosis.tokenfactory.v1beta1.GenesisDenom") +} + +func init() { + proto.RegisterFile("osmosis/tokenfactory/v1beta1/genesis.proto", fileDescriptor_5749c3f71850298b) +} + +var fileDescriptor_5749c3f71850298b = []byte{ + // 365 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0xd2, 0xca, 0x2f, 0xce, 0xcd, + 0x2f, 0xce, 0x2c, 0xd6, 0x2f, 0xc9, 0xcf, 0x4e, 0xcd, 0x4b, 0x4b, 0x4c, 0x2e, 0xc9, 0x2f, 0xaa, + 0xd4, 0x2f, 0x33, 0x4c, 0x4a, 0x2d, 0x49, 0x34, 0xd4, 0x4f, 0x4f, 0xcd, 0x4b, 0x2d, 0xce, 0x2c, + 0xd6, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0x92, 0x81, 0xaa, 0xd5, 0x43, 0x56, 0xab, 0x07, 0x55, + 0x2b, 0x25, 0x92, 0x9e, 0x9f, 0x9e, 0x0f, 0x56, 0xa8, 0x0f, 0x62, 0x41, 0xf4, 0x48, 0x99, 0xe0, + 0x35, 0x3f, 0xb1, 0xb4, 0x24, 0x23, 0xbf, 0x28, 0xb3, 0xa4, 0xd2, 0x37, 0xb5, 0x24, 0x31, 0x25, + 0xb1, 0x24, 0x11, 0xaa, 0x4b, 0x13, 0xaf, 0xae, 0x82, 0xc4, 0xa2, 0xc4, 0x5c, 0xa8, 0xa3, 0x94, + 0x8e, 0x30, 0x72, 0xf1, 0xb8, 0x43, 0x9c, 0x19, 0x5c, 0x92, 0x58, 0x92, 0x2a, 0xe4, 0xc4, 0xc5, + 0x06, 0x51, 0x20, 0xc1, 0xa8, 0xc0, 0xa8, 0xc1, 0x6d, 0xa4, 0xa2, 0x87, 0xcf, 0xd9, 0x7a, 0x01, + 0x60, 0xb5, 0x4e, 0x2c, 0x27, 0xee, 0xc9, 0x33, 0x04, 0x41, 0x75, 0x0a, 0x15, 0x70, 0xf1, 0x41, + 0xd5, 0xc5, 0xa7, 0xa4, 0xe6, 0xe5, 0xe7, 0x16, 0x4b, 0x30, 0x29, 0x30, 0x6b, 0x70, 0x1b, 0x69, + 0xe1, 0x37, 0x0b, 0xea, 0x0e, 0x17, 0x90, 0x16, 0x27, 0x59, 0x90, 0x89, 0x9f, 0xee, 0xc9, 0x8b, + 0x56, 0x26, 0xe6, 0xe6, 0x58, 0x29, 0xa1, 0x9a, 0xa7, 0x14, 0xc4, 0x0b, 0x15, 0x70, 0x81, 0xf0, + 0x8f, 0x22, 0xbc, 0x01, 0x16, 0x11, 0x52, 0xe3, 0x62, 0x05, 0x2b, 0x05, 0xfb, 0x82, 0xd3, 0x49, + 0xe0, 0xd3, 0x3d, 0x79, 0x1e, 0x88, 0x49, 0x60, 0x61, 0xa5, 0x20, 0x88, 0xb4, 0x50, 0x1b, 0x23, + 0x97, 0x10, 0x3c, 0x18, 0xe3, 0x73, 0xa1, 0xe1, 0x28, 0xc1, 0x04, 0xf6, 0xbb, 0x09, 0x7e, 0xf7, + 0x82, 0x6d, 0x72, 0x44, 0x8f, 0x03, 0x27, 0x45, 0xa8, 0xcb, 0x25, 0x21, 0xf6, 0x61, 0x9a, 0xae, + 0x14, 0x24, 0x88, 0x11, 0x73, 0x56, 0x2c, 0x2f, 0x16, 0xc8, 0x33, 0x3a, 0x05, 0x9d, 0x78, 0x24, + 0xc7, 0x78, 0xe1, 0x91, 0x1c, 0xe3, 0x83, 0x47, 0x72, 0x8c, 0x13, 0x1e, 0xcb, 0x31, 0x5c, 0x78, + 0x2c, 0xc7, 0x70, 0xe3, 0xb1, 0x1c, 0x43, 0x94, 0x45, 0x7a, 0x66, 0x49, 0x46, 0x69, 0x92, 0x5e, + 0x72, 0x7e, 0xae, 0x3e, 0xd4, 0x55, 0xba, 0x39, 0x89, 0x49, 0xc5, 0x30, 0x8e, 0x7e, 0x99, 0xa1, + 0x91, 0x7e, 0x05, 0x6a, 0x8c, 0x97, 0x54, 0x16, 0xa4, 0x16, 0x27, 0xb1, 0x81, 0x63, 0xda, 0x18, + 0x10, 0x00, 0x00, 0xff, 0xff, 0x16, 0xe9, 0x52, 0x6a, 0xac, 0x02, 0x00, 0x00, +} + +func (this *GenesisDenom) Equal(that interface{}) bool { + if that == nil { + return this == nil + } + + that1, ok := that.(*GenesisDenom) + if !ok { + that2, ok := that.(GenesisDenom) + if ok { + that1 = &that2 + } else { + return false + } + } + if that1 == nil { + return this == nil + } else if this == nil { + return false + } + if this.Denom != that1.Denom { + return false + } + if !this.AuthorityMetadata.Equal(&that1.AuthorityMetadata) { + return false + } + return true +} +func (m *GenesisState) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *GenesisState) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *GenesisState) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.FactoryDenoms) > 0 { + for iNdEx := len(m.FactoryDenoms) - 1; iNdEx >= 0; iNdEx-- { + { + size, err := m.FactoryDenoms[iNdEx].MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintGenesis(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x12 + } + } + { + size, err := m.Params.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintGenesis(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0xa + return len(dAtA) - i, nil +} + +func (m *GenesisDenom) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *GenesisDenom) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *GenesisDenom) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + { + size, err := m.AuthorityMetadata.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintGenesis(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x12 + if len(m.Denom) > 0 { + i -= len(m.Denom) + copy(dAtA[i:], m.Denom) + i = encodeVarintGenesis(dAtA, i, uint64(len(m.Denom))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func encodeVarintGenesis(dAtA []byte, offset int, v uint64) int { + offset -= sovGenesis(v) + base := offset + for v >= 1<<7 { + dAtA[offset] = uint8(v&0x7f | 0x80) + v >>= 7 + offset++ + } + dAtA[offset] = uint8(v) + return base +} +func (m *GenesisState) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = m.Params.Size() + n += 1 + l + sovGenesis(uint64(l)) + if len(m.FactoryDenoms) > 0 { + for _, e := range m.FactoryDenoms { + l = e.Size() + n += 1 + l + sovGenesis(uint64(l)) + } + } + return n +} + +func (m *GenesisDenom) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.Denom) + if l > 0 { + n += 1 + l + sovGenesis(uint64(l)) + } + l = m.AuthorityMetadata.Size() + n += 1 + l + sovGenesis(uint64(l)) + return n +} + +func sovGenesis(x uint64) (n int) { + return (math_bits.Len64(x|1) + 6) / 7 +} +func sozGenesis(x uint64) (n int) { + return sovGenesis(uint64((x << 1) ^ uint64((int64(x) >> 63)))) +} +func (m *GenesisState) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenesis + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: GenesisState: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: GenesisState: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Params", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenesis + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthGenesis + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthGenesis + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.Params.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field FactoryDenoms", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenesis + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthGenesis + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthGenesis + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.FactoryDenoms = append(m.FactoryDenoms, GenesisDenom{}) + if err := m.FactoryDenoms[len(m.FactoryDenoms)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipGenesis(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthGenesis + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *GenesisDenom) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenesis + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: GenesisDenom: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: GenesisDenom: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Denom", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenesis + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthGenesis + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthGenesis + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Denom = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field AuthorityMetadata", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenesis + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthGenesis + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthGenesis + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.AuthorityMetadata.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipGenesis(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthGenesis + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func skipGenesis(dAtA []byte) (n int, err error) { + l := len(dAtA) + iNdEx := 0 + depth := 0 + for iNdEx < l { + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowGenesis + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + wireType := int(wire & 0x7) + switch wireType { + case 0: + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowGenesis + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + iNdEx++ + if dAtA[iNdEx-1] < 0x80 { + break + } + } + case 1: + iNdEx += 8 + case 2: + var length int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowGenesis + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + length |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if length < 0 { + return 0, ErrInvalidLengthGenesis + } + iNdEx += length + case 3: + depth++ + case 4: + if depth == 0 { + return 0, ErrUnexpectedEndOfGroupGenesis + } + depth-- + case 5: + iNdEx += 4 + default: + return 0, fmt.Errorf("proto: illegal wireType %d", wireType) + } + if iNdEx < 0 { + return 0, ErrInvalidLengthGenesis + } + if depth == 0 { + return iNdEx, nil + } + } + return 0, io.ErrUnexpectedEOF +} + +var ( + ErrInvalidLengthGenesis = fmt.Errorf("proto: negative length found during unmarshaling") + ErrIntOverflowGenesis = fmt.Errorf("proto: integer overflow") + ErrUnexpectedEndOfGroupGenesis = fmt.Errorf("proto: unexpected end of group") +) diff --git a/x/tokenfactory/types/genesis_test.go b/x/tokenfactory/types/genesis_test.go new file mode 100644 index 0000000..6fdba7c --- /dev/null +++ b/x/tokenfactory/types/genesis_test.go @@ -0,0 +1,139 @@ +package types_test + +import ( + "testing" + + "github.com/stretchr/testify/require" + + "github.com/dymensionxyz/rollapp-wasm/x/tokenfactory/types" +) + +func TestGenesisState_Validate(t *testing.T) { + for _, tc := range []struct { + desc string + genState *types.GenesisState + valid bool + }{ + { + desc: "default is valid", + genState: types.DefaultGenesis(), + valid: true, + }, + { + desc: "valid genesis state", + genState: &types.GenesisState{ + FactoryDenoms: []types.GenesisDenom{ + { + Denom: "factory/cosmos1t7egva48prqmzl59x5ngv4zx0dtrwewcdqdjr8/bitcoin", + AuthorityMetadata: types.DenomAuthorityMetadata{ + Admin: "cosmos1t7egva48prqmzl59x5ngv4zx0dtrwewcdqdjr8", + }, + }, + }, + }, + valid: true, + }, + { + desc: "different admin from creator", + genState: &types.GenesisState{ + FactoryDenoms: []types.GenesisDenom{ + { + Denom: "factory/cosmos1t7egva48prqmzl59x5ngv4zx0dtrwewcdqdjr8/bitcoin", + AuthorityMetadata: types.DenomAuthorityMetadata{ + Admin: "cosmos1ft6e5esdtdegnvcr3djd3ftk4kwpcr6jta8eyh", + }, + }, + }, + }, + valid: true, + }, + { + desc: "empty admin", + genState: &types.GenesisState{ + FactoryDenoms: []types.GenesisDenom{ + { + Denom: "factory/cosmos1t7egva48prqmzl59x5ngv4zx0dtrwewcdqdjr8/bitcoin", + AuthorityMetadata: types.DenomAuthorityMetadata{ + Admin: "", + }, + }, + }, + }, + valid: true, + }, + { + desc: "no admin", + genState: &types.GenesisState{ + FactoryDenoms: []types.GenesisDenom{ + { + Denom: "factory/cosmos1t7egva48prqmzl59x5ngv4zx0dtrwewcdqdjr8/bitcoin", + }, + }, + }, + valid: true, + }, + { + desc: "invalid admin", + genState: &types.GenesisState{ + FactoryDenoms: []types.GenesisDenom{ + { + Denom: "factory/cosmos1t7egva48prqmzl59x5ngv4zx0dtrwewcdqdjr8/bitcoin", + AuthorityMetadata: types.DenomAuthorityMetadata{ + Admin: "moose", + }, + }, + }, + }, + valid: false, + }, + { + desc: "multiple denoms", + genState: &types.GenesisState{ + FactoryDenoms: []types.GenesisDenom{ + { + Denom: "factory/cosmos1t7egva48prqmzl59x5ngv4zx0dtrwewcdqdjr8/bitcoin", + AuthorityMetadata: types.DenomAuthorityMetadata{ + Admin: "", + }, + }, + { + Denom: "factory/cosmos1t7egva48prqmzl59x5ngv4zx0dtrwewcdqdjr8/litecoin", + AuthorityMetadata: types.DenomAuthorityMetadata{ + Admin: "", + }, + }, + }, + }, + valid: true, + }, + { + desc: "duplicate denoms", + genState: &types.GenesisState{ + FactoryDenoms: []types.GenesisDenom{ + { + Denom: "factory/cosmos1t7egva48prqmzl59x5ngv4zx0dtrwewcdqdjr8/bitcoin", + AuthorityMetadata: types.DenomAuthorityMetadata{ + Admin: "", + }, + }, + { + Denom: "factory/cosmos1t7egva48prqmzl59x5ngv4zx0dtrwewcdqdjr8/bitcoin", + AuthorityMetadata: types.DenomAuthorityMetadata{ + Admin: "", + }, + }, + }, + }, + valid: false, + }, + } { + t.Run(tc.desc, func(t *testing.T) { + err := tc.genState.Validate() + if tc.valid { + require.NoError(t, err) + } else { + require.Error(t, err) + } + }) + } +} diff --git a/x/tokenfactory/types/keys.go b/x/tokenfactory/types/keys.go new file mode 100644 index 0000000..fac4a6e --- /dev/null +++ b/x/tokenfactory/types/keys.go @@ -0,0 +1,49 @@ +package types + +import ( + "strings" +) + +const ( + // ModuleName defines the module name + ModuleName = "tokenfactory" + + // StoreKey defines the primary module store key + StoreKey = ModuleName + + // RouterKey is the message route for slashing + RouterKey = ModuleName + + // QuerierRoute defines the module's query routing key + QuerierRoute = ModuleName + + // MemStoreKey defines the in-memory store key + MemStoreKey = "mem_tokenfactory" +) + +// KeySeparator is used to combine parts of the keys in the store +const KeySeparator = "|" + +var ( + DenomAuthorityMetadataKey = "authoritymetadata" + DenomsPrefixKey = "denoms" + CreatorPrefixKey = "creator" + AdminPrefixKey = "admin" +) + +// GetDenomPrefixStore returns the store prefix where all the data associated with a specific denom +// is stored +func GetDenomPrefixStore(denom string) []byte { + return []byte(strings.Join([]string{DenomsPrefixKey, denom, ""}, KeySeparator)) +} + +// GetCreatorsPrefix returns the store prefix where the list of the denoms created by a specific +// creator are stored +func GetCreatorPrefix(creator string) []byte { + return []byte(strings.Join([]string{CreatorPrefixKey, creator, ""}, KeySeparator)) +} + +// GetCreatorsPrefix returns the store prefix where a list of all creator addresses are stored +func GetCreatorsPrefix() []byte { + return []byte(strings.Join([]string{CreatorPrefixKey, ""}, KeySeparator)) +} diff --git a/x/tokenfactory/types/msgs.go b/x/tokenfactory/types/msgs.go new file mode 100644 index 0000000..9c78328 --- /dev/null +++ b/x/tokenfactory/types/msgs.go @@ -0,0 +1,246 @@ +package types + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" + sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" + banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" +) + +// constants +const ( + TypeMsgCreateDenom = "create_denom" + TypeMsgMint = "tf_mint" + TypeMsgBurn = "tf_burn" + TypeMsgForceTransfer = "force_transfer" + TypeMsgChangeAdmin = "change_admin" + TypeMsgSetDenomMetadata = "set_denom_metadata" +) + +var _ sdk.Msg = &MsgCreateDenom{} + +// NewMsgCreateDenom creates a msg to create a new denom +func NewMsgCreateDenom(sender, subdenom string) *MsgCreateDenom { + return &MsgCreateDenom{ + Sender: sender, + Subdenom: subdenom, + } +} + +func (m MsgCreateDenom) Route() string { return RouterKey } +func (m MsgCreateDenom) Type() string { return TypeMsgCreateDenom } +func (m MsgCreateDenom) ValidateBasic() error { + _, err := sdk.AccAddressFromBech32(m.Sender) + if err != nil { + return sdkerrors.Wrapf(sdkerrors.ErrInvalidAddress, "Invalid sender address (%s)", err) + } + + _, err = GetTokenDenom(m.Sender, m.Subdenom) + if err != nil { + return sdkerrors.Wrap(ErrInvalidDenom, err.Error()) + } + + return nil +} + +func (m MsgCreateDenom) GetSignBytes() []byte { + return sdk.MustSortJSON(ModuleCdc.MustMarshalJSON(&m)) +} + +func (m MsgCreateDenom) GetSigners() []sdk.AccAddress { + sender, _ := sdk.AccAddressFromBech32(m.Sender) + return []sdk.AccAddress{sender} +} + +var _ sdk.Msg = &MsgMint{} + +// NewMsgMint creates a message to mint tokens +func NewMsgMint(sender string, amount sdk.Coin) *MsgMint { + return &MsgMint{ + Sender: sender, + Amount: amount, + } +} + +func (m MsgMint) Route() string { return RouterKey } +func (m MsgMint) Type() string { return TypeMsgMint } +func (m MsgMint) ValidateBasic() error { + _, err := sdk.AccAddressFromBech32(m.Sender) + if err != nil { + return sdkerrors.Wrapf(sdkerrors.ErrInvalidAddress, "Invalid sender address (%s)", err) + } + + if !m.Amount.IsValid() || m.Amount.Amount.Equal(sdk.ZeroInt()) { + return sdkerrors.Wrap(sdkerrors.ErrInvalidCoins, m.Amount.String()) + } + + return nil +} + +func (m MsgMint) GetSignBytes() []byte { + return sdk.MustSortJSON(ModuleCdc.MustMarshalJSON(&m)) +} + +func (m MsgMint) GetSigners() []sdk.AccAddress { + sender, _ := sdk.AccAddressFromBech32(m.Sender) + return []sdk.AccAddress{sender} +} + +var _ sdk.Msg = &MsgBurn{} + +// NewMsgBurn creates a message to burn tokens +func NewMsgBurn(sender string, amount sdk.Coin) *MsgBurn { + return &MsgBurn{ + Sender: sender, + Amount: amount, + } +} + +func (m MsgBurn) Route() string { return RouterKey } +func (m MsgBurn) Type() string { return TypeMsgBurn } +func (m MsgBurn) ValidateBasic() error { + _, err := sdk.AccAddressFromBech32(m.Sender) + if err != nil { + return sdkerrors.Wrapf(sdkerrors.ErrInvalidAddress, "Invalid sender address (%s)", err) + } + + if !m.Amount.IsValid() || m.Amount.Amount.Equal(sdk.ZeroInt()) { + return sdkerrors.Wrap(sdkerrors.ErrInvalidCoins, m.Amount.String()) + } + + return nil +} + +func (m MsgBurn) GetSignBytes() []byte { + return sdk.MustSortJSON(ModuleCdc.MustMarshalJSON(&m)) +} + +func (m MsgBurn) GetSigners() []sdk.AccAddress { + sender, _ := sdk.AccAddressFromBech32(m.Sender) + return []sdk.AccAddress{sender} +} + +// var _ sdk.Msg = &MsgForceTransfer{} + +// // NewMsgForceTransfer creates a transfer funds from one account to another +// func NewMsgForceTransfer(sender string, amount sdk.Coin, fromAddr, toAddr string) *MsgForceTransfer { +// return &MsgForceTransfer{ +// Sender: sender, +// Amount: amount, +// TransferFromAddress: fromAddr, +// TransferToAddress: toAddr, +// } +// } + +// func (m MsgForceTransfer) Route() string { return RouterKey } +// func (m MsgForceTransfer) Type() string { return TypeMsgForceTransfer } +// func (m MsgForceTransfer) ValidateBasic() error { +// _, err := sdk.AccAddressFromBech32(m.Sender) +// if err != nil { +// return sdkerrors.Wrapf(sdkerrors.ErrInvalidAddress, "Invalid sender address (%s)", err) +// } + +// _, err = sdk.AccAddressFromBech32(m.TransferFromAddress) +// if err != nil { +// return sdkerrors.Wrapf(sdkerrors.ErrInvalidAddress, "Invalid address (%s)", err) +// } +// _, err = sdk.AccAddressFromBech32(m.TransferToAddress) +// if err != nil { +// return sdkerrors.Wrapf(sdkerrors.ErrInvalidAddress, "Invalid address (%s)", err) +// } + +// if !m.Amount.IsValid() { +// return sdkerrors.Wrap(sdkerrors.ErrInvalidCoins, m.Amount.String()) +// } + +// return nil +// } + +// func (m MsgForceTransfer) GetSignBytes() []byte { +// return sdk.MustSortJSON(ModuleCdc.MustMarshalJSON(&m)) +// } + +// func (m MsgForceTransfer) GetSigners() []sdk.AccAddress { +// sender, _ := sdk.AccAddressFromBech32(m.Sender) +// return []sdk.AccAddress{sender} +// } + +var _ sdk.Msg = &MsgChangeAdmin{} + +// NewMsgChangeAdmin creates a message to burn tokens +func NewMsgChangeAdmin(sender, denom, newAdmin string) *MsgChangeAdmin { + return &MsgChangeAdmin{ + Sender: sender, + Denom: denom, + NewAdmin: newAdmin, + } +} + +func (m MsgChangeAdmin) Route() string { return RouterKey } +func (m MsgChangeAdmin) Type() string { return TypeMsgChangeAdmin } +func (m MsgChangeAdmin) ValidateBasic() error { + _, err := sdk.AccAddressFromBech32(m.Sender) + if err != nil { + return sdkerrors.Wrapf(sdkerrors.ErrInvalidAddress, "Invalid sender address (%s)", err) + } + + _, err = sdk.AccAddressFromBech32(m.NewAdmin) + if err != nil { + return sdkerrors.Wrapf(sdkerrors.ErrInvalidAddress, "Invalid address (%s)", err) + } + + _, _, err = DeconstructDenom(m.Denom) + if err != nil { + return err + } + + return nil +} + +func (m MsgChangeAdmin) GetSignBytes() []byte { + return sdk.MustSortJSON(ModuleCdc.MustMarshalJSON(&m)) +} + +func (m MsgChangeAdmin) GetSigners() []sdk.AccAddress { + sender, _ := sdk.AccAddressFromBech32(m.Sender) + return []sdk.AccAddress{sender} +} + +var _ sdk.Msg = &MsgSetDenomMetadata{} + +// NewMsgChangeAdmin creates a message to burn tokens +func NewMsgSetDenomMetadata(sender string, metadata banktypes.Metadata) *MsgSetDenomMetadata { + return &MsgSetDenomMetadata{ + Sender: sender, + Metadata: metadata, + } +} + +func (m MsgSetDenomMetadata) Route() string { return RouterKey } +func (m MsgSetDenomMetadata) Type() string { return TypeMsgSetDenomMetadata } +func (m MsgSetDenomMetadata) ValidateBasic() error { + _, err := sdk.AccAddressFromBech32(m.Sender) + if err != nil { + return sdkerrors.Wrapf(sdkerrors.ErrInvalidAddress, "Invalid sender address (%s)", err) + } + + err = m.Metadata.Validate() + if err != nil { + return err + } + + _, _, err = DeconstructDenom(m.Metadata.Base) + if err != nil { + return err + } + + return nil +} + +func (m MsgSetDenomMetadata) GetSignBytes() []byte { + return sdk.MustSortJSON(ModuleCdc.MustMarshalJSON(&m)) +} + +func (m MsgSetDenomMetadata) GetSigners() []sdk.AccAddress { + sender, _ := sdk.AccAddressFromBech32(m.Sender) + return []sdk.AccAddress{sender} +} diff --git a/x/tokenfactory/types/msgs_test.go b/x/tokenfactory/types/msgs_test.go new file mode 100644 index 0000000..1d9cfdc --- /dev/null +++ b/x/tokenfactory/types/msgs_test.go @@ -0,0 +1,452 @@ +package types_test + +/*import ( + fmt "fmt" + "testing" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/stretchr/testify/require" + + "github.com/dymensionxyz/rollapp-wasm/x/tokenfactory/testhelpers" + "github.com/dymensionxyz/rollapp-wasm/x/tokenfactory/types" + + banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" + "github.com/tendermint/tendermint/crypto/ed25519" +) + +// // Test authz serialize and de-serializes for tokenfactory msg. +func TestAuthzMsg(t *testing.T) { + t.Skip("TODO: figure out how to register authz interfaces for tests") + pk1 := ed25519.GenPrivKey().PubKey() + addr1 := sdk.AccAddress(pk1.Address()).String() + coin := sdk.NewCoin("denom", sdk.NewInt(1)) + + testCases := []struct { + name string + msg sdk.Msg + }{ + { + name: "MsgCreateDenom", + msg: &types.MsgCreateDenom{ + Sender: addr1, + Subdenom: "valoper1xyz", + }, + }, + { + name: "MsgBurn", + msg: &types.MsgBurn{ + Sender: addr1, + Amount: coin, + }, + }, + { + name: "MsgMint", + msg: &types.MsgMint{ + Sender: addr1, + Amount: coin, + }, + }, + { + name: "MsgChangeAdmin", + msg: &types.MsgChangeAdmin{ + Sender: addr1, + Denom: "denom", + NewAdmin: "osmo1q8tq5qhrhw6t970egemuuwywhlhpnmdmts6xnu", + }, + }, + } + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + testhelpers.TestMessageAuthzSerialization(t, tc.msg) + }) + } +} + +// TestMsgCreateDenom tests if valid/invalid create denom messages are properly validated/invalidated +func TestMsgCreateDenom(t *testing.T) { + // generate a private/public key pair and get the respective address + pk1 := ed25519.GenPrivKey().PubKey() + addr1 := sdk.AccAddress(pk1.Address()) + + // make a proper createDenom message + createMsg := func(after func(msg types.MsgCreateDenom) types.MsgCreateDenom) types.MsgCreateDenom { + properMsg := *types.NewMsgCreateDenom( + addr1.String(), + "bitcoin", + ) + + return after(properMsg) + } + + // validate createDenom message was created as intended + msg := createMsg(func(msg types.MsgCreateDenom) types.MsgCreateDenom { + return msg + }) + require.Equal(t, msg.Route(), types.RouterKey) + require.Equal(t, msg.Type(), "create_denom") + signers := msg.GetSigners() + require.Equal(t, len(signers), 1) + require.Equal(t, signers[0].String(), addr1.String()) + + tests := []struct { + name string + msg types.MsgCreateDenom + expectPass bool + }{ + { + name: "proper msg", + msg: createMsg(func(msg types.MsgCreateDenom) types.MsgCreateDenom { + return msg + }), + expectPass: true, + }, + { + name: "empty sender", + msg: createMsg(func(msg types.MsgCreateDenom) types.MsgCreateDenom { + msg.Sender = "" + return msg + }), + expectPass: false, + }, + { + name: "invalid subdenom", + msg: createMsg(func(msg types.MsgCreateDenom) types.MsgCreateDenom { + msg.Subdenom = "thissubdenomismuchtoolongasdkfjaasdfdsafsdlkfnmlksadmflksmdlfmlsakmfdsafasdfasdf" + return msg + }), + expectPass: false, + }, + } + + for _, test := range tests { + if test.expectPass { + require.NoError(t, test.msg.ValidateBasic(), "test: %v", test.name) + } else { + require.Error(t, test.msg.ValidateBasic(), "test: %v", test.name) + } + } +} + +// TestMsgMint tests if valid/invalid create denom messages are properly validated/invalidated +func TestMsgMint(t *testing.T) { + // generate a private/public key pair and get the respective address + pk1 := ed25519.GenPrivKey().PubKey() + addr1 := sdk.AccAddress(pk1.Address()) + + // make a proper mint message + createMsg := func(after func(msg types.MsgMint) types.MsgMint) types.MsgMint { + properMsg := *types.NewMsgMint( + addr1.String(), + sdk.NewCoin("bitcoin", sdk.NewInt(500000000)), + ) + + return after(properMsg) + } + + // validate mint message was created as intended + msg := createMsg(func(msg types.MsgMint) types.MsgMint { + return msg + }) + require.Equal(t, msg.Route(), types.RouterKey) + require.Equal(t, msg.Type(), "tf_mint") + signers := msg.GetSigners() + require.Equal(t, len(signers), 1) + require.Equal(t, signers[0].String(), addr1.String()) + + tests := []struct { + name string + msg types.MsgMint + expectPass bool + }{ + { + name: "proper msg", + msg: createMsg(func(msg types.MsgMint) types.MsgMint { + return msg + }), + expectPass: true, + }, + { + name: "empty sender", + msg: createMsg(func(msg types.MsgMint) types.MsgMint { + msg.Sender = "" + return msg + }), + expectPass: false, + }, + { + name: "zero amount", + msg: createMsg(func(msg types.MsgMint) types.MsgMint { + msg.Amount = sdk.NewCoin("bitcoin", sdk.ZeroInt()) + return msg + }), + expectPass: false, + }, + { + name: "negative amount", + msg: createMsg(func(msg types.MsgMint) types.MsgMint { + msg.Amount.Amount = sdk.NewInt(-10000000) + return msg + }), + expectPass: false, + }, + } + + for _, test := range tests { + if test.expectPass { + require.NoError(t, test.msg.ValidateBasic(), "test: %v", test.name) + } else { + require.Error(t, test.msg.ValidateBasic(), "test: %v", test.name) + } + } +} + +// TestMsgBurn tests if valid/invalid create denom messages are properly validated/invalidated +func TestMsgBurn(t *testing.T) { + // generate a private/public key pair and get the respective address + pk1 := ed25519.GenPrivKey().PubKey() + addr1 := sdk.AccAddress(pk1.Address()) + + // make a proper burn message + baseMsg := types.NewMsgBurn( + addr1.String(), + sdk.NewCoin("bitcoin", sdk.NewInt(500000000)), + ) + + // validate burn message was created as intended + require.Equal(t, baseMsg.Route(), types.RouterKey) + require.Equal(t, baseMsg.Type(), "tf_burn") + signers := baseMsg.GetSigners() + require.Equal(t, len(signers), 1) + require.Equal(t, signers[0].String(), addr1.String()) + + tests := []struct { + name string + msg func() *types.MsgBurn + expectPass bool + }{ + { + name: "proper msg", + msg: func() *types.MsgBurn { + msg := baseMsg + return msg + }, + expectPass: true, + }, + { + name: "empty sender", + msg: func() *types.MsgBurn { + msg := baseMsg + msg.Sender = "" + return msg + }, + expectPass: false, + }, + { + name: "zero amount", + msg: func() *types.MsgBurn { + msg := baseMsg + msg.Amount.Amount = sdk.ZeroInt() + return msg + }, + expectPass: false, + }, + { + name: "negative amount", + msg: func() *types.MsgBurn { + msg := baseMsg + msg.Amount.Amount = sdk.NewInt(-10000000) + return msg + }, + expectPass: false, + }, + } + + for _, test := range tests { + if test.expectPass { + require.NoError(t, test.msg().ValidateBasic(), "test: %v", test.name) + } else { + require.Error(t, test.msg().ValidateBasic(), "test: %v", test.name) + } + } +} + +// TestMsgChangeAdmin tests if valid/invalid create denom messages are properly validated/invalidated +func TestMsgChangeAdmin(t *testing.T) { + // generate a private/public key pair and get the respective address + pk1 := ed25519.GenPrivKey().PubKey() + addr1 := sdk.AccAddress(pk1.Address()) + pk2 := ed25519.GenPrivKey().PubKey() + addr2 := sdk.AccAddress(pk2.Address()) + tokenFactoryDenom := fmt.Sprintf("factory/%s/bitcoin", addr1.String()) + + // make a proper changeAdmin message + baseMsg := types.NewMsgChangeAdmin( + addr1.String(), + tokenFactoryDenom, + addr2.String(), + ) + + // validate changeAdmin message was created as intended + require.Equal(t, baseMsg.Route(), types.RouterKey) + require.Equal(t, baseMsg.Type(), "change_admin") + signers := baseMsg.GetSigners() + require.Equal(t, len(signers), 1) + require.Equal(t, signers[0].String(), addr1.String()) + + tests := []struct { + name string + msg func() *types.MsgChangeAdmin + expectPass bool + }{ + { + name: "proper msg", + msg: func() *types.MsgChangeAdmin { + msg := baseMsg + return msg + }, + expectPass: true, + }, + { + name: "empty sender", + msg: func() *types.MsgChangeAdmin { + msg := baseMsg + msg.Sender = "" + return msg + }, + expectPass: false, + }, + { + name: "empty newAdmin", + msg: func() *types.MsgChangeAdmin { + msg := baseMsg + msg.NewAdmin = "" + return msg + }, + expectPass: false, + }, + { + name: "invalid denom", + msg: func() *types.MsgChangeAdmin { + msg := baseMsg + msg.Denom = "bitcoin" + return msg + }, + expectPass: false, + }, + } + + for _, test := range tests { + if test.expectPass { + require.NoError(t, test.msg().ValidateBasic(), "test: %v", test.name) + } else { + require.Error(t, test.msg().ValidateBasic(), "test: %v", test.name) + } + } +} + +// TestMsgSetDenomMetadata tests if valid/invalid create denom messages are properly validated/invalidated +func TestMsgSetDenomMetadata(t *testing.T) { + // generate a private/public key pair and get the respective address + pk1 := ed25519.GenPrivKey().PubKey() + addr1 := sdk.AccAddress(pk1.Address()) + tokenFactoryDenom := fmt.Sprintf("factory/%s/bitcoin", addr1.String()) + denomMetadata := banktypes.Metadata{ + Description: "nakamoto", + DenomUnits: []*banktypes.DenomUnit{ + { + Denom: tokenFactoryDenom, + Exponent: 0, + }, + { + Denom: "sats", + Exponent: 6, + }, + }, + Display: "sats", + Base: tokenFactoryDenom, + Name: "bitcoin", + Symbol: "BTC", + } + invalidDenomMetadata := banktypes.Metadata{ + Description: "nakamoto", + DenomUnits: []*banktypes.DenomUnit{ + { + Denom: "bitcoin", + Exponent: 0, + }, + { + Denom: "sats", + Exponent: 6, + }, + }, + Display: "sats", + Base: "bitcoin", + Name: "bitcoin", + Symbol: "BTC", + } + + // make a proper setDenomMetadata message + baseMsg := types.NewMsgSetDenomMetadata( + addr1.String(), + denomMetadata, + ) + + // validate setDenomMetadata message was created as intended + require.Equal(t, baseMsg.Route(), types.RouterKey) + require.Equal(t, baseMsg.Type(), "set_denom_metadata") + signers := baseMsg.GetSigners() + require.Equal(t, len(signers), 1) + require.Equal(t, signers[0].String(), addr1.String()) + + tests := []struct { + name string + msg func() *types.MsgSetDenomMetadata + expectPass bool + }{ + { + name: "proper msg", + msg: func() *types.MsgSetDenomMetadata { + msg := baseMsg + return msg + }, + expectPass: true, + }, + { + name: "empty sender", + msg: func() *types.MsgSetDenomMetadata { + msg := baseMsg + msg.Sender = "" + return msg + }, + expectPass: false, + }, + { + name: "invalid metadata", + msg: func() *types.MsgSetDenomMetadata { + msg := baseMsg + msg.Metadata.Name = "" + return msg + }, + + expectPass: false, + }, + { + name: "invalid base", + msg: func() *types.MsgSetDenomMetadata { + msg := baseMsg + msg.Metadata = invalidDenomMetadata + return msg + }, + expectPass: false, + }, + } + + for _, test := range tests { + if test.expectPass { + require.NoError(t, test.msg().ValidateBasic(), "test: %v", test.name) + } else { + require.Error(t, test.msg().ValidateBasic(), "test: %v", test.name) + } + } +} +*/ diff --git a/x/tokenfactory/types/params.go b/x/tokenfactory/types/params.go new file mode 100644 index 0000000..a483078 --- /dev/null +++ b/x/tokenfactory/types/params.go @@ -0,0 +1,62 @@ +package types + +import ( + "fmt" + + sdk "github.com/cosmos/cosmos-sdk/types" + paramtypes "github.com/cosmos/cosmos-sdk/x/params/types" +) + +// Parameter store keys. +var ( + KeyDenomCreationFee = []byte("DenomCreationFee") +) + +// ParamTable for gamm module. +func ParamKeyTable() paramtypes.KeyTable { + return paramtypes.NewKeyTable().RegisterParamSet(&Params{}) +} + +func NewParams(denomCreationFee sdk.Coins) Params { + return Params{ + DenomCreationFee: denomCreationFee, + } +} + +// default gamm module parameters. +func DefaultParams() Params { + return Params{ + // this was from osmosis + // DenomCreationFee: sdk.NewCoins(sdk.NewInt64Coin(appparams.BaseCoinUnit, 10_000_000)), // 10 OSMO + DenomCreationFee: sdk.NewCoins(sdk.NewInt64Coin(sdk.DefaultBondDenom, 10_000_000)), // 10 OSMO + } +} + +// validate params. +func (p Params) Validate() error { + if err := validateDenomCreationFee(p.DenomCreationFee); err != nil { + return err + } + + return nil +} + +// Implements params.ParamSet. +func (p *Params) ParamSetPairs() paramtypes.ParamSetPairs { + return paramtypes.ParamSetPairs{ + paramtypes.NewParamSetPair(KeyDenomCreationFee, &p.DenomCreationFee, validateDenomCreationFee), + } +} + +func validateDenomCreationFee(i interface{}) error { + v, ok := i.(sdk.Coins) + if !ok { + return fmt.Errorf("invalid parameter type: %T", i) + } + + if v.Validate() != nil { + return fmt.Errorf("invalid denom creation fee: %+v", i) + } + + return nil +} diff --git a/x/tokenfactory/types/params.pb.go b/x/tokenfactory/types/params.pb.go new file mode 100644 index 0000000..4ad1f92 --- /dev/null +++ b/x/tokenfactory/types/params.pb.go @@ -0,0 +1,342 @@ +// Code generated by protoc-gen-gogo. DO NOT EDIT. +// source: osmosis/tokenfactory/v1beta1/params.proto + +package types + +import ( + fmt "fmt" + _ "github.com/cosmos/cosmos-proto" + github_com_cosmos_cosmos_sdk_types "github.com/cosmos/cosmos-sdk/types" + types "github.com/cosmos/cosmos-sdk/types" + _ "github.com/gogo/protobuf/gogoproto" + proto "github.com/gogo/protobuf/proto" + io "io" + math "math" + math_bits "math/bits" +) + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the proto package it is being compiled against. +// A compilation error at this line likely means your copy of the +// proto package needs to be updated. +const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package + +// Params defines the parameters for the tokenfactory module. +type Params struct { + DenomCreationFee github_com_cosmos_cosmos_sdk_types.Coins `protobuf:"bytes,1,rep,name=denom_creation_fee,json=denomCreationFee,proto3,castrepeated=github.com/cosmos/cosmos-sdk/types.Coins" json:"denom_creation_fee" yaml:"denom_creation_fee"` +} + +func (m *Params) Reset() { *m = Params{} } +func (m *Params) String() string { return proto.CompactTextString(m) } +func (*Params) ProtoMessage() {} +func (*Params) Descriptor() ([]byte, []int) { + return fileDescriptor_cc8299d306f3ff47, []int{0} +} +func (m *Params) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *Params) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_Params.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *Params) XXX_Merge(src proto.Message) { + xxx_messageInfo_Params.Merge(m, src) +} +func (m *Params) XXX_Size() int { + return m.Size() +} +func (m *Params) XXX_DiscardUnknown() { + xxx_messageInfo_Params.DiscardUnknown(m) +} + +var xxx_messageInfo_Params proto.InternalMessageInfo + +func (m *Params) GetDenomCreationFee() github_com_cosmos_cosmos_sdk_types.Coins { + if m != nil { + return m.DenomCreationFee + } + return nil +} + +func init() { + proto.RegisterType((*Params)(nil), "osmosis.tokenfactory.v1beta1.Params") +} + +func init() { + proto.RegisterFile("osmosis/tokenfactory/v1beta1/params.proto", fileDescriptor_cc8299d306f3ff47) +} + +var fileDescriptor_cc8299d306f3ff47 = []byte{ + // 309 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x7c, 0x90, 0xbf, 0x4e, 0xf3, 0x30, + 0x14, 0xc5, 0x63, 0x7d, 0x52, 0x87, 0x7e, 0x0b, 0xaa, 0x18, 0x68, 0x85, 0x1c, 0x94, 0xa9, 0x0c, + 0xb5, 0x95, 0xc2, 0x80, 0x18, 0x5b, 0x89, 0xad, 0x12, 0xea, 0xc8, 0x12, 0xdd, 0x24, 0x6e, 0x6a, + 0xb5, 0xc9, 0x8d, 0x62, 0xb7, 0x22, 0x6f, 0xc1, 0xc4, 0xce, 0xca, 0x93, 0x74, 0xec, 0xc8, 0x54, + 0x50, 0xf2, 0x06, 0x3c, 0x01, 0xaa, 0xe3, 0xa2, 0x22, 0x24, 0x26, 0xfb, 0xe8, 0x9e, 0xf3, 0xbb, + 0x7f, 0xda, 0x97, 0xa8, 0x52, 0x54, 0x52, 0x71, 0x8d, 0x0b, 0x91, 0xcd, 0x20, 0xd2, 0x58, 0x94, + 0x7c, 0xed, 0x87, 0x42, 0x83, 0xcf, 0x73, 0x28, 0x20, 0x55, 0x2c, 0x2f, 0x50, 0x63, 0xe7, 0xdc, + 0x5a, 0xd9, 0xb1, 0x95, 0x59, 0x6b, 0xef, 0x34, 0xc1, 0x04, 0x8d, 0x91, 0xef, 0x7f, 0x4d, 0xa6, + 0x77, 0xfd, 0x27, 0x1e, 0x56, 0x7a, 0x8e, 0x85, 0xd4, 0xe5, 0x44, 0x68, 0x88, 0x41, 0x83, 0x4d, + 0x75, 0x23, 0x13, 0x0b, 0x1a, 0x5c, 0x23, 0x6c, 0x89, 0x36, 0x8a, 0x87, 0xa0, 0xc4, 0x37, 0x27, + 0x42, 0x99, 0x35, 0x75, 0xef, 0x85, 0xb4, 0x5b, 0xf7, 0x66, 0xea, 0xce, 0x33, 0x69, 0x77, 0x62, + 0x91, 0x61, 0x1a, 0x44, 0x85, 0x00, 0x2d, 0x31, 0x0b, 0x66, 0x42, 0x9c, 0x91, 0x8b, 0x7f, 0xfd, + 0xff, 0xc3, 0x2e, 0xb3, 0xd8, 0x3d, 0xe8, 0xb0, 0x04, 0x1b, 0xa3, 0xcc, 0x46, 0x93, 0xcd, 0xce, + 0x75, 0x3e, 0x77, 0x6e, 0xb7, 0x84, 0x74, 0x79, 0xeb, 0xfd, 0x46, 0x78, 0xaf, 0xef, 0x6e, 0x3f, + 0x91, 0x7a, 0xbe, 0x0a, 0x59, 0x84, 0xa9, 0x1d, 0xd0, 0x3e, 0x03, 0x15, 0x2f, 0xb8, 0x2e, 0x73, + 0xa1, 0x0c, 0x4d, 0x4d, 0x4f, 0x0c, 0x60, 0x6c, 0xf3, 0x77, 0x42, 0x8c, 0xa6, 0x9b, 0x8a, 0x92, + 0x6d, 0x45, 0xc9, 0x47, 0x45, 0xc9, 0x53, 0x4d, 0x9d, 0x6d, 0x4d, 0x9d, 0xb7, 0x9a, 0x3a, 0x0f, + 0x37, 0x47, 0x54, 0x7b, 0xb9, 0xc1, 0x12, 0x42, 0x75, 0x10, 0x7c, 0xed, 0x0f, 0xf9, 0xe3, 0xcf, + 0x63, 0x9a, 0x5e, 0x61, 0xcb, 0xac, 0x7f, 0xf5, 0x15, 0x00, 0x00, 0xff, 0xff, 0xa0, 0x89, 0xb8, + 0xda, 0xd0, 0x01, 0x00, 0x00, +} + +func (m *Params) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *Params) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *Params) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.DenomCreationFee) > 0 { + for iNdEx := len(m.DenomCreationFee) - 1; iNdEx >= 0; iNdEx-- { + { + size, err := m.DenomCreationFee[iNdEx].MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintParams(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0xa + } + } + return len(dAtA) - i, nil +} + +func encodeVarintParams(dAtA []byte, offset int, v uint64) int { + offset -= sovParams(v) + base := offset + for v >= 1<<7 { + dAtA[offset] = uint8(v&0x7f | 0x80) + v >>= 7 + offset++ + } + dAtA[offset] = uint8(v) + return base +} +func (m *Params) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if len(m.DenomCreationFee) > 0 { + for _, e := range m.DenomCreationFee { + l = e.Size() + n += 1 + l + sovParams(uint64(l)) + } + } + return n +} + +func sovParams(x uint64) (n int) { + return (math_bits.Len64(x|1) + 6) / 7 +} +func sozParams(x uint64) (n int) { + return sovParams(uint64((x << 1) ^ uint64((int64(x) >> 63)))) +} +func (m *Params) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowParams + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: Params: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: Params: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field DenomCreationFee", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowParams + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthParams + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthParams + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.DenomCreationFee = append(m.DenomCreationFee, types.Coin{}) + if err := m.DenomCreationFee[len(m.DenomCreationFee)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipParams(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthParams + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func skipParams(dAtA []byte) (n int, err error) { + l := len(dAtA) + iNdEx := 0 + depth := 0 + for iNdEx < l { + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowParams + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + wireType := int(wire & 0x7) + switch wireType { + case 0: + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowParams + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + iNdEx++ + if dAtA[iNdEx-1] < 0x80 { + break + } + } + case 1: + iNdEx += 8 + case 2: + var length int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowParams + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + length |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if length < 0 { + return 0, ErrInvalidLengthParams + } + iNdEx += length + case 3: + depth++ + case 4: + if depth == 0 { + return 0, ErrUnexpectedEndOfGroupParams + } + depth-- + case 5: + iNdEx += 4 + default: + return 0, fmt.Errorf("proto: illegal wireType %d", wireType) + } + if iNdEx < 0 { + return 0, ErrInvalidLengthParams + } + if depth == 0 { + return iNdEx, nil + } + } + return 0, io.ErrUnexpectedEOF +} + +var ( + ErrInvalidLengthParams = fmt.Errorf("proto: negative length found during unmarshaling") + ErrIntOverflowParams = fmt.Errorf("proto: integer overflow") + ErrUnexpectedEndOfGroupParams = fmt.Errorf("proto: unexpected end of group") +) diff --git a/x/tokenfactory/types/query.pb.go b/x/tokenfactory/types/query.pb.go new file mode 100644 index 0000000..aec8c19 --- /dev/null +++ b/x/tokenfactory/types/query.pb.go @@ -0,0 +1,1332 @@ +// Code generated by protoc-gen-gogo. DO NOT EDIT. +// source: osmosis/tokenfactory/v1beta1/query.proto + +package types + +import ( + context "context" + fmt "fmt" + _ "github.com/cosmos/cosmos-sdk/types/query" + _ "github.com/gogo/protobuf/gogoproto" + grpc1 "github.com/gogo/protobuf/grpc" + proto "github.com/gogo/protobuf/proto" + _ "google.golang.org/genproto/googleapis/api/annotations" + grpc "google.golang.org/grpc" + codes "google.golang.org/grpc/codes" + status "google.golang.org/grpc/status" + io "io" + math "math" + math_bits "math/bits" +) + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the proto package it is being compiled against. +// A compilation error at this line likely means your copy of the +// proto package needs to be updated. +const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package + +// QueryParamsRequest is the request type for the Query/Params RPC method. +type QueryParamsRequest struct { +} + +func (m *QueryParamsRequest) Reset() { *m = QueryParamsRequest{} } +func (m *QueryParamsRequest) String() string { return proto.CompactTextString(m) } +func (*QueryParamsRequest) ProtoMessage() {} +func (*QueryParamsRequest) Descriptor() ([]byte, []int) { + return fileDescriptor_6f22013ad0f72e3f, []int{0} +} +func (m *QueryParamsRequest) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *QueryParamsRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_QueryParamsRequest.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *QueryParamsRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_QueryParamsRequest.Merge(m, src) +} +func (m *QueryParamsRequest) XXX_Size() int { + return m.Size() +} +func (m *QueryParamsRequest) XXX_DiscardUnknown() { + xxx_messageInfo_QueryParamsRequest.DiscardUnknown(m) +} + +var xxx_messageInfo_QueryParamsRequest proto.InternalMessageInfo + +// QueryParamsResponse is the response type for the Query/Params RPC method. +type QueryParamsResponse struct { + // params defines the parameters of the module. + Params Params `protobuf:"bytes,1,opt,name=params,proto3" json:"params"` +} + +func (m *QueryParamsResponse) Reset() { *m = QueryParamsResponse{} } +func (m *QueryParamsResponse) String() string { return proto.CompactTextString(m) } +func (*QueryParamsResponse) ProtoMessage() {} +func (*QueryParamsResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_6f22013ad0f72e3f, []int{1} +} +func (m *QueryParamsResponse) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *QueryParamsResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_QueryParamsResponse.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *QueryParamsResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_QueryParamsResponse.Merge(m, src) +} +func (m *QueryParamsResponse) XXX_Size() int { + return m.Size() +} +func (m *QueryParamsResponse) XXX_DiscardUnknown() { + xxx_messageInfo_QueryParamsResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_QueryParamsResponse proto.InternalMessageInfo + +func (m *QueryParamsResponse) GetParams() Params { + if m != nil { + return m.Params + } + return Params{} +} + +// QueryDenomAuthorityMetadataRequest defines the request structure for the +// DenomAuthorityMetadata gRPC query. +type QueryDenomAuthorityMetadataRequest struct { + Denom string `protobuf:"bytes,1,opt,name=denom,proto3" json:"denom,omitempty" yaml:"denom"` +} + +func (m *QueryDenomAuthorityMetadataRequest) Reset() { *m = QueryDenomAuthorityMetadataRequest{} } +func (m *QueryDenomAuthorityMetadataRequest) String() string { return proto.CompactTextString(m) } +func (*QueryDenomAuthorityMetadataRequest) ProtoMessage() {} +func (*QueryDenomAuthorityMetadataRequest) Descriptor() ([]byte, []int) { + return fileDescriptor_6f22013ad0f72e3f, []int{2} +} +func (m *QueryDenomAuthorityMetadataRequest) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *QueryDenomAuthorityMetadataRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_QueryDenomAuthorityMetadataRequest.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *QueryDenomAuthorityMetadataRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_QueryDenomAuthorityMetadataRequest.Merge(m, src) +} +func (m *QueryDenomAuthorityMetadataRequest) XXX_Size() int { + return m.Size() +} +func (m *QueryDenomAuthorityMetadataRequest) XXX_DiscardUnknown() { + xxx_messageInfo_QueryDenomAuthorityMetadataRequest.DiscardUnknown(m) +} + +var xxx_messageInfo_QueryDenomAuthorityMetadataRequest proto.InternalMessageInfo + +func (m *QueryDenomAuthorityMetadataRequest) GetDenom() string { + if m != nil { + return m.Denom + } + return "" +} + +// QueryDenomAuthorityMetadataResponse defines the response structure for the +// DenomAuthorityMetadata gRPC query. +type QueryDenomAuthorityMetadataResponse struct { + AuthorityMetadata DenomAuthorityMetadata `protobuf:"bytes,1,opt,name=authority_metadata,json=authorityMetadata,proto3" json:"authority_metadata" yaml:"authority_metadata"` +} + +func (m *QueryDenomAuthorityMetadataResponse) Reset() { *m = QueryDenomAuthorityMetadataResponse{} } +func (m *QueryDenomAuthorityMetadataResponse) String() string { return proto.CompactTextString(m) } +func (*QueryDenomAuthorityMetadataResponse) ProtoMessage() {} +func (*QueryDenomAuthorityMetadataResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_6f22013ad0f72e3f, []int{3} +} +func (m *QueryDenomAuthorityMetadataResponse) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *QueryDenomAuthorityMetadataResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_QueryDenomAuthorityMetadataResponse.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *QueryDenomAuthorityMetadataResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_QueryDenomAuthorityMetadataResponse.Merge(m, src) +} +func (m *QueryDenomAuthorityMetadataResponse) XXX_Size() int { + return m.Size() +} +func (m *QueryDenomAuthorityMetadataResponse) XXX_DiscardUnknown() { + xxx_messageInfo_QueryDenomAuthorityMetadataResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_QueryDenomAuthorityMetadataResponse proto.InternalMessageInfo + +func (m *QueryDenomAuthorityMetadataResponse) GetAuthorityMetadata() DenomAuthorityMetadata { + if m != nil { + return m.AuthorityMetadata + } + return DenomAuthorityMetadata{} +} + +// QueryDenomsFromCreatorRequest defines the request structure for the +// DenomsFromCreator gRPC query. +type QueryDenomsFromCreatorRequest struct { + Creator string `protobuf:"bytes,1,opt,name=creator,proto3" json:"creator,omitempty" yaml:"creator"` +} + +func (m *QueryDenomsFromCreatorRequest) Reset() { *m = QueryDenomsFromCreatorRequest{} } +func (m *QueryDenomsFromCreatorRequest) String() string { return proto.CompactTextString(m) } +func (*QueryDenomsFromCreatorRequest) ProtoMessage() {} +func (*QueryDenomsFromCreatorRequest) Descriptor() ([]byte, []int) { + return fileDescriptor_6f22013ad0f72e3f, []int{4} +} +func (m *QueryDenomsFromCreatorRequest) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *QueryDenomsFromCreatorRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_QueryDenomsFromCreatorRequest.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *QueryDenomsFromCreatorRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_QueryDenomsFromCreatorRequest.Merge(m, src) +} +func (m *QueryDenomsFromCreatorRequest) XXX_Size() int { + return m.Size() +} +func (m *QueryDenomsFromCreatorRequest) XXX_DiscardUnknown() { + xxx_messageInfo_QueryDenomsFromCreatorRequest.DiscardUnknown(m) +} + +var xxx_messageInfo_QueryDenomsFromCreatorRequest proto.InternalMessageInfo + +func (m *QueryDenomsFromCreatorRequest) GetCreator() string { + if m != nil { + return m.Creator + } + return "" +} + +// QueryDenomsFromCreatorRequest defines the response structure for the +// DenomsFromCreator gRPC query. +type QueryDenomsFromCreatorResponse struct { + Denoms []string `protobuf:"bytes,1,rep,name=denoms,proto3" json:"denoms,omitempty" yaml:"denoms"` +} + +func (m *QueryDenomsFromCreatorResponse) Reset() { *m = QueryDenomsFromCreatorResponse{} } +func (m *QueryDenomsFromCreatorResponse) String() string { return proto.CompactTextString(m) } +func (*QueryDenomsFromCreatorResponse) ProtoMessage() {} +func (*QueryDenomsFromCreatorResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_6f22013ad0f72e3f, []int{5} +} +func (m *QueryDenomsFromCreatorResponse) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *QueryDenomsFromCreatorResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_QueryDenomsFromCreatorResponse.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *QueryDenomsFromCreatorResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_QueryDenomsFromCreatorResponse.Merge(m, src) +} +func (m *QueryDenomsFromCreatorResponse) XXX_Size() int { + return m.Size() +} +func (m *QueryDenomsFromCreatorResponse) XXX_DiscardUnknown() { + xxx_messageInfo_QueryDenomsFromCreatorResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_QueryDenomsFromCreatorResponse proto.InternalMessageInfo + +func (m *QueryDenomsFromCreatorResponse) GetDenoms() []string { + if m != nil { + return m.Denoms + } + return nil +} + +func init() { + proto.RegisterType((*QueryParamsRequest)(nil), "osmosis.tokenfactory.v1beta1.QueryParamsRequest") + proto.RegisterType((*QueryParamsResponse)(nil), "osmosis.tokenfactory.v1beta1.QueryParamsResponse") + proto.RegisterType((*QueryDenomAuthorityMetadataRequest)(nil), "osmosis.tokenfactory.v1beta1.QueryDenomAuthorityMetadataRequest") + proto.RegisterType((*QueryDenomAuthorityMetadataResponse)(nil), "osmosis.tokenfactory.v1beta1.QueryDenomAuthorityMetadataResponse") + proto.RegisterType((*QueryDenomsFromCreatorRequest)(nil), "osmosis.tokenfactory.v1beta1.QueryDenomsFromCreatorRequest") + proto.RegisterType((*QueryDenomsFromCreatorResponse)(nil), "osmosis.tokenfactory.v1beta1.QueryDenomsFromCreatorResponse") +} + +func init() { + proto.RegisterFile("osmosis/tokenfactory/v1beta1/query.proto", fileDescriptor_6f22013ad0f72e3f) +} + +var fileDescriptor_6f22013ad0f72e3f = []byte{ + // 574 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xa4, 0x54, 0xcb, 0x6e, 0xd3, 0x4c, + 0x14, 0xce, 0xfc, 0x7f, 0x1b, 0xd4, 0xe1, 0x22, 0x32, 0x54, 0x08, 0xa2, 0xe2, 0xc0, 0x50, 0x55, + 0x29, 0x2a, 0x1e, 0x12, 0xba, 0x40, 0x14, 0x04, 0x71, 0x11, 0x2c, 0xa0, 0x12, 0x78, 0x07, 0x9b, + 0x68, 0x92, 0x4e, 0x5d, 0x8b, 0xd8, 0xe3, 0x7a, 0x26, 0x15, 0x51, 0xd5, 0x0d, 0x0b, 0xd6, 0x48, + 0x2c, 0x79, 0x07, 0x9e, 0xa3, 0xcb, 0x4a, 0xdd, 0xb0, 0xb2, 0x50, 0x52, 0xf1, 0x00, 0x79, 0x02, + 0xe4, 0x99, 0x49, 0x68, 0x71, 0xb0, 0x02, 0xac, 0x62, 0xcd, 0xf9, 0xce, 0x77, 0x39, 0xe7, 0x28, + 0xb0, 0xca, 0x45, 0xc0, 0x85, 0x2f, 0x88, 0xe4, 0x6f, 0x59, 0xb8, 0x45, 0xdb, 0x92, 0xc7, 0x3d, + 0xb2, 0x5b, 0x6b, 0x31, 0x49, 0x6b, 0x64, 0xa7, 0xcb, 0xe2, 0x9e, 0x1d, 0xc5, 0x5c, 0x72, 0xb4, + 0x60, 0x90, 0xf6, 0x49, 0xa4, 0x6d, 0x90, 0xe5, 0x79, 0x8f, 0x7b, 0x5c, 0x01, 0x49, 0xfa, 0xa5, + 0x7b, 0xca, 0x0b, 0x1e, 0xe7, 0x5e, 0x87, 0x11, 0x1a, 0xf9, 0x84, 0x86, 0x21, 0x97, 0x54, 0xfa, + 0x3c, 0x14, 0xa6, 0x7a, 0xab, 0xad, 0x28, 0x49, 0x8b, 0x0a, 0xa6, 0xa5, 0xc6, 0xc2, 0x11, 0xf5, + 0xfc, 0x50, 0x81, 0x0d, 0x76, 0x35, 0xd7, 0x27, 0xed, 0xca, 0x6d, 0x1e, 0xfb, 0xb2, 0xb7, 0xc1, + 0x24, 0xdd, 0xa4, 0x92, 0x9a, 0xae, 0xe5, 0xdc, 0xae, 0x88, 0xc6, 0x34, 0x30, 0x66, 0xf0, 0x3c, + 0x44, 0xaf, 0x52, 0x0b, 0x2f, 0xd5, 0xa3, 0xcb, 0x76, 0xba, 0x4c, 0x48, 0xfc, 0x1a, 0x5e, 0x3a, + 0xf5, 0x2a, 0x22, 0x1e, 0x0a, 0x86, 0x1c, 0x58, 0xd4, 0xcd, 0x57, 0xc0, 0x75, 0x50, 0x3d, 0x5b, + 0x5f, 0xb4, 0xf3, 0x86, 0x63, 0xeb, 0x6e, 0x67, 0xe6, 0x20, 0xa9, 0x14, 0x5c, 0xd3, 0x89, 0x5f, + 0x40, 0xac, 0xa8, 0x9f, 0xb0, 0x90, 0x07, 0x8d, 0x5f, 0x03, 0x18, 0x03, 0x68, 0x09, 0xce, 0x6e, + 0xa6, 0x00, 0x25, 0x34, 0xe7, 0x5c, 0x1c, 0x26, 0x95, 0x73, 0x3d, 0x1a, 0x74, 0xee, 0x63, 0xf5, + 0x8c, 0x5d, 0x5d, 0xc6, 0x5f, 0x00, 0xbc, 0x99, 0x4b, 0x67, 0x9c, 0x7f, 0x00, 0x10, 0x8d, 0xa7, + 0xd5, 0x0c, 0x4c, 0xd9, 0xc4, 0x58, 0xcd, 0x8f, 0x31, 0x99, 0xda, 0xb9, 0x91, 0xc6, 0x1a, 0x26, + 0x95, 0xab, 0xda, 0x57, 0x96, 0x1d, 0xbb, 0xa5, 0xcc, 0x82, 0xf0, 0x06, 0xbc, 0xf6, 0xd3, 0xaf, + 0x78, 0x1a, 0xf3, 0x60, 0x3d, 0x66, 0x54, 0xf2, 0x78, 0x94, 0x7c, 0x05, 0x9e, 0x69, 0xeb, 0x17, + 0x93, 0x1d, 0x0d, 0x93, 0xca, 0x05, 0xad, 0x61, 0x0a, 0xd8, 0x1d, 0x41, 0xf0, 0x73, 0x68, 0xfd, + 0x8e, 0xce, 0x24, 0x5f, 0x86, 0x45, 0x35, 0xaa, 0x74, 0x67, 0xff, 0x57, 0xe7, 0x9c, 0xd2, 0x30, + 0xa9, 0x9c, 0x3f, 0x31, 0x4a, 0x81, 0x5d, 0x03, 0xa8, 0x1f, 0xcf, 0xc0, 0x59, 0xc5, 0x86, 0x3e, + 0x03, 0x58, 0xd4, 0xdb, 0x43, 0x77, 0xf2, 0x87, 0x93, 0x3d, 0x9e, 0x72, 0xed, 0x0f, 0x3a, 0xb4, + 0x49, 0xbc, 0xf2, 0xfe, 0xe8, 0xf8, 0xd3, 0x7f, 0x4b, 0x68, 0x91, 0x4c, 0x71, 0xb9, 0xe8, 0x3b, + 0x80, 0x97, 0x27, 0x2f, 0x05, 0x3d, 0x9e, 0x42, 0x3b, 0xf7, 0xf2, 0xca, 0x8d, 0x7f, 0x60, 0x30, + 0x69, 0x9e, 0xa9, 0x34, 0x0d, 0xf4, 0x28, 0x3f, 0x8d, 0x9e, 0x3a, 0xd9, 0x53, 0xbf, 0xfb, 0x24, + 0x7b, 0x40, 0xe8, 0x08, 0xc0, 0x52, 0x66, 0xb3, 0x68, 0x6d, 0x5a, 0x87, 0x13, 0xce, 0xab, 0xfc, + 0xe0, 0xef, 0x9a, 0x4d, 0xb2, 0x75, 0x95, 0xec, 0x21, 0x5a, 0x9b, 0x26, 0x59, 0x73, 0x2b, 0xe6, + 0x41, 0xd3, 0x5c, 0x2a, 0xd9, 0x33, 0x1f, 0xfb, 0x8e, 0x7b, 0xd0, 0xb7, 0xc0, 0x61, 0xdf, 0x02, + 0xdf, 0xfa, 0x16, 0xf8, 0x38, 0xb0, 0x0a, 0x87, 0x03, 0xab, 0xf0, 0x75, 0x60, 0x15, 0xde, 0xdc, + 0xf3, 0x7c, 0xb9, 0xdd, 0x6d, 0xd9, 0x6d, 0x1e, 0x8c, 0x04, 0x6e, 0x77, 0x68, 0x4b, 0x8c, 0xd5, + 0x76, 0x6b, 0x75, 0xf2, 0xee, 0xb4, 0xa6, 0xec, 0x45, 0x4c, 0xb4, 0x8a, 0xea, 0xdf, 0xec, 0xee, + 0x8f, 0x00, 0x00, 0x00, 0xff, 0xff, 0x0b, 0x3a, 0x54, 0xaf, 0xd8, 0x05, 0x00, 0x00, +} + +// Reference imports to suppress errors if they are not otherwise used. +var _ context.Context +var _ grpc.ClientConn + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the grpc package it is being compiled against. +const _ = grpc.SupportPackageIsVersion4 + +// QueryClient is the client API for Query service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://godoc.org/google.golang.org/grpc#ClientConn.NewStream. +type QueryClient interface { + // Params defines a gRPC query method that returns the tokenfactory module's + // parameters. + Params(ctx context.Context, in *QueryParamsRequest, opts ...grpc.CallOption) (*QueryParamsResponse, error) + // DenomAuthorityMetadata defines a gRPC query method for fetching + // DenomAuthorityMetadata for a particular denom. + DenomAuthorityMetadata(ctx context.Context, in *QueryDenomAuthorityMetadataRequest, opts ...grpc.CallOption) (*QueryDenomAuthorityMetadataResponse, error) + // DenomsFromCreator defines a gRPC query method for fetching all + // denominations created by a specific admin/creator. + DenomsFromCreator(ctx context.Context, in *QueryDenomsFromCreatorRequest, opts ...grpc.CallOption) (*QueryDenomsFromCreatorResponse, error) +} + +type queryClient struct { + cc grpc1.ClientConn +} + +func NewQueryClient(cc grpc1.ClientConn) QueryClient { + return &queryClient{cc} +} + +func (c *queryClient) Params(ctx context.Context, in *QueryParamsRequest, opts ...grpc.CallOption) (*QueryParamsResponse, error) { + out := new(QueryParamsResponse) + err := c.cc.Invoke(ctx, "/osmosis.tokenfactory.v1beta1.Query/Params", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *queryClient) DenomAuthorityMetadata(ctx context.Context, in *QueryDenomAuthorityMetadataRequest, opts ...grpc.CallOption) (*QueryDenomAuthorityMetadataResponse, error) { + out := new(QueryDenomAuthorityMetadataResponse) + err := c.cc.Invoke(ctx, "/osmosis.tokenfactory.v1beta1.Query/DenomAuthorityMetadata", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *queryClient) DenomsFromCreator(ctx context.Context, in *QueryDenomsFromCreatorRequest, opts ...grpc.CallOption) (*QueryDenomsFromCreatorResponse, error) { + out := new(QueryDenomsFromCreatorResponse) + err := c.cc.Invoke(ctx, "/osmosis.tokenfactory.v1beta1.Query/DenomsFromCreator", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +// QueryServer is the server API for Query service. +type QueryServer interface { + // Params defines a gRPC query method that returns the tokenfactory module's + // parameters. + Params(context.Context, *QueryParamsRequest) (*QueryParamsResponse, error) + // DenomAuthorityMetadata defines a gRPC query method for fetching + // DenomAuthorityMetadata for a particular denom. + DenomAuthorityMetadata(context.Context, *QueryDenomAuthorityMetadataRequest) (*QueryDenomAuthorityMetadataResponse, error) + // DenomsFromCreator defines a gRPC query method for fetching all + // denominations created by a specific admin/creator. + DenomsFromCreator(context.Context, *QueryDenomsFromCreatorRequest) (*QueryDenomsFromCreatorResponse, error) +} + +// UnimplementedQueryServer can be embedded to have forward compatible implementations. +type UnimplementedQueryServer struct { +} + +func (*UnimplementedQueryServer) Params(ctx context.Context, req *QueryParamsRequest) (*QueryParamsResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method Params not implemented") +} +func (*UnimplementedQueryServer) DenomAuthorityMetadata(ctx context.Context, req *QueryDenomAuthorityMetadataRequest) (*QueryDenomAuthorityMetadataResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method DenomAuthorityMetadata not implemented") +} +func (*UnimplementedQueryServer) DenomsFromCreator(ctx context.Context, req *QueryDenomsFromCreatorRequest) (*QueryDenomsFromCreatorResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method DenomsFromCreator not implemented") +} + +func RegisterQueryServer(s grpc1.Server, srv QueryServer) { + s.RegisterService(&_Query_serviceDesc, srv) +} + +func _Query_Params_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(QueryParamsRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(QueryServer).Params(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/osmosis.tokenfactory.v1beta1.Query/Params", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(QueryServer).Params(ctx, req.(*QueryParamsRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _Query_DenomAuthorityMetadata_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(QueryDenomAuthorityMetadataRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(QueryServer).DenomAuthorityMetadata(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/osmosis.tokenfactory.v1beta1.Query/DenomAuthorityMetadata", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(QueryServer).DenomAuthorityMetadata(ctx, req.(*QueryDenomAuthorityMetadataRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _Query_DenomsFromCreator_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(QueryDenomsFromCreatorRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(QueryServer).DenomsFromCreator(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/osmosis.tokenfactory.v1beta1.Query/DenomsFromCreator", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(QueryServer).DenomsFromCreator(ctx, req.(*QueryDenomsFromCreatorRequest)) + } + return interceptor(ctx, in, info, handler) +} + +var _Query_serviceDesc = grpc.ServiceDesc{ + ServiceName: "osmosis.tokenfactory.v1beta1.Query", + HandlerType: (*QueryServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "Params", + Handler: _Query_Params_Handler, + }, + { + MethodName: "DenomAuthorityMetadata", + Handler: _Query_DenomAuthorityMetadata_Handler, + }, + { + MethodName: "DenomsFromCreator", + Handler: _Query_DenomsFromCreator_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "osmosis/tokenfactory/v1beta1/query.proto", +} + +func (m *QueryParamsRequest) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *QueryParamsRequest) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *QueryParamsRequest) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + return len(dAtA) - i, nil +} + +func (m *QueryParamsResponse) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *QueryParamsResponse) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *QueryParamsResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + { + size, err := m.Params.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintQuery(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0xa + return len(dAtA) - i, nil +} + +func (m *QueryDenomAuthorityMetadataRequest) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *QueryDenomAuthorityMetadataRequest) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *QueryDenomAuthorityMetadataRequest) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.Denom) > 0 { + i -= len(m.Denom) + copy(dAtA[i:], m.Denom) + i = encodeVarintQuery(dAtA, i, uint64(len(m.Denom))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *QueryDenomAuthorityMetadataResponse) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *QueryDenomAuthorityMetadataResponse) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *QueryDenomAuthorityMetadataResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + { + size, err := m.AuthorityMetadata.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintQuery(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0xa + return len(dAtA) - i, nil +} + +func (m *QueryDenomsFromCreatorRequest) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *QueryDenomsFromCreatorRequest) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *QueryDenomsFromCreatorRequest) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.Creator) > 0 { + i -= len(m.Creator) + copy(dAtA[i:], m.Creator) + i = encodeVarintQuery(dAtA, i, uint64(len(m.Creator))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *QueryDenomsFromCreatorResponse) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *QueryDenomsFromCreatorResponse) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *QueryDenomsFromCreatorResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.Denoms) > 0 { + for iNdEx := len(m.Denoms) - 1; iNdEx >= 0; iNdEx-- { + i -= len(m.Denoms[iNdEx]) + copy(dAtA[i:], m.Denoms[iNdEx]) + i = encodeVarintQuery(dAtA, i, uint64(len(m.Denoms[iNdEx]))) + i-- + dAtA[i] = 0xa + } + } + return len(dAtA) - i, nil +} + +func encodeVarintQuery(dAtA []byte, offset int, v uint64) int { + offset -= sovQuery(v) + base := offset + for v >= 1<<7 { + dAtA[offset] = uint8(v&0x7f | 0x80) + v >>= 7 + offset++ + } + dAtA[offset] = uint8(v) + return base +} +func (m *QueryParamsRequest) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + return n +} + +func (m *QueryParamsResponse) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = m.Params.Size() + n += 1 + l + sovQuery(uint64(l)) + return n +} + +func (m *QueryDenomAuthorityMetadataRequest) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.Denom) + if l > 0 { + n += 1 + l + sovQuery(uint64(l)) + } + return n +} + +func (m *QueryDenomAuthorityMetadataResponse) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = m.AuthorityMetadata.Size() + n += 1 + l + sovQuery(uint64(l)) + return n +} + +func (m *QueryDenomsFromCreatorRequest) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.Creator) + if l > 0 { + n += 1 + l + sovQuery(uint64(l)) + } + return n +} + +func (m *QueryDenomsFromCreatorResponse) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if len(m.Denoms) > 0 { + for _, s := range m.Denoms { + l = len(s) + n += 1 + l + sovQuery(uint64(l)) + } + } + return n +} + +func sovQuery(x uint64) (n int) { + return (math_bits.Len64(x|1) + 6) / 7 +} +func sozQuery(x uint64) (n int) { + return sovQuery(uint64((x << 1) ^ uint64((int64(x) >> 63)))) +} +func (m *QueryParamsRequest) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: QueryParamsRequest: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: QueryParamsRequest: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + default: + iNdEx = preIndex + skippy, err := skipQuery(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthQuery + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *QueryParamsResponse) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: QueryParamsResponse: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: QueryParamsResponse: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Params", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthQuery + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthQuery + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.Params.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipQuery(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthQuery + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *QueryDenomAuthorityMetadataRequest) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: QueryDenomAuthorityMetadataRequest: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: QueryDenomAuthorityMetadataRequest: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Denom", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthQuery + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthQuery + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Denom = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipQuery(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthQuery + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *QueryDenomAuthorityMetadataResponse) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: QueryDenomAuthorityMetadataResponse: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: QueryDenomAuthorityMetadataResponse: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field AuthorityMetadata", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthQuery + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthQuery + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.AuthorityMetadata.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipQuery(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthQuery + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *QueryDenomsFromCreatorRequest) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: QueryDenomsFromCreatorRequest: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: QueryDenomsFromCreatorRequest: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Creator", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthQuery + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthQuery + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Creator = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipQuery(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthQuery + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *QueryDenomsFromCreatorResponse) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: QueryDenomsFromCreatorResponse: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: QueryDenomsFromCreatorResponse: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Denoms", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthQuery + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthQuery + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Denoms = append(m.Denoms, string(dAtA[iNdEx:postIndex])) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipQuery(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthQuery + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func skipQuery(dAtA []byte) (n int, err error) { + l := len(dAtA) + iNdEx := 0 + depth := 0 + for iNdEx < l { + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowQuery + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + wireType := int(wire & 0x7) + switch wireType { + case 0: + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowQuery + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + iNdEx++ + if dAtA[iNdEx-1] < 0x80 { + break + } + } + case 1: + iNdEx += 8 + case 2: + var length int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowQuery + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + length |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if length < 0 { + return 0, ErrInvalidLengthQuery + } + iNdEx += length + case 3: + depth++ + case 4: + if depth == 0 { + return 0, ErrUnexpectedEndOfGroupQuery + } + depth-- + case 5: + iNdEx += 4 + default: + return 0, fmt.Errorf("proto: illegal wireType %d", wireType) + } + if iNdEx < 0 { + return 0, ErrInvalidLengthQuery + } + if depth == 0 { + return iNdEx, nil + } + } + return 0, io.ErrUnexpectedEOF +} + +var ( + ErrInvalidLengthQuery = fmt.Errorf("proto: negative length found during unmarshaling") + ErrIntOverflowQuery = fmt.Errorf("proto: integer overflow") + ErrUnexpectedEndOfGroupQuery = fmt.Errorf("proto: unexpected end of group") +) diff --git a/x/tokenfactory/types/query.pb.gw.go b/x/tokenfactory/types/query.pb.gw.go new file mode 100644 index 0000000..0b895e7 --- /dev/null +++ b/x/tokenfactory/types/query.pb.gw.go @@ -0,0 +1,355 @@ +// Code generated by protoc-gen-grpc-gateway. DO NOT EDIT. +// source: osmosis/tokenfactory/v1beta1/query.proto + +/* +Package types is a reverse proxy. + +It translates gRPC into RESTful JSON APIs. +*/ +package types + +import ( + "context" + "io" + "net/http" + + "github.com/golang/protobuf/descriptor" + "github.com/golang/protobuf/proto" + "github.com/grpc-ecosystem/grpc-gateway/runtime" + "github.com/grpc-ecosystem/grpc-gateway/utilities" + "google.golang.org/grpc" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/grpclog" + "google.golang.org/grpc/metadata" + "google.golang.org/grpc/status" +) + +// Suppress "imported and not used" errors +var _ codes.Code +var _ io.Reader +var _ status.Status +var _ = runtime.String +var _ = utilities.NewDoubleArray +var _ = descriptor.ForMessage +var _ = metadata.Join + +func request_Query_Params_0(ctx context.Context, marshaler runtime.Marshaler, client QueryClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq QueryParamsRequest + var metadata runtime.ServerMetadata + + msg, err := client.Params(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +func local_request_Query_Params_0(ctx context.Context, marshaler runtime.Marshaler, server QueryServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq QueryParamsRequest + var metadata runtime.ServerMetadata + + msg, err := server.Params(ctx, &protoReq) + return msg, metadata, err + +} + +func request_Query_DenomAuthorityMetadata_0(ctx context.Context, marshaler runtime.Marshaler, client QueryClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq QueryDenomAuthorityMetadataRequest + var metadata runtime.ServerMetadata + + var ( + val string + ok bool + err error + _ = err + ) + + val, ok = pathParams["denom"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "denom") + } + + protoReq.Denom, err = runtime.String(val) + + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "denom", err) + } + + msg, err := client.DenomAuthorityMetadata(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +func local_request_Query_DenomAuthorityMetadata_0(ctx context.Context, marshaler runtime.Marshaler, server QueryServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq QueryDenomAuthorityMetadataRequest + var metadata runtime.ServerMetadata + + var ( + val string + ok bool + err error + _ = err + ) + + val, ok = pathParams["denom"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "denom") + } + + protoReq.Denom, err = runtime.String(val) + + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "denom", err) + } + + msg, err := server.DenomAuthorityMetadata(ctx, &protoReq) + return msg, metadata, err + +} + +func request_Query_DenomsFromCreator_0(ctx context.Context, marshaler runtime.Marshaler, client QueryClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq QueryDenomsFromCreatorRequest + var metadata runtime.ServerMetadata + + var ( + val string + ok bool + err error + _ = err + ) + + val, ok = pathParams["creator"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "creator") + } + + protoReq.Creator, err = runtime.String(val) + + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "creator", err) + } + + msg, err := client.DenomsFromCreator(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +func local_request_Query_DenomsFromCreator_0(ctx context.Context, marshaler runtime.Marshaler, server QueryServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq QueryDenomsFromCreatorRequest + var metadata runtime.ServerMetadata + + var ( + val string + ok bool + err error + _ = err + ) + + val, ok = pathParams["creator"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "creator") + } + + protoReq.Creator, err = runtime.String(val) + + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "creator", err) + } + + msg, err := server.DenomsFromCreator(ctx, &protoReq) + return msg, metadata, err + +} + +// RegisterQueryHandlerServer registers the http handlers for service Query to "mux". +// UnaryRPC :call QueryServer directly. +// StreamingRPC :currently unsupported pending https://github.com/grpc/grpc-go/issues/906. +// Note that using this registration option will cause many gRPC library features to stop working. Consider using RegisterQueryHandlerFromEndpoint instead. +func RegisterQueryHandlerServer(ctx context.Context, mux *runtime.ServeMux, server QueryServer) error { + + mux.Handle("GET", pattern_Query_Params_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + var stream runtime.ServerTransportStream + ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_Query_Params_0(rctx, inboundMarshaler, server, req, pathParams) + md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_Query_Params_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("GET", pattern_Query_DenomAuthorityMetadata_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + var stream runtime.ServerTransportStream + ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_Query_DenomAuthorityMetadata_0(rctx, inboundMarshaler, server, req, pathParams) + md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_Query_DenomAuthorityMetadata_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("GET", pattern_Query_DenomsFromCreator_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + var stream runtime.ServerTransportStream + ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_Query_DenomsFromCreator_0(rctx, inboundMarshaler, server, req, pathParams) + md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_Query_DenomsFromCreator_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + return nil +} + +// RegisterQueryHandlerFromEndpoint is same as RegisterQueryHandler but +// automatically dials to "endpoint" and closes the connection when "ctx" gets done. +func RegisterQueryHandlerFromEndpoint(ctx context.Context, mux *runtime.ServeMux, endpoint string, opts []grpc.DialOption) (err error) { + conn, err := grpc.Dial(endpoint, opts...) + if err != nil { + return err + } + defer func() { + if err != nil { + if cerr := conn.Close(); cerr != nil { + grpclog.Infof("Failed to close conn to %s: %v", endpoint, cerr) + } + return + } + go func() { + <-ctx.Done() + if cerr := conn.Close(); cerr != nil { + grpclog.Infof("Failed to close conn to %s: %v", endpoint, cerr) + } + }() + }() + + return RegisterQueryHandler(ctx, mux, conn) +} + +// RegisterQueryHandler registers the http handlers for service Query to "mux". +// The handlers forward requests to the grpc endpoint over "conn". +func RegisterQueryHandler(ctx context.Context, mux *runtime.ServeMux, conn *grpc.ClientConn) error { + return RegisterQueryHandlerClient(ctx, mux, NewQueryClient(conn)) +} + +// RegisterQueryHandlerClient registers the http handlers for service Query +// to "mux". The handlers forward requests to the grpc endpoint over the given implementation of "QueryClient". +// Note: the gRPC framework executes interceptors within the gRPC handler. If the passed in "QueryClient" +// doesn't go through the normal gRPC flow (creating a gRPC client etc.) then it will be up to the passed in +// "QueryClient" to call the correct interceptors. +func RegisterQueryHandlerClient(ctx context.Context, mux *runtime.ServeMux, client QueryClient) error { + + mux.Handle("GET", pattern_Query_Params_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_Query_Params_0(rctx, inboundMarshaler, client, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_Query_Params_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("GET", pattern_Query_DenomAuthorityMetadata_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_Query_DenomAuthorityMetadata_0(rctx, inboundMarshaler, client, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_Query_DenomAuthorityMetadata_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("GET", pattern_Query_DenomsFromCreator_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_Query_DenomsFromCreator_0(rctx, inboundMarshaler, client, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_Query_DenomsFromCreator_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + return nil +} + +var ( + pattern_Query_Params_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3}, []string{"osmosis", "tokenfactory", "v1beta1", "params"}, "", runtime.AssumeColonVerbOpt(false))) + + pattern_Query_DenomAuthorityMetadata_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3, 1, 0, 4, 1, 5, 4, 2, 5}, []string{"osmosis", "tokenfactory", "v1beta1", "denoms", "denom", "authority_metadata"}, "", runtime.AssumeColonVerbOpt(false))) + + pattern_Query_DenomsFromCreator_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3, 1, 0, 4, 1, 5, 4}, []string{"osmosis", "tokenfactory", "v1beta1", "denoms_from_creator", "creator"}, "", runtime.AssumeColonVerbOpt(false))) +) + +var ( + forward_Query_Params_0 = runtime.ForwardResponseMessage + + forward_Query_DenomAuthorityMetadata_0 = runtime.ForwardResponseMessage + + forward_Query_DenomsFromCreator_0 = runtime.ForwardResponseMessage +) diff --git a/x/tokenfactory/types/tx.pb.go b/x/tokenfactory/types/tx.pb.go new file mode 100644 index 0000000..b61b87b --- /dev/null +++ b/x/tokenfactory/types/tx.pb.go @@ -0,0 +1,2238 @@ +// Code generated by protoc-gen-gogo. DO NOT EDIT. +// source: osmosis/tokenfactory/v1beta1/tx.proto + +package types + +import ( + context "context" + fmt "fmt" + types "github.com/cosmos/cosmos-sdk/types" + types1 "github.com/cosmos/cosmos-sdk/x/bank/types" + _ "github.com/gogo/protobuf/gogoproto" + grpc1 "github.com/gogo/protobuf/grpc" + proto "github.com/gogo/protobuf/proto" + grpc "google.golang.org/grpc" + codes "google.golang.org/grpc/codes" + status "google.golang.org/grpc/status" + io "io" + math "math" + math_bits "math/bits" +) + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the proto package it is being compiled against. +// A compilation error at this line likely means your copy of the +// proto package needs to be updated. +const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package + +// MsgCreateDenom defines the message structure for the CreateDenom gRPC service +// method. It allows an account to create a new denom. It requires a sender +// address and a sub denomination. The (sender_address, sub_denomination) tuple +// must be unique and cannot be re-used. +// +// The resulting denom created is defined as +// . The resulting denom's admin is +// originally set to be the creator, but this can be changed later. The token +// denom does not indicate the current admin. +type MsgCreateDenom struct { + Sender string `protobuf:"bytes,1,opt,name=sender,proto3" json:"sender,omitempty" yaml:"sender"` + // subdenom can be up to 44 "alphanumeric" characters long. + Subdenom string `protobuf:"bytes,2,opt,name=subdenom,proto3" json:"subdenom,omitempty" yaml:"subdenom"` +} + +func (m *MsgCreateDenom) Reset() { *m = MsgCreateDenom{} } +func (m *MsgCreateDenom) String() string { return proto.CompactTextString(m) } +func (*MsgCreateDenom) ProtoMessage() {} +func (*MsgCreateDenom) Descriptor() ([]byte, []int) { + return fileDescriptor_283b6c9a90a846b4, []int{0} +} +func (m *MsgCreateDenom) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *MsgCreateDenom) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_MsgCreateDenom.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *MsgCreateDenom) XXX_Merge(src proto.Message) { + xxx_messageInfo_MsgCreateDenom.Merge(m, src) +} +func (m *MsgCreateDenom) XXX_Size() int { + return m.Size() +} +func (m *MsgCreateDenom) XXX_DiscardUnknown() { + xxx_messageInfo_MsgCreateDenom.DiscardUnknown(m) +} + +var xxx_messageInfo_MsgCreateDenom proto.InternalMessageInfo + +func (m *MsgCreateDenom) GetSender() string { + if m != nil { + return m.Sender + } + return "" +} + +func (m *MsgCreateDenom) GetSubdenom() string { + if m != nil { + return m.Subdenom + } + return "" +} + +// MsgCreateDenomResponse is the return value of MsgCreateDenom +// It returns the full string of the newly created denom +type MsgCreateDenomResponse struct { + NewTokenDenom string `protobuf:"bytes,1,opt,name=new_token_denom,json=newTokenDenom,proto3" json:"new_token_denom,omitempty" yaml:"new_token_denom"` +} + +func (m *MsgCreateDenomResponse) Reset() { *m = MsgCreateDenomResponse{} } +func (m *MsgCreateDenomResponse) String() string { return proto.CompactTextString(m) } +func (*MsgCreateDenomResponse) ProtoMessage() {} +func (*MsgCreateDenomResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_283b6c9a90a846b4, []int{1} +} +func (m *MsgCreateDenomResponse) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *MsgCreateDenomResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_MsgCreateDenomResponse.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *MsgCreateDenomResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_MsgCreateDenomResponse.Merge(m, src) +} +func (m *MsgCreateDenomResponse) XXX_Size() int { + return m.Size() +} +func (m *MsgCreateDenomResponse) XXX_DiscardUnknown() { + xxx_messageInfo_MsgCreateDenomResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_MsgCreateDenomResponse proto.InternalMessageInfo + +func (m *MsgCreateDenomResponse) GetNewTokenDenom() string { + if m != nil { + return m.NewTokenDenom + } + return "" +} + +// MsgMint is the sdk.Msg type for allowing an admin account to mint +// more of a token. For now, we only support minting to the sender account +type MsgMint struct { + Sender string `protobuf:"bytes,1,opt,name=sender,proto3" json:"sender,omitempty" yaml:"sender"` + Amount types.Coin `protobuf:"bytes,2,opt,name=amount,proto3" json:"amount" yaml:"amount"` +} + +func (m *MsgMint) Reset() { *m = MsgMint{} } +func (m *MsgMint) String() string { return proto.CompactTextString(m) } +func (*MsgMint) ProtoMessage() {} +func (*MsgMint) Descriptor() ([]byte, []int) { + return fileDescriptor_283b6c9a90a846b4, []int{2} +} +func (m *MsgMint) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *MsgMint) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_MsgMint.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *MsgMint) XXX_Merge(src proto.Message) { + xxx_messageInfo_MsgMint.Merge(m, src) +} +func (m *MsgMint) XXX_Size() int { + return m.Size() +} +func (m *MsgMint) XXX_DiscardUnknown() { + xxx_messageInfo_MsgMint.DiscardUnknown(m) +} + +var xxx_messageInfo_MsgMint proto.InternalMessageInfo + +func (m *MsgMint) GetSender() string { + if m != nil { + return m.Sender + } + return "" +} + +func (m *MsgMint) GetAmount() types.Coin { + if m != nil { + return m.Amount + } + return types.Coin{} +} + +type MsgMintResponse struct { +} + +func (m *MsgMintResponse) Reset() { *m = MsgMintResponse{} } +func (m *MsgMintResponse) String() string { return proto.CompactTextString(m) } +func (*MsgMintResponse) ProtoMessage() {} +func (*MsgMintResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_283b6c9a90a846b4, []int{3} +} +func (m *MsgMintResponse) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *MsgMintResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_MsgMintResponse.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *MsgMintResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_MsgMintResponse.Merge(m, src) +} +func (m *MsgMintResponse) XXX_Size() int { + return m.Size() +} +func (m *MsgMintResponse) XXX_DiscardUnknown() { + xxx_messageInfo_MsgMintResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_MsgMintResponse proto.InternalMessageInfo + +// MsgBurn is the sdk.Msg type for allowing an admin account to burn +// a token. For now, we only support burning from the sender account. +type MsgBurn struct { + Sender string `protobuf:"bytes,1,opt,name=sender,proto3" json:"sender,omitempty" yaml:"sender"` + Amount types.Coin `protobuf:"bytes,2,opt,name=amount,proto3" json:"amount" yaml:"amount"` +} + +func (m *MsgBurn) Reset() { *m = MsgBurn{} } +func (m *MsgBurn) String() string { return proto.CompactTextString(m) } +func (*MsgBurn) ProtoMessage() {} +func (*MsgBurn) Descriptor() ([]byte, []int) { + return fileDescriptor_283b6c9a90a846b4, []int{4} +} +func (m *MsgBurn) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *MsgBurn) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_MsgBurn.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *MsgBurn) XXX_Merge(src proto.Message) { + xxx_messageInfo_MsgBurn.Merge(m, src) +} +func (m *MsgBurn) XXX_Size() int { + return m.Size() +} +func (m *MsgBurn) XXX_DiscardUnknown() { + xxx_messageInfo_MsgBurn.DiscardUnknown(m) +} + +var xxx_messageInfo_MsgBurn proto.InternalMessageInfo + +func (m *MsgBurn) GetSender() string { + if m != nil { + return m.Sender + } + return "" +} + +func (m *MsgBurn) GetAmount() types.Coin { + if m != nil { + return m.Amount + } + return types.Coin{} +} + +type MsgBurnResponse struct { +} + +func (m *MsgBurnResponse) Reset() { *m = MsgBurnResponse{} } +func (m *MsgBurnResponse) String() string { return proto.CompactTextString(m) } +func (*MsgBurnResponse) ProtoMessage() {} +func (*MsgBurnResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_283b6c9a90a846b4, []int{5} +} +func (m *MsgBurnResponse) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *MsgBurnResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_MsgBurnResponse.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *MsgBurnResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_MsgBurnResponse.Merge(m, src) +} +func (m *MsgBurnResponse) XXX_Size() int { + return m.Size() +} +func (m *MsgBurnResponse) XXX_DiscardUnknown() { + xxx_messageInfo_MsgBurnResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_MsgBurnResponse proto.InternalMessageInfo + +// MsgChangeAdmin is the sdk.Msg type for allowing an admin account to reassign +// adminship of a denom to a new account +type MsgChangeAdmin struct { + Sender string `protobuf:"bytes,1,opt,name=sender,proto3" json:"sender,omitempty" yaml:"sender"` + Denom string `protobuf:"bytes,2,opt,name=denom,proto3" json:"denom,omitempty" yaml:"denom"` + NewAdmin string `protobuf:"bytes,3,opt,name=new_admin,json=newAdmin,proto3" json:"new_admin,omitempty" yaml:"new_admin"` +} + +func (m *MsgChangeAdmin) Reset() { *m = MsgChangeAdmin{} } +func (m *MsgChangeAdmin) String() string { return proto.CompactTextString(m) } +func (*MsgChangeAdmin) ProtoMessage() {} +func (*MsgChangeAdmin) Descriptor() ([]byte, []int) { + return fileDescriptor_283b6c9a90a846b4, []int{6} +} +func (m *MsgChangeAdmin) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *MsgChangeAdmin) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_MsgChangeAdmin.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *MsgChangeAdmin) XXX_Merge(src proto.Message) { + xxx_messageInfo_MsgChangeAdmin.Merge(m, src) +} +func (m *MsgChangeAdmin) XXX_Size() int { + return m.Size() +} +func (m *MsgChangeAdmin) XXX_DiscardUnknown() { + xxx_messageInfo_MsgChangeAdmin.DiscardUnknown(m) +} + +var xxx_messageInfo_MsgChangeAdmin proto.InternalMessageInfo + +func (m *MsgChangeAdmin) GetSender() string { + if m != nil { + return m.Sender + } + return "" +} + +func (m *MsgChangeAdmin) GetDenom() string { + if m != nil { + return m.Denom + } + return "" +} + +func (m *MsgChangeAdmin) GetNewAdmin() string { + if m != nil { + return m.NewAdmin + } + return "" +} + +// MsgChangeAdminResponse defines the response structure for an executed +// MsgChangeAdmin message. +type MsgChangeAdminResponse struct { +} + +func (m *MsgChangeAdminResponse) Reset() { *m = MsgChangeAdminResponse{} } +func (m *MsgChangeAdminResponse) String() string { return proto.CompactTextString(m) } +func (*MsgChangeAdminResponse) ProtoMessage() {} +func (*MsgChangeAdminResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_283b6c9a90a846b4, []int{7} +} +func (m *MsgChangeAdminResponse) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *MsgChangeAdminResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_MsgChangeAdminResponse.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *MsgChangeAdminResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_MsgChangeAdminResponse.Merge(m, src) +} +func (m *MsgChangeAdminResponse) XXX_Size() int { + return m.Size() +} +func (m *MsgChangeAdminResponse) XXX_DiscardUnknown() { + xxx_messageInfo_MsgChangeAdminResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_MsgChangeAdminResponse proto.InternalMessageInfo + +// MsgSetDenomMetadata is the sdk.Msg type for allowing an admin account to set +// the denom's bank metadata +type MsgSetDenomMetadata struct { + Sender string `protobuf:"bytes,1,opt,name=sender,proto3" json:"sender,omitempty" yaml:"sender"` + Metadata types1.Metadata `protobuf:"bytes,2,opt,name=metadata,proto3" json:"metadata" yaml:"metadata"` +} + +func (m *MsgSetDenomMetadata) Reset() { *m = MsgSetDenomMetadata{} } +func (m *MsgSetDenomMetadata) String() string { return proto.CompactTextString(m) } +func (*MsgSetDenomMetadata) ProtoMessage() {} +func (*MsgSetDenomMetadata) Descriptor() ([]byte, []int) { + return fileDescriptor_283b6c9a90a846b4, []int{8} +} +func (m *MsgSetDenomMetadata) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *MsgSetDenomMetadata) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_MsgSetDenomMetadata.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *MsgSetDenomMetadata) XXX_Merge(src proto.Message) { + xxx_messageInfo_MsgSetDenomMetadata.Merge(m, src) +} +func (m *MsgSetDenomMetadata) XXX_Size() int { + return m.Size() +} +func (m *MsgSetDenomMetadata) XXX_DiscardUnknown() { + xxx_messageInfo_MsgSetDenomMetadata.DiscardUnknown(m) +} + +var xxx_messageInfo_MsgSetDenomMetadata proto.InternalMessageInfo + +func (m *MsgSetDenomMetadata) GetSender() string { + if m != nil { + return m.Sender + } + return "" +} + +func (m *MsgSetDenomMetadata) GetMetadata() types1.Metadata { + if m != nil { + return m.Metadata + } + return types1.Metadata{} +} + +// MsgSetDenomMetadataResponse defines the response structure for an executed +// MsgSetDenomMetadata message. +type MsgSetDenomMetadataResponse struct { +} + +func (m *MsgSetDenomMetadataResponse) Reset() { *m = MsgSetDenomMetadataResponse{} } +func (m *MsgSetDenomMetadataResponse) String() string { return proto.CompactTextString(m) } +func (*MsgSetDenomMetadataResponse) ProtoMessage() {} +func (*MsgSetDenomMetadataResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_283b6c9a90a846b4, []int{9} +} +func (m *MsgSetDenomMetadataResponse) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *MsgSetDenomMetadataResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_MsgSetDenomMetadataResponse.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *MsgSetDenomMetadataResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_MsgSetDenomMetadataResponse.Merge(m, src) +} +func (m *MsgSetDenomMetadataResponse) XXX_Size() int { + return m.Size() +} +func (m *MsgSetDenomMetadataResponse) XXX_DiscardUnknown() { + xxx_messageInfo_MsgSetDenomMetadataResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_MsgSetDenomMetadataResponse proto.InternalMessageInfo + +func init() { + proto.RegisterType((*MsgCreateDenom)(nil), "osmosis.tokenfactory.v1beta1.MsgCreateDenom") + proto.RegisterType((*MsgCreateDenomResponse)(nil), "osmosis.tokenfactory.v1beta1.MsgCreateDenomResponse") + proto.RegisterType((*MsgMint)(nil), "osmosis.tokenfactory.v1beta1.MsgMint") + proto.RegisterType((*MsgMintResponse)(nil), "osmosis.tokenfactory.v1beta1.MsgMintResponse") + proto.RegisterType((*MsgBurn)(nil), "osmosis.tokenfactory.v1beta1.MsgBurn") + proto.RegisterType((*MsgBurnResponse)(nil), "osmosis.tokenfactory.v1beta1.MsgBurnResponse") + proto.RegisterType((*MsgChangeAdmin)(nil), "osmosis.tokenfactory.v1beta1.MsgChangeAdmin") + proto.RegisterType((*MsgChangeAdminResponse)(nil), "osmosis.tokenfactory.v1beta1.MsgChangeAdminResponse") + proto.RegisterType((*MsgSetDenomMetadata)(nil), "osmosis.tokenfactory.v1beta1.MsgSetDenomMetadata") + proto.RegisterType((*MsgSetDenomMetadataResponse)(nil), "osmosis.tokenfactory.v1beta1.MsgSetDenomMetadataResponse") +} + +func init() { + proto.RegisterFile("osmosis/tokenfactory/v1beta1/tx.proto", fileDescriptor_283b6c9a90a846b4) +} + +var fileDescriptor_283b6c9a90a846b4 = []byte{ + // 593 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xc4, 0x55, 0x41, 0x6e, 0xd3, 0x40, + 0x14, 0x8d, 0x69, 0x29, 0xe9, 0x94, 0x92, 0xd4, 0x2d, 0x25, 0x18, 0x6a, 0xa3, 0x91, 0x8a, 0x40, + 0xa2, 0xb6, 0x12, 0x58, 0x00, 0x3b, 0x5c, 0x16, 0x6c, 0xbc, 0x31, 0xac, 0x50, 0xa5, 0x6a, 0x9c, + 0x0c, 0xae, 0x95, 0x78, 0x26, 0x64, 0x26, 0x4d, 0xb3, 0x41, 0x1c, 0x81, 0x05, 0xe2, 0x10, 0x9c, + 0xa4, 0xcb, 0x2e, 0x59, 0x59, 0x28, 0xb9, 0x41, 0x4e, 0x80, 0x3c, 0x33, 0x76, 0x9c, 0x06, 0x91, + 0x64, 0xc5, 0x2e, 0xf6, 0x7f, 0xef, 0xcd, 0x9b, 0xf7, 0xff, 0x8f, 0xc1, 0x21, 0x65, 0x31, 0x65, + 0x11, 0x73, 0x38, 0x6d, 0x63, 0xf2, 0x09, 0x35, 0x39, 0xed, 0x0d, 0x9d, 0xf3, 0x7a, 0x80, 0x39, + 0xaa, 0x3b, 0xfc, 0xc2, 0xee, 0xf6, 0x28, 0xa7, 0xfa, 0x43, 0x05, 0xb3, 0x8b, 0x30, 0x5b, 0xc1, + 0x8c, 0xbd, 0x90, 0x86, 0x54, 0x00, 0x9d, 0xf4, 0x97, 0xe4, 0x18, 0x66, 0x53, 0x90, 0x9c, 0x00, + 0x31, 0x9c, 0x2b, 0x36, 0x69, 0x44, 0xe6, 0xea, 0xa4, 0x9d, 0xd7, 0xd3, 0x07, 0x59, 0x87, 0x1d, + 0x70, 0xc7, 0x63, 0xe1, 0x71, 0x0f, 0x23, 0x8e, 0xdf, 0x62, 0x42, 0x63, 0xfd, 0x29, 0xd8, 0x60, + 0x98, 0xb4, 0x70, 0xaf, 0xa6, 0x3d, 0xd2, 0x9e, 0x6c, 0xba, 0x3b, 0x93, 0xc4, 0xda, 0x1e, 0xa2, + 0xb8, 0xf3, 0x1a, 0xca, 0xf7, 0xd0, 0x57, 0x00, 0xdd, 0x01, 0x65, 0xd6, 0x0f, 0x5a, 0x29, 0xad, + 0x76, 0x43, 0x80, 0x77, 0x27, 0x89, 0x55, 0x51, 0x60, 0x55, 0x81, 0x7e, 0x0e, 0x82, 0x27, 0x60, + 0x7f, 0xf6, 0x34, 0x1f, 0xb3, 0x2e, 0x25, 0x0c, 0xeb, 0x2e, 0xa8, 0x10, 0x3c, 0x38, 0x15, 0x37, + 0x3f, 0x95, 0x8a, 0xf2, 0x78, 0x63, 0x92, 0x58, 0xfb, 0x52, 0xf1, 0x1a, 0x00, 0xfa, 0xdb, 0x04, + 0x0f, 0x3e, 0xa4, 0x2f, 0x84, 0x16, 0xfc, 0x02, 0x6e, 0x79, 0x2c, 0xf4, 0x22, 0xc2, 0x57, 0xb9, + 0xc4, 0x3b, 0xb0, 0x81, 0x62, 0xda, 0x27, 0x5c, 0x5c, 0x61, 0xab, 0x71, 0xdf, 0x96, 0x91, 0xd9, + 0x69, 0xa4, 0x59, 0xfa, 0xf6, 0x31, 0x8d, 0x88, 0x7b, 0xf7, 0x32, 0xb1, 0x4a, 0x53, 0x25, 0x49, + 0x83, 0xbe, 0xe2, 0xc3, 0x1d, 0x50, 0x51, 0xe7, 0x67, 0xd7, 0x52, 0x96, 0xdc, 0x7e, 0x8f, 0xfc, + 0x4f, 0x4b, 0xe9, 0xf9, 0xb9, 0xa5, 0x1f, 0x9a, 0x6c, 0xf9, 0x19, 0x22, 0x21, 0x7e, 0xd3, 0x8a, + 0xa3, 0x95, 0xac, 0x3d, 0x06, 0x37, 0x8b, 0xfd, 0xae, 0x4e, 0x12, 0xeb, 0xb6, 0x44, 0xaa, 0x9e, + 0xc8, 0xb2, 0x5e, 0x07, 0x9b, 0x69, 0xbb, 0x50, 0xaa, 0x5f, 0x5b, 0x13, 0xd8, 0xbd, 0x49, 0x62, + 0x55, 0xa7, 0x9d, 0x14, 0x25, 0xe8, 0x97, 0x09, 0x1e, 0x08, 0x17, 0xb0, 0x26, 0x87, 0x63, 0xea, + 0x2b, 0xb7, 0xfc, 0x5d, 0x03, 0xbb, 0x1e, 0x0b, 0xdf, 0x63, 0x2e, 0x1a, 0xed, 0x61, 0x8e, 0x5a, + 0x88, 0xa3, 0x55, 0x7c, 0xfb, 0xa0, 0x1c, 0x2b, 0x9a, 0x0a, 0xf5, 0x60, 0x1a, 0x2a, 0x69, 0xe7, + 0xa1, 0x66, 0xda, 0xee, 0x3d, 0x15, 0xac, 0x9a, 0xe6, 0x8c, 0x0c, 0xfd, 0x5c, 0x07, 0x1e, 0x80, + 0x07, 0x7f, 0x71, 0x95, 0xb9, 0x6e, 0xfc, 0x5c, 0x07, 0x6b, 0x1e, 0x0b, 0xf5, 0xcf, 0x60, 0xab, + 0xb8, 0x5f, 0xcf, 0xec, 0x7f, 0xad, 0xb9, 0x3d, 0xbb, 0x1f, 0xc6, 0x8b, 0x55, 0xd0, 0xf9, 0x36, + 0x9d, 0x80, 0x75, 0xb1, 0x06, 0x87, 0x0b, 0xd9, 0x29, 0xcc, 0x38, 0x5a, 0x0a, 0x56, 0x54, 0x17, + 0x13, 0xbd, 0x58, 0x3d, 0x85, 0x2d, 0xa1, 0x5e, 0x9c, 0x4f, 0x11, 0x57, 0x61, 0x36, 0x97, 0x88, + 0x6b, 0x8a, 0x5e, 0x26, 0xae, 0xf9, 0xf9, 0xd2, 0xbf, 0x6a, 0xa0, 0x3a, 0x37, 0x5c, 0xf5, 0x85, + 0x52, 0xd7, 0x29, 0xc6, 0xab, 0x95, 0x29, 0x99, 0x05, 0xd7, 0xbf, 0x1c, 0x99, 0xda, 0xd5, 0xc8, + 0xd4, 0x7e, 0x8f, 0x4c, 0xed, 0xdb, 0xd8, 0x2c, 0x5d, 0x8d, 0xcd, 0xd2, 0xaf, 0xb1, 0x59, 0xfa, + 0xf8, 0x32, 0x8c, 0xf8, 0x59, 0x3f, 0xb0, 0x9b, 0x34, 0x76, 0x94, 0xfc, 0x51, 0x07, 0x05, 0x2c, + 0x7b, 0x70, 0xce, 0xeb, 0x0d, 0xe7, 0x62, 0xf6, 0xd3, 0xc2, 0x87, 0x5d, 0xcc, 0x82, 0x0d, 0xf1, + 0x17, 0xff, 0xfc, 0x4f, 0x00, 0x00, 0x00, 0xff, 0xff, 0xf7, 0x60, 0x4c, 0x1d, 0x7f, 0x06, 0x00, + 0x00, +} + +// Reference imports to suppress errors if they are not otherwise used. +var _ context.Context +var _ grpc.ClientConn + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the grpc package it is being compiled against. +const _ = grpc.SupportPackageIsVersion4 + +// MsgClient is the client API for Msg service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://godoc.org/google.golang.org/grpc#ClientConn.NewStream. +type MsgClient interface { + CreateDenom(ctx context.Context, in *MsgCreateDenom, opts ...grpc.CallOption) (*MsgCreateDenomResponse, error) + Mint(ctx context.Context, in *MsgMint, opts ...grpc.CallOption) (*MsgMintResponse, error) + Burn(ctx context.Context, in *MsgBurn, opts ...grpc.CallOption) (*MsgBurnResponse, error) + ChangeAdmin(ctx context.Context, in *MsgChangeAdmin, opts ...grpc.CallOption) (*MsgChangeAdminResponse, error) + SetDenomMetadata(ctx context.Context, in *MsgSetDenomMetadata, opts ...grpc.CallOption) (*MsgSetDenomMetadataResponse, error) +} + +type msgClient struct { + cc grpc1.ClientConn +} + +func NewMsgClient(cc grpc1.ClientConn) MsgClient { + return &msgClient{cc} +} + +func (c *msgClient) CreateDenom(ctx context.Context, in *MsgCreateDenom, opts ...grpc.CallOption) (*MsgCreateDenomResponse, error) { + out := new(MsgCreateDenomResponse) + err := c.cc.Invoke(ctx, "/osmosis.tokenfactory.v1beta1.Msg/CreateDenom", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *msgClient) Mint(ctx context.Context, in *MsgMint, opts ...grpc.CallOption) (*MsgMintResponse, error) { + out := new(MsgMintResponse) + err := c.cc.Invoke(ctx, "/osmosis.tokenfactory.v1beta1.Msg/Mint", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *msgClient) Burn(ctx context.Context, in *MsgBurn, opts ...grpc.CallOption) (*MsgBurnResponse, error) { + out := new(MsgBurnResponse) + err := c.cc.Invoke(ctx, "/osmosis.tokenfactory.v1beta1.Msg/Burn", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *msgClient) ChangeAdmin(ctx context.Context, in *MsgChangeAdmin, opts ...grpc.CallOption) (*MsgChangeAdminResponse, error) { + out := new(MsgChangeAdminResponse) + err := c.cc.Invoke(ctx, "/osmosis.tokenfactory.v1beta1.Msg/ChangeAdmin", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *msgClient) SetDenomMetadata(ctx context.Context, in *MsgSetDenomMetadata, opts ...grpc.CallOption) (*MsgSetDenomMetadataResponse, error) { + out := new(MsgSetDenomMetadataResponse) + err := c.cc.Invoke(ctx, "/osmosis.tokenfactory.v1beta1.Msg/SetDenomMetadata", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +// MsgServer is the server API for Msg service. +type MsgServer interface { + CreateDenom(context.Context, *MsgCreateDenom) (*MsgCreateDenomResponse, error) + Mint(context.Context, *MsgMint) (*MsgMintResponse, error) + Burn(context.Context, *MsgBurn) (*MsgBurnResponse, error) + ChangeAdmin(context.Context, *MsgChangeAdmin) (*MsgChangeAdminResponse, error) + SetDenomMetadata(context.Context, *MsgSetDenomMetadata) (*MsgSetDenomMetadataResponse, error) +} + +// UnimplementedMsgServer can be embedded to have forward compatible implementations. +type UnimplementedMsgServer struct { +} + +func (*UnimplementedMsgServer) CreateDenom(ctx context.Context, req *MsgCreateDenom) (*MsgCreateDenomResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method CreateDenom not implemented") +} +func (*UnimplementedMsgServer) Mint(ctx context.Context, req *MsgMint) (*MsgMintResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method Mint not implemented") +} +func (*UnimplementedMsgServer) Burn(ctx context.Context, req *MsgBurn) (*MsgBurnResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method Burn not implemented") +} +func (*UnimplementedMsgServer) ChangeAdmin(ctx context.Context, req *MsgChangeAdmin) (*MsgChangeAdminResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method ChangeAdmin not implemented") +} +func (*UnimplementedMsgServer) SetDenomMetadata(ctx context.Context, req *MsgSetDenomMetadata) (*MsgSetDenomMetadataResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method SetDenomMetadata not implemented") +} + +func RegisterMsgServer(s grpc1.Server, srv MsgServer) { + s.RegisterService(&_Msg_serviceDesc, srv) +} + +func _Msg_CreateDenom_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(MsgCreateDenom) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(MsgServer).CreateDenom(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/osmosis.tokenfactory.v1beta1.Msg/CreateDenom", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(MsgServer).CreateDenom(ctx, req.(*MsgCreateDenom)) + } + return interceptor(ctx, in, info, handler) +} + +func _Msg_Mint_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(MsgMint) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(MsgServer).Mint(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/osmosis.tokenfactory.v1beta1.Msg/Mint", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(MsgServer).Mint(ctx, req.(*MsgMint)) + } + return interceptor(ctx, in, info, handler) +} + +func _Msg_Burn_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(MsgBurn) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(MsgServer).Burn(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/osmosis.tokenfactory.v1beta1.Msg/Burn", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(MsgServer).Burn(ctx, req.(*MsgBurn)) + } + return interceptor(ctx, in, info, handler) +} + +func _Msg_ChangeAdmin_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(MsgChangeAdmin) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(MsgServer).ChangeAdmin(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/osmosis.tokenfactory.v1beta1.Msg/ChangeAdmin", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(MsgServer).ChangeAdmin(ctx, req.(*MsgChangeAdmin)) + } + return interceptor(ctx, in, info, handler) +} + +func _Msg_SetDenomMetadata_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(MsgSetDenomMetadata) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(MsgServer).SetDenomMetadata(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/osmosis.tokenfactory.v1beta1.Msg/SetDenomMetadata", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(MsgServer).SetDenomMetadata(ctx, req.(*MsgSetDenomMetadata)) + } + return interceptor(ctx, in, info, handler) +} + +var _Msg_serviceDesc = grpc.ServiceDesc{ + ServiceName: "osmosis.tokenfactory.v1beta1.Msg", + HandlerType: (*MsgServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "CreateDenom", + Handler: _Msg_CreateDenom_Handler, + }, + { + MethodName: "Mint", + Handler: _Msg_Mint_Handler, + }, + { + MethodName: "Burn", + Handler: _Msg_Burn_Handler, + }, + { + MethodName: "ChangeAdmin", + Handler: _Msg_ChangeAdmin_Handler, + }, + { + MethodName: "SetDenomMetadata", + Handler: _Msg_SetDenomMetadata_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "osmosis/tokenfactory/v1beta1/tx.proto", +} + +func (m *MsgCreateDenom) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *MsgCreateDenom) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *MsgCreateDenom) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.Subdenom) > 0 { + i -= len(m.Subdenom) + copy(dAtA[i:], m.Subdenom) + i = encodeVarintTx(dAtA, i, uint64(len(m.Subdenom))) + i-- + dAtA[i] = 0x12 + } + if len(m.Sender) > 0 { + i -= len(m.Sender) + copy(dAtA[i:], m.Sender) + i = encodeVarintTx(dAtA, i, uint64(len(m.Sender))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *MsgCreateDenomResponse) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *MsgCreateDenomResponse) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *MsgCreateDenomResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.NewTokenDenom) > 0 { + i -= len(m.NewTokenDenom) + copy(dAtA[i:], m.NewTokenDenom) + i = encodeVarintTx(dAtA, i, uint64(len(m.NewTokenDenom))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *MsgMint) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *MsgMint) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *MsgMint) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + { + size, err := m.Amount.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintTx(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x12 + if len(m.Sender) > 0 { + i -= len(m.Sender) + copy(dAtA[i:], m.Sender) + i = encodeVarintTx(dAtA, i, uint64(len(m.Sender))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *MsgMintResponse) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *MsgMintResponse) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *MsgMintResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + return len(dAtA) - i, nil +} + +func (m *MsgBurn) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *MsgBurn) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *MsgBurn) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + { + size, err := m.Amount.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintTx(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x12 + if len(m.Sender) > 0 { + i -= len(m.Sender) + copy(dAtA[i:], m.Sender) + i = encodeVarintTx(dAtA, i, uint64(len(m.Sender))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *MsgBurnResponse) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *MsgBurnResponse) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *MsgBurnResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + return len(dAtA) - i, nil +} + +func (m *MsgChangeAdmin) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *MsgChangeAdmin) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *MsgChangeAdmin) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.NewAdmin) > 0 { + i -= len(m.NewAdmin) + copy(dAtA[i:], m.NewAdmin) + i = encodeVarintTx(dAtA, i, uint64(len(m.NewAdmin))) + i-- + dAtA[i] = 0x1a + } + if len(m.Denom) > 0 { + i -= len(m.Denom) + copy(dAtA[i:], m.Denom) + i = encodeVarintTx(dAtA, i, uint64(len(m.Denom))) + i-- + dAtA[i] = 0x12 + } + if len(m.Sender) > 0 { + i -= len(m.Sender) + copy(dAtA[i:], m.Sender) + i = encodeVarintTx(dAtA, i, uint64(len(m.Sender))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *MsgChangeAdminResponse) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *MsgChangeAdminResponse) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *MsgChangeAdminResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + return len(dAtA) - i, nil +} + +func (m *MsgSetDenomMetadata) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *MsgSetDenomMetadata) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *MsgSetDenomMetadata) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + { + size, err := m.Metadata.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintTx(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x12 + if len(m.Sender) > 0 { + i -= len(m.Sender) + copy(dAtA[i:], m.Sender) + i = encodeVarintTx(dAtA, i, uint64(len(m.Sender))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *MsgSetDenomMetadataResponse) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *MsgSetDenomMetadataResponse) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *MsgSetDenomMetadataResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + return len(dAtA) - i, nil +} + +func encodeVarintTx(dAtA []byte, offset int, v uint64) int { + offset -= sovTx(v) + base := offset + for v >= 1<<7 { + dAtA[offset] = uint8(v&0x7f | 0x80) + v >>= 7 + offset++ + } + dAtA[offset] = uint8(v) + return base +} +func (m *MsgCreateDenom) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.Sender) + if l > 0 { + n += 1 + l + sovTx(uint64(l)) + } + l = len(m.Subdenom) + if l > 0 { + n += 1 + l + sovTx(uint64(l)) + } + return n +} + +func (m *MsgCreateDenomResponse) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.NewTokenDenom) + if l > 0 { + n += 1 + l + sovTx(uint64(l)) + } + return n +} + +func (m *MsgMint) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.Sender) + if l > 0 { + n += 1 + l + sovTx(uint64(l)) + } + l = m.Amount.Size() + n += 1 + l + sovTx(uint64(l)) + return n +} + +func (m *MsgMintResponse) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + return n +} + +func (m *MsgBurn) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.Sender) + if l > 0 { + n += 1 + l + sovTx(uint64(l)) + } + l = m.Amount.Size() + n += 1 + l + sovTx(uint64(l)) + return n +} + +func (m *MsgBurnResponse) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + return n +} + +func (m *MsgChangeAdmin) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.Sender) + if l > 0 { + n += 1 + l + sovTx(uint64(l)) + } + l = len(m.Denom) + if l > 0 { + n += 1 + l + sovTx(uint64(l)) + } + l = len(m.NewAdmin) + if l > 0 { + n += 1 + l + sovTx(uint64(l)) + } + return n +} + +func (m *MsgChangeAdminResponse) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + return n +} + +func (m *MsgSetDenomMetadata) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.Sender) + if l > 0 { + n += 1 + l + sovTx(uint64(l)) + } + l = m.Metadata.Size() + n += 1 + l + sovTx(uint64(l)) + return n +} + +func (m *MsgSetDenomMetadataResponse) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + return n +} + +func sovTx(x uint64) (n int) { + return (math_bits.Len64(x|1) + 6) / 7 +} +func sozTx(x uint64) (n int) { + return sovTx(uint64((x << 1) ^ uint64((int64(x) >> 63)))) +} +func (m *MsgCreateDenom) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: MsgCreateDenom: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: MsgCreateDenom: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Sender", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Sender = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Subdenom", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Subdenom = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipTx(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthTx + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *MsgCreateDenomResponse) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: MsgCreateDenomResponse: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: MsgCreateDenomResponse: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field NewTokenDenom", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.NewTokenDenom = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipTx(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthTx + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *MsgMint) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: MsgMint: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: MsgMint: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Sender", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Sender = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Amount", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.Amount.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipTx(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthTx + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *MsgMintResponse) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: MsgMintResponse: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: MsgMintResponse: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + default: + iNdEx = preIndex + skippy, err := skipTx(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthTx + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *MsgBurn) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: MsgBurn: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: MsgBurn: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Sender", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Sender = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Amount", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.Amount.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipTx(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthTx + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *MsgBurnResponse) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: MsgBurnResponse: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: MsgBurnResponse: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + default: + iNdEx = preIndex + skippy, err := skipTx(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthTx + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *MsgChangeAdmin) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: MsgChangeAdmin: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: MsgChangeAdmin: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Sender", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Sender = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Denom", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Denom = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field NewAdmin", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.NewAdmin = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipTx(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthTx + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *MsgChangeAdminResponse) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: MsgChangeAdminResponse: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: MsgChangeAdminResponse: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + default: + iNdEx = preIndex + skippy, err := skipTx(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthTx + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *MsgSetDenomMetadata) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: MsgSetDenomMetadata: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: MsgSetDenomMetadata: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Sender", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Sender = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Metadata", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.Metadata.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipTx(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthTx + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *MsgSetDenomMetadataResponse) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: MsgSetDenomMetadataResponse: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: MsgSetDenomMetadataResponse: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + default: + iNdEx = preIndex + skippy, err := skipTx(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthTx + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func skipTx(dAtA []byte) (n int, err error) { + l := len(dAtA) + iNdEx := 0 + depth := 0 + for iNdEx < l { + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowTx + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + wireType := int(wire & 0x7) + switch wireType { + case 0: + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowTx + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + iNdEx++ + if dAtA[iNdEx-1] < 0x80 { + break + } + } + case 1: + iNdEx += 8 + case 2: + var length int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowTx + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + length |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if length < 0 { + return 0, ErrInvalidLengthTx + } + iNdEx += length + case 3: + depth++ + case 4: + if depth == 0 { + return 0, ErrUnexpectedEndOfGroupTx + } + depth-- + case 5: + iNdEx += 4 + default: + return 0, fmt.Errorf("proto: illegal wireType %d", wireType) + } + if iNdEx < 0 { + return 0, ErrInvalidLengthTx + } + if depth == 0 { + return iNdEx, nil + } + } + return 0, io.ErrUnexpectedEOF +} + +var ( + ErrInvalidLengthTx = fmt.Errorf("proto: negative length found during unmarshaling") + ErrIntOverflowTx = fmt.Errorf("proto: integer overflow") + ErrUnexpectedEndOfGroupTx = fmt.Errorf("proto: unexpected end of group") +)