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
61 changes: 61 additions & 0 deletions yarn-project/stdlib/src/gas/gas_fees.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import { GasFees } from './gas_fees.js';

/** Helper: ceiling division for bigints, matching the fix's behavior. */
function ceilDiv(a: bigint, b: bigint): bigint {
return (a + b - 1n) / b;
}

describe('GasFees.mul', () => {
it('multiplies by an integer number scalar', () => {
const fees = new GasFees(100n, 200n);
const result = fees.mul(2);
expect(result).toEqual(new GasFees(200n, 400n));
});

it('multiplies by a bigint scalar', () => {
const fees = new GasFees(100n, 200n);
const result = fees.mul(3n);
expect(result).toEqual(new GasFees(300n, 600n));
});

it('multiplies by a non-integer scalar', () => {
const fees = new GasFees(100n, 200n);
const result = fees.mul(1.5);
expect(result).toEqual(new GasFees(150n, 300n));
});

it('applies ceiling to non-integer scalar results', () => {
const fees = new GasFees(1n, 1n);
const result = fees.mul(1.5);
// 1 * 1.5 = 1.5, ceil(1.5) = 2
expect(result).toEqual(new GasFees(2n, 2n));
});

it('returns a clone when scalar is 1', () => {
const fees = new GasFees(100n, 200n);
const result = fees.mul(1);
expect(result).toEqual(fees);
expect(result).not.toBe(fees); // should be a new instance (clone)
});

it('preserves precision for values above 2^53 (regression test)', () => {
const bigValue = 2n ** 64n;
const fees = new GasFees(bigValue, bigValue);
const result = fees.mul(1.5);

// 1.5 = 3/2, so the exact result is ceilDiv(2^64 * 3, 2)
const expected = ceilDiv(bigValue * 3n, 2n);
expect(result.feePerDaGas).toEqual(expected);
expect(result.feePerL2Gas).toEqual(expected);
});

it('preserves precision for values near 2^128', () => {
const bigValue = 2n ** 128n - 1n; // max UInt128
const fees = new GasFees(bigValue, bigValue);
const result = fees.mul(1.5);

const expected = ceilDiv(bigValue * 3n, 2n);
expect(result.feePerDaGas).toEqual(expected);
expect(result.feePerL2Gas).toEqual(expected);
});
});
13 changes: 11 additions & 2 deletions yarn-project/stdlib/src/gas/gas_fees.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,14 @@ import { z } from 'zod';
import type { UInt128 } from '../types/shared.js';
import type { GasDimensions } from './gas.js';

/** Ceiling division for non-negative bigints. */
function ceilDiv(a: bigint, b: bigint): bigint {
return (a + b - 1n) / b;
}

/** Precision scale for converting fractional scalars to integer numerators (18 decimal digits). */
const FRACTIONAL_PRECISION = 10n ** 18n;

/** Gas prices for each dimension. */
export class GasFees {
public readonly feePerDaGas: UInt128;
Expand Down Expand Up @@ -60,9 +68,10 @@ export class GasFees {
const s = BigInt(scalar);
return new GasFees(this.feePerDaGas * s, this.feePerL2Gas * s);
} else {
const numerator = BigInt(Math.round(scalar * Number(FRACTIONAL_PRECISION)));
return new GasFees(
BigInt(Math.ceil(Number(this.feePerDaGas) * scalar)),
BigInt(Math.ceil(Number(this.feePerL2Gas) * scalar)),
ceilDiv(this.feePerDaGas * numerator, FRACTIONAL_PRECISION),
ceilDiv(this.feePerL2Gas * numerator, FRACTIONAL_PRECISION),
);
}
}
Expand Down
Loading