diff --git a/cmd/gaia/app/app.go b/cmd/gaia/app/app.go index b2f51498ba..dbecada004 100644 --- a/cmd/gaia/app/app.go +++ b/cmd/gaia/app/app.go @@ -40,10 +40,11 @@ type GaiaApp struct { keyStake *sdk.KVStoreKey // Manage getting and setting accounts - accountMapper auth.AccountMapper - coinKeeper bank.Keeper - ibcMapper ibc.Mapper - stakeKeeper stake.Keeper + accountMapper auth.AccountMapper + feeCollectionKeeper auth.FeeCollectionKeeper + coinKeeper bank.Keeper + ibcMapper ibc.Mapper + stakeKeeper stake.Keeper } func NewGaiaApp(logger log.Logger, db dbm.DB) *GaiaApp { @@ -81,7 +82,7 @@ func NewGaiaApp(logger log.Logger, db dbm.DB) *GaiaApp { app.SetInitChainer(app.initChainer) app.SetEndBlocker(stake.NewEndBlocker(app.stakeKeeper)) app.MountStoresIAVL(app.keyMain, app.keyAccount, app.keyIBC, app.keyStake) - app.SetAnteHandler(auth.NewAnteHandler(app.accountMapper)) + app.SetAnteHandler(auth.NewAnteHandler(app.accountMapper, app.feeCollectionKeeper)) err := app.LoadLatestVersion(app.keyMain) if err != nil { cmn.Exit(err.Error()) diff --git a/examples/basecoin/app/app.go b/examples/basecoin/app/app.go index 610a9e5525..086fa32b36 100644 --- a/examples/basecoin/app/app.go +++ b/examples/basecoin/app/app.go @@ -35,10 +35,11 @@ type BasecoinApp struct { keyStake *sdk.KVStoreKey // Manage getting and setting accounts - accountMapper auth.AccountMapper - coinKeeper bank.Keeper - ibcMapper ibc.Mapper - stakeKeeper stake.Keeper + accountMapper auth.AccountMapper + feeCollectionKeeper auth.FeeCollectionKeeper + coinKeeper bank.Keeper + ibcMapper ibc.Mapper + stakeKeeper stake.Keeper } func NewBasecoinApp(logger log.Logger, db dbm.DB) *BasecoinApp { @@ -78,7 +79,7 @@ func NewBasecoinApp(logger log.Logger, db dbm.DB) *BasecoinApp { // Initialize BaseApp. app.SetInitChainer(app.initChainer) app.MountStoresIAVL(app.keyMain, app.keyAccount, app.keyIBC, app.keyStake) - app.SetAnteHandler(auth.NewAnteHandler(app.accountMapper)) + app.SetAnteHandler(auth.NewAnteHandler(app.accountMapper, app.feeCollectionKeeper)) err := app.LoadLatestVersion(app.keyMain) if err != nil { cmn.Exit(err.Error()) diff --git a/examples/democoin/app/app.go b/examples/democoin/app/app.go index 9696630b6e..2075a64da0 100644 --- a/examples/democoin/app/app.go +++ b/examples/democoin/app/app.go @@ -39,11 +39,12 @@ type DemocoinApp struct { capKeyStakingStore *sdk.KVStoreKey // keepers - coinKeeper bank.Keeper - coolKeeper cool.Keeper - powKeeper pow.Keeper - ibcMapper ibc.Mapper - stakeKeeper simplestake.Keeper + feeCollectionKeeper auth.FeeCollectionKeeper + coinKeeper bank.Keeper + coolKeeper cool.Keeper + powKeeper pow.Keeper + ibcMapper ibc.Mapper + stakeKeeper simplestake.Keeper // Manage getting and setting accounts accountMapper auth.AccountMapper @@ -89,7 +90,7 @@ func NewDemocoinApp(logger log.Logger, db dbm.DB) *DemocoinApp { // Initialize BaseApp. app.SetInitChainer(app.initChainerFn(app.coolKeeper, app.powKeeper)) app.MountStoresIAVL(app.capKeyMainStore, app.capKeyAccountStore, app.capKeyPowStore, app.capKeyIBCStore, app.capKeyStakingStore) - app.SetAnteHandler(auth.NewAnteHandler(app.accountMapper)) + app.SetAnteHandler(auth.NewAnteHandler(app.accountMapper, app.feeCollectionKeeper)) err := app.LoadLatestVersion(app.capKeyMainStore) if err != nil { cmn.Exit(err.Error()) diff --git a/x/auth/ante.go b/x/auth/ante.go index c92a87641a..9663bcfe45 100644 --- a/x/auth/ante.go +++ b/x/auth/ante.go @@ -9,13 +9,14 @@ import ( ) const ( - verifyCost = 100 + deductFeesCost sdk.Gas = 10 + verifyCost = 100 ) // NewAnteHandler returns an AnteHandler that checks // and increments sequence numbers, checks signatures, // and deducts fees from the first signer. -func NewAnteHandler(am AccountMapper) sdk.AnteHandler { +func NewAnteHandler(am AccountMapper, fck FeeCollectionKeeper) sdk.AnteHandler { return func( ctx sdk.Context, tx sdk.Tx, @@ -77,10 +78,12 @@ func NewAnteHandler(am AccountMapper) sdk.AnteHandler { if i == 0 { // TODO: min fee if !fee.Amount.IsZero() { + ctx.GasMeter().ConsumeGas(deductFeesCost, "deductFees") signerAcc, res = deductFees(signerAcc, fee) if !res.IsOK() { return ctx, res, true } + fck.addCollectedFees(ctx, fee.Amount) } } diff --git a/x/auth/ante_test.go b/x/auth/ante_test.go index fcde2b464b..b7f22e5d54 100644 --- a/x/auth/ante_test.go +++ b/x/auth/ante_test.go @@ -69,11 +69,12 @@ func newTestTxWithSignBytes(msg sdk.Msg, privs []crypto.PrivKey, seqs []int64, f // Test various error cases in the AnteHandler control flow. func TestAnteHandlerSigErrors(t *testing.T) { // setup - ms, capKey := setupMultiStore() + ms, capKey, capKey2 := setupMultiStore() cdc := wire.NewCodec() RegisterBaseAccount(cdc) mapper := NewAccountMapper(cdc, capKey, &BaseAccount{}) - anteHandler := NewAnteHandler(mapper) + feeCollector := NewFeeCollectionKeeper(cdc, capKey2) + anteHandler := NewAnteHandler(mapper, feeCollector) ctx := sdk.NewContext(ms, abci.Header{ChainID: "mychainid"}, false, nil, log.NewNopLogger()) // keys and addresses @@ -110,11 +111,12 @@ func TestAnteHandlerSigErrors(t *testing.T) { // Test logic around sequence checking with one signer and many signers. func TestAnteHandlerSequences(t *testing.T) { // setup - ms, capKey := setupMultiStore() + ms, capKey, capKey2 := setupMultiStore() cdc := wire.NewCodec() RegisterBaseAccount(cdc) mapper := NewAccountMapper(cdc, capKey, &BaseAccount{}) - anteHandler := NewAnteHandler(mapper) + feeCollector := NewFeeCollectionKeeper(cdc, capKey2) + anteHandler := NewAnteHandler(mapper, feeCollector) ctx := sdk.NewContext(ms, abci.Header{ChainID: "mychainid"}, false, nil, log.NewNopLogger()) // keys and addresses @@ -176,11 +178,12 @@ func TestAnteHandlerSequences(t *testing.T) { // Test logic around fee deduction. func TestAnteHandlerFees(t *testing.T) { // setup - ms, capKey := setupMultiStore() + ms, capKey, capKey2 := setupMultiStore() cdc := wire.NewCodec() RegisterBaseAccount(cdc) mapper := NewAccountMapper(cdc, capKey, &BaseAccount{}) - anteHandler := NewAnteHandler(mapper) + feeCollector := NewFeeCollectionKeeper(cdc, capKey2) + anteHandler := NewAnteHandler(mapper, feeCollector) ctx := sdk.NewContext(ms, abci.Header{ChainID: "mychainid"}, false, nil, log.NewNopLogger()) // keys and addresses @@ -206,18 +209,23 @@ func TestAnteHandlerFees(t *testing.T) { mapper.SetAccount(ctx, acc1) checkInvalidTx(t, anteHandler, ctx, tx, sdk.CodeInsufficientFunds) + assert.True(t, feeCollector.GetCollectedFees(ctx).IsEqual(emptyCoins)) + acc1.SetCoins(sdk.Coins{{"atom", 150}}) mapper.SetAccount(ctx, acc1) checkValidTx(t, anteHandler, ctx, tx) + + assert.True(t, feeCollector.GetCollectedFees(ctx).IsEqual(sdk.Coins{{"atom", 150}})) } func TestAnteHandlerBadSignBytes(t *testing.T) { // setup - ms, capKey := setupMultiStore() + ms, capKey, capKey2 := setupMultiStore() cdc := wire.NewCodec() RegisterBaseAccount(cdc) mapper := NewAccountMapper(cdc, capKey, &BaseAccount{}) - anteHandler := NewAnteHandler(mapper) + feeCollector := NewFeeCollectionKeeper(cdc, capKey2) + anteHandler := NewAnteHandler(mapper, feeCollector) ctx := sdk.NewContext(ms, abci.Header{ChainID: "mychainid"}, false, nil, log.NewNopLogger()) // keys and addresses @@ -288,11 +296,12 @@ func TestAnteHandlerBadSignBytes(t *testing.T) { func TestAnteHandlerSetPubKey(t *testing.T) { // setup - ms, capKey := setupMultiStore() + ms, capKey, capKey2 := setupMultiStore() cdc := wire.NewCodec() RegisterBaseAccount(cdc) mapper := NewAccountMapper(cdc, capKey, &BaseAccount{}) - anteHandler := NewAnteHandler(mapper) + feeCollector := NewFeeCollectionKeeper(cdc, capKey2) + anteHandler := NewAnteHandler(mapper, feeCollector) ctx := sdk.NewContext(ms, abci.Header{ChainID: "mychainid"}, false, nil, log.NewNopLogger()) // keys and addresses diff --git a/x/auth/context_test.go b/x/auth/context_test.go index e1db131679..a93de44d0c 100644 --- a/x/auth/context_test.go +++ b/x/auth/context_test.go @@ -12,7 +12,7 @@ import ( ) func TestContextWithSigners(t *testing.T) { - ms, _ := setupMultiStore() + ms, _, _ := setupMultiStore() ctx := sdk.NewContext(ms, abci.Header{ChainID: "mychainid"}, false, nil, log.NewNopLogger()) _, _, addr1 := keyPubAddr() diff --git a/x/auth/feekeeper.go b/x/auth/feekeeper.go new file mode 100644 index 0000000000..3e03a81aa2 --- /dev/null +++ b/x/auth/feekeeper.go @@ -0,0 +1,62 @@ +package auth + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" + wire "github.com/cosmos/cosmos-sdk/wire" +) + +var ( + collectedFeesKey = []byte("collectedFees") +) + +// This FeeCollectionKeeper handles collection of fees in the anteHandler +// and setting of MinFees for different fee tokens +type FeeCollectionKeeper struct { + + // The (unexposed) key used to access the fee store from the Context. + key sdk.StoreKey + + // The wire codec for binary encoding/decoding of accounts. + cdc *wire.Codec +} + +// NewFeeKeeper returns a new FeeKeeper +func NewFeeCollectionKeeper(cdc *wire.Codec, key sdk.StoreKey) FeeCollectionKeeper { + return FeeCollectionKeeper{ + key: key, + cdc: cdc, + } +} + +// Adds to Collected Fee Pool +func (fck FeeCollectionKeeper) GetCollectedFees(ctx sdk.Context) sdk.Coins { + store := ctx.KVStore(fck.key) + bz := store.Get(collectedFeesKey) + if bz == nil { + return sdk.Coins{} + } + + feePool := &(sdk.Coins{}) + fck.cdc.MustUnmarshalBinary(bz, feePool) + return *feePool +} + +// Sets to Collected Fee Pool +func (fck FeeCollectionKeeper) setCollectedFees(ctx sdk.Context, coins sdk.Coins) { + bz := fck.cdc.MustMarshalBinary(coins) + store := ctx.KVStore(fck.key) + store.Set(collectedFeesKey, bz) +} + +// Adds to Collected Fee Pool +func (fck FeeCollectionKeeper) addCollectedFees(ctx sdk.Context, coins sdk.Coins) sdk.Coins { + newCoins := fck.GetCollectedFees(ctx).Plus(coins) + fck.setCollectedFees(ctx, newCoins) + + return newCoins +} + +// Clears the collected Fee Pool +func (fck FeeCollectionKeeper) ClearCollectedFees(ctx sdk.Context) { + fck.setCollectedFees(ctx, sdk.Coins{}) +} diff --git a/x/auth/feekeeper_test.go b/x/auth/feekeeper_test.go new file mode 100644 index 0000000000..2f1ffc59bc --- /dev/null +++ b/x/auth/feekeeper_test.go @@ -0,0 +1,75 @@ +package auth + +import ( + "testing" + + "github.com/stretchr/testify/assert" + + abci "github.com/tendermint/abci/types" + "github.com/tendermint/tmlibs/log" + + sdk "github.com/cosmos/cosmos-sdk/types" + wire "github.com/cosmos/cosmos-sdk/wire" +) + +var ( + emptyCoins = sdk.Coins{} + oneCoin = sdk.Coins{{"foocoin", 1}} + twoCoins = sdk.Coins{{"foocoin", 2}} +) + +func TestFeeCollectionKeeperGetSet(t *testing.T) { + ms, _, capKey2 := setupMultiStore() + cdc := wire.NewCodec() + + // make context and keeper + ctx := sdk.NewContext(ms, abci.Header{}, false, nil, log.NewNopLogger()) + fck := NewFeeCollectionKeeper(cdc, capKey2) + + // no coins initially + currFees := fck.GetCollectedFees(ctx) + assert.True(t, currFees.IsEqual(emptyCoins)) + + // set feeCollection to oneCoin + fck.setCollectedFees(ctx, oneCoin) + + // check that it is equal to oneCoin + assert.True(t, fck.GetCollectedFees(ctx).IsEqual(oneCoin)) +} + +func TestFeeCollectionKeeperAdd(t *testing.T) { + ms, _, capKey2 := setupMultiStore() + cdc := wire.NewCodec() + + // make context and keeper + ctx := sdk.NewContext(ms, abci.Header{}, false, nil, log.NewNopLogger()) + fck := NewFeeCollectionKeeper(cdc, capKey2) + + // no coins initially + assert.True(t, fck.GetCollectedFees(ctx).IsEqual(emptyCoins)) + + // add oneCoin and check that pool is now oneCoin + fck.addCollectedFees(ctx, oneCoin) + assert.True(t, fck.GetCollectedFees(ctx).IsEqual(oneCoin)) + + // add oneCoin again and check that pool is now twoCoins + fck.addCollectedFees(ctx, oneCoin) + assert.True(t, fck.GetCollectedFees(ctx).IsEqual(twoCoins)) +} + +func TestFeeCollectionKeeperClear(t *testing.T) { + ms, _, capKey2 := setupMultiStore() + cdc := wire.NewCodec() + + // make context and keeper + ctx := sdk.NewContext(ms, abci.Header{}, false, nil, log.NewNopLogger()) + fck := NewFeeCollectionKeeper(cdc, capKey2) + + // set coins initially + fck.setCollectedFees(ctx, twoCoins) + assert.True(t, fck.GetCollectedFees(ctx).IsEqual(twoCoins)) + + // clear fees and see that pool is now empty + fck.ClearCollectedFees(ctx) + assert.True(t, fck.GetCollectedFees(ctx).IsEqual(emptyCoins)) +} diff --git a/x/auth/mapper_test.go b/x/auth/mapper_test.go index cdd418990a..7f6397069a 100644 --- a/x/auth/mapper_test.go +++ b/x/auth/mapper_test.go @@ -14,17 +14,19 @@ import ( wire "github.com/cosmos/cosmos-sdk/wire" ) -func setupMultiStore() (sdk.MultiStore, *sdk.KVStoreKey) { +func setupMultiStore() (sdk.MultiStore, *sdk.KVStoreKey, *sdk.KVStoreKey) { db := dbm.NewMemDB() capKey := sdk.NewKVStoreKey("capkey") + capKey2 := sdk.NewKVStoreKey("capkey2") ms := store.NewCommitMultiStore(db) ms.MountStoreWithDB(capKey, sdk.StoreTypeIAVL, db) + ms.MountStoreWithDB(capKey2, sdk.StoreTypeIAVL, db) ms.LoadLatestVersion() - return ms, capKey + return ms, capKey, capKey2 } func TestAccountMapperGetSet(t *testing.T) { - ms, capKey := setupMultiStore() + ms, capKey, _ := setupMultiStore() cdc := wire.NewCodec() RegisterBaseAccount(cdc)