-
Notifications
You must be signed in to change notification settings - Fork 0
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
base: main
Are you sure you want to change the base?
Conversation
ca62b13
to
3f38ad5
Compare
3f38ad5
to
a6c88ec
Compare
SOP-53 Create L1SharedBridge.sol
Create a custom L1SharedBridge.sol that works with the USDC implementation |
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 looks great! The implementation is clear, and the test coverage is thorough.
bytes32 _txHash | ||
) external override onlyBridgehub whenNotPaused { | ||
require(depositHappened[_chainId][_txHash] == 0x00, "ShB tx hap"); | ||
function bridgehubConfirmL2Transaction(uint256 _chainId, bytes32 _txDataHash, bytes32 _txHash) |
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 think a different linter is used, it's fine but it would be a bit better even for auditors to minimise the diff.
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.
Could be, I'm just using foundry's fmt
src/L2SharedBridge.sol
Outdated
address public immutable L2_USDC_TOKEN; | ||
|
||
constructor(address _l1UsdcToken, address _l2UsdcToken) { | ||
// ERA_CHAIN_ID = _eraChainId; // TODO: checke if we need this! |
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 don't think you need it
src/L2SharedBridge.sol
Outdated
address _aliasedOwner | ||
) external reinitializer(2) { | ||
/// _l1Bridge The address of the legacy L1 Bridge contract. | ||
function initialize(address _l1SharedBridge) external reinitializer(2) { |
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.
You don't need reinitalizes(2). But its probably ok to have it (I'm not sure)
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.
Will double check! Ty
Btw, which commit did you branch off from in era-contracts? It is the audited one right? Could you include it somewhere please? |
src/L1SharedBridge.sol
Outdated
view | ||
returns (bytes memory) | ||
{ | ||
(, bytes memory data1) = _l1Token.staticcall(abi.encodeCall(IERC20Metadata.name, ())); |
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 could be hardcoded, it is also good like this.
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.
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 { |
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.
Nit: If the token is specified, then it should be validated, so that users don't screw up.
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.
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 { |
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 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
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.
Also testing on real env, dockerized node probably supports L1<>L2 bridging, or even the local setup
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.
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)); |
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.
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 { |
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 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?
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 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 { |
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 can probably be removed
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! |
would you point me to which are the other legacy things that we are safe to remove? |
src/L1SharedBridge.sol
Outdated
} | ||
} | ||
|
||
// TODO: do we need? |
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.
@kelemeno do we need this? or it's safe to be removed?
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.
yes you can remove.
fcc50ac
to
dc65224
Compare
this is the commit we've forked from https://github.com/matter-labs/era-contracts.git#bce4b2d0f34bd87f1aaadd291772935afb1c3bd6 |
Add utils to save deployed addresses
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):
bridgehub.requestL2TransactionTwoBridges
(same as normally) but you set thesecondBridgeAddress
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 bridgecustomBridgeL1.bridgehubDeposit
function and transfersUSDC
from the user to this contract and emits an event.customL2Bridge.finalizeDeposit
on the custom bridge deployed on L2 (on Sophon).finalizeDeposit
is the one that callsusdc.mint()
to mintUSDC
on L2 (note this custom bridge must haveMINTER
role on theUSDC
contract).To withdraw USDC from L2 (Sophon) -> L1 (Etheruem):
customBridgeL2.withdraw
(same as normally except for the fact that you're calling the custom bridge contract)customL1Bridge.finalizeWithdrawal
to finalise the withdrawalFixes #SOP-53
Fixes #SOP-54
Fixes #SOP-88
Fixes #SOP-89