cosmos-sdk/modules/coin/handler.go

248 lines
6.0 KiB
Go
Raw Normal View History

2017-06-30 04:55:25 -07:00
package coin
import (
2017-07-03 09:58:28 -07:00
"github.com/tendermint/go-wire/data"
"github.com/tendermint/tmlibs/log"
2017-06-30 04:55:25 -07:00
"github.com/tendermint/basecoin"
"github.com/tendermint/basecoin/errors"
"github.com/tendermint/basecoin/modules/auth"
"github.com/tendermint/basecoin/modules/ibc"
"github.com/tendermint/basecoin/stack"
2017-07-06 05:23:38 -07:00
"github.com/tendermint/basecoin/state"
2017-06-30 04:55:25 -07:00
)
const (
//NameCoin - name space of the coin module
NameCoin = "coin"
// CostSend is GasAllocation per input/output
CostSend = uint(10)
// CostCredit is GasAllocation of a credit allocation
CostCredit = uint(20)
)
2017-06-30 04:55:25 -07:00
2017-07-06 02:30:03 -07:00
// Handler includes an accountant
2017-07-30 14:26:25 -07:00
type Handler struct {
stack.PassInitValidate
}
2017-06-30 04:55:25 -07:00
var _ stack.Dispatchable = Handler{}
2017-06-30 04:55:25 -07:00
2017-07-06 02:30:03 -07:00
// NewHandler - new accountant handler for the coin module
2017-07-03 05:50:33 -07:00
func NewHandler() Handler {
return Handler{}
2017-07-03 05:50:33 -07:00
}
2017-07-06 02:30:03 -07:00
// Name - return name space
2017-07-06 02:39:58 -07:00
func (Handler) Name() string {
2017-06-30 04:55:25 -07:00
return NameCoin
}
// AssertDispatcher - to fulfill Dispatchable interface
func (Handler) AssertDispatcher() {}
2017-06-30 04:55:25 -07:00
// CheckTx checks if there is enough money in the account
func (h Handler) CheckTx(ctx basecoin.Context, store state.SimpleDB,
tx basecoin.Tx, _ basecoin.Checker) (res basecoin.CheckResult, err error) {
2017-07-21 14:28:44 -07:00
err = tx.ValidateBasic()
2017-06-30 04:55:25 -07:00
if err != nil {
return res, err
}
2017-07-21 14:28:44 -07:00
switch t := tx.Unwrap().(type) {
case SendTx:
// price based on inputs and outputs
used := uint(len(t.Inputs) + len(t.Outputs))
return basecoin.NewCheck(used*CostSend, ""), h.checkSendTx(ctx, store, t)
2017-07-21 14:28:44 -07:00
case CreditTx:
// default price of 20, constant work
return basecoin.NewCheck(CostCredit, ""), h.creditTx(ctx, store, t)
}
2017-07-21 14:28:44 -07:00
return res, errors.ErrUnknownTxType(tx.Unwrap())
2017-06-30 04:55:25 -07:00
}
// DeliverTx moves the money
func (h Handler) DeliverTx(ctx basecoin.Context, store state.SimpleDB,
tx basecoin.Tx, cb basecoin.Deliver) (res basecoin.DeliverResult, err error) {
2017-07-21 14:28:44 -07:00
err = tx.ValidateBasic()
if err != nil {
return res, err
}
switch t := tx.Unwrap().(type) {
case SendTx:
return res, h.sendTx(ctx, store, t, cb)
2017-07-21 14:28:44 -07:00
case CreditTx:
return res, h.creditTx(ctx, store, t)
2017-07-21 14:28:44 -07:00
}
return res, errors.ErrUnknownTxType(tx.Unwrap())
}
2017-07-30 09:57:48 -07:00
// InitState - sets the genesis account balance
func (h Handler) InitState(l log.Logger, store state.SimpleDB,
module, key, value string, cb basecoin.InitStater) (log string, err error) {
2017-07-21 14:28:44 -07:00
if module != NameCoin {
return "", errors.ErrUnknownModule(module)
}
switch key {
case "account":
return setAccount(store, value)
case "issuer":
return setIssuer(store, value)
}
return "", errors.ErrUnknownKey(key)
}
func (h Handler) sendTx(ctx basecoin.Context, store state.SimpleDB,
send SendTx, cb basecoin.Deliver) error {
2017-07-21 14:28:44 -07:00
err := checkTx(ctx, send)
2017-06-30 04:55:25 -07:00
if err != nil {
return err
2017-06-30 04:55:25 -07:00
}
// deduct from all input accounts
senders := basecoin.Actors{}
for _, in := range send.Inputs {
2017-07-12 09:54:07 -07:00
_, err = ChangeCoins(store, in.Address, in.Coins.Negative())
if err != nil {
return err
}
senders = append(senders, in.Address)
}
// add to all output accounts
for _, out := range send.Outputs {
// TODO: cleaner way, this makes sure we don't consider
// incoming ibc packets with our chain to be remote packets
if out.Address.ChainID == ctx.ChainID() {
out.Address.ChainID = ""
}
2017-07-12 09:54:07 -07:00
_, err = ChangeCoins(store, out.Address, out.Coins)
if err != nil {
return err
}
// now send ibc packet if needed...
if out.Address.ChainID != "" {
// FIXME: if there are many outputs, we need to adjust inputs
// so the amounts in and out match. how?
2017-07-22 05:09:42 -07:00
inputs := make([]TxInput, len(send.Inputs))
for i := range send.Inputs {
inputs[i] = send.Inputs[i]
inputs[i].Address = inputs[i].Address.WithChain(ctx.ChainID())
}
outTx := NewSendTx(inputs, []TxOutput{out})
packet := ibc.CreatePacketTx{
DestChain: out.Address.ChainID,
Permissions: senders,
Tx: outTx,
}
ibcCtx := ctx.WithPermissions(ibc.AllowIBC(NameCoin))
_, err := cb.DeliverTx(ibcCtx, store, packet.Wrap())
if err != nil {
return err
}
}
}
// a-ok!
return nil
2017-06-30 04:55:25 -07:00
}
2017-07-21 14:28:44 -07:00
func (h Handler) creditTx(ctx basecoin.Context, store state.SimpleDB,
credit CreditTx) error {
2017-07-21 14:28:44 -07:00
// first check permissions!!
info, err := loadHandlerInfo(store)
if err != nil {
return err
}
2017-07-21 14:28:44 -07:00
if info.Issuer.Empty() || !ctx.HasPermission(info.Issuer) {
return errors.ErrUnauthorized()
2017-07-03 09:58:28 -07:00
}
2017-07-21 14:28:44 -07:00
// load up the account
addr := ChainAddr(credit.Debitor)
acct, err := GetAccount(store, addr)
2017-06-30 04:55:25 -07:00
if err != nil {
return err
2017-06-30 04:55:25 -07:00
}
2017-07-21 14:28:44 -07:00
// make and check changes
acct.Coins = acct.Coins.Plus(credit.Credit)
if !acct.Coins.IsNonnegative() {
return ErrInsufficientFunds()
2017-07-21 14:28:44 -07:00
}
acct.Credit = acct.Credit.Plus(credit.Credit)
if !acct.Credit.IsNonnegative() {
return ErrInsufficientCredit()
2017-07-21 14:28:44 -07:00
}
err = storeAccount(store, addr.Bytes(), acct)
return err
2017-07-21 14:28:44 -07:00
}
func checkTx(ctx basecoin.Context, send SendTx) error {
2017-06-30 04:55:25 -07:00
// check if all inputs have permission
for _, in := range send.Inputs {
if !ctx.HasPermission(in.Address) {
2017-07-21 14:28:44 -07:00
return errors.ErrUnauthorized()
}
}
return nil
}
func (Handler) checkSendTx(ctx basecoin.Context, store state.SimpleDB, send SendTx) error {
err := checkTx(ctx, send)
if err != nil {
return err
}
// now make sure there is money
for _, in := range send.Inputs {
_, err := CheckCoins(store, in.Address, in.Coins.Negative())
if err != nil {
return err
2017-06-30 04:55:25 -07:00
}
}
2017-07-21 14:28:44 -07:00
return nil
2017-06-30 04:55:25 -07:00
}
2017-07-21 11:24:53 -07:00
2017-07-21 14:28:44 -07:00
func setAccount(store state.SimpleDB, value string) (log string, err error) {
2017-07-21 11:24:53 -07:00
var acc GenesisAccount
err = data.FromJSON([]byte(value), &acc)
if err != nil {
return "", err
}
acc.Balance.Sort()
addr, err := acc.GetAddr()
if err != nil {
return "", ErrInvalidAddress()
}
// this sets the permission for a public key signature, use that app
actor := auth.SigPerm(addr)
err = storeAccount(store, actor.Bytes(), acc.ToAccount())
if err != nil {
return "", err
}
return "Success", nil
}
// setIssuer sets a permission for some super-powerful account to
// mint money
2017-07-21 14:28:44 -07:00
func setIssuer(store state.SimpleDB, value string) (log string, err error) {
2017-07-21 11:24:53 -07:00
var issuer basecoin.Actor
err = data.FromJSON([]byte(value), &issuer)
if err != nil {
return "", err
}
err = storeIssuer(store, issuer)
if err != nil {
return "", err
}
return "Success", nil
}