github.com/ethereum-optimism/optimism@v1.7.2/packages/contracts-bedrock/src/L1/ResourceMetering.sol (about) 1 // SPDX-License-Identifier: MIT 2 pragma solidity 0.8.15; 3 4 import { Initializable } from "@openzeppelin/contracts/proxy/utils/Initializable.sol"; 5 import { Math } from "@openzeppelin/contracts/utils/math/Math.sol"; 6 import { Burn } from "src/libraries/Burn.sol"; 7 import { Arithmetic } from "src/libraries/Arithmetic.sol"; 8 9 /// @custom:upgradeable 10 /// @title ResourceMetering 11 /// @notice ResourceMetering implements an EIP-1559 style resource metering system where pricing 12 /// updates automatically based on current demand. 13 abstract contract ResourceMetering is Initializable { 14 /// @notice Represents the various parameters that control the way in which resources are 15 /// metered. Corresponds to the EIP-1559 resource metering system. 16 /// @custom:field prevBaseFee Base fee from the previous block(s). 17 /// @custom:field prevBoughtGas Amount of gas bought so far in the current block. 18 /// @custom:field prevBlockNum Last block number that the base fee was updated. 19 struct ResourceParams { 20 uint128 prevBaseFee; 21 uint64 prevBoughtGas; 22 uint64 prevBlockNum; 23 } 24 25 /// @notice Represents the configuration for the EIP-1559 based curve for the deposit gas 26 /// market. These values should be set with care as it is possible to set them in 27 /// a way that breaks the deposit gas market. The target resource limit is defined as 28 /// maxResourceLimit / elasticityMultiplier. This struct was designed to fit within a 29 /// single word. There is additional space for additions in the future. 30 /// @custom:field maxResourceLimit Represents the maximum amount of deposit gas that 31 /// can be purchased per block. 32 /// @custom:field elasticityMultiplier Determines the target resource limit along with 33 /// the resource limit. 34 /// @custom:field baseFeeMaxChangeDenominator Determines max change on fee per block. 35 /// @custom:field minimumBaseFee The min deposit base fee, it is clamped to this 36 /// value. 37 /// @custom:field systemTxMaxGas The amount of gas supplied to the system 38 /// transaction. This should be set to the same 39 /// number that the op-node sets as the gas limit 40 /// for the system transaction. 41 /// @custom:field maximumBaseFee The max deposit base fee, it is clamped to this 42 /// value. 43 struct ResourceConfig { 44 uint32 maxResourceLimit; 45 uint8 elasticityMultiplier; 46 uint8 baseFeeMaxChangeDenominator; 47 uint32 minimumBaseFee; 48 uint32 systemTxMaxGas; 49 uint128 maximumBaseFee; 50 } 51 52 /// @notice EIP-1559 style gas parameters. 53 ResourceParams public params; 54 55 /// @notice Reserve extra slots (to a total of 50) in the storage layout for future upgrades. 56 uint256[48] private __gap; 57 58 /// @notice Meters access to a function based an amount of a requested resource. 59 /// @param _amount Amount of the resource requested. 60 modifier metered(uint64 _amount) { 61 // Record initial gas amount so we can refund for it later. 62 uint256 initialGas = gasleft(); 63 64 // Run the underlying function. 65 _; 66 67 // Run the metering function. 68 _metered(_amount, initialGas); 69 } 70 71 /// @notice An internal function that holds all of the logic for metering a resource. 72 /// @param _amount Amount of the resource requested. 73 /// @param _initialGas The amount of gas before any modifier execution. 74 function _metered(uint64 _amount, uint256 _initialGas) internal { 75 // Update block number and base fee if necessary. 76 uint256 blockDiff = block.number - params.prevBlockNum; 77 78 ResourceConfig memory config = _resourceConfig(); 79 int256 targetResourceLimit = 80 int256(uint256(config.maxResourceLimit)) / int256(uint256(config.elasticityMultiplier)); 81 82 if (blockDiff > 0) { 83 // Handle updating EIP-1559 style gas parameters. We use EIP-1559 to restrict the rate 84 // at which deposits can be created and therefore limit the potential for deposits to 85 // spam the L2 system. Fee scheme is very similar to EIP-1559 with minor changes. 86 int256 gasUsedDelta = int256(uint256(params.prevBoughtGas)) - targetResourceLimit; 87 int256 baseFeeDelta = (int256(uint256(params.prevBaseFee)) * gasUsedDelta) 88 / (targetResourceLimit * int256(uint256(config.baseFeeMaxChangeDenominator))); 89 90 // Update base fee by adding the base fee delta and clamp the resulting value between 91 // min and max. 92 int256 newBaseFee = Arithmetic.clamp({ 93 _value: int256(uint256(params.prevBaseFee)) + baseFeeDelta, 94 _min: int256(uint256(config.minimumBaseFee)), 95 _max: int256(uint256(config.maximumBaseFee)) 96 }); 97 98 // If we skipped more than one block, we also need to account for every empty block. 99 // Empty block means there was no demand for deposits in that block, so we should 100 // reflect this lack of demand in the fee. 101 if (blockDiff > 1) { 102 // Update the base fee by repeatedly applying the exponent 1-(1/change_denominator) 103 // blockDiff - 1 times. Simulates multiple empty blocks. Clamp the resulting value 104 // between min and max. 105 newBaseFee = Arithmetic.clamp({ 106 _value: Arithmetic.cdexp({ 107 _coefficient: newBaseFee, 108 _denominator: int256(uint256(config.baseFeeMaxChangeDenominator)), 109 _exponent: int256(blockDiff - 1) 110 }), 111 _min: int256(uint256(config.minimumBaseFee)), 112 _max: int256(uint256(config.maximumBaseFee)) 113 }); 114 } 115 116 // Update new base fee, reset bought gas, and update block number. 117 params.prevBaseFee = uint128(uint256(newBaseFee)); 118 params.prevBoughtGas = 0; 119 params.prevBlockNum = uint64(block.number); 120 } 121 122 // Make sure we can actually buy the resource amount requested by the user. 123 params.prevBoughtGas += _amount; 124 require( 125 int256(uint256(params.prevBoughtGas)) <= int256(uint256(config.maxResourceLimit)), 126 "ResourceMetering: cannot buy more gas than available gas limit" 127 ); 128 129 // Determine the amount of ETH to be paid. 130 uint256 resourceCost = uint256(_amount) * uint256(params.prevBaseFee); 131 132 // We currently charge for this ETH amount as an L1 gas burn, so we convert the ETH amount 133 // into gas by dividing by the L1 base fee. We assume a minimum base fee of 1 gwei to avoid 134 // division by zero for L1s that don't support 1559 or to avoid excessive gas burns during 135 // periods of extremely low L1 demand. One-day average gas fee hasn't dipped below 1 gwei 136 // during any 1 day period in the last 5 years, so should be fine. 137 uint256 gasCost = resourceCost / Math.max(block.basefee, 1 gwei); 138 139 // Give the user a refund based on the amount of gas they used to do all of the work up to 140 // this point. Since we're at the end of the modifier, this should be pretty accurate. Acts 141 // effectively like a dynamic stipend (with a minimum value). 142 uint256 usedGas = _initialGas - gasleft(); 143 if (gasCost > usedGas) { 144 Burn.gas(gasCost - usedGas); 145 } 146 } 147 148 /// @notice Virtual function that returns the resource config. 149 /// Contracts that inherit this contract must implement this function. 150 /// @return ResourceConfig 151 function _resourceConfig() internal virtual returns (ResourceConfig memory); 152 153 /// @notice Sets initial resource parameter values. 154 /// This function must either be called by the initializer function of an upgradeable 155 /// child contract. 156 function __ResourceMetering_init() internal onlyInitializing { 157 if (params.prevBlockNum == 0) { 158 params = ResourceParams({ prevBaseFee: 1 gwei, prevBoughtGas: 0, prevBlockNum: uint64(block.number) }); 159 } 160 } 161 }