Skip to content

Commit

Permalink
WIP
Browse files Browse the repository at this point in the history
  • Loading branch information
benesjan committed Oct 16, 2023
1 parent 51aecb3 commit 4b9848c
Show file tree
Hide file tree
Showing 2 changed files with 51 additions and 200 deletions.
186 changes: 2 additions & 184 deletions yarn-project/acir-simulator/src/client/private_execution.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,6 @@ import {
ImportTestContractArtifact,
ParentContractArtifact,
PendingCommitmentsContractArtifact,
PrivateTokenAirdropContractArtifact,
StatefulTestContractArtifact,
TestContractArtifact,
TokenContractArtifact,
Expand Down Expand Up @@ -191,188 +190,7 @@ describe('Private Execution test suite', () => {
});
});

describe('private token airdrop contract', () => {
const contractAddress = defaultContractAddress;
const mockFirstNullifier = new Fr(1111);
let currentNoteIndex = 0n;

const buildNote = (amount: bigint, owner: AztecAddress, storageSlot = Fr.random()) => {
// WARNING: this is not actually how nonces are computed!
// For the purpose of this test we use a mocked firstNullifier and and a random number
// to compute the nonce. Proper nonces are only enforced later by the kernel/later circuits
// which are not relevant to this test. In practice, the kernel first squashes all transient
// noteHashes with their matching nullifiers. It then reorders the remaining "persistable"
// noteHashes. A TX's real first nullifier (generated by the initial kernel) and a noteHash's
// array index at the output of the final kernel/ordering circuit are used to derive nonce via:
// `hash(firstNullifier, noteHashIndex)`
const noteHashIndex = Math.floor(Math.random()); // mock index in TX's final newNoteHashes array
const nonce = computeCommitmentNonce(circuitsWasm, mockFirstNullifier, noteHashIndex);
const preimage = [new Fr(amount), owner.toField(), Fr.random()];
const innerNoteHash = Fr.fromBuffer(hash(preimage.map(p => p.toBuffer())));
return {
contractAddress,
storageSlot,
nonce,
preimage,
innerNoteHash,
siloedNullifier: new Fr(0),
index: currentNoteIndex++,
};
};

beforeEach(() => {
oracle.getCompleteAddress.mockImplementation((address: AztecAddress) => {
if (address.equals(owner)) return Promise.resolve(ownerCompleteAddress);
if (address.equals(recipient)) return Promise.resolve(recipientCompleteAddress);
throw new Error(`Unknown address ${address}`);
});

oracle.getFunctionArtifact.mockImplementation((_, selector) =>
Promise.resolve(
PrivateTokenAirdropContractArtifact.functions.find(f =>
selector.equals(FunctionSelector.fromNameAndParameters(f.name, f.parameters)),
)!,
),
);
});

it('should have an artifact for computing note hash and nullifier', async () => {
const storageSlot = Fr.random();
const note = buildNote(60n, owner, storageSlot);

// Should be the same as how we compute the values for the ValueNote in the Aztec.nr library.
const valueNoteHash = hashFields(note.preimage);
const innerNoteHash = hashFields([storageSlot, valueNoteHash]);
const siloedNoteHash = siloCommitment(circuitsWasm, contractAddress, innerNoteHash);
const uniqueSiloedNoteHash = computeUniqueCommitment(circuitsWasm, note.nonce, siloedNoteHash);
const innerNullifier = hashFields([uniqueSiloedNoteHash, ownerPk.low, ownerPk.high]);

const result = await acirSimulator.computeNoteHashAndNullifier(
contractAddress,
note.nonce,
storageSlot,
note.preimage,
);

expect(result).toEqual({
innerNoteHash,
siloedNoteHash,
uniqueSiloedNoteHash,
innerNullifier,
});
});

it('should a constructor with arguments that inserts notes', async () => {
const artifact = getFunctionArtifact(PrivateTokenAirdropContractArtifact, 'constructor');

const result = await runSimulator({ args: [140, owner], artifact });

expect(result.newNotes).toHaveLength(1);
const newNote = result.newNotes[0];
expect(newNote.storageSlot).toEqual(computeSlotForMapping(new Fr(1n), owner.toField(), circuitsWasm));

const newCommitments = result.callStackItem.publicInputs.newCommitments.filter(field => !field.equals(Fr.ZERO));
expect(newCommitments).toHaveLength(1);

const [commitment] = newCommitments;
expect(commitment).toEqual(
await acirSimulator.computeInnerNoteHash(contractAddress, newNote.storageSlot, newNote.preimage),
);
});

it('should run the mint function', async () => {
const artifact = getFunctionArtifact(PrivateTokenAirdropContractArtifact, 'mint');

const result = await runSimulator({ args: [140, owner], artifact });

expect(result.newNotes).toHaveLength(1);
const newNote = result.newNotes[0];
expect(newNote.storageSlot).toEqual(computeSlotForMapping(new Fr(1n), owner.toField(), circuitsWasm));

const newCommitments = result.callStackItem.publicInputs.newCommitments.filter(field => !field.equals(Fr.ZERO));
expect(newCommitments).toHaveLength(1);

const [commitment] = newCommitments;
expect(commitment).toEqual(
await acirSimulator.computeInnerNoteHash(contractAddress, newNote.storageSlot, newNote.preimage),
);
});

it('should run the transfer function', async () => {
const amountToTransfer = 100n;
const artifact = getFunctionArtifact(PrivateTokenAirdropContractArtifact, 'transfer');

const storageSlot = computeSlotForMapping(new Fr(1n), owner.toField(), circuitsWasm);
const recipientStorageSlot = computeSlotForMapping(new Fr(1n), recipient.toField(), circuitsWasm);

const notes = [buildNote(60n, owner, storageSlot), buildNote(80n, owner, storageSlot)];
oracle.getNotes.mockResolvedValue(notes);

const consumedNotes = await asyncMap(notes, ({ nonce, preimage }) =>
acirSimulator.computeNoteHashAndNullifier(contractAddress, nonce, storageSlot, preimage),
);
await insertLeaves(consumedNotes.map(n => n.siloedNoteHash));

const args = [amountToTransfer, recipient];
const result = await runSimulator({ args, artifact, msgSender: owner });

// The two notes were nullified
const newNullifiers = result.callStackItem.publicInputs.newNullifiers.filter(field => !field.equals(Fr.ZERO));
expect(newNullifiers).toHaveLength(consumedNotes.length);
expect(newNullifiers).toEqual(expect.arrayContaining(consumedNotes.map(n => n.innerNullifier)));

expect(result.newNotes).toHaveLength(2);
const [changeNote, recipientNote] = result.newNotes;
expect(recipientNote.storageSlot).toEqual(recipientStorageSlot);

const newCommitments = result.callStackItem.publicInputs.newCommitments.filter(field => !field.equals(Fr.ZERO));
expect(newCommitments).toHaveLength(2);

const [changeNoteCommitment, recipientNoteCommitment] = newCommitments;
expect(recipientNoteCommitment).toEqual(
await acirSimulator.computeInnerNoteHash(contractAddress, recipientStorageSlot, recipientNote.preimage),
);
expect(changeNoteCommitment).toEqual(
await acirSimulator.computeInnerNoteHash(contractAddress, storageSlot, changeNote.preimage),
);

expect(recipientNote.preimage[0]).toEqual(new Fr(amountToTransfer));
expect(changeNote.preimage[0]).toEqual(new Fr(40n));

const readRequests = result.callStackItem.publicInputs.readRequests.filter(field => !field.equals(Fr.ZERO));
expect(readRequests).toHaveLength(consumedNotes.length);
expect(readRequests).toEqual(expect.arrayContaining(consumedNotes.map(n => n.uniqueSiloedNoteHash)));
});

it('should be able to transfer with dummy notes', async () => {
const amountToTransfer = 100n;
const balance = 160n;
const artifact = getFunctionArtifact(PrivateTokenAirdropContractArtifact, 'transfer');

const storageSlot = computeSlotForMapping(new Fr(1n), owner.toField(), circuitsWasm);

const notes = [buildNote(balance, owner, storageSlot)];
oracle.getNotes.mockResolvedValue(notes);

const consumedNotes = await asyncMap(notes, ({ nonce, preimage }) =>
acirSimulator.computeNoteHashAndNullifier(contractAddress, nonce, storageSlot, preimage),
);
await insertLeaves(consumedNotes.map(n => n.siloedNoteHash));

const args = [amountToTransfer, recipient];
const result = await runSimulator({ args, artifact, msgSender: owner });

const newNullifiers = result.callStackItem.publicInputs.newNullifiers.filter(field => !field.equals(Fr.ZERO));
expect(newNullifiers).toEqual(consumedNotes.map(n => n.innerNullifier));

expect(result.newNotes).toHaveLength(2);
const [changeNote, recipientNote] = result.newNotes;
expect(recipientNote.preimage[0]).toEqual(new Fr(amountToTransfer));
expect(changeNote.preimage[0]).toEqual(new Fr(balance - amountToTransfer));
});
});

describe('stateful test contract contract', () => {
describe('stateful test contract', () => {
const contractAddress = defaultContractAddress;
const mockFirstNullifier = new Fr(1111);
let currentNoteIndex = 0n;
Expand Down Expand Up @@ -443,7 +261,7 @@ describe('Private Execution test suite', () => {
});
});

it('should a constructor with arguments that inserts notes', async () => {
it('should have a constructor with arguments that inserts notes', async () => {
const artifact = getFunctionArtifact(StatefulTestContractArtifact, 'constructor');

const result = await runSimulator({ args: [owner, 140], artifact });
Expand Down
65 changes: 49 additions & 16 deletions yarn-project/acir-simulator/src/public/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
HistoricBlockData,
L1_TO_L2_MSG_TREE_HEIGHT,
} from '@aztec/circuits.js';
import { computePublicDataTreeIndex } from '@aztec/circuits.js/abis';
import { pedersenPlookupCommitInputs } from '@aztec/circuits.js/barretenberg';
import { FunctionArtifact, FunctionSelector, encodeArguments } from '@aztec/foundation/abi';
import { AztecAddress } from '@aztec/foundation/aztec-address';
Expand All @@ -16,7 +17,6 @@ import { toBigInt } from '@aztec/foundation/serialize';
import {
ChildContractArtifact,
ParentContractArtifact,
PublicTokenContractArtifact,
TestContractArtifact,
TokenContractArtifact,
} from '@aztec/noir-contracts/artifacts';
Expand Down Expand Up @@ -54,22 +54,25 @@ describe('ACIR public execution simulator', () => {
executor = new PublicExecutor(publicState, publicContracts, commitmentsDb, blockData);
}, 10000);

describe('PublicToken contract', () => {
describe('Token contract', () => {
let recipient: AztecAddress;

beforeEach(() => {
recipient = AztecAddress.random();
});

describe('mint', () => {
it('should run the mint function', async () => {
it('should run the mint_public function', async () => {
const contractAddress = AztecAddress.random();
const mintArtifact = PublicTokenContractArtifact.functions.find(f => f.name === 'mint')!;
const mintArtifact = TokenContractArtifact.functions.find(f => f.name === 'mint_public')!;
const functionData = FunctionData.fromAbi(mintArtifact);
const args = encodeArguments(mintArtifact, [140, recipient]);

const mintAmount = 140n;
const args = encodeArguments(mintArtifact, [recipient, mintAmount]);

const msgSender = AztecAddress.random();
const callContext = CallContext.from({
msgSender: AztecAddress.random(),
msgSender,
storageContractAddress: contractAddress,
portalContractAddress: EthAddress.random(),
functionSelector: FunctionSelector.empty(),
Expand All @@ -81,27 +84,57 @@ describe('ACIR public execution simulator', () => {
publicContracts.getBytecode.mockResolvedValue(Buffer.from(mintArtifact.bytecode, 'base64'));

// Mock the old value for the recipient balance to be 20
const isMinter = new Fr(1n); // 1n means true
const previousBalance = new Fr(20n);
publicState.storageRead.mockResolvedValue(previousBalance);
const previousTotalSupply = new Fr(previousBalance.value + 100n);
publicState.storageRead
.mockResolvedValueOnce(isMinter) // reading whether msg_sender is minter
.mockResolvedValueOnce(previousBalance) // reading user's balance
.mockResolvedValueOnce(previousTotalSupply); // reading total supply

const execution: PublicExecution = { contractAddress, functionData, args, callContext };
const result = await executor.simulate(execution, GlobalVariables.empty());

const expectedBalance = new Fr(160n);
expect(result.returnValues[0]).toEqual(expectedBalance);
expect(result.returnValues[0]).toEqual(new Fr(1n));

const recipientBalanceStorageSlot = computeSlotForMapping(new Fr(6n), recipient.toField(), circuitsWasm);
const totalSupplyStorageSlot = new Fr(4n);

const storageSlot = computeSlotForMapping(new Fr(1n), recipient.toField(), circuitsWasm);
const expectedBalance = new Fr(previousBalance.value + mintAmount);
const expectedTotalSupply = new Fr(previousTotalSupply.value + mintAmount);
// There should be 2 storage updates, one for the recipient's balance and one for the total supply
expect(result.contractStorageUpdateRequests).toEqual([
{ storageSlot, oldValue: previousBalance, newValue: expectedBalance, sideEffectCounter: 1 }, // 0th is a read
{
storageSlot: recipientBalanceStorageSlot,
oldValue: previousBalance,
newValue: expectedBalance,
sideEffectCounter: 3,
},
{
storageSlot: totalSupplyStorageSlot,
oldValue: previousTotalSupply,
newValue: expectedTotalSupply,
sideEffectCounter: 4,
},
]);

expect(result.contractStorageReads).toEqual([]);
const mintersStorageSlot = new Fr(2n);
const isMinterStorageSlot = computeSlotForMapping(mintersStorageSlot, msgSender.toField(), circuitsWasm);
// Note: There is only 1 storage read (for the isMinter value) because the other 2 reads get overwritten by
// the updates
expect(result.contractStorageReads).toEqual([
{
storageSlot: isMinterStorageSlot,
currentValue: isMinter,
sideEffectCounter: 0,
},
]);
});
});

describe('transfer', () => {
let contractAddress: AztecAddress;
let artifact: FunctionArtifact;
let transferArtifact: FunctionArtifact;
let functionData: FunctionData;
let args: Fr[];
let sender: AztecAddress;
Expand All @@ -112,9 +145,9 @@ describe('ACIR public execution simulator', () => {

beforeEach(() => {
contractAddress = AztecAddress.random();
artifact = PublicTokenContractArtifact.functions.find(f => f.name === 'transfer')!;
transferArtifact = TokenContractArtifact.functions.find(f => f.name === 'transfer_public')!;
functionData = new FunctionData(FunctionSelector.empty(), false, false, false);
args = encodeArguments(artifact, [140, recipient]);
args = encodeArguments(transferArtifact, [140, recipient]);
sender = AztecAddress.random();

callContext = CallContext.from({
Expand All @@ -130,7 +163,7 @@ describe('ACIR public execution simulator', () => {
recipientStorageSlot = computeSlotForMapping(new Fr(1n), recipient.toField(), circuitsWasm);
senderStorageSlot = computeSlotForMapping(new Fr(1n), Fr.fromBuffer(sender.toBuffer()), circuitsWasm);

publicContracts.getBytecode.mockResolvedValue(Buffer.from(artifact.bytecode, 'base64'));
publicContracts.getBytecode.mockResolvedValue(Buffer.from(transferArtifact.bytecode, 'base64'));

execution = { contractAddress, functionData, args, callContext };
});
Expand Down

0 comments on commit 4b9848c

Please sign in to comment.