diff --git a/l1-contracts/src/core/Rollup.sol b/l1-contracts/src/core/Rollup.sol index ca73f3baaa42..7b620f9378ed 100644 --- a/l1-contracts/src/core/Rollup.sol +++ b/l1-contracts/src/core/Rollup.sol @@ -510,12 +510,18 @@ contract Rollup is EIP712("Aztec Rollup", "1"), Leonidas, IRollup, ITestRollup { if (blockNumber <= assumeProvenThroughBlockNumber) { fakeBlockNumberAsProven(blockNumber); - if (header.globalVariables.coinbase != address(0) && header.totalFees > 0) { + bool isFeeCanonical = address(this) == FEE_JUICE_PORTAL.canonicalRollup(); + bool isSysstiaCanonical = address(this) == SYSSTIA.canonicalRollup(); + + if (isFeeCanonical && header.globalVariables.coinbase != address(0) && header.totalFees > 0) { // @note This will currently fail if there are insufficient funds in the bridge // which WILL happen for the old version after an upgrade where the bridge follow. // Consider allowing a failure. See #7938. FEE_JUICE_PORTAL.distributeFees(header.globalVariables.coinbase, header.totalFees); } + if (isSysstiaCanonical && header.globalVariables.coinbase != address(0)) { + SYSSTIA.claim(header.globalVariables.coinbase); + } emit L2ProofVerified(blockNumber, "CHEAT"); } diff --git a/yarn-project/end-to-end/src/e2e_prover/e2e_prover_test.ts b/yarn-project/end-to-end/src/e2e_prover/e2e_prover_test.ts index b66e4cfc8ed6..1b7b29c6d9d7 100644 --- a/yarn-project/end-to-end/src/e2e_prover/e2e_prover_test.ts +++ b/yarn-project/end-to-end/src/e2e_prover/e2e_prover_test.ts @@ -7,6 +7,7 @@ import { type CompleteAddress, type DebugLogger, type DeployL1Contracts, + EthAddress, ExtendedNote, type Fq, Fr, @@ -88,14 +89,20 @@ export class FullProverTest { private context!: SubsystemsContext; private proverNode!: ProverNode; private simulatedProverNode!: ProverNode; - private l1Contracts!: DeployL1Contracts; - - constructor(testName: string, private minNumberOfTxsPerBlock: number, private realProofs = true) { + public l1Contracts!: DeployL1Contracts; + public proverAddress!: EthAddress; + + constructor( + testName: string, + private minNumberOfTxsPerBlock: number, + coinbase: EthAddress, + private realProofs = true, + ) { this.logger = createDebugLogger(`aztec:full_prover_test:${testName}`); this.snapshotManager = createSnapshotManager( `full_prover_integration/${testName}`, dataPath, - { startProverNode: true }, + { startProverNode: true, fundSysstia: true, coinbase }, { assumeProvenThrough: undefined }, ); } @@ -261,6 +268,7 @@ export class FullProverTest { // The simulated prover node (now shutdown) used private key index 2 const proverNodePrivateKey = getPrivateKeyFromIndex(2); const proverNodeSenderAddress = privateKeyToAddress(new Buffer32(proverNodePrivateKey!).to0xString()); + this.proverAddress = EthAddress.fromString(proverNodeSenderAddress); this.logger.verbose(`Funding prover node at ${proverNodeSenderAddress}`); await this.mintL1ERC20(proverNodeSenderAddress, 100_000_000n); diff --git a/yarn-project/end-to-end/src/e2e_prover/full.test.ts b/yarn-project/end-to-end/src/e2e_prover/full.test.ts index d21075e38270..a19bd41cc0fe 100644 --- a/yarn-project/end-to-end/src/e2e_prover/full.test.ts +++ b/yarn-project/end-to-end/src/e2e_prover/full.test.ts @@ -1,6 +1,8 @@ -import { type AztecAddress, retryUntil } from '@aztec/aztec.js'; +import { type AztecAddress, EthAddress, retryUntil } from '@aztec/aztec.js'; +import { RollupAbi, SysstiaAbi, TestERC20Abi } from '@aztec/l1-artifacts'; import '@jest/globals'; +import { type Chain, type GetContractReturnType, type HttpTransport, type PublicClient, getContract } from 'viem'; import { FullProverTest } from './e2e_prover_test.js'; @@ -11,12 +13,17 @@ process.env.AVM_PROVING_STRICT = '1'; describe('full_prover', () => { const realProofs = !['true', '1'].includes(process.env.FAKE_PROOFS ?? ''); - const t = new FullProverTest('full_prover', 1, realProofs); + const COINBASE_ADDRESS = EthAddress.random(); + const t = new FullProverTest('full_prover', 1, COINBASE_ADDRESS, realProofs); let { provenAssets, accounts, tokenSim, logger, cheatCodes } = t; let sender: AztecAddress; let recipient: AztecAddress; + let rollup: GetContractReturnType>; + let feeJuice: GetContractReturnType>; + let sysstia: GetContractReturnType>; + beforeAll(async () => { await t.applyBaseSnapshots(); await t.applyMintSnapshot(); @@ -25,6 +32,24 @@ describe('full_prover', () => { ({ provenAssets, accounts, tokenSim, logger, cheatCodes } = t); [sender, recipient] = accounts.map(a => a.address); + + rollup = getContract({ + abi: RollupAbi, + address: t.l1Contracts.l1ContractAddresses.rollupAddress.toString(), + client: t.l1Contracts.publicClient, + }); + + feeJuice = getContract({ + abi: TestERC20Abi, + address: t.l1Contracts.l1ContractAddresses.feeJuiceAddress.toString(), + client: t.l1Contracts.publicClient, + }); + + sysstia = getContract({ + abi: SysstiaAbi, + address: t.l1Contracts.l1ContractAddresses.sysstiaAddress.toString(), + client: t.l1Contracts.publicClient, + }); }); afterAll(async () => { @@ -86,6 +111,9 @@ describe('full_prover', () => { logger.info(`Advancing from epoch ${epoch} to next epoch`); await cheatCodes.rollup.advanceToNextEpoch(); + const balanceBeforeCoinbase = await feeJuice.read.balanceOf([COINBASE_ADDRESS.toString()]); + const balanceBeforeProver = await feeJuice.read.balanceOf([t.proverAddress.toString()]); + // Wait until the prover node submits a quote logger.info(`Waiting for prover node to submit quote for epoch ${epoch}`); await retryUntil(() => t.aztecNode.getEpochProofQuotes(epoch).then(qs => qs.length > 0), 'quote', 60, 1); @@ -108,6 +136,25 @@ describe('full_prover', () => { // And wait for the first pair of txs to be proven logger.info(`Awaiting proof for the previous epoch`); await Promise.all(txs.map(tx => tx.wait({ timeout: 300, interval: 10, proven: true, provenTimeout: 1500 }))); + + const provenBn = await rollup.read.getProvenBlockNumber(); + const balanceAfterCoinbase = await feeJuice.read.balanceOf([COINBASE_ADDRESS.toString()]); + const balanceAfterProver = await feeJuice.read.balanceOf([t.proverAddress.toString()]); + const blockReward = (await sysstia.read.BLOCK_REWARD()) as bigint; + const fees = ( + await Promise.all([t.aztecNode.getBlock(Number(provenBn - 1n)), t.aztecNode.getBlock(Number(provenBn))]) + ).map(b => b!.header.totalFees.toBigInt()); + + const rewards = fees.map(fee => fee + blockReward); + const toProver = rewards + .map(reward => (reward * claim!.basisPointFee) / 10_000n) + .reduce((acc, fee) => acc + fee, 0n); + const toCoinbase = rewards.reduce((acc, reward) => acc + reward, 0n) - toProver; + + expect(provenBn + 1n).toBe(await rollup.read.getPendingBlockNumber()); + expect(balanceAfterCoinbase).toBe(balanceBeforeCoinbase + toCoinbase); + expect(balanceAfterProver).toBe(balanceBeforeProver + toProver); + expect(claim!.bondProvider).toEqual(t.proverAddress); }, TIMEOUT, ); diff --git a/yarn-project/end-to-end/src/fixtures/snapshot_manager.ts b/yarn-project/end-to-end/src/fixtures/snapshot_manager.ts index d6dc494e6436..bf7ce4b501e2 100644 --- a/yarn-project/end-to-end/src/fixtures/snapshot_manager.ts +++ b/yarn-project/end-to-end/src/fixtures/snapshot_manager.ts @@ -17,7 +17,7 @@ import { type Wallet, } from '@aztec/aztec.js'; import { deployInstance, registerContractClass } from '@aztec/aztec.js/deployment'; -import { type DeployL1ContractsArgs, createL1Clients } from '@aztec/ethereum'; +import { type DeployL1ContractsArgs, createL1Clients, l1Artifacts } from '@aztec/ethereum'; import { asyncMap } from '@aztec/foundation/async-map'; import { type Logger, createDebugLogger } from '@aztec/foundation/log'; import { resolver, reviver } from '@aztec/foundation/serialize'; @@ -31,7 +31,7 @@ import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'fs'; import { copySync, removeSync } from 'fs-extra/esm'; import getPort from 'get-port'; import { join } from 'path'; -import { type Hex } from 'viem'; +import { type Hex, getContract } from 'viem'; import { mnemonicToAccount } from 'viem/accounts'; import { MNEMONIC } from './fixtures.js'; @@ -336,6 +336,29 @@ async function setupFromFresh( aztecNodeConfig.l1Contracts = deployL1ContractsValues.l1ContractAddresses; aztecNodeConfig.l1PublishRetryIntervalMS = 100; + if (opts.fundSysstia) { + // Mints block rewards for 10000 blocks to the sysstia contract + + const sysstia = getContract({ + address: deployL1ContractsValues.l1ContractAddresses.sysstiaAddress.toString(), + abi: l1Artifacts.sysstia.contractAbi, + client: deployL1ContractsValues.publicClient, + }); + + const blockReward = await sysstia.read.BLOCK_REWARD([]); + const mintAmount = 10_000n * (blockReward as bigint); + + const feeJuice = getContract({ + address: deployL1ContractsValues.l1ContractAddresses.feeJuiceAddress.toString(), + abi: l1Artifacts.feeJuice.contractAbi, + client: deployL1ContractsValues.walletClient, + }); + + const sysstiaMintTxHash = await feeJuice.write.mint([sysstia.address, mintAmount], {} as any); + await deployL1ContractsValues.publicClient.waitForTransactionReceipt({ hash: sysstiaMintTxHash }); + logger.info(`Funding sysstia in ${sysstiaMintTxHash}`); + } + const watcher = new AnvilTestWatcher( new EthCheatCodes(aztecNodeConfig.l1RpcUrl), deployL1ContractsValues.l1ContractAddresses.rollupAddress, diff --git a/yarn-project/end-to-end/src/fixtures/utils.ts b/yarn-project/end-to-end/src/fixtures/utils.ts index 582c49f45db2..b5321c702b19 100644 --- a/yarn-project/end-to-end/src/fixtures/utils.ts +++ b/yarn-project/end-to-end/src/fixtures/utils.ts @@ -30,7 +30,7 @@ import { deployInstance, registerContractClass } from '@aztec/aztec.js/deploymen import { DefaultMultiCallEntrypoint } from '@aztec/aztec.js/entrypoint'; import { type BBNativePrivateKernelProver } from '@aztec/bb-prover'; import { type EthAddress, GasSettings, getContractClassFromArtifact } from '@aztec/circuits.js'; -import { NULL_KEY, isAnvilTestChain } from '@aztec/ethereum'; +import { NULL_KEY, isAnvilTestChain, l1Artifacts } from '@aztec/ethereum'; import { makeBackoff, retry, retryUntil } from '@aztec/foundation/retry'; import { FeeJuiceContract } from '@aztec/noir-contracts.js/FeeJuice'; import { getVKTreeRoot } from '@aztec/noir-protocol-circuits-types'; @@ -50,6 +50,7 @@ import { type PrivateKeyAccount, createPublicClient, createWalletClient, + getContract, http, } from 'viem'; import { mnemonicToAccount, privateKeyToAccount } from 'viem/accounts'; @@ -247,6 +248,8 @@ export type SetupOptions = { assumeProvenThrough?: number; /** Whether to start a prover node */ startProverNode?: boolean; + /** Whether to fund the sysstia */ + fundSysstia?: boolean; } & Partial; /** Context for an end-to-end test as returned by the `setup` function */ @@ -360,6 +363,29 @@ export async function setup( config.l1Contracts = deployL1ContractsValues.l1ContractAddresses; + if (opts.fundSysstia) { + // Mints block rewards for 10000 blocks to the sysstia contract + + const sysstia = getContract({ + address: deployL1ContractsValues.l1ContractAddresses.sysstiaAddress.toString(), + abi: l1Artifacts.sysstia.contractAbi, + client: deployL1ContractsValues.publicClient, + }); + + const blockReward = await sysstia.read.BLOCK_REWARD([]); + const mintAmount = 10_000n * (blockReward as bigint); + + const feeJuice = getContract({ + address: deployL1ContractsValues.l1ContractAddresses.feeJuiceAddress.toString(), + abi: l1Artifacts.feeJuice.contractAbi, + client: deployL1ContractsValues.walletClient, + }); + + const sysstiaMintTxHash = await feeJuice.write.mint([sysstia.address, mintAmount], {} as any); + await deployL1ContractsValues.publicClient.waitForTransactionReceipt({ hash: sysstiaMintTxHash }); + logger.info(`Funding sysstia in ${sysstiaMintTxHash}`); + } + if (opts.l2StartTime) { // This should only be used in synching test or when you need to have a stable // timestamp for the first l2 block.