Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -737,6 +737,17 @@ jobs:
name: "Test"
command: cond_run_script end-to-end ./scripts/run_tests_local e2e_token_contract.test.ts

e2e-token-bridge-contract:
machine:
image: ubuntu-2004:202010-01
resource_class: large
steps:
- *checkout
- *setup_env
- run:
name: "Test"
command: cond_run_script end-to-end ./scripts/run_tests_local e2e_token_bridge_contract.test.ts

e2e-private-token-contract:
machine:
image: ubuntu-2004:202010-01
Expand Down Expand Up @@ -1459,6 +1470,7 @@ workflows:
- e2e-deploy-contract: *e2e_test
- e2e-lending-contract: *e2e_test
- e2e-token-contract: *e2e_test
- e2e-token-bridge-contract: *e2e_test
- e2e-private-token-contract: *e2e_test
- e2e-sandbox-example: *e2e_test
- e2e-multi-transfer-contract: *e2e_test
Expand Down Expand Up @@ -1494,6 +1506,7 @@ workflows:
- e2e-deploy-contract
- e2e-lending-contract
- e2e-token-contract
- e2e-token-bridge-contract
- e2e-private-token-contract
- e2e-sandbox-example
- e2e-multi-transfer-contract
Expand Down
4 changes: 2 additions & 2 deletions l1-contracts/test/portals/UniswapPortal.sol
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,8 @@ contract UniswapPortal {
* @param _amountOutMinimum - The minimum amount of output assets to receive from the swap (slippage protection)
* @param _aztecRecipient - The aztec address to receive the output assets
* @param _secretHash - The hash of the secret consumable message
* @param _deadlineForL1ToL2Message - deadline for when the L1 to L2 message (to mint outpiut assets in L2) must be consumed by
* @param _canceller - The ethereum address that can cancel the deposit
* @param _deadlineForL1ToL2Message - deadline for when the L1 to L2 message (to mint output assets in L2) must be consumed by
* @param _canceller - The ethereum address that can cancel the L1 to L2 deposit
* @param _withCaller - When true, using `msg.sender` as the caller, otherwise address(0)
* @return The entryKey of the deposit transaction in the Inbox
*/
Expand Down
3 changes: 2 additions & 1 deletion yarn-project/end-to-end/src/cli_docs_sandbox.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ describe('CLI docs sandbox', () => {
logs.splice(0);
};

it('prints example contracts', async () => {
it.only('prints example contracts', async () => {
const docs = `
// docs:start:example-contracts
% aztec-cli example-contracts
Expand All @@ -104,6 +104,7 @@ SchnorrAuthWitnessAccountContractAbi
SchnorrHardcodedAccountContractAbi
SchnorrSingleKeyAccountContractAbi
TestContractAbi
TokenBridgeContractAbi
TokenContractAbi
UniswapContractAbi
// docs:end:example-contracts
Expand Down
261 changes: 261 additions & 0 deletions yarn-project/end-to-end/src/e2e_token_bridge_contract.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,261 @@
import { AztecNodeService } from '@aztec/aztec-node';
import { AztecRPCServer } from '@aztec/aztec-rpc';
import { AuthWitnessEntrypointWallet, computeMessageSecretHash } from '@aztec/aztec.js';
import {
AztecAddress,
CircuitsWasm,
CompleteAddress,
EthAddress,
Fr,
FunctionSelector,
GeneratorIndex,
} from '@aztec/circuits.js';
import { pedersenPlookupCompressWithHashIndex } from '@aztec/circuits.js/barretenberg';
import { DeployL1Contracts } from '@aztec/ethereum';
import { DebugLogger } from '@aztec/foundation/log';
import { SchnorrAuthWitnessAccountContract, TokenBridgeContract, TokenContract } from '@aztec/noir-contracts/types';
import { AztecRPC, TxStatus } from '@aztec/types';

import {
createAuthWitnessAccounts,
delay,
deployAndInitializeStandardizedTokenAndBridgeContracts,
setup,
} from './fixtures/utils.js';

const hashPayload = async (payload: Fr[]) => {
return pedersenPlookupCompressWithHashIndex(
await CircuitsWasm.get(),
payload.map(fr => fr.toBuffer()),
GeneratorIndex.SIGNATURE_PAYLOAD,
);
};

describe('e2e_token_bridge_contract', () => {
let aztecNode: AztecNodeService | undefined;
let aztecRpcServer: AztecRPC;
let wallets: AuthWitnessEntrypointWallet[];
let accounts: CompleteAddress[];
let logger: DebugLogger;

let ethAccount: EthAddress;
let ownerAddress: AztecAddress;
let token: TokenContract;
let bridge: TokenBridgeContract;
let tokenPortalAddress: EthAddress;
let tokenPortal: any;
let underlyingERC20: any;

beforeAll(async () => {
let deployL1ContractsValues: DeployL1Contracts;

({ aztecNode, aztecRpcServer, deployL1ContractsValues, logger } = await setup(0));
({ accounts, wallets } = await createAuthWitnessAccounts(aztecRpcServer, 3));

const walletClient = deployL1ContractsValues.walletClient;
const publicClient = deployL1ContractsValues.publicClient;
ethAccount = EthAddress.fromString((await walletClient.getAddresses())[0]);
ownerAddress = accounts[0].address;

logger(`Deploying and initializing token, portal and its bridge...`);
const contracts = await deployAndInitializeStandardizedTokenAndBridgeContracts(
wallets[0],
walletClient,
publicClient,
deployL1ContractsValues!.registryAddress,
ownerAddress,
);
token = contracts.l2Token;
bridge = contracts.bridge;
tokenPortalAddress = contracts.tokenPortalAddress;
tokenPortal = contracts.tokenPortal;
underlyingERC20 = contracts.underlyingERC20;
logger(`Deployed and initialized token, portal and its bridge.`);
}, 65_000);

afterEach(async () => {
await aztecNode?.stop();
if (aztecRpcServer instanceof AztecRPCServer) {
await aztecRpcServer?.stop();
}
});

// TODO(#2291) - replace with new cross chain harness impl
const mintTokensOnL1 = async (amount: bigint) => {
logger('Minting tokens on L1');
await underlyingERC20.write.mint([ethAccount.toString(), amount], {} as any);
expect(await underlyingERC20.read.balanceOf([ethAccount.toString()])).toBe(amount);
};

// TODO(#2291) - replace with new cross chain harness impl
const depositTokensToPortal = async (bridgeAmount: bigint, secretHash: Fr) => {
await underlyingERC20.write.approve([tokenPortalAddress.toString(), bridgeAmount], {} as any);

// Deposit tokens to the TokenPortal
const deadline = 2 ** 32 - 1; // max uint32 - 1
logger('Sending messages to L1 portal to be consumed');
const args = [
ownerAddress.toString(),
bridgeAmount,
deadline,
secretHash.toString(true),
ethAccount.toString(),
] as const;
const { result: messageKeyHex } = await tokenPortal.simulate.depositToAztec(args, {
account: ethAccount.toString(),
} as any);
await tokenPortal.write.depositToAztec(args, {} as any);

return Fr.fromString(messageKeyHex);
};

const mintPublicOnL2 = async (amount: bigint) => {
const tx = token.methods.mint_public({ address: ownerAddress }, amount).send();
const receipt = await tx.wait();
expect(receipt.status).toBe(TxStatus.MINED);
};

it('Deposit funds (publicly) from L1 -> L2 and withdraw (publicly) back to L1', async () => {
const l1TokenBalance = 1000000n;
const bridgeAmount = 100n;
const secret = Fr.random();
const secretHash = await computeMessageSecretHash(secret);

// 1. Mint tokens on L1
await mintTokensOnL1(l1TokenBalance);

// 2. Deposit tokens to the TokenPortal
const messageKey = await depositTokensToPortal(bridgeAmount, secretHash);
expect(await underlyingERC20.read.balanceOf([ethAccount.toString()])).toBe(l1TokenBalance - bridgeAmount);

// Wait for the archiver to process the message
await delay(5000); /// waiting 5 seconds.

// Perform an unrelated transaction on L2 to progress the rollup - here we mint tokens to owner
const amount = 99n;
await mintPublicOnL2(amount);
const balanceBefore = await token.methods.balance_of_public({ address: ownerAddress }).view();
expect(balanceBefore).toBe(amount);

// 3. Consume message on aztec and mint publicly
logger('Consuming messages on L2');
const tx = bridge.methods
.deposit_public(bridgeAmount, messageKey, secret, { address: ethAccount.toField() })
.send();
const receipt = await tx.wait();
expect(receipt.status).toBe(TxStatus.MINED);
const afterBalance = await token.methods.balance_of_public({ address: ownerAddress }).view();
expect(afterBalance).toBe(balanceBefore + bridgeAmount);

// time to withdraw the funds again!
logger('Withdrawing funds from L2');

// 4. Give approval to bridge to burn owner's funds:
const withdrawAmount = 9n;
const nonce = Fr.random();

const burnMessageHash = async (caller: AztecAddress, from: AztecAddress, amount: bigint, nonce: Fr) => {
return await hashPayload([
caller.toField(),
token.address.toField(),
FunctionSelector.fromSignature('burn_public((Field),Field,Field)').toField(),
from.toField(),
new Fr(amount),
nonce,
]);
};

const messageHash = await burnMessageHash(bridge.address, ownerAddress, withdrawAmount, nonce);
// Add it to the wallet as approved
const owner = await SchnorrAuthWitnessAccountContract.at(ownerAddress, wallets[0]);
const setValidTx = owner.methods.set_is_valid_storage(messageHash, 1).send();
const validTxReceipt = await setValidTx.wait();
expect(validTxReceipt.status).toBe(TxStatus.MINED);

// 5. Withdraw from L2 bridge
const withdrawTx = bridge.methods
.withdraw_public({ address: ethAccount.toField() }, withdrawAmount, { address: EthAddress.ZERO.toField() }, nonce)
.send();
const withdrawReceipt = await withdrawTx.wait();
expect(withdrawReceipt.status).toBe(TxStatus.MINED);
expect(await token.methods.balance_of_public({ address: ownerAddress }).view()).toBe(afterBalance - withdrawAmount);

// TODO (#2291): Consume message on L1 -> update crosschain test harness to do this cleanly.
}, 120_000);

it('Deposit funds (privately) from L1 -> L2 and withdraw (privately) back to L1', async () => {
const l1TokenBalance = 1000000n;
const bridgeAmount = 100n;
const secret = Fr.random();
const secretHash = await computeMessageSecretHash(secret);

// 1. Mint tokens on L1
// TODO (#2291): Because same owner is used across two tests, l1 balance already exists. This is why we have two separate cross chain tests.
// Separate them like before
if ((await underlyingERC20.read.balanceOf([ethAccount.toString()])) === 0n) {
await mintTokensOnL1(l1TokenBalance);
}

// 2. Deposit tokens to the TokenPortal
const messageKey = await depositTokensToPortal(bridgeAmount, secretHash);
expect(await underlyingERC20.read.balanceOf([ethAccount.toString()])).toBe(l1TokenBalance - bridgeAmount);

// Wait for the archiver to process the message
await delay(5000); /// waiting 5 seconds.

// Perform an unrelated transaction on L2 to progress the rollup - here we mint tokens to owner
const amount = 99n;
await mintPublicOnL2(amount);
const balanceBefore = await token.methods.balance_of_public({ address: ownerAddress }).view();
expect(balanceBefore).toBe(amount);

// 3. Consume message on aztec and mint publicly
logger('Consuming messages on L2');
const tx = bridge.methods.deposit(bridgeAmount, messageKey, secret, { address: ethAccount.toField() }).send();
const receipt = await tx.wait();
expect(receipt.status).toBe(TxStatus.MINED);
const txClaim = token.methods.redeem_shield({ address: ownerAddress }, bridgeAmount, secret).send();
const receiptClaim = await txClaim.wait();
expect(receiptClaim.status).toBe(TxStatus.MINED);

const afterPrivateBalance = await token.methods.balance_of_private({ address: ownerAddress }).view();
expect(afterPrivateBalance).toBe(bridgeAmount);

// time to withdraw the funds again!
logger('Withdrawing funds from L2');

// 4. Give approval to bridge to burn owner's funds:
const withdrawAmount = 9n;
const nonce = Fr.random();
const burnMessageHash = async (caller: AztecAddress, from: AztecAddress, amount: bigint, nonce: Fr) => {
return await hashPayload([
caller.toField(),
token.address.toField(),
FunctionSelector.fromSignature('burn((Field),Field,Field)').toField(),
from.toField(),
new Fr(amount),
nonce,
]);
};
const messageHash = await burnMessageHash(bridge.address, ownerAddress, withdrawAmount, nonce);
await wallets[0].signAndGetAuthWitness(messageHash); // wallet[0] -> ownerAddress

// 5. Withdraw from L2 bridge
const withdrawTx = bridge.methods
.withdraw(
{ address: token.address },
{ address: ethAccount.toField() },
withdrawAmount,
{ address: EthAddress.ZERO.toField() },
nonce,
)
.send();
const withdrawReceipt = await withdrawTx.wait();
expect(withdrawReceipt.status).toBe(TxStatus.MINED);
expect(await token.methods.balance_of_private({ address: ownerAddress }).view()).toBe(
afterPrivateBalance - withdrawAmount,
);

// TODO (#2291): Consume message on L1 -> update crosschain test harness to do this cleanly.
}, 120_000);
});
43 changes: 4 additions & 39 deletions yarn-project/end-to-end/src/e2e_token_contract.test.ts
Original file line number Diff line number Diff line change
@@ -1,28 +1,15 @@
import { AztecNodeService } from '@aztec/aztec-node';
import { AztecRPCServer } from '@aztec/aztec-rpc';
import {
Account,
AuthWitnessAccountContract,
AuthWitnessEntrypointWallet,
IAuthWitnessAccountEntrypoint,
computeMessageSecretHash,
} from '@aztec/aztec.js';
import {
CircuitsWasm,
CompleteAddress,
Fr,
FunctionSelector,
GeneratorIndex,
GrumpkinScalar,
} from '@aztec/circuits.js';
import { AuthWitnessEntrypointWallet, computeMessageSecretHash } from '@aztec/aztec.js';
import { CircuitsWasm, CompleteAddress, Fr, FunctionSelector, GeneratorIndex } from '@aztec/circuits.js';
import { pedersenPlookupCompressWithHashIndex } from '@aztec/circuits.js/barretenberg';
import { DebugLogger } from '@aztec/foundation/log';
import { SchnorrAuthWitnessAccountContract, TokenContract } from '@aztec/noir-contracts/types';
import { AztecRPC, TxStatus } from '@aztec/types';

import { jest } from '@jest/globals';

import { setup } from './fixtures/utils.js';
import { createAuthWitnessAccounts, setup } from './fixtures/utils.js';
import { TokenSimulator } from './simulators/token_simulator.js';

const hashPayload = async (payload: Fr[]) => {
Expand Down Expand Up @@ -50,29 +37,7 @@ describe('e2e_token_contract', () => {

beforeAll(async () => {
({ aztecNode, aztecRpcServer, logger } = await setup(0));

{
const _accounts = [];
for (let i = 0; i < 3; i++) {
const privateKey = GrumpkinScalar.random();
const account = new Account(aztecRpcServer, privateKey, new AuthWitnessAccountContract(privateKey));
const deployTx = await account.deploy();
await deployTx.wait({ interval: 0.1 });
_accounts.push(account);
}
wallets = await Promise.all(
_accounts.map(
async account =>
new AuthWitnessEntrypointWallet(
aztecRpcServer,
(await account.getEntrypoint()) as unknown as IAuthWitnessAccountEntrypoint,
await account.getCompleteAddress(),
),
),
);
//wallet = new AuthWitnessEntrypointWallet(aztecRpcServer, await AuthEntrypointCollection.fromAccounts(_accounts));
accounts = await wallets[0].getAccounts();
}
({ accounts, wallets } = await createAuthWitnessAccounts(aztecRpcServer, 3));

{
logger(`Deploying token contract`);
Expand Down
Loading