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

Custom USDC bridge #1

wants to merge 26 commits into from

Conversation

fedealconada
Copy link
Contributor

@fedealconada fedealconada commented Sep 18, 2024

We want to use the canonical zkSync Bridge with a custom bridge implementation that works only for USDC (since we want to use the native USDC and not the ERC20 that the zksync bridge deploys by default).

The PR is a little bit large but the key contracts to review are:

Bridge/Withdrawal flow

The flow to bridge and withdraw using custom bridges is the same as when bridging with any token except for the fact that when Bridgehub is called, you need specify the address of the custom L1 bridge.

To bridge USDC from L1 (Ethereum) -> L2 (Sophon):

  • Call bridgehub.requestL2TransactionTwoBridges (same as normally) but you set the secondBridgeAddress with the custom shared bridge deployed on L1. This way, the bridgehub contracts knows which contract to ping to.
  • requestL2TransactionTwoBridges makes a call to the custom shared bridge customBridgeL1.bridgehubDeposit function and transfers USDC from the user to this contract and emits an event.
  • sequencers will pick this event and automatically make a call to customL2Bridge.finalizeDeposit on the custom bridge deployed on L2 (on Sophon).
  • finalizeDeposit is the one that calls usdc.mint() to mint USDC on L2 (note this custom bridge must have MINTER role on the USDC contract).

To withdraw USDC from L2 (Sophon) -> L1 (Etheruem):

  • User makes a call to customBridgeL2.withdraw (same as normally except for the fact that you're calling the custom bridge contract)
  • Once the batch is sealed, user needs to call customL1Bridge.finalizeWithdrawal to finalise the withdrawal

Fixes #SOP-53
Fixes #SOP-54
Fixes #SOP-88
Fixes #SOP-89

@fedealconada fedealconada force-pushed the custom-usdc-bridge branch 2 times, most recently from ca62b13 to 3f38ad5 Compare September 18, 2024 21:50
Copy link

linear bot commented Sep 19, 2024

SOP-54 Create L2SharedBridge.sol

Create a custom L2SharedBridge.sol that works with the native USDC

SOP-53 Create L1SharedBridge.sol

Create a custom L1SharedBridge.sol that works with the USDC implementation

SOP-88 Create unit tests

Copy link

@federava federava left a comment

Choose a reason for hiding this comment

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

This looks great! The implementation is clear, and the test coverage is thorough.

src/L1SharedBridge.sol Outdated Show resolved Hide resolved
test/l2SharedBridge/L2SharedBridgeBase.t.sol Show resolved Hide resolved
src/L1SharedBridge.sol Outdated Show resolved Hide resolved
bytes32 _txHash
) external override onlyBridgehub whenNotPaused {
require(depositHappened[_chainId][_txHash] == 0x00, "ShB tx hap");
function bridgehubConfirmL2Transaction(uint256 _chainId, bytes32 _txDataHash, bytes32 _txHash)

Choose a reason for hiding this comment

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

I think a different linter is used, it's fine but it would be a bit better even for auditors to minimise the diff.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Could be, I'm just using foundry's fmt

src/L1SharedBridge.sol Outdated Show resolved Hide resolved
address public immutable L2_USDC_TOKEN;

constructor(address _l1UsdcToken, address _l2UsdcToken) {
// ERA_CHAIN_ID = _eraChainId; // TODO: checke if we need this!

Choose a reason for hiding this comment

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

I don't think you need it

address _aliasedOwner
) external reinitializer(2) {
/// _l1Bridge The address of the legacy L1 Bridge contract.
function initialize(address _l1SharedBridge) external reinitializer(2) {

Choose a reason for hiding this comment

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

You don't need reinitalizes(2). But its probably ok to have it (I'm not sure)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Will double check! Ty

@kelemeno
Copy link

kelemeno commented Sep 24, 2024

Btw, which commit did you branch off from in era-contracts? It is the audited one right? Could you include it somewhere please?

view
returns (bytes memory)
{
(, bytes memory data1) = _l1Token.staticcall(abi.encodeCall(IERC20Metadata.name, ()));

Choose a reason for hiding this comment

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

This could be hardcoded, it is also good like this.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

True!

/// @param _amount The total amount of tokens to be withdrawn
function withdraw(address _l1Receiver, address _l2Token, uint256 _amount) external override {
function withdraw(address _l1Receiver, address, uint256 _amount) external override {

Choose a reason for hiding this comment

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

Nit: If the token is specified, then it should be validated, so that users don't screw up.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

the _l2Token param is just there to comply with the interface, but it's not used at all, you can send whatever address but the function will always use the USDC one

import {TransparentUpgradeableProxy} from "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol";
import {IL2SharedBridge} from "../../src/interfaces/IL2SharedBridge.sol";

contract MintableToken is MockERC20 {
Copy link

@kelemeno kelemeno Sep 24, 2024

Choose a reason for hiding this comment

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

I don't know how similar this is to the real USDC, it might be good enough, but it might make sense to test it with the full thing

Choose a reason for hiding this comment

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

Also testing on real env, dockerized node probably supports L1<>L2 bridging, or even the local setup

Copy link
Contributor Author

@fedealconada fedealconada Oct 2, 2024

Choose a reason for hiding this comment

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

but it might make sense to test it with the full thing

what do you mean by the full thing @kelemeno ? i think we only need mint and burn to test

function testFinalizeDeposit() public {
uint256 initialBalance = mockL2Token.balanceOf(bob);
address aliasedL1SharedBridge =
address(uint160(l1SharedBridge) + uint160(0x1111000000000000000000000000000000001111));

Choose a reason for hiding this comment

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

you can use the applyAlias function from one of the libraries

import {L2_BASE_TOKEN_SYSTEM_CONTRACT_ADDR} from "@era-contracts/l1-contracts/contracts/common/L2ContractAddresses.sol";

// note, this should be the same as where hyper is disabled
contract L1SharedBridgeHyperEnabledTest is L1SharedBridgeTest {

Choose a reason for hiding this comment

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

This HyperEnabled was a mistake even in our repo, it was the same as the Base case, does it add to coverage or test anything in a different way?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

i don't think so, but since you had it there, i moved those tests here as well in case there was something i was not seeing

/// @author Matter Labs
// note we use the IL1ERC20Bridge only to send L1<>L2 messages,
// and we use this interface so that when the switch happened the old messages could be processed
interface IL1ERC20Bridge {

Choose a reason for hiding this comment

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

This can probably be removed

@kelemeno
Copy link

Look good overall I think, the main is a direct clone of one of era_contracts repo right?

I would get rid of even more of the legacy code, I don't think you need it, modifying the interfaces is ok.

But the approach/security/code looks good!

@fedealconada
Copy link
Contributor Author

Look good overall I think, the main is a direct clone of one of era_contracts repo right?

I would get rid of even more of the legacy code, I don't think you need it, modifying the interfaces is ok.

But the approach/security/code looks good!

Thanks for the comments!

@fedealconada
Copy link
Contributor Author

I would get rid of even more of the legacy code, I don't think you need it, modifying the interfaces is ok.

would you point me to which are the other legacy things that we are safe to remove?

}
}

// TODO: do we need?
Copy link
Contributor Author

Choose a reason for hiding this comment

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

@kelemeno do we need this? or it's safe to be removed?

Copy link

Choose a reason for hiding this comment

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

yes you can remove.

@fedealconada
Copy link
Contributor Author

Btw, which commit did you branch off from in era-contracts? It is the audited one right? Could you include it somewhere please?

this is the commit we've forked from https://github.com/matter-labs/era-contracts.git#bce4b2d0f34bd87f1aaadd291772935afb1c3bd6

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants