diff --git a/modules/fee/handler.go b/modules/fee/handler.go index 6980245bcd..64a64bf893 100644 --- a/modules/fee/handler.go +++ b/modules/fee/handler.go @@ -24,9 +24,10 @@ var _ stack.Middleware = SimpleFeeMiddleware{} // NewSimpleFeeMiddleware returns a fee handler with a fixed minimum fee. // // If minFee is 0, then the FeeTx is optional -func NewSimpleFeeMiddleware(minFee coin.Coin) SimpleFeeMiddleware { +func NewSimpleFeeMiddleware(minFee coin.Coin, collector basecoin.Actor) SimpleFeeMiddleware { return SimpleFeeMiddleware{ - MinFee: minFee, + MinFee: minFee, + Collector: collector, } } diff --git a/modules/fee/handler_test.go b/modules/fee/handler_test.go new file mode 100644 index 0000000000..9e0568de0b --- /dev/null +++ b/modules/fee/handler_test.go @@ -0,0 +1,134 @@ +package fee_test + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/tendermint/basecoin" + "github.com/tendermint/basecoin/modules/coin" + "github.com/tendermint/basecoin/modules/fee" + "github.com/tendermint/basecoin/stack" + "github.com/tendermint/basecoin/state" + "github.com/tendermint/tmlibs/log" +) + +func TestFeeChecks(t *testing.T) { + assert := assert.New(t) + require := require.New(t) + + atom := func(i int64) coin.Coin { return coin.Coin{"atom", i} } + eth := func(i int64) coin.Coin { return coin.Coin{"eth", i} } + atoms := func(i int64) coin.Coins { return coin.Coins{{"atom", i}} } + wallet := func(i, j int64) coin.Coins { return coin.Coins{atom(i), eth(j)} } + + // some coin amounts... + zero := coin.Coin{} + mixed := wallet(1200, 55) + pure := atoms(46657) + + // these are some accounts + collector := basecoin.Actor{App: fee.NameFee, Address: []byte("bank")} + key1 := coin.NewAccountWithKey(mixed) + key2 := coin.NewAccountWithKey(pure) + act1, act2 := key1.Actor(), key2.Actor() + + // set up the apps.... + disp := stack.NewDispatcher( + // OKHandler will just return success to a RawTx + stack.WrapHandler(stack.OKHandler{}), + // coin is needed to handle the IPC call from Fee middleware + stack.WrapHandler(coin.NewHandler()), + ) + // app1 requires no fees + app1 := stack.New(fee.NewSimpleFeeMiddleware(atom(0), collector)).Use(disp) + // app2 requires 2 atom + app2 := stack.New(fee.NewSimpleFeeMiddleware(atom(2), collector)).Use(disp) + + // set up the store and init the accounts + store := state.NewMemKVStore() + l := log.NewNopLogger() + _, err := app1.SetOption(l, store, "coin", "account", key1.MakeOption()) + require.Nil(err, "%+v", err) + _, err = app2.SetOption(l, store, "coin", "account", key2.MakeOption()) + require.Nil(err, "%+v", err) + + cases := []struct { + valid bool + // this is the middleware stack to test + app basecoin.Handler + // they sign the tx + signer basecoin.Actor + // wrap with the given fee if hasFee is true + hasFee bool + payer basecoin.Actor + fee coin.Coin + // expected balance after the tx + left coin.Coins + collected coin.Coins + }{ + // make sure it works with no fee (control group) + {true, app1, act1, false, act1, zero, mixed, nil}, + {true, app1, act2, false, act2, zero, pure, nil}, + + // no fee or too low is rejected + {false, app2, act1, false, act1, zero, mixed, nil}, + {false, app2, act2, false, act2, zero, pure, nil}, + {false, app2, act1, true, act1, zero, mixed, nil}, + {false, app2, act2, true, act2, atom(1), pure, nil}, + + // proper fees are transfered in both cases + {true, app1, act1, true, act1, atom(1), wallet(1199, 55), atoms(1)}, + {true, app2, act2, true, act2, atom(57), atoms(46600), atoms(58)}, + + // // fee must be the proper type... + {false, app1, act1, true, act1, eth(5), wallet(1199, 55), atoms(58)}, + + // signature must match + {false, app2, act1, true, act2, atom(5), atoms(46600), atoms(58)}, + + // send only works within limits + {true, app2, act1, true, act1, atom(1100), wallet(99, 55), atoms(1158)}, + {false, app2, act1, true, act1, atom(500), wallet(99, 55), atoms(1158)}, + } + + for i, tc := range cases { + // build the tx + tx := stack.NewRawTx([]byte{7, 8, 9}) + if tc.hasFee { + tx = fee.NewFee(tx, tc.fee, tc.payer) + } + + // set up the permissions + ctx := stack.MockContext("x", 1).WithPermissions(tc.signer) + + // call checktx... + _, err := tc.app.CheckTx(ctx, store, tx) + if tc.valid { + assert.Nil(err, "%d: %+v", i, err) + } else { + assert.NotNil(err, "%d", i) + } + + // call delivertx... + _, err = tc.app.DeliverTx(ctx, store, tx) + if tc.valid { + assert.Nil(err, "%d: %+v", i, err) + } else { + assert.NotNil(err, "%d", i) + } + + // check the account balance afterwards.... + cspace := stack.PrefixedStore(coin.NameCoin, store) + acct, err := coin.GetAccount(cspace, tc.payer) + require.Nil(err, "%d: %+v", i, err) + assert.Equal(tc.left, acct.Coins, "%d", i) + + // check the collected balance afterwards.... + acct, err = coin.GetAccount(cspace, collector) + require.Nil(err, "%d: %+v", i, err) + assert.Equal(tc.collected, acct.Coins, "%d", i) + } + +} diff --git a/stack/helpers.go b/stack/helpers.go index 5dce066075..7405c16dda 100644 --- a/stack/helpers.go +++ b/stack/helpers.go @@ -21,7 +21,7 @@ const ( ByteRawTx = 0xF0 ByteCheckTx = 0xF1 - TypeRawTx = "raw" + TypeRawTx = NameOK + "/raw" // this will just say a-ok to RawTx TypeCheckTx = NameCheck + "/tx" rawMaxSize = 2000 * 1000