Skip to content

Commit 0898efc

Browse files
alexghrsklppy88
authored and
sklppy88
committed
refactor: portal managers in cli
1 parent ec5a5fb commit 0898efc

File tree

4 files changed

+159
-101
lines changed

4 files changed

+159
-101
lines changed

noir-projects/noir-contracts/contracts/token_bridge_contract/src/main.nr

+16
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,22 @@ contract TokenBridge {
102102
}
103103
// docs:end:claim_private
104104

105+
#[aztec(private)]
106+
fn claim_private_for_caller(
107+
amount: Field,
108+
secret_for_L1_to_L2_message_consumption: Field // secret used to consume the L1 to L2 message
109+
) {
110+
// Consume L1 to L2 message and emit nullifier
111+
let content_hash = get_mint_private_content_hash(0, amount);
112+
context.consume_l1_to_l2_message(
113+
content_hash,
114+
secret_for_L1_to_L2_message_consumption,
115+
storage.portal_address.read_private()
116+
);
117+
118+
Token::at(storage.token.read_private()).mint_private_for(context.msg_sender(), amount).call(&mut context);
119+
}
120+
105121
// docs:start:exit_to_l1_private
106122
// Burns the appropriate amount of tokens and creates a L2 to L1 withdraw message privately
107123
// Requires `msg.sender` (caller of the method) to give approval to the bridge to burn tokens on their behalf using witness signatures

noir-projects/noir-contracts/contracts/token_contract/src/main.nr

+7
Original file line numberDiff line numberDiff line change
@@ -221,6 +221,13 @@ contract Token {
221221
}
222222
// docs:end:mint_private
223223

224+
#[aztec(private)]
225+
fn mint_private_for(to: AztecAddress, amount: Field) {
226+
let minter = context.msg_sender();
227+
storage.balances.add(to, U128::from_integer(amount)).emit(encode_and_encrypt_note(&mut context, minter, to));
228+
Token::at(context.this_address()).assert_minter_and_mint(minter, amount).enqueue(&mut context);
229+
}
230+
224231
// TODO: Nuke this - test functions do not belong to token contract!
225232
#[aztec(private)]
226233
fn privately_mint_private_note(amount: Field) {

yarn-project/cli/src/cmds/l1/bridge_erc20.ts

+13-6
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
1-
import { type AztecAddress, type EthAddress } from '@aztec/circuits.js';
1+
import { type AztecAddress, type EthAddress, type Fr } from '@aztec/circuits.js';
22
import { createEthereumChain, createL1Clients } from '@aztec/ethereum';
33
import { type DebugLogger, type LogFn } from '@aztec/foundation/log';
44

55
import { prettyPrintJSON } from '../../utils/commands.js';
6-
import { ERC20PortalManager } from '../../utils/portal_manager.js';
6+
import { L1PortalManager } from '../../utils/portal_manager.js';
77

88
export async function bridgeERC20(
99
amount: bigint,
@@ -14,6 +14,7 @@ export async function bridgeERC20(
1414
mnemonic: string,
1515
tokenAddress: EthAddress,
1616
portalAddress: EthAddress,
17+
privateTransfer: boolean,
1718
mint: boolean,
1819
json: boolean,
1920
log: LogFn,
@@ -24,14 +25,20 @@ export async function bridgeERC20(
2425
const { publicClient, walletClient } = createL1Clients(chain.rpcUrl, privateKey ?? mnemonic, chain.chainInfo);
2526

2627
// Setup portal manager
27-
const portal = await ERC20PortalManager.create(tokenAddress, portalAddress, publicClient, walletClient, debugLogger);
28-
const { secret } = await portal.prepareTokensOnL1(amount, amount, recipient, mint);
28+
// const portal = await ERC20PortalManager.create(tokenAddress, portalAddress, publicClient, walletClient, debugLogger);
29+
const manager = new L1PortalManager(tokenAddress, portalAddress, publicClient, walletClient, debugLogger);
30+
let claimSecret: Fr;
31+
if (privateTransfer) {
32+
({ claimSecret } = await manager.bridgeTokensPrivate(recipient, amount, mint));
33+
} else {
34+
({ claimSecret } = await manager.bridgeTokensPublic(recipient, amount, mint));
35+
}
2936

3037
if (json) {
3138
log(
3239
prettyPrintJSON({
3340
claimAmount: amount,
34-
claimSecret: secret,
41+
claimSecret: claimSecret,
3542
}),
3643
);
3744
} else {
@@ -40,7 +47,7 @@ export async function bridgeERC20(
4047
} else {
4148
log(`Bridged ${amount} tokens to L2 portal`);
4249
}
43-
log(`claimAmount=${amount},claimSecret=${secret}\n`);
50+
log(`claimAmount=${amount},claimSecret=${claimSecret}\n`);
4451
log(`Note: You need to wait for two L2 blocks before pulling them from the L2 side`);
4552
}
4653
}

yarn-project/cli/src/utils/portal_manager.ts

+123-95
Original file line numberDiff line numberDiff line change
@@ -14,106 +14,107 @@ import {
1414
getContract,
1515
} from 'viem';
1616

17-
/**
18-
* A Class for testing cross chain interactions, contains common interactions
19-
* shared between cross chain tests.
20-
*/
21-
abstract class PortalManager {
22-
protected constructor(
23-
/** Underlying token for portal tests. */
24-
public underlyingERC20Address: EthAddress,
25-
/** Portal address. */
26-
public tokenPortalAddress: EthAddress,
27-
public publicClient: PublicClient<HttpTransport, Chain>,
28-
public walletClient: WalletClient<HttpTransport, Chain, Account>,
29-
/** Logger. */
30-
public logger: DebugLogger,
31-
) {}
32-
33-
generateClaimSecret(): [Fr, Fr] {
34-
this.logger.debug("Generating a claim secret using pedersen's hash function");
35-
const secret = Fr.random();
36-
const secretHash = computeSecretHash(secret);
37-
this.logger.info('Generated claim secret: ' + secretHash.toString());
38-
return [secret, secretHash];
39-
}
17+
export enum TransferType {
18+
PRIVATE,
19+
PUBLIC,
20+
}
21+
22+
export interface L2Claim {
23+
claimSecret: Fr;
24+
claimAmount: Fr;
25+
}
26+
27+
function stringifyEthAddress(address: EthAddress | Hex, name?: string) {
28+
return name ? `${name} (${address.toString()})` : address.toString();
29+
}
4030

41-
getERC20Contract(): GetContractReturnType<typeof PortalERC20Abi, WalletClient<HttpTransport, Chain, Account>> {
42-
return getContract({
43-
address: this.underlyingERC20Address.toString(),
31+
function generateClaimSecret(): [Fr, Fr] {
32+
const secret = Fr.random();
33+
const secretHash = computeSecretHash(secret);
34+
return [secret, secretHash];
35+
}
36+
37+
class L1TokenManager {
38+
private contract: GetContractReturnType<typeof PortalERC20Abi, WalletClient<HttpTransport, Chain, Account>>;
39+
40+
public constructor(
41+
public readonly address: EthAddress,
42+
private publicClient: PublicClient<HttpTransport, Chain>,
43+
private walletClient: WalletClient<HttpTransport, Chain, Account>,
44+
private logger: DebugLogger,
45+
) {
46+
this.contract = getContract({
47+
address: this.address.toString(),
4448
abi: PortalERC20Abi,
4549
client: this.walletClient,
4650
});
4751
}
4852

49-
async mintTokensOnL1(amount: bigint) {
50-
this.logger.info(
51-
`Minting tokens on L1 for ${this.walletClient.account.address} in contract ${this.underlyingERC20Address}`,
52-
);
53-
await this.publicClient.waitForTransactionReceipt({
54-
hash: await this.getERC20Contract().write.mint([this.walletClient.account.address, amount]),
55-
});
56-
}
57-
58-
async getL1TokenBalance(address: EthAddress) {
59-
return await this.getERC20Contract().read.balanceOf([address.toString()]);
53+
public async getL1TokenBalance(address: Hex) {
54+
return await this.contract.read.balanceOf([address]);
6055
}
6156

62-
protected async sendTokensToPortalPublic(bridgeAmount: bigint, l2Address: AztecAddress, secretHash: Fr) {
63-
this.logger.info(`Approving erc20 tokens for the TokenPortal at ${this.tokenPortalAddress.toString()}`);
57+
public async mint(amount: bigint, address: Hex, addressName = '') {
58+
this.logger.info(`Minting ${amount} tokens for ${stringifyEthAddress(address, addressName)}`);
6459
await this.publicClient.waitForTransactionReceipt({
65-
hash: await this.getERC20Contract().write.approve([this.tokenPortalAddress.toString(), bridgeAmount]),
60+
hash: await this.contract.write.mint([address, amount]),
6661
});
67-
68-
const messageHash = await this.bridgeTokens(l2Address, bridgeAmount, secretHash);
69-
return Fr.fromString(messageHash);
7062
}
7163

72-
protected abstract bridgeTokens(to: AztecAddress, amount: bigint, secretHash: Fr): Promise<Hex>;
73-
74-
async prepareTokensOnL1(l1TokenBalance: bigint, bridgeAmount: bigint, owner: AztecAddress, mint = true) {
75-
const [secret, secretHash] = this.generateClaimSecret();
76-
77-
// Mint tokens on L1
78-
if (mint) {
79-
await this.mintTokensOnL1(l1TokenBalance);
80-
}
81-
82-
// Deposit tokens to the TokenPortal
83-
const msgHash = await this.sendTokensToPortalPublic(bridgeAmount, owner, secretHash);
84-
85-
return { secret, msgHash, secretHash };
64+
public async approve(amount: bigint, address: Hex, addressName = '') {
65+
this.logger.info(`Minting ${amount} tokens for ${stringifyEthAddress(address, addressName)}`);
66+
await this.publicClient.waitForTransactionReceipt({
67+
hash: await this.contract.write.approve([address, amount]),
68+
});
8669
}
8770
}
8871

89-
export class FeeJuicePortalManager extends PortalManager {
90-
async bridgeTokens(to: AztecAddress, amount: bigint, secretHash: Fr): Promise<Hex> {
91-
const portal = getContract({
92-
address: this.tokenPortalAddress.toString(),
72+
export class FeeJuicePortalManager {
73+
tokenManager: L1TokenManager;
74+
contract: GetContractReturnType<typeof FeeJuicePortalAbi, WalletClient<HttpTransport, Chain, Account>>;
75+
76+
constructor(
77+
portalAddress: EthAddress,
78+
tokenAddress: EthAddress,
79+
private publicClient: PublicClient<HttpTransport, Chain>,
80+
private walletClient: WalletClient<HttpTransport, Chain, Account>,
81+
/** Logger. */
82+
private logger: DebugLogger,
83+
) {
84+
this.tokenManager = new L1TokenManager(tokenAddress, publicClient, walletClient, logger);
85+
this.contract = getContract({
86+
address: portalAddress.toString(),
9387
abi: FeeJuicePortalAbi,
9488
client: this.walletClient,
9589
});
90+
}
9691

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

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

100+
this.logger.info('Sending L1 Fee Juice to L2 to be claimed publicly');
101+
const args = [to.toString(), amount, claimSecretHash.toString()] as const;
105102
await this.publicClient.waitForTransactionReceipt({
106-
hash: await portal.write.depositToAztecPublic(args),
103+
hash: await this.contract.write.depositToAztecPublic(args),
107104
});
108-
return messageHash;
105+
106+
return {
107+
claimAmount: new Fr(amount),
108+
claimSecret,
109+
};
109110
}
110111

111-
public static async create(
112+
public static async new(
112113
pxe: PXE,
113114
publicClient: PublicClient<HttpTransport, Chain>,
114115
walletClient: WalletClient<HttpTransport, Chain, Account>,
115116
logger: DebugLogger,
116-
): Promise<PortalManager> {
117+
): Promise<FeeJuicePortalManager> {
117118
const {
118119
l1ContractAddresses: { feeJuiceAddress, feeJuicePortalAddress },
119120
} = await pxe.getNodeInfo();
@@ -122,39 +123,66 @@ export class FeeJuicePortalManager extends PortalManager {
122123
throw new Error('Portal or token not deployed on L1');
123124
}
124125

125-
return new FeeJuicePortalManager(feeJuiceAddress, feeJuicePortalAddress, publicClient, walletClient, logger);
126+
return new FeeJuicePortalManager(feeJuicePortalAddress, feeJuicePortalAddress, publicClient, walletClient, logger);
126127
}
127128
}
128129

129-
export class ERC20PortalManager extends PortalManager {
130-
async bridgeTokens(to: AztecAddress, amount: bigint, secretHash: Fr): Promise<Hex> {
131-
const portal = getContract({
132-
address: this.tokenPortalAddress.toString(),
130+
export class L1PortalManager {
131+
contract: GetContractReturnType<typeof TokenPortalAbi, WalletClient<HttpTransport, Chain, Account>>;
132+
private tokenManager: L1TokenManager;
133+
134+
constructor(
135+
portalAddress: EthAddress,
136+
tokenAddress: EthAddress,
137+
private publicClient: PublicClient<HttpTransport, Chain>,
138+
private walletClient: WalletClient<HttpTransport, Chain, Account>,
139+
private logger: DebugLogger,
140+
) {
141+
this.tokenManager = new L1TokenManager(tokenAddress, publicClient, walletClient, logger);
142+
this.contract = getContract({
143+
address: portalAddress.toString(),
133144
abi: TokenPortalAbi,
134145
client: this.walletClient,
135146
});
147+
}
136148

137-
this.logger.info(
138-
`Simulating token portal deposit configured for token ${await portal.read.l2Bridge()} with registry ${await portal.read.registry()} to retrieve message hash`,
139-
);
140-
141-
const args = [to.toString(), amount, secretHash.toString()] as const;
142-
const { result: messageHash } = await portal.simulate.depositToAztecPublic(args);
143-
this.logger.info('Sending messages to L1 portal to be consumed publicly');
149+
public bridgeTokensPublic(to: AztecAddress, amount: bigint, mint = false): Promise<L2Claim> {
150+
return this.bridgeTokens(to, amount, mint, /* privateTransfer */ false);
151+
}
144152

145-
await this.publicClient.waitForTransactionReceipt({
146-
hash: await portal.write.depositToAztecPublic(args),
147-
});
148-
return messageHash;
153+
public bridgeTokensPrivate(to: AztecAddress, amount: bigint, mint = false): Promise<L2Claim> {
154+
return this.bridgeTokens(to, amount, mint, /* privateTransfer */ true);
149155
}
150156

151-
public static create(
152-
tokenAddress: EthAddress,
153-
portalAddress: EthAddress,
154-
publicClient: PublicClient<HttpTransport, Chain>,
155-
walletClient: WalletClient<HttpTransport, Chain, Account>,
156-
logger: DebugLogger,
157-
): Promise<ERC20PortalManager> {
158-
return Promise.resolve(new ERC20PortalManager(tokenAddress, portalAddress, publicClient, walletClient, logger));
157+
private async bridgeTokens(
158+
to: AztecAddress,
159+
amount: bigint,
160+
mint: boolean,
161+
privateTransfer: boolean,
162+
): Promise<L2Claim> {
163+
const [claimSecret, claimSecretHash] = generateClaimSecret();
164+
165+
if (mint) {
166+
await this.tokenManager.mint(amount, this.walletClient.account.address);
167+
}
168+
169+
await this.tokenManager.approve(amount, this.contract.address, 'TokenPortal');
170+
171+
if (privateTransfer) {
172+
this.logger.info('Sending L1 tokens to L2 to be claimed privately');
173+
await this.publicClient.waitForTransactionReceipt({
174+
hash: await this.contract.write.depositToAztecPrivate([Fr.ZERO.toString(), amount, claimSecretHash.toString()]),
175+
});
176+
} else {
177+
this.logger.info('Sending L1 tokens to L2 to be claimed publicly');
178+
await this.publicClient.waitForTransactionReceipt({
179+
hash: await this.contract.write.depositToAztecPublic([to.toString(), amount, claimSecretHash.toString()]),
180+
});
181+
}
182+
183+
return {
184+
claimAmount: new Fr(amount),
185+
claimSecret,
186+
};
159187
}
160188
}

0 commit comments

Comments
 (0)