From 9a792b9bb8543ec19a285d68f457a420490220ac Mon Sep 17 00:00:00 2001 From: ze97286 Date: Wed, 14 Aug 2024 13:24:13 +0100 Subject: [PATCH] fix: Replace additional rebate validation with a cap --- CHANGELOG.md | 1 + ...ngine_update_volume_rebate_program_test.go | 48 ------------------- core/governance/volume_rebate_program.go | 10 ---- core/integration/setup_test.go | 8 ++++ core/protocol/all_services.go | 8 ++++ core/volumerebate/engine.go | 28 +++++++++-- core/volumerebate/engine_test.go | 24 +++++++--- 7 files changed, 59 insertions(+), 68 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6cff2a65a5..6cffee4e94 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -24,6 +24,7 @@ - [11546](https://github.com/vegaprotocol/vega/issues/11546) - Add validation to market proposals metadata. - [11562](https://github.com/vegaprotocol/vega/issues/11562) - Update average notional metric with mark price at the end of the epoch and when calculating live score. - [11570](https://github.com/vegaprotocol/vega/issues/11570) - Include the required set of parties for evaluation for eligible entities reward. +- [11576](https://github.com/vegaprotocol/vega/issues/11576) - Replace additional rebate validation with a cap. ### 🐛 Fixes diff --git a/core/governance/engine_update_volume_rebate_program_test.go b/core/governance/engine_update_volume_rebate_program_test.go index bedf365229..9e0ee1f0dc 100644 --- a/core/governance/engine_update_volume_rebate_program_test.go +++ b/core/governance/engine_update_volume_rebate_program_test.go @@ -33,7 +33,6 @@ import ( func TestProposalForUpdateVolumeRebateProgram(t *testing.T) { t.Run("Submitting a proposal for referral program update succeeds", testSubmittingProposalForVolumeRebateProgramUpdateSucceeds) t.Run("Submitting a proposal for referral program update with too many tiers fails", testSubmittingProposalForVolumeRebateProgramUpdateWithTooManyTiersFails) - t.Run("Submitting a proposal for referral program update with too high rebate factor fails", testSubmittingProposalForVolumeRebateProgramUpdateWithTooHighRebateFactorFails) } func testSubmittingProposalForVolumeRebateProgramUpdateSucceeds(t *testing.T) { @@ -123,50 +122,3 @@ func testSubmittingProposalForVolumeRebateProgramUpdateWithTooManyTiersFails(t * require.Error(t, err) require.Nil(t, toSubmit) } - -func testSubmittingProposalForVolumeRebateProgramUpdateWithTooHighRebateFactorFails(t *testing.T) { - now := time.Now() - ctx := vgtest.VegaContext(vgrand.RandomStr(5), vgtest.RandomPositiveI64()) - eng := getTestEngine(t, now) - - // setup - eng.broker.EXPECT().Send(gomock.Any()).Times(3) - eng.netp.Update(ctx, netparams.GovernanceProposalVolumeRebateProgramMinClose, "48h") - eng.netp.Update(ctx, netparams.GovernanceProposalVolumeRebateProgramMinEnact, "48h") - eng.netp.Update(ctx, netparams.GovernanceProposalVolumeRebateProgramMinProposerBalance, "1000") - - eng.broker.EXPECT().Send(events.NewNetworkParameterEvent(ctx, netparams.VolumeRebateProgramMaxBenefitTiers, "2")).Times(1) - require.NoError(t, eng.netp.Update(ctx, netparams.VolumeRebateProgramMaxBenefitTiers, "2")) - - // given - proposer := vgrand.RandomStr(5) - proposal := eng.newProposalForVolumeRebateProgramUpdate(proposer, now, &types.VolumeRebateProgramChanges{ - EndOfProgramTimestamp: now.Add(4 * 48 * time.Hour), - WindowLength: 15, - VolumeRebateBenefitTiers: []*types.VolumeRebateBenefitTier{ - { - MinimumPartyMakerVolumeFraction: num.DecimalFromFloat(0.1), - AdditionalMakerRebate: num.DecimalFromFloat(0.01), - }, { - MinimumPartyMakerVolumeFraction: num.DecimalFromFloat(0.2), - AdditionalMakerRebate: num.DecimalFromFloat(0.02), - }, - }, - }) - - // setup - eng.ensureTokenBalanceForParty(t, proposer, 1000) - - // expect - eng.expectRejectedProposalEvent(t, proposer, proposal.ID, types.ProposalErrorInvalidVolumeRebateProgram) - - // when - toSubmit, err := eng.submitProposal(t, proposal) - - // then - require.EqualError(t, - err, - "tier 1 defines an additional rebate factor higher than the maximum allowed by the network parameters: maximum is (0.0001+0.0001), but got 0.01", - ) - require.Nil(t, toSubmit) -} diff --git a/core/governance/volume_rebate_program.go b/core/governance/volume_rebate_program.go index 7760f45e09..677cca3e2d 100644 --- a/core/governance/volume_rebate_program.go +++ b/core/governance/volume_rebate_program.go @@ -27,16 +27,6 @@ func validateUpdateVolumeRebateProgram(netp NetParams, p *types.UpdateVolumeReba if len(p.Changes.VolumeRebateBenefitTiers) > int(maxTiers.Uint64()) { return types.ProposalErrorInvalidVolumeRebateProgram, fmt.Errorf("the number of tiers in the proposal is higher than the maximum allowed by the network parameter %q: maximum is %s, but got %d", netparams.VolumeRebateProgramMaxBenefitTiers, maxTiers.String(), len(p.Changes.VolumeRebateBenefitTiers)) } - - treasuryFee, _ := netp.GetDecimal(netparams.MarketFeeFactorsTreasuryFee) - buybackFee, _ := netp.GetDecimal(netparams.MarketFeeFactorsBuyBackFee) - maxRebate := treasuryFee.Add(buybackFee) - - for i, tier := range p.Changes.VolumeRebateBenefitTiers { - if tier.AdditionalMakerRebate.GreaterThan(maxRebate) { - return types.ProposalErrorInvalidVolumeRebateProgram, fmt.Errorf("tier %d defines an additional rebate factor higher than the maximum allowed by the network parameters: maximum is (%s+%s), but got %s", i+1, buybackFee.String(), treasuryFee.String(), tier.AdditionalMakerRebate.String()) - } - } return 0, nil } diff --git a/core/integration/setup_test.go b/core/integration/setup_test.go index 4b2e01164c..d70b794414 100644 --- a/core/integration/setup_test.go +++ b/core/integration/setup_test.go @@ -309,6 +309,14 @@ func (e *executionTestSetup) registerTimeServiceCallbacks() { func (e *executionTestSetup) registerNetParamsCallbacks() error { return e.netParams.Watch( + netparams.WatchParam{ + Param: netparams.MarketFeeFactorsBuyBackFee, + Watcher: e.volumeRebateProgram.OnMarketFeeFactorsBuyBackFeeUpdate, + }, + netparams.WatchParam{ + Param: netparams.MarketFeeFactorsTreasuryFee, + Watcher: e.volumeRebateProgram.OnMarketFeeFactorsTreasuryFeeUpdate, + }, netparams.WatchParam{ Param: netparams.StakingAndDelegationRewardMinimumValidatorStake, Watcher: e.topology.OnMinDelegationUpdated, diff --git a/core/protocol/all_services.go b/core/protocol/all_services.go index cc782ed5fa..f2d8c9b438 100644 --- a/core/protocol/all_services.go +++ b/core/protocol/all_services.go @@ -765,6 +765,14 @@ func (svcs *allServices) setupNetParameters(powWatchers []netparams.WatchParam) Param: netparams.MarketFeeFactorsBuyBackFee, Watcher: svcs.executionEngine.OnMarketFeeFactorsBuyBackFeeUpdate, }, + { + Param: netparams.MarketFeeFactorsTreasuryFee, + Watcher: svcs.volumeRebate.OnMarketFeeFactorsTreasuryFeeUpdate, + }, + { + Param: netparams.MarketFeeFactorsBuyBackFee, + Watcher: svcs.volumeRebate.OnMarketFeeFactorsBuyBackFeeUpdate, + }, { Param: netparams.MarketValueWindowLength, Watcher: svcs.executionEngine.OnMarketValueWindowLengthUpdate, diff --git a/core/volumerebate/engine.go b/core/volumerebate/engine.go index 0acda65190..086b6a86e5 100644 --- a/core/volumerebate/engine.go +++ b/core/volumerebate/engine.go @@ -44,7 +44,10 @@ type Engine struct { newProgram *types.VolumeRebateProgram programHasEnded bool - factorsByParty map[types.PartyID]types.VolumeRebateStats + factorsByParty map[types.PartyID]types.VolumeRebateStats + buyBackFee num.Decimal + treasureFee num.Decimal + maxAdditionalRebate num.Decimal } func New(broker Broker, marketActivityTracker MarketActivityTracker) *Engine { @@ -99,7 +102,10 @@ func (e *Engine) VolumeRebateFactorForParty(party types.PartyID) num.Decimal { return num.DecimalZero() } - return factors.RebateFactor + // this is needed here again because the factors are calculated at the end of the epoch and + // the fee factors may change during the epoch so to ensure the factor is capped at any time + // we apply the min again here + return e.effectiveAdditionalRebate(factors.RebateFactor) } func (e *Engine) MakerVolumeFractionForParty(party types.PartyID) num.Decimal { @@ -198,7 +204,7 @@ func (e *Engine) computeFactorsByParty(ctx context.Context, epoch uint64) { tier := e.currentProgram.VolumeRebateBenefitTiers[i] if makerFraction.GreaterThanOrEqual(tier.MinimumPartyMakerVolumeFraction) { e.factorsByParty[party] = types.VolumeRebateStats{ - RebateFactor: tier.AdditionalMakerRebate, + RebateFactor: e.effectiveAdditionalRebate(tier.AdditionalMakerRebate), } evt.Stats = append(evt.Stats, &eventspb.PartyVolumeRebateStats{ PartyId: party.String(), @@ -223,3 +229,19 @@ func (e *Engine) computeFactorsByParty(ctx context.Context, epoch uint64) { e.broker.Send(events.NewVolumeRebateStatsUpdatedEvent(ctx, evt)) } + +func (e *Engine) OnMarketFeeFactorsTreasuryFeeUpdate(ctx context.Context, d num.Decimal) error { + e.treasureFee = d + e.maxAdditionalRebate = e.treasureFee.Add(e.buyBackFee) + return nil +} + +func (e *Engine) OnMarketFeeFactorsBuyBackFeeUpdate(ctx context.Context, d num.Decimal) error { + e.buyBackFee = d + e.maxAdditionalRebate = e.treasureFee.Add(e.buyBackFee) + return nil +} + +func (e *Engine) effectiveAdditionalRebate(tierRebate num.Decimal) num.Decimal { + return num.MinD(e.maxAdditionalRebate, tierRebate) +} diff --git a/core/volumerebate/engine_test.go b/core/volumerebate/engine_test.go index 584ac9ee53..1a28d643ae 100644 --- a/core/volumerebate/engine_test.go +++ b/core/volumerebate/engine_test.go @@ -142,7 +142,8 @@ func TestRebateFactor(t *testing.T) { broker := mocks.NewMockBroker(ctrl) marketActivityTracker := mocks.NewMockMarketActivityTracker(ctrl) engine := volumerebate.NewSnapshottedEngine(broker, marketActivityTracker) - + engine.OnMarketFeeFactorsBuyBackFeeUpdate(context.Background(), num.NewDecimalFromFloat(0.5)) + engine.OnMarketFeeFactorsTreasuryFeeUpdate(context.Background(), num.NewDecimalFromFloat(0.5)) currentTime := time.Now() p1 := &types.VolumeRebateProgram{ @@ -198,6 +199,8 @@ func TestRebateFactor(t *testing.T) { hashWithEpochNotionalsData, _, err := engine.GetState(key) require.NoError(t, err) loadedEngine := assertSnapshotMatches(t, key, hashWithEpochNotionalsData) + loadedEngine.OnMarketFeeFactorsBuyBackFeeUpdate(context.Background(), num.NewDecimalFromFloat(0.5)) + loadedEngine.OnMarketFeeFactorsTreasuryFeeUpdate(context.Background(), num.NewDecimalFromFloat(0.5)) // party does not exist require.Equal(t, num.DecimalZero(), engine.VolumeRebateFactorForParty("p8")) @@ -253,6 +256,8 @@ func TestRebateFactorWithWindow(t *testing.T) { broker := mocks.NewMockBroker(ctrl) marketActivityTracker := mocks.NewMockMarketActivityTracker(ctrl) engine := volumerebate.NewSnapshottedEngine(broker, marketActivityTracker) + engine.OnMarketFeeFactorsBuyBackFeeUpdate(context.Background(), num.DecimalFromFloat(0.5)) + engine.OnMarketFeeFactorsTreasuryFeeUpdate(context.Background(), num.DecimalFromFloat(0.5)) currentTime := time.Now() p1 := &types.VolumeRebateProgram{ @@ -318,6 +323,9 @@ func TestRebateFactorWithWindow(t *testing.T) { // volume 5000 require.Equal(t, "1", engine.VolumeRebateFactorForParty("p7").String()) + engine.OnMarketFeeFactorsBuyBackFeeUpdate(context.Background(), num.DecimalFromFloat(0.1)) + engine.OnMarketFeeFactorsTreasuryFeeUpdate(context.Background(), num.DecimalFromFloat(0.2)) + // running for another epoch marketActivityTracker.EXPECT().CalculateTotalMakerContributionInQuantum(gomock.Any()).Return(map[string]*num.Uint{ "p8": num.NewUint(2000), @@ -342,6 +350,8 @@ func TestRebateFactorWithWindow(t *testing.T) { hashAfter2Epochs, _, err := engine.GetState(key) require.NoError(t, err) loadedEngine := assertSnapshotMatches(t, key, hashAfter2Epochs) + loadedEngine.OnMarketFeeFactorsBuyBackFeeUpdate(context.Background(), num.NewDecimalFromFloat(0.5)) + loadedEngine.OnMarketFeeFactorsTreasuryFeeUpdate(context.Background(), num.NewDecimalFromFloat(0.5)) // fraction 0.2 => rebate 0.2 require.Equal(t, "0.2", engine.VolumeRebateFactorForParty("p8").String()) @@ -358,12 +368,12 @@ func TestRebateFactorWithWindow(t *testing.T) { // nothing this time require.Equal(t, "0", engine.VolumeRebateFactorForParty("p4").String()) require.Equal(t, "0", loadedEngine.VolumeRebateFactorForParty("p4").String()) - // fraction 0.4 => rebate 0.5 - require.Equal(t, "1", engine.VolumeRebateFactorForParty("p5").String()) - require.Equal(t, "1", loadedEngine.VolumeRebateFactorForParty("p5").String()) - // fraction 0.4 => rebate 0.5 - require.Equal(t, "1", engine.VolumeRebateFactorForParty("p6").String()) - require.Equal(t, "1", loadedEngine.VolumeRebateFactorForParty("p6").String()) + // fraction 0.4 => rebate 1 => capped at 0.3 + require.Equal(t, "0.3", engine.VolumeRebateFactorForParty("p5").String()) + require.Equal(t, "0.3", loadedEngine.VolumeRebateFactorForParty("p5").String()) + // fraction 0.4 => rebate 1 => capped at 0.3 + require.Equal(t, "0.3", engine.VolumeRebateFactorForParty("p6").String()) + require.Equal(t, "0.3", loadedEngine.VolumeRebateFactorForParty("p6").String()) // nothing this time require.Equal(t, "0", engine.VolumeRebateFactorForParty("p7").String()) require.Equal(t, "0", loadedEngine.VolumeRebateFactorForParty("p7").String())