Skip to content

Commit

Permalink
feat: make l1tol2 message consumption take leafIndex
Browse files Browse the repository at this point in the history
  • Loading branch information
fcarreiro committed Apr 17, 2024
1 parent fd720cc commit 6b3bafe
Show file tree
Hide file tree
Showing 11 changed files with 107 additions and 24 deletions.
18 changes: 16 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,21 @@ 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
);

assert(self.l1_to_l2_msg_exists(message_hash, leaf_index), "Tried to consume inexistent L1-to-L2 message");
let nullifier = compute_message_nullifier(message_hash, secret, leaf_index);
// 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 @@ -27,7 +27,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<ARGS_COUNT, 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
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 @@ -280,10 +280,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 @@ -305,10 +315,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 @@ -35,11 +35,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(&mut context, to, amount);
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);
assert(maybeIndexAndPath !== undefined, 'Message not found in tree');
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
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,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 +93,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);
assert(maybeIndexAndPath !== undefined, 'Message not found in tree');
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,9 +167,17 @@ describe('e2e_public_cross_chain_messaging', () => {
secretHash,
);

// get message leaf index, needed for claiming in public
const maybeIndexAndPath = await aztecNode.getL1ToL2MessageMembershipWitness('latest', msgHash, 0n);
assert(maybeIndexAndPath !== undefined, 'Message not found in tree');
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
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,12 +9,10 @@ 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 () => {
Expand Down Expand Up @@ -53,7 +53,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);
assert(maybeIndexAndPath !== undefined, 'Message not found in tree');
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 @@ -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
4 changes: 2 additions & 2 deletions yarn-project/end-to-end/src/shared/gas_portal_test_harness.ts
Original file line number Diff line number Diff line change
Expand Up @@ -201,10 +201,10 @@ class GasBridgingTestHarness implements IGasBridgingTestHarness {
return Fr.fromString(messageHash);
}

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

async getL2PublicBalanceOf(owner: AztecAddress) {
Expand Down
31 changes: 29 additions & 2 deletions yarn-project/end-to-end/src/shared/uniswap_l1_l2.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import { InboxAbi, UniswapPortalAbi, UniswapPortalBytecode } from '@aztec/l1-art
import { UniswapContract } from '@aztec/noir-contracts.js/Uniswap';

import { jest } from '@jest/globals';
import { strict as assert } from 'assert';
import {
type Account,
type Chain,
Expand Down Expand Up @@ -409,9 +410,22 @@ export const uniswapL1L2TestSuite = (
// Wait for the message to be available for consumption
await wethCrossChainHarness.makeMessageConsumable(wethDepositMsgHash);

// Get message leaf index, needed for claiming in public
const wethDepositMaybeIndexAndPath = await aztecNode.getL1ToL2MessageMembershipWitness(
'latest',
wethDepositMsgHash,
0n,
);
assert(wethDepositMaybeIndexAndPath !== undefined, 'Message not found in tree');
const wethDepositMessageLeafIndex = wethDepositMaybeIndexAndPath[0];

// 2. Claim WETH on L2
logger.info('Minting weth on L2');
await wethCrossChainHarness.consumeMessageOnAztecAndMintPublicly(wethAmountToBridge, secretForMintingWeth);
await wethCrossChainHarness.consumeMessageOnAztecAndMintPublicly(
wethAmountToBridge,
secretForMintingWeth,
wethDepositMessageLeafIndex,
);
await wethCrossChainHarness.expectPublicBalanceOnL2(ownerAddress, wethAmountToBridge);

// Store balances
Expand Down Expand Up @@ -585,9 +599,22 @@ export const uniswapL1L2TestSuite = (
// Wait for the message to be available for consumption
await daiCrossChainHarness.makeMessageConsumable(outTokenDepositMsgHash);

// Get message leaf index, needed for claiming in public
const outTokenDepositMaybeIndexAndPath = await aztecNode.getL1ToL2MessageMembershipWitness(
'latest',
outTokenDepositMsgHash,
0n,
);
assert(outTokenDepositMaybeIndexAndPath !== undefined, 'Message not found in tree');
const outTokenDepositMessageLeafIndex = outTokenDepositMaybeIndexAndPath[0];

// 6. claim dai on L2
logger.info('Consuming messages to mint dai on L2');
await daiCrossChainHarness.consumeMessageOnAztecAndMintPublicly(daiAmountToBridge, secretForDepositingSwappedDai);
await daiCrossChainHarness.consumeMessageOnAztecAndMintPublicly(
daiAmountToBridge,
secretForDepositingSwappedDai,
outTokenDepositMessageLeafIndex,
);
await daiCrossChainHarness.expectPublicBalanceOnL2(ownerAddress, daiL2BalanceBeforeSwap + daiAmountToBridge);

const wethL2BalanceAfterSwap = await wethCrossChainHarness.getL2PublicBalanceOf(ownerAddress);
Expand Down

0 comments on commit 6b3bafe

Please sign in to comment.