From 70db89a9fd46bd2e174337454d9d3bd027dea792 Mon Sep 17 00:00:00 2001 From: kelemeno Date: Mon, 8 Jan 2024 11:11:24 +0100 Subject: [PATCH] updated with base token changes and files that need to be copied --- scripts/entrypoint.sh | 2 +- src/adapters.ts | 227 +++++++++++++++++++++++++++++++++++++++--- src/provider.ts | 12 ++- src/utils.ts | 5 +- 4 files changed, 226 insertions(+), 20 deletions(-) diff --git a/scripts/entrypoint.sh b/scripts/entrypoint.sh index ef8e357f..ba93036e 100755 --- a/scripts/entrypoint.sh +++ b/scripts/entrypoint.sh @@ -38,7 +38,7 @@ solc --base-path system-contracts \ mkdir abi /abi mv l1-abi/* system-contracts-abi/* abi -contracts="IBridgehub.abi IL1Bridge.abi IL2Bridge.abi IContractDeployer.abi IEthToken.abi IL1Messenger.abi INonceHolder.abi IPaymasterFlow.abi" +contracts="IBridgehub.abi IZkSyncStateTransition.abi IStateTransitionChain.abi IL1Bridge.abi IL2Bridge.abi IContractDeployer.abi IEthToken.abi IL1Messenger.abi INonceHolder.abi IPaymasterFlow.abi ITestnetERC20Token.abi" for filename in $contracts; do jq '.' "abi/$filename" > "/abi/${filename%.abi}.json" diff --git a/src/adapters.ts b/src/adapters.ts index 5af82e51..6c6a71d8 100644 --- a/src/adapters.ts +++ b/src/adapters.ts @@ -5,6 +5,8 @@ import {Il2BridgeFactory as IL2BridgeFactory} from "../typechain/Il2BridgeFactor import {IBridgehubFactory} from "../typechain/IBridgehubFactory"; import {IBridgehub} from "../typechain/IBridgehub"; import {INonceHolderFactory} from "../typechain/INonceHolderFactory"; +import {IStateTransitionChainFactory} from "../typechain/IStateTransitionChainFactory"; +import {IStateTransitionChain} from "../typechain/IStateTransitionChain"; import {Provider} from "./provider"; import { Address, @@ -22,6 +24,7 @@ import { estimateCustomBridgeDepositL2Gas, estimateDefaultBridgeDepositL2Gas, ETH_ADDRESS, + ETH_ADDRESS_IN_CONTRACTS, getERC20DefaultBridgeData, isETH, L1_MESSENGER_ADDRESS, @@ -58,6 +61,11 @@ export function AdapterL1>(Base: TBase) { throw new Error("Must be implemented by the derived class!"); } + async getMainContract(): Promise { + const address = await this._providerL2().getMainContractAddress(); + return IStateTransitionChainFactory.connect(address, this._signerL1()); + } + async getBridgehubContract(): Promise { const address = await this._providerL2().getBridgehubContractAddress(); return IBridgehubFactory.connect(address, this._signerL1()); @@ -186,6 +194,7 @@ export function AdapterL1>(Base: TBase) { operatorTip?: BigNumberish; bridgeAddress?: Address; approveERC20?: boolean; + approveBaseERC20?: boolean; l2GasLimit?: BigNumberish; gasPerPubdataByte?: BigNumberish; refundRecipient?: Address; @@ -194,8 +203,13 @@ export function AdapterL1>(Base: TBase) { customBridgeData?: BytesLike; }): Promise { const depositTx = await this.getDepositTx(transaction); + const bridgehub = await this.getBridgehubContract(); + const chainId = (await this._providerL2().getNetwork()).chainId; + const baseTokenAddress = await bridgehub.baseToken(chainId); + const baseTokenBridge = await bridgehub.baseTokenBridge(chainId); + const bridgeContracts = await this.getL1BridgeContracts(); - if (transaction.token == ETH_ADDRESS) { + if ((transaction.token == ETH_ADDRESS) && (baseTokenAddress == ETH_ADDRESS_IN_CONTRACTS)) { const baseGasLimit = await this.estimateGasRequestExecute(depositTx); const gasLimit = scaleGasLimit(baseGasLimit); @@ -203,8 +217,7 @@ export function AdapterL1>(Base: TBase) { depositTx.overrides.gasLimit ??= gasLimit; return this.requestExecute(depositTx); - } else { - const bridgeContracts = await this.getL1BridgeContracts(); + } else if (baseTokenAddress == ETH_ADDRESS_IN_CONTRACTS) { if (transaction.approveERC20) { let l2WethToken = ethers.constants.AddressZero; try { @@ -240,6 +253,114 @@ export function AdapterL1>(Base: TBase) { depositTx.gasLimit ??= gasLimit; + return await this._providerL2().getPriorityOpResponse( + await this._signerL1().sendTransaction(depositTx), + ); + } else if (transaction.token == ETH_ADDRESS) { + const mintValue = parseInt(depositTx.data.slice(2+8+3*64, 2+8+4*64), 16); + // we are depositing eth into a non-eth based chain. We go through the weth bridge. + if (transaction.approveBaseERC20) { + // We only request the allowance if the current one is not enough. + const allowance = await this.getAllowanceL1(baseTokenAddress, baseTokenBridge); + if (allowance.lt(mintValue)) { + const approveTx = await this.approveERC20( + baseTokenAddress, + mintValue, + { + bridgeAddress: baseTokenBridge, + ...transaction.approveOverrides, + }, + ); + await approveTx.wait(); + } + } + + const baseGasLimit = await this._providerL1().estimateGas(depositTx); + const gasLimit = scaleGasLimit(baseGasLimit); + + depositTx.gasLimit ??= gasLimit; + + return await this._providerL2().getPriorityOpResponse( + await this._signerL1().sendTransaction(depositTx), + ); + + } else if (transaction.token == baseTokenAddress) { + const mintValue = depositTx.mintValue; + // we are bridging the base token to a non-eth based chain. We go through the bridgehub, and give approval + if ((transaction.approveERC20) || (transaction.approveBaseERC20)) { + // We only request the allowance if the current one is not enough. + const allowance = await this.getAllowanceL1(baseTokenAddress, baseTokenBridge); + if (allowance.lt(mintValue)) { + const approveTx = await this.approveERC20( + baseTokenAddress, + mintValue, + { + bridgeAddress: baseTokenBridge, + ...transaction.approveOverrides, + }, + ); + await approveTx.wait(); + } + } + const baseGasLimit = await this.estimateGasRequestExecute(depositTx); + const gasLimit = scaleGasLimit(baseGasLimit); + + depositTx.overrides ??= {}; + depositTx.overrides.gasLimit ??= gasLimit; + + return this.requestExecute(depositTx); + } else { + const mintValue = parseInt(depositTx.data.slice(2+8+3*64, 2+8+4*64), 16); + // we are depositing a non-eth and non-base token to a non-eth based chain. We go through the bridgehub, and give approval for both tokens + if (transaction.approveERC20) { + let l2WethToken = ethers.constants.AddressZero; + try { + l2WethToken = await bridgeContracts.weth.l2TokenAddress(transaction.token); + } catch (e) {} + // If the token is Wrapped Ether, use its bridge. + const proposedBridge = + l2WethToken != ethers.constants.AddressZero + ? bridgeContracts.weth.address + : bridgeContracts.erc20.address; + const bridgeAddress = transaction.bridgeAddress + ? transaction.bridgeAddress + : proposedBridge; + + // We only request the allowance if the current one is not enough. + const allowance = await this.getAllowanceL1(transaction.token, bridgeAddress); + if (allowance.lt(transaction.amount)) { + const approveTx = await this.approveERC20( + transaction.token, + transaction.amount, + { + bridgeAddress, + ...transaction.approveOverrides, + }, + ); + await approveTx.wait(); + } + } + if (transaction.approveBaseERC20) { + // We only request the allowance if the current one is not enough. + const allowance = await this.getAllowanceL1(baseTokenAddress, baseTokenBridge); + if (allowance.lt(mintValue)) { + const approveTx = await this.approveERC20( + baseTokenAddress, + mintValue, + { + bridgeAddress: baseTokenBridge, + ...transaction.approveOverrides, + }, + ); + await approveTx.wait(); + } + } + + const baseGasLimit = await this._providerL1().estimateGas(depositTx); + const gasLimit = scaleGasLimit(baseGasLimit); + + depositTx.gasLimit ??= gasLimit; + return await this._providerL2().getPriorityOpResponse( await this._signerL1().sendTransaction(depositTx), ); @@ -328,42 +449,108 @@ export function AdapterL1>(Base: TBase) { const gasPriceForEstimation = overrides.maxFeePerGas || overrides.gasPrice; const bridgehub = await this.getBridgehubContract(); + const chainId = (await this._providerL2().getNetwork()).chainId; + const baseTokenAddress = await bridgehub.baseToken(chainId); const baseCost = await bridgehub.l2TransactionBaseCost( - (await this._providerL2().getNetwork()).chainId, + chainId, await gasPriceForEstimation, tx.l2GasLimit, tx.gasPerPubdataByte, ); - if (token == ETH_ADDRESS) { + if ((token == ETH_ADDRESS) && (baseTokenAddress == ETH_ADDRESS_IN_CONTRACTS)) { + // we are depositing Eth to an Eth based chain overrides.value ??= baseCost.add(operatorTip).add(amount); return { contractAddress: to, calldata: "0x", + mintValue: overrides.value, l2Value: amount, // For some reason typescript can not deduce that we've already set the // tx.l2GasLimit l2GasLimit: tx.l2GasLimit!, ...tx, }; - } else { + } else if (baseTokenAddress == ETH_ADDRESS_IN_CONTRACTS) { + // we are depositing some token to an Eth based chain + overrides.value ??= baseCost.add(operatorTip); + await checkBaseCost(baseCost, overrides.value); + let refundRecipient = tx.refundRecipient ?? ethers.constants.AddressZero; const args: [BigNumberish, Address, Address, BigNumberish, BigNumberish, BigNumberish, BigNumberish, Address] = [ (await this._providerL2().getNetwork()).chainId, to, token, - BigNumber.from(0), // TODO CHANGE THIS + await overrides.value, // TODO CHANGE THIS amount, tx.l2GasLimit, tx.gasPerPubdataByte, refundRecipient, ]; - overrides.value ??= baseCost.add(operatorTip); - await checkBaseCost(baseCost, overrides.value); + // check if we're depositing weth + let l2WethToken = ethers.constants.AddressZero; + try { + l2WethToken = await bridgeContracts.weth.l2TokenAddress(tx.token); + } catch (e) {} + + const bridge = + l2WethToken != ethers.constants.AddressZero + ? bridgeContracts.weth + : bridgeContracts.erc20; + return await bridge.populateTransaction.deposit(...args, overrides); + } else if (token == ETH_ADDRESS) { + // we are depositing eth into a non-eth based chain. We go through the weth bridge. + overrides.value ??= amount; + await checkBaseCost(baseCost, baseCost.add(operatorTip)); // this is base token, not eth + + let refundRecipient = tx.refundRecipient ?? ethers.constants.AddressZero; + const args: [BigNumberish, Address, Address, BigNumberish, BigNumberish, BigNumberish, BigNumberish, Address] = [ + (await this._providerL2().getNetwork()).chainId, + to, + ETH_ADDRESS_IN_CONTRACTS, + baseCost.add(operatorTip), // of the base token, not eth + 0, // amount of weth deposited is 0, eth is in msg.value + tx.l2GasLimit, + tx.gasPerPubdataByte, + refundRecipient, + ]; + + return await bridgeContracts.weth.populateTransaction.deposit(...args, overrides); + } else if (token == baseTokenAddress){ + overrides.value ??= 0; + // we are bridging the base token to a non-eth based chain. We go through the bridgehub + return { + contractAddress: to, + calldata: "0x", + mintValue: baseCost.add(operatorTip).add(amount), + l2Value: amount, + // For some reason typescript can not deduce that we've already set the + // tx.l2GasLimit + l2GasLimit: tx.l2GasLimit!, + ...tx, + }; + } else { + // we are bridging not eth and not the base token to a non-eth based chain + let mintValue = baseCost.add(operatorTip); + await checkBaseCost(baseCost, mintValue); + overrides.value ??= 0; + let refundRecipient = tx.refundRecipient ?? ethers.constants.AddressZero; + const args: [BigNumberish, Address, Address, BigNumberish, BigNumberish, BigNumberish, BigNumberish, Address] = [ + (await this._providerL2().getNetwork()).chainId, + to, + token, + mintValue, + amount, + tx.l2GasLimit, + tx.gasPerPubdataByte, + refundRecipient, + ]; + + // check if we're depositing weth let l2WethToken = ethers.constants.AddressZero; try { l2WethToken = await bridgeContracts.weth.l2TokenAddress(tx.token); @@ -578,8 +765,9 @@ export function AdapterL1>(Base: TBase) { const contractAddress = await this._providerL2().getBridgehubContractAddress(); const bridgehub = IBridgehubFactory.connect(contractAddress, this._signerL1()); + const wethBridge = IL1BridgeFactory.connect(await bridgehub.wethBridge(), this._signerL1()); - return await bridgehub.finalizeEthWithdrawal( + return await wethBridge.finalizeWithdrawal( (await this._providerL2().getNetwork()).chainId, l1BatchNumber, l2MessageIndex, @@ -704,7 +892,9 @@ export function AdapterL1>(Base: TBase) { async getRequestExecuteTx(transaction: { contractAddress: Address; calldata: BytesLike; + payer?: Address; l2GasLimit?: BigNumberish; + mintValue?: BigNumberish; l2Value?: BigNumberish; factoryDeps?: ethers.BytesLike[]; operatorTip?: BigNumberish; @@ -713,9 +903,13 @@ export function AdapterL1>(Base: TBase) { overrides?: ethers.PayableOverrides; }): Promise { const bridgehub = await this.getBridgehubContract(); + const chainId = (await this._providerL2().getNetwork()).chainId; + const ethIsBaseToken = (await bridgehub.baseToken(chainId) == ETH_ADDRESS_IN_CONTRACTS); const {...tx} = transaction; tx.l2Value ??= BigNumber.from(0); + tx.payer ??= await this.getAddress(); + tx.mintValue ??= BigNumber.from(0); tx.operatorTip ??= BigNumber.from(0); tx.factoryDeps ??= []; tx.overrides ??= {}; @@ -725,6 +919,8 @@ export function AdapterL1>(Base: TBase) { const { contractAddress, + payer, + mintValue, l2Value, calldata, l2GasLimit, @@ -746,20 +942,19 @@ export function AdapterL1>(Base: TBase) { overrides.value ??= baseCost.add(operatorTip).add(l2Value); - await checkBaseCost(baseCost, overrides.value); + await checkBaseCost(baseCost, ethIsBaseToken? overrides.value : mintValue); - return await bridgehub.populateTransaction.requestL2Transaction( - { - chainId: (await this._providerL2().getNetwork()).chainId, + return await bridgehub.populateTransaction.requestL2Transaction({ + chainId, + payer: payer, l2Contract: contractAddress, + mintValue: mintValue, l2Value: l2Value, l2Calldata: calldata, l2GasLimit: l2GasLimit, l2GasPerPubdataByteLimit: REQUIRED_L1_TO_L2_GAS_PER_PUBDATA_LIMIT, factoryDeps: factoryDeps, refundRecipient: refundRecipient, - payer: null, // TODO CHANGE THIS - mintValue: BigNumber.from(0) // TODO CHANGE THIS }, overrides, ); diff --git a/src/provider.ts b/src/provider.ts index f78a8620..1e552918 100644 --- a/src/provider.ts +++ b/src/provider.ts @@ -47,6 +47,7 @@ let defaultFormatter: Formatter = null; export class Provider extends ethers.providers.JsonRpcProvider { private static _nextPollId = 1; protected contractAddresses: { + bridgehubContract?: Address; mainContract?: Address; erc20BridgeL1?: Address; erc20BridgeL2?: Address; @@ -554,8 +555,15 @@ export class Provider extends ethers.providers.JsonRpcProvider { } async getBridgehubContractAddress(): Promise
{ + if (!this.contractAddresses.bridgehubContract) { + this.contractAddresses.bridgehubContract = await this.send("zks_getBridgehubContract", []); + } + return this.contractAddresses.bridgehubContract; + } + + async getMainContractAddress(): Promise
{ if (!this.contractAddresses.mainContract) { - this.contractAddresses.mainContract = await this.send("zks_getBridgehubContract", []); + this.contractAddresses.mainContract = await this.send("zks_getMainContract", []); } return this.contractAddresses.mainContract; } @@ -825,7 +833,7 @@ export class Provider extends ethers.providers.JsonRpcProvider { l1TxResponse: ethers.providers.TransactionResponse, ): Promise { const receipt = await l1TxResponse.wait(); - const l2Hash = getL2HashFromPriorityOp(receipt, await this.getBridgehubContractAddress()); + const l2Hash = getL2HashFromPriorityOp(receipt, await this.getMainContractAddress()); let status = null; do { diff --git a/src/utils.ts b/src/utils.ts index fa1330ae..24784324 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -18,7 +18,8 @@ import { AbiCoder } from "ethers/lib/utils"; export * from "./paymaster-utils"; -export const ZKSYNC_MAIN_ABI = new utils.Interface(require("../abi/IZkSync.json")); +export const ZKSYNC_MAIN_ABI = new utils.Interface(require("../abi/IStateTransitionChain.json").abi); +export const BRIDGEHUB_ABI = new utils.Interface(require("../abi/IBridgehub.json").abi); export const CONTRACT_DEPLOYER = new utils.Interface(require("../abi/IContractDeployer.json")); export const L1_MESSENGER = new utils.Interface(require("../abi/IL1Messenger.json")); export const IERC20 = new utils.Interface(require("../abi/IERC20.json")); @@ -29,6 +30,8 @@ export const NONCE_HOLDER_ABI = new utils.Interface(require("../abi/INonceHolder export const PAYMASTER_FLOW_ABI = new utils.Interface(require("../abi/IPaymasterFlow.json")); export const ETH_ADDRESS = "0x0000000000000000000000000000000000000000"; +// in the contracts we can't use the zero address, we we use one +export const ETH_ADDRESS_IN_CONTRACTS = "0x0000000000000000000000000000000000000001"; export const BOOTLOADER_FORMAL_ADDRESS = "0x0000000000000000000000000000000000008001"; export const CONTRACT_DEPLOYER_ADDRESS = "0x0000000000000000000000000000000000008006"; export const L1_MESSENGER_ADDRESS = "0x0000000000000000000000000000000000008008";