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

Custom USDC bridge #1

Open
wants to merge 26 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 19 commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
cefdeb1
chore: modify L1SharedBridge.sol and L2SharedBridge.sol contracts
fedealconada Sep 13, 2024
b4169aa
chore: adjust forked L1 tests
fedealconada Sep 13, 2024
bc05068
chore: add deployment scripts
fedealconada Sep 18, 2024
5ed6c5f
chore: add other scripts
fedealconada Sep 18, 2024
42ecd88
chore: fix imports and remove Counter
fedealconada Sep 13, 2024
a2f2ae5
chore: update gitignore
fedealconada Sep 13, 2024
0d38cd6
chore: improve scripts
fedealconada Sep 18, 2024
a5b035b
chore: add README
fedealconada Sep 17, 2024
a169202
chore: fix burn mechanism on L2SharedBridge
fedealconada Sep 17, 2024
1f46975
chore: clean up and fixes
fedealconada Sep 17, 2024
0838cc2
chore: update README
fedealconada Sep 18, 2024
a3e68ba
chore: fix build
fedealconada Sep 18, 2024
a7919a4
chore: remove unused tests
fedealconada Sep 18, 2024
a9b771c
chore: polishes
fedealconada Sep 18, 2024
60854d8
chore: polish contracts and tests
fedealconada Sep 18, 2024
caeb069
chore: update addresses
fedealconada Sep 19, 2024
9840995
chore: fix finalize withdrawal script
fedealconada Sep 18, 2024
a6c88ec
chore: fix finalize withdrawal script II
fedealconada Sep 18, 2024
bb00ffc
chore: add unit tests for l2 bridge
fedealconada Sep 19, 2024
dc65224
chore: add fixes from reviews: rmv legacy code, modify interfaces
fedealconada Oct 2, 2024
76f6c9c
chore: update README
fedealconada Oct 2, 2024
80c6669
chore: add utils to save deployed addresses
fedealconada Sep 19, 2024
6e68ca5
Merge pull request #2 from sophon-org/save-addresses
fedealconada Oct 2, 2024
4e06bbb
chore: rmv receiveEth function
fedealconada Oct 2, 2024
a3355b4
chore: update DeployL1SharedBridge script and upgrade
fedealconada Oct 8, 2024
425dc35
chore: update DeployL2SharedBridge script
fedealconada Oct 8, 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
27 changes: 27 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# general
PRIVATE_KEY=YOUR_PRIVATE_KEY
ETHERSCAN_API_KEY="YOUR_ETHERSCAN_API_KEY"
PROXY_ADMIN=0x299174d47c243B5381c6062aBEFbfF915B601D85

# ethereum sepolia
SEPOLIA_RPC_URL=https://eth-sepolia.g.alchemy.com/v2/sp2BM_VFMURcKWMWg8HDVSBgvnm75_NS
SEPOLIA_VERIFIER_URL=https://api.etherscan.io/api
SEPOLIA_CHAIN_ID="11155111"
SOPH_TOKEN=0x06c03F9319EBbd84065336240dcc243bda9D8896
L1_USDC_TOKEN=0xBF4FdF7BF4014EA78C0A07259FBc4315Cb10d94E

# bridge addresses on ethereum sepolia
ERA_DIAMOND_PROXY=0x9A6DE0f62Aa270A8bCB1e2610078650D539B1Ef9
SEPOLIA_L1_BRIDGEHUB=0x35A54c8C757806eB6820629bc82d90E056394C92
SEPOLIA_SHARED_BRIDGE_L1=0x3E8b2fe58675126ed30d0d12dea2A9bda72D18Ae
SEPOLIA_CUSTOM_SHARED_BRIDGE_L1=0x3f842b5FaD08Bac49D0517C975d393f5f466Fd3b

# sophon sepolia
SOPHON_RPC_URL=https://rpc.testnet.sophon.xyz
SOPHON_SEPOLIA_VERIFIER_URL=https://block-explorer-api.testnet.sophon.xyz/api
SOPHON_SEPOLIA_CHAIN_ID="531050104"
L2_USDC_TOKEN=0x27553b610304b6AB77855a963f8208443D773E60

# bridge addresses on sophon sepolia
SOPHON_CUSTOM_SHARED_BRIDGE_L2=0x72591d4135B712861d8d4513a2f6860Ac30A684D

4 changes: 4 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,10 @@ jobs:
with:
version: nightly

- name: Install NPM dependencies
run: |
yarn install

- name: Show Forge version
run: |
forge --version
Expand Down
6 changes: 5 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,8 @@ docs/
node_modules/

# Libraries
lib/
lib/

# Coverage
report/
lcov.info
51 changes: 27 additions & 24 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
## Foundry
## Custom bridge for Native USDC

**Foundry is a blazing fast, portable and modular toolkit for Ethereum application development written in Rust.**

Foundry consists of:

Expand All @@ -19,48 +18,52 @@ https://book.getfoundry.sh/

```shell
$ forge build

# zkSync build
$ forge build --zksync
```

### Test
### Scripts

```shell
$ forge test
```
# Deploy L1 Shared Bridge
$ source .env && forge script ./script/DeployL1SharedBridge.s.sol --rpc-url sepoliaTestnet --private-key $PRIVATE_KEY --verify --broadcast

### Format
# Deploy L2 Shared Bridge
$ source .env && forge script ./script/DeployL2SharedBridge.s.sol --rpc-url sophonTestnet --private-key $PRIVATE_KEY --zksync --broadcast --verify --slow

```shell
$ forge fmt
```
# Initialise L1 Shared Bridge
$ source .env && forge script ./script/InitialiseL1SharedBridge.s.sol --rpc-url sepoliaTestnet --private-key $PRIVATE_KEY --broadcast

### Gas Snapshots
# Bridge from Sophon to Ethereum (L1 -> L2)
$ source .env && forge script ./script/Bridge.s.sol --rpc-url sepoliaTestnet --private-key $PRIVATE_KEY --ffi --broadcast

```shell
$ forge snapshot
# Withdraw from Sophon to Ethereum (L2 -> L1)
$ source .env && forge script ./script/Withdraw.s.sol --rpc-url sophonTestnet --private-key $PRIVATE_KEY --zksync --slow -vvvv --broadcast

# Finalise withdrawal on Ethereum
$ source .env && export L2_WITHDRAWAL_HASH="YOUR_TX_HASH" && forge script ./script/FinalizeWithdrawal.s.sol --rpc-url sepoliaTestnet --private-key $PRIVATE_KEY --ffi --broadcast
```

### Anvil
### Test

```shell
$ anvil
$ forge test
```

### Deploy

### Coverage
```shell
$ forge script script/Counter.s.sol:CounterScript --rpc-url <your_rpc_url> --private-key <your_private_key>
$ forge coverage --report lcov --no-match-coverage '^.*(node_modules|test|script)/.*$' && genhtml ./lcov.info --branch-coverage --rc derive_function_end_line=0 --output-directory report
```

### Cast
### Format

```shell
$ cast <subcommand>
$ forge fmt
```

### Help
### Gas Snapshots

```shell
$ forge --help
$ anvil --help
$ cast --help
```
$ forge snapshot
```
12 changes: 12 additions & 0 deletions codecov.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# interfaces are automatically ignored by forge coverage
ignore:
- "test" # ignore test/ folder
- "script" # ignore scripts/ folder
- "libs" # ignore libs/ folder

comment:
layout: " diff, flags, files"
behavior: default
require_changes: false # if true: only post the comment if coverage changes
require_base: false # [true :: must have a base report to post]
require_head: true # [true :: must have a head report to post]
9 changes: 9 additions & 0 deletions foundry.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,14 @@
src = "contracts"
out = "out"
libs = ["lib"]
auto_detect_solc = true

# See more config options https://github.com/foundry-rs/foundry/blob/master/crates/config/README.md#all-options

[etherscan]
sepoliaTestnet = { key = "${ETHERSCAN_API_KEY}", url = "${SEPOLIA_VERIFIER_URL}", chain = 11155111 }
sophonTestnet = { key = "${ETHERSCAN_API_KEY}", url = "${SOPHON_SEPOLIA_VERIFIER_URL}", chain = 531050104 }

[rpc_endpoints]
sepoliaTestnet = "${SEPOLIA_RPC_URL}"
sophonTestnet = "${SOPHON_RPC_URL}"
6 changes: 5 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@
"@openzeppelin/contracts": "4.9.5",
"@openzeppelin/contracts-upgradeable": "4.9.5",
"era-contracts": "https://github.com/matter-labs/era-contracts.git"

},
"dependencies": {
"dotenv": "^16.4.5",
"ethers": "^6.13.2",
"zksync-ethers": "^6.12.1"
}
}
96 changes: 96 additions & 0 deletions script/Bridge.s.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.13;

import {Script, console} from "forge-std/Script.sol";
import {IERC20} from "forge-std/interfaces/IERC20.sol";
import {
L2TransactionRequestTwoBridgesOuter,
IBridgehub
} from "@era-contracts/l1-contracts/contracts/bridgehub/IBridgehub.sol";

interface MockUSDC {
function approve(address usr, uint256 wad) external;
}

contract BridgeScript is Script {
function setUp() public {}

function run() public {
vm.startBroadcast();
uint256 amountToBridge = 1 * 10 ** 6; // 1 USDC

approve(vm.envAddress("SOPH_TOKEN"), type(uint256).max);
approve(vm.envAddress("L1_USDC_TOKEN"), amountToBridge);
bridge(amountToBridge);

vm.stopBroadcast();
}

function bridge(uint256 amountToBridge) public {
uint256 L2_GAS_LIMIT = 435293;
uint256 TX_GAS_PER_PUBDATA_BYTE_LIMIT = 800;
uint256 CHAIN_ID = vm.envUint("SOPHON_SEPOLIA_CHAIN_ID"); // Sophon Sepolia

// Prepare data for the bridgehubDeposit call
bytes memory depositData = abi.encode(
vm.envAddress("L1_USDC_TOKEN"),
amountToBridge,
msg.sender // sender is the recipient of the tokens on L2
);

// TODO: gasPrice() call sometimes fails sometimes not, why?
// uint256 l2GasPrice = gasPrice();
// console.log("Gas price:", l2GasPrice);
// uint256 baseCost = IBridgehub(SEPOLIA_L1_BRIDGEHUB).l2TransactionBaseCost(
// CHAIN_ID, l2GasPrice, L2_GAS_LIMIT, TX_GAS_PER_PUBDATA_BYTE_LIMIT
// );
// // TODO: why baseCost is returning a very high number?
// console.log("Base cost:", baseCost);

IBridgehub(vm.envAddress("SEPOLIA_L1_BRIDGEHUB")).requestL2TransactionTwoBridges( // No vale needed if base token is not ETH (e.g SOPH)
L2TransactionRequestTwoBridgesOuter({
chainId: CHAIN_ID,
mintValue: 4e18, // base tokens (SOPH for Sophon Sepolia, ETH for Sepolia)
// mintValue: baseCost,
l2Value: 0,
l2GasLimit: L2_GAS_LIMIT, // TODO: it should take ~300'000
l2GasPerPubdataByteLimit: TX_GAS_PER_PUBDATA_BYTE_LIMIT, // TODO: how to calculate?
refundRecipient: address(0), // TODO: why is 0?
secondBridgeAddress: vm.envAddress("SEPOLIA_CUSTOM_SHARED_BRIDGE_L1"),
secondBridgeValue: 0,
secondBridgeCalldata: depositData
})
);
}

function approve(address token, uint256 amount) public {
IERC20 token = IERC20(token);
address l1Bridge = vm.envAddress("SEPOLIA_CUSTOM_SHARED_BRIDGE_L1");

// approve shared bridge to spend base tokens
console.log("Checking %s allowance...", token.symbol());
uint256 allowance = token.allowance(msg.sender, l1Bridge);
if (allowance < amount) {
console.log("Approving...");
if (address(token) == vm.envAddress("L1_USDC_TOKEN")) {
MockUSDC(address(token)).approve(l1Bridge, amount);
} else {
token.approve(l1Bridge, amount);
}
console.log("New allowance:", token.allowance(msg.sender, l1Bridge));
}
console.log("%s allowance OK", token.symbol());
}

function gasPrice() public returns (uint256 price) {
// cast gas-price --rpc-url https://rpc.testnet.sophon.xyz/
string[] memory args = new string[](4);
args[0] = "cast";
args[1] = "gas-price";
args[2] = "--rpc-url";
args[3] = "https://rpc.testnet.sophon.xyz/"; // TODO: get from ENV
string memory result = string(vm.ffi(args));

return vm.parseUint(result);
}
}
53 changes: 53 additions & 0 deletions script/DeployL1SharedBridge.s.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.13;

import {Script, console} from "forge-std/Script.sol";
import {L1SharedBridge} from "../src/L1SharedBridge.sol";
import {IBridgehub} from "@era-contracts/l1-contracts/contracts/bridgehub/IBridgehub.sol";
import {TransparentUpgradeableProxy} from "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol";

contract DeployL1SharedBridge is Script {
function run() public {
// TODO: fix and send corresponding network and config names
_run("", "");
}

function run(string memory networkName, string memory configName) public {
_run(networkName, configName);
}

// call from tests -- avoiding the return when calling from the script results in cleaner logs
function runAndReturnResults(string memory networkName, string memory configName)
public
returns (address sharedBridgeProxy, address sharedBridgeImpl)
{
return _run(networkName, configName);
}

function _run(string memory networkName, string memory configName) internal returns (address, address) {
// TODO: set proper addresses, maybe read from env
address deployerAddress = msg.sender;
address proxyAdmin = vm.envAddress("PROXY_ADMIN");

vm.startBroadcast();

L1SharedBridge sharedBridgeImpl = new L1SharedBridge(
vm.envAddress("L1_USDC_TOKEN"),
IBridgehub(vm.envAddress("SEPOLIA_L1_BRIDGEHUB")),
vm.envUint("SOPHON_SEPOLIA_CHAIN_ID"),
vm.envAddress("ERA_DIAMOND_PROXY")
);

TransparentUpgradeableProxy sharedBridgeProxy = new TransparentUpgradeableProxy(
address(sharedBridgeImpl),
proxyAdmin,
abi.encodeWithSelector(L1SharedBridge.initialize.selector, deployerAddress)
);

console.log("L1SharedBridge implementation deployed @", address(sharedBridgeImpl));
console.log("L1SharedBridge proxy deployed @", address(sharedBridgeProxy));

vm.stopBroadcast();
return (address(sharedBridgeProxy), address(sharedBridgeImpl));
}
}
55 changes: 55 additions & 0 deletions script/DeployL2SharedBridge.s.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.13;

import {Script, console} from "forge-std/Script.sol";
import {L2SharedBridge} from "../src/L2SharedBridge.sol";
import {TransparentUpgradeableProxy} from "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol";

contract DeployL2SharedBridge is Script {
function run() public {
// TODO: fix and send corresponding network and config names
_run("", "");
}

function run(string memory networkName, string memory configName) public {
_run(networkName, configName);
}

// call from tests -- avoiding the return when calling from the script results in cleaner logs
function runAndReturnResults(string memory networkName, string memory configName)
public
returns (address sharedBridgeProxy, address sharedBridgeImpl)
{
return _run(networkName, configName);
}

function _run(string memory networkName, string memory configName) internal returns (address, address) {
// TODO: set proper addresses, maybe read from env
address proxyAdmin = vm.envAddress("PROXY_ADMIN");

vm.startBroadcast();

L2SharedBridge sharedBridgeImpl =
new L2SharedBridge(vm.envAddress("L1_USDC_TOKEN"), vm.envAddress("L2_USDC_TOKEN"));

TransparentUpgradeableProxy sharedBridgeProxy = new TransparentUpgradeableProxy(
address(sharedBridgeImpl),
proxyAdmin,
abi.encodeWithSelector(L2SharedBridge.initialize.selector, vm.envAddress("SEPOLIA_CUSTOM_SHARED_BRIDGE_L1"))
);

console.log("L2SharedBridge implementation deployed @", address(sharedBridgeImpl));
console.log("L2SharedBridge proxy deployed @", address(sharedBridgeProxy));
console.log("IMPORTANT: L1SharedBridge must be initialised with the L2SharedBridge address.");
console.log(
"L1SharedBridge(address(sharedBridgeProxy)).initializeChainGovernance(531050104, SOPHON_CUSTOM_SHARED_BRIDGE_L2)"
);
console.log("Use the InitialiseL1Bridge script to do this.");
console.log("IMPORTANT: L2SharedBridge must be added as minter on the L2 USDC contract.");
console.log("Use the add-new-minter script on the USDC repo");

vm.stopBroadcast();

return (address(sharedBridgeProxy), address(sharedBridgeImpl));
}
}
Loading