Commit 40214739 authored by Maurelian's avatar Maurelian

feat(ctb): Checked out pre-mcp impl contracts

parent b01ff541
...@@ -12,36 +12,33 @@ import { Constants } from "src/libraries/Constants.sol"; ...@@ -12,36 +12,33 @@ import { Constants } from "src/libraries/Constants.sol";
/// @notice The L1CrossDomainMessenger is a message passing interface between L1 and L2 responsible /// @notice The L1CrossDomainMessenger is a message passing interface between L1 and L2 responsible
/// for sending and receiving data on the L1 side. Users are encouraged to use this /// for sending and receiving data on the L1 side. Users are encouraged to use this
/// interface instead of interacting with lower-level contracts directly. /// interface instead of interacting with lower-level contracts directly.
contract L1CrossDomainMessenger is CrossDomainMessenger, ISemver { contract L1CrossDomainMessenger is CrossDomainMessenger, Semver {
/// @notice Address of the OptimismPortal. The public getter for this /// @notice Address of the OptimismPortal.
/// is legacy and will be removed in the future. Use `portal()` instead. OptimismPortal public immutable PORTAL;
/// @custom:network-specific
/// @custom:legacy
OptimismPortal public PORTAL;
/// @notice Semantic version.
/// @custom:semver 1.7.1
string public constant version = "1.7.1";
/// @custom:semver 1.4.1
/// @notice Constructs the L1CrossDomainMessenger contract. /// @notice Constructs the L1CrossDomainMessenger contract.
constructor() CrossDomainMessenger(Predeploys.L2_CROSS_DOMAIN_MESSENGER) {
initialize({ _portal: OptimismPortal(payable(0)) });
}
/// @notice Initializes the contract.
/// @param _portal Address of the OptimismPortal contract on this network. /// @param _portal Address of the OptimismPortal contract on this network.
function initialize(OptimismPortal _portal) public reinitializer(Constants.INITIALIZER) { constructor(OptimismPortal _portal)
Semver(1, 4, 1)
CrossDomainMessenger(Predeploys.L2_CROSS_DOMAIN_MESSENGER)
{
PORTAL = _portal; PORTAL = _portal;
__CrossDomainMessenger_init(); initialize();
} }
/// @notice Getter for the OptimismPortal address. /// @notice Initializes the contract.
function portal() external view returns (address) { function initialize() public initializer {
return address(PORTAL); __CrossDomainMessenger_init();
} }
/// @inheritdoc CrossDomainMessenger /// @inheritdoc CrossDomainMessenger
function _sendMessage(address _to, uint64 _gasLimit, uint256 _value, bytes memory _data) internal override { function _sendMessage(
address _to,
uint64 _gasLimit,
uint256 _value,
bytes memory _data
) internal override {
PORTAL.depositTransaction{ value: _value }(_to, _value, _gasLimit, false, _data); PORTAL.depositTransaction{ value: _value }(_to, _value, _gasLimit, false, _data);
} }
......
...@@ -13,25 +13,19 @@ import { Constants } from "src/libraries/Constants.sol"; ...@@ -13,25 +13,19 @@ import { Constants } from "src/libraries/Constants.sol";
/// @notice The L1 ERC721 bridge is a contract which works together with the L2 ERC721 bridge to /// @notice The L1 ERC721 bridge is a contract which works together with the L2 ERC721 bridge to
/// make it possible to transfer ERC721 tokens from Ethereum to Optimism. This contract /// make it possible to transfer ERC721 tokens from Ethereum to Optimism. This contract
/// acts as an escrow for ERC721 tokens deposited into L2. /// acts as an escrow for ERC721 tokens deposited into L2.
contract L1ERC721Bridge is ERC721Bridge, ISemver { contract L1ERC721Bridge is ERC721Bridge, Semver {
/// @notice Mapping of L1 token to L2 token to ID to boolean, indicating if the given L1 token /// @notice Mapping of L1 token to L2 token to ID to boolean, indicating if the given L1 token
/// by ID was deposited for a given L2 token. /// by ID was deposited for a given L2 token.
mapping(address => mapping(address => mapping(uint256 => bool))) public deposits; mapping(address => mapping(address => mapping(uint256 => bool))) public deposits;
/// @notice Semantic version. /// @custom:semver 1.1.2
/// @custom:semver 1.4.1 /// @notice Constructs the L1ERC721Bridge contract.
string public constant version = "1.4.1";
/// @notice Constructs the contract.
constructor() ERC721Bridge(Predeploys.L2_ERC721_BRIDGE) {
initialize({ _messenger: CrossDomainMessenger(address(0)) });
}
/// @notice Initializes the contract.
/// @param _messenger Address of the CrossDomainMessenger on this network. /// @param _messenger Address of the CrossDomainMessenger on this network.
function initialize(CrossDomainMessenger _messenger) public reinitializer(Constants.INITIALIZER) { /// @param _otherBridge Address of the ERC721 bridge on the other network.
__ERC721Bridge_init({ _messenger: _messenger }); constructor(address _messenger, address _otherBridge)
} Semver(1, 1, 2)
ERC721Bridge(_messenger, _otherBridge)
{}
/// @notice Completes an ERC721 bridge from the other domain and sends the ERC721 token to the /// @notice Completes an ERC721 bridge from the other domain and sends the ERC721 token to the
/// recipient on this domain. /// recipient on this domain.
...@@ -50,10 +44,7 @@ contract L1ERC721Bridge is ERC721Bridge, ISemver { ...@@ -50,10 +44,7 @@ contract L1ERC721Bridge is ERC721Bridge, ISemver {
address _to, address _to,
uint256 _tokenId, uint256 _tokenId,
bytes calldata _extraData bytes calldata _extraData
) ) external onlyOtherBridge {
external
onlyOtherBridge
{
require(_localToken != address(this), "L1ERC721Bridge: local token cannot be self"); require(_localToken != address(this), "L1ERC721Bridge: local token cannot be self");
// Checks that the L1/L2 NFT pair has a token ID that is escrowed in the L1 Bridge. // Checks that the L1/L2 NFT pair has a token ID that is escrowed in the L1 Bridge.
...@@ -83,15 +74,18 @@ contract L1ERC721Bridge is ERC721Bridge, ISemver { ...@@ -83,15 +74,18 @@ contract L1ERC721Bridge is ERC721Bridge, ISemver {
uint256 _tokenId, uint256 _tokenId,
uint32 _minGasLimit, uint32 _minGasLimit,
bytes calldata _extraData bytes calldata _extraData
) ) internal override {
internal
override
{
require(_remoteToken != address(0), "L1ERC721Bridge: remote token cannot be address(0)"); require(_remoteToken != address(0), "L1ERC721Bridge: remote token cannot be address(0)");
// Construct calldata for _l2Token.finalizeBridgeERC721(_to, _tokenId) // Construct calldata for _l2Token.finalizeBridgeERC721(_to, _tokenId)
bytes memory message = abi.encodeWithSelector( bytes memory message = abi.encodeWithSelector(
L2ERC721Bridge.finalizeBridgeERC721.selector, _remoteToken, _localToken, _from, _to, _tokenId, _extraData L2ERC721Bridge.finalizeBridgeERC721.selector,
_remoteToken,
_localToken,
_from,
_to,
_tokenId,
_extraData
); );
// Lock token into bridge // Lock token into bridge
...@@ -99,7 +93,7 @@ contract L1ERC721Bridge is ERC721Bridge, ISemver { ...@@ -99,7 +93,7 @@ contract L1ERC721Bridge is ERC721Bridge, ISemver {
IERC721(_localToken).transferFrom(_from, address(this), _tokenId); IERC721(_localToken).transferFrom(_from, address(this), _tokenId);
// Send calldata into L2 // Send calldata into L2
messenger.sendMessage(OTHER_BRIDGE, message, _minGasLimit); MESSENGER.sendMessage(OTHER_BRIDGE, message, _minGasLimit);
emit ERC721BridgeInitiated(_localToken, _remoteToken, _from, _to, _tokenId, _extraData); emit ERC721BridgeInitiated(_localToken, _remoteToken, _from, _to, _tokenId, _extraData);
} }
} }
...@@ -17,14 +17,19 @@ import { Constants } from "src/libraries/Constants.sol"; ...@@ -17,14 +17,19 @@ import { Constants } from "src/libraries/Constants.sol";
/// NOTE: this contract is not intended to support all variations of ERC20 tokens. Examples /// NOTE: this contract is not intended to support all variations of ERC20 tokens. Examples
/// of some token types that may not be properly supported by this contract include, but are /// of some token types that may not be properly supported by this contract include, but are
/// not limited to: tokens with transfer fees, rebasing tokens, and tokens with blocklists. /// not limited to: tokens with transfer fees, rebasing tokens, and tokens with blocklists.
contract L1StandardBridge is StandardBridge, ISemver { contract L1StandardBridge is StandardBridge, Semver {
/// @custom:legacy /// @custom:legacy
/// @notice Emitted whenever a deposit of ETH from L1 into L2 is initiated. /// @notice Emitted whenever a deposit of ETH from L1 into L2 is initiated.
/// @param from Address of the depositor. /// @param from Address of the depositor.
/// @param to Address of the recipient on L2. /// @param to Address of the recipient on L2.
/// @param amount Amount of ETH deposited. /// @param amount Amount of ETH deposited.
/// @param extraData Extra data attached to the deposit. /// @param extraData Extra data attached to the deposit.
event ETHDepositInitiated(address indexed from, address indexed to, uint256 amount, bytes extraData); event ETHDepositInitiated(
address indexed from,
address indexed to,
uint256 amount,
bytes extraData
);
/// @custom:legacy /// @custom:legacy
/// @notice Emitted whenever a withdrawal of ETH from L2 to L1 is finalized. /// @notice Emitted whenever a withdrawal of ETH from L2 to L1 is finalized.
...@@ -32,7 +37,12 @@ contract L1StandardBridge is StandardBridge, ISemver { ...@@ -32,7 +37,12 @@ contract L1StandardBridge is StandardBridge, ISemver {
/// @param to Address of the recipient on L1. /// @param to Address of the recipient on L1.
/// @param amount Amount of ETH withdrawn. /// @param amount Amount of ETH withdrawn.
/// @param extraData Extra data attached to the withdrawal. /// @param extraData Extra data attached to the withdrawal.
event ETHWithdrawalFinalized(address indexed from, address indexed to, uint256 amount, bytes extraData); event ETHWithdrawalFinalized(
address indexed from,
address indexed to,
uint256 amount,
bytes extraData
);
/// @custom:legacy /// @custom:legacy
/// @notice Emitted whenever an ERC20 deposit is initiated. /// @notice Emitted whenever an ERC20 deposit is initiated.
...@@ -68,19 +78,13 @@ contract L1StandardBridge is StandardBridge, ISemver { ...@@ -68,19 +78,13 @@ contract L1StandardBridge is StandardBridge, ISemver {
bytes extraData bytes extraData
); );
/// @notice Semantic version. /// @custom:semver 1.1.1
/// @custom:semver 1.4.1
string public constant version = "1.4.1";
/// @notice Constructs the L1StandardBridge contract. /// @notice Constructs the L1StandardBridge contract.
constructor() StandardBridge(StandardBridge(payable(Predeploys.L2_STANDARD_BRIDGE))) { /// @param _messenger Address of the L1CrossDomainMessenger.
initialize({ _messenger: CrossDomainMessenger(address(0)) }); constructor(address payable _messenger)
} Semver(1, 1, 1)
StandardBridge(_messenger, payable(Predeploys.L2_STANDARD_BRIDGE))
/// @notice Initializer {}
function initialize(CrossDomainMessenger _messenger) public reinitializer(Constants.INITIALIZER) {
__StandardBridge_init({ _messenger: _messenger });
}
/// @notice Allows EOAs to bridge ETH by sending directly to the bridge. /// @notice Allows EOAs to bridge ETH by sending directly to the bridge.
receive() external payable override onlyEOA { receive() external payable override onlyEOA {
...@@ -108,7 +112,11 @@ contract L1StandardBridge is StandardBridge, ISemver { ...@@ -108,7 +112,11 @@ contract L1StandardBridge is StandardBridge, ISemver {
/// @param _extraData Optional data to forward to L2. /// @param _extraData Optional data to forward to L2.
/// Data supplied here will not be used to execute any code on L2 and is /// Data supplied here will not be used to execute any code on L2 and is
/// only emitted as extra data for the convenience of off-chain tooling. /// only emitted as extra data for the convenience of off-chain tooling.
function depositETHTo(address _to, uint32 _minGasLimit, bytes calldata _extraData) external payable { function depositETHTo(
address _to,
uint32 _minGasLimit,
bytes calldata _extraData
) external payable {
_initiateETHDeposit(msg.sender, _to, _minGasLimit, _extraData); _initiateETHDeposit(msg.sender, _to, _minGasLimit, _extraData);
} }
...@@ -127,12 +135,16 @@ contract L1StandardBridge is StandardBridge, ISemver { ...@@ -127,12 +135,16 @@ contract L1StandardBridge is StandardBridge, ISemver {
uint256 _amount, uint256 _amount,
uint32 _minGasLimit, uint32 _minGasLimit,
bytes calldata _extraData bytes calldata _extraData
) ) external virtual onlyEOA {
external _initiateERC20Deposit(
virtual _l1Token,
onlyEOA _l2Token,
{ msg.sender,
_initiateERC20Deposit(_l1Token, _l2Token, msg.sender, msg.sender, _amount, _minGasLimit, _extraData); msg.sender,
_amount,
_minGasLimit,
_extraData
);
} }
/// @custom:legacy /// @custom:legacy
...@@ -152,11 +164,16 @@ contract L1StandardBridge is StandardBridge, ISemver { ...@@ -152,11 +164,16 @@ contract L1StandardBridge is StandardBridge, ISemver {
uint256 _amount, uint256 _amount,
uint32 _minGasLimit, uint32 _minGasLimit,
bytes calldata _extraData bytes calldata _extraData
) ) external virtual {
external _initiateERC20Deposit(
virtual _l1Token,
{ _l2Token,
_initiateERC20Deposit(_l1Token, _l2Token, msg.sender, _to, _amount, _minGasLimit, _extraData); msg.sender,
_to,
_amount,
_minGasLimit,
_extraData
);
} }
/// @custom:legacy /// @custom:legacy
...@@ -170,10 +187,7 @@ contract L1StandardBridge is StandardBridge, ISemver { ...@@ -170,10 +187,7 @@ contract L1StandardBridge is StandardBridge, ISemver {
address _to, address _to,
uint256 _amount, uint256 _amount,
bytes calldata _extraData bytes calldata _extraData
) ) external payable {
external
payable
{
finalizeBridgeETH(_from, _to, _amount, _extraData); finalizeBridgeETH(_from, _to, _amount, _extraData);
} }
...@@ -192,9 +206,7 @@ contract L1StandardBridge is StandardBridge, ISemver { ...@@ -192,9 +206,7 @@ contract L1StandardBridge is StandardBridge, ISemver {
address _to, address _to,
uint256 _amount, uint256 _amount,
bytes calldata _extraData bytes calldata _extraData
) ) external {
external
{
finalizeBridgeERC20(_l1Token, _l2Token, _from, _to, _amount, _extraData); finalizeBridgeERC20(_l1Token, _l2Token, _from, _to, _amount, _extraData);
} }
...@@ -210,7 +222,12 @@ contract L1StandardBridge is StandardBridge, ISemver { ...@@ -210,7 +222,12 @@ contract L1StandardBridge is StandardBridge, ISemver {
/// @param _to Address of the recipient on L2. /// @param _to Address of the recipient on L2.
/// @param _minGasLimit Minimum gas limit for the deposit message on L2. /// @param _minGasLimit Minimum gas limit for the deposit message on L2.
/// @param _extraData Optional data to forward to L2. /// @param _extraData Optional data to forward to L2.
function _initiateETHDeposit(address _from, address _to, uint32 _minGasLimit, bytes memory _extraData) internal { function _initiateETHDeposit(
address _from,
address _to,
uint32 _minGasLimit,
bytes memory _extraData
) internal {
_initiateBridgeETH(_from, _to, msg.value, _minGasLimit, _extraData); _initiateBridgeETH(_from, _to, msg.value, _minGasLimit, _extraData);
} }
...@@ -230,9 +247,7 @@ contract L1StandardBridge is StandardBridge, ISemver { ...@@ -230,9 +247,7 @@ contract L1StandardBridge is StandardBridge, ISemver {
uint256 _amount, uint256 _amount,
uint32 _minGasLimit, uint32 _minGasLimit,
bytes memory _extraData bytes memory _extraData
) ) internal {
internal
{
_initiateBridgeERC20(_l1Token, _l2Token, _from, _to, _amount, _minGasLimit, _extraData); _initiateBridgeERC20(_l1Token, _l2Token, _from, _to, _amount, _minGasLimit, _extraData);
} }
...@@ -244,10 +259,7 @@ contract L1StandardBridge is StandardBridge, ISemver { ...@@ -244,10 +259,7 @@ contract L1StandardBridge is StandardBridge, ISemver {
address _to, address _to,
uint256 _amount, uint256 _amount,
bytes memory _extraData bytes memory _extraData
) ) internal override {
internal
override
{
emit ETHDepositInitiated(_from, _to, _amount, _extraData); emit ETHDepositInitiated(_from, _to, _amount, _extraData);
super._emitETHBridgeInitiated(_from, _to, _amount, _extraData); super._emitETHBridgeInitiated(_from, _to, _amount, _extraData);
} }
...@@ -260,10 +272,7 @@ contract L1StandardBridge is StandardBridge, ISemver { ...@@ -260,10 +272,7 @@ contract L1StandardBridge is StandardBridge, ISemver {
address _to, address _to,
uint256 _amount, uint256 _amount,
bytes memory _extraData bytes memory _extraData
) ) internal override {
internal
override
{
emit ETHWithdrawalFinalized(_from, _to, _amount, _extraData); emit ETHWithdrawalFinalized(_from, _to, _amount, _extraData);
super._emitETHBridgeFinalized(_from, _to, _amount, _extraData); super._emitETHBridgeFinalized(_from, _to, _amount, _extraData);
} }
...@@ -278,10 +287,7 @@ contract L1StandardBridge is StandardBridge, ISemver { ...@@ -278,10 +287,7 @@ contract L1StandardBridge is StandardBridge, ISemver {
address _to, address _to,
uint256 _amount, uint256 _amount,
bytes memory _extraData bytes memory _extraData
) ) internal override {
internal
override
{
emit ERC20DepositInitiated(_localToken, _remoteToken, _from, _to, _amount, _extraData); emit ERC20DepositInitiated(_localToken, _remoteToken, _from, _to, _amount, _extraData);
super._emitERC20BridgeInitiated(_localToken, _remoteToken, _from, _to, _amount, _extraData); super._emitERC20BridgeInitiated(_localToken, _remoteToken, _from, _to, _amount, _extraData);
} }
...@@ -296,10 +302,7 @@ contract L1StandardBridge is StandardBridge, ISemver { ...@@ -296,10 +302,7 @@ contract L1StandardBridge is StandardBridge, ISemver {
address _to, address _to,
uint256 _amount, uint256 _amount,
bytes memory _extraData bytes memory _extraData
) ) internal override {
internal
override
{
emit ERC20WithdrawalFinalized(_localToken, _remoteToken, _from, _to, _amount, _extraData); emit ERC20WithdrawalFinalized(_localToken, _remoteToken, _from, _to, _amount, _extraData);
super._emitERC20BridgeFinalized(_localToken, _remoteToken, _from, _to, _amount, _extraData); super._emitERC20BridgeFinalized(_localToken, _remoteToken, _from, _to, _amount, _extraData);
} }
......
...@@ -11,25 +11,22 @@ import { Constants } from "src/libraries/Constants.sol"; ...@@ -11,25 +11,22 @@ import { Constants } from "src/libraries/Constants.sol";
/// @notice The L2OutputOracle contains an array of L2 state outputs, where each output is a /// @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 /// commitment to the state of the L2 chain. Other contracts like the OptimismPortal use
/// these outputs to verify information about the state of L2. /// these outputs to verify information about the state of L2.
contract L2OutputOracle is Initializable, ISemver { contract L2OutputOracle is Initializable, Semver {
/// @notice The interval in L2 blocks at which checkpoints must be submitted. /// @notice The interval in L2 blocks at which checkpoints must be submitted.
/// Although this is immutable, it can safely be modified by upgrading the /// Although this is immutable, it can safely be modified by upgrading the
/// implementation contract. /// implementation contract.
/// Public getter is legacy and will be removed in the future. Use `submissionInterval`
/// instead.
/// @custom:legacy
uint256 public immutable SUBMISSION_INTERVAL; uint256 public immutable SUBMISSION_INTERVAL;
/// @notice The time between L2 blocks in seconds. Once set, this value MUST NOT be modified. /// @notice The time between L2 blocks in seconds. Once set, this value MUST NOT be modified.
/// Public getter is legacy and will be removed in the future. Use `l2BlockTime`
/// instead.
/// @custom:legacy
uint256 public immutable L2_BLOCK_TIME; uint256 public immutable L2_BLOCK_TIME;
/// @notice The address of the challenger. Can be updated via upgrade.
address public immutable CHALLENGER;
/// @notice The address of the proposer. Can be updated via upgrade.
address public immutable PROPOSER;
/// @notice The minimum time (in seconds) that must elapse before a withdrawal can be finalized. /// @notice The minimum time (in seconds) that must elapse before a withdrawal can be finalized.
/// Public getter is legacy and will be removed in the future. Use
// `finalizationPeriodSeconds` instead.
/// @custom:legacy
uint256 public immutable FINALIZATION_PERIOD_SECONDS; uint256 public immutable FINALIZATION_PERIOD_SECONDS;
/// @notice The number of the first L2 block recorded in this contract. /// @notice The number of the first L2 block recorded in this contract.
...@@ -41,21 +38,16 @@ contract L2OutputOracle is Initializable, ISemver { ...@@ -41,21 +38,16 @@ contract L2OutputOracle is Initializable, ISemver {
/// @notice An array of L2 output proposals. /// @notice An array of L2 output proposals.
Types.OutputProposal[] internal l2Outputs; Types.OutputProposal[] internal l2Outputs;
/// @notice The address of the challenger. Can be updated via reinitialize.
/// @custom:network-specific
address public challenger;
/// @notice The address of the proposer. Can be updated via reinitialize.
/// @custom:network-specific
address public proposer;
/// @notice Emitted when an output is proposed. /// @notice Emitted when an output is proposed.
/// @param outputRoot The output root. /// @param outputRoot The output root.
/// @param l2OutputIndex The index of the output in the l2Outputs array. /// @param l2OutputIndex The index of the output in the l2Outputs array.
/// @param l2BlockNumber The L2 block number of the output root. /// @param l2BlockNumber The L2 block number of the output root.
/// @param l1Timestamp The L1 timestamp when proposed. /// @param l1Timestamp The L1 timestamp when proposed.
event OutputProposed( event OutputProposed(
bytes32 indexed outputRoot, uint256 indexed l2OutputIndex, uint256 indexed l2BlockNumber, uint256 l1Timestamp bytes32 indexed outputRoot,
uint256 indexed l2OutputIndex,
uint256 indexed l2BlockNumber,
uint256 l1Timestamp
); );
/// @notice Emitted when outputs are deleted. /// @notice Emitted when outputs are deleted.
...@@ -63,39 +55,44 @@ contract L2OutputOracle is Initializable, ISemver { ...@@ -63,39 +55,44 @@ contract L2OutputOracle is Initializable, ISemver {
/// @param newNextOutputIndex Next L2 output index after the deletion. /// @param newNextOutputIndex Next L2 output index after the deletion.
event OutputsDeleted(uint256 indexed prevNextOutputIndex, uint256 indexed newNextOutputIndex); event OutputsDeleted(uint256 indexed prevNextOutputIndex, uint256 indexed newNextOutputIndex);
/// @notice Semantic version. /// @custom:semver 1.3.1
/// @custom:semver 1.6.0
string public constant version = "1.6.0";
/// @notice Constructs the L2OutputOracle contract. /// @notice Constructs the L2OutputOracle contract.
/// @param _submissionInterval Interval in blocks at which checkpoints must be submitted. /// @param _submissionInterval Interval in blocks at which checkpoints must be submitted.
/// @param _l2BlockTime The time per L2 block, in seconds. /// @param _l2BlockTime The time per L2 block, in seconds.
/// @param _finalizationPeriodSeconds The amount of time that must pass for an output proposal /// @param _startingBlockNumber The number of the first L2 block.
// to be considered canonical. /// @param _startingTimestamp The timestamp of the first L2 block.
constructor(uint256 _submissionInterval, uint256 _l2BlockTime, uint256 _finalizationPeriodSeconds) { /// @param _proposer The address of the proposer.
/// @param _challenger The address of the challenger.
constructor(
uint256 _submissionInterval,
uint256 _l2BlockTime,
uint256 _startingBlockNumber,
uint256 _startingTimestamp,
address _proposer,
address _challenger,
uint256 _finalizationPeriodSeconds
) Semver(1, 3, 1) {
require(_l2BlockTime > 0, "L2OutputOracle: L2 block time must be greater than 0"); require(_l2BlockTime > 0, "L2OutputOracle: L2 block time must be greater than 0");
require(_submissionInterval > 0, "L2OutputOracle: submission interval must be greater than 0"); require(
_submissionInterval > 0,
"L2OutputOracle: submission interval must be greater than 0"
);
SUBMISSION_INTERVAL = _submissionInterval; SUBMISSION_INTERVAL = _submissionInterval;
L2_BLOCK_TIME = _l2BlockTime; L2_BLOCK_TIME = _l2BlockTime;
PROPOSER = _proposer;
CHALLENGER = _challenger;
FINALIZATION_PERIOD_SECONDS = _finalizationPeriodSeconds; FINALIZATION_PERIOD_SECONDS = _finalizationPeriodSeconds;
initialize({ _startingBlockNumber: 0, _startingTimestamp: 0, _proposer: address(0), _challenger: address(0) }); initialize(_startingBlockNumber, _startingTimestamp);
} }
/// @notice Initializer. /// @notice Initializer.
/// @param _startingBlockNumber Block number for the first recoded L2 block. /// @param _startingBlockNumber Block number for the first recoded L2 block.
/// @param _startingTimestamp Timestamp for the first recoded L2 block. /// @param _startingTimestamp Timestamp for the first recoded L2 block.
/// @param _proposer The address of the proposer. function initialize(uint256 _startingBlockNumber, uint256 _startingTimestamp)
/// @param _challenger The address of the challenger.
function initialize(
uint256 _startingBlockNumber,
uint256 _startingTimestamp,
address _proposer,
address _challenger
)
public public
reinitializer(Constants.INITIALIZER) initializer
{ {
require( require(
_startingTimestamp <= block.timestamp, _startingTimestamp <= block.timestamp,
...@@ -104,37 +101,6 @@ contract L2OutputOracle is Initializable, ISemver { ...@@ -104,37 +101,6 @@ contract L2OutputOracle is Initializable, ISemver {
startingTimestamp = _startingTimestamp; startingTimestamp = _startingTimestamp;
startingBlockNumber = _startingBlockNumber; startingBlockNumber = _startingBlockNumber;
proposer = _proposer;
challenger = _challenger;
}
/// @notice Getter for the output proposal submission interval.
function submissionInterval() external view returns (uint256) {
return SUBMISSION_INTERVAL;
}
/// @notice Getter for the L2 block time.
function l2BlockTime() external view returns (uint256) {
return L2_BLOCK_TIME;
}
/// @notice Getter for the finalization period.
function finalizationPeriodSeconds() external view returns (uint256) {
return FINALIZATION_PERIOD_SECONDS;
}
/// @notice Getter for the challenger address. This will be removed
/// in the future, use `challenger` instead.
/// @custom:legacy
function CHALLENGER() external view returns (address) {
return challenger;
}
/// @notice Getter for the proposer address. This will be removed in the
/// future, use `proposer` instead.
/// @custom:legacy
function PROPOSER() external view returns (address) {
return proposer;
} }
/// @notice Deletes all output proposals after and including the proposal that corresponds to /// @notice Deletes all output proposals after and including the proposal that corresponds to
...@@ -143,11 +109,15 @@ contract L2OutputOracle is Initializable, ISemver { ...@@ -143,11 +109,15 @@ contract L2OutputOracle is Initializable, ISemver {
/// All outputs after this output will also be deleted. /// All outputs after this output will also be deleted.
// solhint-disable-next-line ordering // solhint-disable-next-line ordering
function deleteL2Outputs(uint256 _l2OutputIndex) external { function deleteL2Outputs(uint256 _l2OutputIndex) external {
require(msg.sender == challenger, "L2OutputOracle: only the challenger address can delete outputs"); require(
msg.sender == CHALLENGER,
"L2OutputOracle: only the challenger address can delete outputs"
);
// Make sure we're not *increasing* the length of the array. // Make sure we're not *increasing* the length of the array.
require( require(
_l2OutputIndex < l2Outputs.length, "L2OutputOracle: cannot delete outputs after the latest output index" _l2OutputIndex < l2Outputs.length,
"L2OutputOracle: cannot delete outputs after the latest output index"
); );
// Do not allow deleting any outputs that have already been finalized. // Do not allow deleting any outputs that have already been finalized.
...@@ -178,11 +148,11 @@ contract L2OutputOracle is Initializable, ISemver { ...@@ -178,11 +148,11 @@ contract L2OutputOracle is Initializable, ISemver {
uint256 _l2BlockNumber, uint256 _l2BlockNumber,
bytes32 _l1BlockHash, bytes32 _l1BlockHash,
uint256 _l1BlockNumber uint256 _l1BlockNumber
) ) external payable {
external require(
payable msg.sender == PROPOSER,
{ "L2OutputOracle: only the proposer address can propose new outputs"
require(msg.sender == proposer, "L2OutputOracle: only the proposer address can propose new outputs"); );
require( require(
_l2BlockNumber == nextBlockNumber(), _l2BlockNumber == nextBlockNumber(),
...@@ -194,7 +164,10 @@ contract L2OutputOracle is Initializable, ISemver { ...@@ -194,7 +164,10 @@ contract L2OutputOracle is Initializable, ISemver {
"L2OutputOracle: cannot propose L2 output in the future" "L2OutputOracle: cannot propose L2 output in the future"
); );
require(_outputRoot != bytes32(0), "L2OutputOracle: L2 output proposal cannot be the zero hash"); require(
_outputRoot != bytes32(0),
"L2OutputOracle: L2 output proposal cannot be the zero hash"
);
if (_l1BlockHash != bytes32(0)) { if (_l1BlockHash != bytes32(0)) {
// This check allows the proposer to propose an output based on a given L1 block, // This check allows the proposer to propose an output based on a given L1 block,
...@@ -225,7 +198,11 @@ contract L2OutputOracle is Initializable, ISemver { ...@@ -225,7 +198,11 @@ contract L2OutputOracle is Initializable, ISemver {
/// @notice Returns an output by index. Needed to return a struct instead of a tuple. /// @notice Returns an output by index. Needed to return a struct instead of a tuple.
/// @param _l2OutputIndex Index of the output to return. /// @param _l2OutputIndex Index of the output to return.
/// @return The output at the given index. /// @return The output at the given index.
function getL2Output(uint256 _l2OutputIndex) external view returns (Types.OutputProposal memory) { function getL2Output(uint256 _l2OutputIndex)
external
view
returns (Types.OutputProposal memory)
{
return l2Outputs[_l2OutputIndex]; return l2Outputs[_l2OutputIndex];
} }
...@@ -242,7 +219,10 @@ contract L2OutputOracle is Initializable, ISemver { ...@@ -242,7 +219,10 @@ contract L2OutputOracle is Initializable, ISemver {
); );
// Make sure there's at least one output 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"); require(
l2Outputs.length > 0,
"L2OutputOracle: cannot get output as no outputs have been proposed yet"
);
// Find the output via binary search, guaranteed to exist. // Find the output via binary search, guaranteed to exist.
uint256 lo = 0; uint256 lo = 0;
...@@ -264,7 +244,11 @@ contract L2OutputOracle is Initializable, ISemver { ...@@ -264,7 +244,11 @@ contract L2OutputOracle is Initializable, ISemver {
/// block. /// block.
/// @param _l2BlockNumber L2 block number to find a checkpoint for. /// @param _l2BlockNumber L2 block number to find a checkpoint for.
/// @return First checkpoint that commits to the given L2 block number. /// @return First checkpoint that commits to the given L2 block number.
function getL2OutputAfter(uint256 _l2BlockNumber) external view returns (Types.OutputProposal memory) { function getL2OutputAfter(uint256 _l2BlockNumber)
external
view
returns (Types.OutputProposal memory)
{
return l2Outputs[getL2OutputIndexAfter(_l2BlockNumber)]; return l2Outputs[getL2OutputIndexAfter(_l2BlockNumber)];
} }
...@@ -286,7 +270,10 @@ contract L2OutputOracle is Initializable, ISemver { ...@@ -286,7 +270,10 @@ contract L2OutputOracle is Initializable, ISemver {
/// block number. /// block number.
/// @return Latest submitted L2 block number. /// @return Latest submitted L2 block number.
function latestBlockNumber() public view returns (uint256) { function latestBlockNumber() public view returns (uint256) {
return l2Outputs.length == 0 ? startingBlockNumber : l2Outputs[l2Outputs.length - 1].l2BlockNumber; 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. /// @notice Computes the block number of the next L2 block that needs to be checkpointed.
......
...@@ -19,7 +19,7 @@ import { Constants } from "src/libraries/Constants.sol"; ...@@ -19,7 +19,7 @@ import { Constants } from "src/libraries/Constants.sol";
/// @notice The OptimismPortal is a low-level contract responsible for passing messages between L1 /// @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. /// 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. /// Users are encouraged to use the L1CrossDomainMessenger for a higher-level interface.
contract OptimismPortal is Initializable, ResourceMetering, ISemver { contract OptimismPortal is Initializable, ResourceMetering, Semver {
/// @notice Represents a proven withdrawal. /// @notice Represents a proven withdrawal.
/// @custom:field outputRoot Root of the L2 output this was proven against. /// @custom:field outputRoot Root of the L2 output this was proven against.
/// @custom:field timestamp Timestamp at whcih the withdrawal was proven. /// @custom:field timestamp Timestamp at whcih the withdrawal was proven.
...@@ -36,6 +36,15 @@ contract OptimismPortal is Initializable, ResourceMetering, ISemver { ...@@ -36,6 +36,15 @@ contract OptimismPortal is Initializable, ResourceMetering, ISemver {
/// @notice The L2 gas limit set when eth is deposited using the receive() function. /// @notice The L2 gas limit set when eth is deposited using the receive() function.
uint64 internal constant RECEIVE_DEFAULT_GAS_LIMIT = 100_000; uint64 internal constant RECEIVE_DEFAULT_GAS_LIMIT = 100_000;
/// @notice Address of the L2OutputOracle contract.
L2OutputOracle public immutable L2_ORACLE;
/// @notice Address of the SystemConfig contract.
SystemConfig public immutable SYSTEM_CONFIG;
/// @notice Address that has the ability to pause and unpause withdrawals.
address public immutable GUARDIAN;
/// @notice Address of the L2 account which initiated a withdrawal in this transaction. /// @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 /// If the of this variable is the default L2 sender address, then we are NOT inside of
/// a call to finalizeWithdrawalTransaction. /// a call to finalizeWithdrawalTransaction.
...@@ -52,18 +61,6 @@ contract OptimismPortal is Initializable, ResourceMetering, ISemver { ...@@ -52,18 +61,6 @@ contract OptimismPortal is Initializable, ResourceMetering, ISemver {
/// This may be removed in the future. /// This may be removed in the future.
bool public paused; bool public paused;
/// @notice Address of the L2OutputOracle contract.
/// @custom:network-specific
L2OutputOracle public l2Oracle;
/// @notice Address of the SystemConfig contract.
/// @custom:network-specific
SystemConfig public systemConfig;
/// @notice Address that has the ability to pause and unpause withdrawals.
/// @custom:network-specific
address public guardian;
/// @notice Emitted when a transaction is deposited from L1 to L2. /// @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 /// The parameters of this event are read by the rollup node and used to derive deposit
/// transactions on L2. /// transactions on L2.
...@@ -71,13 +68,22 @@ contract OptimismPortal is Initializable, ResourceMetering, ISemver { ...@@ -71,13 +68,22 @@ contract OptimismPortal is Initializable, ResourceMetering, ISemver {
/// @param to Address that the deposit transaction is directed to. /// @param to Address that the deposit transaction is directed to.
/// @param version Version of this deposit transaction event. /// @param version Version of this deposit transaction event.
/// @param opaqueData ABI encoded deposit data to be parsed off-chain. /// @param opaqueData ABI encoded deposit data to be parsed off-chain.
event TransactionDeposited(address indexed from, address indexed to, uint256 indexed version, bytes opaqueData); event TransactionDeposited(
address indexed from,
address indexed to,
uint256 indexed version,
bytes opaqueData
);
/// @notice Emitted when a withdrawal transaction is proven. /// @notice Emitted when a withdrawal transaction is proven.
/// @param withdrawalHash Hash of the withdrawal transaction. /// @param withdrawalHash Hash of the withdrawal transaction.
/// @param from Address that triggered the withdrawal transaction. /// @param from Address that triggered the withdrawal transaction.
/// @param to Address that the withdrawal transaction is directed to. /// @param to Address that the withdrawal transaction is directed to.
event WithdrawalProven(bytes32 indexed withdrawalHash, address indexed from, address indexed to); event WithdrawalProven(
bytes32 indexed withdrawalHash,
address indexed from,
address indexed to
);
/// @notice Emitted when a withdrawal transaction is finalized. /// @notice Emitted when a withdrawal transaction is finalized.
/// @param withdrawalHash Hash of the withdrawal transaction. /// @param withdrawalHash Hash of the withdrawal transaction.
...@@ -98,70 +104,41 @@ contract OptimismPortal is Initializable, ResourceMetering, ISemver { ...@@ -98,70 +104,41 @@ contract OptimismPortal is Initializable, ResourceMetering, ISemver {
_; _;
} }
/// @notice Semantic version. /// @custom:semver 1.7.2
/// @custom:semver 1.10.0
string public constant version = "1.10.0";
/// @notice Constructs the OptimismPortal contract. /// @notice Constructs the OptimismPortal contract.
constructor() {
initialize({
_l2Oracle: L2OutputOracle(address(0)),
_guardian: address(0),
_systemConfig: SystemConfig(address(0)),
_paused: true
});
}
/// @notice Initializer.
/// @param _l2Oracle Address of the L2OutputOracle contract. /// @param _l2Oracle Address of the L2OutputOracle contract.
/// @param _guardian Address that can pause withdrawals. /// @param _guardian Address that can pause withdrawals.
/// @param _paused Sets the contract's pausability state. /// @param _paused Sets the contract's pausability state.
/// @param _systemConfig Address of the SystemConfig contract. /// @param _config Address of the SystemConfig contract.
function initialize( constructor(
L2OutputOracle _l2Oracle, L2OutputOracle _l2Oracle,
address _guardian, address _guardian,
SystemConfig _systemConfig, bool _paused,
bool _paused SystemConfig _config
) ) Semver(1, 7, 2) {
public L2_ORACLE = _l2Oracle;
reinitializer(Constants.INITIALIZER) GUARDIAN = _guardian;
{ SYSTEM_CONFIG = _config;
initialize(_paused);
}
/// @notice Initializer.
function initialize(bool _paused) public initializer {
l2Sender = Constants.DEFAULT_L2_SENDER; l2Sender = Constants.DEFAULT_L2_SENDER;
l2Oracle = _l2Oracle;
systemConfig = _systemConfig;
guardian = _guardian;
paused = _paused; paused = _paused;
__ResourceMetering_init(); __ResourceMetering_init();
} }
/// @notice Getter for the L2OutputOracle
/// @custom:legacy
function L2_ORACLE() external view returns (L2OutputOracle) {
return l2Oracle;
}
/// @notice Getter for the SystemConfig
/// @custom:legacy
function SYSTEM_CONFIG() external view returns (SystemConfig) {
return systemConfig;
}
/// @notice Getter for the Guardian
/// @custom:legacy
function GUARDIAN() external view returns (address) {
return guardian;
}
/// @notice Pauses withdrawals. /// @notice Pauses withdrawals.
function pause() external { function pause() external {
require(msg.sender == guardian, "OptimismPortal: only guardian can pause"); require(msg.sender == GUARDIAN, "OptimismPortal: only guardian can pause");
paused = true; paused = true;
emit Paused(msg.sender); emit Paused(msg.sender);
} }
/// @notice Unpauses withdrawals. /// @notice Unpauses withdrawals.
function unpause() external { function unpause() external {
require(msg.sender == guardian, "OptimismPortal: only guardian can unpause"); require(msg.sender == GUARDIAN, "OptimismPortal: only guardian can unpause");
paused = false; paused = false;
emit Unpaused(msg.sender); emit Unpaused(msg.sender);
} }
...@@ -197,8 +174,13 @@ contract OptimismPortal is Initializable, ResourceMetering, ISemver { ...@@ -197,8 +174,13 @@ contract OptimismPortal is Initializable, ResourceMetering, ISemver {
/// Used internally by the ResourceMetering contract. /// Used internally by the ResourceMetering contract.
/// The SystemConfig is the source of truth for the resource config. /// The SystemConfig is the source of truth for the resource config.
/// @return ResourceMetering ResourceConfig /// @return ResourceMetering ResourceConfig
function _resourceConfig() internal view override returns (ResourceMetering.ResourceConfig memory) { function _resourceConfig()
return systemConfig.resourceConfig(); internal
view
override
returns (ResourceMetering.ResourceConfig memory)
{
return SYSTEM_CONFIG.resourceConfig();
} }
/// @notice Proves a withdrawal transaction. /// @notice Proves a withdrawal transaction.
...@@ -211,22 +193,23 @@ contract OptimismPortal is Initializable, ResourceMetering, ISemver { ...@@ -211,22 +193,23 @@ contract OptimismPortal is Initializable, ResourceMetering, ISemver {
uint256 _l2OutputIndex, uint256 _l2OutputIndex,
Types.OutputRootProof calldata _outputRootProof, Types.OutputRootProof calldata _outputRootProof,
bytes[] calldata _withdrawalProof bytes[] calldata _withdrawalProof
) ) external whenNotPaused {
external
whenNotPaused
{
// Prevent users from creating a deposit transaction where this address is the message // 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 // sender on L2. Because this is checked here, we do not need to check again in
// `finalizeWithdrawalTransaction`. // `finalizeWithdrawalTransaction`.
require(_tx.target != address(this), "OptimismPortal: you cannot send messages to the portal contract"); require(
_tx.target != address(this),
"OptimismPortal: you cannot send messages to the portal contract"
);
// Get the output root and load onto the stack to prevent multiple mloads. This will // 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. // revert if there is no output root for the given block number.
bytes32 outputRoot = l2Oracle.getL2Output(_l2OutputIndex).outputRoot; bytes32 outputRoot = L2_ORACLE.getL2Output(_l2OutputIndex).outputRoot;
// Verify that the output root can be generated with the elements in the proof. // Verify that the output root can be generated with the elements in the proof.
require( require(
outputRoot == Hashing.hashOutputRootProof(_outputRootProof), "OptimismPortal: invalid output root proof" outputRoot == Hashing.hashOutputRootProof(_outputRootProof),
"OptimismPortal: invalid output root proof"
); );
// Load the ProvenWithdrawal into memory, using the withdrawal hash as a unique identifier. // Load the ProvenWithdrawal into memory, using the withdrawal hash as a unique identifier.
...@@ -240,8 +223,9 @@ contract OptimismPortal is Initializable, ResourceMetering, ISemver { ...@@ -240,8 +223,9 @@ contract OptimismPortal is Initializable, ResourceMetering, ISemver {
// to re-prove their withdrawal only in the case that the output root for their specified // to re-prove their withdrawal only in the case that the output root for their specified
// output index has been updated. // output index has been updated.
require( require(
provenWithdrawal.timestamp == 0 provenWithdrawal.timestamp == 0 ||
|| l2Oracle.getL2Output(provenWithdrawal.l2OutputIndex).outputRoot != provenWithdrawal.outputRoot, L2_ORACLE.getL2Output(provenWithdrawal.l2OutputIndex).outputRoot !=
provenWithdrawal.outputRoot,
"OptimismPortal: withdrawal hash has already been proven" "OptimismPortal: withdrawal hash has already been proven"
); );
...@@ -261,7 +245,10 @@ contract OptimismPortal is Initializable, ResourceMetering, ISemver { ...@@ -261,7 +245,10 @@ contract OptimismPortal is Initializable, ResourceMetering, ISemver {
// be relayed on L1. // be relayed on L1.
require( require(
SecureMerkleTrie.verifyInclusionProof( SecureMerkleTrie.verifyInclusionProof(
abi.encode(storageKey), hex"01", _withdrawalProof, _outputRootProof.messagePasserStorageRoot abi.encode(storageKey),
hex"01",
_withdrawalProof,
_outputRootProof.messagePasserStorageRoot
), ),
"OptimismPortal: invalid withdrawal inclusion proof" "OptimismPortal: invalid withdrawal inclusion proof"
); );
...@@ -281,12 +268,16 @@ contract OptimismPortal is Initializable, ResourceMetering, ISemver { ...@@ -281,12 +268,16 @@ contract OptimismPortal is Initializable, ResourceMetering, ISemver {
/// @notice Finalizes a withdrawal transaction. /// @notice Finalizes a withdrawal transaction.
/// @param _tx Withdrawal transaction to finalize. /// @param _tx Withdrawal transaction to finalize.
function finalizeWithdrawalTransaction(Types.WithdrawalTransaction memory _tx) external whenNotPaused { 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 // 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 // than the default value when a withdrawal transaction is being finalized. This check is
// a defacto reentrancy guard. // a defacto reentrancy guard.
require( require(
l2Sender == Constants.DEFAULT_L2_SENDER, "OptimismPortal: can only trigger one withdrawal per transaction" l2Sender == Constants.DEFAULT_L2_SENDER,
"OptimismPortal: can only trigger one withdrawal per transaction"
); );
// Grab the proven withdrawal from the `provenWithdrawals` map. // Grab the proven withdrawal from the `provenWithdrawals` map.
...@@ -296,13 +287,16 @@ contract OptimismPortal is Initializable, ResourceMetering, ISemver { ...@@ -296,13 +287,16 @@ contract OptimismPortal is Initializable, ResourceMetering, ISemver {
// A withdrawal can only be finalized if it has been proven. We know that a withdrawal has // 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 // been proven at least once when its timestamp is non-zero. Unproven withdrawals will have
// a timestamp of zero. // a timestamp of zero.
require(provenWithdrawal.timestamp != 0, "OptimismPortal: withdrawal has not been proven yet"); 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 // 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 // starting timestamp inside the L2OutputOracle. Not strictly necessary but extra layer of
// safety against weird bugs in the proving step. // safety against weird bugs in the proving step.
require( require(
provenWithdrawal.timestamp >= l2Oracle.startingTimestamp(), provenWithdrawal.timestamp >= L2_ORACLE.startingTimestamp(),
"OptimismPortal: withdrawal timestamp less than L2 Oracle starting timestamp" "OptimismPortal: withdrawal timestamp less than L2 Oracle starting timestamp"
); );
...@@ -317,7 +311,9 @@ contract OptimismPortal is Initializable, ResourceMetering, ISemver { ...@@ -317,7 +311,9 @@ contract OptimismPortal is Initializable, ResourceMetering, ISemver {
// Grab the OutputProposal from the L2OutputOracle, will revert if the output that // Grab the OutputProposal from the L2OutputOracle, will revert if the output that
// corresponds to the given index has not been proposed yet. // corresponds to the given index has not been proposed yet.
Types.OutputProposal memory proposal = l2Oracle.getL2Output(provenWithdrawal.l2OutputIndex); Types.OutputProposal memory proposal = L2_ORACLE.getL2Output(
provenWithdrawal.l2OutputIndex
);
// Check that the output root that was used to prove the withdrawal is the same as the // 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 // current output root for the given output index. An output root may change if it is
...@@ -334,7 +330,10 @@ contract OptimismPortal is Initializable, ResourceMetering, ISemver { ...@@ -334,7 +330,10 @@ contract OptimismPortal is Initializable, ResourceMetering, ISemver {
); );
// Check that this withdrawal has not already been finalized, this is replay protection. // Check that this withdrawal has not already been finalized, this is replay protection.
require(finalizedWithdrawals[withdrawalHash] == false, "OptimismPortal: withdrawal has already been finalized"); require(
finalizedWithdrawals[withdrawalHash] == false,
"OptimismPortal: withdrawal has already been finalized"
);
// Mark the withdrawal as finalized so it can't be replayed. // Mark the withdrawal as finalized so it can't be replayed.
finalizedWithdrawals[withdrawalHash] = true; finalizedWithdrawals[withdrawalHash] = true;
...@@ -381,20 +380,22 @@ contract OptimismPortal is Initializable, ResourceMetering, ISemver { ...@@ -381,20 +380,22 @@ contract OptimismPortal is Initializable, ResourceMetering, ISemver {
uint64 _gasLimit, uint64 _gasLimit,
bool _isCreation, bool _isCreation,
bytes memory _data bytes memory _data
) ) public payable metered(_gasLimit) {
public
payable
metered(_gasLimit)
{
// Just to be safe, make sure that people specify address(0) as the target when doing // Just to be safe, make sure that people specify address(0) as the target when doing
// contract creations. // contract creations.
if (_isCreation) { if (_isCreation) {
require(_to == address(0), "OptimismPortal: must send to address(0) when creating a contract"); require(
_to == address(0),
"OptimismPortal: must send to address(0) when creating a contract"
);
} }
// Prevent depositing transactions that have too small of a gas limit. Users should pay // Prevent depositing transactions that have too small of a gas limit. Users should pay
// more for more resource usage. // more for more resource usage.
require(_gasLimit >= minimumGasLimit(uint64(_data.length)), "OptimismPortal: gas limit too small"); require(
_gasLimit >= minimumGasLimit(uint64(_data.length)),
"OptimismPortal: gas limit too small"
);
// Prevent the creation of deposit transactions that have too much calldata. This gives an // 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 // upper limit on the size of unsafe blocks over the p2p network. 120kb is chosen to ensure
...@@ -411,7 +412,13 @@ contract OptimismPortal is Initializable, ResourceMetering, ISemver { ...@@ -411,7 +412,13 @@ contract OptimismPortal is Initializable, ResourceMetering, ISemver {
// Compute the opaque data that will be emitted as part of the TransactionDeposited event. // 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 // We use opaque data so that we can update the TransactionDeposited event in the future
// without breaking the current interface. // without breaking the current interface.
bytes memory opaqueData = abi.encodePacked(msg.value, _value, _gasLimit, _isCreation, _data); bytes memory opaqueData = abi.encodePacked(
msg.value,
_value,
_gasLimit,
_isCreation,
_data
);
// Emit a TransactionDeposited event so that the rollup node can derive a deposit // Emit a TransactionDeposited event so that the rollup node can derive a deposit
// transaction for this deposit. // transaction for this deposit.
...@@ -424,7 +431,7 @@ contract OptimismPortal is Initializable, ResourceMetering, ISemver { ...@@ -424,7 +431,7 @@ contract OptimismPortal is Initializable, ResourceMetering, ISemver {
/// @param _l2OutputIndex Index of the L2 output to check. /// @param _l2OutputIndex Index of the L2 output to check.
/// @return Whether or not the output is finalized. /// @return Whether or not the output is finalized.
function isOutputFinalized(uint256 _l2OutputIndex) external view returns (bool) { function isOutputFinalized(uint256 _l2OutputIndex) external view returns (bool) {
return _isFinalizationPeriodElapsed(l2Oracle.getL2Output(_l2OutputIndex).timestamp); return _isFinalizationPeriodElapsed(L2_ORACLE.getL2Output(_l2OutputIndex).timestamp);
} }
/// @notice Determines whether the finalization period has elapsed with respect to /// @notice Determines whether the finalization period has elapsed with respect to
...@@ -432,6 +439,6 @@ contract OptimismPortal is Initializable, ResourceMetering, ISemver { ...@@ -432,6 +439,6 @@ contract OptimismPortal is Initializable, ResourceMetering, ISemver {
/// @param _timestamp Timestamp to check. /// @param _timestamp Timestamp to check.
/// @return Whether or not the finalization period has elapsed. /// @return Whether or not the finalization period has elapsed.
function _isFinalizationPeriodElapsed(uint256 _timestamp) internal view returns (bool) { function _isFinalizationPeriodElapsed(uint256 _timestamp) internal view returns (bool) {
return block.timestamp > _timestamp + l2Oracle.FINALIZATION_PERIOD_SECONDS(); return block.timestamp > _timestamp + L2_ORACLE.FINALIZATION_PERIOD_SECONDS();
} }
} }
...@@ -76,16 +76,16 @@ abstract contract ResourceMetering is Initializable { ...@@ -76,16 +76,16 @@ abstract contract ResourceMetering is Initializable {
uint256 blockDiff = block.number - params.prevBlockNum; uint256 blockDiff = block.number - params.prevBlockNum;
ResourceConfig memory config = _resourceConfig(); ResourceConfig memory config = _resourceConfig();
int256 targetResourceLimit = int256 targetResourceLimit = int256(uint256(config.maxResourceLimit)) /
int256(uint256(config.maxResourceLimit)) / int256(uint256(config.elasticityMultiplier)); int256(uint256(config.elasticityMultiplier));
if (blockDiff > 0) { if (blockDiff > 0) {
// Handle updating EIP-1559 style gas parameters. We use EIP-1559 to restrict the rate // Handle updating EIP-1559 style gas parameters. We use EIP-1559 to restrict the rate
// at which deposits can be created and therefore limit the potential for deposits to // at which deposits can be created and therefore limit the potential for deposits to
// spam the L2 system. Fee scheme is very similar to EIP-1559 with minor changes. // spam the L2 system. Fee scheme is very similar to EIP-1559 with minor changes.
int256 gasUsedDelta = int256(uint256(params.prevBoughtGas)) - targetResourceLimit; int256 gasUsedDelta = int256(uint256(params.prevBoughtGas)) - targetResourceLimit;
int256 baseFeeDelta = (int256(uint256(params.prevBaseFee)) * gasUsedDelta) int256 baseFeeDelta = (int256(uint256(params.prevBaseFee)) * gasUsedDelta) /
/ (targetResourceLimit * int256(uint256(config.baseFeeMaxChangeDenominator))); (targetResourceLimit * int256(uint256(config.baseFeeMaxChangeDenominator)));
// Update base fee by adding the base fee delta and clamp the resulting value between // Update base fee by adding the base fee delta and clamp the resulting value between
// min and max. // min and max.
...@@ -155,6 +155,10 @@ abstract contract ResourceMetering is Initializable { ...@@ -155,6 +155,10 @@ abstract contract ResourceMetering is Initializable {
/// child contract. /// child contract.
// solhint-disable-next-line func-name-mixedcase // solhint-disable-next-line func-name-mixedcase
function __ResourceMetering_init() internal onlyInitializing { function __ResourceMetering_init() internal onlyInitializing {
params = ResourceParams({ prevBaseFee: 1 gwei, prevBoughtGas: 0, prevBlockNum: uint64(block.number) }); params = ResourceParams({
prevBaseFee: 1 gwei,
prevBoughtGas: 0,
prevBlockNum: uint64(block.number)
});
} }
} }
...@@ -11,7 +11,7 @@ import { Constants } from "src/libraries/Constants.sol"; ...@@ -11,7 +11,7 @@ import { Constants } from "src/libraries/Constants.sol";
/// @notice The SystemConfig contract is used to manage configuration of an Optimism network. /// @notice The SystemConfig contract is used to manage configuration of an Optimism network.
/// All configuration is stored on L1 and picked up by L2 as part of the derviation of /// All configuration is stored on L1 and picked up by L2 as part of the derviation of
/// the L2 chain. /// the L2 chain.
contract SystemConfig is OwnableUpgradeable, ISemver { contract SystemConfig is OwnableUpgradeable, Semver {
/// @notice Enum representing different types of updates. /// @notice Enum representing different types of updates.
/// @custom:value BATCHER Represents an update to the batcher hash. /// @custom:value BATCHER Represents an update to the batcher hash.
/// @custom:value GAS_CONFIG Represents an update to txn fee config on L2. /// @custom:value GAS_CONFIG Represents an update to txn fee config on L2.
...@@ -25,17 +25,6 @@ contract SystemConfig is OwnableUpgradeable, ISemver { ...@@ -25,17 +25,6 @@ contract SystemConfig is OwnableUpgradeable, ISemver {
UNSAFE_BLOCK_SIGNER UNSAFE_BLOCK_SIGNER
} }
/// @notice Struct representing the addresses of L1 system contracts. These should be the
/// proxies and will differ for each OP Stack chain.
struct Addresses {
address l1CrossDomainMessenger;
address l1ERC721Bridge;
address l1StandardBridge;
address l2OutputOracle;
address optimismPortal;
address optimismMintableERC20Factory;
}
/// @notice Version identifier, used for upgrades. /// @notice Version identifier, used for upgrades.
uint256 public constant VERSION = 0; uint256 public constant VERSION = 0;
...@@ -43,35 +32,8 @@ contract SystemConfig is OwnableUpgradeable, ISemver { ...@@ -43,35 +32,8 @@ contract SystemConfig is OwnableUpgradeable, ISemver {
/// Storing it at this deterministic storage slot allows for decoupling the storage /// Storing it at this deterministic storage slot allows for decoupling the storage
/// layout from the way that `solc` lays out storage. The `op-node` uses a storage /// layout from the way that `solc` lays out storage. The `op-node` uses a storage
/// proof to fetch this value. /// proof to fetch this value.
/// @dev NOTE: this value will be migrated to another storage slot in a future version.
/// User input should not be placed in storage in this contract until this migration
/// happens. It is unlikely that keccak second preimage resistance will be broken,
/// but it is better to be safe than sorry.
bytes32 public constant UNSAFE_BLOCK_SIGNER_SLOT = keccak256("systemconfig.unsafeblocksigner"); bytes32 public constant UNSAFE_BLOCK_SIGNER_SLOT = keccak256("systemconfig.unsafeblocksigner");
/// @notice Storage slot that the L1CrossDomainMessenger address is stored at.
bytes32 public constant L1_CROSS_DOMAIN_MESSENGER_SLOT =
bytes32(uint256(keccak256("systemconfig.l1crossdomainmessenger")) - 1);
/// @notice Storage slot that the L1ERC721Bridge address is stored at.
bytes32 public constant L1_ERC_721_BRIDGE_SLOT = bytes32(uint256(keccak256("systemconfig.l1erc721bridge")) - 1);
/// @notice Storage slot that the L1StandardBridge address is stored at.
bytes32 public constant L1_STANDARD_BRIDGE_SLOT = bytes32(uint256(keccak256("systemconfig.l1standardbridge")) - 1);
/// @notice Storage slot that the L2OutputOracle address is stored at.
bytes32 public constant L2_OUTPUT_ORACLE_SLOT = bytes32(uint256(keccak256("systemconfig.l2outputoracle")) - 1);
/// @notice Storage slot that the OptimismPortal address is stored at.
bytes32 public constant OPTIMISM_PORTAL_SLOT = bytes32(uint256(keccak256("systemconfig.optimismportal")) - 1);
/// @notice Storage slot that the OptimismMintableERC20Factory address is stored at.
bytes32 public constant OPTIMISM_MINTABLE_ERC20_FACTORY_SLOT =
bytes32(uint256(keccak256("systemconfig.optimismmintableerc20factory")) - 1);
/// @notice Storage slot that the batch inbox address is stored at.
bytes32 public constant BATCH_INBOX_SLOT = bytes32(uint256(keccak256("systemconfig.batchinbox")) - 1);
/// @notice Fixed L2 gas overhead. Used as part of the L2 fee calculation. /// @notice Fixed L2 gas overhead. Used as part of the L2 fee calculation.
uint256 public overhead; uint256 public overhead;
...@@ -97,42 +59,32 @@ contract SystemConfig is OwnableUpgradeable, ISemver { ...@@ -97,42 +59,32 @@ contract SystemConfig is OwnableUpgradeable, ISemver {
/// @param data Encoded update data. /// @param data Encoded update data.
event ConfigUpdate(uint256 indexed version, UpdateType indexed updateType, bytes data); event ConfigUpdate(uint256 indexed version, UpdateType indexed updateType, bytes data);
/// @notice The block at which the op-node can start searching for logs from. /// @custom:semver 1.3.1
uint256 public startBlock; /// @notice Constructs the SystemConfig contract.
/// @param _owner Initial owner of the contract.
/// @notice Semantic version. /// @param _overhead Initial overhead value.
/// @custom:semver 1.10.0 /// @param _scalar Initial scalar value.
string public constant version = "1.10.0"; /// @param _batcherHash Initial batcher hash.
/// @param _gasLimit Initial gas limit.
/// @notice Constructs the SystemConfig contract. Cannot set /// @param _unsafeBlockSigner Initial unsafe block signer address.
/// the owner to `address(0)` due to the Ownable contract's /// @param _config Initial resource config.
/// implementation, so set it to `address(0xdEaD)` constructor(
constructor() { address _owner,
uint256 _overhead,
uint256 _scalar,
bytes32 _batcherHash,
uint64 _gasLimit,
address _unsafeBlockSigner,
ResourceMetering.ResourceConfig memory _config
) Semver(1, 3, 1) {
initialize({ initialize({
_owner: address(0xdEaD), _owner: _owner,
_overhead: 0, _overhead: _overhead,
_scalar: 0, _scalar: _scalar,
_batcherHash: bytes32(0), _batcherHash: _batcherHash,
_gasLimit: 1, _gasLimit: _gasLimit,
_unsafeBlockSigner: address(0), _unsafeBlockSigner: _unsafeBlockSigner,
_config: ResourceMetering.ResourceConfig({ _config: _config
maxResourceLimit: 1,
elasticityMultiplier: 1,
baseFeeMaxChangeDenominator: 2,
minimumBaseFee: 0,
systemTxMaxGas: 0,
maximumBaseFee: 0
}),
_startBlock: type(uint256).max,
_batchInbox: address(0),
_addresses: SystemConfig.Addresses({
l1CrossDomainMessenger: address(0),
l1ERC721Bridge: address(0),
l1StandardBridge: address(0),
l2OutputOracle: address(0),
optimismPortal: address(0),
optimismMintableERC20Factory: address(0)
})
}); });
} }
...@@ -145,13 +97,6 @@ contract SystemConfig is OwnableUpgradeable, ISemver { ...@@ -145,13 +97,6 @@ contract SystemConfig is OwnableUpgradeable, ISemver {
/// @param _gasLimit Initial gas limit. /// @param _gasLimit Initial gas limit.
/// @param _unsafeBlockSigner Initial unsafe block signer address. /// @param _unsafeBlockSigner Initial unsafe block signer address.
/// @param _config Initial ResourceConfig. /// @param _config Initial ResourceConfig.
/// @param _startBlock Starting block for the op-node to search for logs from.
/// Contracts that were deployed before this field existed
/// need to have this field set manually via an override.
/// Newly deployed contracts should set this value to uint256(0).
/// @param _batchInbox Batch inbox address. An identifier for the op-node to find
/// canonical data.
/// @param _addresses Set of L1 contract addresses. These should be the proxies.
function initialize( function initialize(
address _owner, address _owner,
uint256 _overhead, uint256 _overhead,
...@@ -159,33 +104,15 @@ contract SystemConfig is OwnableUpgradeable, ISemver { ...@@ -159,33 +104,15 @@ contract SystemConfig is OwnableUpgradeable, ISemver {
bytes32 _batcherHash, bytes32 _batcherHash,
uint64 _gasLimit, uint64 _gasLimit,
address _unsafeBlockSigner, address _unsafeBlockSigner,
ResourceMetering.ResourceConfig memory _config, ResourceMetering.ResourceConfig memory _config
uint256 _startBlock, ) public initializer {
address _batchInbox,
SystemConfig.Addresses memory _addresses
)
public
reinitializer(Constants.INITIALIZER)
{
__Ownable_init(); __Ownable_init();
transferOwnership(_owner); transferOwnership(_owner);
overhead = _overhead;
// These are set in ascending order of their UpdateTypes. scalar = _scalar;
_setBatcherHash(_batcherHash); batcherHash = _batcherHash;
_setGasConfig({ _overhead: _overhead, _scalar: _scalar }); gasLimit = _gasLimit;
_setGasLimit(_gasLimit);
_setUnsafeBlockSigner(_unsafeBlockSigner); _setUnsafeBlockSigner(_unsafeBlockSigner);
Storage.setAddress(BATCH_INBOX_SLOT, _batchInbox);
Storage.setAddress(L1_CROSS_DOMAIN_MESSENGER_SLOT, _addresses.l1CrossDomainMessenger);
Storage.setAddress(L1_ERC_721_BRIDGE_SLOT, _addresses.l1ERC721Bridge);
Storage.setAddress(L1_STANDARD_BRIDGE_SLOT, _addresses.l1StandardBridge);
Storage.setAddress(L2_OUTPUT_ORACLE_SLOT, _addresses.l2OutputOracle);
Storage.setAddress(OPTIMISM_PORTAL_SLOT, _addresses.optimismPortal);
Storage.setAddress(OPTIMISM_MINTABLE_ERC20_FACTORY_SLOT, _addresses.optimismMintableERC20Factory);
_setStartBlock(_startBlock);
_setResourceConfig(_config); _setResourceConfig(_config);
require(_gasLimit >= minimumGasLimit(), "SystemConfig: gas limit too low"); require(_gasLimit >= minimumGasLimit(), "SystemConfig: gas limit too low");
} }
...@@ -203,109 +130,39 @@ contract SystemConfig is OwnableUpgradeable, ISemver { ...@@ -203,109 +130,39 @@ contract SystemConfig is OwnableUpgradeable, ISemver {
/// @notice High level getter for the unsafe block signer address. /// @notice High level getter for the unsafe block signer address.
/// Unsafe blocks can be propagated across the p2p network if they are signed by the /// Unsafe blocks can be propagated across the p2p network if they are signed by the
/// key corresponding to this address. /// key corresponding to this address.
/// @return addr_ Address of the unsafe block signer. /// @return Address of the unsafe block signer.
// solhint-disable-next-line ordering // solhint-disable-next-line ordering
function unsafeBlockSigner() public view returns (address addr_) { function unsafeBlockSigner() external view returns (address) {
addr_ = Storage.getAddress(UNSAFE_BLOCK_SIGNER_SLOT); address addr;
} bytes32 slot = UNSAFE_BLOCK_SIGNER_SLOT;
assembly {
/// @notice Getter for the L1CrossDomainMessenger address. addr := sload(slot)
function l1CrossDomainMessenger() external view returns (address addr_) {
addr_ = Storage.getAddress(L1_CROSS_DOMAIN_MESSENGER_SLOT);
}
/// @notice Getter for the L1ERC721Bridge address.
function l1ERC721Bridge() external view returns (address addr_) {
addr_ = Storage.getAddress(L1_ERC_721_BRIDGE_SLOT);
}
/// @notice Getter for the L1StandardBridge address.
function l1StandardBridge() external view returns (address addr_) {
addr_ = Storage.getAddress(L1_STANDARD_BRIDGE_SLOT);
}
/// @notice Getter for the L2OutputOracle address.
function l2OutputOracle() external view returns (address addr_) {
addr_ = Storage.getAddress(L2_OUTPUT_ORACLE_SLOT);
}
/// @notice Getter for the OptimismPortal address.
function optimismPortal() external view returns (address addr_) {
addr_ = Storage.getAddress(OPTIMISM_PORTAL_SLOT);
}
/// @notice Getter for the OptimismMintableERC20Factory address.
function optimismMintableERC20Factory() external view returns (address addr_) {
addr_ = Storage.getAddress(OPTIMISM_MINTABLE_ERC20_FACTORY_SLOT);
}
/// @notice Getter for the BatchInbox address.
function batchInbox() external view returns (address addr_) {
addr_ = Storage.getAddress(BATCH_INBOX_SLOT);
}
/// @notice Sets the start block in a backwards compatible way. Proxies
/// that were initialized before the startBlock existed in storage
/// can have their start block set by a user provided override.
/// A start block of 0 indicates that there is no override and the
/// start block will be set by `block.number`.
/// @dev This logic is used to patch legacy deployments with new storage values.
/// Use the override if it is provided as a non zero value and the value
/// has not already been set in storage. Use `block.number` if the value
/// has already been set in storage
/// @param _startBlock The start block override to set in storage.
function _setStartBlock(uint256 _startBlock) internal {
if (_startBlock != 0 && startBlock == 0) {
// There is an override and it is not already set, this is for legacy chains.
startBlock = _startBlock;
} else if (startBlock == 0) {
// There is no override and it is not set in storage. Set it to the block number.
// This is for newly deployed chains.
startBlock = block.number;
} }
return addr;
} }
/// @notice Updates the unsafe block signer address. Can only be called by the owner. /// @notice Updates the unsafe block signer address.
/// @param _unsafeBlockSigner New unsafe block signer address. /// @param _unsafeBlockSigner New unsafe block signer address.
function setUnsafeBlockSigner(address _unsafeBlockSigner) external onlyOwner { function setUnsafeBlockSigner(address _unsafeBlockSigner) external onlyOwner {
_setUnsafeBlockSigner(_unsafeBlockSigner); _setUnsafeBlockSigner(_unsafeBlockSigner);
}
/// @notice Updates the unsafe block signer address.
/// @param _unsafeBlockSigner New unsafe block signer address.
function _setUnsafeBlockSigner(address _unsafeBlockSigner) internal {
Storage.setAddress(UNSAFE_BLOCK_SIGNER_SLOT, _unsafeBlockSigner);
bytes memory data = abi.encode(_unsafeBlockSigner); bytes memory data = abi.encode(_unsafeBlockSigner);
emit ConfigUpdate(VERSION, UpdateType.UNSAFE_BLOCK_SIGNER, data); emit ConfigUpdate(VERSION, UpdateType.UNSAFE_BLOCK_SIGNER, data);
} }
/// @notice Updates the batcher hash. Can only be called by the owner. /// @notice Updates the batcher hash.
/// @param _batcherHash New batcher hash. /// @param _batcherHash New batcher hash.
function setBatcherHash(bytes32 _batcherHash) external onlyOwner { function setBatcherHash(bytes32 _batcherHash) external onlyOwner {
_setBatcherHash(_batcherHash);
}
/// @notice Internal function for updating the batcher hash.
/// @param _batcherHash New batcher hash.
function _setBatcherHash(bytes32 _batcherHash) internal {
batcherHash = _batcherHash; batcherHash = _batcherHash;
bytes memory data = abi.encode(_batcherHash); bytes memory data = abi.encode(_batcherHash);
emit ConfigUpdate(VERSION, UpdateType.BATCHER, data); emit ConfigUpdate(VERSION, UpdateType.BATCHER, data);
} }
/// @notice Updates gas config. Can only be called by the owner. /// @notice Updates gas config.
/// @param _overhead New overhead value. /// @param _overhead New overhead value.
/// @param _scalar New scalar value. /// @param _scalar New scalar value.
function setGasConfig(uint256 _overhead, uint256 _scalar) external onlyOwner { function setGasConfig(uint256 _overhead, uint256 _scalar) external onlyOwner {
_setGasConfig(_overhead, _scalar);
}
/// @notice Internal function for updating the gas config.
/// @param _overhead New overhead value.
/// @param _scalar New scalar value.
function _setGasConfig(uint256 _overhead, uint256 _scalar) internal {
overhead = _overhead; overhead = _overhead;
scalar = _scalar; scalar = _scalar;
...@@ -313,15 +170,9 @@ contract SystemConfig is OwnableUpgradeable, ISemver { ...@@ -313,15 +170,9 @@ contract SystemConfig is OwnableUpgradeable, ISemver {
emit ConfigUpdate(VERSION, UpdateType.GAS_CONFIG, data); emit ConfigUpdate(VERSION, UpdateType.GAS_CONFIG, data);
} }
/// @notice Updates the L2 gas limit. Can only be called by the owner. /// @notice Updates the L2 gas limit.
/// @param _gasLimit New gas limit. /// @param _gasLimit New gas limit.
function setGasLimit(uint64 _gasLimit) external onlyOwner { function setGasLimit(uint64 _gasLimit) external onlyOwner {
_setGasLimit(_gasLimit);
}
/// @notice Internal function for updating the L2 gas limit.
/// @param _gasLimit New gas limit.
function _setGasLimit(uint64 _gasLimit) internal {
require(_gasLimit >= minimumGasLimit(), "SystemConfig: gas limit too low"); require(_gasLimit >= minimumGasLimit(), "SystemConfig: gas limit too low");
gasLimit = _gasLimit; gasLimit = _gasLimit;
...@@ -329,6 +180,17 @@ contract SystemConfig is OwnableUpgradeable, ISemver { ...@@ -329,6 +180,17 @@ contract SystemConfig is OwnableUpgradeable, ISemver {
emit ConfigUpdate(VERSION, UpdateType.GAS_LIMIT, data); emit ConfigUpdate(VERSION, UpdateType.GAS_LIMIT, data);
} }
/// @notice Low level setter for the unsafe block signer address.
/// This function exists to deduplicate code around storing the unsafeBlockSigner
/// address in storage.
/// @param _unsafeBlockSigner New unsafeBlockSigner value.
function _setUnsafeBlockSigner(address _unsafeBlockSigner) internal {
bytes32 slot = UNSAFE_BLOCK_SIGNER_SLOT;
assembly {
sstore(slot, _unsafeBlockSigner)
}
}
/// @notice A getter for the resource config. /// @notice A getter for the resource config.
/// Ensures that the struct is returned instead of a tuple. /// Ensures that the struct is returned instead of a tuple.
/// @return ResourceConfig /// @return ResourceConfig
...@@ -350,19 +212,29 @@ contract SystemConfig is OwnableUpgradeable, ISemver { ...@@ -350,19 +212,29 @@ contract SystemConfig is OwnableUpgradeable, ISemver {
function _setResourceConfig(ResourceMetering.ResourceConfig memory _config) internal { function _setResourceConfig(ResourceMetering.ResourceConfig memory _config) internal {
// Min base fee must be less than or equal to max base fee. // Min base fee must be less than or equal to max base fee.
require( require(
_config.minimumBaseFee <= _config.maximumBaseFee, "SystemConfig: min base fee must be less than max base" _config.minimumBaseFee <= _config.maximumBaseFee,
"SystemConfig: min base fee must be less than max base"
); );
// Base fee change denominator must be greater than 1. // Base fee change denominator must be greater than 1.
require(_config.baseFeeMaxChangeDenominator > 1, "SystemConfig: denominator must be larger than 1"); require(
_config.baseFeeMaxChangeDenominator > 1,
"SystemConfig: denominator must be larger than 1"
);
// Max resource limit plus system tx gas must be less than or equal to the L2 gas limit. // Max resource limit plus system tx gas must be less than or equal to the L2 gas limit.
// The gas limit must be increased before these values can be increased. // The gas limit must be increased before these values can be increased.
require(_config.maxResourceLimit + _config.systemTxMaxGas <= gasLimit, "SystemConfig: gas limit too low"); require(
_config.maxResourceLimit + _config.systemTxMaxGas <= gasLimit,
"SystemConfig: gas limit too low"
);
// Elasticity multiplier must be greater than 0. // Elasticity multiplier must be greater than 0.
require(_config.elasticityMultiplier > 0, "SystemConfig: elasticity multiplier cannot be 0"); require(
_config.elasticityMultiplier > 0,
"SystemConfig: elasticity multiplier cannot be 0"
);
// No precision loss when computing target resource limit. // No precision loss when computing target resource limit.
require( require(
((_config.maxResourceLimit / _config.elasticityMultiplier) * _config.elasticityMultiplier) ((_config.maxResourceLimit / _config.elasticityMultiplier) *
== _config.maxResourceLimit, _config.elasticityMultiplier) == _config.maxResourceLimit,
"SystemConfig: precision loss with target resource limit" "SystemConfig: precision loss with target resource limit"
); );
......
...@@ -19,20 +19,15 @@ import { Predeploys } from "src/libraries/Predeploys.sol"; ...@@ -19,20 +19,15 @@ import { Predeploys } from "src/libraries/Predeploys.sol";
/// bridge ONLY supports ERC721s originally deployed on Ethereum. Users will need to /// bridge ONLY supports ERC721s originally deployed on Ethereum. Users will need to
/// wait for the one-week challenge period to elapse before their Optimism-native NFT /// wait for the one-week challenge period to elapse before their Optimism-native NFT
/// can be refunded on L2. /// can be refunded on L2.
contract L2ERC721Bridge is ERC721Bridge, ISemver { contract L2ERC721Bridge is ERC721Bridge, Semver {
/// @custom:semver 1.4.0 /// @custom:semver 1.1.1
string public constant version = "1.4.0";
/// @notice Constructs the L2ERC721Bridge contract. /// @notice Constructs the L2ERC721Bridge contract.
/// @param _messenger Address of the CrossDomainMessenger on this network.
/// @param _otherBridge Address of the ERC721 bridge on the other network. /// @param _otherBridge Address of the ERC721 bridge on the other network.
constructor(address _otherBridge) ERC721Bridge(_otherBridge) { constructor(address _messenger, address _otherBridge)
initialize(); Semver(1, 1, 1)
} ERC721Bridge(_messenger, _otherBridge)
{}
/// @notice Initializes the contract.
function initialize() public reinitializer(Constants.INITIALIZER) {
__ERC721Bridge_init({ _messenger: CrossDomainMessenger(Predeploys.L2_CROSS_DOMAIN_MESSENGER) });
}
/// @notice Completes an ERC721 bridge from the other domain and sends the ERC721 token to the /// @notice Completes an ERC721 bridge from the other domain and sends the ERC721 token to the
/// recipient on this domain. /// recipient on this domain.
...@@ -51,10 +46,7 @@ contract L2ERC721Bridge is ERC721Bridge, ISemver { ...@@ -51,10 +46,7 @@ contract L2ERC721Bridge is ERC721Bridge, ISemver {
address _to, address _to,
uint256 _tokenId, uint256 _tokenId,
bytes calldata _extraData bytes calldata _extraData
) ) external onlyOtherBridge {
external
onlyOtherBridge
{
require(_localToken != address(this), "L2ERC721Bridge: local token cannot be self"); require(_localToken != address(this), "L2ERC721Bridge: local token cannot be self");
// Note that supportsInterface makes a callback to the _localToken address which is user // Note that supportsInterface makes a callback to the _localToken address which is user
...@@ -86,10 +78,7 @@ contract L2ERC721Bridge is ERC721Bridge, ISemver { ...@@ -86,10 +78,7 @@ contract L2ERC721Bridge is ERC721Bridge, ISemver {
uint256 _tokenId, uint256 _tokenId,
uint32 _minGasLimit, uint32 _minGasLimit,
bytes calldata _extraData bytes calldata _extraData
) ) internal override {
internal
override
{
require(_remoteToken != address(0), "L2ERC721Bridge: remote token cannot be address(0)"); require(_remoteToken != address(0), "L2ERC721Bridge: remote token cannot be address(0)");
// Check that the withdrawal is being initiated by the NFT owner // Check that the withdrawal is being initiated by the NFT owner
...@@ -101,7 +90,10 @@ contract L2ERC721Bridge is ERC721Bridge, ISemver { ...@@ -101,7 +90,10 @@ contract L2ERC721Bridge is ERC721Bridge, ISemver {
// Construct calldata for l1ERC721Bridge.finalizeBridgeERC721(_to, _tokenId) // Construct calldata for l1ERC721Bridge.finalizeBridgeERC721(_to, _tokenId)
// slither-disable-next-line reentrancy-events // slither-disable-next-line reentrancy-events
address remoteToken = IOptimismMintableERC721(_localToken).remoteToken(); address remoteToken = IOptimismMintableERC721(_localToken).remoteToken();
require(remoteToken == _remoteToken, "L2ERC721Bridge: remote token does not match given value"); require(
remoteToken == _remoteToken,
"L2ERC721Bridge: remote token does not match given value"
);
// When a withdrawal is initiated, we burn the withdrawer's NFT to prevent subsequent L2 // When a withdrawal is initiated, we burn the withdrawer's NFT to prevent subsequent L2
// usage // usage
...@@ -109,12 +101,18 @@ contract L2ERC721Bridge is ERC721Bridge, ISemver { ...@@ -109,12 +101,18 @@ contract L2ERC721Bridge is ERC721Bridge, ISemver {
IOptimismMintableERC721(_localToken).burn(_from, _tokenId); IOptimismMintableERC721(_localToken).burn(_from, _tokenId);
bytes memory message = abi.encodeWithSelector( bytes memory message = abi.encodeWithSelector(
L1ERC721Bridge.finalizeBridgeERC721.selector, remoteToken, _localToken, _from, _to, _tokenId, _extraData L1ERC721Bridge.finalizeBridgeERC721.selector,
remoteToken,
_localToken,
_from,
_to,
_tokenId,
_extraData
); );
// Send message to L1 bridge // Send message to L1 bridge
// slither-disable-next-line reentrancy-events // slither-disable-next-line reentrancy-events
messenger.sendMessage(OTHER_BRIDGE, message, _minGasLimit); MESSENGER.sendMessage(OTHER_BRIDGE, message, _minGasLimit);
// slither-disable-next-line reentrancy-events // slither-disable-next-line reentrancy-events
emit ERC721BridgeInitiated(_localToken, remoteToken, _from, _to, _tokenId, _extraData); emit ERC721BridgeInitiated(_localToken, remoteToken, _from, _to, _tokenId, _extraData);
......
...@@ -17,7 +17,7 @@ import { Constants } from "src/libraries/Constants.sol"; ...@@ -17,7 +17,7 @@ import { Constants } from "src/libraries/Constants.sol";
/// NOTE: this contract is not intended to support all variations of ERC20 tokens. Examples /// NOTE: this contract is not intended to support all variations of ERC20 tokens. Examples
/// of some token types that may not be properly supported by this contract include, but are /// of some token types that may not be properly supported by this contract include, but are
/// not limited to: tokens with transfer fees, rebasing tokens, and tokens with blocklists. /// not limited to: tokens with transfer fees, rebasing tokens, and tokens with blocklists.
contract L2StandardBridge is StandardBridge, ISemver { contract L2StandardBridge is StandardBridge, Semver {
/// @custom:legacy /// @custom:legacy
/// @notice Emitted whenever a withdrawal from L2 to L1 is initiated. /// @notice Emitted whenever a withdrawal from L2 to L1 is initiated.
/// @param l1Token Address of the token on L1. /// @param l1Token Address of the token on L1.
...@@ -52,24 +52,23 @@ contract L2StandardBridge is StandardBridge, ISemver { ...@@ -52,24 +52,23 @@ contract L2StandardBridge is StandardBridge, ISemver {
bytes extraData bytes extraData
); );
/// @custom:semver 1.4.0 /// @custom:semver 1.1.1
string public constant version = "1.4.0";
/// @notice Constructs the L2StandardBridge contract. /// @notice Constructs the L2StandardBridge contract.
/// @param _otherBridge Address of the L1StandardBridge. /// @param _otherBridge Address of the L1StandardBridge.
constructor(StandardBridge _otherBridge) StandardBridge(_otherBridge) { constructor(address payable _otherBridge)
initialize(); Semver(1, 1, 1)
} StandardBridge(payable(Predeploys.L2_CROSS_DOMAIN_MESSENGER), _otherBridge)
{}
/// @notice Initializer
function initialize() public reinitializer(Constants.INITIALIZER) {
__StandardBridge_init({ _messenger: CrossDomainMessenger(Predeploys.L2_CROSS_DOMAIN_MESSENGER) });
}
/// @notice Allows EOAs to bridge ETH by sending directly to the bridge. /// @notice Allows EOAs to bridge ETH by sending directly to the bridge.
receive() external payable override onlyEOA { receive() external payable override onlyEOA {
_initiateWithdrawal( _initiateWithdrawal(
Predeploys.LEGACY_ERC20_ETH, msg.sender, msg.sender, msg.value, RECEIVE_DEFAULT_GAS_LIMIT, bytes("") Predeploys.LEGACY_ERC20_ETH,
msg.sender,
msg.sender,
msg.value,
RECEIVE_DEFAULT_GAS_LIMIT,
bytes("")
); );
} }
...@@ -86,12 +85,7 @@ contract L2StandardBridge is StandardBridge, ISemver { ...@@ -86,12 +85,7 @@ contract L2StandardBridge is StandardBridge, ISemver {
uint256 _amount, uint256 _amount,
uint32 _minGasLimit, uint32 _minGasLimit,
bytes calldata _extraData bytes calldata _extraData
) ) external payable virtual onlyEOA {
external
payable
virtual
onlyEOA
{
_initiateWithdrawal(_l2Token, msg.sender, msg.sender, _amount, _minGasLimit, _extraData); _initiateWithdrawal(_l2Token, msg.sender, msg.sender, _amount, _minGasLimit, _extraData);
} }
...@@ -114,11 +108,7 @@ contract L2StandardBridge is StandardBridge, ISemver { ...@@ -114,11 +108,7 @@ contract L2StandardBridge is StandardBridge, ISemver {
uint256 _amount, uint256 _amount,
uint32 _minGasLimit, uint32 _minGasLimit,
bytes calldata _extraData bytes calldata _extraData
) ) external payable virtual {
external
payable
virtual
{
_initiateWithdrawal(_l2Token, msg.sender, _to, _amount, _minGasLimit, _extraData); _initiateWithdrawal(_l2Token, msg.sender, _to, _amount, _minGasLimit, _extraData);
} }
...@@ -138,11 +128,7 @@ contract L2StandardBridge is StandardBridge, ISemver { ...@@ -138,11 +128,7 @@ contract L2StandardBridge is StandardBridge, ISemver {
address _to, address _to,
uint256 _amount, uint256 _amount,
bytes calldata _extraData bytes calldata _extraData
) ) external payable virtual {
external
payable
virtual
{
if (_l1Token == address(0) && _l2Token == Predeploys.LEGACY_ERC20_ETH) { if (_l1Token == address(0) && _l2Token == Predeploys.LEGACY_ERC20_ETH) {
finalizeBridgeETH(_from, _to, _amount, _extraData); finalizeBridgeETH(_from, _to, _amount, _extraData);
} else { } else {
...@@ -172,9 +158,7 @@ contract L2StandardBridge is StandardBridge, ISemver { ...@@ -172,9 +158,7 @@ contract L2StandardBridge is StandardBridge, ISemver {
uint256 _amount, uint256 _amount,
uint32 _minGasLimit, uint32 _minGasLimit,
bytes memory _extraData bytes memory _extraData
) ) internal {
internal
{
if (_l2Token == Predeploys.LEGACY_ERC20_ETH) { if (_l2Token == Predeploys.LEGACY_ERC20_ETH) {
_initiateBridgeETH(_from, _to, _amount, _minGasLimit, _extraData); _initiateBridgeETH(_from, _to, _amount, _minGasLimit, _extraData);
} else { } else {
...@@ -191,11 +175,15 @@ contract L2StandardBridge is StandardBridge, ISemver { ...@@ -191,11 +175,15 @@ contract L2StandardBridge is StandardBridge, ISemver {
address _to, address _to,
uint256 _amount, uint256 _amount,
bytes memory _extraData bytes memory _extraData
) ) internal override {
internal emit WithdrawalInitiated(
override address(0),
{ Predeploys.LEGACY_ERC20_ETH,
emit WithdrawalInitiated(address(0), Predeploys.LEGACY_ERC20_ETH, _from, _to, _amount, _extraData); _from,
_to,
_amount,
_extraData
);
super._emitETHBridgeInitiated(_from, _to, _amount, _extraData); super._emitETHBridgeInitiated(_from, _to, _amount, _extraData);
} }
...@@ -207,11 +195,15 @@ contract L2StandardBridge is StandardBridge, ISemver { ...@@ -207,11 +195,15 @@ contract L2StandardBridge is StandardBridge, ISemver {
address _to, address _to,
uint256 _amount, uint256 _amount,
bytes memory _extraData bytes memory _extraData
) ) internal override {
internal emit DepositFinalized(
override address(0),
{ Predeploys.LEGACY_ERC20_ETH,
emit DepositFinalized(address(0), Predeploys.LEGACY_ERC20_ETH, _from, _to, _amount, _extraData); _from,
_to,
_amount,
_extraData
);
super._emitETHBridgeFinalized(_from, _to, _amount, _extraData); super._emitETHBridgeFinalized(_from, _to, _amount, _extraData);
} }
...@@ -225,10 +217,7 @@ contract L2StandardBridge is StandardBridge, ISemver { ...@@ -225,10 +217,7 @@ contract L2StandardBridge is StandardBridge, ISemver {
address _to, address _to,
uint256 _amount, uint256 _amount,
bytes memory _extraData bytes memory _extraData
) ) internal override {
internal
override
{
emit WithdrawalInitiated(_remoteToken, _localToken, _from, _to, _amount, _extraData); emit WithdrawalInitiated(_remoteToken, _localToken, _from, _to, _amount, _extraData);
super._emitERC20BridgeInitiated(_localToken, _remoteToken, _from, _to, _amount, _extraData); super._emitERC20BridgeInitiated(_localToken, _remoteToken, _from, _to, _amount, _extraData);
} }
...@@ -243,10 +232,7 @@ contract L2StandardBridge is StandardBridge, ISemver { ...@@ -243,10 +232,7 @@ contract L2StandardBridge is StandardBridge, ISemver {
address _to, address _to,
uint256 _amount, uint256 _amount,
bytes memory _extraData bytes memory _extraData
) ) internal override {
internal
override
{
emit DepositFinalized(_remoteToken, _localToken, _from, _to, _amount, _extraData); emit DepositFinalized(_remoteToken, _localToken, _from, _to, _amount, _extraData);
super._emitERC20BridgeFinalized(_localToken, _remoteToken, _from, _to, _amount, _extraData); super._emitERC20BridgeFinalized(_localToken, _remoteToken, _from, _to, _amount, _extraData);
} }
......
...@@ -7,17 +7,15 @@ import { Initializable } from "@openzeppelin/contracts/proxy/utils/Initializable ...@@ -7,17 +7,15 @@ import { Initializable } from "@openzeppelin/contracts/proxy/utils/Initializable
/// @title ERC721Bridge /// @title ERC721Bridge
/// @notice ERC721Bridge is a base contract for the L1 and L2 ERC721 bridges. /// @notice ERC721Bridge is a base contract for the L1 and L2 ERC721 bridges.
abstract contract ERC721Bridge is Initializable { abstract contract ERC721Bridge {
/// @notice Messenger contract on this domain. /// @notice Messenger contract on this domain.
/// @custom:network-specific CrossDomainMessenger public immutable MESSENGER;
CrossDomainMessenger public messenger;
/// @notice Address of the bridge on the other network. /// @notice Address of the bridge on the other network.
/// @custom:legacy
address public immutable OTHER_BRIDGE; address public immutable OTHER_BRIDGE;
/// @notice Reserve extra slots (to a total of 50) in the storage layout for future upgrades. /// @notice Reserve extra slots (to a total of 50) in the storage layout for future upgrades.
uint256[48] private __gap; uint256[49] private __gap;
/// @notice Emitted when an ERC721 bridge to the other network is initiated. /// @notice Emitted when an ERC721 bridge to the other network is initiated.
/// @param localToken Address of the token on this domain. /// @param localToken Address of the token on this domain.
...@@ -54,31 +52,31 @@ abstract contract ERC721Bridge is Initializable { ...@@ -54,31 +52,31 @@ abstract contract ERC721Bridge is Initializable {
/// @notice Ensures that the caller is a cross-chain message from the other bridge. /// @notice Ensures that the caller is a cross-chain message from the other bridge.
modifier onlyOtherBridge() { modifier onlyOtherBridge() {
require( require(
msg.sender == address(messenger) && messenger.xDomainMessageSender() == OTHER_BRIDGE, msg.sender == address(MESSENGER) && MESSENGER.xDomainMessageSender() == OTHER_BRIDGE,
"ERC721Bridge: function can only be called from the other bridge" "ERC721Bridge: function can only be called from the other bridge"
); );
_; _;
} }
/// @notice Constructs the contract. /// @param _messenger Address of the CrossDomainMessenger on this network.
/// @param _otherBridge Address of the ERC721 bridge on the other network. /// @param _otherBridge Address of the ERC721 bridge on the other network.
constructor(address _otherBridge) { constructor(address _messenger, address _otherBridge) {
require(_messenger != address(0), "ERC721Bridge: messenger cannot be address(0)");
require(_otherBridge != address(0), "ERC721Bridge: other bridge cannot be address(0)"); require(_otherBridge != address(0), "ERC721Bridge: other bridge cannot be address(0)");
OTHER_BRIDGE = _otherBridge;
}
// @notice Initializes the contract. MESSENGER = CrossDomainMessenger(_messenger);
/// @param _messenger Address of the CrossDomainMessenger on this network. OTHER_BRIDGE = _otherBridge;
function __ERC721Bridge_init(CrossDomainMessenger _messenger) internal onlyInitializing {
messenger = _messenger;
} }
/// @notice Getter for messenger contract. /// @custom:legacy
function MESSENGER() external view returns (CrossDomainMessenger) { /// @notice Legacy getter for messenger contract.
return messenger; /// @return Messenger contract on this domain.
function messenger() external view returns (CrossDomainMessenger) {
return MESSENGER;
} }
/// @notice Getter for other bridge address. /// @custom:legacy
/// @notice Legacy getter for other bridge address.
/// @return Address of the bridge on the other network. /// @return Address of the bridge on the other network.
function otherBridge() external view returns (address) { function otherBridge() external view returns (address) {
return OTHER_BRIDGE; return OTHER_BRIDGE;
...@@ -106,9 +104,7 @@ abstract contract ERC721Bridge is Initializable { ...@@ -106,9 +104,7 @@ abstract contract ERC721Bridge is Initializable {
uint256 _tokenId, uint256 _tokenId,
uint32 _minGasLimit, uint32 _minGasLimit,
bytes calldata _extraData bytes calldata _extraData
) ) external {
external
{
// Modifier requiring sender to be EOA. This prevents against a user error that would occur // Modifier requiring sender to be EOA. This prevents against a user error that would occur
// if the sender is a smart contract wallet that has a different address on the remote chain // if the sender is a smart contract wallet that has a different address on the remote chain
// (or doesn't have an address on the remote chain at all). The user would fail to receive // (or doesn't have an address on the remote chain at all). The user would fail to receive
...@@ -117,7 +113,15 @@ abstract contract ERC721Bridge is Initializable { ...@@ -117,7 +113,15 @@ abstract contract ERC721Bridge is Initializable {
// care of the user error we want to avoid. // care of the user error we want to avoid.
require(!Address.isContract(msg.sender), "ERC721Bridge: account is not externally owned"); require(!Address.isContract(msg.sender), "ERC721Bridge: account is not externally owned");
_initiateBridgeERC721(_localToken, _remoteToken, msg.sender, msg.sender, _tokenId, _minGasLimit, _extraData); _initiateBridgeERC721(
_localToken,
_remoteToken,
msg.sender,
msg.sender,
_tokenId,
_minGasLimit,
_extraData
);
} }
/// @notice Initiates a bridge of an NFT to some recipient's account on the other chain. Note /// @notice Initiates a bridge of an NFT to some recipient's account on the other chain. Note
...@@ -142,12 +146,18 @@ abstract contract ERC721Bridge is Initializable { ...@@ -142,12 +146,18 @@ abstract contract ERC721Bridge is Initializable {
uint256 _tokenId, uint256 _tokenId,
uint32 _minGasLimit, uint32 _minGasLimit,
bytes calldata _extraData bytes calldata _extraData
) ) external {
external
{
require(_to != address(0), "ERC721Bridge: nft recipient cannot be address(0)"); require(_to != address(0), "ERC721Bridge: nft recipient cannot be address(0)");
_initiateBridgeERC721(_localToken, _remoteToken, msg.sender, _to, _tokenId, _minGasLimit, _extraData); _initiateBridgeERC721(
_localToken,
_remoteToken,
msg.sender,
_to,
_tokenId,
_minGasLimit,
_extraData
);
} }
/// @notice Internal function for initiating a token bridge to the other domain. /// @notice Internal function for initiating a token bridge to the other domain.
...@@ -168,7 +178,5 @@ abstract contract ERC721Bridge is Initializable { ...@@ -168,7 +178,5 @@ abstract contract ERC721Bridge is Initializable {
uint256 _tokenId, uint256 _tokenId,
uint32 _minGasLimit, uint32 _minGasLimit,
bytes calldata _extraData bytes calldata _extraData
) ) internal virtual;
internal
virtual;
} }
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import { Strings } from "@openzeppelin/contracts/utils/Strings.sol";
/// @title Semver
/// @notice Semver is a simple contract for managing contract versions.
contract Semver {
/// @notice Contract version number (major).
uint256 private immutable MAJOR_VERSION;
/// @notice Contract version number (minor).
uint256 private immutable MINOR_VERSION;
/// @notice Contract version number (patch).
uint256 private immutable PATCH_VERSION;
/// @param _major Version number (major).
/// @param _minor Version number (minor).
/// @param _patch Version number (patch).
constructor(
uint256 _major,
uint256 _minor,
uint256 _patch
) {
MAJOR_VERSION = _major;
MINOR_VERSION = _minor;
PATCH_VERSION = _patch;
}
/// @notice Returns the full semver contract version.
/// @return Semver contract version as a string.
function version() public view returns (string memory) {
return
string(
abi.encodePacked(
Strings.toString(MAJOR_VERSION),
".",
Strings.toString(MINOR_VERSION),
".",
Strings.toString(PATCH_VERSION)
)
);
}
}
...@@ -16,26 +16,22 @@ import { Initializable } from "@openzeppelin/contracts/proxy/utils/Initializable ...@@ -16,26 +16,22 @@ import { Initializable } from "@openzeppelin/contracts/proxy/utils/Initializable
/// @notice StandardBridge is a base contract for the L1 and L2 standard ERC20 bridges. It handles /// @notice StandardBridge is a base contract for the L1 and L2 standard ERC20 bridges. It handles
/// the core bridging logic, including escrowing tokens that are native to the local chain /// the core bridging logic, including escrowing tokens that are native to the local chain
/// and minting/burning tokens that are native to the remote chain. /// and minting/burning tokens that are native to the remote chain.
abstract contract StandardBridge is Initializable { abstract contract StandardBridge {
using SafeERC20 for IERC20; using SafeERC20 for IERC20;
/// @notice The L2 gas limit set when eth is depoisited using the receive() function. /// @notice The L2 gas limit set when eth is depoisited using the receive() function.
uint32 internal constant RECEIVE_DEFAULT_GAS_LIMIT = 200_000; uint32 internal constant RECEIVE_DEFAULT_GAS_LIMIT = 200_000;
/// @notice Corresponding bridge on the other domain. This public getter is deprecated /// @notice Messenger contract on this domain.
/// and will be removed in the future. Please use `otherBridge` instead. CrossDomainMessenger public immutable MESSENGER;
/// This can safely be an immutable because for the L1StandardBridge, it will
/// be set to the L2StandardBridge address, which is the same for all OP Stack /// @notice Corresponding bridge on the other domain.
/// chains. For the L2StandardBridge, there are not multiple proxies using the
/// same implementation.
/// @custom:legacy
/// @custom:network-specific
StandardBridge public immutable OTHER_BRIDGE; StandardBridge public immutable OTHER_BRIDGE;
/// @custom:legacy /// @custom:legacy
/// @custom:spacer messenger /// @custom:spacer messenger
/// @notice Spacer for backwards compatibility. /// @notice Spacer for backwards compatibility.
address private spacer_0_2_20; address private spacer_0_0_20;
/// @custom:legacy /// @custom:legacy
/// @custom:spacer l2TokenBridge /// @custom:spacer l2TokenBridge
...@@ -45,29 +41,34 @@ abstract contract StandardBridge is Initializable { ...@@ -45,29 +41,34 @@ abstract contract StandardBridge is Initializable {
/// @notice Mapping that stores deposits for a given pair of local and remote tokens. /// @notice Mapping that stores deposits for a given pair of local and remote tokens.
mapping(address => mapping(address => uint256)) public deposits; mapping(address => mapping(address => uint256)) public deposits;
/// @notice Messenger contract on this domain. This public getter is deprecated
/// and will be removed in the future. Please use `messenger` instead.
/// @custom:network-specific
CrossDomainMessenger public messenger;
/// @notice Reserve extra slots (to a total of 50) in the storage layout for future upgrades. /// @notice Reserve extra slots (to a total of 50) in the storage layout for future upgrades.
/// A gap size of 46 was chosen here, so that the first slot used in a child contract /// A gap size of 47 was chosen here, so that the first slot used in a child contract
/// would be a multiple of 50. /// would be a multiple of 50.
uint256[46] private __gap; uint256[47] private __gap;
/// @notice Emitted when an ETH bridge is initiated to the other chain. /// @notice Emitted when an ETH bridge is initiated to the other chain.
/// @param from Address of the sender. /// @param from Address of the sender.
/// @param to Address of the receiver. /// @param to Address of the receiver.
/// @param amount Amount of ETH sent. /// @param amount Amount of ETH sent.
/// @param extraData Extra data sent with the transaction. /// @param extraData Extra data sent with the transaction.
event ETHBridgeInitiated(address indexed from, address indexed to, uint256 amount, bytes extraData); event ETHBridgeInitiated(
address indexed from,
address indexed to,
uint256 amount,
bytes extraData
);
/// @notice Emitted when an ETH bridge is finalized on this chain. /// @notice Emitted when an ETH bridge is finalized on this chain.
/// @param from Address of the sender. /// @param from Address of the sender.
/// @param to Address of the receiver. /// @param to Address of the receiver.
/// @param amount Amount of ETH sent. /// @param amount Amount of ETH sent.
/// @param extraData Extra data sent with the transaction. /// @param extraData Extra data sent with the transaction.
event ETHBridgeFinalized(address indexed from, address indexed to, uint256 amount, bytes extraData); event ETHBridgeFinalized(
address indexed from,
address indexed to,
uint256 amount,
bytes extraData
);
/// @notice Emitted when an ERC20 bridge is initiated to the other chain. /// @notice Emitted when an ERC20 bridge is initiated to the other chain.
/// @param localToken Address of the ERC20 on this chain. /// @param localToken Address of the ERC20 on this chain.
...@@ -105,45 +106,39 @@ abstract contract StandardBridge is Initializable { ...@@ -105,45 +106,39 @@ abstract contract StandardBridge is Initializable {
/// calling code within their constructors, but also doesn't really matter since we're /// calling code within their constructors, but also doesn't really matter since we're
/// just trying to prevent users accidentally depositing with smart contract wallets. /// just trying to prevent users accidentally depositing with smart contract wallets.
modifier onlyEOA() { modifier onlyEOA() {
require(!Address.isContract(msg.sender), "StandardBridge: function can only be called from an EOA"); require(
!Address.isContract(msg.sender),
"StandardBridge: function can only be called from an EOA"
);
_; _;
} }
/// @notice Ensures that the caller is a cross-chain message from the other bridge. /// @notice Ensures that the caller is a cross-chain message from the other bridge.
modifier onlyOtherBridge() { modifier onlyOtherBridge() {
require( require(
msg.sender == address(messenger) && messenger.xDomainMessageSender() == address(OTHER_BRIDGE), msg.sender == address(MESSENGER) &&
MESSENGER.xDomainMessageSender() == address(OTHER_BRIDGE),
"StandardBridge: function can only be called from the other bridge" "StandardBridge: function can only be called from the other bridge"
); );
_; _;
} }
/// @param _otherBridge Address of the other StandardBridge contract.
constructor(StandardBridge _otherBridge) {
OTHER_BRIDGE = _otherBridge;
}
/// @notice Initializer.
/// @param _messenger Address of CrossDomainMessenger on this network. /// @param _messenger Address of CrossDomainMessenger on this network.
// solhint-disable-next-line func-name-mixedcase /// @param _otherBridge Address of the other StandardBridge contract.
function __StandardBridge_init(CrossDomainMessenger _messenger) internal onlyInitializing { constructor(address payable _messenger, address payable _otherBridge) {
messenger = _messenger; MESSENGER = CrossDomainMessenger(_messenger);
OTHER_BRIDGE = StandardBridge(_otherBridge);
} }
/// @notice Allows EOAs to bridge ETH by sending directly to the bridge. /// @notice Allows EOAs to bridge ETH by sending directly to the bridge.
/// Must be implemented by contracts that inherit. /// Must be implemented by contracts that inherit.
receive() external payable virtual; receive() external payable virtual;
/// @notice Getter for messenger contract.
/// @custom:legacy /// @custom:legacy
/// @notice Legacy getter for messenger contract.
/// @return Messenger contract on this domain. /// @return Messenger contract on this domain.
function MESSENGER() external view returns (CrossDomainMessenger) { function messenger() external view returns (CrossDomainMessenger) {
return messenger; return MESSENGER;
}
/// @notice Getter for the remote domain bridge contract.
function otherBridge() external view returns (StandardBridge) {
return OTHER_BRIDGE;
} }
/// @notice Sends ETH to the sender's address on the other chain. /// @notice Sends ETH to the sender's address on the other chain.
...@@ -167,7 +162,11 @@ abstract contract StandardBridge is Initializable { ...@@ -167,7 +162,11 @@ abstract contract StandardBridge is Initializable {
/// @param _extraData Extra data to be sent with the transaction. Note that the recipient will /// @param _extraData Extra data to be sent with the transaction. Note that the recipient will
/// not be triggered with this data, but it will be emitted and can be used /// not be triggered with this data, but it will be emitted and can be used
/// to identify the transaction. /// to identify the transaction.
function bridgeETHTo(address _to, uint32 _minGasLimit, bytes calldata _extraData) public payable { function bridgeETHTo(
address _to,
uint32 _minGasLimit,
bytes calldata _extraData
) public payable {
_initiateBridgeETH(msg.sender, _to, msg.value, _minGasLimit, _extraData); _initiateBridgeETH(msg.sender, _to, msg.value, _minGasLimit, _extraData);
} }
...@@ -188,12 +187,16 @@ abstract contract StandardBridge is Initializable { ...@@ -188,12 +187,16 @@ abstract contract StandardBridge is Initializable {
uint256 _amount, uint256 _amount,
uint32 _minGasLimit, uint32 _minGasLimit,
bytes calldata _extraData bytes calldata _extraData
) ) public virtual onlyEOA {
public _initiateBridgeERC20(
virtual _localToken,
onlyEOA _remoteToken,
{ msg.sender,
_initiateBridgeERC20(_localToken, _remoteToken, msg.sender, msg.sender, _amount, _minGasLimit, _extraData); msg.sender,
_amount,
_minGasLimit,
_extraData
);
} }
/// @notice Sends ERC20 tokens to a receiver's address on the other chain. Note that if the /// @notice Sends ERC20 tokens to a receiver's address on the other chain. Note that if the
...@@ -215,11 +218,16 @@ abstract contract StandardBridge is Initializable { ...@@ -215,11 +218,16 @@ abstract contract StandardBridge is Initializable {
uint256 _amount, uint256 _amount,
uint32 _minGasLimit, uint32 _minGasLimit,
bytes calldata _extraData bytes calldata _extraData
) ) public virtual {
public _initiateBridgeERC20(
virtual _localToken,
{ _remoteToken,
_initiateBridgeERC20(_localToken, _remoteToken, msg.sender, _to, _amount, _minGasLimit, _extraData); msg.sender,
_to,
_amount,
_minGasLimit,
_extraData
);
} }
/// @notice Finalizes an ETH bridge on this chain. Can only be triggered by the other /// @notice Finalizes an ETH bridge on this chain. Can only be triggered by the other
...@@ -235,14 +243,10 @@ abstract contract StandardBridge is Initializable { ...@@ -235,14 +243,10 @@ abstract contract StandardBridge is Initializable {
address _to, address _to,
uint256 _amount, uint256 _amount,
bytes calldata _extraData bytes calldata _extraData
) ) public payable onlyOtherBridge {
public
payable
onlyOtherBridge
{
require(msg.value == _amount, "StandardBridge: amount sent does not match amount required"); require(msg.value == _amount, "StandardBridge: amount sent does not match amount required");
require(_to != address(this), "StandardBridge: cannot send to self"); require(_to != address(this), "StandardBridge: cannot send to self");
require(_to != address(messenger), "StandardBridge: cannot send to messenger"); require(_to != address(MESSENGER), "StandardBridge: cannot send to messenger");
// Emit the correct events. By default this will be _amount, but child // Emit the correct events. By default this will be _amount, but child
// contracts may override this function in order to emit legacy events as well. // contracts may override this function in order to emit legacy events as well.
...@@ -269,10 +273,7 @@ abstract contract StandardBridge is Initializable { ...@@ -269,10 +273,7 @@ abstract contract StandardBridge is Initializable {
address _to, address _to,
uint256 _amount, uint256 _amount,
bytes calldata _extraData bytes calldata _extraData
) ) public onlyOtherBridge {
public
onlyOtherBridge
{
if (_isOptimismMintableERC20(_localToken)) { if (_isOptimismMintableERC20(_localToken)) {
require( require(
_isCorrectTokenPair(_localToken, _remoteToken), _isCorrectTokenPair(_localToken, _remoteToken),
...@@ -304,18 +305,25 @@ abstract contract StandardBridge is Initializable { ...@@ -304,18 +305,25 @@ abstract contract StandardBridge is Initializable {
uint256 _amount, uint256 _amount,
uint32 _minGasLimit, uint32 _minGasLimit,
bytes memory _extraData bytes memory _extraData
) ) internal {
internal require(
{ msg.value == _amount,
require(msg.value == _amount, "StandardBridge: bridging ETH must include sufficient ETH value"); "StandardBridge: bridging ETH must include sufficient ETH value"
);
// Emit the correct events. By default this will be _amount, but child // Emit the correct events. By default this will be _amount, but child
// contracts may override this function in order to emit legacy events as well. // contracts may override this function in order to emit legacy events as well.
_emitETHBridgeInitiated(_from, _to, _amount, _extraData); _emitETHBridgeInitiated(_from, _to, _amount, _extraData);
messenger.sendMessage{ value: _amount }( MESSENGER.sendMessage{ value: _amount }(
address(OTHER_BRIDGE), address(OTHER_BRIDGE),
abi.encodeWithSelector(this.finalizeBridgeETH.selector, _from, _to, _amount, _extraData), abi.encodeWithSelector(
this.finalizeBridgeETH.selector,
_from,
_to,
_amount,
_extraData
),
_minGasLimit _minGasLimit
); );
} }
...@@ -337,9 +345,7 @@ abstract contract StandardBridge is Initializable { ...@@ -337,9 +345,7 @@ abstract contract StandardBridge is Initializable {
uint256 _amount, uint256 _amount,
uint32 _minGasLimit, uint32 _minGasLimit,
bytes memory _extraData bytes memory _extraData
) ) internal {
internal
{
if (_isOptimismMintableERC20(_localToken)) { if (_isOptimismMintableERC20(_localToken)) {
require( require(
_isCorrectTokenPair(_localToken, _remoteToken), _isCorrectTokenPair(_localToken, _remoteToken),
...@@ -356,7 +362,7 @@ abstract contract StandardBridge is Initializable { ...@@ -356,7 +362,7 @@ abstract contract StandardBridge is Initializable {
// contracts may override this function in order to emit legacy events as well. // contracts may override this function in order to emit legacy events as well.
_emitERC20BridgeInitiated(_localToken, _remoteToken, _from, _to, _amount, _extraData); _emitERC20BridgeInitiated(_localToken, _remoteToken, _from, _to, _amount, _extraData);
messenger.sendMessage( MESSENGER.sendMessage(
address(OTHER_BRIDGE), address(OTHER_BRIDGE),
abi.encodeWithSelector( abi.encodeWithSelector(
this.finalizeBridgeERC20.selector, this.finalizeBridgeERC20.selector,
...@@ -379,8 +385,9 @@ abstract contract StandardBridge is Initializable { ...@@ -379,8 +385,9 @@ abstract contract StandardBridge is Initializable {
/// @param _token Address of the token to check. /// @param _token Address of the token to check.
/// @return True if the token is an OptimismMintableERC20. /// @return True if the token is an OptimismMintableERC20.
function _isOptimismMintableERC20(address _token) internal view returns (bool) { function _isOptimismMintableERC20(address _token) internal view returns (bool) {
return ERC165Checker.supportsInterface(_token, type(ILegacyMintableERC20).interfaceId) return
|| ERC165Checker.supportsInterface(_token, type(IOptimismMintableERC20).interfaceId); ERC165Checker.supportsInterface(_token, type(ILegacyMintableERC20).interfaceId) ||
ERC165Checker.supportsInterface(_token, type(IOptimismMintableERC20).interfaceId);
} }
/// @notice Checks if the "other token" is the correct pair token for the OptimismMintableERC20. /// @notice Checks if the "other token" is the correct pair token for the OptimismMintableERC20.
...@@ -389,8 +396,14 @@ abstract contract StandardBridge is Initializable { ...@@ -389,8 +396,14 @@ abstract contract StandardBridge is Initializable {
/// @param _mintableToken OptimismMintableERC20 to check against. /// @param _mintableToken OptimismMintableERC20 to check against.
/// @param _otherToken Pair token to check. /// @param _otherToken Pair token to check.
/// @return True if the other token is the correct pair token for the OptimismMintableERC20. /// @return True if the other token is the correct pair token for the OptimismMintableERC20.
function _isCorrectTokenPair(address _mintableToken, address _otherToken) internal view returns (bool) { function _isCorrectTokenPair(address _mintableToken, address _otherToken)
if (ERC165Checker.supportsInterface(_mintableToken, type(ILegacyMintableERC20).interfaceId)) { internal
view
returns (bool)
{
if (
ERC165Checker.supportsInterface(_mintableToken, type(ILegacyMintableERC20).interfaceId)
) {
return _otherToken == ILegacyMintableERC20(_mintableToken).l1Token(); return _otherToken == ILegacyMintableERC20(_mintableToken).l1Token();
} else { } else {
return _otherToken == IOptimismMintableERC20(_mintableToken).remoteToken(); return _otherToken == IOptimismMintableERC20(_mintableToken).remoteToken();
...@@ -408,10 +421,7 @@ abstract contract StandardBridge is Initializable { ...@@ -408,10 +421,7 @@ abstract contract StandardBridge is Initializable {
address _to, address _to,
uint256 _amount, uint256 _amount,
bytes memory _extraData bytes memory _extraData
) ) internal virtual {
internal
virtual
{
emit ETHBridgeInitiated(_from, _to, _amount, _extraData); emit ETHBridgeInitiated(_from, _to, _amount, _extraData);
} }
...@@ -426,10 +436,7 @@ abstract contract StandardBridge is Initializable { ...@@ -426,10 +436,7 @@ abstract contract StandardBridge is Initializable {
address _to, address _to,
uint256 _amount, uint256 _amount,
bytes memory _extraData bytes memory _extraData
) ) internal virtual {
internal
virtual
{
emit ETHBridgeFinalized(_from, _to, _amount, _extraData); emit ETHBridgeFinalized(_from, _to, _amount, _extraData);
} }
...@@ -448,10 +455,7 @@ abstract contract StandardBridge is Initializable { ...@@ -448,10 +455,7 @@ abstract contract StandardBridge is Initializable {
address _to, address _to,
uint256 _amount, uint256 _amount,
bytes memory _extraData bytes memory _extraData
) ) internal virtual {
internal
virtual
{
emit ERC20BridgeInitiated(_localToken, _remoteToken, _from, _to, _amount, _extraData); emit ERC20BridgeInitiated(_localToken, _remoteToken, _from, _to, _amount, _extraData);
} }
...@@ -470,10 +474,7 @@ abstract contract StandardBridge is Initializable { ...@@ -470,10 +474,7 @@ abstract contract StandardBridge is Initializable {
address _to, address _to,
uint256 _amount, uint256 _amount,
bytes memory _extraData bytes memory _extraData
) ) internal virtual {
internal
virtual
{
emit ERC20BridgeFinalized(_localToken, _remoteToken, _from, _to, _amount, _extraData); emit ERC20BridgeFinalized(_localToken, _remoteToken, _from, _to, _amount, _extraData);
} }
} }
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