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

Base Token Bridge #1

Merged
merged 40 commits into from
Nov 20, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
9f209db
Add bridging functions
Jul 9, 2024
cfbdcc0
Block remoteToken == 0
Jul 15, 2024
130be81
Add init lib
Jul 18, 2024
ca1fc25
Mitigate domain persistence issue in test
Jul 19, 2024
f5c545b
Add bridge unit tests
Jul 19, 2024
99d8669
Use internal for common bridgeERC20 logic
Jul 22, 2024
544254c
Bubble up undecoded revert
Jul 22, 2024
8a366da
Add gov relay unit tests
Jul 22, 2024
2c39c49
Add escrow unit tests
Jul 22, 2024
f071a37
Test paused withdraw
Jul 23, 2024
43bcbb0
Add Deploy.s.sol
Jul 24, 2024
f0ffd35
Add testnet init script
Jul 24, 2024
12dc476
Add deposit/withdraw scripts
Jul 24, 2024
3c464fd
Complete README
Aug 5, 2024
b78c006
Add minGasLimit bound check
Aug 6, 2024
e98b020
Update dss-test
Aug 20, 2024
2ec7b56
Update deploy/mocks/ChainLog.sol
telome Aug 20, 2024
a2b29e2
Rearrange L2GovRelay test
Aug 20, 2024
9d82c7c
Update test/L1TokenBridge.t.sol
telome Aug 20, 2024
a9afb22
Update test/Integration.t.sol
telome Aug 20, 2024
0f71468
Add ,
Aug 20, 2024
1e289a3
Update test/L2TokenBridge.t.sol
telome Aug 20, 2024
2c9c30c
Update README.md
telome Aug 20, 2024
b7e2252
Update README.md
telome Aug 20, 2024
ca7cfe1
Update README.md
telome Aug 20, 2024
603dafa
Fix exportContracts issue
Aug 21, 2024
687d9bd
Use new dss-test functions
Aug 21, 2024
22bcee7
Use gas estimate multiplier for Deposit.s.sol
Aug 21, 2024
4b6cd2a
Update CI
Aug 23, 2024
bae891c
Address Cantina audit findings (#2)
telome Sep 2, 2024
6226a76
Add Cantina Audit Report (#3)
telome Sep 10, 2024
5037eb4
Add ChainSecurity Report (#4)
oldchili Sep 16, 2024
a78371b
Implement fileable escrow (#5)
sunbreak1211 Sep 17, 2024
a01b872
Remove unused function from interface
sunbreak1211 Sep 19, 2024
d0c8983
Add maxWithdraws to L2TokenBridge (#6)
telome Oct 1, 2024
0f93550
Upgradable L1TokenBridge and L2TokenBridge (#7)
telome Oct 8, 2024
16f33b5
Update CS audit report (#8)
telome Oct 14, 2024
262383f
Update sepolia deployment
Oct 15, 2024
fb4bfb1
Add Cantina report (#10)
oldchili Nov 13, 2024
8133c00
Add Certora specs (#9)
sunbreak1211 Nov 19, 2024
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
204 changes: 204 additions & 0 deletions src/L1TokenBridge.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,204 @@
// SPDX-License-Identifier: AGPL-3.0-or-later

// Copyright (C) 2024 Dai Foundation
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.

pragma solidity ^0.8.21;

interface TokenLike {
function transferFrom(address, address, uint256) external;
}

interface CrossDomainMessengerLike {
function xDomainMessageSender() external view returns (address);
function sendMessage(address _target, bytes calldata _message, uint32 _minGasLimit) external payable;
}

contract L1TokenBridge {
// --- storage variables ---

mapping(address => uint256) public wards;
mapping(address => address) public l1ToL2Token;
uint256 public isOpen = 1;
oldchili marked this conversation as resolved.
Show resolved Hide resolved

// --- immutables ---

address public immutable otherBridge;
address public immutable escrow;
CrossDomainMessengerLike public immutable messenger;

// --- events ---

event Rely(address indexed usr);
event Deny(address indexed usr);
event Closed();
event TokenSet(address indexed l1Token, address indexed l2Token);
event ERC20BridgeInitiated(
address indexed localToken,
address indexed remoteToken,
address indexed from,
address to,
uint256 amount,
bytes extraData
);
event ERC20BridgeFinalized(
address indexed localToken,
address indexed remoteToken,
address indexed from,
address to,
uint256 amount,
bytes extraData
);

// --- modifiers ---

modifier auth() {
require(wards[msg.sender] == 1, "L1TokenBridge/not-authorized");
_;
}

modifier onlyOtherBridge() {
require(
msg.sender == address(messenger) && messenger.xDomainMessageSender() == otherBridge,
"L1TokenBridge/not-from-other-bridge"
);
_;
}

// --- constructor ---

constructor(
address _otherBridge,
address _escrow,
address _messenger
) {
otherBridge = _otherBridge;
escrow = _escrow;
messenger = CrossDomainMessengerLike(_messenger);

wards[msg.sender] = 1;
emit Rely(msg.sender);
}

// --- administration ---

function rely(address usr) external auth {
wards[usr] = 1;
emit Rely(usr);
}

function deny(address usr) external auth {
wards[usr] = 0;
emit Deny(usr);
}

function close() external auth {
isOpen = 0;
emit Closed();
}

function registerToken(address l1Token, address l2Token) external auth {
l1ToL2Token[l1Token] = l2Token;
emit TokenSet(l1Token, l2Token);
}

// -- bridging --

/// @notice Sends ERC20 tokens to the sender's address on L2.
/// @param _localToken Address of the ERC20 on L1.
/// @param _remoteToken Address of the corresponding token on L2.
/// @param _amount Amount of local tokens to deposit.
/// @param _minGasLimit Minimum amount of gas that the bridge can be relayed with.
/// @param _extraData Extra data to be sent with the transaction. Note that the recipient will
/// not be triggered with this data, but it will be emitted and can be used
/// to identify the transaction.
function bridgeERC20(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see we define different interfaces on the dai bridge, for example - https://github.com/makerdao/optimism-dai-bridge/blob/master/contracts/l1/L1DAITokenBridge.sol#L102

This was a design decision right?
The standard bridge interface was as it is now at the time?

Copy link
Contributor Author

@telome telome Jul 15, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the new interface. The dai bridge was using the old interface, pre-bedrock. We might need to also support the old interface as the standard bridge does, but not 100% sure yet, so for now I've only included the methods for the new interface.

address _localToken,
address _remoteToken,
uint256 _amount,
uint32 _minGasLimit,
bytes calldata _extraData
) external {
require(msg.sender.code.length == 0, "L1TokenBridge/sender-not-eoa");
oldchili marked this conversation as resolved.
Show resolved Hide resolved
bridgeERC20To(_localToken, _remoteToken, msg.sender, _amount, _minGasLimit, _extraData);
}

/// @notice Sends ERC20 tokens to a receiver's address on L2.
/// @param _localToken Address of the ERC20 on L1.
/// @param _remoteToken Address of the corresponding token on L2.
/// @param _to Address of the receiver.
/// @param _amount Amount of local tokens to deposit.
/// @param _minGasLimit Minimum amount of gas that the bridge can be relayed with.
/// @param _extraData Extra data to be sent with the transaction. Note that the recipient will
/// not be triggered with this data, but it will be emitted and can be used
/// to identify the transaction.
function bridgeERC20To(
address _localToken,
address _remoteToken,
address _to,
uint256 _amount,
uint32 _minGasLimit,
bytes calldata _extraData
) public {
sunbreak1211 marked this conversation as resolved.
Show resolved Hide resolved
require(isOpen == 1, "L1TokenBridge/closed"); // do not allow initiating new xchain messages if bridge is closed
require(l1ToL2Token[_localToken] == _remoteToken, "L1TokenBridge/invalid-token");
oldchili marked this conversation as resolved.
Show resolved Hide resolved

TokenLike(_localToken).transferFrom(msg.sender, escrow, _amount);

emit ERC20BridgeInitiated(_localToken, _remoteToken, msg.sender, _to, _amount, _extraData);
sunbreak1211 marked this conversation as resolved.
Show resolved Hide resolved

messenger.sendMessage({
_target: address(otherBridge),
_message: abi.encodeWithSelector(
sunbreak1211 marked this conversation as resolved.
Show resolved Hide resolved
this.finalizeBridgeERC20.selector,
// Because this call will be executed on the remote chain, we reverse the order of
// the remote and local token addresses relative to their order in the
// finalizeBridgeERC20 function.
_remoteToken,
_localToken,
msg.sender,
_to,
_amount,
_extraData
),
_minGasLimit: _minGasLimit
});
}

/// @notice Finalizes an ERC20 bridge on L1. Can only be triggered by the L2TokenBridge.
/// @param _localToken Address of the ERC20 on L1.
/// @param _remoteToken Address of the corresponding token on L2.
/// @param _from Address of the sender.
/// @param _to Address of the receiver.
/// @param _amount Amount of the ERC20 being bridged.
/// @param _extraData Extra data to be sent with the transaction. Note that the recipient will
/// not be triggered with this data, but it will be emitted and can be used
/// to identify the transaction.
function finalizeBridgeERC20(
address _localToken,
address _remoteToken,
address _from,
address _to,
uint256 _amount,
bytes calldata _extraData
)
external
onlyOtherBridge
{
TokenLike(_localToken).transferFrom(escrow, _to, _amount);

emit ERC20BridgeFinalized(_localToken, _remoteToken, _from, _to, _amount, _extraData);
}
}
Loading