From 15f98630c46ec0c09998162a92a5e8bac709e32d Mon Sep 17 00:00:00 2001 From: jxom <7336481+jxom@users.noreply.github.com> Date: Sun, 1 Dec 2024 06:21:42 +0100 Subject: [PATCH] feat: added assertion for ABI-encoding integer ranges --- .changeset/metal-drinks-smoke.md | 5 + src/core/_test/AbiParameters.encode.test.ts | 117 +++++++++++++++++++- src/core/internal/abiParameters.ts | 21 +++- 3 files changed, 139 insertions(+), 4 deletions(-) create mode 100644 .changeset/metal-drinks-smoke.md diff --git a/.changeset/metal-drinks-smoke.md b/.changeset/metal-drinks-smoke.md new file mode 100644 index 00000000..96892c4d --- /dev/null +++ b/.changeset/metal-drinks-smoke.md @@ -0,0 +1,5 @@ +--- +"ox": patch +--- + +Added assertion for ABI-encoding integer ranges. diff --git a/src/core/_test/AbiParameters.encode.test.ts b/src/core/_test/AbiParameters.encode.test.ts index 405c85fe..89ad20d6 100644 --- a/src/core/_test/AbiParameters.encode.test.ts +++ b/src/core/_test/AbiParameters.encode.test.ts @@ -1,4 +1,4 @@ -import { AbiItem, AbiParameters } from 'ox' +import { AbiItem, AbiParameters, Solidity } from 'ox' import { describe, expect, test } from 'vitest' import { seaportContractConfig } from '../../../test/constants/abis.js' @@ -229,7 +229,7 @@ describe('static', () => { [ { name: 'xIn', - type: 'int8', + type: 'int32', }, ], [-2147483648], @@ -1789,6 +1789,119 @@ test('invalid bytes', () => { ) }) +test('integer out of range', () => { + expect(() => + AbiParameters.encode( + [ + { + type: 'uint128', + }, + { + type: 'int128', + }, + { + type: 'int128', + }, + { + type: 'uint', + }, + { + type: 'int', + }, + { + type: 'int', + }, + ], + [ + Solidity.maxUint128, + Solidity.maxInt128, + Solidity.minInt128, + Solidity.maxUint256, + Solidity.maxInt256, + Solidity.minInt256, + ], + ), + ).not.toThrow() + + expect(() => + AbiParameters.encode( + [ + { + type: 'uint128', + }, + ], + [Solidity.maxUint128 + 1n], + ), + ).toThrowErrorMatchingInlineSnapshot( + '[Hex.IntegerOutOfRangeError: Number `340282366920938463463374607431768211456` is not in safe 128-bit unsigned integer range (`0` to `340282366920938463463374607431768211455`)]', + ) + + expect(() => + AbiParameters.encode( + [ + { + type: 'uint128', + }, + ], + [-1n], + ), + ).toThrowErrorMatchingInlineSnapshot( + '[Hex.IntegerOutOfRangeError: Number `-1` is not in safe 128-bit unsigned integer range (`0` to `340282366920938463463374607431768211455`)]', + ) + + expect(() => + AbiParameters.encode( + [ + { + type: 'int128', + }, + ], + [Solidity.maxInt128 + 1n], + ), + ).toThrowErrorMatchingInlineSnapshot( + '[Hex.IntegerOutOfRangeError: Number `170141183460469231731687303715884105728` is not in safe 128-bit signed integer range (`-170141183460469231731687303715884105728` to `170141183460469231731687303715884105727`)]', + ) + + expect(() => + AbiParameters.encode( + [ + { + type: 'int128', + }, + ], + [Solidity.minInt128 - 1n], + ), + ).toThrowErrorMatchingInlineSnapshot( + '[Hex.IntegerOutOfRangeError: Number `-170141183460469231731687303715884105729` is not in safe 128-bit signed integer range (`-170141183460469231731687303715884105728` to `170141183460469231731687303715884105727`)]', + ) + + expect(() => + AbiParameters.encode( + [ + { + type: 'uint', + }, + ], + [Solidity.maxUint256 + 1n], + ), + ).toThrowErrorMatchingInlineSnapshot( + '[Hex.IntegerOutOfRangeError: Number `115792089237316195423570985008687907853269984665640564039457584007913129639936` is not in safe 256-bit unsigned integer range (`0` to `115792089237316195423570985008687907853269984665640564039457584007913129639935`)]', + ) + + expect(() => + AbiParameters.encode( + [ + { + type: 'uint', + }, + ], + [-1n], + ), + ).toThrowErrorMatchingInlineSnapshot( + '[Hex.IntegerOutOfRangeError: Number `-1` is not in safe 256-bit unsigned integer range (`0` to `115792089237316195423570985008687907853269984665640564039457584007913129639935`)]', + ) +}) + test('getArrayComponents', () => { expect(getArrayComponents('uint256[2]')).toEqual([2, 'uint256']) expect(getArrayComponents('uint256[2][3]')).toEqual([3, 'uint256[2]']) diff --git a/src/core/internal/abiParameters.ts b/src/core/internal/abiParameters.ts index 6abfbfa7..9d01d7c1 100644 --- a/src/core/internal/abiParameters.ts +++ b/src/core/internal/abiParameters.ts @@ -9,6 +9,7 @@ import * as Address from '../Address.js' import * as Bytes from '../Bytes.js' import * as Errors from '../Errors.js' import * as Hex from '../Hex.js' +import { integerRegex } from '../Solidity.js' import type * as Cursor from './cursor.js' import type { Compute, IsNarrowable, UnionToIntersection } from './types.js' @@ -433,7 +434,11 @@ export function prepareParameter< } if (parameter.type.startsWith('uint') || parameter.type.startsWith('int')) { const signed = parameter.type.startsWith('int') - return encodeNumber(value as unknown as number, { signed }) + const [, , size = '256'] = integerRegex.exec(parameter.type) ?? [] + return encodeNumber(value as unknown as number, { + signed, + size: Number(size), + }) } if (parameter.type.startsWith('bytes')) { return encodeBytes(value as unknown as Hex.Hex, { type: parameter.type }) @@ -630,8 +635,20 @@ export declare namespace encodeBoolean { /** @internal */ export function encodeNumber( value: number, - { signed }: { signed: boolean }, + { signed, size }: { signed: boolean; size: number }, ): PreparedParameter { + if (typeof size === 'number') { + const max = 2n ** (BigInt(size) - (signed ? 1n : 0n)) - 1n + const min = signed ? -max - 1n : 0n + if (value > max || value < min) + throw new Hex.IntegerOutOfRangeError({ + max: max.toString(), + min: min.toString(), + signed, + size: size / 8, + value: value.toString(), + }) + } return { dynamic: false, encoded: Hex.fromNumber(value, {