diff --git a/packages/order/src/order.test.ts b/packages/order/src/order.test.ts index c71509a8..682c2d12 100644 --- a/packages/order/src/order.test.ts +++ b/packages/order/src/order.test.ts @@ -242,6 +242,7 @@ describe("OrderMatcher", () => { udtToCkb: { matchableCount: 1 }, }, candidates: { + bestGain: 0n, positiveGain: 0, }, }, @@ -249,6 +250,55 @@ describe("OrderMatcher", () => { expect(match.diagnostics?.candidates.rejected.nonPositiveGain).toBeGreaterThan(0); }); + it("does not select non-positive candidates after rejecting the empty allowance", () => { + const order = makeOrderCell({ + ckbUnoccupied: ccc.fixedPointFrom(200), + udtValue: 0n, + info: Info.create(true, { ckbScale: 1n, udtScale: 1n }), + master: { + type: "absolute", + value: { + txHash: byte32FromByte("33"), + index: 1n, + }, + }, + outPoint: { + txHash: byte32FromByte("55"), + index: 0n, + }, + }); + + const match = OrderManager.bestMatch( + [order], + { + ckbValue: -1n, + udtValue: ccc.fixedPointFrom(1000), + }, + { + ckbScale: 1n, + udtScale: 1n, + }, + { + feeRate: 0n, + ckbAllowanceStep: ccc.fixedPointFrom(1), + }, + ); + + expect(match).toMatchObject({ + ckbDelta: 0n, + udtDelta: 0n, + partials: [], + diagnostics: { + candidates: { + bestGain: 0n, + positiveGain: 0, + }, + }, + }); + expect(match.diagnostics?.candidates.rejected.insufficientCkbAllowance).toBeGreaterThan(0); + expect(match.diagnostics?.candidates.rejected.nonPositiveGain).toBeGreaterThan(0); + }); + it("reports one primary allowance rejection reason per candidate", () => { const order = makeUdtToCkbOrder(); @@ -1734,8 +1784,8 @@ function exhaustiveSequentialBestMatch( const udtAllowanceStep = ( options.ckbAllowanceStep * exchangeRate.ckbScale + exchangeRate.udtScale - 1n ) / exchangeRate.udtScale; - let best: Match | undefined; - let bestGain = -1n << 256n; + let best: Match = { ckbDelta: 0n, udtDelta: 0n, partials: [] }; + let bestGain = 0n; for (const c2u of OrderManager.sequentialMatcher( orderPool, true, @@ -1763,13 +1813,17 @@ function exhaustiveSequentialBestMatch( const udtAllowance = allowance.udtValue + udtDelta; const gain = (ckbDelta - ckbFee) * exchangeRate.ckbScale + udtDelta * exchangeRate.udtScale; - if (ckbAllowance >= 0n && udtAllowance >= 0n && gain > bestGain) { + if ( + ckbAllowance >= 0n && + udtAllowance >= 0n && + gain > bestGain + ) { best = { ckbDelta, udtDelta, partials }; bestGain = gain; } } } - return best ?? { ckbDelta: 0n, udtDelta: 0n, partials: [] }; + return best; } function hasUniquePartialOrderOutPoints(partials: Match["partials"]): boolean { diff --git a/packages/order/src/order.ts b/packages/order/src/order.ts index 3b8c2e37..05e42995 100644 --- a/packages/order/src/order.ts +++ b/packages/order/src/order.ts @@ -387,22 +387,22 @@ export class OrderManager implements ScriptDeps { ); let best = { - i: -1, - j: -1, ckbDelta: 0n, udtDelta: 0n, partials: [] as Match["partials"], - ckbAllowance: allowance.ckbValue, - udtAllowance: allowance.udtValue, + gain: 0n, + }; + let advance = { + i: -1, + j: -1, gain: -1n << 256n, }; - // best.i/best.j are offsets into the current two-entry frontiers; (0, 0) + // advance.i/advance.j are offsets into the current two-entry frontiers; (0, 0) // means the current frontier head is already optimal, so the search stops. - while (best.i !== 0 || best.j !== 0) { - ckb2UdtMatches.next(best.i); - udt2CkbMatches.next(best.j); - best.i = 0; - best.j = 0; + while (advance.i !== 0 || advance.j !== 0) { + ckb2UdtMatches.next(advance.i); + udt2CkbMatches.next(advance.j); + advance = { i: 0, j: 0, gain: -1n << 256n }; for (const [i, c2u] of ckb2UdtMatches.buffer.entries()) { for (const [j, u2c] of udt2CkbMatches.buffer.entries()) { @@ -431,23 +431,23 @@ export class OrderManager implements ScriptDeps { continue; } diagnostics.candidates.viable += 1; + if (gain > advance.gain) { + advance = { i, j, gain }; + } if (partials.length > 0) { if (gain > 0n) { diagnostics.candidates.positiveGain += 1; } else { diagnostics.candidates.rejected.nonPositiveGain += 1; + continue; } } if (gain > best.gain) { best = { - i, - j, ckbDelta, udtDelta, partials, - ckbAllowance, - udtAllowance, gain, }; }