Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 9 additions & 1 deletion l1-contracts/src/core/Rollup.sol
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {IAvailabilityOracle} from "./interfaces/IAvailabilityOracle.sol";
import {IInbox} from "./interfaces/messagebridge/IInbox.sol";
import {IOutbox} from "./interfaces/messagebridge/IOutbox.sol";
import {IRegistry} from "./interfaces/messagebridge/IRegistry.sol";
import {IERC20} from "@oz/token/ERC20/IERC20.sol";

// Libraries
import {HeaderLib} from "./libraries/HeaderLib.sol";
Expand All @@ -33,17 +34,19 @@ contract Rollup is IRollup {
IInbox public immutable INBOX;
IOutbox public immutable OUTBOX;
uint256 public immutable VERSION;
IERC20 public immutable GAS_TOKEN;

bytes32 public archive; // Root of the archive tree
uint256 public lastBlockTs;
// Tracks the last time time was warped on L2 ("warp" is the testing cheatcode).
// See https://github.com/AztecProtocol/aztec-packages/issues/1614
uint256 public lastWarpedBlockTs;

constructor(IRegistry _registry, IAvailabilityOracle _availabilityOracle) {
constructor(IRegistry _registry, IAvailabilityOracle _availabilityOracle, IERC20 _gasToken) {
VERIFIER = new MockVerifier();
REGISTRY = _registry;
AVAILABILITY_ORACLE = _availabilityOracle;
GAS_TOKEN = _gasToken;
INBOX = new Inbox(address(this), Constants.L1_TO_L2_MSG_SUBTREE_HEIGHT);
OUTBOX = new Outbox(address(this));
VERSION = 1;
Expand Down Expand Up @@ -92,6 +95,11 @@ contract Rollup is IRollup {
header.globalVariables.blockNumber, header.contentCommitment.outHash, l2ToL1TreeHeight
);

// pay the coinbase 1 gas token if it is not empty
if (header.globalVariables.coinbase != address(0)) {
GAS_TOKEN.transfer(address(header.globalVariables.coinbase), 1);
}

emit L2BlockProcessed(header.globalVariables.blockNumber);
}

Expand Down
10 changes: 9 additions & 1 deletion l1-contracts/test/Rollup.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
// Copyright 2023 Aztec Labs.
pragma solidity >=0.8.18;

import {IERC20} from "@oz/token/ERC20/IERC20.sol";

import {DecoderBase} from "./decoders/Base.sol";

import {DataStructures} from "../src/core/libraries/DataStructures.sol";
Expand All @@ -15,6 +17,7 @@ import {Rollup} from "../src/core/Rollup.sol";
import {AvailabilityOracle} from "../src/core/availability_oracle/AvailabilityOracle.sol";
import {NaiveMerkle} from "./merkle/Naive.sol";
import {MerkleTestUtil} from "./merkle/TestUtil.sol";
import {PortalERC20} from "./portals/PortalERC20.sol";

import {TxsDecoderHelper} from "./decoders/helpers/TxsDecoderHelper.sol";

Expand All @@ -29,18 +32,23 @@ contract RollupTest is DecoderBase {
Rollup internal rollup;
MerkleTestUtil internal merkleTestUtil;
TxsDecoderHelper internal txsHelper;
PortalERC20 internal portalERC20;

AvailabilityOracle internal availabilityOracle;

function setUp() public virtual {
registry = new Registry();
availabilityOracle = new AvailabilityOracle();
rollup = new Rollup(registry, availabilityOracle);
portalERC20 = new PortalERC20();
rollup = new Rollup(registry, availabilityOracle, IERC20(address(portalERC20)));
inbox = Inbox(address(rollup.INBOX()));
outbox = Outbox(address(rollup.OUTBOX()));

registry.upgrade(address(rollup), address(inbox), address(outbox));

// mint some tokens to the rollup
portalERC20.mint(address(rollup), 1000000);

merkleTestUtil = new MerkleTestUtil();
txsHelper = new TxsDecoderHelper();
}
Expand Down
6 changes: 4 additions & 2 deletions l1-contracts/test/portals/TokenPortal.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import {Errors} from "../../src/core/libraries/Errors.sol";
// Interfaces
import {IInbox} from "../../src/core/interfaces/messagebridge/IInbox.sol";
import {IOutbox} from "../../src/core/interfaces/messagebridge/IOutbox.sol";
import {IERC20} from "@oz/token/ERC20/IERC20.sol";

// Portal tokens
import {TokenPortal} from "./TokenPortal.sol";
Expand Down Expand Up @@ -57,13 +58,14 @@ contract TokenPortalTest is Test {

function setUp() public {
registry = new Registry();
rollup = new Rollup(registry, new AvailabilityOracle());
portalERC20 = new PortalERC20();
rollup = new Rollup(registry, new AvailabilityOracle(), IERC20(address(portalERC20)));
inbox = rollup.INBOX();
outbox = rollup.OUTBOX();

registry.upgrade(address(rollup), address(inbox), address(outbox));

portalERC20 = new PortalERC20();
portalERC20.mint(address(rollup), 1000000);
tokenPortal = new TokenPortal();

tokenPortal.initialize(address(registry), address(portalERC20), l2TokenAddress);
Expand Down
7 changes: 6 additions & 1 deletion l1-contracts/test/portals/UniswapPortal.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@ import {NaiveMerkle} from "../merkle/Naive.sol";
import {TokenPortal} from "./TokenPortal.sol";
import {UniswapPortal} from "./UniswapPortal.sol";

// Portal tokens
import {PortalERC20} from "./PortalERC20.sol";

contract UniswapPortalTest is Test {
using Hash for DataStructures.L2ToL1Msg;

Expand Down Expand Up @@ -48,8 +51,10 @@ contract UniswapPortalTest is Test {
vm.selectFork(forkId);

registry = new Registry();
rollup = new Rollup(registry, new AvailabilityOracle());
PortalERC20 portalERC20 = new PortalERC20();
rollup = new Rollup(registry, new AvailabilityOracle(), IERC20(address(portalERC20)));
registry.upgrade(address(rollup), address(rollup.INBOX()), address(rollup.OUTBOX()));
portalERC20.mint(address(rollup), 1000000);

daiTokenPortal = new TokenPortal();
daiTokenPortal.initialize(address(registry), address(DAI), l2TokenAddress);
Expand Down
22 changes: 20 additions & 2 deletions yarn-project/end-to-end/src/e2e_fees/fees_test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,9 @@ import {
createDebugLogger,
} from '@aztec/aztec.js';
import { DefaultMultiCallEntrypoint } from '@aztec/aztec.js/entrypoint';
import { GasSettings } from '@aztec/circuits.js';
import { EthAddress, GasSettings } from '@aztec/circuits.js';
import { createL1Clients } from '@aztec/ethereum';
import { PortalERC20Abi } from '@aztec/l1-artifacts';
import {
AppSubscriptionContract,
TokenContract as BananaCoin,
Expand All @@ -24,6 +25,8 @@ import {
GasTokenContract,
} from '@aztec/noir-contracts.js';

import { getContract } from 'viem';

import { MNEMONIC } from '../fixtures/fixtures.js';
import {
type ISnapshotManager,
Expand Down Expand Up @@ -58,6 +61,7 @@ export class FeesTest {
public bobWallet!: AccountWallet;
public bobAddress!: AztecAddress;
public sequencerAddress!: AztecAddress;
public coinbase!: EthAddress;

public gasSettings = GasSettings.default();
public maxFee = this.gasSettings.getFeeLimit().toBigInt();
Expand All @@ -68,6 +72,7 @@ export class FeesTest {
public counterContract!: CounterContract;
public subscriptionContract!: AppSubscriptionContract;

public getCoinbaseBalance!: () => Promise<bigint>;
public gasBalances!: BalancesFn;
public bananaPublicBalances!: BalancesFn;
public bananaPrivateBalances!: BalancesFn;
Expand All @@ -84,7 +89,7 @@ export class FeesTest {

async setup() {
const context = await this.snapshotManager.setup();
await context.aztecNode.setConfig({ feeRecipient: this.sequencerAddress });
await context.aztecNode.setConfig({ feeRecipient: this.sequencerAddress, coinbase: this.coinbase });
({ pxe: this.pxe, aztecNode: this.aztecNode } = context);
return this;
}
Expand Down Expand Up @@ -138,6 +143,7 @@ export class FeesTest {
this.wallets.forEach((w, i) => this.logger.verbose(`Wallet ${i} address: ${w.getAddress()}`));
[this.aliceWallet, this.bobWallet] = this.wallets.slice(0, 2);
[this.aliceAddress, this.bobAddress, this.sequencerAddress] = this.wallets.map(w => w.getAddress());
this.coinbase = EthAddress.random();
},
);
}
Expand Down Expand Up @@ -185,6 +191,8 @@ export class FeesTest {
bananaCoinAddress: bananaCoin.address,
bananaFPCAddress: bananaFPC.address,
gasTokenAddress: gasTokenContract.address,
l1GasTokenAddress: harness.l1GasTokenAddress,
rpcUrl: context.aztecNodeConfig.rpcUrl,
};
},
async data => {
Expand All @@ -199,6 +207,16 @@ export class FeesTest {
this.bananaPublicBalances = getBalancesFn('🍌.public', bananaCoin.methods.balance_of_public, this.logger);
this.bananaPrivateBalances = getBalancesFn('🍌.private', bananaCoin.methods.balance_of_private, this.logger);
this.gasBalances = getBalancesFn('⛽', gasTokenContract.methods.balance_of_public, this.logger);

this.getCoinbaseBalance = async () => {
const { walletClient } = createL1Clients(data.rpcUrl, MNEMONIC);
const gasL1 = getContract({
address: data.l1GasTokenAddress.toString(),
abi: PortalERC20Abi,
client: walletClient,
});
return await gasL1.read.balanceOf([this.coinbase.toString()]);
};
},
);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ describe('e2e_fees private_payment', () => {
await t.teardown();
});

let InitialSequencerL1Gas: bigint;

let InitialAlicePublicBananas: bigint;
let InitialAlicePrivateBananas: bigint;
let InitialAliceGas: bigint;
Expand All @@ -59,6 +61,8 @@ describe('e2e_fees private_payment', () => {

expect(gasSettings.getFeeLimit().toBigInt()).toEqual(maxFee);

InitialSequencerL1Gas = await t.getCoinbaseBalance();

[
[InitialAlicePrivateBananas, InitialBobPrivateBananas, InitialFPCPrivateBananas],
[InitialAlicePublicBananas, InitialBobPublicBananas, InitialFPCPublicBananas],
Expand Down Expand Up @@ -104,7 +108,6 @@ describe('e2e_fees private_payment', () => {
*/
const transferAmount = 5n;
const interaction = bananaCoin.methods.transfer(aliceAddress, bobAddress, transferAmount, 0n);

const localTx = await interaction.prove({
fee: {
gasSettings,
Expand All @@ -115,6 +118,8 @@ describe('e2e_fees private_payment', () => {

const tx = await interaction.send().wait();

await expect(t.getCoinbaseBalance()).resolves.toEqual(InitialSequencerL1Gas + 1n);

/**
* at present the user is paying DA gas for:
* 3 nullifiers = 3 * DA_BYTES_PER_FIELD * DA_GAS_PER_BYTE = 3 * 32 * 16 = 1536 DA gas
Expand Down
17 changes: 13 additions & 4 deletions yarn-project/end-to-end/src/shared/gas_portal_test_harness.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,10 @@ import {
} from 'viem';

export interface IGasBridgingTestHarness {
getL1GasTokenBalance(address: EthAddress): Promise<bigint>;
bridgeFromL1ToL2(l1TokenBalance: bigint, bridgeAmount: bigint, owner: AztecAddress): Promise<void>;
l2Token: GasTokenContract;
l1GasTokenAddress: EthAddress;
}

export interface GasPortalTestingHarnessFactoryConfig {
Expand All @@ -49,7 +51,7 @@ export class GasPortalTestingHarnessFactory {
contractAddressSalt: getCanonicalGasToken(EthAddress.ZERO).instance.salt,
})
.deployed();
return Promise.resolve(new MockGasBridgingTestHarness(gasL2));
return Promise.resolve(new MockGasBridgingTestHarness(gasL2, EthAddress.ZERO));
}

private async createReal() {
Expand Down Expand Up @@ -111,10 +113,13 @@ export class GasPortalTestingHarnessFactory {
}

class MockGasBridgingTestHarness implements IGasBridgingTestHarness {
constructor(public l2Token: GasTokenContract) {}
constructor(public l2Token: GasTokenContract, public l1GasTokenAddress: EthAddress) {}
async bridgeFromL1ToL2(_l1TokenBalance: bigint, bridgeAmount: bigint, owner: AztecAddress): Promise<void> {
await this.l2Token.methods.mint_public(owner, bridgeAmount).send().wait();
}
getL1GasTokenBalance(_address: EthAddress): Promise<bigint> {
throw new Error('Cannot get gas token balance on mocked L1.');
}
}

/**
Expand Down Expand Up @@ -150,6 +155,10 @@ class GasBridgingTestHarness implements IGasBridgingTestHarness {
public walletClient: WalletClient,
) {}

get l1GasTokenAddress() {
return EthAddress.fromString(this.underlyingERC20.address);
}

generateClaimSecret(): [Fr, Fr] {
this.logger.debug("Generating a claim secret using pedersen's hash function");
const secret = Fr.random();
Expand All @@ -166,7 +175,7 @@ class GasBridgingTestHarness implements IGasBridgingTestHarness {
expect(await this.underlyingERC20.read.balanceOf([this.ethAccount.toString()])).toBe(amount);
}

async getL1BalanceOf(address: EthAddress) {
async getL1GasTokenBalance(address: EthAddress) {
return await this.underlyingERC20.read.balanceOf([address.toString()]);
}

Expand Down Expand Up @@ -211,7 +220,7 @@ class GasBridgingTestHarness implements IGasBridgingTestHarness {

// 2. Deposit tokens to the TokenPortal
const msgHash = await this.sendTokensToPortalPublic(bridgeAmount, owner, secretHash);
expect(await this.getL1BalanceOf(this.ethAccount)).toBe(l1TokenBalance - bridgeAmount);
expect(await this.getL1GasTokenBalance(this.ethAccount)).toBe(l1TokenBalance - bridgeAmount);

// Perform an unrelated transactions on L2 to progress the rollup by 2 blocks.
await this.l2Token.methods.check_balance(0).send().wait();
Expand Down
34 changes: 23 additions & 11 deletions yarn-project/ethereum/src/deploy_l1_contracts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -158,12 +158,25 @@ export const deployL1Contracts = async (
);
logger.info(`Deployed AvailabilityOracle at ${availabilityOracleAddress}`);

const gasTokenAddress = await deployL1Contract(
walletClient,
publicClient,
contractsToDeploy.gasToken.contractAbi,
contractsToDeploy.gasToken.contractBytecode,
);

logger.info(`Deployed Gas Token at ${gasTokenAddress}`);

const rollupAddress = await deployL1Contract(
walletClient,
publicClient,
contractsToDeploy.rollup.contractAbi,
contractsToDeploy.rollup.contractBytecode,
[getAddress(registryAddress.toString()), getAddress(availabilityOracleAddress.toString())],
[
getAddress(registryAddress.toString()),
getAddress(availabilityOracleAddress.toString()),
getAddress(gasTokenAddress.toString()),
],
);
logger.info(`Deployed Rollup at ${rollupAddress}`);

Expand Down Expand Up @@ -201,16 +214,6 @@ export const deployL1Contracts = async (
{ account },
);

// this contract remains uninitialized because at this point we don't know the address of the gas token on L2
const gasTokenAddress = await deployL1Contract(
walletClient,
publicClient,
contractsToDeploy.gasToken.contractAbi,
contractsToDeploy.gasToken.contractBytecode,
);

logger.info(`Deployed Gas Token at ${gasTokenAddress}`);

// this contract remains uninitialized because at this point we don't know the address of the gas token on L2
const gasPortalAddress = await deployL1Contract(
walletClient,
Expand All @@ -221,6 +224,15 @@ export const deployL1Contracts = async (

logger.info(`Deployed Gas Portal at ${gasPortalAddress}`);

// fund the rollup contract with gas tokens
const gasToken = getContract({
address: gasTokenAddress.toString(),
abi: contractsToDeploy.gasToken.contractAbi,
client: walletClient,
});
await gasToken.write.mint([rollupAddress.toString(), 100000000000000000000n], {} as any);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remember to waitForTransactionMined (or however is the helper called in viem), otherwise you can run into race conditions

logger.info(`Funded rollup contract with gas tokens`);

const l1Contracts: L1ContractAddresses = {
availabilityOracleAddress,
rollupAddress,
Expand Down