apportionBasisPoints(percentages: number[]) in src/escrow/split-math.util.ts rounds each percentage to basis points, then always redistributes TOTAL_BASIS_POINTS - sum(bps) (the "delta") across recipients until the output sums to exactly 10,000 — regardless of what the input summed to. Unlike splitStroops in the same file, which explicitly validates its own precondition (if (bpsTotal !== TOTAL_BASIS_POINTS) throw new BadRequestException(...)), apportionBasisPoints has no equivalent guard on its own input.
In production this is masked by EscrowService.assertValidSplits() always running first and rejecting a bad sum before apportionBasisPoints is ever called — but as a standalone exported utility with a doc comment implying general correctness ("the integer vector handed to the contract always represents exactly 100%"), it will silently "fix" an arbitrarily wrong input into a valid-looking 10,000-bps output for any future caller that doesn't happen to pre-validate, rather than failing loudly. It's also an O(delta) loop (for (let i = 0; i < delta; i++)) rather than O(n) — a badly wrong input (e.g. percentages summing to only 20) produces a delta of ~8000, iterated one basis point at a time. src/escrow/split-math.util.spec.ts has no test for a not-summing-to-100 input either, so this is untested as well as unguarded. Consider validating Math.abs(sum(percentages) - 100) <= tolerance at the top of the function, matching splitStroops's own defensive pattern in the same file.
apportionBasisPoints(percentages: number[])insrc/escrow/split-math.util.tsrounds each percentage to basis points, then always redistributesTOTAL_BASIS_POINTS - sum(bps)(the "delta") across recipients until the output sums to exactly 10,000 — regardless of what the input summed to. UnlikesplitStroopsin the same file, which explicitly validates its own precondition (if (bpsTotal !== TOTAL_BASIS_POINTS) throw new BadRequestException(...)),apportionBasisPointshas no equivalent guard on its own input.In production this is masked by
EscrowService.assertValidSplits()always running first and rejecting a bad sum beforeapportionBasisPointsis ever called — but as a standalone exported utility with a doc comment implying general correctness ("the integer vector handed to the contract always represents exactly 100%"), it will silently "fix" an arbitrarily wrong input into a valid-looking 10,000-bps output for any future caller that doesn't happen to pre-validate, rather than failing loudly. It's also an O(delta) loop (for (let i = 0; i < delta; i++)) rather than O(n) — a badly wrong input (e.g. percentages summing to only 20) produces adeltaof ~8000, iterated one basis point at a time.src/escrow/split-math.util.spec.tshas no test for a not-summing-to-100 input either, so this is untested as well as unguarded. Consider validatingMath.abs(sum(percentages) - 100) <= toleranceat the top of the function, matchingsplitStroops's own defensive pattern in the same file.