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

Open
wants to merge 38 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 5 commits
Commits
Show all changes
38 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
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
23 changes: 23 additions & 0 deletions deploy/L1TokenBridgeInstance.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
// SPDX-FileCopyrightText: © 2024 Dai Foundation <www.daifoundation.org>
// SPDX-License-Identifier: AGPL-3.0-or-later
//
// 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.0;

struct L1TokenBridgeInstance {
address govRelay;
address escrow;
address bridge;
}
23 changes: 23 additions & 0 deletions deploy/L2TokenBridgeInstance.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
// SPDX-FileCopyrightText: © 2024 Dai Foundation <www.daifoundation.org>
// SPDX-License-Identifier: AGPL-3.0-or-later
//
// 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.0;

struct L2TokenBridgeInstance {
address govRelay;
address bridge;
address spell;
}
79 changes: 79 additions & 0 deletions deploy/L2TokenBridgeSpell.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
// SPDX-FileCopyrightText: © 2024 Dai Foundation <www.daifoundation.org>
// SPDX-License-Identifier: AGPL-3.0-or-later
//
// 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.0;

interface L2GovRelayLike {
function l1GovernanceRelay() external view returns (address);
function messenger() external view returns (address);
}

interface L2TokenBridgeLike {
function isOpen() external view returns (uint256);
function otherBridge() external view returns (address);
function messenger() external view returns (address);
function rely(address) external;
function deny(address) external;
function close() external;
function registerToken(address, address) external;
}

interface AuthLike {
function rely(address usr) external;
}

// A reusable L2 spell to be used by the L2GovernanceRelay to exert admin control over L2TokenBridge
contract L2TokenBridgeSpell {
L2TokenBridgeLike public immutable l2Bridge;

constructor(address l2Bridge_) {
l2Bridge = L2TokenBridgeLike(l2Bridge_);
}

function rely(address usr) external { l2Bridge.rely(usr); }
function deny(address usr) external { l2Bridge.deny(usr); }
function close() external { l2Bridge.close(); }

function registerTokens(address[] memory l1Tokens, address[] memory l2Tokens) public {
for (uint256 i; i < l2Tokens.length;) {
l2Bridge.registerToken(l1Tokens[i], l2Tokens[i]);
AuthLike(l2Tokens[i]).rely(address(l2Bridge));
unchecked { ++i; }
}
}

function init(
address l2GovRelay_,
address l2Bridge_,
address l1GovRelay,
address l1Bridge,
address l2Messenger,
address[] calldata l1Tokens,
address[] calldata l2Tokens
) external {
L2GovRelayLike l2GovRelay = L2GovRelayLike(l2GovRelay_);

// sanity checks
require(address(l2Bridge) == l2Bridge_, "L2TokenBridgeSpell/l2-gateway-mismatch");
require(l2Bridge.isOpen() == 1, "L2TokenBridgeSpell/not-open");
require(l2Bridge.otherBridge() == l1Bridge, "L2TokenBridgeSpell/other-bridge-mismatch");
require(l2Bridge.messenger() == l2Messenger, "L2TokenBridgeSpell/l2-bridge-messenger-mismatch");
require(l2GovRelay.l1GovernanceRelay() == l1GovRelay, "L2TokenBridgeSpell/l1-gov-relay-mismatch");
require(l2GovRelay.messenger() == l2Messenger, "L2TokenBridgeSpell/l2-gov-relay-messenger-mismatch");

registerTokens(l1Tokens, l2Tokens);
}
}
57 changes: 57 additions & 0 deletions deploy/TokenBridgeDeploy.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
// SPDX-FileCopyrightText: © 2024 Dai Foundation <www.daifoundation.org>
// SPDX-License-Identifier: AGPL-3.0-or-later
//
// 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.0;

import { ScriptTools } from "dss-test/ScriptTools.sol";

import { L1TokenBridgeInstance } from "./L1TokenBridgeInstance.sol";
import { L2TokenBridgeInstance } from "./L2TokenBridgeInstance.sol";
import { L2TokenBridgeSpell } from "./L2TokenBridgeSpell.sol";
import { L1GovernanceRelay } from "src/L1GovernanceRelay.sol";
import { L2GovernanceRelay } from "src/L2GovernanceRelay.sol";
import { Escrow } from "src/Escrow.sol";
import { L1TokenBridge } from "src/L1TokenBridge.sol";
import { L2TokenBridge } from "src/L2TokenBridge.sol";

library TokenBridgeDeploy {
function deployL1Bridge(
address deployer,
address owner,
address l2GovRelay,
address l2Bridge,
address l1Messenger
) internal returns (L1TokenBridgeInstance memory l1BridgeInstance) {
l1BridgeInstance.govRelay = address(new L1GovernanceRelay(l2GovRelay, l1Messenger));
l1BridgeInstance.escrow = address(new Escrow());
l1BridgeInstance.bridge = address(new L1TokenBridge(l2Bridge, l1BridgeInstance.escrow, l1Messenger));
ScriptTools.switchOwner(l1BridgeInstance.govRelay, deployer, owner);
ScriptTools.switchOwner(l1BridgeInstance.escrow, deployer, owner);
ScriptTools.switchOwner(l1BridgeInstance.bridge, deployer, owner);
}

function deployL2Bridge(
address deployer,
address l1GovRelay,
address l1Bridge,
address l2Messenger
) internal returns (L2TokenBridgeInstance memory l2BridgeInstance) {
l2BridgeInstance.govRelay = address(new L2GovernanceRelay(l1GovRelay, l2Messenger));
l2BridgeInstance.bridge = address(new L2TokenBridge(l1Bridge, l2Messenger));
l2BridgeInstance.spell = address(new L2TokenBridgeSpell(l2BridgeInstance.bridge));
ScriptTools.switchOwner(l2BridgeInstance.bridge, deployer, l2BridgeInstance.govRelay);
}
}
106 changes: 106 additions & 0 deletions deploy/TokenBridgeInit.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
// SPDX-FileCopyrightText: © 2024 Dai Foundation <www.daifoundation.org>
// SPDX-License-Identifier: AGPL-3.0-or-later
//
// 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.0;

import { DssInstance } from "dss-test/MCD.sol";
import { L1TokenBridgeInstance } from "./L1TokenBridgeInstance.sol";
import { L2TokenBridgeInstance } from "./L2TokenBridgeInstance.sol";
import { L2TokenBridgeSpell } from "./L2TokenBridgeSpell.sol";

interface L1TokenBridgeLike {
function l1ToL2Token(address) external view returns (address);
function isOpen() external view returns (uint256);
function otherBridge() external view returns (address);
function escrow() external view returns (address);
function messenger() external view returns (address);
function registerToken(address l1Token, address l2Token) external;
}

interface L1RelayLike {
function l2GovernanceRelay() external view returns (address);
function messenger() external view returns (address);
function relay(
address target,
bytes calldata targetData,
uint32 minGasLimit
) external;
}

interface EscrowLike {
function approve(address, address, uint256) external;
}

struct BridgesConfig {
address l1Messenger;
address l2Messenger;
address[] l1Tokens;
address[] l2Tokens;
uint32 minGasLimit;
bytes32 govRelayCLKey;
bytes32 escrowCLKey;
bytes32 l1BridgeCLKey;
}

library TokenBridgeInit {
function initBridges(
DssInstance memory dss,
L1TokenBridgeInstance memory l1BridgeInstance,
L2TokenBridgeInstance memory l2BridgeInstance,
BridgesConfig memory cfg
) internal {
L1RelayLike l1GovRelay = L1RelayLike(l1BridgeInstance.govRelay);
EscrowLike escrow = EscrowLike(l1BridgeInstance.escrow);
L1TokenBridgeLike l1Bridge = L1TokenBridgeLike(l1BridgeInstance.bridge);

// sanity checks
require(l1Bridge.isOpen() == 1, "TokenBridgeInit/not-open");
require(l1Bridge.otherBridge() == l2BridgeInstance.bridge, "TokenBridgeInit/other-bridge-mismatch");
require(l1Bridge.escrow() == address(escrow), "TokenBridgeInit/escrow-mismatch");
require(l1Bridge.messenger() == cfg.l1Messenger, "TokenBridgeInit/l1-bridge-messenger-mismatch");
require(l1GovRelay.l2GovernanceRelay() == l2BridgeInstance.govRelay, "TokenBridgeInit/l2-gov-relay-mismatch");
require(l1GovRelay.messenger() == cfg.l1Messenger, "TokenBridgeInit/l1-gov-relay-messenger-mismatch");
require(cfg.l1Tokens.length == cfg.l2Tokens.length, "TokenBridgeInit/token-arrays-mismatch");

for (uint256 i; i < cfg.l1Tokens.length; ++i) {
(address l1Token, address l2Token) = (cfg.l1Tokens[i], cfg.l2Tokens[i]);
require(l1Token != address(0), "TokenBridgeInit/invalid-l1-token");
require(l2Token != address(0), "TokenBridgeInit/invalid-l2-token");
require(l1Bridge.l1ToL2Token(l1Token) == address(0), "TokenBridgeInit/existing-l1-token");

l1Bridge.registerToken(l1Token, l2Token);
escrow.approve(l1Token, address(l1Bridge), type(uint256).max);
}

l1GovRelay.relay({
target: l2BridgeInstance.spell,
targetData: abi.encodeCall(L2TokenBridgeSpell.init, (
l2BridgeInstance.govRelay,
l2BridgeInstance.bridge,
address(l1GovRelay),
address(l1Bridge),
cfg.l2Messenger,
cfg.l1Tokens,
cfg.l2Tokens
)),
minGasLimit: cfg.minGasLimit
});

dss.chainlog.setAddress(cfg.govRelayCLKey, address(l1GovRelay));
dss.chainlog.setAddress(cfg.escrowCLKey, address(escrow));
dss.chainlog.setAddress(cfg.l1BridgeCLKey, address(l1Bridge));
}
}
8 changes: 6 additions & 2 deletions foundry.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,9 @@
src = "src"
out = "out"
libs = ["lib"]

# See more config options https:/foundry-rs/foundry/blob/master/crates/config/README.md#all-options
solc = "0.8.21"
fs_permissions = [
{ access = "read", path = "./script/input/"},
{ access = "read", path = "./out/"},
{ access = "read-write", path = "./script/output/"}
]
13 changes: 13 additions & 0 deletions script/input/1/config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"domains": {
"mainnet": {
"chainlog": "0xdA0Ab1e0017DEbCd72Be8599041a2aa3bA7e740F",
"tokens": []
},
"base": {
"l1Messenger": "0x866E82a600A1414e583f7F13623F1aC5d58b0Afa",
"l2Messenger": "0x4200000000000000000000000000000000000007",
"tokens": []
}
}
}
9 changes: 9 additions & 0 deletions script/input/11155111/config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"domains": {
"sepolia": {},
"base_sepolia": {
"l1Messenger": "0xC34855F4De64F1840e5686e64278da901e261f20",
"l2Messenger": "0x4200000000000000000000000000000000000007"
}
}
}
67 changes: 67 additions & 0 deletions src/Escrow.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
// 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 GemLike {
function approve(address, uint256) external;
}

// Escrow funds on L1, manage approval rights

contract Escrow {
// --- storage variables ---

mapping(address => uint256) public wards;

// --- events ---

event Rely(address indexed usr);
event Deny(address indexed usr);
event Approve(address indexed token, address indexed spender, uint256 value);

// --- modifiers ---

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

// --- constructor ---

constructor() {
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);
}

// --- approve ---

function approve(address token, address spender, uint256 value) external auth {
GemLike(token).approve(spender, value);
emit Approve(token, spender, value);
}
}
Loading