From 9b77b8fbfc713ef528d3525ca4eca0e0a3781106 Mon Sep 17 00:00:00 2001 From: sklppy88 Date: Tue, 29 Oct 2024 17:34:12 +0000 Subject: [PATCH] init --- .../foundation/src/abi/decoder.test.ts | 109 +++++++++++++++++- yarn-project/foundation/src/abi/decoder.ts | 13 ++- yarn-project/foundation/src/abi/utils.test.ts | 39 +++++++ yarn-project/foundation/src/abi/utils.ts | 28 +++++ 4 files changed, 183 insertions(+), 6 deletions(-) create mode 100644 yarn-project/foundation/src/abi/utils.test.ts diff --git a/yarn-project/foundation/src/abi/decoder.test.ts b/yarn-project/foundation/src/abi/decoder.test.ts index 717f3b38dde3..19ccce4da717 100644 --- a/yarn-project/foundation/src/abi/decoder.test.ts +++ b/yarn-project/foundation/src/abi/decoder.test.ts @@ -1,5 +1,6 @@ +import { Fr } from '../fields/fields.js'; import { type ABIParameterVisibility, type FunctionArtifact } from './abi.js'; -import { decodeFunctionSignature, decodeFunctionSignatureWithParameterNames } from './decoder.js'; +import { decodeFromAbi, decodeFunctionSignature, decodeFunctionSignatureWithParameterNames } from './decoder.js'; describe('abi/decoder', () => { // Copied from noir-contracts/contracts/test_contract/target/Test.json @@ -75,3 +76,109 @@ describe('abi/decoder', () => { ); }); }); + +describe('decoder', () => { + it('decodes an i8', () => { + let decoded = decodeFromAbi( + [ + { + kind: 'integer', + sign: 'signed', + width: 8, + }, + ], + [Fr.fromBuffer(Buffer.from('00000000000000000000000000000000000000000000000000000000000000ff', 'hex'))], + ); + expect(decoded).toBe(-1n); + + decoded = decodeFromAbi( + [ + { + kind: 'integer', + sign: 'signed', + width: 8, + }, + ], + [Fr.fromBuffer(Buffer.from('000000000000000000000000000000000000000000000000000000000000007f', 'hex'))], + ); + expect(decoded).toBe(2n ** 7n - 1n); + }); + + it('decodes an i16', () => { + let decoded = decodeFromAbi( + [ + { + kind: 'integer', + sign: 'signed', + width: 16, + }, + ], + [Fr.fromBuffer(Buffer.from('000000000000000000000000000000000000000000000000000000000000ffff', 'hex'))], + ); + expect(decoded).toBe(-1n); + + decoded = decodeFromAbi( + [ + { + kind: 'integer', + sign: 'signed', + width: 16, + }, + ], + [Fr.fromBuffer(Buffer.from('0000000000000000000000000000000000000000000000000000000000007fff', 'hex'))], + ); + expect(decoded).toBe(2n ** 15n - 1n); + }); + + it('decodes an i32', () => { + let decoded = decodeFromAbi( + [ + { + kind: 'integer', + sign: 'signed', + width: 32, + }, + ], + [Fr.fromBuffer(Buffer.from('00000000000000000000000000000000000000000000000000000000ffffffff', 'hex'))], + ); + expect(decoded).toBe(-1n); + + decoded = decodeFromAbi( + [ + { + kind: 'integer', + sign: 'signed', + width: 32, + }, + ], + [Fr.fromBuffer(Buffer.from('000000000000000000000000000000000000000000000000000000007fffffff', 'hex'))], + ); + expect(decoded).toBe(2n ** 31n - 1n); + }); + + it('decodes an i64', () => { + let decoded = decodeFromAbi( + [ + { + kind: 'integer', + sign: 'signed', + width: 64, + }, + ], + [Fr.fromBuffer(Buffer.from('000000000000000000000000000000000000000000000000ffffffffffffffff', 'hex'))], + ); + expect(decoded).toBe(-1n); + + decoded = decodeFromAbi( + [ + { + kind: 'integer', + sign: 'signed', + width: 64, + }, + ], + [Fr.fromBuffer(Buffer.from('0000000000000000000000000000000000000000000000007fffffffffffffff', 'hex'))], + ); + expect(decoded).toBe(2n ** 63n - 1n); + }); +}); diff --git a/yarn-project/foundation/src/abi/decoder.ts b/yarn-project/foundation/src/abi/decoder.ts index 3cba542cb49d..597b42ca0208 100644 --- a/yarn-project/foundation/src/abi/decoder.ts +++ b/yarn-project/foundation/src/abi/decoder.ts @@ -1,7 +1,7 @@ import { AztecAddress } from '../aztec-address/index.js'; import { type Fr } from '../fields/index.js'; import { type ABIParameter, type ABIVariable, type AbiType } from './abi.js'; -import { isAztecAddressStruct } from './utils.js'; +import { isAztecAddressStruct, parseSignedInt } from './utils.js'; /** * The type of our decoded ABI. @@ -10,7 +10,6 @@ export type AbiDecoded = bigint | boolean | AztecAddress | AbiDecoded[] | { [key /** * Decodes values using a provided ABI. - * Missing support for signed integer. */ class AbiDecoder { constructor(private types: AbiType[], private flattened: Fr[]) {} @@ -24,11 +23,15 @@ class AbiDecoder { switch (abiType.kind) { case 'field': return this.getNextField().toBigInt(); - case 'integer': + case 'integer': { + const nextField = this.getNextField(); + if (abiType.sign === 'signed') { - throw new Error('Unsupported type: signed integer'); + // We parse the buffer using 2's complement + return parseSignedInt(nextField.toBuffer(), abiType.width); } - return this.getNextField().toBigInt(); + return nextField.toBigInt(); + } case 'boolean': return !this.getNextField().isZero(); case 'array': { diff --git a/yarn-project/foundation/src/abi/utils.test.ts b/yarn-project/foundation/src/abi/utils.test.ts new file mode 100644 index 000000000000..cb90bc11dfea --- /dev/null +++ b/yarn-project/foundation/src/abi/utils.test.ts @@ -0,0 +1,39 @@ +import { parseSignedInt } from './utils.js'; + +describe('parse signed int', () => { + it('i8', () => { + let buf = Buffer.from('ff', 'hex'); + expect(parseSignedInt(buf)).toBe(-1n); + + // max positive value + buf = Buffer.from('7f', 'hex'); + expect(parseSignedInt(buf)).toBe(2n ** 7n - 1n); + }); + + it('i16', () => { + let buf = Buffer.from('ffff', 'hex'); + expect(parseSignedInt(buf)).toBe(-1n); + + // max positive value + buf = Buffer.from('7fff', 'hex'); + expect(parseSignedInt(buf)).toBe(2n ** 15n - 1n); + }); + + it('i32', () => { + let buf = Buffer.from('ffffffff', 'hex'); + expect(parseSignedInt(buf)).toBe(-1n); + + // max positive value + buf = Buffer.from('7fffffff', 'hex'); + expect(parseSignedInt(buf)).toBe(2n ** 31n - 1n); + }); + + it('i64', () => { + let buf = Buffer.from('ffffffffffffffff', 'hex'); + expect(parseSignedInt(buf)).toBe(-1n); + + // max positive value + buf = Buffer.from('7fffffffffffffff', 'hex'); + expect(parseSignedInt(buf)).toBe(2n ** 63n - 1n); + }); +}); diff --git a/yarn-project/foundation/src/abi/utils.ts b/yarn-project/foundation/src/abi/utils.ts index 7bc8c55ce5e8..fe3f18bd4843 100644 --- a/yarn-project/foundation/src/abi/utils.ts +++ b/yarn-project/foundation/src/abi/utils.ts @@ -48,3 +48,31 @@ export function isWrappedFieldStruct(abiType: AbiType) { abiType.fields[0].type.kind === 'field' ); } + +/** + * Returns a bigint by parsing a serialized 2's complement signed int. + * @param b - The signed int as a buffer + * @returns - a deserialized bigint + */ +export function parseSignedInt(b: Buffer, width?: number) { + const buf = Buffer.from(b); + + // We get the last (width / 8) bytes where width = bits of type (i64, i32 etc) + const slicedBuf = width !== undefined ? buf.subarray(-(width / 8)) : buf; + + // Then manually deserialize with 2's complement, with the process as follows: + + // If our most significant bit is high... + if (0x80 & slicedBuf.subarray(0, 1).readUInt8()) { + // We flip the bits + for (let i = 0; i < slicedBuf.length; i++) { + slicedBuf[i] = ~slicedBuf[i]; + } + + // Add one, then negate it + return -(BigInt(`0x${slicedBuf.toString('hex')}`) + 1n); + } + + // ...otherwise we just return our positive int + return BigInt(`0x${slicedBuf.toString('hex')}`); +}