Add CreditTx and tests
This commit is contained in:
parent
6135345af8
commit
3027eeb3c3
|
@ -12,6 +12,7 @@ import (
|
||||||
var (
|
var (
|
||||||
errNoAccount = fmt.Errorf("No such account")
|
errNoAccount = fmt.Errorf("No such account")
|
||||||
errInsufficientFunds = fmt.Errorf("Insufficient funds")
|
errInsufficientFunds = fmt.Errorf("Insufficient funds")
|
||||||
|
errInsufficientCredit = fmt.Errorf("Insufficient credit")
|
||||||
errNoInputs = fmt.Errorf("No input coins")
|
errNoInputs = fmt.Errorf("No input coins")
|
||||||
errNoOutputs = fmt.Errorf("No output coins")
|
errNoOutputs = fmt.Errorf("No output coins")
|
||||||
errInvalidAddress = fmt.Errorf("Invalid address")
|
errInvalidAddress = fmt.Errorf("Invalid address")
|
||||||
|
@ -66,6 +67,13 @@ func IsInsufficientFundsErr(err error) bool {
|
||||||
return errors.IsSameError(errInsufficientFunds, err)
|
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 {
|
func ErrNoInputs() errors.TMError {
|
||||||
return errors.WithCode(errNoInputs, invalidInput)
|
return errors.WithCode(errNoInputs, invalidInput)
|
||||||
}
|
}
|
||||||
|
|
|
@ -37,28 +37,57 @@ func (Handler) AssertDispatcher() {}
|
||||||
func (h Handler) CheckTx(ctx basecoin.Context, store state.SimpleDB,
|
func (h Handler) CheckTx(ctx basecoin.Context, store state.SimpleDB,
|
||||||
tx basecoin.Tx, _ basecoin.Checker) (res basecoin.Result, err error) {
|
tx basecoin.Tx, _ basecoin.Checker) (res basecoin.Result, err error) {
|
||||||
|
|
||||||
send, err := checkTx(ctx, tx)
|
err = tx.ValidateBasic()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return res, err
|
return res, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// now make sure there is money
|
switch t := tx.Unwrap().(type) {
|
||||||
for _, in := range send.Inputs {
|
case SendTx:
|
||||||
_, err = CheckCoins(store, in.Address, in.Coins.Negative())
|
return res, h.checkSendTx(ctx, store, t)
|
||||||
if err != nil {
|
case CreditTx:
|
||||||
return res, err
|
return h.creditTx(ctx, store, t)
|
||||||
}
|
}
|
||||||
}
|
return res, errors.ErrUnknownTxType(tx.Unwrap())
|
||||||
|
|
||||||
// otherwise, we are good
|
|
||||||
return res, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// DeliverTx moves the money
|
// DeliverTx moves the money
|
||||||
func (h Handler) DeliverTx(ctx basecoin.Context, store state.SimpleDB,
|
func (h Handler) DeliverTx(ctx basecoin.Context, store state.SimpleDB,
|
||||||
tx basecoin.Tx, cb basecoin.Deliver) (res basecoin.Result, err error) {
|
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 {
|
if err != nil {
|
||||||
return res, err
|
return res, err
|
||||||
}
|
}
|
||||||
|
@ -107,43 +136,65 @@ func (h Handler) DeliverTx(ctx basecoin.Context, store state.SimpleDB,
|
||||||
return res, nil
|
return res, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetOption - sets the genesis account balance
|
func (h Handler) creditTx(ctx basecoin.Context, store state.SimpleDB,
|
||||||
func (h Handler) SetOption(l log.Logger, store state.SimpleDB,
|
credit CreditTx) (res basecoin.Result, err error) {
|
||||||
module, key, value string, _ basecoin.SetOptioner) (log string, err error) {
|
|
||||||
|
|
||||||
if module != NameCoin {
|
// first check permissions!!
|
||||||
return "", errors.ErrUnknownModule(module)
|
info, err := loadHandlerInfo(store)
|
||||||
}
|
|
||||||
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()
|
|
||||||
if err != nil {
|
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
|
// check if all inputs have permission
|
||||||
for _, in := range send.Inputs {
|
for _, in := range send.Inputs {
|
||||||
if !ctx.HasPermission(in.Address) {
|
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
|
var acc GenesisAccount
|
||||||
err = data.FromJSON([]byte(value), &acc)
|
err = data.FromJSON([]byte(value), &acc)
|
||||||
if err != nil {
|
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
|
// setIssuer sets a permission for some super-powerful account to
|
||||||
// mint money
|
// 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
|
var issuer basecoin.Actor
|
||||||
err = data.FromJSON([]byte(value), &issuer)
|
err = data.FromJSON([]byte(value), &issuer)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -11,6 +11,7 @@ import (
|
||||||
"github.com/tendermint/tmlibs/log"
|
"github.com/tendermint/tmlibs/log"
|
||||||
|
|
||||||
"github.com/tendermint/basecoin"
|
"github.com/tendermint/basecoin"
|
||||||
|
"github.com/tendermint/basecoin/errors"
|
||||||
"github.com/tendermint/basecoin/modules/auth"
|
"github.com/tendermint/basecoin/modules/auth"
|
||||||
"github.com/tendermint/basecoin/stack"
|
"github.com/tendermint/basecoin/stack"
|
||||||
"github.com/tendermint/basecoin/state"
|
"github.com/tendermint/basecoin/state"
|
||||||
|
@ -75,7 +76,7 @@ func TestHandlerValidation(t *testing.T) {
|
||||||
|
|
||||||
for i, tc := range cases {
|
for i, tc := range cases {
|
||||||
ctx := stack.MockContext("base-chain", 100).WithPermissions(tc.perms...)
|
ctx := stack.MockContext("base-chain", 100).WithPermissions(tc.perms...)
|
||||||
_, err := checkTx(ctx, tc.tx)
|
err := checkTx(ctx, tc.tx.Unwrap().(SendTx))
|
||||||
if tc.valid {
|
if tc.valid {
|
||||||
assert.Nil(err, "%d: %+v", i, err)
|
assert.Nil(err, "%d: %+v", i, err)
|
||||||
} else {
|
} else {
|
||||||
|
@ -84,7 +85,7 @@ func TestHandlerValidation(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestDeliverTx(t *testing.T) {
|
func TestDeliverSendTx(t *testing.T) {
|
||||||
assert := assert.New(t)
|
assert := assert.New(t)
|
||||||
require := require.New(t)
|
require := require.New(t)
|
||||||
|
|
||||||
|
@ -247,3 +248,109 @@ func TestSetIssuer(t *testing.T) {
|
||||||
assert.Equal(tc.issuer, info.Issuer)
|
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)
|
assert := assert.New(t)
|
||||||
require := require.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) {
|
switch t := tx.Unwrap().(type) {
|
||||||
case RegisterChainTx:
|
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)
|
return h.initSeed(ctx, store, t)
|
||||||
case UpdateChainTx:
|
case UpdateChainTx:
|
||||||
return h.updateSeed(ctx, store, t)
|
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) {
|
switch t := tx.Unwrap().(type) {
|
||||||
case RegisterChainTx:
|
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)
|
return h.initSeed(ctx, store, t)
|
||||||
case UpdateChainTx:
|
case UpdateChainTx:
|
||||||
return h.updateSeed(ctx, store, t)
|
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,
|
func (h Handler) initSeed(ctx basecoin.Context, store state.SimpleDB,
|
||||||
t RegisterChainTx) (res basecoin.Result, err error) {
|
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
|
// verify that the header looks reasonable
|
||||||
chainID := t.ChainID()
|
chainID := t.ChainID()
|
||||||
s := NewChainSet(store)
|
s := NewChainSet(store)
|
||||||
|
|
Loading…
Reference in New Issue