diff --git a/docs/core/examples/app1.go b/docs/core/examples/app1.go index fbbfb575c2..df5c57eb96 100644 --- a/docs/core/examples/app1.go +++ b/docs/core/examples/app1.go @@ -114,7 +114,6 @@ func NewApp1Handler(keyAcc *sdk.KVStoreKey) sdk.Handler { // Handle MsgSend. func handleMsgSend(ctx sdk.Context, key *sdk.KVStoreKey, msg MsgSend) sdk.Result { // NOTE: from, to, and amount were already validated - store := ctx.KVStore(key) // deduct msg amount from sender account @@ -155,10 +154,10 @@ func handleMsgSend(ctx sdk.Context, key *sdk.KVStoreKey, msg MsgSend) sdk.Result bz = store.Get(msg.To) var acc2 account if bz == nil { - // Sender account does not already exist, create a new one. - acc2 = account{} + // Receiver account does not already exist, create a new one. + acc2 = account{Address: msg.To} } else { - // Sender account already exists. Retrieve and decode it. + // Receiver account already exists. Retrieve and decode it. err = json.Unmarshal(bz, &acc2) if err != nil { return sdk.ErrInternal("Account decoding error").Result() @@ -185,7 +184,9 @@ func handleMsgSend(ctx sdk.Context, key *sdk.KVStoreKey, msg MsgSend) sdk.Result } type account struct { + Address sdk.Address `json:"address"` Coins sdk.Coins `json:"coins"` + SequenceNumber int64 `json:"sequence"` } //------------------------------------------------------------------ diff --git a/docs/core/examples/app2.go b/docs/core/examples/app2.go index fec259ca99..034b8b05ce 100644 --- a/docs/core/examples/app2.go +++ b/docs/core/examples/app2.go @@ -2,23 +2,35 @@ package app import ( "reflect" + "encoding/json" + "fmt" cmn "github.com/tendermint/tmlibs/common" dbm "github.com/tendermint/tmlibs/db" "github.com/tendermint/tmlibs/log" + "github.com/tendermint/go-crypto" bapp "github.com/cosmos/cosmos-sdk/baseapp" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/wire" + "github.com/cosmos/cosmos-sdk/x/auth" ) const ( app2Name = "App2" ) +var ( + issuer = crypto.GenPrivKeyEd25519().PubKey().Address() +) + func NewCodec() *wire.Codec { // TODO register - return nil + cdc := wire.NewCodec() + cdc.RegisterInterface((*sdk.Msg)(nil), nil) + cdc.RegisterConcrete(MsgSend{}, "example/MsgSend", nil) + cdc.RegisterConcrete(MsgIssue{}, "example/MsgIssue", nil) + return cdc } func NewApp2(logger log.Logger, db dbm.DB) *bapp.BaseApp { @@ -29,16 +41,19 @@ func NewApp2(logger log.Logger, db dbm.DB) *bapp.BaseApp { app := bapp.NewBaseApp(app2Name, cdc, logger, db) // Create a key for accessing the account store. + keyMain := sdk.NewKVStoreKey("main") keyAccount := sdk.NewKVStoreKey("acc") - keyIssuer := sdk.NewKVStoreKey("issuer") + + // set antehandler function + app.SetAnteHandler(antehandler) // Register message routes. // Note the handler gets access to the account store. app.Router(). - AddRoute("bank", NewApp2Handler(keyAccount, keyIssuer)) + AddRoute("bank", NewApp2Handler(keyAccount, keyMain)) // Mount stores and load the latest state. - app.MountStoresIAVL(keyAccount, keyIssuer) + app.MountStoresIAVL(keyAccount, keyMain) err := app.LoadLatestVersion(keyAccount) if err != nil { cmn.Exit(err.Error()) @@ -46,21 +61,73 @@ func NewApp2(logger log.Logger, db dbm.DB) *bapp.BaseApp { return app } +// Coin Metadata +type CoinMetadata struct { + TotalSupply sdk.Int + CurrentSupply sdk.Int + Issuer sdk.Address + Decimal uint64 +} + //------------------------------------------------------------------ // Msgs -// TODO: MsgIssue +// Create Output struct to allow single message to issue arbitrary coins to multiple users +type Output struct { + Address sdk.Address + Coins sdk.Coins +} + +// Single permissioned issuer can issue multiple outputs +// Implements sdk.Msg Interface +type MsgIssue struct { + Issuer sdk.Address + Outputs []Output +} + +// nolint +func (msg MsgIssue) Type() string { return "bank" } + +func (msg MsgIssue) ValidateBasic() sdk.Error { + if len(msg.Issuer) == 0 { + return sdk.ErrInvalidAddress("Issuer address cannot be empty") + } + + for _, o := range msg.Outputs { + if len(o.Address) == 0 { + return sdk.ErrInvalidAddress("Output address cannot be empty") + } + // Cannot issue zero or negative coins + if !o.Coins.IsPositive() { + return sdk.ErrInvalidCoins("Cannot issue 0 or negative coin amounts") + } + } + return nil +} + +func (msg MsgIssue) GetSignBytes() []byte { + bz, err := json.Marshal(msg) + if err != nil { + panic(err) + } + return bz +} + +// Implements Msg. Return the signer. +func (msg MsgIssue) GetSigners() []sdk.Address { + return []sdk.Address{msg.Issuer} +} //------------------------------------------------------------------ // Handler for the message -func NewApp1Handler(keyAcc *sdk.KVStoreKey) sdk.Handler { +func NewApp2Handler(keyAcc *sdk.KVStoreKey, keyMain *sdk.KVStoreKey) sdk.Handler { return func(ctx sdk.Context, msg sdk.Msg) sdk.Result { switch msg := msg.(type) { case MsgSend: return handleMsgSend(ctx, keyAcc, msg) case MsgIssue: - // TODO + return handleMsgIssue(ctx, keyMain, keyAcc, msg) default: errMsg := "Unrecognized bank Msg type: " + reflect.TypeOf(msg).Name() return sdk.ErrUnknownRequest(errMsg).Result() @@ -68,12 +135,102 @@ func NewApp1Handler(keyAcc *sdk.KVStoreKey) sdk.Handler { } } +// Handle Msg Issue +func handleMsgIssue(ctx sdk.Context, keyMain *sdk.KVStoreKey, keyAcc *sdk.KVStoreKey, msg MsgIssue) sdk.Result { + store := ctx.KVStore(keyMain) + accStore := ctx.KVStore(keyAcc) + + for _, o := range msg.Outputs { + for _, coin := range o.Coins { + bz := store.Get([]byte(coin.Denom)) + var metadata CoinMetadata + + if bz == nil { + // Coin not set yet, initialize with issuer and default values + // Coin amount can't be above default value + if coin.Amount.GT(sdk.NewInt(1000000)) { + return sdk.ErrInvalidCoins("Cannot issue that many new coins").Result() + } + metadata = CoinMetadata{ + TotalSupply: sdk.NewInt(1000000), + CurrentSupply: sdk.NewInt(0), + Issuer: msg.Issuer, + Decimal: 10, + } + } else { + // Decode coin metadata + err := json.Unmarshal(bz, &metadata) + if err != nil { + return sdk.ErrInternal("Decoding coin metadata failed").Result() + } + } + + // Return error result if msg Issuer is not equal to coin issuer + if !reflect.DeepEqual(metadata.Issuer, msg.Issuer) { + return sdk.ErrUnauthorized(fmt.Sprintf("Msg issuer cannot issue these coins: %s", coin.Denom)).Result() + } + + // Issuer cannot issue more than remaining supply + issuerSupply := metadata.TotalSupply.Sub(metadata.CurrentSupply) + if coin.Amount.GT(issuerSupply) { + return sdk.ErrInsufficientCoins(fmt.Sprintf("Issuer cannot issue that many coins. Current issuer supply: %d", issuerSupply.Int64())).Result() + } + + // Update coin metadata + metadata.CurrentSupply = metadata.CurrentSupply.Add(coin.Amount) + + val, err := json.Marshal(metadata) + if err != nil { + return sdk.ErrInternal("Encoding coin metadata failed").Result() + } + + // Update coin metadata in store + store.Set([]byte(coin.Denom), val) + } + + // Add coins to receiver account + bz := accStore.Get(o.Address) + var acc account + if bz == nil { + // Receiver account does not already exist, create a new one. + acc = account{} + } else { + // Receiver account already exists. Retrieve and decode it. + err := json.Unmarshal(bz, &acc) + if err != nil { + return sdk.ErrInternal("Account decoding error").Result() + } + } + + // Add amount to receiver's old coins + receiverCoins := acc.Coins.Plus(o.Coins) + + // Update receiver account + acc.Coins = receiverCoins + + // Encode receiver account + val, err := json.Marshal(acc) + if err != nil { + return sdk.ErrInternal("Account encoding error").Result() + } + + // set account with new issued coins in store + store.Set(o.Address, val) + } + + return sdk.Result{ + // TODO: Tags + } + +} + //------------------------------------------------------------------ // Tx // Simple tx to wrap the Msg. type app2Tx struct { sdk.Msg + Signatures []auth.StdSignature } // This tx only has one Msg. @@ -85,3 +242,46 @@ func (tx app2Tx) GetMsgs() []sdk.Msg { func (tx app2Tx) GetMemo() string { return "" } + +func (tx app2Tx) GetSignatures() []auth.StdSignature { + return tx.Signatures +} + +//------------------------------------------------------------------ + +// Simple antehandler that ensures msg signers has signed over msg signBytes w/ no replay protection +// Implement sdk.AnteHandler interface +func antehandler(ctx sdk.Context, tx sdk.Tx) (_ sdk.Context, _ sdk.Result, abort bool) { + appTx, ok := tx.(app2Tx) + if !ok { + // set abort boolean to true so that we don't continue to process failed tx + return ctx, sdk.ErrTxDecode("Tx must be of format app2Tx").Result(), true + } + + // expect only one msg in app2Tx + msg := tx.GetMsgs()[0] + + signerAddrs := msg.GetSigners() + signBytes := msg.GetSignBytes() + + if len(signerAddrs) != len(appTx.GetSignatures()) { + return ctx, sdk.ErrUnauthorized("Number of signatures do not match required amount").Result(), true + } + + for i, addr := range signerAddrs { + sig := appTx.GetSignatures()[i] + + // check that submitted pubkey belongs to required address + if !reflect.DeepEqual(sig.PubKey.Address(), addr) { + return ctx, sdk.ErrUnauthorized("Provided Pubkey does not match required address").Result(), true + } + + // check that signature is over expected signBytes + if !sig.PubKey.VerifyBytes(signBytes, sig.Signature) { + return ctx, sdk.ErrUnauthorized("Signature verification failed").Result(), true + } + } + + // authentication passed, app to continue processing by sending msg to handler + return ctx, sdk.Result{}, false +} diff --git a/docs/core/examples/app3.go b/docs/core/examples/app3.go index b176816b75..227fbe47d1 100644 --- a/docs/core/examples/app3.go +++ b/docs/core/examples/app3.go @@ -1,12 +1,19 @@ package app import ( + "reflect" + "encoding/json" + "fmt" + cmn "github.com/tendermint/tmlibs/common" dbm "github.com/tendermint/tmlibs/db" "github.com/tendermint/tmlibs/log" bapp "github.com/cosmos/cosmos-sdk/baseapp" sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/auth" + "github.com/cosmos/cosmos-sdk/x/bank" + ) const ( @@ -22,17 +29,24 @@ func NewApp3(logger log.Logger, db dbm.DB) *bapp.BaseApp { // Create a key for accessing the account store. keyAccount := sdk.NewKVStoreKey("acc") - keyIssuer := sdk.NewKVStoreKey("issuer") + keyMain := sdk.NewKVStoreKey("main") + keyFees := sdk.NewKVStoreKey("fee") - // TODO: accounts, ante handler + // Set various mappers/keepers to interact easily with underlying stores + accountMapper := auth.NewAccountMapper(cdc, keyAccount, &auth.BaseAccount{}) + accountKeeper := bank.NewKeeper(accountMapper) + metadataMapper := NewApp3MetaDataMapper(keyMain) + feeKeeper := auth.NewFeeCollectionKeeper(cdc, keyFees) + + app.SetAnteHandler(auth.NewAnteHandler(accountMapper, feeKeeper)) // Register message routes. // Note the handler gets access to the account store. app.Router(). - AddRoute("bank", NewApp2Handler(keyAccount, keyIssuer)) + AddRoute("bank", NewApp3Handler(accountKeeper, metadataMapper)) // Mount stores and load the latest state. - app.MountStoresIAVL(keyAccount, keyIssuer) + app.MountStoresIAVL(keyAccount, keyMain, keyFees) err := app.LoadLatestVersion(keyAccount) if err != nil { cmn.Exit(err.Error()) @@ -40,6 +54,124 @@ func NewApp3(logger log.Logger, db dbm.DB) *bapp.BaseApp { return app } +func NewApp3Handler(accountKeeper bank.Keeper, metadataMapper MetaDataMapper) sdk.Handler { + return func(ctx sdk.Context, msg sdk.Msg) sdk.Result { + switch msg := msg.(type) { + case MsgSend: + return betterHandleMsgSend(ctx, accountKeeper, msg) + case MsgIssue: + return betterHandleMsgIssue(ctx, metadataMapper, accountKeeper, msg) + default: + errMsg := "Unrecognized bank Msg type: " + reflect.TypeOf(msg).Name() + return sdk.ErrUnknownRequest(errMsg).Result() + } + } +} + +func betterHandleMsgSend(ctx sdk.Context, accountKeeper bank.Keeper, msg MsgSend) sdk.Result { + // Subtract coins from sender account + _, _, err := accountKeeper.SubtractCoins(ctx, msg.From, msg.Amount) + if err != nil { + // if error, return its result + return err.Result() + } + + // Add coins to receiver account + _, _, err = accountKeeper.AddCoins(ctx, msg.To, msg.Amount) + if err != nil { + // if error, return its result + return err.Result() + } + + return sdk.Result{} +} + +func betterHandleMsgIssue(ctx sdk.Context, metadataMapper MetaDataMapper, accountKeeper bank.Keeper, msg MsgIssue) sdk.Result { + for _, o := range msg.Outputs { + for _, coin := range o.Coins { + metadata := metadataMapper.GetMetaData(ctx, coin.Denom) + if len(metadata.Issuer) == 0 { + // coin doesn't have issuer yet, set issuer to msg issuer + metadata.Issuer = msg.Issuer + } + + // Check that msg Issuer is authorized to issue these coins + if !reflect.DeepEqual(metadata.Issuer, msg.Issuer) { + return sdk.ErrUnauthorized(fmt.Sprintf("Msg Issuer cannot issue these coins: %s", coin.Denom)).Result() + } + + // Issuer cannot issue more than remaining supply + issuerSupply := metadata.TotalSupply.Sub(metadata.CurrentSupply) + if coin.Amount.GT(issuerSupply) { + return sdk.ErrInsufficientCoins(fmt.Sprintf("Issuer cannot issue that many coins. Current issuer supply: %d", issuerSupply.Int64())).Result() + } + + // update metadata current circulating supply + metadata.CurrentSupply = metadata.CurrentSupply.Add(coin.Amount) + + metadataMapper.SetMetaData(ctx, coin.Denom, metadata) + } + + // Add newly issued coins to output address + _, _, err := accountKeeper.AddCoins(ctx, o.Address, o.Coins) + if err != nil { + return err.Result() + } + } + + return sdk.Result{} +} + + + +//------------------------------------------------------------------ +// Mapper for Coin Metadata +// Example of a very simple user-defined mapper + +type MetaDataMapper interface { + GetMetaData(sdk.Context, string) CoinMetadata + SetMetaData(sdk.Context, string, CoinMetadata) +} + +type App3MetaDataMapper struct { + mainKey *sdk.KVStoreKey +} + +func NewApp3MetaDataMapper(key *sdk.KVStoreKey) App3MetaDataMapper { + return App3MetaDataMapper{mainKey: key} +} + +func (mdm App3MetaDataMapper) GetMetaData(ctx sdk.Context, denom string) CoinMetadata { + store := ctx.KVStore(mdm.mainKey) + + bz := store.Get([]byte(denom)) + if bz == nil { + // Coin metadata doesn't exist, create new metadata with default params + return CoinMetadata{ + TotalSupply: sdk.NewInt(1000000), + } + } + + var metadata CoinMetadata + err := json.Unmarshal(bz, &metadata) + if err != nil { + panic(err) + } + + return metadata +} + +func (mdm App3MetaDataMapper) SetMetaData(ctx sdk.Context, denom string, metadata CoinMetadata) { + store := ctx.KVStore(mdm.mainKey) + + val, err := json.Marshal(metadata) + if err != nil { + panic(err) + } + + store.Set([]byte(denom), val) +} + //------------------------------------------------------------------ // StdTx diff --git a/docs/core/examples/app4.go b/docs/core/examples/app4.go index ae63b4afba..b86976688d 100644 --- a/docs/core/examples/app4.go +++ b/docs/core/examples/app4.go @@ -1,12 +1,20 @@ package app import ( + "encoding/json" + "reflect" + "fmt" + cmn "github.com/tendermint/tmlibs/common" dbm "github.com/tendermint/tmlibs/db" "github.com/tendermint/tmlibs/log" + abci "github.com/tendermint/abci/types" bapp "github.com/cosmos/cosmos-sdk/baseapp" sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/wire" + "github.com/cosmos/cosmos-sdk/x/auth" + "github.com/cosmos/cosmos-sdk/x/bank" ) const ( @@ -18,23 +26,31 @@ func NewApp4(logger log.Logger, db dbm.DB) *bapp.BaseApp { cdc := NewCodec() // Create the base application object. - app := bapp.NewBaseApp(app4Name, cdc, logger, db) + app := bapp.NewBaseApp(app3Name, cdc, logger, db) // Create a key for accessing the account store. keyAccount := sdk.NewKVStoreKey("acc") - keyIssuer := sdk.NewKVStoreKey("issuer") + keyMain := sdk.NewKVStoreKey("main") + keyFees := sdk.NewKVStoreKey("fee") - // TODO: accounts, ante handler + // Set various mappers/keepers to interact easily with underlying stores + accountMapper := auth.NewAccountMapper(cdc, keyAccount, &auth.BaseAccount{}) + accountKeeper := bank.NewKeeper(accountMapper) + metadataMapper := NewApp4MetaDataMapper(keyMain) + feeKeeper := auth.NewFeeCollectionKeeper(cdc, keyFees) - // TODO: AccountMapper, CoinKeepr + app.SetAnteHandler(auth.NewAnteHandler(accountMapper, feeKeeper)) + + // Set InitChainer + app.SetInitChainer(NewInitChainer(cdc, accountMapper, metadataMapper)) // Register message routes. // Note the handler gets access to the account store. app.Router(). - AddRoute("bank", NewApp2Handler(keyAccount, keyIssuer)) + AddRoute("bank", NewApp4Handler(accountKeeper, metadataMapper)) // Mount stores and load the latest state. - app.MountStoresIAVL(keyAccount, keyIssuer) + app.MountStoresIAVL(keyAccount, keyMain, keyFees) err := app.LoadLatestVersion(keyAccount) if err != nil { cmn.Exit(err.Error()) @@ -42,6 +58,159 @@ func NewApp4(logger log.Logger, db dbm.DB) *bapp.BaseApp { return app } +type GenesisState struct { + Accounts []*GenesisAccount `json:"accounts"` + Coins []*GenesisCoin `json:"coins"` +} + +// GenesisAccount doesn't need pubkey or sequence +type GenesisAccount struct { + Address sdk.Address `json:"address"` + Coins sdk.Coins `json:"coins"` +} + +func (ga *GenesisAccount) ToAccount() (acc *auth.BaseAccount, err error) { + baseAcc := auth.BaseAccount{ + Address: ga.Address, + Coins: ga.Coins.Sort(), + } + return &baseAcc, nil +} + +// GenesisCoin enforces CurrentSupply is 0 at genesis. +type GenesisCoin struct { + Issuer sdk.Address `json:"issuer"` + TotalSupply sdk.Int `json:"total_supply` + Decimal uint64 `json:"decimals"` +} + +func (gc *GenesisCoin) ToMetaData() CoinMetadata { + return CoinMetadata{ + Issuer: gc.Issuer, + TotalSupply: gc.TotalSupply, + Decimal: gc.Decimal, + } +} + +// InitChainer will set initial balances for accounts as well as initial coin metadata +// MsgIssue can no longer be used to create new coin +func NewInitChainer(cdc *wire.Codec, accountMapper auth.AccountMapper, metadataMapper MetaDataMapper) sdk.InitChainer { + return func(ctx sdk.Context, req abci.RequestInitChain) abci.ResponseInitChain { + stateJSON := req.AppStateBytes + + genesisState := new(GenesisState) + err := cdc.UnmarshalJSON(stateJSON, genesisState) + if err != nil { + panic(err) // TODO https://github.com/cosmos/cosmos-sdk/issues/468 + // return sdk.ErrGenesisParse("").TraceCause(err, "") + } + + for _, gacc := range genesisState.Accounts { + acc, err := gacc.ToAccount() + if err != nil { + panic(err) // TODO https://github.com/cosmos/cosmos-sdk/issues/468 + // return sdk.ErrGenesisParse("").TraceCause(err, "") + } + acc.AccountNumber = accountMapper.GetNextAccountNumber(ctx) + accountMapper.SetAccount(ctx, acc) + } + + return abci.ResponseInitChain{} + + } +} + +//--------------------------------------------------------------------------------------------- +// Now that initializing coin metadata is done in InitChainer we can simplifiy handleMsgIssue + +func NewApp4Handler(accountKeeper bank.Keeper, metadataMapper MetaDataMapper) sdk.Handler { + return func(ctx sdk.Context, msg sdk.Msg) sdk.Result { + switch msg := msg.(type) { + case MsgSend: + return betterHandleMsgSend(ctx, accountKeeper, msg) + case MsgIssue: + // use new MsgIssue handler + return evenBetterHandleMsgIssue(ctx, metadataMapper, accountKeeper, msg) + default: + errMsg := "Unrecognized bank Msg type: " + reflect.TypeOf(msg).Name() + return sdk.ErrUnknownRequest(errMsg).Result() + } + } +} + +func evenBetterHandleMsgIssue(ctx sdk.Context, metadataMapper MetaDataMapper, accountKeeper bank.Keeper, msg MsgIssue) sdk.Result { + for _, o := range msg.Outputs { + for _, coin := range o.Coins { + // Metadata is no longer created on the fly since it is initalized at genesis with InitChain + metadata := metadataMapper.GetMetaData(ctx, coin.Denom) + // Check that msg Issuer is authorized to issue these coins + if !reflect.DeepEqual(metadata.Issuer, msg.Issuer) { + return sdk.ErrUnauthorized(fmt.Sprintf("Msg Issuer cannot issue these coins: %s", coin.Denom)).Result() + } + + // Issuer cannot issue more than remaining supply + issuerSupply := metadata.TotalSupply.Sub(metadata.CurrentSupply) + if coin.Amount.GT(issuerSupply) { + return sdk.ErrInsufficientCoins(fmt.Sprintf("Issuer cannot issue that many coins. Current issuer supply: %d", issuerSupply.Int64())).Result() + } + + // update metadata current circulating supply + metadata.CurrentSupply = metadata.CurrentSupply.Add(coin.Amount) + + metadataMapper.SetMetaData(ctx, coin.Denom, metadata) + } + + // Add newly issued coins to output address + _, _, err := accountKeeper.AddCoins(ctx, o.Address, o.Coins) + if err != nil { + return err.Result() + } + } + + return sdk.Result{} +} + + +//--------------------------------------------------------------------------------------------- +// Simpler MetaDataMapper no longer able to initalize default CoinMetaData + +type App4MetaDataMapper struct { + mainKey *sdk.KVStoreKey +} + +func NewApp4MetaDataMapper(key *sdk.KVStoreKey) App4MetaDataMapper { + return App4MetaDataMapper{mainKey: key} +} + +func (mdm App4MetaDataMapper) GetMetaData(ctx sdk.Context, denom string) CoinMetadata { + store := ctx.KVStore(mdm.mainKey) + + bz := store.Get([]byte(denom)) + if bz == nil { + // Coin metadata doesn't exist, create new metadata with default params + return CoinMetadata{} + } + + var metadata CoinMetadata + err := json.Unmarshal(bz, &metadata) + if err != nil { + panic(err) + } + + return metadata +} + +func (mdm App4MetaDataMapper) SetMetaData(ctx sdk.Context, denom string, metadata CoinMetadata) { + store := ctx.KVStore(mdm.mainKey) + + val, err := json.Marshal(metadata) + if err != nil { + panic(err) + } + + store.Set([]byte(denom), val) +} + //------------------------------------------------------------------ // AccountMapper