Skip to content

Commit 03f397c

Browse files
committed
Merge branch 'testnet' into josh/testnet-version-update
# Conflicts: # .github/workflows/devnet.yaml
2 parents 9cfe2b1 + 926c8e7 commit 03f397c

12 files changed

Lines changed: 172 additions & 51 deletions

.claude/commands/update-version.md

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ Read these files to understand current state:
3636
- `package.json`
3737
- `config/local-network.json`
3838
- `config/devnet.json`
39+
- `config/testnet.json`
3940
- `README.md`
4041
- `CLAUDE.md`
4142

@@ -54,6 +55,10 @@ aztec = { git = "https://github.com/AztecProtocol/aztec-nr/", tag = "v<VERSION>"
5455

5556
**4. `config/devnet.json`** — Update `settings.version` to the new npm version.
5657

58+
**4b. `config/testnet.json`** — Update `settings.version` to the new npm version.
59+
60+
> **Note:** Only update the config files relevant to the current branch. On `next`, update `local-network.json` and `devnet.json`. On `testnet`, update `local-network.json` and `testnet.json`. Leave other branch configs as-is.
61+
5762
**5. `README.md`** — Update the install command version in the Getting Started section:
5863
```bash
5964
export VERSION=<NEW_VERSION>
@@ -173,7 +178,15 @@ Based on the API research from Step 7 and any changes revealed by `yarn codegen`
173178

174179
### Step 11: Run Noir TXE tests
175180

176-
Run `yarn test:nr` to execute the Noir unit tests in the TXE simulator (no network needed).
181+
Run the Noir unit tests in the TXE simulator (no network needed).
182+
183+
**Known issue:** `yarn test:nr` (which runs `aztec test`) may hang due to a wrapper script issue. If it hangs for more than 30 seconds with no output, use this workaround:
184+
185+
1. Kill any process on port 8081: `lsof -ti:8081 | xargs kill -9 2>/dev/null`
186+
2. Start the TXE server manually: `aztec start --txe --port 8081 &`
187+
3. Wait a few seconds for it to start
188+
4. Run nargo test directly: `nargo test --silence-warnings --oracle-resolver http://localhost:8081`
189+
5. Kill the TXE server when done
177190

178191
If tests fail, diagnose and fix the contract test code. Iterate until tests pass.
179192

.github/workflows/local-network.yaml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,13 @@ on:
99
branches:
1010
- next
1111
- dev
12+
- testnet
1213
workflow_dispatch:
1314

15+
concurrency:
16+
group: local-network-${{ github.ref }}
17+
cancel-in-progress: true
18+
1419
jobs:
1520
local-network-tests:
1621
name: Local Network Tests
@@ -23,6 +28,9 @@ jobs:
2328
- name: Checkout repository
2429
uses: actions/checkout@v5
2530

31+
- name: Read Aztec version from config
32+
run: echo "AZTEC_VERSION=$(jq -r '.settings.version' config/local-network.json)" >> $GITHUB_ENV
33+
2634
- name: Set up Node.js
2735
uses: actions/setup-node@v4
2836
with:

jest.integration.config.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"preset": "ts-jest/presets/default-esm",
33
"transform": {
4-
"^.+\\.tsx?$": ["ts-jest", { "useESM": true }]
4+
"^.+\\.tsx?$": ["ts-jest", { "useESM": true, "diagnostics": false }]
55
},
66
"moduleNameMapper": {
77
"^(\\.{1,2}/.*)\\.js$": "$1"

scripts/fees.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -140,7 +140,7 @@ async function main() {
140140
from: account2.address
141141
});
142142

143-
logger.info(`BananaCoin balance of newWallet is ${bananaBalance}`)
143+
logger.info(`BananaCoin balance of newWallet is ${bananaBalanceResult.result ?? bananaBalanceResult}`)
144144

145145
const feeJuiceInstance = await getCanonicalFeeJuice();
146146
await wallet.registerContract(feeJuiceInstance.instance, FeeJuiceContract.artifact);

scripts/interaction_existing_contract.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import { setupWallet } from "../src/utils/setup_wallet.js";
88
import { getSponsoredFPCInstance } from "../src/utils/sponsored_fpc.js";
99
import { getAccountFromEnv } from "../src/utils/create_account_from_env.js";
1010
import { getTimeouts } from "../config/config.js";
11-
import { getContractInstanceFromInstantiationParams } from "@aztec/aztec.js/contracts";
11+
import { getContractInstanceFromInstantiationParams } from "@aztec/stdlib/contract";
1212

1313
async function main() {
1414
let logger: Logger;

scripts/multiple_wallet.ts

Lines changed: 4 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import { Fr } from "@aztec/aztec.js/fields";
22
import { GrumpkinScalar } from "@aztec/foundation/curves/grumpkin";
3-
import { getContractInstanceFromInstantiationParams, type ContractInstanceWithAddress } from "@aztec/aztec.js/contracts";
43
import { AztecAddress } from "@aztec/aztec.js/addresses";
54
import { NO_FROM } from "@aztec/aztec.js/account";
65
import { SponsoredFeePaymentMethod } from "@aztec/aztec.js/fee";
@@ -20,22 +19,6 @@ const walletOpts = {
2019

2120
const L2_TOKEN_CONTRACT_SALT = Fr.random();
2221

23-
export async function getL2TokenContractInstance(deployerAddress: any, ownerAztecAddress: AztecAddress): Promise<ContractInstanceWithAddress> {
24-
return await getContractInstanceFromInstantiationParams(
25-
TokenContract.artifact,
26-
{
27-
salt: L2_TOKEN_CONTRACT_SALT,
28-
deployer: deployerAddress,
29-
constructorArgs: [
30-
ownerAztecAddress,
31-
'Clean USDC',
32-
'USDC',
33-
6
34-
]
35-
}
36-
)
37-
}
38-
3922
async function main() {
4023
// docs:start:multiple-wallets
4124
const wallet1 = await EmbeddedWallet.create(node, walletOpts);
@@ -64,8 +47,9 @@ async function main() {
6447
from: ownerAddress,
6548
contractAddressSalt: L2_TOKEN_CONTRACT_SALT,
6649
fee: { paymentMethod },
67-
wait: { timeout: timeouts.deployTimeout }
50+
wait: { timeout: timeouts.deployTimeout, returnReceipt: true }
6851
});
52+
const token = receipt.contract;
6953

7054
// setup account on 2nd pxe
7155

@@ -91,7 +75,6 @@ async function main() {
9175
fee: { paymentMethod },
9276
wait: { timeout: timeouts.txTimeout }
9377
});
94-
console.log(await node.getTxEffect(private_mint_tx.txHash))
9578

9679
await token.methods.mint_to_public(schnorrAccount2.address, 100).simulate({ from: ownerAddress });
9780
await token.methods.mint_to_public(schnorrAccount2.address, 100).send({
@@ -117,12 +100,12 @@ async function main() {
117100
const { result: balance } = await l2TokenContract.methods.balance_of_private(wallet2Address).simulate({
118101
from: wallet2Address
119102
})
120-
console.log("private balance should be 100", balance)
103+
console.log("private balance should be 100", balanceResult.result ?? balanceResult)
121104

122105
const { result: publicBalance } = await l2TokenContract.methods.balance_of_public(wallet2Address).simulate({
123106
from: wallet2Address
124107
})
125-
console.log("public balance should be 100", publicBalance)
108+
console.log("public balance should be 100", publicBalanceResult.result ?? publicBalanceResult)
126109

127110
}
128111

src/test/e2e/accounts.test.ts

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import { L1FeeJuicePortalManager, type L2AmountClaim } from "@aztec/aztec.js/eth
1414
import { AztecAddress } from "@aztec/aztec.js/addresses";
1515
import { NO_FROM } from "@aztec/aztec.js/account";
1616
import { type Logger, createLogger } from "@aztec/foundation/log";
17-
import { type ContractInstanceWithAddress, getContractInstanceFromInstantiationParams } from "@aztec/aztec.js/contracts";
17+
import { type ContractInstanceWithAddress, getContractInstanceFromInstantiationParams } from "@aztec/stdlib/contract";
1818
import { Fr } from "@aztec/aztec.js/fields";
1919
import { GrumpkinScalar } from "@aztec/foundation/curves/grumpkin";
2020
import { ContractDeployer } from "@aztec/aztec.js/deployment";
@@ -101,6 +101,7 @@ describe("Accounts", () => {
101101
let balances = await Promise.all(randomAddresses.map(async a =>
102102
(await feeJuiceContract.methods.balance_of_public(a).simulate({ from: ownerAccount.address })).result
103103
));
104+
let balances = balanceResults.map(b => typeof b === 'object' && b !== null && 'result' in b ? b.result : b);
104105
console.log(`Initial balances: ${balances.join(', ')}`);
105106
balances.forEach(b => expect(b).toBe(0n));
106107

@@ -154,7 +155,7 @@ describe("Accounts", () => {
154155
});
155156

156157
// Transaction succeeded if we got here - status could be PROPOSED, CHECKPOINTED, PROVEN, or FINALIZED
157-
expect([TxStatus.PROPOSED, TxStatus.CHECKPOINTED, TxStatus.PROVEN, TxStatus.FINALIZED]).toContain(receipt.status);
158+
expect([TxStatus.PROPOSED, TxStatus.CHECKPOINTED, TxStatus.PROVEN, TxStatus.FINALIZED]).toContain(receipt.receipt.status);
158159

159160
const deployedAccount = await randomAccountManagers[0].getAccount();
160161
expect(deployedAccount.getAddress()).toEqual(randomAccountManagers[0].address);
@@ -209,8 +210,8 @@ describe("Accounts", () => {
209210
const metadata = await wallet.getContractMetadata(deploymentData.address);
210211
expect(metadata.instance).toBeTruthy();
211212
// Transaction succeeded if we got here - status could be PROPOSED, CHECKPOINTED, PROVEN, or FINALIZED
212-
expect([TxStatus.PROPOSED, TxStatus.CHECKPOINTED, TxStatus.PROVEN, TxStatus.FINALIZED]).toContain(receipt.status);
213-
expect(receipt.contract.address).toEqual(deploymentData.address);
213+
expect([TxStatus.PROPOSED, TxStatus.CHECKPOINTED, TxStatus.PROVEN, TxStatus.FINALIZED]).toContain(receipt.receipt.status);
214+
expect(receipt.receipt.contract.address).toEqual(deploymentData.address);
214215
})
215216

216217
});

src/test/e2e/index.test.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import { getTimeouts } from "../../../config/config.js";
1010
import { AztecAddress } from "@aztec/aztec.js/addresses";
1111
import { NO_FROM } from "@aztec/aztec.js/account";
1212
import { type Logger, createLogger } from "@aztec/foundation/log";
13-
import { type ContractInstanceWithAddress } from "@aztec/aztec.js/contracts";
13+
import { type ContractInstanceWithAddress } from "@aztec/stdlib/contract";
1414
import { Fr } from "@aztec/aztec.js/fields";
1515
import { GrumpkinScalar } from "@aztec/foundation/curves/grumpkin";
1616
import { TxStatus } from "@aztec/stdlib/tx";
@@ -169,7 +169,7 @@ describe("Pod Racing Game", () => {
169169
});
170170

171171
// Transaction succeeded if we got here - status could be PROPOSED, CHECKPOINTED, PROVEN, or FINALIZED
172-
expect([TxStatus.PROPOSED, TxStatus.CHECKPOINTED, TxStatus.PROVEN, TxStatus.FINALIZED]).toContain(tx.status);
172+
expect([TxStatus.PROPOSED, TxStatus.CHECKPOINTED, TxStatus.PROVEN, TxStatus.FINALIZED]).toContain(tx.receipt.status);
173173
logger.info('Game created successfully');
174174
}, 600000)
175175

@@ -216,7 +216,7 @@ describe("Pod Racing Game", () => {
216216
);
217217

218218
// Transaction succeeded if we got here - status could be PROPOSED, CHECKPOINTED, PROVEN, or FINALIZED
219-
expect([TxStatus.PROPOSED, TxStatus.CHECKPOINTED, TxStatus.PROVEN, TxStatus.FINALIZED]).toContain(playTx.status);
219+
expect([TxStatus.PROPOSED, TxStatus.CHECKPOINTED, TxStatus.PROVEN, TxStatus.FINALIZED]).toContain(playTx.receipt.status);
220220
logger.info('Round played successfully');
221221
}, 600000)
222222

@@ -386,7 +386,7 @@ describe("Pod Racing Game", () => {
386386
);
387387

388388
// Transaction succeeded if we got here - status could be PROPOSED, CHECKPOINTED, PROVEN, or FINALIZED
389-
expect([TxStatus.PROPOSED, TxStatus.CHECKPOINTED, TxStatus.PROVEN, TxStatus.FINALIZED]).toContain(tx.status);
389+
expect([TxStatus.PROPOSED, TxStatus.CHECKPOINTED, TxStatus.PROVEN, TxStatus.FINALIZED]).toContain(tx.receipt.status);
390390
logger.info('Max points allocation successful');
391391
}, 600000)
392392

@@ -415,7 +415,7 @@ describe("Pod Racing Game", () => {
415415
);
416416

417417
// Transaction succeeded if we got here - status could be PROPOSED, CHECKPOINTED, PROVEN, or FINALIZED
418-
expect([TxStatus.PROPOSED, TxStatus.CHECKPOINTED, TxStatus.PROVEN, TxStatus.FINALIZED]).toContain(tx.status);
418+
expect([TxStatus.PROPOSED, TxStatus.CHECKPOINTED, TxStatus.PROVEN, TxStatus.FINALIZED]).toContain(tx.receipt.status);
419419
logger.info('Zero points allocation successful');
420420
}, 600000)
421421
});

src/test/e2e/public_logging.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ import { getTimeouts } from "../../../config/config.js";
1818
import { AztecAddress } from "@aztec/aztec.js/addresses";
1919
import { NO_FROM } from "@aztec/aztec.js/account";
2020
import { type Logger, createLogger } from "@aztec/foundation/log";
21-
import { type ContractInstanceWithAddress } from "@aztec/aztec.js/contracts";
21+
import { type ContractInstanceWithAddress } from "@aztec/stdlib/contract";
2222
import { Fr } from "@aztec/aztec.js/fields";
2323
import { GrumpkinScalar } from "@aztec/foundation/curves/grumpkin";
2424
import { EmbeddedWallet } from '@aztec/wallets/embedded';

src/utils/bridge_fee_juice.ts

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
import { L1FeeJuicePortalManager } from '@aztec/aztec.js/ethereum';
2+
import type { AztecNode } from '@aztec/aztec.js/node';
3+
import { createEthereumChain } from '@aztec/ethereum/chain';
4+
import { createExtendedL1Client } from '@aztec/ethereum/client';
5+
import { Fr } from '@aztec/aztec.js/fields';
6+
import { ProtocolContractAddress } from '@aztec/aztec.js/protocol';
7+
import { getNonNullifiedL1ToL2MessageWitness } from '@aztec/stdlib/messaging';
8+
import { FeeAssetHandlerAbi } from '@aztec/l1-artifacts/FeeAssetHandlerAbi';
9+
import type { AztecAddress } from '@aztec/aztec.js/addresses';
10+
import type { Logger } from '@aztec/foundation/log';
11+
import { getContract } from 'viem';
12+
import configManager from '../../config/config.js';
13+
14+
const MAX_POLL_ATTEMPTS = 40; // 40 * 30s = 20 minutes max
15+
16+
export async function bridgeL1FeeJuice(
17+
node: AztecNode,
18+
recipient: AztecAddress,
19+
amount: bigint,
20+
logger: Logger,
21+
) {
22+
const { l1RpcUrl, l1ChainId } = configManager.getNetworkConfig();
23+
const l1PrivateKey = process.env.L1_PRIVATE_KEY;
24+
25+
if (!l1PrivateKey) {
26+
throw new Error('L1_PRIVATE_KEY env var is required for testnet fee juice bridging. Must be a Sepolia-funded private key prefixed with 0x.');
27+
}
28+
29+
const key = l1PrivateKey.startsWith('0x') ? l1PrivateKey : `0x${l1PrivateKey}`;
30+
const chain = createEthereumChain([l1RpcUrl], l1ChainId);
31+
32+
logger.info(`🌉 Bridging ${amount} fee juice from L1 to ${recipient}...`);
33+
34+
const l1Client = createExtendedL1Client(chain.rpcUrls, key, chain.chainInfo);
35+
const portal = await L1FeeJuicePortalManager.new(node, l1Client, logger);
36+
const tokenManager = portal.getTokenManager();
37+
38+
// Check if we already have enough tokens on L1
39+
const balance = await tokenManager.getL1TokenBalance(l1Client.account.address);
40+
if (balance < amount) {
41+
// Mint tokens and wait for confirmation.
42+
// Upstream L1TokenManager.mint() doesn't wait for the tx receipt,
43+
// causing nonce conflicts with the subsequent approve transaction.
44+
logger.info(`🪙 Minting fee juice tokens on L1...`);
45+
const handler = getContract({
46+
address: tokenManager.handlerAddress!.toString() as `0x${string}`,
47+
abi: FeeAssetHandlerAbi,
48+
client: l1Client,
49+
});
50+
const mintHash = await handler.write.mint([l1Client.account.address]);
51+
logger.info(`⏳ Waiting for mint tx to confirm: ${mintHash}`);
52+
await l1Client.waitForTransactionReceipt({ hash: mintHash });
53+
logger.info(`✅ Mint confirmed on L1`);
54+
} else {
55+
logger.info(`💰 L1 account already has ${balance} tokens, skipping mint`);
56+
}
57+
58+
// Create a fresh L1 client so viem picks up the current on-chain nonce.
59+
// The mint (or prior runs) may have incremented it beyond what the
60+
// original client has cached. Public RPC nodes may have eventual consistency,
61+
// so wait briefly for the nonce to propagate.
62+
await new Promise(resolve => setTimeout(resolve, 3_000));
63+
const freshClient = createExtendedL1Client(chain.rpcUrls, key, chain.chainInfo);
64+
const freshPortal = await L1FeeJuicePortalManager.new(node, freshClient, logger);
65+
const claim = await freshPortal.bridgeTokensPublic(recipient, amount, false);
66+
67+
logger.info(`✅ Fee juice bridged! Claim amount: ${claim.claimAmount}, message hash: ${claim.messageHash}`);
68+
logger.info(`⏳ Waiting for L1-to-L2 message to be available on L2...`);
69+
70+
const pollInterval = 30_000;
71+
for (let attempt = 0; attempt < MAX_POLL_ATTEMPTS; attempt++) {
72+
const witness = await getNonNullifiedL1ToL2MessageWitness(
73+
node,
74+
ProtocolContractAddress.FeeJuice,
75+
Fr.fromHexString(claim.messageHash),
76+
claim.claimSecret,
77+
).catch(() => undefined);
78+
79+
if (witness) {
80+
logger.info(`✅ L1-to-L2 message is available on L2!`);
81+
return claim;
82+
}
83+
84+
logger.info(`⏳ Message not yet available, checking again in ${pollInterval / 1000}s... (${attempt + 1}/${MAX_POLL_ATTEMPTS})`);
85+
await new Promise(resolve => setTimeout(resolve, pollInterval));
86+
}
87+
88+
throw new Error(`L1-to-L2 message not available after ${MAX_POLL_ATTEMPTS} attempts (${MAX_POLL_ATTEMPTS * 30}s)`);
89+
}

0 commit comments

Comments
 (0)