Skip to content

Commit

Permalink
Merge pull request #11611 from vegaprotocol/reward_scaling
Browse files Browse the repository at this point in the history
feat: reward scaling
  • Loading branch information
jeremyletang authored Sep 5, 2024
2 parents 2985b37 + b73aec7 commit 395b8a4
Show file tree
Hide file tree
Showing 33 changed files with 3,233 additions and 1,569 deletions.
4 changes: 2 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,8 @@
- [11533](https://github.com/vegaprotocol/vega/issues/11533) - Suppose per party fee discounts in fee estimation.
- [11577](https://github.com/vegaprotocol/vega/issues/11577) - Add API for party discounts and rewards.
- [10716](https://github.com/vegaprotocol/vega/issues/10716) - Set Tendermint defaults during init.
- [11612](https://github.com/vegaprotocol/vega/issues/11612) - Reward scaling support.
-[11624](https://github.com/vegaprotocol/vega/issues/11624) - prevent creation of rewards with no payout, but with high computational cost.
- [11624](https://github.com/vegaprotocol/vega/issues/11624) - prevent creation of rewards with no payout, but with high computational cost.
- [11512](https://github.com/vegaprotocol/vega/issues/11512) - Add loss socialisation amounts to funding payment API.

Expand All @@ -79,7 +81,6 @@
- [11568](https://github.com/vegaprotocol/vega/issues/11568) - order book shape on closing `AMM` no longer panics.
- [11540](https://github.com/vegaprotocol/vega/issues/11540) - Fix spam check for spots to use not double count quantum.
- [11542](https://github.com/vegaprotocol/vega/issues/11542) - Fix non determinism in lottery ranking.
- [11616](https://github.com/vegaprotocol/vega/issues/11616) - `AMM` tradable volume now calculated purely in positions to prevent loss of precision.
- [11544](https://github.com/vegaprotocol/vega/issues/11544) - Fix empty candles stream.
- [11583](https://github.com/vegaprotocol/vega/issues/11583) - Rough bound on price interval when matching with `AMMs` is now looser and calculated in the `AMM` engine.
- [11633](https://github.com/vegaprotocol/vega/issues/11633) - Use bridge deployment heights from network parameter when starting network from genesis.
Expand All @@ -90,7 +91,6 @@
- [11607](https://github.com/vegaprotocol/vega/issues/11607) - Wire rank lottery distribution to team reward payout.
- [959](https://github.com/vegaprotocol/core-test-coverage/issues/959) - Include `ELS` for `AMM` sub keys to the parent key `ELS`.
- [11592](https://github.com/vegaprotocol/vega/issues/11592) - Fix the order of calls at end of epoch between rebate engine and market tracker.
- [10907](https://github.com/vegaprotocol/vega/issues/10907) - Fix position API distressed status not getting updated once the party has been closed out.


## 0.77.5
Expand Down
15 changes: 15 additions & 0 deletions commands/transfer_funds.go
Original file line number Diff line number Diff line change
Expand Up @@ -355,4 +355,19 @@ func validateDispatchStrategy(toAccountType vega.AccountType, dispatchStrategy *
if dispatchStrategy.TransferInterval != nil && (*dispatchStrategy.TransferInterval <= 0 || *dispatchStrategy.TransferInterval > 100) {
errs.AddForProperty(prefix+".transfer_interval", errors.New("must be between 1 and 100"))
}

if dispatchStrategy.TargetNotionalVolume != nil &&
((dispatchStrategy.Metric == vega.DispatchMetric_DISPATCH_METRIC_ELIGIBLE_ENTITIES && len(dispatchStrategy.AssetForMetric) == 0) ||
dispatchStrategy.Metric == vega.DispatchMetric_DISPATCH_METRIC_MARKET_VALUE ||
dispatchStrategy.Metric == vega.DispatchMetric_DISPATCH_METRIC_VALIDATOR_RANKING) {
errs.AddForProperty(prefix+".target_notional_volume", fmt.Errorf(fmt.Sprintf("not allowed for metric %s", dispatchStrategy.Metric)))
}
if dispatchStrategy.TargetNotionalVolume != nil && len(*dispatchStrategy.TargetNotionalVolume) > 0 {
n, overflow := num.UintFromString(*dispatchStrategy.TargetNotionalVolume, 10)
if overflow {
errs.AddForProperty(prefix+".target_notional_volume", ErrIsNotValidNumber)
} else if n.IsNegative() || n.IsZero() {
errs.AddForProperty(prefix+".target_notional_volume", ErrMustBePositive)
}
}
}
118 changes: 117 additions & 1 deletion commands/transfer_funds_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,11 @@ func TestTransferFunds(t *testing.T) {
zeroTransferInterval := int32(0)
negativeTransferInterval := int32(-1)

notionalVolumeInvalidNumber := "banana"
notionalVolumeNegative := "-1"
notionalVolumeZero := "0"
notionalVolumeValid := "100"

cases := []struct {
transfer commandspb.Transfer
errString string
Expand Down Expand Up @@ -1441,6 +1446,114 @@ func TestTransferFunds(t *testing.T) {
Reference: "testing",
},
},
{
transfer: commandspb.Transfer{
FromAccountType: vega.AccountType_ACCOUNT_TYPE_GENERAL,
ToAccountType: vega.AccountType_ACCOUNT_TYPE_REWARD_AVERAGE_NOTIONAL,
Kind: &commandspb.Transfer_Recurring{
Recurring: &commandspb.RecurringTransfer{
StartEpoch: 10,
EndEpoch: ptr.From(uint64(11)),
Factor: "1",
DispatchStrategy: &vega.DispatchStrategy{
AssetForMetric: "",
Metric: vega.DispatchMetric_DISPATCH_METRIC_AVERAGE_NOTIONAL,
EntityScope: vega.EntityScope_ENTITY_SCOPE_INDIVIDUALS,
IndividualScope: vega.IndividualScope_INDIVIDUAL_SCOPE_IN_TEAM,
WindowLength: 100,
TransferInterval: &tooLongTransferInterval,
TargetNotionalVolume: &notionalVolumeInvalidNumber,
},
},
},
To: "84e2b15102a8d6c1c6b4bdf40af8a0dc21b040eaaa1c94cd10d17604b75fdc35",
Asset: "080538b7cc2249de568cb4272a17f4d5e0b0a69a1a240acbf5119d816178daff",
Amount: "1",
Reference: "testing",
},
errString: "transfer.kind.dispatch_strategy.target_notional_volume (is not a valid number)",
},
{
transfer: commandspb.Transfer{
FromAccountType: vega.AccountType_ACCOUNT_TYPE_GENERAL,
ToAccountType: vega.AccountType_ACCOUNT_TYPE_REWARD_AVERAGE_NOTIONAL,
Kind: &commandspb.Transfer_Recurring{
Recurring: &commandspb.RecurringTransfer{
StartEpoch: 10,
EndEpoch: ptr.From(uint64(11)),
Factor: "1",
DispatchStrategy: &vega.DispatchStrategy{
AssetForMetric: "",
Metric: vega.DispatchMetric_DISPATCH_METRIC_AVERAGE_NOTIONAL,
EntityScope: vega.EntityScope_ENTITY_SCOPE_INDIVIDUALS,
IndividualScope: vega.IndividualScope_INDIVIDUAL_SCOPE_IN_TEAM,
WindowLength: 100,
TransferInterval: &tooLongTransferInterval,
TargetNotionalVolume: &notionalVolumeNegative,
},
},
},
To: "84e2b15102a8d6c1c6b4bdf40af8a0dc21b040eaaa1c94cd10d17604b75fdc35",
Asset: "080538b7cc2249de568cb4272a17f4d5e0b0a69a1a240acbf5119d816178daff",
Amount: "1",
Reference: "testing",
},
errString: "transfer.kind.dispatch_strategy.target_notional_volume (must be positive)",
},
{
transfer: commandspb.Transfer{
FromAccountType: vega.AccountType_ACCOUNT_TYPE_GENERAL,
ToAccountType: vega.AccountType_ACCOUNT_TYPE_REWARD_AVERAGE_NOTIONAL,
Kind: &commandspb.Transfer_Recurring{
Recurring: &commandspb.RecurringTransfer{
StartEpoch: 10,
EndEpoch: ptr.From(uint64(11)),
Factor: "1",
DispatchStrategy: &vega.DispatchStrategy{
AssetForMetric: "",
Metric: vega.DispatchMetric_DISPATCH_METRIC_AVERAGE_NOTIONAL,
EntityScope: vega.EntityScope_ENTITY_SCOPE_INDIVIDUALS,
IndividualScope: vega.IndividualScope_INDIVIDUAL_SCOPE_IN_TEAM,
WindowLength: 100,
TransferInterval: &tooLongTransferInterval,
TargetNotionalVolume: &notionalVolumeZero,
},
},
},
To: "84e2b15102a8d6c1c6b4bdf40af8a0dc21b040eaaa1c94cd10d17604b75fdc35",
Asset: "080538b7cc2249de568cb4272a17f4d5e0b0a69a1a240acbf5119d816178daff",
Amount: "1",
Reference: "testing",
},
errString: "transfer.kind.dispatch_strategy.target_notional_volume (must be positive)",
},
{
transfer: commandspb.Transfer{
FromAccountType: vega.AccountType_ACCOUNT_TYPE_GENERAL,
ToAccountType: vega.AccountType_ACCOUNT_TYPE_REWARD_MARKET_PROPOSERS,
Kind: &commandspb.Transfer_Recurring{
Recurring: &commandspb.RecurringTransfer{
StartEpoch: 10,
EndEpoch: ptr.From(uint64(11)),
Factor: "1",
DispatchStrategy: &vega.DispatchStrategy{
AssetForMetric: "",
Metric: vega.DispatchMetric_DISPATCH_METRIC_MARKET_VALUE,
EntityScope: vega.EntityScope_ENTITY_SCOPE_INDIVIDUALS,
IndividualScope: vega.IndividualScope_INDIVIDUAL_SCOPE_IN_TEAM,
WindowLength: 100,
TransferInterval: &tooLongTransferInterval,
TargetNotionalVolume: &notionalVolumeValid,
},
},
},
To: "84e2b15102a8d6c1c6b4bdf40af8a0dc21b040eaaa1c94cd10d17604b75fdc35",
Asset: "080538b7cc2249de568cb4272a17f4d5e0b0a69a1a240acbf5119d816178daff",
Amount: "1",
Reference: "testing",
},
errString: "transfer.kind.dispatch_strategy.target_notional_volume (not allowed for metric DISPATCH_METRIC_MARKET_VALUE)",
},
{
transfer: commandspb.Transfer{
FromAccountType: vega.AccountType_ACCOUNT_TYPE_GENERAL,
Expand All @@ -1453,8 +1566,11 @@ func TestTransferFunds(t *testing.T) {
DispatchStrategy: &vega.DispatchStrategy{
AssetForMetric: "",
Metric: vega.DispatchMetric_DISPATCH_METRIC_ELIGIBLE_ENTITIES,
EntityScope: vega.EntityScope_ENTITY_SCOPE_INDIVIDUALS,
IndividualScope: vega.IndividualScope_INDIVIDUAL_SCOPE_IN_TEAM,
WindowLength: 100,
TransferInterval: &tooLongTransferInterval,
DistributionStrategy: vega.DistributionStrategy_DISTRIBUTION_STRATEGY_PRO_RATA,
EntityScope: vega.EntityScope_ENTITY_SCOPE_TEAMS,
// no asset for metric, no markets in scope, no position requirement, no staking requirement
},
},
Expand Down
1 change: 1 addition & 0 deletions core/banking/engine.go
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,7 @@ type MarketActivityTracker interface {
TeamStatsForMarkets(allMarketsForAssets, onlyTheseMarkets []string) map[string]map[string]*num.Uint
PublishGameMetric(ctx context.Context, dispatchStrategy []*vega.DispatchStrategy, now time.Time)
GameFinished(gameID string)
GetNotionalVolumeForAsset(asset string, markets []string, windowSize int) *num.Uint
}

type EthereumEventSource interface {
Expand Down
1 change: 1 addition & 0 deletions core/banking/gov_transfers.go
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@ func (e *Engine) distributeRecurringGovernanceTransfers(ctx context.Context) {
}

amount, err := e.processGovernanceTransfer(ctx, gTransfer)
amount = e.scaleAmountByTargetNotional(gTransfer.Config.RecurringTransferConfig.DispatchStrategy, amount)
e.log.Info("processed transfer", logging.String("amount", amount.String()))

if err != nil {
Expand Down
14 changes: 14 additions & 0 deletions core/banking/mocks/mocks.go

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

20 changes: 20 additions & 0 deletions core/banking/recurring_transfers.go
Original file line number Diff line number Diff line change
Expand Up @@ -234,6 +234,23 @@ func (e *Engine) dispatchRequired(ctx context.Context, ds *vegapb.DispatchStrate
return required
}

func (e *Engine) scaleAmountByTargetNotional(ds *vegapb.DispatchStrategy, amount *num.Uint) *num.Uint {
if ds == nil {
return amount
}
if ds.TargetNotionalVolume == nil {
return amount
}
actualVolumeInWindow := e.marketActivityTracker.GetNotionalVolumeForAsset(ds.AssetForMetric, ds.Markets, int(ds.WindowLength))
if actualVolumeInWindow.IsZero() {
return num.UintZero()
}
targetNotional := num.MustUintFromString(*ds.TargetNotionalVolume, 10)
ratio := num.MinD(actualVolumeInWindow.ToDecimal().Div(targetNotional.ToDecimal()), num.DecimalOne())
amt, _ := num.UintFromDecimal(ratio.Mul(amount.ToDecimal()))
return amt
}

func (e *Engine) distributeRecurringTransfers(ctx context.Context, newEpoch uint64) {
var (
transfersDone = []events.Event{}
Expand Down Expand Up @@ -273,6 +290,9 @@ func (e *Engine) distributeRecurringTransfers(ctx context.Context, newEpoch uint
)
)

// scale transfer amount as necessary
amount = e.scaleAmountByTargetNotional(v.DispatchStrategy, amount)

// check if the amount is still enough
// ensure asset exists
a, err := e.assets.Get(v.Asset)
Expand Down
43 changes: 43 additions & 0 deletions core/execution/common/market_activity_tracker.go
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,8 @@ type marketTracker struct {
treasuryFeesPaid map[string]*num.Uint
markPrice *num.Uint

notionalVolumeForEpoch *num.Uint

totalMakerFeesReceived *num.Uint
totalMakerFeesPaid *num.Uint
totalLpFees *num.Uint
Expand All @@ -97,6 +99,7 @@ type marketTracker struct {
epochTimeWeightedNotional []map[string]*num.Uint
epochPartyM2M []map[string]num.Decimal
epochPartyRealisedReturn []map[string]num.Decimal
epochNotionalVolume []*num.Uint

valueTraded *num.Uint
proposersPaid map[string]struct{} // identifier of payout_asset : funder : markets_in_scope
Expand Down Expand Up @@ -203,6 +206,7 @@ func (mat *MarketActivityTracker) MarketProposed(asset, marketID, proposer strin
lpPaidFees: map[string]*num.Uint{},
buybackFeesPaid: map[string]*num.Uint{},
treasuryFeesPaid: map[string]*num.Uint{},
notionalVolumeForEpoch: num.UintZero(),
totalMakerFeesReceived: num.UintZero(),
totalMakerFeesPaid: num.UintZero(),
totalLpFees: num.UintZero(),
Expand All @@ -219,6 +223,7 @@ func (mat *MarketActivityTracker) MarketProposed(asset, marketID, proposer strin
epochPartyM2M: []map[string]num.Decimal{},
epochPartyRealisedReturn: []map[string]decimal.Decimal{},
epochTimeWeightedPosition: []map[string]uint64{},
epochNotionalVolume: []*num.Uint{},
epochTimeWeightedNotional: []map[string]*num.Uint{},
allPartiesCache: map[string]struct{}{},
ammPartiesCache: map[string]struct{}{},
Expand Down Expand Up @@ -543,6 +548,11 @@ func (mat *MarketActivityTracker) OnEpochEvent(ctx context.Context, epoch types.
mt.processM2MEndOfEpoch()
mt.processPartyRealisedReturnOfEpoch()
mt.clearFeeActivity()
if len(mt.epochNotionalVolume) == maxWindowSize {
mt.epochNotionalVolume = mt.epochNotionalVolume[1:]
}
mt.epochNotionalVolume = append(mt.epochNotionalVolume, mt.notionalVolumeForEpoch)
mt.notionalVolumeForEpoch = num.UintZero()
}
}
if len(mat.takerFeesPaidInEpoch) == maxWindowSize {
Expand All @@ -566,6 +576,33 @@ func (mat *MarketActivityTracker) clearDeletedMarkets() {
}
}

func (mat *MarketActivityTracker) GetNotionalVolumeForAsset(asset string, markets []string, windowSize int) *num.Uint {
total := num.UintZero()
trackers, ok := mat.assetToMarketTrackers[asset]
if !ok {
return total
}
marketsInScope := map[string]struct{}{}
for _, mkt := range markets {
marketsInScope[mkt] = struct{}{}
}
if len(markets) == 0 {
for mkt := range trackers {
marketsInScope[mkt] = struct{}{}
}
}
for mkt := range marketsInScope {
for i := 0; i < windowSize; i++ {
idx := len(trackers[mkt].epochNotionalVolume) - i - 1
if idx < 0 {
break
}
total.AddSum(trackers[mkt].epochNotionalVolume[idx])
}
}
return total
}

func (mat *MarketActivityTracker) CalculateTotalMakerContributionInQuantum(windowSize int) (map[string]*num.Uint, map[string]num.Decimal) {
m := map[string]*num.Uint{}
total := num.UintZero()
Expand Down Expand Up @@ -1202,6 +1239,12 @@ func (mat *MarketActivityTracker) calculateMetricForParty(asset, party string, m
return num.DecimalZero(), found
}

func (mat *MarketActivityTracker) RecordNotionalTraded(asset, marketID string, notional *num.Uint) {
if tracker, ok := mat.getMarketTracker(asset, marketID); ok {
tracker.notionalVolumeForEpoch.AddSum(notional)
}
}

func (mat *MarketActivityTracker) RecordNotionalTakerVolume(marketID string, party string, volumeToAdd *num.Uint) {
if _, ok := mat.partyTakerNotionalVolume[party]; !ok {
mat.partyTakerNotionalVolume[party] = volumeToAdd
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,8 @@ func getDefaultTracker(t *testing.T) *marketTracker {
twNotional: map[string]*twNotional{},
epochPartyM2M: []map[string]num.Decimal{},
epochPartyRealisedReturn: []map[string]num.Decimal{},
epochNotionalVolume: []*num.Uint{},
notionalVolumeForEpoch: num.UintZero(),
}
}

Expand Down Expand Up @@ -1610,6 +1612,8 @@ func TestIntoProto(t *testing.T) {
epochTimeWeightedPosition: []map[string]uint64{{"p1": 100, "p2": 200}, {"p3": 90, "p4": 80}},
epochTimeWeightedNotional: []map[string]*num.Uint{{"p1": num.NewUint(1000), "p2": num.NewUint(2000)}, {"p1": num.NewUint(3000), "p3": num.NewUint(4000)}},
allPartiesCache: map[string]struct{}{"p1": {}, "p2": {}, "p3": {}, "p4": {}, "p5": {}, "p6": {}},
notionalVolumeForEpoch: num.UintZero(),
epochNotionalVolume: []*num.Uint{num.NewUint(100)},
}

mt1Proto := mt.IntoProto("market1")
Expand Down
Loading

0 comments on commit 395b8a4

Please sign in to comment.