Skip to content

Commit

Permalink
Merge branch 'master' into zksync-weth-bridge
Browse files Browse the repository at this point in the history
  • Loading branch information
mrice32 authored Jul 28, 2023
2 parents e302adb + 69fa759 commit ad64fbd
Show file tree
Hide file tree
Showing 5 changed files with 86 additions and 15 deletions.
3 changes: 2 additions & 1 deletion .solhint.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
"extends": "solhint:recommended",
"rules": {
"compiler-version": ["error", "^0.8.0"],
"func-visibility": ["warn", { "ignoreConstructors": true }]
"func-visibility": ["warn", { "ignoreConstructors": true }],
"const-name-snakecase": "off"
}
}
6 changes: 4 additions & 2 deletions contracts/ZkSync_SpokePool.sol
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ contract ZkSync_SpokePool is SpokePool {
ZkBridgeLike public zkErc20Bridge;
ZkBridgeLike public zkWETHBridge;

event SetZkBridge(address indexed erc20Bridge, address indexed wethBridge);
event SetZkBridge(address indexed erc20Bridge, address oldErc20Bridge, address indexed wethBridge, address oldWethBridge);
event ZkSyncTokensBridged(address indexed l2Token, address target, uint256 numberOfTokensBridged);

/**
Expand Down Expand Up @@ -99,9 +99,11 @@ contract ZkSync_SpokePool is SpokePool {
}

function _setZkBridge(ZkBridgeLike _zkErc20Bridge, ZkBridgeLike _zkWETHBridge) internal {
address oldZkErc20Bridge = address(zkErc20Bridge);
address oldZkWETHBridge = address(zkWETHBridge);
zkErc20Bridge = _zkErc20Bridge;
zkWETHBridge = _zkWETHBridge;
emit SetZkBridge(address(_zkErc20Bridge), address(_zkWETHBridge));
emit SetZkBridge(address(_zkErc20Bridge), oldZkErc20Bridge, address(_zkWETHBridge), oldZkWETHBridge);
}

// L1 addresses are transformed during l1->l2 calls.
Expand Down
74 changes: 67 additions & 7 deletions contracts/chain-adapters/ZkSync_Adapter.sol
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,63 @@ interface ZkBridgeLike {
) external payable returns (bytes32 txHash);
}

// Note: this contract just forwards the calls from the HubPool to ZkSync to avoid limits.
// A modified ZKSync_Adapter should be deployed with this address swapped in for all zkSync addresses.
contract LimitBypassProxy is ZkSyncInterface, ZkBridgeLike {
using SafeERC20 for IERC20;
ZkSyncInterface public constant zkSync = ZkSyncInterface(0x32400084C286CF3E17e7B677ea9583e60a000324);
ZkBridgeLike public constant zkErc20Bridge = ZkBridgeLike(0x57891966931Eb4Bb6FB81430E6cE0A03AAbDe063);

function l2TransactionBaseCost(
uint256 _l1GasPrice,
uint256 _l2GasLimit,
uint256 _l2GasPerPubdataByteLimit
) external view returns (uint256) {
return zkSync.l2TransactionBaseCost(_l1GasPrice, _l2GasLimit, _l2GasPerPubdataByteLimit);
}

function requestL2Transaction(
address _contractL2,
uint256 _l2Value,
bytes calldata _calldata,
uint256 _l2GasLimit,
uint256 _l2GasPerPubdataByteLimit,
bytes[] calldata _factoryDeps,
address _refundRecipient
) external payable returns (bytes32 canonicalTxHash) {
return
zkSync.requestL2Transaction{ value: msg.value }(
_contractL2,
_l2Value,
_calldata,
_l2GasLimit,
_l2GasPerPubdataByteLimit,
_factoryDeps,
_refundRecipient
);
}

function deposit(
address _l2Receiver,
address _l1Token,
uint256 _amount,
uint256 _l2TxGasLimit,
uint256 _l2TxGasPerPubdataByte,
address _refundRecipient
) external payable returns (bytes32 txHash) {
IERC20(_l1Token).safeIncreaseAllowance(address(zkErc20Bridge), _amount);
return
zkErc20Bridge.deposit{ value: msg.value }(
_l2Receiver,
_l1Token,
_amount,
_l2TxGasLimit,
_l2TxGasPerPubdataByte,
_refundRecipient
);
}
}

/**
* @notice Contract containing logic to send messages from L1 to ZkSync.
* @dev Public functions calling external contracts do not guard against reentrancy because they are expected to be
Expand Down Expand Up @@ -83,29 +140,31 @@ contract ZkSync_Adapter is AdapterInterface {
// This address receives any remaining fee after an L1 to L2 transaction completes.
// If refund recipient = address(0) then L2 msg.sender is used, unless msg.sender is a contract then its address
// gets aliased.
address public constant l2RefundAddress = 0x428AB2BA90Eba0a4Be7aF34C9Ac451ab061AC010;
address public immutable l2RefundAddress;

// Hardcode the following ZkSync system contract addresses to save gas on construction. This adapter can be
// redeployed in the event that the following addresses change.

// Main contract used to send L1 --> L2 messages. Fetchable via `zks_getMainContract` method on JSON RPC.
ZkSyncInterface public constant zkSync = ZkSyncInterface(0x32400084C286CF3E17e7B677ea9583e60a000324);
ZkSyncInterface public constant zkSyncMessageBridge = ZkSyncInterface(0x32400084C286CF3E17e7B677ea9583e60a000324);

// Bridges to send ERC20 and ETH to L2. Fetchable via `zks_getBridgeContracts` method on JSON RPC.
ZkBridgeLike public constant zkErc20Bridge = ZkBridgeLike(0x57891966931Eb4Bb6FB81430E6cE0A03AAbDe063);
ZkBridgeLike public constant zkWETHBridge = ZkBridgeLike(0x57891966931Eb4Bb6FB81430E6cE0A03AAbDe063);

// Set l1Weth at construction time to make testing easier. TODO: Think of some way to be able to hardcode this
// while keeping unit tests easy to write with custom WETH that we can mint/transfer.
// Set l1Weth at construction time to make testing easier.
WETH9Interface public immutable l1Weth;

event ZkSyncMessageRelayed(bytes32 canonicalTxHash);

/**
* @notice Constructs new Adapter.
* @param _l1Weth WETH address on L1.
* @param _l2RefundAddress address that recieves excess gas refunds on L2.
*/
constructor(WETH9Interface _l1Weth) {
constructor(WETH9Interface _l1Weth, address _l2RefundAddress) {
l1Weth = _l1Weth;
l2RefundAddress = _l2RefundAddress;
}

/**
Expand All @@ -119,7 +178,7 @@ contract ZkSync_Adapter is AdapterInterface {
uint256 txBaseCost = _contractHasSufficientEthBalance();

// Returns the hash of the requested L2 transaction. This hash can be used to follow the transaction status.
bytes32 canonicalTxHash = zkSync.requestL2Transaction{ value: txBaseCost }(
bytes32 canonicalTxHash = zkSyncMessageBridge.requestL2Transaction{ value: txBaseCost }(
target,
// We pass no ETH with the call, otherwise we'd need to add to the txBaseCost this value.
0,
Expand Down Expand Up @@ -195,7 +254,8 @@ contract ZkSync_Adapter is AdapterInterface {
// https://github.com/matter-labs/era-contracts/blob/6391c0d7bf6184d7f6718060e3991ba6f0efe4a7/ethereum/contracts/zksync/facets/Mailbox.sol#L273
// - priority_fee_per_gas = min(transaction.max_priority_fee_per_gas, transaction.max_fee_per_gas - block.base_fee_per_gas)
// - effective_gas_price = priority_fee_per_gas + block.base_fee_per_gas
return zkSync.l2TransactionBaseCost(tx.gasprice, L2_GAS_LIMIT, L1_GAS_TO_L2_GAS_PER_PUB_DATA_LIMIT);
return
zkSyncMessageBridge.l2TransactionBaseCost(tx.gasprice, L2_GAS_LIMIT, L1_GAS_TO_L2_GAS_PER_PUB_DATA_LIMIT);
}

function _contractHasSufficientEthBalance() internal view returns (uint256 requiredL1CallValue) {
Expand Down
7 changes: 5 additions & 2 deletions deploy/015_deploy_zksync_adapter.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,21 @@
import { DeployFunction } from "hardhat-deploy/types";
import { HardhatRuntimeEnvironment } from "hardhat/types";
import { L1_ADDRESS_MAP } from "./consts";

const func: DeployFunction = async function (hre: HardhatRuntimeEnvironment) {
const { deployments, getNamedAccounts } = hre;
const { deployments, getNamedAccounts, getChainId } = hre;

const { deploy } = deployments;

const { deployer } = await getNamedAccounts();
const chainId = parseInt(await getChainId());

await deploy("ZkSync_Adapter", {
from: deployer,
log: true,
skipIfAlreadyDeployed: true,
args: [],
// Most common across dataworker set as the refund address, but changeable by whoever runs the script.
args: [L1_ADDRESS_MAP[chainId].weth, "0x428AB2BA90Eba0a4Be7aF34C9Ac451ab061AC010"],
});
};

Expand Down
11 changes: 8 additions & 3 deletions test/chain-adapters/ZkSync_Adapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,10 @@ import { smock } from "@defi-wonderland/smock";

let hubPool: Contract, zkSyncAdapter: Contract, weth: Contract, dai: Contract, timer: Contract, mockSpoke: Contract;
let l2Weth: string, l2Dai: string;
let owner: SignerWithAddress, dataWorker: SignerWithAddress, liquidityProvider: SignerWithAddress;
let owner: SignerWithAddress,
dataWorker: SignerWithAddress,
liquidityProvider: SignerWithAddress,
refundAddress: SignerWithAddress;
let zkSync: FakeContract, zkSyncErc20Bridge: FakeContract, zkSyncWethBridge: FakeContract;

const zkSyncChainId = 324;
Expand Down Expand Up @@ -72,7 +75,7 @@ const l2TransactionBaseCost = toWei("0.0001");

describe("ZkSync Chain Adapter", function () {
beforeEach(async function () {
[owner, dataWorker, liquidityProvider] = await ethers.getSigners();
[owner, dataWorker, liquidityProvider, refundAddress] = await ethers.getSigners();
({ weth, dai, l2Weth, l2Dai, hubPool, mockSpoke, timer } = await hubPoolFixture());
await seedWallet(dataWorker, [dai], weth, amountToLp);
await seedWallet(liquidityProvider, [dai], weth, amountToLp.mul(10));
Expand All @@ -94,7 +97,9 @@ describe("ZkSync Chain Adapter", function () {
address: "0x57891966931Eb4Bb6FB81430E6cE0A03AAbDe063",
});

zkSyncAdapter = await (await getContractFactory("ZkSync_Adapter", owner)).deploy(weth.address);
zkSyncAdapter = await (
await getContractFactory("ZkSync_Adapter", owner)
).deploy(weth.address, refundAddress.address);

// Seed the HubPool some funds so it can send L1->L2 messages.
await hubPool.connect(liquidityProvider).loadEthForL2Calls({ value: toWei("100000") });
Expand Down

0 comments on commit ad64fbd

Please sign in to comment.