Skip to content

Commit

Permalink
Merge branch 'develop' into bb_auctions
Browse files Browse the repository at this point in the history
  • Loading branch information
jeremyletang authored Sep 4, 2024
2 parents dc74784 + 1db541f commit 9d1258c
Show file tree
Hide file tree
Showing 7 changed files with 335 additions and 6 deletions.
14 changes: 12 additions & 2 deletions core/execution/amm/engine.go
Original file line number Diff line number Diff line change
Expand Up @@ -347,8 +347,18 @@ func (e *Engine) GetVolumeAtPrice(price *num.Uint, side types.Side) uint64 {
vol := uint64(0)
for _, pool := range e.poolsCpy {
// get the pool's current price
fp := pool.BestPrice(nil)
volume := pool.TradableVolumeInRange(side, fp, price)
best := pool.BestPrice(&types.Order{Price: price, Side: side})

// make sure price is in tradable range
if side == types.SideBuy && best.GT(price) {
continue
}

if side == types.SideSell && best.LT(price) {
continue
}

volume := pool.TradableVolumeForPrice(side, price)
vol += volume
}
return vol
Expand Down
37 changes: 36 additions & 1 deletion core/execution/amm/engine_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ func TestAmendAMM(t *testing.T) {
t.Run("test amend AMM which doesn't exist", testAmendAMMWhichDoesntExist)
t.Run("test amend AMM with sparse amend", testAmendAMMSparse)
t.Run("test amend AMM insufficient commitment", testAmendInsufficientCommitment)
t.Run("test amend AMM when position to large", testAmendWhenPositionLarge)
}

func TestClosingAMM(t *testing.T) {
Expand Down Expand Up @@ -125,6 +126,7 @@ func testAmendAMMSparse(t *testing.T) {
amend.Parameters.UpperBound.AddSum(num.UintOne())
amend.Parameters.LowerBound.AddSum(num.UintOne())

ensurePosition(t, tst.pos, 0, nil)
updated, _, err := tst.engine.Amend(ctx, amend, riskFactors, scalingFactors, slippage)
require.NoError(t, err)

Expand Down Expand Up @@ -158,6 +160,39 @@ func testAmendInsufficientCommitment(t *testing.T) {
assert.Equal(t, poolID, tst.engine.poolsCpy[0].ID)
}

func testAmendWhenPositionLarge(t *testing.T) {
ctx := context.Background()
tst := getTestEngine(t)

party, subAccount := getParty(t, tst)
submit := getPoolSubmission(t, party, tst.marketID)
expectSubaccountCreation(t, tst, party, subAccount)
whenAMMIsSubmitted(t, tst, submit)

poolID := tst.engine.poolsCpy[0].ID

amend := getPoolAmendment(t, party, tst.marketID)

// lower commitment so that the AMM's position at the same price bounds will be less
amend.CommitmentAmount = num.NewUint(50000000000)

expectBalanceChecks(t, tst, party, subAccount, 100000000000)
ensurePosition(t, tst.pos, 20000000, nil)
_, _, err := tst.engine.Amend(ctx, amend, riskFactors, scalingFactors, slippage)
require.ErrorContains(t, err, "current position is outside of amended bounds")

// check that the original pool still exists
assert.Equal(t, poolID, tst.engine.poolsCpy[0].ID)

expectBalanceChecks(t, tst, party, subAccount, 100000000000)
ensurePosition(t, tst.pos, -20000000, nil)
_, _, err = tst.engine.Amend(ctx, amend, riskFactors, scalingFactors, slippage)
require.ErrorContains(t, err, "current position is outside of amended bounds")

// check that the original pool still exists
assert.Equal(t, poolID, tst.engine.poolsCpy[0].ID)
}

func testBasicSubmitOrder(t *testing.T) {
tst := getTestEngine(t)

Expand Down Expand Up @@ -559,7 +594,7 @@ func testAmendMakesClosingPoolActive(t *testing.T) {

amend := getPoolAmendment(t, party, tst.marketID)
expectBalanceChecks(t, tst, party, subAccount, amend.CommitmentAmount.Uint64())

ensurePosition(t, tst.pos, 0, num.UintZero())
updated, _, err := tst.engine.Amend(ctx, amend, riskFactors, scalingFactors, slippage)
require.NoError(t, err)
tst.engine.Confirm(ctx, updated)
Expand Down
31 changes: 31 additions & 0 deletions core/execution/amm/pool.go
Original file line number Diff line number Diff line change
Expand Up @@ -316,6 +316,21 @@ func (p *Pool) IntoProto() *snapshotpb.PoolMapEntry_Pool {
}
}

// checkPosition will return false if its position exists outside of the curve boundaries and so the AMM
// is invalid.
func (p *Pool) checkPosition() bool {
pos := p.getPosition()
if pos > p.lower.pv.IntPart() {
return false
}

if -pos > p.upper.pv.IntPart() {
return false
}

return true
}

// Update returns a copy of the give pool but with its curves and parameters update as specified by `amend`.
func (p *Pool) Update(
amend *types.AmendAMM,
Expand Down Expand Up @@ -372,6 +387,11 @@ func (p *Pool) Update(
if err := updated.setCurves(rf, sf, linearSlippage); err != nil {
return nil, err
}

if !updated.checkPosition() {
return nil, errors.New("AMM's current position is outside of amended bounds - reduce position first")
}

return updated, nil
}

Expand Down Expand Up @@ -661,6 +681,17 @@ func (p *Pool) TradableVolumeInRange(side types.Side, price1 *num.Uint, price2 *
return num.MinV(uint64(stP-ndP), uint64(num.AbsV(pos)))
}

// TrableVolumeForPrice returns the volume available between the AMM's fair-price and the given
// price and side of an incoming order. It is a special case of TradableVolumeInRange with
// the benefit of accurately using the AMM's position instead of having to calculate the hop
// from fair-price -> position.
func (p *Pool) TradableVolumeForPrice(side types.Side, price *num.Uint) uint64 {
if side == types.SideSell {
return p.TradableVolumeInRange(side, price, nil)
}
return p.TradableVolumeInRange(side, nil, price)
}

// getBalance returns the total balance of the pool i.e it's general account + it's margin account.
func (p *Pool) getBalance() *num.Uint {
general, err := p.collateral.GetPartyGeneralAccount(p.AMMParty, p.asset)
Expand Down
64 changes: 64 additions & 0 deletions core/execution/amm/pool_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ import (

func TestAMMPool(t *testing.T) {
t.Run("test volume between prices", testTradeableVolumeInRange)
t.Run("test volume between prices when AMM closing", testTradeableVolumeInRangeClosing)
t.Run("test tradable volume at price", testTradableVolumeAtPrice)
t.Run("test best price", testBestPrice)
t.Run("test pool logic with position factor", testPoolPositionFactor)
t.Run("test one sided pool", testOneSidedPool)
Expand Down Expand Up @@ -255,6 +257,64 @@ func testTradeableVolumeInRangeClosing(t *testing.T) {
}
}

func testTradableVolumeAtPrice(t *testing.T) {
p := newTestPool(t)
defer p.ctrl.Finish()

tests := []struct {
name string
price *num.Uint
position int64
side types.Side
expectedVolume uint64
}{
{
name: "full volume upper curve",
price: num.NewUint(2200),
side: types.SideBuy,
expectedVolume: 635,
},
{
name: "full volume lower curve",
price: num.NewUint(1800),
side: types.SideSell,
expectedVolume: 702,
},
{
name: "no volume upper, wrong side",
price: num.NewUint(2200),
side: types.SideSell,
expectedVolume: 0,
},
{
name: "no volume lower, wrong side",
price: num.NewUint(1800),
side: types.SideBuy,
expectedVolume: 0,
},
{
name: "no volume at fair-price buy",
price: num.NewUint(2000),
side: types.SideBuy,
expectedVolume: 0,
},
{
name: "no volume at fair-price sell",
price: num.NewUint(2000),
side: types.SideSell,
expectedVolume: 0,
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
ensurePositionN(t, p.pos, tt.position, num.UintZero(), 1)
volume := p.pool.TradableVolumeForPrice(tt.side, tt.price)
assert.Equal(t, int(tt.expectedVolume), int(volume))
})
}
}

func TestTradeableVolumeWhenAtBoundary(t *testing.T) {
// from ticket 11389 this replicates a scenario found during fuzz testing
submit := &types.SubmitAMM{
Expand Down Expand Up @@ -488,6 +548,10 @@ func newBasicPoolWithSubmit(t *testing.T, submit *types.SubmitAMM) (*Pool, error
col := mocks.NewMockCollateral(ctrl)
pos := mocks.NewMockPosition(ctrl)

pos.EXPECT().GetPositionsByParty(gomock.Any()).AnyTimes().Return(
[]events.MarketPosition{&marketPosition{size: 0, averageEntry: nil}},
)

sqrter := &Sqrter{cache: map[string]num.Decimal{}}

return NewPool(
Expand Down
16 changes: 14 additions & 2 deletions core/execution/amm/shape.go
Original file line number Diff line number Diff line change
Expand Up @@ -93,9 +93,21 @@ func (sm *shapeMaker) makeBoundaryOrder(st, nd *num.Uint) *types.Order {
cu = sm.pool.upper
}

// if one of the boundaries it equal to the fair-price then the equivalent position
// if the AMM's current position and checking removes the risk of precision loss
stPosition := sm.pos
if st.NEQ(sm.fairPrice) {
stPosition = cu.positionAtPrice(sm.pool.sqrt, st)
}

ndPosition := sm.pos
if nd.NEQ(sm.fairPrice) {
ndPosition = cu.positionAtPrice(sm.pool.sqrt, nd)
}

volume := num.DeltaV(
cu.positionAtPrice(sm.pool.sqrt, st),
cu.positionAtPrice(sm.pool.sqrt, nd),
stPosition,
ndPosition,
)

if st.GTE(sm.fairPrice) {
Expand Down
16 changes: 15 additions & 1 deletion core/execution/future/market.go
Original file line number Diff line number Diff line change
Expand Up @@ -1442,6 +1442,20 @@ func (m *Market) getNewPeggedPrice(order *types.Order) (*num.Uint, error) {

// we're converting both offset and tick size to asset decimals so we can adjust the price (in asset) directly
priceInMarket, _ := num.UintFromDecimal(price.ToDecimal().Div(m.priceFactor))

// if the pegged offset is zero and the reference price is non-tick size (from an AMM) then we have to move it so it
// is otherwise the pegged will cross.
if order.PeggedOrder.Offset.IsZero() {
if mod := num.UintZero().Mod(priceInMarket, m.mkt.TickSize); !mod.IsZero() {
if order.Side == types.SideBuy {
priceInMarket.Sub(priceInMarket, mod)
} else {
d := num.UintOne().Sub(m.mkt.TickSize, mod)
priceInMarket.AddSum(d)
}
}
}

if order.Side == types.SideSell {
priceInMarket.AddSum(order.PeggedOrder.Offset)
// this can only happen when pegged to mid, in which case we want to round to the nearest *better* tick size
Expand Down Expand Up @@ -5362,7 +5376,7 @@ func (m *Market) getRebasingOrder(
Walk:
for {
// get the tradable volume necessary to move the AMM's position from fair-price -> price
required := pool.TradableVolumeInRange(types.OtherSide(side), fairPrice, price)
required := pool.TradableVolumeForPrice(types.OtherSide(side), price)

// AMM is close enough to the target that is has no volume between, so we do not need to rebase
if required == 0 {
Expand Down
Loading

0 comments on commit 9d1258c

Please sign in to comment.