Skip to content

Commit

Permalink
WIP on U128 encoding
Browse files Browse the repository at this point in the history
  • Loading branch information
benesjan committed Jan 14, 2025
1 parent 1b4a9d4 commit 4045bd3
Show file tree
Hide file tree
Showing 5 changed files with 113 additions and 2 deletions.
3 changes: 3 additions & 0 deletions yarn-project/aztec.js/src/utils/abi_types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,5 +21,8 @@ export type FunctionSelectorLike = FieldLike | FunctionSelector;
/** Any type that can be converted into an EventSelector Aztec.nr struct. */
export type EventSelectorLike = FieldLike | EventSelector;

/** Any type that can be converted into a U128. */
export type U128Like = bigint | number;

/** Any type that can be converted into a struct with a single `inner` field. */
export type WrappedFieldLike = { /** Wrapped value */ inner: FieldLike } | FieldLike;
4 changes: 4 additions & 0 deletions yarn-project/builder/src/contract-interface-gen/typescript.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
isAztecAddressStruct,
isEthAddressStruct,
isFunctionSelectorStruct,
isU128Struct,
isWrappedFieldStruct,
} from '@aztec/foundation/abi';

Expand Down Expand Up @@ -41,6 +42,9 @@ function abiTypeToTypescript(type: ABIParameter['type']): string {
if (isWrappedFieldStruct(type)) {
return 'WrappedFieldLike';
}
if (isU128Struct(type)) {
return 'U128Like';
}
return `{ ${type.fields.map(f => `${f.name}: ${abiTypeToTypescript(f.type)}`).join(', ')} }`;
default:
throw new Error(`Unknown type ${type}`);
Expand Down
75 changes: 74 additions & 1 deletion yarn-project/foundation/src/abi/encoder.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { Point } from '../fields/point.js';
import { jsonParseWithSchema, jsonStringify } from '../json-rpc/convert.js';
import { schemas } from '../schemas/schemas.js';
import { type FunctionAbi, FunctionType } from './abi.js';
import { encodeArguments } from './encoder.js';
import { convertToU128Limbs, encodeArguments } from './encoder.js';

describe('abi/encoder', () => {
it('serializes fields as fields', () => {
Expand Down Expand Up @@ -235,4 +235,77 @@ describe('abi/encoder', () => {

expect(() => encodeArguments(testFunctionAbi, args)).toThrow(/Invalid hex-encoded string/);
});

describe('convertToU128Limbs', () => {
it('converts 0 correctly', () => {
const result = convertToU128Limbs(0);
expect(result.lo).toBe(0n);
expect(result.hi).toBe(0n);
});

it('converts maximum 128-bit value correctly', () => {
const maxValue = 2n ** 128n - 1n;
const result = convertToU128Limbs(maxValue);
expect(result.lo).toBe(BigInt('0xFFFFFFFFFFFFFFFF'));
expect(result.hi).toBe(BigInt('0xFFFFFFFFFFFFFFFF'));
});

it('converts value with only low bits set', () => {
const value = BigInt('0xABCDEF0123456789');
const result = convertToU128Limbs(value);
expect(result.lo).toBe(BigInt('0xABCDEF0123456789'));
expect(result.hi).toBe(0n);
});

it('converts value with both high and low bits set', () => {
const hi = BigInt('0xFEDCBA9876543210');
const lo = BigInt('0x1234567890ABCDEF');
const value = (hi << 64n) | lo;
const result = convertToU128Limbs(value);
expect(result.lo).toBe(lo);
expect(result.hi).toBe(hi);
});

it('converts number type inputs correctly', () => {
const result = convertToU128Limbs(12345);
expect(result.lo).toBe(12345n);
expect(result.hi).toBe(0n);
});

it('handles edge cases near power of 2 boundaries', () => {
// Test just below 2^64
const nearMax64 = 2n ** 64n - 1n;
const result1 = convertToU128Limbs(nearMax64);
expect(result1.lo).toBe(BigInt('0xFFFFFFFFFFFFFFFF'));
expect(result1.hi).toBe(0n);

// Test just above 2^64
const justAbove64 = 2n ** 64n + 1n;
const result2 = convertToU128Limbs(justAbove64);
expect(result2.lo).toBe(1n);
expect(result2.hi).toBe(1n);
});

describe('error cases', () => {
it('throws error for negative values', () => {
expect(() => convertToU128Limbs(-1)).toThrow(
'Value -1 is not within 128 bits and hence cannot be converted to U128 limbs.',
);
});

it('throws error for values >= 2^128', () => {
const tooLarge = 2n ** 128n;
expect(() => convertToU128Limbs(tooLarge)).toThrow(
`Value ${tooLarge} is not within 128 bits and hence cannot be converted to U128 limbs.`,
);
});

it('throws error for maximum safe integer edge case', () => {
const maxSafe = 2n ** 128n + 1n;
expect(() => convertToU128Limbs(maxSafe)).toThrow(
`Value ${maxSafe} is not within 128 bits and hence cannot be converted to U128 limbs.`,
);
});
});
});
});
25 changes: 24 additions & 1 deletion yarn-project/foundation/src/abi/encoder.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Fr } from '../fields/index.js';
import { type AbiType, type FunctionAbi } from './abi.js';
import { isAddressStruct, isFunctionSelectorStruct, isWrappedFieldStruct } from './utils.js';
import { isAddressStruct, isFunctionSelectorStruct, isU128Struct, isWrappedFieldStruct } from './utils.js';

/**
* Encodes arguments for a function call.
Expand Down Expand Up @@ -105,6 +105,14 @@ class ArgumentEncoder {
this.encodeArgument({ kind: 'integer', sign: 'unsigned', width: 32 }, arg.value ?? arg, `${name}.inner`);
break;
}
if (isU128Struct(abiType)) {
// U128 struct has low and high limbs - so we first convert the value to the 2 limbs and then we encode them
// --> this results in the limbs being added to the final flat array as [..., lo, hi, ...].
const { lo, hi } = convertToU128Limbs(arg);
this.encodeArgument({ kind: 'field' }, lo, `${name}.lo`);
this.encodeArgument({ kind: 'field' }, hi, `${name}.hi`);
break;
}
if (isWrappedFieldStruct(abiType)) {
this.encodeArgument({ kind: 'field' }, arg.inner ?? arg, `${name}.inner`);
break;
Expand Down Expand Up @@ -158,3 +166,18 @@ export function encodeArguments(abi: FunctionAbi, args: any[]) {
export function countArgumentsSize(abi: FunctionAbi) {
return abi.parameters.reduce((acc, parameter) => acc + ArgumentEncoder.typeSize(parameter.type), 0);
}

export function convertToU128Limbs(value: bigint | number): { lo: bigint; hi: bigint } {
if (typeof value === 'number') {
value = BigInt(value);
}

// Check value is within 128 bits
if (value < 0 || value >= 2n ** 128n) {
throw new Error(`Value ${value} is not within 128 bits and hence cannot be converted to U128 limbs.`);
}

const lo = value & BigInt('0xFFFFFFFFFFFFFFFF');
const hi = value >> BigInt(64);
return { lo, hi };
}
8 changes: 8 additions & 0 deletions yarn-project/foundation/src/abi/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,14 @@ export function isFunctionSelectorStruct(abiType: AbiType) {
return abiType.kind === 'struct' && abiType.path.endsWith('types::abis::function_selector::FunctionSelector');
}

/**
* Returns whether the ABI type is a U128 defined in noir::std.
* @param abiType - Type to check.
*/
export function isU128Struct(abiType: AbiType) {
return abiType.kind === 'struct' && abiType.path.endsWith('U128');
}

/**
* Returns whether the ABI type is a struct with a single `inner` field.
* @param abiType - Type to check.
Expand Down

0 comments on commit 4045bd3

Please sign in to comment.