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: automated purchase auctions #11686

Merged
merged 1 commit into from
Sep 27, 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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
- [11644](https://github.com/vegaprotocol/vega/issues/11644) - `liveOnly` flag has been added to the `AMM` API to show only active `AMMs`.
- [11519](https://github.com/vegaprotocol/vega/issues/11519) - Add fees to position API types.
- [11642](https://github.com/vegaprotocol/vega/issues/11642) - `AMMs` with empty price levels are now allowed.
- [11685](https://github.com/vegaprotocol/vega/issues/11685) - Automated purchase support added.
- [11711](https://github.com/vegaprotocol/vega/issues/11711) - Manage closed team membership by updating the allow list.

### 🐛 Fixes
Expand Down
450 changes: 287 additions & 163 deletions commands/proposal_submission.go

Large diffs are not rendered by default.

588 changes: 588 additions & 0 deletions commands/proposal_submission_pap_test.go

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion core/collateral/checkpoint_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -252,7 +252,7 @@ func TestCheckPointWithUndistributedLPFees(t *testing.T) {
},
Type: types.TransferTypeLiquidityFeeUnpaidCollect,
}
_, err = e.TransferSpotFees(context.Background(), "market1", "MYASSET1", &feesTransfer{transfers: []*types.Transfer{lpSpotTransfers}})
_, err = e.TransferSpotFees(context.Background(), "market1", "MYASSET1", &feesTransfer{transfers: []*types.Transfer{lpSpotTransfers}}, types.AccountTypeGeneral)
require.NoError(t, err)

// setup some balance on the LP fee pay account for MYASSET1/market2
Expand Down
175 changes: 142 additions & 33 deletions core/collateral/engine.go
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,8 @@ type Engine struct {
timeService TimeService
broker Broker

earmarkedBalance map[string]*num.Uint

partiesAccsBalanceCache map[string]*num.Uint
partiesAccsBalanceCacheLock sync.RWMutex

Expand Down Expand Up @@ -151,6 +153,7 @@ func New(log *logging.Logger, conf Config, ts TimeService, broker Broker) *Engin
nextBalancesSnapshot: time.Time{},
partyAssetCache: map[string]map[string]*num.Uint{},
partyMarketCache: map[string]map[string]*num.Uint{},
earmarkedBalance: map[string]*num.Uint{},
}
}

Expand Down Expand Up @@ -731,14 +734,14 @@ func (e *Engine) TransferSpotFeesContinuousTrading(ctx context.Context, marketID
}
}

return e.transferSpotFees(ctx, marketID, assetID, ft)
return e.transferSpotFees(ctx, marketID, assetID, ft, types.AccountTypeGeneral)
}

func (e *Engine) TransferSpotFees(ctx context.Context, marketID string, assetID string, ft events.FeesTransfer) ([]*types.LedgerMovement, error) {
return e.transferSpotFees(ctx, marketID, assetID, ft)
func (e *Engine) TransferSpotFees(ctx context.Context, marketID string, assetID string, ft events.FeesTransfer, fromAccountType types.AccountType) ([]*types.LedgerMovement, error) {
return e.transferSpotFees(ctx, marketID, assetID, ft, fromAccountType)
}

func (e *Engine) transferSpotFees(ctx context.Context, marketID string, assetID string, ft events.FeesTransfer) ([]*types.LedgerMovement, error) {
func (e *Engine) transferSpotFees(ctx context.Context, marketID string, assetID string, ft events.FeesTransfer, fromAccountType types.AccountType) ([]*types.LedgerMovement, error) {
makerFee, infraFee, liquiFee, err := e.getFeesAccounts(marketID, assetID)
if err != nil {
return nil, err
Expand All @@ -749,7 +752,7 @@ func (e *Engine) transferSpotFees(ctx context.Context, marketID string, assetID

for _, transfer := range transfers {
req, err := e.getSpotFeeTransferRequest(
ctx, transfer, makerFee, infraFee, liquiFee, marketID, assetID)
ctx, transfer, makerFee, infraFee, liquiFee, marketID, assetID, fromAccountType)
if err != nil {
e.log.Error("Failed to build transfer request for event",
logging.Error(err))
Expand Down Expand Up @@ -784,6 +787,7 @@ func (e *Engine) getSpotFeeTransferRequest(
t *types.Transfer,
makerFee, infraFee, liquiFee *types.Account,
marketID, assetID string,
sourceAccountType types.AccountType,
) (*types.TransferRequest, error) {
getAccount := func(marketID, owner string, accountType vega.AccountType) (*types.Account, error) {
acc, err := e.GetAccountByID(e.accountID(marketID, owner, assetID, accountType))
Expand All @@ -808,7 +812,13 @@ func (e *Engine) getSpotFeeTransferRequest(
return getAccount(marketID, systemOwner, types.AccountTypeLiquidityFeesBonusDistribution)
}

general, err := getAccount(noMarket, t.Owner, types.AccountTypeGeneral)
accTypeForGeneral := types.AccountTypeGeneral
owner := t.Owner
if t.Owner == types.NetworkParty {
owner = systemOwner
accTypeForGeneral = sourceAccountType
}
general, err := getAccount(noMarket, owner, accTypeForGeneral)
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -2736,7 +2746,7 @@ func (e *Engine) getGovernanceTransferFundsTransferRequest(ctx context.Context,
case types.AccountTypeGlobalReward, types.AccountTypeLPFeeReward, types.AccountTypeMakerReceivedFeeReward,
types.AccountTypeMakerPaidFeeReward, types.AccountTypeMarketProposerReward, types.AccountTypeAverageNotionalReward,
types.AccountTypeRelativeReturnReward, types.AccountTypeReturnVolatilityReward, types.AccountTypeRealisedReturnReward,
types.AccountTypeValidatorRankingReward, types.AccountTypeEligibleEntitiesReward:
types.AccountTypeValidatorRankingReward, types.AccountTypeEligibleEntitiesReward, types.AccountTypeBuyBackFees:
market := noMarket
if len(t.Market) > 0 {
market = t.Market
Expand Down Expand Up @@ -2843,7 +2853,7 @@ func (e *Engine) getTransferFundsTransferRequest(ctx context.Context, t *types.T
case types.AccountTypeGlobalReward, types.AccountTypeLPFeeReward, types.AccountTypeMakerReceivedFeeReward, types.AccountTypeNetworkTreasury,
types.AccountTypeMakerPaidFeeReward, types.AccountTypeMarketProposerReward, types.AccountTypeAverageNotionalReward,
types.AccountTypeRelativeReturnReward, types.AccountTypeReturnVolatilityReward, types.AccountTypeRealisedReturnReward,
types.AccountTypeValidatorRankingReward, types.AccountTypeEligibleEntitiesReward:
types.AccountTypeValidatorRankingReward, types.AccountTypeEligibleEntitiesReward, types.AccountTypeBuyBackFees:
market := noMarket
if len(t.Market) > 0 {
market = t.Market
Expand Down Expand Up @@ -4688,6 +4698,10 @@ func (e *Engine) GetNetworkTreasuryAccount(asset string) (*types.Account, error)
return e.GetAccountByID(e.accountID(noMarket, systemOwner, asset, types.AccountTypeNetworkTreasury))
}

func (e *Engine) GetOrCreateBuyBackFeesAccountID(ctx context.Context, asset string) string {
return e.getOrCreateBuyBackFeesAccount(ctx, asset).ID
}

func (e *Engine) getOrCreateBuyBackFeesAccount(ctx context.Context, asset string) *types.Account {
accID := e.accountID(noMarket, systemOwner, asset, types.AccountTypeBuyBackFees)
acc, err := e.GetAccountByID(accID)
Expand Down Expand Up @@ -4728,6 +4742,26 @@ func (e *Engine) GetOrCreateNetworkTreasuryAccount(ctx context.Context, asset st
return ntAcc
}

func (e *Engine) getOrCreateNetworkAccount(ctx context.Context, asset string, accountType types.AccountType) *types.Account {
accID := e.accountID(noMarket, systemOwner, asset, accountType)
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: accountType,
}
e.accs[accID] = ntAcc
e.addAccountToHashableSlice(ntAcc)
e.broker.Send(events.NewAccountEvent(ctx, *ntAcc))
return ntAcc
}

func (e *Engine) GetGlobalInsuranceAccount(asset string) (*types.Account, error) {
return e.GetAccountByID(e.accountID(noMarket, systemOwner, asset, types.AccountTypeGlobalInsurance))
}
Expand Down Expand Up @@ -4801,19 +4835,23 @@ func (e *Engine) GetSystemAccountBalance(asset, market string, accountType types
return account.Balance.Clone(), nil
}

// TransferToHoldingAccount locks funds from general account into holding account account of the party.
func (e *Engine) TransferToHoldingAccount(ctx context.Context, transfer *types.Transfer) (*types.LedgerMovement, error) {
generalAccountID := e.accountID(transfer.Market, transfer.Owner, transfer.Amount.Asset, types.AccountTypeGeneral)
generalAccount, err := e.GetAccountByID(generalAccountID)
// TransferToHoldingAccount locks funds from accountTypeFrom into holding account account of the party.
func (e *Engine) TransferToHoldingAccount(ctx context.Context, transfer *types.Transfer, accountTypeFrom types.AccountType) (*types.LedgerMovement, error) {
party := transfer.Owner
if party == types.NetworkParty {
party = systemOwner
}
sourceAccountID := e.accountID(transfer.Market, party, transfer.Amount.Asset, accountTypeFrom)
sourceAccount, err := e.GetAccountByID(sourceAccountID)
if err != nil {
return nil, err
}

holdingAccountID := e.accountID(noMarket, transfer.Owner, transfer.Amount.Asset, types.AccountTypeHolding)
holdingAccountID := e.accountID(noMarket, party, transfer.Amount.Asset, types.AccountTypeHolding)
holdingAccount, err := e.GetAccountByID(holdingAccountID)
if err != nil {
// if the holding account doesn't exist yet we create it here
holdingAccountID, err := e.CreatePartyHoldingAccount(ctx, transfer.Owner, transfer.Amount.Asset)
holdingAccountID, err := e.CreatePartyHoldingAccount(ctx, party, transfer.Amount.Asset)
if err != nil {
return nil, err
}
Expand All @@ -4825,7 +4863,7 @@ func (e *Engine) TransferToHoldingAccount(ctx context.Context, transfer *types.T
MinAmount: transfer.Amount.Amount.Clone(),
Asset: transfer.Amount.Asset,
Type: types.TransferTypeHoldingAccount,
FromAccount: []*types.Account{generalAccount},
FromAccount: []*types.Account{sourceAccount},
ToAccount: []*types.Account{holdingAccount},
}

Expand All @@ -4845,15 +4883,19 @@ func (e *Engine) TransferToHoldingAccount(ctx context.Context, transfer *types.T
return res, nil
}

// ReleaseFromHoldingAccount releases locked funds from holding account back to the general account of the party.
func (e *Engine) ReleaseFromHoldingAccount(ctx context.Context, transfer *types.Transfer) (*types.LedgerMovement, error) {
holdingAccountID := e.accountID(noMarket, transfer.Owner, transfer.Amount.Asset, types.AccountTypeHolding)
// ReleaseFromHoldingAccount releases locked funds from holding account back to the toAccount of the party.
func (e *Engine) ReleaseFromHoldingAccount(ctx context.Context, transfer *types.Transfer, toAccountType types.AccountType) (*types.LedgerMovement, error) {
party := transfer.Owner
if party == types.NetworkParty {
party = systemOwner
}
holdingAccountID := e.accountID(noMarket, party, transfer.Amount.Asset, types.AccountTypeHolding)
holdingAccount, err := e.GetAccountByID(holdingAccountID)
if err != nil {
return nil, err
}

generalAccount, err := e.GetAccountByID(e.accountID(noMarket, transfer.Owner, transfer.Amount.Asset, types.AccountTypeGeneral))
targetAccount, err := e.GetAccountByID(e.accountID(noMarket, party, transfer.Amount.Asset, toAccountType))
if err != nil {
return nil, err
}
Expand All @@ -4864,7 +4906,7 @@ func (e *Engine) ReleaseFromHoldingAccount(ctx context.Context, transfer *types.
Asset: transfer.Amount.Asset,
Type: types.TransferTypeReleaseHoldingAccount,
FromAccount: []*types.Account{holdingAccount},
ToAccount: []*types.Account{generalAccount},
ToAccount: []*types.Account{targetAccount},
}

res, err := e.getLedgerEntries(ctx, req)
Expand Down Expand Up @@ -5018,9 +5060,14 @@ func (e *Engine) CreateSpotMarketAccounts(ctx context.Context, marketID, quoteAs
return err
}

// PartyHasSufficientBalance checks if the party has sufficient amount in the general account.
func (e *Engine) PartyHasSufficientBalance(asset, partyID string, amount *num.Uint) error {
acc, err := e.GetPartyGeneralAccount(partyID, asset)
// PartyHasSufficientBalance checks if the party has sufficient amount in the <fromAccountType> account.
func (e *Engine) PartyHasSufficientBalance(asset, partyID string, amount *num.Uint, fromAccountType types.AccountType) error {
party := partyID
if party == types.NetworkParty {
party = systemOwner
}
accId := e.accountID(noMarket, party, asset, fromAccountType)
acc, err := e.GetAccountByID(accId)
if err != nil {
return err
}
Expand Down Expand Up @@ -5056,28 +5103,42 @@ func (e *Engine) CreatePartyHoldingAccount(ctx context.Context, partyID, asset s
}

// TransferSpot transfers the given asset/quantity from partyID to partyID.
// The source partyID general account must exist in the asset, the target partyID general account in the asset is created if it doesn't yet exist.
func (e *Engine) TransferSpot(ctx context.Context, partyID, toPartyID, asset string, quantity *num.Uint) (*types.LedgerMovement, error) {
generalAccountID := e.accountID(noMarket, partyID, asset, types.AccountTypeGeneral)
generalAccount, err := e.GetAccountByID(generalAccountID)
// The source partyID fromAccountType account must exist in the asset, the target partyID account in the asset is created if it doesn't yet exist.
func (e *Engine) TransferSpot(ctx context.Context, party, toParty, asset string, quantity *num.Uint, fromAccountType types.AccountType, toAccountType types.AccountType) (*types.LedgerMovement, error) {
partyID := party
if party == types.NetworkParty {
partyID = systemOwner
}

toPartyID := toParty
if toParty == types.NetworkParty {
toPartyID = systemOwner
}

fromAccountID := e.accountID(noMarket, partyID, asset, fromAccountType)
fromAccount, err := e.GetAccountByID(fromAccountID)
if err != nil {
return nil, err
}

targetGeneralAccountID := e.accountID(noMarket, toPartyID, asset, types.AccountTypeGeneral)
toGeneralAccount, err := e.GetAccountByID(targetGeneralAccountID)
toAccountID := e.accountID(noMarket, toPartyID, asset, toAccountType)
toAccount, err := e.GetAccountByID(toAccountID)
if err != nil {
targetGeneralAccountID, _ = e.CreatePartyGeneralAccount(ctx, toPartyID, asset)
toGeneralAccount, _ = e.GetAccountByID(targetGeneralAccountID)
if toAccountType == types.AccountTypeGeneral {
toAccountID, _ = e.CreatePartyGeneralAccount(ctx, toPartyID, asset)
toAccount, _ = e.GetAccountByID(toAccountID)
} else if toPartyID == types.NetworkParty {
toAccount = e.getOrCreateNetworkAccount(ctx, asset, toAccountType)
}
}

req := &types.TransferRequest{
Amount: quantity.Clone(),
MinAmount: quantity.Clone(),
Asset: asset,
Type: types.TransferTypeSpot,
FromAccount: []*types.Account{generalAccount},
ToAccount: []*types.Account{toGeneralAccount},
FromAccount: []*types.Account{fromAccount},
ToAccount: []*types.Account{toAccount},
}

res, err := e.getLedgerEntries(ctx, req)
Expand Down Expand Up @@ -5130,3 +5191,51 @@ func (e *Engine) GetVestingAccounts() []*types.Account {
})
return accs
}

func (e *Engine) EarmarkForAutomatedPurchase(asset string, accountType types.AccountType, min, max *num.Uint) (*num.Uint, error) {
id := e.accountID(noMarket, systemOwner, asset, accountType)
acc, err := e.GetAccountByID(id)
if err != nil {
return num.UintZero(), err
}
earmarked, ok := e.earmarkedBalance[id]
if !ok {
earmarked = num.UintZero()
}

balanceToEarmark := acc.Balance.Clone()
if earmarked.GT(balanceToEarmark) {
e.log.Panic("earmarked balance is greater than account balance, this should never happen")
}
balanceToEarmark = balanceToEarmark.Sub(balanceToEarmark, earmarked)

if balanceToEarmark.IsZero() || balanceToEarmark.LT(min) {
return num.UintZero(), fmt.Errorf("insufficient balance to earmark")
}
if balanceToEarmark.GT(max) {
balanceToEarmark = num.Min(balanceToEarmark, max)
}

earmarked.AddSum(balanceToEarmark)
e.earmarkedBalance[id] = earmarked
e.state.updateEarmarked(e.earmarkedBalance)
return balanceToEarmark, nil
}

func (e *Engine) UnearmarkForAutomatedPurchase(asset string, accountType types.AccountType, releaseRequest *num.Uint) error {
id := e.accountID(noMarket, systemOwner, asset, accountType)
_, err := e.GetAccountByID(id)
if err != nil {
return err
}
earmarked, ok := e.earmarkedBalance[id]
if !ok || releaseRequest.GT(earmarked) {
e.log.Panic("trying to unearmark an amount that is greater than the earmarked balance")
}
earmarked.Sub(earmarked, releaseRequest)
if earmarked.IsZero() {
delete(e.earmarkedBalance, id)
}
e.state.updateEarmarked(e.earmarkedBalance)
return nil
}
Loading
Loading