diff --git a/protocol/testing/e2e/trading_rewards/trading_rewards_test.go b/protocol/testing/e2e/trading_rewards/trading_rewards_test.go index 9541d84054..dff299c7ab 100644 --- a/protocol/testing/e2e/trading_rewards/trading_rewards_test.go +++ b/protocol/testing/e2e/trading_rewards/trading_rewards_test.go @@ -1,13 +1,14 @@ package trading_rewards_test import ( - sdkmath "cosmossdk.io/math" - "github.com/cosmos/gogoproto/proto" - "github.com/dydxprotocol/v4-chain/protocol/app/flags" "math/big" "testing" "time" + sdkmath "cosmossdk.io/math" + "github.com/cosmos/gogoproto/proto" + "github.com/dydxprotocol/v4-chain/protocol/app/flags" + "github.com/cometbft/cometbft/types" sdk "github.com/cosmos/cosmos-sdk/types" authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" @@ -229,20 +230,20 @@ func TestTradingRewards(t *testing.T) { }, { AccAddress: RewardsTreasuryAccAddress, - // Total of ~5.06 full coins have vested, which is less than calculated - // rewards (~5.5 full coins). So all reward tokens were distributed. + // Total of ~5.06 full coins have vested, calculated rewards are + // ~1.99 full coins. So remaining rewards are ~3.07 full coins. Balance: big_testutil.MustFirst(new(big.Int).SetString( - "0", + "3077645653924902967", 10, )), }, { AccAddress: constants.AliceAccAddress, - // starting balance + ~5.06 full coins rewards + // starting balance + ~1.99 full coins rewards Balance: new(big.Int).Add( TestAccountStartingTokenBalance, big_testutil.MustFirst(new(big.Int).SetString( - "5068012730847979890", + "1990367076923076923", 10, )), ), @@ -258,7 +259,7 @@ func TestTradingRewards(t *testing.T) { TradingRewards: []*indexerevents.AddressTradingReward{ { Owner: constants.AliceAccAddress.String(), - DenomAmount: dtypes.NewIntFromUint64(5068012730847979890), + DenomAmount: dtypes.NewIntFromUint64(1990367076923076923), }, }, }, @@ -277,10 +278,10 @@ func TestTradingRewards(t *testing.T) { }, { AccAddress: RewardsTreasuryAccAddress, - // ~25.34 full coins. Note this is exactly 10x the amount vested per block, + // balance + ~25.34 full coins. Note this is exactly 10x the amount vested per block, // since 10 blocks has passed since the last check. Balance: big_testutil.MustFirst(new(big.Int).SetString( - "25340063654239899450", + "28417709308164802417", 10, )), }, @@ -300,23 +301,23 @@ func TestTradingRewards(t *testing.T) { { AccAddress: constants.BobAccAddress, // Starting balance: 500000000000000000000000 - // Total rewards = (TakerFee - TakerVolume * MaxMakerRebate) * 0.99 - // = ($28003 * 0.05% - $28003 * 0.011%) * 0.99 - // = ($14.0015 - $3.08033) 0.99 = $10.8119583 - // Reward tokens = $10.8119583 / $1.95 = 5.544594 full coins + // Total rewards = (TakerFee - TakerVolume * MaxMakerRebate - (takerFee * MaxPossibleTakerFeeRevShare)) * 0.99 + // = ($28003 * 0.05% - $28003 * 0.011% - $28003 * 0.05% * 0.5) * 0.99 + // = ($14.0015 - $3.08033 - $7.00075) 0.99 = $3.8812158 + // Reward tokens = $3.8812158 / $1.95 = 1.9903670769 full coins Balance: new(big.Int).Add( TestAccountStartingTokenBalance, big_testutil.MustFirst(new(big.Int).SetString( - "5544594000000000000", + "1990367076923076923", 10, )), ), }, { AccAddress: RewardsTreasuryAccAddress, - // 25.34 + 2.534 - 5.544594 ~= 22.329 full coins + // balance + 25.34 + 2.534 - 1.9903670769 ~= 22.329 full coins Balance: big_testutil.MustFirst(new(big.Int).SetString( - "22329476019663889395", + "28961348596665715439", 10, )), }, @@ -326,7 +327,7 @@ func TestTradingRewards(t *testing.T) { TradingRewards: []*indexerevents.AddressTradingReward{ { Owner: constants.BobAccAddress.String(), - DenomAmount: dtypes.NewIntFromUint64(5544594000000000000), + DenomAmount: dtypes.NewIntFromUint64(1990367076923076923), }, }, }, @@ -482,8 +483,8 @@ func TestTradingRewards(t *testing.T) { // - Carl and Dave: $12.519 // Total rewards tokens distributed: ~25.34 (less than the value of net fees) // Entitled reward tokens: - // - Alice and Bob: 8.0539 - // - Carl and Dave: 4.616 + // - Alice and Bob: 3.98073 + // - Carl and Dave: 2.28156 ExpectedBalances: []expectedBalance{ { AccAddress: RewardsVesterAccAddress, @@ -495,9 +496,9 @@ func TestTradingRewards(t *testing.T) { }, { AccAddress: RewardsTreasuryAccAddress, - // All vested rewards were distributed, only rounding dusts left. + // 12.52458 full coins distributed, ~12.815 full coins remaining Balance: big_testutil.MustFirst(new(big.Int).SetString( - "2", + "12815456885009130222", 10, )), }, @@ -506,7 +507,7 @@ func TestTradingRewards(t *testing.T) { Balance: new(big.Int).Add( TestAccountStartingTokenBalance, big_testutil.MustFirst(new(big.Int).SetString( - "8053910091363583686", + "3980734153846153845", 10, )), ), @@ -516,7 +517,7 @@ func TestTradingRewards(t *testing.T) { Balance: new(big.Int).Add( TestAccountStartingTokenBalance, big_testutil.MustFirst(new(big.Int).SetString( - "8053910091363583686", + "3980734153846153845", 10, )), ), @@ -526,7 +527,7 @@ func TestTradingRewards(t *testing.T) { Balance: new(big.Int).Add( TestAccountStartingTokenBalance, big_testutil.MustFirst(new(big.Int).SetString( - "4616121735756366038", + "2281569230769230769", 10, )), ), @@ -536,7 +537,7 @@ func TestTradingRewards(t *testing.T) { Balance: new(big.Int).Add( TestAccountStartingTokenBalance, big_testutil.MustFirst(new(big.Int).SetString( - "4616121735756366038", + "2281569230769230769", 10, )), ), @@ -547,19 +548,19 @@ func TestTradingRewards(t *testing.T) { TradingRewards: []*indexerevents.AddressTradingReward{ { Owner: constants.BobAccAddress.String(), - DenomAmount: dtypes.NewIntFromUint64(8053910091363583686), + DenomAmount: dtypes.NewIntFromUint64(3980734153846153845), }, { Owner: constants.AliceAccAddress.String(), - DenomAmount: dtypes.NewIntFromUint64(8053910091363583686), + DenomAmount: dtypes.NewIntFromUint64(3980734153846153845), }, { Owner: constants.CarlAccAddress.String(), - DenomAmount: dtypes.NewIntFromUint64(4616121735756366038), + DenomAmount: dtypes.NewIntFromUint64(2281569230769230769), }, { Owner: constants.DaveAccAddress.String(), - DenomAmount: dtypes.NewIntFromUint64(4616121735756366038), + DenomAmount: dtypes.NewIntFromUint64(2281569230769230769), }, }, }, diff --git a/protocol/x/revshare/keeper/revshare.go b/protocol/x/revshare/keeper/revshare.go index 5a50f4efcd..91d67df6c9 100644 --- a/protocol/x/revshare/keeper/revshare.go +++ b/protocol/x/revshare/keeper/revshare.go @@ -227,7 +227,7 @@ func (k Keeper) getAffiliateRevShares( ) ([]types.RevShare, error) { takerAddr := fill.TakerAddr takerFee := fill.TakerFeeQuoteQuantums - if fill.MonthlyRollingTakerVolumeQuantums >= types.Max30dRefereeVolumeQuantums { + if fill.MonthlyRollingTakerVolumeQuantums >= types.MaxReferee30dVolumeForAffiliateShareQuantums { return nil, nil } diff --git a/protocol/x/revshare/keeper/revshare_test.go b/protocol/x/revshare/keeper/revshare_test.go index 6182ead2a0..7ecc596393 100644 --- a/protocol/x/revshare/keeper/revshare_test.go +++ b/protocol/x/revshare/keeper/revshare_test.go @@ -504,7 +504,7 @@ func TestKeeper_GetAllRevShares_Valid(t *testing.T) { MakerFeeQuoteQuantums: big.NewInt(2_000_000), FillQuoteQuantums: big.NewInt(100_000_000_000), ProductId: perpetualId, - MonthlyRollingTakerVolumeQuantums: types.Max30dRefereeVolumeQuantums + 1, + MonthlyRollingTakerVolumeQuantums: types.MaxReferee30dVolumeForAffiliateShareQuantums + 1, MarketId: marketId, }, expectedRevSharesForFill: types.RevSharesForFill{ diff --git a/protocol/x/revshare/types/constants.go b/protocol/x/revshare/types/constants.go index 4c13aa349e..d88c519943 100644 --- a/protocol/x/revshare/types/constants.go +++ b/protocol/x/revshare/types/constants.go @@ -1,6 +1,6 @@ package types const ( - // 25 million USDC - Max30dRefereeVolumeQuantums = uint64(25_000_000_000_000) + // 50 million USDC + MaxReferee30dVolumeForAffiliateShareQuantums = uint64(50_000_000_000_000) ) diff --git a/protocol/x/rewards/keeper/keeper.go b/protocol/x/rewards/keeper/keeper.go index 14f14d1aa7..f9daa60fac 100644 --- a/protocol/x/rewards/keeper/keeper.go +++ b/protocol/x/rewards/keeper/keeper.go @@ -19,6 +19,7 @@ import ( "github.com/dydxprotocol/v4-chain/protocol/lib" "github.com/dydxprotocol/v4-chain/protocol/lib/log" "github.com/dydxprotocol/v4-chain/protocol/lib/metrics" + affiliatetypes "github.com/dydxprotocol/v4-chain/protocol/x/affiliates/types" clobtypes "github.com/dydxprotocol/v4-chain/protocol/x/clob/types" revsharetypes "github.com/dydxprotocol/v4-chain/protocol/x/revshare/types" "github.com/dydxprotocol/v4-chain/protocol/x/rewards/types" @@ -120,15 +121,20 @@ func (k Keeper) GetRewardShare( // // Within each block, total reward share score for an address is defined as: // -// reward_share_score = total_taker_fees_paid - total_rev_shared_taker_fee +// reward_share_score = total_taker_fees_paid - max_possible_taker_fee_rev_share // - max_possible_maker_rebate * taker_volume + total_positive_maker_fees - total_rev_shared_maker_fee // // Hence, for each fill, increment reward share score as follow: // - Let F = sum(percentages of general rev-share) (excluding taker only rev share i.e. affiliate) // - For maker address, positive_maker_fees * (1 - F) are added to reward share score. // - For taker address, (positive_taker_fees - max_possible_maker_rebate -// * fill_quote_quantum - taker_fee_rev_share) * (1 - F) +// * fill_quote_quantum - max_possible_taker_fee_rev_share) * (1 - F) // are added to reward share score. +// max_possible_taker_fee_rev_share is 0 when taker trailing volume is > MaxReferee30dVolumeForAffiliateShareQuantums, +// since taker_fee_share is only affiliate at the moment, and they don’t generate affiliate rev share. +// When taker volume ≤ MaxReferee30dVolumeForAffiliateShareQuantums, +// max_possible_taker_fee_rev_share = max_vip_affiliate_share * taker_fee +// regardless of if the taker has an affiliate or not. func (k Keeper) AddRewardSharesForFill( ctx sdk.Context, @@ -143,9 +149,15 @@ func (k Keeper) AddRewardSharesForFill( if value, ok := revSharesForFill.FeeSourceToRevSharePpm[revsharetypes.REV_SHARE_FEE_SOURCE_NET_FEE]; ok { totalNetFeeRevSharePpm = value } - totalTakerFeeRevShareQuantums := big.NewInt(0) - if value, ok := revSharesForFill.FeeSourceToQuoteQuantums[revsharetypes.REV_SHARE_FEE_SOURCE_TAKER_FEE]; ok { - totalTakerFeeRevShareQuantums = value + maxPossibleTakerFeeRevShare := big.NewInt(0) + + // taker revshare is always 0 if taker rolling volume is greater than or equal + // to Max30dTakerVolumeQuantums, so no need to reduce score by `max_possible_taker_fee_rev_share` + if fill.MonthlyRollingTakerVolumeQuantums < revsharetypes.MaxReferee30dVolumeForAffiliateShareQuantums { + maxPossibleTakerFeeRevShare = lib.BigMulPpm(fill.TakerFeeQuoteQuantums, + lib.BigU(affiliatetypes.AffiliatesRevSharePpmCap), + false, + ) } totalFeeSubNetRevSharePpm := lib.OneMillion - totalNetFeeRevSharePpm @@ -159,7 +171,7 @@ func (k Keeper) AddRewardSharesForFill( ) netTakerFee = netTakerFee.Sub( netTakerFee, - totalTakerFeeRevShareQuantums, + maxPossibleTakerFeeRevShare, ) takerWeight := lib.BigMulPpm( netTakerFee, diff --git a/protocol/x/rewards/keeper/keeper_test.go b/protocol/x/rewards/keeper/keeper_test.go index d41f89b738..5a018227f1 100644 --- a/protocol/x/rewards/keeper/keeper_test.go +++ b/protocol/x/rewards/keeper/keeper_test.go @@ -184,7 +184,7 @@ func TestAddRewardSharesForFill(t *testing.T) { }, expectedTakerShare: types.RewardShare{ Address: takerAddress, - Weight: dtypes.NewInt(1_200_000), // 2 - 0.1% * 800 + Weight: dtypes.NewInt(200_000), // 2 - 0.1% * 800 -(2 * 0.5) }, expectedMakerShare: types.RewardShare{ Address: makerAddress, @@ -218,7 +218,7 @@ func TestAddRewardSharesForFill(t *testing.T) { }, expectedTakerShare: types.RewardShare{ Address: takerAddress, - Weight: dtypes.NewInt(1_250_000), // 2 - 0.1% * 750 + Weight: dtypes.NewInt(250_000), // 2 - 0.1% * 750 - (2 * 0.5) }, expectedMakerShare: types.RewardShare{ Address: makerAddress, @@ -252,7 +252,7 @@ func TestAddRewardSharesForFill(t *testing.T) { }, expectedTakerShare: types.RewardShare{ Address: takerAddress, - Weight: dtypes.NewInt(1_625_000), // 2 - 0.05% * 750 + Weight: dtypes.NewInt(625_000), // 2 - 0.05% * 750 - (2 * 0.5) }, expectedMakerShare: types.RewardShare{ Address: makerAddress, @@ -319,7 +319,7 @@ func TestAddRewardSharesForFill(t *testing.T) { }, expectedTakerShare: types.RewardShare{ Address: takerAddress, - Weight: dtypes.NewInt(700_000), + Weight: dtypes.NewInt(350_000), // 0.7 - (0.7 * 0.5) }, expectedMakerShare: types.RewardShare{ Address: makerAddress, @@ -367,7 +367,7 @@ func TestAddRewardSharesForFill(t *testing.T) { }, expectedTakerShare: types.RewardShare{ Address: takerAddress, - Weight: dtypes.NewInt(1_080_000), // (2 - 0.1% * 800 - 0) * (1 - 0.1) + Weight: dtypes.NewInt(180_000), // (2 - 0.1% * 800 - 0.5*2) * (1 - 0.1) }, expectedMakerShare: types.RewardShare{ Address: makerAddress, @@ -422,7 +422,7 @@ func TestAddRewardSharesForFill(t *testing.T) { }, expectedTakerShare: types.RewardShare{ Address: takerAddress, - Weight: dtypes.NewInt(960_000), // (2 - 0.1% * 800 - 0) * (1 - 0.2) + Weight: dtypes.NewInt(160_000), // (2 - 0.1% * 800 - 0.5*2) * (1 - 0.2) }, expectedMakerShare: types.RewardShare{ Address: makerAddress, @@ -483,7 +483,68 @@ func TestAddRewardSharesForFill(t *testing.T) { }, expectedTakerShare: types.RewardShare{ Address: takerAddress, - Weight: dtypes.NewInt(900_000), // (2 - 0.1% * 800 - 0.2) * (1 - 0.1) + Weight: dtypes.NewInt(180_000), // (2 - 0.1% * 800 - 1) * (1 - 0.1) + }, + expectedMakerShare: types.RewardShare{ + Address: makerAddress, + Weight: dtypes.NewInt(900_000), // 1 * (1 - 0.1) + }, + }, + "positive maker + taker fees reduced by maker rebate, taker + net fee revshare,rolling taker volume > 50 mil": { + prevTakerRewardShare: nil, + prevMakerRewardShare: nil, + fill: clobtypes.FillForProcess{ + TakerAddr: takerAddress, + TakerFeeQuoteQuantums: big.NewInt(2_000_000), + MakerAddr: makerAddress, + MakerFeeQuoteQuantums: big.NewInt(1_000_000), + FillQuoteQuantums: big.NewInt(800_000_000), + ProductId: uint32(1), + MarketId: uint32(1), + MonthlyRollingTakerVolumeQuantums: 60_000_000_000_000, + }, + revSharesForFill: revsharetypes.RevSharesForFill{ + AllRevShares: []revsharetypes.RevShare{ + { + Recipient: constants.AliceAccAddress.String(), + RevShareFeeSource: revsharetypes.REV_SHARE_FEE_SOURCE_NET_FEE, + RevShareType: revsharetypes.REV_SHARE_TYPE_UNCONDITIONAL, + QuoteQuantums: big.NewInt(200_000), + RevSharePpm: 100_000, // 10% + }, + { + Recipient: takerAddress, + RevShareFeeSource: revsharetypes.REV_SHARE_FEE_SOURCE_TAKER_FEE, + RevShareType: revsharetypes.REV_SHARE_TYPE_AFFILIATE, + QuoteQuantums: big.NewInt(200_000), + RevSharePpm: 100_000, // 10% + }, + }, + FeeSourceToQuoteQuantums: map[revsharetypes.RevShareFeeSource]*big.Int{ + revsharetypes.REV_SHARE_FEE_SOURCE_NET_FEE: big.NewInt(200_000), + revsharetypes.REV_SHARE_FEE_SOURCE_TAKER_FEE: big.NewInt(200_000), + }, + FeeSourceToRevSharePpm: map[revsharetypes.RevShareFeeSource]uint32{ + revsharetypes.REV_SHARE_FEE_SOURCE_NET_FEE: 100_000, // 10% + revsharetypes.REV_SHARE_FEE_SOURCE_TAKER_FEE: 100_000, // 10% + }, + AffiliateRevShare: &revsharetypes.RevShare{ + Recipient: takerAddress, + RevShareFeeSource: revsharetypes.REV_SHARE_FEE_SOURCE_TAKER_FEE, + RevShareType: revsharetypes.REV_SHARE_TYPE_AFFILIATE, + QuoteQuantums: big.NewInt(200_000), + RevSharePpm: 100_000, // 10% + }, + }, + feeTiers: []*feetierstypes.PerpetualFeeTier{ + { + MakerFeePpm: -1_000, // -0.1% + TakerFeePpm: 2_000, // 0.2% + }, + }, + expectedTakerShare: types.RewardShare{ + Address: takerAddress, + Weight: dtypes.NewInt(1_080_000), // (2 - 0.1% * 800 - 0) * (1 - 0.1) }, expectedMakerShare: types.RewardShare{ Address: makerAddress,