From 5cce848fc776abe4fcf54fb39e1b1ed740fd3583 Mon Sep 17 00:00:00 2001 From: Maddiaa <47148561+Maddiaa0@users.noreply.github.com> Date: Wed, 9 Aug 2023 16:52:48 +0100 Subject: [PATCH] feat(oracle): add oracle to get portal contract address (#1474) **Overview** closes: #1464 Adds an oracle that returns the portal address for a given contract address. A unit test is added, and the uniswap portal test is altered to use this oracle rather than injecting the portals as function inputs --- yarn-project/acir-simulator/src/acvm/acvm.ts | 1 + .../src/client/private_execution.test.ts | 22 ++++++++++++- .../src/client/private_execution.ts | 5 +++ .../src/client/unconstrained_execution.ts | 5 +++ .../acir-simulator/src/public/executor.ts | 6 ++++ .../archiver/src/archiver/archiver_store.ts | 32 +++++++++++-------- .../examples/uniswap_trade_on_l1_from_l2.ts | 5 --- .../src/uniswap_trade_on_l1_from_l2.test.ts | 3 -- .../src/contracts/test_contract/src/main.nr | 14 +++++++- .../contracts/uniswap_contract/src/main.nr | 16 ++++++---- .../noir-libs/noir-aztec/src/context.nr | 1 + .../noir-libs/noir-aztec/src/oracle.nr | 1 + .../noir-aztec/src/oracle/context.nr | 7 ++++ 13 files changed, 87 insertions(+), 31 deletions(-) create mode 100644 yarn-project/noir-libs/noir-aztec/src/oracle/context.nr diff --git a/yarn-project/acir-simulator/src/acvm/acvm.ts b/yarn-project/acir-simulator/src/acvm/acvm.ts index ab20ecf93d5..d6d9ed81e8a 100644 --- a/yarn-project/acir-simulator/src/acvm/acvm.ts +++ b/yarn-project/acir-simulator/src/acvm/acvm.ts @@ -38,6 +38,7 @@ type ORACLE_NAMES = | 'createNullifier' | 'getCommitment' | 'getL1ToL2Message' + | 'getPortalContractAddress' | 'emitEncryptedLog' | 'emitUnencryptedLog' | 'getPublicKey' diff --git a/yarn-project/acir-simulator/src/client/private_execution.test.ts b/yarn-project/acir-simulator/src/client/private_execution.test.ts index 2da802b912f..09487891814 100644 --- a/yarn-project/acir-simulator/src/client/private_execution.test.ts +++ b/yarn-project/acir-simulator/src/client/private_execution.test.ts @@ -85,11 +85,13 @@ describe('Private Execution test suite', () => { args = [], origin = AztecAddress.random(), contractAddress = defaultContractAddress, + portalContractAddress = EthAddress.ZERO, txContext = {}, }: { abi: FunctionAbi; origin?: AztecAddress; contractAddress?: AztecAddress; + portalContractAddress?: EthAddress; args?: any[]; txContext?: Partial>; }) => { @@ -107,7 +109,7 @@ describe('Private Execution test suite', () => { txRequest, abi, functionData.isConstructor ? AztecAddress.ZERO : contractAddress, - EthAddress.ZERO, + portalContractAddress, blockData, ); }; @@ -980,4 +982,22 @@ describe('Private Execution test suite', () => { expect(result.returnValues).toEqual([pubKey.x.value, pubKey.y.value]); }); }); + + describe('Context oracles', () => { + it("Should be able to get and return the contract's portal contract address", async () => { + const portalContractAddress = EthAddress.random(); + const aztecAddressToQuery = AztecAddress.random(); + + // Tweak the contract ABI so we can extract return values + const abi = TestContractAbi.functions.find(f => f.name === 'getPortalContractAddress')!; + abi.returnTypes = [{ kind: 'field' }]; + + const args = [aztecAddressToQuery.toField()]; + + // Overwrite the oracle return value + oracle.getPortalContractAddress.mockResolvedValue(portalContractAddress); + const result = await runSimulator({ origin: AztecAddress.random(), abi, args }); + expect(result.returnValues).toEqual([portalContractAddress.toField().value]); + }); + }); }); diff --git a/yarn-project/acir-simulator/src/client/private_execution.ts b/yarn-project/acir-simulator/src/client/private_execution.ts index 73a60982c61..12b8250409a 100644 --- a/yarn-project/acir-simulator/src/client/private_execution.ts +++ b/yarn-project/acir-simulator/src/client/private_execution.ts @@ -175,6 +175,11 @@ export class PrivateFunctionExecution { return Promise.resolve(ZERO_ACVM_FIELD); }, + getPortalContractAddress: async ([aztecAddress]) => { + const contractAddress = AztecAddress.fromString(aztecAddress); + const portalContactAddress = await this.context.db.getPortalContractAddress(contractAddress); + return Promise.resolve(toACVMField(portalContactAddress)); + }, }); const publicInputs = extractPublicInputs(partialWitness, acir); diff --git a/yarn-project/acir-simulator/src/client/unconstrained_execution.ts b/yarn-project/acir-simulator/src/client/unconstrained_execution.ts index bf659acc31f..dda38312c8b 100644 --- a/yarn-project/acir-simulator/src/client/unconstrained_execution.ts +++ b/yarn-project/acir-simulator/src/client/unconstrained_execution.ts @@ -83,6 +83,11 @@ export class UnconstrainedFunctionExecution { } return values.map(v => toACVMField(v)); }, + getPortalContractAddress: async ([aztecAddress]) => { + const contractAddress = AztecAddress.fromString(aztecAddress); + const portalContactAddress = await this.context.db.getPortalContractAddress(contractAddress); + return Promise.resolve(toACVMField(portalContactAddress)); + }, }); const returnValues: ACVMField[] = extractReturnWitness(acir, partialWitness); diff --git a/yarn-project/acir-simulator/src/public/executor.ts b/yarn-project/acir-simulator/src/public/executor.ts index 94c8018686d..5fa5ec06dcc 100644 --- a/yarn-project/acir-simulator/src/public/executor.ts +++ b/yarn-project/acir-simulator/src/public/executor.ts @@ -152,6 +152,12 @@ export class PublicExecutor { this.log(`Emitted unencrypted log: "${log.toString('ascii')}"`); return Promise.resolve(ZERO_ACVM_FIELD); }, + getPortalContractAddress: async ([aztecAddress]) => { + const contractAddress = AztecAddress.fromString(aztecAddress); + const portalContactAddress = + (await this.contractsDb.getPortalContractAddress(contractAddress)) ?? EthAddress.ZERO; + return Promise.resolve(toACVMField(portalContactAddress)); + }, }); const returnValues = extractReturnWitness(acir, partialWitness).map(fromACVMField); diff --git a/yarn-project/archiver/src/archiver/archiver_store.ts b/yarn-project/archiver/src/archiver/archiver_store.ts index 80fbfd82c66..64fab096c37 100644 --- a/yarn-project/archiver/src/archiver/archiver_store.ts +++ b/yarn-project/archiver/src/archiver/archiver_store.ts @@ -160,7 +160,12 @@ export class MemoryArchiverStore implements ArchiverDataStore { /** * A sparse array containing all the contract data that have been fetched so far. */ - private contractPublicData: (ContractPublicData[] | undefined)[] = []; + private contractPublicDataByBlock: (ContractPublicData[] | undefined)[] = []; + + /** + * A mapping of contract address to contract data. + */ + private contractPublicData: Map = new Map(); /** * Contains all the confirmed L1 to L2 messages (i.e. messages that were consumed in an L2 block) @@ -241,10 +246,17 @@ export class MemoryArchiverStore implements ArchiverDataStore { * @returns True if the operation is successful (always in this implementation). */ public addL2ContractPublicData(data: ContractPublicData[], blockNum: number): Promise { - if (this.contractPublicData[blockNum]?.length) { - this.contractPublicData[blockNum]?.push(...data); + // Add to the contracts mapping + for (const contractData of data) { + const key = contractData.contractData.contractAddress.toString(); + this.contractPublicData.set(key, contractData); + } + + // Add the index per block + if (this.contractPublicDataByBlock[blockNum]?.length) { + this.contractPublicDataByBlock[blockNum]?.push(...data); } else { - this.contractPublicData[blockNum] = [...data]; + this.contractPublicDataByBlock[blockNum] = [...data]; } return Promise.resolve(true); } @@ -316,15 +328,7 @@ export class MemoryArchiverStore implements ArchiverDataStore { * @returns The contract's public data. */ public getL2ContractPublicData(contractAddress: AztecAddress): Promise { - let result; - for (let i = INITIAL_L2_BLOCK_NUM; i < this.contractPublicData.length; i++) { - const contracts = this.contractPublicData[i]; - const contract = contracts?.find(c => c.contractData.contractAddress.equals(contractAddress)); - if (contract) { - result = contract; - break; - } - } + const result = this.contractPublicData.get(contractAddress.toString()); return Promise.resolve(result); } @@ -337,7 +341,7 @@ export class MemoryArchiverStore implements ArchiverDataStore { if (blockNum > this.l2Blocks.length) { return Promise.resolve([]); } - return Promise.resolve(this.contractPublicData[blockNum] || []); + return Promise.resolve(this.contractPublicDataByBlock[blockNum] || []); } /** diff --git a/yarn-project/aztec-sandbox/src/examples/uniswap_trade_on_l1_from_l2.ts b/yarn-project/aztec-sandbox/src/examples/uniswap_trade_on_l1_from_l2.ts index bfe14d21cdc..09bd46d7c29 100644 --- a/yarn-project/aztec-sandbox/src/examples/uniswap_trade_on_l1_from_l2.ts +++ b/yarn-project/aztec-sandbox/src/examples/uniswap_trade_on_l1_from_l2.ts @@ -129,7 +129,6 @@ async function deployAllContracts(owner: AztecAddress) { wethTokenPortalAddress, uniswapL2Contract, uniswapPortal, - uniswapPortalAddress, }; } @@ -185,7 +184,6 @@ async function main() { wethTokenPortalAddress, uniswapL2Contract, uniswapPortal, - uniswapPortalAddress, } = result; // Give me some WETH so I can deposit to L2 and do the swap... @@ -246,18 +244,15 @@ async function main() { .swap( selector, wethL2Contract.address.toField(), - wethTokenPortalAddress.toField(), wethAmountToBridge, new Fr(3000), daiL2Contract.address.toField(), - daiTokenPortalAddress.toField(), new Fr(minimumOutputAmount), owner, owner, secretHash, new Fr(2 ** 32 - 1), ethAccount.toField(), - uniswapPortalAddress, ethAccount.toField(), ) .send({ origin: owner }); diff --git a/yarn-project/end-to-end/src/uniswap_trade_on_l1_from_l2.test.ts b/yarn-project/end-to-end/src/uniswap_trade_on_l1_from_l2.test.ts index e3a750fc6b8..9a63333f08a 100644 --- a/yarn-project/end-to-end/src/uniswap_trade_on_l1_from_l2.test.ts +++ b/yarn-project/end-to-end/src/uniswap_trade_on_l1_from_l2.test.ts @@ -196,18 +196,15 @@ describe('uniswap_trade_on_l1_from_l2', () => { .swap( selector, wethCrossChainHarness.l2Contract.address.toField(), - wethCrossChainHarness.tokenPortalAddress.toField(), wethAmountToBridge, new Fr(3000), daiCrossChainHarness.l2Contract.address.toField(), - daiCrossChainHarness.tokenPortalAddress.toField(), new Fr(minimumOutputAmount), ownerAddress, ownerAddress, secretHash, new Fr(2 ** 32 - 1), ethAccount.toField(), - uniswapPortalAddress, ethAccount.toField(), ) .send({ origin: ownerAddress }); diff --git a/yarn-project/noir-contracts/src/contracts/test_contract/src/main.nr b/yarn-project/noir-contracts/src/contracts/test_contract/src/main.nr index 4580fc44c29..e2b66107ffe 100644 --- a/yarn-project/noir-contracts/src/contracts/test_contract/src/main.nr +++ b/yarn-project/noir-contracts/src/contracts/test_contract/src/main.nr @@ -12,7 +12,8 @@ contract Test { use dep::aztec::oracle::{ create_l2_to_l1_message::create_l2_to_l1_message, create_nullifier::create_nullifier, - get_public_key::get_public_key + get_public_key::get_public_key, + context::get_portal_address }; fn constructor( @@ -32,6 +33,17 @@ contract Test { context.finish() } + // Get the portal contract address through an oracle call + fn getPortalContractAddress( + inputs: PrivateContextInputs, + aztec_address: Field + ) -> distinct pub abi::PrivateCircuitPublicInputs { + let mut context = Context::new(inputs, abi::hash_args([])); + let portal_address = get_portal_address(aztec_address); + context.return_values.push_array([portal_address]); + context.finish() + } + // Purely exists for testing open fn createL2ToL1MessagePublic( _inputs: PublicContextInputs, diff --git a/yarn-project/noir-contracts/src/contracts/uniswap_contract/src/main.nr b/yarn-project/noir-contracts/src/contracts/uniswap_contract/src/main.nr index bdb6b086a9d..f8c213173cd 100644 --- a/yarn-project/noir-contracts/src/contracts/uniswap_contract/src/main.nr +++ b/yarn-project/noir-contracts/src/contracts/uniswap_contract/src/main.nr @@ -4,7 +4,10 @@ contract Uniswap { use dep::aztec::abi::PrivateContextInputs; use dep::aztec::abi::PublicContextInputs; use dep::aztec::context::Context; - use dep::aztec::oracle::public_call; + use dep::aztec::oracle::{ + public_call, + context::get_portal_address + }; use dep::aztec::private_call_stack_item::PrivateCallStackItem; use dep::aztec::public_call_stack_item::PublicCallStackItem; use dep::aztec::types::point::Point; @@ -28,38 +31,37 @@ contract Uniswap { inputs: PrivateContextInputs, withdrawFnSelector: Field, // withdraw method on inputAsset (l2 contract) that would withdraw to L1 inputAsset: Field, - inputAssetPortalAddress: Field, // l1 portal of input asset inputAmount: Field, uniswapFeeTier: Field, // which uniswap tier to use (eg 3000 for 0.3% fee) outputAsset: Field, - outputAssetPortalAddress: Field, // l1 portal of output asset minimumOutputAmount: Field, // minimum output amount to receive (slippage protection for the swap) sender: Field, recipient: Field, // receiver address of output asset after the swap secretHash: Field, // for when l1 uniswap portal inserts the message to consume output assets on L2 deadlineForL1ToL2Message: Field, // for when l1 uniswap portal inserts the message to consume output assets on L2 cancellerForL1ToL2Message: Field, // L1 address of who can cancel the message to consume assets on L2. - l1UniswapPortal: Field, // L1 address of uniswap portal contract callerOnL1: Field, // ethereum address that can call this function on the L1 portal (0x0 if anyone can call) ) -> distinct pub abi::PrivateCircuitPublicInputs { let mut context = Context::new(inputs, abi::hash_args([ withdrawFnSelector, inputAsset, - inputAssetPortalAddress, inputAmount, uniswapFeeTier, outputAsset, - outputAssetPortalAddress, minimumOutputAmount, sender, recipient, secretHash, deadlineForL1ToL2Message, cancellerForL1ToL2Message, - l1UniswapPortal, callerOnL1, ])); + // Get portal addresses + let l1UniswapPortal = context.this_portal_address(); + let inputAssetPortalAddress = get_portal_address(inputAsset); + let outputAssetPortalAddress = get_portal_address(outputAsset); + // inputAsset.withdraw(inputAmount, sender, recipient=l1UniswapPortal, callerOnL1=l1UniswapPortal) // only uniswap portal can call this (done to safeguard ordering of message consumption) // ref: https://docs.aztec.network/aztec/how-it-works/l1-l2-messaging#designated-caller diff --git a/yarn-project/noir-libs/noir-aztec/src/context.nr b/yarn-project/noir-libs/noir-aztec/src/context.nr index 4681564d2df..36811cd88f0 100644 --- a/yarn-project/noir-libs/noir-aztec/src/context.nr +++ b/yarn-project/noir-libs/noir-aztec/src/context.nr @@ -47,6 +47,7 @@ use crate::oracle::{ call_private_function::call_private_function_internal, public_call::call_public_function_internal, enqueue_public_function_call::enqueue_public_function_call_internal, + context::get_portal_address, }; diff --git a/yarn-project/noir-libs/noir-aztec/src/oracle.nr b/yarn-project/noir-libs/noir-aztec/src/oracle.nr index b99008a6c1f..13834c14dc6 100644 --- a/yarn-project/noir-libs/noir-aztec/src/oracle.nr +++ b/yarn-project/noir-libs/noir-aztec/src/oracle.nr @@ -1,5 +1,6 @@ mod arguments; mod call_private_function; +mod context; mod create_commitment; mod create_l2_to_l1_message; mod create_nullifier; diff --git a/yarn-project/noir-libs/noir-aztec/src/oracle/context.nr b/yarn-project/noir-libs/noir-aztec/src/oracle/context.nr new file mode 100644 index 00000000000..1b70c476d9a --- /dev/null +++ b/yarn-project/noir-libs/noir-aztec/src/oracle/context.nr @@ -0,0 +1,7 @@ +#[oracle(getPortalContractAddress)] +fn _get_portal_address(_contract_address: Field) -> Field {} + +unconstrained fn get_portal_address(contract_address: Field) -> Field { + let portal_address = _get_portal_address(contract_address); + portal_address +} \ No newline at end of file