From 6a747d0efb192980654f5ebb601eb7912128cbcd Mon Sep 17 00:00:00 2001 From: ze97286 Date: Mon, 5 Aug 2024 13:01:09 +0100 Subject: [PATCH] feat: buyback and treasury fees + split reward/discount components + rebate program --- CHANGELOG.md | 2 + .../commands/start/sqlsubscribers.go | 14 + commands/proposal_submission.go | 98 +- ...submission_update_referral_program_test.go | 130 +- ...ion_update_volume_discount_program_test.go | 50 +- core/collateral/engine.go | 98 +- core/collateral/engine_test.go | 10 +- core/events/bus.go | 17 + core/events/referral_set.go | 20 +- core/events/volume_rebate_program.go | 172 + core/execution/amm/engine_test.go | 2 +- core/execution/common/interfaces.go | 2 + .../common/liquidity_provision_test.go | 2 +- .../common/market_activity_tracker.go | 80 +- .../market_activity_tracker_internal_test.go | 24 +- .../market_activity_tracker_snapshot.go | 20 + .../common/market_activity_tracker_test.go | 161 +- .../mat_intermediate_scores_internal_test.go | 10 +- core/execution/engine.go | 17 +- core/execution/engine_netparams.go | 48 + core/execution/engine_snapshot.go | 2 + core/execution/engine_snapshot_test.go | 22 +- core/execution/future/market.go | 9 +- core/execution/future/market_callbacks.go | 12 + core/execution/future/market_snapshot.go | 2 + core/execution/future/market_snapshot_test.go | 12 +- core/execution/future/market_test.go | 25 +- core/execution/snapshot_test.go | 21 +- core/execution/spot/market.go | 11 +- core/execution/spot/market_callbacks.go | 12 + core/execution/spot/market_snapshot.go | 2 + core/execution/spot/market_test.go | 12 +- core/fee/engine.go | 191 +- core/fee/engine_test.go | 773 ++- core/fee/mocks/mocks.go | 71 +- core/governance/engine.go | 6 + core/governance/engine_test.go | 24 + ...ine_update_discount_volume_program_test.go | 38 +- .../engine_update_referral_program_test.go | 124 +- ...ngine_update_volume_rebate_program_test.go | 172 + core/governance/market.go | 13 + core/governance/market_cp_restore_test.go | 9 +- core/governance/netparams.go | 16 + core/governance/proposal.go | 9 + core/governance/referral_program.go | 20 +- core/governance/volume_discount_program.go | 10 +- core/governance/volume_rebate_program.go | 53 + .../features/0084-VDPR-012.feature | 18 +- .../features/0084-VDPR-013.feature | 30 +- .../features/0084-VDPR-014.feature | 12 +- .../0083-RFPR-benefit_factors.feature | 130 +- .../0083-RFPR-running_volume.feature | 74 +- .../0083-RFPR-staking_multipliers.feature | 14 +- .../features/verified/0029-FEES-fees.feature | 29 +- .../0029-FEES-multiple_discounts.feature | 48 +- core/integration/main_test.go | 42 +- core/integration/setup_test.go | 16 +- .../steps/the_fees_configuration.go | 18 + ...he_referral_benefit_tiers_configuration.go | 44 +- .../steps/the_referral_set_stats_should_be.go | 56 +- .../steps/volume_discount_program.go | 64 +- .../steps/volume_rebate_program.go | 156 + .../integration/stubs/fee_discount_service.go | 4 +- core/netparams/defaults.go | 13 + core/netparams/keys.go | 30 +- core/processor/abci.go | 10 + core/processor/abci_test.go | 4 + core/processor/mocks/mocks.go | 37 +- core/processor/processor.go | 2 +- core/protocol/all_services.go | 21 +- core/protocol/protocol.go | 1 + core/referral/engine.go | 83 +- core/referral/engine_test.go | 180 +- core/referral/snapshot.go | 9 +- core/snapshot/providers.go | 1 + core/types/collateral.go | 2 + core/types/fees.go | 72 +- core/types/governance_proposal.go | 7 + core/types/governance_proposal_terms.go | 26 + .../governance_update_referral_program.go | 20 +- ...vernance_update_volume_discount_program.go | 11 +- ...governance_update_volume_rebate_program.go | 194 + core/types/market_test.go | 4 + core/types/matching_test.go | 6 + core/types/rebate_program.go | 126 + core/types/referral_program.go | 102 +- core/types/referral_set.go | 12 +- core/types/snapshot.go | 1 + core/types/snapshot_nodes.go | 34 + core/types/transfer.go | 7 + core/types/volume_discount_program.go | 15 +- core/volumediscount/engine.go | 16 +- core/volumediscount/engine_test.go | 184 +- core/volumediscount/snapshot.go | 12 +- core/volumerebate/dependencies.go | 31 + core/volumerebate/engine.go | 225 + core/volumerebate/engine_test.go | 389 ++ core/volumerebate/helpers_for_test.go | 98 + core/volumerebate/mocks/mocks.go | 87 + core/volumerebate/snapshot.go | 196 + datanode/api/errors.go | 1 + datanode/api/trading_data_v2.go | 44 + datanode/broker/convert.go | 8 + datanode/entities/entities.go | 2 +- datanode/entities/market.go | 6 + datanode/entities/referral_set_stats.go | 21 +- datanode/entities/trade.go | 97 +- datanode/entities/volume_discount_stats.go | 19 +- datanode/entities/volume_rebate_program.go | 72 + datanode/entities/volume_rebate_stats.go | 97 + .../gateway/graphql/benefit_tier_resolver.go | 66 + datanode/gateway/graphql/generated.go | 4802 ++++++++++++-- datanode/gateway/graphql/gqlgen.yml | 8 + datanode/gateway/graphql/mocks/mocks.go | 40 + datanode/gateway/graphql/models.go | 42 + .../gateway/graphql/referral_set_resolver.go | 27 + datanode/gateway/graphql/resolvers.go | 39 + datanode/gateway/graphql/schema.graphql | 141 +- .../graphql/volume_discount_stats_resolver.go | 22 + .../graphql/volume_rebate_program_resolver.go | 54 + .../graphql/volume_rebate_stats_resolver.go | 39 + datanode/networkhistory/service_test.go | 12 +- datanode/service/stubs.go | 12 + ...ional_referral_set_stats split_factors.sql | 107 + datanode/sqlstore/proposals_test.go | 2 +- datanode/sqlstore/referral_sets.go | 14 +- datanode/sqlstore/referral_sets_test.go | 70 +- datanode/sqlstore/sqlstore.go | 2 + datanode/sqlstore/trades.go | 9 +- datanode/sqlstore/volume_discount_stats.go | 33 +- .../sqlstore/volume_discount_stats_test.go | 41 +- datanode/sqlstore/volume_rebate_program.go | 84 + .../sqlstore/volume_rebate_programs_test.go | 291 + datanode/sqlstore/volume_rebate_stats.go | 102 + datanode/sqlstore/volume_rebate_stats_test.go | 271 + .../sqlsubscribers/volume_rebate_programs.go | 99 + .../volume_rebate_stats_updated.go | 74 + protos/data-node/api/v2/trading_data.pb.go | 5797 ++++++++++------- protos/data-node/api/v2/trading_data.pb.gw.go | 148 + .../data-node/api/v2/trading_data_grpc.pb.go | 86 + protos/embed_test.go | 2 +- .../data-node/api/v2/trading_data.proto | 105 +- .../sources/data-node/grpc-rest-bindings.yml | 4 + .../vega/checkpoint/v1/checkpoint.proto | 3 + protos/sources/vega/events/v1/events.proto | 86 +- protos/sources/vega/governance.proto | 23 + protos/sources/vega/markets.proto | 4 + .../sources/vega/snapshot/v1/snapshot.proto | 30 + protos/sources/vega/vega.proto | 75 +- protos/vega/checkpoint/v1/checkpoint.pb.go | 410 +- protos/vega/events/v1/events.pb.go | 5208 ++++++++------- protos/vega/governance.pb.go | 1627 +++-- protos/vega/markets.pb.go | 573 +- protos/vega/snapshot/v1/snapshot.pb.go | 5475 ++++++++-------- protos/vega/vega.pb.go | 3625 ++++++----- 155 files changed, 24166 insertions(+), 11656 deletions(-) create mode 100644 core/events/volume_rebate_program.go create mode 100644 core/governance/engine_update_volume_rebate_program_test.go create mode 100644 core/governance/volume_rebate_program.go create mode 100644 core/integration/steps/volume_rebate_program.go create mode 100644 core/types/governance_update_volume_rebate_program.go create mode 100644 core/types/rebate_program.go create mode 100644 core/volumerebate/dependencies.go create mode 100644 core/volumerebate/engine.go create mode 100644 core/volumerebate/engine_test.go create mode 100644 core/volumerebate/helpers_for_test.go create mode 100644 core/volumerebate/mocks/mocks.go create mode 100644 core/volumerebate/snapshot.go create mode 100644 datanode/entities/volume_rebate_program.go create mode 100644 datanode/entities/volume_rebate_stats.go create mode 100644 datanode/gateway/graphql/volume_rebate_program_resolver.go create mode 100644 datanode/gateway/graphql/volume_rebate_stats_resolver.go create mode 100644 datanode/sqlstore/migrations/0116_additional_referral_set_stats split_factors.sql create mode 100644 datanode/sqlstore/volume_rebate_program.go create mode 100644 datanode/sqlstore/volume_rebate_programs_test.go create mode 100644 datanode/sqlstore/volume_rebate_stats.go create mode 100644 datanode/sqlstore/volume_rebate_stats_test.go create mode 100644 datanode/sqlsubscribers/volume_rebate_programs.go create mode 100644 datanode/sqlsubscribers/volume_rebate_stats_updated.go diff --git a/CHANGELOG.md b/CHANGELOG.md index cc0e9c8da00..72fa1c89882 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,8 @@ ### 🛠 Improvements +- [11428](https://github.com/vegaprotocol/vega/issues/11428) - Add buy back and treasury fee and separate discount/reward factors. +- [11468](https://github.com/vegaprotocol/vega/issues/11468) - Added support for volume rebate program. - [11459](https://github.com/vegaprotocol/vega/issues/11459) - Deprecate time weight position reward metric and replace it with time weighted notional. ### 🐛 Fixes diff --git a/cmd/data-node/commands/start/sqlsubscribers.go b/cmd/data-node/commands/start/sqlsubscribers.go index f381571e78a..87903ce48a6 100644 --- a/cmd/data-node/commands/start/sqlsubscribers.go +++ b/cmd/data-node/commands/start/sqlsubscribers.go @@ -84,6 +84,8 @@ type SQLSubscribers struct { timeWeightedNotionalPositionStore *sqlstore.TimeWeightedNotionalPosition gameScoreStore *sqlstore.GameScores ammPoolsStore *sqlstore.AMMPools + volumeRebateStatsStore *sqlstore.VolumeRebateStats + volumeRebateProgramsStore *sqlstore.VolumeRebatePrograms // Services candleService *candlesv2.Svc @@ -142,6 +144,8 @@ type SQLSubscribers struct { timeWeightedNotionalPositionService *service.TimeWeightedNotionalPosition gameScoreService *service.GameScore ammPoolsService *service.AMMPools + volumeRebateStatsService *service.VolumeRebateStats + volumeRebateProgramService *service.VolumeRebatePrograms // Subscribers accountSub *sqlsubscribers.Account @@ -196,6 +200,8 @@ type SQLSubscribers struct { timeWeightedNotionalPositionSub *sqlsubscribers.TimeWeightedNotionalPosition gameScoreSub *sqlsubscribers.GameScore ammPoolsSub *sqlsubscribers.AMMPools + volumeRebateStatsSub *sqlsubscribers.VolumeRebateStatsUpdated + volumeRebateProgramSub *sqlsubscribers.VolumeRebateProgram } func (s *SQLSubscribers) GetSQLSubscribers() []broker.SQLBrokerSubscriber { @@ -254,6 +260,8 @@ func (s *SQLSubscribers) GetSQLSubscribers() []broker.SQLBrokerSubscriber { s.timeWeightedNotionalPositionSub, s.gameScoreSub, s.ammPoolsSub, + s.volumeRebateProgramSub, + s.volumeRebateStatsSub, } } @@ -317,6 +325,8 @@ func (s *SQLSubscribers) CreateAllStores(ctx context.Context, Log *logging.Logge s.timeWeightedNotionalPositionStore = sqlstore.NewTimeWeightedNotionalPosition(transactionalConnectionSource) s.gameScoreStore = sqlstore.NewGameScores(transactionalConnectionSource) s.ammPoolsStore = sqlstore.NewAMMPools(transactionalConnectionSource) + s.volumeRebateStatsStore = sqlstore.NewVolumeRebateStats(transactionalConnectionSource) + s.volumeRebateProgramsStore = sqlstore.NewVolumeRebatePrograms(transactionalConnectionSource) } func (s *SQLSubscribers) SetupServices(ctx context.Context, log *logging.Logger, cfg service.Config, candlesConfig candlesv2.Config) error { @@ -374,6 +384,8 @@ func (s *SQLSubscribers) SetupServices(ctx context.Context, log *logging.Logger, s.timeWeightedNotionalPositionService = service.NewTimeWeightedNotionalPosition(s.timeWeightedNotionalPositionStore) s.gameScoreService = service.NewGameScore(s.gameScoreStore, log) s.ammPoolsService = service.NewAMMPools(s.ammPoolsStore) + s.volumeRebateStatsService = service.NewVolumeRebateStats(s.volumeRebateStatsStore) + s.volumeRebateProgramService = service.NewVolumeRebatePrograms(s.volumeRebateProgramsStore) s.marketDepthService = service.NewMarketDepth( cfg.MarketDepth, @@ -455,5 +467,7 @@ func (s *SQLSubscribers) SetupSQLSubscribers() { s.marginModesSub = sqlsubscribers.NewMarginModes(s.marginModesService) s.timeWeightedNotionalPositionSub = sqlsubscribers.NewTimeWeightedNotionalPosition(s.timeWeightedNotionalPositionService) s.gameScoreSub = sqlsubscribers.NewGameScore(s.gameScoreStore) + s.volumeRebateStatsSub = sqlsubscribers.NewVolumeRebateStatsUpdated(s.volumeRebateStatsService) + s.volumeRebateProgramSub = sqlsubscribers.NewVolumeRebateProgram(s.volumeRebateProgramService) s.ammPoolsSub = sqlsubscribers.NewAMMPools(s.ammPoolsService, s.marketDepthService) } diff --git a/commands/proposal_submission.go b/commands/proposal_submission.go index 0d4794448ad..46db41dd4d5 100644 --- a/commands/proposal_submission.go +++ b/commands/proposal_submission.go @@ -454,14 +454,26 @@ func checkVolumeBenefitTier(index int, tier *vegapb.VolumeBenefitTier) Errors { errs.AddForProperty(propertyPath+".minimum_running_notional_taker_volume", ErrMustBePositive) } } - if len(tier.VolumeDiscountFactor) == 0 { - errs.AddForProperty(propertyPath+".volume_discount_factor", ErrIsRequired) + if tier.VolumeDiscountFactors == nil { + errs.AddForProperty(propertyPath+".volume_discount_factors", ErrIsRequired) } else { - rdf, err := num.DecimalFromString(tier.VolumeDiscountFactor) + rdf, err := num.DecimalFromString(tier.VolumeDiscountFactors.MakerDiscountFactor) if err != nil { - errs.AddForProperty(propertyPath+".volume_discount_factor", ErrIsNotValidNumber) + errs.AddForProperty(propertyPath+".volume_discount_factors.maker_discount_factor", ErrIsNotValidNumber) } else if rdf.IsNegative() { - errs.AddForProperty(propertyPath+".volume_discount_factor", ErrMustBePositiveOrZero) + errs.AddForProperty(propertyPath+".volume_discount_factors.maker_discount_factor", ErrMustBePositiveOrZero) + } + rdf, err = num.DecimalFromString(tier.VolumeDiscountFactors.LiquidityDiscountFactor) + if err != nil { + errs.AddForProperty(propertyPath+".volume_discount_factors.liquidity_discount_factor", ErrIsNotValidNumber) + } else if rdf.IsNegative() { + errs.AddForProperty(propertyPath+".volume_discount_factors.liquidity_discount_factor", ErrMustBePositiveOrZero) + } + rdf, err = num.DecimalFromString(tier.VolumeDiscountFactors.InfrastructureDiscountFactor) + if err != nil { + errs.AddForProperty(propertyPath+".volume_discount_factors.infrastructure_discount_factor", ErrIsNotValidNumber) + } else if rdf.IsNegative() { + errs.AddForProperty(propertyPath+".volume_discount_factors.infrastructure_discount_factor", ErrMustBePositiveOrZero) } } return errs @@ -494,25 +506,73 @@ func checkBenefitTier(index int, tier *vegapb.BenefitTier) Errors { } } - if len(tier.ReferralRewardFactor) == 0 { - errs.AddForProperty(propertyPath+".referral_reward_factor", ErrIsRequired) + if tier.ReferralRewardFactors == nil { + errs.AddForProperty(propertyPath+".referral_reward_factors", ErrIsRequired) } else { - rrf, err := num.DecimalFromString(tier.ReferralRewardFactor) - if err != nil { - errs.AddForProperty(propertyPath+".referral_reward_factor", ErrIsNotValidNumber) - } else if rrf.IsNegative() { - errs.AddForProperty(propertyPath+".referral_reward_factor", ErrMustBePositiveOrZero) + if len(tier.ReferralRewardFactors.InfrastructureRewardFactor) == 0 { + errs.AddForProperty(propertyPath+".referral_reward_factors.infrastructure_reward_factor", ErrIsRequired) + } else { + rrf, err := num.DecimalFromString(tier.ReferralRewardFactors.InfrastructureRewardFactor) + if err != nil { + errs.AddForProperty(propertyPath+".referral_reward_factors.infrastructure_reward_factor", ErrIsNotValidNumber) + } else if rrf.IsNegative() { + errs.AddForProperty(propertyPath+".referral_reward_factors.infrastructure_reward_factor", ErrMustBePositiveOrZero) + } + } + if len(tier.ReferralRewardFactors.MakerRewardFactor) == 0 { + errs.AddForProperty(propertyPath+".referral_reward_factors.maker_reward_factor", ErrIsRequired) + } else { + rrf, err := num.DecimalFromString(tier.ReferralRewardFactors.MakerRewardFactor) + if err != nil { + errs.AddForProperty(propertyPath+".referral_reward_factors.maker_reward_factor", ErrIsNotValidNumber) + } else if rrf.IsNegative() { + errs.AddForProperty(propertyPath+".referral_reward_factors.maker_reward_factor", ErrMustBePositiveOrZero) + } + } + if len(tier.ReferralRewardFactors.LiquidityRewardFactor) == 0 { + errs.AddForProperty(propertyPath+".referral_reward_factors.liquidity_reward_factor", ErrIsRequired) + } else { + rrf, err := num.DecimalFromString(tier.ReferralRewardFactors.LiquidityRewardFactor) + if err != nil { + errs.AddForProperty(propertyPath+".referral_reward_factors.liquidity_reward_factor", ErrIsNotValidNumber) + } else if rrf.IsNegative() { + errs.AddForProperty(propertyPath+".referral_reward_factors.liquidity_reward_factor", ErrMustBePositiveOrZero) + } } } - if len(tier.ReferralDiscountFactor) == 0 { - errs.AddForProperty(propertyPath+".referral_discount_factor", ErrIsRequired) + if tier.ReferralDiscountFactors == nil { + errs.AddForProperty(propertyPath+".referral_discount_factors", ErrIsRequired) } else { - rdf, err := num.DecimalFromString(tier.ReferralDiscountFactor) - if err != nil { - errs.AddForProperty(propertyPath+".referral_discount_factor", ErrIsNotValidNumber) - } else if rdf.IsNegative() { - errs.AddForProperty(propertyPath+".referral_discount_factor", ErrMustBePositiveOrZero) + if len(tier.ReferralDiscountFactors.InfrastructureDiscountFactor) == 0 { + errs.AddForProperty(propertyPath+".referral_discount_factors.infrastructure_discount_factor", ErrIsRequired) + } else { + rrf, err := num.DecimalFromString(tier.ReferralDiscountFactors.InfrastructureDiscountFactor) + if err != nil { + errs.AddForProperty(propertyPath+".referral_discount_factors.infrastructure_discount_factor", ErrIsNotValidNumber) + } else if rrf.IsNegative() { + errs.AddForProperty(propertyPath+".referral_discount_factors.infrastructure_discount_factor", ErrMustBePositiveOrZero) + } + } + if len(tier.ReferralDiscountFactors.MakerDiscountFactor) == 0 { + errs.AddForProperty(propertyPath+".referral_discount_factors.maker_discount_factor", ErrIsRequired) + } else { + rrf, err := num.DecimalFromString(tier.ReferralDiscountFactors.MakerDiscountFactor) + if err != nil { + errs.AddForProperty(propertyPath+".referral_discount_factors.maker_discount_factor", ErrIsNotValidNumber) + } else if rrf.IsNegative() { + errs.AddForProperty(propertyPath+".referral_discount_factors.maker_discount_factor", ErrMustBePositiveOrZero) + } + } + if len(tier.ReferralDiscountFactors.LiquidityDiscountFactor) == 0 { + errs.AddForProperty(propertyPath+".referral_discount_factors.liquidity_discount_factor", ErrIsRequired) + } else { + rrf, err := num.DecimalFromString(tier.ReferralDiscountFactors.LiquidityDiscountFactor) + if err != nil { + errs.AddForProperty(propertyPath+".referral_discount_factors.liquidity_discount_factor", ErrIsNotValidNumber) + } else if rrf.IsNegative() { + errs.AddForProperty(propertyPath+".referral_discount_factors.liquidity_discount_factor", ErrMustBePositiveOrZero) + } } } diff --git a/commands/proposal_submission_update_referral_program_test.go b/commands/proposal_submission_update_referral_program_test.go index a987c8e5fff..2df7549bfbf 100644 --- a/commands/proposal_submission_update_referral_program_test.go +++ b/commands/proposal_submission_update_referral_program_test.go @@ -183,6 +183,8 @@ func testSubmissionForReferralProgramUpdateWithoutTierMinimumRunningNotionalTake } func testSubmissionForReferralProgramUpdateWithDuplicateBenefitTierEntriesFails(t *testing.T) { + factors := []string{"1.1", "1.2", "1.3", "1.4", "1.5", "1.6"} + err := checkProposalSubmission(&commandspb.ProposalSubmission{ Terms: &types.ProposalTerms{ Change: &types.ProposalTerms_UpdateReferralProgram{ @@ -192,20 +194,44 @@ func testSubmissionForReferralProgramUpdateWithDuplicateBenefitTierEntriesFails( { MinimumRunningNotionalTakerVolume: "100", MinimumEpochs: "10", - ReferralRewardFactor: "1.1", - ReferralDiscountFactor: "1.2", + ReferralRewardFactors: &types.RewardFactors{ + InfrastructureRewardFactor: factors[0], + MakerRewardFactor: factors[1], + LiquidityRewardFactor: factors[2], + }, + ReferralDiscountFactors: &types.DiscountFactors{ + InfrastructureDiscountFactor: factors[1], + MakerDiscountFactor: factors[2], + LiquidityDiscountFactor: factors[3], + }, }, { MinimumRunningNotionalTakerVolume: "100", MinimumEpochs: "10", - ReferralRewardFactor: "1.2", - ReferralDiscountFactor: "1.3", + ReferralRewardFactors: &types.RewardFactors{ + InfrastructureRewardFactor: factors[1], + MakerRewardFactor: factors[2], + LiquidityRewardFactor: factors[3], + }, + ReferralDiscountFactors: &types.DiscountFactors{ + InfrastructureDiscountFactor: factors[2], + MakerDiscountFactor: factors[3], + LiquidityDiscountFactor: factors[4], + }, }, { MinimumRunningNotionalTakerVolume: "100", MinimumEpochs: "20", - ReferralRewardFactor: "1.3", - ReferralDiscountFactor: "1.4", + ReferralRewardFactors: &types.RewardFactors{ + InfrastructureRewardFactor: factors[2], + MakerRewardFactor: factors[3], + LiquidityRewardFactor: factors[4], + }, + ReferralDiscountFactors: &types.DiscountFactors{ + InfrastructureDiscountFactor: factors[3], + MakerDiscountFactor: factors[4], + LiquidityDiscountFactor: factors[5], + }, }, }, }, @@ -376,8 +402,8 @@ func testSubmissionForReferralProgramUpdateWithoutTierReferralRewardFactorFails( }, }) - assert.Contains(t, err.Get("proposal_submission.terms.change.update_referral_program.changes.benefit_tiers.0.referral_reward_factor"), commands.ErrIsRequired) - assert.Contains(t, err.Get("proposal_submission.terms.change.update_referral_program.changes.benefit_tiers.1.referral_reward_factor"), commands.ErrIsRequired) + assert.Contains(t, err.Get("proposal_submission.terms.change.update_referral_program.changes.benefit_tiers.0.referral_reward_factors"), commands.ErrIsRequired) + assert.Contains(t, err.Get("proposal_submission.terms.change.update_referral_program.changes.benefit_tiers.1.referral_reward_factors"), commands.ErrIsRequired) } func testSubmissionForReferralProgramUpdateWithBadFormatForTierReferralRewardFactorFails(t *testing.T) { @@ -388,9 +414,17 @@ func testSubmissionForReferralProgramUpdateWithBadFormatForTierReferralRewardFac Changes: &types.ReferralProgramChanges{ BenefitTiers: []*types.BenefitTier{ { - ReferralRewardFactor: "qbc", + ReferralRewardFactors: &types.RewardFactors{ + InfrastructureRewardFactor: "qbc", + MakerRewardFactor: "qbc", + LiquidityRewardFactor: "qbc", + }, }, { - ReferralRewardFactor: "0x32", + ReferralRewardFactors: &types.RewardFactors{ + InfrastructureRewardFactor: "0x32", + MakerRewardFactor: "0x32", + LiquidityRewardFactor: "0x32", + }, }, }, }, @@ -399,8 +433,8 @@ func testSubmissionForReferralProgramUpdateWithBadFormatForTierReferralRewardFac }, }) - assert.Contains(t, err.Get("proposal_submission.terms.change.update_referral_program.changes.benefit_tiers.0.referral_reward_factor"), commands.ErrIsNotValidNumber) - assert.Contains(t, err.Get("proposal_submission.terms.change.update_referral_program.changes.benefit_tiers.1.referral_reward_factor"), commands.ErrIsNotValidNumber) + assert.Contains(t, err.Get("proposal_submission.terms.change.update_referral_program.changes.benefit_tiers.0.referral_reward_factors.infrastructure_reward_factor"), commands.ErrIsNotValidNumber) + assert.Contains(t, err.Get("proposal_submission.terms.change.update_referral_program.changes.benefit_tiers.1.referral_reward_factors.infrastructure_reward_factor"), commands.ErrIsNotValidNumber) } func testSubmissionForReferralProgramUpdateWithBadValueForTierReferralRewardFactorFails(t *testing.T) { @@ -411,9 +445,17 @@ func testSubmissionForReferralProgramUpdateWithBadValueForTierReferralRewardFact Changes: &types.ReferralProgramChanges{ BenefitTiers: []*types.BenefitTier{ { - ReferralRewardFactor: "-10", + ReferralRewardFactors: &types.RewardFactors{ + InfrastructureRewardFactor: "-10", + MakerRewardFactor: "-10", + LiquidityRewardFactor: "-10", + }, }, { - ReferralRewardFactor: "-1", + ReferralRewardFactors: &types.RewardFactors{ + InfrastructureRewardFactor: "-1", + MakerRewardFactor: "-1", + LiquidityRewardFactor: "-1", + }, }, }, }, @@ -422,8 +464,12 @@ func testSubmissionForReferralProgramUpdateWithBadValueForTierReferralRewardFact }, }) - assert.Contains(t, err.Get("proposal_submission.terms.change.update_referral_program.changes.benefit_tiers.0.referral_reward_factor"), commands.ErrMustBePositiveOrZero) - assert.Contains(t, err.Get("proposal_submission.terms.change.update_referral_program.changes.benefit_tiers.1.referral_reward_factor"), commands.ErrMustBePositiveOrZero) + assert.Contains(t, err.Get("proposal_submission.terms.change.update_referral_program.changes.benefit_tiers.0.referral_reward_factors.infrastructure_reward_factor"), commands.ErrMustBePositiveOrZero) + assert.Contains(t, err.Get("proposal_submission.terms.change.update_referral_program.changes.benefit_tiers.1.referral_reward_factors.infrastructure_reward_factor"), commands.ErrMustBePositiveOrZero) + assert.Contains(t, err.Get("proposal_submission.terms.change.update_referral_program.changes.benefit_tiers.0.referral_reward_factors.maker_reward_factor"), commands.ErrMustBePositiveOrZero) + assert.Contains(t, err.Get("proposal_submission.terms.change.update_referral_program.changes.benefit_tiers.1.referral_reward_factors.maker_reward_factor"), commands.ErrMustBePositiveOrZero) + assert.Contains(t, err.Get("proposal_submission.terms.change.update_referral_program.changes.benefit_tiers.0.referral_reward_factors.liquidity_reward_factor"), commands.ErrMustBePositiveOrZero) + assert.Contains(t, err.Get("proposal_submission.terms.change.update_referral_program.changes.benefit_tiers.1.referral_reward_factors.liquidity_reward_factor"), commands.ErrMustBePositiveOrZero) } func testSubmissionForReferralProgramUpdateWithoutTierReferralDiscountFactorFails(t *testing.T) { @@ -434,9 +480,9 @@ func testSubmissionForReferralProgramUpdateWithoutTierReferralDiscountFactorFail Changes: &types.ReferralProgramChanges{ BenefitTiers: []*types.BenefitTier{ { - ReferralDiscountFactor: "", + ReferralDiscountFactors: &types.DiscountFactors{}, }, { - ReferralDiscountFactor: "", + ReferralDiscountFactors: &types.DiscountFactors{}, }, }, }, @@ -445,8 +491,12 @@ func testSubmissionForReferralProgramUpdateWithoutTierReferralDiscountFactorFail }, }) - assert.Contains(t, err.Get("proposal_submission.terms.change.update_referral_program.changes.benefit_tiers.0.referral_discount_factor"), commands.ErrIsRequired) - assert.Contains(t, err.Get("proposal_submission.terms.change.update_referral_program.changes.benefit_tiers.1.referral_discount_factor"), commands.ErrIsRequired) + assert.Contains(t, err.Get("proposal_submission.terms.change.update_referral_program.changes.benefit_tiers.0.referral_discount_factors.infrastructure_discount_factor"), commands.ErrIsRequired) + assert.Contains(t, err.Get("proposal_submission.terms.change.update_referral_program.changes.benefit_tiers.1.referral_discount_factors.infrastructure_discount_factor"), commands.ErrIsRequired) + assert.Contains(t, err.Get("proposal_submission.terms.change.update_referral_program.changes.benefit_tiers.0.referral_discount_factors.maker_discount_factor"), commands.ErrIsRequired) + assert.Contains(t, err.Get("proposal_submission.terms.change.update_referral_program.changes.benefit_tiers.1.referral_discount_factors.maker_discount_factor"), commands.ErrIsRequired) + assert.Contains(t, err.Get("proposal_submission.terms.change.update_referral_program.changes.benefit_tiers.0.referral_discount_factors.liquidity_discount_factor"), commands.ErrIsRequired) + assert.Contains(t, err.Get("proposal_submission.terms.change.update_referral_program.changes.benefit_tiers.1.referral_discount_factors.liquidity_discount_factor"), commands.ErrIsRequired) } func testSubmissionForReferralProgramUpdateWithBadFormatForTierReferralDiscountFactorFails(t *testing.T) { @@ -457,9 +507,17 @@ func testSubmissionForReferralProgramUpdateWithBadFormatForTierReferralDiscountF Changes: &types.ReferralProgramChanges{ BenefitTiers: []*types.BenefitTier{ { - ReferralDiscountFactor: "qbc", + ReferralDiscountFactors: &types.DiscountFactors{ + InfrastructureDiscountFactor: "qbc", + LiquidityDiscountFactor: "qbc", + MakerDiscountFactor: "qbc", + }, }, { - ReferralDiscountFactor: "0x32", + ReferralDiscountFactors: &types.DiscountFactors{ + InfrastructureDiscountFactor: "0x32", + LiquidityDiscountFactor: "0x32", + MakerDiscountFactor: "0x32", + }, }, }, }, @@ -468,8 +526,12 @@ func testSubmissionForReferralProgramUpdateWithBadFormatForTierReferralDiscountF }, }) - assert.Contains(t, err.Get("proposal_submission.terms.change.update_referral_program.changes.benefit_tiers.0.referral_discount_factor"), commands.ErrIsNotValidNumber) - assert.Contains(t, err.Get("proposal_submission.terms.change.update_referral_program.changes.benefit_tiers.1.referral_discount_factor"), commands.ErrIsNotValidNumber) + assert.Contains(t, err.Get("proposal_submission.terms.change.update_referral_program.changes.benefit_tiers.0.referral_discount_factors.infrastructure_discount_factor"), commands.ErrIsNotValidNumber) + assert.Contains(t, err.Get("proposal_submission.terms.change.update_referral_program.changes.benefit_tiers.1.referral_discount_factors.infrastructure_discount_factor"), commands.ErrIsNotValidNumber) + assert.Contains(t, err.Get("proposal_submission.terms.change.update_referral_program.changes.benefit_tiers.0.referral_discount_factors.maker_discount_factor"), commands.ErrIsNotValidNumber) + assert.Contains(t, err.Get("proposal_submission.terms.change.update_referral_program.changes.benefit_tiers.1.referral_discount_factors.maker_discount_factor"), commands.ErrIsNotValidNumber) + assert.Contains(t, err.Get("proposal_submission.terms.change.update_referral_program.changes.benefit_tiers.0.referral_discount_factors.liquidity_discount_factor"), commands.ErrIsNotValidNumber) + assert.Contains(t, err.Get("proposal_submission.terms.change.update_referral_program.changes.benefit_tiers.1.referral_discount_factors.liquidity_discount_factor"), commands.ErrIsNotValidNumber) } func testSubmissionForReferralProgramUpdateWithBadValueForTierReferralDiscountFactorFails(t *testing.T) { @@ -480,9 +542,17 @@ func testSubmissionForReferralProgramUpdateWithBadValueForTierReferralDiscountFa Changes: &types.ReferralProgramChanges{ BenefitTiers: []*types.BenefitTier{ { - ReferralDiscountFactor: "-10", + ReferralDiscountFactors: &types.DiscountFactors{ + InfrastructureDiscountFactor: "-10", + MakerDiscountFactor: "-10", + LiquidityDiscountFactor: "-10", + }, }, { - ReferralDiscountFactor: "-1", + ReferralDiscountFactors: &types.DiscountFactors{ + InfrastructureDiscountFactor: "-1", + MakerDiscountFactor: "-1", + LiquidityDiscountFactor: "-1", + }, }, }, }, @@ -491,8 +561,12 @@ func testSubmissionForReferralProgramUpdateWithBadValueForTierReferralDiscountFa }, }) - assert.Contains(t, err.Get("proposal_submission.terms.change.update_referral_program.changes.benefit_tiers.0.referral_discount_factor"), commands.ErrMustBePositiveOrZero) - assert.Contains(t, err.Get("proposal_submission.terms.change.update_referral_program.changes.benefit_tiers.1.referral_discount_factor"), commands.ErrMustBePositiveOrZero) + assert.Contains(t, err.Get("proposal_submission.terms.change.update_referral_program.changes.benefit_tiers.0.referral_discount_factors.infrastructure_discount_factor"), commands.ErrMustBePositiveOrZero) + assert.Contains(t, err.Get("proposal_submission.terms.change.update_referral_program.changes.benefit_tiers.1.referral_discount_factors.infrastructure_discount_factor"), commands.ErrMustBePositiveOrZero) + assert.Contains(t, err.Get("proposal_submission.terms.change.update_referral_program.changes.benefit_tiers.0.referral_discount_factors.liquidity_discount_factor"), commands.ErrMustBePositiveOrZero) + assert.Contains(t, err.Get("proposal_submission.terms.change.update_referral_program.changes.benefit_tiers.1.referral_discount_factors.liquidity_discount_factor"), commands.ErrMustBePositiveOrZero) + assert.Contains(t, err.Get("proposal_submission.terms.change.update_referral_program.changes.benefit_tiers.0.referral_discount_factors.maker_discount_factor"), commands.ErrMustBePositiveOrZero) + assert.Contains(t, err.Get("proposal_submission.terms.change.update_referral_program.changes.benefit_tiers.1.referral_discount_factors.maker_discount_factor"), commands.ErrMustBePositiveOrZero) } func testSubmissionForReferralProgramUpdateWithoutStakingTierMinimumStakedTokensFails(t *testing.T) { diff --git a/commands/proposal_submission_update_volume_discount_program_test.go b/commands/proposal_submission_update_volume_discount_program_test.go index b5107d318a0..9278e3fe0d0 100644 --- a/commands/proposal_submission_update_volume_discount_program_test.go +++ b/commands/proposal_submission_update_volume_discount_program_test.go @@ -220,11 +220,7 @@ func testSubmissionForVolumeDiscountProgramUpdateWithoutTierVolumeDiscountFactor UpdateVolumeDiscountProgram: &types.UpdateVolumeDiscountProgram{ Changes: &types.VolumeDiscountProgramChanges{ BenefitTiers: []*types.VolumeBenefitTier{ - { - VolumeDiscountFactor: "", - }, { - VolumeDiscountFactor: "", - }, + {}, {}, }, }, }, @@ -232,8 +228,8 @@ func testSubmissionForVolumeDiscountProgramUpdateWithoutTierVolumeDiscountFactor }, }) - assert.Contains(t, err.Get("proposal_submission.terms.change.update_volume_discount_program.changes.benefit_tiers.0.volume_discount_factor"), commands.ErrIsRequired) - assert.Contains(t, err.Get("proposal_submission.terms.change.update_volume_discount_program.changes.benefit_tiers.1.volume_discount_factor"), commands.ErrIsRequired) + assert.Contains(t, err.Get("proposal_submission.terms.change.update_volume_discount_program.changes.benefit_tiers.0.volume_discount_factors"), commands.ErrIsRequired) + assert.Contains(t, err.Get("proposal_submission.terms.change.update_volume_discount_program.changes.benefit_tiers.1.volume_discount_factors"), commands.ErrIsRequired) } func testSubmissionForVolumeDiscountProgramUpdateWithBadFormatForTierVolumeDiscountFactorFails(t *testing.T) { @@ -244,9 +240,17 @@ func testSubmissionForVolumeDiscountProgramUpdateWithBadFormatForTierVolumeDisco Changes: &types.VolumeDiscountProgramChanges{ BenefitTiers: []*types.VolumeBenefitTier{ { - VolumeDiscountFactor: "qbc", + VolumeDiscountFactors: &types.DiscountFactors{ + InfrastructureDiscountFactor: "qbc", + LiquidityDiscountFactor: "qbc", + MakerDiscountFactor: "qbc", + }, }, { - VolumeDiscountFactor: "0x32", + VolumeDiscountFactors: &types.DiscountFactors{ + InfrastructureDiscountFactor: "0x32", + LiquidityDiscountFactor: "0x32", + MakerDiscountFactor: "0x32", + }, }, }, }, @@ -255,8 +259,12 @@ func testSubmissionForVolumeDiscountProgramUpdateWithBadFormatForTierVolumeDisco }, }) - assert.Contains(t, err.Get("proposal_submission.terms.change.update_volume_discount_program.changes.benefit_tiers.0.volume_discount_factor"), commands.ErrIsNotValidNumber) - assert.Contains(t, err.Get("proposal_submission.terms.change.update_volume_discount_program.changes.benefit_tiers.1.volume_discount_factor"), commands.ErrIsNotValidNumber) + assert.Contains(t, err.Get("proposal_submission.terms.change.update_volume_discount_program.changes.benefit_tiers.0.volume_discount_factors.maker_discount_factor"), commands.ErrIsNotValidNumber) + assert.Contains(t, err.Get("proposal_submission.terms.change.update_volume_discount_program.changes.benefit_tiers.0.volume_discount_factors.liquidity_discount_factor"), commands.ErrIsNotValidNumber) + assert.Contains(t, err.Get("proposal_submission.terms.change.update_volume_discount_program.changes.benefit_tiers.0.volume_discount_factors.infrastructure_discount_factor"), commands.ErrIsNotValidNumber) + assert.Contains(t, err.Get("proposal_submission.terms.change.update_volume_discount_program.changes.benefit_tiers.1.volume_discount_factors.maker_discount_factor"), commands.ErrIsNotValidNumber) + assert.Contains(t, err.Get("proposal_submission.terms.change.update_volume_discount_program.changes.benefit_tiers.1.volume_discount_factors.liquidity_discount_factor"), commands.ErrIsNotValidNumber) + assert.Contains(t, err.Get("proposal_submission.terms.change.update_volume_discount_program.changes.benefit_tiers.1.volume_discount_factors.infrastructure_discount_factor"), commands.ErrIsNotValidNumber) } func testSubmissionForVolumeDiscountProgramUpdateWithBadValueForTierVolumeDiscountFactorFails(t *testing.T) { @@ -267,9 +275,17 @@ func testSubmissionForVolumeDiscountProgramUpdateWithBadValueForTierVolumeDiscou Changes: &types.VolumeDiscountProgramChanges{ BenefitTiers: []*types.VolumeBenefitTier{ { - VolumeDiscountFactor: "-10", + VolumeDiscountFactors: &types.DiscountFactors{ + InfrastructureDiscountFactor: "-10", + LiquidityDiscountFactor: "-5", + MakerDiscountFactor: "-7", + }, }, { - VolumeDiscountFactor: "-1", + VolumeDiscountFactors: &types.DiscountFactors{ + InfrastructureDiscountFactor: "-1", + LiquidityDiscountFactor: "-3", + MakerDiscountFactor: "-9", + }, }, }, }, @@ -278,6 +294,10 @@ func testSubmissionForVolumeDiscountProgramUpdateWithBadValueForTierVolumeDiscou }, }) - assert.Contains(t, err.Get("proposal_submission.terms.change.update_volume_discount_program.changes.benefit_tiers.0.volume_discount_factor"), commands.ErrMustBePositiveOrZero) - assert.Contains(t, err.Get("proposal_submission.terms.change.update_volume_discount_program.changes.benefit_tiers.1.volume_discount_factor"), commands.ErrMustBePositiveOrZero) + assert.Contains(t, err.Get("proposal_submission.terms.change.update_volume_discount_program.changes.benefit_tiers.0.volume_discount_factors.maker_discount_factor"), commands.ErrMustBePositiveOrZero) + assert.Contains(t, err.Get("proposal_submission.terms.change.update_volume_discount_program.changes.benefit_tiers.0.volume_discount_factors.liquidity_discount_factor"), commands.ErrMustBePositiveOrZero) + assert.Contains(t, err.Get("proposal_submission.terms.change.update_volume_discount_program.changes.benefit_tiers.0.volume_discount_factors.infrastructure_discount_factor"), commands.ErrMustBePositiveOrZero) + assert.Contains(t, err.Get("proposal_submission.terms.change.update_volume_discount_program.changes.benefit_tiers.0.volume_discount_factors.maker_discount_factor"), commands.ErrMustBePositiveOrZero) + assert.Contains(t, err.Get("proposal_submission.terms.change.update_volume_discount_program.changes.benefit_tiers.0.volume_discount_factors.liquidity_discount_factor"), commands.ErrMustBePositiveOrZero) + assert.Contains(t, err.Get("proposal_submission.terms.change.update_volume_discount_program.changes.benefit_tiers.0.volume_discount_factors.infrastructure_discount_factor"), commands.ErrMustBePositiveOrZero) } diff --git a/core/collateral/engine.go b/core/collateral/engine.go index 60a62ce060a..e0e23d32db9 100644 --- a/core/collateral/engine.go +++ b/core/collateral/engine.go @@ -732,7 +732,7 @@ func (e *Engine) transferSpotFees(ctx context.Context, marketID string, assetID for _, transfer := range transfers { req, err := e.getSpotFeeTransferRequest( - transfer, makerFee, infraFee, liquiFee, marketID, assetID) + ctx, transfer, makerFee, infraFee, liquiFee, marketID, assetID) if err != nil { e.log.Error("Failed to build transfer request for event", logging.Error(err)) @@ -763,6 +763,7 @@ func (e *Engine) transferSpotFees(ctx context.Context, marketID string, assetID } func (e *Engine) getSpotFeeTransferRequest( + ctx context.Context, t *types.Transfer, makerFee, infraFee, liquiFee *types.Account, marketID, assetID string, @@ -795,6 +796,13 @@ func (e *Engine) getSpotFeeTransferRequest( return nil, err } + networkTreausry, err := e.GetNetworkTreasuryAccount(assetID) + if err != nil { + return nil, err + } + + buyBackAccount := e.getOrCreateBuyBackFeesAccount(ctx, assetID) + treq := &types.TransferRequest{ Amount: t.Amount.Amount.Clone(), MinAmount: t.Amount.Amount.Clone(), @@ -814,6 +822,20 @@ func (e *Engine) getSpotFeeTransferRequest( treq.FromAccount = []*types.Account{infraFee} treq.ToAccount = []*types.Account{general} return treq, nil + case types.TransferTypeTreasuryPay: + amt := num.Min(treq.Amount, general.Balance.Clone()) + treq.Amount = amt + treq.MinAmount = amt + treq.FromAccount = []*types.Account{general} + treq.ToAccount = []*types.Account{networkTreausry} + return treq, nil + case types.TransferTypeBuyBackFeePay: + amt := num.Min(treq.Amount, general.Balance.Clone()) + treq.Amount = amt + treq.MinAmount = amt + treq.FromAccount = []*types.Account{general} + treq.ToAccount = []*types.Account{buyBackAccount} + return treq, nil case types.TransferTypeLiquidityFeePay: amt := num.Min(treq.Amount, general.Balance.Clone()) treq.Amount = amt @@ -836,6 +858,17 @@ func (e *Engine) getSpotFeeTransferRequest( treq.FromAccount = []*types.Account{makerFee} treq.ToAccount = []*types.Account{general} return treq, nil + case types.TransferTypeHighMakerRebateReceive: + treq.FromAccount = []*types.Account{makerFee} + treq.ToAccount = []*types.Account{general} + return treq, nil + case types.TransferTypeHighMakerRebatePay: + amt := num.Min(treq.Amount, general.Balance.Clone()) + treq.Amount = amt + treq.MinAmount = amt + treq.FromAccount = []*types.Account{general} + treq.ToAccount = []*types.Account{makerFee} + return treq, nil case types.TransferTypeLiquidityFeeAllocate: partyLiquidityFee, err := partyLiquidityFeeAccount() if err != nil { @@ -2234,6 +2267,12 @@ func (e *Engine) getFeeTransferRequest( general *types.Account err error ) + networkTreausry, err := e.GetNetworkTreasuryAccount(assetID) + if err != nil { + return nil, err + } + buyBackAccount := e.getOrCreateBuyBackFeesAccount(ctx, assetID) + if t.Owner == types.NetworkParty { general, err = e.GetMarketInsurancePoolAccount(marketID, assetID) if err != nil { @@ -2295,6 +2334,28 @@ func (e *Engine) getFeeTransferRequest( treq.FromAccount = append(treq.FromAccount, orderMargin) } treq.ToAccount = []*types.Account{infraFee} + case types.TransferTypeTreasuryPay: + margin, err := marginAccount() + if err != nil { + return nil, err + } + orderMargin := orderMarginAccount() + treq.FromAccount = []*types.Account{general, margin} + if orderMargin != nil { + treq.FromAccount = append(treq.FromAccount, orderMargin) + } + treq.ToAccount = []*types.Account{networkTreausry} + case types.TransferTypeBuyBackFeePay: + margin, err := marginAccount() + if err != nil { + return nil, err + } + orderMargin := orderMarginAccount() + treq.FromAccount = []*types.Account{general, margin} + if orderMargin != nil { + treq.FromAccount = append(treq.FromAccount, orderMargin) + } + treq.ToAccount = []*types.Account{buyBackAccount} case types.TransferTypeInfrastructureFeeDistribute: treq.FromAccount = []*types.Account{infraFee} treq.ToAccount = []*types.Account{general} @@ -2326,6 +2387,21 @@ func (e *Engine) getFeeTransferRequest( case types.TransferTypeMakerFeeReceive: treq.FromAccount = []*types.Account{makerFee} treq.ToAccount = []*types.Account{general} + case types.TransferTypeHighMakerRebatePay: + margin, err := marginAccount() + if err != nil { + return nil, err + } + orderMargin := orderMarginAccount() + treq.FromAccount = []*types.Account{general, margin} + if orderMargin != nil { + treq.FromAccount = append(treq.FromAccount, orderMargin) + } + treq.ToAccount = []*types.Account{makerFee} + case types.TransferTypeHighMakerRebateReceive: + treq.FromAccount = []*types.Account{makerFee} + treq.ToAccount = []*types.Account{general} + return treq, nil case types.TransferTypeLiquidityFeeAllocate: partyLiquidityFee, err := partyLiquidityFeeAccount() if err != nil { @@ -4594,6 +4670,26 @@ func (e *Engine) GetNetworkTreasuryAccount(asset string) (*types.Account, error) return e.GetAccountByID(e.accountID(noMarket, systemOwner, asset, types.AccountTypeNetworkTreasury)) } +func (e *Engine) getOrCreateBuyBackFeesAccount(ctx context.Context, asset string) *types.Account { + accID := e.accountID(noMarket, systemOwner, asset, types.AccountTypeBuyBackFees) + acc, err := e.GetAccountByID(accID) + if err == nil { + return acc + } + ntAcc := &types.Account{ + ID: accID, + Asset: asset, + Owner: systemOwner, + Balance: num.UintZero(), + MarketID: noMarket, + Type: types.AccountTypeBuyBackFees, + } + e.accs[accID] = ntAcc + e.addAccountToHashableSlice(ntAcc) + e.broker.Send(events.NewAccountEvent(ctx, *ntAcc)) + return ntAcc +} + func (e *Engine) GetOrCreateNetworkTreasuryAccount(ctx context.Context, asset string) *types.Account { accID := e.accountID(noMarket, systemOwner, asset, types.AccountTypeNetworkTreasury) acc, err := e.GetAccountByID(accID) diff --git a/core/collateral/engine_test.go b/core/collateral/engine_test.go index d418e8a015f..64a589824f5 100644 --- a/core/collateral/engine_test.go +++ b/core/collateral/engine_test.go @@ -574,7 +574,7 @@ func testFeeTransferContinuousOKWithEnoughInGenral(t *testing.T) { defer eng.Finish() party := "myparty" // create party - eng.broker.EXPECT().Send(gomock.Any()).Times(3) + eng.broker.EXPECT().Send(gomock.Any()).Times(4) general, err := eng.CreatePartyGeneralAccount(context.Background(), party, testMarketAsset) require.NoError(t, err) @@ -617,7 +617,7 @@ func testFeeTransferContinuousOKWith0Amount(t *testing.T) { defer eng.Finish() party := "myparty" // create party - eng.broker.EXPECT().Send(gomock.Any()).Times(3) + eng.broker.EXPECT().Send(gomock.Any()).Times(4) general, err := eng.CreatePartyGeneralAccount(context.Background(), party, testMarketAsset) require.NoError(t, err) @@ -662,7 +662,7 @@ func testFeeTransferContinuousOKWithEnoughInMargin(t *testing.T) { defer eng.Finish() party := "myparty" // create party - eng.broker.EXPECT().Send(gomock.Any()).Times(3) + eng.broker.EXPECT().Send(gomock.Any()).Times(4) _, err := eng.CreatePartyGeneralAccount(context.Background(), party, testMarketAsset) require.NoError(t, err) @@ -704,7 +704,7 @@ func testFeeTransferContinuousOKCheckAccountEvents(t *testing.T) { defer eng.Finish() party := "myparty" // create party - eng.broker.EXPECT().Send(gomock.Any()).Times(3) + eng.broker.EXPECT().Send(gomock.Any()).Times(4) _, err := eng.CreatePartyGeneralAccount(context.Background(), party, testMarketAsset) require.NoError(t, err) @@ -776,7 +776,7 @@ func testFeeTransferContinuousOKWithEnoughInGeneralAndMargin(t *testing.T) { defer eng.Finish() party := "myparty" // create party - eng.broker.EXPECT().Send(gomock.Any()).Times(3) + eng.broker.EXPECT().Send(gomock.Any()).Times(4) general, err := eng.CreatePartyGeneralAccount(context.Background(), party, testMarketAsset) require.NoError(t, err) diff --git a/core/events/bus.go b/core/events/bus.go index bc21bac0741..3fa9e0f6c05 100644 --- a/core/events/bus.go +++ b/core/events/bus.go @@ -174,6 +174,10 @@ const ( CancelledOrdersEvent GameScoresEvent AMMPoolEvent + VolumeRebateProgramStartedEvent + VolumeRebateProgramEndedEvent + VolumeRebateProgramUpdatedEvent + VolumeRebateStatsUpdatedEvent ) var ( @@ -277,6 +281,10 @@ var ( eventspb.BusEventType_BUS_EVENT_TYPE_CANCELLED_ORDERS: CancelledOrdersEvent, eventspb.BusEventType_BUS_EVENT_TYPE_GAME_SCORES: GameScoresEvent, eventspb.BusEventType_BUS_EVENT_TYPE_AMM: AMMPoolEvent, + eventspb.BusEventType_BUS_EVENT_TYPE_VOLUME_REBATE_PROGRAM_STARTED: VolumeRebateProgramStartedEvent, + eventspb.BusEventType_BUS_EVENT_TYPE_VOLUME_REBATE_PROGRAM_ENDED: VolumeRebateProgramEndedEvent, + eventspb.BusEventType_BUS_EVENT_TYPE_VOLUME_REBATE_PROGRAM_UPDATED: VolumeRebateProgramUpdatedEvent, + eventspb.BusEventType_BUS_EVENT_TYPE_VOLUME_REBATE_STATS_UPDATED: VolumeRebateStatsUpdatedEvent, // If adding a type here, please also add it to datanode/broker/convert.go } @@ -371,6 +379,11 @@ var ( CancelledOrdersEvent: eventspb.BusEventType_BUS_EVENT_TYPE_CANCELLED_ORDERS, GameScoresEvent: eventspb.BusEventType_BUS_EVENT_TYPE_GAME_SCORES, AMMPoolEvent: eventspb.BusEventType_BUS_EVENT_TYPE_AMM, + VolumeRebateProgramStartedEvent: eventspb.BusEventType_BUS_EVENT_TYPE_VOLUME_REBATE_PROGRAM_STARTED, + VolumeRebateProgramEndedEvent: eventspb.BusEventType_BUS_EVENT_TYPE_VOLUME_REBATE_PROGRAM_ENDED, + VolumeRebateProgramUpdatedEvent: eventspb.BusEventType_BUS_EVENT_TYPE_VOLUME_REBATE_PROGRAM_UPDATED, + VolumeRebateStatsUpdatedEvent: eventspb.BusEventType_BUS_EVENT_TYPE_VOLUME_REBATE_STATS_UPDATED, + // If adding a type here, please also add it to datanode/broker/convert.go } @@ -464,6 +477,10 @@ var ( CancelledOrdersEvent: "CancelledOrdersEvent", GameScoresEvent: "GameScoresEvent", AMMPoolEvent: "AMMPoolEvent", + VolumeRebateProgramStartedEvent: "VolumeRebateProgramStartedEvent", + VolumeRebateProgramEndedEvent: "VolumeRebateProgramEndedEvent", + VolumeRebateProgramUpdatedEvent: "VolumeRebateProgramUpdatedEvent", + VolumeRebateStatsUpdatedEvent: "VolumeRebateStatsUpdatedEvent", } ) diff --git a/core/events/referral_set.go b/core/events/referral_set.go index 7440065ec9c..8b7f67bb124 100644 --- a/core/events/referral_set.go +++ b/core/events/referral_set.go @@ -71,15 +71,13 @@ type ReferralSetStatsUpdated struct { func (t ReferralSetStatsUpdated) Unwrap() *types.ReferralSetStats { volume, _ := num.UintFromString(t.e.ReferralSetRunningNotionalTakerVolume, 10) stats := map[types.PartyID]*types.RefereeStats{} - rewardFactor, _ := num.DecimalFromString(t.e.RewardFactor) rewardsMultiplier, _ := num.DecimalFromString(t.e.RewardsMultiplier) - rewardsFactorMultiplier, _ := num.DecimalFromString(t.e.RewardsFactorMultiplier) - + rewardsFactorsMultiplier := types.FactorsFromRewardFactorsWithDefault(t.e.RewardFactorsMultiplier, t.e.RewardsFactorMultiplier) + rewardFactors := types.FactorsFromRewardFactorsWithDefault(t.e.RewardFactors, t.e.RewardFactor) for _, stat := range t.e.RefereesStats { - discountFactor, _ := num.DecimalFromString(stat.DiscountFactor) - + discountFactors := types.FactorsFromDiscountFactorsWithDefault(stat.DiscountFactors, stat.DiscountFactor) stats[types.PartyID(stat.PartyId)] = &types.RefereeStats{ - DiscountFactor: discountFactor, + DiscountFactors: discountFactors, } } @@ -89,9 +87,9 @@ func (t ReferralSetStatsUpdated) Unwrap() *types.ReferralSetStats { WasEligible: t.e.WasEligible, ReferralSetRunningVolume: volume, RefereesStats: stats, - RewardFactor: rewardFactor, + RewardFactors: rewardFactors, RewardsMultiplier: rewardsMultiplier, - RewardsFactorMultiplier: rewardsFactorMultiplier, + RewardsFactorsMultiplier: rewardsFactorsMultiplier, } } @@ -113,7 +111,7 @@ func NewReferralSetStatsUpdatedEvent(ctx context.Context, update *types.Referral for partyID, stat := range update.RefereesStats { refereesStats = append(refereesStats, &eventspb.RefereeStats{ PartyId: string(partyID), - DiscountFactor: stat.DiscountFactor.String(), + DiscountFactors: stat.DiscountFactors.IntoDiscountFactorsProto(), EpochNotionalTakerVolume: stat.TakerVolume.String(), }) } @@ -131,9 +129,9 @@ func NewReferralSetStatsUpdatedEvent(ctx context.Context, update *types.Referral ReferralSetRunningNotionalTakerVolume: update.ReferralSetRunningVolume.String(), ReferrerTakerVolume: update.ReferrerTakerVolume.String(), RefereesStats: refereesStats, - RewardFactor: update.RewardFactor.String(), + RewardFactors: update.RewardFactors.IntoRewardFactorsProto(), RewardsMultiplier: update.RewardsMultiplier.String(), - RewardsFactorMultiplier: update.RewardsFactorMultiplier.String(), + RewardFactorsMultiplier: update.RewardsFactorsMultiplier.IntoRewardFactorsProto(), }, } } diff --git a/core/events/volume_rebate_program.go b/core/events/volume_rebate_program.go new file mode 100644 index 00000000000..1ff9f86663a --- /dev/null +++ b/core/events/volume_rebate_program.go @@ -0,0 +1,172 @@ +// Copyright (C) 2023 Gobalsky Labs Limited +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +package events + +import ( + "context" + "time" + + "code.vegaprotocol.io/vega/core/types" + "code.vegaprotocol.io/vega/libs/ptr" + eventspb "code.vegaprotocol.io/vega/protos/vega/events/v1" +) + +type VolumeRebateProgramStarted struct { + *Base + e *eventspb.VolumeRebateProgramStarted +} + +func (v *VolumeRebateProgramStarted) GetVolumeRebateProgramStarted() *eventspb.VolumeRebateProgramStarted { + return v.e +} + +func (t *VolumeRebateProgramStarted) StreamMessage() *eventspb.BusEvent { + busEvent := newBusEventFromBase(t.Base) + busEvent.Event = &eventspb.BusEvent_VolumeRebateProgramStarted{ + VolumeRebateProgramStarted: t.e, + } + + return busEvent +} + +func NewVolumeRebateProgramStartedEvent(ctx context.Context, p *types.VolumeRebateProgram, epochTime time.Time, epoch uint64) *VolumeRebateProgramStarted { + return &VolumeRebateProgramStarted{ + Base: newBase(ctx, VolumeRebateProgramStartedEvent), + e: &eventspb.VolumeRebateProgramStarted{ + Program: p.IntoProto(), + StartedAt: epochTime.UnixNano(), + AtEpoch: epoch, + }, + } +} + +func VolumeRebateProgramStartedEventFromStream(ctx context.Context, be *eventspb.BusEvent) *VolumeRebateProgramStarted { + return &VolumeRebateProgramStarted{ + Base: newBaseFromBusEvent(ctx, VolumeRebateProgramStartedEvent, be), + e: be.GetVolumeRebateProgramStarted(), + } +} + +type VolumeRebateProgramUpdated struct { + *Base + e *eventspb.VolumeRebateProgramUpdated +} + +func (v *VolumeRebateProgramUpdated) GetVolumeRebateProgramUpdated() *eventspb.VolumeRebateProgramUpdated { + return v.e +} + +func (t *VolumeRebateProgramUpdated) StreamMessage() *eventspb.BusEvent { + busEvent := newBusEventFromBase(t.Base) + busEvent.Event = &eventspb.BusEvent_VolumeRebateProgramUpdated{ + VolumeRebateProgramUpdated: t.e, + } + + return busEvent +} + +func NewVolumeRebateProgramUpdatedEvent(ctx context.Context, p *types.VolumeRebateProgram, epochTime time.Time, epoch uint64) *VolumeRebateProgramUpdated { + return &VolumeRebateProgramUpdated{ + Base: newBase(ctx, VolumeRebateProgramUpdatedEvent), + e: &eventspb.VolumeRebateProgramUpdated{ + Program: p.IntoProto(), + UpdatedAt: epochTime.UnixNano(), + AtEpoch: epoch, + }, + } +} + +func VolumeRebateProgramUpdatedEventFromStream(ctx context.Context, be *eventspb.BusEvent) *VolumeRebateProgramUpdated { + return &VolumeRebateProgramUpdated{ + Base: newBaseFromBusEvent(ctx, VolumeRebateProgramUpdatedEvent, be), + e: be.GetVolumeRebateProgramUpdated(), + } +} + +type VolumeRebateProgramEnded struct { + *Base + e *eventspb.VolumeRebateProgramEnded +} + +func (v *VolumeRebateProgramEnded) GetVolumeRebateProgramEnded() *eventspb.VolumeRebateProgramEnded { + return v.e +} + +func (t *VolumeRebateProgramEnded) StreamMessage() *eventspb.BusEvent { + busEvent := newBusEventFromBase(t.Base) + busEvent.Event = &eventspb.BusEvent_VolumeRebateProgramEnded{ + VolumeRebateProgramEnded: t.e, + } + + return busEvent +} + +func NewVolumeRebateProgramEndedEvent(ctx context.Context, version uint64, id string, epochTime time.Time, epoch uint64) *VolumeRebateProgramEnded { + return &VolumeRebateProgramEnded{ + Base: newBase(ctx, VolumeRebateProgramEndedEvent), + e: &eventspb.VolumeRebateProgramEnded{ + Version: version, + Id: id, + EndedAt: epochTime.UnixNano(), + AtEpoch: epoch, + }, + } +} + +func VolumeRebateProgramEndedEventFromStream(ctx context.Context, be *eventspb.BusEvent) *VolumeRebateProgramEnded { + return &VolumeRebateProgramEnded{ + Base: newBaseFromBusEvent(ctx, VolumeRebateProgramEndedEvent, be), + e: be.GetVolumeRebateProgramEnded(), + } +} + +type VolumeRebateStatsUpdated struct { + *Base + vdsu eventspb.VolumeRebateStatsUpdated +} + +func NewVolumeRebateStatsUpdatedEvent(ctx context.Context, vdsu *eventspb.VolumeRebateStatsUpdated) *VolumeRebateStatsUpdated { + order := &VolumeRebateStatsUpdated{ + Base: newBase(ctx, VolumeRebateStatsUpdatedEvent), + vdsu: *vdsu, + } + return order +} + +func (p *VolumeRebateStatsUpdated) VolumeRebateStatsUpdated() *eventspb.VolumeRebateStatsUpdated { + return ptr.From(p.vdsu) +} + +func (p VolumeRebateStatsUpdated) Proto() eventspb.VolumeRebateStatsUpdated { + return p.vdsu +} + +func (p VolumeRebateStatsUpdated) StreamMessage() *eventspb.BusEvent { + busEvent := newBusEventFromBase(p.Base) + busEvent.Event = &eventspb.BusEvent_VolumeRebateStatsUpdated{ + VolumeRebateStatsUpdated: ptr.From(p.vdsu), + } + + return busEvent +} + +func VolumeRebateStatsUpdatedEventFromStream(ctx context.Context, be *eventspb.BusEvent) *VolumeRebateStatsUpdated { + order := &VolumeRebateStatsUpdated{ + Base: newBaseFromBusEvent(ctx, VolumeRebateStatsUpdatedEvent, be), + vdsu: ptr.UnBox(be.GetVolumeRebateStatsUpdated()), + } + return order +} diff --git a/core/execution/amm/engine_test.go b/core/execution/amm/engine_test.go index 77c0dd7b426..e545c75638b 100644 --- a/core/execution/amm/engine_test.go +++ b/core/execution/amm/engine_test.go @@ -794,7 +794,7 @@ func getTestEngineWithFactors(t *testing.T, priceFactor, positionFactor num.Deci teams := cmocks.NewMockTeams(ctrl) balanceChecker := cmocks.NewMockAccountBalanceChecker(ctrl) - mat := common.NewMarketActivityTracker(logging.NewTestLogger(), teams, balanceChecker, broker) + mat := common.NewMarketActivityTracker(logging.NewTestLogger(), teams, balanceChecker, broker, col) parties := cmocks.NewMockParties(ctrl) parties.EXPECT().AssignDeriveKey(gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes() diff --git a/core/execution/common/interfaces.go b/core/execution/common/interfaces.go index 7ac1b714e3a..a5ecf247111 100644 --- a/core/execution/common/interfaces.go +++ b/core/execution/common/interfaces.go @@ -374,6 +374,8 @@ type CommonMarket interface { OnMarketProbabilityOfTradingTauScalingUpdate(context.Context, num.Decimal) OnMarketValueWindowLengthUpdate(time.Duration) OnFeeFactorsInfrastructureFeeUpdate(context.Context, num.Decimal) + OnFeeFactorsTreasuryFeeUpdate(context.Context, num.Decimal) + OnFeeFactorsBuyBackFeeUpdate(context.Context, num.Decimal) OnFeeFactorsMakerFeeUpdate(context.Context, num.Decimal) OnMarkPriceUpdateMaximumFrequency(context.Context, time.Duration) OnMarketAuctionMinimumDurationUpdate(context.Context, time.Duration) diff --git a/core/execution/common/liquidity_provision_test.go b/core/execution/common/liquidity_provision_test.go index 779311dfc72..2349dfa782c 100644 --- a/core/execution/common/liquidity_provision_test.go +++ b/core/execution/common/liquidity_provision_test.go @@ -83,7 +83,7 @@ func newMarketLiquidity(t *testing.T) *marketLiquidityTest { teams := mocks.NewMockTeams(ctrl) bc := mocks.NewMockAccountBalanceChecker(ctrl) - marketTracker := common.NewMarketActivityTracker(logging.NewTestLogger(), teams, bc, broker) + marketTracker := common.NewMarketActivityTracker(logging.NewTestLogger(), teams, bc, broker, collateralEngine) epochEngine.NotifyOnEpoch(marketTracker.OnEpochEvent, marketTracker.OnEpochRestore) amm := ammcmocks.NewMockAMM(ctrl) diff --git a/core/execution/common/market_activity_tracker.go b/core/execution/common/market_activity_tracker.go index b3848bb0c2d..2cc97fe1748 100644 --- a/core/execution/common/market_activity_tracker.go +++ b/core/execution/common/market_activity_tracker.go @@ -47,6 +47,10 @@ var ( dScalingFactor = num.DecimalFromInt64(scalingFactor) ) +type QuantumGetter interface { + GetAssetQuantum(asset string) (num.Decimal, error) +} + type twPosition struct { position uint64 // abs last recorded position t time.Time // time of last recorded position @@ -67,6 +71,8 @@ type marketTracker struct { lpFees map[string]*num.Uint infraFees map[string]*num.Uint lpPaidFees map[string]*num.Uint + buybackFeesPaid map[string]*num.Uint + treasuryFeesPaid map[string]*num.Uint totalMakerFeesReceived *num.Uint totalMakerFeesPaid *num.Uint @@ -105,6 +111,7 @@ type MarketActivityTracker struct { teams Teams balanceChecker AccountBalanceChecker eligibilityChecker EligibilityChecker + collateral QuantumGetter currentEpoch uint64 epochStartTime time.Time @@ -120,7 +127,7 @@ type MarketActivityTracker struct { } // NewMarketActivityTracker instantiates the fees tracker. -func NewMarketActivityTracker(log *logging.Logger, teams Teams, balanceChecker AccountBalanceChecker, broker Broker) *MarketActivityTracker { +func NewMarketActivityTracker(log *logging.Logger, teams Teams, balanceChecker AccountBalanceChecker, broker Broker, collateral QuantumGetter) *MarketActivityTracker { mat := &MarketActivityTracker{ log: log, balanceChecker: balanceChecker, @@ -132,6 +139,7 @@ func NewMarketActivityTracker(log *logging.Logger, teams Teams, balanceChecker A ss: &snapshotState{}, takerFeesPaidInEpoch: []map[string]map[string]map[string]*num.Uint{}, broker: broker, + collateral: collateral, } return mat @@ -187,6 +195,8 @@ func (mat *MarketActivityTracker) MarketProposed(asset, marketID, proposer strin lpFees: map[string]*num.Uint{}, infraFees: map[string]*num.Uint{}, lpPaidFees: map[string]*num.Uint{}, + buybackFeesPaid: map[string]*num.Uint{}, + treasuryFeesPaid: map[string]*num.Uint{}, totalMakerFeesReceived: num.UintZero(), totalMakerFeesPaid: num.UintZero(), totalLpFees: num.UintZero(), @@ -467,7 +477,7 @@ func (mat *MarketActivityTracker) RemoveMarket(asset, marketID string) { func (mt *marketTracker) aggregatedFees() map[string]*num.Uint { totalFees := map[string]*num.Uint{} - fees := []map[string]*num.Uint{mt.infraFees, mt.lpPaidFees, mt.makerFeesPaid} + fees := []map[string]*num.Uint{mt.infraFees, mt.lpPaidFees, mt.makerFeesPaid, mt.buybackFeesPaid, mt.treasuryFeesPaid} for _, fee := range fees { for party, paid := range fee { if _, ok := totalFees[party]; !ok { @@ -517,6 +527,63 @@ func (mat *MarketActivityTracker) clearDeletedMarkets() { } } +func (mat *MarketActivityTracker) CalculateTotalMakerContributionInQuantum(windowSize int) (map[string]*num.Uint, map[string]num.Decimal) { + m := map[string]*num.Uint{} + total := num.UintZero() + for ast, trackers := range mat.assetToMarketTrackers { + quantum, err := mat.collateral.GetAssetQuantum(ast) + if err != nil { + continue + } + for _, trckr := range trackers { + for i := 0; i < windowSize; i++ { + idx := len(trckr.epochMakerFeesReceived) - i - 1 + if idx < 0 { + break + } + partyFees := trckr.epochMakerFeesReceived[len(trckr.epochMakerFeesReceived)-i-1] + for party, fees := range partyFees { + if _, ok := m[party]; !ok { + m[party] = num.UintZero() + } + feesInQunatum, overflow := num.UintFromDecimal(fees.ToDecimal().Div(quantum)) + if overflow { + continue + } + m[party].AddSum(feesInQunatum) + total.AddSum(feesInQunatum) + } + } + } + } + if total.IsZero() { + return m, map[string]decimal.Decimal{} + } + totalFrac := num.DecimalZero() + fractions := []*types.PartyContributionScore{} + for p, f := range m { + frac := f.ToDecimal().Div(total.ToDecimal()) + fractions = append(fractions, &types.PartyContributionScore{Party: p, Score: frac}) + totalFrac = totalFrac.Add(frac) + } + capAtOne(fractions, totalFrac) + fracMap := make(map[string]num.Decimal, len(fractions)) + for _, partyFraction := range fractions { + fracMap[partyFraction.Party] = partyFraction.Score + } + return m, fracMap +} + +func capAtOne(partyFractions []*types.PartyContributionScore, total num.Decimal) { + if total.LessThanOrEqual(num.DecimalOne()) { + return + } + + sort.SliceStable(partyFractions, func(i, j int) bool { return partyFractions[i].Score.GreaterThan(partyFractions[j].Score) }) + delta := total.Sub(num.DecimalFromInt64(1)) + partyFractions[0].Score = num.MaxD(num.DecimalZero(), partyFractions[0].Score.Sub(delta)) +} + func (mt *marketTracker) calcFeesAtMilestone() { mt.epochMakerFeesReceived = append(mt.epochMakerFeesReceived, mt.makerFeesReceived) mt.epochMakerFeesPaid = append(mt.epochMakerFeesPaid, mt.makerFeesPaid) @@ -544,6 +611,8 @@ func (mt *marketTracker) clearFeeActivity() { mt.lpFees = map[string]*num.Uint{} mt.infraFees = map[string]*num.Uint{} mt.lpPaidFees = map[string]*num.Uint{} + mt.treasuryFeesPaid = map[string]*num.Uint{} + mt.buybackFeesPaid = map[string]*num.Uint{} mt.epochTotalMakerFeesReceived = append(mt.epochTotalMakerFeesReceived, mt.totalMakerFeesReceived) mt.epochTotalMakerFeesPaid = append(mt.epochTotalMakerFeesPaid, mt.totalMakerFeesPaid) @@ -573,6 +642,13 @@ func (mat *MarketActivityTracker) UpdateFeesFromTransfers(asset, market string, mat.addFees(mt.infraFees, t.Owner, t.Amount.Amount, num.UintZero()) case types.TransferTypeLiquidityFeePay: mat.addFees(mt.lpPaidFees, t.Owner, t.Amount.Amount, num.UintZero()) + case types.TransferTypeBuyBackFeePay: + mat.addFees(mt.buybackFeesPaid, t.Owner, t.Amount.Amount, num.UintZero()) + case types.TransferTypeTreasuryPay: + mat.addFees(mt.treasuryFeesPaid, t.Owner, t.Amount.Amount, num.UintZero()) + case types.TransferTypeHighMakerRebateReceive: + // we count high maker fee receive as maker fees for that purpose. + mat.addFees(mt.makerFeesReceived, t.Owner, t.Amount.Amount, mt.totalMakerFeesReceived) default: } } diff --git a/core/execution/common/market_activity_tracker_internal_test.go b/core/execution/common/market_activity_tracker_internal_test.go index c994d6fc66d..638f4aa9240 100644 --- a/core/execution/common/market_activity_tracker_internal_test.go +++ b/core/execution/common/market_activity_tracker_internal_test.go @@ -350,7 +350,8 @@ func TestCalculateMetricForIndividualsAveNotional(t *testing.T) { broker := bmocks.NewMockBroker(ctrl) broker.EXPECT().SendBatch(gomock.Any()).AnyTimes() broker.EXPECT().Send(gomock.Any()).AnyTimes() - tracker := NewMarketActivityTracker(logging.NewTestLogger(), teams, balanceChecker, broker) + collateralService := mocks.NewMockCollateral(ctrl) + tracker := NewMarketActivityTracker(logging.NewTestLogger(), teams, balanceChecker, broker, collateralService) epochService.NotifyOnEpoch(tracker.OnEpochEvent, tracker.OnEpochRestore) tracker.SetEligibilityChecker(&DummyEligibilityChecker{}) epochService.target(context.Background(), types.Epoch{Seq: 1, Action: vgproto.EpochAction_EPOCH_ACTION_START, StartTime: time.Time{}}) @@ -552,7 +553,8 @@ func TestCalculateMetricForPartyAveNotional(t *testing.T) { balanceChecker := mocks.NewMockAccountBalanceChecker(ctrl) broker := bmocks.NewMockBroker(ctrl) broker.EXPECT().SendBatch(gomock.Any()).AnyTimes() - tracker := NewMarketActivityTracker(logging.NewTestLogger(), teams, balanceChecker, broker) + collateralService := mocks.NewMockCollateral(ctrl) + tracker := NewMarketActivityTracker(logging.NewTestLogger(), teams, balanceChecker, broker, collateralService) epochService.NotifyOnEpoch(tracker.OnEpochEvent, tracker.OnEpochRestore) tracker.SetEligibilityChecker(&DummyEligibilityChecker{}) epochService.target(context.Background(), types.Epoch{Seq: 1, Action: vgproto.EpochAction_EPOCH_ACTION_START, StartTime: time.Time{}}) @@ -790,7 +792,8 @@ func TestCalculateMetricForIndividualReturnVolatility(t *testing.T) { broker := bmocks.NewMockBroker(ctrl) broker.EXPECT().Send(gomock.Any()).AnyTimes() broker.EXPECT().SendBatch(gomock.Any()).AnyTimes() - tracker := NewMarketActivityTracker(logging.NewTestLogger(), teams, balanceChecker, broker) + collateralService := mocks.NewMockCollateral(ctrl) + tracker := NewMarketActivityTracker(logging.NewTestLogger(), teams, balanceChecker, broker, collateralService) epochService.NotifyOnEpoch(tracker.OnEpochEvent, tracker.OnEpochRestore) tracker.SetEligibilityChecker(&DummyEligibilityChecker{}) epochService.target(context.Background(), types.Epoch{Seq: 1, Action: vgproto.EpochAction_EPOCH_ACTION_START, StartTime: time.Time{}}) @@ -996,7 +999,8 @@ func TestCalculateMetricForIndividualsRelativeReturn(t *testing.T) { broker := bmocks.NewMockBroker(ctrl) broker.EXPECT().Send(gomock.Any()).AnyTimes() broker.EXPECT().SendBatch(gomock.Any()).AnyTimes() - tracker := NewMarketActivityTracker(logging.NewTestLogger(), teams, balanceChecker, broker) + collateralService := mocks.NewMockCollateral(ctrl) + tracker := NewMarketActivityTracker(logging.NewTestLogger(), teams, balanceChecker, broker, collateralService) epochService.NotifyOnEpoch(tracker.OnEpochEvent, tracker.OnEpochRestore) tracker.SetEligibilityChecker(&DummyEligibilityChecker{}) epochService.target(context.Background(), types.Epoch{Seq: 1, Action: vgproto.EpochAction_EPOCH_ACTION_START, StartTime: time.Time{}}) @@ -1217,7 +1221,8 @@ func TestCalculateMetricForPartyRelativeReturn(t *testing.T) { balanceChecker := mocks.NewMockAccountBalanceChecker(ctrl) broker := bmocks.NewMockBroker(ctrl) broker.EXPECT().SendBatch(gomock.Any()).AnyTimes() - tracker := NewMarketActivityTracker(logging.NewTestLogger(), teams, balanceChecker, broker) + collateralService := mocks.NewMockCollateral(ctrl) + tracker := NewMarketActivityTracker(logging.NewTestLogger(), teams, balanceChecker, broker, collateralService) epochService.NotifyOnEpoch(tracker.OnEpochEvent, tracker.OnEpochRestore) tracker.SetEligibilityChecker(&DummyEligibilityChecker{}) epochService.target(context.Background(), types.Epoch{Seq: 1, Action: vgproto.EpochAction_EPOCH_ACTION_START, StartTime: time.Time{}}) @@ -1474,7 +1479,8 @@ func TestCalculateMetricForParty(t *testing.T) { teams := mocks.NewMockTeams(ctrl) balanceChecker := mocks.NewMockAccountBalanceChecker(ctrl) broker := bmocks.NewMockBroker(ctrl) - tracker := NewMarketActivityTracker(logging.NewTestLogger(), teams, balanceChecker, broker) + collateralService := mocks.NewMockCollateral(ctrl) + tracker := NewMarketActivityTracker(logging.NewTestLogger(), teams, balanceChecker, broker, collateralService) epochService.NotifyOnEpoch(tracker.OnEpochEvent, tracker.OnEpochRestore) tracker.SetEligibilityChecker(&DummyEligibilityChecker{}) epochService.target(context.Background(), types.Epoch{Seq: 1, Action: vgproto.EpochAction_EPOCH_ACTION_START, StartTime: time.Time{}}) @@ -1563,6 +1569,12 @@ func (e *DummyEpochEngine) NotifyOnEpoch(f func(context.Context, types.Epoch), _ e.target = f } +type DummyCollateralEngine struct{} + +func (e DummyCollateralEngine) GetAssetQuantum(asset string) (num.Decimal, error) { + return num.DecimalOne(), nil +} + type DummyEligibilityChecker struct{} func (e *DummyEligibilityChecker) IsEligibleForProposerBonus(marketID string, volumeTraded *num.Uint) bool { diff --git a/core/execution/common/market_activity_tracker_snapshot.go b/core/execution/common/market_activity_tracker_snapshot.go index 38e92fc88f8..4a95fc4f69c 100644 --- a/core/execution/common/market_activity_tracker_snapshot.go +++ b/core/execution/common/market_activity_tracker_snapshot.go @@ -295,6 +295,8 @@ func (mt *marketTracker) IntoProto(market string) *checkpoint.MarketActivityTrac LpFees: marketFeesToProto(mt.lpFees), InfraFees: marketFeesToProto(mt.infraFees), LpPaidFees: marketFeesToProto(mt.lpPaidFees), + BuyBackFees: marketFeesToProto(mt.buybackFeesPaid), + TreasuryFees: marketFeesToProto(mt.treasuryFeesPaid), Proposer: mt.proposer, BonusPaid: paid, ValueTraded: mt.valueTraded.String(), @@ -394,6 +396,8 @@ func marketTrackerFromProto(tracker *checkpoint.MarketActivityTracker) *marketTr makerFeesReceived: map[string]*num.Uint{}, makerFeesPaid: map[string]*num.Uint{}, lpFees: map[string]*num.Uint{}, + buybackFeesPaid: map[string]*num.Uint{}, + treasuryFeesPaid: map[string]*num.Uint{}, infraFees: map[string]*num.Uint{}, lpPaidFees: map[string]*num.Uint{}, totalMakerFeesReceived: num.UintZero(), @@ -467,6 +471,22 @@ func marketTrackerFromProto(tracker *checkpoint.MarketActivityTracker) *marketTr } } + if len(tracker.BuyBackFees) > 0 { + for _, mf := range tracker.BuyBackFees { + fee, _ := num.UintFromString(mf.Fee, 10) + mft.buybackFeesPaid[mf.Party] = fee + mft.allPartiesCache[mf.Party] = struct{}{} + } + } + + if len(tracker.TreasuryFees) > 0 { + for _, mf := range tracker.TreasuryFees { + fee, _ := num.UintFromString(mf.Fee, 10) + mft.treasuryFeesPaid[mf.Party] = fee + mft.allPartiesCache[mf.Party] = struct{}{} + } + } + if len(tracker.LpPaidFees) > 0 { for _, mf := range tracker.LpPaidFees { fee, _ := num.UintFromString(mf.Fee, 10) diff --git a/core/execution/common/market_activity_tracker_test.go b/core/execution/common/market_activity_tracker_test.go index 6b0a2c778b6..33afecc7b9a 100644 --- a/core/execution/common/market_activity_tracker_test.go +++ b/core/execution/common/market_activity_tracker_test.go @@ -62,7 +62,8 @@ func TestMarketTracker(t *testing.T) { balanceChecker := mocks.NewMockAccountBalanceChecker(ctrl) broker := bmocks.NewMockBroker(ctrl) - tracker := common.NewMarketActivityTracker(logging.NewTestLogger(), teams, balanceChecker, broker) + collateralService := mocks.NewMockCollateral(ctrl) + tracker := common.NewMarketActivityTracker(logging.NewTestLogger(), teams, balanceChecker, broker, collateralService) tracker.SetEligibilityChecker(&EligibilityChecker{}) tracker.MarketProposed("asset1", "market1", "me") @@ -140,7 +141,7 @@ func TestMarketTracker(t *testing.T) { teams2 := mocks.NewMockTeams(ctrl) balanceChecker2 := mocks.NewMockAccountBalanceChecker(ctrl) broker = bmocks.NewMockBroker(ctrl) - trackerLoad := common.NewMarketActivityTracker(logging.NewTestLogger(), teams2, balanceChecker2, broker) + trackerLoad := common.NewMarketActivityTracker(logging.NewTestLogger(), teams2, balanceChecker2, broker, collateralService) pl := snapshotpb.Payload{} require.NoError(t, proto.Unmarshal(state1, &pl)) @@ -160,7 +161,8 @@ func TestRemoveMarket(t *testing.T) { balanceChecker := mocks.NewMockAccountBalanceChecker(ctrl) broker := bmocks.NewMockBroker(ctrl) - tracker := common.NewMarketActivityTracker(logging.NewTestLogger(), teams, balanceChecker, broker) + collateralService := mocks.NewMockCollateral(ctrl) + tracker := common.NewMarketActivityTracker(logging.NewTestLogger(), teams, balanceChecker, broker, collateralService) epochService.NotifyOnEpoch(tracker.OnEpochEvent, tracker.OnEpochRestore) tracker.SetEligibilityChecker(&EligibilityChecker{}) tracker.MarketProposed("asset1", "market1", "me") @@ -187,7 +189,8 @@ func TestAddRemoveAMM(t *testing.T) { broker := bmocks.NewMockBroker(ctrl) balanceChecker := mocks.NewMockAccountBalanceChecker(ctrl) - tracker := common.NewMarketActivityTracker(logging.NewTestLogger(), teams, balanceChecker, broker) + collateralService := mocks.NewMockCollateral(ctrl) + tracker := common.NewMarketActivityTracker(logging.NewTestLogger(), teams, balanceChecker, broker, collateralService) epochService.NotifyOnEpoch(tracker.OnEpochEvent, tracker.OnEpochRestore) tracker.SetEligibilityChecker(&EligibilityChecker{}) tracker.MarketProposed("asset1", "market1", "me") @@ -208,6 +211,105 @@ func TestAddRemoveAMM(t *testing.T) { require.Equal(t, map[string]struct{}{}, tracker.GetAllAMMParties("asset1", nil)) } +func TestCalculateTotalMakerContributionInQuantum(t *testing.T) { + // ctx := context.Background() + epochService := &TestEpochEngine{} + ctrl := gomock.NewController(t) + teams := mocks.NewMockTeams(ctrl) + balanceChecker := mocks.NewMockAccountBalanceChecker(ctrl) + balanceChecker.EXPECT().GetAvailableBalance(gomock.Any()).Return(num.UintZero(), nil).AnyTimes() + broker := bmocks.NewMockBroker(ctrl) + broker.EXPECT().SendBatch(gomock.Any()).AnyTimes() + broker.EXPECT().Send(gomock.Any()).AnyTimes() + collateralService := mocks.NewMockCollateral(ctrl) + tracker := common.NewMarketActivityTracker(logging.NewTestLogger(), teams, balanceChecker, broker, collateralService) + epochService.NotifyOnEpoch(tracker.OnEpochEvent, tracker.OnEpochRestore) + tracker.SetEligibilityChecker(&EligibilityChecker{}) + tracker.MarketProposed("asset1", "market1", "me") + tracker.MarketProposed("asset1", "market2", "me2") + tracker.MarketProposed("asset1", "market4", "me4") + tracker.MarketProposed("asset2", "market3", "me3") + + collateralService.EXPECT().GetAssetQuantum("asset1").Return(num.DecimalOne(), nil).AnyTimes() + collateralService.EXPECT().GetAssetQuantum("asset2").Return(num.DecimalTwo(), nil).AnyTimes() + // no fees generated expect empty slice + epochService.target(context.Background(), types.Epoch{Seq: 1, Action: vgproto.EpochAction_EPOCH_ACTION_START}) + epochService.target(context.Background(), types.Epoch{Seq: 1, Action: vgproto.EpochAction_EPOCH_ACTION_END}) + + // expect no makers fee contributors + contribution, fraction := tracker.CalculateTotalMakerContributionInQuantum(1) + require.Equal(t, 0, len(fraction)) + require.Equal(t, 0, len(contribution)) + + contribution, fraction = tracker.CalculateTotalMakerContributionInQuantum(2) + require.Equal(t, 0, len(fraction)) + require.Equal(t, 0, len(contribution)) + + epochService.target(context.Background(), types.Epoch{Seq: 2, Action: vgproto.EpochAction_EPOCH_ACTION_START}) + + // now lets get some fees paid + // update with a few transfers + transfersM1 := []*types.Transfer{ + {Owner: "party1", Type: types.TransferTypeMakerFeeReceive, Amount: &types.FinancialAmount{Asset: "asset1", Amount: num.NewUint(100)}}, + {Owner: "party1", Type: types.TransferTypeMakerFeeReceive, Amount: &types.FinancialAmount{Asset: "asset1", Amount: num.NewUint(400)}}, + {Owner: "party2", Type: types.TransferTypeMakerFeeReceive, Amount: &types.FinancialAmount{Asset: "asset1", Amount: num.NewUint(900)}}, + {Owner: "party2", Type: types.TransferTypeMakerFeeReceive, Amount: &types.FinancialAmount{Asset: "asset1", Amount: num.NewUint(600)}}, + } + tracker.UpdateFeesFromTransfers("asset1", "market1", transfersM1) + + transfersM2 := []*types.Transfer{ + {Owner: "party1", Type: types.TransferTypeMakerFeeReceive, Amount: &types.FinancialAmount{Asset: "asset1", Amount: num.NewUint(500)}}, + } + tracker.UpdateFeesFromTransfers("asset1", "market2", transfersM2) + + transfersM3 := []*types.Transfer{ + {Owner: "party2", Type: types.TransferTypeMakerFeeReceive, Amount: &types.FinancialAmount{Asset: "asset2", Amount: num.NewUint(450)}}, + } + tracker.UpdateFeesFromTransfers("asset2", "market3", transfersM3) + epochService.target(context.Background(), types.Epoch{Seq: 2, Action: vgproto.EpochAction_EPOCH_ACTION_END}) + + contribution, fraction = tracker.CalculateTotalMakerContributionInQuantum(1) + // party 1 received in total: + // asset1 : 1000 + // party 2 received in total: + // asset1 : 1500 + // asset2: 450/2 = 225 + require.Equal(t, 2, len(fraction)) + require.Equal(t, 2, len(contribution)) + require.Equal(t, "1000", contribution["party1"].String()) + require.Equal(t, "1725", contribution["party2"].String()) + require.Equal(t, "0.3669724770642202", fraction["party1"].String()) + require.Equal(t, "0.6330275229357798", fraction["party2"].String()) + + epochService.target(context.Background(), types.Epoch{Seq: 3, Action: vgproto.EpochAction_EPOCH_ACTION_START}) + + transfersM4 := []*types.Transfer{ + {Owner: "party1", Type: types.TransferTypeMakerFeeReceive, Amount: &types.FinancialAmount{Asset: "asset1", Amount: num.NewUint(2000)}}, + } + tracker.UpdateFeesFromTransfers("asset1", "market2", transfersM4) + + transfersM5 := []*types.Transfer{ + {Owner: "party2", Type: types.TransferTypeMakerFeeReceive, Amount: &types.FinancialAmount{Asset: "asset2", Amount: num.NewUint(550)}}, + } + tracker.UpdateFeesFromTransfers("asset2", "market3", transfersM5) + epochService.target(context.Background(), types.Epoch{Seq: 3, Action: vgproto.EpochAction_EPOCH_ACTION_END}) + + // party 1 received in total this epoch: + // asset1 : 2000 + // party 2 received in total: + // asset2: 550/2 = 275 + // in total for both epochs: + // party1 = 3000 + // party2 = 2000 + contribution, fraction = tracker.CalculateTotalMakerContributionInQuantum(2) + require.Equal(t, 2, len(fraction)) + require.Equal(t, 2, len(contribution)) + require.Equal(t, "3000", contribution["party1"].String()) + require.Equal(t, "2000", contribution["party2"].String()) + require.Equal(t, "0.6", fraction["party1"].String()) + require.Equal(t, "0.4", fraction["party2"].String()) +} + func TestGetScores(t *testing.T) { ctx := context.Background() epochService := &TestEpochEngine{} @@ -218,7 +320,8 @@ func TestGetScores(t *testing.T) { broker := bmocks.NewMockBroker(ctrl) broker.EXPECT().SendBatch(gomock.Any()).AnyTimes() broker.EXPECT().Send(gomock.Any()).AnyTimes() - tracker := common.NewMarketActivityTracker(logging.NewTestLogger(), teams, balanceChecker, broker) + collateralService := mocks.NewMockCollateral(ctrl) + tracker := common.NewMarketActivityTracker(logging.NewTestLogger(), teams, balanceChecker, broker, collateralService) epochService.NotifyOnEpoch(tracker.OnEpochEvent, tracker.OnEpochRestore) tracker.SetEligibilityChecker(&EligibilityChecker{}) tracker.MarketProposed("asset1", "market1", "me") @@ -368,7 +471,8 @@ func TestGetScoresIndividualsDifferentScopes(t *testing.T) { broker := bmocks.NewMockBroker(ctrl) broker.EXPECT().SendBatch(gomock.Any()).AnyTimes() broker.EXPECT().Send(gomock.Any()).AnyTimes() - tracker := common.NewMarketActivityTracker(logging.NewTestLogger(), teams, balanceChecker, broker) + collateralService := mocks.NewMockCollateral(ctrl) + tracker := common.NewMarketActivityTracker(logging.NewTestLogger(), teams, balanceChecker, broker, collateralService) epochService.NotifyOnEpoch(tracker.OnEpochEvent, tracker.OnEpochRestore) tracker.SetEligibilityChecker(&EligibilityChecker{}) tracker.MarketProposed("asset1", "market1", "me") @@ -530,7 +634,8 @@ func TestMarketTrackerStateChange(t *testing.T) { balanceChecker := mocks.NewMockAccountBalanceChecker(ctrl) broker := bmocks.NewMockBroker(ctrl) broker.EXPECT().SendBatch(gomock.Any()).AnyTimes() - tracker := common.NewMarketActivityTracker(logging.NewTestLogger(), teams, balanceChecker, broker) + collateralService := mocks.NewMockCollateral(ctrl) + tracker := common.NewMarketActivityTracker(logging.NewTestLogger(), teams, balanceChecker, broker, collateralService) tracker.SetEligibilityChecker(&EligibilityChecker{}) state1, _, err := tracker.GetState(key) @@ -561,7 +666,8 @@ func TestFeesTrackerWith0(t *testing.T) { balanceChecker.EXPECT().GetAvailableBalance(gomock.Any()).Return(num.UintZero(), nil).AnyTimes() broker := bmocks.NewMockBroker(ctrl) broker.EXPECT().Send(gomock.Any()).AnyTimes() - tracker := common.NewMarketActivityTracker(logging.NewTestLogger(), teams, balanceChecker, broker) + collateralService := mocks.NewMockCollateral(ctrl) + tracker := common.NewMarketActivityTracker(logging.NewTestLogger(), teams, balanceChecker, broker, collateralService) epochEngine.NotifyOnEpoch(tracker.OnEpochEvent, tracker.OnEpochRestore) epochEngine.target(context.Background(), types.Epoch{Seq: 1, Action: vgproto.EpochAction_EPOCH_ACTION_START}) @@ -595,7 +701,8 @@ func TestGetLastEpochTakeFees(t *testing.T) { teams := mocks.NewMockTeams(ctrl) balanceChecker := mocks.NewMockAccountBalanceChecker(ctrl) broker := bmocks.NewMockBroker(ctrl) - tracker := common.NewMarketActivityTracker(logging.NewTestLogger(), teams, balanceChecker, broker) + collateralService := mocks.NewMockCollateral(ctrl) + tracker := common.NewMarketActivityTracker(logging.NewTestLogger(), teams, balanceChecker, broker, collateralService) epochEngine.NotifyOnEpoch(tracker.OnEpochEvent, tracker.OnEpochRestore) epochEngine.target(context.Background(), types.Epoch{Seq: 1, Action: vgproto.EpochAction_EPOCH_ACTION_START}) tracker.SetEligibilityChecker(&EligibilityChecker{}) @@ -659,7 +766,8 @@ func TestGetLastEpochTakeFeesMultiEpochWindow(t *testing.T) { teams := mocks.NewMockTeams(ctrl) balanceChecker := mocks.NewMockAccountBalanceChecker(ctrl) broker := bmocks.NewMockBroker(ctrl) - tracker := common.NewMarketActivityTracker(logging.NewTestLogger(), teams, balanceChecker, broker) + collateralService := mocks.NewMockCollateral(ctrl) + tracker := common.NewMarketActivityTracker(logging.NewTestLogger(), teams, balanceChecker, broker, collateralService) epochEngine.NotifyOnEpoch(tracker.OnEpochEvent, tracker.OnEpochRestore) epochEngine.target(context.Background(), types.Epoch{Seq: 1, Action: vgproto.EpochAction_EPOCH_ACTION_START}) tracker.SetEligibilityChecker(&EligibilityChecker{}) @@ -749,7 +857,8 @@ func TestFeesTracker(t *testing.T) { broker := bmocks.NewMockBroker(ctrl) broker.EXPECT().Send(gomock.Any()).AnyTimes() broker.EXPECT().SendBatch(gomock.Any()).AnyTimes() - tracker := common.NewMarketActivityTracker(logging.NewTestLogger(), teams, balanceChecker, broker) + collateralService := mocks.NewMockCollateral(ctrl) + tracker := common.NewMarketActivityTracker(logging.NewTestLogger(), teams, balanceChecker, broker, collateralService) epochEngine.NotifyOnEpoch(tracker.OnEpochEvent, tracker.OnEpochRestore) epochEngine.target(context.Background(), types.Epoch{Seq: 1, Action: vgproto.EpochAction_EPOCH_ACTION_START}) tracker.SetEligibilityChecker(&EligibilityChecker{}) @@ -851,7 +960,7 @@ func TestFeesTracker(t *testing.T) { balanceChecker = mocks.NewMockAccountBalanceChecker(ctrl) balanceChecker.EXPECT().GetAvailableBalance(gomock.Any()).Return(num.UintZero(), nil).AnyTimes() broker.EXPECT().Send(gomock.Any()).AnyTimes() - trackerLoad := common.NewMarketActivityTracker(logging.NewTestLogger(), teams, balanceChecker, broker) + trackerLoad := common.NewMarketActivityTracker(logging.NewTestLogger(), teams, balanceChecker, broker, collateralService) epochEngineLoad.NotifyOnEpoch(trackerLoad.OnEpochEvent, trackerLoad.OnEpochRestore) pl := snapshotpb.Payload{} @@ -935,7 +1044,8 @@ func TestSnapshot(t *testing.T) { teams := mocks.NewMockTeams(ctrl) balanceChecker := mocks.NewMockAccountBalanceChecker(ctrl) broker := bmocks.NewMockBroker(ctrl) - trackerLoad := common.NewMarketActivityTracker(logging.NewTestLogger(), teams, balanceChecker, broker) + collateralService := mocks.NewMockCollateral(ctrl) + trackerLoad := common.NewMarketActivityTracker(logging.NewTestLogger(), teams, balanceChecker, broker, collateralService) pl := snapshotpb.Payload{} require.NoError(t, proto.Unmarshal(state1, &pl)) @@ -958,7 +1068,8 @@ func TestCheckpoint(t *testing.T) { teams := mocks.NewMockTeams(ctrl) balanceChecker := mocks.NewMockAccountBalanceChecker(ctrl) broker := bmocks.NewMockBroker(ctrl) - trackerLoad := common.NewMarketActivityTracker(logging.NewTestLogger(), teams, balanceChecker, broker) + collateralService := mocks.NewMockCollateral(ctrl) + trackerLoad := common.NewMarketActivityTracker(logging.NewTestLogger(), teams, balanceChecker, broker, collateralService) require.NoError(t, trackerLoad.Load(context.Background(), b)) @@ -1021,7 +1132,8 @@ func TestSnapshotRoundTripViaEngine(t *testing.T) { teams := mocks.NewMockTeams(ctrl) balanceChecker := mocks.NewMockAccountBalanceChecker(ctrl) broker := bmocks.NewMockBroker(ctrl) - tracker2 := common.NewMarketActivityTracker(logging.NewTestLogger(), teams, balanceChecker, broker) + collateralService := mocks.NewMockCollateral(ctrl) + tracker2 := common.NewMarketActivityTracker(logging.NewTestLogger(), teams, balanceChecker, broker, collateralService) snapshotEngine2, err := snp.NewEngine(vegaPath, config, log, timeService, statsData.Blockchain) require.NoError(t, err) defer snapshotEngine2.Close() @@ -1059,7 +1171,8 @@ func TestMarketProposerBonusScenarios(t *testing.T) { teams := mocks.NewMockTeams(ctrl) balanceChecker := mocks.NewMockAccountBalanceChecker(ctrl) broker := bmocks.NewMockBroker(ctrl) - tracker := common.NewMarketActivityTracker(logging.NewTestLogger(), teams, balanceChecker, broker) + collateralService := mocks.NewMockCollateral(ctrl) + tracker := common.NewMarketActivityTracker(logging.NewTestLogger(), teams, balanceChecker, broker, collateralService) epochService.NotifyOnEpoch(tracker.OnEpochEvent, tracker.OnEpochRestore) tracker.SetEligibilityChecker(&EligibilityChecker{}) @@ -1192,7 +1305,8 @@ func TestNotionalMetric(t *testing.T) { broker := bmocks.NewMockBroker(ctrl) broker.EXPECT().SendBatch(gomock.Any()).AnyTimes() broker.EXPECT().Send(gomock.Any()).AnyTimes() - tracker := common.NewMarketActivityTracker(logging.NewTestLogger(), teams, balanceChecker, broker) + collateralService := mocks.NewMockCollateral(ctrl) + tracker := common.NewMarketActivityTracker(logging.NewTestLogger(), teams, balanceChecker, broker, collateralService) epochService.NotifyOnEpoch(tracker.OnEpochEvent, tracker.OnEpochRestore) epochStartTime := time.Now() @@ -1301,7 +1415,8 @@ func TestRealisedReturnMetric(t *testing.T) { broker := bmocks.NewMockBroker(ctrl) broker.EXPECT().SendBatch(gomock.Any()).AnyTimes() broker.EXPECT().Send(gomock.Any()).AnyTimes() - tracker := common.NewMarketActivityTracker(logging.NewTestLogger(), teams, balanceChecker, broker) + collateralService := mocks.NewMockCollateral(ctrl) + tracker := common.NewMarketActivityTracker(logging.NewTestLogger(), teams, balanceChecker, broker, collateralService) epochService.NotifyOnEpoch(tracker.OnEpochEvent, tracker.OnEpochRestore) epochStartTime := time.Now() @@ -1405,7 +1520,8 @@ func TestRelativeReturnMetric(t *testing.T) { broker := bmocks.NewMockBroker(ctrl) broker.EXPECT().Send(gomock.Any()).AnyTimes() broker.EXPECT().SendBatch(gomock.Any()).AnyTimes() - tracker := common.NewMarketActivityTracker(logging.NewTestLogger(), teams, balanceChecker, broker) + collateralService := mocks.NewMockCollateral(ctrl) + tracker := common.NewMarketActivityTracker(logging.NewTestLogger(), teams, balanceChecker, broker, collateralService) epochService.NotifyOnEpoch(tracker.OnEpochEvent, tracker.OnEpochRestore) epochStartTime := time.Now() @@ -1494,7 +1610,8 @@ func TestTeamStatsForMarkets(t *testing.T) { teams := mocks.NewMockTeams(ctrl) balanceChecker := mocks.NewMockAccountBalanceChecker(ctrl) broker := bmocks.NewMockBroker(ctrl) - tracker := common.NewMarketActivityTracker(logging.NewTestLogger(), teams, balanceChecker, broker) + collateralService := mocks.NewMockCollateral(ctrl) + tracker := common.NewMarketActivityTracker(logging.NewTestLogger(), teams, balanceChecker, broker, collateralService) asset1 := vgrand.RandomStr(5) asset2 := vgrand.RandomStr(5) @@ -1606,8 +1723,8 @@ func setupDefaultTrackerForTest(t *testing.T) *common.MarketActivityTracker { teams := mocks.NewMockTeams(ctrl) balanceChecker := mocks.NewMockAccountBalanceChecker(ctrl) broker := bmocks.NewMockBroker(ctrl) - - tracker := common.NewMarketActivityTracker(logging.NewTestLogger(), teams, balanceChecker, broker) + collateralService := mocks.NewMockCollateral(ctrl) + tracker := common.NewMarketActivityTracker(logging.NewTestLogger(), teams, balanceChecker, broker, collateralService) epochService.NotifyOnEpoch(tracker.OnEpochEvent, tracker.OnEpochRestore) epochStartTime := time.Now() diff --git a/core/execution/common/mat_intermediate_scores_internal_test.go b/core/execution/common/mat_intermediate_scores_internal_test.go index 7d3818b10c9..babb3a5d1f8 100644 --- a/core/execution/common/mat_intermediate_scores_internal_test.go +++ b/core/execution/common/mat_intermediate_scores_internal_test.go @@ -47,7 +47,7 @@ func TestPublishGameMetricAverageNotional(t *testing.T) { gameScoreEvents = append(gameScoreEvents, evt) } }).AnyTimes() - tracker := NewMarketActivityTracker(logging.NewTestLogger(), teams, balanceChecker, broker) + tracker := NewMarketActivityTracker(logging.NewTestLogger(), teams, balanceChecker, broker, DummyCollateralEngine{}) epochService.NotifyOnEpoch(tracker.OnEpochEvent, tracker.OnEpochRestore) tracker.SetEligibilityChecker(&DummyEligibilityChecker{}) epochService.target(context.Background(), types.Epoch{Seq: 1, Action: vgproto.EpochAction_EPOCH_ACTION_START, StartTime: time.Time{}}) @@ -289,7 +289,7 @@ func TestPublishGameMetricReturnVolatility(t *testing.T) { gameScoreEvents = append(gameScoreEvents, evt) } }).AnyTimes() - tracker := NewMarketActivityTracker(logging.NewTestLogger(), teams, balanceChecker, broker) + tracker := NewMarketActivityTracker(logging.NewTestLogger(), teams, balanceChecker, broker, DummyCollateralEngine{}) epochService.NotifyOnEpoch(tracker.OnEpochEvent, tracker.OnEpochRestore) tracker.SetEligibilityChecker(&DummyEligibilityChecker{}) epochService.target(context.Background(), types.Epoch{Seq: 1, Action: vgproto.EpochAction_EPOCH_ACTION_START, StartTime: time.Time{}}) @@ -537,7 +537,7 @@ func TestPublishGameMetricRelativeReturn(t *testing.T) { gameScoreEvents = append(gameScoreEvents, evt) } }).AnyTimes() - tracker := NewMarketActivityTracker(logging.NewTestLogger(), teams, balanceChecker, broker) + tracker := NewMarketActivityTracker(logging.NewTestLogger(), teams, balanceChecker, broker, DummyCollateralEngine{}) epochService.NotifyOnEpoch(tracker.OnEpochEvent, tracker.OnEpochRestore) tracker.SetEligibilityChecker(&DummyEligibilityChecker{}) epochService.target(context.Background(), types.Epoch{Seq: 1, Action: vgproto.EpochAction_EPOCH_ACTION_START, StartTime: time.Unix(0, 0)}) @@ -757,7 +757,7 @@ func TestPublishGameMetricRealisedReturn(t *testing.T) { gameScoreEvents = append(gameScoreEvents, evt) } }).AnyTimes() - tracker := NewMarketActivityTracker(logging.NewTestLogger(), teams, balanceChecker, broker) + tracker := NewMarketActivityTracker(logging.NewTestLogger(), teams, balanceChecker, broker, DummyCollateralEngine{}) epochService.NotifyOnEpoch(tracker.OnEpochEvent, tracker.OnEpochRestore) tracker.SetEligibilityChecker(&DummyEligibilityChecker{}) epochService.target(context.Background(), types.Epoch{Seq: 1, Action: vgproto.EpochAction_EPOCH_ACTION_START, StartTime: time.Unix(0, 0)}) @@ -879,7 +879,7 @@ func TestPublishGameMetricFees(t *testing.T) { gameScoreEvents = append(gameScoreEvents, evt) } }).AnyTimes() - tracker := NewMarketActivityTracker(logging.NewTestLogger(), teams, balanceChecker, broker) + tracker := NewMarketActivityTracker(logging.NewTestLogger(), teams, balanceChecker, broker, DummyCollateralEngine{}) epochService.NotifyOnEpoch(tracker.OnEpochEvent, tracker.OnEpochRestore) tracker.SetEligibilityChecker(&DummyEligibilityChecker{}) epochService.target(context.Background(), types.Epoch{Seq: 1, Action: vgproto.EpochAction_EPOCH_ACTION_START, StartTime: time.Unix(0, 0)}) diff --git a/core/execution/engine.go b/core/execution/engine.go index e1b1bf5a707..5431c1f5cd7 100644 --- a/core/execution/engine.go +++ b/core/execution/engine.go @@ -86,8 +86,10 @@ type Engine struct { assets common.Assets referralDiscountRewardService fee.ReferralDiscountRewardService volumeDiscountService fee.VolumeDiscountService - banking common.Banking - parties common.Parties + volumeRebateService fee.VolumeRebateService + + banking common.Banking + parties common.Parties broker common.Broker timeService common.TimeService @@ -137,6 +139,7 @@ func NewEngine( assets common.Assets, referralDiscountRewardService fee.ReferralDiscountRewardService, volumeDiscountService fee.VolumeDiscountService, + volumeRebateService fee.VolumeRebateService, banking common.Banking, parties common.Parties, delayTransactionsTarget common.DelayTransactionsTarget, @@ -165,9 +168,11 @@ func NewEngine( skipRestoreSuccessors: map[string]struct{}{}, referralDiscountRewardService: referralDiscountRewardService, volumeDiscountService: volumeDiscountService, - banking: banking, - parties: parties, - delayTransactionsTarget: delayTransactionsTarget, + volumeRebateService: volumeRebateService, + + banking: banking, + parties: parties, + delayTransactionsTarget: delayTransactionsTarget, } // set the eligibility for proposer bonus checker @@ -707,6 +712,7 @@ func (e *Engine) submitMarket(ctx context.Context, marketConfig *types.Market, o e.peggedOrderCountUpdated, e.referralDiscountRewardService, e.volumeDiscountService, + e.volumeRebateService, e.banking, e.parties, ) @@ -790,6 +796,7 @@ func (e *Engine) submitSpotMarket(ctx context.Context, marketConfig *types.Marke e.peggedOrderCountUpdated, e.referralDiscountRewardService, e.volumeDiscountService, + e.volumeRebateService, e.banking, ) if err != nil { diff --git a/core/execution/engine_netparams.go b/core/execution/engine_netparams.go index 85b3033d7ba..c0580f1c1e6 100644 --- a/core/execution/engine_netparams.go +++ b/core/execution/engine_netparams.go @@ -35,6 +35,8 @@ type netParamsValues struct { suppliedStakeToObligationFactor num.Decimal infrastructureFee num.Decimal makerFee num.Decimal + treasuryFee num.Decimal + buyBackFee num.Decimal scalingFactors *types.ScalingFactors maxLiquidityFee num.Decimal bondPenaltyFactor num.Decimal @@ -76,6 +78,8 @@ func defaultNetParamsValues() netParamsValues { suppliedStakeToObligationFactor: num.DecimalFromInt64(-1), infrastructureFee: num.DecimalFromInt64(-1), makerFee: num.DecimalFromInt64(-1), + buyBackFee: num.DecimalFromInt64(-1), + treasuryFee: num.DecimalFromInt64(-1), scalingFactors: nil, maxLiquidityFee: num.DecimalFromInt64(-1), bondPenaltyFactor: num.DecimalFromInt64(-1), @@ -329,6 +333,34 @@ func (e *Engine) OnMarketFeeFactorsMakerFeeUpdate(ctx context.Context, d num.Dec return nil } +func (e *Engine) OnMarketFeeFactorsTreasuryFeeUpdate(ctx context.Context, d num.Decimal) error { + if e.log.IsDebug() { + e.log.Debug("update treasury fee in market fee factors", + logging.Decimal("treasury-fee", d), + ) + } + + for _, mkt := range e.allMarketsCpy { + mkt.OnFeeFactorsTreasuryFeeUpdate(ctx, d) + } + e.npv.treasuryFee = d + return nil +} + +func (e *Engine) OnMarketFeeFactorsBuyBackFeeUpdate(ctx context.Context, d num.Decimal) error { + if e.log.IsDebug() { + e.log.Debug("update buy back fee in market fee factors", + logging.Decimal("buy-back-fee", d), + ) + } + + for _, mkt := range e.allMarketsCpy { + mkt.OnFeeFactorsBuyBackFeeUpdate(ctx, d) + } + e.npv.buyBackFee = d + return nil +} + func (e *Engine) OnMarketFeeFactorsInfrastructureFeeUpdate(ctx context.Context, d num.Decimal) error { if e.log.IsDebug() { e.log.Debug("update infrastructure fee in market fee factors", @@ -503,6 +535,14 @@ func (e *Engine) propagateSpotInitialNetParams(ctx context.Context, mkt *spot.Ma mkt.OnFeeFactorsMakerFeeUpdate(ctx, e.npv.makerFee) } + if !e.npv.buyBackFee.Equal(num.DecimalFromInt64(-1)) { + mkt.OnFeeFactorsBuyBackFeeUpdate(ctx, e.npv.buyBackFee) + } + + if !e.npv.treasuryFee.Equal(num.DecimalFromInt64(-1)) { + mkt.OnFeeFactorsTreasuryFeeUpdate(ctx, e.npv.treasuryFee) + } + if e.npv.marketValueWindowLength != -1 { mkt.OnMarketValueWindowLengthUpdate(e.npv.marketValueWindowLength) } @@ -567,6 +607,14 @@ func (e *Engine) propagateInitialNetParamsToFutureMarket(ctx context.Context, mk mkt.OnFeeFactorsMakerFeeUpdate(ctx, e.npv.makerFee) } + if !e.npv.buyBackFee.Equal(num.DecimalFromInt64(-1)) { + mkt.OnFeeFactorsBuyBackFeeUpdate(ctx, e.npv.buyBackFee) + } + + if !e.npv.treasuryFee.Equal(num.DecimalFromInt64(-1)) { + mkt.OnFeeFactorsTreasuryFeeUpdate(ctx, e.npv.treasuryFee) + } + if e.npv.scalingFactors != nil { if err := mkt.OnMarginScalingFactorsUpdate(ctx, e.npv.scalingFactors); err != nil { return err diff --git a/core/execution/engine_snapshot.go b/core/execution/engine_snapshot.go index 05a56317fef..3ec884cf124 100644 --- a/core/execution/engine_snapshot.go +++ b/core/execution/engine_snapshot.go @@ -138,6 +138,7 @@ func (e *Engine) restoreSpotMarket(ctx context.Context, em *types.ExecSpotMarket e.peggedOrderCountUpdated, e.referralDiscountRewardService, e.volumeDiscountService, + e.volumeRebateService, e.banking, ) if err != nil { @@ -215,6 +216,7 @@ func (e *Engine) restoreMarket(ctx context.Context, em *types.ExecMarket) (*futu e.peggedOrderCountUpdated, e.referralDiscountRewardService, e.volumeDiscountService, + e.volumeRebateService, e.banking, e.parties, ) diff --git a/core/execution/engine_snapshot_test.go b/core/execution/engine_snapshot_test.go index 9453f95d09b..ed67607aa7b 100644 --- a/core/execution/engine_snapshot_test.go +++ b/core/execution/engine_snapshot_test.go @@ -90,17 +90,18 @@ func getMockedEngine(t *testing.T) *engineFake { balanceChecker := mocks.NewMockAccountBalanceChecker(ctrl) referralDiscountReward := fmock.NewMockReferralDiscountRewardService(ctrl) volumeDiscount := fmock.NewMockVolumeDiscountService(ctrl) + volumeRebate := fmock.NewMockVolumeRebateService(ctrl) - referralDiscountReward.EXPECT().ReferralDiscountFactorForParty(gomock.Any()).Return(num.DecimalZero()).AnyTimes() - referralDiscountReward.EXPECT().RewardsFactorMultiplierAppliedForParty(gomock.Any()).Return(num.DecimalZero()).AnyTimes() - volumeDiscount.EXPECT().VolumeDiscountFactorForParty(gomock.Any()).Return(num.DecimalZero()).AnyTimes() + referralDiscountReward.EXPECT().ReferralDiscountFactorsForParty(gomock.Any()).Return(types.EmptyFactors).AnyTimes() + referralDiscountReward.EXPECT().RewardsFactorsMultiplierAppliedForParty(gomock.Any()).Return(types.EmptyFactors).AnyTimes() + volumeDiscount.EXPECT().VolumeDiscountFactorForParty(gomock.Any()).Return(types.EmptyFactors).AnyTimes() referralDiscountReward.EXPECT().GetReferrer(gomock.Any()).Return(types.PartyID(""), errors.New("not a referrer")).AnyTimes() banking := mocks.NewMockBanking(ctrl) parties := mocks.NewMockParties(ctrl) delayTarget := mocks.NewMockDelayTransactionsTarget(ctrl) delayTarget.EXPECT().MarketDelayRequiredUpdated(gomock.Any(), gomock.Any()).AnyTimes() - mat := common.NewMarketActivityTracker(log, teams, balanceChecker, broker) - exec := execution.NewEngine(log, execConfig, timeService, collateralService, oracleService, broker, statevar, mat, asset, referralDiscountReward, volumeDiscount, banking, parties, delayTarget) + mat := common.NewMarketActivityTracker(log, teams, balanceChecker, broker, collateralService) + exec := execution.NewEngine(log, execConfig, timeService, collateralService, oracleService, broker, statevar, mat, asset, referralDiscountReward, volumeDiscount, volumeRebate, banking, parties, delayTarget) epochEngine.NotifyOnEpoch(mat.OnEpochEvent, mat.OnEpochRestore) return &engineFake{ Engine: exec, @@ -159,16 +160,17 @@ func createEngine(t *testing.T) (*execution.Engine, *gomock.Controller) { balanceChecker := mocks.NewMockAccountBalanceChecker(ctrl) referralDiscountReward := fmock.NewMockReferralDiscountRewardService(ctrl) volumeDiscount := fmock.NewMockVolumeDiscountService(ctrl) - referralDiscountReward.EXPECT().ReferralDiscountFactorForParty(gomock.Any()).Return(num.DecimalZero()).AnyTimes() - referralDiscountReward.EXPECT().RewardsFactorMultiplierAppliedForParty(gomock.Any()).Return(num.DecimalZero()).AnyTimes() - volumeDiscount.EXPECT().VolumeDiscountFactorForParty(gomock.Any()).Return(num.DecimalZero()).AnyTimes() + volumeRebate := fmock.NewMockVolumeRebateService(ctrl) + referralDiscountReward.EXPECT().ReferralDiscountFactorsForParty(gomock.Any()).Return(types.EmptyFactors).AnyTimes() + referralDiscountReward.EXPECT().RewardsFactorsMultiplierAppliedForParty(gomock.Any()).Return(types.EmptyFactors).AnyTimes() + volumeDiscount.EXPECT().VolumeDiscountFactorForParty(gomock.Any()).Return(types.EmptyFactors).AnyTimes() referralDiscountReward.EXPECT().GetReferrer(gomock.Any()).Return(types.PartyID(""), errors.New("not a referrer")).AnyTimes() - mat := common.NewMarketActivityTracker(log, teams, balanceChecker, broker) + mat := common.NewMarketActivityTracker(log, teams, balanceChecker, broker, collateralService) banking := mocks.NewMockBanking(ctrl) parties := mocks.NewMockParties(ctrl) delayTarget := mocks.NewMockDelayTransactionsTarget(ctrl) delayTarget.EXPECT().MarketDelayRequiredUpdated(gomock.Any(), gomock.Any()).AnyTimes() - e := execution.NewEngine(log, executionConfig, timeService, collateralService, oracleService, broker, statevar, mat, asset, referralDiscountReward, volumeDiscount, banking, parties, delayTarget) + e := execution.NewEngine(log, executionConfig, timeService, collateralService, oracleService, broker, statevar, mat, asset, referralDiscountReward, volumeDiscount, volumeRebate, banking, parties, delayTarget) epochEngine.NotifyOnEpoch(mat.OnEpochEvent, mat.OnEpochRestore) return e, ctrl } diff --git a/core/execution/future/market.go b/core/execution/future/market.go index c487c71a539..90170368eeb 100644 --- a/core/execution/future/market.go +++ b/core/execution/future/market.go @@ -96,6 +96,7 @@ type Market struct { fee *fee.Engine referralDiscountRewardService fee.ReferralDiscountRewardService volumeDiscountService fee.VolumeDiscountService + volumeRebateService fee.VolumeRebateService liquidity *common.MarketLiquidity liquidityEngine common.LiquidityEngine @@ -197,6 +198,7 @@ func NewMarket( peggedOrderNotify func(int64), referralDiscountRewardService fee.ReferralDiscountRewardService, volumeDiscountService fee.VolumeDiscountService, + volumeRebateService fee.VolumeRebateService, banking common.Banking, parties common.Parties, ) (*Market, error) { @@ -347,6 +349,7 @@ func NewMarket( perp: marketType == types.MarketTypePerp, referralDiscountRewardService: referralDiscountRewardService, volumeDiscountService: volumeDiscountService, + volumeRebateService: volumeRebateService, partyMarginFactor: map[string]num.Decimal{}, banking: banking, markPriceCalculator: common.NewCompositePriceCalculator(ctx, mkt.MarkPriceConfiguration, oracleEngine, timeService), @@ -2712,12 +2715,12 @@ func (m *Market) calcFees(trades []*types.Trade) (events.FeesTransfer, error) { ) if !m.as.InAuction() { - fees, err = m.fee.CalculateForContinuousMode(trades, m.referralDiscountRewardService, m.volumeDiscountService) + fees, err = m.fee.CalculateForContinuousMode(trades, m.referralDiscountRewardService, m.volumeDiscountService, m.volumeRebateService) } else if m.as.IsMonitorAuction() { // we are in auction mode - fees, err = m.fee.CalculateForAuctionMode(trades, m.referralDiscountRewardService, m.volumeDiscountService) + fees, err = m.fee.CalculateForAuctionMode(trades, m.referralDiscountRewardService, m.volumeDiscountService, m.volumeRebateService) } else if m.as.IsFBA() { - fees, err = m.fee.CalculateForFrequentBatchesAuctionMode(trades, m.referralDiscountRewardService, m.volumeDiscountService) + fees, err = m.fee.CalculateForFrequentBatchesAuctionMode(trades, m.referralDiscountRewardService, m.volumeDiscountService, m.volumeRebateService) } if err != nil { diff --git a/core/execution/future/market_callbacks.go b/core/execution/future/market_callbacks.go index ee8e9d9e0ae..362c6571747 100644 --- a/core/execution/future/market_callbacks.go +++ b/core/execution/future/market_callbacks.go @@ -58,6 +58,18 @@ func (m *Market) OnFeeFactorsMakerFeeUpdate(ctx context.Context, d num.Decimal) m.broker.Send(events.NewMarketUpdatedEvent(ctx, *m.mkt)) } +func (m *Market) OnFeeFactorsTreasuryFeeUpdate(ctx context.Context, d num.Decimal) { + m.fee.OnFeeFactorsTreasuryFeeUpdate(d) + m.mkt.Fees.Factors.TreasuryFee = d + m.broker.Send(events.NewMarketUpdatedEvent(ctx, *m.mkt)) +} + +func (m *Market) OnFeeFactorsBuyBackFeeUpdate(ctx context.Context, d num.Decimal) { + m.fee.OnFeeFactorsBuyBackFeeUpdate(d) + m.mkt.Fees.Factors.BuyBackFee = d + m.broker.Send(events.NewMarketUpdatedEvent(ctx, *m.mkt)) +} + func (m *Market) OnFeeFactorsInfrastructureFeeUpdate(ctx context.Context, d num.Decimal) { m.fee.OnFeeFactorsInfrastructureFeeUpdate(d) m.mkt.Fees.Factors.InfrastructureFee = d diff --git a/core/execution/future/market_snapshot.go b/core/execution/future/market_snapshot.go index cbded574128..ed979d73159 100644 --- a/core/execution/future/market_snapshot.go +++ b/core/execution/future/market_snapshot.go @@ -67,6 +67,7 @@ func NewMarketFromSnapshot( peggedOrderNotify func(int64), referralDiscountRewardService fee.ReferralDiscountRewardService, volumeDiscountService fee.VolumeDiscountService, + volumeRebateService fee.VolumeRebateService, banking common.Banking, parties common.Parties, ) (*Market, error) { @@ -234,6 +235,7 @@ func NewMarketFromSnapshot( fee: feeEngine, referralDiscountRewardService: referralDiscountRewardService, volumeDiscountService: volumeDiscountService, + volumeRebateService: volumeRebateService, liquidityEngine: liquidityEngine, liquidity: marketLiquidity, parties: map[string]struct{}{}, diff --git a/core/execution/future/market_snapshot_test.go b/core/execution/future/market_snapshot_test.go index 239a7008213..1e422c5d1f4 100644 --- a/core/execution/future/market_snapshot_test.go +++ b/core/execution/future/market_snapshot_test.go @@ -242,8 +242,6 @@ func newMarketFromSnapshot(t *testing.T, ctx context.Context, ctrl *gomock.Contr teams := mocks.NewMockTeams(ctrl) bc := mocks.NewMockAccountBalanceChecker(ctrl) broker := bmocks.NewMockBroker(ctrl) - marketActivityTracker := common.NewMarketActivityTracker(logging.NewTestLogger(), teams, bc, broker) - epochEngine.NotifyOnEpoch(marketActivityTracker.OnEpochEvent, marketActivityTracker.OnEpochRestore) broker.EXPECT().Stage(gomock.Any()).AnyTimes() broker.EXPECT().Send(gomock.Any()).AnyTimes() @@ -251,16 +249,20 @@ func newMarketFromSnapshot(t *testing.T, ctx context.Context, ctrl *gomock.Contr timeService.EXPECT().GetTimeNow().AnyTimes() collateralEngine := collateral.New(log, collateral.NewDefaultConfig(), timeService, broker) + marketActivityTracker := common.NewMarketActivityTracker(logging.NewTestLogger(), teams, bc, broker, collateralEngine) + epochEngine.NotifyOnEpoch(marketActivityTracker.OnEpochEvent, marketActivityTracker.OnEpochRestore) + positionConfig.StreamPositionVerbose = true referralDiscountReward := fmock.NewMockReferralDiscountRewardService(ctrl) volumeDiscount := fmock.NewMockVolumeDiscountService(ctrl) - referralDiscountReward.EXPECT().ReferralDiscountFactorForParty(gomock.Any()).Return(num.DecimalZero()).AnyTimes() - volumeDiscount.EXPECT().VolumeDiscountFactorForParty(gomock.Any()).Return(num.DecimalZero()).AnyTimes() + volumeRebate := fmock.NewMockVolumeRebateService(ctrl) + referralDiscountReward.EXPECT().ReferralDiscountFactorsForParty(gomock.Any()).Return(types.EmptyFactors).AnyTimes() + volumeDiscount.EXPECT().VolumeDiscountFactorForParty(gomock.Any()).Return(types.EmptyFactors).AnyTimes() referralDiscountReward.EXPECT().GetReferrer(gomock.Any()).Return(types.PartyID(""), errors.New("not a referrer")).AnyTimes() banking := mocks.NewMockBanking(ctrl) parties := mocks.NewMockParties(ctrl) return future.NewMarketFromSnapshot(ctx, log, em, riskConfig, positionConfig, settlementConfig, matchingConfig, feeConfig, liquidityConfig, collateralEngine, oracleEngine, timeService, broker, stubs.NewStateVar(), cfgAsset, marketActivityTracker, - peggedOrderCounterForTest, referralDiscountReward, volumeDiscount, banking, parties) + peggedOrderCounterForTest, referralDiscountReward, volumeDiscount, volumeRebate, banking, parties) } diff --git a/core/execution/future/market_test.go b/core/execution/future/market_test.go index 8e62e199c84..e243bf1a3f9 100644 --- a/core/execution/future/market_test.go +++ b/core/execution/future/market_test.go @@ -235,22 +235,24 @@ func (tm *testMarket) Run(ctx context.Context, mktCfg types.Market) *testMarket teams := mocks.NewMockTeams(tm.ctrl) bc := mocks.NewMockAccountBalanceChecker(tm.ctrl) broker := bmocks.NewMockBroker(tm.ctrl) - marketActivityTracker := common.NewMarketActivityTracker(logging.NewTestLogger(), teams, bc, broker) + marketActivityTracker := common.NewMarketActivityTracker(logging.NewTestLogger(), teams, bc, broker, collateralEngine) epochEngine.NotifyOnEpoch(marketActivityTracker.OnEpochEvent, marketActivityTracker.OnEpochRestore) referralDiscountReward := fmocks.NewMockReferralDiscountRewardService(tm.ctrl) volumeDiscount := fmocks.NewMockVolumeDiscountService(tm.ctrl) + volumeRebate := fmocks.NewMockVolumeRebateService(tm.ctrl) referralDiscountReward.EXPECT().GetReferrer(gomock.Any()).Return(types.PartyID(""), errors.New("no referrer")).AnyTimes() - referralDiscountReward.EXPECT().ReferralDiscountFactorForParty(gomock.Any()).Return(num.DecimalZero()).AnyTimes() - referralDiscountReward.EXPECT().RewardsFactorMultiplierAppliedForParty(gomock.Any()).Return(num.DecimalZero()).AnyTimes() - volumeDiscount.EXPECT().VolumeDiscountFactorForParty(gomock.Any()).Return(num.DecimalZero()).AnyTimes() + referralDiscountReward.EXPECT().ReferralDiscountFactorsForParty(gomock.Any()).Return(types.EmptyFactors).AnyTimes() + referralDiscountReward.EXPECT().RewardsFactorsMultiplierAppliedForParty(gomock.Any()).Return(types.EmptyFactors).AnyTimes() + volumeDiscount.EXPECT().VolumeDiscountFactorForParty(gomock.Any()).Return(types.EmptyFactors).AnyTimes() + volumeRebate.EXPECT().VolumeRebateFactorForParty(gomock.Any()).Return(num.DecimalZero()).AnyTimes() banking := mocks.NewMockBanking(tm.ctrl) parties := mocks.NewMockParties(tm.ctrl) mktEngine, err := future.NewMarket(ctx, tm.log, riskConfig, positionConfig, settlementConfig, matchingConfig, feeConfig, liquidityConfig, collateralEngine, oracleEngine, &mktCfg, tm.timeService, tm.broker, mas, statevarEngine, marketActivityTracker, cfgAsset, - peggedOrderCounterForTest, referralDiscountReward, volumeDiscount, banking, parties, + peggedOrderCounterForTest, referralDiscountReward, volumeDiscount, volumeRebate, banking, parties, ) require.NoError(tm.t, err) @@ -648,22 +650,25 @@ func getTestMarket2WithDP( epoch.EXPECT().NotifyOnEpoch(gomock.Any(), gomock.Any()).Times(1) teams := mocks.NewMockTeams(tm.ctrl) bc := mocks.NewMockAccountBalanceChecker(tm.ctrl) - marketActivityTracker := common.NewMarketActivityTracker(logging.NewTestLogger(), teams, bc, broker) + marketActivityTracker := common.NewMarketActivityTracker(logging.NewTestLogger(), teams, bc, broker, collateralEngine) epoch.NotifyOnEpoch(marketActivityTracker.OnEpochEvent, marketActivityTracker.OnEpochRestore) referralDiscountReward := fmocks.NewMockReferralDiscountRewardService(tm.ctrl) volumeDiscount := fmocks.NewMockVolumeDiscountService(tm.ctrl) + volumeRebate := fmocks.NewMockVolumeRebateService(tm.ctrl) + referralDiscountReward.EXPECT().GetReferrer(gomock.Any()).Return(types.PartyID(""), errors.New("no referrer")).AnyTimes() - referralDiscountReward.EXPECT().ReferralDiscountFactorForParty(gomock.Any()).Return(num.DecimalZero()).AnyTimes() - referralDiscountReward.EXPECT().RewardsFactorMultiplierAppliedForParty(gomock.Any()).Return(num.DecimalZero()).AnyTimes() - volumeDiscount.EXPECT().VolumeDiscountFactorForParty(gomock.Any()).Return(num.DecimalZero()).AnyTimes() + referralDiscountReward.EXPECT().ReferralDiscountFactorsForParty(gomock.Any()).Return(types.EmptyFactors).AnyTimes() + referralDiscountReward.EXPECT().RewardsFactorsMultiplierAppliedForParty(gomock.Any()).Return(types.EmptyFactors).AnyTimes() + volumeDiscount.EXPECT().VolumeDiscountFactorForParty(gomock.Any()).Return(types.EmptyFactors).AnyTimes() + volumeRebate.EXPECT().VolumeRebateFactorForParty(gomock.Any()).Return(num.DecimalZero()).AnyTimes() banking := mocks.NewMockBanking(ctrl) parties := mocks.NewMockParties(ctrl) mktEngine, err := future.NewMarket(context.Background(), log, riskConfig, positionConfig, settlementConfig, matchingConfig, feeConfig, liquidityConfig, collateralEngine, oracleEngine, mktCfg, timeService, broker, mas, statevar, marketActivityTracker, cfgAsset, - peggedOrderCounterForTest, referralDiscountReward, volumeDiscount, banking, parties) + peggedOrderCounterForTest, referralDiscountReward, volumeDiscount, volumeRebate, banking, parties) if err != nil { t.Fatalf("couldn't create a market: %v", err) } diff --git a/core/execution/snapshot_test.go b/core/execution/snapshot_test.go index 6bb7f899871..359475abe5b 100644 --- a/core/execution/snapshot_test.go +++ b/core/execution/snapshot_test.go @@ -610,7 +610,7 @@ func getEngine(t *testing.T, vegaPath paths.Paths, now time.Time) *snapshotTestD ctrl := gomock.NewController(t) teams := mocks.NewMockTeams(ctrl) bc := mocks.NewMockAccountBalanceChecker(ctrl) - marketActivityTracker := common.NewMarketActivityTracker(logging.NewTestLogger(), teams, bc, broker) + marketActivityTracker := common.NewMarketActivityTracker(logging.NewTestLogger(), teams, bc, broker, collateralEngine) epochEngine.NotifyOnEpoch(marketActivityTracker.OnEpochEvent, marketActivityTracker.OnEpochRestore) ethAsset := types.Asset{ @@ -624,9 +624,10 @@ func getEngine(t *testing.T, vegaPath paths.Paths, now time.Time) *snapshotTestD require.NoError(t, collateralEngine.EnableAsset(context.Background(), ethAsset)) referralDiscountReward := fmock.NewMockReferralDiscountRewardService(ctrl) volumeDiscount := fmock.NewMockVolumeDiscountService(ctrl) - referralDiscountReward.EXPECT().ReferralDiscountFactorForParty(gomock.Any()).Return(num.DecimalZero()).AnyTimes() - referralDiscountReward.EXPECT().RewardsFactorMultiplierAppliedForParty(gomock.Any()).Return(num.DecimalZero()).AnyTimes() - volumeDiscount.EXPECT().VolumeDiscountFactorForParty(gomock.Any()).Return(num.DecimalZero()).AnyTimes() + volumeRebate := fmock.NewMockVolumeRebateService(ctrl) + referralDiscountReward.EXPECT().ReferralDiscountFactorsForParty(gomock.Any()).Return(types.EmptyFactors).AnyTimes() + referralDiscountReward.EXPECT().RewardsFactorsMultiplierAppliedForParty(gomock.Any()).Return(types.EmptyFactors).AnyTimes() + volumeDiscount.EXPECT().VolumeDiscountFactorForParty(gomock.Any()).Return(types.EmptyFactors).AnyTimes() referralDiscountReward.EXPECT().GetReferrer(gomock.Any()).Return(types.PartyID(""), errors.New("not a referrer")).AnyTimes() banking := mocks.NewMockBanking(ctrl) parties := mocks.NewMockParties(ctrl) @@ -644,6 +645,7 @@ func getEngine(t *testing.T, vegaPath paths.Paths, now time.Time) *snapshotTestD stubs.NewAssetStub(), referralDiscountReward, volumeDiscount, + volumeRebate, banking, parties, delayTarget, @@ -680,7 +682,7 @@ func getEngineWithParties(t *testing.T, now time.Time, balance *num.Uint, partie ctrl := gomock.NewController(t) teams := mocks.NewMockTeams(ctrl) bc := mocks.NewMockAccountBalanceChecker(ctrl) - marketActivityTracker := common.NewMarketActivityTracker(logging.NewTestLogger(), teams, bc, broker) + marketActivityTracker := common.NewMarketActivityTracker(logging.NewTestLogger(), teams, bc, broker, collateralEngine) epochEngine.NotifyOnEpoch(marketActivityTracker.OnEpochEvent, marketActivityTracker.OnEpochRestore) ethAsset := types.Asset{ @@ -697,9 +699,11 @@ func getEngineWithParties(t *testing.T, now time.Time, balance *num.Uint, partie } referralDiscountReward := fmock.NewMockReferralDiscountRewardService(ctrl) volumeDiscount := fmock.NewMockVolumeDiscountService(ctrl) - referralDiscountReward.EXPECT().ReferralDiscountFactorForParty(gomock.Any()).Return(num.DecimalZero()).AnyTimes() - referralDiscountReward.EXPECT().RewardsFactorMultiplierAppliedForParty(gomock.Any()).Return(num.DecimalZero()).AnyTimes() - volumeDiscount.EXPECT().VolumeDiscountFactorForParty(gomock.Any()).Return(num.DecimalZero()).AnyTimes() + volumeRebate := fmock.NewMockVolumeRebateService(ctrl) + + referralDiscountReward.EXPECT().ReferralDiscountFactorsForParty(gomock.Any()).Return(types.EmptyFactors).AnyTimes() + referralDiscountReward.EXPECT().RewardsFactorsMultiplierAppliedForParty(gomock.Any()).Return(types.EmptyFactors).AnyTimes() + volumeDiscount.EXPECT().VolumeDiscountFactorForParty(gomock.Any()).Return(types.EmptyFactors).AnyTimes() referralDiscountReward.EXPECT().GetReferrer(gomock.Any()).Return(types.PartyID(""), errors.New("not a referrer")).AnyTimes() banking := mocks.NewMockBanking(ctrl) partiesMock := mocks.NewMockParties(ctrl) @@ -717,6 +721,7 @@ func getEngineWithParties(t *testing.T, now time.Time, balance *num.Uint, partie stubs.NewAssetStub(), referralDiscountReward, volumeDiscount, + volumeRebate, banking, partiesMock, delayTarget, diff --git a/core/execution/spot/market.go b/core/execution/spot/market.go index a2e40ab43df..aa15fe8e9ec 100644 --- a/core/execution/spot/market.go +++ b/core/execution/spot/market.go @@ -82,6 +82,7 @@ type Market struct { fee *fee.Engine referralDiscountRewardService fee.ReferralDiscountRewardService volumeDiscountService fee.VolumeDiscountService + volumeRebateService fee.VolumeRebateService liquidity *common.MarketLiquidity liquidityEngine common.LiquidityEngine @@ -159,6 +160,7 @@ func NewMarket( peggedOrderNotify func(int64), referralDiscountRewardService fee.ReferralDiscountRewardService, volumeDiscountService fee.VolumeDiscountService, + volumeRebateService fee.VolumeRebateService, banking common.Banking, ) (*Market, error) { if len(mkt.ID) == 0 { @@ -232,6 +234,7 @@ func NewMarket( fee: feeEngine, referralDiscountRewardService: referralDiscountRewardService, volumeDiscountService: volumeDiscountService, + volumeRebateService: volumeRebateService, parties: map[string]struct{}{}, as: as, pMonitor: pMonitor, @@ -3177,15 +3180,15 @@ func (m *Market) calculateFeesForTrades(trades []*types.Trade) (events.FeesTrans err error ) if !m.as.InAuction() { - fees, err = m.fee.CalculateForContinuousMode(trades, m.referralDiscountRewardService, m.volumeDiscountService) + fees, err = m.fee.CalculateForContinuousMode(trades, m.referralDiscountRewardService, m.volumeDiscountService, m.volumeRebateService) } else if m.as.IsMonitorAuction() { // we are in auction mode - fees, err = m.fee.CalculateForAuctionMode(trades, m.referralDiscountRewardService, m.volumeDiscountService) + fees, err = m.fee.CalculateForAuctionMode(trades, m.referralDiscountRewardService, m.volumeDiscountService, m.volumeRebateService) } else if m.as.IsFBA() { - fees, err = m.fee.CalculateForFrequentBatchesAuctionMode(trades, m.referralDiscountRewardService, m.volumeDiscountService) + fees, err = m.fee.CalculateForFrequentBatchesAuctionMode(trades, m.referralDiscountRewardService, m.volumeDiscountService, m.volumeRebateService) } else { if !m.as.IsOpeningAuction() { - fees, err = m.fee.CalculateForAuctionMode(trades, m.referralDiscountRewardService, m.volumeDiscountService) + fees, err = m.fee.CalculateForAuctionMode(trades, m.referralDiscountRewardService, m.volumeDiscountService, m.volumeRebateService) } } return fees, err diff --git a/core/execution/spot/market_callbacks.go b/core/execution/spot/market_callbacks.go index 77cc9fa13de..436bc46e2ad 100644 --- a/core/execution/spot/market_callbacks.go +++ b/core/execution/spot/market_callbacks.go @@ -46,6 +46,18 @@ func (m *Market) OnMarketProbabilityOfTradingTauScalingUpdate(_ context.Context, m.liquidity.OnProbabilityOfTradingTauScalingUpdate(d) } +func (m *Market) OnFeeFactorsTreasuryFeeUpdate(ctx context.Context, d num.Decimal) { + m.fee.OnFeeFactorsTreasuryFeeUpdate(d) + m.mkt.Fees.Factors.TreasuryFee = d + m.broker.Send(events.NewMarketUpdatedEvent(ctx, *m.mkt)) +} + +func (m *Market) OnFeeFactorsBuyBackFeeUpdate(ctx context.Context, d num.Decimal) { + m.fee.OnFeeFactorsBuyBackFeeUpdate(d) + m.mkt.Fees.Factors.BuyBackFee = d + m.broker.Send(events.NewMarketUpdatedEvent(ctx, *m.mkt)) +} + func (m *Market) OnFeeFactorsMakerFeeUpdate(ctx context.Context, d num.Decimal) { m.fee.OnFeeFactorsMakerFeeUpdate(d) m.mkt.Fees.Factors.MakerFee = d diff --git a/core/execution/spot/market_snapshot.go b/core/execution/spot/market_snapshot.go index 3c27306163a..252963c965e 100644 --- a/core/execution/spot/market_snapshot.go +++ b/core/execution/spot/market_snapshot.go @@ -64,6 +64,7 @@ func NewMarketFromSnapshot( peggedOrderNotify func(int64), referralDiscountRewardService fee.ReferralDiscountRewardService, volumeDiscountService fee.VolumeDiscountService, + volumeRebateService fee.VolumeRebateService, banking common.Banking, ) (*Market, error) { mkt := em.Market @@ -158,6 +159,7 @@ func NewMarketFromSnapshot( fee: feeEngine, referralDiscountRewardService: referralDiscountRewardService, volumeDiscountService: volumeDiscountService, + volumeRebateService: volumeRebateService, liquidity: marketLiquidity, liquidityEngine: liquidity, parties: map[string]struct{}{}, diff --git a/core/execution/spot/market_test.go b/core/execution/spot/market_test.go index c8b84aa9289..41d3393b039 100644 --- a/core/execution/spot/market_test.go +++ b/core/execution/spot/market_test.go @@ -210,7 +210,7 @@ func newTestMarket( teams := mocks.NewMockTeams(ctrl) bc := mocks.NewMockAccountBalanceChecker(ctrl) broker.EXPECT().SendBatch(gomock.Any()).Times(1) - mat := common.NewMarketActivityTracker(log, teams, bc, broker) + mat := common.NewMarketActivityTracker(log, teams, bc, broker, collateral) epoch.NotifyOnEpoch(mat.OnEpochEvent, mat.OnEpochRestore) baseAsset := NewAssetStub(base, baseDP) @@ -218,13 +218,15 @@ func newTestMarket( referralDiscountReward := fmocks.NewMockReferralDiscountRewardService(ctrl) volumeDiscount := fmocks.NewMockVolumeDiscountService(ctrl) + volumeRebate := fmocks.NewMockVolumeRebateService(ctrl) referralDiscountReward.EXPECT().GetReferrer(gomock.Any()).Return(types.PartyID(""), errors.New("no referrer")).AnyTimes() - referralDiscountReward.EXPECT().ReferralDiscountFactorForParty(gomock.Any()).Return(num.DecimalZero()).AnyTimes() - referralDiscountReward.EXPECT().RewardsFactorMultiplierAppliedForParty(gomock.Any()).Return(num.DecimalZero()).AnyTimes() - volumeDiscount.EXPECT().VolumeDiscountFactorForParty(gomock.Any()).Return(num.DecimalZero()).AnyTimes() + referralDiscountReward.EXPECT().ReferralDiscountFactorsForParty(gomock.Any()).Return(types.EmptyFactors).AnyTimes() + referralDiscountReward.EXPECT().RewardsFactorsMultiplierAppliedForParty(gomock.Any()).Return(types.EmptyFactors).AnyTimes() + volumeDiscount.EXPECT().VolumeDiscountFactorForParty(gomock.Any()).Return(types.EmptyFactors).AnyTimes() + volumeRebate.EXPECT().VolumeRebateFactorForParty(gomock.Any()).Return(num.DecimalZero()).AnyTimes() banking := mocks.NewMockBanking(ctrl) - market, _ := spot.NewMarket(log, matching.NewDefaultConfig(), fee.NewDefaultConfig(), liquidity.NewDefaultConfig(), collateral, &mkt, ts, broker, as, statevarEngine, mat, baseAsset, quoteAsset, peggedOrderCounterForTest, referralDiscountReward, volumeDiscount, banking) + market, _ := spot.NewMarket(log, matching.NewDefaultConfig(), fee.NewDefaultConfig(), liquidity.NewDefaultConfig(), collateral, &mkt, ts, broker, as, statevarEngine, mat, baseAsset, quoteAsset, peggedOrderCounterForTest, referralDiscountReward, volumeDiscount, volumeRebate, banking) tm := &testMarket{ market: market, diff --git a/core/fee/engine.go b/core/fee/engine.go index 516988a724e..1322e8de467 100644 --- a/core/fee/engine.go +++ b/core/fee/engine.go @@ -31,15 +31,19 @@ var ( ErrInvalidFeeFactor = errors.New("fee factors must be positive") ) -//go:generate go run github.com/golang/mock/mockgen -destination mocks/mocks.go -package mocks code.vegaprotocol.io/vega/core/fee ReferralDiscountRewardService,VolumeDiscountService +//go:generate go run github.com/golang/mock/mockgen -destination mocks/mocks.go -package mocks code.vegaprotocol.io/vega/core/fee ReferralDiscountRewardService,VolumeDiscountService,VolumeRebateService type ReferralDiscountRewardService interface { - ReferralDiscountFactorForParty(party types.PartyID) num.Decimal - RewardsFactorMultiplierAppliedForParty(party types.PartyID) num.Decimal + ReferralDiscountFactorsForParty(party types.PartyID) types.Factors + RewardsFactorsMultiplierAppliedForParty(party types.PartyID) types.Factors GetReferrer(referee types.PartyID) (types.PartyID, error) } type VolumeDiscountService interface { - VolumeDiscountFactorForParty(party types.PartyID) num.Decimal + VolumeDiscountFactorForParty(party types.PartyID) types.Factors +} + +type VolumeRebateService interface { + VolumeRebateFactorForParty(party types.PartyID) num.Decimal } type Engine struct { @@ -58,6 +62,8 @@ type factors struct { makerFee num.Decimal infrastructureFee num.Decimal liquidityFee num.Decimal + treasuryFee num.Decimal + buyBackFee num.Decimal } func New( @@ -128,7 +134,7 @@ func (e *Engine) ReloadConf(cfg Config) { } func (e *Engine) UpdateFeeFactors(fees types.Fees) error { - if fees.Factors.MakerFee.IsNegative() || fees.Factors.InfrastructureFee.IsNegative() || fees.Factors.LiquidityFee.IsNegative() { + if fees.Factors.MakerFee.IsNegative() || fees.Factors.InfrastructureFee.IsNegative() || fees.Factors.LiquidityFee.IsNegative() || fees.Factors.BuyBackFee.IsNegative() || fees.Factors.TreasuryFee.IsNegative() { return ErrInvalidFeeFactor } e.f.makerFee = fees.Factors.MakerFee @@ -137,6 +143,8 @@ func (e *Engine) UpdateFeeFactors(fees types.Fees) error { if !fees.Factors.LiquidityFee.IsZero() && fees.Factors.LiquidityFee.IsPositive() { e.f.liquidityFee = fees.Factors.LiquidityFee } + e.f.treasuryFee = fees.Factors.TreasuryFee + e.f.buyBackFee = fees.Factors.BuyBackFee e.feeCfg = fees return nil @@ -155,6 +163,7 @@ func (e *Engine) CalculateForContinuousMode( trades []*types.Trade, referral ReferralDiscountRewardService, volumeDiscountService VolumeDiscountService, + volumeRebateService VolumeRebateService, ) (events.FeesTransfer, error) { if len(trades) <= 0 { return nil, ErrEmptyTrades @@ -179,7 +188,10 @@ func (e *Engine) CalculateForContinuousMode( taker = trade.Seller maker = trade.Buyer } - fee, reward := e.applyDiscountsAndRewards(taker, e.calculateContinuousModeFees(trade), referral, volumeDiscountService) + size := num.NewUint(trade.Size) + // multiply by size + tradeValueForFee := size.Mul(trade.Price, size).ToDecimal().Div(e.positionFactor) + fee, reward := e.applyDiscountsAndRewards(taker, maker, tradeValueForFee, e.calculateContinuousModeFees(trade), referral, volumeDiscountService, volumeRebateService) e.feesStats.RegisterMakerFee(maker, taker, fee.MakerFee) @@ -221,6 +233,47 @@ func (e *Engine) CalculateForContinuousMode( Type: types.TransferTypeMakerFeeReceive, }) + if !fee.HighVolumeMakerFee.IsZero() { + // create a transfer for the aggressor + transfers = append(transfers, &types.Transfer{ + Owner: taker, + Amount: &types.FinancialAmount{ + Asset: e.asset, + Amount: fee.HighVolumeMakerFee.Clone(), + }, + Type: types.TransferTypeHighMakerRebatePay, + }) + // create a transfer for the maker + transfersRecv = append(transfersRecv, &types.Transfer{ + Owner: maker, + Amount: &types.FinancialAmount{ + Asset: e.asset, + Amount: fee.HighVolumeMakerFee.Clone(), + }, + Type: types.TransferTypeHighMakerRebateReceive, + }) + } + + // create a transfer for the aggressor + transfers = append(transfers, &types.Transfer{ + Owner: taker, + Amount: &types.FinancialAmount{ + Asset: e.asset, + Amount: fee.BuyBackFee.Clone(), + }, + Type: types.TransferTypeBuyBackFeePay, + }) + + // create a transfer for the aggressor + transfers = append(transfers, &types.Transfer{ + Owner: taker, + Amount: &types.FinancialAmount{ + Asset: e.asset, + Amount: fee.TreasuryFee.Clone(), + }, + Type: types.TransferTypeTreasuryPay, + }) + if reward == nil { continue } @@ -284,6 +337,7 @@ func (e *Engine) CalculateForAuctionMode( trades []*types.Trade, referral ReferralDiscountRewardService, volumeDiscount VolumeDiscountService, + volumeRebate VolumeRebateService, ) (events.FeesTransfer, error) { if len(trades) <= 0 { return nil, ErrEmptyTrades @@ -299,7 +353,7 @@ func (e *Engine) CalculateForAuctionMode( // for each trades both party needs to pay half of the fees // no maker fees are to be paid here. for _, v := range trades { - buyerFess, sellerFees, newTransfers := e.getAuctionModeFeesAndTransfers(v, referral, volumeDiscount) + buyerFess, sellerFees, newTransfers := e.getAuctionModeFeesAndTransfers(v, referral, volumeDiscount, volumeRebate) transfers = append(transfers, newTransfers...) // increase the total fee for the parties @@ -333,6 +387,7 @@ func (e *Engine) CalculateForFrequentBatchesAuctionMode( trades []*types.Trade, referral ReferralDiscountRewardService, volumeDiscount VolumeDiscountService, + volumeRebate VolumeRebateService, ) (events.FeesTransfer, error) { if len(trades) <= 0 { return nil, ErrEmptyTrades @@ -357,7 +412,7 @@ func (e *Engine) CalculateForFrequentBatchesAuctionMode( ) // we are in the same auction, normal auction fees applies if v.BuyerAuctionBatch == v.SellerAuctionBatch { - v.BuyerFee, v.SellerFee, newTransfers = e.getAuctionModeFeesAndTransfers(v, referral, volumeDiscount) + v.BuyerFee, v.SellerFee, newTransfers = e.getAuctionModeFeesAndTransfers(v, referral, volumeDiscount, volumeRebate) sellerTotalFee = num.Sum(v.BuyerFee.InfrastructureFee, v.BuyerFee.LiquidityFee) buyerTotalFee = num.Sum(v.SellerFee.InfrastructureFee, v.SellerFee.LiquidityFee) } else { @@ -369,7 +424,7 @@ func (e *Engine) CalculateForFrequentBatchesAuctionMode( } // fees are being assign to the trade directly // no need to do add them there as well - ftrnsfr, _ := e.CalculateForContinuousMode([]*types.Trade{v}, referral, volumeDiscount) + ftrnsfr, _ := e.CalculateForContinuousMode([]*types.Trade{v}, referral, volumeDiscount, volumeRebate) newTransfers = ftrnsfr.Transfers() buyerTotalFee = ftrnsfr.TotalFeesAmountPerParty()[v.Buyer] sellerTotalFee = ftrnsfr.TotalFeesAmountPerParty()[v.Seller] @@ -510,6 +565,8 @@ func (e *Engine) getNetworkFeeWithMakerTransfer(fees *types.Fee, current *types. current.MakerFee.AddSum(fees.MakerFee) current.LiquidityFee.AddSum(fees.LiquidityFee) current.InfrastructureFee.AddSum(fees.InfrastructureFee) + current.BuyBackFee.AddSum(fees.BuyBackFee) + current.TreasuryFee.AddSum(fees.TreasuryFee) return current, transfer } @@ -542,21 +599,41 @@ func (e *Engine) getNetworkFeeTransfers(fees *types.Fee) ([]*types.Transfer, *nu MinAmount: num.UintZero(), Type: types.TransferTypeLiquidityFeePay, }, + { + Owner: types.NetworkParty, + Amount: &types.FinancialAmount{ + Asset: e.asset, + Amount: fees.BuyBackFee.Clone(), + }, + MinAmount: num.UintZero(), + Type: types.TransferTypeBuyBackFeePay, + }, + { + Owner: types.NetworkParty, + Amount: &types.FinancialAmount{ + Asset: e.asset, + Amount: fees.TreasuryFee.Clone(), + }, + MinAmount: num.UintZero(), + Type: types.TransferTypeTreasuryPay, + }, }, num.Sum(fees.MakerFee, fees.InfrastructureFee, fees.LiquidityFee) } -func (e *Engine) applyDiscountsAndRewards(taker string, fees *types.Fee, referral ReferralDiscountRewardService, volumeDiscount VolumeDiscountService) (*types.Fee, *types.ReferrerReward) { - referralDiscountFactor := referral.ReferralDiscountFactorForParty(types.PartyID(taker)) - volumeDiscountFactor := volumeDiscount.VolumeDiscountFactorForParty(types.PartyID(taker)) +func (e *Engine) applyDiscountsAndRewards(taker string, maker string, tradeValueForFeePurposes num.Decimal, fees *types.Fee, referral ReferralDiscountRewardService, volumeDiscount VolumeDiscountService, volumeRebate VolumeRebateService) (*types.Fee, *types.ReferrerReward) { + referralDiscountFactors := referral.ReferralDiscountFactorsForParty(types.PartyID(taker)) + volumeDiscountFactors := volumeDiscount.VolumeDiscountFactorForParty(types.PartyID(taker)) + highVolumeMakerFee := volumeRebate.VolumeRebateFactorForParty(types.PartyID(maker)).Mul(tradeValueForFeePurposes) + highVolumeMakerFeeI, _ := num.UintFromDecimal(highVolumeMakerFee) mf := fees.MakerFee.Clone() inf := fees.InfrastructureFee.Clone() lf := fees.LiquidityFee.Clone() // calculate referral discounts - referralMakerDiscount, _ := num.UintFromDecimal(mf.ToDecimal().Mul(referralDiscountFactor).Floor()) - referralInfDiscount, _ := num.UintFromDecimal(inf.ToDecimal().Mul(referralDiscountFactor).Floor()) - referralLfDiscount, _ := num.UintFromDecimal(lf.ToDecimal().Mul(referralDiscountFactor).Floor()) + referralMakerDiscount, _ := num.UintFromDecimal(mf.ToDecimal().Mul(referralDiscountFactors.Maker).Floor()) + referralInfDiscount, _ := num.UintFromDecimal(inf.ToDecimal().Mul(referralDiscountFactors.Infra).Floor()) + referralLfDiscount, _ := num.UintFromDecimal(lf.ToDecimal().Mul(referralDiscountFactors.Liquidity).Floor()) // apply referral discounts mf = mf.Sub(mf, referralMakerDiscount) @@ -564,9 +641,18 @@ func (e *Engine) applyDiscountsAndRewards(taker string, fees *types.Fee, referra lf = lf.Sub(lf, referralLfDiscount) // calculate volume discounts - volumeMakerDiscount, _ := num.UintFromDecimal(mf.ToDecimal().Mul(volumeDiscountFactor).Floor()) - volumeInfDiscount, _ := num.UintFromDecimal(inf.ToDecimal().Mul(volumeDiscountFactor).Floor()) - volumeLfDiscount, _ := num.UintFromDecimal(lf.ToDecimal().Mul(volumeDiscountFactor).Floor()) + volumeMakerDiscount, _ := num.UintFromDecimal(mf.ToDecimal().Mul(volumeDiscountFactors.Maker).Floor()) + volumeInfDiscount, _ := num.UintFromDecimal(inf.ToDecimal().Mul(volumeDiscountFactors.Infra).Floor()) + volumeLfDiscount, _ := num.UintFromDecimal(lf.ToDecimal().Mul(volumeDiscountFactors.Liquidity).Floor()) + + var rebateDiscountFactor num.Decimal + bbAndTreasury := num.Sum(fees.BuyBackFee, fees.TreasuryFee).ToDecimal() + if !bbAndTreasury.IsZero() { + rebateDiscountFactor = num.DecimalOne().Sub(highVolumeMakerFee.Div(bbAndTreasury)) + } + + treasuryFee, _ := num.UintFromDecimal(fees.TreasuryFee.ToDecimal().Mul(rebateDiscountFactor)) + buyBackFee, _ := num.UintFromDecimal(fees.BuyBackFee.ToDecimal().Mul(rebateDiscountFactor)) // apply volume discounts mf = mf.Sub(mf, volumeMakerDiscount) @@ -574,9 +660,12 @@ func (e *Engine) applyDiscountsAndRewards(taker string, fees *types.Fee, referra lf = lf.Sub(lf, volumeLfDiscount) f := &types.Fee{ + HighVolumeMakerFee: highVolumeMakerFeeI, MakerFee: mf, LiquidityFee: lf, InfrastructureFee: inf, + BuyBackFee: buyBackFee.Clone(), + TreasuryFee: treasuryFee.Clone(), MakerFeeVolumeDiscount: volumeMakerDiscount, InfrastructureFeeVolumeDiscount: volumeInfDiscount, LiquidityFeeVolumeDiscount: volumeLfDiscount, @@ -604,16 +693,16 @@ func (e *Engine) applyDiscountsAndRewards(taker string, fees *types.Fee, referra ) // calculate rewards - factor := referral.RewardsFactorMultiplierAppliedForParty(types.PartyID(taker)) - if factor.IsZero() { + factors := referral.RewardsFactorsMultiplierAppliedForParty(types.PartyID(taker)) + if factors == types.EmptyFactors { return f, nil } referrerReward := types.NewReferrerReward() - referrerReward.MakerFeeReferrerReward, _ = num.UintFromDecimal(factor.Mul(mf.ToDecimal()).Floor()) - referrerReward.InfrastructureFeeReferrerReward, _ = num.UintFromDecimal(factor.Mul(inf.ToDecimal()).Floor()) - referrerReward.LiquidityFeeReferrerReward, _ = num.UintFromDecimal(factor.Mul(lf.ToDecimal()).Floor()) + referrerReward.MakerFeeReferrerReward, _ = num.UintFromDecimal(factors.Maker.Mul(mf.ToDecimal()).Floor()) + referrerReward.InfrastructureFeeReferrerReward, _ = num.UintFromDecimal(factors.Infra.Mul(inf.ToDecimal()).Floor()) + referrerReward.LiquidityFeeReferrerReward, _ = num.UintFromDecimal(factors.Liquidity.Mul(lf.ToDecimal()).Floor()) mf = mf.Sub(mf, referrerReward.MakerFeeReferrerReward) inf = inf.Sub(inf, referrerReward.InfrastructureFeeReferrerReward) @@ -640,18 +729,19 @@ func (e *Engine) applyDiscountsAndRewards(taker string, fees *types.Fee, referra return f, referrerReward } -func (e *Engine) getAuctionModeFeesAndTransfers(t *types.Trade, referral ReferralDiscountRewardService, volumeDiscount VolumeDiscountService) (*types.Fee, *types.Fee, []*types.Transfer) { +func (e *Engine) getAuctionModeFeesAndTransfers(t *types.Trade, referral ReferralDiscountRewardService, volumeDiscount VolumeDiscountService, volumeRebate VolumeRebateService) (*types.Fee, *types.Fee, []*types.Transfer) { fee := e.calculateAuctionModeFees(t) - buyerFeers, buyerReferrerRewards := e.applyDiscountsAndRewards(t.Buyer, fee, referral, volumeDiscount) - sellerFeers, sellerReferrerRewards := e.applyDiscountsAndRewards(t.Seller, fee, referral, volumeDiscount) + // in auction there is no maker so there is no rebate, so passing 0 as the trade value + buyerFees, buyerReferrerRewards := e.applyDiscountsAndRewards(t.Buyer, t.Buyer, num.DecimalZero(), fee, referral, volumeDiscount, volumeRebate) + sellerFees, sellerReferrerRewards := e.applyDiscountsAndRewards(t.Seller, t.Seller, num.DecimalZero(), fee, referral, volumeDiscount, volumeRebate) transfers := make([]*types.Transfer, 0, 12) transfers = append(transfers, e.getAuctionModeFeeTransfers( - sellerFeers.InfrastructureFee, sellerFeers.LiquidityFee, t.Seller)...) + sellerFees.InfrastructureFee, sellerFees.LiquidityFee, sellerFees.BuyBackFee, sellerFees.TreasuryFee, t.Seller)...) transfers = append(transfers, e.getAuctionModeFeeTransfers( - buyerFeers.InfrastructureFee, buyerFeers.LiquidityFee, t.Buyer)...) + buyerFees.InfrastructureFee, buyerFees.LiquidityFee, buyerFees.BuyBackFee, buyerFees.TreasuryFee, t.Buyer)...) if buyerReferrerRewards != nil { referrerParty, _ := referral.GetReferrer(types.PartyID(t.Buyer)) @@ -667,7 +757,7 @@ func (e *Engine) getAuctionModeFeesAndTransfers(t *types.Trade, referral Referra num.Sum(sellerReferrerRewards.InfrastructureFeeReferrerReward, sellerReferrerRewards.LiquidityFeeReferrerReward), t.Seller, string(referrerParty))...) } - return buyerFeers, sellerFeers, transfers + return buyerFees, sellerFees, transfers } func (e *Engine) calculateContinuousModeFees(trade *types.Trade) *types.Fee { @@ -677,10 +767,15 @@ func (e *Engine) calculateContinuousModeFees(trade *types.Trade) *types.Fee { mf, _ := num.UintFromDecimal(total.Mul(e.f.makerFee).Ceil()) inf, _ := num.UintFromDecimal(total.Mul(e.f.infrastructureFee).Ceil()) lf, _ := num.UintFromDecimal(total.Mul(e.f.liquidityFee).Ceil()) + bbf, _ := num.UintFromDecimal(total.Mul(e.f.buyBackFee).Ceil()) + tf, _ := num.UintFromDecimal(total.Mul(e.f.treasuryFee).Ceil()) return &types.Fee{ - MakerFee: mf, - InfrastructureFee: inf, - LiquidityFee: lf, + MakerFee: mf, + InfrastructureFee: inf, + LiquidityFee: lf, + BuyBackFee: bbf, + TreasuryFee: tf, + HighVolumeMakerFee: num.UintZero(), } } @@ -689,10 +784,14 @@ func (e *Engine) calculateAuctionModeFees(trade *types.Trade) *types.Fee { two := num.DecimalFromInt64(2) inf, _ := num.UintFromDecimal(fee.InfrastructureFee.ToDecimal().Div(two).Ceil()) lf, _ := num.UintFromDecimal(fee.LiquidityFee.ToDecimal().Div(two).Ceil()) + bbf, _ := num.UintFromDecimal(fee.BuyBackFee.ToDecimal().Div(two).Ceil()) + tf, _ := num.UintFromDecimal(fee.TreasuryFee.ToDecimal().Div(two).Ceil()) return &types.Fee{ MakerFee: num.UintZero(), InfrastructureFee: inf, LiquidityFee: lf, + TreasuryFee: tf, + BuyBackFee: bbf, } } @@ -718,7 +817,7 @@ func (e *Engine) getAuctionModeFeeReferrerRewardTransfers(reward *num.Uint, p, r } } -func (e *Engine) getAuctionModeFeeTransfers(infraFee, liquiFee *num.Uint, p string) []*types.Transfer { +func (e *Engine) getAuctionModeFeeTransfers(infraFee, liquiFee, buyBackFee, treasuryFee *num.Uint, p string) []*types.Transfer { // we return both transfer for the party in a slice // always the infrastructure fee first return []*types.Transfer{ @@ -738,6 +837,22 @@ func (e *Engine) getAuctionModeFeeTransfers(infraFee, liquiFee *num.Uint, p stri }, Type: types.TransferTypeLiquidityFeePay, }, + { + Owner: p, + Amount: &types.FinancialAmount{ + Asset: e.asset, + Amount: buyBackFee.Clone(), + }, + Type: types.TransferTypeBuyBackFeePay, + }, + { + Owner: p, + Amount: &types.FinancialAmount{ + Asset: e.asset, + Amount: treasuryFee.Clone(), + }, + Type: types.TransferTypeTreasuryPay, + }, } } @@ -760,6 +875,16 @@ func (e *Engine) OnFeeFactorsMakerFeeUpdate(f num.Decimal) { e.f.makerFee = f } +func (e *Engine) OnFeeFactorsBuyBackFeeUpdate(f num.Decimal) { + e.feeCfg.Factors.BuyBackFee = f + e.f.buyBackFee = f +} + +func (e *Engine) OnFeeFactorsTreasuryFeeUpdate(f num.Decimal) { + e.feeCfg.Factors.TreasuryFee = f + e.f.treasuryFee = f +} + func (e *Engine) OnFeeFactorsInfrastructureFeeUpdate(f num.Decimal) { e.feeCfg.Factors.InfrastructureFee = f e.f.infrastructureFee = f diff --git a/core/fee/engine_test.go b/core/fee/engine_test.go index 30c0889aceb..e9b145ceb54 100644 --- a/core/fee/engine_test.go +++ b/core/fee/engine_test.go @@ -35,13 +35,24 @@ const ( testAsset = "ETH" ) -var testFees = types.Fees{ - Factors: &types.FeeFactors{ - LiquidityFee: num.DecimalFromFloat(0.1), - InfrastructureFee: num.DecimalFromFloat(0.05), - MakerFee: num.DecimalFromFloat(0.02), - }, -} +var ( + testFees = types.Fees{ + Factors: &types.FeeFactors{ + LiquidityFee: num.DecimalFromFloat(0.1), + InfrastructureFee: num.DecimalFromFloat(0.05), + MakerFee: num.DecimalFromFloat(0.02), + }, + } + extendedTestFees = types.Fees{ + Factors: &types.FeeFactors{ + LiquidityFee: num.DecimalFromFloat(0.1), + InfrastructureFee: num.DecimalFromFloat(0.05), + MakerFee: num.DecimalFromFloat(0.02), + BuyBackFee: num.DecimalFromFloat(0.002), + TreasuryFee: num.DecimalFromFloat(0.003), + }, + } +) type testFee struct { *fee.Engine @@ -60,27 +71,47 @@ func getTestFee(t *testing.T) *testFee { return &testFee{eng} } +func getExtendedTestFee(t *testing.T) *testFee { + t.Helper() + eng, err := fee.New( + logging.NewTestLogger(), + fee.NewDefaultConfig(), + extendedTestFees, + testAsset, + num.DecimalFromInt64(1), + ) + assert.NoError(t, err) + return &testFee{eng} +} + func TestFeeEngine(t *testing.T) { t.Run("update fee factors with invalid input", testUpdateFeeFactorsError) t.Run("update fee factors with valid input", testUpdateFeeFactors) t.Run("calculate continuous trading fee empty trade", testCalcContinuousTradingErrorEmptyTrade) t.Run("calculate continuous trading fee", testCalcContinuousTrading) t.Run("calculate continuous trading fee + check amounts", testCalcContinuousTradingAndCheckAmounts) - t.Run("calculate continuous trading fee + check amounts with discounts and rewards", testCalcContinuousTradingAndCheckAmountsWithDiscount) - - t.Run("calculate continuous trading fee empty trade", testCalcContinuousTradingErrorEmptyTrade) t.Run("calculate auction trading fee empty trade", testCalcAuctionTradingErrorEmptyTrade) t.Run("calculate auction trading fee", testCalcAuctionTrading) - t.Run("calculate batch auction trading fee empty trade", testCalcBatchAuctionTradingErrorEmptyTrade) t.Run("calculate batch auction trading fee same batch", testCalcBatchAuctionTradingSameBatch) t.Run("calculate batch auction trading fee different batches", testCalcBatchAuctionTradingDifferentBatches) - t.Run("Build liquidity fee transfers with remainder", testBuildLiquidityFeesRemainder) t.Run("calculate closeout fees", testCloseoutFees) } +func TestFeeEngineWithBuyBackAndTreasury(t *testing.T) { + t.Run("update fee factors with invalid input", testUpdateExtendedFeeFactorsError) + t.Run("update fee factors with valid input", testUpdateExtendedFeeFactors) + t.Run("calculate continuous trading fee empty trade", testCalcContinuousTradingErrorEmptyTrade) + t.Run("calculate continuous trading fee", testCalcContinuousTradingExtended) + t.Run("calculate continuous trading fee + check amounts", testCalcContinuousTradingAndCheckAmountsExtended) + t.Run("calculate continuous trading fee + check amounts with discounts and rewards", testCalcContinuousTradingAndCheckAmountsWithDiscountExtended) + t.Run("calculate auction trading fee empty trade", testCalcAuctionTradingErrorEmptyTrade) + t.Run("calculate auction trading fee", testCalcAuctionTradingExtended) + t.Run("calculate batch auction trading fee empty trade", testCalcBatchAuctionTradingErrorEmptyTrade) +} + func testUpdateFeeFactors(t *testing.T) { eng := getTestFee(t) okFees := types.Fees{ @@ -94,6 +125,21 @@ func testUpdateFeeFactors(t *testing.T) { assert.NoError(t, err) } +func testUpdateExtendedFeeFactors(t *testing.T) { + eng := getExtendedTestFee(t) + okFees := types.Fees{ + Factors: &types.FeeFactors{ + LiquidityFee: num.DecimalFromFloat(0.1), + InfrastructureFee: num.DecimalFromFloat(0.5), + MakerFee: num.DecimalFromFloat(0.25), + BuyBackFee: num.DecimalFromFloat(0.3), + TreasuryFee: num.DecimalFromFloat(0.4), + }, + } + err := eng.UpdateFeeFactors(okFees) + assert.NoError(t, err) +} + func testUpdateFeeFactorsError(t *testing.T) { eng := getTestFee(t) koFees := types.Fees{ @@ -126,13 +172,46 @@ func testUpdateFeeFactorsError(t *testing.T) { assert.Error(t, err) } +func testUpdateExtendedFeeFactorsError(t *testing.T) { + eng := getExtendedTestFee(t) + koFees := types.Fees{ + Factors: &types.FeeFactors{ + LiquidityFee: num.DecimalFromFloat(0.1), + InfrastructureFee: num.DecimalFromFloat(0.5), + MakerFee: num.DecimalFromFloat(0.25), + BuyBackFee: num.DecimalFromFloat(-1), + TreasuryFee: num.DecimalFromFloat(0.4), + }, + } + err := eng.UpdateFeeFactors(koFees) + assert.Error(t, err) + + koFees = types.Fees{ + Factors: &types.FeeFactors{ + LiquidityFee: num.DecimalFromFloat(0.1), + InfrastructureFee: num.DecimalFromFloat(0.11), + MakerFee: num.DecimalFromFloat(0.25), + BuyBackFee: num.DecimalFromFloat(0.41), + TreasuryFee: num.DecimalFromFloat(-1), + }, + } + err = eng.UpdateFeeFactors(koFees) + assert.Error(t, err) +} + func testCalcContinuousTradingErrorEmptyTrade(t *testing.T) { eng := getTestFee(t) ctrl := gomock.NewController(t) discountRewardService := mocks.NewMockReferralDiscountRewardService(ctrl) volumeDiscountService := mocks.NewMockVolumeDiscountService(ctrl) + volumeRebateService := mocks.NewMockVolumeRebateService(ctrl) + volumeRebateService.EXPECT().VolumeRebateFactorForParty(gomock.Any()).Return(num.DecimalZero()).AnyTimes() - _, err := eng.CalculateForContinuousMode([]*types.Trade{}, discountRewardService, volumeDiscountService) + _, err := eng.CalculateForContinuousMode([]*types.Trade{}, discountRewardService, volumeDiscountService, volumeRebateService) + assert.EqualError(t, err, fee.ErrEmptyTrades.Error()) + + eng = getExtendedTestFee(t) + _, err = eng.CalculateForContinuousMode([]*types.Trade{}, discountRewardService, volumeDiscountService, volumeRebateService) assert.EqualError(t, err, fee.ErrEmptyTrades.Error()) } @@ -141,9 +220,11 @@ func testCalcContinuousTradingAndCheckAmounts(t *testing.T) { ctrl := gomock.NewController(t) discountRewardService := mocks.NewMockReferralDiscountRewardService(ctrl) volumeDiscountService := mocks.NewMockVolumeDiscountService(ctrl) - discountRewardService.EXPECT().ReferralDiscountFactorForParty(gomock.Any()).Return(num.DecimalZero()).AnyTimes() - discountRewardService.EXPECT().RewardsFactorMultiplierAppliedForParty(gomock.Any()).Return(num.DecimalZero()).AnyTimes() - volumeDiscountService.EXPECT().VolumeDiscountFactorForParty(gomock.Any()).Return(num.DecimalZero()).AnyTimes() + volumeRebateService := mocks.NewMockVolumeRebateService(ctrl) + volumeRebateService.EXPECT().VolumeRebateFactorForParty(gomock.Any()).Return(num.DecimalZero()).AnyTimes() + discountRewardService.EXPECT().ReferralDiscountFactorsForParty(gomock.Any()).Return(types.EmptyFactors).AnyTimes() + discountRewardService.EXPECT().RewardsFactorsMultiplierAppliedForParty(gomock.Any()).Return(types.EmptyFactors).AnyTimes() + volumeDiscountService.EXPECT().VolumeDiscountFactorForParty(gomock.Any()).Return(types.EmptyFactors).AnyTimes() discountRewardService.EXPECT().GetReferrer(gomock.Any()).Return(types.PartyID(""), errors.New("not a referrer")).AnyTimes() require.NoError(t, eng.UpdateFeeFactors(types.Fees{ Factors: &types.FeeFactors{ @@ -162,7 +243,7 @@ func testCalcContinuousTradingAndCheckAmounts(t *testing.T) { }, } - ft, err := eng.CalculateForContinuousMode(trades, discountRewardService, volumeDiscountService) + ft, err := eng.CalculateForContinuousMode(trades, discountRewardService, volumeDiscountService, volumeRebateService) assert.NotNil(t, ft) assert.Nil(t, err) transfers := ft.Transfers() @@ -244,14 +325,162 @@ func testCalcContinuousTradingAndCheckAmounts(t *testing.T) { }, eng.GetFeesStatsOnEpochEnd(num.DecimalFromInt64(1))) } +func testCalcContinuousTradingAndCheckAmountsExtended(t *testing.T) { + eng := getExtendedTestFee(t) + ctrl := gomock.NewController(t) + discountRewardService := mocks.NewMockReferralDiscountRewardService(ctrl) + volumeDiscountService := mocks.NewMockVolumeDiscountService(ctrl) + volumeRebateService := mocks.NewMockVolumeRebateService(ctrl) + volumeRebateService.EXPECT().VolumeRebateFactorForParty(types.PartyID("party2")).Return(num.DecimalFromFloat(0.0025)).AnyTimes() + volumeRebateService.EXPECT().VolumeRebateFactorForParty(gomock.Any()).Return(num.DecimalZero()).AnyTimes() + discountRewardService.EXPECT().ReferralDiscountFactorsForParty(gomock.Any()).Return(types.EmptyFactors).AnyTimes() + discountRewardService.EXPECT().RewardsFactorsMultiplierAppliedForParty(gomock.Any()).Return(types.EmptyFactors).AnyTimes() + volumeDiscountService.EXPECT().VolumeDiscountFactorForParty(gomock.Any()).Return(types.EmptyFactors).AnyTimes() + discountRewardService.EXPECT().GetReferrer(gomock.Any()).Return(types.PartyID(""), errors.New("not a referrer")).AnyTimes() + require.NoError(t, eng.UpdateFeeFactors(types.Fees{ + Factors: &types.FeeFactors{ + MakerFee: num.DecimalFromFloat(.000250), + InfrastructureFee: num.DecimalFromFloat(0.0005), + LiquidityFee: num.DecimalFromFloat(0.001), + BuyBackFee: num.DecimalFromFloat(0.002), + TreasuryFee: num.DecimalFromFloat(0.003), + }, + })) + trades := []*types.Trade{ + { + Aggressor: types.SideSell, + Seller: "party1", + Buyer: "party2", + Size: 5, + Price: num.NewUint(100000), + }, + } + + ft, err := eng.CalculateForContinuousMode(trades, discountRewardService, volumeDiscountService, volumeRebateService) + assert.NotNil(t, ft) + assert.Nil(t, err) + transfers := ft.Transfers() + var pay, recv, infra, liquidity, bb, treasury, hmp, hmr int + for _, v := range transfers { + if v.Type == types.TransferTypeLiquidityFeePay { + liquidity++ + assert.Equal(t, num.NewUint(500), v.Amount.Amount) + } + if v.Type == types.TransferTypeInfrastructureFeePay { + infra++ + assert.Equal(t, num.NewUint(250), v.Amount.Amount) + } + if v.Type == types.TransferTypeMakerFeeReceive { + recv++ + assert.Equal(t, num.NewUint(125), v.Amount.Amount) + } + if v.Type == types.TransferTypeMakerFeePay { + pay++ + assert.Equal(t, num.NewUint(125), v.Amount.Amount) + } + if v.Type == types.TransferTypeBuyBackFeePay { + bb++ + assert.Equal(t, num.NewUint(500), v.Amount.Amount) + } + if v.Type == types.TransferTypeTreasuryPay { + treasury++ + assert.Equal(t, num.NewUint(750), v.Amount.Amount) + } + if v.Type == types.TransferTypeHighMakerRebatePay { + hmp++ + assert.Equal(t, num.NewUint(1250), v.Amount.Amount) + } + if v.Type == types.TransferTypeHighMakerRebateReceive { + hmr++ + assert.Equal(t, num.NewUint(1250), v.Amount.Amount) + } + } + + assert.Equal(t, liquidity, 1) + assert.Equal(t, infra, 1) + assert.Equal(t, bb, 1) + assert.Equal(t, treasury, 1) + assert.Equal(t, hmp, 1) + assert.Equal(t, hmr, 1) + assert.Equal(t, recv, len(trades)) + assert.Equal(t, pay, len(trades)) + assert.Equal(t, &eventspb.FeesStats{ + Market: "", + Asset: testAsset, + EpochSeq: 0, + TotalRewardsReceived: []*eventspb.PartyAmount{}, + ReferrerRewardsGenerated: []*eventspb.ReferrerRewardsGenerated{}, + RefereesDiscountApplied: []*eventspb.PartyAmount{ + { + Party: "party1", + Amount: "0", + QuantumAmount: "0", + }, + }, + VolumeDiscountApplied: []*eventspb.PartyAmount{ + { + Party: "party1", + Amount: "0", + QuantumAmount: "0", + }, + }, + TotalMakerFeesReceived: []*eventspb.PartyAmount{ + { + Party: "party2", + Amount: "125", + QuantumAmount: "125", + }, + }, + MakerFeesGenerated: []*eventspb.MakerFeesGenerated{ + { + Taker: "party1", + MakerFeesPaid: []*eventspb.PartyAmount{ + { + Party: "party2", + Amount: "125", + QuantumAmount: "125", + }, + }, + }, + }, + TotalFeesPaidAndReceived: []*eventspb.PartyAmount{ + { + Party: "party1", + Amount: "875", + QuantumAmount: "875", + }, + { + Party: "party2", + Amount: "125", + QuantumAmount: "125", + }, + }, + }, eng.GetFeesStatsOnEpochEnd(num.DecimalFromInt64(1))) +} + func testCalcContinuousTradingAndCheckAmountsWithDiscount(t *testing.T) { eng := getTestFee(t) ctrl := gomock.NewController(t) discountRewardService := mocks.NewMockReferralDiscountRewardService(ctrl) volumeDiscountService := mocks.NewMockVolumeDiscountService(ctrl) - discountRewardService.EXPECT().ReferralDiscountFactorForParty(gomock.Any()).Return(num.DecimalFromFloat(0.3)).AnyTimes() - discountRewardService.EXPECT().RewardsFactorMultiplierAppliedForParty(gomock.Any()).Return(num.DecimalFromFloat(0.2)).AnyTimes() - volumeDiscountService.EXPECT().VolumeDiscountFactorForParty(gomock.Any()).Return(num.DecimalFromFloat(0.1)).AnyTimes() + volumeRebateService := mocks.NewMockVolumeRebateService(ctrl) + volumeRebateService.EXPECT().VolumeRebateFactorForParty(gomock.Any()).Return(num.DecimalZero()).AnyTimes() + discountRewardService.EXPECT().ReferralDiscountFactorsForParty(gomock.Any()).Return(types.Factors{ + Infra: num.NewDecimalFromFloat(0.3), + Maker: num.NewDecimalFromFloat(0.3), + Liquidity: num.NewDecimalFromFloat(0.3), + }).AnyTimes() + discountRewardService.EXPECT().RewardsFactorsMultiplierAppliedForParty(gomock.Any()).Return(types.Factors{ + Infra: num.NewDecimalFromFloat(0.2), + Maker: num.NewDecimalFromFloat(0.2), + Liquidity: num.NewDecimalFromFloat(0.2), + }).AnyTimes() + volumeDiscountService.EXPECT().VolumeDiscountFactorForParty(gomock.Any()).Return( + types.Factors{ + Infra: num.NewDecimalFromFloat(0.1), + Maker: num.NewDecimalFromFloat(0.1), + Liquidity: num.NewDecimalFromFloat(0.1), + }) discountRewardService.EXPECT().GetReferrer(gomock.Any()).Return(types.PartyID("party3"), nil).AnyTimes() require.NoError(t, eng.UpdateFeeFactors(types.Fees{ Factors: &types.FeeFactors{ @@ -270,7 +499,7 @@ func testCalcContinuousTradingAndCheckAmountsWithDiscount(t *testing.T) { }, } - ft, err := eng.CalculateForContinuousMode(trades, discountRewardService, volumeDiscountService) + ft, err := eng.CalculateForContinuousMode(trades, discountRewardService, volumeDiscountService, volumeRebateService) assert.NotNil(t, ft) assert.Nil(t, err) transfers := ft.Transfers() @@ -367,6 +596,166 @@ func testCalcContinuousTradingAndCheckAmountsWithDiscount(t *testing.T) { }, eng.GetFeesStatsOnEpochEnd(num.DecimalFromInt64(1))) } +func testCalcContinuousTradingAndCheckAmountsWithDiscountExtended(t *testing.T) { + eng := getExtendedTestFee(t) + ctrl := gomock.NewController(t) + discountRewardService := mocks.NewMockReferralDiscountRewardService(ctrl) + volumeDiscountService := mocks.NewMockVolumeDiscountService(ctrl) + volumeRebateService := mocks.NewMockVolumeRebateService(ctrl) + volumeRebateService.EXPECT().VolumeRebateFactorForParty(gomock.Any()).Return(num.DecimalFromFloat(0.0025)).AnyTimes() + discountRewardService.EXPECT().ReferralDiscountFactorsForParty(gomock.Any()).Return(types.Factors{ + Infra: num.NewDecimalFromFloat(0.3), + Maker: num.NewDecimalFromFloat(0.3), + Liquidity: num.NewDecimalFromFloat(0.3), + }).AnyTimes() + discountRewardService.EXPECT().RewardsFactorsMultiplierAppliedForParty(gomock.Any()).Return(types.Factors{ + Infra: num.NewDecimalFromFloat(0.2), + Maker: num.NewDecimalFromFloat(0.2), + Liquidity: num.NewDecimalFromFloat(0.2), + }).AnyTimes() + volumeDiscountService.EXPECT().VolumeDiscountFactorForParty(gomock.Any()).Return( + types.Factors{ + Infra: num.NewDecimalFromFloat(0.1), + Maker: num.NewDecimalFromFloat(0.1), + Liquidity: num.NewDecimalFromFloat(0.1), + }) + discountRewardService.EXPECT().GetReferrer(gomock.Any()).Return(types.PartyID("party3"), nil).AnyTimes() + require.NoError(t, eng.UpdateFeeFactors(types.Fees{ + Factors: &types.FeeFactors{ + MakerFee: num.DecimalFromFloat(.000250), + InfrastructureFee: num.DecimalFromFloat(0.0005), + LiquidityFee: num.DecimalFromFloat(0.001), + BuyBackFee: num.DecimalFromFloat(0.002), + TreasuryFee: num.DecimalFromFloat(0.003), + }, + })) + trades := []*types.Trade{ + { + Aggressor: types.SideSell, + Seller: "party1", + Buyer: "party2", + Size: 5, + Price: num.NewUint(100000), + }, + } + + ft, err := eng.CalculateForContinuousMode(trades, discountRewardService, volumeDiscountService, volumeRebateService) + assert.NotNil(t, ft) + assert.Nil(t, err) + transfers := ft.Transfers() + var pay, recv, infra, liquidity, bb, treasury, hmp, hmr int + for _, v := range transfers { + if v.Type == types.TransferTypeLiquidityFeePay { + liquidity++ + assert.Equal(t, num.NewUint(252), v.Amount.Amount) + } + if v.Type == types.TransferTypeInfrastructureFeePay { + infra++ + assert.Equal(t, num.NewUint(127), v.Amount.Amount) + } + if v.Type == types.TransferTypeMakerFeeReceive { + recv++ + assert.Equal(t, num.NewUint(64), v.Amount.Amount) + } + if v.Type == types.TransferTypeMakerFeePay { + pay++ + assert.Equal(t, num.NewUint(64), v.Amount.Amount) + } + if v.Type == types.TransferTypeBuyBackFeePay { + bb++ + assert.Equal(t, num.NewUint(500), v.Amount.Amount) + } + if v.Type == types.TransferTypeTreasuryPay { + treasury++ + assert.Equal(t, num.NewUint(750), v.Amount.Amount) + } + if v.Type == types.TransferTypeHighMakerRebatePay { + hmp++ + assert.Equal(t, num.NewUint(1250), v.Amount.Amount) + } + if v.Type == types.TransferTypeHighMakerRebateReceive { + hmr++ + assert.Equal(t, num.NewUint(1250), v.Amount.Amount) + } + } + + assert.Equal(t, liquidity, 1) + assert.Equal(t, infra, 1) + assert.Equal(t, bb, 1) + assert.Equal(t, treasury, 1) + assert.Equal(t, hmp, 1) + assert.Equal(t, hmr, 1) + assert.Equal(t, recv, len(trades)) + assert.Equal(t, pay, len(trades)) + assert.Equal(t, &eventspb.FeesStats{ + Asset: testAsset, + TotalRewardsReceived: []*eventspb.PartyAmount{ + { + Party: "party3", + Amount: "110", + QuantumAmount: "110", + }, + }, + ReferrerRewardsGenerated: []*eventspb.ReferrerRewardsGenerated{ + { + Referrer: "party3", + GeneratedReward: []*eventspb.PartyAmount{ + { + Party: "party1", + Amount: "110", + QuantumAmount: "110", + }, + }, + }, + }, + RefereesDiscountApplied: []*eventspb.PartyAmount{ + { + Party: "party1", + Amount: "262", + QuantumAmount: "262", + }, + }, + VolumeDiscountApplied: []*eventspb.PartyAmount{ + { + Party: "party1", + Amount: "60", + QuantumAmount: "60", + }, + }, + TotalMakerFeesReceived: []*eventspb.PartyAmount{ + { + Party: "party2", + Amount: "64", + QuantumAmount: "64", + }, + }, + MakerFeesGenerated: []*eventspb.MakerFeesGenerated{ + { + Taker: "party1", + MakerFeesPaid: []*eventspb.PartyAmount{ + { + Party: "party2", + Amount: "64", + QuantumAmount: "64", + }, + }, + }, + }, + TotalFeesPaidAndReceived: []*eventspb.PartyAmount{ + { + Party: "party1", + Amount: "443", + QuantumAmount: "443", + }, + { + Party: "party2", + Amount: "64", + QuantumAmount: "64", + }, + }, + }, eng.GetFeesStatsOnEpochEnd(num.DecimalFromInt64(1))) +} + func testCalcContinuousTradingAndCheckAmountsWithDiscountsAndRewardsBySide(t *testing.T, aggressorSide types.Side) { t.Helper() eng := getTestFee(t) @@ -374,6 +763,8 @@ func testCalcContinuousTradingAndCheckAmountsWithDiscountsAndRewardsBySide(t *te discountRewardService := mocks.NewMockReferralDiscountRewardService(ctrl) volumeDiscountService := mocks.NewMockVolumeDiscountService(ctrl) + volumeRebateService := mocks.NewMockVolumeRebateService(ctrl) + volumeRebateService.EXPECT().VolumeRebateFactorForParty(gomock.Any()).Return(num.DecimalZero()).AnyTimes() eng.UpdateFeeFactors(types.Fees{ Factors: &types.FeeFactors{ @@ -398,13 +789,29 @@ func testCalcContinuousTradingAndCheckAmountsWithDiscountsAndRewardsBySide(t *te aggressor = "party2" } - discountRewardService.EXPECT().ReferralDiscountFactorForParty(gomock.Any()).Return(num.DecimalFromFloat(0.5)).AnyTimes() - volumeDiscountService.EXPECT().VolumeDiscountFactorForParty(gomock.Any()).Return(num.DecimalFromFloat(0.25)).AnyTimes() + discountRewardService.EXPECT().ReferralDiscountFactorsForParty(gomock.Any()).Return(types.Factors{ + Infra: num.NewDecimalFromFloat(0.5), + Maker: num.NewDecimalFromFloat(0.5), + Liquidity: num.NewDecimalFromFloat(0.5), + }).AnyTimes() + volumeDiscountService.EXPECT().VolumeDiscountFactorForParty(gomock.Any()).Return(types.Factors{ + Infra: num.NewDecimalFromFloat(0.25), + Maker: num.NewDecimalFromFloat(0.25), + Liquidity: num.NewDecimalFromFloat(0.25), + }) discountRewardService.EXPECT().GetReferrer(types.PartyID(aggressor)).Return(types.PartyID("referrer"), nil).AnyTimes() - discountRewardService.EXPECT().RewardsFactorMultiplierAppliedForParty(types.PartyID("party1")).Return(num.DecimalFromFloat(0.3)).AnyTimes() - discountRewardService.EXPECT().RewardsFactorMultiplierAppliedForParty(types.PartyID("party2")).Return(num.DecimalFromFloat(0.3)).AnyTimes() + discountRewardService.EXPECT().RewardsFactorsMultiplierAppliedForParty(types.PartyID("party1")).Return(types.Factors{ + Infra: num.NewDecimalFromFloat(0.3), + Maker: num.NewDecimalFromFloat(0.3), + Liquidity: num.NewDecimalFromFloat(0.3), + }).AnyTimes() + discountRewardService.EXPECT().RewardsFactorsMultiplierAppliedForParty(types.PartyID("party2")).Return(types.Factors{ + Infra: num.NewDecimalFromFloat(0.3), + Maker: num.NewDecimalFromFloat(0.3), + Liquidity: num.NewDecimalFromFloat(0.3), + }).AnyTimes() - ft, err := eng.CalculateForContinuousMode(trades, discountRewardService, volumeDiscountService) + ft, err := eng.CalculateForContinuousMode(trades, discountRewardService, volumeDiscountService, volumeRebateService) assert.NotNil(t, ft) assert.Nil(t, err) transfers := ft.Transfers() @@ -466,6 +873,8 @@ func testCalcContinuousTradingAndCheckAmountsWithDiscountsAndRewardsBySideMultip discountRewardService := mocks.NewMockReferralDiscountRewardService(ctrl) volumeDiscountService := mocks.NewMockVolumeDiscountService(ctrl) + volumeRebateService := mocks.NewMockVolumeRebateService(ctrl) + volumeRebateService.EXPECT().VolumeRebateFactorForParty(gomock.Any()).Return(num.DecimalZero()).AnyTimes() eng.UpdateFeeFactors(types.Fees{ Factors: &types.FeeFactors{ @@ -504,14 +913,30 @@ func testCalcContinuousTradingAndCheckAmountsWithDiscountsAndRewardsBySideMultip aggressor = "party2" } - discountRewardService.EXPECT().ReferralDiscountFactorForParty(gomock.Any()).Return(num.DecimalFromFloat(0.5)).AnyTimes() - volumeDiscountService.EXPECT().VolumeDiscountFactorForParty(gomock.Any()).Return(num.DecimalFromFloat(0.25)).AnyTimes() + discountRewardService.EXPECT().ReferralDiscountFactorsForParty(gomock.Any()).Return(types.Factors{ + Infra: num.NewDecimalFromFloat(0.5), + Maker: num.NewDecimalFromFloat(0.5), + Liquidity: num.NewDecimalFromFloat(0.5), + }).AnyTimes() + volumeDiscountService.EXPECT().VolumeDiscountFactorForParty(gomock.Any()).Return(types.Factors{ + Infra: num.NewDecimalFromFloat(0.25), + Maker: num.NewDecimalFromFloat(0.25), + Liquidity: num.NewDecimalFromFloat(0.25), + }).AnyTimes() discountRewardService.EXPECT().GetReferrer(gomock.Any()).Return(types.PartyID("referrer"), nil).AnyTimes() discountRewardService.EXPECT().GetReferrer(types.PartyID(aggressor)).Return(types.PartyID("referrer"), nil).AnyTimes() - discountRewardService.EXPECT().RewardsFactorMultiplierAppliedForParty(aggressor).Return(num.DecimalFromFloat(0.3)).AnyTimes() - discountRewardService.EXPECT().RewardsFactorMultiplierAppliedForParty(gomock.Any()).Return(num.DecimalFromFloat(0.3)).AnyTimes() + discountRewardService.EXPECT().RewardsFactorsMultiplierAppliedForParty(aggressor).Return(types.Factors{ + Infra: num.NewDecimalFromFloat(0.3), + Maker: num.NewDecimalFromFloat(0.3), + Liquidity: num.NewDecimalFromFloat(0.3), + }).AnyTimes() + discountRewardService.EXPECT().RewardsFactorsMultiplierAppliedForParty(gomock.Any()).Return(types.Factors{ + Infra: num.NewDecimalFromFloat(0.3), + Maker: num.NewDecimalFromFloat(0.3), + Liquidity: num.NewDecimalFromFloat(0.3), + }).AnyTimes() - ft, err := eng.CalculateForContinuousMode(trades, discountRewardService, volumeDiscountService) + ft, err := eng.CalculateForContinuousMode(trades, discountRewardService, volumeDiscountService, volumeRebateService) assert.NotNil(t, ft) assert.Nil(t, err) transfers := ft.Transfers() @@ -639,9 +1064,11 @@ func testCalcContinuousTrading(t *testing.T) { ctrl := gomock.NewController(t) discountRewardService := mocks.NewMockReferralDiscountRewardService(ctrl) volumeDiscountService := mocks.NewMockVolumeDiscountService(ctrl) - discountRewardService.EXPECT().ReferralDiscountFactorForParty(gomock.Any()).Return(num.DecimalZero()).AnyTimes() - discountRewardService.EXPECT().RewardsFactorMultiplierAppliedForParty(gomock.Any()).Return(num.DecimalZero()).AnyTimes() - volumeDiscountService.EXPECT().VolumeDiscountFactorForParty(gomock.Any()).Return(num.DecimalZero()).AnyTimes() + volumeRebateService := mocks.NewMockVolumeRebateService(ctrl) + volumeRebateService.EXPECT().VolumeRebateFactorForParty(gomock.Any()).Return(num.DecimalZero()).AnyTimes() + discountRewardService.EXPECT().ReferralDiscountFactorsForParty(gomock.Any()).Return(types.EmptyFactors).AnyTimes() + discountRewardService.EXPECT().RewardsFactorsMultiplierAppliedForParty(gomock.Any()).Return(types.EmptyFactors).AnyTimes() + volumeDiscountService.EXPECT().VolumeDiscountFactorForParty(gomock.Any()).Return(types.EmptyFactors).AnyTimes() discountRewardService.EXPECT().GetReferrer(gomock.Any()).Return(types.PartyID("party1"), errors.New("not a referrer")).AnyTimes() trades := []*types.Trade{ @@ -682,7 +1109,7 @@ func testCalcContinuousTrading(t *testing.T) { }, } - ft, err := eng.CalculateForContinuousMode(trades, discountRewardService, volumeDiscountService) + ft, err := eng.CalculateForContinuousMode(trades, discountRewardService, volumeDiscountService, volumeRebateService) assert.NotNil(t, ft) assert.Nil(t, err) @@ -716,12 +1143,121 @@ func testCalcContinuousTrading(t *testing.T) { assert.Equal(t, pay, len(trades)) } +func testCalcContinuousTradingExtended(t *testing.T) { + eng := getExtendedTestFee(t) + ctrl := gomock.NewController(t) + discountRewardService := mocks.NewMockReferralDiscountRewardService(ctrl) + volumeDiscountService := mocks.NewMockVolumeDiscountService(ctrl) + volumeRebateService := mocks.NewMockVolumeRebateService(ctrl) + volumeRebateService.EXPECT().VolumeRebateFactorForParty(types.PartyID("party2")).Return(num.DecimalFromFloat(0.00005)).AnyTimes() + volumeRebateService.EXPECT().VolumeRebateFactorForParty(types.PartyID("party3")).Return(num.DecimalZero()).AnyTimes() + volumeRebateService.EXPECT().VolumeRebateFactorForParty(types.PartyID("party4")).Return(num.DecimalZero()).AnyTimes() + volumeRebateService.EXPECT().VolumeRebateFactorForParty(types.PartyID("party5")).Return(num.DecimalZero()).AnyTimes() + discountRewardService.EXPECT().ReferralDiscountFactorsForParty(gomock.Any()).Return(types.EmptyFactors).AnyTimes() + discountRewardService.EXPECT().RewardsFactorsMultiplierAppliedForParty(gomock.Any()).Return(types.EmptyFactors).AnyTimes() + volumeDiscountService.EXPECT().VolumeDiscountFactorForParty(gomock.Any()).Return(types.EmptyFactors).AnyTimes() + discountRewardService.EXPECT().GetReferrer(gomock.Any()).Return(types.PartyID("party1"), errors.New("not a referrer")).AnyTimes() + + trades := []*types.Trade{ + { + Aggressor: types.SideSell, + Seller: "party1", + Buyer: "party2", + Size: 10, + Price: num.NewUint(10000), + }, + { + Aggressor: types.SideSell, + Seller: "party1", + Buyer: "party3", + Size: 1, + Price: num.NewUint(10300), + }, + { + Aggressor: types.SideSell, + Seller: "party1", + Buyer: "party4", + Size: 7, + Price: num.NewUint(10300), + }, + { + Aggressor: types.SideSell, + Seller: "party1", + Buyer: "party2", + Size: 2, + Price: num.NewUint(10500), + }, + { + Aggressor: types.SideSell, + Seller: "party1", + Buyer: "party5", + Size: 5, + Price: num.NewUint(11000), + }, + } + + ft, err := eng.CalculateForContinuousMode(trades, discountRewardService, volumeDiscountService, volumeRebateService) + assert.NotNil(t, ft) + assert.Nil(t, err) + + // get the amounts map + feeAmounts := ft.TotalFeesAmountPerParty() + party1Amount, ok := feeAmounts["party1"] + assert.True(t, ok) + assert.Equal(t, num.NewUint(43928), party1Amount) + + // get the transfer and check we have enough of each types + transfers := ft.Transfers() + var pay, recv, infra, liquidity, highMakerPay, highMakerReceive, bb, treasury int + for _, v := range transfers { + if v.Type == types.TransferTypeLiquidityFeePay { + liquidity++ + } + if v.Type == types.TransferTypeInfrastructureFeePay { + infra++ + } + if v.Type == types.TransferTypeMakerFeeReceive { + recv++ + } + if v.Type == types.TransferTypeMakerFeePay { + pay++ + } + if v.Type == types.TransferTypeHighMakerRebatePay { + highMakerPay++ + } + if v.Type == types.TransferTypeHighMakerRebateReceive { + highMakerReceive++ + } + if v.Type == types.TransferTypeBuyBackFeePay { + bb++ + } + if v.Type == types.TransferTypeTreasuryPay { + treasury++ + } + } + + assert.Equal(t, liquidity, 1) + assert.Equal(t, infra, 1) + assert.Equal(t, highMakerPay, 2) + assert.Equal(t, highMakerReceive, 2) + assert.Equal(t, recv, len(trades)) + assert.Equal(t, pay, len(trades)) + assert.Equal(t, bb, len(trades)) + assert.Equal(t, treasury, len(trades)) +} + func testCalcAuctionTradingErrorEmptyTrade(t *testing.T) { eng := getTestFee(t) ctrl := gomock.NewController(t) discountRewardService := mocks.NewMockReferralDiscountRewardService(ctrl) volumeDiscountService := mocks.NewMockVolumeDiscountService(ctrl) - _, err := eng.CalculateForAuctionMode([]*types.Trade{}, discountRewardService, volumeDiscountService) + volumeRebateService := mocks.NewMockVolumeRebateService(ctrl) + volumeRebateService.EXPECT().VolumeRebateFactorForParty(gomock.Any()).Return(num.DecimalZero()).AnyTimes() + _, err := eng.CalculateForAuctionMode([]*types.Trade{}, discountRewardService, volumeDiscountService, volumeRebateService) + assert.EqualError(t, err, fee.ErrEmptyTrades.Error()) + + eng = getExtendedTestFee(t) + _, err = eng.CalculateForAuctionMode([]*types.Trade{}, discountRewardService, volumeDiscountService, volumeRebateService) assert.EqualError(t, err, fee.ErrEmptyTrades.Error()) } @@ -730,9 +1266,11 @@ func testCalcAuctionTrading(t *testing.T) { ctrl := gomock.NewController(t) discountRewardService := mocks.NewMockReferralDiscountRewardService(ctrl) volumeDiscountService := mocks.NewMockVolumeDiscountService(ctrl) - discountRewardService.EXPECT().ReferralDiscountFactorForParty(gomock.Any()).Return(num.DecimalZero()).AnyTimes() - discountRewardService.EXPECT().RewardsFactorMultiplierAppliedForParty(gomock.Any()).Return(num.DecimalZero()).AnyTimes() - volumeDiscountService.EXPECT().VolumeDiscountFactorForParty(gomock.Any()).Return(num.DecimalZero()).AnyTimes() + volumeRebateService := mocks.NewMockVolumeRebateService(ctrl) + volumeRebateService.EXPECT().VolumeRebateFactorForParty(gomock.Any()).Return(num.DecimalZero()).AnyTimes() + discountRewardService.EXPECT().ReferralDiscountFactorsForParty(gomock.Any()).Return(types.EmptyFactors).AnyTimes() + discountRewardService.EXPECT().RewardsFactorsMultiplierAppliedForParty(gomock.Any()).Return(types.EmptyFactors).AnyTimes() + volumeDiscountService.EXPECT().VolumeDiscountFactorForParty(gomock.Any()).Return(types.EmptyFactors).AnyTimes() discountRewardService.EXPECT().GetReferrer(gomock.Any()).Return(types.PartyID(""), errors.New("not a referrer")).AnyTimes() trades := []*types.Trade{ { @@ -744,7 +1282,7 @@ func testCalcAuctionTrading(t *testing.T) { }, } - ft, err := eng.CalculateForAuctionMode(trades, discountRewardService, volumeDiscountService) + ft, err := eng.CalculateForAuctionMode(trades, discountRewardService, volumeDiscountService, volumeRebateService) assert.NotNil(t, ft) assert.Nil(t, err) @@ -785,23 +1323,115 @@ func testCalcAuctionTrading(t *testing.T) { assert.Equal(t, pay, 0) } +func testCalcAuctionTradingExtended(t *testing.T) { + eng := getExtendedTestFee(t) + ctrl := gomock.NewController(t) + discountRewardService := mocks.NewMockReferralDiscountRewardService(ctrl) + volumeDiscountService := mocks.NewMockVolumeDiscountService(ctrl) + volumeRebateService := mocks.NewMockVolumeRebateService(ctrl) + volumeRebateService.EXPECT().VolumeRebateFactorForParty(gomock.Any()).Return(num.DecimalFromFloat(0.0025)).AnyTimes() + discountRewardService.EXPECT().ReferralDiscountFactorsForParty(gomock.Any()).Return(types.EmptyFactors).AnyTimes() + discountRewardService.EXPECT().RewardsFactorsMultiplierAppliedForParty(gomock.Any()).Return(types.EmptyFactors).AnyTimes() + volumeDiscountService.EXPECT().VolumeDiscountFactorForParty(gomock.Any()).Return(types.EmptyFactors).AnyTimes() + discountRewardService.EXPECT().GetReferrer(gomock.Any()).Return(types.PartyID(""), errors.New("not a referrer")).AnyTimes() + trades := []*types.Trade{ + { + Aggressor: types.SideSell, + Seller: "party1", + Buyer: "party2", + Size: 1, + Price: num.NewUint(100), + }, + } + + ft, err := eng.CalculateForAuctionMode(trades, discountRewardService, volumeDiscountService, volumeRebateService) + assert.NotNil(t, ft) + assert.Nil(t, err) + + // get the amounts map + feeAmounts := ft.TotalFeesAmountPerParty() + // fees are (100 * 0.1 + 100 * 0.05) = 15 + // 15 / 2 = 7.5 + // internally the engine Ceil all fees. + // so here we will expect 8 for each + party1Amount, ok := feeAmounts["party1"] + assert.True(t, ok) + assert.Equal(t, num.NewUint(8), party1Amount) + party2Amount, ok := feeAmounts["party2"] + assert.True(t, ok) + assert.Equal(t, num.NewUint(8), party2Amount) + + // get the transfer and check we have enough of each types + transfers := ft.Transfers() + var pay, recv, infra, liquidity, hmp, hmr, bb, treasury int + for _, v := range transfers { + if v.Type == types.TransferTypeLiquidityFeePay { + liquidity++ + } + if v.Type == types.TransferTypeInfrastructureFeePay { + infra++ + } + if v.Type == types.TransferTypeMakerFeeReceive { + recv++ + } + if v.Type == types.TransferTypeMakerFeePay { + pay++ + } + if v.Type == types.TransferTypeHighMakerRebatePay { + hmp++ + } + if v.Type == types.TransferTypeHighMakerRebateReceive { + hmr++ + } + if v.Type == types.TransferTypeBuyBackFeePay { + bb++ + } + if v.Type == types.TransferTypeTreasuryPay { + treasury++ + } + } + + assert.Equal(t, 2, liquidity) + assert.Equal(t, 2, infra) + assert.Equal(t, 0, recv) + assert.Equal(t, 0, pay) + assert.Equal(t, 0, hmp) + assert.Equal(t, 0, hmr) + assert.Equal(t, 2, bb) + assert.Equal(t, 2, treasury) +} + func TestCalcAuctionTradingWithDiscountsAndRewards(t *testing.T) { eng := getTestFee(t) ctrl := gomock.NewController(t) discountRewardService := mocks.NewMockReferralDiscountRewardService(ctrl) volumeDiscountService := mocks.NewMockVolumeDiscountService(ctrl) - discountRewardService.EXPECT().ReferralDiscountFactorForParty(gomock.Any()).DoAndReturn(func(p types.PartyID) num.Decimal { + volumeRebateService := mocks.NewMockVolumeRebateService(ctrl) + volumeRebateService.EXPECT().VolumeRebateFactorForParty(gomock.Any()).Return(num.DecimalZero()).AnyTimes() + discountRewardService.EXPECT().ReferralDiscountFactorsForParty(gomock.Any()).DoAndReturn(func(p types.PartyID) types.Factors { if p == types.PartyID("party1") { - return num.DecimalZero() + return types.EmptyFactors } else { - return num.NewDecimalFromFloat(0.5) + return types.Factors{ + Infra: num.NewDecimalFromFloat(0.5), + Maker: num.NewDecimalFromFloat(0.5), + Liquidity: num.NewDecimalFromFloat(0.5), + } } }).AnyTimes() - volumeDiscountService.EXPECT().VolumeDiscountFactorForParty(gomock.Any()).DoAndReturn(func(p types.PartyID) num.Decimal { + volumeDiscountService.EXPECT().VolumeDiscountFactorForParty(gomock.Any()).DoAndReturn(func(p types.PartyID) types.Factors { if p == types.PartyID("party1") { - return num.NewDecimalFromFloat(0.2) + return types.Factors{ + Infra: num.NewDecimalFromFloat(0.2), + Maker: num.NewDecimalFromFloat(0.2), + Liquidity: num.NewDecimalFromFloat(0.2), + } } else { - return num.NewDecimalFromFloat(0.3) + return types.Factors{ + Infra: num.NewDecimalFromFloat(0.3), + Maker: num.NewDecimalFromFloat(0.3), + Liquidity: num.NewDecimalFromFloat(0.3), + } } }).AnyTimes() discountRewardService.EXPECT().GetReferrer(gomock.Any()).DoAndReturn(func(p types.PartyID) (types.PartyID, error) { @@ -811,8 +1441,12 @@ func TestCalcAuctionTradingWithDiscountsAndRewards(t *testing.T) { return types.PartyID(""), errors.New("No referrer") } }).AnyTimes() - discountRewardService.EXPECT().RewardsFactorMultiplierAppliedForParty(types.PartyID("party1")).Return(num.DecimalFromFloat(0.5)).AnyTimes() - discountRewardService.EXPECT().RewardsFactorMultiplierAppliedForParty(types.PartyID("party2")).Return(num.DecimalZero()).AnyTimes() + discountRewardService.EXPECT().RewardsFactorsMultiplierAppliedForParty(types.PartyID("party1")).Return(types.Factors{ + Infra: num.NewDecimalFromFloat(0.5), + Maker: num.NewDecimalFromFloat(0.5), + Liquidity: num.NewDecimalFromFloat(0.5), + }).AnyTimes() + discountRewardService.EXPECT().RewardsFactorsMultiplierAppliedForParty(types.PartyID("party2")).Return(types.EmptyFactors).AnyTimes() trades := []*types.Trade{ { @@ -824,7 +1458,7 @@ func TestCalcAuctionTradingWithDiscountsAndRewards(t *testing.T) { }, } - ft, err := eng.CalculateForAuctionMode(trades, discountRewardService, volumeDiscountService) + ft, err := eng.CalculateForAuctionMode(trades, discountRewardService, volumeDiscountService, volumeRebateService) assert.NotNil(t, ft) assert.Nil(t, err) @@ -898,7 +1532,13 @@ func testCalcBatchAuctionTradingErrorEmptyTrade(t *testing.T) { ctrl := gomock.NewController(t) discountRewardService := mocks.NewMockReferralDiscountRewardService(ctrl) volumeDiscountService := mocks.NewMockVolumeDiscountService(ctrl) - _, err := eng.CalculateForFrequentBatchesAuctionMode([]*types.Trade{}, discountRewardService, volumeDiscountService) + volumeRebateService := mocks.NewMockVolumeRebateService(ctrl) + volumeRebateService.EXPECT().VolumeRebateFactorForParty(gomock.Any()).Return(num.DecimalZero()).AnyTimes() + _, err := eng.CalculateForFrequentBatchesAuctionMode([]*types.Trade{}, discountRewardService, volumeDiscountService, volumeRebateService) + assert.EqualError(t, err, fee.ErrEmptyTrades.Error()) + + eng = getExtendedTestFee(t) + _, err = eng.CalculateForFrequentBatchesAuctionMode([]*types.Trade{}, discountRewardService, volumeDiscountService, volumeRebateService) assert.EqualError(t, err, fee.ErrEmptyTrades.Error()) } @@ -907,9 +1547,11 @@ func testCalcBatchAuctionTradingSameBatch(t *testing.T) { ctrl := gomock.NewController(t) discountRewardService := mocks.NewMockReferralDiscountRewardService(ctrl) volumeDiscountService := mocks.NewMockVolumeDiscountService(ctrl) - discountRewardService.EXPECT().ReferralDiscountFactorForParty(gomock.Any()).Return(num.DecimalZero()).AnyTimes() - discountRewardService.EXPECT().RewardsFactorMultiplierAppliedForParty(gomock.Any()).Return(num.DecimalZero()).AnyTimes() - volumeDiscountService.EXPECT().VolumeDiscountFactorForParty(gomock.Any()).Return(num.DecimalZero()).AnyTimes() + volumeRebateService := mocks.NewMockVolumeRebateService(ctrl) + volumeRebateService.EXPECT().VolumeRebateFactorForParty(gomock.Any()).Return(num.DecimalZero()).AnyTimes() + discountRewardService.EXPECT().ReferralDiscountFactorsForParty(gomock.Any()).Return(types.EmptyFactors).AnyTimes() + discountRewardService.EXPECT().RewardsFactorsMultiplierAppliedForParty(gomock.Any()).Return(types.EmptyFactors).AnyTimes() + volumeDiscountService.EXPECT().VolumeDiscountFactorForParty(gomock.Any()).Return(types.EmptyFactors).AnyTimes() discountRewardService.EXPECT().GetReferrer(gomock.Any()).Return(types.PartyID(""), errors.New("not a referrer")).AnyTimes() trades := []*types.Trade{ { @@ -923,7 +1565,7 @@ func testCalcBatchAuctionTradingSameBatch(t *testing.T) { }, } - ft, err := eng.CalculateForFrequentBatchesAuctionMode(trades, discountRewardService, volumeDiscountService) + ft, err := eng.CalculateForFrequentBatchesAuctionMode(trades, discountRewardService, volumeDiscountService, volumeRebateService) assert.NotNil(t, ft) assert.Nil(t, err) @@ -969,9 +1611,11 @@ func testCalcBatchAuctionTradingDifferentBatches(t *testing.T) { ctrl := gomock.NewController(t) discountRewardService := mocks.NewMockReferralDiscountRewardService(ctrl) volumeDiscountService := mocks.NewMockVolumeDiscountService(ctrl) - discountRewardService.EXPECT().ReferralDiscountFactorForParty(gomock.Any()).Return(num.DecimalZero()).AnyTimes() - discountRewardService.EXPECT().RewardsFactorMultiplierAppliedForParty(gomock.Any()).Return(num.DecimalZero()).AnyTimes() - volumeDiscountService.EXPECT().VolumeDiscountFactorForParty(gomock.Any()).Return(num.DecimalZero()).AnyTimes() + volumeRebateService := mocks.NewMockVolumeRebateService(ctrl) + volumeRebateService.EXPECT().VolumeRebateFactorForParty(gomock.Any()).Return(num.DecimalZero()).AnyTimes() + discountRewardService.EXPECT().ReferralDiscountFactorsForParty(gomock.Any()).Return(types.EmptyFactors).AnyTimes() + discountRewardService.EXPECT().RewardsFactorsMultiplierAppliedForParty(gomock.Any()).Return(types.EmptyFactors).AnyTimes() + volumeDiscountService.EXPECT().VolumeDiscountFactorForParty(gomock.Any()).Return(types.EmptyFactors).AnyTimes() discountRewardService.EXPECT().GetReferrer(gomock.Any()).Return(types.PartyID(""), errors.New("not a referrer")).AnyTimes() trades := []*types.Trade{ { @@ -985,7 +1629,7 @@ func testCalcBatchAuctionTradingDifferentBatches(t *testing.T) { }, } - ft, err := eng.CalculateForFrequentBatchesAuctionMode(trades, discountRewardService, volumeDiscountService) + ft, err := eng.CalculateForFrequentBatchesAuctionMode(trades, discountRewardService, volumeDiscountService, volumeRebateService) assert.NotNil(t, ft) assert.Nil(t, err) @@ -1028,9 +1672,9 @@ func testCloseoutFees(t *testing.T) { ctrl := gomock.NewController(t) discountRewardService := mocks.NewMockReferralDiscountRewardService(ctrl) volumeDiscountService := mocks.NewMockVolumeDiscountService(ctrl) - discountRewardService.EXPECT().ReferralDiscountFactorForParty(gomock.Any()).Return(num.DecimalZero()).AnyTimes() - discountRewardService.EXPECT().RewardsFactorMultiplierAppliedForParty(gomock.Any()).Return(num.DecimalZero()).AnyTimes() - volumeDiscountService.EXPECT().VolumeDiscountFactorForParty(gomock.Any()).Return(num.DecimalZero()).AnyTimes() + discountRewardService.EXPECT().ReferralDiscountFactorsForParty(gomock.Any()).Return(types.EmptyFactors).AnyTimes() + discountRewardService.EXPECT().RewardsFactorsMultiplierAppliedForParty(gomock.Any()).Return(types.EmptyFactors).AnyTimes() + volumeDiscountService.EXPECT().VolumeDiscountFactorForParty(gomock.Any()).Return(types.EmptyFactors).AnyTimes() discountRewardService.EXPECT().GetReferrer(gomock.Any()).Return(types.PartyID(""), errors.New("not a referrer")).AnyTimes() trades := []*types.Trade{ { @@ -1067,9 +1711,10 @@ func testCloseoutFees(t *testing.T) { assert.NotNil(t, fee) allTransfers := ft.Transfers() // first we have the network -> pay transfers, then 1 transfer per good party - assert.Equal(t, len(trades), len(allTransfers)-3) - goodPartyTransfers := allTransfers[3:] - networkTransfers := allTransfers[:3] + // two additional transfers for the buy back and treasury fees + assert.Equal(t, len(trades), len(allTransfers)-5) + goodPartyTransfers := allTransfers[5:] + networkTransfers := allTransfers[:5] numTrades := num.NewUint(uint64(len(trades))) // maker fee is 100 * 0.02 == 2 diff --git a/core/fee/mocks/mocks.go b/core/fee/mocks/mocks.go index 8b4f5a3a07f..b6cbae9005c 100644 --- a/core/fee/mocks/mocks.go +++ b/core/fee/mocks/mocks.go @@ -1,5 +1,5 @@ // Code generated by MockGen. DO NOT EDIT. -// Source: code.vegaprotocol.io/vega/core/fee (interfaces: ReferralDiscountRewardService,VolumeDiscountService) +// Source: code.vegaprotocol.io/vega/core/fee (interfaces: ReferralDiscountRewardService,VolumeDiscountService,VolumeRebateService) // Package mocks is a generated GoMock package. package mocks @@ -50,32 +50,32 @@ func (mr *MockReferralDiscountRewardServiceMockRecorder) GetReferrer(arg0 interf return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetReferrer", reflect.TypeOf((*MockReferralDiscountRewardService)(nil).GetReferrer), arg0) } -// ReferralDiscountFactorForParty mocks base method. -func (m *MockReferralDiscountRewardService) ReferralDiscountFactorForParty(arg0 types.PartyID) decimal.Decimal { +// ReferralDiscountFactorsForParty mocks base method. +func (m *MockReferralDiscountRewardService) ReferralDiscountFactorsForParty(arg0 types.PartyID) types.Factors { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "ReferralDiscountFactorForParty", arg0) - ret0, _ := ret[0].(decimal.Decimal) + ret := m.ctrl.Call(m, "ReferralDiscountFactorsForParty", arg0) + ret0, _ := ret[0].(types.Factors) return ret0 } -// ReferralDiscountFactorForParty indicates an expected call of ReferralDiscountFactorForParty. -func (mr *MockReferralDiscountRewardServiceMockRecorder) ReferralDiscountFactorForParty(arg0 interface{}) *gomock.Call { +// ReferralDiscountFactorsForParty indicates an expected call of ReferralDiscountFactorsForParty. +func (mr *MockReferralDiscountRewardServiceMockRecorder) ReferralDiscountFactorsForParty(arg0 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ReferralDiscountFactorForParty", reflect.TypeOf((*MockReferralDiscountRewardService)(nil).ReferralDiscountFactorForParty), arg0) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ReferralDiscountFactorsForParty", reflect.TypeOf((*MockReferralDiscountRewardService)(nil).ReferralDiscountFactorsForParty), arg0) } -// RewardsFactorMultiplierAppliedForParty mocks base method. -func (m *MockReferralDiscountRewardService) RewardsFactorMultiplierAppliedForParty(arg0 types.PartyID) decimal.Decimal { +// RewardsFactorsMultiplierAppliedForParty mocks base method. +func (m *MockReferralDiscountRewardService) RewardsFactorsMultiplierAppliedForParty(arg0 types.PartyID) types.Factors { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "RewardsFactorMultiplierAppliedForParty", arg0) - ret0, _ := ret[0].(decimal.Decimal) + ret := m.ctrl.Call(m, "RewardsFactorsMultiplierAppliedForParty", arg0) + ret0, _ := ret[0].(types.Factors) return ret0 } -// RewardsFactorMultiplierAppliedForParty indicates an expected call of RewardsFactorMultiplierAppliedForParty. -func (mr *MockReferralDiscountRewardServiceMockRecorder) RewardsFactorMultiplierAppliedForParty(arg0 interface{}) *gomock.Call { +// RewardsFactorsMultiplierAppliedForParty indicates an expected call of RewardsFactorsMultiplierAppliedForParty. +func (mr *MockReferralDiscountRewardServiceMockRecorder) RewardsFactorsMultiplierAppliedForParty(arg0 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RewardsFactorMultiplierAppliedForParty", reflect.TypeOf((*MockReferralDiscountRewardService)(nil).RewardsFactorMultiplierAppliedForParty), arg0) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RewardsFactorsMultiplierAppliedForParty", reflect.TypeOf((*MockReferralDiscountRewardService)(nil).RewardsFactorsMultiplierAppliedForParty), arg0) } // MockVolumeDiscountService is a mock of VolumeDiscountService interface. @@ -102,10 +102,10 @@ func (m *MockVolumeDiscountService) EXPECT() *MockVolumeDiscountServiceMockRecor } // VolumeDiscountFactorForParty mocks base method. -func (m *MockVolumeDiscountService) VolumeDiscountFactorForParty(arg0 types.PartyID) decimal.Decimal { +func (m *MockVolumeDiscountService) VolumeDiscountFactorForParty(arg0 types.PartyID) types.Factors { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "VolumeDiscountFactorForParty", arg0) - ret0, _ := ret[0].(decimal.Decimal) + ret0, _ := ret[0].(types.Factors) return ret0 } @@ -114,3 +114,40 @@ func (mr *MockVolumeDiscountServiceMockRecorder) VolumeDiscountFactorForParty(ar mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "VolumeDiscountFactorForParty", reflect.TypeOf((*MockVolumeDiscountService)(nil).VolumeDiscountFactorForParty), arg0) } + +// MockVolumeRebateService is a mock of VolumeRebateService interface. +type MockVolumeRebateService struct { + ctrl *gomock.Controller + recorder *MockVolumeRebateServiceMockRecorder +} + +// MockVolumeRebateServiceMockRecorder is the mock recorder for MockVolumeRebateService. +type MockVolumeRebateServiceMockRecorder struct { + mock *MockVolumeRebateService +} + +// NewMockVolumeRebateService creates a new mock instance. +func NewMockVolumeRebateService(ctrl *gomock.Controller) *MockVolumeRebateService { + mock := &MockVolumeRebateService{ctrl: ctrl} + mock.recorder = &MockVolumeRebateServiceMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockVolumeRebateService) EXPECT() *MockVolumeRebateServiceMockRecorder { + return m.recorder +} + +// VolumeRebateFactorForParty mocks base method. +func (m *MockVolumeRebateService) VolumeRebateFactorForParty(arg0 types.PartyID) decimal.Decimal { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "VolumeRebateFactorForParty", arg0) + ret0, _ := ret[0].(decimal.Decimal) + return ret0 +} + +// VolumeRebateFactorForParty indicates an expected call of VolumeRebateFactorForParty. +func (mr *MockVolumeRebateServiceMockRecorder) VolumeRebateFactorForParty(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "VolumeRebateFactorForParty", reflect.TypeOf((*MockVolumeRebateService)(nil).VolumeRebateFactorForParty), arg0) +} diff --git a/core/governance/engine.go b/core/governance/engine.go index 87c8fc57446..ece08bc28aa 100644 --- a/core/governance/engine.go +++ b/core/governance/engine.go @@ -296,6 +296,8 @@ func (e *Engine) preEnactProposal(ctx context.Context, p *proposal) (te *ToEnact te.referralProgramChanges = updatedReferralProgramFromProposal(p) case types.ProposalTermsTypeUpdateVolumeDiscountProgram: te.volumeDiscountProgram = updatedVolumeDiscountProgramFromProposal(p) + case types.ProposalTermsTypeUpdateVolumeRebateProgram: + te.volumeRebateProgram = updatedVolumeRebateProgramFromProposal(p) } return //nolint:nakedret } @@ -741,6 +743,8 @@ func (e *Engine) getProposalParams(proposalTerm types.ProposalTerm) (*types.Prop return e.getReferralProgramNetworkParameters(), nil case types.ProposalTermsTypeUpdateVolumeDiscountProgram: return e.getVolumeDiscountProgramNetworkParameters(), nil + case types.ProposalTermsTypeUpdateVolumeRebateProgram: + return e.getVolumeRebateProgramNetworkParameters(), nil default: return nil, ErrUnsupportedProposalType } @@ -1091,6 +1095,8 @@ func (e *Engine) validateChange(terms *types.ProposalTerms) (types.ProposalError return validateUpdateReferralProgram(e.netp, terms.GetUpdateReferralProgram(), terms.EnactmentTimestamp) case types.ProposalTermsTypeUpdateVolumeDiscountProgram: return validateUpdateVolumeDiscountProgram(e.netp, terms.GetUpdateVolumeDiscountProgram()) + case types.ProposalTermsTypeUpdateVolumeRebateProgram: + return validateUpdateVolumeRebateProgram(e.netp, terms.GetUpdateVolumeRebateProgram()) default: return types.ProposalErrorUnspecified, nil } diff --git a/core/governance/engine_test.go b/core/governance/engine_test.go index 04bf7958afa..0485dc06452 100644 --- a/core/governance/engine_test.go +++ b/core/governance/engine_test.go @@ -2207,6 +2207,30 @@ func (e *tstEngine) newProposalForReferralProgramUpdate(partyID string, now time return prop } +func (e *tstEngine) newProposalForVolumeRebateProgramUpdate(partyID string, now time.Time, configuration *types.VolumeRebateProgramChanges) types.Proposal { + id := e.newProposalID() + prop := types.Proposal{ + ID: id, + Reference: "ref-" + id, + Party: partyID, + State: types.ProposalStateOpen, + Terms: &types.ProposalTerms{ + ClosingTimestamp: now.Add(96 * time.Hour).Unix(), + EnactmentTimestamp: now.Add(4 * 48 * time.Hour).Unix(), + ValidationTimestamp: now.Add(2 * time.Hour).Unix(), + Change: &types.ProposalTermsUpdateVolumeRebateProgram{ + UpdateVolumeRebateProgram: &types.UpdateVolumeRebateProgram{ + Changes: configuration, + }, + }, + }, + Rationale: &types.ProposalRationale{ + Description: "some description", + }, + } + return prop +} + func (e *tstEngine) newProposalForVolumeDiscountProgramUpdate(partyID string, now time.Time, configuration *types.VolumeDiscountProgramChanges) types.Proposal { id := e.newProposalID() prop := types.Proposal{ diff --git a/core/governance/engine_update_discount_volume_program_test.go b/core/governance/engine_update_discount_volume_program_test.go index e6c82993b93..b97ebfbd4a9 100644 --- a/core/governance/engine_update_discount_volume_program_test.go +++ b/core/governance/engine_update_discount_volume_program_test.go @@ -61,10 +61,18 @@ func testSubmittingProposalForVolumeDiscountProgramUpdateSucceeds(t *testing.T) VolumeBenefitTiers: []*types.VolumeBenefitTier{ { MinimumRunningNotionalTakerVolume: num.NewUint(10000), - VolumeDiscountFactor: num.DecimalFromFloat(0.001), + VolumeDiscountFactors: types.Factors{ + Infra: num.DecimalFromFloat(0.001), + Maker: num.DecimalFromFloat(0.001), + Liquidity: num.DecimalFromFloat(0.001), + }, }, { MinimumRunningNotionalTakerVolume: num.NewUint(20000), - VolumeDiscountFactor: num.DecimalFromFloat(0.005), + VolumeDiscountFactors: types.Factors{ + Infra: num.DecimalFromFloat(0.005), + Maker: num.DecimalFromFloat(0.005), + Liquidity: num.DecimalFromFloat(0.005), + }, }, }, }) @@ -108,10 +116,18 @@ func testSubmittingProposalForVolumeDiscountProgramUpdateWithTooManyTiersFails(t VolumeBenefitTiers: []*types.VolumeBenefitTier{ { MinimumRunningNotionalTakerVolume: num.NewUint(10000), - VolumeDiscountFactor: num.DecimalFromFloat(0.001), + VolumeDiscountFactors: types.Factors{ + Infra: num.DecimalFromFloat(0.001), + Maker: num.DecimalFromFloat(0.001), + Liquidity: num.DecimalFromFloat(0.001), + }, }, { MinimumRunningNotionalTakerVolume: num.NewUint(20000), - VolumeDiscountFactor: num.DecimalFromFloat(0.005), + VolumeDiscountFactors: types.Factors{ + Infra: num.DecimalFromFloat(0.005), + Maker: num.DecimalFromFloat(0.005), + Liquidity: num.DecimalFromFloat(0.005), + }, }, }, }) @@ -155,10 +171,18 @@ func testSubmittingProposalForVolumeDiscountProgramUpdateWithTooHighDiscountFact VolumeBenefitTiers: []*types.VolumeBenefitTier{ { MinimumRunningNotionalTakerVolume: num.NewUint(10000), - VolumeDiscountFactor: num.DecimalFromFloat(0.001), + VolumeDiscountFactors: types.Factors{ + Infra: num.DecimalFromFloat(0.001), + Maker: num.DecimalFromFloat(0.001), + Liquidity: num.DecimalFromFloat(0.001), + }, }, { MinimumRunningNotionalTakerVolume: num.NewUint(20000), - VolumeDiscountFactor: num.DecimalFromFloat(0.015), + VolumeDiscountFactors: types.Factors{ + Infra: num.DecimalFromFloat(0.015), + Maker: num.DecimalFromFloat(0.015), + Liquidity: num.DecimalFromFloat(0.015), + }, }, }, }) @@ -175,7 +199,7 @@ func testSubmittingProposalForVolumeDiscountProgramUpdateWithTooHighDiscountFact // then require.EqualError(t, err, - "tier 2 defines a volume discount factor higher than the maximum allowed by the network parameter \"volumeDiscountProgram.maxVolumeDiscountFactor\": maximum is 0.01, but got 0.015", + "tier 2 defines a volume discount infrastructure factor higher than the maximum allowed by the network parameter \"volumeDiscountProgram.maxVolumeDiscountFactor\": maximum is 0.01, but got 0.015", ) require.Nil(t, toSubmit) } diff --git a/core/governance/engine_update_referral_program_test.go b/core/governance/engine_update_referral_program_test.go index d5550a37a20..4bf5f1fea1f 100644 --- a/core/governance/engine_update_referral_program_test.go +++ b/core/governance/engine_update_referral_program_test.go @@ -67,13 +67,29 @@ func testSubmittingProposalForReferralProgramUpdateSucceeds(t *testing.T) { { MinimumEpochs: num.NewUint(1), MinimumRunningNotionalTakerVolume: num.NewUint(10000), - ReferralRewardFactor: num.DecimalFromFloat(0.001), - ReferralDiscountFactor: num.DecimalFromFloat(0.001), + ReferralRewardFactors: types.Factors{ + Infra: num.DecimalFromFloat(0.001), + Maker: num.DecimalFromFloat(0.001), + Liquidity: num.DecimalFromFloat(0.001), + }, + ReferralDiscountFactors: types.Factors{ + Infra: num.DecimalFromFloat(0.001), + Maker: num.DecimalFromFloat(0.001), + Liquidity: num.DecimalFromFloat(0.001), + }, }, { MinimumEpochs: num.NewUint(7), MinimumRunningNotionalTakerVolume: num.NewUint(20000), - ReferralRewardFactor: num.DecimalFromFloat(0.005), - ReferralDiscountFactor: num.DecimalFromFloat(0.005), + ReferralRewardFactors: types.Factors{ + Infra: num.DecimalFromFloat(0.005), + Maker: num.DecimalFromFloat(0.005), + Liquidity: num.DecimalFromFloat(0.005), + }, + ReferralDiscountFactors: types.Factors{ + Infra: num.DecimalFromFloat(0.005), + Maker: num.DecimalFromFloat(0.005), + Liquidity: num.DecimalFromFloat(0.005), + }, }, }, }) @@ -121,13 +137,29 @@ func testSubmittingProposalForReferralProgramUpdateWithTooManyTiersFails(t *test { MinimumEpochs: num.NewUint(1), MinimumRunningNotionalTakerVolume: num.NewUint(10000), - ReferralRewardFactor: num.DecimalFromFloat(0.001), - ReferralDiscountFactor: num.DecimalFromFloat(0.001), + ReferralRewardFactors: types.Factors{ + Infra: num.DecimalFromFloat(0.001), + Maker: num.DecimalFromFloat(0.001), + Liquidity: num.DecimalFromFloat(0.001), + }, + ReferralDiscountFactors: types.Factors{ + Infra: num.DecimalFromFloat(0.001), + Maker: num.DecimalFromFloat(0.001), + Liquidity: num.DecimalFromFloat(0.001), + }, }, { MinimumEpochs: num.NewUint(7), MinimumRunningNotionalTakerVolume: num.NewUint(20000), - ReferralRewardFactor: num.DecimalFromFloat(0.005), - ReferralDiscountFactor: num.DecimalFromFloat(0.005), + ReferralRewardFactors: types.Factors{ + Infra: num.DecimalFromFloat(0.005), + Maker: num.DecimalFromFloat(0.005), + Liquidity: num.DecimalFromFloat(0.005), + }, + ReferralDiscountFactors: types.Factors{ + Infra: num.DecimalFromFloat(0.005), + Maker: num.DecimalFromFloat(0.005), + Liquidity: num.DecimalFromFloat(0.005), + }, }, }, }) @@ -175,13 +207,29 @@ func testSubmittingProposalForReferralProgramUpdateWithTooHighRewardFactorFails( { MinimumEpochs: num.NewUint(1), MinimumRunningNotionalTakerVolume: num.NewUint(10000), - ReferralRewardFactor: num.DecimalFromFloat(0.001), - ReferralDiscountFactor: num.DecimalFromFloat(0.001), + ReferralRewardFactors: types.Factors{ + Infra: num.DecimalFromFloat(0.001), + Maker: num.DecimalFromFloat(0.001), + Liquidity: num.DecimalFromFloat(0.001), + }, + ReferralDiscountFactors: types.Factors{ + Infra: num.DecimalFromFloat(0.001), + Maker: num.DecimalFromFloat(0.001), + Liquidity: num.DecimalFromFloat(0.001), + }, }, { MinimumEpochs: num.NewUint(7), MinimumRunningNotionalTakerVolume: num.NewUint(20000), - ReferralRewardFactor: num.DecimalFromFloat(0.015), - ReferralDiscountFactor: num.DecimalFromFloat(0.005), + ReferralRewardFactors: types.Factors{ + Infra: num.DecimalFromFloat(0.015), + Maker: num.DecimalFromFloat(0.015), + Liquidity: num.DecimalFromFloat(0.015), + }, + ReferralDiscountFactors: types.Factors{ + Infra: num.DecimalFromFloat(0.005), + Maker: num.DecimalFromFloat(0.005), + Liquidity: num.DecimalFromFloat(0.005), + }, }, }, }) @@ -198,7 +246,7 @@ func testSubmittingProposalForReferralProgramUpdateWithTooHighRewardFactorFails( // then require.EqualError(t, err, - "tier 2 defines a referral reward factor higher than the maximum allowed by the network parameter \"referralProgram.maxReferralRewardFactor\": maximum is 0.01, but got 0.015", + "tier 2 defines a referral reward infrastructure factor higher than the maximum allowed by the network parameter \"referralProgram.maxReferralRewardFactor\": maximum is 0.01, but got 0.015", ) require.Nil(t, toSubmit) } @@ -232,13 +280,29 @@ func testSubmittingProposalForReferralProgramUpdateWithTooHighDiscountFactorFail { MinimumEpochs: num.NewUint(1), MinimumRunningNotionalTakerVolume: num.NewUint(10000), - ReferralRewardFactor: num.DecimalFromFloat(0.001), - ReferralDiscountFactor: num.DecimalFromFloat(0.001), + ReferralRewardFactors: types.Factors{ + Infra: num.DecimalFromFloat(0.001), + Maker: num.DecimalFromFloat(0.001), + Liquidity: num.DecimalFromFloat(0.001), + }, + ReferralDiscountFactors: types.Factors{ + Infra: num.DecimalFromFloat(0.001), + Maker: num.DecimalFromFloat(0.001), + Liquidity: num.DecimalFromFloat(0.001), + }, }, { MinimumEpochs: num.NewUint(7), MinimumRunningNotionalTakerVolume: num.NewUint(20000), - ReferralRewardFactor: num.DecimalFromFloat(0.010), - ReferralDiscountFactor: num.DecimalFromFloat(0.015), + ReferralRewardFactors: types.Factors{ + Infra: num.DecimalFromFloat(0.01), + Maker: num.DecimalFromFloat(0.01), + Liquidity: num.DecimalFromFloat(0.01), + }, + ReferralDiscountFactors: types.Factors{ + Infra: num.DecimalFromFloat(0.015), + Maker: num.DecimalFromFloat(0.015), + Liquidity: num.DecimalFromFloat(0.015), + }, }, }, }) @@ -255,7 +319,7 @@ func testSubmittingProposalForReferralProgramUpdateWithTooHighDiscountFactorFail // then require.EqualError(t, err, - "tier 2 defines a referral discount factor higher than the maximum allowed by the network parameter \"referralProgram.maxReferralDiscountFactor\": maximum is 0.01, but got 0.015", + "tier 2 defines a referral discount infrastructure factor higher than the maximum allowed by the network parameter \"referralProgram.maxReferralDiscountFactor\": maximum is 0.01, but got 0.015", ) require.Nil(t, toSubmit) } @@ -291,13 +355,29 @@ func testSubmittingProposalForReferralProgramUpdateEndsBeforeEnactsFails(t *test { MinimumEpochs: num.NewUint(1), MinimumRunningNotionalTakerVolume: num.NewUint(10000), - ReferralRewardFactor: num.DecimalFromFloat(0.001), - ReferralDiscountFactor: num.DecimalFromFloat(0.001), + ReferralRewardFactors: types.Factors{ + Infra: num.DecimalFromFloat(0.001), + Maker: num.DecimalFromFloat(0.001), + Liquidity: num.DecimalFromFloat(0.001), + }, + ReferralDiscountFactors: types.Factors{ + Infra: num.DecimalFromFloat(0.001), + Maker: num.DecimalFromFloat(0.001), + Liquidity: num.DecimalFromFloat(0.001), + }, }, { MinimumEpochs: num.NewUint(7), MinimumRunningNotionalTakerVolume: num.NewUint(20000), - ReferralRewardFactor: num.DecimalFromFloat(0.010), - ReferralDiscountFactor: num.DecimalFromFloat(0.015), + ReferralRewardFactors: types.Factors{ + Infra: num.DecimalFromFloat(0.01), + Maker: num.DecimalFromFloat(0.01), + Liquidity: num.DecimalFromFloat(0.01), + }, + ReferralDiscountFactors: types.Factors{ + Infra: num.DecimalFromFloat(0.015), + Maker: num.DecimalFromFloat(0.015), + Liquidity: num.DecimalFromFloat(0.015), + }, }, }, }, diff --git a/core/governance/engine_update_volume_rebate_program_test.go b/core/governance/engine_update_volume_rebate_program_test.go new file mode 100644 index 00000000000..bedf365229b --- /dev/null +++ b/core/governance/engine_update_volume_rebate_program_test.go @@ -0,0 +1,172 @@ +// Copyright (C) 2023 Gobalsky Labs Limited +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +package governance_test + +import ( + "testing" + "time" + + "code.vegaprotocol.io/vega/core/events" + "code.vegaprotocol.io/vega/core/netparams" + "code.vegaprotocol.io/vega/core/types" + "code.vegaprotocol.io/vega/libs/num" + vgrand "code.vegaprotocol.io/vega/libs/rand" + vgtest "code.vegaprotocol.io/vega/libs/test" + + "github.com/golang/mock/gomock" + "github.com/stretchr/testify/require" +) + +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) { + 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.00001), + }, { + MinimumPartyMakerVolumeFraction: num.DecimalFromFloat(0.2), + AdditionalMakerRebate: num.DecimalFromFloat(0.00002), + }, + }, + }) + + // setup + eng.ensureTokenBalanceForParty(t, proposer, 1000) + + // expect + eng.expectOpenProposalEvent(t, proposer, proposal.ID) + + // when + toSubmit, err := eng.submitProposal(t, proposal) + + // then + require.NoError(t, err) + require.NotNil(t, toSubmit) +} + +func testSubmittingProposalForVolumeRebateProgramUpdateWithTooManyTiersFails(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, "1")).Times(1) + require.NoError(t, eng.netp.Update(ctx, netparams.VolumeRebateProgramMaxBenefitTiers, "1")) + + // 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.001), + }, { + MinimumPartyMakerVolumeFraction: num.DecimalFromFloat(0.2), + AdditionalMakerRebate: num.DecimalFromFloat(0.002), + }, + }, + }) + + // setup + eng.ensureTokenBalanceForParty(t, proposer, 1000) + + // expect + eng.expectRejectedProposalEvent(t, proposer, proposal.ID, types.ProposalErrorInvalidVolumeRebateProgram) + + // when + toSubmit, err := eng.submitProposal(t, proposal) + + // then + 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/market.go b/core/governance/market.go index cbead25e66a..a173c484838 100644 --- a/core/governance/market.go +++ b/core/governance/market.go @@ -226,6 +226,9 @@ func buildMarketFromProposal( // get factors for the market makerFee, _ := netp.Get(netparams.MarketFeeFactorsMakerFee) infraFee, _ := netp.Get(netparams.MarketFeeFactorsInfrastructureFee) + buybackFee, _ := netp.Get(netparams.MarketFeeFactorsBuyBackFee) + treasuryFee, _ := netp.Get(netparams.MarketFeeFactorsTreasuryFee) + // get the margin scaling factors scalingFactors := proto.ScalingFactors{} _ = netp.GetJSONStruct(netparams.MarketMarginScalingFactors, &scalingFactors) @@ -250,6 +253,8 @@ func buildMarketFromProposal( } makerFeeDec, _ := num.DecimalFromString(makerFee) infraFeeDec, _ := num.DecimalFromString(infraFee) + buybackFeeDec, _ := num.DecimalFromString(buybackFee) + treasuryFeeDec, _ := num.DecimalFromString(treasuryFee) // assign here, we want to update this after assigning market variable marginCalc := &types.MarginCalculator{ ScalingFactors: types.ScalingFactorsFromProto(&scalingFactors), @@ -262,6 +267,8 @@ func buildMarketFromProposal( Factors: &types.FeeFactors{ MakerFee: makerFeeDec, InfrastructureFee: infraFeeDec, + TreasuryFee: treasuryFeeDec, + BuyBackFee: buybackFeeDec, }, LiquidityFeeSettings: definition.Changes.LiquidityFeeSettings, }, @@ -312,6 +319,8 @@ func buildSpotMarketFromProposal( // get factors for the market makerFee, _ := netp.Get(netparams.MarketFeeFactorsMakerFee) infraFee, _ := netp.Get(netparams.MarketFeeFactorsInfrastructureFee) + buybackFee, _ := netp.Get(netparams.MarketFeeFactorsBuyBackFee) + treasuryFee, _ := netp.Get(netparams.MarketFeeFactorsTreasuryFee) // get price monitoring parameters if definition.Changes.PriceMonitoringParameters == nil { pmParams := &proto.PriceMonitoringParameters{} @@ -332,6 +341,8 @@ func buildSpotMarketFromProposal( makerFeeDec, _ := num.DecimalFromString(makerFee) infraFeeDec, _ := num.DecimalFromString(infraFee) + buybackFeeDec, _ := num.DecimalFromString(buybackFee) + treasuryFeeDec, _ := num.DecimalFromString(treasuryFee) market := &types.Market{ ID: marketID, DecimalPlaces: definition.Changes.PriceDecimalPlaces, @@ -340,6 +351,8 @@ func buildSpotMarketFromProposal( Factors: &types.FeeFactors{ MakerFee: makerFeeDec, InfrastructureFee: infraFeeDec, + TreasuryFee: treasuryFeeDec, + BuyBackFee: buybackFeeDec, }, LiquidityFeeSettings: definition.Changes.LiquidityFeeSettings, }, diff --git a/core/governance/market_cp_restore_test.go b/core/governance/market_cp_restore_test.go index 15ed96971ce..8b1db22757a 100644 --- a/core/governance/market_cp_restore_test.go +++ b/core/governance/market_cp_restore_test.go @@ -201,19 +201,20 @@ func createExecutionEngine(t *testing.T, tm time.Time) (*execution.Engine, *gove asset, _ := assets.New(context.Background(), log, assets.NewDefaultConfig(), getNodeWallet().Ethereum, nil, nil, broker, primaryBridgeView, secondaryBridgeView, notary, false) teams := emocks.NewMockTeams(ctrl) bc := emocks.NewMockAccountBalanceChecker(ctrl) - marketTracker := common.NewMarketActivityTracker(log, teams, bc, broker) + marketTracker := common.NewMarketActivityTracker(log, teams, bc, broker, collateralService) epochEngine.NotifyOnEpoch(marketTracker.OnEpochEvent, marketTracker.OnEpochRestore) referralDiscountReward := fmocks.NewMockReferralDiscountRewardService(ctrl) volumeDiscount := fmocks.NewMockVolumeDiscountService(ctrl) + volumeRebateService := fmocks.NewMockVolumeRebateService(ctrl) referralDiscountReward.EXPECT().GetReferrer(gomock.Any()).Return(types.PartyID(""), errors.New("no referrer")).AnyTimes() - referralDiscountReward.EXPECT().ReferralDiscountFactorForParty(gomock.Any()).Return(num.DecimalZero()).AnyTimes() - referralDiscountReward.EXPECT().RewardsFactorMultiplierAppliedForParty(gomock.Any()).Return(num.DecimalZero()).AnyTimes() + referralDiscountReward.EXPECT().ReferralDiscountFactorsForParty(gomock.Any()).Return(types.EmptyFactors).AnyTimes() + referralDiscountReward.EXPECT().RewardsFactorsMultiplierAppliedForParty(gomock.Any()).Return(types.EmptyFactors).AnyTimes() volumeDiscount.EXPECT().VolumeDiscountFactorForParty(gomock.Any()).Return(num.DecimalZero()).AnyTimes() execBanking := emocks.NewMockBanking(ctrl) parties := emocks.NewMockParties(ctrl) delayTarget := emocks.NewMockDelayTransactionsTarget(ctrl) delayTarget.EXPECT().MarketDelayRequiredUpdated(gomock.Any(), gomock.Any()).AnyTimes() - exec := execution.NewEngine(log, executionConfig, timeService, collateralService, oracleService, broker, statevar, marketTracker, asset, referralDiscountReward, volumeDiscount, execBanking, parties, delayTarget) + exec := execution.NewEngine(log, executionConfig, timeService, collateralService, oracleService, broker, statevar, marketTracker, asset, referralDiscountReward, volumeDiscount, volumeRebateService, execBanking, parties, delayTarget) accounts := mocks.NewMockStakingAccounts(ctrl) witness := mocks.NewMockWitness(ctrl) diff --git a/core/governance/netparams.go b/core/governance/netparams.go index 8075af2561a..2b5426496bb 100644 --- a/core/governance/netparams.go +++ b/core/governance/netparams.go @@ -139,6 +139,22 @@ func (e *Engine) getVolumeDiscountProgramNetworkParameters() *types.ProposalPara ) } +func (e *Engine) getVolumeRebateProgramNetworkParameters() *types.ProposalParameters { + return e.getProposalParametersFromNetParams( + netparams.GovernanceProposalVolumeRebateProgramMinClose, + netparams.GovernanceProposalVolumeRebateProgramMaxClose, + netparams.GovernanceProposalVolumeRebateProgramMinEnact, + netparams.GovernanceProposalVolumeRebateProgramMaxEnact, + netparams.GovernanceProposalVolumeRebateProgramRequiredParticipation, + netparams.GovernanceProposalVolumeRebateProgramRequiredMajority, + netparams.GovernanceProposalVolumeRebateProgramMinProposerBalance, + netparams.GovernanceProposalVolumeRebateProgramMinVoterBalance, + "0", + "0", + "0", + ) +} + func (e *Engine) getNewAssetProposalParameters() *types.ProposalParameters { return e.getProposalParametersFromNetParams( netparams.GovernanceProposalAssetMinClose, diff --git a/core/governance/proposal.go b/core/governance/proposal.go index 3ab67c7c7b1..fd2a1339afb 100644 --- a/core/governance/proposal.go +++ b/core/governance/proposal.go @@ -255,6 +255,7 @@ type ToEnact struct { msu *ToEnactMarketStateUpdate referralProgramChanges *types.ReferralProgram volumeDiscountProgram *types.VolumeDiscountProgram + volumeRebateProgram *types.VolumeRebateProgram } type ToEnactMarketStateUpdate struct{} @@ -278,6 +279,10 @@ func (t ToEnact) IsVolumeDiscountProgramUpdate() bool { return t.volumeDiscountProgram != nil } +func (t ToEnact) IsVolumeRebateProgramUpdate() bool { + return t.volumeRebateProgram != nil +} + func (t ToEnact) IsReferralProgramUpdate() bool { return t.referralProgramChanges != nil } @@ -363,6 +368,10 @@ func (t *ToEnact) VolumeDiscountProgramUpdate() *types.VolumeDiscountProgram { return t.volumeDiscountProgram } +func (t *ToEnact) VolumeRebateProgramUpdate() *types.VolumeRebateProgram { + return t.volumeRebateProgram +} + func (t *ToEnact) UpdateMarket() *types.Market { return t.updatedMarket } diff --git a/core/governance/referral_program.go b/core/governance/referral_program.go index 81812c72011..5685a5ed020 100644 --- a/core/governance/referral_program.go +++ b/core/governance/referral_program.go @@ -39,11 +39,23 @@ func validateUpdateReferralProgram(netp NetParams, p *types.UpdateReferralProgra maxRewardFactor, _ := netp.GetDecimal(netparams.ReferralProgramMaxReferralRewardFactor) maxDiscountFactor, _ := netp.GetDecimal(netparams.ReferralProgramMaxReferralDiscountFactor) for i, tier := range p.Changes.BenefitTiers { - if tier.ReferralRewardFactor.GreaterThan(maxRewardFactor) { - return types.ProposalErrorInvalidReferralProgram, fmt.Errorf("tier %d defines a referral reward factor higher than the maximum allowed by the network parameter %q: maximum is %s, but got %s", i+1, netparams.ReferralProgramMaxReferralRewardFactor, maxRewardFactor.String(), tier.ReferralRewardFactor.String()) + if tier.ReferralRewardFactors.Infra.GreaterThan(maxRewardFactor) { + return types.ProposalErrorInvalidReferralProgram, fmt.Errorf("tier %d defines a referral reward infrastructure factor higher than the maximum allowed by the network parameter %q: maximum is %s, but got %s", i+1, netparams.ReferralProgramMaxReferralRewardFactor, maxRewardFactor.String(), tier.ReferralRewardFactors.Infra.String()) } - if tier.ReferralDiscountFactor.GreaterThan(maxDiscountFactor) { - return types.ProposalErrorInvalidReferralProgram, fmt.Errorf("tier %d defines a referral discount factor higher than the maximum allowed by the network parameter %q: maximum is %s, but got %s", i+1, netparams.ReferralProgramMaxReferralDiscountFactor, maxDiscountFactor.String(), tier.ReferralDiscountFactor.String()) + if tier.ReferralRewardFactors.Maker.GreaterThan(maxRewardFactor) { + return types.ProposalErrorInvalidReferralProgram, fmt.Errorf("tier %d defines a referral reward maker factor higher than the maximum allowed by the network parameter %q: maximum is %s, but got %s", i+1, netparams.ReferralProgramMaxReferralRewardFactor, maxRewardFactor.String(), tier.ReferralRewardFactors.Maker.String()) + } + if tier.ReferralRewardFactors.Liquidity.GreaterThan(maxRewardFactor) { + return types.ProposalErrorInvalidReferralProgram, fmt.Errorf("tier %d defines a referral reward liquidity factor higher than the maximum allowed by the network parameter %q: maximum is %s, but got %s", i+1, netparams.ReferralProgramMaxReferralRewardFactor, maxRewardFactor.String(), tier.ReferralRewardFactors.Liquidity.String()) + } + if tier.ReferralDiscountFactors.Infra.GreaterThan(maxDiscountFactor) { + return types.ProposalErrorInvalidReferralProgram, fmt.Errorf("tier %d defines a referral discount infrastructure factor higher than the maximum allowed by the network parameter %q: maximum is %s, but got %s", i+1, netparams.ReferralProgramMaxReferralDiscountFactor, maxDiscountFactor.String(), tier.ReferralDiscountFactors.Infra.String()) + } + if tier.ReferralDiscountFactors.Maker.GreaterThan(maxDiscountFactor) { + return types.ProposalErrorInvalidReferralProgram, fmt.Errorf("tier %d defines a referral discount maker factor higher than the maximum allowed by the network parameter %q: maximum is %s, but got %s", i+1, netparams.ReferralProgramMaxReferralDiscountFactor, maxDiscountFactor.String(), tier.ReferralDiscountFactors.Maker.String()) + } + if tier.ReferralDiscountFactors.Liquidity.GreaterThan(maxDiscountFactor) { + return types.ProposalErrorInvalidReferralProgram, fmt.Errorf("tier %d defines a referral discount liquidity factor higher than the maximum allowed by the network parameter %q: maximum is %s, but got %s", i+1, netparams.ReferralProgramMaxReferralDiscountFactor, maxDiscountFactor.String(), tier.ReferralDiscountFactors.Liquidity.String()) } } return types.ProposalErrorUnspecified, nil diff --git a/core/governance/volume_discount_program.go b/core/governance/volume_discount_program.go index b7359bff4d9..1c2465d5555 100644 --- a/core/governance/volume_discount_program.go +++ b/core/governance/volume_discount_program.go @@ -30,8 +30,14 @@ func validateUpdateVolumeDiscountProgram(netp NetParams, p *types.UpdateVolumeDi maxDiscountFactor, _ := netp.GetDecimal(netparams.VolumeDiscountProgramMaxVolumeDiscountFactor) for i, tier := range p.Changes.VolumeBenefitTiers { - if tier.VolumeDiscountFactor.GreaterThan(maxDiscountFactor) { - return types.ProposalErrorInvalidVolumeDiscountProgram, fmt.Errorf("tier %d defines a volume discount factor higher than the maximum allowed by the network parameter %q: maximum is %s, but got %s", i+1, netparams.VolumeDiscountProgramMaxVolumeDiscountFactor, maxDiscountFactor.String(), tier.VolumeDiscountFactor.String()) + if tier.VolumeDiscountFactors.Infra.GreaterThan(maxDiscountFactor) { + return types.ProposalErrorInvalidVolumeDiscountProgram, fmt.Errorf("tier %d defines a volume discount infrastructure factor higher than the maximum allowed by the network parameter %q: maximum is %s, but got %s", i+1, netparams.VolumeDiscountProgramMaxVolumeDiscountFactor, maxDiscountFactor.String(), tier.VolumeDiscountFactors.Infra.String()) + } + if tier.VolumeDiscountFactors.Maker.GreaterThan(maxDiscountFactor) { + return types.ProposalErrorInvalidVolumeDiscountProgram, fmt.Errorf("tier %d defines a volume discount maker factor higher than the maximum allowed by the network parameter %q: maximum is %s, but got %s", i+1, netparams.VolumeDiscountProgramMaxVolumeDiscountFactor, maxDiscountFactor.String(), tier.VolumeDiscountFactors.Maker.String()) + } + if tier.VolumeDiscountFactors.Liquidity.GreaterThan(maxDiscountFactor) { + return types.ProposalErrorInvalidVolumeDiscountProgram, fmt.Errorf("tier %d defines a volume discount liquidity factor higher than the maximum allowed by the network parameter %q: maximum is %s, but got %s", i+1, netparams.VolumeDiscountProgramMaxVolumeDiscountFactor, maxDiscountFactor.String(), tier.VolumeDiscountFactors.Liquidity.String()) } } return 0, nil diff --git a/core/governance/volume_rebate_program.go b/core/governance/volume_rebate_program.go new file mode 100644 index 00000000000..7760f45e090 --- /dev/null +++ b/core/governance/volume_rebate_program.go @@ -0,0 +1,53 @@ +// Copyright (C) 2023 Gobalsky Labs Limited +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +package governance + +import ( + "fmt" + + "code.vegaprotocol.io/vega/core/netparams" + "code.vegaprotocol.io/vega/core/types" +) + +func validateUpdateVolumeRebateProgram(netp NetParams, p *types.UpdateVolumeRebateProgram) (types.ProposalError, error) { + maxTiers, _ := netp.GetUint(netparams.VolumeRebateProgramMaxBenefitTiers) + 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 +} + +func updatedVolumeRebateProgramFromProposal(p *proposal) *types.VolumeRebateProgram { + terms := p.Terms.GetUpdateVolumeRebateProgram() + + return &types.VolumeRebateProgram{ + ID: p.ID, + Version: terms.Changes.Version, + EndOfProgramTimestamp: terms.Changes.EndOfProgramTimestamp, + WindowLength: terms.Changes.WindowLength, + VolumeRebateBenefitTiers: terms.Changes.VolumeRebateBenefitTiers, + } +} diff --git a/core/integration/features/0084-VDPR-012.feature b/core/integration/features/0084-VDPR-012.feature index 2eebd9dcce1..4f0f2dff8a6 100644 --- a/core/integration/features/0084-VDPR-012.feature +++ b/core/integration/features/0084-VDPR-012.feature @@ -25,10 +25,10 @@ Feature: At the start of an epoch, each parties volume_discount_factor is reeval #risk factor short:3.5569036 #risk factor long:0.801225765 And the volume discount program tiers named "VDP-01": - | volume | factor | - | 1000 | 0.001 | - | 2000 | 0.005 | - | 3000 | 0.010 | + | volume | infra factor | liquidity factor | maker factor | + | 1000 | 0.001 | 0.002 | 0.003 | + | 2000 | 0.005 | 0.006 | 0.007 | + | 3000 | 0.010 | 0.012 | 0.014 | And the volume discount program: | id | tiers | closing timestamp | window length | | id1 | VDP-01 | 0 | 4 | @@ -92,18 +92,22 @@ Feature: At the start of an epoch, each parties volume_discount_factor is reeval And the market data for the market "ETH/MAR24" should be: | mark price | trading mode | horizon | min bound | max bound | target stake | supplied stake | open interest | | 1000 | TRADING_MODE_CONTINUOUS | 3600 | 973 | 1027 | 3556 | 100000 | 1 | - And the party "party3" has the following discount factor "0" + And the party "party3" has the following discount infra factor "0" Then the parties place the following orders: | party | market id | side | volume | price | resulting trades | type | tif | | party3 | ETH/MAR24 | buy | 1 | 0 | 1 | TYPE_MARKET | TIF_IOC | | party3 | ETH/MAR24 | sell | 1 | 0 | 1 | TYPE_MARKET | TIF_IOC | When the network moves ahead "1" epochs - And the party "party3" has the following discount factor "0.005" + And the party "party3" has the following discount infra factor "0.005" + And the party "party3" has the following discount liquidity factor "0.006" + And the party "party3" has the following discount maker factor "0.007" Then the parties place the following orders: | party | market id | side | volume | price | resulting trades | type | tif | | party3 | ETH/MAR24 | buy | 20 | 0 | 1 | TYPE_MARKET | TIF_IOC | | party3 | ETH/MAR24 | sell | 20 | 0 | 1 | TYPE_MARKET | TIF_IOC | When the network moves ahead "1" epochs - And the party "party3" has the following discount factor "0.01" + And the party "party3" has the following discount infra factor "0.01" + And the party "party3" has the following discount liquidity factor "0.012" + And the party "party3" has the following discount maker factor "0.014" diff --git a/core/integration/features/0084-VDPR-013.feature b/core/integration/features/0084-VDPR-013.feature index e9e4ca4f88d..bd908aaf635 100644 --- a/core/integration/features/0084-VDPR-013.feature +++ b/core/integration/features/0084-VDPR-013.feature @@ -26,10 +26,10 @@ Feature: A parties volume_discount_factor is set equal to the factors in the hig #risk factor short:3.5569036 #risk factor long:0.801225765 And the volume discount program tiers named "VDP-01": - | volume | factor | - | 1000 | 0.01 | - | 2000 | 0.02 | - | 3000 | 0.03 | + | volume | infra factor | liquidity factor | maker factor | + | 1000 | 0.011 | 0.012 | 0.013 | + | 2000 | 0.021 | 0.022 | 0.023 | + | 3000 | 0.031 | 0.032 | 0.033 | And the volume discount program: | id | tiers | closing timestamp | window length | | id1 | VDP-01 | 0 | 4 | @@ -95,7 +95,7 @@ Feature: A parties volume_discount_factor is set equal to the factors in the hig | mark price | trading mode | horizon | min bound | max bound | target stake | supplied stake | open interest | | 1000 | TRADING_MODE_CONTINUOUS | 3600 | 973 | 1027 | 3556 | 100000 | 1 | And the party "party3" has the following taker notional "0" - And the party "party3" has the following discount factor "0" + And the party "party3" has the following discount infra factor "0" Then the parties place the following orders: | party | market id | side | volume | price | resulting trades | type | tif | @@ -103,7 +103,9 @@ Feature: A parties volume_discount_factor is set equal to the factors in the hig | party3 | ETH/MAR24 | sell | 1 | 0 | 1 | TYPE_MARKET | TIF_IOC | When the network moves ahead "1" epochs And the party "party3" has the following taker notional "2000" - And the party "party3" has the following discount factor "0.02" + And the party "party3" has the following discount infra factor "0.021" + And the party "party3" has the following discount liquidity factor "0.022" + And the party "party3" has the following discount maker factor "0.023" Then the parties place the following orders: | party | market id | side | volume | price | resulting trades | type | tif | @@ -111,7 +113,9 @@ Feature: A parties volume_discount_factor is set equal to the factors in the hig | party3 | ETH/MAR24 | sell | 1 | 0 | 1 | TYPE_MARKET | TIF_IOC | When the network moves ahead "1" epochs And the party "party3" has the following taker notional "4000" - And the party "party3" has the following discount factor "0.03" + And the party "party3" has the following discount infra factor "0.031" + And the party "party3" has the following discount liquidity factor "0.032" + And the party "party3" has the following discount maker factor "0.033" # now that party3 has a discount, lets do a trade with fees # Volume discount rewards are correctly calculated and transferred for each taker fee component during continuous trading. (0029-FEES-027) @@ -119,13 +123,13 @@ Feature: A parties volume_discount_factor is set equal to the factors in the hig | party | market id | side | volume | price | resulting trades | type | tif | | party3 | ETH/MAR24 | buy | 50 | 1000 | 1 | TYPE_MARKET | TIF_IOC | - # maker fee discount = floor(202 * 0.03) = 6 - # infra fee discount - floor(51 *0.03) = 1 - # lp fee discount - floor(1010 * 0.03) = 30 + # maker fee discount = floor(202 * 0.033) = 6 + # infra fee discount - floor(51 *0.031) = 1 + # lp fee discount - floor(1010 * 0.032) = 32 Then the following transfers should happen: | from | to | from account | to account | market id | amount | asset | | party3 | market | ACCOUNT_TYPE_GENERAL | ACCOUNT_TYPE_FEES_MAKER | ETH/MAR24 | 196 | ETH | - | party3 | market | ACCOUNT_TYPE_GENERAL | ACCOUNT_TYPE_FEES_LIQUIDITY | ETH/MAR24 | 980 | ETH | + | party3 | market | ACCOUNT_TYPE_GENERAL | ACCOUNT_TYPE_FEES_LIQUIDITY | ETH/MAR24 | 978 | ETH | | party3 | | ACCOUNT_TYPE_GENERAL | ACCOUNT_TYPE_FEES_INFRASTRUCTURE | | 50 | ETH | Given the parties submit the following liquidity provision: @@ -134,7 +138,9 @@ Feature: A parties volume_discount_factor is set equal to the factors in the hig And the network moves ahead "1" epochs And the trading mode should be "TRADING_MODE_CONTINUOUS" for the market "ETH/MAR24" - And the party "party3" has the following discount factor "0.03" + And the party "party3" has the following discount infra factor "0.031" + And the party "party3" has the following discount liquidity factor "0.032" + And the party "party3" has the following discount maker factor "0.033" Given the parties place the following orders: | party | market id | side | volume | price | resulting trades | type | tif | diff --git a/core/integration/features/0084-VDPR-014.feature b/core/integration/features/0084-VDPR-014.feature index 08263740bbc..087f642da45 100644 --- a/core/integration/features/0084-VDPR-014.feature +++ b/core/integration/features/0084-VDPR-014.feature @@ -23,10 +23,10 @@ Feature: If a party does not qualify for the lowest tier, their volume_discount_ #risk factor short:3.5569036 #risk factor long:0.801225765 And the volume discount program tiers named "VDP-01": - | volume | factor | - | 3000 | 0.01 | - | 4000 | 0.02 | - | 5000 | 0.03 | + | volume | infra factor | liquidity factor | maker factor | + | 3000 | 0.011 | 0.012 | 0.013 | + | 4000 | 0.021 | 0.022 | 0.023 | + | 5000 | 0.031 | 0.032 | 0.033 | And the volume discount program: | id | tiers | closing timestamp | window length | | id1 | VDP-01 | 0 | 4 | @@ -91,7 +91,7 @@ Feature: If a party does not qualify for the lowest tier, their volume_discount_ | mark price | trading mode | horizon | min bound | max bound | target stake | supplied stake | open interest | | 1000 | TRADING_MODE_CONTINUOUS | 3600 | 973 | 1027 | 3556 | 100000 | 1 | And the party "party3" has the following taker notional "0" - And the party "party3" has the following discount factor "0" + And the party "party3" has the following discount infra factor "0" Then the parties place the following orders: | party | market id | side | volume | price | resulting trades | type | tif | @@ -104,4 +104,4 @@ Feature: If a party does not qualify for the lowest tier, their volume_discount_ # The taker trades above are not enough for party3 to hit the first level of the discount tier # so we get a zero for the discount factor And the party "party3" has the following taker notional "2000" - And the party "party3" has the following discount factor "0" + And the party "party3" has the following discount infra factor "0" diff --git a/core/integration/features/referrals/0083-RFPR-benefit_factors.feature b/core/integration/features/referrals/0083-RFPR-benefit_factors.feature index 5b9a65cb655..1560edfd046 100644 --- a/core/integration/features/referrals/0083-RFPR-benefit_factors.feature +++ b/core/integration/features/referrals/0083-RFPR-benefit_factors.feature @@ -28,9 +28,9 @@ Feature: Setting and applying referee benefit factors # Initalise the referral program then move forwards an epoch to start the program Given the referral benefit tiers "rbt": - | minimum running notional taker volume | minimum epochs | referral reward factor | referral discount factor | - | 2000 | 2 | 0.02 | 0.02 | - | 3000 | 3 | 0.20 | 0.20 | + | minimum running notional taker volume | minimum epochs | referral reward infra factor | referral reward maker factor | referral reward liquidity factor | referral discount infra factor | referral discount maker factor | referral discount liquidity factor | + | 2000 | 2 | 0.021 | 0.022 | 0.023 | 0.024 | 0.025 | 0.026 | + | 3000 | 3 | 0.21 | 0.22 | 0.23 | 0.24 | 0.25 | 0.26 | And the referral staking tiers "rst": | minimum staked tokens | referral reward multiplier | | 1 | 1 | @@ -125,8 +125,8 @@ Feature: Setting and applying referee benefit factors | referee1 | ETH/USD.1.1 | sell | | 1000 | 1 | TYPE_LIMIT | TIF_GTC | When the network moves ahead