Skip to content

Commit

Permalink
chore(avm): add/improve tests for AvmContext, tagged memory, etc (Azt…
Browse files Browse the repository at this point in the history
…ecProtocol#4484)

Missing:
* Cannot compare/test journals due to cyclic structure.
* Field division is broken.
  • Loading branch information
fcarreiro authored Feb 7, 2024
1 parent d566c74 commit 2fccdf2
Show file tree
Hide file tree
Showing 10 changed files with 353 additions and 301 deletions.
2 changes: 2 additions & 0 deletions yarn-project/simulator/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -45,10 +45,12 @@
"@jest/globals": "^29.5.0",
"@types/jest": "^29.5.0",
"@types/levelup": "^5.1.3",
"@types/lodash.merge": "^4.6.9",
"@types/memdown": "^3.0.2",
"@types/node": "^18.7.23",
"jest": "^29.5.0",
"jest-mock-extended": "^3.0.4",
"lodash.merge": "^4.6.2",
"ts-jest": "^29.1.0",
"ts-node": "^10.9.1",
"typescript": "^5.0.4",
Expand Down
57 changes: 47 additions & 10 deletions yarn-project/simulator/src/avm/avm_context.test.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,55 @@
// import { AztecAddress, Fr } from '@aztec/circuits.js';
// import { initContext } from './fixtures/index.js';
import { AztecAddress, Fr } from '@aztec/circuits.js';

import { allSameExcept, initContext } from './fixtures/index.js';

describe('Avm Context', () => {
it('New call should fork context correctly', () => {
// const context = initContext();
// const newAddress = AztecAddress.random();
// const newCalldata = [new Fr(1), new Fr(2)];
// const newContext = context.createNestedContractCallContext(newAddress, newCalldata);
const context = initContext();
context.machineState.pc = 20;

const newAddress = AztecAddress.random();
const newCalldata = [new Fr(1), new Fr(2)];
const newContext = context.createNestedContractCallContext(newAddress, newCalldata);

expect(newContext.environment).toEqual(
allSameExcept(context.environment, {
address: newAddress,
storageAddress: newAddress,
calldata: newCalldata,
isStaticCall: false,
}),
);
expect(newContext.machineState).toEqual(
allSameExcept(context.machineState, {
pc: 0,
}),
);
// FIXME: I can't get this to work.
// expect(newContext.worldState).toEqual(context.worldState.fork());
});

it('New static call should fork context correctly', () => {
// const context = initContext();
// const newAddress = AztecAddress.random();
// const newCalldata = [new Fr(1), new Fr(2)];
// const newContext = context.createNestedContractStaticCallContext(newAddress, newCalldata);
const context = initContext();
context.machineState.pc = 20;

const newAddress = AztecAddress.random();
const newCalldata = [new Fr(1), new Fr(2)];
const newContext = context.createNestedContractStaticCallContext(newAddress, newCalldata);

expect(newContext.environment).toEqual(
allSameExcept(context.environment, {
address: newAddress,
storageAddress: newAddress,
calldata: newCalldata,
isStaticCall: true,
}),
);
expect(newContext.machineState).toEqual(
allSameExcept(context.machineState, {
pc: 0,
}),
);
// FIXME: I can't get this to work.
// expect(newContext.worldState).toEqual(context.worldState.fork());
});
});
53 changes: 23 additions & 30 deletions yarn-project/simulator/src/avm/avm_execution_environment.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Fr } from '@aztec/foundation/fields';

import { initExecutionEnvironment } from './fixtures/index.js';
import { allSameExcept, initExecutionEnvironment } from './fixtures/index.js';

describe('Execution Environment', () => {
const newAddress = new Fr(123456n);
Expand All @@ -10,46 +10,39 @@ describe('Execution Environment', () => {
const executionEnvironment = initExecutionEnvironment();
const newExecutionEnvironment = executionEnvironment.deriveEnvironmentForNestedCall(newAddress, calldata);

allTheSameExcept(executionEnvironment, newExecutionEnvironment, {
address: newAddress,
storageAddress: newAddress,
calldata,
});
expect(newExecutionEnvironment).toEqual(
allSameExcept(executionEnvironment, {
address: newAddress,
storageAddress: newAddress,
calldata,
}),
);
});

it('New delegate call should fork execution environment correctly', () => {
const executionEnvironment = initExecutionEnvironment();
const newExecutionEnvironment = executionEnvironment.newDelegateCall(newAddress, calldata);

allTheSameExcept(executionEnvironment, newExecutionEnvironment, {
address: newAddress,
isDelegateCall: true,
calldata,
});
expect(newExecutionEnvironment).toEqual(
allSameExcept(executionEnvironment, {
address: newAddress,
isDelegateCall: true,
calldata,
}),
);
});

it('New static call call should fork execution environment correctly', () => {
const executionEnvironment = initExecutionEnvironment();
const newExecutionEnvironment = executionEnvironment.deriveEnvironmentForNestedStaticCall(newAddress, calldata);

allTheSameExcept(executionEnvironment, newExecutionEnvironment, {
address: newAddress,
storageAddress: newAddress,
isStaticCall: true,
calldata,
});
expect(newExecutionEnvironment).toEqual(
allSameExcept(executionEnvironment, {
address: newAddress,
storageAddress: newAddress,
isStaticCall: true,
calldata,
}),
);
});
});

/**
* Check all properties of one object are the same, except for the specified differentProperties
*/
function allTheSameExcept(referenceObject: any, comparingObject: any, differentProperties: Record<string, any>): void {
for (const key in referenceObject) {
if (Object.keys(differentProperties).includes(key)) {
expect(comparingObject[key]).toEqual(differentProperties[key]);
} else {
expect(comparingObject[key]).toEqual(referenceObject[key]);
}
}
}
190 changes: 179 additions & 11 deletions yarn-project/simulator/src/avm/avm_memory_types.test.ts
Original file line number Diff line number Diff line change
@@ -1,22 +1,190 @@
import { Field, Uint8 } from './avm_memory_types.js';
import { Field, TaggedMemory, Uint8, Uint16, Uint32, Uint64, Uint128 } from './avm_memory_types.js';

// TODO: complete
describe('Uint8', () => {
it('Unsigned 8 max value', () => {
expect(new Uint8(255).toBigInt()).toEqual(255n);
describe('TaggedMemory', () => {
it('Elements should be undefined after construction', () => {
const mem = new TaggedMemory();
expect(mem.get(10)).toBe(undefined);
});

it('Unsigned 8 bit add', () => {
expect(new Uint8(50).add(new Uint8(20))).toEqual(new Uint8(70));
it(`Should set and get integral types`, () => {
const mem = new TaggedMemory();
mem.set(10, new Uint8(5));
expect(mem.get(10)).toStrictEqual(new Uint8(5));
});

it('Unsigned 8 bit add wraps', () => {
expect(new Uint8(200).add(new Uint8(100))).toEqual(new Uint8(44));
it(`Should set and get field elements`, () => {
const mem = new TaggedMemory();
mem.set(10, new Field(5));
expect(mem.get(10)).toStrictEqual(new Field(5));
});

it(`Should getSlice beyond current size`, () => {
const mem = new TaggedMemory();
const val = [new Field(5), new Field(6)];

mem.setSlice(10, val);

expect(mem.getSlice(10, /*size=*/ 4)).toEqual([...val, undefined, undefined]);
});

it(`Should set and get slices`, () => {
const mem = new TaggedMemory();
const val = [new Field(5), new Field(6)];

mem.setSlice(10, val);

expect(mem.getSlice(10, /*size=*/ 2)).toStrictEqual(val);
});
});

type IntegralClass = typeof Uint8 | typeof Uint16 | typeof Uint32 | typeof Uint64 | typeof Uint128;
describe.each([Uint8, Uint16, Uint32, Uint64, Uint128])('Integral Types', (clsValue: IntegralClass) => {
it(`Should construct a new ${clsValue.name} from a number`, () => {
const x = new clsValue(5);
expect(x.toBigInt()).toStrictEqual(5n);
});

it(`Should construct a new ${clsValue.name} from a bigint`, () => {
const x = new clsValue(5n);
expect(x.toBigInt()).toStrictEqual(5n);
});

it(`Should build a new ${clsValue.name}`, () => {
const x = new clsValue(5);
const newX = x.build(10n);
expect(newX).toStrictEqual(new clsValue(10n));
});

it(`Should add two ${clsValue.name} correctly`, () => {
const a = new clsValue(5);
const b = new clsValue(10);
const result = a.add(b);
expect(result).toStrictEqual(new clsValue(15n));
});

it(`Should subtract two ${clsValue.name} correctly`, () => {
const a = new clsValue(10);
const b = new clsValue(5);
const result = a.sub(b);
expect(result).toStrictEqual(new clsValue(5n));
});

it(`Should multiply two ${clsValue.name} correctly`, () => {
const a = new clsValue(5);
const b = new clsValue(10);
const result = a.mul(b);
expect(result).toStrictEqual(new clsValue(50n));
});

it(`Should divide two ${clsValue.name} correctly`, () => {
const a = new clsValue(10);
const b = new clsValue(5);
const result = a.div(b);
expect(result).toStrictEqual(new clsValue(2n));
});

it('Should shift right ${clsValue.name} correctly', () => {
const uintA = new clsValue(10);
const result = uintA.shr(new clsValue(1n));
expect(result).toEqual(new clsValue(5n));
});

it('Should shift left ${clsValue.name} correctly', () => {
const uintA = new clsValue(10);
const result = uintA.shl(new clsValue(1n));
expect(result).toEqual(new clsValue(20n));
});

it('Should and two ${clsValue.name} correctly', () => {
const uintA = new clsValue(10);
const uintB = new clsValue(5);
const result = uintA.and(uintB);
expect(result).toEqual(new clsValue(0n));
});

it('Should or two ${clsValue.name} correctly', () => {
const uintA = new clsValue(10);
const uintB = new clsValue(5);
const result = uintA.or(uintB);
expect(result).toEqual(new clsValue(15n));
});

it('Should xor two ${clsValue.name} correctly', () => {
const uintA = new clsValue(10);
const uintB = new clsValue(5);
const result = uintA.xor(uintB);
expect(result).toEqual(new clsValue(15n));
});

it(`Should check equality of two ${clsValue.name} correctly`, () => {
const a = new clsValue(5);
const b = new clsValue(5);
const c = new clsValue(10);
expect(a.equals(b)).toBe(true);
expect(a.equals(c)).toBe(false);
});

it(`Should check if one ${clsValue.name} is less than another correctly`, () => {
const a = new clsValue(5);
const b = new clsValue(10);
expect(a.lt(b)).toBe(true);
expect(b.lt(a)).toBe(false);
});
});

describe('Field', () => {
it('Add correctly without wrapping', () => {
expect(new Field(27).add(new Field(48))).toEqual(new Field(75));
it(`Should build a new Field`, () => {
const field = new Field(5);
const newField = field.build(10n);
expect(newField.toBigInt()).toStrictEqual(10n);
});

it(`Should add two Fields correctly`, () => {
const field1 = new Field(5);
const field2 = new Field(10);
const result = field1.add(field2);
expect(result).toStrictEqual(new Field(15n));
});

it(`Should subtract two Fields correctly`, () => {
const field1 = new Field(10);
const field2 = new Field(5);
const result = field1.sub(field2);
expect(result).toStrictEqual(new Field(5n));
});

it(`Should multiply two Fields correctly`, () => {
const field1 = new Field(5);
const field2 = new Field(10);
const result = field1.mul(field2);
expect(result).toStrictEqual(new Field(50n));
});

// FIXME: field division is wrong
it.skip(`Should divide two Fields correctly`, () => {
const field1 = new Field(10);
const field2 = new Field(5);
const result = field1.div(field2);
expect(result).toStrictEqual(new Field(2n));
});

it(`Should check equality of two Fields correctly`, () => {
const field1 = new Field(5);
const field2 = new Field(5);
const field3 = new Field(10);
expect(field1.equals(field2)).toBe(true);
expect(field1.equals(field3)).toBe(false);
});

it(`Should check if one Field is less than another correctly`, () => {
const field1 = new Field(5);
const field2 = new Field(10);
expect(field1.lt(field2)).toBe(true);
expect(field2.lt(field1)).toBe(false);
});

it(`Should convert Field to BigInt correctly`, () => {
const field = new Field(5);
expect(field.toBigInt()).toStrictEqual(5n);
});
});
Loading

0 comments on commit 2fccdf2

Please sign in to comment.