Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat/usdc bridge #1

Open
wants to merge 11 commits into
base: develop
Choose a base branch
from
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;
}
239 changes: 239 additions & 0 deletions packages/contracts-bedrock/src/L1/L1UsdcBridge.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,239 @@
// SPDX-License-Identifier: MIT
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 { CrossDomainMessenger } from "src/universal/CrossDomainMessenger.sol";
import { IPartialUsdc } from "src/L1/IPartialUsdc.sol";

/// @custom:proxied
/// @title L1UsdcBridge
/// @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.
contract L1UsdcBridge is UsdcBridge, ISemver {
/// @notice The address that is allowed to burn the held USDC.
address public burner;

/// @custom:legacy
/// @notice Emitted whenever an ERC20 deposit is initiated.
/// @param l1Token Address of the token on L1.
/// @param l2Token Address of the corresponding token on L2.
/// @param from Address of the depositor.
/// @param to Address of the recipient on L2.
/// @param amount Amount of the ERC20 deposited.
/// @param extraData Extra data attached to the deposit.
event ERC20DepositInitiated(
address indexed l1Token,
address indexed l2Token,
address indexed from,
address to,
uint256 amount,
bytes extraData
);

/// @custom:legacy
/// @notice Emitted whenever an ERC20 withdrawal is finalized.
/// @param l1Token Address of the token on L1.
/// @param l2Token Address of the corresponding token on L2.
/// @param from Address of the withdrawer.
/// @param to Address of the recipient on L1.
/// @param amount Amount of the ERC20 withdrawn.
/// @param extraData Extra data attached to the withdrawal.
event ERC20WithdrawalFinalized(
address indexed l1Token,
address indexed l2Token,
address indexed from,
address to,
uint256 amount,
bytes extraData
);

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

/// @notice Constructs the L1UsdcBridge contract.
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: _otherBridge,
_l1Usdc: _l1Usdc,
_l2Usdc: _l2Usdc,
_owner: _owner
});
}

/// @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;
}

/// @notice Burns all the l1 token that is bridged to the l2.
function burnLockedUSDC() external {
require(msg.sender == burner, "Not whitelisted");

uint256 balance = deposits[l1Usdc][l2Usdc];
IPartialUsdc(l1Usdc).burn(balance);
}

/// @custom:legacy
/// @notice Deposits some amount of ERC20 tokens into the sender's account on L2.
/// @param _l1Token Address of the L1 token being deposited.
/// @param _l2Token Address of the corresponding token on L2.
/// @param _amount Amount of the ERC20 to deposit.
/// @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 depositERC20(
address _l1Token,
address _l2Token,
uint256 _amount,
uint32 _minGasLimit,
bytes calldata _extraData
)
external
virtual
onlyEOA
{
_initiateERC20Deposit(_l1Token, _l2Token, msg.sender, msg.sender, _amount, _minGasLimit, _extraData);
}

/// @custom:legacy
/// @notice Deposits some amount of ERC20 tokens into a target account on L2.
/// @param _l1Token Address of the L1 token being deposited.
/// @param _l2Token Address of the corresponding token on L2.
/// @param _to Address of the recipient on L2.
/// @param _amount Amount of the ERC20 to deposit.
/// @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 depositERC20To(
address _l1Token,
address _l2Token,
address _to,
uint256 _amount,
uint32 _minGasLimit,
bytes calldata _extraData
)
external
virtual
{
_initiateERC20Deposit(_l1Token, _l2Token, msg.sender, _to, _amount, _minGasLimit, _extraData);
}

/// @custom:legacy
/// @notice Finalizes a withdrawal of ERC20 tokens from L2.
/// @param _l1Token Address of the token on L1.
/// @param _l2Token Address of the corresponding token on L2.
/// @param _from Address of the withdrawer on L2.
/// @param _to Address of the recipient on L1.
/// @param _amount Amount of the ERC20 to withdraw.
/// @param _extraData Optional data forwarded from L2.
function finalizeERC20Withdrawal(
address _l1Token,
address _l2Token,
address _from,
address _to,
uint256 _amount,
bytes calldata _extraData
)
external
{
finalizeBridgeERC20(_l1Token, _l2Token, _from, _to, _amount, _extraData);
}

/// @custom:legacy
/// @notice Retrieves the access of the corresponding L2 bridge contract.
/// @return Address of the corresponding L2 bridge contract.
function l2TokenBridge() external view returns (address) {
return address(otherBridge);
}

/// @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.
/// @param _from Address of the sender on L1.
/// @param _to Address of the recipient on L2.
/// @param _amount Amount of the ERC20 to deposit.
/// @param _minGasLimit Minimum gas limit for the deposit message on L2.
/// @param _extraData Optional data to forward to L2.
function _initiateERC20Deposit(
address _l1Token,
address _l2Token,
address _from,
address _to,
uint256 _amount,
uint32 _minGasLimit,
bytes memory _extraData
)
internal
{
_initiateBridgeERC20(_l1Token, _l2Token, _from, _to, _amount, _minGasLimit, _extraData);
}

/// @inheritdoc UsdcBridge
/// @notice Emits the legacy ERC20WithdrawalFinalized event followed by the ERC20BridgeFinalized
/// event. This is necessary for backwards compatibility with the legacy bridge.
function _emitERC20BridgeInitiated(
address _localToken,
address _remoteToken,
address _from,
address _to,
uint256 _amount,
bytes memory _extraData
)
internal
override
{
emit ERC20DepositInitiated(_localToken, _remoteToken, _from, _to, _amount, _extraData);
super._emitERC20BridgeInitiated(_localToken, _remoteToken, _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.
function _emitERC20BridgeFinalized(
address _localToken,
address _remoteToken,
address _from,
address _to,
uint256 _amount,
bytes memory _extraData
)
internal
override
{
emit ERC20WithdrawalFinalized(_localToken, _remoteToken, _from, _to, _amount, _extraData);
super._emitERC20BridgeFinalized(_localToken, _remoteToken, _from, _to, _amount, _extraData);
}

/// @inheritdoc UsdcBridge
function _isCorrectUsdcTokenPair(address _localToken, address _remoteToken) internal view override returns (bool) {
return _isL1Usdc(_localToken) && _isL2Usdc(_remoteToken);
}
}
Loading