Skip to content

Commit

Permalink
refactor: portal managers in cli
Browse files Browse the repository at this point in the history
fixing build
  • Loading branch information
sklppy88 committed Aug 19, 2024
1 parent d745663 commit 8842a21
Show file tree
Hide file tree
Showing 5 changed files with 148 additions and 112 deletions.
16 changes: 8 additions & 8 deletions yarn-project/cli-wallet/src/cmds/bridge_fee_juice.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,23 +25,23 @@ export async function bridgeL1FeeJuice(
const client = await createCompatibleClient(rpcUrl, debugLogger);

// Setup portal manager
const portal = await FeeJuicePortalManager.create(client, publicClient, walletClient, debugLogger);
const { secret } = await portal.prepareTokensOnL1(amount, amount, recipient, mint);
const portal = await FeeJuicePortalManager.new(client, publicClient, walletClient, debugLogger);
const { claimAmount, claimSecret } = await portal.bridgeTokensPublic(recipient, amount, mint);

if (json) {
const out = {
claimAmount: amount,
claimSecret: secret,
claimAmount,
claimSecret,
};
log(prettyPrintJSON(out));
} else {
if (mint) {
log(`Minted ${amount} fee juice on L1 and pushed to L2 portal`);
log(`Minted ${claimAmount} fee juice on L1 and pushed to L2 portal`);
} else {
log(`Bridged ${amount} fee juice to L2 portal`);
log(`Bridged ${claimAmount} fee juice to L2 portal`);
}
log(`claimAmount=${amount},claimSecret=${secret}\n`);
log(`claimAmount=${claimAmount},claimSecret=${claimSecret}\n`);
log(`Note: You need to wait for two L2 blocks before pulling them from the L2 side`);
}
return secret;
return claimSecret;
}
6 changes: 3 additions & 3 deletions yarn-project/cli/src/cmds/devnet/bootstrap_network.ts
Original file line number Diff line number Diff line change
Expand Up @@ -243,15 +243,15 @@ async function fundFPC(

const feeJuiceContract = await FeeJuiceContract.at(feeJuice, wallet);

const feeJuicePortal = await FeeJuicePortalManager.create(
const feeJuicePortal = await FeeJuicePortalManager.new(
wallet,
l1Clients.publicClient,
l1Clients.walletClient,
debugLog,
);

const amount = 10n ** 21n;
const { secret } = await feeJuicePortal.prepareTokensOnL1(amount, amount, fpcAddress, true);
const { claimAmount, claimSecret } = await feeJuicePortal.bridgeTokensPublic(fpcAddress, amount, true);

const counter = await CounterContract.at(counterAddress, wallet);

Expand All @@ -260,5 +260,5 @@ async function fundFPC(
await counter.methods.increment(wallet.getAddress(), wallet.getAddress()).send().wait();
await counter.methods.increment(wallet.getAddress(), wallet.getAddress()).send().wait();

await feeJuiceContract.methods.claim(fpcAddress, amount, secret).send().wait();
await feeJuiceContract.methods.claim(fpcAddress, claimAmount, claimSecret).send().wait();
}
18 changes: 12 additions & 6 deletions yarn-project/cli/src/cmds/l1/bridge_erc20.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { type AztecAddress, type EthAddress } from '@aztec/circuits.js';
import { type AztecAddress, type EthAddress, type Fr } from '@aztec/circuits.js';
import { createEthereumChain, createL1Clients } from '@aztec/ethereum';
import { type DebugLogger, type LogFn } from '@aztec/foundation/log';

import { prettyPrintJSON } from '../../utils/commands.js';
import { ERC20PortalManager } from '../../utils/portal_manager.js';
import { L1PortalManager } from '../../utils/portal_manager.js';

export async function bridgeERC20(
amount: bigint,
Expand All @@ -14,6 +14,7 @@ export async function bridgeERC20(
mnemonic: string,
tokenAddress: EthAddress,
portalAddress: EthAddress,
privateTransfer: boolean,
mint: boolean,
json: boolean,
log: LogFn,
Expand All @@ -24,14 +25,19 @@ export async function bridgeERC20(
const { publicClient, walletClient } = createL1Clients(chain.rpcUrl, privateKey ?? mnemonic, chain.chainInfo);

// Setup portal manager
const portal = await ERC20PortalManager.create(tokenAddress, portalAddress, publicClient, walletClient, debugLogger);
const { secret } = await portal.prepareTokensOnL1(amount, amount, recipient, mint);
const manager = new L1PortalManager(portalAddress, tokenAddress, publicClient, walletClient, debugLogger);
let claimSecret: Fr;
if (privateTransfer) {
({ claimSecret } = await manager.bridgeTokensPrivate(recipient, amount, mint));
} else {
({ claimSecret } = await manager.bridgeTokensPublic(recipient, amount, mint));
}

if (json) {
log(
prettyPrintJSON({
claimAmount: amount,
claimSecret: secret,
claimSecret: claimSecret,
}),
);
} else {
Expand All @@ -40,7 +46,7 @@ export async function bridgeERC20(
} else {
log(`Bridged ${amount} tokens to L2 portal`);
}
log(`claimAmount=${amount},claimSecret=${secret}\n`);
log(`claimAmount=${amount},claimSecret=${claimSecret}\n`);
log(`Note: You need to wait for two L2 blocks before pulling them from the L2 side`);
}
}
2 changes: 2 additions & 0 deletions yarn-project/cli/src/cmds/l1/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,7 @@ export function injectCommands(program: Command, log: LogFn, debugLogger: DebugL
'test test test test test test test test test test test junk',
)
.option('--mint', 'Mint the tokens on L1', false)
.option('--private', 'If the bridge should use the private flow', false)
.addOption(l1ChainIdOption)
.requiredOption('-t, --token <string>', 'The address of the token to bridge', parseEthereumAddress)
.requiredOption('-p, --portal <string>', 'The address of the portal contract', parseEthereumAddress)
Expand All @@ -117,6 +118,7 @@ export function injectCommands(program: Command, log: LogFn, debugLogger: DebugL
options.mnemonic,
options.token,
options.portal,
options.private,
options.mint,
options.json,
log,
Expand Down
218 changes: 123 additions & 95 deletions yarn-project/cli/src/utils/portal_manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,106 +14,107 @@ import {
getContract,
} from 'viem';

/**
* A Class for testing cross chain interactions, contains common interactions
* shared between cross chain tests.
*/
abstract class PortalManager {
protected constructor(
/** Underlying token for portal tests. */
public underlyingERC20Address: EthAddress,
/** Portal address. */
public tokenPortalAddress: EthAddress,
public publicClient: PublicClient<HttpTransport, Chain>,
public walletClient: WalletClient<HttpTransport, Chain, Account>,
/** Logger. */
public logger: DebugLogger,
) {}

generateClaimSecret(): [Fr, Fr] {
this.logger.debug("Generating a claim secret using pedersen's hash function");
const secret = Fr.random();
const secretHash = computeSecretHash(secret);
this.logger.info('Generated claim secret: ' + secretHash.toString());
return [secret, secretHash];
}
export enum TransferType {
PRIVATE,
PUBLIC,
}

export interface L2Claim {
claimSecret: Fr;
claimAmount: Fr;
}

function stringifyEthAddress(address: EthAddress | Hex, name?: string) {
return name ? `${name} (${address.toString()})` : address.toString();
}

getERC20Contract(): GetContractReturnType<typeof PortalERC20Abi, WalletClient<HttpTransport, Chain, Account>> {
return getContract({
address: this.underlyingERC20Address.toString(),
function generateClaimSecret(): [Fr, Fr] {
const secret = Fr.random();
const secretHash = computeSecretHash(secret);
return [secret, secretHash];
}

class L1TokenManager {
private contract: GetContractReturnType<typeof PortalERC20Abi, WalletClient<HttpTransport, Chain, Account>>;

public constructor(
public readonly address: EthAddress,
private publicClient: PublicClient<HttpTransport, Chain>,
private walletClient: WalletClient<HttpTransport, Chain, Account>,
private logger: DebugLogger,
) {
this.contract = getContract({
address: this.address.toString(),
abi: PortalERC20Abi,
client: this.walletClient,
});
}

async mintTokensOnL1(amount: bigint) {
this.logger.info(
`Minting tokens on L1 for ${this.walletClient.account.address} in contract ${this.underlyingERC20Address}`,
);
await this.publicClient.waitForTransactionReceipt({
hash: await this.getERC20Contract().write.mint([this.walletClient.account.address, amount]),
});
}

async getL1TokenBalance(address: EthAddress) {
return await this.getERC20Contract().read.balanceOf([address.toString()]);
public async getL1TokenBalance(address: Hex) {
return await this.contract.read.balanceOf([address]);
}

protected async sendTokensToPortalPublic(bridgeAmount: bigint, l2Address: AztecAddress, secretHash: Fr) {
this.logger.info(`Approving erc20 tokens for the TokenPortal at ${this.tokenPortalAddress.toString()}`);
public async mint(amount: bigint, address: Hex, addressName = '') {
this.logger.info(`Minting ${amount} tokens for ${stringifyEthAddress(address, addressName)}`);
await this.publicClient.waitForTransactionReceipt({
hash: await this.getERC20Contract().write.approve([this.tokenPortalAddress.toString(), bridgeAmount]),
hash: await this.contract.write.mint([address, amount]),
});

const messageHash = await this.bridgeTokens(l2Address, bridgeAmount, secretHash);
return Fr.fromString(messageHash);
}

protected abstract bridgeTokens(to: AztecAddress, amount: bigint, secretHash: Fr): Promise<Hex>;

async prepareTokensOnL1(l1TokenBalance: bigint, bridgeAmount: bigint, owner: AztecAddress, mint = true) {
const [secret, secretHash] = this.generateClaimSecret();

// Mint tokens on L1
if (mint) {
await this.mintTokensOnL1(l1TokenBalance);
}

// Deposit tokens to the TokenPortal
const msgHash = await this.sendTokensToPortalPublic(bridgeAmount, owner, secretHash);

return { secret, msgHash, secretHash };
public async approve(amount: bigint, address: Hex, addressName = '') {
this.logger.info(`Approving ${amount} tokens for ${stringifyEthAddress(address, addressName)}`);
await this.publicClient.waitForTransactionReceipt({
hash: await this.contract.write.approve([address, amount]),
});
}
}

export class FeeJuicePortalManager extends PortalManager {
async bridgeTokens(to: AztecAddress, amount: bigint, secretHash: Fr): Promise<Hex> {
const portal = getContract({
address: this.tokenPortalAddress.toString(),
export class FeeJuicePortalManager {
tokenManager: L1TokenManager;
contract: GetContractReturnType<typeof FeeJuicePortalAbi, WalletClient<HttpTransport, Chain, Account>>;

constructor(
portalAddress: EthAddress,
tokenAddress: EthAddress,
private publicClient: PublicClient<HttpTransport, Chain>,
private walletClient: WalletClient<HttpTransport, Chain, Account>,
/** Logger. */
private logger: DebugLogger,
) {
this.tokenManager = new L1TokenManager(tokenAddress, publicClient, walletClient, logger);
this.contract = getContract({
address: portalAddress.toString(),
abi: FeeJuicePortalAbi,
client: this.walletClient,
});
}

this.logger.info(
`Simulating token portal deposit configured for token ${await portal.read.l2TokenAddress()} with registry ${await portal.read.registry()} to retrieve message hash`,
);
public async bridgeTokensPublic(to: AztecAddress, amount: bigint, mint = false): Promise<L2Claim> {
const [claimSecret, claimSecretHash] = generateClaimSecret();
if (mint) {
await this.tokenManager.mint(amount, this.walletClient.account.address);
}

const args = [to.toString(), amount, secretHash.toString()] as const;
const { result: messageHash } = await portal.simulate.depositToAztecPublic(args);
this.logger.info('Sending messages to L1 portal to be consumed publicly');
await this.tokenManager.approve(amount, this.contract.address, 'FeeJuice Portal');

this.logger.info('Sending L1 Fee Juice to L2 to be claimed publicly');
const args = [to.toString(), amount, claimSecretHash.toString()] as const;
await this.publicClient.waitForTransactionReceipt({
hash: await portal.write.depositToAztecPublic(args),
hash: await this.contract.write.depositToAztecPublic(args),
});
return messageHash;

return {
claimAmount: new Fr(amount),
claimSecret,
};
}

public static async create(
public static async new(
pxe: PXE,
publicClient: PublicClient<HttpTransport, Chain>,
walletClient: WalletClient<HttpTransport, Chain, Account>,
logger: DebugLogger,
): Promise<PortalManager> {
): Promise<FeeJuicePortalManager> {
const {
l1ContractAddresses: { feeJuiceAddress, feeJuicePortalAddress },
} = await pxe.getNodeInfo();
Expand All @@ -122,39 +123,66 @@ export class FeeJuicePortalManager extends PortalManager {
throw new Error('Portal or token not deployed on L1');
}

return new FeeJuicePortalManager(feeJuiceAddress, feeJuicePortalAddress, publicClient, walletClient, logger);
return new FeeJuicePortalManager(feeJuicePortalAddress, feeJuiceAddress, publicClient, walletClient, logger);
}
}

export class ERC20PortalManager extends PortalManager {
async bridgeTokens(to: AztecAddress, amount: bigint, secretHash: Fr): Promise<Hex> {
const portal = getContract({
address: this.tokenPortalAddress.toString(),
export class L1PortalManager {
contract: GetContractReturnType<typeof TokenPortalAbi, WalletClient<HttpTransport, Chain, Account>>;
private tokenManager: L1TokenManager;

constructor(
portalAddress: EthAddress,
tokenAddress: EthAddress,
private publicClient: PublicClient<HttpTransport, Chain>,
private walletClient: WalletClient<HttpTransport, Chain, Account>,
private logger: DebugLogger,
) {
this.tokenManager = new L1TokenManager(tokenAddress, publicClient, walletClient, logger);
this.contract = getContract({
address: portalAddress.toString(),
abi: TokenPortalAbi,
client: this.walletClient,
});
}

this.logger.info(
`Simulating token portal deposit configured for token ${await portal.read.l2Bridge()} with registry ${await portal.read.registry()} to retrieve message hash`,
);

const args = [to.toString(), amount, secretHash.toString()] as const;
const { result: messageHash } = await portal.simulate.depositToAztecPublic(args);
this.logger.info('Sending messages to L1 portal to be consumed publicly');
public bridgeTokensPublic(to: AztecAddress, amount: bigint, mint = false): Promise<L2Claim> {
return this.bridgeTokens(to, amount, mint, /* privateTransfer */ false);
}

await this.publicClient.waitForTransactionReceipt({
hash: await portal.write.depositToAztecPublic(args),
});
return messageHash;
public bridgeTokensPrivate(to: AztecAddress, amount: bigint, mint = false): Promise<L2Claim> {
return this.bridgeTokens(to, amount, mint, /* privateTransfer */ true);
}

public static create(
tokenAddress: EthAddress,
portalAddress: EthAddress,
publicClient: PublicClient<HttpTransport, Chain>,
walletClient: WalletClient<HttpTransport, Chain, Account>,
logger: DebugLogger,
): Promise<ERC20PortalManager> {
return Promise.resolve(new ERC20PortalManager(tokenAddress, portalAddress, publicClient, walletClient, logger));
private async bridgeTokens(
to: AztecAddress,
amount: bigint,
mint: boolean,
privateTransfer: boolean,
): Promise<L2Claim> {
const [claimSecret, claimSecretHash] = generateClaimSecret();

if (mint) {
await this.tokenManager.mint(amount, this.walletClient.account.address);
}

await this.tokenManager.approve(amount, this.contract.address, 'TokenPortal');

if (privateTransfer) {
this.logger.info('Sending L1 tokens to L2 to be claimed privately');
await this.publicClient.waitForTransactionReceipt({
hash: await this.contract.write.depositToAztecPrivate([Fr.ZERO.toString(), amount, claimSecretHash.toString()]),
});
} else {
this.logger.info('Sending L1 tokens to L2 to be claimed publicly');
await this.publicClient.waitForTransactionReceipt({
hash: await this.contract.write.depositToAztecPublic([to.toString(), amount, claimSecretHash.toString()]),
});
}

return {
claimAmount: new Fr(amount),
claimSecret,
};
}
}

0 comments on commit 8842a21

Please sign in to comment.