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

fix L2 tests #478

Merged
merged 8 commits into from
May 20, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 1 addition & 1 deletion l1-contracts/contracts/bridge/L1SharedBridge.sol
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
import {Ownable2StepUpgradeable} from "@openzeppelin/contracts-upgradeable/access/Ownable2StepUpgradeable.sol";
import {PausableUpgradeable} from "@openzeppelin/contracts-upgradeable/security/PausableUpgradeable.sol";

import {IERC20Metadata} from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol";

Check failure on line 8 in l1-contracts/contracts/bridge/L1SharedBridge.sol

View workflow job for this annotation

GitHub Actions / lint

imported name IERC20Metadata is not used

Check failure on line 8 in l1-contracts/contracts/bridge/L1SharedBridge.sol

View workflow job for this annotation

GitHub Actions / lint

imported name IERC20Metadata is not used

Check failure on line 8 in l1-contracts/contracts/bridge/L1SharedBridge.sol

View workflow job for this annotation

GitHub Actions / lint

imported name IERC20Metadata is not used
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";

Expand Down Expand Up @@ -211,7 +211,7 @@
uint256 _amount
) external payable virtual onlyBridgehubOrEra(_chainId) whenNotPaused {
(address l1Asset, bytes32 assetInfo) = _getAssetProperties(_assetInfo);
IL1StandardAsset(l1Asset).bridgeBurn{value: msg.value}(
bytes memory bridgeMintData = IL1StandardAsset(l1Asset).bridgeBurn{value: msg.value}(
_chainId,
0,
assetInfo,
Expand Down
4 changes: 2 additions & 2 deletions l1-contracts/contracts/bridgehub/Bridgehub.sol
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ contract Bridgehub is IBridgehub, ReentrancyGuard, Ownable2StepUpgradeable, Paus

ISTMDeploymentTracker public stmDeployer;

/// @dev assset info used to identify chains in the Shared Bridge
/// @dev asset info used to identify chains in the Shared Bridge
mapping(bytes32 stmAssetInfo => address stmAddress) public stmAssetInfoToAddress;

/// @dev used to indicate the currently active settlement layer for a given chainId
Expand Down Expand Up @@ -366,7 +366,7 @@ contract Bridgehub is IBridgehub, ReentrancyGuard, Ownable2StepUpgradeable, Paus
) external override {
require(L1_CHAIN_ID == block.chainid, "BH: not in sync layer mode");
address hyperchain = getHyperchain(_chainId);
IZkSyncHyperchain(hyperchain).bridgehubRequestL2Transaction(_request);
bytes32 canonicalTxHash = IZkSyncHyperchain(hyperchain).bridgehubRequestL2Transaction(_request);
}

function registerCounterpart(uint256 _chainId, address _counterPart) external onlyOwner {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ contract StateTransitionManager is IStateTransitionManager, ReentrancyGuard, Own
/// @dev The address to accept the admin role
address private pendingAdmin;

/// @dev The stored cutData for diamond cut, differes on each settlement chain.
/// @dev The stored cutData for diamond cut, differs on each settlement chain.
// todo: this is only used to check the cutHash before migrating, do we want this?. We could s
mapping(uint256 settlementChainId => bytes32 cutHash) public migrationCutHash;

Expand Down
47 changes: 31 additions & 16 deletions l2-contracts/contracts/bridge/L2SharedBridge.sol
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {UpgradeableBeacon} from "@openzeppelin/contracts/proxy/beacon/Upgradeabl

import {IL1ERC20Bridge} from "./interfaces/IL1ERC20Bridge.sol";
import {IL2SharedBridge} from "./interfaces/IL2SharedBridge.sol";
import {ILegacyL2SharedBridge} from "./interfaces/ILegacyL2SharedBridge.sol";
import {IL2StandardAsset} from "./interfaces/IL2StandardAsset.sol";
import {ILegacyL2SharedBridge} from "./interfaces/ILegacyL2SharedBridge.sol";
import {IL2StandardToken} from "./interfaces/IL2StandardToken.sol";
Expand All @@ -23,6 +24,7 @@ import {SystemContractsCaller} from "../SystemContractsCaller.sol";
/// @notice The "default" bridge implementation for the ERC20 tokens. Note, that it does not
/// support any custom token logic, i.e. rebase tokens' functionality is not supported.
contract L2SharedBridge is IL2SharedBridge, ILegacyL2SharedBridge, Initializable {

/// @dev The address of the L1 shared bridge counterpart.
address public override l1SharedBridge;

Expand Down Expand Up @@ -68,13 +70,16 @@ contract L2SharedBridge is IL2SharedBridge, ILegacyL2SharedBridge, Initializable
address _l1SharedBridge,
address _l1Bridge,
bytes32 _l2TokenProxyBytecodeHash,
address _aliasedOwner
address _aliasedOwner,
IL2StandardDeployer _standardDeployer
) external reinitializer(2) {
require(_l1SharedBridge != address(0), "bf");
require(_l2TokenProxyBytecodeHash != bytes32(0), "df");
require(_aliasedOwner != address(0), "sf");
require(address(_standardDeployer) != address(0), "cf");

l1SharedBridge = _l1SharedBridge;
standardDeployer = _standardDeployer;

if (block.chainid != ERA_CHAIN_ID) {
address l2StandardToken = address(new L2StandardERC20{salt: bytes32(0)}());
Expand All @@ -94,15 +99,14 @@ contract L2SharedBridge is IL2SharedBridge, ILegacyL2SharedBridge, Initializable
/// @param _l1Token The address of the token that was locked on the L1
// / @param _amount Total amount of tokens deposited from L1
/// @param _data The additional data that user can pass with the deposit
function finalizeDeposit(bytes32 _assetInfo, bytes calldata _data) public override {
function finalizeDeposit(bytes32 _assetInfo, bytes memory _data) public override {
// Only the L1 bridge counterpart can initiate and finalize the deposit.

// require(
// AddressAliasHelper.undoL1ToL2Alias(msg.sender) == l1Bridge ||
// AddressAliasHelper.undoL1ToL2Alias(msg.sender) == l1LegacyBridge ||
// msg.sender == address(this),
// "mq"
// );
require(
AddressAliasHelper.undoL1ToL2Alias(msg.sender) == l1Bridge ||
AddressAliasHelper.undoL1ToL2Alias(msg.sender) == l1SharedBridge,
"mq"
);

address asset = assetAddress[_assetInfo];
if (asset != address(0)) {
Expand All @@ -120,13 +124,20 @@ contract L2SharedBridge is IL2SharedBridge, ILegacyL2SharedBridge, Initializable
/// @param _assetData The data that is passed to the asset contract
function withdraw(bytes32 _assetInfo, bytes calldata _assetData) external override {
Raid5594 marked this conversation as resolved.
Show resolved Hide resolved
address asset = assetAddress[_assetInfo];
bytes memory _bridgeMintData = IL2StandardAsset(assetAddress[_assetInfo]).bridgeBurn(
L1_CHAIN_ID,
0,
_assetInfo,
msg.sender,
_assetData
);
bytes memory _bridgeMintData;

/// should the address not be set already? Or do we need this to do it automatically.
if (asset != address(0)) {
_bridgeMintData = IL2StandardAsset(asset).bridgeBurn(L1_CHAIN_ID, 0, _assetInfo, msg.sender, _assetData);
} else {
_bridgeMintData = IL2StandardAsset(standardDeployer).bridgeBurn(
L1_CHAIN_ID,
0,
_assetInfo,
msg.sender,
_assetData
);
}

bytes memory message = _getL1WithdrawMessage(_assetInfo, _bridgeMintData);
L2ContractHelper.sendMessageToL1(message);
Expand Down Expand Up @@ -160,10 +171,13 @@ contract L2SharedBridge is IL2SharedBridge, ILegacyL2SharedBridge, Initializable
abi.encode(L1_CHAIN_ID, NATIVE_TOKEN_VAULT_VIRTUAL_ADDRESS, bytes32(uint256(uint160(_l1Token))))
);
bytes memory data = abi.encode(_l1Sender, _amount, _l2Receiver, _data, _l1Token);
this.finalizeDeposit(assetInfo, data);
finalizeDeposit(assetInfo, data);
}

function withdraw(address _l1Receiver, address _l2Token, uint256 _amount) external {
if (l1TokenAddress[_l2Token] == address(0)) {
getL1TokenAddress(_l2Token);
}
bytes32 assetInfo = keccak256(
abi.encode(
L1_CHAIN_ID,
Expand All @@ -172,6 +186,7 @@ contract L2SharedBridge is IL2SharedBridge, ILegacyL2SharedBridge, Initializable
)
);
bytes memory data = abi.encode(_amount, _l1Receiver);
this.withdraw(assetInfo, data);
}

function getL1TokenAddress(address _l2Token) public view returns (address) {
Expand Down
28 changes: 18 additions & 10 deletions l2-contracts/contracts/bridge/L2StandardDeployer.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

pragma solidity 0.8.20;

import {Initializable} from "@openzeppelin/contracts/proxy/utils/Initializable.sol";
import {Ownable2StepUpgradeable} from "@openzeppelin/contracts-upgradeable/access/Ownable2StepUpgradeable.sol";
import {BeaconProxy} from "@openzeppelin/contracts/proxy/beacon/BeaconProxy.sol";
import {UpgradeableBeacon} from "@openzeppelin/contracts/proxy/beacon/UpgradeableBeacon.sol";

Expand All @@ -20,7 +20,7 @@ import {SystemContractsCaller} from "../SystemContractsCaller.sol";
/// @custom:security-contact [email protected]
/// @notice The "default" bridge implementation for the ERC20 tokens. Note, that it does not
/// support any custom token logic, i.e. rebase tokens' functionality is not supported.
contract L2StandardDeployer is IL2StandardDeployer, Initializable {
contract L2StandardDeployer is IL2StandardDeployer, Ownable2StepUpgradeable {
IL2SharedBridge public override l2Bridge;

/// @dev Contract that stores the implementation address for token.
Expand All @@ -38,27 +38,27 @@ contract L2StandardDeployer is IL2StandardDeployer, Initializable {
}

/// @notice Initializes the bridge contract for later use. Expected to be used in the proxy.
/// @param _l2Bridge The address of the L1 Bridge contract.
/// @param _owner Address which can set the shared bridge address and upgrade the deployer
/// @param _l2TokenProxyBytecodeHash The bytecode hash of the proxy for tokens deployed by the bridge.
/// @param _aliasedOwner The address of the governor contract.
/// @param _contractsDeployedAlready Ensures beacon proxy for standard ERC20 has not been deployed
function initialize(
IL2SharedBridge _l2Bridge,
address _owner,
bytes32 _l2TokenProxyBytecodeHash,
address _aliasedOwner,
bool contractsDeployedAlready
bool _contractsDeployedAlready
) external reinitializer(2) {
require(address(_l2Bridge) != address(0), "bf");
require(_l2TokenProxyBytecodeHash != bytes32(0), "df");
require(_aliasedOwner != address(0), "sf");

l2Bridge = _l2Bridge;

if (!contractsDeployedAlready) {
if (!_contractsDeployedAlready) {
address l2StandardToken = address(new L2StandardERC20{salt: bytes32(0)}());
l2TokenBeacon = new UpgradeableBeacon{salt: bytes32(0)}(l2StandardToken);
l2TokenProxyBytecodeHash = _l2TokenProxyBytecodeHash;
l2TokenBeacon.transferOwnership(_aliasedOwner);
}

_transferOwnership(_owner);
}

function bridgeMint(uint256 _chainId, bytes32 _assetInfo, bytes calldata _data) external payable override {
Expand Down Expand Up @@ -90,7 +90,7 @@ contract L2StandardDeployer is IL2StandardDeployer, Initializable {
bytes32 _assetInfo,
address _prevMsgSender,
bytes calldata _data
) external payable override onlyBridge returns (bytes memory _bridgeMintData) {
) external payable override onlyBridge returns (bytes memory _bridgeBurnData) {
(uint256 _amount, address _l1Receiver) = abi.decode(_data, (uint256, address));
require(_amount > 0, "Amount cannot be zero");

Expand All @@ -99,6 +99,7 @@ contract L2StandardDeployer is IL2StandardDeployer, Initializable {

/// backwards compatible event
emit WithdrawalInitiated(_prevMsgSender, _l1Receiver, l2Token, _amount);
_bridgeBurnData = abi.encodePacked(_chainId, _mintValue, _assetInfo, _prevMsgSender, _data);
}

/// @dev Deploy and initialize the L2 token for the L1 counterpart
Expand Down Expand Up @@ -142,4 +143,11 @@ contract L2StandardDeployer is IL2StandardDeployer, Initializable {
return
L2ContractHelper.computeCreate2Address(address(this), salt, l2TokenProxyBytecodeHash, constructorInputHash);
}

/// @dev Sets the L1ERC20Bridge contract address. Should be called only once.
function setSharedBridge(IL2SharedBridge _sharedBridge) external onlyOwner {
require(address(l2Bridge) == address(0), "SD: shared bridge already set");
require(address(_sharedBridge) != address(0), "SD: shared bridge 0");
l2Bridge = _sharedBridge;
}
}
6 changes: 2 additions & 4 deletions l2-contracts/contracts/bridge/L2StandardERC20.sol
Original file line number Diff line number Diff line change
Expand Up @@ -52,12 +52,10 @@ contract L2StandardERC20 is ERC20PermitUpgradeable, IL2StandardToken, ERC1967Upg
l1Address = _l1Address;

l2Bridge = msg.sender;
// We parse the data exactly as they were created on the L1 Native Token Vault
(uint256 _amount, bytes memory data) = abi.decode(_data, (uint256, bytes));

// We parse the data exactly as they were created on the L1 bridge getERC20Getters
// We parse the data exactly as they were created on the L1 bridge
(bytes memory nameBytes, bytes memory symbolBytes, bytes memory decimalsBytes) = abi.decode(
data,
_data,
(bytes, bytes, bytes)
);

Expand Down
4 changes: 2 additions & 2 deletions l2-contracts/contracts/bridge/interfaces/IL2SharedBridge.sol
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,11 @@ interface IL2SharedBridge {

function withdraw(bytes32 _assetInfo, bytes calldata _data) external;

function l1SharedBridge() external view returns (address);
function l1Bridge() external view returns (address);

function assetAddress(bytes32 _assetInfo) external view returns (address);

function l1Bridge() external view returns (address);
function l1SharedBridge() external view returns (address);

function l1TokenAddress(address _l2Token) external view returns (address);
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,6 @@ interface ILegacyL2SharedBridge {
) external;

function withdraw(address _l1Receiver, address _l2Token, uint256 _amount) external;

function l2TokenAddress(address _l1Token) external view returns (address);
}
37 changes: 27 additions & 10 deletions l2-contracts/test/erc20.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ import * as hre from "hardhat";
import { Provider, Wallet } from "zksync-web3";
import { hashBytecode } from "zksync-web3/build/src/utils";
import { unapplyL1ToL2Alias } from "./test-utils";
import { L2SharedBridgeFactory, L2StandardERC20Factory } from "../typechain";
import type { L2SharedBridge, L2StandardERC20 } from "../typechain";
import { L2SharedBridgeFactory, L2StandardDeployerFactory, L2StandardERC20Factory } from "../typechain";
import { L2SharedBridge, L2StandardDeployer, L2StandardERC20 } from "../typechain";

const richAccount = [
{
Expand Down Expand Up @@ -38,7 +38,9 @@ describe("ERC20Bridge", function () {
const testChainId = 9;

let erc20Bridge: L2SharedBridge;
let erc20StandardDeployer: L2StandardDeployer;
let erc20Token: L2StandardERC20;
let contractsDeployedAlready: boolean = false;

before("Deploy token and bridge", async function () {
const deployer = new Deployer(hre, deployerWallet);
Expand All @@ -49,15 +51,29 @@ describe("ERC20Bridge", function () {
l2TokenImplAddress.address,
]);
await deployer.deploy(await deployer.loadArtifact("BeaconProxy"), [l2Erc20TokenBeacon.address, "0x"]);

const beaconProxyBytecodeHash = hashBytecode((await deployer.loadArtifact("BeaconProxy")).bytecode);
const erc20BridgeImpl = await deployer.deploy(await deployer.loadArtifact("L2SharedBridge"), [testChainId, 1]);
const erc20StandardDeployerImpl = await deployer.deploy(await deployer.loadArtifact("L2StandardDeployer"));
const standardDeployerInitializeData = erc20StandardDeployerImpl.interface.encodeFunctionData("initialize", [
deployerWallet.address,
beaconProxyBytecodeHash,
governorWallet.address,
contractsDeployedAlready,
]);

const erc20StandardDeployerProxy = await deployer.deploy(
await deployer.loadArtifact("TransparentUpgradeableProxy"),
[erc20StandardDeployerImpl.address, governorWallet.address, standardDeployerInitializeData]
);

contractsDeployedAlready = true;

const erc20BridgeImpl = await deployer.deploy(await deployer.loadArtifact("L2SharedBridge"), [testChainId]);
const bridgeInitializeData = erc20BridgeImpl.interface.encodeFunctionData("initialize", [
unapplyL1ToL2Alias(l1BridgeWallet.address),
ethers.constants.AddressZero,
beaconProxyBytecodeHash,
governorWallet.address,
erc20StandardDeployerProxy.address,
]);

const erc20BridgeProxy = await deployer.deploy(await deployer.loadArtifact("TransparentUpgradeableProxy"), [
Expand All @@ -67,16 +83,18 @@ describe("ERC20Bridge", function () {
]);

erc20Bridge = L2SharedBridgeFactory.connect(erc20BridgeProxy.address, deployerWallet);
erc20StandardDeployer = L2StandardDeployerFactory.connect(erc20StandardDeployerProxy.address, deployerWallet);

await erc20StandardDeployer.setSharedBridge(erc20BridgeProxy.address);
});

it("Should finalize deposit ERC20 deposit", async function () {
const erc20BridgeWithL1Bridge = L2SharedBridgeFactory.connect(erc20Bridge.address, l1BridgeWallet);
const erc20BridgeWithL1Bridge = L2SharedBridgeFactory.connect(erc20Bridge.address, l1BridgeWallet); // Todo: why this name?

const l1Depositor = ethers.Wallet.createRandom();
const l2Receiver = ethers.Wallet.createRandom();

const tx = await (
await erc20BridgeWithL1Bridge.finalizeDeposit(
await erc20BridgeWithL1Bridge["finalizeDeposit(address,address,address,uint256,bytes)"](
// Depositor and l2Receiver can be any here
l1Depositor.address,
l2Receiver.address,
Expand All @@ -85,9 +103,8 @@ describe("ERC20Bridge", function () {
encodedTokenData("TestToken", "TT", 18)
)
).wait();

const l2TokenAddress = tx.events.find((event) => event.event === "FinalizeDeposit").args.l2Token;

const l2TokenInfo = tx.events.find((event) => event.event === "FinalizeDepositSharedBridge").args.assetInfo;
const l2TokenAddress = await erc20StandardDeployer.tokenAddress(l2TokenInfo);
// Checking the correctness of the balance:
erc20Token = L2StandardERC20Factory.connect(l2TokenAddress, deployerWallet);
expect(await erc20Token.balanceOf(l2Receiver.address)).to.equal(100);
Expand Down
2 changes: 1 addition & 1 deletion l2-contracts/test/weth.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ describe("WETH token & WETH bridge", function () {
before("Deploy token and bridge", async function () {
const deployer = new Deployer(hre, wallet);
const wethTokenImpl = await deployer.deploy(await deployer.loadArtifact("L2WrappedBaseToken"));
const wethBridgeImpl = await deployer.deploy(await deployer.loadArtifact("L2SharedBridge"), [testChainId]);
const wethBridgeImpl = await deployer.deploy(await deployer.loadArtifact("L2SharedBridge"), [testChainId, 1]);
const randomAddress = ethers.utils.hexlify(ethers.utils.randomBytes(20));

const wethTokenProxy = await deployer.deploy(await deployer.loadArtifact("TransparentUpgradeableProxy"), [
Expand Down
Loading