Skip to content

Commit 738384d

Browse files
authored
fix(bg/paymentManager): improve interval precision & scaling logic (#1347)
1 parent 9cd5119 commit 738384d

File tree

2 files changed

+74
-18
lines changed

2 files changed

+74
-18
lines changed

src/background/services/__tests__/paymentManager.test.ts

Lines changed: 51 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,54 @@
1-
import { distributeAmount } from '../paymentManager';
1+
import { distributeAmount, calculateInterval } from '../paymentManager';
2+
3+
describe('continuous payments / calculateInterval', () => {
4+
test('handles general rate - $0.60/hr', () => {
5+
const result = calculateInterval(60n);
6+
expect(result.units).toBe(1n);
7+
expect(result.period).toBe(60_000);
8+
});
9+
10+
test('handles slow-ish rate - $1.50/hr', () => {
11+
const result = calculateInterval(150n);
12+
expect(result.units).toBe(1n);
13+
expect(result.period).toBe(24_000);
14+
});
15+
16+
test('handles very slow rate - $0.01/hr', () => {
17+
const result = calculateInterval(1n);
18+
expect(result.units).toBe(1n);
19+
expect(result.period).toBe(3_600_000);
20+
});
21+
22+
test('handles exact floor rate - $1.80/hr', () => {
23+
const result = calculateInterval(180n);
24+
expect(result.units).toBe(1n);
25+
expect(result.period).toBe(20_000);
26+
});
27+
28+
test('scales up for fast rates - $18/hr', () => {
29+
const result = calculateInterval(1800n);
30+
expect(result.units).toBe(1n);
31+
expect(result.period).toBe(2000);
32+
});
33+
34+
test('scales up and adjusts period for very fast rates - $36/hr', () => {
35+
const result = calculateInterval(3600n);
36+
expect(result.units).toBe(2n);
37+
expect(result.period).toBe(2000);
38+
});
39+
40+
test('maintains precision with weird decimals - $1.70/hr', () => {
41+
const result = calculateInterval(170n);
42+
expect(result.units).toBe(1n);
43+
expect(result.period).toBe(21_177);
44+
});
45+
46+
test('handles extreme rates - $10,000/hr', () => {
47+
const result = calculateInterval(10_00_000n);
48+
expect(result.units).toBe(556n);
49+
expect(result.period).toBe(2002);
50+
});
51+
});
252

353
describe('one time payments / distributeAmount', () => {
454
class PaymentSession {

src/background/services/paymentManager.ts

Lines changed: 23 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -32,10 +32,10 @@ type Cradle = Pick<
3232
| 'PaymentSession'
3333
>;
3434

35-
/** Payable amount to increase by {@linkcode Interval.units} every {@linkcode Interval.duration}ms. */
36-
type Interval = { units: bigint; duration: number };
35+
/** Payable amount to increase by {@linkcode Interval.units} every {@linkcode Interval.period}ms. */
36+
type Interval = { units: bigint; period: number };
3737

38-
const MS_IN_HOUR = 3600;
38+
const MS_IN_HOUR = 60 * 60 * 1000;
3939

4040
export class PaymentManager {
4141
private rootLogger: Cradle['rootLogger'];
@@ -337,22 +337,14 @@ export class PaymentManager {
337337

338338
setRate(hourlyRate: AmountValue) {
339339
this.hourlyRate = BigInt(hourlyRate);
340-
const secondsPerUnit = MS_IN_HOUR / Number(this.hourlyRate);
341-
const duration = Math.ceil(secondsPerUnit * 1000);
342-
// The math below is equivalent to:
343-
// interval = { units: 1n, duration };
344-
// while (interval.duration < MIN_PAYMENT_WAIT) {
345-
// interval.units += 1n;
346-
// interval.duration += duration;
347-
// }
348-
const units = BigInt(Math.ceil(MIN_PAYMENT_WAIT / duration));
349-
this.interval = { units, duration: Number(units) * duration };
340+
this.interval = calculateInterval(this.hourlyRate);
341+
350342
// TODO: Optimization opportunity above: see if we can set interval based on
351343
// some minSendAmount (HCF of all minSendAmount?). i.e. we will increment
352344
// amount by minSendAmount unit to make the interval longer.
353345

354346
if (this.#state === 'active') {
355-
this.timer.reset(this.interval.duration);
347+
this.timer.reset(this.interval.period);
356348
}
357349
}
358350

@@ -391,7 +383,7 @@ export class PaymentManager {
391383
this.pendingAmount -= amount;
392384

393385
this.checkAndPayContinuously();
394-
this.timer.reset(this.interval.duration);
386+
this.timer.reset(this.interval.period);
395387
}
396388

397389
pause(reason?: string) {
@@ -430,7 +422,7 @@ export class PaymentManager {
430422
}
431423

432424
const elapsed = Date.now() - lastPaymentInfo.ts.valueOf();
433-
const waitTime = this.interval.duration - elapsed;
425+
const waitTime = this.interval.period - elapsed;
434426
if (waitTime > 0) {
435427
this.logger.log('[overpaying] Preventing overpaying:', {
436428
...lastPaymentInfo,
@@ -463,7 +455,7 @@ export class PaymentManager {
463455
}
464456

465457
this.pendingAmount += this.interval.units;
466-
this.timer.reset(this.interval.duration); // as if setInterval
458+
this.timer.reset(this.interval.period); // as if setInterval
467459

468460
const session = this.peekSessionToPay();
469461
if (!session) {
@@ -702,3 +694,17 @@ class PeekAbleIterator<T> implements Iterator<T, never, never> {
702694
return this;
703695
}
704696
}
697+
698+
export function calculateInterval(hourlyRate: bigint): Interval {
699+
const period = MS_IN_HOUR / Number(hourlyRate);
700+
701+
// The math below is equivalent to:
702+
// interval: Interval = { units: 1n, period };
703+
// while (interval.period < MIN_PAYMENT_WAIT) {
704+
// interval.units += 1n;
705+
// interval.period += period;
706+
// }
707+
708+
const units = Math.ceil(MIN_PAYMENT_WAIT / period);
709+
return { units: BigInt(units), period: Math.ceil(units * period) };
710+
}

0 commit comments

Comments
 (0)