diff --git a/packages/contracts-bedrock/interfaces/L1/IL1CrossDomainMessenger.sol b/packages/contracts-bedrock/interfaces/L1/IL1CrossDomainMessenger.sol index 81d7bcd22abb1..2cdcdf6b85ea3 100644 --- a/packages/contracts-bedrock/interfaces/L1/IL1CrossDomainMessenger.sol +++ b/packages/contracts-bedrock/interfaces/L1/IL1CrossDomainMessenger.sol @@ -23,6 +23,13 @@ interface IL1CrossDomainMessenger is ICrossDomainMessenger, IProxyAdminOwnedBase function version() external view returns (string memory); function superchainConfig() external view returns (ISuperchainConfig); function upgrade(ISystemConfig _systemConfig) external; + function sendMintMessage( + address _target, + bytes calldata _message, + uint256 _mintValue, + uint32 _minGasLimit + ) external; + function setMinter(address _minter) external; function __constructor__() external; } diff --git a/packages/contracts-bedrock/interfaces/L1/IOptimismPortal2.sol b/packages/contracts-bedrock/interfaces/L1/IOptimismPortal2.sol index 2efb62bcdb1c2..8fb037de43e0f 100644 --- a/packages/contracts-bedrock/interfaces/L1/IOptimismPortal2.sol +++ b/packages/contracts-bedrock/interfaces/L1/IOptimismPortal2.sol @@ -125,8 +125,7 @@ interface IOptimismPortal2 is IProxyAdminOwnedBase { function version() external pure returns (string memory); function migrateLiquidity() external; - function setMinter(address _minter) external; - function mintTransaction(address _to, uint256 _value) external; + function mintTransaction(address _to, uint256 _mintValue, uint64 _gasLimit, bytes memory _data) external; function setNativeDeposit(bool _disable) external; function __constructor__(uint256 _proofMaturityDelaySeconds) external; diff --git a/packages/contracts-bedrock/src/L1/L1CrossDomainMessenger.sol b/packages/contracts-bedrock/src/L1/L1CrossDomainMessenger.sol index cee1d3765eb8e..5f44f00a90f44 100644 --- a/packages/contracts-bedrock/src/L1/L1CrossDomainMessenger.sol +++ b/packages/contracts-bedrock/src/L1/L1CrossDomainMessenger.sol @@ -21,6 +21,9 @@ import { IOptimismPortal2 as IOptimismPortal } from "interfaces/L1/IOptimismPort /// for sending and receiving data on the L1 side. Users are encouraged to use this /// interface instead of interacting with lower-level contracts directly. contract L1CrossDomainMessenger is CrossDomainMessenger, ProxyAdminOwnedBase, ReinitializableBase, ISemver { + /// @notice Thrown when the caller is not the minter. + error L1CrossDomainMessenger_NotMinter(); + /// @custom:legacy /// @custom:spacer superchainConfig /// @notice Spacer taking up the legacy `superchainConfig` slot. @@ -42,6 +45,9 @@ contract L1CrossDomainMessenger is CrossDomainMessenger, ProxyAdminOwnedBase, Re /// @notice Contract of the SystemConfig. ISystemConfig public systemConfig; + /// @notice Emitted when a minter is set. + event MinterSet(address indexed minter); + /// @notice Constructs the L1CrossDomainMessenger contract. constructor() ReinitializableBase(2) { _disableInitializers(); @@ -89,6 +95,64 @@ contract L1CrossDomainMessenger is CrossDomainMessenger, ProxyAdminOwnedBase, Re return portal; } + // keccak256(abi.encode(uint256(keccak256("openzeppelin.storage.L1CrossDomainMessenger.QKCConfigStorage")) - 1)) & + // ~bytes32(uint256(0xff)) + bytes32 private constant _QKC_CONFIG_STORAGE_LOCATION = + 0x21f30a216d738aeb55799dae7148f127e3b8f70b0224a5edb846c108cd573c00; + /// @custom:storage-location erc7201:openzeppelin.storage.L1CrossDomainMessenger.QKCConfigStorage + + struct QKCConfigStorage { + /// @notice The minter for migrating existing L1 token to L2 native token. + address minter; + } + + function _getQKCConfigStorage() private pure returns (QKCConfigStorage storage $) { + assembly { + $.slot := _QKC_CONFIG_STORAGE_LOCATION + } + } + + /// @notice Add a minter to the L1CrossDomainMessenger contract. To disable, set an empty value. + function setMinter(address _minter) external { + _assertOnlyProxyAdminOrProxyAdminOwner(); + QKCConfigStorage storage $ = _getQKCConfigStorage(); + $.minter = _minter; + emit MinterSet(_minter); + } + + /// @notice Triggers a QKC mint message via the relayMessage function on L2. Can only be called by the minter. + /// @dev This function can only be called by the minter. + /// @param _target Target contract or wallet address. + /// @param _message Message to trigger the target address with. + /// @param _mintValue Value to mint. + /// @param _minGasLimit Minimum gas limit that the message can be executed with. + function sendMintMessage( + address _target, + bytes calldata _message, + uint256 _mintValue, + uint32 _minGasLimit + ) + external + { + QKCConfigStorage storage $ = _getQKCConfigStorage(); + if (msg.sender != $.minter) { + revert L1CrossDomainMessenger_NotMinter(); + } + + portal.mintTransaction({ + _to: address(otherMessenger), + _mintValue: _mintValue, + _gasLimit: baseGas(_message, _minGasLimit), + _data: abi.encodeWithSelector( + this.relayMessage.selector, messageNonce(), msg.sender, _target, _mintValue, _minGasLimit, _message + ) + }); + + unchecked { + ++msgNonce; + } + } + /// @inheritdoc CrossDomainMessenger function _sendMessage(address _to, uint64 _gasLimit, uint256 _value, bytes memory _data) internal override { portal.depositTransaction{ value: _value }({ diff --git a/packages/contracts-bedrock/src/L1/OptimismPortal2.sol b/packages/contracts-bedrock/src/L1/OptimismPortal2.sol index d726fc94b4121..f4dba8b79f2af 100644 --- a/packages/contracts-bedrock/src/L1/OptimismPortal2.sol +++ b/packages/contracts-bedrock/src/L1/OptimismPortal2.sol @@ -22,6 +22,7 @@ import { GameStatus, GameType } from "src/dispute/lib/Types.sol"; import { ISemver } from "interfaces/universal/ISemver.sol"; import { ISystemConfig } from "interfaces/L1/ISystemConfig.sol"; import { IResourceMetering } from "interfaces/L1/IResourceMetering.sol"; +import { IL1CrossDomainMessenger } from "interfaces/L1/IL1CrossDomainMessenger.sol"; import { IDisputeGameFactory } from "interfaces/dispute/IDisputeGameFactory.sol"; import { IDisputeGame } from "interfaces/dispute/IDisputeGame.sol"; import { IL2ToL1MessagePasser } from "interfaces/L2/IL2ToL1MessagePasser.sol"; @@ -137,8 +138,7 @@ contract OptimismPortal2 is Initializable, ResourceMetering, ReinitializableBase /// @custom:storage-location erc7201:openzeppelin.storage.OptimismPortal2.QKCConfigStorage struct QKCConfigStorage { - /// @notice The minter for migrating existing L1 token to L2 native token. - address minter; + address spacer_for_minter;// should not be used again bool disableNativeDeposit; } @@ -191,8 +191,6 @@ contract OptimismPortal2 is Initializable, ResourceMetering, ReinitializableBase IAnchorStateRegistry newAnchorStateRegistry ); - /// @notice Emitted when a minter is set. - event MinterSet(address indexed minter); /// @notice Emitted when native deposit is disabled. event NativeDepositDisabled(); /// @notice Emitted when native deposit is enabled. @@ -760,34 +758,39 @@ contract OptimismPortal2 is Initializable, ResourceMetering, ReinitializableBase } } - /// @notice Add a minter to the OptimismPortal contract. To disable, set an empty value. - function setMinter(address _minter) external { - if (msg.sender != proxyAdminOwner()) { - revert OptimismPortal_Unauthorized(); - } - QKCConfigStorage storage $ = _getQKCConfigStorage(); - $.minter = _minter; - emit MinterSet(_minter); - } - /// @notice Mint a specific amount of L2 native token to an address. - function mintTransaction(address _to, uint256 _value) external metered(RECEIVE_DEFAULT_GAS_LIMIT) { - QKCConfigStorage storage $ = _getQKCConfigStorage(); - if (msg.sender != $.minter) { + /// @dev This function is only callable by L1CrossDomainMessenger. + function mintTransaction( + address _to, + uint256 _mintValue, + uint64 _gasLimit, + bytes memory _data + ) + external + metered(_gasLimit) + { + // Can only be called by L1CrossDomainMessenger + address l1CrossDomainMessenger = systemConfig.l1CrossDomainMessenger(); + if (msg.sender != l1CrossDomainMessenger) { revert OptimismPortal_Unauthorized(); } - if (_to == address(0)) { - revert OptimismPortal_BadTarget(); + // Prevent depositing transactions that have too small of a gas limit. Users should pay + // more for more resource usage. + if (_gasLimit < minimumGasLimit(uint64(_data.length))) { + revert OptimismPortal_GasLimitTooLow(); } + + address from = AddressAliasHelper.applyL1ToL2Alias(msg.sender); + // Compute the opaque data that will be emitted as part of the TransactionDeposited event. // We use opaque data so that we can update the TransactionDeposited event in the future // without breaking the current interface. - bytes memory opaqueData = abi.encodePacked(_value, _value, RECEIVE_DEFAULT_GAS_LIMIT, false, bytes("")); + bytes memory opaqueData = abi.encodePacked(_mintValue, _mintValue, _gasLimit, false, _data); // Emit a TransactionDeposited event so that the rollup node can derive a deposit // transaction for this deposit. - emit TransactionDeposited(Constants.QKC_DEPOSITOR_ACCOUNT, _to, DEPOSIT_VERSION, opaqueData); + emit TransactionDeposited(from, _to, DEPOSIT_VERSION, opaqueData); } /// @notice set native deposit flag. Pass true to disable.