Skip to content

Commit

Permalink
token portal standard and corresponding cross chain test
Browse files Browse the repository at this point in the history
  • Loading branch information
rahul-kothari committed Sep 14, 2023
1 parent a1212ae commit 3e4c6e3
Show file tree
Hide file tree
Showing 10 changed files with 791 additions and 45 deletions.
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
266 changes: 266 additions & 0 deletions yarn-project/end-to-end/src/e2e_token_bridge.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,266 @@
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.`);
}, 60_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
.mint_public({ address: ownerAddress }, 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: ownerAddress },
{ 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
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
.mint({ address: ownerAddress }, 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: ownerAddress },
{ 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

0 comments on commit 3e4c6e3

Please sign in to comment.