-
Notifications
You must be signed in to change notification settings - Fork 70
feat(l2): implement ERC20 bridge #3241
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
base: main
Are you sure you want to change the base?
Conversation
Co-authored-by: Tomás Grüner <[email protected]>
This reverts commit 2273223.
Lines of code reportTotal lines added: Detailed view
|
Co-authored-by: Manuel Iñaki Bilbao <[email protected]>
d2de067
to
18013bc
Compare
/// @notice How much of each L1 token was deposited to each L2 token. | ||
/// @dev Stored as L1 -> L2 -> amount | ||
/// @dev Prevents L2 tokens from faking their L1 address and stealing tokens | ||
mapping (address => mapping (address => uint256)) public depositsERC20; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should consider moving this to the L2 for reduced gas costs. Opened an issue for that: #3299
use genesis_tool::genesis::write_genesis_as_json; | ||
mod cli; | ||
mod error; | ||
|
||
fn main() -> Result<(), SystemContractsUpdaterError> { | ||
let opts = SystemContractsUpdaterOptions::parse(); | ||
compile_contract(&opts.contracts_path, "src/l2/CommonBridgeL2.sol", true)?; | ||
compile_contract(&opts.contracts_path, "src/l2/L1Messenger.sol", true)?; | ||
compile_contract(&opts.contracts_path, "src/l2/L2ToL1Messenger.sol", true)?; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can this contract change in the future? Consider this scenario:
- You update the code, this yields a new bytecode.
- You then add the bytecode to the genesis file like what happens below, on lines 42-45.
If the contract was already deployed, this would result in a different genesis file from the original one, right?
Of course, feel free to correct me.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It would result in a different genesis, but that is intended. The purpose of system_contracts_updater is to update the genesis file with the new bytecode for the system contracts.
As for the contract, it could change albeit very rarely. If you wanted, for example, to allow submitting signed messages on behalf of other users.
} | ||
} | ||
|
||
pub fn download_contract_deps(contracts_path: &Path) -> Result<(), GitError> { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not quite agree putting this in the SDK. @ilitteri what do you think?
import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; | ||
import "../l2/interfaces/IERC20L2.sol"; | ||
|
||
/// @title OnChainProposer contract. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is incorrect
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed in 06232b5.
/// @author LambdaClass | ||
contract TestTokenL2 is ERC20, IERC20L2 { | ||
address public L1_TOKEN = address(0); | ||
address public constant BRIDGE = 0x000000000000000000000000000000000000FFff; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should we put the bridge address on the interface so token operators don't mess up?
IERC20(tokenL1).safeTransferFrom(msg.sender, address(this), amount); | ||
|
||
depositsERC20[tokenL1][tokenL2] += amount; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should we take care of reentrancy here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's remove that by swapping these two lines (the external call and storage access), just to be sure.
I don't think any attacks are possible, though, since here we're not sending any value out of the bridge. Any token transfers that result in recursive deposits will only further increase this amount, checking that the transfer doesn't revert any of those times.
try this._mintERC20(tokenL1, tokenL2, destination, amount) { | ||
} catch { | ||
_withdrawERC20(tokenL1, tokenL2, destination, amount); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we use if (!success)
instead of try-catch
?
_withdrawERC20(tokenL1, tokenL2, destination, amount); | ||
} | ||
|
||
function _withdrawERC20(address tokenL1, address tokenL2, address destination, uint256 amount) private { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would use a better name here. Something like sendERC20WithdrawToL1
(there should be better ones)
IERC20(tokenL1).safeTransfer(msg.sender, claimedAmount); | ||
} | ||
|
||
function _claimWithdrawal( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's find better names for this functions, we have claimWithdrawal
, _claimWithdrawal
, claimWithdrawalERC20
, _claimWithdrawalProof
and _verifyWithdrawProof
(if I'm not missing anyone else). It's hard to understand what does each one do
function _deposit(DepositValues memory depositValues) private { | ||
require(msg.value > 0, "CommonBridge: amount to deposit is zero"); | ||
function _deposit(address from, DepositValues memory depositValues) private { | ||
depositsERC20[ETH_TOKEN][ETH_TOKEN] += msg.value; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This line is confusing since the function is also being called on non-ETH deposits. We should move it to the caller's side
Motivation
We want to be able to bridge ERC20 tokens.
Description
The inner workings are explained on #3223