Skip to content

Commit

Permalink
Auto-swap non-osmo tx fees to osmo (#1145)
Browse files Browse the repository at this point in the history
Co-authored-by: Matt, Park <[email protected]>
Co-authored-by: Aleksandr Bezobchuk <[email protected]>
Co-authored-by: Aleksandr Bezobchuk <[email protected]>
Co-authored-by: Matt, Park <[email protected]>
Co-authored-by: Dev Ojha <[email protected]>
Co-authored-by: Dev Ojha <[email protected]>
  • Loading branch information
6 people authored Apr 22, 2022
1 parent fe1b5d2 commit a33c3c2
Show file tree
Hide file tree
Showing 12 changed files with 470 additions and 32 deletions.
11 changes: 5 additions & 6 deletions app/ante.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,13 @@ package app
import (
wasm "github.com/CosmWasm/wasmd/x/wasm"
wasmkeeper "github.com/CosmWasm/wasmd/x/wasm/keeper"
channelkeeper "github.com/cosmos/ibc-go/v2/modules/core/04-channel/keeper"
ibcante "github.com/cosmos/ibc-go/v2/modules/core/ante"

servertypes "github.com/cosmos/cosmos-sdk/server/types"
sdk "github.com/cosmos/cosmos-sdk/types"
ante "github.com/cosmos/cosmos-sdk/x/auth/ante"
"github.com/cosmos/cosmos-sdk/x/auth/signing"
authtypes "github.com/cosmos/cosmos-sdk/x/auth/types"
channelkeeper "github.com/cosmos/ibc-go/v2/modules/core/04-channel/keeper"
ibcante "github.com/cosmos/ibc-go/v2/modules/core/ante"

txfeeskeeper "github.com/osmosis-labs/osmosis/v7/x/txfees/keeper"
txfeestypes "github.com/osmosis-labs/osmosis/v7/x/txfees/types"
Expand All @@ -23,7 +22,7 @@ func NewAnteHandler(
wasmConfig wasm.Config,
txCounterStoreKey sdk.StoreKey,
ak ante.AccountKeeper,
bankKeeper authtypes.BankKeeper,
bankKeeper txfeestypes.BankKeeper,
txFeesKeeper *txfeeskeeper.Keeper,
spotPriceCalculator txfeestypes.SpotPriceCalculator,
sigGasConsumer ante.SignatureVerificationGasConsumer,
Expand All @@ -32,7 +31,7 @@ func NewAnteHandler(
) sdk.AnteHandler {
mempoolFeeOptions := txfeestypes.NewMempoolFeeOptions(appOpts)
mempoolFeeDecorator := txfeeskeeper.NewMempoolFeeDecorator(*txFeesKeeper, mempoolFeeOptions)

deductFeeDecorator := txfeeskeeper.NewDeductFeeDecorator(*txFeesKeeper, ak, bankKeeper, nil)
return sdk.ChainAnteDecorators(
ante.NewSetUpContextDecorator(), // outermost AnteDecorator. SetUpContext must be called first
wasmkeeper.NewLimitSimulationGasDecorator(wasmConfig.SimulationGasLimit),
Expand All @@ -45,7 +44,7 @@ func NewAnteHandler(
ante.TxTimeoutHeightDecorator{},
ante.NewValidateMemoDecorator(ak),
ante.NewConsumeGasForTxSizeDecorator(ak),
ante.NewDeductFeeDecorator(ak, bankKeeper, nil),
deductFeeDecorator,
ante.NewSetPubKeyDecorator(ak), // SetPubKeyDecorator must be called before all signature verification decorators
ante.NewValidateSigCountDecorator(ak),
ante.NewSigGasConsumeDecorator(ak, sigGasConsumer),
Expand Down
24 changes: 24 additions & 0 deletions app/apptesting/test_suite.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,10 @@ import (
gammtypes "github.com/osmosis-labs/osmosis/v7/x/gamm/types"
lockupkeeper "github.com/osmosis-labs/osmosis/v7/x/lockup/keeper"
lockuptypes "github.com/osmosis-labs/osmosis/v7/x/lockup/types"

"github.com/cosmos/cosmos-sdk/client"
"github.com/cosmos/cosmos-sdk/types/tx/signing"
authsigning "github.com/cosmos/cosmos-sdk/x/auth/signing"
)

type KeeperTestHelper struct {
Expand Down Expand Up @@ -228,6 +232,26 @@ func (keeperTestHelper *KeeperTestHelper) LockTokens(addr sdk.AccAddress, coins
return msgResponse.ID
}

func (keeperTestHelper *KeeperTestHelper) BuildTx(
txBuilder client.TxBuilder,
msgs []sdk.Msg,
sigV2 signing.SignatureV2,
memo string, txFee sdk.Coins,
gasLimit uint64,
) authsigning.Tx {
err := txBuilder.SetMsgs(msgs[0])
keeperTestHelper.Require().NoError(err)

err = txBuilder.SetSignatures(sigV2)
keeperTestHelper.Require().NoError(err)

txBuilder.SetMemo(memo)
txBuilder.SetFeeAmount(txFee)
txBuilder.SetGasLimit(gasLimit)

return txBuilder.GetTx()
}

// CreateRandomAccounts is a function return a list of randomly generated AccAddresses
func CreateRandomAccounts(numAccts int) []sdk.AccAddress {
testAddrs := make([]sdk.AccAddress, numAccts)
Expand Down
8 changes: 8 additions & 0 deletions app/keepers.go
Original file line number Diff line number Diff line change
Expand Up @@ -329,10 +329,17 @@ func (app *OsmosisApp) InitNormalKeepers(
)
app.PoolIncentivesKeeper = &poolIncentivesKeeper

// Note: gammKeeper is expected to satisfy the SpotPriceCalculator interface parameter
txFeesKeeper := txfeeskeeper.NewKeeper(
appCodec,
app.AccountKeeper,
app.BankKeeper,
app.EpochsKeeper,
keys[txfeestypes.StoreKey],
app.GAMMKeeper,
app.GAMMKeeper,
txfeestypes.FeeCollectorName,
txfeestypes.NonNativeFeeCollectorName,
)
app.TxFeesKeeper = &txFeesKeeper

Expand Down Expand Up @@ -437,6 +444,7 @@ func (app *OsmosisApp) SetupHooks() {
app.EpochsKeeper.SetHooks(
epochstypes.NewMultiEpochHooks(
// insert epoch hooks receivers here
app.TxFeesKeeper.Hooks(),
app.SuperfluidKeeper.Hooks(),
app.IncentivesKeeper.Hooks(),
app.MintKeeper.Hooks(),
Expand Down
1 change: 1 addition & 0 deletions app/modules.go
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,7 @@ var moduleAccountPermissions = map[string][]string{
poolincentivestypes.ModuleName: nil,
superfluidtypes.ModuleName: {authtypes.Minter, authtypes.Burner},
txfeestypes.ModuleName: nil,
txfeestypes.NonNativeFeeCollectorName: nil,
wasm.ModuleName: {authtypes.Burner},
}

Expand Down
115 changes: 115 additions & 0 deletions x/txfees/keeper/feedecorator.go
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
package keeper

import (
"fmt"

sdk "github.com/cosmos/cosmos-sdk/types"
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"

"github.com/osmosis-labs/osmosis/v7/x/txfees/keeper/txfee_filters"
"github.com/osmosis-labs/osmosis/v7/x/txfees/types"

authtypes "github.com/cosmos/cosmos-sdk/x/auth/types"
)

// MempoolFeeDecorator will check if the transaction's fee is at least as large
Expand Down Expand Up @@ -121,3 +125,114 @@ func (mfd MempoolFeeDecorator) GetMinBaseGasPriceForTx(ctx sdk.Context, baseDeno
}
return cfgMinGasPrice
}

// DeductFeeDecorator deducts fees from the first signer of the tx.
// If the first signer does not have the funds to pay for the fees, we return an InsufficientFunds error.
// We call next AnteHandler if fees successfully deducted.
//
// CONTRACT: Tx must implement FeeTx interface to use DeductFeeDecorator
type DeductFeeDecorator struct {
ak types.AccountKeeper
bankKeeper types.BankKeeper
feegrantKeeper types.FeegrantKeeper
txFeesKeeper Keeper
}

func NewDeductFeeDecorator(tk Keeper, ak types.AccountKeeper, bk types.BankKeeper, fk types.FeegrantKeeper) DeductFeeDecorator {
return DeductFeeDecorator{
ak: ak,
bankKeeper: bk,
feegrantKeeper: fk,
txFeesKeeper: tk,
}
}

func (dfd DeductFeeDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bool, next sdk.AnteHandler) (newCtx sdk.Context, err error) {
feeTx, ok := tx.(sdk.FeeTx)
if !ok {
return ctx, sdkerrors.Wrap(sdkerrors.ErrTxDecode, "Tx must be a FeeTx")
}

// checks to make sure the module account has been set to collect fees in base token
if addr := dfd.ak.GetModuleAddress(types.FeeCollectorName); addr == nil {
return ctx, fmt.Errorf("Fee collector module account (%s) has not been set", types.FeeCollectorName)
}

// checks to make sure a separate module account has been set to collect fees not in base token
if addrNonNativeFee := dfd.ak.GetModuleAddress(types.NonNativeFeeCollectorName); addrNonNativeFee == nil {
return ctx, fmt.Errorf("non native fee collector module account (%s) has not been set", types.NonNativeFeeCollectorName)
}

// fee can be in any denom (checked for validity later)
fee := feeTx.GetFee()
feePayer := feeTx.FeePayer()
feeGranter := feeTx.FeeGranter()

// set the fee payer as the default address to deduct fees from
deductFeesFrom := feePayer

// If a fee granter was set, deduct fee from the fee granter's account.
if feeGranter != nil {
if dfd.feegrantKeeper == nil {
return ctx, sdkerrors.Wrap(sdkerrors.ErrInvalidRequest, "fee grants is not enabled")
} else if !feeGranter.Equals(feePayer) {
err := dfd.feegrantKeeper.UseGrantedFees(ctx, feeGranter, feePayer, fee, tx.GetMsgs())
if err != nil {
return ctx, sdkerrors.Wrapf(err, "%s not allowed to pay fees from %s", feeGranter, feePayer)
}
}

// if no errors, change the account that is charged for fees to the fee granter
deductFeesFrom = feeGranter
}

deductFeesFromAcc := dfd.ak.GetAccount(ctx, deductFeesFrom)
if deductFeesFromAcc == nil {
return ctx, sdkerrors.Wrapf(sdkerrors.ErrUnknownAddress, "fee payer address: %s does not exist", deductFeesFrom)
}

// deducts the fees and transfer them to the module account
if !feeTx.GetFee().IsZero() {
err = DeductFees(dfd.txFeesKeeper, dfd.bankKeeper, ctx, deductFeesFromAcc, feeTx.GetFee())
if err != nil {
return ctx, err
}
}

ctx.EventManager().EmitEvents(sdk.Events{sdk.NewEvent(sdk.EventTypeTx,
sdk.NewAttribute(sdk.AttributeKeyFee, feeTx.GetFee().String()),
)})

return next(ctx, tx, simulate)
}

// DeductFees deducts fees from the given account and transfers them to the set module account.
func DeductFees(txFeesKeeper types.TxFeesKeeper, bankKeeper types.BankKeeper, ctx sdk.Context, acc authtypes.AccountI, fees sdk.Coins) error {
// Checks the validity of the fee tokens (sorted, have positive amount, valid and unique denomination)
if !fees.IsValid() {
return sdkerrors.Wrapf(sdkerrors.ErrInsufficientFee, "invalid fee amount: %s", fees)
}

// pulls base denom from TxFeesKeeper (should be uOSMO)
baseDenom, err := txFeesKeeper.GetBaseDenom(ctx)
if err != nil {
return err
}

// checks if input fee is uOSMO (assumes only one fee token exists in the fees array (as per the check in mempoolFeeDecorator))
if fees[0].Denom == baseDenom {
// sends to FeeCollectorName module account
err := bankKeeper.SendCoinsFromAccountToModule(ctx, acc.GetAddress(), types.FeeCollectorName, fees)
if err != nil {
return sdkerrors.Wrapf(sdkerrors.ErrInsufficientFunds, err.Error())
}
} else {
// sends to NonNativeFeeCollectorName module account
err := bankKeeper.SendCoinsFromAccountToModule(ctx, acc.GetAddress(), types.NonNativeFeeCollectorName, fees)
if err != nil {
return sdkerrors.Wrapf(sdkerrors.ErrInsufficientFunds, err.Error())
}
}

return nil
}
Loading

0 comments on commit a33c3c2

Please sign in to comment.