From 2a4e57932a620483a63a891f7153bf4e0d6c6c2d Mon Sep 17 00:00:00 2001 From: ze97286 Date: Tue, 13 Aug 2024 18:27:38 +0100 Subject: [PATCH] feat: Include the required set of parties for evaluation for eligible entities reward --- CHANGELOG.md | 1 + core/collateral/engine.go | 17 ++ core/execution/amm/engine.go | 1 + core/execution/amm/mocks/mocks.go | 14 + core/execution/common/interfaces.go | 2 + .../common/market_activity_tracker.go | 21 +- .../market_activity_tracker_internal_test.go | 4 + core/execution/common/mocks/mocks.go | 28 ++ .../rewards/eligible_entities_rewards.feature | 284 +++++++++++++++--- core/integration/setup_test.go | 1 + .../integration/stubs/staking_account_stub.go | 10 + core/protocol/all_services.go | 2 +- core/staking/accounting.go | 10 + 13 files changed, 359 insertions(+), 36 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3b1e8cb8c2a..a4ff15e5b8e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -23,6 +23,7 @@ - [11536](https://github.com/vegaprotocol/vega/issues/11536) - Make the batch market instructions errors programmatically usable. - [11546](https://github.com/vegaprotocol/vega/issues/11546) - Add validation to market proposals metadata. - [11562](https://github.com/vegaprotocol/vega/issues/11562) - Update average notional metric with mark price at the end of the epoch and when calculating live score. +- [11570](https://github.com/vegaprotocol/vega/issues/11570) - Include the required set of parties for evaluation for eligible entities reward. ### 🐛 Fixes diff --git a/core/collateral/engine.go b/core/collateral/engine.go index db59e65f96f..2cc9e77314d 100644 --- a/core/collateral/engine.go +++ b/core/collateral/engine.go @@ -208,6 +208,15 @@ func (e *Engine) CheckOrderSpamAllMarkets(party string) error { return fmt.Errorf("party " + party + " is not eligible to submit order transaction with no market in scope") } +func (e *Engine) GetAllParties() []string { + keys := make([]string, 0, len(e.partiesAccsBalanceCache)) + for k := range e.partiesAccsBalanceCache { + keys = append(keys, k) + } + sort.Strings(keys) + return keys +} + func (e *Engine) CheckOrderSpam(party, market string, assets []string) error { e.cacheLock.RLock() defer e.cacheLock.RUnlock() @@ -448,6 +457,14 @@ func (e *Engine) ReloadConf(cfg Config) { e.cfgMu.Unlock() } +func (e *Engine) OnEpochEvent(ctx context.Context, epoch types.Epoch) { + if epoch.Action == vega.EpochAction_EPOCH_ACTION_START { + e.snapshotBalances() + } +} + +func (e *Engine) OnEpochRestore(ctx context.Context, epoch types.Epoch) {} + // EnableAsset adds a new asset in the collateral engine // this enable the asset to be used by new markets or // parties to deposit funds. diff --git a/core/execution/amm/engine.go b/core/execution/amm/engine.go index 391fe2a1897..e0c931d06f9 100644 --- a/core/execution/amm/engine.go +++ b/core/execution/amm/engine.go @@ -55,6 +55,7 @@ const ( type Collateral interface { GetAssetQuantum(asset string) (num.Decimal, error) + GetAllParties() []string GetPartyMarginAccount(market, party, asset string) (*types.Account, error) GetPartyGeneralAccount(party, asset string) (*types.Account, error) SubAccountUpdate( diff --git a/core/execution/amm/mocks/mocks.go b/core/execution/amm/mocks/mocks.go index 4df3c2c5add..3eab847a39e 100644 --- a/core/execution/amm/mocks/mocks.go +++ b/core/execution/amm/mocks/mocks.go @@ -55,6 +55,20 @@ func (mr *MockCollateralMockRecorder) CreatePartyAMMsSubAccounts(arg0, arg1, arg return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreatePartyAMMsSubAccounts", reflect.TypeOf((*MockCollateral)(nil).CreatePartyAMMsSubAccounts), arg0, arg1, arg2, arg3, arg4) } +// GetAllParties mocks base method. +func (m *MockCollateral) GetAllParties() []string { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetAllParties") + ret0, _ := ret[0].([]string) + return ret0 +} + +// GetAllParties indicates an expected call of GetAllParties. +func (mr *MockCollateralMockRecorder) GetAllParties() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetAllParties", reflect.TypeOf((*MockCollateral)(nil).GetAllParties)) +} + // GetAssetQuantum mocks base method. func (m *MockCollateral) GetAssetQuantum(arg0 string) (decimal.Decimal, error) { m.ctrl.T.Helper() diff --git a/core/execution/common/interfaces.go b/core/execution/common/interfaces.go index a5ecf247111..df3c66cf220 100644 --- a/core/execution/common/interfaces.go +++ b/core/execution/common/interfaces.go @@ -195,6 +195,7 @@ type Collateral interface { GetOrCreateLiquidityFeesBonusDistributionAccount(ctx context.Context, marketID, asset string) (*types.Account, error) CheckOrderSpam(party, market string, assets []string) error CheckOrderSpamAllMarkets(party string) error + GetAllParties() []string // amm stuff SubAccountClosed(ctx context.Context, party, subAccount, asset, market string) ([]*types.LedgerMovement, error) @@ -415,6 +416,7 @@ type CommonMarket interface { type AccountBalanceChecker interface { GetAvailableBalance(party string) (*num.Uint, error) + GetAllStakingParties() []string } type Teams interface { diff --git a/core/execution/common/market_activity_tracker.go b/core/execution/common/market_activity_tracker.go index a4df6f24f7f..3d43ec0f738 100644 --- a/core/execution/common/market_activity_tracker.go +++ b/core/execution/common/market_activity_tracker.go @@ -49,6 +49,7 @@ var ( type QuantumGetter interface { GetAssetQuantum(asset string) (num.Decimal, error) + GetAllParties() []string } type twPosition struct { @@ -819,7 +820,25 @@ func (mat *MarketActivityTracker) getPartiesInScope(ds *vega.DispatchStrategy) [ if ds.IndividualScope == vega.IndividualScope_INDIVIDUAL_SCOPE_IN_TEAM { parties = mat.teams.GetAllPartiesInTeams(mat.minEpochsInTeamForRewardEligibility) } else if ds.IndividualScope == vega.IndividualScope_INDIVIDUAL_SCOPE_ALL { - parties = sortedK(mat.getAllParties(ds.AssetForMetric, ds.Markets)) + if ds.Metric == vega.DispatchMetric_DISPATCH_METRIC_ELIGIBLE_ENTITIES { + notionalReq := num.UintZero() + stakingReq := num.UintZero() + if len(ds.NotionalTimeWeightedAveragePositionRequirement) > 0 { + notionalReq = num.MustUintFromString(ds.NotionalTimeWeightedAveragePositionRequirement, 10) + } + if len(ds.StakingRequirement) > 0 { + stakingReq = num.MustUintFromString(ds.StakingRequirement, 10) + } + if !notionalReq.IsZero() { + parties = sortedK(mat.getAllParties(ds.AssetForMetric, ds.Markets)) + } else if !stakingReq.IsZero() { + parties = mat.balanceChecker.GetAllStakingParties() + } else { + parties = mat.collateral.GetAllParties() + } + } else { + parties = sortedK(mat.getAllParties(ds.AssetForMetric, ds.Markets)) + } } else if ds.IndividualScope == vega.IndividualScope_INDIVIDUAL_SCOPE_NOT_IN_TEAM { parties = sortedK(excludePartiesInTeams(mat.getAllParties(ds.AssetForMetric, ds.Markets), mat.teams.GetAllPartiesInTeams(mat.minEpochsInTeamForRewardEligibility))) } else if ds.IndividualScope == vega.IndividualScope_INDIVIDUAL_SCOPE_AMM { diff --git a/core/execution/common/market_activity_tracker_internal_test.go b/core/execution/common/market_activity_tracker_internal_test.go index 59a1da8b72d..c2194ab9417 100644 --- a/core/execution/common/market_activity_tracker_internal_test.go +++ b/core/execution/common/market_activity_tracker_internal_test.go @@ -1574,6 +1574,10 @@ func (e DummyCollateralEngine) GetAssetQuantum(asset string) (num.Decimal, error return num.DecimalOne(), nil } +func (e DummyCollateralEngine) GetAllParties() []string { + return []string{} +} + type DummyEligibilityChecker struct{} func (e *DummyEligibilityChecker) IsEligibleForProposerBonus(marketID string, volumeTraded *num.Uint) bool { diff --git a/core/execution/common/mocks/mocks.go b/core/execution/common/mocks/mocks.go index f64b6a4cada..d2888956520 100644 --- a/core/execution/common/mocks/mocks.go +++ b/core/execution/common/mocks/mocks.go @@ -474,6 +474,20 @@ func (mr *MockCollateralMockRecorder) FinalSettlement(arg0, arg1, arg2, arg3, ar return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "FinalSettlement", reflect.TypeOf((*MockCollateral)(nil).FinalSettlement), arg0, arg1, arg2, arg3, arg4) } +// GetAllParties mocks base method. +func (m *MockCollateral) GetAllParties() []string { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetAllParties") + ret0, _ := ret[0].([]string) + return ret0 +} + +// GetAllParties indicates an expected call of GetAllParties. +func (mr *MockCollateralMockRecorder) GetAllParties() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetAllParties", reflect.TypeOf((*MockCollateral)(nil).GetAllParties)) +} + // GetAssetQuantum mocks base method. func (m *MockCollateral) GetAssetQuantum(arg0 string) (decimal.Decimal, error) { m.ctrl.T.Helper() @@ -2645,6 +2659,20 @@ func (m *MockAccountBalanceChecker) EXPECT() *MockAccountBalanceCheckerMockRecor return m.recorder } +// GetAllStakingParties mocks base method. +func (m *MockAccountBalanceChecker) GetAllStakingParties() []string { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetAllStakingParties") + ret0, _ := ret[0].([]string) + return ret0 +} + +// GetAllStakingParties indicates an expected call of GetAllStakingParties. +func (mr *MockAccountBalanceCheckerMockRecorder) GetAllStakingParties() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetAllStakingParties", reflect.TypeOf((*MockAccountBalanceChecker)(nil).GetAllStakingParties)) +} + // GetAvailableBalance mocks base method. func (m *MockAccountBalanceChecker) GetAvailableBalance(arg0 string) (*num.Uint, error) { m.ctrl.T.Helper() diff --git a/core/integration/features/rewards/eligible_entities_rewards.feature b/core/integration/features/rewards/eligible_entities_rewards.feature index acc89f41219..cd101c89a94 100644 --- a/core/integration/features/rewards/eligible_entities_rewards.feature +++ b/core/integration/features/rewards/eligible_entities_rewards.feature @@ -42,9 +42,6 @@ Feature: Eligible parties metric rewards | aux2 | ETH | 100000000 | | trader3 | ETH | 10000 | | trader4 | ETH | 10000 | - | lpprov | ETH | 200000000 | - | party1 | ETH | 100000 | - | party2 | ETH | 100000 | And the parties deposit on staking account the following amount: @@ -53,10 +50,6 @@ Feature: Eligible parties metric rewards | aux2 | VEGA | 1000 | | trader3 | VEGA | 1500 | | trader4 | VEGA | 1000 | - | lpprov | VEGA | 10000 | - | party1 | VEGA | 2000 | - | party2 | VEGA | 2000 | - | party3 | VEGA | 500 | Given time is updated to "2023-09-23T00:00:00Z" Given the average block duration is "1" @@ -85,13 +78,6 @@ Feature: Eligible parties metric rewards | 3 | a3c024b4e23230c89884a54a813b1ecb4cb0f827a38641c66eeca466da6b2ddc | ACCOUNT_TYPE_GENERAL | 0000000000000000000000000000000000000000000000000000000000000000 | ACCOUNT_TYPE_REWARD_ELIGIBLE_ENTITIES | VEGA | 10000 | 1 | | 1 | DISPATCH_METRIC_ELIGIBLE_ENTITIES | ETH | | 2 | 1 | PRO_RATA | INDIVIDUALS | ALL | 0 | 10000 | | 4 | a3c024b4e23230c89884a54a813b1ecb4cb0f827a38641c66eeca466da6b2ddd | ACCOUNT_TYPE_GENERAL | 0000000000000000000000000000000000000000000000000000000000000000 | ACCOUNT_TYPE_REWARD_ELIGIBLE_ENTITIES | VEGA | 10000 | 1 | | 1 | DISPATCH_METRIC_ELIGIBLE_ENTITIES | ETH | | 2 | 1 | PRO_RATA | INDIVIDUALS | ALL | 1000 | 10000 | - Then the network moves ahead "2" epochs - # No party is known at this point cos no party made any activity contributing to any metric - And "a3c024b4e23230c89884a54a813b1ecb4cb0f827a38641c66eeca466da6b2dda" should have general account balance of "1000000" for asset "VEGA" - And "a3c024b4e23230c89884a54a813b1ecb4cb0f827a38641c66eeca466da6b2ddb" should have general account balance of "1000000" for asset "VEGA" - And "a3c024b4e23230c89884a54a813b1ecb4cb0f827a38641c66eeca466da6b2ddc" should have general account balance of "1000000" for asset "VEGA" - And "a3c024b4e23230c89884a54a813b1ecb4cb0f827a38641c66eeca466da6b2ddd" should have general account balance of "1000000" for asset "VEGA" - Then the parties place the following orders: | party | market id | side | volume | price | resulting trades | type | tif | | aux1 | ETH/DEC21 | buy | 10 | 1000 | 0 | TYPE_LIMIT | TIF_GTC | @@ -122,14 +108,14 @@ Feature: Eligible parties metric rewards # not distributed as the notional requirement not met And "a3c024b4e23230c89884a54a813b1ecb4cb0f827a38641c66eeca466da6b2ddd" should have general account balance of "1000000" for asset "VEGA" - # they get 1/4 of the reward with no requirements + 1/2 of the reward with staking minimum = 2500+5000=7500 - And "aux1" should have vesting account balance of "7500" for asset "VEGA" - # they get 1/4 of the reward with no requirements = 2500 - And "aux2" should have vesting account balance of "2500" for asset "VEGA" - # they get 1/4 of the reward with no requirements + 1/2 of the reward with staking minimum = 2500+5000=7500 - And "trader3" should have vesting account balance of "7500" for asset "VEGA" - # they get 1/4 of the reward with no requirements = 2500 - And "trader4" should have vesting account balance of "2500" for asset "VEGA" + # they get 1/8 of the reward with no requirements + 1/2 of the reward with staking minimum = 1250+5000=6250 + And "aux1" should have vesting account balance of "6250" for asset "VEGA" + # they get 1/8 of the reward with no requirements = 1250 + And "aux2" should have vesting account balance of "1250" for asset "VEGA" + # they get 1/8 of the reward with no requirements + 1/2 of the reward with staking minimum = 1250+5000=6250 + And "trader3" should have vesting account balance of "6250" for asset "VEGA" + # they get 1/8 of the reward with no requirements = 1250 + And "trader4" should have vesting account balance of "1250" for asset "VEGA" # now lets get some notional so we can satisfy the notional requirement When the parties place the following orders with ticks: @@ -147,18 +133,18 @@ Feature: Eligible parties metric rewards # we have trade3 statisfying the notional requirement so distributed And "a3c024b4e23230c89884a54a813b1ecb4cb0f827a38641c66eeca466da6b2ddd" should have general account balance of "990000" for asset "VEGA" - # for reward (1) each gets a quarter + # for reward (1) each gets a 1/8 # for reward (2) aux1 and trader3 split the reward # for reward (3) each gets a quarter (all have sufficient notional) # for reward (4) each gets a quarter - # aux1 = 7500 + 2500 + 5000 + 2500 + 2500 = 20000 - And "aux1" should have vesting account balance of "20000" for asset "VEGA" - # aux2 = 2500 + 2500 + 2500 + 2500 = 7500 - And "aux2" should have vesting account balance of "10000" for asset "VEGA" - # trader3 = 7500 + 2500 + 5000 + 2500 + 2500 = 20000 - And "trader3" should have vesting account balance of "20000" for asset "VEGA" - # trader4 = 2500 + 2500 + 2500 + 2500 = 10000 - And "trader4" should have vesting account balance of "10000" for asset "VEGA" + # aux1 = 6250 + 1250 + 5000 + 2500 + 2500 = 17500 + And "aux1" should have vesting account balance of "17500" for asset "VEGA" + # aux2 = 1250 + 1250 + 2500 + 2500 = 7500 + And "aux2" should have vesting account balance of "7500" for asset "VEGA" + # trader3 = 6250 + 1250 + 5000 + 2500 + 2500 = 17500 + And "trader3" should have vesting account balance of "17500" for asset "VEGA" + # trader4 = 1250 + 1250 + 2500 + 2500 = 7500 + And "trader4" should have vesting account balance of "7500" for asset "VEGA" Scenario: Given a recurring transfer using the eligible entries metric and scoping individuals. If multiple parties meet all eligibility they should receive rewards proportional to any reward multipliers. (0056-REWA-178) Given the following network parameters are set: @@ -205,6 +191,236 @@ Feature: Eligible parties metric rewards # no requirement so surely distributed # trader3 and aux1 have multipliers of 2 And "a3c024b4e23230c89884a54a813b1ecb4cb0f827a38641c66eeca466da6b2dda" should have general account balance of "990000" for asset "VEGA" - And "aux1" should have vesting account balance of "4000" for asset "VEGA" - And "aux2" should have vesting account balance of "2000" for asset "VEGA" - And "trader3" should have vesting account balance of "4000" for asset "VEGA" \ No newline at end of file + And "aux1" should have vesting account balance of "2000" for asset "VEGA" + And "aux2" should have vesting account balance of "1000" for asset "VEGA" + And "trader3" should have vesting account balance of "2000" for asset "VEGA" + And "trader4" should have vesting account balance of "1000" for asset "VEGA" + And "a3c024b4e23230c89884a54a813b1ecb4cb0f827a38641c66eeca466da6b2dda" should have vesting account balance of "1000" for asset "VEGA" + And "a3c024b4e23230c89884a54a813b1ecb4cb0f827a38641c66eeca466da6b2ddb" should have vesting account balance of "1000" for asset "VEGA" + And "a3c024b4e23230c89884a54a813b1ecb4cb0f827a38641c66eeca466da6b2ddc" should have vesting account balance of "1000" for asset "VEGA" + And "a3c024b4e23230c89884a54a813b1ecb4cb0f827a38641c66eeca466da6b2ddd" should have vesting account balance of "1000" for asset "VEGA" + + Scenario: Given a recurring transfer using the eligible entities metric and a reward window length N greater than one, a party who met the eligibility requirements in the current epoch as well as the previous N-1 epochs will receive rewards at the end of the epoch. (0056-REWA-179) + # setup recurring transfer to the reward account - this will start at the end of this epoch (1) + Given the parties submit the following recurring transfers: + | id | from | from_account_type | to | to_account_type | asset | amount | start_epoch | end_epoch | factor | metric | metric_asset | markets | lock_period | window_length | distribution_strategy | entity_scope | individual_scope | staking_requirement | notional_requirement | + | 1 | a3c024b4e23230c89884a54a813b1ecb4cb0f827a38641c66eeca466da6b2dda | ACCOUNT_TYPE_GENERAL | 0000000000000000000000000000000000000000000000000000000000000000 | ACCOUNT_TYPE_REWARD_ELIGIBLE_ENTITIES | VEGA | 10000 | 1 | | 1 | DISPATCH_METRIC_ELIGIBLE_ENTITIES | | | 2 | 2 | PRO_RATA | INDIVIDUALS | ALL | 0 | 0 | + | 2 | a3c024b4e23230c89884a54a813b1ecb4cb0f827a38641c66eeca466da6b2ddb | ACCOUNT_TYPE_GENERAL | 0000000000000000000000000000000000000000000000000000000000000000 | ACCOUNT_TYPE_REWARD_ELIGIBLE_ENTITIES | VEGA | 10000 | 1 | | 1 | DISPATCH_METRIC_ELIGIBLE_ENTITIES | | | 2 | 2 | PRO_RATA | INDIVIDUALS | ALL | 1200 | 0 | + | 3 | a3c024b4e23230c89884a54a813b1ecb4cb0f827a38641c66eeca466da6b2ddc | ACCOUNT_TYPE_GENERAL | 0000000000000000000000000000000000000000000000000000000000000000 | ACCOUNT_TYPE_REWARD_ELIGIBLE_ENTITIES | VEGA | 10000 | 1 | | 1 | DISPATCH_METRIC_ELIGIBLE_ENTITIES | ETH | | 2 | 2 | PRO_RATA | INDIVIDUALS | ALL | 0 | 10000 | + | 4 | a3c024b4e23230c89884a54a813b1ecb4cb0f827a38641c66eeca466da6b2ddd | ACCOUNT_TYPE_GENERAL | 0000000000000000000000000000000000000000000000000000000000000000 | ACCOUNT_TYPE_REWARD_ELIGIBLE_ENTITIES | VEGA | 10000 | 1 | | 1 | DISPATCH_METRIC_ELIGIBLE_ENTITIES | ETH | | 2 | 2 | PRO_RATA | INDIVIDUALS | ALL | 1000 | 10000 | + + Then the parties place the following orders: + | party | market id | side | volume | price | resulting trades | type | tif | + | aux1 | ETH/DEC21 | buy | 10 | 1000 | 0 | TYPE_LIMIT | TIF_GTC | + | aux2 | ETH/DEC21 | sell | 10 | 1000 | 0 | TYPE_LIMIT | TIF_GTC | + | aux1 | ETH/DEC21 | buy | 1 | 900 | 0 | TYPE_LIMIT | TIF_GTC | + | aux2 | ETH/DEC21 | sell | 1 | 1100 | 0 | TYPE_LIMIT | TIF_GTC | + + Then the opening auction period ends for market "ETH/DEC21" + And the market data for the market "ETH/DEC21" should be: + | mark price | trading mode | + | 1000 | TRADING_MODE_CONTINUOUS | + + When the parties place the following orders with ticks: + | party | market id | side | volume | price | resulting trades | type | tif | + | trader3 | ETH/DEC21 | buy | 3 | 1002 | 0 | TYPE_LIMIT | TIF_GTC | + + Then the parties place the following orders with ticks: + | party | market id | side | volume | price | resulting trades | type | tif | + | trader4 | ETH/DEC21 | sell | 4 | 1002 | 1 | TYPE_LIMIT | TIF_GTC | + + Then the network moves ahead "1" epochs + # no requirement so surely distributed + And "a3c024b4e23230c89884a54a813b1ecb4cb0f827a38641c66eeca466da6b2dda" should have general account balance of "990000" for asset "VEGA" + # only staking requirement so distributed + And "a3c024b4e23230c89884a54a813b1ecb4cb0f827a38641c66eeca466da6b2ddb" should have general account balance of "990000" for asset "VEGA" + # not distributed as the notional requirement not met + And "a3c024b4e23230c89884a54a813b1ecb4cb0f827a38641c66eeca466da6b2ddc" should have general account balance of "1000000" for asset "VEGA" + # not distributed as the notional requirement not met + And "a3c024b4e23230c89884a54a813b1ecb4cb0f827a38641c66eeca466da6b2ddd" should have general account balance of "1000000" for asset "VEGA" + + # they get 1/8 of the reward with no requirements + 1/2 of the reward with staking minimum = 1250+5000=6250 + And "aux1" should have vesting account balance of "6250" for asset "VEGA" + # they get 1/8 of the reward with no requirements = 1250 + And "aux2" should have vesting account balance of "1250" for asset "VEGA" + # they get 1/8 of the reward with no requirements + 1/2 of the reward with staking minimum = 1250+5000=6250 + And "trader3" should have vesting account balance of "6250" for asset "VEGA" + # they get 1/8 of the reward with no requirements = 1250 + And "trader4" should have vesting account balance of "1250" for asset "VEGA" + + # now lets get some notional so we can satisfy the notional requirement + When the parties place the following orders with ticks: + | party | market id | side | volume | price | resulting trades | type | tif | + | trader3 | ETH/DEC21 | buy | 10 | 1002 | 1 | TYPE_LIMIT | TIF_GTC | + | trader4 | ETH/DEC21 | sell | 10 | 1002 | 1 | TYPE_LIMIT | TIF_GTC | + + Then the network moves ahead "1" epochs + # no requirement so surely distributed + And "a3c024b4e23230c89884a54a813b1ecb4cb0f827a38641c66eeca466da6b2dda" should have general account balance of "980000" for asset "VEGA" + # only staking requirement so distributed + And "a3c024b4e23230c89884a54a813b1ecb4cb0f827a38641c66eeca466da6b2ddb" should have general account balance of "980000" for asset "VEGA" + # we have trade3 statisfying the notional requirement so distributed + And "a3c024b4e23230c89884a54a813b1ecb4cb0f827a38641c66eeca466da6b2ddc" should have general account balance of "990000" for asset "VEGA" + # we have trade3 statisfying the notional requirement so distributed + And "a3c024b4e23230c89884a54a813b1ecb4cb0f827a38641c66eeca466da6b2ddd" should have general account balance of "990000" for asset "VEGA" + + # for reward (1) each gets a 1/8 + # for reward (2) aux1 and trader3 split the reward + # for reward (3) each gets a quarter (all have sufficient notional) + # for reward (4) each gets a quarter + # aux1 = 6250 + 1250 + 5000 + 2500 + 2500 = 17500 + And "aux1" should have vesting account balance of "17500" for asset "VEGA" + # aux2 = 1250 + 1250 + 2500 + 2500 = 7500 + And "aux2" should have vesting account balance of "7500" for asset "VEGA" + # trader3 = 6250 + 1250 + 5000 + 2500 + 2500 = 17500 + And "trader3" should have vesting account balance of "17500" for asset "VEGA" + # trader4 = 1250 + 1250 + 2500 + 2500 = 7500 + And "trader4" should have vesting account balance of "7500" for asset "VEGA" + + Scenario: Given a recurring transfer using the eligible entities metric and a reward window length N greater than one, a party who met the eligibility requirements in the current epoch only will receive no rewards at the end of the epoch. + # setup recurring transfer to the reward account - this will start at the end of this epoch (1) + Given the parties submit the following recurring transfers: + | id | from | from_account_type | to | to_account_type | asset | amount | start_epoch | end_epoch | factor | metric | metric_asset | markets | lock_period | window_length | distribution_strategy | entity_scope | individual_scope | staking_requirement | notional_requirement | + | 1 | a3c024b4e23230c89884a54a813b1ecb4cb0f827a38641c66eeca466da6b2ddb | ACCOUNT_TYPE_GENERAL | 0000000000000000000000000000000000000000000000000000000000000000 | ACCOUNT_TYPE_REWARD_ELIGIBLE_ENTITIES | VEGA | 10000 | 1 | | 1 | DISPATCH_METRIC_ELIGIBLE_ENTITIES | | | 2 | 2 | PRO_RATA | INDIVIDUALS | ALL | 1200 | 0 | + | 2 | a3c024b4e23230c89884a54a813b1ecb4cb0f827a38641c66eeca466da6b2ddc | ACCOUNT_TYPE_GENERAL | 0000000000000000000000000000000000000000000000000000000000000000 | ACCOUNT_TYPE_REWARD_ELIGIBLE_ENTITIES | VEGA | 10000 | 1 | | 1 | DISPATCH_METRIC_ELIGIBLE_ENTITIES | ETH | | 2 | 2 | PRO_RATA | INDIVIDUALS | ALL | 0 | 10000 | + | 3 | a3c024b4e23230c89884a54a813b1ecb4cb0f827a38641c66eeca466da6b2ddd | ACCOUNT_TYPE_GENERAL | 0000000000000000000000000000000000000000000000000000000000000000 | ACCOUNT_TYPE_REWARD_ELIGIBLE_ENTITIES | VEGA | 10000 | 1 | | 1 | DISPATCH_METRIC_ELIGIBLE_ENTITIES | ETH | | 2 | 2 | PRO_RATA | INDIVIDUALS | ALL | 1000 | 10000 | + + Then the parties place the following orders: + | party | market id | side | volume | price | resulting trades | type | tif | + | aux1 | ETH/DEC21 | buy | 10 | 1000 | 0 | TYPE_LIMIT | TIF_GTC | + | aux2 | ETH/DEC21 | sell | 10 | 1000 | 0 | TYPE_LIMIT | TIF_GTC | + | aux1 | ETH/DEC21 | buy | 1 | 900 | 0 | TYPE_LIMIT | TIF_GTC | + | aux2 | ETH/DEC21 | sell | 1 | 1100 | 0 | TYPE_LIMIT | TIF_GTC | + + Then the opening auction period ends for market "ETH/DEC21" + And the market data for the market "ETH/DEC21" should be: + | mark price | trading mode | + | 1000 | TRADING_MODE_CONTINUOUS | + + When the parties place the following orders with ticks: + | party | market id | side | volume | price | resulting trades | type | tif | + | trader3 | ETH/DEC21 | buy | 3 | 1002 | 0 | TYPE_LIMIT | TIF_GTC | + + Then the parties place the following orders with ticks: + | party | market id | side | volume | price | resulting trades | type | tif | + | trader4 | ETH/DEC21 | sell | 4 | 1002 | 1 | TYPE_LIMIT | TIF_GTC | + + Then the network moves ahead "1" epochs + # only staking requirement so distributed + And "a3c024b4e23230c89884a54a813b1ecb4cb0f827a38641c66eeca466da6b2ddb" should have general account balance of "990000" for asset "VEGA" + # not distributed as the notional requirement not met + And "a3c024b4e23230c89884a54a813b1ecb4cb0f827a38641c66eeca466da6b2ddc" should have general account balance of "1000000" for asset "VEGA" + # not distributed as the notional requirement not met + And "a3c024b4e23230c89884a54a813b1ecb4cb0f827a38641c66eeca466da6b2ddd" should have general account balance of "1000000" for asset "VEGA" + + # they get 1/2 of the reward with staking minimum = 5000 + And "aux1" should have vesting account balance of "5000" for asset "VEGA" + # they get 1/2 of the reward with staking minimum = 5000 + And "trader3" should have vesting account balance of "5000" for asset "VEGA" + + # now lets get some notional so we can satisfy the notional requirement + When the parties place the following orders with ticks: + | party | market id | side | volume | price | resulting trades | type | tif | + | trader3 | ETH/DEC21 | buy | 10 | 1002 | 1 | TYPE_LIMIT | TIF_GTC | + | trader4 | ETH/DEC21 | sell | 10 | 1002 | 1 | TYPE_LIMIT | TIF_GTC | + + # now lets make aux2 and trader 4 satisfy the staking requirement + And the parties deposit on staking account the following amount: + | party | asset | amount | + | aux2 | VEGA | 1200 | + | trader4 | VEGA | 1200 | + + Then the network moves ahead "1" epochs + And "a3c024b4e23230c89884a54a813b1ecb4cb0f827a38641c66eeca466da6b2ddb" should have general account balance of "980000" for asset "VEGA" + And "a3c024b4e23230c89884a54a813b1ecb4cb0f827a38641c66eeca466da6b2ddc" should have general account balance of "990000" for asset "VEGA" + And "a3c024b4e23230c89884a54a813b1ecb4cb0f827a38641c66eeca466da6b2ddd" should have general account balance of "990000" for asset "VEGA" + + # for reward (1) aux1 and trader3 split the reward (because they satisfied the requirement for both epochs) + # for reward (2) split 4 ways - all have notional, no staking requirement + # for reward (3) split 4 ways - all have notional, lower staking requirement met in both epochs + And "aux1" should have vesting account balance of "15000" for asset "VEGA" + And "aux2" should have vesting account balance of "5000" for asset "VEGA" + And "trader3" should have vesting account balance of "15000" for asset "VEGA" + And "trader4" should have vesting account balance of "5000" for asset "VEGA" + + Then the network moves ahead "1" epochs + And "a3c024b4e23230c89884a54a813b1ecb4cb0f827a38641c66eeca466da6b2ddb" should have general account balance of "970000" for asset "VEGA" + And "a3c024b4e23230c89884a54a813b1ecb4cb0f827a38641c66eeca466da6b2ddc" should have general account balance of "980000" for asset "VEGA" + And "a3c024b4e23230c89884a54a813b1ecb4cb0f827a38641c66eeca466da6b2ddd" should have general account balance of "980000" for asset "VEGA" + + # all 3 rewards are now split 4 ways + # aux1 = 15000 + 2500 + 2500 + 2500 = 22500 + And "aux1" should have vesting account balance of "22500" for asset "VEGA" + # aux2 = 5000 + 2500 + 2500 + 2500 = 12500 + And "aux2" should have vesting account balance of "12500" for asset "VEGA" + # trader3 = 15000 + 2500 + 2500 + 2500 = 22500 + And "trader3" should have vesting account balance of "22500" for asset "VEGA" + # trader4 = 5000 + 2500 + 2500 + 2500 = 7500 + And "trader4" should have vesting account balance of "12500" for asset "VEGA" + + Scenario: Given a recurring transfer using the eligible entities metric and a reward window length greater than one, a party who met the eligibility requirements in a previous epoch in the window, but not the current epoch will receive no rewards at the end of the epoch. (0056-REWA-181) + # setup recurring transfer to the reward account - this will start at the end of this epoch (1) + Given the parties submit the following recurring transfers: + | id | from | from_account_type | to | to_account_type | asset | amount | start_epoch | end_epoch | factor | metric | metric_asset | markets | lock_period | window_length | distribution_strategy | entity_scope | individual_scope | staking_requirement | notional_requirement | + | 1 | a3c024b4e23230c89884a54a813b1ecb4cb0f827a38641c66eeca466da6b2ddb | ACCOUNT_TYPE_GENERAL | 0000000000000000000000000000000000000000000000000000000000000000 | ACCOUNT_TYPE_REWARD_ELIGIBLE_ENTITIES | VEGA | 10000 | 1 | | 1 | DISPATCH_METRIC_ELIGIBLE_ENTITIES | | | 2 | 2 | PRO_RATA | INDIVIDUALS | ALL | 1200 | 0 | + | 2 | a3c024b4e23230c89884a54a813b1ecb4cb0f827a38641c66eeca466da6b2ddc | ACCOUNT_TYPE_GENERAL | 0000000000000000000000000000000000000000000000000000000000000000 | ACCOUNT_TYPE_REWARD_ELIGIBLE_ENTITIES | VEGA | 10000 | 1 | | 1 | DISPATCH_METRIC_ELIGIBLE_ENTITIES | ETH | | 2 | 2 | PRO_RATA | INDIVIDUALS | ALL | 0 | 10000 | + | 3 | a3c024b4e23230c89884a54a813b1ecb4cb0f827a38641c66eeca466da6b2ddd | ACCOUNT_TYPE_GENERAL | 0000000000000000000000000000000000000000000000000000000000000000 | ACCOUNT_TYPE_REWARD_ELIGIBLE_ENTITIES | VEGA | 10000 | 1 | | 1 | DISPATCH_METRIC_ELIGIBLE_ENTITIES | ETH | | 2 | 2 | PRO_RATA | INDIVIDUALS | ALL | 1000 | 10000 | + + Then the parties place the following orders: + | party | market id | side | volume | price | resulting trades | type | tif | + | aux1 | ETH/DEC21 | buy | 10 | 1000 | 0 | TYPE_LIMIT | TIF_GTC | + | aux2 | ETH/DEC21 | sell | 10 | 1000 | 0 | TYPE_LIMIT | TIF_GTC | + | aux1 | ETH/DEC21 | buy | 1 | 900 | 0 | TYPE_LIMIT | TIF_GTC | + | aux2 | ETH/DEC21 | sell | 1 | 1100 | 0 | TYPE_LIMIT | TIF_GTC | + + Then the opening auction period ends for market "ETH/DEC21" + And the market data for the market "ETH/DEC21" should be: + | mark price | trading mode | + | 1000 | TRADING_MODE_CONTINUOUS | + + When the parties place the following orders with ticks: + | party | market id | side | volume | price | resulting trades | type | tif | + | trader3 | ETH/DEC21 | buy | 3 | 1002 | 0 | TYPE_LIMIT | TIF_GTC | + + Then the parties place the following orders with ticks: + | party | market id | side | volume | price | resulting trades | type | tif | + | trader4 | ETH/DEC21 | sell | 4 | 1002 | 1 | TYPE_LIMIT | TIF_GTC | + + Then the network moves ahead "1" epochs + # only staking requirement so distributed + And "a3c024b4e23230c89884a54a813b1ecb4cb0f827a38641c66eeca466da6b2ddb" should have general account balance of "990000" for asset "VEGA" + # not distributed as the notional requirement not met + And "a3c024b4e23230c89884a54a813b1ecb4cb0f827a38641c66eeca466da6b2ddc" should have general account balance of "1000000" for asset "VEGA" + # not distributed as the notional requirement not met + And "a3c024b4e23230c89884a54a813b1ecb4cb0f827a38641c66eeca466da6b2ddd" should have general account balance of "1000000" for asset "VEGA" + + # they get 1/2 of the reward with staking minimum = 5000 + And "aux1" should have vesting account balance of "5000" for asset "VEGA" + # they get 1/2 of the reward with staking minimum = 5000 + And "trader3" should have vesting account balance of "5000" for asset "VEGA" + + # now lets get some notional so we can satisfy the notional requirement + When the parties place the following orders with ticks: + | party | market id | side | volume | price | resulting trades | type | tif | + | trader3 | ETH/DEC21 | buy | 10 | 1002 | 1 | TYPE_LIMIT | TIF_GTC | + | trader4 | ETH/DEC21 | sell | 10 | 1002 | 1 | TYPE_LIMIT | TIF_GTC | + + # lets withdraw the staking to make them ineligible in the following window + And the parties withdraw from staking account the following amount: + | party | asset | amount | + | aux1 | VEGA | 1200 | + | aux2 | VEGA | 800 | + | trader4 | VEGA | 1000 | + + Then the network moves ahead "1" epochs + And "a3c024b4e23230c89884a54a813b1ecb4cb0f827a38641c66eeca466da6b2ddb" should have general account balance of "980000" for asset "VEGA" + And "a3c024b4e23230c89884a54a813b1ecb4cb0f827a38641c66eeca466da6b2ddc" should have general account balance of "990000" for asset "VEGA" + And "a3c024b4e23230c89884a54a813b1ecb4cb0f827a38641c66eeca466da6b2ddd" should have general account balance of "990000" for asset "VEGA" + + # for reward (1) is paid only to trader 3 + # for reward (2) split 4 ways - all have notional, no staking requirement + # for reward (3) is paid only to trader 3 + And "aux1" should have vesting account balance of "7500" for asset "VEGA" + And "aux2" should have vesting account balance of "2500" for asset "VEGA" + And "trader3" should have vesting account balance of "27500" for asset "VEGA" + And "trader4" should have vesting account balance of "2500" for asset "VEGA" diff --git a/core/integration/setup_test.go b/core/integration/setup_test.go index 470507ac11b..4b2e01164ce 100644 --- a/core/integration/setup_test.go +++ b/core/integration/setup_test.go @@ -167,6 +167,7 @@ func newExecutionTestSetup() *executionTestSetup { execsetup.collateralEngine = collateral.New(execsetup.log, collateral.NewDefaultConfig(), execsetup.timeService, execsetup.broker) enableAssets(ctx, execsetup.collateralEngine) + execsetup.epochEngine.NotifyOnEpoch(execsetup.collateralEngine.OnEpochEvent, execsetup.collateralEngine.OnEpochRestore) execsetup.netParams = netparams.New(execsetup.log, netparams.NewDefaultConfig(), execsetup.broker) diff --git a/core/integration/stubs/staking_account_stub.go b/core/integration/stubs/staking_account_stub.go index f5a931bafc2..21d6c084e84 100644 --- a/core/integration/stubs/staking_account_stub.go +++ b/core/integration/stubs/staking_account_stub.go @@ -19,6 +19,7 @@ import ( "context" "errors" "fmt" + "sort" "time" "code.vegaprotocol.io/vega/core/types" @@ -74,6 +75,15 @@ func NewStakingAccountStub() *StakingAccountStub { } } +func (t *StakingAccountStub) GetAllStakingParties() []string { + keys := make([]string, 0, len(t.partyToStake)) + for k := range t.partyToStake { + keys = append(keys, k) + } + sort.Strings(keys) + return keys +} + func (t *StakingAccountStub) GetAvailableBalance(party string) (*num.Uint, error) { ret, ok := t.partyToStake[party] if !ok { diff --git a/core/protocol/all_services.go b/core/protocol/all_services.go index 9450c6bc588..cc782ed5fa0 100644 --- a/core/protocol/all_services.go +++ b/core/protocol/all_services.go @@ -231,7 +231,7 @@ func newServices( svcs.eventService = subscribers.NewService(svcs.log, svcs.broker, svcs.conf.Broker.EventBusClientBufferSize) svcs.collateral = collateral.New(svcs.log, svcs.conf.Collateral, svcs.timeService, svcs.broker) - + svcs.epochService.NotifyOnEpoch(svcs.collateral.OnEpochEvent, svcs.collateral.OnEpochRestore) svcs.limits = limits.New(svcs.log, svcs.conf.Limits, svcs.timeService, svcs.broker) svcs.netParams = netparams.New(svcs.log, svcs.conf.NetworkParameters, svcs.broker) diff --git a/core/staking/accounting.go b/core/staking/accounting.go index 8566583546b..128cd6189fb 100644 --- a/core/staking/accounting.go +++ b/core/staking/accounting.go @@ -20,6 +20,7 @@ import ( "encoding/hex" "errors" "fmt" + "sort" "time" "code.vegaprotocol.io/vega/core/contracts/erc20" @@ -325,6 +326,15 @@ func (a *Accounting) getStakeAssetTotalSupply(address ethcmn.Address) (*num.Uint return totalSupply, nil } +func (a *Accounting) GetAllStakingParties() []string { + keys := make([]string, 0, len(a.accounts)) + for k := range a.accounts { + keys = append(keys, k) + } + sort.Strings(keys) + return keys +} + func (a *Accounting) GetAvailableBalance(party string) (*num.Uint, error) { acc, ok := a.accounts[party] if !ok {