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

Draft contracts for Optimism bridge integration #29

Draft
wants to merge 1 commit into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
138 changes: 138 additions & 0 deletions src/bridge/optimism/L1BobBridge.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.9;

import "@openzeppelin/contracts/utils/Address.sol";
import "../../interfaces/IMintableERC20.sol";
import "../../interfaces/IBurnableERC20.sol";
import "./interfaces/IL1ERC20Bridge.sol";
import "./interfaces/IL2ERC20Bridge.sol";
import "./libraries/CrossDomainEnabled.sol";

/**
* @title L1BobBridge
*/
contract L1BobBridge is IL1ERC20Bridge, CrossDomainEnabled {
address public immutable l2TokenBridge;
address public immutable l1Token;
address public immutable l2Token;

constructor(
address _l1Messenger,
address _l2TokenBridge,
address _l1Token,
address _l2Token
)
CrossDomainEnabled(_l1Messenger)
{
l2TokenBridge = _l2TokenBridge;
l1Token = _l1Token;
l2Token = _l2Token;
}

/**
* @dev Modifier requiring sender to be EOA. This check could be bypassed by a malicious
* contract via initcode, but it takes care of the user error we want to avoid.
*/
modifier onlyEOA() {
// Used to stop deposits from contracts (avoid accidentally lost tokens)
require(!Address.isContract(msg.sender), "Account not EOA");
_;
}

/**
* @inheritdoc IL1ERC20Bridge
*/
function depositERC20(
address _l1Token,
address _l2Token,
uint256 _amount,
uint32 _l2Gas,
bytes calldata _data
)
external
virtual
onlyEOA
{
_initiateERC20Deposit(_l1Token, _l2Token, msg.sender, msg.sender, _amount, _l2Gas, _data);
}

/**
* @inheritdoc IL1ERC20Bridge
*/
function depositERC20To(
address _l1Token,
address _l2Token,
address _to,
uint256 _amount,
uint32 _l2Gas,
bytes calldata _data
)
external
virtual
{
_initiateERC20Deposit(_l1Token, _l2Token, msg.sender, _to, _amount, _l2Gas, _data);
}

/**
* @dev Performs the logic for deposits by informing the L2 Deposited Token
* contract of the deposit and calling a handler to lock the L1 funds. (e.g. transferFrom)
*
* @param _l1Token Address of the L1 ERC20 we are depositing
* @param _l2Token Address of the L1 respective L2 ERC20
* @param _from Account to pull the deposit from on L1
* @param _to Account to give the deposit to on L2
* @param _amount Amount of the ERC20 to deposit.
* @param _l2Gas Gas limit required to complete the deposit on L2.
* @param _data Optional data to forward to L2. This data is provided
* solely as a convenience for external contracts. Aside from enforcing a maximum
* length, these contracts provide no guarantees about its content.
*/
function _initiateERC20Deposit(
address _l1Token,
address _l2Token,
address _from,
address _to,
uint256 _amount,
uint32 _l2Gas,
bytes calldata _data
)
internal
{
require(_l1Token == l1Token, "L1BobBridge: invalid l1Token");
require(_l2Token == l2Token, "L1BobBridge: invalid l2Token");

IBurnableERC20(_l1Token).burnFrom(_from, _amount);

// Construct calldata for _l2Token.finalizeDeposit(_to, _amount)
bytes memory message = abi.encodeWithSelector(
IL2ERC20Bridge.finalizeDeposit.selector, _l1Token, _l2Token, _from, _to, _amount, _data
);

// Send calldata into L2
sendCrossDomainMessage(l2TokenBridge, _l2Gas, message);

emit ERC20DepositInitiated(_l1Token, _l2Token, _from, _to, _amount, _data);
}

/**
* @inheritdoc IL1ERC20Bridge
*/
function finalizeERC20Withdrawal(
address _l1Token,
address _l2Token,
address _from,
address _to,
uint256 _amount,
bytes calldata _data
)
external
onlyFromCrossDomainAccount(l2TokenBridge)
{
require(_l1Token == l1Token, "L1BobBridge: invalid l1Token");
require(_l2Token == l2Token, "L1BobBridge: invalid l2Token");

IMintableERC20(_l1Token).mint(_to, _amount);

emit ERC20WithdrawalFinalized(_l1Token, _l2Token, _from, _to, _amount, _data);
}
}
113 changes: 113 additions & 0 deletions src/bridge/optimism/L2BobBridge.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.9;

import "@openzeppelin/contracts/utils/Address.sol";
import "../../interfaces/IMintableERC20.sol";
import "../../interfaces/IBurnableERC20.sol";
import "./interfaces/IL1ERC20Bridge.sol";
import "./interfaces/IL2ERC20Bridge.sol";
import "./libraries/CrossDomainEnabled.sol";

/**
* @title L2BobBridge
*/
contract L2BobBridge is IL2ERC20Bridge, CrossDomainEnabled {
address public immutable l1TokenBridge;
address public immutable l1Token;
address public immutable l2Token;

constructor(
address _l2Messenger,
address _l1TokenBridge,
address _l1Token,
address _l2Token
)
CrossDomainEnabled(_l2Messenger)
{
l1TokenBridge = _l1TokenBridge;
l1Token = _l1Token;
l2Token = _l2Token;
}

/**
* @inheritdoc IL2ERC20Bridge
*/
function withdraw(address _l2Token, uint256 _amount, uint32 _l1Gas, bytes calldata _data) external virtual {
_initiateWithdrawal(_l2Token, msg.sender, msg.sender, _amount, _l1Gas, _data);
}

/**
* @inheritdoc IL2ERC20Bridge
*/
function withdrawTo(
address _l2Token,
address _to,
uint256 _amount,
uint32 _l1Gas,
bytes calldata _data
)
external
virtual
{
_initiateWithdrawal(_l2Token, msg.sender, _to, _amount, _l1Gas, _data);
}

/**
* @dev Performs the logic for withdrawals by burning the token and informing
* the L1 token Gateway of the withdrawal.
* @param _l2Token Address of L2 token where withdrawal is initiated.
* @param _from Account to pull the withdrawal from on L2.
* @param _to Account to give the withdrawal to on L1.
* @param _amount Amount of the token to withdraw.
* @param _l1Gas Unused, but included for potential forward compatibility considerations.
* @param _data Optional data to forward to L1. This data is provided
* solely as a convenience for external contracts. Aside from enforcing a maximum
* length, these contracts provide no guarantees about its content.
*/
function _initiateWithdrawal(
address _l2Token,
address _from,
address _to,
uint256 _amount,
uint32 _l1Gas,
bytes calldata _data
)
internal
{
require(_l2Token == l2Token, "L2BobBridge: invalid l2Token");

IBurnableERC20(_l2Token).burnFrom(msg.sender, _amount);

bytes memory message = abi.encodeWithSelector(
IL1ERC20Bridge.finalizeERC20Withdrawal.selector, l1Token, _l2Token, _from, _to, _amount, _data
);

// Send message up to L1 bridge
sendCrossDomainMessage(l1TokenBridge, _l1Gas, message);

emit WithdrawalInitiated(l1Token, _l2Token, msg.sender, _to, _amount, _data);
}

/**
* @inheritdoc IL2ERC20Bridge
*/
function finalizeDeposit(
address _l1Token,
address _l2Token,
address _from,
address _to,
uint256 _amount,
bytes calldata _data
)
external
virtual
onlyFromCrossDomainAccount(l1TokenBridge)
{
require(_l1Token == l1Token, "L2BobBridge: invalid l1Token");
require(_l2Token == l2Token, "L2BobBridge: invalid l2Token");

IMintableERC20(_l2Token).mint(_to, _amount);

emit DepositFinalized(_l1Token, _l2Token, _from, _to, _amount, _data);
}
}
96 changes: 96 additions & 0 deletions src/bridge/optimism/interfaces/IL1ERC20Bridge.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
// SPDX-License-Identifier: MIT

pragma solidity >0.5.0 <0.9.0;

/**
* @title IL1ERC20Bridge
*/
interface IL1ERC20Bridge {
event ERC20DepositInitiated(
address indexed _l1Token,
address indexed _l2Token,
address indexed _from,
address _to,
uint256 _amount,
bytes _data
);

event ERC20WithdrawalFinalized(
address indexed _l1Token,
address indexed _l2Token,
address indexed _from,
address _to,
uint256 _amount,
bytes _data
);

/**
* @dev get the address of the corresponding L2 bridge contract.
* @return Address of the corresponding L2 bridge contract.
*/
function l2TokenBridge() external returns (address);

/**
* @dev deposit an amount of the ERC20 to the caller's balance on L2.
* @param _l1Token Address of the L1 ERC20 we are depositing
* @param _l2Token Address of the L1 respective L2 ERC20
* @param _amount Amount of the ERC20 to deposit
* @param _l2Gas Gas limit required to complete the deposit on L2.
* @param _data Optional data to forward to L2. This data is provided
* solely as a convenience for external contracts. Aside from enforcing a maximum
* length, these contracts provide no guarantees about its content.
*/
function depositERC20(
address _l1Token,
address _l2Token,
uint256 _amount,
uint32 _l2Gas,
bytes calldata _data
)
external;

/**
* @dev deposit an amount of ERC20 to a recipient's balance on L2.
* @param _l1Token Address of the L1 ERC20 we are depositing
* @param _l2Token Address of the L1 respective L2 ERC20
* @param _to L2 address to credit the withdrawal to.
* @param _amount Amount of the ERC20 to deposit.
* @param _l2Gas Gas limit required to complete the deposit on L2.
* @param _data Optional data to forward to L2. This data is provided
* solely as a convenience for external contracts. Aside from enforcing a maximum
* length, these contracts provide no guarantees about its content.
*/
function depositERC20To(
address _l1Token,
address _l2Token,
address _to,
uint256 _amount,
uint32 _l2Gas,
bytes calldata _data
)
external;

/**
* @dev Complete a withdrawal from L2 to L1, and credit funds to the recipient's balance of the
* L1 ERC20 token.
* This call will fail if the initialized withdrawal from L2 has not been finalized.
*
* @param _l1Token Address of L1 token to finalizeWithdrawal for.
* @param _l2Token Address of L2 token where withdrawal was initiated.
* @param _from L2 address initiating the transfer.
* @param _to L1 address to credit the withdrawal to.
* @param _amount Amount of the ERC20 to deposit.
* @param _data Data provided by the sender on L2. This data is provided
* solely as a convenience for external contracts. Aside from enforcing a maximum
* length, these contracts provide no guarantees about its content.
*/
function finalizeERC20Withdrawal(
address _l1Token,
address _l2Token,
address _from,
address _to,
uint256 _amount,
bytes calldata _data
)
external;
}
Loading