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);

// 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#L172-L380

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#L382-L615