Add CreditTx and tests
This commit is contained in:
parent
6135345af8
commit
3027eeb3c3
|
@ -12,6 +12,7 @@ import (
|
|||
var (
|
||||
errNoAccount = fmt.Errorf("No such account")
|
||||
errInsufficientFunds = fmt.Errorf("Insufficient funds")
|
||||
errInsufficientCredit = fmt.Errorf("Insufficient credit")
|
||||
errNoInputs = fmt.Errorf("No input coins")
|
||||
errNoOutputs = fmt.Errorf("No output coins")
|
||||
errInvalidAddress = fmt.Errorf("Invalid address")
|
||||
|
@ -66,6 +67,13 @@ func IsInsufficientFundsErr(err error) bool {
|
|||
return errors.IsSameError(errInsufficientFunds, err)
|
||||
}
|
||||
|
||||
func ErrInsufficientCredit() errors.TMError {
|
||||
return errors.WithCode(errInsufficientCredit, invalidInput)
|
||||
}
|
||||
func IsInsufficientCreditErr(err error) bool {
|
||||
return errors.IsSameError(errInsufficientCredit, err)
|
||||
}
|
||||
|
||||
func ErrNoInputs() errors.TMError {
|
||||
return errors.WithCode(errNoInputs, invalidInput)
|
||||
}
|
||||
|
|
|
@ -37,28 +37,57 @@ func (Handler) AssertDispatcher() {}
|
|||
func (h Handler) CheckTx(ctx basecoin.Context, store state.SimpleDB,
|
||||
tx basecoin.Tx, _ basecoin.Checker) (res basecoin.Result, err error) {
|
||||
|
||||
send, err := checkTx(ctx, tx)
|
||||
err = tx.ValidateBasic()
|
||||
if err != nil {
|
||||
return res, err
|
||||
}
|
||||
|
||||
// now make sure there is money
|
||||
for _, in := range send.Inputs {
|
||||
_, err = CheckCoins(store, in.Address, in.Coins.Negative())
|
||||
if err != nil {
|
||||
return res, err
|
||||
switch t := tx.Unwrap().(type) {
|
||||
case SendTx:
|
||||
return res, h.checkSendTx(ctx, store, t)
|
||||
case CreditTx:
|
||||
return h.creditTx(ctx, store, t)
|
||||
}
|
||||
}
|
||||
|
||||
// otherwise, we are good
|
||||
return res, nil
|
||||
return res, errors.ErrUnknownTxType(tx.Unwrap())
|
||||
}
|
||||
|
||||
// DeliverTx moves the money
|
||||
func (h Handler) DeliverTx(ctx basecoin.Context, store state.SimpleDB,
|
||||
tx basecoin.Tx, cb basecoin.Deliver) (res basecoin.Result, err error) {
|
||||
|
||||
send, err := checkTx(ctx, tx)
|
||||
err = tx.ValidateBasic()
|
||||
if err != nil {
|
||||
return res, err
|
||||
}
|
||||
|
||||
switch t := tx.Unwrap().(type) {
|
||||
case SendTx:
|
||||
return h.sendTx(ctx, store, t, cb)
|
||||
case CreditTx:
|
||||
return h.creditTx(ctx, store, t)
|
||||
}
|
||||
return res, errors.ErrUnknownTxType(tx.Unwrap())
|
||||
}
|
||||
|
||||
// SetOption - sets the genesis account balance
|
||||
func (h Handler) SetOption(l log.Logger, store state.SimpleDB,
|
||||
module, key, value string, cb basecoin.SetOptioner) (log string, err error) {
|
||||
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) (res basecoin.Result, err error) {
|
||||
|
||||
err = checkTx(ctx, send)
|
||||
if err != nil {
|
||||
return res, err
|
||||
}
|
||||
|
@ -107,43 +136,65 @@ func (h Handler) DeliverTx(ctx basecoin.Context, store state.SimpleDB,
|
|||
return res, nil
|
||||
}
|
||||
|
||||
// SetOption - sets the genesis account balance
|
||||
func (h Handler) SetOption(l log.Logger, store state.SimpleDB,
|
||||
module, key, value string, _ basecoin.SetOptioner) (log string, err error) {
|
||||
func (h Handler) creditTx(ctx basecoin.Context, store state.SimpleDB,
|
||||
credit CreditTx) (res basecoin.Result, err error) {
|
||||
|
||||
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 checkTx(ctx basecoin.Context, tx basecoin.Tx) (send SendTx, err error) {
|
||||
// check if the tx is proper type and valid
|
||||
send, ok := tx.Unwrap().(SendTx)
|
||||
if !ok {
|
||||
return send, errors.ErrInvalidFormat(TypeSend, tx)
|
||||
}
|
||||
err = send.ValidateBasic()
|
||||
// first check permissions!!
|
||||
info, err := loadHandlerInfo(store)
|
||||
if err != nil {
|
||||
return send, err
|
||||
return res, err
|
||||
}
|
||||
if info.Issuer.Empty() || !ctx.HasPermission(info.Issuer) {
|
||||
return res, errors.ErrUnauthorized()
|
||||
}
|
||||
|
||||
// load up the account
|
||||
addr := ChainAddr(credit.Debitor)
|
||||
acct, err := GetAccount(store, addr)
|
||||
if err != nil {
|
||||
return res, err
|
||||
}
|
||||
|
||||
// make and check changes
|
||||
acct.Coins = acct.Coins.Plus(credit.Credit)
|
||||
if !acct.Coins.IsNonnegative() {
|
||||
return res, ErrInsufficientFunds()
|
||||
}
|
||||
acct.Credit = acct.Credit.Plus(credit.Credit)
|
||||
if !acct.Credit.IsNonnegative() {
|
||||
return res, ErrInsufficientCredit()
|
||||
}
|
||||
|
||||
err = storeAccount(store, addr.Bytes(), acct)
|
||||
return res, err
|
||||
}
|
||||
|
||||
func checkTx(ctx basecoin.Context, send SendTx) error {
|
||||
// check if all inputs have permission
|
||||
for _, in := range send.Inputs {
|
||||
if !ctx.HasPermission(in.Address) {
|
||||
return send, errors.ErrUnauthorized()
|
||||
return errors.ErrUnauthorized()
|
||||
}
|
||||
}
|
||||
return send, nil
|
||||
return nil
|
||||
}
|
||||
|
||||
func setAccount(store state.KVStore, value string) (log string, err error) {
|
||||
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
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func setAccount(store state.SimpleDB, value string) (log string, err error) {
|
||||
var acc GenesisAccount
|
||||
err = data.FromJSON([]byte(value), &acc)
|
||||
if err != nil {
|
||||
|
@ -165,7 +216,7 @@ func setAccount(store state.KVStore, value string) (log string, err error) {
|
|||
|
||||
// setIssuer sets a permission for some super-powerful account to
|
||||
// mint money
|
||||
func setIssuer(store state.KVStore, value string) (log string, err error) {
|
||||
func setIssuer(store state.SimpleDB, value string) (log string, err error) {
|
||||
var issuer basecoin.Actor
|
||||
err = data.FromJSON([]byte(value), &issuer)
|
||||
if err != nil {
|
||||
|
|
|
@ -11,6 +11,7 @@ import (
|
|||
"github.com/tendermint/tmlibs/log"
|
||||
|
||||
"github.com/tendermint/basecoin"
|
||||
"github.com/tendermint/basecoin/errors"
|
||||
"github.com/tendermint/basecoin/modules/auth"
|
||||
"github.com/tendermint/basecoin/stack"
|
||||
"github.com/tendermint/basecoin/state"
|
||||
|
@ -75,7 +76,7 @@ func TestHandlerValidation(t *testing.T) {
|
|||
|
||||
for i, tc := range cases {
|
||||
ctx := stack.MockContext("base-chain", 100).WithPermissions(tc.perms...)
|
||||
_, err := checkTx(ctx, tc.tx)
|
||||
err := checkTx(ctx, tc.tx.Unwrap().(SendTx))
|
||||
if tc.valid {
|
||||
assert.Nil(err, "%d: %+v", i, err)
|
||||
} else {
|
||||
|
@ -84,7 +85,7 @@ func TestHandlerValidation(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestDeliverTx(t *testing.T) {
|
||||
func TestDeliverSendTx(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
require := require.New(t)
|
||||
|
||||
|
@ -247,3 +248,109 @@ func TestSetIssuer(t *testing.T) {
|
|||
assert.Equal(tc.issuer, info.Issuer)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDeliverCreditTx(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
require := require.New(t)
|
||||
|
||||
// sample coins
|
||||
someCoins := Coins{{"atom", 6570}}
|
||||
minusCoins := Coins{{"atom", -1234}}
|
||||
lessCoins := someCoins.Plus(minusCoins)
|
||||
otherCoins := Coins{{"eth", 11}}
|
||||
mixedCoins := someCoins.Plus(otherCoins)
|
||||
|
||||
// some sample addresses
|
||||
owner := basecoin.Actor{App: "foo", Address: []byte("rocks")}
|
||||
addr1 := basecoin.Actor{App: "coin", Address: []byte{1, 2}}
|
||||
key := NewAccountWithKey(someCoins)
|
||||
addr2 := key.Actor()
|
||||
addr3 := basecoin.Actor{ChainID: "other", App: "sigs", Address: []byte{3, 9}}
|
||||
|
||||
h := NewHandler()
|
||||
store := state.NewMemKVStore()
|
||||
ctx := stack.MockContext("secret", 77)
|
||||
|
||||
// set the owner who can issue credit
|
||||
js, err := json.Marshal(owner)
|
||||
require.Nil(err, "%+v", err)
|
||||
_, err = h.SetOption(log.NewNopLogger(), store, "coin", "issuer", string(js), nil)
|
||||
require.Nil(err, "%+v", err)
|
||||
|
||||
// give addr2 some coins to start
|
||||
_, err = h.SetOption(log.NewNopLogger(), store, "coin", "account", key.MakeOption(), nil)
|
||||
require.Nil(err, "%+v", err)
|
||||
|
||||
cases := []struct {
|
||||
tx basecoin.Tx
|
||||
perm basecoin.Actor
|
||||
check errors.CheckErr
|
||||
addr basecoin.Actor
|
||||
expected Account
|
||||
}{
|
||||
// require permission
|
||||
{
|
||||
tx: NewCreditTx(addr1, someCoins),
|
||||
check: errors.IsUnauthorizedErr,
|
||||
},
|
||||
// add credit
|
||||
{
|
||||
tx: NewCreditTx(addr1, someCoins),
|
||||
perm: owner,
|
||||
check: errors.NoErr,
|
||||
addr: addr1,
|
||||
expected: Account{Coins: someCoins, Credit: someCoins},
|
||||
},
|
||||
// remove some
|
||||
{
|
||||
tx: NewCreditTx(addr1, minusCoins),
|
||||
perm: owner,
|
||||
check: errors.NoErr,
|
||||
addr: addr1,
|
||||
expected: Account{Coins: lessCoins, Credit: lessCoins},
|
||||
},
|
||||
// can't remove more cash than there is
|
||||
{
|
||||
tx: NewCreditTx(addr1, otherCoins.Negative()),
|
||||
perm: owner,
|
||||
check: IsInsufficientFundsErr,
|
||||
},
|
||||
// cumulative with initial state
|
||||
{
|
||||
tx: NewCreditTx(addr2, otherCoins),
|
||||
perm: owner,
|
||||
check: errors.NoErr,
|
||||
addr: addr2,
|
||||
expected: Account{Coins: mixedCoins, Credit: otherCoins},
|
||||
},
|
||||
// Even if there is cash, credit can't go negative
|
||||
{
|
||||
tx: NewCreditTx(addr2, minusCoins),
|
||||
perm: owner,
|
||||
check: IsInsufficientCreditErr,
|
||||
},
|
||||
// make sure it works for other chains
|
||||
{
|
||||
tx: NewCreditTx(addr3, mixedCoins),
|
||||
perm: owner,
|
||||
check: errors.NoErr,
|
||||
addr: ChainAddr(addr3),
|
||||
expected: Account{Coins: mixedCoins, Credit: mixedCoins},
|
||||
},
|
||||
}
|
||||
|
||||
for i, tc := range cases {
|
||||
myStore := store.Checkpoint()
|
||||
|
||||
myCtx := ctx.WithPermissions(tc.perm)
|
||||
_, err = h.DeliverTx(myCtx, myStore, tc.tx, nil)
|
||||
assert.True(tc.check(err), "%d: %+v", i, err)
|
||||
|
||||
if err == nil {
|
||||
store.Commit(myStore)
|
||||
acct, err := GetAccount(store, tc.addr)
|
||||
require.Nil(err, "%+v", err)
|
||||
assert.Equal(tc.expected, acct, "%d", i)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -117,7 +117,7 @@ func TestIBCPostPacket(t *testing.T) {
|
|||
|
||||
}
|
||||
|
||||
func assertPacket(t *testing.T, istore state.KVStore, destID string, amount Coins) {
|
||||
func assertPacket(t *testing.T, istore state.SimpleDB, destID string, amount Coins) {
|
||||
assert := assert.New(t)
|
||||
require := require.New(t)
|
||||
|
||||
|
|
|
@ -78,12 +78,6 @@ func (h Handler) CheckTx(ctx basecoin.Context, store state.SimpleDB, tx basecoin
|
|||
|
||||
switch t := tx.Unwrap().(type) {
|
||||
case RegisterChainTx:
|
||||
// check permission to attach, do it here, so no permission check
|
||||
// by SetOption
|
||||
info := LoadInfo(store)
|
||||
if !info.Registrar.Empty() && !ctx.HasPermission(info.Registrar) {
|
||||
return res, errors.ErrUnauthorized()
|
||||
}
|
||||
return h.initSeed(ctx, store, t)
|
||||
case UpdateChainTx:
|
||||
return h.updateSeed(ctx, store, t)
|
||||
|
@ -103,12 +97,6 @@ func (h Handler) DeliverTx(ctx basecoin.Context, store state.SimpleDB, tx baseco
|
|||
|
||||
switch t := tx.Unwrap().(type) {
|
||||
case RegisterChainTx:
|
||||
// check permission to attach, do it here, so no permission check
|
||||
// by SetOption
|
||||
info := LoadInfo(store)
|
||||
if !info.Registrar.Empty() && !ctx.HasPermission(info.Registrar) {
|
||||
return res, errors.ErrUnauthorized()
|
||||
}
|
||||
return h.initSeed(ctx, store, t)
|
||||
case UpdateChainTx:
|
||||
return h.updateSeed(ctx, store, t)
|
||||
|
@ -125,6 +113,11 @@ func (h Handler) DeliverTx(ctx basecoin.Context, store state.SimpleDB, tx baseco
|
|||
func (h Handler) initSeed(ctx basecoin.Context, store state.SimpleDB,
|
||||
t RegisterChainTx) (res basecoin.Result, err error) {
|
||||
|
||||
info := LoadInfo(store)
|
||||
if !info.Registrar.Empty() && !ctx.HasPermission(info.Registrar) {
|
||||
return res, errors.ErrUnauthorized()
|
||||
}
|
||||
|
||||
// verify that the header looks reasonable
|
||||
chainID := t.ChainID()
|
||||
s := NewChainSet(store)
|
||||
|
|
Loading…
Reference in New Issue