Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions scripts/deploy/DeployConfig.s.sol
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ contract DeployConfig is Script {

uint32 public basefeeScalar;
uint32 public blobbasefeeScalar;
uint32 public resourceConfigMinimumBaseFee;

string public saltMixer;

Expand Down Expand Up @@ -98,6 +99,7 @@ contract DeployConfig is Script {

basefeeScalar = uint32(_json.readUint("$.gasPriceOracleBaseFeeScalar"));
blobbasefeeScalar = uint32(_json.readUint("$.gasPriceOracleBlobBaseFeeScalar"));
resourceConfigMinimumBaseFee = uint32(_json.readUintOr("$.resourceConfigMinimumBaseFee", 1 gwei));

baseFeeVaultMinimumWithdrawalAmount = _json.readUint("$.baseFeeVaultMinimumWithdrawalAmount");
baseFeeVaultWithdrawalNetwork = _json.readUint("$.baseFeeVaultWithdrawalNetwork");
Expand Down
11 changes: 9 additions & 2 deletions scripts/deploy/SystemDeploy.s.sol
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import { IOptimismPortal2 as IOptimismPortal } from "interfaces/L1/IOptimismPort
import { IProtocolVersions } from "interfaces/L1/IProtocolVersions.sol";
import { ISuperchainConfig } from "interfaces/L1/ISuperchainConfig.sol";
import { ISystemConfig } from "interfaces/L1/ISystemConfig.sol";
import { IResourceMetering } from "interfaces/L1/IResourceMetering.sol";
import { IAddressManager } from "interfaces/legacy/IAddressManager.sol";
import { IL1ChugSplashProxy } from "interfaces/legacy/IL1ChugSplashProxy.sol";
import { IResolvedDelegateProxy } from "interfaces/legacy/IResolvedDelegateProxy.sol";
Expand Down Expand Up @@ -395,7 +396,8 @@ contract SystemDeploy is Script {
root: Hash.wrap(cfg.multiproofGenesisOutputRoot()), l2SequenceNumber: cfg.multiproofGenesisBlockNumber()
}),
saltMixer: cfg.saltMixer(),
gasLimit: uint64(cfg.l2GenesisBlockGasLimit())
gasLimit: uint64(cfg.l2GenesisBlockGasLimit()),
resourceConfigMinimumBaseFee: cfg.resourceConfigMinimumBaseFee()
});
}

Expand Down Expand Up @@ -842,6 +844,11 @@ contract SystemDeploy is Script {
delayedWETH: address(_output.delayedWETHProxy)
});

uint32 minimumBaseFee =
_input.resourceConfigMinimumBaseFee == 0 ? uint32(1 gwei) : _input.resourceConfigMinimumBaseFee;
IResourceMetering.ResourceConfig memory resourceConfig =
Constants.resourceConfigWithMinimumBaseFee(minimumBaseFee);

return abi.encodeCall(
ISystemConfig.initialize,
(
Expand All @@ -851,7 +858,7 @@ contract SystemDeploy is Script {
bytes32(uint256(uint160(_input.roles.batcher))),
_input.gasLimit,
_input.roles.unsafeBlockSigner,
Constants.DEFAULT_RESOURCE_CONFIG(),
resourceConfig,
Types.chainIdToBatchInboxAddress(_input.l2ChainId),
opChainAddrs,
_input.l2ChainId,
Expand Down
1 change: 1 addition & 0 deletions scripts/libraries/Types.sol
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ library Types {
Proposal startingAnchorRoot;
string saltMixer;
uint64 gasLimit;
uint32 resourceConfigMinimumBaseFee;
}

/// @notice The full set of outputs from deploying a new OP Stack chain.
Expand Down
2 changes: 1 addition & 1 deletion snapshots/semver-lock.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
"sourceCodeHash": "0xd9f576a79e97bc541b3d7a2ee928f34223edaaecb074eeb9e6e2ecee857ce6a0"
},
"src/L1/OptimismPortal2.sol:OptimismPortal2": {
"initCodeHash": "0xdaeac3fae27dc1c5924100d06eb337c60010d88b638d6703c5ec25d750810495",
"initCodeHash": "0x3223d48d63cc9e4a6796f2eb343c82c88c1add5289fa66ab72fa2fa4d83d0790",
"sourceCodeHash": "0x15cef97e2598ac2ed83fd8662c2f31e61a33e89d9f618b97fdeaa142cf6f9262"
},
"src/L1/ProtocolVersions.sol:ProtocolVersions": {
Expand Down
5 changes: 4 additions & 1 deletion src/L1/ResourceMetering.sol
Original file line number Diff line number Diff line change
Expand Up @@ -167,7 +167,10 @@ abstract contract ResourceMetering is Initializable {
/// child contract.
function __ResourceMetering_init() internal onlyInitializing {
if (params.prevBlockNum == 0) {
params = ResourceParams({ prevBaseFee: 1 gwei, prevBoughtGas: 0, prevBlockNum: uint64(block.number) });
ResourceConfig memory config = _resourceConfig();
params = ResourceParams({
prevBaseFee: config.minimumBaseFee, prevBoughtGas: 0, prevBlockNum: uint64(block.number)
});
}
}
}
11 changes: 10 additions & 1 deletion src/libraries/Constants.sol
Original file line number Diff line number Diff line change
Expand Up @@ -45,11 +45,20 @@ library Constants {
/// @notice Returns the default values for the ResourceConfig. These are the recommended values
/// for a production network.
function DEFAULT_RESOURCE_CONFIG() internal pure returns (IResourceMetering.ResourceConfig memory) {
return resourceConfigWithMinimumBaseFee(1 gwei);
}

/// @notice Returns the default resource config with a custom minimum base fee.
function resourceConfigWithMinimumBaseFee(uint32 _minimumBaseFee)
internal
pure
returns (IResourceMetering.ResourceConfig memory)
{
IResourceMetering.ResourceConfig memory config = IResourceMetering.ResourceConfig({
maxResourceLimit: 20_000_000,
elasticityMultiplier: 10,
baseFeeMaxChangeDenominator: 8,
minimumBaseFee: 1 gwei,
minimumBaseFee: _minimumBaseFee,
systemTxMaxGas: 1_000_000,
maximumBaseFee: type(uint128).max
});
Expand Down
8 changes: 7 additions & 1 deletion test/L1/ResourceMetering.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,8 @@ contract MeterUser is ResourceMetering {
ResourceMetering.ResourceConfig public innerConfig;

constructor() {
initialize();
innerConfig = defaultResourceConfig();
initialize();
}

function initialize() public initializer {
Expand Down Expand Up @@ -87,6 +87,12 @@ contract ResourceMetering_Metered_Test is ResourceMetering_TestInit {
assertEq(postBlockNum, prevBlockNum);
}

/// @notice Tests that initialization sets prevBaseFee to the configured minimum base fee.
function test_initialize_prevBaseFee_matchesMinimumBaseFee_succeeds() external view {
(uint128 prevBaseFee,,) = meter.params();
assertEq(prevBaseFee, meter.resourceConfig().minimumBaseFee);
}

/// @notice Tests that updating after multiple empty blocks maintains correct base fee.
function testFuzz_metered_emptyBlocks_succeeds(uint256 _blockDiff) external {
_blockDiff = bound(_blockDiff, 1, 100);
Expand Down
66 changes: 66 additions & 0 deletions test/deploy/DeployConfig.t.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.15;

import { Test } from "lib/forge-std/src/Test.sol";

import { DeployConfig } from "scripts/deploy/DeployConfig.s.sol";

/// @title DeployConfig_ResourceConfigMinimumBaseFee_Test
/// @notice Covers DeployConfig JSON parsing for the optional resource minimum base fee.
contract DeployConfig_ResourceConfigMinimumBaseFee_Test is Test {
DeployConfig internal config;
string internal localConfigPath;
string internal overrideConfigPath;

function setUp() public {
config = new DeployConfig();
localConfigPath = string.concat(vm.projectRoot(), "/deploy-config/local.json");
overrideConfigPath =
string.concat(vm.projectRoot(), "/deployments/deploy-config-resource-minimum-override.json");
vm.createDir(string.concat(vm.projectRoot(), "/deployments"), true);
}

function test_read_resourceConfigMinimumBaseFee_default_succeeds() public {
config.read(localConfigPath);
assertEq(config.resourceConfigMinimumBaseFee(), uint32(1 gwei));
}

function test_read_resourceConfigMinimumBaseFee_override_succeeds() public {
string memory json = _withResourceConfigMinimumBaseFee(vm.readFile(localConfigPath), 10_000_000);
vm.writeFile(overrideConfigPath, json);

config.read(overrideConfigPath);
assertEq(config.resourceConfigMinimumBaseFee(), 10_000_000);

vm.removeFile(overrideConfigPath);
}

function _withResourceConfigMinimumBaseFee(
string memory _json,
uint256 _value
)
internal
pure
returns (string memory json_)
{
require(_value == 10_000_000, "DeployConfig test: unsupported fixture value");

bytes memory jsonBytes = bytes(_json);
uint256 end = jsonBytes.length;
while (end > 0 && (jsonBytes[end - 1] == "\n" || jsonBytes[end - 1] == "\r" || jsonBytes[end - 1] == " ")) {
end--;
}
require(end > 0 && jsonBytes[end - 1] == "}", "DeployConfig test: expected object");

bytes memory suffix = bytes(',\n "resourceConfigMinimumBaseFee": 10000000\n}');
json_ = new string(end - 1 + suffix.length);

bytes memory result = bytes(json_);
for (uint256 i; i < end - 1; ++i) {
result[i] = jsonBytes[i];
}
for (uint256 i; i < suffix.length; ++i) {
result[end - 1 + i] = suffix[i];
}
}
}
149 changes: 149 additions & 0 deletions test/deploy/ResourceConfigMinimumBaseFee.t.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.15;

import { Test } from "lib/forge-std/src/Test.sol";

import { SystemDeploy } from "scripts/deploy/SystemDeploy.s.sol";
import { Types } from "scripts/libraries/Types.sol";
import { Constants } from "src/libraries/Constants.sol";
import { Hash, Proposal } from "src/libraries/bridge/Types.sol";

import { IL1StandardBridge } from "interfaces/L1/IL1StandardBridge.sol";
import { IOptimismPortal2 } from "interfaces/L1/IOptimismPortal2.sol";
import { IResourceMetering } from "interfaces/L1/IResourceMetering.sol";
import { ISystemConfig } from "interfaces/L1/ISystemConfig.sol";
import { ISP1Verifier } from "interfaces/L1/proofs/zk/ISP1Verifier.sol";

/// @title ResourceConfigMinimumBaseFee_Test
/// @notice Tests deploy-time configuration of the deposit resource minimum base fee.
contract ResourceConfigMinimumBaseFee_Test is Test {
SystemDeploy internal systemDeploy;

address internal owner = address(this);
address internal guardian = makeAddr("guardian");
address internal incidentResponder = makeAddr("incidentResponder");
address internal batcher = makeAddr("batcher");
address internal unsafeBlockSigner = makeAddr("unsafeBlockSigner");
address internal proposer = makeAddr("proposer");
address internal challenger = makeAddr("challenger");

uint256 internal l2ChainId = 901;
uint32 internal constant L3_MINIMUM_BASE_FEE = 10_000_000;
/// @dev The Fusaka EIP-7825 transaction gas cap. Chains may not enable it and future forks may change it.
uint256 internal constant BASE_TX_GAS_CAP = 16_777_216;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

could we reference Fusaka here in a doc comment? some chains don't implement the newer tx gas limit, and a future hard fork alters this value


function setUp() public {
systemDeploy = new SystemDeploy();
}

function test_deploy_defaultResourceConfigMinimumBaseFee_succeeds() public {
SystemDeploy.DeployOutput memory output = systemDeploy.deploy(_deployInput(uint32(1 gwei)));

IResourceMetering.ResourceConfig memory config = output.opChain.systemConfigProxy.resourceConfig();
assertEq(config.minimumBaseFee, uint32(1 gwei));
assertEq(config.maximumBaseFee, Constants.DEFAULT_RESOURCE_CONFIG().maximumBaseFee);

(uint128 prevBaseFee,,) = IOptimismPortal2(payable(address(output.opChain.optimismPortalProxy))).params();
assertEq(prevBaseFee, config.minimumBaseFee);
}

function test_deploy_overriddenResourceConfigMinimumBaseFee_succeeds() public {
SystemDeploy.DeployOutput memory output = systemDeploy.deploy(_deployInput(L3_MINIMUM_BASE_FEE));

IResourceMetering.ResourceConfig memory config = output.opChain.systemConfigProxy.resourceConfig();
assertEq(config.minimumBaseFee, L3_MINIMUM_BASE_FEE);
assertEq(config.maximumBaseFee, Constants.DEFAULT_RESOURCE_CONFIG().maximumBaseFee);

(uint128 prevBaseFee,,) = IOptimismPortal2(payable(address(output.opChain.optimismPortalProxy))).params();
assertEq(prevBaseFee, L3_MINIMUM_BASE_FEE);
}

function test_depositETH_lowParentBaseFee_belowTxGasCap_succeeds() public {
SystemDeploy.DeployOutput memory output = systemDeploy.deploy(_deployInput(L3_MINIMUM_BASE_FEE));

ISystemConfig systemConfig = output.opChain.systemConfigProxy;
IOptimismPortal2 optimismPortal = IOptimismPortal2(payable(address(output.opChain.optimismPortalProxy)));
IL1StandardBridge l1StandardBridge = IL1StandardBridge(payable(systemConfig.l1StandardBridge()));

vm.fee(L3_MINIMUM_BASE_FEE);

uint256 gasUsed = _depositETHAndMeasureGas(l1StandardBridge);
assertLt(gasUsed, BASE_TX_GAS_CAP);

(uint128 prevBaseFee, uint64 prevBoughtGas,) = optimismPortal.params();
assertEq(prevBaseFee, L3_MINIMUM_BASE_FEE);
assertGt(prevBoughtGas, 0);
}

function test_depositETH_defaultMinimum_lowParentBaseFee_exceedsFusakaTxGasCap_succeeds() public {
SystemDeploy.DeployOutput memory output = systemDeploy.deploy(_deployInput(uint32(1 gwei)));
IL1StandardBridge l1StandardBridge =
IL1StandardBridge(payable(output.opChain.systemConfigProxy.l1StandardBridge()));

vm.fee(L3_MINIMUM_BASE_FEE);

uint256 gasUsed = _depositETHAndMeasureGas(l1StandardBridge);
assertGe(gasUsed, BASE_TX_GAS_CAP);
}

function _depositETHAndMeasureGas(IL1StandardBridge _l1StandardBridge) internal returns (uint256 gasUsed_) {
uint256 depositorKey = 0xBEEF;
address depositor = vm.addr(depositorKey);
vm.deal(depositor, 10 ether);

vm.startBroadcast(depositorKey);
uint256 gasBefore = gasleft();
_l1StandardBridge.depositETH{ value: 1 ether }(200_000, hex"");
gasUsed_ = gasBefore - gasleft();
vm.stopBroadcast();
}

function _deployInput(uint32 _resourceConfigMinimumBaseFee)
internal
view
returns (SystemDeploy.DeployInput memory input_)
{
input_.saveArtifacts = false;
input_.superchainInput = SystemDeploy.SuperchainInput({
guardian: guardian, incidentResponder: incidentResponder, superchainProxyAdminOwner: owner
});
input_.implementationsInput = SystemDeploy.ImplementationInput({
withdrawalDelaySeconds: 100,
proofMaturityDelaySeconds: 400,
disputeGameFinalityDelaySeconds: 500,
teeImageHash: bytes32(uint256(1)),
zkRangeHash: bytes32(uint256(2)),
zkAggregationHash: bytes32(uint256(3)),
multiproofConfigHash: bytes32(uint256(4)),
multiproofGameType: 621,
nitroEnclaveVerifier: address(0),
multiproofBlockInterval: 100,
multiproofIntermediateBlockInterval: 10,
multiproofMaxUpgradeId: 12,
sp1Verifier: ISP1Verifier(address(0)),
teeProposer: proposer,
teeChallenger: challenger,
devTeeSigner: address(0),
guardian: guardian,
incidentResponder: incidentResponder,
slowFinalizationDelay: 5 days,
fastFinalizationDelay: 1 days
});
input_.opChainInput = Types.DeployInput({
roles: Types.Roles({
opChainProxyAdminOwner: owner,
systemConfigOwner: owner,
batcher: batcher,
unsafeBlockSigner: unsafeBlockSigner,
incidentResponder: incidentResponder
}),
basefeeScalar: 100,
blobBasefeeScalar: 200,
l2ChainId: l2ChainId,
startingAnchorRoot: Proposal({ root: Hash.wrap(bytes32(uint256(1))), l2SequenceNumber: 0 }),
saltMixer: "resource-config-minimum-base-fee-test",
gasLimit: 60_000_000,
resourceConfigMinimumBaseFee: _resourceConfigMinimumBaseFee
});
}
}
6 changes: 4 additions & 2 deletions test/deploy/SystemDeploy.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -427,7 +427,8 @@ contract SystemDeploy_Test is Test, SystemDeployAssertions {
l2ChainId: l2ChainId,
startingAnchorRoot: Proposal({ root: Hash.wrap(bytes32(uint256(1))), l2SequenceNumber: 0 }),
saltMixer: "system-deploy-test",
gasLimit: 60_000_000
gasLimit: 60_000_000,
resourceConfigMinimumBaseFee: uint32(1 gwei)
});
}

Expand Down Expand Up @@ -765,7 +766,8 @@ contract ZKBricking_Test is Test {
l2ChainId: l2ChainId,
startingAnchorRoot: Proposal({ root: Hash.wrap(bytes32(uint256(1))), l2SequenceNumber: 0 }),
saltMixer: "zk-bricking-test",
gasLimit: 60_000_000
gasLimit: 60_000_000,
resourceConfigMinimumBaseFee: uint32(1 gwei)
});
}
}
Loading