Skip to content

Commit

Permalink
wip
Browse files Browse the repository at this point in the history
  • Loading branch information
fcarreiro committed Apr 17, 2024
1 parent 84c930a commit bf7008f
Show file tree
Hide file tree
Showing 15 changed files with 153 additions and 37 deletions.
20 changes: 18 additions & 2 deletions noir-projects/aztec-nr/aztec/src/context/avm_context.nr
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use crate::hash::{compute_secret_hash, compute_message_hash, compute_message_nullifier};
use dep::protocol_types::{
address::{AztecAddress, EthAddress},
constants::{L1_TO_L2_MESSAGE_LENGTH, NESTED_CALL_L2_GAS_BUFFER}, header::Header
Expand Down Expand Up @@ -128,8 +129,23 @@ impl PublicContextInterface for AvmContext {
self.accumulate_unencrypted_logs(event_selector, log);
}

fn consume_l1_to_l2_message(&mut self, content: Field, secret: Field, sender: EthAddress) {
assert(false, "'consume_l1_to_l2_message' not implemented!");
fn consume_l1_to_l2_message(&mut self, content: Field, secret: Field, sender: EthAddress, leaf_index: Field) {
let secret_hash = compute_secret_hash(secret);
let message_hash = compute_message_hash(
sender,
self.chain_id(),
/*recipient=*/self.this_address(),
self.version(),
content,
secret_hash
);
let nullifier = compute_message_nullifier(message_hash, secret, leaf_index);

assert(!self.nullifier_exists(nullifier, self.this_address()), "L1-to-L2 message is already nullified");
assert(self.l1_to_l2_msg_exists(message_hash, leaf_index), "Tried to consume nonexistent L1-to-L2 message");

// Push nullifier (and the "commitment" corresponding to this can be "empty")
self.push_new_nullifier(nullifier, 0);
}

fn message_portal(&mut self, recipient: EthAddress, content: Field) {
Expand Down
2 changes: 1 addition & 1 deletion noir-projects/aztec-nr/aztec/src/context/interface.nr
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ trait PublicContextInterface {
fn fee_per_l1_gas(self) -> Field;
fn fee_per_l2_gas(self) -> Field;
fn message_portal(&mut self, recipient: EthAddress, content: Field);
fn consume_l1_to_l2_message(&mut self, content: Field, secret: Field, sender: EthAddress);
fn consume_l1_to_l2_message(&mut self, content: Field, secret: Field, sender: EthAddress, leaf_index: Field);
fn accumulate_encrypted_logs<N>(&mut self, log: [Field; N]);
fn accumulate_unencrypted_logs<T>(&mut self, log: T);
fn call_public_function<RETURNS_COUNT>(
Expand Down
3 changes: 2 additions & 1 deletion noir-projects/aztec-nr/aztec/src/context/public_context.nr
Original file line number Diff line number Diff line change
Expand Up @@ -263,7 +263,8 @@ impl PublicContextInterface for PublicContext {

// We can consume message with a secret in public context because the message cannot be modified and therefore
// there is no front-running risk (e.g. somebody could front run you to claim your tokens to your address).
fn consume_l1_to_l2_message(&mut self, content: Field, secret: Field, sender: EthAddress) {
// Leaf index is not used in public context, but it is used in the AVMContext which will replace it.
fn consume_l1_to_l2_message(&mut self, content: Field, secret: Field, sender: EthAddress, _leaf_index: Field) {
let this = (*self).this_address();
let nullifier = process_l1_to_l2_message(
self.historical_header.state.l1_to_l2_message_tree.root,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,11 @@ contract GasToken {
}

#[aztec(public)]
fn claim_public(to: AztecAddress, amount: Field, secret: Field) {
fn claim_public(to: AztecAddress, amount: Field, secret: Field, leaf_index: Field) {
let content_hash = get_bridge_gas_msg_hash(to, amount);

// Consume message and emit nullifier
context.consume_l1_to_l2_message(content_hash, secret, context.this_portal_address());
context.consume_l1_to_l2_message(content_hash, secret, context.this_portal_address(), leaf_index);

let new_balance = storage.balances.at(to).read() + U128::from_integer(amount);
storage.balances.at(to).write(new_balance);
Expand Down
19 changes: 15 additions & 4 deletions noir-projects/noir-contracts/contracts/test_contract/src/main.nr
Original file line number Diff line number Diff line change
Expand Up @@ -268,10 +268,20 @@ contract Test {
}

#[aztec(public)]
fn consume_mint_public_message(to: AztecAddress, amount: Field, secret: Field) {
fn consume_mint_public_message(
to: AztecAddress,
amount: Field,
secret: Field,
message_leaf_index: Field
) {
let content_hash = get_mint_public_content_hash(to, amount);
// Consume message and emit nullifier
context.consume_l1_to_l2_message(content_hash, secret, context.this_portal_address());
context.consume_l1_to_l2_message(
content_hash,
secret,
context.this_portal_address(),
message_leaf_index
);
}

#[aztec(private)]
Expand All @@ -293,10 +303,11 @@ contract Test {
fn consume_message_from_arbitrary_sender_public(
content: Field,
secret: Field,
sender: EthAddress
sender: EthAddress,
message_leaf_index: Field
) {
// Consume message and emit nullifier
context.consume_l1_to_l2_message(content, secret, sender);
context.consume_l1_to_l2_message(content, secret, sender, message_leaf_index);
}

#[aztec(private)]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,11 +34,16 @@ contract TokenBridge {
// docs:start:claim_public
// Consumes a L1->L2 message and calls the token contract to mint the appropriate amount publicly
#[aztec(public)]
fn claim_public(to: AztecAddress, amount: Field, secret: Field) {
fn claim_public(to: AztecAddress, amount: Field, secret: Field, message_leaf_index: Field) {
let content_hash = get_mint_public_content_hash(to, amount);

// Consume message and emit nullifier
context.consume_l1_to_l2_message(content_hash, secret, context.this_portal_address());
context.consume_l1_to_l2_message(
content_hash,
secret,
context.this_portal_address(),
message_leaf_index
);

// Mint tokens
Token::at(storage.token.read()).mint_public(to, amount).call(&mut context);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {
import { sha256ToField } from '@aztec/foundation/crypto';
import { type TokenBridgeContract, type TokenContract } from '@aztec/noir-contracts.js';

import { strict as assert } from 'assert';
import { toFunctionSelector } from 'viem/utils';

import { setup } from './fixtures/utils.js';
Expand Down Expand Up @@ -245,11 +246,16 @@ describe('e2e_cross_chain_messaging', () => {
secretHashForL2MessageConsumption,
);

// get message leaf index, needed for claiming in public
const maybeIndexAndPath = await aztecNode.getL1ToL2MessageMembershipWitness('latest', msgHash, 0n);
expect(maybeIndexAndPath).toBeDefined();
const messageLeafIndex = maybeIndexAndPath![0];

// 3. Consume L1 -> L2 message and try to mint publicly on L2 - should fail
await expect(
l2Bridge
.withWallet(user2Wallet)
.methods.claim_public(ownerAddress, bridgeAmount, secretForL2MessageConsumption)
.methods.claim_public(ownerAddress, bridgeAmount, secretForL2MessageConsumption, messageLeafIndex)
.prove(),
).rejects.toThrow(`No non-nullified L1 to L2 message found for message hash ${wrongMessage.hash().toString()}`);
}, 120_000);
Expand Down
1 change: 1 addition & 0 deletions yarn-project/end-to-end/src/e2e_fees.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ describe('e2e_fees', () => {
sequencerAddress = wallets[2].getAddress();

gasBridgeTestHarness = await GasPortalTestingHarnessFactory.create({
aztecNode: aztecNode,
pxeService: pxe,
publicClient: deployL1ContractsValues.publicClient,
walletClient: deployL1ContractsValues.walletClient,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import {
type DebugLogger,
type DeployL1Contracts,
EthAddress,
type EthAddressLike,
type FieldLike,
Fr,
L1Actor,
L1ToL2Message,
Expand All @@ -19,6 +21,7 @@ import { TestContract } from '@aztec/noir-contracts.js';
import { type TokenContract } from '@aztec/noir-contracts.js/Token';
import { type TokenBridgeContract } from '@aztec/noir-contracts.js/TokenBridge';

import { strict as assert } from 'assert';
import { type Chain, type GetContractReturnType, type Hex, type HttpTransport, type PublicClient } from 'viem';
import { decodeEventLog, toFunctionSelector } from 'viem/utils';

Expand Down Expand Up @@ -92,8 +95,13 @@ describe('e2e_public_cross_chain_messaging', () => {
// Wait for the message to be available for consumption
await crossChainTestHarness.makeMessageConsumable(msgHash);

// Get message leaf index, needed for claiming in public
const maybeIndexAndPath = await aztecNode.getL1ToL2MessageMembershipWitness('latest', msgHash, 0n);
expect(maybeIndexAndPath).toBeDefined();
const messageLeafIndex = maybeIndexAndPath![0];

// 3. Consume L1 -> L2 message and mint public tokens on L2
await crossChainTestHarness.consumeMessageOnAztecAndMintPublicly(bridgeAmount, secret);
await crossChainTestHarness.consumeMessageOnAztecAndMintPublicly(bridgeAmount, secret, messageLeafIndex);
await crossChainTestHarness.expectPublicBalanceOnL2(ownerAddress, bridgeAmount);
const afterBalance = bridgeAmount;

Expand Down Expand Up @@ -161,14 +169,26 @@ describe('e2e_public_cross_chain_messaging', () => {
secretHash,
);

// get message leaf index, needed for claiming in public
const maybeIndexAndPath = await aztecNode.getL1ToL2MessageMembershipWitness('latest', msgHash, 0n);
expect(maybeIndexAndPath).toBeDefined();
const messageLeafIndex = maybeIndexAndPath![0];

// user2 tries to consume this message and minting to itself -> should fail since the message is intended to be consumed only by owner.
await expect(
l2Bridge.withWallet(user2Wallet).methods.claim_public(user2Wallet.getAddress(), bridgeAmount, secret).prove(),
l2Bridge
.withWallet(user2Wallet)
.methods.claim_public(user2Wallet.getAddress(), bridgeAmount, secret, messageLeafIndex)
.prove(),
).rejects.toThrow(`No non-nullified L1 to L2 message found for message hash ${wrongMessage.hash().toString()}`);

// user2 consumes owner's L1-> L2 message on bridge contract and mints public tokens on L2
logger.info("user2 consumes owner's message on L2 Publicly");
await l2Bridge.withWallet(user2Wallet).methods.claim_public(ownerAddress, bridgeAmount, secret).send().wait();
await l2Bridge
.withWallet(user2Wallet)
.methods.claim_public(ownerAddress, bridgeAmount, secret, messageLeafIndex)
.send()
.wait();
// ensure funds are gone to owner and not user2.
await crossChainTestHarness.expectPublicBalanceOnL2(ownerAddress, bridgeAmount);
await crossChainTestHarness.expectPublicBalanceOnL2(user2Wallet.getAddress(), 0n);
Expand Down Expand Up @@ -312,7 +332,8 @@ describe('e2e_public_cross_chain_messaging', () => {
const testContract = await TestContract.deploy(user1Wallet).send().deployed();

const consumeMethod = isPrivate
? testContract.methods.consume_message_from_arbitrary_sender_private
? (content: FieldLike, secret: FieldLike, sender: EthAddressLike, _leafIndex: FieldLike) =>
testContract.methods.consume_message_from_arbitrary_sender_private(content, secret, sender)
: testContract.methods.consume_message_from_arbitrary_sender_public;

const secret = Fr.random();
Expand All @@ -329,7 +350,7 @@ describe('e2e_public_cross_chain_messaging', () => {
const [message1Index, _1] = (await aztecNode.getL1ToL2MessageMembershipWitness('latest', message.hash(), 0n))!;

// Finally, we consume the L1 -> L2 message using the test contract either from private or public
await consumeMethod(message.content, secret, message.sender.sender).send().wait();
await consumeMethod(message.content, secret, message.sender.sender, message1Index).send().wait();

// We send and consume the exact same message the second time to test that oracles correctly return the new
// non-nullified message
Expand All @@ -348,7 +369,7 @@ describe('e2e_public_cross_chain_messaging', () => {

// Now we consume the message again. Everything should pass because oracle should return the duplicate message
// which is not nullified
await consumeMethod(message.content, secret, message.sender.sender).send().wait();
await consumeMethod(message.content, secret, message.sender.sender, message2Index).send().wait();
},
120_000,
);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import { type AztecAddress, type DebugLogger, type EthAddress } from '@aztec/aztec.js';
import { type AztecAddress, type AztecNode, type DebugLogger, type EthAddress } from '@aztec/aztec.js';

import { strict as assert } from 'assert';

import { setup } from './fixtures/utils.js';
import { CrossChainTestHarness } from './shared/cross_chain_test_harness.js';
Expand All @@ -7,25 +9,31 @@ describe('e2e_public_to_private_messaging', () => {
let logger: DebugLogger;
let teardown: () => Promise<void>;

let aztecNode: AztecNode;
let ethAccount: EthAddress;

let underlyingERC20: any;

let ownerAddress: AztecAddress;

let crossChainTestHarness: CrossChainTestHarness;

beforeEach(async () => {
const { aztecNode, pxe, deployL1ContractsValues, wallet, logger: logger_, teardown: teardown_ } = await setup(2);
const {
aztecNode: aztecNode_,
pxe,
deployL1ContractsValues,
wallet,
logger: logger_,
teardown: teardown_,
} = await setup(2);
crossChainTestHarness = await CrossChainTestHarness.new(
aztecNode,
aztecNode_,
pxe,
deployL1ContractsValues.publicClient,
deployL1ContractsValues.walletClient,
wallet,
logger_,
);

aztecNode = crossChainTestHarness.aztecNode;
ethAccount = crossChainTestHarness.ethAccount;
ownerAddress = crossChainTestHarness.ownerAddress;
underlyingERC20 = crossChainTestHarness.underlyingERC20;
Expand Down Expand Up @@ -53,7 +61,12 @@ describe('e2e_public_to_private_messaging', () => {

await crossChainTestHarness.makeMessageConsumable(msgHash);

await crossChainTestHarness.consumeMessageOnAztecAndMintPublicly(bridgeAmount, secret);
// get message leaf index, needed for claiming in public
const maybeIndexAndPath = await aztecNode.getL1ToL2MessageMembershipWitness('latest', msgHash, 0n);
expect(maybeIndexAndPath).toBeDefined();
const messageLeafIndex = maybeIndexAndPath![0];

await crossChainTestHarness.consumeMessageOnAztecAndMintPublicly(bridgeAmount, secret, messageLeafIndex);
await crossChainTestHarness.expectPublicBalanceOnL2(ownerAddress, bridgeAmount);

// Create the commitment to be spent in the private domain
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@ describe('e2e_fees_account_init', () => {
});

gasBridgeTestHarness = await GasPortalTestingHarnessFactory.create({
aztecNode: ctx.aztecNode,
pxeService: ctx.pxe,
publicClient: ctx.deployL1ContractsValues.publicClient,
walletClient: ctx.deployL1ContractsValues.walletClient,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -319,10 +319,10 @@ export class CrossChainTestHarness {
await this.addPendingShieldNoteToPXE(bridgeAmount, secretHashForRedeemingMintedNotes, consumptionReceipt.txHash);
}

async consumeMessageOnAztecAndMintPublicly(bridgeAmount: bigint, secret: Fr) {
async consumeMessageOnAztecAndMintPublicly(bridgeAmount: bigint, secret: Fr, leafIndex: bigint) {
this.logger.info('Consuming messages on L2 Publicly');
// Call the mint tokens function on the Aztec.nr contract
await this.l2Bridge.methods.claim_public(this.ownerAddress, bridgeAmount, secret).send().wait();
await this.l2Bridge.methods.claim_public(this.ownerAddress, bridgeAmount, secret, leafIndex).send().wait();
}

async withdrawPrivateFromAztecToL1(withdrawAmount: bigint, nonce: Fr = Fr.ZERO): Promise<FieldsOf<TxReceipt>> {
Expand Down
Loading

0 comments on commit bf7008f

Please sign in to comment.