├── .env.example
├── .gitignore
├── LICENSE
├── README.md
├── assets
└── banner.png
├── contracts
└── ExampleContract.sol
├── hardhat.config.ts
├── package.json
├── scripts
├── gasEstimation.ts
└── helpers
│ ├── oracle.ts
│ ├── transactions.ts
│ └── utils.ts
├── tsconfig.json
└── yarn.lock
/.env.example:
--------------------------------------------------------------------------------
1 | RPC_SCROLL=https://alpha-rpc.scroll.io/l2
2 | EXAMPLE_CONTRACT_ADDRESS=0xc37ee92c73753B46eB865Ee156d89741ad0a8777
3 | ORACLE_PRECOMPILE_ADDRESS=0x5300000000000000000000000000000000000002
4 |
5 | PRIVATE_KEY=
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | .env
3 | coverage
4 | coverage.json
5 | typechain
6 | typechain-types
7 |
8 | # Hardhat files
9 | cache
10 | artifacts
11 |
12 | .idea
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2023 Scroll
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | # Scroll Gas Estimation Example
4 | This project demonstrates how to use estimate gas on Scroll
5 |
6 | ## What do I need to have before I start?
7 | To run the project, you need:
8 | - [Node.js](https://nodejs.org/en/) (Version 16 works)
9 | - [Yarn](https://yarnpkg.com/)
10 |
11 | ## How to run
12 | 1. Navigate to the project root
13 | 2. Run `yarn` to install the dependencies
14 | 3. Fill the `.env` values with yours
15 | 4. Run the script using `yarn gas:estimate`
16 |
17 | ## Deployments
18 | 📜 Scroll Alpha Testnet:
19 | `ExampleContract`: [0xc37ee92c73753B46eB865Ee156d89741ad0a8777](https://blockscout.scroll.io/address/0xc37ee92c73753B46eB865Ee156d89741ad0a8777)
--------------------------------------------------------------------------------
/assets/banner.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/scroll-tech/gas-estimation-example/2cdc32f34ad7c9ad7e8ef1d051a1fda011337070/assets/banner.png
--------------------------------------------------------------------------------
/contracts/ExampleContract.sol:
--------------------------------------------------------------------------------
1 | pragma solidity ^0.8.17;
2 |
3 | contract ExampleContract {
4 | uint public exampleVariable;
5 |
6 | function setExampleVariable(uint _exampleVariable) external {
7 | exampleVariable = _exampleVariable;
8 | }
9 | }
--------------------------------------------------------------------------------
/hardhat.config.ts:
--------------------------------------------------------------------------------
1 | import { HardhatUserConfig } from "hardhat/config";
2 | import "@nomicfoundation/hardhat-toolbox";
3 | import "hardhat-dependency-compiler";
4 |
5 | require("dotenv").config();
6 | const {
7 | RPC_SCROLL,
8 | PRIVATE_KEY
9 | } = process.env;
10 |
11 | const config: HardhatUserConfig = {
12 | solidity: {
13 | version: "0.8.17",
14 | settings: {
15 | optimizer: {
16 | enabled: true,
17 | runs: 200
18 | },
19 | evmVersion: "london"
20 | }
21 | },
22 | networks: {
23 | scroll: {
24 | url: RPC_SCROLL,
25 | accounts: [`0x${PRIVATE_KEY}`]
26 | }
27 | },
28 | defaultNetwork: "scroll",
29 | dependencyCompiler: {
30 | paths: [
31 | "@scroll-tech/contracts/L2/predeploys/IL1GasPriceOracle.sol"
32 | ]
33 | }
34 | };
35 |
36 | export default config;
37 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "gas-estimation-example",
3 | "version": "1.0.0",
4 | "main": "index.js",
5 | "license": "MIT",
6 | "devDependencies": {
7 | "@nomicfoundation/hardhat-chai-matchers": "^2.0.0",
8 | "@nomicfoundation/hardhat-ethers": "^3.0.0",
9 | "@nomicfoundation/hardhat-network-helpers": "^1.0.0",
10 | "@nomicfoundation/hardhat-toolbox": "^3.0.0",
11 | "@nomicfoundation/hardhat-verify": "^1.0.0",
12 | "@typechain/ethers-v6": "^0.4.0",
13 | "@typechain/hardhat": "^8.0.0",
14 | "@types/chai": "^4.2.0",
15 | "@types/mocha": ">=9.1.0",
16 | "@types/node": ">=12.0.0",
17 | "chai": "^4.2.0",
18 | "dotenv": "^16.1.4",
19 | "ethers": "^6.4.0",
20 | "hardhat": "^2.15.0",
21 | "hardhat-dependency-compiler": "^1.1.3",
22 | "hardhat-gas-reporter": "^1.0.8",
23 | "solidity-coverage": "^0.8.0",
24 | "ts-node": ">=8.0.0",
25 | "typechain": "^8.1.0",
26 | "typescript": ">=4.5.0"
27 | },
28 | "scripts": {
29 | "test": "hardhat test",
30 | "compile": "hardhat compile",
31 | "gas:estimate": "hardhat run ./scripts/gasEstimation.ts"
32 | },
33 | "dependencies": {
34 | "@ethersproject/transactions": "^5.7.0",
35 | "@scroll-tech/contracts": "^0.0.3"
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/scripts/gasEstimation.ts:
--------------------------------------------------------------------------------
1 | import { ethers } from "hardhat";
2 |
3 | import dotenv from "dotenv";
4 | import {
5 | getSerializedTransaction,
6 | buildUnsignedTransaction,
7 | buildPopulatedExampleContractTransaction
8 | } from "./helpers/transactions";
9 | import { estimateL1Fee, estimateL2Fee } from "./helpers/oracle";
10 | import { getPercentageDifference, getRandomInt } from "./helpers/utils";
11 | import { ContractTransaction } from "ethers";
12 |
13 | dotenv.config();
14 |
15 | const {
16 | EXAMPLE_CONTRACT_ADDRESS,
17 | ORACLE_PRECOMPILE_ADDRESS
18 | } = process.env;
19 |
20 | async function getEstimatedFees(gasOracleAddress: string, populatedTransaction: ContractTransaction, serializedTransaction: string) {
21 | const estimatedL1Fee = await estimateL1Fee(gasOracleAddress, serializedTransaction);
22 | const estimatedL2Fee = await estimateL2Fee(populatedTransaction);
23 | const estimatedTotalFee = estimatedL1Fee + estimatedL2Fee;
24 |
25 | return {
26 | estimatedL1Fee,
27 | estimatedL2Fee,
28 | estimatedTotalFee
29 | }
30 | }
31 |
32 | async function main() {
33 | if (!EXAMPLE_CONTRACT_ADDRESS || !ORACLE_PRECOMPILE_ADDRESS) {
34 | throw new Error("Please fill the .env file with all values");
35 | }
36 |
37 | const { getSigners } = ethers;
38 | const [ signer ] = await getSigners();
39 | const signerAddress = await signer.getAddress()
40 |
41 | const newValueToSetOnExampleContract = getRandomInt(100);
42 |
43 | // Building the transaction and getting the estimated fees
44 | const populatedTransaction = await buildPopulatedExampleContractTransaction(EXAMPLE_CONTRACT_ADDRESS, newValueToSetOnExampleContract);
45 | const unsignedTransaction = await buildUnsignedTransaction(signer, populatedTransaction);
46 | const serializedTransaction = getSerializedTransaction(unsignedTransaction);
47 | const estimatedFees = await getEstimatedFees(ORACLE_PRECOMPILE_ADDRESS, populatedTransaction, serializedTransaction);
48 |
49 | console.log("Estimated L1 fee (wei):", estimatedFees.estimatedL1Fee.toString());
50 | console.log("Estimated L2 fee (wei):", estimatedFees.estimatedL2Fee.toString());
51 | console.log("Estimated total fee (wei): ", estimatedFees.estimatedTotalFee.toString());
52 | console.log("\n");
53 |
54 | // Executing the transaction on-chain and verifying actual values
55 | const signerBalanceBefore = await ethers.provider.getBalance(signerAddress);
56 | const exampleContract = await ethers.getContractAt("ExampleContract", EXAMPLE_CONTRACT_ADDRESS, signer);
57 | const tx = await exampleContract.setExampleVariable(newValueToSetOnExampleContract);
58 | const txReceipt = await tx.wait(5);
59 | const signerBalanceAfter = await ethers.provider.getBalance(signerAddress);
60 |
61 | if (!txReceipt) {
62 | throw new Error("Failed fetching the tx receipt, please try running the script again");
63 | }
64 |
65 | const totalFee = signerBalanceBefore - signerBalanceAfter;
66 | const l2Fee = txReceipt.gasUsed * txReceipt.gasPrice;
67 | const l1Fee = totalFee - l2Fee;
68 |
69 | console.log("Actual L1 fee (wei):", l1Fee.toString());
70 | console.log("Actual L2 fee (wei):", l2Fee.toString());
71 | console.log("Actual total fee (wei): ", totalFee.toString());
72 |
73 | console.log("\n");
74 |
75 | console.log("(actual fee - estimated fee)");
76 | console.log(`Difference L1 fee (wei): ${l1Fee - estimatedFees.estimatedL1Fee} (${getPercentageDifference(l1Fee, estimatedFees.estimatedL1Fee)}%)`);
77 | console.log(`Difference L2 fee (wei): ${l2Fee - estimatedFees.estimatedL2Fee} (${getPercentageDifference(l2Fee, estimatedFees.estimatedL2Fee)}%)`);
78 | console.log(`Difference total fee (wei): ${totalFee - estimatedFees.estimatedTotalFee} (${getPercentageDifference(totalFee, estimatedFees.estimatedTotalFee)}%)`);
79 | }
80 |
81 | main().catch((error) => {
82 | console.error(error);
83 | process.exitCode = 1;
84 | });
--------------------------------------------------------------------------------
/scripts/helpers/oracle.ts:
--------------------------------------------------------------------------------
1 | import { ethers } from "hardhat";
2 | import { ContractTransaction } from "ethers";
3 |
4 | export async function estimateL1Fee(gasOraclePrecompileAddress: string, unsignedSerializedTransaction: string): Promise {
5 | const l1GasOracle = await ethers.getContractAt("IL1GasPriceOracle", gasOraclePrecompileAddress);
6 |
7 | return l1GasOracle.getL1Fee(unsignedSerializedTransaction);
8 | }
9 |
10 | export async function estimateL2Fee(tx: ContractTransaction): Promise {
11 | const gasToUse = await ethers.provider.estimateGas(tx);
12 | const feeData = await ethers.provider.getFeeData();
13 | const gasPrice = feeData.gasPrice;
14 |
15 | if (!gasPrice) {
16 | throw new Error("There was an error estimating L2 fee");
17 | }
18 |
19 | return gasToUse * gasPrice;
20 | }
--------------------------------------------------------------------------------
/scripts/helpers/transactions.ts:
--------------------------------------------------------------------------------
1 | import { HardhatEthersSigner } from "@nomicfoundation/hardhat-ethers/signers";
2 | import { ethers } from "hardhat";
3 | import { serialize, UnsignedTransaction } from "@ethersproject/transactions";
4 | import { ContractTransaction } from "ethers";
5 |
6 | export async function buildPopulatedExampleContractTransaction(exampleContractAddress: string, newValueToSet: number): Promise {
7 | const exampleContract = await ethers.getContractAt("ExampleContract", exampleContractAddress);
8 |
9 | return exampleContract.setExampleVariable.populateTransaction(newValueToSet);
10 | }
11 |
12 | export async function buildUnsignedTransaction(signer: HardhatEthersSigner, populatedTransaction: ContractTransaction): Promise {
13 | const nonce = await signer.getNonce();
14 |
15 | return {
16 | data: populatedTransaction.data,
17 | to: populatedTransaction.to,
18 | gasPrice: populatedTransaction.gasPrice,
19 | gasLimit: populatedTransaction.gasLimit,
20 | value: populatedTransaction.value,
21 | nonce,
22 | };
23 | }
24 |
25 | export function getSerializedTransaction(tx: UnsignedTransaction) {
26 | return serialize(tx);
27 | }
--------------------------------------------------------------------------------
/scripts/helpers/utils.ts:
--------------------------------------------------------------------------------
1 | export function getRandomInt(max: number) {
2 | return Math.floor(Math.random() * max);
3 | }
4 |
5 | export function getBigNumberDivision(a: bigint, b: bigint): number {
6 | return Number(a) / Number(b);
7 | }
8 |
9 | export function getPercentageDifference(a: bigint, b: bigint): number {
10 | let largerNum, smallerNum, sign = 1;
11 |
12 | if ((a - b) === 0n) {
13 | return 0;
14 | }
15 |
16 | if (a == 0n || b == 0n) {
17 | return 100;
18 | }
19 |
20 | if (a > b) {
21 | largerNum = a;
22 | smallerNum = b;
23 | } else {
24 | largerNum = b;
25 | smallerNum = a;
26 | sign = -1;
27 | }
28 |
29 | const difference = getBigNumberDivision(largerNum, smallerNum);
30 |
31 | return ((difference - 1) * 100) * sign;
32 | }
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es2020",
4 | "module": "commonjs",
5 | "esModuleInterop": true,
6 | "forceConsistentCasingInFileNames": true,
7 | "strict": true,
8 | "skipLibCheck": true,
9 | "resolveJsonModule": true
10 | }
11 | }
12 |
--------------------------------------------------------------------------------