Skip to content

Commit

Permalink
feat: usdc bridge & role manager
Browse files Browse the repository at this point in the history
  • Loading branch information
sander2 committed Apr 11, 2024
1 parent 12996ea commit a80a286
Show file tree
Hide file tree
Showing 6 changed files with 325 additions and 381 deletions.
10 changes: 10 additions & 0 deletions packages/contracts-bedrock/src/L1/IPartialUsdc.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
// SPDX-License-Identifier: APACHE-2.0
pragma solidity 0.8.15;

import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";

/// @notice The parts of the USDC interface we need.
interface IPartialUsdc is IERC20 {
function mint(address _to, uint256 _amount) external;
function burn(uint256 _amount) external;
}
165 changes: 39 additions & 126 deletions packages/contracts-bedrock/src/L1/L1UsdcBridge.sol
Original file line number Diff line number Diff line change
Expand Up @@ -5,35 +5,16 @@ import { Predeploys } from "src/libraries/Predeploys.sol";
import { UsdcBridge } from "src/universal/UsdcBridge.sol";
import { ISemver } from "src/universal/ISemver.sol";
import { CrossDomainMessenger } from "src/universal/CrossDomainMessenger.sol";
import { SuperchainConfig } from "src/L1/SuperchainConfig.sol";
import { Constants } from "src/libraries/Constants.sol";
import { IPartialUsdc } from "src/L1/IPartialUsdc.sol";

/// @custom:proxied
/// @title L1UsdcBridge
/// @notice The L1UsdcBridge is responsible for transfering ETH and ERC20 tokens between L1 and
/// @notice The L1UsdcBridge is responsible for transfering USDC tokens between L1 and
/// L2. In the case that an ERC20 token is native to L1, it will be escrowed within this
/// contract. If the ERC20 token is native to L2, it will be burnt. Before Bedrock, ETH was
/// stored within this contract. After Bedrock, ETH is instead stored inside the
/// OptimismPortal contract.
/// 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
/// not limited to: tokens with transfer fees, rebasing tokens, and tokens with blocklists.
/// contract. If the ERC20 token is native to L2, it will be burnt.
contract L1UsdcBridge is UsdcBridge, ISemver {
/// @custom:legacy
/// @notice Emitted whenever a deposit of ETH from L1 into L2 is initiated.
/// @param from Address of the depositor.
/// @param to Address of the recipient on L2.
/// @param amount Amount of ETH deposited.
/// @param extraData Extra data attached to the deposit.
event ETHDepositInitiated(address indexed from, address indexed to, uint256 amount, bytes extraData);

/// @custom:legacy
/// @notice Emitted whenever a withdrawal of ETH from L2 to L1 is finalized.
/// @param from Address of the withdrawer.
/// @param to Address of the recipient on L1.
/// @param amount Amount of ETH withdrawn.
/// @param extraData Extra data attached to the withdrawal.
event ETHWithdrawalFinalized(address indexed from, address indexed to, uint256 amount, bytes extraData);
/// @notice The address that is allowed to burn the held USDC.
address public burner;

/// @custom:legacy
/// @notice Emitted whenever an ERC20 deposit is initiated.
Expand Down Expand Up @@ -73,58 +54,49 @@ contract L1UsdcBridge is UsdcBridge, ISemver {
/// @custom:semver 2.1.0
string public constant version = "2.1.0";

/// @notice Address of the SuperchainConfig contract.
SuperchainConfig public superchainConfig;

/// @notice Constructs the L1UsdcBridge contract.
constructor() UsdcBridge() {
initialize({ _messenger: CrossDomainMessenger(address(0)), _superchainConfig: SuperchainConfig(address(0)) });
}

/// @notice Initializer.
/// @param _messenger Contract for the CrossDomainMessenger on this network.
/// @param _superchainConfig Contract for the SuperchainConfig on this network.
function initialize(CrossDomainMessenger _messenger, SuperchainConfig _superchainConfig) public initializer {
superchainConfig = _superchainConfig;
constructor() UsdcBridge() { }

/// @notice Initializes the contract.
/// @param _messenger The cross domain messenger proxy.
/// @param _otherBridge The L2 bridge address.
/// @param _l1Usdc The ERC20 address on the L1.
/// @param _l2Usdc The ERC20 address on the L2.
/// @param _owner The initial owner of this contract.
function initialize(
CrossDomainMessenger _messenger,
UsdcBridge _otherBridge,
address _l1Usdc,
address _l2Usdc,
address _owner
)
public
initializer
{
__UsdcBridge_init({
_messenger: _messenger,
_otherBridge: UsdcBridge(payable(Predeploys.L2_STANDARD_BRIDGE))
_otherBridge: _otherBridge,
_l1Usdc: _l1Usdc,
_l2Usdc: _l2Usdc,
_owner: _owner
});
}

/// @inheritdoc UsdcBridge
function paused() public view override returns (bool) {
return superchainConfig.paused();
}

/// @notice Allows EOAs to bridge ETH by sending directly to the bridge.
receive() external payable override onlyEOA {
_initiateETHDeposit(msg.sender, msg.sender, RECEIVE_DEFAULT_GAS_LIMIT, bytes(""));
/// @notice Whitelists an account that will be able to burn the held usdc.
/// This function is callable only once.
function whitelistBurner(address _burner) external onlyOwner {
require(_burner != address(0), "Burner address can not be zero");
require(burner == address(0), "Burner address already set");
burner = _burner;
}

/// @custom:legacy
/// @notice Deposits some amount of ETH into the sender's account on L2.
/// @param _minGasLimit Minimum gas limit for the deposit message on L2.
/// @param _extraData Optional data to forward to L2.
/// 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.
function depositETH(uint32 _minGasLimit, bytes calldata _extraData) external payable onlyEOA {
_initiateETHDeposit(msg.sender, msg.sender, _minGasLimit, _extraData);
}
/// @notice Burns all the l1 token held by this contract.
function burnLockedUSDC() external {
require(msg.sender == burner, "Not whitelisted");

/// @custom:legacy
/// @notice Deposits some amount of ETH into a target account on L2.
/// Note that if ETH is sent to a contract on L2 and the call fails, then that ETH will
/// be locked in the L2UsdcBridge. ETH may be recoverable if the call can be
/// successfully replayed by increasing the amount of gas supplied to the call. If the
/// call will fail for any amount of gas, then the ETH will be locked permanently.
/// @param _to Address of the recipient on L2.
/// @param _minGasLimit Minimum gas limit for the deposit message on L2.
/// @param _extraData Optional data to forward to L2.
/// 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.
function depositETHTo(address _to, uint32 _minGasLimit, bytes calldata _extraData) external payable {
_initiateETHDeposit(msg.sender, _to, _minGasLimit, _extraData);
IPartialUsdc token = IPartialUsdc(l1Usdc);
uint256 balance = token.balanceOf(address(this));
token.burn(balance);
}

/// @custom:legacy
Expand Down Expand Up @@ -174,24 +146,6 @@ contract L1UsdcBridge is UsdcBridge, ISemver {
_initiateERC20Deposit(_l1Token, _l2Token, msg.sender, _to, _amount, _minGasLimit, _extraData);
}

/// @custom:legacy
/// @notice Finalizes a withdrawal of ETH from L2.
/// @param _from Address of the withdrawer on L2.
/// @param _to Address of the recipient on L1.
/// @param _amount Amount of ETH to withdraw.
/// @param _extraData Optional data forwarded from L2.
function finalizeETHWithdrawal(
address _from,
address _to,
uint256 _amount,
bytes calldata _extraData
)
external
payable
{
finalizeBridgeETH(_from, _to, _amount, _extraData);
}

/// @custom:legacy
/// @notice Finalizes a withdrawal of ERC20 tokens from L2.
/// @param _l1Token Address of the token on L1.
Expand Down Expand Up @@ -220,15 +174,6 @@ contract L1UsdcBridge is UsdcBridge, ISemver {
return address(otherBridge);
}

/// @notice Internal function for initiating an ETH deposit.
/// @param _from Address of the sender on L1.
/// @param _to Address of the recipient on L2.
/// @param _minGasLimit Minimum gas limit for the deposit message on L2.
/// @param _extraData Optional data to forward to L2.
function _initiateETHDeposit(address _from, address _to, uint32 _minGasLimit, bytes memory _extraData) internal {
_initiateBridgeETH(_from, _to, msg.value, _minGasLimit, _extraData);
}

/// @notice Internal function for initiating an ERC20 deposit.
/// @param _l1Token Address of the L1 token being deposited.
/// @param _l2Token Address of the corresponding token on L2.
Expand All @@ -251,38 +196,6 @@ contract L1UsdcBridge is UsdcBridge, ISemver {
_initiateBridgeERC20(_l1Token, _l2Token, _from, _to, _amount, _minGasLimit, _extraData);
}

/// @inheritdoc UsdcBridge
/// @notice Emits the legacy ETHDepositInitiated event followed by the ETHBridgeInitiated event.
/// This is necessary for backwards compatibility with the legacy bridge.
function _emitETHBridgeInitiated(
address _from,
address _to,
uint256 _amount,
bytes memory _extraData
)
internal
override
{
emit ETHDepositInitiated(_from, _to, _amount, _extraData);
super._emitETHBridgeInitiated(_from, _to, _amount, _extraData);
}

/// @inheritdoc UsdcBridge
/// @notice Emits the legacy ERC20DepositInitiated event followed by the ERC20BridgeInitiated
/// event. This is necessary for backwards compatibility with the legacy bridge.
function _emitETHBridgeFinalized(
address _from,
address _to,
uint256 _amount,
bytes memory _extraData
)
internal
override
{
emit ETHWithdrawalFinalized(_from, _to, _amount, _extraData);
super._emitETHBridgeFinalized(_from, _to, _amount, _extraData);
}

/// @inheritdoc UsdcBridge
/// @notice Emits the legacy ERC20WithdrawalFinalized event followed by the ERC20BridgeFinalized
/// event. This is necessary for backwards compatibility with the legacy bridge.
Expand Down
90 changes: 22 additions & 68 deletions packages/contracts-bedrock/src/L2/L2UsdcBridge.sol
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,13 @@ pragma solidity 0.8.15;
import { Predeploys } from "src/libraries/Predeploys.sol";
import { UsdcBridge } from "src/universal/UsdcBridge.sol";
import { ISemver } from "src/universal/ISemver.sol";
import { OptimismMintableERC20 } from "src/universal/OptimismMintableERC20.sol";
import { CrossDomainMessenger } from "src/universal/CrossDomainMessenger.sol";
import { Constants } from "src/libraries/Constants.sol";

/// @custom:proxied
/// @custom:predeploy 0x4200000000000000000000000000000000000010
/// @title L2UsdcBridge
/// @notice The L2UsdcBridge is responsible for transfering ETH and ERC20 tokens between L1 and
/// L2. In the case that an ERC20 token is native to L2, it will be escrowed within this
/// contract. If the ERC20 token is native to L1, it will be burnt.
/// @notice The L2UsdcBridge is responsible for transfering USDC tokens between L1 and
/// L2.
/// 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
/// not limited to: tokens with transfer fees, rebasing tokens, and tokens with blocklists.
Expand Down Expand Up @@ -56,30 +53,26 @@ contract L2UsdcBridge is UsdcBridge, ISemver {
string public constant version = "1.8.0";

/// @notice Constructs the L2UsdcBridge contract.
constructor() UsdcBridge() {
initialize({ _otherBridge: UsdcBridge(payable(address(0))) });
}

/// @notice Initializer.
/// @param _otherBridge Contract for the corresponding bridge on the other chain.
function initialize(UsdcBridge _otherBridge) public initializer {
constructor() UsdcBridge() { }

/// @notice Initializes the contract.
/// @param _otherBridge The L1 bridge address.
/// @param _l1Usdc The ERC20 address on the L1.
/// @param _l2Usdc The ERC20 address on the L2.
/// @param _owner The initial owner of this contract.
function initialize(UsdcBridge _otherBridge, address _l1Usdc, address _l2Usdc, address _owner) public initializer {
__UsdcBridge_init({
_messenger: CrossDomainMessenger(Predeploys.L2_CROSS_DOMAIN_MESSENGER),
_otherBridge: _otherBridge
_otherBridge: _otherBridge,
_l1Usdc: _l1Usdc,
_l2Usdc: _l2Usdc,
_owner: _owner
});
}

/// @notice Allows EOAs to bridge ETH by sending directly to the bridge.
receive() external payable override onlyEOA {
_initiateWithdrawal(
Predeploys.LEGACY_ERC20_ETH, msg.sender, msg.sender, msg.value, RECEIVE_DEFAULT_GAS_LIMIT, bytes("")
);
}

/// @custom:legacy
/// @notice Initiates a withdrawal from L2 to L1.
/// This function only works with OptimismMintableERC20 tokens or ether. Use the
/// `bridgeERC20` function to bridge native L2 tokens to L1.
/// This function only works with Usdc
/// @param _l2Token Address of the L2 token to withdraw.
/// @param _amount Amount of the L2 token to withdraw.
/// @param _minGasLimit Minimum gas limit to use for the transaction.
Expand All @@ -100,12 +93,7 @@ contract L2UsdcBridge is UsdcBridge, ISemver {

/// @custom:legacy
/// @notice Initiates a withdrawal from L2 to L1 to a target account on L1.
/// Note that if ETH is sent to a contract on L1 and the call fails, then that ETH will
/// be locked in the L1UsdcBridge. ETH may be recoverable if the call can be
/// successfully replayed by increasing the amount of gas supplied to the call. If the
/// call will fail for any amount of gas, then the ETH will be locked permanently.
/// This function only works with OptimismMintableERC20 tokens or ether. Use the
/// `bridgeERC20To` function to bridge native L2 tokens to L1.
/// This function only works for usdc.
/// @param _l2Token Address of the L2 token to withdraw.
/// @param _to Recipient account on L1.
/// @param _amount Amount of the L2 token to withdraw.
Expand All @@ -126,8 +114,7 @@ contract L2UsdcBridge is UsdcBridge, ISemver {
}

/// @custom:legacy
/// @notice Finalizes a deposit from L1 to L2. To finalize a deposit of ether, use address(0)
/// and the l1Token and the Legacy ERC20 ether predeploy address as the l2Token.
/// @notice Finalizes a deposit from L1 to L2.
/// @param _l1Token Address of the L1 token to deposit.
/// @param _l2Token Address of the corresponding L2 token.
/// @param _from Address of the depositor.
Expand All @@ -146,8 +133,8 @@ contract L2UsdcBridge is UsdcBridge, ISemver {
payable
virtual
{
if (_l1Token == address(0) && _l2Token == Predeploys.LEGACY_ERC20_ETH) {
finalizeBridgeETH(_from, _to, _amount, _extraData);
if (!_isL1Usdc(_l1Token) || !_isL2Usdc(_l2Token)) {
revert("Only USDC allowed");
} else {
finalizeBridgeERC20(_l2Token, _l1Token, _from, _to, _amount, _extraData);
}
Expand Down Expand Up @@ -178,46 +165,13 @@ contract L2UsdcBridge is UsdcBridge, ISemver {
)
internal
{
if (_l2Token == Predeploys.LEGACY_ERC20_ETH) {
_initiateBridgeETH(_from, _to, _amount, _minGasLimit, _extraData);
if (!_isL2Usdc(_l2Token)) {
revert("Only USDC allowed");
} else {
address l1Token = OptimismMintableERC20(_l2Token).l1Token();
_initiateBridgeERC20(_l2Token, l1Token, _from, _to, _amount, _minGasLimit, _extraData);
_initiateBridgeERC20(_l2Token, l1Usdc, _from, _to, _amount, _minGasLimit, _extraData);
}
}

/// @notice Emits the legacy WithdrawalInitiated event followed by the ETHBridgeInitiated event.
/// This is necessary for backwards compatibility with the legacy bridge.
/// @inheritdoc UsdcBridge
function _emitETHBridgeInitiated(
address _from,
address _to,
uint256 _amount,
bytes memory _extraData
)
internal
override
{
emit WithdrawalInitiated(address(0), Predeploys.LEGACY_ERC20_ETH, _from, _to, _amount, _extraData);
super._emitETHBridgeInitiated(_from, _to, _amount, _extraData);
}

/// @notice Emits the legacy DepositFinalized event followed by the ETHBridgeFinalized event.
/// This is necessary for backwards compatibility with the legacy bridge.
/// @inheritdoc UsdcBridge
function _emitETHBridgeFinalized(
address _from,
address _to,
uint256 _amount,
bytes memory _extraData
)
internal
override
{
emit DepositFinalized(address(0), Predeploys.LEGACY_ERC20_ETH, _from, _to, _amount, _extraData);
super._emitETHBridgeFinalized(_from, _to, _amount, _extraData);
}

/// @notice Emits the legacy WithdrawalInitiated event followed by the ERC20BridgeInitiated
/// event. This is necessary for backwards compatibility with the legacy bridge.
/// @inheritdoc UsdcBridge
Expand Down
Loading

0 comments on commit a80a286

Please sign in to comment.