Skip to content

Commit

Permalink
chore: add signed int deserialization to decoder (#9557)
Browse files Browse the repository at this point in the history
  • Loading branch information
sklppy88 authored Oct 31, 2024
1 parent 8891ead commit 0435d00
Show file tree
Hide file tree
Showing 4 changed files with 184 additions and 6 deletions.
109 changes: 108 additions & 1 deletion yarn-project/foundation/src/abi/decoder.test.ts
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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);
});
});
14 changes: 9 additions & 5 deletions yarn-project/foundation/src/abi/decoder.ts
Original file line number Diff line number Diff line change
@@ -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.
Expand All @@ -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[]) {}
Expand All @@ -24,11 +23,16 @@ 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': {
Expand Down
39 changes: 39 additions & 0 deletions yarn-project/foundation/src/abi/utils.test.ts
Original file line number Diff line number Diff line change
@@ -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);
});
});
28 changes: 28 additions & 0 deletions yarn-project/foundation/src/abi/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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')}`);
}

0 comments on commit 0435d00

Please sign in to comment.