Skip to content

Commit

Permalink
Merge pull request #11510 from vegaprotocol/release/v0.77.4
Browse files Browse the repository at this point in the history
Release v0.77.4
  • Loading branch information
jeremyletang committed Jul 30, 2024
2 parents c89ec09 + 0a64717 commit 3224ba4
Show file tree
Hide file tree
Showing 16 changed files with 256 additions and 51 deletions.
9 changes: 9 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,14 @@
- [](https://github.com/vegaprotocol/vega/issues/xxx)


## 0.77.4

### 🐛 Fixes

- [11508](https://github.com/vegaprotocol/vega/issues/11508) - Handle market order for margin spam protection.
- [11507](https://github.com/vegaprotocol/vega/issues/11507) - Margin spam check to remove division by asset quantum.


## 0.77.3

### 🚨 Breaking changes
Expand Down Expand Up @@ -57,6 +65,7 @@

- [11481](https://github.com/vegaprotocol/vega/issues/11481) - Fix margin check for cross margin market orders.
- [11474](https://github.com/vegaprotocol/vega/issues/11474) - Fail `checkTx` downstream for delayed transactions so they don't get included in more than one block.
- [11506](https://github.com/vegaprotocol/vega/issues/11506) - Fix margin spam check to remove division by asset quantum.

## 0.77.1

Expand Down
34 changes: 21 additions & 13 deletions core/execution/future/market.go
Original file line number Diff line number Diff line change
Expand Up @@ -1507,21 +1507,24 @@ func (m *Market) EnterLongBlockAuction(ctx context.Context, duration int64) {
return
}

m.mkt.State = types.MarketStateSuspended
m.mkt.TradingMode = types.MarketTradingModelLongBlockAuction
if m.as.InAuction() {
effDuration := int(m.timeService.GetTimeNow().UnixNano()/1e9) + int(duration) - int(m.as.ExpiresAt().UnixNano()/1e9)
if effDuration <= 0 {
// auction remaining:
now := m.timeService.GetTimeNow()
aRemaining := int64(m.as.ExpiresAt().Sub(now) / time.Second)
if aRemaining >= duration {
return
}
m.as.ExtendAuctionLongBlock(types.AuctionDuration{Duration: int64(effDuration)})
evt := m.as.AuctionExtended(ctx, m.timeService.GetTimeNow())
if evt != nil {
m.as.ExtendAuctionLongBlock(types.AuctionDuration{
Duration: duration - aRemaining,
})
if evt := m.as.AuctionExtended(ctx, now); evt != nil {
m.broker.Send(evt)
}
} else {
m.as.StartLongBlockAuction(m.timeService.GetTimeNow(), duration)
m.tradableInstrument.Instrument.UpdateAuctionState(ctx, true)
m.mkt.TradingMode = types.MarketTradingModelLongBlockAuction
m.mkt.State = types.MarketStateSuspended
m.enterAuction(ctx)
m.broker.Send(events.NewMarketUpdatedEvent(ctx, *m.mkt))
}
Expand Down Expand Up @@ -1707,6 +1710,7 @@ func (m *Market) leaveAuction(ctx context.Context, now time.Time) {
}

m.mkt.State = types.MarketStateActive
// this probably should get the default trading mode from the market definition.
m.mkt.TradingMode = types.MarketTradingModeContinuous
m.broker.Send(events.NewMarketUpdatedEvent(ctx, *m.mkt))

Expand Down Expand Up @@ -5324,16 +5328,20 @@ func (m *Market) CheckOrderSubmissionForSpam(orderSubmission *types.OrderSubmiss
}

var price *num.Uint
if orderSubmission.PeggedOrder == nil {
price, _ = num.UintFromDecimal(orderSubmission.Price.ToDecimal().Mul(m.priceFactor))
} else {
if orderSubmission.PeggedOrder != nil || orderSubmission.Type == vega.Order_TYPE_MARKET {
priceInMarket, _ := num.UintFromDecimal(m.getCurrentMarkPrice().ToDecimal().Div(m.priceFactor))
offset := num.UintZero()
if orderSubmission.PeggedOrder != nil {
offset = orderSubmission.PeggedOrder.Offset
}
if orderSubmission.Side == types.SideBuy {
priceInMarket.AddSum(orderSubmission.PeggedOrder.Offset)
priceInMarket.AddSum(offset)
} else {
priceInMarket = priceInMarket.Sub(priceInMarket, orderSubmission.PeggedOrder.Offset)
priceInMarket = priceInMarket.Sub(priceInMarket, offset)
}
price, _ = num.UintFromDecimal(priceInMarket.ToDecimal().Mul(m.priceFactor))
} else {
price, _ = num.UintFromDecimal(orderSubmission.Price.ToDecimal().Mul(m.priceFactor))
}

margins := num.UintZero().Mul(price, num.NewUint(orderSubmission.Size)).ToDecimal().Div(m.positionFactor)
Expand All @@ -5342,7 +5350,7 @@ func (m *Market) CheckOrderSubmissionForSpam(orderSubmission *types.OrderSubmiss
if err != nil {
return err
}
if margins.Mul(rf.Add(factor)).Div(assetQuantum).LessThan(quantumMultiplier.Mul(assetQuantum)) {
if margins.Mul(rf.Add(factor)).LessThan(quantumMultiplier.Mul(assetQuantum)) {
return fmt.Errorf("order value is less than minimum maintenance margin for spam")
}
return nil
Expand Down
32 changes: 19 additions & 13 deletions core/execution/spot/market.go
Original file line number Diff line number Diff line change
Expand Up @@ -497,20 +497,22 @@ func (m *Market) EnterLongBlockAuction(ctx context.Context, duration int64) {
return
}

m.mkt.State = types.MarketStateSuspended
m.mkt.TradingMode = types.MarketTradingModelLongBlockAuction
if m.as.InAuction() {
effDuration := int(m.timeService.GetTimeNow().UnixNano()/1e9) + int(duration) - int(m.as.ExpiresAt().UnixNano()/1e9)
if effDuration <= 0 {
now := m.timeService.GetTimeNow()
aRemaining := int64(m.as.ExpiresAt().Sub(now) / time.Second)
if aRemaining >= duration {
return
}
m.as.ExtendAuctionLongBlock(types.AuctionDuration{Duration: int64(effDuration)})
evt := m.as.AuctionExtended(ctx, m.timeService.GetTimeNow())
if evt != nil {
m.as.ExtendAuctionLongBlock(types.AuctionDuration{
Duration: duration - aRemaining,
})
if evt := m.as.AuctionExtended(ctx, now); evt != nil {
m.broker.Send(evt)
}
} else {
m.as.StartLongBlockAuction(m.timeService.GetTimeNow(), duration)
m.mkt.TradingMode = types.MarketTradingModelLongBlockAuction
m.mkt.State = types.MarketStateSuspended
m.enterAuction(ctx)
m.broker.Send(events.NewMarketUpdatedEvent(ctx, *m.mkt))
}
Expand Down Expand Up @@ -3356,21 +3358,25 @@ func (m *Market) CheckOrderSubmissionForSpam(orderSubmission *types.OrderSubmiss
}

var price *num.Uint
if orderSubmission.PeggedOrder == nil {
price, _ = num.UintFromDecimal(orderSubmission.Price.ToDecimal().Mul(m.priceFactor))
} else {
if orderSubmission.PeggedOrder != nil || orderSubmission.Type == vega.Order_TYPE_MARKET {
priceInMarket, _ := num.UintFromDecimal(m.getCurrentMarkPrice().ToDecimal().Div(m.priceFactor))
offset := num.UintZero()
if orderSubmission.PeggedOrder != nil {
offset = orderSubmission.PeggedOrder.Offset
}
if orderSubmission.Side == types.SideBuy {
priceInMarket.AddSum(orderSubmission.PeggedOrder.Offset)
priceInMarket.AddSum(offset)
} else {
priceInMarket = priceInMarket.Sub(priceInMarket, orderSubmission.PeggedOrder.Offset)
priceInMarket = priceInMarket.Sub(priceInMarket, offset)
}
price, _ = num.UintFromDecimal(priceInMarket.ToDecimal().Mul(m.priceFactor))
} else {
price, _ = num.UintFromDecimal(orderSubmission.Price.ToDecimal().Mul(m.priceFactor))
}

minQuantum := assetQuantum.Mul(quantumMultiplier)
value := num.UintZero().Mul(num.NewUint(orderSubmission.Size), price).ToDecimal()
value = value.Div(m.positionFactor).Div(assetQuantum)
value = value.Div(m.positionFactor)
if value.LessThan(minQuantum.Mul(assetQuantum)) {
return fmt.Errorf("order value is less than minimum holding requirement for spam")
}
Expand Down
17 changes: 12 additions & 5 deletions core/integration/features/auctions/0094-PRAC-008.feature
Original file line number Diff line number Diff line change
Expand Up @@ -131,20 +131,27 @@ Feature: When a market's trigger and extension_trigger are set to represent that
| 1000000 | TRADING_MODE_MONITORING_AUCTION | 2810994378 | 1873996252 | 1 | 602 | AUCTION_TRIGGER_LONG_BLOCK |

# move ahead another minute
When the network moves ahead "60" blocks
When the network moves ahead "1" blocks
# This is strange, it looks as though the trade went through at the end of the auction, but in doing so triggered a second auction?
Then the market data for the market "ETH/DEC20" should be:
| mark price | trading mode | target stake | supplied stake | open interest | extension trigger |
| 1000000 | TRADING_MODE_MONITORING_AUCTION | 2810994378 | 1873996252 | 1 | AUCTION_TRIGGER_LONG_BLOCK |

When the network moves ahead "8m50s" with block duration of "2s"
When the network moves ahead "9m50s" with block duration of "2s"
Then the trading mode should be "TRADING_MODE_MONITORING_AUCTION" for the market "ETH/DEC20"
And the trading mode should be "TRADING_MODE_LONG_BLOCK_AUCTION" for the market "ETH/DEC19"

# still in auction, but if we move ahead...
When the network moves ahead "150" blocks
And the trading mode should be "TRADING_MODE_CONTINUOUS" for the market "ETH/DEC19"
Then the trading mode should be "TRADING_MODE_CONTINUOUS" for the market "ETH/DEC20"
When the network moves ahead "11" blocks
Then the trading mode should be "TRADING_MODE_CONTINUOUS" for the market "ETH/DEC19"
And the trading mode should be "TRADING_MODE_MONITORING_AUCTION" for the market "ETH/DEC20"
# now move further ahead to leave price auction
# We have moved 1 blocks + 9m50s (9m51) + 11 blocks for a total of 10m2s, the total auction duration
# was 602s, or 10m2s. Leaving the auction will trigger an extension of another 61 seconds
# So the total time in auction would be 11m2s. At this point we're still 61s short.
When the network moves ahead "61" blocks
Then the trading mode should be "TRADING_MODE_CONTINUOUS" for the market "ETH/DEC19"
And the trading mode should be "TRADING_MODE_CONTINUOUS" for the market "ETH/DEC20"
And the following trades should be executed:
| buyer | price | size | seller |
| party5 | 999998 | 1 | party6 |
Expand Down
56 changes: 56 additions & 0 deletions datanode/service/market_depth.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,62 @@ type ammCache struct {
ammOrders map[string][]*types.Order // map amm id -> expanded orders, so we can remove them if amended
activeAMMs map[string]entities.AMMPool // map amm id -> amm definition, so we can refresh its expansion
estimatedOrder map[string]struct{} // order-id -> whether it was an estimated order

// the lowest/highest bounds of all AMMs
lowestBound num.Decimal
highestBound num.Decimal

// reference -> calculation levels, if the reference point hasn't changed we can avoid the busy task
// of recalculating them
levels map[string][]*level
}

func (c *ammCache) addAMM(a entities.AMMPool) {
c.activeAMMs[a.AmmPartyID.String()] = a

low := a.ParametersBase
if a.ParametersLowerBound != nil {
low = *a.ParametersLowerBound
}

if c.lowestBound.IsZero() {
c.lowestBound = low
} else {
c.lowestBound = num.MinD(c.lowestBound, low)
}

high := a.ParametersBase
if a.ParametersUpperBound != nil {
high = *a.ParametersUpperBound
}
c.highestBound = num.MaxD(c.highestBound, high)
}

func (c *ammCache) removeAMM(ammParty string) {
delete(c.activeAMMs, ammParty)
delete(c.ammOrders, ammParty)

// now we need to recalculate the lowest/highest

c.lowestBound = num.DecimalZero()
c.highestBound = num.DecimalZero()
for _, a := range c.activeAMMs {
low := a.ParametersBase
if a.ParametersLowerBound != nil {
low = *a.ParametersLowerBound
}
if c.lowestBound.IsZero() {
c.lowestBound = low
} else {
c.lowestBound = num.MinD(c.lowestBound, low)
}

high := a.ParametersBase
if a.ParametersUpperBound != nil {
high = *a.ParametersUpperBound
}
c.lowestBound = num.MaxD(c.highestBound, high)
}
}

type MarketDepth struct {
Expand Down
61 changes: 52 additions & 9 deletions datanode/service/market_depth_amm.go
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,14 @@ func (m *MarketDepth) getActiveAMMs(ctx context.Context) map[string][]entities.A
return ammByMarket
}

func (m *MarketDepth) getCalculationBounds(reference num.Decimal, priceFactor num.Decimal) []*level {
func (m *MarketDepth) getCalculationBounds(cache *ammCache, reference num.Decimal, priceFactor num.Decimal) []*level {
if levels, ok := cache.levels[reference.String()]; ok {
return levels
}

lowestBound := cache.lowestBound
highestBound := cache.highestBound

// first lets calculate the region we will expand accurately, this will be some percentage either side of the reference price
factor := num.DecimalFromFloat(m.cfg.AmmFullExpansionPercentage).Div(hundred)

Expand Down Expand Up @@ -138,6 +145,34 @@ func (m *MarketDepth) getCalculationBounds(reference num.Decimal, priceFactor nu
estLow := num.UintZero().Sub(accLow, num.Min(accLow, eRange))
estHigh := num.UintZero().Add(accHigh, eRange)

// cap steps to the lowest/highest boundaries of all AMMs
lowD, _ := num.UintFromDecimal(lowestBound)
if accLow.LTE(lowD) {
accLow = lowD.Clone()
estLow = lowD.Clone()
}

highD, _ := num.UintFromDecimal(highestBound)
if accHigh.GTE(highD) {
accHigh = highD.Clone()
estHigh = highD.Clone()
}

// need to find the first n such that
// accLow - (n * eStep) < lowD
// accLow
if estLow.LT(lowD) {
delta, _ := num.UintZero().Delta(accLow, lowD)
delta.Div(delta, eStep)
estLow = num.UintZero().Sub(accLow, delta.Mul(delta, eStep))
}

if estHigh.GT(highD) {
delta, _ := num.UintZero().Delta(accHigh, highD)
delta.Div(delta, eStep)
estHigh = num.UintZero().Add(accHigh, delta.Mul(delta, eStep))
}

levels := []*level{}

// we now have our four prices [estLow, accLow, accHigh, estHigh] where from
Expand All @@ -164,6 +199,10 @@ func (m *MarketDepth) getCalculationBounds(reference num.Decimal, priceFactor nu
price = num.UintZero().Add(price, eStep)
}

cache.levels = map[string][]*level{
reference.String(): levels,
}

return levels
}

Expand Down Expand Up @@ -268,7 +307,6 @@ func (m *MarketDepth) expandByLevels(pool entities.AMMPool, levels []*level, pri
v1 = ammDefn.position
}
}

// calculate the volume
volume := v1.Sub(v2).Abs().IntPart()

Expand Down Expand Up @@ -303,15 +341,15 @@ func (m *MarketDepth) InitialiseAMMs(ctx context.Context) {

// add it to our active list, we want to do this even if we fail to get a reference
for _, a := range amms {
cache.activeAMMs[a.AmmPartyID.String()] = a
cache.addAMM(a)
}

reference, err := m.getReference(ctx, marketID)
if err != nil {
continue
}

levels := m.getCalculationBounds(reference, priceFactor)
levels := m.getCalculationBounds(cache, reference, priceFactor)

for _, amm := range amms {
orders, estimated, err := m.expandByLevels(amm, levels, priceFactor)
Expand Down Expand Up @@ -345,7 +383,12 @@ func (m *MarketDepth) ExpandAMM(ctx context.Context, pool entities.AMMPool, pric
return nil, nil, err
}

levels := m.getCalculationBounds(reference, priceFactor)
cache, err := m.getAMMCache(string(pool.MarketID))
if err != nil {
return nil, nil, err
}

levels := m.getCalculationBounds(cache, reference, priceFactor)

return m.expandByLevels(pool, levels, priceFactor)
}
Expand Down Expand Up @@ -391,11 +434,12 @@ func (m *MarketDepth) refreshAMM(pool entities.AMMPool, depth *entities.MarketDe
}

if pool.Status == entities.AMMStatusCancelled || pool.Status == entities.AMMStatusStopped {
delete(cache.activeAMMs, ammParty)
delete(cache.ammOrders, ammParty)
cache.removeAMM(ammParty)
return
}

cache.addAMM(pool)

// expand it again into new orders and push them into the market depth
orders, estimated, _ := m.ExpandAMM(context.Background(), pool, cache.priceFactor)
for i := range orders {
Expand All @@ -404,9 +448,7 @@ func (m *MarketDepth) refreshAMM(pool entities.AMMPool, depth *entities.MarketDe
cache.estimatedOrder[orders[i].ID] = struct{}{}
}
}

cache.ammOrders[ammParty] = orders
cache.activeAMMs[ammParty] = pool
}

// refreshAMM is used when an AMM has either traded or its definition has changed.
Expand Down Expand Up @@ -484,6 +526,7 @@ func (m *MarketDepth) getAMMCache(marketID string) (*ammCache, error) {
ammOrders: map[string][]*types.Order{},
activeAMMs: map[string]entities.AMMPool{},
estimatedOrder: map[string]struct{}{},
levels: map[string][]*level{},
}
m.ammCache[marketID] = cache

Expand Down
Loading

0 comments on commit 3224ba4

Please sign in to comment.