From 39e88bff5bb8488a7d0924918586a253019a397a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicol=C3=A1s=20Venturo?= Date: Thu, 30 May 2024 20:45:22 +0000 Subject: [PATCH 01/75] Adapt public immutable --- .../aztec/src/state_vars/public_immutable.nr | 58 +++++----- .../src/state_vars/public_immutable/test.nr | 105 ++++++++++++++++++ .../aztec-nr/aztec/src/test/mocks.nr | 1 + .../aztec/src/test/mocks/mock_struct.nr | 36 ++++++ 4 files changed, 175 insertions(+), 25 deletions(-) create mode 100644 noir-projects/aztec-nr/aztec/src/state_vars/public_immutable/test.nr create mode 100644 noir-projects/aztec-nr/aztec/src/test/mocks/mock_struct.nr diff --git a/noir-projects/aztec-nr/aztec/src/state_vars/public_immutable.nr b/noir-projects/aztec-nr/aztec/src/state_vars/public_immutable.nr index 5d9ad2b7e30..b35f4eae1cf 100644 --- a/noir-projects/aztec-nr/aztec/src/state_vars/public_immutable.nr +++ b/noir-projects/aztec-nr/aztec/src/state_vars/public_immutable.nr @@ -1,6 +1,11 @@ -use crate::{context::PublicContext, oracle::{storage::{storage_read, storage_write}}, state_vars::storage::Storage}; +use crate::{ + context::PublicContext, public_storage, oracle::{storage::{storage_read, storage_write}}, + state_vars::storage::Storage +}; use dep::protocol_types::{constants::INITIALIZATION_SLOT_SEPARATOR, traits::{Deserialize, Serialize}}; +mod test; + // Just like SharedImmutable but without the ability to read from private functions. // docs:start:public_immutable_struct struct PublicImmutable { @@ -13,53 +18,56 @@ impl Storage for PublicImmutable {} impl PublicImmutable { // docs:start:public_immutable_struct_new - pub fn new( - // Note: Passing the contexts to new(...) just to have an interface compatible with a Map. - context: Context, - storage_slot: Field - ) -> Self { + pub fn new(context: Context, storage_slot: Field) -> Self { assert(storage_slot != 0, "Storage slot 0 not allowed. Storage slots must start from 1."); PublicImmutable { context, storage_slot } } // docs:end:public_immutable_struct_new + + fn get_initialization_slot(self) -> Field { + INITIALIZATION_SLOT_SEPARATOR + self.storage_slot + } } impl PublicImmutable { // docs:start:public_immutable_struct_write pub fn initialize(self, value: T) where T: Serialize { - // TODO(#4738): Uncomment the following assert - // assert( - // self.context.public.unwrap_unchecked().is_deployment(), "PublicImmutable can only be initialized during contract deployment" - // ); - - // We check that the struct is not yet initialized by checking if the initialization slot is 0 - let initialization_slot = INITIALIZATION_SLOT_SEPARATOR + self.storage_slot; - let fields_read: [Field; 1] = storage_read(initialization_slot); - assert(fields_read[0] == 0, "PublicImmutable already initialized"); + assert(!self.is_initialized(), "PublicImmutable already initialized"); // We populate the initialization slot with a non-zero value to indicate that the struct is initialized - storage_write(initialization_slot, [0xdead]); + public_storage::write(self.get_initialization_slot(), 0xdead); - let fields_write = T::serialize(value); - storage_write(self.storage_slot, fields_write); + // And we then store the actual value + public_storage::write(self.storage_slot, value); } // docs:end:public_immutable_struct_write // Note that we don't access the context, but we do call oracles that are only available in public // docs:start:public_immutable_struct_read pub fn read(self) -> T where T: Deserialize { - let fields = storage_read(self.storage_slot); - T::deserialize(fields) + assert(self.is_initialized(), "PublicImmutable not initialized"); + public_storage::read(self.storage_slot) } // docs:end:public_immutable_struct_read + + pub fn is_initialized(self) -> bool { + let init_slot_contents: Field = public_storage::read(self.get_initialization_slot()); + init_slot_contents != 0 + } } impl PublicImmutable { + // Note that this is the exact same implementation as for public execution, though it might change in the future + // since unconstrained execution might not rely on the same oracles as used for public execution (which + // transpile to AVM opcodes). + pub fn read(self) -> T where T: Deserialize { - // Note that this is the exact same implementation as for public execution, though it might change in the future - // since unconstrained execution might not rely on the same oracles as used for public execution (which - // transpile to AVM opcodes). - let fields = storage_read(self.storage_slot); - T::deserialize(fields) + assert(self.is_initialized(), "PublicImmutable not initialized"); + public_storage::read(self.storage_slot) + } + + pub fn is_initialized(self) -> bool { + let init_slot_contents: Field = public_storage::read(self.get_initialization_slot()); + init_slot_contents != 0 } } diff --git a/noir-projects/aztec-nr/aztec/src/state_vars/public_immutable/test.nr b/noir-projects/aztec-nr/aztec/src/state_vars/public_immutable/test.nr new file mode 100644 index 00000000000..86c5dc49a18 --- /dev/null +++ b/noir-projects/aztec-nr/aztec/src/state_vars/public_immutable/test.nr @@ -0,0 +1,105 @@ +use crate::{context::PublicContext, state_vars::public_immutable::PublicImmutable}; +use crate::test::{helpers::context_builder::ContextBuilder, mocks::mock_struct::MockStruct}; +use dep::std::test::OracleMock; +use dep::protocol_types::traits::Serialize; + +fn setup() -> PublicImmutable { + let mut context = ContextBuilder::new().public(); + let storage_slot = 7; + + PublicImmutable::new(&mut context, storage_slot) +} + +impl PublicImmutable { + fn mock_initialized_storage(self) { + let _ = OracleMock::mock("storageRead").with_params((self.get_initialization_slot(), 1)).returns([1]).times(1); + } + + fn mock_uninitialized_storage(self) { + let _ = OracleMock::mock("storageRead").with_params((self.get_initialization_slot(), 1)).returns([0]).times(1); + } + + fn mock_initialize_write(self) -> OracleMock { + // Unfortunately we can't only pass the storage slot to with_params, so we must know the initialization value. + OracleMock::mock("storageWrite").with_params((self.get_initialization_slot(), [0xdead])).returns([0; 1]) + } + + fn assert_initialize_write(self, mock: OracleMock) { + assert(mock.get_last_params() != (self.storage_slot, [0])); + } + + fn mock_value_write(self, value: MockStruct) -> OracleMock where MockStruct: Serialize { + // Unfortunately we can't only pass the storage slot to with_params, so we must know what will be stored as + // well. + OracleMock::mock("storageWrite").with_params((self.storage_slot, value.serialize())).returns([0; N]) + } + + fn mock_value_read(self, value: MockStruct) where MockStruct: Serialize { + // TBD https://github.com/noir-lang/noir/issues/4633: replace 2 with N + let _ = OracleMock::mock("storageRead").with_params((self.storage_slot, 2)).returns(value.serialize()).times(1); + } + + fn assert_value_write(self, mock: OracleMock, value: MockStruct) { + assert_eq(mock.get_last_params(), (self.storage_slot, value.serialize())); + } +} + +#[test] +fn test_is_initialized() { + let state_var = setup(); + + state_var.mock_uninitialized_storage(); + assert_eq(state_var.is_initialized(), false); + + state_var.mock_initialized_storage(); + assert_eq(state_var.is_initialized(), true); +} + +#[test] +fn test_initialize_uninitialized() { + let state_var = setup(); + + let value = MockStruct::new(5, 6); + + state_var.mock_uninitialized_storage(); + + let init_mock = state_var.mock_initialize_write(); + let value_mock = state_var.mock_value_write(value); + + state_var.initialize(value); + + state_var.assert_initialize_write(init_mock); + state_var.assert_value_write(value_mock, value); +} + +#[test(should_fail_with="PublicImmutable already initialized")] +fn test_initialize_already_initialized() { + let state_var = setup(); + + let value = MockStruct::new(5, 6); + + state_var.mock_initialized_storage(); + + state_var.initialize(value); +} + +#[test] +fn test_read_initialized() { + let state_var = setup(); + + let value = MockStruct::new(5, 6); + + state_var.mock_initialized_storage(); + state_var.mock_value_read(value); + + assert_eq(state_var.read(), value); +} + +#[test(should_fail_with="PublicImmutable not initialized")] +fn test_read_uninitialized() { + let state_var = setup(); + + state_var.mock_uninitialized_storage(); + + let _ = state_var.read(); +} diff --git a/noir-projects/aztec-nr/aztec/src/test/mocks.nr b/noir-projects/aztec-nr/aztec/src/test/mocks.nr index fb8ef27c7bf..148cc284a9f 100644 --- a/noir-projects/aztec-nr/aztec/src/test/mocks.nr +++ b/noir-projects/aztec-nr/aztec/src/test/mocks.nr @@ -1 +1,2 @@ mod mock_note; +mod mock_struct; diff --git a/noir-projects/aztec-nr/aztec/src/test/mocks/mock_struct.nr b/noir-projects/aztec-nr/aztec/src/test/mocks/mock_struct.nr new file mode 100644 index 00000000000..1a0ed1a304e --- /dev/null +++ b/noir-projects/aztec-nr/aztec/src/test/mocks/mock_struct.nr @@ -0,0 +1,36 @@ +use dep::protocol_types::traits::{Eq, Serialize, Deserialize}; + +struct MockStruct { + a: Field, + b: Field, +} + +impl MockStruct { + fn new(a: Field, b: Field) -> Self { + Self { a, b } + } +} + +impl Eq for MockStruct { + fn eq(self, other: Self) -> bool { + (self.a == other.a) & (self.b == other.b) + } +} + +impl Serialize<2> for MockStruct { + fn serialize(self) -> [Field; 2] { + [self.a, self.b] + } +} + +impl Deserialize<2> for MockStruct { + fn deserialize(fields: [Field; 2]) -> Self { + Self { a: fields[0], b: fields[1] } + } +} + +#[test] +fn test_serde() { + let val = MockStruct::new(5, 6); + assert_eq(val, MockStruct::deserialize(val.serialize())); +} From de4dfef19afd9960b3d85c2e5ce70c206f95f727 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicol=C3=A1s=20Venturo?= Date: Fri, 31 May 2024 03:23:51 +0000 Subject: [PATCH 02/75] Add comments to private imnmutable --- .../aztec/src/state_vars/private_immutable.nr | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/noir-projects/aztec-nr/aztec/src/state_vars/private_immutable.nr b/noir-projects/aztec-nr/aztec/src/state_vars/private_immutable.nr index 3c70465a8d8..8e3fa474323 100644 --- a/noir-projects/aztec-nr/aztec/src/state_vars/private_immutable.nr +++ b/noir-projects/aztec-nr/aztec/src/state_vars/private_immutable.nr @@ -50,7 +50,8 @@ impl PrivateImmutable { ovpk_m: GrumpkinPoint, ivpk_m: GrumpkinPoint ) where Note: NoteInterface { - // Nullify the storage slot. + // Nullify the storage slot - since we always push the same nullifier, this can only be done once, as further + // attempts to do it will result in the transaction being reverted by the sequencer. let nullifier = self.compute_initialization_nullifier(); self.context.push_new_nullifier(nullifier, 0); @@ -60,8 +61,10 @@ impl PrivateImmutable { // docs:start:get_note pub fn get_note(self) -> Note where Note: NoteInterface { - let storage_slot = self.storage_slot; - get_note(self.context, storage_slot) + // We do not need to check for initialization because we'll only be able to read a note and prove its inclusion + // if one was actually created, and this can be done during initialization. Therefore, a successful read implies + // initialization. + get_note(self.context, self.storage_slot) } // docs:end:get_note } @@ -77,8 +80,8 @@ impl PrivateImmutable { // view_note does not actually use the context, but it calls oracles that are only available in private // docs:start:view_note unconstrained pub fn view_note(self) -> Note where Note: NoteInterface { - let mut options = NoteViewerOptions::new(); - view_notes(self.storage_slot, options.set_limit(1))[0].unwrap() + let options = NoteViewerOptions::new(); + view_notes(self.storage_slot, options)[0].unwrap() } // docs:end:view_note } From 8c5bdf1a62ef263953afc7087297cac2887652c2 Mon Sep 17 00:00:00 2001 From: thunkar Date: Mon, 3 Jun 2024 17:14:35 +0000 Subject: [PATCH 03/75] TXE --- .../src/state_vars/public_immutable/test.nr | 8 +- yarn-project/package.json | 1 + yarn-project/txe/.eslintrc.cjs | 1 + yarn-project/txe/package.json | 74 ++++++ yarn-project/txe/src/bin/index.ts | 28 ++ yarn-project/txe/src/http_rpc_server/index.ts | 29 ++ yarn-project/txe/src/index.ts | 1 + .../txe/src/txe_service/txe_service.ts | 251 ++++++++++++++++++ yarn-project/txe/src/util/encoding.ts | 29 ++ yarn-project/txe/tsconfig.json | 9 + yarn-project/yarn.lock | 20 ++ 11 files changed, 444 insertions(+), 7 deletions(-) create mode 100644 yarn-project/txe/.eslintrc.cjs create mode 100644 yarn-project/txe/package.json create mode 100644 yarn-project/txe/src/bin/index.ts create mode 100644 yarn-project/txe/src/http_rpc_server/index.ts create mode 100644 yarn-project/txe/src/index.ts create mode 100644 yarn-project/txe/src/txe_service/txe_service.ts create mode 100644 yarn-project/txe/src/util/encoding.ts create mode 100644 yarn-project/txe/tsconfig.json diff --git a/noir-projects/aztec-nr/aztec/src/state_vars/public_immutable/test.nr b/noir-projects/aztec-nr/aztec/src/state_vars/public_immutable/test.nr index 86c5dc49a18..65fe0a7a253 100644 --- a/noir-projects/aztec-nr/aztec/src/state_vars/public_immutable/test.nr +++ b/noir-projects/aztec-nr/aztec/src/state_vars/public_immutable/test.nr @@ -61,15 +61,9 @@ fn test_initialize_uninitialized() { let value = MockStruct::new(5, 6); - state_var.mock_uninitialized_storage(); - - let init_mock = state_var.mock_initialize_write(); - let value_mock = state_var.mock_value_write(value); - state_var.initialize(value); - state_var.assert_initialize_write(init_mock); - state_var.assert_value_write(value_mock, value); + assert(state_var.read() == value); } #[test(should_fail_with="PublicImmutable already initialized")] diff --git a/yarn-project/package.json b/yarn-project/package.json index fa08f05b063..388c8f4d6df 100644 --- a/yarn-project/package.json +++ b/yarn-project/package.json @@ -51,6 +51,7 @@ "sequencer-client", "scripts", "types", + "txe", "world-state" ], "prettier": "@aztec/foundation/prettier", diff --git a/yarn-project/txe/.eslintrc.cjs b/yarn-project/txe/.eslintrc.cjs new file mode 100644 index 00000000000..e659927475c --- /dev/null +++ b/yarn-project/txe/.eslintrc.cjs @@ -0,0 +1 @@ +module.exports = require('@aztec/foundation/eslint'); diff --git a/yarn-project/txe/package.json b/yarn-project/txe/package.json new file mode 100644 index 00000000000..c3c56084a64 --- /dev/null +++ b/yarn-project/txe/package.json @@ -0,0 +1,74 @@ +{ + "name": "@aztec/txe", + "version": "0.0.0", + "type": "module", + "exports": "./dest/index.js", + "bin": "./dest/bin/index.js", + "typedocOptions": { + "entryPoints": [ + "./src/index.ts" + ], + "name": "TXE", + "tsconfig": "./tsconfig.json" + }, + "scripts": { + "build": "yarn clean && tsc -b", + "build:dev": "tsc -b --watch", + "clean": "rm -rf ./dest .tsbuildinfo", + "formatting": "run -T prettier --check ./src && run -T eslint ./src", + "formatting:fix": "run -T eslint --fix ./src && run -T prettier -w ./src", + "test": "NODE_NO_WARNINGS=1 node --experimental-vm-modules ../node_modules/.bin/jest --passWithNoTests", + "start": "DEBUG='aztec:*' && node ./dest/bin/index.js" + }, + "inherits": [ + "../package.common.json" + ], + "jest": { + "moduleNameMapper": { + "^(\\.{1,2}/.*)\\.[cm]?js$": "$1" + }, + "testRegex": "./src/.*\\.test\\.(js|mjs|ts)$", + "rootDir": "./src", + "workerThreads": true, + "transform": { + "^.+\\.tsx?$": [ + "@swc/jest" + ] + }, + "extensionsToTreatAsEsm": [ + ".ts" + ], + "reporters": [ + [ + "default", + { + "summaryThreshold": 9999 + } + ] + ] + }, + "dependencies": { + "@aztec/circuits.js": "workspace:^", + "@aztec/foundation": "workspace:^", + "@aztec/types": "workspace:^", + "@aztec/world-state": "workspace:^" + }, + "devDependencies": { + "@jest/globals": "^29.5.0", + "@types/jest": "^29.5.0", + "@types/node": "^18.7.23", + "jest": "^29.5.0", + "jest-mock-extended": "^3.0.3", + "ts-node": "^10.9.1", + "typescript": "^5.0.4" + }, + "files": [ + "dest", + "src", + "!*.test.*" + ], + "types": "./dest/index.d.ts", + "engines": { + "node": ">=18" + } +} diff --git a/yarn-project/txe/src/bin/index.ts b/yarn-project/txe/src/bin/index.ts new file mode 100644 index 00000000000..9787db5de83 --- /dev/null +++ b/yarn-project/txe/src/bin/index.ts @@ -0,0 +1,28 @@ +#!/usr/bin/env -S node --no-warnings +import { createDebugLogger } from '@aztec/foundation/log'; + +import { startTXEHttpServer } from '../index.js'; +import { TXE, TXEService } from '../txe_service/txe_service.js'; + +const { TXE_PORT = 8080 } = process.env; + +const logger = createDebugLogger('aztec:txe_service'); + +/** + * Create and start a new PXE HTTP Server + */ +async function main() { + logger.info(`Setting up TXE...`); + + const txe = await TXE.init(logger); + const txeService = new TXEService(txe); + + startTXEHttpServer(txeService, TXE_PORT); + + logger.info(`TXE listening on port ${TXE_PORT}`); +} + +main().catch(err => { + logger.error(err); + process.exit(1); +}); diff --git a/yarn-project/txe/src/http_rpc_server/index.ts b/yarn-project/txe/src/http_rpc_server/index.ts new file mode 100644 index 00000000000..f861fb446d0 --- /dev/null +++ b/yarn-project/txe/src/http_rpc_server/index.ts @@ -0,0 +1,29 @@ +import { JsonRpcServer } from '@aztec/foundation/json-rpc/server'; + +import http from 'http'; + +import { type TXEService } from '../txe_service/txe_service.js'; + +/** + * Wraps an instance of Private eXecution Environment (TXE) implementation to a JSON RPC HTTP interface. + * @returns A new instance of the HTTP server. + */ +export function createTXERpcServer(txeService: TXEService): JsonRpcServer { + return new JsonRpcServer(txeService, {}, {}, ['start', 'stop']); +} + +/** + * Creates an http server that forwards calls to the TXE and starts it on the given port. + * @param txeService - TXE that answers queries to the created HTTP server. + * @param port - Port to listen in. + * @returns A running http server. + */ +export function startTXEHttpServer(txeService: TXEService, port: string | number): http.Server { + const txeServer = createTXERpcServer(txeService); + + const app = txeServer.getApp(); + const httpServer = http.createServer(app.callback()); + httpServer.listen(port); + + return httpServer; +} diff --git a/yarn-project/txe/src/index.ts b/yarn-project/txe/src/index.ts new file mode 100644 index 00000000000..6f83d1f63b2 --- /dev/null +++ b/yarn-project/txe/src/index.ts @@ -0,0 +1 @@ +export * from './http_rpc_server/index.js'; diff --git a/yarn-project/txe/src/txe_service/txe_service.ts b/yarn-project/txe/src/txe_service/txe_service.ts new file mode 100644 index 00000000000..a8d3ae81ece --- /dev/null +++ b/yarn-project/txe/src/txe_service/txe_service.ts @@ -0,0 +1,251 @@ +import { + type MerkleTreeId, + type NoteStatus, + type NullifierMembershipWitness, + type PublicDataWitness, + type UnencryptedL2Log, +} from '@aztec/circuit-types'; +import { + type CompleteAddress, + type Header, + type KeyValidationRequest, + type PrivateCallStackItem, + type PublicCallRequest, +} from '@aztec/circuits.js'; +import { type FunctionSelector } from '@aztec/foundation/abi'; +import { AztecAddress } from '@aztec/foundation/aztec-address'; +import { Fr, type Point } from '@aztec/foundation/fields'; +import { type Logger } from '@aztec/foundation/log'; +import { openTmpStore } from '@aztec/kv-store/utils'; +import { + type ACVMField, + type MessageLoadOracleInputs, + type NoteData, + type TypedOracle, + WorldStateDB, + WorldStatePublicDB, + fromACVMField, + toACVMField, +} from '@aztec/simulator'; +import { type ContractInstance } from '@aztec/types/contracts'; +import { MerkleTrees } from '@aztec/world-state'; + +import { + ForeignCallArray, + ForeignCallSingle, + fromArray, + fromSingle, + toArray, + toForeignCallResult, +} from '../util/encoding.js'; + +export class TXE implements TypedOracle { + constructor( + private logger: Logger, + private worldStatePublicDB: WorldStatePublicDB, + private worldStateDB: WorldStateDB, + private contractAddress: AztecAddress, + ) {} + + static async init(logger: Logger) { + const store = openTmpStore(true); + const merkleTrees = await MerkleTrees.new(store, logger); + const worldStatePublicDB = new WorldStatePublicDB(merkleTrees.asLatest()); + const worldStateDB = new WorldStateDB(merkleTrees.asLatest()); + const contractAddress = AztecAddress.random(); + return new TXE(logger, worldStatePublicDB, worldStateDB, contractAddress); + } + + getRandomField() { + return Fr.random(); + } + + packArgumentsArray(_args: Fr[]): Promise { + throw new Error('Method not implemented.'); + } + packReturns(_returns: Fr[]): Promise { + throw new Error('Method not implemented.'); + } + unpackReturns(_returnsHash: Fr): Promise { + throw new Error('Method not implemented.'); + } + getKeyValidationRequest(_pkMHash: Fr): Promise { + throw new Error('Method not implemented.'); + } + getContractInstance(_address: AztecAddress): Promise { + throw new Error('Method not implemented.'); + } + getMembershipWitness(_blockNumber: number, _treeId: MerkleTreeId, _leafValue: Fr): Promise { + throw new Error('Method not implemented.'); + } + getSiblingPath(_blockNumber: number, _treeId: MerkleTreeId, _leafIndex: Fr): Promise { + throw new Error('Method not implemented.'); + } + getNullifierMembershipWitness(_blockNumber: number, _nullifier: Fr): Promise { + throw new Error('Method not implemented.'); + } + getPublicDataTreeWitness(_blockNumber: number, _leafSlot: Fr): Promise { + throw new Error('Method not implemented.'); + } + getLowNullifierMembershipWitness( + _blockNumber: number, + _nullifier: Fr, + ): Promise { + throw new Error('Method not implemented.'); + } + getHeader(_blockNumber: number): Promise
{ + throw new Error('Method not implemented.'); + } + getCompleteAddress(_account: AztecAddress): Promise { + throw new Error('Method not implemented.'); + } + getAuthWitness(_messageHash: Fr): Promise { + throw new Error('Method not implemented.'); + } + popCapsule(): Promise { + throw new Error('Method not implemented.'); + } + getNotes( + _storageSlot: Fr, + _numSelects: number, + _selectByIndexes: number[], + _selectByOffsets: number[], + _selectByLengths: number[], + _selectValues: Fr[], + _selectComparators: number[], + _sortByIndexes: number[], + _sortByOffsets: number[], + _sortByLengths: number[], + _sortOrder: number[], + _limit: number, + _offset: number, + _status: NoteStatus, + ): Promise { + throw new Error('Method not implemented.'); + } + notifyCreatedNote(_storageSlot: Fr, _noteTypeId: Fr, _note: Fr[], _innerNoteHash: Fr, _counter: number): void { + throw new Error('Method not implemented.'); + } + notifyNullifiedNote(_innerNullifier: Fr, _innerNoteHash: Fr, _counter: number): Promise { + throw new Error('Method not implemented.'); + } + checkNullifierExists(_innerNullifier: Fr): Promise { + throw new Error('Method not implemented.'); + } + getL1ToL2MembershipWitness( + _contractAddress: AztecAddress, + _messageHash: Fr, + _secret: Fr, + ): Promise> { + throw new Error('Method not implemented.'); + } + async storageRead(startStorageSlot: Fr, numberOfElements: number): Promise { + console.log(`startStorageSlot ${startStorageSlot}`); + console.log(`numberOfElements ${numberOfElements}`); + const values = []; + for (let i = 0n; i < numberOfElements; i++) { + const storageSlot = startStorageSlot.add(new Fr(i)); + const value = await this.worldStatePublicDB.storageRead(this.contractAddress, storageSlot); + this.logger.debug(`Oracle storage read: slot=${storageSlot.toString()} value=${value}`); + values.push(value); + } + return values; + } + async storageWrite(startStorageSlot: Fr, values: Fr[]): Promise { + return await Promise.all( + values.map(async (value, i) => { + const storageSlot = startStorageSlot.add(new Fr(i)); + const result = await this.worldStatePublicDB.storageWrite(this.contractAddress, storageSlot, value); + this.logger.debug(`Oracle storage write: slot=${storageSlot.toString()} value=${value}`); + return new Fr(result); + }), + ); + } + emitEncryptedLog(_contractAddress: AztecAddress, _randomness: Fr, _encryptedNote: Buffer, _counter: number): void { + throw new Error('Method not implemented.'); + } + emitEncryptedNoteLog(_noteHashCounter: number, _encryptedNote: Buffer, _counter: number): void { + throw new Error('Method not implemented.'); + } + computeEncryptedLog( + _contractAddress: AztecAddress, + _storageSlot: Fr, + _noteTypeId: Fr, + _ovKeys: KeyValidationRequest, + _ivpkM: Point, + _preimage: Fr[], + ): Buffer { + throw new Error('Method not implemented.'); + } + emitUnencryptedLog(_log: UnencryptedL2Log, _counter: number): void { + throw new Error('Method not implemented.'); + } + emitContractClassUnencryptedLog(_log: UnencryptedL2Log, _counter: number): Fr { + throw new Error('Method not implemented.'); + } + callPrivateFunction( + _targetContractAddress: AztecAddress, + _functionSelector: FunctionSelector, + _argsHash: Fr, + _sideEffectCounter: number, + _isStaticCall: boolean, + _isDelegateCall: boolean, + ): Promise { + throw new Error('Method not implemented.'); + } + callPublicFunction( + _targetContractAddress: AztecAddress, + _functionSelector: FunctionSelector, + _argsHash: Fr, + _sideEffectCounter: number, + _isStaticCall: boolean, + _isDelegateCall: boolean, + ): Promise { + throw new Error('Method not implemented.'); + } + enqueuePublicFunctionCall( + _targetContractAddress: AztecAddress, + _functionSelector: FunctionSelector, + _argsHash: Fr, + _sideEffectCounter: number, + _isStaticCall: boolean, + _isDelegateCall: boolean, + ): Promise { + throw new Error('Method not implemented.'); + } + setPublicTeardownFunctionCall( + _targetContractAddress: AztecAddress, + _functionSelector: FunctionSelector, + _argsHash: Fr, + _sideEffectCounter: number, + _isStaticCall: boolean, + _isDelegateCall: boolean, + ): Promise { + throw new Error('Method not implemented.'); + } + aes128Encrypt(_input: Buffer, _initializationVector: Buffer, _key: Buffer): Buffer { + throw new Error('Method not implemented.'); + } + debugLog(_message: string, _fields: Fr[]): void { + throw new Error('Method not implemented.'); + } +} + +export class TXEService { + constructor(private typedOracle: TypedOracle) { + this.typedOracle = typedOracle; + } + + async storageRead(startStorageSlot: ForeignCallSingle, numberOfElements: ForeignCallSingle) { + const values = await this.typedOracle.storageRead( + fromSingle(startStorageSlot), + fromSingle(numberOfElements).toNumber(), + ); + return toForeignCallResult([toArray(values)]); + } + + async storageWrite(startStorageSlot: ForeignCallSingle, values: ForeignCallArray) { + const newValues = await this.typedOracle.storageWrite(fromSingle(startStorageSlot), fromArray(values)); + return toForeignCallResult([toArray(newValues)]); + } +} diff --git a/yarn-project/txe/src/util/encoding.ts b/yarn-project/txe/src/util/encoding.ts new file mode 100644 index 00000000000..fdc1941e6dd --- /dev/null +++ b/yarn-project/txe/src/util/encoding.ts @@ -0,0 +1,29 @@ +import { Fr } from '@aztec/foundation/fields'; + +export type ForeignCallSingle = { + Single: string; +}; + +export type ForeignCallArray = { + Array: string[]; +}; + +export function fromSingle(obj: ForeignCallSingle) { + return Fr.fromBuffer(Buffer.from(obj.Single, 'hex')); +} + +export function fromArray(obj: ForeignCallArray) { + return obj.Array.map(str => Fr.fromBuffer(Buffer.from(str, 'hex'))); +} + +export function toSingle(obj: Fr) { + return { Single: obj.toString() }; +} + +export function toArray(objs: Fr[]) { + return { Array: objs.map(obj => obj.toString()) }; +} + +export function toForeignCallResult(obj: (ForeignCallSingle | ForeignCallArray)[]) { + return { values: obj }; +} diff --git a/yarn-project/txe/tsconfig.json b/yarn-project/txe/tsconfig.json new file mode 100644 index 00000000000..f67ddec9fd6 --- /dev/null +++ b/yarn-project/txe/tsconfig.json @@ -0,0 +1,9 @@ +{ + "extends": "..", + "compilerOptions": { + "outDir": "dest", + "rootDir": "src", + "tsBuildInfoFile": ".tsbuildinfo" + }, + "include": ["src"] +} diff --git a/yarn-project/yarn.lock b/yarn-project/yarn.lock index bac3f94a04c..4ddea3b0d95 100644 --- a/yarn-project/yarn.lock +++ b/yarn-project/yarn.lock @@ -930,6 +930,26 @@ __metadata: languageName: unknown linkType: soft +"@aztec/txe@workspace:txe": + version: 0.0.0-use.local + resolution: "@aztec/txe@workspace:txe" + dependencies: + "@aztec/circuits.js": "workspace:^" + "@aztec/foundation": "workspace:^" + "@aztec/types": "workspace:^" + "@aztec/world-state": "workspace:^" + "@jest/globals": ^29.5.0 + "@types/jest": ^29.5.0 + "@types/node": ^18.7.23 + jest: ^29.5.0 + jest-mock-extended: ^3.0.3 + ts-node: ^10.9.1 + typescript: ^5.0.4 + bin: + txe: ./dest/bin/index.js + languageName: unknown + linkType: soft + "@aztec/types@workspace:^, @aztec/types@workspace:types": version: 0.0.0-use.local resolution: "@aztec/types@workspace:types" From 3fbc3ee5102619f70da04407894a37b60bae2716 Mon Sep 17 00:00:00 2001 From: thunkar Date: Mon, 3 Jun 2024 17:36:18 +0000 Subject: [PATCH 04/75] improvements --- yarn-project/txe/src/bin/index.ts | 5 +- .../txe/src/txe_service/txe_service.ts | 49 ++++++++++--------- 2 files changed, 28 insertions(+), 26 deletions(-) diff --git a/yarn-project/txe/src/bin/index.ts b/yarn-project/txe/src/bin/index.ts index 9787db5de83..365e2054be7 100644 --- a/yarn-project/txe/src/bin/index.ts +++ b/yarn-project/txe/src/bin/index.ts @@ -2,7 +2,7 @@ import { createDebugLogger } from '@aztec/foundation/log'; import { startTXEHttpServer } from '../index.js'; -import { TXE, TXEService } from '../txe_service/txe_service.js'; +import { TXEService } from '../txe_service/txe_service.js'; const { TXE_PORT = 8080 } = process.env; @@ -14,8 +14,7 @@ const logger = createDebugLogger('aztec:txe_service'); async function main() { logger.info(`Setting up TXE...`); - const txe = await TXE.init(logger); - const txeService = new TXEService(txe); + const txeService = await TXEService.init(logger); startTXEHttpServer(txeService, TXE_PORT); diff --git a/yarn-project/txe/src/txe_service/txe_service.ts b/yarn-project/txe/src/txe_service/txe_service.ts index a8d3ae81ece..e4d9a48026f 100644 --- a/yarn-project/txe/src/txe_service/txe_service.ts +++ b/yarn-project/txe/src/txe_service/txe_service.ts @@ -16,23 +16,21 @@ import { type FunctionSelector } from '@aztec/foundation/abi'; import { AztecAddress } from '@aztec/foundation/aztec-address'; import { Fr, type Point } from '@aztec/foundation/fields'; import { type Logger } from '@aztec/foundation/log'; +import { type AztecKVStore } from '@aztec/kv-store'; import { openTmpStore } from '@aztec/kv-store/utils'; import { - type ACVMField, type MessageLoadOracleInputs, type NoteData, type TypedOracle, WorldStateDB, WorldStatePublicDB, - fromACVMField, - toACVMField, } from '@aztec/simulator'; import { type ContractInstance } from '@aztec/types/contracts'; -import { MerkleTrees } from '@aztec/world-state'; +import { type MerkleTreeOperations, MerkleTrees } from '@aztec/world-state'; import { - ForeignCallArray, - ForeignCallSingle, + type ForeignCallArray, + type ForeignCallSingle, fromArray, fromSingle, toArray, @@ -40,20 +38,12 @@ import { } from '../util/encoding.js'; export class TXE implements TypedOracle { - constructor( - private logger: Logger, - private worldStatePublicDB: WorldStatePublicDB, - private worldStateDB: WorldStateDB, - private contractAddress: AztecAddress, - ) {} + private worldStatePublicDB: WorldStatePublicDB; + private worldStateDB: WorldStateDB; - static async init(logger: Logger) { - const store = openTmpStore(true); - const merkleTrees = await MerkleTrees.new(store, logger); - const worldStatePublicDB = new WorldStatePublicDB(merkleTrees.asLatest()); - const worldStateDB = new WorldStateDB(merkleTrees.asLatest()); - const contractAddress = AztecAddress.random(); - return new TXE(logger, worldStatePublicDB, worldStateDB, contractAddress); + constructor(private logger: Logger, private trees: MerkleTreeOperations, private contractAddress: AztecAddress) { + this.worldStatePublicDB = new WorldStatePublicDB(this.trees); + this.worldStateDB = new WorldStateDB(this.trees); } getRandomField() { @@ -140,8 +130,6 @@ export class TXE implements TypedOracle { throw new Error('Method not implemented.'); } async storageRead(startStorageSlot: Fr, numberOfElements: number): Promise { - console.log(`startStorageSlot ${startStorageSlot}`); - console.log(`numberOfElements ${numberOfElements}`); const values = []; for (let i = 0n; i < numberOfElements; i++) { const storageSlot = startStorageSlot.add(new Fr(i)); @@ -151,6 +139,7 @@ export class TXE implements TypedOracle { } return values; } + async storageWrite(startStorageSlot: Fr, values: Fr[]): Promise { return await Promise.all( values.map(async (value, i) => { @@ -232,8 +221,22 @@ export class TXE implements TypedOracle { } export class TXEService { - constructor(private typedOracle: TypedOracle) { - this.typedOracle = typedOracle; + constructor(private typedOracle: TypedOracle, private store: AztecKVStore, private contractAddress: AztecAddress) {} + + static async init(logger: Logger, contractAddress = AztecAddress.random()) { + const store = openTmpStore(true); + const trees = await MerkleTrees.new(store, logger); + const txe = new TXE(logger, trees.asLatest(), contractAddress); + return new TXEService(txe, store, contractAddress); + } + + setContractAddress(address = AztecAddress.random()): AztecAddress { + this.contractAddress = address; + return this.contractAddress; + } + + async reset() { + await this.store.clear(); } async storageRead(startStorageSlot: ForeignCallSingle, numberOfElements: ForeignCallSingle) { From 58c4c8294c311e037e3ab78a9717dd00470ad7d6 Mon Sep 17 00:00:00 2001 From: thunkar Date: Mon, 3 Jun 2024 17:39:00 +0000 Subject: [PATCH 05/75] fixed tsconfig --- yarn-project/txe/tsconfig.json | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/yarn-project/txe/tsconfig.json b/yarn-project/txe/tsconfig.json index f67ddec9fd6..effb5a7151c 100644 --- a/yarn-project/txe/tsconfig.json +++ b/yarn-project/txe/tsconfig.json @@ -5,5 +5,37 @@ "rootDir": "src", "tsBuildInfoFile": ".tsbuildinfo" }, + "references": [ + { + "path": "../circuit-types" + }, + { + "path": "../circuits.js" + }, + { + "path": "../foundation" + }, + { + "path": "../noir-protocol-circuits-types" + }, + { + "path": "../protocol-contracts" + }, + { + "path": "../types" + }, + { + "path": "../world-state" + }, + { + "path": "../kv-store" + }, + { + "path": "../merkle-tree" + }, + { + "path": "../noir-contracts.js" + } + ], "include": ["src"] } From 84af5e866cd27edca10ff67d4316899c43bedc0b Mon Sep 17 00:00:00 2001 From: thunkar Date: Mon, 3 Jun 2024 18:03:41 +0000 Subject: [PATCH 06/75] public data tree witness --- yarn-project/txe/src/http_rpc_server/index.ts | 2 +- .../txe/src/txe_service/txe_service.ts | 35 ++++++++++++++++--- 2 files changed, 32 insertions(+), 5 deletions(-) diff --git a/yarn-project/txe/src/http_rpc_server/index.ts b/yarn-project/txe/src/http_rpc_server/index.ts index f861fb446d0..69d9e08464b 100644 --- a/yarn-project/txe/src/http_rpc_server/index.ts +++ b/yarn-project/txe/src/http_rpc_server/index.ts @@ -9,7 +9,7 @@ import { type TXEService } from '../txe_service/txe_service.js'; * @returns A new instance of the HTTP server. */ export function createTXERpcServer(txeService: TXEService): JsonRpcServer { - return new JsonRpcServer(txeService, {}, {}, ['start', 'stop']); + return new JsonRpcServer(txeService, {}, {}, ['init']); } /** diff --git a/yarn-project/txe/src/txe_service/txe_service.ts b/yarn-project/txe/src/txe_service/txe_service.ts index e4d9a48026f..4efd2499545 100644 --- a/yarn-project/txe/src/txe_service/txe_service.ts +++ b/yarn-project/txe/src/txe_service/txe_service.ts @@ -1,16 +1,18 @@ import { - type MerkleTreeId, + MerkleTreeId, type NoteStatus, type NullifierMembershipWitness, - type PublicDataWitness, + PublicDataWitness, type UnencryptedL2Log, } from '@aztec/circuit-types'; import { type CompleteAddress, type Header, type KeyValidationRequest, + PUBLIC_DATA_TREE_HEIGHT, type PrivateCallStackItem, type PublicCallRequest, + PublicDataTreeLeafPreimage, } from '@aztec/circuits.js'; import { type FunctionSelector } from '@aztec/foundation/abi'; import { AztecAddress } from '@aztec/foundation/aztec-address'; @@ -74,8 +76,22 @@ export class TXE implements TypedOracle { getNullifierMembershipWitness(_blockNumber: number, _nullifier: Fr): Promise { throw new Error('Method not implemented.'); } - getPublicDataTreeWitness(_blockNumber: number, _leafSlot: Fr): Promise { - throw new Error('Method not implemented.'); + async getPublicDataTreeWitness(_blockNumber: number, leafSlot: Fr): Promise { + const committedDb = this.trees; + const lowLeafResult = await committedDb.getPreviousValueIndex(MerkleTreeId.PUBLIC_DATA_TREE, leafSlot.toBigInt()); + if (!lowLeafResult) { + return undefined; + } else { + const preimage = (await committedDb.getLeafPreimage( + MerkleTreeId.PUBLIC_DATA_TREE, + lowLeafResult.index, + )) as PublicDataTreeLeafPreimage; + const path = await committedDb.getSiblingPath( + MerkleTreeId.PUBLIC_DATA_TREE, + lowLeafResult.index, + ); + return new PublicDataWitness(lowLeafResult.index, preimage, path); + } } getLowNullifierMembershipWitness( _blockNumber: number, @@ -251,4 +267,15 @@ export class TXEService { const newValues = await this.typedOracle.storageWrite(fromSingle(startStorageSlot), fromArray(values)); return toForeignCallResult([toArray(newValues)]); } + + async getPublicDataTreeWitness(blockNumber: ForeignCallSingle, leafSlot: ForeignCallSingle) { + const parsedBlockNumber = fromSingle(blockNumber).toNumber(); + const parsedLeafSlot = fromSingle(leafSlot); + + const witness = await this.typedOracle.getPublicDataTreeWitness(parsedBlockNumber, parsedLeafSlot); + if (!witness) { + throw new Error(`Public data witness not found for slot ${parsedLeafSlot} at block ${parsedBlockNumber}.`); + } + return toForeignCallResult([toArray(witness.toFields())]); + } } From aab9959ea348864cc9617d2683ad1d0d6b33b990 Mon Sep 17 00:00:00 2001 From: thunkar Date: Mon, 3 Jun 2024 18:07:01 +0000 Subject: [PATCH 07/75] return null --- yarn-project/txe/src/txe_service/txe_service.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/yarn-project/txe/src/txe_service/txe_service.ts b/yarn-project/txe/src/txe_service/txe_service.ts index 4efd2499545..48f0fc4210c 100644 --- a/yarn-project/txe/src/txe_service/txe_service.ts +++ b/yarn-project/txe/src/txe_service/txe_service.ts @@ -253,6 +253,7 @@ export class TXEService { async reset() { await this.store.clear(); + return toForeignCallResult([]); } async storageRead(startStorageSlot: ForeignCallSingle, numberOfElements: ForeignCallSingle) { From c94d32eaa7f6aa00c77ff6567d49e16d3a6c8e7e Mon Sep 17 00:00:00 2001 From: thunkar Date: Tue, 4 Jun 2024 09:31:38 +0000 Subject: [PATCH 08/75] more oracle calls --- yarn-project/txe/package.json | 2 + .../txe/src/txe_service/txe_service.ts | 112 +++++++++++++++--- yarn-project/yarn.lock | 2 + 3 files changed, 99 insertions(+), 17 deletions(-) diff --git a/yarn-project/txe/package.json b/yarn-project/txe/package.json index c3c56084a64..97214621a3a 100644 --- a/yarn-project/txe/package.json +++ b/yarn-project/txe/package.json @@ -48,8 +48,10 @@ ] }, "dependencies": { + "@aztec/circuit-types": "workspace:^", "@aztec/circuits.js": "workspace:^", "@aztec/foundation": "workspace:^", + "@aztec/simulator": "workspace:^", "@aztec/types": "workspace:^", "@aztec/world-state": "workspace:^" }, diff --git a/yarn-project/txe/src/txe_service/txe_service.ts b/yarn-project/txe/src/txe_service/txe_service.ts index 48f0fc4210c..72068691843 100644 --- a/yarn-project/txe/src/txe_service/txe_service.ts +++ b/yarn-project/txe/src/txe_service/txe_service.ts @@ -9,20 +9,22 @@ import { type CompleteAddress, type Header, type KeyValidationRequest, - PUBLIC_DATA_TREE_HEIGHT, + type PUBLIC_DATA_TREE_HEIGHT, type PrivateCallStackItem, type PublicCallRequest, - PublicDataTreeLeafPreimage, + type PublicDataTreeLeafPreimage, } from '@aztec/circuits.js'; +import { Aes128 } from '@aztec/circuits.js/barretenberg'; import { type FunctionSelector } from '@aztec/foundation/abi'; import { AztecAddress } from '@aztec/foundation/aztec-address'; import { Fr, type Point } from '@aztec/foundation/fields'; -import { type Logger } from '@aztec/foundation/log'; +import { type Logger, applyStringFormatting } from '@aztec/foundation/log'; import { type AztecKVStore } from '@aztec/kv-store'; import { openTmpStore } from '@aztec/kv-store/utils'; import { type MessageLoadOracleInputs, type NoteData, + PackedValuesCache, type TypedOracle, WorldStateDB, WorldStatePublicDB, @@ -37,45 +39,60 @@ import { fromSingle, toArray, toForeignCallResult, + toSingle, } from '../util/encoding.js'; export class TXE implements TypedOracle { private worldStatePublicDB: WorldStatePublicDB; private worldStateDB: WorldStateDB; - constructor(private logger: Logger, private trees: MerkleTreeOperations, private contractAddress: AztecAddress) { + constructor( + private logger: Logger, + private trees: MerkleTreeOperations, + private packedValuesCache: PackedValuesCache, + private contractAddress: AztecAddress, + ) { this.worldStatePublicDB = new WorldStatePublicDB(this.trees); this.worldStateDB = new WorldStateDB(this.trees); + this.packedValuesCache = packedValuesCache; } getRandomField() { return Fr.random(); } - packArgumentsArray(_args: Fr[]): Promise { - throw new Error('Method not implemented.'); + packArgumentsArray(args: Fr[]): Promise { + return Promise.resolve(this.packedValuesCache.pack(args)); } - packReturns(_returns: Fr[]): Promise { - throw new Error('Method not implemented.'); + + packReturns(returns: Fr[]): Promise { + return Promise.resolve(this.packedValuesCache.pack(returns)); } - unpackReturns(_returnsHash: Fr): Promise { - throw new Error('Method not implemented.'); + + unpackReturns(returnsHash: Fr): Promise { + return Promise.resolve(this.packedValuesCache.unpack(returnsHash)); } + getKeyValidationRequest(_pkMHash: Fr): Promise { throw new Error('Method not implemented.'); } + getContractInstance(_address: AztecAddress): Promise { throw new Error('Method not implemented.'); } + getMembershipWitness(_blockNumber: number, _treeId: MerkleTreeId, _leafValue: Fr): Promise { throw new Error('Method not implemented.'); } + getSiblingPath(_blockNumber: number, _treeId: MerkleTreeId, _leafIndex: Fr): Promise { throw new Error('Method not implemented.'); } + getNullifierMembershipWitness(_blockNumber: number, _nullifier: Fr): Promise { throw new Error('Method not implemented.'); } + async getPublicDataTreeWitness(_blockNumber: number, leafSlot: Fr): Promise { const committedDb = this.trees; const lowLeafResult = await committedDb.getPreviousValueIndex(MerkleTreeId.PUBLIC_DATA_TREE, leafSlot.toBigInt()); @@ -93,24 +110,30 @@ export class TXE implements TypedOracle { return new PublicDataWitness(lowLeafResult.index, preimage, path); } } + getLowNullifierMembershipWitness( _blockNumber: number, _nullifier: Fr, ): Promise { throw new Error('Method not implemented.'); } + getHeader(_blockNumber: number): Promise
{ throw new Error('Method not implemented.'); } + getCompleteAddress(_account: AztecAddress): Promise { throw new Error('Method not implemented.'); } + getAuthWitness(_messageHash: Fr): Promise { throw new Error('Method not implemented.'); } + popCapsule(): Promise { throw new Error('Method not implemented.'); } + getNotes( _storageSlot: Fr, _numSelects: number, @@ -129,15 +152,19 @@ export class TXE implements TypedOracle { ): Promise { throw new Error('Method not implemented.'); } + notifyCreatedNote(_storageSlot: Fr, _noteTypeId: Fr, _note: Fr[], _innerNoteHash: Fr, _counter: number): void { throw new Error('Method not implemented.'); } + notifyNullifiedNote(_innerNullifier: Fr, _innerNoteHash: Fr, _counter: number): Promise { throw new Error('Method not implemented.'); } + checkNullifierExists(_innerNullifier: Fr): Promise { throw new Error('Method not implemented.'); } + getL1ToL2MembershipWitness( _contractAddress: AztecAddress, _messageHash: Fr, @@ -145,6 +172,7 @@ export class TXE implements TypedOracle { ): Promise> { throw new Error('Method not implemented.'); } + async storageRead(startStorageSlot: Fr, numberOfElements: number): Promise { const values = []; for (let i = 0n; i < numberOfElements; i++) { @@ -166,12 +194,15 @@ export class TXE implements TypedOracle { }), ); } + emitEncryptedLog(_contractAddress: AztecAddress, _randomness: Fr, _encryptedNote: Buffer, _counter: number): void { throw new Error('Method not implemented.'); } + emitEncryptedNoteLog(_noteHashCounter: number, _encryptedNote: Buffer, _counter: number): void { throw new Error('Method not implemented.'); } + computeEncryptedLog( _contractAddress: AztecAddress, _storageSlot: Fr, @@ -182,12 +213,15 @@ export class TXE implements TypedOracle { ): Buffer { throw new Error('Method not implemented.'); } + emitUnencryptedLog(_log: UnencryptedL2Log, _counter: number): void { throw new Error('Method not implemented.'); } + emitContractClassUnencryptedLog(_log: UnencryptedL2Log, _counter: number): Fr { throw new Error('Method not implemented.'); } + callPrivateFunction( _targetContractAddress: AztecAddress, _functionSelector: FunctionSelector, @@ -198,6 +232,7 @@ export class TXE implements TypedOracle { ): Promise { throw new Error('Method not implemented.'); } + callPublicFunction( _targetContractAddress: AztecAddress, _functionSelector: FunctionSelector, @@ -208,6 +243,7 @@ export class TXE implements TypedOracle { ): Promise { throw new Error('Method not implemented.'); } + enqueuePublicFunctionCall( _targetContractAddress: AztecAddress, _functionSelector: FunctionSelector, @@ -218,6 +254,7 @@ export class TXE implements TypedOracle { ): Promise { throw new Error('Method not implemented.'); } + setPublicTeardownFunctionCall( _targetContractAddress: AztecAddress, _functionSelector: FunctionSelector, @@ -228,22 +265,31 @@ export class TXE implements TypedOracle { ): Promise { throw new Error('Method not implemented.'); } - aes128Encrypt(_input: Buffer, _initializationVector: Buffer, _key: Buffer): Buffer { - throw new Error('Method not implemented.'); + + aes128Encrypt(input: Buffer, initializationVector: Buffer, key: Buffer): Buffer { + const aes128 = new Aes128(); + return aes128.encryptBufferCBC(input, initializationVector, key); } - debugLog(_message: string, _fields: Fr[]): void { - throw new Error('Method not implemented.'); + + debugLog(message: string, fields: Fr[]): void { + this.logger.verbose(`debug_log ${applyStringFormatting(message, fields)}`); } } export class TXEService { - constructor(private typedOracle: TypedOracle, private store: AztecKVStore, private contractAddress: AztecAddress) {} + constructor( + private typedOracle: TypedOracle, + private store: AztecKVStore, + private packedValuesCache: PackedValuesCache, + private contractAddress: AztecAddress, + ) {} static async init(logger: Logger, contractAddress = AztecAddress.random()) { const store = openTmpStore(true); const trees = await MerkleTrees.new(store, logger); - const txe = new TXE(logger, trees.asLatest(), contractAddress); - return new TXEService(txe, store, contractAddress); + const packedValuesCache = new PackedValuesCache(); + const txe = new TXE(logger, trees.asLatest(), packedValuesCache, contractAddress); + return new TXEService(txe, store, packedValuesCache, contractAddress); } setContractAddress(address = AztecAddress.random()): AztecAddress { @@ -253,6 +299,38 @@ export class TXEService { async reset() { await this.store.clear(); + this.packedValuesCache = new PackedValuesCache(); + return toForeignCallResult([]); + } + + async packArgumentsArray(args: ForeignCallArray) { + const packed = await this.typedOracle.packArgumentsArray(fromArray(args)); + return toForeignCallResult([toSingle(packed)]); + } + + async packArguments(_length: ForeignCallSingle, values: ForeignCallArray) { + const packed = await this.typedOracle.packArgumentsArray(fromArray(values)); + return toForeignCallResult([toSingle(packed)]); + } + + // Since the argument is a slice, noir automatically adds a length field to oracle call. + async packReturns(_length: ForeignCallSingle, values: ForeignCallArray) { + const packed = await this.typedOracle.packReturns(fromArray(values)); + return toForeignCallResult([toSingle(packed)]); + } + + async unpackReturns(returnsHash: ForeignCallSingle) { + const unpacked = await this.typedOracle.unpackReturns(fromSingle(returnsHash)); + return toForeignCallResult([toArray(unpacked)]); + } + + // Since the argument is a slice, noir automatically adds a length field to oracle call. + debugLog(message: ForeignCallArray, _length: ForeignCallSingle, fields: ForeignCallArray) { + const messageStr = fromArray(message) + .map(field => String.fromCharCode(field.toNumber())) + .join(''); + const fieldsFr = fromArray(fields); + this.typedOracle.debugLog(messageStr, fieldsFr); return toForeignCallResult([]); } diff --git a/yarn-project/yarn.lock b/yarn-project/yarn.lock index 4ddea3b0d95..678e3a9c4ee 100644 --- a/yarn-project/yarn.lock +++ b/yarn-project/yarn.lock @@ -934,8 +934,10 @@ __metadata: version: 0.0.0-use.local resolution: "@aztec/txe@workspace:txe" dependencies: + "@aztec/circuit-types": "workspace:^" "@aztec/circuits.js": "workspace:^" "@aztec/foundation": "workspace:^" + "@aztec/simulator": "workspace:^" "@aztec/types": "workspace:^" "@aztec/world-state": "workspace:^" "@jest/globals": ^29.5.0 From 5d857ae7896c06c63be342859f407bac4044717e Mon Sep 17 00:00:00 2001 From: thunkar Date: Tue, 4 Jun 2024 11:39:37 +0000 Subject: [PATCH 09/75] more oracles, timetravel --- yarn-project/txe/package.json | 1 + yarn-project/txe/src/oracle/txe_oracle.ts | 264 ++++++++++++++ .../txe/src/txe_service/txe_service.ts | 331 ++++-------------- .../world-state/src/world-state-db/index.ts | 2 + yarn-project/yarn.lock | 1 + 5 files changed, 333 insertions(+), 266 deletions(-) create mode 100644 yarn-project/txe/src/oracle/txe_oracle.ts diff --git a/yarn-project/txe/package.json b/yarn-project/txe/package.json index 97214621a3a..19aa02bf07c 100644 --- a/yarn-project/txe/package.json +++ b/yarn-project/txe/package.json @@ -51,6 +51,7 @@ "@aztec/circuit-types": "workspace:^", "@aztec/circuits.js": "workspace:^", "@aztec/foundation": "workspace:^", + "@aztec/kv-store": "workspace:^", "@aztec/simulator": "workspace:^", "@aztec/types": "workspace:^", "@aztec/world-state": "workspace:^" diff --git a/yarn-project/txe/src/oracle/txe_oracle.ts b/yarn-project/txe/src/oracle/txe_oracle.ts new file mode 100644 index 00000000000..bfc01defe11 --- /dev/null +++ b/yarn-project/txe/src/oracle/txe_oracle.ts @@ -0,0 +1,264 @@ +import { + MerkleTreeId, + type NoteStatus, + type NullifierMembershipWitness, + PublicDataWitness, + type UnencryptedL2Log, +} from '@aztec/circuit-types'; +import { + type CompleteAddress, + type Header, + type KeyValidationRequest, + type PUBLIC_DATA_TREE_HEIGHT, + type PrivateCallStackItem, + type PublicCallRequest, + type PublicDataTreeLeafPreimage, +} from '@aztec/circuits.js'; +import { Aes128 } from '@aztec/circuits.js/barretenberg'; +import { type FunctionSelector } from '@aztec/foundation/abi'; +import { type AztecAddress } from '@aztec/foundation/aztec-address'; +import { Fr, type Point } from '@aztec/foundation/fields'; +import { type Logger, applyStringFormatting } from '@aztec/foundation/log'; +import { + type MessageLoadOracleInputs, + type NoteData, + type PackedValuesCache, + type TypedOracle, + WorldStatePublicDB, +} from '@aztec/simulator'; +import { type ContractInstance } from '@aztec/types/contracts'; +import { MerkleTreeSnapshotOperationsFacade, type MerkleTrees } from '@aztec/world-state'; + +export class TXE implements TypedOracle { + private worldStatePublicDB: WorldStatePublicDB; + + constructor( + private logger: Logger, + private trees: MerkleTrees, + private packedValuesCache: PackedValuesCache, + private contractAddress: AztecAddress, + ) { + this.packedValuesCache = packedValuesCache; + this.worldStatePublicDB = new WorldStatePublicDB(this.trees.asLatest()); + } + + getRandomField() { + return Fr.random(); + } + + packArgumentsArray(args: Fr[]): Promise { + return Promise.resolve(this.packedValuesCache.pack(args)); + } + + packReturns(returns: Fr[]): Promise { + return Promise.resolve(this.packedValuesCache.pack(returns)); + } + + unpackReturns(returnsHash: Fr): Promise { + return Promise.resolve(this.packedValuesCache.unpack(returnsHash)); + } + + getKeyValidationRequest(_pkMHash: Fr): Promise { + throw new Error('Method not implemented.'); + } + + getContractInstance(_address: AztecAddress): Promise { + throw new Error('Method not implemented.'); + } + + getMembershipWitness(_blockNumber: number, _treeId: MerkleTreeId, _leafValue: Fr): Promise { + throw new Error('Method not implemented.'); + } + + async getSiblingPath(blockNumber: number, treeId: MerkleTreeId, leafIndex: Fr) { + const committedDb = new MerkleTreeSnapshotOperationsFacade(this.trees, blockNumber); + const result = await committedDb.getSiblingPath(treeId, leafIndex.toBigInt()); + return result.toFields(); + } + + getNullifierMembershipWitness(_blockNumber: number, _nullifier: Fr): Promise { + throw new Error('Method not implemented.'); + } + + async getPublicDataTreeWitness(blockNumber: number, leafSlot: Fr): Promise { + const committedDb = new MerkleTreeSnapshotOperationsFacade(this.trees, blockNumber); + const lowLeafResult = await committedDb.getPreviousValueIndex(MerkleTreeId.PUBLIC_DATA_TREE, leafSlot.toBigInt()); + if (!lowLeafResult) { + return undefined; + } else { + const preimage = (await committedDb.getLeafPreimage( + MerkleTreeId.PUBLIC_DATA_TREE, + lowLeafResult.index, + )) as PublicDataTreeLeafPreimage; + const path = await committedDb.getSiblingPath( + MerkleTreeId.PUBLIC_DATA_TREE, + lowLeafResult.index, + ); + return new PublicDataWitness(lowLeafResult.index, preimage, path); + } + } + + getLowNullifierMembershipWitness( + _blockNumber: number, + _nullifier: Fr, + ): Promise { + throw new Error('Method not implemented.'); + } + + getHeader(_blockNumber: number): Promise
{ + throw new Error('Method not implemented.'); + } + + getCompleteAddress(_account: AztecAddress): Promise { + throw new Error('Method not implemented.'); + } + + getAuthWitness(_messageHash: Fr): Promise { + throw new Error('Method not implemented.'); + } + + popCapsule(): Promise { + throw new Error('Method not implemented.'); + } + + getNotes( + _storageSlot: Fr, + _numSelects: number, + _selectByIndexes: number[], + _selectByOffsets: number[], + _selectByLengths: number[], + _selectValues: Fr[], + _selectComparators: number[], + _sortByIndexes: number[], + _sortByOffsets: number[], + _sortByLengths: number[], + _sortOrder: number[], + _limit: number, + _offset: number, + _status: NoteStatus, + ): Promise { + throw new Error('Method not implemented.'); + } + + notifyCreatedNote(_storageSlot: Fr, _noteTypeId: Fr, _note: Fr[], _innerNoteHash: Fr, _counter: number): void { + throw new Error('Method not implemented.'); + } + + notifyNullifiedNote(_innerNullifier: Fr, _innerNoteHash: Fr, _counter: number): Promise { + throw new Error('Method not implemented.'); + } + + checkNullifierExists(_innerNullifier: Fr): Promise { + throw new Error('Method not implemented.'); + } + + getL1ToL2MembershipWitness( + _contractAddress: AztecAddress, + _messageHash: Fr, + _secret: Fr, + ): Promise> { + throw new Error('Method not implemented.'); + } + + async storageRead(startStorageSlot: Fr, numberOfElements: number): Promise { + const values = []; + for (let i = 0n; i < numberOfElements; i++) { + const storageSlot = startStorageSlot.add(new Fr(i)); + const value = await this.worldStatePublicDB.storageRead(this.contractAddress, storageSlot); + this.logger.debug(`Oracle storage read: slot=${storageSlot.toString()} value=${value}`); + values.push(value); + } + return values; + } + + async storageWrite(startStorageSlot: Fr, values: Fr[]): Promise { + return await Promise.all( + values.map(async (value, i) => { + const storageSlot = startStorageSlot.add(new Fr(i)); + const result = await this.worldStatePublicDB.storageWrite(this.contractAddress, storageSlot, value); + this.logger.debug(`Oracle storage write: slot=${storageSlot.toString()} value=${value}`); + return new Fr(result); + }), + ); + } + + emitEncryptedLog(_contractAddress: AztecAddress, _randomness: Fr, _encryptedNote: Buffer, _counter: number): void { + throw new Error('Method not implemented.'); + } + + emitEncryptedNoteLog(_noteHashCounter: number, _encryptedNote: Buffer, _counter: number): void { + throw new Error('Method not implemented.'); + } + + computeEncryptedLog( + _contractAddress: AztecAddress, + _storageSlot: Fr, + _noteTypeId: Fr, + _ovKeys: KeyValidationRequest, + _ivpkM: Point, + _preimage: Fr[], + ): Buffer { + throw new Error('Method not implemented.'); + } + + emitUnencryptedLog(_log: UnencryptedL2Log, _counter: number): void { + throw new Error('Method not implemented.'); + } + + emitContractClassUnencryptedLog(_log: UnencryptedL2Log, _counter: number): Fr { + throw new Error('Method not implemented.'); + } + + callPrivateFunction( + _targetContractAddress: AztecAddress, + _functionSelector: FunctionSelector, + _argsHash: Fr, + _sideEffectCounter: number, + _isStaticCall: boolean, + _isDelegateCall: boolean, + ): Promise { + throw new Error('Method not implemented.'); + } + + callPublicFunction( + _targetContractAddress: AztecAddress, + _functionSelector: FunctionSelector, + _argsHash: Fr, + _sideEffectCounter: number, + _isStaticCall: boolean, + _isDelegateCall: boolean, + ): Promise { + throw new Error('Method not implemented.'); + } + + enqueuePublicFunctionCall( + _targetContractAddress: AztecAddress, + _functionSelector: FunctionSelector, + _argsHash: Fr, + _sideEffectCounter: number, + _isStaticCall: boolean, + _isDelegateCall: boolean, + ): Promise { + throw new Error('Method not implemented.'); + } + + setPublicTeardownFunctionCall( + _targetContractAddress: AztecAddress, + _functionSelector: FunctionSelector, + _argsHash: Fr, + _sideEffectCounter: number, + _isStaticCall: boolean, + _isDelegateCall: boolean, + ): Promise { + throw new Error('Method not implemented.'); + } + + aes128Encrypt(input: Buffer, initializationVector: Buffer, key: Buffer): Buffer { + const aes128 = new Aes128(); + return aes128.encryptBufferCBC(input, initializationVector, key); + } + + debugLog(message: string, fields: Fr[]): void { + this.logger.verbose(`debug_log ${applyStringFormatting(message, fields)}`); + } +} diff --git a/yarn-project/txe/src/txe_service/txe_service.ts b/yarn-project/txe/src/txe_service/txe_service.ts index 72068691843..6ae302081b7 100644 --- a/yarn-project/txe/src/txe_service/txe_service.ts +++ b/yarn-project/txe/src/txe_service/txe_service.ts @@ -1,37 +1,13 @@ -import { - MerkleTreeId, - type NoteStatus, - type NullifierMembershipWitness, - PublicDataWitness, - type UnencryptedL2Log, -} from '@aztec/circuit-types'; -import { - type CompleteAddress, - type Header, - type KeyValidationRequest, - type PUBLIC_DATA_TREE_HEIGHT, - type PrivateCallStackItem, - type PublicCallRequest, - type PublicDataTreeLeafPreimage, -} from '@aztec/circuits.js'; -import { Aes128 } from '@aztec/circuits.js/barretenberg'; -import { type FunctionSelector } from '@aztec/foundation/abi'; +import { L2Block, MerkleTreeId } from '@aztec/circuit-types'; +import { Fr, Header } from '@aztec/circuits.js'; import { AztecAddress } from '@aztec/foundation/aztec-address'; -import { Fr, type Point } from '@aztec/foundation/fields'; -import { type Logger, applyStringFormatting } from '@aztec/foundation/log'; +import { type Logger } from '@aztec/foundation/log'; import { type AztecKVStore } from '@aztec/kv-store'; import { openTmpStore } from '@aztec/kv-store/utils'; -import { - type MessageLoadOracleInputs, - type NoteData, - PackedValuesCache, - type TypedOracle, - WorldStateDB, - WorldStatePublicDB, -} from '@aztec/simulator'; -import { type ContractInstance } from '@aztec/types/contracts'; -import { type MerkleTreeOperations, MerkleTrees } from '@aztec/world-state'; +import { PackedValuesCache, type TypedOracle } from '@aztec/simulator'; +import { MerkleTrees } from '@aztec/world-state'; +import { TXE } from '../oracle/txe_oracle.js'; import { type ForeignCallArray, type ForeignCallSingle, @@ -42,244 +18,13 @@ import { toSingle, } from '../util/encoding.js'; -export class TXE implements TypedOracle { - private worldStatePublicDB: WorldStatePublicDB; - private worldStateDB: WorldStateDB; - - constructor( - private logger: Logger, - private trees: MerkleTreeOperations, - private packedValuesCache: PackedValuesCache, - private contractAddress: AztecAddress, - ) { - this.worldStatePublicDB = new WorldStatePublicDB(this.trees); - this.worldStateDB = new WorldStateDB(this.trees); - this.packedValuesCache = packedValuesCache; - } - - getRandomField() { - return Fr.random(); - } - - packArgumentsArray(args: Fr[]): Promise { - return Promise.resolve(this.packedValuesCache.pack(args)); - } - - packReturns(returns: Fr[]): Promise { - return Promise.resolve(this.packedValuesCache.pack(returns)); - } - - unpackReturns(returnsHash: Fr): Promise { - return Promise.resolve(this.packedValuesCache.unpack(returnsHash)); - } - - getKeyValidationRequest(_pkMHash: Fr): Promise { - throw new Error('Method not implemented.'); - } - - getContractInstance(_address: AztecAddress): Promise { - throw new Error('Method not implemented.'); - } - - getMembershipWitness(_blockNumber: number, _treeId: MerkleTreeId, _leafValue: Fr): Promise { - throw new Error('Method not implemented.'); - } - - getSiblingPath(_blockNumber: number, _treeId: MerkleTreeId, _leafIndex: Fr): Promise { - throw new Error('Method not implemented.'); - } - - getNullifierMembershipWitness(_blockNumber: number, _nullifier: Fr): Promise { - throw new Error('Method not implemented.'); - } - - async getPublicDataTreeWitness(_blockNumber: number, leafSlot: Fr): Promise { - const committedDb = this.trees; - const lowLeafResult = await committedDb.getPreviousValueIndex(MerkleTreeId.PUBLIC_DATA_TREE, leafSlot.toBigInt()); - if (!lowLeafResult) { - return undefined; - } else { - const preimage = (await committedDb.getLeafPreimage( - MerkleTreeId.PUBLIC_DATA_TREE, - lowLeafResult.index, - )) as PublicDataTreeLeafPreimage; - const path = await committedDb.getSiblingPath( - MerkleTreeId.PUBLIC_DATA_TREE, - lowLeafResult.index, - ); - return new PublicDataWitness(lowLeafResult.index, preimage, path); - } - } - - getLowNullifierMembershipWitness( - _blockNumber: number, - _nullifier: Fr, - ): Promise { - throw new Error('Method not implemented.'); - } - - getHeader(_blockNumber: number): Promise
{ - throw new Error('Method not implemented.'); - } - - getCompleteAddress(_account: AztecAddress): Promise { - throw new Error('Method not implemented.'); - } - - getAuthWitness(_messageHash: Fr): Promise { - throw new Error('Method not implemented.'); - } - - popCapsule(): Promise { - throw new Error('Method not implemented.'); - } - - getNotes( - _storageSlot: Fr, - _numSelects: number, - _selectByIndexes: number[], - _selectByOffsets: number[], - _selectByLengths: number[], - _selectValues: Fr[], - _selectComparators: number[], - _sortByIndexes: number[], - _sortByOffsets: number[], - _sortByLengths: number[], - _sortOrder: number[], - _limit: number, - _offset: number, - _status: NoteStatus, - ): Promise { - throw new Error('Method not implemented.'); - } - - notifyCreatedNote(_storageSlot: Fr, _noteTypeId: Fr, _note: Fr[], _innerNoteHash: Fr, _counter: number): void { - throw new Error('Method not implemented.'); - } - - notifyNullifiedNote(_innerNullifier: Fr, _innerNoteHash: Fr, _counter: number): Promise { - throw new Error('Method not implemented.'); - } - - checkNullifierExists(_innerNullifier: Fr): Promise { - throw new Error('Method not implemented.'); - } - - getL1ToL2MembershipWitness( - _contractAddress: AztecAddress, - _messageHash: Fr, - _secret: Fr, - ): Promise> { - throw new Error('Method not implemented.'); - } - - async storageRead(startStorageSlot: Fr, numberOfElements: number): Promise { - const values = []; - for (let i = 0n; i < numberOfElements; i++) { - const storageSlot = startStorageSlot.add(new Fr(i)); - const value = await this.worldStatePublicDB.storageRead(this.contractAddress, storageSlot); - this.logger.debug(`Oracle storage read: slot=${storageSlot.toString()} value=${value}`); - values.push(value); - } - return values; - } - - async storageWrite(startStorageSlot: Fr, values: Fr[]): Promise { - return await Promise.all( - values.map(async (value, i) => { - const storageSlot = startStorageSlot.add(new Fr(i)); - const result = await this.worldStatePublicDB.storageWrite(this.contractAddress, storageSlot, value); - this.logger.debug(`Oracle storage write: slot=${storageSlot.toString()} value=${value}`); - return new Fr(result); - }), - ); - } - - emitEncryptedLog(_contractAddress: AztecAddress, _randomness: Fr, _encryptedNote: Buffer, _counter: number): void { - throw new Error('Method not implemented.'); - } - - emitEncryptedNoteLog(_noteHashCounter: number, _encryptedNote: Buffer, _counter: number): void { - throw new Error('Method not implemented.'); - } - - computeEncryptedLog( - _contractAddress: AztecAddress, - _storageSlot: Fr, - _noteTypeId: Fr, - _ovKeys: KeyValidationRequest, - _ivpkM: Point, - _preimage: Fr[], - ): Buffer { - throw new Error('Method not implemented.'); - } - - emitUnencryptedLog(_log: UnencryptedL2Log, _counter: number): void { - throw new Error('Method not implemented.'); - } - - emitContractClassUnencryptedLog(_log: UnencryptedL2Log, _counter: number): Fr { - throw new Error('Method not implemented.'); - } - - callPrivateFunction( - _targetContractAddress: AztecAddress, - _functionSelector: FunctionSelector, - _argsHash: Fr, - _sideEffectCounter: number, - _isStaticCall: boolean, - _isDelegateCall: boolean, - ): Promise { - throw new Error('Method not implemented.'); - } - - callPublicFunction( - _targetContractAddress: AztecAddress, - _functionSelector: FunctionSelector, - _argsHash: Fr, - _sideEffectCounter: number, - _isStaticCall: boolean, - _isDelegateCall: boolean, - ): Promise { - throw new Error('Method not implemented.'); - } - - enqueuePublicFunctionCall( - _targetContractAddress: AztecAddress, - _functionSelector: FunctionSelector, - _argsHash: Fr, - _sideEffectCounter: number, - _isStaticCall: boolean, - _isDelegateCall: boolean, - ): Promise { - throw new Error('Method not implemented.'); - } - - setPublicTeardownFunctionCall( - _targetContractAddress: AztecAddress, - _functionSelector: FunctionSelector, - _argsHash: Fr, - _sideEffectCounter: number, - _isStaticCall: boolean, - _isDelegateCall: boolean, - ): Promise { - throw new Error('Method not implemented.'); - } - - aes128Encrypt(input: Buffer, initializationVector: Buffer, key: Buffer): Buffer { - const aes128 = new Aes128(); - return aes128.encryptBufferCBC(input, initializationVector, key); - } - - debugLog(message: string, fields: Fr[]): void { - this.logger.verbose(`debug_log ${applyStringFormatting(message, fields)}`); - } -} - export class TXEService { + private blockNumber = 0; + constructor( private typedOracle: TypedOracle, private store: AztecKVStore, + private trees: MerkleTrees, private packedValuesCache: PackedValuesCache, private contractAddress: AztecAddress, ) {} @@ -288,8 +33,37 @@ export class TXEService { const store = openTmpStore(true); const trees = await MerkleTrees.new(store, logger); const packedValuesCache = new PackedValuesCache(); - const txe = new TXE(logger, trees.asLatest(), packedValuesCache, contractAddress); - return new TXEService(txe, store, packedValuesCache, contractAddress); + logger.info(`TXE service initialized`); + const txe = new TXE(logger, trees, packedValuesCache, contractAddress); + const service = new TXEService(txe, store, trees, packedValuesCache, contractAddress); + await service.timeTravel(1); + return service; + } + + async timeTravel(blocks: number) { + this.blockNumber += blocks; + const header = Header.empty(); + const l2Block = L2Block.empty(); + + for (let i = 0; i < blocks; i++) { + header.state = await this.trees.getStateReference(true); + header.state.partial.nullifierTree.root = Fr.fromBuffer( + (await this.trees.getTreeInfo(MerkleTreeId.NULLIFIER_TREE, true)).root, + ); + header.state.partial.noteHashTree.root = Fr.fromBuffer( + (await this.trees.getTreeInfo(MerkleTreeId.NOTE_HASH_TREE, true)).root, + ); + header.state.partial.publicDataTree.root = Fr.fromBuffer( + (await this.trees.getTreeInfo(MerkleTreeId.PUBLIC_DATA_TREE, true)).root, + ); + header.state.l1ToL2MessageTree.root = Fr.fromBuffer( + (await this.trees.getTreeInfo(MerkleTreeId.L1_TO_L2_MESSAGE_TREE, true)).root, + ); + l2Block.archive.root = Fr.fromBuffer((await this.trees.getTreeInfo(MerkleTreeId.ARCHIVE, true)).root); + l2Block.header = header; + await this.trees.handleL2BlockAndMessages(l2Block, []); + this.blockNumber++; + } } setContractAddress(address = AztecAddress.random()): AztecAddress { @@ -297,6 +71,22 @@ export class TXEService { return this.contractAddress; } + getContractAddress() { + return toForeignCallResult([toSingle(this.contractAddress)]); + } + + getBlockNumber() { + return toForeignCallResult([toSingle(new Fr(this.blockNumber))]); + } + + avmOpcodeAddress() { + return toForeignCallResult([toSingle(this.contractAddress)]); + } + + avmOpcodeBlockNumber() { + return toForeignCallResult([toSingle(new Fr(this.blockNumber))]); + } + async reset() { await this.store.clear(); this.packedValuesCache = new PackedValuesCache(); @@ -357,4 +147,13 @@ export class TXEService { } return toForeignCallResult([toArray(witness.toFields())]); } + + async getSiblingPath(blockNumber: ForeignCallSingle, treeId: ForeignCallSingle, leafIndex: ForeignCallSingle) { + const result = await this.typedOracle.getSiblingPath( + fromSingle(blockNumber).toNumber(), + fromSingle(treeId).toNumber(), + fromSingle(leafIndex), + ); + return toForeignCallResult([toArray(result)]); + } } diff --git a/yarn-project/world-state/src/world-state-db/index.ts b/yarn-project/world-state/src/world-state-db/index.ts index 9d72e0991e8..f4c20a567f9 100644 --- a/yarn-project/world-state/src/world-state-db/index.ts +++ b/yarn-project/world-state/src/world-state-db/index.ts @@ -1,3 +1,5 @@ export * from './merkle_trees.js'; export * from './merkle_tree_db.js'; export * from './merkle_tree_operations.js'; +export * from './merkle_tree_operations_facade.js'; +export * from './merkle_tree_snapshot_operations_facade.js'; diff --git a/yarn-project/yarn.lock b/yarn-project/yarn.lock index 678e3a9c4ee..e02b14abb9f 100644 --- a/yarn-project/yarn.lock +++ b/yarn-project/yarn.lock @@ -937,6 +937,7 @@ __metadata: "@aztec/circuit-types": "workspace:^" "@aztec/circuits.js": "workspace:^" "@aztec/foundation": "workspace:^" + "@aztec/kv-store": "workspace:^" "@aztec/simulator": "workspace:^" "@aztec/types": "workspace:^" "@aztec/world-state": "workspace:^" From 717a16a2f02bc209db82ff7760b7906f555c6a40 Mon Sep 17 00:00:00 2001 From: thunkar Date: Tue, 4 Jun 2024 11:44:49 +0000 Subject: [PATCH 10/75] fix --- yarn-project/txe/src/txe_service/txe_service.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/yarn-project/txe/src/txe_service/txe_service.ts b/yarn-project/txe/src/txe_service/txe_service.ts index 6ae302081b7..9d70f063cf8 100644 --- a/yarn-project/txe/src/txe_service/txe_service.ts +++ b/yarn-project/txe/src/txe_service/txe_service.ts @@ -64,11 +64,12 @@ export class TXEService { await this.trees.handleL2BlockAndMessages(l2Block, []); this.blockNumber++; } + return toForeignCallResult([]); } - setContractAddress(address = AztecAddress.random()): AztecAddress { + setContractAddress(address = AztecAddress.random()) { this.contractAddress = address; - return this.contractAddress; + return toForeignCallResult([]); } getContractAddress() { From 58e7bfa5d893537201d3251cc9167243800d0d1e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicol=C3=A1s=20Venturo?= Date: Tue, 4 Jun 2024 11:46:17 +0000 Subject: [PATCH 11/75] Sketch nr tests for txe testing --- .../src/state_vars/public_immutable/test.nr | 58 +------------------ .../shared_mutable/shared_mutable.nr | 15 ++--- .../src/state_vars/shared_mutable/test.nr | 58 +++++++++++-------- .../aztec-nr/aztec/src/test/helpers.nr | 1 + .../aztec/src/test/helpers/cheatcodes.nr | 36 ++++++++++++ .../aztec/src/test/helpers/context_builder.nr | 31 ++++++---- 6 files changed, 100 insertions(+), 99 deletions(-) create mode 100644 noir-projects/aztec-nr/aztec/src/test/helpers/cheatcodes.nr diff --git a/noir-projects/aztec-nr/aztec/src/state_vars/public_immutable/test.nr b/noir-projects/aztec-nr/aztec/src/state_vars/public_immutable/test.nr index 65fe0a7a253..59e7b0a37ef 100644 --- a/noir-projects/aztec-nr/aztec/src/state_vars/public_immutable/test.nr +++ b/noir-projects/aztec-nr/aztec/src/state_vars/public_immutable/test.nr @@ -10,49 +10,11 @@ fn setup() -> PublicImmutable { PublicImmutable::new(&mut context, storage_slot) } -impl PublicImmutable { - fn mock_initialized_storage(self) { - let _ = OracleMock::mock("storageRead").with_params((self.get_initialization_slot(), 1)).returns([1]).times(1); - } - - fn mock_uninitialized_storage(self) { - let _ = OracleMock::mock("storageRead").with_params((self.get_initialization_slot(), 1)).returns([0]).times(1); - } - - fn mock_initialize_write(self) -> OracleMock { - // Unfortunately we can't only pass the storage slot to with_params, so we must know the initialization value. - OracleMock::mock("storageWrite").with_params((self.get_initialization_slot(), [0xdead])).returns([0; 1]) - } - - fn assert_initialize_write(self, mock: OracleMock) { - assert(mock.get_last_params() != (self.storage_slot, [0])); - } - - fn mock_value_write(self, value: MockStruct) -> OracleMock where MockStruct: Serialize { - // Unfortunately we can't only pass the storage slot to with_params, so we must know what will be stored as - // well. - OracleMock::mock("storageWrite").with_params((self.storage_slot, value.serialize())).returns([0; N]) - } - - fn mock_value_read(self, value: MockStruct) where MockStruct: Serialize { - // TBD https://github.com/noir-lang/noir/issues/4633: replace 2 with N - let _ = OracleMock::mock("storageRead").with_params((self.storage_slot, 2)).returns(value.serialize()).times(1); - } - - fn assert_value_write(self, mock: OracleMock, value: MockStruct) { - assert_eq(mock.get_last_params(), (self.storage_slot, value.serialize())); - } -} - #[test] -fn test_is_initialized() { +fn test_uninitialized_by_default() { let state_var = setup(); - state_var.mock_uninitialized_storage(); assert_eq(state_var.is_initialized(), false); - - state_var.mock_initialized_storage(); - assert_eq(state_var.is_initialized(), true); } #[test] @@ -63,6 +25,7 @@ fn test_initialize_uninitialized() { state_var.initialize(value); + assert(state_var.is_initialized()); assert(state_var.read() == value); } @@ -72,28 +35,13 @@ fn test_initialize_already_initialized() { let value = MockStruct::new(5, 6); - state_var.mock_initialized_storage(); - state_var.initialize(value); -} - -#[test] -fn test_read_initialized() { - let state_var = setup(); - - let value = MockStruct::new(5, 6); - - state_var.mock_initialized_storage(); - state_var.mock_value_read(value); - - assert_eq(state_var.read(), value); + state_var.initialize(value); } #[test(should_fail_with="PublicImmutable not initialized")] fn test_read_uninitialized() { let state_var = setup(); - state_var.mock_uninitialized_storage(); - let _ = state_var.read(); } diff --git a/noir-projects/aztec-nr/aztec/src/state_vars/shared_mutable/shared_mutable.nr b/noir-projects/aztec-nr/aztec/src/state_vars/shared_mutable/shared_mutable.nr index 91c864a03b2..38f36ae41db 100644 --- a/noir-projects/aztec-nr/aztec/src/state_vars/shared_mutable/shared_mutable.nr +++ b/noir-projects/aztec-nr/aztec/src/state_vars/shared_mutable/shared_mutable.nr @@ -121,7 +121,7 @@ impl SharedMutable { // will only be valid for however many blocks we can ensure the value will not change, which will depend on the // current delay and any scheduled delay changes. - let (value_change, delay_change, historical_block_number) = self.historical_read_from_public_storage(*self.context); + let (value_change, delay_change, historical_block_number) = self.historical_read_from_public_storage(); // We use the effective minimum delay as opposed to the current delay at the historical block as this one also // takes into consideration any scheduled delay changes. @@ -139,29 +139,26 @@ impl SharedMutable { value_change.get_current_at(historical_block_number) } - fn historical_read_from_public_storage( - self, - context: PrivateContext - ) -> (ScheduledValueChange, ScheduledDelayChange, u32) where T: FromField { - let header = context.get_header(); + fn historical_read_from_public_storage(self) -> (ScheduledValueChange, ScheduledDelayChange, u32) where T: FromField { + let header = self.context.get_header(); // Ideally the following would be simply public_storage::read_historical, but we can't implement that yet. let value_change_slot = self.get_value_change_storage_slot(); let mut raw_value_change_fields = [0; 3]; for i in 0..3 { raw_value_change_fields[i] = header.public_storage_historical_read( value_change_slot + i as Field, - context.this_address() + self.context.this_address() ); } // Ideally the following would be simply public_storage::read_historical, but we can't implement that yet. let delay_change_slot = self.get_delay_change_storage_slot(); - let raw_delay_change_fields = [header.public_storage_historical_read(delay_change_slot, context.this_address())]; + let raw_delay_change_fields = [header.public_storage_historical_read(delay_change_slot, self.context.this_address())]; let value_change = ScheduledValueChange::deserialize(raw_value_change_fields); let delay_change = ScheduledDelayChange::deserialize(raw_delay_change_fields); - let historical_block_number = context.historical_header.global_variables.block_number as u32; + let historical_block_number = self.context.historical_header.global_variables.block_number as u32; (value_change, delay_change, historical_block_number) } diff --git a/noir-projects/aztec-nr/aztec/src/state_vars/shared_mutable/test.nr b/noir-projects/aztec-nr/aztec/src/state_vars/shared_mutable/test.nr index 6403f166e9e..7214057b9bc 100644 --- a/noir-projects/aztec-nr/aztec/src/state_vars/shared_mutable/test.nr +++ b/noir-projects/aztec-nr/aztec/src/state_vars/shared_mutable/test.nr @@ -6,7 +6,8 @@ use crate::{ shared_mutable::SharedMutable, scheduled_value_change::ScheduledValueChange, scheduled_delay_change::ScheduledDelayChange }, - test::helpers::context_builder::ContextBuilder, oracle::get_public_data_witness::PublicDataWitness + test::helpers::{cheatcodes, context_builder::ContextBuilder}, + oracle::get_public_data_witness::PublicDataWitness }; use dep::protocol_types::{ @@ -135,18 +136,15 @@ fn test_get_current_value_in_public() { #[test] fn test_get_scheduled_value_in_public() { let (state_var, block_number) = setup(); - - // Change in the future, scheduled is post (always is) - mock_value_change_read(state_var, pre_value, post_value, block_number + 1); - assert_eq(state_var.get_scheduled_value_in_public(), (post_value, (block_number + 1) as u32)); - - // Change in the current block, scheduled is post (always is) - mock_value_change_read(state_var, pre_value, post_value, block_number); - assert_eq(state_var.get_scheduled_value_in_public(), (post_value, block_number as u32)); - - // Change in the past, scheduled is post (always is) - mock_value_change_read(state_var, pre_value, post_value, block_number - 1); - assert_eq(state_var.get_scheduled_value_in_public(), (post_value, (block_number - 1) as u32)); + // // Change in the future, scheduled is post (always is) + // mock_value_change_read(state_var, pre_value, post_value, block_number + 1); + // assert_eq(state_var.get_scheduled_value_in_public(), (post_value, (block_number + 1) as u32)); + // // Change in the current block, scheduled is post (always is) + // mock_value_change_read(state_var, pre_value, post_value, block_number); + // assert_eq(state_var.get_scheduled_value_in_public(), (post_value, block_number as u32)); + // // Change in the past, scheduled is post (always is) + // mock_value_change_read(state_var, pre_value, post_value, block_number - 1); + // assert_eq(state_var.get_scheduled_value_in_public(), (post_value, (block_number - 1) as u32)); } #[test] @@ -363,15 +361,25 @@ fn test_schedule_delay_reduction_after_change() { ); } -#[test] -fn test_get_current_value_in_private_before_change() { - // Here we'd want to test that the private getter returns the correct value and sets max_block_number in the - // context to the expected block horizon, in all the possible scenarios (long before change, before near change, - // after change). - // However, this requires mocking the getPublicDataTreeWitness oracle so that we can convince the circuit that - // it got a valid historical proof. Because we can set the tree root to whatever we want in the context, this is - // trivial for a single historical value (we add a leaf and compute the root with any random path), but is quite - // hard if we're reading more than one value for the same root (as SharedMutable does): we essentially need to - // create an actual indexed tree and compute the correct path for each of the inserted values. - // TODO: implement an actual tree and use it here https://github.com/AztecProtocol/aztec-packages/issues/5494 -} +// #[test] +// fn test_get_current_value_in_private_before_change() { +// let (state_var, block_number) = setup(); +// state_var.schedule_value_change(5); + +// let block_number = 40; +// let mut context = ContextBuilder::new().block_number(block_number).private(); + +// let storage_slot = 57; +// let private_state_var: SharedMutable = SharedMutable::new(&mut context, storage_slot); + +// let foo = private_state_var.get_current_value_in_private(); +// // Here we'd want to test that the private getter returns the correct value and sets max_block_number in the +// // context to the expected block horizon, in all the possible scenarios (long before change, before near change, +// // after change). +// // However, this requires mocking the getPublicDataTreeWitness oracle so that we can convince the circuit that +// // it got a valid historical proof. Because we can set the tree root to whatever we want in the context, this is +// // trivial for a single historical value (we add a leaf and compute the root with any random path), but is quite +// // hard if we're reading more than one value for the same root (as SharedMutable does): we essentially need to +// // create an actual indexed tree and compute the correct path for each of the inserted values. +// // TODO: implement an actual tree and use it here https://github.com/AztecProtocol/aztec-packages/issues/5494 +// } diff --git a/noir-projects/aztec-nr/aztec/src/test/helpers.nr b/noir-projects/aztec-nr/aztec/src/test/helpers.nr index c5c7a4b5f31..333197dd09b 100644 --- a/noir-projects/aztec-nr/aztec/src/test/helpers.nr +++ b/noir-projects/aztec-nr/aztec/src/test/helpers.nr @@ -1 +1,2 @@ mod context_builder; +mod cheatcodes; diff --git a/noir-projects/aztec-nr/aztec/src/test/helpers/cheatcodes.nr b/noir-projects/aztec-nr/aztec/src/test/helpers/cheatcodes.nr new file mode 100644 index 00000000000..18dd20aaff8 --- /dev/null +++ b/noir-projects/aztec-nr/aztec/src/test/helpers/cheatcodes.nr @@ -0,0 +1,36 @@ +use dep::protocol_types::address::AztecAddress; + +#[oracle(reset)] +fn oracle_reset() {} + +unconstrained pub fn reset() { + oracle_reset(); +} + +#[oracle(getContractAddress)] +fn oracle_get_contract_address() -> AztecAddress {} + +unconstrained pub fn get_contract_address() -> AztecAddress { + oracle_get_contract_address() +} + +#[oracle(setContractAddress)] +fn oracle_set_contract_address(address: AztecAddress) {} + +unconstrained pub fn set_contract_address(address: AztecAddress) { + oracle_set_contract_address(address); +} + +#[oracle(getBlockNumber)] +fn oracle_get_block_number() -> Field {} + +unconstrained pub fn get_block_number() -> Field { + oracle_get_block_number() +} + +#[oracle(timeTravel)] +fn oracle_time_travel(blocks: Field) {} + +unconstrained pub fn advance_blocks(blocks: Field) { + oracle_time_travel(blocks); +} diff --git a/noir-projects/aztec-nr/aztec/src/test/helpers/context_builder.nr b/noir-projects/aztec-nr/aztec/src/test/helpers/context_builder.nr index 0b04ac52ccb..ed71ad75093 100644 --- a/noir-projects/aztec-nr/aztec/src/test/helpers/context_builder.nr +++ b/noir-projects/aztec-nr/aztec/src/test/helpers/context_builder.nr @@ -1,7 +1,6 @@ use crate::context::{PrivateContext, PublicContext}; +use crate::test::helpers::cheatcodes; use dep::protocol_types::address::AztecAddress; -use dep::std::test::OracleMock; - struct ContextBuilder { block_number: Option, contract_address: Option, @@ -22,31 +21,43 @@ impl ContextBuilder { self } - fn private(&mut self) -> PrivateContext { + fn private(self) -> PrivateContext { + cheatcodes::reset(); let mut context = PrivateContext::empty(); if self.block_number.is_some() { - context.inputs.historical_header.global_variables.block_number = self.block_number.unwrap_unchecked(); + let block_number = self.block_number.unwrap_unchecked(); + context.inputs.historical_header.global_variables.block_number = block_number; + + let difference = block_number - cheatcodes::get_block_number(); + cheatcodes::advance_blocks(difference); } if self.contract_address.is_some() { - context.inputs.call_context.storage_contract_address = self.contract_address.unwrap_unchecked(); + let contract_address = self.contract_address.unwrap_unchecked(); + context.inputs.call_context.storage_contract_address = contract_address; + cheatcodes::set_contract_address(contract_address); } context } - fn public(&mut self) -> PublicContext { + fn public(self) -> PublicContext { + // cheatcodes::reset(); let mut context = PublicContext::empty(); if self.block_number.is_some() { - let _ = OracleMock::mock("avmOpcodeBlockNumber").returns(self.block_number.unwrap()); - } + let block_number = self.block_number.unwrap_unchecked(); - if self.contract_address.is_some() { - let _ = OracleMock::mock("avmOpcodeAddress").returns(self.contract_address.unwrap()); + let difference = block_number - cheatcodes::get_block_number(); + // cheatcodes::advance_blocks(difference); } + // if self.contract_address.is_some() { + // let contract_address = self.contract_address.unwrap_unchecked(); + // cheatcodes::set_contract_address(contract_address); + // } + context } } From d543edf5479bdc27da979510a593e28138f10a2c Mon Sep 17 00:00:00 2001 From: thunkar Date: Tue, 4 Jun 2024 11:49:49 +0000 Subject: [PATCH 12/75] fix --- yarn-project/txe/src/txe_service/txe_service.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/yarn-project/txe/src/txe_service/txe_service.ts b/yarn-project/txe/src/txe_service/txe_service.ts index 9d70f063cf8..dd85991f6b3 100644 --- a/yarn-project/txe/src/txe_service/txe_service.ts +++ b/yarn-project/txe/src/txe_service/txe_service.ts @@ -73,7 +73,7 @@ export class TXEService { } getContractAddress() { - return toForeignCallResult([toSingle(this.contractAddress)]); + return toForeignCallResult([toSingle(this.contractAddress.toField())]); } getBlockNumber() { @@ -81,7 +81,7 @@ export class TXEService { } avmOpcodeAddress() { - return toForeignCallResult([toSingle(this.contractAddress)]); + return toForeignCallResult([toSingle(this.contractAddress.toField())]); } avmOpcodeBlockNumber() { From 92b9a485fc68323821a48571d99d4290144e3e9f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicol=C3=A1s=20Venturo?= Date: Tue, 4 Jun 2024 12:47:21 +0000 Subject: [PATCH 13/75] Begin test refactor --- .../src/state_vars/shared_mutable/test.nr | 22 +++++++++++-------- .../aztec/src/test/helpers/cheatcodes.nr | 6 +++++ .../aztec/src/test/helpers/context_builder.nr | 19 +++++++--------- 3 files changed, 27 insertions(+), 20 deletions(-) diff --git a/noir-projects/aztec-nr/aztec/src/state_vars/shared_mutable/test.nr b/noir-projects/aztec-nr/aztec/src/state_vars/shared_mutable/test.nr index 7214057b9bc..5875740b350 100644 --- a/noir-projects/aztec-nr/aztec/src/state_vars/shared_mutable/test.nr +++ b/noir-projects/aztec-nr/aztec/src/state_vars/shared_mutable/test.nr @@ -136,15 +136,19 @@ fn test_get_current_value_in_public() { #[test] fn test_get_scheduled_value_in_public() { let (state_var, block_number) = setup(); - // // Change in the future, scheduled is post (always is) - // mock_value_change_read(state_var, pre_value, post_value, block_number + 1); - // assert_eq(state_var.get_scheduled_value_in_public(), (post_value, (block_number + 1) as u32)); - // // Change in the current block, scheduled is post (always is) - // mock_value_change_read(state_var, pre_value, post_value, block_number); - // assert_eq(state_var.get_scheduled_value_in_public(), (post_value, block_number as u32)); - // // Change in the past, scheduled is post (always is) - // mock_value_change_read(state_var, pre_value, post_value, block_number - 1); - // assert_eq(state_var.get_scheduled_value_in_public(), (post_value, (block_number - 1) as u32)); + + state_var.schedule_value_change(post_value); + + // Change in the future, scheduled is post (always is) + assert_eq( + state_var.get_scheduled_value_in_public(), (post_value, (block_number + TEST_INITIAL_DELAY) as u32) + ); + + cheatcodes::advance_blocks(TEST_INITIAL_DELAY); + + assert_eq( + state_var.get_scheduled_value_in_public(), (post_value, (block_number + TEST_INITIAL_DELAY) as u32) + ); } #[test] diff --git a/noir-projects/aztec-nr/aztec/src/test/helpers/cheatcodes.nr b/noir-projects/aztec-nr/aztec/src/test/helpers/cheatcodes.nr index 18dd20aaff8..5a3fc46f0c4 100644 --- a/noir-projects/aztec-nr/aztec/src/test/helpers/cheatcodes.nr +++ b/noir-projects/aztec-nr/aztec/src/test/helpers/cheatcodes.nr @@ -34,3 +34,9 @@ fn oracle_time_travel(blocks: Field) {} unconstrained pub fn advance_blocks(blocks: Field) { oracle_time_travel(blocks); } + +unconstrained pub fn advance_block_to(block_number: Field) { + let difference = block_number - get_block_number(); + advance_blocks(difference); +} + diff --git a/noir-projects/aztec-nr/aztec/src/test/helpers/context_builder.nr b/noir-projects/aztec-nr/aztec/src/test/helpers/context_builder.nr index ed71ad75093..1b069ea0214 100644 --- a/noir-projects/aztec-nr/aztec/src/test/helpers/context_builder.nr +++ b/noir-projects/aztec-nr/aztec/src/test/helpers/context_builder.nr @@ -1,6 +1,7 @@ use crate::context::{PrivateContext, PublicContext}; use crate::test::helpers::cheatcodes; use dep::protocol_types::address::AztecAddress; + struct ContextBuilder { block_number: Option, contract_address: Option, @@ -28,9 +29,7 @@ impl ContextBuilder { if self.block_number.is_some() { let block_number = self.block_number.unwrap_unchecked(); context.inputs.historical_header.global_variables.block_number = block_number; - - let difference = block_number - cheatcodes::get_block_number(); - cheatcodes::advance_blocks(difference); + cheatcodes::advance_block_to(block_number); } if self.contract_address.is_some() { @@ -43,20 +42,18 @@ impl ContextBuilder { } fn public(self) -> PublicContext { - // cheatcodes::reset(); + cheatcodes::reset(); let mut context = PublicContext::empty(); if self.block_number.is_some() { let block_number = self.block_number.unwrap_unchecked(); - - let difference = block_number - cheatcodes::get_block_number(); - // cheatcodes::advance_blocks(difference); + cheatcodes::advance_block_to(block_number); } - // if self.contract_address.is_some() { - // let contract_address = self.contract_address.unwrap_unchecked(); - // cheatcodes::set_contract_address(contract_address); - // } + if self.contract_address.is_some() { + let contract_address = self.contract_address.unwrap_unchecked(); + cheatcodes::set_contract_address(contract_address); + } context } From c6fdef68897eefb102fddf33f8cf3a83d1913527 Mon Sep 17 00:00:00 2001 From: thunkar Date: Tue, 4 Jun 2024 12:52:17 +0000 Subject: [PATCH 14/75] fix --- yarn-project/txe/src/txe_service/txe_service.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/yarn-project/txe/src/txe_service/txe_service.ts b/yarn-project/txe/src/txe_service/txe_service.ts index dd85991f6b3..2abb77b7479 100644 --- a/yarn-project/txe/src/txe_service/txe_service.ts +++ b/yarn-project/txe/src/txe_service/txe_service.ts @@ -41,7 +41,6 @@ export class TXEService { } async timeTravel(blocks: number) { - this.blockNumber += blocks; const header = Header.empty(); const l2Block = L2Block.empty(); From b29b3a41a5d265b2e93b2834a5ea1b98baf935f4 Mon Sep 17 00:00:00 2001 From: thunkar Date: Tue, 4 Jun 2024 13:13:35 +0000 Subject: [PATCH 15/75] fixes --- yarn-project/txe/src/txe_service/txe_service.ts | 16 +++++++++++----- yarn-project/txe/src/util/encoding.ts | 2 +- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/yarn-project/txe/src/txe_service/txe_service.ts b/yarn-project/txe/src/txe_service/txe_service.ts index 2abb77b7479..bc81791466d 100644 --- a/yarn-project/txe/src/txe_service/txe_service.ts +++ b/yarn-project/txe/src/txe_service/txe_service.ts @@ -22,6 +22,7 @@ export class TXEService { private blockNumber = 0; constructor( + private logger: Logger, private typedOracle: TypedOracle, private store: AztecKVStore, private trees: MerkleTrees, @@ -35,17 +36,22 @@ export class TXEService { const packedValuesCache = new PackedValuesCache(); logger.info(`TXE service initialized`); const txe = new TXE(logger, trees, packedValuesCache, contractAddress); - const service = new TXEService(txe, store, trees, packedValuesCache, contractAddress); - await service.timeTravel(1); + const service = new TXEService(logger, txe, store, trees, packedValuesCache, contractAddress); + await service.timeTravel(toSingle(new Fr(1n))); return service; } - async timeTravel(blocks: number) { - const header = Header.empty(); - const l2Block = L2Block.empty(); + timeTravel(blocks: ForeignCallSingle) { + return this.#timeTravelInner(fromSingle(blocks).toNumber()); + } + async #timeTravelInner(blocks: number) { + this.logger.info(`time traveling ${blocks} blocks`); for (let i = 0; i < blocks; i++) { + const header = Header.empty(); + const l2Block = L2Block.empty(); header.state = await this.trees.getStateReference(true); + header.globalVariables.blockNumber = new Fr(this.blockNumber); header.state.partial.nullifierTree.root = Fr.fromBuffer( (await this.trees.getTreeInfo(MerkleTreeId.NULLIFIER_TREE, true)).root, ); diff --git a/yarn-project/txe/src/util/encoding.ts b/yarn-project/txe/src/util/encoding.ts index fdc1941e6dd..42dc32b88b4 100644 --- a/yarn-project/txe/src/util/encoding.ts +++ b/yarn-project/txe/src/util/encoding.ts @@ -17,7 +17,7 @@ export function fromArray(obj: ForeignCallArray) { } export function toSingle(obj: Fr) { - return { Single: obj.toString() }; + return { Single: obj.toString().slice(2) }; } export function toArray(objs: Fr[]) { From 6628ab035299ad90a9c3965b3b0fd1eb704d2d84 Mon Sep 17 00:00:00 2001 From: thunkar Date: Tue, 4 Jun 2024 13:21:40 +0000 Subject: [PATCH 16/75] fix --- yarn-project/txe/src/txe_service/txe_service.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/yarn-project/txe/src/txe_service/txe_service.ts b/yarn-project/txe/src/txe_service/txe_service.ts index bc81791466d..a564b09bd72 100644 --- a/yarn-project/txe/src/txe_service/txe_service.ts +++ b/yarn-project/txe/src/txe_service/txe_service.ts @@ -94,8 +94,10 @@ export class TXEService { } async reset() { + this.blockNumber = 0; await this.store.clear(); this.packedValuesCache = new PackedValuesCache(); + await this.#timeTravelInner(1); return toForeignCallResult([]); } From 460a1d1bb6b4d8ed78792e2ef58e33ba0d424878 Mon Sep 17 00:00:00 2001 From: thunkar Date: Tue, 4 Jun 2024 13:38:50 +0000 Subject: [PATCH 17/75] private context inputs --- yarn-project/txe/src/txe_service/txe_service.ts | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/yarn-project/txe/src/txe_service/txe_service.ts b/yarn-project/txe/src/txe_service/txe_service.ts index a564b09bd72..e7abe5dcf11 100644 --- a/yarn-project/txe/src/txe_service/txe_service.ts +++ b/yarn-project/txe/src/txe_service/txe_service.ts @@ -1,5 +1,5 @@ import { L2Block, MerkleTreeId } from '@aztec/circuit-types'; -import { Fr, Header } from '@aztec/circuits.js'; +import { Fr, Header, PrivateContextInputs } from '@aztec/circuits.js'; import { AztecAddress } from '@aztec/foundation/aztec-address'; import { type Logger } from '@aztec/foundation/log'; import { type AztecKVStore } from '@aztec/kv-store'; @@ -41,6 +41,15 @@ export class TXEService { return service; } + async getPrivateContextInputs() { + const inputs = PrivateContextInputs.empty(); + inputs.historicalHeader.globalVariables.blockNumber = new Fr(this.blockNumber); + inputs.historicalHeader.state.partial = (await this.trees.getStateReference(true)).partial; + inputs.callContext.msgSender = AztecAddress.random(); + inputs.callContext.storageContractAddress = this.contractAddress; + return toForeignCallResult([toArray(inputs.toFields())]); + } + timeTravel(blocks: ForeignCallSingle) { return this.#timeTravelInner(fromSingle(blocks).toNumber()); } From 069a09a374f5158c3f772d3e77e9d2bd2c69644c Mon Sep 17 00:00:00 2001 From: thunkar Date: Tue, 4 Jun 2024 13:52:22 +0000 Subject: [PATCH 18/75] return struct --- yarn-project/txe/src/txe_service/txe_service.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/yarn-project/txe/src/txe_service/txe_service.ts b/yarn-project/txe/src/txe_service/txe_service.ts index e7abe5dcf11..d5149d40d49 100644 --- a/yarn-project/txe/src/txe_service/txe_service.ts +++ b/yarn-project/txe/src/txe_service/txe_service.ts @@ -47,7 +47,7 @@ export class TXEService { inputs.historicalHeader.state.partial = (await this.trees.getStateReference(true)).partial; inputs.callContext.msgSender = AztecAddress.random(); inputs.callContext.storageContractAddress = this.contractAddress; - return toForeignCallResult([toArray(inputs.toFields())]); + return toForeignCallResult(inputs.toFields().map(toSingle)); } timeTravel(blocks: ForeignCallSingle) { From 7edc7190ad094d7f112fc137f41ee5231beeddd7 Mon Sep 17 00:00:00 2001 From: thunkar Date: Tue, 4 Jun 2024 14:03:56 +0000 Subject: [PATCH 19/75] fix --- yarn-project/txe/src/txe_service/txe_service.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/yarn-project/txe/src/txe_service/txe_service.ts b/yarn-project/txe/src/txe_service/txe_service.ts index d5149d40d49..c80e9df1fb7 100644 --- a/yarn-project/txe/src/txe_service/txe_service.ts +++ b/yarn-project/txe/src/txe_service/txe_service.ts @@ -43,8 +43,9 @@ export class TXEService { async getPrivateContextInputs() { const inputs = PrivateContextInputs.empty(); + const stateReference = await this.trees.getStateReference(true); inputs.historicalHeader.globalVariables.blockNumber = new Fr(this.blockNumber); - inputs.historicalHeader.state.partial = (await this.trees.getStateReference(true)).partial; + inputs.historicalHeader.state = stateReference; inputs.callContext.msgSender = AztecAddress.random(); inputs.callContext.storageContractAddress = this.contractAddress; return toForeignCallResult(inputs.toFields().map(toSingle)); From f2820a7f4865b305af3b565156c7e9eed558fe94 Mon Sep 17 00:00:00 2001 From: thunkar Date: Tue, 4 Jun 2024 14:12:18 +0000 Subject: [PATCH 20/75] configurable private inputs --- yarn-project/txe/src/txe_service/txe_service.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/yarn-project/txe/src/txe_service/txe_service.ts b/yarn-project/txe/src/txe_service/txe_service.ts index c80e9df1fb7..4c23cda4d9c 100644 --- a/yarn-project/txe/src/txe_service/txe_service.ts +++ b/yarn-project/txe/src/txe_service/txe_service.ts @@ -41,10 +41,10 @@ export class TXEService { return service; } - async getPrivateContextInputs() { + async getPrivateContextInputs(blockNumber: ForeignCallSingle) { const inputs = PrivateContextInputs.empty(); const stateReference = await this.trees.getStateReference(true); - inputs.historicalHeader.globalVariables.blockNumber = new Fr(this.blockNumber); + inputs.historicalHeader.globalVariables.blockNumber = fromSingle(blockNumber); inputs.historicalHeader.state = stateReference; inputs.callContext.msgSender = AztecAddress.random(); inputs.callContext.storageContractAddress = this.contractAddress; From cbe1564254326df2ac9605018414e44e839c9cd1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicol=C3=A1s=20Venturo?= Date: Tue, 4 Jun 2024 14:15:47 +0000 Subject: [PATCH 21/75] ??? --- .../src/state_vars/shared_mutable/test.nr | 408 +++++++++--------- .../aztec/src/test/helpers/cheatcodes.nr | 44 +- .../aztec/src/test/helpers/context_builder.nr | 21 +- 3 files changed, 244 insertions(+), 229 deletions(-) diff --git a/noir-projects/aztec-nr/aztec/src/state_vars/shared_mutable/test.nr b/noir-projects/aztec-nr/aztec/src/state_vars/shared_mutable/test.nr index 5875740b350..d21a0f4f54e 100644 --- a/noir-projects/aztec-nr/aztec/src/state_vars/shared_mutable/test.nr +++ b/noir-projects/aztec-nr/aztec/src/state_vars/shared_mutable/test.nr @@ -23,9 +23,9 @@ global new_value = 57; global pre_delay = 20; global post_delay = 15; -global TEST_INITIAL_DELAY = 3; +global TEST_INITIAL_DELAY: u32 = 3; -fn setup() -> (SharedMutable, Field) { +fn setup() -> (SharedMutable, u32) { let block_number = 40; let mut context = ContextBuilder::new().block_number(block_number).public(); @@ -120,270 +120,276 @@ fn assert_delay_change_write( fn test_get_current_value_in_public() { let (state_var, block_number) = setup(); - // Change in the future, current value is pre - mock_value_change_read(state_var, pre_value, post_value, block_number + 1); - assert_eq(state_var.get_current_value_in_public(), pre_value); + state_var.schedule_value_change(new_value); + + let (scheduled_value, block_of_change) = state_var.get_scheduled_value_in_public(); + + assert_eq(scheduled_value, new_value); + assert_eq(block_of_change, block_number + TEST_INITIAL_DELAY); + assert_eq(state_var.get_current_value_in_public(), 0); // initial value - // Change in the current block, current value is post - mock_value_change_read(state_var, pre_value, post_value, block_number); - assert_eq(state_var.get_current_value_in_public(), post_value); + cheatcodes::advance_block_to(block_of_change); - // Change in the past, current value is post - mock_value_change_read(state_var, pre_value, post_value, block_number - 1); - assert_eq(state_var.get_current_value_in_public(), post_value); + assert_eq(state_var.get_current_value_in_public(), new_value); + assert_eq(state_var.get_scheduled_value_in_public(), (scheduled_value, block_of_change)); + + cheatcodes::advance_blocks(10); + + assert_eq(state_var.get_current_value_in_public(), new_value); + assert_eq(state_var.get_scheduled_value_in_public(), (scheduled_value, block_of_change)); } #[test] fn test_get_scheduled_value_in_public() { let (state_var, block_number) = setup(); - state_var.schedule_value_change(post_value); + state_var.schedule_value_change(new_value); - // Change in the future, scheduled is post (always is) assert_eq( - state_var.get_scheduled_value_in_public(), (post_value, (block_number + TEST_INITIAL_DELAY) as u32) + state_var.get_scheduled_value_in_public(), (new_value, (block_number + TEST_INITIAL_DELAY) as u32) ); - cheatcodes::advance_blocks(TEST_INITIAL_DELAY); + cheatcodes::advance_blocks(TEST_INITIAL_DELAY * 2); assert_eq( - state_var.get_scheduled_value_in_public(), (post_value, (block_number + TEST_INITIAL_DELAY) as u32) + state_var.get_scheduled_value_in_public(), (new_value, (block_number + TEST_INITIAL_DELAY) as u32) ); } -#[test] -fn test_get_current_delay_in_public() { - let (state_var, block_number) = setup(); +// #[test] +// fn test_get_current_delay_in_public() { +// let (state_var, block_number) = setup(); - // Uninitialized - mock_delay_change_read_uninitialized(state_var); - assert_eq(state_var.get_current_delay_in_public(), TEST_INITIAL_DELAY as u32); +// // Uninitialized +// mock_delay_change_read_uninitialized(state_var); +// assert_eq(state_var.get_current_delay_in_public(), TEST_INITIAL_DELAY as u32); - // Change in the future, current value is pre - mock_delay_change_read(state_var, pre_delay, post_delay, block_number + 1); - assert_eq(state_var.get_current_delay_in_public(), pre_delay as u32); +// // Change in the future, current value is pre +// mock_delay_change_read(state_var, pre_delay, post_delay, block_number + 1); +// assert_eq(state_var.get_current_delay_in_public(), pre_delay as u32); - // Change in the current block, current value is post - mock_delay_change_read(state_var, pre_delay, post_delay, block_number); - assert_eq(state_var.get_current_delay_in_public(), post_delay as u32); +// // Change in the current block, current value is post +// mock_delay_change_read(state_var, pre_delay, post_delay, block_number); +// assert_eq(state_var.get_current_delay_in_public(), post_delay as u32); - // Change in the past, current value is post - mock_delay_change_read(state_var, pre_delay, post_delay, block_number - 1); - assert_eq(state_var.get_current_delay_in_public(), post_delay as u32); -} +// // Change in the past, current value is post +// mock_delay_change_read(state_var, pre_delay, post_delay, block_number - 1); +// assert_eq(state_var.get_current_delay_in_public(), post_delay as u32); +// } -#[test] -fn test_get_scheduled_delay_in_public_before_change() { - let (state_var, block_number) = setup(); +// #[test] +// fn test_get_scheduled_delay_in_public_before_change() { +// let (state_var, block_number) = setup(); - // Uninitialized - mock_delay_change_read_uninitialized(state_var); - assert_eq(state_var.get_scheduled_delay_in_public(), (TEST_INITIAL_DELAY as u32, 0)); +// // Uninitialized +// mock_delay_change_read_uninitialized(state_var); +// assert_eq(state_var.get_scheduled_delay_in_public(), (TEST_INITIAL_DELAY as u32, 0)); - // Change in the future, scheduled is post (always is) - mock_delay_change_read(state_var, pre_delay, post_delay, block_number + 1); - assert_eq(state_var.get_scheduled_delay_in_public(), (post_delay as u32, (block_number + 1) as u32)); +// // Change in the future, scheduled is post (always is) +// mock_delay_change_read(state_var, pre_delay, post_delay, block_number + 1); +// assert_eq(state_var.get_scheduled_delay_in_public(), (post_delay as u32, (block_number + 1) as u32)); - // Change in the current block, scheduled is post (always is) - mock_delay_change_read(state_var, pre_delay, post_delay, block_number); - assert_eq(state_var.get_scheduled_delay_in_public(), (post_delay as u32, block_number as u32)); +// // Change in the current block, scheduled is post (always is) +// mock_delay_change_read(state_var, pre_delay, post_delay, block_number); +// assert_eq(state_var.get_scheduled_delay_in_public(), (post_delay as u32, block_number as u32)); - // Change in the past, scheduled is post (always is) - mock_delay_change_read(state_var, pre_delay, post_delay, block_number - 1); - assert_eq(state_var.get_scheduled_delay_in_public(), (post_delay as u32, (block_number - 1) as u32)); -} +// // Change in the past, scheduled is post (always is) +// mock_delay_change_read(state_var, pre_delay, post_delay, block_number - 1); +// assert_eq(state_var.get_scheduled_delay_in_public(), (post_delay as u32, (block_number - 1) as u32)); +// } -#[test] -fn test_schedule_value_change_no_delay() { - let (state_var, block_number) = setup(); +// #[test] +// fn test_schedule_value_change_no_delay() { +// let (state_var, block_number) = setup(); - // Last value change was in the past - mock_value_change_read(state_var, pre_value, post_value, 0); +// // Last value change was in the past +// mock_value_change_read(state_var, pre_value, post_value, 0); - // Current delay is 0 - mock_delay_change_read(state_var, 0, 0, block_number); +// // Current delay is 0 +// mock_delay_change_read(state_var, 0, 0, block_number); - let write_mock = mock_value_change_write(); +// let write_mock = mock_value_change_write(); - state_var.schedule_value_change(new_value); +// state_var.schedule_value_change(new_value); - // The new value has a block of change equal to the current block, i.e. it is the current value - assert_value_change_write(state_var, write_mock, post_value, new_value, block_number); -} +// // The new value has a block of change equal to the current block, i.e. it is the current value +// assert_value_change_write(state_var, write_mock, post_value, new_value, block_number); +// } -#[test] -fn test_schedule_value_change_before_change_no_scheduled_delay() { - let (state_var, block_number) = setup(); +// #[test] +// fn test_schedule_value_change_before_change_no_scheduled_delay() { +// let (state_var, block_number) = setup(); - // Value change in the future, delay change in the past - mock_value_and_delay_read(state_var, block_number + 1, block_number - 1); - let write_mock = mock_value_change_write(); +// // Value change in the future, delay change in the past +// mock_value_and_delay_read(state_var, block_number + 1, block_number - 1); +// let write_mock = mock_value_change_write(); - state_var.schedule_value_change(new_value); +// state_var.schedule_value_change(new_value); - // The new scheduled value change replaces the old one, post delay (current) is used - assert_value_change_write( - state_var, - write_mock, - pre_value, - new_value, - block_number + post_delay - ); -} +// // The new scheduled value change replaces the old one, post delay (current) is used +// assert_value_change_write( +// state_var, +// write_mock, +// pre_value, +// new_value, +// block_number + post_delay +// ); +// } -#[test] -fn test_schedule_value_change_before_change_scheduled_delay() { - let (state_var, block_number) = setup(); +// #[test] +// fn test_schedule_value_change_before_change_scheduled_delay() { +// let (state_var, block_number) = setup(); - // Value change in the future, delay change in the future - mock_value_and_delay_read(state_var, block_number + 1, block_number + 1); +// // Value change in the future, delay change in the future +// mock_value_and_delay_read(state_var, block_number + 1, block_number + 1); - let write_mock = mock_value_change_write(); +// let write_mock = mock_value_change_write(); - state_var.schedule_value_change(new_value); +// state_var.schedule_value_change(new_value); - // The new scheduled value change replaces the old one, pre delay (current, not scheduled) is used - assert_value_change_write( - state_var, - write_mock, - pre_value, - new_value, - block_number + pre_delay - ); -} +// // The new scheduled value change replaces the old one, pre delay (current, not scheduled) is used +// assert_value_change_write( +// state_var, +// write_mock, +// pre_value, +// new_value, +// block_number + pre_delay +// ); +// } -#[test] -fn test_schedule_value_change_after_change_no_scheduled_delay() { - let (state_var, block_number) = setup(); +// #[test] +// fn test_schedule_value_change_after_change_no_scheduled_delay() { +// let (state_var, block_number) = setup(); - // Value change in the past, delay change in the past - mock_value_and_delay_read(state_var, block_number - 1, block_number - 1); - let write_mock = mock_value_change_write(); +// // Value change in the past, delay change in the past +// mock_value_and_delay_read(state_var, block_number - 1, block_number - 1); +// let write_mock = mock_value_change_write(); - state_var.schedule_value_change(new_value); +// state_var.schedule_value_change(new_value); - // The previous post value becomes the pre value, post delay (current) is used - assert_value_change_write( - state_var, - write_mock, - post_value, - new_value, - block_number + post_delay - ); -} +// // The previous post value becomes the pre value, post delay (current) is used +// assert_value_change_write( +// state_var, +// write_mock, +// post_value, +// new_value, +// block_number + post_delay +// ); +// } -#[test] -fn test_schedule_value_change_after_change_scheduled_delay() { - let (state_var, block_number) = setup(); +// #[test] +// fn test_schedule_value_change_after_change_scheduled_delay() { +// let (state_var, block_number) = setup(); - // Value change in the past, delay change in the future - mock_value_and_delay_read(state_var, block_number - 1, block_number + 1); +// // Value change in the past, delay change in the future +// mock_value_and_delay_read(state_var, block_number - 1, block_number + 1); - let write_mock = mock_value_change_write(); +// let write_mock = mock_value_change_write(); - state_var.schedule_value_change(new_value); +// state_var.schedule_value_change(new_value); - // The previous post value becomes the pre value, pre delay (current, not scheduled) is used - assert_value_change_write( - state_var, - write_mock, - post_value, - new_value, - block_number + pre_delay - ); -} +// // The previous post value becomes the pre value, pre delay (current, not scheduled) is used +// assert_value_change_write( +// state_var, +// write_mock, +// post_value, +// new_value, +// block_number + pre_delay +// ); +// } -#[test] -fn test_schedule_delay_increase_before_change() { - let (state_var, block_number) = setup(); +// #[test] +// fn test_schedule_delay_increase_before_change() { +// let (state_var, block_number) = setup(); - // Delay change in future, current delay is pre - mock_delay_change_read(state_var, pre_delay, post_delay, block_number + 1); - let write_mock = mock_delay_change_write(); +// // Delay change in future, current delay is pre +// mock_delay_change_read(state_var, pre_delay, post_delay, block_number + 1); +// let write_mock = mock_delay_change_write(); - let new_delay = pre_delay + 1; - state_var.schedule_delay_change(new_delay as u32); +// let new_delay = pre_delay + 1; +// state_var.schedule_delay_change(new_delay as u32); - // The previous scheduled change is lost, change is immediate (due to increase) - assert_delay_change_write(state_var, write_mock, pre_delay, new_delay, block_number); -} +// // The previous scheduled change is lost, change is immediate (due to increase) +// assert_delay_change_write(state_var, write_mock, pre_delay, new_delay, block_number); +// } -#[test] -fn test_schedule_delay_reduction_before_change() { - let (state_var, block_number) = setup(); +// #[test] +// fn test_schedule_delay_reduction_before_change() { +// let (state_var, block_number) = setup(); - // Delay change in future, current delay is pre - mock_delay_change_read(state_var, pre_delay, post_delay, block_number + 1); - let write_mock = mock_delay_change_write(); +// // Delay change in future, current delay is pre +// mock_delay_change_read(state_var, pre_delay, post_delay, block_number + 1); +// let write_mock = mock_delay_change_write(); + +// let new_delay = pre_delay - 1; +// state_var.schedule_delay_change(new_delay as u32); + +// // The previous scheduled change is lost, change delay equals difference (due to reduction) +// assert_delay_change_write( +// state_var, +// write_mock, +// pre_delay, +// new_delay, +// block_number + pre_delay - new_delay +// ); +// } - let new_delay = pre_delay - 1; - state_var.schedule_delay_change(new_delay as u32); +// #[test] +// fn test_schedule_delay_increase_after_change() { +// let (state_var, block_number) = setup(); - // The previous scheduled change is lost, change delay equals difference (due to reduction) - assert_delay_change_write( - state_var, - write_mock, - pre_delay, - new_delay, - block_number + pre_delay - new_delay - ); -} +// // Delay change in the past, current delay is post +// mock_delay_change_read(state_var, pre_delay, post_delay, block_number - 1); +// let write_mock = mock_delay_change_write(); -#[test] -fn test_schedule_delay_increase_after_change() { - let (state_var, block_number) = setup(); +// let new_delay = post_delay + 1; +// state_var.schedule_delay_change(new_delay as u32); - // Delay change in the past, current delay is post - mock_delay_change_read(state_var, pre_delay, post_delay, block_number - 1); - let write_mock = mock_delay_change_write(); +// // The current value becomes pre, change is immediate (due to increase) +// assert_delay_change_write(state_var, write_mock, post_delay, new_delay, block_number); +// } - let new_delay = post_delay + 1; - state_var.schedule_delay_change(new_delay as u32); +// #[test] +// fn test_schedule_delay_reduction_after_change() { +// let (state_var, block_number) = setup(); - // The current value becomes pre, change is immediate (due to increase) - assert_delay_change_write(state_var, write_mock, post_delay, new_delay, block_number); -} +// // Delay change in the past, current delay is post +// mock_delay_change_read(state_var, pre_delay, post_delay, block_number - 1); +// let write_mock = mock_delay_change_write(); + +// let new_delay = post_delay - 1; +// state_var.schedule_delay_change(new_delay as u32); + +// // The current value becomes pre, change delay equals difference (due to reduction) +// assert_delay_change_write( +// state_var, +// write_mock, +// post_delay, +// new_delay, +// block_number + post_delay - new_delay +// ); +// } #[test] -fn test_schedule_delay_reduction_after_change() { +fn test_get_current_value_in_private_before_change() { let (state_var, block_number) = setup(); + state_var.schedule_value_change(new_value); - // Delay change in the past, current delay is post - mock_delay_change_read(state_var, pre_delay, post_delay, block_number - 1); - let write_mock = mock_delay_change_write(); - - let new_delay = post_delay - 1; - state_var.schedule_delay_change(new_delay as u32); + cheatcodes::advance_blocks(1); - // The current value becomes pre, change delay equals difference (due to reduction) - assert_delay_change_write( - state_var, - write_mock, - post_delay, - new_delay, - block_number + post_delay - new_delay - ); + let block_number = 40; + let mut context = ContextBuilder::new().block_number(block_number).private(); + let storage_slot = 57; + let private_state_var: SharedMutable = SharedMutable::new(&mut context, storage_slot); + + assert_eq(private_state_var.get_current_value_in_private(), 0); + // Here we'd want to test that the private getter returns the correct value and sets max_block_number in the + // context to the expected block horizon, in all the possible scenarios (long before change, before near change, + // after change). + // However, this requires mocking the getPublicDataTreeWitness oracle so that we can convince the circuit that + // it got a valid historical proof. Because we can set the tree root to whatever we want in the context, this is + // trivial for a single historical value (we add a leaf and compute the root with any random path), but is quite + // hard if we're reading more than one value for the same root (as SharedMutable does): we essentially need to + // create an actual indexed tree and compute the correct path for each of the inserted values. + // TODO: implement an actual tree and use it here https://github.com/AztecProtocol/aztec-packages/issues/5494 } - -// #[test] -// fn test_get_current_value_in_private_before_change() { -// let (state_var, block_number) = setup(); -// state_var.schedule_value_change(5); - -// let block_number = 40; -// let mut context = ContextBuilder::new().block_number(block_number).private(); - -// let storage_slot = 57; -// let private_state_var: SharedMutable = SharedMutable::new(&mut context, storage_slot); - -// let foo = private_state_var.get_current_value_in_private(); -// // Here we'd want to test that the private getter returns the correct value and sets max_block_number in the -// // context to the expected block horizon, in all the possible scenarios (long before change, before near change, -// // after change). -// // However, this requires mocking the getPublicDataTreeWitness oracle so that we can convince the circuit that -// // it got a valid historical proof. Because we can set the tree root to whatever we want in the context, this is -// // trivial for a single historical value (we add a leaf and compute the root with any random path), but is quite -// // hard if we're reading more than one value for the same root (as SharedMutable does): we essentially need to -// // create an actual indexed tree and compute the correct path for each of the inserted values. -// // TODO: implement an actual tree and use it here https://github.com/AztecProtocol/aztec-packages/issues/5494 -// } diff --git a/noir-projects/aztec-nr/aztec/src/test/helpers/cheatcodes.nr b/noir-projects/aztec-nr/aztec/src/test/helpers/cheatcodes.nr index 5a3fc46f0c4..39c4d304cb6 100644 --- a/noir-projects/aztec-nr/aztec/src/test/helpers/cheatcodes.nr +++ b/noir-projects/aztec-nr/aztec/src/test/helpers/cheatcodes.nr @@ -1,42 +1,50 @@ use dep::protocol_types::address::AztecAddress; - -#[oracle(reset)] -fn oracle_reset() {} +use crate::context::inputs::PrivateContextInputs; unconstrained pub fn reset() { oracle_reset(); } -#[oracle(getContractAddress)] -fn oracle_get_contract_address() -> AztecAddress {} - unconstrained pub fn get_contract_address() -> AztecAddress { oracle_get_contract_address() } -#[oracle(setContractAddress)] -fn oracle_set_contract_address(address: AztecAddress) {} - unconstrained pub fn set_contract_address(address: AztecAddress) { oracle_set_contract_address(address); } -#[oracle(getBlockNumber)] -fn oracle_get_block_number() -> Field {} - -unconstrained pub fn get_block_number() -> Field { +unconstrained pub fn get_block_number() -> u32 { oracle_get_block_number() } -#[oracle(timeTravel)] -fn oracle_time_travel(blocks: Field) {} - -unconstrained pub fn advance_blocks(blocks: Field) { +unconstrained pub fn advance_blocks(blocks: u32) { oracle_time_travel(blocks); } -unconstrained pub fn advance_block_to(block_number: Field) { +unconstrained pub fn advance_block_to(block_number: u32) { let difference = block_number - get_block_number(); advance_blocks(difference); } +unconstrained pub fn get_private_context_inputs(block_number: u32) -> PrivateContextInputs { + oracle_get_private_context_inputs(block_number) +} + +#[oracle(reset)] +fn oracle_reset() {} + +#[oracle(getContractAddress)] +fn oracle_get_contract_address() -> AztecAddress {} + +#[oracle(setContractAddress)] +fn oracle_set_contract_address(address: AztecAddress) {} + +#[oracle(getBlockNumber)] +fn oracle_get_block_number() -> u32 {} + +#[oracle(timeTravel)] +fn oracle_time_travel(blocks: u32) {} + +#[oracle(getPrivateContextInputs)] +fn oracle_get_private_context_inputs(block_number: u32) -> PrivateContextInputs {} + diff --git a/noir-projects/aztec-nr/aztec/src/test/helpers/context_builder.nr b/noir-projects/aztec-nr/aztec/src/test/helpers/context_builder.nr index 1b069ea0214..6a99fe6241b 100644 --- a/noir-projects/aztec-nr/aztec/src/test/helpers/context_builder.nr +++ b/noir-projects/aztec-nr/aztec/src/test/helpers/context_builder.nr @@ -2,8 +2,10 @@ use crate::context::{PrivateContext, PublicContext}; use crate::test::helpers::cheatcodes; use dep::protocol_types::address::AztecAddress; +use dep::protocol_types::debug_log; + struct ContextBuilder { - block_number: Option, + block_number: Option, contract_address: Option, } @@ -12,7 +14,7 @@ impl ContextBuilder { Self { block_number: Option::none(), contract_address: Option::none() } } - fn block_number(&mut self, block_number: Field) -> &mut Self { + fn block_number(&mut self, block_number: u32) -> &mut Self { self.block_number = Option::some(block_number); self } @@ -24,35 +26,34 @@ impl ContextBuilder { fn private(self) -> PrivateContext { cheatcodes::reset(); - let mut context = PrivateContext::empty(); if self.block_number.is_some() { let block_number = self.block_number.unwrap_unchecked(); - context.inputs.historical_header.global_variables.block_number = block_number; cheatcodes::advance_block_to(block_number); } if self.contract_address.is_some() { let contract_address = self.contract_address.unwrap_unchecked(); - context.inputs.call_context.storage_contract_address = contract_address; cheatcodes::set_contract_address(contract_address); } - context + let inputs = cheatcodes::get_private_context_inputs(self.block_number.unwrap()); + + let args_hash = 0; + PrivateContext::new(inputs, args_hash) } fn public(self) -> PublicContext { cheatcodes::reset(); + let mut context = PublicContext::empty(); if self.block_number.is_some() { - let block_number = self.block_number.unwrap_unchecked(); - cheatcodes::advance_block_to(block_number); + cheatcodes::advance_block_to(self.block_number.unwrap_unchecked()); } if self.contract_address.is_some() { - let contract_address = self.contract_address.unwrap_unchecked(); - cheatcodes::set_contract_address(contract_address); + cheatcodes::set_contract_address(self.contract_address.unwrap_unchecked()); } context From 2250b98af50f94ab03bf6daaf1fd2309f5746e75 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicol=C3=A1s=20Venturo?= Date: Tue, 4 Jun 2024 14:36:43 +0000 Subject: [PATCH 22/75] ??? --- .../src/state_vars/public_immutable/test.nr | 4 ++- .../src/state_vars/shared_mutable/test.nr | 25 ++++++++++++++----- .../aztec/src/test/helpers/context_builder.nr | 14 ----------- 3 files changed, 22 insertions(+), 21 deletions(-) diff --git a/noir-projects/aztec-nr/aztec/src/state_vars/public_immutable/test.nr b/noir-projects/aztec-nr/aztec/src/state_vars/public_immutable/test.nr index 59e7b0a37ef..d1e84deae03 100644 --- a/noir-projects/aztec-nr/aztec/src/state_vars/public_immutable/test.nr +++ b/noir-projects/aztec-nr/aztec/src/state_vars/public_immutable/test.nr @@ -1,9 +1,11 @@ use crate::{context::PublicContext, state_vars::public_immutable::PublicImmutable}; -use crate::test::{helpers::context_builder::ContextBuilder, mocks::mock_struct::MockStruct}; +use crate::test::{helpers::{cheatcodes, context_builder::ContextBuilder}, mocks::mock_struct::MockStruct}; use dep::std::test::OracleMock; use dep::protocol_types::traits::Serialize; fn setup() -> PublicImmutable { + cheatcodes::reset(); + let mut context = ContextBuilder::new().public(); let storage_slot = 7; diff --git a/noir-projects/aztec-nr/aztec/src/state_vars/shared_mutable/test.nr b/noir-projects/aztec-nr/aztec/src/state_vars/shared_mutable/test.nr index d21a0f4f54e..98e61e46754 100644 --- a/noir-projects/aztec-nr/aztec/src/state_vars/shared_mutable/test.nr +++ b/noir-projects/aztec-nr/aztec/src/state_vars/shared_mutable/test.nr @@ -26,6 +26,8 @@ global post_delay = 15; global TEST_INITIAL_DELAY: u32 = 3; fn setup() -> (SharedMutable, u32) { + cheatcodes::reset(); + let block_number = 40; let mut context = ContextBuilder::new().block_number(block_number).public(); @@ -35,6 +37,13 @@ fn setup() -> (SharedMutable, u32 (state_var, block_number) } +fn private_setup(block_number: u32) -> SharedMutable { + let mut context = ContextBuilder::new().block_number(block_number).private(); + + let storage_slot = 57; + SharedMutable::new(&mut context, storage_slot) +} + fn mock_value_change_read( state_var: SharedMutable, pre: Field, @@ -375,14 +384,18 @@ fn test_get_current_value_in_private_before_change() { let (state_var, block_number) = setup(); state_var.schedule_value_change(new_value); - cheatcodes::advance_blocks(1); + let (_, block_horizon) = state_var.get_scheduled_value_in_public(); - let block_number = 40; - let mut context = ContextBuilder::new().block_number(block_number).private(); - let storage_slot = 57; - let private_state_var: SharedMutable = SharedMutable::new(&mut context, storage_slot); + cheatcodes::advance_blocks(100); + + let pre_change_state_var = private_setup(block_number); + assert_eq(pre_change_state_var.get_current_value_in_private(), 0); + + let pre_change_state_var = private_setup(block_horizon - 1); + assert_eq(pre_change_state_var.get_current_value_in_private(), 0); - assert_eq(private_state_var.get_current_value_in_private(), 0); + let pre_change_state_var = private_setup(block_horizon); + assert_eq(pre_change_state_var.get_current_value_in_private(), new_value); // Here we'd want to test that the private getter returns the correct value and sets max_block_number in the // context to the expected block horizon, in all the possible scenarios (long before change, before near change, // after change). diff --git a/noir-projects/aztec-nr/aztec/src/test/helpers/context_builder.nr b/noir-projects/aztec-nr/aztec/src/test/helpers/context_builder.nr index 6a99fe6241b..45e7e7571ff 100644 --- a/noir-projects/aztec-nr/aztec/src/test/helpers/context_builder.nr +++ b/noir-projects/aztec-nr/aztec/src/test/helpers/context_builder.nr @@ -25,18 +25,6 @@ impl ContextBuilder { } fn private(self) -> PrivateContext { - cheatcodes::reset(); - - if self.block_number.is_some() { - let block_number = self.block_number.unwrap_unchecked(); - cheatcodes::advance_block_to(block_number); - } - - if self.contract_address.is_some() { - let contract_address = self.contract_address.unwrap_unchecked(); - cheatcodes::set_contract_address(contract_address); - } - let inputs = cheatcodes::get_private_context_inputs(self.block_number.unwrap()); let args_hash = 0; @@ -44,8 +32,6 @@ impl ContextBuilder { } fn public(self) -> PublicContext { - cheatcodes::reset(); - let mut context = PublicContext::empty(); if self.block_number.is_some() { From 45434f9d91773c0e6aeceb3e866aeed1808995fe Mon Sep 17 00:00:00 2001 From: thunkar Date: Tue, 4 Jun 2024 16:39:36 +0000 Subject: [PATCH 23/75] fixes --- yarn-project/txe/src/oracle/txe_oracle.ts | 38 +++++++++++++++---- .../txe/src/txe_service/txe_service.ts | 3 +- 2 files changed, 32 insertions(+), 9 deletions(-) diff --git a/yarn-project/txe/src/oracle/txe_oracle.ts b/yarn-project/txe/src/oracle/txe_oracle.ts index bfc01defe11..be15cfd2859 100644 --- a/yarn-project/txe/src/oracle/txe_oracle.ts +++ b/yarn-project/txe/src/oracle/txe_oracle.ts @@ -3,18 +3,22 @@ import { type NoteStatus, type NullifierMembershipWitness, PublicDataWitness, + PublicDataWrite, type UnencryptedL2Log, } from '@aztec/circuit-types'; import { type CompleteAddress, type Header, type KeyValidationRequest, + PUBLIC_DATA_SUBTREE_HEIGHT, type PUBLIC_DATA_TREE_HEIGHT, type PrivateCallStackItem, type PublicCallRequest, + PublicDataTreeLeaf, type PublicDataTreeLeafPreimage, } from '@aztec/circuits.js'; import { Aes128 } from '@aztec/circuits.js/barretenberg'; +import { computePublicDataTreeLeafSlot } from '@aztec/circuits.js/hash'; import { type FunctionSelector } from '@aztec/foundation/abi'; import { type AztecAddress } from '@aztec/foundation/aztec-address'; import { Fr, type Point } from '@aztec/foundation/fields'; @@ -161,10 +165,23 @@ export class TXE implements TypedOracle { } async storageRead(startStorageSlot: Fr, numberOfElements: number): Promise { + const db = this.trees.asLatest(); + const values = []; for (let i = 0n; i < numberOfElements; i++) { const storageSlot = startStorageSlot.add(new Fr(i)); - const value = await this.worldStatePublicDB.storageRead(this.contractAddress, storageSlot); + const leafSlot = computePublicDataTreeLeafSlot(this.contractAddress, storageSlot).toBigInt(); + + const lowLeafResult = await db.getPreviousValueIndex(MerkleTreeId.PUBLIC_DATA_TREE, leafSlot); + + let value = Fr.ZERO; + if (lowLeafResult && lowLeafResult.alreadyPresent) { + const preimage = (await db.getLeafPreimage( + MerkleTreeId.PUBLIC_DATA_TREE, + lowLeafResult.index, + )) as PublicDataTreeLeafPreimage; + value = preimage.value; + } this.logger.debug(`Oracle storage read: slot=${storageSlot.toString()} value=${value}`); values.push(value); } @@ -172,14 +189,19 @@ export class TXE implements TypedOracle { } async storageWrite(startStorageSlot: Fr, values: Fr[]): Promise { - return await Promise.all( - values.map(async (value, i) => { - const storageSlot = startStorageSlot.add(new Fr(i)); - const result = await this.worldStatePublicDB.storageWrite(this.contractAddress, storageSlot, value); - this.logger.debug(`Oracle storage write: slot=${storageSlot.toString()} value=${value}`); - return new Fr(result); - }), + const db = this.trees.asLatest(); + + const publicDataWrites = values.map((value, i) => { + const storageSlot = startStorageSlot.add(new Fr(i)); + this.logger.debug(`Oracle storage write: slot=${storageSlot.toString()} value=${value}`); + return new PublicDataWrite(computePublicDataTreeLeafSlot(this.contractAddress, storageSlot), value); + }); + await db.batchInsert( + MerkleTreeId.PUBLIC_DATA_TREE, + publicDataWrites.map(write => new PublicDataTreeLeaf(write.leafIndex, write.newValue).toBuffer()), + PUBLIC_DATA_SUBTREE_HEIGHT, ); + return publicDataWrites.map(write => write.newValue); } emitEncryptedLog(_contractAddress: AztecAddress, _randomness: Fr, _encryptedNote: Buffer, _counter: number): void { diff --git a/yarn-project/txe/src/txe_service/txe_service.ts b/yarn-project/txe/src/txe_service/txe_service.ts index 4c23cda4d9c..373c556ec3c 100644 --- a/yarn-project/txe/src/txe_service/txe_service.ts +++ b/yarn-project/txe/src/txe_service/txe_service.ts @@ -105,7 +105,8 @@ export class TXEService { async reset() { this.blockNumber = 0; - await this.store.clear(); + this.store = openTmpStore(true); + this.trees = await MerkleTrees.new(this.store, this.logger); this.packedValuesCache = new PackedValuesCache(); await this.#timeTravelInner(1); return toForeignCallResult([]); From c180bdbf6128a71bd41da91144c4f68abc7b65e2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicol=C3=A1s=20Venturo?= Date: Tue, 4 Jun 2024 16:54:43 +0000 Subject: [PATCH 24/75] git add . --- .../aztec/src/note/note_getter/test.nr | 41 ++-- .../src/state_vars/private_mutable/test.nr | 14 +- .../src/state_vars/public_immutable/test.nr | 25 +- .../src/state_vars/shared_mutable/test.nr | 221 +++++++----------- .../aztec/src/test/helpers/cheatcodes.nr | 11 +- .../aztec/src/test/helpers/context_builder.nr | 57 +++-- 6 files changed, 173 insertions(+), 196 deletions(-) diff --git a/noir-projects/aztec-nr/aztec/src/note/note_getter/test.nr b/noir-projects/aztec-nr/aztec/src/note/note_getter/test.nr index 7229268f1e5..46020d5606e 100644 --- a/noir-projects/aztec-nr/aztec/src/note/note_getter/test.nr +++ b/noir-projects/aztec-nr/aztec/src/note/note_getter/test.nr @@ -9,13 +9,13 @@ use crate::{ }; use dep::protocol_types::address::AztecAddress; -use crate::test::{helpers::context_builder::ContextBuilder, mocks::mock_note::MockNote}; +use crate::test::{helpers::{context_builder::TestEnvironment, cheatcodes}, mocks::mock_note::MockNote}; global contract_address = AztecAddress::from_field(69); global storage_slot: Field = 42; -fn setup() -> PrivateContext { - ContextBuilder::new().contract_address(contract_address).private() +fn setup() -> TestEnvironment { + TestEnvironment::new().contract_address(contract_address) } fn build_valid_note(value: Field) -> MockNote { @@ -24,7 +24,8 @@ fn build_valid_note(value: Field) -> MockNote { #[test] fn processes_single_note() { - let mut context = setup(); + let mut env = setup(); + let mut context = env.private(); let mut notes_to_constrain = [Option::none(); MAX_NOTE_HASH_READ_REQUESTS_PER_CALL]; notes_to_constrain[0] = Option::some(build_valid_note(13)); @@ -38,7 +39,8 @@ fn processes_single_note() { #[test] fn processes_many_notes() { - let mut context = setup(); + let mut env = setup(); + let mut context = env.private(); let mut notes_to_constrain = [Option::none(); MAX_NOTE_HASH_READ_REQUESTS_PER_CALL]; notes_to_constrain[0] = Option::some(build_valid_note(13)); @@ -53,7 +55,8 @@ fn processes_many_notes() { #[test] fn collapses_notes_at_the_beginning_of_the_array() { - let mut context = setup(); + let mut env = setup(); + let mut context = env.private(); let mut opt_notes = [Option::none(); MAX_NOTE_HASH_READ_REQUESTS_PER_CALL]; opt_notes[1] = Option::some(build_valid_note(0)); @@ -80,8 +83,9 @@ fn collapses_notes_at_the_beginning_of_the_array() { } #[test(should_fail_with="Cannot return zero notes")] - fn rejects_zero_notes() { - let mut context = setup(); +fn rejects_zero_notes() { + let mut env = setup(); + let mut context = env.private(); let opt_notes: [Option; MAX_NOTE_HASH_READ_REQUESTS_PER_CALL] = [Option::none(); MAX_NOTE_HASH_READ_REQUESTS_PER_CALL]; @@ -91,7 +95,8 @@ fn collapses_notes_at_the_beginning_of_the_array() { #[test(should_fail_with="Got more notes than limit.")] fn rejects_mote_notes_than_limit() { - let mut context = setup(); + let mut env = setup(); + let mut context = env.private(); let mut opt_notes: [Option; MAX_NOTE_HASH_READ_REQUESTS_PER_CALL] = [Option::none(); MAX_NOTE_HASH_READ_REQUESTS_PER_CALL]; opt_notes[1] = Option::some(build_valid_note(0)); @@ -105,7 +110,8 @@ fn rejects_mote_notes_than_limit() { #[test] fn applies_filter_before_constraining() { - let mut context = setup(); + let mut env = setup(); + let mut context = env.private(); let mut notes_to_constrain = [Option::none(); MAX_NOTE_HASH_READ_REQUESTS_PER_CALL]; let invalid_note = MockNote::new(13).build(); // This note does not have the correct address or storage slot @@ -138,7 +144,8 @@ fn applies_filter_before_constraining() { #[test(should_fail_with="Mismatch note header contract address.")] fn rejects_mismatched_address() { - let mut context = setup(); + let mut env = setup(); + let mut context = env.private(); let note = MockNote::new(1).storage_slot(storage_slot).build(); // We're not setting the right contract address @@ -151,7 +158,8 @@ fn rejects_mismatched_address() { #[test(should_fail_with="Mismatch note header storage slot.")] fn rejects_mismatched_storage_slot() { - let mut context = setup(); + let mut env = setup(); + let mut context = env.private(); let note = MockNote::new(1).contract_address(contract_address).build(); // We're not setting the right storage slot @@ -164,7 +172,8 @@ fn rejects_mismatched_storage_slot() { #[test(should_fail_with="Mismatch return note field.")] fn rejects_mismatched_selector() { - let mut context = setup(); + let mut env = setup(); + let mut context = env.private(); let value = 10; let note = build_valid_note(value); @@ -184,7 +193,8 @@ fn rejects_mismatched_selector() { #[test(should_fail_with="Return notes not sorted in descending order.")] fn rejects_mismatched_desc_sort_order() { - let mut context = setup(); + let mut env = setup(); + let mut context = env.private(); let mut opt_notes = [Option::none(); MAX_NOTE_HASH_READ_REQUESTS_PER_CALL]; // Notes in ascending order @@ -201,7 +211,8 @@ fn rejects_mismatched_desc_sort_order() { #[test(should_fail_with="Return notes not sorted in ascending order.")] fn rejects_mismatched_asc_sort_order() { - let mut context = setup(); + let mut env = setup(); + let mut context = env.private(); let mut opt_notes = [Option::none(); MAX_NOTE_HASH_READ_REQUESTS_PER_CALL]; // Notes in descending order diff --git a/noir-projects/aztec-nr/aztec/src/state_vars/private_mutable/test.nr b/noir-projects/aztec-nr/aztec/src/state_vars/private_mutable/test.nr index f0272af161f..fce59247639 100644 --- a/noir-projects/aztec-nr/aztec/src/state_vars/private_mutable/test.nr +++ b/noir-projects/aztec-nr/aztec/src/state_vars/private_mutable/test.nr @@ -1,14 +1,17 @@ use dep::protocol_types::{address::AztecAddress, grumpkin_point::GrumpkinPoint}; use crate::{context::PrivateContext, state_vars::private_mutable::PrivateMutable}; -use crate::test::{mocks::mock_note::MockNote, helpers::context_builder::ContextBuilder}; +use crate::test::{mocks::mock_note::MockNote, helpers::context_builder::TestEnvironment}; use dep::std::{unsafe::zeroed, test::OracleMock}; global contract_address = AztecAddress::from_field(13); global storage_slot = 17; -fn setup() -> PrivateMutable { - let mut context = ContextBuilder::new().contract_address(contract_address).private(); - let state_var = PrivateMutable::new(&mut context, storage_slot); +fn setup() -> TestEnvironment { + TestEnvironment::new().contract_address(contract_address) +} + +fn in_private(env: &mut TestEnvironment) -> PrivateMutable { + let state_var = PrivateMutable::new(&mut env.private(), storage_slot); // This oracle is called for its side effects alone - it's always expected to return 0. OracleMock::mock("notifyCreatedNote").returns(0); @@ -18,7 +21,8 @@ fn setup() -> PrivateMutable { #[test] fn test_initialize_or_replace_without_nullifier() { - let state_var = setup(); + let mut env = setup(); + let state_var = in_private(&mut env); let ovpk_m: GrumpkinPoint = zeroed(); let ivpk_m: GrumpkinPoint = zeroed(); diff --git a/noir-projects/aztec-nr/aztec/src/state_vars/public_immutable/test.nr b/noir-projects/aztec-nr/aztec/src/state_vars/public_immutable/test.nr index d1e84deae03..a54640ecaa1 100644 --- a/noir-projects/aztec-nr/aztec/src/state_vars/public_immutable/test.nr +++ b/noir-projects/aztec-nr/aztec/src/state_vars/public_immutable/test.nr @@ -1,27 +1,30 @@ use crate::{context::PublicContext, state_vars::public_immutable::PublicImmutable}; -use crate::test::{helpers::{cheatcodes, context_builder::ContextBuilder}, mocks::mock_struct::MockStruct}; +use crate::test::{helpers::context_builder::TestEnvironment, mocks::mock_struct::MockStruct}; use dep::std::test::OracleMock; use dep::protocol_types::traits::Serialize; -fn setup() -> PublicImmutable { - cheatcodes::reset(); +global storage_slot = 7; - let mut context = ContextBuilder::new().public(); - let storage_slot = 7; +fn setup() -> TestEnvironment { + TestEnvironment::new() +} - PublicImmutable::new(&mut context, storage_slot) +fn in_public(env: TestEnvironment) -> PublicImmutable { + PublicImmutable::new(&mut env.public(), storage_slot) } #[test] fn test_uninitialized_by_default() { - let state_var = setup(); + let env = setup(); + let state_var = in_public(env); assert_eq(state_var.is_initialized(), false); } #[test] fn test_initialize_uninitialized() { - let state_var = setup(); + let env = setup(); + let state_var = in_public(env); let value = MockStruct::new(5, 6); @@ -33,7 +36,8 @@ fn test_initialize_uninitialized() { #[test(should_fail_with="PublicImmutable already initialized")] fn test_initialize_already_initialized() { - let state_var = setup(); + let env = setup(); + let state_var = in_public(env); let value = MockStruct::new(5, 6); @@ -43,7 +47,8 @@ fn test_initialize_already_initialized() { #[test(should_fail_with="PublicImmutable not initialized")] fn test_read_uninitialized() { - let state_var = setup(); + let env = setup(); + let state_var = in_public(env); let _ = state_var.read(); } diff --git a/noir-projects/aztec-nr/aztec/src/state_vars/shared_mutable/test.nr b/noir-projects/aztec-nr/aztec/src/state_vars/shared_mutable/test.nr index 98e61e46754..f81b9bc499e 100644 --- a/noir-projects/aztec-nr/aztec/src/state_vars/shared_mutable/test.nr +++ b/noir-projects/aztec-nr/aztec/src/state_vars/shared_mutable/test.nr @@ -6,8 +6,7 @@ use crate::{ shared_mutable::SharedMutable, scheduled_value_change::ScheduledValueChange, scheduled_delay_change::ScheduledDelayChange }, - test::helpers::{cheatcodes, context_builder::ContextBuilder}, - oracle::get_public_data_witness::PublicDataWitness + test::helpers::context_builder::TestEnvironment, oracle::get_public_data_witness::PublicDataWitness }; use dep::protocol_types::{ @@ -15,154 +14,82 @@ use dep::protocol_types::{ address::AztecAddress, public_data_tree_leaf_preimage::PublicDataTreeLeafPreimage }; -global pre_value = 13; -global post_value = 42; - global new_value = 57; global pre_delay = 20; global post_delay = 15; -global TEST_INITIAL_DELAY: u32 = 3; - -fn setup() -> (SharedMutable, u32) { - cheatcodes::reset(); +global storage_slot = 57; - let block_number = 40; - let mut context = ContextBuilder::new().block_number(block_number).public(); - - let storage_slot = 57; - let state_var = SharedMutable::new(&mut context, storage_slot); +global TEST_INITIAL_DELAY: u32 = 3; - (state_var, block_number) +fn setup() -> TestEnvironment { + TestEnvironment::new() } -fn private_setup(block_number: u32) -> SharedMutable { - let mut context = ContextBuilder::new().block_number(block_number).private(); - - let storage_slot = 57; - SharedMutable::new(&mut context, storage_slot) +fn in_public(env: TestEnvironment) -> SharedMutable { + SharedMutable::new(&mut env.public(), storage_slot) } -fn mock_value_change_read( - state_var: SharedMutable, - pre: Field, - post: Field, - block_of_change: Field -) { - let value_change_slot = state_var.get_value_change_storage_slot(); - let fields = ScheduledValueChange::new(pre, post, block_of_change as u32).serialize(); - - let _ = OracleMock::mock("storageRead").with_params((value_change_slot, 3)).returns(fields).times(1); +fn in_private( + env: &mut TestEnvironment, + historical_block_number: u32 +) -> SharedMutable { + SharedMutable::new(&mut env.private_at(historical_block_number), storage_slot) } -fn mock_delay_change_read( - state_var: SharedMutable, - pre: Field, - post: Field, - block_of_change: Field -) { - let delay_change_slot = state_var.get_delay_change_storage_slot(); - let delay_change: ScheduledDelayChange = ScheduledDelayChange::new( - Option::some(pre as u32), - Option::some(post as u32), - block_of_change as u32 - ); - let fields = delay_change.serialize(); - - let _ = OracleMock::mock("storageRead").with_params((delay_change_slot, 1)).returns(fields).times(1); -} +#[test] +fn test_get_current_value_in_public_initial() { + let env = setup(); + let state_var = in_public(env); -fn mock_delay_change_read_uninitialized(state_var: SharedMutable) { - let delay_change_slot = state_var.get_delay_change_storage_slot(); - let _ = OracleMock::mock("storageRead").with_params((delay_change_slot, 1)).returns([0]).times(1); + // 0 is the default empty value for a Field + assert_eq(state_var.get_current_value_in_public(), 0); } -// Useful since change and delay values are always the global pre/post ones, so we typically only care about their -// block of change. -fn mock_value_and_delay_read( - state_var: SharedMutable, - value_block_of_change: Field, - delay_block_of_change: Field -) { - mock_value_change_read(state_var, pre_value, post_value, value_block_of_change); - mock_delay_change_read(state_var, pre_delay, post_delay, delay_block_of_change); -} +#[test] +fn test_get_current_value_in_public_before_scheduled_change() { + let mut env = setup(); + let state_var = in_public(env); -fn mock_value_change_write() -> OracleMock { - OracleMock::mock("storageWrite").returns([0; 3]) -} + state_var.schedule_value_change(new_value); -fn mock_delay_change_write() -> OracleMock { - OracleMock::mock("storageWrite").returns([0; 1]) -} + let (_, block_of_change) = state_var.get_scheduled_value_in_public(); -fn assert_value_change_write( - state_var: SharedMutable, - mock: OracleMock, - pre: Field, - post: Field, - block_of_change: Field -) { - let fields = ScheduledValueChange::new(pre, post, block_of_change as u32).serialize(); - assert_eq(mock.get_last_params(), (state_var.get_value_change_storage_slot(), fields)); -} + let original_value = 0; -fn assert_delay_change_write( - state_var: SharedMutable, - mock: OracleMock, - pre: Field, - post: Field, - block_of_change: Field -) { - let delay_change: ScheduledDelayChange = ScheduledDelayChange::new( - Option::some(pre as u32), - Option::some(post as u32), - block_of_change as u32 - ); - - let fields = delay_change.serialize(); - assert_eq(mock.get_last_params(), (state_var.get_delay_change_storage_slot(), fields)); + // The current value has not changed + assert_eq(state_var.get_current_value_in_public(), original_value); + + // The current value still does not change right before the block of change + env.advance_block_to(block_of_change - 1); + assert_eq(state_var.get_current_value_in_public(), original_value); } #[test] -fn test_get_current_value_in_public() { - let (state_var, block_number) = setup(); +fn test_get_current_value_in_public_at_scheduled_change() { + let mut env = setup(); + let state_var = in_public(env); state_var.schedule_value_change(new_value); - let (scheduled_value, block_of_change) = state_var.get_scheduled_value_in_public(); - - assert_eq(scheduled_value, new_value); - assert_eq(block_of_change, block_number + TEST_INITIAL_DELAY); - assert_eq(state_var.get_current_value_in_public(), 0); // initial value - - cheatcodes::advance_block_to(block_of_change); + let (_, block_of_change) = state_var.get_scheduled_value_in_public(); + env.advance_block_to(block_of_change); assert_eq(state_var.get_current_value_in_public(), new_value); - assert_eq(state_var.get_scheduled_value_in_public(), (scheduled_value, block_of_change)); - - cheatcodes::advance_blocks(10); - - assert_eq(state_var.get_current_value_in_public(), new_value); - assert_eq(state_var.get_scheduled_value_in_public(), (scheduled_value, block_of_change)); } #[test] -fn test_get_scheduled_value_in_public() { - let (state_var, block_number) = setup(); +fn test_get_current_value_in_public_after_scheduled_change() { + let mut env = setup(); + let state_var = in_public(env); state_var.schedule_value_change(new_value); - assert_eq( - state_var.get_scheduled_value_in_public(), (new_value, (block_number + TEST_INITIAL_DELAY) as u32) - ); + let (_, block_of_change) = state_var.get_scheduled_value_in_public(); - cheatcodes::advance_blocks(TEST_INITIAL_DELAY * 2); - - assert_eq( - state_var.get_scheduled_value_in_public(), (new_value, (block_number + TEST_INITIAL_DELAY) as u32) - ); + env.advance_block_to(block_of_change + 10); + assert_eq(state_var.get_current_value_in_public(), new_value); } // #[test] @@ -381,28 +308,52 @@ fn test_get_scheduled_value_in_public() { #[test] fn test_get_current_value_in_private_before_change() { - let (state_var, block_number) = setup(); + let mut env = setup(); + + let public_state_var = in_public(env); + public_state_var.schedule_value_change(new_value); + + let schedule_block_number = env.block_number; + + let private_state_var = in_private(&mut env, schedule_block_number); + assert_eq(private_state_var.get_current_value_in_private(), 0); +} + +#[test] +fn test_get_current_value_in_private_immediately_before_change() { + let mut env = setup(); + + let state_var = in_public(env); + state_var.schedule_value_change(new_value); + + let (_, block_horizon) = state_var.get_scheduled_value_in_public(); + + let private_state_var = in_private(&mut env, block_horizon - 1); + assert_eq(private_state_var.get_current_value_in_private(), 0); +} + +#[test] +fn test_get_current_value_in_private_at_change() { + let mut env = setup(); + + let state_var = in_public(env); + state_var.schedule_value_change(new_value); + + let (_, block_horizon) = state_var.get_scheduled_value_in_public(); + + let private_state_var = in_private(&mut env, block_horizon); + assert_eq(private_state_var.get_current_value_in_private(), new_value); +} + +#[test] +fn test_get_current_value_in_private_after_change() { + let mut env = setup(); + + let state_var = in_public(env); state_var.schedule_value_change(new_value); let (_, block_horizon) = state_var.get_scheduled_value_in_public(); - cheatcodes::advance_blocks(100); - - let pre_change_state_var = private_setup(block_number); - assert_eq(pre_change_state_var.get_current_value_in_private(), 0); - - let pre_change_state_var = private_setup(block_horizon - 1); - assert_eq(pre_change_state_var.get_current_value_in_private(), 0); - - let pre_change_state_var = private_setup(block_horizon); - assert_eq(pre_change_state_var.get_current_value_in_private(), new_value); - // Here we'd want to test that the private getter returns the correct value and sets max_block_number in the - // context to the expected block horizon, in all the possible scenarios (long before change, before near change, - // after change). - // However, this requires mocking the getPublicDataTreeWitness oracle so that we can convince the circuit that - // it got a valid historical proof. Because we can set the tree root to whatever we want in the context, this is - // trivial for a single historical value (we add a leaf and compute the root with any random path), but is quite - // hard if we're reading more than one value for the same root (as SharedMutable does): we essentially need to - // create an actual indexed tree and compute the correct path for each of the inserted values. - // TODO: implement an actual tree and use it here https://github.com/AztecProtocol/aztec-packages/issues/5494 + let private_state_var = in_private(&mut env, block_horizon + 10); + assert_eq(private_state_var.get_current_value_in_private(), new_value); } diff --git a/noir-projects/aztec-nr/aztec/src/test/helpers/cheatcodes.nr b/noir-projects/aztec-nr/aztec/src/test/helpers/cheatcodes.nr index 39c4d304cb6..b043543b142 100644 --- a/noir-projects/aztec-nr/aztec/src/test/helpers/cheatcodes.nr +++ b/noir-projects/aztec-nr/aztec/src/test/helpers/cheatcodes.nr @@ -21,13 +21,8 @@ unconstrained pub fn advance_blocks(blocks: u32) { oracle_time_travel(blocks); } -unconstrained pub fn advance_block_to(block_number: u32) { - let difference = block_number - get_block_number(); - advance_blocks(difference); -} - -unconstrained pub fn get_private_context_inputs(block_number: u32) -> PrivateContextInputs { - oracle_get_private_context_inputs(block_number) +unconstrained pub fn get_private_context_inputs(historical_block_number: u32) -> PrivateContextInputs { + oracle_get_private_context_inputs(historical_block_number) } #[oracle(reset)] @@ -46,5 +41,5 @@ fn oracle_get_block_number() -> u32 {} fn oracle_time_travel(blocks: u32) {} #[oracle(getPrivateContextInputs)] -fn oracle_get_private_context_inputs(block_number: u32) -> PrivateContextInputs {} +fn oracle_get_private_context_inputs(historical_block_number: u32) -> PrivateContextInputs {} diff --git a/noir-projects/aztec-nr/aztec/src/test/helpers/context_builder.nr b/noir-projects/aztec-nr/aztec/src/test/helpers/context_builder.nr index 45e7e7571ff..31ef8fe6c41 100644 --- a/noir-projects/aztec-nr/aztec/src/test/helpers/context_builder.nr +++ b/noir-projects/aztec-nr/aztec/src/test/helpers/context_builder.nr @@ -1,47 +1,58 @@ +use dep::protocol_types::address::AztecAddress; use crate::context::{PrivateContext, PublicContext}; use crate::test::helpers::cheatcodes; -use dep::protocol_types::address::AztecAddress; - -use dep::protocol_types::debug_log; -struct ContextBuilder { - block_number: Option, +struct TestEnvironment { + block_number: u32, contract_address: Option, } -impl ContextBuilder { +impl TestEnvironment { fn new() -> Self { - Self { block_number: Option::none(), contract_address: Option::none() } - } + cheatcodes::reset(); - fn block_number(&mut self, block_number: u32) -> &mut Self { - self.block_number = Option::some(block_number); - self + Self { block_number: 1, contract_address: Option::none() } } - fn contract_address(&mut self, contract_address: AztecAddress) -> &mut Self { + fn contract_address(&mut self, contract_address: AztecAddress) -> Self { self.contract_address = Option::some(contract_address); - self + *self } - fn private(self) -> PrivateContext { - let inputs = cheatcodes::get_private_context_inputs(self.block_number.unwrap()); + fn advance_block_to(&mut self, block_number: u32) { + let difference = block_number - self.block_number; + self.advance_block_by(difference); + } - let args_hash = 0; - PrivateContext::new(inputs, args_hash) + fn advance_block_by(&mut self, blocks: u32) { + self.block_number += blocks; + cheatcodes::advance_blocks(blocks); } fn public(self) -> PublicContext { - let mut context = PublicContext::empty(); + if (self.contract_address.is_some()) { + cheatcodes::set_contract_address(self.contract_address.unwrap_unchecked()); + } - if self.block_number.is_some() { - cheatcodes::advance_block_to(self.block_number.unwrap_unchecked()); + PublicContext::empty() + } + + fn private(&mut self) -> PrivateContext { + self.private_at(self.block_number) + } + + fn private_at(&mut self, historical_block_number: u32) -> PrivateContext { + if historical_block_number >= self.block_number { + self.advance_block_to(historical_block_number + 2); } - if self.contract_address.is_some() { - cheatcodes::set_contract_address(self.contract_address.unwrap_unchecked()); + let mut inputs = cheatcodes::get_private_context_inputs(historical_block_number); + let args_hash = 0; + + if (self.contract_address.is_some()) { + inputs.call_context.storage_contract_address = self.contract_address.unwrap_unchecked(); } - context + PrivateContext::new(inputs, args_hash) } } From a34bf85b3d27af0c010a1266f84bc5649694f1a9 Mon Sep 17 00:00:00 2001 From: thunkar Date: Tue, 4 Jun 2024 17:02:37 +0000 Subject: [PATCH 25/75] fixes and moar --- yarn-project/txe/src/oracle/txe_oracle.ts | 4 ---- yarn-project/txe/src/txe_service/txe_service.ts | 5 ++--- 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/yarn-project/txe/src/oracle/txe_oracle.ts b/yarn-project/txe/src/oracle/txe_oracle.ts index be15cfd2859..9d73681632f 100644 --- a/yarn-project/txe/src/oracle/txe_oracle.ts +++ b/yarn-project/txe/src/oracle/txe_oracle.ts @@ -28,14 +28,11 @@ import { type NoteData, type PackedValuesCache, type TypedOracle, - WorldStatePublicDB, } from '@aztec/simulator'; import { type ContractInstance } from '@aztec/types/contracts'; import { MerkleTreeSnapshotOperationsFacade, type MerkleTrees } from '@aztec/world-state'; export class TXE implements TypedOracle { - private worldStatePublicDB: WorldStatePublicDB; - constructor( private logger: Logger, private trees: MerkleTrees, @@ -43,7 +40,6 @@ export class TXE implements TypedOracle { private contractAddress: AztecAddress, ) { this.packedValuesCache = packedValuesCache; - this.worldStatePublicDB = new WorldStatePublicDB(this.trees.asLatest()); } getRandomField() { diff --git a/yarn-project/txe/src/txe_service/txe_service.ts b/yarn-project/txe/src/txe_service/txe_service.ts index 373c556ec3c..f696a404f1d 100644 --- a/yarn-project/txe/src/txe_service/txe_service.ts +++ b/yarn-project/txe/src/txe_service/txe_service.ts @@ -26,7 +26,6 @@ export class TXEService { private typedOracle: TypedOracle, private store: AztecKVStore, private trees: MerkleTrees, - private packedValuesCache: PackedValuesCache, private contractAddress: AztecAddress, ) {} @@ -36,7 +35,7 @@ export class TXEService { const packedValuesCache = new PackedValuesCache(); logger.info(`TXE service initialized`); const txe = new TXE(logger, trees, packedValuesCache, contractAddress); - const service = new TXEService(logger, txe, store, trees, packedValuesCache, contractAddress); + const service = new TXEService(logger, txe, store, trees, contractAddress); await service.timeTravel(toSingle(new Fr(1n))); return service; } @@ -107,7 +106,7 @@ export class TXEService { this.blockNumber = 0; this.store = openTmpStore(true); this.trees = await MerkleTrees.new(this.store, this.logger); - this.packedValuesCache = new PackedValuesCache(); + this.typedOracle = new TXE(this.logger, this.trees, new PackedValuesCache(), this.contractAddress); await this.#timeTravelInner(1); return toForeignCallResult([]); } From fdbeaf87fa54169f735ef478dd260a262f53ff4c Mon Sep 17 00:00:00 2001 From: thunkar Date: Tue, 4 Jun 2024 18:20:44 +0000 Subject: [PATCH 26/75] note handling --- yarn-project/simulator/src/client/index.ts | 2 + yarn-project/txe/src/oracle/txe_oracle.ts | 100 ++++++++++++---- .../txe/src/txe_service/txe_service.ts | 109 +++++++++++++++++- 3 files changed, 185 insertions(+), 26 deletions(-) diff --git a/yarn-project/simulator/src/client/index.ts b/yarn-project/simulator/src/client/index.ts index 60fcf15d4a9..bb30eff1312 100644 --- a/yarn-project/simulator/src/client/index.ts +++ b/yarn-project/simulator/src/client/index.ts @@ -1,3 +1,5 @@ export * from './simulator.js'; export * from './db_oracle.js'; export * from './execution_result.js'; +export * from './pick_notes.js'; +export * from './execution_note_cache.js'; diff --git a/yarn-project/txe/src/oracle/txe_oracle.ts b/yarn-project/txe/src/oracle/txe_oracle.ts index 9d73681632f..fae81f7375c 100644 --- a/yarn-project/txe/src/oracle/txe_oracle.ts +++ b/yarn-project/txe/src/oracle/txe_oracle.ts @@ -1,5 +1,6 @@ import { MerkleTreeId, + Note, type NoteStatus, type NullifierMembershipWitness, PublicDataWitness, @@ -10,6 +11,7 @@ import { type CompleteAddress, type Header, type KeyValidationRequest, + NULLIFIER_SUBTREE_HEIGHT, PUBLIC_DATA_SUBTREE_HEIGHT, type PUBLIC_DATA_TREE_HEIGHT, type PrivateCallStackItem, @@ -18,16 +20,18 @@ import { type PublicDataTreeLeafPreimage, } from '@aztec/circuits.js'; import { Aes128 } from '@aztec/circuits.js/barretenberg'; -import { computePublicDataTreeLeafSlot } from '@aztec/circuits.js/hash'; +import { computePublicDataTreeLeafSlot, siloNoteHash, siloNullifier } from '@aztec/circuits.js/hash'; import { type FunctionSelector } from '@aztec/foundation/abi'; import { type AztecAddress } from '@aztec/foundation/aztec-address'; import { Fr, type Point } from '@aztec/foundation/fields'; import { type Logger, applyStringFormatting } from '@aztec/foundation/log'; import { + type ExecutionNoteCache, type MessageLoadOracleInputs, type NoteData, type PackedValuesCache, type TypedOracle, + pickNotes, } from '@aztec/simulator'; import { type ContractInstance } from '@aztec/types/contracts'; import { MerkleTreeSnapshotOperationsFacade, type MerkleTrees } from '@aztec/world-state'; @@ -37,9 +41,11 @@ export class TXE implements TypedOracle { private logger: Logger, private trees: MerkleTrees, private packedValuesCache: PackedValuesCache, + private noteCache: ExecutionNoteCache, private contractAddress: AztecAddress, ) { this.packedValuesCache = packedValuesCache; + this.noteCache = noteCache; } getRandomField() { @@ -122,34 +128,82 @@ export class TXE implements TypedOracle { } getNotes( - _storageSlot: Fr, - _numSelects: number, - _selectByIndexes: number[], - _selectByOffsets: number[], - _selectByLengths: number[], - _selectValues: Fr[], - _selectComparators: number[], - _sortByIndexes: number[], - _sortByOffsets: number[], - _sortByLengths: number[], - _sortOrder: number[], - _limit: number, - _offset: number, + storageSlot: Fr, + numSelects: number, + selectByIndexes: number[], + selectByOffsets: number[], + selectByLengths: number[], + selectValues: Fr[], + selectComparators: number[], + sortByIndexes: number[], + sortByOffsets: number[], + sortByLengths: number[], + sortOrder: number[], + limit: number, + offset: number, _status: NoteStatus, - ): Promise { - throw new Error('Method not implemented.'); - } + ) { + // Nullified pending notes are already removed from the list. + const pendingNotes = this.noteCache.getNotes(this.contractAddress, storageSlot); + + // const pendingNullifiers = this.noteCache.getNullifiers(this.contractAddress); + // const dbNotes = await this.db.getNotes(this.contractAddress, storageSlot, status); + // const dbNotesFiltered = dbNotes.filter(n => !pendingNullifiers.has((n.siloedNullifier as Fr).value)); + + const notes = pickNotes(pendingNotes, { + selects: selectByIndexes.slice(0, numSelects).map((index, i) => ({ + selector: { index, offset: selectByOffsets[i], length: selectByLengths[i] }, + value: selectValues[i], + comparator: selectComparators[i], + })), + sorts: sortByIndexes.map((index, i) => ({ + selector: { index, offset: sortByOffsets[i], length: sortByLengths[i] }, + order: sortOrder[i], + })), + limit, + offset, + }); - notifyCreatedNote(_storageSlot: Fr, _noteTypeId: Fr, _note: Fr[], _innerNoteHash: Fr, _counter: number): void { - throw new Error('Method not implemented.'); + this.logger.debug( + `Returning ${notes.length} notes for ${this.contractAddress} at ${storageSlot}: ${notes + .map(n => `${n.nonce.toString()}:[${n.note.items.map(i => i.toString()).join(',')}]`) + .join(', ')}`, + ); + + return Promise.resolve(notes); + } + + async notifyCreatedNote(storageSlot: Fr, noteTypeId: Fr, noteItems: Fr[], innerNoteHash: Fr, counter: number) { + const note = new Note(noteItems); + this.noteCache.addNewNote( + { + contractAddress: this.contractAddress, + storageSlot, + nonce: Fr.ZERO, // Nonce cannot be known during private execution. + note, + siloedNullifier: undefined, // Siloed nullifier cannot be known for newly created note. + innerNoteHash, + }, + counter, + ); + const db = this.trees.asLatest(); + const noteHash = siloNoteHash(this.contractAddress, innerNoteHash); + await db.appendLeaves(MerkleTreeId.NOTE_HASH_TREE, [noteHash]); } - notifyNullifiedNote(_innerNullifier: Fr, _innerNoteHash: Fr, _counter: number): Promise { - throw new Error('Method not implemented.'); + async notifyNullifiedNote(innerNullifier: Fr, innerNoteHash: Fr, _counter: number) { + this.noteCache.nullifyNote(this.contractAddress, innerNullifier, innerNoteHash); + const db = this.trees.asLatest(); + const siloedNullifier = siloNullifier(this.contractAddress, innerNullifier); + await db.batchInsert(MerkleTreeId.NULLIFIER_TREE, [siloedNullifier.toBuffer()], NULLIFIER_SUBTREE_HEIGHT); + return Promise.resolve(); } - checkNullifierExists(_innerNullifier: Fr): Promise { - throw new Error('Method not implemented.'); + async checkNullifierExists(innerNullifier: Fr): Promise { + const nullifier = siloNullifier(this.contractAddress, innerNullifier!); + const db = this.trees.asLatest(); + const index = await db.findLeafIndex(MerkleTreeId.NULLIFIER_TREE, nullifier.toBuffer()); + return index !== undefined; } getL1ToL2MembershipWitness( diff --git a/yarn-project/txe/src/txe_service/txe_service.ts b/yarn-project/txe/src/txe_service/txe_service.ts index f696a404f1d..3aef9ab0301 100644 --- a/yarn-project/txe/src/txe_service/txe_service.ts +++ b/yarn-project/txe/src/txe_service/txe_service.ts @@ -4,7 +4,7 @@ import { AztecAddress } from '@aztec/foundation/aztec-address'; import { type Logger } from '@aztec/foundation/log'; import { type AztecKVStore } from '@aztec/kv-store'; import { openTmpStore } from '@aztec/kv-store/utils'; -import { PackedValuesCache, type TypedOracle } from '@aztec/simulator'; +import { ExecutionNoteCache, PackedValuesCache, type TypedOracle } from '@aztec/simulator'; import { MerkleTrees } from '@aztec/world-state'; import { TXE } from '../oracle/txe_oracle.js'; @@ -33,8 +33,9 @@ export class TXEService { const store = openTmpStore(true); const trees = await MerkleTrees.new(store, logger); const packedValuesCache = new PackedValuesCache(); + const noteCache = new ExecutionNoteCache(); logger.info(`TXE service initialized`); - const txe = new TXE(logger, trees, packedValuesCache, contractAddress); + const txe = new TXE(logger, trees, packedValuesCache, noteCache, contractAddress); const service = new TXEService(logger, txe, store, trees, contractAddress); await service.timeTravel(toSingle(new Fr(1n))); return service; @@ -106,7 +107,13 @@ export class TXEService { this.blockNumber = 0; this.store = openTmpStore(true); this.trees = await MerkleTrees.new(this.store, this.logger); - this.typedOracle = new TXE(this.logger, this.trees, new PackedValuesCache(), this.contractAddress); + this.typedOracle = new TXE( + this.logger, + this.trees, + new PackedValuesCache(), + new ExecutionNoteCache(), + this.contractAddress, + ); await this.#timeTravelInner(1); return toForeignCallResult([]); } @@ -174,4 +181,100 @@ export class TXEService { ); return toForeignCallResult([toArray(result)]); } + + async getNotes( + storageSlot: ForeignCallSingle, + numSelects: ForeignCallSingle, + selectByIndexes: ForeignCallArray, + selectByOffsets: ForeignCallArray, + selectByLengths: ForeignCallArray, + selectValues: ForeignCallArray, + selectComparators: ForeignCallArray, + sortByIndexes: ForeignCallArray, + sortByOffsets: ForeignCallArray, + sortByLengths: ForeignCallArray, + sortOrder: ForeignCallArray, + limit: ForeignCallSingle, + offset: ForeignCallSingle, + status: ForeignCallSingle, + returnSize: ForeignCallSingle, + ) { + const noteDatas = await this.typedOracle.getNotes( + fromSingle(storageSlot), + fromSingle(numSelects).toNumber(), + fromArray(selectByIndexes).map(fr => fr.toNumber()), + fromArray(selectByOffsets).map(fr => fr.toNumber()), + fromArray(selectByLengths).map(fr => fr.toNumber()), + fromArray(selectValues), + fromArray(selectComparators).map(fr => fr.toNumber()), + fromArray(sortByIndexes).map(fr => fr.toNumber()), + fromArray(sortByOffsets).map(fr => fr.toNumber()), + fromArray(sortByLengths).map(fr => fr.toNumber()), + fromArray(sortOrder).map(fr => fr.toNumber()), + fromSingle(limit).toNumber(), + fromSingle(offset).toNumber(), + fromSingle(status).toNumber(), + ); + const noteLength = noteDatas?.[0]?.note.items.length ?? 0; + if (!noteDatas.every(({ note }) => noteLength === note.items.length)) { + throw new Error('Notes should all be the same length.'); + } + + const contractAddress = noteDatas[0]?.contractAddress ?? Fr.ZERO; + + // Values indicates whether the note is settled or transient. + const noteTypes = { + isSettled: new Fr(0), + isTransient: new Fr(1), + }; + const flattenData = noteDatas.flatMap(({ nonce, note, index }) => [ + nonce, + index === undefined ? noteTypes.isTransient : noteTypes.isSettled, + ...note.items, + ]); + + const returnFieldSize = fromSingle(returnSize).toNumber(); + const returnData = [noteDatas.length, contractAddress, ...flattenData].map(v => new Fr(v)); + if (returnData.length > returnFieldSize) { + throw new Error(`Return data size too big. Maximum ${returnFieldSize} fields. Got ${flattenData.length}.`); + } + + const paddedZeros = Array(returnFieldSize - returnData.length).fill(new Fr(0)); + return toForeignCallResult([toArray([...returnData, ...paddedZeros])]); + } + + notifyCreatedNote( + storageSlot: ForeignCallSingle, + noteTypeId: ForeignCallSingle, + note: ForeignCallArray, + innerNoteHash: ForeignCallSingle, + counter: ForeignCallSingle, + ) { + this.typedOracle.notifyCreatedNote( + fromSingle(storageSlot), + fromSingle(noteTypeId), + fromArray(note), + fromSingle(innerNoteHash), + fromSingle(counter).toNumber(), + ); + return toForeignCallResult([]); + } + + async notifyNullifiedNote( + innerNullifier: ForeignCallSingle, + innerNoteHash: ForeignCallSingle, + counter: ForeignCallSingle, + ) { + await this.typedOracle.notifyNullifiedNote( + fromSingle(innerNullifier), + fromSingle(innerNoteHash), + fromSingle(counter).toNumber(), + ); + return toForeignCallResult([]); + } + + async checkNullifierExists(innerNullifier: ForeignCallSingle) { + const exists = await this.typedOracle.checkNullifierExists(fromSingle(innerNullifier)); + return toForeignCallResult([toSingle(new Fr(exists))]); + } } From 12453eae864f6bc3def7d2f06c2ef83227f56a31 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicol=C3=A1s=20Venturo?= Date: Wed, 5 Jun 2024 15:05:25 +0000 Subject: [PATCH 27/75] Samples --- .../aztec/src/note/note_getter/test.nr | 2 +- .../src/state_vars/private_mutable/test.nr | 2 +- .../src/state_vars/public_immutable/test.nr | 3 +- .../src/state_vars/shared_mutable/test.nr | 136 +++++++++--------- .../aztec-nr/aztec/src/test/helpers.nr | 2 +- ...context_builder.nr => test_environment.nr} | 2 +- 6 files changed, 75 insertions(+), 72 deletions(-) rename noir-projects/aztec-nr/aztec/src/test/helpers/{context_builder.nr => test_environment.nr} (96%) diff --git a/noir-projects/aztec-nr/aztec/src/note/note_getter/test.nr b/noir-projects/aztec-nr/aztec/src/note/note_getter/test.nr index 46020d5606e..0ba1c1c08bb 100644 --- a/noir-projects/aztec-nr/aztec/src/note/note_getter/test.nr +++ b/noir-projects/aztec-nr/aztec/src/note/note_getter/test.nr @@ -9,7 +9,7 @@ use crate::{ }; use dep::protocol_types::address::AztecAddress; -use crate::test::{helpers::{context_builder::TestEnvironment, cheatcodes}, mocks::mock_note::MockNote}; +use crate::test::{helpers::{test_environment::TestEnvironment, cheatcodes}, mocks::mock_note::MockNote}; global contract_address = AztecAddress::from_field(69); global storage_slot: Field = 42; diff --git a/noir-projects/aztec-nr/aztec/src/state_vars/private_mutable/test.nr b/noir-projects/aztec-nr/aztec/src/state_vars/private_mutable/test.nr index fce59247639..3db22ca38e2 100644 --- a/noir-projects/aztec-nr/aztec/src/state_vars/private_mutable/test.nr +++ b/noir-projects/aztec-nr/aztec/src/state_vars/private_mutable/test.nr @@ -1,6 +1,6 @@ use dep::protocol_types::{address::AztecAddress, grumpkin_point::GrumpkinPoint}; use crate::{context::PrivateContext, state_vars::private_mutable::PrivateMutable}; -use crate::test::{mocks::mock_note::MockNote, helpers::context_builder::TestEnvironment}; +use crate::test::{mocks::mock_note::MockNote, helpers::test_environment::TestEnvironment}; use dep::std::{unsafe::zeroed, test::OracleMock}; global contract_address = AztecAddress::from_field(13); diff --git a/noir-projects/aztec-nr/aztec/src/state_vars/public_immutable/test.nr b/noir-projects/aztec-nr/aztec/src/state_vars/public_immutable/test.nr index a54640ecaa1..7614ffff1d1 100644 --- a/noir-projects/aztec-nr/aztec/src/state_vars/public_immutable/test.nr +++ b/noir-projects/aztec-nr/aztec/src/state_vars/public_immutable/test.nr @@ -1,6 +1,5 @@ use crate::{context::PublicContext, state_vars::public_immutable::PublicImmutable}; -use crate::test::{helpers::context_builder::TestEnvironment, mocks::mock_struct::MockStruct}; -use dep::std::test::OracleMock; +use crate::test::{helpers::test_environment::TestEnvironment, mocks::mock_struct::MockStruct}; use dep::protocol_types::traits::Serialize; global storage_slot = 7; diff --git a/noir-projects/aztec-nr/aztec/src/state_vars/shared_mutable/test.nr b/noir-projects/aztec-nr/aztec/src/state_vars/shared_mutable/test.nr index f81b9bc499e..d16344201ce 100644 --- a/noir-projects/aztec-nr/aztec/src/state_vars/shared_mutable/test.nr +++ b/noir-projects/aztec-nr/aztec/src/state_vars/shared_mutable/test.nr @@ -1,18 +1,9 @@ -use dep::std::{merkle::compute_merkle_root, test::OracleMock}; - use crate::{ - context::{PublicContext, PrivateContext}, - state_vars::shared_mutable::{ - shared_mutable::SharedMutable, scheduled_value_change::ScheduledValueChange, - scheduled_delay_change::ScheduledDelayChange -}, - test::helpers::context_builder::TestEnvironment, oracle::get_public_data_witness::PublicDataWitness + context::{PublicContext, PrivateContext}, state_vars::shared_mutable::shared_mutable::SharedMutable, + test::helpers::test_environment::TestEnvironment }; -use dep::protocol_types::{ - constants::{GENERATOR_INDEX__PUBLIC_LEAF_INDEX, PUBLIC_DATA_TREE_HEIGHT}, hash::pedersen_hash, - address::AztecAddress, public_data_tree_leaf_preimage::PublicDataTreeLeafPreimage -}; +use dep::protocol_types::address::AztecAddress; global new_value = 57; @@ -21,7 +12,7 @@ global post_delay = 15; global storage_slot = 57; -global TEST_INITIAL_DELAY: u32 = 3; +global TEST_INITIAL_DELAY: u32 = 30; fn setup() -> TestEnvironment { TestEnvironment::new() @@ -92,6 +83,71 @@ fn test_get_current_value_in_public_after_scheduled_change() { assert_eq(state_var.get_current_value_in_public(), new_value); } +#[test] +fn test_get_current_value_in_private_before_change() { + let mut env = setup(); + + let public_state_var = in_public(env); + public_state_var.schedule_value_change(new_value); + + let (_, block_of_change) = public_state_var.get_scheduled_value_in_public(); + + let schedule_block_number = env.block_number; + + let private_state_var = in_private(&mut env, schedule_block_number); + assert_eq(private_state_var.get_current_value_in_private(), 0); + assert_eq(private_state_var.context.max_block_number.unwrap(), block_of_change - 1); +} + +#[test] +fn test_get_current_value_in_private_immediately_before_change() { + let mut env = setup(); + + let public_state_var = in_public(env); + public_state_var.schedule_value_change(new_value); + + let (_, block_of_change) = public_state_var.get_scheduled_value_in_public(); + + let private_state_var = in_private(&mut env, block_of_change - 1); + + assert_eq(private_state_var.get_current_value_in_private(), 0); + assert_eq(private_state_var.context.max_block_number.unwrap(), block_of_change - 1); +} + +#[test] +fn test_get_current_value_in_private_at_change() { + let mut env = setup(); + + let public_state_var = in_public(env); + public_state_var.schedule_value_change(new_value); + + let (_, block_of_change) = public_state_var.get_scheduled_value_in_public(); + + let historical_block_number = block_of_change; + let private_state_var = in_private(&mut env, historical_block_number); + assert_eq(private_state_var.get_current_value_in_private(), new_value); + assert_eq( + private_state_var.context.max_block_number.unwrap(), historical_block_number + TEST_INITIAL_DELAY + ); +} + +#[test] +fn test_get_current_value_in_private_after_change() { + let mut env = setup(); + + let public_state_var = in_public(env); + public_state_var.schedule_value_change(new_value); + + let (_, block_of_change) = public_state_var.get_scheduled_value_in_public(); + + let historical_block_number = block_of_change + 10; + let private_state_var = in_private(&mut env, historical_block_number); + assert_eq(private_state_var.get_current_value_in_private(), new_value); + assert_eq( + private_state_var.context.max_block_number.unwrap(), historical_block_number + TEST_INITIAL_DELAY + ); +} + // #[test] // fn test_get_current_delay_in_public() { // let (state_var, block_number) = setup(); @@ -304,56 +360,4 @@ fn test_get_current_value_in_public_after_scheduled_change() { // new_delay, // block_number + post_delay - new_delay // ); -// } - -#[test] -fn test_get_current_value_in_private_before_change() { - let mut env = setup(); - - let public_state_var = in_public(env); - public_state_var.schedule_value_change(new_value); - - let schedule_block_number = env.block_number; - - let private_state_var = in_private(&mut env, schedule_block_number); - assert_eq(private_state_var.get_current_value_in_private(), 0); -} - -#[test] -fn test_get_current_value_in_private_immediately_before_change() { - let mut env = setup(); - - let state_var = in_public(env); - state_var.schedule_value_change(new_value); - - let (_, block_horizon) = state_var.get_scheduled_value_in_public(); - - let private_state_var = in_private(&mut env, block_horizon - 1); - assert_eq(private_state_var.get_current_value_in_private(), 0); -} - -#[test] -fn test_get_current_value_in_private_at_change() { - let mut env = setup(); - - let state_var = in_public(env); - state_var.schedule_value_change(new_value); - - let (_, block_horizon) = state_var.get_scheduled_value_in_public(); - - let private_state_var = in_private(&mut env, block_horizon); - assert_eq(private_state_var.get_current_value_in_private(), new_value); -} - -#[test] -fn test_get_current_value_in_private_after_change() { - let mut env = setup(); - - let state_var = in_public(env); - state_var.schedule_value_change(new_value); - - let (_, block_horizon) = state_var.get_scheduled_value_in_public(); - - let private_state_var = in_private(&mut env, block_horizon + 10); - assert_eq(private_state_var.get_current_value_in_private(), new_value); -} +// } \ No newline at end of file diff --git a/noir-projects/aztec-nr/aztec/src/test/helpers.nr b/noir-projects/aztec-nr/aztec/src/test/helpers.nr index 333197dd09b..ae72bf389e7 100644 --- a/noir-projects/aztec-nr/aztec/src/test/helpers.nr +++ b/noir-projects/aztec-nr/aztec/src/test/helpers.nr @@ -1,2 +1,2 @@ -mod context_builder; +mod test_environment; mod cheatcodes; diff --git a/noir-projects/aztec-nr/aztec/src/test/helpers/context_builder.nr b/noir-projects/aztec-nr/aztec/src/test/helpers/test_environment.nr similarity index 96% rename from noir-projects/aztec-nr/aztec/src/test/helpers/context_builder.nr rename to noir-projects/aztec-nr/aztec/src/test/helpers/test_environment.nr index 31ef8fe6c41..6eb8f766c20 100644 --- a/noir-projects/aztec-nr/aztec/src/test/helpers/context_builder.nr +++ b/noir-projects/aztec-nr/aztec/src/test/helpers/test_environment.nr @@ -43,7 +43,7 @@ impl TestEnvironment { fn private_at(&mut self, historical_block_number: u32) -> PrivateContext { if historical_block_number >= self.block_number { - self.advance_block_to(historical_block_number + 2); + self.advance_block_to(historical_block_number + 1); } let mut inputs = cheatcodes::get_private_context_inputs(historical_block_number); From fdd5cf1e7a3ffc42ccfd7cbdcbc67fb033c026f0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicol=C3=A1s=20Venturo?= Date: Fri, 7 Jun 2024 15:24:00 +0000 Subject: [PATCH 28/75] Add address fixes --- .../simulator/src/client/execution_note_cache.ts | 3 +++ yarn-project/txe/src/oracle/txe_oracle.ts | 5 +++++ yarn-project/txe/src/txe_service/txe_service.ts | 13 +++++++++---- 3 files changed, 17 insertions(+), 4 deletions(-) diff --git a/yarn-project/simulator/src/client/execution_note_cache.ts b/yarn-project/simulator/src/client/execution_note_cache.ts index 325e0b8f80b..e74123138e6 100644 --- a/yarn-project/simulator/src/client/execution_note_cache.ts +++ b/yarn-project/simulator/src/client/execution_note_cache.ts @@ -54,6 +54,7 @@ export class ExecutionNoteCache { let nullifiedNoteHashCounter: number | undefined = undefined; // Find and remove the matching new note and log(s) if the emitted innerNoteHash is not empty. if (!innerNoteHash.equals(Fr.ZERO)) { + console.log('DELETING A NOTE'); const notes = this.newNotes.get(contractAddress.toBigInt()) ?? []; const noteIndexToRemove = notes.findIndex(n => n.note.innerNoteHash.equals(innerNoteHash)); if (noteIndexToRemove === -1) { @@ -62,6 +63,8 @@ export class ExecutionNoteCache { const note = notes.splice(noteIndexToRemove, 1)[0]; nullifiedNoteHashCounter = note.counter; this.newNotes.set(contractAddress.toBigInt(), notes); + } else { + console.log('NOT DELETING A NOTE'); } return nullifiedNoteHashCounter; diff --git a/yarn-project/txe/src/oracle/txe_oracle.ts b/yarn-project/txe/src/oracle/txe_oracle.ts index fae81f7375c..83cf514fe64 100644 --- a/yarn-project/txe/src/oracle/txe_oracle.ts +++ b/yarn-project/txe/src/oracle/txe_oracle.ts @@ -48,6 +48,10 @@ export class TXE implements TypedOracle { this.noteCache = noteCache; } + setContractAddress(contractAddress: AztecAddress) { + this.contractAddress = contractAddress; + } + getRandomField() { return Fr.random(); } @@ -144,6 +148,7 @@ export class TXE implements TypedOracle { _status: NoteStatus, ) { // Nullified pending notes are already removed from the list. + console.log(`fecthing notes for ${this.contractAddress}`); const pendingNotes = this.noteCache.getNotes(this.contractAddress, storageSlot); // const pendingNullifiers = this.noteCache.getNullifiers(this.contractAddress); diff --git a/yarn-project/txe/src/txe_service/txe_service.ts b/yarn-project/txe/src/txe_service/txe_service.ts index 3aef9ab0301..9655b498b58 100644 --- a/yarn-project/txe/src/txe_service/txe_service.ts +++ b/yarn-project/txe/src/txe_service/txe_service.ts @@ -82,8 +82,10 @@ export class TXEService { return toForeignCallResult([]); } - setContractAddress(address = AztecAddress.random()) { - this.contractAddress = address; + setContractAddress(address: ForeignCallSingle) { + const typedAddress = AztecAddress.fromField(fromSingle(address)); + this.contractAddress = typedAddress; + (this.typedOracle as TXE).setContractAddress(typedAddress); return toForeignCallResult([]); } @@ -105,8 +107,10 @@ export class TXEService { async reset() { this.blockNumber = 0; + this.contractAddress = AztecAddress.random(); this.store = openTmpStore(true); this.trees = await MerkleTrees.new(this.store, this.logger); + this.typedOracle = new TXE( this.logger, this.trees, @@ -221,6 +225,7 @@ export class TXEService { } const contractAddress = noteDatas[0]?.contractAddress ?? Fr.ZERO; + console.log(`injected header: ${contractAddress}`); // Values indicates whether the note is settled or transient. const noteTypes = { @@ -257,7 +262,7 @@ export class TXEService { fromSingle(innerNoteHash), fromSingle(counter).toNumber(), ); - return toForeignCallResult([]); + return toForeignCallResult([toSingle(new Fr(0))]); } async notifyNullifiedNote( @@ -270,7 +275,7 @@ export class TXEService { fromSingle(innerNoteHash), fromSingle(counter).toNumber(), ); - return toForeignCallResult([]); + return toForeignCallResult([toSingle(new Fr(0))]); } async checkNullifierExists(innerNullifier: ForeignCallSingle) { From ff3fa7165d9bf7aed8a9057ea96d2a9d8d83965f Mon Sep 17 00:00:00 2001 From: thunkar Date: Fri, 7 Jun 2024 15:34:20 +0000 Subject: [PATCH 29/75] almost there --- .vscode/settings.json | 2 +- .../aztec/src/context/call_interfaces.nr | 135 ++++++++--- .../aztec-nr/aztec/src/keys/getters.nr | 2 +- .../aztec-nr/aztec/src/state_vars/storage.nr | 5 +- .../aztec-nr/aztec/src/test/helpers.nr | 1 + .../aztec/src/test/helpers/cheatcodes.nr | 39 +++- .../src/test/helpers/test_environment.nr | 75 +++++- .../aztec-nr/aztec/src/test/helpers/types.nr | 44 ++++ .../contracts/counter_contract/src/main.nr | 21 ++ .../crates/types/src/traits.nr | 8 +- noir/noir-repo/aztec_macros/src/lib.rs | 2 +- .../src/transforms/contract_interface.rs | 214 ++++++++++++------ .../aztec_macros/src/transforms/storage.rs | 16 +- .../src/monomorphization/mod.rs | 7 +- yarn-project/archiver/src/archiver/index.ts | 1 + .../src/contract-interface-gen/typescript.ts | 3 +- .../src/contract/contract_instance.ts | 1 - yarn-project/foundation/src/abi/abi.ts | 4 - yarn-project/txe/package.json | 2 + yarn-project/txe/src/oracle/txe_oracle.ts | 16 +- .../txe/src/txe_service/txe_service.ts | 142 +++++++++--- .../types/src/abi/contract_artifact.ts | 3 - yarn-project/yarn.lock | 2 + 23 files changed, 571 insertions(+), 174 deletions(-) create mode 100644 noir-projects/aztec-nr/aztec/src/test/helpers/types.nr diff --git a/.vscode/settings.json b/.vscode/settings.json index ab8056e8a7c..6d67a9599fd 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -167,5 +167,5 @@ "**/barretenberg/cpp/build*/**": true }, "cmake.sourceDirectory": "${workspaceFolder}/barretenberg/cpp", - "noir.nargoPath": "./noir/noir-repo/target/release/nargo" + "noir.nargoPath": "/mnt/user-data/grego/.nargo/bin/nargo" } diff --git a/noir-projects/aztec-nr/aztec/src/context/call_interfaces.nr b/noir-projects/aztec-nr/aztec/src/context/call_interfaces.nr index 69904595c96..d57a4eb3b3d 100644 --- a/noir-projects/aztec-nr/aztec/src/context/call_interfaces.nr +++ b/noir-projects/aztec-nr/aztec/src/context/call_interfaces.nr @@ -1,20 +1,51 @@ -use dep::protocol_types::{abis::function_selector::FunctionSelector, address::AztecAddress, traits::Deserialize}; +use dep::protocol_types::{ + abis::{function_selector::FunctionSelector, private_circuit_public_inputs::PrivateCircuitPublicInputs}, + address::AztecAddress, traits::Deserialize +}; -use crate::context::private_context::PrivateContext; -use crate::context::public_context::PublicContext; -use crate::context::gas::GasOpts; -use crate::context::public_context::FunctionReturns; +use crate::context::{ + private_context::PrivateContext, public_context::PublicContext, gas::GasOpts, + public_context::FunctionReturns, inputs::{PrivateContextInputs, PublicContextInputs} +}; use crate::oracle::arguments; -struct PrivateCallInterface { +trait CallInterface { + fn get_args(self) -> [Field]; + fn get_original(self) -> fn[Env](T) -> P; + fn get_selector(self) -> FunctionSelector; + fn get_name(self) -> str; +} + +impl CallInterface for PrivateCallInterface { + fn get_args(self) -> [Field] { + self.args + } + + fn get_original(self) -> fn[Env](PrivateContextInputs) -> PrivateCircuitPublicInputs { + self.original + } + + fn get_selector(self) -> FunctionSelector { + self.selector + } + + fn get_name(self) -> str { + self.name + } +} + +struct PrivateCallInterface { target_contract: AztecAddress, selector: FunctionSelector, + name: str, args_hash: Field, + args: [Field], + original: fn[Env](PrivateContextInputs) -> PrivateCircuitPublicInputs } -impl PrivateCallInterface { - pub fn call(self, context: &mut PrivateContext) -> T where T: Deserialize { +impl PrivateCallInterface { + pub fn call(self, context: &mut PrivateContext) -> T where T: Deserialize { let returns = context.call_private_function_with_packed_args( self.target_contract, self.selector, @@ -26,24 +57,45 @@ impl PrivateCallInterface { unpacked } - pub fn view(self, context: &mut PrivateContext) -> T where T: Deserialize { + pub fn view(self, context: &mut PrivateContext) -> T where T: Deserialize { let returns = context.call_private_function_with_packed_args(self.target_contract, self.selector, self.args_hash, true, false); returns.unpack_into() } - pub fn delegate_call(self, context: &mut PrivateContext) -> T where T: Deserialize { + pub fn delegate_call(self, context: &mut PrivateContext) -> T where T: Deserialize { let returns = context.call_private_function_with_packed_args(self.target_contract, self.selector, self.args_hash, false, true); returns.unpack_into() } } -struct PrivateVoidCallInterface { +impl CallInterface for PrivateVoidCallInterface { + fn get_args(self) -> [Field] { + self.args + } + + fn get_original(self) -> fn[Env](PrivateContextInputs) -> PrivateCircuitPublicInputs { + self.original + } + + fn get_selector(self) -> FunctionSelector { + self.selector + } + + fn get_name(self) -> str { + self.name + } +} + +struct PrivateVoidCallInterface { target_contract: AztecAddress, selector: FunctionSelector, + name: str, args_hash: Field, + args: [Field], + original: fn[Env](PrivateContextInputs) -> PrivateCircuitPublicInputs } -impl PrivateVoidCallInterface { +impl PrivateVoidCallInterface { pub fn call(self, context: &mut PrivateContext) { context.call_private_function_with_packed_args( self.target_contract, @@ -63,55 +115,63 @@ impl PrivateVoidCallInterface { } } -struct PrivateStaticCallInterface { +struct PrivateStaticCallInterface { target_contract: AztecAddress, selector: FunctionSelector, + name: str, args_hash: Field, + args: [Field], + original: fn[Env](PrivateContextInputs) -> PrivateCircuitPublicInputs } -impl PrivateStaticCallInterface { - pub fn view(self, context: &mut PrivateContext) -> T where T: Deserialize { +impl PrivateStaticCallInterface { + pub fn view(self, context: &mut PrivateContext) -> T where T: Deserialize { let returns = context.call_private_function_with_packed_args(self.target_contract, self.selector, self.args_hash, true, false); returns.unpack_into() } } -struct PrivateStaticVoidCallInterface { +struct PrivateStaticVoidCallInterface { target_contract: AztecAddress, selector: FunctionSelector, + name: str, args_hash: Field, + args: [Field], + original: fn[Env](PrivateContextInputs) -> PrivateCircuitPublicInputs } -impl PrivateStaticVoidCallInterface { +impl PrivateStaticVoidCallInterface { pub fn view(self, context: &mut PrivateContext) { context.call_private_function_with_packed_args(self.target_contract, self.selector, self.args_hash, true, false).assert_empty(); } } -struct PublicCallInterface { +struct PublicCallInterface { target_contract: AztecAddress, selector: FunctionSelector, + name: str, args: [Field], gas_opts: GasOpts, + original: fn[Env](PublicContextInputs) -> T } -impl PublicCallInterface { +impl PublicCallInterface { pub fn with_gas(self: &mut Self, gas_opts: GasOpts) -> &mut Self { self.gas_opts = gas_opts; self } - pub fn call(self, context: &mut PublicContext) -> T where T: Deserialize { + pub fn call(self, context: &mut PublicContext) -> T where T: Deserialize { let returns = context.call_public_function(self.target_contract, self.selector, self.args, self.gas_opts); returns.deserialize_into() } - pub fn view(self, context: &mut PublicContext) -> T where T: Deserialize { + pub fn view(self, context: &mut PublicContext) -> T where T: Deserialize { let returns = context.static_call_public_function(self.target_contract, self.selector, self.args, self.gas_opts); returns.deserialize_into() } - pub fn delegate_call(self, context: &mut PublicContext) -> T where T: Deserialize { + pub fn delegate_call(self, context: &mut PublicContext) -> T where T: Deserialize { let returns = context.delegate_call_public_function(self.target_contract, self.selector, self.args); returns.deserialize_into() } @@ -153,30 +213,32 @@ impl PublicCallInterface { } } -struct PublicVoidCallInterface { +struct PublicVoidCallInterface { target_contract: AztecAddress, selector: FunctionSelector, + name: str, args: [Field], gas_opts: GasOpts, + original: fn[Env](PublicContextInputs) -> () } -impl PublicVoidCallInterface { +impl PublicVoidCallInterface { pub fn with_gas(self: &mut Self, gas_opts: GasOpts) -> &mut Self { self.gas_opts = gas_opts; self } - pub fn call(self, context: &mut PublicContext) { + pub fn call(self, context: &mut PublicContext) { let returns = context.call_public_function(self.target_contract, self.selector, self.args, self.gas_opts); returns.assert_empty() } - pub fn view(self, context: &mut PublicContext) { + pub fn view(self, context: &mut PublicContext) { let returns = context.static_call_public_function(self.target_contract, self.selector, self.args, self.gas_opts); returns.assert_empty() } - pub fn delegate_call(self, context: &mut PublicContext) { + pub fn delegate_call(self, context: &mut PublicContext) { let returns = context.delegate_call_public_function(self.target_contract, self.selector, self.args); returns.assert_empty() } @@ -218,22 +280,25 @@ impl PublicVoidCallInterface { } } -struct PublicStaticCallInterface { +struct PublicStaticCallInterface { target_contract: AztecAddress, selector: FunctionSelector, + name: str, args: [Field], gas_opts: GasOpts, + original: fn[Env](PublicContextInputs) -> T } -impl PublicStaticCallInterface { +impl PublicStaticCallInterface { pub fn with_gas(self: &mut Self, gas_opts: GasOpts) -> &mut Self { self.gas_opts = gas_opts; self } - pub fn view(self, context: &mut PublicContext) -> T where T: Deserialize { + pub fn view(self, context: &mut PublicContext) -> T where T: Deserialize { let returns = context.static_call_public_function(self.target_contract, self.selector, self.args, self.gas_opts); - returns.deserialize_into() + let unpacked: T = returns.deserialize_into(); + unpacked } pub fn enqueue_view(self, context: &mut PrivateContext) { @@ -249,20 +314,22 @@ impl PublicStaticCallInterface { } } -struct PublicStaticVoidCallInterface { +struct PublicStaticVoidCallInterface { target_contract: AztecAddress, selector: FunctionSelector, + name: str, args: [Field], gas_opts: GasOpts, + original: fn[Env](PublicContextInputs) -> () } -impl PublicStaticVoidCallInterface { +impl PublicStaticVoidCallInterface { pub fn with_gas(self: &mut Self, gas_opts: GasOpts) -> &mut Self { self.gas_opts = gas_opts; self } - pub fn view(self, context: &mut PublicContext) { + pub fn view(self, context: &mut PublicContext) { let returns = context.static_call_public_function(self.target_contract, self.selector, self.args, self.gas_opts); returns.assert_empty() } diff --git a/noir-projects/aztec-nr/aztec/src/keys/getters.nr b/noir-projects/aztec-nr/aztec/src/keys/getters.nr index 8e56174ac83..8a2c7c440fb 100644 --- a/noir-projects/aztec-nr/aztec/src/keys/getters.nr +++ b/noir-projects/aztec-nr/aztec/src/keys/getters.nr @@ -95,7 +95,7 @@ fn fetch_and_constrain_keys(address: AztecAddress) -> PublicKeys { let computed_address = AztecAddress::compute(public_keys.hash(), partial_address); - assert(computed_address.eq(address)); + //assert(computed_address.eq(address)); public_keys } diff --git a/noir-projects/aztec-nr/aztec/src/state_vars/storage.nr b/noir-projects/aztec-nr/aztec/src/state_vars/storage.nr index 0f8cce2323c..8ae9faf228c 100644 --- a/noir-projects/aztec-nr/aztec/src/state_vars/storage.nr +++ b/noir-projects/aztec-nr/aztec/src/state_vars/storage.nr @@ -8,9 +8,8 @@ trait Storage where T: Serialize + Deserialize { // Struct representing an exportable storage variable in the contract // Every entry in the storage struct will be exported in the compilation artifact as a -// Storable entity, containing the storage slot and the type of the variable -struct Storable { +// Storable entity, containing the storage slot +struct Storable { slot: Field, - typ: str } diff --git a/noir-projects/aztec-nr/aztec/src/test/helpers.nr b/noir-projects/aztec-nr/aztec/src/test/helpers.nr index ae72bf389e7..80045b39a64 100644 --- a/noir-projects/aztec-nr/aztec/src/test/helpers.nr +++ b/noir-projects/aztec-nr/aztec/src/test/helpers.nr @@ -1,2 +1,3 @@ mod test_environment; mod cheatcodes; +mod types; diff --git a/noir-projects/aztec-nr/aztec/src/test/helpers/cheatcodes.nr b/noir-projects/aztec-nr/aztec/src/test/helpers/cheatcodes.nr index b043543b142..033e2823574 100644 --- a/noir-projects/aztec-nr/aztec/src/test/helpers/cheatcodes.nr +++ b/noir-projects/aztec-nr/aztec/src/test/helpers/cheatcodes.nr @@ -1,5 +1,6 @@ use dep::protocol_types::address::AztecAddress; -use crate::context::inputs::PrivateContextInputs; +use crate::context::inputs::{PublicContextInputs, PrivateContextInputs}; +use crate::test::helpers::types::Deployer; unconstrained pub fn reset() { oracle_reset(); @@ -25,6 +26,30 @@ unconstrained pub fn get_private_context_inputs(historical_block_number: u32) -> oracle_get_private_context_inputs(historical_block_number) } +unconstrained pub fn get_public_context_inputs() -> PublicContextInputs { + oracle_get_public_context_inputs() +} + +unconstrained pub fn deploy_inner( + path: str, + initializer: str, + args: [Field] +) -> AztecAddress { + oracle_deploy(path, initializer, args) +} + +pub fn deploy(path: str) -> Deployer { + Deployer { path } +} + +unconstrained pub fn direct_storage_write( + contract_address: AztecAddress, + storage_slot: Field, + fields: [Field; N] +) { + let _hash = direct_storage_write_oracle(contract_address, storage_slot, fields); +} + #[oracle(reset)] fn oracle_reset() {} @@ -43,3 +68,15 @@ fn oracle_time_travel(blocks: u32) {} #[oracle(getPrivateContextInputs)] fn oracle_get_private_context_inputs(historical_block_number: u32) -> PrivateContextInputs {} +#[oracle(getPublicContextInputs)] +fn oracle_get_public_context_inputs() -> PublicContextInputs {} + +#[oracle(deploy)] +fn oracle_deploy(path: str, initializer: str, args: [Field]) -> AztecAddress {} + +#[oracle(directStorageWrite)] +fn direct_storage_write_oracle( + _contract_address: AztecAddress, + _storage_slot: Field, + _values: [Field; N] +) -> [Field; N] {} diff --git a/noir-projects/aztec-nr/aztec/src/test/helpers/test_environment.nr b/noir-projects/aztec-nr/aztec/src/test/helpers/test_environment.nr index 6eb8f766c20..865ff948286 100644 --- a/noir-projects/aztec-nr/aztec/src/test/helpers/test_environment.nr +++ b/noir-projects/aztec-nr/aztec/src/test/helpers/test_environment.nr @@ -1,17 +1,30 @@ -use dep::protocol_types::address::AztecAddress; -use crate::context::{PrivateContext, PublicContext}; +use dep::protocol_types::{ + abis::function_selector::FunctionSelector, address::{AztecAddress, PartialAddress}, + storage::map::derive_storage_slot_in_map, constants::CANONICAL_KEY_REGISTRY_ADDRESS, + grumpkin_point::GrumpkinPoint +}; + +use crate::context::{PrivateContext, PublicContext, PrivateVoidCallInterface}; use crate::test::helpers::cheatcodes; +use crate::keys::{public_keys::PublicKeys, constants::{NULLIFIER_INDEX, INCOMING_INDEX, OUTGOING_INDEX, TAGGING_INDEX}}; struct TestEnvironment { block_number: u32, contract_address: Option, + args_hash: Option, + function_selector: Option } impl TestEnvironment { fn new() -> Self { cheatcodes::reset(); - Self { block_number: 1, contract_address: Option::none() } + Self { + block_number: 1, + contract_address: Option::none(), + args_hash: Option::none(), + function_selector: Option::none() + } } fn contract_address(&mut self, contract_address: AztecAddress) -> Self { @@ -19,6 +32,16 @@ impl TestEnvironment { *self } + fn function_selector(&mut self, function_selector: FunctionSelector) -> Self { + self.function_selector = Option::some(function_selector); + *self + } + + fn args_hash(&mut self, args_hash: Field) -> Self { + self.args_hash = Option::some(args_hash); + *self + } + fn advance_block_to(&mut self, block_number: u32) { let difference = block_number - self.block_number; self.advance_block_by(difference); @@ -47,12 +70,56 @@ impl TestEnvironment { } let mut inputs = cheatcodes::get_private_context_inputs(historical_block_number); - let args_hash = 0; if (self.contract_address.is_some()) { inputs.call_context.storage_contract_address = self.contract_address.unwrap_unchecked(); } + if (self.function_selector.is_some()) { + inputs.call_context.function_selector = self.function_selector.unwrap_unchecked(); + } + + let mut args_hash = 0; + + if (self.args_hash.is_some()) { + args_hash = self.args_hash.unwrap_unchecked(); + } + PrivateContext::new(inputs, args_hash) } + + fn store_master_key(self, key_index: Field, address: AztecAddress) -> GrumpkinPoint { + let x_coordinate_map_slot = key_index * 2 + 1; + let y_coordinate_map_slot = x_coordinate_map_slot + 1; + let x_coordinate_derived_slot = derive_storage_slot_in_map(x_coordinate_map_slot, address); + let y_coordinate_derived_slot = derive_storage_slot_in_map(y_coordinate_map_slot, address); + + let canonical_registry_address = AztecAddress::from_field(CANONICAL_KEY_REGISTRY_ADDRESS); + + let fake_key = GrumpkinPoint { x: 1, y: 2 }; + + cheatcodes::direct_storage_write( + canonical_registry_address, + x_coordinate_derived_slot, + [fake_key.x] + ); + + cheatcodes::direct_storage_write( + canonical_registry_address, + y_coordinate_derived_slot, + [fake_key.y] + ); + fake_key + } + + fn get_address_with_keys(self, address: AztecAddress) -> AztecAddress { + let keys = PublicKeys { + npk_m: self.store_master_key(NULLIFIER_INDEX, address), + ivpk_m: self.store_master_key(INCOMING_INDEX, address), + ovpk_m: self.store_master_key(OUTGOING_INDEX, address), + tpk_m: self.store_master_key(TAGGING_INDEX, address) + }; + + AztecAddress::compute(keys.hash(), PartialAddress::from_field(1)) + } } diff --git a/noir-projects/aztec-nr/aztec/src/test/helpers/types.nr b/noir-projects/aztec-nr/aztec/src/test/helpers/types.nr new file mode 100644 index 00000000000..d443e007224 --- /dev/null +++ b/noir-projects/aztec-nr/aztec/src/test/helpers/types.nr @@ -0,0 +1,44 @@ +use dep::protocol_types::address::AztecAddress; + +use crate::context::inputs::{PublicContextInputs, PrivateContextInputs}; +use crate::context::call_interfaces::CallInterface; +use crate::test::helpers::cheatcodes::{get_private_context_inputs, get_public_context_inputs, deploy_inner}; + +struct Deployer { + path: str, + } + +impl Deployer { + pub fn with_private_initializer( + self, + call_interface: C, + historical_block_number: u32 + ) -> AztecAddress where C: CallInterface { + let address = deploy_inner( + self.path, + call_interface.get_name(), + call_interface.get_args() + ); + let original_fn = call_interface.get_original(); + let mut inputs = get_private_context_inputs(historical_block_number); + inputs.call_context.storage_contract_address = address; + inputs.call_context.function_selector = call_interface.get_selector(); + let _result = original_fn(inputs); + address + } + + pub fn with_public_initializer( + self, + call_interface: C + ) -> AztecAddress where C: CallInterface { + let address = deploy_inner( + self.path, + call_interface.get_name(), + call_interface.get_args() + ); + let original_fn = call_interface.get_original(); + let mut inputs = get_public_context_inputs(); + let _result = original_fn(inputs); + address + } +} diff --git a/noir-projects/noir-contracts/contracts/counter_contract/src/main.nr b/noir-projects/noir-contracts/contracts/counter_contract/src/main.nr index cf2a235e798..dcd8a272fc6 100644 --- a/noir-projects/noir-contracts/contracts/counter_contract/src/main.nr +++ b/noir-projects/noir-contracts/contracts/counter_contract/src/main.nr @@ -37,4 +37,25 @@ contract Counter { balance_utils::get_balance(counters.at(owner).set) } // docs:end:get_counter + + use dep::aztec::test::{helpers::{cheatcodes, test_environment::TestEnvironment}}; + use dep::aztec::protocol_types::storage::map::derive_storage_slot_in_map; + use dep::aztec::oracle::{storage::{storage_read, storage_write}}; + use dep::aztec::keys::PublicKeys; + use dep::aztec::protocol_types::grumpkin_point::GrumpkinPoint; + use dep::aztec::protocol_types::address::PartialAddress; + + #[test] + fn test_initialize() { + let mut env = TestEnvironment::new(); + let owner = env.get_address_with_keys(AztecAddress::from_field(13)); + let outgoing_viewer = env.get_address_with_keys(AztecAddress::from_field(14)); + cheatcodes::advance_blocks(30); + let initializer = Counter::interface().initialize(5, owner, outgoing_viewer); + let _contract_address = cheatcodes::deploy("@aztec/noir-contracts.js/Counter").with_private_initializer(initializer, 30); + let counter_slot = Counter::storage().counters.slot; + let owner_slot = derive_storage_slot_in_map(counter_slot, owner); + let stored: [Field; 1] = storage_read(owner_slot); + assert(stored[0] == 5); + } } diff --git a/noir-projects/noir-protocol-circuits/crates/types/src/traits.nr b/noir-projects/noir-protocol-circuits/crates/types/src/traits.nr index e4e2b1825ec..2a92992cc4f 100644 --- a/noir-projects/noir-protocol-circuits/crates/types/src/traits.nr +++ b/noir-projects/noir-protocol-circuits/crates/types/src/traits.nr @@ -105,4 +105,10 @@ impl Serialize for str { trait Deserialize { fn deserialize(fields: [Field; N]) -> Self; } -// docs:end:deserialize \ No newline at end of file +// docs:end:deserialize + +impl Deserialize for [Field; N] { + fn deserialize(fields: [Field; N]) -> Self { + fields + } +} diff --git a/noir/noir-repo/aztec_macros/src/lib.rs b/noir/noir-repo/aztec_macros/src/lib.rs index 2daf86dc643..217f86c4d9a 100644 --- a/noir/noir-repo/aztec_macros/src/lib.rs +++ b/noir/noir-repo/aztec_macros/src/lib.rs @@ -160,7 +160,7 @@ fn transform_module( // Apply transformations to the function based on collected attributes if is_private || is_public { let fn_type = if is_private { "Private" } else { "Public" }; - let stub_src = stub_function(fn_type, func, is_static); + let stub_src = stub_function(fn_type, func, is_static, is_initializer); stubs.push((stub_src, Location { file: *file_id, span: func.name_ident().span() })); export_fn_abi(&mut module.types, func)?; diff --git a/noir/noir-repo/aztec_macros/src/transforms/contract_interface.rs b/noir/noir-repo/aztec_macros/src/transforms/contract_interface.rs index 90f9ce6164a..c63538e3601 100644 --- a/noir/noir-repo/aztec_macros/src/transforms/contract_interface.rs +++ b/noir/noir-repo/aztec_macros/src/transforms/contract_interface.rs @@ -40,7 +40,12 @@ use crate::utils::{ // } // // The selector placeholder has to be replaced with the actual function signature after type checking in the next macro pass -pub fn stub_function(aztec_visibility: &str, func: &NoirFunction, is_static_call: bool) -> String { +pub fn stub_function( + aztec_visibility: &str, + func: &NoirFunction, + is_static_call: bool, + is_initializer: bool, +) -> (String, bool) { let fn_name = func.name().to_string(); let fn_parameters = func .parameters() @@ -61,11 +66,7 @@ pub fn stub_function(aztec_visibility: &str, func: &NoirFunction, is_static_call let parameters = func.parameters(); let is_void = if matches!(fn_return_type.typ, UnresolvedTypeData::Unit) { "Void" } else { "" }; let is_static = if is_static_call { "Static" } else { "" }; - let return_type_hint = if is_void == "Void" { - "".to_string() - } else { - format!("<{}>", fn_return_type.typ.to_string().replace("plain::", "")) - }; + let return_type_hint = fn_return_type.typ.to_string().replace("plain::", ""); let call_args = parameters .iter() .map(|arg| { @@ -74,21 +75,64 @@ pub fn stub_function(aztec_visibility: &str, func: &NoirFunction, is_static_call UnresolvedTypeData::Array(_, typ) => { format!( "let hash_{0} = {0}.map(|x: {1}| x.serialize()); - for i in 0..{0}.len() {{ - args_acc = args_acc.append(hash_{0}[i].as_slice()); - }}\n", + for i in 0..{0}.len() {{ + args_acc = args_acc.append(hash_{0}[i].as_slice()); + }}\n", param_name, typ.typ.to_string().replace("plain::", "") ) } - _ => { + UnresolvedTypeData::Named(_, _, _) | UnresolvedTypeData::String(_) => { format!("args_acc = args_acc.append({}.serialize().as_slice());\n", param_name) } + _ => { + format!("args_acc = args_acc.append(&[{}.to_field()]);\n", param_name) + } } }) .collect::>() .join(""); - if aztec_visibility != "Public" { + + let param_types = if parameters.len() > 0 { + parameters + .iter() + .map(|param| param.pattern.name_ident().0.contents.clone()) + .collect::>() + .join(", ") + } else { + "".to_string() + }; + + let original = format!( + "| inputs: dep::aztec::context::inputs::{}ContextInputs | -> {} {{ + let _ = failing_env_workaround; + {}(inputs{}) + }}", + aztec_visibility, + if aztec_visibility == "Private" { + "dep::aztec::protocol_types::abis::private_circuit_public_inputs::PrivateCircuitPublicInputs".to_string() + } else { + return_type_hint.clone() + }, + fn_name, + if param_types.is_empty() { "".to_string() } else { format!(" ,{} ", param_types) } + ); + let arg_types = format!( + "(Field, {})", + parameters + .iter() + .map(|param| param.typ.typ.to_string().replace("plain::", "")) + .collect::>() + .join(",") + ); + + let generics = if is_void == "Void" { + format!("{}>", arg_types) + } else { + format!("{}, {}>", return_type_hint, arg_types) + }; + + let fn_body = if aztec_visibility != "Public" { let args_hash = if !parameters.is_empty() { format!( "let mut args_acc: [Field] = &[]; @@ -101,20 +145,19 @@ pub fn stub_function(aztec_visibility: &str, func: &NoirFunction, is_static_call "let args_hash = 0;".to_string() }; - let fn_body = format!( - "{} - dep::aztec::context::{}{}{}CallInterface {{ - target_contract: self.target_contract, - selector: {}, - args_hash, - }}", - args_hash, aztec_visibility, is_static, is_void, fn_selector, - ); format!( - "pub fn {}(self, {}) -> dep::aztec::context::{}{}{}CallInterface{} {{ - {} - }}", - fn_name, fn_parameters, aztec_visibility, is_static, is_void, return_type_hint, fn_body + "{} + let failing_env_workaround = 0; + let selector = {}; + dep::aztec::context::{}{}{}CallInterface {{ + target_contract: self.target_contract, + selector, + name: \"{}\", + args_hash, + args: args_acc, + original: {} + }}", + args_hash, fn_selector, aztec_visibility, is_static, is_void, fn_name, original ) } else { let args = format!( @@ -123,23 +166,37 @@ pub fn stub_function(aztec_visibility: &str, func: &NoirFunction, is_static_call ", call_args ); - let fn_body = format!( + format!( "{} - dep::aztec::context::Public{}{}CallInterface {{ + let failing_env_workaround = 0; + let selector = {}; + dep::aztec::context::{}{}{}CallInterface {{ target_contract: self.target_contract, - selector: {}, + selector, + name: \"{}\", args: args_acc, gas_opts: dep::aztec::context::gas::GasOpts::default(), + original: {} }}", - args, is_static, is_void, fn_selector, - ); - format!( - "pub fn {}(self, {}) -> dep::aztec::context::Public{}{}CallInterface{} {{ - {} - }}", - fn_name, fn_parameters, is_static, is_void, return_type_hint, fn_body + args, fn_selector, aztec_visibility, is_static, is_void, fn_name, original ) - } + }; + ( + format!( + "pub fn {}(self, {}) -> dep::aztec::context::{}{}{}CallInterface<{},{} {{ + {} + }}", + fn_name, + fn_parameters, + aztec_visibility, + is_static, + is_void, + fn_name.len(), + generics, + fn_body + ), + is_initializer, + ) } // Generates the contract interface as a struct with an `at` function that holds the stubbed functions and provides @@ -148,7 +205,7 @@ pub fn stub_function(aztec_visibility: &str, func: &NoirFunction, is_static_call pub fn generate_contract_interface( module: &mut SortedModule, module_name: &str, - stubs: &[(String, Location)], + stubs: &[((String, bool), Location)], ) -> Result<(), AztecMacroError> { let contract_interface = format!( " @@ -164,6 +221,14 @@ pub fn generate_contract_interface( ) -> Self {{ Self {{ target_contract }} }} + + pub fn interface() -> Self {{ + Self {{ target_contract: dep::aztec::protocol_types::address::AztecAddress::zero() }} + }} + + pub fn storage() -> StorageLayout {{ + STORAGE_LAYOUT + }} }} #[contract_library_method] @@ -172,9 +237,19 @@ pub fn generate_contract_interface( ) -> {0} {{ {0} {{ target_contract }} }} + + #[contract_library_method] + pub fn interface() -> {0} {{ + {0} {{ target_contract: dep::aztec::protocol_types::address::AztecAddress::zero() }} + }} + + #[contract_library_method] + pub fn storage() -> StorageLayout {{ + STORAGE_LAYOUT + }} ", module_name, - stubs.iter().map(|(src, _)| src.to_owned()).collect::>().join("\n"), + stubs.iter().map(|((src, _), _)| src.to_owned()).collect::>().join("\n"), ); let (contract_interface_ast, errors) = parse_program(&contract_interface); @@ -191,7 +266,7 @@ pub fn generate_contract_interface( .iter() .enumerate() .map(|(i, (method, orig_span))| { - if method.name() == "at" { + if method.name() == "at" || method.name() == "interface" || method.name() == "storage" { (method.clone(), *orig_span) } else { let (_, new_location) = stubs[i]; @@ -205,7 +280,9 @@ pub fn generate_contract_interface( module.types.push(contract_interface_ast.types.pop().unwrap()); module.impls.push(impl_with_locations); - module.functions.push(contract_interface_ast.functions.pop().unwrap()); + for function in contract_interface_ast.functions { + module.functions.push(function); + } Ok(()) } @@ -244,7 +321,7 @@ pub fn update_fn_signatures_in_contract_interface( let name = context.def_interner.function_name(func_id); let fn_parameters = &context.def_interner.function_meta(func_id).parameters.clone(); - if name == "at" { + if name == "at" || name == "interface" || name == "storage" { continue; } @@ -257,42 +334,29 @@ pub fn update_fn_signatures_in_contract_interface( .collect::>(), ); let hir_func = context.def_interner.function(func_id).block(&context.def_interner); - let call_interface_constructor_statement = context.def_interner.statement( - hir_func - .statements() - .last() - .ok_or((AztecMacroError::AztecDepNotFound, file_id))?, + + let function_selector_statement = context.def_interner.statement( + hir_func.statements().get(hir_func.statements().len() - 2).ok_or(( + AztecMacroError::CouldNotGenerateContractInterface { + secondary_message: Some( + "Function signature statement not found, invalid body length" + .to_string(), + ), + }, + file_id, + ))?, ); - let call_interface_constructor_expression = - match call_interface_constructor_statement { - HirStatement::Expression(expression_id) => { - match context.def_interner.expression(&expression_id) { - HirExpression::Constructor(hir_constructor_expression) => { - Ok(hir_constructor_expression) - } - _ => Err(( - AztecMacroError::CouldNotGenerateContractInterface { - secondary_message: Some( - "CallInterface constructor statement must be a constructor expression" - .to_string(), - ), - }, - file_id, - )), - } - } - _ => Err(( - AztecMacroError::CouldNotGenerateContractInterface { - secondary_message: Some( - "CallInterface constructor statement must be an expression" - .to_string(), - ), - }, - file_id, - )), - }?; - let (_, function_selector_expression_id) = - call_interface_constructor_expression.fields[1]; + let function_selector_expression_id = match function_selector_statement { + HirStatement::Let(let_statement) => Ok(let_statement.expression), + _ => Err(( + AztecMacroError::CouldNotGenerateContractInterface { + secondary_message: Some( + "Function selector statement must be an expression".to_string(), + ), + }, + file_id, + )), + }?; let function_selector_expression = context.def_interner.expression(&function_selector_expression_id); diff --git a/noir/noir-repo/aztec_macros/src/transforms/storage.rs b/noir/noir-repo/aztec_macros/src/transforms/storage.rs index bd9fff3c3d3..99f1f76fb06 100644 --- a/noir/noir-repo/aztec_macros/src/transforms/storage.rs +++ b/noir/noir-repo/aztec_macros/src/transforms/storage.rs @@ -504,23 +504,18 @@ pub fn generate_storage_layout( .find(|r#struct| r#struct.name.0.contents == *storage_struct_name) .unwrap(); - let mut generic_args = vec![]; let mut storable_fields = vec![]; let mut storable_fields_impl = vec![]; - definition.fields.iter().enumerate().for_each(|(index, (field_ident, field_type))| { - storable_fields.push(format!("{}: dep::aztec::prelude::Storable", field_ident, index)); - generic_args.push(format!("N{}", index)); - storable_fields_impl.push(format!( - "{}: dep::aztec::prelude::Storable {{ slot: 0, typ: \"{}\" }}", - field_ident, - field_type.to_string().replace("plain::", "") - )); + definition.fields.iter().for_each(|(field_ident, _)| { + storable_fields.push(format!("{}: dep::aztec::prelude::Storable", field_ident)); + storable_fields_impl + .push(format!("{}: dep::aztec::prelude::Storable {{ slot: 0 }}", field_ident,)); }); let storage_fields_source = format!( " - struct StorageLayout<{}> {{ + struct StorageLayout {{ {} }} @@ -529,7 +524,6 @@ pub fn generate_storage_layout( {} }}; ", - generic_args.join(", "), storable_fields.join(",\n"), storable_fields_impl.join(",\n") ); diff --git a/noir/noir-repo/compiler/noirc_frontend/src/monomorphization/mod.rs b/noir/noir-repo/compiler/noirc_frontend/src/monomorphization/mod.rs index 2e74eb87e60..c7dbd526d48 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/monomorphization/mod.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/monomorphization/mod.rs @@ -246,7 +246,12 @@ impl<'interner> Monomorphizer<'interner> { } } FunctionKind::Recursive => { - unreachable!("Only main can be specified as recursive, which should already be checked"); + // let func = self.interner.function_meta(&id); + // println!("{:#?}", func.name); + let id = + self.queue_function(id, expr_id, typ, turbofish_generics, trait_method); + Definition::Function(id) + //unreachable!("wtf"); } } } diff --git a/yarn-project/archiver/src/archiver/index.ts b/yarn-project/archiver/src/archiver/index.ts index a7294537624..81aa8727e17 100644 --- a/yarn-project/archiver/src/archiver/index.ts +++ b/yarn-project/archiver/src/archiver/index.ts @@ -3,3 +3,4 @@ export * from './config.js'; export { MemoryArchiverStore } from './memory_archiver_store/memory_archiver_store.js'; export { ArchiverDataStore } from './archiver_store.js'; export { KVArchiverDataStore } from './kv_archiver_store/kv_archiver_store.js'; +export { ContractInstanceStore } from './kv_archiver_store/contract_instance_store.js'; diff --git a/yarn-project/builder/src/contract-interface-gen/typescript.ts b/yarn-project/builder/src/contract-interface-gen/typescript.ts index 4c2911fba97..e4519b9e0af 100644 --- a/yarn-project/builder/src/contract-interface-gen/typescript.ts +++ b/yarn-project/builder/src/contract-interface-gen/typescript.ts @@ -194,10 +194,9 @@ function generateStorageLayoutGetter(input: ContractArtifact) { const storageFieldsUnionType = entries.map(([name]) => `'${name}'`).join(' | '); const layout = entries .map( - ([name, { slot, typ }]) => + ([name, { slot }]) => `${name}: { slot: new Fr(${slot.toBigInt()}n), - typ: "${typ}", }`, ) .join(',\n'); diff --git a/yarn-project/circuits.js/src/contract/contract_instance.ts b/yarn-project/circuits.js/src/contract/contract_instance.ts index 1d45791cdab..4c047461664 100644 --- a/yarn-project/circuits.js/src/contract/contract_instance.ts +++ b/yarn-project/circuits.js/src/contract/contract_instance.ts @@ -27,7 +27,6 @@ export function getContractInstanceFromDeployParams( const salt = opts.salt ?? Fr.random(); const constructorArtifact = getConstructorArtifact(artifact, opts.constructorArtifact); const deployer = opts.deployer ?? AztecAddress.ZERO; - const contractClass = getContractClassFromArtifact(artifact); const contractClassId = computeContractClassId(contractClass); const initializationHash = computeInitializationHash(constructorArtifact, args); diff --git a/yarn-project/foundation/src/abi/abi.ts b/yarn-project/foundation/src/abi/abi.ts index 200e7b0088c..2b608086604 100644 --- a/yarn-project/foundation/src/abi/abi.ts +++ b/yarn-project/foundation/src/abi/abi.ts @@ -290,10 +290,6 @@ export type FieldLayout = { * Slot in which the field is stored. */ slot: Fr; - /** - * Type being stored at the slot (e.g., 'Map>') - */ - typ: string; }; /** diff --git a/yarn-project/txe/package.json b/yarn-project/txe/package.json index 19aa02bf07c..fc5a4bd87a8 100644 --- a/yarn-project/txe/package.json +++ b/yarn-project/txe/package.json @@ -48,6 +48,8 @@ ] }, "dependencies": { + "@aztec/archiver": "workspace:^", + "@aztec/aztec.js": "workspace:^", "@aztec/circuit-types": "workspace:^", "@aztec/circuits.js": "workspace:^", "@aztec/foundation": "workspace:^", diff --git a/yarn-project/txe/src/oracle/txe_oracle.ts b/yarn-project/txe/src/oracle/txe_oracle.ts index fae81f7375c..cb924545568 100644 --- a/yarn-project/txe/src/oracle/txe_oracle.ts +++ b/yarn-project/txe/src/oracle/txe_oracle.ts @@ -1,3 +1,4 @@ +import { type ContractInstanceStore } from '@aztec/archiver'; import { MerkleTreeId, Note, @@ -8,7 +9,7 @@ import { type UnencryptedL2Log, } from '@aztec/circuit-types'; import { - type CompleteAddress, + CompleteAddress, type Header, type KeyValidationRequest, NULLIFIER_SUBTREE_HEIGHT, @@ -42,6 +43,7 @@ export class TXE implements TypedOracle { private trees: MerkleTrees, private packedValuesCache: PackedValuesCache, private noteCache: ExecutionNoteCache, + private contractInstanceStore: ContractInstanceStore, private contractAddress: AztecAddress, ) { this.packedValuesCache = packedValuesCache; @@ -68,8 +70,12 @@ export class TXE implements TypedOracle { throw new Error('Method not implemented.'); } - getContractInstance(_address: AztecAddress): Promise { - throw new Error('Method not implemented.'); + getContractInstance(address: AztecAddress): Promise { + const contractInstance = this.contractInstanceStore.getContractInstance(address); + if (!contractInstance) { + throw new Error(`Contract instance not found for address ${address}`); + } + return Promise.resolve(contractInstance); } getMembershipWitness(_blockNumber: number, _treeId: MerkleTreeId, _leafValue: Fr): Promise { @@ -115,8 +121,8 @@ export class TXE implements TypedOracle { throw new Error('Method not implemented.'); } - getCompleteAddress(_account: AztecAddress): Promise { - throw new Error('Method not implemented.'); + getCompleteAddress(account: AztecAddress): Promise { + return Promise.resolve(CompleteAddress.fromSecretKeyAndPartialAddress(Fr.ZERO, account)); } getAuthWitness(_messageHash: Fr): Promise { diff --git a/yarn-project/txe/src/txe_service/txe_service.ts b/yarn-project/txe/src/txe_service/txe_service.ts index 3aef9ab0301..1f0d9f45a94 100644 --- a/yarn-project/txe/src/txe_service/txe_service.ts +++ b/yarn-project/txe/src/txe_service/txe_service.ts @@ -1,5 +1,14 @@ -import { L2Block, MerkleTreeId } from '@aztec/circuit-types'; -import { Fr, Header, PrivateContextInputs } from '@aztec/circuits.js'; +import { ContractInstanceStore } from '@aztec/archiver'; +import { L2Block, MerkleTreeId, PublicDataWrite } from '@aztec/circuit-types'; +import { + Fr, + Header, + PUBLIC_DATA_SUBTREE_HEIGHT, + PrivateContextInputs, + PublicDataTreeLeaf, + getContractInstanceFromDeployParams, +} from '@aztec/circuits.js'; +import { computePublicDataTreeLeafSlot } from '@aztec/circuits.js/hash'; import { AztecAddress } from '@aztec/foundation/aztec-address'; import { type Logger } from '@aztec/foundation/log'; import { type AztecKVStore } from '@aztec/kv-store'; @@ -26,6 +35,7 @@ export class TXEService { private typedOracle: TypedOracle, private store: AztecKVStore, private trees: MerkleTrees, + private contractInstanceStore: ContractInstanceStore, private contractAddress: AztecAddress, ) {} @@ -34,13 +44,16 @@ export class TXEService { const trees = await MerkleTrees.new(store, logger); const packedValuesCache = new PackedValuesCache(); const noteCache = new ExecutionNoteCache(); + const contractInstanceStore = new ContractInstanceStore(store); logger.info(`TXE service initialized`); - const txe = new TXE(logger, trees, packedValuesCache, noteCache, contractAddress); - const service = new TXEService(logger, txe, store, trees, contractAddress); + const txe = new TXE(logger, trees, packedValuesCache, noteCache, contractInstanceStore, contractAddress); + const service = new TXEService(logger, txe, store, trees, contractInstanceStore, contractAddress); await service.timeTravel(toSingle(new Fr(1n))); return service; } + // Cheatcodes + async getPrivateContextInputs(blockNumber: ForeignCallSingle) { const inputs = PrivateContextInputs.empty(); const stateReference = await this.trees.getStateReference(true); @@ -51,13 +64,10 @@ export class TXEService { return toForeignCallResult(inputs.toFields().map(toSingle)); } - timeTravel(blocks: ForeignCallSingle) { - return this.#timeTravelInner(fromSingle(blocks).toNumber()); - } - - async #timeTravelInner(blocks: number) { - this.logger.info(`time traveling ${blocks} blocks`); - for (let i = 0; i < blocks; i++) { + async timeTravel(blocks: ForeignCallSingle) { + const nBlocks = fromSingle(blocks).toNumber(); + this.logger.info(`time traveling ${nBlocks} blocks`); + for (let i = 0; i < nBlocks; i++) { const header = Header.empty(); const l2Block = L2Block.empty(); header.state = await this.trees.getStateReference(true); @@ -82,11 +92,86 @@ export class TXEService { return toForeignCallResult([]); } + async reset() { + this.blockNumber = 0; + this.store = openTmpStore(true); + this.trees = await MerkleTrees.new(this.store, this.logger); + this.contractInstanceStore = new ContractInstanceStore(this.store); + this.typedOracle = new TXE( + this.logger, + this.trees, + new PackedValuesCache(), + new ExecutionNoteCache(), + this.contractInstanceStore, + this.contractAddress, + ); + await this.timeTravel(toSingle(new Fr(1))); + return toForeignCallResult([]); + } + setContractAddress(address = AztecAddress.random()) { this.contractAddress = address; return toForeignCallResult([]); } + async deploy( + path: ForeignCallArray, + initializer: ForeignCallArray, + _length: ForeignCallSingle, + args: ForeignCallArray, + ) { + const pathStr = fromArray(path) + .map(char => String.fromCharCode(char.toNumber())) + .join(''); + const initializerStr = fromArray(initializer) + .map(char => String.fromCharCode(char.toNumber())) + .join(''); + const decodedArgs = fromArray(args); + this.logger.debug(`Deploy ${pathStr} with ${initializerStr} and ${decodedArgs}`); + const contractModule = await import(pathStr); + // Hacky way of getting the class, the name of the Artifact is always longer + const contractClass = contractModule[Object.keys(contractModule).sort((a, b) => a.length - b.length)[0]]; + const instance = getContractInstanceFromDeployParams(contractClass.artifact, { + constructorArgs: decodedArgs, + salt: Fr.ONE, + publicKeysHash: Fr.ZERO, + constructorArtifact: initializerStr, + deployer: AztecAddress.ZERO, + }); + this.logger.debug(`Deployed ${contractClass.artifact.name} at ${instance.address}`); + await this.contractInstanceStore.addContractInstance(instance); + return toForeignCallResult([toSingle(instance.address)]); + } + + async directStorageWrite( + contractAddress: ForeignCallSingle, + startStorageSlot: ForeignCallSingle, + values: ForeignCallArray, + ) { + const startStorageSlotFr = fromSingle(startStorageSlot); + const valuesFr = fromArray(values); + const contractAddressFr = fromSingle(contractAddress); + const db = this.trees.asLatest(); + + const publicDataWrites = valuesFr.map((value, i) => { + const storageSlot = startStorageSlotFr.add(new Fr(i)); + this.logger.debug(`Oracle storage write: slot=${storageSlot.toString()} value=${value}`); + return new PublicDataWrite(computePublicDataTreeLeafSlot(contractAddressFr, storageSlot), value); + }); + await db.batchInsert( + MerkleTreeId.PUBLIC_DATA_TREE, + publicDataWrites.map(write => new PublicDataTreeLeaf(write.leafIndex, write.newValue).toBuffer()), + PUBLIC_DATA_SUBTREE_HEIGHT, + ); + return toForeignCallResult([toArray(publicDataWrites.map(write => write.newValue))]); + } + + // PXE oracles + + getRandomField() { + return toForeignCallResult([toSingle(this.typedOracle.getRandomField())]); + } + getContractAddress() { return toForeignCallResult([toSingle(this.contractAddress.toField())]); } @@ -103,21 +188,6 @@ export class TXEService { return toForeignCallResult([toSingle(new Fr(this.blockNumber))]); } - async reset() { - this.blockNumber = 0; - this.store = openTmpStore(true); - this.trees = await MerkleTrees.new(this.store, this.logger); - this.typedOracle = new TXE( - this.logger, - this.trees, - new PackedValuesCache(), - new ExecutionNoteCache(), - this.contractAddress, - ); - await this.#timeTravelInner(1); - return toForeignCallResult([]); - } - async packArgumentsArray(args: ForeignCallArray) { const packed = await this.typedOracle.packArgumentsArray(fromArray(args)); return toForeignCallResult([toSingle(packed)]); @@ -277,4 +347,24 @@ export class TXEService { const exists = await this.typedOracle.checkNullifierExists(fromSingle(innerNullifier)); return toForeignCallResult([toSingle(new Fr(exists))]); } + + async getContractInstance(address: ForeignCallSingle) { + const instance = await this.typedOracle.getContractInstance(fromSingle(address)); + return toForeignCallResult([ + toArray([ + instance.salt, + instance.deployer, + instance.contractClassId, + instance.initializationHash, + instance.publicKeysHash, + ]), + ]); + } + + async getPublicKeysAndPartialAddress(address: ForeignCallSingle) { + const parsedAddress = AztecAddress.fromField(fromSingle(address)); + const { publicKeys, partialAddress } = await this.typedOracle.getCompleteAddress(parsedAddress); + + return toForeignCallResult([toArray([...publicKeys.toFields(), partialAddress])]); + } } diff --git a/yarn-project/types/src/abi/contract_artifact.ts b/yarn-project/types/src/abi/contract_artifact.ts index 8ca72aa68a7..9fff2b21b6a 100644 --- a/yarn-project/types/src/abi/contract_artifact.ts +++ b/yarn-project/types/src/abi/contract_artifact.ts @@ -2,7 +2,6 @@ import { type ABIParameter, type ABIParameterVisibility, type AbiType, - type BasicValue, type ContractArtifact, type ContractNote, type FieldLayout, @@ -227,10 +226,8 @@ function getStorageLayout(input: NoirCompiledContract) { return storageFields.reduce((acc: Record, field) => { const name = field.name; const slot = field.value.fields[0].value as IntegerValue; - const typ = field.value.fields[1].value as BasicValue<'string', string>; acc[name] = { slot: new Fr(BigInt(slot.value)), - typ: typ.value, }; return acc; }, {}); diff --git a/yarn-project/yarn.lock b/yarn-project/yarn.lock index e02b14abb9f..a7a16340361 100644 --- a/yarn-project/yarn.lock +++ b/yarn-project/yarn.lock @@ -934,6 +934,8 @@ __metadata: version: 0.0.0-use.local resolution: "@aztec/txe@workspace:txe" dependencies: + "@aztec/archiver": "workspace:^" + "@aztec/aztec.js": "workspace:^" "@aztec/circuit-types": "workspace:^" "@aztec/circuits.js": "workspace:^" "@aztec/foundation": "workspace:^" From 562e92ed1a9cfd41be860107d09837da73f0a7f7 Mon Sep 17 00:00:00 2001 From: thunkar Date: Mon, 10 Jun 2024 10:40:49 +0000 Subject: [PATCH 30/75] initial txe version --- .../src/state_vars/shared_mutable/test.nr | 2 +- .../src/test/helpers/test_environment.nr | 25 ++++++++++--------- .../aztec-nr/aztec/src/test/helpers/types.nr | 12 ++++----- .../contracts/counter_contract/src/main.nr | 11 ++++---- .../tooling/nargo_cli/src/cli/test_cmd.rs | 4 +-- yarn-project/txe/src/oracle/txe_oracle.ts | 2 +- .../txe/src/txe_service/txe_service.ts | 14 +++++------ 7 files changed, 34 insertions(+), 36 deletions(-) diff --git a/noir-projects/aztec-nr/aztec/src/state_vars/shared_mutable/test.nr b/noir-projects/aztec-nr/aztec/src/state_vars/shared_mutable/test.nr index d16344201ce..42f432a5534 100644 --- a/noir-projects/aztec-nr/aztec/src/state_vars/shared_mutable/test.nr +++ b/noir-projects/aztec-nr/aztec/src/state_vars/shared_mutable/test.nr @@ -92,7 +92,7 @@ fn test_get_current_value_in_private_before_change() { let (_, block_of_change) = public_state_var.get_scheduled_value_in_public(); - let schedule_block_number = env.block_number; + let schedule_block_number = env.block_number(); let private_state_var = in_private(&mut env, schedule_block_number); assert_eq(private_state_var.get_current_value_in_private(), 0); diff --git a/noir-projects/aztec-nr/aztec/src/test/helpers/test_environment.nr b/noir-projects/aztec-nr/aztec/src/test/helpers/test_environment.nr index 865ff948286..5dd18c6871c 100644 --- a/noir-projects/aztec-nr/aztec/src/test/helpers/test_environment.nr +++ b/noir-projects/aztec-nr/aztec/src/test/helpers/test_environment.nr @@ -5,11 +5,10 @@ use dep::protocol_types::{ }; use crate::context::{PrivateContext, PublicContext, PrivateVoidCallInterface}; -use crate::test::helpers::cheatcodes; +use crate::test::helpers::{cheatcodes, types::Deployer}; use crate::keys::{public_keys::PublicKeys, constants::{NULLIFIER_INDEX, INCOMING_INDEX, OUTGOING_INDEX, TAGGING_INDEX}}; struct TestEnvironment { - block_number: u32, contract_address: Option, args_hash: Option, function_selector: Option @@ -19,12 +18,11 @@ impl TestEnvironment { fn new() -> Self { cheatcodes::reset(); - Self { - block_number: 1, - contract_address: Option::none(), - args_hash: Option::none(), - function_selector: Option::none() - } + Self { contract_address: Option::none(), args_hash: Option::none(), function_selector: Option::none() } + } + + fn block_number(self) -> u32 { + cheatcodes::get_block_number() } fn contract_address(&mut self, contract_address: AztecAddress) -> Self { @@ -43,12 +41,11 @@ impl TestEnvironment { } fn advance_block_to(&mut self, block_number: u32) { - let difference = block_number - self.block_number; + let difference = block_number - cheatcodes::get_block_number(); self.advance_block_by(difference); } fn advance_block_by(&mut self, blocks: u32) { - self.block_number += blocks; cheatcodes::advance_blocks(blocks); } @@ -61,11 +58,11 @@ impl TestEnvironment { } fn private(&mut self) -> PrivateContext { - self.private_at(self.block_number) + self.private_at(cheatcodes::get_block_number()) } fn private_at(&mut self, historical_block_number: u32) -> PrivateContext { - if historical_block_number >= self.block_number { + if historical_block_number >= cheatcodes::get_block_number() { self.advance_block_to(historical_block_number + 1); } @@ -122,4 +119,8 @@ impl TestEnvironment { AztecAddress::compute(keys.hash(), PartialAddress::from_field(1)) } + + fn deploy(self, path: str) -> Deployer { + cheatcodes::deploy(path) + } } diff --git a/noir-projects/aztec-nr/aztec/src/test/helpers/types.nr b/noir-projects/aztec-nr/aztec/src/test/helpers/types.nr index d443e007224..e4b43538c34 100644 --- a/noir-projects/aztec-nr/aztec/src/test/helpers/types.nr +++ b/noir-projects/aztec-nr/aztec/src/test/helpers/types.nr @@ -2,25 +2,23 @@ use dep::protocol_types::address::AztecAddress; use crate::context::inputs::{PublicContextInputs, PrivateContextInputs}; use crate::context::call_interfaces::CallInterface; -use crate::test::helpers::cheatcodes::{get_private_context_inputs, get_public_context_inputs, deploy_inner}; +use crate::test::helpers::cheatcodes::{get_private_context_inputs, get_public_context_inputs, deploy_inner, advance_blocks, get_block_number}; struct Deployer { path: str, } impl Deployer { - pub fn with_private_initializer( - self, - call_interface: C, - historical_block_number: u32 - ) -> AztecAddress where C: CallInterface { + pub fn with_private_initializer(self, call_interface: C) -> AztecAddress where C: CallInterface { let address = deploy_inner( self.path, call_interface.get_name(), call_interface.get_args() ); + advance_blocks(1); + let block_number = get_block_number(); let original_fn = call_interface.get_original(); - let mut inputs = get_private_context_inputs(historical_block_number); + let mut inputs = get_private_context_inputs(block_number - 1); inputs.call_context.storage_contract_address = address; inputs.call_context.function_selector = call_interface.get_selector(); let _result = original_fn(inputs); diff --git a/noir-projects/noir-contracts/contracts/counter_contract/src/main.nr b/noir-projects/noir-contracts/contracts/counter_contract/src/main.nr index 9ae4c64db4c..c3c8cc6981f 100644 --- a/noir-projects/noir-contracts/contracts/counter_contract/src/main.nr +++ b/noir-projects/noir-contracts/contracts/counter_contract/src/main.nr @@ -41,25 +41,24 @@ contract Counter { use dep::aztec::test::{helpers::{cheatcodes, test_environment::TestEnvironment}}; use dep::aztec::protocol_types::storage::map::derive_storage_slot_in_map; use dep::aztec::oracle::{storage::{storage_read, storage_write}}; - use dep::aztec::keys::PublicKeys; - use dep::aztec::protocol_types::grumpkin_point::GrumpkinPoint; - use dep::aztec::protocol_types::address::PartialAddress; use dep::aztec::note::note_getter::{MAX_NOTES_PER_PAGE, view_notes}; use dep::aztec::note::note_viewer_options::NoteViewerOptions; #[test] fn test_initialize() { + // Setup env, generate keys let mut env = TestEnvironment::new(); let owner = env.get_address_with_keys(AztecAddress::from_field(13)); let outgoing_viewer = env.get_address_with_keys(AztecAddress::from_field(14)); - cheatcodes::advance_blocks(2); + + // Deploy contract and initialize let initializer = Counter::interface().initialize(5, owner, outgoing_viewer); - let _contract_address = cheatcodes::deploy("@aztec/noir-contracts.js/Counter").with_private_initializer(initializer, 1); + let _contract_address = env.deploy("@aztec/noir-contracts.js/Counter").with_private_initializer(initializer); + // Read the stored value in the note let counter_slot = Counter::storage().counters.slot; let owner_slot = derive_storage_slot_in_map(counter_slot, owner); let mut options = NoteViewerOptions::new(); let opt_notes: [Option; MAX_NOTES_PER_PAGE] = view_notes(owner_slot, options); - assert(opt_notes[0].unwrap().value == 5); } } diff --git a/noir/noir-repo/tooling/nargo_cli/src/cli/test_cmd.rs b/noir/noir-repo/tooling/nargo_cli/src/cli/test_cmd.rs index 99c284e5019..838642b587d 100644 --- a/noir/noir-repo/tooling/nargo_cli/src/cli/test_cmd.rs +++ b/noir/noir-repo/tooling/nargo_cli/src/cli/test_cmd.rs @@ -82,7 +82,7 @@ pub(crate) fn run(args: TestCommand, config: NargoConfig) -> Result<(), CliError let test_reports: Vec> = workspace .into_iter() - .par_bridge() + //.par_bridge() .map(|package| { run_tests::( &workspace_file_manager, @@ -137,7 +137,7 @@ fn run_tests + Default>( println!("[{}] Running {count_all} test function{plural}", package.name); let test_report: Vec<(String, TestStatus)> = test_functions - .into_par_iter() + .into_iter() .map(|test_name| { let status = run_test::( file_manager, diff --git a/yarn-project/txe/src/oracle/txe_oracle.ts b/yarn-project/txe/src/oracle/txe_oracle.ts index ec7f09ec6a5..59ae24e756d 100644 --- a/yarn-project/txe/src/oracle/txe_oracle.ts +++ b/yarn-project/txe/src/oracle/txe_oracle.ts @@ -128,7 +128,7 @@ export class TXE implements TypedOracle { } getCompleteAddress(account: AztecAddress): Promise { - return Promise.resolve(CompleteAddress.fromSecretKeyAndPartialAddress(Fr.ZERO, account)); + return Promise.resolve(CompleteAddress.fromSecretKeyAndPartialAddress(Fr.ONE, account)); } getAuthWitness(_messageHash: Fr): Promise { diff --git a/yarn-project/txe/src/txe_service/txe_service.ts b/yarn-project/txe/src/txe_service/txe_service.ts index fc7cd9bb558..7da9912a06d 100644 --- a/yarn-project/txe/src/txe_service/txe_service.ts +++ b/yarn-project/txe/src/txe_service/txe_service.ts @@ -413,18 +413,18 @@ export class TXEService { } emitEncryptedLog( - contractAddress: ForeignCallSingle, - randomandomness: ForeignCallSingle, - encryptedLog: ForeignCallSingle, - counter: ForeignCallSingle, + _contractAddress: ForeignCallSingle, + _randomandomness: ForeignCallSingle, + _encryptedLog: ForeignCallSingle, + _counter: ForeignCallSingle, ) { return toForeignCallResult([]); } emitEncryptedNoteLog( - noteHashCounter: ForeignCallSingle, - encryptedNote: ForeignCallArray, - counter: ForeignCallSingle, + _noteHashCounter: ForeignCallSingle, + _encryptedNote: ForeignCallArray, + _counter: ForeignCallSingle, ) { return toForeignCallResult([]); } From 21f3178a73ae10b99bc4eca178168bae2ff94ffa Mon Sep 17 00:00:00 2001 From: thunkar Date: Mon, 10 Jun 2024 10:50:33 +0000 Subject: [PATCH 31/75] removed file --- .vscode/settings.json | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index 03168efe258..ab8056e8a7c 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -158,16 +158,14 @@ "avm-transpiler/Cargo.toml" ], "files.watcherExclude": { - "**/.git/**": true, + "**/.git/objects/**": true, + "**/.git/subtree-cache/**": true, "**/node_modules/**": true, "**/.hg/store/**": true, "**/target/**": true, "**/l1-contracts/lib/**": true, - "**/barretenberg/cpp/build*/**": true, - "**/barretenberg/cpp/src/wasi-sdk*/**": true, - "**/dest/**": true, - "**/noir/noir-repo/docs/versioned_docs/**": true + "**/barretenberg/cpp/build*/**": true }, "cmake.sourceDirectory": "${workspaceFolder}/barretenberg/cpp", - "noir.nargoPath": "/mnt/user-data/grego/.nargo/bin/nargo" + "noir.nargoPath": "./noir/noir-repo/target/release/nargo" } From c543a2cca50e5db2ea8496591bc3d82f11e67b23 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicol=C3=A1s=20Venturo?= Date: Mon, 10 Jun 2024 11:07:27 +0000 Subject: [PATCH 32/75] Add TXE --- .vscode/settings.json | 8 +- .../aztec/src/context/call_interfaces.nr | 135 ++++-- .../aztec/src/context/private_context.nr | 2 +- .../aztec-nr/aztec/src/keys/getters.nr | 2 +- .../aztec/src/note/note_getter/test.nr | 41 +- .../src/state_vars/private_mutable/test.nr | 14 +- .../src/state_vars/public_immutable/test.nr | 53 +++ .../aztec-nr/aztec/src/state_vars/storage.nr | 5 +- .../aztec-nr/aztec/src/test/helpers.nr | 4 +- .../aztec/src/test/helpers/cheatcodes.nr | 82 ++++ .../aztec/src/test/helpers/context_builder.nr | 52 --- .../src/test/helpers/test_environment.nr | 126 +++++ .../aztec-nr/aztec/src/test/helpers/types.nr | 42 ++ .../aztec-nr/aztec/src/test/mocks.nr | 1 + .../aztec/src/test/mocks/mock_struct.nr | 36 ++ .../contracts/counter_contract/src/main.nr | 24 + .../crates/types/src/traits.nr | 8 +- noir/noir-repo/aztec_macros/src/lib.rs | 2 +- .../src/transforms/contract_interface.rs | 214 ++++++--- .../aztec_macros/src/transforms/storage.rs | 16 +- .../src/monomorphization/mod.rs | 7 +- .../tooling/nargo_cli/src/cli/test_cmd.rs | 4 +- yarn-project/archiver/src/archiver/index.ts | 1 + .../src/contract-interface-gen/typescript.ts | 3 +- .../src/contract/contract_instance.ts | 1 - yarn-project/foundation/src/abi/abi.ts | 4 - yarn-project/package.json | 1 + .../src/client/execution_note_cache.ts | 3 + yarn-project/simulator/src/client/index.ts | 2 + yarn-project/txe/.eslintrc.cjs | 1 + yarn-project/txe/package.json | 80 ++++ yarn-project/txe/src/bin/index.ts | 27 ++ yarn-project/txe/src/http_rpc_server/index.ts | 29 ++ yarn-project/txe/src/index.ts | 1 + yarn-project/txe/src/oracle/txe_oracle.ts | 356 +++++++++++++++ .../txe/src/txe_service/txe_service.ts | 431 ++++++++++++++++++ yarn-project/txe/src/util/encoding.ts | 29 ++ yarn-project/txe/tsconfig.json | 41 ++ .../types/src/abi/contract_artifact.ts | 3 - .../world-state/src/world-state-db/index.ts | 2 + yarn-project/yarn.lock | 26 ++ 41 files changed, 1701 insertions(+), 218 deletions(-) create mode 100644 noir-projects/aztec-nr/aztec/src/state_vars/public_immutable/test.nr create mode 100644 noir-projects/aztec-nr/aztec/src/test/helpers/cheatcodes.nr delete mode 100644 noir-projects/aztec-nr/aztec/src/test/helpers/context_builder.nr create mode 100644 noir-projects/aztec-nr/aztec/src/test/helpers/test_environment.nr create mode 100644 noir-projects/aztec-nr/aztec/src/test/helpers/types.nr create mode 100644 noir-projects/aztec-nr/aztec/src/test/mocks/mock_struct.nr create mode 100644 yarn-project/txe/.eslintrc.cjs create mode 100644 yarn-project/txe/package.json create mode 100644 yarn-project/txe/src/bin/index.ts create mode 100644 yarn-project/txe/src/http_rpc_server/index.ts create mode 100644 yarn-project/txe/src/index.ts create mode 100644 yarn-project/txe/src/oracle/txe_oracle.ts create mode 100644 yarn-project/txe/src/txe_service/txe_service.ts create mode 100644 yarn-project/txe/src/util/encoding.ts create mode 100644 yarn-project/txe/tsconfig.json diff --git a/.vscode/settings.json b/.vscode/settings.json index a8ab844ebe1..ab8056e8a7c 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -158,15 +158,13 @@ "avm-transpiler/Cargo.toml" ], "files.watcherExclude": { - "**/.git/**": true, + "**/.git/objects/**": true, + "**/.git/subtree-cache/**": true, "**/node_modules/**": true, "**/.hg/store/**": true, "**/target/**": true, "**/l1-contracts/lib/**": true, - "**/barretenberg/cpp/build*/**": true, - "**/barretenberg/cpp/src/wasi-sdk*/**": true, - "**/dest/**": true, - "**/noir/noir-repo/docs/versioned_docs/**": true + "**/barretenberg/cpp/build*/**": true }, "cmake.sourceDirectory": "${workspaceFolder}/barretenberg/cpp", "noir.nargoPath": "./noir/noir-repo/target/release/nargo" diff --git a/noir-projects/aztec-nr/aztec/src/context/call_interfaces.nr b/noir-projects/aztec-nr/aztec/src/context/call_interfaces.nr index 69904595c96..d57a4eb3b3d 100644 --- a/noir-projects/aztec-nr/aztec/src/context/call_interfaces.nr +++ b/noir-projects/aztec-nr/aztec/src/context/call_interfaces.nr @@ -1,20 +1,51 @@ -use dep::protocol_types::{abis::function_selector::FunctionSelector, address::AztecAddress, traits::Deserialize}; +use dep::protocol_types::{ + abis::{function_selector::FunctionSelector, private_circuit_public_inputs::PrivateCircuitPublicInputs}, + address::AztecAddress, traits::Deserialize +}; -use crate::context::private_context::PrivateContext; -use crate::context::public_context::PublicContext; -use crate::context::gas::GasOpts; -use crate::context::public_context::FunctionReturns; +use crate::context::{ + private_context::PrivateContext, public_context::PublicContext, gas::GasOpts, + public_context::FunctionReturns, inputs::{PrivateContextInputs, PublicContextInputs} +}; use crate::oracle::arguments; -struct PrivateCallInterface { +trait CallInterface { + fn get_args(self) -> [Field]; + fn get_original(self) -> fn[Env](T) -> P; + fn get_selector(self) -> FunctionSelector; + fn get_name(self) -> str; +} + +impl CallInterface for PrivateCallInterface { + fn get_args(self) -> [Field] { + self.args + } + + fn get_original(self) -> fn[Env](PrivateContextInputs) -> PrivateCircuitPublicInputs { + self.original + } + + fn get_selector(self) -> FunctionSelector { + self.selector + } + + fn get_name(self) -> str { + self.name + } +} + +struct PrivateCallInterface { target_contract: AztecAddress, selector: FunctionSelector, + name: str, args_hash: Field, + args: [Field], + original: fn[Env](PrivateContextInputs) -> PrivateCircuitPublicInputs } -impl PrivateCallInterface { - pub fn call(self, context: &mut PrivateContext) -> T where T: Deserialize { +impl PrivateCallInterface { + pub fn call(self, context: &mut PrivateContext) -> T where T: Deserialize { let returns = context.call_private_function_with_packed_args( self.target_contract, self.selector, @@ -26,24 +57,45 @@ impl PrivateCallInterface { unpacked } - pub fn view(self, context: &mut PrivateContext) -> T where T: Deserialize { + pub fn view(self, context: &mut PrivateContext) -> T where T: Deserialize { let returns = context.call_private_function_with_packed_args(self.target_contract, self.selector, self.args_hash, true, false); returns.unpack_into() } - pub fn delegate_call(self, context: &mut PrivateContext) -> T where T: Deserialize { + pub fn delegate_call(self, context: &mut PrivateContext) -> T where T: Deserialize { let returns = context.call_private_function_with_packed_args(self.target_contract, self.selector, self.args_hash, false, true); returns.unpack_into() } } -struct PrivateVoidCallInterface { +impl CallInterface for PrivateVoidCallInterface { + fn get_args(self) -> [Field] { + self.args + } + + fn get_original(self) -> fn[Env](PrivateContextInputs) -> PrivateCircuitPublicInputs { + self.original + } + + fn get_selector(self) -> FunctionSelector { + self.selector + } + + fn get_name(self) -> str { + self.name + } +} + +struct PrivateVoidCallInterface { target_contract: AztecAddress, selector: FunctionSelector, + name: str, args_hash: Field, + args: [Field], + original: fn[Env](PrivateContextInputs) -> PrivateCircuitPublicInputs } -impl PrivateVoidCallInterface { +impl PrivateVoidCallInterface { pub fn call(self, context: &mut PrivateContext) { context.call_private_function_with_packed_args( self.target_contract, @@ -63,55 +115,63 @@ impl PrivateVoidCallInterface { } } -struct PrivateStaticCallInterface { +struct PrivateStaticCallInterface { target_contract: AztecAddress, selector: FunctionSelector, + name: str, args_hash: Field, + args: [Field], + original: fn[Env](PrivateContextInputs) -> PrivateCircuitPublicInputs } -impl PrivateStaticCallInterface { - pub fn view(self, context: &mut PrivateContext) -> T where T: Deserialize { +impl PrivateStaticCallInterface { + pub fn view(self, context: &mut PrivateContext) -> T where T: Deserialize { let returns = context.call_private_function_with_packed_args(self.target_contract, self.selector, self.args_hash, true, false); returns.unpack_into() } } -struct PrivateStaticVoidCallInterface { +struct PrivateStaticVoidCallInterface { target_contract: AztecAddress, selector: FunctionSelector, + name: str, args_hash: Field, + args: [Field], + original: fn[Env](PrivateContextInputs) -> PrivateCircuitPublicInputs } -impl PrivateStaticVoidCallInterface { +impl PrivateStaticVoidCallInterface { pub fn view(self, context: &mut PrivateContext) { context.call_private_function_with_packed_args(self.target_contract, self.selector, self.args_hash, true, false).assert_empty(); } } -struct PublicCallInterface { +struct PublicCallInterface { target_contract: AztecAddress, selector: FunctionSelector, + name: str, args: [Field], gas_opts: GasOpts, + original: fn[Env](PublicContextInputs) -> T } -impl PublicCallInterface { +impl PublicCallInterface { pub fn with_gas(self: &mut Self, gas_opts: GasOpts) -> &mut Self { self.gas_opts = gas_opts; self } - pub fn call(self, context: &mut PublicContext) -> T where T: Deserialize { + pub fn call(self, context: &mut PublicContext) -> T where T: Deserialize { let returns = context.call_public_function(self.target_contract, self.selector, self.args, self.gas_opts); returns.deserialize_into() } - pub fn view(self, context: &mut PublicContext) -> T where T: Deserialize { + pub fn view(self, context: &mut PublicContext) -> T where T: Deserialize { let returns = context.static_call_public_function(self.target_contract, self.selector, self.args, self.gas_opts); returns.deserialize_into() } - pub fn delegate_call(self, context: &mut PublicContext) -> T where T: Deserialize { + pub fn delegate_call(self, context: &mut PublicContext) -> T where T: Deserialize { let returns = context.delegate_call_public_function(self.target_contract, self.selector, self.args); returns.deserialize_into() } @@ -153,30 +213,32 @@ impl PublicCallInterface { } } -struct PublicVoidCallInterface { +struct PublicVoidCallInterface { target_contract: AztecAddress, selector: FunctionSelector, + name: str, args: [Field], gas_opts: GasOpts, + original: fn[Env](PublicContextInputs) -> () } -impl PublicVoidCallInterface { +impl PublicVoidCallInterface { pub fn with_gas(self: &mut Self, gas_opts: GasOpts) -> &mut Self { self.gas_opts = gas_opts; self } - pub fn call(self, context: &mut PublicContext) { + pub fn call(self, context: &mut PublicContext) { let returns = context.call_public_function(self.target_contract, self.selector, self.args, self.gas_opts); returns.assert_empty() } - pub fn view(self, context: &mut PublicContext) { + pub fn view(self, context: &mut PublicContext) { let returns = context.static_call_public_function(self.target_contract, self.selector, self.args, self.gas_opts); returns.assert_empty() } - pub fn delegate_call(self, context: &mut PublicContext) { + pub fn delegate_call(self, context: &mut PublicContext) { let returns = context.delegate_call_public_function(self.target_contract, self.selector, self.args); returns.assert_empty() } @@ -218,22 +280,25 @@ impl PublicVoidCallInterface { } } -struct PublicStaticCallInterface { +struct PublicStaticCallInterface { target_contract: AztecAddress, selector: FunctionSelector, + name: str, args: [Field], gas_opts: GasOpts, + original: fn[Env](PublicContextInputs) -> T } -impl PublicStaticCallInterface { +impl PublicStaticCallInterface { pub fn with_gas(self: &mut Self, gas_opts: GasOpts) -> &mut Self { self.gas_opts = gas_opts; self } - pub fn view(self, context: &mut PublicContext) -> T where T: Deserialize { + pub fn view(self, context: &mut PublicContext) -> T where T: Deserialize { let returns = context.static_call_public_function(self.target_contract, self.selector, self.args, self.gas_opts); - returns.deserialize_into() + let unpacked: T = returns.deserialize_into(); + unpacked } pub fn enqueue_view(self, context: &mut PrivateContext) { @@ -249,20 +314,22 @@ impl PublicStaticCallInterface { } } -struct PublicStaticVoidCallInterface { +struct PublicStaticVoidCallInterface { target_contract: AztecAddress, selector: FunctionSelector, + name: str, args: [Field], gas_opts: GasOpts, + original: fn[Env](PublicContextInputs) -> () } -impl PublicStaticVoidCallInterface { +impl PublicStaticVoidCallInterface { pub fn with_gas(self: &mut Self, gas_opts: GasOpts) -> &mut Self { self.gas_opts = gas_opts; self } - pub fn view(self, context: &mut PublicContext) { + pub fn view(self, context: &mut PublicContext) { let returns = context.static_call_public_function(self.target_contract, self.selector, self.args, self.gas_opts); returns.assert_empty() } diff --git a/noir-projects/aztec-nr/aztec/src/context/private_context.nr b/noir-projects/aztec-nr/aztec/src/context/private_context.nr index aeba73e0554..e0f31144ed2 100644 --- a/noir-projects/aztec-nr/aztec/src/context/private_context.nr +++ b/noir-projects/aztec-nr/aztec/src/context/private_context.nr @@ -237,7 +237,7 @@ impl PrivateContext { let request_and_generator = KeyValidationRequestAndGenerator { request, sk_app_generator: sk_generators[key_index] }; // We constrain that the pk_m_hash matches the one in the request (otherwise we could get an arbitrary // valid key request and not the one corresponding to pk_m_hash). - assert(request.pk_m.hash() == pk_m_hash); + //assert(request.pk_m.hash() == pk_m_hash); self.key_validation_requests_and_generators.push(request_and_generator); self.last_key_validation_requests[key_index] = Option::some(request); request.sk_app diff --git a/noir-projects/aztec-nr/aztec/src/keys/getters.nr b/noir-projects/aztec-nr/aztec/src/keys/getters.nr index 8e56174ac83..8a2c7c440fb 100644 --- a/noir-projects/aztec-nr/aztec/src/keys/getters.nr +++ b/noir-projects/aztec-nr/aztec/src/keys/getters.nr @@ -95,7 +95,7 @@ fn fetch_and_constrain_keys(address: AztecAddress) -> PublicKeys { let computed_address = AztecAddress::compute(public_keys.hash(), partial_address); - assert(computed_address.eq(address)); + //assert(computed_address.eq(address)); public_keys } diff --git a/noir-projects/aztec-nr/aztec/src/note/note_getter/test.nr b/noir-projects/aztec-nr/aztec/src/note/note_getter/test.nr index 7229268f1e5..0ba1c1c08bb 100644 --- a/noir-projects/aztec-nr/aztec/src/note/note_getter/test.nr +++ b/noir-projects/aztec-nr/aztec/src/note/note_getter/test.nr @@ -9,13 +9,13 @@ use crate::{ }; use dep::protocol_types::address::AztecAddress; -use crate::test::{helpers::context_builder::ContextBuilder, mocks::mock_note::MockNote}; +use crate::test::{helpers::{test_environment::TestEnvironment, cheatcodes}, mocks::mock_note::MockNote}; global contract_address = AztecAddress::from_field(69); global storage_slot: Field = 42; -fn setup() -> PrivateContext { - ContextBuilder::new().contract_address(contract_address).private() +fn setup() -> TestEnvironment { + TestEnvironment::new().contract_address(contract_address) } fn build_valid_note(value: Field) -> MockNote { @@ -24,7 +24,8 @@ fn build_valid_note(value: Field) -> MockNote { #[test] fn processes_single_note() { - let mut context = setup(); + let mut env = setup(); + let mut context = env.private(); let mut notes_to_constrain = [Option::none(); MAX_NOTE_HASH_READ_REQUESTS_PER_CALL]; notes_to_constrain[0] = Option::some(build_valid_note(13)); @@ -38,7 +39,8 @@ fn processes_single_note() { #[test] fn processes_many_notes() { - let mut context = setup(); + let mut env = setup(); + let mut context = env.private(); let mut notes_to_constrain = [Option::none(); MAX_NOTE_HASH_READ_REQUESTS_PER_CALL]; notes_to_constrain[0] = Option::some(build_valid_note(13)); @@ -53,7 +55,8 @@ fn processes_many_notes() { #[test] fn collapses_notes_at_the_beginning_of_the_array() { - let mut context = setup(); + let mut env = setup(); + let mut context = env.private(); let mut opt_notes = [Option::none(); MAX_NOTE_HASH_READ_REQUESTS_PER_CALL]; opt_notes[1] = Option::some(build_valid_note(0)); @@ -80,8 +83,9 @@ fn collapses_notes_at_the_beginning_of_the_array() { } #[test(should_fail_with="Cannot return zero notes")] - fn rejects_zero_notes() { - let mut context = setup(); +fn rejects_zero_notes() { + let mut env = setup(); + let mut context = env.private(); let opt_notes: [Option; MAX_NOTE_HASH_READ_REQUESTS_PER_CALL] = [Option::none(); MAX_NOTE_HASH_READ_REQUESTS_PER_CALL]; @@ -91,7 +95,8 @@ fn collapses_notes_at_the_beginning_of_the_array() { #[test(should_fail_with="Got more notes than limit.")] fn rejects_mote_notes_than_limit() { - let mut context = setup(); + let mut env = setup(); + let mut context = env.private(); let mut opt_notes: [Option; MAX_NOTE_HASH_READ_REQUESTS_PER_CALL] = [Option::none(); MAX_NOTE_HASH_READ_REQUESTS_PER_CALL]; opt_notes[1] = Option::some(build_valid_note(0)); @@ -105,7 +110,8 @@ fn rejects_mote_notes_than_limit() { #[test] fn applies_filter_before_constraining() { - let mut context = setup(); + let mut env = setup(); + let mut context = env.private(); let mut notes_to_constrain = [Option::none(); MAX_NOTE_HASH_READ_REQUESTS_PER_CALL]; let invalid_note = MockNote::new(13).build(); // This note does not have the correct address or storage slot @@ -138,7 +144,8 @@ fn applies_filter_before_constraining() { #[test(should_fail_with="Mismatch note header contract address.")] fn rejects_mismatched_address() { - let mut context = setup(); + let mut env = setup(); + let mut context = env.private(); let note = MockNote::new(1).storage_slot(storage_slot).build(); // We're not setting the right contract address @@ -151,7 +158,8 @@ fn rejects_mismatched_address() { #[test(should_fail_with="Mismatch note header storage slot.")] fn rejects_mismatched_storage_slot() { - let mut context = setup(); + let mut env = setup(); + let mut context = env.private(); let note = MockNote::new(1).contract_address(contract_address).build(); // We're not setting the right storage slot @@ -164,7 +172,8 @@ fn rejects_mismatched_storage_slot() { #[test(should_fail_with="Mismatch return note field.")] fn rejects_mismatched_selector() { - let mut context = setup(); + let mut env = setup(); + let mut context = env.private(); let value = 10; let note = build_valid_note(value); @@ -184,7 +193,8 @@ fn rejects_mismatched_selector() { #[test(should_fail_with="Return notes not sorted in descending order.")] fn rejects_mismatched_desc_sort_order() { - let mut context = setup(); + let mut env = setup(); + let mut context = env.private(); let mut opt_notes = [Option::none(); MAX_NOTE_HASH_READ_REQUESTS_PER_CALL]; // Notes in ascending order @@ -201,7 +211,8 @@ fn rejects_mismatched_desc_sort_order() { #[test(should_fail_with="Return notes not sorted in ascending order.")] fn rejects_mismatched_asc_sort_order() { - let mut context = setup(); + let mut env = setup(); + let mut context = env.private(); let mut opt_notes = [Option::none(); MAX_NOTE_HASH_READ_REQUESTS_PER_CALL]; // Notes in descending order diff --git a/noir-projects/aztec-nr/aztec/src/state_vars/private_mutable/test.nr b/noir-projects/aztec-nr/aztec/src/state_vars/private_mutable/test.nr index f0272af161f..3db22ca38e2 100644 --- a/noir-projects/aztec-nr/aztec/src/state_vars/private_mutable/test.nr +++ b/noir-projects/aztec-nr/aztec/src/state_vars/private_mutable/test.nr @@ -1,14 +1,17 @@ use dep::protocol_types::{address::AztecAddress, grumpkin_point::GrumpkinPoint}; use crate::{context::PrivateContext, state_vars::private_mutable::PrivateMutable}; -use crate::test::{mocks::mock_note::MockNote, helpers::context_builder::ContextBuilder}; +use crate::test::{mocks::mock_note::MockNote, helpers::test_environment::TestEnvironment}; use dep::std::{unsafe::zeroed, test::OracleMock}; global contract_address = AztecAddress::from_field(13); global storage_slot = 17; -fn setup() -> PrivateMutable { - let mut context = ContextBuilder::new().contract_address(contract_address).private(); - let state_var = PrivateMutable::new(&mut context, storage_slot); +fn setup() -> TestEnvironment { + TestEnvironment::new().contract_address(contract_address) +} + +fn in_private(env: &mut TestEnvironment) -> PrivateMutable { + let state_var = PrivateMutable::new(&mut env.private(), storage_slot); // This oracle is called for its side effects alone - it's always expected to return 0. OracleMock::mock("notifyCreatedNote").returns(0); @@ -18,7 +21,8 @@ fn setup() -> PrivateMutable { #[test] fn test_initialize_or_replace_without_nullifier() { - let state_var = setup(); + let mut env = setup(); + let state_var = in_private(&mut env); let ovpk_m: GrumpkinPoint = zeroed(); let ivpk_m: GrumpkinPoint = zeroed(); diff --git a/noir-projects/aztec-nr/aztec/src/state_vars/public_immutable/test.nr b/noir-projects/aztec-nr/aztec/src/state_vars/public_immutable/test.nr new file mode 100644 index 00000000000..7614ffff1d1 --- /dev/null +++ b/noir-projects/aztec-nr/aztec/src/state_vars/public_immutable/test.nr @@ -0,0 +1,53 @@ +use crate::{context::PublicContext, state_vars::public_immutable::PublicImmutable}; +use crate::test::{helpers::test_environment::TestEnvironment, mocks::mock_struct::MockStruct}; +use dep::protocol_types::traits::Serialize; + +global storage_slot = 7; + +fn setup() -> TestEnvironment { + TestEnvironment::new() +} + +fn in_public(env: TestEnvironment) -> PublicImmutable { + PublicImmutable::new(&mut env.public(), storage_slot) +} + +#[test] +fn test_uninitialized_by_default() { + let env = setup(); + let state_var = in_public(env); + + assert_eq(state_var.is_initialized(), false); +} + +#[test] +fn test_initialize_uninitialized() { + let env = setup(); + let state_var = in_public(env); + + let value = MockStruct::new(5, 6); + + state_var.initialize(value); + + assert(state_var.is_initialized()); + assert(state_var.read() == value); +} + +#[test(should_fail_with="PublicImmutable already initialized")] +fn test_initialize_already_initialized() { + let env = setup(); + let state_var = in_public(env); + + let value = MockStruct::new(5, 6); + + state_var.initialize(value); + state_var.initialize(value); +} + +#[test(should_fail_with="PublicImmutable not initialized")] +fn test_read_uninitialized() { + let env = setup(); + let state_var = in_public(env); + + let _ = state_var.read(); +} diff --git a/noir-projects/aztec-nr/aztec/src/state_vars/storage.nr b/noir-projects/aztec-nr/aztec/src/state_vars/storage.nr index 0f8cce2323c..8ae9faf228c 100644 --- a/noir-projects/aztec-nr/aztec/src/state_vars/storage.nr +++ b/noir-projects/aztec-nr/aztec/src/state_vars/storage.nr @@ -8,9 +8,8 @@ trait Storage where T: Serialize + Deserialize { // Struct representing an exportable storage variable in the contract // Every entry in the storage struct will be exported in the compilation artifact as a -// Storable entity, containing the storage slot and the type of the variable -struct Storable { +// Storable entity, containing the storage slot +struct Storable { slot: Field, - typ: str } diff --git a/noir-projects/aztec-nr/aztec/src/test/helpers.nr b/noir-projects/aztec-nr/aztec/src/test/helpers.nr index c5c7a4b5f31..80045b39a64 100644 --- a/noir-projects/aztec-nr/aztec/src/test/helpers.nr +++ b/noir-projects/aztec-nr/aztec/src/test/helpers.nr @@ -1 +1,3 @@ -mod context_builder; +mod test_environment; +mod cheatcodes; +mod types; diff --git a/noir-projects/aztec-nr/aztec/src/test/helpers/cheatcodes.nr b/noir-projects/aztec-nr/aztec/src/test/helpers/cheatcodes.nr new file mode 100644 index 00000000000..033e2823574 --- /dev/null +++ b/noir-projects/aztec-nr/aztec/src/test/helpers/cheatcodes.nr @@ -0,0 +1,82 @@ +use dep::protocol_types::address::AztecAddress; +use crate::context::inputs::{PublicContextInputs, PrivateContextInputs}; +use crate::test::helpers::types::Deployer; + +unconstrained pub fn reset() { + oracle_reset(); +} + +unconstrained pub fn get_contract_address() -> AztecAddress { + oracle_get_contract_address() +} + +unconstrained pub fn set_contract_address(address: AztecAddress) { + oracle_set_contract_address(address); +} + +unconstrained pub fn get_block_number() -> u32 { + oracle_get_block_number() +} + +unconstrained pub fn advance_blocks(blocks: u32) { + oracle_time_travel(blocks); +} + +unconstrained pub fn get_private_context_inputs(historical_block_number: u32) -> PrivateContextInputs { + oracle_get_private_context_inputs(historical_block_number) +} + +unconstrained pub fn get_public_context_inputs() -> PublicContextInputs { + oracle_get_public_context_inputs() +} + +unconstrained pub fn deploy_inner( + path: str, + initializer: str, + args: [Field] +) -> AztecAddress { + oracle_deploy(path, initializer, args) +} + +pub fn deploy(path: str) -> Deployer { + Deployer { path } +} + +unconstrained pub fn direct_storage_write( + contract_address: AztecAddress, + storage_slot: Field, + fields: [Field; N] +) { + let _hash = direct_storage_write_oracle(contract_address, storage_slot, fields); +} + +#[oracle(reset)] +fn oracle_reset() {} + +#[oracle(getContractAddress)] +fn oracle_get_contract_address() -> AztecAddress {} + +#[oracle(setContractAddress)] +fn oracle_set_contract_address(address: AztecAddress) {} + +#[oracle(getBlockNumber)] +fn oracle_get_block_number() -> u32 {} + +#[oracle(timeTravel)] +fn oracle_time_travel(blocks: u32) {} + +#[oracle(getPrivateContextInputs)] +fn oracle_get_private_context_inputs(historical_block_number: u32) -> PrivateContextInputs {} + +#[oracle(getPublicContextInputs)] +fn oracle_get_public_context_inputs() -> PublicContextInputs {} + +#[oracle(deploy)] +fn oracle_deploy(path: str, initializer: str, args: [Field]) -> AztecAddress {} + +#[oracle(directStorageWrite)] +fn direct_storage_write_oracle( + _contract_address: AztecAddress, + _storage_slot: Field, + _values: [Field; N] +) -> [Field; N] {} diff --git a/noir-projects/aztec-nr/aztec/src/test/helpers/context_builder.nr b/noir-projects/aztec-nr/aztec/src/test/helpers/context_builder.nr deleted file mode 100644 index 0b04ac52ccb..00000000000 --- a/noir-projects/aztec-nr/aztec/src/test/helpers/context_builder.nr +++ /dev/null @@ -1,52 +0,0 @@ -use crate::context::{PrivateContext, PublicContext}; -use dep::protocol_types::address::AztecAddress; -use dep::std::test::OracleMock; - -struct ContextBuilder { - block_number: Option, - contract_address: Option, -} - -impl ContextBuilder { - fn new() -> Self { - Self { block_number: Option::none(), contract_address: Option::none() } - } - - fn block_number(&mut self, block_number: Field) -> &mut Self { - self.block_number = Option::some(block_number); - self - } - - fn contract_address(&mut self, contract_address: AztecAddress) -> &mut Self { - self.contract_address = Option::some(contract_address); - self - } - - fn private(&mut self) -> PrivateContext { - let mut context = PrivateContext::empty(); - - if self.block_number.is_some() { - context.inputs.historical_header.global_variables.block_number = self.block_number.unwrap_unchecked(); - } - - if self.contract_address.is_some() { - context.inputs.call_context.storage_contract_address = self.contract_address.unwrap_unchecked(); - } - - context - } - - fn public(&mut self) -> PublicContext { - let mut context = PublicContext::empty(); - - if self.block_number.is_some() { - let _ = OracleMock::mock("avmOpcodeBlockNumber").returns(self.block_number.unwrap()); - } - - if self.contract_address.is_some() { - let _ = OracleMock::mock("avmOpcodeAddress").returns(self.contract_address.unwrap()); - } - - context - } -} diff --git a/noir-projects/aztec-nr/aztec/src/test/helpers/test_environment.nr b/noir-projects/aztec-nr/aztec/src/test/helpers/test_environment.nr new file mode 100644 index 00000000000..5dd18c6871c --- /dev/null +++ b/noir-projects/aztec-nr/aztec/src/test/helpers/test_environment.nr @@ -0,0 +1,126 @@ +use dep::protocol_types::{ + abis::function_selector::FunctionSelector, address::{AztecAddress, PartialAddress}, + storage::map::derive_storage_slot_in_map, constants::CANONICAL_KEY_REGISTRY_ADDRESS, + grumpkin_point::GrumpkinPoint +}; + +use crate::context::{PrivateContext, PublicContext, PrivateVoidCallInterface}; +use crate::test::helpers::{cheatcodes, types::Deployer}; +use crate::keys::{public_keys::PublicKeys, constants::{NULLIFIER_INDEX, INCOMING_INDEX, OUTGOING_INDEX, TAGGING_INDEX}}; + +struct TestEnvironment { + contract_address: Option, + args_hash: Option, + function_selector: Option +} + +impl TestEnvironment { + fn new() -> Self { + cheatcodes::reset(); + + Self { contract_address: Option::none(), args_hash: Option::none(), function_selector: Option::none() } + } + + fn block_number(self) -> u32 { + cheatcodes::get_block_number() + } + + fn contract_address(&mut self, contract_address: AztecAddress) -> Self { + self.contract_address = Option::some(contract_address); + *self + } + + fn function_selector(&mut self, function_selector: FunctionSelector) -> Self { + self.function_selector = Option::some(function_selector); + *self + } + + fn args_hash(&mut self, args_hash: Field) -> Self { + self.args_hash = Option::some(args_hash); + *self + } + + fn advance_block_to(&mut self, block_number: u32) { + let difference = block_number - cheatcodes::get_block_number(); + self.advance_block_by(difference); + } + + fn advance_block_by(&mut self, blocks: u32) { + cheatcodes::advance_blocks(blocks); + } + + fn public(self) -> PublicContext { + if (self.contract_address.is_some()) { + cheatcodes::set_contract_address(self.contract_address.unwrap_unchecked()); + } + + PublicContext::empty() + } + + fn private(&mut self) -> PrivateContext { + self.private_at(cheatcodes::get_block_number()) + } + + fn private_at(&mut self, historical_block_number: u32) -> PrivateContext { + if historical_block_number >= cheatcodes::get_block_number() { + self.advance_block_to(historical_block_number + 1); + } + + let mut inputs = cheatcodes::get_private_context_inputs(historical_block_number); + + if (self.contract_address.is_some()) { + inputs.call_context.storage_contract_address = self.contract_address.unwrap_unchecked(); + } + + if (self.function_selector.is_some()) { + inputs.call_context.function_selector = self.function_selector.unwrap_unchecked(); + } + + let mut args_hash = 0; + + if (self.args_hash.is_some()) { + args_hash = self.args_hash.unwrap_unchecked(); + } + + PrivateContext::new(inputs, args_hash) + } + + fn store_master_key(self, key_index: Field, address: AztecAddress) -> GrumpkinPoint { + let x_coordinate_map_slot = key_index * 2 + 1; + let y_coordinate_map_slot = x_coordinate_map_slot + 1; + let x_coordinate_derived_slot = derive_storage_slot_in_map(x_coordinate_map_slot, address); + let y_coordinate_derived_slot = derive_storage_slot_in_map(y_coordinate_map_slot, address); + + let canonical_registry_address = AztecAddress::from_field(CANONICAL_KEY_REGISTRY_ADDRESS); + + let fake_key = GrumpkinPoint { x: 1, y: 2 }; + + cheatcodes::direct_storage_write( + canonical_registry_address, + x_coordinate_derived_slot, + [fake_key.x] + ); + + cheatcodes::direct_storage_write( + canonical_registry_address, + y_coordinate_derived_slot, + [fake_key.y] + ); + fake_key + } + + fn get_address_with_keys(self, address: AztecAddress) -> AztecAddress { + let keys = PublicKeys { + npk_m: self.store_master_key(NULLIFIER_INDEX, address), + ivpk_m: self.store_master_key(INCOMING_INDEX, address), + ovpk_m: self.store_master_key(OUTGOING_INDEX, address), + tpk_m: self.store_master_key(TAGGING_INDEX, address) + }; + + AztecAddress::compute(keys.hash(), PartialAddress::from_field(1)) + } + + fn deploy(self, path: str) -> Deployer { + cheatcodes::deploy(path) + } +} diff --git a/noir-projects/aztec-nr/aztec/src/test/helpers/types.nr b/noir-projects/aztec-nr/aztec/src/test/helpers/types.nr new file mode 100644 index 00000000000..e4b43538c34 --- /dev/null +++ b/noir-projects/aztec-nr/aztec/src/test/helpers/types.nr @@ -0,0 +1,42 @@ +use dep::protocol_types::address::AztecAddress; + +use crate::context::inputs::{PublicContextInputs, PrivateContextInputs}; +use crate::context::call_interfaces::CallInterface; +use crate::test::helpers::cheatcodes::{get_private_context_inputs, get_public_context_inputs, deploy_inner, advance_blocks, get_block_number}; + +struct Deployer { + path: str, + } + +impl Deployer { + pub fn with_private_initializer(self, call_interface: C) -> AztecAddress where C: CallInterface { + let address = deploy_inner( + self.path, + call_interface.get_name(), + call_interface.get_args() + ); + advance_blocks(1); + let block_number = get_block_number(); + let original_fn = call_interface.get_original(); + let mut inputs = get_private_context_inputs(block_number - 1); + inputs.call_context.storage_contract_address = address; + inputs.call_context.function_selector = call_interface.get_selector(); + let _result = original_fn(inputs); + address + } + + pub fn with_public_initializer( + self, + call_interface: C + ) -> AztecAddress where C: CallInterface { + let address = deploy_inner( + self.path, + call_interface.get_name(), + call_interface.get_args() + ); + let original_fn = call_interface.get_original(); + let mut inputs = get_public_context_inputs(); + let _result = original_fn(inputs); + address + } +} diff --git a/noir-projects/aztec-nr/aztec/src/test/mocks.nr b/noir-projects/aztec-nr/aztec/src/test/mocks.nr index fb8ef27c7bf..148cc284a9f 100644 --- a/noir-projects/aztec-nr/aztec/src/test/mocks.nr +++ b/noir-projects/aztec-nr/aztec/src/test/mocks.nr @@ -1 +1,2 @@ mod mock_note; +mod mock_struct; diff --git a/noir-projects/aztec-nr/aztec/src/test/mocks/mock_struct.nr b/noir-projects/aztec-nr/aztec/src/test/mocks/mock_struct.nr new file mode 100644 index 00000000000..1a0ed1a304e --- /dev/null +++ b/noir-projects/aztec-nr/aztec/src/test/mocks/mock_struct.nr @@ -0,0 +1,36 @@ +use dep::protocol_types::traits::{Eq, Serialize, Deserialize}; + +struct MockStruct { + a: Field, + b: Field, +} + +impl MockStruct { + fn new(a: Field, b: Field) -> Self { + Self { a, b } + } +} + +impl Eq for MockStruct { + fn eq(self, other: Self) -> bool { + (self.a == other.a) & (self.b == other.b) + } +} + +impl Serialize<2> for MockStruct { + fn serialize(self) -> [Field; 2] { + [self.a, self.b] + } +} + +impl Deserialize<2> for MockStruct { + fn deserialize(fields: [Field; 2]) -> Self { + Self { a: fields[0], b: fields[1] } + } +} + +#[test] +fn test_serde() { + let val = MockStruct::new(5, 6); + assert_eq(val, MockStruct::deserialize(val.serialize())); +} diff --git a/noir-projects/noir-contracts/contracts/counter_contract/src/main.nr b/noir-projects/noir-contracts/contracts/counter_contract/src/main.nr index cf2a235e798..c3c8cc6981f 100644 --- a/noir-projects/noir-contracts/contracts/counter_contract/src/main.nr +++ b/noir-projects/noir-contracts/contracts/counter_contract/src/main.nr @@ -37,4 +37,28 @@ contract Counter { balance_utils::get_balance(counters.at(owner).set) } // docs:end:get_counter + + use dep::aztec::test::{helpers::{cheatcodes, test_environment::TestEnvironment}}; + use dep::aztec::protocol_types::storage::map::derive_storage_slot_in_map; + use dep::aztec::oracle::{storage::{storage_read, storage_write}}; + use dep::aztec::note::note_getter::{MAX_NOTES_PER_PAGE, view_notes}; + use dep::aztec::note::note_viewer_options::NoteViewerOptions; + + #[test] + fn test_initialize() { + // Setup env, generate keys + let mut env = TestEnvironment::new(); + let owner = env.get_address_with_keys(AztecAddress::from_field(13)); + let outgoing_viewer = env.get_address_with_keys(AztecAddress::from_field(14)); + + // Deploy contract and initialize + let initializer = Counter::interface().initialize(5, owner, outgoing_viewer); + let _contract_address = env.deploy("@aztec/noir-contracts.js/Counter").with_private_initializer(initializer); + // Read the stored value in the note + let counter_slot = Counter::storage().counters.slot; + let owner_slot = derive_storage_slot_in_map(counter_slot, owner); + let mut options = NoteViewerOptions::new(); + let opt_notes: [Option; MAX_NOTES_PER_PAGE] = view_notes(owner_slot, options); + assert(opt_notes[0].unwrap().value == 5); + } } diff --git a/noir-projects/noir-protocol-circuits/crates/types/src/traits.nr b/noir-projects/noir-protocol-circuits/crates/types/src/traits.nr index bce2078479a..01af1206fec 100644 --- a/noir-projects/noir-protocol-circuits/crates/types/src/traits.nr +++ b/noir-projects/noir-protocol-circuits/crates/types/src/traits.nr @@ -110,4 +110,10 @@ impl Serialize for str { trait Deserialize { fn deserialize(fields: [Field; N]) -> Self; } -// docs:end:deserialize \ No newline at end of file +// docs:end:deserialize + +impl Deserialize for [Field; N] { + fn deserialize(fields: [Field; N]) -> Self { + fields + } +} diff --git a/noir/noir-repo/aztec_macros/src/lib.rs b/noir/noir-repo/aztec_macros/src/lib.rs index 2daf86dc643..217f86c4d9a 100644 --- a/noir/noir-repo/aztec_macros/src/lib.rs +++ b/noir/noir-repo/aztec_macros/src/lib.rs @@ -160,7 +160,7 @@ fn transform_module( // Apply transformations to the function based on collected attributes if is_private || is_public { let fn_type = if is_private { "Private" } else { "Public" }; - let stub_src = stub_function(fn_type, func, is_static); + let stub_src = stub_function(fn_type, func, is_static, is_initializer); stubs.push((stub_src, Location { file: *file_id, span: func.name_ident().span() })); export_fn_abi(&mut module.types, func)?; diff --git a/noir/noir-repo/aztec_macros/src/transforms/contract_interface.rs b/noir/noir-repo/aztec_macros/src/transforms/contract_interface.rs index 90f9ce6164a..c63538e3601 100644 --- a/noir/noir-repo/aztec_macros/src/transforms/contract_interface.rs +++ b/noir/noir-repo/aztec_macros/src/transforms/contract_interface.rs @@ -40,7 +40,12 @@ use crate::utils::{ // } // // The selector placeholder has to be replaced with the actual function signature after type checking in the next macro pass -pub fn stub_function(aztec_visibility: &str, func: &NoirFunction, is_static_call: bool) -> String { +pub fn stub_function( + aztec_visibility: &str, + func: &NoirFunction, + is_static_call: bool, + is_initializer: bool, +) -> (String, bool) { let fn_name = func.name().to_string(); let fn_parameters = func .parameters() @@ -61,11 +66,7 @@ pub fn stub_function(aztec_visibility: &str, func: &NoirFunction, is_static_call let parameters = func.parameters(); let is_void = if matches!(fn_return_type.typ, UnresolvedTypeData::Unit) { "Void" } else { "" }; let is_static = if is_static_call { "Static" } else { "" }; - let return_type_hint = if is_void == "Void" { - "".to_string() - } else { - format!("<{}>", fn_return_type.typ.to_string().replace("plain::", "")) - }; + let return_type_hint = fn_return_type.typ.to_string().replace("plain::", ""); let call_args = parameters .iter() .map(|arg| { @@ -74,21 +75,64 @@ pub fn stub_function(aztec_visibility: &str, func: &NoirFunction, is_static_call UnresolvedTypeData::Array(_, typ) => { format!( "let hash_{0} = {0}.map(|x: {1}| x.serialize()); - for i in 0..{0}.len() {{ - args_acc = args_acc.append(hash_{0}[i].as_slice()); - }}\n", + for i in 0..{0}.len() {{ + args_acc = args_acc.append(hash_{0}[i].as_slice()); + }}\n", param_name, typ.typ.to_string().replace("plain::", "") ) } - _ => { + UnresolvedTypeData::Named(_, _, _) | UnresolvedTypeData::String(_) => { format!("args_acc = args_acc.append({}.serialize().as_slice());\n", param_name) } + _ => { + format!("args_acc = args_acc.append(&[{}.to_field()]);\n", param_name) + } } }) .collect::>() .join(""); - if aztec_visibility != "Public" { + + let param_types = if parameters.len() > 0 { + parameters + .iter() + .map(|param| param.pattern.name_ident().0.contents.clone()) + .collect::>() + .join(", ") + } else { + "".to_string() + }; + + let original = format!( + "| inputs: dep::aztec::context::inputs::{}ContextInputs | -> {} {{ + let _ = failing_env_workaround; + {}(inputs{}) + }}", + aztec_visibility, + if aztec_visibility == "Private" { + "dep::aztec::protocol_types::abis::private_circuit_public_inputs::PrivateCircuitPublicInputs".to_string() + } else { + return_type_hint.clone() + }, + fn_name, + if param_types.is_empty() { "".to_string() } else { format!(" ,{} ", param_types) } + ); + let arg_types = format!( + "(Field, {})", + parameters + .iter() + .map(|param| param.typ.typ.to_string().replace("plain::", "")) + .collect::>() + .join(",") + ); + + let generics = if is_void == "Void" { + format!("{}>", arg_types) + } else { + format!("{}, {}>", return_type_hint, arg_types) + }; + + let fn_body = if aztec_visibility != "Public" { let args_hash = if !parameters.is_empty() { format!( "let mut args_acc: [Field] = &[]; @@ -101,20 +145,19 @@ pub fn stub_function(aztec_visibility: &str, func: &NoirFunction, is_static_call "let args_hash = 0;".to_string() }; - let fn_body = format!( - "{} - dep::aztec::context::{}{}{}CallInterface {{ - target_contract: self.target_contract, - selector: {}, - args_hash, - }}", - args_hash, aztec_visibility, is_static, is_void, fn_selector, - ); format!( - "pub fn {}(self, {}) -> dep::aztec::context::{}{}{}CallInterface{} {{ - {} - }}", - fn_name, fn_parameters, aztec_visibility, is_static, is_void, return_type_hint, fn_body + "{} + let failing_env_workaround = 0; + let selector = {}; + dep::aztec::context::{}{}{}CallInterface {{ + target_contract: self.target_contract, + selector, + name: \"{}\", + args_hash, + args: args_acc, + original: {} + }}", + args_hash, fn_selector, aztec_visibility, is_static, is_void, fn_name, original ) } else { let args = format!( @@ -123,23 +166,37 @@ pub fn stub_function(aztec_visibility: &str, func: &NoirFunction, is_static_call ", call_args ); - let fn_body = format!( + format!( "{} - dep::aztec::context::Public{}{}CallInterface {{ + let failing_env_workaround = 0; + let selector = {}; + dep::aztec::context::{}{}{}CallInterface {{ target_contract: self.target_contract, - selector: {}, + selector, + name: \"{}\", args: args_acc, gas_opts: dep::aztec::context::gas::GasOpts::default(), + original: {} }}", - args, is_static, is_void, fn_selector, - ); - format!( - "pub fn {}(self, {}) -> dep::aztec::context::Public{}{}CallInterface{} {{ - {} - }}", - fn_name, fn_parameters, is_static, is_void, return_type_hint, fn_body + args, fn_selector, aztec_visibility, is_static, is_void, fn_name, original ) - } + }; + ( + format!( + "pub fn {}(self, {}) -> dep::aztec::context::{}{}{}CallInterface<{},{} {{ + {} + }}", + fn_name, + fn_parameters, + aztec_visibility, + is_static, + is_void, + fn_name.len(), + generics, + fn_body + ), + is_initializer, + ) } // Generates the contract interface as a struct with an `at` function that holds the stubbed functions and provides @@ -148,7 +205,7 @@ pub fn stub_function(aztec_visibility: &str, func: &NoirFunction, is_static_call pub fn generate_contract_interface( module: &mut SortedModule, module_name: &str, - stubs: &[(String, Location)], + stubs: &[((String, bool), Location)], ) -> Result<(), AztecMacroError> { let contract_interface = format!( " @@ -164,6 +221,14 @@ pub fn generate_contract_interface( ) -> Self {{ Self {{ target_contract }} }} + + pub fn interface() -> Self {{ + Self {{ target_contract: dep::aztec::protocol_types::address::AztecAddress::zero() }} + }} + + pub fn storage() -> StorageLayout {{ + STORAGE_LAYOUT + }} }} #[contract_library_method] @@ -172,9 +237,19 @@ pub fn generate_contract_interface( ) -> {0} {{ {0} {{ target_contract }} }} + + #[contract_library_method] + pub fn interface() -> {0} {{ + {0} {{ target_contract: dep::aztec::protocol_types::address::AztecAddress::zero() }} + }} + + #[contract_library_method] + pub fn storage() -> StorageLayout {{ + STORAGE_LAYOUT + }} ", module_name, - stubs.iter().map(|(src, _)| src.to_owned()).collect::>().join("\n"), + stubs.iter().map(|((src, _), _)| src.to_owned()).collect::>().join("\n"), ); let (contract_interface_ast, errors) = parse_program(&contract_interface); @@ -191,7 +266,7 @@ pub fn generate_contract_interface( .iter() .enumerate() .map(|(i, (method, orig_span))| { - if method.name() == "at" { + if method.name() == "at" || method.name() == "interface" || method.name() == "storage" { (method.clone(), *orig_span) } else { let (_, new_location) = stubs[i]; @@ -205,7 +280,9 @@ pub fn generate_contract_interface( module.types.push(contract_interface_ast.types.pop().unwrap()); module.impls.push(impl_with_locations); - module.functions.push(contract_interface_ast.functions.pop().unwrap()); + for function in contract_interface_ast.functions { + module.functions.push(function); + } Ok(()) } @@ -244,7 +321,7 @@ pub fn update_fn_signatures_in_contract_interface( let name = context.def_interner.function_name(func_id); let fn_parameters = &context.def_interner.function_meta(func_id).parameters.clone(); - if name == "at" { + if name == "at" || name == "interface" || name == "storage" { continue; } @@ -257,42 +334,29 @@ pub fn update_fn_signatures_in_contract_interface( .collect::>(), ); let hir_func = context.def_interner.function(func_id).block(&context.def_interner); - let call_interface_constructor_statement = context.def_interner.statement( - hir_func - .statements() - .last() - .ok_or((AztecMacroError::AztecDepNotFound, file_id))?, + + let function_selector_statement = context.def_interner.statement( + hir_func.statements().get(hir_func.statements().len() - 2).ok_or(( + AztecMacroError::CouldNotGenerateContractInterface { + secondary_message: Some( + "Function signature statement not found, invalid body length" + .to_string(), + ), + }, + file_id, + ))?, ); - let call_interface_constructor_expression = - match call_interface_constructor_statement { - HirStatement::Expression(expression_id) => { - match context.def_interner.expression(&expression_id) { - HirExpression::Constructor(hir_constructor_expression) => { - Ok(hir_constructor_expression) - } - _ => Err(( - AztecMacroError::CouldNotGenerateContractInterface { - secondary_message: Some( - "CallInterface constructor statement must be a constructor expression" - .to_string(), - ), - }, - file_id, - )), - } - } - _ => Err(( - AztecMacroError::CouldNotGenerateContractInterface { - secondary_message: Some( - "CallInterface constructor statement must be an expression" - .to_string(), - ), - }, - file_id, - )), - }?; - let (_, function_selector_expression_id) = - call_interface_constructor_expression.fields[1]; + let function_selector_expression_id = match function_selector_statement { + HirStatement::Let(let_statement) => Ok(let_statement.expression), + _ => Err(( + AztecMacroError::CouldNotGenerateContractInterface { + secondary_message: Some( + "Function selector statement must be an expression".to_string(), + ), + }, + file_id, + )), + }?; let function_selector_expression = context.def_interner.expression(&function_selector_expression_id); diff --git a/noir/noir-repo/aztec_macros/src/transforms/storage.rs b/noir/noir-repo/aztec_macros/src/transforms/storage.rs index a1c21c7efcf..a46754d02fa 100644 --- a/noir/noir-repo/aztec_macros/src/transforms/storage.rs +++ b/noir/noir-repo/aztec_macros/src/transforms/storage.rs @@ -504,23 +504,18 @@ pub fn generate_storage_layout( .find(|r#struct| r#struct.name.0.contents == *storage_struct_name) .unwrap(); - let mut generic_args = vec![]; let mut storable_fields = vec![]; let mut storable_fields_impl = vec![]; - definition.fields.iter().enumerate().for_each(|(index, (field_ident, field_type))| { - storable_fields.push(format!("{}: dep::aztec::prelude::Storable", field_ident, index)); - generic_args.push(format!("N{}", index)); - storable_fields_impl.push(format!( - "{}: dep::aztec::prelude::Storable {{ slot: 0, typ: \"{}\" }}", - field_ident, - field_type.to_string().replace("plain::", "") - )); + definition.fields.iter().for_each(|(field_ident, _)| { + storable_fields.push(format!("{}: dep::aztec::prelude::Storable", field_ident)); + storable_fields_impl + .push(format!("{}: dep::aztec::prelude::Storable {{ slot: 0 }}", field_ident,)); }); let storage_fields_source = format!( " - struct StorageLayout<{}> {{ + struct StorageLayout {{ {} }} @@ -529,7 +524,6 @@ pub fn generate_storage_layout( {} }}; ", - generic_args.join(", "), storable_fields.join(",\n"), storable_fields_impl.join(",\n") ); diff --git a/noir/noir-repo/compiler/noirc_frontend/src/monomorphization/mod.rs b/noir/noir-repo/compiler/noirc_frontend/src/monomorphization/mod.rs index a25d6488c83..239f644faff 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/monomorphization/mod.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/monomorphization/mod.rs @@ -246,7 +246,12 @@ impl<'interner> Monomorphizer<'interner> { } } FunctionKind::Recursive => { - unreachable!("Only main can be specified as recursive, which should already be checked"); + // let func = self.interner.function_meta(&id); + // println!("{:#?}", func.name); + let id = + self.queue_function(id, expr_id, typ, turbofish_generics, trait_method); + Definition::Function(id) + //unreachable!("wtf"); } } } diff --git a/noir/noir-repo/tooling/nargo_cli/src/cli/test_cmd.rs b/noir/noir-repo/tooling/nargo_cli/src/cli/test_cmd.rs index 99c284e5019..838642b587d 100644 --- a/noir/noir-repo/tooling/nargo_cli/src/cli/test_cmd.rs +++ b/noir/noir-repo/tooling/nargo_cli/src/cli/test_cmd.rs @@ -82,7 +82,7 @@ pub(crate) fn run(args: TestCommand, config: NargoConfig) -> Result<(), CliError let test_reports: Vec> = workspace .into_iter() - .par_bridge() + //.par_bridge() .map(|package| { run_tests::( &workspace_file_manager, @@ -137,7 +137,7 @@ fn run_tests + Default>( println!("[{}] Running {count_all} test function{plural}", package.name); let test_report: Vec<(String, TestStatus)> = test_functions - .into_par_iter() + .into_iter() .map(|test_name| { let status = run_test::( file_manager, diff --git a/yarn-project/archiver/src/archiver/index.ts b/yarn-project/archiver/src/archiver/index.ts index a7294537624..81aa8727e17 100644 --- a/yarn-project/archiver/src/archiver/index.ts +++ b/yarn-project/archiver/src/archiver/index.ts @@ -3,3 +3,4 @@ export * from './config.js'; export { MemoryArchiverStore } from './memory_archiver_store/memory_archiver_store.js'; export { ArchiverDataStore } from './archiver_store.js'; export { KVArchiverDataStore } from './kv_archiver_store/kv_archiver_store.js'; +export { ContractInstanceStore } from './kv_archiver_store/contract_instance_store.js'; diff --git a/yarn-project/builder/src/contract-interface-gen/typescript.ts b/yarn-project/builder/src/contract-interface-gen/typescript.ts index 4c2911fba97..e4519b9e0af 100644 --- a/yarn-project/builder/src/contract-interface-gen/typescript.ts +++ b/yarn-project/builder/src/contract-interface-gen/typescript.ts @@ -194,10 +194,9 @@ function generateStorageLayoutGetter(input: ContractArtifact) { const storageFieldsUnionType = entries.map(([name]) => `'${name}'`).join(' | '); const layout = entries .map( - ([name, { slot, typ }]) => + ([name, { slot }]) => `${name}: { slot: new Fr(${slot.toBigInt()}n), - typ: "${typ}", }`, ) .join(',\n'); diff --git a/yarn-project/circuits.js/src/contract/contract_instance.ts b/yarn-project/circuits.js/src/contract/contract_instance.ts index 1d45791cdab..4c047461664 100644 --- a/yarn-project/circuits.js/src/contract/contract_instance.ts +++ b/yarn-project/circuits.js/src/contract/contract_instance.ts @@ -27,7 +27,6 @@ export function getContractInstanceFromDeployParams( const salt = opts.salt ?? Fr.random(); const constructorArtifact = getConstructorArtifact(artifact, opts.constructorArtifact); const deployer = opts.deployer ?? AztecAddress.ZERO; - const contractClass = getContractClassFromArtifact(artifact); const contractClassId = computeContractClassId(contractClass); const initializationHash = computeInitializationHash(constructorArtifact, args); diff --git a/yarn-project/foundation/src/abi/abi.ts b/yarn-project/foundation/src/abi/abi.ts index 200e7b0088c..2b608086604 100644 --- a/yarn-project/foundation/src/abi/abi.ts +++ b/yarn-project/foundation/src/abi/abi.ts @@ -290,10 +290,6 @@ export type FieldLayout = { * Slot in which the field is stored. */ slot: Fr; - /** - * Type being stored at the slot (e.g., 'Map>') - */ - typ: string; }; /** diff --git a/yarn-project/package.json b/yarn-project/package.json index fa08f05b063..388c8f4d6df 100644 --- a/yarn-project/package.json +++ b/yarn-project/package.json @@ -51,6 +51,7 @@ "sequencer-client", "scripts", "types", + "txe", "world-state" ], "prettier": "@aztec/foundation/prettier", diff --git a/yarn-project/simulator/src/client/execution_note_cache.ts b/yarn-project/simulator/src/client/execution_note_cache.ts index 325e0b8f80b..e74123138e6 100644 --- a/yarn-project/simulator/src/client/execution_note_cache.ts +++ b/yarn-project/simulator/src/client/execution_note_cache.ts @@ -54,6 +54,7 @@ export class ExecutionNoteCache { let nullifiedNoteHashCounter: number | undefined = undefined; // Find and remove the matching new note and log(s) if the emitted innerNoteHash is not empty. if (!innerNoteHash.equals(Fr.ZERO)) { + console.log('DELETING A NOTE'); const notes = this.newNotes.get(contractAddress.toBigInt()) ?? []; const noteIndexToRemove = notes.findIndex(n => n.note.innerNoteHash.equals(innerNoteHash)); if (noteIndexToRemove === -1) { @@ -62,6 +63,8 @@ export class ExecutionNoteCache { const note = notes.splice(noteIndexToRemove, 1)[0]; nullifiedNoteHashCounter = note.counter; this.newNotes.set(contractAddress.toBigInt(), notes); + } else { + console.log('NOT DELETING A NOTE'); } return nullifiedNoteHashCounter; diff --git a/yarn-project/simulator/src/client/index.ts b/yarn-project/simulator/src/client/index.ts index 60fcf15d4a9..bb30eff1312 100644 --- a/yarn-project/simulator/src/client/index.ts +++ b/yarn-project/simulator/src/client/index.ts @@ -1,3 +1,5 @@ export * from './simulator.js'; export * from './db_oracle.js'; export * from './execution_result.js'; +export * from './pick_notes.js'; +export * from './execution_note_cache.js'; diff --git a/yarn-project/txe/.eslintrc.cjs b/yarn-project/txe/.eslintrc.cjs new file mode 100644 index 00000000000..e659927475c --- /dev/null +++ b/yarn-project/txe/.eslintrc.cjs @@ -0,0 +1 @@ +module.exports = require('@aztec/foundation/eslint'); diff --git a/yarn-project/txe/package.json b/yarn-project/txe/package.json new file mode 100644 index 00000000000..87159321240 --- /dev/null +++ b/yarn-project/txe/package.json @@ -0,0 +1,80 @@ +{ + "name": "@aztec/txe", + "version": "0.0.0", + "type": "module", + "exports": "./dest/index.js", + "bin": "./dest/bin/index.js", + "typedocOptions": { + "entryPoints": [ + "./src/index.ts" + ], + "name": "TXE", + "tsconfig": "./tsconfig.json" + }, + "scripts": { + "build": "yarn clean && tsc -b", + "build:dev": "tsc -b --watch", + "clean": "rm -rf ./dest .tsbuildinfo", + "formatting": "run -T prettier --check ./src && run -T eslint ./src", + "formatting:fix": "run -T eslint --fix ./src && run -T prettier -w ./src", + "test": "NODE_NO_WARNINGS=1 node --experimental-vm-modules ../node_modules/.bin/jest --passWithNoTests", + "start": "DEBUG='aztec:*' && node ./dest/bin/index.js" + }, + "inherits": [ + "../package.common.json" + ], + "jest": { + "moduleNameMapper": { + "^(\\.{1,2}/.*)\\.[cm]?js$": "$1" + }, + "testRegex": "./src/.*\\.test\\.(js|mjs|ts)$", + "rootDir": "./src", + "workerThreads": true, + "transform": { + "^.+\\.tsx?$": [ + "@swc/jest" + ] + }, + "extensionsToTreatAsEsm": [ + ".ts" + ], + "reporters": [ + [ + "default", + { + "summaryThreshold": 9999 + } + ] + ] + }, + "dependencies": { + "@aztec/archiver": "workspace:^", + "@aztec/aztec.js": "workspace:^", + "@aztec/circuit-types": "workspace:^", + "@aztec/circuits.js": "workspace:^", + "@aztec/foundation": "workspace:^", + "@aztec/key-store": "workspace:^", + "@aztec/kv-store": "workspace:^", + "@aztec/simulator": "workspace:^", + "@aztec/types": "workspace:^", + "@aztec/world-state": "workspace:^" + }, + "devDependencies": { + "@jest/globals": "^29.5.0", + "@types/jest": "^29.5.0", + "@types/node": "^18.7.23", + "jest": "^29.5.0", + "jest-mock-extended": "^3.0.3", + "ts-node": "^10.9.1", + "typescript": "^5.0.4" + }, + "files": [ + "dest", + "src", + "!*.test.*" + ], + "types": "./dest/index.d.ts", + "engines": { + "node": ">=18" + } +} diff --git a/yarn-project/txe/src/bin/index.ts b/yarn-project/txe/src/bin/index.ts new file mode 100644 index 00000000000..365e2054be7 --- /dev/null +++ b/yarn-project/txe/src/bin/index.ts @@ -0,0 +1,27 @@ +#!/usr/bin/env -S node --no-warnings +import { createDebugLogger } from '@aztec/foundation/log'; + +import { startTXEHttpServer } from '../index.js'; +import { TXEService } from '../txe_service/txe_service.js'; + +const { TXE_PORT = 8080 } = process.env; + +const logger = createDebugLogger('aztec:txe_service'); + +/** + * Create and start a new PXE HTTP Server + */ +async function main() { + logger.info(`Setting up TXE...`); + + const txeService = await TXEService.init(logger); + + startTXEHttpServer(txeService, TXE_PORT); + + logger.info(`TXE listening on port ${TXE_PORT}`); +} + +main().catch(err => { + logger.error(err); + process.exit(1); +}); diff --git a/yarn-project/txe/src/http_rpc_server/index.ts b/yarn-project/txe/src/http_rpc_server/index.ts new file mode 100644 index 00000000000..69d9e08464b --- /dev/null +++ b/yarn-project/txe/src/http_rpc_server/index.ts @@ -0,0 +1,29 @@ +import { JsonRpcServer } from '@aztec/foundation/json-rpc/server'; + +import http from 'http'; + +import { type TXEService } from '../txe_service/txe_service.js'; + +/** + * Wraps an instance of Private eXecution Environment (TXE) implementation to a JSON RPC HTTP interface. + * @returns A new instance of the HTTP server. + */ +export function createTXERpcServer(txeService: TXEService): JsonRpcServer { + return new JsonRpcServer(txeService, {}, {}, ['init']); +} + +/** + * Creates an http server that forwards calls to the TXE and starts it on the given port. + * @param txeService - TXE that answers queries to the created HTTP server. + * @param port - Port to listen in. + * @returns A running http server. + */ +export function startTXEHttpServer(txeService: TXEService, port: string | number): http.Server { + const txeServer = createTXERpcServer(txeService); + + const app = txeServer.getApp(); + const httpServer = http.createServer(app.callback()); + httpServer.listen(port); + + return httpServer; +} diff --git a/yarn-project/txe/src/index.ts b/yarn-project/txe/src/index.ts new file mode 100644 index 00000000000..6f83d1f63b2 --- /dev/null +++ b/yarn-project/txe/src/index.ts @@ -0,0 +1 @@ +export * from './http_rpc_server/index.js'; diff --git a/yarn-project/txe/src/oracle/txe_oracle.ts b/yarn-project/txe/src/oracle/txe_oracle.ts new file mode 100644 index 00000000000..59ae24e756d --- /dev/null +++ b/yarn-project/txe/src/oracle/txe_oracle.ts @@ -0,0 +1,356 @@ +import { type ContractInstanceStore } from '@aztec/archiver'; +import { + L1NotePayload, + MerkleTreeId, + Note, + type NoteStatus, + type NullifierMembershipWitness, + PublicDataWitness, + PublicDataWrite, + TaggedNote, + type UnencryptedL2Log, +} from '@aztec/circuit-types'; +import { + CompleteAddress, + type Header, + KeyValidationRequest, + NULLIFIER_SUBTREE_HEIGHT, + PUBLIC_DATA_SUBTREE_HEIGHT, + type PUBLIC_DATA_TREE_HEIGHT, + type PrivateCallStackItem, + type PublicCallRequest, + PublicDataTreeLeaf, + type PublicDataTreeLeafPreimage, +} from '@aztec/circuits.js'; +import { Aes128 } from '@aztec/circuits.js/barretenberg'; +import { computePublicDataTreeLeafSlot, siloNoteHash, siloNullifier } from '@aztec/circuits.js/hash'; +import { type FunctionSelector } from '@aztec/foundation/abi'; +import { AztecAddress } from '@aztec/foundation/aztec-address'; +import { Fr, GrumpkinScalar, type Point } from '@aztec/foundation/fields'; +import { type Logger, applyStringFormatting } from '@aztec/foundation/log'; +import { KeyStore } from '@aztec/key-store'; +import { + type ExecutionNoteCache, + type MessageLoadOracleInputs, + type NoteData, + type PackedValuesCache, + type TypedOracle, + pickNotes, +} from '@aztec/simulator'; +import { type ContractInstance } from '@aztec/types/contracts'; +import { MerkleTreeSnapshotOperationsFacade, type MerkleTrees } from '@aztec/world-state'; + +export class TXE implements TypedOracle { + constructor( + private logger: Logger, + private trees: MerkleTrees, + private packedValuesCache: PackedValuesCache, + private noteCache: ExecutionNoteCache, + private contractInstanceStore: ContractInstanceStore, + private keyStore: KeyStore, + private contractAddress: AztecAddress, + ) {} + + setContractAddress(contractAddress: AztecAddress) { + this.contractAddress = contractAddress; + } + + getRandomField() { + return Fr.random(); + } + + packArgumentsArray(args: Fr[]): Promise { + return Promise.resolve(this.packedValuesCache.pack(args)); + } + + packReturns(returns: Fr[]): Promise { + return Promise.resolve(this.packedValuesCache.pack(returns)); + } + + unpackReturns(returnsHash: Fr): Promise { + return Promise.resolve(this.packedValuesCache.unpack(returnsHash)); + } + + getKeyValidationRequest(pkMHash: Fr): Promise { + //return this.keyStore.getKeyValidationRequest(pkMHash, this.contractAddress); + return Promise.resolve(KeyValidationRequest.empty()); + } + + getContractInstance(address: AztecAddress): Promise { + const contractInstance = this.contractInstanceStore.getContractInstance(address); + if (!contractInstance) { + throw new Error(`Contract instance not found for address ${address}`); + } + return Promise.resolve(contractInstance); + } + + getMembershipWitness(_blockNumber: number, _treeId: MerkleTreeId, _leafValue: Fr): Promise { + throw new Error('Method not implemented.'); + } + + async getSiblingPath(blockNumber: number, treeId: MerkleTreeId, leafIndex: Fr) { + const committedDb = new MerkleTreeSnapshotOperationsFacade(this.trees, blockNumber); + const result = await committedDb.getSiblingPath(treeId, leafIndex.toBigInt()); + return result.toFields(); + } + + getNullifierMembershipWitness(_blockNumber: number, _nullifier: Fr): Promise { + throw new Error('Method not implemented.'); + } + + async getPublicDataTreeWitness(blockNumber: number, leafSlot: Fr): Promise { + const committedDb = new MerkleTreeSnapshotOperationsFacade(this.trees, blockNumber); + const lowLeafResult = await committedDb.getPreviousValueIndex(MerkleTreeId.PUBLIC_DATA_TREE, leafSlot.toBigInt()); + if (!lowLeafResult) { + return undefined; + } else { + const preimage = (await committedDb.getLeafPreimage( + MerkleTreeId.PUBLIC_DATA_TREE, + lowLeafResult.index, + )) as PublicDataTreeLeafPreimage; + const path = await committedDb.getSiblingPath( + MerkleTreeId.PUBLIC_DATA_TREE, + lowLeafResult.index, + ); + return new PublicDataWitness(lowLeafResult.index, preimage, path); + } + } + + getLowNullifierMembershipWitness( + _blockNumber: number, + _nullifier: Fr, + ): Promise { + throw new Error('Method not implemented.'); + } + + getHeader(_blockNumber: number): Promise
{ + throw new Error('Method not implemented.'); + } + + getCompleteAddress(account: AztecAddress): Promise { + return Promise.resolve(CompleteAddress.fromSecretKeyAndPartialAddress(Fr.ONE, account)); + } + + getAuthWitness(_messageHash: Fr): Promise { + throw new Error('Method not implemented.'); + } + + popCapsule(): Promise { + throw new Error('Method not implemented.'); + } + + getNotes( + storageSlot: Fr, + numSelects: number, + selectByIndexes: number[], + selectByOffsets: number[], + selectByLengths: number[], + selectValues: Fr[], + selectComparators: number[], + sortByIndexes: number[], + sortByOffsets: number[], + sortByLengths: number[], + sortOrder: number[], + limit: number, + offset: number, + _status: NoteStatus, + ) { + // Nullified pending notes are already removed from the list. + const pendingNotes = this.noteCache.getNotes(this.contractAddress, storageSlot); + + // const pendingNullifiers = this.noteCache.getNullifiers(this.contractAddress); + // const dbNotes = await this.db.getNotes(this.contractAddress, storageSlot, status); + // const dbNotesFiltered = dbNotes.filter(n => !pendingNullifiers.has((n.siloedNullifier as Fr).value)); + + const notes = pickNotes(pendingNotes, { + selects: selectByIndexes.slice(0, numSelects).map((index, i) => ({ + selector: { index, offset: selectByOffsets[i], length: selectByLengths[i] }, + value: selectValues[i], + comparator: selectComparators[i], + })), + sorts: sortByIndexes.map((index, i) => ({ + selector: { index, offset: sortByOffsets[i], length: sortByLengths[i] }, + order: sortOrder[i], + })), + limit, + offset, + }); + + this.logger.debug( + `Returning ${notes.length} notes for ${this.contractAddress} at ${storageSlot}: ${notes + .map(n => `${n.nonce.toString()}:[${n.note.items.map(i => i.toString()).join(',')}]`) + .join(', ')}`, + ); + + return Promise.resolve(notes); + } + + async notifyCreatedNote(storageSlot: Fr, noteTypeId: Fr, noteItems: Fr[], innerNoteHash: Fr, counter: number) { + const note = new Note(noteItems); + this.noteCache.addNewNote( + { + contractAddress: this.contractAddress, + storageSlot, + nonce: Fr.ZERO, // Nonce cannot be known during private execution. + note, + siloedNullifier: undefined, // Siloed nullifier cannot be known for newly created note. + innerNoteHash, + }, + counter, + ); + const db = this.trees.asLatest(); + const noteHash = siloNoteHash(this.contractAddress, innerNoteHash); + await db.appendLeaves(MerkleTreeId.NOTE_HASH_TREE, [noteHash]); + } + + async notifyNullifiedNote(innerNullifier: Fr, innerNoteHash: Fr, _counter: number) { + this.noteCache.nullifyNote(this.contractAddress, innerNullifier, innerNoteHash); + const db = this.trees.asLatest(); + const siloedNullifier = siloNullifier(this.contractAddress, innerNullifier); + await db.batchInsert(MerkleTreeId.NULLIFIER_TREE, [siloedNullifier.toBuffer()], NULLIFIER_SUBTREE_HEIGHT); + return Promise.resolve(); + } + + async checkNullifierExists(innerNullifier: Fr): Promise { + const nullifier = siloNullifier(this.contractAddress, innerNullifier!); + const db = this.trees.asLatest(); + const index = await db.findLeafIndex(MerkleTreeId.NULLIFIER_TREE, nullifier.toBuffer()); + return index !== undefined; + } + + getL1ToL2MembershipWitness( + _contractAddress: AztecAddress, + _messageHash: Fr, + _secret: Fr, + ): Promise> { + throw new Error('Method not implemented.'); + } + + async storageRead(startStorageSlot: Fr, numberOfElements: number): Promise { + const db = this.trees.asLatest(); + + const values = []; + for (let i = 0n; i < numberOfElements; i++) { + const storageSlot = startStorageSlot.add(new Fr(i)); + const leafSlot = computePublicDataTreeLeafSlot(this.contractAddress, storageSlot).toBigInt(); + + const lowLeafResult = await db.getPreviousValueIndex(MerkleTreeId.PUBLIC_DATA_TREE, leafSlot); + + let value = Fr.ZERO; + if (lowLeafResult && lowLeafResult.alreadyPresent) { + const preimage = (await db.getLeafPreimage( + MerkleTreeId.PUBLIC_DATA_TREE, + lowLeafResult.index, + )) as PublicDataTreeLeafPreimage; + value = preimage.value; + } + this.logger.debug(`Oracle storage read: slot=${storageSlot.toString()} value=${value}`); + values.push(value); + } + return values; + } + + async storageWrite(startStorageSlot: Fr, values: Fr[]): Promise { + const db = this.trees.asLatest(); + + const publicDataWrites = values.map((value, i) => { + const storageSlot = startStorageSlot.add(new Fr(i)); + this.logger.debug(`Oracle storage write: slot=${storageSlot.toString()} value=${value}`); + return new PublicDataWrite(computePublicDataTreeLeafSlot(this.contractAddress, storageSlot), value); + }); + await db.batchInsert( + MerkleTreeId.PUBLIC_DATA_TREE, + publicDataWrites.map(write => new PublicDataTreeLeaf(write.leafIndex, write.newValue).toBuffer()), + PUBLIC_DATA_SUBTREE_HEIGHT, + ); + return publicDataWrites.map(write => write.newValue); + } + + emitEncryptedLog(_contractAddress: AztecAddress, _randomness: Fr, _encryptedNote: Buffer, _counter: number): void { + throw new Error('Method not implemented.'); + } + + emitEncryptedNoteLog(_noteHashCounter: number, _encryptedNote: Buffer, _counter: number): void { + throw new Error('Method not implemented.'); + } + + computeEncryptedLog( + contractAddress: AztecAddress, + storageSlot: Fr, + noteTypeId: Fr, + ovKeys: KeyValidationRequest, + ivpkM: Point, + preimage: Fr[], + ): Buffer { + const note = new Note(preimage); + const l1NotePayload = new L1NotePayload(note, contractAddress, storageSlot, noteTypeId); + const taggedNote = new TaggedNote(l1NotePayload); + + const ephSk = GrumpkinScalar.random(); + + const recipient = AztecAddress.random(); + + return taggedNote.encrypt(ephSk, recipient, ivpkM, ovKeys); + } + + emitUnencryptedLog(_log: UnencryptedL2Log, _counter: number): void { + throw new Error('Method not implemented.'); + } + + emitContractClassUnencryptedLog(_log: UnencryptedL2Log, _counter: number): Fr { + throw new Error('Method not implemented.'); + } + + callPrivateFunction( + _targetContractAddress: AztecAddress, + _functionSelector: FunctionSelector, + _argsHash: Fr, + _sideEffectCounter: number, + _isStaticCall: boolean, + _isDelegateCall: boolean, + ): Promise { + throw new Error('Method not implemented.'); + } + + callPublicFunction( + _targetContractAddress: AztecAddress, + _functionSelector: FunctionSelector, + _argsHash: Fr, + _sideEffectCounter: number, + _isStaticCall: boolean, + _isDelegateCall: boolean, + ): Promise { + throw new Error('Method not implemented.'); + } + + enqueuePublicFunctionCall( + _targetContractAddress: AztecAddress, + _functionSelector: FunctionSelector, + _argsHash: Fr, + _sideEffectCounter: number, + _isStaticCall: boolean, + _isDelegateCall: boolean, + ): Promise { + throw new Error('Method not implemented.'); + } + + setPublicTeardownFunctionCall( + _targetContractAddress: AztecAddress, + _functionSelector: FunctionSelector, + _argsHash: Fr, + _sideEffectCounter: number, + _isStaticCall: boolean, + _isDelegateCall: boolean, + ): Promise { + throw new Error('Method not implemented.'); + } + + aes128Encrypt(input: Buffer, initializationVector: Buffer, key: Buffer): Buffer { + const aes128 = new Aes128(); + return aes128.encryptBufferCBC(input, initializationVector, key); + } + + debugLog(message: string, fields: Fr[]): void { + this.logger.verbose(`debug_log ${applyStringFormatting(message, fields)}`); + } +} diff --git a/yarn-project/txe/src/txe_service/txe_service.ts b/yarn-project/txe/src/txe_service/txe_service.ts new file mode 100644 index 00000000000..7da9912a06d --- /dev/null +++ b/yarn-project/txe/src/txe_service/txe_service.ts @@ -0,0 +1,431 @@ +import { ContractInstanceStore } from '@aztec/archiver'; +import { L2Block, MerkleTreeId, PublicDataWrite } from '@aztec/circuit-types'; +import { + Fr, + Header, + KeyValidationRequest, + PUBLIC_DATA_SUBTREE_HEIGHT, + Point, + PrivateContextInputs, + PublicDataTreeLeaf, + getContractInstanceFromDeployParams, +} from '@aztec/circuits.js'; +import { computePublicDataTreeLeafSlot } from '@aztec/circuits.js/hash'; +import { AztecAddress } from '@aztec/foundation/aztec-address'; +import { type Logger } from '@aztec/foundation/log'; +import { KeyStore } from '@aztec/key-store'; +import { type AztecKVStore } from '@aztec/kv-store'; +import { openTmpStore } from '@aztec/kv-store/utils'; +import { ExecutionNoteCache, PackedValuesCache, type TypedOracle } from '@aztec/simulator'; +import { MerkleTrees } from '@aztec/world-state'; + +import { TXE } from '../oracle/txe_oracle.js'; +import { + type ForeignCallArray, + type ForeignCallSingle, + fromArray, + fromSingle, + toArray, + toForeignCallResult, + toSingle, +} from '../util/encoding.js'; + +export class TXEService { + private blockNumber = 0; + + constructor( + private logger: Logger, + private typedOracle: TypedOracle, + private store: AztecKVStore, + private trees: MerkleTrees, + private contractInstanceStore: ContractInstanceStore, + private keyStore: KeyStore, + private contractAddress: AztecAddress, + ) {} + + static async init(logger: Logger, contractAddress = AztecAddress.random()) { + const store = openTmpStore(true); + const trees = await MerkleTrees.new(store, logger); + const packedValuesCache = new PackedValuesCache(); + const noteCache = new ExecutionNoteCache(); + const contractInstanceStore = new ContractInstanceStore(store); + const keyStore = new KeyStore(store); + logger.info(`TXE service initialized`); + const txe = new TXE(logger, trees, packedValuesCache, noteCache, contractInstanceStore, keyStore, contractAddress); + const service = new TXEService(logger, txe, store, trees, contractInstanceStore, keyStore, contractAddress); + await service.timeTravel(toSingle(new Fr(1n))); + return service; + } + + // Cheatcodes + + async getPrivateContextInputs(blockNumber: ForeignCallSingle) { + const inputs = PrivateContextInputs.empty(); + const stateReference = await this.trees.getStateReference(true); + inputs.historicalHeader.globalVariables.blockNumber = fromSingle(blockNumber); + inputs.historicalHeader.state = stateReference; + inputs.callContext.msgSender = AztecAddress.random(); + inputs.callContext.storageContractAddress = this.contractAddress; + return toForeignCallResult(inputs.toFields().map(toSingle)); + } + + async timeTravel(blocks: ForeignCallSingle) { + const nBlocks = fromSingle(blocks).toNumber(); + this.logger.info(`time traveling ${nBlocks} blocks`); + for (let i = 0; i < nBlocks; i++) { + const header = Header.empty(); + const l2Block = L2Block.empty(); + header.state = await this.trees.getStateReference(true); + header.globalVariables.blockNumber = new Fr(this.blockNumber); + header.state.partial.nullifierTree.root = Fr.fromBuffer( + (await this.trees.getTreeInfo(MerkleTreeId.NULLIFIER_TREE, true)).root, + ); + header.state.partial.noteHashTree.root = Fr.fromBuffer( + (await this.trees.getTreeInfo(MerkleTreeId.NOTE_HASH_TREE, true)).root, + ); + header.state.partial.publicDataTree.root = Fr.fromBuffer( + (await this.trees.getTreeInfo(MerkleTreeId.PUBLIC_DATA_TREE, true)).root, + ); + header.state.l1ToL2MessageTree.root = Fr.fromBuffer( + (await this.trees.getTreeInfo(MerkleTreeId.L1_TO_L2_MESSAGE_TREE, true)).root, + ); + l2Block.archive.root = Fr.fromBuffer((await this.trees.getTreeInfo(MerkleTreeId.ARCHIVE, true)).root); + l2Block.header = header; + await this.trees.handleL2BlockAndMessages(l2Block, []); + this.blockNumber++; + } + return toForeignCallResult([]); + } + + async reset() { + this.blockNumber = 0; + this.store = openTmpStore(true); + this.trees = await MerkleTrees.new(this.store, this.logger); + this.contractInstanceStore = new ContractInstanceStore(this.store); + this.keyStore = new KeyStore(this.store); + this.typedOracle = new TXE( + this.logger, + this.trees, + new PackedValuesCache(), + new ExecutionNoteCache(), + this.contractInstanceStore, + this.keyStore, + this.contractAddress, + ); + await this.timeTravel(toSingle(new Fr(1))); + return toForeignCallResult([]); + } + + setContractAddress(address: ForeignCallSingle) { + const typedAddress = AztecAddress.fromField(fromSingle(address)); + this.contractAddress = typedAddress; + (this.typedOracle as TXE).setContractAddress(typedAddress); + return toForeignCallResult([]); + } + + async deploy( + path: ForeignCallArray, + initializer: ForeignCallArray, + _length: ForeignCallSingle, + args: ForeignCallArray, + ) { + const pathStr = fromArray(path) + .map(char => String.fromCharCode(char.toNumber())) + .join(''); + const initializerStr = fromArray(initializer) + .map(char => String.fromCharCode(char.toNumber())) + .join(''); + const decodedArgs = fromArray(args); + this.logger.debug(`Deploy ${pathStr} with ${initializerStr} and ${decodedArgs}`); + const contractModule = await import(pathStr); + // Hacky way of getting the class, the name of the Artifact is always longer + const contractClass = contractModule[Object.keys(contractModule).sort((a, b) => a.length - b.length)[0]]; + const instance = getContractInstanceFromDeployParams(contractClass.artifact, { + constructorArgs: decodedArgs, + salt: Fr.ONE, + publicKeysHash: Fr.ZERO, + constructorArtifact: initializerStr, + deployer: AztecAddress.ZERO, + }); + this.logger.debug(`Deployed ${contractClass.artifact.name} at ${instance.address}`); + await this.contractInstanceStore.addContractInstance(instance); + return toForeignCallResult([toSingle(instance.address)]); + } + + async directStorageWrite( + contractAddress: ForeignCallSingle, + startStorageSlot: ForeignCallSingle, + values: ForeignCallArray, + ) { + const startStorageSlotFr = fromSingle(startStorageSlot); + const valuesFr = fromArray(values); + const contractAddressFr = fromSingle(contractAddress); + const db = this.trees.asLatest(); + + const publicDataWrites = valuesFr.map((value, i) => { + const storageSlot = startStorageSlotFr.add(new Fr(i)); + this.logger.debug(`Oracle storage write: slot=${storageSlot.toString()} value=${value}`); + return new PublicDataWrite(computePublicDataTreeLeafSlot(contractAddressFr, storageSlot), value); + }); + await db.batchInsert( + MerkleTreeId.PUBLIC_DATA_TREE, + publicDataWrites.map(write => new PublicDataTreeLeaf(write.leafIndex, write.newValue).toBuffer()), + PUBLIC_DATA_SUBTREE_HEIGHT, + ); + return toForeignCallResult([toArray(publicDataWrites.map(write => write.newValue))]); + } + + // PXE oracles + + getRandomField() { + return toForeignCallResult([toSingle(this.typedOracle.getRandomField())]); + } + + getContractAddress() { + return toForeignCallResult([toSingle(this.contractAddress.toField())]); + } + + getBlockNumber() { + return toForeignCallResult([toSingle(new Fr(this.blockNumber))]); + } + + avmOpcodeAddress() { + return toForeignCallResult([toSingle(this.contractAddress.toField())]); + } + + avmOpcodeBlockNumber() { + return toForeignCallResult([toSingle(new Fr(this.blockNumber))]); + } + + async packArgumentsArray(args: ForeignCallArray) { + const packed = await this.typedOracle.packArgumentsArray(fromArray(args)); + return toForeignCallResult([toSingle(packed)]); + } + + async packArguments(_length: ForeignCallSingle, values: ForeignCallArray) { + const packed = await this.typedOracle.packArgumentsArray(fromArray(values)); + return toForeignCallResult([toSingle(packed)]); + } + + // Since the argument is a slice, noir automatically adds a length field to oracle call. + async packReturns(_length: ForeignCallSingle, values: ForeignCallArray) { + const packed = await this.typedOracle.packReturns(fromArray(values)); + return toForeignCallResult([toSingle(packed)]); + } + + async unpackReturns(returnsHash: ForeignCallSingle) { + const unpacked = await this.typedOracle.unpackReturns(fromSingle(returnsHash)); + return toForeignCallResult([toArray(unpacked)]); + } + + // Since the argument is a slice, noir automatically adds a length field to oracle call. + debugLog(message: ForeignCallArray, _length: ForeignCallSingle, fields: ForeignCallArray) { + const messageStr = fromArray(message) + .map(field => String.fromCharCode(field.toNumber())) + .join(''); + const fieldsFr = fromArray(fields); + this.typedOracle.debugLog(messageStr, fieldsFr); + return toForeignCallResult([]); + } + + async storageRead(startStorageSlot: ForeignCallSingle, numberOfElements: ForeignCallSingle) { + const values = await this.typedOracle.storageRead( + fromSingle(startStorageSlot), + fromSingle(numberOfElements).toNumber(), + ); + return toForeignCallResult([toArray(values)]); + } + + async storageWrite(startStorageSlot: ForeignCallSingle, values: ForeignCallArray) { + const newValues = await this.typedOracle.storageWrite(fromSingle(startStorageSlot), fromArray(values)); + return toForeignCallResult([toArray(newValues)]); + } + + async getPublicDataTreeWitness(blockNumber: ForeignCallSingle, leafSlot: ForeignCallSingle) { + const parsedBlockNumber = fromSingle(blockNumber).toNumber(); + const parsedLeafSlot = fromSingle(leafSlot); + + const witness = await this.typedOracle.getPublicDataTreeWitness(parsedBlockNumber, parsedLeafSlot); + if (!witness) { + throw new Error(`Public data witness not found for slot ${parsedLeafSlot} at block ${parsedBlockNumber}.`); + } + return toForeignCallResult([toArray(witness.toFields())]); + } + + async getSiblingPath(blockNumber: ForeignCallSingle, treeId: ForeignCallSingle, leafIndex: ForeignCallSingle) { + const result = await this.typedOracle.getSiblingPath( + fromSingle(blockNumber).toNumber(), + fromSingle(treeId).toNumber(), + fromSingle(leafIndex), + ); + return toForeignCallResult([toArray(result)]); + } + + async getNotes( + storageSlot: ForeignCallSingle, + numSelects: ForeignCallSingle, + selectByIndexes: ForeignCallArray, + selectByOffsets: ForeignCallArray, + selectByLengths: ForeignCallArray, + selectValues: ForeignCallArray, + selectComparators: ForeignCallArray, + sortByIndexes: ForeignCallArray, + sortByOffsets: ForeignCallArray, + sortByLengths: ForeignCallArray, + sortOrder: ForeignCallArray, + limit: ForeignCallSingle, + offset: ForeignCallSingle, + status: ForeignCallSingle, + returnSize: ForeignCallSingle, + ) { + const noteDatas = await this.typedOracle.getNotes( + fromSingle(storageSlot), + fromSingle(numSelects).toNumber(), + fromArray(selectByIndexes).map(fr => fr.toNumber()), + fromArray(selectByOffsets).map(fr => fr.toNumber()), + fromArray(selectByLengths).map(fr => fr.toNumber()), + fromArray(selectValues), + fromArray(selectComparators).map(fr => fr.toNumber()), + fromArray(sortByIndexes).map(fr => fr.toNumber()), + fromArray(sortByOffsets).map(fr => fr.toNumber()), + fromArray(sortByLengths).map(fr => fr.toNumber()), + fromArray(sortOrder).map(fr => fr.toNumber()), + fromSingle(limit).toNumber(), + fromSingle(offset).toNumber(), + fromSingle(status).toNumber(), + ); + const noteLength = noteDatas?.[0]?.note.items.length ?? 0; + if (!noteDatas.every(({ note }) => noteLength === note.items.length)) { + throw new Error('Notes should all be the same length.'); + } + + const contractAddress = noteDatas[0]?.contractAddress ?? Fr.ZERO; + + // Values indicates whether the note is settled or transient. + const noteTypes = { + isSettled: new Fr(0), + isTransient: new Fr(1), + }; + const flattenData = noteDatas.flatMap(({ nonce, note, index }) => [ + nonce, + index === undefined ? noteTypes.isTransient : noteTypes.isSettled, + ...note.items, + ]); + + const returnFieldSize = fromSingle(returnSize).toNumber(); + const returnData = [noteDatas.length, contractAddress, ...flattenData].map(v => new Fr(v)); + if (returnData.length > returnFieldSize) { + throw new Error(`Return data size too big. Maximum ${returnFieldSize} fields. Got ${flattenData.length}.`); + } + + const paddedZeros = Array(returnFieldSize - returnData.length).fill(new Fr(0)); + return toForeignCallResult([toArray([...returnData, ...paddedZeros])]); + } + + notifyCreatedNote( + storageSlot: ForeignCallSingle, + noteTypeId: ForeignCallSingle, + note: ForeignCallArray, + innerNoteHash: ForeignCallSingle, + counter: ForeignCallSingle, + ) { + this.typedOracle.notifyCreatedNote( + fromSingle(storageSlot), + fromSingle(noteTypeId), + fromArray(note), + fromSingle(innerNoteHash), + fromSingle(counter).toNumber(), + ); + return toForeignCallResult([toSingle(new Fr(0))]); + } + + async notifyNullifiedNote( + innerNullifier: ForeignCallSingle, + innerNoteHash: ForeignCallSingle, + counter: ForeignCallSingle, + ) { + await this.typedOracle.notifyNullifiedNote( + fromSingle(innerNullifier), + fromSingle(innerNoteHash), + fromSingle(counter).toNumber(), + ); + return toForeignCallResult([toSingle(new Fr(0))]); + } + + async checkNullifierExists(innerNullifier: ForeignCallSingle) { + const exists = await this.typedOracle.checkNullifierExists(fromSingle(innerNullifier)); + return toForeignCallResult([toSingle(new Fr(exists))]); + } + + async getContractInstance(address: ForeignCallSingle) { + const instance = await this.typedOracle.getContractInstance(fromSingle(address)); + return toForeignCallResult([ + toArray([ + instance.salt, + instance.deployer, + instance.contractClassId, + instance.initializationHash, + instance.publicKeysHash, + ]), + ]); + } + + async getPublicKeysAndPartialAddress(address: ForeignCallSingle) { + const parsedAddress = AztecAddress.fromField(fromSingle(address)); + const { publicKeys, partialAddress } = await this.typedOracle.getCompleteAddress(parsedAddress); + + return toForeignCallResult([toArray([...publicKeys.toFields(), partialAddress])]); + } + + async getKeyValidationRequest(pkMHash: ForeignCallSingle) { + const keyValidationRequest = await this.typedOracle.getKeyValidationRequest(fromSingle(pkMHash)); + return toForeignCallResult([toArray(keyValidationRequest.toFields())]); + } + + computeEncryptedLog( + contractAddress: ForeignCallSingle, + storageSlot: ForeignCallSingle, + noteTypeId: ForeignCallSingle, + ovskApp: ForeignCallSingle, + ovpkMX: ForeignCallSingle, + ovpkMY: ForeignCallSingle, + ivpkMX: ForeignCallSingle, + ivpkMY: ForeignCallSingle, + preimage: ForeignCallArray, + ) { + const ovpkM = new Point(fromSingle(ovpkMX), fromSingle(ovpkMY)); + const ovKeys = new KeyValidationRequest(ovpkM, Fr.fromString(fromSingle(ovskApp).toString())); + const ivpkM = new Point(fromSingle(ivpkMX), fromSingle(ivpkMY)); + const encLog = this.typedOracle.computeEncryptedLog( + AztecAddress.fromString(fromSingle(contractAddress).toString()), + Fr.fromString(fromSingle(storageSlot).toString()), + Fr.fromString(fromSingle(noteTypeId).toString()), + ovKeys, + ivpkM, + fromArray(preimage), + ); + const bytes: Fr[] = []; + + encLog.forEach(v => { + bytes.push(new Fr(v)); + }); + return toForeignCallResult([toArray(bytes)]); + } + + emitEncryptedLog( + _contractAddress: ForeignCallSingle, + _randomandomness: ForeignCallSingle, + _encryptedLog: ForeignCallSingle, + _counter: ForeignCallSingle, + ) { + return toForeignCallResult([]); + } + + emitEncryptedNoteLog( + _noteHashCounter: ForeignCallSingle, + _encryptedNote: ForeignCallArray, + _counter: ForeignCallSingle, + ) { + return toForeignCallResult([]); + } +} diff --git a/yarn-project/txe/src/util/encoding.ts b/yarn-project/txe/src/util/encoding.ts new file mode 100644 index 00000000000..42dc32b88b4 --- /dev/null +++ b/yarn-project/txe/src/util/encoding.ts @@ -0,0 +1,29 @@ +import { Fr } from '@aztec/foundation/fields'; + +export type ForeignCallSingle = { + Single: string; +}; + +export type ForeignCallArray = { + Array: string[]; +}; + +export function fromSingle(obj: ForeignCallSingle) { + return Fr.fromBuffer(Buffer.from(obj.Single, 'hex')); +} + +export function fromArray(obj: ForeignCallArray) { + return obj.Array.map(str => Fr.fromBuffer(Buffer.from(str, 'hex'))); +} + +export function toSingle(obj: Fr) { + return { Single: obj.toString().slice(2) }; +} + +export function toArray(objs: Fr[]) { + return { Array: objs.map(obj => obj.toString()) }; +} + +export function toForeignCallResult(obj: (ForeignCallSingle | ForeignCallArray)[]) { + return { values: obj }; +} diff --git a/yarn-project/txe/tsconfig.json b/yarn-project/txe/tsconfig.json new file mode 100644 index 00000000000..effb5a7151c --- /dev/null +++ b/yarn-project/txe/tsconfig.json @@ -0,0 +1,41 @@ +{ + "extends": "..", + "compilerOptions": { + "outDir": "dest", + "rootDir": "src", + "tsBuildInfoFile": ".tsbuildinfo" + }, + "references": [ + { + "path": "../circuit-types" + }, + { + "path": "../circuits.js" + }, + { + "path": "../foundation" + }, + { + "path": "../noir-protocol-circuits-types" + }, + { + "path": "../protocol-contracts" + }, + { + "path": "../types" + }, + { + "path": "../world-state" + }, + { + "path": "../kv-store" + }, + { + "path": "../merkle-tree" + }, + { + "path": "../noir-contracts.js" + } + ], + "include": ["src"] +} diff --git a/yarn-project/types/src/abi/contract_artifact.ts b/yarn-project/types/src/abi/contract_artifact.ts index 8ca72aa68a7..9fff2b21b6a 100644 --- a/yarn-project/types/src/abi/contract_artifact.ts +++ b/yarn-project/types/src/abi/contract_artifact.ts @@ -2,7 +2,6 @@ import { type ABIParameter, type ABIParameterVisibility, type AbiType, - type BasicValue, type ContractArtifact, type ContractNote, type FieldLayout, @@ -227,10 +226,8 @@ function getStorageLayout(input: NoirCompiledContract) { return storageFields.reduce((acc: Record, field) => { const name = field.name; const slot = field.value.fields[0].value as IntegerValue; - const typ = field.value.fields[1].value as BasicValue<'string', string>; acc[name] = { slot: new Fr(BigInt(slot.value)), - typ: typ.value, }; return acc; }, {}); diff --git a/yarn-project/world-state/src/world-state-db/index.ts b/yarn-project/world-state/src/world-state-db/index.ts index 9d72e0991e8..f4c20a567f9 100644 --- a/yarn-project/world-state/src/world-state-db/index.ts +++ b/yarn-project/world-state/src/world-state-db/index.ts @@ -1,3 +1,5 @@ export * from './merkle_trees.js'; export * from './merkle_tree_db.js'; export * from './merkle_tree_operations.js'; +export * from './merkle_tree_operations_facade.js'; +export * from './merkle_tree_snapshot_operations_facade.js'; diff --git a/yarn-project/yarn.lock b/yarn-project/yarn.lock index d1fe44fd9df..321559b00b0 100644 --- a/yarn-project/yarn.lock +++ b/yarn-project/yarn.lock @@ -933,6 +933,32 @@ __metadata: languageName: unknown linkType: soft +"@aztec/txe@workspace:txe": + version: 0.0.0-use.local + resolution: "@aztec/txe@workspace:txe" + dependencies: + "@aztec/archiver": "workspace:^" + "@aztec/aztec.js": "workspace:^" + "@aztec/circuit-types": "workspace:^" + "@aztec/circuits.js": "workspace:^" + "@aztec/foundation": "workspace:^" + "@aztec/key-store": "workspace:^" + "@aztec/kv-store": "workspace:^" + "@aztec/simulator": "workspace:^" + "@aztec/types": "workspace:^" + "@aztec/world-state": "workspace:^" + "@jest/globals": ^29.5.0 + "@types/jest": ^29.5.0 + "@types/node": ^18.7.23 + jest: ^29.5.0 + jest-mock-extended: ^3.0.3 + ts-node: ^10.9.1 + typescript: ^5.0.4 + bin: + txe: ./dest/bin/index.js + languageName: unknown + linkType: soft + "@aztec/types@workspace:^, @aztec/types@workspace:types": version: 0.0.0-use.local resolution: "@aztec/types@workspace:types" From 6083adea78b753e440041fd121bb55589d30821f Mon Sep 17 00:00:00 2001 From: thunkar Date: Mon, 10 Jun 2024 13:52:50 +0000 Subject: [PATCH 33/75] fixes --- .vscode/settings.json | 8 +++--- noir/noir-repo/aztec_macros/src/lib.rs | 10 ++++---- .../src/transforms/contract_interface.rs | 25 +++++++++++++------ .../aztec_macros/src/transforms/storage.rs | 4 ++- 4 files changed, 30 insertions(+), 17 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index ab8056e8a7c..a8ab844ebe1 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -158,13 +158,15 @@ "avm-transpiler/Cargo.toml" ], "files.watcherExclude": { - "**/.git/objects/**": true, - "**/.git/subtree-cache/**": true, + "**/.git/**": true, "**/node_modules/**": true, "**/.hg/store/**": true, "**/target/**": true, "**/l1-contracts/lib/**": true, - "**/barretenberg/cpp/build*/**": true + "**/barretenberg/cpp/build*/**": true, + "**/barretenberg/cpp/src/wasi-sdk*/**": true, + "**/dest/**": true, + "**/noir/noir-repo/docs/versioned_docs/**": true }, "cmake.sourceDirectory": "${workspaceFolder}/barretenberg/cpp", "noir.nargoPath": "./noir/noir-repo/target/release/nargo" diff --git a/noir/noir-repo/aztec_macros/src/lib.rs b/noir/noir-repo/aztec_macros/src/lib.rs index 217f86c4d9a..46eb95604a3 100644 --- a/noir/noir-repo/aztec_macros/src/lib.rs +++ b/noir/noir-repo/aztec_macros/src/lib.rs @@ -13,7 +13,7 @@ use transforms::{ }, note_interface::{generate_note_interface_impl, inject_note_exports}, storage::{ - assign_storage_slots, check_for_storage_definition, check_for_storage_implementation, + self, assign_storage_slots, check_for_storage_definition, check_for_storage_implementation, generate_storage_implementation, generate_storage_layout, inject_context_in_storage, }, }; @@ -109,9 +109,9 @@ fn transform_module( // Make sure we're only generating the storage layout for the root crate // In case we got a contract importing other contracts for their interface, we // don't want to generate the storage layout for them - if crate_id == context.root_crate_id() { - generate_storage_layout(module, storage_struct_name.clone())?; - } + // if crate_id == context.root_crate_id() { + generate_storage_layout(module, storage_struct_name.clone(), module_name)?; + //} } for structure in module.types.iter_mut() { @@ -219,7 +219,7 @@ fn transform_module( }); } - generate_contract_interface(module, module_name, &stubs)?; + generate_contract_interface(module, module_name, &stubs, storage_defined)?; } Ok(has_transformed_module) diff --git a/noir/noir-repo/aztec_macros/src/transforms/contract_interface.rs b/noir/noir-repo/aztec_macros/src/transforms/contract_interface.rs index c63538e3601..da89cbe608e 100644 --- a/noir/noir-repo/aztec_macros/src/transforms/contract_interface.rs +++ b/noir/noir-repo/aztec_macros/src/transforms/contract_interface.rs @@ -142,7 +142,11 @@ pub fn stub_function( call_args ) } else { - "let args_hash = 0;".to_string() + " + let mut args_acc: [Field] = &[]; + let args_hash = 0; + " + .to_string() }; format!( @@ -206,7 +210,15 @@ pub fn generate_contract_interface( module: &mut SortedModule, module_name: &str, stubs: &[((String, bool), Location)], + has_storage_layout: bool, ) -> Result<(), AztecMacroError> { + let storage_layout_getter = format!( + "#[contract_library_method] + pub fn storage() -> StorageLayout {{ + {}_STORAGE_LAYOUT + }}", + module_name, + ); let contract_interface = format!( " struct {0} {{ @@ -226,9 +238,7 @@ pub fn generate_contract_interface( Self {{ target_contract: dep::aztec::protocol_types::address::AztecAddress::zero() }} }} - pub fn storage() -> StorageLayout {{ - STORAGE_LAYOUT - }} + {2} }} #[contract_library_method] @@ -243,13 +253,12 @@ pub fn generate_contract_interface( {0} {{ target_contract: dep::aztec::protocol_types::address::AztecAddress::zero() }} }} - #[contract_library_method] - pub fn storage() -> StorageLayout {{ - STORAGE_LAYOUT - }} + {3} ", module_name, stubs.iter().map(|((src, _), _)| src.to_owned()).collect::>().join("\n"), + if has_storage_layout { storage_layout_getter.clone() } else { "".to_string() }, + if has_storage_layout { format!("#[contract_library_method]\n{}", storage_layout_getter) } else { "".to_string() } ); let (contract_interface_ast, errors) = parse_program(&contract_interface); diff --git a/noir/noir-repo/aztec_macros/src/transforms/storage.rs b/noir/noir-repo/aztec_macros/src/transforms/storage.rs index a46754d02fa..bac87502c7d 100644 --- a/noir/noir-repo/aztec_macros/src/transforms/storage.rs +++ b/noir/noir-repo/aztec_macros/src/transforms/storage.rs @@ -497,6 +497,7 @@ pub fn assign_storage_slots( pub fn generate_storage_layout( module: &mut SortedModule, storage_struct_name: String, + module_name: &str, ) -> Result<(), AztecMacroError> { let definition = module .types @@ -520,11 +521,12 @@ pub fn generate_storage_layout( }} #[abi(storage)] - global STORAGE_LAYOUT = StorageLayout {{ + global {}_STORAGE_LAYOUT = StorageLayout {{ {} }}; ", storable_fields.join(",\n"), + module_name, storable_fields_impl.join(",\n") ); From b80f6c7fc0a0f9217db73f8d978fc1fc1d954d3f Mon Sep 17 00:00:00 2001 From: thunkar Date: Mon, 10 Jun 2024 15:21:32 +0000 Subject: [PATCH 34/75] fixes --- .vscode/settings.json | 1 - .../aztec/src/state_vars/private_immutable.nr | 13 +++---- .../aztec/src/state_vars/public_immutable.nr | 37 ++++++++++--------- .../shared_mutable/shared_mutable.nr | 15 +++++--- .../contracts/test_contract/src/main.nr | 6 +-- noir/noir-repo/aztec_macros/src/lib.rs | 21 ++--------- .../src/transforms/contract_interface.rs | 37 ++++++++----------- 7 files changed, 55 insertions(+), 75 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index a8ab844ebe1..86a0427f790 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -169,5 +169,4 @@ "**/noir/noir-repo/docs/versioned_docs/**": true }, "cmake.sourceDirectory": "${workspaceFolder}/barretenberg/cpp", - "noir.nargoPath": "./noir/noir-repo/target/release/nargo" } diff --git a/noir-projects/aztec-nr/aztec/src/state_vars/private_immutable.nr b/noir-projects/aztec-nr/aztec/src/state_vars/private_immutable.nr index 6e1d4f6ccd4..967df14e52f 100644 --- a/noir-projects/aztec-nr/aztec/src/state_vars/private_immutable.nr +++ b/noir-projects/aztec-nr/aztec/src/state_vars/private_immutable.nr @@ -50,8 +50,7 @@ impl PrivateImmutable { ovpk_m: GrumpkinPoint, ivpk_m: GrumpkinPoint ) where Note: NoteInterface { - // Nullify the storage slot - since we always push the same nullifier, this can only be done once, as further - // attempts to do it will result in the transaction being reverted by the sequencer. + // Nullify the storage slot. let nullifier = self.compute_initialization_nullifier(); self.context.push_new_nullifier(nullifier, 0); @@ -61,10 +60,8 @@ impl PrivateImmutable { // docs:start:get_note pub fn get_note(self) -> Note where Note: NoteInterface { - // We do not need to check for initialization because we'll only be able to read a note and prove its inclusion - // if one was actually created, and this can be done during initialization. Therefore, a successful read implies - // initialization. - get_note(self.context, self.storage_slot) + let storage_slot = self.storage_slot; + get_note(self.context, storage_slot) } // docs:end:get_note } @@ -80,8 +77,8 @@ impl PrivateImmutable { // view_note does not actually use the context, but it calls oracles that are only available in private // docs:start:view_note unconstrained pub fn view_note(self) -> Note where Note: NoteInterface { - let options = NoteViewerOptions::new(); - view_notes(self.storage_slot, options)[0].unwrap() + let mut options = NoteViewerOptions::new(); + view_notes(self.storage_slot, options.set_limit(1))[0].unwrap() } // docs:end:view_note } diff --git a/noir-projects/aztec-nr/aztec/src/state_vars/public_immutable.nr b/noir-projects/aztec-nr/aztec/src/state_vars/public_immutable.nr index f6cb509f432..b8f2c1d2dde 100644 --- a/noir-projects/aztec-nr/aztec/src/state_vars/public_immutable.nr +++ b/noir-projects/aztec-nr/aztec/src/state_vars/public_immutable.nr @@ -4,8 +4,6 @@ use crate::{ }; use dep::protocol_types::{constants::INITIALIZATION_SLOT_SEPARATOR, traits::{Deserialize, Serialize}}; -mod test; - // Just like SharedImmutable but without the ability to read from private functions. // docs:start:public_immutable_struct struct PublicImmutable { @@ -18,42 +16,45 @@ impl Storage for PublicImmutable {} impl PublicImmutable { // docs:start:public_immutable_struct_new - pub fn new(context: Context, storage_slot: Field) -> Self { + pub fn new( + // Note: Passing the contexts to new(...) just to have an interface compatible with a Map. + context: Context, + storage_slot: Field + ) -> Self { assert(storage_slot != 0, "Storage slot 0 not allowed. Storage slots must start from 1."); PublicImmutable { context, storage_slot } } // docs:end:public_immutable_struct_new - - fn get_initialization_slot(self) -> Field { - INITIALIZATION_SLOT_SEPARATOR + self.storage_slot - } } impl PublicImmutable { // docs:start:public_immutable_struct_write pub fn initialize(self, value: T) where T: Serialize { - assert(!self.is_initialized(), "PublicImmutable already initialized"); + // TODO(#4738): Uncomment the following assert + // assert( + // self.context.public.unwrap_unchecked().is_deployment(), "PublicImmutable can only be initialized during contract deployment" + // ); + + // We check that the struct is not yet initialized by checking if the initialization slot is 0 + let initialization_slot = INITIALIZATION_SLOT_SEPARATOR + self.storage_slot; + let fields_read: [Field; 1] = storage_read(initialization_slot); + assert(fields_read[0] == 0, "PublicImmutable already initialized"); // We populate the initialization slot with a non-zero value to indicate that the struct is initialized - public_storage::write(self.get_initialization_slot(), 0xdead); + storage_write(initialization_slot, [0xdead]); - // And we then store the actual value - public_storage::write(self.storage_slot, value); + let fields_write = T::serialize(value); + storage_write(self.storage_slot, fields_write); } // docs:end:public_immutable_struct_write // Note that we don't access the context, but we do call oracles that are only available in public // docs:start:public_immutable_struct_read pub fn read(self) -> T where T: Deserialize { - assert(self.is_initialized(), "PublicImmutable not initialized"); - public_storage::read(self.storage_slot) + let fields = storage_read(self.storage_slot); + T::deserialize(fields) } // docs:end:public_immutable_struct_read - - pub fn is_initialized(self) -> bool { - let init_slot_contents: Field = public_storage::read(self.get_initialization_slot()); - init_slot_contents != 0 - } } impl PublicImmutable { diff --git a/noir-projects/aztec-nr/aztec/src/state_vars/shared_mutable/shared_mutable.nr b/noir-projects/aztec-nr/aztec/src/state_vars/shared_mutable/shared_mutable.nr index 38f36ae41db..91c864a03b2 100644 --- a/noir-projects/aztec-nr/aztec/src/state_vars/shared_mutable/shared_mutable.nr +++ b/noir-projects/aztec-nr/aztec/src/state_vars/shared_mutable/shared_mutable.nr @@ -121,7 +121,7 @@ impl SharedMutable { // will only be valid for however many blocks we can ensure the value will not change, which will depend on the // current delay and any scheduled delay changes. - let (value_change, delay_change, historical_block_number) = self.historical_read_from_public_storage(); + let (value_change, delay_change, historical_block_number) = self.historical_read_from_public_storage(*self.context); // We use the effective minimum delay as opposed to the current delay at the historical block as this one also // takes into consideration any scheduled delay changes. @@ -139,26 +139,29 @@ impl SharedMutable { value_change.get_current_at(historical_block_number) } - fn historical_read_from_public_storage(self) -> (ScheduledValueChange, ScheduledDelayChange, u32) where T: FromField { - let header = self.context.get_header(); + fn historical_read_from_public_storage( + self, + context: PrivateContext + ) -> (ScheduledValueChange, ScheduledDelayChange, u32) where T: FromField { + let header = context.get_header(); // Ideally the following would be simply public_storage::read_historical, but we can't implement that yet. let value_change_slot = self.get_value_change_storage_slot(); let mut raw_value_change_fields = [0; 3]; for i in 0..3 { raw_value_change_fields[i] = header.public_storage_historical_read( value_change_slot + i as Field, - self.context.this_address() + context.this_address() ); } // Ideally the following would be simply public_storage::read_historical, but we can't implement that yet. let delay_change_slot = self.get_delay_change_storage_slot(); - let raw_delay_change_fields = [header.public_storage_historical_read(delay_change_slot, self.context.this_address())]; + let raw_delay_change_fields = [header.public_storage_historical_read(delay_change_slot, context.this_address())]; let value_change = ScheduledValueChange::deserialize(raw_value_change_fields); let delay_change = ScheduledDelayChange::deserialize(raw_delay_change_fields); - let historical_block_number = self.context.historical_header.global_variables.block_number as u32; + let historical_block_number = context.historical_header.global_variables.block_number as u32; (value_change, delay_change, historical_block_number) } diff --git a/noir-projects/noir-contracts/contracts/test_contract/src/main.nr b/noir-projects/noir-contracts/contracts/test_contract/src/main.nr index d1329c33dd0..5f45f2b1a75 100644 --- a/noir-projects/noir-contracts/contracts/test_contract/src/main.nr +++ b/noir-projects/noir-contracts/contracts/test_contract/src/main.nr @@ -506,12 +506,12 @@ contract Test { // This function is used in the e2e_state_vars to test the SharedMutablePrivateGetter in isolation #[aztec(private)] - fn test_shared_mutable_private_getter( + fn test_shared_mutable_private_getter( contract_address_to_read: AztecAddress, storage_slot_of_shared_mutable: Field - ) -> Field where T: FromField, T: ToField { + ) -> Field { // It's a bit wonky because we need to know the delay for get_current_value_in_private to work correctly - let test: SharedMutablePrivateGetter = SharedMutablePrivateGetter::new( + let test: SharedMutablePrivateGetter = SharedMutablePrivateGetter::new( &mut context, contract_address_to_read, storage_slot_of_shared_mutable diff --git a/noir/noir-repo/aztec_macros/src/lib.rs b/noir/noir-repo/aztec_macros/src/lib.rs index 46eb95604a3..2f4244981a4 100644 --- a/noir/noir-repo/aztec_macros/src/lib.rs +++ b/noir/noir-repo/aztec_macros/src/lib.rs @@ -13,7 +13,7 @@ use transforms::{ }, note_interface::{generate_note_interface_impl, inject_note_exports}, storage::{ - self, assign_storage_slots, check_for_storage_definition, check_for_storage_implementation, + assign_storage_slots, check_for_storage_definition, check_for_storage_implementation, generate_storage_implementation, generate_storage_layout, inject_context_in_storage, }, }; @@ -65,14 +65,8 @@ fn transform( // Usage -> mut ast -> aztec_library::transform(&mut ast) // Covers all functions in the ast for submodule in ast.submodules.iter_mut().filter(|submodule| submodule.is_contract) { - if transform_module( - crate_id, - &file_id, - context, - &mut submodule.contents, - submodule.name.0.contents.as_str(), - ) - .map_err(|err| (err.into(), file_id))? + if transform_module(&file_id, &mut submodule.contents, submodule.name.0.contents.as_str()) + .map_err(|err| (err.into(), file_id))? { check_for_aztec_dependency(crate_id, context)?; } @@ -87,9 +81,7 @@ fn transform( /// For annotated functions it calls the `transform` function which will perform the required transformations. /// Returns true if an annotated node is found, false otherwise fn transform_module( - crate_id: &CrateId, file_id: &FileId, - context: &HirContext, module: &mut SortedModule, module_name: &str, ) -> Result { @@ -106,12 +98,7 @@ fn transform_module( if !check_for_storage_implementation(module, storage_struct_name) { generate_storage_implementation(module, storage_struct_name)?; } - // Make sure we're only generating the storage layout for the root crate - // In case we got a contract importing other contracts for their interface, we - // don't want to generate the storage layout for them - // if crate_id == context.root_crate_id() { generate_storage_layout(module, storage_struct_name.clone(), module_name)?; - //} } for structure in module.types.iter_mut() { @@ -160,7 +147,7 @@ fn transform_module( // Apply transformations to the function based on collected attributes if is_private || is_public { let fn_type = if is_private { "Private" } else { "Public" }; - let stub_src = stub_function(fn_type, func, is_static, is_initializer); + let stub_src = stub_function(fn_type, func, is_static); stubs.push((stub_src, Location { file: *file_id, span: func.name_ident().span() })); export_fn_abi(&mut module.types, func)?; diff --git a/noir/noir-repo/aztec_macros/src/transforms/contract_interface.rs b/noir/noir-repo/aztec_macros/src/transforms/contract_interface.rs index da89cbe608e..5e3daced44d 100644 --- a/noir/noir-repo/aztec_macros/src/transforms/contract_interface.rs +++ b/noir/noir-repo/aztec_macros/src/transforms/contract_interface.rs @@ -40,12 +40,7 @@ use crate::utils::{ // } // // The selector placeholder has to be replaced with the actual function signature after type checking in the next macro pass -pub fn stub_function( - aztec_visibility: &str, - func: &NoirFunction, - is_static_call: bool, - is_initializer: bool, -) -> (String, bool) { +pub fn stub_function(aztec_visibility: &str, func: &NoirFunction, is_static_call: bool) -> String { let fn_name = func.name().to_string(); let fn_parameters = func .parameters() @@ -93,7 +88,7 @@ pub fn stub_function( .collect::>() .join(""); - let param_types = if parameters.len() > 0 { + let param_types = if !parameters.is_empty() { parameters .iter() .map(|param| param.pattern.name_ident().0.contents.clone()) @@ -185,21 +180,19 @@ pub fn stub_function( args, fn_selector, aztec_visibility, is_static, is_void, fn_name, original ) }; - ( - format!( - "pub fn {}(self, {}) -> dep::aztec::context::{}{}{}CallInterface<{},{} {{ + + format!( + "pub fn {}(self, {}) -> dep::aztec::context::{}{}{}CallInterface<{},{} {{ {} }}", - fn_name, - fn_parameters, - aztec_visibility, - is_static, - is_void, - fn_name.len(), - generics, - fn_body - ), - is_initializer, + fn_name, + fn_parameters, + aztec_visibility, + is_static, + is_void, + fn_name.len(), + generics, + fn_body ) } @@ -209,7 +202,7 @@ pub fn stub_function( pub fn generate_contract_interface( module: &mut SortedModule, module_name: &str, - stubs: &[((String, bool), Location)], + stubs: &[(String, Location)], has_storage_layout: bool, ) -> Result<(), AztecMacroError> { let storage_layout_getter = format!( @@ -256,7 +249,7 @@ pub fn generate_contract_interface( {3} ", module_name, - stubs.iter().map(|((src, _), _)| src.to_owned()).collect::>().join("\n"), + stubs.iter().map(|(src, _)| src.to_owned()).collect::>().join("\n"), if has_storage_layout { storage_layout_getter.clone() } else { "".to_string() }, if has_storage_layout { format!("#[contract_library_method]\n{}", storage_layout_getter) } else { "".to_string() } ); From ca42523b5807cdce1e463f651a6b8107772e6b7d Mon Sep 17 00:00:00 2001 From: thunkar Date: Tue, 11 Jun 2024 08:38:29 +0000 Subject: [PATCH 35/75] account abstraction --- .../aztec/src/context/private_context.nr | 2 +- .../aztec-nr/aztec/src/keys/getters.nr | 2 +- .../aztec/src/test/helpers/cheatcodes.nr | 19 ++-- .../src/test/helpers/test_environment.nr | 43 +++---- .../aztec-nr/aztec/src/test/helpers/types.nr | 56 +++++++-- .../contracts/counter_contract/src/main.nr | 4 +- yarn-project/txe/src/oracle/txe_oracle.ts | 50 +++++++- .../txe/src/txe_service/txe_service.ts | 107 +++++++++++------- yarn-project/txe/src/util/account_store.ts | 19 ++++ 9 files changed, 205 insertions(+), 97 deletions(-) create mode 100644 yarn-project/txe/src/util/account_store.ts diff --git a/noir-projects/aztec-nr/aztec/src/context/private_context.nr b/noir-projects/aztec-nr/aztec/src/context/private_context.nr index e0f31144ed2..aeba73e0554 100644 --- a/noir-projects/aztec-nr/aztec/src/context/private_context.nr +++ b/noir-projects/aztec-nr/aztec/src/context/private_context.nr @@ -237,7 +237,7 @@ impl PrivateContext { let request_and_generator = KeyValidationRequestAndGenerator { request, sk_app_generator: sk_generators[key_index] }; // We constrain that the pk_m_hash matches the one in the request (otherwise we could get an arbitrary // valid key request and not the one corresponding to pk_m_hash). - //assert(request.pk_m.hash() == pk_m_hash); + assert(request.pk_m.hash() == pk_m_hash); self.key_validation_requests_and_generators.push(request_and_generator); self.last_key_validation_requests[key_index] = Option::some(request); request.sk_app diff --git a/noir-projects/aztec-nr/aztec/src/keys/getters.nr b/noir-projects/aztec-nr/aztec/src/keys/getters.nr index 8a2c7c440fb..8e56174ac83 100644 --- a/noir-projects/aztec-nr/aztec/src/keys/getters.nr +++ b/noir-projects/aztec-nr/aztec/src/keys/getters.nr @@ -95,7 +95,7 @@ fn fetch_and_constrain_keys(address: AztecAddress) -> PublicKeys { let computed_address = AztecAddress::compute(public_keys.hash(), partial_address); - //assert(computed_address.eq(address)); + assert(computed_address.eq(address)); public_keys } diff --git a/noir-projects/aztec-nr/aztec/src/test/helpers/cheatcodes.nr b/noir-projects/aztec-nr/aztec/src/test/helpers/cheatcodes.nr index 033e2823574..648acba82e5 100644 --- a/noir-projects/aztec-nr/aztec/src/test/helpers/cheatcodes.nr +++ b/noir-projects/aztec-nr/aztec/src/test/helpers/cheatcodes.nr @@ -1,6 +1,6 @@ use dep::protocol_types::address::AztecAddress; use crate::context::inputs::{PublicContextInputs, PrivateContextInputs}; -use crate::test::helpers::types::Deployer; +use crate::test::helpers::types::{Deployer, TestAccount}; unconstrained pub fn reset() { oracle_reset(); @@ -30,18 +30,10 @@ unconstrained pub fn get_public_context_inputs() -> PublicContextInputs { oracle_get_public_context_inputs() } -unconstrained pub fn deploy_inner( - path: str, - initializer: str, - args: [Field] -) -> AztecAddress { +unconstrained pub fn deploy(path: str, initializer: str, args: [Field]) -> AztecAddress { oracle_deploy(path, initializer, args) } -pub fn deploy(path: str) -> Deployer { - Deployer { path } -} - unconstrained pub fn direct_storage_write( contract_address: AztecAddress, storage_slot: Field, @@ -50,6 +42,10 @@ unconstrained pub fn direct_storage_write( let _hash = direct_storage_write_oracle(contract_address, storage_slot, fields); } +unconstrained pub fn add_account(secret: Field) -> TestAccount { + oracle_add_account(secret) +} + #[oracle(reset)] fn oracle_reset() {} @@ -80,3 +76,6 @@ fn direct_storage_write_oracle( _storage_slot: Field, _values: [Field; N] ) -> [Field; N] {} + +#[oracle(addAccount)] +fn oracle_add_account(secret: Field) -> TestAccount {} diff --git a/noir-projects/aztec-nr/aztec/src/test/helpers/test_environment.nr b/noir-projects/aztec-nr/aztec/src/test/helpers/test_environment.nr index 5dd18c6871c..39ba83a9cd0 100644 --- a/noir-projects/aztec-nr/aztec/src/test/helpers/test_environment.nr +++ b/noir-projects/aztec-nr/aztec/src/test/helpers/test_environment.nr @@ -4,9 +4,11 @@ use dep::protocol_types::{ grumpkin_point::GrumpkinPoint }; +use crate::oracle::unsafe_rand::unsafe_rand; + use crate::context::{PrivateContext, PublicContext, PrivateVoidCallInterface}; -use crate::test::helpers::{cheatcodes, types::Deployer}; -use crate::keys::{public_keys::PublicKeys, constants::{NULLIFIER_INDEX, INCOMING_INDEX, OUTGOING_INDEX, TAGGING_INDEX}}; +use crate::test::helpers::{cheatcodes, types::{Deployer, TestAccount}}; +use crate::keys::constants::{NULLIFIER_INDEX, INCOMING_INDEX, OUTGOING_INDEX, TAGGING_INDEX}; struct TestEnvironment { contract_address: Option, @@ -85,7 +87,7 @@ impl TestEnvironment { PrivateContext::new(inputs, args_hash) } - fn store_master_key(self, key_index: Field, address: AztecAddress) -> GrumpkinPoint { + fn store_master_key(self, key_index: Field, address: AztecAddress, key: GrumpkinPoint) { let x_coordinate_map_slot = key_index * 2 + 1; let y_coordinate_map_slot = x_coordinate_map_slot + 1; let x_coordinate_derived_slot = derive_storage_slot_in_map(x_coordinate_map_slot, address); @@ -93,34 +95,25 @@ impl TestEnvironment { let canonical_registry_address = AztecAddress::from_field(CANONICAL_KEY_REGISTRY_ADDRESS); - let fake_key = GrumpkinPoint { x: 1, y: 2 }; - - cheatcodes::direct_storage_write( - canonical_registry_address, - x_coordinate_derived_slot, - [fake_key.x] - ); + cheatcodes::direct_storage_write(canonical_registry_address, x_coordinate_derived_slot, [key.x]); - cheatcodes::direct_storage_write( - canonical_registry_address, - y_coordinate_derived_slot, - [fake_key.y] - ); - fake_key + cheatcodes::direct_storage_write(canonical_registry_address, y_coordinate_derived_slot, [key.y]); } - fn get_address_with_keys(self, address: AztecAddress) -> AztecAddress { - let keys = PublicKeys { - npk_m: self.store_master_key(NULLIFIER_INDEX, address), - ivpk_m: self.store_master_key(INCOMING_INDEX, address), - ovpk_m: self.store_master_key(OUTGOING_INDEX, address), - tpk_m: self.store_master_key(TAGGING_INDEX, address) - }; + fn get_account(self) -> AztecAddress { + let test_account = cheatcodes::add_account(unsafe_rand()); + let address = test_account.address; + let keys = test_account.keys; + + self.store_master_key(NULLIFIER_INDEX, address, keys.npk_m); + self.store_master_key(INCOMING_INDEX, address, keys.ivpk_m); + self.store_master_key(OUTGOING_INDEX, address, keys.ovpk_m); + self.store_master_key(TAGGING_INDEX, address, keys.tpk_m); - AztecAddress::compute(keys.hash(), PartialAddress::from_field(1)) + test_account.address } fn deploy(self, path: str) -> Deployer { - cheatcodes::deploy(path) + Deployer { path } } } diff --git a/noir-projects/aztec-nr/aztec/src/test/helpers/types.nr b/noir-projects/aztec-nr/aztec/src/test/helpers/types.nr index e4b43538c34..3ce27bdade0 100644 --- a/noir-projects/aztec-nr/aztec/src/test/helpers/types.nr +++ b/noir-projects/aztec-nr/aztec/src/test/helpers/types.nr @@ -1,24 +1,28 @@ -use dep::protocol_types::address::AztecAddress; +use dep::protocol_types::{traits::{Deserialize, Serialize}, address::AztecAddress}; use crate::context::inputs::{PublicContextInputs, PrivateContextInputs}; use crate::context::call_interfaces::CallInterface; -use crate::test::helpers::cheatcodes::{get_private_context_inputs, get_public_context_inputs, deploy_inner, advance_blocks, get_block_number}; +use crate::test::helpers::cheatcodes; +use crate::keys::public_keys::{PUBLIC_KEYS_LENGTH, PublicKeys}; struct Deployer { path: str, } impl Deployer { - pub fn with_private_initializer(self, call_interface: C) -> AztecAddress where C: CallInterface { - let address = deploy_inner( + pub fn with_private_initializer( + self, + call_interface: C + ) -> AztecAddress where C: CallInterface { + let address = cheatcodes::deploy( self.path, call_interface.get_name(), call_interface.get_args() ); - advance_blocks(1); - let block_number = get_block_number(); + cheatcodes::advance_blocks(1); + let block_number = cheatcodes::get_block_number(); let original_fn = call_interface.get_original(); - let mut inputs = get_private_context_inputs(block_number - 1); + let mut inputs = cheatcodes::get_private_context_inputs(block_number - 1); inputs.call_context.storage_contract_address = address; inputs.call_context.function_selector = call_interface.get_selector(); let _result = original_fn(inputs); @@ -29,14 +33,48 @@ impl Deployer { self, call_interface: C ) -> AztecAddress where C: CallInterface { - let address = deploy_inner( + let address = cheatcodes::deploy( self.path, call_interface.get_name(), call_interface.get_args() ); let original_fn = call_interface.get_original(); - let mut inputs = get_public_context_inputs(); + let mut inputs = cheatcodes::get_public_context_inputs(); let _result = original_fn(inputs); address } } + +// Keys length + address +global TEST_ACCOUNT_LENGTH = PUBLIC_KEYS_LENGTH + 1; + +struct TestAccount { + address: AztecAddress, + keys: PublicKeys +} + +impl Serialize for TestAccount { + fn serialize(self) -> [Field; TEST_ACCOUNT_LENGTH] { + let mut output = [0; TEST_ACCOUNT_LENGTH]; + + output[0] = self.address.to_field(); + + for i in 0..PUBLIC_KEYS_LENGTH { + output[i+1] = self.keys.serialize()[i]; + } + output + } +} + +impl Deserialize for TestAccount { + fn deserialize(input: [Field; TEST_ACCOUNT_LENGTH]) -> Self { + let address = AztecAddress::from_field(input[0]); + let mut key_buffer = [0; PUBLIC_KEYS_LENGTH]; + for i in 0..PUBLIC_KEYS_LENGTH { + key_buffer[i] = input[i+1]; + } + let keys = PublicKeys::deserialize(key_buffer); + + Self { address, keys } + } +} diff --git a/noir-projects/noir-contracts/contracts/counter_contract/src/main.nr b/noir-projects/noir-contracts/contracts/counter_contract/src/main.nr index c3c8cc6981f..3ae2f399d77 100644 --- a/noir-projects/noir-contracts/contracts/counter_contract/src/main.nr +++ b/noir-projects/noir-contracts/contracts/counter_contract/src/main.nr @@ -48,8 +48,8 @@ contract Counter { fn test_initialize() { // Setup env, generate keys let mut env = TestEnvironment::new(); - let owner = env.get_address_with_keys(AztecAddress::from_field(13)); - let outgoing_viewer = env.get_address_with_keys(AztecAddress::from_field(14)); + let owner = env.get_account(); + let outgoing_viewer = env.get_account(); // Deploy contract and initialize let initializer = Counter::interface().initialize(5, owner, outgoing_viewer); diff --git a/yarn-project/txe/src/oracle/txe_oracle.ts b/yarn-project/txe/src/oracle/txe_oracle.ts index 59ae24e756d..8891b2c2718 100644 --- a/yarn-project/txe/src/oracle/txe_oracle.ts +++ b/yarn-project/txe/src/oracle/txe_oracle.ts @@ -11,7 +11,7 @@ import { type UnencryptedL2Log, } from '@aztec/circuit-types'; import { - CompleteAddress, + type CompleteAddress, type Header, KeyValidationRequest, NULLIFIER_SUBTREE_HEIGHT, @@ -28,7 +28,7 @@ import { type FunctionSelector } from '@aztec/foundation/abi'; import { AztecAddress } from '@aztec/foundation/aztec-address'; import { Fr, GrumpkinScalar, type Point } from '@aztec/foundation/fields'; import { type Logger, applyStringFormatting } from '@aztec/foundation/log'; -import { KeyStore } from '@aztec/key-store'; +import { type KeyStore } from '@aztec/key-store'; import { type ExecutionNoteCache, type MessageLoadOracleInputs, @@ -37,10 +37,12 @@ import { type TypedOracle, pickNotes, } from '@aztec/simulator'; -import { type ContractInstance } from '@aztec/types/contracts'; +import { type ContractInstance, type ContractInstanceWithAddress } from '@aztec/types/contracts'; import { MerkleTreeSnapshotOperationsFacade, type MerkleTrees } from '@aztec/world-state'; export class TXE implements TypedOracle { + private blockNumber = 0; + constructor( private logger: Logger, private trees: MerkleTrees, @@ -48,13 +50,50 @@ export class TXE implements TypedOracle { private noteCache: ExecutionNoteCache, private contractInstanceStore: ContractInstanceStore, private keyStore: KeyStore, + private accountStore: any, private contractAddress: AztecAddress, ) {} + // Utils + setContractAddress(contractAddress: AztecAddress) { this.contractAddress = contractAddress; } + setBlockNumber(blockNumber: number) { + this.blockNumber = blockNumber; + } + + getTrees() { + return this.trees; + } + + getContractInstanceStore() { + return this.contractInstanceStore; + } + + getKeyStore() { + return this.keyStore; + } + + getAccountStore() { + return this.accountStore; + } + + async addContractInstance(contractInstance: ContractInstanceWithAddress) { + await this.contractInstanceStore.addContractInstance(contractInstance); + } + + // TypedOracle + + getBlockNumber(): Promise { + return Promise.resolve(this.blockNumber); + } + + getContractAddress(): Promise { + return Promise.resolve(this.contractAddress); + } + getRandomField() { return Fr.random(); } @@ -72,8 +111,7 @@ export class TXE implements TypedOracle { } getKeyValidationRequest(pkMHash: Fr): Promise { - //return this.keyStore.getKeyValidationRequest(pkMHash, this.contractAddress); - return Promise.resolve(KeyValidationRequest.empty()); + return this.keyStore.getKeyValidationRequest(pkMHash, this.contractAddress); } getContractInstance(address: AztecAddress): Promise { @@ -128,7 +166,7 @@ export class TXE implements TypedOracle { } getCompleteAddress(account: AztecAddress): Promise { - return Promise.resolve(CompleteAddress.fromSecretKeyAndPartialAddress(Fr.ONE, account)); + return Promise.resolve(this.accountStore.getAccount(account)); } getAuthWitness(_messageHash: Fr): Promise { diff --git a/yarn-project/txe/src/txe_service/txe_service.ts b/yarn-project/txe/src/txe_service/txe_service.ts index 7da9912a06d..ea47b17044c 100644 --- a/yarn-project/txe/src/txe_service/txe_service.ts +++ b/yarn-project/txe/src/txe_service/txe_service.ts @@ -20,6 +20,7 @@ import { ExecutionNoteCache, PackedValuesCache, type TypedOracle } from '@aztec/ import { MerkleTrees } from '@aztec/world-state'; import { TXE } from '../oracle/txe_oracle.js'; +import { AccountStore } from '../util/account_store.js'; import { type ForeignCallArray, type ForeignCallSingle, @@ -31,17 +32,7 @@ import { } from '../util/encoding.js'; export class TXEService { - private blockNumber = 0; - - constructor( - private logger: Logger, - private typedOracle: TypedOracle, - private store: AztecKVStore, - private trees: MerkleTrees, - private contractInstanceStore: ContractInstanceStore, - private keyStore: KeyStore, - private contractAddress: AztecAddress, - ) {} + constructor(private logger: Logger, private typedOracle: TypedOracle, private store: AztecKVStore) {} static async init(logger: Logger, contractAddress = AztecAddress.random()) { const store = openTmpStore(true); @@ -50,9 +41,19 @@ export class TXEService { const noteCache = new ExecutionNoteCache(); const contractInstanceStore = new ContractInstanceStore(store); const keyStore = new KeyStore(store); + const accountStore = new AccountStore(store); logger.info(`TXE service initialized`); - const txe = new TXE(logger, trees, packedValuesCache, noteCache, contractInstanceStore, keyStore, contractAddress); - const service = new TXEService(logger, txe, store, trees, contractInstanceStore, keyStore, contractAddress); + const txe = new TXE( + logger, + trees, + packedValuesCache, + noteCache, + contractInstanceStore, + keyStore, + accountStore, + contractAddress, + ); + const service = new TXEService(logger, txe, store); await service.timeTravel(toSingle(new Fr(1n))); return service; } @@ -61,56 +62,60 @@ export class TXEService { async getPrivateContextInputs(blockNumber: ForeignCallSingle) { const inputs = PrivateContextInputs.empty(); - const stateReference = await this.trees.getStateReference(true); + const trees = (this.typedOracle as TXE).getTrees(); + const stateReference = await trees.getStateReference(true); inputs.historicalHeader.globalVariables.blockNumber = fromSingle(blockNumber); inputs.historicalHeader.state = stateReference; inputs.callContext.msgSender = AztecAddress.random(); - inputs.callContext.storageContractAddress = this.contractAddress; + inputs.callContext.storageContractAddress = await this.typedOracle.getContractAddress(); return toForeignCallResult(inputs.toFields().map(toSingle)); } async timeTravel(blocks: ForeignCallSingle) { const nBlocks = fromSingle(blocks).toNumber(); this.logger.info(`time traveling ${nBlocks} blocks`); + const trees = (this.typedOracle as TXE).getTrees(); + const blockNumber = await this.typedOracle.getBlockNumber(); for (let i = 0; i < nBlocks; i++) { const header = Header.empty(); const l2Block = L2Block.empty(); - header.state = await this.trees.getStateReference(true); - header.globalVariables.blockNumber = new Fr(this.blockNumber); + header.state = await trees.getStateReference(true); + header.globalVariables.blockNumber = new Fr(blockNumber); header.state.partial.nullifierTree.root = Fr.fromBuffer( - (await this.trees.getTreeInfo(MerkleTreeId.NULLIFIER_TREE, true)).root, + (await trees.getTreeInfo(MerkleTreeId.NULLIFIER_TREE, true)).root, ); header.state.partial.noteHashTree.root = Fr.fromBuffer( - (await this.trees.getTreeInfo(MerkleTreeId.NOTE_HASH_TREE, true)).root, + (await trees.getTreeInfo(MerkleTreeId.NOTE_HASH_TREE, true)).root, ); header.state.partial.publicDataTree.root = Fr.fromBuffer( - (await this.trees.getTreeInfo(MerkleTreeId.PUBLIC_DATA_TREE, true)).root, + (await trees.getTreeInfo(MerkleTreeId.PUBLIC_DATA_TREE, true)).root, ); header.state.l1ToL2MessageTree.root = Fr.fromBuffer( - (await this.trees.getTreeInfo(MerkleTreeId.L1_TO_L2_MESSAGE_TREE, true)).root, + (await trees.getTreeInfo(MerkleTreeId.L1_TO_L2_MESSAGE_TREE, true)).root, ); - l2Block.archive.root = Fr.fromBuffer((await this.trees.getTreeInfo(MerkleTreeId.ARCHIVE, true)).root); + l2Block.archive.root = Fr.fromBuffer((await trees.getTreeInfo(MerkleTreeId.ARCHIVE, true)).root); l2Block.header = header; - await this.trees.handleL2BlockAndMessages(l2Block, []); - this.blockNumber++; + await trees.handleL2BlockAndMessages(l2Block, []); + (this.typedOracle as TXE).setBlockNumber(blockNumber + 1); } return toForeignCallResult([]); } async reset() { - this.blockNumber = 0; this.store = openTmpStore(true); - this.trees = await MerkleTrees.new(this.store, this.logger); - this.contractInstanceStore = new ContractInstanceStore(this.store); - this.keyStore = new KeyStore(this.store); + const trees = await MerkleTrees.new(this.store, this.logger); + const contractInstanceStore = new ContractInstanceStore(this.store); + const keyStore = new KeyStore(this.store); + const accountStore = new AccountStore(this.store); this.typedOracle = new TXE( this.logger, - this.trees, + trees, new PackedValuesCache(), new ExecutionNoteCache(), - this.contractInstanceStore, - this.keyStore, - this.contractAddress, + contractInstanceStore, + keyStore, + accountStore, + AztecAddress.random(), ); await this.timeTravel(toSingle(new Fr(1))); return toForeignCallResult([]); @@ -118,7 +123,6 @@ export class TXEService { setContractAddress(address: ForeignCallSingle) { const typedAddress = AztecAddress.fromField(fromSingle(address)); - this.contractAddress = typedAddress; (this.typedOracle as TXE).setContractAddress(typedAddress); return toForeignCallResult([]); } @@ -148,7 +152,7 @@ export class TXEService { deployer: AztecAddress.ZERO, }); this.logger.debug(`Deployed ${contractClass.artifact.name} at ${instance.address}`); - await this.contractInstanceStore.addContractInstance(instance); + await (this.typedOracle as TXE).addContractInstance(instance); return toForeignCallResult([toSingle(instance.address)]); } @@ -157,10 +161,11 @@ export class TXEService { startStorageSlot: ForeignCallSingle, values: ForeignCallArray, ) { + const trees = (this.typedOracle as TXE).getTrees(); const startStorageSlotFr = fromSingle(startStorageSlot); const valuesFr = fromArray(values); const contractAddressFr = fromSingle(contractAddress); - const db = this.trees.asLatest(); + const db = trees.asLatest(); const publicDataWrites = valuesFr.map((value, i) => { const storageSlot = startStorageSlotFr.add(new Fr(i)); @@ -175,26 +180,42 @@ export class TXEService { return toForeignCallResult([toArray(publicDataWrites.map(write => write.newValue))]); } + async addAccount(secret: ForeignCallSingle) { + const secretKey = fromSingle(secret); + const keyStore = (this.typedOracle as TXE).getKeyStore(); + const completeAddress = await keyStore.addAccount(secretKey, Fr.ONE); + const accountStore = (this.typedOracle as TXE).getAccountStore(); + await accountStore.setAccount(completeAddress.address, completeAddress); + return toForeignCallResult([ + toSingle(completeAddress.address), + ...completeAddress.publicKeys.toFields().map(toSingle), + ]); + } + // PXE oracles getRandomField() { return toForeignCallResult([toSingle(this.typedOracle.getRandomField())]); } - getContractAddress() { - return toForeignCallResult([toSingle(this.contractAddress.toField())]); + async getContractAddress() { + const contractAddress = await this.typedOracle.getContractAddress(); + return toForeignCallResult([toSingle(contractAddress.toField())]); } - getBlockNumber() { - return toForeignCallResult([toSingle(new Fr(this.blockNumber))]); + async getBlockNumber() { + const blockNumber = await this.typedOracle.getBlockNumber(); + return toForeignCallResult([toSingle(new Fr(blockNumber))]); } - avmOpcodeAddress() { - return toForeignCallResult([toSingle(this.contractAddress.toField())]); + async avmOpcodeAddress() { + const contractAddress = await this.typedOracle.getContractAddress(); + return toForeignCallResult([toSingle(contractAddress.toField())]); } - avmOpcodeBlockNumber() { - return toForeignCallResult([toSingle(new Fr(this.blockNumber))]); + async avmOpcodeBlockNumber() { + const blockNumber = await this.typedOracle.getBlockNumber(); + return toForeignCallResult([toSingle(new Fr(blockNumber))]); } async packArgumentsArray(args: ForeignCallArray) { diff --git a/yarn-project/txe/src/util/account_store.ts b/yarn-project/txe/src/util/account_store.ts new file mode 100644 index 00000000000..f35589812dc --- /dev/null +++ b/yarn-project/txe/src/util/account_store.ts @@ -0,0 +1,19 @@ +import { type AztecAddress, CompleteAddress } from '@aztec/circuits.js'; +import { type AztecKVStore, type AztecMap } from '@aztec/kv-store'; + +export class AccountStore { + #accounts: AztecMap; + + constructor(database: AztecKVStore) { + this.#accounts = database.openMap('accounts'); + } + + getAccount(key: AztecAddress) { + const completeAddress = this.#accounts.get(key.toString()); + return CompleteAddress.fromBuffer(completeAddress!); + } + + async setAccount(key: AztecAddress, value: CompleteAddress) { + await this.#accounts.set(key.toString(), value.toBuffer()); + } +} From 38fb77e681d420bef33f049a06a9538a98a239aa Mon Sep 17 00:00:00 2001 From: thunkar Date: Tue, 11 Jun 2024 08:55:33 +0000 Subject: [PATCH 36/75] reverted parallel changes since we have session ids now --- .../compiler/noirc_frontend/src/monomorphization/mod.rs | 3 --- noir/noir-repo/tooling/nargo_cli/src/cli/test_cmd.rs | 4 ++-- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/noir/noir-repo/compiler/noirc_frontend/src/monomorphization/mod.rs b/noir/noir-repo/compiler/noirc_frontend/src/monomorphization/mod.rs index 239f644faff..451a884d59c 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/monomorphization/mod.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/monomorphization/mod.rs @@ -246,12 +246,9 @@ impl<'interner> Monomorphizer<'interner> { } } FunctionKind::Recursive => { - // let func = self.interner.function_meta(&id); - // println!("{:#?}", func.name); let id = self.queue_function(id, expr_id, typ, turbofish_generics, trait_method); Definition::Function(id) - //unreachable!("wtf"); } } } diff --git a/noir/noir-repo/tooling/nargo_cli/src/cli/test_cmd.rs b/noir/noir-repo/tooling/nargo_cli/src/cli/test_cmd.rs index 838642b587d..99c284e5019 100644 --- a/noir/noir-repo/tooling/nargo_cli/src/cli/test_cmd.rs +++ b/noir/noir-repo/tooling/nargo_cli/src/cli/test_cmd.rs @@ -82,7 +82,7 @@ pub(crate) fn run(args: TestCommand, config: NargoConfig) -> Result<(), CliError let test_reports: Vec> = workspace .into_iter() - //.par_bridge() + .par_bridge() .map(|package| { run_tests::( &workspace_file_manager, @@ -137,7 +137,7 @@ fn run_tests + Default>( println!("[{}] Running {count_all} test function{plural}", package.name); let test_report: Vec<(String, TestStatus)> = test_functions - .into_iter() + .into_par_iter() .map(|test_name| { let status = run_test::( file_manager, From 85b7decab5b81080b64da7ea2dcc129fe84cbe86 Mon Sep 17 00:00:00 2001 From: thunkar Date: Tue, 11 Jun 2024 09:40:52 +0000 Subject: [PATCH 37/75] updated tsconfig --- yarn-project/txe/tsconfig.json | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/yarn-project/txe/tsconfig.json b/yarn-project/txe/tsconfig.json index effb5a7151c..1db338e8540 100644 --- a/yarn-project/txe/tsconfig.json +++ b/yarn-project/txe/tsconfig.json @@ -6,32 +6,41 @@ "tsBuildInfoFile": ".tsbuildinfo" }, "references": [ + { + "path": "../bb-prover" + }, + { + "path": "../builder" + }, { "path": "../circuit-types" }, { "path": "../circuits.js" }, + { + "path": "../ethereum" + }, { "path": "../foundation" }, { - "path": "../noir-protocol-circuits-types" + "path": "../key-store" }, { - "path": "../protocol-contracts" + "path": "../kv-store" }, { - "path": "../types" + "path": "../noir-protocol-circuits-types" }, { - "path": "../world-state" + "path": "../protocol-contracts" }, { - "path": "../kv-store" + "path": "../simulator" }, { - "path": "../merkle-tree" + "path": "../types" }, { "path": "../noir-contracts.js" From 907e26d49c685e0e9f3cfe8a345f40f01bedd338 Mon Sep 17 00:00:00 2001 From: thunkar Date: Wed, 12 Jun 2024 10:36:33 +0000 Subject: [PATCH 38/75] external calls --- .../src/state_vars/shared_mutable/test.nr | 170 ++++++++-------- .../aztec-nr/aztec/src/test/helpers.nr | 1 + .../aztec/src/test/helpers/cheatcodes.nr | 15 +- .../aztec-nr/aztec/src/test/helpers/keys.nr | 18 ++ .../src/test/helpers/test_environment.nr | 46 ++--- .../aztec-nr/aztec/src/test/helpers/types.nr | 18 +- .../contracts/child_contract/src/main.nr | 2 +- .../contracts/counter_contract/src/main.nr | 5 +- .../contracts/parent_contract/Nargo.toml | 2 + .../contracts/parent_contract/src/main.nr | 33 +++ yarn-project/pxe/src/database/index.ts | 1 + yarn-project/pxe/src/index.ts | 2 + yarn-project/txe/package.json | 1 + yarn-project/txe/src/oracle/txe_oracle.ts | 190 +++++++++++++++--- .../txe/src/txe_service/txe_service.ts | 77 ++++--- .../{account_store.ts => txe_database.ts} | 13 +- yarn-project/yarn.lock | 1 + 17 files changed, 411 insertions(+), 184 deletions(-) create mode 100644 noir-projects/aztec-nr/aztec/src/test/helpers/keys.nr rename yarn-project/txe/src/util/{account_store.ts => txe_database.ts} (55%) diff --git a/noir-projects/aztec-nr/aztec/src/state_vars/shared_mutable/test.nr b/noir-projects/aztec-nr/aztec/src/state_vars/shared_mutable/test.nr index 42f432a5534..b193640a54b 100644 --- a/noir-projects/aztec-nr/aztec/src/state_vars/shared_mutable/test.nr +++ b/noir-projects/aztec-nr/aztec/src/state_vars/shared_mutable/test.nr @@ -29,124 +29,124 @@ fn in_private( SharedMutable::new(&mut env.private_at(historical_block_number), storage_slot) } -#[test] -fn test_get_current_value_in_public_initial() { - let env = setup(); - let state_var = in_public(env); +// #[test] +// fn test_get_current_value_in_public_initial() { +// let env = setup(); +// let state_var = in_public(env); - // 0 is the default empty value for a Field - assert_eq(state_var.get_current_value_in_public(), 0); -} +// // 0 is the default empty value for a Field +// assert_eq(state_var.get_current_value_in_public(), 0); +// } -#[test] -fn test_get_current_value_in_public_before_scheduled_change() { - let mut env = setup(); - let state_var = in_public(env); +// #[test] +// fn test_get_current_value_in_public_before_scheduled_change() { +// let mut env = setup(); +// let state_var = in_public(env); - state_var.schedule_value_change(new_value); +// state_var.schedule_value_change(new_value); - let (_, block_of_change) = state_var.get_scheduled_value_in_public(); +// let (_, block_of_change) = state_var.get_scheduled_value_in_public(); - let original_value = 0; +// let original_value = 0; - // The current value has not changed - assert_eq(state_var.get_current_value_in_public(), original_value); +// // The current value has not changed +// assert_eq(state_var.get_current_value_in_public(), original_value); - // The current value still does not change right before the block of change - env.advance_block_to(block_of_change - 1); - assert_eq(state_var.get_current_value_in_public(), original_value); -} +// // The current value still does not change right before the block of change +// env.advance_block_to(block_of_change - 1); +// assert_eq(state_var.get_current_value_in_public(), original_value); +// } -#[test] -fn test_get_current_value_in_public_at_scheduled_change() { - let mut env = setup(); - let state_var = in_public(env); +// #[test] +// fn test_get_current_value_in_public_at_scheduled_change() { +// let mut env = setup(); +// let state_var = in_public(env); - state_var.schedule_value_change(new_value); +// state_var.schedule_value_change(new_value); - let (_, block_of_change) = state_var.get_scheduled_value_in_public(); +// let (_, block_of_change) = state_var.get_scheduled_value_in_public(); - env.advance_block_to(block_of_change); - assert_eq(state_var.get_current_value_in_public(), new_value); -} +// env.advance_block_to(block_of_change); +// assert_eq(state_var.get_current_value_in_public(), new_value); +// } -#[test] -fn test_get_current_value_in_public_after_scheduled_change() { - let mut env = setup(); - let state_var = in_public(env); +// #[test] +// fn test_get_current_value_in_public_after_scheduled_change() { +// let mut env = setup(); +// let state_var = in_public(env); - state_var.schedule_value_change(new_value); +// state_var.schedule_value_change(new_value); - let (_, block_of_change) = state_var.get_scheduled_value_in_public(); +// let (_, block_of_change) = state_var.get_scheduled_value_in_public(); - env.advance_block_to(block_of_change + 10); - assert_eq(state_var.get_current_value_in_public(), new_value); -} +// env.advance_block_to(block_of_change + 10); +// assert_eq(state_var.get_current_value_in_public(), new_value); +// } -#[test] -fn test_get_current_value_in_private_before_change() { - let mut env = setup(); +// #[test] +// fn test_get_current_value_in_private_before_change() { +// let mut env = setup(); - let public_state_var = in_public(env); - public_state_var.schedule_value_change(new_value); +// let public_state_var = in_public(env); +// public_state_var.schedule_value_change(new_value); - let (_, block_of_change) = public_state_var.get_scheduled_value_in_public(); +// let (_, block_of_change) = public_state_var.get_scheduled_value_in_public(); - let schedule_block_number = env.block_number(); +// let schedule_block_number = env.block_number(); - let private_state_var = in_private(&mut env, schedule_block_number); - assert_eq(private_state_var.get_current_value_in_private(), 0); - assert_eq(private_state_var.context.max_block_number.unwrap(), block_of_change - 1); -} +// let private_state_var = in_private(&mut env, schedule_block_number); +// assert_eq(private_state_var.get_current_value_in_private(), 0); +// assert_eq(private_state_var.context.max_block_number.unwrap(), block_of_change - 1); +// } -#[test] -fn test_get_current_value_in_private_immediately_before_change() { - let mut env = setup(); +// #[test] +// fn test_get_current_value_in_private_immediately_before_change() { +// let mut env = setup(); - let public_state_var = in_public(env); - public_state_var.schedule_value_change(new_value); +// let public_state_var = in_public(env); +// public_state_var.schedule_value_change(new_value); - let (_, block_of_change) = public_state_var.get_scheduled_value_in_public(); +// let (_, block_of_change) = public_state_var.get_scheduled_value_in_public(); - let private_state_var = in_private(&mut env, block_of_change - 1); +// let private_state_var = in_private(&mut env, block_of_change - 1); - assert_eq(private_state_var.get_current_value_in_private(), 0); - assert_eq(private_state_var.context.max_block_number.unwrap(), block_of_change - 1); -} +// assert_eq(private_state_var.get_current_value_in_private(), 0); +// assert_eq(private_state_var.context.max_block_number.unwrap(), block_of_change - 1); +// } -#[test] -fn test_get_current_value_in_private_at_change() { - let mut env = setup(); +// #[test] +// fn test_get_current_value_in_private_at_change() { +// let mut env = setup(); - let public_state_var = in_public(env); - public_state_var.schedule_value_change(new_value); +// let public_state_var = in_public(env); +// public_state_var.schedule_value_change(new_value); - let (_, block_of_change) = public_state_var.get_scheduled_value_in_public(); +// let (_, block_of_change) = public_state_var.get_scheduled_value_in_public(); - let historical_block_number = block_of_change; - let private_state_var = in_private(&mut env, historical_block_number); - assert_eq(private_state_var.get_current_value_in_private(), new_value); - assert_eq( - private_state_var.context.max_block_number.unwrap(), historical_block_number + TEST_INITIAL_DELAY - ); -} +// let historical_block_number = block_of_change; +// let private_state_var = in_private(&mut env, historical_block_number); +// assert_eq(private_state_var.get_current_value_in_private(), new_value); +// assert_eq( +// private_state_var.context.max_block_number.unwrap(), historical_block_number + TEST_INITIAL_DELAY +// ); +// } -#[test] -fn test_get_current_value_in_private_after_change() { - let mut env = setup(); +// #[test] +// fn test_get_current_value_in_private_after_change() { +// let mut env = setup(); - let public_state_var = in_public(env); - public_state_var.schedule_value_change(new_value); +// let public_state_var = in_public(env); +// public_state_var.schedule_value_change(new_value); - let (_, block_of_change) = public_state_var.get_scheduled_value_in_public(); +// let (_, block_of_change) = public_state_var.get_scheduled_value_in_public(); - let historical_block_number = block_of_change + 10; - let private_state_var = in_private(&mut env, historical_block_number); - assert_eq(private_state_var.get_current_value_in_private(), new_value); - assert_eq( - private_state_var.context.max_block_number.unwrap(), historical_block_number + TEST_INITIAL_DELAY - ); -} +// let historical_block_number = block_of_change + 10; +// let private_state_var = in_private(&mut env, historical_block_number); +// assert_eq(private_state_var.get_current_value_in_private(), new_value); +// assert_eq( +// private_state_var.context.max_block_number.unwrap(), historical_block_number + TEST_INITIAL_DELAY +// ); +// } // #[test] // fn test_get_current_delay_in_public() { diff --git a/noir-projects/aztec-nr/aztec/src/test/helpers.nr b/noir-projects/aztec-nr/aztec/src/test/helpers.nr index 80045b39a64..b28a85add1c 100644 --- a/noir-projects/aztec-nr/aztec/src/test/helpers.nr +++ b/noir-projects/aztec-nr/aztec/src/test/helpers.nr @@ -1,3 +1,4 @@ mod test_environment; mod cheatcodes; mod types; +mod keys; diff --git a/noir-projects/aztec-nr/aztec/src/test/helpers/cheatcodes.nr b/noir-projects/aztec-nr/aztec/src/test/helpers/cheatcodes.nr index 648acba82e5..44c22ef574a 100644 --- a/noir-projects/aztec-nr/aztec/src/test/helpers/cheatcodes.nr +++ b/noir-projects/aztec-nr/aztec/src/test/helpers/cheatcodes.nr @@ -1,4 +1,4 @@ -use dep::protocol_types::address::AztecAddress; +use dep::protocol_types::address::{AztecAddress, PartialAddress}; use crate::context::inputs::{PublicContextInputs, PrivateContextInputs}; use crate::test::helpers::types::{Deployer, TestAccount}; @@ -42,8 +42,12 @@ unconstrained pub fn direct_storage_write( let _hash = direct_storage_write_oracle(contract_address, storage_slot, fields); } -unconstrained pub fn add_account(secret: Field) -> TestAccount { - oracle_add_account(secret) +unconstrained pub fn create_account() -> TestAccount { + oracle_create_account() +} + +unconstrained pub fn add_account(secret: Field, partial_address: PartialAddress) -> TestAccount { + oracle_add_account(secret, partial_address) } #[oracle(reset)] @@ -77,5 +81,8 @@ fn direct_storage_write_oracle( _values: [Field; N] ) -> [Field; N] {} +#[oracle(createAccount)] +fn oracle_create_account() -> TestAccount {} + #[oracle(addAccount)] -fn oracle_add_account(secret: Field) -> TestAccount {} +fn oracle_add_account(secret: Field, partial_address: PartialAddress) -> TestAccount {} diff --git a/noir-projects/aztec-nr/aztec/src/test/helpers/keys.nr b/noir-projects/aztec-nr/aztec/src/test/helpers/keys.nr new file mode 100644 index 00000000000..f4c53c95c20 --- /dev/null +++ b/noir-projects/aztec-nr/aztec/src/test/helpers/keys.nr @@ -0,0 +1,18 @@ +use dep::protocol_types::{ + address::AztecAddress, storage::map::derive_storage_slot_in_map, + constants::CANONICAL_KEY_REGISTRY_ADDRESS, grumpkin_point::GrumpkinPoint +}; + +use crate::test::helpers::cheatcodes; + +pub fn store_master_key(key_index: Field, address: AztecAddress, key: GrumpkinPoint) { + let x_coordinate_map_slot = key_index * 2 + 1; + let y_coordinate_map_slot = x_coordinate_map_slot + 1; + let x_coordinate_derived_slot = derive_storage_slot_in_map(x_coordinate_map_slot, address); + let y_coordinate_derived_slot = derive_storage_slot_in_map(y_coordinate_map_slot, address); + + let canonical_registry_address = AztecAddress::from_field(CANONICAL_KEY_REGISTRY_ADDRESS); + + cheatcodes::direct_storage_write(canonical_registry_address, x_coordinate_derived_slot, [key.x]); + cheatcodes::direct_storage_write(canonical_registry_address, y_coordinate_derived_slot, [key.y]); +} diff --git a/noir-projects/aztec-nr/aztec/src/test/helpers/test_environment.nr b/noir-projects/aztec-nr/aztec/src/test/helpers/test_environment.nr index 39ba83a9cd0..4a837a918df 100644 --- a/noir-projects/aztec-nr/aztec/src/test/helpers/test_environment.nr +++ b/noir-projects/aztec-nr/aztec/src/test/helpers/test_environment.nr @@ -1,13 +1,14 @@ use dep::protocol_types::{ - abis::function_selector::FunctionSelector, address::{AztecAddress, PartialAddress}, - storage::map::derive_storage_slot_in_map, constants::CANONICAL_KEY_REGISTRY_ADDRESS, - grumpkin_point::GrumpkinPoint + abis::{function_selector::FunctionSelector, private_circuit_public_inputs::PrivateCircuitPublicInputs}, + address::{AztecAddress, PartialAddress}, storage::map::derive_storage_slot_in_map, + constants::CANONICAL_KEY_REGISTRY_ADDRESS, grumpkin_point::GrumpkinPoint, traits::Deserialize }; -use crate::oracle::unsafe_rand::unsafe_rand; +use crate::context::inputs::{PublicContextInputs, PrivateContextInputs}; +use crate::context::{packed_returns::PackedReturns, call_interfaces::CallInterface}; use crate::context::{PrivateContext, PublicContext, PrivateVoidCallInterface}; -use crate::test::helpers::{cheatcodes, types::{Deployer, TestAccount}}; +use crate::test::helpers::{cheatcodes, types::{Deployer, TestAccount}, keys}; use crate::keys::constants::{NULLIFIER_INDEX, INCOMING_INDEX, OUTGOING_INDEX, TAGGING_INDEX}; struct TestEnvironment { @@ -87,28 +88,15 @@ impl TestEnvironment { PrivateContext::new(inputs, args_hash) } - fn store_master_key(self, key_index: Field, address: AztecAddress, key: GrumpkinPoint) { - let x_coordinate_map_slot = key_index * 2 + 1; - let y_coordinate_map_slot = x_coordinate_map_slot + 1; - let x_coordinate_derived_slot = derive_storage_slot_in_map(x_coordinate_map_slot, address); - let y_coordinate_derived_slot = derive_storage_slot_in_map(y_coordinate_map_slot, address); - - let canonical_registry_address = AztecAddress::from_field(CANONICAL_KEY_REGISTRY_ADDRESS); - - cheatcodes::direct_storage_write(canonical_registry_address, x_coordinate_derived_slot, [key.x]); - - cheatcodes::direct_storage_write(canonical_registry_address, y_coordinate_derived_slot, [key.y]); - } - - fn get_account(self) -> AztecAddress { - let test_account = cheatcodes::add_account(unsafe_rand()); + fn create_account(self) -> AztecAddress { + let test_account = cheatcodes::create_account(); let address = test_account.address; let keys = test_account.keys; - self.store_master_key(NULLIFIER_INDEX, address, keys.npk_m); - self.store_master_key(INCOMING_INDEX, address, keys.ivpk_m); - self.store_master_key(OUTGOING_INDEX, address, keys.ovpk_m); - self.store_master_key(TAGGING_INDEX, address, keys.tpk_m); + keys::store_master_key(NULLIFIER_INDEX, address, keys.npk_m); + keys::store_master_key(INCOMING_INDEX, address, keys.ivpk_m); + keys::store_master_key(OUTGOING_INDEX, address, keys.ovpk_m); + keys::store_master_key(TAGGING_INDEX, address, keys.tpk_m); test_account.address } @@ -116,4 +104,14 @@ impl TestEnvironment { fn deploy(self, path: str) -> Deployer { Deployer { path } } + + fn call_private( + self, + call_interface: C + ) -> T where C: CallInterface, T: Deserialize { + let original_fn = call_interface.get_original(); + let mut inputs = cheatcodes::get_private_context_inputs(cheatcodes::get_block_number() - 1); + let public_inputs = original_fn(inputs); + PackedReturns::new(public_inputs.returns_hash).unpack_into() + } } diff --git a/noir-projects/aztec-nr/aztec/src/test/helpers/types.nr b/noir-projects/aztec-nr/aztec/src/test/helpers/types.nr index 3ce27bdade0..9ce08939e8d 100644 --- a/noir-projects/aztec-nr/aztec/src/test/helpers/types.nr +++ b/noir-projects/aztec-nr/aztec/src/test/helpers/types.nr @@ -1,4 +1,7 @@ -use dep::protocol_types::{traits::{Deserialize, Serialize}, address::AztecAddress}; +use dep::protocol_types::{ + traits::{Deserialize, Serialize}, address::AztecAddress, + abis::{function_selector::FunctionSelector, private_circuit_public_inputs::PrivateCircuitPublicInputs} +}; use crate::context::inputs::{PublicContextInputs, PrivateContextInputs}; use crate::context::call_interfaces::CallInterface; @@ -10,10 +13,10 @@ struct Deployer { } impl Deployer { - pub fn with_private_initializer( + pub fn with_private_initializer( self, call_interface: C - ) -> AztecAddress where C: CallInterface { + ) -> AztecAddress where C: CallInterface { let address = cheatcodes::deploy( self.path, call_interface.get_name(), @@ -29,10 +32,10 @@ impl Deployer { address } - pub fn with_public_initializer( + pub fn with_public_initializer( self, call_interface: C - ) -> AztecAddress where C: CallInterface { + ) -> AztecAddress where C: CallInterface { let address = cheatcodes::deploy( self.path, call_interface.get_name(), @@ -43,6 +46,11 @@ impl Deployer { let _result = original_fn(inputs); address } + + pub fn without_initializer(self) -> AztecAddress { + let address = cheatcodes::deploy(self.path, "", &[]); + address + } } // Keys length + address diff --git a/noir-projects/noir-contracts/contracts/child_contract/src/main.nr b/noir-projects/noir-contracts/contracts/child_contract/src/main.nr index 8542d7a0ffd..35ad6206362 100644 --- a/noir-projects/noir-contracts/contracts/child_contract/src/main.nr +++ b/noir-projects/noir-contracts/contracts/child_contract/src/main.nr @@ -53,7 +53,7 @@ contract Child { fn private_set_value(new_value: Field, owner: AztecAddress) -> Field { let header = context.get_header(); let owner_npk_m_hash = header.get_npk_m_hash(&mut context, owner); - let msg_sender_ovpk_m = header.get_ovpk_m(&mut context, context.msg_sender()); + let msg_sender_ovpk_m = header.get_ovpk_m(&mut context, owner); let owner_ivpk_m = header.get_ivpk_m(&mut context, owner); let mut note = ValueNote::new(new_value, owner_npk_m_hash); diff --git a/noir-projects/noir-contracts/contracts/counter_contract/src/main.nr b/noir-projects/noir-contracts/contracts/counter_contract/src/main.nr index 3ae2f399d77..dc20ba272db 100644 --- a/noir-projects/noir-contracts/contracts/counter_contract/src/main.nr +++ b/noir-projects/noir-contracts/contracts/counter_contract/src/main.nr @@ -40,7 +40,6 @@ contract Counter { use dep::aztec::test::{helpers::{cheatcodes, test_environment::TestEnvironment}}; use dep::aztec::protocol_types::storage::map::derive_storage_slot_in_map; - use dep::aztec::oracle::{storage::{storage_read, storage_write}}; use dep::aztec::note::note_getter::{MAX_NOTES_PER_PAGE, view_notes}; use dep::aztec::note::note_viewer_options::NoteViewerOptions; @@ -48,8 +47,8 @@ contract Counter { fn test_initialize() { // Setup env, generate keys let mut env = TestEnvironment::new(); - let owner = env.get_account(); - let outgoing_viewer = env.get_account(); + let owner = env.create_account(); + let outgoing_viewer = env.create_account(); // Deploy contract and initialize let initializer = Counter::interface().initialize(5, owner, outgoing_viewer); diff --git a/noir-projects/noir-contracts/contracts/parent_contract/Nargo.toml b/noir-projects/noir-contracts/contracts/parent_contract/Nargo.toml index 53a4d614564..a91d3bd758c 100644 --- a/noir-projects/noir-contracts/contracts/parent_contract/Nargo.toml +++ b/noir-projects/noir-contracts/contracts/parent_contract/Nargo.toml @@ -6,3 +6,5 @@ type = "contract" [dependencies] aztec = { path = "../../../aztec-nr/aztec" } +child_contract = { path = "../child_contract" } +value_note = { path = "../../../aztec-nr/value-note" } diff --git a/noir-projects/noir-contracts/contracts/parent_contract/src/main.nr b/noir-projects/noir-contracts/contracts/parent_contract/src/main.nr index fb066de5ad6..d2163b7f8ce 100644 --- a/noir-projects/noir-contracts/contracts/parent_contract/src/main.nr +++ b/noir-projects/noir-contracts/contracts/parent_contract/src/main.nr @@ -242,4 +242,37 @@ contract Parent { // Call the target private function context.static_call_public_function(target_contract, target_selector, args); } + + use dep::aztec::test::{helpers::{cheatcodes, test_environment::TestEnvironment}}; + use dep::aztec::protocol_types::storage::map::derive_storage_slot_in_map; + use dep::aztec::oracle::{storage::{storage_read, storage_write}}; + use dep::aztec::note::note_getter::{MAX_NOTES_PER_PAGE, view_notes}; + use dep::aztec::note::note_viewer_options::NoteViewerOptions; + use dep::child_contract::Child; + use dep::value_note::value_note::ValueNote; + + #[test] + fn test_private_call() { + // Setup env, generate keys + let mut env = TestEnvironment::new(); + let owner = env.create_account(); + + // Deploy child contract + let child_contract_address = env.deploy("@aztec/noir-contracts.js/Child").without_initializer(); + cheatcodes::advance_blocks(1); + // Set value in child through parent + let call_interface = Parent::interface().private_call( + child_contract_address, + FunctionSelector::from_signature("private_set_value(Field,(Field))"), + [7, owner.to_field()] + ); + let result: Field = env.call_private(call_interface); + // Read the stored value in the note + //cheatcodes::set_contract_address(child_contract_address); + let counter_slot = Child::storage().a_map_with_private_values.slot; + let owner_slot = derive_storage_slot_in_map(counter_slot, owner); + let mut options = NoteViewerOptions::new(); + let opt_notes: [Option; MAX_NOTES_PER_PAGE] = view_notes(owner_slot, options); + assert(opt_notes[0].unwrap().value == result); + } } diff --git a/yarn-project/pxe/src/database/index.ts b/yarn-project/pxe/src/database/index.ts index 4685cc2f7a8..e01c1803241 100644 --- a/yarn-project/pxe/src/database/index.ts +++ b/yarn-project/pxe/src/database/index.ts @@ -1 +1,2 @@ export * from './pxe_database.js'; +export * from './kv_pxe_database.js'; diff --git a/yarn-project/pxe/src/index.ts b/yarn-project/pxe/src/index.ts index d7cf6d57253..7c62b24d3ff 100644 --- a/yarn-project/pxe/src/index.ts +++ b/yarn-project/pxe/src/index.ts @@ -9,3 +9,5 @@ export * from '@aztec/foundation/fields'; export * from '@aztec/foundation/eth-address'; export * from '@aztec/foundation/aztec-address'; export * from '@aztec/key-store'; +export * from './database/index.js'; +export { ContractDataOracle } from './contract_data_oracle/index.js'; diff --git a/yarn-project/txe/package.json b/yarn-project/txe/package.json index 87159321240..c5548db696d 100644 --- a/yarn-project/txe/package.json +++ b/yarn-project/txe/package.json @@ -55,6 +55,7 @@ "@aztec/foundation": "workspace:^", "@aztec/key-store": "workspace:^", "@aztec/kv-store": "workspace:^", + "@aztec/pxe": "workspace:^", "@aztec/simulator": "workspace:^", "@aztec/types": "workspace:^", "@aztec/world-state": "workspace:^" diff --git a/yarn-project/txe/src/oracle/txe_oracle.ts b/yarn-project/txe/src/oracle/txe_oracle.ts index 8891b2c2718..cd15873be1e 100644 --- a/yarn-project/txe/src/oracle/txe_oracle.ts +++ b/yarn-project/txe/src/oracle/txe_oracle.ts @@ -1,4 +1,3 @@ -import { type ContractInstanceStore } from '@aztec/archiver'; import { L1NotePayload, MerkleTreeId, @@ -7,55 +6,87 @@ import { type NullifierMembershipWitness, PublicDataWitness, PublicDataWrite, - TaggedNote, + TaggedLog, type UnencryptedL2Log, } from '@aztec/circuit-types'; +import { type CircuitWitnessGenerationStats } from '@aztec/circuit-types/stats'; import { type CompleteAddress, + FunctionData, type Header, - KeyValidationRequest, + type KeyValidationRequest, NULLIFIER_SUBTREE_HEIGHT, PUBLIC_DATA_SUBTREE_HEIGHT, type PUBLIC_DATA_TREE_HEIGHT, - type PrivateCallStackItem, + PrivateCallStackItem, + PrivateCircuitPublicInputs, + PrivateContextInputs, type PublicCallRequest, PublicDataTreeLeaf, type PublicDataTreeLeafPreimage, + computeContractClassId, + getContractClassFromArtifact, } from '@aztec/circuits.js'; import { Aes128 } from '@aztec/circuits.js/barretenberg'; import { computePublicDataTreeLeafSlot, siloNoteHash, siloNullifier } from '@aztec/circuits.js/hash'; -import { type FunctionSelector } from '@aztec/foundation/abi'; +import { + type ContractArtifact, + type FunctionAbi, + type FunctionSelector, + countArgumentsSize, +} from '@aztec/foundation/abi'; import { AztecAddress } from '@aztec/foundation/aztec-address'; import { Fr, GrumpkinScalar, type Point } from '@aztec/foundation/fields'; import { type Logger, applyStringFormatting } from '@aztec/foundation/log'; +import { Timer } from '@aztec/foundation/timer'; import { type KeyStore } from '@aztec/key-store'; +import { ContractDataOracle } from '@aztec/pxe'; import { + ExecutionError, type ExecutionNoteCache, type MessageLoadOracleInputs, type NoteData, + Oracle, type PackedValuesCache, type TypedOracle, + acvm, + extractCallStack, pickNotes, + toACVMWitness, + witnessMapToFields, } from '@aztec/simulator'; import { type ContractInstance, type ContractInstanceWithAddress } from '@aztec/types/contracts'; import { MerkleTreeSnapshotOperationsFacade, type MerkleTrees } from '@aztec/world-state'; +import { type TXEDatabase } from '../util/txe_database.js'; + export class TXE implements TypedOracle { private blockNumber = 0; + private sideEffectCounter = 0; + private contractDataOracle: ContractDataOracle; constructor( private logger: Logger, private trees: MerkleTrees, private packedValuesCache: PackedValuesCache, private noteCache: ExecutionNoteCache, - private contractInstanceStore: ContractInstanceStore, private keyStore: KeyStore, - private accountStore: any, + private txeDatabase: TXEDatabase, private contractAddress: AztecAddress, - ) {} + ) { + this.contractDataOracle = new ContractDataOracle(txeDatabase); + } // Utils + getSideEffectCounter() { + return this.sideEffectCounter; + } + + setSideEffectCounter(sideEffectCounter: number) { + this.sideEffectCounter = sideEffectCounter; + } + setContractAddress(contractAddress: AztecAddress) { this.contractAddress = contractAddress; } @@ -68,20 +99,37 @@ export class TXE implements TypedOracle { return this.trees; } - getContractInstanceStore() { - return this.contractInstanceStore; + getTXEDatabase() { + return this.txeDatabase; } getKeyStore() { return this.keyStore; } - getAccountStore() { - return this.accountStore; + async addContractInstance(contractInstance: ContractInstanceWithAddress) { + await this.txeDatabase.addContractInstance(contractInstance); } - async addContractInstance(contractInstance: ContractInstanceWithAddress) { - await this.contractInstanceStore.addContractInstance(contractInstance); + async addContractArtifact(artifact: ContractArtifact) { + const contractClass = getContractClassFromArtifact(artifact); + await this.txeDatabase.addContractArtifact(computeContractClassId(contractClass), artifact); + } + + async getPrivateContextInputs( + blockNumber: number, + msgSender = AztecAddress.random(), + contractAddress = this.contractAddress, + ) { + const trees = this.getTrees(); + const stateReference = await trees.getStateReference(true); + const inputs = PrivateContextInputs.empty(); + inputs.historicalHeader.globalVariables.blockNumber = new Fr(blockNumber); + inputs.historicalHeader.state = stateReference; + inputs.callContext.msgSender = msgSender; + inputs.callContext.storageContractAddress = contractAddress; + inputs.callContext.sideEffectCounter = this.getSideEffectCounter(); + return inputs; } // TypedOracle @@ -114,8 +162,8 @@ export class TXE implements TypedOracle { return this.keyStore.getKeyValidationRequest(pkMHash, this.contractAddress); } - getContractInstance(address: AztecAddress): Promise { - const contractInstance = this.contractInstanceStore.getContractInstance(address); + async getContractInstance(address: AztecAddress): Promise { + const contractInstance = await this.txeDatabase.getContractInstance(address); if (!contractInstance) { throw new Error(`Contract instance not found for address ${address}`); } @@ -166,7 +214,7 @@ export class TXE implements TypedOracle { } getCompleteAddress(account: AztecAddress): Promise { - return Promise.resolve(this.accountStore.getAccount(account)); + return Promise.resolve(this.txeDatabase.getAccount(account)); } getAuthWitness(_messageHash: Fr): Promise { @@ -305,14 +353,14 @@ export class TXE implements TypedOracle { } emitEncryptedLog(_contractAddress: AztecAddress, _randomness: Fr, _encryptedNote: Buffer, _counter: number): void { - throw new Error('Method not implemented.'); + return; } emitEncryptedNoteLog(_noteHashCounter: number, _encryptedNote: Buffer, _counter: number): void { - throw new Error('Method not implemented.'); + return; } - computeEncryptedLog( + computeEncryptedNoteLog( contractAddress: AztecAddress, storageSlot: Fr, noteTypeId: Fr, @@ -322,7 +370,7 @@ export class TXE implements TypedOracle { ): Buffer { const note = new Note(preimage); const l1NotePayload = new L1NotePayload(note, contractAddress, storageSlot, noteTypeId); - const taggedNote = new TaggedNote(l1NotePayload); + const taggedNote = new TaggedLog(l1NotePayload); const ephSk = GrumpkinScalar.random(); @@ -339,15 +387,85 @@ export class TXE implements TypedOracle { throw new Error('Method not implemented.'); } - callPrivateFunction( - _targetContractAddress: AztecAddress, - _functionSelector: FunctionSelector, - _argsHash: Fr, + async callPrivateFunction( + targetContractAddress: AztecAddress, + functionSelector: FunctionSelector, + argsHash: Fr, _sideEffectCounter: number, _isStaticCall: boolean, _isDelegateCall: boolean, ): Promise { - throw new Error('Method not implemented.'); + this.logger.debug( + `Calling private function ${targetContractAddress}:${functionSelector} from ${this.contractAddress}`, + ); + const artifact = await this.contractDataOracle.getFunctionArtifact(targetContractAddress, functionSelector); + + const acir = artifact.bytecode; + const initialWitness = await this.getInitialWitness(artifact, argsHash, targetContractAddress); + const acvmCallback = new Oracle(this); + const timer = new Timer(); + const acirExecutionResult = await acvm(acir, initialWitness, acvmCallback).catch((err: Error) => { + const execError = new ExecutionError( + err.message, + { + contractAddress: targetContractAddress, + functionSelector, + }, + extractCallStack(err, artifact.debug), + { cause: err }, + ); + this.logger.debug( + `Error executing private function ${targetContractAddress}:${functionSelector}\n${JSON.stringify( + execError, + null, + 4, + )}`, + ); + throw execError; + }); + const duration = timer.ms(); + const returnWitness = witnessMapToFields(acirExecutionResult.returnWitness); + const publicInputs = PrivateCircuitPublicInputs.fromFields(returnWitness); + + // TODO (alexg) estimate this size + const initialWitnessSize = witnessMapToFields(initialWitness).length * Fr.SIZE_IN_BYTES; + this.logger.debug(`Ran external function ${targetContractAddress.toString()}:${functionSelector}`, { + circuitName: 'app-circuit', + duration, + eventName: 'circuit-witness-generation', + inputSize: initialWitnessSize, + outputSize: publicInputs.toBuffer().length, + appCircuitName: 'noname', + } satisfies CircuitWitnessGenerationStats); + + const callStackItem = new PrivateCallStackItem( + targetContractAddress, + new FunctionData(functionSelector, true), + publicInputs, + ); + this.sideEffectCounter += publicInputs.callContext.sideEffectCounter; + + return callStackItem; + } + + async getInitialWitness(abi: FunctionAbi, argsHash: Fr, targetContractAddress: AztecAddress) { + const argumentsSize = countArgumentsSize(abi); + + const args = this.packedValuesCache.unpack(argsHash); + + if (args.length !== argumentsSize) { + throw new Error('Invalid arguments size'); + } + + const privateContextInputs = await this.getPrivateContextInputs( + this.blockNumber - 1, + this.contractAddress, + targetContractAddress, + ); + + const fields = [...privateContextInputs.toFields(), ...args]; + + return toACVMWitness(0, fields); } callPublicFunction( @@ -391,4 +509,24 @@ export class TXE implements TypedOracle { debugLog(message: string, fields: Fr[]): void { this.logger.verbose(`debug_log ${applyStringFormatting(message, fields)}`); } + + emitEncryptedEventLog( + _contractAddress: AztecAddress, + _randomness: Fr, + _encryptedEvent: Buffer, + _counter: number, + ): void { + return; + } + + computeEncryptedEventLog( + _contractAddress: AztecAddress, + _randomness: Fr, + _eventTypeId: Fr, + _ovKeys: KeyValidationRequest, + _ivpkM: Point, + _preimage: Fr[], + ): Buffer { + throw new Error('Method not implemented.'); + } } diff --git a/yarn-project/txe/src/txe_service/txe_service.ts b/yarn-project/txe/src/txe_service/txe_service.ts index ea47b17044c..9783829661d 100644 --- a/yarn-project/txe/src/txe_service/txe_service.ts +++ b/yarn-project/txe/src/txe_service/txe_service.ts @@ -1,7 +1,7 @@ -import { ContractInstanceStore } from '@aztec/archiver'; import { L2Block, MerkleTreeId, PublicDataWrite } from '@aztec/circuit-types'; import { Fr, + FunctionSelector, Header, KeyValidationRequest, PUBLIC_DATA_SUBTREE_HEIGHT, @@ -20,7 +20,6 @@ import { ExecutionNoteCache, PackedValuesCache, type TypedOracle } from '@aztec/ import { MerkleTrees } from '@aztec/world-state'; import { TXE } from '../oracle/txe_oracle.js'; -import { AccountStore } from '../util/account_store.js'; import { type ForeignCallArray, type ForeignCallSingle, @@ -30,6 +29,7 @@ import { toForeignCallResult, toSingle, } from '../util/encoding.js'; +import { TXEDatabase } from '../util/txe_database.js'; export class TXEService { constructor(private logger: Logger, private typedOracle: TypedOracle, private store: AztecKVStore) {} @@ -39,20 +39,10 @@ export class TXEService { const trees = await MerkleTrees.new(store, logger); const packedValuesCache = new PackedValuesCache(); const noteCache = new ExecutionNoteCache(); - const contractInstanceStore = new ContractInstanceStore(store); const keyStore = new KeyStore(store); - const accountStore = new AccountStore(store); + const txeDatabase = new TXEDatabase(store); logger.info(`TXE service initialized`); - const txe = new TXE( - logger, - trees, - packedValuesCache, - noteCache, - contractInstanceStore, - keyStore, - accountStore, - contractAddress, - ); + const txe = new TXE(logger, trees, packedValuesCache, noteCache, keyStore, txeDatabase, contractAddress); const service = new TXEService(logger, txe, store); await service.timeTravel(toSingle(new Fr(1n))); return service; @@ -61,13 +51,7 @@ export class TXEService { // Cheatcodes async getPrivateContextInputs(blockNumber: ForeignCallSingle) { - const inputs = PrivateContextInputs.empty(); - const trees = (this.typedOracle as TXE).getTrees(); - const stateReference = await trees.getStateReference(true); - inputs.historicalHeader.globalVariables.blockNumber = fromSingle(blockNumber); - inputs.historicalHeader.state = stateReference; - inputs.callContext.msgSender = AztecAddress.random(); - inputs.callContext.storageContractAddress = await this.typedOracle.getContractAddress(); + const inputs = await (this.typedOracle as TXE).getPrivateContextInputs(fromSingle(blockNumber).toNumber()); return toForeignCallResult(inputs.toFields().map(toSingle)); } @@ -104,17 +88,15 @@ export class TXEService { async reset() { this.store = openTmpStore(true); const trees = await MerkleTrees.new(this.store, this.logger); - const contractInstanceStore = new ContractInstanceStore(this.store); const keyStore = new KeyStore(this.store); - const accountStore = new AccountStore(this.store); + const txeDatabase = new TXEDatabase(this.store); this.typedOracle = new TXE( this.logger, trees, new PackedValuesCache(), new ExecutionNoteCache(), - contractInstanceStore, keyStore, - accountStore, + txeDatabase, AztecAddress.random(), ); await this.timeTravel(toSingle(new Fr(1))); @@ -148,11 +130,13 @@ export class TXEService { constructorArgs: decodedArgs, salt: Fr.ONE, publicKeysHash: Fr.ZERO, - constructorArtifact: initializerStr, + constructorArtifact: initializerStr ? initializerStr : undefined, deployer: AztecAddress.ZERO, }); + this.logger.debug(`Deployed ${contractClass.artifact.name} at ${instance.address}`); await (this.typedOracle as TXE).addContractInstance(instance); + await (this.typedOracle as TXE).addContractArtifact(contractClass.artifact); return toForeignCallResult([toSingle(instance.address)]); } @@ -180,11 +164,21 @@ export class TXEService { return toForeignCallResult([toArray(publicDataWrites.map(write => write.newValue))]); } - async addAccount(secret: ForeignCallSingle) { - const secretKey = fromSingle(secret); + async createAccount() { const keyStore = (this.typedOracle as TXE).getKeyStore(); - const completeAddress = await keyStore.addAccount(secretKey, Fr.ONE); - const accountStore = (this.typedOracle as TXE).getAccountStore(); + const completeAddress = await keyStore.createAccount(); + const accountStore = (this.typedOracle as TXE).getTXEDatabase(); + await accountStore.setAccount(completeAddress.address, completeAddress); + return toForeignCallResult([ + toSingle(completeAddress.address), + ...completeAddress.publicKeys.toFields().map(toSingle), + ]); + } + + async addAccount(secret: ForeignCallSingle, partialAddress: ForeignCallSingle) { + const keyStore = (this.typedOracle as TXE).getKeyStore(); + const completeAddress = await keyStore.addAccount(fromSingle(secret), fromSingle(partialAddress)); + const accountStore = (this.typedOracle as TXE).getTXEDatabase(); await accountStore.setAccount(completeAddress.address, completeAddress); return toForeignCallResult([ toSingle(completeAddress.address), @@ -403,7 +397,7 @@ export class TXEService { return toForeignCallResult([toArray(keyValidationRequest.toFields())]); } - computeEncryptedLog( + computeEncryptedNoteLog( contractAddress: ForeignCallSingle, storageSlot: ForeignCallSingle, noteTypeId: ForeignCallSingle, @@ -417,7 +411,7 @@ export class TXEService { const ovpkM = new Point(fromSingle(ovpkMX), fromSingle(ovpkMY)); const ovKeys = new KeyValidationRequest(ovpkM, Fr.fromString(fromSingle(ovskApp).toString())); const ivpkM = new Point(fromSingle(ivpkMX), fromSingle(ivpkMY)); - const encLog = this.typedOracle.computeEncryptedLog( + const encLog = this.typedOracle.computeEncryptedNoteLog( AztecAddress.fromString(fromSingle(contractAddress).toString()), Fr.fromString(fromSingle(storageSlot).toString()), Fr.fromString(fromSingle(noteTypeId).toString()), @@ -449,4 +443,23 @@ export class TXEService { ) { return toForeignCallResult([]); } + + async callPrivateFunction( + targetContractAddress: ForeignCallSingle, + functionSelector: ForeignCallSingle, + argsHash: ForeignCallSingle, + sideEffectCounter: ForeignCallSingle, + isStaticCall: ForeignCallSingle, + isDelegateCall: ForeignCallSingle, + ) { + const result = await this.typedOracle.callPrivateFunction( + fromSingle(targetContractAddress), + FunctionSelector.fromField(fromSingle(functionSelector)), + fromSingle(argsHash), + fromSingle(sideEffectCounter).toNumber(), + fromSingle(isStaticCall).toBool(), + fromSingle(isDelegateCall).toBool(), + ); + return toForeignCallResult([toArray(result.toFields())]); + } } diff --git a/yarn-project/txe/src/util/account_store.ts b/yarn-project/txe/src/util/txe_database.ts similarity index 55% rename from yarn-project/txe/src/util/account_store.ts rename to yarn-project/txe/src/util/txe_database.ts index f35589812dc..b154fd8702a 100644 --- a/yarn-project/txe/src/util/account_store.ts +++ b/yarn-project/txe/src/util/txe_database.ts @@ -1,16 +1,21 @@ import { type AztecAddress, CompleteAddress } from '@aztec/circuits.js'; import { type AztecKVStore, type AztecMap } from '@aztec/kv-store'; +import { KVPxeDatabase } from '@aztec/pxe'; -export class AccountStore { +export class TXEDatabase extends KVPxeDatabase { #accounts: AztecMap; - constructor(database: AztecKVStore) { - this.#accounts = database.openMap('accounts'); + constructor(db: AztecKVStore) { + super(db); + this.#accounts = db.openMap('accounts'); } getAccount(key: AztecAddress) { const completeAddress = this.#accounts.get(key.toString()); - return CompleteAddress.fromBuffer(completeAddress!); + if (!completeAddress) { + throw new Error(`Account not found: ${key.toString()}`); + } + return CompleteAddress.fromBuffer(completeAddress); } async setAccount(key: AztecAddress, value: CompleteAddress) { diff --git a/yarn-project/yarn.lock b/yarn-project/yarn.lock index 321559b00b0..c09a315a1b0 100644 --- a/yarn-project/yarn.lock +++ b/yarn-project/yarn.lock @@ -944,6 +944,7 @@ __metadata: "@aztec/foundation": "workspace:^" "@aztec/key-store": "workspace:^" "@aztec/kv-store": "workspace:^" + "@aztec/pxe": "workspace:^" "@aztec/simulator": "workspace:^" "@aztec/types": "workspace:^" "@aztec/world-state": "workspace:^" From 5a925532f5fee78486de63dfdd60e27ebc2a8c5a Mon Sep 17 00:00:00 2001 From: thunkar Date: Wed, 12 Jun 2024 16:13:42 +0000 Subject: [PATCH 39/75] private calls --- .../aztec/src/note/note_getter/test.nr | 7 ++-- .../src/state_vars/private_mutable/test.nr | 7 ++-- .../src/test/helpers/test_environment.nr | 35 +------------------ .../contracts/parent_contract/src/main.nr | 27 +++++++++++--- .../contracts/token_contract/src/main.nr | 3 ++ yarn-project/txe/src/oracle/txe_oracle.ts | 33 ++++++++--------- 6 files changed, 49 insertions(+), 63 deletions(-) diff --git a/noir-projects/aztec-nr/aztec/src/note/note_getter/test.nr b/noir-projects/aztec-nr/aztec/src/note/note_getter/test.nr index 0ba1c1c08bb..9ee0738312e 100644 --- a/noir-projects/aztec-nr/aztec/src/note/note_getter/test.nr +++ b/noir-projects/aztec-nr/aztec/src/note/note_getter/test.nr @@ -11,15 +11,14 @@ use dep::protocol_types::address::AztecAddress; use crate::test::{helpers::{test_environment::TestEnvironment, cheatcodes}, mocks::mock_note::MockNote}; -global contract_address = AztecAddress::from_field(69); global storage_slot: Field = 42; fn setup() -> TestEnvironment { - TestEnvironment::new().contract_address(contract_address) + TestEnvironment::new() } fn build_valid_note(value: Field) -> MockNote { - MockNote::new(value).contract_address(contract_address).storage_slot(storage_slot).build() + MockNote::new(value).contract_address(cheatcodes::get_contract_address()).storage_slot(storage_slot).build() } #[test] @@ -161,7 +160,7 @@ fn rejects_mismatched_storage_slot() { let mut env = setup(); let mut context = env.private(); - let note = MockNote::new(1).contract_address(contract_address).build(); // We're not setting the right storage slot + let note = MockNote::new(1).contract_address(cheatcodes::get_contract_address()).build(); // We're not setting the right storage slot let mut opt_notes = [Option::none(); MAX_NOTE_HASH_READ_REQUESTS_PER_CALL]; opt_notes[0] = Option::some(note); diff --git a/noir-projects/aztec-nr/aztec/src/state_vars/private_mutable/test.nr b/noir-projects/aztec-nr/aztec/src/state_vars/private_mutable/test.nr index 3db22ca38e2..6c1b3569fa7 100644 --- a/noir-projects/aztec-nr/aztec/src/state_vars/private_mutable/test.nr +++ b/noir-projects/aztec-nr/aztec/src/state_vars/private_mutable/test.nr @@ -1,13 +1,12 @@ use dep::protocol_types::{address::AztecAddress, grumpkin_point::GrumpkinPoint}; use crate::{context::PrivateContext, state_vars::private_mutable::PrivateMutable}; -use crate::test::{mocks::mock_note::MockNote, helpers::test_environment::TestEnvironment}; +use crate::test::{mocks::mock_note::MockNote, helpers::{cheatcodes, test_environment::TestEnvironment}}; use dep::std::{unsafe::zeroed, test::OracleMock}; -global contract_address = AztecAddress::from_field(13); global storage_slot = 17; fn setup() -> TestEnvironment { - TestEnvironment::new().contract_address(contract_address) + TestEnvironment::new() } fn in_private(env: &mut TestEnvironment) -> PrivateMutable { @@ -28,7 +27,7 @@ fn test_initialize_or_replace_without_nullifier() { let ivpk_m: GrumpkinPoint = zeroed(); let value = 42; - let mut note = MockNote::new(value).contract_address(contract_address).storage_slot(storage_slot).build(); + let mut note = MockNote::new(value).contract_address(cheatcodes::get_contract_address()).storage_slot(storage_slot).build(); OracleMock::mock("checkNullifierExists").returns(0); state_var.initialize_or_replace(&mut note, ovpk_m, ivpk_m); diff --git a/noir-projects/aztec-nr/aztec/src/test/helpers/test_environment.nr b/noir-projects/aztec-nr/aztec/src/test/helpers/test_environment.nr index 4a837a918df..28485ae60a5 100644 --- a/noir-projects/aztec-nr/aztec/src/test/helpers/test_environment.nr +++ b/noir-projects/aztec-nr/aztec/src/test/helpers/test_environment.nr @@ -28,21 +28,6 @@ impl TestEnvironment { cheatcodes::get_block_number() } - fn contract_address(&mut self, contract_address: AztecAddress) -> Self { - self.contract_address = Option::some(contract_address); - *self - } - - fn function_selector(&mut self, function_selector: FunctionSelector) -> Self { - self.function_selector = Option::some(function_selector); - *self - } - - fn args_hash(&mut self, args_hash: Field) -> Self { - self.args_hash = Option::some(args_hash); - *self - } - fn advance_block_to(&mut self, block_number: u32) { let difference = block_number - cheatcodes::get_block_number(); self.advance_block_by(difference); @@ -53,10 +38,6 @@ impl TestEnvironment { } fn public(self) -> PublicContext { - if (self.contract_address.is_some()) { - cheatcodes::set_contract_address(self.contract_address.unwrap_unchecked()); - } - PublicContext::empty() } @@ -71,21 +52,7 @@ impl TestEnvironment { let mut inputs = cheatcodes::get_private_context_inputs(historical_block_number); - if (self.contract_address.is_some()) { - inputs.call_context.storage_contract_address = self.contract_address.unwrap_unchecked(); - } - - if (self.function_selector.is_some()) { - inputs.call_context.function_selector = self.function_selector.unwrap_unchecked(); - } - - let mut args_hash = 0; - - if (self.args_hash.is_some()) { - args_hash = self.args_hash.unwrap_unchecked(); - } - - PrivateContext::new(inputs, args_hash) + PrivateContext::new(inputs, 0) } fn create_account(self) -> AztecAddress { diff --git a/noir-projects/noir-contracts/contracts/parent_contract/src/main.nr b/noir-projects/noir-contracts/contracts/parent_contract/src/main.nr index d2163b7f8ce..75239c19537 100644 --- a/noir-projects/noir-contracts/contracts/parent_contract/src/main.nr +++ b/noir-projects/noir-contracts/contracts/parent_contract/src/main.nr @@ -256,23 +256,40 @@ contract Parent { // Setup env, generate keys let mut env = TestEnvironment::new(); let owner = env.create_account(); + let parent_contract_address = cheatcodes::get_contract_address(); // Deploy child contract let child_contract_address = env.deploy("@aztec/noir-contracts.js/Child").without_initializer(); cheatcodes::advance_blocks(1); + // Set value in child through parent - let call_interface = Parent::interface().private_call( + let value_to_set = 7; + let parent_private_set_call_interface = Parent::interface().private_call( child_contract_address, FunctionSelector::from_signature("private_set_value(Field,(Field))"), - [7, owner.to_field()] + [value_to_set, owner.to_field()] ); - let result: Field = env.call_private(call_interface); + let result: Field = env.call_private(parent_private_set_call_interface); + assert(result == value_to_set); + // Read the stored value in the note - //cheatcodes::set_contract_address(child_contract_address); + cheatcodes::set_contract_address(child_contract_address); let counter_slot = Child::storage().a_map_with_private_values.slot; let owner_slot = derive_storage_slot_in_map(counter_slot, owner); let mut options = NoteViewerOptions::new(); let opt_notes: [Option; MAX_NOTES_PER_PAGE] = view_notes(owner_slot, options); - assert(opt_notes[0].unwrap().value == result); + let note_value = opt_notes[0].unwrap().value; + assert(note_value == value_to_set); + assert(note_value == result); + + // Get value from child through parent + cheatcodes::set_contract_address(parent_contract_address); + let parent_private_get_call_interface = Parent::interface().private_call( + child_contract_address, + FunctionSelector::from_signature("private_get_value(Field,(Field))"), + [7, owner.to_field()] + ); + let read_result: Field = env.call_private(parent_private_get_call_interface); + assert(note_value == read_result); } } diff --git a/noir-projects/noir-contracts/contracts/token_contract/src/main.nr b/noir-projects/noir-contracts/contracts/token_contract/src/main.nr index 6360cbc442e..f53f18b8cdd 100644 --- a/noir-projects/noir-contracts/contracts/token_contract/src/main.nr +++ b/noir-projects/noir-contracts/contracts/token_contract/src/main.nr @@ -370,5 +370,8 @@ contract Token { storage.balances.balance_of(owner).to_field() } // docs:end:balance_of_private + + #[test] + fn test_private_transfer() {} } // docs:end:token_all diff --git a/yarn-project/txe/src/oracle/txe_oracle.ts b/yarn-project/txe/src/oracle/txe_oracle.ts index cd15873be1e..ca81dc05ea3 100644 --- a/yarn-project/txe/src/oracle/txe_oracle.ts +++ b/yarn-project/txe/src/oracle/txe_oracle.ts @@ -50,6 +50,7 @@ import { type PackedValuesCache, type TypedOracle, acvm, + createSimulationError, extractCallStack, pickNotes, toACVMWitness, @@ -119,7 +120,7 @@ export class TXE implements TypedOracle { async getPrivateContextInputs( blockNumber: number, msgSender = AztecAddress.random(), - contractAddress = this.contractAddress, + sideEffectCounter = this.sideEffectCounter, ) { const trees = this.getTrees(); const stateReference = await trees.getStateReference(true); @@ -127,8 +128,9 @@ export class TXE implements TypedOracle { inputs.historicalHeader.globalVariables.blockNumber = new Fr(blockNumber); inputs.historicalHeader.state = stateReference; inputs.callContext.msgSender = msgSender; - inputs.callContext.storageContractAddress = contractAddress; - inputs.callContext.sideEffectCounter = this.getSideEffectCounter(); + inputs.callContext.storageContractAddress = this.contractAddress; + inputs.callContext.sideEffectCounter = sideEffectCounter; + inputs.startSideEffectCounter = sideEffectCounter; return inputs; } @@ -391,17 +393,21 @@ export class TXE implements TypedOracle { targetContractAddress: AztecAddress, functionSelector: FunctionSelector, argsHash: Fr, - _sideEffectCounter: number, + sideEffectCounter: number, _isStaticCall: boolean, _isDelegateCall: boolean, ): Promise { this.logger.debug( `Calling private function ${targetContractAddress}:${functionSelector} from ${this.contractAddress}`, ); + // Modify env + const from = AztecAddress.fromField(this.contractAddress); + this.setContractAddress(targetContractAddress); + const artifact = await this.contractDataOracle.getFunctionArtifact(targetContractAddress, functionSelector); const acir = artifact.bytecode; - const initialWitness = await this.getInitialWitness(artifact, argsHash, targetContractAddress); + const initialWitness = await this.getInitialWitness(artifact, argsHash, from, sideEffectCounter); const acvmCallback = new Oracle(this); const timer = new Timer(); const acirExecutionResult = await acvm(acir, initialWitness, acvmCallback).catch((err: Error) => { @@ -415,10 +421,8 @@ export class TXE implements TypedOracle { { cause: err }, ); this.logger.debug( - `Error executing private function ${targetContractAddress}:${functionSelector}\n${JSON.stringify( + `Error executing private function ${targetContractAddress}:${functionSelector}\n${createSimulationError( execError, - null, - 4, )}`, ); throw execError; @@ -427,7 +431,6 @@ export class TXE implements TypedOracle { const returnWitness = witnessMapToFields(acirExecutionResult.returnWitness); const publicInputs = PrivateCircuitPublicInputs.fromFields(returnWitness); - // TODO (alexg) estimate this size const initialWitnessSize = witnessMapToFields(initialWitness).length * Fr.SIZE_IN_BYTES; this.logger.debug(`Ran external function ${targetContractAddress.toString()}:${functionSelector}`, { circuitName: 'app-circuit', @@ -443,12 +446,14 @@ export class TXE implements TypedOracle { new FunctionData(functionSelector, true), publicInputs, ); - this.sideEffectCounter += publicInputs.callContext.sideEffectCounter; + // Apply side effects + this.sideEffectCounter += publicInputs.endSideEffectCounter.toNumber(); + this.setContractAddress(from); return callStackItem; } - async getInitialWitness(abi: FunctionAbi, argsHash: Fr, targetContractAddress: AztecAddress) { + async getInitialWitness(abi: FunctionAbi, argsHash: Fr, msgSender: AztecAddress, sideEffectCounter: number) { const argumentsSize = countArgumentsSize(abi); const args = this.packedValuesCache.unpack(argsHash); @@ -457,11 +462,7 @@ export class TXE implements TypedOracle { throw new Error('Invalid arguments size'); } - const privateContextInputs = await this.getPrivateContextInputs( - this.blockNumber - 1, - this.contractAddress, - targetContractAddress, - ); + const privateContextInputs = await this.getPrivateContextInputs(this.blockNumber - 1, msgSender, sideEffectCounter); const fields = [...privateContextInputs.toFields(), ...args]; From 5e9486eb417d3dcb30efc0b7c9ac7f5c4a53e8fd Mon Sep 17 00:00:00 2001 From: thunkar Date: Thu, 13 Jun 2024 14:04:54 +0000 Subject: [PATCH 40/75] public initializers --- .../aztec/src/context/call_interfaces.nr | 141 ++++++++++++++++++ .../aztec/src/test/helpers/cheatcodes.nr | 40 ++++- .../src/test/helpers/test_environment.nr | 73 ++++++++- .../aztec-nr/aztec/src/test/helpers/types.nr | 32 +++- .../contracts/child_contract/src/main.nr | 2 +- .../contracts/counter_contract/src/main.nr | 4 +- .../contracts/parent_contract/src/main.nr | 11 +- .../contracts/token_contract/src/main.nr | 42 +++++- .../src/transforms/contract_interface.rs | 4 +- .../src/contract/contract_instance.ts | 22 ++- yarn-project/txe/src/oracle/txe_oracle.ts | 111 +++++++++++--- .../txe/src/txe_service/txe_service.ts | 81 +++++++++- 12 files changed, 510 insertions(+), 53 deletions(-) diff --git a/noir-projects/aztec-nr/aztec/src/context/call_interfaces.nr b/noir-projects/aztec-nr/aztec/src/context/call_interfaces.nr index d57a4eb3b3d..dd1374f9eb0 100644 --- a/noir-projects/aztec-nr/aztec/src/context/call_interfaces.nr +++ b/noir-projects/aztec-nr/aztec/src/context/call_interfaces.nr @@ -15,6 +15,7 @@ trait CallInterface { fn get_original(self) -> fn[Env](T) -> P; fn get_selector(self) -> FunctionSelector; fn get_name(self) -> str; + fn get_contract_address(self) -> AztecAddress; } impl CallInterface for PrivateCallInterface { @@ -33,6 +34,10 @@ impl CallInterface str { self.name } + + fn get_contract_address(self) -> AztecAddress { + self.target_contract + } } struct PrivateCallInterface { @@ -84,6 +89,10 @@ impl CallInterface str { self.name } + + fn get_contract_address(self) -> AztecAddress { + self.target_contract + } } struct PrivateVoidCallInterface { @@ -115,6 +124,28 @@ impl PrivateVoidCallInterface { } } +impl CallInterface for PrivateStaticCallInterface { + fn get_args(self) -> [Field] { + self.args + } + + fn get_original(self) -> fn[Env](PrivateContextInputs) -> PrivateCircuitPublicInputs { + self.original + } + + fn get_selector(self) -> FunctionSelector { + self.selector + } + + fn get_name(self) -> str { + self.name + } + + fn get_contract_address(self) -> AztecAddress { + self.target_contract + } +} + struct PrivateStaticCallInterface { target_contract: AztecAddress, selector: FunctionSelector, @@ -131,6 +162,28 @@ impl PrivateStaticCallInterface { } } +impl CallInterface for PrivateStaticVoidCallInterface { + fn get_args(self) -> [Field] { + self.args + } + + fn get_original(self) -> fn[Env](PrivateContextInputs) -> PrivateCircuitPublicInputs { + self.original + } + + fn get_selector(self) -> FunctionSelector { + self.selector + } + + fn get_name(self) -> str { + self.name + } + + fn get_contract_address(self) -> AztecAddress { + self.target_contract + } +} + struct PrivateStaticVoidCallInterface { target_contract: AztecAddress, selector: FunctionSelector, @@ -146,6 +199,28 @@ impl PrivateStaticVoidCallInterface { } } +impl CallInterface for PublicCallInterface { + fn get_args(self) -> [Field] { + self.args + } + + fn get_original(self) -> fn[Env](PublicContextInputs) -> T { + self.original + } + + fn get_selector(self) -> FunctionSelector { + self.selector + } + + fn get_name(self) -> str { + self.name + } + + fn get_contract_address(self) -> AztecAddress { + self.target_contract + } +} + struct PublicCallInterface { target_contract: AztecAddress, selector: FunctionSelector, @@ -213,6 +288,28 @@ impl PublicCallInterface { } } +impl CallInterface for PublicVoidCallInterface { + fn get_args(self) -> [Field] { + self.args + } + + fn get_original(self) -> fn[Env](PublicContextInputs) -> () { + self.original + } + + fn get_selector(self) -> FunctionSelector { + self.selector + } + + fn get_name(self) -> str { + self.name + } + + fn get_contract_address(self) -> AztecAddress { + self.target_contract + } +} + struct PublicVoidCallInterface { target_contract: AztecAddress, selector: FunctionSelector, @@ -280,6 +377,28 @@ impl PublicVoidCallInterface { } } +impl CallInterface for PublicStaticCallInterface { + fn get_args(self) -> [Field] { + self.args + } + + fn get_original(self) -> fn[Env](PublicContextInputs) -> T { + self.original + } + + fn get_selector(self) -> FunctionSelector { + self.selector + } + + fn get_name(self) -> str { + self.name + } + + fn get_contract_address(self) -> AztecAddress { + self.target_contract + } +} + struct PublicStaticCallInterface { target_contract: AztecAddress, selector: FunctionSelector, @@ -314,6 +433,28 @@ impl PublicStaticCallInterface { } } +impl CallInterface for PublicStaticVoidCallInterface { + fn get_args(self) -> [Field] { + self.args + } + + fn get_original(self) -> fn[Env](PublicContextInputs) -> () { + self.original + } + + fn get_selector(self) -> FunctionSelector { + self.selector + } + + fn get_name(self) -> str { + self.name + } + + fn get_contract_address(self) -> AztecAddress { + self.target_contract + } +} + struct PublicStaticVoidCallInterface { target_contract: AztecAddress, selector: FunctionSelector, diff --git a/noir-projects/aztec-nr/aztec/src/test/helpers/cheatcodes.nr b/noir-projects/aztec-nr/aztec/src/test/helpers/cheatcodes.nr index 44c22ef574a..55f05b1554a 100644 --- a/noir-projects/aztec-nr/aztec/src/test/helpers/cheatcodes.nr +++ b/noir-projects/aztec-nr/aztec/src/test/helpers/cheatcodes.nr @@ -1,6 +1,7 @@ -use dep::protocol_types::address::{AztecAddress, PartialAddress}; +use dep::protocol_types::{abis::function_selector::FunctionSelector, address::{AztecAddress, PartialAddress}}; use crate::context::inputs::{PublicContextInputs, PrivateContextInputs}; use crate::test::helpers::types::{Deployer, TestAccount}; +use crate::keys::public_keys::PublicKeys; unconstrained pub fn reset() { oracle_reset(); @@ -30,8 +31,13 @@ unconstrained pub fn get_public_context_inputs() -> PublicContextInputs { oracle_get_public_context_inputs() } -unconstrained pub fn deploy(path: str, initializer: str, args: [Field]) -> AztecAddress { - oracle_deploy(path, initializer, args) +unconstrained pub fn deploy( + path: str, + initializer: str, + args: [Field], + public_keys_hash: Field +) -> AztecAddress { + oracle_deploy(path, initializer, args, public_keys_hash) } unconstrained pub fn direct_storage_write( @@ -50,6 +56,18 @@ unconstrained pub fn add_account(secret: Field, partial_address: PartialAddress) oracle_add_account(secret, partial_address) } +unconstrained pub fn derive_keys(secret: Field) -> PublicKeys { + oracle_derive_keys(secret) +} + +unconstrained pub fn set_msg_sender(msg_sender: AztecAddress) { + oracle_set_msg_sender(msg_sender) +} + +unconstrained pub fn get_msg_sender() -> AztecAddress { + oracle_get_msg_sender() +} + #[oracle(reset)] fn oracle_reset() {} @@ -72,7 +90,12 @@ fn oracle_get_private_context_inputs(historical_block_number: u32) -> PrivateCon fn oracle_get_public_context_inputs() -> PublicContextInputs {} #[oracle(deploy)] -fn oracle_deploy(path: str, initializer: str, args: [Field]) -> AztecAddress {} +fn oracle_deploy( + path: str, + initializer: str, + args: [Field], + public_keys_hash: Field +) -> AztecAddress {} #[oracle(directStorageWrite)] fn direct_storage_write_oracle( @@ -86,3 +109,12 @@ fn oracle_create_account() -> TestAccount {} #[oracle(addAccount)] fn oracle_add_account(secret: Field, partial_address: PartialAddress) -> TestAccount {} + +#[oracle(deriveKeys)] +fn oracle_derive_keys(secret: Field) -> PublicKeys {} + +#[oracle(getMsgSender)] +fn oracle_get_msg_sender() -> AztecAddress {} + +#[oracle(setMsgSender)] +fn oracle_set_msg_sender(msg_sender: AztecAddress) {} diff --git a/noir-projects/aztec-nr/aztec/src/test/helpers/test_environment.nr b/noir-projects/aztec-nr/aztec/src/test/helpers/test_environment.nr index 28485ae60a5..d3db6178f17 100644 --- a/noir-projects/aztec-nr/aztec/src/test/helpers/test_environment.nr +++ b/noir-projects/aztec-nr/aztec/src/test/helpers/test_environment.nr @@ -10,6 +10,7 @@ use crate::context::{packed_returns::PackedReturns, call_interfaces::CallInterfa use crate::context::{PrivateContext, PublicContext, PrivateVoidCallInterface}; use crate::test::helpers::{cheatcodes, types::{Deployer, TestAccount}, keys}; use crate::keys::constants::{NULLIFIER_INDEX, INCOMING_INDEX, OUTGOING_INDEX, TAGGING_INDEX}; +use crate::hash::hash_args; struct TestEnvironment { contract_address: Option, @@ -68,8 +69,30 @@ impl TestEnvironment { test_account.address } + fn create_account_contract(self, secret: Field) -> AztecAddress { + let public_keys = cheatcodes::derive_keys(secret); + let args = &[public_keys.ivpk_m.x, public_keys.ivpk_m.y]; + let address = cheatcodes::deploy( + "@aztec/noir-contracts.js/SchnorrAccount", + "constructor", + args, + public_keys.hash().to_field() + ); + cheatcodes::advance_blocks(1); + let test_account = cheatcodes::add_account(secret, PartialAddress::from_field(address.to_field())); + let address = test_account.address; + let keys = test_account.keys; + + keys::store_master_key(NULLIFIER_INDEX, address, keys.npk_m); + keys::store_master_key(INCOMING_INDEX, address, keys.ivpk_m); + keys::store_master_key(OUTGOING_INDEX, address, keys.ovpk_m); + keys::store_master_key(TAGGING_INDEX, address, keys.tpk_m); + + test_account.address + } + fn deploy(self, path: str) -> Deployer { - Deployer { path } + Deployer { path, public_keys_hash: 0 } } fn call_private( @@ -77,8 +100,56 @@ impl TestEnvironment { call_interface: C ) -> T where C: CallInterface, T: Deserialize { let original_fn = call_interface.get_original(); + let original_msg_sender = cheatcodes::get_msg_sender(); + let original_contract_address = cheatcodes::get_contract_address(); + let target_address = call_interface.get_contract_address(); + + cheatcodes::set_contract_address(target_address); + cheatcodes::set_msg_sender(original_contract_address); let mut inputs = cheatcodes::get_private_context_inputs(cheatcodes::get_block_number() - 1); + inputs.call_context.function_selector = call_interface.get_selector(); let public_inputs = original_fn(inputs); + + cheatcodes::set_contract_address(original_contract_address); + cheatcodes::set_msg_sender(original_msg_sender); PackedReturns::new(public_inputs.returns_hash).unpack_into() } + + fn call_private_void( + self, + call_interface: C + ) where C: CallInterface { + let original_fn = call_interface.get_original(); + let original_msg_sender = cheatcodes::get_msg_sender(); + let original_contract_address = cheatcodes::get_contract_address(); + let target_address = call_interface.get_contract_address(); + + cheatcodes::set_contract_address(target_address); + cheatcodes::set_msg_sender(original_contract_address); + let mut inputs = cheatcodes::get_private_context_inputs(cheatcodes::get_block_number() - 1); + inputs.call_context.function_selector = call_interface.get_selector(); + let public_inputs = original_fn(inputs); + + cheatcodes::set_contract_address(original_contract_address); + cheatcodes::set_msg_sender(original_msg_sender); + PackedReturns::new(public_inputs.returns_hash).assert_empty(); + } + + fn call_public(self, call_interface: C) -> T where C: CallInterface { + let original_fn = call_interface.get_original(); + let original_msg_sender = cheatcodes::get_msg_sender(); + let original_contract_address = cheatcodes::get_contract_address(); + let target_address = call_interface.get_contract_address(); + + cheatcodes::set_contract_address(target_address); + cheatcodes::set_msg_sender(original_contract_address); + let mut inputs = cheatcodes::get_public_context_inputs(); + inputs.selector = call_interface.get_selector().to_field(); + inputs.args_hash = hash_args(call_interface.get_args()); + let result = original_fn(inputs); + + cheatcodes::set_contract_address(original_contract_address); + cheatcodes::set_msg_sender(original_msg_sender); + result + } } diff --git a/noir-projects/aztec-nr/aztec/src/test/helpers/types.nr b/noir-projects/aztec-nr/aztec/src/test/helpers/types.nr index 9ce08939e8d..0274a5c15ea 100644 --- a/noir-projects/aztec-nr/aztec/src/test/helpers/types.nr +++ b/noir-projects/aztec-nr/aztec/src/test/helpers/types.nr @@ -7,9 +7,11 @@ use crate::context::inputs::{PublicContextInputs, PrivateContextInputs}; use crate::context::call_interfaces::CallInterface; use crate::test::helpers::cheatcodes; use crate::keys::public_keys::{PUBLIC_KEYS_LENGTH, PublicKeys}; +use crate::hash::hash_args; struct Deployer { path: str, + public_keys_hash: Field } impl Deployer { @@ -20,15 +22,23 @@ impl Deployer { let address = cheatcodes::deploy( self.path, call_interface.get_name(), - call_interface.get_args() + call_interface.get_args(), + self.public_keys_hash ); cheatcodes::advance_blocks(1); let block_number = cheatcodes::get_block_number(); let original_fn = call_interface.get_original(); + let original_msg_sender = cheatcodes::get_msg_sender(); + let original_contract_address = cheatcodes::get_contract_address(); + + cheatcodes::set_contract_address(address); + cheatcodes::set_msg_sender(original_contract_address); let mut inputs = cheatcodes::get_private_context_inputs(block_number - 1); - inputs.call_context.storage_contract_address = address; inputs.call_context.function_selector = call_interface.get_selector(); let _result = original_fn(inputs); + + cheatcodes::set_contract_address(original_contract_address); + cheatcodes::set_msg_sender(original_msg_sender); address } @@ -39,16 +49,28 @@ impl Deployer { let address = cheatcodes::deploy( self.path, call_interface.get_name(), - call_interface.get_args() + call_interface.get_args(), + self.public_keys_hash ); + cheatcodes::advance_blocks(1); let original_fn = call_interface.get_original(); + let original_msg_sender = cheatcodes::get_msg_sender(); + let original_contract_address = cheatcodes::get_contract_address(); + + cheatcodes::set_contract_address(address); + cheatcodes::set_msg_sender(original_contract_address); let mut inputs = cheatcodes::get_public_context_inputs(); - let _result = original_fn(inputs); + inputs.selector = call_interface.get_selector().to_field(); + inputs.args_hash = hash_args(call_interface.get_args()); + let _result: T = original_fn(inputs); + + cheatcodes::set_contract_address(original_contract_address); + cheatcodes::set_msg_sender(original_msg_sender); address } pub fn without_initializer(self) -> AztecAddress { - let address = cheatcodes::deploy(self.path, "", &[]); + let address = cheatcodes::deploy(self.path, "", &[], self.public_keys_hash); address } } diff --git a/noir-projects/noir-contracts/contracts/child_contract/src/main.nr b/noir-projects/noir-contracts/contracts/child_contract/src/main.nr index fea13f3b346..bdf9390d1e4 100644 --- a/noir-projects/noir-contracts/contracts/child_contract/src/main.nr +++ b/noir-projects/noir-contracts/contracts/child_contract/src/main.nr @@ -56,7 +56,7 @@ contract Child { let owner_npk_m_hash = header.get_npk_m_hash(&mut context, owner); let mut note = ValueNote::new(new_value, owner_npk_m_hash); - storage.a_map_with_private_values.at(owner).insert(&mut note).emit(encode_and_encrypt(&mut context, context.msg_sender(), owner)); + storage.a_map_with_private_values.at(owner).insert(&mut note).emit(encode_and_encrypt(&mut context, owner, owner)); new_value } diff --git a/noir-projects/noir-contracts/contracts/counter_contract/src/main.nr b/noir-projects/noir-contracts/contracts/counter_contract/src/main.nr index dc20ba272db..e296d211aab 100644 --- a/noir-projects/noir-contracts/contracts/counter_contract/src/main.nr +++ b/noir-projects/noir-contracts/contracts/counter_contract/src/main.nr @@ -52,8 +52,10 @@ contract Counter { // Deploy contract and initialize let initializer = Counter::interface().initialize(5, owner, outgoing_viewer); - let _contract_address = env.deploy("@aztec/noir-contracts.js/Counter").with_private_initializer(initializer); + let contract_address = env.deploy("@aztec/noir-contracts.js/Counter").with_private_initializer(initializer); // Read the stored value in the note + + cheatcodes::set_contract_address(contract_address); let counter_slot = Counter::storage().counters.slot; let owner_slot = derive_storage_slot_in_map(counter_slot, owner); let mut options = NoteViewerOptions::new(); diff --git a/noir-projects/noir-contracts/contracts/parent_contract/src/main.nr b/noir-projects/noir-contracts/contracts/parent_contract/src/main.nr index 75239c19537..4247d25da6f 100644 --- a/noir-projects/noir-contracts/contracts/parent_contract/src/main.nr +++ b/noir-projects/noir-contracts/contracts/parent_contract/src/main.nr @@ -245,7 +245,6 @@ contract Parent { use dep::aztec::test::{helpers::{cheatcodes, test_environment::TestEnvironment}}; use dep::aztec::protocol_types::storage::map::derive_storage_slot_in_map; - use dep::aztec::oracle::{storage::{storage_read, storage_write}}; use dep::aztec::note::note_getter::{MAX_NOTES_PER_PAGE, view_notes}; use dep::aztec::note::note_viewer_options::NoteViewerOptions; use dep::child_contract::Child; @@ -256,6 +255,7 @@ contract Parent { // Setup env, generate keys let mut env = TestEnvironment::new(); let owner = env.create_account(); + // Initial address, since we don't have to deploy Parent in order to test it let parent_contract_address = cheatcodes::get_contract_address(); // Deploy child contract @@ -264,15 +264,14 @@ contract Parent { // Set value in child through parent let value_to_set = 7; - let parent_private_set_call_interface = Parent::interface().private_call( + let parent_private_set_call_interface = Parent::at(parent_contract_address).private_call( child_contract_address, FunctionSelector::from_signature("private_set_value(Field,(Field))"), [value_to_set, owner.to_field()] ); let result: Field = env.call_private(parent_private_set_call_interface); assert(result == value_to_set); - - // Read the stored value in the note + // Read the stored value in the note. We have to change the contract address to the child contract in order to read its notes cheatcodes::set_contract_address(child_contract_address); let counter_slot = Child::storage().a_map_with_private_values.slot; let owner_slot = derive_storage_slot_in_map(counter_slot, owner); @@ -281,10 +280,8 @@ contract Parent { let note_value = opt_notes[0].unwrap().value; assert(note_value == value_to_set); assert(note_value == result); - // Get value from child through parent - cheatcodes::set_contract_address(parent_contract_address); - let parent_private_get_call_interface = Parent::interface().private_call( + let parent_private_get_call_interface = Parent::at(parent_contract_address).private_call( child_contract_address, FunctionSelector::from_signature("private_get_value(Field,(Field))"), [7, owner.to_field()] diff --git a/noir-projects/noir-contracts/contracts/token_contract/src/main.nr b/noir-projects/noir-contracts/contracts/token_contract/src/main.nr index 21223cb8e53..73340ddb466 100644 --- a/noir-projects/noir-contracts/contracts/token_contract/src/main.nr +++ b/noir-projects/noir-contracts/contracts/token_contract/src/main.nr @@ -396,7 +396,47 @@ contract Token { } // docs:end:balance_of_private + use dep::aztec::test::{helpers::{cheatcodes, test_environment::TestEnvironment}}; + use dep::aztec::protocol_types::storage::map::derive_storage_slot_in_map; + use dep::aztec::note::note_getter::{MAX_NOTES_PER_PAGE, view_notes}; + use dep::aztec::note::note_viewer_options::NoteViewerOptions; + #[test] - fn test_private_transfer() {} + fn test_private_transfer() { + // Setup env, generate keys + let mut env = TestEnvironment::new(); + let owner = env.create_account(); + let recipient = env.create_account(); + let mint_amount = 10000; + + cheatcodes::set_contract_address(owner); + + // Deploy token contract + let initializer_call_interface = Token::interface().constructor( + owner, + "TestToken0000000000000000000000", + "TT00000000000000000000000000000", + 18 + ); + let token_contract_address = env.deploy("@aztec/noir-contracts.js/Token").with_public_initializer(initializer_call_interface); + env.advance_block_by(1); + + // Mint some tokens + let secret = 1; + let secret_hash = compute_secret_hash(secret); + let mint_private_call_interface = Token::at(token_contract_address).mint_private(mint_amount, secret_hash); + env.call_public(mint_private_call_interface); + + // Time travel so we can read keys from the registry + env.advance_block_by(6); + + // Redeem our shielded tokens + let redeem_shield_call_interface = Token::at(token_contract_address).redeem_shield(owner, mint_amount, secret); + env.call_private_void(redeem_shield_call_interface); + + // Transfer tokens + let private_token_transfer_call_interface = Token::at(token_contract_address).transfer(recipient, 1000); + env.call_private_void(private_token_transfer_call_interface); + } } // docs:end:token_all \ No newline at end of file diff --git a/noir/noir-repo/aztec_macros/src/transforms/contract_interface.rs b/noir/noir-repo/aztec_macros/src/transforms/contract_interface.rs index 5e3daced44d..a8babdc944d 100644 --- a/noir/noir-repo/aztec_macros/src/transforms/contract_interface.rs +++ b/noir/noir-repo/aztec_macros/src/transforms/contract_interface.rs @@ -69,9 +69,9 @@ pub fn stub_function(aztec_visibility: &str, func: &NoirFunction, is_static_call match &arg.typ.typ { UnresolvedTypeData::Array(_, typ) => { format!( - "let hash_{0} = {0}.map(|x: {1}| x.serialize()); + "let serialized_{0} = {0}.map(|x: {1}| x.serialize()); for i in 0..{0}.len() {{ - args_acc = args_acc.append(hash_{0}[i].as_slice()); + args_acc = args_acc.append(serialized_{0}[i].as_slice()); }}\n", param_name, typ.typ.to_string().replace("plain::", "") diff --git a/yarn-project/circuits.js/src/contract/contract_instance.ts b/yarn-project/circuits.js/src/contract/contract_instance.ts index 4c047461664..3168be0ce0c 100644 --- a/yarn-project/circuits.js/src/contract/contract_instance.ts +++ b/yarn-project/circuits.js/src/contract/contract_instance.ts @@ -1,11 +1,20 @@ -import { type ContractArtifact, type FunctionArtifact, getDefaultInitializer } from '@aztec/foundation/abi'; +import { + type ContractArtifact, + type FunctionArtifact, + FunctionSelector, + getDefaultInitializer, +} from '@aztec/foundation/abi'; import { AztecAddress } from '@aztec/foundation/aztec-address'; import { Fr } from '@aztec/foundation/fields'; import { type ContractInstance, type ContractInstanceWithAddress } from '@aztec/types/contracts'; import { getContractClassFromArtifact } from '../contract/contract_class.js'; import { computeContractClassId } from '../contract/contract_class_id.js'; -import { computeContractAddressFromInstance, computeInitializationHash } from './contract_address.js'; +import { + computeContractAddressFromInstance, + computeInitializationHash, + computeInitializationHashFromEncodedArgs, +} from './contract_address.js'; /** * Generates a Contract Instance from the deployment params. @@ -18,6 +27,7 @@ export function getContractInstanceFromDeployParams( opts: { constructorArtifact?: FunctionArtifact | string; constructorArgs?: any[]; + skipArgsDecoding?: boolean; salt?: Fr; publicKeysHash?: Fr; deployer?: AztecAddress; @@ -29,7 +39,13 @@ export function getContractInstanceFromDeployParams( const deployer = opts.deployer ?? AztecAddress.ZERO; const contractClass = getContractClassFromArtifact(artifact); const contractClassId = computeContractClassId(contractClass); - const initializationHash = computeInitializationHash(constructorArtifact, args); + const initializationHash = + constructorArtifact && opts?.skipArgsDecoding + ? computeInitializationHashFromEncodedArgs( + FunctionSelector.fromNameAndParameters(constructorArtifact?.name, constructorArtifact?.parameters), + args, + ) + : computeInitializationHash(constructorArtifact, args); const publicKeysHash = opts.publicKeysHash ?? Fr.ZERO; const instance: ContractInstance = { diff --git a/yarn-project/txe/src/oracle/txe_oracle.ts b/yarn-project/txe/src/oracle/txe_oracle.ts index ca81dc05ea3..90b521bfffc 100644 --- a/yarn-project/txe/src/oracle/txe_oracle.ts +++ b/yarn-project/txe/src/oracle/txe_oracle.ts @@ -3,7 +3,7 @@ import { MerkleTreeId, Note, type NoteStatus, - type NullifierMembershipWitness, + NullifierMembershipWitness, PublicDataWitness, PublicDataWrite, TaggedLog, @@ -16,6 +16,8 @@ import { type Header, type KeyValidationRequest, NULLIFIER_SUBTREE_HEIGHT, + type NULLIFIER_TREE_HEIGHT, + type NullifierLeafPreimage, PUBLIC_DATA_SUBTREE_HEIGHT, type PUBLIC_DATA_TREE_HEIGHT, PrivateCallStackItem, @@ -25,16 +27,12 @@ import { PublicDataTreeLeaf, type PublicDataTreeLeafPreimage, computeContractClassId, + deriveKeys, getContractClassFromArtifact, } from '@aztec/circuits.js'; import { Aes128 } from '@aztec/circuits.js/barretenberg'; import { computePublicDataTreeLeafSlot, siloNoteHash, siloNullifier } from '@aztec/circuits.js/hash'; -import { - type ContractArtifact, - type FunctionAbi, - type FunctionSelector, - countArgumentsSize, -} from '@aztec/foundation/abi'; +import { type ContractArtifact, type FunctionAbi, FunctionSelector, countArgumentsSize } from '@aztec/foundation/abi'; import { AztecAddress } from '@aztec/foundation/aztec-address'; import { Fr, GrumpkinScalar, type Point } from '@aztec/foundation/fields'; import { type Logger, applyStringFormatting } from '@aztec/foundation/log'; @@ -64,6 +62,9 @@ import { type TXEDatabase } from '../util/txe_database.js'; export class TXE implements TypedOracle { private blockNumber = 0; private sideEffectCounter = 0; + private contractAddress: AztecAddress; + private msgSender: AztecAddress; + private contractDataOracle: ContractDataOracle; constructor( @@ -73,13 +74,22 @@ export class TXE implements TypedOracle { private noteCache: ExecutionNoteCache, private keyStore: KeyStore, private txeDatabase: TXEDatabase, - private contractAddress: AztecAddress, ) { this.contractDataOracle = new ContractDataOracle(txeDatabase); + this.contractAddress = AztecAddress.random(); + this.msgSender = AztecAddress.fromField(new Fr(0)); } // Utils + getMsgSender() { + return this.msgSender; + } + + setMsgSender(msgSender: Fr) { + this.msgSender = msgSender; + } + getSideEffectCounter() { return this.sideEffectCounter; } @@ -117,23 +127,56 @@ export class TXE implements TypedOracle { await this.txeDatabase.addContractArtifact(computeContractClassId(contractClass), artifact); } - async getPrivateContextInputs( - blockNumber: number, - msgSender = AztecAddress.random(), - sideEffectCounter = this.sideEffectCounter, - ) { + async getPrivateContextInputs(blockNumber: number, sideEffectCounter = this.sideEffectCounter) { const trees = this.getTrees(); const stateReference = await trees.getStateReference(true); const inputs = PrivateContextInputs.empty(); inputs.historicalHeader.globalVariables.blockNumber = new Fr(blockNumber); inputs.historicalHeader.state = stateReference; - inputs.callContext.msgSender = msgSender; + inputs.callContext.msgSender = this.msgSender; inputs.callContext.storageContractAddress = this.contractAddress; inputs.callContext.sideEffectCounter = sideEffectCounter; inputs.startSideEffectCounter = sideEffectCounter; return inputs; } + getPublicContextInputs() { + const inputs = { + functionSelector: FunctionSelector.fromField(new Fr(0)), + argsHash: new Fr(0), + isStaticCall: false, + toFields: function () { + return [this.functionSelector.toField(), this.argsHash, new Fr(this.isStaticCall)]; + }, + }; + return inputs; + } + + async avmOpcodeNullifierExists(innerNullifier: Fr, targetAddress: AztecAddress): Promise { + const nullifier = siloNullifier(targetAddress, innerNullifier!); + const db = this.trees.asLatest(); + const index = await db.findLeafIndex(MerkleTreeId.NULLIFIER_TREE, nullifier.toBuffer()); + return index !== undefined; + } + + async avmOpcodeEmitNullifier(nullifier: Fr) { + const db = this.trees.asLatest(); + const siloedNullifier = siloNullifier(this.contractAddress, nullifier); + await db.batchInsert(MerkleTreeId.NULLIFIER_TREE, [siloedNullifier.toBuffer()], NULLIFIER_SUBTREE_HEIGHT); + return Promise.resolve(); + } + + async avmOpcodeEmitNoteHash(innerNoteHash: Fr) { + const db = this.trees.asLatest(); + const noteHash = siloNoteHash(this.contractAddress, innerNoteHash); + await db.appendLeaves(MerkleTreeId.NOTE_HASH_TREE, [noteHash]); + return Promise.resolve(); + } + + deriveKeys(secret: Fr) { + return deriveKeys(secret); + } + // TypedOracle getBlockNumber(): Promise { @@ -182,8 +225,29 @@ export class TXE implements TypedOracle { return result.toFields(); } - getNullifierMembershipWitness(_blockNumber: number, _nullifier: Fr): Promise { - throw new Error('Method not implemented.'); + async getNullifierMembershipWitness( + blockNumber: number, + nullifier: Fr, + ): Promise { + const committedDb = new MerkleTreeSnapshotOperationsFacade(this.trees, blockNumber); + const index = await committedDb.findLeafIndex(MerkleTreeId.NULLIFIER_TREE, nullifier.toBuffer()); + if (!index) { + return undefined; + } + + const leafPreimagePromise = committedDb.getLeafPreimage(MerkleTreeId.NULLIFIER_TREE, index); + const siblingPathPromise = committedDb.getSiblingPath( + MerkleTreeId.NULLIFIER_TREE, + BigInt(index), + ); + + const [leafPreimage, siblingPath] = await Promise.all([leafPreimagePromise, siblingPathPromise]); + + if (!leafPreimage) { + return undefined; + } + + return new NullifierMembershipWitness(BigInt(index), leafPreimage as NullifierLeafPreimage, siblingPath); } async getPublicDataTreeWitness(blockNumber: number, leafSlot: Fr): Promise { @@ -400,14 +464,16 @@ export class TXE implements TypedOracle { this.logger.debug( `Calling private function ${targetContractAddress}:${functionSelector} from ${this.contractAddress}`, ); - // Modify env - const from = AztecAddress.fromField(this.contractAddress); + // Store and modify env + const currentContractAddress = AztecAddress.fromField(this.contractAddress); + const currentMessageSender = AztecAddress.fromField(this.msgSender); + this.setMsgSender(this.contractAddress); this.setContractAddress(targetContractAddress); const artifact = await this.contractDataOracle.getFunctionArtifact(targetContractAddress, functionSelector); const acir = artifact.bytecode; - const initialWitness = await this.getInitialWitness(artifact, argsHash, from, sideEffectCounter); + const initialWitness = await this.getInitialWitness(artifact, argsHash, sideEffectCounter); const acvmCallback = new Oracle(this); const timer = new Timer(); const acirExecutionResult = await acvm(acir, initialWitness, acvmCallback).catch((err: Error) => { @@ -448,12 +514,13 @@ export class TXE implements TypedOracle { ); // Apply side effects this.sideEffectCounter += publicInputs.endSideEffectCounter.toNumber(); - this.setContractAddress(from); + this.setContractAddress(currentContractAddress); + this.setMsgSender(currentMessageSender); return callStackItem; } - async getInitialWitness(abi: FunctionAbi, argsHash: Fr, msgSender: AztecAddress, sideEffectCounter: number) { + async getInitialWitness(abi: FunctionAbi, argsHash: Fr, sideEffectCounter: number) { const argumentsSize = countArgumentsSize(abi); const args = this.packedValuesCache.unpack(argsHash); @@ -462,7 +529,7 @@ export class TXE implements TypedOracle { throw new Error('Invalid arguments size'); } - const privateContextInputs = await this.getPrivateContextInputs(this.blockNumber - 1, msgSender, sideEffectCounter); + const privateContextInputs = await this.getPrivateContextInputs(this.blockNumber - 1, sideEffectCounter); const fields = [...privateContextInputs.toFields(), ...args]; diff --git a/yarn-project/txe/src/txe_service/txe_service.ts b/yarn-project/txe/src/txe_service/txe_service.ts index 9783829661d..e312ff817f7 100644 --- a/yarn-project/txe/src/txe_service/txe_service.ts +++ b/yarn-project/txe/src/txe_service/txe_service.ts @@ -6,7 +6,6 @@ import { KeyValidationRequest, PUBLIC_DATA_SUBTREE_HEIGHT, Point, - PrivateContextInputs, PublicDataTreeLeaf, getContractInstanceFromDeployParams, } from '@aztec/circuits.js'; @@ -34,7 +33,7 @@ import { TXEDatabase } from '../util/txe_database.js'; export class TXEService { constructor(private logger: Logger, private typedOracle: TypedOracle, private store: AztecKVStore) {} - static async init(logger: Logger, contractAddress = AztecAddress.random()) { + static async init(logger: Logger) { const store = openTmpStore(true); const trees = await MerkleTrees.new(store, logger); const packedValuesCache = new PackedValuesCache(); @@ -42,7 +41,7 @@ export class TXEService { const keyStore = new KeyStore(store); const txeDatabase = new TXEDatabase(store); logger.info(`TXE service initialized`); - const txe = new TXE(logger, trees, packedValuesCache, noteCache, keyStore, txeDatabase, contractAddress); + const txe = new TXE(logger, trees, packedValuesCache, noteCache, keyStore, txeDatabase); const service = new TXEService(logger, txe, store); await service.timeTravel(toSingle(new Fr(1n))); return service; @@ -55,15 +54,21 @@ export class TXEService { return toForeignCallResult(inputs.toFields().map(toSingle)); } + getPublicContextInputs() { + const inputs = (this.typedOracle as TXE).getPublicContextInputs(); + return toForeignCallResult(inputs.toFields().map(toSingle)); + } + async timeTravel(blocks: ForeignCallSingle) { const nBlocks = fromSingle(blocks).toNumber(); this.logger.info(`time traveling ${nBlocks} blocks`); const trees = (this.typedOracle as TXE).getTrees(); - const blockNumber = await this.typedOracle.getBlockNumber(); for (let i = 0; i < nBlocks; i++) { const header = Header.empty(); const l2Block = L2Block.empty(); header.state = await trees.getStateReference(true); + const blockNumber = await this.typedOracle.getBlockNumber(); + header.globalVariables.blockNumber = new Fr(blockNumber); header.state.partial.nullifierTree.root = Fr.fromBuffer( (await trees.getTreeInfo(MerkleTreeId.NULLIFIER_TREE, true)).root, @@ -97,7 +102,6 @@ export class TXEService { new ExecutionNoteCache(), keyStore, txeDatabase, - AztecAddress.random(), ); await this.timeTravel(toSingle(new Fr(1))); return toForeignCallResult([]); @@ -109,11 +113,17 @@ export class TXEService { return toForeignCallResult([]); } + deriveKeys(secret: ForeignCallSingle) { + const keys = (this.typedOracle as TXE).deriveKeys(fromSingle(secret)); + return toForeignCallResult(keys.publicKeys.toFields().map(toSingle)); + } + async deploy( path: ForeignCallArray, initializer: ForeignCallArray, _length: ForeignCallSingle, args: ForeignCallArray, + publicKeysHash: ForeignCallSingle, ) { const pathStr = fromArray(path) .map(char => String.fromCharCode(char.toNumber())) @@ -128,8 +138,9 @@ export class TXEService { const contractClass = contractModule[Object.keys(contractModule).sort((a, b) => a.length - b.length)[0]]; const instance = getContractInstanceFromDeployParams(contractClass.artifact, { constructorArgs: decodedArgs, + skipArgsDecoding: true, salt: Fr.ONE, - publicKeysHash: Fr.ZERO, + publicKeysHash: fromSingle(publicKeysHash), constructorArtifact: initializerStr ? initializerStr : undefined, deployer: AztecAddress.ZERO, }); @@ -169,6 +180,7 @@ export class TXEService { const completeAddress = await keyStore.createAccount(); const accountStore = (this.typedOracle as TXE).getTXEDatabase(); await accountStore.setAccount(completeAddress.address, completeAddress); + this.logger.debug(`Created account ${completeAddress.address}`); return toForeignCallResult([ toSingle(completeAddress.address), ...completeAddress.publicKeys.toFields().map(toSingle), @@ -186,6 +198,16 @@ export class TXEService { ]); } + setMsgSender(msgSender: ForeignCallSingle) { + (this.typedOracle as TXE).setMsgSender(fromSingle(msgSender)); + return toForeignCallResult([]); + } + + getMsgSender() { + const msgSender = (this.typedOracle as TXE).getMsgSender(); + return toForeignCallResult([toSingle(msgSender)]); + } + // PXE oracles getRandomField() { @@ -385,6 +407,44 @@ export class TXEService { ]); } + async avmOpcodeGetContractInstance(address: ForeignCallSingle) { + const instance = await this.typedOracle.getContractInstance(fromSingle(address)); + return toForeignCallResult([ + toArray([ + // AVM requires an extra boolean indicating the instance was found + new Fr(1), + instance.salt, + instance.deployer, + instance.contractClassId, + instance.initializationHash, + instance.publicKeysHash, + ]), + ]); + } + + avmOpcodeSender() { + const sender = (this.typedOracle as TXE).getMsgSender(); + return toForeignCallResult([toSingle(sender)]); + } + + async avmOpcodeEmitNullifier(nullifier: ForeignCallSingle) { + await (this.typedOracle as TXE).avmOpcodeEmitNullifier(fromSingle(nullifier)); + return toForeignCallResult([]); + } + + async avmOpcodeEmitNoteHash(innerNoteHash: ForeignCallSingle) { + await (this.typedOracle as TXE).avmOpcodeEmitNoteHash(fromSingle(innerNoteHash)); + return toForeignCallResult([]); + } + + async avmOpcodeNullifierExists(innerNullifier: ForeignCallSingle, targetAddress: ForeignCallSingle) { + const exists = await (this.typedOracle as TXE).avmOpcodeNullifierExists( + fromSingle(innerNullifier), + AztecAddress.fromField(fromSingle(targetAddress)), + ); + return toForeignCallResult([toSingle(new Fr(exists))]); + } + async getPublicKeysAndPartialAddress(address: ForeignCallSingle) { const parsedAddress = AztecAddress.fromField(fromSingle(address)); const { publicKeys, partialAddress } = await this.typedOracle.getCompleteAddress(parsedAddress); @@ -462,4 +522,13 @@ export class TXEService { ); return toForeignCallResult([toArray(result.toFields())]); } + + async getNullifierMembershipWitness(blockNumber: ForeignCallSingle, nullifier: ForeignCallSingle) { + const parsedBlockNumber = fromSingle(blockNumber).toNumber(); + const witness = await this.typedOracle.getNullifierMembershipWitness(parsedBlockNumber, fromSingle(nullifier)); + if (!witness) { + throw new Error(`Nullifier membership witness not found at block ${parsedBlockNumber}.`); + } + return toForeignCallResult([toArray(witness.toFields())]); + } } From bbeaa00ac2bf88e4f35d5041aecab0fefaf33ba4 Mon Sep 17 00:00:00 2001 From: thunkar Date: Thu, 13 Jun 2024 14:59:01 +0000 Subject: [PATCH 41/75] 34 token transfer --- .../aztec/src/test/helpers/cheatcodes.nr | 41 +++++++++++++++++++ .../contracts/token_contract/src/main.nr | 28 ++++++++++++- yarn-project/txe/src/oracle/txe_oracle.ts | 18 ++++---- .../txe/src/txe_service/txe_service.ts | 5 +++ 4 files changed, 82 insertions(+), 10 deletions(-) diff --git a/noir-projects/aztec-nr/aztec/src/test/helpers/cheatcodes.nr b/noir-projects/aztec-nr/aztec/src/test/helpers/cheatcodes.nr index 55f05b1554a..0fd8270f263 100644 --- a/noir-projects/aztec-nr/aztec/src/test/helpers/cheatcodes.nr +++ b/noir-projects/aztec-nr/aztec/src/test/helpers/cheatcodes.nr @@ -2,6 +2,11 @@ use dep::protocol_types::{abis::function_selector::FunctionSelector, address::{A use crate::context::inputs::{PublicContextInputs, PrivateContextInputs}; use crate::test::helpers::types::{Deployer, TestAccount}; use crate::keys::public_keys::PublicKeys; +use crate::note::{ + note_header::NoteHeader, note_interface::NoteInterface, + utils::{compute_note_hash_for_insertion, compute_note_hash_for_consumption} +}; +use crate::oracle::notes::notify_created_note; unconstrained pub fn reset() { oracle_reset(); @@ -68,6 +73,39 @@ unconstrained pub fn get_msg_sender() -> AztecAddress { oracle_get_msg_sender() } +unconstrained fn get_side_effects_counter() -> u32 { + oracle_get_side_effects_counter() +} + +pub fn store_note_in_cache( + note: &mut Note, + storage_slot: Field, + contract_address: AztecAddress +) where Note: NoteInterface { + let original_contract_address = get_contract_address(); + set_contract_address(contract_address); + let note_hash_counter = get_side_effects_counter(); + + let header = NoteHeader { contract_address, storage_slot, nonce: 0, note_hash_counter }; + // TODO: change this to note.set_header(header) once https://github.com/noir-lang/noir/issues/4095 is fixed + Note::set_header(note, header); + let inner_note_hash = compute_note_hash_for_insertion(*note); + + // TODO: Strong typing required because of https://github.com/noir-lang/noir/issues/4088 + let serialized_note: [Field; N] = Note::serialize_content(*note); + assert( + notify_created_note( + storage_slot, + Note::get_note_type_id(), + serialized_note, + inner_note_hash, + note_hash_counter + ) + == 0 + ); + set_contract_address(original_contract_address); +} + #[oracle(reset)] fn oracle_reset() {} @@ -118,3 +156,6 @@ fn oracle_get_msg_sender() -> AztecAddress {} #[oracle(setMsgSender)] fn oracle_set_msg_sender(msg_sender: AztecAddress) {} + +#[oracle(getSideEffectsCounter)] +fn oracle_get_side_effects_counter() -> u32 {} diff --git a/noir-projects/noir-contracts/contracts/token_contract/src/main.nr b/noir-projects/noir-contracts/contracts/token_contract/src/main.nr index 73340ddb466..4a95b79b116 100644 --- a/noir-projects/noir-contracts/contracts/token_contract/src/main.nr +++ b/noir-projects/noir-contracts/contracts/token_contract/src/main.nr @@ -409,6 +409,7 @@ contract Token { let recipient = env.create_account(); let mint_amount = 10000; + // Start the test in the account contract address cheatcodes::set_contract_address(owner); // Deploy token contract @@ -430,13 +431,38 @@ contract Token { // Time travel so we can read keys from the registry env.advance_block_by(6); + // Store a note in the cache so we can redeem it + cheatcodes::store_note_in_cache( + &mut TransparentNote::new(mint_amount, secret_hash), + Token::storage().pending_shields.slot, + token_contract_address + ); + // Redeem our shielded tokens let redeem_shield_call_interface = Token::at(token_contract_address).redeem_shield(owner, mint_amount, secret); env.call_private_void(redeem_shield_call_interface); + // Not really sure why this is needed? + env.advance_block_by(1); + // Transfer tokens - let private_token_transfer_call_interface = Token::at(token_contract_address).transfer(recipient, 1000); + let transfer_amount = 1000; + let private_token_transfer_call_interface = Token::at(token_contract_address).transfer(recipient, transfer_amount); env.call_private_void(private_token_transfer_call_interface); + + // Check balances + cheatcodes::set_contract_address(token_contract_address); + + let balances_slot = Token::storage().balances.slot; + let recipient_slot = derive_storage_slot_in_map(balances_slot, recipient); + let mut options = NoteViewerOptions::new(); + let opt_notes: [Option; MAX_NOTES_PER_PAGE] = view_notes(recipient_slot, options); + assert(opt_notes[0].unwrap().amount.to_field() == transfer_amount); + + let owner_slot = derive_storage_slot_in_map(balances_slot, owner); + let mut options = NoteViewerOptions::new(); + let opt_notes: [Option; MAX_NOTES_PER_PAGE] = view_notes(owner_slot, options); + assert(opt_notes[0].unwrap().amount.to_field() == mint_amount - transfer_amount); } } // docs:end:token_all \ No newline at end of file diff --git a/yarn-project/txe/src/oracle/txe_oracle.ts b/yarn-project/txe/src/oracle/txe_oracle.ts index 90b521bfffc..9cbb375f096 100644 --- a/yarn-project/txe/src/oracle/txe_oracle.ts +++ b/yarn-project/txe/src/oracle/txe_oracle.ts @@ -61,7 +61,7 @@ import { type TXEDatabase } from '../util/txe_database.js'; export class TXE implements TypedOracle { private blockNumber = 0; - private sideEffectCounter = 0; + private sideEffectsCounter = 0; private contractAddress: AztecAddress; private msgSender: AztecAddress; @@ -90,12 +90,12 @@ export class TXE implements TypedOracle { this.msgSender = msgSender; } - getSideEffectCounter() { - return this.sideEffectCounter; + getSideEffectsCounter() { + return this.sideEffectsCounter; } - setSideEffectCounter(sideEffectCounter: number) { - this.sideEffectCounter = sideEffectCounter; + setSideEffectsCounter(sideEffectsCounter: number) { + this.sideEffectsCounter = sideEffectsCounter; } setContractAddress(contractAddress: AztecAddress) { @@ -127,7 +127,7 @@ export class TXE implements TypedOracle { await this.txeDatabase.addContractArtifact(computeContractClassId(contractClass), artifact); } - async getPrivateContextInputs(blockNumber: number, sideEffectCounter = this.sideEffectCounter) { + async getPrivateContextInputs(blockNumber: number, sideEffectsCounter = this.sideEffectsCounter) { const trees = this.getTrees(); const stateReference = await trees.getStateReference(true); const inputs = PrivateContextInputs.empty(); @@ -135,8 +135,8 @@ export class TXE implements TypedOracle { inputs.historicalHeader.state = stateReference; inputs.callContext.msgSender = this.msgSender; inputs.callContext.storageContractAddress = this.contractAddress; - inputs.callContext.sideEffectCounter = sideEffectCounter; - inputs.startSideEffectCounter = sideEffectCounter; + inputs.callContext.sideEffectCounter = sideEffectsCounter; + inputs.startSideEffectCounter = sideEffectsCounter; return inputs; } @@ -513,7 +513,7 @@ export class TXE implements TypedOracle { publicInputs, ); // Apply side effects - this.sideEffectCounter += publicInputs.endSideEffectCounter.toNumber(); + this.sideEffectsCounter += publicInputs.endSideEffectCounter.toNumber(); this.setContractAddress(currentContractAddress); this.setMsgSender(currentMessageSender); diff --git a/yarn-project/txe/src/txe_service/txe_service.ts b/yarn-project/txe/src/txe_service/txe_service.ts index e312ff817f7..a93b80ce145 100644 --- a/yarn-project/txe/src/txe_service/txe_service.ts +++ b/yarn-project/txe/src/txe_service/txe_service.ts @@ -208,6 +208,11 @@ export class TXEService { return toForeignCallResult([toSingle(msgSender)]); } + getSideEffectsCounter() { + const counter = (this.typedOracle as TXE).getSideEffectsCounter(); + return toForeignCallResult([toSingle(new Fr(counter))]); + } + // PXE oracles getRandomField() { From 963990a4769c02923b68648a78b15aeb513e79af Mon Sep 17 00:00:00 2001 From: thunkar Date: Fri, 14 Jun 2024 07:41:23 +0000 Subject: [PATCH 42/75] cleanup --- .../aztec/src/test/helpers/cheatcodes.nr | 36 +------------ .../src/test/helpers/test_environment.nr | 53 +++++++++++++++++++ .../contracts/token_contract/src/main.nr | 2 +- 3 files changed, 55 insertions(+), 36 deletions(-) diff --git a/noir-projects/aztec-nr/aztec/src/test/helpers/cheatcodes.nr b/noir-projects/aztec-nr/aztec/src/test/helpers/cheatcodes.nr index 0fd8270f263..014757cf9b0 100644 --- a/noir-projects/aztec-nr/aztec/src/test/helpers/cheatcodes.nr +++ b/noir-projects/aztec-nr/aztec/src/test/helpers/cheatcodes.nr @@ -2,11 +2,6 @@ use dep::protocol_types::{abis::function_selector::FunctionSelector, address::{A use crate::context::inputs::{PublicContextInputs, PrivateContextInputs}; use crate::test::helpers::types::{Deployer, TestAccount}; use crate::keys::public_keys::PublicKeys; -use crate::note::{ - note_header::NoteHeader, note_interface::NoteInterface, - utils::{compute_note_hash_for_insertion, compute_note_hash_for_consumption} -}; -use crate::oracle::notes::notify_created_note; unconstrained pub fn reset() { oracle_reset(); @@ -73,39 +68,10 @@ unconstrained pub fn get_msg_sender() -> AztecAddress { oracle_get_msg_sender() } -unconstrained fn get_side_effects_counter() -> u32 { +unconstrained pub fn get_side_effects_counter() -> u32 { oracle_get_side_effects_counter() } -pub fn store_note_in_cache( - note: &mut Note, - storage_slot: Field, - contract_address: AztecAddress -) where Note: NoteInterface { - let original_contract_address = get_contract_address(); - set_contract_address(contract_address); - let note_hash_counter = get_side_effects_counter(); - - let header = NoteHeader { contract_address, storage_slot, nonce: 0, note_hash_counter }; - // TODO: change this to note.set_header(header) once https://github.com/noir-lang/noir/issues/4095 is fixed - Note::set_header(note, header); - let inner_note_hash = compute_note_hash_for_insertion(*note); - - // TODO: Strong typing required because of https://github.com/noir-lang/noir/issues/4088 - let serialized_note: [Field; N] = Note::serialize_content(*note); - assert( - notify_created_note( - storage_slot, - Note::get_note_type_id(), - serialized_note, - inner_note_hash, - note_hash_counter - ) - == 0 - ); - set_contract_address(original_contract_address); -} - #[oracle(reset)] fn oracle_reset() {} diff --git a/noir-projects/aztec-nr/aztec/src/test/helpers/test_environment.nr b/noir-projects/aztec-nr/aztec/src/test/helpers/test_environment.nr index d3db6178f17..3f14fb0e5fa 100644 --- a/noir-projects/aztec-nr/aztec/src/test/helpers/test_environment.nr +++ b/noir-projects/aztec-nr/aztec/src/test/helpers/test_environment.nr @@ -12,6 +12,12 @@ use crate::test::helpers::{cheatcodes, types::{Deployer, TestAccount}, keys}; use crate::keys::constants::{NULLIFIER_INDEX, INCOMING_INDEX, OUTGOING_INDEX, TAGGING_INDEX}; use crate::hash::hash_args; +use crate::note::{ + note_header::NoteHeader, note_interface::NoteInterface, + utils::{compute_note_hash_for_insertion, compute_note_hash_for_consumption} +}; +use crate::oracle::notes::notify_created_note; + struct TestEnvironment { contract_address: Option, args_hash: Option, @@ -152,4 +158,51 @@ impl TestEnvironment { cheatcodes::set_msg_sender(original_msg_sender); result } + + fn call_public_void(self, call_interface: C) where C: CallInterface { + let original_fn = call_interface.get_original(); + let original_msg_sender = cheatcodes::get_msg_sender(); + let original_contract_address = cheatcodes::get_contract_address(); + let target_address = call_interface.get_contract_address(); + + cheatcodes::set_contract_address(target_address); + cheatcodes::set_msg_sender(original_contract_address); + let mut inputs = cheatcodes::get_public_context_inputs(); + inputs.selector = call_interface.get_selector().to_field(); + inputs.args_hash = hash_args(call_interface.get_args()); + original_fn(inputs); + + cheatcodes::set_contract_address(original_contract_address); + cheatcodes::set_msg_sender(original_msg_sender); + } + + pub fn store_note_in_cache( + self, + note: &mut Note, + storage_slot: Field, + contract_address: AztecAddress + ) where Note: NoteInterface { + let original_contract_address = cheatcodes::get_contract_address(); + cheatcodes::set_contract_address(contract_address); + let note_hash_counter = cheatcodes::get_side_effects_counter(); + + let header = NoteHeader { contract_address, storage_slot, nonce: 0, note_hash_counter }; + // TODO: change this to note.set_header(header) once https://github.com/noir-lang/noir/issues/4095 is fixed + Note::set_header(note, header); + let inner_note_hash = compute_note_hash_for_insertion(*note); + + // TODO: Strong typing required because of https://github.com/noir-lang/noir/issues/4088 + let serialized_note: [Field; N] = Note::serialize_content(*note); + assert( + notify_created_note( + storage_slot, + Note::get_note_type_id(), + serialized_note, + inner_note_hash, + note_hash_counter + ) + == 0 + ); + cheatcodes::set_contract_address(original_contract_address); + } } diff --git a/noir-projects/noir-contracts/contracts/token_contract/src/main.nr b/noir-projects/noir-contracts/contracts/token_contract/src/main.nr index 4a95b79b116..0d957144885 100644 --- a/noir-projects/noir-contracts/contracts/token_contract/src/main.nr +++ b/noir-projects/noir-contracts/contracts/token_contract/src/main.nr @@ -432,7 +432,7 @@ contract Token { env.advance_block_by(6); // Store a note in the cache so we can redeem it - cheatcodes::store_note_in_cache( + env.store_note_in_cache( &mut TransparentNote::new(mint_amount, secret_hash), Token::storage().pending_shields.slot, token_contract_address From 44d63aad426afabe86d8ff38049ace4dd649fb93 Mon Sep 17 00:00:00 2001 From: thunkar Date: Fri, 14 Jun 2024 10:51:59 +0000 Subject: [PATCH 43/75] sessionId compatibility --- .../src/test/helpers/test_environment.nr | 1 - yarn-project/txe/src/bin/index.ts | 81 ++++++++++++++++--- yarn-project/txe/src/http_rpc_server/index.ts | 29 ------- yarn-project/txe/src/index.ts | 1 - .../txe/src/txe_service/txe_service.ts | 17 ---- yarn-project/txe/src/util/encoding.ts | 18 ++--- 6 files changed, 79 insertions(+), 68 deletions(-) delete mode 100644 yarn-project/txe/src/http_rpc_server/index.ts delete mode 100644 yarn-project/txe/src/index.ts diff --git a/noir-projects/aztec-nr/aztec/src/test/helpers/test_environment.nr b/noir-projects/aztec-nr/aztec/src/test/helpers/test_environment.nr index 3f14fb0e5fa..142f6fd58f8 100644 --- a/noir-projects/aztec-nr/aztec/src/test/helpers/test_environment.nr +++ b/noir-projects/aztec-nr/aztec/src/test/helpers/test_environment.nr @@ -27,7 +27,6 @@ struct TestEnvironment { impl TestEnvironment { fn new() -> Self { cheatcodes::reset(); - Self { contract_address: Option::none(), args_hash: Option::none(), function_selector: Option::none() } } diff --git a/yarn-project/txe/src/bin/index.ts b/yarn-project/txe/src/bin/index.ts index 365e2054be7..14934762159 100644 --- a/yarn-project/txe/src/bin/index.ts +++ b/yarn-project/txe/src/bin/index.ts @@ -1,27 +1,86 @@ #!/usr/bin/env -S node --no-warnings -import { createDebugLogger } from '@aztec/foundation/log'; +import { JsonRpcServer } from '@aztec/foundation/json-rpc/server'; +import { type Logger, createDebugLogger } from '@aztec/foundation/log'; + +import http from 'http'; -import { startTXEHttpServer } from '../index.js'; import { TXEService } from '../txe_service/txe_service.js'; +import { type ForeignCallResult, toForeignCallResult } from '../util/encoding.js'; const { TXE_PORT = 8080 } = process.env; const logger = createDebugLogger('aztec:txe_service'); +const TXESessions = new Map(); + +type MethodNames = { + [K in keyof T]: T[K] extends (...args: any[]) => any ? K : never; +}[keyof T]; + +type TXEForeignCallInput = { + session_id: number; + function: MethodNames | 'reset'; + inputs: any[]; +}; + +class TXEDispatcher { + constructor(private logger: Logger) {} + + // eslint-disable-next-line camelcase + async resolve_foreign_call({ + session_id: sessionId, + function: functionName, + inputs, + }: TXEForeignCallInput): Promise { + this.logger.debug( + `Calling ${functionName} with inputs: ${JSON.stringify(inputs, null, 2)} on session ${sessionId}`, + ); + + if (!TXESessions.has(sessionId) && functionName != 'reset') { + this.logger.debug(`Creating new session ${sessionId}`); + TXESessions.set(sessionId, await TXEService.init(logger)); + } + + if (functionName === 'reset') { + TXESessions.delete(sessionId) && + this.logger.debug(`Called reset on session ${sessionId}, yeeting it out of existence`); + return toForeignCallResult([]); + } else { + const txeService = TXESessions.get(sessionId); + const response = await (txeService as any)[functionName](...inputs); + this.logger.debug( + `${sessionId}:${functionName}(${JSON.stringify(inputs, null, 2)}) -> ${JSON.stringify(response, null, 2)}`, + ); + return response; + } + } +} + /** - * Create and start a new PXE HTTP Server + * Creates an http server that forwards calls to the TXE and starts it on the given port. + * @param txeService - TXE that answers queries to the created HTTP server. + * @param port - Port to listen in. + * @returns A running http server. */ -async function main() { - logger.info(`Setting up TXE...`); +export function startTXEHttpServer(dispatcher: TXEDispatcher, port: string | number): http.Server { + const txeServer = new JsonRpcServer(dispatcher, {}, {}, ['init']); + + const app = txeServer.getApp(); + const httpServer = http.createServer(app.callback()); + httpServer.listen(port); + + return httpServer; +} - const txeService = await TXEService.init(logger); +/** + * Create and start a new TXE HTTP Server + */ +function main() { + logger.info(`Setting up TXE...`); - startTXEHttpServer(txeService, TXE_PORT); + startTXEHttpServer(new TXEDispatcher(logger), TXE_PORT); logger.info(`TXE listening on port ${TXE_PORT}`); } -main().catch(err => { - logger.error(err); - process.exit(1); -}); +main(); diff --git a/yarn-project/txe/src/http_rpc_server/index.ts b/yarn-project/txe/src/http_rpc_server/index.ts deleted file mode 100644 index 69d9e08464b..00000000000 --- a/yarn-project/txe/src/http_rpc_server/index.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { JsonRpcServer } from '@aztec/foundation/json-rpc/server'; - -import http from 'http'; - -import { type TXEService } from '../txe_service/txe_service.js'; - -/** - * Wraps an instance of Private eXecution Environment (TXE) implementation to a JSON RPC HTTP interface. - * @returns A new instance of the HTTP server. - */ -export function createTXERpcServer(txeService: TXEService): JsonRpcServer { - return new JsonRpcServer(txeService, {}, {}, ['init']); -} - -/** - * Creates an http server that forwards calls to the TXE and starts it on the given port. - * @param txeService - TXE that answers queries to the created HTTP server. - * @param port - Port to listen in. - * @returns A running http server. - */ -export function startTXEHttpServer(txeService: TXEService, port: string | number): http.Server { - const txeServer = createTXERpcServer(txeService); - - const app = txeServer.getApp(); - const httpServer = http.createServer(app.callback()); - httpServer.listen(port); - - return httpServer; -} diff --git a/yarn-project/txe/src/index.ts b/yarn-project/txe/src/index.ts deleted file mode 100644 index 6f83d1f63b2..00000000000 --- a/yarn-project/txe/src/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './http_rpc_server/index.js'; diff --git a/yarn-project/txe/src/txe_service/txe_service.ts b/yarn-project/txe/src/txe_service/txe_service.ts index a93b80ce145..6b53c0de1eb 100644 --- a/yarn-project/txe/src/txe_service/txe_service.ts +++ b/yarn-project/txe/src/txe_service/txe_service.ts @@ -90,23 +90,6 @@ export class TXEService { return toForeignCallResult([]); } - async reset() { - this.store = openTmpStore(true); - const trees = await MerkleTrees.new(this.store, this.logger); - const keyStore = new KeyStore(this.store); - const txeDatabase = new TXEDatabase(this.store); - this.typedOracle = new TXE( - this.logger, - trees, - new PackedValuesCache(), - new ExecutionNoteCache(), - keyStore, - txeDatabase, - ); - await this.timeTravel(toSingle(new Fr(1))); - return toForeignCallResult([]); - } - setContractAddress(address: ForeignCallSingle) { const typedAddress = AztecAddress.fromField(fromSingle(address)); (this.typedOracle as TXE).setContractAddress(typedAddress); diff --git a/yarn-project/txe/src/util/encoding.ts b/yarn-project/txe/src/util/encoding.ts index 42dc32b88b4..0d4a78ba61a 100644 --- a/yarn-project/txe/src/util/encoding.ts +++ b/yarn-project/txe/src/util/encoding.ts @@ -1,27 +1,27 @@ import { Fr } from '@aztec/foundation/fields'; -export type ForeignCallSingle = { - Single: string; -}; +export type ForeignCallSingle = string; + +export type ForeignCallArray = string[]; -export type ForeignCallArray = { - Array: string[]; +export type ForeignCallResult = { + values: (ForeignCallSingle | ForeignCallArray)[]; }; export function fromSingle(obj: ForeignCallSingle) { - return Fr.fromBuffer(Buffer.from(obj.Single, 'hex')); + return Fr.fromBuffer(Buffer.from(obj, 'hex')); } export function fromArray(obj: ForeignCallArray) { - return obj.Array.map(str => Fr.fromBuffer(Buffer.from(str, 'hex'))); + return obj.map(str => Fr.fromBuffer(Buffer.from(str, 'hex'))); } export function toSingle(obj: Fr) { - return { Single: obj.toString().slice(2) }; + return obj.toString().slice(2); } export function toArray(objs: Fr[]) { - return { Array: objs.map(obj => obj.toString()) }; + return objs.map(obj => obj.toString()); } export function toForeignCallResult(obj: (ForeignCallSingle | ForeignCallArray)[]) { From 9f1dbe6e8ab0171296e9e051dcb4f3c0796c7da6 Mon Sep 17 00:00:00 2001 From: thunkar Date: Fri, 14 Jun 2024 10:52:09 +0000 Subject: [PATCH 44/75] corrected docs --- noir/noir-repo/docs/docs/how_to/how-to-oracles.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/noir/noir-repo/docs/docs/how_to/how-to-oracles.md b/noir/noir-repo/docs/docs/how_to/how-to-oracles.md index 62ead1f534f..64d984c8cf9 100644 --- a/noir/noir-repo/docs/docs/how_to/how-to-oracles.md +++ b/noir/noir-repo/docs/docs/how_to/how-to-oracles.md @@ -141,10 +141,10 @@ server.addMethod("resolve_function_call", async (params) => { if params.function !== "getSqrt" { throw Error("Unexpected foreign call") }; - const values = params.inputs[0].Array.map((field) => { + const values = params.inputs[0].map((field) => { return `${Math.sqrt(parseInt(field, 16))}`; }); - return { values: [{ Array: values }] }; + return { values }; }); ``` @@ -232,9 +232,9 @@ const foreignCallHandler = async (name, input) => { // notice that the "inputs" parameter contains *all* the inputs // in this case we to make the RPC request with the first parameter "numbers", which would be input[0] const oracleReturn = await client.request(name, [ - { Array: input[0].map((i) => i.toString("hex")) }, + input[0].map((i) => i.toString("hex")), ]); - return [oracleReturn.values[0].Array]; + return { values: oracleReturn }; }; // the rest of your NoirJS code From 93f32d0a655ec9bfbf960d12788cd5118c4105f3 Mon Sep 17 00:00:00 2001 From: thunkar Date: Fri, 14 Jun 2024 11:07:29 +0000 Subject: [PATCH 45/75] cleanup --- yarn-project/simulator/src/client/execution_note_cache.ts | 4 ---- 1 file changed, 4 deletions(-) diff --git a/yarn-project/simulator/src/client/execution_note_cache.ts b/yarn-project/simulator/src/client/execution_note_cache.ts index e74123138e6..3615b012c99 100644 --- a/yarn-project/simulator/src/client/execution_note_cache.ts +++ b/yarn-project/simulator/src/client/execution_note_cache.ts @@ -54,7 +54,6 @@ export class ExecutionNoteCache { let nullifiedNoteHashCounter: number | undefined = undefined; // Find and remove the matching new note and log(s) if the emitted innerNoteHash is not empty. if (!innerNoteHash.equals(Fr.ZERO)) { - console.log('DELETING A NOTE'); const notes = this.newNotes.get(contractAddress.toBigInt()) ?? []; const noteIndexToRemove = notes.findIndex(n => n.note.innerNoteHash.equals(innerNoteHash)); if (noteIndexToRemove === -1) { @@ -63,10 +62,7 @@ export class ExecutionNoteCache { const note = notes.splice(noteIndexToRemove, 1)[0]; nullifiedNoteHashCounter = note.counter; this.newNotes.set(contractAddress.toBigInt(), notes); - } else { - console.log('NOT DELETING A NOTE'); } - return nullifiedNoteHashCounter; } From 19123bd39e67046a579c273a32c35fceacb7675a Mon Sep 17 00:00:00 2001 From: thunkar Date: Fri, 14 Jun 2024 11:20:22 +0000 Subject: [PATCH 46/75] formatting --- .../aztec-nr/aztec/src/test/helpers/types.nr | 4 ++-- yarn-project/txe/tsconfig.json | 14 ++++---------- 2 files changed, 6 insertions(+), 12 deletions(-) diff --git a/noir-projects/aztec-nr/aztec/src/test/helpers/types.nr b/noir-projects/aztec-nr/aztec/src/test/helpers/types.nr index 0274a5c15ea..7baec3523d8 100644 --- a/noir-projects/aztec-nr/aztec/src/test/helpers/types.nr +++ b/noir-projects/aztec-nr/aztec/src/test/helpers/types.nr @@ -38,7 +38,7 @@ impl Deployer { let _result = original_fn(inputs); cheatcodes::set_contract_address(original_contract_address); - cheatcodes::set_msg_sender(original_msg_sender); + cheatcodes::set_msg_sender(original_msg_sender); address } @@ -65,7 +65,7 @@ impl Deployer { let _result: T = original_fn(inputs); cheatcodes::set_contract_address(original_contract_address); - cheatcodes::set_msg_sender(original_msg_sender); + cheatcodes::set_msg_sender(original_msg_sender); address } diff --git a/yarn-project/txe/tsconfig.json b/yarn-project/txe/tsconfig.json index 1db338e8540..a57cb2cd994 100644 --- a/yarn-project/txe/tsconfig.json +++ b/yarn-project/txe/tsconfig.json @@ -7,10 +7,10 @@ }, "references": [ { - "path": "../bb-prover" + "path": "../archiver" }, { - "path": "../builder" + "path": "../aztec.js" }, { "path": "../circuit-types" @@ -18,9 +18,6 @@ { "path": "../circuits.js" }, - { - "path": "../ethereum" - }, { "path": "../foundation" }, @@ -31,10 +28,7 @@ "path": "../kv-store" }, { - "path": "../noir-protocol-circuits-types" - }, - { - "path": "../protocol-contracts" + "path": "../pxe" }, { "path": "../simulator" @@ -43,7 +37,7 @@ "path": "../types" }, { - "path": "../noir-contracts.js" + "path": "../world-state" } ], "include": ["src"] From 35fe257127e6fe3fa4924a9a65c70a499a2624fe Mon Sep 17 00:00:00 2001 From: thunkar Date: Fri, 14 Jun 2024 11:31:17 +0000 Subject: [PATCH 47/75] better comment --- .../noir-contracts/contracts/token_contract/src/main.nr | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/noir-projects/noir-contracts/contracts/token_contract/src/main.nr b/noir-projects/noir-contracts/contracts/token_contract/src/main.nr index 0d957144885..fca6ed10b8f 100644 --- a/noir-projects/noir-contracts/contracts/token_contract/src/main.nr +++ b/noir-projects/noir-contracts/contracts/token_contract/src/main.nr @@ -442,7 +442,8 @@ contract Token { let redeem_shield_call_interface = Token::at(token_contract_address).redeem_shield(owner, mint_amount, secret); env.call_private_void(redeem_shield_call_interface); - // Not really sure why this is needed? + // Not really sure why this is needed? Nullifier inclusion in contract initializer fails otherwise. + // If it were to fail, it should do it at line 443, investigation required env.advance_block_by(1); // Transfer tokens From cca4e8c90700f8476e86784df4b74ec1296175cc Mon Sep 17 00:00:00 2001 From: thunkar Date: Mon, 17 Jun 2024 11:18:58 +0000 Subject: [PATCH 48/75] test ci --- .github/workflows/ci.yml | 2 +- noir-projects/Dockerfile | 13 +++++++++++-- noir-projects/Earthfile | 6 ++++++ 3 files changed, 18 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 671ce7a2acb..196a9efb59a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -418,7 +418,7 @@ jobs: run: earthly-ci --no-output ./noir+packages-test noir-projects: - needs: [setup, changes] + needs: [setup, changes, build] runs-on: ${{ github.event.pull_request.user.login || github.actor }}-x86 if: ${{ needs.changes.outputs.barretenberg == 'true' || needs.changes.outputs.noir == 'true' || needs.changes.outputs.noir-projects == 'true' }} steps: diff --git a/noir-projects/Dockerfile b/noir-projects/Dockerfile index ab4c6270e97..4404a9e5acc 100644 --- a/noir-projects/Dockerfile +++ b/noir-projects/Dockerfile @@ -1,5 +1,6 @@ FROM aztecprotocol/noir as noir FROM aztecprotocol/avm-transpiler as transpiler +FROM aztecprotocol/yarn-project AS yarn-project FROM ubuntu:lunar AS builder RUN apt-get update && apt-get install -y parallel nodejs npm @@ -13,13 +14,21 @@ ENV PATH="/usr/src/avm-transpiler/target/release:${PATH}" # Copy in noir projects WORKDIR /usr/src/noir-projects COPY . . + +# Launch TXE +WORKDIR /usr/src/yarn-project/txe +RUN yarn start & echo $! > /tmp/txe.pid + # Build WORKDIR /usr/src/noir-projects/noir-contracts -RUN ./bootstrap.sh && nargo test --silence-warnings +RUN ./bootstrap.sh && nargo test --silence-warnings --oracle-resolver http://localhost:8080 WORKDIR /usr/src/noir-projects/noir-protocol-circuits RUN ./bootstrap.sh && nargo test --silence-warnings WORKDIR /usr/src/noir-projects/aztec-nr -RUN nargo test --silence-warnings +RUN nargo test --silence-warnings --oracle-resolver http://localhost:8080 + +# Kill TXE +RUN kill $(cat /tmp/txe.pid) FROM scratch COPY --from=builder /usr/src/noir-projects /usr/src/noir-projects \ No newline at end of file diff --git a/noir-projects/Earthfile b/noir-projects/Earthfile index 2a7c32ab568..ad06dddc45c 100644 --- a/noir-projects/Earthfile +++ b/noir-projects/Earthfile @@ -42,9 +42,15 @@ build: test: FROM +build + # Install and start TXE + COPY ../yarn-project/+build/usr/src/yarn-project /usr/src/yarn-project + + RUN cd /usr/src/yarn-project/txe && yarn start & echo $! > /tmp/txe.pid RUN cd noir-protocol-circuits && nargo test --silence-warnings RUN cd aztec-nr && nargo test --silence-warnings RUN cd noir-contracts && nargo test --silence-warnings + # Kill TXE + RUN kill $(cat /tmp/txe.pid) format: FROM +build From f564d92f4ff8bd7d666771b7464ac0e0f0e0cd2e Mon Sep 17 00:00:00 2001 From: thunkar Date: Mon, 17 Jun 2024 14:24:40 +0000 Subject: [PATCH 49/75] working CI --- noir-projects/Earthfile | 29 +++++++++++++++++++---------- yarn-project/Earthfile | 13 +++++++++++++ yarn-project/txe/package.json | 1 + yarn-project/txe/tsconfig.json | 3 +++ yarn-project/yarn.lock | 1 + 5 files changed, 37 insertions(+), 10 deletions(-) diff --git a/noir-projects/Earthfile b/noir-projects/Earthfile index ad06dddc45c..6ca6b1e296e 100644 --- a/noir-projects/Earthfile +++ b/noir-projects/Earthfile @@ -41,16 +41,25 @@ build: SAVE ARTIFACT noir-protocol-circuits test: - FROM +build - # Install and start TXE - COPY ../yarn-project/+build/usr/src/yarn-project /usr/src/yarn-project - - RUN cd /usr/src/yarn-project/txe && yarn start & echo $! > /tmp/txe.pid - RUN cd noir-protocol-circuits && nargo test --silence-warnings - RUN cd aztec-nr && nargo test --silence-warnings - RUN cd noir-contracts && nargo test --silence-warnings - # Kill TXE - RUN kill $(cat /tmp/txe.pid) + FROM ../yarn-project/+txe + + # Install nargo + COPY ../noir/+nargo/nargo /usr/bin/nargo + + COPY +build/. /usr/src/noir-projects + + RUN cd /usr/src/noir-projects/noir-protocol-circuits && nargo test --silence-warnings + RUN cd /usr/src/yarn-project/txe && yarn start & echo $! > /tmp/txe.pid && \ + # Wait for TXE to initialize + sleep 5 && \ + cd /usr/src/noir-projects/aztec-nr && nargo test --silence-warnings --oracle-resolver http://localhost:8080 && \ + kill $(cat /tmp/txe.pid) + + RUN cd /usr/src/yarn-project/txe && yarn start & echo $! > /tmp/txe.pid && \ + # Wait for TXE to initialize + sleep 5 && \ + cd /usr/src/noir-projects/noir-contracts && nargo test --silence-warnings --oracle-resolver http://localhost:8080 && \ + kill $(cat /tmp/txe.pid) format: FROM +build diff --git a/yarn-project/Earthfile b/yarn-project/Earthfile index 14b724a28b5..f39379e20a1 100644 --- a/yarn-project/Earthfile +++ b/yarn-project/Earthfile @@ -115,6 +115,19 @@ rollup-verifier-contract: RUN --entrypoint write-contract -c RootRollupArtifact -n UltraVerifier.sol SAVE ARTIFACT /usr/src/bb /usr/src/bb +txe: + FROM +build + RUN yarn workspaces focus @aztec/txe --production && yarn cache clean + # Remove a bunch of stuff that we don't need that takes up space. + RUN rm -rf \ + ../noir-projects \ + ../l1-contracts \ + ../barretenberg/ts/src \ + ../barretenberg/ts/dest/node-cjs \ + ../barretenberg/ts/dest/browser \ + **/artifacts + SAVE ARTIFACT /usr/src /usr/src + aztec-prod: FROM +build RUN yarn workspaces focus @aztec/aztec @aztec/builder --production && yarn cache clean diff --git a/yarn-project/txe/package.json b/yarn-project/txe/package.json index c5548db696d..5658cd3b454 100644 --- a/yarn-project/txe/package.json +++ b/yarn-project/txe/package.json @@ -55,6 +55,7 @@ "@aztec/foundation": "workspace:^", "@aztec/key-store": "workspace:^", "@aztec/kv-store": "workspace:^", + "@aztec/noir-contracts.js": "workspace:^", "@aztec/pxe": "workspace:^", "@aztec/simulator": "workspace:^", "@aztec/types": "workspace:^", diff --git a/yarn-project/txe/tsconfig.json b/yarn-project/txe/tsconfig.json index a57cb2cd994..7db2bf79778 100644 --- a/yarn-project/txe/tsconfig.json +++ b/yarn-project/txe/tsconfig.json @@ -27,6 +27,9 @@ { "path": "../kv-store" }, + { + "path": "../noir-contracts.js" + }, { "path": "../pxe" }, diff --git a/yarn-project/yarn.lock b/yarn-project/yarn.lock index c09a315a1b0..cc328c7abd1 100644 --- a/yarn-project/yarn.lock +++ b/yarn-project/yarn.lock @@ -944,6 +944,7 @@ __metadata: "@aztec/foundation": "workspace:^" "@aztec/key-store": "workspace:^" "@aztec/kv-store": "workspace:^" + "@aztec/noir-contracts.js": "workspace:^" "@aztec/pxe": "workspace:^" "@aztec/simulator": "workspace:^" "@aztec/types": "workspace:^" From 083b1f86dd684270496dc1bf3e6fb72912c1134e Mon Sep 17 00:00:00 2001 From: thunkar Date: Mon, 17 Jun 2024 14:32:22 +0000 Subject: [PATCH 50/75] better dockerfile --- noir-projects/Dockerfile | 26 +++++++++++++++----------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/noir-projects/Dockerfile b/noir-projects/Dockerfile index 4404a9e5acc..c4eaa46b39f 100644 --- a/noir-projects/Dockerfile +++ b/noir-projects/Dockerfile @@ -15,20 +15,24 @@ ENV PATH="/usr/src/avm-transpiler/target/release:${PATH}" WORKDIR /usr/src/noir-projects COPY . . -# Launch TXE -WORKDIR /usr/src/yarn-project/txe -RUN yarn start & echo $! > /tmp/txe.pid # Build -WORKDIR /usr/src/noir-projects/noir-contracts -RUN ./bootstrap.sh && nargo test --silence-warnings --oracle-resolver http://localhost:8080 -WORKDIR /usr/src/noir-projects/noir-protocol-circuits -RUN ./bootstrap.sh && nargo test --silence-warnings -WORKDIR /usr/src/noir-projects/aztec-nr -RUN nargo test --silence-warnings --oracle-resolver http://localhost:8080 +WORKDIR /usr/src/noir-projects +RUN cd noir-protocol-circuits && ./bootstrap.sh && nargo test --silence-warnings + +RUN cd /usr/src/yarn-project/txe && yarn start & echo $! > /tmp/txe.pid && \ + # Wait for TXE to initialize + sleep 5 && \ + cd /usr/src/noir-projects/noir-contracts && \ + ./bootstrap.sh && nargo test --silence-warnings --oracle-resolver http://localhost:8080 && \ + kill $(cat /tmp/txe.pid) + +RUN cd /usr/src/yarn-project/txe && yarn start & echo $! > /tmp/txe.pid && \ + # Wait for TXE to initialize + sleep 5 && \ + cd /usr/src/noir-projects/aztec-nr && \ + nargo test --silence-warnings --oracle-resolver http://localhost:8080 -# Kill TXE -RUN kill $(cat /tmp/txe.pid) FROM scratch COPY --from=builder /usr/src/noir-projects /usr/src/noir-projects \ No newline at end of file From d214093c1dd5a9632eddd47e06d3ef34f6bf9d77 Mon Sep 17 00:00:00 2001 From: thunkar Date: Tue, 18 Jun 2024 10:37:36 +0000 Subject: [PATCH 51/75] wip --- .../src/test/helpers/test_environment.nr | 3 +- .../contracts/token_contract/src/main.nr | 82 ++++++------------- .../contracts/token_contract/src/test.nr | 63 ++++++++++++++ .../src/hir/resolution/import.rs | 6 +- .../txe/src/txe_service/txe_service.ts | 11 ++- 5 files changed, 100 insertions(+), 65 deletions(-) create mode 100644 noir-projects/noir-contracts/contracts/token_contract/src/test.nr diff --git a/noir-projects/aztec-nr/aztec/src/test/helpers/test_environment.nr b/noir-projects/aztec-nr/aztec/src/test/helpers/test_environment.nr index 142f6fd58f8..018b7dd4c92 100644 --- a/noir-projects/aztec-nr/aztec/src/test/helpers/test_environment.nr +++ b/noir-projects/aztec-nr/aztec/src/test/helpers/test_environment.nr @@ -85,7 +85,6 @@ impl TestEnvironment { ); cheatcodes::advance_blocks(1); let test_account = cheatcodes::add_account(secret, PartialAddress::from_field(address.to_field())); - let address = test_account.address; let keys = test_account.keys; keys::store_master_key(NULLIFIER_INDEX, address, keys.npk_m); @@ -93,7 +92,7 @@ impl TestEnvironment { keys::store_master_key(OUTGOING_INDEX, address, keys.ovpk_m); keys::store_master_key(TAGGING_INDEX, address, keys.tpk_m); - test_account.address + address } fn deploy(self, path: str) -> Deployer { diff --git a/noir-projects/noir-contracts/contracts/token_contract/src/main.nr b/noir-projects/noir-contracts/contracts/token_contract/src/main.nr index fca6ed10b8f..3115ba036e3 100644 --- a/noir-projects/noir-contracts/contracts/token_contract/src/main.nr +++ b/noir-projects/noir-contracts/contracts/token_contract/src/main.nr @@ -1,6 +1,7 @@ // docs:start:token_all // docs:start:imports mod types; +mod test; // Minimal token implementation that supports `AuthWit` accounts. // The auth message follows a similar pattern to the cross-chain message and includes a designated caller. @@ -396,74 +397,37 @@ contract Token { } // docs:end:balance_of_private - use dep::aztec::test::{helpers::{cheatcodes, test_environment::TestEnvironment}}; - use dep::aztec::protocol_types::storage::map::derive_storage_slot_in_map; - use dep::aztec::note::note_getter::{MAX_NOTES_PER_PAGE, view_notes}; - use dep::aztec::note::note_viewer_options::NoteViewerOptions; + use crate::test; + use dep::aztec::test::helpers::cheatcodes; #[test] fn test_private_transfer() { - // Setup env, generate keys - let mut env = TestEnvironment::new(); - let owner = env.create_account(); - let recipient = env.create_account(); - let mint_amount = 10000; - - // Start the test in the account contract address - cheatcodes::set_contract_address(owner); - - // Deploy token contract - let initializer_call_interface = Token::interface().constructor( - owner, - "TestToken0000000000000000000000", - "TT00000000000000000000000000000", - 18 - ); - let token_contract_address = env.deploy("@aztec/noir-contracts.js/Token").with_public_initializer(initializer_call_interface); - env.advance_block_by(1); - - // Mint some tokens - let secret = 1; - let secret_hash = compute_secret_hash(secret); - let mint_private_call_interface = Token::at(token_contract_address).mint_private(mint_amount, secret_hash); - env.call_public(mint_private_call_interface); - - // Time travel so we can read keys from the registry - env.advance_block_by(6); - - // Store a note in the cache so we can redeem it - env.store_note_in_cache( - &mut TransparentNote::new(mint_amount, secret_hash), - Token::storage().pending_shields.slot, - token_contract_address - ); - - // Redeem our shielded tokens - let redeem_shield_call_interface = Token::at(token_contract_address).redeem_shield(owner, mint_amount, secret); - env.call_private_void(redeem_shield_call_interface); + // Setup + let (env, token_contract_address, owner, recipient, mint_amount) = test::setup_and_mint(); + // Transfer tokens + let transfer_amount = 1000; + let private_transfer_call_interface = Token::at(token_contract_address).transfer(recipient, transfer_amount); + env.call_private_void(private_transfer_call_interface); - // Not really sure why this is needed? Nullifier inclusion in contract initializer fails otherwise. - // If it were to fail, it should do it at line 443, investigation required - env.advance_block_by(1); + // Check balances + test::check_balance(token_contract_address, owner, mint_amount - transfer_amount); + test::check_balance(token_contract_address, recipient, transfer_amount); + } + #[test] + fn test_private_transfer_from() { + // Setup + let (env, token_contract_address, owner, recipient, mint_amount) = test::setup_and_mint(); + // Impersonate recipient to test authwits + cheatcodes::set_contract_address(recipient); // Transfer tokens let transfer_amount = 1000; - let private_token_transfer_call_interface = Token::at(token_contract_address).transfer(recipient, transfer_amount); - env.call_private_void(private_token_transfer_call_interface); + let private_transfer_from_call_interface = Token::at(token_contract_address).transfer_from(owner, recipient, transfer_amount, 1); + env.call_private_void(private_transfer_from_call_interface); // Check balances - cheatcodes::set_contract_address(token_contract_address); - - let balances_slot = Token::storage().balances.slot; - let recipient_slot = derive_storage_slot_in_map(balances_slot, recipient); - let mut options = NoteViewerOptions::new(); - let opt_notes: [Option; MAX_NOTES_PER_PAGE] = view_notes(recipient_slot, options); - assert(opt_notes[0].unwrap().amount.to_field() == transfer_amount); - - let owner_slot = derive_storage_slot_in_map(balances_slot, owner); - let mut options = NoteViewerOptions::new(); - let opt_notes: [Option; MAX_NOTES_PER_PAGE] = view_notes(owner_slot, options); - assert(opt_notes[0].unwrap().amount.to_field() == mint_amount - transfer_amount); + test::check_balance(token_contract_address, owner, mint_amount - transfer_amount); + test::check_balance(token_contract_address, recipient, transfer_amount); } } // docs:end:token_all \ No newline at end of file diff --git a/noir-projects/noir-contracts/contracts/token_contract/src/test.nr b/noir-projects/noir-contracts/contracts/token_contract/src/test.nr new file mode 100644 index 00000000000..328fe09cd6d --- /dev/null +++ b/noir-projects/noir-contracts/contracts/token_contract/src/test.nr @@ -0,0 +1,63 @@ +use dep::aztec::{hash::compute_secret_hash, prelude::AztecAddress}; +use dep::aztec::test::helpers::{cheatcodes, test_environment::TestEnvironment}; +use dep::aztec::protocol_types::storage::map::derive_storage_slot_in_map; +use dep::aztec::note::note_getter::{MAX_NOTES_PER_PAGE, view_notes}; +use dep::aztec::note::note_viewer_options::NoteViewerOptions; +use crate::{types::{token_note::TokenNote, transparent_note::TransparentNote}, Token}; + +pub fn setup_and_mint() -> (TestEnvironment, AztecAddress, AztecAddress, AztecAddress, Field) { + // Setup env, generate keys + let mut env = TestEnvironment::new(); + let owner = env.create_account_contract(1); + let recipient = env.create_account_contract(2); + let mint_amount = 10000; + + // Start the test in the account contract address + cheatcodes::set_contract_address(owner); + + // Deploy token contract + let initializer_call_interface = Token::interface().constructor( + owner, + "TestToken0000000000000000000000", + "TT00000000000000000000000000000", + 18 + ); + let token_contract_address = env.deploy("@aztec/noir-contracts.js/Token").with_public_initializer(initializer_call_interface); + env.advance_block_by(1); + + // Mint some tokens + let secret = 1; + let secret_hash = compute_secret_hash(secret); + let mint_private_call_interface = Token::at(token_contract_address).mint_private(mint_amount, secret_hash); + env.call_public(mint_private_call_interface); + + // Time travel so we can read keys from the registry + env.advance_block_by(6); + + // Store a note in the cache so we can redeem it + env.store_note_in_cache( + &mut TransparentNote::new(mint_amount, secret_hash), + Token::storage().pending_shields.slot, + token_contract_address + ); + + // Redeem our shielded tokens + let redeem_shield_call_interface = Token::at(token_contract_address).redeem_shield(owner, mint_amount, secret); + env.call_private_void(redeem_shield_call_interface); + + // Not really sure why this is needed? Nullifier inclusion in contract initializer fails otherwise. + // If it were to fail, it should do it at line 443, investigation required + env.advance_block_by(1); + + (env, token_contract_address, owner, recipient, mint_amount) +} + +pub fn check_balance(token_contract_address: AztecAddress, address: AztecAddress, address_amount: Field) { + cheatcodes::set_contract_address(token_contract_address); + + let balances_slot = Token::storage().balances.slot; + let address_slot = derive_storage_slot_in_map(balances_slot, address); + let mut options = NoteViewerOptions::new(); + let opt_notes: [Option; MAX_NOTES_PER_PAGE] = view_notes(address_slot, options); + assert(opt_notes[0].unwrap().amount.to_field() == address_amount); +} diff --git a/noir/noir-repo/compiler/noirc_frontend/src/hir/resolution/import.rs b/noir/noir-repo/compiler/noirc_frontend/src/hir/resolution/import.rs index 343113836ed..527959dd2af 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/hir/resolution/import.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/hir/resolution/import.rs @@ -268,9 +268,9 @@ fn resolve_name_in_module( } // Check if it is a contract and we're calling from a non-contract context - if current_mod.is_contract && !allow_contracts { - return Err(PathResolutionError::ExternalContractUsed(current_segment.clone())); - } + // if current_mod.is_contract && !allow_contracts { + // return Err(PathResolutionError::ExternalContractUsed(current_segment.clone())); + // } current_ns = found_ns; } diff --git a/yarn-project/txe/src/txe_service/txe_service.ts b/yarn-project/txe/src/txe_service/txe_service.ts index 6b53c0de1eb..0b52f6151f0 100644 --- a/yarn-project/txe/src/txe_service/txe_service.ts +++ b/yarn-project/txe/src/txe_service/txe_service.ts @@ -16,6 +16,7 @@ import { KeyStore } from '@aztec/key-store'; import { type AztecKVStore } from '@aztec/kv-store'; import { openTmpStore } from '@aztec/kv-store/utils'; import { ExecutionNoteCache, PackedValuesCache, type TypedOracle } from '@aztec/simulator'; +import { SerializableContractInstance } from '@aztec/types/contracts'; import { MerkleTrees } from '@aztec/world-state'; import { TXE } from '../oracle/txe_oracle.js'; @@ -131,7 +132,15 @@ export class TXEService { this.logger.debug(`Deployed ${contractClass.artifact.name} at ${instance.address}`); await (this.typedOracle as TXE).addContractInstance(instance); await (this.typedOracle as TXE).addContractArtifact(contractClass.artifact); - return toForeignCallResult([toSingle(instance.address)]); + return toForeignCallResult( + toArray([ + instance.salt, + instance.deployer, + instance.contractClassId, + instance.initializationHash, + instance.publicKeysHash, + ]), + ); } async directStorageWrite( From 0a1c2e1f2eb35123d3e8a2a934cf9023f1b3df1f Mon Sep 17 00:00:00 2001 From: thunkar Date: Tue, 18 Jun 2024 14:10:43 +0000 Subject: [PATCH 52/75] fixed account contract deployments --- .../aztec/src/test/helpers/cheatcodes.nr | 23 ++++------ .../src/test/helpers/test_environment.nr | 28 +++++++++--- .../aztec-nr/aztec/src/test/helpers/types.nr | 24 +++++------ .../contracts/token_contract/src/main.nr | 1 - .../contracts/token_contract/src/test.nr | 3 +- yarn-project/txe/src/bin/index.ts | 7 +-- yarn-project/txe/src/oracle/txe_oracle.ts | 43 ++++++++++++++++--- .../txe/src/txe_service/txe_service.ts | 22 +++++++--- 8 files changed, 101 insertions(+), 50 deletions(-) diff --git a/noir-projects/aztec-nr/aztec/src/test/helpers/cheatcodes.nr b/noir-projects/aztec-nr/aztec/src/test/helpers/cheatcodes.nr index 014757cf9b0..ca53ed9c1b7 100644 --- a/noir-projects/aztec-nr/aztec/src/test/helpers/cheatcodes.nr +++ b/noir-projects/aztec-nr/aztec/src/test/helpers/cheatcodes.nr @@ -1,4 +1,7 @@ -use dep::protocol_types::{abis::function_selector::FunctionSelector, address::{AztecAddress, PartialAddress}}; +use dep::protocol_types::{ + abis::function_selector::FunctionSelector, address::{AztecAddress, PartialAddress}, + constants::CONTRACT_INSTANCE_LENGTH, contract_instance::ContractInstance +}; use crate::context::inputs::{PublicContextInputs, PrivateContextInputs}; use crate::test::helpers::types::{Deployer, TestAccount}; use crate::keys::public_keys::PublicKeys; @@ -31,20 +34,12 @@ unconstrained pub fn get_public_context_inputs() -> PublicContextInputs { oracle_get_public_context_inputs() } -unconstrained pub fn deploy( - path: str, - initializer: str, - args: [Field], - public_keys_hash: Field -) -> AztecAddress { - oracle_deploy(path, initializer, args, public_keys_hash) +unconstrained pub fn deploy(path: str, initializer: str, args: [Field], public_keys_hash: Field) -> ContractInstance { + let instance_fields = oracle_deploy(path, initializer, args, public_keys_hash); + ContractInstance::deserialize(instance_fields) } -unconstrained pub fn direct_storage_write( - contract_address: AztecAddress, - storage_slot: Field, - fields: [Field; N] -) { +unconstrained pub fn direct_storage_write(contract_address: AztecAddress, storage_slot: Field, fields: [Field; N]) { let _hash = direct_storage_write_oracle(contract_address, storage_slot, fields); } @@ -99,7 +94,7 @@ fn oracle_deploy( initializer: str, args: [Field], public_keys_hash: Field -) -> AztecAddress {} +) -> [Field; CONTRACT_INSTANCE_LENGTH] {} #[oracle(directStorageWrite)] fn direct_storage_write_oracle( diff --git a/noir-projects/aztec-nr/aztec/src/test/helpers/test_environment.nr b/noir-projects/aztec-nr/aztec/src/test/helpers/test_environment.nr index 018b7dd4c92..afbb6caf22c 100644 --- a/noir-projects/aztec-nr/aztec/src/test/helpers/test_environment.nr +++ b/noir-projects/aztec-nr/aztec/src/test/helpers/test_environment.nr @@ -10,7 +10,7 @@ use crate::context::{packed_returns::PackedReturns, call_interfaces::CallInterfa use crate::context::{PrivateContext, PublicContext, PrivateVoidCallInterface}; use crate::test::helpers::{cheatcodes, types::{Deployer, TestAccount}, keys}; use crate::keys::constants::{NULLIFIER_INDEX, INCOMING_INDEX, OUTGOING_INDEX, TAGGING_INDEX}; -use crate::hash::hash_args; +use crate::hash::{hash_args, hash_args_array}; use crate::note::{ note_header::NoteHeader, note_interface::NoteInterface, @@ -74,24 +74,40 @@ impl TestEnvironment { test_account.address } - fn create_account_contract(self, secret: Field) -> AztecAddress { + fn create_account_contract(&mut self, secret: Field) -> AztecAddress { let public_keys = cheatcodes::derive_keys(secret); - let args = &[public_keys.ivpk_m.x, public_keys.ivpk_m.y]; - let address = cheatcodes::deploy( + let args = [public_keys.ivpk_m.x, public_keys.ivpk_m.y]; + let instance = cheatcodes::deploy( "@aztec/noir-contracts.js/SchnorrAccount", "constructor", - args, + args.as_slice(), public_keys.hash().to_field() ); cheatcodes::advance_blocks(1); - let test_account = cheatcodes::add_account(secret, PartialAddress::from_field(address.to_field())); + let test_account = cheatcodes::add_account( + secret, + PartialAddress::compute( + instance.contract_class_id, + instance.salt, + instance.initialization_hash, + instance.deployer + ) + ); let keys = test_account.keys; + let address = instance.to_address(); + keys::store_master_key(NULLIFIER_INDEX, address, keys.npk_m); keys::store_master_key(INCOMING_INDEX, address, keys.ivpk_m); keys::store_master_key(OUTGOING_INDEX, address, keys.ovpk_m); keys::store_master_key(TAGGING_INDEX, address, keys.tpk_m); + let selector = FunctionSelector::from_signature("constructor(Field,Field)"); + + let mut context = self.private_at(cheatcodes::get_block_number()); + + let _ = context.call_private_function(address, selector, args); + address } diff --git a/noir-projects/aztec-nr/aztec/src/test/helpers/types.nr b/noir-projects/aztec-nr/aztec/src/test/helpers/types.nr index 7baec3523d8..b277d3f41a6 100644 --- a/noir-projects/aztec-nr/aztec/src/test/helpers/types.nr +++ b/noir-projects/aztec-nr/aztec/src/test/helpers/types.nr @@ -1,6 +1,7 @@ use dep::protocol_types::{ traits::{Deserialize, Serialize}, address::AztecAddress, - abis::{function_selector::FunctionSelector, private_circuit_public_inputs::PrivateCircuitPublicInputs} + abis::{function_selector::FunctionSelector, private_circuit_public_inputs::PrivateCircuitPublicInputs}, + contract_instance::ContractInstance }; use crate::context::inputs::{PublicContextInputs, PrivateContextInputs}; @@ -18,8 +19,8 @@ impl Deployer { pub fn with_private_initializer( self, call_interface: C - ) -> AztecAddress where C: CallInterface { - let address = cheatcodes::deploy( + ) -> ContractInstance where C: CallInterface { + let instance = cheatcodes::deploy( self.path, call_interface.get_name(), call_interface.get_args(), @@ -31,7 +32,7 @@ impl Deployer { let original_msg_sender = cheatcodes::get_msg_sender(); let original_contract_address = cheatcodes::get_contract_address(); - cheatcodes::set_contract_address(address); + cheatcodes::set_contract_address(instance.to_address()); cheatcodes::set_msg_sender(original_contract_address); let mut inputs = cheatcodes::get_private_context_inputs(block_number - 1); inputs.call_context.function_selector = call_interface.get_selector(); @@ -39,14 +40,14 @@ impl Deployer { cheatcodes::set_contract_address(original_contract_address); cheatcodes::set_msg_sender(original_msg_sender); - address + instance } pub fn with_public_initializer( self, call_interface: C - ) -> AztecAddress where C: CallInterface { - let address = cheatcodes::deploy( + ) -> ContractInstance where C: CallInterface { + let instance = cheatcodes::deploy( self.path, call_interface.get_name(), call_interface.get_args(), @@ -57,7 +58,7 @@ impl Deployer { let original_msg_sender = cheatcodes::get_msg_sender(); let original_contract_address = cheatcodes::get_contract_address(); - cheatcodes::set_contract_address(address); + cheatcodes::set_contract_address(instance.to_address()); cheatcodes::set_msg_sender(original_contract_address); let mut inputs = cheatcodes::get_public_context_inputs(); inputs.selector = call_interface.get_selector().to_field(); @@ -66,12 +67,11 @@ impl Deployer { cheatcodes::set_contract_address(original_contract_address); cheatcodes::set_msg_sender(original_msg_sender); - address + instance } - pub fn without_initializer(self) -> AztecAddress { - let address = cheatcodes::deploy(self.path, "", &[], self.public_keys_hash); - address + pub fn without_initializer(self) -> ContractInstance { + cheatcodes::deploy(self.path, "", &[], self.public_keys_hash) } } diff --git a/noir-projects/noir-contracts/contracts/token_contract/src/main.nr b/noir-projects/noir-contracts/contracts/token_contract/src/main.nr index 3115ba036e3..fe0cedebbc7 100644 --- a/noir-projects/noir-contracts/contracts/token_contract/src/main.nr +++ b/noir-projects/noir-contracts/contracts/token_contract/src/main.nr @@ -424,7 +424,6 @@ contract Token { let transfer_amount = 1000; let private_transfer_from_call_interface = Token::at(token_contract_address).transfer_from(owner, recipient, transfer_amount, 1); env.call_private_void(private_transfer_from_call_interface); - // Check balances test::check_balance(token_contract_address, owner, mint_amount - transfer_amount); test::check_balance(token_contract_address, recipient, transfer_amount); diff --git a/noir-projects/noir-contracts/contracts/token_contract/src/test.nr b/noir-projects/noir-contracts/contracts/token_contract/src/test.nr index 328fe09cd6d..de480c05c9f 100644 --- a/noir-projects/noir-contracts/contracts/token_contract/src/test.nr +++ b/noir-projects/noir-contracts/contracts/token_contract/src/test.nr @@ -22,7 +22,8 @@ pub fn setup_and_mint() -> (TestEnvironment, AztecAddress, AztecAddress, AztecAd "TT00000000000000000000000000000", 18 ); - let token_contract_address = env.deploy("@aztec/noir-contracts.js/Token").with_public_initializer(initializer_call_interface); + let token_contract = env.deploy("@aztec/noir-contracts.js/Token").with_public_initializer(initializer_call_interface); + let token_contract_address = token_contract.to_address(); env.advance_block_by(1); // Mint some tokens diff --git a/yarn-project/txe/src/bin/index.ts b/yarn-project/txe/src/bin/index.ts index 14934762159..5c5b41bc873 100644 --- a/yarn-project/txe/src/bin/index.ts +++ b/yarn-project/txe/src/bin/index.ts @@ -32,9 +32,7 @@ class TXEDispatcher { function: functionName, inputs, }: TXEForeignCallInput): Promise { - this.logger.debug( - `Calling ${functionName} with inputs: ${JSON.stringify(inputs, null, 2)} on session ${sessionId}`, - ); + this.logger.debug(`Calling ${functionName} on session ${sessionId}`); if (!TXESessions.has(sessionId) && functionName != 'reset') { this.logger.debug(`Creating new session ${sessionId}`); @@ -48,9 +46,6 @@ class TXEDispatcher { } else { const txeService = TXESessions.get(sessionId); const response = await (txeService as any)[functionName](...inputs); - this.logger.debug( - `${sessionId}:${functionName}(${JSON.stringify(inputs, null, 2)}) -> ${JSON.stringify(response, null, 2)}`, - ); return response; } } diff --git a/yarn-project/txe/src/oracle/txe_oracle.ts b/yarn-project/txe/src/oracle/txe_oracle.ts index 9cbb375f096..1677b32ba92 100644 --- a/yarn-project/txe/src/oracle/txe_oracle.ts +++ b/yarn-project/txe/src/oracle/txe_oracle.ts @@ -64,6 +64,7 @@ export class TXE implements TypedOracle { private sideEffectsCounter = 0; private contractAddress: AztecAddress; private msgSender: AztecAddress; + private functionSelector = FunctionSelector.fromField(new Fr(0)); private contractDataOracle: ContractDataOracle; @@ -90,6 +91,10 @@ export class TXE implements TypedOracle { this.msgSender = msgSender; } + setFunctionSelector(functionSelector: FunctionSelector) { + this.functionSelector = functionSelector; + } + getSideEffectsCounter() { return this.sideEffectsCounter; } @@ -137,6 +142,7 @@ export class TXE implements TypedOracle { inputs.callContext.storageContractAddress = this.contractAddress; inputs.callContext.sideEffectCounter = sideEffectsCounter; inputs.startSideEffectCounter = sideEffectsCounter; + inputs.callContext.functionSelector = this.functionSelector; return inputs; } @@ -283,8 +289,8 @@ export class TXE implements TypedOracle { return Promise.resolve(this.txeDatabase.getAccount(account)); } - getAuthWitness(_messageHash: Fr): Promise { - throw new Error('Method not implemented.'); + async getAuthWitness(messageHash: Fr): Promise { + return await this.txeDatabase.getAuthWitness(messageHash); } popCapsule(): Promise { @@ -446,7 +452,7 @@ export class TXE implements TypedOracle { } emitUnencryptedLog(_log: UnencryptedL2Log, _counter: number): void { - throw new Error('Method not implemented.'); + return; } emitContractClassUnencryptedLog(_log: UnencryptedL2Log, _counter: number): Fr { @@ -461,14 +467,20 @@ export class TXE implements TypedOracle { _isStaticCall: boolean, _isDelegateCall: boolean, ): Promise { - this.logger.debug( - `Calling private function ${targetContractAddress}:${functionSelector} from ${this.contractAddress}`, + this.logger.verbose( + `Executing external function ${targetContractAddress}:${functionSelector}(${await this.getDebugFunctionName( + targetContractAddress, + functionSelector, + )})`, ); + // Store and modify env const currentContractAddress = AztecAddress.fromField(this.contractAddress); const currentMessageSender = AztecAddress.fromField(this.msgSender); + const currentFunctionSelector = FunctionSelector.fromField(this.functionSelector.toField()); this.setMsgSender(this.contractAddress); this.setContractAddress(targetContractAddress); + this.setFunctionSelector(functionSelector); const artifact = await this.contractDataOracle.getFunctionArtifact(targetContractAddress, functionSelector); @@ -516,6 +528,7 @@ export class TXE implements TypedOracle { this.sideEffectsCounter += publicInputs.endSideEffectCounter.toNumber(); this.setContractAddress(currentContractAddress); this.setMsgSender(currentMessageSender); + this.setFunctionSelector(currentFunctionSelector); return callStackItem; } @@ -536,6 +549,26 @@ export class TXE implements TypedOracle { return toACVMWitness(0, fields); } + public async getDebugFunctionName(address: AztecAddress, selector: FunctionSelector): Promise { + const instance = await this.txeDatabase.getContractInstance(address); + if (!instance) { + return Promise.resolve(undefined); + } + const artifact = await this.txeDatabase.getContractArtifact(instance!.contractClassId); + if (!artifact) { + return Promise.resolve(undefined); + } + + const f = artifact.functions.find(f => + FunctionSelector.fromNameAndParameters(f.name, f.parameters).equals(selector), + ); + if (!f) { + return Promise.resolve(undefined); + } + + return Promise.resolve(`${artifact.name}:${f.name}`); + } + callPublicFunction( _targetContractAddress: AztecAddress, _functionSelector: FunctionSelector, diff --git a/yarn-project/txe/src/txe_service/txe_service.ts b/yarn-project/txe/src/txe_service/txe_service.ts index 0b52f6151f0..07ec8a717b7 100644 --- a/yarn-project/txe/src/txe_service/txe_service.ts +++ b/yarn-project/txe/src/txe_service/txe_service.ts @@ -16,7 +16,6 @@ import { KeyStore } from '@aztec/key-store'; import { type AztecKVStore } from '@aztec/kv-store'; import { openTmpStore } from '@aztec/kv-store/utils'; import { ExecutionNoteCache, PackedValuesCache, type TypedOracle } from '@aztec/simulator'; -import { SerializableContractInstance } from '@aztec/types/contracts'; import { MerkleTrees } from '@aztec/world-state'; import { TXE } from '../oracle/txe_oracle.js'; @@ -116,7 +115,10 @@ export class TXEService { .map(char => String.fromCharCode(char.toNumber())) .join(''); const decodedArgs = fromArray(args); - this.logger.debug(`Deploy ${pathStr} with ${initializerStr} and ${decodedArgs}`); + const publicKeysHashFr = fromSingle(publicKeysHash); + this.logger.debug( + `Deploy ${pathStr} with initializer ${initializerStr}(${decodedArgs}) and public keys hash ${publicKeysHashFr}`, + ); const contractModule = await import(pathStr); // Hacky way of getting the class, the name of the Artifact is always longer const contractClass = contractModule[Object.keys(contractModule).sort((a, b) => a.length - b.length)[0]]; @@ -124,7 +126,7 @@ export class TXEService { constructorArgs: decodedArgs, skipArgsDecoding: true, salt: Fr.ONE, - publicKeysHash: fromSingle(publicKeysHash), + publicKeysHash: publicKeysHashFr, constructorArtifact: initializerStr ? initializerStr : undefined, deployer: AztecAddress.ZERO, }); @@ -132,7 +134,7 @@ export class TXEService { this.logger.debug(`Deployed ${contractClass.artifact.name} at ${instance.address}`); await (this.typedOracle as TXE).addContractInstance(instance); await (this.typedOracle as TXE).addContractArtifact(contractClass.artifact); - return toForeignCallResult( + return toForeignCallResult([ toArray([ instance.salt, instance.deployer, @@ -140,7 +142,7 @@ export class TXEService { instance.initializationHash, instance.publicKeysHash, ]), - ); + ]); } async directStorageWrite( @@ -184,6 +186,7 @@ export class TXEService { const completeAddress = await keyStore.addAccount(fromSingle(secret), fromSingle(partialAddress)); const accountStore = (this.typedOracle as TXE).getTXEDatabase(); await accountStore.setAccount(completeAddress.address, completeAddress); + this.logger.debug(`Created account ${completeAddress.address}`); return toForeignCallResult([ toSingle(completeAddress.address), ...completeAddress.publicKeys.toFields().map(toSingle), @@ -528,4 +531,13 @@ export class TXEService { } return toForeignCallResult([toArray(witness.toFields())]); } + + async getAuthWitness(messageHash: ForeignCallSingle) { + const parsedMessageHash = fromSingle(messageHash); + const authWitness = await this.typedOracle.getAuthWitness(parsedMessageHash); + if (!authWitness) { + throw new Error(`Auth witness not found for message hash ${parsedMessageHash}.`); + } + return toForeignCallResult([toArray(authWitness)]); + } } From e711f2133d42fca1b61cfd75ced67152be95f440 Mon Sep 17 00:00:00 2001 From: thunkar Date: Tue, 18 Jun 2024 17:06:17 +0000 Subject: [PATCH 53/75] compatibility with new deployment --- .../noir-contracts/contracts/counter_contract/src/main.nr | 3 ++- .../noir-contracts/contracts/parent_contract/src/main.nr | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/noir-projects/noir-contracts/contracts/counter_contract/src/main.nr b/noir-projects/noir-contracts/contracts/counter_contract/src/main.nr index e296d211aab..c2d04854cbe 100644 --- a/noir-projects/noir-contracts/contracts/counter_contract/src/main.nr +++ b/noir-projects/noir-contracts/contracts/counter_contract/src/main.nr @@ -52,7 +52,8 @@ contract Counter { // Deploy contract and initialize let initializer = Counter::interface().initialize(5, owner, outgoing_viewer); - let contract_address = env.deploy("@aztec/noir-contracts.js/Counter").with_private_initializer(initializer); + let counter_contract = env.deploy("@aztec/noir-contracts.js/Counter").with_private_initializer(initializer); + let contract_address = counter_contract.to_address(); // Read the stored value in the note cheatcodes::set_contract_address(contract_address); diff --git a/noir-projects/noir-contracts/contracts/parent_contract/src/main.nr b/noir-projects/noir-contracts/contracts/parent_contract/src/main.nr index e6901b1a319..f3ea3966d61 100644 --- a/noir-projects/noir-contracts/contracts/parent_contract/src/main.nr +++ b/noir-projects/noir-contracts/contracts/parent_contract/src/main.nr @@ -257,7 +257,8 @@ contract Parent { let owner = env.create_account(); // Deploy child contract - let child_contract_address = env.deploy("@aztec/noir-contracts.js/Child").without_initializer(); + let child_contract = env.deploy("@aztec/noir-contracts.js/Child").without_initializer(); + let child_contract_address = child_contract.to_address(); cheatcodes::advance_blocks(1); // Set value in child through parent From 9a09e2b1874721f785b3545be10e5cd9f425ba6f Mon Sep 17 00:00:00 2001 From: thunkar Date: Wed, 19 Jun 2024 14:44:27 +0000 Subject: [PATCH 54/75] working private authwits --- noir-projects/aztec-nr/authwit/src/lib.nr | 1 + noir-projects/aztec-nr/authwit/src/test.nr | 22 +++++++++ .../aztec/src/test/helpers/cheatcodes.nr | 7 +++ .../contracts/token_contract/src/main.nr | 4 +- yarn-project/txe/src/oracle/txe_oracle.ts | 14 +++++- .../txe/src/txe_service/txe_service.ts | 46 +++++++++++-------- 6 files changed, 71 insertions(+), 23 deletions(-) create mode 100644 noir-projects/aztec-nr/authwit/src/test.nr diff --git a/noir-projects/aztec-nr/authwit/src/lib.nr b/noir-projects/aztec-nr/authwit/src/lib.nr index e56460fd701..29e0036748d 100644 --- a/noir-projects/aztec-nr/authwit/src/lib.nr +++ b/noir-projects/aztec-nr/authwit/src/lib.nr @@ -2,3 +2,4 @@ mod account; mod auth_witness; mod auth; mod entrypoint; +mod test; \ No newline at end of file diff --git a/noir-projects/aztec-nr/authwit/src/test.nr b/noir-projects/aztec-nr/authwit/src/test.nr new file mode 100644 index 00000000000..267373f1765 --- /dev/null +++ b/noir-projects/aztec-nr/authwit/src/test.nr @@ -0,0 +1,22 @@ +use dep::aztec::context::call_interfaces::CallInterface; +use dep::aztec::protocol_types::address::AztecAddress; +use dep::aztec::test::helpers::cheatcodes; +use dep::aztec::hash::hash_args; + +use crate::auth::{compute_inner_authwit_hash, compute_outer_authwit_hash}; + +pub fn add_authwit_from_call_interface( + on_behalf_of: AztecAddress, + caller: AztecAddress, + call_interface: C +) where C: CallInterface { + let target = call_interface.get_contract_address(); + let inputs = cheatcodes::get_private_context_inputs(cheatcodes::get_block_number()); + let chain_id = inputs.tx_context.chain_id; + let version = inputs.tx_context.version; + let args_hash = hash_args(call_interface.get_args()); + let selector = call_interface.get_selector(); + let inner_hash = compute_inner_authwit_hash([caller.to_field(), selector.to_field(), args_hash]); + let message_hash = compute_outer_authwit_hash(target, chain_id, version, inner_hash); + cheatcodes::add_authwit(on_behalf_of, message_hash); +} diff --git a/noir-projects/aztec-nr/aztec/src/test/helpers/cheatcodes.nr b/noir-projects/aztec-nr/aztec/src/test/helpers/cheatcodes.nr index ca53ed9c1b7..22b9aa64cce 100644 --- a/noir-projects/aztec-nr/aztec/src/test/helpers/cheatcodes.nr +++ b/noir-projects/aztec-nr/aztec/src/test/helpers/cheatcodes.nr @@ -67,6 +67,10 @@ unconstrained pub fn get_side_effects_counter() -> u32 { oracle_get_side_effects_counter() } +unconstrained pub fn add_authwit(address: AztecAddress, message_hash: Field) { + orable_add_authwit(address, message_hash) +} + #[oracle(reset)] fn oracle_reset() {} @@ -120,3 +124,6 @@ fn oracle_set_msg_sender(msg_sender: AztecAddress) {} #[oracle(getSideEffectsCounter)] fn oracle_get_side_effects_counter() -> u32 {} + +#[oracle(addAuthWitness)] +fn orable_add_authwit(address: AztecAddress, message_hash: Field) {} diff --git a/noir-projects/noir-contracts/contracts/token_contract/src/main.nr b/noir-projects/noir-contracts/contracts/token_contract/src/main.nr index fe0cedebbc7..ba2a559445d 100644 --- a/noir-projects/noir-contracts/contracts/token_contract/src/main.nr +++ b/noir-projects/noir-contracts/contracts/token_contract/src/main.nr @@ -399,6 +399,7 @@ contract Token { use crate::test; use dep::aztec::test::helpers::cheatcodes; + use dep::authwit::test as autwit_test; #[test] fn test_private_transfer() { @@ -420,9 +421,10 @@ contract Token { let (env, token_contract_address, owner, recipient, mint_amount) = test::setup_and_mint(); // Impersonate recipient to test authwits cheatcodes::set_contract_address(recipient); - // Transfer tokens let transfer_amount = 1000; let private_transfer_from_call_interface = Token::at(token_contract_address).transfer_from(owner, recipient, transfer_amount, 1); + autwit_test::add_authwit_from_call_interface(owner, recipient, private_transfer_from_call_interface); + // Transfer tokens env.call_private_void(private_transfer_from_call_interface); // Check balances test::check_balance(token_contract_address, owner, mint_amount - transfer_amount); diff --git a/yarn-project/txe/src/oracle/txe_oracle.ts b/yarn-project/txe/src/oracle/txe_oracle.ts index 1677b32ba92..617adfab9a1 100644 --- a/yarn-project/txe/src/oracle/txe_oracle.ts +++ b/yarn-project/txe/src/oracle/txe_oracle.ts @@ -1,4 +1,5 @@ import { + AuthWitness, L1NotePayload, MerkleTreeId, Note, @@ -30,11 +31,11 @@ import { deriveKeys, getContractClassFromArtifact, } from '@aztec/circuits.js'; -import { Aes128 } from '@aztec/circuits.js/barretenberg'; +import { Aes128, Schnorr } from '@aztec/circuits.js/barretenberg'; import { computePublicDataTreeLeafSlot, siloNoteHash, siloNullifier } from '@aztec/circuits.js/hash'; import { type ContractArtifact, type FunctionAbi, FunctionSelector, countArgumentsSize } from '@aztec/foundation/abi'; import { AztecAddress } from '@aztec/foundation/aztec-address'; -import { Fr, GrumpkinScalar, type Point } from '@aztec/foundation/fields'; +import { Fq, Fr, GrumpkinScalar, type Point } from '@aztec/foundation/fields'; import { type Logger, applyStringFormatting } from '@aztec/foundation/log'; import { Timer } from '@aztec/foundation/timer'; import { type KeyStore } from '@aztec/key-store'; @@ -183,6 +184,15 @@ export class TXE implements TypedOracle { return deriveKeys(secret); } + async addAuthWitness(address: AztecAddress, messageHash: Fr) { + const account = await this.txeDatabase.getAccount(address); + const privateKey = await this.keyStore.getMasterSecretKey(account.publicKeys.masterIncomingViewingPublicKey); + const schnorr = new Schnorr(); + const signature = schnorr.constructSignature(messageHash.toBuffer(), privateKey).toBuffer(); + const authWitness = new AuthWitness(messageHash, [...signature]); + return this.txeDatabase.addAuthWitness(authWitness.requestHash, authWitness.witness); + } + // TypedOracle getBlockNumber(): Promise { diff --git a/yarn-project/txe/src/txe_service/txe_service.ts b/yarn-project/txe/src/txe_service/txe_service.ts index 07ec8a717b7..a5b92fe9597 100644 --- a/yarn-project/txe/src/txe_service/txe_service.ts +++ b/yarn-project/txe/src/txe_service/txe_service.ts @@ -31,7 +31,7 @@ import { import { TXEDatabase } from '../util/txe_database.js'; export class TXEService { - constructor(private logger: Logger, private typedOracle: TypedOracle, private store: AztecKVStore) {} + constructor(private logger: Logger, private typedOracle: TypedOracle) {} static async init(logger: Logger) { const store = openTmpStore(true); @@ -42,7 +42,7 @@ export class TXEService { const txeDatabase = new TXEDatabase(store); logger.info(`TXE service initialized`); const txe = new TXE(logger, trees, packedValuesCache, noteCache, keyStore, txeDatabase); - const service = new TXEService(logger, txe, store); + const service = new TXEService(logger, txe); await service.timeTravel(toSingle(new Fr(1n))); return service; } @@ -63,27 +63,28 @@ export class TXEService { const nBlocks = fromSingle(blocks).toNumber(); this.logger.info(`time traveling ${nBlocks} blocks`); const trees = (this.typedOracle as TXE).getTrees(); + const header = Header.empty(); + const l2Block = L2Block.empty(); + header.state = await trees.getStateReference(true); + const blockNumber = await this.typedOracle.getBlockNumber(); + header.globalVariables.blockNumber = new Fr(blockNumber); + header.state.partial.nullifierTree.root = Fr.fromBuffer( + (await trees.getTreeInfo(MerkleTreeId.NULLIFIER_TREE, true)).root, + ); + header.state.partial.noteHashTree.root = Fr.fromBuffer( + (await trees.getTreeInfo(MerkleTreeId.NOTE_HASH_TREE, true)).root, + ); + header.state.partial.publicDataTree.root = Fr.fromBuffer( + (await trees.getTreeInfo(MerkleTreeId.PUBLIC_DATA_TREE, true)).root, + ); + header.state.l1ToL2MessageTree.root = Fr.fromBuffer( + (await trees.getTreeInfo(MerkleTreeId.L1_TO_L2_MESSAGE_TREE, true)).root, + ); + l2Block.archive.root = Fr.fromBuffer((await trees.getTreeInfo(MerkleTreeId.ARCHIVE, true)).root); + l2Block.header = header; for (let i = 0; i < nBlocks; i++) { - const header = Header.empty(); - const l2Block = L2Block.empty(); - header.state = await trees.getStateReference(true); const blockNumber = await this.typedOracle.getBlockNumber(); - header.globalVariables.blockNumber = new Fr(blockNumber); - header.state.partial.nullifierTree.root = Fr.fromBuffer( - (await trees.getTreeInfo(MerkleTreeId.NULLIFIER_TREE, true)).root, - ); - header.state.partial.noteHashTree.root = Fr.fromBuffer( - (await trees.getTreeInfo(MerkleTreeId.NOTE_HASH_TREE, true)).root, - ); - header.state.partial.publicDataTree.root = Fr.fromBuffer( - (await trees.getTreeInfo(MerkleTreeId.PUBLIC_DATA_TREE, true)).root, - ); - header.state.l1ToL2MessageTree.root = Fr.fromBuffer( - (await trees.getTreeInfo(MerkleTreeId.L1_TO_L2_MESSAGE_TREE, true)).root, - ); - l2Block.archive.root = Fr.fromBuffer((await trees.getTreeInfo(MerkleTreeId.ARCHIVE, true)).root); - l2Block.header = header; await trees.handleL2BlockAndMessages(l2Block, []); (this.typedOracle as TXE).setBlockNumber(blockNumber + 1); } @@ -208,6 +209,11 @@ export class TXEService { return toForeignCallResult([toSingle(new Fr(counter))]); } + addAuthWitness(address: ForeignCallSingle, messageHash: ForeignCallSingle) { + (this.typedOracle as TXE).addAuthWitness(fromSingle(address), fromSingle(messageHash)); + return toForeignCallResult([]); + } + // PXE oracles getRandomField() { From e3c5f71eb716ba63e616785fbf69dfb3ac90fadf Mon Sep 17 00:00:00 2001 From: thunkar Date: Wed, 19 Jun 2024 15:39:13 +0000 Subject: [PATCH 55/75] avoid unnecesary account contract creation --- .../contracts/token_contract/src/main.nr | 8 ++++---- .../contracts/token_contract/src/test.nr | 13 ++++++++++--- 2 files changed, 14 insertions(+), 7 deletions(-) diff --git a/noir-projects/noir-contracts/contracts/token_contract/src/main.nr b/noir-projects/noir-contracts/contracts/token_contract/src/main.nr index ba2a559445d..12c9dd3ecc2 100644 --- a/noir-projects/noir-contracts/contracts/token_contract/src/main.nr +++ b/noir-projects/noir-contracts/contracts/token_contract/src/main.nr @@ -403,8 +403,8 @@ contract Token { #[test] fn test_private_transfer() { - // Setup - let (env, token_contract_address, owner, recipient, mint_amount) = test::setup_and_mint(); + // Setup without account contracts. We are not using authwits here, so dummy accounts are enough + let (env, token_contract_address, owner, recipient, mint_amount) = test::setup_and_mint(/* with_account_contracts */ false); // Transfer tokens let transfer_amount = 1000; let private_transfer_call_interface = Token::at(token_contract_address).transfer(recipient, transfer_amount); @@ -417,8 +417,8 @@ contract Token { #[test] fn test_private_transfer_from() { - // Setup - let (env, token_contract_address, owner, recipient, mint_amount) = test::setup_and_mint(); + // Setup with account contracts. Slower since we actually deploy them, but needed for authwits. + let (env, token_contract_address, owner, recipient, mint_amount) = test::setup_and_mint(/* with_account_contracts */ true); // Impersonate recipient to test authwits cheatcodes::set_contract_address(recipient); let transfer_amount = 1000; diff --git a/noir-projects/noir-contracts/contracts/token_contract/src/test.nr b/noir-projects/noir-contracts/contracts/token_contract/src/test.nr index de480c05c9f..28853ffdbac 100644 --- a/noir-projects/noir-contracts/contracts/token_contract/src/test.nr +++ b/noir-projects/noir-contracts/contracts/token_contract/src/test.nr @@ -5,11 +5,18 @@ use dep::aztec::note::note_getter::{MAX_NOTES_PER_PAGE, view_notes}; use dep::aztec::note::note_viewer_options::NoteViewerOptions; use crate::{types::{token_note::TokenNote, transparent_note::TransparentNote}, Token}; -pub fn setup_and_mint() -> (TestEnvironment, AztecAddress, AztecAddress, AztecAddress, Field) { +pub fn setup_and_mint(with_account_contracts: bool) -> (TestEnvironment, AztecAddress, AztecAddress, AztecAddress, Field) { // Setup env, generate keys let mut env = TestEnvironment::new(); - let owner = env.create_account_contract(1); - let recipient = env.create_account_contract(2); + let (owner, recipient) = if with_account_contracts { + let owner = env.create_account_contract(1); + let recipient = env.create_account_contract(2); + (owner, recipient) + } else { + let owner = env.create_account(); + let recipient = env.create_account(); + (owner, recipient) + }; let mint_amount = 10000; // Start the test in the account contract address From 891d331a2346294668c530fb7149854ac2f63054 Mon Sep 17 00:00:00 2001 From: thunkar Date: Thu, 20 Jun 2024 12:02:58 +0000 Subject: [PATCH 56/75] public execution --- .../contracts/token_contract/src/main.nr | 22 +- .../contracts/token_contract/src/test.nr | 24 +- .../crates/types/src/utils.nr | 3 +- yarn-project/pxe/src/index.ts | 1 + yarn-project/simulator/src/avm/index.ts | 3 + yarn-project/simulator/src/public/index.ts | 1 + yarn-project/txe/src/oracle/txe_oracle.ts | 218 ++++++++++++++++-- .../txe/src/txe_service/txe_service.ts | 25 ++ .../util/txe_public_contract_data_source.ts | 66 ++++++ .../txe/src/util/txe_public_state_db.ts | 58 +++++ 10 files changed, 389 insertions(+), 32 deletions(-) create mode 100644 yarn-project/txe/src/util/txe_public_contract_data_source.ts create mode 100644 yarn-project/txe/src/util/txe_public_state_db.ts diff --git a/noir-projects/noir-contracts/contracts/token_contract/src/main.nr b/noir-projects/noir-contracts/contracts/token_contract/src/main.nr index 12c9dd3ecc2..2d9068c8ff1 100644 --- a/noir-projects/noir-contracts/contracts/token_contract/src/main.nr +++ b/noir-projects/noir-contracts/contracts/token_contract/src/main.nr @@ -402,7 +402,7 @@ contract Token { use dep::authwit::test as autwit_test; #[test] - fn test_private_transfer() { + unconstrained fn test_private_transfer() { // Setup without account contracts. We are not using authwits here, so dummy accounts are enough let (env, token_contract_address, owner, recipient, mint_amount) = test::setup_and_mint(/* with_account_contracts */ false); // Transfer tokens @@ -411,12 +411,12 @@ contract Token { env.call_private_void(private_transfer_call_interface); // Check balances - test::check_balance(token_contract_address, owner, mint_amount - transfer_amount); - test::check_balance(token_contract_address, recipient, transfer_amount); + test::check_private_balance(token_contract_address, owner, mint_amount - transfer_amount); + test::check_private_balance(token_contract_address, recipient, transfer_amount); } #[test] - fn test_private_transfer_from() { + unconstrained fn test_private_transfer_from() { // Setup with account contracts. Slower since we actually deploy them, but needed for authwits. let (env, token_contract_address, owner, recipient, mint_amount) = test::setup_and_mint(/* with_account_contracts */ true); // Impersonate recipient to test authwits @@ -427,8 +427,18 @@ contract Token { // Transfer tokens env.call_private_void(private_transfer_from_call_interface); // Check balances - test::check_balance(token_contract_address, owner, mint_amount - transfer_amount); - test::check_balance(token_contract_address, recipient, transfer_amount); + test::check_private_balance(token_contract_address, owner, mint_amount - transfer_amount); + test::check_private_balance(token_contract_address, recipient, transfer_amount); + } + + #[test] + unconstrained fn test_unshield() { + // Setup without account contracts. We are not using authwits here, so dummy accounts are enough + let (env, token_contract_address, owner, _, _) = test::setup_and_mint(/* with_account_contracts */ false); + let unshield_amount = 1000; + let unshield_call_interface = Token::at(token_contract_address).unshield(owner, owner, unshield_amount, 0); + env.call_private_void(unshield_call_interface); + test::check_public_balance(token_contract_address, owner, unshield_amount); } } // docs:end:token_all \ No newline at end of file diff --git a/noir-projects/noir-contracts/contracts/token_contract/src/test.nr b/noir-projects/noir-contracts/contracts/token_contract/src/test.nr index 28853ffdbac..4c19c9cba15 100644 --- a/noir-projects/noir-contracts/contracts/token_contract/src/test.nr +++ b/noir-projects/noir-contracts/contracts/token_contract/src/test.nr @@ -1,8 +1,11 @@ -use dep::aztec::{hash::compute_secret_hash, prelude::AztecAddress}; -use dep::aztec::test::helpers::{cheatcodes, test_environment::TestEnvironment}; -use dep::aztec::protocol_types::storage::map::derive_storage_slot_in_map; -use dep::aztec::note::note_getter::{MAX_NOTES_PER_PAGE, view_notes}; -use dep::aztec::note::note_viewer_options::NoteViewerOptions; +use dep::aztec::{ + hash::compute_secret_hash, prelude::AztecAddress, + test::helpers::{cheatcodes, test_environment::TestEnvironment}, + protocol_types::storage::map::derive_storage_slot_in_map, + note::{note_getter::{MAX_NOTES_PER_PAGE, view_notes}, note_viewer_options::NoteViewerOptions}, + oracle::storage::storage_read +}; + use crate::{types::{token_note::TokenNote, transparent_note::TransparentNote}, Token}; pub fn setup_and_mint(with_account_contracts: bool) -> (TestEnvironment, AztecAddress, AztecAddress, AztecAddress, Field) { @@ -60,7 +63,16 @@ pub fn setup_and_mint(with_account_contracts: bool) -> (TestEnvironment, AztecAd (env, token_contract_address, owner, recipient, mint_amount) } -pub fn check_balance(token_contract_address: AztecAddress, address: AztecAddress, address_amount: Field) { +pub fn check_public_balance(token_contract_address: AztecAddress, address: AztecAddress, address_amount: Field) { + cheatcodes::set_contract_address(token_contract_address); + + let balances_slot = Token::storage().public_balances.slot; + let address_slot = derive_storage_slot_in_map(balances_slot, address); + let fields = storage_read(address_slot); + assert(U128::deserialize(fields).to_field() == address_amount); +} + +pub fn check_private_balance(token_contract_address: AztecAddress, address: AztecAddress, address_amount: Field) { cheatcodes::set_contract_address(token_contract_address); let balances_slot = Token::storage().balances.slot; diff --git a/noir-projects/noir-protocol-circuits/crates/types/src/utils.nr b/noir-projects/noir-protocol-circuits/crates/types/src/utils.nr index 95561df1094..88624e25476 100644 --- a/noir-projects/noir-protocol-circuits/crates/types/src/utils.nr +++ b/noir-projects/noir-protocol-circuits/crates/types/src/utils.nr @@ -13,7 +13,8 @@ pub fn conditional_assign(predicate: bool, lhs: Field, rhs: Field) -> Field { } pub fn arr_copy_slice(src: [T; N], mut dst: [T; M], offset: u32) -> [T; M] { - for i in 0..dst.len() { + let iterator_len = if N > M { M } else { N }; + for i in 0..iterator_len { dst[i] = src[i + offset]; } dst diff --git a/yarn-project/pxe/src/index.ts b/yarn-project/pxe/src/index.ts index 7c62b24d3ff..86b3f1205e7 100644 --- a/yarn-project/pxe/src/index.ts +++ b/yarn-project/pxe/src/index.ts @@ -11,3 +11,4 @@ export * from '@aztec/foundation/aztec-address'; export * from '@aztec/key-store'; export * from './database/index.js'; export { ContractDataOracle } from './contract_data_oracle/index.js'; +export { PrivateFunctionsTree } from './contract_data_oracle/private_functions_tree.js'; diff --git a/yarn-project/simulator/src/avm/index.ts b/yarn-project/simulator/src/avm/index.ts index 5f426c8aa7d..293890a73fb 100644 --- a/yarn-project/simulator/src/avm/index.ts +++ b/yarn-project/simulator/src/avm/index.ts @@ -1 +1,4 @@ export * from './avm_simulator.js'; +export * from './journal/index.js'; +export * from './avm_machine_state.js'; +export * from './avm_context.js'; diff --git a/yarn-project/simulator/src/public/index.ts b/yarn-project/simulator/src/public/index.ts index 933b918f4c9..900b5ce4210 100644 --- a/yarn-project/simulator/src/public/index.ts +++ b/yarn-project/simulator/src/public/index.ts @@ -8,3 +8,4 @@ export * from './public_db_sources.js'; export * from './public_kernel.js'; export * from './public_kernel_circuit_simulator.js'; export { PublicProcessor, PublicProcessorFactory } from './public_processor.js'; +export * from './transitional_adaptors.js'; diff --git a/yarn-project/txe/src/oracle/txe_oracle.ts b/yarn-project/txe/src/oracle/txe_oracle.ts index 617adfab9a1..91f71b08cae 100644 --- a/yarn-project/txe/src/oracle/txe_oracle.ts +++ b/yarn-project/txe/src/oracle/txe_oracle.ts @@ -10,11 +10,15 @@ import { TaggedLog, type UnencryptedL2Log, } from '@aztec/circuit-types'; -import { type CircuitWitnessGenerationStats } from '@aztec/circuit-types/stats'; +import { AvmSimulationStats, type CircuitWitnessGenerationStats } from '@aztec/circuit-types/stats'; import { + CallContext, type CompleteAddress, FunctionData, - type Header, + Gas, + GasSettings, + GlobalVariables, + Header, type KeyValidationRequest, NULLIFIER_SUBTREE_HEIGHT, type NULLIFIER_TREE_HEIGHT, @@ -24,7 +28,7 @@ import { PrivateCallStackItem, PrivateCircuitPublicInputs, PrivateContextInputs, - type PublicCallRequest, + PublicCallRequest, PublicDataTreeLeaf, type PublicDataTreeLeafPreimage, computeContractClassId, @@ -41,14 +45,23 @@ import { Timer } from '@aztec/foundation/timer'; import { type KeyStore } from '@aztec/key-store'; import { ContractDataOracle } from '@aztec/pxe'; import { + AvmContext, + AvmMachineState, + AvmPersistableStateManager, + AvmSimulator, + ContractsDataSourcePublicDB, ExecutionError, type ExecutionNoteCache, + HostStorage, type MessageLoadOracleInputs, type NoteData, Oracle, type PackedValuesCache, type TypedOracle, + WorldStateDB, acvm, + convertAvmResultsToPxResult, + createAvmExecutionEnvironment, createSimulationError, extractCallStack, pickNotes, @@ -59,6 +72,8 @@ import { type ContractInstance, type ContractInstanceWithAddress } from '@aztec/ import { MerkleTreeSnapshotOperationsFacade, type MerkleTrees } from '@aztec/world-state'; import { type TXEDatabase } from '../util/txe_database.js'; +import { TXEPublicContractDataSource } from '../util/txe_public_contract_data_source.js'; +import { TXEPublicStateDB } from '../util/txe_public_state_db.js'; export class TXE implements TypedOracle { private blockNumber = 0; @@ -116,6 +131,10 @@ export class TXE implements TypedOracle { return this.trees; } + getContractDataOracle() { + return this.contractDataOracle; + } + getTXEDatabase() { return this.txeDatabase; } @@ -579,26 +598,187 @@ export class TXE implements TypedOracle { return Promise.resolve(`${artifact.name}:${f.name}`); } - callPublicFunction( - _targetContractAddress: AztecAddress, - _functionSelector: FunctionSelector, - _argsHash: Fr, - _sideEffectCounter: number, - _isStaticCall: boolean, - _isDelegateCall: boolean, + async executePublicFunction( + targetContractAddress: AztecAddress, + functionSelector: FunctionSelector, + args: Fr[], + callContext: CallContext, + ) { + const fnName = + (await this.getDebugFunctionName(targetContractAddress, functionSelector)) ?? + `${targetContractAddress}:${functionSelector}`; + + this.logger.debug(`[AVM] Executing public external function ${fnName}.`); + const timer = new Timer(); + const startGas = Gas.test(); + const hostStorage = new HostStorage( + new TXEPublicStateDB(this), + new ContractsDataSourcePublicDB(new TXEPublicContractDataSource(this)), + new WorldStateDB(this.trees.asLatest()), + ); + + const worldStateJournal = new AvmPersistableStateManager(hostStorage); + for (const nullifier of this.noteCache.getNullifiers(targetContractAddress)) { + worldStateJournal.nullifiers.cache.appendSiloed(new Fr(nullifier)); + } + worldStateJournal.trace.accessCounter = callContext.sideEffectCounter; + const execution = { + contractAddress: targetContractAddress, + functionSelector, + args, + callContext, + }; + const header = Header.empty(); + header.state = await this.trees.getStateReference(true); + header.globalVariables.blockNumber = new Fr(await this.getBlockNumber()); + header.state.partial.nullifierTree.root = Fr.fromBuffer( + (await this.trees.getTreeInfo(MerkleTreeId.NULLIFIER_TREE, true)).root, + ); + header.state.partial.noteHashTree.root = Fr.fromBuffer( + (await this.trees.getTreeInfo(MerkleTreeId.NOTE_HASH_TREE, true)).root, + ); + header.state.partial.publicDataTree.root = Fr.fromBuffer( + (await this.trees.getTreeInfo(MerkleTreeId.PUBLIC_DATA_TREE, true)).root, + ); + header.state.l1ToL2MessageTree.root = Fr.fromBuffer( + (await this.trees.getTreeInfo(MerkleTreeId.L1_TO_L2_MESSAGE_TREE, true)).root, + ); + const executionEnv = createAvmExecutionEnvironment( + execution, + header, + GlobalVariables.empty(), + GasSettings.default(), + Fr.ZERO, + ); + + const machineState = new AvmMachineState(startGas); + const avmContext = new AvmContext(worldStateJournal, executionEnv, machineState); + const simulator = new AvmSimulator(avmContext); + const avmResult = await simulator.execute(); + const bytecode = simulator.getBytecode(); + + await avmContext.persistableState.publicStorage.commitToDB(); + + this.logger.verbose( + `[AVM] ${fnName} returned, reverted: ${avmResult.reverted}${ + avmResult.reverted ? ', reason: ' + avmResult.revertReason : '' + }.`, + { + eventName: 'avm-simulation', + appCircuitName: fnName, + duration: timer.ms(), + bytecodeSize: bytecode!.length, + } satisfies AvmSimulationStats, + ); + + const executionResult = convertAvmResultsToPxResult( + avmResult, + callContext.sideEffectCounter, + execution, + startGas, + avmContext, + bytecode, + fnName, + ); + return executionResult; + } + + async callPublicFunction( + targetContractAddress: AztecAddress, + functionSelector: FunctionSelector, + argsHash: Fr, + sideEffectCounter: number, + isStaticCall: boolean, + isDelegateCall: boolean, ): Promise { - throw new Error('Method not implemented.'); + // Store and modify env + const currentContractAddress = AztecAddress.fromField(this.contractAddress); + const currentMessageSender = AztecAddress.fromField(this.msgSender); + const currentFunctionSelector = FunctionSelector.fromField(this.functionSelector.toField()); + this.setMsgSender(this.contractAddress); + this.setContractAddress(targetContractAddress); + this.setFunctionSelector(functionSelector); + + const callContext = CallContext.empty(); + callContext.msgSender = this.msgSender; + callContext.functionSelector = this.functionSelector; + callContext.sideEffectCounter = sideEffectCounter; + callContext.storageContractAddress = targetContractAddress; + callContext.isStaticCall = isStaticCall; + callContext.isDelegateCall = isDelegateCall; + + const args = this.packedValuesCache.unpack(argsHash); + + const executionResult = await this.executePublicFunction( + targetContractAddress, + functionSelector, + args, + callContext, + ); + + // Apply side effects + this.sideEffectsCounter += executionResult.endSideEffectCounter.toNumber(); + this.setContractAddress(currentContractAddress); + this.setMsgSender(currentMessageSender); + this.setFunctionSelector(currentFunctionSelector); + + return executionResult.returnValues; } - enqueuePublicFunctionCall( - _targetContractAddress: AztecAddress, - _functionSelector: FunctionSelector, - _argsHash: Fr, - _sideEffectCounter: number, - _isStaticCall: boolean, - _isDelegateCall: boolean, + async enqueuePublicFunctionCall( + targetContractAddress: AztecAddress, + functionSelector: FunctionSelector, + argsHash: Fr, + sideEffectCounter: number, + isStaticCall: boolean, + isDelegateCall: boolean, ): Promise { - throw new Error('Method not implemented.'); + // Store and modify env + const currentContractAddress = AztecAddress.fromField(this.contractAddress); + const currentMessageSender = AztecAddress.fromField(this.msgSender); + const currentFunctionSelector = FunctionSelector.fromField(this.functionSelector.toField()); + this.setMsgSender(this.contractAddress); + this.setContractAddress(targetContractAddress); + this.setFunctionSelector(functionSelector); + + const callContext = CallContext.empty(); + callContext.msgSender = this.msgSender; + callContext.functionSelector = this.functionSelector; + callContext.sideEffectCounter = sideEffectCounter; + callContext.storageContractAddress = targetContractAddress; + callContext.isStaticCall = isStaticCall; + callContext.isDelegateCall = isDelegateCall; + + const args = this.packedValuesCache.unpack(argsHash); + + const executionResult = await this.executePublicFunction( + targetContractAddress, + functionSelector, + args, + callContext, + ); + + // Apply side effects + this.sideEffectsCounter += executionResult.endSideEffectCounter.toNumber(); + this.setContractAddress(currentContractAddress); + this.setMsgSender(currentMessageSender); + this.setFunctionSelector(currentFunctionSelector); + + const parentCallContext = CallContext.empty(); + parentCallContext.msgSender = currentMessageSender; + parentCallContext.functionSelector = currentFunctionSelector; + parentCallContext.sideEffectCounter = sideEffectCounter; + parentCallContext.storageContractAddress = currentContractAddress; + parentCallContext.isStaticCall = isStaticCall; + parentCallContext.isDelegateCall = isDelegateCall; + + return PublicCallRequest.from({ + parentCallContext, + contractAddress: targetContractAddress, + functionSelector, + callContext, + args, + }); } setPublicTeardownFunctionCall( diff --git a/yarn-project/txe/src/txe_service/txe_service.ts b/yarn-project/txe/src/txe_service/txe_service.ts index a5b92fe9597..26ef38ad0a4 100644 --- a/yarn-project/txe/src/txe_service/txe_service.ts +++ b/yarn-project/txe/src/txe_service/txe_service.ts @@ -546,4 +546,29 @@ export class TXEService { } return toForeignCallResult([toArray(authWitness)]); } + + async enqueuePublicFunctionCall( + targetContractAddress: ForeignCallSingle, + functionSelector: ForeignCallSingle, + argsHash: ForeignCallSingle, + sideEffectCounter: ForeignCallSingle, + isStaticCall: ForeignCallSingle, + isDelegateCall: ForeignCallSingle, + ) { + const publicCallRequest = await this.typedOracle.enqueuePublicFunctionCall( + fromSingle(targetContractAddress), + FunctionSelector.fromField(fromSingle(functionSelector)), + fromSingle(argsHash), + fromSingle(sideEffectCounter).toNumber(), + fromSingle(isStaticCall).toBool(), + fromSingle(isDelegateCall).toBool(), + ); + const fields = [ + publicCallRequest.contractAddress.toField(), + publicCallRequest.functionSelector.toField(), + ...publicCallRequest.callContext.toFields(), + publicCallRequest.getArgsHash(), + ]; + return toForeignCallResult([toArray(fields)]); + } } diff --git a/yarn-project/txe/src/util/txe_public_contract_data_source.ts b/yarn-project/txe/src/util/txe_public_contract_data_source.ts new file mode 100644 index 00000000000..98d128fabc9 --- /dev/null +++ b/yarn-project/txe/src/util/txe_public_contract_data_source.ts @@ -0,0 +1,66 @@ +import { AztecAddress, Fr, FunctionSelector, unpackBytecode } from '@aztec/circuits.js'; +import { ContractArtifact } from '@aztec/foundation/abi'; +import { PrivateFunctionsTree } from '@aztec/pxe'; +import { + ContractClassPublic, + ContractDataSource, + ContractInstanceWithAddress, + PublicFunction, +} from '@aztec/types/contracts'; + +import { TXE } from '../oracle/txe_oracle.js'; + +export class TXEPublicContractDataSource implements ContractDataSource { + constructor(private txeOracle: TXE) {} + + async getPublicFunction(address: AztecAddress, selector: FunctionSelector): Promise { + const bytecode = await this.txeOracle.getContractDataOracle().getBytecode(address, selector); + if (!bytecode) { + return undefined; + } + return { bytecode, selector }; + } + + getBlockNumber(): Promise { + return this.txeOracle.getBlockNumber(); + } + + async getContractClass(id: Fr): Promise { + let contractClass; + let privateFunctionsRoot; + try { + contractClass = await this.txeOracle.getContractDataOracle().getContractClass(id); + const artifact = await this.txeOracle.getContractDataOracle().getContractArtifact(id); + const tree = new PrivateFunctionsTree(artifact); + privateFunctionsRoot = await tree.getFunctionTreeRoot(); + } catch {} + + return { + id, + artifactHash: contractClass!.artifactHash, + packedBytecode: contractClass!.packedBytecode, + publicFunctions: unpackBytecode(contractClass!.packedBytecode), + privateFunctionsRoot: new Fr(privateFunctionsRoot!.root), + version: contractClass!.version, + privateFunctions: [], + unconstrainedFunctions: [], + }; + } + + async getContract(address: AztecAddress): Promise { + const instance = await this.txeOracle.getContractDataOracle().getContractInstance(address); + return { ...instance, address }; + } + + getContractClassIds(): Promise { + throw new Error('Method not implemented.'); + } + + getContractArtifact(address: AztecAddress): Promise { + return this.txeOracle.getContractDataOracle().getContractArtifact(address); + } + + addContractArtifact(address: AztecAddress, contract: ContractArtifact): Promise { + return this.txeOracle.addContractArtifact(contract); + } +} diff --git a/yarn-project/txe/src/util/txe_public_state_db.ts b/yarn-project/txe/src/util/txe_public_state_db.ts new file mode 100644 index 00000000000..55abac9c1f3 --- /dev/null +++ b/yarn-project/txe/src/util/txe_public_state_db.ts @@ -0,0 +1,58 @@ +import { MerkleTreeId, PublicDataWrite } from '@aztec/circuit-types'; +import { + AztecAddress, + Fr, + PUBLIC_DATA_SUBTREE_HEIGHT, + PublicDataTreeLeaf, + PublicDataTreeLeafPreimage, +} from '@aztec/circuits.js'; +import { computePublicDataTreeLeafSlot } from '@aztec/circuits.js/hash'; +import { PublicStateDB } from '@aztec/simulator'; +import { MerkleTrees } from '@aztec/world-state'; + +import { TXE } from '../oracle/txe_oracle.js'; + +export class TXEPublicStateDB implements PublicStateDB { + constructor(private txeOracle: TXE) {} + + async storageRead(contract: AztecAddress, slot: Fr): Promise { + const db = this.txeOracle.getTrees().asLatest(); + const leafSlot = computePublicDataTreeLeafSlot(contract, slot).toBigInt(); + + const lowLeafResult = await db.getPreviousValueIndex(MerkleTreeId.PUBLIC_DATA_TREE, leafSlot); + + let value = Fr.ZERO; + if (lowLeafResult && lowLeafResult.alreadyPresent) { + const preimage = (await db.getLeafPreimage( + MerkleTreeId.PUBLIC_DATA_TREE, + lowLeafResult.index, + )) as PublicDataTreeLeafPreimage; + value = preimage.value; + } + return value; + } + + async storageWrite(contract: AztecAddress, slot: Fr, newValue: Fr): Promise { + const db = this.txeOracle.getTrees().asLatest(); + + await db.batchInsert( + MerkleTreeId.PUBLIC_DATA_TREE, + [new PublicDataTreeLeaf(computePublicDataTreeLeafSlot(contract, slot), newValue).toBuffer()], + PUBLIC_DATA_SUBTREE_HEIGHT, + ); + return newValue.toBigInt(); + } + + checkpoint(): Promise { + return Promise.resolve(); + } + rollbackToCheckpoint(): Promise { + throw new Error('Cannot rollback'); + } + commit(): Promise { + return Promise.resolve(); + } + rollbackToCommit(): Promise { + throw new Error('Cannot rollback'); + } +} From c6b46756dec862a40733d84dce195851b0642298 Mon Sep 17 00:00:00 2001 From: thunkar Date: Thu, 20 Jun 2024 20:37:26 +0000 Subject: [PATCH 57/75] authwits, expect failures, token suite --- .../aztec-nr/authwit/src/cheatcodes.nr | 44 ++++++ noir-projects/aztec-nr/authwit/src/lib.nr | 2 +- noir-projects/aztec-nr/authwit/src/test.nr | 22 --- .../aztec/src/context/call_interfaces.nr | 59 +++++-- .../aztec/src/test/helpers/cheatcodes.nr | 39 +++++ .../src/test/helpers/test_environment.nr | 33 ++-- .../contracts/token_contract/src/main.nr | 44 +----- .../contracts/token_contract/src/test.nr | 88 +---------- .../token_contract/src/test/access_control.nr | 51 ++++++ .../contracts/token_contract/src/test/burn.nr | 145 ++++++++++++++++++ .../token_contract/src/test/transfer.nr | 65 ++++++++ .../token_contract/src/test/unshield.nr | 12 ++ .../token_contract/src/test/utils.nr | 93 +++++++++++ .../src/transforms/contract_interface.rs | 24 ++- yarn-project/txe/src/oracle/txe_oracle.ts | 132 ++++++++++------ .../txe/src/txe_service/txe_service.ts | 67 ++++++++ .../txe/src/util/expected_failure_error.ts | 5 + .../util/txe_public_contract_data_source.ts | 5 +- 18 files changed, 707 insertions(+), 223 deletions(-) create mode 100644 noir-projects/aztec-nr/authwit/src/cheatcodes.nr delete mode 100644 noir-projects/aztec-nr/authwit/src/test.nr create mode 100644 noir-projects/noir-contracts/contracts/token_contract/src/test/access_control.nr create mode 100644 noir-projects/noir-contracts/contracts/token_contract/src/test/burn.nr create mode 100644 noir-projects/noir-contracts/contracts/token_contract/src/test/transfer.nr create mode 100644 noir-projects/noir-contracts/contracts/token_contract/src/test/unshield.nr create mode 100644 noir-projects/noir-contracts/contracts/token_contract/src/test/utils.nr create mode 100644 yarn-project/txe/src/util/expected_failure_error.ts diff --git a/noir-projects/aztec-nr/authwit/src/cheatcodes.nr b/noir-projects/aztec-nr/authwit/src/cheatcodes.nr new file mode 100644 index 00000000000..f673a203277 --- /dev/null +++ b/noir-projects/aztec-nr/authwit/src/cheatcodes.nr @@ -0,0 +1,44 @@ +use dep::aztec::{ + protocol_types::address::AztecAddress, + context::{public_context::PublicContext, call_interfaces::CallInterface}, test::helpers::cheatcodes, + hash::hash_args +}; + +use crate::auth::{compute_inner_authwit_hash, compute_outer_authwit_hash, set_authorized}; + +pub fn add_private_authwit_from_call_interface( + on_behalf_of: AztecAddress, + caller: AztecAddress, + call_interface: C +) where C: CallInterface { + let target = call_interface.get_contract_address(); + let inputs = cheatcodes::get_private_context_inputs(cheatcodes::get_block_number()); + let chain_id = inputs.tx_context.chain_id; + let version = inputs.tx_context.version; + let args_hash = hash_args(call_interface.get_args()); + let selector = call_interface.get_selector(); + let inner_hash = compute_inner_authwit_hash([caller.to_field(), selector.to_field(), args_hash]); + let message_hash = compute_outer_authwit_hash(target, chain_id, version, inner_hash); + cheatcodes::add_authwit(on_behalf_of, message_hash); +} + +pub fn add_public_authwit_from_call_interface( + on_behalf_of: AztecAddress, + caller: AztecAddress, + call_interface: C +) where C: CallInterface { + let current_contract = cheatcodes::get_contract_address(); + cheatcodes::set_contract_address(on_behalf_of); + let target = call_interface.get_contract_address(); + let inputs = cheatcodes::get_private_context_inputs(cheatcodes::get_block_number()); + let chain_id = inputs.tx_context.chain_id; + let version = inputs.tx_context.version; + let args_hash = hash_args(call_interface.get_args()); + let selector = call_interface.get_selector(); + let inner_hash = compute_inner_authwit_hash([caller.to_field(), selector.to_field(), args_hash]); + let message_hash = compute_outer_authwit_hash(target, chain_id, version, inner_hash); + let mut inputs = cheatcodes::get_public_context_inputs(); + let mut context = PublicContext::new(inputs); + set_authorized(&mut context, message_hash, true); + cheatcodes::set_contract_address(current_contract); +} diff --git a/noir-projects/aztec-nr/authwit/src/lib.nr b/noir-projects/aztec-nr/authwit/src/lib.nr index 29e0036748d..c4d792a4a26 100644 --- a/noir-projects/aztec-nr/authwit/src/lib.nr +++ b/noir-projects/aztec-nr/authwit/src/lib.nr @@ -2,4 +2,4 @@ mod account; mod auth_witness; mod auth; mod entrypoint; -mod test; \ No newline at end of file +mod cheatcodes; diff --git a/noir-projects/aztec-nr/authwit/src/test.nr b/noir-projects/aztec-nr/authwit/src/test.nr deleted file mode 100644 index 267373f1765..00000000000 --- a/noir-projects/aztec-nr/authwit/src/test.nr +++ /dev/null @@ -1,22 +0,0 @@ -use dep::aztec::context::call_interfaces::CallInterface; -use dep::aztec::protocol_types::address::AztecAddress; -use dep::aztec::test::helpers::cheatcodes; -use dep::aztec::hash::hash_args; - -use crate::auth::{compute_inner_authwit_hash, compute_outer_authwit_hash}; - -pub fn add_authwit_from_call_interface( - on_behalf_of: AztecAddress, - caller: AztecAddress, - call_interface: C -) where C: CallInterface { - let target = call_interface.get_contract_address(); - let inputs = cheatcodes::get_private_context_inputs(cheatcodes::get_block_number()); - let chain_id = inputs.tx_context.chain_id; - let version = inputs.tx_context.version; - let args_hash = hash_args(call_interface.get_args()); - let selector = call_interface.get_selector(); - let inner_hash = compute_inner_authwit_hash([caller.to_field(), selector.to_field(), args_hash]); - let message_hash = compute_outer_authwit_hash(target, chain_id, version, inner_hash); - cheatcodes::add_authwit(on_behalf_of, message_hash); -} diff --git a/noir-projects/aztec-nr/aztec/src/context/call_interfaces.nr b/noir-projects/aztec-nr/aztec/src/context/call_interfaces.nr index dd1374f9eb0..35151d1427d 100644 --- a/noir-projects/aztec-nr/aztec/src/context/call_interfaces.nr +++ b/noir-projects/aztec-nr/aztec/src/context/call_interfaces.nr @@ -16,6 +16,7 @@ trait CallInterface { fn get_selector(self) -> FunctionSelector; fn get_name(self) -> str; fn get_contract_address(self) -> AztecAddress; + fn get_is_static(self) -> bool; } impl CallInterface for PrivateCallInterface { @@ -38,6 +39,10 @@ impl CallInterface AztecAddress { self.target_contract } + + fn get_is_static(self) -> bool { + self.is_static + } } struct PrivateCallInterface { @@ -46,7 +51,8 @@ struct PrivateCallInterface { name: str, args_hash: Field, args: [Field], - original: fn[Env](PrivateContextInputs) -> PrivateCircuitPublicInputs + original: fn[Env](PrivateContextInputs) -> PrivateCircuitPublicInputs, + is_static: bool } impl PrivateCallInterface { @@ -93,6 +99,10 @@ impl CallInterface AztecAddress { self.target_contract } + + fn get_is_static(self) -> bool { + self.is_static + } } struct PrivateVoidCallInterface { @@ -101,7 +111,8 @@ struct PrivateVoidCallInterface { name: str, args_hash: Field, args: [Field], - original: fn[Env](PrivateContextInputs) -> PrivateCircuitPublicInputs + original: fn[Env](PrivateContextInputs) -> PrivateCircuitPublicInputs, + is_static: bool } impl PrivateVoidCallInterface { @@ -144,6 +155,10 @@ impl CallInterface AztecAddress { self.target_contract } + + fn get_is_static(self) -> bool { + self.is_static + } } struct PrivateStaticCallInterface { @@ -152,7 +167,8 @@ struct PrivateStaticCallInterface { name: str, args_hash: Field, args: [Field], - original: fn[Env](PrivateContextInputs) -> PrivateCircuitPublicInputs + original: fn[Env](PrivateContextInputs) -> PrivateCircuitPublicInputs, + is_static: bool } impl PrivateStaticCallInterface { @@ -182,6 +198,10 @@ impl CallInterface AztecAddress { self.target_contract } + + fn get_is_static(self) -> bool { + self.is_static + } } struct PrivateStaticVoidCallInterface { @@ -190,7 +210,8 @@ struct PrivateStaticVoidCallInterface { name: str, args_hash: Field, args: [Field], - original: fn[Env](PrivateContextInputs) -> PrivateCircuitPublicInputs + original: fn[Env](PrivateContextInputs) -> PrivateCircuitPublicInputs, + is_static: bool } impl PrivateStaticVoidCallInterface { @@ -219,6 +240,10 @@ impl CallInterface for PublicCallI fn get_contract_address(self) -> AztecAddress { self.target_contract } + + fn get_is_static(self) -> bool { + self.is_static + } } struct PublicCallInterface { @@ -227,7 +252,8 @@ struct PublicCallInterface { name: str, args: [Field], gas_opts: GasOpts, - original: fn[Env](PublicContextInputs) -> T + original: fn[Env](PublicContextInputs) -> T, + is_static: bool } impl PublicCallInterface { @@ -308,6 +334,10 @@ impl CallInterface for PublicVoid fn get_contract_address(self) -> AztecAddress { self.target_contract } + + fn get_is_static(self) -> bool { + self.is_static + } } struct PublicVoidCallInterface { @@ -316,7 +346,8 @@ struct PublicVoidCallInterface { name: str, args: [Field], gas_opts: GasOpts, - original: fn[Env](PublicContextInputs) -> () + original: fn[Env](PublicContextInputs) -> (), + is_static: bool } impl PublicVoidCallInterface { @@ -378,7 +409,7 @@ impl PublicVoidCallInterface { } impl CallInterface for PublicStaticCallInterface { - fn get_args(self) -> [Field] { + fn get_args(self) -> [Field] { self.args } @@ -397,6 +428,10 @@ impl CallInterface for PublicStati fn get_contract_address(self) -> AztecAddress { self.target_contract } + + fn get_is_static(self) -> bool { + self.is_static + } } struct PublicStaticCallInterface { @@ -405,7 +440,8 @@ struct PublicStaticCallInterface { name: str, args: [Field], gas_opts: GasOpts, - original: fn[Env](PublicContextInputs) -> T + original: fn[Env](PublicContextInputs) -> T, + is_static: bool } impl PublicStaticCallInterface { @@ -453,6 +489,10 @@ impl CallInterface for PublicStat fn get_contract_address(self) -> AztecAddress { self.target_contract } + + fn get_is_static(self) -> bool { + self.is_static + } } struct PublicStaticVoidCallInterface { @@ -461,7 +501,8 @@ struct PublicStaticVoidCallInterface { name: str, args: [Field], gas_opts: GasOpts, - original: fn[Env](PublicContextInputs) -> () + original: fn[Env](PublicContextInputs) -> (), + is_static: bool } impl PublicStaticVoidCallInterface { diff --git a/noir-projects/aztec-nr/aztec/src/test/helpers/cheatcodes.nr b/noir-projects/aztec-nr/aztec/src/test/helpers/cheatcodes.nr index 22b9aa64cce..9712f1eb930 100644 --- a/noir-projects/aztec-nr/aztec/src/test/helpers/cheatcodes.nr +++ b/noir-projects/aztec-nr/aztec/src/test/helpers/cheatcodes.nr @@ -71,6 +71,28 @@ unconstrained pub fn add_authwit(address: AztecAddress, message_hash: Field) { orable_add_authwit(address, message_hash) } +unconstrained pub fn assert_public_call_fails(target_address: AztecAddress, function_selector: FunctionSelector, args: [Field]) { + oracle_assert_public_call_fails(target_address, function_selector, args) +} + +unconstrained pub fn assert_private_call_fails( + target_address: AztecAddress, + function_selector: FunctionSelector, + argsHash: Field, + sideEffectsCounter: Field, + isStaticCall: bool, + isDelegateCall: bool +) { + oracle_assert_private_call_fails( + target_address, + function_selector, + argsHash, + sideEffectsCounter, + isStaticCall, + isDelegateCall + ) +} + #[oracle(reset)] fn oracle_reset() {} @@ -127,3 +149,20 @@ fn oracle_get_side_effects_counter() -> u32 {} #[oracle(addAuthWitness)] fn orable_add_authwit(address: AztecAddress, message_hash: Field) {} + +#[oracle(assertPublicCallFails)] +fn oracle_assert_public_call_fails( + target_address: AztecAddress, + function_selector: FunctionSelector, + args: [Field] +) {} + +#[oracle(assertPrivateCallFails)] +fn oracle_assert_private_call_fails( + target_address: AztecAddress, + function_selector: FunctionSelector, + argsHash: Field, + sideEffectsCounter: Field, + isStaticCall: bool, + isDelegateCall: bool +) {} diff --git a/noir-projects/aztec-nr/aztec/src/test/helpers/test_environment.nr b/noir-projects/aztec-nr/aztec/src/test/helpers/test_environment.nr index afbb6caf22c..41c40d514b3 100644 --- a/noir-projects/aztec-nr/aztec/src/test/helpers/test_environment.nr +++ b/noir-projects/aztec-nr/aztec/src/test/helpers/test_environment.nr @@ -128,6 +128,7 @@ impl TestEnvironment { cheatcodes::set_msg_sender(original_contract_address); let mut inputs = cheatcodes::get_private_context_inputs(cheatcodes::get_block_number() - 1); inputs.call_context.function_selector = call_interface.get_selector(); + inputs.call_context.is_static_call = call_interface.get_is_static(); let public_inputs = original_fn(inputs); cheatcodes::set_contract_address(original_contract_address); @@ -148,6 +149,7 @@ impl TestEnvironment { cheatcodes::set_msg_sender(original_contract_address); let mut inputs = cheatcodes::get_private_context_inputs(cheatcodes::get_block_number() - 1); inputs.call_context.function_selector = call_interface.get_selector(); + inputs.call_context.is_static_call = call_interface.get_is_static(); let public_inputs = original_fn(inputs); cheatcodes::set_contract_address(original_contract_address); @@ -166,6 +168,7 @@ impl TestEnvironment { let mut inputs = cheatcodes::get_public_context_inputs(); inputs.selector = call_interface.get_selector().to_field(); inputs.args_hash = hash_args(call_interface.get_args()); + inputs.is_static_call = call_interface.get_is_static(); let result = original_fn(inputs); cheatcodes::set_contract_address(original_contract_address); @@ -173,21 +176,23 @@ impl TestEnvironment { result } - fn call_public_void(self, call_interface: C) where C: CallInterface { - let original_fn = call_interface.get_original(); - let original_msg_sender = cheatcodes::get_msg_sender(); - let original_contract_address = cheatcodes::get_contract_address(); - let target_address = call_interface.get_contract_address(); - - cheatcodes::set_contract_address(target_address); - cheatcodes::set_msg_sender(original_contract_address); - let mut inputs = cheatcodes::get_public_context_inputs(); - inputs.selector = call_interface.get_selector().to_field(); - inputs.args_hash = hash_args(call_interface.get_args()); - original_fn(inputs); + fn assert_public_call_fails(self, call_interface: C) where C: CallInterface { + cheatcodes::assert_public_call_fails( + call_interface.get_contract_address(), + call_interface.get_selector(), + call_interface.get_args() + ); + } - cheatcodes::set_contract_address(original_contract_address); - cheatcodes::set_msg_sender(original_msg_sender); + fn assert_private_call_fails(self, call_interface: C) where C: CallInterface { + cheatcodes::assert_private_call_fails( + call_interface.get_contract_address(), + call_interface.get_selector(), + hash_args(call_interface.get_args()), + cheatcodes::get_side_effects_counter() as Field, + call_interface.get_is_static(), + false + ); } pub fn store_note_in_cache( diff --git a/noir-projects/noir-contracts/contracts/token_contract/src/main.nr b/noir-projects/noir-contracts/contracts/token_contract/src/main.nr index 2d9068c8ff1..1e65617be8c 100644 --- a/noir-projects/noir-contracts/contracts/token_contract/src/main.nr +++ b/noir-projects/noir-contracts/contracts/token_contract/src/main.nr @@ -397,48 +397,6 @@ contract Token { } // docs:end:balance_of_private - use crate::test; - use dep::aztec::test::helpers::cheatcodes; - use dep::authwit::test as autwit_test; - - #[test] - unconstrained fn test_private_transfer() { - // Setup without account contracts. We are not using authwits here, so dummy accounts are enough - let (env, token_contract_address, owner, recipient, mint_amount) = test::setup_and_mint(/* with_account_contracts */ false); - // Transfer tokens - let transfer_amount = 1000; - let private_transfer_call_interface = Token::at(token_contract_address).transfer(recipient, transfer_amount); - env.call_private_void(private_transfer_call_interface); - - // Check balances - test::check_private_balance(token_contract_address, owner, mint_amount - transfer_amount); - test::check_private_balance(token_contract_address, recipient, transfer_amount); - } - - #[test] - unconstrained fn test_private_transfer_from() { - // Setup with account contracts. Slower since we actually deploy them, but needed for authwits. - let (env, token_contract_address, owner, recipient, mint_amount) = test::setup_and_mint(/* with_account_contracts */ true); - // Impersonate recipient to test authwits - cheatcodes::set_contract_address(recipient); - let transfer_amount = 1000; - let private_transfer_from_call_interface = Token::at(token_contract_address).transfer_from(owner, recipient, transfer_amount, 1); - autwit_test::add_authwit_from_call_interface(owner, recipient, private_transfer_from_call_interface); - // Transfer tokens - env.call_private_void(private_transfer_from_call_interface); - // Check balances - test::check_private_balance(token_contract_address, owner, mint_amount - transfer_amount); - test::check_private_balance(token_contract_address, recipient, transfer_amount); - } - - #[test] - unconstrained fn test_unshield() { - // Setup without account contracts. We are not using authwits here, so dummy accounts are enough - let (env, token_contract_address, owner, _, _) = test::setup_and_mint(/* with_account_contracts */ false); - let unshield_amount = 1000; - let unshield_call_interface = Token::at(token_contract_address).unshield(owner, owner, unshield_amount, 0); - env.call_private_void(unshield_call_interface); - test::check_public_balance(token_contract_address, owner, unshield_amount); - } + } // docs:end:token_all \ No newline at end of file diff --git a/noir-projects/noir-contracts/contracts/token_contract/src/test.nr b/noir-projects/noir-contracts/contracts/token_contract/src/test.nr index 4c19c9cba15..d63bcd4d625 100644 --- a/noir-projects/noir-contracts/contracts/token_contract/src/test.nr +++ b/noir-projects/noir-contracts/contracts/token_contract/src/test.nr @@ -1,83 +1,5 @@ -use dep::aztec::{ - hash::compute_secret_hash, prelude::AztecAddress, - test::helpers::{cheatcodes, test_environment::TestEnvironment}, - protocol_types::storage::map::derive_storage_slot_in_map, - note::{note_getter::{MAX_NOTES_PER_PAGE, view_notes}, note_viewer_options::NoteViewerOptions}, - oracle::storage::storage_read -}; - -use crate::{types::{token_note::TokenNote, transparent_note::TransparentNote}, Token}; - -pub fn setup_and_mint(with_account_contracts: bool) -> (TestEnvironment, AztecAddress, AztecAddress, AztecAddress, Field) { - // Setup env, generate keys - let mut env = TestEnvironment::new(); - let (owner, recipient) = if with_account_contracts { - let owner = env.create_account_contract(1); - let recipient = env.create_account_contract(2); - (owner, recipient) - } else { - let owner = env.create_account(); - let recipient = env.create_account(); - (owner, recipient) - }; - let mint_amount = 10000; - - // Start the test in the account contract address - cheatcodes::set_contract_address(owner); - - // Deploy token contract - let initializer_call_interface = Token::interface().constructor( - owner, - "TestToken0000000000000000000000", - "TT00000000000000000000000000000", - 18 - ); - let token_contract = env.deploy("@aztec/noir-contracts.js/Token").with_public_initializer(initializer_call_interface); - let token_contract_address = token_contract.to_address(); - env.advance_block_by(1); - - // Mint some tokens - let secret = 1; - let secret_hash = compute_secret_hash(secret); - let mint_private_call_interface = Token::at(token_contract_address).mint_private(mint_amount, secret_hash); - env.call_public(mint_private_call_interface); - - // Time travel so we can read keys from the registry - env.advance_block_by(6); - - // Store a note in the cache so we can redeem it - env.store_note_in_cache( - &mut TransparentNote::new(mint_amount, secret_hash), - Token::storage().pending_shields.slot, - token_contract_address - ); - - // Redeem our shielded tokens - let redeem_shield_call_interface = Token::at(token_contract_address).redeem_shield(owner, mint_amount, secret); - env.call_private_void(redeem_shield_call_interface); - - // Not really sure why this is needed? Nullifier inclusion in contract initializer fails otherwise. - // If it were to fail, it should do it at line 443, investigation required - env.advance_block_by(1); - - (env, token_contract_address, owner, recipient, mint_amount) -} - -pub fn check_public_balance(token_contract_address: AztecAddress, address: AztecAddress, address_amount: Field) { - cheatcodes::set_contract_address(token_contract_address); - - let balances_slot = Token::storage().public_balances.slot; - let address_slot = derive_storage_slot_in_map(balances_slot, address); - let fields = storage_read(address_slot); - assert(U128::deserialize(fields).to_field() == address_amount); -} - -pub fn check_private_balance(token_contract_address: AztecAddress, address: AztecAddress, address_amount: Field) { - cheatcodes::set_contract_address(token_contract_address); - - let balances_slot = Token::storage().balances.slot; - let address_slot = derive_storage_slot_in_map(balances_slot, address); - let mut options = NoteViewerOptions::new(); - let opt_notes: [Option; MAX_NOTES_PER_PAGE] = view_notes(address_slot, options); - assert(opt_notes[0].unwrap().amount.to_field() == address_amount); -} +mod access_control; +mod burn; +mod utils; +mod transfer; +mod unshield; diff --git a/noir-projects/noir-contracts/contracts/token_contract/src/test/access_control.nr b/noir-projects/noir-contracts/contracts/token_contract/src/test/access_control.nr new file mode 100644 index 00000000000..3e7150d9621 --- /dev/null +++ b/noir-projects/noir-contracts/contracts/token_contract/src/test/access_control.nr @@ -0,0 +1,51 @@ +use crate::test::utils; +use dep::aztec::test::helpers::cheatcodes; +use crate::Token; + +// Access control + +#[test] +unconstrained fn access_control() { + // Setup without account contracts. We are not using authwits here, so dummy accounts are enough + let (env, token_contract_address, owner, recipient) = utils::setup(/* with_account_contracts */ false); + + // Set a new admin + let set_admin_call_interface = Token::at(token_contract_address).set_admin(recipient); + env.call_public(set_admin_call_interface); + + // Check it worked + let get_admin_call_interface = Token::at(token_contract_address).admin(); + let admin = env.call_public(get_admin_call_interface); + assert(admin == recipient.to_field()); + + // Impersonate new admin + cheatcodes::set_contract_address(recipient); + + // Add minter as admin + let set_minter_call_interface = Token::at(token_contract_address).set_minter(recipient, true); + env.call_public(set_minter_call_interface); + + // Check it worked + let is_minter_call_interface = Token::at(token_contract_address).is_minter(recipient); + let is_minter = env.call_public(is_minter_call_interface); + assert(is_minter == true); + + // Revoke minter as admin + let set_minter_call_interface = Token::at(token_contract_address).set_minter(recipient, false); + env.call_public(set_minter_call_interface); + + // Check it worked + let is_minter = env.call_public(is_minter_call_interface); + assert(is_minter == false); + + // Impersonate original admin + cheatcodes::set_contract_address(owner); + + // Try to set ourselves as admin, fail miserably + let set_admin_call_interface = Token::at(token_contract_address).set_admin(recipient); + env.assert_public_call_fails(set_admin_call_interface); + + // Try to revoke minter status to recipient, fail miserably + let set_minter_call_interface = Token::at(token_contract_address).set_minter(recipient, false); + env.assert_public_call_fails(set_minter_call_interface); +} diff --git a/noir-projects/noir-contracts/contracts/token_contract/src/test/burn.nr b/noir-projects/noir-contracts/contracts/token_contract/src/test/burn.nr new file mode 100644 index 00000000000..fc77daa5d15 --- /dev/null +++ b/noir-projects/noir-contracts/contracts/token_contract/src/test/burn.nr @@ -0,0 +1,145 @@ +use crate::test::utils; +use dep::aztec::{test::helpers::cheatcodes, oracle::unsafe_rand::unsafe_rand}; +use dep::authwit::cheatcodes as authwit_cheatcodes; +use crate::Token; + +#[test] +unconstrained fn burn_public_success() { + let (env, token_contract_address, owner, recipient, mint_amount) = utils::setup_and_mint(/* with_account_contracts */ true); + let burn_amount = mint_amount / 10; + + // Burn less than balance + let burn_call_interface = Token::at(token_contract_address).burn_public(owner, burn_amount, 0); + env.call_public(burn_call_interface); + utils::check_public_balance(token_contract_address, owner, mint_amount - burn_amount); + + // Burn on behalf of other + let burn_call_interface = Token::at(token_contract_address).burn_public(owner, burn_amount, unsafe_rand()); + authwit_cheatcodes::add_public_authwit_from_call_interface(owner, recipient, burn_call_interface); + // Impersonate recipient to perform the call + cheatcodes::set_contract_address(recipient); + // Burn tokens + env.call_public(burn_call_interface); + utils::check_public_balance(token_contract_address, owner, mint_amount - 2 * burn_amount); +} + +#[test] +unconstrained fn burn_public_failure() { + let (env, token_contract_address, owner, recipient, mint_amount) = utils::setup_and_mint(/* with_account_contracts */ true); + + // Burn more than balance + let burn_amount = mint_amount * 10; + let burn_call_interface = Token::at(token_contract_address).burn_public(owner, burn_amount, 0); + env.assert_public_call_fails(burn_call_interface); + utils::check_public_balance(token_contract_address, owner, mint_amount); + + // Burn on behalf of self with non-zero nonce + let burn_amount = mint_amount / 10; + let burn_call_interface = Token::at(token_contract_address).burn_public(owner, burn_amount, unsafe_rand()); + env.assert_public_call_fails(burn_call_interface); + utils::check_public_balance(token_contract_address, owner, mint_amount); + + // Burn on behalf of other without approval + let burn_call_interface = Token::at(token_contract_address).burn_public(owner, burn_amount, unsafe_rand()); + // Impersonate recipient to perform the call + cheatcodes::set_contract_address(recipient); + env.assert_public_call_fails(burn_call_interface); + utils::check_public_balance(token_contract_address, owner, mint_amount); + + // Burn on behalf of other, wrong designated caller + let burn_call_interface = Token::at(token_contract_address).burn_public(owner, burn_amount, unsafe_rand()); + authwit_cheatcodes::add_public_authwit_from_call_interface(owner, owner, burn_call_interface); + // Impersonate recipient to perform the call + cheatcodes::set_contract_address(recipient); + env.assert_public_call_fails(burn_call_interface); + utils::check_public_balance(token_contract_address, owner, mint_amount); +} + +#[test] +unconstrained fn burn_private_success() { + let (env, token_contract_address, owner, recipient, mint_amount) = utils::setup_and_mint(/* with_account_contracts */ true); + let burn_amount = mint_amount / 10; + + // Burn less than balance + let burn_call_interface = Token::at(token_contract_address).burn(owner, burn_amount, 0); + env.call_private_void(burn_call_interface); + utils::check_private_balance(token_contract_address, owner, mint_amount - burn_amount); + + // Another misterious initializer nullifier failure + env.advance_block_by(1); + + // Burn on behalf of other + let burn_call_interface = Token::at(token_contract_address).burn(owner, burn_amount, unsafe_rand()); + authwit_cheatcodes::add_private_authwit_from_call_interface(owner, recipient, burn_call_interface); + // Impersonate recipient to perform the call + cheatcodes::set_contract_address(recipient); + // Burn tokens + env.call_private_void(burn_call_interface); + utils::check_private_balance(token_contract_address, owner, mint_amount - 2 * burn_amount); +} + +#[test] +unconstrained fn burn_private_failure_more_than_balance() { + let (env, token_contract_address, owner, _, mint_amount) = utils::setup_and_mint(/* with_account_contracts */ false); + + // Burn more than balance + let burn_amount = mint_amount * 10; + let burn_call_interface = Token::at(token_contract_address).burn(owner, burn_amount, 0); + env.assert_private_call_fails(burn_call_interface); + // Private doesnt revert, so we cannot check balances here since notes have already been nullified. Test is done. +} + +#[test] +unconstrained fn burn_private_failure_on_behalf_of_self_non_zero_nonce() { + let (env, token_contract_address, owner, _, mint_amount) = utils::setup_and_mint(/* with_account_contracts */ false); + + // Burn more than balance + let burn_amount = mint_amount / 10; + let burn_call_interface = Token::at(token_contract_address).burn(owner, burn_amount, unsafe_rand()); + env.assert_private_call_fails(burn_call_interface); + // Private doesnt revert, so we cannot check balances here since notes have already been nullified. Test is done. +} + +#[test] +unconstrained fn burn_private_failure_on_behalf_of_other_more_than_balance() { + let (env, token_contract_address, owner, recipient, mint_amount) = utils::setup_and_mint(/* with_account_contracts */ false); + + // Burn more than balance + let burn_amount = mint_amount * 10; + // Burn on behalf of other + let burn_call_interface = Token::at(token_contract_address).burn(owner, burn_amount, unsafe_rand()); + authwit_cheatcodes::add_private_authwit_from_call_interface(owner, recipient, burn_call_interface); + // Impersonate recipient to perform the call + cheatcodes::set_contract_address(recipient); + env.assert_private_call_fails(burn_call_interface); + // Private doesnt revert, so we cannot check balances here since notes have already been nullified. Test is done. +} + +#[test] +unconstrained fn burn_private_failure_on_behalf_of_other_without_approval() { + let (env, token_contract_address, owner, recipient, mint_amount) = utils::setup_and_mint(/* with_account_contracts */ false); + + // Burn more than balance + let burn_amount = mint_amount / 10; + // Burn on behalf of other + let burn_call_interface = Token::at(token_contract_address).burn(owner, burn_amount, unsafe_rand()); + // Impersonate recipient to perform the call + cheatcodes::set_contract_address(recipient); + env.assert_private_call_fails(burn_call_interface); + // Private doesnt revert, so we cannot check balances here since notes have already been nullified. Test is done. +} + +#[test] +unconstrained fn burn_private_failure_on_behalf_of_other_wrong_designated_caller() { + let (env, token_contract_address, owner, recipient, mint_amount) = utils::setup_and_mint(/* with_account_contracts */ false); + + // Burn more than balance + let burn_amount = mint_amount / 10; + // Burn on behalf of other + let burn_call_interface = Token::at(token_contract_address).burn(owner, burn_amount, unsafe_rand()); + authwit_cheatcodes::add_private_authwit_from_call_interface(owner, owner, burn_call_interface); + // Impersonate recipient to perform the call + cheatcodes::set_contract_address(recipient); + env.assert_private_call_fails(burn_call_interface); + // Private doesnt revert, so we cannot check balances here since notes have already been nullified. Test is done. +} diff --git a/noir-projects/noir-contracts/contracts/token_contract/src/test/transfer.nr b/noir-projects/noir-contracts/contracts/token_contract/src/test/transfer.nr new file mode 100644 index 00000000000..d87732ba758 --- /dev/null +++ b/noir-projects/noir-contracts/contracts/token_contract/src/test/transfer.nr @@ -0,0 +1,65 @@ +use crate::test::utils; +use dep::aztec::{test::helpers::cheatcodes, oracle::unsafe_rand::unsafe_rand}; +use dep::authwit::cheatcodes as authwit_cheatcodes; +use crate::Token; + +#[test] +unconstrained fn private_transfer() { + // Setup without account contracts. We are not using authwits here, so dummy accounts are enough + let (env, token_contract_address, owner, recipient, mint_amount) = utils::setup_and_mint(/* with_account_contracts */ false); + // Transfer tokens + let transfer_amount = 1000; + let private_transfer_call_interface = Token::at(token_contract_address).transfer(recipient, transfer_amount); + env.call_private_void(private_transfer_call_interface); + + // Check balances + utils::check_private_balance(token_contract_address, owner, mint_amount - transfer_amount); + utils::check_private_balance(token_contract_address, recipient, transfer_amount); +} + +#[test] +unconstrained fn private_transfer_on_behalf_of() { + // Setup with account contracts. Slower since we actually deploy them, but needed for authwits. + let (env, token_contract_address, owner, recipient, mint_amount) = utils::setup_and_mint(/* with_account_contracts */ true); + // Add authwit + let transfer_amount = 1000; + let private_transfer_from_call_interface = Token::at(token_contract_address).transfer_from(owner, recipient, transfer_amount, 1); + authwit_cheatcodes::add_private_authwit_from_call_interface(owner, recipient, private_transfer_from_call_interface); + // Impersonate recipient to perform the call + cheatcodes::set_contract_address(recipient); + // Transfer tokens + env.call_private_void(private_transfer_from_call_interface); + // Check balances + utils::check_private_balance(token_contract_address, owner, mint_amount - transfer_amount); + utils::check_private_balance(token_contract_address, recipient, transfer_amount); +} + +#[test] +unconstrained fn public_transfer() { + // Setup without account contracts. We are not using authwits here, so dummy accounts are enough + let (env, token_contract_address, owner, recipient, mint_amount) = utils::setup_and_mint(/* with_account_contracts */ false); + // Transfer tokens + let transfer_amount = 1000; + let public_transfer_call_interface = Token::at(token_contract_address).transfer_public(owner, recipient, transfer_amount, 0); + env.call_public(public_transfer_call_interface); + + // Check balances + utils::check_public_balance(token_contract_address, owner, mint_amount - transfer_amount); + utils::check_public_balance(token_contract_address, recipient, transfer_amount); +} + +#[test] +unconstrained fn public_transfer_on_behalf_of() { + // Setup with account contracts. Slower since we actually deploy them, but needed for authwits. + let (env, token_contract_address, owner, recipient, mint_amount) = utils::setup_and_mint(/* with_account_contracts */ true); + let transfer_amount = 1000; + let public_transfer_from_call_interface = Token::at(token_contract_address).transfer_public(owner, recipient, transfer_amount, 1); + authwit_cheatcodes::add_public_authwit_from_call_interface(owner, recipient, public_transfer_from_call_interface); + // Impersonate recipient to perform the call + cheatcodes::set_contract_address(recipient); + // Transfer tokens + env.call_public(public_transfer_from_call_interface); + // Check balances + utils::check_public_balance(token_contract_address, owner, mint_amount - transfer_amount); + utils::check_public_balance(token_contract_address, recipient, transfer_amount); +} diff --git a/noir-projects/noir-contracts/contracts/token_contract/src/test/unshield.nr b/noir-projects/noir-contracts/contracts/token_contract/src/test/unshield.nr new file mode 100644 index 00000000000..23b4b31d4d5 --- /dev/null +++ b/noir-projects/noir-contracts/contracts/token_contract/src/test/unshield.nr @@ -0,0 +1,12 @@ +use crate::test::utils; +use crate::Token; + +#[test] +unconstrained fn unshield() { + // Setup without account contracts. We are not using authwits here, so dummy accounts are enough + let (env, token_contract_address, owner, _, mint_amount) = utils::setup_and_mint(/* with_account_contracts */ false); + let unshield_amount = 1000; + let unshield_call_interface = Token::at(token_contract_address).unshield(owner, owner, unshield_amount, 0); + env.call_private_void(unshield_call_interface); + utils::check_public_balance(token_contract_address, owner, mint_amount + unshield_amount); +} diff --git a/noir-projects/noir-contracts/contracts/token_contract/src/test/utils.nr b/noir-projects/noir-contracts/contracts/token_contract/src/test/utils.nr new file mode 100644 index 00000000000..743d598c910 --- /dev/null +++ b/noir-projects/noir-contracts/contracts/token_contract/src/test/utils.nr @@ -0,0 +1,93 @@ +use dep::aztec::{ + hash::compute_secret_hash, prelude::AztecAddress, + test::helpers::{cheatcodes, test_environment::TestEnvironment}, + protocol_types::storage::map::derive_storage_slot_in_map, + note::{note_getter::{MAX_NOTES_PER_PAGE, view_notes}, note_viewer_options::NoteViewerOptions}, + oracle::storage::storage_read +}; + +use crate::{types::{token_note::TokenNote, transparent_note::TransparentNote}, Token}; + +pub fn setup(with_account_contracts: bool) -> (&mut TestEnvironment, AztecAddress, AztecAddress, AztecAddress) { + // Setup env, generate keys + let mut env = TestEnvironment::new(); + let (owner, recipient) = if with_account_contracts { + let owner = env.create_account_contract(1); + let recipient = env.create_account_contract(2); + // Deploy canonical auth registry + let _auth_registry = env.deploy("@aztec/noir-contracts.js/AuthRegistry").without_initializer(); + (owner, recipient) + } else { + let owner = env.create_account(); + let recipient = env.create_account(); + (owner, recipient) + }; + + // Start the test in the account contract address + cheatcodes::set_contract_address(owner); + + // Deploy token contract + let initializer_call_interface = Token::interface().constructor( + owner, + "TestToken0000000000000000000000", + "TT00000000000000000000000000000", + 18 + ); + let token_contract = env.deploy("@aztec/noir-contracts.js/Token").with_public_initializer(initializer_call_interface); + let token_contract_address = token_contract.to_address(); + env.advance_block_by(1); + (&mut env, token_contract_address, owner, recipient) +} + +pub fn setup_and_mint(with_account_contracts: bool) -> (&mut TestEnvironment, AztecAddress, AztecAddress, AztecAddress, Field) { + // Setup + let (env, token_contract_address, owner, recipient) = setup(with_account_contracts); + let mint_amount = 10000; + // Mint some tokens + let secret = 1; + let secret_hash = compute_secret_hash(secret); + let mint_private_call_interface = Token::at(token_contract_address).mint_private(mint_amount, secret_hash); + env.call_public(mint_private_call_interface); + + let mint_public_call_interface = Token::at(token_contract_address).mint_public(owner, mint_amount); + env.call_public(mint_public_call_interface); + + // Time travel so we can read keys from the registry + env.advance_block_by(6); + + // Store a note in the cache so we can redeem it + env.store_note_in_cache( + &mut TransparentNote::new(mint_amount, secret_hash), + Token::storage().pending_shields.slot, + token_contract_address + ); + + // Redeem our shielded tokens + let redeem_shield_call_interface = Token::at(token_contract_address).redeem_shield(owner, mint_amount, secret); + env.call_private_void(redeem_shield_call_interface); + + // Not really sure why this is needed? Nullifier inclusion in contract initializer fails otherwise. + // If it were to fail, it should do it at line 443, investigation required + env.advance_block_by(1); + + (env, token_contract_address, owner, recipient, mint_amount) +} + +pub fn check_public_balance(token_contract_address: AztecAddress, address: AztecAddress, address_amount: Field) { + cheatcodes::set_contract_address(token_contract_address); + + let balances_slot = Token::storage().public_balances.slot; + let address_slot = derive_storage_slot_in_map(balances_slot, address); + let fields = storage_read(address_slot); + assert(U128::deserialize(fields).to_field() == address_amount); +} + +pub fn check_private_balance(token_contract_address: AztecAddress, address: AztecAddress, address_amount: Field) { + cheatcodes::set_contract_address(token_contract_address); + + let balances_slot = Token::storage().balances.slot; + let address_slot = derive_storage_slot_in_map(balances_slot, address); + let mut options = NoteViewerOptions::new(); + let opt_notes: [Option; MAX_NOTES_PER_PAGE] = view_notes(address_slot, options); + assert(opt_notes[0].unwrap().amount.to_field() == address_amount); +} diff --git a/noir/noir-repo/aztec_macros/src/transforms/contract_interface.rs b/noir/noir-repo/aztec_macros/src/transforms/contract_interface.rs index 1875ab0b252..8b763dfcc57 100644 --- a/noir/noir-repo/aztec_macros/src/transforms/contract_interface.rs +++ b/noir/noir-repo/aztec_macros/src/transforms/contract_interface.rs @@ -155,9 +155,17 @@ pub fn stub_function(aztec_visibility: &str, func: &NoirFunction, is_static_call name: \"{}\", args_hash, args: args_acc, - original: {} + original: {}, + is_static: {} }}", - args_hash, fn_selector, aztec_visibility, is_static, is_void, fn_name, original + args_hash, + fn_selector, + aztec_visibility, + is_static, + is_void, + fn_name, + original, + is_static_call ) } else { let args = format!( @@ -175,9 +183,17 @@ pub fn stub_function(aztec_visibility: &str, func: &NoirFunction, is_static_call name: \"{}\", args: args_acc, gas_opts: dep::aztec::context::gas::GasOpts::default(), - original: {} + original: {}, + is_static: {} }}", - args, fn_selector, aztec_visibility, is_static, is_void, fn_name, original + args, + fn_selector, + aztec_visibility, + is_static, + is_void, + fn_name, + original, + is_static_call ) }; diff --git a/yarn-project/txe/src/oracle/txe_oracle.ts b/yarn-project/txe/src/oracle/txe_oracle.ts index 91f71b08cae..f0110c40fbb 100644 --- a/yarn-project/txe/src/oracle/txe_oracle.ts +++ b/yarn-project/txe/src/oracle/txe_oracle.ts @@ -243,7 +243,7 @@ export class TXE implements TypedOracle { } async getContractInstance(address: AztecAddress): Promise { - const contractInstance = await this.txeDatabase.getContractInstance(address); + const contractInstance = await this.contractDataOracle.getContractInstance(address); if (!contractInstance) { throw new Error(`Contract instance not found for address ${address}`); } @@ -517,49 +517,52 @@ export class TXE implements TypedOracle { const initialWitness = await this.getInitialWitness(artifact, argsHash, sideEffectCounter); const acvmCallback = new Oracle(this); const timer = new Timer(); - const acirExecutionResult = await acvm(acir, initialWitness, acvmCallback).catch((err: Error) => { - const execError = new ExecutionError( - err.message, - { - contractAddress: targetContractAddress, - functionSelector, - }, - extractCallStack(err, artifact.debug), - { cause: err }, - ); - this.logger.debug( - `Error executing private function ${targetContractAddress}:${functionSelector}\n${createSimulationError( - execError, - )}`, + try { + const acirExecutionResult = await acvm(acir, initialWitness, acvmCallback).catch((err: Error) => { + const execError = new ExecutionError( + err.message, + { + contractAddress: targetContractAddress, + functionSelector, + }, + extractCallStack(err, artifact.debug), + { cause: err }, + ); + this.logger.debug( + `Error executing private function ${targetContractAddress}:${functionSelector}\n${createSimulationError( + execError, + )}`, + ); + throw execError; + }); + const duration = timer.ms(); + const returnWitness = witnessMapToFields(acirExecutionResult.returnWitness); + const publicInputs = PrivateCircuitPublicInputs.fromFields(returnWitness); + + const initialWitnessSize = witnessMapToFields(initialWitness).length * Fr.SIZE_IN_BYTES; + this.logger.debug(`Ran external function ${targetContractAddress.toString()}:${functionSelector}`, { + circuitName: 'app-circuit', + duration, + eventName: 'circuit-witness-generation', + inputSize: initialWitnessSize, + outputSize: publicInputs.toBuffer().length, + appCircuitName: 'noname', + } satisfies CircuitWitnessGenerationStats); + + const callStackItem = new PrivateCallStackItem( + targetContractAddress, + new FunctionData(functionSelector, true), + publicInputs, ); - throw execError; - }); - const duration = timer.ms(); - const returnWitness = witnessMapToFields(acirExecutionResult.returnWitness); - const publicInputs = PrivateCircuitPublicInputs.fromFields(returnWitness); - - const initialWitnessSize = witnessMapToFields(initialWitness).length * Fr.SIZE_IN_BYTES; - this.logger.debug(`Ran external function ${targetContractAddress.toString()}:${functionSelector}`, { - circuitName: 'app-circuit', - duration, - eventName: 'circuit-witness-generation', - inputSize: initialWitnessSize, - outputSize: publicInputs.toBuffer().length, - appCircuitName: 'noname', - } satisfies CircuitWitnessGenerationStats); - - const callStackItem = new PrivateCallStackItem( - targetContractAddress, - new FunctionData(functionSelector, true), - publicInputs, - ); - // Apply side effects - this.sideEffectsCounter += publicInputs.endSideEffectCounter.toNumber(); - this.setContractAddress(currentContractAddress); - this.setMsgSender(currentMessageSender); - this.setFunctionSelector(currentFunctionSelector); - - return callStackItem; + // Apply side effects + this.sideEffectsCounter += publicInputs.endSideEffectCounter.toNumber(); + + return callStackItem; + } finally { + this.setContractAddress(currentContractAddress); + this.setMsgSender(currentMessageSender); + this.setFunctionSelector(currentFunctionSelector); + } } async getInitialWitness(abi: FunctionAbi, argsHash: Fr, sideEffectCounter: number) { @@ -579,11 +582,11 @@ export class TXE implements TypedOracle { } public async getDebugFunctionName(address: AztecAddress, selector: FunctionSelector): Promise { - const instance = await this.txeDatabase.getContractInstance(address); + const instance = await this.contractDataOracle.getContractInstance(address); if (!instance) { return Promise.resolve(undefined); } - const artifact = await this.txeDatabase.getContractArtifact(instance!.contractClassId); + const artifact = await this.contractDataOracle.getContractArtifact(instance!.contractClassId); if (!artifact) { return Promise.resolve(undefined); } @@ -683,6 +686,45 @@ export class TXE implements TypedOracle { return executionResult; } + async avmOpcodeCall( + targetContractAddress: AztecAddress, + functionSelector: FunctionSelector, + args: Fr[], + isStaticCall: boolean, + isDelegateCall: boolean, + ) { + // Store and modify env + const currentContractAddress = AztecAddress.fromField(this.contractAddress); + const currentMessageSender = AztecAddress.fromField(this.msgSender); + const currentFunctionSelector = FunctionSelector.fromField(this.functionSelector.toField()); + this.setMsgSender(this.contractAddress); + this.setContractAddress(targetContractAddress); + this.setFunctionSelector(functionSelector); + + const callContext = CallContext.empty(); + callContext.msgSender = this.msgSender; + callContext.functionSelector = this.functionSelector; + callContext.sideEffectCounter = this.sideEffectsCounter; + callContext.storageContractAddress = targetContractAddress; + callContext.isStaticCall = isStaticCall; + callContext.isDelegateCall = isDelegateCall; + + const executionResult = await this.executePublicFunction( + targetContractAddress, + functionSelector, + args, + callContext, + ); + + // Apply side effects + this.sideEffectsCounter += executionResult.endSideEffectCounter.toNumber(); + this.setContractAddress(currentContractAddress); + this.setMsgSender(currentMessageSender); + this.setFunctionSelector(currentFunctionSelector); + + return executionResult; + } + async callPublicFunction( targetContractAddress: AztecAddress, functionSelector: FunctionSelector, diff --git a/yarn-project/txe/src/txe_service/txe_service.ts b/yarn-project/txe/src/txe_service/txe_service.ts index 26ef38ad0a4..347d59c4b4c 100644 --- a/yarn-project/txe/src/txe_service/txe_service.ts +++ b/yarn-project/txe/src/txe_service/txe_service.ts @@ -28,6 +28,7 @@ import { toForeignCallResult, toSingle, } from '../util/encoding.js'; +import { ExpectedFailureError } from '../util/expected_failure_error.js'; import { TXEDatabase } from '../util/txe_database.js'; export class TXEService { @@ -214,6 +215,54 @@ export class TXEService { return toForeignCallResult([]); } + async assertPublicCallFails( + address: ForeignCallSingle, + functionSelector: ForeignCallSingle, + _length: ForeignCallSingle, + args: ForeignCallArray, + ) { + const parsedAddress = fromSingle(address); + const parsedSelector = FunctionSelector.fromField(fromSingle(functionSelector)); + let result = await (this.typedOracle as TXE).avmOpcodeCall( + parsedAddress, + parsedSelector, + fromArray(args), + false, + false, + ); + if (!result.reverted) { + throw new ExpectedFailureError('Public call did not revert'); + } + + return toForeignCallResult([]); + } + + async assertPrivateCallFails( + targetContractAddress: ForeignCallSingle, + functionSelector: ForeignCallSingle, + argsHash: ForeignCallSingle, + sideEffectCounter: ForeignCallSingle, + isStaticCall: ForeignCallSingle, + isDelegateCall: ForeignCallSingle, + ) { + try { + await this.typedOracle.callPrivateFunction( + fromSingle(targetContractAddress), + FunctionSelector.fromField(fromSingle(functionSelector)), + fromSingle(argsHash), + fromSingle(sideEffectCounter).toNumber(), + fromSingle(isStaticCall).toBool(), + fromSingle(isDelegateCall).toBool(), + ); + throw new ExpectedFailureError('Private call did not fail'); + } catch (e) { + if (e instanceof ExpectedFailureError) { + throw e; + } + } + return toForeignCallResult([]); + } + // PXE oracles getRandomField() { @@ -451,6 +500,24 @@ export class TXEService { return toForeignCallResult([toSingle(new Fr(exists))]); } + async avmOpcodeCall( + _gas: ForeignCallArray, + address: ForeignCallSingle, + _length: ForeignCallSingle, + args: ForeignCallArray, + functionSelector: ForeignCallSingle, + ) { + const result = await (this.typedOracle as TXE).avmOpcodeCall( + fromSingle(address), + FunctionSelector.fromField(fromSingle(functionSelector)), + fromArray(args), + false, + false, + ); + + return toForeignCallResult([toArray(result.returnValues), toSingle(new Fr(1))]); + } + async getPublicKeysAndPartialAddress(address: ForeignCallSingle) { const parsedAddress = AztecAddress.fromField(fromSingle(address)); const { publicKeys, partialAddress } = await this.typedOracle.getCompleteAddress(parsedAddress); diff --git a/yarn-project/txe/src/util/expected_failure_error.ts b/yarn-project/txe/src/util/expected_failure_error.ts new file mode 100644 index 00000000000..8f97a3ae2bf --- /dev/null +++ b/yarn-project/txe/src/util/expected_failure_error.ts @@ -0,0 +1,5 @@ +export class ExpectedFailureError extends Error { + constructor(message: string) { + super(message); + } +} diff --git a/yarn-project/txe/src/util/txe_public_contract_data_source.ts b/yarn-project/txe/src/util/txe_public_contract_data_source.ts index 98d128fabc9..47f1ca37dcb 100644 --- a/yarn-project/txe/src/util/txe_public_contract_data_source.ts +++ b/yarn-project/txe/src/util/txe_public_contract_data_source.ts @@ -56,8 +56,9 @@ export class TXEPublicContractDataSource implements ContractDataSource { throw new Error('Method not implemented.'); } - getContractArtifact(address: AztecAddress): Promise { - return this.txeOracle.getContractDataOracle().getContractArtifact(address); + async getContractArtifact(address: AztecAddress): Promise { + const instance = await this.txeOracle.getContractDataOracle().getContractInstance(address); + return this.txeOracle.getContractDataOracle().getContractArtifact(instance.contractClassId); } addContractArtifact(address: AztecAddress, contract: ContractArtifact): Promise { From fcf7823b129fe502988ad2564091a2efafd19de6 Mon Sep 17 00:00:00 2001 From: thunkar Date: Fri, 21 Jun 2024 10:18:26 +0000 Subject: [PATCH 58/75] more tests --- .../contracts/token_contract/src/test.nr | 1 + .../contracts/token_contract/src/test/burn.nr | 20 +- .../token_contract/src/test/minting.nr | 248 ++++++++++++++++++ .../token_contract/src/test/utils.nr | 5 + yarn-project/txe/package.json | 3 +- yarn-project/txe/src/bin/index.ts | 4 +- yarn-project/txe/src/oracle/txe_oracle.ts | 4 +- .../txe/src/txe_service/txe_service.ts | 2 +- 8 files changed, 272 insertions(+), 15 deletions(-) create mode 100644 noir-projects/noir-contracts/contracts/token_contract/src/test/minting.nr diff --git a/noir-projects/noir-contracts/contracts/token_contract/src/test.nr b/noir-projects/noir-contracts/contracts/token_contract/src/test.nr index d63bcd4d625..2cbb088e60c 100644 --- a/noir-projects/noir-contracts/contracts/token_contract/src/test.nr +++ b/noir-projects/noir-contracts/contracts/token_contract/src/test.nr @@ -3,3 +3,4 @@ mod burn; mod utils; mod transfer; mod unshield; +mod minting; diff --git a/noir-projects/noir-contracts/contracts/token_contract/src/test/burn.nr b/noir-projects/noir-contracts/contracts/token_contract/src/test/burn.nr index fc77daa5d15..0653e506bd3 100644 --- a/noir-projects/noir-contracts/contracts/token_contract/src/test/burn.nr +++ b/noir-projects/noir-contracts/contracts/token_contract/src/test/burn.nr @@ -78,29 +78,29 @@ unconstrained fn burn_private_success() { utils::check_private_balance(token_contract_address, owner, mint_amount - 2 * burn_amount); } -#[test] +#[test(should_fail_with="Balance too low")] unconstrained fn burn_private_failure_more_than_balance() { let (env, token_contract_address, owner, _, mint_amount) = utils::setup_and_mint(/* with_account_contracts */ false); // Burn more than balance let burn_amount = mint_amount * 10; let burn_call_interface = Token::at(token_contract_address).burn(owner, burn_amount, 0); - env.assert_private_call_fails(burn_call_interface); + env.call_private_void(burn_call_interface); // Private doesnt revert, so we cannot check balances here since notes have already been nullified. Test is done. } -#[test] +#[test(should_fail_with="invalid nonce")] unconstrained fn burn_private_failure_on_behalf_of_self_non_zero_nonce() { let (env, token_contract_address, owner, _, mint_amount) = utils::setup_and_mint(/* with_account_contracts */ false); // Burn more than balance let burn_amount = mint_amount / 10; let burn_call_interface = Token::at(token_contract_address).burn(owner, burn_amount, unsafe_rand()); - env.assert_private_call_fails(burn_call_interface); + env.call_private_void(burn_call_interface); // Private doesnt revert, so we cannot check balances here since notes have already been nullified. Test is done. } -#[test] +#[test(should_fail)] unconstrained fn burn_private_failure_on_behalf_of_other_more_than_balance() { let (env, token_contract_address, owner, recipient, mint_amount) = utils::setup_and_mint(/* with_account_contracts */ false); @@ -111,11 +111,11 @@ unconstrained fn burn_private_failure_on_behalf_of_other_more_than_balance() { authwit_cheatcodes::add_private_authwit_from_call_interface(owner, recipient, burn_call_interface); // Impersonate recipient to perform the call cheatcodes::set_contract_address(recipient); - env.assert_private_call_fails(burn_call_interface); + env.call_private_void(burn_call_interface); // Private doesnt revert, so we cannot check balances here since notes have already been nullified. Test is done. } -#[test] +#[test(should_fail)] unconstrained fn burn_private_failure_on_behalf_of_other_without_approval() { let (env, token_contract_address, owner, recipient, mint_amount) = utils::setup_and_mint(/* with_account_contracts */ false); @@ -125,11 +125,11 @@ unconstrained fn burn_private_failure_on_behalf_of_other_without_approval() { let burn_call_interface = Token::at(token_contract_address).burn(owner, burn_amount, unsafe_rand()); // Impersonate recipient to perform the call cheatcodes::set_contract_address(recipient); - env.assert_private_call_fails(burn_call_interface); + env.call_private_void(burn_call_interface); // Private doesnt revert, so we cannot check balances here since notes have already been nullified. Test is done. } -#[test] +#[test(should_fail)] unconstrained fn burn_private_failure_on_behalf_of_other_wrong_designated_caller() { let (env, token_contract_address, owner, recipient, mint_amount) = utils::setup_and_mint(/* with_account_contracts */ false); @@ -140,6 +140,6 @@ unconstrained fn burn_private_failure_on_behalf_of_other_wrong_designated_caller authwit_cheatcodes::add_private_authwit_from_call_interface(owner, owner, burn_call_interface); // Impersonate recipient to perform the call cheatcodes::set_contract_address(recipient); - env.assert_private_call_fails(burn_call_interface); + env.call_private_void(burn_call_interface); // Private doesnt revert, so we cannot check balances here since notes have already been nullified. Test is done. } diff --git a/noir-projects/noir-contracts/contracts/token_contract/src/test/minting.nr b/noir-projects/noir-contracts/contracts/token_contract/src/test/minting.nr new file mode 100644 index 00000000000..4e05a128536 --- /dev/null +++ b/noir-projects/noir-contracts/contracts/token_contract/src/test/minting.nr @@ -0,0 +1,248 @@ +use crate::test::utils; +use dep::aztec::{test::helpers::cheatcodes, oracle::unsafe_rand::unsafe_rand, hash::compute_secret_hash}; +use crate::{types::transparent_note::TransparentNote, Token}; + +#[test] +unconstrained fn mint_public_success() { + // Setup without account contracts. We are not using authwits here, so dummy accounts are enough + let (env, token_contract_address, owner, _) = utils::setup(/* with_account_contracts */ false); + + let mint_amount = 10000; + let mint_public_call_interface = Token::at(token_contract_address).mint_public(owner, mint_amount); + env.call_public(mint_public_call_interface); + + utils::check_public_balance(token_contract_address, owner, mint_amount); + + let total_supply_call_interface = Token::at(token_contract_address).total_supply(); + let total_supply = env.call_public(total_supply_call_interface); + + assert(total_supply == mint_amount); +} + +#[test] +unconstrained fn mint_public_failures() { + // Setup without account contracts. We are not using authwits here, so dummy accounts are enough + let (env, token_contract_address, owner, recipient) = utils::setup(/* with_account_contracts */ false); + + // As non-minter + let mint_amount = 10000; + cheatcodes::set_contract_address(recipient); + let mint_public_call_interface = Token::at(token_contract_address).mint_public(owner, mint_amount); + env.assert_public_call_fails(mint_public_call_interface); + + utils::check_public_balance(token_contract_address, owner, 0); + + cheatcodes::set_contract_address(owner); + + // Overflow recipient + + let mint_amount = 2.pow_32(128); + let mint_public_call_interface = Token::at(token_contract_address).mint_public(owner, mint_amount); + env.assert_public_call_fails(mint_public_call_interface); + + utils::check_public_balance(token_contract_address, owner, 0); + + // Overflow total supply + + let mint_for_recipient_amount = 1000; + + let mint_public_call_interface = Token::at(token_contract_address).mint_public(recipient, mint_for_recipient_amount); + env.call_public(mint_public_call_interface); + + let mint_amount = 2.pow_32(128) - mint_for_recipient_amount; + let mint_public_call_interface = Token::at(token_contract_address).mint_public(owner, mint_amount); + env.assert_public_call_fails(mint_public_call_interface); + + utils::check_public_balance(token_contract_address, recipient, mint_for_recipient_amount); + utils::check_public_balance(token_contract_address, owner, 0); +} + +#[test] +unconstrained fn mint_private_success() { + // Setup without account contracts. We are not using authwits here, so dummy accounts are enough + let (env, token_contract_address, owner, _) = utils::setup(/* with_account_contracts */ false); + let mint_amount = 10000; + // Mint some tokens + let secret = 1; + let secret_hash = compute_secret_hash(secret); + let mint_private_call_interface = Token::at(token_contract_address).mint_private(mint_amount, secret_hash); + env.call_public(mint_private_call_interface); + + let mint_public_call_interface = Token::at(token_contract_address).mint_public(owner, mint_amount); + env.call_public(mint_public_call_interface); + + // Time travel so we can read keys from the registry + env.advance_block_by(6); + + // Store a note in the cache so we can redeem it + env.store_note_in_cache( + &mut TransparentNote::new(mint_amount, secret_hash), + Token::storage().pending_shields.slot, + token_contract_address + ); + + // Redeem our shielded tokens + let redeem_shield_call_interface = Token::at(token_contract_address).redeem_shield(owner, mint_amount, secret); + env.call_private_void(redeem_shield_call_interface); + + utils::check_private_balance(token_contract_address, owner, mint_amount); +} + +#[test(should_fail_with="Cannot return zero notes")] +unconstrained fn mint_private_failure_double_spend() { + // Setup without account contracts. We are not using authwits here, so dummy accounts are enough + let (env, token_contract_address, owner, recipient) = utils::setup(/* with_account_contracts */ false); + let mint_amount = 10000; + // Mint some tokens + let secret = 1; + let secret_hash = compute_secret_hash(secret); + let mint_private_call_interface = Token::at(token_contract_address).mint_private(mint_amount, secret_hash); + env.call_public(mint_private_call_interface); + + let mint_public_call_interface = Token::at(token_contract_address).mint_public(owner, mint_amount); + env.call_public(mint_public_call_interface); + + // Time travel so we can read keys from the registry + env.advance_block_by(6); + + // Store a note in the cache so we can redeem it + env.store_note_in_cache( + &mut TransparentNote::new(mint_amount, secret_hash), + Token::storage().pending_shields.slot, + token_contract_address + ); + + // Redeem our shielded tokens + let redeem_shield_call_interface = Token::at(token_contract_address).redeem_shield(owner, mint_amount, secret); + env.call_private_void(redeem_shield_call_interface); + + utils::check_private_balance(token_contract_address, owner, mint_amount); + + // TODO: https://github.com/AztecProtocol/aztec-packages/issues/7086 + env.advance_block_by(1); + + // Attempt to double spend + let redeem_shield_call_interface = Token::at(token_contract_address).redeem_shield(recipient, mint_amount, secret); + env.call_private_void(redeem_shield_call_interface); +} + +#[test(should_fail_with="caller is not minter")] +unconstrained fn mint_private_failure_non_minter() { + // Setup without account contracts. We are not using authwits here, so dummy accounts are enough + let (env, token_contract_address, owner, recipient) = utils::setup(/* with_account_contracts */ false); + let mint_amount = 10000; + // Try to mint some tokens impersonating recipient + cheatcodes::set_contract_address(recipient); + + let secret = 1; + let secret_hash = compute_secret_hash(secret); + let mint_private_call_interface = Token::at(token_contract_address).mint_private(mint_amount, secret_hash); + env.call_public(mint_private_call_interface); +} + +#[test(should_fail_with="call to assert_max_bit_size")] +unconstrained fn mint_private_failure_overflow() { + // Setup without account contracts. We are not using authwits here, so dummy accounts are enough + let (env, token_contract_address, _, _) = utils::setup(/* with_account_contracts */ false); + + // Overflow recipient + let mint_amount = 2.pow_32(128); + let secret = 1; + let secret_hash = compute_secret_hash(secret); + let mint_private_call_interface = Token::at(token_contract_address).mint_private(mint_amount, secret_hash); + env.call_public(mint_private_call_interface); +} + +#[test(should_fail_with="attempt to add with overflow")] +unconstrained fn mint_private_failure_overflow_recipient() { + // Setup without account contracts. We are not using authwits here, so dummy accounts are enough + let (env, token_contract_address, owner, recipient) = utils::setup(/* with_account_contracts */ false); + let mint_amount = 10000; + // Mint some tokens + let secret = 1; + let secret_hash = compute_secret_hash(secret); + let mint_private_call_interface = Token::at(token_contract_address).mint_private(mint_amount, secret_hash); + env.call_public(mint_private_call_interface); + + // Time travel so we can read keys from the registry + env.advance_block_by(6); + + // Store a note in the cache so we can redeem it + env.store_note_in_cache( + &mut TransparentNote::new(mint_amount, secret_hash), + Token::storage().pending_shields.slot, + token_contract_address + ); + + // Redeem our shielded tokens + let redeem_shield_call_interface = Token::at(token_contract_address).redeem_shield(owner, mint_amount, secret); + env.call_private_void(redeem_shield_call_interface); + + utils::check_private_balance(token_contract_address, owner, mint_amount); + + // TODO: https://github.com/AztecProtocol/aztec-packages/issues/7086 + env.advance_block_by(1); + + let mint_amount = 2.pow_32(128) - mint_amount; + // Mint some tokens + let secret = 1; + let secret_hash = compute_secret_hash(secret); + let mint_private_call_interface = Token::at(token_contract_address).mint_private(mint_amount, secret_hash); + env.call_public(mint_private_call_interface); +} + +#[test(should_fail_with="attempt to add with overflow")] +unconstrained fn mint_private_failure_overflow_total_supply() { + // Setup without account contracts. We are not using authwits here, so dummy accounts are enough + let (env, token_contract_address, owner, recipient) = utils::setup(/* with_account_contracts */ false); + let mint_amount = 10000; + // Mint some tokens + let secret_owner = 1; + let secret_recipient = 2; + let secret_hash_owner = compute_secret_hash(secret_owner); + let secret_hash_recipient = compute_secret_hash(secret_recipient); + + let mint_private_call_interface = Token::at(token_contract_address).mint_private(mint_amount, secret_hash_owner); + env.call_public(mint_private_call_interface); + let mint_private_call_interface = Token::at(token_contract_address).mint_private(mint_amount, secret_hash_recipient); + env.call_public(mint_private_call_interface); + + // Time travel so we can read keys from the registry + env.advance_block_by(6); + + // Store 2 notes in the cache so we can redeem it for owner and recipient + env.store_note_in_cache( + &mut TransparentNote::new(mint_amount, secret_hash_owner), + Token::storage().pending_shields.slot, + token_contract_address + ); + env.store_note_in_cache( + &mut TransparentNote::new(mint_amount, secret_hash_recipient), + Token::storage().pending_shields.slot, + token_contract_address + ); + + // Redeem owner's shielded tokens + cheatcodes::set_contract_address(owner); + let redeem_shield_call_interface = Token::at(token_contract_address).redeem_shield(owner, mint_amount, secret_owner); + env.call_private_void(redeem_shield_call_interface); + + // TODO: https://github.com/AztecProtocol/aztec-packages/issues/7086 + env.advance_block_by(1); + + // Redeem recipient's shielded tokens + cheatcodes::set_contract_address(recipient); + let redeem_shield_call_interface = Token::at(token_contract_address).redeem_shield(recipient, mint_amount, secret_recipient); + env.call_private_void(redeem_shield_call_interface); + + utils::check_private_balance(token_contract_address, owner, mint_amount); + utils::check_private_balance(token_contract_address, recipient, mint_amount); + + cheatcodes::set_contract_address(owner); + let mint_amount = 2.pow_32(128) - 2 * mint_amount; + // Try to mint some tokens + let secret = 1; + let secret_hash = compute_secret_hash(secret); + let mint_private_call_interface = Token::at(token_contract_address).mint_private(mint_amount, secret_hash); + env.call_public(mint_private_call_interface); +} diff --git a/noir-projects/noir-contracts/contracts/token_contract/src/test/utils.nr b/noir-projects/noir-contracts/contracts/token_contract/src/test/utils.nr index 743d598c910..0a90ff8ac31 100644 --- a/noir-projects/noir-contracts/contracts/token_contract/src/test/utils.nr +++ b/noir-projects/noir-contracts/contracts/token_contract/src/test/utils.nr @@ -66,6 +66,7 @@ pub fn setup_and_mint(with_account_contracts: bool) -> (&mut TestEnvironment, Az let redeem_shield_call_interface = Token::at(token_contract_address).redeem_shield(owner, mint_amount, secret); env.call_private_void(redeem_shield_call_interface); + // TODO: https://github.com/AztecProtocol/aztec-packages/issues/7086 // Not really sure why this is needed? Nullifier inclusion in contract initializer fails otherwise. // If it were to fail, it should do it at line 443, investigation required env.advance_block_by(1); @@ -74,15 +75,18 @@ pub fn setup_and_mint(with_account_contracts: bool) -> (&mut TestEnvironment, Az } pub fn check_public_balance(token_contract_address: AztecAddress, address: AztecAddress, address_amount: Field) { + let current_contract_address = cheatcodes::get_contract_address(); cheatcodes::set_contract_address(token_contract_address); let balances_slot = Token::storage().public_balances.slot; let address_slot = derive_storage_slot_in_map(balances_slot, address); let fields = storage_read(address_slot); assert(U128::deserialize(fields).to_field() == address_amount); + cheatcodes::set_contract_address(current_contract_address); } pub fn check_private_balance(token_contract_address: AztecAddress, address: AztecAddress, address_amount: Field) { + let current_contract_address = cheatcodes::get_contract_address(); cheatcodes::set_contract_address(token_contract_address); let balances_slot = Token::storage().balances.slot; @@ -90,4 +94,5 @@ pub fn check_private_balance(token_contract_address: AztecAddress, address: Azte let mut options = NoteViewerOptions::new(); let opt_notes: [Option; MAX_NOTES_PER_PAGE] = view_notes(address_slot, options); assert(opt_notes[0].unwrap().amount.to_field() == address_amount); + cheatcodes::set_contract_address(current_contract_address); } diff --git a/yarn-project/txe/package.json b/yarn-project/txe/package.json index 5658cd3b454..506521672f5 100644 --- a/yarn-project/txe/package.json +++ b/yarn-project/txe/package.json @@ -18,7 +18,8 @@ "formatting": "run -T prettier --check ./src && run -T eslint ./src", "formatting:fix": "run -T eslint --fix ./src && run -T prettier -w ./src", "test": "NODE_NO_WARNINGS=1 node --experimental-vm-modules ../node_modules/.bin/jest --passWithNoTests", - "start": "DEBUG='aztec:*' && node ./dest/bin/index.js" + "dev": "DEBUG='aztec:*' && node ./dest/bin/index.js", + "start": "node ./dest/bin/index.js" }, "inherits": [ "../package.common.json" diff --git a/yarn-project/txe/src/bin/index.ts b/yarn-project/txe/src/bin/index.ts index 5c5b41bc873..62eb72f7a63 100644 --- a/yarn-project/txe/src/bin/index.ts +++ b/yarn-project/txe/src/bin/index.ts @@ -35,13 +35,13 @@ class TXEDispatcher { this.logger.debug(`Calling ${functionName} on session ${sessionId}`); if (!TXESessions.has(sessionId) && functionName != 'reset') { - this.logger.debug(`Creating new session ${sessionId}`); + this.logger.info(`Creating new session ${sessionId}`); TXESessions.set(sessionId, await TXEService.init(logger)); } if (functionName === 'reset') { TXESessions.delete(sessionId) && - this.logger.debug(`Called reset on session ${sessionId}, yeeting it out of existence`); + this.logger.info(`Called reset on session ${sessionId}, yeeting it out of existence`); return toForeignCallResult([]); } else { const txeService = TXESessions.get(sessionId); diff --git a/yarn-project/txe/src/oracle/txe_oracle.ts b/yarn-project/txe/src/oracle/txe_oracle.ts index f0110c40fbb..f14badd91cc 100644 --- a/yarn-project/txe/src/oracle/txe_oracle.ts +++ b/yarn-project/txe/src/oracle/txe_oracle.ts @@ -717,7 +717,9 @@ export class TXE implements TypedOracle { ); // Apply side effects - this.sideEffectsCounter += executionResult.endSideEffectCounter.toNumber(); + if (!executionResult.reverted) { + this.sideEffectsCounter += executionResult.endSideEffectCounter.toNumber(); + } this.setContractAddress(currentContractAddress); this.setMsgSender(currentMessageSender); this.setFunctionSelector(currentFunctionSelector); diff --git a/yarn-project/txe/src/txe_service/txe_service.ts b/yarn-project/txe/src/txe_service/txe_service.ts index 347d59c4b4c..b1ae9449293 100644 --- a/yarn-project/txe/src/txe_service/txe_service.ts +++ b/yarn-project/txe/src/txe_service/txe_service.ts @@ -62,7 +62,7 @@ export class TXEService { async timeTravel(blocks: ForeignCallSingle) { const nBlocks = fromSingle(blocks).toNumber(); - this.logger.info(`time traveling ${nBlocks} blocks`); + this.logger.debug(`time traveling ${nBlocks} blocks`); const trees = (this.typedOracle as TXE).getTrees(); const header = Header.empty(); const l2Block = L2Block.empty(); From 5dc4c9a0202c7fff4b928f54133790847d711692 Mon Sep 17 00:00:00 2001 From: thunkar Date: Fri, 21 Jun 2024 11:20:00 +0000 Subject: [PATCH 59/75] test constrained --- .../token_contract/src/test/access_control.nr | 2 +- .../contracts/token_contract/src/test/burn.nr | 16 ++++++++-------- .../contracts/token_contract/src/test/minting.nr | 16 ++++++++-------- .../token_contract/src/test/transfer.nr | 8 ++++---- .../token_contract/src/test/unshield.nr | 2 +- 5 files changed, 22 insertions(+), 22 deletions(-) diff --git a/noir-projects/noir-contracts/contracts/token_contract/src/test/access_control.nr b/noir-projects/noir-contracts/contracts/token_contract/src/test/access_control.nr index 3e7150d9621..9511cb802ac 100644 --- a/noir-projects/noir-contracts/contracts/token_contract/src/test/access_control.nr +++ b/noir-projects/noir-contracts/contracts/token_contract/src/test/access_control.nr @@ -5,7 +5,7 @@ use crate::Token; // Access control #[test] -unconstrained fn access_control() { +fn access_control() { // Setup without account contracts. We are not using authwits here, so dummy accounts are enough let (env, token_contract_address, owner, recipient) = utils::setup(/* with_account_contracts */ false); diff --git a/noir-projects/noir-contracts/contracts/token_contract/src/test/burn.nr b/noir-projects/noir-contracts/contracts/token_contract/src/test/burn.nr index 0653e506bd3..c55d429d2bc 100644 --- a/noir-projects/noir-contracts/contracts/token_contract/src/test/burn.nr +++ b/noir-projects/noir-contracts/contracts/token_contract/src/test/burn.nr @@ -4,7 +4,7 @@ use dep::authwit::cheatcodes as authwit_cheatcodes; use crate::Token; #[test] -unconstrained fn burn_public_success() { +fn burn_public_success() { let (env, token_contract_address, owner, recipient, mint_amount) = utils::setup_and_mint(/* with_account_contracts */ true); let burn_amount = mint_amount / 10; @@ -24,7 +24,7 @@ unconstrained fn burn_public_success() { } #[test] -unconstrained fn burn_public_failure() { +fn burn_public_failure() { let (env, token_contract_address, owner, recipient, mint_amount) = utils::setup_and_mint(/* with_account_contracts */ true); // Burn more than balance @@ -56,7 +56,7 @@ unconstrained fn burn_public_failure() { } #[test] -unconstrained fn burn_private_success() { +fn burn_private_success() { let (env, token_contract_address, owner, recipient, mint_amount) = utils::setup_and_mint(/* with_account_contracts */ true); let burn_amount = mint_amount / 10; @@ -79,7 +79,7 @@ unconstrained fn burn_private_success() { } #[test(should_fail_with="Balance too low")] -unconstrained fn burn_private_failure_more_than_balance() { +fn burn_private_failure_more_than_balance() { let (env, token_contract_address, owner, _, mint_amount) = utils::setup_and_mint(/* with_account_contracts */ false); // Burn more than balance @@ -90,7 +90,7 @@ unconstrained fn burn_private_failure_more_than_balance() { } #[test(should_fail_with="invalid nonce")] -unconstrained fn burn_private_failure_on_behalf_of_self_non_zero_nonce() { +fn burn_private_failure_on_behalf_of_self_non_zero_nonce() { let (env, token_contract_address, owner, _, mint_amount) = utils::setup_and_mint(/* with_account_contracts */ false); // Burn more than balance @@ -101,7 +101,7 @@ unconstrained fn burn_private_failure_on_behalf_of_self_non_zero_nonce() { } #[test(should_fail)] -unconstrained fn burn_private_failure_on_behalf_of_other_more_than_balance() { +fn burn_private_failure_on_behalf_of_other_more_than_balance() { let (env, token_contract_address, owner, recipient, mint_amount) = utils::setup_and_mint(/* with_account_contracts */ false); // Burn more than balance @@ -116,7 +116,7 @@ unconstrained fn burn_private_failure_on_behalf_of_other_more_than_balance() { } #[test(should_fail)] -unconstrained fn burn_private_failure_on_behalf_of_other_without_approval() { +fn burn_private_failure_on_behalf_of_other_without_approval() { let (env, token_contract_address, owner, recipient, mint_amount) = utils::setup_and_mint(/* with_account_contracts */ false); // Burn more than balance @@ -130,7 +130,7 @@ unconstrained fn burn_private_failure_on_behalf_of_other_without_approval() { } #[test(should_fail)] -unconstrained fn burn_private_failure_on_behalf_of_other_wrong_designated_caller() { +fn burn_private_failure_on_behalf_of_other_wrong_designated_caller() { let (env, token_contract_address, owner, recipient, mint_amount) = utils::setup_and_mint(/* with_account_contracts */ false); // Burn more than balance diff --git a/noir-projects/noir-contracts/contracts/token_contract/src/test/minting.nr b/noir-projects/noir-contracts/contracts/token_contract/src/test/minting.nr index 4e05a128536..d89e232b2da 100644 --- a/noir-projects/noir-contracts/contracts/token_contract/src/test/minting.nr +++ b/noir-projects/noir-contracts/contracts/token_contract/src/test/minting.nr @@ -3,7 +3,7 @@ use dep::aztec::{test::helpers::cheatcodes, oracle::unsafe_rand::unsafe_rand, ha use crate::{types::transparent_note::TransparentNote, Token}; #[test] -unconstrained fn mint_public_success() { +fn mint_public_success() { // Setup without account contracts. We are not using authwits here, so dummy accounts are enough let (env, token_contract_address, owner, _) = utils::setup(/* with_account_contracts */ false); @@ -20,7 +20,7 @@ unconstrained fn mint_public_success() { } #[test] -unconstrained fn mint_public_failures() { +fn mint_public_failures() { // Setup without account contracts. We are not using authwits here, so dummy accounts are enough let (env, token_contract_address, owner, recipient) = utils::setup(/* with_account_contracts */ false); @@ -58,7 +58,7 @@ unconstrained fn mint_public_failures() { } #[test] -unconstrained fn mint_private_success() { +fn mint_private_success() { // Setup without account contracts. We are not using authwits here, so dummy accounts are enough let (env, token_contract_address, owner, _) = utils::setup(/* with_account_contracts */ false); let mint_amount = 10000; @@ -89,7 +89,7 @@ unconstrained fn mint_private_success() { } #[test(should_fail_with="Cannot return zero notes")] -unconstrained fn mint_private_failure_double_spend() { +fn mint_private_failure_double_spend() { // Setup without account contracts. We are not using authwits here, so dummy accounts are enough let (env, token_contract_address, owner, recipient) = utils::setup(/* with_account_contracts */ false); let mint_amount = 10000; @@ -127,7 +127,7 @@ unconstrained fn mint_private_failure_double_spend() { } #[test(should_fail_with="caller is not minter")] -unconstrained fn mint_private_failure_non_minter() { +fn mint_private_failure_non_minter() { // Setup without account contracts. We are not using authwits here, so dummy accounts are enough let (env, token_contract_address, owner, recipient) = utils::setup(/* with_account_contracts */ false); let mint_amount = 10000; @@ -141,7 +141,7 @@ unconstrained fn mint_private_failure_non_minter() { } #[test(should_fail_with="call to assert_max_bit_size")] -unconstrained fn mint_private_failure_overflow() { +fn mint_private_failure_overflow() { // Setup without account contracts. We are not using authwits here, so dummy accounts are enough let (env, token_contract_address, _, _) = utils::setup(/* with_account_contracts */ false); @@ -154,7 +154,7 @@ unconstrained fn mint_private_failure_overflow() { } #[test(should_fail_with="attempt to add with overflow")] -unconstrained fn mint_private_failure_overflow_recipient() { +fn mint_private_failure_overflow_recipient() { // Setup without account contracts. We are not using authwits here, so dummy accounts are enough let (env, token_contract_address, owner, recipient) = utils::setup(/* with_account_contracts */ false); let mint_amount = 10000; @@ -192,7 +192,7 @@ unconstrained fn mint_private_failure_overflow_recipient() { } #[test(should_fail_with="attempt to add with overflow")] -unconstrained fn mint_private_failure_overflow_total_supply() { +fn mint_private_failure_overflow_total_supply() { // Setup without account contracts. We are not using authwits here, so dummy accounts are enough let (env, token_contract_address, owner, recipient) = utils::setup(/* with_account_contracts */ false); let mint_amount = 10000; diff --git a/noir-projects/noir-contracts/contracts/token_contract/src/test/transfer.nr b/noir-projects/noir-contracts/contracts/token_contract/src/test/transfer.nr index d87732ba758..c08b93ea151 100644 --- a/noir-projects/noir-contracts/contracts/token_contract/src/test/transfer.nr +++ b/noir-projects/noir-contracts/contracts/token_contract/src/test/transfer.nr @@ -4,7 +4,7 @@ use dep::authwit::cheatcodes as authwit_cheatcodes; use crate::Token; #[test] -unconstrained fn private_transfer() { +fn private_transfer() { // Setup without account contracts. We are not using authwits here, so dummy accounts are enough let (env, token_contract_address, owner, recipient, mint_amount) = utils::setup_and_mint(/* with_account_contracts */ false); // Transfer tokens @@ -18,7 +18,7 @@ unconstrained fn private_transfer() { } #[test] -unconstrained fn private_transfer_on_behalf_of() { +fn private_transfer_on_behalf_of() { // Setup with account contracts. Slower since we actually deploy them, but needed for authwits. let (env, token_contract_address, owner, recipient, mint_amount) = utils::setup_and_mint(/* with_account_contracts */ true); // Add authwit @@ -35,7 +35,7 @@ unconstrained fn private_transfer_on_behalf_of() { } #[test] -unconstrained fn public_transfer() { +fn public_transfer() { // Setup without account contracts. We are not using authwits here, so dummy accounts are enough let (env, token_contract_address, owner, recipient, mint_amount) = utils::setup_and_mint(/* with_account_contracts */ false); // Transfer tokens @@ -49,7 +49,7 @@ unconstrained fn public_transfer() { } #[test] -unconstrained fn public_transfer_on_behalf_of() { +fn public_transfer_on_behalf_of() { // Setup with account contracts. Slower since we actually deploy them, but needed for authwits. let (env, token_contract_address, owner, recipient, mint_amount) = utils::setup_and_mint(/* with_account_contracts */ true); let transfer_amount = 1000; diff --git a/noir-projects/noir-contracts/contracts/token_contract/src/test/unshield.nr b/noir-projects/noir-contracts/contracts/token_contract/src/test/unshield.nr index 23b4b31d4d5..e13764ddedc 100644 --- a/noir-projects/noir-contracts/contracts/token_contract/src/test/unshield.nr +++ b/noir-projects/noir-contracts/contracts/token_contract/src/test/unshield.nr @@ -2,7 +2,7 @@ use crate::test::utils; use crate::Token; #[test] -unconstrained fn unshield() { +fn unshield() { // Setup without account contracts. We are not using authwits here, so dummy accounts are enough let (env, token_contract_address, owner, _, mint_amount) = utils::setup_and_mint(/* with_account_contracts */ false); let unshield_amount = 1000; From 1206b87d7a03faab3aa9c69385007ddca44ae276 Mon Sep 17 00:00:00 2001 From: thunkar Date: Fri, 21 Jun 2024 11:25:09 +0000 Subject: [PATCH 60/75] Revert "test constrained" This reverts commit 5dc4c9a0202c7fff4b928f54133790847d711692. --- .../token_contract/src/test/access_control.nr | 2 +- .../contracts/token_contract/src/test/burn.nr | 16 ++++++++-------- .../contracts/token_contract/src/test/minting.nr | 16 ++++++++-------- .../token_contract/src/test/transfer.nr | 8 ++++---- .../token_contract/src/test/unshield.nr | 2 +- 5 files changed, 22 insertions(+), 22 deletions(-) diff --git a/noir-projects/noir-contracts/contracts/token_contract/src/test/access_control.nr b/noir-projects/noir-contracts/contracts/token_contract/src/test/access_control.nr index 9511cb802ac..3e7150d9621 100644 --- a/noir-projects/noir-contracts/contracts/token_contract/src/test/access_control.nr +++ b/noir-projects/noir-contracts/contracts/token_contract/src/test/access_control.nr @@ -5,7 +5,7 @@ use crate::Token; // Access control #[test] -fn access_control() { +unconstrained fn access_control() { // Setup without account contracts. We are not using authwits here, so dummy accounts are enough let (env, token_contract_address, owner, recipient) = utils::setup(/* with_account_contracts */ false); diff --git a/noir-projects/noir-contracts/contracts/token_contract/src/test/burn.nr b/noir-projects/noir-contracts/contracts/token_contract/src/test/burn.nr index c55d429d2bc..0653e506bd3 100644 --- a/noir-projects/noir-contracts/contracts/token_contract/src/test/burn.nr +++ b/noir-projects/noir-contracts/contracts/token_contract/src/test/burn.nr @@ -4,7 +4,7 @@ use dep::authwit::cheatcodes as authwit_cheatcodes; use crate::Token; #[test] -fn burn_public_success() { +unconstrained fn burn_public_success() { let (env, token_contract_address, owner, recipient, mint_amount) = utils::setup_and_mint(/* with_account_contracts */ true); let burn_amount = mint_amount / 10; @@ -24,7 +24,7 @@ fn burn_public_success() { } #[test] -fn burn_public_failure() { +unconstrained fn burn_public_failure() { let (env, token_contract_address, owner, recipient, mint_amount) = utils::setup_and_mint(/* with_account_contracts */ true); // Burn more than balance @@ -56,7 +56,7 @@ fn burn_public_failure() { } #[test] -fn burn_private_success() { +unconstrained fn burn_private_success() { let (env, token_contract_address, owner, recipient, mint_amount) = utils::setup_and_mint(/* with_account_contracts */ true); let burn_amount = mint_amount / 10; @@ -79,7 +79,7 @@ fn burn_private_success() { } #[test(should_fail_with="Balance too low")] -fn burn_private_failure_more_than_balance() { +unconstrained fn burn_private_failure_more_than_balance() { let (env, token_contract_address, owner, _, mint_amount) = utils::setup_and_mint(/* with_account_contracts */ false); // Burn more than balance @@ -90,7 +90,7 @@ fn burn_private_failure_more_than_balance() { } #[test(should_fail_with="invalid nonce")] -fn burn_private_failure_on_behalf_of_self_non_zero_nonce() { +unconstrained fn burn_private_failure_on_behalf_of_self_non_zero_nonce() { let (env, token_contract_address, owner, _, mint_amount) = utils::setup_and_mint(/* with_account_contracts */ false); // Burn more than balance @@ -101,7 +101,7 @@ fn burn_private_failure_on_behalf_of_self_non_zero_nonce() { } #[test(should_fail)] -fn burn_private_failure_on_behalf_of_other_more_than_balance() { +unconstrained fn burn_private_failure_on_behalf_of_other_more_than_balance() { let (env, token_contract_address, owner, recipient, mint_amount) = utils::setup_and_mint(/* with_account_contracts */ false); // Burn more than balance @@ -116,7 +116,7 @@ fn burn_private_failure_on_behalf_of_other_more_than_balance() { } #[test(should_fail)] -fn burn_private_failure_on_behalf_of_other_without_approval() { +unconstrained fn burn_private_failure_on_behalf_of_other_without_approval() { let (env, token_contract_address, owner, recipient, mint_amount) = utils::setup_and_mint(/* with_account_contracts */ false); // Burn more than balance @@ -130,7 +130,7 @@ fn burn_private_failure_on_behalf_of_other_without_approval() { } #[test(should_fail)] -fn burn_private_failure_on_behalf_of_other_wrong_designated_caller() { +unconstrained fn burn_private_failure_on_behalf_of_other_wrong_designated_caller() { let (env, token_contract_address, owner, recipient, mint_amount) = utils::setup_and_mint(/* with_account_contracts */ false); // Burn more than balance diff --git a/noir-projects/noir-contracts/contracts/token_contract/src/test/minting.nr b/noir-projects/noir-contracts/contracts/token_contract/src/test/minting.nr index d89e232b2da..4e05a128536 100644 --- a/noir-projects/noir-contracts/contracts/token_contract/src/test/minting.nr +++ b/noir-projects/noir-contracts/contracts/token_contract/src/test/minting.nr @@ -3,7 +3,7 @@ use dep::aztec::{test::helpers::cheatcodes, oracle::unsafe_rand::unsafe_rand, ha use crate::{types::transparent_note::TransparentNote, Token}; #[test] -fn mint_public_success() { +unconstrained fn mint_public_success() { // Setup without account contracts. We are not using authwits here, so dummy accounts are enough let (env, token_contract_address, owner, _) = utils::setup(/* with_account_contracts */ false); @@ -20,7 +20,7 @@ fn mint_public_success() { } #[test] -fn mint_public_failures() { +unconstrained fn mint_public_failures() { // Setup without account contracts. We are not using authwits here, so dummy accounts are enough let (env, token_contract_address, owner, recipient) = utils::setup(/* with_account_contracts */ false); @@ -58,7 +58,7 @@ fn mint_public_failures() { } #[test] -fn mint_private_success() { +unconstrained fn mint_private_success() { // Setup without account contracts. We are not using authwits here, so dummy accounts are enough let (env, token_contract_address, owner, _) = utils::setup(/* with_account_contracts */ false); let mint_amount = 10000; @@ -89,7 +89,7 @@ fn mint_private_success() { } #[test(should_fail_with="Cannot return zero notes")] -fn mint_private_failure_double_spend() { +unconstrained fn mint_private_failure_double_spend() { // Setup without account contracts. We are not using authwits here, so dummy accounts are enough let (env, token_contract_address, owner, recipient) = utils::setup(/* with_account_contracts */ false); let mint_amount = 10000; @@ -127,7 +127,7 @@ fn mint_private_failure_double_spend() { } #[test(should_fail_with="caller is not minter")] -fn mint_private_failure_non_minter() { +unconstrained fn mint_private_failure_non_minter() { // Setup without account contracts. We are not using authwits here, so dummy accounts are enough let (env, token_contract_address, owner, recipient) = utils::setup(/* with_account_contracts */ false); let mint_amount = 10000; @@ -141,7 +141,7 @@ fn mint_private_failure_non_minter() { } #[test(should_fail_with="call to assert_max_bit_size")] -fn mint_private_failure_overflow() { +unconstrained fn mint_private_failure_overflow() { // Setup without account contracts. We are not using authwits here, so dummy accounts are enough let (env, token_contract_address, _, _) = utils::setup(/* with_account_contracts */ false); @@ -154,7 +154,7 @@ fn mint_private_failure_overflow() { } #[test(should_fail_with="attempt to add with overflow")] -fn mint_private_failure_overflow_recipient() { +unconstrained fn mint_private_failure_overflow_recipient() { // Setup without account contracts. We are not using authwits here, so dummy accounts are enough let (env, token_contract_address, owner, recipient) = utils::setup(/* with_account_contracts */ false); let mint_amount = 10000; @@ -192,7 +192,7 @@ fn mint_private_failure_overflow_recipient() { } #[test(should_fail_with="attempt to add with overflow")] -fn mint_private_failure_overflow_total_supply() { +unconstrained fn mint_private_failure_overflow_total_supply() { // Setup without account contracts. We are not using authwits here, so dummy accounts are enough let (env, token_contract_address, owner, recipient) = utils::setup(/* with_account_contracts */ false); let mint_amount = 10000; diff --git a/noir-projects/noir-contracts/contracts/token_contract/src/test/transfer.nr b/noir-projects/noir-contracts/contracts/token_contract/src/test/transfer.nr index c08b93ea151..d87732ba758 100644 --- a/noir-projects/noir-contracts/contracts/token_contract/src/test/transfer.nr +++ b/noir-projects/noir-contracts/contracts/token_contract/src/test/transfer.nr @@ -4,7 +4,7 @@ use dep::authwit::cheatcodes as authwit_cheatcodes; use crate::Token; #[test] -fn private_transfer() { +unconstrained fn private_transfer() { // Setup without account contracts. We are not using authwits here, so dummy accounts are enough let (env, token_contract_address, owner, recipient, mint_amount) = utils::setup_and_mint(/* with_account_contracts */ false); // Transfer tokens @@ -18,7 +18,7 @@ fn private_transfer() { } #[test] -fn private_transfer_on_behalf_of() { +unconstrained fn private_transfer_on_behalf_of() { // Setup with account contracts. Slower since we actually deploy them, but needed for authwits. let (env, token_contract_address, owner, recipient, mint_amount) = utils::setup_and_mint(/* with_account_contracts */ true); // Add authwit @@ -35,7 +35,7 @@ fn private_transfer_on_behalf_of() { } #[test] -fn public_transfer() { +unconstrained fn public_transfer() { // Setup without account contracts. We are not using authwits here, so dummy accounts are enough let (env, token_contract_address, owner, recipient, mint_amount) = utils::setup_and_mint(/* with_account_contracts */ false); // Transfer tokens @@ -49,7 +49,7 @@ fn public_transfer() { } #[test] -fn public_transfer_on_behalf_of() { +unconstrained fn public_transfer_on_behalf_of() { // Setup with account contracts. Slower since we actually deploy them, but needed for authwits. let (env, token_contract_address, owner, recipient, mint_amount) = utils::setup_and_mint(/* with_account_contracts */ true); let transfer_amount = 1000; diff --git a/noir-projects/noir-contracts/contracts/token_contract/src/test/unshield.nr b/noir-projects/noir-contracts/contracts/token_contract/src/test/unshield.nr index e13764ddedc..23b4b31d4d5 100644 --- a/noir-projects/noir-contracts/contracts/token_contract/src/test/unshield.nr +++ b/noir-projects/noir-contracts/contracts/token_contract/src/test/unshield.nr @@ -2,7 +2,7 @@ use crate::test::utils; use crate::Token; #[test] -fn unshield() { +unconstrained fn unshield() { // Setup without account contracts. We are not using authwits here, so dummy accounts are enough let (env, token_contract_address, owner, _, mint_amount) = utils::setup_and_mint(/* with_account_contracts */ false); let unshield_amount = 1000; From aa8e0066829b25d1153a4be8c31312f3793fa79a Mon Sep 17 00:00:00 2001 From: thunkar Date: Fri, 21 Jun 2024 15:51:43 +0000 Subject: [PATCH 61/75] moar tests --- .../contracts/token_contract/src/test.nr | 7 +- .../token_contract/src/test/access_control.nr | 2 - .../contracts/token_contract/src/test/burn.nr | 40 ++++- .../token_contract/src/test/minting.nr | 22 +-- .../src/test/reading_constants.nr | 29 ++++ .../token_contract/src/test/shielding.nr | 162 ++++++++++++++++++ .../token_contract/src/test/transfer.nr | 65 ------- .../src/test/transfer_private.nr | 131 ++++++++++++++ .../src/test/transfer_public.nr | 122 +++++++++++++ .../token_contract/src/test/unshield.nr | 12 -- .../token_contract/src/test/unshielding.nr | 87 ++++++++++ .../token_contract/src/test/utils.nr | 14 +- yarn-project/txe/src/bin/index.ts | 3 +- yarn-project/txe/src/oracle/txe_oracle.ts | 108 ++++++------ .../txe/src/txe_service/txe_service.ts | 9 +- 15 files changed, 656 insertions(+), 157 deletions(-) create mode 100644 noir-projects/noir-contracts/contracts/token_contract/src/test/reading_constants.nr create mode 100644 noir-projects/noir-contracts/contracts/token_contract/src/test/shielding.nr delete mode 100644 noir-projects/noir-contracts/contracts/token_contract/src/test/transfer.nr create mode 100644 noir-projects/noir-contracts/contracts/token_contract/src/test/transfer_private.nr create mode 100644 noir-projects/noir-contracts/contracts/token_contract/src/test/transfer_public.nr delete mode 100644 noir-projects/noir-contracts/contracts/token_contract/src/test/unshield.nr create mode 100644 noir-projects/noir-contracts/contracts/token_contract/src/test/unshielding.nr diff --git a/noir-projects/noir-contracts/contracts/token_contract/src/test.nr b/noir-projects/noir-contracts/contracts/token_contract/src/test.nr index 2cbb088e60c..cf797ce3bcc 100644 --- a/noir-projects/noir-contracts/contracts/token_contract/src/test.nr +++ b/noir-projects/noir-contracts/contracts/token_contract/src/test.nr @@ -1,6 +1,9 @@ mod access_control; mod burn; mod utils; -mod transfer; -mod unshield; +mod transfer_public; +mod transfer_private; +mod unshielding; mod minting; +mod reading_constants; +mod shielding; diff --git a/noir-projects/noir-contracts/contracts/token_contract/src/test/access_control.nr b/noir-projects/noir-contracts/contracts/token_contract/src/test/access_control.nr index 3e7150d9621..07fe052c9d6 100644 --- a/noir-projects/noir-contracts/contracts/token_contract/src/test/access_control.nr +++ b/noir-projects/noir-contracts/contracts/token_contract/src/test/access_control.nr @@ -2,8 +2,6 @@ use crate::test::utils; use dep::aztec::test::helpers::cheatcodes; use crate::Token; -// Access control - #[test] unconstrained fn access_control() { // Setup without account contracts. We are not using authwits here, so dummy accounts are enough diff --git a/noir-projects/noir-contracts/contracts/token_contract/src/test/burn.nr b/noir-projects/noir-contracts/contracts/token_contract/src/test/burn.nr index 0653e506bd3..45142210359 100644 --- a/noir-projects/noir-contracts/contracts/token_contract/src/test/burn.nr +++ b/noir-projects/noir-contracts/contracts/token_contract/src/test/burn.nr @@ -24,22 +24,33 @@ unconstrained fn burn_public_success() { } #[test] -unconstrained fn burn_public_failure() { - let (env, token_contract_address, owner, recipient, mint_amount) = utils::setup_and_mint(/* with_account_contracts */ true); +unconstrained fn burn_public_failure_more_than_balance() { + let (env, token_contract_address, owner, _, mint_amount) = utils::setup_and_mint(/* with_account_contracts */ false); // Burn more than balance let burn_amount = mint_amount * 10; let burn_call_interface = Token::at(token_contract_address).burn_public(owner, burn_amount, 0); env.assert_public_call_fails(burn_call_interface); utils::check_public_balance(token_contract_address, owner, mint_amount); +} + +#[test] +unconstrained fn burn_public_failure_on_behalf_of_self_non_zero_nonce() { + let (env, token_contract_address, owner, _, mint_amount) = utils::setup_and_mint(/* with_account_contracts */ false); // Burn on behalf of self with non-zero nonce let burn_amount = mint_amount / 10; let burn_call_interface = Token::at(token_contract_address).burn_public(owner, burn_amount, unsafe_rand()); env.assert_public_call_fails(burn_call_interface); utils::check_public_balance(token_contract_address, owner, mint_amount); +} + +#[test] +unconstrained fn burn_public_failure_on_behalf_of_other_without_approval() { + let (env, token_contract_address, owner, recipient, mint_amount) = utils::setup_and_mint(/* with_account_contracts */ true); // Burn on behalf of other without approval + let burn_amount = mint_amount / 10; let burn_call_interface = Token::at(token_contract_address).burn_public(owner, burn_amount, unsafe_rand()); // Impersonate recipient to perform the call cheatcodes::set_contract_address(recipient); @@ -56,17 +67,34 @@ unconstrained fn burn_public_failure() { } #[test] -unconstrained fn burn_private_success() { +unconstrained fn burn_public_failure_on_behalf_of_other_wrong_caller() { let (env, token_contract_address, owner, recipient, mint_amount) = utils::setup_and_mint(/* with_account_contracts */ true); + + // Burn on behalf of other, wrong designated caller + let burn_amount = mint_amount / 10; + let burn_call_interface = Token::at(token_contract_address).burn_public(owner, burn_amount, unsafe_rand()); + authwit_cheatcodes::add_public_authwit_from_call_interface(owner, owner, burn_call_interface); + // Impersonate recipient to perform the call + cheatcodes::set_contract_address(recipient); + env.assert_public_call_fails(burn_call_interface); + utils::check_public_balance(token_contract_address, owner, mint_amount); +} + +#[test] +unconstrained fn burn_private_on_behalf_of_self() { + let (env, token_contract_address, owner, _, mint_amount) = utils::setup_and_mint(/* with_account_contracts */ false); let burn_amount = mint_amount / 10; // Burn less than balance let burn_call_interface = Token::at(token_contract_address).burn(owner, burn_amount, 0); env.call_private_void(burn_call_interface); utils::check_private_balance(token_contract_address, owner, mint_amount - burn_amount); +} - // Another misterious initializer nullifier failure - env.advance_block_by(1); +#[test] +unconstrained fn burn_private_on_behalf_of_other() { + let (env, token_contract_address, owner, recipient, mint_amount) = utils::setup_and_mint(/* with_account_contracts */ true); + let burn_amount = mint_amount / 10; // Burn on behalf of other let burn_call_interface = Token::at(token_contract_address).burn(owner, burn_amount, unsafe_rand()); @@ -75,7 +103,7 @@ unconstrained fn burn_private_success() { cheatcodes::set_contract_address(recipient); // Burn tokens env.call_private_void(burn_call_interface); - utils::check_private_balance(token_contract_address, owner, mint_amount - 2 * burn_amount); + utils::check_private_balance(token_contract_address, owner, mint_amount - burn_amount); } #[test(should_fail_with="Balance too low")] diff --git a/noir-projects/noir-contracts/contracts/token_contract/src/test/minting.nr b/noir-projects/noir-contracts/contracts/token_contract/src/test/minting.nr index 4e05a128536..2fe72dbe9e8 100644 --- a/noir-projects/noir-contracts/contracts/token_contract/src/test/minting.nr +++ b/noir-projects/noir-contracts/contracts/token_contract/src/test/minting.nr @@ -63,7 +63,7 @@ unconstrained fn mint_private_success() { let (env, token_contract_address, owner, _) = utils::setup(/* with_account_contracts */ false); let mint_amount = 10000; // Mint some tokens - let secret = 1; + let secret = unsafe_rand(); let secret_hash = compute_secret_hash(secret); let mint_private_call_interface = Token::at(token_contract_address).mint_private(mint_amount, secret_hash); env.call_public(mint_private_call_interface); @@ -94,7 +94,7 @@ unconstrained fn mint_private_failure_double_spend() { let (env, token_contract_address, owner, recipient) = utils::setup(/* with_account_contracts */ false); let mint_amount = 10000; // Mint some tokens - let secret = 1; + let secret = unsafe_rand(); let secret_hash = compute_secret_hash(secret); let mint_private_call_interface = Token::at(token_contract_address).mint_private(mint_amount, secret_hash); env.call_public(mint_private_call_interface); @@ -129,12 +129,12 @@ unconstrained fn mint_private_failure_double_spend() { #[test(should_fail_with="caller is not minter")] unconstrained fn mint_private_failure_non_minter() { // Setup without account contracts. We are not using authwits here, so dummy accounts are enough - let (env, token_contract_address, owner, recipient) = utils::setup(/* with_account_contracts */ false); + let (env, token_contract_address, _, recipient) = utils::setup(/* with_account_contracts */ false); let mint_amount = 10000; // Try to mint some tokens impersonating recipient cheatcodes::set_contract_address(recipient); - let secret = 1; + let secret = unsafe_rand(); let secret_hash = compute_secret_hash(secret); let mint_private_call_interface = Token::at(token_contract_address).mint_private(mint_amount, secret_hash); env.call_public(mint_private_call_interface); @@ -147,7 +147,7 @@ unconstrained fn mint_private_failure_overflow() { // Overflow recipient let mint_amount = 2.pow_32(128); - let secret = 1; + let secret = unsafe_rand(); let secret_hash = compute_secret_hash(secret); let mint_private_call_interface = Token::at(token_contract_address).mint_private(mint_amount, secret_hash); env.call_public(mint_private_call_interface); @@ -156,10 +156,10 @@ unconstrained fn mint_private_failure_overflow() { #[test(should_fail_with="attempt to add with overflow")] unconstrained fn mint_private_failure_overflow_recipient() { // Setup without account contracts. We are not using authwits here, so dummy accounts are enough - let (env, token_contract_address, owner, recipient) = utils::setup(/* with_account_contracts */ false); + let (env, token_contract_address, owner, _) = utils::setup(/* with_account_contracts */ false); let mint_amount = 10000; // Mint some tokens - let secret = 1; + let secret = unsafe_rand(); let secret_hash = compute_secret_hash(secret); let mint_private_call_interface = Token::at(token_contract_address).mint_private(mint_amount, secret_hash); env.call_public(mint_private_call_interface); @@ -185,7 +185,7 @@ unconstrained fn mint_private_failure_overflow_recipient() { let mint_amount = 2.pow_32(128) - mint_amount; // Mint some tokens - let secret = 1; + let secret = unsafe_rand(); let secret_hash = compute_secret_hash(secret); let mint_private_call_interface = Token::at(token_contract_address).mint_private(mint_amount, secret_hash); env.call_public(mint_private_call_interface); @@ -197,8 +197,8 @@ unconstrained fn mint_private_failure_overflow_total_supply() { let (env, token_contract_address, owner, recipient) = utils::setup(/* with_account_contracts */ false); let mint_amount = 10000; // Mint some tokens - let secret_owner = 1; - let secret_recipient = 2; + let secret_owner = unsafe_rand(); + let secret_recipient = unsafe_rand(); let secret_hash_owner = compute_secret_hash(secret_owner); let secret_hash_recipient = compute_secret_hash(secret_recipient); @@ -241,7 +241,7 @@ unconstrained fn mint_private_failure_overflow_total_supply() { cheatcodes::set_contract_address(owner); let mint_amount = 2.pow_32(128) - 2 * mint_amount; // Try to mint some tokens - let secret = 1; + let secret = unsafe_rand(); let secret_hash = compute_secret_hash(secret); let mint_private_call_interface = Token::at(token_contract_address).mint_private(mint_amount, secret_hash); env.call_public(mint_private_call_interface); diff --git a/noir-projects/noir-contracts/contracts/token_contract/src/test/reading_constants.nr b/noir-projects/noir-contracts/contracts/token_contract/src/test/reading_constants.nr new file mode 100644 index 00000000000..469ff747590 --- /dev/null +++ b/noir-projects/noir-contracts/contracts/token_contract/src/test/reading_constants.nr @@ -0,0 +1,29 @@ +use crate::test::utils; +use dep::aztec::test::helpers::cheatcodes; +use crate::Token; + +// It is not possible to deserialize strings in Noir ATM, so name and symbol cannot be checked yet. + +#[test] +unconstrained fn check_decimals_private() { + // Setup without account contracts. We are not using authwits here, so dummy accounts are enough + let (env, token_contract_address, _, _) = utils::setup(/* with_account_contracts */ false); + + // Check decimals + let private_get_decimals_call_interface = Token::at(token_contract_address).private_get_decimals(); + let result = env.call_private(private_get_decimals_call_interface); + + assert(result == 18); +} + +#[test] +unconstrained fn check_decimals_public() { + // Setup without account contracts. We are not using authwits here, so dummy accounts are enough + let (env, token_contract_address, _, _) = utils::setup(/* with_account_contracts */ false); + + // Check decimals + let public_get_decimals_call_interface = Token::at(token_contract_address).public_get_decimals(); + let result = env.call_public(public_get_decimals_call_interface); + + assert(result == 18 as u8); +} diff --git a/noir-projects/noir-contracts/contracts/token_contract/src/test/shielding.nr b/noir-projects/noir-contracts/contracts/token_contract/src/test/shielding.nr new file mode 100644 index 00000000000..42e0f4ff322 --- /dev/null +++ b/noir-projects/noir-contracts/contracts/token_contract/src/test/shielding.nr @@ -0,0 +1,162 @@ +use crate::test::utils; +use dep::aztec::{test::helpers::cheatcodes, oracle::unsafe_rand::unsafe_rand, hash::compute_secret_hash}; +use dep::authwit::cheatcodes as authwit_cheatcodes; +use crate::{types::transparent_note::TransparentNote, Token}; + +#[test] +unconstrained fn shielding_on_behalf_of_self() { + // Setup without account contracts. We are not using authwits here, so dummy accounts are enough + let (env, token_contract_address, owner, _, mint_amount) = utils::setup_and_mint(/* with_account_contracts */ false); + let secret = unsafe_rand(); + let secret_hash = compute_secret_hash(secret); + // Shield tokens + let shield_amount = mint_amount / 10; + let shield_call_interface = Token::at(token_contract_address).shield(owner, shield_amount, secret_hash, 0); + env.call_public(shield_call_interface); + + // TODO: https://github.com/AztecProtocol/aztec-packages/issues/7086 + env.advance_block_by(1); + + // Store a note in the cache so we can redeem it + env.store_note_in_cache( + &mut TransparentNote::new(shield_amount, secret_hash), + Token::storage().pending_shields.slot, + token_contract_address + ); + + // Redeem our shielded tokens + let redeem_shield_call_interface = Token::at(token_contract_address).redeem_shield(owner, shield_amount, secret); + env.call_private_void(redeem_shield_call_interface); + + // Check balances + utils::check_public_balance(token_contract_address, owner, mint_amount - shield_amount); + utils::check_private_balance(token_contract_address, owner, mint_amount + shield_amount); +} + +#[test] +unconstrained fn shielding_on_behalf_of_other() { + let (env, token_contract_address, owner, recipient, mint_amount) = utils::setup_and_mint(/* with_account_contracts */ true); + let secret = unsafe_rand(); + let secret_hash = compute_secret_hash(secret); + + // Shield tokens on behalf of owner + let shield_amount = 1000; + let shield_call_interface = Token::at(token_contract_address).shield(owner, shield_amount, secret_hash, 0); + authwit_cheatcodes::add_public_authwit_from_call_interface(owner, recipient, shield_call_interface); + // Impersonate recipient to perform the call + cheatcodes::set_contract_address(recipient); + // Shield tokens + env.call_public(shield_call_interface); + + // TODO: https://github.com/AztecProtocol/aztec-packages/issues/7086 + env.advance_block_by(1); + + // Become owner again + cheatcodes::set_contract_address(owner); + // Store a note in the cache so we can redeem it + env.store_note_in_cache( + &mut TransparentNote::new(shield_amount, secret_hash), + Token::storage().pending_shields.slot, + token_contract_address + ); + + // Redeem our shielded tokens + let redeem_shield_call_interface = Token::at(token_contract_address).redeem_shield(owner, shield_amount, secret); + env.call_private_void(redeem_shield_call_interface); + + // Check balances + utils::check_public_balance(token_contract_address, owner, mint_amount - shield_amount); + utils::check_private_balance(token_contract_address, owner, mint_amount + shield_amount); +} + +#[test] +unconstrained fn shielding_failure_on_behalf_of_self_more_than_balance() { + // Setup without account contracts. We are not using authwits here, so dummy accounts are enough + let (env, token_contract_address, owner, _, mint_amount) = utils::setup_and_mint(/* with_account_contracts */ false); + let secret = unsafe_rand(); + let secret_hash = compute_secret_hash(secret); + // Shield tokens + let shield_amount = mint_amount + 1; + let shield_call_interface = Token::at(token_contract_address).shield(owner, shield_amount, secret_hash, 0); + env.assert_public_call_fails(shield_call_interface); + + // Check balances + utils::check_public_balance(token_contract_address, owner, mint_amount); + utils::check_private_balance(token_contract_address, owner, mint_amount); +} + +#[test] +unconstrained fn shielding_failure_on_behalf_of_self_invalid_nonce() { + // Setup without account contracts. We are not using authwits here, so dummy accounts are enough + let (env, token_contract_address, owner, _, mint_amount) = utils::setup_and_mint(/* with_account_contracts */ false); + let secret = unsafe_rand(); + let secret_hash = compute_secret_hash(secret); + // Shield tokens + let shield_amount = mint_amount / 10; + let shield_call_interface = Token::at(token_contract_address).shield(owner, shield_amount, secret_hash, unsafe_rand()); + env.assert_public_call_fails(shield_call_interface); + + // Check balances + utils::check_public_balance(token_contract_address, owner, mint_amount); + utils::check_private_balance(token_contract_address, owner, mint_amount); +} + +#[test] +unconstrained fn shielding_failure_on_behalf_of_other_more_than_balance() { + // Setup without account contracts. We are not using authwits here, so dummy accounts are enough + let (env, token_contract_address, owner, recipient, mint_amount) = utils::setup_and_mint(/* with_account_contracts */ true); + let secret = unsafe_rand(); + let secret_hash = compute_secret_hash(secret); + // Shield tokens on behalf of owner + let shield_amount = mint_amount + 1; + let shield_call_interface = Token::at(token_contract_address).shield(owner, shield_amount, secret_hash, 0); + authwit_cheatcodes::add_public_authwit_from_call_interface(owner, recipient, shield_call_interface); + // Impersonate recipient to perform the call + cheatcodes::set_contract_address(recipient); + // Shield tokens + env.assert_public_call_fails(shield_call_interface); + + // Check balances + utils::check_public_balance(token_contract_address, owner, mint_amount); + utils::check_private_balance(token_contract_address, owner, mint_amount); +} + +#[test] +unconstrained fn shielding_failure_on_behalf_of_other_wrong_caller() { + // Setup without account contracts. We are not using authwits here, so dummy accounts are enough + let (env, token_contract_address, owner, recipient, mint_amount) = utils::setup_and_mint(/* with_account_contracts */ true); + let secret = unsafe_rand(); + let secret_hash = compute_secret_hash(secret); + // Shield tokens on behalf of owner + let shield_amount = mint_amount + 1; + let shield_call_interface = Token::at(token_contract_address).shield(owner, shield_amount, secret_hash, 0); + authwit_cheatcodes::add_public_authwit_from_call_interface(owner, owner, shield_call_interface); + // Impersonate recipient to perform the call + cheatcodes::set_contract_address(recipient); + // Shield tokens + env.assert_public_call_fails(shield_call_interface); + + // Check balances + utils::check_public_balance(token_contract_address, owner, mint_amount); + utils::check_private_balance(token_contract_address, owner, mint_amount); +} + +#[test] +unconstrained fn shielding_failure_on_behalf_of_other_without_approval() { + // Setup without account contracts. We are not using authwits here, so dummy accounts are enough + let (env, token_contract_address, owner, recipient, mint_amount) = utils::setup_and_mint(/* with_account_contracts */ true); + let secret = unsafe_rand(); + let secret_hash = compute_secret_hash(secret); + // Shield tokens on behalf of owner + let shield_amount = mint_amount + 1; + let shield_call_interface = Token::at(token_contract_address).shield(owner, shield_amount, secret_hash, 0); + // Impersonate recipient to perform the call + cheatcodes::set_contract_address(recipient); + // Shield tokens + env.assert_public_call_fails(shield_call_interface); + + // Check balances + utils::check_public_balance(token_contract_address, owner, mint_amount); + utils::check_private_balance(token_contract_address, owner, mint_amount); +} + diff --git a/noir-projects/noir-contracts/contracts/token_contract/src/test/transfer.nr b/noir-projects/noir-contracts/contracts/token_contract/src/test/transfer.nr deleted file mode 100644 index d87732ba758..00000000000 --- a/noir-projects/noir-contracts/contracts/token_contract/src/test/transfer.nr +++ /dev/null @@ -1,65 +0,0 @@ -use crate::test::utils; -use dep::aztec::{test::helpers::cheatcodes, oracle::unsafe_rand::unsafe_rand}; -use dep::authwit::cheatcodes as authwit_cheatcodes; -use crate::Token; - -#[test] -unconstrained fn private_transfer() { - // Setup without account contracts. We are not using authwits here, so dummy accounts are enough - let (env, token_contract_address, owner, recipient, mint_amount) = utils::setup_and_mint(/* with_account_contracts */ false); - // Transfer tokens - let transfer_amount = 1000; - let private_transfer_call_interface = Token::at(token_contract_address).transfer(recipient, transfer_amount); - env.call_private_void(private_transfer_call_interface); - - // Check balances - utils::check_private_balance(token_contract_address, owner, mint_amount - transfer_amount); - utils::check_private_balance(token_contract_address, recipient, transfer_amount); -} - -#[test] -unconstrained fn private_transfer_on_behalf_of() { - // Setup with account contracts. Slower since we actually deploy them, but needed for authwits. - let (env, token_contract_address, owner, recipient, mint_amount) = utils::setup_and_mint(/* with_account_contracts */ true); - // Add authwit - let transfer_amount = 1000; - let private_transfer_from_call_interface = Token::at(token_contract_address).transfer_from(owner, recipient, transfer_amount, 1); - authwit_cheatcodes::add_private_authwit_from_call_interface(owner, recipient, private_transfer_from_call_interface); - // Impersonate recipient to perform the call - cheatcodes::set_contract_address(recipient); - // Transfer tokens - env.call_private_void(private_transfer_from_call_interface); - // Check balances - utils::check_private_balance(token_contract_address, owner, mint_amount - transfer_amount); - utils::check_private_balance(token_contract_address, recipient, transfer_amount); -} - -#[test] -unconstrained fn public_transfer() { - // Setup without account contracts. We are not using authwits here, so dummy accounts are enough - let (env, token_contract_address, owner, recipient, mint_amount) = utils::setup_and_mint(/* with_account_contracts */ false); - // Transfer tokens - let transfer_amount = 1000; - let public_transfer_call_interface = Token::at(token_contract_address).transfer_public(owner, recipient, transfer_amount, 0); - env.call_public(public_transfer_call_interface); - - // Check balances - utils::check_public_balance(token_contract_address, owner, mint_amount - transfer_amount); - utils::check_public_balance(token_contract_address, recipient, transfer_amount); -} - -#[test] -unconstrained fn public_transfer_on_behalf_of() { - // Setup with account contracts. Slower since we actually deploy them, but needed for authwits. - let (env, token_contract_address, owner, recipient, mint_amount) = utils::setup_and_mint(/* with_account_contracts */ true); - let transfer_amount = 1000; - let public_transfer_from_call_interface = Token::at(token_contract_address).transfer_public(owner, recipient, transfer_amount, 1); - authwit_cheatcodes::add_public_authwit_from_call_interface(owner, recipient, public_transfer_from_call_interface); - // Impersonate recipient to perform the call - cheatcodes::set_contract_address(recipient); - // Transfer tokens - env.call_public(public_transfer_from_call_interface); - // Check balances - utils::check_public_balance(token_contract_address, owner, mint_amount - transfer_amount); - utils::check_public_balance(token_contract_address, recipient, transfer_amount); -} diff --git a/noir-projects/noir-contracts/contracts/token_contract/src/test/transfer_private.nr b/noir-projects/noir-contracts/contracts/token_contract/src/test/transfer_private.nr new file mode 100644 index 00000000000..47e04809114 --- /dev/null +++ b/noir-projects/noir-contracts/contracts/token_contract/src/test/transfer_private.nr @@ -0,0 +1,131 @@ +use crate::test::utils; +use dep::aztec::{test::helpers::cheatcodes, oracle::unsafe_rand::unsafe_rand, protocol_types::address::AztecAddress}; +use dep::authwit::cheatcodes as authwit_cheatcodes; +use crate::Token; + +#[test] +unconstrained fn transfer_private() { + // Setup without account contracts. We are not using authwits here, so dummy accounts are enough + let (env, token_contract_address, owner, recipient, mint_amount) = utils::setup_and_mint(/* with_account_contracts */ false); + // Transfer tokens + let transfer_amount = 1000; + let transfer_private_call_interface = Token::at(token_contract_address).transfer(recipient, transfer_amount); + env.call_private_void(transfer_private_call_interface); + + // Check balances + utils::check_private_balance(token_contract_address, owner, mint_amount - transfer_amount); + utils::check_private_balance(token_contract_address, recipient, transfer_amount); +} + +#[test] +unconstrained fn transfer_private_to_self() { + // Setup without account contracts. We are not using authwits here, so dummy accounts are enough + let (env, token_contract_address, owner, _, mint_amount) = utils::setup_and_mint(/* with_account_contracts */ false); + // Transfer tokens + let transfer_amount = 1000; + let transfer_private_call_interface = Token::at(token_contract_address).transfer(owner, transfer_amount); + env.call_private_void(transfer_private_call_interface); + + // Check balances + utils::check_private_balance(token_contract_address, owner, mint_amount); +} + +#[test] +unconstrained fn transfer_private_to_non_deployed_account() { + // Setup without account contracts. We are not using authwits here, so dummy accounts are enough + let (env, token_contract_address, owner, _, mint_amount) = utils::setup_and_mint(/* with_account_contracts */ false); + let not_deployed = cheatcodes::create_account(); + // Transfer tokens + let transfer_amount = 1000; + let transfer_private_call_interface = Token::at(token_contract_address).transfer(not_deployed.address, transfer_amount); + env.call_private_void(transfer_private_call_interface); + + // Check balances + utils::check_private_balance(token_contract_address, owner, mint_amount - transfer_amount); + utils::check_private_balance(token_contract_address, not_deployed.address, transfer_amount); +} + +#[test] +unconstrained fn transfer_private_on_behalf_of_other() { + // Setup with account contracts. Slower since we actually deploy them, but needed for authwits. + let (env, token_contract_address, owner, recipient, mint_amount) = utils::setup_and_mint(/* with_account_contracts */ true); + // Add authwit + let transfer_amount = 1000; + let transfer_private_from_call_interface = Token::at(token_contract_address).transfer_from(owner, recipient, transfer_amount, 1); + authwit_cheatcodes::add_private_authwit_from_call_interface(owner, recipient, transfer_private_from_call_interface); + // Impersonate recipient to perform the call + cheatcodes::set_contract_address(recipient); + // Transfer tokens + env.call_private_void(transfer_private_from_call_interface); + // Check balances + utils::check_private_balance(token_contract_address, owner, mint_amount - transfer_amount); + utils::check_private_balance(token_contract_address, recipient, transfer_amount); +} + +#[test(should_fail_with="Balance too low")] +unconstrained fn transfer_private_failure_more_than_balance() { + // Setup without account contracts. We are not using authwits here, so dummy accounts are enough + let (env, token_contract_address, _, recipient, mint_amount) = utils::setup_and_mint(/* with_account_contracts */ false); + // Transfer tokens + let transfer_amount = mint_amount + 1; + let transfer_private_call_interface = Token::at(token_contract_address).transfer(recipient, transfer_amount); + env.call_private_void(transfer_private_call_interface); +} + +#[test(should_fail_with="invalid nonce")] +unconstrained fn transfer_private_failure_on_behalf_of_self_non_zero_nonce() { + // Setup with account contracts. Slower since we actually deploy them, but needed for authwits. + let (env, token_contract_address, owner, recipient, _) = utils::setup_and_mint(/* with_account_contracts */ true); + // Add authwit + let transfer_amount = 1000; + let transfer_private_from_call_interface = Token::at(token_contract_address).transfer_from(owner, recipient, transfer_amount, 1); + // Transfer tokens + env.call_private_void(transfer_private_from_call_interface); +} + +#[test(should_fail_with="Balance too low")] +unconstrained fn transfer_private_failure_on_behalf_of_more_than_balance() { + // Setup with account contracts. Slower since we actually deploy them, but needed for authwits. + let (env, token_contract_address, owner, recipient, mint_amount) = utils::setup_and_mint(/* with_account_contracts */ true); + // Add authwit + let transfer_amount = mint_amount + 1; + let transfer_private_from_call_interface = Token::at(token_contract_address).transfer_from(owner, recipient, transfer_amount, 1); + authwit_cheatcodes::add_private_authwit_from_call_interface(owner, recipient, transfer_private_from_call_interface); + // Impersonate recipient to perform the call + cheatcodes::set_contract_address(recipient); + // Transfer tokens + env.call_private_void(transfer_private_from_call_interface); +} + +#[test(should_fail)] +unconstrained fn transfer_private_failure_on_behalf_of_other_without_approval() { + // Setup with account contracts. Slower since we actually deploy them, but needed for authwits. + let (env, token_contract_address, owner, recipient, mint_amount) = utils::setup_and_mint(/* with_account_contracts */ true); + // Add authwit + let transfer_amount = 1000; + let transfer_private_from_call_interface = Token::at(token_contract_address).transfer_from(owner, recipient, transfer_amount, 1); + // Impersonate recipient to perform the call + cheatcodes::set_contract_address(recipient); + // Transfer tokens + env.call_private_void(transfer_private_from_call_interface); + // Check balances + utils::check_private_balance(token_contract_address, owner, mint_amount - transfer_amount); + utils::check_private_balance(token_contract_address, recipient, transfer_amount); +} + +#[test(should_fail)] +unconstrained fn transfer_private_failure_on_behalf_of_other_wrong_caller() { + // Setup with account contracts. Slower since we actually deploy them, but needed for authwits. + let (env, token_contract_address, owner, recipient, mint_amount) = utils::setup_and_mint(/* with_account_contracts */ true); + // Add authwit + let transfer_amount = 1000; + let transfer_private_from_call_interface = Token::at(token_contract_address).transfer_from(owner, recipient, transfer_amount, 1); + authwit_cheatcodes::add_private_authwit_from_call_interface(owner, owner, transfer_private_from_call_interface); + // Impersonate recipient to perform the call + cheatcodes::set_contract_address(recipient); + // Transfer tokens + env.call_private_void(transfer_private_from_call_interface); + // Check balances + utils::check_private_balance(token_contract_address, owner, mint_amount - transfer_amount); + utils::check_private_balance(token_contract_address, recipient, transfer_amount); +} diff --git a/noir-projects/noir-contracts/contracts/token_contract/src/test/transfer_public.nr b/noir-projects/noir-contracts/contracts/token_contract/src/test/transfer_public.nr new file mode 100644 index 00000000000..2294a8a05d3 --- /dev/null +++ b/noir-projects/noir-contracts/contracts/token_contract/src/test/transfer_public.nr @@ -0,0 +1,122 @@ +use crate::test::utils; +use dep::aztec::{test::helpers::cheatcodes, oracle::unsafe_rand::unsafe_rand}; +use dep::authwit::cheatcodes as authwit_cheatcodes; +use crate::Token; + +#[test] +unconstrained fn public_transfer() { + // Setup without account contracts. We are not using authwits here, so dummy accounts are enough + let (env, token_contract_address, owner, recipient, mint_amount) = utils::setup_and_mint(/* with_account_contracts */ false); + // Transfer tokens + let transfer_amount = mint_amount / 10; + let public_transfer_call_interface = Token::at(token_contract_address).transfer_public(owner, recipient, transfer_amount, 0); + env.call_public(public_transfer_call_interface); + + // Check balances + utils::check_public_balance(token_contract_address, owner, mint_amount - transfer_amount); + utils::check_public_balance(token_contract_address, recipient, transfer_amount); +} + +#[test] +unconstrained fn public_transfer_to_self() { + // Setup without account contracts. We are not using authwits here, so dummy accounts are enough + let (env, token_contract_address, owner, _, mint_amount) = utils::setup_and_mint(/* with_account_contracts */ false); + // Transfer tokens + let transfer_amount = mint_amount / 10; + let public_transfer_call_interface = Token::at(token_contract_address).transfer_public(owner, owner, transfer_amount, 0); + env.call_public(public_transfer_call_interface); + + // Check balances + utils::check_public_balance(token_contract_address, owner, mint_amount); +} + +#[test] +unconstrained fn public_transfer_on_behalf_of_other() { + // Setup with account contracts. Slower since we actually deploy them, but needed for authwits. + let (env, token_contract_address, owner, recipient, mint_amount) = utils::setup_and_mint(/* with_account_contracts */ true); + let transfer_amount = mint_amount / 10; + let public_transfer_from_call_interface = Token::at(token_contract_address).transfer_public(owner, recipient, transfer_amount, 1); + authwit_cheatcodes::add_public_authwit_from_call_interface(owner, recipient, public_transfer_from_call_interface); + // Impersonate recipient to perform the call + cheatcodes::set_contract_address(recipient); + // Transfer tokens + env.call_public(public_transfer_from_call_interface); + // Check balances + utils::check_public_balance(token_contract_address, owner, mint_amount - transfer_amount); + utils::check_public_balance(token_contract_address, recipient, transfer_amount); +} + +#[test] +unconstrained fn public_transfer_failure_more_than_balance() { + // Setup without account contracts. We are not using authwits here, so dummy accounts are enough + let (env, token_contract_address, owner, recipient, mint_amount) = utils::setup_and_mint(/* with_account_contracts */ false); + // Transfer tokens + let transfer_amount = mint_amount + 1; + let public_transfer_call_interface = Token::at(token_contract_address).transfer_public(owner, recipient, transfer_amount, 0); + // Try to transfer tokens + env.assert_public_call_fails(public_transfer_call_interface); + + // Check balances + utils::check_public_balance(token_contract_address, owner, mint_amount); +} + +#[test] +unconstrained fn public_transfer_failure_on_behalf_of_self_non_zero_nonce() { + // Setup without account contracts. We are not using authwits here, so dummy accounts are enough + let (env, token_contract_address, owner, recipient, mint_amount) = utils::setup_and_mint(/* with_account_contracts */ false); + // Transfer tokens + let transfer_amount = mint_amount / 10; + let public_transfer_call_interface = Token::at(token_contract_address).transfer_public(owner, recipient, transfer_amount, unsafe_rand()); + // Try to transfer tokens + env.assert_public_call_fails(public_transfer_call_interface); + + // Check balances + utils::check_public_balance(token_contract_address, owner, mint_amount); +} + +#[test] +unconstrained fn public_transfer_failure_on_behalf_of_other_without_approval() { + // Setup with account contracts. Slower since we actually deploy them, but needed for authwits. + let (env, token_contract_address, owner, recipient, mint_amount) = utils::setup_and_mint(/* with_account_contracts */ true); + let transfer_amount = mint_amount / 10; + let public_transfer_from_call_interface = Token::at(token_contract_address).transfer_public(owner, recipient, transfer_amount, 1); + // Impersonate recipient to perform the call + cheatcodes::set_contract_address(recipient); + // Try to transfer tokens + env.assert_public_call_fails(public_transfer_from_call_interface); + // Check balances + utils::check_public_balance(token_contract_address, owner, mint_amount); + utils::check_public_balance(token_contract_address, recipient, 0); +} + +#[test] +unconstrained fn public_transfer_failure_on_behalf_of_other_more_than_balance() { + // Setup with account contracts. Slower since we actually deploy them, but needed for authwits. + let (env, token_contract_address, owner, recipient, mint_amount) = utils::setup_and_mint(/* with_account_contracts */ true); + let transfer_amount = mint_amount + 1; + let public_transfer_from_call_interface = Token::at(token_contract_address).transfer_public(owner, recipient, transfer_amount, 1); + authwit_cheatcodes::add_public_authwit_from_call_interface(owner, recipient, public_transfer_from_call_interface); + // Impersonate recipient to perform the call + cheatcodes::set_contract_address(recipient); + // Try to transfer tokens + env.assert_public_call_fails(public_transfer_from_call_interface); + // Check balances + utils::check_public_balance(token_contract_address, owner, mint_amount); + utils::check_public_balance(token_contract_address, recipient, 0); +} + +#[test] +unconstrained fn public_transfer_failure_on_behalf_of_other_wrong_caller() { + // Setup with account contracts. Slower since we actually deploy them, but needed for authwits. + let (env, token_contract_address, owner, recipient, mint_amount) = utils::setup_and_mint(/* with_account_contracts */ true); + let transfer_amount = mint_amount / 10; + let public_transfer_from_call_interface = Token::at(token_contract_address).transfer_public(owner, recipient, transfer_amount, 1); + authwit_cheatcodes::add_public_authwit_from_call_interface(owner, owner, public_transfer_from_call_interface); + // Impersonate recipient to perform the call + cheatcodes::set_contract_address(recipient); + // Try to transfer tokens + env.assert_public_call_fails(public_transfer_from_call_interface); + // Check balances + utils::check_public_balance(token_contract_address, owner, mint_amount); + utils::check_public_balance(token_contract_address, recipient, 0); +} diff --git a/noir-projects/noir-contracts/contracts/token_contract/src/test/unshield.nr b/noir-projects/noir-contracts/contracts/token_contract/src/test/unshield.nr deleted file mode 100644 index 23b4b31d4d5..00000000000 --- a/noir-projects/noir-contracts/contracts/token_contract/src/test/unshield.nr +++ /dev/null @@ -1,12 +0,0 @@ -use crate::test::utils; -use crate::Token; - -#[test] -unconstrained fn unshield() { - // Setup without account contracts. We are not using authwits here, so dummy accounts are enough - let (env, token_contract_address, owner, _, mint_amount) = utils::setup_and_mint(/* with_account_contracts */ false); - let unshield_amount = 1000; - let unshield_call_interface = Token::at(token_contract_address).unshield(owner, owner, unshield_amount, 0); - env.call_private_void(unshield_call_interface); - utils::check_public_balance(token_contract_address, owner, mint_amount + unshield_amount); -} diff --git a/noir-projects/noir-contracts/contracts/token_contract/src/test/unshielding.nr b/noir-projects/noir-contracts/contracts/token_contract/src/test/unshielding.nr new file mode 100644 index 00000000000..c5ba8f304a6 --- /dev/null +++ b/noir-projects/noir-contracts/contracts/token_contract/src/test/unshielding.nr @@ -0,0 +1,87 @@ +use crate::test::utils; +use dep::aztec::{oracle::unsafe_rand::unsafe_rand, test::helpers::cheatcodes}; +use dep::authwit::cheatcodes as authwit_cheatcodes; +use crate::Token; + +#[test] +unconstrained fn unshield_on_behalf_of_self() { + // Setup without account contracts. We are not using authwits here, so dummy accounts are enough + let (env, token_contract_address, owner, _, mint_amount) = utils::setup_and_mint(/* with_account_contracts */ false); + + let unshield_amount = mint_amount / 10; + let unshield_call_interface = Token::at(token_contract_address).unshield(owner, owner, unshield_amount, 0); + env.call_private_void(unshield_call_interface); + utils::check_public_balance(token_contract_address, owner, mint_amount + unshield_amount); +} + +#[test] +unconstrained fn unshield_on_behalf_of_other() { + let (env, token_contract_address, owner, recipient, mint_amount) = utils::setup_and_mint(/* with_account_contracts */ true); + + let unshield_amount = mint_amount / 10; + let unshield_call_interface = Token::at(token_contract_address).unshield(owner, recipient, unshield_amount, 0); + authwit_cheatcodes::add_private_authwit_from_call_interface(owner, recipient, unshield_call_interface); + // Impersonate recipient + cheatcodes::set_contract_address(recipient); + // Unshield tokens + env.call_private_void(unshield_call_interface); + utils::check_public_balance(token_contract_address, recipient, unshield_amount); +} + +#[test(should_fail_with="Balance too low")] +unconstrained fn unshield_failure_more_than_balance() { + // Setup without account contracts. We are not using authwits here, so dummy accounts are enough + let (env, token_contract_address, owner, _, mint_amount) = utils::setup_and_mint(/* with_account_contracts */ false); + + let unshield_amount = mint_amount + 1; + let unshield_call_interface = Token::at(token_contract_address).unshield(owner, owner, unshield_amount, 0); + env.call_private_void(unshield_call_interface); +} + +#[test(should_fail_with="invalid nonce")] +unconstrained fn unshield_failure_on_behalf_of_self_non_zero_nonce() { + // Setup without account contracts. We are not using authwits here, so dummy accounts are enough + let (env, token_contract_address, owner, _, mint_amount) = utils::setup_and_mint(/* with_account_contracts */ false); + + let unshield_amount = mint_amount + 1; + let unshield_call_interface = Token::at(token_contract_address).unshield(owner, owner, unshield_amount, unsafe_rand()); + env.call_private_void(unshield_call_interface); +} + +#[test(should_fail_with="Balance too low")] +unconstrained fn unshield_failure_on_behalf_of_other_more_than_balance() { + let (env, token_contract_address, owner, recipient, mint_amount) = utils::setup_and_mint(/* with_account_contracts */ true); + + let unshield_amount = mint_amount + 1; + let unshield_call_interface = Token::at(token_contract_address).unshield(owner, recipient, unshield_amount, 0); + authwit_cheatcodes::add_private_authwit_from_call_interface(owner, recipient, unshield_call_interface); + // Impersonate recipient + cheatcodes::set_contract_address(recipient); + // Unshield tokens + env.call_private_void(unshield_call_interface); +} + +#[test(should_fail)] +unconstrained fn unshield_failure_on_behalf_of_other_invalid_designated_caller() { + let (env, token_contract_address, owner, recipient, mint_amount) = utils::setup_and_mint(/* with_account_contracts */ true); + + let unshield_amount = mint_amount + 1; + let unshield_call_interface = Token::at(token_contract_address).unshield(owner, recipient, unshield_amount, 0); + authwit_cheatcodes::add_private_authwit_from_call_interface(owner, owner, unshield_call_interface); + // Impersonate recipient + cheatcodes::set_contract_address(recipient); + // Unshield tokens + env.call_private_void(unshield_call_interface); +} + +#[test(should_fail)] +unconstrained fn unshield_failure_on_behalf_of_other_no_approval() { + let (env, token_contract_address, owner, recipient, mint_amount) = utils::setup_and_mint(/* with_account_contracts */ true); + + let unshield_amount = mint_amount + 1; + let unshield_call_interface = Token::at(token_contract_address).unshield(owner, recipient, unshield_amount, 0); + // Impersonate recipient + cheatcodes::set_contract_address(recipient); + // Unshield tokens + env.call_private_void(unshield_call_interface); +} diff --git a/noir-projects/noir-contracts/contracts/token_contract/src/test/utils.nr b/noir-projects/noir-contracts/contracts/token_contract/src/test/utils.nr index 0a90ff8ac31..0af0e4731e3 100644 --- a/noir-projects/noir-contracts/contracts/token_contract/src/test/utils.nr +++ b/noir-projects/noir-contracts/contracts/token_contract/src/test/utils.nr @@ -3,7 +3,7 @@ use dep::aztec::{ test::helpers::{cheatcodes, test_environment::TestEnvironment}, protocol_types::storage::map::derive_storage_slot_in_map, note::{note_getter::{MAX_NOTES_PER_PAGE, view_notes}, note_viewer_options::NoteViewerOptions}, - oracle::storage::storage_read + oracle::{unsafe_rand::unsafe_rand, storage::storage_read} }; use crate::{types::{token_note::TokenNote, transparent_note::TransparentNote}, Token}; @@ -44,7 +44,7 @@ pub fn setup_and_mint(with_account_contracts: bool) -> (&mut TestEnvironment, Az let (env, token_contract_address, owner, recipient) = setup(with_account_contracts); let mint_amount = 10000; // Mint some tokens - let secret = 1; + let secret = unsafe_rand(); let secret_hash = compute_secret_hash(secret); let mint_private_call_interface = Token::at(token_contract_address).mint_private(mint_amount, secret_hash); env.call_public(mint_private_call_interface); @@ -81,18 +81,14 @@ pub fn check_public_balance(token_contract_address: AztecAddress, address: Aztec let balances_slot = Token::storage().public_balances.slot; let address_slot = derive_storage_slot_in_map(balances_slot, address); let fields = storage_read(address_slot); - assert(U128::deserialize(fields).to_field() == address_amount); + assert(U128::deserialize(fields).to_field() == address_amount, "Public balance is not correct"); cheatcodes::set_contract_address(current_contract_address); } pub fn check_private_balance(token_contract_address: AztecAddress, address: AztecAddress, address_amount: Field) { let current_contract_address = cheatcodes::get_contract_address(); cheatcodes::set_contract_address(token_contract_address); - - let balances_slot = Token::storage().balances.slot; - let address_slot = derive_storage_slot_in_map(balances_slot, address); - let mut options = NoteViewerOptions::new(); - let opt_notes: [Option; MAX_NOTES_PER_PAGE] = view_notes(address_slot, options); - assert(opt_notes[0].unwrap().amount.to_field() == address_amount); + let balance_of_private = Token::balance_of_private(address); + assert(balance_of_private == address_amount, "Private balance is not correct"); cheatcodes::set_contract_address(current_contract_address); } diff --git a/yarn-project/txe/src/bin/index.ts b/yarn-project/txe/src/bin/index.ts index 62eb72f7a63..b54f1e91cc2 100644 --- a/yarn-project/txe/src/bin/index.ts +++ b/yarn-project/txe/src/bin/index.ts @@ -1,4 +1,5 @@ #!/usr/bin/env -S node --no-warnings +import { Fr } from '@aztec/foundation/fields'; import { JsonRpcServer } from '@aztec/foundation/json-rpc/server'; import { type Logger, createDebugLogger } from '@aztec/foundation/log'; @@ -58,7 +59,7 @@ class TXEDispatcher { * @returns A running http server. */ export function startTXEHttpServer(dispatcher: TXEDispatcher, port: string | number): http.Server { - const txeServer = new JsonRpcServer(dispatcher, {}, {}, ['init']); + const txeServer = new JsonRpcServer(dispatcher, { Fr }, {}, ['init']); const app = txeServer.getApp(); const httpServer = http.createServer(app.callback()); diff --git a/yarn-project/txe/src/oracle/txe_oracle.ts b/yarn-project/txe/src/oracle/txe_oracle.ts index 488a71c4940..11ffcd0204f 100644 --- a/yarn-project/txe/src/oracle/txe_oracle.ts +++ b/yarn-project/txe/src/oracle/txe_oracle.ts @@ -39,7 +39,7 @@ import { Aes128, Schnorr } from '@aztec/circuits.js/barretenberg'; import { computePublicDataTreeLeafSlot, siloNoteHash, siloNullifier } from '@aztec/circuits.js/hash'; import { type ContractArtifact, type FunctionAbi, FunctionSelector, countArgumentsSize } from '@aztec/foundation/abi'; import { AztecAddress } from '@aztec/foundation/aztec-address'; -import { Fq, Fr, GrumpkinScalar, type Point } from '@aztec/foundation/fields'; +import { Fr, GrumpkinScalar, type Point } from '@aztec/foundation/fields'; import { type Logger, applyStringFormatting } from '@aztec/foundation/log'; import { Timer } from '@aztec/foundation/timer'; import { type KeyStore } from '@aztec/key-store'; @@ -102,20 +102,12 @@ export class TXE implements TypedOracle { // Utils - getChainId(): Promise { - return Promise.resolve(this.chainId); + async getChainId() { + return this.chainId; } - getVersion(): Promise { - return Promise.resolve(this.version); - } - - setChainId(chainId: Fr) { - this.chainId = chainId; - } - - setVersion(version: Fr) { - this.version = version; + async getVersion() { + return this.version; } getMsgSender() { @@ -171,7 +163,12 @@ export class TXE implements TypedOracle { await this.txeDatabase.addContractArtifact(computeContractClassId(contractClass), artifact); } - async getPrivateContextInputs(blockNumber: number, sideEffectsCounter = this.sideEffectsCounter) { + async getPrivateContextInputs( + blockNumber: number, + sideEffectsCounter = this.sideEffectsCounter, + isStaticCall = false, + isDelegateCall = false, + ) { const trees = this.getTrees(); const stateReference = await trees.getStateReference(true); const inputs = PrivateContextInputs.empty(); @@ -180,6 +177,8 @@ export class TXE implements TypedOracle { inputs.callContext.msgSender = this.msgSender; inputs.callContext.storageContractAddress = this.contractAddress; inputs.callContext.sideEffectCounter = sideEffectsCounter; + inputs.callContext.isStaticCall = isStaticCall; + inputs.callContext.isDelegateCall = isDelegateCall; inputs.startSideEffectCounter = sideEffectsCounter; inputs.callContext.functionSelector = this.functionSelector; return inputs; @@ -208,14 +207,14 @@ export class TXE implements TypedOracle { const db = this.trees.asLatest(); const siloedNullifier = siloNullifier(this.contractAddress, nullifier); await db.batchInsert(MerkleTreeId.NULLIFIER_TREE, [siloedNullifier.toBuffer()], NULLIFIER_SUBTREE_HEIGHT); - return Promise.resolve(); + return; } async avmOpcodeEmitNoteHash(innerNoteHash: Fr) { const db = this.trees.asLatest(); const noteHash = siloNoteHash(this.contractAddress, innerNoteHash); await db.appendLeaves(MerkleTreeId.NOTE_HASH_TREE, [noteHash]); - return Promise.resolve(); + return; } deriveKeys(secret: Fr) { @@ -233,28 +232,28 @@ export class TXE implements TypedOracle { // TypedOracle - getBlockNumber(): Promise { - return Promise.resolve(this.blockNumber); + async getBlockNumber() { + return this.blockNumber; } - getContractAddress(): Promise { - return Promise.resolve(this.contractAddress); + async getContractAddress() { + return this.contractAddress; } getRandomField() { return Fr.random(); } - packArgumentsArray(args: Fr[]): Promise { - return Promise.resolve(this.packedValuesCache.pack(args)); + async packArgumentsArray(args: Fr[]): Promise { + return this.packedValuesCache.pack(args); } - packReturns(returns: Fr[]): Promise { - return Promise.resolve(this.packedValuesCache.pack(returns)); + async packReturns(returns: Fr[]): Promise { + return this.packedValuesCache.pack(returns); } - unpackReturns(returnsHash: Fr): Promise { - return Promise.resolve(this.packedValuesCache.unpack(returnsHash)); + async unpackReturns(returnsHash: Fr): Promise { + return this.packedValuesCache.unpack(returnsHash); } getKeyValidationRequest(pkMHash: Fr): Promise { @@ -266,7 +265,7 @@ export class TXE implements TypedOracle { if (!contractInstance) { throw new Error(`Contract instance not found for address ${address}`); } - return Promise.resolve(contractInstance); + return contractInstance; } getMembershipWitness(_blockNumber: number, _treeId: MerkleTreeId, _leafValue: Fr): Promise { @@ -333,19 +332,19 @@ export class TXE implements TypedOracle { throw new Error('Method not implemented.'); } - getCompleteAddress(account: AztecAddress): Promise { - return Promise.resolve(this.txeDatabase.getAccount(account)); + async getCompleteAddress(account: AztecAddress): Promise { + return this.txeDatabase.getAccount(account); } async getAuthWitness(messageHash: Fr): Promise { - return await this.txeDatabase.getAuthWitness(messageHash); + return this.txeDatabase.getAuthWitness(messageHash); } popCapsule(): Promise { throw new Error('Method not implemented.'); } - getNotes( + async getNotes( storageSlot: Fr, numSelects: number, selectByIndexes: number[], @@ -388,7 +387,7 @@ export class TXE implements TypedOracle { .join(', ')}`, ); - return Promise.resolve(notes); + return notes; } async notifyCreatedNote(storageSlot: Fr, noteTypeId: Fr, noteItems: Fr[], innerNoteHash: Fr, counter: number) { @@ -414,7 +413,7 @@ export class TXE implements TypedOracle { const db = this.trees.asLatest(); const siloedNullifier = siloNullifier(this.contractAddress, innerNullifier); await db.batchInsert(MerkleTreeId.NULLIFIER_TREE, [siloedNullifier.toBuffer()], NULLIFIER_SUBTREE_HEIGHT); - return Promise.resolve(); + return; } async checkNullifierExists(innerNullifier: Fr): Promise { @@ -512,8 +511,8 @@ export class TXE implements TypedOracle { functionSelector: FunctionSelector, argsHash: Fr, sideEffectCounter: number, - _isStaticCall: boolean, - _isDelegateCall: boolean, + isStaticCall: boolean, + isDelegateCall: boolean, ): Promise { this.logger.verbose( `Executing external function ${targetContractAddress}:${functionSelector}(${await this.getDebugFunctionName( @@ -533,7 +532,13 @@ export class TXE implements TypedOracle { const artifact = await this.contractDataOracle.getFunctionArtifact(targetContractAddress, functionSelector); const acir = artifact.bytecode; - const initialWitness = await this.getInitialWitness(artifact, argsHash, sideEffectCounter); + const initialWitness = await this.getInitialWitness( + artifact, + argsHash, + sideEffectCounter, + isStaticCall, + isDelegateCall, + ); const acvmCallback = new Oracle(this); const timer = new Timer(); try { @@ -547,12 +552,8 @@ export class TXE implements TypedOracle { extractCallStack(err, artifact.debug), { cause: err }, ); - this.logger.debug( - `Error executing private function ${targetContractAddress}:${functionSelector}\n${createSimulationError( - execError, - )}`, - ); - throw execError; + this.logger.debug(`Error executing private function ${targetContractAddress}:${functionSelector}`); + throw createSimulationError(execError); }); const duration = timer.ms(); const returnWitness = witnessMapToFields(acirExecutionResult.returnWitness); @@ -584,7 +585,13 @@ export class TXE implements TypedOracle { } } - async getInitialWitness(abi: FunctionAbi, argsHash: Fr, sideEffectCounter: number) { + async getInitialWitness( + abi: FunctionAbi, + argsHash: Fr, + sideEffectCounter: number, + isStaticCall: boolean, + isDelegateCall: boolean, + ) { const argumentsSize = countArgumentsSize(abi); const args = this.packedValuesCache.unpack(argsHash); @@ -593,7 +600,12 @@ export class TXE implements TypedOracle { throw new Error('Invalid arguments size'); } - const privateContextInputs = await this.getPrivateContextInputs(this.blockNumber - 1, sideEffectCounter); + const privateContextInputs = await this.getPrivateContextInputs( + this.blockNumber - 1, + sideEffectCounter, + isStaticCall, + isDelegateCall, + ); const fields = [...privateContextInputs.toFields(), ...args]; @@ -603,21 +615,21 @@ export class TXE implements TypedOracle { public async getDebugFunctionName(address: AztecAddress, selector: FunctionSelector): Promise { const instance = await this.contractDataOracle.getContractInstance(address); if (!instance) { - return Promise.resolve(undefined); + return undefined; } const artifact = await this.contractDataOracle.getContractArtifact(instance!.contractClassId); if (!artifact) { - return Promise.resolve(undefined); + return undefined; } const f = artifact.functions.find(f => FunctionSelector.fromNameAndParameters(f.name, f.parameters).equals(selector), ); if (!f) { - return Promise.resolve(undefined); + return undefined; } - return Promise.resolve(`${artifact.name}:${f.name}`); + return `${artifact.name}:${f.name}`; } async executePublicFunction( diff --git a/yarn-project/txe/src/txe_service/txe_service.ts b/yarn-project/txe/src/txe_service/txe_service.ts index b1ae9449293..eeea2543b7a 100644 --- a/yarn-project/txe/src/txe_service/txe_service.ts +++ b/yarn-project/txe/src/txe_service/txe_service.ts @@ -521,7 +521,6 @@ export class TXEService { async getPublicKeysAndPartialAddress(address: ForeignCallSingle) { const parsedAddress = AztecAddress.fromField(fromSingle(address)); const { publicKeys, partialAddress } = await this.typedOracle.getCompleteAddress(parsedAddress); - return toForeignCallResult([toArray([...publicKeys.toFields(), partialAddress])]); } @@ -638,4 +637,12 @@ export class TXEService { ]; return toForeignCallResult([toArray(fields)]); } + + async getChainId() { + return toForeignCallResult([toSingle(await this.typedOracle.getChainId())]); + } + + async getVersion() { + return toForeignCallResult([toSingle(await this.typedOracle.getVersion())]); + } } From 53b2192dae528c2735a8a312d0afce5190dd3e4b Mon Sep 17 00:00:00 2001 From: thunkar Date: Fri, 21 Jun 2024 16:10:09 +0000 Subject: [PATCH 62/75] test tweaks --- .../contracts/token_contract/src/test/burn.nr | 16 +++++++++++----- .../token_contract/src/test/shielding.nr | 4 ++-- .../token_contract/src/test/transfer_public.nr | 2 +- 3 files changed, 14 insertions(+), 8 deletions(-) diff --git a/noir-projects/noir-contracts/contracts/token_contract/src/test/burn.nr b/noir-projects/noir-contracts/contracts/token_contract/src/test/burn.nr index 45142210359..af0e6cb3c31 100644 --- a/noir-projects/noir-contracts/contracts/token_contract/src/test/burn.nr +++ b/noir-projects/noir-contracts/contracts/token_contract/src/test/burn.nr @@ -5,13 +5,19 @@ use crate::Token; #[test] unconstrained fn burn_public_success() { - let (env, token_contract_address, owner, recipient, mint_amount) = utils::setup_and_mint(/* with_account_contracts */ true); + let (env, token_contract_address, owner, recipient, mint_amount) = utils::setup_and_mint(/* with_account_contracts */ false); let burn_amount = mint_amount / 10; // Burn less than balance let burn_call_interface = Token::at(token_contract_address).burn_public(owner, burn_amount, 0); env.call_public(burn_call_interface); utils::check_public_balance(token_contract_address, owner, mint_amount - burn_amount); +} + +#[test] +unconstrained fn burn_public_on_behalf_of_other() { + let (env, token_contract_address, owner, recipient, mint_amount) = utils::setup_and_mint(/* with_account_contracts */ true); + let burn_amount = mint_amount / 10; // Burn on behalf of other let burn_call_interface = Token::at(token_contract_address).burn_public(owner, burn_amount, unsafe_rand()); @@ -20,7 +26,7 @@ unconstrained fn burn_public_success() { cheatcodes::set_contract_address(recipient); // Burn tokens env.call_public(burn_call_interface); - utils::check_public_balance(token_contract_address, owner, mint_amount - 2 * burn_amount); + utils::check_public_balance(token_contract_address, owner, mint_amount - burn_amount); } #[test] @@ -130,7 +136,7 @@ unconstrained fn burn_private_failure_on_behalf_of_self_non_zero_nonce() { #[test(should_fail)] unconstrained fn burn_private_failure_on_behalf_of_other_more_than_balance() { - let (env, token_contract_address, owner, recipient, mint_amount) = utils::setup_and_mint(/* with_account_contracts */ false); + let (env, token_contract_address, owner, recipient, mint_amount) = utils::setup_and_mint(/* with_account_contracts */ true); // Burn more than balance let burn_amount = mint_amount * 10; @@ -145,7 +151,7 @@ unconstrained fn burn_private_failure_on_behalf_of_other_more_than_balance() { #[test(should_fail)] unconstrained fn burn_private_failure_on_behalf_of_other_without_approval() { - let (env, token_contract_address, owner, recipient, mint_amount) = utils::setup_and_mint(/* with_account_contracts */ false); + let (env, token_contract_address, owner, recipient, mint_amount) = utils::setup_and_mint(/* with_account_contracts */ true); // Burn more than balance let burn_amount = mint_amount / 10; @@ -159,7 +165,7 @@ unconstrained fn burn_private_failure_on_behalf_of_other_without_approval() { #[test(should_fail)] unconstrained fn burn_private_failure_on_behalf_of_other_wrong_designated_caller() { - let (env, token_contract_address, owner, recipient, mint_amount) = utils::setup_and_mint(/* with_account_contracts */ false); + let (env, token_contract_address, owner, recipient, mint_amount) = utils::setup_and_mint(/* with_account_contracts */ true); // Burn more than balance let burn_amount = mint_amount / 10; diff --git a/noir-projects/noir-contracts/contracts/token_contract/src/test/shielding.nr b/noir-projects/noir-contracts/contracts/token_contract/src/test/shielding.nr index 42e0f4ff322..970831697d8 100644 --- a/noir-projects/noir-contracts/contracts/token_contract/src/test/shielding.nr +++ b/noir-projects/noir-contracts/contracts/token_contract/src/test/shielding.nr @@ -72,7 +72,7 @@ unconstrained fn shielding_on_behalf_of_other() { #[test] unconstrained fn shielding_failure_on_behalf_of_self_more_than_balance() { // Setup without account contracts. We are not using authwits here, so dummy accounts are enough - let (env, token_contract_address, owner, _, mint_amount) = utils::setup_and_mint(/* with_account_contracts */ false); + let (env, token_contract_address, owner, _, mint_amount) = utils::setup_and_mint(/* with_account_contracts */ true); let secret = unsafe_rand(); let secret_hash = compute_secret_hash(secret); // Shield tokens @@ -88,7 +88,7 @@ unconstrained fn shielding_failure_on_behalf_of_self_more_than_balance() { #[test] unconstrained fn shielding_failure_on_behalf_of_self_invalid_nonce() { // Setup without account contracts. We are not using authwits here, so dummy accounts are enough - let (env, token_contract_address, owner, _, mint_amount) = utils::setup_and_mint(/* with_account_contracts */ false); + let (env, token_contract_address, owner, _, mint_amount) = utils::setup_and_mint(/* with_account_contracts */ true); let secret = unsafe_rand(); let secret_hash = compute_secret_hash(secret); // Shield tokens diff --git a/noir-projects/noir-contracts/contracts/token_contract/src/test/transfer_public.nr b/noir-projects/noir-contracts/contracts/token_contract/src/test/transfer_public.nr index 2294a8a05d3..ae0b631ce37 100644 --- a/noir-projects/noir-contracts/contracts/token_contract/src/test/transfer_public.nr +++ b/noir-projects/noir-contracts/contracts/token_contract/src/test/transfer_public.nr @@ -63,7 +63,7 @@ unconstrained fn public_transfer_failure_more_than_balance() { #[test] unconstrained fn public_transfer_failure_on_behalf_of_self_non_zero_nonce() { // Setup without account contracts. We are not using authwits here, so dummy accounts are enough - let (env, token_contract_address, owner, recipient, mint_amount) = utils::setup_and_mint(/* with_account_contracts */ false); + let (env, token_contract_address, owner, recipient, mint_amount) = utils::setup_and_mint(/* with_account_contracts */ true); // Transfer tokens let transfer_amount = mint_amount / 10; let public_transfer_call_interface = Token::at(token_contract_address).transfer_public(owner, recipient, transfer_amount, unsafe_rand()); From 2c2424dfd75c3865cfbcee2f774bafe4716ab4ce Mon Sep 17 00:00:00 2001 From: thunkar Date: Mon, 24 Jun 2024 09:42:10 +0000 Subject: [PATCH 63/75] use only public avm exports --- yarn-project/simulator/src/avm/index.ts | 3 - yarn-project/simulator/src/public/index.ts | 1 - yarn-project/txe/src/bin/index.ts | 1 + yarn-project/txe/src/oracle/txe_oracle.ts | 83 +++++----------------- 4 files changed, 20 insertions(+), 68 deletions(-) diff --git a/yarn-project/simulator/src/avm/index.ts b/yarn-project/simulator/src/avm/index.ts index 293890a73fb..5f426c8aa7d 100644 --- a/yarn-project/simulator/src/avm/index.ts +++ b/yarn-project/simulator/src/avm/index.ts @@ -1,4 +1 @@ export * from './avm_simulator.js'; -export * from './journal/index.js'; -export * from './avm_machine_state.js'; -export * from './avm_context.js'; diff --git a/yarn-project/simulator/src/public/index.ts b/yarn-project/simulator/src/public/index.ts index 900b5ce4210..933b918f4c9 100644 --- a/yarn-project/simulator/src/public/index.ts +++ b/yarn-project/simulator/src/public/index.ts @@ -8,4 +8,3 @@ export * from './public_db_sources.js'; export * from './public_kernel.js'; export * from './public_kernel_circuit_simulator.js'; export { PublicProcessor, PublicProcessorFactory } from './public_processor.js'; -export * from './transitional_adaptors.js'; diff --git a/yarn-project/txe/src/bin/index.ts b/yarn-project/txe/src/bin/index.ts index b54f1e91cc2..f46cd541c95 100644 --- a/yarn-project/txe/src/bin/index.ts +++ b/yarn-project/txe/src/bin/index.ts @@ -63,6 +63,7 @@ export function startTXEHttpServer(dispatcher: TXEDispatcher, port: string | num const app = txeServer.getApp(); const httpServer = http.createServer(app.callback()); + httpServer.timeout = 1e3 * 60 * 5; // 5 minutes httpServer.listen(port); return httpServer; diff --git a/yarn-project/txe/src/oracle/txe_oracle.ts b/yarn-project/txe/src/oracle/txe_oracle.ts index 11ffcd0204f..c853963dc15 100644 --- a/yarn-project/txe/src/oracle/txe_oracle.ts +++ b/yarn-project/txe/src/oracle/txe_oracle.ts @@ -31,6 +31,7 @@ import { PublicCallRequest, PublicDataTreeLeaf, type PublicDataTreeLeafPreimage, + TxContext, computeContractClassId, deriveKeys, getContractClassFromArtifact, @@ -45,23 +46,17 @@ import { Timer } from '@aztec/foundation/timer'; import { type KeyStore } from '@aztec/key-store'; import { ContractDataOracle } from '@aztec/pxe'; import { - AvmContext, - AvmMachineState, - AvmPersistableStateManager, - AvmSimulator, ContractsDataSourcePublicDB, ExecutionError, type ExecutionNoteCache, - HostStorage, type MessageLoadOracleInputs, type NoteData, Oracle, type PackedValuesCache, + PublicExecutor, type TypedOracle, WorldStateDB, acvm, - convertAvmResultsToPxResult, - createAvmExecutionEnvironment, createSimulationError, extractCallStack, pickNotes, @@ -638,30 +633,6 @@ export class TXE implements TypedOracle { args: Fr[], callContext: CallContext, ) { - const fnName = - (await this.getDebugFunctionName(targetContractAddress, functionSelector)) ?? - `${targetContractAddress}:${functionSelector}`; - - this.logger.debug(`[AVM] Executing public external function ${fnName}.`); - const timer = new Timer(); - const startGas = Gas.test(); - const hostStorage = new HostStorage( - new TXEPublicStateDB(this), - new ContractsDataSourcePublicDB(new TXEPublicContractDataSource(this)), - new WorldStateDB(this.trees.asLatest()), - ); - - const worldStateJournal = new AvmPersistableStateManager(hostStorage); - for (const nullifier of this.noteCache.getNullifiers(targetContractAddress)) { - worldStateJournal.nullifiers.cache.appendSiloed(new Fr(nullifier)); - } - worldStateJournal.trace.accessCounter = callContext.sideEffectCounter; - const execution = { - contractAddress: targetContractAddress, - functionSelector, - args, - callContext, - }; const header = Header.empty(); header.state = await this.trees.getStateReference(true); header.globalVariables.blockNumber = new Fr(await this.getBlockNumber()); @@ -677,44 +648,28 @@ export class TXE implements TypedOracle { header.state.l1ToL2MessageTree.root = Fr.fromBuffer( (await this.trees.getTreeInfo(MerkleTreeId.L1_TO_L2_MESSAGE_TREE, true)).root, ); - const executionEnv = createAvmExecutionEnvironment( - execution, + const executor = new PublicExecutor( + new TXEPublicStateDB(this), + new ContractsDataSourcePublicDB(new TXEPublicContractDataSource(this)), + new WorldStateDB(this.trees.asLatest()), header, - GlobalVariables.empty(), - GasSettings.default(), - Fr.ZERO, - ); - - const machineState = new AvmMachineState(startGas); - const avmContext = new AvmContext(worldStateJournal, executionEnv, machineState); - const simulator = new AvmSimulator(avmContext); - const avmResult = await simulator.execute(); - const bytecode = simulator.getBytecode(); - - await avmContext.persistableState.publicStorage.commitToDB(); - - this.logger.verbose( - `[AVM] ${fnName} returned, reverted: ${avmResult.reverted}${ - avmResult.reverted ? ', reason: ' + avmResult.revertReason : '' - }.`, - { - eventName: 'avm-simulation', - appCircuitName: fnName, - duration: timer.ms(), - bytecodeSize: bytecode!.length, - } satisfies AvmSimulationStats, ); + const execution = { + contractAddress: targetContractAddress, + functionSelector, + args, + callContext, + }; - const executionResult = convertAvmResultsToPxResult( - avmResult, - callContext.sideEffectCounter, + return executor.simulate( execution, - startGas, - avmContext, - bytecode, - fnName, + GlobalVariables.empty(), + Gas.test(), + TxContext.empty(), + [], + Fr.ZERO, + callContext.sideEffectCounter, ); - return executionResult; } async avmOpcodeCall( From 937f5611b41efc835a9d650b83d6c79437fc3922 Mon Sep 17 00:00:00 2001 From: thunkar Date: Mon, 24 Jun 2024 09:49:40 +0000 Subject: [PATCH 64/75] added comments for context --- yarn-project/txe/src/oracle/txe_oracle.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/yarn-project/txe/src/oracle/txe_oracle.ts b/yarn-project/txe/src/oracle/txe_oracle.ts index c853963dc15..36ce89d2dee 100644 --- a/yarn-project/txe/src/oracle/txe_oracle.ts +++ b/yarn-project/txe/src/oracle/txe_oracle.ts @@ -666,8 +666,8 @@ export class TXE implements TypedOracle { GlobalVariables.empty(), Gas.test(), TxContext.empty(), - [], - Fr.ZERO, + /* pendingNullifiers */ [], + /* transactionFee */ Fr.ZERO, callContext.sideEffectCounter, ); } From 04d215a87c1c213d10291d9282b406068311f37f Mon Sep 17 00:00:00 2001 From: thunkar Date: Mon, 24 Jun 2024 10:05:12 +0000 Subject: [PATCH 65/75] tweaked sideEffectCounters --- yarn-project/txe/src/oracle/txe_oracle.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/yarn-project/txe/src/oracle/txe_oracle.ts b/yarn-project/txe/src/oracle/txe_oracle.ts index 36ce89d2dee..907c9235eb0 100644 --- a/yarn-project/txe/src/oracle/txe_oracle.ts +++ b/yarn-project/txe/src/oracle/txe_oracle.ts @@ -570,7 +570,7 @@ export class TXE implements TypedOracle { publicInputs, ); // Apply side effects - this.sideEffectsCounter += publicInputs.endSideEffectCounter.toNumber(); + this.sideEffectsCounter = publicInputs.endSideEffectCounter.toNumber(); return callStackItem; } finally { @@ -747,7 +747,7 @@ export class TXE implements TypedOracle { ); // Apply side effects - this.sideEffectsCounter += executionResult.endSideEffectCounter.toNumber(); + this.sideEffectsCounter = executionResult.endSideEffectCounter.toNumber(); this.setContractAddress(currentContractAddress); this.setMsgSender(currentMessageSender); this.setFunctionSelector(currentFunctionSelector); From 899c65aa843b499cc02ef56147ff27911417b49c Mon Sep 17 00:00:00 2001 From: thunkar Date: Mon, 24 Jun 2024 16:18:59 +0000 Subject: [PATCH 66/75] fixed problem with initializers, more flexibility on nullifiers and notes --- .../aztec-nr/aztec/src/oracle/notes.nr | 3 +- .../aztec-nr/aztec/src/test/helpers.nr | 2 +- .../aztec/src/test/helpers/cheatcodes.nr | 17 ++++++++- .../src/test/helpers/test_environment.nr | 12 +++---- .../src/test/helpers/{types.nr => utils.nr} | 27 ++++++++++++-- .../contracts/counter_contract/src/main.nr | 21 +++++++++-- .../token_contract/src/test/minting.nr | 9 ----- .../token_contract/src/test/shielding.nr | 6 ---- .../token_contract/src/test/utils.nr | 5 --- yarn-project/txe/src/oracle/txe_oracle.ts | 36 +++++++++++++------ .../txe/src/txe_service/txe_service.ts | 12 ++++++- 11 files changed, 101 insertions(+), 49 deletions(-) rename noir-projects/aztec-nr/aztec/src/test/helpers/{types.nr => utils.nr} (80%) diff --git a/noir-projects/aztec-nr/aztec/src/oracle/notes.nr b/noir-projects/aztec-nr/aztec/src/oracle/notes.nr index 42c6bcdb7ee..4d7aad6f6e2 100644 --- a/noir-projects/aztec-nr/aztec/src/oracle/notes.nr +++ b/noir-projects/aztec-nr/aztec/src/oracle/notes.nr @@ -145,8 +145,7 @@ unconstrained pub fn get_notes( let header = NoteHeader { contract_address, nonce, storage_slot, note_hash_counter }; let serialized_note = arr_copy_slice(fields, [0; N], read_offset + 2); let mut note = Note::deserialize_content(serialized_note); - // TODO: change this to note.set_header(header) once https://github.com/noir-lang/noir/issues/4095 is fixed - Note::set_header(&mut note, header); + note.set_header(header); placeholder_opt_notes[i] = Option::some(note); }; } diff --git a/noir-projects/aztec-nr/aztec/src/test/helpers.nr b/noir-projects/aztec-nr/aztec/src/test/helpers.nr index b28a85add1c..b7164a82359 100644 --- a/noir-projects/aztec-nr/aztec/src/test/helpers.nr +++ b/noir-projects/aztec-nr/aztec/src/test/helpers.nr @@ -1,4 +1,4 @@ mod test_environment; mod cheatcodes; -mod types; +mod utils; mod keys; diff --git a/noir-projects/aztec-nr/aztec/src/test/helpers/cheatcodes.nr b/noir-projects/aztec-nr/aztec/src/test/helpers/cheatcodes.nr index 9712f1eb930..3eae7bcf256 100644 --- a/noir-projects/aztec-nr/aztec/src/test/helpers/cheatcodes.nr +++ b/noir-projects/aztec-nr/aztec/src/test/helpers/cheatcodes.nr @@ -3,7 +3,7 @@ use dep::protocol_types::{ constants::CONTRACT_INSTANCE_LENGTH, contract_instance::ContractInstance }; use crate::context::inputs::{PublicContextInputs, PrivateContextInputs}; -use crate::test::helpers::types::{Deployer, TestAccount}; +use crate::test::helpers::utils::{Deployer, TestAccount}; use crate::keys::public_keys::PublicKeys; unconstrained pub fn reset() { @@ -93,6 +93,14 @@ unconstrained pub fn assert_private_call_fails( ) } +unconstrained pub fn add_nullifiers(contractAddress: AztecAddress, nullifiers: [Field]) { + oracle_add_nullifiers(contractAddress, nullifiers) +} + +unconstrained pub fn add_note_hashes(contractAddress: AztecAddress, inner_note_hashes: [Field]) { + oracle_add_note_hashes(contractAddress, inner_note_hashes) +} + #[oracle(reset)] fn oracle_reset() {} @@ -166,3 +174,10 @@ fn oracle_assert_private_call_fails( isStaticCall: bool, isDelegateCall: bool ) {} + +#[oracle(addNullifiers)] +fn oracle_add_nullifiers(contractAddress: AztecAddress, nullifiers: [Field]) {} + +#[oracle(addNoteHashes)] +fn oracle_add_note_hashes(contractAddress: AztecAddress, inner_note_hashes: [Field]) {} + diff --git a/noir-projects/aztec-nr/aztec/src/test/helpers/test_environment.nr b/noir-projects/aztec-nr/aztec/src/test/helpers/test_environment.nr index 6447ed1f4d7..b3080218745 100644 --- a/noir-projects/aztec-nr/aztec/src/test/helpers/test_environment.nr +++ b/noir-projects/aztec-nr/aztec/src/test/helpers/test_environment.nr @@ -8,7 +8,7 @@ use crate::context::inputs::{PublicContextInputs, PrivateContextInputs}; use crate::context::{packed_returns::PackedReturns, call_interfaces::CallInterface}; use crate::context::{PrivateContext, PublicContext, PrivateVoidCallInterface}; -use crate::test::helpers::{cheatcodes, types::{Deployer, TestAccount}, keys}; +use crate::test::helpers::{cheatcodes, utils::{apply_side_effects_private, Deployer, TestAccount}, keys}; use crate::keys::constants::{NULLIFIER_INDEX, INCOMING_INDEX, OUTGOING_INDEX, TAGGING_INDEX}; use crate::hash::{hash_args, hash_args_array}; @@ -18,16 +18,12 @@ use crate::note::{ }; use crate::oracle::notes::notify_created_note; -struct TestEnvironment { - contract_address: Option, - args_hash: Option, - function_selector: Option -} +struct TestEnvironment {} impl TestEnvironment { fn new() -> Self { cheatcodes::reset(); - Self { contract_address: Option::none(), args_hash: Option::none(), function_selector: Option::none() } + Self { } } fn block_number(self) -> u32 { @@ -130,6 +126,7 @@ impl TestEnvironment { inputs.call_context.function_selector = call_interface.get_selector(); inputs.call_context.is_static_call = call_interface.get_is_static(); let public_inputs = original_fn(inputs); + apply_side_effects_private(target_address, public_inputs); cheatcodes::set_contract_address(original_contract_address); cheatcodes::set_msg_sender(original_msg_sender); @@ -151,6 +148,7 @@ impl TestEnvironment { inputs.call_context.function_selector = call_interface.get_selector(); inputs.call_context.is_static_call = call_interface.get_is_static(); let public_inputs = original_fn(inputs); + apply_side_effects_private(target_address, public_inputs); cheatcodes::set_contract_address(original_contract_address); cheatcodes::set_msg_sender(original_msg_sender); diff --git a/noir-projects/aztec-nr/aztec/src/test/helpers/types.nr b/noir-projects/aztec-nr/aztec/src/test/helpers/utils.nr similarity index 80% rename from noir-projects/aztec-nr/aztec/src/test/helpers/types.nr rename to noir-projects/aztec-nr/aztec/src/test/helpers/utils.nr index b277d3f41a6..3ae3215f3db 100644 --- a/noir-projects/aztec-nr/aztec/src/test/helpers/types.nr +++ b/noir-projects/aztec-nr/aztec/src/test/helpers/utils.nr @@ -10,6 +10,25 @@ use crate::test::helpers::cheatcodes; use crate::keys::public_keys::{PUBLIC_KEYS_LENGTH, PublicKeys}; use crate::hash::hash_args; +use crate::oracle::notes::notify_nullified_note; + +pub fn apply_side_effects_private(contract_address: AztecAddress, public_inputs: PrivateCircuitPublicInputs) { + let mut nullifiers = &[]; + for nullifier in public_inputs.new_nullifiers { + if nullifier.value != 0 { + nullifiers = nullifiers.push_back(nullifier.value); + } + } + cheatcodes::add_nullifiers(contract_address, nullifiers); + let mut note_hashes = &[]; + for note_hash in public_inputs.new_note_hashes { + if note_hash.value != 0 { + note_hashes = note_hashes.push_back(note_hash.value); + } + } + cheatcodes::add_note_hashes(contract_address, note_hashes); +} + struct Deployer { path: str, public_keys_hash: Field @@ -26,18 +45,20 @@ impl Deployer { call_interface.get_args(), self.public_keys_hash ); + let address = instance.to_address(); cheatcodes::advance_blocks(1); let block_number = cheatcodes::get_block_number(); let original_fn = call_interface.get_original(); let original_msg_sender = cheatcodes::get_msg_sender(); let original_contract_address = cheatcodes::get_contract_address(); - cheatcodes::set_contract_address(instance.to_address()); + cheatcodes::set_contract_address(address); cheatcodes::set_msg_sender(original_contract_address); let mut inputs = cheatcodes::get_private_context_inputs(block_number - 1); inputs.call_context.function_selector = call_interface.get_selector(); - let _result = original_fn(inputs); - + let public_inputs = original_fn(inputs); + apply_side_effects_private(address, public_inputs); + cheatcodes::advance_blocks(1); cheatcodes::set_contract_address(original_contract_address); cheatcodes::set_msg_sender(original_msg_sender); instance diff --git a/noir-projects/noir-contracts/contracts/counter_contract/src/main.nr b/noir-projects/noir-contracts/contracts/counter_contract/src/main.nr index 2b6ca927ce5..27631ddbe72 100644 --- a/noir-projects/noir-contracts/contracts/counter_contract/src/main.nr +++ b/noir-projects/noir-contracts/contracts/counter_contract/src/main.nr @@ -44,16 +44,19 @@ contract Counter { use dep::aztec::note::note_viewer_options::NoteViewerOptions; #[test] - fn test_initialize() { + fn test_increment() { // Setup env, generate keys let mut env = TestEnvironment::new(); let owner = env.create_account(); let outgoing_viewer = env.create_account(); + let initial_value: Field = 5; + cheatcodes::set_contract_address(owner); // Deploy contract and initialize - let initializer = Counter::interface().initialize(5, owner, outgoing_viewer); + let initializer = Counter::interface().initialize(initial_value as u64, owner, outgoing_viewer); let counter_contract = env.deploy("@aztec/noir-contracts.js/Counter").with_private_initializer(initializer); let contract_address = counter_contract.to_address(); + // Read the stored value in the note cheatcodes::set_contract_address(contract_address); @@ -61,6 +64,18 @@ contract Counter { let owner_slot = derive_storage_slot_in_map(counter_slot, owner); let mut options = NoteViewerOptions::new(); let notes: BoundedVec = view_notes(owner_slot, options); - assert(notes.get(0).value == 5); + let initial_note_value = notes.get(0).value; + assert( + initial_note_value == initial_value, f"Expected {initial_value} but got {initial_note_value}" + ); + + // Increment the counter + let increment_call_interface = Counter::at(contract_address).increment(owner, outgoing_viewer); + env.call_private_void(increment_call_interface); + let current_value_for_owner = get_counter(owner); + let expected_current_value = initial_value + 1; + assert( + expected_current_value == current_value_for_owner, f"Expected {expected_current_value} but got {current_value_for_owner}" + ); } } diff --git a/noir-projects/noir-contracts/contracts/token_contract/src/test/minting.nr b/noir-projects/noir-contracts/contracts/token_contract/src/test/minting.nr index 2fe72dbe9e8..4e92489a59a 100644 --- a/noir-projects/noir-contracts/contracts/token_contract/src/test/minting.nr +++ b/noir-projects/noir-contracts/contracts/token_contract/src/test/minting.nr @@ -118,9 +118,6 @@ unconstrained fn mint_private_failure_double_spend() { utils::check_private_balance(token_contract_address, owner, mint_amount); - // TODO: https://github.com/AztecProtocol/aztec-packages/issues/7086 - env.advance_block_by(1); - // Attempt to double spend let redeem_shield_call_interface = Token::at(token_contract_address).redeem_shield(recipient, mint_amount, secret); env.call_private_void(redeem_shield_call_interface); @@ -180,9 +177,6 @@ unconstrained fn mint_private_failure_overflow_recipient() { utils::check_private_balance(token_contract_address, owner, mint_amount); - // TODO: https://github.com/AztecProtocol/aztec-packages/issues/7086 - env.advance_block_by(1); - let mint_amount = 2.pow_32(128) - mint_amount; // Mint some tokens let secret = unsafe_rand(); @@ -227,9 +221,6 @@ unconstrained fn mint_private_failure_overflow_total_supply() { let redeem_shield_call_interface = Token::at(token_contract_address).redeem_shield(owner, mint_amount, secret_owner); env.call_private_void(redeem_shield_call_interface); - // TODO: https://github.com/AztecProtocol/aztec-packages/issues/7086 - env.advance_block_by(1); - // Redeem recipient's shielded tokens cheatcodes::set_contract_address(recipient); let redeem_shield_call_interface = Token::at(token_contract_address).redeem_shield(recipient, mint_amount, secret_recipient); diff --git a/noir-projects/noir-contracts/contracts/token_contract/src/test/shielding.nr b/noir-projects/noir-contracts/contracts/token_contract/src/test/shielding.nr index 970831697d8..66280304481 100644 --- a/noir-projects/noir-contracts/contracts/token_contract/src/test/shielding.nr +++ b/noir-projects/noir-contracts/contracts/token_contract/src/test/shielding.nr @@ -14,9 +14,6 @@ unconstrained fn shielding_on_behalf_of_self() { let shield_call_interface = Token::at(token_contract_address).shield(owner, shield_amount, secret_hash, 0); env.call_public(shield_call_interface); - // TODO: https://github.com/AztecProtocol/aztec-packages/issues/7086 - env.advance_block_by(1); - // Store a note in the cache so we can redeem it env.store_note_in_cache( &mut TransparentNote::new(shield_amount, secret_hash), @@ -48,9 +45,6 @@ unconstrained fn shielding_on_behalf_of_other() { // Shield tokens env.call_public(shield_call_interface); - // TODO: https://github.com/AztecProtocol/aztec-packages/issues/7086 - env.advance_block_by(1); - // Become owner again cheatcodes::set_contract_address(owner); // Store a note in the cache so we can redeem it diff --git a/noir-projects/noir-contracts/contracts/token_contract/src/test/utils.nr b/noir-projects/noir-contracts/contracts/token_contract/src/test/utils.nr index 0af0e4731e3..1801ddd7213 100644 --- a/noir-projects/noir-contracts/contracts/token_contract/src/test/utils.nr +++ b/noir-projects/noir-contracts/contracts/token_contract/src/test/utils.nr @@ -66,11 +66,6 @@ pub fn setup_and_mint(with_account_contracts: bool) -> (&mut TestEnvironment, Az let redeem_shield_call_interface = Token::at(token_contract_address).redeem_shield(owner, mint_amount, secret); env.call_private_void(redeem_shield_call_interface); - // TODO: https://github.com/AztecProtocol/aztec-packages/issues/7086 - // Not really sure why this is needed? Nullifier inclusion in contract initializer fails otherwise. - // If it were to fail, it should do it at line 443, investigation required - env.advance_block_by(1); - (env, token_contract_address, owner, recipient, mint_amount) } diff --git a/yarn-project/txe/src/oracle/txe_oracle.ts b/yarn-project/txe/src/oracle/txe_oracle.ts index 907c9235eb0..d7adcda65a5 100644 --- a/yarn-project/txe/src/oracle/txe_oracle.ts +++ b/yarn-project/txe/src/oracle/txe_oracle.ts @@ -165,7 +165,7 @@ export class TXE implements TypedOracle { isDelegateCall = false, ) { const trees = this.getTrees(); - const stateReference = await trees.getStateReference(true); + const stateReference = await trees.getStateReference(false); const inputs = PrivateContextInputs.empty(); inputs.historicalHeader.globalVariables.blockNumber = new Fr(blockNumber); inputs.historicalHeader.state = stateReference; @@ -225,6 +225,19 @@ export class TXE implements TypedOracle { return this.txeDatabase.addAuthWitness(authWitness.requestHash, authWitness.witness); } + async addNullifiers(contractAddress: AztecAddress, nullifiers: Fr[]) { + const db = this.trees.asLatest(); + const siloedNullifiers = nullifiers.map(nullifier => siloNullifier(contractAddress, nullifier).toBuffer()); + + await db.batchInsert(MerkleTreeId.NULLIFIER_TREE, siloedNullifiers, NULLIFIER_SUBTREE_HEIGHT); + } + + async addNoteHashes(contractAddress: AztecAddress, innerNoteHashes: Fr[]) { + const db = this.trees.asLatest(); + const siloedNoteHashes = innerNoteHashes.map(innerNoteHash => siloNoteHash(contractAddress, innerNoteHash)); + await db.appendLeaves(MerkleTreeId.NOTE_HASH_TREE, siloedNoteHashes); + } + // TypedOracle async getBlockNumber() { @@ -358,10 +371,6 @@ export class TXE implements TypedOracle { // Nullified pending notes are already removed from the list. const pendingNotes = this.noteCache.getNotes(this.contractAddress, storageSlot); - // const pendingNullifiers = this.noteCache.getNullifiers(this.contractAddress); - // const dbNotes = await this.db.getNotes(this.contractAddress, storageSlot, status); - // const dbNotesFiltered = dbNotes.filter(n => !pendingNullifiers.has((n.siloedNullifier as Fr).value)); - const notes = pickNotes(pendingNotes, { selects: selectByIndexes.slice(0, numSelects).map((index, i) => ({ selector: { index, offset: selectByOffsets[i], length: selectByLengths[i] }, @@ -398,16 +407,11 @@ export class TXE implements TypedOracle { }, counter, ); - const db = this.trees.asLatest(); - const noteHash = siloNoteHash(this.contractAddress, innerNoteHash); - await db.appendLeaves(MerkleTreeId.NOTE_HASH_TREE, [noteHash]); + return; } async notifyNullifiedNote(innerNullifier: Fr, innerNoteHash: Fr, _counter: number) { this.noteCache.nullifyNote(this.contractAddress, innerNullifier, innerNoteHash); - const db = this.trees.asLatest(); - const siloedNullifier = siloNullifier(this.contractAddress, innerNullifier); - await db.batchInsert(MerkleTreeId.NULLIFIER_TREE, [siloedNullifier.toBuffer()], NULLIFIER_SUBTREE_HEIGHT); return; } @@ -572,6 +576,16 @@ export class TXE implements TypedOracle { // Apply side effects this.sideEffectsCounter = publicInputs.endSideEffectCounter.toNumber(); + await this.addNullifiers( + targetContractAddress, + publicInputs.newNullifiers.filter(nullifier => !nullifier.isEmpty()).map(nullifier => nullifier.value), + ); + + await this.addNoteHashes( + targetContractAddress, + publicInputs.newNoteHashes.filter(noteHash => !noteHash.isEmpty()).map(noteHash => noteHash.value), + ); + return callStackItem; } finally { this.setContractAddress(currentContractAddress); diff --git a/yarn-project/txe/src/txe_service/txe_service.ts b/yarn-project/txe/src/txe_service/txe_service.ts index eeea2543b7a..ffcbb9df261 100644 --- a/yarn-project/txe/src/txe_service/txe_service.ts +++ b/yarn-project/txe/src/txe_service/txe_service.ts @@ -9,7 +9,7 @@ import { PublicDataTreeLeaf, getContractInstanceFromDeployParams, } from '@aztec/circuits.js'; -import { computePublicDataTreeLeafSlot } from '@aztec/circuits.js/hash'; +import { computePublicDataTreeLeafSlot, siloNullifier } from '@aztec/circuits.js/hash'; import { AztecAddress } from '@aztec/foundation/aztec-address'; import { type Logger } from '@aztec/foundation/log'; import { KeyStore } from '@aztec/key-store'; @@ -645,4 +645,14 @@ export class TXEService { async getVersion() { return toForeignCallResult([toSingle(await this.typedOracle.getVersion())]); } + + async addNullifier(contractAddress: ForeignCallSingle, _length: ForeignCallSingle, nullifiers: ForeignCallArray) { + await (this.typedOracle as TXE).addNullifiers(fromSingle(contractAddress), fromArray(nullifiers)); + return toForeignCallResult([]); + } + + async addNoteHash(contractAddress: ForeignCallSingle, _length: ForeignCallSingle, noteHashes: ForeignCallArray) { + await (this.typedOracle as TXE).addNoteHashes(fromSingle(contractAddress), fromArray(noteHashes)); + return toForeignCallResult([]); + } } From bd944d2e8adce8f89d0962e9c154018d1804ce1c Mon Sep 17 00:00:00 2001 From: thunkar Date: Mon, 24 Jun 2024 16:21:54 +0000 Subject: [PATCH 67/75] typo --- yarn-project/txe/src/txe_service/txe_service.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/yarn-project/txe/src/txe_service/txe_service.ts b/yarn-project/txe/src/txe_service/txe_service.ts index ffcbb9df261..f1a83c751ac 100644 --- a/yarn-project/txe/src/txe_service/txe_service.ts +++ b/yarn-project/txe/src/txe_service/txe_service.ts @@ -646,12 +646,12 @@ export class TXEService { return toForeignCallResult([toSingle(await this.typedOracle.getVersion())]); } - async addNullifier(contractAddress: ForeignCallSingle, _length: ForeignCallSingle, nullifiers: ForeignCallArray) { + async addNullifiers(contractAddress: ForeignCallSingle, _length: ForeignCallSingle, nullifiers: ForeignCallArray) { await (this.typedOracle as TXE).addNullifiers(fromSingle(contractAddress), fromArray(nullifiers)); return toForeignCallResult([]); } - async addNoteHash(contractAddress: ForeignCallSingle, _length: ForeignCallSingle, noteHashes: ForeignCallArray) { + async addNoteHashes(contractAddress: ForeignCallSingle, _length: ForeignCallSingle, noteHashes: ForeignCallArray) { await (this.typedOracle as TXE).addNoteHashes(fromSingle(contractAddress), fromArray(noteHashes)); return toForeignCallResult([]); } From 9b8062918116373a4a78bfecaa933f7ecf53dacc Mon Sep 17 00:00:00 2001 From: thunkar Date: Mon, 24 Jun 2024 16:58:35 +0000 Subject: [PATCH 68/75] removed contract import restriction --- .../src/hir/resolution/import.rs | 43 +++---------------- 1 file changed, 6 insertions(+), 37 deletions(-) diff --git a/noir/noir-repo/compiler/noirc_frontend/src/hir/resolution/import.rs b/noir/noir-repo/compiler/noirc_frontend/src/hir/resolution/import.rs index 527959dd2af..9a0be775c30 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/hir/resolution/import.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/hir/resolution/import.rs @@ -88,15 +88,12 @@ pub fn resolve_import( import_directive: &ImportDirective, def_maps: &BTreeMap, ) -> Result { - let allow_contracts = - allow_referencing_contracts(def_maps, crate_id, import_directive.module_id); - let module_scope = import_directive.module_id; let NamespaceResolution { module_id: resolved_module, namespace: resolved_namespace, mut error, - } = resolve_path_to_ns(import_directive, crate_id, crate_id, def_maps, allow_contracts)?; + } = resolve_path_to_ns(import_directive, crate_id, crate_id, def_maps)?; let name = resolve_path_name(import_directive); @@ -129,20 +126,11 @@ pub fn resolve_import( }) } -fn allow_referencing_contracts( - def_maps: &BTreeMap, - krate: CrateId, - local_id: LocalModuleId, -) -> bool { - ModuleId { krate, local_id }.module(def_maps).is_contract -} - fn resolve_path_to_ns( import_directive: &ImportDirective, crate_id: CrateId, importing_crate: CrateId, def_maps: &BTreeMap, - allow_contracts: bool, ) -> NamespaceResolutionResult { let import_path = &import_directive.path.segments; let def_map = &def_maps[&crate_id]; @@ -150,21 +138,11 @@ fn resolve_path_to_ns( match import_directive.path.kind { crate::ast::PathKind::Crate => { // Resolve from the root of the crate - resolve_path_from_crate_root( - crate_id, - importing_crate, - import_path, - def_maps, - allow_contracts, - ) + resolve_path_from_crate_root(crate_id, importing_crate, import_path, def_maps) + } + crate::ast::PathKind::Dep => { + resolve_external_dep(def_map, import_directive, def_maps, importing_crate) } - crate::ast::PathKind::Dep => resolve_external_dep( - def_map, - import_directive, - def_maps, - allow_contracts, - importing_crate, - ), crate::ast::PathKind::Plain => { // Plain paths are only used to import children modules. It's possible to allow import of external deps, but maybe this distinction is better? // In Rust they can also point to external Dependencies, if no children can be found with the specified name @@ -174,7 +152,6 @@ fn resolve_path_to_ns( import_path, import_directive.module_id, def_maps, - allow_contracts, ) } } @@ -186,7 +163,6 @@ fn resolve_path_from_crate_root( import_path: &[Ident], def_maps: &BTreeMap, - allow_contracts: bool, ) -> NamespaceResolutionResult { resolve_name_in_module( crate_id, @@ -194,7 +170,6 @@ fn resolve_path_from_crate_root( import_path, def_maps[&crate_id].root, def_maps, - allow_contracts, ) } @@ -204,7 +179,6 @@ fn resolve_name_in_module( import_path: &[Ident], starting_mod: LocalModuleId, def_maps: &BTreeMap, - allow_contracts: bool, ) -> NamespaceResolutionResult { let def_map = &def_maps[&krate]; let mut current_mod_id = ModuleId { krate, local_id: starting_mod }; @@ -267,10 +241,6 @@ fn resolve_name_in_module( return Err(PathResolutionError::Unresolved(current_segment.clone())); } - // Check if it is a contract and we're calling from a non-contract context - // if current_mod.is_contract && !allow_contracts { - // return Err(PathResolutionError::ExternalContractUsed(current_segment.clone())); - // } current_ns = found_ns; } @@ -288,7 +258,6 @@ fn resolve_external_dep( current_def_map: &CrateDefMap, directive: &ImportDirective, def_maps: &BTreeMap, - allow_contracts: bool, importing_crate: CrateId, ) -> NamespaceResolutionResult { // Use extern_prelude to get the dep @@ -316,7 +285,7 @@ fn resolve_external_dep( is_prelude: false, }; - resolve_path_to_ns(&dep_directive, dep_module.krate, importing_crate, def_maps, allow_contracts) + resolve_path_to_ns(&dep_directive, dep_module.krate, importing_crate, def_maps) } // Issue an error if the given private function is being called from a non-child module, or From 0227efefc55a26c60d3c664c62cf53ab7bc04290 Mon Sep 17 00:00:00 2001 From: thunkar Date: Mon, 24 Jun 2024 17:06:16 +0000 Subject: [PATCH 69/75] nargo fmt --- .../aztec-nr/aztec/src/test/helpers/test_environment.nr | 2 +- noir-projects/aztec-nr/aztec/src/test/helpers/utils.nr | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/noir-projects/aztec-nr/aztec/src/test/helpers/test_environment.nr b/noir-projects/aztec-nr/aztec/src/test/helpers/test_environment.nr index b3080218745..8008edf640a 100644 --- a/noir-projects/aztec-nr/aztec/src/test/helpers/test_environment.nr +++ b/noir-projects/aztec-nr/aztec/src/test/helpers/test_environment.nr @@ -23,7 +23,7 @@ struct TestEnvironment {} impl TestEnvironment { fn new() -> Self { cheatcodes::reset(); - Self { } + Self {} } fn block_number(self) -> u32 { diff --git a/noir-projects/aztec-nr/aztec/src/test/helpers/utils.nr b/noir-projects/aztec-nr/aztec/src/test/helpers/utils.nr index 3ae3215f3db..ee9c880d67d 100644 --- a/noir-projects/aztec-nr/aztec/src/test/helpers/utils.nr +++ b/noir-projects/aztec-nr/aztec/src/test/helpers/utils.nr @@ -14,7 +14,7 @@ use crate::oracle::notes::notify_nullified_note; pub fn apply_side_effects_private(contract_address: AztecAddress, public_inputs: PrivateCircuitPublicInputs) { let mut nullifiers = &[]; - for nullifier in public_inputs.new_nullifiers { + for nullifier in public_inputs.new_nullifiers { if nullifier.value != 0 { nullifiers = nullifiers.push_back(nullifier.value); } From 625a4928bec71d47f5abd9b91a5537df94634c3d Mon Sep 17 00:00:00 2001 From: thunkar Date: Mon, 24 Jun 2024 17:22:47 +0000 Subject: [PATCH 70/75] increased timeouts --- noir-projects/Dockerfile.test | 2 +- noir-projects/Earthfile | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/noir-projects/Dockerfile.test b/noir-projects/Dockerfile.test index 40edcbaaf35..1a682d9782f 100644 --- a/noir-projects/Dockerfile.test +++ b/noir-projects/Dockerfile.test @@ -28,7 +28,7 @@ RUN cd /usr/src/yarn-project/txe && yarn start & echo $! > /tmp/txe.pid && \ # Wait for TXE to initialize sleep 5 && \ cd ./noir-contracts && \ - ./bootstrap.sh && nargo test --silence-warnings --oracle-resolver http://localhost:8080 ; \ + ./bootstrap.sh && NARGO_FOREIGN_CALL_TIMEOUT=300000 nargo test --silence-warnings --oracle-resolver http://localhost:8080 ; \ kill $(cat /tmp/txe.pid) RUN cd /usr/src/yarn-project/txe && yarn start & echo $! > /tmp/txe.pid && \ diff --git a/noir-projects/Earthfile b/noir-projects/Earthfile index df8db2aa046..c9c073c3485 100644 --- a/noir-projects/Earthfile +++ b/noir-projects/Earthfile @@ -58,7 +58,8 @@ test: RUN cd /usr/src/yarn-project/txe && yarn start & echo $! > /tmp/txe.pid && \ # Wait for TXE to initialize sleep 5 && \ - cd /usr/src/noir-projects/noir-contracts && nargo test --silence-warnings --oracle-resolver http://localhost:8080 ; \ + cd /usr/src/noir-projects/noir-contracts && \ + NARGO_FOREIGN_CALL_TIMEOUT=300000 nargo test --silence-warnings --oracle-resolver http://localhost:8080 ; \ kill $(cat /tmp/txe.pid) format: From 151e31235053c73fd8ac86e95f3d5cdf84679f0a Mon Sep 17 00:00:00 2001 From: thunkar Date: Mon, 24 Jun 2024 18:15:05 +0000 Subject: [PATCH 71/75] yarn formatting --- yarn-project/txe/src/oracle/txe_oracle.ts | 8 ++++---- yarn-project/txe/src/txe_service/txe_service.ts | 2 +- .../src/util/txe_public_contract_data_source.ts | 14 +++++++------- yarn-project/txe/src/util/txe_public_state_db.ts | 8 ++++---- 4 files changed, 16 insertions(+), 16 deletions(-) diff --git a/yarn-project/txe/src/oracle/txe_oracle.ts b/yarn-project/txe/src/oracle/txe_oracle.ts index d7adcda65a5..44ad7a8af6d 100644 --- a/yarn-project/txe/src/oracle/txe_oracle.ts +++ b/yarn-project/txe/src/oracle/txe_oracle.ts @@ -202,14 +202,14 @@ export class TXE implements TypedOracle { const db = this.trees.asLatest(); const siloedNullifier = siloNullifier(this.contractAddress, nullifier); await db.batchInsert(MerkleTreeId.NULLIFIER_TREE, [siloedNullifier.toBuffer()], NULLIFIER_SUBTREE_HEIGHT); - return; + return Promise.resolve(); } async avmOpcodeEmitNoteHash(innerNoteHash: Fr) { const db = this.trees.asLatest(); const noteHash = siloNoteHash(this.contractAddress, innerNoteHash); await db.appendLeaves(MerkleTreeId.NOTE_HASH_TREE, [noteHash]); - return; + return Promise.resolve(); } deriveKeys(secret: Fr) { @@ -407,12 +407,12 @@ export class TXE implements TypedOracle { }, counter, ); - return; + return Promise.resolve(); } async notifyNullifiedNote(innerNullifier: Fr, innerNoteHash: Fr, _counter: number) { this.noteCache.nullifyNote(this.contractAddress, innerNullifier, innerNoteHash); - return; + return Promise.resolve(); } async checkNullifierExists(innerNullifier: Fr): Promise { diff --git a/yarn-project/txe/src/txe_service/txe_service.ts b/yarn-project/txe/src/txe_service/txe_service.ts index f1a83c751ac..796fc284bfc 100644 --- a/yarn-project/txe/src/txe_service/txe_service.ts +++ b/yarn-project/txe/src/txe_service/txe_service.ts @@ -223,7 +223,7 @@ export class TXEService { ) { const parsedAddress = fromSingle(address); const parsedSelector = FunctionSelector.fromField(fromSingle(functionSelector)); - let result = await (this.typedOracle as TXE).avmOpcodeCall( + const result = await (this.typedOracle as TXE).avmOpcodeCall( parsedAddress, parsedSelector, fromArray(args), diff --git a/yarn-project/txe/src/util/txe_public_contract_data_source.ts b/yarn-project/txe/src/util/txe_public_contract_data_source.ts index 47f1ca37dcb..17cd8e62683 100644 --- a/yarn-project/txe/src/util/txe_public_contract_data_source.ts +++ b/yarn-project/txe/src/util/txe_public_contract_data_source.ts @@ -1,14 +1,14 @@ -import { AztecAddress, Fr, FunctionSelector, unpackBytecode } from '@aztec/circuits.js'; -import { ContractArtifact } from '@aztec/foundation/abi'; +import { type AztecAddress, Fr, type FunctionSelector, unpackBytecode } from '@aztec/circuits.js'; +import { type ContractArtifact } from '@aztec/foundation/abi'; import { PrivateFunctionsTree } from '@aztec/pxe'; import { - ContractClassPublic, - ContractDataSource, - ContractInstanceWithAddress, - PublicFunction, + type ContractClassPublic, + type ContractDataSource, + type ContractInstanceWithAddress, + type PublicFunction, } from '@aztec/types/contracts'; -import { TXE } from '../oracle/txe_oracle.js'; +import { type TXE } from '../oracle/txe_oracle.js'; export class TXEPublicContractDataSource implements ContractDataSource { constructor(private txeOracle: TXE) {} diff --git a/yarn-project/txe/src/util/txe_public_state_db.ts b/yarn-project/txe/src/util/txe_public_state_db.ts index 55abac9c1f3..c877d558343 100644 --- a/yarn-project/txe/src/util/txe_public_state_db.ts +++ b/yarn-project/txe/src/util/txe_public_state_db.ts @@ -1,16 +1,16 @@ import { MerkleTreeId, PublicDataWrite } from '@aztec/circuit-types'; import { - AztecAddress, + type AztecAddress, Fr, PUBLIC_DATA_SUBTREE_HEIGHT, PublicDataTreeLeaf, - PublicDataTreeLeafPreimage, + type PublicDataTreeLeafPreimage, } from '@aztec/circuits.js'; import { computePublicDataTreeLeafSlot } from '@aztec/circuits.js/hash'; -import { PublicStateDB } from '@aztec/simulator'; +import { type PublicStateDB } from '@aztec/simulator'; import { MerkleTrees } from '@aztec/world-state'; -import { TXE } from '../oracle/txe_oracle.js'; +import { type TXE } from '../oracle/txe_oracle.js'; export class TXEPublicStateDB implements PublicStateDB { constructor(private txeOracle: TXE) {} From 19e323ff50398bf285be43c3bca4e849b285cbbf Mon Sep 17 00:00:00 2001 From: thunkar Date: Mon, 24 Jun 2024 19:54:45 +0000 Subject: [PATCH 72/75] formatting --- yarn-project/txe/src/oracle/txe_oracle.ts | 48 +++++++++---------- .../txe/src/txe_service/txe_service.ts | 7 ++- .../util/txe_public_contract_data_source.ts | 12 ++--- .../txe/src/util/txe_public_state_db.ts | 3 +- 4 files changed, 31 insertions(+), 39 deletions(-) diff --git a/yarn-project/txe/src/oracle/txe_oracle.ts b/yarn-project/txe/src/oracle/txe_oracle.ts index 44ad7a8af6d..03491c0bb67 100644 --- a/yarn-project/txe/src/oracle/txe_oracle.ts +++ b/yarn-project/txe/src/oracle/txe_oracle.ts @@ -10,13 +10,11 @@ import { TaggedLog, type UnencryptedL2Log, } from '@aztec/circuit-types'; -import { AvmSimulationStats, type CircuitWitnessGenerationStats } from '@aztec/circuit-types/stats'; +import { type CircuitWitnessGenerationStats } from '@aztec/circuit-types/stats'; import { CallContext, - type CompleteAddress, FunctionData, Gas, - GasSettings, GlobalVariables, Header, type KeyValidationRequest, @@ -97,12 +95,12 @@ export class TXE implements TypedOracle { // Utils - async getChainId() { - return this.chainId; + getChainId() { + return Promise.resolve(this.chainId); } - async getVersion() { - return this.version; + getVersion() { + return Promise.resolve(this.version); } getMsgSender() { @@ -217,7 +215,7 @@ export class TXE implements TypedOracle { } async addAuthWitness(address: AztecAddress, messageHash: Fr) { - const account = await this.txeDatabase.getAccount(address); + const account = this.txeDatabase.getAccount(address); const privateKey = await this.keyStore.getMasterSecretKey(account.publicKeys.masterIncomingViewingPublicKey); const schnorr = new Schnorr(); const signature = schnorr.constructSignature(messageHash.toBuffer(), privateKey).toBuffer(); @@ -240,28 +238,28 @@ export class TXE implements TypedOracle { // TypedOracle - async getBlockNumber() { - return this.blockNumber; + getBlockNumber() { + return Promise.resolve(this.blockNumber); } - async getContractAddress() { - return this.contractAddress; + getContractAddress() { + return Promise.resolve(this.contractAddress); } getRandomField() { return Fr.random(); } - async packArgumentsArray(args: Fr[]): Promise { - return this.packedValuesCache.pack(args); + packArgumentsArray(args: Fr[]) { + return Promise.resolve(this.packedValuesCache.pack(args)); } - async packReturns(returns: Fr[]): Promise { - return this.packedValuesCache.pack(returns); + packReturns(returns: Fr[]) { + return Promise.resolve(this.packedValuesCache.pack(returns)); } - async unpackReturns(returnsHash: Fr): Promise { - return this.packedValuesCache.unpack(returnsHash); + unpackReturns(returnsHash: Fr) { + return Promise.resolve(this.packedValuesCache.unpack(returnsHash)); } getKeyValidationRequest(pkMHash: Fr): Promise { @@ -340,11 +338,11 @@ export class TXE implements TypedOracle { throw new Error('Method not implemented.'); } - async getCompleteAddress(account: AztecAddress): Promise { - return this.txeDatabase.getAccount(account); + getCompleteAddress(account: AztecAddress) { + return Promise.resolve(this.txeDatabase.getAccount(account)); } - async getAuthWitness(messageHash: Fr): Promise { + getAuthWitness(messageHash: Fr) { return this.txeDatabase.getAuthWitness(messageHash); } @@ -352,7 +350,7 @@ export class TXE implements TypedOracle { throw new Error('Method not implemented.'); } - async getNotes( + getNotes( storageSlot: Fr, numSelects: number, selectByIndexes: number[], @@ -391,10 +389,10 @@ export class TXE implements TypedOracle { .join(', ')}`, ); - return notes; + return Promise.resolve(notes); } - async notifyCreatedNote(storageSlot: Fr, noteTypeId: Fr, noteItems: Fr[], innerNoteHash: Fr, counter: number) { + notifyCreatedNote(storageSlot: Fr, noteTypeId: Fr, noteItems: Fr[], innerNoteHash: Fr, counter: number) { const note = new Note(noteItems); this.noteCache.addNewNote( { @@ -410,7 +408,7 @@ export class TXE implements TypedOracle { return Promise.resolve(); } - async notifyNullifiedNote(innerNullifier: Fr, innerNoteHash: Fr, _counter: number) { + notifyNullifiedNote(innerNullifier: Fr, innerNoteHash: Fr, _counter: number) { this.noteCache.nullifyNote(this.contractAddress, innerNullifier, innerNoteHash); return Promise.resolve(); } diff --git a/yarn-project/txe/src/txe_service/txe_service.ts b/yarn-project/txe/src/txe_service/txe_service.ts index 796fc284bfc..0630e170407 100644 --- a/yarn-project/txe/src/txe_service/txe_service.ts +++ b/yarn-project/txe/src/txe_service/txe_service.ts @@ -9,11 +9,10 @@ import { PublicDataTreeLeaf, getContractInstanceFromDeployParams, } from '@aztec/circuits.js'; -import { computePublicDataTreeLeafSlot, siloNullifier } from '@aztec/circuits.js/hash'; +import { computePublicDataTreeLeafSlot } from '@aztec/circuits.js/hash'; import { AztecAddress } from '@aztec/foundation/aztec-address'; import { type Logger } from '@aztec/foundation/log'; import { KeyStore } from '@aztec/key-store'; -import { type AztecKVStore } from '@aztec/kv-store'; import { openTmpStore } from '@aztec/kv-store/utils'; import { ExecutionNoteCache, PackedValuesCache, type TypedOracle } from '@aztec/simulator'; import { MerkleTrees } from '@aztec/world-state'; @@ -210,8 +209,8 @@ export class TXEService { return toForeignCallResult([toSingle(new Fr(counter))]); } - addAuthWitness(address: ForeignCallSingle, messageHash: ForeignCallSingle) { - (this.typedOracle as TXE).addAuthWitness(fromSingle(address), fromSingle(messageHash)); + async addAuthWitness(address: ForeignCallSingle, messageHash: ForeignCallSingle) { + await (this.typedOracle as TXE).addAuthWitness(fromSingle(address), fromSingle(messageHash)); return toForeignCallResult([]); } diff --git a/yarn-project/txe/src/util/txe_public_contract_data_source.ts b/yarn-project/txe/src/util/txe_public_contract_data_source.ts index 17cd8e62683..64f410f9595 100644 --- a/yarn-project/txe/src/util/txe_public_contract_data_source.ts +++ b/yarn-project/txe/src/util/txe_public_contract_data_source.ts @@ -26,14 +26,10 @@ export class TXEPublicContractDataSource implements ContractDataSource { } async getContractClass(id: Fr): Promise { - let contractClass; - let privateFunctionsRoot; - try { - contractClass = await this.txeOracle.getContractDataOracle().getContractClass(id); - const artifact = await this.txeOracle.getContractDataOracle().getContractArtifact(id); - const tree = new PrivateFunctionsTree(artifact); - privateFunctionsRoot = await tree.getFunctionTreeRoot(); - } catch {} + const contractClass = await this.txeOracle.getContractDataOracle().getContractClass(id); + const artifact = await this.txeOracle.getContractDataOracle().getContractArtifact(id); + const tree = new PrivateFunctionsTree(artifact); + const privateFunctionsRoot = tree.getFunctionTreeRoot(); return { id, diff --git a/yarn-project/txe/src/util/txe_public_state_db.ts b/yarn-project/txe/src/util/txe_public_state_db.ts index c877d558343..62bdbaf7e5b 100644 --- a/yarn-project/txe/src/util/txe_public_state_db.ts +++ b/yarn-project/txe/src/util/txe_public_state_db.ts @@ -1,4 +1,4 @@ -import { MerkleTreeId, PublicDataWrite } from '@aztec/circuit-types'; +import { MerkleTreeId } from '@aztec/circuit-types'; import { type AztecAddress, Fr, @@ -8,7 +8,6 @@ import { } from '@aztec/circuits.js'; import { computePublicDataTreeLeafSlot } from '@aztec/circuits.js/hash'; import { type PublicStateDB } from '@aztec/simulator'; -import { MerkleTrees } from '@aztec/world-state'; import { type TXE } from '../oracle/txe_oracle.js'; From d5f28b4a9e68728ee1304df4e78c3bd9b8803e04 Mon Sep 17 00:00:00 2001 From: thunkar Date: Tue, 25 Jun 2024 10:28:10 +0000 Subject: [PATCH 73/75] comments from PR --- noir-projects/Dockerfile.test | 2 ++ noir-projects/Earthfile | 2 ++ noir-projects/aztec-nr/aztec/src/note/lifecycle.nr | 6 ++---- .../aztec-nr/aztec/src/test/helpers/cheatcodes.nr | 8 ++++---- .../aztec/src/test/helpers/test_environment.nr | 11 ++++------- .../aztec-nr/aztec/src/test/helpers/utils.nr | 6 +++--- .../contracts/parent_contract/src/main.nr | 2 +- .../token_contract/src/test/access_control.nr | 7 +++++-- yarn-project/txe/src/oracle/txe_oracle.ts | 2 +- yarn-project/txe/src/txe_service/txe_service.ts | 4 ++-- 10 files changed, 26 insertions(+), 24 deletions(-) diff --git a/noir-projects/Dockerfile.test b/noir-projects/Dockerfile.test index 1a682d9782f..91adc723c6a 100644 --- a/noir-projects/Dockerfile.test +++ b/noir-projects/Dockerfile.test @@ -28,6 +28,8 @@ RUN cd /usr/src/yarn-project/txe && yarn start & echo $! > /tmp/txe.pid && \ # Wait for TXE to initialize sleep 5 && \ cd ./noir-contracts && \ + # We need to increase the timeout since all tests running in parallel hammer TXE at the same time, and processing slows down leading to timeouts + # The only way we currently have to batch tests is via RAYON_NUM_THREADS, which is not ideal ./bootstrap.sh && NARGO_FOREIGN_CALL_TIMEOUT=300000 nargo test --silence-warnings --oracle-resolver http://localhost:8080 ; \ kill $(cat /tmp/txe.pid) diff --git a/noir-projects/Earthfile b/noir-projects/Earthfile index c9c073c3485..a828544fea2 100644 --- a/noir-projects/Earthfile +++ b/noir-projects/Earthfile @@ -59,6 +59,8 @@ test: # Wait for TXE to initialize sleep 5 && \ cd /usr/src/noir-projects/noir-contracts && \ + # We need to increase the timeout since all tests running in parallel hammer TXE at the same time and processing slows down, leading to timeouts + # The only way we currently have to batch tests is via RAYON_NUM_THREADS, which is not ideal NARGO_FOREIGN_CALL_TIMEOUT=300000 nargo test --silence-warnings --oracle-resolver http://localhost:8080 ; \ kill $(cat /tmp/txe.pid) diff --git a/noir-projects/aztec-nr/aztec/src/note/lifecycle.nr b/noir-projects/aztec-nr/aztec/src/note/lifecycle.nr index 4a7a3a95e94..7fe6021326a 100644 --- a/noir-projects/aztec-nr/aztec/src/note/lifecycle.nr +++ b/noir-projects/aztec-nr/aztec/src/note/lifecycle.nr @@ -15,12 +15,10 @@ pub fn create_note( let note_hash_counter = context.side_effect_counter; let header = NoteHeader { contract_address, storage_slot, nonce: 0, note_hash_counter }; - // TODO: change this to note.set_header(header) once https://github.com/noir-lang/noir/issues/4095 is fixed - Note::set_header(note, header); + note.set_header(header); let inner_note_hash = compute_inner_note_hash(*note); - // TODO: Strong typing required because of https://github.com/noir-lang/noir/issues/4088 - let serialized_note: [Field; N] = Note::serialize_content(*note); + let serialized_note = Note::serialize_content(*note); assert( notify_created_note( storage_slot, diff --git a/noir-projects/aztec-nr/aztec/src/test/helpers/cheatcodes.nr b/noir-projects/aztec-nr/aztec/src/test/helpers/cheatcodes.nr index 3eae7bcf256..db5e13ed424 100644 --- a/noir-projects/aztec-nr/aztec/src/test/helpers/cheatcodes.nr +++ b/noir-projects/aztec-nr/aztec/src/test/helpers/cheatcodes.nr @@ -22,8 +22,8 @@ unconstrained pub fn get_block_number() -> u32 { oracle_get_block_number() } -unconstrained pub fn advance_blocks(blocks: u32) { - oracle_time_travel(blocks); +unconstrained pub fn advance_blocks_by(blocks: u32) { + oracle_advance_blocks_by(blocks); } unconstrained pub fn get_private_context_inputs(historical_block_number: u32) -> PrivateContextInputs { @@ -113,8 +113,8 @@ fn oracle_set_contract_address(address: AztecAddress) {} #[oracle(getBlockNumber)] fn oracle_get_block_number() -> u32 {} -#[oracle(timeTravel)] -fn oracle_time_travel(blocks: u32) {} +#[oracle(advanceBlocksBy)] +fn oracle_advance_blocks_by(blocks: u32) {} #[oracle(getPrivateContextInputs)] fn oracle_get_private_context_inputs(historical_block_number: u32) -> PrivateContextInputs {} diff --git a/noir-projects/aztec-nr/aztec/src/test/helpers/test_environment.nr b/noir-projects/aztec-nr/aztec/src/test/helpers/test_environment.nr index 8008edf640a..9b66e64264b 100644 --- a/noir-projects/aztec-nr/aztec/src/test/helpers/test_environment.nr +++ b/noir-projects/aztec-nr/aztec/src/test/helpers/test_environment.nr @@ -36,7 +36,7 @@ impl TestEnvironment { } fn advance_block_by(&mut self, blocks: u32) { - cheatcodes::advance_blocks(blocks); + cheatcodes::advance_blocks_by(blocks); } fn public(self) -> PublicContext { @@ -79,7 +79,7 @@ impl TestEnvironment { args.as_slice(), public_keys.hash().to_field() ); - cheatcodes::advance_blocks(1); + cheatcodes::advance_blocks_by(1); let test_account = cheatcodes::add_account( secret, PartialAddress::compute( @@ -204,12 +204,9 @@ impl TestEnvironment { let note_hash_counter = cheatcodes::get_side_effects_counter(); let header = NoteHeader { contract_address, storage_slot, nonce: 0, note_hash_counter }; - // TODO: change this to note.set_header(header) once https://github.com/noir-lang/noir/issues/4095 is fixed - Note::set_header(note, header); + note.set_header(header); let inner_note_hash = compute_inner_note_hash(*note); - - // TODO: Strong typing required because of https://github.com/noir-lang/noir/issues/4088 - let serialized_note: [Field; N] = Note::serialize_content(*note); + let serialized_note = Note::serialize_content(*note); assert( notify_created_note( storage_slot, diff --git a/noir-projects/aztec-nr/aztec/src/test/helpers/utils.nr b/noir-projects/aztec-nr/aztec/src/test/helpers/utils.nr index ee9c880d67d..808b5ad37f5 100644 --- a/noir-projects/aztec-nr/aztec/src/test/helpers/utils.nr +++ b/noir-projects/aztec-nr/aztec/src/test/helpers/utils.nr @@ -46,7 +46,7 @@ impl Deployer { self.public_keys_hash ); let address = instance.to_address(); - cheatcodes::advance_blocks(1); + cheatcodes::advance_blocks_by(1); let block_number = cheatcodes::get_block_number(); let original_fn = call_interface.get_original(); let original_msg_sender = cheatcodes::get_msg_sender(); @@ -58,7 +58,7 @@ impl Deployer { inputs.call_context.function_selector = call_interface.get_selector(); let public_inputs = original_fn(inputs); apply_side_effects_private(address, public_inputs); - cheatcodes::advance_blocks(1); + cheatcodes::advance_blocks_by(1); cheatcodes::set_contract_address(original_contract_address); cheatcodes::set_msg_sender(original_msg_sender); instance @@ -74,7 +74,7 @@ impl Deployer { call_interface.get_args(), self.public_keys_hash ); - cheatcodes::advance_blocks(1); + cheatcodes::advance_blocks_by(1); let original_fn = call_interface.get_original(); let original_msg_sender = cheatcodes::get_msg_sender(); let original_contract_address = cheatcodes::get_contract_address(); diff --git a/noir-projects/noir-contracts/contracts/parent_contract/src/main.nr b/noir-projects/noir-contracts/contracts/parent_contract/src/main.nr index 5bc293b655a..efeae7bcda8 100644 --- a/noir-projects/noir-contracts/contracts/parent_contract/src/main.nr +++ b/noir-projects/noir-contracts/contracts/parent_contract/src/main.nr @@ -259,7 +259,7 @@ contract Parent { // Deploy child contract let child_contract = env.deploy("@aztec/noir-contracts.js/Child").without_initializer(); let child_contract_address = child_contract.to_address(); - cheatcodes::advance_blocks(1); + cheatcodes::advance_blocks_by(1); // Set value in child through parent let value_to_set = 7; diff --git a/noir-projects/noir-contracts/contracts/token_contract/src/test/access_control.nr b/noir-projects/noir-contracts/contracts/token_contract/src/test/access_control.nr index 07fe052c9d6..37a84e09a7b 100644 --- a/noir-projects/noir-contracts/contracts/token_contract/src/test/access_control.nr +++ b/noir-projects/noir-contracts/contracts/token_contract/src/test/access_control.nr @@ -19,12 +19,15 @@ unconstrained fn access_control() { // Impersonate new admin cheatcodes::set_contract_address(recipient); - // Add minter as admin + // Check new admin is not a minter + let is_minter_call_interface = Token::at(token_contract_address).is_minter(recipient); + let is_minter = env.call_public(is_minter_call_interface); + assert(is_minter == false); + // Set admin as minter let set_minter_call_interface = Token::at(token_contract_address).set_minter(recipient, true); env.call_public(set_minter_call_interface); // Check it worked - let is_minter_call_interface = Token::at(token_contract_address).is_minter(recipient); let is_minter = env.call_public(is_minter_call_interface); assert(is_minter == true); diff --git a/yarn-project/txe/src/oracle/txe_oracle.ts b/yarn-project/txe/src/oracle/txe_oracle.ts index 03491c0bb67..bdf5273f232 100644 --- a/yarn-project/txe/src/oracle/txe_oracle.ts +++ b/yarn-project/txe/src/oracle/txe_oracle.ts @@ -515,7 +515,7 @@ export class TXE implements TypedOracle { `Executing external function ${targetContractAddress}:${functionSelector}(${await this.getDebugFunctionName( targetContractAddress, functionSelector, - )})`, + )}) isStaticCall=${isStaticCall} isDelegateCall=${isDelegateCall}`, ); // Store and modify env diff --git a/yarn-project/txe/src/txe_service/txe_service.ts b/yarn-project/txe/src/txe_service/txe_service.ts index 0630e170407..800a24f6825 100644 --- a/yarn-project/txe/src/txe_service/txe_service.ts +++ b/yarn-project/txe/src/txe_service/txe_service.ts @@ -43,7 +43,7 @@ export class TXEService { logger.info(`TXE service initialized`); const txe = new TXE(logger, trees, packedValuesCache, noteCache, keyStore, txeDatabase); const service = new TXEService(logger, txe); - await service.timeTravel(toSingle(new Fr(1n))); + await service.advanceBlocksBy(toSingle(new Fr(1n))); return service; } @@ -59,7 +59,7 @@ export class TXEService { return toForeignCallResult(inputs.toFields().map(toSingle)); } - async timeTravel(blocks: ForeignCallSingle) { + async advanceBlocksBy(blocks: ForeignCallSingle) { const nBlocks = fromSingle(blocks).toNumber(); this.logger.debug(`time traveling ${nBlocks} blocks`); const trees = (this.typedOracle as TXE).getTrees(); From 9c55d3365d2e9d48ddfc8bc9c322f2e2080b9565 Mon Sep 17 00:00:00 2001 From: thunkar Date: Tue, 25 Jun 2024 10:30:29 +0000 Subject: [PATCH 74/75] more comments --- .../contracts/token_contract/src/test/unshielding.nr | 2 ++ 1 file changed, 2 insertions(+) diff --git a/noir-projects/noir-contracts/contracts/token_contract/src/test/unshielding.nr b/noir-projects/noir-contracts/contracts/token_contract/src/test/unshielding.nr index c5ba8f304a6..52987cb1736 100644 --- a/noir-projects/noir-contracts/contracts/token_contract/src/test/unshielding.nr +++ b/noir-projects/noir-contracts/contracts/token_contract/src/test/unshielding.nr @@ -11,6 +11,7 @@ unconstrained fn unshield_on_behalf_of_self() { let unshield_amount = mint_amount / 10; let unshield_call_interface = Token::at(token_contract_address).unshield(owner, owner, unshield_amount, 0); env.call_private_void(unshield_call_interface); + utils::check_private_balance(token_contract_address, owner, mint_amount - unshield_amount); utils::check_public_balance(token_contract_address, owner, mint_amount + unshield_amount); } @@ -25,6 +26,7 @@ unconstrained fn unshield_on_behalf_of_other() { cheatcodes::set_contract_address(recipient); // Unshield tokens env.call_private_void(unshield_call_interface); + utils::check_private_balance(token_contract_address, owner, mint_amount - unshield_amount); utils::check_public_balance(token_contract_address, recipient, unshield_amount); } From 80954103c513af70a466da6fe3b2fed88ec7026b Mon Sep 17 00:00:00 2001 From: thunkar Date: Tue, 25 Jun 2024 10:43:30 +0000 Subject: [PATCH 75/75] removed unnecessary tree accesses --- yarn-project/txe/src/txe_service/txe_service.ts | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/yarn-project/txe/src/txe_service/txe_service.ts b/yarn-project/txe/src/txe_service/txe_service.ts index 800a24f6825..aef0d8e3d49 100644 --- a/yarn-project/txe/src/txe_service/txe_service.ts +++ b/yarn-project/txe/src/txe_service/txe_service.ts @@ -68,18 +68,6 @@ export class TXEService { header.state = await trees.getStateReference(true); const blockNumber = await this.typedOracle.getBlockNumber(); header.globalVariables.blockNumber = new Fr(blockNumber); - header.state.partial.nullifierTree.root = Fr.fromBuffer( - (await trees.getTreeInfo(MerkleTreeId.NULLIFIER_TREE, true)).root, - ); - header.state.partial.noteHashTree.root = Fr.fromBuffer( - (await trees.getTreeInfo(MerkleTreeId.NOTE_HASH_TREE, true)).root, - ); - header.state.partial.publicDataTree.root = Fr.fromBuffer( - (await trees.getTreeInfo(MerkleTreeId.PUBLIC_DATA_TREE, true)).root, - ); - header.state.l1ToL2MessageTree.root = Fr.fromBuffer( - (await trees.getTreeInfo(MerkleTreeId.L1_TO_L2_MESSAGE_TREE, true)).root, - ); l2Block.archive.root = Fr.fromBuffer((await trees.getTreeInfo(MerkleTreeId.ARCHIVE, true)).root); l2Block.header = header; for (let i = 0; i < nBlocks; i++) {