Skip to content

Commit

Permalink
feat: add staking staking from collateral asset
Browse files Browse the repository at this point in the history
- staking from general account with a transfer to the locked_for_staking account
- staking from vesting and vested account, as soon as a reward is payed on the vesting account
- unstaking as soon as funds are transfered back to the general account from the locked_for_staking
- unstaking as soon as funds are transfered out of the vested account.

Signed-off-by: Jeremy Letang <[email protected]>
  • Loading branch information
jeremyletang committed Oct 18, 2024
1 parent 260cc2e commit afe6ce9
Show file tree
Hide file tree
Showing 22 changed files with 550 additions and 239 deletions.
5 changes: 3 additions & 2 deletions commands/transfer_funds.go
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,8 @@ func checkTransfer(cmd *commandspb.Transfer) (e Errors) {
}

if cmd.FromAccountType != vega.AccountType_ACCOUNT_TYPE_GENERAL &&
cmd.FromAccountType != vega.AccountType_ACCOUNT_TYPE_VESTED_REWARDS {
cmd.FromAccountType != vega.AccountType_ACCOUNT_TYPE_VESTED_REWARDS &&
cmd.FromAccountType != vega.AccountType_ACCOUNT_TYPE_LOCKED_FOR_STAKING {
errs.AddForProperty("transfer.from_account_type", ErrIsNotValid)
}

Expand Down Expand Up @@ -109,7 +110,7 @@ func checkTransfer(cmd *commandspb.Transfer) (e Errors) {
} else {
switch k := cmd.Kind.(type) {
case *commandspb.Transfer_OneOff:
if cmd.ToAccountType != vega.AccountType_ACCOUNT_TYPE_GLOBAL_REWARD && cmd.ToAccountType != vega.AccountType_ACCOUNT_TYPE_GENERAL && cmd.ToAccountType != vega.AccountType_ACCOUNT_TYPE_UNSPECIFIED && cmd.ToAccountType != vega.AccountType_ACCOUNT_TYPE_NETWORK_TREASURY && cmd.ToAccountType != vega.AccountType_ACCOUNT_TYPE_BUY_BACK_FEES {
if cmd.ToAccountType != vega.AccountType_ACCOUNT_TYPE_GLOBAL_REWARD && cmd.ToAccountType != vega.AccountType_ACCOUNT_TYPE_GENERAL && cmd.ToAccountType != vega.AccountType_ACCOUNT_TYPE_UNSPECIFIED && cmd.ToAccountType != vega.AccountType_ACCOUNT_TYPE_NETWORK_TREASURY && cmd.ToAccountType != vega.AccountType_ACCOUNT_TYPE_BUY_BACK_FEES && cmd.ToAccountType != vega.AccountType_ACCOUNT_TYPE_LOCKED_FOR_STAKING {
errs.AddForProperty("transfer.to_account_type", errors.New("account type is not valid for one off transfer"))
}
if k.OneOff.GetDeliverOn() < 0 {
Expand Down
17 changes: 16 additions & 1 deletion core/banking/engine.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ import (
"golang.org/x/exp/maps"
)

//go:generate go run github.com/golang/mock/mockgen -destination mocks/mocks.go -package mocks code.vegaprotocol.io/vega/core/banking Assets,Notary,Collateral,Witness,TimeService,EpochService,Topology,MarketActivityTracker,ERC20BridgeView,EthereumEventSource,Parties
//go:generate go run github.com/golang/mock/mockgen -destination mocks/mocks.go -package mocks code.vegaprotocol.io/vega/core/banking Assets,Notary,Collateral,Witness,TimeService,EpochService,Topology,MarketActivityTracker,ERC20BridgeView,EthereumEventSource,Parties,StakeAccounting

var (
ErrWrongAssetTypeUsedInBuiltinAssetChainEvent = errors.New("non builtin asset used for builtin asset chain event")
Expand Down Expand Up @@ -106,6 +106,11 @@ type Topology interface {
IsValidator() bool
}

// StakeAccounting ...
type StakeAccounting interface {
AddEvent(ctx context.Context, evt *types.StakeLinking)
}

type MarketActivityTracker interface {
CalculateMetricForIndividuals(ctx context.Context, ds *vega.DispatchStrategy) []*types.PartyContributionScore
CalculateMetricForTeams(ctx context.Context, ds *vega.DispatchStrategy) ([]*types.PartyContributionScore, map[string][]*types.PartyContributionScore)
Expand Down Expand Up @@ -223,6 +228,9 @@ type Engine struct {

// transient cache used to market a dispatch strategy as checked for eligibility for this round so we don't check again.
dispatchRequiredCache map[string]bool

stakingAsset string
stakeAccounting StakeAccounting
}

type withdrawalRef struct {
Expand All @@ -244,6 +252,7 @@ func New(log *logging.Logger,
secondaryBridgeView ERC20BridgeView,
ethEventSource EthereumEventSource,
parties Parties,
stakeAccounting StakeAccounting,
) (e *Engine) {
log = log.Named(namedLogger)
log.SetLevel(cfg.Level.Get())
Expand Down Expand Up @@ -290,9 +299,15 @@ func New(log *logging.Logger,
primaryBridgeView: primaryBridgeView,
secondaryBridgeView: secondaryBridgeView,
dispatchRequiredCache: map[string]bool{},
stakeAccounting: stakeAccounting,
}
}

func (e *Engine) OnStakingAsset(_ context.Context, a string) error {
e.stakingAsset = a
return nil
}

func (e *Engine) OnMaxFractionChanged(ctx context.Context, f num.Decimal) error {
e.maxGovTransferFraction = f
return nil
Expand Down
5 changes: 4 additions & 1 deletion core/banking/engine_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ type testEngine struct {
ethSource *mocks.MockEthereumEventSource
secondaryBridgeView *mocks.MockERC20BridgeView
parties *mocks.MockParties
stakeAccounting *mocks.MockStakeAccounting
}

func getTestEngine(t *testing.T) *testEngine {
Expand All @@ -73,6 +74,7 @@ func getTestEngine(t *testing.T) *testEngine {
broker := bmocks.NewMockBroker(ctrl)
top := mocks.NewMockTopology(ctrl)
epoch := mocks.NewMockEpochService(ctrl)
stakeAccounting := mocks.NewMockStakeAccounting(ctrl)
primaryBridgeView := mocks.NewMockERC20BridgeView(ctrl)
secondaryBridgeView := mocks.NewMockERC20BridgeView(ctrl)
marketActivityTracker := mocks.NewMockMarketActivityTracker(ctrl)
Expand All @@ -82,7 +84,7 @@ func getTestEngine(t *testing.T) *testEngine {
notary.EXPECT().OfferSignatures(gomock.Any(), gomock.Any()).AnyTimes()
epoch.EXPECT().NotifyOnEpoch(gomock.Any(), gomock.Any()).AnyTimes()
parties := mocks.NewMockParties(ctrl)
eng := banking.New(logging.NewTestLogger(), banking.NewDefaultConfig(), col, witness, tsvc, assets, notary, broker, top, marketActivityTracker, primaryBridgeView, secondaryBridgeView, ethSource, parties)
eng := banking.New(logging.NewTestLogger(), banking.NewDefaultConfig(), col, witness, tsvc, assets, notary, broker, top, marketActivityTracker, primaryBridgeView, secondaryBridgeView, ethSource, parties, stakeAccounting)

require.NoError(t, eng.OnMaxQuantumAmountUpdate(context.Background(), num.DecimalOne()))
eng.OnPrimaryEthChainIDUpdated("1", "hello")
Expand All @@ -103,6 +105,7 @@ func getTestEngine(t *testing.T) *testEngine {
marketActivityTracker: marketActivityTracker,
ethSource: ethSource,
parties: parties,
stakeAccounting: stakeAccounting,
}
}

Expand Down
37 changes: 36 additions & 1 deletion core/banking/mocks/mocks.go

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

62 changes: 62 additions & 0 deletions core/banking/oneoff_transfers.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ import (

"code.vegaprotocol.io/vega/core/events"
"code.vegaprotocol.io/vega/core/types"
vgcontext "code.vegaprotocol.io/vega/libs/context"
"code.vegaprotocol.io/vega/libs/crypto"
"code.vegaprotocol.io/vega/logging"
checkpoint "code.vegaprotocol.io/vega/protos/vega/checkpoint/v1"
)
Expand Down Expand Up @@ -61,6 +63,65 @@ func scheduledTransferFromProto(p *checkpoint.ScheduledTransfer) (scheduledTrans
}, nil
}

func (e *Engine) updateStakingAccounts(
ctx context.Context, transfer *types.OneOffTransfer,
) {
if transfer.Asset != e.stakingAsset {
// nothing to do
return
}

var (
now = e.timeService.GetTimeNow().Unix()
height, _ = vgcontext.BlockHeightFromContext(ctx)
txhash, _ = vgcontext.TxHashFromContext(ctx)
id = crypto.HashStrToHex(fmt.Sprintf("%v%v", txhash, height))
stakeLinking *types.StakeLinking
)

// manually send funds from the general account to the locked for staking
if transfer.FromAccountType == types.AccountTypeGeneral && transfer.ToAccountType == types.AccountTypeLockedForStaking {
stakeLinking = &types.StakeLinking{
ID: id,
Type: types.StakeLinkingTypeDeposited,
TS: now,
Party: transfer.From,
Amount: transfer.Amount.Clone(),
Status: types.StakeLinkingStatusAccepted,
FinalizedAt: now,
TxHash: txhash,
BlockHeight: height,
BlockTime: now,
LogIndex: 1,
EthereumAddress: transfer.From,
}
}

// from staking account or vested rewards, we send a remove event
if (transfer.FromAccountType == types.AccountTypeLockedForStaking && transfer.ToAccountType == types.AccountTypeGeneral) ||
(transfer.FromAccountType == types.AccountTypeVestedRewards && transfer.ToAccountType == types.AccountTypeGeneral) {
stakeLinking = &types.StakeLinking{
ID: id,
Type: types.StakeLinkingTypeRemoved,
TS: now,
Party: transfer.From,
Amount: transfer.Amount.Clone(),
Status: types.StakeLinkingStatusAccepted,
FinalizedAt: now,
TxHash: txhash,
BlockHeight: height,
BlockTime: now,
LogIndex: 1,
EthereumAddress: transfer.From,
}
}

if stakeLinking != nil {
e.stakeAccounting.AddEvent(ctx, stakeLinking)
e.broker.Send(events.NewStakeLinking(ctx, *stakeLinking))
}
}

func (e *Engine) oneOffTransfer(
ctx context.Context,
transfer *types.OneOffTransfer,
Expand All @@ -70,6 +131,7 @@ func (e *Engine) oneOffTransfer(
e.broker.Send(events.NewOneOffTransferFundsEventWithReason(ctx, transfer, err.Error()))
} else {
e.broker.Send(events.NewOneOffTransferFundsEvent(ctx, transfer))
e.updateStakingAccounts(ctx, transfer)
}
}()

Expand Down
12 changes: 12 additions & 0 deletions core/collateral/engine.go
Original file line number Diff line number Diff line change
Expand Up @@ -356,6 +356,18 @@ func (e *Engine) GetAllVestingQuantumBalance(party string) num.Decimal {
return balance
}

func (e *Engine) GetAllVestingAndVestedAccountForAsset(asset string) []*types.Account {
accs := []*types.Account{}

for _, v := range e.hashableAccs {
if v.Asset == asset && (v.Type == types.AccountTypeVestingRewards || v.Type == types.AccountTypeVestedRewards) {
accs = append(accs, v.Clone())
}
}

return accs
}

func (e *Engine) GetVestingRecovery() map[string]map[string]*num.Uint {
out := e.vesting
e.vesting = map[string]map[string]*num.Uint{}
Expand Down
4 changes: 2 additions & 2 deletions core/integration/setup_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -202,7 +202,7 @@ func newExecutionTestSetup() *executionTestSetup {
execsetup.epochEngine.NotifyOnEpoch(execsetup.volumeDiscountProgram.OnEpoch, execsetup.volumeDiscountProgram.OnEpochRestore)
execsetup.volumeRebateProgram = volumerebate.New(execsetup.broker, execsetup.marketActivityTracker)

execsetup.banking = banking.New(execsetup.log, banking.NewDefaultConfig(), execsetup.collateralEngine, execsetup.witness, execsetup.timeService, execsetup.assetsEngine, execsetup.notary, execsetup.broker, execsetup.topology, execsetup.marketActivityTracker, stubs.NewBridgeViewStub(), stubs.NewBridgeViewStub(), eventHeartbeat, execsetup.profilesEngine)
execsetup.banking = banking.New(execsetup.log, banking.NewDefaultConfig(), execsetup.collateralEngine, execsetup.witness, execsetup.timeService, execsetup.assetsEngine, execsetup.notary, execsetup.broker, execsetup.topology, execsetup.marketActivityTracker, stubs.NewBridgeViewStub(), stubs.NewBridgeViewStub(), eventHeartbeat, execsetup.profilesEngine, execsetup.stakingAccount)

execsetup.executionEngine = newExEng(
execution.NewEngine(
Expand Down Expand Up @@ -233,7 +233,7 @@ func newExecutionTestSetup() *executionTestSetup {
execsetup.activityStreak = activitystreak.New(execsetup.log, execsetup.executionEngine, execsetup.broker)
execsetup.epochEngine.NotifyOnEpoch(execsetup.activityStreak.OnEpochEvent, execsetup.activityStreak.OnEpochRestore)

execsetup.vesting = vesting.New(execsetup.log, execsetup.collateralEngine, execsetup.activityStreak, execsetup.broker, execsetup.assetsEngine, execsetup.profilesEngine)
execsetup.vesting = vesting.New(execsetup.log, execsetup.collateralEngine, execsetup.activityStreak, execsetup.broker, execsetup.assetsEngine, execsetup.profilesEngine, execsetup.timeService)
execsetup.rewardsEngine = rewards.New(execsetup.log, rewards.NewDefaultConfig(), execsetup.broker, execsetup.delegationEngine, execsetup.epochEngine, execsetup.collateralEngine, execsetup.timeService, execsetup.marketActivityTracker, execsetup.topology, execsetup.vesting, execsetup.banking, execsetup.activityStreak)

// register this after the rewards engine is created to make sure the on epoch is called in the right order.
Expand Down
2 changes: 2 additions & 0 deletions core/integration/stubs/staking_account_stub.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ type StakingAccountStub struct {
currentEpoch *types.Epoch
}

func (t *StakingAccountStub) AddEvent(ctx context.Context, evt *types.StakeLinking) {}

func (t *StakingAccountStub) OnEpochEvent(ctx context.Context, epoch types.Epoch) {
if t.currentEpoch == nil || t.currentEpoch.Seq != epoch.Seq {
t.currentEpoch = &epoch
Expand Down
12 changes: 10 additions & 2 deletions core/protocol/all_services.go
Original file line number Diff line number Diff line change
Expand Up @@ -340,7 +340,7 @@ func newServices(
svcs.volumeRebate = volumerebate.NewSnapshottedEngine(svcs.broker, svcs.marketActivityTracker)
svcs.banking = banking.New(svcs.log, svcs.conf.Banking, svcs.collateral, svcs.witness, svcs.timeService,
svcs.assets, svcs.notary, svcs.broker, svcs.topology, svcs.marketActivityTracker, svcs.primaryBridgeView,
svcs.secondaryBridgeView, svcs.forwarderHeartbeat, svcs.partiesEngine)
svcs.secondaryBridgeView, svcs.forwarderHeartbeat, svcs.partiesEngine, svcs.stakingAccounts)

// instantiate the execution engine
svcs.executionEngine = execution.NewEngine(
Expand Down Expand Up @@ -392,7 +392,7 @@ func newServices(
svcs.activityStreak.OnEpochRestore,
)

svcs.vesting = vesting.NewSnapshotEngine(svcs.log, svcs.collateral, svcs.activityStreak, svcs.broker, svcs.assets, svcs.partiesEngine)
svcs.vesting = vesting.NewSnapshotEngine(svcs.log, svcs.collateral, svcs.activityStreak, svcs.broker, svcs.assets, svcs.partiesEngine, svcs.timeService)
svcs.timeService.NotifyOnTick(svcs.vesting.OnTick)
svcs.rewards = rewards.New(svcs.log, svcs.conf.Rewards, svcs.broker, svcs.delegation, svcs.epochService, svcs.collateral, svcs.timeService, svcs.marketActivityTracker, svcs.topology, svcs.vesting, svcs.banking, svcs.activityStreak)

Expand Down Expand Up @@ -673,6 +673,14 @@ func (svcs *allServices) setupNetParameters(powWatchers []netparams.WatchParam)
}

watchers := []netparams.WatchParam{
{
Param: netparams.RewardAsset,
Watcher: svcs.banking.OnStakingAsset,
},
{
Param: netparams.RewardAsset,
Watcher: svcs.vesting.OnStakingAssetUpdate,
},
{
Param: netparams.SpamProtectionBalanceSnapshotFrequency,
Watcher: svcs.collateral.OnBalanceSnapshotFrequencyUpdated,
Expand Down
4 changes: 2 additions & 2 deletions core/rewards/engine.go
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ type Teams interface {
}

type Vesting interface {
AddReward(party, asset string, amount *num.Uint, lockedForEpochs uint64)
AddReward(ctx context.Context, party, asset string, amount *num.Uint, lockedForEpochs uint64)
GetSingleAndSummedRewardBonusMultipliers(party string) (vesting.MultiplierAndQuantBalance, vesting.MultiplierAndQuantBalance)
}

Expand Down Expand Up @@ -492,7 +492,7 @@ func (e *Engine) distributePayout(ctx context.Context, po *payout) {
if po.rewardType != types.AccountTypeFeesInfrastructure {
for _, party := range partyIDs {
amt := po.partyToAmount[party]
e.vesting.AddReward(party, po.asset, amt, po.lockedForEpochs)
e.vesting.AddReward(ctx, party, po.asset, amt, po.lockedForEpochs)
}
}
e.broker.Send(events.NewLedgerMovements(ctx, responses))
Expand Down
2 changes: 1 addition & 1 deletion core/rewards/engine_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -450,7 +450,7 @@ func getEngine(t *testing.T) *testEngine {
topology := mocks.NewMockTopology(ctrl)
marketActivityTracker := mocks.NewMockMarketActivityTracker(ctrl)
vesting := mocks.NewMockVesting(ctrl)
vesting.EXPECT().AddReward(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes()
vesting.EXPECT().AddReward(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes()
transfers := mocks.NewMockTransfers(ctrl)
activityStreak := mocks.NewMockActivityStreak(ctrl)
engine := New(logger, conf, broker, delegation, epochEngine, collateralEng, ts, marketActivityTracker, topology, vesting, transfers, activityStreak)
Expand Down
8 changes: 4 additions & 4 deletions core/rewards/mocks/mocks.go

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

1 change: 1 addition & 0 deletions core/types/collateral.go
Original file line number Diff line number Diff line change
Expand Up @@ -325,4 +325,5 @@ const (
AccountTypeBuyBackFees AccountType = proto.AccountType_ACCOUNT_TYPE_BUY_BACK_FEES
// Account for eligible entities rewards.
AccountTypeEligibleEntitiesReward = proto.AccountType_ACCOUNT_TYPE_REWARD_ELIGIBLE_ENTITIES
AccountTypeLockedForStaking = proto.AccountType_ACCOUNT_TYPE_LOCKED_FOR_STAKING
)
Loading

0 comments on commit afe6ce9

Please sign in to comment.