Commit f3b9f0c9 authored by Michael Amadi's avatar Michael Amadi Committed by GitHub

Remove L2OutputOracle and legacy OptimismPortal (#13489)

* attempt...

* fixes

* fix checks

* fix kontrol build

* update DeploymentSummaryFaultProofs kontrol hash

* fixes

* undo unnecessary change

* feat: Reduce diff and usage of OptimismPortal2

* semver

* fix: init test

---------
Co-authored-by: default avatarMaurelian <maurelian@protonmail.ch>
parent d356d92a
......@@ -3,7 +3,7 @@ pragma solidity ^0.8.0;
import { ICrossDomainMessenger } from "interfaces/universal/ICrossDomainMessenger.sol";
import { ISuperchainConfig } from "interfaces/L1/ISuperchainConfig.sol";
import { IOptimismPortal } from "interfaces/L1/IOptimismPortal.sol";
import { IOptimismPortal2 as IOptimismPortal } from "interfaces/L1/IOptimismPortal2.sol";
import { ISystemConfig } from "interfaces/L1/ISystemConfig.sol";
interface IL1CrossDomainMessenger is ICrossDomainMessenger {
......
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import { Types } from "src/libraries/Types.sol";
interface IL2OutputOracle {
event Initialized(uint8 version);
event OutputProposed(
bytes32 indexed outputRoot, uint256 indexed l2OutputIndex, uint256 indexed l2BlockNumber, uint256 l1Timestamp
);
event OutputsDeleted(uint256 indexed prevNextOutputIndex, uint256 indexed newNextOutputIndex);
function CHALLENGER() external view returns (address);
function FINALIZATION_PERIOD_SECONDS() external view returns (uint256);
function L2_BLOCK_TIME() external view returns (uint256);
function PROPOSER() external view returns (address);
function SUBMISSION_INTERVAL() external view returns (uint256);
function challenger() external view returns (address);
function computeL2Timestamp(uint256 _l2BlockNumber) external view returns (uint256);
function deleteL2Outputs(uint256 _l2OutputIndex) external;
function finalizationPeriodSeconds() external view returns (uint256);
function getL2Output(uint256 _l2OutputIndex) external view returns (Types.OutputProposal memory);
function getL2OutputAfter(uint256 _l2BlockNumber) external view returns (Types.OutputProposal memory);
function getL2OutputIndexAfter(uint256 _l2BlockNumber) external view returns (uint256);
function initialize(
uint256 _submissionInterval,
uint256 _l2BlockTime,
uint256 _startingBlockNumber,
uint256 _startingTimestamp,
address _proposer,
address _challenger,
uint256 _finalizationPeriodSeconds
)
external;
function l2BlockTime() external view returns (uint256);
function latestBlockNumber() external view returns (uint256);
function latestOutputIndex() external view returns (uint256);
function nextBlockNumber() external view returns (uint256);
function nextOutputIndex() external view returns (uint256);
function proposeL2Output(
bytes32 _outputRoot,
uint256 _l2BlockNumber,
bytes32 _l1BlockHash,
uint256 _l1BlockNumber
)
external
payable;
function proposer() external view returns (address);
function startingBlockNumber() external view returns (uint256);
function startingTimestamp() external view returns (uint256);
function submissionInterval() external view returns (uint256);
function version() external view returns (string memory);
function __constructor__() external;
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import { Types } from "src/libraries/Types.sol";
import { ISystemConfig } from "interfaces/L1/ISystemConfig.sol";
import { ISuperchainConfig } from "interfaces/L1/ISuperchainConfig.sol";
import { IL2OutputOracle } from "interfaces/L1/IL2OutputOracle.sol";
interface IOptimismPortal {
error BadTarget();
error CallPaused();
error ContentLengthMismatch();
error EmptyItem();
error GasEstimation();
error InvalidDataRemainder();
error InvalidHeader();
error LargeCalldata();
error NoValue();
error NonReentrant();
error OnlyCustomGasToken();
error OutOfGas();
error SmallGasLimit();
error TransferFailed();
error Unauthorized();
error UnexpectedList();
error UnexpectedString();
event Initialized(uint8 version);
event TransactionDeposited(address indexed from, address indexed to, uint256 indexed version, bytes opaqueData);
event WithdrawalFinalized(bytes32 indexed withdrawalHash, bool success);
event WithdrawalProven(bytes32 indexed withdrawalHash, address indexed from, address indexed to);
receive() external payable;
function balance() external view returns (uint256);
function depositERC20Transaction(
address _to,
uint256 _mint,
uint256 _value,
uint64 _gasLimit,
bool _isCreation,
bytes memory _data
)
external;
function depositTransaction(
address _to,
uint256 _value,
uint64 _gasLimit,
bool _isCreation,
bytes memory _data
)
external
payable;
function donateETH() external payable;
function finalizeWithdrawalTransaction(Types.WithdrawalTransaction memory _tx) external;
function finalizedWithdrawals(bytes32) external view returns (bool);
function guardian() external view returns (address);
function initialize(
IL2OutputOracle _l2Oracle,
ISystemConfig _systemConfig,
ISuperchainConfig _superchainConfig
)
external;
function isOutputFinalized(uint256 _l2OutputIndex) external view returns (bool);
function l2Oracle() external view returns (IL2OutputOracle);
function l2Sender() external view returns (address);
function minimumGasLimit(uint64 _byteCount) external pure returns (uint64);
function params() external view returns (uint128 prevBaseFee, uint64 prevBoughtGas, uint64 prevBlockNum); // nosemgrep
function paused() external view returns (bool paused_);
function proveWithdrawalTransaction(
Types.WithdrawalTransaction memory _tx,
uint256 _l2OutputIndex,
Types.OutputRootProof memory _outputRootProof,
bytes[] memory _withdrawalProof
)
external;
function provenWithdrawals(bytes32)
external
view
returns (bytes32 outputRoot, uint128 timestamp, uint128 l2OutputIndex); // nosemgrep
function setGasPayingToken(address _token, uint8 _decimals, bytes32 _name, bytes32 _symbol) external;
function superchainConfig() external view returns (ISuperchainConfig);
function systemConfig() external view returns (ISystemConfig);
function version() external pure returns (string memory);
function __constructor__() external;
}
......@@ -23,10 +23,8 @@ import { OPContractsManager } from "src/L1/OPContractsManager.sol";
// Interfaces
import { IResourceMetering } from "interfaces/L1/IResourceMetering.sol";
import { ISystemConfig } from "interfaces/L1/ISystemConfig.sol";
import { IL2OutputOracle } from "interfaces/L1/IL2OutputOracle.sol";
import { ISuperchainConfig } from "interfaces/L1/ISuperchainConfig.sol";
import { IL1CrossDomainMessenger } from "interfaces/L1/IL1CrossDomainMessenger.sol";
import { IOptimismPortal } from "interfaces/L1/IOptimismPortal.sol";
import { IOptimismPortal2 } from "interfaces/L1/IOptimismPortal2.sol";
import { IL1ERC721Bridge } from "interfaces/L1/IL1ERC721Bridge.sol";
import { IL1StandardBridge } from "interfaces/L1/IL1StandardBridge.sol";
......@@ -42,15 +40,7 @@ library ChainAssertions {
/// @notice Asserts the correctness of an L1 deployment. This function expects that all contracts
/// within the `prox` ContractSet are proxies that have been setup and initialized.
function postDeployAssertions(
Types.ContractSet memory _prox,
DeployConfig _cfg,
uint256 _l2OutputOracleStartingTimestamp,
Vm _vm
)
internal
view
{
function postDeployAssertions(Types.ContractSet memory _prox, DeployConfig _cfg, Vm _vm) internal view {
console.log("Running post-deploy assertions");
IResourceMetering.ResourceConfig memory rcfg = ISystemConfig(_prox.SystemConfig).resourceConfig();
IResourceMetering.ResourceConfig memory dflt = Constants.DEFAULT_RESOURCE_CONFIG();
......@@ -59,15 +49,8 @@ library ChainAssertions {
checkSystemConfig({ _contracts: _prox, _cfg: _cfg, _isProxy: true });
checkL1CrossDomainMessenger({ _contracts: _prox, _vm: _vm, _isProxy: true });
checkL1StandardBridge({ _contracts: _prox, _isProxy: true });
checkL2OutputOracle({
_contracts: _prox,
_cfg: _cfg,
_l2OutputOracleStartingTimestamp: _l2OutputOracleStartingTimestamp,
_isProxy: true
});
checkOptimismMintableERC20Factory({ _contracts: _prox, _isProxy: true });
checkL1ERC721Bridge({ _contracts: _prox, _isProxy: true });
checkOptimismPortal({ _contracts: _prox, _cfg: _cfg, _isProxy: true });
checkOptimismPortal2({ _contracts: _prox, _cfg: _cfg, _isProxy: true });
checkProtocolVersions({ _contracts: _prox, _cfg: _cfg, _isProxy: true });
}
......@@ -326,56 +309,6 @@ library ChainAssertions {
}
}
/// @notice Asserts that the L2OutputOracle is setup correctly
function checkL2OutputOracle(
Types.ContractSet memory _contracts,
DeployConfig _cfg,
uint256 _l2OutputOracleStartingTimestamp,
bool _isProxy
)
internal
view
{
IL2OutputOracle oracle = IL2OutputOracle(_contracts.L2OutputOracle);
console.log(
"Running chain assertions on the L2OutputOracle %s at %s",
_isProxy ? "proxy" : "implementation",
address(oracle)
);
require(address(oracle) != address(0), "CHECK-L2OO-10");
// Check that the contract is initialized
DeployUtils.assertInitialized({ _contractAddress: address(oracle), _isProxy: _isProxy, _slot: 0, _offset: 0 });
if (_isProxy) {
require(oracle.SUBMISSION_INTERVAL() == _cfg.l2OutputOracleSubmissionInterval(), "CHECK-L2OO-20");
require(oracle.submissionInterval() == _cfg.l2OutputOracleSubmissionInterval(), "CHECK-L2OO-30");
require(oracle.L2_BLOCK_TIME() == _cfg.l2BlockTime(), "CHECK-L2OO-40");
require(oracle.l2BlockTime() == _cfg.l2BlockTime(), "CHECK-L2OO-50");
require(oracle.PROPOSER() == _cfg.l2OutputOracleProposer(), "CHECK-L2OO-60");
require(oracle.proposer() == _cfg.l2OutputOracleProposer(), "CHECK-L2OO-70");
require(oracle.CHALLENGER() == _cfg.l2OutputOracleChallenger(), "CHECK-L2OO-80");
require(oracle.challenger() == _cfg.l2OutputOracleChallenger(), "CHECK-L2OO-90");
require(oracle.FINALIZATION_PERIOD_SECONDS() == _cfg.finalizationPeriodSeconds(), "CHECK-L2OO-100");
require(oracle.finalizationPeriodSeconds() == _cfg.finalizationPeriodSeconds(), "CHECK-L2OO-110");
require(oracle.startingBlockNumber() == _cfg.l2OutputOracleStartingBlockNumber(), "CHECK-L2OO-120");
require(oracle.startingTimestamp() == _l2OutputOracleStartingTimestamp, "CHECK-L2OO-130");
} else {
require(oracle.SUBMISSION_INTERVAL() == 0, "CHECK-L2OO-140");
require(oracle.submissionInterval() == 0, "CHECK-L2OO-150");
require(oracle.L2_BLOCK_TIME() == 0, "CHECK-L2OO-160");
require(oracle.l2BlockTime() == 0, "CHECK-L2OO-170");
require(oracle.PROPOSER() == address(0), "CHECK-L2OO-180");
require(oracle.proposer() == address(0), "CHECK-L2OO-190");
require(oracle.CHALLENGER() == address(0), "CHECK-L2OO-200");
require(oracle.challenger() == address(0), "CHECK-L2OO-210");
require(oracle.FINALIZATION_PERIOD_SECONDS() == 0, "CHECK-L2OO-220");
require(oracle.finalizationPeriodSeconds() == 0, "CHECK-L2OO-230");
require(oracle.startingBlockNumber() == 0, "CHECK-L2OO-240");
require(oracle.startingTimestamp() == 0, "CHECK-L2OO-250");
}
}
/// @notice Asserts that the OptimismMintableERC20Factory is setup correctly
function checkOptimismMintableERC20Factory(Types.ContractSet memory _contracts, bool _isProxy) internal view {
IOptimismMintableERC20Factory factory = IOptimismMintableERC20Factory(_contracts.OptimismMintableERC20Factory);
......@@ -428,39 +361,6 @@ library ChainAssertions {
}
/// @notice Asserts the OptimismPortal is setup correctly
function checkOptimismPortal(Types.ContractSet memory _contracts, DeployConfig _cfg, bool _isProxy) internal view {
IOptimismPortal portal = IOptimismPortal(payable(_contracts.OptimismPortal));
console.log(
"Running chain assertions on the OptimismPortal %s at %s",
_isProxy ? "proxy" : "implementation",
address(portal)
);
require(address(portal) != address(0), "CHECK-OP-10");
// Check that the contract is initialized
DeployUtils.assertInitialized({ _contractAddress: address(portal), _isProxy: _isProxy, _slot: 0, _offset: 0 });
address guardian = _cfg.superchainConfigGuardian();
if (guardian.code.length == 0) {
console.log("Guardian has no code: %s", guardian);
}
if (_isProxy) {
require(address(portal.l2Oracle()) == _contracts.L2OutputOracle, "CHECK-OP-20");
require(address(portal.systemConfig()) == _contracts.SystemConfig, "CHECK-OP-30");
require(portal.guardian() == guardian, "CHECK-OP-40");
require(address(portal.superchainConfig()) == address(_contracts.SuperchainConfig), "CHECK-OP-50");
require(portal.paused() == ISuperchainConfig(_contracts.SuperchainConfig).paused(), "CHECK-OP-60");
require(portal.l2Sender() == Constants.DEFAULT_L2_SENDER, "CHECK-OP-70");
} else {
require(address(portal.l2Oracle()) == address(0), "CHECK-OP-80");
require(address(portal.systemConfig()) == address(0), "CHECK-OP-90");
require(address(portal.superchainConfig()) == address(0), "CHECK-OP-100");
require(portal.l2Sender() == address(0), "CHECK-OP-110");
}
}
/// @notice Asserts the OptimismPortal2 is setup correctly
function checkOptimismPortal2(
Types.ContractSet memory _contracts,
DeployConfig _cfg,
......@@ -469,7 +369,7 @@ library ChainAssertions {
internal
view
{
IOptimismPortal2 portal = IOptimismPortal2(payable(_contracts.OptimismPortal2));
IOptimismPortal2 portal = IOptimismPortal2(payable(_contracts.OptimismPortal));
console.log(
"Running chain assertions on the OptimismPortal2 %s at %s",
_isProxy ? "proxy" : "implementation",
......@@ -596,7 +496,7 @@ library ChainAssertions {
// Ensure that the OPCM impls are correctly saved
OPContractsManager.Implementations memory impls = _opcm.implementations();
require(impls.l1ERC721BridgeImpl == _contracts.L1ERC721Bridge, "CHECK-OPCM-50");
require(impls.optimismPortalImpl == _contracts.OptimismPortal2, "CHECK-OPCM-60");
require(impls.optimismPortalImpl == _contracts.OptimismPortal, "CHECK-OPCM-60");
require(impls.systemConfigImpl == _contracts.SystemConfig, "CHECK-OPCM-70");
require(impls.optimismMintableERC20FactoryImpl == _contracts.OptimismMintableERC20Factory, "CHECK-OPCM-80");
require(impls.l1CrossDomainMessengerImpl == _contracts.L1CrossDomainMessenger, "CHECK-OPCM-90");
......
......@@ -37,9 +37,7 @@ import { GameType, Claim, GameTypes, OutputRoot, Hash } from "src/dispute/lib/Ty
// Interfaces
import { IProxy } from "interfaces/universal/IProxy.sol";
import { IProxyAdmin } from "interfaces/universal/IProxyAdmin.sol";
import { IOptimismPortal } from "interfaces/L1/IOptimismPortal.sol";
import { IOptimismPortal2 } from "interfaces/L1/IOptimismPortal2.sol";
import { IL2OutputOracle } from "interfaces/L1/IL2OutputOracle.sol";
import { ISuperchainConfig } from "interfaces/L1/ISuperchainConfig.sol";
import { ISystemConfig } from "interfaces/L1/ISystemConfig.sol";
import { IDataAvailabilityChallenge } from "interfaces/L1/IDataAvailabilityChallenge.sol";
......@@ -124,7 +122,6 @@ contract Deploy is Deployer {
AnchorStateRegistry: getAddress("AnchorStateRegistryProxy"),
OptimismMintableERC20Factory: getAddress("OptimismMintableERC20FactoryProxy"),
OptimismPortal: getAddress("OptimismPortalProxy"),
OptimismPortal2: getAddress("OptimismPortalProxy"),
SystemConfig: getAddress("SystemConfigProxy"),
L1ERC721Bridge: getAddress("L1ERC721BridgeProxy"),
ProtocolVersions: getAddress("ProtocolVersionsProxy"),
......@@ -143,8 +140,7 @@ contract Deploy is Deployer {
PermissionedDelayedWETH: getAddress("PermissionedDelayedWETH"),
AnchorStateRegistry: getAddress("AnchorStateRegistry"),
OptimismMintableERC20Factory: getAddress("OptimismMintableERC20Factory"),
OptimismPortal: getAddress("OptimismPortal"),
OptimismPortal2: getAddress("OptimismPortal2"),
OptimismPortal: getAddress("OptimismPortal2"),
SystemConfig: getAddress("SystemConfig"),
L1ERC721Bridge: getAddress("L1ERC721Bridge"),
ProtocolVersions: getAddress("ProtocolVersions"),
......@@ -218,16 +214,6 @@ contract Deploy is Deployer {
GameType.wrap(uint32(cfg.respectedGameType()))
);
vm.stopPrank();
} else {
// The L2OutputOracle is not deployed by the OPCM, we deploy the proxy and initialize it here.
deployERC1967Proxy("L2OutputOracleProxy");
initializeL2OutputOracle();
// The OptimismPortalProxy contract is used both with and without Fault Proofs enabled, and is deployed by
// deployOPChain. If Fault Proofs are disabled, then we need to reinitialize the OptimismPortalProxy
// as the legacy OptimismPortal.
resetInitializedProxy("OptimismPortal");
initializeOptimismPortal();
}
if (cfg.useCustomGasToken()) {
......@@ -320,12 +306,6 @@ contract Deploy is Deployer {
}
di.run(dii, dio);
// Temporary patch for legacy system
if (!cfg.useFaultProofs()) {
deployOptimismPortal();
deployL2OutputOracle();
}
save("L1CrossDomainMessenger", address(dio.l1CrossDomainMessengerImpl()));
save("OptimismMintableERC20Factory", address(dio.optimismMintableERC20FactoryImpl()));
save("SystemConfig", address(dio.systemConfigImpl()));
......@@ -333,6 +313,7 @@ contract Deploy is Deployer {
save("L1ERC721Bridge", address(dio.l1ERC721BridgeImpl()));
// Fault proofs
save("OptimismPortal", address(dio.optimismPortalImpl()));
save("OptimismPortal2", address(dio.optimismPortalImpl()));
save("DisputeGameFactory", address(dio.disputeGameFactoryImpl()));
save("DelayedWETH", address(dio.delayedWETHImpl()));
......@@ -400,6 +381,7 @@ contract Deploy is Deployer {
save("AnchorStateRegistry", address(deployOutput.anchorStateRegistryImpl));
save("PermissionedDisputeGame", address(deployOutput.permissionedDisputeGame));
save("OptimismPortalProxy", address(deployOutput.optimismPortalProxy));
save("OptimismPortal2Proxy", address(deployOutput.optimismPortalProxy));
// Check if the permissionless game implementation is already set
IDisputeGameFactory factory = IDisputeGameFactory(mustGetAddress("DisputeGameFactoryProxy"));
......@@ -492,52 +474,6 @@ contract Deploy is Deployer {
// Implementation Deployment Functions //
////////////////////////////////////////////////////////////////
/// @notice Deploy the OptimismPortal
function deployOptimismPortal() public broadcast returns (address addr_) {
require(!cfg.useFaultProofs(), "Deploy: FaultProofs OptimismPortal is deployed by OPCM");
require(!cfg.useInterop(), "Deploy: The legacy OptimismPortal does not support interop");
addr_ = DeployUtils.create2AndSave({
_save: this,
_salt: _implSalt(),
_name: "OptimismPortal",
_args: DeployUtils.encodeConstructor(abi.encodeCall(IOptimismPortal.__constructor__, ()))
});
// Override the `OptimismPortal` contract to the deployed implementation. This is necessary
// to check the `OptimismPortal` implementation alongside dependent contracts, which
// are always proxies.
Types.ContractSet memory contracts = _proxies();
contracts.OptimismPortal = addr_;
ChainAssertions.checkOptimismPortal({ _contracts: contracts, _cfg: cfg, _isProxy: false });
}
/// @notice Deploy the L2OutputOracle
function deployL2OutputOracle() public broadcast returns (address addr_) {
IL2OutputOracle oracle = IL2OutputOracle(
DeployUtils.create2AndSave({
_save: this,
_salt: _implSalt(),
_name: "L2OutputOracle",
_args: DeployUtils.encodeConstructor(abi.encodeCall(IL2OutputOracle.__constructor__, ()))
})
);
// Override the `L2OutputOracle` contract to the deployed implementation. This is necessary
// to check the `L2OutputOracle` implementation alongside dependent contracts, which
// are always proxies.
Types.ContractSet memory contracts = _proxies();
contracts.L2OutputOracle = address(oracle);
ChainAssertions.checkL2OutputOracle({
_contracts: contracts,
_cfg: cfg,
_l2OutputOracleStartingTimestamp: 0,
_isProxy: false
});
addr_ = address(oracle);
}
/// @notice Deploy the DataAvailabilityChallenge
function deployDataAvailabilityChallenge() public broadcast returns (address addr_) {
IDataAvailabilityChallenge dac = IDataAvailabilityChallenge(
......@@ -603,73 +539,6 @@ contract Deploy is Deployer {
ChainAssertions.checkSystemConfig({ _contracts: _proxies(), _cfg: cfg, _isProxy: true });
}
/// @notice Initialize the L2OutputOracle
function initializeL2OutputOracle() public broadcast {
console.log("Upgrading and initializing L2OutputOracle proxy");
address l2OutputOracleProxy = mustGetAddress("L2OutputOracleProxy");
address l2OutputOracle = mustGetAddress("L2OutputOracle");
IProxyAdmin proxyAdmin = IProxyAdmin(payable(mustGetAddress("ProxyAdmin")));
proxyAdmin.upgradeAndCall({
_proxy: payable(l2OutputOracleProxy),
_implementation: l2OutputOracle,
_data: abi.encodeCall(
IL2OutputOracle.initialize,
(
cfg.l2OutputOracleSubmissionInterval(),
cfg.l2BlockTime(),
cfg.l2OutputOracleStartingBlockNumber(),
cfg.l2OutputOracleStartingTimestamp(),
cfg.l2OutputOracleProposer(),
cfg.l2OutputOracleChallenger(),
cfg.finalizationPeriodSeconds()
)
)
});
IL2OutputOracle oracle = IL2OutputOracle(l2OutputOracleProxy);
string memory version = oracle.version();
console.log("L2OutputOracle version: %s", version);
ChainAssertions.checkL2OutputOracle({
_contracts: _proxies(),
_cfg: cfg,
_l2OutputOracleStartingTimestamp: cfg.l2OutputOracleStartingTimestamp(),
_isProxy: true
});
}
/// @notice Initialize the OptimismPortal
function initializeOptimismPortal() public broadcast {
console.log("Upgrading and initializing OptimismPortal proxy");
require(!cfg.useFaultProofs(), "Deploy: FaultProofs OptimismPortal is initialized by OPCM");
address optimismPortalProxy = mustGetAddress("OptimismPortalProxy");
address systemConfigProxy = mustGetAddress("SystemConfigProxy");
address superchainConfigProxy = mustGetAddress("SuperchainConfigProxy");
address optimismPortal = mustGetAddress("OptimismPortal");
address l2OutputOracleProxy = mustGetAddress("L2OutputOracleProxy");
IProxyAdmin proxyAdmin = IProxyAdmin(payable(mustGetAddress("ProxyAdmin")));
proxyAdmin.upgradeAndCall({
_proxy: payable(optimismPortalProxy),
_implementation: optimismPortal,
_data: abi.encodeCall(
IOptimismPortal.initialize,
(
IL2OutputOracle(l2OutputOracleProxy),
ISystemConfig(systemConfigProxy),
ISuperchainConfig(superchainConfigProxy)
)
)
});
IOptimismPortal portal = IOptimismPortal(payable(optimismPortalProxy));
string memory version = portal.version();
console.log("OptimismPortal version: %s", version);
ChainAssertions.checkOptimismPortal({ _contracts: _proxies(), _cfg: cfg, _isProxy: true });
}
/// @notice Initialize the DataAvailabilityChallenge
function initializeDataAvailabilityChallenge() public broadcast {
console.log("Upgrading and initializing DataAvailabilityChallenge proxy");
......
......@@ -14,7 +14,6 @@ library Types {
address AnchorStateRegistry;
address OptimismMintableERC20Factory;
address OptimismPortal;
address OptimismPortal2;
address SystemConfig;
address L1ERC721Bridge;
address ProtocolVersions;
......
GasBenchMark_L1BlockInterop_DepositsComplete:test_depositsComplete_benchmark() (gas: 7589)
GasBenchMark_L1BlockInterop_DepositsComplete_Warm:test_depositsComplete_benchmark() (gas: 5589)
GasBenchMark_L1BlockInterop_DepositsComplete:test_depositsComplete_benchmark() (gas: 7567)
GasBenchMark_L1BlockInterop_DepositsComplete_Warm:test_depositsComplete_benchmark() (gas: 5567)
GasBenchMark_L1BlockInterop_SetValuesInterop:test_setL1BlockValuesInterop_benchmark() (gas: 175722)
GasBenchMark_L1BlockInterop_SetValuesInterop_Warm:test_setL1BlockValuesInterop_benchmark() (gas: 5144)
GasBenchMark_L1Block_SetValuesEcotone:test_setL1BlockValuesEcotone_benchmark() (gas: 158553)
GasBenchMark_L1Block_SetValuesEcotone_Warm:test_setL1BlockValuesEcotone_benchmark() (gas: 7619)
GasBenchMark_L1Block_SetValuesEcotone:test_setL1BlockValuesEcotone_benchmark() (gas: 158531)
GasBenchMark_L1Block_SetValuesEcotone_Warm:test_setL1BlockValuesEcotone_benchmark() (gas: 7597)
GasBenchMark_L1CrossDomainMessenger:test_sendMessage_benchmark_0() (gas: 369235)
GasBenchMark_L1CrossDomainMessenger:test_sendMessage_benchmark_1() (gas: 2967442)
GasBenchMark_L1StandardBridge_Deposit:test_depositERC20_benchmark_0() (gas: 564398)
GasBenchMark_L1StandardBridge_Deposit:test_depositERC20_benchmark_1() (gas: 4076568)
GasBenchMark_L1StandardBridge_Deposit:test_depositETH_benchmark_0() (gas: 467098)
GasBenchMark_L1StandardBridge_Deposit:test_depositETH_benchmark_1() (gas: 3512802)
GasBenchMark_L1StandardBridge_Finalize:test_finalizeETHWithdrawal_benchmark() (gas: 72619)
GasBenchMark_L2OutputOracle:test_proposeL2Output_benchmark() (gas: 92973)
GasBenchMark_OptimismPortal:test_depositTransaction_benchmark() (gas: 68422)
GasBenchMark_OptimismPortal:test_depositTransaction_benchmark_1() (gas: 69008)
GasBenchMark_OptimismPortal:test_proveWithdrawalTransaction_benchmark() (gas: 155565)
\ No newline at end of file
GasBenchMark_L1StandardBridge_Deposit:test_depositERC20_benchmark_0() (gas: 564429)
GasBenchMark_L1StandardBridge_Deposit:test_depositERC20_benchmark_1() (gas: 4076577)
GasBenchMark_L1StandardBridge_Deposit:test_depositETH_benchmark_0() (gas: 467041)
GasBenchMark_L1StandardBridge_Deposit:test_depositETH_benchmark_1() (gas: 3512790)
GasBenchMark_L1StandardBridge_Finalize:test_finalizeETHWithdrawal_benchmark() (gas: 72667)
\ No newline at end of file
......@@ -74,7 +74,7 @@
"name": "PORTAL",
"outputs": [
{
"internalType": "contract IOptimismPortal",
"internalType": "contract IOptimismPortal2",
"name": "",
"type": "address"
}
......@@ -185,7 +185,7 @@
"type": "address"
},
{
"internalType": "contract IOptimismPortal",
"internalType": "contract IOptimismPortal2",
"name": "_portal",
"type": "address"
},
......@@ -244,7 +244,7 @@
"name": "portal",
"outputs": [
{
"internalType": "contract IOptimismPortal",
"internalType": "contract IOptimismPortal2",
"name": "",
"type": "address"
}
......
[
{
"inputs": [],
"stateMutability": "nonpayable",
"type": "constructor"
},
{
"inputs": [],
"name": "CHALLENGER",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "FINALIZATION_PERIOD_SECONDS",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "L2_BLOCK_TIME",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "PROPOSER",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "SUBMISSION_INTERVAL",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "challenger",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "_l2BlockNumber",
"type": "uint256"
}
],
"name": "computeL2Timestamp",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "_l2OutputIndex",
"type": "uint256"
}
],
"name": "deleteL2Outputs",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "finalizationPeriodSeconds",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "_l2OutputIndex",
"type": "uint256"
}
],
"name": "getL2Output",
"outputs": [
{
"components": [
{
"internalType": "bytes32",
"name": "outputRoot",
"type": "bytes32"
},
{
"internalType": "uint128",
"name": "timestamp",
"type": "uint128"
},
{
"internalType": "uint128",
"name": "l2BlockNumber",
"type": "uint128"
}
],
"internalType": "struct Types.OutputProposal",
"name": "",
"type": "tuple"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "_l2BlockNumber",
"type": "uint256"
}
],
"name": "getL2OutputAfter",
"outputs": [
{
"components": [
{
"internalType": "bytes32",
"name": "outputRoot",
"type": "bytes32"
},
{
"internalType": "uint128",
"name": "timestamp",
"type": "uint128"
},
{
"internalType": "uint128",
"name": "l2BlockNumber",
"type": "uint128"
}
],
"internalType": "struct Types.OutputProposal",
"name": "",
"type": "tuple"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "_l2BlockNumber",
"type": "uint256"
}
],
"name": "getL2OutputIndexAfter",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "_submissionInterval",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "_l2BlockTime",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "_startingBlockNumber",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "_startingTimestamp",
"type": "uint256"
},
{
"internalType": "address",
"name": "_proposer",
"type": "address"
},
{
"internalType": "address",
"name": "_challenger",
"type": "address"
},
{
"internalType": "uint256",
"name": "_finalizationPeriodSeconds",
"type": "uint256"
}
],
"name": "initialize",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "l2BlockTime",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "latestBlockNumber",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "latestOutputIndex",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "nextBlockNumber",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "nextOutputIndex",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "bytes32",
"name": "_outputRoot",
"type": "bytes32"
},
{
"internalType": "uint256",
"name": "_l2BlockNumber",
"type": "uint256"
},
{
"internalType": "bytes32",
"name": "_l1BlockHash",
"type": "bytes32"
},
{
"internalType": "uint256",
"name": "_l1BlockNumber",
"type": "uint256"
}
],
"name": "proposeL2Output",
"outputs": [],
"stateMutability": "payable",
"type": "function"
},
{
"inputs": [],
"name": "proposer",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "startingBlockNumber",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "startingTimestamp",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "submissionInterval",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "version",
"outputs": [
{
"internalType": "string",
"name": "",
"type": "string"
}
],
"stateMutability": "view",
"type": "function"
},
{
"anonymous": false,
"inputs": [
{
"indexed": false,
"internalType": "uint8",
"name": "version",
"type": "uint8"
}
],
"name": "Initialized",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "bytes32",
"name": "outputRoot",
"type": "bytes32"
},
{
"indexed": true,
"internalType": "uint256",
"name": "l2OutputIndex",
"type": "uint256"
},
{
"indexed": true,
"internalType": "uint256",
"name": "l2BlockNumber",
"type": "uint256"
},
{
"indexed": false,
"internalType": "uint256",
"name": "l1Timestamp",
"type": "uint256"
}
],
"name": "OutputProposed",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "uint256",
"name": "prevNextOutputIndex",
"type": "uint256"
},
{
"indexed": true,
"internalType": "uint256",
"name": "newNextOutputIndex",
"type": "uint256"
}
],
"name": "OutputsDeleted",
"type": "event"
}
]
\ No newline at end of file
[
{
"inputs": [],
"stateMutability": "nonpayable",
"type": "constructor"
},
{
"stateMutability": "payable",
"type": "receive"
},
{
"inputs": [],
"name": "balance",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "_to",
"type": "address"
},
{
"internalType": "uint256",
"name": "_mint",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "_value",
"type": "uint256"
},
{
"internalType": "uint64",
"name": "_gasLimit",
"type": "uint64"
},
{
"internalType": "bool",
"name": "_isCreation",
"type": "bool"
},
{
"internalType": "bytes",
"name": "_data",
"type": "bytes"
}
],
"name": "depositERC20Transaction",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "_to",
"type": "address"
},
{
"internalType": "uint256",
"name": "_value",
"type": "uint256"
},
{
"internalType": "uint64",
"name": "_gasLimit",
"type": "uint64"
},
{
"internalType": "bool",
"name": "_isCreation",
"type": "bool"
},
{
"internalType": "bytes",
"name": "_data",
"type": "bytes"
}
],
"name": "depositTransaction",
"outputs": [],
"stateMutability": "payable",
"type": "function"
},
{
"inputs": [],
"name": "donateETH",
"outputs": [],
"stateMutability": "payable",
"type": "function"
},
{
"inputs": [
{
"components": [
{
"internalType": "uint256",
"name": "nonce",
"type": "uint256"
},
{
"internalType": "address",
"name": "sender",
"type": "address"
},
{
"internalType": "address",
"name": "target",
"type": "address"
},
{
"internalType": "uint256",
"name": "value",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "gasLimit",
"type": "uint256"
},
{
"internalType": "bytes",
"name": "data",
"type": "bytes"
}
],
"internalType": "struct Types.WithdrawalTransaction",
"name": "_tx",
"type": "tuple"
}
],
"name": "finalizeWithdrawalTransaction",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "bytes32",
"name": "",
"type": "bytes32"
}
],
"name": "finalizedWithdrawals",
"outputs": [
{
"internalType": "bool",
"name": "",
"type": "bool"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "guardian",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "contract IL2OutputOracle",
"name": "_l2Oracle",
"type": "address"
},
{
"internalType": "contract ISystemConfig",
"name": "_systemConfig",
"type": "address"
},
{
"internalType": "contract ISuperchainConfig",
"name": "_superchainConfig",
"type": "address"
}
],
"name": "initialize",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "_l2OutputIndex",
"type": "uint256"
}
],
"name": "isOutputFinalized",
"outputs": [
{
"internalType": "bool",
"name": "",
"type": "bool"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "l2Oracle",
"outputs": [
{
"internalType": "contract IL2OutputOracle",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "l2Sender",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint64",
"name": "_byteCount",
"type": "uint64"
}
],
"name": "minimumGasLimit",
"outputs": [
{
"internalType": "uint64",
"name": "",
"type": "uint64"
}
],
"stateMutability": "pure",
"type": "function"
},
{
"inputs": [],
"name": "params",
"outputs": [
{
"internalType": "uint128",
"name": "prevBaseFee",
"type": "uint128"
},
{
"internalType": "uint64",
"name": "prevBoughtGas",
"type": "uint64"
},
{
"internalType": "uint64",
"name": "prevBlockNum",
"type": "uint64"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "paused",
"outputs": [
{
"internalType": "bool",
"name": "paused_",
"type": "bool"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"components": [
{
"internalType": "uint256",
"name": "nonce",
"type": "uint256"
},
{
"internalType": "address",
"name": "sender",
"type": "address"
},
{
"internalType": "address",
"name": "target",
"type": "address"
},
{
"internalType": "uint256",
"name": "value",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "gasLimit",
"type": "uint256"
},
{
"internalType": "bytes",
"name": "data",
"type": "bytes"
}
],
"internalType": "struct Types.WithdrawalTransaction",
"name": "_tx",
"type": "tuple"
},
{
"internalType": "uint256",
"name": "_l2OutputIndex",
"type": "uint256"
},
{
"components": [
{
"internalType": "bytes32",
"name": "version",
"type": "bytes32"
},
{
"internalType": "bytes32",
"name": "stateRoot",
"type": "bytes32"
},
{
"internalType": "bytes32",
"name": "messagePasserStorageRoot",
"type": "bytes32"
},
{
"internalType": "bytes32",
"name": "latestBlockhash",
"type": "bytes32"
}
],
"internalType": "struct Types.OutputRootProof",
"name": "_outputRootProof",
"type": "tuple"
},
{
"internalType": "bytes[]",
"name": "_withdrawalProof",
"type": "bytes[]"
}
],
"name": "proveWithdrawalTransaction",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "bytes32",
"name": "",
"type": "bytes32"
}
],
"name": "provenWithdrawals",
"outputs": [
{
"internalType": "bytes32",
"name": "outputRoot",
"type": "bytes32"
},
{
"internalType": "uint128",
"name": "timestamp",
"type": "uint128"
},
{
"internalType": "uint128",
"name": "l2OutputIndex",
"type": "uint128"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "_token",
"type": "address"
},
{
"internalType": "uint8",
"name": "_decimals",
"type": "uint8"
},
{
"internalType": "bytes32",
"name": "_name",
"type": "bytes32"
},
{
"internalType": "bytes32",
"name": "_symbol",
"type": "bytes32"
}
],
"name": "setGasPayingToken",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "superchainConfig",
"outputs": [
{
"internalType": "contract ISuperchainConfig",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "systemConfig",
"outputs": [
{
"internalType": "contract ISystemConfig",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "version",
"outputs": [
{
"internalType": "string",
"name": "",
"type": "string"
}
],
"stateMutability": "pure",
"type": "function"
},
{
"anonymous": false,
"inputs": [
{
"indexed": false,
"internalType": "uint8",
"name": "version",
"type": "uint8"
}
],
"name": "Initialized",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "from",
"type": "address"
},
{
"indexed": true,
"internalType": "address",
"name": "to",
"type": "address"
},
{
"indexed": true,
"internalType": "uint256",
"name": "version",
"type": "uint256"
},
{
"indexed": false,
"internalType": "bytes",
"name": "opaqueData",
"type": "bytes"
}
],
"name": "TransactionDeposited",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "bytes32",
"name": "withdrawalHash",
"type": "bytes32"
},
{
"indexed": false,
"internalType": "bool",
"name": "success",
"type": "bool"
}
],
"name": "WithdrawalFinalized",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "bytes32",
"name": "withdrawalHash",
"type": "bytes32"
},
{
"indexed": true,
"internalType": "address",
"name": "from",
"type": "address"
},
{
"indexed": true,
"internalType": "address",
"name": "to",
"type": "address"
}
],
"name": "WithdrawalProven",
"type": "event"
},
{
"inputs": [],
"name": "BadTarget",
"type": "error"
},
{
"inputs": [],
"name": "CallPaused",
"type": "error"
},
{
"inputs": [],
"name": "ContentLengthMismatch",
"type": "error"
},
{
"inputs": [],
"name": "EmptyItem",
"type": "error"
},
{
"inputs": [],
"name": "GasEstimation",
"type": "error"
},
{
"inputs": [],
"name": "InvalidDataRemainder",
"type": "error"
},
{
"inputs": [],
"name": "InvalidHeader",
"type": "error"
},
{
"inputs": [],
"name": "LargeCalldata",
"type": "error"
},
{
"inputs": [],
"name": "NoValue",
"type": "error"
},
{
"inputs": [],
"name": "NonReentrant",
"type": "error"
},
{
"inputs": [],
"name": "OnlyCustomGasToken",
"type": "error"
},
{
"inputs": [],
"name": "OutOfGas",
"type": "error"
},
{
"inputs": [],
"name": "SmallGasLimit",
"type": "error"
},
{
"inputs": [],
"name": "TransferFailed",
"type": "error"
},
{
"inputs": [],
"name": "Unauthorized",
"type": "error"
},
{
"inputs": [],
"name": "UnexpectedList",
"type": "error"
},
{
"inputs": [],
"name": "UnexpectedString",
"type": "error"
}
]
\ No newline at end of file
......@@ -4,8 +4,8 @@
"sourceCodeHash": "0xae49c741c8cd546981ab59b85b88e9fc1055c4fae085e7078d601b42464f86e6"
},
"src/L1/L1CrossDomainMessenger.sol": {
"initCodeHash": "0x833d739d04125bf14f3bd11d9da9405354d37ad246737b56d5b112cddc0031ff",
"sourceCodeHash": "0xca20239ad3eb140e0d3d165ec1d3d1c68581d878ec2b86c5d33b07c6c87f6f45"
"initCodeHash": "0x192b98a93759f2044ee1ea56270cd203d4f16210c06be388f29b8c1114b9329d",
"sourceCodeHash": "0xfa18fb2e6ac73a143303b07735239a383ae84bd0d0042cceb8253f4784e07afb"
},
"src/L1/L1ERC721Bridge.sol": {
"initCodeHash": "0x280488bce8b4fb364740c59de14c423851902088f384e077bccc79b9df48528a",
......@@ -15,18 +15,10 @@
"initCodeHash": "0x7d7030359826f64714ef0c2a5198901812fb0a99e949f23fe54ccf87a0df2e67",
"sourceCodeHash": "0xa91b445bdc666a02ba18e3b91ba94b6d54bbe65da714002fc734814201319d57"
},
"src/L1/L2OutputOracle.sol": {
"initCodeHash": "0xd992c45b8461b9546fe4e3cecbce15d17ce366a62aab17058aad3b15bf36d21d",
"sourceCodeHash": "0xa35478e9e2659a320da725a117b200dea2826175d2b17d881de1196da0cc91eb"
},
"src/L1/OPContractsManager.sol": {
"initCodeHash": "0x9b704574a7005dc2aa8d6a3e0d85572493cc4bbd60033a23e437632a5fef7720",
"sourceCodeHash": "0x05ed7ad68e4e9bca7334314e794a1f66e5899532bb01cfa3a7716cb2688df9d5"
},
"src/L1/OptimismPortal.sol": {
"initCodeHash": "0x2f5427c78c3445cdbe38931e67274ae87a6ac7fcc1a2279f1e9fc294808b67f9",
"sourceCodeHash": "0x77e0f0f8caa9742896a3e7cdee15b0061e298f9c950159bb7231b8196517c9d2"
},
"src/L1/OptimismPortal2.sol": {
"initCodeHash": "0xfd14fd690752519064d6de6c3e15d69ec9146bc8714e56ac286305773dbb1533",
"sourceCodeHash": "0x3dbd4601c67a43c42f403f6b28e6e2d8bf4f3d2cf2f2d8f7460026e0c6c66def"
......@@ -44,12 +36,12 @@
"sourceCodeHash": "0xafa784ea78818a382ff3a61e2d84be58c7978110c06b9273db68c0213ead02d3"
},
"src/L1/SystemConfig.sol": {
"initCodeHash": "0x11707ca5ef2bfc0da82e945cb97e62226852d850e8bdecb8ec22fc1ab7967894",
"sourceCodeHash": "0x71cedc36b538c3db2ac43d1d9554583a3f09eee9e9a5c5d39608418bd466ee0e"
"initCodeHash": "0xe1baf5d3415baf65a45031c37e4bd794ecb7f779b227f6cbed06d945aa2097fc",
"sourceCodeHash": "0x52b7d8825b4962e720513906ac993d36964cf03c45364c9f03d88507a0caac57"
},
"src/L1/SystemConfigInterop.sol": {
"initCodeHash": "0x53d4adf9db26e1217daccff4e91f9717925ad2e666d0953d43009535efd5ad3d",
"sourceCodeHash": "0x8a08e71f05581449c32ea15702c1881463a878243fd8bb90dea33a795abd0b84"
"initCodeHash": "0x91ed371ee2f6d4a9ed1483971df8a6888cc8b5aca5212b180e395476f21cb268",
"sourceCodeHash": "0x6a51dc1b2bfadd9781c729f8db70972926f364b0e605536fb075bfddd0d4c433"
},
"src/L2/BaseFeeVault.sol": {
"initCodeHash": "0xc403d4c555d8e69a2699e01d192ae7327136701fa02da10a6d75a584b3c364c9",
......
......@@ -137,7 +137,7 @@
"label": "portal",
"offset": 0,
"slot": "252",
"type": "contract IOptimismPortal"
"type": "contract IOptimismPortal2"
},
{
"bytes": "20",
......
[
{
"bytes": "1",
"label": "_initialized",
"offset": 0,
"slot": "0",
"type": "uint8"
},
{
"bytes": "1",
"label": "_initializing",
"offset": 1,
"slot": "0",
"type": "bool"
},
{
"bytes": "32",
"label": "startingBlockNumber",
"offset": 0,
"slot": "1",
"type": "uint256"
},
{
"bytes": "32",
"label": "startingTimestamp",
"offset": 0,
"slot": "2",
"type": "uint256"
},
{
"bytes": "32",
"label": "l2Outputs",
"offset": 0,
"slot": "3",
"type": "struct Types.OutputProposal[]"
},
{
"bytes": "32",
"label": "submissionInterval",
"offset": 0,
"slot": "4",
"type": "uint256"
},
{
"bytes": "32",
"label": "l2BlockTime",
"offset": 0,
"slot": "5",
"type": "uint256"
},
{
"bytes": "20",
"label": "challenger",
"offset": 0,
"slot": "6",
"type": "address"
},
{
"bytes": "20",
"label": "proposer",
"offset": 0,
"slot": "7",
"type": "address"
},
{
"bytes": "32",
"label": "finalizationPeriodSeconds",
"offset": 0,
"slot": "8",
"type": "uint256"
}
]
\ No newline at end of file
[
{
"bytes": "1",
"label": "_initialized",
"offset": 0,
"slot": "0",
"type": "uint8"
},
{
"bytes": "1",
"label": "_initializing",
"offset": 1,
"slot": "0",
"type": "bool"
},
{
"bytes": "32",
"label": "params",
"offset": 0,
"slot": "1",
"type": "struct ResourceMetering.ResourceParams"
},
{
"bytes": "1536",
"label": "__gap",
"offset": 0,
"slot": "2",
"type": "uint256[48]"
},
{
"bytes": "20",
"label": "l2Sender",
"offset": 0,
"slot": "50",
"type": "address"
},
{
"bytes": "32",
"label": "finalizedWithdrawals",
"offset": 0,
"slot": "51",
"type": "mapping(bytes32 => bool)"
},
{
"bytes": "32",
"label": "provenWithdrawals",
"offset": 0,
"slot": "52",
"type": "mapping(bytes32 => struct OptimismPortal.ProvenWithdrawal)"
},
{
"bytes": "1",
"label": "spacer_53_0_1",
"offset": 0,
"slot": "53",
"type": "bool"
},
{
"bytes": "20",
"label": "superchainConfig",
"offset": 1,
"slot": "53",
"type": "contract ISuperchainConfig"
},
{
"bytes": "20",
"label": "l2Oracle",
"offset": 0,
"slot": "54",
"type": "contract IL2OutputOracle"
},
{
"bytes": "20",
"label": "systemConfig",
"offset": 0,
"slot": "55",
"type": "contract ISystemConfig"
},
{
"bytes": "20",
"label": "spacer_56_0_20",
"offset": 0,
"slot": "56",
"type": "address"
},
{
"bytes": "32",
"label": "spacer_57_0_32",
"offset": 0,
"slot": "57",
"type": "bytes32"
},
{
"bytes": "32",
"label": "spacer_58_0_32",
"offset": 0,
"slot": "58",
"type": "bytes32"
},
{
"bytes": "32",
"label": "spacer_59_0_32",
"offset": 0,
"slot": "59",
"type": "bytes32"
},
{
"bytes": "32",
"label": "spacer_60_0_32",
"offset": 0,
"slot": "60",
"type": "bytes32"
},
{
"bytes": "32",
"label": "_balance",
"offset": 0,
"slot": "61",
"type": "uint256"
}
]
\ No newline at end of file
......@@ -11,7 +11,7 @@ import { Predeploys } from "src/libraries/Predeploys.sol";
import { ISemver } from "interfaces/universal/ISemver.sol";
import { ISuperchainConfig } from "interfaces/L1/ISuperchainConfig.sol";
import { ISystemConfig } from "interfaces/L1/ISystemConfig.sol";
import { IOptimismPortal } from "interfaces/L1/IOptimismPortal.sol";
import { IOptimismPortal2 as IOptimismPortal } from "interfaces/L1/IOptimismPortal2.sol";
/// @custom:proxied true
/// @title L1CrossDomainMessenger
......@@ -30,8 +30,8 @@ contract L1CrossDomainMessenger is CrossDomainMessenger, ISemver {
ISystemConfig public systemConfig;
/// @notice Semantic version.
/// @custom:semver 2.4.1-beta.4
string public constant version = "2.4.1-beta.4";
/// @custom:semver 2.4.1-beta.5
string public constant version = "2.4.1-beta.5";
/// @notice Constructs the L1CrossDomainMessenger contract.
constructor() {
......
// SPDX-License-Identifier: MIT
pragma solidity 0.8.15;
// Contracts
import { Initializable } from "@openzeppelin/contracts/proxy/utils/Initializable.sol";
// Libraries
import { Types } from "src/libraries/Types.sol";
// Interfaces
import { ISemver } from "interfaces/universal/ISemver.sol";
/// @custom:proxied true
/// @title L2OutputOracle
/// @notice The L2OutputOracle contains an array of L2 state outputs, where each output is a
/// commitment to the state of the L2 chain. Other contracts like the OptimismPortal use
/// these outputs to verify information about the state of L2.
contract L2OutputOracle is Initializable, ISemver {
/// @notice The number of the first L2 block recorded in this contract.
uint256 public startingBlockNumber;
/// @notice The timestamp of the first L2 block recorded in this contract.
uint256 public startingTimestamp;
/// @notice An array of L2 output proposals.
Types.OutputProposal[] internal l2Outputs;
/// @notice The interval in L2 blocks at which checkpoints must be submitted.
/// @custom:network-specific
uint256 public submissionInterval;
/// @notice The time between L2 blocks in seconds. Once set, this value MUST NOT be modified.
/// @custom:network-specific
uint256 public l2BlockTime;
/// @notice The address of the challenger. Can be updated via upgrade.
/// @custom:network-specific
address public challenger;
/// @notice The address of the proposer. Can be updated via upgrade.
/// @custom:network-specific
address public proposer;
/// @notice The minimum time (in seconds) that must elapse before a withdrawal can be finalized.
/// @custom:network-specific
uint256 public finalizationPeriodSeconds;
/// @notice Emitted when an output is proposed.
/// @param outputRoot The output root.
/// @param l2OutputIndex The index of the output in the l2Outputs array.
/// @param l2BlockNumber The L2 block number of the output root.
/// @param l1Timestamp The L1 timestamp when proposed.
event OutputProposed(
bytes32 indexed outputRoot, uint256 indexed l2OutputIndex, uint256 indexed l2BlockNumber, uint256 l1Timestamp
);
/// @notice Emitted when outputs are deleted.
/// @param prevNextOutputIndex Next L2 output index before the deletion.
/// @param newNextOutputIndex Next L2 output index after the deletion.
event OutputsDeleted(uint256 indexed prevNextOutputIndex, uint256 indexed newNextOutputIndex);
/// @notice Semantic version.
/// @custom:semver 1.8.1-beta.4
string public constant version = "1.8.1-beta.4";
/// @notice Constructs the L2OutputOracle contract. Initializes variables to the same values as
/// in the getting-started config.
constructor() {
_disableInitializers();
}
/// @notice Initializer.
/// @param _submissionInterval Interval in blocks at which checkpoints must be submitted.
/// @param _l2BlockTime The time per L2 block, in seconds.
/// @param _startingBlockNumber The number of the first L2 block.
/// @param _startingTimestamp The timestamp of the first L2 block.
/// @param _proposer The address of the proposer.
/// @param _challenger The address of the challenger.
/// @param _finalizationPeriodSeconds The minimum time (in seconds) that must elapse before a withdrawal
/// can be finalized.
function initialize(
uint256 _submissionInterval,
uint256 _l2BlockTime,
uint256 _startingBlockNumber,
uint256 _startingTimestamp,
address _proposer,
address _challenger,
uint256 _finalizationPeriodSeconds
)
external
initializer
{
require(_submissionInterval > 0, "L2OutputOracle: submission interval must be greater than 0");
require(_l2BlockTime > 0, "L2OutputOracle: L2 block time must be greater than 0");
require(
_startingTimestamp <= block.timestamp,
"L2OutputOracle: starting L2 timestamp must be less than current time"
);
submissionInterval = _submissionInterval;
l2BlockTime = _l2BlockTime;
startingBlockNumber = _startingBlockNumber;
startingTimestamp = _startingTimestamp;
proposer = _proposer;
challenger = _challenger;
finalizationPeriodSeconds = _finalizationPeriodSeconds;
}
/// @notice Getter for the submissionInterval.
/// Public getter is legacy and will be removed in the future. Use `submissionInterval` instead.
/// @return Submission interval.
/// @custom:legacy
function SUBMISSION_INTERVAL() external view returns (uint256) {
return submissionInterval;
}
/// @notice Getter for the l2BlockTime.
/// Public getter is legacy and will be removed in the future. Use `l2BlockTime` instead.
/// @return L2 block time.
/// @custom:legacy
function L2_BLOCK_TIME() external view returns (uint256) {
return l2BlockTime;
}
/// @notice Getter for the challenger address.
/// Public getter is legacy and will be removed in the future. Use `challenger` instead.
/// @return Address of the challenger.
/// @custom:legacy
function CHALLENGER() external view returns (address) {
return challenger;
}
/// @notice Getter for the proposer address.
/// Public getter is legacy and will be removed in the future. Use `proposer` instead.
/// @return Address of the proposer.
/// @custom:legacy
function PROPOSER() external view returns (address) {
return proposer;
}
/// @notice Getter for the finalizationPeriodSeconds.
/// Public getter is legacy and will be removed in the future. Use `finalizationPeriodSeconds` instead.
/// @return Finalization period in seconds.
/// @custom:legacy
function FINALIZATION_PERIOD_SECONDS() external view returns (uint256) {
return finalizationPeriodSeconds;
}
/// @notice Deletes all output proposals after and including the proposal that corresponds to
/// the given output index. Only the challenger address can delete outputs.
/// @param _l2OutputIndex Index of the first L2 output to be deleted.
/// All outputs after this output will also be deleted.
function deleteL2Outputs(uint256 _l2OutputIndex) external {
require(msg.sender == challenger, "L2OutputOracle: only the challenger address can delete outputs");
// Make sure we're not *increasing* the length of the array.
require(
_l2OutputIndex < l2Outputs.length, "L2OutputOracle: cannot delete outputs after the latest output index"
);
// Do not allow deleting any outputs that have already been finalized.
require(
block.timestamp - l2Outputs[_l2OutputIndex].timestamp < finalizationPeriodSeconds,
"L2OutputOracle: cannot delete outputs that have already been finalized"
);
uint256 prevNextL2OutputIndex = nextOutputIndex();
// Use assembly to delete the array elements because Solidity doesn't allow it.
assembly {
sstore(l2Outputs.slot, _l2OutputIndex)
}
emit OutputsDeleted(prevNextL2OutputIndex, _l2OutputIndex);
}
/// @notice Accepts an outputRoot and the timestamp of the corresponding L2 block.
/// The timestamp must be equal to the current value returned by `nextTimestamp()` in
/// order to be accepted. This function may only be called by the Proposer.
/// @param _outputRoot The L2 output of the checkpoint block.
/// @param _l2BlockNumber The L2 block number that resulted in _outputRoot.
/// @param _l1BlockHash A block hash which must be included in the current chain.
/// @param _l1BlockNumber The block number with the specified block hash.
function proposeL2Output(
bytes32 _outputRoot,
uint256 _l2BlockNumber,
bytes32 _l1BlockHash,
uint256 _l1BlockNumber
)
external
payable
{
require(msg.sender == proposer, "L2OutputOracle: only the proposer address can propose new outputs");
require(
_l2BlockNumber == nextBlockNumber(),
"L2OutputOracle: block number must be equal to next expected block number"
);
require(
computeL2Timestamp(_l2BlockNumber) < block.timestamp,
"L2OutputOracle: cannot propose L2 output in the future"
);
require(_outputRoot != bytes32(0), "L2OutputOracle: L2 output proposal cannot be the zero hash");
if (_l1BlockHash != bytes32(0)) {
// This check allows the proposer to propose an output based on a given L1 block,
// without fear that it will be reorged out.
// It will also revert if the blockheight provided is more than 256 blocks behind the
// chain tip (as the hash will return as zero). This does open the door to a griefing
// attack in which the proposer's submission is censored until the block is no longer
// retrievable, if the proposer is experiencing this attack it can simply leave out the
// blockhash value, and delay submission until it is confident that the L1 block is
// finalized.
require(
blockhash(_l1BlockNumber) == _l1BlockHash,
"L2OutputOracle: block hash does not match the hash at the expected height"
);
}
emit OutputProposed(_outputRoot, nextOutputIndex(), _l2BlockNumber, block.timestamp);
l2Outputs.push(
Types.OutputProposal({
outputRoot: _outputRoot,
timestamp: uint128(block.timestamp),
l2BlockNumber: uint128(_l2BlockNumber)
})
);
}
/// @notice Returns an output by index. Needed to return a struct instead of a tuple.
/// @param _l2OutputIndex Index of the output to return.
/// @return The output at the given index.
function getL2Output(uint256 _l2OutputIndex) external view returns (Types.OutputProposal memory) {
return l2Outputs[_l2OutputIndex];
}
/// @notice Returns the index of the L2 output that checkpoints a given L2 block number.
/// Uses a binary search to find the first output greater than or equal to the given
/// block.
/// @param _l2BlockNumber L2 block number to find a checkpoint for.
/// @return Index of the first checkpoint that commits to the given L2 block number.
function getL2OutputIndexAfter(uint256 _l2BlockNumber) public view returns (uint256) {
// Make sure an output for this block number has actually been proposed.
require(
_l2BlockNumber <= latestBlockNumber(),
"L2OutputOracle: cannot get output for a block that has not been proposed"
);
// Make sure there's at least one output proposed.
require(l2Outputs.length > 0, "L2OutputOracle: cannot get output as no outputs have been proposed yet");
// Find the output via binary search, guaranteed to exist.
uint256 lo = 0;
uint256 hi = l2Outputs.length;
while (lo < hi) {
uint256 mid = (lo + hi) / 2;
if (l2Outputs[mid].l2BlockNumber < _l2BlockNumber) {
lo = mid + 1;
} else {
hi = mid;
}
}
return lo;
}
/// @notice Returns the L2 output proposal that checkpoints a given L2 block number.
/// Uses a binary search to find the first output greater than or equal to the given
/// block.
/// @param _l2BlockNumber L2 block number to find a checkpoint for.
/// @return First checkpoint that commits to the given L2 block number.
function getL2OutputAfter(uint256 _l2BlockNumber) external view returns (Types.OutputProposal memory) {
return l2Outputs[getL2OutputIndexAfter(_l2BlockNumber)];
}
/// @notice Returns the number of outputs that have been proposed.
/// Will revert if no outputs have been proposed yet.
/// @return The number of outputs that have been proposed.
function latestOutputIndex() external view returns (uint256) {
return l2Outputs.length - 1;
}
/// @notice Returns the index of the next output to be proposed.
/// @return The index of the next output to be proposed.
function nextOutputIndex() public view returns (uint256) {
return l2Outputs.length;
}
/// @notice Returns the block number of the latest submitted L2 output proposal.
/// If no proposals been submitted yet then this function will return the starting
/// block number.
/// @return Latest submitted L2 block number.
function latestBlockNumber() public view returns (uint256) {
return l2Outputs.length == 0 ? startingBlockNumber : l2Outputs[l2Outputs.length - 1].l2BlockNumber;
}
/// @notice Computes the block number of the next L2 block that needs to be checkpointed.
/// @return Next L2 block number.
function nextBlockNumber() public view returns (uint256) {
return latestBlockNumber() + submissionInterval;
}
/// @notice Returns the L2 timestamp corresponding to a given L2 block number.
/// @param _l2BlockNumber The L2 block number of the target block.
/// @return L2 timestamp of the given block.
function computeL2Timestamp(uint256 _l2BlockNumber) public view returns (uint256) {
return startingTimestamp + ((_l2BlockNumber - startingBlockNumber) * l2BlockTime);
}
}
// SPDX-License-Identifier: MIT
pragma solidity 0.8.15;
// Contracts
import { Initializable } from "@openzeppelin/contracts/proxy/utils/Initializable.sol";
import { ResourceMetering } from "src/L1/ResourceMetering.sol";
// Libraries
import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import { SafeCall } from "src/libraries/SafeCall.sol";
import { Constants } from "src/libraries/Constants.sol";
import { Types } from "src/libraries/Types.sol";
import { Hashing } from "src/libraries/Hashing.sol";
import { SecureMerkleTrie } from "src/libraries/trie/SecureMerkleTrie.sol";
import { Predeploys } from "src/libraries/Predeploys.sol";
import { AddressAliasHelper } from "src/vendor/AddressAliasHelper.sol";
import {
BadTarget,
LargeCalldata,
SmallGasLimit,
TransferFailed,
OnlyCustomGasToken,
NoValue,
Unauthorized,
CallPaused,
GasEstimation,
NonReentrant,
Unproven
} from "src/libraries/PortalErrors.sol";
// Interfaces
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import { ISemver } from "interfaces/universal/ISemver.sol";
import { IL2OutputOracle } from "interfaces/L1/IL2OutputOracle.sol";
import { ISystemConfig } from "interfaces/L1/ISystemConfig.sol";
import { IResourceMetering } from "interfaces/L1/IResourceMetering.sol";
import { ISuperchainConfig } from "interfaces/L1/ISuperchainConfig.sol";
import { IL1Block } from "interfaces/L2/IL1Block.sol";
/// @custom:proxied true
/// @title OptimismPortal
/// @notice The OptimismPortal is a low-level contract responsible for passing messages between L1
/// and L2. Messages sent directly to the OptimismPortal have no form of replayability.
/// Users are encouraged to use the L1CrossDomainMessenger for a higher-level interface.
contract OptimismPortal is Initializable, ResourceMetering, ISemver {
/// @notice Allows for interactions with non standard ERC20 tokens.
using SafeERC20 for IERC20;
/// @notice Represents a proven withdrawal.
/// @custom:field outputRoot Root of the L2 output this was proven against.
/// @custom:field timestamp Timestamp at whcih the withdrawal was proven.
/// @custom:field l2OutputIndex Index of the output this was proven against.
struct ProvenWithdrawal {
bytes32 outputRoot;
uint128 timestamp;
uint128 l2OutputIndex;
}
/// @notice Version of the deposit event.
uint256 internal constant DEPOSIT_VERSION = 0;
/// @notice The L2 gas limit set when eth is deposited using the receive() function.
uint64 internal constant RECEIVE_DEFAULT_GAS_LIMIT = 100_000;
/// @notice The L2 gas limit for system deposit transactions that are initiated from L1.
uint32 internal constant SYSTEM_DEPOSIT_GAS_LIMIT = 200_000;
/// @notice Address of the L2 account which initiated a withdrawal in this transaction.
/// If the of this variable is the default L2 sender address, then we are NOT inside of
/// a call to finalizeWithdrawalTransaction.
address public l2Sender;
/// @notice A list of withdrawal hashes which have been successfully finalized.
mapping(bytes32 => bool) public finalizedWithdrawals;
/// @notice A mapping of withdrawal hashes to `ProvenWithdrawal` data.
mapping(bytes32 => ProvenWithdrawal) public provenWithdrawals;
/// @custom:legacy
/// @custom:spacer paused
/// @notice Spacer for backwards compatibility.
bool private spacer_53_0_1;
/// @notice Contract of the Superchain Config.
ISuperchainConfig public superchainConfig;
/// @notice Contract of the L2OutputOracle.
/// @custom:network-specific
IL2OutputOracle public l2Oracle;
/// @notice Contract of the SystemConfig.
/// @custom:network-specific
ISystemConfig public systemConfig;
/// @custom:spacer disputeGameFactory
/// @notice Spacer for backwards compatibility.
address private spacer_56_0_20;
/// @custom:spacer provenWithdrawals
/// @notice Spacer for backwards compatibility.
bytes32 private spacer_57_0_32;
/// @custom:spacer disputeGameBlacklist
/// @notice Spacer for backwards compatibility.
bytes32 private spacer_58_0_32;
/// @custom:spacer respectedGameType + respectedGameTypeUpdatedAt
/// @notice Spacer for backwards compatibility.
bytes32 private spacer_59_0_32;
/// @custom:spacer proofSubmitters
/// @notice Spacer for backwards compatibility.
bytes32 private spacer_60_0_32;
/// @notice Represents the amount of native asset minted in L2. This may not
/// be 100% accurate due to the ability to send ether to the contract
/// without triggering a deposit transaction. It also is used to prevent
/// overflows for L2 account balances when custom gas tokens are used.
/// It is not safe to trust `ERC20.balanceOf` as it may lie.
uint256 internal _balance;
/// @notice Emitted when a transaction is deposited from L1 to L2.
/// The parameters of this event are read by the rollup node and used to derive deposit
/// transactions on L2.
/// @param from Address that triggered the deposit transaction.
/// @param to Address that the deposit transaction is directed to.
/// @param version Version of this deposit transaction event.
/// @param opaqueData ABI encoded deposit data to be parsed off-chain.
event TransactionDeposited(address indexed from, address indexed to, uint256 indexed version, bytes opaqueData);
/// @notice Emitted when a withdrawal transaction is proven.
/// @param withdrawalHash Hash of the withdrawal transaction.
/// @param from Address that triggered the withdrawal transaction.
/// @param to Address that the withdrawal transaction is directed to.
event WithdrawalProven(bytes32 indexed withdrawalHash, address indexed from, address indexed to);
/// @notice Emitted when a withdrawal transaction is finalized.
/// @param withdrawalHash Hash of the withdrawal transaction.
/// @param success Whether the withdrawal transaction was successful.
event WithdrawalFinalized(bytes32 indexed withdrawalHash, bool success);
/// @notice Reverts when paused.
modifier whenNotPaused() {
if (paused()) revert CallPaused();
_;
}
/// @notice Semantic version.
/// @custom:semver 2.8.1-beta.6
function version() public pure virtual returns (string memory) {
return "2.8.1-beta.6";
}
/// @notice Constructs the OptimismPortal contract.
constructor() {
_disableInitializers();
}
/// @notice Initializer.
/// @param _l2Oracle Contract of the L2OutputOracle.
/// @param _systemConfig Contract of the SystemConfig.
/// @param _superchainConfig Contract of the SuperchainConfig.
function initialize(
IL2OutputOracle _l2Oracle,
ISystemConfig _systemConfig,
ISuperchainConfig _superchainConfig
)
external
initializer
{
l2Oracle = _l2Oracle;
systemConfig = _systemConfig;
superchainConfig = _superchainConfig;
if (l2Sender == address(0)) {
l2Sender = Constants.DEFAULT_L2_SENDER;
}
__ResourceMetering_init();
}
/// @notice Getter for the balance of the contract.
function balance() public view returns (uint256) {
(address token,) = gasPayingToken();
if (token == Constants.ETHER) {
return address(this).balance;
} else {
return _balance;
}
}
/// @notice Getter function for the address of the guardian.
/// Public getter is legacy and will be removed in the future. Use `SuperchainConfig.guardian()` instead.
/// @return Address of the guardian.
/// @custom:legacy
function guardian() public view returns (address) {
return superchainConfig.guardian();
}
/// @notice Getter for the current paused status.
/// @return paused_ Whether or not the contract is paused.
function paused() public view returns (bool paused_) {
paused_ = superchainConfig.paused();
}
/// @notice Computes the minimum gas limit for a deposit.
/// The minimum gas limit linearly increases based on the size of the calldata.
/// This is to prevent users from creating L2 resource usage without paying for it.
/// This function can be used when interacting with the portal to ensure forwards
/// compatibility.
/// @param _byteCount Number of bytes in the calldata.
/// @return The minimum gas limit for a deposit.
function minimumGasLimit(uint64 _byteCount) public pure returns (uint64) {
return _byteCount * 16 + 21000;
}
/// @notice Accepts value so that users can send ETH directly to this contract and have the
/// funds be deposited to their address on L2. This is intended as a convenience
/// function for EOAs. Contracts should call the depositTransaction() function directly
/// otherwise any deposited funds will be lost due to address aliasing.
receive() external payable {
depositTransaction(msg.sender, msg.value, RECEIVE_DEFAULT_GAS_LIMIT, false, bytes(""));
}
/// @notice Accepts ETH value without triggering a deposit to L2.
/// This function mainly exists for the sake of the migration between the legacy
/// Optimism system and Bedrock.
function donateETH() external payable {
// Intentionally empty.
}
/// @notice Returns the gas paying token and its decimals.
function gasPayingToken() internal view returns (address addr_, uint8 decimals_) {
(addr_, decimals_) = systemConfig.gasPayingToken();
}
/// @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 (ResourceConfig memory) {
IResourceMetering.ResourceConfig memory config = systemConfig.resourceConfig();
return ResourceConfig({
maxResourceLimit: config.maxResourceLimit,
elasticityMultiplier: config.elasticityMultiplier,
baseFeeMaxChangeDenominator: config.baseFeeMaxChangeDenominator,
minimumBaseFee: config.minimumBaseFee,
systemTxMaxGas: config.systemTxMaxGas,
maximumBaseFee: config.maximumBaseFee
});
}
/// @notice Proves a withdrawal transaction.
/// @param _tx Withdrawal transaction to finalize.
/// @param _l2OutputIndex L2 output index to prove against.
/// @param _outputRootProof Inclusion proof of the L2ToL1MessagePasser contract's storage root.
/// @param _withdrawalProof Inclusion proof of the withdrawal in L2ToL1MessagePasser contract.
function proveWithdrawalTransaction(
Types.WithdrawalTransaction memory _tx,
uint256 _l2OutputIndex,
Types.OutputRootProof calldata _outputRootProof,
bytes[] calldata _withdrawalProof
)
external
whenNotPaused
{
// Prevent users from creating a deposit transaction where this address is the message
// sender on L2. Because this is checked here, we do not need to check again in
// `finalizeWithdrawalTransaction`.
if (_tx.target == address(this)) revert BadTarget();
// Get the output root and load onto the stack to prevent multiple mloads. This will
// revert if there is no output root for the given block number.
bytes32 outputRoot = l2Oracle.getL2Output(_l2OutputIndex).outputRoot;
// Verify that the output root can be generated with the elements in the proof.
require(
outputRoot == Hashing.hashOutputRootProof(_outputRootProof), "OptimismPortal: invalid output root proof"
);
// Load the ProvenWithdrawal into memory, using the withdrawal hash as a unique identifier.
bytes32 withdrawalHash = Hashing.hashWithdrawal(_tx);
ProvenWithdrawal memory provenWithdrawal = provenWithdrawals[withdrawalHash];
// We generally want to prevent users from proving the same withdrawal multiple times
// because each successive proof will update the timestamp. A malicious user can take
// advantage of this to prevent other users from finalizing their withdrawal. However,
// since withdrawals are proven before an output root is finalized, we need to allow users
// to re-prove their withdrawal only in the case that the output root for their specified
// output index has been updated.
require(
provenWithdrawal.timestamp == 0
|| l2Oracle.getL2Output(provenWithdrawal.l2OutputIndex).outputRoot != provenWithdrawal.outputRoot,
"OptimismPortal: withdrawal hash has already been proven"
);
// Compute the storage slot of the withdrawal hash in the L2ToL1MessagePasser contract.
// Refer to the Solidity documentation for more information on how storage layouts are
// computed for mappings.
bytes32 storageKey = keccak256(
abi.encode(
withdrawalHash,
uint256(0) // The withdrawals mapping is at the first slot in the layout.
)
);
// Verify that the hash of this withdrawal was stored in the L2toL1MessagePasser contract
// on L2. If this is true, under the assumption that the SecureMerkleTrie does not have
// bugs, then we know that this withdrawal was actually triggered on L2 and can therefore
// be relayed on L1.
require(
SecureMerkleTrie.verifyInclusionProof({
_key: abi.encode(storageKey),
_value: hex"01",
_proof: _withdrawalProof,
_root: _outputRootProof.messagePasserStorageRoot
}),
"OptimismPortal: invalid withdrawal inclusion proof"
);
// Designate the withdrawalHash as proven by storing the `outputRoot`, `timestamp`, and
// `l2BlockNumber` in the `provenWithdrawals` mapping. A `withdrawalHash` can only be
// proven once unless it is submitted again with a different outputRoot.
provenWithdrawals[withdrawalHash] = ProvenWithdrawal({
outputRoot: outputRoot,
timestamp: uint128(block.timestamp),
l2OutputIndex: uint128(_l2OutputIndex)
});
// Emit a `WithdrawalProven` event.
emit WithdrawalProven(withdrawalHash, _tx.sender, _tx.target);
}
/// @notice Finalizes a withdrawal transaction.
/// @param _tx Withdrawal transaction to finalize.
function finalizeWithdrawalTransaction(Types.WithdrawalTransaction memory _tx) external whenNotPaused {
// Make sure that the l2Sender has not yet been set. The l2Sender is set to a value other
// than the default value when a withdrawal transaction is being finalized. This check is
// a defacto reentrancy guard.
if (l2Sender != Constants.DEFAULT_L2_SENDER) revert NonReentrant();
// Grab the proven withdrawal from the `provenWithdrawals` map.
bytes32 withdrawalHash = Hashing.hashWithdrawal(_tx);
ProvenWithdrawal memory provenWithdrawal = provenWithdrawals[withdrawalHash];
// A withdrawal can only be finalized if it has been proven. We know that a withdrawal has
// been proven at least once when its timestamp is non-zero. Unproven withdrawals will have
// a timestamp of zero.
require(provenWithdrawal.timestamp != 0, "OptimismPortal: withdrawal has not been proven yet");
// As a sanity check, we make sure that the proven withdrawal's timestamp is greater than
// starting timestamp inside the L2OutputOracle. Not strictly necessary but extra layer of
// safety against weird bugs in the proving step.
require(
provenWithdrawal.timestamp >= l2Oracle.startingTimestamp(),
"OptimismPortal: withdrawal timestamp less than L2 Oracle starting timestamp"
);
// A proven withdrawal must wait at least the finalization period before it can be
// finalized. This waiting period can elapse in parallel with the waiting period for the
// output the withdrawal was proven against. In effect, this means that the minimum
// withdrawal time is proposal submission time + finalization period.
require(
_isFinalizationPeriodElapsed(provenWithdrawal.timestamp),
"OptimismPortal: proven withdrawal finalization period has not elapsed"
);
// Grab the OutputProposal from the L2OutputOracle, will revert if the output that
// corresponds to the given index has not been proposed yet.
Types.OutputProposal memory proposal = l2Oracle.getL2Output(provenWithdrawal.l2OutputIndex);
// Check that the output root that was used to prove the withdrawal is the same as the
// current output root for the given output index. An output root may change if it is
// deleted by the challenger address and then re-proposed.
require(
proposal.outputRoot == provenWithdrawal.outputRoot,
"OptimismPortal: output root proven is not the same as current output root"
);
// Check that the output proposal has also been finalized.
require(
_isFinalizationPeriodElapsed(proposal.timestamp),
"OptimismPortal: output proposal finalization period has not elapsed"
);
// Check that this withdrawal has not already been finalized, this is replay protection.
require(finalizedWithdrawals[withdrawalHash] == false, "OptimismPortal: withdrawal has already been finalized");
// Mark the withdrawal as finalized so it can't be replayed.
finalizedWithdrawals[withdrawalHash] = true;
// Set the l2Sender so contracts know who triggered this withdrawal on L2.
// This acts as a reentrancy guard.
l2Sender = _tx.sender;
bool success;
(address token,) = gasPayingToken();
if (token == Constants.ETHER) {
// Trigger the call to the target contract. We use a custom low level method
// SafeCall.callWithMinGas to ensure two key properties
// 1. Target contracts cannot force this call to run out of gas by returning a very large
// amount of data (and this is OK because we don't care about the returndata here).
// 2. The amount of gas provided to the execution context of the target is at least the
// gas limit specified by the user. If there is not enough gas in the current context
// to accomplish this, `callWithMinGas` will revert.
success = SafeCall.callWithMinGas(_tx.target, _tx.gasLimit, _tx.value, _tx.data);
} else {
// Cannot call the token contract directly from the portal. This would allow an attacker
// to call approve from a withdrawal and drain the balance of the portal.
if (_tx.target == token) revert BadTarget();
// Only transfer value when a non zero value is specified. This saves gas in the case of
// using the standard bridge or arbitrary message passing.
if (_tx.value != 0) {
// Update the contracts internal accounting of the amount of native asset in L2.
_balance -= _tx.value;
// Read the balance of the target contract before the transfer so the consistency
// of the transfer can be checked afterwards.
uint256 startBalance = IERC20(token).balanceOf(address(this));
// Transfer the ERC20 balance to the target, accounting for non standard ERC20
// implementations that may not return a boolean. This reverts if the low level
// call is not successful.
IERC20(token).safeTransfer({ to: _tx.target, value: _tx.value });
// The balance must be transferred exactly.
if (IERC20(token).balanceOf(address(this)) != startBalance - _tx.value) {
revert TransferFailed();
}
}
// Make a call to the target contract only if there is calldata.
if (_tx.data.length != 0) {
success = SafeCall.callWithMinGas(_tx.target, _tx.gasLimit, 0, _tx.data);
} else {
success = true;
}
}
// Reset the l2Sender back to the default value.
l2Sender = Constants.DEFAULT_L2_SENDER;
// All withdrawals are immediately finalized. Replayability can
// be achieved through contracts built on top of this contract
emit WithdrawalFinalized(withdrawalHash, success);
// Reverting here is useful for determining the exact gas cost to successfully execute the
// sub call to the target contract if the minimum gas limit specified by the user would not
// be sufficient to execute the sub call.
if (success == false && tx.origin == Constants.ESTIMATION_ADDRESS) {
revert GasEstimation();
}
}
/// @notice Entrypoint to depositing an ERC20 token as a custom gas token.
/// This function depends on a well formed ERC20 token. There are only
/// so many checks that can be done on chain for this so it is assumed
/// that chain operators will deploy chains with well formed ERC20 tokens.
/// @param _to Target address on L2.
/// @param _mint Units of ERC20 token to deposit into L2.
/// @param _value Units of ERC20 token to send on L2 to the recipient.
/// @param _gasLimit Amount of L2 gas to purchase by burning gas on L1.
/// @param _isCreation Whether or not the transaction is a contract creation.
/// @param _data Data to trigger the recipient with.
function depositERC20Transaction(
address _to,
uint256 _mint,
uint256 _value,
uint64 _gasLimit,
bool _isCreation,
bytes memory _data
)
public
metered(_gasLimit)
{
// Can only be called if an ERC20 token is used for gas paying on L2
(address token,) = gasPayingToken();
if (token == Constants.ETHER) revert OnlyCustomGasToken();
// Gives overflow protection for L2 account balances.
_balance += _mint;
// Get the balance of the portal before the transfer.
uint256 startBalance = IERC20(token).balanceOf(address(this));
// Take ownership of the token. It is assumed that the user has given the portal an approval.
IERC20(token).safeTransferFrom({ from: msg.sender, to: address(this), value: _mint });
// Double check that the portal now has the exact amount of token.
if (IERC20(token).balanceOf(address(this)) != startBalance + _mint) {
revert TransferFailed();
}
_depositTransaction({
_to: _to,
_mint: _mint,
_value: _value,
_gasLimit: _gasLimit,
_isCreation: _isCreation,
_data: _data
});
}
/// @notice Accepts deposits of ETH and data, and emits a TransactionDeposited event for use in
/// deriving deposit transactions. Note that if a deposit is made by a contract, its
/// address will be aliased when retrieved using `tx.origin` or `msg.sender`. Consider
/// using the CrossDomainMessenger contracts for a simpler developer experience.
/// @param _to Target address on L2.
/// @param _value ETH value to send to the recipient.
/// @param _gasLimit Amount of L2 gas to purchase by burning gas on L1.
/// @param _isCreation Whether or not the transaction is a contract creation.
/// @param _data Data to trigger the recipient with.
function depositTransaction(
address _to,
uint256 _value,
uint64 _gasLimit,
bool _isCreation,
bytes memory _data
)
public
payable
metered(_gasLimit)
{
(address token,) = gasPayingToken();
if (token != Constants.ETHER && msg.value != 0) revert NoValue();
_depositTransaction({
_to: _to,
_mint: msg.value,
_value: _value,
_gasLimit: _gasLimit,
_isCreation: _isCreation,
_data: _data
});
}
/// @notice Common logic for creating deposit transactions.
/// @param _to Target address on L2.
/// @param _mint Units of asset to deposit into L2.
/// @param _value Units of asset to send on L2 to the recipient.
/// @param _gasLimit Amount of L2 gas to purchase by burning gas on L1.
/// @param _isCreation Whether or not the transaction is a contract creation.
/// @param _data Data to trigger the recipient with.
function _depositTransaction(
address _to,
uint256 _mint,
uint256 _value,
uint64 _gasLimit,
bool _isCreation,
bytes memory _data
)
internal
{
// Just to be safe, make sure that people specify address(0) as the target when doing
// contract creations.
if (_isCreation && _to != address(0)) revert BadTarget();
// Prevent depositing transactions that have too small of a gas limit. Users should pay
// more for more resource usage.
if (_gasLimit < minimumGasLimit(uint64(_data.length))) revert SmallGasLimit();
// Prevent the creation of deposit transactions that have too much calldata. This gives an
// upper limit on the size of unsafe blocks over the p2p network. 120kb is chosen to ensure
// that the transaction can fit into the p2p network policy of 128kb even though deposit
// transactions are not gossipped over the p2p network.
if (_data.length > 120_000) revert LargeCalldata();
// Transform the from-address to its alias if the caller is a contract.
address from = msg.sender;
if (msg.sender != tx.origin) {
from = AddressAliasHelper.applyL1ToL2Alias(msg.sender);
}
// Compute the opaque data that will be emitted as part of the TransactionDeposited event.
// We use opaque data so that we can update the TransactionDeposited event in the future
// without breaking the current interface.
bytes memory opaqueData = abi.encodePacked(_mint, _value, _gasLimit, _isCreation, _data);
// Emit a TransactionDeposited event so that the rollup node can derive a deposit
// transaction for this deposit.
emit TransactionDeposited(from, _to, DEPOSIT_VERSION, opaqueData);
}
/// @notice Sets the gas paying token for the L2 system. This token is used as the
/// L2 native asset. Only the SystemConfig contract can call this function.
function setGasPayingToken(address _token, uint8 _decimals, bytes32 _name, bytes32 _symbol) external {
if (msg.sender != address(systemConfig)) revert Unauthorized();
// Set L2 deposit gas as used without paying burning gas. Ensures that deposits cannot use too much L2 gas.
// This value must be large enough to cover the cost of calling `L1Block.setGasPayingToken`.
useGas(SYSTEM_DEPOSIT_GAS_LIMIT);
// Emit the special deposit transaction directly that sets the gas paying
// token in the L1Block predeploy contract.
emit TransactionDeposited(
Constants.DEPOSITOR_ACCOUNT,
Predeploys.L1_BLOCK_ATTRIBUTES,
DEPOSIT_VERSION,
abi.encodePacked(
uint256(0), // mint
uint256(0), // value
uint64(SYSTEM_DEPOSIT_GAS_LIMIT), // gasLimit
false, // isCreation,
abi.encodeCall(IL1Block.setGasPayingToken, (_token, _decimals, _name, _symbol))
)
);
}
/// @notice Determine if a given output is finalized.
/// Reverts if the call to l2Oracle.getL2Output reverts.
/// Returns a boolean otherwise.
/// @param _l2OutputIndex Index of the L2 output to check.
/// @return Whether or not the output is finalized.
function isOutputFinalized(uint256 _l2OutputIndex) external view returns (bool) {
return _isFinalizationPeriodElapsed(l2Oracle.getL2Output(_l2OutputIndex).timestamp);
}
/// @notice Determines whether the finalization period has elapsed with respect to
/// the provided block timestamp.
/// @param _timestamp Timestamp to check.
/// @return Whether or not the finalization period has elapsed.
function _isFinalizationPeriodElapsed(uint256 _timestamp) internal view returns (bool) {
return block.timestamp > _timestamp + l2Oracle.FINALIZATION_PERIOD_SECONDS();
}
}
......@@ -12,7 +12,7 @@ import { GasPayingToken, IGasToken } from "src/libraries/GasPayingToken.sol";
// Interfaces
import { ISemver } from "interfaces/universal/ISemver.sol";
import { IOptimismPortal } from "interfaces/L1/IOptimismPortal.sol";
import { IOptimismPortal2 } from "interfaces/L1/IOptimismPortal2.sol";
import { IResourceMetering } from "interfaces/L1/IResourceMetering.sol";
/// @custom:proxied true
......@@ -137,9 +137,9 @@ contract SystemConfig is OwnableUpgradeable, ISemver, IGasToken {
event ConfigUpdate(uint256 indexed version, UpdateType indexed updateType, bytes data);
/// @notice Semantic version.
/// @custom:semver 2.3.0-beta.8
/// @custom:semver 2.3.0-beta.9
function version() public pure virtual returns (string memory) {
return "2.3.0-beta.8";
return "2.3.0-beta.9";
}
/// @notice Constructs the SystemConfig contract.
......@@ -302,7 +302,7 @@ contract SystemConfig is OwnableUpgradeable, ISemver, IGasToken {
// Set the gas paying token in storage and in the OptimismPortal.
GasPayingToken.set({ _token: _token, _decimals: GAS_PAYING_TOKEN_DECIMALS, _name: name, _symbol: symbol });
IOptimismPortal(payable(optimismPortal())).setGasPayingToken({
IOptimismPortal2(payable(optimismPortal())).setGasPayingToken({
_token: _token,
_decimals: GAS_PAYING_TOKEN_DECIMALS,
_name: name,
......
......@@ -68,9 +68,9 @@ contract SystemConfigInterop is SystemConfig {
Storage.setAddress(DEPENDENCY_MANAGER_SLOT, _dependencyManager);
}
/// @custom:semver +interop-beta.7
/// @custom:semver +interop-beta.8
function version() public pure override returns (string memory) {
return string.concat(super.version(), "+interop-beta.7");
return string.concat(super.version(), "+interop-beta.8");
}
/// @notice Internal setter for the gas paying token address, includes validation.
......
......@@ -14,7 +14,7 @@ import { Encoding } from "src/libraries/Encoding.sol";
// Target contract dependencies
import { IL1CrossDomainMessenger } from "interfaces/L1/IL1CrossDomainMessenger.sol";
import { IOptimismPortal } from "interfaces/L1/IOptimismPortal.sol";
import { IOptimismPortal2 } from "interfaces/L1/IOptimismPortal2.sol";
import { ISuperchainConfig } from "interfaces/L1/ISuperchainConfig.sol";
import { ISystemConfig } from "interfaces/L1/ISystemConfig.sol";
......@@ -43,8 +43,8 @@ contract L1CrossDomainMessenger_Test is CommonTest {
/// @dev Tests that the proxy is initialized correctly.
function test_initialize_succeeds() external view {
assertEq(address(l1CrossDomainMessenger.superchainConfig()), address(superchainConfig));
assertEq(address(l1CrossDomainMessenger.PORTAL()), address(optimismPortal));
assertEq(address(l1CrossDomainMessenger.portal()), address(optimismPortal));
assertEq(address(l1CrossDomainMessenger.PORTAL()), address(optimismPortal2));
assertEq(address(l1CrossDomainMessenger.portal()), address(optimismPortal2));
assertEq(address(l1CrossDomainMessenger.OTHER_MESSENGER()), Predeploys.L2_CROSS_DOMAIN_MESSENGER);
assertEq(address(l1CrossDomainMessenger.otherMessenger()), Predeploys.L2_CROSS_DOMAIN_MESSENGER);
}
......@@ -61,9 +61,9 @@ contract L1CrossDomainMessenger_Test is CommonTest {
function test_sendMessage_succeeds() external {
// deposit transaction on the optimism portal should be called
vm.expectCall(
address(optimismPortal),
address(optimismPortal2),
abi.encodeCall(
IOptimismPortal.depositTransaction,
IOptimismPortal2.depositTransaction,
(
Predeploys.L2_CROSS_DOMAIN_MESSENGER,
0,
......@@ -77,7 +77,7 @@ contract L1CrossDomainMessenger_Test is CommonTest {
);
// TransactionDeposited event
vm.expectEmit(address(optimismPortal));
vm.expectEmit(address(optimismPortal2));
emitTransactionDeposited(
AddressAliasHelper.applyL1ToL2Alias(address(l1CrossDomainMessenger)),
Predeploys.L2_CROSS_DOMAIN_MESSENGER,
......@@ -125,13 +125,13 @@ contract L1CrossDomainMessenger_Test is CommonTest {
address sender = Predeploys.L2_CROSS_DOMAIN_MESSENGER;
// Set the value of op.l2Sender() to be the L2 Cross Domain Messenger.
vm.store(address(optimismPortal), bytes32(senderSlotIndex), bytes32(abi.encode(sender)));
vm.store(address(optimismPortal2), bytes32(senderSlotIndex), bytes32(abi.encode(sender)));
// Expect a revert.
vm.expectRevert("CrossDomainMessenger: only version 0 or 1 messages are supported at this time");
// Try to relay a v2 message.
vm.prank(address(optimismPortal));
vm.prank(address(optimismPortal2));
l1CrossDomainMessenger.relayMessage(
Encoding.encodeVersionedNonce({ _nonce: 0, _version: 2 }), // nonce
sender,
......@@ -151,8 +151,8 @@ contract L1CrossDomainMessenger_Test is CommonTest {
vm.expectCall(target, hex"1111");
// set the value of op.l2Sender() to be the L2 Cross Domain Messenger.
vm.store(address(optimismPortal), bytes32(senderSlotIndex), bytes32(abi.encode(sender)));
vm.prank(address(optimismPortal));
vm.store(address(optimismPortal2), bytes32(senderSlotIndex), bytes32(abi.encode(sender)));
vm.prank(address(optimismPortal2));
vm.expectEmit(address(l1CrossDomainMessenger));
......@@ -177,42 +177,42 @@ contract L1CrossDomainMessenger_Test is CommonTest {
assertEq(l1CrossDomainMessenger.failedMessages(hash), false);
}
/// @dev Tests that relayMessage reverts if caller is optimismPortal and the value sent does not match the amount
/// @dev Tests that relayMessage reverts if caller is optimismPortal2 and the value sent does not match the amount
function test_relayMessage_fromOtherMessengerValueMismatch_reverts() external {
address target = alice;
address sender = Predeploys.L2_CROSS_DOMAIN_MESSENGER;
bytes memory message = hex"1111";
// set the value of op.l2Sender() to be the L2CrossDomainMessenger.
vm.store(address(optimismPortal), bytes32(senderSlotIndex), bytes32(abi.encode(sender)));
vm.store(address(optimismPortal2), bytes32(senderSlotIndex), bytes32(abi.encode(sender)));
// correctly sending as OptimismPortal but amount does not match msg.value
vm.deal(address(optimismPortal), 10 ether);
vm.prank(address(optimismPortal));
vm.deal(address(optimismPortal2), 10 ether);
vm.prank(address(optimismPortal2));
vm.expectRevert(stdError.assertionError);
l1CrossDomainMessenger.relayMessage{ value: 10 ether }(
Encoding.encodeVersionedNonce({ _nonce: 0, _version: 1 }), sender, target, 9 ether, 0, message
);
}
/// @dev Tests that relayMessage reverts if a failed message is attempted to be replayed via the optimismPortal
/// @dev Tests that relayMessage reverts if a failed message is attempted to be replayed via the optimismPortal2
function test_relayMessage_fromOtherMessengerFailedMessageReplay_reverts() external {
address target = alice;
address sender = Predeploys.L2_CROSS_DOMAIN_MESSENGER;
bytes memory message = hex"1111";
// set the value of op.l2Sender() to be the L2 Cross Domain Messenger.
vm.store(address(optimismPortal), bytes32(senderSlotIndex), bytes32(abi.encode(sender)));
vm.store(address(optimismPortal2), bytes32(senderSlotIndex), bytes32(abi.encode(sender)));
// make a failed message
vm.etch(target, hex"fe");
vm.prank(address(optimismPortal));
vm.prank(address(optimismPortal2));
l1CrossDomainMessenger.relayMessage(
Encoding.encodeVersionedNonce({ _nonce: 0, _version: 1 }), sender, target, 0, 0, message
);
// cannot replay messages when optimism portal is msg.sender
vm.prank(address(optimismPortal));
vm.prank(address(optimismPortal2));
vm.expectRevert(stdError.assertionError);
l1CrossDomainMessenger.relayMessage(
Encoding.encodeVersionedNonce({ _nonce: 0, _version: 1 }), sender, target, 0, 0, message
......@@ -225,9 +225,9 @@ contract L1CrossDomainMessenger_Test is CommonTest {
address sender = Predeploys.L2_CROSS_DOMAIN_MESSENGER;
bytes memory message = hex"1111";
vm.store(address(optimismPortal), bytes32(senderSlotIndex), bytes32(abi.encode(sender)));
vm.store(address(optimismPortal2), bytes32(senderSlotIndex), bytes32(abi.encode(sender)));
vm.prank(address(optimismPortal));
vm.prank(address(optimismPortal2));
vm.expectRevert("CrossDomainMessenger: cannot send message to blocked system address");
l1CrossDomainMessenger.relayMessage(
Encoding.encodeVersionedNonce({ _nonce: 0, _version: 1 }),
......@@ -245,23 +245,23 @@ contract L1CrossDomainMessenger_Test is CommonTest {
address sender = Predeploys.L2_CROSS_DOMAIN_MESSENGER;
bytes memory message = hex"1111";
vm.store(address(optimismPortal), bytes32(senderSlotIndex), bytes32(abi.encode(sender)));
vm.store(address(optimismPortal2), bytes32(senderSlotIndex), bytes32(abi.encode(sender)));
vm.prank(address(optimismPortal));
vm.prank(address(optimismPortal2));
vm.expectRevert("CrossDomainMessenger: cannot send message to blocked system address");
l1CrossDomainMessenger.relayMessage(
Encoding.encodeVersionedNonce({ _nonce: 0, _version: 1 }), sender, address(optimismPortal), 0, 0, message
Encoding.encodeVersionedNonce({ _nonce: 0, _version: 1 }), sender, address(optimismPortal2), 0, 0, message
);
}
/// @dev Tests that the relayMessage function reverts if the message called by non-optimismPortal but not a failed
/// @dev Tests that the relayMessage function reverts if the message called by non-optimismPortal2 but not a failed
/// message
function test_relayMessage_relayingNewMessageByExternalUser_reverts() external {
address target = address(alice);
address sender = Predeploys.L2_CROSS_DOMAIN_MESSENGER;
bytes memory message = hex"1111";
vm.store(address(optimismPortal), bytes32(senderSlotIndex), bytes32(abi.encode(sender)));
vm.store(address(optimismPortal2), bytes32(senderSlotIndex), bytes32(abi.encode(sender)));
vm.prank(bob);
vm.expectRevert("CrossDomainMessenger: message cannot be replayed");
......@@ -291,8 +291,8 @@ contract L1CrossDomainMessenger_Test is CommonTest {
address sender = Predeploys.L2_CROSS_DOMAIN_MESSENGER;
vm.store(address(optimismPortal), bytes32(senderSlotIndex), bytes32(abi.encode(sender)));
vm.prank(address(optimismPortal));
vm.store(address(optimismPortal2), bytes32(senderSlotIndex), bytes32(abi.encode(sender)));
vm.prank(address(optimismPortal2));
l1CrossDomainMessenger.relayMessage(
Encoding.encodeVersionedNonce({ _nonce: 0, _version: 1 }), address(0), address(0), 0, 0, hex""
);
......@@ -315,10 +315,10 @@ contract L1CrossDomainMessenger_Test is CommonTest {
Encoding.encodeVersionedNonce({ _nonce: 0, _version: 1 }), sender, target, value, 0, hex"1111"
);
vm.store(address(optimismPortal), bytes32(senderSlotIndex), bytes32(abi.encode(sender)));
vm.store(address(optimismPortal2), bytes32(senderSlotIndex), bytes32(abi.encode(sender)));
vm.etch(target, address(new Reverter()).code);
vm.deal(address(optimismPortal), value);
vm.prank(address(optimismPortal));
vm.deal(address(optimismPortal2), value);
vm.prank(address(optimismPortal2));
l1CrossDomainMessenger.relayMessage{ value: value }(
Encoding.encodeVersionedNonce({ _nonce: 0, _version: 1 }), // nonce
sender,
......@@ -373,7 +373,7 @@ contract L1CrossDomainMessenger_Test is CommonTest {
);
// Set the value of op.l2Sender() to be the L2 Cross Domain Messenger.
vm.store(address(optimismPortal), bytes32(senderSlotIndex), bytes32(abi.encode(sender)));
vm.store(address(optimismPortal2), bytes32(senderSlotIndex), bytes32(abi.encode(sender)));
// Target should be called with expected data.
vm.expectCall(target, hex"1111");
......@@ -383,7 +383,7 @@ contract L1CrossDomainMessenger_Test is CommonTest {
emit RelayedMessage(hash);
// Relay the message.
vm.prank(address(optimismPortal));
vm.prank(address(optimismPortal2));
l1CrossDomainMessenger.relayMessage(
Encoding.encodeVersionedNonce({ _nonce: 0, _version: 0 }), // nonce
sender,
......@@ -415,7 +415,7 @@ contract L1CrossDomainMessenger_Test is CommonTest {
);
// Set the value of op.l2Sender() to be the L2 Cross Domain Messenger.
vm.store(address(optimismPortal), bytes32(senderSlotIndex), bytes32(abi.encode(sender)));
vm.store(address(optimismPortal2), bytes32(senderSlotIndex), bytes32(abi.encode(sender)));
// Mark legacy message as already relayed.
uint256 successfulMessagesSlot = 203;
bytes32 oldHash = Hashing.hashCrossDomainMessageV0(target, sender, hex"1111", 0);
......@@ -426,7 +426,7 @@ contract L1CrossDomainMessenger_Test is CommonTest {
vm.expectRevert("CrossDomainMessenger: legacy withdrawal already relayed");
// Relay the message.
vm.prank(address(optimismPortal));
vm.prank(address(optimismPortal2));
l1CrossDomainMessenger.relayMessage(
Encoding.encodeVersionedNonce({ _nonce: 0, _version: 0 }), // nonce
sender,
......@@ -459,7 +459,7 @@ contract L1CrossDomainMessenger_Test is CommonTest {
);
// Set the value of op.l2Sender() to be the L2 Cross Domain Messenger.
vm.store(address(optimismPortal), bytes32(senderSlotIndex), bytes32(abi.encode(sender)));
vm.store(address(optimismPortal2), bytes32(senderSlotIndex), bytes32(abi.encode(sender)));
// Turn the target into a Reverter.
vm.etch(target, address(new Reverter()).code);
......@@ -472,8 +472,8 @@ contract L1CrossDomainMessenger_Test is CommonTest {
emit FailedRelayedMessage(hash);
// Relay the message.
vm.deal(address(optimismPortal), value);
vm.prank(address(optimismPortal));
vm.deal(address(optimismPortal2), value);
vm.prank(address(optimismPortal2));
l1CrossDomainMessenger.relayMessage{ value: value }(
Encoding.encodeVersionedNonce({ _nonce: 0, _version: 0 }), // nonce
sender,
......@@ -535,7 +535,7 @@ contract L1CrossDomainMessenger_Test is CommonTest {
);
// Set the value of op.l2Sender() to be the L2 Cross Domain Messenger.
vm.store(address(optimismPortal), bytes32(senderSlotIndex), bytes32(abi.encode(sender)));
vm.store(address(optimismPortal2), bytes32(senderSlotIndex), bytes32(abi.encode(sender)));
// Target should be called with expected data.
vm.expectCall(target, hex"1111");
......@@ -545,8 +545,8 @@ contract L1CrossDomainMessenger_Test is CommonTest {
emit RelayedMessage(hash);
// Relay the message.
vm.deal(address(optimismPortal), value);
vm.prank(address(optimismPortal));
vm.deal(address(optimismPortal2), value);
vm.prank(address(optimismPortal2));
l1CrossDomainMessenger.relayMessage{ value: value }(
Encoding.encodeVersionedNonce({ _nonce: 0, _version: 0 }), // nonce
sender,
......@@ -595,7 +595,7 @@ contract L1CrossDomainMessenger_Test is CommonTest {
);
// Set the value of op.l2Sender() to be the L2 Cross Domain Messenger.
vm.store(address(optimismPortal), bytes32(senderSlotIndex), bytes32(abi.encode(sender)));
vm.store(address(optimismPortal2), bytes32(senderSlotIndex), bytes32(abi.encode(sender)));
// Turn the target into a Reverter.
vm.etch(target, address(new Reverter()).code);
......@@ -604,8 +604,8 @@ contract L1CrossDomainMessenger_Test is CommonTest {
vm.expectCall(target, hex"1111");
// Relay the message.
vm.deal(address(optimismPortal), value);
vm.prank(address(optimismPortal));
vm.deal(address(optimismPortal2), value);
vm.prank(address(optimismPortal2));
l1CrossDomainMessenger.relayMessage{ value: value }(
Encoding.encodeVersionedNonce({ _nonce: 0, _version: 0 }), // nonce
sender,
......@@ -707,9 +707,9 @@ contract L1CrossDomainMessenger_Test is CommonTest {
// deposit transaction on the optimism portal should be called
vm.expectCall(
address(optimismPortal),
address(optimismPortal2),
abi.encodeCall(
IOptimismPortal.depositTransaction,
IOptimismPortal2.depositTransaction,
(
Predeploys.L2_CROSS_DOMAIN_MESSENGER,
0,
......@@ -723,7 +723,7 @@ contract L1CrossDomainMessenger_Test is CommonTest {
);
// TransactionDeposited event
vm.expectEmit(address(optimismPortal));
vm.expectEmit(address(optimismPortal2));
emitTransactionDeposited(
AddressAliasHelper.applyL1ToL2Alias(address(l1CrossDomainMessenger)),
Predeploys.L2_CROSS_DOMAIN_MESSENGER,
......@@ -776,8 +776,8 @@ contract L1CrossDomainMessenger_Test is CommonTest {
vm.expectCall(target, hex"1111");
// set the value of op.l2Sender() to be the L2 Cross Domain Messenger.
vm.store(address(optimismPortal), bytes32(senderSlotIndex), bytes32(abi.encode(sender)));
vm.prank(address(optimismPortal));
vm.store(address(optimismPortal2), bytes32(senderSlotIndex), bytes32(abi.encode(sender)));
vm.prank(address(optimismPortal2));
vm.expectEmit(address(l1CrossDomainMessenger));
......@@ -855,7 +855,7 @@ contract L1CrossDomainMessenger_ReinitReentryTest is CommonTest {
// call the initializer function
l1CrossDomainMessenger.initialize(
ISuperchainConfig(superchainConfig), IOptimismPortal(optimismPortal), ISystemConfig(systemConfig)
ISuperchainConfig(superchainConfig), IOptimismPortal2(optimismPortal2), ISystemConfig(systemConfig)
);
// attempt to re-replay the withdrawal
......
......@@ -16,7 +16,7 @@ import { AddressAliasHelper } from "src/vendor/AddressAliasHelper.sol";
// Interfaces
import { ICrossDomainMessenger } from "interfaces/universal/ICrossDomainMessenger.sol";
import { ISuperchainConfig } from "interfaces/L1/ISuperchainConfig.sol";
import { IOptimismPortal } from "interfaces/L1/IOptimismPortal.sol";
import { IOptimismPortal2 } from "interfaces/L1/IOptimismPortal2.sol";
import { IL1StandardBridge } from "interfaces/L1/IL1StandardBridge.sol";
contract L1StandardBridge_Getter_Test is CommonTest {
......@@ -169,7 +169,7 @@ contract L1StandardBridge_Initialize_TestFail is CommonTest { }
contract L1StandardBridge_Receive_Test is CommonTest {
/// @dev Tests receive bridges ETH successfully.
function test_receive_succeeds() external {
uint256 balanceBefore = address(optimismPortal).balance;
uint256 balanceBefore = address(optimismPortal2).balance;
// The legacy event must be emitted for backwards compatibility
vm.expectEmit(address(l1StandardBridge));
......@@ -193,7 +193,7 @@ contract L1StandardBridge_Receive_Test is CommonTest {
vm.prank(alice, alice);
(bool success,) = address(l1StandardBridge).call{ value: 100 }(hex"");
assertEq(success, true);
assertEq(address(optimismPortal).balance, balanceBefore + 100);
assertEq(address(optimismPortal2).balance, balanceBefore + 100);
}
}
......@@ -222,7 +222,7 @@ contract PreBridgeETH is CommonTest {
/// on whether the bridge call is legacy or not.
function _preBridgeETH(bool isLegacy, uint256 value) internal {
if (!isForkTest()) {
assertEq(address(optimismPortal).balance, 0);
assertEq(address(optimismPortal2).balance, 0);
}
uint256 nonce = l1CrossDomainMessenger.messageNonce();
uint256 version = 0; // Internal constant in the OptimismPortal: DEPOSIT_VERSION
......@@ -252,10 +252,10 @@ contract PreBridgeETH is CommonTest {
uint64 baseGas = l1CrossDomainMessenger.baseGas(message, 50000);
vm.expectCall(
address(optimismPortal),
address(optimismPortal2),
value,
abi.encodeCall(
IOptimismPortal.depositTransaction,
IOptimismPortal2.depositTransaction,
(address(l2CrossDomainMessenger), value, baseGas, false, innerMessage)
)
);
......@@ -269,7 +269,7 @@ contract PreBridgeETH is CommonTest {
emit ETHBridgeInitiated(alice, alice, value, hex"dead");
// OptimismPortal emits a TransactionDeposited event on `depositTransaction` call
vm.expectEmit(address(optimismPortal));
vm.expectEmit(address(optimismPortal2));
emit TransactionDeposited(l1MessengerAliased, address(l2CrossDomainMessenger), version, opaqueData);
// SentMessage event emitted by the CrossDomainMessenger
......@@ -292,9 +292,9 @@ contract L1StandardBridge_DepositETH_Test is PreBridgeETH {
/// ETH ends up in the optimismPortal.
function test_depositETH_succeeds() external {
_preBridgeETH({ isLegacy: true, value: 500 });
uint256 balanceBefore = address(optimismPortal).balance;
uint256 balanceBefore = address(optimismPortal2).balance;
l1StandardBridge.depositETH{ value: 500 }(50000, hex"dead");
assertEq(address(optimismPortal).balance, balanceBefore + 500);
assertEq(address(optimismPortal2).balance, balanceBefore + 500);
}
}
......@@ -329,9 +329,9 @@ contract L1StandardBridge_BridgeETH_Test is PreBridgeETH {
/// ETH ends up in the optimismPortal.
function test_bridgeETH_succeeds() external {
_preBridgeETH({ isLegacy: false, value: 500 });
uint256 balanceBefore = address(optimismPortal).balance;
uint256 balanceBefore = address(optimismPortal2).balance;
l1StandardBridge.bridgeETH{ value: 500 }(50000, hex"dead");
assertEq(address(optimismPortal).balance, balanceBefore + 500);
assertEq(address(optimismPortal2).balance, balanceBefore + 500);
}
}
......@@ -385,9 +385,9 @@ contract PreBridgeETHTo is CommonTest {
uint64 baseGas = l1CrossDomainMessenger.baseGas(message, 60000);
vm.expectCall(
address(optimismPortal),
address(optimismPortal2),
abi.encodeCall(
IOptimismPortal.depositTransaction,
IOptimismPortal2.depositTransaction,
(address(l2CrossDomainMessenger), value, baseGas, false, innerMessage)
)
);
......@@ -401,7 +401,7 @@ contract PreBridgeETHTo is CommonTest {
emit ETHBridgeInitiated(alice, bob, value, hex"dead");
// OptimismPortal emits a TransactionDeposited event on `depositTransaction` call
vm.expectEmit(address(optimismPortal));
vm.expectEmit(address(optimismPortal2));
emit TransactionDeposited(l1MessengerAliased, address(l2CrossDomainMessenger), version, opaqueData);
// SentMessage event emitted by the CrossDomainMessenger
......@@ -425,9 +425,9 @@ contract L1StandardBridge_DepositETHTo_Test is PreBridgeETHTo {
/// ETH ends up in the optimismPortal.
function test_depositETHTo_succeeds() external {
_preBridgeETHTo({ isLegacy: true, value: 600 });
uint256 balanceBefore = address(optimismPortal).balance;
uint256 balanceBefore = address(optimismPortal2).balance;
l1StandardBridge.depositETHTo{ value: 600 }(bob, 60000, hex"dead");
assertEq(address(optimismPortal).balance, balanceBefore + 600);
assertEq(address(optimismPortal2).balance, balanceBefore + 600);
}
}
......@@ -462,9 +462,9 @@ contract L1StandardBridge_BridgeETHTo_Test is PreBridgeETHTo {
/// ETH ends up in the optimismPortal.
function test_bridgeETHTo_succeeds() external {
_preBridgeETHTo({ isLegacy: false, value: 600 });
uint256 balanceBefore = address(optimismPortal).balance;
uint256 balanceBefore = address(optimismPortal2).balance;
l1StandardBridge.bridgeETHTo{ value: 600 }(bob, 60000, hex"dead");
assertEq(address(optimismPortal).balance, balanceBefore + 600);
assertEq(address(optimismPortal2).balance, balanceBefore + 600);
}
}
......@@ -534,9 +534,9 @@ contract L1StandardBridge_DepositERC20_Test is CommonTest {
uint64 baseGas = l1CrossDomainMessenger.baseGas(message, 10000);
vm.expectCall(
address(optimismPortal),
address(optimismPortal2),
abi.encodeCall(
IOptimismPortal.depositTransaction, (address(l2CrossDomainMessenger), 0, baseGas, false, innerMessage)
IOptimismPortal2.depositTransaction, (address(l2CrossDomainMessenger), 0, baseGas, false, innerMessage)
)
);
......@@ -550,7 +550,7 @@ contract L1StandardBridge_DepositERC20_Test is CommonTest {
emit ERC20BridgeInitiated(address(L1Token), address(L2Token), alice, alice, 100, hex"");
// OptimismPortal emits a TransactionDeposited event on `depositTransaction` call
vm.expectEmit(address(optimismPortal));
vm.expectEmit(address(optimismPortal2));
emit TransactionDeposited(l1MessengerAliased, address(l2CrossDomainMessenger), version, opaqueData);
// SentMessage event emitted by the CrossDomainMessenger
......@@ -617,7 +617,7 @@ contract L1StandardBridge_DepositERC20To_Test is CommonTest {
emit ERC20BridgeInitiated(address(L1Token), address(L2Token), alice, bob, 1000, hex"");
// OptimismPortal emits a TransactionDeposited event on `depositTransaction` call
vm.expectEmit(address(optimismPortal));
vm.expectEmit(address(optimismPortal2));
emit TransactionDeposited(l1MessengerAliased, address(l2CrossDomainMessenger), version, opaqueData);
// SentMessage event emitted by the CrossDomainMessenger
......@@ -635,9 +635,9 @@ contract L1StandardBridge_DepositERC20To_Test is CommonTest {
);
// The L1 XDM should call OptimismPortal.depositTransaction
vm.expectCall(
address(optimismPortal),
address(optimismPortal2),
abi.encodeCall(
IOptimismPortal.depositTransaction, (address(l2CrossDomainMessenger), 0, baseGas, false, innerMessage)
IOptimismPortal2.depositTransaction, (address(l2CrossDomainMessenger), 0, baseGas, false, innerMessage)
)
);
vm.expectCall(address(L1Token), abi.encodeCall(ERC20.transferFrom, (alice, address(l1StandardBridge), 1000)));
......
// SPDX-License-Identifier: MIT
pragma solidity 0.8.15;
// Testing utilities
import { stdError } from "forge-std/Test.sol";
import { CommonTest } from "test/setup/CommonTest.sol";
import { NextImpl } from "test/mocks/NextImpl.sol";
import { EIP1967Helper } from "test/mocks/EIP1967Helper.sol";
// Libraries
import { Types } from "src/libraries/Types.sol";
// Target contract dependencies
import { Proxy } from "src/universal/Proxy.sol";
// Target contract
import { L2OutputOracle } from "src/L1/L2OutputOracle.sol";
import { IL2OutputOracle } from "interfaces/L1/IL2OutputOracle.sol";
contract L2OutputOracle_TestBase is CommonTest {
function setUp() public override {
super.enableLegacyContracts();
super.setUp();
}
/// @dev Helper function to propose an output.
function proposeAnotherOutput() public {
bytes32 proposedOutput2 = keccak256(abi.encode());
uint256 nextBlockNumber = l2OutputOracle.nextBlockNumber();
uint256 nextOutputIndex = l2OutputOracle.nextOutputIndex();
warpToProposeTime(nextBlockNumber);
uint256 proposedNumber = l2OutputOracle.latestBlockNumber();
uint256 submissionInterval = deploy.cfg().l2OutputOracleSubmissionInterval();
// Ensure the submissionInterval is enforced
assertEq(nextBlockNumber, proposedNumber + submissionInterval);
vm.roll(nextBlockNumber + 1);
vm.expectEmit(true, true, true, true);
emit OutputProposed(proposedOutput2, nextOutputIndex, nextBlockNumber, block.timestamp);
address proposer = deploy.cfg().l2OutputOracleProposer();
vm.prank(proposer);
l2OutputOracle.proposeL2Output(proposedOutput2, nextBlockNumber, 0, 0);
}
/// @dev Tests that constructor sets the initial values correctly.
function test_constructor_succeeds() external {
IL2OutputOracle oracleImpl = IL2OutputOracle(address(new L2OutputOracle()));
assertEq(oracleImpl.SUBMISSION_INTERVAL(), 0);
assertEq(oracleImpl.submissionInterval(), 0);
assertEq(oracleImpl.L2_BLOCK_TIME(), 0);
assertEq(oracleImpl.l2BlockTime(), 0);
assertEq(oracleImpl.latestBlockNumber(), 0);
assertEq(oracleImpl.startingBlockNumber(), 0);
assertEq(oracleImpl.startingTimestamp(), 0);
assertEq(oracleImpl.PROPOSER(), address(0));
assertEq(oracleImpl.proposer(), address(0));
assertEq(oracleImpl.CHALLENGER(), address(0));
assertEq(oracleImpl.challenger(), address(0));
assertEq(oracleImpl.finalizationPeriodSeconds(), 0);
assertEq(oracleImpl.FINALIZATION_PERIOD_SECONDS(), 0);
}
/// @dev Tests that the proxy is initialized with the correct values.
function test_initialize_succeeds() external {
address proposer = deploy.cfg().l2OutputOracleProposer();
address challenger = deploy.cfg().l2OutputOracleChallenger();
uint256 submissionInterval = deploy.cfg().l2OutputOracleSubmissionInterval();
uint256 startingBlockNumber = deploy.cfg().l2OutputOracleStartingBlockNumber();
uint256 startingTimestamp = deploy.cfg().l2OutputOracleStartingTimestamp();
uint256 l2BlockTime = deploy.cfg().l2BlockTime();
uint256 finalizationPeriodSeconds = deploy.cfg().finalizationPeriodSeconds();
assertEq(l2OutputOracle.SUBMISSION_INTERVAL(), submissionInterval);
assertEq(l2OutputOracle.submissionInterval(), submissionInterval);
assertEq(l2OutputOracle.L2_BLOCK_TIME(), l2BlockTime);
assertEq(l2OutputOracle.l2BlockTime(), l2BlockTime);
assertEq(l2OutputOracle.latestBlockNumber(), startingBlockNumber);
assertEq(l2OutputOracle.startingBlockNumber(), startingBlockNumber);
assertEq(l2OutputOracle.startingTimestamp(), startingTimestamp);
assertEq(l2OutputOracle.finalizationPeriodSeconds(), finalizationPeriodSeconds);
assertEq(l2OutputOracle.PROPOSER(), proposer);
assertEq(l2OutputOracle.proposer(), proposer);
assertEq(l2OutputOracle.CHALLENGER(), challenger);
assertEq(l2OutputOracle.FINALIZATION_PERIOD_SECONDS(), finalizationPeriodSeconds);
assertEq(l2OutputOracle.challenger(), challenger);
}
}
contract L2OutputOracle_getter_Test is L2OutputOracle_TestBase {
bytes32 proposedOutput1 = keccak256(abi.encode(1));
/// @dev Tests that `latestBlockNumber` returns the correct value.
function test_latestBlockNumber_succeeds() external {
uint256 proposedNumber = l2OutputOracle.nextBlockNumber();
// Roll to after the block number we'll propose
warpToProposeTime(proposedNumber);
vm.prank(deploy.cfg().l2OutputOracleProposer());
l2OutputOracle.proposeL2Output(proposedOutput1, proposedNumber, 0, 0);
assertEq(l2OutputOracle.latestBlockNumber(), proposedNumber);
}
/// @dev Tests that `getL2Output` returns the correct value.
function test_getL2Output_succeeds() external {
uint256 nextBlockNumber = l2OutputOracle.nextBlockNumber();
uint256 nextOutputIndex = l2OutputOracle.nextOutputIndex();
warpToProposeTime(nextBlockNumber);
vm.prank(deploy.cfg().l2OutputOracleProposer());
l2OutputOracle.proposeL2Output(proposedOutput1, nextBlockNumber, 0, 0);
Types.OutputProposal memory proposal = l2OutputOracle.getL2Output(nextOutputIndex);
assertEq(proposal.outputRoot, proposedOutput1);
assertEq(proposal.timestamp, block.timestamp);
// The block number is larger than the latest proposed output:
vm.expectRevert(stdError.indexOOBError);
l2OutputOracle.getL2Output(nextOutputIndex + 1);
}
/// @dev Tests that `getL2OutputAfter` of an L2 block number returns the L2 output of the `getL2OutputIndexAfter` of
/// that block number.
function test_getL2OutputAfter_succeeds() external {
uint8 iterations = 5;
Types.OutputProposal memory output;
Types.OutputProposal memory expectedOutput;
for (uint8 i; i < iterations; i++) {
proposeAnotherOutput();
}
uint256 latestBlockNumber = l2OutputOracle.latestBlockNumber();
for (uint8 i = iterations - 1; i > 0; i--) {
uint256 index = l2OutputOracle.getL2OutputIndexAfter(latestBlockNumber);
output = l2OutputOracle.getL2OutputAfter(latestBlockNumber);
expectedOutput = l2OutputOracle.getL2Output(index);
assertEq(output.outputRoot, expectedOutput.outputRoot);
assertEq(output.timestamp, expectedOutput.timestamp);
assertEq(output.l2BlockNumber, expectedOutput.l2BlockNumber);
latestBlockNumber -= l2OutputOracle.SUBMISSION_INTERVAL();
}
}
/// @dev Tests that `getL2OutputIndexAfter` returns the correct value
/// when the input is the exact block number of the proposal.
function test_getL2OutputIndexAfter_sameBlock_succeeds() external {
bytes32 output1 = keccak256(abi.encode(1));
uint256 nextBlockNumber1 = l2OutputOracle.nextBlockNumber();
warpToProposeTime(nextBlockNumber1);
vm.prank(deploy.cfg().l2OutputOracleProposer());
l2OutputOracle.proposeL2Output(output1, nextBlockNumber1, 0, 0);
// Querying with exact same block as proposed returns the proposal.
uint256 index1 = l2OutputOracle.getL2OutputIndexAfter(nextBlockNumber1);
assertEq(index1, 0);
assertEq(
keccak256(abi.encode(l2OutputOracle.getL2Output(index1))),
keccak256(abi.encode(output1, block.timestamp, nextBlockNumber1))
);
}
/// @dev Tests that `getL2OutputIndexAfter` returns the correct value
/// when the input is the previous block number of the proposal.
function test_getL2OutputIndexAfter_previousBlock_succeeds() external {
bytes32 output1 = keccak256(abi.encode(1));
uint256 nextBlockNumber1 = l2OutputOracle.nextBlockNumber();
warpToProposeTime(nextBlockNumber1);
vm.prank(deploy.cfg().l2OutputOracleProposer());
l2OutputOracle.proposeL2Output(output1, nextBlockNumber1, 0, 0);
// Querying with previous block returns the proposal too.
uint256 index1 = l2OutputOracle.getL2OutputIndexAfter(nextBlockNumber1 - 1);
assertEq(index1, 0);
assertEq(
keccak256(abi.encode(l2OutputOracle.getL2Output(index1))),
keccak256(abi.encode(output1, block.timestamp, nextBlockNumber1))
);
}
/// @dev Tests that `getL2OutputIndexAfter` returns the correct value.
function test_getL2OutputIndexAfter_multipleOutputsExist_succeeds() external {
bytes32 output1 = keccak256(abi.encode(1));
uint256 nextBlockNumber1 = l2OutputOracle.nextBlockNumber();
warpToProposeTime(nextBlockNumber1);
vm.prank(deploy.cfg().l2OutputOracleProposer());
l2OutputOracle.proposeL2Output(output1, nextBlockNumber1, 0, 0);
bytes32 output2 = keccak256(abi.encode(2));
uint256 nextBlockNumber2 = l2OutputOracle.nextBlockNumber();
warpToProposeTime(nextBlockNumber2);
vm.prank(deploy.cfg().l2OutputOracleProposer());
l2OutputOracle.proposeL2Output(output2, nextBlockNumber2, 0, 0);
bytes32 output3 = keccak256(abi.encode(3));
uint256 nextBlockNumber3 = l2OutputOracle.nextBlockNumber();
warpToProposeTime(nextBlockNumber3);
vm.prank(deploy.cfg().l2OutputOracleProposer());
l2OutputOracle.proposeL2Output(output3, nextBlockNumber3, 0, 0);
bytes32 output4 = keccak256(abi.encode(4));
uint256 nextBlockNumber4 = l2OutputOracle.nextBlockNumber();
warpToProposeTime(nextBlockNumber4);
vm.prank(deploy.cfg().l2OutputOracleProposer());
l2OutputOracle.proposeL2Output(output4, nextBlockNumber4, 0, 0);
// Querying with a block number between the first and second proposal
uint256 index1 = l2OutputOracle.getL2OutputIndexAfter(nextBlockNumber1 + 1);
assertEq(index1, 1);
assertEq(
keccak256(abi.encode(l2OutputOracle.getL2Output(index1))),
keccak256(abi.encode(output2, l2OutputOracle.computeL2Timestamp(nextBlockNumber2) + 1, nextBlockNumber2))
);
// Querying with a block number between the second and third proposal
uint256 index2 = l2OutputOracle.getL2OutputIndexAfter(nextBlockNumber2 + 1);
assertEq(index2, 2);
assertEq(
keccak256(abi.encode(l2OutputOracle.getL2Output(index2))),
keccak256(abi.encode(output3, l2OutputOracle.computeL2Timestamp(nextBlockNumber3) + 1, nextBlockNumber3))
);
// Querying with a block number between the third and fourth proposal
uint256 index3 = l2OutputOracle.getL2OutputIndexAfter(nextBlockNumber3 + 1);
assertEq(index3, 3);
assertEq(
keccak256(abi.encode(l2OutputOracle.getL2Output(index3))),
keccak256(abi.encode(output4, l2OutputOracle.computeL2Timestamp(nextBlockNumber4) + 1, nextBlockNumber4))
);
}
/// @dev Tests that `getL2OutputIndexAfter` reverts when no output exists.
function test_getL2OutputIndexAfter_noOutputsExis_reverts() external {
vm.expectRevert("L2OutputOracle: cannot get output as no outputs have been proposed yet");
l2OutputOracle.getL2OutputIndexAfter(0);
}
/// @dev Tests that `nextBlockNumber` returns the correct value.
function test_nextBlockNumber_succeeds() external view {
assertEq(
l2OutputOracle.nextBlockNumber(),
// The return value should match this arithmetic
l2OutputOracle.latestBlockNumber() + l2OutputOracle.SUBMISSION_INTERVAL()
);
}
/// @dev Tests that `computeL2Timestamp` returns the correct value.
function test_computeL2Timestamp_succeeds() external {
uint256 startingBlockNumber = deploy.cfg().l2OutputOracleStartingBlockNumber();
uint256 startingTimestamp = deploy.cfg().l2OutputOracleStartingTimestamp();
uint256 l2BlockTime = deploy.cfg().l2BlockTime();
// reverts if timestamp is too low
vm.expectRevert(stdError.arithmeticError);
l2OutputOracle.computeL2Timestamp(startingBlockNumber - 1);
// check timestamp for the very first block
assertEq(l2OutputOracle.computeL2Timestamp(startingBlockNumber), startingTimestamp);
// check timestamp for the first block after the starting block
assertEq(l2OutputOracle.computeL2Timestamp(startingBlockNumber + 1), startingTimestamp + l2BlockTime);
// check timestamp for some other block number
assertEq(
l2OutputOracle.computeL2Timestamp(startingBlockNumber + 96024), startingTimestamp + l2BlockTime * 96024
);
}
}
contract L2OutputOracle_proposeL2Output_Test is L2OutputOracle_TestBase {
/// @dev Test that `proposeL2Output` succeeds for a valid input
/// and when a block hash and number are not specified.
function test_proposeL2Output_proposeAnotherOutput_succeeds() public {
proposeAnotherOutput();
}
/// @dev Tests that `proposeL2Output` succeeds when given valid input and
/// when a block hash and number are specified for reorg protection.
function test_proposeWithBlockhashAndHeight_succeeds() external {
// Get the number and hash of a previous block in the chain
uint256 prevL1BlockNumber = block.number - 1;
bytes32 prevL1BlockHash = blockhash(prevL1BlockNumber);
uint256 nextBlockNumber = l2OutputOracle.nextBlockNumber();
warpToProposeTime(nextBlockNumber);
vm.prank(deploy.cfg().l2OutputOracleProposer());
l2OutputOracle.proposeL2Output(nonZeroHash, nextBlockNumber, prevL1BlockHash, prevL1BlockNumber);
}
/// @dev Tests that `proposeL2Output` reverts when called by a party
/// that is not the proposer.
function test_proposeL2Output_notProposer_reverts() external {
uint256 nextBlockNumber = l2OutputOracle.nextBlockNumber();
warpToProposeTime(nextBlockNumber);
vm.prank(address(128));
vm.expectRevert("L2OutputOracle: only the proposer address can propose new outputs");
l2OutputOracle.proposeL2Output(nonZeroHash, nextBlockNumber, 0, 0);
}
/// @dev Tests that `proposeL2Output` reverts when given a zero blockhash.
function test_proposeL2Output_emptyOutput_reverts() external {
bytes32 outputToPropose = bytes32(0);
uint256 nextBlockNumber = l2OutputOracle.nextBlockNumber();
warpToProposeTime(nextBlockNumber);
vm.prank(deploy.cfg().l2OutputOracleProposer());
vm.expectRevert("L2OutputOracle: L2 output proposal cannot be the zero hash");
l2OutputOracle.proposeL2Output(outputToPropose, nextBlockNumber, 0, 0);
}
/// @dev Tests that `proposeL2Output` reverts when given a block number
/// that does not match the next expected block number.
function test_proposeL2Output_unexpectedBlockNumber_reverts() external {
uint256 nextBlockNumber = l2OutputOracle.nextBlockNumber();
warpToProposeTime(nextBlockNumber);
vm.prank(deploy.cfg().l2OutputOracleProposer());
vm.expectRevert("L2OutputOracle: block number must be equal to next expected block number");
l2OutputOracle.proposeL2Output(nonZeroHash, nextBlockNumber - 1, 0, 0);
}
/// @dev Tests that `proposeL2Output` reverts when given a block number
/// that has a timestamp in the future.
function test_proposeL2Output_futureTimetamp_reverts() external {
uint256 nextBlockNumber = l2OutputOracle.nextBlockNumber();
uint256 nextTimestamp = l2OutputOracle.computeL2Timestamp(nextBlockNumber);
vm.warp(nextTimestamp);
vm.prank(deploy.cfg().l2OutputOracleProposer());
vm.expectRevert("L2OutputOracle: cannot propose L2 output in the future");
l2OutputOracle.proposeL2Output(nonZeroHash, nextBlockNumber, 0, 0);
}
/// @dev Tests that `proposeL2Output` reverts when given a block number
/// whose hash does not match the given block hash.
function test_proposeL2Output_wrongFork_reverts() external {
uint256 nextBlockNumber = l2OutputOracle.nextBlockNumber();
warpToProposeTime(nextBlockNumber);
vm.prank(deploy.cfg().l2OutputOracleProposer());
vm.expectRevert("L2OutputOracle: block hash does not match the hash at the expected height");
l2OutputOracle.proposeL2Output(nonZeroHash, nextBlockNumber, bytes32(uint256(0x01)), block.number);
}
/// @dev Tests that `proposeL2Output` reverts when given a block number
/// whose block hash does not match the given block hash.
function test_proposeL2Output_unmatchedBlockhash_reverts() external {
// Move ahead to block 100 so that we can reference historical blocks
vm.roll(100);
// Get the number and hash of a previous block in the chain
uint256 l1BlockNumber = block.number - 1;
bytes32 l1BlockHash = blockhash(l1BlockNumber);
uint256 nextBlockNumber = l2OutputOracle.nextBlockNumber();
warpToProposeTime(nextBlockNumber);
vm.prank(deploy.cfg().l2OutputOracleProposer());
// This will fail when foundry no longer returns zerod block hashes
vm.expectRevert("L2OutputOracle: block hash does not match the hash at the expected height");
l2OutputOracle.proposeL2Output(nonZeroHash, nextBlockNumber, l1BlockHash, l1BlockNumber - 1);
}
}
contract L2OutputOracle_deleteOutputs_Test is L2OutputOracle_TestBase {
/// @dev Tests that `deleteL2Outputs` succeeds for a single output.
function test_deleteOutputs_singleOutput_succeeds() external {
proposeAnotherOutput();
proposeAnotherOutput();
uint256 latestBlockNumber = l2OutputOracle.latestBlockNumber();
uint256 latestOutputIndex = l2OutputOracle.latestOutputIndex();
Types.OutputProposal memory newLatestOutput = l2OutputOracle.getL2Output(latestOutputIndex - 1);
vm.prank(l2OutputOracle.CHALLENGER());
vm.prank(l2OutputOracle.challenger());
vm.expectEmit(true, true, false, false);
emit OutputsDeleted(latestOutputIndex + 1, latestOutputIndex);
l2OutputOracle.deleteL2Outputs(latestOutputIndex);
// validate latestBlockNumber has been reduced
uint256 latestBlockNumberAfter = l2OutputOracle.latestBlockNumber();
uint256 latestOutputIndexAfter = l2OutputOracle.latestOutputIndex();
uint256 submissionInterval = deploy.cfg().l2OutputOracleSubmissionInterval();
assertEq(latestBlockNumber - submissionInterval, latestBlockNumberAfter);
// validate that the new latest output is as expected.
Types.OutputProposal memory proposal = l2OutputOracle.getL2Output(latestOutputIndexAfter);
assertEq(newLatestOutput.outputRoot, proposal.outputRoot);
assertEq(newLatestOutput.timestamp, proposal.timestamp);
}
/// @dev Tests that `deleteL2Outputs` succeeds for multiple outputs.
function test_deleteOutputs_multipleOutputs_succeeds() external {
proposeAnotherOutput();
proposeAnotherOutput();
proposeAnotherOutput();
proposeAnotherOutput();
uint256 latestBlockNumber = l2OutputOracle.latestBlockNumber();
uint256 latestOutputIndex = l2OutputOracle.latestOutputIndex();
Types.OutputProposal memory newLatestOutput = l2OutputOracle.getL2Output(latestOutputIndex - 3);
vm.prank(l2OutputOracle.CHALLENGER());
vm.prank(l2OutputOracle.challenger());
vm.expectEmit(true, true, false, false);
emit OutputsDeleted(latestOutputIndex + 1, latestOutputIndex - 2);
l2OutputOracle.deleteL2Outputs(latestOutputIndex - 2);
// validate latestBlockNumber has been reduced
uint256 latestBlockNumberAfter = l2OutputOracle.latestBlockNumber();
uint256 latestOutputIndexAfter = l2OutputOracle.latestOutputIndex();
uint256 submissionInterval = deploy.cfg().l2OutputOracleSubmissionInterval();
assertEq(latestBlockNumber - submissionInterval * 3, latestBlockNumberAfter);
// validate that the new latest output is as expected.
Types.OutputProposal memory proposal = l2OutputOracle.getL2Output(latestOutputIndexAfter);
assertEq(newLatestOutput.outputRoot, proposal.outputRoot);
assertEq(newLatestOutput.timestamp, proposal.timestamp);
}
/// @dev Tests that `deleteL2Outputs` reverts when not called by the challenger.
function test_deleteL2Outputs_ifNotChallenger_reverts() external {
uint256 latestBlockNumber = l2OutputOracle.latestBlockNumber();
vm.expectRevert("L2OutputOracle: only the challenger address can delete outputs");
l2OutputOracle.deleteL2Outputs(latestBlockNumber);
}
/// @dev Tests that `deleteL2Outputs` reverts for a non-existant output index.
function test_deleteL2Outputs_nonExistent_reverts() external {
proposeAnotherOutput();
uint256 latestBlockNumber = l2OutputOracle.latestBlockNumber();
vm.prank(l2OutputOracle.CHALLENGER());
vm.prank(l2OutputOracle.challenger());
vm.expectRevert("L2OutputOracle: cannot delete outputs after the latest output index");
l2OutputOracle.deleteL2Outputs(latestBlockNumber + 1);
}
/// @dev Tests that `deleteL2Outputs` reverts when trying to delete outputs
/// after the latest output index.
function test_deleteL2Outputs_afterLatest_reverts() external {
proposeAnotherOutput();
proposeAnotherOutput();
proposeAnotherOutput();
// Delete the latest two outputs
uint256 latestOutputIndex = l2OutputOracle.latestOutputIndex();
vm.prank(l2OutputOracle.CHALLENGER());
vm.prank(l2OutputOracle.challenger());
l2OutputOracle.deleteL2Outputs(latestOutputIndex - 2);
// Now try to delete the same output again
vm.prank(l2OutputOracle.CHALLENGER());
vm.prank(l2OutputOracle.challenger());
vm.expectRevert("L2OutputOracle: cannot delete outputs after the latest output index");
l2OutputOracle.deleteL2Outputs(latestOutputIndex - 2);
}
/// @dev Tests that `deleteL2Outputs` reverts for finalized outputs.
function test_deleteL2Outputs_finalized_reverts() external {
proposeAnotherOutput();
// Warp past the finalization period + 1 second
vm.warp(block.timestamp + l2OutputOracle.FINALIZATION_PERIOD_SECONDS() + 1);
uint256 latestOutputIndex = l2OutputOracle.latestOutputIndex();
// Try to delete a finalized output
vm.prank(l2OutputOracle.CHALLENGER());
vm.prank(l2OutputOracle.challenger());
vm.expectRevert("L2OutputOracle: cannot delete outputs that have already been finalized");
l2OutputOracle.deleteL2Outputs(latestOutputIndex);
}
}
contract L2OutputOracleUpgradeable_Test is L2OutputOracle_TestBase {
/// @dev Tests that the proxy can be successfully upgraded.
function test_upgrading_succeeds() external {
Proxy proxy = Proxy(deploy.mustGetAddress("L2OutputOracleProxy"));
// Check an unused slot before upgrading.
bytes32 slot21Before = vm.load(address(l2OutputOracle), bytes32(uint256(21)));
assertEq(bytes32(0), slot21Before);
NextImpl nextImpl = new NextImpl();
vm.startPrank(EIP1967Helper.getAdmin(address(proxy)));
// Reviewer note: the NextImpl() still uses reinitializer. If we want to remove that, we'll need to use a
// two step upgrade with the Storage lib.
proxy.upgradeToAndCall(address(nextImpl), abi.encodeCall(NextImpl.initialize, (2)));
assertEq(proxy.implementation(), address(nextImpl));
// Verify that the NextImpl contract initialized its values according as expected
bytes32 slot21After = vm.load(address(l2OutputOracle), bytes32(uint256(21)));
bytes32 slot21Expected = NextImpl(address(l2OutputOracle)).slot21Init();
assertEq(slot21Expected, slot21After);
}
/// @dev Tests that initialize reverts if the submissionInterval is zero.
function test_initialize_submissionInterval_reverts() external {
// Reset the initialized field in the 0th storage slot
// so that initialize can be called again.
vm.store(address(l2OutputOracle), bytes32(uint256(0)), bytes32(uint256(0)));
uint256 l2BlockTime = deploy.cfg().l2BlockTime();
uint256 startingBlockNumber = deploy.cfg().l2OutputOracleStartingBlockNumber();
uint256 startingTimestamp = deploy.cfg().l2OutputOracleStartingTimestamp();
address proposer = deploy.cfg().l2OutputOracleProposer();
address challenger = deploy.cfg().l2OutputOracleChallenger();
uint256 finalizationPeriodSeconds = deploy.cfg().finalizationPeriodSeconds();
vm.expectRevert("L2OutputOracle: submission interval must be greater than 0");
l2OutputOracle.initialize({
_submissionInterval: 0,
_l2BlockTime: l2BlockTime,
_startingBlockNumber: startingBlockNumber,
_startingTimestamp: startingTimestamp,
_proposer: proposer,
_challenger: challenger,
_finalizationPeriodSeconds: finalizationPeriodSeconds
});
}
/// @dev Tests that initialize reverts if the l2BlockTime is invalid.
function test_initialize_l2BlockTimeZero_reverts() external {
// Reset the initialized field in the 0th storage slot
// so that initialize can be called again.
vm.store(address(l2OutputOracle), bytes32(uint256(0)), bytes32(uint256(0)));
uint256 submissionInterval = deploy.cfg().l2OutputOracleSubmissionInterval();
uint256 startingBlockNumber = deploy.cfg().l2OutputOracleStartingBlockNumber();
uint256 startingTimestamp = deploy.cfg().l2OutputOracleStartingTimestamp();
address proposer = deploy.cfg().l2OutputOracleProposer();
address challenger = deploy.cfg().l2OutputOracleChallenger();
uint256 finalizationPeriodSeconds = deploy.cfg().finalizationPeriodSeconds();
vm.expectRevert("L2OutputOracle: L2 block time must be greater than 0");
l2OutputOracle.initialize({
_submissionInterval: submissionInterval,
_l2BlockTime: 0,
_startingBlockNumber: startingBlockNumber,
_startingTimestamp: startingTimestamp,
_proposer: proposer,
_challenger: challenger,
_finalizationPeriodSeconds: finalizationPeriodSeconds
});
}
/// @dev Tests that initialize reverts if the starting timestamp is invalid.
function test_initialize_badTimestamp_reverts() external {
// Reset the initialized field in the 0th storage slot
// so that initialize can be called again.
vm.store(address(l2OutputOracle), bytes32(uint256(0)), bytes32(uint256(0)));
uint256 submissionInterval = deploy.cfg().l2OutputOracleSubmissionInterval();
uint256 l2BlockTime = deploy.cfg().l2BlockTime();
uint256 startingBlockNumber = deploy.cfg().l2OutputOracleStartingBlockNumber();
address proposer = deploy.cfg().l2OutputOracleProposer();
address challenger = deploy.cfg().l2OutputOracleChallenger();
uint256 finalizationPeriodSeconds = deploy.cfg().finalizationPeriodSeconds();
vm.expectRevert("L2OutputOracle: starting L2 timestamp must be less than current time");
l2OutputOracle.initialize({
_submissionInterval: submissionInterval,
_l2BlockTime: l2BlockTime,
_startingBlockNumber: startingBlockNumber,
_startingTimestamp: block.timestamp + 1,
_proposer: proposer,
_challenger: challenger,
_finalizationPeriodSeconds: finalizationPeriodSeconds
});
}
}
// SPDX-License-Identifier: MIT
pragma solidity 0.8.15;
// Testing
import { stdError } from "forge-std/Test.sol";
import { VmSafe } from "forge-std/Vm.sol";
import { MockERC20 } from "solmate/test/utils/mocks/MockERC20.sol";
import { CommonTest } from "test/setup/CommonTest.sol";
import { NextImpl } from "test/mocks/NextImpl.sol";
import { EIP1967Helper } from "test/mocks/EIP1967Helper.sol";
// Contracts
import { SuperchainConfig } from "src/L1/SuperchainConfig.sol";
// Libraries
import { Types } from "src/libraries/Types.sol";
import { Hashing } from "src/libraries/Hashing.sol";
import { Constants } from "src/libraries/Constants.sol";
import { Predeploys } from "src/libraries/Predeploys.sol";
import { GasPayingToken } from "src/libraries/GasPayingToken.sol";
import { AddressAliasHelper } from "src/vendor/AddressAliasHelper.sol";
import "src/libraries/PortalErrors.sol";
// Interfaces
import { IResourceMetering } from "interfaces/L1/IResourceMetering.sol";
import { IL2OutputOracle } from "interfaces/L1/IL2OutputOracle.sol";
import { IL1Block } from "interfaces/L2/IL1Block.sol";
import { IOptimismPortal } from "interfaces/L1/IOptimismPortal.sol";
import { IProxy } from "interfaces/universal/IProxy.sol";
contract OptimismPortal_Test is CommonTest {
address depositor;
/// @notice Marked virtual to be overridden in
/// test/kontrol/deployment/DeploymentSummary.t.sol
function setUp() public virtual override {
super.enableLegacyContracts();
super.setUp();
depositor = makeAddr("depositor");
}
/// @dev Tests that the constructor sets the correct values.
/// @notice Marked virtual to be overridden in
/// test/kontrol/deployment/DeploymentSummary.t.sol
function test_constructor_succeeds() external virtual {
IOptimismPortal opImpl = IOptimismPortal(payable(deploy.mustGetAddress("OptimismPortal")));
assertEq(address(opImpl.l2Oracle()), address(0));
assertEq(address(opImpl.systemConfig()), address(0));
assertEq(address(opImpl.superchainConfig()), address(0));
assertEq(opImpl.l2Sender(), address(0));
(uint128 prevBaseFee, uint64 prevBoughtGas, uint64 prevBlockNum) = opImpl.params();
assertEq(prevBaseFee, 0);
assertEq(prevBoughtGas, 0);
assertEq(prevBlockNum, 0);
}
/// @dev Tests that the initializer sets the correct values.
/// @notice Marked virtual to be overridden in
/// test/kontrol/deployment/DeploymentSummary.t.sol
function test_initialize_succeeds() external virtual {
address guardian = deploy.cfg().superchainConfigGuardian();
assertEq(address(optimismPortal.l2Oracle()), address(l2OutputOracle));
assertEq(address(optimismPortal.systemConfig()), address(systemConfig));
assertEq(optimismPortal.guardian(), guardian);
assertEq(address(optimismPortal.superchainConfig()), address(superchainConfig));
assertEq(optimismPortal.l2Sender(), Constants.DEFAULT_L2_SENDER);
assertEq(optimismPortal.paused(), false);
(uint128 prevBaseFee, uint64 prevBoughtGas, uint64 prevBlockNum) = optimismPortal.params();
assertEq(prevBaseFee, 1 gwei);
assertEq(prevBoughtGas, 0);
assertEq(prevBlockNum, uint64(block.number));
}
/// @dev Tests that `pause` successfully pauses
/// when called by the GUARDIAN.
function test_pause_succeeds() external {
address guardian = optimismPortal.guardian();
assertEq(optimismPortal.paused(), false);
vm.expectEmit(address(superchainConfig));
emit Paused("identifier");
vm.prank(guardian);
superchainConfig.pause("identifier");
assertEq(optimismPortal.paused(), true);
}
/// @dev Tests that `pause` reverts when called by a non-GUARDIAN.
function test_pause_onlyGuardian_reverts() external {
assertEq(optimismPortal.paused(), false);
assertTrue(optimismPortal.guardian() != alice);
vm.expectRevert("SuperchainConfig: only guardian can pause");
vm.prank(alice);
superchainConfig.pause("identifier");
assertEq(optimismPortal.paused(), false);
}
/// @dev Tests that `unpause` successfully unpauses
/// when called by the GUARDIAN.
function test_unpause_succeeds() external {
address guardian = optimismPortal.guardian();
vm.prank(guardian);
superchainConfig.pause("identifier");
assertEq(optimismPortal.paused(), true);
vm.expectEmit(address(superchainConfig));
emit Unpaused();
vm.prank(guardian);
superchainConfig.unpause();
assertEq(optimismPortal.paused(), false);
}
/// @dev Tests that `unpause` reverts when called by a non-GUARDIAN.
function test_unpause_onlyGuardian_reverts() external {
address guardian = optimismPortal.guardian();
vm.prank(guardian);
superchainConfig.pause("identifier");
assertEq(optimismPortal.paused(), true);
assertTrue(optimismPortal.guardian() != alice);
vm.expectRevert("SuperchainConfig: only guardian can unpause");
vm.prank(alice);
superchainConfig.unpause();
assertEq(optimismPortal.paused(), true);
}
/// @dev Tests that `receive` successdully deposits ETH.
function testFuzz_receive_succeeds(uint256 _value) external {
vm.expectEmit(address(optimismPortal));
emitTransactionDeposited({
_from: alice,
_to: alice,
_value: _value,
_mint: _value,
_gasLimit: 100_000,
_isCreation: false,
_data: hex""
});
// give alice money and send as an eoa
vm.deal(alice, _value);
vm.prank(alice, alice);
(bool s,) = address(optimismPortal).call{ value: _value }(hex"");
assertTrue(s);
assertEq(address(optimismPortal).balance, _value);
}
/// @notice Helper function for depositing a transaction.
function depositTransaction(
address _from,
address _to,
uint256 _mint,
uint256 _value,
uint64 _gasLimit,
bool _isCreation,
bytes calldata _data
)
internal
{
if (_isCreation) {
_to = address(0);
}
if (_data.length > 120_000) {
_data = _data[0:120_000];
}
IResourceMetering.ResourceConfig memory rcfg = systemConfig.resourceConfig();
_gasLimit =
uint64(bound(_gasLimit, optimismPortal.minimumGasLimit(uint64(_data.length)), rcfg.maxResourceLimit));
uint256 prevBalance = address(optimismPortal).balance;
// Ensure that no custom gas token is set
(address gasPayingToken,) = systemConfig.gasPayingToken();
assertEq(gasPayingToken, Constants.ETHER);
bytes memory opaqueData = abi.encodePacked(_mint, _value, _gasLimit, _isCreation, _data);
vm.expectEmit(address(optimismPortal));
emit TransactionDeposited(
_from, // from
_to,
uint256(0), // DEPOSIT_VERSION
opaqueData
);
vm.deal(address(this), _mint);
// Deposit the token into the portal
optimismPortal.depositTransaction{ value: _mint }(_to, _value, _gasLimit, _isCreation, _data);
// Assert final balance equals the deposited amount
assertEq(address(optimismPortal).balance, _mint + prevBalance);
assertEq(optimismPortal.balance(), _mint + prevBalance);
}
/// @dev Tests that `depositTransaction` succeeds when msg.sender == tx.origin and non-custom gas is used.
function testFuzz_depositTransaction_senderIsOrigin_succeeds(
address _to,
uint256 _mint,
uint256 _value,
uint64 _gasLimit,
bool _isCreation,
bytes calldata _data
)
external
{
// Ensure that msg.sender == tx.origin
vm.startPrank(address(this), address(this));
depositTransaction({
_from: address(this),
_to: _to,
_mint: _mint,
_value: _value,
_gasLimit: _gasLimit,
_isCreation: _isCreation,
_data: _data
});
}
/// @dev Tests that `depositTransaction` succeeds when msg.sender != tx.origin and non-custom gas is used.
function testFuzz_depositTransaction_senderNotOrigin_succeeds(
address _to,
uint256 _mint,
uint256 _value,
uint64 _gasLimit,
bool _isCreation,
bytes calldata _data
)
external
{
// Ensure that msg.sender != tx.origin
vm.startPrank(address(this), address(1));
depositTransaction({
_from: AddressAliasHelper.applyL1ToL2Alias(address(this)),
_to: _to,
_mint: _mint,
_value: _value,
_gasLimit: _gasLimit,
_isCreation: _isCreation,
_data: _data
});
}
/// @dev Tests that `depositTransaction` reverts when the destination address is non-zero
/// for a contract creation deposit.
function test_depositTransaction_contractCreation_reverts() external {
// contract creation must have a target of address(0)
vm.expectRevert(BadTarget.selector);
optimismPortal.depositTransaction(address(1), 1, 0, true, hex"");
}
/// @dev Tests that `depositTransaction` reverts when the data is too large.
/// This places an upper bound on unsafe blocks sent over p2p.
function test_depositTransaction_largeData_reverts() external {
uint256 size = 120_001;
uint64 gasLimit = optimismPortal.minimumGasLimit(uint64(size));
vm.expectRevert(LargeCalldata.selector);
optimismPortal.depositTransaction({
_to: address(0),
_value: 0,
_gasLimit: gasLimit,
_isCreation: false,
_data: new bytes(size)
});
}
/// @dev Tests that `depositTransaction` reverts when the gas limit is too small.
function test_depositTransaction_smallGasLimit_reverts() external {
vm.expectRevert(SmallGasLimit.selector);
optimismPortal.depositTransaction({ _to: address(1), _value: 0, _gasLimit: 0, _isCreation: false, _data: hex"" });
}
/// @dev Tests that `depositTransaction` succeeds for small,
/// but sufficient, gas limits.
function testFuzz_depositTransaction_smallGasLimit_succeeds(bytes memory _data, bool _shouldFail) external {
uint64 gasLimit = optimismPortal.minimumGasLimit(uint64(_data.length));
if (_shouldFail) {
gasLimit = uint64(bound(gasLimit, 0, gasLimit - 1));
vm.expectRevert(SmallGasLimit.selector);
}
optimismPortal.depositTransaction({
_to: address(0x40),
_value: 0,
_gasLimit: gasLimit,
_isCreation: false,
_data: _data
});
}
/// @dev Tests that `minimumGasLimit` succeeds for small calldata sizes.
/// The gas limit should be 21k for 0 calldata and increase linearly
/// for larger calldata sizes.
function test_minimumGasLimit_succeeds() external view {
assertEq(optimismPortal.minimumGasLimit(0), 21_000);
assertTrue(optimismPortal.minimumGasLimit(2) > optimismPortal.minimumGasLimit(1));
assertTrue(optimismPortal.minimumGasLimit(3) > optimismPortal.minimumGasLimit(2));
}
/// @dev Tests that `depositTransaction` succeeds for an EOA.
function testFuzz_depositTransaction_eoa_succeeds(
address _to,
uint64 _gasLimit,
uint256 _value,
uint256 _mint,
bool _isCreation,
bytes memory _data
)
external
{
_gasLimit = uint64(
bound(
_gasLimit,
optimismPortal.minimumGasLimit(uint64(_data.length)),
systemConfig.resourceConfig().maxResourceLimit
)
);
if (_isCreation) _to = address(0);
// EOA emulation
vm.expectEmit(address(optimismPortal));
emitTransactionDeposited({
_from: depositor,
_to: _to,
_value: _value,
_mint: _mint,
_gasLimit: _gasLimit,
_isCreation: _isCreation,
_data: _data
});
vm.deal(depositor, _mint);
vm.prank(depositor, depositor);
optimismPortal.depositTransaction{ value: _mint }({
_to: _to,
_value: _value,
_gasLimit: _gasLimit,
_isCreation: _isCreation,
_data: _data
});
assertEq(address(optimismPortal).balance, _mint);
}
/// @dev Tests that `depositTransaction` succeeds for a contract.
function testFuzz_depositTransaction_contract_succeeds(
address _to,
uint64 _gasLimit,
uint256 _value,
uint256 _mint,
bool _isCreation,
bytes memory _data
)
external
{
_gasLimit = uint64(
bound(
_gasLimit,
optimismPortal.minimumGasLimit(uint64(_data.length)),
systemConfig.resourceConfig().maxResourceLimit
)
);
if (_isCreation) _to = address(0);
vm.expectEmit(address(optimismPortal));
emitTransactionDeposited({
_from: AddressAliasHelper.applyL1ToL2Alias(address(this)),
_to: _to,
_value: _value,
_mint: _mint,
_gasLimit: _gasLimit,
_isCreation: _isCreation,
_data: _data
});
vm.deal(address(this), _mint);
vm.prank(address(this));
optimismPortal.depositTransaction{ value: _mint }({
_to: _to,
_value: _value,
_gasLimit: _gasLimit,
_isCreation: _isCreation,
_data: _data
});
assertEq(address(optimismPortal).balance, _mint);
}
/// @dev Tests that `isOutputFinalized` succeeds for an EOA depositing a tx with ETH and data.
/// @notice Marked virtual to be overridden in
/// test/kontrol/deployment/DeploymentSummary.t.sol
function test_simple_isOutputFinalized_succeeds() external virtual {
uint256 startingBlockNumber = deploy.cfg().l2OutputOracleStartingBlockNumber();
uint256 ts = block.timestamp;
vm.mockCall(
address(optimismPortal.l2Oracle()),
abi.encodePacked(IL2OutputOracle.getL2Output.selector),
abi.encode(Types.OutputProposal(bytes32(uint256(1)), uint128(ts), uint128(startingBlockNumber)))
);
// warp to the finalization period
vm.warp(ts + l2OutputOracle.FINALIZATION_PERIOD_SECONDS());
assertEq(optimismPortal.isOutputFinalized(0), false);
// warp past the finalization period
vm.warp(ts + l2OutputOracle.FINALIZATION_PERIOD_SECONDS() + 1);
assertEq(optimismPortal.isOutputFinalized(0), true);
}
/// @dev Tests `isOutputFinalized` for a finalized output.
/// @notice Marked virtual to be overridden in
/// test/kontrol/deployment/DeploymentSummary.t.sol
function test_isOutputFinalized_succeeds() external virtual {
uint256 checkpoint = l2OutputOracle.nextBlockNumber();
uint256 nextOutputIndex = l2OutputOracle.nextOutputIndex();
vm.roll(checkpoint);
vm.warp(l2OutputOracle.computeL2Timestamp(checkpoint) + 1);
vm.prank(l2OutputOracle.PROPOSER());
l2OutputOracle.proposeL2Output(keccak256(abi.encode(2)), checkpoint, 0, 0);
// warp to the final second of the finalization period
uint256 finalizationHorizon = block.timestamp + l2OutputOracle.FINALIZATION_PERIOD_SECONDS();
vm.warp(finalizationHorizon);
// The checkpointed block should not be finalized until 1 second from now.
assertEq(optimismPortal.isOutputFinalized(nextOutputIndex), false);
// Nor should a block after it
vm.expectRevert(stdError.indexOOBError);
assertEq(optimismPortal.isOutputFinalized(nextOutputIndex + 1), false);
// warp past the finalization period
vm.warp(finalizationHorizon + 1);
// It should now be finalized.
assertEq(optimismPortal.isOutputFinalized(nextOutputIndex), true);
// But not the block after it.
vm.expectRevert(stdError.indexOOBError);
assertEq(optimismPortal.isOutputFinalized(nextOutputIndex + 1), false);
}
/// @dev Tests that the gas paying token can be set.
function testFuzz_setGasPayingToken_succeeds(
address _token,
uint8 _decimals,
bytes32 _name,
bytes32 _symbol
)
external
{
vm.expectEmit(address(optimismPortal));
emit TransactionDeposited(
0xDeaDDEaDDeAdDeAdDEAdDEaddeAddEAdDEAd0001,
Predeploys.L1_BLOCK_ATTRIBUTES,
0,
abi.encodePacked(
uint256(0), // mint
uint256(0), // value
uint64(200_000), // gasLimit
false, // isCreation,
abi.encodeCall(IL1Block.setGasPayingToken, (_token, _decimals, _name, _symbol))
)
);
vm.prank(address(systemConfig));
optimismPortal.setGasPayingToken({ _token: _token, _decimals: _decimals, _name: _name, _symbol: _symbol });
}
/// @notice Ensures that the deposit event is correct for the `setGasPayingToken`
/// code path that manually emits a deposit transaction outside of the
/// `depositTransaction` function. This is a simple differential test.
function test_setGasPayingToken_correctEvent_succeeds(
address _token,
string calldata _name,
string calldata _symbol
)
external
{
if (bytes(_name).length > 32) {
_name = _name[0:32];
}
if (bytes(_symbol).length > 32) {
_symbol = _symbol[0:32];
}
bytes32 name = GasPayingToken.sanitize(_name);
bytes32 symbol = GasPayingToken.sanitize(_symbol);
vm.recordLogs();
vm.prank(address(systemConfig));
optimismPortal.setGasPayingToken({ _token: _token, _decimals: 18, _name: name, _symbol: symbol });
vm.prank(Constants.DEPOSITOR_ACCOUNT, Constants.DEPOSITOR_ACCOUNT);
optimismPortal.depositTransaction({
_to: Predeploys.L1_BLOCK_ATTRIBUTES,
_value: 0,
_gasLimit: 200_000,
_isCreation: false,
_data: abi.encodeCall(IL1Block.setGasPayingToken, (_token, 18, name, symbol))
});
VmSafe.Log[] memory logs = vm.getRecordedLogs();
assertEq(logs.length, 2);
VmSafe.Log memory systemPath = logs[0];
VmSafe.Log memory userPath = logs[1];
assertEq(systemPath.topics.length, 4);
assertEq(systemPath.topics.length, userPath.topics.length);
assertEq(systemPath.topics[0], userPath.topics[0]);
assertEq(systemPath.topics[1], userPath.topics[1]);
assertEq(systemPath.topics[2], userPath.topics[2]);
assertEq(systemPath.topics[3], userPath.topics[3]);
assertEq(systemPath.data, userPath.data);
}
/// @dev Tests that the gas paying token cannot be set by a non-system config.
function test_setGasPayingToken_notSystemConfig_fails(address _caller) external {
vm.assume(_caller != address(systemConfig));
vm.prank(_caller);
vm.expectRevert(Unauthorized.selector);
optimismPortal.setGasPayingToken({ _token: address(0), _decimals: 0, _name: "", _symbol: "" });
}
/// @dev Tests that `depositERC20Transaction` reverts when the gas paying token is ether.
function test_depositERC20Transaction_noCustomGasToken_reverts() external {
// TODO(opcm upgrades): remove skip once upgrade path is implemented
skipIfForkTest("OptimismPortal_Test: gas paying token functionality DNE on op mainnet");
// Check that the gas paying token is set to ether
(address token,) = systemConfig.gasPayingToken();
assertEq(token, Constants.ETHER);
vm.expectRevert(OnlyCustomGasToken.selector);
optimismPortal.depositERC20Transaction(address(0), 0, 0, 0, false, "");
}
function test_depositERC20Transaction_balanceOverflow_reverts() external {
// TODO(opcm upgrades): remove skip once upgrade path is implemented
skipIfForkTest("OptimismPortal_Test: custom gas token DNE on op mainnet");
vm.mockCall(address(systemConfig), abi.encodeCall(systemConfig.gasPayingToken, ()), abi.encode(address(42), 18));
// The balance slot
vm.store(address(optimismPortal), bytes32(uint256(61)), bytes32(type(uint256).max));
assertEq(optimismPortal.balance(), type(uint256).max);
vm.expectRevert(stdError.arithmeticError);
optimismPortal.depositERC20Transaction({
_to: address(0),
_mint: 1,
_value: 1,
_gasLimit: 10_000,
_isCreation: false,
_data: ""
});
}
/// @dev Tests that `balance()` returns the correct balance when the gas paying token is ether.
function testFuzz_balance_ether_succeeds(uint256 _amount) external {
// Check that the gas paying token is set to ether
(address token,) = systemConfig.gasPayingToken();
assertEq(token, Constants.ETHER);
// Increase the balance of the gas paying token
vm.deal(address(optimismPortal), _amount);
// Check that the balance has been correctly updated
assertEq(optimismPortal.balance(), address(optimismPortal).balance);
}
/// @dev Tests that the donateETH function donates ETH and does no state read/write
function test_donateETH_succeeds(uint256 _amount) external {
vm.startPrank(alice);
vm.deal(alice, _amount);
uint256 preBalance = address(optimismPortal).balance;
vm.startStateDiffRecording();
optimismPortal.donateETH{ value: _amount }();
VmSafe.AccountAccess[] memory accountAccesses = vm.stopAndReturnStateDiff();
// not necessary since it's checked below
assertEq(address(optimismPortal).balance, preBalance + _amount);
// 0 for extcodesize of proxy before being called by this test,
// 1 for the call to the proxy by the pranked address
// 2 for the delegate call to the impl by the proxy
assertEq(accountAccesses.length, 3);
assertEq(uint8(accountAccesses[1].kind), uint8(VmSafe.AccountAccessKind.Call));
assertEq(uint8(accountAccesses[2].kind), uint8(VmSafe.AccountAccessKind.DelegateCall));
// to of 1 is the optimism portal proxy
assertEq(accountAccesses[1].account, address(optimismPortal));
// accessor is the pranked address
assertEq(accountAccesses[1].accessor, alice);
// value is the amount of ETH donated
assertEq(accountAccesses[1].value, _amount);
// old balance is the balance of the optimism portal before the donation
assertEq(accountAccesses[1].oldBalance, preBalance);
// new balance is the balance of the optimism portal after the donation
assertEq(accountAccesses[1].newBalance, preBalance + _amount);
// data is the selector of the donateETH function
assertEq(accountAccesses[1].data, abi.encodePacked(optimismPortal.donateETH.selector));
// reverted of alice call to proxy is false
assertEq(accountAccesses[1].reverted, false);
// reverted of delegate call of proxy to impl is false
assertEq(accountAccesses[2].reverted, false);
// storage accesses of delegate call of proxy to impl is empty (No storage read or write!)
assertEq(accountAccesses[2].storageAccesses.length, 0);
}
}
contract OptimismPortal_FinalizeWithdrawal_Test is CommonTest {
// Reusable default values for a test withdrawal
Types.WithdrawalTransaction _defaultTx;
uint256 _proposedOutputIndex;
uint256 _proposedBlockNumber;
bytes32 _stateRoot;
bytes32 _storageRoot;
bytes32 _outputRoot;
bytes32 _withdrawalHash;
bytes[] _withdrawalProof;
Types.OutputRootProof internal _outputRootProof;
// Use a constructor to set the storage vars above, so as to minimize the number of ffi calls.
constructor() {
super.enableLegacyContracts();
super.setUp();
_defaultTx = Types.WithdrawalTransaction({
nonce: 0,
sender: alice,
target: bob,
value: 100,
gasLimit: 100_000,
data: hex"aa" // includes calldata for ERC20 withdrawal test
});
// Get withdrawal proof data we can use for testing.
(_stateRoot, _storageRoot, _outputRoot, _withdrawalHash, _withdrawalProof) =
ffi.getProveWithdrawalTransactionInputs(_defaultTx);
// Setup a dummy output root proof for reuse.
_outputRootProof = Types.OutputRootProof({
version: bytes32(uint256(0)),
stateRoot: _stateRoot,
messagePasserStorageRoot: _storageRoot,
latestBlockhash: bytes32(uint256(0))
});
_proposedBlockNumber = l2OutputOracle.nextBlockNumber();
_proposedOutputIndex = l2OutputOracle.nextOutputIndex();
}
/// @dev Setup the system for a ready-to-use state.
function setUp() public virtual override {
// Configure the oracle to return the output root we've prepared.
vm.warp(l2OutputOracle.computeL2Timestamp(_proposedBlockNumber) + 1);
vm.prank(l2OutputOracle.PROPOSER());
l2OutputOracle.proposeL2Output(_outputRoot, _proposedBlockNumber, 0, 0);
// Warp beyond the finalization period for the block we've proposed.
vm.warp(
l2OutputOracle.getL2Output(_proposedOutputIndex).timestamp + l2OutputOracle.FINALIZATION_PERIOD_SECONDS()
+ 1
);
// Fund the portal so that we can withdraw ETH.
vm.deal(address(optimismPortal), 0xFFFFFFFF);
}
/// @dev Asserts that the reentrant call will revert.
function callPortalAndExpectRevert() external payable {
vm.expectRevert(NonReentrant.selector);
// Arguments here don't matter, as the require check is the first thing that happens.
// We assume that this has already been proven.
optimismPortal.finalizeWithdrawalTransaction(_defaultTx);
// Assert that the withdrawal was not finalized.
assertFalse(optimismPortal.finalizedWithdrawals(Hashing.hashWithdrawal(_defaultTx)));
}
/// @dev Tests that `proveWithdrawalTransaction` reverts when paused.
function test_proveWithdrawalTransaction_paused_reverts() external {
vm.prank(optimismPortal.guardian());
superchainConfig.pause("identifier");
vm.expectRevert(CallPaused.selector);
optimismPortal.proveWithdrawalTransaction({
_tx: _defaultTx,
_l2OutputIndex: _proposedOutputIndex,
_outputRootProof: _outputRootProof,
_withdrawalProof: _withdrawalProof
});
}
/// @dev Tests that `proveWithdrawalTransaction` reverts when the target is the portal contract.
function test_proveWithdrawalTransaction_onSelfCall_reverts() external {
_defaultTx.target = address(optimismPortal);
vm.expectRevert(BadTarget.selector);
optimismPortal.proveWithdrawalTransaction(_defaultTx, _proposedOutputIndex, _outputRootProof, _withdrawalProof);
}
/// @dev Tests that `proveWithdrawalTransaction` reverts when
/// the outputRootProof does not match the output root
function test_proveWithdrawalTransaction_onInvalidOutputRootProof_reverts() external {
// Modify the version to invalidate the withdrawal proof.
_outputRootProof.version = bytes32(uint256(1));
vm.expectRevert("OptimismPortal: invalid output root proof");
optimismPortal.proveWithdrawalTransaction(_defaultTx, _proposedOutputIndex, _outputRootProof, _withdrawalProof);
}
/// @dev Tests that `proveWithdrawalTransaction` reverts when the withdrawal is missing.
function test_proveWithdrawalTransaction_onInvalidWithdrawalProof_reverts() external {
// modify the default test values to invalidate the proof.
_defaultTx.data = hex"abcd";
vm.expectRevert("MerkleTrie: path remainder must share all nibbles with key");
optimismPortal.proveWithdrawalTransaction(_defaultTx, _proposedOutputIndex, _outputRootProof, _withdrawalProof);
}
/// @dev Tests that `proveWithdrawalTransaction` reverts when the withdrawal has already
/// been proven.
function test_proveWithdrawalTransaction_replayProve_reverts() external {
vm.expectEmit(true, true, true, true);
emit WithdrawalProven(_withdrawalHash, alice, bob);
optimismPortal.proveWithdrawalTransaction(_defaultTx, _proposedOutputIndex, _outputRootProof, _withdrawalProof);
vm.expectRevert("OptimismPortal: withdrawal hash has already been proven");
optimismPortal.proveWithdrawalTransaction(_defaultTx, _proposedOutputIndex, _outputRootProof, _withdrawalProof);
}
/// @dev Tests that `proveWithdrawalTransaction` succeeds when the withdrawal has already
/// been proven and the output root has changed and the l2BlockNumber stays the same.
function test_proveWithdrawalTransaction_replayProveChangedOutputRoot_succeeds() external {
vm.expectEmit(true, true, true, true);
emit WithdrawalProven(_withdrawalHash, alice, bob);
optimismPortal.proveWithdrawalTransaction(_defaultTx, _proposedOutputIndex, _outputRootProof, _withdrawalProof);
// Compute the storage slot of the outputRoot corresponding to the `withdrawalHash`
// inside of the `provenWithdrawal`s mapping.
bytes32 slot;
assembly {
mstore(0x00, sload(_withdrawalHash.slot))
mstore(0x20, 52) // 52 is the slot of the `provenWithdrawals` mapping in the OptimismPortal
slot := keccak256(0x00, 0x40)
}
// Store a different output root within the `provenWithdrawals` mapping without
// touching the l2BlockNumber or timestamp.
vm.store(address(optimismPortal), slot, bytes32(0));
// Warp ahead 1 second
vm.warp(block.timestamp + 1);
// Even though we have already proven this withdrawalHash, we should be allowed to re-submit
// our proof with a changed outputRoot
vm.expectEmit(true, true, true, true);
emit WithdrawalProven(_withdrawalHash, alice, bob);
optimismPortal.proveWithdrawalTransaction(_defaultTx, _proposedOutputIndex, _outputRootProof, _withdrawalProof);
// Ensure that the withdrawal was updated within the mapping
(, uint128 timestamp,) = optimismPortal.provenWithdrawals(_withdrawalHash);
assertEq(timestamp, block.timestamp);
}
/// @dev Tests that `proveWithdrawalTransaction` succeeds when the withdrawal has already
/// been proven and the output root, output index, and l2BlockNumber have changed.
function test_proveWithdrawalTransaction_replayProveChangedOutputRootAndOutputIndex_succeeds() external {
vm.expectEmit(true, true, true, true);
emit WithdrawalProven(_withdrawalHash, alice, bob);
optimismPortal.proveWithdrawalTransaction(_defaultTx, _proposedOutputIndex, _outputRootProof, _withdrawalProof);
// Compute the storage slot of the outputRoot corresponding to the `withdrawalHash`
// inside of the `provenWithdrawal`s mapping.
bytes32 slot;
assembly {
mstore(0x00, sload(_withdrawalHash.slot))
mstore(0x20, 52) // 52 is the slot of the `provenWithdrawals` mapping in OptimismPortal
slot := keccak256(0x00, 0x40)
}
// Store a dummy output root within the `provenWithdrawals` mapping without touching the
// l2BlockNumber or timestamp.
vm.store(address(optimismPortal), slot, bytes32(0));
// Fetch the output proposal at `_proposedOutputIndex` from the L2OutputOracle
Types.OutputProposal memory proposal = optimismPortal.l2Oracle().getL2Output(_proposedOutputIndex);
// Propose the same output root again, creating the same output at a different index + l2BlockNumber.
vm.startPrank(optimismPortal.l2Oracle().PROPOSER());
optimismPortal.l2Oracle().proposeL2Output(
proposal.outputRoot, optimismPortal.l2Oracle().nextBlockNumber(), blockhash(block.number), block.number
);
vm.stopPrank();
// Warp ahead 1 second
vm.warp(block.timestamp + 1);
// Even though we have already proven this withdrawalHash, we should be allowed to re-submit
// our proof with a changed outputRoot + a different output index
vm.expectEmit(true, true, true, true);
emit WithdrawalProven(_withdrawalHash, alice, bob);
optimismPortal.proveWithdrawalTransaction(
_defaultTx, _proposedOutputIndex + 1, _outputRootProof, _withdrawalProof
);
// Ensure that the withdrawal was updated within the mapping
(, uint128 timestamp,) = optimismPortal.provenWithdrawals(_withdrawalHash);
assertEq(timestamp, block.timestamp);
}
/// @dev Tests that `proveWithdrawalTransaction` succeeds.
function test_proveWithdrawalTransaction_validWithdrawalProof_succeeds() external {
vm.expectEmit(true, true, true, true);
emit WithdrawalProven(_withdrawalHash, alice, bob);
optimismPortal.proveWithdrawalTransaction(_defaultTx, _proposedOutputIndex, _outputRootProof, _withdrawalProof);
}
/// @dev Tests that `finalizeWithdrawalTransaction` succeeds.
function test_finalizeWithdrawalTransaction_provenWithdrawalHashEther_succeeds() external {
uint256 bobBalanceBefore = address(bob).balance;
vm.expectEmit(true, true, true, true);
emit WithdrawalProven(_withdrawalHash, alice, bob);
optimismPortal.proveWithdrawalTransaction(_defaultTx, _proposedOutputIndex, _outputRootProof, _withdrawalProof);
vm.warp(block.timestamp + l2OutputOracle.FINALIZATION_PERIOD_SECONDS() + 1);
vm.expectEmit(true, true, false, true);
emit WithdrawalFinalized(_withdrawalHash, true);
optimismPortal.finalizeWithdrawalTransaction(_defaultTx);
assertEq(address(bob).balance, bobBalanceBefore + 100);
}
/// @dev Tests that `finalizeWithdrawalTransaction` succeeds.
function test_finalizeWithdrawalTransaction_provenWithdrawalHashNonEtherTargetToken_reverts() external {
vm.mockCall(
address(systemConfig),
abi.encodeCall(systemConfig.gasPayingToken, ()),
abi.encode(address(_defaultTx.target), 18)
);
optimismPortal.proveWithdrawalTransaction(_defaultTx, _proposedOutputIndex, _outputRootProof, _withdrawalProof);
vm.warp(block.timestamp + l2OutputOracle.FINALIZATION_PERIOD_SECONDS() + 1);
vm.expectRevert(BadTarget.selector);
optimismPortal.finalizeWithdrawalTransaction(_defaultTx);
}
/// @dev Tests that `finalizeWithdrawalTransaction` reverts if the contract is paused.
function test_finalizeWithdrawalTransaction_paused_reverts() external {
vm.prank(optimismPortal.guardian());
superchainConfig.pause("identifier");
vm.expectRevert(CallPaused.selector);
optimismPortal.finalizeWithdrawalTransaction(_defaultTx);
}
/// @dev Tests that `finalizeWithdrawalTransaction` reverts if the withdrawal has not been
function test_finalizeWithdrawalTransaction_ifWithdrawalNotProven_reverts() external {
uint256 bobBalanceBefore = address(bob).balance;
vm.expectRevert("OptimismPortal: withdrawal has not been proven yet");
optimismPortal.finalizeWithdrawalTransaction(_defaultTx);
assert(address(bob).balance == bobBalanceBefore);
}
/// @dev Tests that `finalizeWithdrawalTransaction` reverts if the withdrawal has not been
/// proven long enough ago.
function test_finalizeWithdrawalTransaction_ifWithdrawalProofNotOldEnough_reverts() external {
uint256 bobBalanceBefore = address(bob).balance;
vm.expectEmit(true, true, true, true);
emit WithdrawalProven(_withdrawalHash, alice, bob);
optimismPortal.proveWithdrawalTransaction(_defaultTx, _proposedOutputIndex, _outputRootProof, _withdrawalProof);
// Mock a call where the resulting output root is anything but the original output root. In
// this case we just use bytes32(uint256(1)).
vm.mockCall(
address(optimismPortal.l2Oracle()),
abi.encodePacked(IL2OutputOracle.getL2Output.selector),
abi.encode(bytes32(uint256(1)), _proposedBlockNumber)
);
vm.expectRevert("OptimismPortal: proven withdrawal finalization period has not elapsed");
optimismPortal.finalizeWithdrawalTransaction(_defaultTx);
assert(address(bob).balance == bobBalanceBefore);
}
/// @dev Tests that `finalizeWithdrawalTransaction` reverts if the provenWithdrawal's timestamp
/// is less than the L2 output oracle's starting timestamp.
function test_finalizeWithdrawalTransaction_timestampLessThanL2OracleStart_reverts() external {
uint256 bobBalanceBefore = address(bob).balance;
// Prove our withdrawal
vm.expectEmit(true, true, true, true);
emit WithdrawalProven(_withdrawalHash, alice, bob);
optimismPortal.proveWithdrawalTransaction(_defaultTx, _proposedOutputIndex, _outputRootProof, _withdrawalProof);
// Warp to after the finalization period
vm.warp(block.timestamp + l2OutputOracle.FINALIZATION_PERIOD_SECONDS() + 1);
// Mock a startingTimestamp change on the L2 Oracle
vm.mockCall(
address(optimismPortal.l2Oracle()),
abi.encodeCall(IL2OutputOracle.startingTimestamp, ()),
abi.encode(block.timestamp + 1)
);
// Attempt to finalize the withdrawal
vm.expectRevert("OptimismPortal: withdrawal timestamp less than L2 Oracle starting timestamp");
optimismPortal.finalizeWithdrawalTransaction(_defaultTx);
// Ensure that bob's balance has remained the same
assertEq(bobBalanceBefore, address(bob).balance);
}
/// @dev Tests that `finalizeWithdrawalTransaction` reverts if the output root proven is not the
/// same as the output root at the time of finalization.
function test_finalizeWithdrawalTransaction_ifOutputRootChanges_reverts() external {
uint256 bobBalanceBefore = address(bob).balance;
// Prove our withdrawal
vm.expectEmit(true, true, true, true);
emit WithdrawalProven(_withdrawalHash, alice, bob);
optimismPortal.proveWithdrawalTransaction(_defaultTx, _proposedOutputIndex, _outputRootProof, _withdrawalProof);
// Warp to after the finalization period
vm.warp(block.timestamp + l2OutputOracle.FINALIZATION_PERIOD_SECONDS() + 1);
// Mock an outputRoot change on the output proposal before attempting
// to finalize the withdrawal.
vm.mockCall(
address(optimismPortal.l2Oracle()),
abi.encodePacked(IL2OutputOracle.getL2Output.selector),
abi.encode(
Types.OutputProposal(bytes32(uint256(0)), uint128(block.timestamp), uint128(_proposedBlockNumber))
)
);
// Attempt to finalize the withdrawal
vm.expectRevert("OptimismPortal: output root proven is not the same as current output root");
optimismPortal.finalizeWithdrawalTransaction(_defaultTx);
// Ensure that bob's balance has remained the same
assertEq(bobBalanceBefore, address(bob).balance);
}
/// @dev Tests that `finalizeWithdrawalTransaction` reverts if the output proposal's timestamp
/// has not passed the finalization period.
function test_finalizeWithdrawalTransaction_ifOutputTimestampIsNotFinalized_reverts() external {
uint256 bobBalanceBefore = address(bob).balance;
// Prove our withdrawal
vm.expectEmit(true, true, true, true);
emit WithdrawalProven(_withdrawalHash, alice, bob);
optimismPortal.proveWithdrawalTransaction(_defaultTx, _proposedOutputIndex, _outputRootProof, _withdrawalProof);
// Warp to after the finalization period
vm.warp(block.timestamp + l2OutputOracle.FINALIZATION_PERIOD_SECONDS() + 1);
// Mock a timestamp change on the output proposal that has not passed the
// finalization period.
vm.mockCall(
address(optimismPortal.l2Oracle()),
abi.encodePacked(IL2OutputOracle.getL2Output.selector),
abi.encode(Types.OutputProposal(_outputRoot, uint128(block.timestamp + 1), uint128(_proposedBlockNumber)))
);
// Attempt to finalize the withdrawal
vm.expectRevert("OptimismPortal: output proposal finalization period has not elapsed");
optimismPortal.finalizeWithdrawalTransaction(_defaultTx);
// Ensure that bob's balance has remained the same
assertEq(bobBalanceBefore, address(bob).balance);
}
/// @dev Tests that `finalizeWithdrawalTransaction` fails if the target reverts.
function test_finalizeWithdrawalTransaction_targetFails_fails() external {
uint256 bobBalanceBefore = address(bob).balance;
vm.etch(bob, hex"fe"); // Contract with just the invalid opcode.
vm.expectEmit(true, true, true, true);
emit WithdrawalProven(_withdrawalHash, alice, bob);
optimismPortal.proveWithdrawalTransaction(_defaultTx, _proposedOutputIndex, _outputRootProof, _withdrawalProof);
vm.warp(block.timestamp + l2OutputOracle.FINALIZATION_PERIOD_SECONDS() + 1);
vm.expectEmit(true, true, true, true);
emit WithdrawalFinalized(_withdrawalHash, false);
optimismPortal.finalizeWithdrawalTransaction(_defaultTx);
assert(address(bob).balance == bobBalanceBefore);
}
/// @dev Tests that `finalizeWithdrawalTransaction` reverts if the target reverts and caller is the
/// ESTIMATION_ADDRESS.
function test_finalizeWithdrawalTransaction_targetFailsAndCallerIsEstimationAddress_reverts() external {
vm.etch(bob, hex"fe"); // Contract with just the invalid opcode.
vm.expectEmit(true, true, true, true);
emit WithdrawalProven(_withdrawalHash, alice, bob);
optimismPortal.proveWithdrawalTransaction(_defaultTx, _proposedOutputIndex, _outputRootProof, _withdrawalProof);
vm.warp(block.timestamp + l2OutputOracle.FINALIZATION_PERIOD_SECONDS() + 1);
vm.startPrank(Constants.ESTIMATION_ADDRESS, Constants.ESTIMATION_ADDRESS);
vm.expectRevert(GasEstimation.selector);
optimismPortal.finalizeWithdrawalTransaction(_defaultTx);
}
/// @dev Tests that `finalizeWithdrawalTransaction` succeeds when _tx.data is empty.
function test_finalizeWithdrawalTransaction_noTxData_succeeds() external {
Types.WithdrawalTransaction memory _defaultTx_noData = Types.WithdrawalTransaction({
nonce: 0,
sender: alice,
target: bob,
value: 100,
gasLimit: 100_000,
data: hex""
});
// Get withdrawal proof data we can use for testing.
(
bytes32 _stateRoot_noData,
bytes32 _storageRoot_noData,
bytes32 _outputRoot_noData,
bytes32 _withdrawalHash_noData,
bytes[] memory _withdrawalProof_noData
) = ffi.getProveWithdrawalTransactionInputs(_defaultTx_noData);
// Setup a dummy output root proof for reuse.
Types.OutputRootProof memory _outputRootProof_noData = Types.OutputRootProof({
version: bytes32(uint256(0)),
stateRoot: _stateRoot_noData,
messagePasserStorageRoot: _storageRoot_noData,
latestBlockhash: bytes32(uint256(0))
});
// Configure the oracle to return the output root we've prepared.
vm.mockCall(
address(l2OutputOracle),
abi.encodePacked(IL2OutputOracle.getL2Output.selector),
abi.encode(
Types.OutputProposal(
_outputRoot_noData,
l2OutputOracle.getL2Output(_proposedOutputIndex).timestamp,
uint128(_proposedBlockNumber)
)
)
);
// Fund the portal so that we can withdraw ETH.
vm.store(address(optimismPortal), bytes32(uint256(61)), bytes32(uint256(0xFFFFFFFF)));
vm.deal(address(optimismPortal), 0xFFFFFFFF);
uint256 bobBalanceBefore = bob.balance;
vm.expectEmit(true, true, true, true);
emit WithdrawalProven(_withdrawalHash_noData, alice, bob);
optimismPortal.proveWithdrawalTransaction(
_defaultTx_noData, _proposedOutputIndex, _outputRootProof_noData, _withdrawalProof_noData
);
vm.warp(block.timestamp + l2OutputOracle.FINALIZATION_PERIOD_SECONDS() + 1);
vm.expectEmit(true, true, false, true);
emit WithdrawalFinalized(_withdrawalHash_noData, true);
optimismPortal.finalizeWithdrawalTransaction(_defaultTx_noData);
assertEq(bob.balance, bobBalanceBefore + 100);
}
/// @dev Tests that `finalizeWithdrawalTransaction` succeeds when _tx.data is empty and with a custom gas token.
function test_finalizeWithdrawalTransaction_noTxDataNonEtherGasToken_succeeds() external {
Types.WithdrawalTransaction memory _defaultTx_noData = Types.WithdrawalTransaction({
nonce: 0,
sender: alice,
target: bob,
value: 100,
gasLimit: 100_000,
data: hex""
});
// Get withdrawal proof data we can use for testing.
(
bytes32 _stateRoot_noData,
bytes32 _storageRoot_noData,
bytes32 _outputRoot_noData,
bytes32 _withdrawalHash_noData,
bytes[] memory _withdrawalProof_noData
) = ffi.getProveWithdrawalTransactionInputs(_defaultTx_noData);
// Setup a dummy output root proof for reuse.
Types.OutputRootProof memory _outputRootProof_noData = Types.OutputRootProof({
version: bytes32(uint256(0)),
stateRoot: _stateRoot_noData,
messagePasserStorageRoot: _storageRoot_noData,
latestBlockhash: bytes32(uint256(0))
});
// Configure the oracle to return the output root we've prepared.
vm.mockCall(
address(l2OutputOracle),
abi.encodePacked(IL2OutputOracle.getL2Output.selector),
abi.encode(
Types.OutputProposal(
_outputRoot_noData,
l2OutputOracle.getL2Output(_proposedOutputIndex).timestamp,
uint128(_proposedBlockNumber)
)
)
);
// Fund the portal so that we can withdraw ETH.
vm.store(address(optimismPortal), bytes32(uint256(61)), bytes32(uint256(0xFFFFFFFF)));
deal(address(L1Token), address(optimismPortal), 0xFFFFFFFF);
// modify the gas token to be non ether
vm.mockCall(
address(systemConfig), abi.encodeCall(systemConfig.gasPayingToken, ()), abi.encode(address(L1Token), 18)
);
uint256 bobBalanceBefore = L1Token.balanceOf(bob);
vm.expectEmit(true, true, true, true);
emit WithdrawalProven(_withdrawalHash_noData, alice, bob);
optimismPortal.proveWithdrawalTransaction(
_defaultTx_noData, _proposedOutputIndex, _outputRootProof_noData, _withdrawalProof_noData
);
vm.warp(block.timestamp + l2OutputOracle.FINALIZATION_PERIOD_SECONDS() + 1);
vm.expectEmit(true, true, false, true);
emit WithdrawalFinalized(_withdrawalHash_noData, true);
optimismPortal.finalizeWithdrawalTransaction(_defaultTx_noData);
assertEq(L1Token.balanceOf(bob), bobBalanceBefore + 100);
}
/// @dev Tests that `finalizeWithdrawalTransaction` reverts if the finalization period
/// has not yet passed.
function test_finalizeWithdrawalTransaction_onRecentWithdrawal_reverts() external {
// Setup the Oracle to return an output with a recent timestamp
uint256 recentTimestamp = block.timestamp - 1;
vm.mockCall(
address(optimismPortal.l2Oracle()),
abi.encodePacked(IL2OutputOracle.getL2Output.selector),
abi.encode(Types.OutputProposal(_outputRoot, uint128(recentTimestamp), uint128(_proposedBlockNumber)))
);
optimismPortal.proveWithdrawalTransaction(_defaultTx, _proposedOutputIndex, _outputRootProof, _withdrawalProof);
vm.expectRevert("OptimismPortal: proven withdrawal finalization period has not elapsed");
optimismPortal.finalizeWithdrawalTransaction(_defaultTx);
}
/// @dev Tests that `finalizeWithdrawalTransaction` reverts if the withdrawal has already been
/// finalized.
function test_finalizeWithdrawalTransaction_onReplay_reverts() external {
vm.expectEmit(true, true, true, true);
emit WithdrawalProven(_withdrawalHash, alice, bob);
optimismPortal.proveWithdrawalTransaction(_defaultTx, _proposedOutputIndex, _outputRootProof, _withdrawalProof);
vm.warp(block.timestamp + l2OutputOracle.FINALIZATION_PERIOD_SECONDS() + 1);
vm.expectEmit(true, true, true, true);
emit WithdrawalFinalized(_withdrawalHash, true);
optimismPortal.finalizeWithdrawalTransaction(_defaultTx);
vm.expectRevert("OptimismPortal: withdrawal has already been finalized");
optimismPortal.finalizeWithdrawalTransaction(_defaultTx);
}
/// @dev Tests that `finalizeWithdrawalTransaction` reverts if the withdrawal transaction
/// does not have enough gas to execute.
function test_finalizeWithdrawalTransaction_onInsufficientGas_reverts() external {
// This number was identified through trial and error.
uint256 gasLimit = 150_000;
Types.WithdrawalTransaction memory insufficientGasTx = Types.WithdrawalTransaction({
nonce: 0,
sender: alice,
target: bob,
value: 100,
gasLimit: gasLimit,
data: hex""
});
// Get updated proof inputs.
(bytes32 stateRoot, bytes32 storageRoot,,, bytes[] memory withdrawalProof) =
ffi.getProveWithdrawalTransactionInputs(insufficientGasTx);
Types.OutputRootProof memory outputRootProof = Types.OutputRootProof({
version: bytes32(0),
stateRoot: stateRoot,
messagePasserStorageRoot: storageRoot,
latestBlockhash: bytes32(0)
});
vm.mockCall(
address(optimismPortal.l2Oracle()),
abi.encodePacked(IL2OutputOracle.getL2Output.selector),
abi.encode(
Types.OutputProposal(
Hashing.hashOutputRootProof(outputRootProof),
uint128(block.timestamp),
uint128(_proposedBlockNumber)
)
)
);
optimismPortal.proveWithdrawalTransaction(
insufficientGasTx, _proposedOutputIndex, outputRootProof, withdrawalProof
);
vm.warp(block.timestamp + l2OutputOracle.FINALIZATION_PERIOD_SECONDS() + 1);
vm.expectRevert("SafeCall: Not enough gas");
optimismPortal.finalizeWithdrawalTransaction{ gas: gasLimit }(insufficientGasTx);
}
/// @dev Tests that `finalizeWithdrawalTransaction` reverts if a sub-call attempts to finalize
/// another withdrawal.
function test_finalizeWithdrawalTransaction_onReentrancy_reverts() external {
uint256 bobBalanceBefore = address(bob).balance;
// Copy and modify the default test values to attempt a reentrant call by first calling to
// this contract's callPortalAndExpectRevert() function above.
Types.WithdrawalTransaction memory _testTx = _defaultTx;
_testTx.target = address(this);
_testTx.data = abi.encodeCall(this.callPortalAndExpectRevert, ());
// Get modified proof inputs.
(
bytes32 stateRoot,
bytes32 storageRoot,
bytes32 outputRoot,
bytes32 withdrawalHash,
bytes[] memory withdrawalProof
) = ffi.getProveWithdrawalTransactionInputs(_testTx);
Types.OutputRootProof memory outputRootProof = Types.OutputRootProof({
version: bytes32(0),
stateRoot: stateRoot,
messagePasserStorageRoot: storageRoot,
latestBlockhash: bytes32(0)
});
// Setup the Oracle to return the outputRoot we want as well as a finalized timestamp.
uint256 finalizedTimestamp = block.timestamp - l2OutputOracle.FINALIZATION_PERIOD_SECONDS() - 1;
vm.mockCall(
address(optimismPortal.l2Oracle()),
abi.encodePacked(IL2OutputOracle.getL2Output.selector),
abi.encode(Types.OutputProposal(outputRoot, uint128(finalizedTimestamp), uint128(_proposedBlockNumber)))
);
vm.expectEmit(true, true, true, true);
emit WithdrawalProven(withdrawalHash, alice, address(this));
optimismPortal.proveWithdrawalTransaction(_testTx, _proposedBlockNumber, outputRootProof, withdrawalProof);
vm.warp(block.timestamp + l2OutputOracle.FINALIZATION_PERIOD_SECONDS() + 1);
vm.expectCall(address(this), _testTx.data);
vm.expectEmit(true, true, true, true);
emit WithdrawalFinalized(withdrawalHash, true);
optimismPortal.finalizeWithdrawalTransaction(_testTx);
// Ensure that bob's balance was not changed by the reentrant call.
assert(address(bob).balance == bobBalanceBefore);
}
/// @dev Tests that `finalizeWithdrawalTransaction` succeeds.
function testDiff_finalizeWithdrawalTransaction_succeeds(
address _sender,
address _target,
uint256 _value,
uint256 _gasLimit,
bytes memory _data
)
external
{
vm.assume(
_target != address(optimismPortal) // Cannot call the optimism portal or a contract
&& _target.code.length == 0 // No accounts with code
&& _target != CONSOLE // The console has no code but behaves like a contract
&& uint160(_target) > 9 // No precompiles (or zero address)
);
// Total ETH supply is currently about 120M ETH.
uint256 value = bound(_value, 0, 200_000_000 ether);
vm.deal(address(optimismPortal), value);
uint256 gasLimit = bound(_gasLimit, 0, 50_000_000);
uint256 nonce = l2ToL1MessagePasser.messageNonce();
// Get a withdrawal transaction and mock proof from the differential testing script.
Types.WithdrawalTransaction memory _tx = Types.WithdrawalTransaction({
nonce: nonce,
sender: _sender,
target: _target,
value: value,
gasLimit: gasLimit,
data: _data
});
(
bytes32 stateRoot,
bytes32 storageRoot,
bytes32 outputRoot,
bytes32 withdrawalHash,
bytes[] memory withdrawalProof
) = ffi.getProveWithdrawalTransactionInputs(_tx);
// Create the output root proof
Types.OutputRootProof memory proof = Types.OutputRootProof({
version: bytes32(uint256(0)),
stateRoot: stateRoot,
messagePasserStorageRoot: storageRoot,
latestBlockhash: bytes32(uint256(0))
});
// Ensure the values returned from ffi are correct
assertEq(outputRoot, Hashing.hashOutputRootProof(proof));
assertEq(withdrawalHash, Hashing.hashWithdrawal(_tx));
// Setup the Oracle to return the outputRoot
vm.mockCall(
address(l2OutputOracle),
abi.encodePacked(l2OutputOracle.getL2Output.selector),
abi.encode(outputRoot, block.timestamp, 100)
);
// Prove the withdrawal transaction
optimismPortal.proveWithdrawalTransaction(
_tx,
100, // l2BlockNumber
proof,
withdrawalProof
);
(bytes32 _root,,) = optimismPortal.provenWithdrawals(withdrawalHash);
assertTrue(_root != bytes32(0));
// Warp past the finalization period
vm.warp(block.timestamp + l2OutputOracle.FINALIZATION_PERIOD_SECONDS() + 1);
// Finalize the withdrawal transaction
vm.expectCallMinGas(_tx.target, _tx.value, uint64(_tx.gasLimit), _tx.data);
optimismPortal.finalizeWithdrawalTransaction(_tx);
assertTrue(optimismPortal.finalizedWithdrawals(withdrawalHash));
}
}
contract OptimismPortalUpgradeable_Test is CommonTest {
/// @dev Tests that the proxy is initialized correctly.
function test_params_initValuesOnProxy_succeeds() external {
// TODO(opcm upgrades): remove skip once upgrade path is implemented
skipIfForkTest("OptimismPortal_Test: resource config varies on mainnet");
(uint128 prevBaseFee, uint64 prevBoughtGas, uint64 prevBlockNum) = optimismPortal.params();
IResourceMetering.ResourceConfig memory rcfg = systemConfig.resourceConfig();
assertEq(prevBaseFee, rcfg.minimumBaseFee);
assertEq(prevBoughtGas, 0);
assertEq(prevBlockNum, block.number);
}
/// @dev Tests that the proxy can be upgraded.
function test_upgradeToAndCall_upgrading_succeeds() external {
// Check an unused slot before upgrading.
bytes32 slot21Before = vm.load(address(optimismPortal), bytes32(uint256(21)));
assertEq(bytes32(0), slot21Before);
NextImpl nextImpl = new NextImpl();
vm.startPrank(EIP1967Helper.getAdmin(address(optimismPortal)));
// The value passed to the initialize must be larger than the last value
// that initialize was called with.
IProxy(payable(address(optimismPortal))).upgradeToAndCall(
address(nextImpl), abi.encodeCall(NextImpl.initialize, (2))
);
assertEq(IProxy(payable(address(optimismPortal))).implementation(), address(nextImpl));
// Verify that the NextImpl contract initialized its values according as expected
bytes32 slot21After = vm.load(address(optimismPortal), bytes32(uint256(21)));
bytes32 slot21Expected = NextImpl(address(optimismPortal)).slot21Init();
assertEq(slot21Expected, slot21After);
}
}
/// @title OptimismPortalResourceFuzz_Test
/// @dev Test various values of the resource metering config to ensure that deposits cannot be
/// broken by changing the config.
contract OptimismPortalResourceFuzz_Test is CommonTest {
/// @dev The max gas limit observed throughout this test. Setting this too high can cause
/// the test to take too long to run.
uint256 constant MAX_GAS_LIMIT = 30_000_000;
/// @dev Test that various values of the resource metering config will not break deposits.
function testFuzz_systemConfigDeposit_succeeds(
uint32 _maxResourceLimit,
uint8 _elasticityMultiplier,
uint8 _baseFeeMaxChangeDenominator,
uint32 _minimumBaseFee,
uint32 _systemTxMaxGas,
uint128 _maximumBaseFee,
uint64 _gasLimit,
uint64 _prevBoughtGas,
uint128 _prevBaseFee,
uint8 _blockDiff
)
external
{
// Get the set system gas limit
uint64 gasLimit = systemConfig.gasLimit();
// Bound resource config
_systemTxMaxGas = uint32(bound(_systemTxMaxGas, 0, gasLimit - 21000));
_maxResourceLimit = uint32(bound(_maxResourceLimit, 21000, MAX_GAS_LIMIT / 8));
_maxResourceLimit = uint32(bound(_maxResourceLimit, 21000, gasLimit - _systemTxMaxGas));
_maximumBaseFee = uint128(bound(_maximumBaseFee, 1, type(uint128).max));
_minimumBaseFee = uint32(bound(_minimumBaseFee, 0, _maximumBaseFee - 1));
_gasLimit = uint64(bound(_gasLimit, 21000, _maxResourceLimit));
_gasLimit = uint64(bound(_gasLimit, 0, gasLimit));
_prevBaseFee = uint128(bound(_prevBaseFee, 0, 3 gwei));
_prevBoughtGas = uint64(bound(_prevBoughtGas, 0, _maxResourceLimit - _gasLimit));
_blockDiff = uint8(bound(_blockDiff, 0, 3));
_baseFeeMaxChangeDenominator = uint8(bound(_baseFeeMaxChangeDenominator, 2, type(uint8).max));
_elasticityMultiplier = uint8(bound(_elasticityMultiplier, 1, type(uint8).max));
// Prevent values that would cause reverts
vm.assume(uint256(_maxResourceLimit) + uint256(_systemTxMaxGas) <= gasLimit);
vm.assume(((_maxResourceLimit / _elasticityMultiplier) * _elasticityMultiplier) == _maxResourceLimit);
// Although we typically want to limit the usage of vm.assume, we've constructed the above
// bounds to satisfy the assumptions listed in this specific section. These assumptions
// serve only to act as an additional sanity check on top of the bounds and should not
// result in an unnecessary number of test rejections.
vm.assume(gasLimit >= _gasLimit);
vm.assume(_minimumBaseFee < _maximumBaseFee);
// Base fee can increase quickly and mean that we can't buy the amount of gas we want.
// Here we add a VM assumption to bound the potential increase.
// Compute the maximum possible increase in base fee.
uint256 maxPercentIncrease = uint256(_elasticityMultiplier - 1) * 100 / uint256(_baseFeeMaxChangeDenominator);
// Assume that we have enough gas to burn.
// Compute the maximum amount of gas we'd need to burn.
// Assume we need 1/5 of our gas to do other stuff.
vm.assume(_prevBaseFee * maxPercentIncrease * _gasLimit / 100 < MAX_GAS_LIMIT * 4 / 5);
// Pick a pseudorandom block number
vm.roll(uint256(keccak256(abi.encode(_blockDiff))) % uint256(type(uint16).max) + uint256(_blockDiff));
// Create a resource config to mock the call to the system config with
IResourceMetering.ResourceConfig memory rcfg = IResourceMetering.ResourceConfig({
maxResourceLimit: _maxResourceLimit,
elasticityMultiplier: _elasticityMultiplier,
baseFeeMaxChangeDenominator: _baseFeeMaxChangeDenominator,
minimumBaseFee: _minimumBaseFee,
systemTxMaxGas: _systemTxMaxGas,
maximumBaseFee: _maximumBaseFee
});
vm.mockCall(address(systemConfig), abi.encodeCall(systemConfig.resourceConfig, ()), abi.encode(rcfg));
// Set the resource params
uint256 _prevBlockNum = block.number - _blockDiff;
vm.store(
address(optimismPortal),
bytes32(uint256(1)),
bytes32((_prevBlockNum << 192) | (uint256(_prevBoughtGas) << 128) | _prevBaseFee)
);
// Ensure that the storage setting is correct
(uint128 prevBaseFee, uint64 prevBoughtGas, uint64 prevBlockNum) = optimismPortal.params();
assertEq(prevBaseFee, _prevBaseFee);
assertEq(prevBoughtGas, _prevBoughtGas);
assertEq(prevBlockNum, _prevBlockNum);
// Do a deposit, should not revert
optimismPortal.depositTransaction{ gas: MAX_GAS_LIMIT }({
_to: address(0x20),
_value: 0x40,
_gasLimit: _gasLimit,
_isCreation: false,
_data: hex""
});
}
}
contract OptimismPortalWithMockERC20_Test is OptimismPortal_FinalizeWithdrawal_Test {
MockERC20 token;
function setUp() public override {
super.setUp();
token = new MockERC20("Test", "TST", 18);
}
function depositERC20Transaction(
address _from,
address _to,
uint256 _mint,
uint256 _value,
uint64 _gasLimit,
bool _isCreation,
bytes calldata _data
)
internal
{
if (_isCreation) {
_to = address(0);
}
if (_data.length > 120_000) {
_data = _data[0:120_000];
}
IResourceMetering.ResourceConfig memory rcfg = systemConfig.resourceConfig();
_gasLimit =
uint64(bound(_gasLimit, optimismPortal.minimumGasLimit(uint64(_data.length)), rcfg.maxResourceLimit));
// Mint the token to the contract and approve the token for the portal
token.mint(address(this), _mint);
token.approve(address(optimismPortal), _mint);
// Mock the gas paying token to be the ERC20 token
vm.mockCall(
address(systemConfig), abi.encodeCall(systemConfig.gasPayingToken, ()), abi.encode(address(token), 18)
);
bytes memory opaqueData = abi.encodePacked(_mint, _value, _gasLimit, _isCreation, _data);
vm.expectEmit(address(optimismPortal));
emit TransactionDeposited(
_from, // from
_to,
uint256(0), // DEPOSIT_VERSION
opaqueData
);
// Deposit the token into the portal
optimismPortal.depositERC20Transaction(_to, _mint, _value, _gasLimit, _isCreation, _data);
// Assert final balance equals the deposited amount
assertEq(token.balanceOf(address(optimismPortal)), _mint);
assertEq(optimismPortal.balance(), _mint);
}
/// @dev Tests that `depositERC20Transaction` succeeds when msg.sender == tx.origin.
function testFuzz_depositERC20Transaction_senderIsOrigin_succeeds(
address _to,
uint256 _mint,
uint256 _value,
uint64 _gasLimit,
bool _isCreation,
bytes calldata _data
)
external
{
// Ensure that msg.sender == tx.origin
vm.startPrank(address(this), address(this));
depositERC20Transaction({
_from: address(this),
_to: _to,
_mint: _mint,
_value: _value,
_gasLimit: _gasLimit,
_isCreation: _isCreation,
_data: _data
});
}
/// @dev Tests that `depositERC20Transaction` succeeds when msg.sender != tx.origin.
function testFuzz_depositERC20Transaction_senderNotOrigin_succeeds(
address _to,
uint256 _mint,
uint256 _value,
uint64 _gasLimit,
bool _isCreation,
bytes calldata _data
)
external
{
// Ensure that msg.sender != tx.origin
vm.startPrank(address(this), address(1));
depositERC20Transaction({
_from: AddressAliasHelper.applyL1ToL2Alias(address(this)),
_to: _to,
_mint: _mint,
_value: _value,
_gasLimit: _gasLimit,
_isCreation: _isCreation,
_data: _data
});
}
/// @dev Tests that `depositERC20Transaction` reverts when not enough of the token is approved.
function test_depositERC20Transaction_notEnoughAmount_reverts() external {
// Mock the gas paying token to be the ERC20 token
vm.mockCall(
address(systemConfig), abi.encodeCall(systemConfig.gasPayingToken, ()), abi.encode(address(token), 18)
);
vm.expectRevert(stdError.arithmeticError);
// Deposit the token into the portal
optimismPortal.depositERC20Transaction(address(0), 1, 0, 0, false, "");
}
/// @dev Tests that `depositERC20Transaction` reverts when token balance does not update correctly after transfer.
function test_depositERC20Transaction_incorrectTokenBalance_reverts() external {
// Mint the token to the contract and approve the token for the portal
token.mint(address(this), 100);
token.approve(address(optimismPortal), 100);
// Mock the gas paying token to be the ERC20 token
vm.mockCall(
address(systemConfig), abi.encodeCall(systemConfig.gasPayingToken, ()), abi.encode(address(token), 18)
);
// Mock the token balance
vm.mockCall(address(token), abi.encodeCall(token.balanceOf, (address(optimismPortal))), abi.encode(0));
// Call minimumGasLimit(0) before vm.expectRevert to ensure vm.expectRevert is for depositERC20Transaction
uint64 gasLimit = optimismPortal.minimumGasLimit(0);
vm.expectRevert(TransferFailed.selector);
// Deposit the token into the portal
optimismPortal.depositERC20Transaction(address(1), 100, 0, gasLimit, false, "");
}
/// @dev Tests that `depositERC20Transaction` reverts when creating a contract with a non-zero target.
function test_depositERC20Transaction_isCreationNotZeroTarget_reverts() external {
// Mock the gas paying token to be the ERC20 token
vm.mockCall(
address(systemConfig), abi.encodeCall(systemConfig.gasPayingToken, ()), abi.encode(address(token), 18)
);
// Call minimumGasLimit(0) before vm.expectRevert to ensure vm.expectRevert is for depositERC20Transaction
uint64 gasLimit = optimismPortal.minimumGasLimit(0);
vm.expectRevert(BadTarget.selector);
// Deposit the token into the portal
optimismPortal.depositERC20Transaction(address(1), 0, 0, gasLimit, true, "");
}
/// @dev Tests that `depositERC20Transaction` reverts when the gas limit is too low.
function test_depositERC20Transaction_gasLimitTooLow_reverts() external {
// Mock the gas paying token to be the ERC20 token
vm.mockCall(
address(systemConfig), abi.encodeCall(systemConfig.gasPayingToken, ()), abi.encode(address(token), 18)
);
vm.expectRevert(SmallGasLimit.selector);
// Deposit the token into the portal
optimismPortal.depositERC20Transaction(address(0), 0, 0, 0, false, "");
}
/// @dev Tests that `depositERC20Transaction` reverts when the data is too large.
function test_depositERC20Transaction_dataTooLarge_reverts() external {
bytes memory data = new bytes(120_001);
data[120_000] = 0x01;
// Mock the gas paying token to be the ERC20 token
vm.mockCall(
address(systemConfig), abi.encodeCall(systemConfig.gasPayingToken, ()), abi.encode(address(token), 18)
);
uint64 gasLimit = optimismPortal.minimumGasLimit(120_001);
vm.expectRevert(LargeCalldata.selector);
// Deposit the token into the portal
optimismPortal.depositERC20Transaction(address(0), 0, 0, gasLimit, false, data);
}
/// @dev Tests that `balance()` returns the correct balance when the gas paying token is not ether.
function testFuzz_balance_nonEther_succeeds(uint256 _amount) external {
// Mint the token to the contract and approve the token for the portal
token.mint(address(this), _amount);
token.approve(address(optimismPortal), _amount);
// Mock the gas paying token to be the ERC20 token
vm.mockCall(
address(systemConfig), abi.encodeCall(systemConfig.gasPayingToken, ()), abi.encode(address(token), 18)
);
// Deposit the token into the portal
optimismPortal.depositERC20Transaction(address(0), _amount, 0, optimismPortal.minimumGasLimit(0), false, "");
// Check that the balance has been correctly updated
assertEq(optimismPortal.balance(), _amount);
}
/// @dev Tests that `finalizeWithdrawalTransaction` succeeds.
function test_finalizeWithdrawalTransaction_provenWithdrawalHashNonEther_succeeds() external {
// Mint the token to the contract and approve the token for the portal
token.mint(address(this), _defaultTx.value);
token.approve(address(optimismPortal), _defaultTx.value);
// Mock the gas paying token to be the ERC20 token
vm.mockCall(
address(systemConfig), abi.encodeCall(systemConfig.gasPayingToken, ()), abi.encode(address(token), 18)
);
// Deposit the token into the portal
optimismPortal.depositERC20Transaction(
address(bob), _defaultTx.value, 0, optimismPortal.minimumGasLimit(0), false, ""
);
assertEq(optimismPortal.balance(), _defaultTx.value);
vm.expectEmit(address(optimismPortal));
emit WithdrawalProven(_withdrawalHash, alice, bob);
optimismPortal.proveWithdrawalTransaction(_defaultTx, _proposedOutputIndex, _outputRootProof, _withdrawalProof);
vm.warp(block.timestamp + l2OutputOracle.FINALIZATION_PERIOD_SECONDS() + 1);
vm.expectEmit(address(optimismPortal));
emit WithdrawalFinalized(_withdrawalHash, true);
vm.expectCall(_defaultTx.target, 0, _defaultTx.data);
vm.expectCall(address(token), 0, abi.encodeCall(token.transfer, (_defaultTx.target, _defaultTx.value)));
optimismPortal.finalizeWithdrawalTransaction(_defaultTx);
assertEq(optimismPortal.balance(), 0);
assertEq(token.balanceOf(address(bob)), 100);
}
/// @dev Helper for depositing a transaction.
function depositTransaction(
address _from,
address _to,
uint256 _value,
uint64 _gasLimit,
bool _isCreation,
bytes calldata _data
)
internal
{
if (_isCreation) {
_to = address(0);
}
if (_data.length > 120_000) {
_data = _data[0:120_000];
}
IResourceMetering.ResourceConfig memory rcfg = systemConfig.resourceConfig();
_gasLimit =
uint64(bound(_gasLimit, optimismPortal.minimumGasLimit(uint64(_data.length)), rcfg.maxResourceLimit));
// Mock the gas paying token to be the ERC20 token
vm.mockCall(
address(systemConfig), abi.encodeCall(systemConfig.gasPayingToken, ()), abi.encode(address(token), 18)
);
bytes memory opaqueData = abi.encodePacked(uint256(0), _value, _gasLimit, _isCreation, _data);
vm.expectEmit(address(optimismPortal));
emit TransactionDeposited(
_from, // from
_to,
uint256(0), // DEPOSIT_VERSION
opaqueData
);
// Deposit the token into the portal
optimismPortal.depositTransaction(_to, _value, _gasLimit, _isCreation, _data);
// Assert final balance equals the deposited amount
assertEq(token.balanceOf(address(optimismPortal)), 0);
assertEq(optimismPortal.balance(), 0);
}
/// @dev Tests that `depositTransaction` succeeds when a custom gas token is used but the msg.value is zero.
function testFuzz_depositTransaction_customGasTokenWithNoValueAndSenderIsOrigin_succeeds(
address _to,
uint256 _value,
uint64 _gasLimit,
bool _isCreation,
bytes calldata _data
)
external
{
// TODO(opcm upgrades): remove skip once upgrade path is implemented
skipIfForkTest("OptimismPortal_Test: gas paying token functionality DNE on op mainnet");
// Ensure that msg.sender == tx.origin
vm.startPrank(address(this), address(this));
depositTransaction({
_from: address(this),
_to: _to,
_value: _value,
_gasLimit: _gasLimit,
_isCreation: _isCreation,
_data: _data
});
}
/// @dev Tests that `depositTransaction` succeeds when a custom gas token is used but the msg.value is zero.
function testFuzz_depositTransaction_customGasTokenWithNoValueAndSenderNotOrigin_succeeds(
address _to,
uint256 _value,
uint64 _gasLimit,
bool _isCreation,
bytes calldata _data
)
external
{
// TODO(opcm upgrades): remove skip once upgrade path is implemented
skipIfForkTest("OptimismPortal_Test: gas paying token functionality DNE on op mainnet");
// Ensure that msg.sender != tx.origin
vm.startPrank(address(this), address(1));
depositTransaction({
_from: AddressAliasHelper.applyL1ToL2Alias(address(this)),
_to: _to,
_value: _value,
_gasLimit: _gasLimit,
_isCreation: _isCreation,
_data: _data
});
}
/// @dev Tests that `depositTransaction` fails when a custom gas token is used and msg.value is non-zero.
function test_depositTransaction_customGasTokenWithValue_reverts() external {
// TODO(opcm upgrades): remove skip once upgrade path is implemented
skipIfForkTest("OptimismPortal_Test: gas paying token functionality DNE on op mainnet");
// Mock the gas paying token to be the ERC20 token
vm.mockCall(
address(systemConfig), abi.encodeCall(systemConfig.gasPayingToken, ()), abi.encode(address(token), 18)
);
vm.expectRevert(NoValue.selector);
// Deposit the token into the portal
optimismPortal.depositTransaction{ value: 100 }(address(0), 0, 0, false, "");
}
}
......@@ -35,13 +35,6 @@ contract OptimismPortal2_Test is CommonTest {
function setUp() public virtual override {
super.setUp();
// zero out contracts that should not be used
assembly {
sstore(l2OutputOracle.slot, 0)
sstore(optimismPortal.slot, 0)
}
depositor = makeAddr("depositor");
}
......
......@@ -23,7 +23,7 @@ contract OptimismPortalInterop_Test is CommonTest {
/// @dev Tests that the config for the gas paying token can be set.
function testFuzz_setConfig_gasPayingToken_succeeds(bytes calldata _value) public {
vm.expectEmit(address(optimismPortal));
vm.expectEmit(address(optimismPortal2));
emitTransactionDeposited({
_from: Constants.DEPOSITOR_ACCOUNT,
_to: Predeploys.L1_BLOCK_ATTRIBUTES,
......@@ -46,7 +46,7 @@ contract OptimismPortalInterop_Test is CommonTest {
/// @dev Tests that the config for adding a dependency can be set.
function testFuzz_setConfig_addDependency_succeeds(bytes calldata _value) public {
vm.expectEmit(address(optimismPortal));
vm.expectEmit(address(optimismPortal2));
emitTransactionDeposited({
_from: Constants.DEPOSITOR_ACCOUNT,
_to: Predeploys.L1_BLOCK_ATTRIBUTES,
......@@ -69,7 +69,7 @@ contract OptimismPortalInterop_Test is CommonTest {
/// @dev Tests that the config for removing a dependency can be set.
function testFuzz_setConfig_removeDependency_succeeds(bytes calldata _value) public {
vm.expectEmit(address(optimismPortal));
vm.expectEmit(address(optimismPortal2));
emitTransactionDeposited({
_from: Constants.DEPOSITOR_ACCOUNT,
_to: Predeploys.L1_BLOCK_ATTRIBUTES,
......@@ -92,6 +92,6 @@ contract OptimismPortalInterop_Test is CommonTest {
/// @dev Returns the OptimismPortalInterop instance.
function _optimismPortalInterop() internal view returns (IOptimismPortalInterop) {
return IOptimismPortalInterop(payable(address(optimismPortal)));
return IOptimismPortalInterop(payable(address(optimismPortal2)));
}
}
......@@ -107,7 +107,7 @@ contract SystemConfig_Initialize_Test is SystemConfig_Init {
assertEq(address(systemConfig.l1ERC721Bridge()), address(l1ERC721Bridge));
assertEq(address(systemConfig.l1StandardBridge()), address(l1StandardBridge));
assertEq(address(systemConfig.disputeGameFactory()), address(disputeGameFactory));
assertEq(address(systemConfig.optimismPortal()), address(optimismPortal));
assertEq(address(systemConfig.optimismPortal()), address(optimismPortal2));
assertEq(address(systemConfig.optimismMintableERC20Factory()), address(optimismMintableERC20Factory));
// Check gas paying token
(address token, uint8 decimals) = systemConfig.gasPayingToken();
......@@ -352,7 +352,7 @@ contract SystemConfig_Init_CustomGasToken is SystemConfig_Init {
l1ERC721Bridge: address(0),
disputeGameFactory: address(0),
l1StandardBridge: address(0),
optimismPortal: address(optimismPortal),
optimismPortal: address(optimismPortal2),
optimismMintableERC20Factory: address(0),
gasPayingToken: _gasPayingToken
})
......@@ -477,11 +477,11 @@ contract SystemConfig_Init_CustomGasToken is SystemConfig_Init {
/// @dev Tests that initialization works with OptimismPortal.
function test_initialize_customGasTokenCall_succeeds() external {
vm.expectCall(
address(optimismPortal),
abi.encodeCall(optimismPortal.setGasPayingToken, (address(token), 18, bytes32("Silly"), bytes32("SIL")))
address(optimismPortal2),
abi.encodeCall(optimismPortal2.setGasPayingToken, (address(token), 18, bytes32("Silly"), bytes32("SIL")))
);
vm.expectEmit(address(optimismPortal));
vm.expectEmit(address(optimismPortal2));
emit TransactionDeposited(
0xDeaDDEaDDeAdDeAdDEAdDEaddeAddEAdDEAd0001,
Predeploys.L1_BLOCK_ATTRIBUTES,
......
......@@ -68,7 +68,7 @@ contract SystemConfigInterop_Test is CommonTest {
vm.mockCall(_token, abi.encodeCall(ERC20.symbol, ()), abi.encode(symbol));
vm.expectCall(
address(optimismPortal),
address(optimismPortal2),
abi.encodeCall(
IOptimismPortalInterop.setConfig,
(
......@@ -89,7 +89,7 @@ contract SystemConfigInterop_Test is CommonTest {
/// @dev Tests that a dependency can be added.
function testFuzz_addDependency_succeeds(uint256 _chainId) public {
vm.expectCall(
address(optimismPortal),
address(optimismPortal2),
abi.encodeCall(
IOptimismPortalInterop.setConfig,
(ConfigType.ADD_DEPENDENCY, StaticConfig.encodeAddDependency(_chainId))
......@@ -111,7 +111,7 @@ contract SystemConfigInterop_Test is CommonTest {
/// @dev Tests that a dependency can be removed.
function testFuzz_removeDependency_succeeds(uint256 _chainId) public {
vm.expectCall(
address(optimismPortal),
address(optimismPortal2),
abi.encodeCall(
IOptimismPortalInterop.setConfig,
(ConfigType.REMOVE_DEPENDENCY, StaticConfig.encodeRemoveDependency(_chainId))
......@@ -152,7 +152,7 @@ contract SystemConfigInterop_Test is CommonTest {
l1ERC721Bridge: address(0),
disputeGameFactory: address(0),
l1StandardBridge: address(0),
optimismPortal: address(optimismPortal),
optimismPortal: address(optimismPortal2),
optimismMintableERC20Factory: address(0),
gasPayingToken: _token
})
......
......@@ -63,7 +63,7 @@ contract CrossDomainOwnableThroughPortal_Test is CommonTest {
vm.recordLogs();
vm.prank(alice);
optimismPortal.depositTransaction({
optimismPortal2.depositTransaction({
_to: address(setter),
_value: 0,
_gasLimit: 30_000,
......
......@@ -197,7 +197,7 @@ contract L2CrossDomainMessenger_Test is CommonTest {
address caller = AddressAliasHelper.applyL1ToL2Alias(address(l1CrossDomainMessenger));
bytes memory message = hex"1111";
vm.store(address(optimismPortal), bytes32(0), bytes32(abi.encode(sender)));
vm.store(address(optimismPortal2), bytes32(0), bytes32(abi.encode(sender)));
vm.prank(caller);
vm.expectRevert("CrossDomainMessenger: cannot send message to blocked system address");
......@@ -218,7 +218,7 @@ contract L2CrossDomainMessenger_Test is CommonTest {
address caller = AddressAliasHelper.applyL1ToL2Alias(address(l1CrossDomainMessenger));
bytes memory message = hex"1111";
vm.store(address(optimismPortal), bytes32(0), bytes32(abi.encode(sender)));
vm.store(address(optimismPortal2), bytes32(0), bytes32(abi.encode(sender)));
vm.prank(caller);
vm.expectRevert("CrossDomainMessenger: cannot send message to blocked system address");
......@@ -232,14 +232,14 @@ contract L2CrossDomainMessenger_Test is CommonTest {
);
}
/// @dev Tests that the relayMessage function reverts if the message called by non-optimismPortal but not a failed
/// @dev Tests that the relayMessage function reverts if the message called by non-optimismPortal2 but not a failed
/// message
function test_relayMessage_relayingNewMessageByExternalUser_reverts() external {
address target = address(alice);
address sender = address(l1CrossDomainMessenger);
bytes memory message = hex"1111";
vm.store(address(optimismPortal), bytes32(0), bytes32(abi.encode(sender)));
vm.store(address(optimismPortal2), bytes32(0), bytes32(abi.encode(sender)));
vm.prank(bob);
vm.expectRevert("CrossDomainMessenger: message cannot be replayed");
......
......@@ -155,7 +155,7 @@ contract DelayedWETH_Withdraw_Test is DelayedWETH_Init {
vm.warp(block.timestamp + delayedWeth.delay() + 1);
// Pause the contract.
address guardian = optimismPortal.guardian();
address guardian = optimismPortal2.guardian();
vm.prank(guardian);
superchainConfig.pause("identifier");
......@@ -259,7 +259,7 @@ contract DelayedWETH_WithdrawFrom_Test is DelayedWETH_Init {
vm.warp(block.timestamp + delayedWeth.delay() + 1);
// Pause the contract.
address guardian = optimismPortal.guardian();
address guardian = optimismPortal2.guardian();
vm.prank(guardian);
superchainConfig.pause("identifier");
......
......@@ -3,7 +3,7 @@ pragma solidity 0.8.15;
import { StdUtils } from "forge-std/StdUtils.sol";
import { Vm } from "forge-std/Vm.sol";
import { IOptimismPortal } from "interfaces/L1/IOptimismPortal.sol";
import { IOptimismPortal2 } from "interfaces/L1/IOptimismPortal2.sol";
import { IL1CrossDomainMessenger } from "interfaces/L1/IL1CrossDomainMessenger.sol";
import { CommonTest } from "test/setup/CommonTest.sol";
import { Predeploys } from "src/libraries/Predeploys.sol";
......@@ -19,12 +19,12 @@ contract RelayActor is StdUtils {
bytes32[] public hashes;
bool public reverted = false;
IOptimismPortal op;
IOptimismPortal2 op;
IL1CrossDomainMessenger xdm;
Vm vm;
bool doFail;
constructor(IOptimismPortal _op, IL1CrossDomainMessenger _xdm, Vm _vm, bool _doFail) {
constructor(IOptimismPortal2 _op, IL1CrossDomainMessenger _xdm, Vm _vm, bool _doFail) {
op = _op;
xdm = _xdm;
vm = _vm;
......@@ -95,10 +95,10 @@ contract XDM_MinGasLimits is CommonTest {
super.setUp();
// Deploy a relay actor
actor = new RelayActor(optimismPortal, l1CrossDomainMessenger, vm, doFail);
actor = new RelayActor(optimismPortal2, l1CrossDomainMessenger, vm, doFail);
// Give the portal some ether to send to `relayMessage`
vm.deal(address(optimismPortal), type(uint128).max);
vm.deal(address(optimismPortal2), type(uint128).max);
// Target the `RelayActor` contract
targetContract(address(actor));
......
// SPDX-License-Identifier: MIT
pragma solidity 0.8.15;
import { CommonTest } from "test/setup/CommonTest.sol";
import { IL2OutputOracle } from "interfaces/L1/IL2OutputOracle.sol";
import { Vm } from "forge-std/Vm.sol";
contract L2OutputOracle_Proposer {
IL2OutputOracle internal oracle;
Vm internal vm;
constructor(IL2OutputOracle _oracle, Vm _vm) {
oracle = _oracle;
vm = _vm;
}
/// @dev Allows the actor to propose an L2 output to the `L2OutputOracle`
function proposeL2Output(
bytes32 _outputRoot,
uint256 _l2BlockNumber,
bytes32 _l1BlockHash,
uint256 _l1BlockNumber
)
external
{
// Act as the proposer and propose a new output.
vm.prank(oracle.PROPOSER());
oracle.proposeL2Output(_outputRoot, _l2BlockNumber, _l1BlockHash, _l1BlockNumber);
}
}
contract L2OutputOracle_MonotonicBlockNumIncrease_Invariant is CommonTest {
L2OutputOracle_Proposer internal actor;
function setUp() public override {
super.enableLegacyContracts();
super.setUp();
// Create a proposer actor.
actor = new L2OutputOracle_Proposer(IL2OutputOracle(address(l2OutputOracle)), vm);
// Set the target contract to the proposer actor.
targetContract(address(actor));
// Set the target selector for `proposeL2Output`
// `proposeL2Output` is the only function we care about, as it is the only function
// that can modify the `l2Outputs` array in the oracle.
bytes4[] memory selectors = new bytes4[](1);
selectors[0] = actor.proposeL2Output.selector;
FuzzSelector memory selector = FuzzSelector({ addr: address(actor), selectors: selectors });
targetSelector(selector);
}
/// @custom:invariant The block number of the output root proposals should monotonically
/// increase.
///
/// When a new output is submitted, it should never be allowed to
/// correspond to a block number that is less than the current output.
function invariant_monotonicBlockNumIncrease() external view {
// Assert that the block number of proposals must monotonically increase.
assertTrue(l2OutputOracle.nextBlockNumber() >= l2OutputOracle.latestBlockNumber());
}
}
// SPDX-License-Identifier: MIT
pragma solidity 0.8.15;
import { StdUtils } from "forge-std/Test.sol";
import { Vm } from "forge-std/Vm.sol";
import { IOptimismPortal } from "interfaces/L1/IOptimismPortal.sol";
import { ResourceMetering } from "src/L1/ResourceMetering.sol";
import { IResourceMetering } from "interfaces/L1/IResourceMetering.sol";
import { Constants } from "src/libraries/Constants.sol";
import { CommonTest } from "test/setup/CommonTest.sol";
import { EIP1967Helper } from "test/mocks/EIP1967Helper.sol";
import { Types } from "src/libraries/Types.sol";
contract OptimismPortal_Depositor is StdUtils, ResourceMetering {
Vm internal vm;
IOptimismPortal internal portal;
bool public failedToComplete;
constructor(Vm _vm, IOptimismPortal _portal) {
vm = _vm;
portal = _portal;
initialize();
}
function initialize() internal initializer {
__ResourceMetering_init();
}
function resourceConfig() public pure returns (ResourceMetering.ResourceConfig memory) {
return _resourceConfig();
}
function _resourceConfig() internal pure override returns (ResourceMetering.ResourceConfig memory config_) {
IResourceMetering.ResourceConfig memory rcfg = Constants.DEFAULT_RESOURCE_CONFIG();
assembly ("memory-safe") {
config_ := rcfg
}
}
// A test intended to identify any unexpected halting conditions
function depositTransactionCompletes(
address _to,
uint256 _value,
uint64 _gasLimit,
bool _isCreation,
bytes memory _data
)
public
payable
{
vm.assume((!_isCreation || _to == address(0)) && _data.length <= 120_000);
uint256 preDepositvalue = bound(_value, 0, type(uint128).max);
// Give the depositor some ether
vm.deal(address(this), preDepositvalue);
// cache the contract's eth balance
uint256 preDepositBalance = address(this).balance;
uint256 value = bound(preDepositvalue, 0, preDepositBalance);
(, uint64 cachedPrevBoughtGas,) = ResourceMetering(address(portal)).params();
ResourceMetering.ResourceConfig memory rcfg = resourceConfig();
uint256 maxResourceLimit = uint64(rcfg.maxResourceLimit);
uint64 gasLimit = uint64(
bound(_gasLimit, portal.minimumGasLimit(uint64(_data.length)), maxResourceLimit - cachedPrevBoughtGas)
);
try portal.depositTransaction{ value: value }(_to, value, gasLimit, _isCreation, _data) {
// Do nothing; Call succeeded
} catch {
failedToComplete = true;
}
}
}
contract OptimismPortal_Invariant_Harness is CommonTest {
// Reusable default values for a test withdrawal
Types.WithdrawalTransaction _defaultTx;
uint256 _proposedOutputIndex;
uint256 _proposedBlockNumber;
bytes32 _stateRoot;
bytes32 _storageRoot;
bytes32 _outputRoot;
bytes32 _withdrawalHash;
bytes[] _withdrawalProof;
Types.OutputRootProof internal _outputRootProof;
function setUp() public virtual override {
super.enableLegacyContracts();
super.setUp();
_defaultTx = Types.WithdrawalTransaction({
nonce: 0,
sender: alice,
target: bob,
value: 100,
gasLimit: 100_000,
data: hex""
});
// Get withdrawal proof data we can use for testing.
(_stateRoot, _storageRoot, _outputRoot, _withdrawalHash, _withdrawalProof) =
ffi.getProveWithdrawalTransactionInputs(_defaultTx);
// Setup a dummy output root proof for reuse.
_outputRootProof = Types.OutputRootProof({
version: bytes32(uint256(0)),
stateRoot: _stateRoot,
messagePasserStorageRoot: _storageRoot,
latestBlockhash: bytes32(uint256(0))
});
_proposedBlockNumber = l2OutputOracle.nextBlockNumber();
_proposedOutputIndex = l2OutputOracle.nextOutputIndex();
// Configure the oracle to return the output root we've prepared.
vm.warp(l2OutputOracle.computeL2Timestamp(_proposedBlockNumber) + 1);
vm.prank(l2OutputOracle.PROPOSER());
l2OutputOracle.proposeL2Output(_outputRoot, _proposedBlockNumber, 0, 0);
// Warp beyond the finalization period for the block we've proposed.
vm.warp(
l2OutputOracle.getL2Output(_proposedOutputIndex).timestamp + l2OutputOracle.FINALIZATION_PERIOD_SECONDS()
+ 1
);
// Fund the portal so that we can withdraw ETH.
vm.deal(address(optimismPortal), 0xFFFFFFFF);
}
}
contract OptimismPortal_Deposit_Invariant is CommonTest {
OptimismPortal_Depositor internal actor;
function setUp() public override {
super.setUp();
// Create a deposit actor.
actor = new OptimismPortal_Depositor(vm, optimismPortal);
targetContract(address(actor));
bytes4[] memory selectors = new bytes4[](1);
selectors[0] = actor.depositTransactionCompletes.selector;
FuzzSelector memory selector = FuzzSelector({ addr: address(actor), selectors: selectors });
targetSelector(selector);
}
/// @custom:invariant Deposits of any value should always succeed unless
/// `_to` = `address(0)` or `_isCreation` = `true`.
///
/// All deposits, barring creation transactions and transactions
/// sent to `address(0)`, should always succeed.
function invariant_deposit_completes() external view {
assertEq(actor.failedToComplete(), false);
}
}
contract OptimismPortal_CannotTimeTravel is OptimismPortal_Invariant_Harness {
function setUp() public override {
super.setUp();
// Prove the withdrawal transaction
optimismPortal.proveWithdrawalTransaction(_defaultTx, _proposedOutputIndex, _outputRootProof, _withdrawalProof);
// Set the target contract to the portal proxy
targetContract(address(optimismPortal));
// Exclude the proxy admin from the senders so that the proxy cannot be upgraded
excludeSender(EIP1967Helper.getAdmin(address(optimismPortal)));
}
/// @custom:invariant `finalizeWithdrawalTransaction` should revert if the finalization
/// period has not elapsed.
///
/// A withdrawal that has been proven should not be able to be finalized
/// until after the finalization period has elapsed.
function invariant_cannotFinalizeBeforePeriodHasPassed() external {
vm.expectRevert("OptimismPortal: proven withdrawal finalization period has not elapsed");
optimismPortal.finalizeWithdrawalTransaction(_defaultTx);
}
}
contract OptimismPortal_CannotFinalizeTwice is OptimismPortal_Invariant_Harness {
function setUp() public override {
super.setUp();
// Prove the withdrawal transaction
optimismPortal.proveWithdrawalTransaction(_defaultTx, _proposedOutputIndex, _outputRootProof, _withdrawalProof);
// Warp past the finalization period.
vm.warp(block.timestamp + l2OutputOracle.FINALIZATION_PERIOD_SECONDS() + 1);
// Finalize the withdrawal transaction.
optimismPortal.finalizeWithdrawalTransaction(_defaultTx);
// Set the target contract to the portal proxy
targetContract(address(optimismPortal));
// Exclude the proxy admin from the senders so that the proxy cannot be upgraded
excludeSender(EIP1967Helper.getAdmin(address(optimismPortal)));
}
/// @custom:invariant `finalizeWithdrawalTransaction` should revert if the withdrawal
/// has already been finalized.
///
/// Ensures that there is no chain of calls that can be made that
/// allows a withdrawal to be finalized twice.
function invariant_cannotFinalizeTwice() external {
vm.expectRevert("OptimismPortal: withdrawal has already been finalized");
optimismPortal.finalizeWithdrawalTransaction(_defaultTx);
}
}
contract OptimismPortal_CanAlwaysFinalizeAfterWindow is OptimismPortal_Invariant_Harness {
function setUp() public override {
super.setUp();
// Prove the withdrawal transaction
optimismPortal.proveWithdrawalTransaction(_defaultTx, _proposedOutputIndex, _outputRootProof, _withdrawalProof);
// Warp past the finalization period.
vm.warp(block.timestamp + l2OutputOracle.FINALIZATION_PERIOD_SECONDS() + 1);
// Set the target contract to the portal proxy
targetContract(address(optimismPortal));
// Exclude the proxy admin from the senders so that the proxy cannot be upgraded
excludeSender(EIP1967Helper.getAdmin(address(optimismPortal)));
}
/// @custom:invariant A withdrawal should **always** be able to be finalized
/// `FINALIZATION_PERIOD_SECONDS` after it was successfully proven.
///
/// This invariant asserts that there is no chain of calls that can
/// be made that will prevent a withdrawal from being finalized
/// exactly `FINALIZATION_PERIOD_SECONDS` after it was successfully
/// proven.
function invariant_canAlwaysFinalize() external {
uint256 bobBalanceBefore = address(bob).balance;
optimismPortal.finalizeWithdrawalTransaction(_defaultTx);
assertEq(address(bob).balance, bobBalanceBefore + _defaultTx.value);
}
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.13;
import { DeploymentSummary } from "./utils/DeploymentSummary.sol";
import { KontrolUtils } from "./utils/KontrolUtils.sol";
import { Types } from "src/libraries/Types.sol";
import { IOptimismPortal as OptimismPortal } from "interfaces/L1/IOptimismPortal.sol";
import { ISuperchainConfig as SuperchainConfig } from "interfaces/L1/ISuperchainConfig.sol";
import "src/libraries/PortalErrors.sol";
contract OptimismPortalKontrol is DeploymentSummary, KontrolUtils {
OptimismPortal optimismPortal;
SuperchainConfig superchainConfig;
/// @dev Inlined setUp function for faster Kontrol performance
/// Tracking issue: https://github.com/runtimeverification/kontrol/issues/282
function setUpInlined() public {
optimismPortal = OptimismPortal(payable(optimismPortalProxyAddress));
superchainConfig = SuperchainConfig(superchainConfigProxyAddress);
}
function prove_finalizeWithdrawalTransaction_paused(Types.WithdrawalTransaction calldata _tx) external {
setUpInlined();
// Pause Optimism Portal
vm.prank(optimismPortal.guardian());
superchainConfig.pause("identifier");
vm.expectRevert(CallPaused.selector);
optimismPortal.finalizeWithdrawalTransaction(_tx);
}
/// @dev Function containing the logic for prove_proveWithdrawalTransaction_paused
/// The reason for this is that we want the _withdrawalProof to range in size from
/// 0 to 10. These 11 proofs will exercise the same logic, which is contained in this function
function prove_proveWithdrawalTransaction_paused_internal(
Types.WithdrawalTransaction memory _tx,
uint256 _l2OutputIndex,
Types.OutputRootProof calldata _outputRootProof,
bytes[] memory _withdrawalProof
)
internal
{
setUpInlined();
// Pause Optimism Portal
vm.prank(optimismPortal.guardian());
superchainConfig.pause("identifier");
vm.expectRevert(CallPaused.selector);
optimismPortal.proveWithdrawalTransaction(_tx, _l2OutputIndex, _outputRootProof, _withdrawalProof);
}
/// @custom:kontrol-array-length-equals _withdrawalProof: 10,
/// @custom:kontrol-bytes-length-equals _withdrawalProof: 600,
function prove_proveWithdrawalTransaction_paused10(
Types.WithdrawalTransaction memory _tx,
uint256 _l2OutputIndex,
Types.OutputRootProof calldata _outputRootProof,
bytes[] calldata _withdrawalProof
)
external
{
prove_proveWithdrawalTransaction_paused_internal(_tx, _l2OutputIndex, _outputRootProof, _withdrawalProof);
}
/// @custom:kontrol-array-length-equals _withdrawalProof: 9,
/// @custom:kontrol-bytes-length-equals _withdrawalProof: 600,
function prove_proveWithdrawalTransaction_paused9(
Types.WithdrawalTransaction memory _tx,
uint256 _l2OutputIndex,
Types.OutputRootProof calldata _outputRootProof,
bytes[] calldata _withdrawalProof
)
external
{
prove_proveWithdrawalTransaction_paused_internal(_tx, _l2OutputIndex, _outputRootProof, _withdrawalProof);
}
/// @custom:kontrol-array-length-equals _withdrawalProof: 8,
/// @custom:kontrol-bytes-length-equals _withdrawalProof: 600,
function prove_proveWithdrawalTransaction_paused8(
Types.WithdrawalTransaction memory _tx,
uint256 _l2OutputIndex,
Types.OutputRootProof calldata _outputRootProof,
bytes[] calldata _withdrawalProof
)
external
{
prove_proveWithdrawalTransaction_paused_internal(_tx, _l2OutputIndex, _outputRootProof, _withdrawalProof);
}
/// @custom:kontrol-array-length-equals _withdrawalProof: 7,
/// @custom:kontrol-bytes-length-equals _withdrawalProof: 600,
function prove_proveWithdrawalTransaction_paused7(
Types.WithdrawalTransaction memory _tx,
uint256 _l2OutputIndex,
Types.OutputRootProof calldata _outputRootProof,
bytes[] calldata _withdrawalProof
)
external
{
prove_proveWithdrawalTransaction_paused_internal(_tx, _l2OutputIndex, _outputRootProof, _withdrawalProof);
}
/// @custom:kontrol-array-length-equals _withdrawalProof: 6,
/// @custom:kontrol-bytes-length-equals _withdrawalProof: 600,
function prove_proveWithdrawalTransaction_paused6(
Types.WithdrawalTransaction memory _tx,
uint256 _l2OutputIndex,
Types.OutputRootProof calldata _outputRootProof,
bytes[] calldata _withdrawalProof
)
external
{
prove_proveWithdrawalTransaction_paused_internal(_tx, _l2OutputIndex, _outputRootProof, _withdrawalProof);
}
/// @custom:kontrol-array-length-equals _withdrawalProof: 5,
/// @custom:kontrol-bytes-length-equals _withdrawalProof: 600,
function prove_proveWithdrawalTransaction_paused5(
Types.WithdrawalTransaction memory _tx,
uint256 _l2OutputIndex,
Types.OutputRootProof calldata _outputRootProof,
bytes[] calldata _withdrawalProof
)
external
{
prove_proveWithdrawalTransaction_paused_internal(_tx, _l2OutputIndex, _outputRootProof, _withdrawalProof);
}
/// @custom:kontrol-array-length-equals _withdrawalProof: 4,
/// @custom:kontrol-bytes-length-equals _withdrawalProof: 600,
function prove_proveWithdrawalTransaction_paused4(
Types.WithdrawalTransaction memory _tx,
uint256 _l2OutputIndex,
Types.OutputRootProof calldata _outputRootProof,
bytes[] calldata _withdrawalProof
)
external
{
prove_proveWithdrawalTransaction_paused_internal(_tx, _l2OutputIndex, _outputRootProof, _withdrawalProof);
}
/// @custom:kontrol-array-length-equals _withdrawalProof: 3,
/// @custom:kontrol-bytes-length-equals _withdrawalProof: 600,
function prove_proveWithdrawalTransaction_paused3(
Types.WithdrawalTransaction memory _tx,
uint256 _l2OutputIndex,
Types.OutputRootProof calldata _outputRootProof,
bytes[] calldata _withdrawalProof
)
external
{
prove_proveWithdrawalTransaction_paused_internal(_tx, _l2OutputIndex, _outputRootProof, _withdrawalProof);
}
/// @custom:kontrol-array-length-equals _withdrawalProof: 2,
/// @custom:kontrol-bytes-length-equals _withdrawalProof: 600,
function prove_proveWithdrawalTransaction_paused2(
Types.WithdrawalTransaction memory _tx,
uint256 _l2OutputIndex,
Types.OutputRootProof calldata _outputRootProof,
bytes[] calldata _withdrawalProof
)
external
{
prove_proveWithdrawalTransaction_paused_internal(_tx, _l2OutputIndex, _outputRootProof, _withdrawalProof);
}
/// @custom:kontrol-array-length-equals _withdrawalProof: 1,
/// @custom:kontrol-bytes-length-equals _withdrawalProof: 600,
function prove_proveWithdrawalTransaction_paused1(
Types.WithdrawalTransaction memory _tx,
uint256 _l2OutputIndex,
Types.OutputRootProof calldata _outputRootProof,
bytes[] calldata _withdrawalProof
)
external
{
prove_proveWithdrawalTransaction_paused_internal(_tx, _l2OutputIndex, _outputRootProof, _withdrawalProof);
}
function prove_proveWithdrawalTransaction_paused0(
Types.WithdrawalTransaction memory _tx,
uint256 _l2OutputIndex,
Types.OutputRootProof calldata _outputRootProof
)
external
{
bytes[] memory _withdrawalProof = new bytes[](0);
prove_proveWithdrawalTransaction_paused_internal(_tx, _l2OutputIndex, _outputRootProof, _withdrawalProof);
}
}
......@@ -4,7 +4,7 @@ pragma solidity ^0.8.13;
import { DeploymentSummaryFaultProofs } from "./utils/DeploymentSummaryFaultProofs.sol";
import { KontrolUtils } from "./utils/KontrolUtils.sol";
import { Types } from "src/libraries/Types.sol";
import { IOptimismPortal as OptimismPortal } from "interfaces/L1/IOptimismPortal.sol";
import { IOptimismPortal2 as OptimismPortal } from "interfaces/L1/IOptimismPortal2.sol";
import { ISuperchainConfig as SuperchainConfig } from "interfaces/L1/ISuperchainConfig.sol";
import "src/libraries/PortalErrors.sol";
......
......@@ -133,7 +133,7 @@ contract DeployOPChainOutput_Test is Test {
(IL1ChugSplashProxy l1StandardBridgeProxy) = DeployUtils.buildL1ChugSplashProxyWithImpl("l1StandardBridgeProxy");
(IResolvedDelegateProxy l1CrossDomainMessengerProxy) =
DeployUtils.buildResolvedDelegateProxyWithImpl(addressManager, "OVM_L1CrossDomainMessenger");
(IProxy optimismPortalProxy) = DeployUtils.buildERC1967ProxyWithImpl("optimismPortalProxy");
(IProxy optimismPortalProxy) = DeployUtils.buildERC1967ProxyWithImpl("OptimismPortalProxy");
(IProxy disputeGameFactoryProxy) = DeployUtils.buildERC1967ProxyWithImpl("disputeGameFactoryProxy");
(IProxy anchorStateRegistryProxy) = DeployUtils.buildERC1967ProxyWithImpl("anchorStateRegistryProxy");
vm.etch(address(anchorStateRegistryImpl), hex"01");
......
......@@ -171,11 +171,6 @@ contract CommonTest is Test, Setup, Events {
emit TransactionDeposited(_from, _to, 0, abi.encodePacked(_mint, _value, _gasLimit, _isCreation, _data));
}
// @dev Advance the evm's time to meet the L2OutputOracle's requirements for proposeL2Output
function warpToProposeTime(uint256 _nextBlockNumber) public {
vm.warp(l2OutputOracle.computeL2Timestamp(_nextBlockNumber) + 1);
}
function enableLegacyContracts() public {
// Check if the system has already been deployed, based off of the heuristic that alice and bob have not been
// set by the `setUp` function yet.
......
......@@ -19,10 +19,8 @@ import { AddressAliasHelper } from "src/vendor/AddressAliasHelper.sol";
import { Chains } from "scripts/libraries/Chains.sol";
// Interfaces
import { IOptimismPortal } from "interfaces/L1/IOptimismPortal.sol";
import { IOptimismPortal2 } from "interfaces/L1/IOptimismPortal2.sol";
import { IL1CrossDomainMessenger } from "interfaces/L1/IL1CrossDomainMessenger.sol";
import { IL2OutputOracle } from "interfaces/L1/IL2OutputOracle.sol";
import { ISystemConfig } from "interfaces/L1/ISystemConfig.sol";
import { ISuperchainConfig } from "interfaces/L1/ISuperchainConfig.sol";
import { IDataAvailabilityChallenge } from "interfaces/L1/IDataAvailabilityChallenge.sol";
......@@ -80,9 +78,7 @@ contract Setup {
IDisputeGameFactory disputeGameFactory;
IAnchorStateRegistry anchorStateRegistry;
IDelayedWETH delayedWeth;
IOptimismPortal optimismPortal;
IOptimismPortal2 optimismPortal2;
IL2OutputOracle l2OutputOracle;
ISystemConfig systemConfig;
IL1StandardBridge l1StandardBridge;
IL1CrossDomainMessenger l1CrossDomainMessenger;
......@@ -201,7 +197,6 @@ contract Setup {
deploy.run();
console.log("Setup: completed L1 deployment, registering addresses now");
optimismPortal = IOptimismPortal(deploy.mustGetAddress("OptimismPortalProxy"));
optimismPortal2 = IOptimismPortal2(deploy.mustGetAddress("OptimismPortalProxy"));
disputeGameFactory = IDisputeGameFactory(deploy.mustGetAddress("DisputeGameFactoryProxy"));
delayedWeth = IDelayedWETH(deploy.mustGetAddress("DelayedWETHProxy"));
......@@ -216,7 +211,6 @@ contract Setup {
superchainConfig = ISuperchainConfig(deploy.mustGetAddress("SuperchainConfigProxy"));
anchorStateRegistry = IAnchorStateRegistry(deploy.mustGetAddress("AnchorStateRegistryProxy"));
vm.label(address(optimismPortal), "OptimismPortal");
vm.label(deploy.mustGetAddress("OptimismPortalProxy"), "OptimismPortalProxy");
vm.label(address(disputeGameFactory), "DisputeGameFactory");
vm.label(deploy.mustGetAddress("DisputeGameFactoryProxy"), "DisputeGameFactoryProxy");
......@@ -240,12 +234,6 @@ contract Setup {
vm.label(address(anchorStateRegistry), "AnchorStateRegistryProxy");
vm.label(AddressAliasHelper.applyL1ToL2Alias(address(l1CrossDomainMessenger)), "L1CrossDomainMessenger_aliased");
if (!deploy.cfg().useFaultProofs()) {
l2OutputOracle = IL2OutputOracle(deploy.mustGetAddress("L2OutputOracleProxy"));
vm.label(address(l2OutputOracle), "L2OutputOracle");
vm.label(deploy.mustGetAddress("L2OutputOracleProxy"), "L2OutputOracleProxy");
}
if (deploy.cfg().useAltDA()) {
dataAvailabilityChallenge =
IDataAvailabilityChallenge(deploy.mustGetAddress("DataAvailabilityChallengeProxy"));
......
......@@ -6,7 +6,6 @@ import { Vm } from "forge-std/Vm.sol";
import { CommonTest } from "test/setup/CommonTest.sol";
// Libraries
import { Types } from "src/libraries/Types.sol";
import { SafeCall } from "src/libraries/SafeCall.sol";
import { IL1BlockInterop } from "interfaces/L2/IL1BlockInterop.sol";
import { Encoding } from "src/libraries/Encoding.sol";
......@@ -22,96 +21,17 @@ function setPrevBaseFee(Vm _vm, address _op, uint128 _prevBaseFee) {
contract SetPrevBaseFee_Test is CommonTest {
function test_setPrevBaseFee_succeeds() external {
setPrevBaseFee(vm, address(optimismPortal), 100 gwei);
(uint128 prevBaseFee,, uint64 prevBlockNum) = optimismPortal.params();
setPrevBaseFee(vm, address(optimismPortal2), 100 gwei);
(uint128 prevBaseFee,, uint64 prevBlockNum) = optimismPortal2.params();
assertEq(uint256(prevBaseFee), 100 gwei);
assertEq(uint256(prevBlockNum), block.number);
}
}
// Tests for obtaining pure gas cost estimates for commonly used functions.
// The objective with these benchmarks is to strip down the actual test functions
// so that they are nothing more than the call we want measure the gas cost of.
// In order to achieve this we make no assertions, and handle everything else in the setUp()
// function.
contract GasBenchMark_OptimismPortal is CommonTest {
// Reusable default values for a test withdrawal
Types.WithdrawalTransaction _defaultTx;
uint256 _proposedOutputIndex;
uint256 _proposedBlockNumber;
bytes[] _withdrawalProof;
Types.OutputRootProof internal _outputRootProof;
bytes32 _outputRoot;
// Use a constructor to set the storage vars above, so as to minimize the number of ffi calls.
constructor() {
super.enableLegacyContracts();
super.setUp();
_defaultTx = Types.WithdrawalTransaction({
nonce: 0,
sender: alice,
target: bob,
value: 100,
gasLimit: 100_000,
data: hex""
});
// Get withdrawal proof data we can use for testing.
bytes32 _storageRoot;
bytes32 _stateRoot;
(_stateRoot, _storageRoot, _outputRoot,, _withdrawalProof) = ffi.getProveWithdrawalTransactionInputs(_defaultTx);
// Setup a dummy output root proof for reuse.
_outputRootProof = Types.OutputRootProof({
version: bytes32(uint256(0)),
stateRoot: _stateRoot,
messagePasserStorageRoot: _storageRoot,
latestBlockhash: bytes32(uint256(0))
});
_proposedBlockNumber = l2OutputOracle.nextBlockNumber();
_proposedOutputIndex = l2OutputOracle.nextOutputIndex();
}
// Get the system into a nice ready-to-use state.
function setUp() public virtual override {
// Configure the oracle to return the output root we've prepared.
vm.warp(l2OutputOracle.computeL2Timestamp(_proposedBlockNumber) + 1);
vm.prank(l2OutputOracle.PROPOSER());
l2OutputOracle.proposeL2Output(_outputRoot, _proposedBlockNumber, 0, 0);
// Warp beyond the finalization period for the block we've proposed.
vm.warp(
l2OutputOracle.getL2Output(_proposedOutputIndex).timestamp + l2OutputOracle.FINALIZATION_PERIOD_SECONDS()
+ 1
);
// Fund the portal so that we can withdraw ETH.
vm.deal(address(optimismPortal), 0xFFFFFFFF);
}
function test_depositTransaction_benchmark() external {
optimismPortal.depositTransaction{ value: 100 }(
address(1), 0, 50000, false, hex"0000111122223333444455556666777788889999aaaabbbbccccddddeeeeffff0000"
);
}
function test_depositTransaction_benchmark_1() external {
setPrevBaseFee(vm, address(optimismPortal), 1 gwei);
optimismPortal.depositTransaction{ value: 100 }(
address(1), 0, 50000, false, hex"0000111122223333444455556666777788889999aaaabbbbccccddddeeeeffff0000"
);
}
function test_proveWithdrawalTransaction_benchmark() external {
optimismPortal.proveWithdrawalTransaction(_defaultTx, _proposedOutputIndex, _outputRootProof, _withdrawalProof);
}
}
contract GasBenchMark_L1CrossDomainMessenger is CommonTest {
function test_sendMessage_benchmark_0() external {
vm.pauseGasMetering();
setPrevBaseFee(vm, address(optimismPortal), 1 gwei);
setPrevBaseFee(vm, address(optimismPortal2), 1 gwei);
// The amount of data typically sent during a bridge deposit.
bytes memory data =
hex"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff";
......@@ -121,7 +41,7 @@ contract GasBenchMark_L1CrossDomainMessenger is CommonTest {
function test_sendMessage_benchmark_1() external {
vm.pauseGasMetering();
setPrevBaseFee(vm, address(optimismPortal), 10 gwei);
setPrevBaseFee(vm, address(optimismPortal2), 10 gwei);
// The amount of data typically sent during a bridge deposit.
bytes memory data =
hex"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff";
......@@ -140,21 +60,21 @@ contract GasBenchMark_L1StandardBridge_Deposit is CommonTest {
function test_depositETH_benchmark_0() external {
vm.pauseGasMetering();
setPrevBaseFee(vm, address(optimismPortal), 1 gwei);
setPrevBaseFee(vm, address(optimismPortal2), 1 gwei);
vm.resumeGasMetering();
l1StandardBridge.depositETH{ value: 500 }(50000, hex"");
}
function test_depositETH_benchmark_1() external {
vm.pauseGasMetering();
setPrevBaseFee(vm, address(optimismPortal), 10 gwei);
setPrevBaseFee(vm, address(optimismPortal2), 10 gwei);
vm.resumeGasMetering();
l1StandardBridge.depositETH{ value: 500 }(50000, hex"");
}
function test_depositERC20_benchmark_0() external {
vm.pauseGasMetering();
setPrevBaseFee(vm, address(optimismPortal), 1 gwei);
setPrevBaseFee(vm, address(optimismPortal2), 1 gwei);
vm.resumeGasMetering();
l1StandardBridge.bridgeERC20({
_localToken: address(L1Token),
......@@ -167,7 +87,7 @@ contract GasBenchMark_L1StandardBridge_Deposit is CommonTest {
function test_depositERC20_benchmark_1() external {
vm.pauseGasMetering();
setPrevBaseFee(vm, address(optimismPortal), 10 gwei);
setPrevBaseFee(vm, address(optimismPortal2), 10 gwei);
vm.resumeGasMetering();
l1StandardBridge.bridgeERC20({
_localToken: address(L1Token),
......@@ -200,23 +120,6 @@ contract GasBenchMark_L1StandardBridge_Finalize is CommonTest {
}
}
contract GasBenchMark_L2OutputOracle is CommonTest {
uint256 nextBlockNumber;
function setUp() public override {
super.enableLegacyContracts();
super.setUp();
nextBlockNumber = l2OutputOracle.nextBlockNumber();
warpToProposeTime(nextBlockNumber);
address proposer = deploy.cfg().l2OutputOracleProposer();
vm.startPrank(proposer);
}
function test_proposeL2Output_benchmark() external {
l2OutputOracle.proposeL2Output(nonZeroHash, nextBlockNumber, 0, 0);
}
}
contract GasBenchMark_L1Block is CommonTest {
address depositor;
bytes setValuesCalldata;
......
......@@ -36,7 +36,7 @@ contract CrossDomainMessenger_BaseGas_Test is CommonTest {
}
uint64 baseGas = l1CrossDomainMessenger.baseGas(_data, _minGasLimit);
uint64 minGasLimit = optimismPortal.minimumGasLimit(uint64(_data.length));
uint64 minGasLimit = optimismPortal2.minimumGasLimit(uint64(_data.length));
assertTrue(baseGas >= minGasLimit);
}
}
......@@ -121,7 +121,7 @@ contract CrossDomainMessenger_RelayMessage_Test is CommonTest {
function setUp() public override {
super.setUp();
er = new ExternalRelay(l1CrossDomainMessenger, address(optimismPortal));
er = new ExternalRelay(l1CrossDomainMessenger, address(optimismPortal2));
}
/// @dev This test mocks an OptimismPortal call to the L1CrossDomainMessenger via
......@@ -153,8 +153,8 @@ contract CrossDomainMessenger_RelayMessage_Test is CommonTest {
});
// set the value of op.l2Sender() to be the L2 Cross Domain Messenger.
vm.store(address(optimismPortal), bytes32(senderSlotIndex), bytes32(abi.encode(sender)));
vm.prank(address(optimismPortal));
vm.store(address(optimismPortal2), bytes32(senderSlotIndex), bytes32(abi.encode(sender)));
vm.prank(address(optimismPortal2));
l1CrossDomainMessenger.relayMessage({
_nonce: Encoding.encodeVersionedNonce({ _nonce: 0, _version: 1 }),
_sender: sender,
......
......@@ -15,7 +15,7 @@ contract ExtendedPause_Test is CommonTest {
// validate the paused state
assertTrue(superchainConfig.paused());
assertTrue(optimismPortal.paused());
assertTrue(optimismPortal2.paused());
assertTrue(l1CrossDomainMessenger.paused());
assertTrue(l1StandardBridge.paused());
assertTrue(l1ERC721Bridge.paused());
......@@ -31,7 +31,7 @@ contract ExtendedPause_Test is CommonTest {
// validate the unpaused state
assertFalse(superchainConfig.paused());
assertFalse(optimismPortal.paused());
assertFalse(optimismPortal2.paused());
assertFalse(l1CrossDomainMessenger.paused());
assertFalse(l1StandardBridge.paused());
assertFalse(l1ERC721Bridge.paused());
......
......@@ -12,7 +12,6 @@ import { ForgeArtifacts, Abi, AbiEntry } from "scripts/libraries/ForgeArtifacts.
import { OPContractsManager } from "src/L1/OPContractsManager.sol";
// Interfaces
import { IOptimismPortal } from "interfaces/L1/IOptimismPortal.sol";
import { IOptimismPortal2 } from "interfaces/L1/IOptimismPortal2.sol";
import { IOptimismPortalInterop } from "interfaces/L1/IOptimismPortalInterop.sol";
import { ISystemConfig } from "interfaces/L1/ISystemConfig.sol";
......@@ -210,68 +209,6 @@ contract Specification_Test is CommonTest {
_addSpec({ _name: "L1StandardBridge", _sel: _getSel("version()") });
_addSpec({ _name: "L1StandardBridge", _sel: _getSel("systemConfig()") });
// L2OutputOracle
_addSpec({ _name: "L2OutputOracle", _sel: _getSel("CHALLENGER()") });
_addSpec({ _name: "L2OutputOracle", _sel: _getSel("FINALIZATION_PERIOD_SECONDS()") });
_addSpec({ _name: "L2OutputOracle", _sel: _getSel("L2_BLOCK_TIME()") });
_addSpec({ _name: "L2OutputOracle", _sel: _getSel("PROPOSER()") });
_addSpec({ _name: "L2OutputOracle", _sel: _getSel("SUBMISSION_INTERVAL()") });
_addSpec({ _name: "L2OutputOracle", _sel: _getSel("challenger()") });
_addSpec({ _name: "L2OutputOracle", _sel: _getSel("computeL2Timestamp(uint256)") });
_addSpec({ _name: "L2OutputOracle", _sel: _getSel("deleteL2Outputs(uint256)"), _auth: Role.CHALLENGER });
_addSpec({ _name: "L2OutputOracle", _sel: _getSel("finalizationPeriodSeconds()") });
_addSpec({ _name: "L2OutputOracle", _sel: _getSel("getL2Output(uint256)") });
_addSpec({ _name: "L2OutputOracle", _sel: _getSel("getL2OutputAfter(uint256)") });
_addSpec({ _name: "L2OutputOracle", _sel: _getSel("getL2OutputIndexAfter(uint256)") });
_addSpec({
_name: "L2OutputOracle",
_sel: _getSel("initialize(uint256,uint256,uint256,uint256,address,address,uint256)")
});
_addSpec({ _name: "L2OutputOracle", _sel: _getSel("l2BlockTime()") });
_addSpec({ _name: "L2OutputOracle", _sel: _getSel("latestBlockNumber()") });
_addSpec({ _name: "L2OutputOracle", _sel: _getSel("latestOutputIndex()") });
_addSpec({ _name: "L2OutputOracle", _sel: _getSel("nextBlockNumber()") });
_addSpec({ _name: "L2OutputOracle", _sel: _getSel("nextOutputIndex()") });
_addSpec({
_name: "L2OutputOracle",
_sel: _getSel("proposeL2Output(bytes32,uint256,bytes32,uint256)"),
_auth: Role.PROPOSER
});
_addSpec({ _name: "L2OutputOracle", _sel: _getSel("proposer()") });
_addSpec({ _name: "L2OutputOracle", _sel: _getSel("startingBlockNumber()") });
_addSpec({ _name: "L2OutputOracle", _sel: _getSel("startingTimestamp()") });
_addSpec({ _name: "L2OutputOracle", _sel: _getSel("submissionInterval()") });
_addSpec({ _name: "L2OutputOracle", _sel: _getSel("version()") });
// OptimismPortal
_addSpec({ _name: "OptimismPortal", _sel: _getSel("depositTransaction(address,uint256,uint64,bool,bytes)") });
_addSpec({ _name: "OptimismPortal", _sel: _getSel("donateETH()") });
_addSpec({
_name: "OptimismPortal",
_sel: IOptimismPortal.finalizeWithdrawalTransaction.selector,
_pausable: true
});
_addSpec({ _name: "OptimismPortal", _sel: _getSel("finalizedWithdrawals(bytes32)") });
_addSpec({ _name: "OptimismPortal", _sel: _getSel("guardian()") });
_addSpec({ _name: "OptimismPortal", _sel: _getSel("initialize(address,address,address)") });
_addSpec({ _name: "OptimismPortal", _sel: _getSel("isOutputFinalized(uint256)") });
_addSpec({ _name: "OptimismPortal", _sel: _getSel("l2Oracle()") });
_addSpec({ _name: "OptimismPortal", _sel: _getSel("l2Sender()") });
_addSpec({ _name: "OptimismPortal", _sel: _getSel("minimumGasLimit(uint64)") });
_addSpec({ _name: "OptimismPortal", _sel: _getSel("params()") });
_addSpec({ _name: "OptimismPortal", _sel: _getSel("paused()") });
_addSpec({ _name: "OptimismPortal", _sel: IOptimismPortal.proveWithdrawalTransaction.selector, _pausable: true });
_addSpec({ _name: "OptimismPortal", _sel: _getSel("provenWithdrawals(bytes32)") });
_addSpec({ _name: "OptimismPortal", _sel: _getSel("superchainConfig()") });
_addSpec({ _name: "OptimismPortal", _sel: _getSel("systemConfig()") });
_addSpec({ _name: "OptimismPortal", _sel: _getSel("version()") });
_addSpec({ _name: "OptimismPortal", _sel: _getSel("balance()") });
_addSpec({
_name: "OptimismPortal",
_sel: _getSel("depositERC20Transaction(address,uint256,uint256,uint64,bool,bytes)")
});
_addSpec({ _name: "OptimismPortal", _sel: _getSel("setGasPayingToken(address,uint8,bytes32,bytes32)") });
// OptimismPortalInterop
_addSpec({
_name: "OptimismPortalInterop",
......
......@@ -72,7 +72,7 @@ contract Initializer_Test is CommonTest {
name: "L1CrossDomainMessenger",
target: deploy.mustGetAddress("L1CrossDomainMessenger"),
initCalldata: abi.encodeCall(
l1CrossDomainMessenger.initialize, (superchainConfig, optimismPortal, systemConfig)
l1CrossDomainMessenger.initialize, (superchainConfig, optimismPortal2, systemConfig)
)
})
);
......@@ -82,7 +82,7 @@ contract Initializer_Test is CommonTest {
name: "L1CrossDomainMessengerProxy",
target: address(l1CrossDomainMessenger),
initCalldata: abi.encodeCall(
l1CrossDomainMessenger.initialize, (superchainConfig, optimismPortal, systemConfig)
l1CrossDomainMessenger.initialize, (superchainConfig, optimismPortal2, systemConfig)
)
})
);
......@@ -118,43 +118,27 @@ contract Initializer_Test is CommonTest {
initCalldata: abi.encodeCall(delayedWeth.initialize, (address(0), ISuperchainConfig(address(0))))
})
);
// L2OutputOracleImpl
contracts.push(
InitializeableContract({
name: "L2OutputOracle",
target: deploy.mustGetAddress("L2OutputOracle"),
initCalldata: abi.encodeCall(l2OutputOracle.initialize, (0, 0, 0, 0, address(0), address(0), 0))
})
);
// L2OutputOracleProxy
contracts.push(
InitializeableContract({
name: "L2OutputOracleProxy",
target: address(l2OutputOracle),
initCalldata: abi.encodeCall(l2OutputOracle.initialize, (0, 0, 0, 0, address(0), address(0), 0))
})
);
// OptimismPortalImpl
// OptimismPortal2Impl
contracts.push(
InitializeableContract({
name: "OptimismPortal",
target: deploy.mustGetAddress("OptimismPortal"),
initCalldata: abi.encodeCall(optimismPortal.initialize, (l2OutputOracle, systemConfig, superchainConfig))
name: "OptimismPortal2",
target: deploy.mustGetAddress("OptimismPortal2"),
initCalldata: abi.encodeCall(
optimismPortal2.initialize,
(
disputeGameFactory,
systemConfig,
superchainConfig,
GameType.wrap(uint32(deploy.cfg().respectedGameType()))
)
)
})
);
// OptimismPortalProxy
contracts.push(
InitializeableContract({
name: "OptimismPortalProxy",
target: address(optimismPortal),
initCalldata: abi.encodeCall(optimismPortal.initialize, (l2OutputOracle, systemConfig, superchainConfig))
})
);
// OptimismPortal2Impl
contracts.push(
InitializeableContract({
name: "OptimismPortal2",
target: deploy.mustGetAddress("OptimismPortal2"),
name: "OptimismPortal2Proxy",
target: address(optimismPortal2),
initCalldata: abi.encodeCall(
optimismPortal2.initialize,
(
......@@ -380,9 +364,6 @@ contract Initializer_Test is CommonTest {
)
})
);
// Nicknamed contracts.
nicknames["OptimismPortal2Proxy"] = "OptimismPortalProxy";
}
/// @notice Tests that:
......@@ -488,7 +469,8 @@ contract Initializer_Test is CommonTest {
function _hasMatchingContract(string memory _name) internal view returns (bool matching_) {
for (uint256 i; i < contracts.length; i++) {
if (LibString.eq(contracts[i].name, _getRealContractName(_name))) {
matching_ = true;
// return early
return true;
}
}
}
......
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