Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(txfees, gamm): remove taker fee swap event from trade events #87

Draft
wants to merge 3 commits into
base: main-dym
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions proto/dymensionxyz/dymension/txfees/v1beta1/events.proto
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,12 @@ import "cosmos/base/v1beta1/coin.proto";
option go_package = "github.com/osmosis-labs/osmosis/v15/x/txfees/types";

message EventChargeFee {
string payer = 1;
string payer = 1;
string taker_fee = 2;
// Beneficiary is the address that will receive the fee. Optional: may be empty.
string beneficiary = 3;
string beneficiary = 3;
string beneficiary_revenue = 4;

string swapped_taker_fee = 5;
bool community_pool = 6;
}
18 changes: 2 additions & 16 deletions x/gamm/keeper/msg_server.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import (
"github.com/osmosis-labs/osmosis/v15/osmoutils"
"github.com/osmosis-labs/osmosis/v15/x/gamm/pool-models/balancer"
"github.com/osmosis-labs/osmosis/v15/x/gamm/types"
poolmanagertypes "github.com/osmosis-labs/osmosis/v15/x/poolmanager/types"
)

type msgServer struct {
Expand Down Expand Up @@ -180,17 +179,14 @@ func (server msgServer) SwapExactAmountIn(goCtx context.Context, msg *types.MsgS
return nil, err
}

// first pool for taker fee swaps if needed
takerFeeRoute := msg.Routes[0]

// If the IN denom is a RollApp, we reward the IN RollApp owner.
// Otherwise, if the OUT denom is a RollApp, we reward the OUT RollApp owner.
// OUT denom is the last route's token out denom.
outDenom := msg.Routes[len(msg.Routes)-1].TokenOutDenom

beneficiary := server.keeper.getTakerFeeBeneficiary(ctx, msg.TokenIn.Denom, outDenom)

err = server.keeper.chargeTakerFee(ctx, takerFeesCoins, sender, takerFeeRoute, beneficiary)
err = server.keeper.chargeTakerFee(ctx, takerFeesCoins, sender, beneficiary)
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -235,23 +231,13 @@ func (server msgServer) SwapExactAmountOut(goCtx context.Context, msg *types.Msg
tokenInCoin := sdk.NewCoin(msg.Routes[0].TokenInDenom, tokenInAmount)
tokenInAmountWithTakerFee, takerFeeCoin := server.keeper.AddTakerFee(tokenInCoin, takerFee)

// first pool for taker fee swaps if needed
takerFeeRoute := poolmanagertypes.SwapAmountInRoute{}
takerFeeRoute.PoolId = msg.Routes[0].PoolId
if len(msg.Routes) > 1 {
takerFeeRoute.TokenOutDenom = msg.Routes[1].TokenInDenom
} else {
takerFeeRoute.TokenOutDenom = msg.TokenOutDenom()
}

// If the IN denom is a RollApp, we reward the IN RollApp owner.
// Otherwise, if the OUT denom is a RollApp, we reward the OUT RollApp owner.
// IN denom is the first route's token in denom.
inDenom := msg.Routes[0].TokenInDenom

beneficiary := server.keeper.getTakerFeeBeneficiary(ctx, inDenom, msg.TokenOut.Denom)

err = server.keeper.chargeTakerFee(ctx, takerFeeCoin, sender, takerFeeRoute, beneficiary)
err = server.keeper.chargeTakerFee(ctx, takerFeeCoin, sender, beneficiary)
if err != nil {
return nil, err
}
Expand Down
44 changes: 1 addition & 43 deletions x/gamm/keeper/taker_fee.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,6 @@ import (
"fmt"

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

poolmanagertypes "github.com/osmosis-labs/osmosis/v15/x/poolmanager/types"
)

// ChargeTakerFee charges the taker fee to the sender
Expand All @@ -17,57 +15,17 @@ func (k Keeper) chargeTakerFee(
ctx sdk.Context,
takerFeeCoin sdk.Coin,
sender sdk.AccAddress,
route poolmanagertypes.SwapAmountInRoute,
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

chargeTakerFee used to swap if it's unknown denom.
as we don't expect pools with non-dym, we don't need this logic.
And if we would like it in the future, it needs to be part of the x/txfees logic anyway

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think it's correct we don't expect pools with non-dym.
we may have usdc pools.

beneficiary *sdk.AccAddress,
) error {
if takerFeeCoin.IsZero() {
return nil
}

// Check if the taker fee coin is the base denom
denom, err := k.txfeeKeeper.GetBaseDenom(ctx)
if err != nil {
return err
}
if takerFeeCoin.Denom == denom {
return k.sendToTxFees(ctx, sender, takerFeeCoin, beneficiary)
}

// Check if the taker fee coin is a registered fee token
_, err = k.txfeeKeeper.GetFeeToken(ctx, takerFeeCoin.Denom)
if err == nil {
return k.sendToTxFees(ctx, sender, takerFeeCoin, beneficiary)
}

// If not supported denom, swap on the first pool to get some pool base denom, which has liquidity with DYM
ctx.Logger().Debug("taker fee coin is not supported by txfee module, requires swap", "takerFeeCoin", takerFeeCoin)
swappedTakerFee, err := k.swapTakerFee(ctx, sender, route, takerFeeCoin)
if err != nil {
return err
}

return k.sendToTxFees(ctx, sender, swappedTakerFee, beneficiary)
}

// swapTakerFee swaps the taker fee coin to the base denom on the first pool
func (k Keeper) swapTakerFee(ctx sdk.Context, sender sdk.AccAddress, route poolmanagertypes.SwapAmountInRoute, tokenIn sdk.Coin) (sdk.Coin, error) {
minAmountOut := sdk.ZeroInt()
swapRoutes := poolmanagertypes.SwapAmountInRoutes{route}
out, err := k.poolManager.RouteExactAmountIn(ctx, sender, swapRoutes, tokenIn, minAmountOut)
if err != nil {
return sdk.Coin{}, err
}
coin := sdk.NewCoin(route.TokenOutDenom, out)
return coin, nil
}

// sendToTxFees sends the taker fee coin to the txfees module
func (k Keeper) sendToTxFees(ctx sdk.Context, sender sdk.AccAddress, takerFeeCoin sdk.Coin, beneficiary *sdk.AccAddress) error {
err := k.txfeeKeeper.ChargeFeesFromPayer(ctx, sender, takerFeeCoin, beneficiary)
if err != nil {
return fmt.Errorf("charge fees: sender: %s: fee: %s: %w", sender, takerFeeCoin, err)
}
return nil

}

// While charging taker fee, we reward the owner of the rollapp involved in swap. In that case,
Expand Down
99 changes: 51 additions & 48 deletions x/txfees/keeper/fees.go
Original file line number Diff line number Diff line change
@@ -1,16 +1,20 @@
package keeper

import (
"errors"
"fmt"

sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/dymensionxyz/sdk-utils/utils/uevent"

"github.com/osmosis-labs/osmosis/v15/osmoutils"
poolmanagertypes "github.com/osmosis-labs/osmosis/v15/x/poolmanager/types"
"github.com/osmosis-labs/osmosis/v15/x/txfees/types"
)

var (
errUnswappableFeeToken = fmt.Errorf("fee token cannot be swapped to base denom")
)

// ChargeFeesFromPayer charges the specified taker fee from the payer's account and
// processes it according to the fee token's properties.
// Wrapper for ChargeFees that sends the fee to x/txfees in advance.
Expand Down Expand Up @@ -44,74 +48,73 @@ func (k Keeper) ChargeFeesFromPayer(
// If the fee token is unknown, it is sent to the community pool.
func (k Keeper) ChargeFees(
ctx sdk.Context,
takerFeeCoin sdk.Coin,
takerFee sdk.Coin,
beneficiary *sdk.AccAddress,
payer string, // optional, only used for the event
) error {
if takerFeeCoin.IsZero() {
if takerFee.IsZero() {
// Nothing to charge
return nil
}

// Swap the taker fee to the base denom
baseDenomFee, communityPoolCoins, err := k.swapFeeToBaseDenom(ctx, takerFeeCoin)
if err != nil {
return fmt.Errorf("swap fee to base denom: %w", err)
}
takerFeeBaseDenom, err := k.swapFeeToBaseDenom(ctx, takerFee)
// Send unknown fee tokens to the community pool
if errors.Is(err, errUnswappableFeeToken) {
k.Logger(ctx).With("fee", takerFee.String(), "payer", payer).
Debug("Cannot swap fee to base denom. Sending it to the community pool.")

// If the fee token is unknown or the swap is unsuccessful, the fee is sent to the community pool.
if !communityPoolCoins.Empty() {
// Send unknown fee tokens to the community pool
err = k.communityPool.FundCommunityPool(ctx, communityPoolCoins, k.accountKeeper.GetModuleAddress(types.ModuleName))
err = k.communityPool.FundCommunityPool(ctx, sdk.NewCoins(takerFee), k.accountKeeper.GetModuleAddress(types.ModuleName))
if err != nil {
return fmt.Errorf("unknown fee token: func community pool: %w", err)
return fmt.Errorf("fund community pool: %w", err)
}

k.Logger(ctx).With("fee", communityPoolCoins.String(), "error", err).
Error("Cannot swap fee to base denom. Send it to the community pool.")
}

// If the fee token is unknown or the swap is unsuccessful, emit event and return
if baseDenomFee.Empty() {
err = uevent.EmitTypedEvent(ctx, &types.EventChargeFee{
Payer: payer,
TakerFee: takerFeeCoin.String(),
Beneficiary: ValueFromPtr(beneficiary).String(),
BeneficiaryRevenue: "",
Payer: payer,
TakerFee: takerFee.String(),
CommunityPool: true,
})
if err != nil {
k.Logger(ctx).Error("Failed to emit event", "event", "EventChargeFee", "error", err)
k.Logger(ctx).Error("emit event", "event", "EventChargeFee", "error", err)
}
return nil
} else if err != nil {
return fmt.Errorf("swap fee to base denom: %w", err)
}

// Nothing to charge after swapping (not supposed to happen actually)
if takerFeeBaseDenom.IsNil() || takerFeeBaseDenom.IsZero() {
k.Logger(ctx).With("fee", takerFee.String(), "payer", payer).
Error("Fee after swapping to base denom is zero. Nothing to charge.")
return nil
}

// Send 50% of the base denom fee to the beneficiary if presented
var beneficiaryCoins sdk.Coins
beneficiaryFee := sdk.Coins{}
if beneficiary != nil {
fee := baseDenomFee[0]
// beneficiaryCoin = takerFeeCoin / 2
// note that beneficiaryCoin * 2 != takerFeeCoin because of the integer division rounding
beneficiaryCoins = sdk.Coins{{Denom: fee.Denom, Amount: fee.Amount.QuoRaw(2)}}
// takerFeeCoin = takerFeeCoin - beneficiaryCoin
baseDenomFee = baseDenomFee.Sub(beneficiaryCoins...)

err = k.bankKeeper.SendCoinsFromModuleToAccount(ctx, types.ModuleName, *beneficiary, beneficiaryCoins)
// beneficiaryFee = takerFee / 2
beneficiaryFee = sdk.Coins{sdk.NewCoin(takerFeeBaseDenom.Denom, takerFeeBaseDenom.Amount.QuoRaw(2))}
err = k.bankKeeper.SendCoinsFromModuleToAccount(ctx, types.ModuleName, *beneficiary, beneficiaryFee)
if err != nil {
return fmt.Errorf("send coins from fee payer to beneficiary: %w", err)
}

// update remaining taker fee
takerFeeBaseDenom = takerFeeBaseDenom.Sub(beneficiaryFee[0])
}

// Burn the remaining base denom fee
err = k.bankKeeper.BurnCoins(ctx, types.ModuleName, baseDenomFee)
err = k.bankKeeper.BurnCoins(ctx, types.ModuleName, sdk.NewCoins(takerFeeBaseDenom))
if err != nil {
return fmt.Errorf("burn coins: %w", err)
}

err = uevent.EmitTypedEvent(ctx, &types.EventChargeFee{
Payer: payer,
TakerFee: baseDenomFee.String(),
TakerFee: takerFee.String(),
SwappedTakerFee: takerFeeBaseDenom.String(),
Beneficiary: ValueFromPtr(beneficiary).String(),
BeneficiaryRevenue: beneficiaryCoins.String(),
BeneficiaryRevenue: beneficiaryFee.String(),
})
if err != nil {
k.Logger(ctx).Error("Failed to emit event", "event", "EventChargeFee", "error", err)
Expand All @@ -128,44 +131,44 @@ func ValueFromPtr[T any](ptr *T) (zero T) {
}

// swapFeeToBaseDenom swaps the taker fee coin to the base denom.
// If the fee token is unknown, it is sent to the community pool.
// Returns error if the fee token is unknown or if swapping fails.
// The fee must be sent to the txfees module account beforehand.
func (k Keeper) swapFeeToBaseDenom(
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

swapFeeToBaseDenom was cleaned of the communityPool logic.
now handled by caller, based on returned err

ctx sdk.Context,
takerFeeCoin sdk.Coin,
) (baseDenomFee, communityPoolFee sdk.Coins, err error) {
) (sdk.Coin, error) {
baseDenom, err := k.GetBaseDenom(ctx)
if err != nil {
return nil, nil, fmt.Errorf("get base denom: %w", err)
return sdk.Coin{}, fmt.Errorf("get base denom: %w", err)
}
moduleAddr := k.accountKeeper.GetModuleAddress(types.ModuleName)

// The fee is already in the base denom
if takerFeeCoin.Denom == baseDenom {
return sdk.Coins{takerFeeCoin}, nil, nil
return takerFeeCoin, nil
}

// Get a fee token for the coin
feetoken, err := k.GetFeeToken(ctx, takerFeeCoin.Denom)
if err != nil {
return nil, sdk.Coins{takerFeeCoin}, nil
return sdk.Coin{}, errUnswappableFeeToken
}

// Swap the coin to base denom
var (
tokenOutAmount = sdk.ZeroInt() // Token amount in base denom
route = []poolmanagertypes.SwapAmountInRoute{{
route = []poolmanagertypes.SwapAmountInRoute{{
PoolId: feetoken.PoolID,
TokenOutDenom: baseDenom,
}}
)
err = osmoutils.ApplyFuncIfNoError(ctx, func(ctx sdk.Context) error {
tokenOutAmount, err = k.poolManager.RouteExactAmountIn(ctx, moduleAddr, route, takerFeeCoin, sdk.ZeroInt())
return err
})

cacheCtx, write := ctx.CacheContext()
tokenOutAmount, err := k.poolManager.RouteExactAmountIn(cacheCtx, moduleAddr, route, takerFeeCoin, sdk.ZeroInt())
if err != nil {
return nil, sdk.Coins{takerFeeCoin}, nil
return sdk.Coin{}, fmt.Errorf("failed to swap fee token: %w", err)
}
cacheCtx.WithEventManager(sdk.NewEventManager()) // This clears any events that were emitted
write() // This writes the state changes but with cleared events

return sdk.Coins{{Denom: baseDenom, Amount: tokenOutAmount}}, nil, nil
return sdk.NewCoin(baseDenom, tokenOutAmount), nil
}
Loading