Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(oracle): add oracle to get portal contract address #1474

Merged
merged 10 commits into from
Aug 9, 2023
1 change: 1 addition & 0 deletions yarn-project/acir-simulator/src/acvm/acvm.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ type ORACLE_NAMES =
| 'createNullifier'
| 'getCommitment'
| 'getL1ToL2Message'
| 'getPortalContractAddress'
| 'emitEncryptedLog'
| 'emitUnencryptedLog'
| 'getPublicKey'
Expand Down
22 changes: 21 additions & 1 deletion yarn-project/acir-simulator/src/client/private_execution.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<FieldsOf<TxContext>>;
}) => {
Expand All @@ -107,7 +109,7 @@ describe('Private Execution test suite', () => {
txRequest,
abi,
functionData.isConstructor ? AztecAddress.ZERO : contractAddress,
EthAddress.ZERO,
portalContractAddress,
blockData,
);
};
Expand Down Expand Up @@ -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]);
});
});
});
5 changes: 5 additions & 0 deletions yarn-project/acir-simulator/src/client/private_execution.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
6 changes: 6 additions & 0 deletions yarn-project/acir-simulator/src/public/executor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
let portalContactAddress = await this.contractsDb.getPortalContractAddress(contractAddress);
if (!portalContactAddress) portalContactAddress = EthAddress.ZERO;
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thoughts on returning zero here, or throwing. I feel like it should throw

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i agree -> although can you remind me how the error would bubble up to the user? (what would be their experience)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What practically happens if the return value from this.contractsDb.getPortalContractAddress is zero now? Because I would assume that it is already zero for contracts that don't have portals.

The main thing keeping me from saying that we should throw, would be that you would have trouble if you want to check if something have a portal and it don't have that 🤔

Copy link
Member Author

@Maddiaa0 Maddiaa0 Aug 9, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

good point, the user should probably have an explicit zero check,

Copy link
Member Author

@Maddiaa0 Maddiaa0 Aug 9, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(foot)

image

return Promise.resolve(toACVMField(portalContactAddress));
},
});

const returnValues = extractReturnWitness(acir, partialWitness).map(fromACVMField);
Expand Down
32 changes: 18 additions & 14 deletions yarn-project/archiver/src/archiver/archiver_store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<string, ContractPublicData> = new Map();

/**
* Contains all the confirmed L1 to L2 messages (i.e. messages that were consumed in an L2 block)
Expand Down Expand Up @@ -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<boolean> {
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);
}
Expand Down Expand Up @@ -316,15 +328,7 @@ export class MemoryArchiverStore implements ArchiverDataStore {
* @returns The contract's public data.
*/
public getL2ContractPublicData(contractAddress: AztecAddress): Promise<ContractPublicData | undefined> {
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);
}

Expand All @@ -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] || []);
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,6 @@ async function deployAllContracts(owner: AztecAddress) {
wethTokenPortalAddress,
uniswapL2Contract,
uniswapPortal,
uniswapPortalAddress,
};
}

Expand Down Expand Up @@ -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...
Expand Down Expand Up @@ -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 });
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 });
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand All @@ -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,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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
Expand Down
1 change: 1 addition & 0 deletions yarn-project/noir-libs/noir-aztec/src/context.nr
Original file line number Diff line number Diff line change
Expand Up @@ -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,
};


Expand Down
1 change: 1 addition & 0 deletions yarn-project/noir-libs/noir-aztec/src/oracle.nr
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
mod arguments;
mod call_private_function;
mod context;
mod create_commitment;
mod create_l2_to_l1_message;
mod create_nullifier;
Expand Down
7 changes: 7 additions & 0 deletions yarn-project/noir-libs/noir-aztec/src/oracle/context.nr
Original file line number Diff line number Diff line change
@@ -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
}