Skip to content

Commit 167a921

Browse files
authored
feat(reaper): Add reapers for insufficient balance states (#593)
* Add reapers for insufficient balance states * Update tests
1 parent 73930ca commit 167a921

File tree

2 files changed

+93
-19
lines changed

2 files changed

+93
-19
lines changed

lib/crons/gs-reaper/gs-reaper.ts

Lines changed: 27 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -64,16 +64,18 @@ const SLEEP_TIME_MS = 1000 // 1 second between iterations
6464
export class GSReaper {
6565
private log: Logger
6666
private repo: BaseOrdersRepository<UniswapXOrderEntity>
67+
private unresolvedOrderStatus: ORDER_STATUS
6768
private providers: Map<ChainId, ethers.providers.StaticJsonRpcProvider>
6869
private cursors: Map<ChainId, string | undefined> = new Map()
6970

70-
constructor(repo: BaseOrdersRepository<UniswapXOrderEntity>) {
71+
constructor(repo: BaseOrdersRepository<UniswapXOrderEntity>, unresolvedOrderStatus: ORDER_STATUS) {
7172
this.log = bunyan.createLogger({
7273
name: 'GSReaper',
7374
serializers: bunyan.stdSerializers,
7475
level: 'info',
7576
})
7677
this.repo = repo
78+
this.unresolvedOrderStatus = unresolvedOrderStatus
7779
this.providers = new Map<ChainId, ethers.providers.StaticJsonRpcProvider>()
7880
this.initializeProviders()
7981
}
@@ -130,8 +132,9 @@ export class GSReaper {
130132
switch (state.stage) {
131133
case ReaperStage.GET_OPEN_ORDERS: {
132134
this.log.info(`GET_OPEN_ORDERS for chainId ${state.chainId}`)
133-
const { orderHashes, cursor } = await getOpenOrderHashes(
134-
this.repo,
135+
const { orderHashes, cursor } = await getUnresolvedOrderHashes(
136+
this.repo,
137+
this.unresolvedOrderStatus,
135138
state.chainId,
136139
MAX_ORDERS_PER_CHAIN,
137140
this.log,
@@ -384,8 +387,9 @@ async function updateOrders(
384387
/**
385388
* Get hashes of open orders from the database, continuing from the provided cursor if present
386389
*/
387-
async function getOpenOrderHashes(
388-
repo: BaseOrdersRepository<UniswapXOrderEntity>,
390+
async function getUnresolvedOrderHashes(
391+
repo: BaseOrdersRepository<UniswapXOrderEntity>,
392+
unresolvedOrderStatus: ORDER_STATUS,
389393
chainId: ChainId,
390394
maxOrders: number,
391395
log: Logger,
@@ -399,7 +403,7 @@ async function getOpenOrderHashes(
399403
const openOrders: QueryResult<UniswapXOrderEntity> = await repo.getOrders(
400404
DYNAMO_BATCH_WRITE_MAX,
401405
{
402-
orderStatus: ORDER_STATUS.OPEN,
406+
orderStatus: unresolvedOrderStatus,
403407
chainId: chainId,
404408
},
405409
cursor
@@ -432,7 +436,7 @@ async function getOpenOrderHashes(
432436
// If cursor is invalid, start from the beginning
433437
if (error instanceof Error && error.message.includes('Invalid cursor')) {
434438
log.info(`Invalid cursor for chainId ${chainId}, starting from beginning`)
435-
return getOpenOrderHashes(repo, chainId, maxOrders, log, undefined)
439+
return getUnresolvedOrderHashes(repo, unresolvedOrderStatus, chainId, maxOrders, log, undefined)
436440
}
437441
throw error
438442
}
@@ -482,18 +486,24 @@ async function getOrderFillInfo(
482486
}
483487

484488
async function startReapers() {
485-
const dutchReaper = new GSReaper(DutchOrdersRepository.create(new DynamoDB.DocumentClient()))
486-
const limitReaper = new GSReaper(LimitOrdersRepository.create(new DynamoDB.DocumentClient()))
489+
const dutchOpenOrderReaper = new GSReaper(DutchOrdersRepository.create(new DynamoDB.DocumentClient()), ORDER_STATUS.OPEN)
490+
const limitOpenOrderReaper = new GSReaper(LimitOrdersRepository.create(new DynamoDB.DocumentClient()), ORDER_STATUS.OPEN)
491+
const dutchInsufficientFundsOrderReaper = new GSReaper(DutchOrdersRepository.create(new DynamoDB.DocumentClient()), ORDER_STATUS.INSUFFICIENT_FUNDS)
492+
const limitInsufficientFundsOrderReaper = new GSReaper(LimitOrdersRepository.create(new DynamoDB.DocumentClient()), ORDER_STATUS.INSUFFICIENT_FUNDS)
493+
const reapers = [
494+
dutchOpenOrderReaper,
495+
limitOpenOrderReaper,
496+
dutchInsufficientFundsOrderReaper,
497+
limitInsufficientFundsOrderReaper
498+
]
487499
// eslint-disable-next-line no-constant-condition
488500
while (true) {
489-
await dutchReaper.start().catch(error => {
490-
console.error('Fatal error in GS Reaper:', error)
491-
process.exit(1)
492-
})
493-
await limitReaper.start().catch(error => {
494-
console.error('Fatal error in GS Reaper:', error)
495-
process.exit(1)
496-
})
501+
for (const reaper of reapers) {
502+
await reaper.start().catch(error => {
503+
console.error('Fatal error in GS Reaper:', error)
504+
process.exit(1)
505+
})
506+
}
497507
}
498508
}
499509

test/unit/handlers/gs-reaper/gs-reaper.test.ts

Lines changed: 66 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -170,15 +170,79 @@ describe('GSReaper', () => {
170170
await mockOrdersRepository.addOrder(MOCK_ORDER_ENTITY)
171171
mockWatcher.getFillEvents.mockResolvedValue([{ orderHash: MOCK_V2_ORDER_ENTITY.orderHash }, { orderHash: MOCK_ORDER_ENTITY.orderHash }])
172172

173-
// Create new reaper instance
174-
reaper = new GSReaper(mockOrdersRepository)
173+
// Create new reaper instance with OPEN status
174+
reaper = new GSReaper(mockOrdersRepository, ORDER_STATUS.OPEN)
175175
})
176176

177177
afterEach(async () => {
178178
mockOrdersRepository.orders.clear()
179179
jest.clearAllMocks()
180180
})
181181

182+
describe('constructor with different order statuses', () => {
183+
it('creates reaper with OPEN status', () => {
184+
const openReaper = new GSReaper(mockOrdersRepository, ORDER_STATUS.OPEN)
185+
expect(openReaper).toBeDefined()
186+
})
187+
188+
it('creates reaper with INSUFFICIENT_FUNDS status', () => {
189+
const insufficientFundsReaper = new GSReaper(mockOrdersRepository, ORDER_STATUS.INSUFFICIENT_FUNDS)
190+
expect(insufficientFundsReaper).toBeDefined()
191+
})
192+
})
193+
194+
describe('order status filtering', () => {
195+
it('filters orders by OPEN status', async () => {
196+
// Add orders with different statuses
197+
await mockOrdersRepository.addOrder({
198+
...MOCK_ORDER_ENTITY,
199+
orderHash: 'open-order-hash',
200+
orderStatus: ORDER_STATUS.OPEN
201+
})
202+
await mockOrdersRepository.addOrder({
203+
...MOCK_ORDER_ENTITY,
204+
orderHash: 'insufficient-funds-order-hash',
205+
orderStatus: ORDER_STATUS.INSUFFICIENT_FUNDS
206+
})
207+
208+
const openReaper = new GSReaper(mockOrdersRepository, ORDER_STATUS.OPEN)
209+
const state = await openReaper.initializeChainState(ChainId.MAINNET)
210+
211+
const result = await openReaper.processChainState({
212+
...state,
213+
stage: ReaperStage.GET_OPEN_ORDERS
214+
})
215+
216+
expect(result?.orderHashes).toContain('open-order-hash')
217+
expect(result?.orderHashes).not.toContain('insufficient-funds-order-hash')
218+
})
219+
220+
it('filters orders by INSUFFICIENT_FUNDS status', async () => {
221+
// Add orders with different statuses
222+
await mockOrdersRepository.addOrder({
223+
...MOCK_ORDER_ENTITY,
224+
orderHash: 'open-order-hash',
225+
orderStatus: ORDER_STATUS.OPEN
226+
})
227+
await mockOrdersRepository.addOrder({
228+
...MOCK_ORDER_ENTITY,
229+
orderHash: 'insufficient-funds-order-hash',
230+
orderStatus: ORDER_STATUS.INSUFFICIENT_FUNDS
231+
})
232+
233+
const insufficientFundsReaper = new GSReaper(mockOrdersRepository, ORDER_STATUS.INSUFFICIENT_FUNDS)
234+
const state = await insufficientFundsReaper.initializeChainState(ChainId.MAINNET)
235+
236+
const result = await insufficientFundsReaper.processChainState({
237+
...state,
238+
stage: ReaperStage.GET_OPEN_ORDERS
239+
})
240+
241+
expect(result?.orderHashes).toContain('insufficient-funds-order-hash')
242+
expect(result?.orderHashes).not.toContain('open-order-hash')
243+
})
244+
})
245+
182246
describe('state machine', () => {
183247
it('initializes first chain state correctly', async () => {
184248
const state = await reaper.initializeChainState(ChainId.MAINNET)

0 commit comments

Comments
 (0)