Skip to content

Commit

Permalink
feat: introduce SuperchainWETH and ETHLiquidity
Browse files Browse the repository at this point in the history
Introduces the SuperchainWETH and ETHLiquidity contracts. More
information about these contracts can be found in the OP Stack
Specs repository.
  • Loading branch information
smartcontracts committed Jun 28, 2024
1 parent 379973e commit bf61875
Show file tree
Hide file tree
Showing 12 changed files with 526 additions and 15 deletions.
15 changes: 15 additions & 0 deletions packages/contracts-bedrock/scripts/L2Genesis.s.sol
Original file line number Diff line number Diff line change
Expand Up @@ -245,6 +245,8 @@ contract L2Genesis is Deployer {
if (cfg.useInterop()) {
setCrossL2Inbox(); // 22
setL2ToL2CrossDomainMessenger(); // 23
setSuperchainWETH(); // 24
setETHLiquidity(); // 25
}
}

Expand Down Expand Up @@ -475,6 +477,19 @@ contract L2Genesis is Deployer {
_setImplementationCode(Predeploys.L2_TO_L2_CROSS_DOMAIN_MESSENGER);
}

/// @notice This predeploy is following the saftey invariant #1.
/// This contract has no initializer.
function setETHLiquidity() internal {
_setImplementationCode(Predeploys.ETH_LIQUIDITY);
vm.deal(Predeploys.ETH_LIQUIDITY, type(uint248).max);
}

/// @notice This predeploy is following the saftey invariant #1.
/// This contract has no initializer.
function setSuperchainWETH() internal {
_setImplementationCode(Predeploys.SUPERCHAIN_WETH);
}

/// @notice Sets all the preinstalls.
/// Warning: the creator-accounts of the preinstall contracts have 0 nonce values.
/// When performing a regular user-initiated contract-creation of a preinstall,
Expand Down
52 changes: 52 additions & 0 deletions packages/contracts-bedrock/src/L2/ETHLiquidity.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.15;

import { WETH98 } from "src/dispute/weth/WETH98.sol";
import { Predeploys } from "src/libraries/Predeploys.sol";
import { L1Block } from "src/L2/L1Block.sol";
import { SafeSend } from "src/universal/SafeSend.sol";
import { ISemver } from "src/universal/ISemver.sol";

import "src/libraries/errors/CommonErrors.sol";

/// @title ETHLiquidity
/// @notice The ETHLiquidity contract allows other contracts to access ETH liquidity without
/// needing to modify the EVM to generate new ETH.
contract ETHLiquidity {
/// @notice Emitted when an address burns ETH liquidity.
event LiquidityBurned(address indexed caller, uint256 value);

/// @notice Emitted when an address mints ETH liquidity.
event LiquidityMinted(address indexed caller, uint256 value);

/// @notice Semantic version.
/// @custom:semver 1.0.0
string public constant version = "1.0.0";

/// @notice Throws if the caller is not authorized.
modifier onlyAuthorized() {
// Only the SuperchainWETH contract is authorized to call this contract for now. Other
// contracts may be allowed to call this contract in the future so we're using a generic
// name for the modifier.
if (msg.sender != Predeploys.SUPERCHAIN_WETH) revert Unauthorized();
_;
}

/// @notice Throws this chain is using a custom gas token.
modifier notCustomGasToken() {
if (L1Block(Predeploys.L1_BLOCK_ATTRIBUTES).isCustomGasToken()) revert NotCustomGasToken();
_;
}

/// @notice Allows an address to lock ETH liquidity into this contract.
function burn() external payable onlyAuthorized notCustomGasToken {
emit LiquidityBurned(msg.sender, msg.value);
}

/// @notice Allows an address to unlock ETH liquidity from this contract.
/// @param _amount The amount of liquidity to unlock.
function mint(uint256 _amount) external onlyAuthorized notCustomGasToken {
new SafeSend{ value: _amount }(payable(msg.sender));
emit LiquidityMinted(msg.sender, _amount);
}
}
93 changes: 93 additions & 0 deletions packages/contracts-bedrock/src/L2/SuperchainWETH.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.15;

import { WETH98 } from "src/dispute/weth/WETH98.sol";
import { Predeploys } from "src/libraries/Predeploys.sol";
import { L1Block } from "src/L2/L1Block.sol";
import { IL2ToL2CrossDomainMessenger } from "src/L2/IL2ToL2CrossDomainMessenger.sol";
import { ETHLiquidity } from "src/L2/ETHLiquidity.sol";
import { ISemver } from "src/universal/ISemver.sol";

import "src/libraries/errors/CommonErrors.sol";

/// @title SuperchainWETH
/// @notice SuperchainWETH is a version of WETH that can be freely transfered between chains within
/// the superchain. SuperchainWETH can be converted into native ETH on chains that do not
// use a custom gas token.
contract SuperchainWETH is WETH98, ISemver {
/// @notice Semantic version.
/// @custom:semver 1.0.0
string public constant version = "1.0.0";

/// @notice Throws this chain is using a custom gas token.
modifier notCustomGasToken() {
if (L1Block(Predeploys.L1_BLOCK_ATTRIBUTES).isCustomGasToken()) revert OnlyCustomGasToken();
_;
}

/// @inheritdoc WETH98
function deposit() public payable override notCustomGasToken {
super.deposit();
}

/// @inheritdoc WETH98
function withdraw(uint256 wad) public override notCustomGasToken {
super.withdraw(wad);
}

/// TODO: .inheritdoc ISuperchainERC20
function sendERC20(uint256 wad, uint256 chainId) external {
sendERC20To(msg.sender, wad, chainId);
}

/// TODO: .inheritdoc ISuperchainERC20
function sendERC20To(address dst, uint256 wad, uint256 chainId) public {
// Burn from user's balance.
_burn(msg.sender, wad);

// Burn to ETHLiquidity contract.
if (!L1Block(Predeploys.L1_BLOCK_ATTRIBUTES).isCustomGasToken()) {
ETHLiquidity(Predeploys.ETH_LIQUIDITY).burn{ value: wad }();
}

// Send message to other chain.
IL2ToL2CrossDomainMessenger(Predeploys.L2_TO_L2_CROSS_DOMAIN_MESSENGER).sendMessage({
_destination: chainId,
_target: address(this),
_message: abi.encodeCall(this.finalizeSendERC20, (dst, wad))
});
}

/// TODO: .inheritdoc ISuperchainERC20
function finalizeSendERC20(address dst, uint256 wad) external {
// Receive message from other chain.
IL2ToL2CrossDomainMessenger messenger = IL2ToL2CrossDomainMessenger(Predeploys.L2_TO_L2_CROSS_DOMAIN_MESSENGER);
if (msg.sender != address(messenger)) revert Unauthorized();
if (messenger.crossDomainMessageSender() != address(this)) revert Unauthorized();

// Mint from ETHLiquidity contract.
if (!L1Block(Predeploys.L1_BLOCK_ATTRIBUTES).isCustomGasToken()) {
ETHLiquidity(Predeploys.ETH_LIQUIDITY).mint(wad);
}

// Mint to user's balance.
_mint(dst, wad);
}

/// @notice Mints WETH to an address.
/// @param guy The address to mint WETH to.
/// @param wad The amount of WETH to mint.
function _mint(address guy, uint256 wad) internal {
balanceOf[guy] += wad;
emit Transfer(address(0), guy, wad);
}

/// @notice Burns WETH from an address.
/// @param guy The address to burn WETH from.
/// @param wad The amount of WETH to burn.
function _burn(address guy, uint256 wad) internal {
require(balanceOf[guy] >= wad);
balanceOf[guy] -= wad;
emit Transfer(guy, address(0), wad);
}
}
2 changes: 1 addition & 1 deletion packages/contracts-bedrock/src/dispute/weth/WETH98.sol
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ contract WETH98 is IWETH {
}

/// @inheritdoc IWETH
function deposit() public payable {
function deposit() public payable virtual {
balanceOf[msg.sender] += msg.value;
emit Deposit(msg.sender, msg.value);
}
Expand Down
11 changes: 10 additions & 1 deletion packages/contracts-bedrock/src/libraries/Predeploys.sol
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,12 @@ library Predeploys {
/// @notice Address of the L2ToL2CrossDomainMessenger predeploy.
address internal constant L2_TO_L2_CROSS_DOMAIN_MESSENGER = 0x4200000000000000000000000000000000000023;

/// @notice Address of the SuperchainWETH predeploy.
address internal constant SUPERCHAIN_WETH = 0x4200000000000000000000000000000000000024;

/// @notice Address of the ETHLiquidty predeploy.
address internal constant ETH_LIQUIDITY = 0x4200000000000000000000000000000000000025;

/// @notice Returns the name of the predeploy at the given address.
function getName(address _addr) internal pure returns (string memory out_) {
require(isPredeployNamespace(_addr), "Predeploys: address must be a predeploy");
Expand All @@ -115,6 +121,8 @@ library Predeploys {
if (_addr == LEGACY_ERC20_ETH) return "LegacyERC20ETH";
if (_addr == CROSS_L2_INBOX) return "CrossL2Inbox";
if (_addr == L2_TO_L2_CROSS_DOMAIN_MESSENGER) return "L2ToL2CrossDomainMessenger";
if (_addr == SUPERCHAIN_WETH) return "SuperchainWETH";
if (_addr == ETH_LIQUIDITY) return "ETHLiquidity";
revert("Predeploys: unnamed predeploy");
}

Expand All @@ -131,7 +139,8 @@ library Predeploys {
|| _addr == L2_ERC721_BRIDGE || _addr == L1_BLOCK_ATTRIBUTES || _addr == L2_TO_L1_MESSAGE_PASSER
|| _addr == OPTIMISM_MINTABLE_ERC721_FACTORY || _addr == PROXY_ADMIN || _addr == BASE_FEE_VAULT
|| _addr == L1_FEE_VAULT || _addr == SCHEMA_REGISTRY || _addr == EAS || _addr == GOVERNANCE_TOKEN
|| (_useInterop && _addr == CROSS_L2_INBOX) || (_useInterop && _addr == L2_TO_L2_CROSS_DOMAIN_MESSENGER);
|| (_useInterop && _addr == CROSS_L2_INBOX) || (_useInterop && _addr == L2_TO_L2_CROSS_DOMAIN_MESSENGER)
|| (_useInterop && _addr == SUPERCHAIN_WETH) || (_useInterop && _addr == ETH_LIQUIDITY);
}

function isPredeployNamespace(address _addr) internal pure returns (bool) {
Expand Down
14 changes: 14 additions & 0 deletions packages/contracts-bedrock/src/libraries/errors/CommonErrors.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

/// @notice Error for an unauthorized CALLER.
error Unauthorized();

/// @notice Error for when a method is called that only works when using a custom gas token.
error OnlyCustomGasToken();

/// @notice Error for when a method is called that only works when NOT using a custom gas token.
error NotCustomGasToken();

/// @notice Error for when a transfer via call fails.
error TransferFailed();
14 changes: 3 additions & 11 deletions packages/contracts-bedrock/src/periphery/faucet/Faucet.sol
Original file line number Diff line number Diff line change
@@ -1,17 +1,9 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.15;

import { IFaucetAuthModule } from "./authmodules/IFaucetAuthModule.sol";
import { SafeCall } from "../../libraries/SafeCall.sol";

/// @title SafeSend
/// @notice Sends ETH to a recipient account without triggering any code.
contract SafeSend {
/// @param _recipient Account to send ETH to.
constructor(address payable _recipient) payable {
selfdestruct(_recipient);
}
}
import { IFaucetAuthModule } from "src/periphery/faucet/authmodules/IFaucetAuthModule.sol";
import { SafeCall } from "src/libraries/SafeCall.sol";
import { SafeSend } from "src/universal/SafeSend.sol";

/// @title Faucet
/// @notice Faucet contract that drips ETH to users.
Expand Down
11 changes: 11 additions & 0 deletions packages/contracts-bedrock/src/universal/SafeSend.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.15;

/// @title SafeSend
/// @notice Sends ETH to a recipient account without triggering any code.
contract SafeSend {
/// @param _recipient Account to send ETH to.
constructor(address payable _recipient) payable {
selfdestruct(_recipient);
}
}
Loading

0 comments on commit bf61875

Please sign in to comment.