Skip to main content

e2e tests (TypeScript)

Private flow test

uniswap_private
it('should uniswap trade on L1 from L2 funds privately (swaps WETH -> DAI)', async () => {
const wethL1BeforeBalance = await wethCrossChainHarness.getL1BalanceOf(ownerEthAddress);

// 1. Approve and deposit weth to the portal and move to L2
const [secretForMintingWeth, secretHashForMintingWeth] = wethCrossChainHarness.generateClaimSecret();
const [secretForRedeemingWeth, secretHashForRedeemingWeth] = wethCrossChainHarness.generateClaimSecret();

const tokenDepositMsgHash = await wethCrossChainHarness.sendTokensToPortalPrivate(
secretHashForRedeemingWeth,
wethAmountToBridge,
secretHashForMintingWeth,
);
// funds transferred from owner to token portal
expect(await wethCrossChainHarness.getL1BalanceOf(ownerEthAddress)).toBe(
wethL1BeforeBalance - wethAmountToBridge,
);
expect(await wethCrossChainHarness.getL1BalanceOf(wethCrossChainHarness.tokenPortalAddress)).toBe(
wethAmountToBridge,
);

await wethCrossChainHarness.makeMessageConsumable(tokenDepositMsgHash);

// 2. Claim WETH on L2
logger.info('Minting weth on L2');
await wethCrossChainHarness.consumeMessageOnAztecAndMintPrivately(
secretHashForRedeemingWeth,
wethAmountToBridge,
secretForMintingWeth,
);
await wethCrossChainHarness.redeemShieldPrivatelyOnL2(wethAmountToBridge, secretForRedeemingWeth);
await wethCrossChainHarness.expectPrivateBalanceOnL2(ownerAddress, wethAmountToBridge);

// Store balances
const wethL2BalanceBeforeSwap = await wethCrossChainHarness.getL2PrivateBalanceOf(ownerAddress);
const daiL2BalanceBeforeSwap = await daiCrossChainHarness.getL2PrivateBalanceOf(ownerAddress);

// 3. Owner gives uniswap approval to unshield funds to self on its behalf
logger.info('Approving uniswap to unshield funds to self on my behalf');
const nonceForWETHUnshieldApproval = new Fr(1n);
await ownerWallet.createAuthWit({
caller: uniswapL2Contract.address,
action: wethCrossChainHarness.l2Token.methods.unshield(
ownerAddress,
uniswapL2Contract.address,
wethAmountToBridge,
nonceForWETHUnshieldApproval,
),
});

// 4. Swap on L1 - sends L2 to L1 message to withdraw WETH to L1 and another message to swap assets.
logger.info('Withdrawing weth to L1 and sending message to swap to dai');
const [secretForDepositingSwappedDai, secretHashForDepositingSwappedDai] =
daiCrossChainHarness.generateClaimSecret();
const [secretForRedeemingDai, secretHashForRedeemingDai] = daiCrossChainHarness.generateClaimSecret();

const l2UniswapInteractionReceipt = await uniswapL2Contract.methods
.swap_private(
wethCrossChainHarness.l2Token.address,
wethCrossChainHarness.l2Bridge.address,
wethAmountToBridge,
daiCrossChainHarness.l2Bridge.address,
nonceForWETHUnshieldApproval,
uniswapFeeTier,
minimumOutputAmount,
secretHashForRedeemingDai,
secretHashForDepositingSwappedDai,
ownerEthAddress,
)
.send()
.wait();

const swapPrivateContent = sha256ToField([
Buffer.from(
toFunctionSelector('swap_private(address,uint256,uint24,address,uint256,bytes32,bytes32,address)').substring(
2,
),
'hex',
),
wethCrossChainHarness.tokenPortalAddress.toBuffer32(),
new Fr(wethAmountToBridge),
new Fr(uniswapFeeTier),
daiCrossChainHarness.tokenPortalAddress.toBuffer32(),
new Fr(minimumOutputAmount),
secretHashForRedeemingDai,
secretHashForDepositingSwappedDai,
ownerEthAddress.toBuffer32(),
]);

const swapPrivateLeaf = sha256ToField([
uniswapL2Contract.address,
new Fr(1), // aztec version
EthAddress.fromString(uniswapPortal.address).toBuffer32(),
new Fr(publicClient.chain.id), // chain id
swapPrivateContent,
]);

const withdrawContent = sha256ToField([
Buffer.from(toFunctionSelector('withdraw(address,uint256,address)').substring(2), 'hex'),
uniswapPortalAddress.toBuffer32(),
new Fr(wethAmountToBridge),
uniswapPortalAddress.toBuffer32(),
]);

const withdrawLeaf = sha256ToField([
wethCrossChainHarness.l2Bridge.address,
new Fr(1), // aztec version
wethCrossChainHarness.tokenPortalAddress.toBuffer32(),
new Fr(publicClient.chain.id), // chain id
withdrawContent,
]);

// ensure that user's funds were burnt
await wethCrossChainHarness.expectPrivateBalanceOnL2(ownerAddress, wethL2BalanceBeforeSwap - wethAmountToBridge);
// ensure that uniswap contract didn't eat the funds.
await wethCrossChainHarness.expectPublicBalanceOnL2(uniswapL2Contract.address, 0n);

// Since the outbox is only consumable when the block is proven, we need to set the block to be proven
await rollup.write.setAssumeProvenUntilBlockNumber([await rollup.read.pendingBlockCount()]);

// 5. Consume L2 to L1 message by calling uniswapPortal.swap_private()
logger.info('Execute withdraw and swap on the uniswapPortal!');
const daiL1BalanceOfPortalBeforeSwap = await daiCrossChainHarness.getL1BalanceOf(
daiCrossChainHarness.tokenPortalAddress,
);

const [swapPrivateL2MessageIndex, swapPrivateSiblingPath] = await aztecNode.getL2ToL1MessageMembershipWitness(
l2UniswapInteractionReceipt.blockNumber!,
swapPrivateLeaf,
);
const [withdrawL2MessageIndex, withdrawSiblingPath] = await aztecNode.getL2ToL1MessageMembershipWitness(
l2UniswapInteractionReceipt.blockNumber!,
withdrawLeaf,
);

const withdrawMessageMetadata = {
_l2BlockNumber: BigInt(l2UniswapInteractionReceipt.blockNumber!),
_leafIndex: BigInt(withdrawL2MessageIndex),
_path: withdrawSiblingPath
.toBufferArray()
.map((buf: Buffer) => `0x${buf.toString('hex')}`) as readonly `0x${string}`[],
};

const swapPrivateMessageMetadata = {
_l2BlockNumber: BigInt(l2UniswapInteractionReceipt.blockNumber!),
_leafIndex: BigInt(swapPrivateL2MessageIndex),
_path: swapPrivateSiblingPath
.toBufferArray()
.map((buf: Buffer) => `0x${buf.toString('hex')}`) as readonly `0x${string}`[],
};

const swapArgs = [
wethCrossChainHarness.tokenPortalAddress.toString(),
wethAmountToBridge,
Number(uniswapFeeTier),
daiCrossChainHarness.tokenPortalAddress.toString(),
minimumOutputAmount,
secretHashForRedeemingDai.toString(),
secretHashForDepositingSwappedDai.toString(),
true,
[withdrawMessageMetadata, swapPrivateMessageMetadata],
] as const;

// this should also insert a message into the inbox.
const txHash = await uniswapPortal.write.swapPrivate(swapArgs, {} as any);

// We get the msg leaf from event so that we can later wait for it to be available for consumption
let tokenOutMsgHash: Fr;
{
const txReceipt = await daiCrossChainHarness.publicClient.waitForTransactionReceipt({
hash: txHash,
});

const txLog = txReceipt.logs[9];
const topics = decodeEventLog({
abi: InboxAbi,
data: txLog.data,
topics: txLog.topics,
});
tokenOutMsgHash = Fr.fromString(topics.args.hash);
}

// weth was swapped to dai and send to portal
const daiL1BalanceOfPortalAfter = await daiCrossChainHarness.getL1BalanceOf(
daiCrossChainHarness.tokenPortalAddress,
);
expect(daiL1BalanceOfPortalAfter).toBeGreaterThan(daiL1BalanceOfPortalBeforeSwap);
const daiAmountToBridge = BigInt(daiL1BalanceOfPortalAfter - daiL1BalanceOfPortalBeforeSwap);

// Wait for the message to be available for consumption
await daiCrossChainHarness.makeMessageConsumable(tokenOutMsgHash);

// 6. claim dai on L2
logger.info('Consuming messages to mint dai on L2');
await daiCrossChainHarness.consumeMessageOnAztecAndMintPrivately(
secretHashForRedeemingDai,
daiAmountToBridge,
secretForDepositingSwappedDai,
);
await daiCrossChainHarness.redeemShieldPrivatelyOnL2(daiAmountToBridge, secretForRedeemingDai);
await daiCrossChainHarness.expectPrivateBalanceOnL2(ownerAddress, daiL2BalanceBeforeSwap + daiAmountToBridge);

const wethL2BalanceAfterSwap = await wethCrossChainHarness.getL2PrivateBalanceOf(ownerAddress);
const daiL2BalanceAfterSwap = await daiCrossChainHarness.getL2PrivateBalanceOf(ownerAddress);

logger.info('WETH balance before swap: ' + wethL2BalanceBeforeSwap.toString());
logger.info('DAI balance before swap : ' + daiL2BalanceBeforeSwap.toString());
logger.info('***** 🧚‍♀️ SWAP L2 assets on L1 Uniswap 🧚‍♀️ *****');
logger.info('WETH balance after swap : ', wethL2BalanceAfterSwap.toString());
logger.info('DAI balance after swap : ', daiL2BalanceAfterSwap.toString());
});
Source code: yarn-project/end-to-end/src/shared/uniswap_l1_l2.ts#L182-L393

Public flow test

uniswap_public
// it('should uniswap trade on L1 from L2 funds publicly (swaps WETH -> DAI)', async () => {
// const wethL1BeforeBalance = await wethCrossChainHarness.getL1BalanceOf(ownerEthAddress);

// // 1. Approve and deposit weth to the portal and move to L2
// const [secretForMintingWeth, secretHashForMintingWeth] = wethCrossChainHarness.generateClaimSecret();

// const wethDepositMsgHash = await wethCrossChainHarness.sendTokensToPortalPublic(
// wethAmountToBridge,
// secretHashForMintingWeth,
// );
// // funds transferred from owner to token portal
// expect(await wethCrossChainHarness.getL1BalanceOf(ownerEthAddress)).toBe(
// wethL1BeforeBalance - wethAmountToBridge,
// );
// expect(await wethCrossChainHarness.getL1BalanceOf(wethCrossChainHarness.tokenPortalAddress)).toBe(
// wethAmountToBridge,
// );

// // 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,
// wethDepositMessageLeafIndex,
// );
// await wethCrossChainHarness.expectPublicBalanceOnL2(ownerAddress, wethAmountToBridge);

// // Store balances
// const wethL2BalanceBeforeSwap = await wethCrossChainHarness.getL2PublicBalanceOf(ownerAddress);
// const daiL2BalanceBeforeSwap = await daiCrossChainHarness.getL2PublicBalanceOf(ownerAddress);

// // 3. Owner gives uniswap approval to transfer funds on its behalf
// const nonceForWETHTransferApproval = new Fr(1n);

// await ownerWallet
// .setPublicAuthWit(
// {
// caller: uniswapL2Contract.address,
// action: wethCrossChainHarness.l2Token.methods
// .transfer_public(
// ownerAddress,
// uniswapL2Contract.address,
// wethAmountToBridge,
// nonceForWETHTransferApproval,
// )
// .request(),
// },
// true,
// )
// .send()
// .wait();

// // 4. Swap on L1 - sends L2 to L1 message to withdraw WETH to L1 and another message to swap assets.
// const [secretForDepositingSwappedDai, secretHashForDepositingSwappedDai] =
// daiCrossChainHarness.generateClaimSecret();

// // 4.1 Owner approves user to swap on their behalf:
// const nonceForSwap = new Fr(3n);
// const action = uniswapL2Contract
// .withWallet(sponsorWallet)
// .methods.swap_public(
// ownerAddress,
// wethCrossChainHarness.l2Bridge.address,
// wethAmountToBridge,
// daiCrossChainHarness.l2Bridge.address,
// nonceForWETHTransferApproval,
// uniswapFeeTier,
// minimumOutputAmount,
// ownerAddress,
// secretHashForDepositingSwappedDai,
// ownerEthAddress,
// nonceForSwap,
// );
// await ownerWallet.setPublicAuthWit({ caller: sponsorAddress, action }, true).send().wait();

// // 4.2 Call swap_public from user2 on behalf of owner
// const uniswapL2Interaction = await action.send().wait();

// const swapPublicContent = sha256ToField([
// Buffer.from(
// toFunctionSelector('swap_public(address,uint256,uint24,address,uint256,bytes32,bytes32,address)').substring(
// 2,
// ),
// 'hex',
// ),
// wethCrossChainHarness.tokenPortalAddress.toBuffer32(),
// new Fr(wethAmountToBridge),
// new Fr(uniswapFeeTier),
// daiCrossChainHarness.tokenPortalAddress.toBuffer32(),
// new Fr(minimumOutputAmount),
// ownerAddress,
// secretHashForDepositingSwappedDai,
// ownerEthAddress.toBuffer32(),
// ]);

// const swapPublicLeaf = sha256ToField([
// uniswapL2Contract.address,
// new Fr(1), // aztec version
// EthAddress.fromString(uniswapPortal.address).toBuffer32(),
// new Fr(publicClient.chain.id), // chain id
// swapPublicContent,
// ]);

// const withdrawContent = sha256ToField([
// Buffer.from(toFunctionSelector('withdraw(address,uint256,address)').substring(2), 'hex'),
// uniswapPortalAddress.toBuffer32(),
// new Fr(wethAmountToBridge),
// uniswapPortalAddress.toBuffer32(),
// ]);

// const withdrawLeaf = sha256ToField([
// wethCrossChainHarness.l2Bridge.address,
// new Fr(1), // aztec version
// wethCrossChainHarness.tokenPortalAddress.toBuffer32(),
// new Fr(publicClient.chain.id), // chain id
// withdrawContent,
// ]);

// // check weth balance of owner on L2 (we first bridged `wethAmountToBridge` into L2 and now withdrew it!)
// await wethCrossChainHarness.expectPublicBalanceOnL2(ownerAddress, wethL2BalanceBeforeSwap - wethAmountToBridge);

// // 5. Perform the swap on L1 with the `uniswapPortal.swap_private()` (consuming L2 to L1 messages)
// logger.info('Execute withdraw and swap on the uniswapPortal!');
// const daiL1BalanceOfPortalBeforeSwap = await daiCrossChainHarness.getL1BalanceOf(
// daiCrossChainHarness.tokenPortalAddress,
// );

// const [swapPrivateL2MessageIndex, swapPrivateSiblingPath] = await aztecNode.getL2ToL1MessageMembershipWitness(
// uniswapL2Interaction.blockNumber!,
// swapPublicLeaf,
// );
// const [withdrawL2MessageIndex, withdrawSiblingPath] = await aztecNode.getL2ToL1MessageMembershipWitness(
// uniswapL2Interaction.blockNumber!,
// withdrawLeaf,
// );

// const withdrawMessageMetadata = {
// _l2BlockNumber: BigInt(uniswapL2Interaction.blockNumber!),
// _leafIndex: BigInt(withdrawL2MessageIndex),
// _path: withdrawSiblingPath
// .toBufferArray()
// .map((buf: Buffer) => `0x${buf.toString('hex')}`) as readonly `0x${string}`[],
// };

// const swapPrivateMessageMetadata = {
// _l2BlockNumber: BigInt(uniswapL2Interaction.blockNumber!),
// _leafIndex: BigInt(swapPrivateL2MessageIndex),
// _path: swapPrivateSiblingPath
// .toBufferArray()
// .map((buf: Buffer) => `0x${buf.toString('hex')}`) as readonly `0x${string}`[],
// };

// const swapArgs = [
// wethCrossChainHarness.tokenPortalAddress.toString(),
// wethAmountToBridge,
// Number(uniswapFeeTier),
// daiCrossChainHarness.tokenPortalAddress.toString(),
// minimumOutputAmount,
// ownerAddress.toString(),
// secretHashForDepositingSwappedDai.toString(),
// true,
// [withdrawMessageMetadata, swapPrivateMessageMetadata],
// ] as const;

// // this should also insert a message into the inbox.
// const txHash = await uniswapPortal.write.swapPublic(swapArgs, {} as any);

// // We get the msg leaf from event so that we can later wait for it to be available for consumption
// let outTokenDepositMsgHash: Fr;
// {
// const txReceipt = await daiCrossChainHarness.publicClient.waitForTransactionReceipt({
// hash: txHash,
// });

// const txLog = txReceipt.logs[9];
// const topics = decodeEventLog({
// abi: InboxAbi,
// data: txLog.data,
// topics: txLog.topics,
// });
// outTokenDepositMsgHash = Fr.fromString(topics.args.hash);
// }

// // weth was swapped to dai and send to portal
// const daiL1BalanceOfPortalAfter = await daiCrossChainHarness.getL1BalanceOf(
// daiCrossChainHarness.tokenPortalAddress,
// );
// expect(daiL1BalanceOfPortalAfter).toBeGreaterThan(daiL1BalanceOfPortalBeforeSwap);
// const daiAmountToBridge = BigInt(daiL1BalanceOfPortalAfter - daiL1BalanceOfPortalBeforeSwap);

// // 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,
// outTokenDepositMessageLeafIndex,
// );
// await daiCrossChainHarness.expectPublicBalanceOnL2(ownerAddress, daiL2BalanceBeforeSwap + daiAmountToBridge);

// const wethL2BalanceAfterSwap = await wethCrossChainHarness.getL2PublicBalanceOf(ownerAddress);
// const daiL2BalanceAfterSwap = await daiCrossChainHarness.getL2PublicBalanceOf(ownerAddress);

// logger.info('WETH balance before swap: ', wethL2BalanceBeforeSwap.toString());
// logger.info('DAI balance before swap : ', daiL2BalanceBeforeSwap.toString());
// logger.info('***** 🧚‍♀️ SWAP L2 assets on L1 Uniswap 🧚‍♀️ *****');
// logger.info('WETH balance after swap : ', wethL2BalanceAfterSwap.toString());
// logger.info('DAI balance after swap : ', daiL2BalanceAfterSwap.toString());
// });
Source code: yarn-project/end-to-end/src/shared/uniswap_l1_l2.ts#L396-L629