cosmos-sdk/docs/core/examples/app2.go

261 lines
6.8 KiB
Go
Raw Normal View History

package app
import (
2018-06-28 16:06:10 -07:00
"bytes"
2018-06-26 19:09:54 -07:00
"encoding/json"
"fmt"
"github.com/tendermint/go-crypto"
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/wire"
2018-06-26 19:09:54 -07:00
"github.com/cosmos/cosmos-sdk/x/auth"
)
const (
app2Name = "App2"
)
2018-06-26 19:09:54 -07:00
var (
issuer = crypto.GenPrivKeyEd25519().PubKey().Address()
)
func NewCodec() *wire.Codec {
2018-06-26 19:09:54 -07:00
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 {
cdc := NewCodec()
// Create the base application object.
app := bapp.NewBaseApp(app2Name, cdc, logger, db)
// Create a key for accessing the account store.
2018-06-26 19:09:54 -07:00
keyMain := sdk.NewKVStoreKey("main")
keyAccount := sdk.NewKVStoreKey("acc")
2018-06-26 19:09:54 -07:00
// set antehandler function
app.SetAnteHandler(antehandler)
// Register message routes.
// Note the handler gets access to the account store.
app.Router().
AddRoute("send", handleMsgSend(keyAccount)).
AddRoute("issue", handleMsgIssue(keyAccount, keyMain))
// Mount stores and load the latest state.
2018-06-26 19:09:54 -07:00
app.MountStoresIAVL(keyAccount, keyMain)
err := app.LoadLatestVersion(keyAccount)
if err != nil {
cmn.Exit(err.Error())
}
return app
}
2018-06-26 19:09:54 -07:00
// Coin Metadata
type CoinMetadata struct {
TotalSupply sdk.Int
2018-06-26 19:09:54 -07:00
CurrentSupply sdk.Int
Issuer sdk.Address
Decimal uint64
2018-06-26 19:09:54 -07:00
}
//------------------------------------------------------------------
// Msgs
// Single permissioned issuer can issue Coin to Receiver
// if he is the issuer in Coin Metadata
2018-06-26 19:09:54 -07:00
// Implements sdk.Msg Interface
type MsgIssue struct {
2018-06-28 16:06:10 -07:00
Issuer sdk.Address
Receiver sdk.Address
2018-06-28 16:06:10 -07:00
Coin sdk.Coin
2018-06-26 19:09:54 -07:00
}
2018-06-27 15:05:04 -07:00
// Implements Msg.
func (msg MsgIssue) Type() string { return "issue" }
2018-06-26 19:09:54 -07:00
2018-06-27 15:05:04 -07:00
// Implements Msg. Ensures addresses are valid and Coin is positive
2018-06-26 19:09:54 -07:00
func (msg MsgIssue) ValidateBasic() sdk.Error {
if len(msg.Issuer) == 0 {
return sdk.ErrInvalidAddress("Issuer address cannot be empty")
}
2018-06-26 19:09:54 -07:00
if len(msg.Receiver) == 0 {
return sdk.ErrInvalidAddress("Receiver address cannot be empty")
2018-06-26 19:09:54 -07:00
}
// Cannot issue zero or negative coins
if !msg.Coin.IsPositive() {
return sdk.ErrInvalidCoins("Cannot issue 0 or negative coin amounts")
}
2018-06-26 19:09:54 -07:00
return nil
}
2018-06-27 15:05:04 -07:00
// Implements Msg. Get canonical sign bytes for MsgIssue
2018-06-26 19:09:54 -07:00
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}
}
// 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
// Handle Msg Issue
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()
}
2018-06-27 14:50:59 -07:00
// Retrieve stores
store := ctx.KVStore(keyMain)
accStore := ctx.KVStore(keyAcc)
2018-06-27 14:50:59 -07:00
// Handle updating metadata
if res := handleMetaData(store, issueMsg.Issuer, issueMsg.Coin); !res.IsOK() {
return res
2018-06-26 19:09:54 -07:00
}
// Issue coins to receiver using previously defined handleTo function
if res := handleTo(accStore, issueMsg.Receiver, []sdk.Coin{issueMsg.Coin}); !res.IsOK() {
return res
2018-06-26 19:09:54 -07:00
}
return sdk.Result{
2018-06-27 14:50:59 -07:00
// Return result with Issue msg tags
Tags: issueMsg.Tags(),
}
}
}
2018-06-26 19:09:54 -07:00
func handleMetaData(store sdk.KVStore, issuer sdk.Address, coin sdk.Coin) sdk.Result {
bz := store.Get([]byte(coin.Denom))
var metadata CoinMetadata
2018-06-26 19:09:54 -07:00
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)
2018-06-26 19:09:54 -07:00
if err != nil {
return sdk.ErrInternal("Decoding coin metadata failed").Result()
2018-06-26 19:09:54 -07:00
}
}
2018-06-27 14:50:59 -07:00
// Msg Issuer is not authorized to issue these coins
2018-06-28 16:06:10 -07:00
if !bytes.Equal(metadata.Issuer, issuer) {
return sdk.ErrUnauthorized(fmt.Sprintf("Msg Issuer cannot issue tokens: %s", coin.Denom)).Result()
}
2018-06-26 19:09:54 -07:00
// 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()
2018-06-26 19:09:54 -07:00
}
val, err := json.Marshal(metadata)
if err != nil {
return sdk.ErrInternal(fmt.Sprintf("Error encoding metadata: %s", err.Error())).Result()
2018-06-26 19:09:54 -07:00
}
// Update store with new metadata
store.Set([]byte(coin.Denom), val)
2018-06-28 16:06:10 -07:00
return sdk.Result{}
2018-06-26 19:09:54 -07:00
}
//------------------------------------------------------------------
// Tx
// Simple tx to wrap the Msg.
type app2Tx struct {
sdk.Msg
2018-06-26 19:09:54 -07:00
Signatures []auth.StdSignature
}
// This tx only has one Msg.
func (tx app2Tx) GetMsgs() []sdk.Msg {
return []sdk.Msg{tx.Msg}
}
2018-06-26 19:09:54 -07:00
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
2018-06-28 16:06:10 -07:00
if !bytes.Equal(sig.PubKey.Address(), addr) {
2018-06-26 19:09:54 -07:00
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
}