From 1ffd39aeda718746142231c9775964f5c0148216 Mon Sep 17 00:00:00 2001 From: petarTxFusion Date: Mon, 19 Aug 2024 16:04:34 +0200 Subject: [PATCH] feat(provider): add `estimateDefaultBridgeDepositL2Gas` and `estimateCustomBridgeDepositL2Gas` --- src/provider.ts | 167 +++++++++++++++++++++++++++++ tests/integration/provider.test.ts | 64 ++++++++++- 2 files changed, 229 insertions(+), 2 deletions(-) diff --git a/src/provider.ts b/src/provider.ts index 728524e..41a0848 100644 --- a/src/provider.ts +++ b/src/provider.ts @@ -62,6 +62,9 @@ import { L2_BASE_TOKEN_ADDRESS, LEGACY_ETH_ADDRESS, isAddressEq, + getERC20DefaultBridgeData, + getERC20BridgeCalldata, + applyL1ToL2Alias, } from './utils'; import {Signer} from './signer'; @@ -1104,6 +1107,170 @@ export function JsonRpcApiProvider< }; } + /** + * Returns an estimation of the L2 gas required for token bridging via the default ERC20 bridge. + * + * @param providerL1 The Ethers provider for the L1 network. + * @param providerL2 The ZKsync provider for the L2 network. + * @param token The address of the token to be bridged. + * @param amount The deposit amount. + * @param to The recipient address on the L2 network. + * @param from The sender address on the L1 network. + * @param gasPerPubdataByte The current gas per byte of pubdata. + * + * @see + * {@link https://docs.zksync.io/build/developer-reference/bridging-asset.html#default-bridges Default bridges documentation}. + * + * @example + * + * import { Provider, utils, types } from "zksync-ethers"; + * import { ethers } from "ethers"; + * + * const provider = Provider.getDefaultProvider(types.Network.Sepolia); + * const ethProvider = ethers.getDefaultProvider("sepolia"); + * + * const token = "0x0000000000000000000000000000000000000001"; + * const amount = 5; + * const to = "0x36615Cf349d7F6344891B1e7CA7C72883F5dc049"; + * const from = "0x36615Cf349d7F6344891B1e7CA7C72883F5dc049"; + * const gasPerPubdataByte = utils.REQUIRED_L1_TO_L2_GAS_PER_PUBDATA_LIMIT; + * + * const gas = await utils.estimateCustomBridgeDepositL2Gas( + * ethProvider, + * provider, + * token, + * amount, + * to, + * from, + * gasPerPubdataByte + * ); + * // gas = 355_704 + */ + async estimateDefaultBridgeDepositL2Gas( + providerL1: ethers.Provider, + token: Address, + amount: BigNumberish, + to: Address, + from?: Address, + gasPerPubdataByte?: BigNumberish + ): Promise { + // If the `from` address is not provided, we use a random address, because + // due to storage slot aggregation, the gas estimation will depend on the address + // and so estimation for the zero address may be smaller than for the sender. + from ??= ethers.Wallet.createRandom().address; + if (await this.isBaseToken(token)) { + return await this.estimateL1ToL2Execute({ + contractAddress: to, + gasPerPubdataByte: gasPerPubdataByte, + caller: from, + calldata: '0x', + l2Value: amount, + }); + } else { + const bridgeAddresses = await this.getDefaultBridgeAddresses(); + + const value = 0; + const l1BridgeAddress = bridgeAddresses.sharedL1; + const l2BridgeAddress = bridgeAddresses.sharedL2; + const bridgeData = await getERC20DefaultBridgeData(token, providerL1); + + return await this.estimateCustomBridgeDepositL2Gas( + l1BridgeAddress, + l2BridgeAddress, + isAddressEq(token, LEGACY_ETH_ADDRESS) + ? ETH_ADDRESS_IN_CONTRACTS + : token, + amount, + to, + bridgeData, + from, + gasPerPubdataByte, + value + ); + } + } + + /** + * Returns an estimation of the L2 gas required for token bridging via the custom ERC20 bridge. + * + * @param providerL2 The ZKsync provider for the L2 network. + * @param l1BridgeAddress The address of the custom L1 bridge. + * @param l2BridgeAddress The address of the custom L2 bridge. + * @param token The address of the token to be bridged. + * @param amount The deposit amount. + * @param to The recipient address on the L2 network. + * @param bridgeData Additional bridge data. + * @param from The sender address on the L1 network. + * @param gasPerPubdataByte The current gas per byte of pubdata. + * @param l2Value The `msg.value` of L2 transaction. + * + * @see + * {@link https://docs.zksync.io/build/developer-reference/bridging-asset.html#custom-bridges-on-l1-and-l2 Custom bridges documentation}. + * + * @example + * + * import { Provider, utils, types } from "zksync-ethers"; + * + * const provider = Provider.getDefaultProvider(types.Network.Sepolia); + * + * const l1BridgeAddress = "0x3e8b2fe58675126ed30d0d12dea2a9bda72d18ae"; + * const l2BridgeAddress = "0x681a1afdc2e06776816386500d2d461a6c96cb45"; + * const token = "0x56E69Fa1BB0d1402c89E3A4E3417882DeA6B14Be"; + * const amount = 5; + * const to = "0x36615Cf349d7F6344891B1e7CA7C72883F5dc049"; + * const bridgeData = "0x00000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000 + * 0000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000016000000000000000000000 + * 000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000020000000000 + * 000000000000000000000000000000000000000000000000000000543726f776e0000000000000000000000000000000000000000000000000000 + * 000000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000 + * 0000000000020000000000000000000000000000000000000000000000000000000000000000543726f776e000000000000000000000000000000 + * 000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000 + * 00000000000000000000000000000000012"; + * const from = "0x36615Cf349d7F6344891B1e7CA7C72883F5dc049"; + * const gasPerPubdataByte = utils.REQUIRED_L1_TO_L2_GAS_PER_PUBDATA_LIMIT; + * const l2Value = 0; + * + * const gas = await utils.estimateCustomBridgeDepositL2Gas( + * provider, + * l1BridgeAddress, + * l2BridgeAddress, + * token, + * amount, + * to, + * bridgeData, + * from, + * gasPerPubdataByte, + * l2Value + * ); + * // gas = 683_830 + */ + async estimateCustomBridgeDepositL2Gas( + l1BridgeAddress: Address, + l2BridgeAddress: Address, + token: Address, + amount: BigNumberish, + to: Address, + bridgeData: BytesLike, + from: Address, + gasPerPubdataByte?: BigNumberish, + l2Value?: BigNumberish + ): Promise { + const calldata = await getERC20BridgeCalldata( + token, + from, + to, + amount, + bridgeData + ); + return await this.estimateL1ToL2Execute({ + caller: applyL1ToL2Alias(l1BridgeAddress), + contractAddress: l2BridgeAddress, + gasPerPubdataByte: gasPerPubdataByte, + calldata: calldata, + l2Value: l2Value, + }); + } + /** * Returns gas estimation for an L1 to L2 execute operation. * diff --git a/tests/integration/provider.test.ts b/tests/integration/provider.test.ts index a2fe17c..83a48ae 100644 --- a/tests/integration/provider.test.ts +++ b/tests/integration/provider.test.ts @@ -10,13 +10,14 @@ import { DAI_L1, APPROVAL_TOKEN, PAYMASTER, - L2_CHAIN_URL, -} from '../utils'; + L2_CHAIN_URL, L1_CHAIN_URL +} from "../utils"; import { EIP712_TX_TYPE } from "../../src/utils"; describe('Provider', () => { const provider = new Provider(L2_CHAIN_URL); const wallet = new Wallet(PRIVATE_KEY1, provider); + const ethProvider = ethers.getDefaultProvider(L1_CHAIN_URL); let receipt: types.TransactionReceipt; let baseToken: string; @@ -916,6 +917,65 @@ describe('Provider', () => { }); }); + describe('#estimateDefaultBridgeDepositL2Gas()', () => { + if (IS_ETH_BASED) { + it('should return estimation for ETH token', async () => { + const result = await provider.estimateDefaultBridgeDepositL2Gas( + ethProvider, + utils.LEGACY_ETH_ADDRESS, + ethers.parseEther('1'), + ADDRESS2, + ADDRESS1 + ); + expect(result > 0n).to.be.true; + }); + + it('should return estimation for DAI token', async () => { + const result = await provider.estimateDefaultBridgeDepositL2Gas( + ethProvider, + DAI_L1, + 5, + ADDRESS2, + ADDRESS1 + ); + expect(result > 0n).to.be.true; + }); + } else { + it('should return estimation for ETH token', async () => { + const result = await provider.estimateDefaultBridgeDepositL2Gas( + ethProvider, + utils.LEGACY_ETH_ADDRESS, + ethers.parseEther('1'), + ADDRESS2, + ADDRESS1 + ); + expect(result > 0n).to.be.true; + }); + + it('should return estimation for base token', async () => { + const result = await provider.estimateDefaultBridgeDepositL2Gas( + ethProvider, + await provider.getBaseTokenContractAddress(), + ethers.parseEther('1'), + ADDRESS2, + ADDRESS1 + ); + expect(result > 0n).to.be.true; + }); + + it('should return estimation for DAI token', async () => { + const result = await provider.estimateDefaultBridgeDepositL2Gas( + ethProvider, + DAI_L1, + 5, + ADDRESS2, + ADDRESS1 + ); + expect(result > 0n).to.be.true; + }); + } + }); + describe('#getFilterChanges()', () => { it('should return the filtered logs', async () => { const filter = await provider.newFilter({