From bbde6ea45b3fdf4c6d790952844e1160d7c3e3b1 Mon Sep 17 00:00:00 2001 From: Dan Forbes Date: Fri, 11 Oct 2024 12:41:14 -0700 Subject: [PATCH] Add Gas Estimation Guide (#7319) * Add Gas Estimation Guide Closes #7299 * Update Docs for calculateFeeData * Add Access List Docs * Update docs/docs/guides/04_transactions/gas-and-fees.md Co-authored-by: Kris <605420+krzysu@users.noreply.github.com> * Update docs/docs/guides/04_transactions/gas-and-fees.md Co-authored-by: Alex * Update docs/docs/guides/04_transactions/gas-and-fees.md Co-authored-by: Kris <605420+krzysu@users.noreply.github.com> * Don't Mutate Values in Docs * Add Links to Gas Estimation Docs --------- Co-authored-by: Kris <605420+krzysu@users.noreply.github.com> Co-authored-by: Alex --- .../guides/04_transactions/gas-and-fees.md | 110 ++++++++++++++++++ .../guides/04_transactions/transactions.md | 4 + .../mastering_smart_contracts.md | 4 + .../smart_contracts_guide.md | 4 + packages/web3-eth/src/web3_eth.ts | 25 ++-- 5 files changed, 139 insertions(+), 8 deletions(-) create mode 100644 docs/docs/guides/04_transactions/gas-and-fees.md diff --git a/docs/docs/guides/04_transactions/gas-and-fees.md b/docs/docs/guides/04_transactions/gas-and-fees.md new file mode 100644 index 00000000000..6e67fb03775 --- /dev/null +++ b/docs/docs/guides/04_transactions/gas-and-fees.md @@ -0,0 +1,110 @@ +--- +sidebar_position: 3 +sidebar_label: 'Gas and Priority Fees' +--- + +# Gas and Priority Fees + +To prevent spam and reward node operators, Ethereum uses a mechanism called ["gas"](https://ethereum.org/en/gas/), which is a cost that is charged to execute a transaction. Gas costs are associated with all transactions that require computation or update state (e.g. transferring tokens from one account to another). Gas costs are not applied to requests that only involve reading state (e.g. querying the balance of an account). The amount of gas required to pay for a transaction is calculated based on the actual computational actions that are required to execute that transaction. For instance, a simple transaction to transfer ETH from one account to another will require less gas than invoking a complicated smart contract function that involves lots of computation and storage. The cost of gas varies based on network usage. Gas costs more during period of high network activity. The cost per unit of gas is known as the "base fee". + +In addition to the calculated gas cost, an Ethereum transaction can specify an optional priority fee, which is an additional fee that is paid directly to the operator of the node that executes the transaction. Priority fees are intended to incentive node operators to execute transactions. A priority fee is specified as a value in addition to the base fee, which means that the total cost of the priority fee is always a factor of the amount of gas required to execute a transaction. + +With the above in mind, the total cost of fees associated with a transaction is calculated as follows: + +Total cost of fees = _units of gas used \* (base fee + priority fee)_ + +## Estimating Gas + +The Ethereum JSON-RPC specifies the [`eth_estimateGas` method](https://ethereum.org/en/developers/docs/apis/json-rpc/#eth_estimategas), which accepts a transaction and returns an estimate of the amount of gas required to execute that transaction. The transaction will not be executed and added to the blockchain. The estimate may be different (typically more) than the amount of gas actually used by the transaction for a variety of reasons, including EVM mechanics, node performance, and changes to the state of a smart contract. To invoke the `eth_estimateGas` RPC method, use the [`Web3Eth.estimateGas` method](/api/web3-eth/class/Web3Eth#estimateGas) and provide the [`Transaction`](/api/web3-types/interface/Transaction) for which to estimate gas. + +Web3.js transactions may specify a gas limit (the maximum amount of gas they are able to consume) by providing the [`Transaction.gas` property](/api/web3/namespace/types#gas). If the specified gas limit is less than the actual amount of gas required to execute the transaction, the transaction will consume an amount of gas equal to the gas limit, which is not refunded, before failing and reverting any state changes made by the transaction. + +```ts +const transactionDraft: Transaction = { + from: '', + to: '', + value: web3.utils.ethUnitMap.ether, +}; + +const gas: bigint = await web3.eth.estimateGas(transactionDraft); + +const transaction: Transaction = { + ...transactionDraft, + gas, +}; +``` + +## Calculating Fees + +Web3.js exposes a helper, the [`Web3Eth.calculateFeeData` method](/api/web3-eth/class/Web3Eth#calculateFeeData), that can be used to intelligently calculate the value of the base and priority fees to specify for a transaction. `Web3Eth.calculateFeeData` accepts two optional parameters: `baseFeePerGasFactor` (default value: 2) and `alternativeMaxPriorityFeePerGas` (default value: 1 gwei). Both optional parameters are used in calculating the `maxFeePerGas`, which is described below. The return type of `Web3Eth.calculateFeeData` implements the [`FeeData` interface](/api/web3/namespace/types/#FeeData), which specifies four values: + +- `baseFeePerGas`: base fee from the last block +- `gasPrice`: result of the [`eth_gasPrice` RPC method](https://ethereum.org/en/developers/docs/apis/json-rpc/#eth_gasprice) (legacy purposes only) +- `maxPriorityFeePerGas`: result of the [`eth_maxPriorityFeePerGas` RPC method](https://github.com/ethereum/execution-apis/blob/4140e528360fea53c34a766d86a000c6c039100e/src/eth/fee_market.yaml#L29-L42) +- `maxFeePerGas`: calculated as `baseFeePerGas * baseFeePerGasFactor + (maxPriorityFeePerGas ?? alternativeMaxPriorityFeePerGas)` + +Web3.js transactions may specify `maxFeePerGas` and `maxPriorityFeePerGas` values. If both values are specified, `maxFeePerGas` must be greater than or equal to `maxPriorityFeePerGas`. If `maxFeePerGas` is less than the current base fee, the transaction will not execute until the base fee drops to a value that is less than or equal to the `maxFeePerGas`. + +```ts +const transactionDraft: Transaction = { + from: '', + to: '', + value: web3.utils.ethUnitMap.ether, +}; + +const feeData: FeeData = await web3.eth.calculateFeeData(); + +const transaction: Transaction = { + ...transactionDraft, + maxFeePerGas: feeData.maxFeePerGas, + maxPriorityFeePerGas: feeData.maxPriorityFeePerGas, +}; +``` + +## Generating Access Lists + +An access list specifies the addresses and storage keys that a transaction plans to access. Specifying these elements in advance makes a transaction's gas costs more predictable. + +The Ethereum JSON-RPC specifies the [`eth_createAccessList` method](https://github.com/ethereum/execution-apis/blob/4140e528360fea53c34a766d86a000c6c039100e/src/eth/execute.yaml#L54-L97), which accepts a transaction and returns an object that lists the addresses and storage keys that the transaction will access, as well as the approximate gas cost for the transaction if the access list is included. The transaction will not be executed and added to the blockchain. To invoke the `eth_createAccessList` RPC method, use the [`Web3Eth.createAccessList` method](/api/web3-eth/function/createAccessList) and provide the [`TransactionForAccessList`](/api/web3-types/interface/TransactionForAccessList) for which to generate the access list. + +Web3.js transactions may specify an access list by providing the [`Transaction.accessList` property](/api/web3/namespace/types#accessList). + +```ts +const transactionDraft: TransactionForAccessList = { + from: '', + to: '', + value: web3.utils.ethUnitMap.ether, +}; + +const accessListResult: AccessListResult = await web3.eth.createAccessList(transactionDraft); + +const transaction: Transaction = { + ...transactionDraft, + accessList: accessListResult.accessList, + gas: accessListResult.gasUsed, +}; +``` + +## Smart Contract Fees + +The following example demonstrates specifying fee data and creating an access list for a transaction that invokes a [smart contract](/guides/smart_contracts/smart_contracts_guide) function: + +```ts +const transfer: NonPayableMethodObject = erc20.methods.transfer(receiver.address, 1); + +const transferOpts: NonPayableCallOptions = { from: sender.address }; +const accessListResult: AccessListResult = await transfer.createAccessList(transferOpts); +const transactionDraft: Transaction = transfer.populateTransaction(transferOpts); + +const feeData: FeeData = await web3.eth.calculateFeeData(); + +const transferTxn: Transaction = { + ...transactionDraft, + maxFeePerGas: feeData.maxFeePerGas, + maxPriorityFeePerGas: feeData.maxPriorityFeePerGas, + accessList: accessListResult.accessList, + gas: accessListResult.gasUsed, +}; + +const receipt: TransactionReceipt = await web3.eth.sendTransaction(transferTxn); +``` diff --git a/docs/docs/guides/04_transactions/transactions.md b/docs/docs/guides/04_transactions/transactions.md index 8b830554593..35078f053ec 100644 --- a/docs/docs/guides/04_transactions/transactions.md +++ b/docs/docs/guides/04_transactions/transactions.md @@ -231,6 +231,10 @@ Final receiver balance: 100n Note that the sender's balance has decreased by more than the amount that was transferred to the receiver. This is because the sender's balance is also used to pay for the transaction's [gas fees](https://ethereum.org/en/developers/docs/gas/). The transaction receipt specifies the amount of gas that was used (`cumulativeGasUsed`) and the gas price in wei (`effectiveGasPrice`). The total amount deducted from the sender's balance is equal to the amount transferred plus the cost of the gas fees (`cumulativeGasUsed` multiplied by `effectiveGasPrice`). +:::info +Gas and other fees are important topics for Ethereum developers to understand. Refer to the [Gas and Priority Fees guide](/guides/transactions/gas-and-fees) to learn more. +::: + ## Step 5: Send a Transaction and Subscribe to Its Events In the previous example, the `transaction-receipt.js` script demonstrates sending a transaction to the network and reviewing the results after it has been successfully received. However, there are more stages to the [transaction lifecycle](https://ethereum.org/en/developers/docs/transactions/#transaction-lifecycle) and Web3.js makes it easy to subscribe to these lifecycle stages and create custom handlers for each one. Web3.js supports subscriptions for the following transaction lifecycle events: diff --git a/docs/docs/guides/05_smart_contracts/mastering_smart_contracts.md b/docs/docs/guides/05_smart_contracts/mastering_smart_contracts.md index aa129c5ef07..46beb429dfa 100644 --- a/docs/docs/guides/05_smart_contracts/mastering_smart_contracts.md +++ b/docs/docs/guides/05_smart_contracts/mastering_smart_contracts.md @@ -200,6 +200,10 @@ contract.methods.METHOD_NAME(METHOD_PARAMETERS).send({ from: '0x...' }); contract.methods.METHOD_NAME(METHOD_PARAMETERS).call(); ``` +:::tip +Refer to the [Gas and Priority Fees guide](/guides/transactions/gas-and-fees#smart-contract-fees) to learn how to control and optimize fees when using Web3.js to interact with smart contracts. +::: + - **events**: An object mapping your contract's events, allowing you to subscribe to them. And here is an example on how to use it: diff --git a/docs/docs/guides/05_smart_contracts/smart_contracts_guide.md b/docs/docs/guides/05_smart_contracts/smart_contracts_guide.md index f1d7d537fc3..b913e372ddb 100644 --- a/docs/docs/guides/05_smart_contracts/smart_contracts_guide.md +++ b/docs/docs/guides/05_smart_contracts/smart_contracts_guide.md @@ -362,6 +362,10 @@ interact(); This code uses the previously generated ABI and contract address to instantiate a [`Contract`](/api/web3-eth-contract/class/Contract) object for interacting with the `MyContract` smart contract. It gets the current value of `myNumber` from `MyContract`, logs it, updates it, and gets its updated value. It logs the updated `myNumber` value and the [transaction hash](https://help.coinbase.com/en-au/coinbase/getting-started/crypto-education/what-is-a-transaction-hash-hash-id) to the console. +:::info +For the purposes of this tutorial, the above script uses relatively arbitrary values for `gas` and `gasPrice` when demonstrating how to interact with a smart contract. Refer to the [Gas and Priority Fees guide](/guides/transactions/gas-and-fees#smart-contract-fees) to learn how to properly set these values. +::: + Run the following command to interact with the smart contract: ```bash diff --git a/packages/web3-eth/src/web3_eth.ts b/packages/web3-eth/src/web3_eth.ts index a9db13d5abb..6aa4f2ab89d 100644 --- a/packages/web3-eth/src/web3_eth.ts +++ b/packages/web3-eth/src/web3_eth.ts @@ -273,28 +273,37 @@ export class Web3Eth extends Web3Context { * gasPrice: 20000000000n, - * maxFeePerGas: 20000000000n, + * maxFeePerGas: 60000000000n, * maxPriorityFeePerGas: 20000000000n, - * baseFeePerGas: 20000000000n + * baseFeePerGas: 20000000000n * } * - * web3.eth.calculateFeeData(ethUnitMap.Gwei, 2n).then(console.log); + * web3.eth.calculateFeeData(1n).then(console.log); * > { * gasPrice: 20000000000n, * maxFeePerGas: 40000000000n, * maxPriorityFeePerGas: 20000000000n, - * baseFeePerGas: 20000000000n + * baseFeePerGas: 20000000000n + * } + * + * web3.eth.calculateFeeData(3n).then(console.log); + * > { + * gasPrice: 20000000000n, + * maxFeePerGas: 80000000000n, + * maxPriorityFeePerGas: 20000000000n, + * baseFeePerGas: 20000000000n * } * ``` */