2018-06-25 19:43:57 -07:00
|
|
|
package app
|
|
|
|
|
|
|
|
import (
|
2018-06-26 19:09:54 -07:00
|
|
|
"encoding/json"
|
|
|
|
"fmt"
|
2018-06-26 19:22:50 -07:00
|
|
|
"reflect"
|
2018-06-25 19:43:57 -07:00
|
|
|
|
2018-06-26 19:22:50 -07:00
|
|
|
"github.com/tendermint/go-crypto"
|
2018-06-25 19:43:57 -07:00
|
|
|
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"
|
2018-06-25 19:43:57 -07:00
|
|
|
)
|
|
|
|
|
|
|
|
const (
|
|
|
|
app2Name = "App2"
|
|
|
|
)
|
|
|
|
|
2018-06-26 19:09:54 -07:00
|
|
|
var (
|
|
|
|
issuer = crypto.GenPrivKeyEd25519().PubKey().Address()
|
|
|
|
)
|
|
|
|
|
2018-06-25 19:43:57 -07:00
|
|
|
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
|
2018-06-25 19:43:57 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
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")
|
2018-06-25 19:43:57 -07:00
|
|
|
keyAccount := sdk.NewKVStoreKey("acc")
|
2018-06-26 19:09:54 -07:00
|
|
|
|
|
|
|
// set antehandler function
|
|
|
|
app.SetAnteHandler(antehandler)
|
2018-06-25 19:43:57 -07:00
|
|
|
|
|
|
|
// Register message routes.
|
|
|
|
// Note the handler gets access to the account store.
|
|
|
|
app.Router().
|
2018-06-27 14:42:30 -07:00
|
|
|
AddRoute("send", handleMsgSend(keyAccount)).
|
|
|
|
AddRoute("issue", handleMsgIssue(keyAccount, keyMain))
|
2018-06-25 19:43:57 -07:00
|
|
|
|
|
|
|
// Mount stores and load the latest state.
|
2018-06-26 19:09:54 -07:00
|
|
|
app.MountStoresIAVL(keyAccount, keyMain)
|
2018-06-25 19:43:57 -07:00
|
|
|
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 {
|
2018-06-26 19:22:50 -07:00
|
|
|
TotalSupply sdk.Int
|
2018-06-26 19:09:54 -07:00
|
|
|
CurrentSupply sdk.Int
|
2018-06-26 19:22:50 -07:00
|
|
|
Issuer sdk.Address
|
|
|
|
Decimal uint64
|
2018-06-26 19:09:54 -07:00
|
|
|
}
|
|
|
|
|
2018-06-25 19:43:57 -07:00
|
|
|
//------------------------------------------------------------------
|
|
|
|
// Msgs
|
|
|
|
|
2018-06-27 14:42:30 -07:00
|
|
|
// 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-26 19:22:50 -07:00
|
|
|
Issuer sdk.Address
|
2018-06-27 14:42:30 -07:00
|
|
|
Receiver sdk.Address
|
|
|
|
Coin sdk.Coin
|
2018-06-26 19:09:54 -07:00
|
|
|
}
|
|
|
|
|
2018-06-27 15:05:04 -07:00
|
|
|
// Implements Msg.
|
2018-06-27 14:42:30 -07:00
|
|
|
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:22:50 -07:00
|
|
|
}
|
2018-06-26 19:09:54 -07:00
|
|
|
|
2018-06-27 14:42:30 -07:00
|
|
|
if len(msg.Receiver) == 0 {
|
|
|
|
return sdk.ErrInvalidAddress("Receiver address cannot be empty")
|
2018-06-26 19:09:54 -07:00
|
|
|
}
|
2018-06-27 14:42:30 -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}
|
|
|
|
}
|
2018-06-25 19:43:57 -07:00
|
|
|
|
2018-06-27 14:42:30 -07:00
|
|
|
// 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()))
|
|
|
|
}
|
|
|
|
|
2018-06-25 19:43:57 -07:00
|
|
|
//------------------------------------------------------------------
|
|
|
|
// Handler for the message
|
|
|
|
|
2018-06-27 14:42:30 -07:00
|
|
|
// Handle Msg Issue
|
|
|
|
func handleMsgIssue(keyMain *sdk.KVStoreKey, keyAcc *sdk.KVStoreKey) sdk.Handler {
|
2018-06-25 19:43:57 -07:00
|
|
|
return func(ctx sdk.Context, msg sdk.Msg) sdk.Result {
|
2018-06-27 14:42:30 -07:00
|
|
|
issueMsg, ok := msg.(MsgIssue)
|
|
|
|
if !ok {
|
|
|
|
return sdk.NewError(2, 1, "IssueMsg is malformed").Result()
|
2018-06-25 19:43:57 -07:00
|
|
|
}
|
|
|
|
|
2018-06-27 14:50:59 -07:00
|
|
|
// Retrieve stores
|
2018-06-27 14:42:30 -07:00
|
|
|
store := ctx.KVStore(keyMain)
|
|
|
|
accStore := ctx.KVStore(keyAcc)
|
|
|
|
|
2018-06-27 14:50:59 -07:00
|
|
|
// Handle updating metadata
|
2018-06-27 14:42:30 -07:00
|
|
|
if res := handleMetaData(store, issueMsg.Issuer, issueMsg.Coin); !res.IsOK() {
|
|
|
|
return res
|
2018-06-26 19:09:54 -07:00
|
|
|
}
|
|
|
|
|
2018-06-27 14:42:30 -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
|
|
|
}
|
|
|
|
|
2018-06-27 14:42:30 -07:00
|
|
|
return sdk.Result{
|
2018-06-27 14:50:59 -07:00
|
|
|
// Return result with Issue msg tags
|
2018-06-27 14:42:30 -07:00
|
|
|
Tags: issueMsg.Tags(),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2018-06-26 19:09:54 -07:00
|
|
|
|
2018-06-27 14:42:30 -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
|
|
|
|
2018-06-27 14:42:30 -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 {
|
2018-06-27 14:42:30 -07:00
|
|
|
return sdk.ErrInternal("Decoding coin metadata failed").Result()
|
2018-06-26 19:09:54 -07:00
|
|
|
}
|
2018-06-27 14:42:30 -07:00
|
|
|
}
|
|
|
|
|
2018-06-27 14:50:59 -07:00
|
|
|
// Msg Issuer is not authorized to issue these coins
|
2018-06-27 14:42:30 -07:00
|
|
|
if !reflect.DeepEqual(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
|
|
|
|
2018-06-27 14:42:30 -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
|
|
|
}
|
|
|
|
|
2018-06-27 14:42:30 -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
|
|
|
}
|
|
|
|
|
2018-06-27 14:42:30 -07:00
|
|
|
// Update store with new metadata
|
|
|
|
store.Set([]byte(coin.Denom), val)
|
|
|
|
|
|
|
|
return sdk.Result{}
|
2018-06-26 19:09:54 -07:00
|
|
|
}
|
|
|
|
|
2018-06-27 14:42:30 -07:00
|
|
|
|
2018-06-25 19:43:57 -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
|
2018-06-25 19:43:57 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
// 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
|
|
|
|
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
|
|
|
|
}
|