-
Notifications
You must be signed in to change notification settings - Fork 0
/
taker_fee.go
193 lines (169 loc) · 9.05 KB
/
taker_fee.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
package poolmanager
import (
"fmt"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/osmosis-labs/osmosis/osmomath"
appparams "github.com/fury-labs/furya/v20/app/params"
"github.com/osmosis-labs/osmosis/osmoutils"
"github.com/fury-labs/furya/v20/x/poolmanager/types"
txfeestypes "github.com/fury-labs/furya/v20/x/txfees/types"
)
// SetDenomPairTakerFee sets the taker fee for the given trading pair.
// If the taker fee for this denom pair matches the default taker fee, then
// it is deleted from state.
func (k Keeper) SetDenomPairTakerFee(ctx sdk.Context, denom0, denom1 string, takerFee osmomath.Dec) {
store := ctx.KVStore(k.storeKey)
// if given taker fee is equal to the default taker fee,
// delete whatever we have in current state to use default taker fee.
if takerFee.Equal(k.GetParams(ctx).TakerFeeParams.DefaultTakerFee) {
store.Delete(types.FormatDenomTradePairKey(denom0, denom1))
return
} else {
osmoutils.MustSetDec(store, types.FormatDenomTradePairKey(denom0, denom1), takerFee)
}
}
// SenderValidationSetDenomPairTakerFee sets the taker fee for the given trading pair iff the sender's address
// also exists in the pool manager taker fee admin address list.
func (k Keeper) SenderValidationSetDenomPairTakerFee(ctx sdk.Context, sender, denom0, denom1 string, takerFee osmomath.Dec) error {
adminAddresses := k.GetParams(ctx).TakerFeeParams.AdminAddresses
isAdmin := false
for _, admin := range adminAddresses {
if admin == sender {
isAdmin = true
break
}
}
if !isAdmin {
return fmt.Errorf("%s is not in the pool manager taker fee admin address list", sender)
}
k.SetDenomPairTakerFee(ctx, denom0, denom1, takerFee)
ctx.EventManager().EmitEvents(sdk.Events{
sdk.NewEvent(
types.TypeMsgSetDenomPairTakerFee,
sdk.NewAttribute(sdk.AttributeKeyModule, types.AttributeValueCategory),
sdk.NewAttribute(sdk.AttributeKeySender, sender),
sdk.NewAttribute(types.AttributeKeyDenom0, denom0),
sdk.NewAttribute(types.AttributeKeyDenom1, denom1),
sdk.NewAttribute(types.AttributeKeyTakerFee, takerFee.String()),
),
})
return nil
}
// GetTradingPairTakerFee returns the taker fee for the given trading pair.
// If the trading pair does not exist, it returns the default taker fee.
func (k Keeper) GetTradingPairTakerFee(ctx sdk.Context, denom0, denom1 string) (osmomath.Dec, error) {
store := ctx.KVStore(k.storeKey)
key := types.FormatDenomTradePairKey(denom0, denom1)
takerFee := &sdk.DecProto{}
found, err := osmoutils.Get(store, key, takerFee)
if err != nil {
return osmomath.Dec{}, err
}
if !found {
return k.GetParams(ctx).TakerFeeParams.DefaultTakerFee, nil
}
return takerFee.Dec, nil
}
// chargeTakerFee extracts the taker fee from the given tokenIn and sends it to the appropriate
// module account. It returns the tokenIn after the taker fee has been extracted.
// If the sender is in the taker fee reduced whitelisted, it returns the tokenIn without extracting the taker fee.
// In the future, we might charge a lower taker fee as opposed to no fee at all.
func (k Keeper) chargeTakerFee(ctx sdk.Context, tokenIn sdk.Coin, tokenOutDenom string, sender sdk.AccAddress, exactIn bool) (sdk.Coin, error) {
feeCollectorForStakingRewardsName := txfeestypes.FeeCollectorForStakingRewardsName
feeCollectorForCommunityPoolName := txfeestypes.FeeCollectorForCommunityPoolName
defaultTakerFeeDenom := appparams.BaseCoinUnit
poolManagerParams := k.GetParams(ctx)
// Determine if eligible to bypass taker fee.
if osmoutils.Contains(poolManagerParams.TakerFeeParams.ReducedFeeWhitelist, sender.String()) {
return tokenIn, nil
}
takerFee, err := k.GetTradingPairTakerFee(ctx, tokenIn.Denom, tokenOutDenom)
if err != nil {
return sdk.Coin{}, err
}
var tokenInAfterTakerFee sdk.Coin
var takerFeeCoin sdk.Coin
if exactIn {
tokenInAfterTakerFee, takerFeeCoin = k.calcTakerFeeExactIn(tokenIn, takerFee)
} else {
tokenInAfterTakerFee, takerFeeCoin = k.calcTakerFeeExactOut(tokenIn, takerFee)
}
// N.B. We truncate from the community pool calculation, then remove that from the total, and use the remaining for staking rewards.
// If we truncate both, these can leave tokens in the users wallet when swapping and exact amount in, which is bad UX.
// We determine the distributution of the taker fee based on its denom
// If the denom is the base denom:
takerFeeAmtRemaining := takerFeeCoin.Amount
if takerFeeCoin.Denom == defaultTakerFeeDenom {
// Community Pool:
if poolManagerParams.TakerFeeParams.FuryTakerFeeDistribution.CommunityPool.GT(osmomath.ZeroDec()) {
// Fury community pool funds is a direct send
osmoTakerFeeToCommunityPoolDec := takerFeeAmtRemaining.ToLegacyDec().Mul(poolManagerParams.TakerFeeParams.FuryTakerFeeDistribution.CommunityPool)
osmoTakerFeeToCommunityPoolCoins := sdk.NewCoins(sdk.NewCoin(defaultTakerFeeDenom, osmoTakerFeeToCommunityPoolDec.TruncateInt()))
err := k.communityPoolKeeper.FundCommunityPool(ctx, osmoTakerFeeToCommunityPoolCoins, sender)
if err != nil {
return sdk.Coin{}, err
}
takerFeeAmtRemaining = takerFeeAmtRemaining.Sub(osmoTakerFeeToCommunityPoolCoins.AmountOf(defaultTakerFeeDenom))
}
// Staking Rewards:
if poolManagerParams.TakerFeeParams.FuryTakerFeeDistribution.StakingRewards.GT(osmomath.ZeroDec()) {
// Fury staking rewards funds are sent to the non native fee pool module account (even though its native, we want to distribute at the same time as the non native fee tokens)
// We could stream these rewards via the fee collector account, but this is decision to be made by governance.
osmoTakerFeeToStakingRewardsCoins := sdk.NewCoins(sdk.NewCoin(defaultTakerFeeDenom, takerFeeAmtRemaining))
err := k.bankKeeper.SendCoinsFromAccountToModule(ctx, sender, feeCollectorForStakingRewardsName, osmoTakerFeeToStakingRewardsCoins)
if err != nil {
return sdk.Coin{}, err
}
}
// If the denom is not the base denom:
} else {
// Community Pool:
if poolManagerParams.TakerFeeParams.NonFuryTakerFeeDistribution.CommunityPool.GT(osmomath.ZeroDec()) {
denomIsWhitelisted := isDenomWhitelisted(takerFeeCoin.Denom, poolManagerParams.AuthorizedQuoteDenoms)
// If the non fury denom is a whitelisted quote asset, we send to the community pool
if denomIsWhitelisted {
nonFuryTakerFeeToCommunityPoolDec := takerFeeAmtRemaining.ToLegacyDec().Mul(poolManagerParams.TakerFeeParams.NonFuryTakerFeeDistribution.CommunityPool)
nonFuryTakerFeeToCommunityPoolCoins := sdk.NewCoins(sdk.NewCoin(tokenIn.Denom, nonFuryTakerFeeToCommunityPoolDec.TruncateInt()))
err := k.communityPoolKeeper.FundCommunityPool(ctx, nonFuryTakerFeeToCommunityPoolCoins, sender)
if err != nil {
return sdk.Coin{}, err
}
takerFeeAmtRemaining = takerFeeAmtRemaining.Sub(nonFuryTakerFeeToCommunityPoolCoins.AmountOf(tokenIn.Denom))
} else {
// If the non fury denom is not a whitelisted asset, we send to the non native fee pool for community pool module account.
// At epoch, this account swaps the non native, non whitelisted assets for XXX and sends to the community pool.
nonFuryTakerFeeToCommunityPoolDec := takerFeeAmtRemaining.ToLegacyDec().Mul(poolManagerParams.TakerFeeParams.NonFuryTakerFeeDistribution.CommunityPool)
nonFuryTakerFeeToCommunityPoolCoins := sdk.NewCoins(sdk.NewCoin(tokenIn.Denom, nonFuryTakerFeeToCommunityPoolDec.TruncateInt()))
err := k.bankKeeper.SendCoinsFromAccountToModule(ctx, sender, feeCollectorForCommunityPoolName, nonFuryTakerFeeToCommunityPoolCoins)
if err != nil {
return sdk.Coin{}, err
}
takerFeeAmtRemaining = takerFeeAmtRemaining.Sub(nonFuryTakerFeeToCommunityPoolCoins.AmountOf(tokenIn.Denom))
}
}
// Staking Rewards:
if poolManagerParams.TakerFeeParams.NonFuryTakerFeeDistribution.StakingRewards.GT(osmomath.ZeroDec()) {
// Non Fury staking rewards are sent to the non native fee pool module account
nonFuryTakerFeeToStakingRewardsCoins := sdk.NewCoins(sdk.NewCoin(takerFeeCoin.Denom, takerFeeAmtRemaining))
err := k.bankKeeper.SendCoinsFromAccountToModule(ctx, sender, feeCollectorForStakingRewardsName, nonFuryTakerFeeToStakingRewardsCoins)
if err != nil {
return sdk.Coin{}, err
}
}
}
return tokenInAfterTakerFee, nil
}
// Returns remaining amount in to swap, and takerFeeCoins.
// returns (1 - takerFee) * tokenIn, takerFee * tokenIn
func (k Keeper) calcTakerFeeExactIn(tokenIn sdk.Coin, takerFee osmomath.Dec) (sdk.Coin, sdk.Coin) {
amountInAfterSubTakerFee := tokenIn.Amount.ToLegacyDec().MulTruncate(osmomath.OneDec().Sub(takerFee))
tokenInAfterSubTakerFee := sdk.NewCoin(tokenIn.Denom, amountInAfterSubTakerFee.TruncateInt())
takerFeeCoin := sdk.NewCoin(tokenIn.Denom, tokenIn.Amount.Sub(tokenInAfterSubTakerFee.Amount))
return tokenInAfterSubTakerFee, takerFeeCoin
}
func (k Keeper) calcTakerFeeExactOut(tokenIn sdk.Coin, takerFee osmomath.Dec) (sdk.Coin, sdk.Coin) {
amountInAfterAddTakerFee := tokenIn.Amount.ToLegacyDec().Quo(osmomath.OneDec().Sub(takerFee))
tokenInAfterAddTakerFee := sdk.NewCoin(tokenIn.Denom, amountInAfterAddTakerFee.Ceil().TruncateInt())
takerFeeCoin := sdk.NewCoin(tokenIn.Denom, tokenInAfterAddTakerFee.Amount.Sub(tokenIn.Amount))
return tokenInAfterAddTakerFee, takerFeeCoin
}