Commit 9b9f78c6 authored by Mark Tyneway's avatar Mark Tyneway Committed by GitHub

Merge pull request #5233 from ethereum-optimism/fix/syscfg-prevent-low-gaslimit-3

contracts-bedrock: modularize deposit resource config
parents cb6c4238 0daba1ef
This source diff could not be displayed because it is too large. You can view the blob instead.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
...@@ -8,6 +8,7 @@ import ( ...@@ -8,6 +8,7 @@ import (
"strings" "strings"
"github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/accounts/abi/bind/backends" "github.com/ethereum/go-ethereum/accounts/abi/bind/backends"
...@@ -22,19 +23,45 @@ import ( ...@@ -22,19 +23,45 @@ import (
"github.com/ethereum-optimism/optimism/op-chain-ops/state" "github.com/ethereum-optimism/optimism/op-chain-ops/state"
) )
var proxies = []string{ var (
"SystemConfigProxy", // proxies represents the set of proxies in front of contracts.
"L2OutputOracleProxy", proxies = []string{
"L1CrossDomainMessengerProxy", "SystemConfigProxy",
"L1StandardBridgeProxy", "L2OutputOracleProxy",
"OptimismPortalProxy", "L1CrossDomainMessengerProxy",
"OptimismMintableERC20FactoryProxy", "L1StandardBridgeProxy",
} "OptimismPortalProxy",
"OptimismMintableERC20FactoryProxy",
var portalMeteringSlot = common.Hash{31: 0x01} }
// portalMeteringSlot is the storage slot containing the metering params.
portalMeteringSlot = common.Hash{31: 0x01}
// zeroHash represents the zero value for a hash.
zeroHash = common.Hash{}
// uint128Max is type(uint128).max and is set in the init function.
uint128Max = new(big.Int)
// The default values for the ResourceConfig, used as part of
// an EIP-1559 curve for deposit gas.
defaultResourceConfig = bindings.ResourceMeteringResourceConfig{
MaxResourceLimit: 20_000_000,
ElasticityMultiplier: 10,
BaseFeeMaxChangeDenominator: 8,
MinimumBaseFee: params.GWei,
SystemTxMaxGas: 1_000_000,
}
)
var zeroHash common.Hash func init() {
var ok bool
uint128Max, ok = new(big.Int).SetString("ffffffffffffffffffffffffffffffff", 16)
if !ok {
panic("bad uint128Max")
}
// Set the maximum base fee on the default config.
defaultResourceConfig.MaximumBaseFee = uint128Max
}
// BuildL1DeveloperGenesis will create a L1 genesis block after creating
// all of the state required for an Optimism network to function.
func BuildL1DeveloperGenesis(config *DeployConfig) (*core.Genesis, error) { func BuildL1DeveloperGenesis(config *DeployConfig) (*core.Genesis, error) {
if config.L2OutputOracleStartingTimestamp != -1 { if config.L2OutputOracleStartingTimestamp != -1 {
return nil, errors.New("l2oo starting timestamp must be -1") return nil, errors.New("l2oo starting timestamp must be -1")
...@@ -67,6 +94,26 @@ func BuildL1DeveloperGenesis(config *DeployConfig) (*core.Genesis, error) { ...@@ -67,6 +94,26 @@ func BuildL1DeveloperGenesis(config *DeployConfig) (*core.Genesis, error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
portalABI, err := bindings.OptimismPortalMetaData.GetAbi()
if err != nil {
return nil, err
}
// Initialize the OptimismPortal without being paused
data, err := portalABI.Pack("initialize", false)
if err != nil {
return nil, fmt.Errorf("cannot abi encode initialize for OptimismPortal: %w", err)
}
if _, err := upgradeProxy(
backend,
opts,
depsByName["OptimismPortalProxy"].Address,
depsByName["OptimismPortal"].Address,
data,
); err != nil {
return nil, fmt.Errorf("cannot upgrade OptimismPortalProxy: %w", err)
}
sysCfgABI, err := bindings.SystemConfigMetaData.GetAbi() sysCfgABI, err := bindings.SystemConfigMetaData.GetAbi()
if err != nil { if err != nil {
return nil, err return nil, err
...@@ -75,7 +122,8 @@ func BuildL1DeveloperGenesis(config *DeployConfig) (*core.Genesis, error) { ...@@ -75,7 +122,8 @@ func BuildL1DeveloperGenesis(config *DeployConfig) (*core.Genesis, error) {
if gasLimit == 0 { if gasLimit == 0 {
gasLimit = defaultL2GasLimit gasLimit = defaultL2GasLimit
} }
data, err := sysCfgABI.Pack(
data, err = sysCfgABI.Pack(
"initialize", "initialize",
config.FinalSystemOwner, config.FinalSystemOwner,
uint642Big(config.GasPriceOracleOverhead), uint642Big(config.GasPriceOracleOverhead),
...@@ -83,6 +131,7 @@ func BuildL1DeveloperGenesis(config *DeployConfig) (*core.Genesis, error) { ...@@ -83,6 +131,7 @@ func BuildL1DeveloperGenesis(config *DeployConfig) (*core.Genesis, error) {
config.BatchSenderAddress.Hash(), config.BatchSenderAddress.Hash(),
gasLimit, gasLimit,
config.P2PSequencerAddress, config.P2PSequencerAddress,
defaultResourceConfig,
) )
if err != nil { if err != nil {
return nil, fmt.Errorf("cannot abi encode initialize for SystemConfig: %w", err) return nil, fmt.Errorf("cannot abi encode initialize for SystemConfig: %w", err)
...@@ -94,7 +143,7 @@ func BuildL1DeveloperGenesis(config *DeployConfig) (*core.Genesis, error) { ...@@ -94,7 +143,7 @@ func BuildL1DeveloperGenesis(config *DeployConfig) (*core.Genesis, error) {
depsByName["SystemConfig"].Address, depsByName["SystemConfig"].Address,
data, data,
); err != nil { ); err != nil {
return nil, err return nil, fmt.Errorf("cannot upgrade SystemConfigProxy: %w", err)
} }
l2ooABI, err := bindings.L2OutputOracleMetaData.GetAbi() l2ooABI, err := bindings.L2OutputOracleMetaData.GetAbi()
...@@ -119,24 +168,6 @@ func BuildL1DeveloperGenesis(config *DeployConfig) (*core.Genesis, error) { ...@@ -119,24 +168,6 @@ func BuildL1DeveloperGenesis(config *DeployConfig) (*core.Genesis, error) {
return nil, err return nil, err
} }
portalABI, err := bindings.OptimismPortalMetaData.GetAbi()
if err != nil {
return nil, err
}
// Initialize the OptimismPortal without being paused
data, err = portalABI.Pack("initialize", false)
if err != nil {
return nil, fmt.Errorf("cannot abi encode initialize for OptimismPortal: %w", err)
}
if _, err := upgradeProxy(
backend,
opts,
depsByName["OptimismPortalProxy"].Address,
depsByName["OptimismPortal"].Address,
data,
); err != nil {
return nil, err
}
l1XDMABI, err := bindings.L1CrossDomainMessengerMetaData.GetAbi() l1XDMABI, err := bindings.L1CrossDomainMessengerMetaData.GetAbi()
if err != nil { if err != nil {
return nil, err return nil, err
...@@ -264,6 +295,7 @@ func deployL1Contracts(config *DeployConfig, backend *backends.SimulatedBackend) ...@@ -264,6 +295,7 @@ func deployL1Contracts(config *DeployConfig, backend *backends.SimulatedBackend)
if gasLimit == 0 { if gasLimit == 0 {
gasLimit = defaultL2GasLimit gasLimit = defaultL2GasLimit
} }
constructors = append(constructors, []deployer.Constructor{ constructors = append(constructors, []deployer.Constructor{
{ {
Name: "SystemConfig", Name: "SystemConfig",
...@@ -274,6 +306,7 @@ func deployL1Contracts(config *DeployConfig, backend *backends.SimulatedBackend) ...@@ -274,6 +306,7 @@ func deployL1Contracts(config *DeployConfig, backend *backends.SimulatedBackend)
config.BatchSenderAddress.Hash(), // left-padded 32 bytes value, version is zero anyway config.BatchSenderAddress.Hash(), // left-padded 32 bytes value, version is zero anyway
gasLimit, gasLimit,
config.P2PSequencerAddress, config.P2PSequencerAddress,
defaultResourceConfig,
}, },
}, },
{ {
...@@ -297,6 +330,7 @@ func deployL1Contracts(config *DeployConfig, backend *backends.SimulatedBackend) ...@@ -297,6 +330,7 @@ func deployL1Contracts(config *DeployConfig, backend *backends.SimulatedBackend)
predeploys.DevL2OutputOracleAddr, predeploys.DevL2OutputOracleAddr,
config.PortalGuardian, config.PortalGuardian,
true, // _paused true, // _paused
predeploys.DevSystemConfigAddr,
}, },
}, },
{ {
...@@ -342,6 +376,7 @@ func l1Deployer(backend *backends.SimulatedBackend, opts *bind.TransactOpts, dep ...@@ -342,6 +376,7 @@ func l1Deployer(backend *backends.SimulatedBackend, opts *bind.TransactOpts, dep
deployment.Args[3].(common.Hash), deployment.Args[3].(common.Hash),
deployment.Args[4].(uint64), deployment.Args[4].(uint64),
deployment.Args[5].(common.Address), deployment.Args[5].(common.Address),
deployment.Args[6].(bindings.ResourceMeteringResourceConfig),
) )
case "L2OutputOracle": case "L2OutputOracle":
_, tx, _, err = bindings.DeployL2OutputOracle( _, tx, _, err = bindings.DeployL2OutputOracle(
...@@ -362,6 +397,7 @@ func l1Deployer(backend *backends.SimulatedBackend, opts *bind.TransactOpts, dep ...@@ -362,6 +397,7 @@ func l1Deployer(backend *backends.SimulatedBackend, opts *bind.TransactOpts, dep
deployment.Args[0].(common.Address), deployment.Args[0].(common.Address),
deployment.Args[1].(common.Address), deployment.Args[1].(common.Address),
deployment.Args[2].(bool), deployment.Args[2].(bool),
deployment.Args[3].(common.Address),
) )
case "L1CrossDomainMessenger": case "L1CrossDomainMessenger":
_, tx, _, err = bindings.DeployL1CrossDomainMessenger( _, tx, _, err = bindings.DeployL1CrossDomainMessenger(
...@@ -421,6 +457,15 @@ func l1Deployer(backend *backends.SimulatedBackend, opts *bind.TransactOpts, dep ...@@ -421,6 +457,15 @@ func l1Deployer(backend *backends.SimulatedBackend, opts *bind.TransactOpts, dep
func upgradeProxy(backend *backends.SimulatedBackend, opts *bind.TransactOpts, proxyAddr common.Address, implAddr common.Address, callData []byte) (*types.Transaction, error) { func upgradeProxy(backend *backends.SimulatedBackend, opts *bind.TransactOpts, proxyAddr common.Address, implAddr common.Address, callData []byte) (*types.Transaction, error) {
var tx *types.Transaction var tx *types.Transaction
code, err := backend.CodeAt(context.Background(), implAddr, nil)
if err != nil {
return nil, err
}
if len(code) == 0 {
return nil, fmt.Errorf("no code at %s", implAddr)
}
proxy, err := bindings.NewProxy(proxyAddr, backend) proxy, err := bindings.NewProxy(proxyAddr, backend)
if err != nil { if err != nil {
return nil, err return nil, err
......
...@@ -8,6 +8,7 @@ import ( ...@@ -8,6 +8,7 @@ import (
"testing" "testing"
"time" "time"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum-optimism/optimism/op-bindings/bindings" "github.com/ethereum-optimism/optimism/op-bindings/bindings"
...@@ -100,6 +101,30 @@ func TestBuildL1DeveloperGenesis(t *testing.T) { ...@@ -100,6 +101,30 @@ func TestBuildL1DeveloperGenesis(t *testing.T) {
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, "Wrapped Ether", name) require.Equal(t, "Wrapped Ether", name)
sysCfg, err := bindings.NewSystemConfig(predeploys.DevSystemConfigAddr, sim)
require.NoError(t, err)
cfg, err := sysCfg.ResourceConfig(&bind.CallOpts{})
require.NoError(t, err)
require.Equal(t, cfg, defaultResourceConfig)
owner, err = sysCfg.Owner(&bind.CallOpts{})
require.NoError(t, err)
require.Equal(t, owner, config.FinalSystemOwner)
overhead, err := sysCfg.Overhead(&bind.CallOpts{})
require.NoError(t, err)
require.Equal(t, overhead.Uint64(), config.GasPriceOracleOverhead)
scalar, err := sysCfg.Scalar(&bind.CallOpts{})
require.NoError(t, err)
require.Equal(t, scalar.Uint64(), config.GasPriceOracleScalar)
batcherHash, err := sysCfg.BatcherHash(&bind.CallOpts{})
require.NoError(t, err)
require.Equal(t, common.Hash(batcherHash), config.BatchSenderAddress.Hash())
gasLimit, err := sysCfg.GasLimit(&bind.CallOpts{})
require.NoError(t, err)
require.Equal(t, gasLimit, uint64(config.L2GenesisBlockGasLimit))
unsafeBlockSigner, err := sysCfg.UnsafeBlockSigner(&bind.CallOpts{})
require.NoError(t, err)
require.Equal(t, unsafeBlockSigner, config.P2PSequencerAddress)
// test that we can do deposits, etc. // test that we can do deposits, etc.
priv, err := crypto.HexToECDSA("ac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80") priv, err := crypto.HexToECDSA("ac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80")
require.NoError(t, err) require.NoError(t, err)
......
...@@ -16,7 +16,7 @@ ...@@ -16,7 +16,7 @@
"l1BlockTime": 15, "l1BlockTime": 15,
"l1GenesisBlockNonce": "0x0", "l1GenesisBlockNonce": "0x0",
"cliqueSignerAddress": "0x0000000000000000000000000000000000000000", "cliqueSignerAddress": "0x0000000000000000000000000000000000000000",
"l1GenesisBlockGasLimit": "0xe4e1c0", "l1GenesisBlockGasLimit": "0x1c9c380",
"l1GenesisBlockDifficulty": "0x1", "l1GenesisBlockDifficulty": "0x1",
"finalSystemOwner": "0x0000000000000000000000000000000000000111", "finalSystemOwner": "0x0000000000000000000000000000000000000111",
"portalGuardian": "0x0000000000000000000000000000000000000112", "portalGuardian": "0x0000000000000000000000000000000000000112",
...@@ -29,7 +29,7 @@ ...@@ -29,7 +29,7 @@
"l1GenesisBlockTimestamp": "0x0", "l1GenesisBlockTimestamp": "0x0",
"l1GenesisBlockBaseFeePerGas": "0x3b9aca00", "l1GenesisBlockBaseFeePerGas": "0x3b9aca00",
"l2GenesisBlockNonce": "0x0", "l2GenesisBlockNonce": "0x0",
"l2GenesisBlockGasLimit": "0xe4e1c0", "l2GenesisBlockGasLimit": "0x1c9c380",
"l2GenesisBlockDifficulty": "0x1", "l2GenesisBlockDifficulty": "0x1",
"l2GenesisBlockMixHash": "0x0000000000000000000000000000000000000000000000000000000000000000", "l2GenesisBlockMixHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
"l2GenesisBlockNumber": "0x0", "l2GenesisBlockNumber": "0x0",
......
...@@ -79,7 +79,7 @@ func MakeDeployParams(t require.TestingT, tp *TestParams) *DeployParams { ...@@ -79,7 +79,7 @@ func MakeDeployParams(t require.TestingT, tp *TestParams) *DeployParams {
L1GenesisBlockNonce: 0, L1GenesisBlockNonce: 0,
CliqueSignerAddress: common.Address{}, // proof of stake, no clique CliqueSignerAddress: common.Address{}, // proof of stake, no clique
L1GenesisBlockTimestamp: hexutil.Uint64(time.Now().Unix()), L1GenesisBlockTimestamp: hexutil.Uint64(time.Now().Unix()),
L1GenesisBlockGasLimit: 15_000_000, L1GenesisBlockGasLimit: 30_000_000,
L1GenesisBlockDifficulty: uint64ToBig(1), L1GenesisBlockDifficulty: uint64ToBig(1),
L1GenesisBlockMixHash: common.Hash{}, L1GenesisBlockMixHash: common.Hash{},
L1GenesisBlockCoinbase: common.Address{}, L1GenesisBlockCoinbase: common.Address{},
...@@ -90,7 +90,7 @@ func MakeDeployParams(t require.TestingT, tp *TestParams) *DeployParams { ...@@ -90,7 +90,7 @@ func MakeDeployParams(t require.TestingT, tp *TestParams) *DeployParams {
FinalizationPeriodSeconds: 12, FinalizationPeriodSeconds: 12,
L2GenesisBlockNonce: 0, L2GenesisBlockNonce: 0,
L2GenesisBlockGasLimit: 15_000_000, L2GenesisBlockGasLimit: 30_000_000,
L2GenesisBlockDifficulty: uint64ToBig(0), L2GenesisBlockDifficulty: uint64ToBig(0),
L2GenesisBlockMixHash: common.Hash{}, L2GenesisBlockMixHash: common.Hash{},
L2GenesisBlockNumber: 0, L2GenesisBlockNumber: 0,
......
...@@ -75,7 +75,7 @@ func DefaultSystemConfig(t *testing.T) SystemConfig { ...@@ -75,7 +75,7 @@ func DefaultSystemConfig(t *testing.T) SystemConfig {
L1GenesisBlockNonce: 4660, L1GenesisBlockNonce: 4660,
CliqueSignerAddress: addresses.CliqueSigner, CliqueSignerAddress: addresses.CliqueSigner,
L1GenesisBlockTimestamp: hexutil.Uint64(time.Now().Unix()), L1GenesisBlockTimestamp: hexutil.Uint64(time.Now().Unix()),
L1GenesisBlockGasLimit: 8_000_000, L1GenesisBlockGasLimit: 30_000_000,
L1GenesisBlockDifficulty: uint642big(1), L1GenesisBlockDifficulty: uint642big(1),
L1GenesisBlockMixHash: common.Hash{}, L1GenesisBlockMixHash: common.Hash{},
L1GenesisBlockCoinbase: common.Address{}, L1GenesisBlockCoinbase: common.Address{},
...@@ -85,7 +85,7 @@ func DefaultSystemConfig(t *testing.T) SystemConfig { ...@@ -85,7 +85,7 @@ func DefaultSystemConfig(t *testing.T) SystemConfig {
L1GenesisBlockBaseFeePerGas: uint642big(7), L1GenesisBlockBaseFeePerGas: uint642big(7),
L2GenesisBlockNonce: 0, L2GenesisBlockNonce: 0,
L2GenesisBlockGasLimit: 8_000_000, L2GenesisBlockGasLimit: 30_000_000,
L2GenesisBlockDifficulty: uint642big(1), L2GenesisBlockDifficulty: uint642big(1),
L2GenesisBlockMixHash: common.Hash{}, L2GenesisBlockMixHash: common.Hash{},
L2GenesisBlockNumber: 0, L2GenesisBlockNumber: 0,
......
This diff is collapsed.
...@@ -69,17 +69,18 @@ ...@@ -69,17 +69,18 @@
➡ contracts/L1/SystemConfig.sol:SystemConfig ➡ contracts/L1/SystemConfig.sol:SystemConfig
======================= =======================
| Name | Type | Slot | Offset | Bytes | Contract | | Name | Type | Slot | Offset | Bytes | Contract |
|---------------|-------------|------|--------|-------|--------------------------------------------| |-----------------|----------------------------------------|------|--------|-------|--------------------------------------------|
| _initialized | uint8 | 0 | 0 | 1 | contracts/L1/SystemConfig.sol:SystemConfig | | _initialized | uint8 | 0 | 0 | 1 | contracts/L1/SystemConfig.sol:SystemConfig |
| _initializing | bool | 0 | 1 | 1 | contracts/L1/SystemConfig.sol:SystemConfig | | _initializing | bool | 0 | 1 | 1 | contracts/L1/SystemConfig.sol:SystemConfig |
| __gap | uint256[50] | 1 | 0 | 1600 | contracts/L1/SystemConfig.sol:SystemConfig | | __gap | uint256[50] | 1 | 0 | 1600 | contracts/L1/SystemConfig.sol:SystemConfig |
| _owner | address | 51 | 0 | 20 | contracts/L1/SystemConfig.sol:SystemConfig | | _owner | address | 51 | 0 | 20 | contracts/L1/SystemConfig.sol:SystemConfig |
| __gap | uint256[49] | 52 | 0 | 1568 | contracts/L1/SystemConfig.sol:SystemConfig | | __gap | uint256[49] | 52 | 0 | 1568 | contracts/L1/SystemConfig.sol:SystemConfig |
| overhead | uint256 | 101 | 0 | 32 | contracts/L1/SystemConfig.sol:SystemConfig | | overhead | uint256 | 101 | 0 | 32 | contracts/L1/SystemConfig.sol:SystemConfig |
| scalar | uint256 | 102 | 0 | 32 | contracts/L1/SystemConfig.sol:SystemConfig | | scalar | uint256 | 102 | 0 | 32 | contracts/L1/SystemConfig.sol:SystemConfig |
| batcherHash | bytes32 | 103 | 0 | 32 | contracts/L1/SystemConfig.sol:SystemConfig | | batcherHash | bytes32 | 103 | 0 | 32 | contracts/L1/SystemConfig.sol:SystemConfig |
| gasLimit | uint64 | 104 | 0 | 8 | contracts/L1/SystemConfig.sol:SystemConfig | | gasLimit | uint64 | 104 | 0 | 8 | contracts/L1/SystemConfig.sol:SystemConfig |
| _resourceConfig | struct ResourceMetering.ResourceConfig | 105 | 0 | 32 | contracts/L1/SystemConfig.sol:SystemConfig |
======================= =======================
➡ contracts/legacy/DeployerWhitelist.sol:DeployerWhitelist ➡ contracts/legacy/DeployerWhitelist.sol:DeployerWhitelist
......
...@@ -4,6 +4,7 @@ pragma solidity 0.8.15; ...@@ -4,6 +4,7 @@ pragma solidity 0.8.15;
import { Initializable } from "@openzeppelin/contracts/proxy/utils/Initializable.sol"; import { Initializable } from "@openzeppelin/contracts/proxy/utils/Initializable.sol";
import { SafeCall } from "../libraries/SafeCall.sol"; import { SafeCall } from "../libraries/SafeCall.sol";
import { L2OutputOracle } from "./L2OutputOracle.sol"; import { L2OutputOracle } from "./L2OutputOracle.sol";
import { SystemConfig } from "./SystemConfig.sol";
import { Constants } from "../libraries/Constants.sol"; import { Constants } from "../libraries/Constants.sol";
import { Types } from "../libraries/Types.sol"; import { Types } from "../libraries/Types.sol";
import { Hashing } from "../libraries/Hashing.sol"; import { Hashing } from "../libraries/Hashing.sol";
...@@ -44,10 +45,15 @@ contract OptimismPortal is Initializable, ResourceMetering, Semver { ...@@ -44,10 +45,15 @@ contract OptimismPortal is Initializable, ResourceMetering, Semver {
uint64 internal constant RECEIVE_DEFAULT_GAS_LIMIT = 100_000; uint64 internal constant RECEIVE_DEFAULT_GAS_LIMIT = 100_000;
/** /**
* @notice Address of the L2OutputOracle. * @notice Address of the L2OutputOracle contract.
*/ */
L2OutputOracle public immutable L2_ORACLE; L2OutputOracle public immutable L2_ORACLE;
/**
* @notice Address of the SystemConfig contract.
*/
SystemConfig public immutable SYSTEM_CONFIG;
/** /**
* @notice Address that has the ability to pause and unpause withdrawals. * @notice Address that has the ability to pause and unpause withdrawals.
*/ */
...@@ -135,19 +141,22 @@ contract OptimismPortal is Initializable, ResourceMetering, Semver { ...@@ -135,19 +141,22 @@ contract OptimismPortal is Initializable, ResourceMetering, Semver {
} }
/** /**
* @custom:semver 1.2.0 * @custom:semver 1.3.0
* *
* @param _l2Oracle Address of the L2OutputOracle contract. * @param _l2Oracle Address of the L2OutputOracle contract.
* @param _guardian Address that can pause deposits and withdrawals. * @param _guardian Address that can pause deposits and withdrawals.
* @param _paused Sets the contract's pausability state. * @param _paused Sets the contract's pausability state.
* @param _config Address of the SystemConfig contract.
*/ */
constructor( constructor(
L2OutputOracle _l2Oracle, L2OutputOracle _l2Oracle,
address _guardian, address _guardian,
bool _paused bool _paused,
) Semver(1, 2, 0) { SystemConfig _config
) Semver(1, 3, 0) {
L2_ORACLE = _l2Oracle; L2_ORACLE = _l2Oracle;
GUARDIAN = _guardian; GUARDIAN = _guardian;
SYSTEM_CONFIG = _config;
initialize(_paused); initialize(_paused);
} }
...@@ -197,6 +206,21 @@ contract OptimismPortal is Initializable, ResourceMetering, Semver { ...@@ -197,6 +206,21 @@ contract OptimismPortal is Initializable, ResourceMetering, Semver {
// Intentionally empty. // Intentionally empty.
} }
/**
* @notice Getter for the resource config. Used internally by the ResourceMetering
* contract. The SystemConfig is the source of truth for the resource config.
*
* @return ResourceMetering.ResourceConfig
*/
function _resourceConfig()
internal
view
override
returns (ResourceMetering.ResourceConfig memory)
{
return SYSTEM_CONFIG.resourceConfig();
}
/** /**
* @notice Proves a withdrawal transaction. * @notice Proves a withdrawal transaction.
* *
......
...@@ -28,44 +28,34 @@ abstract contract ResourceMetering is Initializable { ...@@ -28,44 +28,34 @@ abstract contract ResourceMetering is Initializable {
} }
/** /**
* @notice Maximum amount of the resource that can be used within this block. * @notice Represents the configuration for the EIP-1559 based curve for the deposit gas
* This value cannot be larger than the L2 block gas limit. * market. These values should be set with care as it is possible to set them in
*/ * a way that breaks the deposit gas market. The target resource limit is defined as
int256 public constant MAX_RESOURCE_LIMIT = 20_000_000; * maxResourceLimit / elasticityMultiplier. This struct was designed to fit within a
* single word. There is additional space for additions in the future.
/** *
* @notice Along with the resource limit, determines the target resource limit. * @custom:field maxResourceLimit Represents the maximum amount of deposit gas that
*/ * can be purchased per block.
int256 public constant ELASTICITY_MULTIPLIER = 10; * @custom:field elasticityMultiplier Determines the target resource limit along with
* the resource limit.
/** * @custom:field baseFeeMaxChangeDenominator Determines max change on fee per block.
* @notice Target amount of the resource that should be used within this block. * @custom:field minimumBaseFee The min deposit base fee, it is clamped to this
*/ * value.
int256 public constant TARGET_RESOURCE_LIMIT = MAX_RESOURCE_LIMIT / ELASTICITY_MULTIPLIER; * @custom:field systemTxMaxGas The amount of gas supplied to the system
* transaction. This should be set to the same number
/** * that the op-node sets as the gas limit for the
* @notice Denominator that determines max change on fee per block. * system transaction.
*/ * @custom:field maximumBaseFee The max deposit base fee, it is clamped to this
int256 public constant BASE_FEE_MAX_CHANGE_DENOMINATOR = 8; * value.
/**
* @notice Minimum base fee value, cannot go lower than this.
*/
int256 public constant MINIMUM_BASE_FEE = 1 gwei;
/**
* @notice Maximum base fee value, cannot go higher than this.
* It is possible for the MAXIMUM_BASE_FEE to raise to a value
* that is so large it will consume the entire gas limit of
* an L1 block.
*/
int256 public constant MAXIMUM_BASE_FEE = int256(uint256(type(uint128).max));
/**
* @notice Initial base fee value. This value must be smaller than the
* MAXIMUM_BASE_FEE.
*/ */
uint128 public constant INITIAL_BASE_FEE = 1 gwei; struct ResourceConfig {
uint32 maxResourceLimit;
uint8 elasticityMultiplier;
uint8 baseFeeMaxChangeDenominator;
uint32 minimumBaseFee;
uint32 systemTxMaxGas;
uint128 maximumBaseFee;
}
/** /**
* @notice EIP-1559 style gas parameters. * @notice EIP-1559 style gas parameters.
...@@ -102,20 +92,25 @@ abstract contract ResourceMetering is Initializable { ...@@ -102,20 +92,25 @@ abstract contract ResourceMetering is Initializable {
function _metered(uint64 _amount, uint256 _initialGas) internal { function _metered(uint64 _amount, uint256 _initialGas) internal {
// Update block number and base fee if necessary. // Update block number and base fee if necessary.
uint256 blockDiff = block.number - params.prevBlockNum; uint256 blockDiff = block.number - params.prevBlockNum;
ResourceConfig memory config = _resourceConfig();
int256 targetResourceLimit = int256(uint256(config.maxResourceLimit)) /
int256(uint256(config.elasticityMultiplier));
if (blockDiff > 0) { if (blockDiff > 0) {
// Handle updating EIP-1559 style gas parameters. We use EIP-1559 to restrict the rate // Handle updating EIP-1559 style gas parameters. We use EIP-1559 to restrict the rate
// at which deposits can be created and therefore limit the potential for deposits to // at which deposits can be created and therefore limit the potential for deposits to
// spam the L2 system. Fee scheme is very similar to EIP-1559 with minor changes. // spam the L2 system. Fee scheme is very similar to EIP-1559 with minor changes.
int256 gasUsedDelta = int256(uint256(params.prevBoughtGas)) - TARGET_RESOURCE_LIMIT; int256 gasUsedDelta = int256(uint256(params.prevBoughtGas)) - targetResourceLimit;
int256 baseFeeDelta = (int256(uint256(params.prevBaseFee)) * gasUsedDelta) / int256 baseFeeDelta = (int256(uint256(params.prevBaseFee)) * gasUsedDelta) /
(TARGET_RESOURCE_LIMIT * BASE_FEE_MAX_CHANGE_DENOMINATOR); (targetResourceLimit * int256(uint256(config.baseFeeMaxChangeDenominator)));
// Update base fee by adding the base fee delta and clamp the resulting value between // Update base fee by adding the base fee delta and clamp the resulting value between
// min and max. // min and max.
int256 newBaseFee = Arithmetic.clamp({ int256 newBaseFee = Arithmetic.clamp({
_value: int256(uint256(params.prevBaseFee)) + baseFeeDelta, _value: int256(uint256(params.prevBaseFee)) + baseFeeDelta,
_min: MINIMUM_BASE_FEE, _min: int256(uint256(config.minimumBaseFee)),
_max: MAXIMUM_BASE_FEE _max: int256(uint256(config.maximumBaseFee))
}); });
// If we skipped more than one block, we also need to account for every empty block. // If we skipped more than one block, we also need to account for every empty block.
...@@ -128,11 +123,11 @@ abstract contract ResourceMetering is Initializable { ...@@ -128,11 +123,11 @@ abstract contract ResourceMetering is Initializable {
newBaseFee = Arithmetic.clamp({ newBaseFee = Arithmetic.clamp({
_value: Arithmetic.cdexp({ _value: Arithmetic.cdexp({
_coefficient: newBaseFee, _coefficient: newBaseFee,
_denominator: BASE_FEE_MAX_CHANGE_DENOMINATOR, _denominator: int256(uint256(config.baseFeeMaxChangeDenominator)),
_exponent: int256(blockDiff - 1) _exponent: int256(blockDiff - 1)
}), }),
_min: MINIMUM_BASE_FEE, _min: int256(uint256(config.minimumBaseFee)),
_max: MAXIMUM_BASE_FEE _max: int256(uint256(config.maximumBaseFee))
}); });
} }
...@@ -145,7 +140,7 @@ abstract contract ResourceMetering is Initializable { ...@@ -145,7 +140,7 @@ abstract contract ResourceMetering is Initializable {
// Make sure we can actually buy the resource amount requested by the user. // Make sure we can actually buy the resource amount requested by the user.
params.prevBoughtGas += _amount; params.prevBoughtGas += _amount;
require( require(
int256(uint256(params.prevBoughtGas)) <= MAX_RESOURCE_LIMIT, int256(uint256(params.prevBoughtGas)) <= int256(uint256(config.maxResourceLimit)),
"ResourceMetering: cannot buy more gas than available gas limit" "ResourceMetering: cannot buy more gas than available gas limit"
); );
...@@ -168,6 +163,14 @@ abstract contract ResourceMetering is Initializable { ...@@ -168,6 +163,14 @@ abstract contract ResourceMetering is Initializable {
} }
} }
/**
* @notice Virtual function that returns the resource config. Contracts that inherit this
* contract must implement this function.
*
* @return ResourceConfig
*/
function _resourceConfig() internal virtual returns (ResourceConfig memory);
/** /**
* @notice Sets initial resource parameter values. This function must either be called by the * @notice Sets initial resource parameter values. This function must either be called by the
* initializer function of an upgradeable child contract. * initializer function of an upgradeable child contract.
...@@ -175,7 +178,7 @@ abstract contract ResourceMetering is Initializable { ...@@ -175,7 +178,7 @@ abstract contract ResourceMetering is Initializable {
// solhint-disable-next-line func-name-mixedcase // solhint-disable-next-line func-name-mixedcase
function __ResourceMetering_init() internal onlyInitializing { function __ResourceMetering_init() internal onlyInitializing {
params = ResourceParams({ params = ResourceParams({
prevBaseFee: INITIAL_BASE_FEE, prevBaseFee: 1 gwei,
prevBoughtGas: 0, prevBoughtGas: 0,
prevBlockNum: uint64(block.number) prevBlockNum: uint64(block.number)
}); });
......
...@@ -5,6 +5,7 @@ import { ...@@ -5,6 +5,7 @@ import {
OwnableUpgradeable OwnableUpgradeable
} from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; } from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
import { Semver } from "../universal/Semver.sol"; import { Semver } from "../universal/Semver.sol";
import { ResourceMetering } from "./ResourceMetering.sol";
/** /**
* @title SystemConfig * @title SystemConfig
...@@ -49,12 +50,12 @@ contract SystemConfig is OwnableUpgradeable, Semver { ...@@ -49,12 +50,12 @@ contract SystemConfig is OwnableUpgradeable, Semver {
uint64 public constant MINIMUM_GAS_LIMIT = 8_000_000; uint64 public constant MINIMUM_GAS_LIMIT = 8_000_000;
/** /**
* @notice Fixed L2 gas overhead. * @notice Fixed L2 gas overhead. Used as part of the L2 fee calculation.
*/ */
uint256 public overhead; uint256 public overhead;
/** /**
* @notice Dynamic L2 gas overhead. * @notice Dynamic L2 gas overhead. Used as part of the L2 fee calculation.
*/ */
uint256 public scalar; uint256 public scalar;
...@@ -65,10 +66,17 @@ contract SystemConfig is OwnableUpgradeable, Semver { ...@@ -65,10 +66,17 @@ contract SystemConfig is OwnableUpgradeable, Semver {
bytes32 public batcherHash; bytes32 public batcherHash;
/** /**
* @notice L2 gas limit. * @notice L2 block gas limit.
*/ */
uint64 public gasLimit; uint64 public gasLimit;
/**
* @notice The configuration for the deposit fee market. Used by the OptimismPortal
* to meter the cost of buying L2 gas on L1. Set as internal and wrapped with a getter
* so that the struct is returned instead of a tuple.
*/
ResourceMetering.ResourceConfig internal _resourceConfig;
/** /**
* @notice Emitted when configuration is updated * @notice Emitted when configuration is updated
* *
...@@ -79,7 +87,7 @@ contract SystemConfig is OwnableUpgradeable, Semver { ...@@ -79,7 +87,7 @@ contract SystemConfig is OwnableUpgradeable, Semver {
event ConfigUpdate(uint256 indexed version, UpdateType indexed updateType, bytes data); event ConfigUpdate(uint256 indexed version, UpdateType indexed updateType, bytes data);
/** /**
* @custom:semver 1.0.1 * @custom:semver 1.1.0
* *
* @param _owner Initial owner of the contract. * @param _owner Initial owner of the contract.
* @param _overhead Initial overhead value. * @param _overhead Initial overhead value.
...@@ -87,6 +95,7 @@ contract SystemConfig is OwnableUpgradeable, Semver { ...@@ -87,6 +95,7 @@ contract SystemConfig is OwnableUpgradeable, Semver {
* @param _batcherHash Initial batcher hash. * @param _batcherHash Initial batcher hash.
* @param _gasLimit Initial gas limit. * @param _gasLimit Initial gas limit.
* @param _unsafeBlockSigner Initial unsafe block signer address. * @param _unsafeBlockSigner Initial unsafe block signer address.
* @param _config Initial resource config.
*/ */
constructor( constructor(
address _owner, address _owner,
...@@ -94,13 +103,23 @@ contract SystemConfig is OwnableUpgradeable, Semver { ...@@ -94,13 +103,23 @@ contract SystemConfig is OwnableUpgradeable, Semver {
uint256 _scalar, uint256 _scalar,
bytes32 _batcherHash, bytes32 _batcherHash,
uint64 _gasLimit, uint64 _gasLimit,
address _unsafeBlockSigner address _unsafeBlockSigner,
) Semver(1, 0, 1) { ResourceMetering.ResourceConfig memory _config
initialize(_owner, _overhead, _scalar, _batcherHash, _gasLimit, _unsafeBlockSigner); ) Semver(1, 1, 0) {
initialize({
_owner: _owner,
_overhead: _overhead,
_scalar: _scalar,
_batcherHash: _batcherHash,
_gasLimit: _gasLimit,
_unsafeBlockSigner: _unsafeBlockSigner,
_config: _config
});
} }
/** /**
* @notice Initializer. * @notice Initializer. The resource config must be set before the
* require check.
* *
* @param _owner Initial owner of the contract. * @param _owner Initial owner of the contract.
* @param _overhead Initial overhead value. * @param _overhead Initial overhead value.
...@@ -108,6 +127,7 @@ contract SystemConfig is OwnableUpgradeable, Semver { ...@@ -108,6 +127,7 @@ contract SystemConfig is OwnableUpgradeable, Semver {
* @param _batcherHash Initial batcher hash. * @param _batcherHash Initial batcher hash.
* @param _gasLimit Initial gas limit. * @param _gasLimit Initial gas limit.
* @param _unsafeBlockSigner Initial unsafe block signer address. * @param _unsafeBlockSigner Initial unsafe block signer address.
* @param _config Initial ResourceConfig.
*/ */
function initialize( function initialize(
address _owner, address _owner,
...@@ -115,9 +135,9 @@ contract SystemConfig is OwnableUpgradeable, Semver { ...@@ -115,9 +135,9 @@ contract SystemConfig is OwnableUpgradeable, Semver {
uint256 _scalar, uint256 _scalar,
bytes32 _batcherHash, bytes32 _batcherHash,
uint64 _gasLimit, uint64 _gasLimit,
address _unsafeBlockSigner address _unsafeBlockSigner,
ResourceMetering.ResourceConfig memory _config
) public initializer { ) public initializer {
require(_gasLimit >= MINIMUM_GAS_LIMIT, "SystemConfig: gas limit too low");
__Ownable_init(); __Ownable_init();
transferOwnership(_owner); transferOwnership(_owner);
overhead = _overhead; overhead = _overhead;
...@@ -125,6 +145,21 @@ contract SystemConfig is OwnableUpgradeable, Semver { ...@@ -125,6 +145,21 @@ contract SystemConfig is OwnableUpgradeable, Semver {
batcherHash = _batcherHash; batcherHash = _batcherHash;
gasLimit = _gasLimit; gasLimit = _gasLimit;
_setUnsafeBlockSigner(_unsafeBlockSigner); _setUnsafeBlockSigner(_unsafeBlockSigner);
_setResourceConfig(_config);
require(_gasLimit >= minimumGasLimit(), "SystemConfig: gas limit too low");
}
/**
* @notice Returns the minimum L2 gas limit that can be safely set for the system to
* operate. The L2 gas limit must be larger than or equal to the amount of
* gas that is allocated for deposits per block plus the amount of gas that
* is allocated for the system transaction.
* This function is used to determine if changes to parameters are safe.
*
* @return uint64
*/
function minimumGasLimit() public view returns (uint64) {
return uint64(_resourceConfig.maxResourceLimit) + uint64(_resourceConfig.systemTxMaxGas);
} }
/** /**
...@@ -188,7 +223,7 @@ contract SystemConfig is OwnableUpgradeable, Semver { ...@@ -188,7 +223,7 @@ contract SystemConfig is OwnableUpgradeable, Semver {
* @param _gasLimit New gas limit. * @param _gasLimit New gas limit.
*/ */
function setGasLimit(uint64 _gasLimit) external onlyOwner { function setGasLimit(uint64 _gasLimit) external onlyOwner {
require(_gasLimit >= MINIMUM_GAS_LIMIT, "SystemConfig: gas limit too low"); require(_gasLimit >= minimumGasLimit(), "SystemConfig: gas limit too low");
gasLimit = _gasLimit; gasLimit = _gasLimit;
bytes memory data = abi.encode(_gasLimit); bytes memory data = abi.encode(_gasLimit);
...@@ -207,4 +242,60 @@ contract SystemConfig is OwnableUpgradeable, Semver { ...@@ -207,4 +242,60 @@ contract SystemConfig is OwnableUpgradeable, Semver {
sstore(slot, _unsafeBlockSigner) sstore(slot, _unsafeBlockSigner)
} }
} }
/**
* @notice A getter for the resource config. Ensures that the struct is
* returned instead of a tuple.
*
* @return ResourceConfig
*/
function resourceConfig() external view returns (ResourceMetering.ResourceConfig memory) {
return _resourceConfig;
}
/**
* @notice An external setter for the resource config. In the future, this
* method may emit an event that the `op-node` picks up for when the
* resource config is changed.
*
* @param _config The new resource config values.
*/
function setResourceConfig(ResourceMetering.ResourceConfig memory _config) external onlyOwner {
_setResourceConfig(_config);
}
/**
* @notice An internal setter for the resource config. Ensures that the
* config is sane before storing it by checking for invariants.
*
* @param _config The new resource config.
*/
function _setResourceConfig(ResourceMetering.ResourceConfig memory _config) internal {
// Min base fee must be less than or equal to max base fee.
require(
_config.minimumBaseFee <= _config.maximumBaseFee,
"SystemConfig: min base fee must be less than max base"
);
// Base fee change denominator must be greater than 0.
require(_config.baseFeeMaxChangeDenominator > 0, "SystemConfig: denominator cannot be 0");
// Max resource limit plus system tx gas must be less than or equal to the L2 gas limit.
// The gas limit must be increased before these values can be increased.
require(
_config.maxResourceLimit + _config.systemTxMaxGas <= gasLimit,
"SystemConfig: gas limit too low"
);
// Elasticity multiplier must be greater than 0.
require(
_config.elasticityMultiplier > 0,
"SystemConfig: elasticity multiplier cannot be 0"
);
// No precision loss when computing target resource limit.
require(
((_config.maxResourceLimit / _config.elasticityMultiplier) *
_config.elasticityMultiplier) == _config.maxResourceLimit,
"SystemConfig: precision loss with target resource limit"
);
_resourceConfig = _config;
}
} }
...@@ -16,6 +16,8 @@ import { ProxyAdmin } from "../universal/ProxyAdmin.sol"; ...@@ -16,6 +16,8 @@ import { ProxyAdmin } from "../universal/ProxyAdmin.sol";
import { OptimismMintableERC20Factory } from "../universal/OptimismMintableERC20Factory.sol"; import { OptimismMintableERC20Factory } from "../universal/OptimismMintableERC20Factory.sol";
import { PortalSender } from "./PortalSender.sol"; import { PortalSender } from "./PortalSender.sol";
import { SystemConfig } from "../L1/SystemConfig.sol"; import { SystemConfig } from "../L1/SystemConfig.sol";
import { ResourceMetering } from "../L1/ResourceMetering.sol";
import { Constants } from "../libraries/Constants.sol";
/** /**
* @title SystemDictator * @title SystemDictator
...@@ -79,6 +81,7 @@ contract SystemDictator is OwnableUpgradeable { ...@@ -79,6 +81,7 @@ contract SystemDictator is OwnableUpgradeable {
bytes32 batcherHash; bytes32 batcherHash;
uint64 gasLimit; uint64 gasLimit;
address unsafeBlockSigner; address unsafeBlockSigner;
ResourceMetering.ResourceConfig resourceConfig;
} }
/** /**
...@@ -160,6 +163,8 @@ contract SystemDictator is OwnableUpgradeable { ...@@ -160,6 +163,8 @@ contract SystemDictator is OwnableUpgradeable {
* initialized upon deployment. * initialized upon deployment.
*/ */
constructor() { constructor() {
ResourceMetering.ResourceConfig memory rcfg = Constants.DEFAULT_RESOURCE_CONFIG();
// Using this shorter variable as an alias for address(0) just prevents us from having to // Using this shorter variable as an alias for address(0) just prevents us from having to
// to use a new line for every single parameter. // to use a new line for every single parameter.
address zero = address(0); address zero = address(0);
...@@ -177,7 +182,7 @@ contract SystemDictator is OwnableUpgradeable { ...@@ -177,7 +182,7 @@ contract SystemDictator is OwnableUpgradeable {
PortalSender(zero), PortalSender(zero),
SystemConfig(zero) SystemConfig(zero)
), ),
SystemConfigConfig(zero, 0, 0, bytes32(0), 0, zero) SystemConfigConfig(zero, 0, 0, bytes32(0), 0, zero, rcfg)
) )
); );
} }
...@@ -244,7 +249,8 @@ contract SystemDictator is OwnableUpgradeable { ...@@ -244,7 +249,8 @@ contract SystemDictator is OwnableUpgradeable {
config.systemConfigConfig.scalar, config.systemConfigConfig.scalar,
config.systemConfigConfig.batcherHash, config.systemConfigConfig.batcherHash,
config.systemConfigConfig.gasLimit, config.systemConfigConfig.gasLimit,
config.systemConfigConfig.unsafeBlockSigner config.systemConfigConfig.unsafeBlockSigner,
config.systemConfigConfig.resourceConfig
) )
) )
); );
......
...@@ -3,16 +3,32 @@ pragma solidity 0.8.15; ...@@ -3,16 +3,32 @@ pragma solidity 0.8.15;
import { OptimismPortal } from "../L1/OptimismPortal.sol"; import { OptimismPortal } from "../L1/OptimismPortal.sol";
import { L2OutputOracle } from "../L1/L2OutputOracle.sol"; import { L2OutputOracle } from "../L1/L2OutputOracle.sol";
import { AddressAliasHelper } from "../vendor/AddressAliasHelper.sol"; import { AddressAliasHelper } from "../vendor/AddressAliasHelper.sol";
import { SystemConfig } from "../L1/SystemConfig.sol";
import { ResourceMetering } from "../L1/ResourceMetering.sol";
import { Constants } from "../libraries/Constants.sol";
contract EchidnaFuzzOptimismPortal { contract EchidnaFuzzOptimismPortal {
OptimismPortal internal portal; OptimismPortal internal portal;
bool internal failedToComplete; bool internal failedToComplete;
constructor() { constructor() {
ResourceMetering.ResourceConfig memory rcfg = Constants.DEFAULT_RESOURCE_CONFIG();
SystemConfig systemConfig = new SystemConfig({
_owner: address(1),
_overhead: 0,
_scalar: 10000,
_batcherHash: bytes32(0),
_gasLimit: 30_000_000,
_unsafeBlockSigner: address(0),
_config: rcfg
});
portal = new OptimismPortal({ portal = new OptimismPortal({
_l2Oracle: L2OutputOracle(address(0)), _l2Oracle: L2OutputOracle(address(0)),
_guardian: address(0), _guardian: address(0),
_paused: false _paused: false,
_config: systemConfig
}); });
} }
......
...@@ -3,6 +3,7 @@ pragma solidity 0.8.15; ...@@ -3,6 +3,7 @@ pragma solidity 0.8.15;
import { ResourceMetering } from "../L1/ResourceMetering.sol"; import { ResourceMetering } from "../L1/ResourceMetering.sol";
import { Arithmetic } from "../libraries/Arithmetic.sol"; import { Arithmetic } from "../libraries/Arithmetic.sol";
import { StdUtils } from "forge-std/Test.sol"; import { StdUtils } from "forge-std/Test.sol";
import { Constants } from "../libraries/Constants.sol";
contract EchidnaFuzzResourceMetering is ResourceMetering, StdUtils { contract EchidnaFuzzResourceMetering is ResourceMetering, StdUtils {
bool internal failedMaxGasPerBlock; bool internal failedMaxGasPerBlock;
...@@ -24,6 +25,20 @@ contract EchidnaFuzzResourceMetering is ResourceMetering, StdUtils { ...@@ -24,6 +25,20 @@ contract EchidnaFuzzResourceMetering is ResourceMetering, StdUtils {
__ResourceMetering_init(); __ResourceMetering_init();
} }
function resourceConfig() public pure returns (ResourceMetering.ResourceConfig memory) {
return _resourceConfig();
}
function _resourceConfig()
internal
pure
override
returns (ResourceMetering.ResourceConfig memory)
{
ResourceMetering.ResourceConfig memory rcfg = Constants.DEFAULT_RESOURCE_CONFIG();
return rcfg;
}
/** /**
* @notice Takes the necessary parameters to allow us to burn arbitrary amounts of gas to test * @notice Takes the necessary parameters to allow us to burn arbitrary amounts of gas to test
* the underlying resource metering/gas market logic * the underlying resource metering/gas market logic
...@@ -34,12 +49,16 @@ contract EchidnaFuzzResourceMetering is ResourceMetering, StdUtils { ...@@ -34,12 +49,16 @@ contract EchidnaFuzzResourceMetering is ResourceMetering, StdUtils {
uint256 cachedPrevBoughtGas = uint256(params.prevBoughtGas); uint256 cachedPrevBoughtGas = uint256(params.prevBoughtGas);
uint256 cachedPrevBlockNum = uint256(params.prevBlockNum); uint256 cachedPrevBlockNum = uint256(params.prevBlockNum);
ResourceMetering.ResourceConfig memory rcfg = resourceConfig();
uint256 targetResourceLimit = uint256(rcfg.maxResourceLimit) /
uint256(rcfg.elasticityMultiplier);
// check that the last block's base fee hasn't dropped below the minimum // check that the last block's base fee hasn't dropped below the minimum
if (cachedPrevBaseFee < uint256(MINIMUM_BASE_FEE)) { if (cachedPrevBaseFee < uint256(rcfg.minimumBaseFee)) {
failedNeverBelowMinBaseFee = true; failedNeverBelowMinBaseFee = true;
} }
// check that the last block didn't consume more than the max amount of gas // check that the last block didn't consume more than the max amount of gas
if (cachedPrevBoughtGas > uint256(MAX_RESOURCE_LIMIT)) { if (cachedPrevBoughtGas > uint256(rcfg.maxResourceLimit)) {
failedMaxGasPerBlock = true; failedMaxGasPerBlock = true;
} }
...@@ -51,11 +70,11 @@ contract EchidnaFuzzResourceMetering is ResourceMetering, StdUtils { ...@@ -51,11 +70,11 @@ contract EchidnaFuzzResourceMetering is ResourceMetering, StdUtils {
if (_raiseBaseFee) { if (_raiseBaseFee) {
gasToBurn = bound( gasToBurn = bound(
_gasToBurn, _gasToBurn,
uint256(TARGET_RESOURCE_LIMIT), uint256(targetResourceLimit),
uint256(MAX_RESOURCE_LIMIT) uint256(rcfg.maxResourceLimit)
); );
} else { } else {
gasToBurn = bound(_gasToBurn, 0, uint256(TARGET_RESOURCE_LIMIT)); gasToBurn = bound(_gasToBurn, 0, targetResourceLimit);
} }
_burnInternal(uint64(gasToBurn)); _burnInternal(uint64(gasToBurn));
...@@ -63,13 +82,13 @@ contract EchidnaFuzzResourceMetering is ResourceMetering, StdUtils { ...@@ -63,13 +82,13 @@ contract EchidnaFuzzResourceMetering is ResourceMetering, StdUtils {
// Part 3: we run checks and modify our invariant flags based on the updated params values // Part 3: we run checks and modify our invariant flags based on the updated params values
// Calculate the maximum allowed baseFee change (per block) // Calculate the maximum allowed baseFee change (per block)
uint256 maxBaseFeeChange = cachedPrevBaseFee / uint256(BASE_FEE_MAX_CHANGE_DENOMINATOR); uint256 maxBaseFeeChange = cachedPrevBaseFee / uint256(rcfg.baseFeeMaxChangeDenominator);
// If the last block used more than the target amount of gas (and there were no // If the last block used more than the target amount of gas (and there were no
// empty blocks in between), ensure this block's baseFee increased, but not by // empty blocks in between), ensure this block's baseFee increased, but not by
// more than the max amount per block // more than the max amount per block
if ( if (
(cachedPrevBoughtGas > uint256(TARGET_RESOURCE_LIMIT)) && (cachedPrevBoughtGas > uint256(targetResourceLimit)) &&
(uint256(params.prevBlockNum) - cachedPrevBlockNum == 1) (uint256(params.prevBlockNum) - cachedPrevBlockNum == 1)
) { ) {
failedRaiseBaseFee = failedRaiseBaseFee || (params.prevBaseFee <= cachedPrevBaseFee); failedRaiseBaseFee = failedRaiseBaseFee || (params.prevBaseFee <= cachedPrevBaseFee);
...@@ -81,7 +100,7 @@ contract EchidnaFuzzResourceMetering is ResourceMetering, StdUtils { ...@@ -81,7 +100,7 @@ contract EchidnaFuzzResourceMetering is ResourceMetering, StdUtils {
// If the last block used less than the target amount of gas, (or was empty), // If the last block used less than the target amount of gas, (or was empty),
// ensure that: this block's baseFee was decreased, but not by more than the max amount // ensure that: this block's baseFee was decreased, but not by more than the max amount
if ( if (
(cachedPrevBoughtGas < uint256(TARGET_RESOURCE_LIMIT)) || (cachedPrevBoughtGas < uint256(targetResourceLimit)) ||
(uint256(params.prevBlockNum) - cachedPrevBlockNum > 1) (uint256(params.prevBlockNum) - cachedPrevBlockNum > 1)
) { ) {
// Invariant: baseFee should decrease // Invariant: baseFee should decrease
...@@ -104,11 +123,11 @@ contract EchidnaFuzzResourceMetering is ResourceMetering, StdUtils { ...@@ -104,11 +123,11 @@ contract EchidnaFuzzResourceMetering is ResourceMetering, StdUtils {
Arithmetic.clamp( Arithmetic.clamp(
Arithmetic.cdexp( Arithmetic.cdexp(
int256(cachedPrevBaseFee), int256(cachedPrevBaseFee),
BASE_FEE_MAX_CHANGE_DENOMINATOR, int256(uint256(rcfg.baseFeeMaxChangeDenominator)),
int256(uint256(params.prevBlockNum) - cachedPrevBlockNum) int256(uint256(params.prevBlockNum) - cachedPrevBlockNum)
), ),
MINIMUM_BASE_FEE, int256(uint256(rcfg.minimumBaseFee)),
MAXIMUM_BASE_FEE int256(uint256(rcfg.maximumBaseFee))
) )
); );
} }
......
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
pragma solidity ^0.8.0; pragma solidity ^0.8.0;
import { ResourceMetering } from "../L1/ResourceMetering.sol";
/** /**
* @title Constants * @title Constants
* @notice Constants is a library for storing constants. Simple! Don't put everything in here, just * @notice Constants is a library for storing constants. Simple! Don't put everything in here, just
...@@ -24,4 +26,24 @@ library Constants { ...@@ -24,4 +26,24 @@ library Constants {
* non-zero to reduce the gas cost of message passing transactions. * non-zero to reduce the gas cost of message passing transactions.
*/ */
address internal constant DEFAULT_L2_SENDER = 0x000000000000000000000000000000000000dEaD; address internal constant DEFAULT_L2_SENDER = 0x000000000000000000000000000000000000dEaD;
/**
* @notice Returns the default values for the ResourceConfig. These are the recommended values
* for a production network.
*/
function DEFAULT_RESOURCE_CONFIG()
internal
pure
returns (ResourceMetering.ResourceConfig memory)
{
ResourceMetering.ResourceConfig memory config = ResourceMetering.ResourceConfig({
maxResourceLimit: 20_000_000,
elasticityMultiplier: 10,
baseFeeMaxChangeDenominator: 8,
minimumBaseFee: 1 gwei,
systemTxMaxGas: 1_000_000,
maximumBaseFee: type(uint128).max
});
return config;
}
} }
...@@ -32,8 +32,6 @@ contract SetPrevBaseFee_Test is Portal_Initializer { ...@@ -32,8 +32,6 @@ contract SetPrevBaseFee_Test is Portal_Initializer {
// In order to achieve this we make no assertions, and handle everything else in the setUp() // In order to achieve this we make no assertions, and handle everything else in the setUp()
// function. // function.
contract GasBenchMark_OptimismPortal is Portal_Initializer { contract GasBenchMark_OptimismPortal is Portal_Initializer {
uint128 internal INITIAL_BASE_FEE;
// Reusable default values for a test withdrawal // Reusable default values for a test withdrawal
Types.WithdrawalTransaction _defaultTx; Types.WithdrawalTransaction _defaultTx;
...@@ -86,8 +84,6 @@ contract GasBenchMark_OptimismPortal is Portal_Initializer { ...@@ -86,8 +84,6 @@ contract GasBenchMark_OptimismPortal is Portal_Initializer {
1 1
); );
INITIAL_BASE_FEE = op.INITIAL_BASE_FEE();
// Fund the portal so that we can withdraw ETH. // Fund the portal so that we can withdraw ETH.
vm.deal(address(op), 0xFFFFFFFF); vm.deal(address(op), 0xFFFFFFFF);
} }
...@@ -103,7 +99,7 @@ contract GasBenchMark_OptimismPortal is Portal_Initializer { ...@@ -103,7 +99,7 @@ contract GasBenchMark_OptimismPortal is Portal_Initializer {
} }
function test_depositTransaction_benchmark_1() external { function test_depositTransaction_benchmark_1() external {
setPrevBaseFee(vm, address(op), INITIAL_BASE_FEE); setPrevBaseFee(vm, address(op), 1 gwei);
op.depositTransaction{ value: NON_ZERO_VALUE }( op.depositTransaction{ value: NON_ZERO_VALUE }(
NON_ZERO_ADDRESS, NON_ZERO_ADDRESS,
ZERO_VALUE, ZERO_VALUE,
...@@ -124,16 +120,9 @@ contract GasBenchMark_OptimismPortal is Portal_Initializer { ...@@ -124,16 +120,9 @@ contract GasBenchMark_OptimismPortal is Portal_Initializer {
} }
contract GasBenchMark_L1CrossDomainMessenger is Messenger_Initializer { contract GasBenchMark_L1CrossDomainMessenger is Messenger_Initializer {
uint128 internal INITIAL_BASE_FEE;
function setUp() public virtual override {
super.setUp();
INITIAL_BASE_FEE = op.INITIAL_BASE_FEE();
}
function test_sendMessage_benchmark_0() external { function test_sendMessage_benchmark_0() external {
vm.pauseGasMetering(); vm.pauseGasMetering();
setPrevBaseFee(vm, address(op), INITIAL_BASE_FEE); setPrevBaseFee(vm, address(op), 1 gwei);
// The amount of data typically sent during a bridge deposit. // The amount of data typically sent during a bridge deposit.
bytes bytes
memory data = hex"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"; memory data = hex"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff";
...@@ -153,11 +142,8 @@ contract GasBenchMark_L1CrossDomainMessenger is Messenger_Initializer { ...@@ -153,11 +142,8 @@ contract GasBenchMark_L1CrossDomainMessenger is Messenger_Initializer {
} }
contract GasBenchMark_L1StandardBridge_Deposit is Bridge_Initializer { contract GasBenchMark_L1StandardBridge_Deposit is Bridge_Initializer {
uint128 internal INITIAL_BASE_FEE;
function setUp() public virtual override { function setUp() public virtual override {
super.setUp(); super.setUp();
INITIAL_BASE_FEE = op.INITIAL_BASE_FEE();
deal(address(L1Token), alice, 100000, true); deal(address(L1Token), alice, 100000, true);
vm.startPrank(alice, alice); vm.startPrank(alice, alice);
L1Token.approve(address(L1Bridge), type(uint256).max); L1Token.approve(address(L1Bridge), type(uint256).max);
...@@ -165,7 +151,7 @@ contract GasBenchMark_L1StandardBridge_Deposit is Bridge_Initializer { ...@@ -165,7 +151,7 @@ contract GasBenchMark_L1StandardBridge_Deposit is Bridge_Initializer {
function test_depositETH_benchmark_0() external { function test_depositETH_benchmark_0() external {
vm.pauseGasMetering(); vm.pauseGasMetering();
setPrevBaseFee(vm, address(op), INITIAL_BASE_FEE); setPrevBaseFee(vm, address(op), 1 gwei);
vm.resumeGasMetering(); vm.resumeGasMetering();
L1Bridge.depositETH{ value: 500 }(50000, hex""); L1Bridge.depositETH{ value: 500 }(50000, hex"");
} }
...@@ -179,7 +165,7 @@ contract GasBenchMark_L1StandardBridge_Deposit is Bridge_Initializer { ...@@ -179,7 +165,7 @@ contract GasBenchMark_L1StandardBridge_Deposit is Bridge_Initializer {
function test_depositERC20_benchmark_0() external { function test_depositERC20_benchmark_0() external {
vm.pauseGasMetering(); vm.pauseGasMetering();
setPrevBaseFee(vm, address(op), INITIAL_BASE_FEE); setPrevBaseFee(vm, address(op), 1 gwei);
vm.resumeGasMetering(); vm.resumeGasMetering();
L1Bridge.bridgeERC20({ L1Bridge.bridgeERC20({
_localToken: address(L1Token), _localToken: address(L1Token),
......
...@@ -28,6 +28,9 @@ import { L1ChugSplashProxy } from "../legacy/L1ChugSplashProxy.sol"; ...@@ -28,6 +28,9 @@ import { L1ChugSplashProxy } from "../legacy/L1ChugSplashProxy.sol";
import { IL1ChugSplashDeployer } from "../legacy/L1ChugSplashProxy.sol"; import { IL1ChugSplashDeployer } from "../legacy/L1ChugSplashProxy.sol";
import { Strings } from "@openzeppelin/contracts/utils/Strings.sol"; import { Strings } from "@openzeppelin/contracts/utils/Strings.sol";
import { LegacyMintableERC20 } from "../legacy/LegacyMintableERC20.sol"; import { LegacyMintableERC20 } from "../legacy/LegacyMintableERC20.sol";
import { SystemConfig } from "../L1/SystemConfig.sol";
import { ResourceMetering } from "../L1/ResourceMetering.sol";
import { Constants } from "../libraries/Constants.sol";
contract CommonTest is Test { contract CommonTest is Test {
address alice = address(128); address alice = address(128);
...@@ -158,6 +161,7 @@ contract Portal_Initializer is L2OutputOracle_Initializer { ...@@ -158,6 +161,7 @@ contract Portal_Initializer is L2OutputOracle_Initializer {
// Test target // Test target
OptimismPortal internal opImpl; OptimismPortal internal opImpl;
OptimismPortal internal op; OptimismPortal internal op;
SystemConfig systemConfig;
event WithdrawalFinalized(bytes32 indexed withdrawalHash, bool success); event WithdrawalFinalized(bytes32 indexed withdrawalHash, bool success);
event WithdrawalProven( event WithdrawalProven(
...@@ -169,7 +173,25 @@ contract Portal_Initializer is L2OutputOracle_Initializer { ...@@ -169,7 +173,25 @@ contract Portal_Initializer is L2OutputOracle_Initializer {
function setUp() public virtual override { function setUp() public virtual override {
super.setUp(); super.setUp();
opImpl = new OptimismPortal({ _l2Oracle: oracle, _guardian: guardian, _paused: true }); ResourceMetering.ResourceConfig memory config = Constants.DEFAULT_RESOURCE_CONFIG();
systemConfig = new SystemConfig({
_owner: address(1),
_overhead: 0,
_scalar: 10000,
_batcherHash: bytes32(0),
_gasLimit: 30_000_000,
_unsafeBlockSigner: address(0),
_config: config
});
opImpl = new OptimismPortal({
_l2Oracle: oracle,
_guardian: guardian,
_paused: true,
_config: systemConfig
});
Proxy proxy = new Proxy(multisig); Proxy proxy = new Proxy(multisig);
vm.prank(multisig); vm.prank(multisig);
proxy.upgradeToAndCall( proxy.upgradeToAndCall(
......
...@@ -9,6 +9,7 @@ import { OptimismPortal } from "../L1/OptimismPortal.sol"; ...@@ -9,6 +9,7 @@ import { OptimismPortal } from "../L1/OptimismPortal.sol";
import { Types } from "../libraries/Types.sol"; import { Types } from "../libraries/Types.sol";
import { Hashing } from "../libraries/Hashing.sol"; import { Hashing } from "../libraries/Hashing.sol";
import { Proxy } from "../universal/Proxy.sol"; import { Proxy } from "../universal/Proxy.sol";
import { ResourceMetering } from "../L1/ResourceMetering.sol";
contract OptimismPortal_Test is Portal_Initializer { contract OptimismPortal_Test is Portal_Initializer {
event Paused(address); event Paused(address);
...@@ -1045,10 +1046,12 @@ contract OptimismPortalUpgradeable_Test is Portal_Initializer { ...@@ -1045,10 +1046,12 @@ contract OptimismPortalUpgradeable_Test is Portal_Initializer {
} }
function test_params_initValuesOnProxy_succeeds() external { function test_params_initValuesOnProxy_succeeds() external {
(uint128 prevBaseFee, uint64 prevBoughtGas, uint64 prevBlockNum) = OptimismPortal( OptimismPortal p = OptimismPortal(payable(address(proxy)));
payable(address(proxy))
).params(); (uint128 prevBaseFee, uint64 prevBoughtGas, uint64 prevBlockNum) = p.params();
assertEq(prevBaseFee, opImpl.INITIAL_BASE_FEE());
ResourceMetering.ResourceConfig memory rcfg = systemConfig.resourceConfig();
assertEq(prevBaseFee, rcfg.minimumBaseFee);
assertEq(prevBoughtGas, 0); assertEq(prevBoughtGas, 0);
assertEq(prevBlockNum, initialBlockNum); assertEq(prevBlockNum, initialBlockNum);
} }
......
...@@ -4,6 +4,7 @@ pragma solidity 0.8.15; ...@@ -4,6 +4,7 @@ pragma solidity 0.8.15;
import { Test } from "forge-std/Test.sol"; import { Test } from "forge-std/Test.sol";
import { ResourceMetering } from "../L1/ResourceMetering.sol"; import { ResourceMetering } from "../L1/ResourceMetering.sol";
import { Proxy } from "../universal/Proxy.sol"; import { Proxy } from "../universal/Proxy.sol";
import { Constants } from "../libraries/Constants.sol";
contract MeterUser is ResourceMetering { contract MeterUser is ResourceMetering {
constructor() { constructor() {
...@@ -14,6 +15,19 @@ contract MeterUser is ResourceMetering { ...@@ -14,6 +15,19 @@ contract MeterUser is ResourceMetering {
__ResourceMetering_init(); __ResourceMetering_init();
} }
function resourceConfig() public pure returns (ResourceMetering.ResourceConfig memory) {
return _resourceConfig();
}
function _resourceConfig()
internal
pure
override
returns (ResourceMetering.ResourceConfig memory)
{
return Constants.DEFAULT_RESOURCE_CONFIG();
}
function use(uint64 _amount) public metered(_amount) {} function use(uint64 _amount) public metered(_amount) {}
function set( function set(
...@@ -29,6 +43,11 @@ contract MeterUser is ResourceMetering { ...@@ -29,6 +43,11 @@ contract MeterUser is ResourceMetering {
} }
} }
/**
* @title ResourceConfig
* @notice The tests are based on the default config values. It is expected that
* the config values used in these tests are ran in production.
*/
contract ResourceMetering_Test is Test { contract ResourceMetering_Test is Test {
MeterUser internal meter; MeterUser internal meter;
uint64 initialBlockNum; uint64 initialBlockNum;
...@@ -38,42 +57,15 @@ contract ResourceMetering_Test is Test { ...@@ -38,42 +57,15 @@ contract ResourceMetering_Test is Test {
initialBlockNum = uint64(block.number); initialBlockNum = uint64(block.number);
} }
/**
* @notice The INITIAL_BASE_FEE must be less than the MAXIMUM_BASE_FEE
* and greater than the MINIMUM_BASE_FEE.
*/
function test_meter_initialBaseFee_succeeds() external {
uint256 max = uint256(meter.MAXIMUM_BASE_FEE());
uint256 min = uint256(meter.MINIMUM_BASE_FEE());
uint256 initial = uint256(meter.INITIAL_BASE_FEE());
assertTrue(max >= initial);
assertTrue(min <= initial);
}
/**
* @notice The MINIMUM_BASE_FEE must be less than the MAXIMUM_BASE_FEE.
*/
function test_meter_minBaseFeeLessThanMaxBaseFee_succeeds() external {
uint256 max = uint256(meter.MAXIMUM_BASE_FEE());
uint256 min = uint256(meter.MINIMUM_BASE_FEE());
assertTrue(max > min);
}
function test_meter_initialResourceParams_succeeds() external { function test_meter_initialResourceParams_succeeds() external {
(uint128 prevBaseFee, uint64 prevBoughtGas, uint64 prevBlockNum) = meter.params(); (uint128 prevBaseFee, uint64 prevBoughtGas, uint64 prevBlockNum) = meter.params();
ResourceMetering.ResourceConfig memory rcfg = meter.resourceConfig();
assertEq(prevBaseFee, meter.INITIAL_BASE_FEE()); assertEq(prevBaseFee, rcfg.minimumBaseFee);
assertEq(prevBoughtGas, 0); assertEq(prevBoughtGas, 0);
assertEq(prevBlockNum, initialBlockNum); assertEq(prevBlockNum, initialBlockNum);
} }
function test_meter_maxValue_succeeds() external {
uint256 max = uint256(meter.MAX_RESOURCE_LIMIT());
uint256 target = uint256(meter.TARGET_RESOURCE_LIMIT());
uint256 elasticity = uint256(meter.ELASTICITY_MULTIPLIER());
assertEq(max / elasticity, target);
}
function test_meter_updateParamsNoChange_succeeds() external { function test_meter_updateParamsNoChange_succeeds() external {
meter.use(0); // equivalent to just updating the base fee and block number meter.use(0); // equivalent to just updating the base fee and block number
(uint128 prevBaseFee, uint64 prevBoughtGas, uint64 prevBlockNum) = meter.params(); (uint128 prevBaseFee, uint64 prevBoughtGas, uint64 prevBlockNum) = meter.params();
...@@ -116,8 +108,9 @@ contract ResourceMetering_Test is Test { ...@@ -116,8 +108,9 @@ contract ResourceMetering_Test is Test {
} }
function test_meter_updateNoGasDelta_succeeds() external { function test_meter_updateNoGasDelta_succeeds() external {
uint64 target = uint64(uint256(meter.TARGET_RESOURCE_LIMIT())); ResourceMetering.ResourceConfig memory rcfg = meter.resourceConfig();
meter.use(target); uint256 target = uint256(rcfg.maxResourceLimit) / uint256(rcfg.elasticityMultiplier);
meter.use(uint64(target));
(uint128 prevBaseFee, uint64 prevBoughtGas, uint64 prevBlockNum) = meter.params(); (uint128 prevBaseFee, uint64 prevBoughtGas, uint64 prevBlockNum) = meter.params();
assertEq(prevBaseFee, 1000000000); assertEq(prevBaseFee, 1000000000);
...@@ -126,12 +119,14 @@ contract ResourceMetering_Test is Test { ...@@ -126,12 +119,14 @@ contract ResourceMetering_Test is Test {
} }
function test_meter_useMax_succeeds() external { function test_meter_useMax_succeeds() external {
uint64 target = uint64(uint256(meter.TARGET_RESOURCE_LIMIT())); ResourceMetering.ResourceConfig memory rcfg = meter.resourceConfig();
uint64 elasticity = uint64(uint256(meter.ELASTICITY_MULTIPLIER())); uint64 target = uint64(rcfg.maxResourceLimit) / uint64(rcfg.elasticityMultiplier);
meter.use(target * elasticity); uint64 elasticityMultiplier = uint64(rcfg.elasticityMultiplier);
meter.use(target * elasticityMultiplier);
(, uint64 prevBoughtGas, ) = meter.params(); (, uint64 prevBoughtGas, ) = meter.params();
assertEq(prevBoughtGas, target * elasticity); assertEq(prevBoughtGas, target * elasticityMultiplier);
vm.roll(initialBlockNum + 1); vm.roll(initialBlockNum + 1);
meter.use(0); meter.use(0);
...@@ -140,10 +135,12 @@ contract ResourceMetering_Test is Test { ...@@ -140,10 +135,12 @@ contract ResourceMetering_Test is Test {
} }
function test_meter_useMoreThanMax_reverts() external { function test_meter_useMoreThanMax_reverts() external {
uint64 target = uint64(uint256(meter.TARGET_RESOURCE_LIMIT())); ResourceMetering.ResourceConfig memory rcfg = meter.resourceConfig();
uint64 elasticity = uint64(uint256(meter.ELASTICITY_MULTIPLIER())); uint64 target = uint64(rcfg.maxResourceLimit) / uint64(rcfg.elasticityMultiplier);
uint64 elasticityMultiplier = uint64(rcfg.elasticityMultiplier);
vm.expectRevert("ResourceMetering: cannot buy more gas than available gas limit"); vm.expectRevert("ResourceMetering: cannot buy more gas than available gas limit");
meter.use(target * elasticity + 1); meter.use(target * elasticityMultiplier + 1);
} }
// Demonstrates that the resource metering arithmetic can tolerate very large gaps between // Demonstrates that the resource metering arithmetic can tolerate very large gaps between
...@@ -153,9 +150,11 @@ contract ResourceMetering_Test is Test { ...@@ -153,9 +150,11 @@ contract ResourceMetering_Test is Test {
// At 12 seconds per block, this number is effectively unreachable. // At 12 seconds per block, this number is effectively unreachable.
vm.assume(_blockDiff < 433576281058164217753225238677900874458691); vm.assume(_blockDiff < 433576281058164217753225238677900874458691);
uint64 target = uint64(uint256(meter.TARGET_RESOURCE_LIMIT())); ResourceMetering.ResourceConfig memory rcfg = meter.resourceConfig();
uint64 elasticity = uint64(uint256(meter.ELASTICITY_MULTIPLIER())); uint64 target = uint64(rcfg.maxResourceLimit) / uint64(rcfg.elasticityMultiplier);
vm.assume(_amount < target * elasticity); uint64 elasticityMultiplier = uint64(rcfg.elasticityMultiplier);
vm.assume(_amount < target * elasticityMultiplier);
vm.roll(initialBlockNum + _blockDiff); vm.roll(initialBlockNum + _blockDiff);
meter.use(_amount); meter.use(_amount);
} }
...@@ -182,6 +181,15 @@ contract CustomMeterUser is ResourceMetering { ...@@ -182,6 +181,15 @@ contract CustomMeterUser is ResourceMetering {
}); });
} }
function _resourceConfig()
internal
pure
override
returns (ResourceMetering.ResourceConfig memory)
{
return Constants.DEFAULT_RESOURCE_CONFIG();
}
function use(uint64 _amount) public returns (uint256) { function use(uint64 _amount) public returns (uint256) {
uint256 initialGas = gasleft(); uint256 initialGas = gasleft();
_metered(_amount, initialGas); _metered(_amount, initialGas);
...@@ -224,10 +232,11 @@ contract ArtifactResourceMetering_Test is Test { ...@@ -224,10 +232,11 @@ contract ArtifactResourceMetering_Test is Test {
vm.roll(1_000_000); vm.roll(1_000_000);
MeterUser base = new MeterUser(); MeterUser base = new MeterUser();
minimumBaseFee = uint128(uint256(base.MINIMUM_BASE_FEE())); ResourceMetering.ResourceConfig memory rcfg = base.resourceConfig();
maximumBaseFee = uint128(uint256(base.MAXIMUM_BASE_FEE())); minimumBaseFee = uint128(rcfg.minimumBaseFee);
maxResourceLimit = uint64(uint256(base.MAX_RESOURCE_LIMIT())); maximumBaseFee = rcfg.maximumBaseFee;
targetResourceLimit = uint64(uint256(base.TARGET_RESOURCE_LIMIT())); maxResourceLimit = uint64(rcfg.maxResourceLimit);
targetResourceLimit = uint64(rcfg.maxResourceLimit) / uint64(rcfg.elasticityMultiplier);
outfile = string.concat(vm.projectRoot(), "/.resource-metering.csv"); outfile = string.concat(vm.projectRoot(), "/.resource-metering.csv");
try vm.removeFile(outfile) {} catch {} try vm.removeFile(outfile) {} catch {}
......
...@@ -3,36 +3,58 @@ pragma solidity 0.8.15; ...@@ -3,36 +3,58 @@ pragma solidity 0.8.15;
import { CommonTest } from "./CommonTest.t.sol"; import { CommonTest } from "./CommonTest.t.sol";
import { SystemConfig } from "../L1/SystemConfig.sol"; import { SystemConfig } from "../L1/SystemConfig.sol";
import { ResourceMetering } from "../L1/ResourceMetering.sol";
import { Constants } from "../libraries/Constants.sol";
contract SystemConfig_Init is CommonTest { contract SystemConfig_Init is CommonTest {
SystemConfig sysConf; SystemConfig sysConf;
function setUp() public virtual override { function setUp() public virtual override {
super.setUp(); super.setUp();
ResourceMetering.ResourceConfig memory config = ResourceMetering.ResourceConfig({
maxResourceLimit: 20_000_000,
elasticityMultiplier: 10,
baseFeeMaxChangeDenominator: 8,
minimumBaseFee: 1 gwei,
systemTxMaxGas: 1_000_000,
maximumBaseFee: type(uint128).max
});
sysConf = new SystemConfig({ sysConf = new SystemConfig({
_owner: alice, _owner: alice,
_overhead: 2100, _overhead: 2100,
_scalar: 1000000, _scalar: 1000000,
_batcherHash: bytes32(hex"abcd"), _batcherHash: bytes32(hex"abcd"),
_gasLimit: 9_000_000, _gasLimit: 30_000_000,
_unsafeBlockSigner: address(1) _unsafeBlockSigner: address(1),
_config: config
}); });
} }
} }
contract SystemConfig_Initialize_TestFail is CommonTest { contract SystemConfig_Initialize_TestFail is SystemConfig_Init {
function test_initialize_lowGasLimit_reverts() external { function test_initialize_lowGasLimit_reverts() external {
vm.expectRevert("SystemConfig: gas limit too low"); uint64 minimumGasLimit = sysConf.minimumGasLimit();
ResourceMetering.ResourceConfig memory cfg = ResourceMetering.ResourceConfig({
maxResourceLimit: 20_000_000,
elasticityMultiplier: 10,
baseFeeMaxChangeDenominator: 8,
minimumBaseFee: 1 gwei,
systemTxMaxGas: 1_000_000,
maximumBaseFee: type(uint128).max
});
// The minimum gas limit defined in SystemConfig: vm.expectRevert("SystemConfig: gas limit too low");
uint64 MINIMUM_GAS_LIMIT = 8_000_000;
new SystemConfig({ new SystemConfig({
_owner: alice, _owner: alice,
_overhead: 0, _overhead: 0,
_scalar: 0, _scalar: 0,
_batcherHash: bytes32(hex""), _batcherHash: bytes32(hex""),
_gasLimit: MINIMUM_GAS_LIMIT - 1, _gasLimit: minimumGasLimit - 1,
_unsafeBlockSigner: address(1) _unsafeBlockSigner: address(1),
_config: cfg
}); });
} }
} }
...@@ -57,6 +79,70 @@ contract SystemConfig_Setters_TestFail is SystemConfig_Init { ...@@ -57,6 +79,70 @@ contract SystemConfig_Setters_TestFail is SystemConfig_Init {
vm.expectRevert("Ownable: caller is not the owner"); vm.expectRevert("Ownable: caller is not the owner");
sysConf.setUnsafeBlockSigner(address(0x20)); sysConf.setUnsafeBlockSigner(address(0x20));
} }
function test_setResourceConfig_notOwner_reverts() external {
ResourceMetering.ResourceConfig memory config = Constants.DEFAULT_RESOURCE_CONFIG();
vm.expectRevert("Ownable: caller is not the owner");
sysConf.setResourceConfig(config);
}
function test_setResourceConfig_badMinMax_reverts() external {
ResourceMetering.ResourceConfig memory config = ResourceMetering.ResourceConfig({
maxResourceLimit: 20_000_000,
elasticityMultiplier: 10,
baseFeeMaxChangeDenominator: 8,
systemTxMaxGas: 1_000_000,
minimumBaseFee: 2 gwei,
maximumBaseFee: 1 gwei
});
vm.prank(sysConf.owner());
vm.expectRevert("SystemConfig: min base fee must be less than max base");
sysConf.setResourceConfig(config);
}
function test_setResourceConfig_zeroDenominator_reverts() external {
ResourceMetering.ResourceConfig memory config = ResourceMetering.ResourceConfig({
maxResourceLimit: 20_000_000,
elasticityMultiplier: 10,
baseFeeMaxChangeDenominator: 0,
systemTxMaxGas: 1_000_000,
minimumBaseFee: 1 gwei,
maximumBaseFee: 2 gwei
});
vm.prank(sysConf.owner());
vm.expectRevert("SystemConfig: denominator cannot be 0");
sysConf.setResourceConfig(config);
}
function test_setResourceConfig_lowGasLimit_reverts() external {
uint64 gasLimit = sysConf.gasLimit();
ResourceMetering.ResourceConfig memory config = ResourceMetering.ResourceConfig({
maxResourceLimit: uint32(gasLimit),
elasticityMultiplier: 10,
baseFeeMaxChangeDenominator: 8,
systemTxMaxGas: uint32(gasLimit),
minimumBaseFee: 1 gwei,
maximumBaseFee: 2 gwei
});
vm.prank(sysConf.owner());
vm.expectRevert("SystemConfig: gas limit too low");
sysConf.setResourceConfig(config);
}
function test_setResourceConfig_badPrecision_reverts() external {
ResourceMetering.ResourceConfig memory config = ResourceMetering.ResourceConfig({
maxResourceLimit: 20_000_000,
elasticityMultiplier: 11,
baseFeeMaxChangeDenominator: 8,
systemTxMaxGas: 1_000_000,
minimumBaseFee: 1 gwei,
maximumBaseFee: 2 gwei
});
vm.prank(sysConf.owner());
vm.expectRevert("SystemConfig: precision loss with target resource limit");
sysConf.setResourceConfig(config);
}
} }
contract SystemConfig_Setters_Test is SystemConfig_Init { contract SystemConfig_Setters_Test is SystemConfig_Init {
......
...@@ -2,18 +2,23 @@ pragma solidity 0.8.15; ...@@ -2,18 +2,23 @@ pragma solidity 0.8.15;
import { Test } from "forge-std/Test.sol"; import { Test } from "forge-std/Test.sol";
import { SystemConfig } from "../../L1/SystemConfig.sol"; import { SystemConfig } from "../../L1/SystemConfig.sol";
import { ResourceMetering } from "../../L1/ResourceMetering.sol";
import { Constants } from "../../libraries/Constants.sol";
contract SystemConfig_GasLimitLowerBound_Invariant is Test { contract SystemConfig_GasLimitLowerBound_Invariant is Test {
SystemConfig public config; SystemConfig public config;
function setUp() public { function setUp() public {
ResourceMetering.ResourceConfig memory cfg = Constants.DEFAULT_RESOURCE_CONFIG();
config = new SystemConfig({ config = new SystemConfig({
_owner: address(0xbeef), _owner: address(0xbeef),
_overhead: 2100, _overhead: 2100,
_scalar: 1000000, _scalar: 1000000,
_batcherHash: bytes32(hex"abcd"), _batcherHash: bytes32(hex"abcd"),
_gasLimit: 8_000_000, _gasLimit: 30_000_000,
_unsafeBlockSigner: address(1) _unsafeBlockSigner: address(1),
_config: cfg
}); });
// Set the target contract to the `config` // Set the target contract to the `config`
......
...@@ -17,6 +17,10 @@ const deployFn: DeployFunction = async (hre) => { ...@@ -17,6 +17,10 @@ const deployFn: DeployFunction = async (hre) => {
'L2OutputOracleProxy' 'L2OutputOracleProxy'
) )
const Artifact__SystemConfigProxy = await hre.deployments.get(
'SystemConfigProxy'
)
const portalGuardian = hre.deployConfig.portalGuardian const portalGuardian = hre.deployConfig.portalGuardian
const portalGuardianCode = await hre.ethers.provider.getCode(portalGuardian) const portalGuardianCode = await hre.ethers.provider.getCode(portalGuardian)
if (portalGuardianCode === '0x') { if (portalGuardianCode === '0x') {
...@@ -41,6 +45,7 @@ const deployFn: DeployFunction = async (hre) => { ...@@ -41,6 +45,7 @@ const deployFn: DeployFunction = async (hre) => {
L2OutputOracleProxy.address, L2OutputOracleProxy.address,
portalGuardian, portalGuardian,
true, // paused true, // paused
Artifact__SystemConfigProxy.address,
], ],
postDeployAction: async (contract) => { postDeployAction: async (contract) => {
await assertContractVariable( await assertContractVariable(
...@@ -53,6 +58,11 @@ const deployFn: DeployFunction = async (hre) => { ...@@ -53,6 +58,11 @@ const deployFn: DeployFunction = async (hre) => {
'GUARDIAN', 'GUARDIAN',
hre.deployConfig.portalGuardian hre.deployConfig.portalGuardian
) )
await assertContractVariable(
contract,
'SYSTEM_CONFIG',
Artifact__SystemConfigProxy.address
)
}, },
}) })
} }
......
import assert from 'assert'
import { DeployFunction } from 'hardhat-deploy/dist/types' import { DeployFunction } from 'hardhat-deploy/dist/types'
import '@eth-optimism/hardhat-deploy-config' import '@eth-optimism/hardhat-deploy-config'
import { ethers } from 'ethers'
import { assertContractVariable, deploy } from '../src/deploy-utils' import { assertContractVariable, deploy } from '../src/deploy-utils'
const uint128Max = ethers.BigNumber.from('0xffffffffffffffffffffffffffffffff')
const deployFn: DeployFunction = async (hre) => { const deployFn: DeployFunction = async (hre) => {
const batcherHash = hre.ethers.utils const batcherHash = hre.ethers.utils
.hexZeroPad(hre.deployConfig.batchSenderAddress, 32) .hexZeroPad(hre.deployConfig.batchSenderAddress, 32)
...@@ -18,6 +23,14 @@ const deployFn: DeployFunction = async (hre) => { ...@@ -18,6 +23,14 @@ const deployFn: DeployFunction = async (hre) => {
batcherHash, batcherHash,
hre.deployConfig.l2GenesisBlockGasLimit, hre.deployConfig.l2GenesisBlockGasLimit,
hre.deployConfig.p2pSequencerAddress, hre.deployConfig.p2pSequencerAddress,
{
maxResourceLimit: 20_000_000,
elasticityMultiplier: 10,
baseFeeMaxChangeDenominator: 8,
systemTxMaxGas: 1_000_000,
minimumBaseFee: ethers.utils.parseUnits('1', 'gwei'),
maximumBaseFee: uint128Max,
},
], ],
postDeployAction: async (contract) => { postDeployAction: async (contract) => {
await assertContractVariable( await assertContractVariable(
...@@ -41,6 +54,14 @@ const deployFn: DeployFunction = async (hre) => { ...@@ -41,6 +54,14 @@ const deployFn: DeployFunction = async (hre) => {
'unsafeBlockSigner', 'unsafeBlockSigner',
hre.deployConfig.p2pSequencerAddress hre.deployConfig.p2pSequencerAddress
) )
const config = await contract.resourceConfig()
assert(config.maxResourceLimit === 20_000_000)
assert(config.elasticityMultiplier === 10)
assert(config.baseFeeMaxChangeDenominator === 8)
assert(config.systemTxMaxGas === 1_000_000)
assert(ethers.utils.parseUnits('1', 'gwei').eq(config.minimumBaseFee))
assert(config.maximumBaseFee.eq(uint128Max))
}, },
}) })
} }
......
import assert from 'assert' import assert from 'assert'
import { ethers } from 'ethers' import { ethers, BigNumber } from 'ethers'
import { DeployFunction } from 'hardhat-deploy/dist/types' import { DeployFunction } from 'hardhat-deploy/dist/types'
import { awaitCondition } from '@eth-optimism/core-utils' import { awaitCondition } from '@eth-optimism/core-utils'
import '@eth-optimism/hardhat-deploy-config' import '@eth-optimism/hardhat-deploy-config'
...@@ -100,6 +100,18 @@ const deployFn: DeployFunction = async (hre) => { ...@@ -100,6 +100,18 @@ const deployFn: DeployFunction = async (hre) => {
), ),
gasLimit: hre.deployConfig.l2GenesisBlockGasLimit, gasLimit: hre.deployConfig.l2GenesisBlockGasLimit,
unsafeBlockSigner: hre.deployConfig.p2pSequencerAddress, unsafeBlockSigner: hre.deployConfig.p2pSequencerAddress,
// The resource config is not exposed to the end user
// to simplify deploy config. It may be introduced in the future.
resourceConfig: {
maxResourceLimit: 20_000_000,
elasticityMultiplier: 10,
baseFeeMaxChangeDenominator: 8,
minimumBaseFee: ethers.utils.parseUnits('1', 'gwei'),
systemTxMaxGas: 1_000_000,
maximumBaseFee: BigNumber.from(
'0xffffffffffffffffffffffffffffffff'
).toString(),
},
}, },
} }
......
...@@ -17,6 +17,8 @@ import { ...@@ -17,6 +17,8 @@ import {
getCastCommand, getCastCommand,
} from '../src/deploy-utils' } from '../src/deploy-utils'
const uint128Max = ethers.BigNumber.from('0xffffffffffffffffffffffffffffffff')
const deployFn: DeployFunction = async (hre) => { const deployFn: DeployFunction = async (hre) => {
const { deployer } = await hre.getNamedAccounts() const { deployer } = await hre.getNamedAccounts()
...@@ -254,6 +256,14 @@ const deployFn: DeployFunction = async (hre) => { ...@@ -254,6 +256,14 @@ const deployFn: DeployFunction = async (hre) => {
'gasLimit', 'gasLimit',
hre.deployConfig.l2GenesisBlockGasLimit hre.deployConfig.l2GenesisBlockGasLimit
) )
const config = await SystemConfigProxy.resourceConfig()
assert(config.maxResourceLimit === 20_000_000)
assert(config.elasticityMultiplier === 10)
assert(config.baseFeeMaxChangeDenominator === 8)
assert(config.systemTxMaxGas === 1_000_000)
assert(ethers.utils.parseUnits('1', 'gwei').eq(config.minimumBaseFee))
assert(config.maximumBaseFee.eq(uint128Max))
}, },
}) })
......
...@@ -234,7 +234,7 @@ const deployFn: DeployFunction = async (hre) => { ...@@ -234,7 +234,7 @@ const deployFn: DeployFunction = async (hre) => {
) )
const resourceParams = await OptimismPortal.params() const resourceParams = await OptimismPortal.params()
assert( assert(
resourceParams.prevBaseFee.eq(await OptimismPortal.INITIAL_BASE_FEE()), resourceParams.prevBaseFee.eq(ethers.utils.parseUnits('1', 'gwei')),
`OptimismPortal was not initialized with the correct initial base fee` `OptimismPortal was not initialized with the correct initial base fee`
) )
assert( assert(
......
...@@ -19,6 +19,6 @@ This invariant asserts that there is no chain of calls that can be made that wil ...@@ -19,6 +19,6 @@ This invariant asserts that there is no chain of calls that can be made that wil
## Deposits of any value should always succeed unless `_to` = `address(0)` or `_isCreation` = `true`. ## Deposits of any value should always succeed unless `_to` = `address(0)` or `_isCreation` = `true`.
**Test:** [`FuzzOptimismPortal.sol#L41`](../contracts/echidna/FuzzOptimismPortal.sol#L41) **Test:** [`FuzzOptimismPortal.sol#L57`](../contracts/echidna/FuzzOptimismPortal.sol#L57)
All deposits, barring creation transactions and transactions sent to `address(0)`, should always succeed. All deposits, barring creation transactions and transactions sent to `address(0)`, should always succeed.
# `ResourceMetering` Invariants # `ResourceMetering` Invariants
## The base fee should increase if the last block used more than the target amount of gas ## The base fee should increase if the last block used more than the target amount of gas
**Test:** [`FuzzResourceMetering.sol#L139`](../contracts/echidna/FuzzResourceMetering.sol#L139) **Test:** [`FuzzResourceMetering.sol#L158`](../contracts/echidna/FuzzResourceMetering.sol#L158)
If the last block used more than the target amount of gas (and there were no empty blocks in between), ensure this block's baseFee increased, but not by more than the max amount per block. If the last block used more than the target amount of gas (and there were no empty blocks in between), ensure this block's baseFee increased, but not by more than the max amount per block.
## The base fee should decrease if the last block used less than the target amount of gas ## The base fee should decrease if the last block used less than the target amount of gas
**Test:** [`FuzzResourceMetering.sol#L150`](../contracts/echidna/FuzzResourceMetering.sol#L150) **Test:** [`FuzzResourceMetering.sol#L169`](../contracts/echidna/FuzzResourceMetering.sol#L169)
If the previous block used less than the target amount of gas, the base fee should decrease, but not more than the max amount. If the previous block used less than the target amount of gas, the base fee should decrease, but not more than the max amount.
## A block's base fee should never be below `MINIMUM_BASE_FEE` ## A block's base fee should never be below `MINIMUM_BASE_FEE`
**Test:** [`FuzzResourceMetering.sol#L160`](../contracts/echidna/FuzzResourceMetering.sol#L160) **Test:** [`FuzzResourceMetering.sol#L179`](../contracts/echidna/FuzzResourceMetering.sol#L179)
This test asserts that a block's base fee can never drop below the `MINIMUM_BASE_FEE` threshold. This test asserts that a block's base fee can never drop below the `MINIMUM_BASE_FEE` threshold.
## A block can never consume more than `MAX_RESOURCE_LIMIT` gas. ## A block can never consume more than `MAX_RESOURCE_LIMIT` gas.
**Test:** [`FuzzResourceMetering.sol#L170`](../contracts/echidna/FuzzResourceMetering.sol#L170) **Test:** [`FuzzResourceMetering.sol#L189`](../contracts/echidna/FuzzResourceMetering.sol#L189)
This test asserts that a block can never consume more than the `MAX_RESOURCE_LIMIT` gas threshold. This test asserts that a block can never consume more than the `MAX_RESOURCE_LIMIT` gas threshold.
## The base fee can never be raised more than the max base fee change. ## The base fee can never be raised more than the max base fee change.
**Test:** [`FuzzResourceMetering.sol#L181`](../contracts/echidna/FuzzResourceMetering.sol#L181) **Test:** [`FuzzResourceMetering.sol#L200`](../contracts/echidna/FuzzResourceMetering.sol#L200)
After a block consumes more gas than the target gas, the base fee cannot be raised more than the maximum amount allowed. The max base fee change (per-block) is derived as follows: `prevBaseFee / BASE_FEE_MAX_CHANGE_DENOMINATOR` After a block consumes more gas than the target gas, the base fee cannot be raised more than the maximum amount allowed. The max base fee change (per-block) is derived as follows: `prevBaseFee / BASE_FEE_MAX_CHANGE_DENOMINATOR`
## The base fee can never be lowered more than the max base fee change. ## The base fee can never be lowered more than the max base fee change.
**Test:** [`FuzzResourceMetering.sol#L192`](../contracts/echidna/FuzzResourceMetering.sol#L192) **Test:** [`FuzzResourceMetering.sol#L211`](../contracts/echidna/FuzzResourceMetering.sol#L211)
After a block consumes less than the target gas, the base fee cannot be lowered more than the maximum amount allowed. The max base fee change (per-block) is derived as follows: `prevBaseFee / BASE_FEE_MAX_CHANGE_DENOMINATOR` After a block consumes less than the target gas, the base fee cannot be lowered more than the maximum amount allowed. The max base fee change (per-block) is derived as follows: `prevBaseFee / BASE_FEE_MAX_CHANGE_DENOMINATOR`
## The `maxBaseFeeChange` calculation over multiple blocks can never underflow. ## The `maxBaseFeeChange` calculation over multiple blocks can never underflow.
**Test:** [`FuzzResourceMetering.sol#L203`](../contracts/echidna/FuzzResourceMetering.sol#L203) **Test:** [`FuzzResourceMetering.sol#L222`](../contracts/echidna/FuzzResourceMetering.sol#L222)
When calculating the `maxBaseFeeChange` after multiple empty blocks, the calculation should never be allowed to underflow. When calculating the `maxBaseFeeChange` after multiple empty blocks, the calculation should never be allowed to underflow.
# `SystemConfig` Invariants # `SystemConfig` Invariants
## The gas limit of the `SystemConfig` contract can never be lower than the hard-coded lower bound. ## The gas limit of the `SystemConfig` contract can never be lower than the hard-coded lower bound.
**Test:** [`SystemConfig.t.sol#L39`](../contracts/test/invariants/SystemConfig.t.sol#L39) **Test:** [`SystemConfig.t.sol#L44`](../contracts/test/invariants/SystemConfig.t.sol#L44)
...@@ -5,8 +5,10 @@ ...@@ -5,8 +5,10 @@
**Table of Contents** **Table of Contents**
- [Gas Stipend](#gas-stipend) - [Gas Stipend](#gas-stipend)
- [Default Values](#default-values)
- [Limiting Guaranteed Gas](#limiting-guaranteed-gas) - [Limiting Guaranteed Gas](#limiting-guaranteed-gas)
- [Rationale for burning L1 Gas](#rationale-for-burning-l1-gas) - [Rationale for burning L1 Gas](#rationale-for-burning-l1-gas)
- [On Preventing Griefing Attacks](#on-preventing-griefing-attacks)
<!-- END doctoc generated TOC please keep comment here to allow auto update --> <!-- END doctoc generated TOC please keep comment here to allow auto update -->
...@@ -36,6 +38,17 @@ the L2 gas, where `gas spent` is the amount of L1 gas spent processing the depos ...@@ -36,6 +38,17 @@ the L2 gas, where `gas spent` is the amount of L1 gas spent processing the depos
of this credit is greater than the ETH value of the requested guaranteed gas of this credit is greater than the ETH value of the requested guaranteed gas
(`requested guaranteed gas * L2 gas price`), no L1 gas is burnt. (`requested guaranteed gas * L2 gas price`), no L1 gas is burnt.
## Default Values
| Variable | Value |
| ------------------------------- | ----------------- |
| Max Resource Limit | 20,000,000 |
| Elasticity Multiplier | 10 |
| Base Fee Max Change Denominator | 8 |
| Minimum Base Fee | 1 gwei |
| Maximum Base Fee | type(uint128).max |
| System Tx Max Gas | 1,000,000 |
## Limiting Guaranteed Gas ## Limiting Guaranteed Gas
The total amount of guaranteed gas that can be bought in a single L1 block must be limited to The total amount of guaranteed gas that can be bought in a single L1 block must be limited to
...@@ -123,3 +136,20 @@ The payable version (Option 2) will likely have discount applied to it (or conve ...@@ -123,3 +136,20 @@ The payable version (Option 2) will likely have discount applied to it (or conve
premium applied to it). premium applied to it).
For the initial release of bedrock, only #1 is supported. For the initial release of bedrock, only #1 is supported.
## On Preventing Griefing Attacks
The cost of purchasing all of the deposit gas in every block must be expensive
enough to prevent attackers from griefing all deposits to the network.
An attacker would observe a deposit in the mempool and frontrun it with a deposit
that purchases enough gas such that the other deposit reverts.
The smaller the max resource limit is, the easier this attack is to pull off.
This attack is mitigated by having a large resource limit as well as a large
elastcity multiplier. This means that the target resource usage is kept small,
giving a lot of room for the deposit base fee to rise when the max resource limit
is being purchased.
This attack should be too expensive to pull off in practice, but if an extremely
wealthy adversary does decide to grief network deposits for an extended period
of time, efforts will be placed to ensure that deposits are able to be processed
on the network.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment