Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: reward scaling #11611

Merged
merged 5 commits into from
Sep 5, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading