Commit e0c5cb1c authored by Mark Tyneway's avatar Mark Tyneway

contracts-bedrock: modularize deposit resource config

parent 0446d83b
......@@ -4,6 +4,7 @@ pragma solidity 0.8.15;
import { Initializable } from "@openzeppelin/contracts/proxy/utils/Initializable.sol";
import { SafeCall } from "../libraries/SafeCall.sol";
import { L2OutputOracle } from "./L2OutputOracle.sol";
import { SystemConfig } from "./SystemConfig.sol";
import { Constants } from "../libraries/Constants.sol";
import { Types } from "../libraries/Types.sol";
import { Hashing } from "../libraries/Hashing.sol";
......@@ -48,6 +49,11 @@ contract OptimismPortal is Initializable, ResourceMetering, Semver {
*/
L2OutputOracle public immutable L2_ORACLE;
/**
*
*/
SystemConfig public immutable SYSTEM_CONFIG;
/**
* @notice Address that has the ability to pause and unpause withdrawals.
*/
......@@ -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 _guardian Address that can pause deposits and withdrawals.
* @param _paused Sets the contract's pausability state.
* @param _config Address of the SystemConfig contract.
*/
constructor(
L2OutputOracle _l2Oracle,
address _guardian,
bool _paused
) Semver(1, 2, 0) {
bool _paused,
SystemConfig _config
) Semver(1, 3, 0) {
L2_ORACLE = _l2Oracle;
GUARDIAN = _guardian;
SYSTEM_CONFIG = _config;
initialize(_paused);
}
......@@ -197,6 +206,30 @@ contract OptimismPortal is Initializable, ResourceMetering, Semver {
// Intentionally empty.
}
// TODO: I don't like having so many return args but I also don't like
// wrapping them into a struct
function resourceConfig() public view override returns (SystemConfig.ResourceConfig memory) {
(
uint32 maxResourceLimit,
uint8 elasticityMultiplier,
uint8 baseFeeMaxChangeDenominator,
uint32 minimumBaseFee,
uint32 systemTxMaxGas,
uint128 maximumBaseFee
) = SYSTEM_CONFIG.resourceConfig();
SystemConfig.ResourceConfig memory config = SystemConfig.ResourceConfig({
maxResourceLimit: maxResourceLimit,
elasticityMultiplier: elasticityMultiplier,
baseFeeMaxChangeDenominator: baseFeeMaxChangeDenominator,
minimumBaseFee: minimumBaseFee,
systemTxMaxGas: systemTxMaxGas,
maximumBaseFee: maximumBaseFee
});
return config;
}
/**
* @notice Proves a withdrawal transaction.
*
......
......@@ -5,6 +5,7 @@ import { Initializable } from "@openzeppelin/contracts/proxy/utils/Initializable
import { Math } from "@openzeppelin/contracts/utils/math/Math.sol";
import { Burn } from "../libraries/Burn.sol";
import { Arithmetic } from "../libraries/Arithmetic.sol";
import { SystemConfig } from "../L1/SystemConfig.sol";
/**
* @custom:upgradeable
......@@ -27,46 +28,6 @@ abstract contract ResourceMetering is Initializable {
uint64 prevBlockNum;
}
/**
* @notice Maximum amount of the resource that can be used within this block.
* This value cannot be larger than the L2 block gas limit.
*/
int256 public constant MAX_RESOURCE_LIMIT = 20_000_000;
/**
* @notice Along with the resource limit, determines the target resource limit.
*/
int256 public constant ELASTICITY_MULTIPLIER = 10;
/**
* @notice Target amount of the resource that should be used within this block.
*/
int256 public constant TARGET_RESOURCE_LIMIT = MAX_RESOURCE_LIMIT / ELASTICITY_MULTIPLIER;
/**
* @notice Denominator that determines max change on fee per block.
*/
int256 public constant BASE_FEE_MAX_CHANGE_DENOMINATOR = 8;
/**
* @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;
/**
* @notice EIP-1559 style gas parameters.
*/
......@@ -102,20 +63,24 @@ abstract contract ResourceMetering is Initializable {
function _metered(uint64 _amount, uint256 _initialGas) internal {
// Update block number and base fee if necessary.
uint256 blockDiff = block.number - params.prevBlockNum;
SystemConfig.ResourceConfig memory config = resourceConfig();
int256 targetResourceLimit = int256(uint256(config.maxResourceLimit)) / int256(uint256(config.elasticityMultiplier));
if (blockDiff > 0) {
// 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
// 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) /
(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
// min and max.
int256 newBaseFee = Arithmetic.clamp({
_value: int256(uint256(params.prevBaseFee)) + baseFeeDelta,
_min: MINIMUM_BASE_FEE,
_max: MAXIMUM_BASE_FEE
_min: int256(uint256(config.minimumBaseFee)),
_max: int256(uint256(config.maximumBaseFee))
});
// If we skipped more than one block, we also need to account for every empty block.
......@@ -128,11 +93,11 @@ abstract contract ResourceMetering is Initializable {
newBaseFee = Arithmetic.clamp({
_value: Arithmetic.cdexp({
_coefficient: newBaseFee,
_denominator: BASE_FEE_MAX_CHANGE_DENOMINATOR,
_denominator: int256(uint256(config.baseFeeMaxChangeDenominator)),
_exponent: int256(blockDiff - 1)
}),
_min: MINIMUM_BASE_FEE,
_max: MAXIMUM_BASE_FEE
_min: int256(uint256(config.minimumBaseFee)),
_max: int256(uint256(config.maximumBaseFee))
});
}
......@@ -145,7 +110,7 @@ abstract contract ResourceMetering is Initializable {
// Make sure we can actually buy the resource amount requested by the user.
params.prevBoughtGas += _amount;
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"
);
......@@ -168,14 +133,21 @@ abstract contract ResourceMetering is Initializable {
}
}
/**
* @notice
*/
function resourceConfig() public virtual returns (SystemConfig.ResourceConfig memory);
/**
* @notice Sets initial resource parameter values. This function must either be called by the
* initializer function of an upgradeable child contract.
*/
// solhint-disable-next-line func-name-mixedcase
function __ResourceMetering_init() internal onlyInitializing {
SystemConfig.ResourceConfig memory config = resourceConfig();
params = ResourceParams({
prevBaseFee: INITIAL_BASE_FEE,
prevBaseFee: config.minimumBaseFee,
prevBoughtGas: 0,
prevBlockNum: uint64(block.number)
});
......
......@@ -26,7 +26,20 @@ contract SystemConfig is OwnableUpgradeable, Semver {
BATCHER,
GAS_CONFIG,
GAS_LIMIT,
UNSAFE_BLOCK_SIGNER
UNSAFE_BLOCK_SIGNER,
RESOURCE_CONFIG
}
/**
* @notice
*/
struct ResourceConfig {
uint32 maxResourceLimit;
uint8 elasticityMultiplier;
uint8 baseFeeMaxChangeDenominator;
uint32 minimumBaseFee;
uint32 systemTxMaxGas;
uint128 maximumBaseFee;
}
/**
......@@ -69,6 +82,8 @@ contract SystemConfig is OwnableUpgradeable, Semver {
*/
uint64 public gasLimit;
ResourceConfig public resourceConfig;
/**
* @notice Emitted when configuration is updated
*
......@@ -79,7 +94,7 @@ contract SystemConfig is OwnableUpgradeable, Semver {
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 _overhead Initial overhead value.
......@@ -95,12 +110,22 @@ contract SystemConfig is OwnableUpgradeable, Semver {
bytes32 _batcherHash,
uint64 _gasLimit,
address _unsafeBlockSigner
) Semver(1, 0, 1) {
initialize(_owner, _overhead, _scalar, _batcherHash, _gasLimit, _unsafeBlockSigner);
) Semver(1, 1, 0) {
ResourceConfig memory config = ResourceConfig({
maxResourceLimit: 20_000_000,
elasticityMultiplier: 10,
baseFeeMaxChangeDenominator: 8,
minimumBaseFee: 1 gwei,
systemTxMaxGas: 1_000_000,
maximumBaseFee: type(uint128).max
});
initialize(_owner, _overhead, _scalar, _batcherHash, _gasLimit, _unsafeBlockSigner, config);
}
/**
* @notice Initializer.
* @notice Initializer. The resource config must be set before the
* require check.
*
* @param _owner Initial owner of the contract.
* @param _overhead Initial overhead value.
......@@ -115,9 +140,9 @@ contract SystemConfig is OwnableUpgradeable, Semver {
uint256 _scalar,
bytes32 _batcherHash,
uint64 _gasLimit,
address _unsafeBlockSigner
address _unsafeBlockSigner,
ResourceConfig memory _resourceConfig
) public initializer {
require(_gasLimit >= MINIMUM_GAS_LIMIT, "SystemConfig: gas limit too low");
__Ownable_init();
transferOwnership(_owner);
overhead = _overhead;
......@@ -125,6 +150,8 @@ contract SystemConfig is OwnableUpgradeable, Semver {
batcherHash = _batcherHash;
gasLimit = _gasLimit;
_setUnsafeBlockSigner(_unsafeBlockSigner);
_setResourceConfig(_resourceConfig);
require(_gasLimit >= minimumGasLimit(), "SystemConfig: gas limit too low");
}
/**
......@@ -188,7 +215,7 @@ contract SystemConfig is OwnableUpgradeable, Semver {
* @param _gasLimit New gas limit.
*/
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;
bytes memory data = abi.encode(_gasLimit);
......@@ -197,7 +224,7 @@ contract SystemConfig is OwnableUpgradeable, Semver {
/**
* @notice Low level setter for the unsafe block signer address. This function exists to
* deduplicate code around storing the unsafeBlockSigner address in storage.
* deduplicate code arou,nd storing the unsafeBlockSigner address in storage.
*
* @param _unsafeBlockSigner New unsafeBlockSigner value.
*/
......@@ -207,4 +234,23 @@ contract SystemConfig is OwnableUpgradeable, Semver {
sstore(slot, _unsafeBlockSigner)
}
}
function setResourceConfig(ResourceConfig memory _config) external onlyOwner {
_setResourceConfig(_config);
bytes memory data = abi.encode(_config);
emit ConfigUpdate(VERSION, UpdateType.RESOURCE_CONFIG, data);
}
function _setResourceConfig(ResourceConfig memory _config) internal {
require(_config.minimumBaseFee <= _config.maximumBaseFee);
require(_config.baseFeeMaxChangeDenominator > 0);
require(_config.maxResourceLimit + _config.systemTxMaxGas <= gasLimit);
resourceConfig = _config;
}
function minimumGasLimit() public view returns (uint256) {
return resourceConfig.maxResourceLimit + resourceConfig.systemTxMaxGas;
}
}
......@@ -79,6 +79,7 @@ contract SystemDictator is OwnableUpgradeable {
bytes32 batcherHash;
uint64 gasLimit;
address unsafeBlockSigner;
SystemConfig.ResourceConfig resourceConfig;
}
/**
......@@ -160,6 +161,15 @@ contract SystemDictator is OwnableUpgradeable {
* initialized upon deployment.
*/
constructor() {
SystemConfig.ResourceConfig memory rcfg = SystemConfig.ResourceConfig({
maxResourceLimit: 20_000_000,
elasticityMultiplier: 10,
baseFeeMaxChangeDenominator: 8,
minimumBaseFee: 1 gwei,
systemTxMaxGas: 1_000_000,
maximumBaseFee: type(uint128).max
});
// 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.
address zero = address(0);
......@@ -177,7 +187,7 @@ contract SystemDictator is OwnableUpgradeable {
PortalSender(zero),
SystemConfig(zero)
),
SystemConfigConfig(zero, 0, 0, bytes32(0), 0, zero)
SystemConfigConfig(zero, 0, 0, bytes32(0), 0, zero, rcfg)
)
);
}
......@@ -244,7 +254,8 @@ contract SystemDictator is OwnableUpgradeable {
config.systemConfigConfig.scalar,
config.systemConfigConfig.batcherHash,
config.systemConfigConfig.gasLimit,
config.systemConfigConfig.unsafeBlockSigner
config.systemConfigConfig.unsafeBlockSigner,
config.systemConfigConfig.resourceConfig
)
)
);
......
......@@ -3,16 +3,27 @@ pragma solidity 0.8.15;
import { OptimismPortal } from "../L1/OptimismPortal.sol";
import { L2OutputOracle } from "../L1/L2OutputOracle.sol";
import { AddressAliasHelper } from "../vendor/AddressAliasHelper.sol";
import { SystemConfig } from "../L1/SystemConfig.sol";
contract EchidnaFuzzOptimismPortal {
OptimismPortal internal portal;
bool internal failedToComplete;
constructor() {
SystemConfig config = new SystemConfig({
_owner: address(0),
_overhead: 0,
_scalar: 10000,
_batcherHash: bytes32(0),
_gasLimit: 30_000_000,
_unsafeBlockSigner: address(0)
});
portal = new OptimismPortal({
_l2Oracle: L2OutputOracle(address(0)),
_guardian: address(0),
_paused: false
_paused: false,
_config: config
});
}
......
pragma solidity 0.8.15;
import { ResourceMetering } from "../L1/ResourceMetering.sol";
import { SystemConfig } from "../L1/SystemConfig.sol";
import { Arithmetic } from "../libraries/Arithmetic.sol";
import { StdUtils } from "forge-std/Test.sol";
......@@ -24,6 +25,18 @@ contract EchidnaFuzzResourceMetering is ResourceMetering, StdUtils {
__ResourceMetering_init();
}
function resourceConfig() public pure override returns (SystemConfig.ResourceConfig memory) {
SystemConfig.ResourceConfig memory config = SystemConfig.ResourceConfig({
maxResourceLimit: 20_000_000,
elasticityMultiplier: 10,
baseFeeMaxChangeDenominator: 8,
minimumBaseFee: 1 gwei,
systemTxMaxGas: 1_000_000,
maximumBaseFee: type(uint128).max
});
return config;
}
/**
* @notice Takes the necessary parameters to allow us to burn arbitrary amounts of gas to test
* the underlying resource metering/gas market logic
......@@ -34,12 +47,15 @@ contract EchidnaFuzzResourceMetering is ResourceMetering, StdUtils {
uint256 cachedPrevBoughtGas = uint256(params.prevBoughtGas);
uint256 cachedPrevBlockNum = uint256(params.prevBlockNum);
SystemConfig.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
if (cachedPrevBaseFee < uint256(MINIMUM_BASE_FEE)) {
if (cachedPrevBaseFee < uint256(rcfg.minimumBaseFee)) {
failedNeverBelowMinBaseFee = true;
}
// 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;
}
......@@ -51,11 +67,11 @@ contract EchidnaFuzzResourceMetering is ResourceMetering, StdUtils {
if (_raiseBaseFee) {
gasToBurn = bound(
_gasToBurn,
uint256(TARGET_RESOURCE_LIMIT),
uint256(MAX_RESOURCE_LIMIT)
uint256(targetResourceLimit),
uint256(rcfg.maxResourceLimit)
);
} else {
gasToBurn = bound(_gasToBurn, 0, uint256(TARGET_RESOURCE_LIMIT));
gasToBurn = bound(_gasToBurn, 0, targetResourceLimit);
}
_burnInternal(uint64(gasToBurn));
......@@ -63,13 +79,13 @@ contract EchidnaFuzzResourceMetering is ResourceMetering, StdUtils {
// Part 3: we run checks and modify our invariant flags based on the updated params values
// 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
// empty blocks in between), ensure this block's baseFee increased, but not by
// more than the max amount per block
if (
(cachedPrevBoughtGas > uint256(TARGET_RESOURCE_LIMIT)) &&
(cachedPrevBoughtGas > uint256(targetResourceLimit)) &&
(uint256(params.prevBlockNum) - cachedPrevBlockNum == 1)
) {
failedRaiseBaseFee = failedRaiseBaseFee || (params.prevBaseFee <= cachedPrevBaseFee);
......@@ -81,7 +97,7 @@ contract EchidnaFuzzResourceMetering is ResourceMetering, StdUtils {
// 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
if (
(cachedPrevBoughtGas < uint256(TARGET_RESOURCE_LIMIT)) ||
(cachedPrevBoughtGas < uint256(targetResourceLimit)) ||
(uint256(params.prevBlockNum) - cachedPrevBlockNum > 1)
) {
// Invariant: baseFee should decrease
......@@ -104,11 +120,11 @@ contract EchidnaFuzzResourceMetering is ResourceMetering, StdUtils {
Arithmetic.clamp(
Arithmetic.cdexp(
int256(cachedPrevBaseFee),
BASE_FEE_MAX_CHANGE_DENOMINATOR,
int256(uint256(rcfg.baseFeeMaxChangeDenominator)),
int256(uint256(params.prevBlockNum) - cachedPrevBlockNum)
),
MINIMUM_BASE_FEE,
MAXIMUM_BASE_FEE
int256(uint256(rcfg.minimumBaseFee)),
int256(uint256(rcfg.maximumBaseFee))
)
);
}
......
......@@ -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()
// function.
contract GasBenchMark_OptimismPortal is Portal_Initializer {
uint128 internal INITIAL_BASE_FEE;
// Reusable default values for a test withdrawal
Types.WithdrawalTransaction _defaultTx;
......@@ -86,8 +84,6 @@ contract GasBenchMark_OptimismPortal is Portal_Initializer {
1
);
INITIAL_BASE_FEE = op.INITIAL_BASE_FEE();
// Fund the portal so that we can withdraw ETH.
vm.deal(address(op), 0xFFFFFFFF);
}
......@@ -103,7 +99,7 @@ contract GasBenchMark_OptimismPortal is Portal_Initializer {
}
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 }(
NON_ZERO_ADDRESS,
ZERO_VALUE,
......@@ -124,16 +120,9 @@ contract GasBenchMark_OptimismPortal is Portal_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 {
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.
bytes
memory data = hex"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff";
......@@ -153,11 +142,8 @@ contract GasBenchMark_L1CrossDomainMessenger is Messenger_Initializer {
}
contract GasBenchMark_L1StandardBridge_Deposit is Bridge_Initializer {
uint128 internal INITIAL_BASE_FEE;
function setUp() public virtual override {
super.setUp();
INITIAL_BASE_FEE = op.INITIAL_BASE_FEE();
deal(address(L1Token), alice, 100000, true);
vm.startPrank(alice, alice);
L1Token.approve(address(L1Bridge), type(uint256).max);
......@@ -165,7 +151,7 @@ contract GasBenchMark_L1StandardBridge_Deposit is Bridge_Initializer {
function test_depositETH_benchmark_0() external {
vm.pauseGasMetering();
setPrevBaseFee(vm, address(op), INITIAL_BASE_FEE);
setPrevBaseFee(vm, address(op), 1 gwei);
vm.resumeGasMetering();
L1Bridge.depositETH{ value: 500 }(50000, hex"");
}
......@@ -179,7 +165,7 @@ contract GasBenchMark_L1StandardBridge_Deposit is Bridge_Initializer {
function test_depositERC20_benchmark_0() external {
vm.pauseGasMetering();
setPrevBaseFee(vm, address(op), INITIAL_BASE_FEE);
setPrevBaseFee(vm, address(op), 1 gwei);
vm.resumeGasMetering();
L1Bridge.bridgeERC20({
_localToken: address(L1Token),
......
......@@ -28,6 +28,7 @@ import { L1ChugSplashProxy } from "../legacy/L1ChugSplashProxy.sol";
import { IL1ChugSplashDeployer } from "../legacy/L1ChugSplashProxy.sol";
import { Strings } from "@openzeppelin/contracts/utils/Strings.sol";
import { LegacyMintableERC20 } from "../legacy/LegacyMintableERC20.sol";
import { SystemConfig } from "../L1/SystemConfig.sol";
contract CommonTest is Test {
address alice = address(128);
......@@ -158,6 +159,7 @@ contract Portal_Initializer is L2OutputOracle_Initializer {
// Test target
OptimismPortal internal opImpl;
OptimismPortal internal op;
SystemConfig systemConfig;
event WithdrawalFinalized(bytes32 indexed withdrawalHash, bool success);
event WithdrawalProven(
......@@ -169,7 +171,22 @@ contract Portal_Initializer is L2OutputOracle_Initializer {
function setUp() public virtual override {
super.setUp();
opImpl = new OptimismPortal({ _l2Oracle: oracle, _guardian: guardian, _paused: true });
systemConfig = new SystemConfig({
_owner: address(0),
_overhead: 0,
_scalar: 10000,
_batcherHash: bytes32(0),
_gasLimit: 30_000_000,
_unsafeBlockSigner: address(0)
});
opImpl = new OptimismPortal({
_l2Oracle: oracle,
_guardian: guardian,
_paused: true,
_config: systemConfig
});
Proxy proxy = new Proxy(multisig);
vm.prank(multisig);
proxy.upgradeToAndCall(
......
......@@ -9,6 +9,7 @@ import { OptimismPortal } from "../L1/OptimismPortal.sol";
import { Types } from "../libraries/Types.sol";
import { Hashing } from "../libraries/Hashing.sol";
import { Proxy } from "../universal/Proxy.sol";
import { SystemConfig } from "../L1/SystemConfig.sol";
contract OptimismPortal_Test is Portal_Initializer {
event Paused(address);
......@@ -1045,10 +1046,12 @@ contract OptimismPortalUpgradeable_Test is Portal_Initializer {
}
function test_params_initValuesOnProxy_succeeds() external {
(uint128 prevBaseFee, uint64 prevBoughtGas, uint64 prevBlockNum) = OptimismPortal(
payable(address(proxy))
).params();
assertEq(prevBaseFee, opImpl.INITIAL_BASE_FEE());
OptimismPortal p = OptimismPortal(payable(address(proxy)));
(uint128 prevBaseFee, uint64 prevBoughtGas, uint64 prevBlockNum) = p.params();
SystemConfig.ResourceConfig memory rcfg = p.resourceConfig();
assertEq(prevBaseFee, rcfg.minimumBaseFee);
assertEq(prevBoughtGas, 0);
assertEq(prevBlockNum, initialBlockNum);
}
......
......@@ -3,6 +3,7 @@ pragma solidity 0.8.15;
import { Test } from "forge-std/Test.sol";
import { ResourceMetering } from "../L1/ResourceMetering.sol";
import { SystemConfig } from "../L1/SystemConfig.sol";
import { Proxy } from "../universal/Proxy.sol";
contract MeterUser is ResourceMetering {
......@@ -14,6 +15,18 @@ contract MeterUser is ResourceMetering {
__ResourceMetering_init();
}
function resourceConfig() public override pure returns (SystemConfig.ResourceConfig memory) {
SystemConfig.ResourceConfig memory config = SystemConfig.ResourceConfig({
maxResourceLimit: 20_000_000,
elasticityMultiplier: 10,
baseFeeMaxChangeDenominator: 8,
minimumBaseFee: 1 gwei,
systemTxMaxGas: 1_000_000,
maximumBaseFee: type(uint128).max
});
return config;
}
function use(uint64 _amount) public metered(_amount) {}
function set(
......@@ -29,6 +42,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 {
MeterUser internal meter;
uint64 initialBlockNum;
......@@ -38,42 +56,15 @@ contract ResourceMetering_Test is Test {
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 {
(uint128 prevBaseFee, uint64 prevBoughtGas, uint64 prevBlockNum) = meter.params();
SystemConfig.ResourceConfig memory rcfg = meter.resourceConfig();
assertEq(prevBaseFee, meter.INITIAL_BASE_FEE());
assertEq(prevBaseFee, rcfg.minimumBaseFee);
assertEq(prevBoughtGas, 0);
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 {
meter.use(0); // equivalent to just updating the base fee and block number
(uint128 prevBaseFee, uint64 prevBoughtGas, uint64 prevBlockNum) = meter.params();
......@@ -116,8 +107,9 @@ contract ResourceMetering_Test is Test {
}
function test_meter_updateNoGasDelta_succeeds() external {
uint64 target = uint64(uint256(meter.TARGET_RESOURCE_LIMIT()));
meter.use(target);
SystemConfig.ResourceConfig memory rcfg = meter.resourceConfig();
uint256 target = uint256(rcfg.maxResourceLimit) / uint256(rcfg.elasticityMultiplier);
meter.use(uint64(target));
(uint128 prevBaseFee, uint64 prevBoughtGas, uint64 prevBlockNum) = meter.params();
assertEq(prevBaseFee, 1000000000);
......@@ -126,12 +118,14 @@ contract ResourceMetering_Test is Test {
}
function test_meter_useMax_succeeds() external {
uint64 target = uint64(uint256(meter.TARGET_RESOURCE_LIMIT()));
uint64 elasticity = uint64(uint256(meter.ELASTICITY_MULTIPLIER()));
meter.use(target * elasticity);
SystemConfig.ResourceConfig memory rcfg = meter.resourceConfig();
uint64 target = uint64(rcfg.maxResourceLimit) / uint64(rcfg.elasticityMultiplier);
uint64 elasticityMultiplier = uint64(rcfg.elasticityMultiplier);
meter.use(target * elasticityMultiplier);
(, uint64 prevBoughtGas, ) = meter.params();
assertEq(prevBoughtGas, target * elasticity);
assertEq(prevBoughtGas, target * elasticityMultiplier);
vm.roll(initialBlockNum + 1);
meter.use(0);
......@@ -140,10 +134,12 @@ contract ResourceMetering_Test is Test {
}
function test_meter_useMoreThanMax_reverts() external {
uint64 target = uint64(uint256(meter.TARGET_RESOURCE_LIMIT()));
uint64 elasticity = uint64(uint256(meter.ELASTICITY_MULTIPLIER()));
SystemConfig.ResourceConfig memory rcfg = meter.resourceConfig();
uint64 target = uint64(rcfg.maxResourceLimit) / uint64(rcfg.elasticityMultiplier);
uint64 elasticityMultiplier = uint64(rcfg.elasticityMultiplier);
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
......@@ -153,9 +149,11 @@ contract ResourceMetering_Test is Test {
// At 12 seconds per block, this number is effectively unreachable.
vm.assume(_blockDiff < 433576281058164217753225238677900874458691);
uint64 target = uint64(uint256(meter.TARGET_RESOURCE_LIMIT()));
uint64 elasticity = uint64(uint256(meter.ELASTICITY_MULTIPLIER()));
vm.assume(_amount < target * elasticity);
SystemConfig.ResourceConfig memory rcfg = meter.resourceConfig();
uint64 target = uint64(rcfg.maxResourceLimit) / uint64(rcfg.elasticityMultiplier);
uint64 elasticityMultiplier = uint64(rcfg.elasticityMultiplier);
vm.assume(_amount < target * elasticityMultiplier);
vm.roll(initialBlockNum + _blockDiff);
meter.use(_amount);
}
......@@ -182,6 +180,18 @@ contract CustomMeterUser is ResourceMetering {
});
}
function resourceConfig() public override pure returns (SystemConfig.ResourceConfig memory) {
SystemConfig.ResourceConfig memory config = SystemConfig.ResourceConfig({
maxResourceLimit: 20_000_000,
elasticityMultiplier: 10,
baseFeeMaxChangeDenominator: 8,
minimumBaseFee: 1 gwei,
systemTxMaxGas: 1_000_000,
maximumBaseFee: type(uint128).max
});
return config;
}
function use(uint64 _amount) public returns (uint256) {
uint256 initialGas = gasleft();
_metered(_amount, initialGas);
......@@ -224,10 +234,11 @@ contract ArtifactResourceMetering_Test is Test {
vm.roll(1_000_000);
MeterUser base = new MeterUser();
minimumBaseFee = uint128(uint256(base.MINIMUM_BASE_FEE()));
maximumBaseFee = uint128(uint256(base.MAXIMUM_BASE_FEE()));
maxResourceLimit = uint64(uint256(base.MAX_RESOURCE_LIMIT()));
targetResourceLimit = uint64(uint256(base.TARGET_RESOURCE_LIMIT()));
SystemConfig.ResourceConfig memory rcfg = base.resourceConfig();
minimumBaseFee = uint128(rcfg.minimumBaseFee);
maximumBaseFee = rcfg.maximumBaseFee;
maxResourceLimit = uint64(rcfg.maxResourceLimit);
targetResourceLimit = uint64(rcfg.maxResourceLimit) / uint64(rcfg.elasticityMultiplier);
outfile = string.concat(vm.projectRoot(), "/.resource-metering.csv");
try vm.removeFile(outfile) {} catch {}
......
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