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

Implement Taker Fee Burn Mechanism #9022

Closed
wants to merge 9 commits into from
Closed
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
1 change: 1 addition & 0 deletions app/modules.go
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,7 @@ var moduleAccountPermissions = map[string][]string{
txfeestypes.NonNativeTxFeeCollectorName: nil,
txfeestypes.TakerFeeStakersName: nil,
txfeestypes.TakerFeeCommunityPoolName: nil,
txfeestypes.TakerFeeBurnName: nil,
txfeestypes.TakerFeeCollectorName: nil,
wasmtypes.ModuleName: {authtypes.Burner},
tokenfactorytypes.ModuleName: {authtypes.Minter, authtypes.Burner},
Expand Down
10 changes: 9 additions & 1 deletion proto/osmosis/poolmanager/v1beta1/genesis.proto
Original file line number Diff line number Diff line change
Expand Up @@ -123,13 +123,21 @@ message TakerFeeDistributionPercentage {
(gogoproto.moretags) = "yaml:\"community_pool\"",
(gogoproto.nullable) = false
];
string burn = 3 [

(gogoproto.customtype) = "cosmossdk.io/math.LegacyDec",
(gogoproto.moretags) = "yaml:\"burn\"",
(gogoproto.nullable) = false
];
}

message TakerFeesTracker {
repeated cosmos.base.v1beta1.Coin taker_fees_to_stakers = 1
[ (gogoproto.nullable) = false ];
repeated cosmos.base.v1beta1.Coin taker_fees_to_community_pool = 2
[ (gogoproto.nullable) = false ];
repeated cosmos.base.v1beta1.Coin taker_fees_burned = 4
[ (gogoproto.nullable) = false ];
int64 height_accounting_starts_from = 3
[ (gogoproto.moretags) = "yaml:\"height_accounting_starts_from\"" ];
}
Expand All @@ -144,4 +152,4 @@ message PoolVolume {
(gogoproto.nullable) = false,
(gogoproto.castrepeated) = "github.com/cosmos/cosmos-sdk/types.Coins"
];
}
}
17 changes: 17 additions & 0 deletions x/poolmanager/protorev.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,23 @@ func (k Keeper) UpdateTakerFeeTrackerForCommunityPoolByDenom(ctx sdk.Context, de
return osmoutils.IncreaseCoinByDenomFromPrefix(ctx, k.storeKey, types.KeyTakerFeeCommunityPoolProtoRevArray, denom, increasedAmt)
}

// GetTakerFeeTrackerForBurn returns the taker fee for burn tracker for all denoms that has been
// collected since the accounting height.
func (k Keeper) GetTakerFeeTrackerForBurn(ctx sdk.Context) []sdk.Coin {
return osmoutils.GetCoinArrayFromPrefix(ctx, k.storeKey, types.KeyTakerFeeBurnProtoRevArray)
}

// GetTakerFeeTrackerForBurnByDenom returns the taker fee for burn tracker for the specified denom that has been
// collected since the accounting height. If the denom is not found, a zero coin is returned.
func (k Keeper) GetTakerFeeTrackerForBurnByDenom(ctx sdk.Context, denom string) (sdk.Coin, error) {
return osmoutils.GetCoinByDenomFromPrefix(ctx, k.storeKey, types.KeyTakerFeeBurnProtoRevArray, denom)
}

// UpdateTakerFeeTrackerForBurnByDenom increases the take fee for burn tracker for the specified denom by the specified amount.
func (k Keeper) UpdateTakerFeeTrackerForBurnByDenom(ctx sdk.Context, denom string, increasedAmt osmomath.Int) error {
return osmoutils.IncreaseCoinByDenomFromPrefix(ctx, k.storeKey, types.KeyTakerFeeBurnProtoRevArray, denom, increasedAmt)
}

// GetTakerFeeTrackerStartHeight gets the height from which we started accounting for taker fees.
func (k Keeper) GetTakerFeeTrackerStartHeight(ctx sdk.Context) int64 {
startHeight := gogotypes.Int64Value{}
Expand Down
1 change: 1 addition & 0 deletions x/poolmanager/types/genesis.pb.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions x/poolmanager/types/keys.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,9 @@ var (
// KeyTakerFeeCommunityPoolProtoRevArray defines key to store the taker fee for community pool tracker coin array.
KeyTakerFeeCommunityPoolProtoRevArray = []byte{0x09}

// KeyTakerFeeBurnProtoRevArray defines key to store the taker fee for burn tracker coin array.
KeyTakerFeeBurnProtoRevArray = []byte{0x0B}
Comment on lines +52 to +53
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Potential key collision detected.

The new KeyTakerFeeBurnProtoRevArray is using byte value 0x0B, but this same value is already used for KeyTakerFeeShare defined on line 59. This could cause key collisions in storage, potentially leading to data corruption or unexpected behavior.

Consider using a different byte value that is not currently in use, such as 0x0D or higher.


// TakerFeeSkimAccrualPrefix defines the prefix to store taker fee skim accrual data.
TakerFeeSkimAccrualPrefix = []byte{0x0A}

Expand Down
80 changes: 80 additions & 0 deletions x/txfees/keeper/hooks.go
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,31 @@ func (k Keeper) calculateDistributeAndTrackTakerFees(ctx sdk.Context, defaultFee
takerFeeParams := poolManagerParams.TakerFeeParams
osmoTakerFeeDistribution := takerFeeParams.OsmoTakerFeeDistribution

// Burn:
if osmoTakerFeeDistribution.Burn.GT(zeroDec) && osmoFromTakerFeeModuleAccount.Amount.GT(osmomath.ZeroInt()) {
// Calculate burn amount
osmoTakerFeeToBurnDec := osmoFromTakerFeeModuleAccount.Amount.ToLegacyDec().Mul(osmoTakerFeeDistribution.Burn)
osmoTakerFeeToBurnCoin := sdk.NewCoin(defaultFeesDenom, osmoTakerFeeToBurnDec.TruncateInt())

// Send to the burn address (null address)
burnAddress, _ := sdk.AccAddressFromBech32("osmo1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqmcn030")

applyFuncIfNoErrorAndLog(ctx, func(cacheCtx sdk.Context) error {
err := k.bankKeeper.SendCoins(ctx, takerFeeModuleAccount, burnAddress, sdk.NewCoins(osmoTakerFeeToBurnCoin))
if err != nil {
return err
}
trackerErr := k.poolManager.UpdateTakerFeeTrackerForBurnByDenom(ctx, osmoTakerFeeToBurnCoin.Denom, osmoTakerFeeToBurnCoin.Amount)
if trackerErr != nil {
ctx.Logger().Error("Error updating taker fee tracker for burn by denom", "error", trackerErr)
}
return nil
}, txfeestypes.TakerFeeFailedBurnUpdateMetricName, osmoTakerFeeToBurnCoin)

// Update the remaining amount after burn
osmoFromTakerFeeModuleAccount = osmoFromTakerFeeModuleAccount.Sub(osmoTakerFeeToBurnCoin)
}

// Community Pool:
if osmoTakerFeeDistribution.CommunityPool.GT(zeroDec) && osmoFromTakerFeeModuleAccount.Amount.GT(osmomath.ZeroInt()) {
// Osmo community pool funds are a direct send to the community pool.
Expand Down Expand Up @@ -139,9 +164,41 @@ func (k Keeper) calculateDistributeAndTrackTakerFees(ctx sdk.Context, defaultFee
authorizedQuoteDenoms := poolManagerParams.AuthorizedQuoteDenoms

nonOsmoForCommunityPool := sdk.NewCoins()
nonOsmoForBurn := sdk.NewCoins()

// Loop through all remaining tokens in the taker fee module account.
for _, takerFeeCoin := range takerFeeModuleAccountCoins {
// Burn:
if nonOsmoTakerFeeDistribution.Burn.GT(zeroDec) && takerFeeCoin.Amount.GT(osmomath.ZeroInt()) {
denomIsWhitelisted := isDenomWhitelisted(takerFeeCoin.Denom, authorizedQuoteDenoms)
// If the non osmo denom is a whitelisted quote asset, we directly send to the burn address
if denomIsWhitelisted {
nonOsmoTakerFeeToBurnDec := takerFeeCoin.Amount.ToLegacyDec().Mul(nonOsmoTakerFeeDistribution.Burn)
nonOsmoTakerFeeToBurnCoin := sdk.NewCoin(takerFeeCoin.Denom, nonOsmoTakerFeeToBurnDec.TruncateInt())

// Send to the burn address (null address)
burnAddress, _ := sdk.AccAddressFromBech32("osmo1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqmcn030")

applyFuncIfNoErrorAndLog(ctx, func(cacheCtx sdk.Context) error {
err := k.bankKeeper.SendCoins(ctx, takerFeeModuleAccount, burnAddress, sdk.NewCoins(nonOsmoTakerFeeToBurnCoin))
if err == nil {
takerFeeCoin.Amount = takerFeeCoin.Amount.Sub(nonOsmoTakerFeeToBurnCoin.Amount)
}
trackerErr := k.poolManager.UpdateTakerFeeTrackerForBurnByDenom(ctx, nonOsmoTakerFeeToBurnCoin.Denom, nonOsmoTakerFeeToBurnCoin.Amount)
if trackerErr != nil {
ctx.Logger().Error("Error updating taker fee tracker for burn by denom", "error", trackerErr)
}
return err
}, txfeestypes.TakerFeeFailedBurnUpdateMetricName, nonOsmoTakerFeeToBurnCoin)
} else {
// If the non osmo denom is not a whitelisted asset, we track the assets here and later swap everything to OSMO and then burn
nonOsmoTakerFeeToBurnDec := takerFeeCoin.Amount.ToLegacyDec().Mul(nonOsmoTakerFeeDistribution.Burn)
nonOsmoTakerFeeToBurnCoin := sdk.NewCoin(takerFeeCoin.Denom, nonOsmoTakerFeeToBurnDec.TruncateInt())
nonOsmoForBurn = nonOsmoForBurn.Add(nonOsmoTakerFeeToBurnCoin)
takerFeeCoin.Amount = takerFeeCoin.Amount.Sub(nonOsmoTakerFeeToBurnCoin.Amount)
}
}

// Community Pool:
if nonOsmoTakerFeeDistribution.CommunityPool.GT(zeroDec) && takerFeeCoin.Amount.GT(osmomath.ZeroInt()) {
denomIsWhitelisted := isDenomWhitelisted(takerFeeCoin.Denom, authorizedQuoteDenoms)
Expand Down Expand Up @@ -179,6 +236,13 @@ func (k Keeper) calculateDistributeAndTrackTakerFees(ctx sdk.Context, defaultFee
return err
}, txfeestypes.TakerFeeFailedCommunityPoolUpdateMetricName, nonOsmoForCommunityPool)

// Send the non-native, non-whitelisted taker fees slated for burn to the taker fee burn module account.
// We do this in the event that the swap fails, we can still track the amount of non-native, non-whitelisted taker fees that were intended for burning.
applyFuncIfNoErrorAndLog(ctx, func(cacheCtx sdk.Context) error {
err := k.bankKeeper.SendCoinsFromAccountToModule(ctx, takerFeeModuleAccount, txfeestypes.TakerFeeBurnName, nonOsmoForBurn)
return err
}, txfeestypes.TakerFeeFailedBurnUpdateMetricName, nonOsmoForBurn)

// Swap the non-native, non-whitelisted taker fees slated for community pool into the denom specified in the pool manager params.
takerFeeCommunityPoolModuleAccount := k.accountKeeper.GetModuleAddress(txfeestypes.TakerFeeCommunityPoolName)
denomToSwapTo := poolManagerParams.TakerFeeParams.CommunityPoolDenomToSwapNonWhitelistedAssetsTo
Expand All @@ -195,6 +259,22 @@ func (k Keeper) calculateDistributeAndTrackTakerFees(ctx sdk.Context, defaultFee
}, txfeestypes.TakerFeeFailedCommunityPoolUpdateMetricName, totalCoinOut)
}

// Swap the non-native, non-whitelisted taker fees slated for burn into OSMO.
takerFeeBurnModuleAccount := k.accountKeeper.GetModuleAddress(txfeestypes.TakerFeeBurnName)
totalCoinOut = k.swapNonNativeFeeToDenom(ctx, defaultFeesDenom, takerFeeBurnModuleAccount)
// Now that the non whitelisted assets have been swapped, send them to the burn address.
if totalCoinOut.Amount.GT(osmomath.ZeroInt()) {
burnAddress, _ := sdk.AccAddressFromBech32("osmo1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqmcn030")
applyFuncIfNoErrorAndLog(ctx, func(cacheCtx sdk.Context) error {
err := k.bankKeeper.SendCoins(ctx, takerFeeBurnModuleAccount, burnAddress, sdk.NewCoins(totalCoinOut))
trackerErr := k.poolManager.UpdateTakerFeeTrackerForBurnByDenom(ctx, totalCoinOut.Denom, totalCoinOut.Amount)
if trackerErr != nil {
ctx.Logger().Error("Error updating taker fee tracker for burn by denom", "error", trackerErr)
}
return err
}, txfeestypes.TakerFeeFailedBurnUpdateMetricName, totalCoinOut)
}

// Send the non-native taker fees slated for stakers to the taker fee staking module account.
// We do this in the event that the swap fails, we can still track the amount of non-native taker fees that were intended for stakers.
var remainingTakerFeeModuleAccBal sdk.Coins
Expand Down
1 change: 1 addition & 0 deletions x/txfees/types/expected_keepers.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ type PoolManager interface {
) (price osmomath.BigDec, err error)
UpdateTakerFeeTrackerForCommunityPoolByDenom(ctx sdk.Context, denom string, increasedAmt osmomath.Int) error
UpdateTakerFeeTrackerForStakersByDenom(ctx sdk.Context, denom string, increasedAmt osmomath.Int) error
UpdateTakerFeeTrackerForBurnByDenom(ctx sdk.Context, denom string, increasedAmt osmomath.Int) error
GetAllTakerFeeShareAccumulators(ctx sdk.Context) ([]poolmanagertypes.TakerFeeSkimAccumulator, error)
GetTakerFeeShareAgreementFromDenomNoCache(ctx sdk.Context, takerFeeShareDenom string) (poolmanagertypes.TakerFeeShareAgreement, bool)
DeleteAllTakerFeeShareAccumulatorsForTakerFeeShareDenom(ctx sdk.Context, takerFeeShareDenom string)
Expand Down
5 changes: 5 additions & 0 deletions x/txfees/types/keys.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,11 @@ const (
// This is done so that, in the event of a failed swap, the funds slated for stakers are not grouped back with the rest of the taker fees in the next epoch.
TakerFeeStakersName = "non_native_fee_collector_stakers"

// TakerFeeBurnName is the name of the module account that collects non-native taker fees, swaps, and sends them to the burn address.
// Note, all taker fees initially get sent to the TakerFeeCollectorName, and then prior to the taker fees slated for burning being swapped and sent to burn address, they are sent to this account.
// This is done so that, in the event of a failed swap, the funds slated for burning are not grouped back with the rest of the taker fees in the next epoch.
TakerFeeBurnName = "non_native_fee_collector_burn"

// TakerFeeCollectorName is the module account name for the taker fee collector account address. It collects both native and non-native taker fees.
TakerFeeCollectorName = "taker_fee_collector"

Expand Down
7 changes: 7 additions & 0 deletions x/txfees/types/telemetry.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,13 @@ var (
// * match_denom - the match denom to swap to.
// * err - the error occurred
TakerFeeNoSkipRouteMetricName = formatTxFeesMetricName("takerfee_no_skip_route")
// txfees_takerfee_failed_burn_update
//
// counter that is increased if taker fee burn distribution fails
// Has the following labels:
// * coins - the coins that fail to be sent.
// * err - the error occurred
TakerFeeFailedBurnUpdateMetricName = formatTxFeesMetricName("takerfee_failed_burn_update")
)

// formatTxFeesMetricName formats the tx fees module metric name.
Expand Down