Connect coin handler to a store and test it
This commit is contained in:
parent
225904b010
commit
c5a1b4883a
@ -29,12 +29,18 @@ func (_ Handler) Name() string {
|
||||
|
||||
// CheckTx checks if there is enough money in the account
|
||||
func (h Handler) CheckTx(ctx basecoin.Context, store types.KVStore, tx basecoin.Tx) (res basecoin.Result, err error) {
|
||||
_, err = checkTx(ctx, tx)
|
||||
send, err := checkTx(ctx, tx)
|
||||
if err != nil {
|
||||
return res, err
|
||||
}
|
||||
|
||||
// now make sure there is money
|
||||
for _, in := range send.Inputs {
|
||||
_, err = h.CheckCoins(store, in.Address, in.Coins, in.Sequence)
|
||||
if err != nil {
|
||||
return res, err
|
||||
}
|
||||
}
|
||||
|
||||
// otherwise, we are good
|
||||
return res, nil
|
||||
@ -42,12 +48,29 @@ func (h Handler) CheckTx(ctx basecoin.Context, store types.KVStore, tx basecoin.
|
||||
|
||||
// DeliverTx moves the money
|
||||
func (h Handler) DeliverTx(ctx basecoin.Context, store types.KVStore, tx basecoin.Tx) (res basecoin.Result, err error) {
|
||||
_, err = checkTx(ctx, tx)
|
||||
send, err := checkTx(ctx, tx)
|
||||
if err != nil {
|
||||
return res, err
|
||||
}
|
||||
|
||||
// now move the money
|
||||
// deduct from all input accounts
|
||||
for _, in := range send.Inputs {
|
||||
_, err = h.ChangeCoins(store, in.Address, in.Coins.Negative(), in.Sequence)
|
||||
if err != nil {
|
||||
return res, err
|
||||
}
|
||||
}
|
||||
|
||||
// add to all output accounts
|
||||
for _, out := range send.Outputs {
|
||||
// note: sequence number is ignored when adding coins, only checked for subtracting
|
||||
_, err = h.ChangeCoins(store, out.Address, out.Coins, 0)
|
||||
if err != nil {
|
||||
return res, err
|
||||
}
|
||||
}
|
||||
|
||||
// a-ok!
|
||||
return basecoin.Result{}, nil
|
||||
}
|
||||
|
||||
|
||||
@ -4,72 +4,159 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/tendermint/basecoin"
|
||||
"github.com/tendermint/basecoin/stack"
|
||||
"github.com/tendermint/basecoin/types"
|
||||
)
|
||||
|
||||
func TestHandlerPermissions(t *testing.T) {
|
||||
// this makes sure that txs are rejected with invalid data or permissions
|
||||
func TestHandlerValidation(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
// TODO: need to update this when we actually have token store
|
||||
h := NewHandler()
|
||||
|
||||
// these are all valid, except for minusCoins
|
||||
addr1 := basecoin.Actor{App: "coin", Address: []byte{1, 2}}
|
||||
addr2 := basecoin.Actor{App: "role", Address: []byte{7, 8}}
|
||||
someCoins := types.Coins{{"atom", 123}}
|
||||
doubleCoins := types.Coins{{"atom", 246}}
|
||||
minusCoins := types.Coins{{"eth", -34}}
|
||||
|
||||
cases := []struct {
|
||||
valid bool
|
||||
tx SendTx
|
||||
tx basecoin.Tx
|
||||
perms []basecoin.Actor
|
||||
}{
|
||||
// auth works with different apps
|
||||
{true,
|
||||
SendTx{
|
||||
Inputs: []TxInput{NewTxInput(addr1, someCoins, 2)},
|
||||
Outputs: []TxOutput{NewTxOutput(addr2, someCoins)}},
|
||||
NewSendTx(
|
||||
[]TxInput{NewTxInput(addr1, someCoins, 2)},
|
||||
[]TxOutput{NewTxOutput(addr2, someCoins)}),
|
||||
[]basecoin.Actor{addr1}},
|
||||
{true,
|
||||
SendTx{
|
||||
Inputs: []TxInput{NewTxInput(addr2, someCoins, 2)},
|
||||
Outputs: []TxOutput{NewTxOutput(addr1, someCoins)}},
|
||||
NewSendTx(
|
||||
[]TxInput{NewTxInput(addr2, someCoins, 2)},
|
||||
[]TxOutput{NewTxOutput(addr1, someCoins)}),
|
||||
[]basecoin.Actor{addr1, addr2}},
|
||||
// check multi-input with both sigs
|
||||
{true,
|
||||
NewSendTx(
|
||||
[]TxInput{NewTxInput(addr1, someCoins, 2), NewTxInput(addr2, someCoins, 3)},
|
||||
[]TxOutput{NewTxOutput(addr1, doubleCoins)}),
|
||||
[]basecoin.Actor{addr1, addr2}},
|
||||
// wrong permissions fail
|
||||
{false,
|
||||
SendTx{
|
||||
Inputs: []TxInput{NewTxInput(addr1, someCoins, 2)},
|
||||
Outputs: []TxOutput{NewTxOutput(addr2, someCoins)}},
|
||||
NewSendTx(
|
||||
[]TxInput{NewTxInput(addr1, someCoins, 2)},
|
||||
[]TxOutput{NewTxOutput(addr2, someCoins)}),
|
||||
[]basecoin.Actor{}},
|
||||
{false,
|
||||
SendTx{
|
||||
Inputs: []TxInput{NewTxInput(addr1, someCoins, 2)},
|
||||
Outputs: []TxOutput{NewTxOutput(addr2, someCoins)}},
|
||||
NewSendTx(
|
||||
[]TxInput{NewTxInput(addr1, someCoins, 2)},
|
||||
[]TxOutput{NewTxOutput(addr2, someCoins)}),
|
||||
[]basecoin.Actor{addr2}},
|
||||
{false,
|
||||
NewSendTx(
|
||||
[]TxInput{NewTxInput(addr1, someCoins, 2), NewTxInput(addr2, someCoins, 3)},
|
||||
[]TxOutput{NewTxOutput(addr1, doubleCoins)}),
|
||||
[]basecoin.Actor{addr1}},
|
||||
// invalid input fails
|
||||
{false,
|
||||
SendTx{
|
||||
Inputs: []TxInput{NewTxInput(addr1, minusCoins, 2)},
|
||||
Outputs: []TxOutput{NewTxOutput(addr2, minusCoins)}},
|
||||
NewSendTx(
|
||||
[]TxInput{NewTxInput(addr1, minusCoins, 2)},
|
||||
[]TxOutput{NewTxOutput(addr2, minusCoins)}),
|
||||
[]basecoin.Actor{addr2}},
|
||||
}
|
||||
|
||||
for i, tc := range cases {
|
||||
ctx := stack.MockContext().WithPermissions(tc.perms...)
|
||||
_, err := h.CheckTx(ctx, nil, tc.tx.Wrap())
|
||||
_, err := checkTx(ctx, tc.tx)
|
||||
if tc.valid {
|
||||
assert.Nil(err, "%d: %+v", i, err)
|
||||
} else {
|
||||
assert.NotNil(err, "%d", i)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_, err = h.DeliverTx(ctx, nil, tc.tx.Wrap())
|
||||
if tc.valid {
|
||||
func TestDeliverTx(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
require := require.New(t)
|
||||
|
||||
// some sample settings
|
||||
addr1 := basecoin.Actor{App: "coin", Address: []byte{1, 2}}
|
||||
addr2 := basecoin.Actor{App: "role", Address: []byte{7, 8}}
|
||||
addr3 := basecoin.Actor{App: "coin", Address: []byte{6, 5, 4, 3}}
|
||||
|
||||
someCoins := types.Coins{{"atom", 123}}
|
||||
moreCoins := types.Coins{{"atom", 6487}}
|
||||
diffCoins := moreCoins.Minus(someCoins)
|
||||
otherCoins := types.Coins{{"eth", 11}}
|
||||
mixedCoins := someCoins.Plus(otherCoins)
|
||||
|
||||
type money struct {
|
||||
addr basecoin.Actor
|
||||
coins types.Coins
|
||||
}
|
||||
|
||||
cases := []struct {
|
||||
init []money
|
||||
tx basecoin.Tx
|
||||
perms []basecoin.Actor
|
||||
final []money // nil for error
|
||||
}{
|
||||
{
|
||||
[]money{{addr1, moreCoins}},
|
||||
NewSendTx(
|
||||
[]TxInput{NewTxInput(addr1, someCoins, 1)},
|
||||
[]TxOutput{NewTxOutput(addr2, someCoins)}),
|
||||
[]basecoin.Actor{addr1},
|
||||
[]money{{addr1, diffCoins}, {addr2, someCoins}},
|
||||
},
|
||||
// simple multi-sig 2 accounts to 1
|
||||
{
|
||||
[]money{{addr1, mixedCoins}, {addr2, moreCoins}},
|
||||
NewSendTx(
|
||||
[]TxInput{NewTxInput(addr1, otherCoins, 1), NewTxInput(addr2, someCoins, 1)},
|
||||
[]TxOutput{NewTxOutput(addr3, mixedCoins)}),
|
||||
[]basecoin.Actor{addr1, addr2},
|
||||
[]money{{addr1, someCoins}, {addr2, diffCoins}, {addr3, mixedCoins}},
|
||||
},
|
||||
// multi-sig with one account sending many times
|
||||
{
|
||||
[]money{{addr1, moreCoins.Plus(otherCoins)}},
|
||||
NewSendTx(
|
||||
[]TxInput{NewTxInput(addr1, otherCoins, 1), NewTxInput(addr1, someCoins, 2)},
|
||||
[]TxOutput{NewTxOutput(addr2, mixedCoins)}),
|
||||
[]basecoin.Actor{addr1},
|
||||
[]money{{addr1, diffCoins}, {addr2, mixedCoins}},
|
||||
},
|
||||
}
|
||||
|
||||
h := NewHandler()
|
||||
for i, tc := range cases {
|
||||
// setup the cases....
|
||||
store := types.NewMemKVStore()
|
||||
for _, m := range tc.init {
|
||||
acct := Account{Coins: m.coins}
|
||||
err := storeAccount(store, h.makeKey(m.addr), acct)
|
||||
require.Nil(err, "%d: %+v", i, err)
|
||||
}
|
||||
|
||||
ctx := stack.MockContext().WithPermissions(tc.perms...)
|
||||
_, err := h.DeliverTx(ctx, store, tc.tx)
|
||||
if len(tc.final) > 0 { // valid
|
||||
assert.Nil(err, "%d: %+v", i, err)
|
||||
// make sure the final balances are correct
|
||||
for _, f := range tc.final {
|
||||
acct, err := loadAccount(store, h.makeKey(f.addr))
|
||||
assert.Nil(err, "%d: %+v", i, err)
|
||||
assert.Equal(f.coins, acct.Coins)
|
||||
}
|
||||
} else {
|
||||
assert.NotNil(err, "%d", i)
|
||||
// TODO: make sure balances unchanged!
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -45,18 +45,24 @@ func (a Accountant) ChangeCoins(store types.KVStore, addr basecoin.Actor, coins
|
||||
// it doesn't save anything, that is up to you to decide (Check/Change Coins)
|
||||
func (a Accountant) updateCoins(store types.KVStore, addr basecoin.Actor, coins types.Coins, seq int) (acct Account, err error) {
|
||||
acct, err = loadAccount(store, a.makeKey(addr))
|
||||
// we can increase an empty account...
|
||||
if IsNoAccountErr(err) && coins.IsPositive() {
|
||||
err = nil
|
||||
}
|
||||
if err != nil {
|
||||
return acct, err
|
||||
}
|
||||
|
||||
// check sequence
|
||||
if seq != acct.Sequence+1 {
|
||||
return acct, ErrInvalidSequence()
|
||||
// check sequence if we are deducting... ugh, need a cleaner replay protection
|
||||
if !coins.IsPositive() {
|
||||
if seq != acct.Sequence+1 {
|
||||
return acct, ErrInvalidSequence()
|
||||
}
|
||||
acct.Sequence += 1
|
||||
}
|
||||
acct.Sequence += 1
|
||||
|
||||
// check amount
|
||||
final := acct.Coins.Minus(coins)
|
||||
final := acct.Coins.Plus(coins)
|
||||
if !final.IsNonnegative() {
|
||||
return acct, ErrInsufficientFunds()
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user