diff --git a/docs/core/examples/app1.go b/docs/core/examples/app1.go index ec5b779e7..56fcea0ec 100644 --- a/docs/core/examples/app1.go +++ b/docs/core/examples/app1.go @@ -2,7 +2,6 @@ package app import ( "encoding/json" - "reflect" cmn "github.com/tendermint/tmlibs/common" dbm "github.com/tendermint/tmlibs/db" @@ -35,7 +34,7 @@ func NewApp1(logger log.Logger, db dbm.DB) *bapp.BaseApp { // Register message routes. // Note the handler gets access to the account store. app.Router(). - AddRoute("bank", NewApp1Handler(keyAccount)) + AddRoute("send", handleMsgSend(keyAccount)) // Mount stores and load the latest state. app.MountStoresIAVL(keyAccount) @@ -65,7 +64,7 @@ func NewMsgSend(from, to sdk.Address, amt sdk.Coins) MsgSend { } // Implements Msg. -func (msg MsgSend) Type() string { return "bank" } +func (msg MsgSend) Type() string { return "send" } // Implements Msg. Ensure the addresses are good and the // amount is positive. @@ -105,41 +104,40 @@ func (msg MsgSend) Tags() sdk.Tags { //------------------------------------------------------------------ // Handler for the message -func NewApp1Handler(keyAcc *sdk.KVStoreKey) sdk.Handler { +// Handle MsgSend. +// NOTE: msg.From, msg.To, and msg.Amount were already validated +func handleMsgSend(key *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) - default: - errMsg := "Unrecognized bank Msg type: " + reflect.TypeOf(msg).Name() - return sdk.ErrUnknownRequest(errMsg).Result() + sendMsg, ok := msg.(MsgSend) + if !ok { + // Create custom error message and return result + // Note: Using unreserved error codespace + return sdk.NewError(2, 1, "Send Message is malformed").Result() + } + + + // Load the store. + store := ctx.KVStore(key) + + // Debit from the sender. + if res := handleFrom(store, sendMsg.From, sendMsg.Amount); !res.IsOK() { + return res + } + + // Credit the receiver. + if res := handleTo(store, sendMsg.To, sendMsg.Amount); !res.IsOK() { + return res + } + + // Return a success (Code 0). + // Add list of key-value pair descriptors ("tags"). + return sdk.Result{ + Tags: sendMsg.Tags(), } } } -// Handle MsgSend. -// NOTE: msg.From, msg.To, and msg.Amount were already validated -func handleMsgSend(ctx sdk.Context, key *sdk.KVStoreKey, msg MsgSend) sdk.Result { - // Load the store. - store := ctx.KVStore(key) - - // Debit from the sender. - if res := handleFrom(store, msg.From, msg.Amount); !res.IsOK() { - return res - } - - // Credit the receiver. - if res := handleTo(store, msg.To, msg.Amount); !res.IsOK() { - return res - } - - // Return a success (Code 0). - // Add list of key-value pair descriptors ("tags"). - return sdk.Result{ - Tags: msg.Tags(), - } -} - +// Convenience Handlers func handleFrom(store sdk.KVStore, from sdk.Address, amt sdk.Coins) sdk.Result { // Get sender account from the store. accBytes := store.Get(from) @@ -210,6 +208,7 @@ func handleTo(store sdk.KVStore, to sdk.Address, amt sdk.Coins) sdk.Result { return sdk.Result{} } +// Simple account struct type appAccount struct { Coins sdk.Coins `json:"coins"` } diff --git a/docs/core/examples/app2.go b/docs/core/examples/app2.go index 5959fcd39..f0260ac43 100644 --- a/docs/core/examples/app2.go +++ b/docs/core/examples/app2.go @@ -49,7 +49,8 @@ func NewApp2(logger log.Logger, db dbm.DB) *bapp.BaseApp { // Register message routes. // Note the handler gets access to the account store. app.Router(). - AddRoute("bank", NewApp2Handler(keyAccount, keyMain)) + AddRoute("send", handleMsgSend(keyAccount)). + AddRoute("issue", handleMsgIssue(keyAccount, keyMain)) // Mount stores and load the latest state. app.MountStoresIAVL(keyAccount, keyMain) @@ -71,39 +72,37 @@ type CoinMetadata struct { //------------------------------------------------------------------ // Msgs -// 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 +// Single permissioned issuer can issue Coin to Receiver +// if he is the issuer in Coin Metadata // Implements sdk.Msg Interface type MsgIssue struct { Issuer sdk.Address - Outputs []Output + Receiver sdk.Address + Coin sdk.Coin } -// nolint -func (msg MsgIssue) Type() string { return "bank" } +// Implements Msg. +func (msg MsgIssue) Type() string { return "issue" } +// Implements Msg. Ensures addresses are valid and Coin is positive 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") - } + if len(msg.Receiver) == 0 { + return sdk.ErrInvalidAddress("Receiver address cannot be empty") } + + // Cannot issue zero or negative coins + if !msg.Coin.IsPositive() { + return sdk.ErrInvalidCoins("Cannot issue 0 or negative coin amounts") + } + return nil } +// Implements Msg. Get canonical sign bytes for MsgIssue func (msg MsgIssue) GetSignBytes() []byte { bz, err := json.Marshal(msg) if err != nil { @@ -117,112 +116,93 @@ func (msg MsgIssue) GetSigners() []sdk.Address { return []sdk.Address{msg.Issuer} } +// Returns the sdk.Tags for the message +func (msg MsgIssue) Tags() sdk.Tags { + return sdk.NewTags("issuer", []byte(msg.Issuer.String())). + AppendTag("receiver", []byte(msg.Receiver.String())) +} + //------------------------------------------------------------------ // Handler for the message -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: - return handleMsgIssue(ctx, keyMain, keyAcc, msg) - default: - errMsg := "Unrecognized bank Msg type: " + reflect.TypeOf(msg).Name() - return sdk.ErrUnknownRequest(errMsg).Result() - } - } -} - // 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) +func handleMsgIssue(keyMain *sdk.KVStoreKey, keyAcc *sdk.KVStoreKey) sdk.Handler { + return func(ctx sdk.Context, msg sdk.Msg) sdk.Result { + issueMsg, ok := msg.(MsgIssue) + if !ok { + return sdk.NewError(2, 1, "IssueMsg is malformed").Result() } - // Add coins to receiver account - bz := accStore.Get(o.Address) - var acc appAccount - if bz == nil { - // Receiver account does not already exist, create a new one. - acc = appAccount{} - } else { - // Receiver account already exists. Retrieve and decode it. - err := json.Unmarshal(bz, &acc) - if err != nil { - return sdk.ErrInternal("Account decoding error").Result() - } + // Retrieve stores + store := ctx.KVStore(keyMain) + accStore := ctx.KVStore(keyAcc) + + // Handle updating metadata + if res := handleMetaData(store, issueMsg.Issuer, issueMsg.Coin); !res.IsOK() { + return res } - // 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() + // Issue coins to receiver using previously defined handleTo function + if res := handleTo(accStore, issueMsg.Receiver, []sdk.Coin{issueMsg.Coin}); !res.IsOK() { + return res } - // set account with new issued coins in store - store.Set(o.Address, val) + return sdk.Result{ + // Return result with Issue msg tags + Tags: issueMsg.Tags(), + } } - - return sdk.Result{ - // TODO: Tags - } - } +func handleMetaData(store sdk.KVStore, issuer sdk.Address, coin sdk.Coin) sdk.Result { + 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: issuer, + Decimal: 10, + } + } else { + // Decode coin metadata + err := json.Unmarshal(bz, &metadata) + if err != nil { + return sdk.ErrInternal("Decoding coin metadata failed").Result() + } + } + + // Msg Issuer is not authorized to issue these coins + if !reflect.DeepEqual(metadata.Issuer, issuer) { + return sdk.ErrUnauthorized(fmt.Sprintf("Msg Issuer cannot issue tokens: %s", coin.Denom)).Result() + } + + // Update coin current circulating supply + metadata.CurrentSupply = metadata.CurrentSupply.Add(coin.Amount) + + // Current supply cannot exceed total supply + if metadata.TotalSupply.LT(metadata.CurrentSupply) { + return sdk.ErrInsufficientCoins("Issuer cannot issue more than total supply of coin").Result() + } + + val, err := json.Marshal(metadata) + if err != nil { + return sdk.ErrInternal(fmt.Sprintf("Error encoding metadata: %s", err.Error())).Result() + } + + // Update store with new metadata + store.Set([]byte(coin.Denom), val) + + return sdk.Result{} +} + + //------------------------------------------------------------------ // Tx diff --git a/docs/core/examples/app3.go b/docs/core/examples/app3.go index 25036ef1b..e20bbcb70 100644 --- a/docs/core/examples/app3.go +++ b/docs/core/examples/app3.go @@ -2,8 +2,8 @@ package app import ( "encoding/json" - "fmt" "reflect" + "fmt" cmn "github.com/tendermint/tmlibs/common" dbm "github.com/tendermint/tmlibs/db" @@ -44,7 +44,8 @@ func NewApp3(logger log.Logger, db dbm.DB) *bapp.BaseApp { // Register message routes. // Note the handler gets access to the account store. app.Router(). - AddRoute("bank", NewApp3Handler(accountKeeper, metadataMapper)) + AddRoute("send", betterHandleMsgSend(accountKeeper)). + AddRoute("issue", betterHandleMsgIssue(metadataMapper, accountKeeper)) // Mount stores and load the latest state. app.MountStoresIAVL(keyAccount, keyMain, keyFees) @@ -55,91 +56,105 @@ func NewApp3(logger log.Logger, db dbm.DB) *bapp.BaseApp { return app } -func NewApp3Handler(accountKeeper bank.Keeper, metadataMapper MetaDataMapper) sdk.Handler { +func betterHandleMsgSend(accountKeeper bank.Keeper) 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() + sendMsg, ok := msg.(MsgSend) + if !ok { + return sdk.NewError(2, 1, "Send Message Malformed").Result() + } + + // Subtract coins from sender account + _, _, err := accountKeeper.SubtractCoins(ctx, sendMsg.From, sendMsg.Amount) + if err != nil { + // if error, return its result + return err.Result() + } + + // Add coins to receiver account + _, _, err = accountKeeper.AddCoins(ctx, sendMsg.To, sendMsg.Amount) + if err != nil { + // if error, return its result + return err.Result() + } + + return sdk.Result{ + Tags: sendMsg.Tags(), } } } -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) +func betterHandleMsgIssue(metadataMapper MetaDataMapper, accountKeeper bank.Keeper) sdk.Handler { + return func(ctx sdk.Context, msg sdk.Msg) sdk.Result { + issueMsg, ok := msg.(MsgIssue) + if !ok { + return sdk.NewError(2, 1, "Issue Message Malformed").Result() } + // Handle updating metadata + if res := betterHandleMetaData(ctx, metadataMapper, issueMsg.Issuer, issueMsg.Coin); !res.IsOK() { + return res + } + // Add newly issued coins to output address - _, _, err := accountKeeper.AddCoins(ctx, o.Address, o.Coins) + _, _, err := accountKeeper.AddCoins(ctx, issueMsg.Receiver, []sdk.Coin{issueMsg.Coin}) if err != nil { return err.Result() } + + return sdk.Result{ + // Return result with Issue msg tags + Tags: issueMsg.Tags(), + } + } +} + +func betterHandleMetaData(ctx sdk.Context, metadataMapper MetaDataMapper, issuer sdk.Address, coin sdk.Coin) sdk.Result { + metadata := metadataMapper.GetMetaData(ctx, coin.Denom) + + // Metadata was created fresh, should set issuer to msg issuer + if len(metadata.Issuer) == 0 { + metadata.Issuer = issuer } + // Msg Issuer is not authorized to issue these coins + if !reflect.DeepEqual(metadata.Issuer, issuer) { + return sdk.ErrUnauthorized(fmt.Sprintf("Msg Issuer cannot issue tokens: %s", coin.Denom)).Result() + } + + // Update current circulating supply + metadata.CurrentSupply = metadata.CurrentSupply.Add(coin.Amount) + + // Current supply cannot exceed total supply + if metadata.TotalSupply.LT(metadata.CurrentSupply) { + return sdk.ErrInsufficientCoins("Issuer cannot issue more than total supply of coin").Result() + } + + metadataMapper.SetMetaData(ctx, coin.Denom, metadata) return sdk.Result{} } //------------------------------------------------------------------ // Mapper for Coin Metadata -// Example of a very simple user-defined mapper +// Example of a very simple user-defined mapper interface type MetaDataMapper interface { GetMetaData(sdk.Context, string) CoinMetadata SetMetaData(sdk.Context, string, CoinMetadata) } +// Implements MetaDataMapper type App3MetaDataMapper struct { mainKey *sdk.KVStoreKey } +// Construct new App3MetaDataMapper func NewApp3MetaDataMapper(key *sdk.KVStoreKey) App3MetaDataMapper { return App3MetaDataMapper{mainKey: key} } +// Implements MetaDataMpper. Returns metadata for coin +// If metadata does not exist in store, function creates default metadata and returns it +// without adding it to the store. func (mdm App3MetaDataMapper) GetMetaData(ctx sdk.Context, denom string) CoinMetadata { store := ctx.KVStore(mdm.mainKey) @@ -160,6 +175,7 @@ func (mdm App3MetaDataMapper) GetMetaData(ctx sdk.Context, denom string) CoinMet return metadata } +// Implements MetaDataMapper. Sets metadata in store with key equal to denom. func (mdm App3MetaDataMapper) SetMetaData(ctx sdk.Context, denom string, metadata CoinMetadata) { store := ctx.KVStore(mdm.mainKey) diff --git a/docs/core/examples/app4.go b/docs/core/examples/app4.go index c7048409e..564039798 100644 --- a/docs/core/examples/app4.go +++ b/docs/core/examples/app4.go @@ -47,7 +47,8 @@ func NewApp4(logger log.Logger, db dbm.DB) *bapp.BaseApp { // Register message routes. // Note the handler gets access to the account store. app.Router(). - AddRoute("bank", NewApp4Handler(accountKeeper, metadataMapper)) + AddRoute("send", betterHandleMsgSend(accountKeeper)). + AddRoute("issue", evenBetterHandleMsgIssue(metadataMapper, accountKeeper)) // Mount stores and load the latest state. app.MountStoresIAVL(keyAccount, keyMain, keyFees) @@ -58,6 +59,7 @@ func NewApp4(logger log.Logger, db dbm.DB) *bapp.BaseApp { return app } +// Application state at Genesis has accounts with starting balances and coins with starting metadata type GenesisState struct { Accounts []*GenesisAccount `json:"accounts"` Coins []*GenesisCoin `json:"coins"` @@ -69,6 +71,7 @@ type GenesisAccount struct { Coins sdk.Coins `json:"coins"` } +// Converts GenesisAccount to auth.BaseAccount for storage in account store func (ga *GenesisAccount) ToAccount() (acc *auth.BaseAccount, err error) { baseAcc := auth.BaseAccount{ Address: ga.Address, @@ -85,6 +88,7 @@ type GenesisCoin struct { Decimal uint64 `json:"decimals"` } +// Converts GenesisCoin to its denom and metadata for storage in main store func (gc *GenesisCoin) ToMetaData() (string, CoinMetadata) { return gc.Denom, CoinMetadata{ Issuer: gc.Issuer, @@ -128,66 +132,76 @@ func NewInitChainer(cdc *wire.Codec, accountMapper auth.AccountMapper, metadataM } //--------------------------------------------------------------------------------------------- -// Now that initializing coin metadata is done in InitChainer we can simplifiy handleMsgIssue +// Now that initializing coin metadata is done in InitChainer we can simplify handleMsgIssue -func NewApp4Handler(accountKeeper bank.Keeper, metadataMapper MetaDataMapper) sdk.Handler { +// New MsgIssue handler will no longer generate coin metadata on the fly. +// Allows issuers (permissioned at genesis) to issue coin to receiver. +func evenBetterHandleMsgIssue(metadataMapper MetaDataMapper, accountKeeper bank.Keeper) 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() + issueMsg, ok := msg.(MsgIssue) + if !ok { + return sdk.NewError(2, 1, "Issue Message Malformed").Result() + } + + // Handle updating metadata + if res := evenBetterHandleMetaData(ctx, metadataMapper, issueMsg.Issuer, issueMsg.Coin); !res.IsOK() { + return res + } + + // Add newly issued coins to output address + _, _, err := accountKeeper.AddCoins(ctx, issueMsg.Receiver, []sdk.Coin{issueMsg.Coin}) + if err != nil { + return err.Result() + } + + return sdk.Result{ + // Return result with Issue msg tags + Tags: issueMsg.Tags(), } } } -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() - } +// No longer generates metadata on the fly. +// Returns error result when it cannot find coin metadata +func evenBetterHandleMetaData(ctx sdk.Context, metadataMapper MetaDataMapper, issuer sdk.Address, coin sdk.Coin) sdk.Result { + metadata := metadataMapper.GetMetaData(ctx, coin.Denom) - // 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() - } + // Coin metadata does not exist in store + if reflect.DeepEqual(metadata, CoinMetadata{}) { + return sdk.ErrInvalidCoins(fmt.Sprintf("Cannot find metadata for coin: %s", coin.Denom)).Result() } + // Msg Issuer not authorized to issue these coins + if !reflect.DeepEqual(metadata.Issuer, issuer) { + return sdk.ErrUnauthorized(fmt.Sprintf("Msg Issuer cannot issue tokens: %s", coin.Denom)).Result() + } + + // Update current circulating supply + metadata.CurrentSupply = metadata.CurrentSupply.Add(coin.Amount) + + // Current supply cannot exceed total supply + if metadata.TotalSupply.LT(metadata.CurrentSupply) { + return sdk.ErrInsufficientCoins("Issuer cannot issue more than total supply of coin").Result() + } + + metadataMapper.SetMetaData(ctx, coin.Denom, metadata) return sdk.Result{} } //--------------------------------------------------------------------------------------------- -// Simpler MetaDataMapper no longer able to initalize default CoinMetaData +// Simpler MetaDataMapper no longer able to initialize default CoinMetaData +// Implements MetaDataMapper type App4MetaDataMapper struct { mainKey *sdk.KVStoreKey } +// Constructs new App4MetaDataMapper func NewApp4MetaDataMapper(key *sdk.KVStoreKey) App4MetaDataMapper { return App4MetaDataMapper{mainKey: key} } +// Returns coin Metadata. If metadata not found in store, function returns empty struct. func (mdm App4MetaDataMapper) GetMetaData(ctx sdk.Context, denom string) CoinMetadata { store := ctx.KVStore(mdm.mainKey) @@ -206,6 +220,7 @@ func (mdm App4MetaDataMapper) GetMetaData(ctx sdk.Context, denom string) CoinMet return metadata } +// Sets metadata in store with key equal to coin denom. Same behavior as App3 implementation. func (mdm App4MetaDataMapper) SetMetaData(ctx sdk.Context, denom string, metadata CoinMetadata) { store := ctx.KVStore(mdm.mainKey)