Specialized provider implementation for Optimism Layer 2 blockchain testing, extending standard JSON-RPC provider functionality with L1 fee calculation and optimized wallet management for Optimism network characteristics.
Enhanced provider specifically designed for Optimism L2 testing with additional methods for L1 fee calculation and wallet access.
/**
* Enhanced provider for Optimism L2 blockchain testing
* Extends JsonRpcProvider with wallet access and L1 fee calculation
*/
class OptimismProvider extends providers.JsonRpcProvider implements TestProvider {
/**
* Get array of pre-funded test wallets for Optimism testing
* @returns Array of Wallet instances with ETH balances
*/
getWallets(): Wallet[];
/**
* Calculate L1 fee for a completed transaction on Optimism
* @param transactionHash - Hash of the transaction to calculate L1 fee for
* @returns Promise resolving to L1 fee amount in wei
*/
getL1Fee(transactionHash: string): Promise<BigNumber>;
}Usage Examples:
import { OptimismProvider } from "@ethereum-waffle/optimism";
import { BigNumber } from "ethers";
// Create provider connected to Optimism testnet
const provider = new OptimismProvider("https://goerli.optimism.io");
// Get test wallets (funded with testnet ETH)
const wallets = provider.getWallets();
const [wallet, otherWallet] = wallets;
// Deploy contract on Optimism
const contract = await deployContract(wallet, ContractJson, [constructorArgs]);
// Execute transaction
const tx = await contract.someFunction();
const receipt = await tx.wait();
// Calculate L1 fee (unique to Optimism)
const l1Fee = await provider.getL1Fee(receipt.transactionHash);
console.log("L1 Fee:", ethers.utils.formatEther(l1Fee), "ETH");
// Total transaction cost on Optimism includes both L2 gas and L1 fee
const l2GasCost = receipt.gasUsed.mul(receipt.effectiveGasPrice);
const totalCost = l2GasCost.add(l1Fee);
console.log("Total transaction cost:", ethers.utils.formatEther(totalCost), "ETH");Specialized testing patterns for Optimism-specific functionality including L1 fee verification and cross-layer interactions.
L1 Fee Testing:
import { OptimismProvider } from "@ethereum-waffle/optimism";
import { expect } from "chai";
describe("Optimism L1 Fee Testing", () => {
let provider: OptimismProvider;
let wallet: Wallet;
beforeEach(() => {
provider = new OptimismProvider("https://goerli.optimism.io");
[wallet] = provider.getWallets();
});
it("should calculate L1 fees correctly", async () => {
const contract = await deployContract(wallet, SimpleContractJson);
// Execute transaction with calldata
const tx = await contract.setData("0x" + "00".repeat(1000)); // Large calldata
const receipt = await tx.wait();
// L1 fee should be proportional to calldata size
const l1Fee = await provider.getL1Fee(receipt.transactionHash);
expect(l1Fee).to.be.above(0);
// Compare with smaller calldata transaction
const tx2 = await contract.setData("0x00");
const receipt2 = await tx2.wait();
const l1Fee2 = await provider.getL1Fee(receipt2.transactionHash);
expect(l1Fee).to.be.above(l1Fee2);
});
it("should include L1 fees in balance changes", async () => {
const initialBalance = await wallet.getBalance();
const tx = await wallet.sendTransaction({
to: otherWallet.address,
value: ethers.utils.parseEther("0.1")
});
const receipt = await tx.wait();
const finalBalance = await wallet.getBalance();
const l1Fee = await provider.getL1Fee(receipt.transactionHash);
const l2GasCost = receipt.gasUsed.mul(receipt.effectiveGasPrice);
const expectedBalance = initialBalance
.sub(ethers.utils.parseEther("0.1")) // Transfer amount
.sub(l2GasCost) // L2 gas cost
.sub(l1Fee); // L1 fee
expect(finalBalance).to.equal(expectedBalance);
});
});Integration with Standard Waffle Testing:
import { OptimismProvider } from "@ethereum-waffle/optimism";
import { solidity } from "ethereum-waffle";
import { use, expect } from "chai";
use(solidity);
describe("Contract on Optimism", () => {
let provider: OptimismProvider;
let wallet: Wallet;
let contract: Contract;
beforeEach(async () => {
provider = new OptimismProvider(process.env.OPTIMISM_RPC_URL);
[wallet] = provider.getWallets();
contract = await deployContract(wallet, TokenContractJson, ["OptimismToken", "OPT"]);
});
it("should work with standard Waffle matchers", async () => {
const [, recipient] = provider.getWallets();
// Standard Waffle matchers work with OptimismProvider
await expect(contract.transfer(recipient.address, 100))
.to.emit(contract, "Transfer")
.withArgs(wallet.address, recipient.address, 100);
await expect(() => contract.transfer(recipient.address, 100))
.to.changeTokenBalance(contract, recipient, 100);
});
it("should handle L1 fee calculation for complex transactions", async () => {
// Deploy another contract to interact with
const secondContract = await deployContract(wallet, SecondContractJson);
// Complex transaction with multiple contract calls
const tx = await contract.complexInteraction(
secondContract.address,
"0x" + "ff".repeat(500) // Large calldata
);
const receipt = await tx.wait();
// L1 fee should reflect the complexity of the transaction
const l1Fee = await provider.getL1Fee(receipt.transactionHash);
expect(l1Fee).to.be.above(ethers.utils.parseUnits("0.001", "ether"));
});
});Cross-Layer Testing Utilities:
import { OptimismProvider } from "@ethereum-waffle/optimism";
import { MockProvider } from "ethereum-waffle";
// Utility for testing contracts that work on both L1 and L2
async function testOnBothLayers(contractJson: any, testFn: (contract: Contract, provider: any) => Promise<void>) {
// Test on L1 (MockProvider)
const l1Provider = new MockProvider();
const [l1Wallet] = l1Provider.getWallets();
const l1Contract = await deployContract(l1Wallet, contractJson);
console.log("Testing on L1...");
await testFn(l1Contract, l1Provider);
// Test on L2 (OptimismProvider)
const l2Provider = new OptimismProvider(process.env.OPTIMISM_RPC_URL);
const [l2Wallet] = l2Provider.getWallets();
const l2Contract = await deployContract(l2Wallet, contractJson);
console.log("Testing on L2...");
await testFn(l2Contract, l2Provider);
}
// Usage
await testOnBothLayers(MyContractJson, async (contract, provider) => {
await contract.basicFunction();
expect(await contract.getValue()).to.equal(42);
});Fee Optimization Testing:
import { OptimismProvider } from "@ethereum-waffle/optimism";
describe("Optimism Fee Optimization", () => {
let provider: OptimismProvider;
let wallet: Wallet;
beforeEach(() => {
provider = new OptimismProvider(process.env.OPTIMISM_RPC_URL);
[wallet] = provider.getWallets();
});
it("should demonstrate calldata optimization benefits", async () => {
const contract = await deployContract(wallet, OptimizedContractJson);
// Unoptimized function with large calldata
const tx1 = await contract.unoptimizedFunction([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]);
const receipt1 = await tx1.wait();
const l1Fee1 = await provider.getL1Fee(receipt1.transactionHash);
// Optimized function with compressed data
const tx2 = await contract.optimizedFunction(0x3ff); // Packed representation
const receipt2 = await tx2.wait();
const l1Fee2 = await provider.getL1Fee(receipt2.transactionHash);
// Optimized version should have lower L1 fee
expect(l1Fee2).to.be.below(l1Fee1);
console.log(`Unoptimized L1 fee: ${ethers.utils.formatEther(l1Fee1)} ETH`);
console.log(`Optimized L1 fee: ${ethers.utils.formatEther(l1Fee2)} ETH`);
console.log(`Savings: ${ethers.utils.formatEther(l1Fee1.sub(l1Fee2))} ETH`);
});
it("should verify zero-data transactions have minimal L1 fees", async () => {
// Simple ETH transfer (minimal calldata)
const tx = await wallet.sendTransaction({
to: ethers.Wallet.createRandom().address,
value: ethers.utils.parseEther("0.001")
});
const receipt = await tx.wait();
const l1Fee = await provider.getL1Fee(receipt.transactionHash);
// L1 fee should be minimal for simple transfers
expect(l1Fee).to.be.below(ethers.utils.parseUnits("0.001", "ether"));
});
});OptimismProvider implements the TestProvider interface, making it compatible with all Waffle testing utilities while adding L2-specific functionality.
// OptimismProvider implements TestProvider interface
interface TestProvider {
getWallets(): Wallet[];
getL1Fee?(txHash: string): Promise<BigNumber>;
}Usage with Generic Test Functions:
import { OptimismProvider } from "@ethereum-waffle/optimism";
import { MockProvider, TestProvider } from "ethereum-waffle";
// Generic test function that works with any TestProvider
async function testContract(provider: TestProvider, contractJson: any) {
const wallets = provider.getWallets();
const [wallet] = wallets;
const contract = await deployContract(wallet, contractJson);
await contract.testFunction();
// Check if provider supports L1 fee calculation
if (provider.getL1Fee) {
const lastTx = await contract.provider.getTransaction(
contract.deployTransaction.hash
);
const receipt = await lastTx.wait();
const l1Fee = await provider.getL1Fee(receipt.transactionHash);
console.log("L1 Fee:", ethers.utils.formatEther(l1Fee));
}
}
// Works with both MockProvider and OptimismProvider
const mockProvider = new MockProvider();
const optimismProvider = new OptimismProvider(process.env.OPTIMISM_RPC_URL);
await testContract(mockProvider, MyContractJson); // L1 testing
await testContract(optimismProvider, MyContractJson); // L2 testing with L1 fees