Skip to content
Open
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
65 changes: 3 additions & 62 deletions fsm/dex.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,8 @@ import (
"strings"
)

const (
// dexLPEvictionIDDomain separates replacement withdrawal IDs from other deterministic DEX IDs.
dexLPEvictionIDDomain = "dex-lp-eviction-v1"
// dexLPMigrationIDDomain separates migration withdrawal IDs from other deterministic DEX IDs.
dexLPMigrationIDDomain = "dex-lp-migration-v1"
)
// dexLPEvictionIDDomain separates replacement withdrawal IDs from other deterministic DEX IDs.
const dexLPEvictionIDDomain = "dex-lp-eviction-v1"

/* Dex.go implements logic to handle AMM style atomic exchanges between root & nested chains
not to be confused with 1 way order book swaps implemented in swap.go */
Expand Down Expand Up @@ -673,13 +669,9 @@ func (s *StateMachine) handleBatchDeposit(batch *lib.DexBatch, chainId uint64, x
return nil
}

// handleCappedBatchDeposit migrates legacy pools and deterministically admits the best-funded newcomers.
// handleCappedBatchDeposit deterministically admits the best-funded newcomers.
// MaxLiquidityProviders bounds serialized point entries, so the permanent dead address consumes one slot.
func (s *StateMachine) handleCappedBatchDeposit(batch *lib.DexBatch, p *Pool, chainId uint64, x, y *uint64, local bool) (bool, lib.ErrorI) {
// execute the one-time legacy migration before processing newcomers
if err := s.migrateLegacyLPs(batch, p, chainId, x, y, local); err != nil {
return true, err
}
// initialize vars
var err lib.ErrorI
type candidate struct {
Expand Down Expand Up @@ -815,57 +807,6 @@ func liquidityDepositPoints(totalPoints, x, y, amount uint64) (uint64, lib.Error
return lib.SafeMulDiv(totalPoints, newK-oldK, oldK), nil
}

// migrateLegacyLPs() performs the one-time migration of a pool above the provider limit.
func (s *StateMachine) migrateLegacyLPs(batch *lib.DexBatch, p *Pool, chainId uint64, x, y *uint64, local bool) lib.ErrorI {
// calculate how many legacy providers must be removed to reach the limit
excess := len(p.Points) - lib.MaxLiquidityProviders
if excess <= 0 {
return nil
}
// rank the providers from lowest to highest points
sort.Slice(p.Points, func(i, j int) bool {
// rank by points first
if p.Points[i].Points != p.Points[j].Points {
return p.Points[i].Points < p.Points[j].Points
}
// break equal-point ties deterministically using the receipt hash
return bytes.Compare(liquidityProviderTieBreakHash(batch.ReceiptHash, p.Points[i].Address), liquidityProviderTieBreakHash(batch.ReceiptHash, p.Points[j].Address)) < 0
})
// preallocate one full withdrawal for each excess provider
withdrawals := make([]*lib.DexLiquidityWithdraw, 0, excess)
// select the lowest-ranked providers
for _, point := range p.Points {
// the permanent dead address cannot be evicted
if !bytes.Equal(point.Address, deadAddr.Bytes()) {
// create a full withdrawal with a deterministic migration order ID
withdrawals = append(withdrawals, &lib.DexLiquidityWithdraw{Address: point.Address, Percent: 100,
OrderId: crypto.ShortHash(lib.JoinLenPrefix([]byte(dexLPMigrationIDDomain), batch.ReceiptHash, point.Address))})
}
// stop after selecting exactly the excess providers
if len(withdrawals) == excess {
break
}
}
// ensure the pool contained enough removable providers
if len(withdrawals) != excess {
return ErrInvalidLiquidityPool()
}
// execute and persist the migration through the standard withdrawal path
if err := s.HandleBatchWithdraw(&lib.DexBatch{Withdrawals: withdrawals}, chainId, x, y, local); err != nil {
return err
}
// reload the pool updated by HandleBatchWithdraw()
migrated, err := s.GetPool(chainId + LiquidityPoolAddend)
if err != nil {
return err
}
// refresh the working pool before processing the incoming deposits
p.Amount = migrated.Amount
p.Points = migrated.Points
p.TotalPoolPoints = migrated.TotalPoolPoints
return nil
}

// RotateDexBatches() sets 'next batch' as 'locked batch' and deletes reference for 'next batch'
// (1) checks if locked batch is processed yet - if not exit
// (2) sets the upcoming 'sell' batch as 'last' sell batch
Expand Down
36 changes: 2 additions & 34 deletions fsm/dex_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2780,17 +2780,15 @@ func TestHandleBatchDepositRemoteReplacesLowestProvider(t *testing.T) {
newProvider := newTestAddress(t, 2)
receiptHash, depositOrderId := []byte("receipt"), []byte{0x22}

points := make([]*lib.PoolPoints, lib.MaxLiquidityProviders+1)
points := make([]*lib.PoolPoints, lib.MaxLiquidityProviders)
totalPoints := uint64(100)
for i := range points {
address, amount := make([]byte, crypto.AddressSize), uint64(1)
address[len(address)-2], address[len(address)-1] = byte(i>>8), byte(i)
if i == 0 {
address, amount = deadAddr.Bytes(), 100
} else if i < lib.MaxLiquidityProviders {
totalPoints++
} else {
amount = 0 // legacy zero-point holder
totalPoints++
}
points[i] = &lib.PoolPoints{Address: address, Points: amount}
}
Expand Down Expand Up @@ -2895,36 +2893,6 @@ func TestHandleBatchDepositRanksNewcomersByProviderTotal(t *testing.T) {
require.Error(t, err)
}

func TestHandleBatchDepositMigratesPoolBeforeDeposits(t *testing.T) {
sm := newTestStateMachine(t)
chainID := uint64(2)
points := make([]*lib.PoolPoints, 50_000)
var total uint64
for i := range points {
address := make([]byte, crypto.AddressSize)
address[len(address)-2], address[len(address)-1] = byte(i>>8), byte(i)
amount := uint64(i + 1)
if i == 0 {
address, amount = deadAddr.Bytes(), 100
}
points[i], total = &lib.PoolPoints{Address: address, Points: amount}, total+amount
}
require.NoError(t, sm.SetPool(&Pool{Id: chainID + LiquidityPoolAddend, Amount: 1_000_000, Points: points, TotalPoolPoints: total}))

x, y := uint64(1_000_000), uint64(1_000_000)
require.NoError(t, sm.HandleBatchDeposit(&lib.DexBatch{Deposits: []*lib.DexLiquidityDeposit{{
Address: points[len(points)-1].Address, Amount: 100,
}}}, chainID, &x, &y, false))

pool, err := sm.GetPool(chainID + LiquidityPoolAddend)
require.NoError(t, err)
require.Len(t, pool.Points, lib.MaxLiquidityProviders)
for _, evicted := range points[1:3] {
_, err = pool.GetPointsFor(evicted.Address)
require.Error(t, err)
}
}

func TestHandleBatchDepositUsesReceiptHashForEqualStakeTie(t *testing.T) {
sm := newTestStateMachine(t)
chainID, seed := uint64(2), []byte("receipt")
Expand Down
Loading