From 61f6dc8dda65f06e1ccd371aa174b948c579d926 Mon Sep 17 00:00:00 2001 From: Muhammad-Altabba <24407834+Muhammad-Altabba@users.noreply.github.com> Date: Sat, 25 May 2024 20:08:44 +0200 Subject: [PATCH 01/30] add ABI files and some types and do few naming and formatting --- src/contracts/ERC721Token.ts | 173 -- src/contracts/IBridgehub.ts | 631 +++++ src/contracts/IContractDeployer.ts | 410 ++++ src/contracts/IERC1271.ts | 43 + src/contracts/{ERC20Token.ts => IERC20.ts} | 2 +- src/contracts/IEthToken.ts | 245 ++ src/contracts/IL1Bridge.ts | 512 ++++ src/contracts/IL1ERC20Bridge.ts | 377 +++ src/contracts/IL1Messenger.ts | 146 ++ src/contracts/IL1SharedBridge.ts | 692 ++++++ src/contracts/{L2Bridge.ts => IL2Bridge.ts} | 2 +- src/contracts/INonceHolder.ts | 219 ++ src/contracts/IPaymasterFlow.ts | 38 + src/contracts/ITestnetERC20Token.ts | 39 + src/contracts/IZkSync.ts | 1622 +++++++++++++ src/contracts/IZkSyncStateTransition.ts | 2403 +++++++++++++++++++ src/plugin.ts | 28 +- src/types.ts | 308 ++- test/fixtures.ts | 83 +- 19 files changed, 7712 insertions(+), 261 deletions(-) delete mode 100644 src/contracts/ERC721Token.ts create mode 100644 src/contracts/IBridgehub.ts create mode 100644 src/contracts/IContractDeployer.ts create mode 100644 src/contracts/IERC1271.ts rename src/contracts/{ERC20Token.ts => IERC20.ts} (99%) create mode 100644 src/contracts/IEthToken.ts create mode 100644 src/contracts/IL1Bridge.ts create mode 100644 src/contracts/IL1ERC20Bridge.ts create mode 100644 src/contracts/IL1Messenger.ts create mode 100644 src/contracts/IL1SharedBridge.ts rename src/contracts/{L2Bridge.ts => IL2Bridge.ts} (98%) create mode 100644 src/contracts/INonceHolder.ts create mode 100644 src/contracts/IPaymasterFlow.ts create mode 100644 src/contracts/ITestnetERC20Token.ts create mode 100644 src/contracts/IZkSync.ts create mode 100644 src/contracts/IZkSyncStateTransition.ts diff --git a/src/contracts/ERC721Token.ts b/src/contracts/ERC721Token.ts deleted file mode 100644 index 973db8e..0000000 --- a/src/contracts/ERC721Token.ts +++ /dev/null @@ -1,173 +0,0 @@ -/* -This file is part of web3.js. - -web3.js is free software: you can redistribute it and/or modify -it under the terms of the GNU Lesser General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -web3.js is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU Lesser General Public License for more details. - -You should have received a copy of the GNU Lesser General Public License -along with web3.js. If not, see . -*/ - -export const ERC721TokenAbi = [ - { inputs: [], stateMutability: 'nonpayable', type: 'constructor' }, - { - anonymous: false, - inputs: [ - { indexed: true, internalType: 'address', name: 'owner', type: 'address' }, - { indexed: true, internalType: 'address', name: 'approved', type: 'address' }, - { indexed: true, internalType: 'uint256', name: 'tokenId', type: 'uint256' }, - ], - name: 'Approval', - type: 'event', - }, - { - anonymous: false, - inputs: [ - { indexed: true, internalType: 'address', name: 'owner', type: 'address' }, - { indexed: true, internalType: 'address', name: 'operator', type: 'address' }, - { indexed: false, internalType: 'bool', name: 'approved', type: 'bool' }, - ], - name: 'ApprovalForAll', - type: 'event', - }, - { - anonymous: false, - inputs: [ - { indexed: true, internalType: 'address', name: 'from', type: 'address' }, - { indexed: true, internalType: 'address', name: 'to', type: 'address' }, - { indexed: true, internalType: 'uint256', name: 'tokenId', type: 'uint256' }, - ], - name: 'Transfer', - type: 'event', - }, - { - inputs: [ - { internalType: 'address', name: 'to', type: 'address' }, - { internalType: 'uint256', name: 'tokenId', type: 'uint256' }, - ], - name: 'approve', - outputs: [], - stateMutability: 'nonpayable', - type: 'function', - }, - { - inputs: [ - { internalType: 'address', name: 'player', type: 'address' }, - { internalType: 'string', name: 'tokenURI', type: 'string' }, - ], - name: 'awardItem', - outputs: [{ internalType: 'uint256', name: '', type: 'uint256' }], - stateMutability: 'nonpayable', - type: 'function', - }, - { - inputs: [{ internalType: 'address', name: 'owner', type: 'address' }], - name: 'balanceOf', - outputs: [{ internalType: 'uint256', name: '', type: 'uint256' }], - stateMutability: 'view', - type: 'function', - }, - { - inputs: [{ internalType: 'uint256', name: 'tokenId', type: 'uint256' }], - name: 'getApproved', - outputs: [{ internalType: 'address', name: '', type: 'address' }], - stateMutability: 'view', - type: 'function', - }, - { - inputs: [ - { internalType: 'address', name: 'owner', type: 'address' }, - { internalType: 'address', name: 'operator', type: 'address' }, - ], - name: 'isApprovedForAll', - outputs: [{ internalType: 'bool', name: '', type: 'bool' }], - stateMutability: 'view', - type: 'function', - }, - { - inputs: [], - name: 'name', - outputs: [{ internalType: 'string', name: '', type: 'string' }], - stateMutability: 'view', - type: 'function', - }, - { - inputs: [{ internalType: 'uint256', name: 'tokenId', type: 'uint256' }], - name: 'ownerOf', - outputs: [{ internalType: 'address', name: '', type: 'address' }], - stateMutability: 'view', - type: 'function', - }, - { - inputs: [ - { internalType: 'address', name: 'from', type: 'address' }, - { internalType: 'address', name: 'to', type: 'address' }, - { internalType: 'uint256', name: 'tokenId', type: 'uint256' }, - ], - name: 'safeTransferFrom', - outputs: [], - stateMutability: 'nonpayable', - type: 'function', - }, - { - inputs: [ - { internalType: 'address', name: 'from', type: 'address' }, - { internalType: 'address', name: 'to', type: 'address' }, - { internalType: 'uint256', name: 'tokenId', type: 'uint256' }, - { internalType: 'bytes', name: 'data', type: 'bytes' }, - ], - name: 'safeTransferFrom', - outputs: [], - stateMutability: 'nonpayable', - type: 'function', - }, - { - inputs: [ - { internalType: 'address', name: 'operator', type: 'address' }, - { internalType: 'bool', name: 'approved', type: 'bool' }, - ], - name: 'setApprovalForAll', - outputs: [], - stateMutability: 'nonpayable', - type: 'function', - }, - { - inputs: [{ internalType: 'bytes4', name: 'interfaceId', type: 'bytes4' }], - name: 'supportsInterface', - outputs: [{ internalType: 'bool', name: '', type: 'bool' }], - stateMutability: 'view', - type: 'function', - }, - { - inputs: [], - name: 'symbol', - outputs: [{ internalType: 'string', name: '', type: 'string' }], - stateMutability: 'view', - type: 'function', - }, - { - inputs: [{ internalType: 'uint256', name: 'tokenId', type: 'uint256' }], - name: 'tokenURI', - outputs: [{ internalType: 'string', name: '', type: 'string' }], - stateMutability: 'view', - type: 'function', - }, - { - inputs: [ - { internalType: 'address', name: 'from', type: 'address' }, - { internalType: 'address', name: 'to', type: 'address' }, - { internalType: 'uint256', name: 'tokenId', type: 'uint256' }, - ], - name: 'transferFrom', - outputs: [], - stateMutability: 'nonpayable', - type: 'function', - }, -] as const; diff --git a/src/contracts/IBridgehub.ts b/src/contracts/IBridgehub.ts new file mode 100644 index 0000000..d1d1468 --- /dev/null +++ b/src/contracts/IBridgehub.ts @@ -0,0 +1,631 @@ +export const IBridgehubABI = [ + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: 'address', + name: 'oldAdmin', + type: 'address', + }, + { + indexed: true, + internalType: 'address', + name: 'newAdmin', + type: 'address', + }, + ], + name: 'NewAdmin', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: 'uint256', + name: 'chainId', + type: 'uint256', + }, + { + indexed: false, + internalType: 'address', + name: 'stateTransitionManager', + type: 'address', + }, + { + indexed: true, + internalType: 'address', + name: 'chainGovernance', + type: 'address', + }, + ], + name: 'NewChain', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: 'address', + name: 'oldPendingAdmin', + type: 'address', + }, + { + indexed: true, + internalType: 'address', + name: 'newPendingAdmin', + type: 'address', + }, + ], + name: 'NewPendingAdmin', + type: 'event', + }, + { + inputs: [], + name: 'acceptAdmin', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'address', + name: '_stateTransitionManager', + type: 'address', + }, + ], + name: 'addStateTransitionManager', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'address', + name: '_token', + type: 'address', + }, + ], + name: 'addToken', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'uint256', + name: '_chainId', + type: 'uint256', + }, + ], + name: 'baseToken', + outputs: [ + { + internalType: 'address', + name: '', + type: 'address', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { + internalType: 'uint256', + name: '_chainId', + type: 'uint256', + }, + { + internalType: 'address', + name: '_stateTransitionManager', + type: 'address', + }, + { + internalType: 'address', + name: '_baseToken', + type: 'address', + }, + { + internalType: 'uint256', + name: '_salt', + type: 'uint256', + }, + { + internalType: 'address', + name: '_admin', + type: 'address', + }, + { + internalType: 'bytes', + name: '_initData', + type: 'bytes', + }, + ], + name: 'createNewChain', + outputs: [ + { + internalType: 'uint256', + name: 'chainId', + type: 'uint256', + }, + ], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'uint256', + name: '_chainId', + type: 'uint256', + }, + ], + name: 'getStateTransition', + outputs: [ + { + internalType: 'address', + name: '', + type: 'address', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { + internalType: 'uint256', + name: '_chainId', + type: 'uint256', + }, + { + internalType: 'uint256', + name: '_gasPrice', + type: 'uint256', + }, + { + internalType: 'uint256', + name: '_l2GasLimit', + type: 'uint256', + }, + { + internalType: 'uint256', + name: '_l2GasPerPubdataByteLimit', + type: 'uint256', + }, + ], + name: 'l2TransactionBaseCost', + outputs: [ + { + internalType: 'uint256', + name: '', + type: 'uint256', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { + internalType: 'uint256', + name: '_chainId', + type: 'uint256', + }, + { + internalType: 'bytes32', + name: '_l2TxHash', + type: 'bytes32', + }, + { + internalType: 'uint256', + name: '_l2BatchNumber', + type: 'uint256', + }, + { + internalType: 'uint256', + name: '_l2MessageIndex', + type: 'uint256', + }, + { + internalType: 'uint16', + name: '_l2TxNumberInBatch', + type: 'uint16', + }, + { + internalType: 'bytes32[]', + name: '_merkleProof', + type: 'bytes32[]', + }, + { + internalType: 'enum TxStatus', + name: '_status', + type: 'uint8', + }, + ], + name: 'proveL1ToL2TransactionStatus', + outputs: [ + { + internalType: 'bool', + name: '', + type: 'bool', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { + internalType: 'uint256', + name: '_chainId', + type: 'uint256', + }, + { + internalType: 'uint256', + name: '_batchNumber', + type: 'uint256', + }, + { + internalType: 'uint256', + name: '_index', + type: 'uint256', + }, + { + components: [ + { + internalType: 'uint8', + name: 'l2ShardId', + type: 'uint8', + }, + { + internalType: 'bool', + name: 'isService', + type: 'bool', + }, + { + internalType: 'uint16', + name: 'txNumberInBatch', + type: 'uint16', + }, + { + internalType: 'address', + name: 'sender', + type: 'address', + }, + { + internalType: 'bytes32', + name: 'key', + type: 'bytes32', + }, + { + internalType: 'bytes32', + name: 'value', + type: 'bytes32', + }, + ], + internalType: 'struct L2Log', + name: '_log', + type: 'tuple', + }, + { + internalType: 'bytes32[]', + name: '_proof', + type: 'bytes32[]', + }, + ], + name: 'proveL2LogInclusion', + outputs: [ + { + internalType: 'bool', + name: '', + type: 'bool', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { + internalType: 'uint256', + name: '_chainId', + type: 'uint256', + }, + { + internalType: 'uint256', + name: '_batchNumber', + type: 'uint256', + }, + { + internalType: 'uint256', + name: '_index', + type: 'uint256', + }, + { + components: [ + { + internalType: 'uint16', + name: 'txNumberInBatch', + type: 'uint16', + }, + { + internalType: 'address', + name: 'sender', + type: 'address', + }, + { + internalType: 'bytes', + name: 'data', + type: 'bytes', + }, + ], + internalType: 'struct L2Message', + name: '_message', + type: 'tuple', + }, + { + internalType: 'bytes32[]', + name: '_proof', + type: 'bytes32[]', + }, + ], + name: 'proveL2MessageInclusion', + outputs: [ + { + internalType: 'bool', + name: '', + type: 'bool', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { + internalType: 'address', + name: '_stateTransitionManager', + type: 'address', + }, + ], + name: 'removeStateTransitionManager', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + components: [ + { + internalType: 'uint256', + name: 'chainId', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'mintValue', + type: 'uint256', + }, + { + internalType: 'address', + name: 'l2Contract', + type: 'address', + }, + { + internalType: 'uint256', + name: 'l2Value', + type: 'uint256', + }, + { + internalType: 'bytes', + name: 'l2Calldata', + type: 'bytes', + }, + { + internalType: 'uint256', + name: 'l2GasLimit', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'l2GasPerPubdataByteLimit', + type: 'uint256', + }, + { + internalType: 'bytes[]', + name: 'factoryDeps', + type: 'bytes[]', + }, + { + internalType: 'address', + name: 'refundRecipient', + type: 'address', + }, + ], + internalType: 'struct L2TransactionRequestDirect', + name: '_request', + type: 'tuple', + }, + ], + name: 'requestL2TransactionDirect', + outputs: [ + { + internalType: 'bytes32', + name: 'canonicalTxHash', + type: 'bytes32', + }, + ], + stateMutability: 'payable', + type: 'function', + }, + { + inputs: [ + { + components: [ + { + internalType: 'uint256', + name: 'chainId', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'mintValue', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'l2Value', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'l2GasLimit', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'l2GasPerPubdataByteLimit', + type: 'uint256', + }, + { + internalType: 'address', + name: 'refundRecipient', + type: 'address', + }, + { + internalType: 'address', + name: 'secondBridgeAddress', + type: 'address', + }, + { + internalType: 'uint256', + name: 'secondBridgeValue', + type: 'uint256', + }, + { + internalType: 'bytes', + name: 'secondBridgeCalldata', + type: 'bytes', + }, + ], + internalType: 'struct L2TransactionRequestTwoBridgesOuter', + name: '_request', + type: 'tuple', + }, + ], + name: 'requestL2TransactionTwoBridges', + outputs: [ + { + internalType: 'bytes32', + name: 'canonicalTxHash', + type: 'bytes32', + }, + ], + stateMutability: 'payable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'address', + name: '_newPendingAdmin', + type: 'address', + }, + ], + name: 'setPendingAdmin', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'address', + name: '_sharedBridge', + type: 'address', + }, + ], + name: 'setSharedBridge', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [], + name: 'sharedBridge', + outputs: [ + { + internalType: 'contract IL1SharedBridge', + name: '', + type: 'address', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { + internalType: 'uint256', + name: '_chainId', + type: 'uint256', + }, + ], + name: 'stateTransitionManager', + outputs: [ + { + internalType: 'address', + name: '', + type: 'address', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { + internalType: 'address', + name: '_stateTransitionManager', + type: 'address', + }, + ], + name: 'stateTransitionManagerIsRegistered', + outputs: [ + { + internalType: 'bool', + name: '', + type: 'bool', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { + internalType: 'address', + name: '_baseToken', + type: 'address', + }, + ], + name: 'tokenIsRegistered', + outputs: [ + { + internalType: 'bool', + name: '', + type: 'bool', + }, + ], + stateMutability: 'view', + type: 'function', + }, +] as const; diff --git a/src/contracts/IContractDeployer.ts b/src/contracts/IContractDeployer.ts new file mode 100644 index 0000000..37e4041 --- /dev/null +++ b/src/contracts/IContractDeployer.ts @@ -0,0 +1,410 @@ +export const IContractDeployerABI = [ + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: 'address', + name: 'accountAddress', + type: 'address', + }, + { + indexed: false, + internalType: 'enum IContractDeployer.AccountNonceOrdering', + name: 'nonceOrdering', + type: 'uint8', + }, + ], + name: 'AccountNonceOrderingUpdated', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: 'address', + name: 'accountAddress', + type: 'address', + }, + { + indexed: false, + internalType: 'enum IContractDeployer.AccountAbstractionVersion', + name: 'aaVersion', + type: 'uint8', + }, + ], + name: 'AccountVersionUpdated', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: 'address', + name: 'deployerAddress', + type: 'address', + }, + { + indexed: true, + internalType: 'bytes32', + name: 'bytecodeHash', + type: 'bytes32', + }, + { + indexed: true, + internalType: 'address', + name: 'contractAddress', + type: 'address', + }, + ], + name: 'ContractDeployed', + type: 'event', + }, + { + inputs: [ + { + internalType: 'bytes32', + name: '_salt', + type: 'bytes32', + }, + { + internalType: 'bytes32', + name: '_bytecodeHash', + type: 'bytes32', + }, + { + internalType: 'bytes', + name: '_input', + type: 'bytes', + }, + ], + name: 'create', + outputs: [ + { + internalType: 'address', + name: '', + type: 'address', + }, + ], + stateMutability: 'payable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'bytes32', + name: '_salt', + type: 'bytes32', + }, + { + internalType: 'bytes32', + name: '_bytecodeHash', + type: 'bytes32', + }, + { + internalType: 'bytes', + name: '_input', + type: 'bytes', + }, + ], + name: 'create2', + outputs: [ + { + internalType: 'address', + name: '', + type: 'address', + }, + ], + stateMutability: 'payable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'bytes32', + name: '_salt', + type: 'bytes32', + }, + { + internalType: 'bytes32', + name: '_bytecodeHash', + type: 'bytes32', + }, + { + internalType: 'bytes', + name: '_input', + type: 'bytes', + }, + { + internalType: 'enum IContractDeployer.AccountAbstractionVersion', + name: '_aaVersion', + type: 'uint8', + }, + ], + name: 'create2Account', + outputs: [ + { + internalType: 'address', + name: '', + type: 'address', + }, + ], + stateMutability: 'payable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'bytes32', + name: '', + type: 'bytes32', + }, + { + internalType: 'bytes32', + name: '_bytecodeHash', + type: 'bytes32', + }, + { + internalType: 'bytes', + name: '_input', + type: 'bytes', + }, + { + internalType: 'enum IContractDeployer.AccountAbstractionVersion', + name: '_aaVersion', + type: 'uint8', + }, + ], + name: 'createAccount', + outputs: [ + { + internalType: 'address', + name: '', + type: 'address', + }, + ], + stateMutability: 'payable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'address', + name: '_address', + type: 'address', + }, + ], + name: 'extendedAccountVersion', + outputs: [ + { + internalType: 'enum IContractDeployer.AccountAbstractionVersion', + name: '', + type: 'uint8', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { + components: [ + { + internalType: 'bytes32', + name: 'bytecodeHash', + type: 'bytes32', + }, + { + internalType: 'address', + name: 'newAddress', + type: 'address', + }, + { + internalType: 'bool', + name: 'callConstructor', + type: 'bool', + }, + { + internalType: 'uint256', + name: 'value', + type: 'uint256', + }, + { + internalType: 'bytes', + name: 'input', + type: 'bytes', + }, + ], + internalType: 'struct ContractDeployer.ForceDeployment', + name: '_deployment', + type: 'tuple', + }, + { + internalType: 'address', + name: '_sender', + type: 'address', + }, + ], + name: 'forceDeployOnAddress', + outputs: [], + stateMutability: 'payable', + type: 'function', + }, + { + inputs: [ + { + components: [ + { + internalType: 'bytes32', + name: 'bytecodeHash', + type: 'bytes32', + }, + { + internalType: 'address', + name: 'newAddress', + type: 'address', + }, + { + internalType: 'bool', + name: 'callConstructor', + type: 'bool', + }, + { + internalType: 'uint256', + name: 'value', + type: 'uint256', + }, + { + internalType: 'bytes', + name: 'input', + type: 'bytes', + }, + ], + internalType: 'struct ContractDeployer.ForceDeployment[]', + name: '_deployments', + type: 'tuple[]', + }, + ], + name: 'forceDeployOnAddresses', + outputs: [], + stateMutability: 'payable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'address', + name: '_address', + type: 'address', + }, + ], + name: 'getAccountInfo', + outputs: [ + { + components: [ + { + internalType: 'enum IContractDeployer.AccountAbstractionVersion', + name: 'supportedAAVersion', + type: 'uint8', + }, + { + internalType: 'enum IContractDeployer.AccountNonceOrdering', + name: 'nonceOrdering', + type: 'uint8', + }, + ], + internalType: 'struct IContractDeployer.AccountInfo', + name: 'info', + type: 'tuple', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { + internalType: 'address', + name: '_sender', + type: 'address', + }, + { + internalType: 'uint256', + name: '_senderNonce', + type: 'uint256', + }, + ], + name: 'getNewAddressCreate', + outputs: [ + { + internalType: 'address', + name: 'newAddress', + type: 'address', + }, + ], + stateMutability: 'pure', + type: 'function', + }, + { + inputs: [ + { + internalType: 'address', + name: '_sender', + type: 'address', + }, + { + internalType: 'bytes32', + name: '_bytecodeHash', + type: 'bytes32', + }, + { + internalType: 'bytes32', + name: '_salt', + type: 'bytes32', + }, + { + internalType: 'bytes', + name: '_input', + type: 'bytes', + }, + ], + name: 'getNewAddressCreate2', + outputs: [ + { + internalType: 'address', + name: 'newAddress', + type: 'address', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { + internalType: 'enum IContractDeployer.AccountAbstractionVersion', + name: '_version', + type: 'uint8', + }, + ], + name: 'updateAccountVersion', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'enum IContractDeployer.AccountNonceOrdering', + name: '_nonceOrdering', + type: 'uint8', + }, + ], + name: 'updateNonceOrdering', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, +] as const; diff --git a/src/contracts/IERC1271.ts b/src/contracts/IERC1271.ts new file mode 100644 index 0000000..2028356 --- /dev/null +++ b/src/contracts/IERC1271.ts @@ -0,0 +1,43 @@ +/* +This file is part of web3.js. + +web3.js is free software: you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +web3.js is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License +along with web3.js. If not, see . +*/ + +export const IERC1271ABI = [ + { + inputs: [ + { + internalType: 'bytes32', + name: 'hash', + type: 'bytes32', + }, + { + internalType: 'bytes', + name: 'signature', + type: 'bytes', + }, + ], + name: 'isValidSignature', + outputs: [ + { + internalType: 'bytes4', + name: 'magicValue', + type: 'bytes4', + }, + ], + stateMutability: 'view', + type: 'function', + }, +] as const; diff --git a/src/contracts/ERC20Token.ts b/src/contracts/IERC20.ts similarity index 99% rename from src/contracts/ERC20Token.ts rename to src/contracts/IERC20.ts index 98bdf44..7b64f8f 100644 --- a/src/contracts/ERC20Token.ts +++ b/src/contracts/IERC20.ts @@ -15,7 +15,7 @@ You should have received a copy of the GNU Lesser General Public License along with web3.js. If not, see . */ -export const ERC20TokenAbi = [ +export const IERC20ABI = [ { inputs: [{ internalType: 'uint256', name: 'initialSupply', type: 'uint256' }], stateMutability: 'nonpayable', diff --git a/src/contracts/IEthToken.ts b/src/contracts/IEthToken.ts new file mode 100644 index 0000000..4820993 --- /dev/null +++ b/src/contracts/IEthToken.ts @@ -0,0 +1,245 @@ +export const Abi = [ + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: 'address', + name: 'account', + type: 'address', + }, + { + indexed: false, + internalType: 'uint256', + name: 'amount', + type: 'uint256', + }, + ], + name: 'Mint', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: 'address', + name: 'from', + type: 'address', + }, + { + indexed: true, + internalType: 'address', + name: 'to', + type: 'address', + }, + { + indexed: false, + internalType: 'uint256', + name: 'value', + type: 'uint256', + }, + ], + name: 'Transfer', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: 'address', + name: '_l2Sender', + type: 'address', + }, + { + indexed: true, + internalType: 'address', + name: '_l1Receiver', + type: 'address', + }, + { + indexed: false, + internalType: 'uint256', + name: '_amount', + type: 'uint256', + }, + ], + name: 'Withdrawal', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: 'address', + name: '_l2Sender', + type: 'address', + }, + { + indexed: true, + internalType: 'address', + name: '_l1Receiver', + type: 'address', + }, + { + indexed: false, + internalType: 'uint256', + name: '_amount', + type: 'uint256', + }, + { + indexed: false, + internalType: 'bytes', + name: '_additionalData', + type: 'bytes', + }, + ], + name: 'WithdrawalWithMessage', + type: 'event', + }, + { + inputs: [ + { + internalType: 'uint256', + name: '', + type: 'uint256', + }, + ], + name: 'balanceOf', + outputs: [ + { + internalType: 'uint256', + name: '', + type: 'uint256', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'decimals', + outputs: [ + { + internalType: 'uint8', + name: '', + type: 'uint8', + }, + ], + stateMutability: 'pure', + type: 'function', + }, + { + inputs: [ + { + internalType: 'address', + name: '_account', + type: 'address', + }, + { + internalType: 'uint256', + name: '_amount', + type: 'uint256', + }, + ], + name: 'mint', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [], + name: 'name', + outputs: [ + { + internalType: 'string', + name: '', + type: 'string', + }, + ], + stateMutability: 'pure', + type: 'function', + }, + { + inputs: [], + name: 'symbol', + outputs: [ + { + internalType: 'string', + name: '', + type: 'string', + }, + ], + stateMutability: 'pure', + type: 'function', + }, + { + inputs: [], + name: 'totalSupply', + outputs: [ + { + internalType: 'uint256', + name: '', + type: 'uint256', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { + internalType: 'address', + name: '_from', + type: 'address', + }, + { + internalType: 'address', + name: '_to', + type: 'address', + }, + { + internalType: 'uint256', + name: '_amount', + type: 'uint256', + }, + ], + name: 'transferFromTo', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'address', + name: '_l1Receiver', + type: 'address', + }, + ], + name: 'withdraw', + outputs: [], + stateMutability: 'payable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'address', + name: '_l1Receiver', + type: 'address', + }, + { + internalType: 'bytes', + name: '_additionalData', + type: 'bytes', + }, + ], + name: 'withdrawWithMessage', + outputs: [], + stateMutability: 'payable', + type: 'function', + }, +] as const; diff --git a/src/contracts/IL1Bridge.ts b/src/contracts/IL1Bridge.ts new file mode 100644 index 0000000..3e24678 --- /dev/null +++ b/src/contracts/IL1Bridge.ts @@ -0,0 +1,512 @@ +export const IL1BridgeABI = [ + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: 'uint256', + name: 'chainId', + type: 'uint256', + }, + { + indexed: true, + internalType: 'bytes32', + name: 'txDataHash', + type: 'bytes32', + }, + { + indexed: true, + internalType: 'bytes32', + name: 'l2DepositTxHash', + type: 'bytes32', + }, + ], + name: 'BridgehubDepositFinalized', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: 'uint256', + name: 'chainId', + type: 'uint256', + }, + { + indexed: true, + internalType: 'bytes32', + name: 'txDataHash', + type: 'bytes32', + }, + { + indexed: true, + internalType: 'address', + name: 'from', + type: 'address', + }, + { + indexed: false, + internalType: 'address', + name: 'to', + type: 'address', + }, + { + indexed: false, + internalType: 'address', + name: 'l1Token', + type: 'address', + }, + { + indexed: false, + internalType: 'uint256', + name: 'amount', + type: 'uint256', + }, + ], + name: 'BridgehubDepositInitiatedSharedBridge', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: 'uint256', + name: 'chainId', + type: 'uint256', + }, + { + indexed: true, + internalType: 'address', + name: 'to', + type: 'address', + }, + { + indexed: true, + internalType: 'address', + name: 'l1Token', + type: 'address', + }, + { + indexed: false, + internalType: 'uint256', + name: 'amount', + type: 'uint256', + }, + ], + name: 'ClaimedFailedDepositSharedBridge', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: 'uint256', + name: 'chainId', + type: 'uint256', + }, + { + indexed: true, + internalType: 'bytes32', + name: 'l2DepositTxHash', + type: 'bytes32', + }, + { + indexed: true, + internalType: 'address', + name: 'from', + type: 'address', + }, + { + indexed: false, + internalType: 'address', + name: 'to', + type: 'address', + }, + { + indexed: false, + internalType: 'address', + name: 'l1Token', + type: 'address', + }, + { + indexed: false, + internalType: 'uint256', + name: 'amount', + type: 'uint256', + }, + ], + name: 'DepositInitiatedSharedBridge', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: 'uint256', + name: 'chainId', + type: 'uint256', + }, + { + indexed: true, + internalType: 'address', + name: 'to', + type: 'address', + }, + { + indexed: true, + internalType: 'address', + name: 'l1Token', + type: 'address', + }, + { + indexed: false, + internalType: 'uint256', + name: 'amount', + type: 'uint256', + }, + ], + name: 'WithdrawalFinalizedSharedBridge', + type: 'event', + }, + { + inputs: [], + name: 'bridgehub', + outputs: [ + { + internalType: 'contract IBridgehub', + name: '', + type: 'address', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { + internalType: 'uint256', + name: '_chainId', + type: 'uint256', + }, + { + internalType: 'bytes32', + name: '_txDataHash', + type: 'bytes32', + }, + { + internalType: 'bytes32', + name: '_txHash', + type: 'bytes32', + }, + ], + name: 'bridgehubConfirmL2Transaction', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'uint256', + name: '_chainId', + type: 'uint256', + }, + { + internalType: 'address', + name: '_prevMsgSender', + type: 'address', + }, + { + internalType: 'bytes', + name: '_data', + type: 'bytes', + }, + ], + name: 'bridgehubDeposit', + outputs: [ + { + components: [ + { + internalType: 'bytes32', + name: 'magicValue', + type: 'bytes32', + }, + { + internalType: 'address', + name: 'l2Contract', + type: 'address', + }, + { + internalType: 'bytes', + name: 'l2Calldata', + type: 'bytes', + }, + { + internalType: 'bytes[]', + name: 'factoryDeps', + type: 'bytes[]', + }, + { + internalType: 'bytes32', + name: 'txDataHash', + type: 'bytes32', + }, + ], + internalType: 'struct L2TransactionRequestTwoBridgesInner', + name: 'request', + type: 'tuple', + }, + ], + stateMutability: 'payable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'uint256', + name: '_chainId', + type: 'uint256', + }, + { + internalType: 'address', + name: '_prevMsgSender', + type: 'address', + }, + { + internalType: 'address', + name: '_l1Token', + type: 'address', + }, + { + internalType: 'uint256', + name: '_amount', + type: 'uint256', + }, + ], + name: 'bridgehubDepositBaseToken', + outputs: [], + stateMutability: 'payable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'uint256', + name: '_chainId', + type: 'uint256', + }, + { + internalType: 'address', + name: '_depositSender', + type: 'address', + }, + { + internalType: 'address', + name: '_l1Token', + type: 'address', + }, + { + internalType: 'uint256', + name: '_amount', + type: 'uint256', + }, + { + internalType: 'bytes32', + name: '_l2TxHash', + type: 'bytes32', + }, + { + internalType: 'uint256', + name: '_l2BatchNumber', + type: 'uint256', + }, + { + internalType: 'uint256', + name: '_l2MessageIndex', + type: 'uint256', + }, + { + internalType: 'uint16', + name: '_l2TxNumberInBatch', + type: 'uint16', + }, + { + internalType: 'bytes32[]', + name: '_merkleProof', + type: 'bytes32[]', + }, + ], + name: 'claimFailedDeposit', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'uint256', + name: '_chainId', + type: 'uint256', + }, + { + internalType: 'address', + name: '_l2Receiver', + type: 'address', + }, + { + internalType: 'address', + name: '_l1Token', + type: 'address', + }, + { + internalType: 'uint256', + name: '_mintValue', + type: 'uint256', + }, + { + internalType: 'uint256', + name: '_amount', + type: 'uint256', + }, + { + internalType: 'uint256', + name: '_l2TxGasLimit', + type: 'uint256', + }, + { + internalType: 'uint256', + name: '_l2TxGasPerPubdataByte', + type: 'uint256', + }, + { + internalType: 'address', + name: '_refundRecipient', + type: 'address', + }, + ], + name: 'deposit', + outputs: [ + { + internalType: 'bytes32', + name: 'txHash', + type: 'bytes32', + }, + ], + stateMutability: 'payable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'uint256', + name: '_chainId', + type: 'uint256', + }, + { + internalType: 'bytes32', + name: '_l2TxHash', + type: 'bytes32', + }, + ], + name: 'depositHappened', + outputs: [ + { + internalType: 'bytes32', + name: '', + type: 'bytes32', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { + internalType: 'uint256', + name: '_chainId', + type: 'uint256', + }, + { + internalType: 'uint256', + name: '_l2BatchNumber', + type: 'uint256', + }, + { + internalType: 'uint256', + name: '_l2MessageIndex', + type: 'uint256', + }, + { + internalType: 'uint16', + name: '_l2TxNumberInBatch', + type: 'uint16', + }, + { + internalType: 'bytes', + name: '_message', + type: 'bytes', + }, + { + internalType: 'bytes32[]', + name: '_merkleProof', + type: 'bytes32[]', + }, + ], + name: 'finalizeWithdrawal', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'uint256', + name: '_chainId', + type: 'uint256', + }, + { + internalType: 'uint256', + name: '_l2BatchNumber', + type: 'uint256', + }, + { + internalType: 'uint256', + name: '_l2MessageIndex', + type: 'uint256', + }, + ], + name: 'isWithdrawalFinalizedShared', + outputs: [ + { + internalType: 'bool', + name: '', + type: 'bool', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { + internalType: 'uint256', + name: '_chainId', + type: 'uint256', + }, + ], + name: 'l2BridgeAddress', + outputs: [ + { + internalType: 'address', + name: '', + type: 'address', + }, + ], + stateMutability: 'view', + type: 'function', + }, +] as const; diff --git a/src/contracts/IL1ERC20Bridge.ts b/src/contracts/IL1ERC20Bridge.ts new file mode 100644 index 0000000..ee84bc5 --- /dev/null +++ b/src/contracts/IL1ERC20Bridge.ts @@ -0,0 +1,377 @@ +export const IL1BridgeABI = [ + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: 'address', + name: 'to', + type: 'address', + }, + { + indexed: true, + internalType: 'address', + name: 'l1Token', + type: 'address', + }, + { + indexed: false, + internalType: 'uint256', + name: 'amount', + type: 'uint256', + }, + ], + name: 'ClaimedFailedDeposit', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: 'bytes32', + name: 'l2DepositTxHash', + type: 'bytes32', + }, + { + indexed: true, + internalType: 'address', + name: 'from', + type: 'address', + }, + { + indexed: true, + internalType: 'address', + name: 'to', + type: 'address', + }, + { + indexed: false, + internalType: 'address', + name: 'l1Token', + type: 'address', + }, + { + indexed: false, + internalType: 'uint256', + name: 'amount', + type: 'uint256', + }, + ], + name: 'DepositInitiated', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: 'address', + name: 'to', + type: 'address', + }, + { + indexed: true, + internalType: 'address', + name: 'l1Token', + type: 'address', + }, + { + indexed: false, + internalType: 'uint256', + name: 'amount', + type: 'uint256', + }, + ], + name: 'WithdrawalFinalized', + type: 'event', + }, + { + inputs: [ + { + internalType: 'address', + name: '_depositSender', + type: 'address', + }, + { + internalType: 'address', + name: '_l1Token', + type: 'address', + }, + { + internalType: 'bytes32', + name: '_l2TxHash', + type: 'bytes32', + }, + { + internalType: 'uint256', + name: '_l2BatchNumber', + type: 'uint256', + }, + { + internalType: 'uint256', + name: '_l2MessageIndex', + type: 'uint256', + }, + { + internalType: 'uint16', + name: '_l2TxNumberInBatch', + type: 'uint16', + }, + { + internalType: 'bytes32[]', + name: '_merkleProof', + type: 'bytes32[]', + }, + ], + name: 'claimFailedDeposit', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'address', + name: '_l2Receiver', + type: 'address', + }, + { + internalType: 'address', + name: '_l1Token', + type: 'address', + }, + { + internalType: 'uint256', + name: '_amount', + type: 'uint256', + }, + { + internalType: 'uint256', + name: '_l2TxGasLimit', + type: 'uint256', + }, + { + internalType: 'uint256', + name: '_l2TxGasPerPubdataByte', + type: 'uint256', + }, + ], + name: 'deposit', + outputs: [ + { + internalType: 'bytes32', + name: 'txHash', + type: 'bytes32', + }, + ], + stateMutability: 'payable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'address', + name: '_l2Receiver', + type: 'address', + }, + { + internalType: 'address', + name: '_l1Token', + type: 'address', + }, + { + internalType: 'uint256', + name: '_amount', + type: 'uint256', + }, + { + internalType: 'uint256', + name: '_l2TxGasLimit', + type: 'uint256', + }, + { + internalType: 'uint256', + name: '_l2TxGasPerPubdataByte', + type: 'uint256', + }, + { + internalType: 'address', + name: '_refundRecipient', + type: 'address', + }, + ], + name: 'deposit', + outputs: [ + { + internalType: 'bytes32', + name: 'txHash', + type: 'bytes32', + }, + ], + stateMutability: 'payable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'address', + name: '_account', + type: 'address', + }, + { + internalType: 'address', + name: '_l1Token', + type: 'address', + }, + { + internalType: 'bytes32', + name: '_depositL2TxHash', + type: 'bytes32', + }, + ], + name: 'depositAmount', + outputs: [ + { + internalType: 'uint256', + name: 'amount', + type: 'uint256', + }, + ], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'uint256', + name: '_l2BatchNumber', + type: 'uint256', + }, + { + internalType: 'uint256', + name: '_l2MessageIndex', + type: 'uint256', + }, + { + internalType: 'uint16', + name: '_l2TxNumberInBatch', + type: 'uint16', + }, + { + internalType: 'bytes', + name: '_message', + type: 'bytes', + }, + { + internalType: 'bytes32[]', + name: '_merkleProof', + type: 'bytes32[]', + }, + ], + name: 'finalizeWithdrawal', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'uint256', + name: '_l2BatchNumber', + type: 'uint256', + }, + { + internalType: 'uint256', + name: '_l2MessageIndex', + type: 'uint256', + }, + ], + name: 'isWithdrawalFinalized', + outputs: [ + { + internalType: 'bool', + name: '', + type: 'bool', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'l2Bridge', + outputs: [ + { + internalType: 'address', + name: '', + type: 'address', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { + internalType: 'address', + name: '_l1Token', + type: 'address', + }, + ], + name: 'l2TokenAddress', + outputs: [ + { + internalType: 'address', + name: '', + type: 'address', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'l2TokenBeacon', + outputs: [ + { + internalType: 'address', + name: '', + type: 'address', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'sharedBridge', + outputs: [ + { + internalType: 'contract IL1SharedBridge', + name: '', + type: 'address', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { + internalType: 'address', + name: '_token', + type: 'address', + }, + { + internalType: 'uint256', + name: '_amount', + type: 'uint256', + }, + ], + name: 'transferTokenToSharedBridge', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, +] as const; diff --git a/src/contracts/IL1Messenger.ts b/src/contracts/IL1Messenger.ts new file mode 100644 index 0000000..47627a4 --- /dev/null +++ b/src/contracts/IL1Messenger.ts @@ -0,0 +1,146 @@ +export const IL1MessengerABI = [ + { + anonymous: false, + inputs: [ + { + indexed: false, + internalType: 'bytes32', + name: '_bytecodeHash', + type: 'bytes32', + }, + ], + name: 'BytecodeL1PublicationRequested', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: 'address', + name: '_sender', + type: 'address', + }, + { + indexed: true, + internalType: 'bytes32', + name: '_hash', + type: 'bytes32', + }, + { + indexed: false, + internalType: 'bytes', + name: '_message', + type: 'bytes', + }, + ], + name: 'L1MessageSent', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + components: [ + { + internalType: 'uint8', + name: 'l2ShardId', + type: 'uint8', + }, + { + internalType: 'bool', + name: 'isService', + type: 'bool', + }, + { + internalType: 'uint16', + name: 'txNumberInBlock', + type: 'uint16', + }, + { + internalType: 'address', + name: 'sender', + type: 'address', + }, + { + internalType: 'bytes32', + name: 'key', + type: 'bytes32', + }, + { + internalType: 'bytes32', + name: 'value', + type: 'bytes32', + }, + ], + indexed: false, + internalType: 'struct L2ToL1Log', + name: '_l2log', + type: 'tuple', + }, + ], + name: 'L2ToL1LogSent', + type: 'event', + }, + { + inputs: [ + { + internalType: 'bytes32', + name: '_bytecodeHash', + type: 'bytes32', + }, + ], + name: 'requestBytecodeL1Publication', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'bool', + name: '_isService', + type: 'bool', + }, + { + internalType: 'bytes32', + name: '_key', + type: 'bytes32', + }, + { + internalType: 'bytes32', + name: '_value', + type: 'bytes32', + }, + ], + name: 'sendL2ToL1Log', + outputs: [ + { + internalType: 'uint256', + name: 'logIdInMerkleTree', + type: 'uint256', + }, + ], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'bytes', + name: '_message', + type: 'bytes', + }, + ], + name: 'sendToL1', + outputs: [ + { + internalType: 'bytes32', + name: '', + type: 'bytes32', + }, + ], + stateMutability: 'nonpayable', + type: 'function', + }, +] as const; diff --git a/src/contracts/IL1SharedBridge.ts b/src/contracts/IL1SharedBridge.ts new file mode 100644 index 0000000..17d7b60 --- /dev/null +++ b/src/contracts/IL1SharedBridge.ts @@ -0,0 +1,692 @@ +export const Abi = [ + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: 'uint256', + name: 'chainId', + type: 'uint256', + }, + { + indexed: true, + internalType: 'address', + name: 'from', + type: 'address', + }, + { + indexed: false, + internalType: 'address', + name: 'l1Token', + type: 'address', + }, + { + indexed: false, + internalType: 'uint256', + name: 'amount', + type: 'uint256', + }, + ], + name: 'BridgehubDepositBaseTokenInitiated', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: 'uint256', + name: 'chainId', + type: 'uint256', + }, + { + indexed: true, + internalType: 'bytes32', + name: 'txDataHash', + type: 'bytes32', + }, + { + indexed: true, + internalType: 'bytes32', + name: 'l2DepositTxHash', + type: 'bytes32', + }, + ], + name: 'BridgehubDepositFinalized', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: 'uint256', + name: 'chainId', + type: 'uint256', + }, + { + indexed: true, + internalType: 'bytes32', + name: 'txDataHash', + type: 'bytes32', + }, + { + indexed: true, + internalType: 'address', + name: 'from', + type: 'address', + }, + { + indexed: false, + internalType: 'address', + name: 'to', + type: 'address', + }, + { + indexed: false, + internalType: 'address', + name: 'l1Token', + type: 'address', + }, + { + indexed: false, + internalType: 'uint256', + name: 'amount', + type: 'uint256', + }, + ], + name: 'BridgehubDepositInitiated', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: 'uint256', + name: 'chainId', + type: 'uint256', + }, + { + indexed: true, + internalType: 'address', + name: 'to', + type: 'address', + }, + { + indexed: true, + internalType: 'address', + name: 'l1Token', + type: 'address', + }, + { + indexed: false, + internalType: 'uint256', + name: 'amount', + type: 'uint256', + }, + ], + name: 'ClaimedFailedDepositSharedBridge', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: 'uint256', + name: 'chainId', + type: 'uint256', + }, + { + indexed: true, + internalType: 'bytes32', + name: 'l2DepositTxHash', + type: 'bytes32', + }, + { + indexed: true, + internalType: 'address', + name: 'from', + type: 'address', + }, + { + indexed: false, + internalType: 'address', + name: 'to', + type: 'address', + }, + { + indexed: false, + internalType: 'address', + name: 'l1Token', + type: 'address', + }, + { + indexed: false, + internalType: 'uint256', + name: 'amount', + type: 'uint256', + }, + ], + name: 'LegacyDepositInitiated', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: 'uint256', + name: 'chainId', + type: 'uint256', + }, + { + indexed: true, + internalType: 'address', + name: 'to', + type: 'address', + }, + { + indexed: true, + internalType: 'address', + name: 'l1Token', + type: 'address', + }, + { + indexed: false, + internalType: 'uint256', + name: 'amount', + type: 'uint256', + }, + ], + name: 'WithdrawalFinalizedSharedBridge', + type: 'event', + }, + { + inputs: [], + name: 'bridgehub', + outputs: [ + { + internalType: 'contract IBridgehub', + name: '', + type: 'address', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { + internalType: 'uint256', + name: '_chainId', + type: 'uint256', + }, + { + internalType: 'bytes32', + name: '_txDataHash', + type: 'bytes32', + }, + { + internalType: 'bytes32', + name: '_txHash', + type: 'bytes32', + }, + ], + name: 'bridgehubConfirmL2Transaction', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'uint256', + name: '_chainId', + type: 'uint256', + }, + { + internalType: 'address', + name: '_prevMsgSender', + type: 'address', + }, + { + internalType: 'uint256', + name: '_l2Value', + type: 'uint256', + }, + { + internalType: 'bytes', + name: '_data', + type: 'bytes', + }, + ], + name: 'bridgehubDeposit', + outputs: [ + { + components: [ + { + internalType: 'bytes32', + name: 'magicValue', + type: 'bytes32', + }, + { + internalType: 'address', + name: 'l2Contract', + type: 'address', + }, + { + internalType: 'bytes', + name: 'l2Calldata', + type: 'bytes', + }, + { + internalType: 'bytes[]', + name: 'factoryDeps', + type: 'bytes[]', + }, + { + internalType: 'bytes32', + name: 'txDataHash', + type: 'bytes32', + }, + ], + internalType: 'struct L2TransactionRequestTwoBridgesInner', + name: 'request', + type: 'tuple', + }, + ], + stateMutability: 'payable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'uint256', + name: '_chainId', + type: 'uint256', + }, + { + internalType: 'address', + name: '_prevMsgSender', + type: 'address', + }, + { + internalType: 'address', + name: '_l1Token', + type: 'address', + }, + { + internalType: 'uint256', + name: '_amount', + type: 'uint256', + }, + ], + name: 'bridgehubDepositBaseToken', + outputs: [], + stateMutability: 'payable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'uint256', + name: '_chainId', + type: 'uint256', + }, + { + internalType: 'address', + name: '_depositSender', + type: 'address', + }, + { + internalType: 'address', + name: '_l1Token', + type: 'address', + }, + { + internalType: 'uint256', + name: '_amount', + type: 'uint256', + }, + { + internalType: 'bytes32', + name: '_l2TxHash', + type: 'bytes32', + }, + { + internalType: 'uint256', + name: '_l2BatchNumber', + type: 'uint256', + }, + { + internalType: 'uint256', + name: '_l2MessageIndex', + type: 'uint256', + }, + { + internalType: 'uint16', + name: '_l2TxNumberInBatch', + type: 'uint16', + }, + { + internalType: 'bytes32[]', + name: '_merkleProof', + type: 'bytes32[]', + }, + ], + name: 'claimFailedDeposit', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'address', + name: '_depositSender', + type: 'address', + }, + { + internalType: 'address', + name: '_l1Token', + type: 'address', + }, + { + internalType: 'uint256', + name: '_amount', + type: 'uint256', + }, + { + internalType: 'bytes32', + name: '_l2TxHash', + type: 'bytes32', + }, + { + internalType: 'uint256', + name: '_l2BatchNumber', + type: 'uint256', + }, + { + internalType: 'uint256', + name: '_l2MessageIndex', + type: 'uint256', + }, + { + internalType: 'uint16', + name: '_l2TxNumberInBatch', + type: 'uint16', + }, + { + internalType: 'bytes32[]', + name: '_merkleProof', + type: 'bytes32[]', + }, + ], + name: 'claimFailedDepositLegacyErc20Bridge', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'uint256', + name: '_chainId', + type: 'uint256', + }, + { + internalType: 'bytes32', + name: '_l2TxHash', + type: 'bytes32', + }, + ], + name: 'depositHappened', + outputs: [ + { + internalType: 'bytes32', + name: '', + type: 'bytes32', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { + internalType: 'address', + name: '_msgSender', + type: 'address', + }, + { + internalType: 'address', + name: '_l2Receiver', + type: 'address', + }, + { + internalType: 'address', + name: '_l1Token', + type: 'address', + }, + { + internalType: 'uint256', + name: '_amount', + type: 'uint256', + }, + { + internalType: 'uint256', + name: '_l2TxGasLimit', + type: 'uint256', + }, + { + internalType: 'uint256', + name: '_l2TxGasPerPubdataByte', + type: 'uint256', + }, + { + internalType: 'address', + name: '_refundRecipient', + type: 'address', + }, + ], + name: 'depositLegacyErc20Bridge', + outputs: [ + { + internalType: 'bytes32', + name: 'txHash', + type: 'bytes32', + }, + ], + stateMutability: 'payable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'uint256', + name: '_chainId', + type: 'uint256', + }, + { + internalType: 'uint256', + name: '_l2BatchNumber', + type: 'uint256', + }, + { + internalType: 'uint256', + name: '_l2MessageIndex', + type: 'uint256', + }, + { + internalType: 'uint16', + name: '_l2TxNumberInBatch', + type: 'uint16', + }, + { + internalType: 'bytes', + name: '_message', + type: 'bytes', + }, + { + internalType: 'bytes32[]', + name: '_merkleProof', + type: 'bytes32[]', + }, + ], + name: 'finalizeWithdrawal', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'uint256', + name: '_l2BatchNumber', + type: 'uint256', + }, + { + internalType: 'uint256', + name: '_l2MessageIndex', + type: 'uint256', + }, + { + internalType: 'uint16', + name: '_l2TxNumberInBatch', + type: 'uint16', + }, + { + internalType: 'bytes', + name: '_message', + type: 'bytes', + }, + { + internalType: 'bytes32[]', + name: '_merkleProof', + type: 'bytes32[]', + }, + ], + name: 'finalizeWithdrawalLegacyErc20Bridge', + outputs: [ + { + internalType: 'address', + name: 'l1Receiver', + type: 'address', + }, + { + internalType: 'address', + name: 'l1Token', + type: 'address', + }, + { + internalType: 'uint256', + name: 'amount', + type: 'uint256', + }, + ], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'uint256', + name: '_chainId', + type: 'uint256', + }, + { + internalType: 'uint256', + name: '_l2BatchNumber', + type: 'uint256', + }, + { + internalType: 'uint256', + name: '_l2MessageIndex', + type: 'uint256', + }, + ], + name: 'isWithdrawalFinalized', + outputs: [ + { + internalType: 'bool', + name: '', + type: 'bool', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'l1WethAddress', + outputs: [ + { + internalType: 'address', + name: '', + type: 'address', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { + internalType: 'uint256', + name: '_chainId', + type: 'uint256', + }, + ], + name: 'l2BridgeAddress', + outputs: [ + { + internalType: 'address', + name: '', + type: 'address', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'legacyBridge', + outputs: [ + { + internalType: 'contract IL1ERC20Bridge', + name: '', + type: 'address', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { + internalType: 'uint256', + name: '_chainId', + type: 'uint256', + }, + ], + name: 'receiveEth', + outputs: [], + stateMutability: 'payable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'uint256', + name: '_eraFirstPostUpgradeBatch', + type: 'uint256', + }, + ], + name: 'setEraFirstPostUpgradeBatch', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, +] as const; diff --git a/src/contracts/L2Bridge.ts b/src/contracts/IL2Bridge.ts similarity index 98% rename from src/contracts/L2Bridge.ts rename to src/contracts/IL2Bridge.ts index 31e9f8f..51e177e 100644 --- a/src/contracts/L2Bridge.ts +++ b/src/contracts/IL2Bridge.ts @@ -1,5 +1,5 @@ // https://docs.zksync.io/build/sdks/go/types/types.html#l2bridgecontracts -export const L2BridgeAbi = [ +export const IL2BridgeABI = [ { inputs: [ { diff --git a/src/contracts/INonceHolder.ts b/src/contracts/INonceHolder.ts new file mode 100644 index 0000000..bd4f685 --- /dev/null +++ b/src/contracts/INonceHolder.ts @@ -0,0 +1,219 @@ +export const INonceHolderABI = [ + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: 'address', + name: 'accountAddress', + type: 'address', + }, + { + indexed: true, + internalType: 'uint256', + name: 'key', + type: 'uint256', + }, + { + indexed: false, + internalType: 'uint256', + name: 'value', + type: 'uint256', + }, + ], + name: 'ValueSetUnderNonce', + type: 'event', + }, + { + inputs: [ + { + internalType: 'address', + name: '_address', + type: 'address', + }, + ], + name: 'getDeploymentNonce', + outputs: [ + { + internalType: 'uint256', + name: '', + type: 'uint256', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { + internalType: 'address', + name: '_address', + type: 'address', + }, + ], + name: 'getMinNonce', + outputs: [ + { + internalType: 'uint256', + name: '', + type: 'uint256', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { + internalType: 'address', + name: '_address', + type: 'address', + }, + ], + name: 'getRawNonce', + outputs: [ + { + internalType: 'uint256', + name: '', + type: 'uint256', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { + internalType: 'uint256', + name: '_key', + type: 'uint256', + }, + ], + name: 'getValueUnderNonce', + outputs: [ + { + internalType: 'uint256', + name: '', + type: 'uint256', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { + internalType: 'uint256', + name: '_value', + type: 'uint256', + }, + ], + name: 'increaseMinNonce', + outputs: [ + { + internalType: 'uint256', + name: '', + type: 'uint256', + }, + ], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'address', + name: '_address', + type: 'address', + }, + ], + name: 'incrementDeploymentNonce', + outputs: [ + { + internalType: 'uint256', + name: '', + type: 'uint256', + }, + ], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'uint256', + name: '_expectedNonce', + type: 'uint256', + }, + ], + name: 'incrementMinNonceIfEquals', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'address', + name: '_address', + type: 'address', + }, + { + internalType: 'uint256', + name: '_nonce', + type: 'uint256', + }, + ], + name: 'isNonceUsed', + outputs: [ + { + internalType: 'bool', + name: '', + type: 'bool', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { + internalType: 'uint256', + name: '_key', + type: 'uint256', + }, + { + internalType: 'uint256', + name: '_value', + type: 'uint256', + }, + ], + name: 'setValueUnderNonce', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'address', + name: '_address', + type: 'address', + }, + { + internalType: 'uint256', + name: '_key', + type: 'uint256', + }, + { + internalType: 'bool', + name: '_shouldBeUsed', + type: 'bool', + }, + ], + name: 'validateNonceUsage', + outputs: [], + stateMutability: 'view', + type: 'function', + }, +] as const; diff --git a/src/contracts/IPaymasterFlow.ts b/src/contracts/IPaymasterFlow.ts new file mode 100644 index 0000000..5c1f3a3 --- /dev/null +++ b/src/contracts/IPaymasterFlow.ts @@ -0,0 +1,38 @@ +export const Abi = [ + { + inputs: [ + { + internalType: 'address', + name: '_token', + type: 'address', + }, + { + internalType: 'uint256', + name: '_minAllowance', + type: 'uint256', + }, + { + internalType: 'bytes', + name: '_innerInput', + type: 'bytes', + }, + ], + name: 'approvalBased', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'bytes', + name: 'input', + type: 'bytes', + }, + ], + name: 'general', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, +] as const; diff --git a/src/contracts/ITestnetERC20Token.ts b/src/contracts/ITestnetERC20Token.ts new file mode 100644 index 0000000..c1e590d --- /dev/null +++ b/src/contracts/ITestnetERC20Token.ts @@ -0,0 +1,39 @@ +export const Abi = [ + { + inputs: [], + name: 'decimals', + outputs: [ + { + internalType: 'uint8', + name: '', + type: 'uint8', + }, + ], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'address', + name: '_to', + type: 'address', + }, + { + internalType: 'uint256', + name: '_amount', + type: 'uint256', + }, + ], + name: 'mint', + outputs: [ + { + internalType: 'bool', + name: '', + type: 'bool', + }, + ], + stateMutability: 'nonpayable', + type: 'function', + }, +] as const; diff --git a/src/contracts/IZkSync.ts b/src/contracts/IZkSync.ts new file mode 100644 index 0000000..bff7c89 --- /dev/null +++ b/src/contracts/IZkSync.ts @@ -0,0 +1,1622 @@ +export const Abi = [ + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: 'uint256', + name: 'batchNumber', + type: 'uint256', + }, + { + indexed: true, + internalType: 'bytes32', + name: 'batchHash', + type: 'bytes32', + }, + { + indexed: true, + internalType: 'bytes32', + name: 'commitment', + type: 'bytes32', + }, + ], + name: 'BlockCommit', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: 'uint256', + name: 'batchNumber', + type: 'uint256', + }, + { + indexed: true, + internalType: 'bytes32', + name: 'batchHash', + type: 'bytes32', + }, + { + indexed: true, + internalType: 'bytes32', + name: 'commitment', + type: 'bytes32', + }, + ], + name: 'BlockExecution', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: false, + internalType: 'uint256', + name: 'totalBatchesCommitted', + type: 'uint256', + }, + { + indexed: false, + internalType: 'uint256', + name: 'totalBatchesVerified', + type: 'uint256', + }, + { + indexed: false, + internalType: 'uint256', + name: 'totalBatchesExecuted', + type: 'uint256', + }, + ], + name: 'BlocksRevert', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: 'uint256', + name: 'previousLastVerifiedBatch', + type: 'uint256', + }, + { + indexed: true, + internalType: 'uint256', + name: 'currentLastVerifiedBatch', + type: 'uint256', + }, + ], + name: 'BlocksVerification', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: 'address', + name: 'to', + type: 'address', + }, + { + indexed: false, + internalType: 'uint256', + name: 'amount', + type: 'uint256', + }, + ], + name: 'EthWithdrawalFinalized', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + components: [ + { + components: [ + { + internalType: 'address', + name: 'facet', + type: 'address', + }, + { + internalType: 'enum Diamond.Action', + name: 'action', + type: 'uint8', + }, + { + internalType: 'bool', + name: 'isFreezable', + type: 'bool', + }, + { + internalType: 'bytes4[]', + name: 'selectors', + type: 'bytes4[]', + }, + ], + internalType: 'struct Diamond.FacetCut[]', + name: 'facetCuts', + type: 'tuple[]', + }, + { + internalType: 'address', + name: 'initAddress', + type: 'address', + }, + { + internalType: 'bytes', + name: 'initCalldata', + type: 'bytes', + }, + ], + indexed: false, + internalType: 'struct Diamond.DiamondCutData', + name: 'diamondCut', + type: 'tuple', + }, + ], + name: 'ExecuteUpgrade', + type: 'event', + }, + { + anonymous: false, + inputs: [], + name: 'Freeze', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: false, + internalType: 'bool', + name: 'isPorterAvailable', + type: 'bool', + }, + ], + name: 'IsPorterAvailableStatusUpdate', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: 'address', + name: 'oldAdmin', + type: 'address', + }, + { + indexed: true, + internalType: 'address', + name: 'newAdmin', + type: 'address', + }, + ], + name: 'NewAdmin', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: 'address', + name: 'oldGovernor', + type: 'address', + }, + { + indexed: true, + internalType: 'address', + name: 'newGovernor', + type: 'address', + }, + ], + name: 'NewGovernor', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: 'address', + name: 'oldPendingAdmin', + type: 'address', + }, + { + indexed: true, + internalType: 'address', + name: 'newPendingAdmin', + type: 'address', + }, + ], + name: 'NewPendingAdmin', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: 'address', + name: 'oldPendingGovernor', + type: 'address', + }, + { + indexed: true, + internalType: 'address', + name: 'newPendingGovernor', + type: 'address', + }, + ], + name: 'NewPendingGovernor', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: false, + internalType: 'uint256', + name: 'txId', + type: 'uint256', + }, + { + indexed: false, + internalType: 'bytes32', + name: 'txHash', + type: 'bytes32', + }, + { + indexed: false, + internalType: 'uint64', + name: 'expirationTimestamp', + type: 'uint64', + }, + { + components: [ + { + internalType: 'uint256', + name: 'txType', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'from', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'to', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'gasLimit', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'gasPerPubdataByteLimit', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'maxFeePerGas', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'maxPriorityFeePerGas', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'paymaster', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'nonce', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'value', + type: 'uint256', + }, + { + internalType: 'uint256[4]', + name: 'reserved', + type: 'uint256[4]', + }, + { + internalType: 'bytes', + name: 'data', + type: 'bytes', + }, + { + internalType: 'bytes', + name: 'signature', + type: 'bytes', + }, + { + internalType: 'uint256[]', + name: 'factoryDeps', + type: 'uint256[]', + }, + { + internalType: 'bytes', + name: 'paymasterInput', + type: 'bytes', + }, + { + internalType: 'bytes', + name: 'reservedDynamic', + type: 'bytes', + }, + ], + indexed: false, + internalType: 'struct IMailbox.L2CanonicalTransaction', + name: 'transaction', + type: 'tuple', + }, + { + indexed: false, + internalType: 'bytes[]', + name: 'factoryDeps', + type: 'bytes[]', + }, + ], + name: 'NewPriorityRequest', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: false, + internalType: 'uint256', + name: 'oldPriorityTxMaxGasLimit', + type: 'uint256', + }, + { + indexed: false, + internalType: 'uint256', + name: 'newPriorityTxMaxGasLimit', + type: 'uint256', + }, + ], + name: 'NewPriorityTxMaxGasLimit', + type: 'event', + }, + { + anonymous: false, + inputs: [], + name: 'Unfreeze', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: 'address', + name: 'validatorAddress', + type: 'address', + }, + { + indexed: false, + internalType: 'bool', + name: 'isActive', + type: 'bool', + }, + ], + name: 'ValidatorStatusUpdate', + type: 'event', + }, + { + inputs: [], + name: 'acceptAdmin', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [], + name: 'acceptGovernor', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + components: [ + { + internalType: 'uint64', + name: 'batchNumber', + type: 'uint64', + }, + { + internalType: 'bytes32', + name: 'batchHash', + type: 'bytes32', + }, + { + internalType: 'uint64', + name: 'indexRepeatedStorageChanges', + type: 'uint64', + }, + { + internalType: 'uint256', + name: 'numberOfLayer1Txs', + type: 'uint256', + }, + { + internalType: 'bytes32', + name: 'priorityOperationsHash', + type: 'bytes32', + }, + { + internalType: 'bytes32', + name: 'l2LogsTreeRoot', + type: 'bytes32', + }, + { + internalType: 'uint256', + name: 'timestamp', + type: 'uint256', + }, + { + internalType: 'bytes32', + name: 'commitment', + type: 'bytes32', + }, + ], + internalType: 'struct IExecutor.StoredBatchInfo', + name: '_lastCommittedBatchData', + type: 'tuple', + }, + { + components: [ + { + internalType: 'uint64', + name: 'batchNumber', + type: 'uint64', + }, + { + internalType: 'uint64', + name: 'timestamp', + type: 'uint64', + }, + { + internalType: 'uint64', + name: 'indexRepeatedStorageChanges', + type: 'uint64', + }, + { + internalType: 'bytes32', + name: 'newStateRoot', + type: 'bytes32', + }, + { + internalType: 'uint256', + name: 'numberOfLayer1Txs', + type: 'uint256', + }, + { + internalType: 'bytes32', + name: 'priorityOperationsHash', + type: 'bytes32', + }, + { + internalType: 'bytes32', + name: 'bootloaderHeapInitialContentsHash', + type: 'bytes32', + }, + { + internalType: 'bytes32', + name: 'eventsQueueStateHash', + type: 'bytes32', + }, + { + internalType: 'bytes', + name: 'systemLogs', + type: 'bytes', + }, + { + internalType: 'bytes', + name: 'totalL2ToL1Pubdata', + type: 'bytes', + }, + ], + internalType: 'struct IExecutor.CommitBatchInfo[]', + name: '_newBatchesData', + type: 'tuple[]', + }, + ], + name: 'commitBatches', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + components: [ + { + internalType: 'uint64', + name: 'batchNumber', + type: 'uint64', + }, + { + internalType: 'bytes32', + name: 'batchHash', + type: 'bytes32', + }, + { + internalType: 'uint64', + name: 'indexRepeatedStorageChanges', + type: 'uint64', + }, + { + internalType: 'uint256', + name: 'numberOfLayer1Txs', + type: 'uint256', + }, + { + internalType: 'bytes32', + name: 'priorityOperationsHash', + type: 'bytes32', + }, + { + internalType: 'bytes32', + name: 'l2LogsTreeRoot', + type: 'bytes32', + }, + { + internalType: 'uint256', + name: 'timestamp', + type: 'uint256', + }, + { + internalType: 'bytes32', + name: 'commitment', + type: 'bytes32', + }, + ], + internalType: 'struct IExecutor.StoredBatchInfo[]', + name: '_batchesData', + type: 'tuple[]', + }, + ], + name: 'executeBatches', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + components: [ + { + components: [ + { + internalType: 'address', + name: 'facet', + type: 'address', + }, + { + internalType: 'enum Diamond.Action', + name: 'action', + type: 'uint8', + }, + { + internalType: 'bool', + name: 'isFreezable', + type: 'bool', + }, + { + internalType: 'bytes4[]', + name: 'selectors', + type: 'bytes4[]', + }, + ], + internalType: 'struct Diamond.FacetCut[]', + name: 'facetCuts', + type: 'tuple[]', + }, + { + internalType: 'address', + name: 'initAddress', + type: 'address', + }, + { + internalType: 'bytes', + name: 'initCalldata', + type: 'bytes', + }, + ], + internalType: 'struct Diamond.DiamondCutData', + name: '_diamondCut', + type: 'tuple', + }, + ], + name: 'executeUpgrade', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'bytes4', + name: '_selector', + type: 'bytes4', + }, + ], + name: 'facetAddress', + outputs: [ + { + internalType: 'address', + name: 'facet', + type: 'address', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'facetAddresses', + outputs: [ + { + internalType: 'address[]', + name: 'facets', + type: 'address[]', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { + internalType: 'address', + name: '_facet', + type: 'address', + }, + ], + name: 'facetFunctionSelectors', + outputs: [ + { + internalType: 'bytes4[]', + name: '', + type: 'bytes4[]', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'facets', + outputs: [ + { + components: [ + { + internalType: 'address', + name: 'addr', + type: 'address', + }, + { + internalType: 'bytes4[]', + name: 'selectors', + type: 'bytes4[]', + }, + ], + internalType: 'struct IGetters.Facet[]', + name: '', + type: 'tuple[]', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { + internalType: 'uint256', + name: '_l2BatchNumber', + type: 'uint256', + }, + { + internalType: 'uint256', + name: '_l2MessageIndex', + type: 'uint256', + }, + { + internalType: 'uint16', + name: '_l2TxNumberInBatch', + type: 'uint16', + }, + { + internalType: 'bytes', + name: '_message', + type: 'bytes', + }, + { + internalType: 'bytes32[]', + name: '_merkleProof', + type: 'bytes32[]', + }, + ], + name: 'finalizeEthWithdrawal', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [], + name: 'freezeDiamond', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [], + name: 'getFirstUnprocessedPriorityTx', + outputs: [ + { + internalType: 'uint256', + name: '', + type: 'uint256', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'getGovernor', + outputs: [ + { + internalType: 'address', + name: '', + type: 'address', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'getL2BootloaderBytecodeHash', + outputs: [ + { + internalType: 'bytes32', + name: '', + type: 'bytes32', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'getL2DefaultAccountBytecodeHash', + outputs: [ + { + internalType: 'bytes32', + name: '', + type: 'bytes32', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'getL2SystemContractsUpgradeBatchNumber', + outputs: [ + { + internalType: 'uint256', + name: '', + type: 'uint256', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'getL2SystemContractsUpgradeTxHash', + outputs: [ + { + internalType: 'bytes32', + name: '', + type: 'bytes32', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'getName', + outputs: [ + { + internalType: 'string', + name: '', + type: 'string', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'getPendingGovernor', + outputs: [ + { + internalType: 'address', + name: '', + type: 'address', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'getPriorityQueueSize', + outputs: [ + { + internalType: 'uint256', + name: '', + type: 'uint256', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'getPriorityTxMaxGasLimit', + outputs: [ + { + internalType: 'uint256', + name: '', + type: 'uint256', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'getProtocolVersion', + outputs: [ + { + internalType: 'uint256', + name: '', + type: 'uint256', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'getTotalBatchesCommitted', + outputs: [ + { + internalType: 'uint256', + name: '', + type: 'uint256', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'getTotalBatchesExecuted', + outputs: [ + { + internalType: 'uint256', + name: '', + type: 'uint256', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'getTotalBatchesVerified', + outputs: [ + { + internalType: 'uint256', + name: '', + type: 'uint256', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'getTotalPriorityTxs', + outputs: [ + { + internalType: 'uint256', + name: '', + type: 'uint256', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'getVerifier', + outputs: [ + { + internalType: 'address', + name: '', + type: 'address', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'getVerifierParams', + outputs: [ + { + components: [ + { + internalType: 'bytes32', + name: 'recursionNodeLevelVkHash', + type: 'bytes32', + }, + { + internalType: 'bytes32', + name: 'recursionLeafLevelVkHash', + type: 'bytes32', + }, + { + internalType: 'bytes32', + name: 'recursionCircuitsSetVksHash', + type: 'bytes32', + }, + ], + internalType: 'struct VerifierParams', + name: '', + type: 'tuple', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'isDiamondStorageFrozen', + outputs: [ + { + internalType: 'bool', + name: '', + type: 'bool', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { + internalType: 'uint256', + name: '_l2BatchNumber', + type: 'uint256', + }, + { + internalType: 'uint256', + name: '_l2MessageIndex', + type: 'uint256', + }, + ], + name: 'isEthWithdrawalFinalized', + outputs: [ + { + internalType: 'bool', + name: '', + type: 'bool', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { + internalType: 'address', + name: '_facet', + type: 'address', + }, + ], + name: 'isFacetFreezable', + outputs: [ + { + internalType: 'bool', + name: 'isFreezable', + type: 'bool', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { + internalType: 'bytes4', + name: '_selector', + type: 'bytes4', + }, + ], + name: 'isFunctionFreezable', + outputs: [ + { + internalType: 'bool', + name: '', + type: 'bool', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { + internalType: 'address', + name: '_address', + type: 'address', + }, + ], + name: 'isValidator', + outputs: [ + { + internalType: 'bool', + name: '', + type: 'bool', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { + internalType: 'uint256', + name: '_batchNumber', + type: 'uint256', + }, + ], + name: 'l2LogsRootHash', + outputs: [ + { + internalType: 'bytes32', + name: 'hash', + type: 'bytes32', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { + internalType: 'uint256', + name: '_gasPrice', + type: 'uint256', + }, + { + internalType: 'uint256', + name: '_l2GasLimit', + type: 'uint256', + }, + { + internalType: 'uint256', + name: '_l2GasPerPubdataByteLimit', + type: 'uint256', + }, + ], + name: 'l2TransactionBaseCost', + outputs: [ + { + internalType: 'uint256', + name: '', + type: 'uint256', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'priorityQueueFrontOperation', + outputs: [ + { + components: [ + { + internalType: 'bytes32', + name: 'canonicalTxHash', + type: 'bytes32', + }, + { + internalType: 'uint64', + name: 'expirationTimestamp', + type: 'uint64', + }, + { + internalType: 'uint192', + name: 'layer2Tip', + type: 'uint192', + }, + ], + internalType: 'struct PriorityOperation', + name: '', + type: 'tuple', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { + components: [ + { + internalType: 'uint64', + name: 'batchNumber', + type: 'uint64', + }, + { + internalType: 'bytes32', + name: 'batchHash', + type: 'bytes32', + }, + { + internalType: 'uint64', + name: 'indexRepeatedStorageChanges', + type: 'uint64', + }, + { + internalType: 'uint256', + name: 'numberOfLayer1Txs', + type: 'uint256', + }, + { + internalType: 'bytes32', + name: 'priorityOperationsHash', + type: 'bytes32', + }, + { + internalType: 'bytes32', + name: 'l2LogsTreeRoot', + type: 'bytes32', + }, + { + internalType: 'uint256', + name: 'timestamp', + type: 'uint256', + }, + { + internalType: 'bytes32', + name: 'commitment', + type: 'bytes32', + }, + ], + internalType: 'struct IExecutor.StoredBatchInfo', + name: '_prevBatch', + type: 'tuple', + }, + { + components: [ + { + internalType: 'uint64', + name: 'batchNumber', + type: 'uint64', + }, + { + internalType: 'bytes32', + name: 'batchHash', + type: 'bytes32', + }, + { + internalType: 'uint64', + name: 'indexRepeatedStorageChanges', + type: 'uint64', + }, + { + internalType: 'uint256', + name: 'numberOfLayer1Txs', + type: 'uint256', + }, + { + internalType: 'bytes32', + name: 'priorityOperationsHash', + type: 'bytes32', + }, + { + internalType: 'bytes32', + name: 'l2LogsTreeRoot', + type: 'bytes32', + }, + { + internalType: 'uint256', + name: 'timestamp', + type: 'uint256', + }, + { + internalType: 'bytes32', + name: 'commitment', + type: 'bytes32', + }, + ], + internalType: 'struct IExecutor.StoredBatchInfo[]', + name: '_committedBatches', + type: 'tuple[]', + }, + { + components: [ + { + internalType: 'uint256[]', + name: 'recursiveAggregationInput', + type: 'uint256[]', + }, + { + internalType: 'uint256[]', + name: 'serializedProof', + type: 'uint256[]', + }, + ], + internalType: 'struct IExecutor.ProofInput', + name: '_proof', + type: 'tuple', + }, + ], + name: 'proveBatches', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'bytes32', + name: '_l2TxHash', + type: 'bytes32', + }, + { + internalType: 'uint256', + name: '_l2BatchNumber', + type: 'uint256', + }, + { + internalType: 'uint256', + name: '_l2MessageIndex', + type: 'uint256', + }, + { + internalType: 'uint16', + name: '_l2TxNumberInBatch', + type: 'uint16', + }, + { + internalType: 'bytes32[]', + name: '_merkleProof', + type: 'bytes32[]', + }, + { + internalType: 'enum TxStatus', + name: '_status', + type: 'uint8', + }, + ], + name: 'proveL1ToL2TransactionStatus', + outputs: [ + { + internalType: 'bool', + name: '', + type: 'bool', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { + internalType: 'uint256', + name: '_l2BatchNumber', + type: 'uint256', + }, + { + internalType: 'uint256', + name: '_index', + type: 'uint256', + }, + { + components: [ + { + internalType: 'uint8', + name: 'l2ShardId', + type: 'uint8', + }, + { + internalType: 'bool', + name: 'isService', + type: 'bool', + }, + { + internalType: 'uint16', + name: 'txNumberInBatch', + type: 'uint16', + }, + { + internalType: 'address', + name: 'sender', + type: 'address', + }, + { + internalType: 'bytes32', + name: 'key', + type: 'bytes32', + }, + { + internalType: 'bytes32', + name: 'value', + type: 'bytes32', + }, + ], + internalType: 'struct L2Log', + name: '_log', + type: 'tuple', + }, + { + internalType: 'bytes32[]', + name: '_proof', + type: 'bytes32[]', + }, + ], + name: 'proveL2LogInclusion', + outputs: [ + { + internalType: 'bool', + name: '', + type: 'bool', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { + internalType: 'uint256', + name: '_l2BatchNumber', + type: 'uint256', + }, + { + internalType: 'uint256', + name: '_index', + type: 'uint256', + }, + { + components: [ + { + internalType: 'uint16', + name: 'txNumberInBatch', + type: 'uint16', + }, + { + internalType: 'address', + name: 'sender', + type: 'address', + }, + { + internalType: 'bytes', + name: 'data', + type: 'bytes', + }, + ], + internalType: 'struct L2Message', + name: '_message', + type: 'tuple', + }, + { + internalType: 'bytes32[]', + name: '_proof', + type: 'bytes32[]', + }, + ], + name: 'proveL2MessageInclusion', + outputs: [ + { + internalType: 'bool', + name: '', + type: 'bool', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { + internalType: 'address', + name: '_contractL2', + type: 'address', + }, + { + internalType: 'uint256', + name: '_l2Value', + type: 'uint256', + }, + { + internalType: 'bytes', + name: '_calldata', + type: 'bytes', + }, + { + internalType: 'uint256', + name: '_l2GasLimit', + type: 'uint256', + }, + { + internalType: 'uint256', + name: '_l2GasPerPubdataByteLimit', + type: 'uint256', + }, + { + internalType: 'bytes[]', + name: '_factoryDeps', + type: 'bytes[]', + }, + { + internalType: 'address', + name: '_refundRecipient', + type: 'address', + }, + ], + name: 'requestL2Transaction', + outputs: [ + { + internalType: 'bytes32', + name: 'canonicalTxHash', + type: 'bytes32', + }, + ], + stateMutability: 'payable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'uint256', + name: '_newLastBatch', + type: 'uint256', + }, + ], + name: 'revertBatches', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'address', + name: '_newPendingAdmin', + type: 'address', + }, + ], + name: 'setPendingAdmin', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'address', + name: '_newPendingGovernor', + type: 'address', + }, + ], + name: 'setPendingGovernor', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'bool', + name: '_zkPorterIsAvailable', + type: 'bool', + }, + ], + name: 'setPorterAvailability', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'uint256', + name: '_newPriorityTxMaxGasLimit', + type: 'uint256', + }, + ], + name: 'setPriorityTxMaxGasLimit', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'address', + name: '_validator', + type: 'address', + }, + { + internalType: 'bool', + name: '_active', + type: 'bool', + }, + ], + name: 'setValidator', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'uint256', + name: '_batchNumber', + type: 'uint256', + }, + ], + name: 'storedBatchHash', + outputs: [ + { + internalType: 'bytes32', + name: '', + type: 'bytes32', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'unfreezeDiamond', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, +] as const; diff --git a/src/contracts/IZkSyncStateTransition.ts b/src/contracts/IZkSyncStateTransition.ts new file mode 100644 index 0000000..71f4d09 --- /dev/null +++ b/src/contracts/IZkSyncStateTransition.ts @@ -0,0 +1,2403 @@ +export const IZkSyncABI = [ + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: 'uint256', + name: 'batchNumber', + type: 'uint256', + }, + { + indexed: true, + internalType: 'bytes32', + name: 'batchHash', + type: 'bytes32', + }, + { + indexed: true, + internalType: 'bytes32', + name: 'commitment', + type: 'bytes32', + }, + ], + name: 'BlockCommit', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: 'uint256', + name: 'batchNumber', + type: 'uint256', + }, + { + indexed: true, + internalType: 'bytes32', + name: 'batchHash', + type: 'bytes32', + }, + { + indexed: true, + internalType: 'bytes32', + name: 'commitment', + type: 'bytes32', + }, + ], + name: 'BlockExecution', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: false, + internalType: 'uint256', + name: 'totalBatchesCommitted', + type: 'uint256', + }, + { + indexed: false, + internalType: 'uint256', + name: 'totalBatchesVerified', + type: 'uint256', + }, + { + indexed: false, + internalType: 'uint256', + name: 'totalBatchesExecuted', + type: 'uint256', + }, + ], + name: 'BlocksRevert', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: 'uint256', + name: 'previousLastVerifiedBatch', + type: 'uint256', + }, + { + indexed: true, + internalType: 'uint256', + name: 'currentLastVerifiedBatch', + type: 'uint256', + }, + ], + name: 'BlocksVerification', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: 'address', + name: 'to', + type: 'address', + }, + { + indexed: false, + internalType: 'uint256', + name: 'amount', + type: 'uint256', + }, + ], + name: 'EthWithdrawalFinalized', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + components: [ + { + components: [ + { + internalType: 'address', + name: 'facet', + type: 'address', + }, + { + internalType: 'enum Diamond.Action', + name: 'action', + type: 'uint8', + }, + { + internalType: 'bool', + name: 'isFreezable', + type: 'bool', + }, + { + internalType: 'bytes4[]', + name: 'selectors', + type: 'bytes4[]', + }, + ], + internalType: 'struct Diamond.FacetCut[]', + name: 'facetCuts', + type: 'tuple[]', + }, + { + internalType: 'address', + name: 'initAddress', + type: 'address', + }, + { + internalType: 'bytes', + name: 'initCalldata', + type: 'bytes', + }, + ], + indexed: false, + internalType: 'struct Diamond.DiamondCutData', + name: 'diamondCut', + type: 'tuple', + }, + ], + name: 'ExecuteUpgrade', + type: 'event', + }, + { + anonymous: false, + inputs: [], + name: 'Freeze', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: false, + internalType: 'bool', + name: 'isPorterAvailable', + type: 'bool', + }, + ], + name: 'IsPorterAvailableStatusUpdate', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: 'address', + name: 'oldAdmin', + type: 'address', + }, + { + indexed: true, + internalType: 'address', + name: 'newAdmin', + type: 'address', + }, + ], + name: 'NewAdmin', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: false, + internalType: 'uint128', + name: 'oldNominator', + type: 'uint128', + }, + { + indexed: false, + internalType: 'uint128', + name: 'oldDenominator', + type: 'uint128', + }, + { + indexed: false, + internalType: 'uint128', + name: 'newNominator', + type: 'uint128', + }, + { + indexed: false, + internalType: 'uint128', + name: 'newDenominator', + type: 'uint128', + }, + ], + name: 'NewBaseTokenMultiplier', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + components: [ + { + internalType: 'enum PubdataPricingMode', + name: 'pubdataPricingMode', + type: 'uint8', + }, + { + internalType: 'uint32', + name: 'batchOverheadL1Gas', + type: 'uint32', + }, + { + internalType: 'uint32', + name: 'maxPubdataPerBatch', + type: 'uint32', + }, + { + internalType: 'uint32', + name: 'maxL2GasPerBatch', + type: 'uint32', + }, + { + internalType: 'uint32', + name: 'priorityTxMaxPubdata', + type: 'uint32', + }, + { + internalType: 'uint64', + name: 'minimalL2GasPrice', + type: 'uint64', + }, + ], + indexed: false, + internalType: 'struct FeeParams', + name: 'oldFeeParams', + type: 'tuple', + }, + { + components: [ + { + internalType: 'enum PubdataPricingMode', + name: 'pubdataPricingMode', + type: 'uint8', + }, + { + internalType: 'uint32', + name: 'batchOverheadL1Gas', + type: 'uint32', + }, + { + internalType: 'uint32', + name: 'maxPubdataPerBatch', + type: 'uint32', + }, + { + internalType: 'uint32', + name: 'maxL2GasPerBatch', + type: 'uint32', + }, + { + internalType: 'uint32', + name: 'priorityTxMaxPubdata', + type: 'uint32', + }, + { + internalType: 'uint64', + name: 'minimalL2GasPrice', + type: 'uint64', + }, + ], + indexed: false, + internalType: 'struct FeeParams', + name: 'newFeeParams', + type: 'tuple', + }, + ], + name: 'NewFeeParams', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: 'address', + name: 'oldPendingAdmin', + type: 'address', + }, + { + indexed: true, + internalType: 'address', + name: 'newPendingAdmin', + type: 'address', + }, + ], + name: 'NewPendingAdmin', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: false, + internalType: 'uint256', + name: 'txId', + type: 'uint256', + }, + { + indexed: false, + internalType: 'bytes32', + name: 'txHash', + type: 'bytes32', + }, + { + indexed: false, + internalType: 'uint64', + name: 'expirationTimestamp', + type: 'uint64', + }, + { + components: [ + { + internalType: 'uint256', + name: 'txType', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'from', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'to', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'gasLimit', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'gasPerPubdataByteLimit', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'maxFeePerGas', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'maxPriorityFeePerGas', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'paymaster', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'nonce', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'value', + type: 'uint256', + }, + { + internalType: 'uint256[4]', + name: 'reserved', + type: 'uint256[4]', + }, + { + internalType: 'bytes', + name: 'data', + type: 'bytes', + }, + { + internalType: 'bytes', + name: 'signature', + type: 'bytes', + }, + { + internalType: 'uint256[]', + name: 'factoryDeps', + type: 'uint256[]', + }, + { + internalType: 'bytes', + name: 'paymasterInput', + type: 'bytes', + }, + { + internalType: 'bytes', + name: 'reservedDynamic', + type: 'bytes', + }, + ], + indexed: false, + internalType: 'struct L2CanonicalTransaction', + name: 'transaction', + type: 'tuple', + }, + { + indexed: false, + internalType: 'bytes[]', + name: 'factoryDeps', + type: 'bytes[]', + }, + ], + name: 'NewPriorityRequest', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: false, + internalType: 'uint256', + name: 'oldPriorityTxMaxGasLimit', + type: 'uint256', + }, + { + indexed: false, + internalType: 'uint256', + name: 'newPriorityTxMaxGasLimit', + type: 'uint256', + }, + ], + name: 'NewPriorityTxMaxGasLimit', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: false, + internalType: 'address', + name: 'oldTransactionFilterer', + type: 'address', + }, + { + indexed: false, + internalType: 'address', + name: 'newTransactionFilterer', + type: 'address', + }, + ], + name: 'NewTransactionFilterer', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + components: [ + { + components: [ + { + internalType: 'address', + name: 'facet', + type: 'address', + }, + { + internalType: 'enum Diamond.Action', + name: 'action', + type: 'uint8', + }, + { + internalType: 'bool', + name: 'isFreezable', + type: 'bool', + }, + { + internalType: 'bytes4[]', + name: 'selectors', + type: 'bytes4[]', + }, + ], + internalType: 'struct Diamond.FacetCut[]', + name: 'facetCuts', + type: 'tuple[]', + }, + { + internalType: 'address', + name: 'initAddress', + type: 'address', + }, + { + internalType: 'bytes', + name: 'initCalldata', + type: 'bytes', + }, + ], + indexed: false, + internalType: 'struct Diamond.DiamondCutData', + name: 'diamondCut', + type: 'tuple', + }, + { + indexed: true, + internalType: 'uint256', + name: 'proposalId', + type: 'uint256', + }, + { + indexed: false, + internalType: 'bytes32', + name: 'proposalSalt', + type: 'bytes32', + }, + ], + name: 'ProposeTransparentUpgrade', + type: 'event', + }, + { + anonymous: false, + inputs: [], + name: 'Unfreeze', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: 'address', + name: 'validatorAddress', + type: 'address', + }, + { + indexed: false, + internalType: 'bool', + name: 'isActive', + type: 'bool', + }, + ], + name: 'ValidatorStatusUpdate', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: false, + internalType: 'enum PubdataPricingMode', + name: 'validiumMode', + type: 'uint8', + }, + ], + name: 'ValidiumModeStatusUpdate', + type: 'event', + }, + { + inputs: [], + name: 'acceptAdmin', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [], + name: 'baseTokenGasPriceMultiplierDenominator', + outputs: [ + { + internalType: 'uint128', + name: '', + type: 'uint128', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'baseTokenGasPriceMultiplierNominator', + outputs: [ + { + internalType: 'uint128', + name: '', + type: 'uint128', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { + components: [ + { + internalType: 'address', + name: 'sender', + type: 'address', + }, + { + internalType: 'address', + name: 'contractL2', + type: 'address', + }, + { + internalType: 'uint256', + name: 'mintValue', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'l2Value', + type: 'uint256', + }, + { + internalType: 'bytes', + name: 'l2Calldata', + type: 'bytes', + }, + { + internalType: 'uint256', + name: 'l2GasLimit', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'l2GasPerPubdataByteLimit', + type: 'uint256', + }, + { + internalType: 'bytes[]', + name: 'factoryDeps', + type: 'bytes[]', + }, + { + internalType: 'address', + name: 'refundRecipient', + type: 'address', + }, + ], + internalType: 'struct BridgehubL2TransactionRequest', + name: '_request', + type: 'tuple', + }, + ], + name: 'bridgehubRequestL2Transaction', + outputs: [ + { + internalType: 'bytes32', + name: 'canonicalTxHash', + type: 'bytes32', + }, + ], + stateMutability: 'payable', + type: 'function', + }, + { + inputs: [ + { + components: [ + { + internalType: 'enum PubdataPricingMode', + name: 'pubdataPricingMode', + type: 'uint8', + }, + { + internalType: 'uint32', + name: 'batchOverheadL1Gas', + type: 'uint32', + }, + { + internalType: 'uint32', + name: 'maxPubdataPerBatch', + type: 'uint32', + }, + { + internalType: 'uint32', + name: 'maxL2GasPerBatch', + type: 'uint32', + }, + { + internalType: 'uint32', + name: 'priorityTxMaxPubdata', + type: 'uint32', + }, + { + internalType: 'uint64', + name: 'minimalL2GasPrice', + type: 'uint64', + }, + ], + internalType: 'struct FeeParams', + name: '_newFeeParams', + type: 'tuple', + }, + ], + name: 'changeFeeParams', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + components: [ + { + internalType: 'uint64', + name: 'batchNumber', + type: 'uint64', + }, + { + internalType: 'bytes32', + name: 'batchHash', + type: 'bytes32', + }, + { + internalType: 'uint64', + name: 'indexRepeatedStorageChanges', + type: 'uint64', + }, + { + internalType: 'uint256', + name: 'numberOfLayer1Txs', + type: 'uint256', + }, + { + internalType: 'bytes32', + name: 'priorityOperationsHash', + type: 'bytes32', + }, + { + internalType: 'bytes32', + name: 'l2LogsTreeRoot', + type: 'bytes32', + }, + { + internalType: 'uint256', + name: 'timestamp', + type: 'uint256', + }, + { + internalType: 'bytes32', + name: 'commitment', + type: 'bytes32', + }, + ], + internalType: 'struct IExecutor.StoredBatchInfo', + name: '_lastCommittedBatchData', + type: 'tuple', + }, + { + components: [ + { + internalType: 'uint64', + name: 'batchNumber', + type: 'uint64', + }, + { + internalType: 'uint64', + name: 'timestamp', + type: 'uint64', + }, + { + internalType: 'uint64', + name: 'indexRepeatedStorageChanges', + type: 'uint64', + }, + { + internalType: 'bytes32', + name: 'newStateRoot', + type: 'bytes32', + }, + { + internalType: 'uint256', + name: 'numberOfLayer1Txs', + type: 'uint256', + }, + { + internalType: 'bytes32', + name: 'priorityOperationsHash', + type: 'bytes32', + }, + { + internalType: 'bytes32', + name: 'bootloaderHeapInitialContentsHash', + type: 'bytes32', + }, + { + internalType: 'bytes32', + name: 'eventsQueueStateHash', + type: 'bytes32', + }, + { + internalType: 'bytes', + name: 'systemLogs', + type: 'bytes', + }, + { + internalType: 'bytes', + name: 'pubdataCommitments', + type: 'bytes', + }, + ], + internalType: 'struct IExecutor.CommitBatchInfo[]', + name: '_newBatchesData', + type: 'tuple[]', + }, + ], + name: 'commitBatches', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'uint256', + name: '_chainId', + type: 'uint256', + }, + { + components: [ + { + internalType: 'uint64', + name: 'batchNumber', + type: 'uint64', + }, + { + internalType: 'bytes32', + name: 'batchHash', + type: 'bytes32', + }, + { + internalType: 'uint64', + name: 'indexRepeatedStorageChanges', + type: 'uint64', + }, + { + internalType: 'uint256', + name: 'numberOfLayer1Txs', + type: 'uint256', + }, + { + internalType: 'bytes32', + name: 'priorityOperationsHash', + type: 'bytes32', + }, + { + internalType: 'bytes32', + name: 'l2LogsTreeRoot', + type: 'bytes32', + }, + { + internalType: 'uint256', + name: 'timestamp', + type: 'uint256', + }, + { + internalType: 'bytes32', + name: 'commitment', + type: 'bytes32', + }, + ], + internalType: 'struct IExecutor.StoredBatchInfo', + name: '_lastCommittedBatchData', + type: 'tuple', + }, + { + components: [ + { + internalType: 'uint64', + name: 'batchNumber', + type: 'uint64', + }, + { + internalType: 'uint64', + name: 'timestamp', + type: 'uint64', + }, + { + internalType: 'uint64', + name: 'indexRepeatedStorageChanges', + type: 'uint64', + }, + { + internalType: 'bytes32', + name: 'newStateRoot', + type: 'bytes32', + }, + { + internalType: 'uint256', + name: 'numberOfLayer1Txs', + type: 'uint256', + }, + { + internalType: 'bytes32', + name: 'priorityOperationsHash', + type: 'bytes32', + }, + { + internalType: 'bytes32', + name: 'bootloaderHeapInitialContentsHash', + type: 'bytes32', + }, + { + internalType: 'bytes32', + name: 'eventsQueueStateHash', + type: 'bytes32', + }, + { + internalType: 'bytes', + name: 'systemLogs', + type: 'bytes', + }, + { + internalType: 'bytes', + name: 'pubdataCommitments', + type: 'bytes', + }, + ], + internalType: 'struct IExecutor.CommitBatchInfo[]', + name: '_newBatchesData', + type: 'tuple[]', + }, + ], + name: 'commitBatchesSharedBridge', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + components: [ + { + internalType: 'uint64', + name: 'batchNumber', + type: 'uint64', + }, + { + internalType: 'bytes32', + name: 'batchHash', + type: 'bytes32', + }, + { + internalType: 'uint64', + name: 'indexRepeatedStorageChanges', + type: 'uint64', + }, + { + internalType: 'uint256', + name: 'numberOfLayer1Txs', + type: 'uint256', + }, + { + internalType: 'bytes32', + name: 'priorityOperationsHash', + type: 'bytes32', + }, + { + internalType: 'bytes32', + name: 'l2LogsTreeRoot', + type: 'bytes32', + }, + { + internalType: 'uint256', + name: 'timestamp', + type: 'uint256', + }, + { + internalType: 'bytes32', + name: 'commitment', + type: 'bytes32', + }, + ], + internalType: 'struct IExecutor.StoredBatchInfo[]', + name: '_batchesData', + type: 'tuple[]', + }, + ], + name: 'executeBatches', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'uint256', + name: '_chainId', + type: 'uint256', + }, + { + components: [ + { + internalType: 'uint64', + name: 'batchNumber', + type: 'uint64', + }, + { + internalType: 'bytes32', + name: 'batchHash', + type: 'bytes32', + }, + { + internalType: 'uint64', + name: 'indexRepeatedStorageChanges', + type: 'uint64', + }, + { + internalType: 'uint256', + name: 'numberOfLayer1Txs', + type: 'uint256', + }, + { + internalType: 'bytes32', + name: 'priorityOperationsHash', + type: 'bytes32', + }, + { + internalType: 'bytes32', + name: 'l2LogsTreeRoot', + type: 'bytes32', + }, + { + internalType: 'uint256', + name: 'timestamp', + type: 'uint256', + }, + { + internalType: 'bytes32', + name: 'commitment', + type: 'bytes32', + }, + ], + internalType: 'struct IExecutor.StoredBatchInfo[]', + name: '_batchesData', + type: 'tuple[]', + }, + ], + name: 'executeBatchesSharedBridge', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + components: [ + { + components: [ + { + internalType: 'address', + name: 'facet', + type: 'address', + }, + { + internalType: 'enum Diamond.Action', + name: 'action', + type: 'uint8', + }, + { + internalType: 'bool', + name: 'isFreezable', + type: 'bool', + }, + { + internalType: 'bytes4[]', + name: 'selectors', + type: 'bytes4[]', + }, + ], + internalType: 'struct Diamond.FacetCut[]', + name: 'facetCuts', + type: 'tuple[]', + }, + { + internalType: 'address', + name: 'initAddress', + type: 'address', + }, + { + internalType: 'bytes', + name: 'initCalldata', + type: 'bytes', + }, + ], + internalType: 'struct Diamond.DiamondCutData', + name: '_diamondCut', + type: 'tuple', + }, + ], + name: 'executeUpgrade', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'bytes4', + name: '_selector', + type: 'bytes4', + }, + ], + name: 'facetAddress', + outputs: [ + { + internalType: 'address', + name: 'facet', + type: 'address', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'facetAddresses', + outputs: [ + { + internalType: 'address[]', + name: 'facets', + type: 'address[]', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { + internalType: 'address', + name: '_facet', + type: 'address', + }, + ], + name: 'facetFunctionSelectors', + outputs: [ + { + internalType: 'bytes4[]', + name: '', + type: 'bytes4[]', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'facets', + outputs: [ + { + components: [ + { + internalType: 'address', + name: 'addr', + type: 'address', + }, + { + internalType: 'bytes4[]', + name: 'selectors', + type: 'bytes4[]', + }, + ], + internalType: 'struct IGetters.Facet[]', + name: '', + type: 'tuple[]', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { + internalType: 'uint256', + name: '_l2BatchNumber', + type: 'uint256', + }, + { + internalType: 'uint256', + name: '_l2MessageIndex', + type: 'uint256', + }, + { + internalType: 'uint16', + name: '_l2TxNumberInBatch', + type: 'uint16', + }, + { + internalType: 'bytes', + name: '_message', + type: 'bytes', + }, + { + internalType: 'bytes32[]', + name: '_merkleProof', + type: 'bytes32[]', + }, + ], + name: 'finalizeEthWithdrawal', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [], + name: 'freezeDiamond', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [], + name: 'getAdmin', + outputs: [ + { + internalType: 'address', + name: '', + type: 'address', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'getBaseToken', + outputs: [ + { + internalType: 'address', + name: '', + type: 'address', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'getBaseTokenBridge', + outputs: [ + { + internalType: 'address', + name: '', + type: 'address', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'getBridgehub', + outputs: [ + { + internalType: 'address', + name: '', + type: 'address', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'getFirstUnprocessedPriorityTx', + outputs: [ + { + internalType: 'uint256', + name: '', + type: 'uint256', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'getL2BootloaderBytecodeHash', + outputs: [ + { + internalType: 'bytes32', + name: '', + type: 'bytes32', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'getL2DefaultAccountBytecodeHash', + outputs: [ + { + internalType: 'bytes32', + name: '', + type: 'bytes32', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'getL2SystemContractsUpgradeBatchNumber', + outputs: [ + { + internalType: 'uint256', + name: '', + type: 'uint256', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'getL2SystemContractsUpgradeTxHash', + outputs: [ + { + internalType: 'bytes32', + name: '', + type: 'bytes32', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'getName', + outputs: [ + { + internalType: 'string', + name: '', + type: 'string', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'getPendingAdmin', + outputs: [ + { + internalType: 'address', + name: '', + type: 'address', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'getPriorityQueueSize', + outputs: [ + { + internalType: 'uint256', + name: '', + type: 'uint256', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'getPriorityTxMaxGasLimit', + outputs: [ + { + internalType: 'uint256', + name: '', + type: 'uint256', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'getProtocolVersion', + outputs: [ + { + internalType: 'uint256', + name: '', + type: 'uint256', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'getPubdataPricingMode', + outputs: [ + { + internalType: 'enum PubdataPricingMode', + name: '', + type: 'uint8', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'getStateTransitionManager', + outputs: [ + { + internalType: 'address', + name: '', + type: 'address', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'getTotalBatchesCommitted', + outputs: [ + { + internalType: 'uint256', + name: '', + type: 'uint256', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'getTotalBatchesExecuted', + outputs: [ + { + internalType: 'uint256', + name: '', + type: 'uint256', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'getTotalBatchesVerified', + outputs: [ + { + internalType: 'uint256', + name: '', + type: 'uint256', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'getTotalPriorityTxs', + outputs: [ + { + internalType: 'uint256', + name: '', + type: 'uint256', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'getVerifier', + outputs: [ + { + internalType: 'address', + name: '', + type: 'address', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'getVerifierParams', + outputs: [ + { + components: [ + { + internalType: 'bytes32', + name: 'recursionNodeLevelVkHash', + type: 'bytes32', + }, + { + internalType: 'bytes32', + name: 'recursionLeafLevelVkHash', + type: 'bytes32', + }, + { + internalType: 'bytes32', + name: 'recursionCircuitsSetVksHash', + type: 'bytes32', + }, + ], + internalType: 'struct VerifierParams', + name: '', + type: 'tuple', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'isDiamondStorageFrozen', + outputs: [ + { + internalType: 'bool', + name: '', + type: 'bool', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { + internalType: 'uint256', + name: '_l2BatchNumber', + type: 'uint256', + }, + { + internalType: 'uint256', + name: '_l2MessageIndex', + type: 'uint256', + }, + ], + name: 'isEthWithdrawalFinalized', + outputs: [ + { + internalType: 'bool', + name: '', + type: 'bool', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { + internalType: 'address', + name: '_facet', + type: 'address', + }, + ], + name: 'isFacetFreezable', + outputs: [ + { + internalType: 'bool', + name: 'isFreezable', + type: 'bool', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { + internalType: 'bytes4', + name: '_selector', + type: 'bytes4', + }, + ], + name: 'isFunctionFreezable', + outputs: [ + { + internalType: 'bool', + name: '', + type: 'bool', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { + internalType: 'address', + name: '_address', + type: 'address', + }, + ], + name: 'isValidator', + outputs: [ + { + internalType: 'bool', + name: '', + type: 'bool', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { + internalType: 'uint256', + name: '_batchNumber', + type: 'uint256', + }, + ], + name: 'l2LogsRootHash', + outputs: [ + { + internalType: 'bytes32', + name: 'merkleRoot', + type: 'bytes32', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { + internalType: 'uint256', + name: '_gasPrice', + type: 'uint256', + }, + { + internalType: 'uint256', + name: '_l2GasLimit', + type: 'uint256', + }, + { + internalType: 'uint256', + name: '_l2GasPerPubdataByteLimit', + type: 'uint256', + }, + ], + name: 'l2TransactionBaseCost', + outputs: [ + { + internalType: 'uint256', + name: '', + type: 'uint256', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'priorityQueueFrontOperation', + outputs: [ + { + components: [ + { + internalType: 'bytes32', + name: 'canonicalTxHash', + type: 'bytes32', + }, + { + internalType: 'uint64', + name: 'expirationTimestamp', + type: 'uint64', + }, + { + internalType: 'uint192', + name: 'layer2Tip', + type: 'uint192', + }, + ], + internalType: 'struct PriorityOperation', + name: '', + type: 'tuple', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { + components: [ + { + internalType: 'uint64', + name: 'batchNumber', + type: 'uint64', + }, + { + internalType: 'bytes32', + name: 'batchHash', + type: 'bytes32', + }, + { + internalType: 'uint64', + name: 'indexRepeatedStorageChanges', + type: 'uint64', + }, + { + internalType: 'uint256', + name: 'numberOfLayer1Txs', + type: 'uint256', + }, + { + internalType: 'bytes32', + name: 'priorityOperationsHash', + type: 'bytes32', + }, + { + internalType: 'bytes32', + name: 'l2LogsTreeRoot', + type: 'bytes32', + }, + { + internalType: 'uint256', + name: 'timestamp', + type: 'uint256', + }, + { + internalType: 'bytes32', + name: 'commitment', + type: 'bytes32', + }, + ], + internalType: 'struct IExecutor.StoredBatchInfo', + name: '_prevBatch', + type: 'tuple', + }, + { + components: [ + { + internalType: 'uint64', + name: 'batchNumber', + type: 'uint64', + }, + { + internalType: 'bytes32', + name: 'batchHash', + type: 'bytes32', + }, + { + internalType: 'uint64', + name: 'indexRepeatedStorageChanges', + type: 'uint64', + }, + { + internalType: 'uint256', + name: 'numberOfLayer1Txs', + type: 'uint256', + }, + { + internalType: 'bytes32', + name: 'priorityOperationsHash', + type: 'bytes32', + }, + { + internalType: 'bytes32', + name: 'l2LogsTreeRoot', + type: 'bytes32', + }, + { + internalType: 'uint256', + name: 'timestamp', + type: 'uint256', + }, + { + internalType: 'bytes32', + name: 'commitment', + type: 'bytes32', + }, + ], + internalType: 'struct IExecutor.StoredBatchInfo[]', + name: '_committedBatches', + type: 'tuple[]', + }, + { + components: [ + { + internalType: 'uint256[]', + name: 'recursiveAggregationInput', + type: 'uint256[]', + }, + { + internalType: 'uint256[]', + name: 'serializedProof', + type: 'uint256[]', + }, + ], + internalType: 'struct IExecutor.ProofInput', + name: '_proof', + type: 'tuple', + }, + ], + name: 'proveBatches', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'uint256', + name: '_chainId', + type: 'uint256', + }, + { + components: [ + { + internalType: 'uint64', + name: 'batchNumber', + type: 'uint64', + }, + { + internalType: 'bytes32', + name: 'batchHash', + type: 'bytes32', + }, + { + internalType: 'uint64', + name: 'indexRepeatedStorageChanges', + type: 'uint64', + }, + { + internalType: 'uint256', + name: 'numberOfLayer1Txs', + type: 'uint256', + }, + { + internalType: 'bytes32', + name: 'priorityOperationsHash', + type: 'bytes32', + }, + { + internalType: 'bytes32', + name: 'l2LogsTreeRoot', + type: 'bytes32', + }, + { + internalType: 'uint256', + name: 'timestamp', + type: 'uint256', + }, + { + internalType: 'bytes32', + name: 'commitment', + type: 'bytes32', + }, + ], + internalType: 'struct IExecutor.StoredBatchInfo', + name: '_prevBatch', + type: 'tuple', + }, + { + components: [ + { + internalType: 'uint64', + name: 'batchNumber', + type: 'uint64', + }, + { + internalType: 'bytes32', + name: 'batchHash', + type: 'bytes32', + }, + { + internalType: 'uint64', + name: 'indexRepeatedStorageChanges', + type: 'uint64', + }, + { + internalType: 'uint256', + name: 'numberOfLayer1Txs', + type: 'uint256', + }, + { + internalType: 'bytes32', + name: 'priorityOperationsHash', + type: 'bytes32', + }, + { + internalType: 'bytes32', + name: 'l2LogsTreeRoot', + type: 'bytes32', + }, + { + internalType: 'uint256', + name: 'timestamp', + type: 'uint256', + }, + { + internalType: 'bytes32', + name: 'commitment', + type: 'bytes32', + }, + ], + internalType: 'struct IExecutor.StoredBatchInfo[]', + name: '_committedBatches', + type: 'tuple[]', + }, + { + components: [ + { + internalType: 'uint256[]', + name: 'recursiveAggregationInput', + type: 'uint256[]', + }, + { + internalType: 'uint256[]', + name: 'serializedProof', + type: 'uint256[]', + }, + ], + internalType: 'struct IExecutor.ProofInput', + name: '_proof', + type: 'tuple', + }, + ], + name: 'proveBatchesSharedBridge', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'bytes32', + name: '_l2TxHash', + type: 'bytes32', + }, + { + internalType: 'uint256', + name: '_l2BatchNumber', + type: 'uint256', + }, + { + internalType: 'uint256', + name: '_l2MessageIndex', + type: 'uint256', + }, + { + internalType: 'uint16', + name: '_l2TxNumberInBatch', + type: 'uint16', + }, + { + internalType: 'bytes32[]', + name: '_merkleProof', + type: 'bytes32[]', + }, + { + internalType: 'enum TxStatus', + name: '_status', + type: 'uint8', + }, + ], + name: 'proveL1ToL2TransactionStatus', + outputs: [ + { + internalType: 'bool', + name: '', + type: 'bool', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { + internalType: 'uint256', + name: '_batchNumber', + type: 'uint256', + }, + { + internalType: 'uint256', + name: '_index', + type: 'uint256', + }, + { + components: [ + { + internalType: 'uint8', + name: 'l2ShardId', + type: 'uint8', + }, + { + internalType: 'bool', + name: 'isService', + type: 'bool', + }, + { + internalType: 'uint16', + name: 'txNumberInBatch', + type: 'uint16', + }, + { + internalType: 'address', + name: 'sender', + type: 'address', + }, + { + internalType: 'bytes32', + name: 'key', + type: 'bytes32', + }, + { + internalType: 'bytes32', + name: 'value', + type: 'bytes32', + }, + ], + internalType: 'struct L2Log', + name: '_log', + type: 'tuple', + }, + { + internalType: 'bytes32[]', + name: '_proof', + type: 'bytes32[]', + }, + ], + name: 'proveL2LogInclusion', + outputs: [ + { + internalType: 'bool', + name: '', + type: 'bool', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { + internalType: 'uint256', + name: '_batchNumber', + type: 'uint256', + }, + { + internalType: 'uint256', + name: '_index', + type: 'uint256', + }, + { + components: [ + { + internalType: 'uint16', + name: 'txNumberInBatch', + type: 'uint16', + }, + { + internalType: 'address', + name: 'sender', + type: 'address', + }, + { + internalType: 'bytes', + name: 'data', + type: 'bytes', + }, + ], + internalType: 'struct L2Message', + name: '_message', + type: 'tuple', + }, + { + internalType: 'bytes32[]', + name: '_proof', + type: 'bytes32[]', + }, + ], + name: 'proveL2MessageInclusion', + outputs: [ + { + internalType: 'bool', + name: '', + type: 'bool', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { + internalType: 'address', + name: '_contractL2', + type: 'address', + }, + { + internalType: 'uint256', + name: '_l2Value', + type: 'uint256', + }, + { + internalType: 'bytes', + name: '_calldata', + type: 'bytes', + }, + { + internalType: 'uint256', + name: '_l2GasLimit', + type: 'uint256', + }, + { + internalType: 'uint256', + name: '_l2GasPerPubdataByteLimit', + type: 'uint256', + }, + { + internalType: 'bytes[]', + name: '_factoryDeps', + type: 'bytes[]', + }, + { + internalType: 'address', + name: '_refundRecipient', + type: 'address', + }, + ], + name: 'requestL2Transaction', + outputs: [ + { + internalType: 'bytes32', + name: 'canonicalTxHash', + type: 'bytes32', + }, + ], + stateMutability: 'payable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'uint256', + name: '_newLastBatch', + type: 'uint256', + }, + ], + name: 'revertBatches', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'uint256', + name: '_chainId', + type: 'uint256', + }, + { + internalType: 'uint256', + name: '_newLastBatch', + type: 'uint256', + }, + ], + name: 'revertBatchesSharedBridge', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'address', + name: '_newPendingAdmin', + type: 'address', + }, + ], + name: 'setPendingAdmin', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'bool', + name: '_zkPorterIsAvailable', + type: 'bool', + }, + ], + name: 'setPorterAvailability', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'uint256', + name: '_newPriorityTxMaxGasLimit', + type: 'uint256', + }, + ], + name: 'setPriorityTxMaxGasLimit', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'uint128', + name: '_nominator', + type: 'uint128', + }, + { + internalType: 'uint128', + name: '_denominator', + type: 'uint128', + }, + ], + name: 'setTokenMultiplier', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'address', + name: '_transactionFilterer', + type: 'address', + }, + ], + name: 'setTransactionFilterer', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'address', + name: '_validator', + type: 'address', + }, + { + internalType: 'bool', + name: '_active', + type: 'bool', + }, + ], + name: 'setValidator', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'enum PubdataPricingMode', + name: '_validiumMode', + type: 'uint8', + }, + ], + name: 'setValidiumMode', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'uint256', + name: '_batchNumber', + type: 'uint256', + }, + ], + name: 'storedBatchHash', + outputs: [ + { + internalType: 'bytes32', + name: '', + type: 'bytes32', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'transferEthToSharedBridge', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [], + name: 'unfreezeDiamond', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'uint256', + name: '_protocolVersion', + type: 'uint256', + }, + { + components: [ + { + components: [ + { + internalType: 'address', + name: 'facet', + type: 'address', + }, + { + internalType: 'enum Diamond.Action', + name: 'action', + type: 'uint8', + }, + { + internalType: 'bool', + name: 'isFreezable', + type: 'bool', + }, + { + internalType: 'bytes4[]', + name: 'selectors', + type: 'bytes4[]', + }, + ], + internalType: 'struct Diamond.FacetCut[]', + name: 'facetCuts', + type: 'tuple[]', + }, + { + internalType: 'address', + name: 'initAddress', + type: 'address', + }, + { + internalType: 'bytes', + name: 'initCalldata', + type: 'bytes', + }, + ], + internalType: 'struct Diamond.DiamondCutData', + name: '_cutData', + type: 'tuple', + }, + ], + name: 'upgradeChainFromVersion', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, +] as const; diff --git a/src/plugin.ts b/src/plugin.ts index 606d44f..d3cb9e7 100644 --- a/src/plugin.ts +++ b/src/plugin.ts @@ -1,10 +1,10 @@ import type { Address } from 'web3'; import { Web3PluginBase, Contract } from 'web3'; import type { Web3RequestManager } from 'web3-core'; -import { ERC20TokenAbi } from './contracts/ERC20Token'; +import { IERC20ABI } from './contracts/IERC20'; import { RpcMethods } from './rpc.methods'; import { ETH_ADDRESS, ZERO_ADDRESS } from './constants'; -import { L2BridgeAbi } from './contracts/L2Bridge'; +import { IL2BridgeABI } from './contracts/IL2Bridge'; export class ZkSyncPlugin extends Web3PluginBase { public pluginNamespace = 'zkSync'; @@ -13,8 +13,8 @@ export class ZkSyncPlugin extends Web3PluginBase { public wethBridgeL1: string; public wethBridgeL2: string; public _rpc?: RpcMethods; - public _l2BridgeContracts: Record>; - public _erc20Contracts: Record>; + public _l2BridgeContracts: Record>; + public _erc20Contracts: Record>; constructor() { super(); @@ -32,9 +32,7 @@ export class ZkSyncPlugin extends Web3PluginBase { */ get rpc(): RpcMethods { if (!this._rpc) { - this._rpc = new RpcMethods( - this.requestManager as unknown as Web3RequestManager, - ); + this._rpc = new RpcMethods(this.requestManager as unknown as Web3RequestManager); } return this._rpc; } @@ -43,9 +41,9 @@ export class ZkSyncPlugin extends Web3PluginBase { * Get L2 bridge contract instance * @param address - Contract address */ - getL2BridgeContract(address: Address): Contract { + getL2BridgeContract(address: Address): Contract { if (!this._l2BridgeContracts[address]) { - this._l2BridgeContracts[address] = new Contract(L2BridgeAbi, address); + this._l2BridgeContracts[address] = new Contract(IL2BridgeABI, address); this._l2BridgeContracts[address].link(this); } return this._l2BridgeContracts[address]; @@ -55,9 +53,9 @@ export class ZkSyncPlugin extends Web3PluginBase { * Get the ERC20 contract instance * @param address - Contract address */ - erc20(address: string): Contract { + erc20(address: string): Contract { if (!this._erc20Contracts[address]) { - this._erc20Contracts[address] = new Contract(ERC20TokenAbi, address); + this._erc20Contracts[address] = new Contract(IERC20ABI, address); this._erc20Contracts[address].link(this); } return this._erc20Contracts[address]; @@ -104,9 +102,7 @@ export class ZkSyncPlugin extends Web3PluginBase { return l1Token; } } catch (e) { - throw new Error( - `Error getting L1 address for token ${token}. ${JSON.stringify(e)}`, - ); + throw new Error(`Error getting L1 address for token ${token}. ${JSON.stringify(e)}`); } } @@ -132,9 +128,7 @@ export class ZkSyncPlugin extends Web3PluginBase { return l2WethToken; } } catch (e) { - throw new Error( - `Error getting L2 address for token ${token}. ${JSON.stringify(e)}`, - ); + throw new Error(`Error getting L2 address for token ${token}. ${JSON.stringify(e)}`); } } diff --git a/src/types.ts b/src/types.ts index 2ea8086..0eee7e4 100644 --- a/src/types.ts +++ b/src/types.ts @@ -1,22 +1,235 @@ -import type { Address, Bytes, HexString, Numbers } from 'web3'; +export type { Bytes, HexString, Numbers } from 'web3'; +import { Bytes, HexString, Numbers } from 'web3-types'; -export interface BatchDetails { - number: number; - timestamp: number; - l1TxCount: number; - l2TxCount: number; - rootHash?: string; - status: string; - commitTxHash?: string; - committedAt?: Date; - proveTxHash?: string; - provenAt?: Date; - executeTxHash?: string; - executedAt?: Date; - l1GasPrice: number; - l2FairGasPrice: number; +import { EIP1193Provider } from 'web3'; + +import { FeeMarketEIP1559TxData } from 'web3-eth-accounts'; + +import { RpcMethods } from './rpc.methods'; + +/** 0x-prefixed, hex encoded, ethereum account address. */ +export type Address = string; + +/** 0x-prefixed, hex encoded, ECDSA signature. */ +export type Signature = string; + +/** Ethereum network. */ +export enum Network { + Mainnet = 1, + Ropsten = 3, + Rinkeby = 4, + Goerli = 5, + Sepolia = 6, + Localhost = 9, + EraTestNode = 10, +} + +/** Enumerated list of priority queue types. */ +export enum PriorityQueueType { + Deque = 0, + HeapBuffer = 1, + Heap = 2, +} + +/** Enumerated list of priority operation tree types. */ +export enum PriorityOpTree { + Full = 0, + Rollup = 1, +} + +/** Enumerated list of transaction status types. */ +export enum TransactionStatus { + /** Transaction not found. */ + NotFound = 'not-found', + /** Transaction is processing. */ + Processing = 'processing', + /** Transaction has been committed. */ + Committed = 'committed', + /** Transaction has been finalized. */ + Finalized = 'finalized', +} + +/** Type defining a paymaster by its address and the bytestream input. */ +export type PaymasterParams = { + /** The address of the paymaster. */ + paymaster: Address; + /** The bytestream input for the paymaster. */ + paymasterInput: Bytes; +}; + +/** Contains EIP712 transaction metadata. */ +export type Eip712Meta = { + /** The maximum amount of gas the user is willing to pay for a single byte of pubdata. */ + gasPerPubdata?: Numbers; + /** An array of bytes containing the bytecode of the contract being deployed and any related contracts it can deploy. */ + factoryDeps?: Bytes[]; + /** Custom signature used for cases where the signer's account is not an EOA. */ + customSignature?: Bytes; + /** Parameters for configuring the custom paymaster for the transaction. */ + paymasterParams?: PaymasterParams; +}; + +/** + * Specifies a specific block. This can be represented by: + * - A numeric value (number, bigint, or hexadecimal string) representing the block height, where the genesis block is block 0. + * A negative value indicates the block number should be deducted from the most recent block. + * - A block hash as a string, specifying a specific block by its block hash. + * This allows potentially orphaned blocks to be specified without ambiguity, but many backends do not support this for some operations. + * - Constants representing special blocks such as 'committed', 'finalized', 'latest', 'earliest', or 'pending'. + */ +export type BlockTag = + | Numbers + | string // block hash + | 'committed' + | 'finalized' + | 'latest' + | 'earliest' + | 'pending'; + +/** Pipe-delimited choice of deployment types. */ +export type DeploymentType = 'create' | 'createAccount' | 'create2' | 'create2Account'; + +/** Bridged token. */ +export interface Token { + l1Address: Address; + l2Address: Address; + name: string; + symbol: string; + decimals: number; +} + +/** Represents the transaction fee parameters. */ +export interface Fee { + /** The maximum amount of gas allowed for the transaction. */ + gasLimit: bigint; + /** The maximum amount of gas the user is willing to pay for a single byte of pubdata. */ + gasPerPubdataLimit: bigint; + /** The EIP1559 tip per gas. */ + maxPriorityFeePerGas: bigint; + /** The EIP1559 fee cap per gas. */ + maxFeePerGas: bigint; +} + +/** Represents a message proof. */ +export interface MessageProof { + id: number; + proof: string[]; + root: string; +} + +export interface zkSyncTxData extends FeeMarketEIP1559TxData { + /** The batch number on the L1 network. */ + readonly l1BatchNumber: null | number; + /** The transaction index within the batch on the L1 network. */ + readonly l1BatchTxIndex: null | number; +} + +/** + * Represents a L2 to L1 transaction log. + */ +export interface L2ToL1Log { + blockNumber: number; + blockHash: string; + l1BatchNumber: number; + transactionIndex: number; + shardId: number; + isService: boolean; + sender: string; + key: string; + value: string; + transactionHash: string; + logIndex: number; +} + +/** A map containing accounts and their balances. */ +export type BalancesMap = { [key: string]: bigint }; + +/** Represents deployment information. */ +export interface DeploymentInfo { + /** The account responsible for deployment. */ + sender: Address; + /** The hash of the contract/account bytecode. */ + bytecodeHash: string; + /** The deployed address of the contract/address. */ + deployedAddress: Address; +} + +/** + * Represents the input data structure for an approval-based paymaster. + */ +export interface ApprovalBasedPaymasterInput { + /** The type of the paymaster input. */ + type: 'ApprovalBased'; + /** The address of the token to be approved. */ + token: Address; + /** The minimum allowance required for the token approval. */ + minimalAllowance: Numbers; + /** The additional input data. */ + innerInput: Bytes; +} + +/** + * Represents the input data structure for a general paymaster. + */ +export interface GeneralPaymasterInput { + /** The type of the paymaster input. */ + type: 'General'; + /** The additional input data. */ + innerInput: Bytes; +} + +/** + * Represents an Ethereum signature consisting of the components `v`, `r`, and `s`. + */ +export interface EthereumSignature { + /** The recovery id. */ + v: number; + /** The "r" value of the signature. */ + r: Bytes; + /** The "s" value of the signature. */ + s: Bytes; +} + +/** + * Represents the input data structure for a paymaster. + * It can be either approval-based or general. + */ +export type PaymasterInput = ApprovalBasedPaymasterInput | GeneralPaymasterInput; + +/** Enumerated list of account abstraction versions. */ +export enum AccountAbstractionVersion { + /** Used for contracts that are not accounts */ + None = 0, + /** Used for contracts that are accounts */ + Version1 = 1, } +/** + * Enumerated list of account nonce ordering formats. + */ +export enum AccountNonceOrdering { + /** + * Nonces should be ordered in the same way as in externally owned accounts (EOAs). + * This means, for instance, that the operator will always wait for a transaction with nonce `X` + * before processing a transaction with nonce `X+1`. + */ + Sequential = 0, + /** Nonces can be ordered in arbitrary order. */ + Arbitrary = 1, +} + +/** + * Interface representing contract account information containing details on the supported account abstraction version + * and nonce ordering format. + */ +export interface ContractAccountInfo { + /** The supported account abstraction version. */ + supportedAAVersion: AccountAbstractionVersion; + /** The nonce ordering format. */ + nonceOrdering: AccountNonceOrdering; +} + +/** Contains batch information. */ export interface BlockDetails { number: bigint; timestamp: bigint; @@ -33,6 +246,7 @@ export interface BlockDetails { executedAt?: Date; } +/** Contains transaction details information. */ export interface TransactionDetails { isL1Originated: boolean; status: string; @@ -44,6 +258,23 @@ export interface TransactionDetails { ethExecuteTxHash?: string; } +/** Represents the full deposit fee containing fees for both L1 and L2 transactions. */ +export interface FullDepositFee { + /** The maximum fee per gas for L1 transaction. */ + maxFeePerGas?: BigInt; + /** The maximum priority fee per gas for L1 transaction. */ + maxPriorityFeePerGas?: BigInt; + /** The gas price for L2 transaction. */ + gasPrice?: BigInt; + /** The base cost of the deposit transaction on L2. */ + baseCost: BigInt; + /** The gas limit for L1 transaction. */ + l1GasLimit: BigInt; + /** The gas limit for L2 transaction. */ + l2GasLimit: BigInt; +} + +/** Represents a raw block transaction. */ export interface RawBlockTransaction { common_data: { L2: { @@ -77,6 +308,41 @@ export interface RawBlockTransaction { raw_bytes: string; } +/** Contains parameters for finalizing the withdrawal transaction. */ +export interface FinalizeWithdrawalParams { + l1BatchNumber: number | null; + l2MessageIndex: number; + l2TxNumberInBlock: number | null; + message: any; + sender: string; + proof: string[]; +} + +/** Represents storage proof */ +export interface StorageProof { + address: Address; + storageProof: { + index: Numbers; + key: HexString; + value: HexString; + proof: HexString[]; + }[]; +} + +/** + * Signs various types of payloads, optionally using a some kind of secret. + * + * @param payload The payload that needs to be sign already populated transaction to sign. + * @param [secret] The secret used for signing the `payload`. + * @param [provider] The provider is used to fetch data from the network if it is required for signing. + * @returns A promise that resolves to the serialized signature in hexadecimal format. + */ +export type PayloadSigner = ( + payload: Bytes, + secret?: any, + provider?: null | EIP1193Provider, +) => Promise; + export interface WalletBalances { [key: Address]: Numbers; } @@ -101,16 +367,6 @@ export interface L2ToL1Proof { root: HexString; } -export interface Proof { - address: Address; - storageProof: { - index: Numbers; - key: HexString; - value: HexString; - proof: HexString[]; - }[]; -} - export interface EstimateFee { gas_limit: Numbers; gas_per_pubdata_limit: Numbers; diff --git a/test/fixtures.ts b/test/fixtures.ts index f29387c..265d79a 100644 --- a/test/fixtures.ts +++ b/test/fixtures.ts @@ -1,5 +1,5 @@ import type { Address, HexString } from 'web3'; -import type { Proof } from '../src/types'; +import type { StorageProof } from '../src/types'; export const getRawBlockTransactionsData = { input: 251491, @@ -16,52 +16,46 @@ export const getRawBlockTransactionsData = { }, initiatorAddress: '0x202bd724d72fd5a169c8930203e1b60870e4df95', signature: [ - 137, 115, 161, 220, 2, 48, 185, 157, 125, 236, 198, 85, 99, 212, 128, 24, - 126, 171, 22, 34, 146, 36, 193, 208, 83, 3, 134, 11, 74, 38, 89, 252, 37, - 222, 4, 59, 169, 237, 144, 64, 12, 82, 61, 251, 40, 85, 42, 89, 21, 199, 71, - 128, 151, 231, 166, 230, 60, 14, 17, 59, 67, 118, 175, 216, 28, + 137, 115, 161, 220, 2, 48, 185, 157, 125, 236, 198, 85, 99, 212, 128, 24, 126, 171, 22, + 34, 146, 36, 193, 208, 83, 3, 134, 11, 74, 38, 89, 252, 37, 222, 4, 59, 169, 237, 144, + 64, 12, 82, 61, 251, 40, 85, 42, 89, 21, 199, 71, 128, 151, 231, 166, 230, 60, 14, 17, + 59, 67, 118, 175, 216, 28, ], transactionType: 'LegacyTransaction', input: { hash: '0x16d5e37b848eed8b33d927b7c3b9d974cc48e164af5f7a6d172334a1330a5e76', data: [ - 249, 2, 206, 130, 104, 112, 132, 59, 154, 202, 0, 131, 61, 9, 0, 148, - 187, 92, 48, 154, 58, 147, 71, 192, 19, 91, 147, 203, 213, 61, 57, 74, - 168, 67, 69, 229, 128, 185, 2, 100, 201, 128, 117, 57, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 128, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 160, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 253, 105, 228, 93, 111, 81, 228, 130, 172, 79, 143, 46, 20, 242, 21, 82, - 0, 0, 93, 139, 6, 0, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 96, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 56, 197, 134, 51, 192, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 56, 197, - 134, 51, 192, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 56, 197, 134, 51, 192, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 56, 197, 134, - 51, 192, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 221, 91, 114, 141, 15, 115, 53, 225, - 115, 233, 223, 14, 194, 138, 27, 152, 228, 159, 84, 144, 67, 246, 163, - 74, 17, 27, 161, 247, 5, 126, 30, 1, 243, 98, 134, 218, 191, 139, 161, - 27, 167, 121, 27, 171, 231, 94, 248, 206, 73, 195, 156, 142, 246, 164, - 198, 205, 78, 67, 188, 193, 147, 210, 209, 46, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, - 1, 56, 204, 92, 213, 109, 169, 231, 44, 75, 130, 19, 30, 79, 248, 50, - 78, 119, 48, 161, 206, 119, 24, 74, 4, 190, 125, 90, 23, 101, 13, 83, - 24, 231, 138, 70, 132, 57, 11, 124, 83, 101, 7, 232, 64, 136, 14, 187, - 52, 224, 220, 104, 8, 215, 48, 44, 207, 203, 144, 38, 231, 116, 87, 208, - 130, 2, 124, 160, 137, 115, 161, 220, 2, 48, 185, 157, 125, 236, 198, - 85, 99, 212, 128, 24, 126, 171, 22, 34, 146, 36, 193, 208, 83, 3, 134, - 11, 74, 38, 89, 252, 160, 37, 222, 4, 59, 169, 237, 144, 64, 12, 82, 61, - 251, 40, 85, 42, 89, 21, 199, 71, 128, 151, 231, 166, 230, 60, 14, 17, - 59, 67, 118, 175, 216, + 249, 2, 206, 130, 104, 112, 132, 59, 154, 202, 0, 131, 61, 9, 0, 148, 187, 92, 48, + 154, 58, 147, 71, 192, 19, 91, 147, 203, 213, 61, 57, 74, 168, 67, 69, 229, 128, 185, + 2, 100, 201, 128, 117, 57, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 128, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 160, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 253, 105, 228, 93, 111, 81, 228, 130, 172, 79, 143, 46, 20, + 242, 21, 82, 0, 0, 93, 139, 6, 0, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 96, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 56, 197, 134, 51, 192, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 56, 197, + 134, 51, 192, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 56, 197, 134, 51, 192, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 56, 197, 134, 51, 192, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 221, 91, 114, 141, 15, + 115, 53, 225, 115, 233, 223, 14, 194, 138, 27, 152, 228, 159, 84, 144, 67, 246, 163, + 74, 17, 27, 161, 247, 5, 126, 30, 1, 243, 98, 134, 218, 191, 139, 161, 27, 167, 121, + 27, 171, 231, 94, 248, 206, 73, 195, 156, 142, 246, 164, 198, 205, 78, 67, 188, 193, + 147, 210, 209, 46, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 1, 56, 204, 92, 213, 109, 169, 231, 44, 75, 130, 19, 30, + 79, 248, 50, 78, 119, 48, 161, 206, 119, 24, 74, 4, 190, 125, 90, 23, 101, 13, 83, 24, + 231, 138, 70, 132, 57, 11, 124, 83, 101, 7, 232, 64, 136, 14, 187, 52, 224, 220, 104, + 8, 215, 48, 44, 207, 203, 144, 38, 231, 116, 87, 208, 130, 2, 124, 160, 137, 115, 161, + 220, 2, 48, 185, 157, 125, 236, 198, 85, 99, 212, 128, 24, 126, 171, 22, 34, 146, 36, + 193, 208, 83, 3, 134, 11, 74, 38, 89, 252, 160, 37, 222, 4, 59, 169, 237, 144, 64, 12, + 82, 61, 251, 40, 85, 42, 89, 21, 199, 71, 128, 151, 231, 166, 230, 60, 14, 17, 59, 67, + 118, 175, 216, ], }, paymasterParams: { @@ -142,7 +136,10 @@ export const getL2ToL1LogProofData = { root: '0x920c63cb0066a08da45f0a9bf934517141bd72d8e5a51421a94b517bf49a0d39', }, }; -export const getProofData: { input: [Address, [HexString], number]; output: Proof } = { +export const getProofData: { + input: [Address, [HexString], number]; + output: StorageProof; +} = { input: [ '0x0000000000000000000000000000000000008003', ['0x8b65c0cf1012ea9f393197eb24619fd814379b298b238285649e14f936a5eb12'], From 9279e2ebbaf33f973a4a64841a8821d86974d7dc Mon Sep 17 00:00:00 2001 From: Muhammad-Altabba <24407834+Muhammad-Altabba@users.noreply.github.com> Date: Sat, 25 May 2024 20:09:35 +0200 Subject: [PATCH 02/30] add .history to .gitignore --- .gitignore | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 9b26ed0..0d57b0f 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ node_modules -lib \ No newline at end of file +lib +.history From 758df6f2cf324a7c8e17730672f2ce0a831d407b Mon Sep 17 00:00:00 2001 From: Muhammad-Altabba <24407834+Muhammad-Altabba@users.noreply.github.com> Date: Mon, 27 May 2024 15:27:26 +0200 Subject: [PATCH 03/30] add some util functions --- package.json | 6 +- src/plugin.ts | 2 +- src/rpc.methods.ts | 34 +- src/types.ts | 468 +++++++++++++++- src/utils.ts | 1269 ++++++++++++++++++++++++++++++++++++++++++++ yarn.lock | 79 +++ 6 files changed, 1837 insertions(+), 21 deletions(-) create mode 100644 src/utils.ts diff --git a/package.json b/package.json index c7abdff..2c31b7c 100644 --- a/package.json +++ b/package.json @@ -23,7 +23,11 @@ }, "dependencies": { "hardhat": "^2.19.4", - "web3-utils": "^4.1.1" + "web3-core": "^4.4.0", + "web3-eth-abi": "^4.2.2", + "web3-eth-accounts": "^4.1.2", + "web3-types": "^1.6.0", + "web3-utils": "^4.3.0" }, "devDependencies": { "@chainsafe/eslint-config": "^2.1.1", diff --git a/src/plugin.ts b/src/plugin.ts index d3cb9e7..fc922e8 100644 --- a/src/plugin.ts +++ b/src/plugin.ts @@ -140,7 +140,7 @@ export class ZkSyncPlugin extends Web3PluginBase { // Module Augmentation declare module 'web3' { - interface Web3Context { + interface Web3 { zkSync: ZkSyncPlugin; } } diff --git a/src/rpc.methods.ts b/src/rpc.methods.ts index 46f2c7d..53a1453 100644 --- a/src/rpc.methods.ts +++ b/src/rpc.methods.ts @@ -1,7 +1,16 @@ import type { Web3RequestManager } from 'web3-core'; import { format, toNumber } from 'web3-utils'; -import type { Address, Bytes, HexString32Bytes, Numbers, TransactionWithSenderAPI } from 'web3'; -import { DEFAULT_RETURN_FORMAT } from 'web3'; +import type { + Address, + Bytes, + HexString32Bytes, + Numbers, + TransactionWithSenderAPI, +} from 'web3-types'; +import { + DEFAULT_RETURN_FORMAT, + // Web3BaseProvider +} from 'web3'; import type { DataFormat } from 'web3-types/src/data_format_types'; import type { BatchDetails, @@ -9,7 +18,7 @@ import type { BridgeAddresses, EstimateFee, L2ToL1Proof, - Proof, + StorageProof, RawBlockTransaction, TransactionDetails, WalletBalances, @@ -32,6 +41,7 @@ import { // The ZkSync methods described here https://docs.zksync.io/build/api.html +// TODO: Think about inheritance from Web3Eth export class RpcMethods { requestManager: Web3RequestManager; @@ -60,9 +70,7 @@ export class RpcMethods { * * @param returnFormat - The format of the return value. */ - public async getL1BatchNumber( - returnFormat: DataFormat = DEFAULT_RETURN_FORMAT, - ): Promise { + public async getL1BatchNumber(returnFormat: DataFormat = DEFAULT_RETURN_FORMAT): Promise { return format(IntSchema, await this._send('zks_L1BatchNumber', []), returnFormat) as bigint; } @@ -227,9 +235,7 @@ export class RpcMethods { * * @param returnFormat - The format of the return value. */ - public async getMainContract( - returnFormat: DataFormat = DEFAULT_RETURN_FORMAT, - ): Promise
{ + public async getMainContract(returnFormat: DataFormat = DEFAULT_RETURN_FORMAT): Promise
{ return format( AddressSchema, await this._send('zks_getMainContract', []), @@ -271,13 +277,13 @@ export class RpcMethods { keys: string[], l1BatchNumber: Numbers, returnFormat: DataFormat = DEFAULT_RETURN_FORMAT, - ): Promise { + ): Promise { const res = (await this._send('zks_getProof', [ address, keys, typeof l1BatchNumber === 'number' ? l1BatchNumber : Number(toNumber(l1BatchNumber)), - ])) as Proof; - const result = format(ProofSchema, res, returnFormat) as Proof; + ])) as StorageProof; + const result = format(ProofSchema, res, returnFormat) as StorageProof; result.storageProof = []; for (let i = 0; i < res.storageProof.length; i++) { result.storageProof[i] = format( @@ -325,9 +331,7 @@ export class RpcMethods { const params: [HexString32Bytes, number?] = [txHash]; if (l2ToL1LogIndex) { params.push( - typeof l2ToL1LogIndex === 'number' - ? l2ToL1LogIndex - : Number(toNumber(l2ToL1LogIndex)), + typeof l2ToL1LogIndex === 'number' ? l2ToL1LogIndex : Number(toNumber(l2ToL1LogIndex)), ); } return format( diff --git a/src/types.ts b/src/types.ts index 0eee7e4..af647f1 100644 --- a/src/types.ts +++ b/src/types.ts @@ -1,11 +1,31 @@ -export type { Bytes, HexString, Numbers } from 'web3'; -import { Bytes, HexString, Numbers } from 'web3-types'; +export type { Bytes, HexString, Numbers } from 'web3-types'; -import { EIP1193Provider } from 'web3'; +// import { FMT_BYTES, FMT_NUMBER, TransactionReceipt, Web3Eth } from 'web3'; -import { FeeMarketEIP1559TxData } from 'web3-eth-accounts'; +// // TODO: // is it needed to be re-exported from web3 +// import { watchTransactionForConfirmations } from 'web3-eth/lib/types/utils/watch_transaction_for_confirmations.js'; + +import { Bytes, HexString, Numbers, Transaction, EIP1193Provider } from 'web3-types'; + +import { + // FeeMarketEIP1559Transaction, + FeeMarketEIP1559TxData, + // TxOptions +} from 'web3-eth-accounts'; + +// import { +// EIP712_TX_TYPE, +// parseEip712, +// serializeEip712, +// sleep, +// eip712TxHash, +// isAddressEq, +// } from './utils'; import { RpcMethods } from './rpc.methods'; +export interface TransactionOverrides extends Omit {} + +export const ZeroAddress: Address = '0x0000000000000000000000000000000000000000'; /** 0x-prefixed, hex encoded, ethereum account address. */ export type Address = string; @@ -124,6 +144,378 @@ export interface zkSyncTxData extends FeeMarketEIP1559TxData { readonly l1BatchTxIndex: null | number; } +// /** +// * A `TransactionResponse` is an extension of {@link TransactionResponse} with additional features for +// * interacting with zkSync Era. +// */ +// export class TransactionResponse extends FeeMarketEIP1559Transaction { +// private web3Eth: Web3Eth; + +// /** The batch number on the L1 network. */ +// readonly l1BatchNumber: null | number; +// /** The transaction index within the batch on the L1 network. */ +// readonly l1BatchTxIndex: null | number; + +// constructor(txData: zkSyncTxData, provider: EIP1193Provider, opts?: TxOptions) { +// super(txData, opts); +// this.web3Eth = new Web3Eth(provider); +// this.l1BatchNumber = txData.l1BatchNumber; +// this.l1BatchTxIndex = txData.l1BatchTxIndex; + +// // copied from old base ethers.TransactionResponse! +// // this.blockNumber = tx.blockNumber != null ? tx.blockNumber : null; +// // this.blockHash = tx.blockHash != null ? tx.blockHash : null; + +// // this.hash = tx.hash; +// // this.index = tx.index; + +// // this.type = tx.type; + +// // this.from = tx.from; +// // this.to = tx.to || null; + +// // this.gasLimit = tx.gasLimit; +// // this.nonce = tx.nonce; +// // this.data = tx.data; +// // this.value = tx.value; + +// // this.gasPrice = tx.gasPrice; +// // this.maxPriorityFeePerGas = tx.maxPriorityFeePerGas != null ? tx.maxPriorityFeePerGas : null; +// // this.maxFeePerGas = tx.maxFeePerGas != null ? tx.maxFeePerGas : null; +// // this.maxFeePerBlobGas = tx.maxFeePerBlobGas != null ? tx.maxFeePerBlobGas : null; + +// // this.chainId = tx.chainId; +// // this.signature = tx.signature; + +// // this.accessList = tx.accessList != null ? tx.accessList : null; +// // this.blobVersionedHashes = tx.blobVersionedHashes != null ? tx.blobVersionedHashes : null; + +// // this.#startBlock = -1; +// } + +// /** +// * Waits for this transaction to be mined and have a specified number of confirmation blocks. +// * Resolves once the transaction has `confirmations` blocks including it. +// * If `confirmations` is 0 and the transaction has not been mined, it resolves to `null`. +// * Otherwise, it waits until enough confirmations have completed. +// * +// * @param confirmations The number of confirmation blocks. Defaults to 1. +// * @returns A promise that resolves to the transaction receipt. +// */ +// async wait(confirmations?: number): Promise { +// // eslint-disable-next-line no-constant-condition +// while (true) { +// // // it needs to be replaced with something like: + +// // // const receipt = await this.web3Eth.getTransactionReceipt(this.hash); +// // // eth.setConfig({ transactionConfirmationBlocks: waitConfirmations }); +// // // watchTransactionForConfirmations(this.web3Eth, , receipt, this.hash, 'hex'); + +// // // Or far better: to be replaced with a wait for confirmations on the PromiEvent of the sent transaction + +// // const receipt = (await super.wait(confirmations)) as TransactionReceipt; + +// // if (receipt && receipt.blockNumber) { +// // return receipt; +// // } +// await sleep(500); +// } +// } + +// async getTransaction() { +// return await this.web3Eth.getTransaction(this.data, { +// number: FMT_NUMBER.BIGINT, +// bytes: FMT_BYTES.HEX, +// }); +// } + +// // replaceableTransaction(startBlock: number): TransactionResponse { +// // return new TransactionResponse(super.replaceableTransaction(startBlock), this.provider); +// // } + +// // async getBlock(): Promise { +// // return await base.getBlock(this.hash); +// // } + +// // /** Waits for transaction to be finalized. */ +// // async waitFinalize(): Promise { +// // // eslint-disable-next-line no-constant-condition +// // while (true) { +// // const receipt = await this.wait(); +// // if (receipt && receipt.blockNumber) { +// // const block = await this.provider.getBlock('finalized'); +// // if (receipt.blockNumber <= block!.number) { +// // return (await this.provider.getTransactionReceipt(receipt.hash)) as TransactionReceipt; +// // } +// // } else { +// // await sleep(500); +// // } +// // } +// // } + +// override toJSON(): any { +// const { l1BatchNumber, l1BatchTxIndex } = this; + +// return { +// ...super.toJSON(), +// l1BatchNumber, +// l1BatchTxIndex, +// }; +// } +// } + +// /** +// * A `TransactionReceipt` is an extension of {@link ethers.TransactionReceipt} with additional features for +// * interacting with zkSync Era. +// */ +// export class TransactionReceipt extends FeeMarketEIP1559Transaction { +// private web3Eth: Web3Eth; + +// /** The batch number on the L1 network. */ +// readonly l1BatchNumber: null | number; +// /** The transaction index within the batch on the L1 network. */ +// readonly l1BatchTxIndex: null | number; +// /** The logs of L2 to L1 messages. */ +// readonly l2ToL1Logs: L2ToL1Log[]; +// /** All logs included in the transaction receipt. */ +// readonly _logs: ReadonlyArray; + +// constructor(params: any, provider: EIP1193Provider) { +// super(params); +// this.web3Eth = new Web3Eth(provider); +// this.l1BatchNumber = params.l1BatchNumber; +// this.l1BatchTxIndex = params.l1BatchTxIndex; +// this.l2ToL1Logs = params.l2ToL1Logs; +// this._logs = Object.freeze( +// params.logs.map((log: Log) => { +// return new Log(log, provider); +// }), +// ); +// } + +// // override get logs(): ReadonlyArray { +// // return this._logs; +// // } + +// // override getBlock(): Promise { +// // return super.getBlock(this.hash()) as Promise; +// // } + +// // override getTransaction(): Promise { +// // return super.getTransaction() as Promise; +// // } + +// override toJSON(): any { +// const { l1BatchNumber, l1BatchTxIndex, l2ToL1Logs } = this; +// return { +// ...super.toJSON(), +// l1BatchNumber, +// l1BatchTxIndex, +// l2ToL1Logs, +// }; +// } +// } + +// /** A `Block` is an extension of {@link ethers.Block} with additional features for interacting with zkSync Era. */ +// export class Block extends ethers.Block { +// /** The batch number on L1. */ +// readonly l1BatchNumber: null | number; +// /** The timestamp of the batch on L1. */ +// readonly l1BatchTimestamp: null | number; + +// constructor(params: any, provider: ethers.Provider) { +// super(params, provider); +// this.l1BatchNumber = params.l1BatchNumber; +// this.l1BatchTimestamp = params.l1BatchTxIndex; +// } + +// override toJSON(): any { +// const { l1BatchNumber, l1BatchTimestamp: l1BatchTxIndex } = this; +// return { +// ...super.toJSON(), +// l1BatchNumber, +// l1BatchTxIndex, +// }; +// } + +// override get prefetchedTransactions(): TransactionResponse[] { +// return super.prefetchedTransactions as TransactionResponse[]; +// } + +// override getTransaction(indexOrHash: number | string): Promise { +// return super.getTransaction(indexOrHash) as Promise; +// } +// } + +// /** A `LogParams` is an extension of {@link ethers.LogParams} with additional features for interacting with zkSync Era. */ +// export interface LogParams extends ethers.LogParams { +// /** The batch number on L1. */ +// readonly l1BatchNumber: null | number; +// } + +// /** A `Log` is an extension of {@link ethers.Log} with additional features for interacting with zkSync Era. */ +// export class Log extends ethers.Log { +// /** The batch number on L1. */ +// readonly l1BatchNumber: null | number; + +// constructor(params: LogParams, provider: ethers.Provider) { +// super(params, provider); +// this.l1BatchNumber = params.l1BatchNumber; +// } + +// override toJSON(): any { +// const { l1BatchNumber } = this; +// return { +// ...super.toJSON(), +// l1BatchNumber, +// }; +// } + +// override async getBlock(): Promise { +// return (await super.getBlock()) as Block; +// } + +// override async getTransaction(): Promise { +// return (await super.getTransaction()) as TransactionResponse; +// } + +// override async getTransactionReceipt(): Promise { +// return (await super.getTransactionReceipt()) as TransactionReceipt; +// } +// } + +// /** +// * A `TransactionLike` is an extension of {@link ethers.TransactionLike} with additional features for interacting +// * with zkSync Era. +// */ +// export interface TransactionLike extends ethers.TransactionLike { +// /** The custom data for EIP712 transaction metadata. */ +// customData?: null | Eip712Meta; +// } + +// /** +// * A `Transaction` is an extension of {@link ethers.Transaction} with additional features for interacting +// * with zkSync Era. +// */ +// export class Transaction extends ethers.Transaction { +// /** The custom data for EIP712 transaction metadata. */ +// customData?: null | Eip712Meta; +// // super.#type is private and there is no way to override which enforced to +// // introduce following variable +// #type?: null | number; +// #from?: null | string; + +// override get type(): number | null { +// return this.#type === EIP712_TX_TYPE ? this.#type : super.type; +// } + +// override set type(value: number | string | null) { +// switch (value) { +// case EIP712_TX_TYPE: +// case 'eip-712': +// this.#type = EIP712_TX_TYPE; +// break; +// default: +// super.type = value; +// } +// } + +// static override from(tx: string | TransactionLike): Transaction { +// if (typeof tx === 'string') { +// const payload = ethers.getBytes(tx); +// if (payload[0] !== EIP712_TX_TYPE) { +// return Transaction.from(ethers.Transaction.from(tx)); +// } else { +// return Transaction.from(parseEip712(payload)); +// } +// } else { +// const result = new Transaction(); +// if (tx.type === EIP712_TX_TYPE) { +// result.type = EIP712_TX_TYPE; +// result.customData = tx.customData; +// result.from = tx.from!; +// } +// if (tx.type !== null && tx.type !== undefined) result.type = tx.type; +// if (tx.to) result.to = tx.to; +// if (tx.nonce) result.nonce = tx.nonce; +// if (tx.gasLimit) result.gasLimit = tx.gasLimit; +// if (tx.gasPrice) result.gasPrice = tx.gasPrice; +// if (tx.maxPriorityFeePerGas) result.maxPriorityFeePerGas = tx.maxPriorityFeePerGas; +// if (tx.maxFeePerGas) result.maxFeePerGas = tx.maxFeePerGas; +// if (tx.data) result.data = tx.data; +// if (tx.value) result.value = tx.value; +// if (tx.chainId) result.chainId = tx.chainId; +// if (tx.signature) result.signature = EthersSignature.from(tx.signature); +// result.accessList = null; + +// if (tx.from) { +// assertArgument(result.isSigned(), 'unsigned transaction cannot define from', 'tx', tx); +// assertArgument(isAddressEq(result.from, tx.from), 'from mismatch', 'tx', tx); +// } + +// if (tx.hash) { +// assertArgument(result.isSigned(), 'unsigned transaction cannot define hash', 'tx', tx); +// assertArgument(result.hash === tx.hash, 'hash mismatch', 'tx', tx); +// } + +// return result; +// } +// } + +// override get serialized(): string { +// if (!this.customData && this.#type !== EIP712_TX_TYPE) { +// return super.serialized; +// } +// return serializeEip712(this, this.signature!); +// } + +// override get unsignedSerialized(): string { +// if (!this.customData && this.type !== EIP712_TX_TYPE) { +// return super.unsignedSerialized; +// } +// return serializeEip712(this); +// } + +// override toJSON(): any { +// const { customData } = this; +// return { +// ...super.toJSON(), +// type: !this.#type ? this.type : this.#type, +// customData, +// }; +// } + +// override get typeName(): string | null { +// return this.#type === EIP712_TX_TYPE ? 'zksync' : super.typeName; +// } + +// override isSigned(): this is Transaction & { +// type: number; +// typeName: string; +// from: string; +// signature: Signature; +// } { +// return this.#type === EIP712_TX_TYPE +// ? this.customData?.customSignature !== null +// : super.isSigned(); +// } + +// override get hash(): string | null { +// if (this.#type === EIP712_TX_TYPE) { +// return this.customData?.customSignature !== null ? eip712TxHash(this) : null; +// } else { +// return super.hash; +// } +// } + +// override get from(): string | null { +// return this.#type === EIP712_TX_TYPE ? this.#from! : super.from; +// } +// override set from(value: string | null) { +// this.#from = value; +// } +// } + /** * Represents a L2 to L1 transaction log. */ @@ -141,6 +533,28 @@ export interface L2ToL1Log { logIndex: number; } +// /** +// * A `TransactionRequest` is an extension of {@link ethers.TransactionRequest} with additional features for interacting +// * with zkSync Era. +// */ +// export interface TransactionRequest extends EthersTransactionRequest { +// /** The custom data for EIP712 transaction metadata. */ +// customData?: null | Eip712Meta; +// } + +// /** +// * Interface representation of priority op response that extends {@link ethers.TransactionResponse} and adds a function +// * that waits to commit a L1 transaction, including when given on optional confirmation number. +// */ +// export interface PriorityOpResponse extends TransactionResponse { +// /** +// * Waits for the L1 transaction to be committed, including waiting for the specified number of confirmations. +// * @param confirmation The number of confirmations to wait for. Defaults to 1. +// * @returns A promise that resolves to the transaction receipt once committed. +// */ +// waitL1Commit(confirmation?: number): Promise; +// } + /** A map containing accounts and their balances. */ export type BalancesMap = { [key: string]: bigint }; @@ -229,6 +643,24 @@ export interface ContractAccountInfo { nonceOrdering: AccountNonceOrdering; } +/** Contains batch information. */ +export interface BatchDetails { + number: number; + timestamp: number; + l1TxCount: number; + l2TxCount: number; + rootHash?: string; + status: string; + commitTxHash?: string; + committedAt?: Date; + proveTxHash?: string; + provenAt?: Date; + executeTxHash?: string; + executedAt?: Date; + l1GasPrice: number; + l2FairGasPrice: number; +} + /** Contains batch information. */ export interface BlockDetails { number: bigint; @@ -343,6 +775,34 @@ export type PayloadSigner = ( provider?: null | EIP1193Provider, ) => Promise; +// /** +// * Populates missing fields in a transaction with default values. +// * +// * @param transaction The transaction that needs to be populated. +// * @param [secret] The secret used for populating the transaction. +// * @param [provider] The provider is used to fetch data from the network if it is required for signing. +// * @returns A promise that resolves to the populated transaction. +// */ +// export type TransactionBuilder = ( +// transaction: TransactionRequest, +// secret?: any, +// provider?: null | EIP1193Provider, +// ) => Promise; + +// /** +// * Encapsulates the required input parameters for creating a signer for `SmartAccount`. +// */ +// export interface SmartAccountSigner { +// /** Address to which the `SmartAccount` is bound. */ +// address: string; +// /** Secret in any form that can be used for signing different payloads. */ +// secret: any; +// /** Custom method for signing different payloads. */ +// payloadSigner?: PayloadSigner; +// /** Custom method for populating transaction requests. */ +// transactionBuilder?: TransactionBuilder; +// } + export interface WalletBalances { [key: Address]: Numbers; } diff --git a/src/utils.ts b/src/utils.ts new file mode 100644 index 0000000..c77ddfa --- /dev/null +++ b/src/utils.ts @@ -0,0 +1,1269 @@ +// import { AbiCoder, BigNumberish, Bytes, ethers, SignatureLike } from 'ethers'; + +import { sha256 } from 'ethereum-cryptography/sha256.js'; +// import { RLP } from '@ethereumjs/rlp'; +// import { secp256k1 } from '@noble/curves/secp256k1'; +// import { keccak256 } from '@ethersproject/keccak256'; + +import * as web3 from 'web3'; + +import * as web3Utils from 'web3-utils'; +import * as web3Accounts from 'web3-eth-accounts'; +import * as web3Types from 'web3-types'; +import * as web3Abi from 'web3-eth-abi'; + +import { + DeploymentInfo, + // Eip712Meta, + EthereumSignature, + // PaymasterParams, + PriorityOpTree, + PriorityQueueType, + // Transaction, + // TransactionLike, + // TransactionRequest, +} from './types'; +// import { EIP712Signer } from './signer'; +// import { IERC20__factory } from './typechain'; +import { IZkSyncABI } from './contracts/IZkSyncStateTransition'; +import { IBridgehubABI } from './contracts/IBridgehub'; +import { IContractDeployerABI } from './contracts/IContractDeployer'; +import { IL1MessengerABI } from './contracts/IL1Messenger'; +import { IERC20ABI } from './contracts/IERC20'; +import { IERC1271ABI } from './contracts/IERC1271'; +import { IL1BridgeABI } from './contracts/IL1ERC20Bridge'; +import { IL2BridgeABI } from './contracts/IL2Bridge'; +import { INonceHolderABI } from './contracts/INonceHolder'; + +// import { RpcMethods } from './rpc.methods'; // to be used instead of the one at zksync-ethers: Provider from ./provider + +// export * from './paymaster-utils'; +// export * from './smart-account-utils'; +// export { EIP712_TYPES } from './signer'; + +/** + * The web3.js Contract instance for the `ZkSync` interface. + * @constant + */ +export const ZkSyncMainContract = new web3.Contract(IZkSyncABI); + +/** + * The ABI of the `Bridgehub` interface. + * @constant + */ +export const BridgehubContract = new web3.Contract(IBridgehubABI); + +/** + * The web3.js Contract instance for the `IContractDeployer` interface, which is utilized for deploying smart contracts. + * @constant + */ +export const ContractDeployerContract = new web3.Contract(IContractDeployerABI); + +/** + * The web3.js Contract instance for the `IL1Messenger` interface, which is utilized for sending messages from the L2 to L1. + * @constant + */ +export const L1MessengerContract = new web3.Contract(IL1MessengerABI); + +/** + * The web3.js Contract instance for the `IERC20` interface, which is utilized for interacting with ERC20 tokens. + * @constant + */ +export const IERC20Contract = new web3.Contract(IERC20ABI); + +/** + * The web3.js Contract instance for the `IERC1271` interface, which is utilized for signature validation by contracts. + * @constant + */ +export const IERC1271Contract = new web3.Contract(IERC1271ABI); + +/** + * The web3.js Contract instance for the `IL1Bridge` interface, which is utilized for transferring ERC20 tokens from L1 to L2. + * @constant + */ +export const L1BridgeContract = new web3.Contract(IL1BridgeABI); + +/** + * The web3.js Contract instance for the `IL2Bridge` interface, which is utilized for transferring ERC20 tokens from L2 to L1. + * @constant + */ +export const L2Bridge = new web3.Contract(IL2BridgeABI); + +/** + * The web3.js Contract instance for the `INonceHolder` interface, which is utilized for managing deployment nonces. + * @constant + */ +export const NonceHolderContract = new web3.Contract(INonceHolderABI); + +/** + * The address of the L1 `ETH` token. + * @constant + */ +export const ETH_ADDRESS: web3.Address = '0x0000000000000000000000000000000000000000'; + +/** + * The address of the L1 `ETH` token. + * @constant + */ +export const LEGACY_ETH_ADDRESS: web3.Address = '0x0000000000000000000000000000000000000000'; + +/** + * In the contracts the zero address can not be used, use one instead + * @constant + */ +export const ETH_ADDRESS_IN_CONTRACTS: web3.Address = '0x0000000000000000000000000000000000000001'; + +/** + * The formal address for the `Bootloader`. + * @constant + */ +export const BOOTLOADER_FORMAL_ADDRESS: web3.Address = '0x0000000000000000000000000000000000008001'; + +/** + * The address of the Contract deployer. + * @constant + */ +export const CONTRACT_DEPLOYER_ADDRESS: web3.Address = '0x0000000000000000000000000000000000008006'; + +/** + * The address of the L1 messenger. + * @constant + */ +export const L1_MESSENGER_ADDRESS: web3.Address = '0x0000000000000000000000000000000000008008'; + +/** + * The address of the L2 `ETH` token. + * @constant + * @deprecated In favor of {@link L2_BASE_TOKEN_ADDRESS}. + */ +export const L2_ETH_TOKEN_ADDRESS: web3.Address = '0x000000000000000000000000000000000000800a'; + +/** + * The address of the base token. + * @constant + */ +export const L2_BASE_TOKEN_ADDRESS = '0x000000000000000000000000000000000000800a'; + +/** + * The address of the Nonce holder. + * @constant + */ +export const NONCE_HOLDER_ADDRESS: web3.Address = '0x0000000000000000000000000000000000008003'; + +/** + * Used for applying and undoing aliases on addresses during bridging from L1 to L2. + * @constant + */ +export const L1_TO_L2_ALIAS_OFFSET: web3.Address = '0x1111000000000000000000000000000000001111'; + +/** + * The EIP1271 magic value used for signature validation in smart contracts. + * This predefined constant serves as a standardized indicator to signal successful + * signature validation by the contract. + * + * @constant + */ +export const EIP1271_MAGIC_VALUE = '0x1626ba7e'; + +/** + * Represents an EIP712 transaction type. + * + * @constant + */ +export const EIP712_TX_TYPE = 0x71; + +/** + * Represents a priority transaction operation on L2. + * + * @constant + */ +export const PRIORITY_OPERATION_L2_TX_TYPE = 0xff; + +/** + * The maximum bytecode length in bytes that can be deployed. + * + * @constant + */ +export const MAX_BYTECODE_LEN_BYTES: number = ((1 << 16) - 1) * 32; + +/** + * Numerator used in scaling the gas limit to ensure acceptance of `L1->L2` transactions. + * + * This constant is part of a coefficient calculation to adjust the gas limit to account for variations + * in the SDK estimation, ensuring the transaction will be accepted. + * + * @constant + */ +export const L1_FEE_ESTIMATION_COEF_NUMERATOR = 12; + +/** + * Denominator used in scaling the gas limit to ensure acceptance of `L1->L2` transactions. + * + * This constant is part of a coefficient calculation to adjust the gas limit to account for variations + * in the SDK estimation, ensuring the transaction will be accepted. + * + * @constant + */ +export const L1_FEE_ESTIMATION_COEF_DENOMINATOR = 10; + +/** + * Gas limit used for displaying the error messages when the + * users do not have enough fee when depositing ERC20 token from L1 to L2. + * + * @constant + */ +export const L1_RECOMMENDED_MIN_ERC20_DEPOSIT_GAS_LIMIT = 400_000; + +/** + * Gas limit used for displaying the error messages when the + * users do not have enough fee when depositing `ETH` token from L1 to L2. + * + * @constant + */ +export const L1_RECOMMENDED_MIN_ETH_DEPOSIT_GAS_LIMIT = 200_000; + +/** + * Default gas per pubdata byte for L2 transactions. + * This value is utilized when inserting a default value for type 2 + * and EIP712 type transactions. + * + * @constant + */ +// It is a realistic value, but it is large enough to fill into any batch regardless of the pubdata price. +export const DEFAULT_GAS_PER_PUBDATA_LIMIT = 50_000; + +/** + * The `L1->L2` transactions are required to have the following gas per pubdata byte. + * + * @constant + */ +export const REQUIRED_L1_TO_L2_GAS_PER_PUBDATA_LIMIT = 800; + +/** + * consider adding the next few functions to web3.js: + * */ + +export const NumberToByes = (number: web3Types.Numbers) => + web3Utils.hexToBytes(web3Utils.numberToHex(number)); + +export function concat(strings: web3Types.Bytes[]): string { + return '0x' + strings.map(d => web3Utils.toHex(d).substring(2)).join(''); +} + +export function contractFunctionId(value: string): string { + return web3Utils.keccak256(web3Utils.utf8ToBytes(value)); +} + +function recoverSignerAddress( + messageOrData: string | web3Types.Eip712TypedData, + signature: string | EthereumSignature, +) { + let message; + if (typeof messageOrData !== 'string') { + message = web3Abi.getEncodedEip712Data(messageOrData, true); + } else { + message = messageOrData; + } + + const r = web3Accounts.toUint8Array( + (signature as EthereumSignature).r ?? (signature as string).slice(0, 66), + ); + const s = web3Accounts.toUint8Array( + (signature as EthereumSignature).s ?? `0x${(signature as string).slice(66, 130)}`, + ); + const v = BigInt( + (signature as EthereumSignature).v ?? + web3Utils.hexToNumber(`0x${(signature as string).slice(130, 132)}`), + ); + + const recoveredPublicKey = web3Utils.bytesToHex( + web3Accounts.ecrecover(web3Accounts.toUint8Array(message), v, r, s), + ); + + const recoveredAddress = `0x${web3Utils.keccak256(web3Utils.bytesToHex(recoveredPublicKey)).slice(-40)}`; + return recoveredAddress; +} + +/** + * Returns true if token represents ETH on L1 or L2. + * + * @param token The token address. + * + * @example + * + * const isL1ETH = utils.isETH(utils.ETH_ADDRESS); // true + * const isL2ETH = utils.isETH(utils.ETH_ADDRESS_IN_CONTRACTS); // true + */ +export function isETH(token: web3.Address) { + return ( + isAddressEq(token, LEGACY_ETH_ADDRESS) || + isAddressEq(token, L2_BASE_TOKEN_ADDRESS) || + isAddressEq(token, ETH_ADDRESS_IN_CONTRACTS) + ); +} + +/** + * Pauses execution for a specified number of milliseconds. + * + * @param millis The number of milliseconds to pause execution. + * + * @example + * + * await sleep(1_000); + */ +export function sleep(millis: number): Promise { + return new Promise(resolve => setTimeout(resolve, millis)); +} + +/** + * Returns the default settings for L1 transactions. + */ +export function layer1TxDefaults(): { + queueType: PriorityQueueType.Deque; + opTree: PriorityOpTree.Full; +} { + return { + queueType: PriorityQueueType.Deque, + opTree: PriorityOpTree.Full, + }; +} + +/** + * Returns a `keccak` encoded message with a given sender address and block number from the L1 messenger contract. + * + * @param sender The sender of the message on L2. + * @param msg The encoded message. + * @param txNumberInBlock The index of the transaction in the block. + * @returns The hashed `L2->L1` message. + * + * @example + * + * const withdrawETHMessage = "0x6c0960f936615cf349d7f6344891b1e7ca7c72883f5dc04900000000000000000000000000000000000000000000000000000001a13b8600"; + * const withdrawETHMessageHash = utils.getHashedL2ToL1Msg("0x36615Cf349d7F6344891B1e7CA7C72883F5dc049", withdrawETHMessage, 0); + * // withdrawETHMessageHash = "0xd8c80ecb64619e343f57c3b133c6c6d8dd0572dd3488f1ca3276c5b7fd3a938d" + */ +export function getHashedL2ToL1Msg( + sender: web3.Address, + msg: web3Types.Bytes, + txNumberInBlock: number, +): string { + const encodedMsg = new Uint8Array([ + 0, // l2ShardId + 1, // isService + ...web3Utils.hexToBytes(web3Utils.padLeft(web3Utils.toHex(txNumberInBlock), 2)), + ...web3Utils.hexToBytes(L1_MESSENGER_ADDRESS), + ...web3Utils.hexToBytes(web3Utils.padLeft(sender, 32)), + ...web3Utils.hexToBytes(web3Utils.keccak256(msg)), + ]); + + return web3Utils.keccak256(encodedMsg); +} + +/** + * Returns a log containing details of all deployed contracts related to a transaction receipt. + * + * @param receipt The transaction receipt containing deployment information. + * + * @example + * + * + */ +export function getDeployedContracts(receipt: web3Types.TransactionReceipt): DeploymentInfo[] { + const addressBytesLen = 40; + return ( + receipt.logs + .filter( + log => + log.topics && + log.topics[0] === contractFunctionId('ContractDeployed(address,bytes32,address)') && + log.address && + isAddressEq(log.address, CONTRACT_DEPLOYER_ADDRESS), + ) + // Take the last topic (deployed contract address as U256) and extract address from it (U160). + .map(log => { + if (!log.topics) throw new Error('No topics in log'); + const sender = `0x${log.topics[1].slice(log.topics[1].length - addressBytesLen)}`; + const bytecodeHash = log.topics[2]; + const address = `0x${log.topics[3].slice(log.topics[3].length - addressBytesLen)}`; + return { + sender: web3Utils.toChecksumAddress(sender), + bytecodeHash: web3Utils.toHex(bytecodeHash), + deployedAddress: web3Utils.toChecksumAddress(address), + }; + }) + ); +} + +/** + * Generates a future-proof contract address using a salt plus bytecode, allowing the determination of an address before deployment. + * + * @param sender The sender's address. + * @param bytecodeHash The hash of the bytecode, typically the output from `zkSolc`. + * @param salt A randomization element used to create the contract address. + * @param input The ABI-encoded constructor arguments, if any. + * + * @remarks The implementation of `create2Address` in zkSync Era may differ slightly from Ethereum. + * + * @example + * + * const address = utils.create2Address("0x36615Cf349d7F6344891B1e7CA7C72883F5dc049", "0x010001cb6a6e8d5f6829522f19fa9568660e0a9cd53b2e8be4deb0a679452e41", "0x01", "0x01"); + * // address = "0x29bac3E5E8FFE7415F97C956BFA106D70316ad50" + */ +export function create2Address( + sender: web3Types.Address, + bytecodeHash: web3Types.Bytes, + salt: web3Types.Bytes, + input: web3Types.Bytes = '', +): string { + const prefix = web3Utils.keccak256(web3Utils.utf8ToBytes('zksyncCreate2')); + const inputHash = web3Utils.keccak256(input); + const addressBytes = web3Utils + .keccak256(concat([prefix, web3Utils.padLeft(sender, 32), salt, bytecodeHash, inputHash])) + .slice(26); + return web3Utils.toChecksumAddress(addressBytes); +} + +/** + * Generates a contract address from the deployer's account and nonce. + * + * @param sender The address of the deployer's account. + * @param senderNonce The nonce of the deployer's account. + * + * @example + * + * const address = utils.createAddress("0x36615Cf349d7F6344891B1e7CA7C72883F5dc049", 1); + * // address = "0x4B5DF730c2e6b28E17013A1485E5d9BC41Efe021" + */ +export function createAddress(sender: web3.Address, senderNonce: web3Types.Numbers): string { + const prefix = web3Utils.keccak256(web3Utils.utf8ToBytes('zksyncCreate')); + const addressBytes = web3Utils + .keccak256( + concat([ + prefix, + web3Utils.padLeft(sender, 32), + web3Utils.padLeft(web3Utils.toHex(senderNonce), 32), + ]), + ) + .slice(26); + + return web3Utils.toChecksumAddress(addressBytes); +} + +/** + * Checks if the transaction's base cost is greater than the provided value, which covers the transaction's cost. + * + * @param baseCost The base cost of the transaction. + * @param value The value covering the transaction's cost. + * @throws {Error} The base cost must be greater than the provided value. + * + * @example + * + * const baseCost = 100; + * const value = 99; + * try { + * await utils.checkBaseCost(baseCost, value); + * } catch (e) { + * // e.message = `The base cost of performing the priority operation is higher than the provided value parameter for the transaction: baseCost: ${baseCost}, provided value: ${value}`, + * } + */ +export async function checkBaseCost( + baseCost: web3Types.Numbers, + value: web3Types.Numbers | Promise, +): Promise { + if (baseCost > (await value)) { + throw new Error( + 'The base cost of performing the priority operation is higher than the provided value parameter ' + + `for the transaction: baseCost: ${baseCost}, provided value: ${value}!`, + ); + } +} + +// /** +// * Serializes an EIP712 transaction and includes a signature if provided. +// * +// * @param transaction The transaction that needs to be serialized. +// * @param signature Ethers signature to be included in the transaction. +// * @throws {Error} Throws an error if: +// * - `transaction.customData.customSignature` is an empty string. The transaction should be signed, and the `transaction.customData.customSignature` field should be populated with the signature. It should not be specified if the transaction is not signed. +// * - `transaction.chainId` is not provided. +// * - `transaction.from` is not provided. +// * +// * @example Serialize EIP712 transaction without signature. +// * +// * const serializedTx = utils.serializeEip712({ chainId: 270, from: "0x36615Cf349d7F6344891B1e7CA7C72883F5dc049" }, null); +// * +// * // serializedTx = "0x71ea8080808080808082010e808082010e9436615cf349d7f6344891b1e7ca7c72883f5dc04982c350c080c0" +// * +// * @example Serialize EIP712 transaction with signature. +// * +// * const signature = ethers.Signature.from("0x73a20167b8d23b610b058c05368174495adf7da3a4ed4a57eb6dbdeb1fafc24aaf87530d663a0d061f69bb564d2c6fb46ae5ae776bbd4bd2a2a4478b9cd1b42a"); +// * +// * const serializedTx = utils.serializeEip712( +// * { +// * chainId: 270, +// * from: "0x36615Cf349d7F6344891B1e7CA7C72883F5dc049", +// * to: "0xa61464658AfeAf65CccaaFD3a512b69A83B77618", +// * value: 1_000_000, +// * }, +// * signature +// * ); +// * // serializedTx = "0x71f87f8080808094a61464658afeaf65cccaafd3a512b69a83b77618830f42408001a073a20167b8d23b610b058c05368174495adf7da3a4ed4a57eb6dbdeb1fafc24aa02f87530d663a0d061f69bb564d2c6fb46ae5ae776bbd4bd2a2a4478b9cd1b42a82010e9436615cf349d7f6344891b1e7ca7c72883f5dc04982c350c080c0" +// */ +// export function serializeEip712( +// transaction: TransactionLike, +// signature?: ethers.SignatureLike, +// ): string { +// if (!transaction.chainId) { +// throw Error("Transaction chainId isn't set!"); +// } + +// if (!transaction.from) { +// throw new Error('Explicitly providing `from` field is required for EIP712 transactions!'); +// } +// const from = transaction.from; +// const meta: Eip712Meta = transaction.customData ?? {}; +// const maxFeePerGas = transaction.maxFeePerGas || transaction.gasPrice || 0; +// const maxPriorityFeePerGas = transaction.maxPriorityFeePerGas || maxFeePerGas; + +// const fields: any[] = [ +// NumberToByes(transaction.nonce || 0), +// NumberToByes(maxPriorityFeePerGas), +// NumberToByes(maxFeePerGas), +// NumberToByes(transaction.gasLimit || 0), +// transaction.to ? web3Utils.toChecksumAddress(transaction.to) : '0x', +// NumberToByes(transaction.value || 0), +// transaction.data || '0x', +// ]; + +// if (signature) { +// const sig = ethers.Signature.from(signature); +// fields.push(NumberToByes(sig.yParity)); +// fields.push(NumberToByes(sig.r)); +// fields.push(NumberToByes(sig.s)); +// } else { +// fields.push(NumberToByes(transaction.chainId)); +// fields.push('0x'); +// fields.push('0x'); +// } +// fields.push(NumberToByes(transaction.chainId)); +// fields.push(web3Utils.toChecksumAddress(from)); + +// // Add meta +// fields.push(NumberToByes(meta.gasPerPubdata || DEFAULT_GAS_PER_PUBDATA_LIMIT)); +// fields.push((meta.factoryDeps ?? []).map(dep => web3Utils.toHex(dep))); + +// if (meta.customSignature && web3Utils.bytesToUint8Array(meta.customSignature).length === 0) { +// throw new Error('Empty signatures are not supported!'); +// } +// fields.push(meta.customSignature || '0x'); + +// if (meta.paymasterParams) { +// fields.push([ +// meta.paymasterParams.paymaster, +// web3Utils.toHex(meta.paymasterParams.paymasterInput), +// ]); +// } else { +// fields.push([]); +// } + +// return concat([new Uint8Array([EIP712_TX_TYPE]), RLP.encode(fields)]); +// } + +/** + * Returns the hash of the given bytecode. + * + * @param bytecode The bytecode to hash. + * + * @example + * + * const bytecode = + * "0x000200000000000200010000000103550000006001100270000000130010019d0000008001000039000000400010043f0000000101200190000000290000c13d0000000001000031000000040110008c000000420000413d0000000101000367000000000101043b000000e001100270000000150210009c000000310000613d000000160110009c000000420000c13d0000000001000416000000000110004c000000420000c13d000000040100008a00000000011000310000001702000041000000200310008c000000000300001900000000030240190000001701100197000000000410004c000000000200a019000000170110009c00000000010300190000000001026019000000000110004c000000420000c13d00000004010000390000000101100367000000000101043b000000000010041b0000000001000019000000490001042e0000000001000416000000000110004c000000420000c13d0000002001000039000001000010044300000120000004430000001401000041000000490001042e0000000001000416000000000110004c000000420000c13d000000040100008a00000000011000310000001702000041000000000310004c000000000300001900000000030240190000001701100197000000000410004c000000000200a019000000170110009c00000000010300190000000001026019000000000110004c000000440000613d00000000010000190000004a00010430000000000100041a000000800010043f0000001801000041000000490001042e0000004800000432000000490001042e0000004a00010430000000000000000000000000000000000000000000000000000000000000000000000000ffffffff0000000200000000000000000000000000000040000001000000000000000000000000000000000000000000000000000000000000000000000000006d4ce63c0000000000000000000000000000000000000000000000000000000060fe47b18000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000080000000000000000000000000000000000000000000000000000000000000000000000000000000009c8c8fa789967eb514f3ec9def748480945cc9b10fcbd1a19597d924eb201083"; + * const hashedBytecode = utils.hashBytecode(bytecode); + * /* + * hashedBytecode = new Uint8Array([ + * 1, 0, 0, 27, 57, 231, 154, 55, 0, 164, 201, 96, 244, 120, 23, 112, 54, 34, 224, 133, + * 160, 122, 88, 164, 112, 80, 0, 134, 48, 138, 74, 16, + * ]), + * ); + * *\/ + */ +export function hashBytecode(bytecode: web3Types.Bytes): Uint8Array { + // For getting the consistent length we first convert the bytecode to UInt8Array + const bytecodeAsArray = web3Utils.bytesToUint8Array(bytecode); + + if (bytecodeAsArray.length % 32 !== 0) { + throw new Error('The bytecode length in bytes must be divisible by 32!'); + } + + if (bytecodeAsArray.length > MAX_BYTECODE_LEN_BYTES) { + throw new Error(`Bytecode can not be longer than ${MAX_BYTECODE_LEN_BYTES} bytes!`); + } + + const hashStr = sha256(Buffer.from(bytecodeAsArray)); + const hash = web3Utils.bytesToUint8Array(hashStr); + + // Note that the length of the bytecode + // should be provided in 32-byte words. + const bytecodeLengthInWords = bytecodeAsArray.length / 32; + if (bytecodeLengthInWords % 2 === 0) { + throw new Error('Bytecode length in 32-byte words must be odd!'); + } + + // The bytecode should always take the first 2 bytes of the bytecode hash, + // so we pad it from the left in case the length is smaller than 2 bytes. + const bytecodeLengthPadded = web3Utils.bytesToUint8Array( + web3Utils.padLeft(bytecodeLengthInWords, 2), + ); + + const codeHashVersion = new Uint8Array([1, 0]); + hash.set(codeHashVersion, 0); + hash.set(bytecodeLengthPadded, 2); + + return hash; +} + +// /** +// * Parses an EIP712 transaction from a payload. +// * +// * @param payload The payload to parse. +// * +// * @example +// * +// * import { types } from "zksync-ethers"; +// * +// * const serializedTx = +// * "0x71f87f8080808094a61464658afeaf65cccaafd3a512b69a83b77618830f42408001a073a20167b8d23b610b058c05368174495adf7da3a4ed4a57eb6dbdeb1fafc24aa02f87530d663a0d061f69bb564d2c6fb46ae5ae776bbd4bd2a2a4478b9cd1b42a82010e9436615cf349d7f6344891b1e7ca7c72883f5dc04982c350c080c0"; +// * const tx: types.TransactionLike = utils.parseEip712(serializedTx); +// * /* +// * tx: types.TransactionLike = { +// * type: 113, +// * nonce: 0, +// * maxPriorityFeePerGas: BigInt(0), +// * maxFeePerGas: BigInt(0), +// * gasLimit: BigInt(0), +// * to: "0xa61464658AfeAf65CccaaFD3a512b69A83B77618", +// * value: BigInt(1000000), +// * data: "0x", +// * chainId: BigInt(270), +// * from: "0x36615Cf349d7F6344891B1e7CA7C72883F5dc049", +// * customData: { +// * gasPerPubdata: BigInt(50000), +// * factoryDeps: [], +// * customSignature: "0x", +// * paymasterParams: null, +// * }, +// * hash: "0x9ed410ce33179ac1ff6b721060605afc72d64febfe0c08cacab5a246602131ee", +// * }; +// * *\/ +// */ +// // TODO: extend ethers.Transaction and add custom fields +// export function parseEip712(payload: web3Types.Bytes): TransactionLike { +// function handleAddress(value: string): string | null { +// if (value === '0x') { +// return null; +// } +// return web3Utils.toChecksumAddress(value); +// } + +// function handleNumber(value: string): bigint { +// if (!value || value === '0x') { +// return 0n; +// } +// return BigInt(value); +// } + +// function arrayToPaymasterParams(arr: string[]): PaymasterParams | undefined { +// if (arr.length === 0) { +// return undefined; +// } +// if (arr.length !== 2) { +// throw new Error( +// `Invalid paymaster parameters, expected to have length of 2, found ${arr.length}!`, +// ); +// } + +// return { +// paymaster: web3Utils.toChecksumAddress(arr[0]), +// paymasterInput: web3Utils.bytesToUint8Array(arr[1]), +// }; +// } + +// const bytes = web3Utils.bytesToUint8Array(payload); + +// // try using: RLP.decode +// const raw = ethers.decodeRlp(bytes.slice(1)) as string[]; +// const transaction: TransactionLike = { +// type: EIP712_TX_TYPE, +// nonce: Number(handleNumber(raw[0])), +// maxPriorityFeePerGas: handleNumber(raw[1]), +// maxFeePerGas: handleNumber(raw[2]), +// gasLimit: handleNumber(raw[3]), +// to: handleAddress(raw[4]), +// value: handleNumber(raw[5]), +// data: raw[6], +// chainId: handleNumber(raw[10]), +// from: handleAddress(raw[11]), +// customData: { +// gasPerPubdata: handleNumber(raw[12]), +// factoryDeps: raw[13] as unknown as string[], +// customSignature: raw[14], +// paymasterParams: arrayToPaymasterParams(raw[15] as unknown as string[]), +// }, +// }; + +// const ethSignature = { +// v: Number(handleNumber(raw[7])), +// r: raw[8], +// s: raw[9], +// }; + +// if ( +// (web3Utils.toHex(ethSignature.r) === '0x' || web3Utils.toHex(ethSignature.s) === '0x') && +// !transaction.customData?.customSignature +// ) { +// return transaction; +// } + +// if (ethSignature.v !== 0 && ethSignature.v !== 1 && !transaction.customData?.customSignature) { +// throw new Error('Failed to parse signature!'); +// } + +// if (!transaction.customData?.customSignature) { +// // TODO: either try to use a string or a signature object +// // const signatureStr = `${ethSignature.r}${ethSignature.s.slice(2)}${ethSignature.v.slice(2)}`; +// // or maybe try to use @noble/curves (but it does not deal with `v`): +// // import { SignatureType } from '@noble/curves'; +// // new secp256k1.Signature(ethSignature.r, ethSignature.s, ethSignature.v); +// transaction.signature = ethers.Signature.from(ethSignature); +// } + +// transaction.hash = eip712TxHash(transaction, ethSignature); + +// return transaction; +// } + +export function getSignature(transaction: any, ethSignature?: EthereumSignature): Uint8Array { + if (transaction?.customData?.customSignature && transaction.customData.customSignature.length) { + return web3Utils.bytesToUint8Array(transaction.customData.customSignature); + } + + if (!ethSignature) { + throw new Error('No signature provided!'); + } + + const r = web3Utils.bytesToUint8Array(web3Utils.padLeft(web3Utils.toHex(ethSignature.r), 32)); + const s = web3Utils.bytesToUint8Array(web3Utils.padLeft(web3Utils.toHex(ethSignature.s), 32)); + const v = ethSignature.v; + + return new Uint8Array([...r, ...s, v]); +} + +// /** +// * Returns the hash of an EIP712 transaction. +// * +// * @param transaction The EIP-712 transaction. +// * @param ethSignature The ECDSA signature of the transaction. +// * +// * @example +// * +// * +// */ +// export function eip712TxHash( +// transaction: Transaction | TransactionRequest, +// ethSignature?: EthereumSignature, +// ): string { +// const signedDigest = EIP712Signer.getSignedDigest(transaction); +// const hashedSignature = web3Utils.keccak256(getSignature(transaction, ethSignature)); + +// return web3Utils.keccak256(concat([signedDigest, hashedSignature])); +// } + +// /** +// * Returns the hash of the L2 priority operation from a given transaction receipt and L2 address. +// * +// * @param txReceipt The receipt of the L1 transaction. +// * @param zkSyncAddress The address of the zkSync Era main contract. +// * +// * @example +// */ +// export function getL2HashFromPriorityOp( +// txReceipt: web3Types.TransactionReceipt, +// zkSyncAddress: web3.Address, +// ): string { +// let txHash: string | null = null; +// for (const log of txReceipt.logs) { +// if (!isAddressEq(log.address as string, zkSyncAddress)) { +// continue; +// } + +// try { +// // TODO: implement at web3.js Contract the parsing of the logs similar to new ethers.Interface(ABI).parseLog(...) +// const priorityQueueLog = ZkSyncMainContract.parseLog({ +// topics: log.topics as string[], +// data: log.data, +// }); +// if (priorityQueueLog && priorityQueueLog.args.txHash !== null) { +// txHash = priorityQueueLog.args.txHash; +// } +// } catch { +// // skip +// } +// } +// if (!txHash) { +// throw new Error('Failed to parse tx logs!'); +// } + +// return txHash; +// } + +const ADDRESS_MODULO = 2n ** 160n; + +/** + * Converts the address that submitted a transaction to the inbox on L1 to the `msg.sender` viewed on L2. + * Returns the `msg.sender` of the `L1->L2` transaction as the address of the contract that initiated the transaction. + * + * All available cases: + * - During a normal transaction, if contract `A` calls contract `B`, the `msg.sender` is `A`. + * - During `L1->L2` communication, if an EOA `X` calls contract `B`, the `msg.sender` is `X`. + * - During `L1->L2` communication, if a contract `A` calls contract `B`, the `msg.sender` is `applyL1ToL2Alias(A)`. + * + * @param address The address of the contract. + * @returns The transformed address representing the `msg.sender` on L2. + * + * @see + * {@link undoL1ToL2Alias}. + * + * @example + * + * const l1ContractAddress = "0x702942B8205E5dEdCD3374E5f4419843adA76Eeb"; + * const l2ContractAddress = utils.applyL1ToL2Alias(l1ContractAddress); + * // l2ContractAddress = "0x813A42B8205E5DedCd3374e5f4419843ADa77FFC" + * + */ +export function applyL1ToL2Alias(address: string): string { + return web3Utils.padLeft( + web3Utils.toHex((BigInt(address) + BigInt(L1_TO_L2_ALIAS_OFFSET)) % ADDRESS_MODULO), + 20, + ); +} + +/** + * Converts and returns the `msg.sender` viewed on L2 to the address that submitted a transaction to the inbox on L1. + * + * @param address The sender address viewed on L2. + * + * @see + * {@link applyL1ToL2Alias}. + * + * @example + * + * const l2ContractAddress = "0x813A42B8205E5DedCd3374e5f4419843ADa77FFC"; + * const l1ContractAddress = utils.undoL1ToL2Alias(l2ContractAddress); + * // const l1ContractAddress = "0x702942B8205E5dEdCD3374E5f4419843adA76Eeb" + */ +export function undoL1ToL2Alias(address: string): string { + let result = BigInt(address) - BigInt(L1_TO_L2_ALIAS_OFFSET); + if (result < 0n) { + result += ADDRESS_MODULO; + } + return web3Utils.padLeft(web3Utils.toHex(result), 20); +} + +// /** +// * Returns the data needed for correct initialization of an L1 token counterpart on L2. +// * +// * @param l1TokenAddress The token address on L1. +// * @param provider The client that is able to work with contracts on a read-write basis. +// * @returns The encoded bytes which contains token name, symbol and decimals. +// */ +// export async function getERC20DefaultBridgeData( +// l1TokenAddress: string, +// context: web3.Web3Context, // or maybe use RpcMethods? +// ): Promise { +// if (isAddressEq(l1TokenAddress, LEGACY_ETH_ADDRESS)) { +// l1TokenAddress = ETH_ADDRESS_IN_CONTRACTS; +// } +// const token = IERC20__factory.connect(l1TokenAddress, context); + +// const name = isAddressEq(l1TokenAddress, ETH_ADDRESS_IN_CONTRACTS) ? 'Ether' : await token.name(); +// const symbol = isAddressEq(l1TokenAddress, ETH_ADDRESS_IN_CONTRACTS) +// ? 'ETH' +// : await token.symbol(); +// const decimals = isAddressEq(l1TokenAddress, ETH_ADDRESS_IN_CONTRACTS) +// ? 18 +// : await token.decimals(); + +// const coder = new AbiCoder(); + +// const nameBytes = coder.encode(['string'], [name]); +// const symbolBytes = coder.encode(['string'], [symbol]); +// const decimalsBytes = coder.encode(['uint256'], [decimals]); + +// return coder.encode(['bytes', 'bytes', 'bytes'], [nameBytes, symbolBytes, decimalsBytes]); +// } + +// /** +// * Returns the calldata sent by an L1 ERC20 bridge to its L2 counterpart during token bridging. +// * +// * @param l1TokenAddress The token address on L1. +// * @param l1Sender The sender address on L1. +// * @param l2Receiver The recipient address on L2. +// * @param amount The gas fee for the number of tokens to bridge. +// * @param bridgeData Additional bridge data. +// * +// * @example +// * +// * +// */ +// export async function getERC20BridgeCalldata( +// l1TokenAddress: string, +// l1Sender: string, +// l2Receiver: string, +// amount: web3Types.Numbers, +// bridgeData: web3Types.Bytes, +// ): Promise { +// return L2_BRIDGE_ABI.encodeFunctionData('finalizeDeposit', [ +// l1Sender, +// l2Receiver, +// l1TokenAddress, +// amount, +// bridgeData, +// ]); +// } + +/** + * Validates signatures from non-contract account addresses (EOA). + * Provides similar functionality to `ethers.js` but returns `true` + * if the validation process succeeds, otherwise returns `false`. + * + * Called from {@link isSignatureCorrect} for non-contract account addresses. + * + * @param address The address which signs the `msgHash`. + * @param msgHash The hash of the message. + * @param signature The Ethers signature. + * + * @example + * + * import { Wallet, utils } from "zksync-ethers"; + * + * const ADDRESS = ""; + * const PRIVATE_KEY = ""; + * + * const message = "Hello, world!"; + * const signature = await new Wallet(PRIVATE_KEY).signMessage(message); + * // ethers.Wallet can be used as well + * // const signature = await new ethers.Wallet(PRIVATE_KEY).signMessage(message); + * + * const isValidSignature = await utils.isECDSASignatureCorrect(ADDRESS, message, signature); + * // isValidSignature = true + */ +function isECDSASignatureCorrect( + address: string, + msgHash: string, + signature: EthereumSignature, +): boolean { + try { + return isAddressEq(address, recoverSignerAddress(msgHash, signature)); + } catch { + // In case ECDSA signature verification has thrown an error, + // we simply consider the signature as incorrect. + return false; + } +} + +/** + * Called from {@link isSignatureCorrect} for contract account addresses. + * The function returns `true` if the validation process results + * in the {@link EIP1271_MAGIC_VALUE}. + * + * @param context The web3 context. + * @param address The sender address. + * @param msgHash The hash of the message. + * @param signature The Ethers signature. + * + * @see + * {@link isMessageSignatureCorrect} and {@link isTypedDataSignatureCorrect} to validate signatures. + * + * @example + * + */ +async function isEIP1271SignatureCorrect( + context: web3.Web3Context, // or maybe use RpcMethods? + address: string, + msgHash: string, + signature: EthereumSignature, +): Promise { + const accountContract = new web3.Contract(IERC1271ABI, address, context); + + // This line may throw an exception if the contract does not implement the EIP1271 correctly. + // But it may also throw an exception in case the internet connection is lost. + // It is the caller's responsibility to handle the exception. + const result = await accountContract.methods.isValidSignature(msgHash, signature).call(); + + return result === EIP1271_MAGIC_VALUE; +} + +/** + * Called from {@link isMessageSignatureCorrect} and {@link isTypedDataSignatureCorrect}. + * Returns whether the account abstraction signature is correct. + * Signature can be created using EIP1271 or ECDSA. + * + * @param context The web3 context. + * @param address The sender address. + * @param msgHash The hash of the message. + * @param signature The Ethers signature. + */ +async function isSignatureCorrect( + context: web3.Web3Context, // or maybe use RpcMethods? + address: string, + msgHash: string, + signature: EthereumSignature, +): Promise { + const code = await web3.eth.getCode(context, address, undefined, web3Types.DEFAULT_RETURN_FORMAT); + const isContractAccount = web3Utils.bytesToUint8Array(code).length !== 0; + + if (!isContractAccount) { + return isECDSASignatureCorrect(address, msgHash, signature); + } else { + return await isEIP1271SignatureCorrect(context, address, msgHash, signature); + } +} + +// /** +// * Returns whether the account abstraction message signature is correct. +// * Signature can be created using EIP1271 or ECDSA. +// * +// * @param provider The provider. +// * @param address The sender address. +// * @param message The hash of the message. +// * @param signature The Ethers signature. +// * +// * @example +// * +// * import { Wallet, utils, Provider } from "zksync-ethers"; +// * +// * const ADDRESS = ""; +// * const PRIVATE_KEY = ""; +// * const context = Provider.getDefaultProvider(types.Network.Sepolia); +// * +// * const message = "Hello, world!"; +// * const signature = await new Wallet(PRIVATE_KEY).signMessage(message); +// * // ethers.Wallet can be used as well +// * // const signature = await new ethers.Wallet(PRIVATE_KEY).signMessage(message); +// * +// * const isValidSignature = await utils.isMessageSignatureCorrect(context, ADDRESS, message, signature); +// * // isValidSignature = true +// */ +// export async function isMessageSignatureCorrect( +// context: web3.Web3Context, // or maybe use RpcMethods? +// address: string, +// message: Uint8Array | string, +// signature: SignatureLike, +// ): Promise { +// // TODO: needs to implement this (similar to web3Abi.getEncodedEip712Data but for stings and Uint8Array) +// const msgHash = ethers.getMessage(message); +// return await isSignatureCorrect(context, address, msgHash, signature); +// } + +// /** +// * Returns whether the account abstraction EIP712 signature is correct. +// * +// * @param context The web3 context. +// * @param address The sender address. +// * @param domain The domain data. +// * @param types A map of records pointing from field name to field type. +// * @param value A single record value. +// * @param signature The Ethers signature. +// * +// * @example +// * +// * import { Wallet, utils, Provider, EIP712Signer } from "zksync-ethers"; +// * +// * const ADDRESS = ""; +// * const PRIVATE_KEY = ""; +// * const context = Provider.getDefaultProvider(types.Network.Sepolia); +// * +// * const tx: types.TransactionRequest = { +// * type: 113, +// * chainId: 270, +// * from: ADDRESS, +// * to: "0xa61464658AfeAf65CccaaFD3a512b69A83B77618", +// * value: BigInt(7_000_000), +// * }; +// * +// * const eip712Signer = new EIP712Signer( +// * new Wallet(PRIVATE_KEY), // or new ethers.Wallet(PRIVATE_KEY), +// * Number((await context.getNetwork()).chainId) +// * ); +// * +// * const signature = await eip712Signer.sign(tx); +// * +// * const isValidSignature = await utils.isTypedDataSignatureCorrect(context, ADDRESS, await eip712Signer.getDomain(), utils.EIP712_TYPES, EIP712Signer.getSignInput(tx), signature); +// * // isValidSignature = true +// */ +// export async function isTypedDataSignatureCorrect( +// context: web3.Web3Context, // or maybe use RpcMethods? +// address: string, +// domain: ethers.TypedDataDomain, +// types: Record>, +// value: Record, +// signature: EthereumSignature, +// ): Promise { +// const msgHash = ethers.TypedDataEncoder.hash(domain, types, value); +// return await isSignatureCorrect(context, address, msgHash, signature); +// } + +// /** +// * 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 +// * +// * +// */ +// export async function estimateDefaultBridgeDepositL2Gas( +// providerL1: Web3Eth, +// providerL2: Provider, +// token: web3.Address, +// amount: web3Types.Numbers, +// to: web3.Address, +// from?: web3.Address, +// gasPerPubdataByte?: web3Types.Numbers, +// ): 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 ??= web3Accounts.create().address; +// if (await providerL2.isBaseToken(token)) { +// return await providerL2.estimateL1ToL2Execute({ +// contractAddress: to, +// gasPerPubdataByte: gasPerPubdataByte, +// caller: from, +// calldata: '0x', +// l2Value: amount, +// }); +// } else { +// const bridgeAddresses = await providerL2.getDefaultBridgeAddresses(); + +// const value = 0; +// const l1BridgeAddress = bridgeAddresses.sharedL1; +// const l2BridgeAddress = bridgeAddresses.sharedL2; +// const bridgeData = await getERC20DefaultBridgeData(token, providerL1); + +// return await estimateCustomBridgeDepositL2Gas( +// providerL2, +// l1BridgeAddress, +// l2BridgeAddress, +// isAddressEq(token, LEGACY_ETH_ADDRESS) ? ETH_ADDRESS_IN_CONTRACTS : token, +// amount, +// to, +// bridgeData, +// from, +// gasPerPubdataByte, +// value, +// ); +// } +// } + +/** + * Scales the provided gas limit using a coefficient to ensure acceptance of L1->L2 transactions. + * + * This function adjusts the gas limit by multiplying it with a coefficient calculated from the + * `L1_FEE_ESTIMATION_COEF_NUMERATOR` and `L1_FEE_ESTIMATION_COEF_DENOMINATOR` constants. + * + * @param gasLimit - The gas limit to be scaled. + * + * @example + * + * const scaledGasLimit = utils.scaleGasLimit(10_000); + * // scaledGasLimit = 12_000 + */ +export function scaleGasLimit(gasLimit: bigint): bigint { + return ( + (gasLimit * BigInt(L1_FEE_ESTIMATION_COEF_NUMERATOR)) / + BigInt(L1_FEE_ESTIMATION_COEF_DENOMINATOR) + ); +} + +// /** +// * 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 +// * +// * +// */ +// export async function estimateCustomBridgeDepositL2Gas( +// providerL2: RpcMethods, +// l1BridgeAddress: web3.Address, +// l2BridgeAddress: web3.Address, +// token: web3.Address, +// amount: web3Types.Numbers, +// to: web3.Address, +// bridgeData: web3Types.Bytes, +// from: web3.Address, +// gasPerPubdataByte?: web3Types.Numbers, +// l2Value?: web3Types.Numbers, +// ): Promise { +// const calldata = await getERC20BridgeCalldata(token, from, to, amount, bridgeData); +// return await providerL2.estimateL1ToL2Execute({ +// caller: applyL1ToL2Alias(l1BridgeAddress), +// contractAddress: l2BridgeAddress, +// gasPerPubdataByte: gasPerPubdataByte, +// calldata: calldata, +// l2Value: l2Value, +// }); +// } + +/** + * Creates a JSON string from an object, including support for serializing bigint types. + * + * @param object The object to serialize to JSON. + */ +export function toJSON(object: any): string { + return JSON.stringify( + object, + (_, value) => { + if (typeof value === 'bigint') { + return value.toString(); // Convert BigInt to string + } + return value; + }, + 2, + ); +} + +/** + * Compares stringified addresses, taking into account the fact that + * addresses might be represented in different casing. + * + * @param a - The first address to compare. + * @param b - The second address to compare. + * @returns A boolean indicating whether the addresses are equal. + */ +export function isAddressEq(a: web3.Address, b: web3.Address): boolean { + return a.toLowerCase() === b.toLowerCase(); +} diff --git a/yarn.lock b/yarn.lock index 48c3688..05168b1 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2988,6 +2988,11 @@ ethjs-util@0.1.6, ethjs-util@^0.1.6: is-hex-prefixed "1.0.0" strip-hex-prefix "1.0.0" +eventemitter3@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-5.0.1.tgz#53f5ffd0a492ac800721bb42c66b841de96423c4" + integrity sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA== + evp_bytestokey@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/evp_bytestokey/-/evp_bytestokey-1.0.3.tgz#7fcbdb198dc71959432efe13842684e0525acb02" @@ -5715,6 +5720,22 @@ web3-core@^4.3.2: optionalDependencies: web3-providers-ipc "^4.0.7" +web3-core@^4.4.0: + version "4.4.0" + resolved "https://registry.yarnpkg.com/web3-core/-/web3-core-4.4.0.tgz#f2cd48aecda5ec34170edf7470001f90fdea1dc6" + integrity sha512-sN1AkhTAFI89anOeCaO0c3GtiGeWtOGVc2tmTdQs2Rd14HuxLyDuLIF3/WwjtkDFRM2189uYy8HJJSWJvW2mYA== + dependencies: + web3-errors "^1.2.0" + web3-eth-accounts "^4.1.2" + web3-eth-iban "^4.0.7" + web3-providers-http "^4.1.0" + web3-providers-ws "^4.0.7" + web3-types "^1.6.0" + web3-utils "^4.3.0" + web3-validator "^2.0.6" + optionalDependencies: + web3-providers-ipc "^4.0.7" + web3-errors@^1.1.3, web3-errors@^1.1.4: version "1.1.4" resolved "https://registry.yarnpkg.com/web3-errors/-/web3-errors-1.1.4.tgz#5667a0a5f66fc936e101ef32032ccc1e8ca4d5a1" @@ -5722,6 +5743,13 @@ web3-errors@^1.1.3, web3-errors@^1.1.4: dependencies: web3-types "^1.3.1" +web3-errors@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/web3-errors/-/web3-errors-1.2.0.tgz#441acfd7fd744c9beedf23f277f20759fae92433" + integrity sha512-58Kczou5zyjcm9LuSs5Hrm6VrG8t9p2J8X0yGArZrhKNPZL66gMGkOUpPx+EopE944Sk4yE+Q25hKv4H5BH+kA== + dependencies: + web3-types "^1.6.0" + web3-eth-abi@^4.1.4: version "4.1.4" resolved "https://registry.yarnpkg.com/web3-eth-abi/-/web3-eth-abi-4.1.4.tgz#56ae7ebb1385db1a948e69fb35f4057bff6743af" @@ -5744,6 +5772,17 @@ web3-eth-abi@^4.2.0: web3-utils "^4.1.1" web3-validator "^2.0.4" +web3-eth-abi@^4.2.2: + version "4.2.2" + resolved "https://registry.yarnpkg.com/web3-eth-abi/-/web3-eth-abi-4.2.2.tgz#d7592e2cc113fd34da3fb4c933537ddf8639d9b2" + integrity sha512-akbGi642UtKG3k3JuLbhl9KuG7LM/cXo/by2WfdwfOptGZrzRsWJNWje1d2xfw1n9kkVG9SAMvPJl1uSyR3dfw== + dependencies: + abitype "0.7.1" + web3-errors "^1.2.0" + web3-types "^1.6.0" + web3-utils "^4.3.0" + web3-validator "^2.0.6" + web3-eth-accounts@^4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/web3-eth-accounts/-/web3-eth-accounts-4.1.0.tgz#5b5e6c60d457e7b829ec590021fc87ada8585920" @@ -5770,6 +5809,19 @@ web3-eth-accounts@^4.1.1: web3-utils "^4.1.1" web3-validator "^2.0.4" +web3-eth-accounts@^4.1.2: + version "4.1.2" + resolved "https://registry.yarnpkg.com/web3-eth-accounts/-/web3-eth-accounts-4.1.2.tgz#652d6e3daf4d6cb3fe67cec6a878e768f6e8b8e8" + integrity sha512-y0JynDeTDnclyuE9mShXLeEj+BCrPHxPHOyPCgTchUBQsALF9+0OhP7WiS3IqUuu0Hle5bjG2f5ddeiPtNEuLg== + dependencies: + "@ethereumjs/rlp" "^4.0.1" + crc-32 "^1.2.2" + ethereum-cryptography "^2.0.0" + web3-errors "^1.1.4" + web3-types "^1.6.0" + web3-utils "^4.2.3" + web3-validator "^2.0.5" + web3-eth-contract@^4.1.2: version "4.1.3" resolved "https://registry.yarnpkg.com/web3-eth-contract/-/web3-eth-contract-4.1.3.tgz#15dd4c978eaf0d8f894b2c1f3e9f94edd29ff57c" @@ -5931,6 +5983,11 @@ web3-types@^1.3.0, web3-types@^1.3.1: resolved "https://registry.yarnpkg.com/web3-types/-/web3-types-1.3.1.tgz#cf6148ad46b68c5c89714613380b270d31e297be" integrity sha512-8fXi7h/t95VKRtgU4sxprLPZpsTh3jYDfSghshIDBgUD/OoGe5S+syP24SUzBZYllZ/L+hMr2gdp/0bGJa8pYQ== +web3-types@^1.6.0: + version "1.6.0" + resolved "https://registry.yarnpkg.com/web3-types/-/web3-types-1.6.0.tgz#ebe7f140c31f7cc0ad15f238ad7e7ac72797ff3b" + integrity sha512-qgOtADqlD5hw+KPKBUGaXAcdNLL0oh6qTeVgXwewCfbL/lG9R+/GrgMQB1gbTJ3cit8hMwtH8KX2Em6OwO0HRw== + web3-utils@^4.0.7: version "4.0.7" resolved "https://registry.yarnpkg.com/web3-utils/-/web3-utils-4.0.7.tgz#7df497b7cdd06cdfe7d02036c45fecbe3382d137" @@ -5951,6 +6008,17 @@ web3-utils@^4.1.0, web3-utils@^4.1.1: web3-types "^1.3.1" web3-validator "^2.0.4" +web3-utils@^4.2.3, web3-utils@^4.3.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/web3-utils/-/web3-utils-4.3.0.tgz#c18918f0d692f745d622d22172406f6102528860" + integrity sha512-fGG2IZr0XB1vEoWZiyJzoy28HpsIfZgz4mgPeQA9aj5rIx8z0o80qUPtIyrCYX/Bo2gYALlV5SWIJWxJNUQn9Q== + dependencies: + ethereum-cryptography "^2.0.0" + eventemitter3 "^5.0.1" + web3-errors "^1.2.0" + web3-types "^1.6.0" + web3-validator "^2.0.6" + web3-validator@^2.0.3: version "2.0.3" resolved "https://registry.yarnpkg.com/web3-validator/-/web3-validator-2.0.3.tgz#e5dcd4b4902612cff21b7f8817dd233393999d97" @@ -5973,6 +6041,17 @@ web3-validator@^2.0.4: web3-types "^1.3.1" zod "^3.21.4" +web3-validator@^2.0.5, web3-validator@^2.0.6: + version "2.0.6" + resolved "https://registry.yarnpkg.com/web3-validator/-/web3-validator-2.0.6.tgz#a0cdaa39e1d1708ece5fae155b034e29d6a19248" + integrity sha512-qn9id0/l1bWmvH4XfnG/JtGKKwut2Vokl6YXP5Kfg424npysmtRLe9DgiNBM9Op7QL/aSiaA0TVXibuIuWcizg== + dependencies: + ethereum-cryptography "^2.0.0" + util "^0.12.5" + web3-errors "^1.2.0" + web3-types "^1.6.0" + zod "^3.21.4" + web3@^4.4.0: version "4.4.0" resolved "https://registry.yarnpkg.com/web3/-/web3-4.4.0.tgz#83e5906675608adf9a14841f257e441c9154a8c7" From 5c999a7806c095d1c804f0655a0f898c480fb23d Mon Sep 17 00:00:00 2001 From: Muhammad-Altabba <24407834+Muhammad-Altabba@users.noreply.github.com> Date: Tue, 28 May 2024 05:53:03 +0200 Subject: [PATCH 04/30] update a test --- test/index.test.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/test/index.test.ts b/test/index.test.ts index c23ce91..4855aef 100644 --- a/test/index.test.ts +++ b/test/index.test.ts @@ -1,10 +1,10 @@ -import { core } from 'web3'; +import { Web3 } from 'web3'; import { ZkSyncPlugin } from '../src'; describe('ZkSyncPlugin tests', () => { it('should register ZkSync plugin on Web3Context instance', () => { - const web3Context = new core.Web3Context('http://127.0.0.1:8545'); - web3Context.registerPlugin(new ZkSyncPlugin()); - expect(web3Context.zkSync).toBeDefined(); + const web3 = new Web3('http://127.0.0.1:8545'); + web3.registerPlugin(new ZkSyncPlugin()); + expect(web3.zkSync).toBeDefined(); }); }); From 5c57acf02ba2bf0b0c086497721dbb0f9ebbd810 Mon Sep 17 00:00:00 2001 From: Muhammad-Altabba <24407834+Muhammad-Altabba@users.noreply.github.com> Date: Tue, 28 May 2024 06:21:26 +0200 Subject: [PATCH 05/30] fix spelling --- src/utils.ts | 30 ++++++++++++++++++------------ 1 file changed, 18 insertions(+), 12 deletions(-) diff --git a/src/utils.ts b/src/utils.ts index c77ddfa..8c4c716 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -240,10 +240,11 @@ export const DEFAULT_GAS_PER_PUBDATA_LIMIT = 50_000; export const REQUIRED_L1_TO_L2_GAS_PER_PUBDATA_LIMIT = 800; /** + * ------------------------------------------------------------ * consider adding the next few functions to web3.js: * */ -export const NumberToByes = (number: web3Types.Numbers) => +export const NumberToBytes = (number: web3Types.Numbers) => web3Utils.hexToBytes(web3Utils.numberToHex(number)); export function concat(strings: web3Types.Bytes[]): string { @@ -284,6 +285,11 @@ function recoverSignerAddress( return recoveredAddress; } +/** + * ------------------------------------------------------------ + * End of the function section that would be added to web3.js + */ + /** * Returns true if token represents ETH on L1 or L2. * @@ -526,30 +532,30 @@ export async function checkBaseCost( // const maxPriorityFeePerGas = transaction.maxPriorityFeePerGas || maxFeePerGas; // const fields: any[] = [ -// NumberToByes(transaction.nonce || 0), -// NumberToByes(maxPriorityFeePerGas), -// NumberToByes(maxFeePerGas), -// NumberToByes(transaction.gasLimit || 0), +// NumberToBytes(transaction.nonce || 0), +// NumberToBytes(maxPriorityFeePerGas), +// NumberToBytes(maxFeePerGas), +// NumberToBytes(transaction.gasLimit || 0), // transaction.to ? web3Utils.toChecksumAddress(transaction.to) : '0x', -// NumberToByes(transaction.value || 0), +// NumberToBytes(transaction.value || 0), // transaction.data || '0x', // ]; // if (signature) { // const sig = ethers.Signature.from(signature); -// fields.push(NumberToByes(sig.yParity)); -// fields.push(NumberToByes(sig.r)); -// fields.push(NumberToByes(sig.s)); +// fields.push(NumberToBytes(sig.yParity)); +// fields.push(NumberToBytes(sig.r)); +// fields.push(NumberToBytes(sig.s)); // } else { -// fields.push(NumberToByes(transaction.chainId)); +// fields.push(NumberToBytes(transaction.chainId)); // fields.push('0x'); // fields.push('0x'); // } -// fields.push(NumberToByes(transaction.chainId)); +// fields.push(NumberToBytes(transaction.chainId)); // fields.push(web3Utils.toChecksumAddress(from)); // // Add meta -// fields.push(NumberToByes(meta.gasPerPubdata || DEFAULT_GAS_PER_PUBDATA_LIMIT)); +// fields.push(NumberToBytes(meta.gasPerPubdata || DEFAULT_GAS_PER_PUBDATA_LIMIT)); // fields.push((meta.factoryDeps ?? []).map(dep => web3Utils.toHex(dep))); // if (meta.customSignature && web3Utils.bytesToUint8Array(meta.customSignature).length === 0) { From c55f043b4c39550422038a004f1e24f236632bb4 Mon Sep 17 00:00:00 2001 From: Muhammad-Altabba <24407834+Muhammad-Altabba@users.noreply.github.com> Date: Tue, 28 May 2024 06:38:05 +0200 Subject: [PATCH 06/30] tiny renaming --- src/utils.ts | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/src/utils.ts b/src/utils.ts index 8c4c716..9b1d320 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -244,11 +244,11 @@ export const REQUIRED_L1_TO_L2_GAS_PER_PUBDATA_LIMIT = 800; * consider adding the next few functions to web3.js: * */ -export const NumberToBytes = (number: web3Types.Numbers) => +export const numberToBytes = (number: web3Types.Numbers) => web3Utils.hexToBytes(web3Utils.numberToHex(number)); -export function concat(strings: web3Types.Bytes[]): string { - return '0x' + strings.map(d => web3Utils.toHex(d).substring(2)).join(''); +export function concat(bytes: web3Types.Bytes[]): string { + return '0x' + bytes.map(d => web3Utils.toHex(d).substring(2)).join(''); } export function contractFunctionId(value: string): string { @@ -532,30 +532,30 @@ export async function checkBaseCost( // const maxPriorityFeePerGas = transaction.maxPriorityFeePerGas || maxFeePerGas; // const fields: any[] = [ -// NumberToBytes(transaction.nonce || 0), -// NumberToBytes(maxPriorityFeePerGas), -// NumberToBytes(maxFeePerGas), -// NumberToBytes(transaction.gasLimit || 0), +// numberToBytes(transaction.nonce || 0), +// numberToBytes(maxPriorityFeePerGas), +// numberToBytes(maxFeePerGas), +// numberToBytes(transaction.gasLimit || 0), // transaction.to ? web3Utils.toChecksumAddress(transaction.to) : '0x', -// NumberToBytes(transaction.value || 0), +// numberToBytes(transaction.value || 0), // transaction.data || '0x', // ]; // if (signature) { // const sig = ethers.Signature.from(signature); -// fields.push(NumberToBytes(sig.yParity)); -// fields.push(NumberToBytes(sig.r)); -// fields.push(NumberToBytes(sig.s)); +// fields.push(numberToBytes(sig.yParity)); +// fields.push(numberToBytes(sig.r)); +// fields.push(numberToBytes(sig.s)); // } else { -// fields.push(NumberToBytes(transaction.chainId)); +// fields.push(numberToBytes(transaction.chainId)); // fields.push('0x'); // fields.push('0x'); // } -// fields.push(NumberToBytes(transaction.chainId)); +// fields.push(numberToBytes(transaction.chainId)); // fields.push(web3Utils.toChecksumAddress(from)); // // Add meta -// fields.push(NumberToBytes(meta.gasPerPubdata || DEFAULT_GAS_PER_PUBDATA_LIMIT)); +// fields.push(numberToBytes(meta.gasPerPubdata || DEFAULT_GAS_PER_PUBDATA_LIMIT)); // fields.push((meta.factoryDeps ?? []).map(dep => web3Utils.toHex(dep))); // if (meta.customSignature && web3Utils.bytesToUint8Array(meta.customSignature).length === 0) { From 3ba6c10dd1707f7c47335e90fdf97c9b438347f9 Mon Sep 17 00:00:00 2001 From: Muhammad-Altabba <24407834+Muhammad-Altabba@users.noreply.github.com> Date: Tue, 28 May 2024 13:25:42 +0200 Subject: [PATCH 07/30] add unit test and apply padding fixes + organize test files --- src/index.ts | 5 +- src/utils.ts | 24 +- test/{ => integration}/mainnet.test.ts | 2 +- test/{ => integration}/rpc.mainnet.test.ts | 4 +- test/{ => integration}/rpc.test.ts | 8 +- test/{ => unit}/index.test.ts | 4 +- test/unit/utils.test.ts | 279 +++++++++++++++++++++ test/utils.ts | 13 + 8 files changed, 316 insertions(+), 23 deletions(-) rename test/{ => integration}/mainnet.test.ts (95%) rename test/{ => integration}/rpc.mainnet.test.ts (97%) rename test/{ => integration}/rpc.test.ts (92%) rename test/{ => unit}/index.test.ts (70%) create mode 100644 test/unit/utils.test.ts create mode 100644 test/utils.ts diff --git a/src/index.ts b/src/index.ts index 4098ae4..705e799 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,3 +1,4 @@ export { ZkSyncPlugin } from './plugin'; -export * from './types'; -export * from './constants'; +export * as utils from './utils'; +export * as types from './types'; +export * as constants from './constants'; diff --git a/src/utils.ts b/src/utils.ts index 9b1d320..d3bdb9e 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -356,9 +356,9 @@ export function getHashedL2ToL1Msg( const encodedMsg = new Uint8Array([ 0, // l2ShardId 1, // isService - ...web3Utils.hexToBytes(web3Utils.padLeft(web3Utils.toHex(txNumberInBlock), 2)), + ...web3Utils.hexToBytes(web3Utils.padLeft(web3Utils.toHex(txNumberInBlock), 2 * 2)), ...web3Utils.hexToBytes(L1_MESSENGER_ADDRESS), - ...web3Utils.hexToBytes(web3Utils.padLeft(sender, 32)), + ...web3Utils.hexToBytes(web3Utils.padLeft(sender, 32 * 2)), ...web3Utils.hexToBytes(web3Utils.keccak256(msg)), ]); @@ -424,7 +424,7 @@ export function create2Address( const prefix = web3Utils.keccak256(web3Utils.utf8ToBytes('zksyncCreate2')); const inputHash = web3Utils.keccak256(input); const addressBytes = web3Utils - .keccak256(concat([prefix, web3Utils.padLeft(sender, 32), salt, bytecodeHash, inputHash])) + .keccak256(concat([prefix, web3Utils.padLeft(sender, 32 * 2), salt, bytecodeHash, inputHash])) .slice(26); return web3Utils.toChecksumAddress(addressBytes); } @@ -446,8 +446,8 @@ export function createAddress(sender: web3.Address, senderNonce: web3Types.Numbe .keccak256( concat([ prefix, - web3Utils.padLeft(sender, 32), - web3Utils.padLeft(web3Utils.toHex(senderNonce), 32), + web3Utils.padLeft(sender, 32 * 2), + web3Utils.padLeft(web3Utils.toHex(senderNonce), 32 * 2), ]), ) .slice(26); @@ -605,8 +605,10 @@ export function hashBytecode(bytecode: web3Types.Bytes): Uint8Array { throw new Error(`Bytecode can not be longer than ${MAX_BYTECODE_LEN_BYTES} bytes!`); } - const hashStr = sha256(Buffer.from(bytecodeAsArray)); + const hashStr = web3Utils.toHex(sha256(Buffer.from(bytecodeAsArray))); + console.log('hashStr:', hashStr); const hash = web3Utils.bytesToUint8Array(hashStr); + console.log('hash:', hash); // Note that the length of the bytecode // should be provided in 32-byte words. @@ -618,7 +620,7 @@ export function hashBytecode(bytecode: web3Types.Bytes): Uint8Array { // The bytecode should always take the first 2 bytes of the bytecode hash, // so we pad it from the left in case the length is smaller than 2 bytes. const bytecodeLengthPadded = web3Utils.bytesToUint8Array( - web3Utils.padLeft(bytecodeLengthInWords, 2), + web3Utils.padLeft(bytecodeLengthInWords, 2 * 2), ); const codeHashVersion = new Uint8Array([1, 0]); @@ -757,8 +759,8 @@ export function getSignature(transaction: any, ethSignature?: EthereumSignature) throw new Error('No signature provided!'); } - const r = web3Utils.bytesToUint8Array(web3Utils.padLeft(web3Utils.toHex(ethSignature.r), 32)); - const s = web3Utils.bytesToUint8Array(web3Utils.padLeft(web3Utils.toHex(ethSignature.s), 32)); + const r = web3Utils.bytesToUint8Array(web3Utils.padLeft(web3Utils.toHex(ethSignature.r), 32 * 2)); + const s = web3Utils.bytesToUint8Array(web3Utils.padLeft(web3Utils.toHex(ethSignature.s), 32 * 2)); const v = ethSignature.v; return new Uint8Array([...r, ...s, v]); @@ -849,7 +851,7 @@ const ADDRESS_MODULO = 2n ** 160n; export function applyL1ToL2Alias(address: string): string { return web3Utils.padLeft( web3Utils.toHex((BigInt(address) + BigInt(L1_TO_L2_ALIAS_OFFSET)) % ADDRESS_MODULO), - 20, + 20 * 2, ); } @@ -872,7 +874,7 @@ export function undoL1ToL2Alias(address: string): string { if (result < 0n) { result += ADDRESS_MODULO; } - return web3Utils.padLeft(web3Utils.toHex(result), 20); + return web3Utils.padLeft(web3Utils.toHex(result), 20 * 2); } // /** diff --git a/test/mainnet.test.ts b/test/integration/mainnet.test.ts similarity index 95% rename from test/mainnet.test.ts rename to test/integration/mainnet.test.ts index 96907f2..be37604 100644 --- a/test/mainnet.test.ts +++ b/test/integration/mainnet.test.ts @@ -1,5 +1,5 @@ import { Web3 } from 'web3'; -import { ZkSyncPlugin } from '../src'; +import { ZkSyncPlugin } from '../../src'; const EXAMPLE_ERC20_TOKEN = { address: '0x3355df6D4c9C3035724Fd0e3914dE96A5a83aaf4', diff --git a/test/rpc.mainnet.test.ts b/test/integration/rpc.mainnet.test.ts similarity index 97% rename from test/rpc.mainnet.test.ts rename to test/integration/rpc.mainnet.test.ts index 65ad652..559e422 100644 --- a/test/rpc.mainnet.test.ts +++ b/test/integration/rpc.mainnet.test.ts @@ -1,11 +1,11 @@ import { Web3 } from 'web3'; -import { ZkSyncPlugin } from '../src'; +import { ZkSyncPlugin } from '../../src'; import { estimateData, getL1BatchDetailsData, getL2ToL1LogProofData, getProofData, -} from './fixtures'; +} from '../fixtures'; describe('ZkSyncPlugin rpc mainnet tests', () => { let web3: Web3; diff --git a/test/rpc.test.ts b/test/integration/rpc.test.ts similarity index 92% rename from test/rpc.test.ts rename to test/integration/rpc.test.ts index bb08fe7..f07553e 100644 --- a/test/rpc.test.ts +++ b/test/integration/rpc.test.ts @@ -1,11 +1,11 @@ import { Web3 } from 'web3'; -import { ZkSyncPlugin } from '../src'; +import { ZkSyncPlugin } from '../../src'; import { getBlockDetailsData, getBridgeContractsData, getRawBlockTransactionsData, getTransactionDetailsData, -} from './fixtures'; +} from '../fixtures'; describe('ZkSyncPlugin rpc tests', () => { let web3: Web3; @@ -38,9 +38,7 @@ describe('ZkSyncPlugin rpc tests', () => { expect(res).toBeDefined(); }); it('getRawBlockTransactions', async () => { - const res = await web3.zkSync.rpc.getRawBlockTransactions( - getRawBlockTransactionsData.input, - ); + const res = await web3.zkSync.rpc.getRawBlockTransactions(getRawBlockTransactionsData.input); expect(res).toEqual(getRawBlockTransactionsData.output); }); it('getMainContract', async () => { diff --git a/test/index.test.ts b/test/unit/index.test.ts similarity index 70% rename from test/index.test.ts rename to test/unit/index.test.ts index 4855aef..a18b5a4 100644 --- a/test/index.test.ts +++ b/test/unit/index.test.ts @@ -1,9 +1,9 @@ import { Web3 } from 'web3'; -import { ZkSyncPlugin } from '../src'; +import { ZkSyncPlugin } from '../../src'; describe('ZkSyncPlugin tests', () => { it('should register ZkSync plugin on Web3Context instance', () => { - const web3 = new Web3('http://127.0.0.1:8545'); + const web3 = new Web3('http://some-rpc-url.com'); web3.registerPlugin(new ZkSyncPlugin()); expect(web3.zkSync).toBeDefined(); }); diff --git a/test/unit/utils.test.ts b/test/unit/utils.test.ts new file mode 100644 index 0000000..3e20363 --- /dev/null +++ b/test/unit/utils.test.ts @@ -0,0 +1,279 @@ +import { + // types, + utils, +} from '../../src'; +import { + ADDRESS1, + // ADDRESS2 +} from '../utils'; + +describe('utils', () => { + describe('#getHashedL2ToL1Msg()', () => { + it('should return a hashed L2 to L1 message', async () => { + const withdrawETHMessage = + '0x6c0960f936615cf349d7f6344891b1e7ca7c72883f5dc04900000000000000000000000000000000000000000000000000000001a13b8600'; + const withdrawETHMessageHash = + '0x521bd25904766c83fe868d6a29cbffa011afd8a1754f6c9a52b053b693e42f18'; + const result = utils.getHashedL2ToL1Msg(ADDRESS1, withdrawETHMessage, 0); + expect(result).toBe(withdrawETHMessageHash); + }); + }); + + describe('#isETH()', () => { + it('should return true for legacy L1 ETH address', async () => { + const result = utils.isETH(utils.LEGACY_ETH_ADDRESS); + expect(result).toBeTruthy(); + }); + + it('should return true for L1 ETH address', async () => { + const result = utils.isETH(utils.ETH_ADDRESS_IN_CONTRACTS); + expect(result).toBeTruthy(); + }); + + it('should return true for L2 ETH address', async () => { + const result = utils.isETH(utils.L2_BASE_TOKEN_ADDRESS); + expect(result).toBeTruthy(); + }); + }); + + describe('#createAddress()', () => { + it('should return a correct address', async () => { + const address = utils.createAddress(ADDRESS1, 1); + expect(address).toBe('0x4B5DF730c2e6b28E17013A1485E5d9BC41Efe021'); + }); + }); + + describe('#create2Address()', () => { + it('should return a correct address', async () => { + const address = utils.create2Address( + ADDRESS1, + '0x010001cb6a6e8d5f6829522f19fa9568660e0a9cd53b2e8be4deb0a679452e41', + '0x01', + '0x01', + ); + expect(address).toBe('0x29bac3E5E8FFE7415F97C956BFA106D70316ad50'); + }); + }); + + describe('#applyL1ToL2Alias()', () => { + it('should return the L2 contract address based on provided L1 contract address', async () => { + const l1ContractAddress = '0x702942B8205E5dEdCD3374E5f4419843adA76Eeb'; + const l2ContractAddress = utils.applyL1ToL2Alias(l1ContractAddress); + expect(l2ContractAddress.toLowerCase()).toBe( + '0x813A42B8205E5DedCd3374e5f4419843ADa77FFC'.toLowerCase(), + ); + }); + + it('should return the L2 contract address by padding zero to left', async () => { + const l1ContractAddress = '0xeeeeffffffffffffffffffffffffffffffffeeef'; + const l2ContractAddress = utils.applyL1ToL2Alias(l1ContractAddress); + expect(l2ContractAddress.toLowerCase()).toBe( + '0x0000000000000000000000000000000000000000'.toLowerCase(), + ); + }); + }); + + describe('#undoL1ToL2Alias()', () => { + it('should return the L1 contract address based on provided L2 contract address', async () => { + const l2ContractAddress = '0x813A42B8205E5DedCd3374e5f4419843ADa77FFC'; + const l1ContractAddress = utils.undoL1ToL2Alias(l2ContractAddress); + expect(l1ContractAddress.toLowerCase()).toBe( + '0x702942B8205E5dEdCD3374E5f4419843adA76Eeb'.toLowerCase(), + ); + }); + + it('should return the L1 contract address by padding zero to left', async () => { + const l2ContractAddress = '0x1111000000000000000000000000000000001111'; + const l1ContractAddress = utils.undoL1ToL2Alias(l2ContractAddress); + expect(l1ContractAddress.toLowerCase()).toBe( + '0x0000000000000000000000000000000000000000'.toLowerCase(), + ); + }); + + it('should handle a case where L1_TO_L2_ALIAS_OFFSET is greater than the address', () => { + const l2ContractAddress = '0x100'; + const l1ContractAddress = utils.undoL1ToL2Alias(l2ContractAddress); + + expect(l1ContractAddress.toLowerCase()).toBe( + '0xeeeeffffffffffffffffffffffffffffffffefef'.toLowerCase(), + ); + }); + }); + + describe('#checkBaseCost()', () => { + it('should throw an error if the base cost bigger than value', async () => { + const baseCost = 100; + const value = 99; + try { + await utils.checkBaseCost(baseCost, value); + } catch (e) { + expect((e as Error).message).toBe( + `The base cost of performing the priority operation is higher than the provided value parameter for the transaction: baseCost: ${baseCost}, provided value: ${value}!`, + ); + } + }); + }); + + // describe('#serializeEip712()', () => { + // it('should throw an error when `tx.chainId` is not specified', async () => { + // try { + // utils.serializeEip712({}); + // } catch (e) { + // expect((e as Error).message).toBe("Transaction chainId isn't set!"); + // } + // }); + + // it('should throw an error when `tx.from` is not specified', async () => { + // try { + // utils.serializeEip712({ chainId: 270 }); + // } catch (e) { + // expect((e as Error).message).toBe( + // 'Explicitly providing `from` field is required for EIP712 transactions!', + // ); + // } + // }); + + // it('should throw an error when `tx.customData.customSignature` is empty string', async () => { + // try { + // utils.serializeEip712({ + // chainId: 270, + // from: ADDRESS1, + // customData: { + // customSignature: '', + // }, + // }); + // } catch (e) { + // expect((e as Error).message).toBe('Empty signatures are not supported'); + // } + // }); + + // it('should return a serialized transaction with populated default values', async () => { + // const tx = + // '0x71ea8080808080808082010e808082010e9436615cf349d7f6344891b1e7ca7c72883f5dc04982c350c080c0'; + // const result = utils.serializeEip712({ + // chainId: 270, + // from: ADDRESS1, + // }); + // expect(result).toBe(tx); + // }); + + // it('should return a serialized transaction with provided signature', async () => { + // const tx = + // '0x71f87f8080808094a61464658afeaf65cccaafd3a512b69a83b77618830f42408001a073a20167b8d23b610b058c05368174495adf7da3a4ed4a57eb6dbdeb1fafc24aa02f87530d663a0d061f69bb564d2c6fb46ae5ae776bbd4bd2a2a4478b9cd1b42a82010e9436615cf349d7f6344891b1e7ca7c72883f5dc04982c350c080c0'; + // const signature = ethers.Signature.from( + // '0x73a20167b8d23b610b058c05368174495adf7da3a4ed4a57eb6dbdeb1fafc24aaf87530d663a0d061f69bb564d2c6fb46ae5ae776bbd4bd2a2a4478b9cd1b42a', + // ); + // const result = utils.serializeEip712( + // { + // chainId: 270, + // from: ADDRESS1, + // to: ADDRESS2, + // value: 1_000_000, + // }, + // signature, + // ); + // expect(result).toBe(tx); + // }); + // }); + + describe('#hashBytecode()', () => { + it('should return the hash of bytecode which length is not 2 bytes so padding needs to be performed', async () => { + const bytecode = + '0x000200000000000200010000000103550000006001100270000000130010019d0000008001000039000000400010043f0000000101200190000000290000c13d0000000001000031000000040110008c000000420000413d0000000101000367000000000101043b000000e001100270000000150210009c000000310000613d000000160110009c000000420000c13d0000000001000416000000000110004c000000420000c13d000000040100008a00000000011000310000001702000041000000200310008c000000000300001900000000030240190000001701100197000000000410004c000000000200a019000000170110009c00000000010300190000000001026019000000000110004c000000420000c13d00000004010000390000000101100367000000000101043b000000000010041b0000000001000019000000490001042e0000000001000416000000000110004c000000420000c13d0000002001000039000001000010044300000120000004430000001401000041000000490001042e0000000001000416000000000110004c000000420000c13d000000040100008a00000000011000310000001702000041000000000310004c000000000300001900000000030240190000001701100197000000000410004c000000000200a019000000170110009c00000000010300190000000001026019000000000110004c000000440000613d00000000010000190000004a00010430000000000100041a000000800010043f0000001801000041000000490001042e0000004800000432000000490001042e0000004a00010430000000000000000000000000000000000000000000000000000000000000000000000000ffffffff0000000200000000000000000000000000000040000001000000000000000000000000000000000000000000000000000000000000000000000000006d4ce63c0000000000000000000000000000000000000000000000000000000060fe47b18000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000080000000000000000000000000000000000000000000000000000000000000000000000000000000009c8c8fa789967eb514f3ec9def748480945cc9b10fcbd1a19597d924eb201083'; + const hashedBytecode = utils.hashBytecode(bytecode); + expect(hashedBytecode).toEqual( + new Uint8Array([ + 1, 0, 0, 27, 57, 231, 154, 55, 0, 164, 201, 96, 244, 120, 23, 112, 54, 34, 224, 133, 160, + 122, 88, 164, 112, 80, 0, 134, 48, 138, 74, 16, + ]), + ); + }); + + it('should return the hash of bytecode which length is 2 bytes so padding does not need to be performed', async () => { + const bytecode = + '0x0002000000000002000900000000000200010000000103550000006001100270000001980010019d0000008001000039000000400010043f0000000101200190000000340000c13d0000000001000031000000040110008c000003670000413d0000000101000367000000000101043b000000e0011002700000019d0210009c000001420000213d000001a50210009c000001720000213d000001a90210009c000001fd0000613d000001aa0210009c000002210000613d000001ab0110009c000003670000c13d0000000001000416000000000110004c000003670000c13d000000040100008a00000000011000310000019a02000041000000000310004c000000000300001900000000030240190000019a01100197000000000410004c000000000200a0190000019a0110009c00000000010300190000000001026019000000000110004c000003670000c13d0000000201000039000000000101041a000000400200043d00000000001204350000019801000041000001980320009c00000000010240190000004001100210000001ad011001c70000065c0001042e0000000001000416000000000110004c000003670000c13d00000000020000310000001f01200039000000200a00008a0000000004a1016f000000400100043d0000000003140019000000000443004b00000000040000190000000104004039000001990530009c000003c90000213d0000000104400190000003c90000c13d000000400030043f0000001f0320018f00000001040003670000000505200272000000520000613d000000000600001900000005076002100000000008710019000000000774034f000000000707043b00000000007804350000000106600039000000000756004b0000004a0000413d000000000630004c000000610000613d0000000505500210000000000454034f00000000055100190000000303300210000000000605043300000000063601cf000000000636022f000000000404043b0000010003300089000000000434022f00000000033401cf000000000363019f00000000003504350000019a03000041000000600420008c000000000400001900000000040340190000019a05200197000000000650004c000000000300a0190000019a0550009c000000000304c019000000000330004c000003670000c13d0000000034010434000001990540009c000003670000213d000000000221001900000000041400190000001f054000390000019a06000041000000000725004b000000000700001900000000070680190000019a055001970000019a08200197000000000985004b0000000006008019000000000585013f0000019a0550009c00000000050700190000000005066019000000000550004c000003670000c13d0000000005040433000001990650009c000003c90000213d0000003f065000390000000006a6016f000000400b00043d00000000066b00190000000007b6004b00000000070000190000000107004039000001990860009c000003c90000213d0000000107700190000003c90000c13d000000400060043f000000000c5b043600000020065000390000000007460019000000000727004b000003670000213d000000000750004c0000009e0000613d000000000700001900000020077000390000000008b70019000000000947001900000000090904330000000000980435000000000857004b000000970000413d00000000046b001900000000000404350000000003030433000001990430009c000003670000213d00000000031300190000001f043000390000019a05000041000000000624004b000000000600001900000000060580190000019a044001970000019a07200197000000000874004b0000000005008019000000000474013f0000019a0440009c00000000040600190000000004056019000000000440004c000003670000c13d0000000004030433000001990540009c000003c90000213d0000003f054000390000000005a5016f000000400800043d0000000005580019000000000685004b00000000060000190000000106004039000001990750009c000003c90000213d0000000106600190000003c90000c13d000000400050043f0000000005480436000800000005001d00000020054000390000000006350019000000000226004b000003670000213d00060000000c001d00090000000b001d00070000000a001d000000000240004c000000d50000613d000000000200001900000020022000390000000006820019000000000732001900000000070704330000000000760435000000000642004b000000ce0000413d0000000002580019000000000002043500000040011000390000000001010433000500000001001d000000ff0110008c0000000901000029000003670000213d0000000001010433000400000001001d000001990110009c000003c90000213d000100000008001d0000000301000039000300000001001d000000000101041a000000010210019000000001011002700000007f0310018f0000000001036019000200000001001d0000001f0110008c00000000010000190000000101002039000000010110018f000000000112004b0000021b0000c13d0000000201000029000000200110008c000001100000413d0000000301000029000000000010043500000198010000410000000002000414000001980320009c0000000001024019000000c0011002100000019b011001c70000801002000039065b06560000040f0000000102200190000003670000613d00000004030000290000001f023000390000000502200270000000200330008c0000000002004019000000000301043b00000002010000290000001f01100039000000050110027000000000011300190000000002230019000000000312004b000001100000813d000000000002041b0000000102200039000000000312004b0000010c0000413d00000004010000290000001f0110008c000004990000a13d0000000301000029000000000010043500000198010000410000000002000414000001980320009c0000000001024019000000c0011002100000019b011001c70000801002000039065b06560000040f000000010220019000000007020000290000000906000029000003670000613d000000040300002900000000032301700000002002000039000000000101043b000001300000613d0000002002000039000000000400001900000000056200190000000005050433000000000051041b000000200220003900000001011000390000002004400039000000000534004b000001280000413d0000000404000029000000000343004b0000013e0000813d00000004030000290000000303300210000000f80330018f000000010400008a000000000334022f000000000343013f000000090400002900000000024200190000000002020433000000000232016f000000000021041b0000000401000029000000010110021000000001011001bf000004a70000013d0000019e0210009c000001c60000213d000001a20210009c000002440000613d000001a30210009c000002700000613d000001a40110009c000003670000c13d0000000001000416000000000110004c000003670000c13d000000040100008a00000000011000310000019a02000041000000000310004c000000000300001900000000030240190000019a01100197000000000410004c000000000200a0190000019a0110009c00000000010300190000000001026019000000000110004c000003670000c13d0000000405000039000000000405041a000000010640019000000001014002700000007f0210018f00000000010260190000001f0210008c00000000020000190000000102002039000000000224013f00000001022001900000021b0000c13d000000400200043d0000000003120436000000000660004c000003800000c13d000001000500008a000000000454016f0000000000430435000000000110004c000000200400003900000000040060190000038d0000013d000001a60210009c000002940000613d000001a70210009c000002e30000613d000001a80110009c000003670000c13d0000000001000416000000000110004c000003670000c13d000000040100008a00000000011000310000019a02000041000000400310008c000000000300001900000000030240190000019a01100197000000000410004c000000000200a0190000019a0110009c00000000010300190000000001026019000000000110004c000003670000c13d00000004010000390000000101100367000000000101043b000900000001001d000001ac0110009c000003670000213d0000000001000411000700000001001d00000000001004350000000101000039000800000001001d000000200010043f00000198010000410000000002000414000001980320009c0000000001024019000000c001100210000001ae011001c70000801002000039065b06560000040f0000000102200190000003670000613d000000000101043b00000009020000290000000000200435000000200010043f00000024010000390000000101100367000000000101043b000600000001001d00000198010000410000000002000414000001980320009c0000000001024019000000c001100210000001ae011001c70000801002000039065b06560000040f0000000102200190000003670000613d000000000101043b000000000101041a00000006020000290000000003210019000000000113004b000000000100001900000001010040390000000101100190000003ae0000c13d00000007010000290000000902000029065b05ea0000040f000000400100043d000000080200002900000000002104350000019802000041000001980310009c00000000010280190000004001100210000001ad011001c70000065c0001042e0000019f0210009c000002ff0000613d000001a00210009c000003510000613d000001a10110009c000003670000c13d0000000001000416000000000110004c000003670000c13d000000040100008a00000000011000310000019a02000041000000400310008c000000000300001900000000030240190000019a01100197000000000410004c000000000200a0190000019a0110009c00000000010300190000000001026019000000000110004c000003670000c13d00000001020003670000000401200370000000000101043b000001ac0310009c000003670000213d0000002402200370000000000302043b000001ac0230009c000003670000213d00000000001004350000000101000039000000200010043f0000004002000039000900000002001d0000000001000019000800000003001d065b052b0000040f00000008020000290000000000200435000000200010043f00000000010000190000000902000029065b052b0000040f000000000101041a000000400200043d00000000001204350000019801000041000001980320009c00000000010240190000004001100210000001ad011001c70000065c0001042e0000000001000416000000000110004c000003670000c13d000000040100008a00000000011000310000019a02000041000000000310004c000000000300001900000000030240190000019a01100197000000000410004c000000000200a0190000019a0110009c00000000010300190000000001026019000000000110004c000003670000c13d0000000303000039000000000203041a000000010420019000000001012002700000007f0510018f000000000601001900000000060560190000001f0560008c00000000050000190000000105002039000000000552013f0000000105500190000003690000613d000001b70100004100000000001004350000002201000039000000040010043f000001b8010000410000065d000104300000000001000416000000000110004c000003670000c13d000000040100008a00000000011000310000019a02000041000000400310008c000000000300001900000000030240190000019a01100197000000000410004c000000000200a0190000019a0110009c00000000010300190000000001026019000000000110004c000003670000c13d00000001010003670000000402100370000000000202043b000001ac0320009c000003670000213d0000002401100370000000000301043b0000000001000411065b05ea0000040f0000000101000039000000400200043d00000000001204350000019801000041000001980320009c00000000010240190000004001100210000001ad011001c70000065c0001042e0000000001000416000000000110004c000003670000c13d000000040100008a00000000011000310000019a02000041000000400310008c000000000300001900000000030240190000019a01100197000000000410004c000000000200a0190000019a0110009c00000000010300190000000001026019000000000110004c000003670000c13d00000001010003670000000402100370000000000402043b000001ac0240009c000003670000213d0000002401100370000000000501043b000000000140004c000003a60000c13d000000400100043d0000004402100039000001b503000041000000000032043500000024021000390000001f030000390000000000320435000001b10200004100000000002104350000000402100039000000200300003900000000003204350000019802000041000001980310009c00000000010280190000004001100210000001b6011001c70000065d000104300000000001000416000000000110004c000003670000c13d000000040100008a00000000011000310000019a02000041000000200310008c000000000300001900000000030240190000019a01100197000000000410004c000000000200a0190000019a0110009c00000000010300190000000001026019000000000110004c000003670000c13d00000004010000390000000101100367000000000101043b000001ac0210009c000003670000213d0000000000100435000000200000043f00000040020000390000000001000019065b052b0000040f000000000101041a000000400200043d00000000001204350000019801000041000001980320009c00000000010240190000004001100210000001ad011001c70000065c0001042e0000000001000416000000000110004c000003670000c13d000000040100008a00000000011000310000019a02000041000000600310008c000000000300001900000000030240190000019a01100197000000000410004c000000000200a0190000019a0110009c00000000010300190000000001026019000000000110004c000003670000c13d00000001010003670000000402100370000000000402043b000001ac0240009c000003670000213d0000002402100370000000000202043b000900000002001d000001ac0220009c000003670000213d0000004401100370000000000101043b000700000001001d00000000004004350000000101000039000600000001001d000000200010043f00000198010000410000000002000414000001980320009c0000000001024019000000c001100210000001ae011001c70000801002000039000800000004001d065b06560000040f0000000102200190000003670000613d000000000101043b0000000002000411000500000002001d0000000000200435000000200010043f00000198010000410000000002000414000001980320009c0000000001024019000000c001100210000001ae011001c70000801002000039065b06560000040f00000008030000290000000102200190000003670000613d000000000101043b000000000201041a000000010100008a000000000112004b0000041c0000c13d000000000103001900000009020000290000000703000029065b05570000040f000000400100043d000000060200002900000000002104350000019802000041000001980310009c00000000010280190000004001100210000001ad011001c70000065c0001042e0000000001000416000000000110004c000003670000c13d000000040100008a00000000011000310000019a02000041000000000310004c000000000300001900000000030240190000019a01100197000000000410004c000000000200a0190000019a0110009c00000000010300190000000001026019000000000110004c000003670000c13d0000000501000039000000000101041a000000ff0110018f000000400200043d00000000001204350000019801000041000001980320009c00000000010240190000004001100210000001ad011001c70000065c0001042e0000000001000416000000000110004c000003670000c13d000000040100008a00000000011000310000019a02000041000000400310008c000000000300001900000000030240190000019a01100197000000000410004c000000000200a0190000019a0110009c00000000010300190000000001026019000000000110004c000003670000c13d00000001010003670000000402100370000000000202043b000900000002001d000001ac0220009c000003670000213d0000002401100370000000000101043b000800000001001d0000000001000411000600000001001d00000000001004350000000101000039000700000001001d000000200010043f00000198010000410000000002000414000001980320009c0000000001024019000000c001100210000001ae011001c70000801002000039065b06560000040f0000000102200190000003670000613d000000000101043b00000009020000290000000000200435000000200010043f00000198010000410000000002000414000001980320009c0000000001024019000000c001100210000001ae011001c70000801002000039065b06560000040f0000000102200190000003670000613d000000000101043b000000000101041a0000000803000029000000000231004b0000040f0000813d000000400100043d0000006402100039000001af0300004100000000003204350000004402100039000001b0030000410000000000320435000000240210003900000025030000390000000000320435000001b10200004100000000002104350000000402100039000000200300003900000000003204350000019802000041000001980310009c00000000010280190000004001100210000001b2011001c70000065d000104300000000001000416000000000110004c000003670000c13d000000040100008a00000000011000310000019a02000041000000400310008c000000000300001900000000030240190000019a01100197000000000410004c000000000200a0190000019a0110009c00000000010300190000000001026019000000000110004c000003670000c13d00000001010003670000000402100370000000000202043b000001ac0320009c000003730000a13d00000000010000190000065d00010430000000800060043f000000000440004c000003b40000c13d000001000300008a000000000232016f000000a00020043f000000000160004c000000c001000039000000a001006039000003c30000013d0000002401100370000000000301043b0000000001000411065b05570000040f0000000101000039000000400200043d00000000001204350000019801000041000001980320009c00000000010240190000004001100210000001ad011001c70000065c0001042e0000000000500435000000000410004c00000000040000190000038d0000613d000001b30500004100000000040000190000000006430019000000000705041a000000000076043500000001055000390000002004400039000000000614004b000003860000413d0000003f01400039000000200300008a000000000331016f0000000001230019000000000331004b00000000040000190000000104004039000001990310009c000003c90000213d0000000103400190000003c90000c13d000000400010043f000900000001001d065b05410000040f000000090400002900000000014100490000019802000041000001980310009c0000000001028019000001980340009c000000000204401900000040022002100000006001100210000000000121019f0000065c0001042e0000000201000039000000000301041a0000000002530019000000000332004b000000000300001900000001030040390000000103300190000003de0000613d000001b70100004100000000001004350000001101000039000000040010043f000001b8010000410000065d000104300000000000300435000000a001000039000000000260004c000003cf0000613d000001bf0200004100000000040000190000000003040019000000000402041a000000a005300039000000000045043500000001022000390000002004300039000000000564004b000003ba0000413d000000c0013000390000001f01100039000000200200008a000000000121016f000001c002100041000001c10220009c000003cf0000813d000001b70100004100000000001004350000004101000039000000040010043f000001b8010000410000065d00010430000900000001001d000000400010043f0000008002000039065b05410000040f000000090400002900000000014100490000019802000041000001980310009c0000000001028019000001980340009c000000000204401900000040022002100000006001100210000000000121019f0000065c0001042e000800000005001d000000000021041b0000000000400435000000200000043f00000198010000410000000002000414000001980320009c0000000001024019000000c001100210000001ae011001c70000801002000039000900000004001d065b06560000040f00000009060000290000000102200190000003670000613d000000000101043b000000000201041a00000008030000290000000002320019000000000021041b000000400100043d000000000031043500000198020000410000000003000414000001980430009c0000000003028019000001980410009c00000000010280190000004001100210000000c002300210000000000112019f0000019b011001c70000800d020000390000000303000039000001b4040000410000000005000019065b06510000040f0000000101200190000003670000613d000000400100043d000000010200003900000000002104350000019802000041000001980310009c00000000010280190000004001100210000001ad011001c70000065c0001042e000000000331004900000006010000290000000902000029065b05ea0000040f000000400100043d000000070200002900000000002104350000019802000041000001980310009c00000000010280190000004001100210000001ad011001c70000065c0001042e0000000701000029000000000112004b000004310000813d000000400100043d0000004402100039000001be03000041000000000032043500000024021000390000001d030000390000000000320435000001b10200004100000000002104350000000402100039000000200300003900000000003204350000019802000041000001980310009c00000000010280190000004001100210000001b6011001c70000065d00010430000400000002001d000000000130004c000004490000c13d000000400100043d0000006402100039000001bc0300004100000000003204350000004402100039000001bd030000410000000000320435000000240210003900000024030000390000000000320435000001b10200004100000000002104350000000402100039000000200300003900000000003204350000019802000041000001980310009c00000000010280190000004001100210000001b2011001c70000065d000104300000000501000029000001ac01100198000500000001001d000004620000c13d000000400100043d0000006402100039000001ba0300004100000000003204350000004402100039000001bb030000410000000000320435000000240210003900000022030000390000000000320435000001b10200004100000000002104350000000402100039000000200300003900000000003204350000019802000041000001980310009c00000000010280190000004001100210000001b2011001c70000065d00010430000000080100002900000000001004350000000601000029000000200010043f00000198010000410000000002000414000001980320009c0000000001024019000000c001100210000001ae011001c70000801002000039065b06560000040f0000000102200190000003670000613d000000000101043b00000005020000290000000000200435000000200010043f00000198010000410000000002000414000001980320009c0000000001024019000000c001100210000001ae011001c70000801002000039065b06560000040f00000004030000290000000102200190000003670000613d00000007020000290000000002230049000000000101043b000000000021041b000000400100043d000000000021043500000198020000410000000003000414000001980430009c0000000003028019000001980410009c00000000010280190000004001100210000000c002300210000000000112019f0000019b011001c70000800d020000390000000303000039000001b90400004100000008050000290000000506000029065b06510000040f00000008030000290000000101200190000002d60000c13d000003670000013d0000000401000029000000000110004c00000000010000190000049f0000613d0000000601000029000000000101043300000004040000290000000302400210000000010300008a000000000223022f000000000232013f000000000121016f0000000102400210000000000121019f0000000302000029000000000012041b00000001010000290000000001010433000900000001001d000001990110009c000003c90000213d0000000401000039000600000001001d000000000101041a000000010210019000000001021002700000007f0320018f0000000002036019000400000002001d0000001f0220008c00000000020000190000000102002039000000000121013f00000001011001900000021b0000c13d0000000401000029000000200110008c000004dc0000413d0000000601000029000000000010043500000198010000410000000002000414000001980320009c0000000001024019000000c0011002100000019b011001c70000801002000039065b06560000040f0000000102200190000003670000613d00000009030000290000001f023000390000000502200270000000200330008c0000000002004019000000000301043b00000004010000290000001f01100039000000050110027000000000011300190000000002230019000000000312004b000004dc0000813d000000000002041b0000000102200039000000000312004b000004d80000413d00000009010000290000001f0110008c0000050e0000a13d0000000601000029000000000010043500000198010000410000000002000414000001980320009c0000000001024019000000c0011002100000019b011001c70000801002000039065b06560000040f000000010220019000000007020000290000000106000029000003670000613d000000090300002900000000032301700000002002000039000000000101043b000004fc0000613d0000002002000039000000000400001900000000056200190000000005050433000000000051041b000000200220003900000001011000390000002004400039000000000534004b000004f40000413d0000000904000029000000000343004b0000050a0000813d00000009030000290000000303300210000000f80330018f000000010400008a000000000334022f000000000343013f000000010400002900000000024200190000000002020433000000000232016f000000000021041b0000000101000039000000090200002900000001022002100000051b0000013d0000000901000029000000000110004c0000000001000019000005140000613d0000000801000029000000000101043300000009040000290000000302400210000000010300008a000000000223022f000000000232013f000000000221016f0000000101400210000000000112019f0000000602000029000000000012041b0000000501000039000000000201041a000001000300008a000000000232016f0000000503000029000000ff0330018f000000000232019f000000000021041b0000002001000039000001000010044300000120000004430000019c010000410000065c0001042e0000019803000041000001980410009c00000000010380190000004001100210000001980420009c00000000020380190000006002200210000000000112019f0000000002000414000001980420009c0000000002038019000000c002200210000000000112019f000001c2011001c70000801002000039065b06560000040f00000001022001900000053f0000613d000000000101043b000000000001042d00000000010000190000065d0001043000000020030000390000000004310436000000000302043300000000003404350000004001100039000000000430004c000005500000613d000000000400001900000000054100190000002004400039000000000624001900000000060604330000000000650435000000000534004b000005490000413d000000000231001900000000000204350000001f02300039000000200300008a000000000232016f0000000001210019000000000001042d0004000000000002000400000003001d000001ac01100198000005ab0000613d000001ac02200198000200000002001d000005c00000613d000300000001001d0000000000100435000000200000043f00000198010000410000000002000414000001980320009c0000000001024019000000c001100210000001ae011001c70000801002000039065b06560000040f0000000102200190000005a90000613d000000000101043b000000000201041a0000000401000029000100000002001d000000000112004b000005d50000413d00000003010000290000000000100435000000200000043f00000198010000410000000002000414000001980320009c0000000001024019000000c001100210000001ae011001c70000801002000039065b06560000040f0000000102200190000005a90000613d000000040200002900000001030000290000000002230049000000000101043b000000000021041b0000000201000029000000000010043500000198010000410000000002000414000001980320009c0000000001024019000000c001100210000001ae011001c70000801002000039065b06560000040f0000000102200190000005a90000613d000000000101043b000000000201041a00000004030000290000000002320019000000000021041b000000400100043d000000000031043500000198020000410000000003000414000001980430009c0000000003028019000001980410009c00000000010280190000004001100210000000c002300210000000000112019f0000019b011001c70000800d020000390000000303000039000001b40400004100000003050000290000000206000029065b06510000040f0000000101200190000005a90000613d000000000001042d00000000010000190000065d00010430000000400100043d0000006402100039000001c70300004100000000003204350000004402100039000001c8030000410000000000320435000000240210003900000025030000390000000000320435000001b10200004100000000002104350000000402100039000000200300003900000000003204350000019802000041000001980310009c00000000010280190000004001100210000001b2011001c70000065d00010430000000400100043d0000006402100039000001c50300004100000000003204350000004402100039000001c6030000410000000000320435000000240210003900000023030000390000000000320435000001b10200004100000000002104350000000402100039000000200300003900000000003204350000019802000041000001980310009c00000000010280190000004001100210000001b2011001c70000065d00010430000000400100043d0000006402100039000001c30300004100000000003204350000004402100039000001c4030000410000000000320435000000240210003900000026030000390000000000320435000001b10200004100000000002104350000000402100039000000200300003900000000003204350000019802000041000001980310009c00000000010280190000004001100210000001b2011001c70000065d000104300003000000000002000001ac01100198000006270000613d000200000003001d000001ac02200198000300000002001d0000063c0000613d000100000001001d00000000001004350000000101000039000000200010043f00000198010000410000000002000414000001980320009c0000000001024019000000c001100210000001ae011001c70000801002000039065b06560000040f00000001022001900000000304000029000006250000613d000000000101043b0000000000400435000000200010043f00000198010000410000000002000414000001980320009c0000000001024019000000c001100210000001ae011001c70000801002000039065b06560000040f00000003060000290000000102200190000006250000613d000000000101043b0000000202000029000000000021041b000000400100043d000000000021043500000198020000410000000003000414000001980430009c0000000003028019000001980410009c00000000010280190000004001100210000000c002300210000000000112019f0000019b011001c70000800d020000390000000303000039000001b9040000410000000105000029065b06510000040f0000000101200190000006250000613d000000000001042d00000000010000190000065d00010430000000400100043d0000006402100039000001bc0300004100000000003204350000004402100039000001bd030000410000000000320435000000240210003900000024030000390000000000320435000001b10200004100000000002104350000000402100039000000200300003900000000003204350000019802000041000001980310009c00000000010280190000004001100210000001b2011001c70000065d00010430000000400100043d0000006402100039000001ba0300004100000000003204350000004402100039000001bb030000410000000000320435000000240210003900000022030000390000000000320435000001b10200004100000000002104350000000402100039000000200300003900000000003204350000019802000041000001980310009c00000000010280190000004001100210000001b2011001c70000065d0001043000000654002104210000000102000039000000000001042d0000000002000019000000000001042d00000659002104230000000102000039000000000001042d0000000002000019000000000001042d0000065b000004320000065c0001042e0000065d000104300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffff000000000000000000000000000000000000000000000000ffffffffffffffff8000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000002000000000000000000000000000000002000000000000000000000000000000400000010000000000000000000000000000000000000000000000000000000000000000000000000040c10f1800000000000000000000000000000000000000000000000000000000a457c2d600000000000000000000000000000000000000000000000000000000a457c2d700000000000000000000000000000000000000000000000000000000a9059cbb00000000000000000000000000000000000000000000000000000000dd62ed3e0000000000000000000000000000000000000000000000000000000040c10f190000000000000000000000000000000000000000000000000000000070a082310000000000000000000000000000000000000000000000000000000095d89b410000000000000000000000000000000000000000000000000000000023b872dc0000000000000000000000000000000000000000000000000000000023b872dd00000000000000000000000000000000000000000000000000000000313ce56700000000000000000000000000000000000000000000000000000000395093510000000000000000000000000000000000000000000000000000000006fdde0300000000000000000000000000000000000000000000000000000000095ea7b30000000000000000000000000000000000000000000000000000000018160ddd000000000000000000000000ffffffffffffffffffffffffffffffffffffffff00000000000000000000000000000000000000200000000000000000000000000200000000000000000000000000000000000040000000000000000000000000207a65726f00000000000000000000000000000000000000000000000000000045524332303a2064656372656173656420616c6c6f77616e63652062656c6f7708c379a00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000840000000000000000000000008a35acfbc15ff81a39ae7d344fd709f28e8600b4aa8c65c6b64bfe7fe36bd19bddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef45524332303a206d696e7420746f20746865207a65726f20616464726573730000000000000000000000000000000000000000640000000000000000000000004e487b710000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000240000000000000000000000008c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925737300000000000000000000000000000000000000000000000000000000000045524332303a20617070726f766520746f20746865207a65726f206164647265726573730000000000000000000000000000000000000000000000000000000045524332303a20617070726f76652066726f6d20746865207a65726f2061646445524332303a20696e73756666696369656e7420616c6c6f77616e6365000000c2575a0e9e593c00f959f8c92f12db2869c3395a3b0502d05e2516446f71f85bffffffffffffffffffffffffffffffffffffffffffffffff0000000000000000ffffffffffffffffffffffffffffffffffffffffffffffff00000000000000800200000000000000000000000000000000000000000000000000000000000000616c616e6365000000000000000000000000000000000000000000000000000045524332303a207472616e7366657220616d6f756e7420657863656564732062657373000000000000000000000000000000000000000000000000000000000045524332303a207472616e7366657220746f20746865207a65726f2061646472647265737300000000000000000000000000000000000000000000000000000045524332303a207472616e736665722066726f6d20746865207a65726f206164000000000000000000000000000000000000000000000000000000000000000018469939d00da7016fd24775544e09a6a1ad29697146a060aa4a0baa144c2ede'; + const hashedBytecode = utils.hashBytecode(bytecode); + expect(hashedBytecode).toEqual( + new Uint8Array([ + 1, 0, 1, 203, 106, 110, 141, 95, 104, 41, 82, 47, 25, 250, 149, 104, 102, 14, 10, 156, + 213, 59, 46, 139, 228, 222, 176, 166, 121, 69, 46, 65, + ]), + ); + }); + + it('should throw an error when bytecode is not divisible by 32', async () => { + try { + utils.hashBytecode('0x0002'); + } catch (e) { + expect((e as Error).message).toBe('The bytecode length in bytes must be divisible by 32!'); + } + }); + + it('should throw an error when bytecode is has even number of 32-byte words', async () => { + try { + utils.hashBytecode(`0x${'00020000000000020009000000000002'.repeat(2)}`); + } catch (e) { + expect((e as Error).message).toBe( + `Bytecode can not be longer than ${utils.MAX_BYTECODE_LEN_BYTES} bytes`, + ); + } + }); + }); + + // describe('#parseEip712()', () => { + // it('should parse a transaction with a signature', async () => { + // const tx: types.TransactionLike = { + // type: 113, + // nonce: 0, + // maxPriorityFeePerGas: 0n, + // maxFeePerGas: 0n, + // gasLimit: 0n, + // to: ADDRESS2, + // value: 1_000_000n, + // data: '0x', + // chainId: 270n, + // from: ADDRESS1, + // customData: { + // gasPerPubdata: 50_000n, + // factoryDeps: [], + // customSignature: '0x', + // paymasterParams: undefined, + // }, + // hash: '0x9ed410ce33179ac1ff6b721060605afc72d64febfe0c08cacab5a246602131ee', + // }; + + // const serializedTx = + // '0x71f87f8080808094a61464658afeaf65cccaafd3a512b69a83b77618830f42408001a073a20167b8d23b610b058c05368174495adf7da3a4ed4a57eb6dbdeb1fafc24aa02f87530d663a0d061f69bb564d2c6fb46ae5ae776bbd4bd2a2a4478b9cd1b42a82010e9436615cf349d7f6344891b1e7ca7c72883f5dc04982c350c080c0'; + // const result = utils.parseEip712(serializedTx); + // expect(result).toEqual(tx); + // }); + + // it('should parse a transaction without a signature', async () => { + // const tx: types.TransactionLike = { + // type: 113, + // nonce: 0, + // maxPriorityFeePerGas: 0n, + // maxFeePerGas: 0n, + // gasLimit: 0n, + // to: ADDRESS2, + // value: 0n, + // data: '0x', + // chainId: 270n, + // from: ADDRESS1, + // customData: { + // gasPerPubdata: 50_000n, + // factoryDeps: [], + // customSignature: '0x', + // paymasterParams: undefined, + // }, + // hash: '0x7d3aab3e3d06d6a702228d911c2a9afaccddd52514fb89dc9d0ff81a67bfff04', + // }; + + // const serializedTx = + // '0x71f83e8080808094a61464658afeaf65cccaafd3a512b69a83b77618808082010e808082010e9436615cf349d7f6344891b1e7ca7c72883f5dc04982c350c080c0'; + + // const result = utils.parseEip712(serializedTx); + // expect(result).toEqual(tx); + // }); + // }); +}); diff --git a/test/utils.ts b/test/utils.ts new file mode 100644 index 0000000..0a72a7d --- /dev/null +++ b/test/utils.ts @@ -0,0 +1,13 @@ +export const ADDRESS1 = '0x36615Cf349d7F6344891B1e7CA7C72883F5dc049'; +export const PRIVATE_KEY1 = + '0x7726827caac94a7f9e1b160f7ea819f172f7b6f9d2a97f992c38edeab82d4110'; +export const MNEMONIC1 = + 'stuff slice staff easily soup parent arm payment cotton trade scatter struggle'; +export const ADDRESS2 = '0xa61464658AfeAf65CccaaFD3a512b69A83B77618'; +export const PRIVATE_KEY2 = + '0xac1e735be8536c6534bb4f17f06f6afc73b2b5ba84ac2cfb12f7461b20c0bbe3'; +export const DAI_L1 = '0x70a0F165d6f8054d0d0CF8dFd4DD2005f0AF6B55'; +export const APPROVAL_TOKEN = '0x841c43Fa5d8fFfdB9efE3358906f7578d8700Dd4'; // Crown token +export const PAYMASTER = '0xa222f0c183AFA73a8Bc1AFb48D34C88c9Bf7A174'; // Crown token paymaster + +export const IS_ETH_BASED = true; From a3430636a9fba44c54961bd2e89718aafa09917c Mon Sep 17 00:00:00 2001 From: Muhammad-Altabba <24407834+Muhammad-Altabba@users.noreply.github.com> Date: Tue, 28 May 2024 13:31:54 +0200 Subject: [PATCH 08/30] move constants to a separate file --- src/constants.ts | 157 +++++++++++++++++++++++++++++++++++++-- src/utils.ts | 158 +++------------------------------------- test/unit/utils.test.ts | 9 ++- 3 files changed, 167 insertions(+), 157 deletions(-) diff --git a/src/constants.ts b/src/constants.ts index f475531..739a3b9 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -1,8 +1,151 @@ -export const ETH_ADDRESS = '0x0000000000000000000000000000000000000000'; +import * as web3Types from 'web3-types'; + export const ZERO_ADDRESS = '0x0000000000000000000000000000000000000000'; -export const BOOTLOADER_FORMAL_ADDRESS = '0x0000000000000000000000000000000000008001'; -export const CONTRACT_DEPLOYER_ADDRESS = '0x0000000000000000000000000000000000008006'; -export const L1_MESSENGER_ADDRESS = '0x0000000000000000000000000000000000008008'; -export const L2_ETH_TOKEN_ADDRESS = '0x000000000000000000000000000000000000800a'; -export const NONCE_HOLDER_ADDRESS = '0x0000000000000000000000000000000000008003'; -export const L1_TO_L2_ALIAS_OFFSET = '0x1111000000000000000000000000000000001111'; + +/** + * The address of the L1 `ETH` token. + * @constant + */ +export const ETH_ADDRESS: web3Types.Address = '0x0000000000000000000000000000000000000000'; + +/** + * The address of the L1 `ETH` token. + * @constant + */ +export const LEGACY_ETH_ADDRESS: web3Types.Address = '0x0000000000000000000000000000000000000000'; + +/** + * In the contracts the zero address can not be used, use one instead + * @constant + */ +export const ETH_ADDRESS_IN_CONTRACTS: web3Types.Address = + '0x0000000000000000000000000000000000000001'; + +/** + * The formal address for the `Bootloader`. + * @constant + */ +export const BOOTLOADER_FORMAL_ADDRESS: web3Types.Address = + '0x0000000000000000000000000000000000008001'; + +/** + * The address of the Contract deployer. + * @constant + */ +export const CONTRACT_DEPLOYER_ADDRESS: web3Types.Address = + '0x0000000000000000000000000000000000008006'; + +/** + * The address of the L1 messenger. + * @constant + */ +export const L1_MESSENGER_ADDRESS: web3Types.Address = '0x0000000000000000000000000000000000008008'; + +/** + * The address of the L2 `ETH` token. + * @constant + * @deprecated In favor of {@link L2_BASE_TOKEN_ADDRESS}. + */ +export const L2_ETH_TOKEN_ADDRESS: web3Types.Address = '0x000000000000000000000000000000000000800a'; + +/** + * The address of the base token. + * @constant + */ +export const L2_BASE_TOKEN_ADDRESS = '0x000000000000000000000000000000000000800a'; + +/** + * The address of the Nonce holder. + * @constant + */ +export const NONCE_HOLDER_ADDRESS: web3Types.Address = '0x0000000000000000000000000000000000008003'; + +/** + * Used for applying and undoing aliases on addresses during bridging from L1 to L2. + * @constant + */ +export const L1_TO_L2_ALIAS_OFFSET: web3Types.Address = + '0x1111000000000000000000000000000000001111'; + +/** + * The EIP1271 magic value used for signature validation in smart contracts. + * This predefined constant serves as a standardized indicator to signal successful + * signature validation by the contract. + * + * @constant + */ +export const EIP1271_MAGIC_VALUE = '0x1626ba7e'; + +/** + * Represents an EIP712 transaction type. + * + * @constant + */ +export const EIP712_TX_TYPE = 0x71; + +/** + * Represents a priority transaction operation on L2. + * + * @constant + */ +export const PRIORITY_OPERATION_L2_TX_TYPE = 0xff; + +/** + * The maximum bytecode length in bytes that can be deployed. + * + * @constant + */ +export const MAX_BYTECODE_LEN_BYTES: number = ((1 << 16) - 1) * 32; + +/** + * Numerator used in scaling the gas limit to ensure acceptance of `L1->L2` transactions. + * + * This constant is part of a coefficient calculation to adjust the gas limit to account for variations + * in the SDK estimation, ensuring the transaction will be accepted. + * + * @constant + */ +export const L1_FEE_ESTIMATION_COEF_NUMERATOR = 12; + +/** + * Denominator used in scaling the gas limit to ensure acceptance of `L1->L2` transactions. + * + * This constant is part of a coefficient calculation to adjust the gas limit to account for variations + * in the SDK estimation, ensuring the transaction will be accepted. + * + * @constant + */ +export const L1_FEE_ESTIMATION_COEF_DENOMINATOR = 10; + +/** + * Gas limit used for displaying the error messages when the + * users do not have enough fee when depositing ERC20 token from L1 to L2. + * + * @constant + */ +export const L1_RECOMMENDED_MIN_ERC20_DEPOSIT_GAS_LIMIT = 400_000; + +/** + * Gas limit used for displaying the error messages when the + * users do not have enough fee when depositing `ETH` token from L1 to L2. + * + * @constant + */ +export const L1_RECOMMENDED_MIN_ETH_DEPOSIT_GAS_LIMIT = 200_000; + +/** + * Default gas per pubdata byte for L2 transactions. + * This value is utilized when inserting a default value for type 2 + * and EIP712 type transactions. + * + * @constant + */ +// It is a realistic value, but it is large enough to fill into any batch regardless of the pubdata price. +export const DEFAULT_GAS_PER_PUBDATA_LIMIT = 50_000; + +/** + * The `L1->L2` transactions are required to have the following gas per pubdata byte. + * + * @constant + */ +export const REQUIRED_L1_TO_L2_GAS_PER_PUBDATA_LIMIT = 800; diff --git a/src/utils.ts b/src/utils.ts index d3bdb9e..3177fdb 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -34,6 +34,18 @@ import { IERC1271ABI } from './contracts/IERC1271'; import { IL1BridgeABI } from './contracts/IL1ERC20Bridge'; import { IL2BridgeABI } from './contracts/IL2Bridge'; import { INonceHolderABI } from './contracts/INonceHolder'; +import { + LEGACY_ETH_ADDRESS, + L2_BASE_TOKEN_ADDRESS, + ETH_ADDRESS_IN_CONTRACTS, + L1_MESSENGER_ADDRESS, + CONTRACT_DEPLOYER_ADDRESS, + MAX_BYTECODE_LEN_BYTES, + L1_TO_L2_ALIAS_OFFSET, + EIP1271_MAGIC_VALUE, + L1_FEE_ESTIMATION_COEF_NUMERATOR, + L1_FEE_ESTIMATION_COEF_DENOMINATOR, +} from './constants'; // import { RpcMethods } from './rpc.methods'; // to be used instead of the one at zksync-ethers: Provider from ./provider @@ -95,150 +107,6 @@ export const L2Bridge = new web3.Contract(IL2BridgeABI); */ export const NonceHolderContract = new web3.Contract(INonceHolderABI); -/** - * The address of the L1 `ETH` token. - * @constant - */ -export const ETH_ADDRESS: web3.Address = '0x0000000000000000000000000000000000000000'; - -/** - * The address of the L1 `ETH` token. - * @constant - */ -export const LEGACY_ETH_ADDRESS: web3.Address = '0x0000000000000000000000000000000000000000'; - -/** - * In the contracts the zero address can not be used, use one instead - * @constant - */ -export const ETH_ADDRESS_IN_CONTRACTS: web3.Address = '0x0000000000000000000000000000000000000001'; - -/** - * The formal address for the `Bootloader`. - * @constant - */ -export const BOOTLOADER_FORMAL_ADDRESS: web3.Address = '0x0000000000000000000000000000000000008001'; - -/** - * The address of the Contract deployer. - * @constant - */ -export const CONTRACT_DEPLOYER_ADDRESS: web3.Address = '0x0000000000000000000000000000000000008006'; - -/** - * The address of the L1 messenger. - * @constant - */ -export const L1_MESSENGER_ADDRESS: web3.Address = '0x0000000000000000000000000000000000008008'; - -/** - * The address of the L2 `ETH` token. - * @constant - * @deprecated In favor of {@link L2_BASE_TOKEN_ADDRESS}. - */ -export const L2_ETH_TOKEN_ADDRESS: web3.Address = '0x000000000000000000000000000000000000800a'; - -/** - * The address of the base token. - * @constant - */ -export const L2_BASE_TOKEN_ADDRESS = '0x000000000000000000000000000000000000800a'; - -/** - * The address of the Nonce holder. - * @constant - */ -export const NONCE_HOLDER_ADDRESS: web3.Address = '0x0000000000000000000000000000000000008003'; - -/** - * Used for applying and undoing aliases on addresses during bridging from L1 to L2. - * @constant - */ -export const L1_TO_L2_ALIAS_OFFSET: web3.Address = '0x1111000000000000000000000000000000001111'; - -/** - * The EIP1271 magic value used for signature validation in smart contracts. - * This predefined constant serves as a standardized indicator to signal successful - * signature validation by the contract. - * - * @constant - */ -export const EIP1271_MAGIC_VALUE = '0x1626ba7e'; - -/** - * Represents an EIP712 transaction type. - * - * @constant - */ -export const EIP712_TX_TYPE = 0x71; - -/** - * Represents a priority transaction operation on L2. - * - * @constant - */ -export const PRIORITY_OPERATION_L2_TX_TYPE = 0xff; - -/** - * The maximum bytecode length in bytes that can be deployed. - * - * @constant - */ -export const MAX_BYTECODE_LEN_BYTES: number = ((1 << 16) - 1) * 32; - -/** - * Numerator used in scaling the gas limit to ensure acceptance of `L1->L2` transactions. - * - * This constant is part of a coefficient calculation to adjust the gas limit to account for variations - * in the SDK estimation, ensuring the transaction will be accepted. - * - * @constant - */ -export const L1_FEE_ESTIMATION_COEF_NUMERATOR = 12; - -/** - * Denominator used in scaling the gas limit to ensure acceptance of `L1->L2` transactions. - * - * This constant is part of a coefficient calculation to adjust the gas limit to account for variations - * in the SDK estimation, ensuring the transaction will be accepted. - * - * @constant - */ -export const L1_FEE_ESTIMATION_COEF_DENOMINATOR = 10; - -/** - * Gas limit used for displaying the error messages when the - * users do not have enough fee when depositing ERC20 token from L1 to L2. - * - * @constant - */ -export const L1_RECOMMENDED_MIN_ERC20_DEPOSIT_GAS_LIMIT = 400_000; - -/** - * Gas limit used for displaying the error messages when the - * users do not have enough fee when depositing `ETH` token from L1 to L2. - * - * @constant - */ -export const L1_RECOMMENDED_MIN_ETH_DEPOSIT_GAS_LIMIT = 200_000; - -/** - * Default gas per pubdata byte for L2 transactions. - * This value is utilized when inserting a default value for type 2 - * and EIP712 type transactions. - * - * @constant - */ -// It is a realistic value, but it is large enough to fill into any batch regardless of the pubdata price. -export const DEFAULT_GAS_PER_PUBDATA_LIMIT = 50_000; - -/** - * The `L1->L2` transactions are required to have the following gas per pubdata byte. - * - * @constant - */ -export const REQUIRED_L1_TO_L2_GAS_PER_PUBDATA_LIMIT = 800; - /** * ------------------------------------------------------------ * consider adding the next few functions to web3.js: @@ -606,9 +474,7 @@ export function hashBytecode(bytecode: web3Types.Bytes): Uint8Array { } const hashStr = web3Utils.toHex(sha256(Buffer.from(bytecodeAsArray))); - console.log('hashStr:', hashStr); const hash = web3Utils.bytesToUint8Array(hashStr); - console.log('hash:', hash); // Note that the length of the bytecode // should be provided in 32-byte words. diff --git a/test/unit/utils.test.ts b/test/unit/utils.test.ts index 3e20363..686865b 100644 --- a/test/unit/utils.test.ts +++ b/test/unit/utils.test.ts @@ -6,6 +6,7 @@ import { ADDRESS1, // ADDRESS2 } from '../utils'; +import * as constants from '../../src/constants'; describe('utils', () => { describe('#getHashedL2ToL1Msg()', () => { @@ -21,17 +22,17 @@ describe('utils', () => { describe('#isETH()', () => { it('should return true for legacy L1 ETH address', async () => { - const result = utils.isETH(utils.LEGACY_ETH_ADDRESS); + const result = utils.isETH(constants.LEGACY_ETH_ADDRESS); expect(result).toBeTruthy(); }); it('should return true for L1 ETH address', async () => { - const result = utils.isETH(utils.ETH_ADDRESS_IN_CONTRACTS); + const result = utils.isETH(constants.ETH_ADDRESS_IN_CONTRACTS); expect(result).toBeTruthy(); }); it('should return true for L2 ETH address', async () => { - const result = utils.isETH(utils.L2_BASE_TOKEN_ADDRESS); + const result = utils.isETH(constants.L2_BASE_TOKEN_ADDRESS); expect(result).toBeTruthy(); }); }); @@ -214,7 +215,7 @@ describe('utils', () => { utils.hashBytecode(`0x${'00020000000000020009000000000002'.repeat(2)}`); } catch (e) { expect((e as Error).message).toBe( - `Bytecode can not be longer than ${utils.MAX_BYTECODE_LEN_BYTES} bytes`, + `Bytecode can not be longer than ${constants.MAX_BYTECODE_LEN_BYTES} bytes`, ); } }); From fcb8734a3cf25ec8c4c6f6c9dd19608ab5e450bd Mon Sep 17 00:00:00 2001 From: Muhammad-Altabba <24407834+Muhammad-Altabba@users.noreply.github.com> Date: Thu, 30 May 2024 00:36:47 +0200 Subject: [PATCH 09/30] add some utils constants and types --- src/constants.ts | 21 ++ src/rpc.methods.ts | 284 ++++++++++++++++---- src/types.ts | 50 ++-- src/utils.ts | 640 +++++++++++++++++++++++++-------------------- 4 files changed, 631 insertions(+), 364 deletions(-) diff --git a/src/constants.ts b/src/constants.ts index 739a3b9..133c88f 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -149,3 +149,24 @@ export const DEFAULT_GAS_PER_PUBDATA_LIMIT = 50_000; * @constant */ export const REQUIRED_L1_TO_L2_GAS_PER_PUBDATA_LIMIT = 800; + +/** + * All typed data conforming to the EIP712 standard within zkSync Era. + */ +export const EIP712_TYPES = { + Transaction: [ + {name: 'txType', type: 'uint256'}, + {name: 'from', type: 'uint256'}, + {name: 'to', type: 'uint256'}, + {name: 'gasLimit', type: 'uint256'}, + {name: 'gasPerPubdataByteLimit', type: 'uint256'}, + {name: 'maxFeePerGas', type: 'uint256'}, + {name: 'maxPriorityFeePerGas', type: 'uint256'}, + {name: 'paymaster', type: 'uint256'}, + {name: 'nonce', type: 'uint256'}, + {name: 'value', type: 'uint256'}, + {name: 'data', type: 'bytes'}, + {name: 'factoryDeps', type: 'bytes32[]'}, + {name: 'paymasterInput', type: 'bytes'}, + ], + }; \ No newline at end of file diff --git a/src/rpc.methods.ts b/src/rpc.methods.ts index 53a1453..7f98606 100644 --- a/src/rpc.methods.ts +++ b/src/rpc.methods.ts @@ -1,12 +1,7 @@ import type { Web3RequestManager } from 'web3-core'; -import { format, toNumber } from 'web3-utils'; -import type { - Address, - Bytes, - HexString32Bytes, - Numbers, - TransactionWithSenderAPI, -} from 'web3-types'; +import * as web3Utils from 'web3-utils'; +import * as web3Types from 'web3-types'; +import * as web3Accounts from 'web3-eth-accounts'; import { DEFAULT_RETURN_FORMAT, // Web3BaseProvider @@ -22,6 +17,8 @@ import type { RawBlockTransaction, TransactionDetails, WalletBalances, + TransactionRequest, + TransactionOverrides, } from './types'; import { AddressSchema, @@ -38,6 +35,12 @@ import { TransactionDetailsSchema, UintSchema, } from './schemas'; +import { + ETH_ADDRESS_IN_CONTRACTS, + L2_BASE_TOKEN_ADDRESS, + REQUIRED_L1_TO_L2_GAS_PER_PUBDATA_LIMIT, +} from './constants'; +import { isAddressEq } from './utils'; // The ZkSync methods described here https://docs.zksync.io/build/api.html @@ -45,8 +48,36 @@ import { export class RpcMethods { requestManager: Web3RequestManager; + protected _contractAddresses: { + bridgehubContract?: web3Types.Address; + mainContract?: web3Types.Address; + erc20BridgeL1?: web3Types.Address; + erc20BridgeL2?: web3Types.Address; + wethBridgeL1?: web3Types.Address; + wethBridgeL2?: web3Types.Address; + sharedBridgeL1?: web3Types.Address; + sharedBridgeL2?: web3Types.Address; + baseToken?: web3Types.Address; + }; + + protected contractAddresses(): { + bridgehubContract?: web3Types.Address; + mainContract?: web3Types.Address; + erc20BridgeL1?: web3Types.Address; + erc20BridgeL2?: web3Types.Address; + wethBridgeL1?: web3Types.Address; + wethBridgeL2?: web3Types.Address; + sharedBridgeL1?: web3Types.Address; + sharedBridgeL2?: web3Types.Address; + baseToken?: web3Types.Address; + } { + return this._contractAddresses; + } + constructor(requestManager: Web3RequestManager) { this.requestManager = requestManager; + + this._contractAddresses = {}; } private async _send(method: string, params: unknown[]): Promise { @@ -62,7 +93,11 @@ export class RpcMethods { * @param returnFormat - The format of the return value. */ public async l1ChainId(returnFormat: DataFormat = DEFAULT_RETURN_FORMAT): Promise { - return format(IntSchema, await this._send('zks_L1ChainId', []), returnFormat) as bigint; + return web3Utils.format( + IntSchema, + await this._send('zks_L1ChainId', []), + returnFormat, + ) as bigint; } /** @@ -71,7 +106,11 @@ export class RpcMethods { * @param returnFormat - The format of the return value. */ public async getL1BatchNumber(returnFormat: DataFormat = DEFAULT_RETURN_FORMAT): Promise { - return format(IntSchema, await this._send('zks_L1BatchNumber', []), returnFormat) as bigint; + return web3Utils.format( + IntSchema, + await this._send('zks_L1BatchNumber', []), + returnFormat, + ) as bigint; } /** @@ -81,13 +120,13 @@ export class RpcMethods { * @param returnFormat - The format of the return value. */ public async getL1BatchDetails( - number: Numbers, + number: web3Types.Numbers, returnFormat: DataFormat = DEFAULT_RETURN_FORMAT, ): Promise { - return format( + return web3Utils.format( BatchDetailsSchema, await this._send('zks_getL1BatchDetails', [ - typeof number === 'number' ? number : Number(toNumber(number)), + typeof number === 'number' ? number : Number(web3Utils.toNumber(number)), ]), returnFormat, ) as BatchDetails; @@ -104,13 +143,13 @@ export class RpcMethods { * @param returnFormat - The format of the return value. */ public async getBlockDetails( - number: Numbers, + number: web3Types.Numbers, returnFormat: DataFormat = DEFAULT_RETURN_FORMAT, ): Promise { - return format( + return web3Utils.format( BlockDetailsSchema, await this._send('zks_getBlockDetails', [ - typeof number === 'number' ? number : Number(toNumber(number)), + typeof number === 'number' ? number : Number(web3Utils.toNumber(number)), ]), returnFormat, ) as BlockDetails; @@ -123,10 +162,10 @@ export class RpcMethods { * @param returnFormat - The format of the return value. */ public async getTransactionDetails( - txHash: Bytes, + txHash: web3Types.Bytes, returnFormat: DataFormat = DEFAULT_RETURN_FORMAT, ): Promise { - return format( + return web3Utils.format( TransactionDetailsSchema, await this._send('zks_getTransactionDetails', [txHash]), returnFormat, @@ -140,10 +179,10 @@ export class RpcMethods { * @param returnFormat - The format of the return value. */ public async getBytecodeByHash( - bytecodeHash: Bytes, + bytecodeHash: web3Types.Bytes, returnFormat: DataFormat = DEFAULT_RETURN_FORMAT, ): Promise { - return format( + return web3Utils.format( BytesSchema, await this._send('zks_getBytecodeByHash', [bytecodeHash]), returnFormat, @@ -157,15 +196,15 @@ export class RpcMethods { * @param returnFormat - The format of the return value. */ public async getRawBlockTransactions( - number: Numbers, + number: web3Types.Numbers, returnFormat: DataFormat = DEFAULT_RETURN_FORMAT, ): Promise { const result = await this._send('zks_getRawBlockTransactions', [ - typeof number === 'number' ? number : Number(toNumber(number)), + typeof number === 'number' ? number : Number(web3Utils.toNumber(number)), ]); if (Array.isArray(result)) { return result.map(tx => { - return format(RawBlockTransactionSchema, tx, returnFormat) as RawBlockTransaction; + return web3Utils.format(RawBlockTransactionSchema, tx, returnFormat) as RawBlockTransaction; }); } return []; @@ -178,16 +217,98 @@ export class RpcMethods { * @param returnFormat - The format of the return value. */ public async estimateFee( - transaction: Partial, + transaction: Partial, returnFormat: DataFormat = DEFAULT_RETURN_FORMAT, ): Promise { - return format( + return web3Utils.format( EstimateFeeSchema, await this._send('zks_estimateFee', [transaction]), returnFormat, ) as EstimateFee; } + /** + * Returns the L1 base token address. + */ + async getBaseTokenContractAddress(): Promise { + if (!this.contractAddresses().baseToken) { + this.contractAddresses().baseToken = (await this._send( + 'zks_getBaseTokenL1Address', + [], + )) as string; + } + return web3Utils.toChecksumAddress(this.contractAddresses().baseToken!); + } + + /** + * Returns whether the chain is ETH-based. + */ + async isEthBasedChain(): Promise { + return isAddressEq(await this.getBaseTokenContractAddress(), ETH_ADDRESS_IN_CONTRACTS); + } + + /** + * Returns whether the `token` is the base token. + */ + async isBaseToken(token: web3Types.Address): Promise { + return ( + isAddressEq(token, await this.getBaseTokenContractAddress()) || + isAddressEq(token, L2_BASE_TOKEN_ADDRESS) + ); + } + + /** + * Returns the testnet {@link https://docs.zksync.io/build/developer-reference/account-abstraction.html#paymasters paymaster address} + * if available, or `null`. + * + * Calls the {@link https://docs.zksync.io/build/api.html#zks-gettestnetpaymaster zks_getTestnetPaymaster} JSON-RPC method. + */ + async getTestnetPaymasterAddress(): Promise { + // Unlike contract's addresses, the testnet paymaster is not cached, since it can be trivially changed + // on the fly by the server and should not be relied on to be constant + return (await this._send('zks_getTestnetPaymaster', [])) as web3Types.Address | null; + } + + /** + * Returns the addresses of the default zkSync Era bridge contracts on both L1 and L2. + * + * Calls the {@link https://docs.zksync.io/build/api.html#zks-getbridgecontracts zks_getBridgeContracts} JSON-RPC method. + */ + async getDefaultBridgeAddresses(): Promise<{ + erc20L1: string; + erc20L2: string; + wethL1: string; + wethL2: string; + sharedL1: string; + sharedL2: string; + }> { + if (!this.contractAddresses().erc20BridgeL1) { + const addresses: { + l1Erc20DefaultBridge: string; + l2Erc20DefaultBridge: string; + l1WethBridge: string; + l2WethBridge: string; + l1SharedDefaultBridge: string; + l2SharedDefaultBridge: string; + } = (await this._send('zks_getBridgeContracts', [])) as any; + + this.contractAddresses().erc20BridgeL1 = addresses.l1Erc20DefaultBridge; + this.contractAddresses().erc20BridgeL2 = addresses.l2Erc20DefaultBridge; + this.contractAddresses().wethBridgeL1 = addresses.l1WethBridge; + this.contractAddresses().wethBridgeL2 = addresses.l2WethBridge; + this.contractAddresses().sharedBridgeL1 = addresses.l1SharedDefaultBridge; + this.contractAddresses().sharedBridgeL2 = addresses.l2SharedDefaultBridge; + } + return { + erc20L1: this.contractAddresses().erc20BridgeL1!, + erc20L2: this.contractAddresses().erc20BridgeL2!, + wethL1: this.contractAddresses().wethBridgeL1!, + wethL2: this.contractAddresses().wethBridgeL2!, + sharedL1: this.contractAddresses().sharedBridgeL1!, + sharedL2: this.contractAddresses().sharedBridgeL2!, + }; + } + /** * Returns an estimate of the gas required for a L1 to L2 transaction. * @@ -195,14 +316,14 @@ export class RpcMethods { * @param returnFormat - The format of the return value. */ public async estimateGasL1ToL2( - transaction: Partial, + transaction: Partial, returnFormat: DataFormat = DEFAULT_RETURN_FORMAT, - ): Promise { - return format( + ): Promise { + return web3Utils.format( UintSchema, await this._send('zks_estimateGasL1ToL2', [transaction]), returnFormat, - ) as Numbers; + ) as web3Types.Numbers; } /** @@ -212,7 +333,7 @@ export class RpcMethods { * @param returnFormat - The format of the return value. */ public async getAllAccountBalances( - address: Address, + address: web3Types.Address, returnFormat: DataFormat = DEFAULT_RETURN_FORMAT, ): Promise { const res = (await this._send('zks_getAllAccountBalances', [address])) as WalletBalances; @@ -220,11 +341,11 @@ export class RpcMethods { return {}; } for (let i = 0; i < Object.keys(res).length; i++) { - res[Object.keys(res)[i]] = format( + res[Object.keys(res)[i]] = web3Utils.format( UintSchema, res[Object.keys(res)[i]], returnFormat, - ) as Numbers; + ) as web3Types.Numbers; } return res; @@ -235,12 +356,14 @@ export class RpcMethods { * * @param returnFormat - The format of the return value. */ - public async getMainContract(returnFormat: DataFormat = DEFAULT_RETURN_FORMAT): Promise
{ - return format( + public async getMainContract( + returnFormat: DataFormat = DEFAULT_RETURN_FORMAT, + ): Promise { + return web3Utils.format( AddressSchema, await this._send('zks_getMainContract', []), returnFormat, - ) as Address; + ) as web3Types.Address; } /** @@ -251,16 +374,16 @@ export class RpcMethods { * @param returnFormat - The format of the return value. */ public async getL1BatchBlockRange( - number: Numbers, + number: web3Types.Numbers, returnFormat: DataFormat = DEFAULT_RETURN_FORMAT, - ): Promise { - return format( + ): Promise { + return web3Utils.format( BytesArraySchema, await this._send('zks_getL1BatchBlockRange', [ - typeof number === 'number' ? number : Number(toNumber(number)), + typeof number === 'number' ? number : Number(web3Utils.toNumber(number)), ]), returnFormat, - ) as Bytes[]; + ) as web3Types.Bytes[]; } /** @@ -273,20 +396,20 @@ export class RpcMethods { * @param returnFormat - The format of the return value. */ public async getProof( - address: Address, + address: web3Types.Address, keys: string[], - l1BatchNumber: Numbers, + l1BatchNumber: web3Types.Numbers, returnFormat: DataFormat = DEFAULT_RETURN_FORMAT, ): Promise { const res = (await this._send('zks_getProof', [ address, keys, - typeof l1BatchNumber === 'number' ? l1BatchNumber : Number(toNumber(l1BatchNumber)), + typeof l1BatchNumber === 'number' ? l1BatchNumber : Number(web3Utils.toNumber(l1BatchNumber)), ])) as StorageProof; - const result = format(ProofSchema, res, returnFormat) as StorageProof; + const result = web3Utils.format(ProofSchema, res, returnFormat) as StorageProof; result.storageProof = []; for (let i = 0; i < res.storageProof.length; i++) { - result.storageProof[i] = format( + result.storageProof[i] = web3Utils.format( { type: 'object', properties: ProofSchema.properties.storageProof.properties, @@ -306,12 +429,12 @@ export class RpcMethods { */ public async getTestnetPaymaster( returnFormat: DataFormat = DEFAULT_RETURN_FORMAT, - ): Promise
{ - return format( + ): Promise { + return web3Utils.format( AddressSchema, await this._send('zks_getTestnetPaymaster', []), returnFormat, - ) as Address; + ) as web3Types.Address; } /** @@ -324,17 +447,19 @@ export class RpcMethods { * @param returnFormat - The format of the return value. */ public async getL2ToL1LogProof( - txHash: HexString32Bytes, - l2ToL1LogIndex?: Numbers, + txHash: web3Types.HexString32Bytes, + l2ToL1LogIndex?: web3Types.Numbers, returnFormat: DataFormat = DEFAULT_RETURN_FORMAT, ): Promise { - const params: [HexString32Bytes, number?] = [txHash]; + const params: [web3Types.HexString32Bytes, number?] = [txHash]; if (l2ToL1LogIndex) { params.push( - typeof l2ToL1LogIndex === 'number' ? l2ToL1LogIndex : Number(toNumber(l2ToL1LogIndex)), + typeof l2ToL1LogIndex === 'number' + ? l2ToL1LogIndex + : Number(web3Utils.toNumber(l2ToL1LogIndex)), ); } - return format( + return web3Utils.format( L2ToL1ProofSchema, await this._send('zks_getL2ToL1LogProof', params), returnFormat, @@ -349,10 +474,61 @@ export class RpcMethods { public async getBridgeContracts( returnFormat: DataFormat = DEFAULT_RETURN_FORMAT, ): Promise { - return format( + return web3Utils.format( BridgeAddressesSchema, await this._send('zks_getBridgeContracts', []), returnFormat, ) as BridgeAddresses; } + + /** + * Returns gas estimation for an L1 to L2 execute operation. + * + * @param transaction The transaction details. + * @param transaction.contractAddress The address of the contract. + * @param transaction.calldata The transaction call data. + * @param [transaction.caller] The caller's address. + * @param [transaction.l2Value] The current L2 gas value. + * @param [transaction.factoryDeps] An array of bytes containing contract bytecode. + * @param [transaction.gasPerPubdataByte] The current gas per byte value. + * @param [transaction.overrides] Transaction overrides including `gasLimit`, `gasPrice`, and `value`. + */ + // TODO (EVM-3): support refundRecipient for fee estimation + async estimateL1ToL2Execute( + transaction: { + contractAddress: web3Types.Address; + calldata: string; + caller?: web3Types.Address; + l2Value?: web3Types.Numbers; + factoryDeps?: web3Types.Bytes[]; + gasPerPubdataByte?: web3Types.Numbers; + overrides?: TransactionOverrides; + }, + returnFormat: DataFormat = DEFAULT_RETURN_FORMAT, + ): Promise { + transaction.gasPerPubdataByte ??= REQUIRED_L1_TO_L2_GAS_PER_PUBDATA_LIMIT; + + // 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. + transaction.caller ??= web3Accounts.create().address; + + const customData = { + gasPerPubdata: transaction.gasPerPubdataByte, + }; + if (transaction.factoryDeps) { + Object.assign(customData, { factoryDeps: transaction.factoryDeps }); + } + + return await this.estimateGasL1ToL2( + { + from: transaction.caller, + data: transaction.calldata, + to: transaction.contractAddress, + value: transaction.l2Value ? web3Utils.toHex(transaction.l2Value) : undefined, + customData, + }, + returnFormat, + ); + } } diff --git a/src/types.ts b/src/types.ts index af647f1..ec12339 100644 --- a/src/types.ts +++ b/src/types.ts @@ -5,7 +5,15 @@ export type { Bytes, HexString, Numbers } from 'web3-types'; // // TODO: // is it needed to be re-exported from web3 // import { watchTransactionForConfirmations } from 'web3-eth/lib/types/utils/watch_transaction_for_confirmations.js'; -import { Bytes, HexString, Numbers, Transaction, EIP1193Provider } from 'web3-types'; +import { + Bytes, + HexString, + Numbers, + Transaction, + EIP1193Provider, + TransactionWithSenderAPI, + TransactionReceipt, +} from 'web3-types'; import { // FeeMarketEIP1559Transaction, @@ -533,27 +541,27 @@ export interface L2ToL1Log { logIndex: number; } -// /** -// * A `TransactionRequest` is an extension of {@link ethers.TransactionRequest} with additional features for interacting -// * with zkSync Era. -// */ -// export interface TransactionRequest extends EthersTransactionRequest { -// /** The custom data for EIP712 transaction metadata. */ -// customData?: null | Eip712Meta; -// } +/** + * A `TransactionRequest` is an extension of {@link ethers.TransactionRequest} with additional features for interacting + * with zkSync Era. + */ +export declare type TransactionRequest = TransactionWithSenderAPI & { + /** The custom data for EIP712 transaction metadata. */ + customData?: null | Eip712Meta; +}; -// /** -// * Interface representation of priority op response that extends {@link ethers.TransactionResponse} and adds a function -// * that waits to commit a L1 transaction, including when given on optional confirmation number. -// */ -// export interface PriorityOpResponse extends TransactionResponse { -// /** -// * Waits for the L1 transaction to be committed, including waiting for the specified number of confirmations. -// * @param confirmation The number of confirmations to wait for. Defaults to 1. -// * @returns A promise that resolves to the transaction receipt once committed. -// */ -// waitL1Commit(confirmation?: number): Promise; -// } +/** + * Interface representation of priority op response that extends {@link ethers.TransactionResponse} and adds a function + * that waits to commit a L1 transaction, including when given on optional confirmation number. + */ +export interface PriorityOpResponse extends Transaction { + /** + * Waits for the L1 transaction to be committed, including waiting for the specified number of confirmations. + * @param confirmation The number of confirmations to wait for. Defaults to 1. + * @returns A promise that resolves to the transaction receipt once committed. + */ + waitL1Commit(confirmation?: number): Promise; +} /** A map containing accounts and their balances. */ export type BalancesMap = { [key: string]: bigint }; diff --git a/src/utils.ts b/src/utils.ts index 3177fdb..6602a89 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -11,6 +11,7 @@ import * as web3Utils from 'web3-utils'; import * as web3Accounts from 'web3-eth-accounts'; import * as web3Types from 'web3-types'; import * as web3Abi from 'web3-eth-abi'; +import * as web3Contract from 'web3-eth-contract'; import { DeploymentInfo, @@ -45,9 +46,11 @@ import { EIP1271_MAGIC_VALUE, L1_FEE_ESTIMATION_COEF_NUMERATOR, L1_FEE_ESTIMATION_COEF_DENOMINATOR, + // EIP712_TX_TYPE, + // DEFAULT_GAS_PER_PUBDATA_LIMIT, } from './constants'; -// import { RpcMethods } from './rpc.methods'; // to be used instead of the one at zksync-ethers: Provider from ./provider +import { RpcMethods } from './rpc.methods'; // to be used instead of the one at zksync-ethers: Provider from ./provider // export * from './paymaster-utils'; // export * from './smart-account-utils'; @@ -99,7 +102,7 @@ export const L1BridgeContract = new web3.Contract(IL1BridgeABI); * The web3.js Contract instance for the `IL2Bridge` interface, which is utilized for transferring ERC20 tokens from L2 to L1. * @constant */ -export const L2Bridge = new web3.Contract(IL2BridgeABI); +export const L2BridgeContract = new web3.Contract(IL2BridgeABI); /** * The web3.js Contract instance for the `INonceHolder` interface, which is utilized for managing deployment nonces. @@ -112,8 +115,12 @@ export const NonceHolderContract = new web3.Contract(INonceHolderABI); * consider adding the next few functions to web3.js: * */ -export const numberToBytes = (number: web3Types.Numbers) => - web3Utils.hexToBytes(web3Utils.numberToHex(number)); +export const toBytes = (number: web3Types.Numbers | Uint8Array) => + web3Utils.hexToBytes( + typeof number === 'number' || typeof number === 'bigint' + ? web3Utils.numberToHex(number) + : web3Utils.bytesToHex(number), + ); export function concat(bytes: web3Types.Bytes[]): string { return '0x' + bytes.map(d => web3Utils.toHex(d).substring(2)).join(''); @@ -123,9 +130,10 @@ export function contractFunctionId(value: string): string { return web3Utils.keccak256(web3Utils.utf8ToBytes(value)); } +// TODO: needs to test for the first parameter being Eip712TypedData function recoverSignerAddress( messageOrData: string | web3Types.Eip712TypedData, - signature: string | EthereumSignature, + signature: SignatureLike, ) { let message; if (typeof messageOrData !== 'string') { @@ -134,25 +142,87 @@ function recoverSignerAddress( message = messageOrData; } - const r = web3Accounts.toUint8Array( - (signature as EthereumSignature).r ?? (signature as string).slice(0, 66), - ); - const s = web3Accounts.toUint8Array( - (signature as EthereumSignature).s ?? `0x${(signature as string).slice(66, 130)}`, - ); - const v = BigInt( - (signature as EthereumSignature).v ?? - web3Utils.hexToNumber(`0x${(signature as string).slice(130, 132)}`), - ); + if (typeof signature === 'string') { + return web3Accounts.recover(message, signature); + } - const recoveredPublicKey = web3Utils.bytesToHex( - web3Accounts.ecrecover(web3Accounts.toUint8Array(message), v, r, s), - ); + const r = web3Utils.toHex(signature.r); + const s = web3Utils.toHex(signature.s); + const v = web3Utils.toHex(signature.v); - const recoveredAddress = `0x${web3Utils.keccak256(web3Utils.bytesToHex(recoveredPublicKey)).slice(-40)}`; + const recoveredAddress = web3Accounts.recover(message, v, r, s); return recoveredAddress; } +export class SignatureObject { + public r: Uint8Array; + public s: Uint8Array; + public v: bigint; + + constructor(r: Uint8Array, s: Uint8Array, v: web3Types.Numbers); + constructor(signature: string | SignatureLike); + constructor( + rOrSignature: string | Uint8Array | SignatureLike, + s?: Uint8Array, + v?: web3Types.Numbers, + ) { + if (typeof rOrSignature === 'string') { + if (rOrSignature.length !== 132) { + throw new Error('Invalid signature length'); + } + // Initialize with a single string parameter + const signature = rOrSignature as string; + this.r = web3Accounts.toUint8Array(signature.slice(0, 66)); + this.s = web3Accounts.toUint8Array(`0x${signature.slice(66, 130)}`); + this.v = BigInt(web3Utils.hexToNumber(`0x${signature.slice(130, 132)}`)); + } else if ( + (rOrSignature as EthereumSignature).r && + (rOrSignature as EthereumSignature).s && + (rOrSignature as EthereumSignature).v + ) { + const ethereumSignature = rOrSignature as EthereumSignature; + this.r = web3Utils.bytesToUint8Array(ethereumSignature.r); + this.s = web3Utils.bytesToUint8Array(ethereumSignature.s); + this.v = BigInt(ethereumSignature.v); + } else { + const signature = { r: rOrSignature as Uint8Array, s: s, v: v }; + // Initialize with individual parameters + this.r = signature.r!; + this.s = signature.s!; + this.v = BigInt(signature.v!); + } + } + + public toString() { + return `${this.r}${this.s.slice(2)}${web3Utils.toHex(this.v).slice(2)}`; + } +} + +export type SignatureLike = SignatureObject | EthereumSignature | string; + +/** + * eip-191 message prefix + */ +export const MessagePrefix: string = '\x19Ethereum Signed Message:\n'; + +/** + * Has the message according to eip-191 + * @param message - message to hash + * @returns hash of the message + */ +export function hashMessage(message: Uint8Array | string): string { + if (typeof message === 'string') { + message = web3Accounts.toUint8Array(message); + } + return web3Utils.keccak256( + concat([ + web3Accounts.toUint8Array(MessagePrefix), + web3Accounts.toUint8Array(String(message.length)), + message, + ]), + ); +} + /** * ------------------------------------------------------------ * End of the function section that would be added to web3.js @@ -383,10 +453,7 @@ export async function checkBaseCost( // * ); // * // serializedTx = "0x71f87f8080808094a61464658afeaf65cccaafd3a512b69a83b77618830f42408001a073a20167b8d23b610b058c05368174495adf7da3a4ed4a57eb6dbdeb1fafc24aa02f87530d663a0d061f69bb564d2c6fb46ae5ae776bbd4bd2a2a4478b9cd1b42a82010e9436615cf349d7f6344891b1e7ca7c72883f5dc04982c350c080c0" // */ -// export function serializeEip712( -// transaction: TransactionLike, -// signature?: ethers.SignatureLike, -// ): string { +// export function serializeEip712(transaction: TransactionLike, signature?: SignatureLike): string { // if (!transaction.chainId) { // throw Error("Transaction chainId isn't set!"); // } @@ -400,30 +467,30 @@ export async function checkBaseCost( // const maxPriorityFeePerGas = transaction.maxPriorityFeePerGas || maxFeePerGas; // const fields: any[] = [ -// numberToBytes(transaction.nonce || 0), -// numberToBytes(maxPriorityFeePerGas), -// numberToBytes(maxFeePerGas), -// numberToBytes(transaction.gasLimit || 0), +// toBytes(transaction.nonce || 0), +// toBytes(maxPriorityFeePerGas), +// toBytes(maxFeePerGas), +// toBytes(transaction.gasLimit || 0), // transaction.to ? web3Utils.toChecksumAddress(transaction.to) : '0x', -// numberToBytes(transaction.value || 0), +// toBytes(transaction.value || 0), // transaction.data || '0x', // ]; // if (signature) { -// const sig = ethers.Signature.from(signature); -// fields.push(numberToBytes(sig.yParity)); -// fields.push(numberToBytes(sig.r)); -// fields.push(numberToBytes(sig.s)); +// const sig = new SignatureObject(signature); +// fields.push(toBytes(sig.yParity)); +// fields.push(toBytes(sig.r)); +// fields.push(toBytes(sig.s)); // } else { -// fields.push(numberToBytes(transaction.chainId)); +// fields.push(toBytes(transaction.chainId)); // fields.push('0x'); // fields.push('0x'); // } -// fields.push(numberToBytes(transaction.chainId)); +// fields.push(toBytes(transaction.chainId)); // fields.push(web3Utils.toChecksumAddress(from)); // // Add meta -// fields.push(numberToBytes(meta.gasPerPubdata || DEFAULT_GAS_PER_PUBDATA_LIMIT)); +// fields.push(toBytes(meta.gasPerPubdata || DEFAULT_GAS_PER_PUBDATA_LIMIT)); // fields.push((meta.factoryDeps ?? []).map(dep => web3Utils.toHex(dep))); // if (meta.customSignature && web3Utils.bytesToUint8Array(meta.customSignature).length === 0) { @@ -603,12 +670,7 @@ export function hashBytecode(bytecode: web3Types.Bytes): Uint8Array { // } // if (!transaction.customData?.customSignature) { -// // TODO: either try to use a string or a signature object -// // const signatureStr = `${ethSignature.r}${ethSignature.s.slice(2)}${ethSignature.v.slice(2)}`; -// // or maybe try to use @noble/curves (but it does not deal with `v`): -// // import { SignatureType } from '@noble/curves'; -// // new secp256k1.Signature(ethSignature.r, ethSignature.s, ethSignature.v); -// transaction.signature = ethers.Signature.from(ethSignature); +// transaction.signature = new SignatureObject(ethSignature).toString(); // } // transaction.hash = eip712TxHash(transaction, ethSignature); @@ -743,67 +805,59 @@ export function undoL1ToL2Alias(address: string): string { return web3Utils.padLeft(web3Utils.toHex(result), 20 * 2); } -// /** -// * Returns the data needed for correct initialization of an L1 token counterpart on L2. -// * -// * @param l1TokenAddress The token address on L1. -// * @param provider The client that is able to work with contracts on a read-write basis. -// * @returns The encoded bytes which contains token name, symbol and decimals. -// */ -// export async function getERC20DefaultBridgeData( -// l1TokenAddress: string, -// context: web3.Web3Context, // or maybe use RpcMethods? -// ): Promise { -// if (isAddressEq(l1TokenAddress, LEGACY_ETH_ADDRESS)) { -// l1TokenAddress = ETH_ADDRESS_IN_CONTRACTS; -// } -// const token = IERC20__factory.connect(l1TokenAddress, context); - -// const name = isAddressEq(l1TokenAddress, ETH_ADDRESS_IN_CONTRACTS) ? 'Ether' : await token.name(); -// const symbol = isAddressEq(l1TokenAddress, ETH_ADDRESS_IN_CONTRACTS) -// ? 'ETH' -// : await token.symbol(); -// const decimals = isAddressEq(l1TokenAddress, ETH_ADDRESS_IN_CONTRACTS) -// ? 18 -// : await token.decimals(); - -// const coder = new AbiCoder(); - -// const nameBytes = coder.encode(['string'], [name]); -// const symbolBytes = coder.encode(['string'], [symbol]); -// const decimalsBytes = coder.encode(['uint256'], [decimals]); - -// return coder.encode(['bytes', 'bytes', 'bytes'], [nameBytes, symbolBytes, decimalsBytes]); -// } +/** + * Returns the data needed for correct initialization of an L1 token counterpart on L2. + * + * @param l1TokenAddress The token address on L1. + * @param provider The client that is able to work with contracts on a read-write basis. + * @returns The encoded bytes which contains token name, symbol and decimals. + */ +export async function getERC20DefaultBridgeData( + l1TokenAddress: string, + context: web3.Web3Context, // or maybe use RpcMethods? +): Promise { + if (isAddressEq(l1TokenAddress, LEGACY_ETH_ADDRESS)) { + l1TokenAddress = ETH_ADDRESS_IN_CONTRACTS; + } + const token = new web3Contract.Contract(IERC20ABI, l1TokenAddress, context); + + const name = isAddressEq(l1TokenAddress, ETH_ADDRESS_IN_CONTRACTS) + ? 'Ether' + : await token.methods.name().call(); + const symbol = isAddressEq(l1TokenAddress, ETH_ADDRESS_IN_CONTRACTS) + ? 'ETH' + : await token.methods.symbol().call(); + const decimals = isAddressEq(l1TokenAddress, ETH_ADDRESS_IN_CONTRACTS) + ? 18 + : await token.methods.decimals().call(); + + return web3Abi.encodeParameters(['string', 'string', 'uint256'], [name, symbol, decimals]); +} -// /** -// * Returns the calldata sent by an L1 ERC20 bridge to its L2 counterpart during token bridging. -// * -// * @param l1TokenAddress The token address on L1. -// * @param l1Sender The sender address on L1. -// * @param l2Receiver The recipient address on L2. -// * @param amount The gas fee for the number of tokens to bridge. -// * @param bridgeData Additional bridge data. -// * -// * @example -// * -// * -// */ -// export async function getERC20BridgeCalldata( -// l1TokenAddress: string, -// l1Sender: string, -// l2Receiver: string, -// amount: web3Types.Numbers, -// bridgeData: web3Types.Bytes, -// ): Promise { -// return L2_BRIDGE_ABI.encodeFunctionData('finalizeDeposit', [ -// l1Sender, -// l2Receiver, -// l1TokenAddress, -// amount, -// bridgeData, -// ]); -// } +/** + * Returns the calldata sent by an L1 ERC20 bridge to its L2 counterpart during token bridging. + * + * @param l1TokenAddress The token address on L1. + * @param l1Sender The sender address on L1. + * @param l2Receiver The recipient address on L2. + * @param amount The gas fee for the number of tokens to bridge. + * @param bridgeData Additional bridge data. + * + * @example + * + * + */ +export async function getERC20BridgeCalldata( + l1TokenAddress: string, + l1Sender: string, + l2Receiver: string, + amount: web3Types.Numbers, + bridgeData: web3Types.Bytes, +): Promise { + return L2BridgeContract.methods + .finalizeDeposit(l1Sender, l2Receiver, l1TokenAddress, amount, bridgeData) + .encodeABI(); +} /** * Validates signatures from non-contract account addresses (EOA). @@ -834,7 +888,7 @@ export function undoL1ToL2Alias(address: string): string { function isECDSASignatureCorrect( address: string, msgHash: string, - signature: EthereumSignature, + signature: SignatureLike, ): boolean { try { return isAddressEq(address, recoverSignerAddress(msgHash, signature)); @@ -865,7 +919,7 @@ async function isEIP1271SignatureCorrect( context: web3.Web3Context, // or maybe use RpcMethods? address: string, msgHash: string, - signature: EthereumSignature, + signature: SignatureLike, ): Promise { const accountContract = new web3.Contract(IERC1271ABI, address, context); @@ -891,7 +945,7 @@ async function isSignatureCorrect( context: web3.Web3Context, // or maybe use RpcMethods? address: string, msgHash: string, - signature: EthereumSignature, + signature: SignatureLike, ): Promise { const code = await web3.eth.getCode(context, address, undefined, web3Types.DEFAULT_RETURN_FORMAT); const isContractAccount = web3Utils.bytesToUint8Array(code).length !== 0; @@ -903,151 +957,159 @@ async function isSignatureCorrect( } } -// /** -// * Returns whether the account abstraction message signature is correct. -// * Signature can be created using EIP1271 or ECDSA. -// * -// * @param provider The provider. -// * @param address The sender address. -// * @param message The hash of the message. -// * @param signature The Ethers signature. -// * -// * @example -// * -// * import { Wallet, utils, Provider } from "zksync-ethers"; -// * -// * const ADDRESS = ""; -// * const PRIVATE_KEY = ""; -// * const context = Provider.getDefaultProvider(types.Network.Sepolia); -// * -// * const message = "Hello, world!"; -// * const signature = await new Wallet(PRIVATE_KEY).signMessage(message); -// * // ethers.Wallet can be used as well -// * // const signature = await new ethers.Wallet(PRIVATE_KEY).signMessage(message); -// * -// * const isValidSignature = await utils.isMessageSignatureCorrect(context, ADDRESS, message, signature); -// * // isValidSignature = true -// */ -// export async function isMessageSignatureCorrect( -// context: web3.Web3Context, // or maybe use RpcMethods? -// address: string, -// message: Uint8Array | string, -// signature: SignatureLike, -// ): Promise { -// // TODO: needs to implement this (similar to web3Abi.getEncodedEip712Data but for stings and Uint8Array) -// const msgHash = ethers.getMessage(message); -// return await isSignatureCorrect(context, address, msgHash, signature); -// } +/** + * Returns whether the account abstraction message signature is correct. + * Signature can be created using EIP1271 or ECDSA. + * + * @param provider The provider. + * @param address The sender address. + * @param message The hash of the message. + * @param signature The Ethers signature. + * + * @example + * + * import { Wallet, utils, Provider } from "zksync-ethers"; + * + * const ADDRESS = ""; + * const PRIVATE_KEY = ""; + * const context = Provider.getDefaultProvider(types.Network.Sepolia); + * + * const message = "Hello, world!"; + * const signature = await new Wallet(PRIVATE_KEY).signMessage(message); + * // ethers.Wallet can be used as well + * // const signature = await new ethers.Wallet(PRIVATE_KEY).signMessage(message); + * + * const isValidSignature = await utils.isMessageSignatureCorrect(context, ADDRESS, message, signature); + * // isValidSignature = true + */ +export async function isMessageSignatureCorrect( + context: web3.Web3Context, // or maybe use RpcMethods? + address: string, + message: Uint8Array | string, + signature: SignatureLike, +): Promise { + const msgHash = web3Accounts.hashMessage( + typeof message === 'string' ? message : web3Utils.bytesToHex(message), + ); + return await isSignatureCorrect(context, address, msgHash, signature); +} -// /** -// * Returns whether the account abstraction EIP712 signature is correct. -// * -// * @param context The web3 context. -// * @param address The sender address. -// * @param domain The domain data. -// * @param types A map of records pointing from field name to field type. -// * @param value A single record value. -// * @param signature The Ethers signature. -// * -// * @example -// * -// * import { Wallet, utils, Provider, EIP712Signer } from "zksync-ethers"; -// * -// * const ADDRESS = ""; -// * const PRIVATE_KEY = ""; -// * const context = Provider.getDefaultProvider(types.Network.Sepolia); -// * -// * const tx: types.TransactionRequest = { -// * type: 113, -// * chainId: 270, -// * from: ADDRESS, -// * to: "0xa61464658AfeAf65CccaaFD3a512b69A83B77618", -// * value: BigInt(7_000_000), -// * }; -// * -// * const eip712Signer = new EIP712Signer( -// * new Wallet(PRIVATE_KEY), // or new ethers.Wallet(PRIVATE_KEY), -// * Number((await context.getNetwork()).chainId) -// * ); -// * -// * const signature = await eip712Signer.sign(tx); -// * -// * const isValidSignature = await utils.isTypedDataSignatureCorrect(context, ADDRESS, await eip712Signer.getDomain(), utils.EIP712_TYPES, EIP712Signer.getSignInput(tx), signature); -// * // isValidSignature = true -// */ -// export async function isTypedDataSignatureCorrect( -// context: web3.Web3Context, // or maybe use RpcMethods? -// address: string, -// domain: ethers.TypedDataDomain, -// types: Record>, -// value: Record, -// signature: EthereumSignature, -// ): Promise { -// const msgHash = ethers.TypedDataEncoder.hash(domain, types, value); -// return await isSignatureCorrect(context, address, msgHash, signature); -// } +/** + * Returns whether the account abstraction EIP712 signature is correct. + * + * @param context The web3 context. + * @param address The sender address. + * @param domain The domain data. + * @param types A map of records pointing from field name to field type. + * @param value A single record value. + * @param signature The Ethers signature. + * + * @example + * + * import { Wallet, utils, constants, Provider, EIP712Signer } from "zksync-ethers"; + * + * const ADDRESS = ""; + * const PRIVATE_KEY = ""; + * const context = Provider.getDefaultProvider(types.Network.Sepolia); + * + * const tx: types.TransactionRequest = { + * type: 113, + * chainId: 270, + * from: ADDRESS, + * to: "0xa61464658AfeAf65CccaaFD3a512b69A83B77618", + * value: BigInt(7_000_000), + * }; + * + * const eip712Signer = new EIP712Signer( + * new Wallet(PRIVATE_KEY), // or new ethers.Wallet(PRIVATE_KEY), + * Number((await context.getNetwork()).chainId) + * ); + * + * const signature = await eip712Signer.sign(tx); + * + * const isValidSignature = await utils.isTypedDataSignatureCorrect(context, ADDRESS, await eip712Signer.getDomain(), constants.EIP712_TYPES, 'Transaction', EIP712Signer.getSignInput(tx), signature); + * // isValidSignature = true + */ +export async function isTypedDataSignatureCorrect( + context: web3.Web3Context, // or maybe use RpcMethods? + address: string, + domain: Extract, + types: Extract, + primaryType: Extract, + value: Record, + signature: EthereumSignature, +): Promise { + const data: web3Types.Eip712TypedData = { + domain, + types, + primaryType, + message: value, + }; + const msgHash = web3Abi.getEncodedEip712Data(data); + return await isSignatureCorrect(context, address, msgHash, signature); +} -// /** -// * 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 -// * -// * -// */ -// export async function estimateDefaultBridgeDepositL2Gas( -// providerL1: Web3Eth, -// providerL2: Provider, -// token: web3.Address, -// amount: web3Types.Numbers, -// to: web3.Address, -// from?: web3.Address, -// gasPerPubdataByte?: web3Types.Numbers, -// ): 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 ??= web3Accounts.create().address; -// if (await providerL2.isBaseToken(token)) { -// return await providerL2.estimateL1ToL2Execute({ -// contractAddress: to, -// gasPerPubdataByte: gasPerPubdataByte, -// caller: from, -// calldata: '0x', -// l2Value: amount, -// }); -// } else { -// const bridgeAddresses = await providerL2.getDefaultBridgeAddresses(); - -// const value = 0; -// const l1BridgeAddress = bridgeAddresses.sharedL1; -// const l2BridgeAddress = bridgeAddresses.sharedL2; -// const bridgeData = await getERC20DefaultBridgeData(token, providerL1); - -// return await estimateCustomBridgeDepositL2Gas( -// providerL2, -// 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 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 + * + * + */ +export async function estimateDefaultBridgeDepositL2Gas( + providerL1: web3.Web3Eth, + providerL2: RpcMethods, + token: web3.Address, + amount: web3Types.Numbers, + to: web3.Address, + from?: web3.Address, + gasPerPubdataByte?: web3Types.Numbers, +): 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 ??= web3Accounts.create().address; + if (await providerL2.isBaseToken(token)) { + return await providerL2.estimateL1ToL2Execute({ + contractAddress: to, + gasPerPubdataByte: gasPerPubdataByte, + caller: from, + calldata: '0x', + l2Value: amount, + }); + } else { + const bridgeAddresses = await providerL2.getDefaultBridgeAddresses(); + + const value = 0; + const l1BridgeAddress = bridgeAddresses.sharedL1; + const l2BridgeAddress = bridgeAddresses.sharedL2; + const bridgeData = await getERC20DefaultBridgeData(token, providerL1); + + return await estimateCustomBridgeDepositL2Gas( + providerL2, + l1BridgeAddress, + l2BridgeAddress, + isAddressEq(token, LEGACY_ETH_ADDRESS) ? ETH_ADDRESS_IN_CONTRACTS : token, + amount, + to, + bridgeData, + from, + gasPerPubdataByte, + value, + ); + } +} /** * Scales the provided gas limit using a coefficient to ensure acceptance of L1->L2 transactions. @@ -1069,48 +1131,48 @@ export function scaleGasLimit(gasLimit: bigint): bigint { ); } -// /** -// * 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 -// * -// * -// */ -// export async function estimateCustomBridgeDepositL2Gas( -// providerL2: RpcMethods, -// l1BridgeAddress: web3.Address, -// l2BridgeAddress: web3.Address, -// token: web3.Address, -// amount: web3Types.Numbers, -// to: web3.Address, -// bridgeData: web3Types.Bytes, -// from: web3.Address, -// gasPerPubdataByte?: web3Types.Numbers, -// l2Value?: web3Types.Numbers, -// ): Promise { -// const calldata = await getERC20BridgeCalldata(token, from, to, amount, bridgeData); -// return await providerL2.estimateL1ToL2Execute({ -// caller: applyL1ToL2Alias(l1BridgeAddress), -// contractAddress: l2BridgeAddress, -// gasPerPubdataByte: gasPerPubdataByte, -// calldata: calldata, -// l2Value: l2Value, -// }); -// } +/** + * 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 + * + * + */ +export async function estimateCustomBridgeDepositL2Gas( + providerL2: RpcMethods, + l1BridgeAddress: web3.Address, + l2BridgeAddress: web3.Address, + token: web3.Address, + amount: web3Types.Numbers, + to: web3.Address, + bridgeData: web3Types.Bytes, + from: web3.Address, + gasPerPubdataByte?: web3Types.Numbers, + l2Value?: web3Types.Numbers, +): Promise { + const calldata = await getERC20BridgeCalldata(token, from, to, amount, bridgeData); + return await providerL2.estimateL1ToL2Execute({ + caller: applyL1ToL2Alias(l1BridgeAddress), + contractAddress: l2BridgeAddress, + gasPerPubdataByte: gasPerPubdataByte, + calldata: calldata, + l2Value: l2Value, + }); +} /** * Creates a JSON string from an object, including support for serializing bigint types. From 6fb6fdd5e74ad12a6c54c07add4cb6e1d736da38 Mon Sep 17 00:00:00 2001 From: Oleksii Kosynskyi Date: Tue, 4 Jun 2024 13:27:41 -0400 Subject: [PATCH 10/30] update web3js version --- .gitignore | 1 + package.json | 2 +- yarn.lock | 204 ++++++++++++++++++--------------------------------- 3 files changed, 72 insertions(+), 135 deletions(-) diff --git a/.gitignore b/.gitignore index 0d57b0f..6db120b 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ node_modules lib .history +.idea diff --git a/package.json b/package.json index 2c31b7c..1bc3559 100644 --- a/package.json +++ b/package.json @@ -39,7 +39,7 @@ "ts-jest": "^29.1.2", "ts-node": "^10.9.2", "typescript": "^5.3.3", - "web3": "^4.4.0" + "web3": "^4.9.0" }, "peerDependencies": { "web3": ">= 4.0.3" diff --git a/yarn.lock b/yarn.lock index 05168b1..57f63e3 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5689,7 +5689,7 @@ walker@^1.0.8: dependencies: makeerror "1.0.12" -web3-core@^4.3.0, web3-core@^4.3.1: +web3-core@^4.3.0: version "4.3.1" resolved "https://registry.yarnpkg.com/web3-core/-/web3-core-4.3.1.tgz#5c3b5b59f1e31537a64237caa5fd83f5ffd7b0f3" integrity sha512-xa3w5n/ESxp5HIbrwsYBhpAPx2KI5LprjRFEtRwP0GpqqhTcCSMMYoyItRqQQ+k9YnB0PoFpWJfJI6Qn5x8YUQ== @@ -5704,22 +5704,6 @@ web3-core@^4.3.0, web3-core@^4.3.1: optionalDependencies: web3-providers-ipc "^4.0.7" -web3-core@^4.3.2: - version "4.3.2" - resolved "https://registry.yarnpkg.com/web3-core/-/web3-core-4.3.2.tgz#f24b11d6a57dee527de8d42c89de2a439f0c4bed" - integrity sha512-uIMVd/j4BgOnwfpY8ZT+QKubOyM4xohEhFZXz9xB8wimXWMMlYVlIK/TbfHqFolS9uOerdSGhsMbcK9lETae8g== - dependencies: - web3-errors "^1.1.4" - web3-eth-accounts "^4.1.0" - web3-eth-iban "^4.0.7" - web3-providers-http "^4.1.0" - web3-providers-ws "^4.0.7" - web3-types "^1.3.1" - web3-utils "^4.1.0" - web3-validator "^2.0.3" - optionalDependencies: - web3-providers-ipc "^4.0.7" - web3-core@^4.4.0: version "4.4.0" resolved "https://registry.yarnpkg.com/web3-core/-/web3-core-4.4.0.tgz#f2cd48aecda5ec34170edf7470001f90fdea1dc6" @@ -5761,17 +5745,6 @@ web3-eth-abi@^4.1.4: web3-utils "^4.0.7" web3-validator "^2.0.3" -web3-eth-abi@^4.2.0: - version "4.2.0" - resolved "https://registry.yarnpkg.com/web3-eth-abi/-/web3-eth-abi-4.2.0.tgz#398d415e7783442d06fb7939e40ce3de7a3f04e9" - integrity sha512-x7dUCmk6th+5N63s5kUusoNtsDJKUUQgl9+jECvGTBOTiyHe/V6aOY0120FUjaAGaapOnR7BImQdhqHv6yT2YQ== - dependencies: - abitype "0.7.1" - web3-errors "^1.1.4" - web3-types "^1.3.1" - web3-utils "^4.1.1" - web3-validator "^2.0.4" - web3-eth-abi@^4.2.2: version "4.2.2" resolved "https://registry.yarnpkg.com/web3-eth-abi/-/web3-eth-abi-4.2.2.tgz#d7592e2cc113fd34da3fb4c933537ddf8639d9b2" @@ -5796,19 +5769,6 @@ web3-eth-accounts@^4.1.0: web3-utils "^4.0.7" web3-validator "^2.0.3" -web3-eth-accounts@^4.1.1: - version "4.1.1" - resolved "https://registry.yarnpkg.com/web3-eth-accounts/-/web3-eth-accounts-4.1.1.tgz#55225e5510b961e1cacb4eccc996544998e907fc" - integrity sha512-9JqhRi1YhO1hQOEmmBHgEGsME/B1FHMxpA/AK3vhpvQ8QeP6KbJW+cForTLfPpUbkmPxnRunG4PNNaETNlZfrA== - dependencies: - "@ethereumjs/rlp" "^4.0.1" - crc-32 "^1.2.2" - ethereum-cryptography "^2.0.0" - web3-errors "^1.1.4" - web3-types "^1.3.1" - web3-utils "^4.1.1" - web3-validator "^2.0.4" - web3-eth-accounts@^4.1.2: version "4.1.2" resolved "https://registry.yarnpkg.com/web3-eth-accounts/-/web3-eth-accounts-4.1.2.tgz#652d6e3daf4d6cb3fe67cec6a878e768f6e8b8e8" @@ -5822,46 +5782,33 @@ web3-eth-accounts@^4.1.2: web3-utils "^4.2.3" web3-validator "^2.0.5" -web3-eth-contract@^4.1.2: - version "4.1.3" - resolved "https://registry.yarnpkg.com/web3-eth-contract/-/web3-eth-contract-4.1.3.tgz#15dd4c978eaf0d8f894b2c1f3e9f94edd29ff57c" - integrity sha512-F6e3eyetUDiNOb78EDVJtNOb0H1GPz3xAZH8edSfYdhaxI9tTutP2V3p++kh2ZJ/RrdE2+xil7H/nPLgHymBvg== - dependencies: - web3-core "^4.3.1" - web3-errors "^1.1.4" - web3-eth "^4.3.1" - web3-eth-abi "^4.1.4" - web3-types "^1.3.1" - web3-utils "^4.0.7" - web3-validator "^2.0.3" - -web3-eth-contract@^4.2.0: - version "4.2.0" - resolved "https://registry.yarnpkg.com/web3-eth-contract/-/web3-eth-contract-4.2.0.tgz#73f70b19217cd4854211c05846f0c841763b3b29" - integrity sha512-K7bUypsomTs8/Oa0Lgvq5plsSB5afgKJ79NMuXxvC5jfV+AxNrQyKcr5Vd5yEGNqrdQuIPduGQa8DpuY+rMe1g== +web3-eth-contract@^4.5.0: + version "4.5.0" + resolved "https://registry.yarnpkg.com/web3-eth-contract/-/web3-eth-contract-4.5.0.tgz#954c9cf4c055f3f2c33189bdd69093a8e20a569a" + integrity sha512-AX6OiDrIryz/T28k9Xz0gXpUrlOUjcooEgGluu2s5dFDWCPM/zlN5RsUZlXZiXpQyj52VCUy5+bkvu3yDPA4fg== dependencies: - web3-core "^4.3.2" - web3-errors "^1.1.4" - web3-eth "^4.4.0" - web3-eth-abi "^4.2.0" - web3-types "^1.3.1" - web3-utils "^4.1.1" - web3-validator "^2.0.4" + web3-core "^4.4.0" + web3-errors "^1.2.0" + web3-eth "^4.7.0" + web3-eth-abi "^4.2.2" + web3-types "^1.6.0" + web3-utils "^4.3.0" + web3-validator "^2.0.6" -web3-eth-ens@^4.0.8: - version "4.0.8" - resolved "https://registry.yarnpkg.com/web3-eth-ens/-/web3-eth-ens-4.0.8.tgz#f4e0a018ce6cc69e37007ee92063867feb5994f0" - integrity sha512-nj0JfeD45BbzVJcVYpUJnSo8iwDcY9CQ7CZhhIVVOFjvpMAPw0zEwjTvZEIQyCW61OoDG9xcBzwxe2tZoYhMRw== +web3-eth-ens@^4.3.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/web3-eth-ens/-/web3-eth-ens-4.3.0.tgz#f44f279a4a07eae36e3de8384989ed069201a795" + integrity sha512-QpiKT9GqJouH5kEI/pRFprh88YPCtbht2Ym6rrklZ+VoWl9D+wLfbwvW7Aox349FS7k0UX2qVins5tVNLJ5GCQ== dependencies: "@adraffy/ens-normalize" "^1.8.8" - web3-core "^4.3.0" - web3-errors "^1.1.3" - web3-eth "^4.3.1" - web3-eth-contract "^4.1.2" - web3-net "^4.0.7" - web3-types "^1.3.0" - web3-utils "^4.0.7" - web3-validator "^2.0.3" + web3-core "^4.4.0" + web3-errors "^1.2.0" + web3-eth "^4.7.0" + web3-eth-contract "^4.5.0" + web3-net "^4.1.0" + web3-types "^1.6.0" + web3-utils "^4.3.0" + web3-validator "^2.0.6" web3-eth-iban@^4.0.7: version "4.0.7" @@ -5902,22 +5849,22 @@ web3-eth@^4.3.1: web3-utils "^4.0.7" web3-validator "^2.0.3" -web3-eth@^4.4.0: - version "4.4.0" - resolved "https://registry.yarnpkg.com/web3-eth/-/web3-eth-4.4.0.tgz#755c34a769109836d122a53b33814d63f9ec5a65" - integrity sha512-HswKdzF44wUrciRAtEJaml9O7rDYDxElHmFs+27WcO3nel2zku+n0xs4e2ZAehfrCZ+05/y7TgnOZnaDU8Fb/A== +web3-eth@^4.7.0: + version "4.7.0" + resolved "https://registry.yarnpkg.com/web3-eth/-/web3-eth-4.7.0.tgz#7d18815c7a79c200552bd0df8d3127f7532b3cc2" + integrity sha512-gqlWq4Xjz+yKL2MdxQ+BgR3F4CRo4AXWDXzftb3LDzvauEfjk/yRyoxkMSK4S9RIG96ylRImS172cV6cYzcukw== dependencies: setimmediate "^1.0.5" - web3-core "^4.3.2" - web3-errors "^1.1.4" - web3-eth-abi "^4.2.0" - web3-eth-accounts "^4.1.1" - web3-net "^4.0.7" + web3-core "^4.4.0" + web3-errors "^1.2.0" + web3-eth-abi "^4.2.2" + web3-eth-accounts "^4.1.2" + web3-net "^4.1.0" web3-providers-ws "^4.0.7" - web3-rpc-methods "^1.1.4" - web3-types "^1.3.1" - web3-utils "^4.1.1" - web3-validator "^2.0.4" + web3-rpc-methods "^1.3.0" + web3-types "^1.6.0" + web3-utils "^4.3.0" + web3-validator "^2.0.6" web3-net@^4.0.7: version "4.0.7" @@ -5929,6 +5876,16 @@ web3-net@^4.0.7: web3-types "^1.3.0" web3-utils "^4.0.7" +web3-net@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/web3-net/-/web3-net-4.1.0.tgz#db7bde675e58b153339e4f149f29ec0410d6bab2" + integrity sha512-WWmfvHVIXWEoBDWdgKNYKN8rAy6SgluZ0abyRyXOL3ESr7ym7pKWbfP4fjApIHlYTh8tNqkrdPfM4Dyi6CA0SA== + dependencies: + web3-core "^4.4.0" + web3-rpc-methods "^1.3.0" + web3-types "^1.6.0" + web3-utils "^4.3.0" + web3-providers-http@^4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/web3-providers-http/-/web3-providers-http-4.1.0.tgz#8d7afda67d1d8542ca85b30f60a3d1fe1993b561" @@ -5969,14 +5926,14 @@ web3-rpc-methods@^1.1.3: web3-types "^1.3.0" web3-validator "^2.0.3" -web3-rpc-methods@^1.1.4: - version "1.1.4" - resolved "https://registry.yarnpkg.com/web3-rpc-methods/-/web3-rpc-methods-1.1.4.tgz#0b478e38231d3f3260ac504307c6dc4059af6fda" - integrity sha512-LTFNg4LFaeU8K9ecuT8fHDp/LOXyxCneeZjCrRYIW1u82Ly52SrY55FIzMIISGoG/iT5Wh7UiHOB3CQsWLBmbQ== +web3-rpc-methods@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/web3-rpc-methods/-/web3-rpc-methods-1.3.0.tgz#d5ee299a69389d63822d354ddee2c6a121a6f670" + integrity sha512-/CHmzGN+IYgdBOme7PdqzF+FNeMleefzqs0LVOduncSaqsppeOEoskLXb2anSpzmQAP3xZJPaTrkQPWSJMORig== dependencies: - web3-core "^4.3.2" - web3-types "^1.3.1" - web3-validator "^2.0.3" + web3-core "^4.4.0" + web3-types "^1.6.0" + web3-validator "^2.0.6" web3-types@^1.3.0, web3-types@^1.3.1: version "1.3.1" @@ -5998,16 +5955,6 @@ web3-utils@^4.0.7: web3-types "^1.3.0" web3-validator "^2.0.3" -web3-utils@^4.1.0, web3-utils@^4.1.1: - version "4.1.1" - resolved "https://registry.yarnpkg.com/web3-utils/-/web3-utils-4.1.1.tgz#88c0fe404abc3f038b7318f3a31141676cefeb63" - integrity sha512-5AOmLKH6QuwHunLCNdVFlPSDE+T88bJYRQP+HWYoKNbI4STALCYQiJvj7LXE+Ed6cPfqANaK/LwKNbMPLCPFwA== - dependencies: - ethereum-cryptography "^2.0.0" - web3-errors "^1.1.4" - web3-types "^1.3.1" - web3-validator "^2.0.4" - web3-utils@^4.2.3, web3-utils@^4.3.0: version "4.3.0" resolved "https://registry.yarnpkg.com/web3-utils/-/web3-utils-4.3.0.tgz#c18918f0d692f745d622d22172406f6102528860" @@ -6030,17 +5977,6 @@ web3-validator@^2.0.3: web3-types "^1.3.0" zod "^3.21.4" -web3-validator@^2.0.4: - version "2.0.4" - resolved "https://registry.yarnpkg.com/web3-validator/-/web3-validator-2.0.4.tgz#66f34c94f21a3c94d0dc2a2d30deb8a379825d38" - integrity sha512-qRxVePwdW+SByOmTpDZFWHIUAa7PswvxNszrOua6BoGqAhERo5oJZBN+EbWtK/+O+ApNxt5FR3nCPmiZldiOQA== - dependencies: - ethereum-cryptography "^2.0.0" - util "^0.12.5" - web3-errors "^1.1.4" - web3-types "^1.3.1" - zod "^3.21.4" - web3-validator@^2.0.5, web3-validator@^2.0.6: version "2.0.6" resolved "https://registry.yarnpkg.com/web3-validator/-/web3-validator-2.0.6.tgz#a0cdaa39e1d1708ece5fae155b034e29d6a19248" @@ -6052,27 +5988,27 @@ web3-validator@^2.0.5, web3-validator@^2.0.6: web3-types "^1.6.0" zod "^3.21.4" -web3@^4.4.0: - version "4.4.0" - resolved "https://registry.yarnpkg.com/web3/-/web3-4.4.0.tgz#83e5906675608adf9a14841f257e441c9154a8c7" - integrity sha512-WcFA3RgE+cyM96MHLPs5Ec155nkYQuwhjk3JIhxcy6J1QrZK2zvKGzTQCCYHe3JhraduNTMbWy+EeNNGGji9+Q== +web3@^4.9.0: + version "4.9.0" + resolved "https://registry.yarnpkg.com/web3/-/web3-4.9.0.tgz#2d18f6a48f366601aef32308260542ed14bd2b1b" + integrity sha512-O0R90ijjyqUlG1Wk3SXqfYMU1ZGJvLCAF/WfSg/isDz/0Fkpqxoj893wauZ+ieRvTXITlbQHVXGfpp8qrhWZ1g== dependencies: - web3-core "^4.3.2" - web3-errors "^1.1.4" - web3-eth "^4.4.0" - web3-eth-abi "^4.2.0" - web3-eth-accounts "^4.1.1" - web3-eth-contract "^4.2.0" - web3-eth-ens "^4.0.8" + web3-core "^4.4.0" + web3-errors "^1.2.0" + web3-eth "^4.7.0" + web3-eth-abi "^4.2.2" + web3-eth-accounts "^4.1.2" + web3-eth-contract "^4.5.0" + web3-eth-ens "^4.3.0" web3-eth-iban "^4.0.7" web3-eth-personal "^4.0.8" - web3-net "^4.0.7" + web3-net "^4.1.0" web3-providers-http "^4.1.0" web3-providers-ws "^4.0.7" - web3-rpc-methods "^1.1.4" - web3-types "^1.3.1" - web3-utils "^4.1.1" - web3-validator "^2.0.4" + web3-rpc-methods "^1.3.0" + web3-types "^1.6.0" + web3-utils "^4.3.0" + web3-validator "^2.0.6" webidl-conversions@^3.0.0: version "3.0.1" From 3dcaa3d966baaf4640db1fb7560dd776bec5b726 Mon Sep 17 00:00:00 2001 From: Muhammad-Altabba <24407834+Muhammad-Altabba@users.noreply.github.com> Date: Mon, 3 Jun 2024 17:31:49 +0200 Subject: [PATCH 11/30] fix and test the signature validation function --- src/utils.ts | 65 ++++++++++++++++++++++++----------------- test/unit/utils.test.ts | 19 ++++++++++++ 2 files changed, 57 insertions(+), 27 deletions(-) diff --git a/src/utils.ts b/src/utils.ts index 6602a89..e13d6d1 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -867,31 +867,30 @@ export async function getERC20BridgeCalldata( * Called from {@link isSignatureCorrect} for non-contract account addresses. * * @param address The address which signs the `msgHash`. - * @param msgHash The hash of the message. + * @param message The message which its hash had been signed early. * @param signature The Ethers signature. * * @example * - * import { Wallet, utils } from "zksync-ethers"; - * - * const ADDRESS = ""; - * const PRIVATE_KEY = ""; + * import * as web3Accounts from 'web3-eth-accounts'; + * + * const wallet = web3Accounts.create(); + * const ADDRESS = wallet.address; + * const PRIVATE_KEY = wallet.privateKey; * * const message = "Hello, world!"; - * const signature = await new Wallet(PRIVATE_KEY).signMessage(message); - * // ethers.Wallet can be used as well - * // const signature = await new ethers.Wallet(PRIVATE_KEY).signMessage(message); * + * const signature = web3Accounts.sign(message, PRIVATE_KEY).signature; * const isValidSignature = await utils.isECDSASignatureCorrect(ADDRESS, message, signature); * // isValidSignature = true */ function isECDSASignatureCorrect( address: string, - msgHash: string, + message: string, signature: SignatureLike, ): boolean { try { - return isAddressEq(address, recoverSignerAddress(msgHash, signature)); + return isAddressEq(address, recoverSignerAddress(message, signature)); } catch { // In case ECDSA signature verification has thrown an error, // we simply consider the signature as incorrect. @@ -938,21 +937,32 @@ async function isEIP1271SignatureCorrect( * * @param context The web3 context. * @param address The sender address. - * @param msgHash The hash of the message. + * @param message The message which its hash had been signed early. * @param signature The Ethers signature. */ async function isSignatureCorrect( context: web3.Web3Context, // or maybe use RpcMethods? address: string, - msgHash: string, + message: string, signature: SignatureLike, ): Promise { - const code = await web3.eth.getCode(context, address, undefined, web3Types.DEFAULT_RETURN_FORMAT); - const isContractAccount = web3Utils.bytesToUint8Array(code).length !== 0; + let isContractAccount; + if (context.provider) { + const code = await web3.eth.getCode( + context, + address, + undefined, + web3Types.DEFAULT_RETURN_FORMAT, + ); + isContractAccount = web3Utils.bytesToUint8Array(code).length !== 0; + } if (!isContractAccount) { - return isECDSASignatureCorrect(address, msgHash, signature); + return isECDSASignatureCorrect(address, message, signature); } else { + const msgHash = web3Accounts.hashMessage( + typeof message === 'string' ? message : web3Utils.bytesToHex(message), + ); return await isEIP1271SignatureCorrect(context, address, msgHash, signature); } } @@ -968,18 +978,22 @@ async function isSignatureCorrect( * * @example * - * import { Wallet, utils, Provider } from "zksync-ethers"; + * import { Web3 } from 'web3'; + * import * as web3Accounts from 'web3-eth-accounts'; * - * const ADDRESS = ""; - * const PRIVATE_KEY = ""; - * const context = Provider.getDefaultProvider(types.Network.Sepolia); + * const wallet = web3Accounts.create(); + * const ADDRESS = wallet.address; + * const PRIVATE_KEY = wallet.privateKey; + * const context = new Web3('some-rpc-url'); * * const message = "Hello, world!"; * const signature = await new Wallet(PRIVATE_KEY).signMessage(message); - * // ethers.Wallet can be used as well - * // const signature = await new ethers.Wallet(PRIVATE_KEY).signMessage(message); - * - * const isValidSignature = await utils.isMessageSignatureCorrect(context, ADDRESS, message, signature); + * const isValidSignature = await utils.isMessageSignatureCorrect( + * web3, + * ADDRESS, + * message, + * signature, + * ); * // isValidSignature = true */ export async function isMessageSignatureCorrect( @@ -988,10 +1002,7 @@ export async function isMessageSignatureCorrect( message: Uint8Array | string, signature: SignatureLike, ): Promise { - const msgHash = web3Accounts.hashMessage( - typeof message === 'string' ? message : web3Utils.bytesToHex(message), - ); - return await isSignatureCorrect(context, address, msgHash, signature); + return await isSignatureCorrect(context, address, web3Utils.toHex(message), signature); } /** diff --git a/test/unit/utils.test.ts b/test/unit/utils.test.ts index 686865b..291a307 100644 --- a/test/unit/utils.test.ts +++ b/test/unit/utils.test.ts @@ -1,3 +1,6 @@ +import { Web3 } from 'web3'; +import * as web3Accounts from 'web3-eth-accounts'; + import { // types, utils, @@ -277,4 +280,20 @@ describe('utils', () => { // expect(result).toEqual(tx); // }); // }); + describe('#isMessageSignatureCorrect()', () => { + it.only('should return true if signature made by a private key was correct', async () => { + const wallet = web3Accounts.create(); + const ADDRESS = wallet.address; + const message = 'Hello, world!'; + const signature = wallet.sign(message).signature; + const web3 = new Web3(); + const isValidSignature = await utils.isMessageSignatureCorrect( + web3, + ADDRESS, + message, + signature, + ); + expect(isValidSignature).toBe(true); + }); + }); }); From 1610afa93cad92c420c2699671bacd64a4675b74 Mon Sep 17 00:00:00 2001 From: Muhammad-Altabba <24407834+Muhammad-Altabba@users.noreply.github.com> Date: Thu, 6 Jun 2024 14:27:43 +0200 Subject: [PATCH 12/30] fix and add a test case but still need investigation --- src/constants.ts | 33 +++++++++++--------- src/utils.ts | 36 +++++++++++----------- test/unit/utils.test.ts | 68 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 106 insertions(+), 31 deletions(-) diff --git a/src/constants.ts b/src/constants.ts index 133c88f..bacbace 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -154,19 +154,24 @@ export const REQUIRED_L1_TO_L2_GAS_PER_PUBDATA_LIMIT = 800; * All typed data conforming to the EIP712 standard within zkSync Era. */ export const EIP712_TYPES = { + EIP712Domain: [ + { name: 'name', type: 'string' }, + { name: 'version', type: 'string' }, + { name: 'chainId', type: 'uint256' }, + ], Transaction: [ - {name: 'txType', type: 'uint256'}, - {name: 'from', type: 'uint256'}, - {name: 'to', type: 'uint256'}, - {name: 'gasLimit', type: 'uint256'}, - {name: 'gasPerPubdataByteLimit', type: 'uint256'}, - {name: 'maxFeePerGas', type: 'uint256'}, - {name: 'maxPriorityFeePerGas', type: 'uint256'}, - {name: 'paymaster', type: 'uint256'}, - {name: 'nonce', type: 'uint256'}, - {name: 'value', type: 'uint256'}, - {name: 'data', type: 'bytes'}, - {name: 'factoryDeps', type: 'bytes32[]'}, - {name: 'paymasterInput', type: 'bytes'}, + { name: 'txType', type: 'uint256' }, + { name: 'from', type: 'uint256' }, + { name: 'to', type: 'uint256' }, + { name: 'gasLimit', type: 'uint256' }, + { name: 'gasPerPubdataByteLimit', type: 'uint256' }, + { name: 'maxFeePerGas', type: 'uint256' }, + { name: 'maxPriorityFeePerGas', type: 'uint256' }, + { name: 'paymaster', type: 'uint256' }, + { name: 'nonce', type: 'uint256' }, + { name: 'value', type: 'uint256' }, + { name: 'data', type: 'bytes' }, + { name: 'factoryDeps', type: 'bytes32[]' }, + { name: 'paymasterInput', type: 'bytes' }, ], - }; \ No newline at end of file +}; \ No newline at end of file diff --git a/src/utils.ts b/src/utils.ts index e13d6d1..4aa287f 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -137,7 +137,7 @@ function recoverSignerAddress( ) { let message; if (typeof messageOrData !== 'string') { - message = web3Abi.getEncodedEip712Data(messageOrData, true); + message = web3Abi.getEncodedEip712Data(messageOrData); } else { message = messageOrData; } @@ -860,8 +860,8 @@ export async function getERC20BridgeCalldata( } /** - * Validates signatures from non-contract account addresses (EOA). - * Provides similar functionality to `ethers.js` but returns `true` + * Validates signatures from non-contract account addresses (EOA: Externally Owned Account). + * Provides similar functionality to `new Web3().eth.accounts.recover(message, v, r, s)` but returns `true` * if the validation process succeeds, otherwise returns `false`. * * Called from {@link isSignatureCorrect} for non-contract account addresses. @@ -873,20 +873,20 @@ export async function getERC20BridgeCalldata( * @example * * import * as web3Accounts from 'web3-eth-accounts'; - * + * * const wallet = web3Accounts.create(); * const ADDRESS = wallet.address; * const PRIVATE_KEY = wallet.privateKey; * * const message = "Hello, world!"; * - * const signature = web3Accounts.sign(message, PRIVATE_KEY).signature; + * const signature = web3Accounts.sign(message, PRIVATE_KEY).signature; * const isValidSignature = await utils.isECDSASignatureCorrect(ADDRESS, message, signature); * // isValidSignature = true */ function isECDSASignatureCorrect( address: string, - message: string, + message: string | web3Types.Eip712TypedData, signature: SignatureLike, ): boolean { try { @@ -943,7 +943,7 @@ async function isEIP1271SignatureCorrect( async function isSignatureCorrect( context: web3.Web3Context, // or maybe use RpcMethods? address: string, - message: string, + message: string | web3Types.Eip712TypedData, signature: SignatureLike, ): Promise { let isContractAccount; @@ -961,7 +961,7 @@ async function isSignatureCorrect( return isECDSASignatureCorrect(address, message, signature); } else { const msgHash = web3Accounts.hashMessage( - typeof message === 'string' ? message : web3Utils.bytesToHex(message), + typeof message === 'string' ? message : web3Utils.bytesToHex(message as unknown as string), ); return await isEIP1271SignatureCorrect(context, address, msgHash, signature); } @@ -1017,7 +1017,7 @@ export async function isMessageSignatureCorrect( * * @example * - * import { Wallet, utils, constants, Provider, EIP712Signer } from "zksync-ethers"; + * import { Wallet, utils, constants, Provider, EIP712Signer } from "web3-plugin-zksync"; * * const ADDRESS = ""; * const PRIVATE_KEY = ""; @@ -1032,7 +1032,7 @@ export async function isMessageSignatureCorrect( * }; * * const eip712Signer = new EIP712Signer( - * new Wallet(PRIVATE_KEY), // or new ethers.Wallet(PRIVATE_KEY), + * new Wallet(PRIVATE_KEY), // or web3Accounts.privateKeyToAccount(PRIVATE_KEY), * Number((await context.getNetwork()).chainId) * ); * @@ -1044,20 +1044,22 @@ export async function isMessageSignatureCorrect( export async function isTypedDataSignatureCorrect( context: web3.Web3Context, // or maybe use RpcMethods? address: string, - domain: Extract, - types: Extract, - primaryType: Extract, + domain: web3Types.Eip712TypedData['domain'], + types: web3Types.Eip712TypedData['types'], value: Record, - signature: EthereumSignature, + signature: SignatureLike, ): Promise { const data: web3Types.Eip712TypedData = { domain, types, - primaryType, + primaryType: 'Transaction', message: value, }; - const msgHash = web3Abi.getEncodedEip712Data(data); - return await isSignatureCorrect(context, address, msgHash, signature); + // could be also: + // const message = web3Abi.getEncodedEip712Data(data); + // return await isSignatureCorrect(context, address, message, signature); + + return await isSignatureCorrect(context, address, data, signature); } /** diff --git a/test/unit/utils.test.ts b/test/unit/utils.test.ts index 291a307..a7fd6e8 100644 --- a/test/unit/utils.test.ts +++ b/test/unit/utils.test.ts @@ -296,4 +296,72 @@ describe('utils', () => { expect(isValidSignature).toBe(true); }); }); + + describe('#isTypedDataSignatureCorrect()', () => { + // TODO: Needs investigation + it.skip('should return true if correct', async () => { + // const wallet = web3Accounts.create(); + // const ADDRESS = wallet.address; + // const PRIVATE_KEY = wallet.privateKey; + + const ADDRESS = '0x99F3629e38c617cb619682f721Aaf9F61a3DE3d3'; + const PRIVATE_KEY = '0x5b032dc95add073bbacb2a2cbe0d667855cca807abe1461c72257b7ee0d7d334'; + console.log('ADDRESS', ADDRESS); + console.log('PRIVATE_KEY', PRIVATE_KEY); + + const web3 = new Web3(); + + const tx = { + type: 113, + chainId: 300, + from: ADDRESS, + to: '0xa61464658AfeAf65CccaaFD3a512b69A83B77618', + value: BigInt(7_000_000), + }; + + // const eip712Signer = new EIP712Signer(new Wallet(PRIVATE_KEY), web3.config.chainId); + + // const signInput = EIP712Signer.getSignInput(tx); + + const signInput = { + txType: 113, + from: '0x99F3629e38c617cb619682f721Aaf9F61a3DE3d3', + to: '0xa61464658AfeAf65CccaaFD3a512b69A83B77618', + gasLimit: 0n, + gasPerPubdataByteLimit: 50000, + maxFeePerGas: 0n, + maxPriorityFeePerGas: 0n, + paymaster: '0x0000000000000000000000000000000000000000', + nonce: 0, + value: 7000000n, + data: '0x', + factoryDeps: [], + paymasterInput: '0x', + }; + const domain = { + name: 'zkSync', + version: '2', + chainId: tx.chainId, + }; + console.log('domain', domain); + + // const signature = await eip712Signer.sign(tx); + + const signature = + '0x14920a5306c8b739fd2fbdd6bb933c54c05391ab9454741b5fa1132c31c6f35d4f8a716939e43df6eb660b5298d8354d08710a0d94e8d0c14dc88032cac5deda1b'; + console.log('signature\n\t', signature); + + const isValidSignature = await utils.isTypedDataSignatureCorrect( + web3, + ADDRESS, + domain, + constants.EIP712_TYPES, + signInput, + signature, + ); + console.log('isValidSignature', isValidSignature); + expect(isValidSignature).toBe(true); + }); + }); + }); From 8798d6edd4f24d1a9c0c5ee1dd7be0a597b52613 Mon Sep 17 00:00:00 2001 From: Muhammad-Altabba <24407834+Muhammad-Altabba@users.noreply.github.com> Date: Thu, 6 Jun 2024 16:44:04 +0200 Subject: [PATCH 13/30] add contracts inside the plugin instance --- package.json | 3 +- src/plugin.ts | 128 ++++++++++++++++++++++++++++++- yarn.lock | 204 +++++++++++++++++--------------------------------- 3 files changed, 198 insertions(+), 137 deletions(-) diff --git a/package.json b/package.json index d4c57a4..5a9160c 100644 --- a/package.json +++ b/package.json @@ -26,6 +26,7 @@ "web3-core": "^4.4.0", "web3-eth-abi": "^4.2.2", "web3-eth-accounts": "^4.1.2", + "web3-eth-contract": "^4.5.0", "web3-types": "^1.6.0", "web3-utils": "^4.3.0" }, @@ -39,7 +40,7 @@ "ts-jest": "^29.1.2", "ts-node": "^10.9.2", "typescript": "^5.3.3", - "web3": "^4.4.0" + "web3": "^4.9.0" }, "peerDependencies": { "web3": ">= 4.0.3" diff --git a/src/plugin.ts b/src/plugin.ts index fc922e8..dbe69a5 100644 --- a/src/plugin.ts +++ b/src/plugin.ts @@ -1,10 +1,21 @@ -import type { Address } from 'web3'; -import { Web3PluginBase, Contract } from 'web3'; import type { Web3RequestManager } from 'web3-core'; +import type { Address } from 'web3-types'; + +import { Contract } from 'web3-eth-contract'; +import { Web3Context, Web3PluginBase } from 'web3-core'; + import { IERC20ABI } from './contracts/IERC20'; import { RpcMethods } from './rpc.methods'; import { ETH_ADDRESS, ZERO_ADDRESS } from './constants'; + import { IL2BridgeABI } from './contracts/IL2Bridge'; +import { IZkSyncABI } from './contracts/IZkSyncStateTransition'; +import { IBridgehubABI } from './contracts/IBridgehub'; +import { IContractDeployerABI } from './contracts/IContractDeployer'; +import { IL1MessengerABI } from './contracts/IL1Messenger'; +import { IERC1271ABI } from './contracts/IERC1271'; +import { IL1BridgeABI } from './contracts/IL1ERC20Bridge'; +import { INonceHolderABI } from './contracts/INonceHolder'; export class ZkSyncPlugin extends Web3PluginBase { public pluginNamespace = 'zkSync'; @@ -15,6 +26,44 @@ export class ZkSyncPlugin extends Web3PluginBase { public _rpc?: RpcMethods; public _l2BridgeContracts: Record>; public _erc20Contracts: Record>; + Contracts: { + /** + * The web3.js Contract instance for the `ZkSync` interface. + */ + ZkSyncMainContract: Contract; + /** + * The ABI of the `Bridgehub` interface. + */ + BridgehubContract: Contract; + /** + * The web3.js Contract instance for the `IContractDeployer` interface, which is utilized for deploying smart contracts. + */ + ContractDeployerContract: Contract; + /** + * The web3.js Contract instance for the `IL1Messenger` interface, which is utilized for sending messages from the L2 to L1. + */ + L1MessengerContract: Contract; + /** + * The web3.js Contract instance for the `IERC20` interface, which is utilized for interacting with ERC20 tokens. + */ + IERC20Contract: Contract; + /** + * The web3.js Contract instance for the `IERC1271` interface, which is utilized for signature validation by contracts. + */ + IERC1271Contract: Contract; + /** + * The web3.js Contract instance for the `IL1Bridge` interface, which is utilized for transferring ERC20 tokens from L1 to L2. + */ + L1BridgeContract: Contract; + /** + * The web3.js Contract instance for the `IL2Bridge` interface, which is utilized for transferring ERC20 tokens from L2 to L1. + */ + L2BridgeContract: Contract; + /** + * The web3.js Contract instance for the `INonceHolder` interface, which is utilized for managing deployment nonces. + */ + NonceHolderContract: Contract; + }; constructor() { super(); @@ -25,6 +74,81 @@ export class ZkSyncPlugin extends Web3PluginBase { this.wethBridgeL2 = ''; this._l2BridgeContracts = {}; this._erc20Contracts = {}; + + this.initializeContractsInstances(); + } + + private async initializeContractsInstances() { + // TODO: optionally fetch and set the contract addresses from the Adapter once methods like getBridgehubContract and getDefaultBridgeAddresses are implemented + this.Contracts = { + /** + * The web3.js Contract instance for the `ZkSync` interface. + * @constant + */ + ZkSyncMainContract: new Contract(IZkSyncABI), + + /** + * The ABI of the `Bridgehub` interface. + * @constant + */ + BridgehubContract: new Contract(IBridgehubABI), + + /** + * The web3.js Contract instance for the `IContractDeployer` interface, which is utilized for deploying smart contracts. + * @constant + */ + ContractDeployerContract: new Contract(IContractDeployerABI), + + /** + * The web3.js Contract instance for the `IL1Messenger` interface, which is utilized for sending messages from the L2 to L1. + * @constant + */ + L1MessengerContract: new Contract(IL1MessengerABI), + + /** + * The web3.js Contract instance for the `IERC20` interface, which is utilized for interacting with ERC20 tokens. + * @constant + */ + IERC20Contract: new Contract(IERC20ABI), + + /** + * The web3.js Contract instance for the `IERC1271` interface, which is utilized for signature validation by contracts. + * @constant + */ + IERC1271Contract: new Contract(IERC1271ABI), + + /** + * The web3.js Contract instance for the `IL1Bridge` interface, which is utilized for transferring ERC20 tokens from L1 to L2. + * @constant + */ + L1BridgeContract: new Contract(IL1BridgeABI), + + /** + * The web3.js Contract instance for the `IL2Bridge` interface, which is utilized for transferring ERC20 tokens from L2 to L1. + * @constant + */ + L2BridgeContract: new Contract(IL2BridgeABI), + + /** + * The web3.js Contract instance for the `INonceHolder` interface, which is utilized for managing deployment nonces. + * @constant + */ + NonceHolderContract: new Contract(INonceHolderABI), + }; + } + + public link(parentContext: Web3Context): void { + super.link(parentContext); + + this.Contracts.ZkSyncMainContract.link(parentContext); + this.Contracts.BridgehubContract.link(parentContext); + this.Contracts.ContractDeployerContract.link(parentContext); + this.Contracts.L1MessengerContract.link(parentContext); + this.Contracts.IERC20Contract.link(parentContext); + this.Contracts.IERC1271Contract.link(parentContext); + this.Contracts.L1BridgeContract.link(parentContext); + this.Contracts.L2BridgeContract.link(parentContext); + this.Contracts.NonceHolderContract.link(parentContext); } /** diff --git a/yarn.lock b/yarn.lock index 05168b1..57f63e3 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5689,7 +5689,7 @@ walker@^1.0.8: dependencies: makeerror "1.0.12" -web3-core@^4.3.0, web3-core@^4.3.1: +web3-core@^4.3.0: version "4.3.1" resolved "https://registry.yarnpkg.com/web3-core/-/web3-core-4.3.1.tgz#5c3b5b59f1e31537a64237caa5fd83f5ffd7b0f3" integrity sha512-xa3w5n/ESxp5HIbrwsYBhpAPx2KI5LprjRFEtRwP0GpqqhTcCSMMYoyItRqQQ+k9YnB0PoFpWJfJI6Qn5x8YUQ== @@ -5704,22 +5704,6 @@ web3-core@^4.3.0, web3-core@^4.3.1: optionalDependencies: web3-providers-ipc "^4.0.7" -web3-core@^4.3.2: - version "4.3.2" - resolved "https://registry.yarnpkg.com/web3-core/-/web3-core-4.3.2.tgz#f24b11d6a57dee527de8d42c89de2a439f0c4bed" - integrity sha512-uIMVd/j4BgOnwfpY8ZT+QKubOyM4xohEhFZXz9xB8wimXWMMlYVlIK/TbfHqFolS9uOerdSGhsMbcK9lETae8g== - dependencies: - web3-errors "^1.1.4" - web3-eth-accounts "^4.1.0" - web3-eth-iban "^4.0.7" - web3-providers-http "^4.1.0" - web3-providers-ws "^4.0.7" - web3-types "^1.3.1" - web3-utils "^4.1.0" - web3-validator "^2.0.3" - optionalDependencies: - web3-providers-ipc "^4.0.7" - web3-core@^4.4.0: version "4.4.0" resolved "https://registry.yarnpkg.com/web3-core/-/web3-core-4.4.0.tgz#f2cd48aecda5ec34170edf7470001f90fdea1dc6" @@ -5761,17 +5745,6 @@ web3-eth-abi@^4.1.4: web3-utils "^4.0.7" web3-validator "^2.0.3" -web3-eth-abi@^4.2.0: - version "4.2.0" - resolved "https://registry.yarnpkg.com/web3-eth-abi/-/web3-eth-abi-4.2.0.tgz#398d415e7783442d06fb7939e40ce3de7a3f04e9" - integrity sha512-x7dUCmk6th+5N63s5kUusoNtsDJKUUQgl9+jECvGTBOTiyHe/V6aOY0120FUjaAGaapOnR7BImQdhqHv6yT2YQ== - dependencies: - abitype "0.7.1" - web3-errors "^1.1.4" - web3-types "^1.3.1" - web3-utils "^4.1.1" - web3-validator "^2.0.4" - web3-eth-abi@^4.2.2: version "4.2.2" resolved "https://registry.yarnpkg.com/web3-eth-abi/-/web3-eth-abi-4.2.2.tgz#d7592e2cc113fd34da3fb4c933537ddf8639d9b2" @@ -5796,19 +5769,6 @@ web3-eth-accounts@^4.1.0: web3-utils "^4.0.7" web3-validator "^2.0.3" -web3-eth-accounts@^4.1.1: - version "4.1.1" - resolved "https://registry.yarnpkg.com/web3-eth-accounts/-/web3-eth-accounts-4.1.1.tgz#55225e5510b961e1cacb4eccc996544998e907fc" - integrity sha512-9JqhRi1YhO1hQOEmmBHgEGsME/B1FHMxpA/AK3vhpvQ8QeP6KbJW+cForTLfPpUbkmPxnRunG4PNNaETNlZfrA== - dependencies: - "@ethereumjs/rlp" "^4.0.1" - crc-32 "^1.2.2" - ethereum-cryptography "^2.0.0" - web3-errors "^1.1.4" - web3-types "^1.3.1" - web3-utils "^4.1.1" - web3-validator "^2.0.4" - web3-eth-accounts@^4.1.2: version "4.1.2" resolved "https://registry.yarnpkg.com/web3-eth-accounts/-/web3-eth-accounts-4.1.2.tgz#652d6e3daf4d6cb3fe67cec6a878e768f6e8b8e8" @@ -5822,46 +5782,33 @@ web3-eth-accounts@^4.1.2: web3-utils "^4.2.3" web3-validator "^2.0.5" -web3-eth-contract@^4.1.2: - version "4.1.3" - resolved "https://registry.yarnpkg.com/web3-eth-contract/-/web3-eth-contract-4.1.3.tgz#15dd4c978eaf0d8f894b2c1f3e9f94edd29ff57c" - integrity sha512-F6e3eyetUDiNOb78EDVJtNOb0H1GPz3xAZH8edSfYdhaxI9tTutP2V3p++kh2ZJ/RrdE2+xil7H/nPLgHymBvg== - dependencies: - web3-core "^4.3.1" - web3-errors "^1.1.4" - web3-eth "^4.3.1" - web3-eth-abi "^4.1.4" - web3-types "^1.3.1" - web3-utils "^4.0.7" - web3-validator "^2.0.3" - -web3-eth-contract@^4.2.0: - version "4.2.0" - resolved "https://registry.yarnpkg.com/web3-eth-contract/-/web3-eth-contract-4.2.0.tgz#73f70b19217cd4854211c05846f0c841763b3b29" - integrity sha512-K7bUypsomTs8/Oa0Lgvq5plsSB5afgKJ79NMuXxvC5jfV+AxNrQyKcr5Vd5yEGNqrdQuIPduGQa8DpuY+rMe1g== +web3-eth-contract@^4.5.0: + version "4.5.0" + resolved "https://registry.yarnpkg.com/web3-eth-contract/-/web3-eth-contract-4.5.0.tgz#954c9cf4c055f3f2c33189bdd69093a8e20a569a" + integrity sha512-AX6OiDrIryz/T28k9Xz0gXpUrlOUjcooEgGluu2s5dFDWCPM/zlN5RsUZlXZiXpQyj52VCUy5+bkvu3yDPA4fg== dependencies: - web3-core "^4.3.2" - web3-errors "^1.1.4" - web3-eth "^4.4.0" - web3-eth-abi "^4.2.0" - web3-types "^1.3.1" - web3-utils "^4.1.1" - web3-validator "^2.0.4" + web3-core "^4.4.0" + web3-errors "^1.2.0" + web3-eth "^4.7.0" + web3-eth-abi "^4.2.2" + web3-types "^1.6.0" + web3-utils "^4.3.0" + web3-validator "^2.0.6" -web3-eth-ens@^4.0.8: - version "4.0.8" - resolved "https://registry.yarnpkg.com/web3-eth-ens/-/web3-eth-ens-4.0.8.tgz#f4e0a018ce6cc69e37007ee92063867feb5994f0" - integrity sha512-nj0JfeD45BbzVJcVYpUJnSo8iwDcY9CQ7CZhhIVVOFjvpMAPw0zEwjTvZEIQyCW61OoDG9xcBzwxe2tZoYhMRw== +web3-eth-ens@^4.3.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/web3-eth-ens/-/web3-eth-ens-4.3.0.tgz#f44f279a4a07eae36e3de8384989ed069201a795" + integrity sha512-QpiKT9GqJouH5kEI/pRFprh88YPCtbht2Ym6rrklZ+VoWl9D+wLfbwvW7Aox349FS7k0UX2qVins5tVNLJ5GCQ== dependencies: "@adraffy/ens-normalize" "^1.8.8" - web3-core "^4.3.0" - web3-errors "^1.1.3" - web3-eth "^4.3.1" - web3-eth-contract "^4.1.2" - web3-net "^4.0.7" - web3-types "^1.3.0" - web3-utils "^4.0.7" - web3-validator "^2.0.3" + web3-core "^4.4.0" + web3-errors "^1.2.0" + web3-eth "^4.7.0" + web3-eth-contract "^4.5.0" + web3-net "^4.1.0" + web3-types "^1.6.0" + web3-utils "^4.3.0" + web3-validator "^2.0.6" web3-eth-iban@^4.0.7: version "4.0.7" @@ -5902,22 +5849,22 @@ web3-eth@^4.3.1: web3-utils "^4.0.7" web3-validator "^2.0.3" -web3-eth@^4.4.0: - version "4.4.0" - resolved "https://registry.yarnpkg.com/web3-eth/-/web3-eth-4.4.0.tgz#755c34a769109836d122a53b33814d63f9ec5a65" - integrity sha512-HswKdzF44wUrciRAtEJaml9O7rDYDxElHmFs+27WcO3nel2zku+n0xs4e2ZAehfrCZ+05/y7TgnOZnaDU8Fb/A== +web3-eth@^4.7.0: + version "4.7.0" + resolved "https://registry.yarnpkg.com/web3-eth/-/web3-eth-4.7.0.tgz#7d18815c7a79c200552bd0df8d3127f7532b3cc2" + integrity sha512-gqlWq4Xjz+yKL2MdxQ+BgR3F4CRo4AXWDXzftb3LDzvauEfjk/yRyoxkMSK4S9RIG96ylRImS172cV6cYzcukw== dependencies: setimmediate "^1.0.5" - web3-core "^4.3.2" - web3-errors "^1.1.4" - web3-eth-abi "^4.2.0" - web3-eth-accounts "^4.1.1" - web3-net "^4.0.7" + web3-core "^4.4.0" + web3-errors "^1.2.0" + web3-eth-abi "^4.2.2" + web3-eth-accounts "^4.1.2" + web3-net "^4.1.0" web3-providers-ws "^4.0.7" - web3-rpc-methods "^1.1.4" - web3-types "^1.3.1" - web3-utils "^4.1.1" - web3-validator "^2.0.4" + web3-rpc-methods "^1.3.0" + web3-types "^1.6.0" + web3-utils "^4.3.0" + web3-validator "^2.0.6" web3-net@^4.0.7: version "4.0.7" @@ -5929,6 +5876,16 @@ web3-net@^4.0.7: web3-types "^1.3.0" web3-utils "^4.0.7" +web3-net@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/web3-net/-/web3-net-4.1.0.tgz#db7bde675e58b153339e4f149f29ec0410d6bab2" + integrity sha512-WWmfvHVIXWEoBDWdgKNYKN8rAy6SgluZ0abyRyXOL3ESr7ym7pKWbfP4fjApIHlYTh8tNqkrdPfM4Dyi6CA0SA== + dependencies: + web3-core "^4.4.0" + web3-rpc-methods "^1.3.0" + web3-types "^1.6.0" + web3-utils "^4.3.0" + web3-providers-http@^4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/web3-providers-http/-/web3-providers-http-4.1.0.tgz#8d7afda67d1d8542ca85b30f60a3d1fe1993b561" @@ -5969,14 +5926,14 @@ web3-rpc-methods@^1.1.3: web3-types "^1.3.0" web3-validator "^2.0.3" -web3-rpc-methods@^1.1.4: - version "1.1.4" - resolved "https://registry.yarnpkg.com/web3-rpc-methods/-/web3-rpc-methods-1.1.4.tgz#0b478e38231d3f3260ac504307c6dc4059af6fda" - integrity sha512-LTFNg4LFaeU8K9ecuT8fHDp/LOXyxCneeZjCrRYIW1u82Ly52SrY55FIzMIISGoG/iT5Wh7UiHOB3CQsWLBmbQ== +web3-rpc-methods@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/web3-rpc-methods/-/web3-rpc-methods-1.3.0.tgz#d5ee299a69389d63822d354ddee2c6a121a6f670" + integrity sha512-/CHmzGN+IYgdBOme7PdqzF+FNeMleefzqs0LVOduncSaqsppeOEoskLXb2anSpzmQAP3xZJPaTrkQPWSJMORig== dependencies: - web3-core "^4.3.2" - web3-types "^1.3.1" - web3-validator "^2.0.3" + web3-core "^4.4.0" + web3-types "^1.6.0" + web3-validator "^2.0.6" web3-types@^1.3.0, web3-types@^1.3.1: version "1.3.1" @@ -5998,16 +5955,6 @@ web3-utils@^4.0.7: web3-types "^1.3.0" web3-validator "^2.0.3" -web3-utils@^4.1.0, web3-utils@^4.1.1: - version "4.1.1" - resolved "https://registry.yarnpkg.com/web3-utils/-/web3-utils-4.1.1.tgz#88c0fe404abc3f038b7318f3a31141676cefeb63" - integrity sha512-5AOmLKH6QuwHunLCNdVFlPSDE+T88bJYRQP+HWYoKNbI4STALCYQiJvj7LXE+Ed6cPfqANaK/LwKNbMPLCPFwA== - dependencies: - ethereum-cryptography "^2.0.0" - web3-errors "^1.1.4" - web3-types "^1.3.1" - web3-validator "^2.0.4" - web3-utils@^4.2.3, web3-utils@^4.3.0: version "4.3.0" resolved "https://registry.yarnpkg.com/web3-utils/-/web3-utils-4.3.0.tgz#c18918f0d692f745d622d22172406f6102528860" @@ -6030,17 +5977,6 @@ web3-validator@^2.0.3: web3-types "^1.3.0" zod "^3.21.4" -web3-validator@^2.0.4: - version "2.0.4" - resolved "https://registry.yarnpkg.com/web3-validator/-/web3-validator-2.0.4.tgz#66f34c94f21a3c94d0dc2a2d30deb8a379825d38" - integrity sha512-qRxVePwdW+SByOmTpDZFWHIUAa7PswvxNszrOua6BoGqAhERo5oJZBN+EbWtK/+O+ApNxt5FR3nCPmiZldiOQA== - dependencies: - ethereum-cryptography "^2.0.0" - util "^0.12.5" - web3-errors "^1.1.4" - web3-types "^1.3.1" - zod "^3.21.4" - web3-validator@^2.0.5, web3-validator@^2.0.6: version "2.0.6" resolved "https://registry.yarnpkg.com/web3-validator/-/web3-validator-2.0.6.tgz#a0cdaa39e1d1708ece5fae155b034e29d6a19248" @@ -6052,27 +5988,27 @@ web3-validator@^2.0.5, web3-validator@^2.0.6: web3-types "^1.6.0" zod "^3.21.4" -web3@^4.4.0: - version "4.4.0" - resolved "https://registry.yarnpkg.com/web3/-/web3-4.4.0.tgz#83e5906675608adf9a14841f257e441c9154a8c7" - integrity sha512-WcFA3RgE+cyM96MHLPs5Ec155nkYQuwhjk3JIhxcy6J1QrZK2zvKGzTQCCYHe3JhraduNTMbWy+EeNNGGji9+Q== +web3@^4.9.0: + version "4.9.0" + resolved "https://registry.yarnpkg.com/web3/-/web3-4.9.0.tgz#2d18f6a48f366601aef32308260542ed14bd2b1b" + integrity sha512-O0R90ijjyqUlG1Wk3SXqfYMU1ZGJvLCAF/WfSg/isDz/0Fkpqxoj893wauZ+ieRvTXITlbQHVXGfpp8qrhWZ1g== dependencies: - web3-core "^4.3.2" - web3-errors "^1.1.4" - web3-eth "^4.4.0" - web3-eth-abi "^4.2.0" - web3-eth-accounts "^4.1.1" - web3-eth-contract "^4.2.0" - web3-eth-ens "^4.0.8" + web3-core "^4.4.0" + web3-errors "^1.2.0" + web3-eth "^4.7.0" + web3-eth-abi "^4.2.2" + web3-eth-accounts "^4.1.2" + web3-eth-contract "^4.5.0" + web3-eth-ens "^4.3.0" web3-eth-iban "^4.0.7" web3-eth-personal "^4.0.8" - web3-net "^4.0.7" + web3-net "^4.1.0" web3-providers-http "^4.1.0" web3-providers-ws "^4.0.7" - web3-rpc-methods "^1.1.4" - web3-types "^1.3.1" - web3-utils "^4.1.1" - web3-validator "^2.0.4" + web3-rpc-methods "^1.3.0" + web3-types "^1.6.0" + web3-utils "^4.3.0" + web3-validator "^2.0.6" webidl-conversions@^3.0.0: version "3.0.1" From 9cf4a60ed02d8459d97c41a59a91755655ffcc72 Mon Sep 17 00:00:00 2001 From: Muhammad-Altabba <24407834+Muhammad-Altabba@users.noreply.github.com> Date: Thu, 6 Jun 2024 16:49:56 +0200 Subject: [PATCH 14/30] tiny fix --- src/plugin.ts | 84 ++++++++++++----------------------------- test/unit/utils.test.ts | 2 +- 2 files changed, 26 insertions(+), 60 deletions(-) diff --git a/src/plugin.ts b/src/plugin.ts index dbe69a5..992fb73 100644 --- a/src/plugin.ts +++ b/src/plugin.ts @@ -26,7 +26,7 @@ export class ZkSyncPlugin extends Web3PluginBase { public _rpc?: RpcMethods; public _l2BridgeContracts: Record>; public _erc20Contracts: Record>; - Contracts: { + public Contracts: { /** * The web3.js Contract instance for the `ZkSync` interface. */ @@ -74,67 +74,33 @@ export class ZkSyncPlugin extends Web3PluginBase { this.wethBridgeL2 = ''; this._l2BridgeContracts = {}; this._erc20Contracts = {}; - - this.initializeContractsInstances(); - } - - private async initializeContractsInstances() { - // TODO: optionally fetch and set the contract addresses from the Adapter once methods like getBridgehubContract and getDefaultBridgeAddresses are implemented this.Contracts = { - /** - * The web3.js Contract instance for the `ZkSync` interface. - * @constant - */ - ZkSyncMainContract: new Contract(IZkSyncABI), - - /** - * The ABI of the `Bridgehub` interface. - * @constant - */ - BridgehubContract: new Contract(IBridgehubABI), - - /** - * The web3.js Contract instance for the `IContractDeployer` interface, which is utilized for deploying smart contracts. - * @constant - */ - ContractDeployerContract: new Contract(IContractDeployerABI), - - /** - * The web3.js Contract instance for the `IL1Messenger` interface, which is utilized for sending messages from the L2 to L1. - * @constant - */ - L1MessengerContract: new Contract(IL1MessengerABI), - - /** - * The web3.js Contract instance for the `IERC20` interface, which is utilized for interacting with ERC20 tokens. - * @constant - */ - IERC20Contract: new Contract(IERC20ABI), - - /** - * The web3.js Contract instance for the `IERC1271` interface, which is utilized for signature validation by contracts. - * @constant - */ - IERC1271Contract: new Contract(IERC1271ABI), - - /** - * The web3.js Contract instance for the `IL1Bridge` interface, which is utilized for transferring ERC20 tokens from L1 to L2. - * @constant - */ - L1BridgeContract: new Contract(IL1BridgeABI), + ZkSyncMainContract: new Contract(IZkSyncABI, ''), + BridgehubContract: new Contract(IBridgehubABI, ''), + ContractDeployerContract: new Contract(IContractDeployerABI, ''), + L1MessengerContract: new Contract(IL1MessengerABI, ''), + IERC20Contract: new Contract(IERC20ABI, ''), + IERC1271Contract: new Contract(IERC1271ABI, ''), + L1BridgeContract: new Contract(IL1BridgeABI, ''), + L2BridgeContract: new Contract(IL2BridgeABI, ''), + NonceHolderContract: new Contract(INonceHolderABI, ''), + }; - /** - * The web3.js Contract instance for the `IL2Bridge` interface, which is utilized for transferring ERC20 tokens from L2 to L1. - * @constant - */ - L2BridgeContract: new Contract(IL2BridgeABI), + this.fillContractsAddresses(); + } - /** - * The web3.js Contract instance for the `INonceHolder` interface, which is utilized for managing deployment nonces. - * @constant - */ - NonceHolderContract: new Contract(INonceHolderABI), - }; + private async fillContractsAddresses() { + // TODO: optionally fetch and set the contract addresses from the Adapter, + // nonce methods like getBridgehubContract and getDefaultBridgeAddresses are implemented. + // this.Contracts.ZkSyncMainContract.options.address = + // this.Contracts.BridgehubContract.options.address = + // this.Contracts.ContractDeployerContract.options.address = + // this.Contracts.L1MessengerContract.options.address = + // this.Contracts.IERC20Contract.options.address = + // this.Contracts.IERC1271Contract.options.address = + // this.Contracts.L1BridgeContract.options.address = + // this.Contracts.L2BridgeContract.options.address = + // this.Contracts.NonceHolderContract.options.address = } public link(parentContext: Web3Context): void { diff --git a/test/unit/utils.test.ts b/test/unit/utils.test.ts index a7fd6e8..d7ee1c0 100644 --- a/test/unit/utils.test.ts +++ b/test/unit/utils.test.ts @@ -281,7 +281,7 @@ describe('utils', () => { // }); // }); describe('#isMessageSignatureCorrect()', () => { - it.only('should return true if signature made by a private key was correct', async () => { + it('should return true if signature made by a private key was correct', async () => { const wallet = web3Accounts.create(); const ADDRESS = wallet.address; const message = 'Hello, world!'; From 4f34c20a9fce71336e8f17afa6adc465c354830e Mon Sep 17 00:00:00 2001 From: Oleksii Kosynskyi Date: Thu, 6 Jun 2024 14:24:29 -0400 Subject: [PATCH 15/30] eip712 class --- package.json | 5 +- src/constants.ts | 30 +- src/eip712/EIP712Transaction.ts | 572 ++++++++++++++++++++++++++++++++ src/eip712/constants.ts | 12 + src/eip712/types.ts | 87 +++++ src/plugin.ts | 16 +- src/rpc.methods.ts | 16 +- src/types.ts | 22 +- src/utils.ts | 118 ++----- test/fixtures.ts | 76 +++-- test/integration/rpc.test.ts | 4 +- test/unit/utils.test.ts | 146 ++++---- test/utils.ts | 8 +- yarn.lock | 44 +++ 14 files changed, 919 insertions(+), 237 deletions(-) create mode 100644 src/eip712/EIP712Transaction.ts create mode 100644 src/eip712/constants.ts create mode 100644 src/eip712/types.ts diff --git a/package.json b/package.json index 9301bf2..9637d20 100644 --- a/package.json +++ b/package.json @@ -10,6 +10,7 @@ }, "scripts": { "lint": "eslint '{src,test}/**/*.ts'", + "lint:fix": "eslint '{src,test}/**/*.ts' --fix", "build": "tsc --project tsconfig.build.json", "test": "jest --config=./test/jest.config.js" }, @@ -22,12 +23,14 @@ "url": "git+ssh://git@github.com/web3/web3-plugin-zksync.git" }, "dependencies": { + "ethereum-cryptography": "^2.1.3", "hardhat": "^2.19.4", "web3-core": "^4.4.0", "web3-eth-abi": "^4.2.2", "web3-eth-accounts": "^4.1.2", "web3-types": "^1.6.0", - "web3-utils": "^4.3.0" + "web3-utils": "^4.3.0", + "web3-validator": "^2.0.6" }, "devDependencies": { "@chainsafe/eslint-config": "^2.1.1", diff --git a/src/constants.ts b/src/constants.ts index 133c88f..08d403f 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -1,4 +1,4 @@ -import * as web3Types from 'web3-types'; +import type * as web3Types from 'web3-types'; export const ZERO_ADDRESS = '0x0000000000000000000000000000000000000000'; @@ -155,18 +155,18 @@ export const REQUIRED_L1_TO_L2_GAS_PER_PUBDATA_LIMIT = 800; */ export const EIP712_TYPES = { Transaction: [ - {name: 'txType', type: 'uint256'}, - {name: 'from', type: 'uint256'}, - {name: 'to', type: 'uint256'}, - {name: 'gasLimit', type: 'uint256'}, - {name: 'gasPerPubdataByteLimit', type: 'uint256'}, - {name: 'maxFeePerGas', type: 'uint256'}, - {name: 'maxPriorityFeePerGas', type: 'uint256'}, - {name: 'paymaster', type: 'uint256'}, - {name: 'nonce', type: 'uint256'}, - {name: 'value', type: 'uint256'}, - {name: 'data', type: 'bytes'}, - {name: 'factoryDeps', type: 'bytes32[]'}, - {name: 'paymasterInput', type: 'bytes'}, + { name: 'txType', type: 'uint256' }, + { name: 'from', type: 'uint256' }, + { name: 'to', type: 'uint256' }, + { name: 'gasLimit', type: 'uint256' }, + { name: 'gasPerPubdataByteLimit', type: 'uint256' }, + { name: 'maxFeePerGas', type: 'uint256' }, + { name: 'maxPriorityFeePerGas', type: 'uint256' }, + { name: 'paymaster', type: 'uint256' }, + { name: 'nonce', type: 'uint256' }, + { name: 'value', type: 'uint256' }, + { name: 'data', type: 'bytes' }, + { name: 'factoryDeps', type: 'bytes32[]' }, + { name: 'paymasterInput', type: 'bytes' }, ], - }; \ No newline at end of file +}; diff --git a/src/eip712/EIP712Transaction.ts b/src/eip712/EIP712Transaction.ts new file mode 100644 index 0000000..a803d2c --- /dev/null +++ b/src/eip712/EIP712Transaction.ts @@ -0,0 +1,572 @@ +/* +This file is part of web3.js. + +web3.js is free software: you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +web3.js is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License +along with web3.js. If not, see . +*/ +import type { Bytes } from 'web3-types'; +import { RLP } from '@ethereumjs/rlp'; +import { keccak256 } from 'ethereum-cryptography/keccak.js'; +import { + bytesToHex, + bytesToUint8Array, + hexToBytes, + toBigInt, + toChecksumAddress, + toHex, + uint8ArrayConcat, +} from 'web3-utils'; +import { validateNoLeadingZeroes } from 'web3-validator'; +import { + bigIntToHex, + bigIntToUnpaddedUint8Array, + ecrecover, + toUint8Array, + uint8ArrayToBigInt, + BaseTransaction, + Capability, +} from 'web3-eth-accounts'; +import type { Common, JsonTx, TxOptions, TxValuesArray } from 'web3-eth-accounts'; +import { DEFAULT_GAS_PER_PUBDATA_LIMIT, ZERO_ADDRESS } from '../constants'; +import { concat, hashBytecode, toBytes } from '../utils'; +import type { Address } from '../types'; +import { MAX_INTEGER } from './constants'; +import type { Eip712TxData, TypedDataDomain, Eip712Meta } from './types'; + +export const EIP712_TX_TYPE = 113; // 0x71 +const EIP712_TX_TYPE_UINT8ARRAY = hexToBytes(EIP712_TX_TYPE.toString(16).padStart(2, '0')); + +function meetsEIP155(_v: bigint, chainId: bigint) { + const v = Number(_v); + const chainIdDoubled = Number(chainId) * 2; + return v === chainIdDoubled + 35 || v === chainIdDoubled + 36; +} + +/** + * Class to create the EIP-712 Transaction object. + * - TransactionType: 113 + * - EIP: [EIP-712](https://eips.ethereum.org/EIPS/eip-712) + */ + +export class EIP712Transaction extends BaseTransaction { + private txData: Eip712TxData; + public readonly chainId?: bigint; + public readonly gasPrice: bigint; + public eip712Domain: TypedDataDomain; + + public readonly common: Common; + + /** + * Instantiate a transaction from a data dictionary. + * + * Format: { nonce, gasPrice, gasLimit, to, value, data, v, r, s } + * + * Notes: + * - All parameters are optional and have some basic default values + */ + public static fromTxData(txData: Eip712TxData, opts: TxOptions = {}) { + return new EIP712Transaction(txData, opts); + } + + /** + * Instantiate a transaction from the serialized tx. + * + * Format: `rlp([nonce, gasPrice, gasLimit, to, value, data, v, r, s])` + */ + public static fromSerializedTx(serialized: Uint8Array, opts: TxOptions = {}) { + const values = RLP.decode(serialized); + + if (!Array.isArray(values)) { + throw new Error('Invalid serialized tx input. Must be array'); + } + + return this.fromValuesArray(values as Uint8Array[], opts); + } + + /** + * Create a transaction from a values array. + * + * Format: `[nonce, gasPrice, gasLimit, to, value, data, v, r, s]` + */ + public static fromValuesArray(values: TxValuesArray, opts: TxOptions = {}) { + // If length is not 6, it has length 9. If v/r/s are empty Uint8Array, it is still an unsigned transaction + // This happens if you get the RLP data from `raw()` + if (values.length !== 6 && values.length !== 9) { + throw new Error( + 'Invalid transaction. Only expecting 6 values (for unsigned tx) or 9 values (for signed tx).', + ); + } + + const [nonce, gasPrice, gasLimit, to, value, data, v, r, s] = values; + + validateNoLeadingZeroes({ nonce, gasPrice, gasLimit, value, v, r, s }); + + return new EIP712Transaction( + { + nonce, + // @ts-ignore + gasPrice, + gasLimit, + to, + value, + data, + v, + r, + s, + }, + opts, + ); + } + + /** + * This constructor takes the values, validates them, assigns them and freezes the object. + * + * It is not recommended to use this constructor directly. Instead use + * the static factory methods to assist in creating a Transaction object from + * varying data types. + */ + public constructor(txData: Eip712TxData, opts: TxOptions = {}) { + super({ ...txData, type: EIP712_TX_TYPE }, opts); + + this.txData = EIP712Transaction.getSignInput(txData); + this.eip712Domain = { + name: 'zkSync', + version: '2', + chainId: txData.chainId, + }; + this.common = this._validateTxV(this.v, opts.common); + console.log('txData', txData); + this.chainId = txData.chainId ? toBigInt(txData.chainId) : undefined; + console.log('this.chainId', this.chainId); + this.gasPrice = uint8ArrayToBigInt( + // @ts-ignore + toUint8Array(txData.gasPrice === '' ? '0x' : txData.gasPrice), + ); + + if (this.gasPrice * this.gasLimit > MAX_INTEGER) { + const msg = this._errorMsg('gas limit * gasPrice cannot exceed MAX_INTEGER (2^256-1)'); + throw new Error(msg); + } + this._validateCannotExceedMaxInteger({ gasPrice: this.gasPrice }); + BaseTransaction._validateNotArray(txData); + + if (this.common.gteHardfork('spuriousDragon')) { + if (!this.isSigned()) { + this.activeCapabilities.push(Capability.EIP155ReplayProtection); + } else { + // EIP155 spec: + // If block.number >= 2,675,000 and v = CHAIN_ID * 2 + 35 or v = CHAIN_ID * 2 + 36 + // then when computing the hash of a transaction for purposes of signing or recovering + // instead of hashing only the first six elements (i.e. nonce, gasprice, startgas, to, value, data) + // hash nine elements, with v replaced by CHAIN_ID, r = 0 and s = 0. + // v and chain ID meet EIP-155 conditions + + if (meetsEIP155(this.v!, this.common.chainId())) { + this.activeCapabilities.push(Capability.EIP155ReplayProtection); + } + } + } + + const freeze = opts?.freeze ?? true; + if (freeze) { + Object.freeze(this); + } + } + + /** + * Generates the EIP712 typed data from provided transaction. Optional fields are populated by zero values. + * + * @param transaction The transaction request that needs to be populated. + * + * @example + * + * const tx = EIP712Signer.getSignInput({ + * type: utils.EIP712_TX_TYPE, + * to: "0xa61464658AfeAf65CccaaFD3a512b69A83B77618", + * value: BigInt(7_000_000), + * from: "0x36615Cf349d7F6344891B1e7CA7C72883F5dc049", + * nonce: 0, + * chainId: BigInt(270), + * gasPrice: BigInt(250_000_000), + * gasLimit: BigInt(21_000), + * customData: {}, + * }); + */ + static getSignInput(transaction: Eip712TxData) { + const maxFeePerGas = toBigInt(transaction.maxFeePerGas || transaction.gasPrice || 0n); + const maxPriorityFeePerGas = toBigInt(transaction.maxPriorityFeePerGas || maxFeePerGas); + const gasPerPubdataByteLimit = toBigInt( + transaction.customData?.gasPerPubdata || DEFAULT_GAS_PER_PUBDATA_LIMIT, + ); + return { + txType: transaction.type || EIP712_TX_TYPE, + from: transaction.from ? toHex(transaction.from) : undefined, + to: transaction.to ? toHex(transaction.to) : undefined, + gasLimit: transaction.gasLimit || 0n, + gasPerPubdataByteLimit: gasPerPubdataByteLimit, + customData: transaction.customData, + maxFeePerGas, + maxPriorityFeePerGas, + paymaster: transaction.customData?.paymasterParams?.paymaster || ZERO_ADDRESS, + nonce: transaction.nonce || 0, + value: transaction.value || BigInt(0), + data: transaction.data || '0x', + factoryDeps: + transaction.customData?.factoryDeps?.map((dep: Bytes) => hashBytecode(dep)) || [], + paymasterInput: transaction.customData?.paymasterParams?.paymasterInput || '0x', + }; + } + + /** + * Returns a Uint8Array Array of the raw Uint8Arrays of the legacy transaction, in order. + * + * Format: `[nonce, gasPrice, gasLimit, to, value, data, v, r, s]` + * + * For legacy txs this is also the correct format to add transactions + * to a block with {@link Block.fromValuesArray} (use the `serialize()` method + * for typed txs). + * + * For an unsigned tx this method returns the empty Uint8Array values + * for the signature parameters `v`, `r` and `s`. For an EIP-155 compliant + * representation have a look at {@link Transaction.getMessageToSign}. + */ + public raw(): TxValuesArray { + const transaction = this.txData; + console.log('raw', this.chainId); + if (!this.chainId) { + throw Error("Transaction chainId isn't set!"); + } + + if (!transaction.from) { + throw new Error( + 'Explicitly providing `from` field is required for EIP712 transactions!', + ); + } + const from = transaction.from; + const meta: Eip712Meta = transaction.customData ?? {}; + // const maxFeePerGas = transaction.maxFeePerGas || transaction.gasPrice || 0; + // const maxPriorityFeePerGas = transaction.maxPriorityFeePerGas || maxFeePerGas; + console.log('transaction', transaction); + console.log('1'); + console.log('toBytes(transaction.nonce || 0)', toBytes('0x0')); + console.log('2'); + + const fields: any[] = [ + toBytes(transaction.nonce || 0), + // toBytes(maxPriorityFeePerGas), + // toBytes(maxFeePerGas), + toBytes(transaction.gasLimit || 0), + transaction.to ? toChecksumAddress(transaction.to as Address) : '0x', + toBytes(transaction.value || 0), + transaction.data || '0x', + ]; + console.log('fields', fields); + + // if (signature) { + // const sig = new SignatureObject(signature); + fields.push( + this.v !== undefined ? bigIntToUnpaddedUint8Array(this.v) : Uint8Array.from([]), + ); + fields.push( + this.r !== undefined ? bigIntToUnpaddedUint8Array(this.r) : Uint8Array.from([]), + ); + fields.push( + this.s !== undefined ? bigIntToUnpaddedUint8Array(this.s) : Uint8Array.from([]), + ); + // } else { + // fields.push(toBytes(transaction.chainId)); + // fields.push("0x"); + // fields.push("0x"); + // } + console.log('this.chainId', this.chainId); + fields.push(toBytes(this.chainId)); + fields.push(toChecksumAddress(from)); + + // Add meta + console.log('meta', meta); + fields.push(toBytes(meta.gasPerPubdata || DEFAULT_GAS_PER_PUBDATA_LIMIT)); + fields.push((meta.factoryDeps ?? []).map(dep => toHex(dep))); + if (meta.customSignature && bytesToUint8Array(meta.customSignature).length === 0) { + throw new Error('Empty signatures are not supported!'); + } + fields.push(meta.customSignature || '0x'); + + if (meta.paymasterParams) { + fields.push([ + meta.paymasterParams.paymaster, + toHex(meta.paymasterParams.paymasterInput), + ]); + } else { + fields.push([]); + } + return fields; + } + + /** + * Serializes an EIP712 transaction and includes a signature if provided. + * + * @param transaction The transaction that needs to be serialized. + * @param signature Ethers signature to be included in the transaction. + * @throws {Error} Throws an error if: + * - `transaction.customData.customSignature` is an empty string. The transaction should be signed, and the `transaction.customData.customSignature` field should be populated with the signature. It should not be specified if the transaction is not signed. + * - `transaction.chainId` is not provided. + * - `transaction.from` is not provided. + * + * @example Serialize EIP712 transaction without signature. + * + * const serializedTx = utils.serializeEip712({ chainId: 270, from: "0x36615Cf349d7F6344891B1e7CA7C72883F5dc049" }, null); + * + * // serializedTx = "0x71ea8080808080808082010e808082010e9436615cf349d7f6344891b1e7ca7c72883f5dc04982c350c080c0" + * + * @example Serialize EIP712 transaction with signature. + * + * const signature = ethers.Signature.from("0x73a20167b8d23b610b058c05368174495adf7da3a4ed4a57eb6dbdeb1fafc24aaf87530d663a0d061f69bb564d2c6fb46ae5ae776bbd4bd2a2a4478b9cd1b42a"); + * + * const serializedTx = utils.serializeEip712( + * { + * chainId: 270, + * from: "0x36615Cf349d7F6344891B1e7CA7C72883F5dc049", + * to: "0xa61464658AfeAf65CccaaFD3a512b69A83B77618", + * value: 1_000_000, + * }, + * signature + * ); + * // serializedTx = "0x71f87f8080808094a61464658afeaf65cccaafd3a512b69a83b77618830f42408001a073a20167b8d23b610b058c05368174495adf7da3a4ed4a57eb6dbdeb1fafc24aa02f87530d663a0d061f69bb564d2c6fb46ae5ae776bbd4bd2a2a4478b9cd1b42a82010e9436615cf349d7f6344891b1e7ca7c72883f5dc04982c350c080c0" + */ + // @ts-ignore + serialize() { + return concat([new Uint8Array([EIP712_TX_TYPE]), RLP.encode(this.raw())]); + } + + /** + * Returns the unsigned tx (hashed or raw), which can be used + * to sign the transaction (e.g. for sending to a hardware wallet). + * + * Note: the raw message message format for the legacy tx is not RLP encoded + * and you might need to do yourself with: + * + * ```javascript + * import { bufArrToArr } from '../util' + * import { RLP } from '../rlp' + * const message = tx.getMessageToSign(false) + * const serializedMessage = RLP.encode(message) // use this for the HW wallet input + * ``` + * + * @param hashMessage - Return hashed message if set to true (default: true) + */ + public getMessageToSign(hashMessage = true) { + const base = this.raw().slice(0, 9); + const message = uint8ArrayConcat(EIP712_TX_TYPE_UINT8ARRAY, RLP.encode(base)); + if (hashMessage) { + return keccak256(message); + } + return message; + } + + /** + * The amount of gas paid for the data in this tx + */ + public getDataFee(): bigint { + // TODO: this is a temporary solution until we have a better way to calculate the data fee + if (this.cache.dataFee && this.cache.dataFee.hardfork === this.common.hardfork()) { + return this.cache.dataFee.value; + } + + if (Object.isFrozen(this)) { + this.cache.dataFee = { + value: super.getDataFee(), + hardfork: this.common.hardfork(), + }; + } + + return super.getDataFee(); + } + + /** + * The up front amount that an account must have for this transaction to be valid + */ + public getUpfrontCost(): bigint { + // TODO: this is a temporary solution until we have a better way to calculate the upfront cost + return this.gasLimit * this.gasPrice + this.value; + } + + /** + * Computes a sha3-256 hash of the serialized tx. + * + * This method can only be used for signed txs (it throws otherwise). + * Use {@link Transaction.getMessageToSign} to get a tx hash for the purpose of signing. + */ + public hash(): Uint8Array { + if (!this.isSigned()) { + const msg = this._errorMsg('Cannot call hash method if transaction is not signed'); + throw new Error(msg); + } + + if (Object.isFrozen(this)) { + if (!this.cache.hash) { + this.cache.hash = keccak256(RLP.encode(this.raw())); + } + return this.cache.hash; + } + + return keccak256(RLP.encode(this.raw())); + } + + /** + * Computes a sha3-256 hash which can be used to verify the signature + */ + + public getMessageToVerifySignature(): Uint8Array { + if (!this.isSigned()) { + const msg = this._errorMsg('This transaction is not signed'); + throw new Error(msg); + } + return this.getMessageToSign(); + } + + /** + * Returns the public key of the sender + */ + public getSenderPublicKey(): Uint8Array { + if (!this.isSigned()) { + const msg = this._errorMsg('Cannot call this method if transaction is not signed'); + throw new Error(msg); + } + + const msgHash = this.getMessageToVerifySignature(); + const { v, r, s } = this; + + this._validateHighS(); + + try { + return ecrecover( + msgHash, + v! + BigInt(27), // Recover the 27 which was stripped from ecsign + bigIntToUnpaddedUint8Array(r!), + bigIntToUnpaddedUint8Array(s!), + ); + } catch (e: any) { + const msg = this._errorMsg('Invalid Signature'); + throw new Error(msg); + } + } + + /** + * Process the v, r, s values from the `sign` method of the base transaction. + */ + protected _processSignature(_v: bigint, r: Uint8Array, s: Uint8Array) { + let v = _v; + if (this.supports(Capability.EIP155ReplayProtection)) { + v += this.common.chainId() * BigInt(2) + BigInt(8); + } + + const opts = { ...this.txOptions, common: this.common }; + + return EIP712Transaction.fromTxData( + { + chainId: this.chainId, + nonce: this.nonce, + gasLimit: this.gasLimit, + to: this.to, + value: this.value, + data: this.data, + v: v - BigInt(27), // This looks extremely hacky: /util actually adds 27 to the value, the recovery bit is either 0 or 1. + r: uint8ArrayToBigInt(r), + s: uint8ArrayToBigInt(s), + }, + opts, + ); + } + + /** + * Returns an object with the JSON representation of the transaction. + */ + public toJSON(): JsonTx { + return { + nonce: bigIntToHex(this.nonce), + gasPrice: bigIntToHex(this.gasPrice), + gasLimit: bigIntToHex(this.gasLimit), + to: this.to !== undefined ? this.to.toString() : undefined, + value: bigIntToHex(this.value), + data: bytesToHex(this.data), + v: this.v !== undefined ? bigIntToHex(this.v) : undefined, + r: this.r !== undefined ? bigIntToHex(this.r) : undefined, + s: this.s !== undefined ? bigIntToHex(this.s) : undefined, + }; + } + + /** + * Validates tx's `v` value + */ + private _validateTxV(_v?: bigint, common?: Common): Common { + let chainIdBigInt; + const v = _v !== undefined ? Number(_v) : undefined; + // Check for valid v values in the scope of a signed legacy tx + if (v !== undefined) { + // v is 1. not matching the EIP-155 chainId included case and... + // v is 2. not matching the classic v=27 or v=28 case + if (v < 37 && v !== 27 && v !== 28) { + throw new Error( + `Legacy txs need either v = 27/28 or v >= 37 (EIP-155 replay protection), got v = ${v}`, + ); + } + } + + // No unsigned tx and EIP-155 activated and chain ID included + if ( + v !== undefined && + v !== 0 && + (!common || common.gteHardfork('spuriousDragon')) && + v !== 27 && + v !== 28 + ) { + if (common) { + if (!meetsEIP155(BigInt(v), common.chainId())) { + throw new Error( + `Incompatible EIP155-based V ${v} and chain id ${common.chainId()}. See the Common parameter of the Transaction constructor to set the chain id.`, + ); + } + } else { + // Derive the original chain ID + let numSub; + if ((v - 35) % 2 === 0) { + numSub = 35; + } else { + numSub = 36; + } + // Use derived chain ID to create a proper Common + chainIdBigInt = BigInt(v - numSub) / BigInt(2); + } + } + return this._getCommon(common, chainIdBigInt); + } + + /** + * Return a compact error string representation of the object + */ + public errorStr() { + let errorStr = this._getSharedErrorPostfix(); + errorStr += ` gasPrice=${this.gasPrice}`; + return errorStr; + } + + /** + * Internal helper function to create an annotated error message + * + * @param msg Base error message + * @hidden + */ + protected _errorMsg(msg: string) { + return `${msg} (${this.errorStr()})`; + } +} diff --git a/src/eip712/constants.ts b/src/eip712/constants.ts new file mode 100644 index 0000000..e669696 --- /dev/null +++ b/src/eip712/constants.ts @@ -0,0 +1,12 @@ +/** + * Default gas per pubdata byte for L2 transactions. + * This value is utilized when inserting a default value for type 2 + * and EIP712 type transactions. + * + * @constant + */ +// It is a realistic value, but it is large enough to fill into any batch regardless of the pubdata price. +export const DEFAULT_GAS_PER_PUBDATA_LIMIT = 50_000; +export const MAX_INTEGER = BigInt( + '0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff', +); diff --git a/src/eip712/types.ts b/src/eip712/types.ts new file mode 100644 index 0000000..1f091df --- /dev/null +++ b/src/eip712/types.ts @@ -0,0 +1,87 @@ +import type { Bytes, Numbers } from 'web3-types'; +import type { Address } from 'web3'; +import type { FeeMarketEIP1559TxData } from 'web3-eth-accounts'; + +export interface TypedDataDomain { + /** + * The human-readable name of the signing domain. + */ + name?: null | string; + + /** + * The major version of the signing domain. + */ + version?: null | string; + + /** + * The chain ID of the signing domain. + */ + chainId?: null | Numbers; + + /** + * The the address of the contract that will verify the signature. + */ + verifyingContract?: null | string; + + /** + * A salt used for purposes decided by the specific domain. + */ + salt?: null | Bytes; +} + +/** + * A specific field of a structured [[link-eip-712]] type. + */ +export interface TypedDataField { + /** + * The field name. + */ + name: string; + + /** + * The type of the field. + */ + type: string; +} + +export type PaymasterParams = { + /** The address of the paymaster. */ + paymaster: Address; + /** The bytestream input for the paymaster. */ + paymasterInput: Bytes; +}; + +export type Eip712Meta = { + /** The maximum amount of gas the user is willing to pay for a single byte of pubdata. */ + gasPerPubdata?: Numbers; + /** An array of bytes containing the bytecode of the contract being deployed and any related contracts it can deploy. */ + factoryDeps?: Bytes[]; + /** Custom signature used for cases where the signer's account is not an EOA. */ + customSignature?: Bytes; + /** Parameters for configuring the custom paymaster for the transaction. */ + paymasterParams?: PaymasterParams; +}; + +export type Eip712TxData = FeeMarketEIP1559TxData & { + /** The custom data for EIP712 transaction metadata. */ + customData?: null | Eip712Meta; + from?: Address; +}; + +export const EIP712_TYPES = { + Transaction: [ + { name: 'txType', type: 'uint256' }, + { name: 'from', type: 'uint256' }, + { name: 'to', type: 'uint256' }, + { name: 'gasLimit', type: 'uint256' }, + { name: 'gasPerPubdataByteLimit', type: 'uint256' }, + { name: 'maxFeePerGas', type: 'uint256' }, + { name: 'maxPriorityFeePerGas', type: 'uint256' }, + { name: 'paymaster', type: 'uint256' }, + { name: 'nonce', type: 'uint256' }, + { name: 'value', type: 'uint256' }, + { name: 'data', type: 'bytes' }, + { name: 'factoryDeps', type: 'bytes32[]' }, + { name: 'paymasterInput', type: 'bytes' }, + ], +}; diff --git a/src/plugin.ts b/src/plugin.ts index fc922e8..ba5f2bd 100644 --- a/src/plugin.ts +++ b/src/plugin.ts @@ -1,10 +1,12 @@ import type { Address } from 'web3'; import { Web3PluginBase, Contract } from 'web3'; import type { Web3RequestManager } from 'web3-core'; +import { TransactionFactory } from 'web3-eth-accounts'; import { IERC20ABI } from './contracts/IERC20'; import { RpcMethods } from './rpc.methods'; import { ETH_ADDRESS, ZERO_ADDRESS } from './constants'; import { IL2BridgeABI } from './contracts/IL2Bridge'; +import { EIP712Transaction, EIP712_TX_TYPE } from './eip712/EIP712Transaction'; export class ZkSyncPlugin extends Web3PluginBase { public pluginNamespace = 'zkSync'; @@ -25,6 +27,8 @@ export class ZkSyncPlugin extends Web3PluginBase { this.wethBridgeL2 = ''; this._l2BridgeContracts = {}; this._erc20Contracts = {}; + // @ts-ignore + TransactionFactory.registerTransactionType(EIP712_TX_TYPE, EIP712Transaction); } /** @@ -32,7 +36,9 @@ export class ZkSyncPlugin extends Web3PluginBase { */ get rpc(): RpcMethods { if (!this._rpc) { - this._rpc = new RpcMethods(this.requestManager as unknown as Web3RequestManager); + this._rpc = new RpcMethods( + this.requestManager as unknown as Web3RequestManager, + ); } return this._rpc; } @@ -102,7 +108,9 @@ export class ZkSyncPlugin extends Web3PluginBase { return l1Token; } } catch (e) { - throw new Error(`Error getting L1 address for token ${token}. ${JSON.stringify(e)}`); + throw new Error( + `Error getting L1 address for token ${token}. ${JSON.stringify(e)}`, + ); } } @@ -128,7 +136,9 @@ export class ZkSyncPlugin extends Web3PluginBase { return l2WethToken; } } catch (e) { - throw new Error(`Error getting L2 address for token ${token}. ${JSON.stringify(e)}`); + throw new Error( + `Error getting L2 address for token ${token}. ${JSON.stringify(e)}`, + ); } } diff --git a/src/rpc.methods.ts b/src/rpc.methods.ts index 7f98606..a98b6f9 100644 --- a/src/rpc.methods.ts +++ b/src/rpc.methods.ts @@ -1,6 +1,6 @@ import type { Web3RequestManager } from 'web3-core'; import * as web3Utils from 'web3-utils'; -import * as web3Types from 'web3-types'; +import type * as web3Types from 'web3-types'; import * as web3Accounts from 'web3-eth-accounts'; import { DEFAULT_RETURN_FORMAT, @@ -105,7 +105,9 @@ export class RpcMethods { * * @param returnFormat - The format of the return value. */ - public async getL1BatchNumber(returnFormat: DataFormat = DEFAULT_RETURN_FORMAT): Promise { + public async getL1BatchNumber( + returnFormat: DataFormat = DEFAULT_RETURN_FORMAT, + ): Promise { return web3Utils.format( IntSchema, await this._send('zks_L1BatchNumber', []), @@ -204,7 +206,11 @@ export class RpcMethods { ]); if (Array.isArray(result)) { return result.map(tx => { - return web3Utils.format(RawBlockTransactionSchema, tx, returnFormat) as RawBlockTransaction; + return web3Utils.format( + RawBlockTransactionSchema, + tx, + returnFormat, + ) as RawBlockTransaction; }); } return []; @@ -404,7 +410,9 @@ export class RpcMethods { const res = (await this._send('zks_getProof', [ address, keys, - typeof l1BatchNumber === 'number' ? l1BatchNumber : Number(web3Utils.toNumber(l1BatchNumber)), + typeof l1BatchNumber === 'number' + ? l1BatchNumber + : Number(web3Utils.toNumber(l1BatchNumber)), ])) as StorageProof; const result = web3Utils.format(ProofSchema, res, returnFormat) as StorageProof; result.storageProof = []; diff --git a/src/types.ts b/src/types.ts index ec12339..a1dfbb3 100644 --- a/src/types.ts +++ b/src/types.ts @@ -1,11 +1,9 @@ -export type { Bytes, HexString, Numbers } from 'web3-types'; - // import { FMT_BYTES, FMT_NUMBER, TransactionReceipt, Web3Eth } from 'web3'; // // TODO: // is it needed to be re-exported from web3 // import { watchTransactionForConfirmations } from 'web3-eth/lib/types/utils/watch_transaction_for_confirmations.js'; -import { +import type { Bytes, HexString, Numbers, @@ -15,7 +13,7 @@ import { TransactionReceipt, } from 'web3-types'; -import { +import type { // FeeMarketEIP1559Transaction, FeeMarketEIP1559TxData, // TxOptions @@ -30,7 +28,9 @@ import { // isAddressEq, // } from './utils'; -import { RpcMethods } from './rpc.methods'; +import type { RpcMethods } from './rpc.methods'; + +export type { Bytes, HexString, Numbers } from 'web3-types'; export interface TransactionOverrides extends Omit {} export const ZeroAddress: Address = '0x0000000000000000000000000000000000000000'; @@ -701,17 +701,17 @@ export interface TransactionDetails { /** Represents the full deposit fee containing fees for both L1 and L2 transactions. */ export interface FullDepositFee { /** The maximum fee per gas for L1 transaction. */ - maxFeePerGas?: BigInt; + maxFeePerGas?: bigint; /** The maximum priority fee per gas for L1 transaction. */ - maxPriorityFeePerGas?: BigInt; + maxPriorityFeePerGas?: bigint; /** The gas price for L2 transaction. */ - gasPrice?: BigInt; + gasPrice?: bigint; /** The base cost of the deposit transaction on L2. */ - baseCost: BigInt; + baseCost: bigint; /** The gas limit for L1 transaction. */ - l1GasLimit: BigInt; + l1GasLimit: bigint; /** The gas limit for L2 transaction. */ - l2GasLimit: BigInt; + l2GasLimit: bigint; } /** Represents a raw block transaction. */ diff --git a/src/utils.ts b/src/utils.ts index 6602a89..14e11d7 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -13,10 +13,12 @@ import * as web3Types from 'web3-types'; import * as web3Abi from 'web3-eth-abi'; import * as web3Contract from 'web3-eth-contract'; -import { +import type { DeploymentInfo, // Eip712Meta, EthereumSignature, +} from './types'; +import { // PaymasterParams, PriorityOpTree, PriorityQueueType, @@ -50,7 +52,7 @@ import { // DEFAULT_GAS_PER_PUBDATA_LIMIT, } from './constants'; -import { RpcMethods } from './rpc.methods'; // to be used instead of the one at zksync-ethers: Provider from ./provider +import type { RpcMethods } from './rpc.methods'; // to be used instead of the one at zksync-ethers: Provider from ./provider // export * from './paymaster-utils'; // export * from './smart-account-utils'; @@ -171,7 +173,7 @@ export class SignatureObject { throw new Error('Invalid signature length'); } // Initialize with a single string parameter - const signature = rOrSignature as string; + const signature = rOrSignature; this.r = web3Accounts.toUint8Array(signature.slice(0, 66)); this.s = web3Accounts.toUint8Array(`0x${signature.slice(66, 130)}`); this.v = BigInt(web3Utils.hexToNumber(`0x${signature.slice(130, 132)}`)); @@ -319,7 +321,8 @@ export function getDeployedContracts(receipt: web3Types.TransactionReceipt): Dep .filter( log => log.topics && - log.topics[0] === contractFunctionId('ContractDeployed(address,bytes32,address)') && + log.topics[0] === + contractFunctionId('ContractDeployed(address,bytes32,address)') && log.address && isAddressEq(log.address, CONTRACT_DEPLOYER_ADDRESS), ) @@ -362,7 +365,9 @@ export function create2Address( const prefix = web3Utils.keccak256(web3Utils.utf8ToBytes('zksyncCreate2')); const inputHash = web3Utils.keccak256(input); const addressBytes = web3Utils - .keccak256(concat([prefix, web3Utils.padLeft(sender, 32 * 2), salt, bytecodeHash, inputHash])) + .keccak256( + concat([prefix, web3Utils.padLeft(sender, 32 * 2), salt, bytecodeHash, inputHash]), + ) .slice(26); return web3Utils.toChecksumAddress(addressBytes); } @@ -422,94 +427,6 @@ export async function checkBaseCost( } } -// /** -// * Serializes an EIP712 transaction and includes a signature if provided. -// * -// * @param transaction The transaction that needs to be serialized. -// * @param signature Ethers signature to be included in the transaction. -// * @throws {Error} Throws an error if: -// * - `transaction.customData.customSignature` is an empty string. The transaction should be signed, and the `transaction.customData.customSignature` field should be populated with the signature. It should not be specified if the transaction is not signed. -// * - `transaction.chainId` is not provided. -// * - `transaction.from` is not provided. -// * -// * @example Serialize EIP712 transaction without signature. -// * -// * const serializedTx = utils.serializeEip712({ chainId: 270, from: "0x36615Cf349d7F6344891B1e7CA7C72883F5dc049" }, null); -// * -// * // serializedTx = "0x71ea8080808080808082010e808082010e9436615cf349d7f6344891b1e7ca7c72883f5dc04982c350c080c0" -// * -// * @example Serialize EIP712 transaction with signature. -// * -// * const signature = ethers.Signature.from("0x73a20167b8d23b610b058c05368174495adf7da3a4ed4a57eb6dbdeb1fafc24aaf87530d663a0d061f69bb564d2c6fb46ae5ae776bbd4bd2a2a4478b9cd1b42a"); -// * -// * const serializedTx = utils.serializeEip712( -// * { -// * chainId: 270, -// * from: "0x36615Cf349d7F6344891B1e7CA7C72883F5dc049", -// * to: "0xa61464658AfeAf65CccaaFD3a512b69A83B77618", -// * value: 1_000_000, -// * }, -// * signature -// * ); -// * // serializedTx = "0x71f87f8080808094a61464658afeaf65cccaafd3a512b69a83b77618830f42408001a073a20167b8d23b610b058c05368174495adf7da3a4ed4a57eb6dbdeb1fafc24aa02f87530d663a0d061f69bb564d2c6fb46ae5ae776bbd4bd2a2a4478b9cd1b42a82010e9436615cf349d7f6344891b1e7ca7c72883f5dc04982c350c080c0" -// */ -// export function serializeEip712(transaction: TransactionLike, signature?: SignatureLike): string { -// if (!transaction.chainId) { -// throw Error("Transaction chainId isn't set!"); -// } - -// if (!transaction.from) { -// throw new Error('Explicitly providing `from` field is required for EIP712 transactions!'); -// } -// const from = transaction.from; -// const meta: Eip712Meta = transaction.customData ?? {}; -// const maxFeePerGas = transaction.maxFeePerGas || transaction.gasPrice || 0; -// const maxPriorityFeePerGas = transaction.maxPriorityFeePerGas || maxFeePerGas; - -// const fields: any[] = [ -// toBytes(transaction.nonce || 0), -// toBytes(maxPriorityFeePerGas), -// toBytes(maxFeePerGas), -// toBytes(transaction.gasLimit || 0), -// transaction.to ? web3Utils.toChecksumAddress(transaction.to) : '0x', -// toBytes(transaction.value || 0), -// transaction.data || '0x', -// ]; - -// if (signature) { -// const sig = new SignatureObject(signature); -// fields.push(toBytes(sig.yParity)); -// fields.push(toBytes(sig.r)); -// fields.push(toBytes(sig.s)); -// } else { -// fields.push(toBytes(transaction.chainId)); -// fields.push('0x'); -// fields.push('0x'); -// } -// fields.push(toBytes(transaction.chainId)); -// fields.push(web3Utils.toChecksumAddress(from)); - -// // Add meta -// fields.push(toBytes(meta.gasPerPubdata || DEFAULT_GAS_PER_PUBDATA_LIMIT)); -// fields.push((meta.factoryDeps ?? []).map(dep => web3Utils.toHex(dep))); - -// if (meta.customSignature && web3Utils.bytesToUint8Array(meta.customSignature).length === 0) { -// throw new Error('Empty signatures are not supported!'); -// } -// fields.push(meta.customSignature || '0x'); - -// if (meta.paymasterParams) { -// fields.push([ -// meta.paymasterParams.paymaster, -// web3Utils.toHex(meta.paymasterParams.paymasterInput), -// ]); -// } else { -// fields.push([]); -// } - -// return concat([new Uint8Array([EIP712_TX_TYPE]), RLP.encode(fields)]); -// } - /** * Returns the hash of the given bytecode. * @@ -687,8 +604,12 @@ export function getSignature(transaction: any, ethSignature?: EthereumSignature) throw new Error('No signature provided!'); } - const r = web3Utils.bytesToUint8Array(web3Utils.padLeft(web3Utils.toHex(ethSignature.r), 32 * 2)); - const s = web3Utils.bytesToUint8Array(web3Utils.padLeft(web3Utils.toHex(ethSignature.s), 32 * 2)); + const r = web3Utils.bytesToUint8Array( + web3Utils.padLeft(web3Utils.toHex(ethSignature.r), 32 * 2), + ); + const s = web3Utils.bytesToUint8Array( + web3Utils.padLeft(web3Utils.toHex(ethSignature.s), 32 * 2), + ); const v = ethSignature.v; return new Uint8Array([...r, ...s, v]); @@ -947,7 +868,12 @@ async function isSignatureCorrect( msgHash: string, signature: SignatureLike, ): Promise { - const code = await web3.eth.getCode(context, address, undefined, web3Types.DEFAULT_RETURN_FORMAT); + const code = await web3.eth.getCode( + context, + address, + undefined, + web3Types.DEFAULT_RETURN_FORMAT, + ); const isContractAccount = web3Utils.bytesToUint8Array(code).length !== 0; if (!isContractAccount) { diff --git a/test/fixtures.ts b/test/fixtures.ts index 265d79a..942aa8d 100644 --- a/test/fixtures.ts +++ b/test/fixtures.ts @@ -16,46 +16,52 @@ export const getRawBlockTransactionsData = { }, initiatorAddress: '0x202bd724d72fd5a169c8930203e1b60870e4df95', signature: [ - 137, 115, 161, 220, 2, 48, 185, 157, 125, 236, 198, 85, 99, 212, 128, 24, 126, 171, 22, - 34, 146, 36, 193, 208, 83, 3, 134, 11, 74, 38, 89, 252, 37, 222, 4, 59, 169, 237, 144, - 64, 12, 82, 61, 251, 40, 85, 42, 89, 21, 199, 71, 128, 151, 231, 166, 230, 60, 14, 17, - 59, 67, 118, 175, 216, 28, + 137, 115, 161, 220, 2, 48, 185, 157, 125, 236, 198, 85, 99, 212, 128, 24, + 126, 171, 22, 34, 146, 36, 193, 208, 83, 3, 134, 11, 74, 38, 89, 252, 37, + 222, 4, 59, 169, 237, 144, 64, 12, 82, 61, 251, 40, 85, 42, 89, 21, 199, 71, + 128, 151, 231, 166, 230, 60, 14, 17, 59, 67, 118, 175, 216, 28, ], transactionType: 'LegacyTransaction', input: { hash: '0x16d5e37b848eed8b33d927b7c3b9d974cc48e164af5f7a6d172334a1330a5e76', data: [ - 249, 2, 206, 130, 104, 112, 132, 59, 154, 202, 0, 131, 61, 9, 0, 148, 187, 92, 48, - 154, 58, 147, 71, 192, 19, 91, 147, 203, 213, 61, 57, 74, 168, 67, 69, 229, 128, 185, - 2, 100, 201, 128, 117, 57, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 128, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 160, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 253, 105, 228, 93, 111, 81, 228, 130, 172, 79, 143, 46, 20, - 242, 21, 82, 0, 0, 93, 139, 6, 0, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 96, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 56, 197, 134, 51, 192, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 56, 197, - 134, 51, 192, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 56, 197, 134, 51, 192, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 56, 197, 134, 51, 192, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 221, 91, 114, 141, 15, - 115, 53, 225, 115, 233, 223, 14, 194, 138, 27, 152, 228, 159, 84, 144, 67, 246, 163, - 74, 17, 27, 161, 247, 5, 126, 30, 1, 243, 98, 134, 218, 191, 139, 161, 27, 167, 121, - 27, 171, 231, 94, 248, 206, 73, 195, 156, 142, 246, 164, 198, 205, 78, 67, 188, 193, - 147, 210, 209, 46, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 1, 56, 204, 92, 213, 109, 169, 231, 44, 75, 130, 19, 30, - 79, 248, 50, 78, 119, 48, 161, 206, 119, 24, 74, 4, 190, 125, 90, 23, 101, 13, 83, 24, - 231, 138, 70, 132, 57, 11, 124, 83, 101, 7, 232, 64, 136, 14, 187, 52, 224, 220, 104, - 8, 215, 48, 44, 207, 203, 144, 38, 231, 116, 87, 208, 130, 2, 124, 160, 137, 115, 161, - 220, 2, 48, 185, 157, 125, 236, 198, 85, 99, 212, 128, 24, 126, 171, 22, 34, 146, 36, - 193, 208, 83, 3, 134, 11, 74, 38, 89, 252, 160, 37, 222, 4, 59, 169, 237, 144, 64, 12, - 82, 61, 251, 40, 85, 42, 89, 21, 199, 71, 128, 151, 231, 166, 230, 60, 14, 17, 59, 67, - 118, 175, 216, + 249, 2, 206, 130, 104, 112, 132, 59, 154, 202, 0, 131, 61, 9, 0, 148, + 187, 92, 48, 154, 58, 147, 71, 192, 19, 91, 147, 203, 213, 61, 57, 74, + 168, 67, 69, 229, 128, 185, 2, 100, 201, 128, 117, 57, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 128, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 160, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 253, 105, 228, 93, 111, 81, 228, 130, 172, 79, 143, 46, 20, 242, 21, 82, + 0, 0, 93, 139, 6, 0, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 96, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 56, 197, 134, 51, 192, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 56, 197, + 134, 51, 192, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 56, 197, 134, 51, 192, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 56, 197, 134, + 51, 192, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 221, 91, 114, 141, 15, 115, 53, 225, + 115, 233, 223, 14, 194, 138, 27, 152, 228, 159, 84, 144, 67, 246, 163, + 74, 17, 27, 161, 247, 5, 126, 30, 1, 243, 98, 134, 218, 191, 139, 161, + 27, 167, 121, 27, 171, 231, 94, 248, 206, 73, 195, 156, 142, 246, 164, + 198, 205, 78, 67, 188, 193, 147, 210, 209, 46, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, + 1, 56, 204, 92, 213, 109, 169, 231, 44, 75, 130, 19, 30, 79, 248, 50, + 78, 119, 48, 161, 206, 119, 24, 74, 4, 190, 125, 90, 23, 101, 13, 83, + 24, 231, 138, 70, 132, 57, 11, 124, 83, 101, 7, 232, 64, 136, 14, 187, + 52, 224, 220, 104, 8, 215, 48, 44, 207, 203, 144, 38, 231, 116, 87, 208, + 130, 2, 124, 160, 137, 115, 161, 220, 2, 48, 185, 157, 125, 236, 198, + 85, 99, 212, 128, 24, 126, 171, 22, 34, 146, 36, 193, 208, 83, 3, 134, + 11, 74, 38, 89, 252, 160, 37, 222, 4, 59, 169, 237, 144, 64, 12, 82, 61, + 251, 40, 85, 42, 89, 21, 199, 71, 128, 151, 231, 166, 230, 60, 14, 17, + 59, 67, 118, 175, 216, ], }, paymasterParams: { diff --git a/test/integration/rpc.test.ts b/test/integration/rpc.test.ts index f07553e..38685fa 100644 --- a/test/integration/rpc.test.ts +++ b/test/integration/rpc.test.ts @@ -38,7 +38,9 @@ describe('ZkSyncPlugin rpc tests', () => { expect(res).toBeDefined(); }); it('getRawBlockTransactions', async () => { - const res = await web3.zkSync.rpc.getRawBlockTransactions(getRawBlockTransactionsData.input); + const res = await web3.zkSync.rpc.getRawBlockTransactions( + getRawBlockTransactionsData.input, + ); expect(res).toEqual(getRawBlockTransactionsData.output); }); it('getMainContract', async () => { diff --git a/test/unit/utils.test.ts b/test/unit/utils.test.ts index 686865b..8d5d1f3 100644 --- a/test/unit/utils.test.ts +++ b/test/unit/utils.test.ts @@ -2,11 +2,9 @@ import { // types, utils, } from '../../src'; -import { - ADDRESS1, - // ADDRESS2 -} from '../utils'; +import { ADDRESS1, ADDRESS2 } from '../utils'; import * as constants from '../../src/constants'; +import { EIP712Transaction } from '../../src/eip712/EIP712Transaction'; describe('utils', () => { describe('#getHashedL2ToL1Msg()', () => { @@ -115,67 +113,81 @@ describe('utils', () => { }); }); - // describe('#serializeEip712()', () => { - // it('should throw an error when `tx.chainId` is not specified', async () => { - // try { - // utils.serializeEip712({}); - // } catch (e) { - // expect((e as Error).message).toBe("Transaction chainId isn't set!"); - // } - // }); + describe('#serializeEip712()', () => { + it('should throw an error when `tx.chainId` is not specified', async () => { + try { + const txInstance = new EIP712Transaction({}); + txInstance.serialize(); + } catch (e) { + expect((e as Error).message).toBe("Transaction chainId isn't set!"); + } + }); - // it('should throw an error when `tx.from` is not specified', async () => { - // try { - // utils.serializeEip712({ chainId: 270 }); - // } catch (e) { - // expect((e as Error).message).toBe( - // 'Explicitly providing `from` field is required for EIP712 transactions!', - // ); - // } - // }); + it('should throw an error when `tx.from` is not specified', async () => { + try { + const txInstance = new EIP712Transaction({ chainId: 270 }); + txInstance.serialize(); + } catch (e) { + expect((e as Error).message).toBe( + 'Explicitly providing `from` field is required for EIP712 transactions!', + ); + } + }); - // it('should throw an error when `tx.customData.customSignature` is empty string', async () => { - // try { - // utils.serializeEip712({ - // chainId: 270, - // from: ADDRESS1, - // customData: { - // customSignature: '', - // }, - // }); - // } catch (e) { - // expect((e as Error).message).toBe('Empty signatures are not supported'); - // } - // }); + it.only('should throw an error when `tx.customData.customSignature` is empty string', async () => { + try { + const txInstance = new EIP712Transaction({ + chainId: 270, + from: ADDRESS1, + customData: { + customSignature: '', + }, + }); + txInstance.serialize(); + } catch (e) { + expect((e as Error).message).toBe('Empty signatures are not supported'); + } + }); - // it('should return a serialized transaction with populated default values', async () => { - // const tx = - // '0x71ea8080808080808082010e808082010e9436615cf349d7f6344891b1e7ca7c72883f5dc04982c350c080c0'; - // const result = utils.serializeEip712({ - // chainId: 270, - // from: ADDRESS1, - // }); - // expect(result).toBe(tx); - // }); + it('should return a serialized transaction with populated default values', async () => { + const txInstance = new EIP712Transaction({ + chainId: 270, + from: ADDRESS1, + }); + const result = txInstance.serialize(); - // it('should return a serialized transaction with provided signature', async () => { - // const tx = - // '0x71f87f8080808094a61464658afeaf65cccaafd3a512b69a83b77618830f42408001a073a20167b8d23b610b058c05368174495adf7da3a4ed4a57eb6dbdeb1fafc24aa02f87530d663a0d061f69bb564d2c6fb46ae5ae776bbd4bd2a2a4478b9cd1b42a82010e9436615cf349d7f6344891b1e7ca7c72883f5dc04982c350c080c0'; - // const signature = ethers.Signature.from( - // '0x73a20167b8d23b610b058c05368174495adf7da3a4ed4a57eb6dbdeb1fafc24aaf87530d663a0d061f69bb564d2c6fb46ae5ae776bbd4bd2a2a4478b9cd1b42a', - // ); - // const result = utils.serializeEip712( - // { - // chainId: 270, - // from: ADDRESS1, - // to: ADDRESS2, - // value: 1_000_000, - // }, - // signature, - // ); - // expect(result).toBe(tx); - // }); - // }); + const tx = + '0x71ea8080808080808082010e808082010e9436615cf349d7f6344891b1e7ca7c72883f5dc04982c350c080c0'; + expect(result).toBe(tx); + }); + + it('should return a serialized transaction with provided signature', async () => { + const txInstance = new EIP712Transaction({ + chainId: 270, + from: ADDRESS1, + to: ADDRESS2, + value: 1_000_000, + }); + const result = txInstance.serialize(); + + // const tx = + // '0x71f87f8080808094a61464658afeaf65cccaafd3a512b69a83b77618830f42408001a073a20167b8d23b610b058c05368174495adf7da3a4ed4a57eb6dbdeb1fafc24aa02f87530d663a0d061f69bb564d2c6fb46ae5ae776bbd4bd2a2a4478b9cd1b42a82010e9436615cf349d7f6344891b1e7ca7c72883f5dc04982c350c080c0'; + // const signature = ethers.Signature.from( + // '0x73a20167b8d23b610b058c05368174495adf7da3a4ed4a57eb6dbdeb1fafc24aaf87530d663a0d061f69bb564d2c6fb46ae5ae776bbd4bd2a2a4478b9cd1b42a', + // ); + // const result = utils.serializeEip712( + // { + // chainId: 270, + // from: ADDRESS1, + // to: ADDRESS2, + // value: 1_000_000, + // }, + // signature, + // ); + expect(result).toBeDefined(); + // expect(result).toBe(tx); + }); + }); describe('#hashBytecode()', () => { it('should return the hash of bytecode which length is not 2 bytes so padding needs to be performed', async () => { @@ -184,8 +196,8 @@ describe('utils', () => { const hashedBytecode = utils.hashBytecode(bytecode); expect(hashedBytecode).toEqual( new Uint8Array([ - 1, 0, 0, 27, 57, 231, 154, 55, 0, 164, 201, 96, 244, 120, 23, 112, 54, 34, 224, 133, 160, - 122, 88, 164, 112, 80, 0, 134, 48, 138, 74, 16, + 1, 0, 0, 27, 57, 231, 154, 55, 0, 164, 201, 96, 244, 120, 23, 112, 54, 34, 224, + 133, 160, 122, 88, 164, 112, 80, 0, 134, 48, 138, 74, 16, ]), ); }); @@ -196,8 +208,8 @@ describe('utils', () => { const hashedBytecode = utils.hashBytecode(bytecode); expect(hashedBytecode).toEqual( new Uint8Array([ - 1, 0, 1, 203, 106, 110, 141, 95, 104, 41, 82, 47, 25, 250, 149, 104, 102, 14, 10, 156, - 213, 59, 46, 139, 228, 222, 176, 166, 121, 69, 46, 65, + 1, 0, 1, 203, 106, 110, 141, 95, 104, 41, 82, 47, 25, 250, 149, 104, 102, 14, + 10, 156, 213, 59, 46, 139, 228, 222, 176, 166, 121, 69, 46, 65, ]), ); }); @@ -206,7 +218,9 @@ describe('utils', () => { try { utils.hashBytecode('0x0002'); } catch (e) { - expect((e as Error).message).toBe('The bytecode length in bytes must be divisible by 32!'); + expect((e as Error).message).toBe( + 'The bytecode length in bytes must be divisible by 32!', + ); } }); diff --git a/test/utils.ts b/test/utils.ts index 0a72a7d..525b832 100644 --- a/test/utils.ts +++ b/test/utils.ts @@ -1,11 +1,9 @@ export const ADDRESS1 = '0x36615Cf349d7F6344891B1e7CA7C72883F5dc049'; -export const PRIVATE_KEY1 = - '0x7726827caac94a7f9e1b160f7ea819f172f7b6f9d2a97f992c38edeab82d4110'; +export const PRIVATE_KEY1 = '0x7726827caac94a7f9e1b160f7ea819f172f7b6f9d2a97f992c38edeab82d4110'; export const MNEMONIC1 = - 'stuff slice staff easily soup parent arm payment cotton trade scatter struggle'; + 'stuff slice staff easily soup parent arm payment cotton trade scatter struggle'; export const ADDRESS2 = '0xa61464658AfeAf65CccaaFD3a512b69A83B77618'; -export const PRIVATE_KEY2 = - '0xac1e735be8536c6534bb4f17f06f6afc73b2b5ba84ac2cfb12f7461b20c0bbe3'; +export const PRIVATE_KEY2 = '0xac1e735be8536c6534bb4f17f06f6afc73b2b5ba84ac2cfb12f7461b20c0bbe3'; export const DAI_L1 = '0x70a0F165d6f8054d0d0CF8dFd4DD2005f0AF6B55'; export const APPROVAL_TOKEN = '0x841c43Fa5d8fFfdB9efE3358906f7578d8700Dd4'; // Crown token export const PAYMASTER = '0xa222f0c183AFA73a8Bc1AFb48D34C88c9Bf7A174'; // Crown token paymaster diff --git a/yarn.lock b/yarn.lock index 57f63e3..f0b5754 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1140,6 +1140,13 @@ dependencies: "@noble/hashes" "1.3.1" +"@noble/curves@1.3.0", "@noble/curves@~1.3.0": + version "1.3.0" + resolved "https://registry.yarnpkg.com/@noble/curves/-/curves-1.3.0.tgz#01be46da4fd195822dab821e72f71bf4aeec635e" + integrity sha512-t01iSXPuN+Eqzb4eBX0S5oubSqXbK/xXa1Ne18Hj8f9pStxztHCE2gfboSp/dZRLSqfuLpRK2nDXDK+W9puocA== + dependencies: + "@noble/hashes" "1.3.3" + "@noble/hashes@1.2.0", "@noble/hashes@~1.2.0": version "1.2.0" resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.2.0.tgz#a3150eeb09cc7ab207ebf6d7b9ad311a9bdbed12" @@ -1150,6 +1157,11 @@ resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.3.1.tgz#8831ef002114670c603c458ab8b11328406953a9" integrity sha512-EbqwksQwz9xDRGfDST86whPBgM65E0OH/pCgqW0GBVzO22bNE+NuIbeTb714+IfSjU3aRk47EUvXIb5bTsenKA== +"@noble/hashes@1.3.3", "@noble/hashes@~1.3.2": + version "1.3.3" + resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.3.3.tgz#39908da56a4adc270147bb07968bf3b16cfe1699" + integrity sha512-V7/fPHgl+jsVPXqqeOzT8egNj2iBIVt+ECeMMG8TdcnTikP3oaBtUVqpT/gYCR68aEBJSF+XbYUxStjbFMqIIA== + "@noble/secp256k1@1.7.1", "@noble/secp256k1@~1.7.0": version "1.7.1" resolved "https://registry.yarnpkg.com/@noble/secp256k1/-/secp256k1-1.7.1.tgz#b251c70f824ce3ca7f8dc3df08d58f005cc0507c" @@ -1386,6 +1398,11 @@ resolved "https://registry.yarnpkg.com/@scure/base/-/base-1.1.1.tgz#ebb651ee52ff84f420097055f4bf46cfba403938" integrity sha512-ZxOhsSyxYwLJj3pLZCefNitxsj093tb2vq90mp2txoYeBqbcjDjqFhyM8eUjq/uFm6zJ+mUuqxlS2FkuSY1MTA== +"@scure/base@~1.1.4": + version "1.1.6" + resolved "https://registry.yarnpkg.com/@scure/base/-/base-1.1.6.tgz#8ce5d304b436e4c84f896e0550c83e4d88cb917d" + integrity sha512-ok9AWwhcgYuGG3Zfhyqg+zwl+Wn5uE+dwC0NV/2qQkx4dABbb/bx96vWu8NSj+BNjjSjno+JRYRjle1jV08k3g== + "@scure/bip32@1.1.5": version "1.1.5" resolved "https://registry.yarnpkg.com/@scure/bip32/-/bip32-1.1.5.tgz#d2ccae16dcc2e75bc1d75f5ef3c66a338d1ba300" @@ -1404,6 +1421,15 @@ "@noble/hashes" "~1.3.1" "@scure/base" "~1.1.0" +"@scure/bip32@1.3.3": + version "1.3.3" + resolved "https://registry.yarnpkg.com/@scure/bip32/-/bip32-1.3.3.tgz#a9624991dc8767087c57999a5d79488f48eae6c8" + integrity sha512-LJaN3HwRbfQK0X1xFSi0Q9amqOgzQnnDngIt+ZlsBC3Bm7/nE7K0kwshZHyaru79yIVRv/e1mQAjZyuZG6jOFQ== + dependencies: + "@noble/curves" "~1.3.0" + "@noble/hashes" "~1.3.2" + "@scure/base" "~1.1.4" + "@scure/bip39@1.1.1": version "1.1.1" resolved "https://registry.yarnpkg.com/@scure/bip39/-/bip39-1.1.1.tgz#b54557b2e86214319405db819c4b6a370cf340c5" @@ -1420,6 +1446,14 @@ "@noble/hashes" "~1.3.0" "@scure/base" "~1.1.0" +"@scure/bip39@1.2.2": + version "1.2.2" + resolved "https://registry.yarnpkg.com/@scure/bip39/-/bip39-1.2.2.tgz#f3426813f4ced11a47489cbcf7294aa963966527" + integrity sha512-HYf9TUXG80beW+hGAt3TRM8wU6pQoYur9iNypTROm42dorCGmLnFe3eWjz3gOq6G62H2WRh0FCzAR1PI+29zIA== + dependencies: + "@noble/hashes" "~1.3.2" + "@scure/base" "~1.1.4" + "@sentry/core@5.30.0": version "5.30.0" resolved "https://registry.yarnpkg.com/@sentry/core/-/core-5.30.0.tgz#6b203664f69e75106ee8b5a2fe1d717379b331f3" @@ -2923,6 +2957,16 @@ ethereum-cryptography@^2.0.0: "@scure/bip32" "1.3.1" "@scure/bip39" "1.2.1" +ethereum-cryptography@^2.1.3: + version "2.1.3" + resolved "https://registry.yarnpkg.com/ethereum-cryptography/-/ethereum-cryptography-2.1.3.tgz#1352270ed3b339fe25af5ceeadcf1b9c8e30768a" + integrity sha512-BlwbIL7/P45W8FGW2r7LGuvoEZ+7PWsniMvQ4p5s2xCyw9tmaDlpfsN9HjAucbF+t/qpVHwZUisgfK24TCW8aA== + dependencies: + "@noble/curves" "1.3.0" + "@noble/hashes" "1.3.3" + "@scure/bip32" "1.3.3" + "@scure/bip39" "1.2.2" + ethereumjs-abi@^0.6.8: version "0.6.8" resolved "https://registry.yarnpkg.com/ethereumjs-abi/-/ethereumjs-abi-0.6.8.tgz#71bc152db099f70e62f108b7cdfca1b362c6fcae" From b0d67dce659fc0ced406b622467f551087cec55e Mon Sep 17 00:00:00 2001 From: Oleksii Kosynskyi Date: Mon, 10 Jun 2024 21:52:06 -0400 Subject: [PATCH 16/30] eip712 --- src/eip712/types.ts | 2 + src/plugin.ts | 12 +- src/types.ts | 9 - src/utils.ts | 500 +++++++++++++++++++++++++--------------- test/unit/utils.test.ts | 273 +++++++++++----------- 5 files changed, 452 insertions(+), 344 deletions(-) diff --git a/src/eip712/types.ts b/src/eip712/types.ts index 1f091df..007b37e 100644 --- a/src/eip712/types.ts +++ b/src/eip712/types.ts @@ -66,6 +66,8 @@ export type Eip712TxData = FeeMarketEIP1559TxData & { /** The custom data for EIP712 transaction metadata. */ customData?: null | Eip712Meta; from?: Address; + hash?: string; + signature?: string; }; export const EIP712_TYPES = { diff --git a/src/plugin.ts b/src/plugin.ts index ad3ae4c..1331669 100644 --- a/src/plugin.ts +++ b/src/plugin.ts @@ -125,7 +125,9 @@ export class ZkSyncPlugin extends Web3PluginBase { */ get rpc(): RpcMethods { if (!this._rpc) { - this._rpc = new RpcMethods(this.requestManager as unknown as Web3RequestManager); + this._rpc = new RpcMethods( + this.requestManager as unknown as Web3RequestManager, + ); } return this._rpc; } @@ -195,7 +197,9 @@ export class ZkSyncPlugin extends Web3PluginBase { return l1Token; } } catch (e) { - throw new Error(`Error getting L1 address for token ${token}. ${JSON.stringify(e)}`); + throw new Error( + `Error getting L1 address for token ${token}. ${JSON.stringify(e)}`, + ); } } @@ -221,7 +225,9 @@ export class ZkSyncPlugin extends Web3PluginBase { return l2WethToken; } } catch (e) { - throw new Error(`Error getting L2 address for token ${token}. ${JSON.stringify(e)}`); + throw new Error( + `Error getting L2 address for token ${token}. ${JSON.stringify(e)}`, + ); } } diff --git a/src/types.ts b/src/types.ts index a1dfbb3..4fd810d 100644 --- a/src/types.ts +++ b/src/types.ts @@ -392,15 +392,6 @@ export interface zkSyncTxData extends FeeMarketEIP1559TxData { // } // } -// /** -// * A `TransactionLike` is an extension of {@link ethers.TransactionLike} with additional features for interacting -// * with zkSync Era. -// */ -// export interface TransactionLike extends ethers.TransactionLike { -// /** The custom data for EIP712 transaction metadata. */ -// customData?: null | Eip712Meta; -// } - // /** // * A `Transaction` is an extension of {@link ethers.Transaction} with additional features for interacting // * with zkSync Era. diff --git a/src/utils.ts b/src/utils.ts index 5e69377..447fc51 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -1,7 +1,6 @@ // import { AbiCoder, BigNumberish, Bytes, ethers, SignatureLike } from 'ethers'; import { sha256 } from 'ethereum-cryptography/sha256.js'; -// import { RLP } from '@ethereumjs/rlp'; // import { secp256k1 } from '@noble/curves/secp256k1'; // import { keccak256 } from '@ethersproject/keccak256'; @@ -13,17 +12,16 @@ import * as web3Types from 'web3-types'; import * as web3Abi from 'web3-eth-abi'; import * as web3Contract from 'web3-eth-contract'; -import type { - DeploymentInfo, - // Eip712Meta, - EthereumSignature, -} from './types'; +import { RLP } from '@ethereumjs/rlp'; // to be used instead of the one at zksync-ethers: Provider from ./provider +import { bytesToHex, toBigInt, toHex } from 'web3-utils'; +import type { Address } from 'web3'; +import type { Bytes, Eip712TypedData } from 'web3-types'; +import type { DeploymentInfo, Eip712Meta, EthereumSignature, PaymasterParams } from './types'; import { // PaymasterParams, PriorityOpTree, PriorityQueueType, // Transaction, - // TransactionLike, // TransactionRequest, } from './types'; // import { EIP712Signer } from './signer'; @@ -48,11 +46,16 @@ import { EIP1271_MAGIC_VALUE, L1_FEE_ESTIMATION_COEF_NUMERATOR, L1_FEE_ESTIMATION_COEF_DENOMINATOR, + EIP712_TX_TYPE, + EIP712_TYPES, + DEFAULT_GAS_PER_PUBDATA_LIMIT, + ZERO_ADDRESS, // EIP712_TX_TYPE, // DEFAULT_GAS_PER_PUBDATA_LIMIT, } from './constants'; -import type { RpcMethods } from './rpc.methods'; // to be used instead of the one at zksync-ethers: Provider from ./provider +import type { RpcMethods } from './rpc.methods'; +import type { Eip712TxData } from './eip712/types'; // export * from './paymaster-utils'; // export * from './smart-account-utils'; @@ -152,8 +155,7 @@ function recoverSignerAddress( const s = web3Utils.toHex(signature.s); const v = web3Utils.toHex(signature.v); - const recoveredAddress = web3Accounts.recover(message, v, r, s); - return recoveredAddress; + return web3Accounts.recover(message, v, r, s); } export class SignatureObject { @@ -321,7 +323,8 @@ export function getDeployedContracts(receipt: web3Types.TransactionReceipt): Dep .filter( log => log.topics && - log.topics[0] === contractFunctionId('ContractDeployed(address,bytes32,address)') && + log.topics[0] === + contractFunctionId('ContractDeployed(address,bytes32,address)') && log.address && isAddressEq(log.address, CONTRACT_DEPLOYER_ADDRESS), ) @@ -364,7 +367,9 @@ export function create2Address( const prefix = web3Utils.keccak256(web3Utils.utf8ToBytes('zksyncCreate2')); const inputHash = web3Utils.keccak256(input); const addressBytes = web3Utils - .keccak256(concat([prefix, web3Utils.padLeft(sender, 32 * 2), salt, bytecodeHash, inputHash])) + .keccak256( + concat([prefix, web3Utils.padLeft(sender, 32 * 2), salt, bytecodeHash, inputHash]), + ) .slice(26); return web3Utils.toChecksumAddress(addressBytes); } @@ -477,122 +482,187 @@ export function hashBytecode(bytecode: web3Types.Bytes): Uint8Array { return hash; } -// /** -// * Parses an EIP712 transaction from a payload. -// * -// * @param payload The payload to parse. -// * -// * @example -// * -// * import { types } from "zksync-ethers"; -// * -// * const serializedTx = -// * "0x71f87f8080808094a61464658afeaf65cccaafd3a512b69a83b77618830f42408001a073a20167b8d23b610b058c05368174495adf7da3a4ed4a57eb6dbdeb1fafc24aa02f87530d663a0d061f69bb564d2c6fb46ae5ae776bbd4bd2a2a4478b9cd1b42a82010e9436615cf349d7f6344891b1e7ca7c72883f5dc04982c350c080c0"; -// * const tx: types.TransactionLike = utils.parseEip712(serializedTx); -// * /* -// * tx: types.TransactionLike = { -// * type: 113, -// * nonce: 0, -// * maxPriorityFeePerGas: BigInt(0), -// * maxFeePerGas: BigInt(0), -// * gasLimit: BigInt(0), -// * to: "0xa61464658AfeAf65CccaaFD3a512b69A83B77618", -// * value: BigInt(1000000), -// * data: "0x", -// * chainId: BigInt(270), -// * from: "0x36615Cf349d7F6344891B1e7CA7C72883F5dc049", -// * customData: { -// * gasPerPubdata: BigInt(50000), -// * factoryDeps: [], -// * customSignature: "0x", -// * paymasterParams: null, -// * }, -// * hash: "0x9ed410ce33179ac1ff6b721060605afc72d64febfe0c08cacab5a246602131ee", -// * }; -// * *\/ -// */ -// // TODO: extend ethers.Transaction and add custom fields -// export function parseEip712(payload: web3Types.Bytes): TransactionLike { -// function handleAddress(value: string): string | null { -// if (value === '0x') { -// return null; -// } -// return web3Utils.toChecksumAddress(value); -// } - -// function handleNumber(value: string): bigint { -// if (!value || value === '0x') { -// return 0n; -// } -// return BigInt(value); -// } - -// function arrayToPaymasterParams(arr: string[]): PaymasterParams | undefined { -// if (arr.length === 0) { -// return undefined; -// } -// if (arr.length !== 2) { -// throw new Error( -// `Invalid paymaster parameters, expected to have length of 2, found ${arr.length}!`, -// ); -// } - -// return { -// paymaster: web3Utils.toChecksumAddress(arr[0]), -// paymasterInput: web3Utils.bytesToUint8Array(arr[1]), -// }; -// } - -// const bytes = web3Utils.bytesToUint8Array(payload); - -// // try using: RLP.decode -// const raw = ethers.decodeRlp(bytes.slice(1)) as string[]; -// const transaction: TransactionLike = { -// type: EIP712_TX_TYPE, -// nonce: Number(handleNumber(raw[0])), -// maxPriorityFeePerGas: handleNumber(raw[1]), -// maxFeePerGas: handleNumber(raw[2]), -// gasLimit: handleNumber(raw[3]), -// to: handleAddress(raw[4]), -// value: handleNumber(raw[5]), -// data: raw[6], -// chainId: handleNumber(raw[10]), -// from: handleAddress(raw[11]), -// customData: { -// gasPerPubdata: handleNumber(raw[12]), -// factoryDeps: raw[13] as unknown as string[], -// customSignature: raw[14], -// paymasterParams: arrayToPaymasterParams(raw[15] as unknown as string[]), -// }, -// }; - -// const ethSignature = { -// v: Number(handleNumber(raw[7])), -// r: raw[8], -// s: raw[9], -// }; - -// if ( -// (web3Utils.toHex(ethSignature.r) === '0x' || web3Utils.toHex(ethSignature.s) === '0x') && -// !transaction.customData?.customSignature -// ) { -// return transaction; -// } - -// if (ethSignature.v !== 0 && ethSignature.v !== 1 && !transaction.customData?.customSignature) { -// throw new Error('Failed to parse signature!'); -// } - -// if (!transaction.customData?.customSignature) { -// transaction.signature = new SignatureObject(ethSignature).toString(); -// } - -// transaction.hash = eip712TxHash(transaction, ethSignature); - -// return transaction; -// } - -export function getSignature(transaction: any, ethSignature?: EthereumSignature): Uint8Array { +function handleAddress(value?: Uint8Array): string | null { + if (!value) { + return null; + } + const hexValue = bytesToHex(value); + if (hexValue === '0x') { + return null; + } + + return web3Utils.toChecksumAddress(hexValue); +} + +function handleNumber(value?: Uint8Array): bigint { + if (!value) { + return 0n; + } + const hexValue = bytesToHex(value); + if (hexValue === '0x') { + return 0n; + } + return toBigInt(hexValue); +} +function arrayToPaymasterParams(arr: Uint8Array): PaymasterParams | undefined { + if (arr.length === 0) { + return undefined; + } + if (arr.length !== 2) { + throw new Error( + `Invalid paymaster parameters, expected to have length of 2, found ${arr.length}!`, + ); + } + + return { + paymaster: web3Utils.toChecksumAddress(toHex(arr[0])), + paymasterInput: web3Utils.bytesToUint8Array(toHex(arr[1])), + }; +} + +export const getSignInput = (transaction: Eip712TxData) => { + const maxFeePerGas = toHex(transaction.maxFeePerGas || transaction.gasPrice || 0n); + const maxPriorityFeePerGas = toHex(transaction.maxPriorityFeePerGas || maxFeePerGas); + const gasPerPubdataByteLimit = toHex( + transaction.customData?.gasPerPubdata || DEFAULT_GAS_PER_PUBDATA_LIMIT, + ); + return { + txType: transaction.type || EIP712_TX_TYPE, + from: transaction.from ? toHex(transaction.from) : undefined, + to: transaction.to ? toHex(transaction.to) : undefined, + gasLimit: transaction.gasLimit || 0, + gasPerPubdataByteLimit: gasPerPubdataByteLimit, + customData: transaction.customData, + maxFeePerGas, + maxPriorityFeePerGas, + paymaster: transaction.customData?.paymasterParams?.paymaster || ZERO_ADDRESS, + nonce: transaction.nonce || 0, + value: transaction.value || toHex(0), + data: transaction.data || '0x', + factoryDeps: + transaction.customData?.factoryDeps?.map((dep: Bytes) => hashBytecode(dep)) || [], + paymasterInput: transaction.customData?.paymasterParams?.paymasterInput || '0x', + }; +}; + +export function eip712TxTypedData(transaction: Eip712TxData): Eip712TypedData { + return { + types: EIP712_TYPES, + primaryType: 'Transaction', + domain: { + name: 'zkSync', + version: '2', + chainId: Number(transaction.chainId), + }, + message: getSignInput(transaction), + }; +} +/** + * Returns the hash of an EIP712 transaction. + * + * @param transaction The EIP-712 transaction. + * @param ethSignature The ECDSA signature of the transaction. + * + * @example + * + * + */ +export function eip712TxHash(transaction: Eip712TxData, ethSignature?: EthereumSignature): string { + const bytes: string[] = []; + + const typedDataStruct = eip712TxTypedData(transaction); + + bytes.push(web3Abi.getEncodedEip712Data(typedDataStruct, true)); + bytes.push(web3Utils.keccak256(getSignature(typedDataStruct.message, ethSignature))); + return web3Utils.keccak256(concat(bytes)); +} +/** + * Parses an EIP712 transaction from a payload. + * + * @param payload The payload to parse. + * + * @example + * + * + * const serializedTx = + * "0x71f87f8080808094a61464658afeaf65cccaafd3a512b69a83b77618830f42408001a073a20167b8d23b610b058c05368174495adf7da3a4ed4a57eb6dbdeb1fafc24aa02f87530d663a0d061f69bb564d2c6fb46ae5ae776bbd4bd2a2a4478b9cd1b42a82010e9436615cf349d7f6344891b1e7ca7c72883f5dc04982c350c080c0"; + * const tx: types.TransactionLike = utils.parseEip712(serializedTx); + * /* + * tx: types.Eip712TxData = { + * type: 113, + * nonce: 0, + * maxPriorityFeePerGas: BigInt(0), + * maxFeePerGas: BigInt(0), + * gasLimit: BigInt(0), + * to: "0xa61464658AfeAf65CccaaFD3a512b69A83B77618", + * value: BigInt(1000000), + * data: "0x", + * chainId: BigInt(270), + * from: "0x36615Cf349d7F6344891B1e7CA7C72883F5dc049", + * customData: { + * gasPerPubdata: BigInt(50000), + * factoryDeps: [], + * customSignature: "0x", + * paymasterParams: null, + * }, + * hash: "0x9ed410ce33179ac1ff6b721060605afc72d64febfe0c08cacab5a246602131ee", + * }; + * *\/ + */ + +export function parseEip712(payload: web3Types.Bytes): Eip712TxData { + const bytes = web3Utils.bytesToUint8Array(payload); + + // try using: RLP.decode + const raw = RLP.decode(bytes.slice(1)) as Array; + const transaction: Eip712TxData = { + type: EIP712_TX_TYPE, + nonce: handleNumber(raw[0]), + maxPriorityFeePerGas: handleNumber(raw[1]), + maxFeePerGas: handleNumber(raw[2]), + gasLimit: handleNumber(raw[3]), + to: handleAddress(raw[4]) as Address, + value: handleNumber(raw[5]), + data: bytesToHex(raw[6]), + chainId: handleNumber(raw[10]), + from: handleAddress(raw[11]) as Address, + customData: { + gasPerPubdata: handleNumber(raw[12]), + factoryDeps: raw[13] as unknown as string[], + customSignature: bytesToHex(raw[14]), + paymasterParams: arrayToPaymasterParams(raw[15]), + }, + }; + const ethSignature = { + v: Number(handleNumber(raw[7])), + r: raw[8], + s: raw[9], + }; + + if ( + (web3Utils.toHex(ethSignature.r) === '0x' || web3Utils.toHex(ethSignature.s) === '0x') && + !transaction.customData?.customSignature + ) { + return transaction; + } + + if (ethSignature.v !== 0 && ethSignature.v !== 1 && !transaction.customData?.customSignature) { + throw new Error('Failed to parse signature!'); + } + + if (!transaction.customData?.customSignature) { + transaction.signature = new SignatureObject(ethSignature).toString(); + } + + transaction.hash = eip712TxHash(transaction, ethSignature); + + return transaction; +} + +export function getSignature( + transaction: Eip712TxData, + ethSignature?: EthereumSignature, +): Uint8Array { if (transaction?.customData?.customSignature && transaction.customData.customSignature.length) { return web3Utils.bytesToUint8Array(transaction.customData.customSignature); } @@ -601,70 +671,116 @@ export function getSignature(transaction: any, ethSignature?: EthereumSignature) throw new Error('No signature provided!'); } - const r = web3Utils.bytesToUint8Array(web3Utils.padLeft(web3Utils.toHex(ethSignature.r), 32 * 2)); - const s = web3Utils.bytesToUint8Array(web3Utils.padLeft(web3Utils.toHex(ethSignature.s), 32 * 2)); + const r = web3Utils.bytesToUint8Array( + web3Utils.padLeft(web3Utils.toHex(ethSignature.r), 32 * 2), + ); + const s = web3Utils.bytesToUint8Array( + web3Utils.padLeft(web3Utils.toHex(ethSignature.s), 32 * 2), + ); const v = ethSignature.v; return new Uint8Array([...r, ...s, v]); } -// /** -// * Returns the hash of an EIP712 transaction. -// * -// * @param transaction The EIP-712 transaction. -// * @param ethSignature The ECDSA signature of the transaction. -// * -// * @example -// * -// * -// */ -// export function eip712TxHash( -// transaction: Transaction | TransactionRequest, -// ethSignature?: EthereumSignature, -// ): string { -// const signedDigest = EIP712Signer.getSignedDigest(transaction); -// const hashedSignature = web3Utils.keccak256(getSignature(transaction, ethSignature)); - -// return web3Utils.keccak256(concat([signedDigest, hashedSignature])); -// } - -// /** -// * Returns the hash of the L2 priority operation from a given transaction receipt and L2 address. -// * -// * @param txReceipt The receipt of the L1 transaction. -// * @param zkSyncAddress The address of the zkSync Era main contract. -// * -// * @example -// */ -// export function getL2HashFromPriorityOp( -// txReceipt: web3Types.TransactionReceipt, -// zkSyncAddress: web3.Address, -// ): string { -// let txHash: string | null = null; -// for (const log of txReceipt.logs) { -// if (!isAddressEq(log.address as string, zkSyncAddress)) { -// continue; -// } - -// try { -// // TODO: implement at web3.js Contract the parsing of the logs similar to new ethers.Interface(ABI).parseLog(...) -// const priorityQueueLog = ZkSyncMainContract.parseLog({ -// topics: log.topics as string[], -// data: log.data, -// }); -// if (priorityQueueLog && priorityQueueLog.args.txHash !== null) { -// txHash = priorityQueueLog.args.txHash; -// } -// } catch { -// // skip -// } -// } -// if (!txHash) { -// throw new Error('Failed to parse tx logs!'); -// } - -// return txHash; -// } +export function serializeEip712(transaction: Eip712TxData, signature?: SignatureLike): string { + if (!transaction.chainId) { + throw Error("Transaction chainId isn't set!"); + } + + if (!transaction.from) { + throw new Error('Explicitly providing `from` field is required for EIP712 transactions!'); + } + const from = transaction.from; + const meta: Eip712Meta = transaction.customData ?? {}; + const maxFeePerGas = toHex(transaction.maxFeePerGas || transaction.gasPrice || 0); + const maxPriorityFeePerGas = toHex(transaction.maxPriorityFeePerGas || maxFeePerGas); + + const nonce = toHex(transaction.nonce || 0); + const fields: Array = [ + nonce === '0x0' ? new Uint8Array() : toBytes(nonce), + maxPriorityFeePerGas === '0x0' ? new Uint8Array() : toBytes(maxPriorityFeePerGas), + maxFeePerGas === '0x0' ? new Uint8Array() : toBytes(maxFeePerGas), + toHex(transaction.gasLimit || 0) === '0x0' + ? new Uint8Array() + : toBytes(transaction.gasLimit!), + transaction.to ? web3Utils.toChecksumAddress(toHex(transaction.to)) : '0x', + toHex(transaction.value || 0) === '0x0' ? new Uint8Array() : toBytes(nonce), + toHex(transaction.data || '0x'), + ]; + + if (signature) { + const sig = new SignatureObject(signature); + // fields.push(toBytes(sig.yParity)); + fields.push(toBytes(Number(sig.v) === 27 ? 0 : 1)); + fields.push(toBytes(sig.r)); + fields.push(toBytes(sig.s)); + } else { + fields.push(toHex(transaction.chainId)); + fields.push('0x'); + fields.push('0x'); + } + fields.push(toHex(transaction.chainId)); + fields.push(web3Utils.toChecksumAddress(from)); + + // Add meta + fields.push(toHex(meta.gasPerPubdata || DEFAULT_GAS_PER_PUBDATA_LIMIT)); + fields.push((meta.factoryDeps ?? []).map(dep => web3Utils.toHex(dep))); + + if (meta.customSignature && web3Utils.bytesToUint8Array(meta.customSignature).length === 0) { + throw new Error('Empty signatures are not supported!'); + } + fields.push(meta.customSignature || '0x'); + + if (meta.paymasterParams) { + fields.push([ + meta.paymasterParams.paymaster, + web3Utils.toHex(meta.paymasterParams.paymasterInput), + ]); + } else { + fields.push([]); + } + + return concat([new Uint8Array([EIP712_TX_TYPE]), RLP.encode(fields)]); +} + +/** + * Returns the hash of the L2 priority operation from a given transaction receipt and L2 address. + * + * @param txReceipt The receipt of the L1 transaction. + * @param zkSyncAddress The address of the zkSync Era main contract. + * + * @example + */ +export function getL2HashFromPriorityOp( + txReceipt: web3Types.TransactionReceipt, + zkSyncAddress: web3.Address, +): string { + let txHash: string | null = null; + for (const log of txReceipt.logs) { + if (!isAddressEq(log.address as string, zkSyncAddress)) { + continue; + } + + try { + // TODO: implement at web3.js Contract the parsing of the logs similar to new ethers.Interface(ABI).parseLog(...) + // @ts-ignore + const priorityQueueLog = ZkSyncMainContract.parseLog({ + topics: log.topics as string[], + data: log.data, + }); + if (priorityQueueLog && priorityQueueLog.args.txHash !== null) { + txHash = priorityQueueLog.args.txHash; + } + } catch { + // skip + } + } + if (!txHash) { + throw new Error('Failed to parse tx logs!'); + } + + return txHash; +} const ADDRESS_MODULO = 2n ** 160n; @@ -875,7 +991,9 @@ async function isSignatureCorrect( return isECDSASignatureCorrect(address, message, signature); } else { const msgHash = web3Accounts.hashMessage( - typeof message === 'string' ? message : web3Utils.bytesToHex(message as unknown as string), + typeof message === 'string' + ? message + : web3Utils.bytesToHex(message as unknown as string), ); return await isEIP1271SignatureCorrect(context, address, msgHash, signature); } @@ -970,10 +1088,10 @@ export async function isTypedDataSignatureCorrect( message: value, }; // could be also: - // const message = web3Abi.getEncodedEip712Data(data); - // return await isSignatureCorrect(context, address, message, signature); + const message = web3Abi.getEncodedEip712Data(data); + return isSignatureCorrect(context, address, message, signature); - return await isSignatureCorrect(context, address, data, signature); + // return isSignatureCorrect(context, address, data, signature); } /** diff --git a/test/unit/utils.test.ts b/test/unit/utils.test.ts index e75077f..9b94eea 100644 --- a/test/unit/utils.test.ts +++ b/test/unit/utils.test.ts @@ -7,11 +7,12 @@ import { } from '../../src'; import { ADDRESS1, ADDRESS2 } from '../utils'; import * as constants from '../../src/constants'; -import { EIP712Transaction } from '../../src/eip712/EIP712Transaction'; +import type { Eip712TxData } from '../../src/eip712/types'; +import { eip712TxTypedData, getSignInput } from '../../src/utils'; describe('utils', () => { describe('#getHashedL2ToL1Msg()', () => { - it('should return a hashed L2 to L1 message', async () => { + it('should return a hashed L2 to L1 message', () => { const withdrawETHMessage = '0x6c0960f936615cf349d7f6344891b1e7ca7c72883f5dc04900000000000000000000000000000000000000000000000000000001a13b8600'; const withdrawETHMessageHash = @@ -22,31 +23,31 @@ describe('utils', () => { }); describe('#isETH()', () => { - it('should return true for legacy L1 ETH address', async () => { + it('should return true for legacy L1 ETH address', () => { const result = utils.isETH(constants.LEGACY_ETH_ADDRESS); expect(result).toBeTruthy(); }); - it('should return true for L1 ETH address', async () => { + it('should return true for L1 ETH address', () => { const result = utils.isETH(constants.ETH_ADDRESS_IN_CONTRACTS); expect(result).toBeTruthy(); }); - it('should return true for L2 ETH address', async () => { + it('should return true for L2 ETH address', () => { const result = utils.isETH(constants.L2_BASE_TOKEN_ADDRESS); expect(result).toBeTruthy(); }); }); describe('#createAddress()', () => { - it('should return a correct address', async () => { + it('should return a correct address', () => { const address = utils.createAddress(ADDRESS1, 1); expect(address).toBe('0x4B5DF730c2e6b28E17013A1485E5d9BC41Efe021'); }); }); describe('#create2Address()', () => { - it('should return a correct address', async () => { + it('should return a correct address', () => { const address = utils.create2Address( ADDRESS1, '0x010001cb6a6e8d5f6829522f19fa9568660e0a9cd53b2e8be4deb0a679452e41', @@ -58,7 +59,7 @@ describe('utils', () => { }); describe('#applyL1ToL2Alias()', () => { - it('should return the L2 contract address based on provided L1 contract address', async () => { + it('should return the L2 contract address based on provided L1 contract address', () => { const l1ContractAddress = '0x702942B8205E5dEdCD3374E5f4419843adA76Eeb'; const l2ContractAddress = utils.applyL1ToL2Alias(l1ContractAddress); expect(l2ContractAddress.toLowerCase()).toBe( @@ -66,7 +67,7 @@ describe('utils', () => { ); }); - it('should return the L2 contract address by padding zero to left', async () => { + it('should return the L2 contract address by padding zero to left', () => { const l1ContractAddress = '0xeeeeffffffffffffffffffffffffffffffffeeef'; const l2ContractAddress = utils.applyL1ToL2Alias(l1ContractAddress); expect(l2ContractAddress.toLowerCase()).toBe( @@ -76,7 +77,7 @@ describe('utils', () => { }); describe('#undoL1ToL2Alias()', () => { - it('should return the L1 contract address based on provided L2 contract address', async () => { + it('should return the L1 contract address based on provided L2 contract address', () => { const l2ContractAddress = '0x813A42B8205E5DedCd3374e5f4419843ADa77FFC'; const l1ContractAddress = utils.undoL1ToL2Alias(l2ContractAddress); expect(l1ContractAddress.toLowerCase()).toBe( @@ -84,7 +85,7 @@ describe('utils', () => { ); }); - it('should return the L1 contract address by padding zero to left', async () => { + it('should return the L1 contract address by padding zero to left', () => { const l2ContractAddress = '0x1111000000000000000000000000000000001111'; const l1ContractAddress = utils.undoL1ToL2Alias(l2ContractAddress); expect(l1ContractAddress.toLowerCase()).toBe( @@ -103,7 +104,7 @@ describe('utils', () => { }); describe('#checkBaseCost()', () => { - it('should throw an error if the base cost bigger than value', async () => { + it('should throw an error if the base cost bigger than value', () => { const baseCost = 100; const value = 99; try { @@ -117,19 +118,17 @@ describe('utils', () => { }); describe('#serializeEip712()', () => { - it('should throw an error when `tx.chainId` is not specified', async () => { + it('should throw an error when `tx.chainId` is not specified', () => { try { - const txInstance = new EIP712Transaction({}); - txInstance.serialize(); + utils.serializeEip712({}); } catch (e) { expect((e as Error).message).toBe("Transaction chainId isn't set!"); } }); - it('should throw an error when `tx.from` is not specified', async () => { + it('should throw an error when `tx.from` is not specified', () => { try { - const txInstance = new EIP712Transaction({ chainId: 270 }); - txInstance.serialize(); + utils.serializeEip712({ chainId: 270 }); } catch (e) { expect((e as Error).message).toBe( 'Explicitly providing `from` field is required for EIP712 transactions!', @@ -137,63 +136,51 @@ describe('utils', () => { } }); - it.only('should throw an error when `tx.customData.customSignature` is empty string', async () => { + it('should throw an error when `tx.customData.customSignature` is empty string', () => { try { - const txInstance = new EIP712Transaction({ + utils.serializeEip712({ chainId: 270, from: ADDRESS1, customData: { customSignature: '', }, }); - txInstance.serialize(); } catch (e) { expect((e as Error).message).toBe('Empty signatures are not supported'); } }); - it('should return a serialized transaction with populated default values', async () => { - const txInstance = new EIP712Transaction({ - chainId: 270, - from: ADDRESS1, - }); - const result = txInstance.serialize(); - + it('should return a serialized transaction with populated default values', () => { const tx = '0x71ea8080808080808082010e808082010e9436615cf349d7f6344891b1e7ca7c72883f5dc04982c350c080c0'; - expect(result).toBe(tx); - }); - - it('should return a serialized transaction with provided signature', async () => { - const txInstance = new EIP712Transaction({ + const result = utils.serializeEip712({ chainId: 270, from: ADDRESS1, - to: ADDRESS2, - value: 1_000_000, }); - const result = txInstance.serialize(); - - // const tx = - // '0x71f87f8080808094a61464658afeaf65cccaafd3a512b69a83b77618830f42408001a073a20167b8d23b610b058c05368174495adf7da3a4ed4a57eb6dbdeb1fafc24aa02f87530d663a0d061f69bb564d2c6fb46ae5ae776bbd4bd2a2a4478b9cd1b42a82010e9436615cf349d7f6344891b1e7ca7c72883f5dc04982c350c080c0'; - // const signature = ethers.Signature.from( - // '0x73a20167b8d23b610b058c05368174495adf7da3a4ed4a57eb6dbdeb1fafc24aaf87530d663a0d061f69bb564d2c6fb46ae5ae776bbd4bd2a2a4478b9cd1b42a', - // ); - // const result = utils.serializeEip712( - // { - // chainId: 270, - // from: ADDRESS1, - // to: ADDRESS2, - // value: 1_000_000, - // }, - // signature, - // ); - expect(result).toBeDefined(); - // expect(result).toBe(tx); + expect(result).toBe(tx); }); + + // it('should return a serialized transaction with provided signature', async () => { + // const tx = + // '0x71f87f8080808094a61464658afeaf65cccaafd3a512b69a83b77618830f42408001a073a20167b8d23b610b058c05368174495adf7da3a4ed4a57eb6dbdeb1fafc24aa02f87530d663a0d061f69bb564d2c6fb46ae5ae776bbd4bd2a2a4478b9cd1b42a82010e9436615cf349d7f6344891b1e7ca7c72883f5dc04982c350c080c0'; + // const signature = ethers.Signature.from( + // '0x73a20167b8d23b610b058c05368174495adf7da3a4ed4a57eb6dbdeb1fafc24aaf87530d663a0d061f69bb564d2c6fb46ae5ae776bbd4bd2a2a4478b9cd1b42a', + // ); + // const result = utils.serializeEip712( + // { + // chainId: 270, + // from: ADDRESS1, + // to: ADDRESS2, + // value: 1_000_000, + // }, + // signature, + // ); + // expect(result).toBe(tx); + // }); }); describe('#hashBytecode()', () => { - it('should return the hash of bytecode which length is not 2 bytes so padding needs to be performed', async () => { + it('should return the hash of bytecode which length is not 2 bytes so padding needs to be performed', () => { const bytecode = '0x000200000000000200010000000103550000006001100270000000130010019d0000008001000039000000400010043f0000000101200190000000290000c13d0000000001000031000000040110008c000000420000413d0000000101000367000000000101043b000000e001100270000000150210009c000000310000613d000000160110009c000000420000c13d0000000001000416000000000110004c000000420000c13d000000040100008a00000000011000310000001702000041000000200310008c000000000300001900000000030240190000001701100197000000000410004c000000000200a019000000170110009c00000000010300190000000001026019000000000110004c000000420000c13d00000004010000390000000101100367000000000101043b000000000010041b0000000001000019000000490001042e0000000001000416000000000110004c000000420000c13d0000002001000039000001000010044300000120000004430000001401000041000000490001042e0000000001000416000000000110004c000000420000c13d000000040100008a00000000011000310000001702000041000000000310004c000000000300001900000000030240190000001701100197000000000410004c000000000200a019000000170110009c00000000010300190000000001026019000000000110004c000000440000613d00000000010000190000004a00010430000000000100041a000000800010043f0000001801000041000000490001042e0000004800000432000000490001042e0000004a00010430000000000000000000000000000000000000000000000000000000000000000000000000ffffffff0000000200000000000000000000000000000040000001000000000000000000000000000000000000000000000000000000000000000000000000006d4ce63c0000000000000000000000000000000000000000000000000000000060fe47b18000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000080000000000000000000000000000000000000000000000000000000000000000000000000000000009c8c8fa789967eb514f3ec9def748480945cc9b10fcbd1a19597d924eb201083'; const hashedBytecode = utils.hashBytecode(bytecode); @@ -205,7 +192,7 @@ describe('utils', () => { ); }); - it('should return the hash of bytecode which length is 2 bytes so padding does not need to be performed', async () => { + it('should return the hash of bytecode which length is 2 bytes so padding does not need to be performed', () => { const bytecode = '0x0002000000000002000900000000000200010000000103550000006001100270000001980010019d0000008001000039000000400010043f0000000101200190000000340000c13d0000000001000031000000040110008c000003670000413d0000000101000367000000000101043b000000e0011002700000019d0210009c000001420000213d000001a50210009c000001720000213d000001a90210009c000001fd0000613d000001aa0210009c000002210000613d000001ab0110009c000003670000c13d0000000001000416000000000110004c000003670000c13d000000040100008a00000000011000310000019a02000041000000000310004c000000000300001900000000030240190000019a01100197000000000410004c000000000200a0190000019a0110009c00000000010300190000000001026019000000000110004c000003670000c13d0000000201000039000000000101041a000000400200043d00000000001204350000019801000041000001980320009c00000000010240190000004001100210000001ad011001c70000065c0001042e0000000001000416000000000110004c000003670000c13d00000000020000310000001f01200039000000200a00008a0000000004a1016f000000400100043d0000000003140019000000000443004b00000000040000190000000104004039000001990530009c000003c90000213d0000000104400190000003c90000c13d000000400030043f0000001f0320018f00000001040003670000000505200272000000520000613d000000000600001900000005076002100000000008710019000000000774034f000000000707043b00000000007804350000000106600039000000000756004b0000004a0000413d000000000630004c000000610000613d0000000505500210000000000454034f00000000055100190000000303300210000000000605043300000000063601cf000000000636022f000000000404043b0000010003300089000000000434022f00000000033401cf000000000363019f00000000003504350000019a03000041000000600420008c000000000400001900000000040340190000019a05200197000000000650004c000000000300a0190000019a0550009c000000000304c019000000000330004c000003670000c13d0000000034010434000001990540009c000003670000213d000000000221001900000000041400190000001f054000390000019a06000041000000000725004b000000000700001900000000070680190000019a055001970000019a08200197000000000985004b0000000006008019000000000585013f0000019a0550009c00000000050700190000000005066019000000000550004c000003670000c13d0000000005040433000001990650009c000003c90000213d0000003f065000390000000006a6016f000000400b00043d00000000066b00190000000007b6004b00000000070000190000000107004039000001990860009c000003c90000213d0000000107700190000003c90000c13d000000400060043f000000000c5b043600000020065000390000000007460019000000000727004b000003670000213d000000000750004c0000009e0000613d000000000700001900000020077000390000000008b70019000000000947001900000000090904330000000000980435000000000857004b000000970000413d00000000046b001900000000000404350000000003030433000001990430009c000003670000213d00000000031300190000001f043000390000019a05000041000000000624004b000000000600001900000000060580190000019a044001970000019a07200197000000000874004b0000000005008019000000000474013f0000019a0440009c00000000040600190000000004056019000000000440004c000003670000c13d0000000004030433000001990540009c000003c90000213d0000003f054000390000000005a5016f000000400800043d0000000005580019000000000685004b00000000060000190000000106004039000001990750009c000003c90000213d0000000106600190000003c90000c13d000000400050043f0000000005480436000800000005001d00000020054000390000000006350019000000000226004b000003670000213d00060000000c001d00090000000b001d00070000000a001d000000000240004c000000d50000613d000000000200001900000020022000390000000006820019000000000732001900000000070704330000000000760435000000000642004b000000ce0000413d0000000002580019000000000002043500000040011000390000000001010433000500000001001d000000ff0110008c0000000901000029000003670000213d0000000001010433000400000001001d000001990110009c000003c90000213d000100000008001d0000000301000039000300000001001d000000000101041a000000010210019000000001011002700000007f0310018f0000000001036019000200000001001d0000001f0110008c00000000010000190000000101002039000000010110018f000000000112004b0000021b0000c13d0000000201000029000000200110008c000001100000413d0000000301000029000000000010043500000198010000410000000002000414000001980320009c0000000001024019000000c0011002100000019b011001c70000801002000039065b06560000040f0000000102200190000003670000613d00000004030000290000001f023000390000000502200270000000200330008c0000000002004019000000000301043b00000002010000290000001f01100039000000050110027000000000011300190000000002230019000000000312004b000001100000813d000000000002041b0000000102200039000000000312004b0000010c0000413d00000004010000290000001f0110008c000004990000a13d0000000301000029000000000010043500000198010000410000000002000414000001980320009c0000000001024019000000c0011002100000019b011001c70000801002000039065b06560000040f000000010220019000000007020000290000000906000029000003670000613d000000040300002900000000032301700000002002000039000000000101043b000001300000613d0000002002000039000000000400001900000000056200190000000005050433000000000051041b000000200220003900000001011000390000002004400039000000000534004b000001280000413d0000000404000029000000000343004b0000013e0000813d00000004030000290000000303300210000000f80330018f000000010400008a000000000334022f000000000343013f000000090400002900000000024200190000000002020433000000000232016f000000000021041b0000000401000029000000010110021000000001011001bf000004a70000013d0000019e0210009c000001c60000213d000001a20210009c000002440000613d000001a30210009c000002700000613d000001a40110009c000003670000c13d0000000001000416000000000110004c000003670000c13d000000040100008a00000000011000310000019a02000041000000000310004c000000000300001900000000030240190000019a01100197000000000410004c000000000200a0190000019a0110009c00000000010300190000000001026019000000000110004c000003670000c13d0000000405000039000000000405041a000000010640019000000001014002700000007f0210018f00000000010260190000001f0210008c00000000020000190000000102002039000000000224013f00000001022001900000021b0000c13d000000400200043d0000000003120436000000000660004c000003800000c13d000001000500008a000000000454016f0000000000430435000000000110004c000000200400003900000000040060190000038d0000013d000001a60210009c000002940000613d000001a70210009c000002e30000613d000001a80110009c000003670000c13d0000000001000416000000000110004c000003670000c13d000000040100008a00000000011000310000019a02000041000000400310008c000000000300001900000000030240190000019a01100197000000000410004c000000000200a0190000019a0110009c00000000010300190000000001026019000000000110004c000003670000c13d00000004010000390000000101100367000000000101043b000900000001001d000001ac0110009c000003670000213d0000000001000411000700000001001d00000000001004350000000101000039000800000001001d000000200010043f00000198010000410000000002000414000001980320009c0000000001024019000000c001100210000001ae011001c70000801002000039065b06560000040f0000000102200190000003670000613d000000000101043b00000009020000290000000000200435000000200010043f00000024010000390000000101100367000000000101043b000600000001001d00000198010000410000000002000414000001980320009c0000000001024019000000c001100210000001ae011001c70000801002000039065b06560000040f0000000102200190000003670000613d000000000101043b000000000101041a00000006020000290000000003210019000000000113004b000000000100001900000001010040390000000101100190000003ae0000c13d00000007010000290000000902000029065b05ea0000040f000000400100043d000000080200002900000000002104350000019802000041000001980310009c00000000010280190000004001100210000001ad011001c70000065c0001042e0000019f0210009c000002ff0000613d000001a00210009c000003510000613d000001a10110009c000003670000c13d0000000001000416000000000110004c000003670000c13d000000040100008a00000000011000310000019a02000041000000400310008c000000000300001900000000030240190000019a01100197000000000410004c000000000200a0190000019a0110009c00000000010300190000000001026019000000000110004c000003670000c13d00000001020003670000000401200370000000000101043b000001ac0310009c000003670000213d0000002402200370000000000302043b000001ac0230009c000003670000213d00000000001004350000000101000039000000200010043f0000004002000039000900000002001d0000000001000019000800000003001d065b052b0000040f00000008020000290000000000200435000000200010043f00000000010000190000000902000029065b052b0000040f000000000101041a000000400200043d00000000001204350000019801000041000001980320009c00000000010240190000004001100210000001ad011001c70000065c0001042e0000000001000416000000000110004c000003670000c13d000000040100008a00000000011000310000019a02000041000000000310004c000000000300001900000000030240190000019a01100197000000000410004c000000000200a0190000019a0110009c00000000010300190000000001026019000000000110004c000003670000c13d0000000303000039000000000203041a000000010420019000000001012002700000007f0510018f000000000601001900000000060560190000001f0560008c00000000050000190000000105002039000000000552013f0000000105500190000003690000613d000001b70100004100000000001004350000002201000039000000040010043f000001b8010000410000065d000104300000000001000416000000000110004c000003670000c13d000000040100008a00000000011000310000019a02000041000000400310008c000000000300001900000000030240190000019a01100197000000000410004c000000000200a0190000019a0110009c00000000010300190000000001026019000000000110004c000003670000c13d00000001010003670000000402100370000000000202043b000001ac0320009c000003670000213d0000002401100370000000000301043b0000000001000411065b05ea0000040f0000000101000039000000400200043d00000000001204350000019801000041000001980320009c00000000010240190000004001100210000001ad011001c70000065c0001042e0000000001000416000000000110004c000003670000c13d000000040100008a00000000011000310000019a02000041000000400310008c000000000300001900000000030240190000019a01100197000000000410004c000000000200a0190000019a0110009c00000000010300190000000001026019000000000110004c000003670000c13d00000001010003670000000402100370000000000402043b000001ac0240009c000003670000213d0000002401100370000000000501043b000000000140004c000003a60000c13d000000400100043d0000004402100039000001b503000041000000000032043500000024021000390000001f030000390000000000320435000001b10200004100000000002104350000000402100039000000200300003900000000003204350000019802000041000001980310009c00000000010280190000004001100210000001b6011001c70000065d000104300000000001000416000000000110004c000003670000c13d000000040100008a00000000011000310000019a02000041000000200310008c000000000300001900000000030240190000019a01100197000000000410004c000000000200a0190000019a0110009c00000000010300190000000001026019000000000110004c000003670000c13d00000004010000390000000101100367000000000101043b000001ac0210009c000003670000213d0000000000100435000000200000043f00000040020000390000000001000019065b052b0000040f000000000101041a000000400200043d00000000001204350000019801000041000001980320009c00000000010240190000004001100210000001ad011001c70000065c0001042e0000000001000416000000000110004c000003670000c13d000000040100008a00000000011000310000019a02000041000000600310008c000000000300001900000000030240190000019a01100197000000000410004c000000000200a0190000019a0110009c00000000010300190000000001026019000000000110004c000003670000c13d00000001010003670000000402100370000000000402043b000001ac0240009c000003670000213d0000002402100370000000000202043b000900000002001d000001ac0220009c000003670000213d0000004401100370000000000101043b000700000001001d00000000004004350000000101000039000600000001001d000000200010043f00000198010000410000000002000414000001980320009c0000000001024019000000c001100210000001ae011001c70000801002000039000800000004001d065b06560000040f0000000102200190000003670000613d000000000101043b0000000002000411000500000002001d0000000000200435000000200010043f00000198010000410000000002000414000001980320009c0000000001024019000000c001100210000001ae011001c70000801002000039065b06560000040f00000008030000290000000102200190000003670000613d000000000101043b000000000201041a000000010100008a000000000112004b0000041c0000c13d000000000103001900000009020000290000000703000029065b05570000040f000000400100043d000000060200002900000000002104350000019802000041000001980310009c00000000010280190000004001100210000001ad011001c70000065c0001042e0000000001000416000000000110004c000003670000c13d000000040100008a00000000011000310000019a02000041000000000310004c000000000300001900000000030240190000019a01100197000000000410004c000000000200a0190000019a0110009c00000000010300190000000001026019000000000110004c000003670000c13d0000000501000039000000000101041a000000ff0110018f000000400200043d00000000001204350000019801000041000001980320009c00000000010240190000004001100210000001ad011001c70000065c0001042e0000000001000416000000000110004c000003670000c13d000000040100008a00000000011000310000019a02000041000000400310008c000000000300001900000000030240190000019a01100197000000000410004c000000000200a0190000019a0110009c00000000010300190000000001026019000000000110004c000003670000c13d00000001010003670000000402100370000000000202043b000900000002001d000001ac0220009c000003670000213d0000002401100370000000000101043b000800000001001d0000000001000411000600000001001d00000000001004350000000101000039000700000001001d000000200010043f00000198010000410000000002000414000001980320009c0000000001024019000000c001100210000001ae011001c70000801002000039065b06560000040f0000000102200190000003670000613d000000000101043b00000009020000290000000000200435000000200010043f00000198010000410000000002000414000001980320009c0000000001024019000000c001100210000001ae011001c70000801002000039065b06560000040f0000000102200190000003670000613d000000000101043b000000000101041a0000000803000029000000000231004b0000040f0000813d000000400100043d0000006402100039000001af0300004100000000003204350000004402100039000001b0030000410000000000320435000000240210003900000025030000390000000000320435000001b10200004100000000002104350000000402100039000000200300003900000000003204350000019802000041000001980310009c00000000010280190000004001100210000001b2011001c70000065d000104300000000001000416000000000110004c000003670000c13d000000040100008a00000000011000310000019a02000041000000400310008c000000000300001900000000030240190000019a01100197000000000410004c000000000200a0190000019a0110009c00000000010300190000000001026019000000000110004c000003670000c13d00000001010003670000000402100370000000000202043b000001ac0320009c000003730000a13d00000000010000190000065d00010430000000800060043f000000000440004c000003b40000c13d000001000300008a000000000232016f000000a00020043f000000000160004c000000c001000039000000a001006039000003c30000013d0000002401100370000000000301043b0000000001000411065b05570000040f0000000101000039000000400200043d00000000001204350000019801000041000001980320009c00000000010240190000004001100210000001ad011001c70000065c0001042e0000000000500435000000000410004c00000000040000190000038d0000613d000001b30500004100000000040000190000000006430019000000000705041a000000000076043500000001055000390000002004400039000000000614004b000003860000413d0000003f01400039000000200300008a000000000331016f0000000001230019000000000331004b00000000040000190000000104004039000001990310009c000003c90000213d0000000103400190000003c90000c13d000000400010043f000900000001001d065b05410000040f000000090400002900000000014100490000019802000041000001980310009c0000000001028019000001980340009c000000000204401900000040022002100000006001100210000000000121019f0000065c0001042e0000000201000039000000000301041a0000000002530019000000000332004b000000000300001900000001030040390000000103300190000003de0000613d000001b70100004100000000001004350000001101000039000000040010043f000001b8010000410000065d000104300000000000300435000000a001000039000000000260004c000003cf0000613d000001bf0200004100000000040000190000000003040019000000000402041a000000a005300039000000000045043500000001022000390000002004300039000000000564004b000003ba0000413d000000c0013000390000001f01100039000000200200008a000000000121016f000001c002100041000001c10220009c000003cf0000813d000001b70100004100000000001004350000004101000039000000040010043f000001b8010000410000065d00010430000900000001001d000000400010043f0000008002000039065b05410000040f000000090400002900000000014100490000019802000041000001980310009c0000000001028019000001980340009c000000000204401900000040022002100000006001100210000000000121019f0000065c0001042e000800000005001d000000000021041b0000000000400435000000200000043f00000198010000410000000002000414000001980320009c0000000001024019000000c001100210000001ae011001c70000801002000039000900000004001d065b06560000040f00000009060000290000000102200190000003670000613d000000000101043b000000000201041a00000008030000290000000002320019000000000021041b000000400100043d000000000031043500000198020000410000000003000414000001980430009c0000000003028019000001980410009c00000000010280190000004001100210000000c002300210000000000112019f0000019b011001c70000800d020000390000000303000039000001b4040000410000000005000019065b06510000040f0000000101200190000003670000613d000000400100043d000000010200003900000000002104350000019802000041000001980310009c00000000010280190000004001100210000001ad011001c70000065c0001042e000000000331004900000006010000290000000902000029065b05ea0000040f000000400100043d000000070200002900000000002104350000019802000041000001980310009c00000000010280190000004001100210000001ad011001c70000065c0001042e0000000701000029000000000112004b000004310000813d000000400100043d0000004402100039000001be03000041000000000032043500000024021000390000001d030000390000000000320435000001b10200004100000000002104350000000402100039000000200300003900000000003204350000019802000041000001980310009c00000000010280190000004001100210000001b6011001c70000065d00010430000400000002001d000000000130004c000004490000c13d000000400100043d0000006402100039000001bc0300004100000000003204350000004402100039000001bd030000410000000000320435000000240210003900000024030000390000000000320435000001b10200004100000000002104350000000402100039000000200300003900000000003204350000019802000041000001980310009c00000000010280190000004001100210000001b2011001c70000065d000104300000000501000029000001ac01100198000500000001001d000004620000c13d000000400100043d0000006402100039000001ba0300004100000000003204350000004402100039000001bb030000410000000000320435000000240210003900000022030000390000000000320435000001b10200004100000000002104350000000402100039000000200300003900000000003204350000019802000041000001980310009c00000000010280190000004001100210000001b2011001c70000065d00010430000000080100002900000000001004350000000601000029000000200010043f00000198010000410000000002000414000001980320009c0000000001024019000000c001100210000001ae011001c70000801002000039065b06560000040f0000000102200190000003670000613d000000000101043b00000005020000290000000000200435000000200010043f00000198010000410000000002000414000001980320009c0000000001024019000000c001100210000001ae011001c70000801002000039065b06560000040f00000004030000290000000102200190000003670000613d00000007020000290000000002230049000000000101043b000000000021041b000000400100043d000000000021043500000198020000410000000003000414000001980430009c0000000003028019000001980410009c00000000010280190000004001100210000000c002300210000000000112019f0000019b011001c70000800d020000390000000303000039000001b90400004100000008050000290000000506000029065b06510000040f00000008030000290000000101200190000002d60000c13d000003670000013d0000000401000029000000000110004c00000000010000190000049f0000613d0000000601000029000000000101043300000004040000290000000302400210000000010300008a000000000223022f000000000232013f000000000121016f0000000102400210000000000121019f0000000302000029000000000012041b00000001010000290000000001010433000900000001001d000001990110009c000003c90000213d0000000401000039000600000001001d000000000101041a000000010210019000000001021002700000007f0320018f0000000002036019000400000002001d0000001f0220008c00000000020000190000000102002039000000000121013f00000001011001900000021b0000c13d0000000401000029000000200110008c000004dc0000413d0000000601000029000000000010043500000198010000410000000002000414000001980320009c0000000001024019000000c0011002100000019b011001c70000801002000039065b06560000040f0000000102200190000003670000613d00000009030000290000001f023000390000000502200270000000200330008c0000000002004019000000000301043b00000004010000290000001f01100039000000050110027000000000011300190000000002230019000000000312004b000004dc0000813d000000000002041b0000000102200039000000000312004b000004d80000413d00000009010000290000001f0110008c0000050e0000a13d0000000601000029000000000010043500000198010000410000000002000414000001980320009c0000000001024019000000c0011002100000019b011001c70000801002000039065b06560000040f000000010220019000000007020000290000000106000029000003670000613d000000090300002900000000032301700000002002000039000000000101043b000004fc0000613d0000002002000039000000000400001900000000056200190000000005050433000000000051041b000000200220003900000001011000390000002004400039000000000534004b000004f40000413d0000000904000029000000000343004b0000050a0000813d00000009030000290000000303300210000000f80330018f000000010400008a000000000334022f000000000343013f000000010400002900000000024200190000000002020433000000000232016f000000000021041b0000000101000039000000090200002900000001022002100000051b0000013d0000000901000029000000000110004c0000000001000019000005140000613d0000000801000029000000000101043300000009040000290000000302400210000000010300008a000000000223022f000000000232013f000000000221016f0000000101400210000000000112019f0000000602000029000000000012041b0000000501000039000000000201041a000001000300008a000000000232016f0000000503000029000000ff0330018f000000000232019f000000000021041b0000002001000039000001000010044300000120000004430000019c010000410000065c0001042e0000019803000041000001980410009c00000000010380190000004001100210000001980420009c00000000020380190000006002200210000000000112019f0000000002000414000001980420009c0000000002038019000000c002200210000000000112019f000001c2011001c70000801002000039065b06560000040f00000001022001900000053f0000613d000000000101043b000000000001042d00000000010000190000065d0001043000000020030000390000000004310436000000000302043300000000003404350000004001100039000000000430004c000005500000613d000000000400001900000000054100190000002004400039000000000624001900000000060604330000000000650435000000000534004b000005490000413d000000000231001900000000000204350000001f02300039000000200300008a000000000232016f0000000001210019000000000001042d0004000000000002000400000003001d000001ac01100198000005ab0000613d000001ac02200198000200000002001d000005c00000613d000300000001001d0000000000100435000000200000043f00000198010000410000000002000414000001980320009c0000000001024019000000c001100210000001ae011001c70000801002000039065b06560000040f0000000102200190000005a90000613d000000000101043b000000000201041a0000000401000029000100000002001d000000000112004b000005d50000413d00000003010000290000000000100435000000200000043f00000198010000410000000002000414000001980320009c0000000001024019000000c001100210000001ae011001c70000801002000039065b06560000040f0000000102200190000005a90000613d000000040200002900000001030000290000000002230049000000000101043b000000000021041b0000000201000029000000000010043500000198010000410000000002000414000001980320009c0000000001024019000000c001100210000001ae011001c70000801002000039065b06560000040f0000000102200190000005a90000613d000000000101043b000000000201041a00000004030000290000000002320019000000000021041b000000400100043d000000000031043500000198020000410000000003000414000001980430009c0000000003028019000001980410009c00000000010280190000004001100210000000c002300210000000000112019f0000019b011001c70000800d020000390000000303000039000001b40400004100000003050000290000000206000029065b06510000040f0000000101200190000005a90000613d000000000001042d00000000010000190000065d00010430000000400100043d0000006402100039000001c70300004100000000003204350000004402100039000001c8030000410000000000320435000000240210003900000025030000390000000000320435000001b10200004100000000002104350000000402100039000000200300003900000000003204350000019802000041000001980310009c00000000010280190000004001100210000001b2011001c70000065d00010430000000400100043d0000006402100039000001c50300004100000000003204350000004402100039000001c6030000410000000000320435000000240210003900000023030000390000000000320435000001b10200004100000000002104350000000402100039000000200300003900000000003204350000019802000041000001980310009c00000000010280190000004001100210000001b2011001c70000065d00010430000000400100043d0000006402100039000001c30300004100000000003204350000004402100039000001c4030000410000000000320435000000240210003900000026030000390000000000320435000001b10200004100000000002104350000000402100039000000200300003900000000003204350000019802000041000001980310009c00000000010280190000004001100210000001b2011001c70000065d000104300003000000000002000001ac01100198000006270000613d000200000003001d000001ac02200198000300000002001d0000063c0000613d000100000001001d00000000001004350000000101000039000000200010043f00000198010000410000000002000414000001980320009c0000000001024019000000c001100210000001ae011001c70000801002000039065b06560000040f00000001022001900000000304000029000006250000613d000000000101043b0000000000400435000000200010043f00000198010000410000000002000414000001980320009c0000000001024019000000c001100210000001ae011001c70000801002000039065b06560000040f00000003060000290000000102200190000006250000613d000000000101043b0000000202000029000000000021041b000000400100043d000000000021043500000198020000410000000003000414000001980430009c0000000003028019000001980410009c00000000010280190000004001100210000000c002300210000000000112019f0000019b011001c70000800d020000390000000303000039000001b9040000410000000105000029065b06510000040f0000000101200190000006250000613d000000000001042d00000000010000190000065d00010430000000400100043d0000006402100039000001bc0300004100000000003204350000004402100039000001bd030000410000000000320435000000240210003900000024030000390000000000320435000001b10200004100000000002104350000000402100039000000200300003900000000003204350000019802000041000001980310009c00000000010280190000004001100210000001b2011001c70000065d00010430000000400100043d0000006402100039000001ba0300004100000000003204350000004402100039000001bb030000410000000000320435000000240210003900000022030000390000000000320435000001b10200004100000000002104350000000402100039000000200300003900000000003204350000019802000041000001980310009c00000000010280190000004001100210000001b2011001c70000065d0001043000000654002104210000000102000039000000000001042d0000000002000019000000000001042d00000659002104230000000102000039000000000001042d0000000002000019000000000001042d0000065b000004320000065c0001042e0000065d000104300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffff000000000000000000000000000000000000000000000000ffffffffffffffff8000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000002000000000000000000000000000000002000000000000000000000000000000400000010000000000000000000000000000000000000000000000000000000000000000000000000040c10f1800000000000000000000000000000000000000000000000000000000a457c2d600000000000000000000000000000000000000000000000000000000a457c2d700000000000000000000000000000000000000000000000000000000a9059cbb00000000000000000000000000000000000000000000000000000000dd62ed3e0000000000000000000000000000000000000000000000000000000040c10f190000000000000000000000000000000000000000000000000000000070a082310000000000000000000000000000000000000000000000000000000095d89b410000000000000000000000000000000000000000000000000000000023b872dc0000000000000000000000000000000000000000000000000000000023b872dd00000000000000000000000000000000000000000000000000000000313ce56700000000000000000000000000000000000000000000000000000000395093510000000000000000000000000000000000000000000000000000000006fdde0300000000000000000000000000000000000000000000000000000000095ea7b30000000000000000000000000000000000000000000000000000000018160ddd000000000000000000000000ffffffffffffffffffffffffffffffffffffffff00000000000000000000000000000000000000200000000000000000000000000200000000000000000000000000000000000040000000000000000000000000207a65726f00000000000000000000000000000000000000000000000000000045524332303a2064656372656173656420616c6c6f77616e63652062656c6f7708c379a00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000840000000000000000000000008a35acfbc15ff81a39ae7d344fd709f28e8600b4aa8c65c6b64bfe7fe36bd19bddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef45524332303a206d696e7420746f20746865207a65726f20616464726573730000000000000000000000000000000000000000640000000000000000000000004e487b710000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000240000000000000000000000008c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925737300000000000000000000000000000000000000000000000000000000000045524332303a20617070726f766520746f20746865207a65726f206164647265726573730000000000000000000000000000000000000000000000000000000045524332303a20617070726f76652066726f6d20746865207a65726f2061646445524332303a20696e73756666696369656e7420616c6c6f77616e6365000000c2575a0e9e593c00f959f8c92f12db2869c3395a3b0502d05e2516446f71f85bffffffffffffffffffffffffffffffffffffffffffffffff0000000000000000ffffffffffffffffffffffffffffffffffffffffffffffff00000000000000800200000000000000000000000000000000000000000000000000000000000000616c616e6365000000000000000000000000000000000000000000000000000045524332303a207472616e7366657220616d6f756e7420657863656564732062657373000000000000000000000000000000000000000000000000000000000045524332303a207472616e7366657220746f20746865207a65726f2061646472647265737300000000000000000000000000000000000000000000000000000045524332303a207472616e736665722066726f6d20746865207a65726f206164000000000000000000000000000000000000000000000000000000000000000018469939d00da7016fd24775544e09a6a1ad29697146a060aa4a0baa144c2ede'; const hashedBytecode = utils.hashBytecode(bytecode); @@ -217,7 +204,7 @@ describe('utils', () => { ); }); - it('should throw an error when bytecode is not divisible by 32', async () => { + it('should throw an error when bytecode is not divisible by 32', () => { try { utils.hashBytecode('0x0002'); } catch (e) { @@ -227,7 +214,7 @@ describe('utils', () => { } }); - it('should throw an error when bytecode is has even number of 32-byte words', async () => { + it('should throw an error when bytecode is has even number of 32-byte words', () => { try { utils.hashBytecode(`0x${'00020000000000020009000000000002'.repeat(2)}`); } catch (e) { @@ -238,62 +225,62 @@ describe('utils', () => { }); }); - // describe('#parseEip712()', () => { - // it('should parse a transaction with a signature', async () => { - // const tx: types.TransactionLike = { - // type: 113, - // nonce: 0, - // maxPriorityFeePerGas: 0n, - // maxFeePerGas: 0n, - // gasLimit: 0n, - // to: ADDRESS2, - // value: 1_000_000n, - // data: '0x', - // chainId: 270n, - // from: ADDRESS1, - // customData: { - // gasPerPubdata: 50_000n, - // factoryDeps: [], - // customSignature: '0x', - // paymasterParams: undefined, - // }, - // hash: '0x9ed410ce33179ac1ff6b721060605afc72d64febfe0c08cacab5a246602131ee', - // }; - - // const serializedTx = - // '0x71f87f8080808094a61464658afeaf65cccaafd3a512b69a83b77618830f42408001a073a20167b8d23b610b058c05368174495adf7da3a4ed4a57eb6dbdeb1fafc24aa02f87530d663a0d061f69bb564d2c6fb46ae5ae776bbd4bd2a2a4478b9cd1b42a82010e9436615cf349d7f6344891b1e7ca7c72883f5dc04982c350c080c0'; - // const result = utils.parseEip712(serializedTx); - // expect(result).toEqual(tx); - // }); - - // it('should parse a transaction without a signature', async () => { - // const tx: types.TransactionLike = { - // type: 113, - // nonce: 0, - // maxPriorityFeePerGas: 0n, - // maxFeePerGas: 0n, - // gasLimit: 0n, - // to: ADDRESS2, - // value: 0n, - // data: '0x', - // chainId: 270n, - // from: ADDRESS1, - // customData: { - // gasPerPubdata: 50_000n, - // factoryDeps: [], - // customSignature: '0x', - // paymasterParams: undefined, - // }, - // hash: '0x7d3aab3e3d06d6a702228d911c2a9afaccddd52514fb89dc9d0ff81a67bfff04', - // }; - - // const serializedTx = - // '0x71f83e8080808094a61464658afeaf65cccaafd3a512b69a83b77618808082010e808082010e9436615cf349d7f6344891b1e7ca7c72883f5dc04982c350c080c0'; - - // const result = utils.parseEip712(serializedTx); - // expect(result).toEqual(tx); - // }); - // }); + describe('#parseEip712()', () => { + it('should parse a transaction with a signature', () => { + const tx: Eip712TxData = { + type: 113, + nonce: 0n, + maxPriorityFeePerGas: 0n, + maxFeePerGas: 0n, + gasLimit: 0n, + to: ADDRESS2, + value: 1_000_000n, + data: '0x', + chainId: 270n, + from: ADDRESS1, + customData: { + gasPerPubdata: 50_000n, + factoryDeps: [], + customSignature: '0x', + paymasterParams: undefined, + }, + hash: '0x9ed410ce33179ac1ff6b721060605afc72d64febfe0c08cacab5a246602131ee', + }; + + const serializedTx = + '0x71f87f8080808094a61464658afeaf65cccaafd3a512b69a83b77618830f42408001a073a20167b8d23b610b058c05368174495adf7da3a4ed4a57eb6dbdeb1fafc24aa02f87530d663a0d061f69bb564d2c6fb46ae5ae776bbd4bd2a2a4478b9cd1b42a82010e9436615cf349d7f6344891b1e7ca7c72883f5dc04982c350c080c0'; + const result = utils.parseEip712(serializedTx); + expect(result).toEqual(tx); + }); + + it('should parse a transaction without a signature', () => { + const tx: Eip712TxData = { + type: 113, + nonce: 0n, + maxPriorityFeePerGas: 0n, + maxFeePerGas: 0n, + gasLimit: 0n, + to: ADDRESS2, + value: 0n, + data: '0x', + chainId: 270n, + from: ADDRESS1, + customData: { + gasPerPubdata: 50_000n, + factoryDeps: [], + customSignature: '0x', + paymasterParams: undefined, + }, + hash: '0x7d3aab3e3d06d6a702228d911c2a9afaccddd52514fb89dc9d0ff81a67bfff04', + }; + + const serializedTx = + '0x71f83e8080808094a61464658afeaf65cccaafd3a512b69a83b77618808082010e808082010e9436615cf349d7f6344891b1e7ca7c72883f5dc04982c350c080c0'; + + const result = utils.parseEip712(serializedTx); + expect(result).toEqual(tx); + }); + }); describe('#isMessageSignatureCorrect()', () => { it('should return true if signature made by a private key was correct', async () => { const wallet = web3Accounts.create(); @@ -314,55 +301,60 @@ describe('utils', () => { describe('#isTypedDataSignatureCorrect()', () => { // TODO: Needs investigation it.skip('should return true if correct', async () => { - // const wallet = web3Accounts.create(); - // const ADDRESS = wallet.address; - // const PRIVATE_KEY = wallet.privateKey; + const account = web3Accounts.create(); + const ADDRESS = account.address; + const PRIVATE_KEY = account.privateKey; - const ADDRESS = '0x99F3629e38c617cb619682f721Aaf9F61a3DE3d3'; - const PRIVATE_KEY = '0x5b032dc95add073bbacb2a2cbe0d667855cca807abe1461c72257b7ee0d7d334'; + // const ADDRESS = '0x99F3629e38c617cb619682f721Aaf9F61a3DE3d3'; + // const PRIVATE_KEY = + // '0x5b032dc95add073bbacb2a2cbe0d667855cca807abe1461c72257b7ee0d7d334'; console.log('ADDRESS', ADDRESS); console.log('PRIVATE_KEY', PRIVATE_KEY); - const web3 = new Web3(); + const web3 = new Web3('https://mainnet.era.zksync.io'); const tx = { type: 113, chainId: 300, from: ADDRESS, to: '0xa61464658AfeAf65CccaaFD3a512b69A83B77618', - value: BigInt(7_000_000), + value: 7_000_000, }; // const eip712Signer = new EIP712Signer(new Wallet(PRIVATE_KEY), web3.config.chainId); - // const signInput = EIP712Signer.getSignInput(tx); - - const signInput = { - txType: 113, - from: '0x99F3629e38c617cb619682f721Aaf9F61a3DE3d3', - to: '0xa61464658AfeAf65CccaaFD3a512b69A83B77618', - gasLimit: 0n, - gasPerPubdataByteLimit: 50000, - maxFeePerGas: 0n, - maxPriorityFeePerGas: 0n, - paymaster: '0x0000000000000000000000000000000000000000', - nonce: 0, - value: 7000000n, - data: '0x', - factoryDeps: [], - paymasterInput: '0x', - }; + const signInput = getSignInput(tx); + + // const signInput = { + // txType: 113, + // from: '0x99F3629e38c617cb619682f721Aaf9F61a3DE3d3', + // to: '0xa61464658AfeAf65CccaaFD3a512b69A83B77618', + // gasLimit: 0n, + // gasPerPubdataByteLimit: 50000, + // maxFeePerGas: 0n, + // maxPriorityFeePerGas: 0n, + // paymaster: '0x0000000000000000000000000000000000000000', + // nonce: 0, + // value: 7000000n, + // data: '0x', + // factoryDeps: [], + // paymasterInput: '0x', + // }; const domain = { name: 'zkSync', version: '2', chainId: tx.chainId, }; console.log('domain', domain); + web3.eth.accounts.wallet.add(account); - // const signature = await eip712Signer.sign(tx); + console.log('account', account); + const typedData = eip712TxTypedData(tx); + console.log('typedData', typedData); + const signature = await web3.eth.signTypedData(account.address, typedData); - const signature = - '0x14920a5306c8b739fd2fbdd6bb933c54c05391ab9454741b5fa1132c31c6f35d4f8a716939e43df6eb660b5298d8354d08710a0d94e8d0c14dc88032cac5deda1b'; + // const signature = + // '0x14920a5306c8b739fd2fbdd6bb933c54c05391ab9454741b5fa1132c31c6f35d4f8a716939e43df6eb660b5298d8354d08710a0d94e8d0c14dc88032cac5deda1b'; console.log('signature\n\t', signature); const isValidSignature = await utils.isTypedDataSignatureCorrect( @@ -377,5 +369,4 @@ describe('utils', () => { expect(isValidSignature).toBe(true); }); }); - }); From 93b28a51a123c04f26c5d7c4f27421c3e67a89d4 Mon Sep 17 00:00:00 2001 From: Oleksii Kosynskyi Date: Tue, 11 Jun 2024 19:10:16 -0400 Subject: [PATCH 17/30] remove tx type --- src/constants.ts | 4 + src/eip712/EIP712Transaction.ts | 572 -------------------------------- src/eip712/constants.ts | 12 - src/eip712/types.ts | 89 ----- src/plugin.ts | 5 - src/types.ts | 79 ++++- src/utils.ts | 16 +- test/unit/utils.test.ts | 90 ++--- 8 files changed, 138 insertions(+), 729 deletions(-) delete mode 100644 src/eip712/EIP712Transaction.ts delete mode 100644 src/eip712/constants.ts delete mode 100644 src/eip712/types.ts diff --git a/src/constants.ts b/src/constants.ts index 518b15a..bdbc5c6 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -175,3 +175,7 @@ export const EIP712_TYPES = { { name: 'paymasterInput', type: 'bytes' }, ], }; + +export const MAX_INTEGER = BigInt( + '0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff', +); diff --git a/src/eip712/EIP712Transaction.ts b/src/eip712/EIP712Transaction.ts deleted file mode 100644 index a803d2c..0000000 --- a/src/eip712/EIP712Transaction.ts +++ /dev/null @@ -1,572 +0,0 @@ -/* -This file is part of web3.js. - -web3.js is free software: you can redistribute it and/or modify -it under the terms of the GNU Lesser General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -web3.js is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU Lesser General Public License for more details. - -You should have received a copy of the GNU Lesser General Public License -along with web3.js. If not, see . -*/ -import type { Bytes } from 'web3-types'; -import { RLP } from '@ethereumjs/rlp'; -import { keccak256 } from 'ethereum-cryptography/keccak.js'; -import { - bytesToHex, - bytesToUint8Array, - hexToBytes, - toBigInt, - toChecksumAddress, - toHex, - uint8ArrayConcat, -} from 'web3-utils'; -import { validateNoLeadingZeroes } from 'web3-validator'; -import { - bigIntToHex, - bigIntToUnpaddedUint8Array, - ecrecover, - toUint8Array, - uint8ArrayToBigInt, - BaseTransaction, - Capability, -} from 'web3-eth-accounts'; -import type { Common, JsonTx, TxOptions, TxValuesArray } from 'web3-eth-accounts'; -import { DEFAULT_GAS_PER_PUBDATA_LIMIT, ZERO_ADDRESS } from '../constants'; -import { concat, hashBytecode, toBytes } from '../utils'; -import type { Address } from '../types'; -import { MAX_INTEGER } from './constants'; -import type { Eip712TxData, TypedDataDomain, Eip712Meta } from './types'; - -export const EIP712_TX_TYPE = 113; // 0x71 -const EIP712_TX_TYPE_UINT8ARRAY = hexToBytes(EIP712_TX_TYPE.toString(16).padStart(2, '0')); - -function meetsEIP155(_v: bigint, chainId: bigint) { - const v = Number(_v); - const chainIdDoubled = Number(chainId) * 2; - return v === chainIdDoubled + 35 || v === chainIdDoubled + 36; -} - -/** - * Class to create the EIP-712 Transaction object. - * - TransactionType: 113 - * - EIP: [EIP-712](https://eips.ethereum.org/EIPS/eip-712) - */ - -export class EIP712Transaction extends BaseTransaction { - private txData: Eip712TxData; - public readonly chainId?: bigint; - public readonly gasPrice: bigint; - public eip712Domain: TypedDataDomain; - - public readonly common: Common; - - /** - * Instantiate a transaction from a data dictionary. - * - * Format: { nonce, gasPrice, gasLimit, to, value, data, v, r, s } - * - * Notes: - * - All parameters are optional and have some basic default values - */ - public static fromTxData(txData: Eip712TxData, opts: TxOptions = {}) { - return new EIP712Transaction(txData, opts); - } - - /** - * Instantiate a transaction from the serialized tx. - * - * Format: `rlp([nonce, gasPrice, gasLimit, to, value, data, v, r, s])` - */ - public static fromSerializedTx(serialized: Uint8Array, opts: TxOptions = {}) { - const values = RLP.decode(serialized); - - if (!Array.isArray(values)) { - throw new Error('Invalid serialized tx input. Must be array'); - } - - return this.fromValuesArray(values as Uint8Array[], opts); - } - - /** - * Create a transaction from a values array. - * - * Format: `[nonce, gasPrice, gasLimit, to, value, data, v, r, s]` - */ - public static fromValuesArray(values: TxValuesArray, opts: TxOptions = {}) { - // If length is not 6, it has length 9. If v/r/s are empty Uint8Array, it is still an unsigned transaction - // This happens if you get the RLP data from `raw()` - if (values.length !== 6 && values.length !== 9) { - throw new Error( - 'Invalid transaction. Only expecting 6 values (for unsigned tx) or 9 values (for signed tx).', - ); - } - - const [nonce, gasPrice, gasLimit, to, value, data, v, r, s] = values; - - validateNoLeadingZeroes({ nonce, gasPrice, gasLimit, value, v, r, s }); - - return new EIP712Transaction( - { - nonce, - // @ts-ignore - gasPrice, - gasLimit, - to, - value, - data, - v, - r, - s, - }, - opts, - ); - } - - /** - * This constructor takes the values, validates them, assigns them and freezes the object. - * - * It is not recommended to use this constructor directly. Instead use - * the static factory methods to assist in creating a Transaction object from - * varying data types. - */ - public constructor(txData: Eip712TxData, opts: TxOptions = {}) { - super({ ...txData, type: EIP712_TX_TYPE }, opts); - - this.txData = EIP712Transaction.getSignInput(txData); - this.eip712Domain = { - name: 'zkSync', - version: '2', - chainId: txData.chainId, - }; - this.common = this._validateTxV(this.v, opts.common); - console.log('txData', txData); - this.chainId = txData.chainId ? toBigInt(txData.chainId) : undefined; - console.log('this.chainId', this.chainId); - this.gasPrice = uint8ArrayToBigInt( - // @ts-ignore - toUint8Array(txData.gasPrice === '' ? '0x' : txData.gasPrice), - ); - - if (this.gasPrice * this.gasLimit > MAX_INTEGER) { - const msg = this._errorMsg('gas limit * gasPrice cannot exceed MAX_INTEGER (2^256-1)'); - throw new Error(msg); - } - this._validateCannotExceedMaxInteger({ gasPrice: this.gasPrice }); - BaseTransaction._validateNotArray(txData); - - if (this.common.gteHardfork('spuriousDragon')) { - if (!this.isSigned()) { - this.activeCapabilities.push(Capability.EIP155ReplayProtection); - } else { - // EIP155 spec: - // If block.number >= 2,675,000 and v = CHAIN_ID * 2 + 35 or v = CHAIN_ID * 2 + 36 - // then when computing the hash of a transaction for purposes of signing or recovering - // instead of hashing only the first six elements (i.e. nonce, gasprice, startgas, to, value, data) - // hash nine elements, with v replaced by CHAIN_ID, r = 0 and s = 0. - // v and chain ID meet EIP-155 conditions - - if (meetsEIP155(this.v!, this.common.chainId())) { - this.activeCapabilities.push(Capability.EIP155ReplayProtection); - } - } - } - - const freeze = opts?.freeze ?? true; - if (freeze) { - Object.freeze(this); - } - } - - /** - * Generates the EIP712 typed data from provided transaction. Optional fields are populated by zero values. - * - * @param transaction The transaction request that needs to be populated. - * - * @example - * - * const tx = EIP712Signer.getSignInput({ - * type: utils.EIP712_TX_TYPE, - * to: "0xa61464658AfeAf65CccaaFD3a512b69A83B77618", - * value: BigInt(7_000_000), - * from: "0x36615Cf349d7F6344891B1e7CA7C72883F5dc049", - * nonce: 0, - * chainId: BigInt(270), - * gasPrice: BigInt(250_000_000), - * gasLimit: BigInt(21_000), - * customData: {}, - * }); - */ - static getSignInput(transaction: Eip712TxData) { - const maxFeePerGas = toBigInt(transaction.maxFeePerGas || transaction.gasPrice || 0n); - const maxPriorityFeePerGas = toBigInt(transaction.maxPriorityFeePerGas || maxFeePerGas); - const gasPerPubdataByteLimit = toBigInt( - transaction.customData?.gasPerPubdata || DEFAULT_GAS_PER_PUBDATA_LIMIT, - ); - return { - txType: transaction.type || EIP712_TX_TYPE, - from: transaction.from ? toHex(transaction.from) : undefined, - to: transaction.to ? toHex(transaction.to) : undefined, - gasLimit: transaction.gasLimit || 0n, - gasPerPubdataByteLimit: gasPerPubdataByteLimit, - customData: transaction.customData, - maxFeePerGas, - maxPriorityFeePerGas, - paymaster: transaction.customData?.paymasterParams?.paymaster || ZERO_ADDRESS, - nonce: transaction.nonce || 0, - value: transaction.value || BigInt(0), - data: transaction.data || '0x', - factoryDeps: - transaction.customData?.factoryDeps?.map((dep: Bytes) => hashBytecode(dep)) || [], - paymasterInput: transaction.customData?.paymasterParams?.paymasterInput || '0x', - }; - } - - /** - * Returns a Uint8Array Array of the raw Uint8Arrays of the legacy transaction, in order. - * - * Format: `[nonce, gasPrice, gasLimit, to, value, data, v, r, s]` - * - * For legacy txs this is also the correct format to add transactions - * to a block with {@link Block.fromValuesArray} (use the `serialize()` method - * for typed txs). - * - * For an unsigned tx this method returns the empty Uint8Array values - * for the signature parameters `v`, `r` and `s`. For an EIP-155 compliant - * representation have a look at {@link Transaction.getMessageToSign}. - */ - public raw(): TxValuesArray { - const transaction = this.txData; - console.log('raw', this.chainId); - if (!this.chainId) { - throw Error("Transaction chainId isn't set!"); - } - - if (!transaction.from) { - throw new Error( - 'Explicitly providing `from` field is required for EIP712 transactions!', - ); - } - const from = transaction.from; - const meta: Eip712Meta = transaction.customData ?? {}; - // const maxFeePerGas = transaction.maxFeePerGas || transaction.gasPrice || 0; - // const maxPriorityFeePerGas = transaction.maxPriorityFeePerGas || maxFeePerGas; - console.log('transaction', transaction); - console.log('1'); - console.log('toBytes(transaction.nonce || 0)', toBytes('0x0')); - console.log('2'); - - const fields: any[] = [ - toBytes(transaction.nonce || 0), - // toBytes(maxPriorityFeePerGas), - // toBytes(maxFeePerGas), - toBytes(transaction.gasLimit || 0), - transaction.to ? toChecksumAddress(transaction.to as Address) : '0x', - toBytes(transaction.value || 0), - transaction.data || '0x', - ]; - console.log('fields', fields); - - // if (signature) { - // const sig = new SignatureObject(signature); - fields.push( - this.v !== undefined ? bigIntToUnpaddedUint8Array(this.v) : Uint8Array.from([]), - ); - fields.push( - this.r !== undefined ? bigIntToUnpaddedUint8Array(this.r) : Uint8Array.from([]), - ); - fields.push( - this.s !== undefined ? bigIntToUnpaddedUint8Array(this.s) : Uint8Array.from([]), - ); - // } else { - // fields.push(toBytes(transaction.chainId)); - // fields.push("0x"); - // fields.push("0x"); - // } - console.log('this.chainId', this.chainId); - fields.push(toBytes(this.chainId)); - fields.push(toChecksumAddress(from)); - - // Add meta - console.log('meta', meta); - fields.push(toBytes(meta.gasPerPubdata || DEFAULT_GAS_PER_PUBDATA_LIMIT)); - fields.push((meta.factoryDeps ?? []).map(dep => toHex(dep))); - if (meta.customSignature && bytesToUint8Array(meta.customSignature).length === 0) { - throw new Error('Empty signatures are not supported!'); - } - fields.push(meta.customSignature || '0x'); - - if (meta.paymasterParams) { - fields.push([ - meta.paymasterParams.paymaster, - toHex(meta.paymasterParams.paymasterInput), - ]); - } else { - fields.push([]); - } - return fields; - } - - /** - * Serializes an EIP712 transaction and includes a signature if provided. - * - * @param transaction The transaction that needs to be serialized. - * @param signature Ethers signature to be included in the transaction. - * @throws {Error} Throws an error if: - * - `transaction.customData.customSignature` is an empty string. The transaction should be signed, and the `transaction.customData.customSignature` field should be populated with the signature. It should not be specified if the transaction is not signed. - * - `transaction.chainId` is not provided. - * - `transaction.from` is not provided. - * - * @example Serialize EIP712 transaction without signature. - * - * const serializedTx = utils.serializeEip712({ chainId: 270, from: "0x36615Cf349d7F6344891B1e7CA7C72883F5dc049" }, null); - * - * // serializedTx = "0x71ea8080808080808082010e808082010e9436615cf349d7f6344891b1e7ca7c72883f5dc04982c350c080c0" - * - * @example Serialize EIP712 transaction with signature. - * - * const signature = ethers.Signature.from("0x73a20167b8d23b610b058c05368174495adf7da3a4ed4a57eb6dbdeb1fafc24aaf87530d663a0d061f69bb564d2c6fb46ae5ae776bbd4bd2a2a4478b9cd1b42a"); - * - * const serializedTx = utils.serializeEip712( - * { - * chainId: 270, - * from: "0x36615Cf349d7F6344891B1e7CA7C72883F5dc049", - * to: "0xa61464658AfeAf65CccaaFD3a512b69A83B77618", - * value: 1_000_000, - * }, - * signature - * ); - * // serializedTx = "0x71f87f8080808094a61464658afeaf65cccaafd3a512b69a83b77618830f42408001a073a20167b8d23b610b058c05368174495adf7da3a4ed4a57eb6dbdeb1fafc24aa02f87530d663a0d061f69bb564d2c6fb46ae5ae776bbd4bd2a2a4478b9cd1b42a82010e9436615cf349d7f6344891b1e7ca7c72883f5dc04982c350c080c0" - */ - // @ts-ignore - serialize() { - return concat([new Uint8Array([EIP712_TX_TYPE]), RLP.encode(this.raw())]); - } - - /** - * Returns the unsigned tx (hashed or raw), which can be used - * to sign the transaction (e.g. for sending to a hardware wallet). - * - * Note: the raw message message format for the legacy tx is not RLP encoded - * and you might need to do yourself with: - * - * ```javascript - * import { bufArrToArr } from '../util' - * import { RLP } from '../rlp' - * const message = tx.getMessageToSign(false) - * const serializedMessage = RLP.encode(message) // use this for the HW wallet input - * ``` - * - * @param hashMessage - Return hashed message if set to true (default: true) - */ - public getMessageToSign(hashMessage = true) { - const base = this.raw().slice(0, 9); - const message = uint8ArrayConcat(EIP712_TX_TYPE_UINT8ARRAY, RLP.encode(base)); - if (hashMessage) { - return keccak256(message); - } - return message; - } - - /** - * The amount of gas paid for the data in this tx - */ - public getDataFee(): bigint { - // TODO: this is a temporary solution until we have a better way to calculate the data fee - if (this.cache.dataFee && this.cache.dataFee.hardfork === this.common.hardfork()) { - return this.cache.dataFee.value; - } - - if (Object.isFrozen(this)) { - this.cache.dataFee = { - value: super.getDataFee(), - hardfork: this.common.hardfork(), - }; - } - - return super.getDataFee(); - } - - /** - * The up front amount that an account must have for this transaction to be valid - */ - public getUpfrontCost(): bigint { - // TODO: this is a temporary solution until we have a better way to calculate the upfront cost - return this.gasLimit * this.gasPrice + this.value; - } - - /** - * Computes a sha3-256 hash of the serialized tx. - * - * This method can only be used for signed txs (it throws otherwise). - * Use {@link Transaction.getMessageToSign} to get a tx hash for the purpose of signing. - */ - public hash(): Uint8Array { - if (!this.isSigned()) { - const msg = this._errorMsg('Cannot call hash method if transaction is not signed'); - throw new Error(msg); - } - - if (Object.isFrozen(this)) { - if (!this.cache.hash) { - this.cache.hash = keccak256(RLP.encode(this.raw())); - } - return this.cache.hash; - } - - return keccak256(RLP.encode(this.raw())); - } - - /** - * Computes a sha3-256 hash which can be used to verify the signature - */ - - public getMessageToVerifySignature(): Uint8Array { - if (!this.isSigned()) { - const msg = this._errorMsg('This transaction is not signed'); - throw new Error(msg); - } - return this.getMessageToSign(); - } - - /** - * Returns the public key of the sender - */ - public getSenderPublicKey(): Uint8Array { - if (!this.isSigned()) { - const msg = this._errorMsg('Cannot call this method if transaction is not signed'); - throw new Error(msg); - } - - const msgHash = this.getMessageToVerifySignature(); - const { v, r, s } = this; - - this._validateHighS(); - - try { - return ecrecover( - msgHash, - v! + BigInt(27), // Recover the 27 which was stripped from ecsign - bigIntToUnpaddedUint8Array(r!), - bigIntToUnpaddedUint8Array(s!), - ); - } catch (e: any) { - const msg = this._errorMsg('Invalid Signature'); - throw new Error(msg); - } - } - - /** - * Process the v, r, s values from the `sign` method of the base transaction. - */ - protected _processSignature(_v: bigint, r: Uint8Array, s: Uint8Array) { - let v = _v; - if (this.supports(Capability.EIP155ReplayProtection)) { - v += this.common.chainId() * BigInt(2) + BigInt(8); - } - - const opts = { ...this.txOptions, common: this.common }; - - return EIP712Transaction.fromTxData( - { - chainId: this.chainId, - nonce: this.nonce, - gasLimit: this.gasLimit, - to: this.to, - value: this.value, - data: this.data, - v: v - BigInt(27), // This looks extremely hacky: /util actually adds 27 to the value, the recovery bit is either 0 or 1. - r: uint8ArrayToBigInt(r), - s: uint8ArrayToBigInt(s), - }, - opts, - ); - } - - /** - * Returns an object with the JSON representation of the transaction. - */ - public toJSON(): JsonTx { - return { - nonce: bigIntToHex(this.nonce), - gasPrice: bigIntToHex(this.gasPrice), - gasLimit: bigIntToHex(this.gasLimit), - to: this.to !== undefined ? this.to.toString() : undefined, - value: bigIntToHex(this.value), - data: bytesToHex(this.data), - v: this.v !== undefined ? bigIntToHex(this.v) : undefined, - r: this.r !== undefined ? bigIntToHex(this.r) : undefined, - s: this.s !== undefined ? bigIntToHex(this.s) : undefined, - }; - } - - /** - * Validates tx's `v` value - */ - private _validateTxV(_v?: bigint, common?: Common): Common { - let chainIdBigInt; - const v = _v !== undefined ? Number(_v) : undefined; - // Check for valid v values in the scope of a signed legacy tx - if (v !== undefined) { - // v is 1. not matching the EIP-155 chainId included case and... - // v is 2. not matching the classic v=27 or v=28 case - if (v < 37 && v !== 27 && v !== 28) { - throw new Error( - `Legacy txs need either v = 27/28 or v >= 37 (EIP-155 replay protection), got v = ${v}`, - ); - } - } - - // No unsigned tx and EIP-155 activated and chain ID included - if ( - v !== undefined && - v !== 0 && - (!common || common.gteHardfork('spuriousDragon')) && - v !== 27 && - v !== 28 - ) { - if (common) { - if (!meetsEIP155(BigInt(v), common.chainId())) { - throw new Error( - `Incompatible EIP155-based V ${v} and chain id ${common.chainId()}. See the Common parameter of the Transaction constructor to set the chain id.`, - ); - } - } else { - // Derive the original chain ID - let numSub; - if ((v - 35) % 2 === 0) { - numSub = 35; - } else { - numSub = 36; - } - // Use derived chain ID to create a proper Common - chainIdBigInt = BigInt(v - numSub) / BigInt(2); - } - } - return this._getCommon(common, chainIdBigInt); - } - - /** - * Return a compact error string representation of the object - */ - public errorStr() { - let errorStr = this._getSharedErrorPostfix(); - errorStr += ` gasPrice=${this.gasPrice}`; - return errorStr; - } - - /** - * Internal helper function to create an annotated error message - * - * @param msg Base error message - * @hidden - */ - protected _errorMsg(msg: string) { - return `${msg} (${this.errorStr()})`; - } -} diff --git a/src/eip712/constants.ts b/src/eip712/constants.ts deleted file mode 100644 index e669696..0000000 --- a/src/eip712/constants.ts +++ /dev/null @@ -1,12 +0,0 @@ -/** - * Default gas per pubdata byte for L2 transactions. - * This value is utilized when inserting a default value for type 2 - * and EIP712 type transactions. - * - * @constant - */ -// It is a realistic value, but it is large enough to fill into any batch regardless of the pubdata price. -export const DEFAULT_GAS_PER_PUBDATA_LIMIT = 50_000; -export const MAX_INTEGER = BigInt( - '0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff', -); diff --git a/src/eip712/types.ts b/src/eip712/types.ts deleted file mode 100644 index 007b37e..0000000 --- a/src/eip712/types.ts +++ /dev/null @@ -1,89 +0,0 @@ -import type { Bytes, Numbers } from 'web3-types'; -import type { Address } from 'web3'; -import type { FeeMarketEIP1559TxData } from 'web3-eth-accounts'; - -export interface TypedDataDomain { - /** - * The human-readable name of the signing domain. - */ - name?: null | string; - - /** - * The major version of the signing domain. - */ - version?: null | string; - - /** - * The chain ID of the signing domain. - */ - chainId?: null | Numbers; - - /** - * The the address of the contract that will verify the signature. - */ - verifyingContract?: null | string; - - /** - * A salt used for purposes decided by the specific domain. - */ - salt?: null | Bytes; -} - -/** - * A specific field of a structured [[link-eip-712]] type. - */ -export interface TypedDataField { - /** - * The field name. - */ - name: string; - - /** - * The type of the field. - */ - type: string; -} - -export type PaymasterParams = { - /** The address of the paymaster. */ - paymaster: Address; - /** The bytestream input for the paymaster. */ - paymasterInput: Bytes; -}; - -export type Eip712Meta = { - /** The maximum amount of gas the user is willing to pay for a single byte of pubdata. */ - gasPerPubdata?: Numbers; - /** An array of bytes containing the bytecode of the contract being deployed and any related contracts it can deploy. */ - factoryDeps?: Bytes[]; - /** Custom signature used for cases where the signer's account is not an EOA. */ - customSignature?: Bytes; - /** Parameters for configuring the custom paymaster for the transaction. */ - paymasterParams?: PaymasterParams; -}; - -export type Eip712TxData = FeeMarketEIP1559TxData & { - /** The custom data for EIP712 transaction metadata. */ - customData?: null | Eip712Meta; - from?: Address; - hash?: string; - signature?: string; -}; - -export const EIP712_TYPES = { - Transaction: [ - { name: 'txType', type: 'uint256' }, - { name: 'from', type: 'uint256' }, - { name: 'to', type: 'uint256' }, - { name: 'gasLimit', type: 'uint256' }, - { name: 'gasPerPubdataByteLimit', type: 'uint256' }, - { name: 'maxFeePerGas', type: 'uint256' }, - { name: 'maxPriorityFeePerGas', type: 'uint256' }, - { name: 'paymaster', type: 'uint256' }, - { name: 'nonce', type: 'uint256' }, - { name: 'value', type: 'uint256' }, - { name: 'data', type: 'bytes' }, - { name: 'factoryDeps', type: 'bytes32[]' }, - { name: 'paymasterInput', type: 'bytes' }, - ], -}; diff --git a/src/plugin.ts b/src/plugin.ts index 1331669..36d67c1 100644 --- a/src/plugin.ts +++ b/src/plugin.ts @@ -1,5 +1,4 @@ import type { Web3Context, Web3RequestManager } from 'web3-core'; -import { TransactionFactory } from 'web3-eth-accounts'; import type { Address } from 'web3-types'; import { Contract } from 'web3-eth-contract'; import { Web3PluginBase } from 'web3-core'; @@ -7,9 +6,7 @@ import { Web3PluginBase } from 'web3-core'; import { IERC20ABI } from './contracts/IERC20'; import { RpcMethods } from './rpc.methods'; import { ETH_ADDRESS, ZERO_ADDRESS } from './constants'; - import { IL2BridgeABI } from './contracts/IL2Bridge'; -import { EIP712Transaction, EIP712_TX_TYPE } from './eip712/EIP712Transaction'; import { IZkSyncABI } from './contracts/IZkSyncStateTransition'; import { IBridgehubABI } from './contracts/IBridgehub'; import { IContractDeployerABI } from './contracts/IContractDeployer'; @@ -75,8 +72,6 @@ export class ZkSyncPlugin extends Web3PluginBase { this.wethBridgeL2 = ''; this._l2BridgeContracts = {}; this._erc20Contracts = {}; - // @ts-ignore - TransactionFactory.registerTransactionType(EIP712_TX_TYPE, EIP712Transaction); this.Contracts = { ZkSyncMainContract: new Contract(IZkSyncABI, ''), BridgehubContract: new Contract(IBridgehubABI, ''), diff --git a/src/types.ts b/src/types.ts index 4fd810d..c7a2f4b 100644 --- a/src/types.ts +++ b/src/types.ts @@ -1,5 +1,5 @@ // import { FMT_BYTES, FMT_NUMBER, TransactionReceipt, Web3Eth } from 'web3'; - +import type { FeeMarketEIP1559TxData } from 'web3-eth-accounts'; // // TODO: // is it needed to be re-exported from web3 // import { watchTransactionForConfirmations } from 'web3-eth/lib/types/utils/watch_transaction_for_confirmations.js'; @@ -13,11 +13,10 @@ import type { TransactionReceipt, } from 'web3-types'; -import type { - // FeeMarketEIP1559Transaction, - FeeMarketEIP1559TxData, - // TxOptions -} from 'web3-eth-accounts'; +// import type { +// FeeMarketEIP1559Transaction, +// TxOptions +// } from 'web3-eth-accounts'; // import { // EIP712_TX_TYPE, @@ -832,3 +831,71 @@ export interface EstimateFee { max_fee_per_gas: Numbers; max_priority_fee_per_gas: Numbers; } + +export interface TypedDataDomain { + /** + * The human-readable name of the signing domain. + */ + name?: null | string; + + /** + * The major version of the signing domain. + */ + version?: null | string; + + /** + * The chain ID of the signing domain. + */ + chainId?: null | Numbers; + + /** + * The the address of the contract that will verify the signature. + */ + verifyingContract?: null | string; + + /** + * A salt used for purposes decided by the specific domain. + */ + salt?: null | Bytes; +} + +/** + * A specific field of a structured [[link-eip-712]] type. + */ +export interface TypedDataField { + /** + * The field name. + */ + name: string; + + /** + * The type of the field. + */ + type: string; +} + +export type Eip712TxData = FeeMarketEIP1559TxData & { + /** The custom data for EIP712 transaction metadata. */ + customData?: null | Eip712Meta; + from?: Address; + hash?: string; + signature?: string; +}; + +export const EIP712_TYPES = { + Transaction: [ + { name: 'txType', type: 'uint256' }, + { name: 'from', type: 'uint256' }, + { name: 'to', type: 'uint256' }, + { name: 'gasLimit', type: 'uint256' }, + { name: 'gasPerPubdataByteLimit', type: 'uint256' }, + { name: 'maxFeePerGas', type: 'uint256' }, + { name: 'maxPriorityFeePerGas', type: 'uint256' }, + { name: 'paymaster', type: 'uint256' }, + { name: 'nonce', type: 'uint256' }, + { name: 'value', type: 'uint256' }, + { name: 'data', type: 'bytes' }, + { name: 'factoryDeps', type: 'bytes32[]' }, + { name: 'paymasterInput', type: 'bytes' }, + ], +}; diff --git a/src/utils.ts b/src/utils.ts index 447fc51..8ecab74 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -16,7 +16,13 @@ import { RLP } from '@ethereumjs/rlp'; // to be used instead of the one at zksyn import { bytesToHex, toBigInt, toHex } from 'web3-utils'; import type { Address } from 'web3'; import type { Bytes, Eip712TypedData } from 'web3-types'; -import type { DeploymentInfo, Eip712Meta, EthereumSignature, PaymasterParams } from './types'; +import type { + DeploymentInfo, + Eip712Meta, + Eip712TxData, + EthereumSignature, + PaymasterParams, +} from './types'; import { // PaymasterParams, PriorityOpTree, @@ -55,7 +61,6 @@ import { } from './constants'; import type { RpcMethods } from './rpc.methods'; -import type { Eip712TxData } from './eip712/types'; // export * from './paymaster-utils'; // export * from './smart-account-utils'; @@ -527,6 +532,7 @@ export const getSignInput = (transaction: Eip712TxData) => { transaction.customData?.gasPerPubdata || DEFAULT_GAS_PER_PUBDATA_LIMIT, ); return { + chainId: transaction.chainId ? toHex(transaction.chainId) : undefined, txType: transaction.type || EIP712_TX_TYPE, from: transaction.from ? toHex(transaction.from) : undefined, to: transaction.to ? toHex(transaction.to) : undefined, @@ -704,7 +710,9 @@ export function serializeEip712(transaction: Eip712TxData, signature?: Signature ? new Uint8Array() : toBytes(transaction.gasLimit!), transaction.to ? web3Utils.toChecksumAddress(toHex(transaction.to)) : '0x', - toHex(transaction.value || 0) === '0x0' ? new Uint8Array() : toBytes(nonce), + toHex(transaction.value || 0) === '0x0' + ? new Uint8Array() + : toBytes(toHex(transaction.value || 0)), toHex(transaction.data || '0x'), ]; @@ -740,6 +748,8 @@ export function serializeEip712(transaction: Eip712TxData, signature?: Signature fields.push([]); } + console.log(fields); + return concat([new Uint8Array([EIP712_TX_TYPE]), RLP.encode(fields)]); } diff --git a/test/unit/utils.test.ts b/test/unit/utils.test.ts index 9b94eea..fd0b417 100644 --- a/test/unit/utils.test.ts +++ b/test/unit/utils.test.ts @@ -1,14 +1,11 @@ import { Web3 } from 'web3'; import * as web3Accounts from 'web3-eth-accounts'; -import { - // types, - utils, -} from '../../src'; +import type { types } from '../../src'; +import { utils } from '../../src'; import { ADDRESS1, ADDRESS2 } from '../utils'; import * as constants from '../../src/constants'; -import type { Eip712TxData } from '../../src/eip712/types'; -import { eip712TxTypedData, getSignInput } from '../../src/utils'; +import { getSignInput, serializeEip712 } from '../../src/utils'; describe('utils', () => { describe('#getHashedL2ToL1Msg()', () => { @@ -104,7 +101,7 @@ describe('utils', () => { }); describe('#checkBaseCost()', () => { - it('should throw an error if the base cost bigger than value', () => { + it('should throw an error if the base cost bigger than value', async () => { const baseCost = 100; const value = 99; try { @@ -160,23 +157,26 @@ describe('utils', () => { expect(result).toBe(tx); }); - // it('should return a serialized transaction with provided signature', async () => { - // const tx = - // '0x71f87f8080808094a61464658afeaf65cccaafd3a512b69a83b77618830f42408001a073a20167b8d23b610b058c05368174495adf7da3a4ed4a57eb6dbdeb1fafc24aa02f87530d663a0d061f69bb564d2c6fb46ae5ae776bbd4bd2a2a4478b9cd1b42a82010e9436615cf349d7f6344891b1e7ca7c72883f5dc04982c350c080c0'; - // const signature = ethers.Signature.from( - // '0x73a20167b8d23b610b058c05368174495adf7da3a4ed4a57eb6dbdeb1fafc24aaf87530d663a0d061f69bb564d2c6fb46ae5ae776bbd4bd2a2a4478b9cd1b42a', - // ); - // const result = utils.serializeEip712( - // { - // chainId: 270, - // from: ADDRESS1, - // to: ADDRESS2, - // value: 1_000_000, - // }, - // signature, - // ); - // expect(result).toBe(tx); - // }); + it.only('should return a serialized transaction with provided signature', async () => { + const tx = + '0x71f87f8080808094a61464658afeaf65cccaafd3a512b69a83b77618830f42408001a073a20167b8d23b610b058c05368174495adf7da3a4ed4a57eb6dbdeb1fafc24aa02f87530d663a0d061f69bb564d2c6fb46ae5ae776bbd4bd2a2a4478b9cd1b42a82010e9436615cf349d7f6344891b1e7ca7c72883f5dc04982c350c080c0'; + + const signature = + '0x73a20167b8d23b610b058c05368174495adf7da3a4ed4a57eb6dbdeb1fafc24aaf87530d663a0d061f69bb564d2c6fb46ae5ae776bbd4bd2a2a4478b9cd1b42a'; + + // wallet.sign(message).signature; + // const signature = s.toString(); + const result = utils.serializeEip712( + { + chainId: 270, + from: ADDRESS1, + to: ADDRESS2, + value: 1_000_000, + }, + signature, + ); + expect(result).toBe(tx); + }); }); describe('#hashBytecode()', () => { @@ -227,7 +227,7 @@ describe('utils', () => { describe('#parseEip712()', () => { it('should parse a transaction with a signature', () => { - const tx: Eip712TxData = { + const tx: types.Eip712TxData = { type: 113, nonce: 0n, maxPriorityFeePerGas: 0n, @@ -254,7 +254,7 @@ describe('utils', () => { }); it('should parse a transaction without a signature', () => { - const tx: Eip712TxData = { + const tx: types.Eip712TxData = { type: 113, nonce: 0n, maxPriorityFeePerGas: 0n, @@ -301,24 +301,23 @@ describe('utils', () => { describe('#isTypedDataSignatureCorrect()', () => { // TODO: Needs investigation it.skip('should return true if correct', async () => { - const account = web3Accounts.create(); - const ADDRESS = account.address; - const PRIVATE_KEY = account.privateKey; + // const account = web3Accounts.create(); + // const ADDRESS = account.address; + // const PRIVATE_KEY = account.privateKey; - // const ADDRESS = '0x99F3629e38c617cb619682f721Aaf9F61a3DE3d3'; - // const PRIVATE_KEY = - // '0x5b032dc95add073bbacb2a2cbe0d667855cca807abe1461c72257b7ee0d7d334'; - console.log('ADDRESS', ADDRESS); - console.log('PRIVATE_KEY', PRIVATE_KEY); + const PRIVATE_KEY = + '0x7726827caac94a7f9e1b160f7ea819f172f7b6f9d2a97f992c38edeab82d4110'; + + const account = web3Accounts.privateKeyToAccount(PRIVATE_KEY); const web3 = new Web3('https://mainnet.era.zksync.io'); const tx = { type: 113, - chainId: 300, - from: ADDRESS, + chainId: 270, + from: account.address, to: '0xa61464658AfeAf65CccaaFD3a512b69A83B77618', - value: 7_000_000, + value: 7_000_000n, }; // const eip712Signer = new EIP712Signer(new Wallet(PRIVATE_KEY), web3.config.chainId); @@ -349,17 +348,24 @@ describe('utils', () => { web3.eth.accounts.wallet.add(account); console.log('account', account); - const typedData = eip712TxTypedData(tx); - console.log('typedData', typedData); - const signature = await web3.eth.signTypedData(account.address, typedData); + console.log('signInput', signInput); + // const hash = eip712TxHash(tx); + // console.log('typedData', hash); + + const signature = serializeEip712(signInput); + console.log('signature', signature); + // const signature = await web3.eth.signTypedData(account.address, typedData); // const signature = // '0x14920a5306c8b739fd2fbdd6bb933c54c05391ab9454741b5fa1132c31c6f35d4f8a716939e43df6eb660b5298d8354d08710a0d94e8d0c14dc88032cac5deda1b'; + // '0x71f8418080808094a61464658afeaf65cccaafd3a512b69a83b77618836acfc08082012c808082012c9434a535b103a4641ae33bb774a5922765a9ac2a6282c350c080c0' console.log('signature\n\t', signature); - + expect(signature).toBe( + '0x14920a5306c8b739fd2fbdd6bb933c54c05391ab9454741b5fa1132c31c6f35d4f8a716939e43df6eb660b5298d8354d08710a0d94e8d0c14dc88032cac5deda1b', + ); const isValidSignature = await utils.isTypedDataSignatureCorrect( web3, - ADDRESS, + account.address, domain, constants.EIP712_TYPES, signInput, From 7c00ed8202d2dc0d0eca393b7d72426cffed841f Mon Sep 17 00:00:00 2001 From: Oleksii Kosynskyi Date: Wed, 12 Jun 2024 16:00:40 -0400 Subject: [PATCH 18/30] fix tests --- src/types.ts | 18 ------------------ src/utils.ts | 16 +++++++--------- test/unit/utils.test.ts | 16 +++++++--------- 3 files changed, 14 insertions(+), 36 deletions(-) diff --git a/src/types.ts b/src/types.ts index c7a2f4b..3bd3c7b 100644 --- a/src/types.ts +++ b/src/types.ts @@ -881,21 +881,3 @@ export type Eip712TxData = FeeMarketEIP1559TxData & { hash?: string; signature?: string; }; - -export const EIP712_TYPES = { - Transaction: [ - { name: 'txType', type: 'uint256' }, - { name: 'from', type: 'uint256' }, - { name: 'to', type: 'uint256' }, - { name: 'gasLimit', type: 'uint256' }, - { name: 'gasPerPubdataByteLimit', type: 'uint256' }, - { name: 'maxFeePerGas', type: 'uint256' }, - { name: 'maxPriorityFeePerGas', type: 'uint256' }, - { name: 'paymaster', type: 'uint256' }, - { name: 'nonce', type: 'uint256' }, - { name: 'value', type: 'uint256' }, - { name: 'data', type: 'bytes' }, - { name: 'factoryDeps', type: 'bytes32[]' }, - { name: 'paymasterInput', type: 'bytes' }, - ], -}; diff --git a/src/utils.ts b/src/utils.ts index 8ecab74..c068d74 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -176,9 +176,9 @@ export class SignatureObject { v?: web3Types.Numbers, ) { if (typeof rOrSignature === 'string') { - if (rOrSignature.length !== 132) { - throw new Error('Invalid signature length'); - } + // if (rOrSignature.length !== 132) { + // throw new Error('Invalid signature length'); + // } // Initialize with a single string parameter const signature = rOrSignature; this.r = web3Accounts.toUint8Array(signature.slice(0, 66)); @@ -710,18 +710,16 @@ export function serializeEip712(transaction: Eip712TxData, signature?: Signature ? new Uint8Array() : toBytes(transaction.gasLimit!), transaction.to ? web3Utils.toChecksumAddress(toHex(transaction.to)) : '0x', - toHex(transaction.value || 0) === '0x0' - ? new Uint8Array() - : toBytes(toHex(transaction.value || 0)), + toHex(transaction.value || 0) === '0x0' ? new Uint8Array() : toHex(transaction.value || 0), toHex(transaction.data || '0x'), ]; if (signature) { const sig = new SignatureObject(signature); // fields.push(toBytes(sig.yParity)); - fields.push(toBytes(Number(sig.v) === 27 ? 0 : 1)); - fields.push(toBytes(sig.r)); - fields.push(toBytes(sig.s)); + fields.push(toHex(Number(sig.v) === 27 ? 0 : 1)); + fields.push(toHex(sig.r)); + fields.push(toHex(sig.s)); } else { fields.push(toHex(transaction.chainId)); fields.push('0x'); diff --git a/test/unit/utils.test.ts b/test/unit/utils.test.ts index fd0b417..570a96a 100644 --- a/test/unit/utils.test.ts +++ b/test/unit/utils.test.ts @@ -1,11 +1,9 @@ import { Web3 } from 'web3'; import * as web3Accounts from 'web3-eth-accounts'; - import type { types } from '../../src'; import { utils } from '../../src'; import { ADDRESS1, ADDRESS2 } from '../utils'; import * as constants from '../../src/constants'; -import { getSignInput, serializeEip712 } from '../../src/utils'; describe('utils', () => { describe('#getHashedL2ToL1Msg()', () => { @@ -160,11 +158,11 @@ describe('utils', () => { it.only('should return a serialized transaction with provided signature', async () => { const tx = '0x71f87f8080808094a61464658afeaf65cccaafd3a512b69a83b77618830f42408001a073a20167b8d23b610b058c05368174495adf7da3a4ed4a57eb6dbdeb1fafc24aa02f87530d663a0d061f69bb564d2c6fb46ae5ae776bbd4bd2a2a4478b9cd1b42a82010e9436615cf349d7f6344891b1e7ca7c72883f5dc04982c350c080c0'; - - const signature = - '0x73a20167b8d23b610b058c05368174495adf7da3a4ed4a57eb6dbdeb1fafc24aaf87530d663a0d061f69bb564d2c6fb46ae5ae776bbd4bd2a2a4478b9cd1b42a'; - - // wallet.sign(message).signature; + const signature = new utils.SignatureObject( + '0x73a20167b8d23b610b058c05368174495adf7da3a4ed4a57eb6dbdeb1fafc24aaf87530d663a0d061f69bb564d2c6fb46ae5ae776bbd4bd2a2a4478b9cd1b42a', + ); + const recoveredTx = utils.parseEip712(tx); + console.log('recoveredTx', recoveredTx); // const signature = s.toString(); const result = utils.serializeEip712( { @@ -322,7 +320,7 @@ describe('utils', () => { // const eip712Signer = new EIP712Signer(new Wallet(PRIVATE_KEY), web3.config.chainId); - const signInput = getSignInput(tx); + const signInput = utils.getSignInput(tx); // const signInput = { // txType: 113, @@ -352,7 +350,7 @@ describe('utils', () => { // const hash = eip712TxHash(tx); // console.log('typedData', hash); - const signature = serializeEip712(signInput); + const signature = utils.serializeEip712(signInput); console.log('signature', signature); // const signature = await web3.eth.signTypedData(account.address, typedData); From a874af906960bd8ce10e6b80f900b9386403653a Mon Sep 17 00:00:00 2001 From: Oleksii Kosynskyi Date: Thu, 13 Jun 2024 01:09:30 -0400 Subject: [PATCH 19/30] all tests covered --- src/Eip712.ts | 318 +++++++++++++++++++++++++++++++++++ src/utils.ts | 358 +++++++--------------------------------- test/unit/utils.test.ts | 109 ++++-------- 3 files changed, 412 insertions(+), 373 deletions(-) create mode 100644 src/Eip712.ts diff --git a/src/Eip712.ts b/src/Eip712.ts new file mode 100644 index 0000000..73fc1ce --- /dev/null +++ b/src/Eip712.ts @@ -0,0 +1,318 @@ +import { bytesToHex, toBigInt, toHex } from 'web3-utils'; +import type { Bytes, Eip712TypedData } from 'web3-types'; +import * as web3Abi from 'web3-eth-abi'; +import * as web3Utils from 'web3-utils'; +import * as web3Acccounts from 'web3-eth-accounts'; +import { RLP } from '@ethereumjs/rlp'; +import type { Address } from 'web3'; +import * as ethereumCryptography from 'ethereum-cryptography/secp256k1'; +import { + DEFAULT_GAS_PER_PUBDATA_LIMIT, + EIP712_TX_TYPE, + EIP712_TYPES, + ZERO_ADDRESS, +} from './constants'; +import type { Eip712Meta, Eip712TxData, EthereumSignature, PaymasterParams } from './types'; +import type { SignatureLike } from './utils'; +import { concat, hashBytecode, SignatureObject, toBytes } from './utils'; + +function handleAddress(value?: Uint8Array): string | null { + if (!value) { + return null; + } + const hexValue = bytesToHex(value); + if (hexValue === '0x') { + return null; + } + + return web3Utils.toChecksumAddress(hexValue); +} + +function handleNumber(value?: Uint8Array): bigint { + if (!value) { + return 0n; + } + const hexValue = bytesToHex(value); + if (hexValue === '0x') { + return 0n; + } + return toBigInt(hexValue); +} +function arrayToPaymasterParams(arr: Uint8Array): PaymasterParams | undefined { + if (arr.length === 0) { + return undefined; + } + if (arr.length !== 2) { + throw new Error( + `Invalid paymaster parameters, expected to have length of 2, found ${arr.length}!`, + ); + } + + return { + paymaster: web3Utils.toChecksumAddress(toHex(arr[0])), + paymasterInput: web3Utils.bytesToUint8Array(toHex(arr[1])), + }; +} + +export class Eip712 { + static getSignInput(transaction: Eip712TxData) { + const maxFeePerGas = toHex(transaction.maxFeePerGas || transaction.gasPrice || 0n); + const maxPriorityFeePerGas = toHex(transaction.maxPriorityFeePerGas || maxFeePerGas); + const gasPerPubdataByteLimit = toHex( + transaction.customData?.gasPerPubdata || DEFAULT_GAS_PER_PUBDATA_LIMIT, + ); + return { + txType: transaction.type || EIP712_TX_TYPE, + from: transaction.from ? toHex(transaction.from) : undefined, + to: transaction.to ? toHex(transaction.to) : undefined, + gasLimit: transaction.gasLimit || 0, + gasPerPubdataByteLimit: gasPerPubdataByteLimit, + customData: transaction.customData, + maxFeePerGas, + maxPriorityFeePerGas, + paymaster: transaction.customData?.paymasterParams?.paymaster || ZERO_ADDRESS, + nonce: transaction.nonce || 0, + value: transaction.value || toHex(0), + data: transaction.data || '0x', + factoryDeps: + transaction.customData?.factoryDeps?.map((dep: Bytes) => hashBytecode(dep)) || [], + paymasterInput: transaction.customData?.paymasterParams?.paymasterInput || '0x', + }; + } + + static txTypedData(transaction: Eip712TxData): Eip712TypedData { + return { + types: EIP712_TYPES, + primaryType: 'Transaction', + domain: { + name: 'zkSync', + version: '2', + chainId: Number(transaction.chainId), + }, + message: Eip712.getSignInput(transaction), + }; + } + /** + * Returns the hash of an EIP712 transaction. + * + * @param transaction The EIP-712 transaction. + * @param ethSignature The ECDSA signature of the transaction. + * + * @example + * + * + */ + static txHash(transaction: Eip712TxData, ethSignature?: EthereumSignature): string { + const bytes: string[] = []; + + const typedDataStruct = Eip712.txTypedData(transaction); + + bytes.push(web3Abi.getEncodedEip712Data(typedDataStruct, true)); + bytes.push(web3Utils.keccak256(Eip712.getSignature(typedDataStruct.message, ethSignature))); + return web3Utils.keccak256(concat(bytes)); + } + /** + * Parses an EIP712 transaction from a payload. + * + * @param payload The payload to parse. + * + * @example + * + * + * const serializedTx = + * "0x71f87f8080808094a61464658afeaf65cccaafd3a512b69a83b77618830f42408001a073a20167b8d23b610b058c05368174495adf7da3a4ed4a57eb6dbdeb1fafc24aa02f87530d663a0d061f69bb564d2c6fb46ae5ae776bbd4bd2a2a4478b9cd1b42a82010e9436615cf349d7f6344891b1e7ca7c72883f5dc04982c350c080c0"; + * const tx: types.TransactionLike = utils.parse(serializedTx); + * /* + * tx: types.Eip712TxData = { + * type: 113, + * nonce: 0, + * maxPriorityFeePerGas: BigInt(0), + * maxFeePerGas: BigInt(0), + * gasLimit: BigInt(0), + * to: "0xa61464658AfeAf65CccaaFD3a512b69A83B77618", + * value: BigInt(1000000), + * data: "0x", + * chainId: BigInt(270), + * from: "0x36615Cf349d7F6344891B1e7CA7C72883F5dc049", + * customData: { + * gasPerPubdata: BigInt(50000), + * factoryDeps: [], + * customSignature: "0x", + * paymasterParams: null, + * }, + * hash: "0x9ed410ce33179ac1ff6b721060605afc72d64febfe0c08cacab5a246602131ee", + * }; + * *\/ + */ + + static fromSerializedTx(payload: Bytes): Eip712TxData { + const bytes = web3Utils.bytesToUint8Array(payload); + + const raw = RLP.decode(bytes.slice(1)) as Array; + const transaction: Eip712TxData = { + type: EIP712_TX_TYPE, + nonce: handleNumber(raw[0]), + maxPriorityFeePerGas: handleNumber(raw[1]), + maxFeePerGas: handleNumber(raw[2]), + gasLimit: handleNumber(raw[3]), + to: handleAddress(raw[4]) as Address, + value: handleNumber(raw[5]), + data: bytesToHex(raw[6]), + chainId: handleNumber(raw[10]), + from: handleAddress(raw[11]) as Address, + customData: { + gasPerPubdata: handleNumber(raw[12]), + factoryDeps: raw[13] as unknown as string[], + customSignature: bytesToHex(raw[14]), + paymasterParams: arrayToPaymasterParams(raw[15]), + }, + }; + const ethSignature = { + v: Number(handleNumber(raw[7])), + r: raw[8], + s: raw[9], + }; + + if ( + (web3Utils.toHex(ethSignature.r) === '0x' || + web3Utils.toHex(ethSignature.s) === '0x') && + !transaction.customData?.customSignature + ) { + return transaction; + } + + if ( + ethSignature.v !== 0 && + ethSignature.v !== 1 && + !transaction.customData?.customSignature + ) { + throw new Error('Failed to parse signature!'); + } + + if (!transaction.customData?.customSignature) { + transaction.signature = new SignatureObject(ethSignature).toString(); + } + + transaction.hash = Eip712.txHash(transaction, ethSignature); + + return transaction; + } + + static getSignature(transaction: Eip712TxData, ethSignature?: EthereumSignature): Uint8Array { + if ( + transaction?.customData?.customSignature && + transaction.customData.customSignature.length + ) { + return web3Utils.bytesToUint8Array(transaction.customData.customSignature); + } + + if (!ethSignature) { + throw new Error('No signature provided!'); + } + + const r = web3Utils.bytesToUint8Array( + web3Utils.padLeft(web3Utils.toHex(ethSignature.r), 32 * 2), + ); + const s = web3Utils.bytesToUint8Array( + web3Utils.padLeft(web3Utils.toHex(ethSignature.s), 32 * 2), + ); + const v = ethSignature.v; + + return new Uint8Array([...r, ...s, v]); + } + + static serialize(transaction: Eip712TxData, signature?: SignatureLike): string { + if (!transaction.chainId) { + throw Error("Transaction chainId isn't set!"); + } + + if (!transaction.from) { + throw new Error( + 'Explicitly providing `from` field is required for EIP712 transactions!', + ); + } + const from = transaction.from; + const meta: Eip712Meta = transaction.customData ?? {}; + const maxFeePerGas = toHex(transaction.maxFeePerGas || transaction.gasPrice || 0); + const maxPriorityFeePerGas = toHex(transaction.maxPriorityFeePerGas || maxFeePerGas); + + const nonce = toHex(transaction.nonce || 0); + const fields: Array = [ + nonce === '0x0' ? new Uint8Array() : toBytes(nonce), + maxPriorityFeePerGas === '0x0' ? new Uint8Array() : toBytes(maxPriorityFeePerGas), + maxFeePerGas === '0x0' ? new Uint8Array() : toBytes(maxFeePerGas), + toHex(transaction.gasLimit || 0) === '0x0' + ? new Uint8Array() + : toBytes(transaction.gasLimit!), + transaction.to ? web3Utils.toChecksumAddress(toHex(transaction.to)) : '0x', + toHex(transaction.value || 0) === '0x0' + ? new Uint8Array() + : toHex(transaction.value || 0), + toHex(transaction.data || '0x'), + ]; + + if (signature) { + const signatureObject = new SignatureObject(signature); + + fields.push(toHex(Number(signatureObject.v) === 27 ? 0 : 1)); + fields.push(toHex(signatureObject.r)); + fields.push(toHex(signatureObject.s)); + + // const sig = new SignatureObject(signature); + // fields.push(toBytes(sig.yParity)); + } else { + fields.push(toHex(transaction.chainId)); + fields.push('0x'); + fields.push('0x'); + } + fields.push(toHex(transaction.chainId)); + fields.push(web3Utils.toChecksumAddress(from)); + + // Add meta + fields.push(toHex(meta.gasPerPubdata || DEFAULT_GAS_PER_PUBDATA_LIMIT)); + fields.push((meta.factoryDeps ?? []).map(dep => web3Utils.toHex(dep))); + + if ( + meta.customSignature && + web3Utils.bytesToUint8Array(meta.customSignature).length === 0 + ) { + throw new Error('Empty signatures are not supported!'); + } + fields.push(meta.customSignature || '0x'); + + if (meta.paymasterParams) { + fields.push([ + meta.paymasterParams.paymaster, + web3Utils.toHex(meta.paymasterParams.paymasterInput), + ]); + } else { + fields.push([]); + } + + return concat([new Uint8Array([EIP712_TX_TYPE]), RLP.encode(fields)]); + } + + static sign(hash: string, PRIVATE_KEY: string) { + return web3Acccounts.sign(hash, PRIVATE_KEY, true); + } + static computePublicKey(key: Bytes, compressed?: boolean): string { + let bytes = toBytes(key); + + // private key + if (bytes.length === 32) { + const pubKey = ethereumCryptography.secp256k1.getPublicKey(bytes, !!compressed); + return toHex(pubKey); + } + + // raw public key; use uncompressed key with 0x04 prefix + if (bytes.length === 64) { + const pub = new Uint8Array(65); + pub[0] = 0x04; + pub.set(bytes, 1); + bytes = pub; + } + + const point = ethereumCryptography.secp256k1.ProjectivePoint.fromHex(bytes); + return toHex(point.toRawBytes(compressed)); + } +} diff --git a/src/utils.ts b/src/utils.ts index c068d74..6dd5ac6 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -11,18 +11,8 @@ import * as web3Accounts from 'web3-eth-accounts'; import * as web3Types from 'web3-types'; import * as web3Abi from 'web3-eth-abi'; import * as web3Contract from 'web3-eth-contract'; - -import { RLP } from '@ethereumjs/rlp'; // to be used instead of the one at zksync-ethers: Provider from ./provider -import { bytesToHex, toBigInt, toHex } from 'web3-utils'; -import type { Address } from 'web3'; -import type { Bytes, Eip712TypedData } from 'web3-types'; -import type { - DeploymentInfo, - Eip712Meta, - Eip712TxData, - EthereumSignature, - PaymasterParams, -} from './types'; +import type { Bytes } from 'web3-types'; +import type { DeploymentInfo, EthereumSignature } from './types'; import { // PaymasterParams, PriorityOpTree, @@ -52,16 +42,14 @@ import { EIP1271_MAGIC_VALUE, L1_FEE_ESTIMATION_COEF_NUMERATOR, L1_FEE_ESTIMATION_COEF_DENOMINATOR, - EIP712_TX_TYPE, - EIP712_TYPES, - DEFAULT_GAS_PER_PUBDATA_LIMIT, - ZERO_ADDRESS, // EIP712_TX_TYPE, // DEFAULT_GAS_PER_PUBDATA_LIMIT, } from './constants'; import type { RpcMethods } from './rpc.methods'; +export * from './Eip712'; + // export * from './paymaster-utils'; // export * from './smart-account-utils'; // export { EIP712_TYPES } from './signer'; @@ -140,7 +128,6 @@ export function contractFunctionId(value: string): string { return web3Utils.keccak256(web3Utils.utf8ToBytes(value)); } -// TODO: needs to test for the first parameter being Eip712TypedData function recoverSignerAddress( messageOrData: string | web3Types.Eip712TypedData, signature: SignatureLike, @@ -153,16 +140,31 @@ function recoverSignerAddress( } if (typeof signature === 'string') { - return web3Accounts.recover(message, signature); + return web3Accounts.recover(message, signature, undefined, undefined, undefined, true); } const r = web3Utils.toHex(signature.r); const s = web3Utils.toHex(signature.s); const v = web3Utils.toHex(signature.v); - return web3Accounts.recover(message, v, r, s); + return web3Accounts.recover(message, v, r, s, undefined, true); } +function _getBytes(value: Bytes): Uint8Array { + if (value instanceof Uint8Array) { + return value; + } + if (typeof value === 'string' && value.match(/^0x([0-9a-f][0-9a-f])*$/i)) { + const result = new Uint8Array((value.length - 2) / 2); + let offset = 2; + for (let i = 0; i < result.length; i++) { + result[i] = parseInt(value.substring(offset, offset + 2), 16); + offset += 2; + } + return result; + } + throw new Error('Invalid BytesLike value'); +} export class SignatureObject { public r: Uint8Array; public s: Uint8Array; @@ -176,14 +178,26 @@ export class SignatureObject { v?: web3Types.Numbers, ) { if (typeof rOrSignature === 'string') { - // if (rOrSignature.length !== 132) { - // throw new Error('Invalid signature length'); - // } - // Initialize with a single string parameter - const signature = rOrSignature; - this.r = web3Accounts.toUint8Array(signature.slice(0, 66)); - this.s = web3Accounts.toUint8Array(`0x${signature.slice(66, 130)}`); - this.v = BigInt(web3Utils.hexToNumber(`0x${signature.slice(130, 132)}`)); + const bytes: Uint8Array = _getBytes(rOrSignature); + + if (bytes.length === 64) { + const r = bytes.slice(0, 32); + const s = bytes.slice(32, 64); + const v = BigInt(s[0] & 0x80 ? 28 : 27); + s[0] &= 0x7f; + this.r = r; + this.s = s; + this.v = v; + } else if (bytes.length === 65) { + const r = bytes.slice(0, 32); + const s = bytes.slice(32, 64); + const v = BigInt(SignatureObject.getNormalizedV(bytes[64])); + this.r = r; + this.s = s; + this.v = v; + } else { + throw new Error('Invalid signature length'); + } } else if ( (rOrSignature as EthereumSignature).r && (rOrSignature as EthereumSignature).s && @@ -201,7 +215,26 @@ export class SignatureObject { this.v = BigInt(signature.v!); } } + static getNormalizedV(v: number): 27 | 28 { + if (v === 0 || v === 27) { + return 27; + } + if (v === 1 || v === 28) { + return 28; + } + // Otherwise, EIP-155 v means odd is 27 and even is 28 + return v & 1 ? 27 : 28; + } + concat(datas: ReadonlyArray): string { + return '0x' + datas.map(d => web3Utils.toHex(d).substring(2)).join(''); + } + get yParity(): 0 | 1 { + return this.v === 27n ? 0 : 1; + } + public get serialized(): string { + return this.concat([this.r, this.s, this.yParity ? '0x1c' : '0x1b']); + } public toString() { return `${this.r}${this.s.slice(2)}${web3Utils.toHex(this.v).slice(2)}`; } @@ -487,270 +520,6 @@ export function hashBytecode(bytecode: web3Types.Bytes): Uint8Array { return hash; } -function handleAddress(value?: Uint8Array): string | null { - if (!value) { - return null; - } - const hexValue = bytesToHex(value); - if (hexValue === '0x') { - return null; - } - - return web3Utils.toChecksumAddress(hexValue); -} - -function handleNumber(value?: Uint8Array): bigint { - if (!value) { - return 0n; - } - const hexValue = bytesToHex(value); - if (hexValue === '0x') { - return 0n; - } - return toBigInt(hexValue); -} -function arrayToPaymasterParams(arr: Uint8Array): PaymasterParams | undefined { - if (arr.length === 0) { - return undefined; - } - if (arr.length !== 2) { - throw new Error( - `Invalid paymaster parameters, expected to have length of 2, found ${arr.length}!`, - ); - } - - return { - paymaster: web3Utils.toChecksumAddress(toHex(arr[0])), - paymasterInput: web3Utils.bytesToUint8Array(toHex(arr[1])), - }; -} - -export const getSignInput = (transaction: Eip712TxData) => { - const maxFeePerGas = toHex(transaction.maxFeePerGas || transaction.gasPrice || 0n); - const maxPriorityFeePerGas = toHex(transaction.maxPriorityFeePerGas || maxFeePerGas); - const gasPerPubdataByteLimit = toHex( - transaction.customData?.gasPerPubdata || DEFAULT_GAS_PER_PUBDATA_LIMIT, - ); - return { - chainId: transaction.chainId ? toHex(transaction.chainId) : undefined, - txType: transaction.type || EIP712_TX_TYPE, - from: transaction.from ? toHex(transaction.from) : undefined, - to: transaction.to ? toHex(transaction.to) : undefined, - gasLimit: transaction.gasLimit || 0, - gasPerPubdataByteLimit: gasPerPubdataByteLimit, - customData: transaction.customData, - maxFeePerGas, - maxPriorityFeePerGas, - paymaster: transaction.customData?.paymasterParams?.paymaster || ZERO_ADDRESS, - nonce: transaction.nonce || 0, - value: transaction.value || toHex(0), - data: transaction.data || '0x', - factoryDeps: - transaction.customData?.factoryDeps?.map((dep: Bytes) => hashBytecode(dep)) || [], - paymasterInput: transaction.customData?.paymasterParams?.paymasterInput || '0x', - }; -}; - -export function eip712TxTypedData(transaction: Eip712TxData): Eip712TypedData { - return { - types: EIP712_TYPES, - primaryType: 'Transaction', - domain: { - name: 'zkSync', - version: '2', - chainId: Number(transaction.chainId), - }, - message: getSignInput(transaction), - }; -} -/** - * Returns the hash of an EIP712 transaction. - * - * @param transaction The EIP-712 transaction. - * @param ethSignature The ECDSA signature of the transaction. - * - * @example - * - * - */ -export function eip712TxHash(transaction: Eip712TxData, ethSignature?: EthereumSignature): string { - const bytes: string[] = []; - - const typedDataStruct = eip712TxTypedData(transaction); - - bytes.push(web3Abi.getEncodedEip712Data(typedDataStruct, true)); - bytes.push(web3Utils.keccak256(getSignature(typedDataStruct.message, ethSignature))); - return web3Utils.keccak256(concat(bytes)); -} -/** - * Parses an EIP712 transaction from a payload. - * - * @param payload The payload to parse. - * - * @example - * - * - * const serializedTx = - * "0x71f87f8080808094a61464658afeaf65cccaafd3a512b69a83b77618830f42408001a073a20167b8d23b610b058c05368174495adf7da3a4ed4a57eb6dbdeb1fafc24aa02f87530d663a0d061f69bb564d2c6fb46ae5ae776bbd4bd2a2a4478b9cd1b42a82010e9436615cf349d7f6344891b1e7ca7c72883f5dc04982c350c080c0"; - * const tx: types.TransactionLike = utils.parseEip712(serializedTx); - * /* - * tx: types.Eip712TxData = { - * type: 113, - * nonce: 0, - * maxPriorityFeePerGas: BigInt(0), - * maxFeePerGas: BigInt(0), - * gasLimit: BigInt(0), - * to: "0xa61464658AfeAf65CccaaFD3a512b69A83B77618", - * value: BigInt(1000000), - * data: "0x", - * chainId: BigInt(270), - * from: "0x36615Cf349d7F6344891B1e7CA7C72883F5dc049", - * customData: { - * gasPerPubdata: BigInt(50000), - * factoryDeps: [], - * customSignature: "0x", - * paymasterParams: null, - * }, - * hash: "0x9ed410ce33179ac1ff6b721060605afc72d64febfe0c08cacab5a246602131ee", - * }; - * *\/ - */ - -export function parseEip712(payload: web3Types.Bytes): Eip712TxData { - const bytes = web3Utils.bytesToUint8Array(payload); - - // try using: RLP.decode - const raw = RLP.decode(bytes.slice(1)) as Array; - const transaction: Eip712TxData = { - type: EIP712_TX_TYPE, - nonce: handleNumber(raw[0]), - maxPriorityFeePerGas: handleNumber(raw[1]), - maxFeePerGas: handleNumber(raw[2]), - gasLimit: handleNumber(raw[3]), - to: handleAddress(raw[4]) as Address, - value: handleNumber(raw[5]), - data: bytesToHex(raw[6]), - chainId: handleNumber(raw[10]), - from: handleAddress(raw[11]) as Address, - customData: { - gasPerPubdata: handleNumber(raw[12]), - factoryDeps: raw[13] as unknown as string[], - customSignature: bytesToHex(raw[14]), - paymasterParams: arrayToPaymasterParams(raw[15]), - }, - }; - const ethSignature = { - v: Number(handleNumber(raw[7])), - r: raw[8], - s: raw[9], - }; - - if ( - (web3Utils.toHex(ethSignature.r) === '0x' || web3Utils.toHex(ethSignature.s) === '0x') && - !transaction.customData?.customSignature - ) { - return transaction; - } - - if (ethSignature.v !== 0 && ethSignature.v !== 1 && !transaction.customData?.customSignature) { - throw new Error('Failed to parse signature!'); - } - - if (!transaction.customData?.customSignature) { - transaction.signature = new SignatureObject(ethSignature).toString(); - } - - transaction.hash = eip712TxHash(transaction, ethSignature); - - return transaction; -} - -export function getSignature( - transaction: Eip712TxData, - ethSignature?: EthereumSignature, -): Uint8Array { - if (transaction?.customData?.customSignature && transaction.customData.customSignature.length) { - return web3Utils.bytesToUint8Array(transaction.customData.customSignature); - } - - if (!ethSignature) { - throw new Error('No signature provided!'); - } - - const r = web3Utils.bytesToUint8Array( - web3Utils.padLeft(web3Utils.toHex(ethSignature.r), 32 * 2), - ); - const s = web3Utils.bytesToUint8Array( - web3Utils.padLeft(web3Utils.toHex(ethSignature.s), 32 * 2), - ); - const v = ethSignature.v; - - return new Uint8Array([...r, ...s, v]); -} - -export function serializeEip712(transaction: Eip712TxData, signature?: SignatureLike): string { - if (!transaction.chainId) { - throw Error("Transaction chainId isn't set!"); - } - - if (!transaction.from) { - throw new Error('Explicitly providing `from` field is required for EIP712 transactions!'); - } - const from = transaction.from; - const meta: Eip712Meta = transaction.customData ?? {}; - const maxFeePerGas = toHex(transaction.maxFeePerGas || transaction.gasPrice || 0); - const maxPriorityFeePerGas = toHex(transaction.maxPriorityFeePerGas || maxFeePerGas); - - const nonce = toHex(transaction.nonce || 0); - const fields: Array = [ - nonce === '0x0' ? new Uint8Array() : toBytes(nonce), - maxPriorityFeePerGas === '0x0' ? new Uint8Array() : toBytes(maxPriorityFeePerGas), - maxFeePerGas === '0x0' ? new Uint8Array() : toBytes(maxFeePerGas), - toHex(transaction.gasLimit || 0) === '0x0' - ? new Uint8Array() - : toBytes(transaction.gasLimit!), - transaction.to ? web3Utils.toChecksumAddress(toHex(transaction.to)) : '0x', - toHex(transaction.value || 0) === '0x0' ? new Uint8Array() : toHex(transaction.value || 0), - toHex(transaction.data || '0x'), - ]; - - if (signature) { - const sig = new SignatureObject(signature); - // fields.push(toBytes(sig.yParity)); - fields.push(toHex(Number(sig.v) === 27 ? 0 : 1)); - fields.push(toHex(sig.r)); - fields.push(toHex(sig.s)); - } else { - fields.push(toHex(transaction.chainId)); - fields.push('0x'); - fields.push('0x'); - } - fields.push(toHex(transaction.chainId)); - fields.push(web3Utils.toChecksumAddress(from)); - - // Add meta - fields.push(toHex(meta.gasPerPubdata || DEFAULT_GAS_PER_PUBDATA_LIMIT)); - fields.push((meta.factoryDeps ?? []).map(dep => web3Utils.toHex(dep))); - - if (meta.customSignature && web3Utils.bytesToUint8Array(meta.customSignature).length === 0) { - throw new Error('Empty signatures are not supported!'); - } - fields.push(meta.customSignature || '0x'); - - if (meta.paymasterParams) { - fields.push([ - meta.paymasterParams.paymaster, - web3Utils.toHex(meta.paymasterParams.paymasterInput), - ]); - } else { - fields.push([]); - } - - console.log(fields); - - return concat([new Uint8Array([EIP712_TX_TYPE]), RLP.encode(fields)]); -} - /** * Returns the hash of the L2 priority operation from a given transaction receipt and L2 address. * @@ -1089,17 +858,14 @@ export async function isTypedDataSignatureCorrect( value: Record, signature: SignatureLike, ): Promise { - const data: web3Types.Eip712TypedData = { + const typedDataStruct: web3Types.Eip712TypedData = { domain, types, primaryType: 'Transaction', message: value, }; - // could be also: - const message = web3Abi.getEncodedEip712Data(data); - return isSignatureCorrect(context, address, message, signature); - // return isSignatureCorrect(context, address, data, signature); + return isSignatureCorrect(context, address, typedDataStruct, signature); } /** diff --git a/test/unit/utils.test.ts b/test/unit/utils.test.ts index 570a96a..361ee57 100644 --- a/test/unit/utils.test.ts +++ b/test/unit/utils.test.ts @@ -1,5 +1,6 @@ import { Web3 } from 'web3'; import * as web3Accounts from 'web3-eth-accounts'; +import * as web3Abi from 'web3-eth-abi'; import type { types } from '../../src'; import { utils } from '../../src'; import { ADDRESS1, ADDRESS2 } from '../utils'; @@ -115,7 +116,7 @@ describe('utils', () => { describe('#serializeEip712()', () => { it('should throw an error when `tx.chainId` is not specified', () => { try { - utils.serializeEip712({}); + utils.Eip712.serialize({}); } catch (e) { expect((e as Error).message).toBe("Transaction chainId isn't set!"); } @@ -123,7 +124,7 @@ describe('utils', () => { it('should throw an error when `tx.from` is not specified', () => { try { - utils.serializeEip712({ chainId: 270 }); + utils.Eip712.serialize({ chainId: 270 }); } catch (e) { expect((e as Error).message).toBe( 'Explicitly providing `from` field is required for EIP712 transactions!', @@ -133,7 +134,7 @@ describe('utils', () => { it('should throw an error when `tx.customData.customSignature` is empty string', () => { try { - utils.serializeEip712({ + utils.Eip712.serialize({ chainId: 270, from: ADDRESS1, customData: { @@ -148,30 +149,24 @@ describe('utils', () => { it('should return a serialized transaction with populated default values', () => { const tx = '0x71ea8080808080808082010e808082010e9436615cf349d7f6344891b1e7ca7c72883f5dc04982c350c080c0'; - const result = utils.serializeEip712({ + const result = utils.Eip712.serialize({ chainId: 270, from: ADDRESS1, }); expect(result).toBe(tx); }); - it.only('should return a serialized transaction with provided signature', async () => { + it('should return a serialized transaction with provided signature', async () => { const tx = '0x71f87f8080808094a61464658afeaf65cccaafd3a512b69a83b77618830f42408001a073a20167b8d23b610b058c05368174495adf7da3a4ed4a57eb6dbdeb1fafc24aa02f87530d663a0d061f69bb564d2c6fb46ae5ae776bbd4bd2a2a4478b9cd1b42a82010e9436615cf349d7f6344891b1e7ca7c72883f5dc04982c350c080c0'; - const signature = new utils.SignatureObject( - '0x73a20167b8d23b610b058c05368174495adf7da3a4ed4a57eb6dbdeb1fafc24aaf87530d663a0d061f69bb564d2c6fb46ae5ae776bbd4bd2a2a4478b9cd1b42a', - ); - const recoveredTx = utils.parseEip712(tx); - console.log('recoveredTx', recoveredTx); - // const signature = s.toString(); - const result = utils.serializeEip712( + const result = utils.Eip712.serialize( { chainId: 270, from: ADDRESS1, to: ADDRESS2, value: 1_000_000, }, - signature, + '0x73a20167b8d23b610b058c05368174495adf7da3a4ed4a57eb6dbdeb1fafc24aaf87530d663a0d061f69bb564d2c6fb46ae5ae776bbd4bd2a2a4478b9cd1b42a', ); expect(result).toBe(tx); }); @@ -247,7 +242,7 @@ describe('utils', () => { const serializedTx = '0x71f87f8080808094a61464658afeaf65cccaafd3a512b69a83b77618830f42408001a073a20167b8d23b610b058c05368174495adf7da3a4ed4a57eb6dbdeb1fafc24aa02f87530d663a0d061f69bb564d2c6fb46ae5ae776bbd4bd2a2a4478b9cd1b42a82010e9436615cf349d7f6344891b1e7ca7c72883f5dc04982c350c080c0'; - const result = utils.parseEip712(serializedTx); + const result = utils.Eip712.fromSerializedTx(serializedTx); expect(result).toEqual(tx); }); @@ -275,16 +270,16 @@ describe('utils', () => { const serializedTx = '0x71f83e8080808094a61464658afeaf65cccaafd3a512b69a83b77618808082010e808082010e9436615cf349d7f6344891b1e7ca7c72883f5dc04982c350c080c0'; - const result = utils.parseEip712(serializedTx); + const result = utils.Eip712.fromSerializedTx(serializedTx); expect(result).toEqual(tx); }); }); describe('#isMessageSignatureCorrect()', () => { it('should return true if signature made by a private key was correct', async () => { - const wallet = web3Accounts.create(); - const ADDRESS = wallet.address; + const account = web3Accounts.create(); + const ADDRESS = account.address; const message = 'Hello, world!'; - const signature = wallet.sign(message).signature; + const signature = utils.Eip712.sign(message, account.privateKey).signature; const web3 = new Web3(); const isValidSignature = await utils.isMessageSignatureCorrect( web3, @@ -297,79 +292,39 @@ describe('utils', () => { }); describe('#isTypedDataSignatureCorrect()', () => { - // TODO: Needs investigation - it.skip('should return true if correct', async () => { - // const account = web3Accounts.create(); - // const ADDRESS = account.address; - // const PRIVATE_KEY = account.privateKey; - + it('should return true if correct', async () => { const PRIVATE_KEY = '0x7726827caac94a7f9e1b160f7ea819f172f7b6f9d2a97f992c38edeab82d4110'; - - const account = web3Accounts.privateKeyToAccount(PRIVATE_KEY); - - const web3 = new Web3('https://mainnet.era.zksync.io'); - + const ADDRESS = '0x36615Cf349d7F6344891B1e7CA7C72883F5dc049'; const tx = { type: 113, chainId: 270, - from: account.address, + from: ADDRESS, to: '0xa61464658AfeAf65CccaaFD3a512b69A83B77618', value: 7_000_000n, }; - // const eip712Signer = new EIP712Signer(new Wallet(PRIVATE_KEY), web3.config.chainId); - - const signInput = utils.getSignInput(tx); - - // const signInput = { - // txType: 113, - // from: '0x99F3629e38c617cb619682f721Aaf9F61a3DE3d3', - // to: '0xa61464658AfeAf65CccaaFD3a512b69A83B77618', - // gasLimit: 0n, - // gasPerPubdataByteLimit: 50000, - // maxFeePerGas: 0n, - // maxPriorityFeePerGas: 0n, - // paymaster: '0x0000000000000000000000000000000000000000', - // nonce: 0, - // value: 7000000n, - // data: '0x', - // factoryDeps: [], - // paymasterInput: '0x', - // }; - const domain = { - name: 'zkSync', - version: '2', - chainId: tx.chainId, - }; - console.log('domain', domain); - web3.eth.accounts.wallet.add(account); - - console.log('account', account); - console.log('signInput', signInput); - // const hash = eip712TxHash(tx); - // console.log('typedData', hash); - - const signature = utils.serializeEip712(signInput); - console.log('signature', signature); - // const signature = await web3.eth.signTypedData(account.address, typedData); - - // const signature = - // '0x14920a5306c8b739fd2fbdd6bb933c54c05391ab9454741b5fa1132c31c6f35d4f8a716939e43df6eb660b5298d8354d08710a0d94e8d0c14dc88032cac5deda1b'; - // '0x71f8418080808094a61464658afeaf65cccaafd3a512b69a83b77618836acfc08082012c808082012c9434a535b103a4641ae33bb774a5922765a9ac2a6282c350c080c0' - console.log('signature\n\t', signature); - expect(signature).toBe( - '0x14920a5306c8b739fd2fbdd6bb933c54c05391ab9454741b5fa1132c31c6f35d4f8a716939e43df6eb660b5298d8354d08710a0d94e8d0c14dc88032cac5deda1b', + const typedDataStruct = utils.Eip712.txTypedData(tx); + + const message = web3Abi.getEncodedEip712Data(typedDataStruct); + + const web3 = new Web3('http://localhost:8545'); + const acc = web3.eth.accounts.privateKeyToAccount(PRIVATE_KEY); + web3.eth.accounts.wallet.add(acc); + // const signature = web3.eth.accounts.wallet.sign(message, true); + const signature = utils.Eip712.sign(message, PRIVATE_KEY); + + expect(signature.signature).toBe( + '0x5ea12f3d54a1624d7e7f5161dbf6ab746c3335e643b2966264e740cf8e10e9b64b0251fb79d9a5b11730387085a0d58f105926f72e20242ecb274639991939ca1b', ); const isValidSignature = await utils.isTypedDataSignatureCorrect( web3, - account.address, - domain, + ADDRESS, + typedDataStruct.domain, constants.EIP712_TYPES, - signInput, - signature, + utils.Eip712.getSignInput(tx), + signature.signature, ); - console.log('isValidSignature', isValidSignature); expect(isValidSignature).toBe(true); }); }); From 0c5918f0e26d8f4c0b2974c2f2b4a30d0ef31532 Mon Sep 17 00:00:00 2001 From: Oleksii Kosynskyi Date: Thu, 13 Jun 2024 13:16:31 -0400 Subject: [PATCH 20/30] fixes --- src/Eip712.ts | 54 ++++++++++++++++++++++++----------------- test/unit/utils.test.ts | 36 ++++++++++++--------------- 2 files changed, 48 insertions(+), 42 deletions(-) diff --git a/src/Eip712.ts b/src/Eip712.ts index 73fc1ce..77d5624 100644 --- a/src/Eip712.ts +++ b/src/Eip712.ts @@ -5,7 +5,6 @@ import * as web3Utils from 'web3-utils'; import * as web3Acccounts from 'web3-eth-accounts'; import { RLP } from '@ethereumjs/rlp'; import type { Address } from 'web3'; -import * as ethereumCryptography from 'ethereum-cryptography/secp256k1'; import { DEFAULT_GAS_PER_PUBDATA_LIMIT, EIP712_TX_TYPE, @@ -54,7 +53,7 @@ function arrayToPaymasterParams(arr: Uint8Array): PaymasterParams | undefined { }; } -export class Eip712 { +export class EIP712 { static getSignInput(transaction: Eip712TxData) { const maxFeePerGas = toHex(transaction.maxFeePerGas || transaction.gasPrice || 0n); const maxPriorityFeePerGas = toHex(transaction.maxPriorityFeePerGas || maxFeePerGas); @@ -89,7 +88,7 @@ export class Eip712 { version: '2', chainId: Number(transaction.chainId), }, - message: Eip712.getSignInput(transaction), + message: EIP712.getSignInput(transaction), }; } /** @@ -105,10 +104,10 @@ export class Eip712 { static txHash(transaction: Eip712TxData, ethSignature?: EthereumSignature): string { const bytes: string[] = []; - const typedDataStruct = Eip712.txTypedData(transaction); + const typedDataStruct = EIP712.txTypedData(transaction); bytes.push(web3Abi.getEncodedEip712Data(typedDataStruct, true)); - bytes.push(web3Utils.keccak256(Eip712.getSignature(typedDataStruct.message, ethSignature))); + bytes.push(web3Utils.keccak256(EIP712.getSignature(typedDataStruct.message, ethSignature))); return web3Utils.keccak256(concat(bytes)); } /** @@ -193,7 +192,7 @@ export class Eip712 { transaction.signature = new SignatureObject(ethSignature).toString(); } - transaction.hash = Eip712.txHash(transaction, ethSignature); + transaction.hash = EIP712.txHash(transaction, ethSignature); return transaction; } @@ -295,24 +294,35 @@ export class Eip712 { static sign(hash: string, PRIVATE_KEY: string) { return web3Acccounts.sign(hash, PRIVATE_KEY, true); } - static computePublicKey(key: Bytes, compressed?: boolean): string { - let bytes = toBytes(key); +} - // private key - if (bytes.length === 32) { - const pubKey = ethereumCryptography.secp256k1.getPublicKey(bytes, !!compressed); - return toHex(pubKey); - } +export class EIP712Signer { + private eip712Domain: Eip712TypedData['domain']; + private web3Account: web3Acccounts.Web3Account; + private chainId: number; + constructor(web3Account: web3Acccounts.Web3Account, chainId: number) { + this.web3Account = web3Account; + this.chainId = Number(web3Utils.toNumber(chainId)); + this.eip712Domain = { + name: 'zkSync', + version: '2', + chainId: Number(this.chainId), + }; + } - // raw public key; use uncompressed key with 0x04 prefix - if (bytes.length === 64) { - const pub = new Uint8Array(65); - pub[0] = 0x04; - pub.set(bytes, 1); - bytes = pub; - } + sign(tx: Eip712TxData): web3Acccounts.SignResult { + const typedDataStruct = EIP712.txTypedData({ + chainId: this.chainId, + ...tx, + }); + const message = web3Abi.getEncodedEip712Data(typedDataStruct); + return EIP712.sign(message, this.web3Account.privateKey); + } - const point = ethereumCryptography.secp256k1.ProjectivePoint.fromHex(bytes); - return toHex(point.toRawBytes(compressed)); + /** + * Returns zkSync Era EIP712 domain. + */ + getDomain(): Eip712TypedData['domain'] { + return this.eip712Domain; } } diff --git a/test/unit/utils.test.ts b/test/unit/utils.test.ts index 361ee57..be1c0c3 100644 --- a/test/unit/utils.test.ts +++ b/test/unit/utils.test.ts @@ -1,10 +1,10 @@ import { Web3 } from 'web3'; import * as web3Accounts from 'web3-eth-accounts'; -import * as web3Abi from 'web3-eth-abi'; import type { types } from '../../src'; import { utils } from '../../src'; import { ADDRESS1, ADDRESS2 } from '../utils'; import * as constants from '../../src/constants'; +import { EIP712Signer } from '../../src/Eip712'; describe('utils', () => { describe('#getHashedL2ToL1Msg()', () => { @@ -116,7 +116,7 @@ describe('utils', () => { describe('#serializeEip712()', () => { it('should throw an error when `tx.chainId` is not specified', () => { try { - utils.Eip712.serialize({}); + utils.EIP712.serialize({}); } catch (e) { expect((e as Error).message).toBe("Transaction chainId isn't set!"); } @@ -124,7 +124,7 @@ describe('utils', () => { it('should throw an error when `tx.from` is not specified', () => { try { - utils.Eip712.serialize({ chainId: 270 }); + utils.EIP712.serialize({ chainId: 270 }); } catch (e) { expect((e as Error).message).toBe( 'Explicitly providing `from` field is required for EIP712 transactions!', @@ -134,7 +134,7 @@ describe('utils', () => { it('should throw an error when `tx.customData.customSignature` is empty string', () => { try { - utils.Eip712.serialize({ + utils.EIP712.serialize({ chainId: 270, from: ADDRESS1, customData: { @@ -149,7 +149,7 @@ describe('utils', () => { it('should return a serialized transaction with populated default values', () => { const tx = '0x71ea8080808080808082010e808082010e9436615cf349d7f6344891b1e7ca7c72883f5dc04982c350c080c0'; - const result = utils.Eip712.serialize({ + const result = utils.EIP712.serialize({ chainId: 270, from: ADDRESS1, }); @@ -159,7 +159,7 @@ describe('utils', () => { it('should return a serialized transaction with provided signature', async () => { const tx = '0x71f87f8080808094a61464658afeaf65cccaafd3a512b69a83b77618830f42408001a073a20167b8d23b610b058c05368174495adf7da3a4ed4a57eb6dbdeb1fafc24aa02f87530d663a0d061f69bb564d2c6fb46ae5ae776bbd4bd2a2a4478b9cd1b42a82010e9436615cf349d7f6344891b1e7ca7c72883f5dc04982c350c080c0'; - const result = utils.Eip712.serialize( + const result = utils.EIP712.serialize( { chainId: 270, from: ADDRESS1, @@ -242,7 +242,7 @@ describe('utils', () => { const serializedTx = '0x71f87f8080808094a61464658afeaf65cccaafd3a512b69a83b77618830f42408001a073a20167b8d23b610b058c05368174495adf7da3a4ed4a57eb6dbdeb1fafc24aa02f87530d663a0d061f69bb564d2c6fb46ae5ae776bbd4bd2a2a4478b9cd1b42a82010e9436615cf349d7f6344891b1e7ca7c72883f5dc04982c350c080c0'; - const result = utils.Eip712.fromSerializedTx(serializedTx); + const result = utils.EIP712.fromSerializedTx(serializedTx); expect(result).toEqual(tx); }); @@ -270,7 +270,7 @@ describe('utils', () => { const serializedTx = '0x71f83e8080808094a61464658afeaf65cccaafd3a512b69a83b77618808082010e808082010e9436615cf349d7f6344891b1e7ca7c72883f5dc04982c350c080c0'; - const result = utils.Eip712.fromSerializedTx(serializedTx); + const result = utils.EIP712.fromSerializedTx(serializedTx); expect(result).toEqual(tx); }); }); @@ -279,7 +279,7 @@ describe('utils', () => { const account = web3Accounts.create(); const ADDRESS = account.address; const message = 'Hello, world!'; - const signature = utils.Eip712.sign(message, account.privateKey).signature; + const signature = utils.EIP712.sign(message, account.privateKey).signature; const web3 = new Web3(); const isValidSignature = await utils.isMessageSignatureCorrect( web3, @@ -303,16 +303,12 @@ describe('utils', () => { to: '0xa61464658AfeAf65CccaaFD3a512b69A83B77618', value: 7_000_000n, }; - - const typedDataStruct = utils.Eip712.txTypedData(tx); - - const message = web3Abi.getEncodedEip712Data(typedDataStruct); - + const eip712Signer = new EIP712Signer( + web3Accounts.privateKeyToAccount(PRIVATE_KEY), + 270, + ); + const signature = eip712Signer.sign(tx); const web3 = new Web3('http://localhost:8545'); - const acc = web3.eth.accounts.privateKeyToAccount(PRIVATE_KEY); - web3.eth.accounts.wallet.add(acc); - // const signature = web3.eth.accounts.wallet.sign(message, true); - const signature = utils.Eip712.sign(message, PRIVATE_KEY); expect(signature.signature).toBe( '0x5ea12f3d54a1624d7e7f5161dbf6ab746c3335e643b2966264e740cf8e10e9b64b0251fb79d9a5b11730387085a0d58f105926f72e20242ecb274639991939ca1b', @@ -320,9 +316,9 @@ describe('utils', () => { const isValidSignature = await utils.isTypedDataSignatureCorrect( web3, ADDRESS, - typedDataStruct.domain, + eip712Signer.getDomain(), constants.EIP712_TYPES, - utils.Eip712.getSignInput(tx), + utils.EIP712.getSignInput(tx), signature.signature, ); expect(isValidSignature).toBe(true); From 03252b3d958766e3c28615bea1c55c543748de48 Mon Sep 17 00:00:00 2001 From: Oleksii Kosynskyi Date: Thu, 13 Jun 2024 13:33:11 -0400 Subject: [PATCH 21/30] remove unnecessary things --- package.json | 3 +-- src/utils.ts | 17 +------------- test/integration/utils.test.ts | 41 ++++++++++++++++++++++++++++++++++ test/unit/utils.test.ts | 35 ----------------------------- 4 files changed, 43 insertions(+), 53 deletions(-) create mode 100644 test/integration/utils.test.ts diff --git a/package.json b/package.json index eeaff5a..212268d 100644 --- a/package.json +++ b/package.json @@ -30,8 +30,7 @@ "web3-eth-accounts": "^4.1.2", "web3-eth-contract": "^4.5.0", "web3-types": "^1.6.0", - "web3-utils": "^4.3.0", - "web3-validator": "^2.0.6" + "web3-utils": "^4.3.0" }, "devDependencies": { "@chainsafe/eslint-config": "^2.1.1", diff --git a/src/utils.ts b/src/utils.ts index 6dd5ac6..a5641aa 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -149,22 +149,7 @@ function recoverSignerAddress( return web3Accounts.recover(message, v, r, s, undefined, true); } -function _getBytes(value: Bytes): Uint8Array { - if (value instanceof Uint8Array) { - return value; - } - if (typeof value === 'string' && value.match(/^0x([0-9a-f][0-9a-f])*$/i)) { - const result = new Uint8Array((value.length - 2) / 2); - let offset = 2; - for (let i = 0; i < result.length; i++) { - result[i] = parseInt(value.substring(offset, offset + 2), 16); - offset += 2; - } - return result; - } - throw new Error('Invalid BytesLike value'); -} export class SignatureObject { public r: Uint8Array; public s: Uint8Array; @@ -178,7 +163,7 @@ export class SignatureObject { v?: web3Types.Numbers, ) { if (typeof rOrSignature === 'string') { - const bytes: Uint8Array = _getBytes(rOrSignature); + const bytes: Uint8Array = web3Utils.hexToBytes(rOrSignature); if (bytes.length === 64) { const r = bytes.slice(0, 32); diff --git a/test/integration/utils.test.ts b/test/integration/utils.test.ts new file mode 100644 index 0000000..3f6c9e4 --- /dev/null +++ b/test/integration/utils.test.ts @@ -0,0 +1,41 @@ +import { Web3 } from 'web3'; +import * as web3Accounts from 'web3-eth-accounts'; +import { utils } from '../../src'; +import * as constants from '../../src/constants'; +import { EIP712Signer } from '../../src/Eip712'; + +describe('utils', () => { + describe('#isTypedDataSignatureCorrect()', () => { + it('should return true if correct', async () => { + const PRIVATE_KEY = + '0x7726827caac94a7f9e1b160f7ea819f172f7b6f9d2a97f992c38edeab82d4110'; + const ADDRESS = '0x36615Cf349d7F6344891B1e7CA7C72883F5dc049'; + const tx = { + type: 113, + chainId: 270, + from: ADDRESS, + to: '0xa61464658AfeAf65CccaaFD3a512b69A83B77618', + value: 7_000_000n, + }; + const eip712Signer = new EIP712Signer( + web3Accounts.privateKeyToAccount(PRIVATE_KEY), + 270, + ); + const signature = eip712Signer.sign(tx); + const web3 = new Web3('http://localhost:8545'); + + expect(signature.signature).toBe( + '0x5ea12f3d54a1624d7e7f5161dbf6ab746c3335e643b2966264e740cf8e10e9b64b0251fb79d9a5b11730387085a0d58f105926f72e20242ecb274639991939ca1b', + ); + const isValidSignature = await utils.isTypedDataSignatureCorrect( + web3, + ADDRESS, + eip712Signer.getDomain(), + constants.EIP712_TYPES, + utils.EIP712.getSignInput(tx), + signature.signature, + ); + expect(isValidSignature).toBe(true); + }); + }); +}); diff --git a/test/unit/utils.test.ts b/test/unit/utils.test.ts index be1c0c3..058afb3 100644 --- a/test/unit/utils.test.ts +++ b/test/unit/utils.test.ts @@ -4,7 +4,6 @@ import type { types } from '../../src'; import { utils } from '../../src'; import { ADDRESS1, ADDRESS2 } from '../utils'; import * as constants from '../../src/constants'; -import { EIP712Signer } from '../../src/Eip712'; describe('utils', () => { describe('#getHashedL2ToL1Msg()', () => { @@ -290,38 +289,4 @@ describe('utils', () => { expect(isValidSignature).toBe(true); }); }); - - describe('#isTypedDataSignatureCorrect()', () => { - it('should return true if correct', async () => { - const PRIVATE_KEY = - '0x7726827caac94a7f9e1b160f7ea819f172f7b6f9d2a97f992c38edeab82d4110'; - const ADDRESS = '0x36615Cf349d7F6344891B1e7CA7C72883F5dc049'; - const tx = { - type: 113, - chainId: 270, - from: ADDRESS, - to: '0xa61464658AfeAf65CccaaFD3a512b69A83B77618', - value: 7_000_000n, - }; - const eip712Signer = new EIP712Signer( - web3Accounts.privateKeyToAccount(PRIVATE_KEY), - 270, - ); - const signature = eip712Signer.sign(tx); - const web3 = new Web3('http://localhost:8545'); - - expect(signature.signature).toBe( - '0x5ea12f3d54a1624d7e7f5161dbf6ab746c3335e643b2966264e740cf8e10e9b64b0251fb79d9a5b11730387085a0d58f105926f72e20242ecb274639991939ca1b', - ); - const isValidSignature = await utils.isTypedDataSignatureCorrect( - web3, - ADDRESS, - eip712Signer.getDomain(), - constants.EIP712_TYPES, - utils.EIP712.getSignInput(tx), - signature.signature, - ); - expect(isValidSignature).toBe(true); - }); - }); }); From 7a2e1ea8fbda8e61e4620c6e746e7cf4ec224877 Mon Sep 17 00:00:00 2001 From: Oleksii Kosynskyi Date: Thu, 13 Jun 2024 14:30:05 -0400 Subject: [PATCH 22/30] fix --- src/Eip712.ts | 4 ---- src/types.ts | 14 -------------- 2 files changed, 18 deletions(-) diff --git a/src/Eip712.ts b/src/Eip712.ts index 77d5624..f27402d 100644 --- a/src/Eip712.ts +++ b/src/Eip712.ts @@ -252,13 +252,9 @@ export class EIP712 { if (signature) { const signatureObject = new SignatureObject(signature); - fields.push(toHex(Number(signatureObject.v) === 27 ? 0 : 1)); fields.push(toHex(signatureObject.r)); fields.push(toHex(signatureObject.s)); - - // const sig = new SignatureObject(signature); - // fields.push(toBytes(sig.yParity)); } else { fields.push(toHex(transaction.chainId)); fields.push('0x'); diff --git a/src/types.ts b/src/types.ts index 3bd3c7b..cf5721c 100644 --- a/src/types.ts +++ b/src/types.ts @@ -13,20 +13,6 @@ import type { TransactionReceipt, } from 'web3-types'; -// import type { -// FeeMarketEIP1559Transaction, -// TxOptions -// } from 'web3-eth-accounts'; - -// import { -// EIP712_TX_TYPE, -// parseEip712, -// serializeEip712, -// sleep, -// eip712TxHash, -// isAddressEq, -// } from './utils'; - import type { RpcMethods } from './rpc.methods'; export type { Bytes, HexString, Numbers } from 'web3-types'; From 7fcf12d75cc8eccfbf4b47cd68e757384af79ded Mon Sep 17 00:00:00 2001 From: Oleksii Kosynskyi Date: Fri, 14 Jun 2024 13:58:46 -0400 Subject: [PATCH 23/30] use baseTransaction for signing --- src/Eip712.ts | 126 +++++++++++++++++++++++++++++---- src/plugin.ts | 7 +- src/utils.ts | 18 ++--- test/integration/utils.test.ts | 14 ++-- test/unit/utils.test.ts | 2 +- 5 files changed, 134 insertions(+), 33 deletions(-) diff --git a/src/Eip712.ts b/src/Eip712.ts index f27402d..100673c 100644 --- a/src/Eip712.ts +++ b/src/Eip712.ts @@ -1,8 +1,10 @@ import { bytesToHex, toBigInt, toHex } from 'web3-utils'; -import type { Bytes, Eip712TypedData } from 'web3-types'; +import type { Bytes, Eip712TypedData, Numbers } from 'web3-types'; import * as web3Abi from 'web3-eth-abi'; import * as web3Utils from 'web3-utils'; -import * as web3Acccounts from 'web3-eth-accounts'; +import type * as web3Acccounts from 'web3-eth-accounts'; +import type { JsonTx, TxOptions, TxValuesArray } from 'web3-eth-accounts'; +import { BaseTransaction, toUint8Array } from 'web3-eth-accounts'; import { RLP } from '@ethereumjs/rlp'; import type { Address } from 'web3'; import { @@ -219,8 +221,7 @@ export class EIP712 { return new Uint8Array([...r, ...s, v]); } - - static serialize(transaction: Eip712TxData, signature?: SignatureLike): string { + static raw(transaction: Eip712TxData, signature?: SignatureLike) { if (!transaction.chainId) { throw Error("Transaction chainId isn't set!"); } @@ -283,12 +284,18 @@ export class EIP712 { } else { fields.push([]); } - + return fields; + } + static serialize(transaction: Eip712TxData, signature?: SignatureLike): string { + const fields = EIP712.raw(transaction, signature); return concat([new Uint8Array([EIP712_TX_TYPE]), RLP.encode(fields)]); } - static sign(hash: string, PRIVATE_KEY: string) { - return web3Acccounts.sign(hash, PRIVATE_KEY, true); + static sign(hash: string, privateKey: string) { + return new EIP712Transaction({}).ecsign( + toUint8Array(web3Utils.keccak256(hash)), + toUint8Array(privateKey), + ); } } @@ -306,13 +313,8 @@ export class EIP712Signer { }; } - sign(tx: Eip712TxData): web3Acccounts.SignResult { - const typedDataStruct = EIP712.txTypedData({ - chainId: this.chainId, - ...tx, - }); - const message = web3Abi.getEncodedEip712Data(typedDataStruct); - return EIP712.sign(message, this.web3Account.privateKey); + sign(tx: Eip712TxData): SignatureObject | undefined { + return new EIP712Transaction(tx).sign(toBytes(this.web3Account.privateKey)).getSignature(); } /** @@ -322,3 +324,99 @@ export class EIP712Signer { return this.eip712Domain; } } +export class EIP712Transaction extends BaseTransaction { + private txData: Eip712TxData; + private signature?: SignatureObject; + constructor(txData: Eip712TxData) { + super(txData, {} as TxOptions); + const { v, r, s, ...data } = txData; + + if (r && s) { + this.signature = new SignatureObject(toUint8Array(r), toUint8Array(s), toBigInt(v)); + } + + this.txData = data; + } + public getSignature(): SignatureObject | undefined { + return this.signature; + } + public getMessageToSign(isHash = false): Uint8Array { + const typedDataStruct = EIP712.txTypedData(this.txData); + const message = web3Abi.getEncodedEip712Data(typedDataStruct, isHash); + return web3Utils.hexToBytes(message); + } + _processSignature( + v: Numbers, + r: EthereumSignature['r'], + s: EthereumSignature['s'], + ): EIP712Transaction { + const signature = new SignatureObject(toUint8Array(r), toUint8Array(s), toBigInt(v)); + return new EIP712Transaction({ + ...this.txData, + v: toBigInt(signature.v), + r: toHex(signature.r), + s: toHex(signature.s), + }); + } + public ecsign(msgHash: Uint8Array, privateKey: Uint8Array, chainId?: bigint) { + const { s, r, v } = this._ecsign(msgHash, privateKey, chainId); + this.signature = new SignatureObject(toUint8Array(r), toUint8Array(s), toBigInt(v)); + return this.signature; + } + + protected _errorMsg(msg: string): string { + return `${msg} (${this.errorStr()})`; + } + + errorStr(): string { + return ''; + } + + getMessageToVerifySignature(): Uint8Array { + return this.getMessageToSign(); + } + + getSenderPublicKey(): Uint8Array { + // @TODO: implement recover transaction here + return new Uint8Array(); + } + + getUpfrontCost(): bigint { + return 0n; + } + + hash(): Uint8Array { + return toUint8Array(EIP712.txHash(this.txData)); + } + // @ts-ignore-next-line + raw(): TxValuesArray[] { + return EIP712.raw(this.txData) as unknown as TxValuesArray[]; + } + + serialize(): Uint8Array { + return toUint8Array(EIP712.serialize(this.txData)); + } + + toJSON(): JsonTx { + const data = EIP712.getSignInput(this.txData); + return { + to: data.to, + gasLimit: toHex(data.gasLimit), + // @ts-ignore-next-line + gasPerPubdataByteLimit: data.gasPerPubdataByteLimit, + customData: data.customData, + maxFeePerGas: data.maxFeePerGas, + maxPriorityFeePerGas: data.maxPriorityFeePerGas, + paymaster: data.paymaster, + nonce: toHex(data.nonce), + value: toHex(data.value), + data: toHex(data.data), + factoryDeps: data.factoryDeps, + paymasterInput: data.paymasterInput, + type: toHex(data.txType), + v: this.signature?.v ? toHex(this.signature.v) : undefined, + r: this.signature?.r ? toHex(this.signature?.r) : undefined, + s: this.signature?.s ? toHex(this.signature?.s) : undefined, + }; + } +} diff --git a/src/plugin.ts b/src/plugin.ts index 36d67c1..9041550 100644 --- a/src/plugin.ts +++ b/src/plugin.ts @@ -3,9 +3,10 @@ import type { Address } from 'web3-types'; import { Contract } from 'web3-eth-contract'; import { Web3PluginBase } from 'web3-core'; +import { TransactionFactory } from '../../web3.js/packages/web3-eth-accounts'; import { IERC20ABI } from './contracts/IERC20'; import { RpcMethods } from './rpc.methods'; -import { ETH_ADDRESS, ZERO_ADDRESS } from './constants'; +import { EIP712_TX_TYPE, ETH_ADDRESS, ZERO_ADDRESS } from './constants'; import { IL2BridgeABI } from './contracts/IL2Bridge'; import { IZkSyncABI } from './contracts/IZkSyncStateTransition'; import { IBridgehubABI } from './contracts/IBridgehub'; @@ -14,6 +15,7 @@ import { IL1MessengerABI } from './contracts/IL1Messenger'; import { IERC1271ABI } from './contracts/IERC1271'; import { IL1BridgeABI } from './contracts/IL1ERC20Bridge'; import { INonceHolderABI } from './contracts/INonceHolder'; +import { EIP712Transaction } from './Eip712'; export class ZkSyncPlugin extends Web3PluginBase { public pluginNamespace = 'zkSync'; @@ -65,7 +67,8 @@ export class ZkSyncPlugin extends Web3PluginBase { constructor() { super(); - + // @ts-ignore-next-line + TransactionFactory.registerTransactionType(EIP712_TX_TYPE, EIP712Transaction); this.erc20BridgeL1 = ''; this.erc20BridgeL2 = ''; this.wethBridgeL1 = ''; diff --git a/src/utils.ts b/src/utils.ts index a5641aa..67af0d0 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -12,6 +12,7 @@ import * as web3Types from 'web3-types'; import * as web3Abi from 'web3-eth-abi'; import * as web3Contract from 'web3-eth-contract'; import type { Bytes } from 'web3-types'; +import { toUint8Array } from 'web3-eth-accounts'; import type { DeploymentInfo, EthereumSignature } from './types'; import { // PaymasterParams, @@ -139,15 +140,16 @@ function recoverSignerAddress( message = messageOrData; } - if (typeof signature === 'string') { - return web3Accounts.recover(message, signature, undefined, undefined, undefined, true); - } - - const r = web3Utils.toHex(signature.r); - const s = web3Utils.toHex(signature.s); - const v = web3Utils.toHex(signature.v); + const signatureObject = + typeof signature === 'string' + ? new SignatureObject(signature) + : new SignatureObject( + toUint8Array(signature.r), + toUint8Array(signature.s), + signature.v, + ); - return web3Accounts.recover(message, v, r, s, undefined, true); + return web3Accounts.recover(web3Utils.keccak256(message), signatureObject.serialized, true); } export class SignatureObject { diff --git a/test/integration/utils.test.ts b/test/integration/utils.test.ts index 3f6c9e4..1c18066 100644 --- a/test/integration/utils.test.ts +++ b/test/integration/utils.test.ts @@ -1,8 +1,7 @@ -import { Web3 } from 'web3'; import * as web3Accounts from 'web3-eth-accounts'; -import { utils } from '../../src'; +import { Web3 } from '../../../web3.js/packages/web3'; import * as constants from '../../src/constants'; -import { EIP712Signer } from '../../src/Eip712'; +import * as utils from '../../src/utils'; describe('utils', () => { describe('#isTypedDataSignatureCorrect()', () => { @@ -17,14 +16,13 @@ describe('utils', () => { to: '0xa61464658AfeAf65CccaaFD3a512b69A83B77618', value: 7_000_000n, }; - const eip712Signer = new EIP712Signer( + const eip712Signer = new utils.EIP712Signer( web3Accounts.privateKeyToAccount(PRIVATE_KEY), 270, ); - const signature = eip712Signer.sign(tx); + const signature = eip712Signer.sign(tx) as utils.SignatureObject; const web3 = new Web3('http://localhost:8545'); - - expect(signature.signature).toBe( + expect(signature.serialized).toBe( '0x5ea12f3d54a1624d7e7f5161dbf6ab746c3335e643b2966264e740cf8e10e9b64b0251fb79d9a5b11730387085a0d58f105926f72e20242ecb274639991939ca1b', ); const isValidSignature = await utils.isTypedDataSignatureCorrect( @@ -33,7 +31,7 @@ describe('utils', () => { eip712Signer.getDomain(), constants.EIP712_TYPES, utils.EIP712.getSignInput(tx), - signature.signature, + signature.serialized, ); expect(isValidSignature).toBe(true); }); diff --git a/test/unit/utils.test.ts b/test/unit/utils.test.ts index 058afb3..7a12da7 100644 --- a/test/unit/utils.test.ts +++ b/test/unit/utils.test.ts @@ -278,7 +278,7 @@ describe('utils', () => { const account = web3Accounts.create(); const ADDRESS = account.address; const message = 'Hello, world!'; - const signature = utils.EIP712.sign(message, account.privateKey).signature; + const signature = utils.EIP712.sign(message, account.privateKey).serialized; const web3 = new Web3(); const isValidSignature = await utils.isMessageSignatureCorrect( web3, From 90d3a05dd14f1596a2f91862bd7e0298c36aafe6 Mon Sep 17 00:00:00 2001 From: Oleksii Kosynskyi Date: Mon, 17 Jun 2024 21:45:10 -0400 Subject: [PATCH 24/30] latest version web3js --- package.json | 6 +- src/Eip712.ts | 40 ++++++----- src/types.ts | 16 +++++ yarn.lock | 185 ++++++++++++-------------------------------------- 4 files changed, 86 insertions(+), 161 deletions(-) diff --git a/package.json b/package.json index 212268d..ef67ea6 100644 --- a/package.json +++ b/package.json @@ -25,11 +25,11 @@ "dependencies": { "ethereum-cryptography": "^2.1.3", "hardhat": "^2.19.4", - "web3-core": "^4.4.0", + "web3-core": "^4.5.0", "web3-eth-abi": "^4.2.2", "web3-eth-accounts": "^4.1.2", "web3-eth-contract": "^4.5.0", - "web3-types": "^1.6.0", + "web3-types": "^1.7.0", "web3-utils": "^4.3.0" }, "devDependencies": { @@ -42,7 +42,7 @@ "ts-jest": "^29.1.2", "ts-node": "^10.9.2", "typescript": "^5.3.3", - "web3": "^4.9.0" + "web3": "^4.10.0" }, "peerDependencies": { "web3": ">= 4.0.3" diff --git a/src/Eip712.ts b/src/Eip712.ts index 100673c..8fa13ba 100644 --- a/src/Eip712.ts +++ b/src/Eip712.ts @@ -2,8 +2,7 @@ import { bytesToHex, toBigInt, toHex } from 'web3-utils'; import type { Bytes, Eip712TypedData, Numbers } from 'web3-types'; import * as web3Abi from 'web3-eth-abi'; import * as web3Utils from 'web3-utils'; -import type * as web3Acccounts from 'web3-eth-accounts'; -import type { JsonTx, TxOptions, TxValuesArray } from 'web3-eth-accounts'; +import type * as web3Accounts from 'web3-eth-accounts'; import { BaseTransaction, toUint8Array } from 'web3-eth-accounts'; import { RLP } from '@ethereumjs/rlp'; import type { Address } from 'web3'; @@ -13,7 +12,13 @@ import { EIP712_TYPES, ZERO_ADDRESS, } from './constants'; -import type { Eip712Meta, Eip712TxData, EthereumSignature, PaymasterParams } from './types'; +import type { + Eip712Meta, + Eip712SignedInput, + Eip712TxData, + EthereumSignature, + PaymasterParams, +} from './types'; import type { SignatureLike } from './utils'; import { concat, hashBytecode, SignatureObject, toBytes } from './utils'; @@ -56,7 +61,7 @@ function arrayToPaymasterParams(arr: Uint8Array): PaymasterParams | undefined { } export class EIP712 { - static getSignInput(transaction: Eip712TxData) { + static getSignInput(transaction: Eip712TxData): Eip712SignedInput { const maxFeePerGas = toHex(transaction.maxFeePerGas || transaction.gasPrice || 0n); const maxPriorityFeePerGas = toHex(transaction.maxPriorityFeePerGas || maxFeePerGas); const gasPerPubdataByteLimit = toHex( @@ -66,15 +71,15 @@ export class EIP712 { txType: transaction.type || EIP712_TX_TYPE, from: transaction.from ? toHex(transaction.from) : undefined, to: transaction.to ? toHex(transaction.to) : undefined, - gasLimit: transaction.gasLimit || 0, + gasLimit: transaction.gasLimit ? toBigInt(transaction.gasLimit) : 0, gasPerPubdataByteLimit: gasPerPubdataByteLimit, customData: transaction.customData, maxFeePerGas, maxPriorityFeePerGas, paymaster: transaction.customData?.paymasterParams?.paymaster || ZERO_ADDRESS, - nonce: transaction.nonce || 0, - value: transaction.value || toHex(0), - data: transaction.data || '0x', + nonce: transaction.nonce ? toBigInt(transaction.nonce) : 0, + value: transaction.value ? toHex(transaction.value) : '0x0', + data: transaction.data ? toHex(transaction.data) : '0x', factoryDeps: transaction.customData?.factoryDeps?.map((dep: Bytes) => hashBytecode(dep)) || [], paymasterInput: transaction.customData?.paymasterParams?.paymasterInput || '0x', @@ -301,9 +306,9 @@ export class EIP712 { export class EIP712Signer { private eip712Domain: Eip712TypedData['domain']; - private web3Account: web3Acccounts.Web3Account; + private web3Account: web3Accounts.Web3Account; private chainId: number; - constructor(web3Account: web3Acccounts.Web3Account, chainId: number) { + constructor(web3Account: web3Accounts.Web3Account, chainId: number) { this.web3Account = web3Account; this.chainId = Number(web3Utils.toNumber(chainId)); this.eip712Domain = { @@ -328,7 +333,7 @@ export class EIP712Transaction extends BaseTransaction { private txData: Eip712TxData; private signature?: SignatureObject; constructor(txData: Eip712TxData) { - super(txData, {} as TxOptions); + super(txData, {} as web3Accounts.TxOptions); const { v, r, s, ...data } = txData; if (r && s) { @@ -359,6 +364,7 @@ export class EIP712Transaction extends BaseTransaction { }); } public ecsign(msgHash: Uint8Array, privateKey: Uint8Array, chainId?: bigint) { + // @ts-ignore-next-time until new web3js release const { s, r, v } = this._ecsign(msgHash, privateKey, chainId); this.signature = new SignatureObject(toUint8Array(r), toUint8Array(s), toBigInt(v)); return this.signature; @@ -389,24 +395,24 @@ export class EIP712Transaction extends BaseTransaction { return toUint8Array(EIP712.txHash(this.txData)); } // @ts-ignore-next-line - raw(): TxValuesArray[] { - return EIP712.raw(this.txData) as unknown as TxValuesArray[]; + raw(): web3Accounts.TxValuesArray[] { + return EIP712.raw(this.txData) as unknown as web3Accounts.TxValuesArray[]; } serialize(): Uint8Array { return toUint8Array(EIP712.serialize(this.txData)); } - toJSON(): JsonTx { + toJSON(): web3Accounts.JsonTx { const data = EIP712.getSignInput(this.txData); return { - to: data.to, + to: data.to && toHex(data.to), gasLimit: toHex(data.gasLimit), // @ts-ignore-next-line gasPerPubdataByteLimit: data.gasPerPubdataByteLimit, customData: data.customData, - maxFeePerGas: data.maxFeePerGas, - maxPriorityFeePerGas: data.maxPriorityFeePerGas, + maxFeePerGas: toHex(data.maxFeePerGas), + maxPriorityFeePerGas: toHex(data.maxPriorityFeePerGas), paymaster: data.paymaster, nonce: toHex(data.nonce), value: toHex(data.value), diff --git a/src/types.ts b/src/types.ts index cf5721c..243c60a 100644 --- a/src/types.ts +++ b/src/types.ts @@ -867,3 +867,19 @@ export type Eip712TxData = FeeMarketEIP1559TxData & { hash?: string; signature?: string; }; +export type Eip712SignedInput = FeeMarketEIP1559TxData & { + customData?: null | Eip712Meta; + data: Bytes; + value: Bytes; + nonce: Numbers; + gasLimit: Numbers; + maxFeePerGas: Numbers; + maxPriorityFeePerGas: Numbers; + from?: Address; + txType: Numbers; + gasPerPubdataByteLimit?: Numbers; + paymaster: Address; + factoryDeps: Bytes[]; + paymasterInput: Bytes; + [key: string]: unknown; +}; diff --git a/yarn.lock b/yarn.lock index f0b5754..0e64382 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5733,62 +5733,29 @@ walker@^1.0.8: dependencies: makeerror "1.0.12" -web3-core@^4.3.0: - version "4.3.1" - resolved "https://registry.yarnpkg.com/web3-core/-/web3-core-4.3.1.tgz#5c3b5b59f1e31537a64237caa5fd83f5ffd7b0f3" - integrity sha512-xa3w5n/ESxp5HIbrwsYBhpAPx2KI5LprjRFEtRwP0GpqqhTcCSMMYoyItRqQQ+k9YnB0PoFpWJfJI6Qn5x8YUQ== - dependencies: - web3-errors "^1.1.4" - web3-eth-iban "^4.0.7" - web3-providers-http "^4.1.0" - web3-providers-ws "^4.0.7" - web3-types "^1.3.1" - web3-utils "^4.0.7" - web3-validator "^2.0.3" - optionalDependencies: - web3-providers-ipc "^4.0.7" - -web3-core@^4.4.0: - version "4.4.0" - resolved "https://registry.yarnpkg.com/web3-core/-/web3-core-4.4.0.tgz#f2cd48aecda5ec34170edf7470001f90fdea1dc6" - integrity sha512-sN1AkhTAFI89anOeCaO0c3GtiGeWtOGVc2tmTdQs2Rd14HuxLyDuLIF3/WwjtkDFRM2189uYy8HJJSWJvW2mYA== +web3-core@^4.3.0, web3-core@^4.4.0, web3-core@^4.5.0: + version "4.5.0" + resolved "https://registry.yarnpkg.com/web3-core/-/web3-core-4.5.0.tgz#f16e7f5bfa6373c7be45f0ed233aff479fd33079" + integrity sha512-Q8LIAqmF7vkRydBPiU+OC7wI44nEU6JEExolFaOakqrjMtQ1CWFHRUQMNJRDsk5bRirjyShuAsuqLeYByvvXhg== dependencies: web3-errors "^1.2.0" web3-eth-accounts "^4.1.2" web3-eth-iban "^4.0.7" web3-providers-http "^4.1.0" web3-providers-ws "^4.0.7" - web3-types "^1.6.0" + web3-types "^1.7.0" web3-utils "^4.3.0" web3-validator "^2.0.6" optionalDependencies: web3-providers-ipc "^4.0.7" -web3-errors@^1.1.3, web3-errors@^1.1.4: - version "1.1.4" - resolved "https://registry.yarnpkg.com/web3-errors/-/web3-errors-1.1.4.tgz#5667a0a5f66fc936e101ef32032ccc1e8ca4d5a1" - integrity sha512-WahtszSqILez+83AxGecVroyZsMuuRT+KmQp4Si5P4Rnqbczno1k748PCrZTS1J4UCPmXMG2/Vt+0Bz2zwXkwQ== - dependencies: - web3-types "^1.3.1" - -web3-errors@^1.2.0: +web3-errors@^1.1.3, web3-errors@^1.1.4, web3-errors@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/web3-errors/-/web3-errors-1.2.0.tgz#441acfd7fd744c9beedf23f277f20759fae92433" integrity sha512-58Kczou5zyjcm9LuSs5Hrm6VrG8t9p2J8X0yGArZrhKNPZL66gMGkOUpPx+EopE944Sk4yE+Q25hKv4H5BH+kA== dependencies: web3-types "^1.6.0" -web3-eth-abi@^4.1.4: - version "4.1.4" - resolved "https://registry.yarnpkg.com/web3-eth-abi/-/web3-eth-abi-4.1.4.tgz#56ae7ebb1385db1a948e69fb35f4057bff6743af" - integrity sha512-YLOBVVxxxLYKXjaiwZjEWYEnkMmmrm0nswZsvzSsINy/UgbWbzfoiZU+zn4YNWIEhORhx1p37iS3u/dP6VyC2w== - dependencies: - abitype "0.7.1" - web3-errors "^1.1.3" - web3-types "^1.3.0" - web3-utils "^4.0.7" - web3-validator "^2.0.3" - web3-eth-abi@^4.2.2: version "4.2.2" resolved "https://registry.yarnpkg.com/web3-eth-abi/-/web3-eth-abi-4.2.2.tgz#d7592e2cc113fd34da3fb4c933537ddf8639d9b2" @@ -5800,19 +5767,6 @@ web3-eth-abi@^4.2.2: web3-utils "^4.3.0" web3-validator "^2.0.6" -web3-eth-accounts@^4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/web3-eth-accounts/-/web3-eth-accounts-4.1.0.tgz#5b5e6c60d457e7b829ec590021fc87ada8585920" - integrity sha512-UFtAsOANsvihTQ6SSvOKguupmQkResyR9M9JNuOxYpKh7+3W+sTnbLXw2UbOSYIsKlc1mpqqW9bVr1SjqHDpUQ== - dependencies: - "@ethereumjs/rlp" "^4.0.1" - crc-32 "^1.2.2" - ethereum-cryptography "^2.0.0" - web3-errors "^1.1.3" - web3-types "^1.3.0" - web3-utils "^4.0.7" - web3-validator "^2.0.3" - web3-eth-accounts@^4.1.2: version "4.1.2" resolved "https://registry.yarnpkg.com/web3-eth-accounts/-/web3-eth-accounts-4.1.2.tgz#652d6e3daf4d6cb3fe67cec6a878e768f6e8b8e8" @@ -5839,18 +5793,18 @@ web3-eth-contract@^4.5.0: web3-utils "^4.3.0" web3-validator "^2.0.6" -web3-eth-ens@^4.3.0: - version "4.3.0" - resolved "https://registry.yarnpkg.com/web3-eth-ens/-/web3-eth-ens-4.3.0.tgz#f44f279a4a07eae36e3de8384989ed069201a795" - integrity sha512-QpiKT9GqJouH5kEI/pRFprh88YPCtbht2Ym6rrklZ+VoWl9D+wLfbwvW7Aox349FS7k0UX2qVins5tVNLJ5GCQ== +web3-eth-ens@^4.4.0: + version "4.4.0" + resolved "https://registry.yarnpkg.com/web3-eth-ens/-/web3-eth-ens-4.4.0.tgz#bc0d11d755cb15ed4b82e38747c5104622d9a4b9" + integrity sha512-DeyVIS060hNV9g8dnTx92syqvgbvPricE3MerCxe/DquNZT3tD8aVgFfq65GATtpCgDDJffO2bVeHp3XBemnSQ== dependencies: "@adraffy/ens-normalize" "^1.8.8" - web3-core "^4.4.0" + web3-core "^4.5.0" web3-errors "^1.2.0" - web3-eth "^4.7.0" + web3-eth "^4.8.0" web3-eth-contract "^4.5.0" web3-net "^4.1.0" - web3-types "^1.6.0" + web3-types "^1.7.0" web3-utils "^4.3.0" web3-validator "^2.0.6" @@ -5876,50 +5830,23 @@ web3-eth-personal@^4.0.8: web3-utils "^4.0.7" web3-validator "^2.0.3" -web3-eth@^4.3.1: - version "4.3.1" - resolved "https://registry.yarnpkg.com/web3-eth/-/web3-eth-4.3.1.tgz#cb4224356716da71e694091aa3fbf10bcb813fb5" - integrity sha512-zJir3GOXooHQT85JB8SrufE+Voo5TtXdjhf1D8IGXmxM8MrhI8AT+Pgt4siBTupJcu5hF17iGmTP/Nj2XnaibQ== - dependencies: - setimmediate "^1.0.5" - web3-core "^4.3.0" - web3-errors "^1.1.3" - web3-eth-abi "^4.1.4" - web3-eth-accounts "^4.1.0" - web3-net "^4.0.7" - web3-providers-ws "^4.0.7" - web3-rpc-methods "^1.1.3" - web3-types "^1.3.0" - web3-utils "^4.0.7" - web3-validator "^2.0.3" - -web3-eth@^4.7.0: - version "4.7.0" - resolved "https://registry.yarnpkg.com/web3-eth/-/web3-eth-4.7.0.tgz#7d18815c7a79c200552bd0df8d3127f7532b3cc2" - integrity sha512-gqlWq4Xjz+yKL2MdxQ+BgR3F4CRo4AXWDXzftb3LDzvauEfjk/yRyoxkMSK4S9RIG96ylRImS172cV6cYzcukw== +web3-eth@^4.3.1, web3-eth@^4.7.0, web3-eth@^4.8.0: + version "4.8.0" + resolved "https://registry.yarnpkg.com/web3-eth/-/web3-eth-4.8.0.tgz#1db0b573d933b69032038e814df730136f4837ba" + integrity sha512-fobkdpwN9SH785/0LSLfxOMH4rZNAD/EvTKIHdpl4ZVz5XdKehX+xPMpSGDGwMlAQ7yXByjZDX3opzoqEQLWxg== dependencies: setimmediate "^1.0.5" - web3-core "^4.4.0" + web3-core "^4.5.0" web3-errors "^1.2.0" web3-eth-abi "^4.2.2" web3-eth-accounts "^4.1.2" web3-net "^4.1.0" web3-providers-ws "^4.0.7" web3-rpc-methods "^1.3.0" - web3-types "^1.6.0" + web3-types "^1.7.0" web3-utils "^4.3.0" web3-validator "^2.0.6" -web3-net@^4.0.7: - version "4.0.7" - resolved "https://registry.yarnpkg.com/web3-net/-/web3-net-4.0.7.tgz#ed2c1bd700cf94be93a6dbd8bd8aa413d8681942" - integrity sha512-SzEaXFrBjY25iQGk5myaOfO9ZyfTwQEa4l4Ps4HDNVMibgZji3WPzpjq8zomVHMwi8bRp6VV7YS71eEsX7zLow== - dependencies: - web3-core "^4.3.0" - web3-rpc-methods "^1.1.3" - web3-types "^1.3.0" - web3-utils "^4.0.7" - web3-net@^4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/web3-net/-/web3-net-4.1.0.tgz#db7bde675e58b153339e4f149f29ec0410d6bab2" @@ -5961,16 +5888,7 @@ web3-providers-ws@^4.0.7: web3-utils "^4.0.7" ws "^8.8.1" -web3-rpc-methods@^1.1.3: - version "1.1.3" - resolved "https://registry.yarnpkg.com/web3-rpc-methods/-/web3-rpc-methods-1.1.3.tgz#4be8a85628d8b69846e2e0afa0ed71e3f6eaf163" - integrity sha512-XB6SsCZZPdZUMPIRqDxJkZFKMu0/Y+yaExAt+Z7RqmuM7xF55fJ/Qb84LQho8zarvUoYziy4jnIfs+SXImxQUw== - dependencies: - web3-core "^4.3.0" - web3-types "^1.3.0" - web3-validator "^2.0.3" - -web3-rpc-methods@^1.3.0: +web3-rpc-methods@^1.1.3, web3-rpc-methods@^1.3.0: version "1.3.0" resolved "https://registry.yarnpkg.com/web3-rpc-methods/-/web3-rpc-methods-1.3.0.tgz#d5ee299a69389d63822d354ddee2c6a121a6f670" integrity sha512-/CHmzGN+IYgdBOme7PdqzF+FNeMleefzqs0LVOduncSaqsppeOEoskLXb2anSpzmQAP3xZJPaTrkQPWSJMORig== @@ -5979,27 +5897,22 @@ web3-rpc-methods@^1.3.0: web3-types "^1.6.0" web3-validator "^2.0.6" -web3-types@^1.3.0, web3-types@^1.3.1: - version "1.3.1" - resolved "https://registry.yarnpkg.com/web3-types/-/web3-types-1.3.1.tgz#cf6148ad46b68c5c89714613380b270d31e297be" - integrity sha512-8fXi7h/t95VKRtgU4sxprLPZpsTh3jYDfSghshIDBgUD/OoGe5S+syP24SUzBZYllZ/L+hMr2gdp/0bGJa8pYQ== - -web3-types@^1.6.0: - version "1.6.0" - resolved "https://registry.yarnpkg.com/web3-types/-/web3-types-1.6.0.tgz#ebe7f140c31f7cc0ad15f238ad7e7ac72797ff3b" - integrity sha512-qgOtADqlD5hw+KPKBUGaXAcdNLL0oh6qTeVgXwewCfbL/lG9R+/GrgMQB1gbTJ3cit8hMwtH8KX2Em6OwO0HRw== - -web3-utils@^4.0.7: - version "4.0.7" - resolved "https://registry.yarnpkg.com/web3-utils/-/web3-utils-4.0.7.tgz#7df497b7cdd06cdfe7d02036c45fecbe3382d137" - integrity sha512-sy8S6C2FIa5NNHc8DjND+Fx3S8KDAizuh5RbL1RX3h0PRbFgPfWzF5RfUns8gTt0mjJuOhs/IaDhrZfeTszG5A== +web3-rpc-providers@^1.0.0-rc.0: + version "1.0.0-rc.0" + resolved "https://registry.yarnpkg.com/web3-rpc-providers/-/web3-rpc-providers-1.0.0-rc.0.tgz#fa5f4aac5e15707b50d6b72a10c087a58ea2e281" + integrity sha512-lmBOZ4PE+wf5JyptPZJ+GHeGPyTBfnCRbrfOxWLU+Q7g+D6NukgS3fk2xcunEvUsR/b5fp+uXk0TkmhX9/GCKg== dependencies: - ethereum-cryptography "^2.0.0" - web3-errors "^1.1.3" - web3-types "^1.3.0" - web3-validator "^2.0.3" + web3-providers-http "^4.1.0" + web3-providers-ws "^4.0.7" + web3-types "^1.7.0" + web3-utils "^4.3.0" -web3-utils@^4.2.3, web3-utils@^4.3.0: +web3-types@^1.3.0, web3-types@^1.6.0, web3-types@^1.7.0: + version "1.7.0" + resolved "https://registry.yarnpkg.com/web3-types/-/web3-types-1.7.0.tgz#9945fa644af96b20b1db18564aff9ab8db00df59" + integrity sha512-nhXxDJ7a5FesRw9UG5SZdP/C/3Q2EzHGnB39hkAV+YGXDMgwxBXFWebQLfEzZzuArfHnvC0sQqkIHNwSKcVjdA== + +web3-utils@^4.0.7, web3-utils@^4.2.3, web3-utils@^4.3.0: version "4.3.0" resolved "https://registry.yarnpkg.com/web3-utils/-/web3-utils-4.3.0.tgz#c18918f0d692f745d622d22172406f6102528860" integrity sha512-fGG2IZr0XB1vEoWZiyJzoy28HpsIfZgz4mgPeQA9aj5rIx8z0o80qUPtIyrCYX/Bo2gYALlV5SWIJWxJNUQn9Q== @@ -6010,18 +5923,7 @@ web3-utils@^4.2.3, web3-utils@^4.3.0: web3-types "^1.6.0" web3-validator "^2.0.6" -web3-validator@^2.0.3: - version "2.0.3" - resolved "https://registry.yarnpkg.com/web3-validator/-/web3-validator-2.0.3.tgz#e5dcd4b4902612cff21b7f8817dd233393999d97" - integrity sha512-fJbAQh+9LSNWy+l5Ze6HABreml8fra98o5+vS073T35jUcLbRZ0IOjF/ZPJhJNbJDt+jP1vseZsc3z3uX9mxxQ== - dependencies: - ethereum-cryptography "^2.0.0" - util "^0.12.5" - web3-errors "^1.1.3" - web3-types "^1.3.0" - zod "^3.21.4" - -web3-validator@^2.0.5, web3-validator@^2.0.6: +web3-validator@^2.0.3, web3-validator@^2.0.5, web3-validator@^2.0.6: version "2.0.6" resolved "https://registry.yarnpkg.com/web3-validator/-/web3-validator-2.0.6.tgz#a0cdaa39e1d1708ece5fae155b034e29d6a19248" integrity sha512-qn9id0/l1bWmvH4XfnG/JtGKKwut2Vokl6YXP5Kfg424npysmtRLe9DgiNBM9Op7QL/aSiaA0TVXibuIuWcizg== @@ -6032,25 +5934,26 @@ web3-validator@^2.0.5, web3-validator@^2.0.6: web3-types "^1.6.0" zod "^3.21.4" -web3@^4.9.0: - version "4.9.0" - resolved "https://registry.yarnpkg.com/web3/-/web3-4.9.0.tgz#2d18f6a48f366601aef32308260542ed14bd2b1b" - integrity sha512-O0R90ijjyqUlG1Wk3SXqfYMU1ZGJvLCAF/WfSg/isDz/0Fkpqxoj893wauZ+ieRvTXITlbQHVXGfpp8qrhWZ1g== +web3@^4.10.0: + version "4.10.0" + resolved "https://registry.yarnpkg.com/web3/-/web3-4.10.0.tgz#b20a9b0c510efacfc6f776630a0d1b4e87f41398" + integrity sha512-0A0SEZG4QL5DRtZQtF1pL+LldHn0kAAb4/vUdQua1k4CrZ+hRoDAc3dfFZw94EOV69oXuAFo8fqhITxyWfCHaw== dependencies: - web3-core "^4.4.0" + web3-core "^4.5.0" web3-errors "^1.2.0" - web3-eth "^4.7.0" + web3-eth "^4.8.0" web3-eth-abi "^4.2.2" web3-eth-accounts "^4.1.2" web3-eth-contract "^4.5.0" - web3-eth-ens "^4.3.0" + web3-eth-ens "^4.4.0" web3-eth-iban "^4.0.7" web3-eth-personal "^4.0.8" web3-net "^4.1.0" web3-providers-http "^4.1.0" web3-providers-ws "^4.0.7" web3-rpc-methods "^1.3.0" - web3-types "^1.6.0" + web3-rpc-providers "^1.0.0-rc.0" + web3-types "^1.7.0" web3-utils "^4.3.0" web3-validator "^2.0.6" From 7d5bd657113a385dc5fcb8751ff86bc5639e8cb0 Mon Sep 17 00:00:00 2001 From: Oleksii Kosynskyi Date: Mon, 17 Jun 2024 21:51:39 -0400 Subject: [PATCH 25/30] test --- test/integration/rpc.test.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/test/integration/rpc.test.ts b/test/integration/rpc.test.ts index 38685fa..d370053 100644 --- a/test/integration/rpc.test.ts +++ b/test/integration/rpc.test.ts @@ -7,6 +7,7 @@ import { getTransactionDetailsData, } from '../fixtures'; + describe('ZkSyncPlugin rpc tests', () => { let web3: Web3; From 23288405e092ed0a8314c050fbbfa728855a1d0a Mon Sep 17 00:00:00 2001 From: Oleksii Kosynskyi Date: Mon, 17 Jun 2024 21:53:12 -0400 Subject: [PATCH 26/30] fix tests --- test/integration/utils.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/integration/utils.test.ts b/test/integration/utils.test.ts index 1c18066..c00fa01 100644 --- a/test/integration/utils.test.ts +++ b/test/integration/utils.test.ts @@ -1,5 +1,5 @@ import * as web3Accounts from 'web3-eth-accounts'; -import { Web3 } from '../../../web3.js/packages/web3'; +import { Web3 } from 'web3'; import * as constants from '../../src/constants'; import * as utils from '../../src/utils'; From 167f04ef2704f9dcf630ca845b34b46145579c52 Mon Sep 17 00:00:00 2001 From: Oleksii Kosynskyi Date: Mon, 17 Jun 2024 21:55:19 -0400 Subject: [PATCH 27/30] test --- .github/workflows/build.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 31ca21f..5df3950 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -7,7 +7,7 @@ jobs: test: strategy: matrix: - node: [18, 20] + node: [20] name: Install and test runs-on: ubuntu-latest steps: @@ -15,6 +15,6 @@ jobs: - uses: actions/setup-node@v4 with: node-version: ${{ matrix.node }} - cache: yarn +# cache: yarn - run: yarn install - run: yarn test From c5c4098a50c7d3b0e32d4dec31778189bb63d906 Mon Sep 17 00:00:00 2001 From: Oleksii Kosynskyi Date: Mon, 17 Jun 2024 21:57:03 -0400 Subject: [PATCH 28/30] fix tests --- .github/workflows/build.yml | 4 ++-- src/plugin.ts | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 5df3950..31ca21f 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -7,7 +7,7 @@ jobs: test: strategy: matrix: - node: [20] + node: [18, 20] name: Install and test runs-on: ubuntu-latest steps: @@ -15,6 +15,6 @@ jobs: - uses: actions/setup-node@v4 with: node-version: ${{ matrix.node }} -# cache: yarn + cache: yarn - run: yarn install - run: yarn test diff --git a/src/plugin.ts b/src/plugin.ts index 9041550..774ad1e 100644 --- a/src/plugin.ts +++ b/src/plugin.ts @@ -3,7 +3,7 @@ import type { Address } from 'web3-types'; import { Contract } from 'web3-eth-contract'; import { Web3PluginBase } from 'web3-core'; -import { TransactionFactory } from '../../web3.js/packages/web3-eth-accounts'; +import { TransactionFactory } from 'web3-eth-accounts'; import { IERC20ABI } from './contracts/IERC20'; import { RpcMethods } from './rpc.methods'; import { EIP712_TX_TYPE, ETH_ADDRESS, ZERO_ADDRESS } from './constants'; From 51e36d25d0ab6bbe505a871181080169eb8a722d Mon Sep 17 00:00:00 2001 From: Oleksii Kosynskyi Date: Mon, 17 Jun 2024 21:58:32 -0400 Subject: [PATCH 29/30] fix to test network --- test/integration/utils.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/integration/utils.test.ts b/test/integration/utils.test.ts index c00fa01..a42dfb7 100644 --- a/test/integration/utils.test.ts +++ b/test/integration/utils.test.ts @@ -21,7 +21,7 @@ describe('utils', () => { 270, ); const signature = eip712Signer.sign(tx) as utils.SignatureObject; - const web3 = new Web3('http://localhost:8545'); + const web3 = new Web3('https://sepolia.era.zksync.dev'); expect(signature.serialized).toBe( '0x5ea12f3d54a1624d7e7f5161dbf6ab746c3335e643b2966264e740cf8e10e9b64b0251fb79d9a5b11730387085a0d58f105926f72e20242ecb274639991939ca1b', ); From 12db0dadf3d86111a8a620bdd5480d3e9323d3a3 Mon Sep 17 00:00:00 2001 From: Muhammad Altabba <24407834+Muhammad-Altabba@users.noreply.github.com> Date: Wed, 10 Jul 2024 02:24:33 +0200 Subject: [PATCH 30/30] [DRAFT] MS2 (#11) * add the draft for `Web3ZkSync` * add a very early draft for `ZKSyncWallet` * apply lint:fx * add very early draft for AdapterL1 and AdapterL2 * fix * Adapters, zk l2, zk l1 * add 1 unit test for Web3ZkSyncL2 * fix build * fix a test * added test file and a method with a TODO * Fix tests. implement Wallet * tiny fix and apply lint:fix * deposit + withdraw * add wallet methods * few code fixes to wallet2.test.ts and apply lint:fix * fix build * apply lint:fix * populate transaction, sign, send refactor * fix withdraw and deposit * fix tests * apply lint:fix * add gasLimit * fixes * fixes and tests * update fixtures * fix wallet. tests * feat: enhance plugin context (#20) * enhance plugin context * enhance plugin contracts init * enhance plugin contracts init and add a test * ensure the correct provider used inside the plugin * revert a draft change * tiny to fix provider usage at plugin * link to a web3.js issue * pre deploy state * fix tests * fix * add env * test * secret to env * test * test * use envs * test * test * test * test var * test * test * fix * move web3 to dependencies * fix web3 version compatibility * apply yarn lint:fix * fix version * v1.0.0-alpha.0 * ignore file --------- Co-authored-by: Oleksii Kosynskyi --- .github/workflows/build.yml | 5 +- .npmignore | 1 + package.json | 96 +- src/Eip712.ts | 70 +- src/adapters.ts | 1932 +++++++++++++++++++ src/contracts/IERC20.ts | 241 ++- src/index.ts | 3 + src/paymaster-utils.ts | 81 + src/plugin.ts | 317 ++- src/rpc.methods.ts | 179 +- src/schemas.ts | 43 + src/types.ts | 56 +- src/utils.ts | 240 ++- src/web3zksync-l1.ts | 6 + src/web3zksync-l2.ts | 397 ++++ src/web3zksync.ts | 630 ++++++ src/zksync-wallet.ts | 219 +++ test/fixtures.ts | 3 + test/integration/_wallet.test.ts | 52 + test/integration/mainnet.test.ts | 4 +- test/integration/rpc.mainnet.test.ts | 14 +- test/integration/rpc.test.ts | 18 +- test/integration/wallet.test.ts | 1777 +++++++++++++++++ test/integration/zksync.contracts.test.ts | 40 + test/jest.config.js | 4 +- test/unit/index.test.ts | 4 +- test/unit/signer.test.ts | 75 + test/unit/utils.test.ts | 137 +- test/unit/web3zksync-l2.as.provider.test.ts | 59 + test/utils.ts | 23 +- yarn.lock | 382 ++-- 31 files changed, 6465 insertions(+), 643 deletions(-) create mode 100644 src/adapters.ts create mode 100644 src/paymaster-utils.ts create mode 100644 src/web3zksync-l1.ts create mode 100644 src/web3zksync-l2.ts create mode 100644 src/web3zksync.ts create mode 100644 src/zksync-wallet.ts create mode 100644 test/integration/_wallet.test.ts create mode 100644 test/integration/wallet.test.ts create mode 100644 test/integration/zksync.contracts.test.ts create mode 100644 test/unit/signer.test.ts create mode 100644 test/unit/web3zksync-l2.as.provider.test.ts diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 31ca21f..3bcf60d 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -7,7 +7,7 @@ jobs: test: strategy: matrix: - node: [18, 20] + node: [20] name: Install and test runs-on: ubuntu-latest steps: @@ -17,4 +17,5 @@ jobs: node-version: ${{ matrix.node }} cache: yarn - run: yarn install - - run: yarn test + - run: yarn build + - run: export PRIVATE_KEY=${{secrets.PRIVATE_KEY}} && yarn test diff --git a/.npmignore b/.npmignore index 44d5996..ea16e65 100644 --- a/.npmignore +++ b/.npmignore @@ -9,6 +9,7 @@ .prettierrc.json .prettierignore *.log +.idea coverage/ benchmark/ diff --git a/package.json b/package.json index ef67ea6..810f758 100644 --- a/package.json +++ b/package.json @@ -1,50 +1,50 @@ { - "name": "web3-plugin-zksync", - "version": "0.1.5", - "description": "web3.js plugin for ZkSync", - "main": "lib/index.js", - "types": "lib/index.d.ts", - "homepage": "https://github.com/web3/web3-plugin-zksync#readme", - "bugs": { - "url": "https://github.com/web3/web3-plugin-zksync/issues" - }, - "scripts": { - "lint": "eslint '{src,test}/**/*.ts'", - "lint:fix": "eslint '{src,test}/**/*.ts' --fix", - "build": "tsc --project tsconfig.build.json", - "test": "jest --config=./test/jest.config.js" - }, - "contributors": [ - "ChainSafe " - ], - "license": "MIT", - "repository": { - "type": "git", - "url": "git+ssh://git@github.com/web3/web3-plugin-zksync.git" - }, - "dependencies": { - "ethereum-cryptography": "^2.1.3", - "hardhat": "^2.19.4", - "web3-core": "^4.5.0", - "web3-eth-abi": "^4.2.2", - "web3-eth-accounts": "^4.1.2", - "web3-eth-contract": "^4.5.0", - "web3-types": "^1.7.0", - "web3-utils": "^4.3.0" - }, - "devDependencies": { - "@chainsafe/eslint-config": "^2.1.1", - "@types/jest": "^29.5.11", - "@types/node": "^20.11.10", - "eslint": "8.56.0", - "jest": "^29.7.0", - "jest-extended": "^4.0.2", - "ts-jest": "^29.1.2", - "ts-node": "^10.9.2", - "typescript": "^5.3.3", - "web3": "^4.10.0" - }, - "peerDependencies": { - "web3": ">= 4.0.3" - } + "name": "web3-plugin-zksync", + "version": "1.0.0-alpha.0", + "description": "web3.js plugin for ZkSync", + "main": "lib/index.js", + "types": "lib/index.d.ts", + "homepage": "https://github.com/web3/web3-plugin-zksync#readme", + "bugs": { + "url": "https://github.com/web3/web3-plugin-zksync/issues" + }, + "scripts": { + "lint": "eslint '{src,test}/**/*.ts'", + "lint:fix": "eslint '{src,test}/**/*.ts' --fix", + "build": "tsc --project tsconfig.build.json", + "test": "jest --config=./test/jest.config.js" + }, + "contributors": [ + "ChainSafe " + ], + "license": "MIT", + "repository": { + "type": "git", + "url": "git+ssh://git@github.com/web3/web3-plugin-zksync.git" + }, + "dependencies": { + "ethereum-cryptography": "^2.1.3", + "hardhat": "^2.19.4", + "web3": "4.10.1-dev.1436228.0", + "web3-core": "4.5.1-dev.1436228.0", + "web3-eth-abi": "^4.2.2", + "web3-eth-accounts": "^4.1.2", + "web3-eth-contract": "4.5.1-dev.1436228.0", + "web3-types": "1.7.1-dev.1436228.0", + "web3-utils": "4.3.1-dev.1436228.0" + }, + "devDependencies": { + "@chainsafe/eslint-config": "^2.1.1", + "@types/jest": "^29.5.11", + "@types/node": "^20.11.10", + "eslint": "8.56.0", + "jest": "^29.7.0", + "jest-extended": "^4.0.2", + "ts-jest": "^29.1.2", + "ts-node": "^10.9.2", + "typescript": "^5.3.3" + }, + "peerDependencies": { + "web3": ">= 4.0.3" + } } diff --git a/src/Eip712.ts b/src/Eip712.ts index 8fa13ba..94070a5 100644 --- a/src/Eip712.ts +++ b/src/Eip712.ts @@ -3,7 +3,7 @@ import type { Bytes, Eip712TypedData, Numbers } from 'web3-types'; import * as web3Abi from 'web3-eth-abi'; import * as web3Utils from 'web3-utils'; import type * as web3Accounts from 'web3-eth-accounts'; -import { BaseTransaction, toUint8Array } from 'web3-eth-accounts'; +import { BaseTransaction, bigIntToUint8Array, toUint8Array } from 'web3-eth-accounts'; import { RLP } from '@ethereumjs/rlp'; import type { Address } from 'web3'; import { @@ -62,27 +62,37 @@ function arrayToPaymasterParams(arr: Uint8Array): PaymasterParams | undefined { export class EIP712 { static getSignInput(transaction: Eip712TxData): Eip712SignedInput { - const maxFeePerGas = toHex(transaction.maxFeePerGas || transaction.gasPrice || 0n); - const maxPriorityFeePerGas = toHex(transaction.maxPriorityFeePerGas || maxFeePerGas); - const gasPerPubdataByteLimit = toHex( - transaction.customData?.gasPerPubdata || DEFAULT_GAS_PER_PUBDATA_LIMIT, - ); + const maxFeePerGas = toBigInt(transaction.maxFeePerGas || transaction.gasPrice || 0n); + const maxPriorityFeePerGas = toBigInt(transaction.maxPriorityFeePerGas || maxFeePerGas); + const gasPerPubdataByteLimit = + transaction.customData?.gasPerPubdata || DEFAULT_GAS_PER_PUBDATA_LIMIT; return { txType: transaction.type || EIP712_TX_TYPE, - from: transaction.from ? toHex(transaction.from) : undefined, - to: transaction.to ? toHex(transaction.to) : undefined, - gasLimit: transaction.gasLimit ? toBigInt(transaction.gasLimit) : 0, + from: transaction.from + ? typeof transaction.from === 'string' + ? transaction.from + : toHex(transaction.from) + : undefined, + to: transaction.to + ? typeof transaction.to === 'string' + ? transaction.to + : toHex(transaction.to) + : undefined, + gasLimit: transaction.gasLimit ? toBigInt(transaction.gasLimit) : 0n, gasPerPubdataByteLimit: gasPerPubdataByteLimit, - customData: transaction.customData, maxFeePerGas, maxPriorityFeePerGas, paymaster: transaction.customData?.paymasterParams?.paymaster || ZERO_ADDRESS, nonce: transaction.nonce ? toBigInt(transaction.nonce) : 0, - value: transaction.value ? toHex(transaction.value) : '0x0', + value: transaction.value ? toBigInt(transaction.value) : 0n, data: transaction.data ? toHex(transaction.data) : '0x', factoryDeps: transaction.customData?.factoryDeps?.map((dep: Bytes) => hashBytecode(dep)) || [], paymasterInput: transaction.customData?.paymasterParams?.paymasterInput || '0x', + customData: + transaction.customData && Object.keys(transaction.customData).length > 0 + ? transaction.customData + : undefined, }; } @@ -241,14 +251,17 @@ export class EIP712 { const maxFeePerGas = toHex(transaction.maxFeePerGas || transaction.gasPrice || 0); const maxPriorityFeePerGas = toHex(transaction.maxPriorityFeePerGas || maxFeePerGas); - const nonce = toHex(transaction.nonce || 0); + let gasLimitBytes = new Uint8Array(); + if (transaction.gasLimit && toHex(transaction.gasLimit) !== '0x0') { + gasLimitBytes = toBytes(transaction.gasLimit); + } + + const nonce = toBigInt(transaction.nonce || 0); const fields: Array = [ - nonce === '0x0' ? new Uint8Array() : toBytes(nonce), + nonce === 0n ? new Uint8Array() : bigIntToUint8Array(nonce), maxPriorityFeePerGas === '0x0' ? new Uint8Array() : toBytes(maxPriorityFeePerGas), maxFeePerGas === '0x0' ? new Uint8Array() : toBytes(maxFeePerGas), - toHex(transaction.gasLimit || 0) === '0x0' - ? new Uint8Array() - : toBytes(transaction.gasLimit!), + gasLimitBytes, transaction.to ? web3Utils.toChecksumAddress(toHex(transaction.to)) : '0x', toHex(transaction.value || 0) === '0x0' ? new Uint8Array() @@ -322,6 +335,31 @@ export class EIP712Signer { return new EIP712Transaction(tx).sign(toBytes(this.web3Account.privateKey)).getSignature(); } + /** + * Hashes the transaction request using EIP712. + * + * @param transaction The transaction request that needs to be hashed. + * @returns A hash (digest) of the transaction request. + * + * @throws {Error} If `transaction.chainId` is not set. + */ + static getSignedDigest(transaction: Eip712TxData): Bytes { + if (!transaction.chainId) { + throw Error("Transaction chainId isn't set!"); + } + + return EIP712.txHash(transaction); + + // const domain = { + // name: 'zkSync', + // version: '2', + // chainId: transaction.chainId, + // }; + // TODO: Implement replacement of the following line + // @ts-ignore + // return ethers.TypedDataEncoder.hash(domain, EIP712_TYPES, EIP712.getSignInput(transaction)); + } + /** * Returns zkSync Era EIP712 domain. */ diff --git a/src/adapters.ts b/src/adapters.ts new file mode 100644 index 0000000..2b153a3 --- /dev/null +++ b/src/adapters.ts @@ -0,0 +1,1932 @@ +import type * as web3Types from 'web3-types'; +import * as web3Utils from 'web3-utils'; +import * as Web3EthAbi from 'web3-eth-abi'; +import { DEFAULT_RETURN_FORMAT } from 'web3'; +import * as Web3 from 'web3'; +import type { PayableMethodObject, PayableTxOptions } from 'web3-eth-contract'; +import { toBigInt, toHex, toNumber } from 'web3-utils'; +import type { Transaction, TransactionHash, TransactionReceipt } from 'web3-types'; +import type { Web3ZkSyncL2 } from './web3zksync-l2'; + +import type { EIP712Signer } from './utils'; +import { + getPriorityOpResponse, + checkBaseCost, + estimateCustomBridgeDepositL2Gas, + estimateDefaultBridgeDepositL2Gas, + getERC20DefaultBridgeData, + isETH, + layer1TxDefaults, + scaleGasLimit, + undoL1ToL2Alias, + isAddressEq, + id, + dataSlice, + toBytes, +} from './utils'; + +import { + BOOTLOADER_FORMAL_ADDRESS, + L1_MESSENGER_ADDRESS, + L1_RECOMMENDED_MIN_ERC20_DEPOSIT_GAS_LIMIT, + L1_RECOMMENDED_MIN_ETH_DEPOSIT_GAS_LIMIT, + REQUIRED_L1_TO_L2_GAS_PER_PUBDATA_LIMIT, + NONCE_HOLDER_ADDRESS, + ETH_ADDRESS_IN_CONTRACTS, + LEGACY_ETH_ADDRESS, + EIP712_TX_TYPE, +} from './constants'; +import type { + Address, + FinalizeWithdrawalParams, + FullDepositFee, + TransactionOverrides, + PaymasterParams, + PriorityOpResponse, + WalletBalances, + Eip712TxData, +} from './types'; +import { ZeroAddress, ZeroHash } from './types'; +import { IZkSyncABI } from './contracts/IZkSyncStateTransition'; +import { IBridgehubABI } from './contracts/IBridgehub'; +import { IERC20ABI } from './contracts/IERC20'; +import { IL1BridgeABI } from './contracts/IL1Bridge'; +import { Abi as IL1SharedBridgeABI } from './contracts/IL1SharedBridge'; +import { IL2BridgeABI } from './contracts/IL2Bridge'; +import { INonceHolderABI } from './contracts/INonceHolder'; +import type { Web3ZkSyncL1 } from './web3zksync-l1'; + +interface TxSender { + getAddress(): Promise
; +} + +export class AdapterL1 implements TxSender { + /** + * Returns a provider instance for connecting to an L2 network. + */ + protected _contextL2(): Web3ZkSyncL2 { + throw new Error('Must be implemented by the derived class!'); + } + + /** + * Returns a context (provider + Signer) instance for connecting to a L1 network. + */ + protected _contextL1(): Web3ZkSyncL1 { + throw new Error('Must be implemented by the derived class!'); + } + + /** + * Returns `Contract` wrapper of the zkSync Era smart contract. + */ + async getMainContract( + returnFormat: web3Types.DataFormat = DEFAULT_RETURN_FORMAT, + ): Promise> { + const address = await this._contextL2().getMainContract(returnFormat); + const contract = new Web3.Contract(IZkSyncABI, address, returnFormat); + contract.setProvider(this._contextL2().provider); + return contract; + } + + /** + * Returns `Contract` wrapper of the Bridgehub smart contract. + */ + async getBridgehubContract( + returnFormat: web3Types.DataFormat = DEFAULT_RETURN_FORMAT, + ): Promise> { + const address = await this._contextL2().getBridgehubContractAddress(); + return new (this._contextL1().eth.Contract)(IBridgehubABI, address, returnFormat); + } + + /** + * Returns L1 bridge contracts. + * + * @remarks There is no separate Ether bridge contract, {@link getBridgehubContractAddress Bridgehub} is used instead. + */ + async getL1BridgeContracts( + returnFormat: web3Types.DataFormat = DEFAULT_RETURN_FORMAT, + ): Promise<{ + erc20: Web3.Contract; + weth: Web3.Contract; + shared: Web3.Contract; + }> { + const addresses = await this._contextL2().getDefaultBridgeAddresses(); + const erc20 = new (this._contextL1().eth.Contract)( + IERC20ABI, + addresses.erc20L1, + returnFormat, + ); + const weth = new (this._contextL1().eth.Contract)( + IERC20ABI, + addresses.wethL1, + returnFormat, + ); + const shared = new (this._contextL1().eth.Contract)( + IL1SharedBridgeABI, + addresses.sharedL1, + returnFormat, + ); + + return { + erc20, + weth, + shared, + }; + } + + /** + * Returns the address of the base token on L1. + */ + async getBaseToken(): Promise
{ + const bridgehub = await this.getBridgehubContract(); + const chainId = await this._contextL2().eth.getChainId(); + return bridgehub.methods.baseToken(chainId).call(); + } + + /** + * Returns whether the chain is ETH-based. + */ + async isETHBasedChain(): Promise { + return this._contextL2().isEthBasedChain(); + } + + /** + * Returns the amount of the token held by the account on the L1 network. + * + * @param [token] The address of the token. Defaults to ETH if not provided. + * @param [blockTag] The block in which the balance should be checked. + * Defaults to 'committed', i.e., the latest processed block. + */ + async getBalanceL1(token?: Address, blockTag?: web3Types.BlockNumberOrTag): Promise { + token ??= LEGACY_ETH_ADDRESS; + if (isETH(token)) { + return await this._contextL1().eth.getBalance(this.getAddress(), blockTag); + } else { + const erc20 = new (this._contextL1().eth.Contract)(IERC20ABI, token); + + return await erc20.methods.balanceOf(this.getAddress()).call(); + } + } + + /** + * Returns the amount of approved tokens for a specific L1 bridge. + * + * @param token The Ethereum address of the token. + * @param [bridgeAddress] The address of the bridge contract to be used. + * Defaults to the default zkSync Era bridge, either `L1EthBridge` or `L1Erc20Bridge`. + * @param [blockTag] The block in which an allowance should be checked. + * Defaults to 'committed', i.e., the latest processed block. + */ + async getAllowanceL1( + token: Address, + bridgeAddress?: Address, + blockTag?: web3Types.BlockNumberOrTag, + ): Promise { + if (!bridgeAddress) { + const bridgeContracts = await this.getL1BridgeContracts(); + bridgeAddress = bridgeContracts.shared.options.address; + } + + const erc20 = new (this._contextL1().eth.Contract)(IERC20ABI, token); + + return erc20.methods + .allowance(this.getAddress(), bridgeAddress, { + blockTag, + }) + .call(); + } + + /** + * Returns the L2 token address equivalent for a L1 token address as they are not necessarily equal. + * The ETH address is set to the zero address. + * + * @remarks Only works for tokens bridged on default zkSync Era bridges. + * + * @param token The address of the token on L1. + */ + async l2TokenAddress(token: Address): Promise { + return this._contextL2().l2TokenAddress(token); + } + + /** + * Bridging ERC20 tokens from L1 requires approving the tokens to the zkSync Era smart contract. + * + * @param token The L1 address of the token. + * @param amount The amount of the token to be approved. + * @param [overrides] Transaction's overrides which may be used to pass L1 `gasLimit`, `gasPrice`, `value`, etc. + * @returns A promise that resolves to the response of the approval transaction. + * @throws {Error} If attempting to approve an ETH token. + */ + async approveERC20( + token: Address, + amount: web3Types.Numbers, + overrides?: TransactionOverrides & { bridgeAddress?: Address }, + ) { + if (isETH(token)) { + throw new Error( + "ETH token can't be approved! The address of the token does not exist on L1.", + ); + } + + overrides ??= {}; + let bridgeAddress = overrides.bridgeAddress; + + const erc20 = new (this._contextL1().eth.Contract)(IERC20ABI, token); + + if (!bridgeAddress) { + bridgeAddress = (await this.getL1BridgeContracts()).shared.options.address; + } else { + delete overrides.bridgeAddress; + } + + return erc20.methods.approve(bridgeAddress, amount, overrides).send({ + from: this.getAddress(), + }); + } + + /** + * Returns the base cost for an L2 transaction. + * + * @param params The parameters for calculating the base cost. + * @param params.gasLimit The gasLimit for the L2 contract call. + * @param [params.gasPerPubdataByte] The L2 gas price for each published L1 calldata byte. + * @param [params.gasPrice] The L1 gas price of the L1 transaction that will send the request for an execute call. + */ + async getBaseCost(params: { + gasLimit: web3Types.Numbers; + gasPerPubdataByte?: web3Types.Numbers; + gasPrice?: web3Types.Numbers; + chainId?: web3Types.Numbers; + }): Promise { + const bridgehub = await this.getBridgehubContract(); + const parameters = { ...layer1TxDefaults(), ...params }; + parameters.gasPrice ??= (await this._contextL1().eth.calculateFeeData()).gasPrice!; + parameters.gasPerPubdataByte ??= REQUIRED_L1_TO_L2_GAS_PER_PUBDATA_LIMIT; + + return await bridgehub.methods + .l2TransactionBaseCost( + parameters.chainId ?? (await this._contextL2().eth.getChainId()), + parameters.gasPrice, + parameters.gasLimit, + parameters.gasPerPubdataByte, + ) + .call(); + } + + /** + * Returns the parameters for the approval token transaction based on the deposit token and amount. + * Some deposit transactions require multiple approvals. Existing allowance for the bridge is not checked; + * allowance is calculated solely based on the specified amount. + * + * @param token The address of the token to deposit. + * @param amount The amount of the token to deposit. + */ + async getDepositAllowanceParams( + token: Address, + amount: web3Types.Numbers, + ): Promise<{ token: Address; allowance: web3Types.Numbers }[]> { + if (isAddressEq(token, LEGACY_ETH_ADDRESS)) { + token = ETH_ADDRESS_IN_CONTRACTS; + } + const baseTokenAddress = await this.getBaseToken(); + const isETHBasedChain = await this.isETHBasedChain(); + + if (isETHBasedChain && isAddressEq(token, ETH_ADDRESS_IN_CONTRACTS)) { + throw new Error( + "ETH token can't be approved! The address of the token does not exist on L1.", + ); + } else if (isAddressEq(baseTokenAddress, ETH_ADDRESS_IN_CONTRACTS)) { + return [{ token, allowance: amount }]; + } else if (isAddressEq(token, ETH_ADDRESS_IN_CONTRACTS)) { + return [ + { + token: baseTokenAddress, + allowance: (await this._getDepositETHOnNonETHBasedChainTx({ token, amount })) + .mintValue, + }, + ]; + } else if (isAddressEq(token, baseTokenAddress)) { + return [ + { + token: baseTokenAddress, + allowance: ( + await this._getDepositBaseTokenOnNonETHBasedChainTx({ + token, + amount, + }) + ).mintValue, + }, + ]; + } else { + // A deposit of a non-base token to a non-ETH-based chain requires two approvals. + return [ + { + token: baseTokenAddress, + allowance: ( + await this._getDepositNonBaseTokenToNonETHBasedChainTx({ + token, + amount, + }) + ).mintValue, + }, + { + token: token, + allowance: amount, + }, + ]; + } + } + + /** + * Transfers the specified token from the associated account on the L1 network to the target account on the L2 network. + * The token can be either ETH or any ERC20 token. For ERC20 tokens, enough approved tokens must be associated with + * the specified L1 bridge (default one or the one defined in `transaction.bridgeAddress`). + * In this case, depending on is the chain ETH-based or not `transaction.approveERC20` or `transaction.approveBaseERC20` + * can be enabled to perform token approval. If there are already enough approved tokens for the L1 bridge, + * token approval will be skipped. To check the amount of approved tokens for a specific bridge, + * use the {@link getAllowanceL1} method. + * + * @param transaction The transaction object containing deposit details. + * @param transaction.token The address of the token to deposit. ETH by default. + * @param transaction.amount The amount of the token to deposit. + * @param [transaction.to] The address that will receive the deposited tokens on L2. + * @param [transaction.operatorTip] (currently not used) If the ETH value passed with the transaction is not + * explicitly stated in the overrides, this field will be equal to the tip the operator will receive on top of + * the base cost of the transaction. + * @param [transaction.bridgeAddress] The address of the bridge contract to be used. + * Defaults to the default zkSync Era bridge (either `L1EthBridge` or `L1Erc20Bridge`). + * @param [transaction.approveERC20] Whether or not token approval should be performed under the hood. + * Set this flag to true if you bridge an ERC20 token and didn't call the {@link approveERC20} function beforehand. + * @param [transaction.approveBaseERC20] Whether or not base token approval should be performed under the hood. + * Set this flag to true if you bridge a base token and didn't call the {@link approveERC20} function beforehand. + * @param [transaction.l2GasLimit] Maximum amount of L2 gas that the transaction can consume during execution on L2. + * @param [transaction.gasPerPubdataByte] The L2 gas price for each published L1 calldata byte. + * @param [transaction.refundRecipient] The address on L2 that will receive the refund for the transaction. + * If the transaction fails, it will also be the address to receive `l2Value`. + * @param [transaction.overrides] Transaction's overrides for deposit which may be used to pass + * L1 `gasLimit`, `gasPrice`, `value`, etc. + * @param [transaction.approveOverrides] Transaction's overrides for approval of an ERC20 token which may be used + * to pass L1 `gasLimit`, `gasPrice`, `value`, etc. + * @param [transaction.approveBaseOverrides] Transaction's overrides for approval of a base token which may be used + * to pass L1 `gasLimit`, `gasPrice`, `value`, etc. + * @param [transaction.customBridgeData] Additional data that can be sent to a bridge. + */ + async deposit(transaction: { + token: Address; + amount: web3Types.Numbers; + to?: Address; + operatorTip?: web3Types.Numbers; + bridgeAddress?: Address; + approveERC20?: boolean; + approveBaseERC20?: boolean; + l2GasLimit?: web3Types.Numbers; + gasPerPubdataByte?: web3Types.Numbers; + refundRecipient?: Address; + overrides?: TransactionOverrides; + approveOverrides?: TransactionOverrides; + approveBaseOverrides?: TransactionOverrides; + customBridgeData?: web3Types.Bytes; + }): Promise { + if (isAddressEq(transaction.token, LEGACY_ETH_ADDRESS)) { + transaction.token = ETH_ADDRESS_IN_CONTRACTS; + } + const baseTokenAddress = await this.getBaseToken(); + + const isETHBasedChain = isAddressEq(baseTokenAddress, ETH_ADDRESS_IN_CONTRACTS); + + if (isETHBasedChain && isAddressEq(transaction.token, ETH_ADDRESS_IN_CONTRACTS)) { + return await this._depositETHToETHBasedChain(transaction); + } else if (isAddressEq(baseTokenAddress, ETH_ADDRESS_IN_CONTRACTS)) { + return await this._depositTokenToETHBasedChain(transaction); + } else if (isAddressEq(transaction.token, ETH_ADDRESS_IN_CONTRACTS)) { + return await this._depositETHToNonETHBasedChain(transaction); + } else if (isAddressEq(transaction.token, baseTokenAddress)) { + return await this._depositBaseTokenToNonETHBasedChain(transaction); + } else { + return await this._depositNonBaseTokenToNonETHBasedChain(transaction); + } + } + + async _depositNonBaseTokenToNonETHBasedChain(transaction: { + token: Address; + amount: web3Types.Numbers; + to?: Address; + operatorTip?: web3Types.Numbers; + bridgeAddress?: Address; + approveERC20?: boolean; + approveBaseERC20?: boolean; + l2GasLimit?: web3Types.Numbers; + gasPerPubdataByte?: web3Types.Numbers; + refundRecipient?: Address; + overrides?: TransactionOverrides; + approveOverrides?: TransactionOverrides; + approveBaseOverrides?: TransactionOverrides; + customBridgeData?: web3Types.Bytes; + }): Promise { + // Deposit a non-ETH and non-base token to a non-ETH-based chain. + // Go through the BridgeHub and obtain approval for both tokens. + const bridgehub = await this.getBridgehubContract(); + const chainId = await this._contextL2().eth.getChainId(); + const baseTokenAddress = await bridgehub.methods.baseToken(chainId).call(); + const bridgeContracts = await this.getL1BridgeContracts(); + const { tx, mintValue } = + await this._getDepositNonBaseTokenToNonETHBasedChainTx(transaction); + + if (transaction.approveBaseERC20) { + // Only request the allowance if the current one is not enough. + const allowance = await this.getAllowanceL1( + baseTokenAddress, + bridgeContracts.shared.options.address, + ); + if (allowance < mintValue) { + await this.approveERC20(baseTokenAddress, mintValue, { + bridgeAddress: bridgeContracts.shared.options.address, + ...transaction.approveBaseOverrides, + }); + } + } + + if (transaction.approveERC20) { + const bridgeAddress = transaction.bridgeAddress + ? transaction.bridgeAddress + : bridgeContracts.shared.options.address; + + // Only request the allowance if the current one is not enough. + const allowance = await this.getAllowanceL1(transaction.token, bridgeAddress); + if (allowance < BigInt(transaction.amount)) { + await this.approveERC20(transaction.token, transaction.amount, { + bridgeAddress, + ...transaction.approveOverrides, + }); + } + } + + const baseGasLimit = await tx.estimateGas(); + const gasLimit = scaleGasLimit(baseGasLimit); + + return this.signAndSend(tx.populateTransaction({ gasLimit } as PayableTxOptions)); + } + + async _depositBaseTokenToNonETHBasedChain(transaction: { + token: Address; + amount: web3Types.Numbers; + to?: Address; + operatorTip?: web3Types.Numbers; + bridgeAddress?: Address; + approveERC20?: boolean; + approveBaseERC20?: boolean; + l2GasLimit?: web3Types.Numbers; + gasPerPubdataByte?: web3Types.Numbers; + refundRecipient?: Address; + overrides?: TransactionOverrides; + approveOverrides?: TransactionOverrides; + approveBaseOverrides?: TransactionOverrides; + customBridgeData?: web3Types.Bytes; + }): Promise { + // Bridging the base token to a non-ETH-based chain. + // Go through the BridgeHub, and give approval. + const bridgehub = await this.getBridgehubContract(); + const chainId = await this._contextL2().eth.getChainId(); + const baseTokenAddress = await bridgehub.methods.baseToken(chainId).call(); + const sharedBridge = (await this.getL1BridgeContracts()).shared.options.address; + const { tx, mintValue } = await this._getDepositBaseTokenOnNonETHBasedChainTx(transaction); + + if (transaction.approveERC20 || transaction.approveBaseERC20) { + const approveOverrides = + transaction.approveBaseOverrides ?? transaction.approveOverrides!; + // Only request the allowance if the current one is not enough. + const allowance = await this.getAllowanceL1(baseTokenAddress, sharedBridge); + if (allowance < mintValue) { + await this.approveERC20(baseTokenAddress, mintValue, { + bridgeAddress: sharedBridge, + ...approveOverrides, + }); + } + } + const baseGasLimit = await this.estimateGasRequestExecute(tx); + const gasLimit = scaleGasLimit(baseGasLimit); + + tx.overrides ??= {}; + tx.overrides.gasLimit ??= gasLimit; + + return this.requestExecute(tx); + } + + async _depositETHToNonETHBasedChain(transaction: { + token: Address; + amount: web3Types.Numbers; + to?: Address; + operatorTip?: web3Types.Numbers; + bridgeAddress?: Address; + approveERC20?: boolean; + approveBaseERC20?: boolean; + l2GasLimit?: web3Types.Numbers; + gasPerPubdataByte?: web3Types.Numbers; + refundRecipient?: Address; + overrides?: TransactionOverrides; + approveOverrides?: TransactionOverrides; + approveBaseOverrides?: TransactionOverrides; + customBridgeData?: web3Types.Bytes; + }): Promise { + // Depositing ETH into a non-ETH-based chain. + // Use requestL2TransactionTwoBridges, secondBridge is the wETH bridge. + const bridgehub = await this.getBridgehubContract(); + const chainId = await this._contextL2().eth.getChainId(); + const baseTokenAddress = await bridgehub.methods.baseToken(chainId).call(); + const sharedBridge = (await this.getL1BridgeContracts()).shared.options.address; + const { tx, overrides, mintValue } = + await this._getDepositETHOnNonETHBasedChainTx(transaction); + + if (transaction.approveBaseERC20) { + // Only request the allowance if the current one is not enough. + const allowance = await this.getAllowanceL1(baseTokenAddress, sharedBridge); + if (allowance < mintValue) { + await this.approveERC20(baseTokenAddress, mintValue, { + bridgeAddress: sharedBridge, + ...transaction.approveBaseOverrides, + }); + } + } + + const baseGasLimit = await tx.estimateGas({ + value: overrides.value ? web3Utils.toHex(overrides.value) : undefined, + }); + const gasLimit = scaleGasLimit(baseGasLimit); + + overrides.gasLimit ??= gasLimit; + + return this.signAndSend(tx.populateTransaction(overrides as PayableTxOptions)); + } + + async _depositTokenToETHBasedChain(transaction: { + token: Address; + amount: web3Types.Numbers; + to?: Address; + operatorTip?: web3Types.Numbers; + bridgeAddress?: Address; + approveERC20?: boolean; + approveBaseERC20?: boolean; + l2GasLimit?: web3Types.Numbers; + gasPerPubdataByte?: web3Types.Numbers; + refundRecipient?: Address; + overrides?: TransactionOverrides; + approveOverrides?: TransactionOverrides; + approveBaseOverrides?: TransactionOverrides; + customBridgeData?: web3Types.Bytes; + }): Promise { + const bridgeContracts = await this.getL1BridgeContracts(); + const { tx, overrides } = await this._getDepositTokenOnETHBasedChainTx(transaction); + + if (transaction.approveERC20) { + const proposedBridge = bridgeContracts.shared.options.address; + const bridgeAddress = transaction.bridgeAddress + ? transaction.bridgeAddress + : proposedBridge; + + // Only request the allowance if the current one is not enough. + const allowance = await this.getAllowanceL1(transaction.token, bridgeAddress); + if (allowance < BigInt(transaction.amount)) { + await this.approveERC20(transaction.token, transaction.amount, { + bridgeAddress, + ...transaction.approveOverrides, + }); + } + } + + const baseGasLimit = await tx.estimateGas(overrides as PayableTxOptions); + const gasLimit = scaleGasLimit(baseGasLimit); + + overrides.gasLimit ??= gasLimit; + + return this.signAndSend(tx.populateTransaction(overrides as PayableTxOptions)); + } + + async _depositETHToETHBasedChain(transaction: { + token: Address; + amount: web3Types.Numbers; + to?: Address; + operatorTip?: web3Types.Numbers; + bridgeAddress?: Address; + approveERC20?: boolean; + approveBaseERC20?: boolean; + l2GasLimit?: web3Types.Numbers; + gasPerPubdataByte?: web3Types.Numbers; + refundRecipient?: Address; + overrides?: TransactionOverrides; + approveOverrides?: TransactionOverrides; + approveBaseOverrides?: TransactionOverrides; + customBridgeData?: web3Types.Bytes; + }): Promise { + const tx = await this._getDepositETHOnETHBasedChainTx(transaction); + const baseGasLimit = await this.estimateGasRequestExecute(tx); + const gasLimit = scaleGasLimit(baseGasLimit); + + tx.overrides ??= {}; + tx.overrides.gasLimit ??= gasLimit; + + return this.requestExecute(tx); + } + + /** + * Estimates the amount of gas required for a deposit transaction on the L1 network. + * Gas for approving ERC20 tokens is not included in the estimation. + * + * In order for estimation to work, enough token allowance is required in the following cases: + * - Depositing ERC20 tokens on an ETH-based chain. + * - Depositing any token (including ETH) on a non-ETH-based chain. + * + * @param transaction The transaction details. + * @param transaction.token The address of the token to deposit. ETH by default. + * @param transaction.amount The amount of the token to deposit. + * @param [transaction.to] The address that will receive the deposited tokens on L2. + * @param [transaction.operatorTip] (currently not used) If the ETH value passed with the transaction is not + * explicitly stated in the overrides, this field will be equal to the tip the operator will receive on top of the + * base cost of the transaction. + * @param [transaction.bridgeAddress] The address of the bridge contract to be used. + * Defaults to the default zkSync Era bridge (either `L1EthBridge` or `L1Erc20Bridge`). + * @param [transaction.l2GasLimit] Maximum amount of L2 gas that the transaction can consume during execution on L2. + * @param [transaction.gasPerPubdataByte] The L2 gas price for each published L1 calldata byte. + * @param [transaction.customBridgeData] Additional data that can be sent to a bridge. + * @param [transaction.refundRecipient] The address on L2 that will receive the refund for the transaction. + * If the transaction fails, it will also be the address to receive `l2Value`. + * @param [transaction.overrides] Transaction's overrides which may be used to pass L1 `gasLimit`, `gasPrice`, `value`, etc. + */ + async estimateGasDeposit(transaction: { + token: Address; + amount: web3Types.Numbers; + to?: Address; + operatorTip?: web3Types.Numbers; + bridgeAddress?: Address; + customBridgeData?: web3Types.Bytes; + l2GasLimit?: web3Types.Numbers; + gasPerPubdataByte?: web3Types.Numbers; + refundRecipient?: Address; + overrides?: TransactionOverrides; + }): Promise { + if (isAddressEq(transaction.token, LEGACY_ETH_ADDRESS)) { + transaction.token = ETH_ADDRESS_IN_CONTRACTS; + } + const tx = await this.getDepositTx(transaction); + + let baseGasLimit: bigint; + if (tx.token && isAddressEq(tx.token, await this.getBaseToken())) { + baseGasLimit = await this.estimateGasRequestExecute(tx); + } else { + baseGasLimit = await this._contextL1().eth.estimateGas(tx); + } + + return scaleGasLimit(baseGasLimit); + } + + /** + * Returns a populated deposit transaction. + * + * @param transaction The transaction details. + * @param transaction.token The address of the token to deposit. ETH by default. + * @param transaction.amount The amount of the token to deposit. + * @param [transaction.to] The address that will receive the deposited tokens on L2. + * @param [transaction.operatorTip] (currently not used) If the ETH value passed with the transaction is not + * explicitly stated in the overrides, this field will be equal to the tip the operator will receive on top of the + * base cost of the transaction. + * @param [transaction.bridgeAddress] The address of the bridge contract to be used. Defaults to the default zkSync + * Era bridge (either `L1EthBridge` or `L1Erc20Bridge`). + * @param [transaction.l2GasLimit] Maximum amount of L2 gas that the transaction can consume during execution on L2. + * @param [transaction.gasPerPubdataByte] The L2 gas price for each published L1 calldata byte. + * @param [transaction.customBridgeData] Additional data that can be sent to a bridge. + * @param [transaction.refundRecipient] The address on L2 that will receive the refund for the transaction. + * If the transaction fails, it will also be the address to receive `l2Value`. + * @param [transaction.overrides] Transaction's overrides which may be used to pass L1 `gasLimit`, `gasPrice`, `value`, etc. + */ + async getDepositTx(transaction: { + token: Address; + amount: web3Types.Numbers; + to?: Address; + operatorTip?: web3Types.Numbers; + bridgeAddress?: Address; + l2GasLimit?: web3Types.Numbers; + gasPerPubdataByte?: web3Types.Numbers; + customBridgeData?: web3Types.Bytes; + refundRecipient?: Address; + overrides?: TransactionOverrides; + }): Promise { + if (isAddressEq(transaction.token, LEGACY_ETH_ADDRESS)) { + transaction.token = ETH_ADDRESS_IN_CONTRACTS; + } + const baseTokenAddress = await this.getBaseToken(); + const isETHBasedChain = isAddressEq(baseTokenAddress, ETH_ADDRESS_IN_CONTRACTS); + + if (isETHBasedChain && isAddressEq(transaction.token, ETH_ADDRESS_IN_CONTRACTS)) { + return await this._getDepositETHOnETHBasedChainTx(transaction); + } else if (isETHBasedChain) { + return await this._getDepositTokenOnETHBasedChainTx(transaction); + } else if (isAddressEq(transaction.token, ETH_ADDRESS_IN_CONTRACTS)) { + return (await this._getDepositETHOnNonETHBasedChainTx(transaction)).tx; + } else if (isAddressEq(transaction.token, baseTokenAddress)) { + return (await this._getDepositBaseTokenOnNonETHBasedChainTx(transaction)).tx; + } else { + return (await this._getDepositNonBaseTokenToNonETHBasedChainTx(transaction)).tx; + } + } + + async _getDepositNonBaseTokenToNonETHBasedChainTx(transaction: { + token: Address; + amount: web3Types.Numbers; + to?: Address; + operatorTip?: web3Types.Numbers; + bridgeAddress?: Address; + l2GasLimit?: web3Types.Numbers; + gasPerPubdataByte?: web3Types.Numbers; + customBridgeData?: web3Types.Bytes; + refundRecipient?: Address; + overrides?: TransactionOverrides; + }) { + const bridgehub = await this.getBridgehubContract(); + const chainId = await this._contextL2().eth.getChainId(); + const bridgeContracts = await this.getL1BridgeContracts(); + + const tx = await this._getDepositTxWithDefaults(transaction); + const { + token, + operatorTip, + amount, + overrides, + l2GasLimit, + to, + refundRecipient, + gasPerPubdataByte, + } = tx; + + const baseCost = await this.getBaseCost({ + gasPrice: overrides.maxFeePerGas || overrides.gasPrice, + gasLimit: l2GasLimit, + gasPerPubdataByte: gasPerPubdataByte, + chainId, + }); + + const mintValue = web3Utils.toBigInt(baseCost) + web3Utils.toBigInt(operatorTip); + await checkBaseCost(baseCost, mintValue); + overrides.value ??= 0; + + return { + tx: bridgehub.methods.requestL2TransactionTwoBridges({ + chainId: chainId, + mintValue, + l2Value: 0, + l2GasLimit: l2GasLimit, + l2GasPerPubdataByteLimit: gasPerPubdataByte, + refundRecipient: refundRecipient ?? ZeroAddress, + secondBridgeAddress: bridgeContracts.shared.options.address, + secondBridgeValue: 0, + secondBridgeCalldata: Web3EthAbi.encodeParameters( + ['address', 'uint256', 'address'], + [token, amount, to], + ), + }), + overrides, + mintValue: mintValue, + }; + } + + async _getDepositBaseTokenOnNonETHBasedChainTx(transaction: { + token: Address; + amount: web3Types.Numbers; + to?: Address; + operatorTip?: web3Types.Numbers; + bridgeAddress?: Address; + l2GasLimit?: web3Types.Numbers; + gasPerPubdataByte?: web3Types.Numbers; + customBridgeData?: web3Types.Bytes; + refundRecipient?: Address; + overrides?: TransactionOverrides; + }) { + // Depositing the base token to a non-ETH-based chain. + // Goes through the BridgeHub. + // Have to give approvals for the sharedBridge. + + const tx = await this._getDepositTxWithDefaults(transaction); + const { operatorTip, amount, to, overrides, l2GasLimit, gasPerPubdataByte } = tx; + + const baseCost = await this.getBaseCost({ + gasPrice: overrides.maxFeePerGas || overrides.gasPrice, + gasLimit: l2GasLimit, + gasPerPubdataByte: gasPerPubdataByte, + }); + + tx.overrides.value = 0; + return { + tx: { + contractAddress: to, + calldata: '0x', + mintValue: toBigInt(baseCost) + BigInt(operatorTip) + BigInt(amount), + l2Value: amount, + ...tx, + }, + mintValue: toBigInt(baseCost) + BigInt(operatorTip) + BigInt(amount), + }; + } + + async _getDepositETHOnNonETHBasedChainTx(transaction: { + token: Address; + amount: web3Types.Numbers; + to?: Address; + operatorTip?: web3Types.Numbers; + bridgeAddress?: Address; + l2GasLimit?: web3Types.Numbers; + gasPerPubdataByte?: web3Types.Numbers; + customBridgeData?: web3Types.Bytes; + refundRecipient?: Address; + overrides?: TransactionOverrides; + }) { + const bridgehub = await this.getBridgehubContract(); + const chainId = await this._contextL2().eth.getChainId(); + const sharedBridge = (await this.getL1BridgeContracts()).shared.options.address; + + const tx = await this._getDepositTxWithDefaults(transaction); + const { + operatorTip, + amount, + overrides, + l2GasLimit, + to, + refundRecipient, + gasPerPubdataByte, + } = tx; + + const baseCost = await this.getBaseCost({ + gasPrice: overrides.maxFeePerGas || overrides.gasPrice, + gasLimit: l2GasLimit, + chainId: chainId, + gasPerPubdataByte: gasPerPubdataByte, + }); + + overrides.value ??= amount; + const mintValue = web3Utils.toBigInt(baseCost) + web3Utils.toBigInt(operatorTip); + await checkBaseCost(baseCost, mintValue); + + return { + tx: bridgehub.methods.requestL2TransactionTwoBridges({ + chainId, + mintValue, + l2Value: 0, + l2GasLimit: l2GasLimit, + l2GasPerPubdataByteLimit: gasPerPubdataByte, + refundRecipient: refundRecipient ?? ZeroAddress, + secondBridgeAddress: sharedBridge, + secondBridgeValue: amount, + secondBridgeCalldata: Web3EthAbi.encodeParameters( + ['address', 'uint256', 'address'], + [ETH_ADDRESS_IN_CONTRACTS, 0, to], + ), + }), + overrides, + mintValue: mintValue, + }; + } + + async _getDepositTokenOnETHBasedChainTx(transaction: { + token: Address; + amount: web3Types.Numbers; + to?: Address; + operatorTip?: web3Types.Numbers; + bridgeAddress?: Address; + l2GasLimit?: web3Types.Numbers; + gasPerPubdataByte?: web3Types.Numbers; + customBridgeData?: web3Types.Bytes; + refundRecipient?: Address; + overrides?: TransactionOverrides; + }): Promise<{ + tx: PayableMethodObject; + overrides: TransactionOverrides; + }> { + // Depositing token to an ETH-based chain. Use the ERC20 bridge as done before. + const bridgehub = await this.getBridgehubContract(); + const chainId = await this._contextL2().eth.getChainId(); + + const tx = await this._getDepositTxWithDefaults(transaction); + const { + token, + operatorTip, + amount, + overrides, + l2GasLimit, + to, + refundRecipient, + gasPerPubdataByte, + } = tx; + + const baseCost = await this.getBaseCost({ + gasPrice: overrides.maxFeePerGas || overrides.gasPrice, + gasLimit: l2GasLimit, + gasPerPubdataByte, + chainId, + }); + + const mintValue = web3Utils.toBigInt(baseCost) + web3Utils.toBigInt(operatorTip); + overrides.value ??= mintValue; + await checkBaseCost(baseCost, mintValue); + + let secondBridgeAddress: Address; + let secondBridgeCalldata: web3Types.Bytes; + if (tx.bridgeAddress) { + secondBridgeAddress = tx.bridgeAddress; + secondBridgeCalldata = await getERC20DefaultBridgeData( + transaction.token, + this._contextL1(), + ); + } else { + secondBridgeAddress = (await this.getL1BridgeContracts()).shared.options + .address as Address; + secondBridgeCalldata = Web3EthAbi.encodeParameters( + ['address', 'uint256', 'address'], + [token, amount, to], + ); + } + + return { + tx: bridgehub.methods.requestL2TransactionTwoBridges({ + chainId, + mintValue, + l2Value: 0, + l2GasLimit, + l2GasPerPubdataByteLimit: gasPerPubdataByte, + refundRecipient: refundRecipient ?? ZeroAddress, + secondBridgeAddress, + secondBridgeValue: 0, + secondBridgeCalldata, + }), + overrides, + }; + } + + async _getDepositETHOnETHBasedChainTx(transaction: { + token: Address; + amount: web3Types.Numbers; + to?: Address; + operatorTip?: web3Types.Numbers; + bridgeAddress?: Address; + l2GasLimit?: web3Types.Numbers; + gasPerPubdataByte?: web3Types.Numbers; + customBridgeData?: web3Types.Bytes; + refundRecipient?: Address; + overrides?: TransactionOverrides; + }) { + // Call the BridgeHub directly, like it's done with the DiamondProxy. + + const tx = await this._getDepositTxWithDefaults(transaction); + const { operatorTip, amount, overrides, l2GasLimit, gasPerPubdataByte, to } = tx; + const baseCost = await this.getBaseCost({ + gasPrice: overrides.maxFeePerGas || overrides.gasPrice, + gasLimit: l2GasLimit, + gasPerPubdataByte, + }); + + overrides.value ??= + web3Utils.toBigInt(baseCost) + + web3Utils.toBigInt(operatorTip) + + web3Utils.toBigInt(amount); + + return { + contractAddress: to, + calldata: '0x', + mintValue: overrides.value, + l2Value: amount, + ...tx, + }; + } + + // Creates a shallow copy of a transaction and populates missing fields with defaults. + async _getDepositTxWithDefaults(transaction: { + token: Address; + amount: web3Types.Numbers; + to?: Address; + operatorTip?: web3Types.Numbers; + bridgeAddress?: Address; + l2GasLimit?: web3Types.Numbers; + gasPerPubdataByte?: web3Types.Numbers; + customBridgeData?: web3Types.Bytes; + refundRecipient?: Address; + overrides?: TransactionOverrides; + }): Promise<{ + token: Address; + amount: web3Types.Numbers; + to: Address; + operatorTip: web3Types.Numbers; + bridgeAddress?: Address; + l2GasLimit: web3Types.Numbers; + gasPerPubdataByte: web3Types.Numbers; + customBridgeData?: web3Types.Bytes; + refundRecipient?: Address; + overrides: TransactionOverrides; + }> { + const { ...tx } = transaction; + tx.to = tx.to ?? this.getAddress(); + tx.operatorTip ??= 0; + tx.overrides ??= {}; + tx.overrides.from = this.getAddress(); + tx.gasPerPubdataByte ??= REQUIRED_L1_TO_L2_GAS_PER_PUBDATA_LIMIT; + tx.l2GasLimit ??= await this._getL2GasLimit(tx); + await insertGasPrice(this._contextL1(), tx.overrides); + + return tx as { + token: Address; + amount: web3Types.Numbers; + to: Address; + operatorTip: web3Types.Numbers; + bridgeAddress?: Address; + l2GasLimit: web3Types.Numbers; + gasPerPubdataByte: web3Types.Numbers; + customBridgeData?: web3Types.Bytes; + refundRecipient?: Address; + overrides: TransactionOverrides; + }; + } + + // Default behaviour for calculating l2GasLimit of deposit transaction. + async _getL2GasLimit(transaction: { + token: Address; + amount: web3Types.Numbers; + to?: Address; + operatorTip?: web3Types.Numbers; + bridgeAddress?: Address; + l2GasLimit?: web3Types.Numbers; + gasPerPubdataByte?: web3Types.Numbers; + customBridgeData?: web3Types.Bytes; + refundRecipient?: Address; + overrides?: TransactionOverrides; + }): Promise { + if (transaction.bridgeAddress) { + return await this._getL2GasLimitFromCustomBridge(transaction); + } else { + return await estimateDefaultBridgeDepositL2Gas( + this._contextL1(), + this._contextL2(), + transaction.token, + transaction.amount, + transaction.to!, + this.getAddress(), + transaction.gasPerPubdataByte, + ); + } + } + + // Calculates the l2GasLimit of deposit transaction using custom bridge. + async _getL2GasLimitFromCustomBridge(transaction: { + token: Address; + amount: web3Types.Numbers; + to?: Address; + operatorTip?: web3Types.Numbers; + bridgeAddress?: Address; + l2GasLimit?: web3Types.Numbers; + gasPerPubdataByte?: web3Types.Numbers; + customBridgeData?: web3Types.Bytes; + refundRecipient?: Address; + overrides?: TransactionOverrides; + }): Promise { + const customBridgeData = + transaction.customBridgeData ?? + (await getERC20DefaultBridgeData(transaction.token, this._contextL1())); + + const bridge = new (this._contextL1().eth.Contract)( + IL1BridgeABI, + transaction.bridgeAddress, + ); + const chainId = (await this._contextL2().eth.getChainId()) as web3Types.Numbers; + const l2Address = await bridge.methods.l2BridgeAddress(chainId).call(); + return await estimateCustomBridgeDepositL2Gas( + this._contextL2(), + transaction.bridgeAddress!, + l2Address, + transaction.token, + transaction.amount, + transaction.to!, + customBridgeData, + this.getAddress(), + transaction.gasPerPubdataByte, + ); + } + + /** + * Retrieves the full needed ETH fee for the deposit. Returns the L1 fee and the L2 fee {@link FullDepositFee}. + * + * @param transaction The transaction details. + * @param transaction.token The address of the token to deposit. ETH by default. + * @param [transaction.to] The address that will receive the deposited tokens on L2. + * @param [transaction.bridgeAddress] The address of the bridge contract to be used. + * Defaults to the default zkSync Era bridge (either `L1EthBridge` or `L1Erc20Bridge`). + * @param [transaction.customBridgeData] Additional data that can be sent to a bridge. + * @param [transaction.gasPerPubdataByte] The L2 gas price for each published L1 calldata byte. + * @param [transaction.overrides] Transaction's overrides which may be used to pass L1 `gasLimit`, `gasPrice`, `value`, etc. + * @throws {Error} If: + * - There's not enough balance for the deposit under the provided gas price. + * - There's not enough allowance to cover the deposit. + */ + async getFullRequiredDepositFee(transaction: { + token: Address; + to?: Address; + bridgeAddress?: Address; + customBridgeData?: web3Types.Bytes; + gasPerPubdataByte?: web3Types.Numbers; + overrides?: TransactionOverrides; + }): Promise { + if (isAddressEq(transaction.token, LEGACY_ETH_ADDRESS)) { + transaction.token = ETH_ADDRESS_IN_CONTRACTS; + } + // It is assumed that the L2 fee for the transaction does not depend on its value. + const dummyAmount = 1n; + const bridgehub = await this.getBridgehubContract(); + + const chainId = await this._contextL2().eth.getChainId(); + const baseTokenAddress = await this.getBaseToken(); + + const isETHBasedChain = isAddressEq(baseTokenAddress, ETH_ADDRESS_IN_CONTRACTS); + + const tx = await this._getDepositTxWithDefaults({ + ...transaction, + amount: dummyAmount, + }); + + const gasPriceForEstimation = tx.overrides.maxFeePerGas || tx.overrides.gasPrice; + const baseCost = await bridgehub.methods + .l2TransactionBaseCost( + chainId as web3Types.Numbers, + gasPriceForEstimation as web3Types.Numbers, + tx.l2GasLimit, + tx.gasPerPubdataByte, + ) + .call(); + + if (isETHBasedChain) { + // To ensure that L1 gas estimation succeeds when using estimateGasDeposit, + // the account needs to have a sufficient ETH balance. + const selfBalanceETH = await this.getBalanceL1(); + if (toBigInt(baseCost) >= toBigInt(selfBalanceETH) + toBigInt(dummyAmount)) { + const recommendedL1GasLimit = isAddressEq(tx.token, LEGACY_ETH_ADDRESS) + ? L1_RECOMMENDED_MIN_ETH_DEPOSIT_GAS_LIMIT + : L1_RECOMMENDED_MIN_ERC20_DEPOSIT_GAS_LIMIT; + const recommendedETHBalance = + BigInt(recommendedL1GasLimit) * BigInt(gasPriceForEstimation!) + + toBigInt(baseCost); + const formattedRecommendedBalance = web3Utils.fromWei( + recommendedETHBalance, + 'ether', + ); + throw new Error( + `Not enough balance for deposit! Under the provided gas price, the recommended balance to perform a deposit is ${formattedRecommendedBalance} ETH`, + ); + } + // In case of token deposit, a sufficient token allowance is also required. + if ( + !isAddressEq(tx.token, ETH_ADDRESS_IN_CONTRACTS) && + (await this.getAllowanceL1(tx.token, tx.bridgeAddress)) < dummyAmount + ) { + throw new Error('Not enough allowance to cover the deposit!'); + } + } else { + const mintValue = toBigInt(baseCost) + BigInt(tx.operatorTip); + if ((await this.getAllowanceL1(baseTokenAddress)) < mintValue) { + throw new Error('Not enough base token allowance to cover the deposit!'); + } + if ( + isAddressEq(tx.token, ETH_ADDRESS_IN_CONTRACTS) || + isAddressEq(tx.token, baseTokenAddress) + ) { + tx.overrides.value ??= tx.amount; + } else { + tx.overrides.value ??= 0; + if ((await this.getAllowanceL1(tx.token)) < dummyAmount) { + throw new Error('Not enough token allowance to cover the deposit!'); + } + } + } + + // Deleting the explicit gas limits in the fee estimation + // in order to prevent the situation where the transaction + // fails because the user does not have enough balance + const estimationOverrides = { ...tx.overrides }; + delete estimationOverrides.gasPrice; + delete estimationOverrides.maxFeePerGas; + delete estimationOverrides.maxPriorityFeePerGas; + + const l1GasLimit = await this.estimateGasDeposit({ + ...tx, + amount: dummyAmount, + overrides: estimationOverrides, + l2GasLimit: tx.l2GasLimit, + }); + + const fullCost: FullDepositFee = { + baseCost: toBigInt(baseCost), + l1GasLimit, + l2GasLimit: BigInt(tx.l2GasLimit), + }; + + if (tx.overrides.gasPrice) { + fullCost.gasPrice = BigInt(tx.overrides.gasPrice); + } else { + fullCost.maxFeePerGas = BigInt(tx.overrides.maxFeePerGas!); + fullCost.maxPriorityFeePerGas = BigInt(tx.overrides.maxPriorityFeePerGas!); + } + + return fullCost; + } + + /** + * Returns the transaction confirmation data that is part of `L2->L1` message. + * + * @param txHash The hash of the L2 transaction where the message was initiated. + * @param [index=0] In case there were multiple transactions in one message, you may pass an index of the + * transaction which confirmation data should be fetched. + * @throws {Error} If log proof can not be found. + */ + async getPriorityOpConfirmation(txHash: string, index = 0) { + return this._contextL2().getPriorityOpConfirmation(txHash, index); + } + + async _getWithdrawalLog(withdrawalHash: web3Types.Bytes, index = 0) { + const hash = web3Utils.toHex(withdrawalHash); + const receipt = await this._contextL2().getZKTransactionReceipt(hash); + if (!receipt) { + // @todo: or throw? + return {}; + } + // @ts-ignore + const log = (receipt?.logs || []).filter( + // @ts-ignore + log => + isAddressEq(String(log?.address), L1_MESSENGER_ADDRESS) && + log?.topics && + String(log?.topics[0]) === id('L1MessageSent(address,bytes32,bytes)'), + )[index]; + + return { + log, + l1BatchTxId: receipt.l1BatchTxIndex, + }; + } + + async _getWithdrawalL2ToL1Log(withdrawalHash: web3Types.Bytes, index = 0) { + const hash = web3Utils.toHex(withdrawalHash); + const receipt = await this._contextL2().getZKTransactionReceipt(hash); + if (!receipt) { + // @todo: or throw? + return {}; + } + const messages = Array.from(receipt.l2ToL1Logs.entries()).filter(([, log]) => + isAddressEq(log.sender, L1_MESSENGER_ADDRESS), + ); + const [l2ToL1LogIndex, l2ToL1Log] = messages[index]; + + return { + l2ToL1LogIndex, + l2ToL1Log, + }; + } + + /** + * Returns the {@link FinalizeWithdrawalParams parameters} required for finalizing a withdrawal from the + * withdrawal transaction's log on the L1 network. + * + * @param withdrawalHash Hash of the L2 transaction where the withdrawal was initiated. + * @param [index=0] In case there were multiple withdrawals in one transaction, you may pass an index of the + * withdrawal you want to finalize. + * @throws {Error} If log proof can not be found. + */ + async finalizeWithdrawalParams( + withdrawalHash: web3Types.Bytes, + index = 0, + ): Promise { + const { log, l1BatchTxId } = await this._getWithdrawalLog(withdrawalHash, index); + const { l2ToL1LogIndex } = await this._getWithdrawalL2ToL1Log(withdrawalHash, index); + const sender = log?.topics && dataSlice(toBytes(log?.topics[1]), 12); + const proof = await this._contextL2().getL2ToL1LogProof( + toHex(withdrawalHash), + l2ToL1LogIndex, + ); + if (!proof) { + throw new Error('Log proof not found!'); + } + const message = Web3EthAbi.decodeParameters(['bytes'], log?.data)[0]; + return { + l1BatchNumber: log.l1BatchNumber, + l2MessageIndex: Number(toNumber(proof.id)), + l2TxNumberInBlock: l1BatchTxId ? Number(toNumber(l1BatchTxId)) : null, + message, + sender, + proof: proof.proof, + }; + } + + /** + * Proves the inclusion of the `L2->L1` withdrawal message. + * + * @param withdrawalHash Hash of the L2 transaction where the withdrawal was initiated. + * @param [index=0] In case there were multiple withdrawals in one transaction, you may pass an index of the + * withdrawal you want to finalize. + * @param [overrides] Transaction's overrides which may be used to pass L1 `gasLimit`, `gasPrice`, `value`, etc. + * @returns A promise that resolves to the proof of inclusion of the withdrawal message. + * @throws {Error} If log proof can not be found. + */ + async finalizeWithdrawal( + withdrawalHash: web3Types.Bytes, + index = 0, + overrides?: TransactionOverrides, + ) { + const { l1BatchNumber, l2MessageIndex, l2TxNumberInBlock, message, proof } = + await this.finalizeWithdrawalParams(withdrawalHash, index); + + const l1Contracts = await this.getL1BridgeContracts(); + const contract = new (this._contextL1().eth.Contract)( + IL1BridgeABI, + l1Contracts.shared.options.address, + ); + overrides = overrides ?? {}; + overrides.from ??= this.getAddress(); + + return ( + contract.methods + .finalizeWithdrawal( + (await this._contextL2().eth.getChainId()) as web3Types.Numbers, + l1BatchNumber as web3Types.Numbers, + l2MessageIndex as web3Types.Numbers, + l2TxNumberInBlock as web3Types.Numbers, + message, + proof, + ) + // @ts-ignore + .send(overrides ?? {}) + ); + } + + /** + * Returns whether the withdrawal transaction is finalized on the L1 network. + * + * @param withdrawalHash Hash of the L2 transaction where the withdrawal was initiated. + * @param [index=0] In case there were multiple withdrawals in one transaction, you may pass an index of the + * withdrawal you want to finalize. + * @throws {Error} If log proof can not be found. + */ + async isWithdrawalFinalized(withdrawalHash: web3Types.Bytes, index = 0): Promise { + const { log } = await this._getWithdrawalLog(withdrawalHash, index); + const { l2ToL1LogIndex } = await this._getWithdrawalL2ToL1Log(withdrawalHash, index); + const sender = dataSlice(log.topics[1], 12); + // `getLogProof` is called not to get proof but + // to get the index of the corresponding L2->L1 log, + // which is returned as `proof.id`. + const proof = await this._contextL2().getL2ToL1LogProof( + toHex(withdrawalHash), + l2ToL1LogIndex, + ); + if (!proof) { + throw new Error('Log proof not found!'); + } + + const chainId = await this._contextL2().eth.getChainId(); + + let l1Bridge: Web3.Contract | Web3.Contract; + + if (await this._contextL2().isBaseToken(sender)) { + l1Bridge = (await this.getL1BridgeContracts()).shared; + } else { + const l2BridgeContract = new (this._contextL2().eth.Contract)(IL2BridgeABI, sender); + const l1BridgeAddress = await l2BridgeContract.methods.l1Bridge().call(); + l1Bridge = new (this._contextL1().eth.Contract)(IL1BridgeABI, l1BridgeAddress); + } + + return await l1Bridge.methods + .isWithdrawalFinalized(chainId, log.l1BatchNumber, proof.id) + .call(); + } + + /** + * Withdraws funds from the initiated deposit, which failed when finalizing on L2. + * If the deposit L2 transaction has failed, it sends an L1 transaction calling `claimFailedDeposit` method of the + * L1 bridge, which results in returning L1 tokens back to the depositor. + * + * @param depositHash The L2 transaction hash of the failed deposit. + * @param [overrides] Transaction's overrides which may be used to pass L1 `gasLimit`, `gasPrice`, `value`, etc. + * @returns A promise that resolves to the response of the `claimFailedDeposit` transaction. + * @throws {Error} If attempting to claim successful deposit. + */ + async claimFailedDeposit( + depositHash: web3Types.Bytes, + overrides?: TransactionOverrides, + ): Promise { + const receipt = await this._contextL2().getZKTransactionReceipt( + web3Utils.toHex(depositHash), + ); + if (!receipt) { + throw new Error('Transaction not found!'); + } + const successL2ToL1LogIndex = receipt.l2ToL1Logs.findIndex( + l2ToL1log => + isAddressEq(l2ToL1log.sender, BOOTLOADER_FORMAL_ADDRESS) && + l2ToL1log.key === depositHash, + ); + const successL2ToL1Log = receipt.l2ToL1Logs[successL2ToL1LogIndex]; + if (successL2ToL1Log.value !== ZeroHash) { + throw new Error('Cannot claim successful deposit!'); + } + + const tx = await this._contextL2().eth.getTransaction(web3Utils.toHex(depositHash)); + + // Undo the aliasing, since the Mailbox contract set it as for contract address. + const l1BridgeAddress = undoL1ToL2Alias(receipt.from); + const l2BridgeAddress = receipt.to; + if (!l2BridgeAddress) { + throw new Error('L2 bridge address not found!'); + } + const l1Bridge = new (this._contextL1().eth.Contract)(IL1BridgeABI, l1BridgeAddress); + + const l2Bridge = new Web3.Contract(IL2BridgeABI, l1BridgeAddress); + l2Bridge.setProvider(this._contextL2().provider); + + const calldata = l2Bridge.methods + .finalizeDeposit() + .decodeData(web3Utils.toHex(String(tx.data))); + + const proof = await this._contextL2().getL2ToL1LogProof( + web3Utils.toHex(depositHash), + successL2ToL1LogIndex, + ); + if (!proof) { + throw new Error('Log proof not found!'); + } + return ( + l1Bridge.methods + .claimFailedDeposit( + (await this._contextL2().eth.getChainId()) as web3Types.Numbers, + calldata[0], //_l1Sender + calldata[2], //_l1Token + calldata[3], //_amount + depositHash, + receipt.l1BatchNumber, + proof.id, + receipt.l1BatchTxIndex, + proof.proof, + ) + // @ts-ignore + .send(overrides ?? {}) + ); + } + + /** + * Requests execution of an L2 transaction from L1. + * + * @param transaction The transaction details. + * @param transaction.contractAddress The L2 contract to be called. + * @param transaction.calldata The input of the L2 transaction. + * @param [transaction.l2GasLimit] Maximum amount of L2 gas that transaction can consume during execution on L2. + * @param [transaction.mintValue] The amount of base token that needs to be minted on non-ETH-based L2. + * @param [transaction.l2Value] `msg.value` of L2 transaction. + * @param [transaction.factoryDeps] An array of L2 bytecodes that will be marked as known on L2. + * @param [transaction.operatorTip] (currently not used) If the ETH value passed with the transaction is not + * explicitly stated in the overrides, this field will be equal to the tip the operator will receive on top of + * the base cost of the transaction. + * @param [transaction.gasPerPubdataByte] The L2 gas price for each published L1 calldata byte. + * @param [transaction.refundRecipient] The address on L2 that will receive the refund for the transaction. + * If the transaction fails, it will also be the address to receive `l2Value`. + * @param [transaction.overrides] Transaction's overrides which may be used to pass L2 `gasLimit`, `gasPrice`, `value`, etc. + * @returns A promise that resolves to the response of the execution request. + */ + async requestExecute(transaction: { + contractAddress: Address; + calldata: string; + l2GasLimit?: web3Types.Numbers; + mintValue?: web3Types.Numbers; + l2Value?: web3Types.Numbers; + factoryDeps?: web3Types.Bytes[]; + operatorTip?: web3Types.Numbers; + gasPerPubdataByte?: web3Types.Numbers; + refundRecipient?: Address; + overrides?: TransactionOverrides; + }): Promise { + const tx = await this.getRequestExecuteTx(transaction); + return this.signAndSend(tx); + } + async signAndSend(tx: Transaction, _context?: Web3ZkSyncL1 | Web3ZkSyncL2) { + const context = _context || this._contextL1(); + const populated = await context.populateTransaction(tx); + const signed = await context.signTransaction(populated as Transaction); + + return getPriorityOpResponse( + context, + context.sendRawTransaction(signed), + this._contextL2(), + ); + } + async signTransaction(tx: Transaction): Promise { + return this._contextL1().signTransaction(tx); + } + async sendRawTransaction(signedTx: string): Promise { + return this._contextL1().sendRawTransaction(signedTx); + } + /** + * Estimates the amount of gas required for a request execute transaction. + * + * @param transaction The transaction details. + * @param transaction.contractAddress The L2 contract to be called. + * @param transaction.calldata The input of the L2 transaction. + * @param [transaction.l2GasLimit] Maximum amount of L2 gas that transaction can consume during execution on L2. + * @param [transaction.mintValue] The amount of base token that needs to be minted on non-ETH-based L2. + * @param [transaction.l2Value] `msg.value` of L2 transaction. + * @param [transaction.factoryDeps] An array of L2 bytecodes that will be marked as known on L2. + * @param [transaction.operatorTip] (currently not used) If the ETH value passed with the transaction is not + * explicitly stated in the overrides, this field will be equal to the tip the operator will receive on top + * of the base cost of the transaction. + * @param [transaction.gasPerPubdataByte] The L2 gas price for each published L1 calldata byte. + * @param [transaction.refundRecipient] The address on L2 that will receive the refund for the transaction. + * If the transaction fails, it will also be the address to receive `l2Value`. + * @param [transaction.overrides] Transaction's overrides which may be used to pass L1 `gasLimit`, `gasPrice`, `value`, etc. + */ + async estimateGasRequestExecute(transaction: { + contractAddress: Address; + calldata: string; + l2GasLimit?: web3Types.Numbers; + mintValue?: web3Types.Numbers; + l2Value?: web3Types.Numbers; + factoryDeps?: web3Types.Bytes[]; + operatorTip?: web3Types.Numbers; + gasPerPubdataByte?: web3Types.Numbers; + refundRecipient?: Address; + overrides?: TransactionOverrides; + }): Promise { + const { method, overrides } = await this.getRequestExecuteContractMethod(transaction); + + delete overrides.gasPrice; + delete overrides.maxFeePerGas; + delete overrides.maxPriorityFeePerGas; + + return method.estimateGas(overrides as PayableTxOptions); + } + + /** + * Returns the parameters for the approval token transaction based on the request execute transaction. + * Existing allowance for the bridge is not checked; allowance is calculated solely based on the specified transaction. + * + * @param transaction The request execute transaction on which approval parameters are calculated. + */ + async getRequestExecuteAllowanceParams(transaction: { + contractAddress: Address; + calldata: string; + l2GasLimit?: web3Types.Numbers; + l2Value?: web3Types.Numbers; + factoryDeps?: web3Types.Bytes[]; + operatorTip?: web3Types.Numbers; + gasPerPubdataByte?: web3Types.Numbers; + refundRecipient?: Address; + overrides?: TransactionOverrides; + }): Promise<{ token: Address; allowance: web3Types.Numbers }> { + const bridgehub = await this.getBridgehubContract(); + const chainId = await this._contextL2().eth.getChainId(); + const isETHBaseToken = isAddressEq( + await bridgehub.methods.baseToken(chainId).call(), + ETH_ADDRESS_IN_CONTRACTS, + ); + + if (isETHBaseToken) { + throw new Error( + "ETH token can't be approved! The address of the token does not exist on L1.", + ); + } + + const { ...tx } = transaction; + tx.l2Value ??= 0n; + tx.operatorTip ??= 0n; + tx.factoryDeps ??= []; + tx.overrides ??= {}; + tx.gasPerPubdataByte ??= REQUIRED_L1_TO_L2_GAS_PER_PUBDATA_LIMIT; + tx.refundRecipient ??= this.getAddress(); + tx.l2GasLimit ??= await this._contextL2().estimateL1ToL2Execute(transaction); + + const { l2Value, l2GasLimit, operatorTip, overrides, gasPerPubdataByte } = tx; + + await insertGasPrice(this._contextL1(), overrides); + const gasPriceForEstimation = overrides.maxFeePerGas || overrides.gasPrice; + + const baseCost = await this.getBaseCost({ + gasPrice: gasPriceForEstimation!, + gasPerPubdataByte, + gasLimit: l2GasLimit, + }); + + return { + token: await this.getBaseToken(), + allowance: baseCost + BigInt(operatorTip) + BigInt(l2Value), + }; + } + async getRequestExecuteContractMethod(transaction: { + contractAddress: Address; + calldata: string; + l2GasLimit?: web3Types.Numbers; + mintValue?: web3Types.Numbers; + l2Value?: web3Types.Numbers; + factoryDeps?: web3Types.Bytes[]; + operatorTip?: web3Types.Numbers; + gasPerPubdataByte?: web3Types.Numbers; + refundRecipient?: Address; + overrides?: TransactionOverrides; + }) { + const bridgehub = await this.getBridgehubContract(); + const chainId = await this._contextL2().eth.getChainId(); + const isETHBaseToken = isAddressEq( + await bridgehub.methods.baseToken(chainId).call(), + ETH_ADDRESS_IN_CONTRACTS, + ); + + const { ...tx } = transaction; + tx.l2Value ??= 0; + tx.mintValue ??= 0; + tx.operatorTip ??= 0; + tx.factoryDeps ??= []; + tx.overrides ??= {}; + tx.overrides.from ??= this.getAddress(); + tx.gasPerPubdataByte ??= REQUIRED_L1_TO_L2_GAS_PER_PUBDATA_LIMIT; + tx.refundRecipient ??= this.getAddress(); + tx.l2GasLimit ??= await this._contextL2().estimateL1ToL2Execute(transaction); + + const { + contractAddress, + mintValue, + l2Value, + calldata, + l2GasLimit, + factoryDeps, + operatorTip, + overrides, + gasPerPubdataByte, + refundRecipient, + } = tx; + + await insertGasPrice(this._contextL1(), overrides); + const gasPriceForEstimation = overrides.maxFeePerGas || overrides.gasPrice; + + const baseCost = await this.getBaseCost({ + gasPrice: gasPriceForEstimation!, + gasPerPubdataByte, + gasLimit: l2GasLimit, + }); + + const l2Costs = baseCost + BigInt(operatorTip) + BigInt(l2Value); + let providedValue = isETHBaseToken ? overrides.value : mintValue; + if (providedValue === undefined || providedValue === null || BigInt(providedValue) === 0n) { + providedValue = l2Costs; + if (isETHBaseToken) overrides.value = providedValue; + } + await checkBaseCost(baseCost, providedValue); + + const method = bridgehub.methods.requestL2TransactionDirect({ + chainId, + mintValue: providedValue, + l2Contract: contractAddress, + l2Value: l2Value, + l2Calldata: calldata, + l2GasLimit: l2GasLimit, + l2GasPerPubdataByteLimit: REQUIRED_L1_TO_L2_GAS_PER_PUBDATA_LIMIT, + factoryDeps: factoryDeps, + refundRecipient: refundRecipient, + }); + return { method, overrides }; + } + /** + * Returns a populated request execute transaction. + * + * @param transaction The transaction details. + * @param transaction.contractAddress The L2 contract to be called. + * @param transaction.calldata The input of the L2 transaction. + * @param [transaction.l2GasLimit] Maximum amount of L2 gas that transaction can consume during execution on L2. + * @param [transaction.mintValue] The amount of base token that needs to be minted on non-ETH-based L2. + * @param [transaction.l2Value] `msg.value` of L2 transaction. + * @param [transaction.factoryDeps] An array of L2 bytecodes that will be marked as known on L2. + * @param [transaction.operatorTip] (currently not used) If the ETH value passed with the transaction is not + * explicitly stated in the overrides, this field will be equal to the tip the operator will receive on top of the + * base cost of the transaction. + * @param [transaction.gasPerPubdataByte] The L2 gas price for each published L1 calldata byte. + * @param [transaction.refundRecipient] The address on L2 that will receive the refund for the transaction. + * If the transaction fails, it will also be the address to receive `l2Value`. + * @param [transaction.overrides] Transaction's overrides which may be used to pass L1 `gasLimit`, `gasPrice`, `value`, etc. + */ + async getRequestExecuteTx(transaction: { + contractAddress: Address; + calldata: string; + l2GasLimit?: web3Types.Numbers; + mintValue?: web3Types.Numbers; + l2Value?: web3Types.Numbers; + factoryDeps?: web3Types.Bytes[]; + operatorTip?: web3Types.Numbers; + gasPerPubdataByte?: web3Types.Numbers; + refundRecipient?: Address; + overrides?: TransactionOverrides; + }) { + const { method, overrides } = await this.getRequestExecuteContractMethod(transaction); + return method.populateTransaction(overrides as PayableTxOptions); + } + + async populateTransaction(tx: Transaction): Promise { + tx.from = this.getAddress(); + + if ( + (!tx.type || (tx.type && toHex(tx.type) !== toHex(EIP712_TX_TYPE))) && + !(tx as Eip712TxData).customData + ) { + return this._contextL1().populateTransaction(tx); + } + + const populated = (await this._contextL1().populateTransaction(tx)) as Eip712TxData; + populated.type = EIP712_TX_TYPE; + populated.value ??= 0; + populated.data ??= '0x'; + + return populated; + } + // @ts-ignore + public getAddress(): string { + throw new Error('Must be implemented by the derived class!'); + } +} + +export class AdapterL2 implements TxSender { + /** + * Returns a context (provider + Signer) instance for connecting to an L2 network. + */ + _contextL2(): Web3ZkSyncL2 { + throw new Error('Must be implemented by the derived class!'); + } + async _eip712Signer(): Promise { + throw new Error('Must be implemented by the derived class!'); + } + + /** + * Returns the balance of the account. + * + * @param [token] The token address to query balance for. Defaults to the native token. + * @param [blockTag='committed'] The block tag to get the balance at. + */ + async getBalance( + token?: Address, + blockTag: web3Types.BlockNumberOrTag = 'committed', + ): Promise { + if (token) { + const contract = new (this._contextL2().eth.Contract)(IERC20ABI, token); + return contract.methods.balanceOf(this.getAddress()).call(); + } + + return await this._contextL2().eth.getBalance(this.getAddress(), blockTag); + } + + /** + * Returns all token balances of the account. + */ + async getAllBalances(): Promise { + return this._contextL2().getAllAccountBalances(this.getAddress()); + } + + /** + * Returns the deployment nonce of the account. + */ + async getDeploymentNonce(): Promise { + const contract = new Web3.Contract(INonceHolderABI, NONCE_HOLDER_ADDRESS); + contract.setProvider(this._contextL2().provider); + return contract.methods.getDeploymentNonce(this.getAddress()).call(); + } + + /** + * Returns L2 bridge contracts. + */ + async getL2BridgeContracts(): Promise<{ + erc20: Web3.Contract; + weth: Web3.Contract; + shared: Web3.Contract; + }> { + const addresses = await this._contextL2().getDefaultBridgeAddresses(); + const erc20 = new Web3.Contract(IL2BridgeABI, addresses.erc20L2); + const weth = new Web3.Contract(IL2BridgeABI, addresses.wethL2); + const shared = new Web3.Contract(IL2BridgeABI, addresses.sharedL2); + + erc20.setProvider(this._contextL2().provider); + weth.setProvider(this._contextL2().provider); + shared.setProvider(this._contextL2().provider); + + return { + erc20, + weth, + shared, + }; + } + + /** + * Initiates the withdrawal process which withdraws ETH or any ERC20 token + * from the associated account on L2 network to the target account on L1 network. + * + * @param transaction Withdrawal transaction request. + * @param transaction.token The address of the token. Defaults to ETH. + * @param transaction.amount The amount of the token to withdraw. + * @param [transaction.to] The address of the recipient on L1. + * @param [transaction.bridgeAddress] The address of the bridge contract to be used. + * @param [transaction.paymasterParams] Paymaster parameters. + * @param [transaction.overrides] Transaction's overrides which may be used to pass L2 `gasLimit`, `gasPrice`, `value`, etc. + * @returns A Promise resolving to a withdrawal transaction response. + */ + async withdraw(transaction: { + token: Address; + amount: web3Types.Numbers; + to?: Address; + bridgeAddress?: Address; + paymasterParams?: PaymasterParams; + overrides?: TransactionOverrides; + }) { + const tx = await this._contextL2().getWithdrawTx({ + ...transaction, + from: this.getAddress(), + }); + const populated = await this.populateTransaction(tx as Transaction); + const signed = await this.signTransaction(populated as Transaction); + return getPriorityOpResponse( + this._contextL2(), + this.sendRawTransaction(signed), + this._contextL2(), + ); + } + + async signTransaction(tx: Transaction): Promise { + return this._contextL2().signTransaction(tx); + } + async sendRawTransaction(signedTx: string): Promise { + return this._contextL2().sendRawTransaction(signedTx); + } + + /** + * Transfer ETH or any ERC20 token within the same interface. + * + * @param transaction Transfer transaction request. + * @param transaction.to The address of the recipient. + * @param transaction.amount The amount of the token to transfer. + * @param [transaction.token] The address of the token. Defaults to ETH. + * @param [transaction.paymasterParams] Paymaster parameters. + * @param [transaction.overrides] Transaction's overrides which may be used to pass L2 `gasLimit`, `gasPrice`, `value`, etc. + * @returns A Promise resolving to a transfer transaction response. + */ + async transfer(transaction: { + to: Address; + amount: web3Types.Numbers; + token?: Address; + paymasterParams?: PaymasterParams; + overrides?: TransactionOverrides; + }) { + return this._contextL2().getTransferTx({ + from: this.getAddress(), + ...transaction, + }); + } + // @ts-ignore + public getAddress(): string { + throw new Error('Must be implemented by the derived class!'); + } + + async populateTransaction(tx: Transaction): Promise { + tx.from = this.getAddress(); + if ( + (!tx.type || (tx.type && toHex(tx.type) !== toHex(EIP712_TX_TYPE))) && + !(tx as Eip712TxData).customData + ) { + return this._contextL2().populateTransaction(tx); + } + + const populated = (await this._contextL2().populateTransaction(tx)) as Eip712TxData; + populated.type = EIP712_TX_TYPE; + populated.value ??= 0; + populated.data ??= '0x'; + + return populated; + } +} + +// This method checks if the overrides contain a gasPrice (or maxFeePerGas), +// if not it will insert the maxFeePerGas +async function insertGasPrice( + l1Provider: Web3.Web3, + overrides: TransactionOverrides, +): Promise { + if (!overrides.gasPrice && !overrides.maxFeePerGas) { + const l1FeeData = await l1Provider.eth.calculateFeeData(); + // Sometimes baseFeePerGas is not available, so we use gasPrice instead. + const baseFee = BigInt( + l1FeeData.maxFeePerGas! ? getBaseCostFromFeeData(l1FeeData) : l1FeeData.gasPrice!, + ); + if (!baseFee) { + throw new Error('Failed to calculate base fee!'); + } + + // ethers.js by default uses multiplication by 2, but since the price for the L2 part + // will depend on the L1 part, doubling base fee is typically too much. + overrides.maxFeePerGas = + (baseFee * 3n) / 2n + (BigInt(l1FeeData.maxPriorityFeePerGas!) ?? 0n); + overrides.maxPriorityFeePerGas = + l1FeeData.maxPriorityFeePerGas && toHex(l1FeeData.maxPriorityFeePerGas); + } +} + +function getBaseCostFromFeeData(feeData: web3Types.FeeData): bigint { + const maxFeePerGas = feeData.maxFeePerGas!; + const maxPriorityFeePerGas = feeData.maxPriorityFeePerGas!; + + return (BigInt(maxFeePerGas) - BigInt(maxPriorityFeePerGas)) / 2n; +} diff --git a/src/contracts/IERC20.ts b/src/contracts/IERC20.ts index 7b64f8f..03239cf 100644 --- a/src/contracts/IERC20.ts +++ b/src/contracts/IERC20.ts @@ -17,124 +17,223 @@ along with web3.js. If not, see . export const IERC20ABI = [ { - inputs: [{ internalType: 'uint256', name: 'initialSupply', type: 'uint256' }], - stateMutability: 'nonpayable', - type: 'constructor', - }, - { - anonymous: false, - inputs: [ - { indexed: true, internalType: 'address', name: 'owner', type: 'address' }, - { indexed: true, internalType: 'address', name: 'spender', type: 'address' }, - { indexed: false, internalType: 'uint256', name: 'value', type: 'uint256' }, - ], - name: 'Approval', - type: 'event', - }, - { - anonymous: false, - inputs: [ - { indexed: true, internalType: 'address', name: 'from', type: 'address' }, - { indexed: true, internalType: 'address', name: 'to', type: 'address' }, - { indexed: false, internalType: 'uint256', name: 'value', type: 'uint256' }, - ], - name: 'Transfer', - type: 'event', - }, - { - inputs: [ - { internalType: 'address', name: 'owner', type: 'address' }, - { internalType: 'address', name: 'spender', type: 'address' }, + constant: true, + inputs: [], + name: 'name', + outputs: [ + { + name: '', + type: 'string', + }, ], - name: 'allowance', - outputs: [{ internalType: 'uint256', name: '', type: 'uint256' }], + payable: false, stateMutability: 'view', type: 'function', }, { + constant: false, inputs: [ - { internalType: 'address', name: 'spender', type: 'address' }, - { internalType: 'uint256', name: 'amount', type: 'uint256' }, + { + name: '_spender', + type: 'address', + }, + { + name: '_value', + type: 'uint256', + }, ], name: 'approve', - outputs: [{ internalType: 'bool', name: '', type: 'bool' }], + outputs: [ + { + name: '', + type: 'bool', + }, + ], + payable: false, stateMutability: 'nonpayable', type: 'function', }, { - inputs: [{ internalType: 'address', name: 'account', type: 'address' }], - name: 'balanceOf', - outputs: [{ internalType: 'uint256', name: '', type: 'uint256' }], - stateMutability: 'view', - type: 'function', - }, - { + constant: true, inputs: [], - name: 'decimals', - outputs: [{ internalType: 'uint8', name: '', type: 'uint8' }], + name: 'totalSupply', + outputs: [ + { + name: '', + type: 'uint256', + }, + ], + payable: false, stateMutability: 'view', type: 'function', }, { + constant: false, inputs: [ - { internalType: 'address', name: 'spender', type: 'address' }, - { internalType: 'uint256', name: 'subtractedValue', type: 'uint256' }, + { + name: '_from', + type: 'address', + }, + { + name: '_to', + type: 'address', + }, + { + name: '_value', + type: 'uint256', + }, ], - name: 'decreaseAllowance', - outputs: [{ internalType: 'bool', name: '', type: 'bool' }], - stateMutability: 'nonpayable', - type: 'function', - }, - { - inputs: [ - { internalType: 'address', name: 'spender', type: 'address' }, - { internalType: 'uint256', name: 'addedValue', type: 'uint256' }, + name: 'transferFrom', + outputs: [ + { + name: '', + type: 'bool', + }, ], - name: 'increaseAllowance', - outputs: [{ internalType: 'bool', name: '', type: 'bool' }], + payable: false, stateMutability: 'nonpayable', type: 'function', }, { + constant: true, inputs: [], - name: 'name', - outputs: [{ internalType: 'string', name: '', type: 'string' }], + name: 'decimals', + outputs: [ + { + name: '', + type: 'uint8', + }, + ], + payable: false, stateMutability: 'view', type: 'function', }, { - inputs: [], - name: 'symbol', - outputs: [{ internalType: 'string', name: '', type: 'string' }], + constant: true, + inputs: [ + { + name: '_owner', + type: 'address', + }, + ], + name: 'balanceOf', + outputs: [ + { + name: 'balance', + type: 'uint256', + }, + ], + payable: false, stateMutability: 'view', type: 'function', }, { + constant: true, inputs: [], - name: 'totalSupply', - outputs: [{ internalType: 'uint256', name: '', type: 'uint256' }], + name: 'symbol', + outputs: [ + { + name: '', + type: 'string', + }, + ], + payable: false, stateMutability: 'view', type: 'function', }, { + constant: false, inputs: [ - { internalType: 'address', name: 'to', type: 'address' }, - { internalType: 'uint256', name: 'amount', type: 'uint256' }, + { + name: '_to', + type: 'address', + }, + { + name: '_value', + type: 'uint256', + }, ], name: 'transfer', - outputs: [{ internalType: 'bool', name: '', type: 'bool' }], + outputs: [ + { + name: '', + type: 'bool', + }, + ], + payable: false, stateMutability: 'nonpayable', type: 'function', }, { + constant: true, inputs: [ - { internalType: 'address', name: 'from', type: 'address' }, - { internalType: 'address', name: 'to', type: 'address' }, - { internalType: 'uint256', name: 'amount', type: 'uint256' }, + { + name: '_owner', + type: 'address', + }, + { + name: '_spender', + type: 'address', + }, ], - name: 'transferFrom', - outputs: [{ internalType: 'bool', name: '', type: 'bool' }], - stateMutability: 'nonpayable', + name: 'allowance', + outputs: [ + { + name: '', + type: 'uint256', + }, + ], + payable: false, + stateMutability: 'view', type: 'function', }, + { + payable: true, + stateMutability: 'payable', + type: 'fallback', + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + name: 'owner', + type: 'address', + }, + { + indexed: true, + name: 'spender', + type: 'address', + }, + { + indexed: false, + name: 'value', + type: 'uint256', + }, + ], + name: 'Approval', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + name: 'from', + type: 'address', + }, + { + indexed: true, + name: 'to', + type: 'address', + }, + { + indexed: false, + name: 'value', + type: 'uint256', + }, + ], + name: 'Transfer', + type: 'event', + }, ] as const; diff --git a/src/index.ts b/src/index.ts index 705e799..31a1263 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,4 +1,7 @@ export { ZkSyncPlugin } from './plugin'; +export { Web3ZkSyncL1 } from './web3zksync-l1'; +export { Web3ZkSyncL2 } from './web3zksync-l2'; +export { ZKSyncWallet } from './zksync-wallet'; export * as utils from './utils'; export * as types from './types'; export * as constants from './constants'; diff --git a/src/paymaster-utils.ts b/src/paymaster-utils.ts new file mode 100644 index 0000000..0d11837 --- /dev/null +++ b/src/paymaster-utils.ts @@ -0,0 +1,81 @@ +import { Contract } from 'web3-eth-contract'; + +import type { + Address, + ApprovalBasedPaymasterInput, + GeneralPaymasterInput, + PaymasterInput, + PaymasterParams, +} from './types'; +import { Abi } from './contracts/IPaymasterFlow'; + +/** + * The ABI for the `IPaymasterFlow` interface, which is utilized + * for encoding input parameters for paymaster flows. + * @constant + */ +export const PAYMASTER_FLOW_ABI = new Contract(Abi); + +/** + * Returns encoded input for an approval-based paymaster. + * + * @param paymasterInput The input data for the paymaster. + */ +export function getApprovalBasedPaymasterInput(paymasterInput: ApprovalBasedPaymasterInput) { + return PAYMASTER_FLOW_ABI.methods + .approvalBased( + paymasterInput.token, + paymasterInput.minimalAllowance, + paymasterInput.innerInput, + ) + .encodeABI(); +} + +/** + * Returns encoded input for a general-based paymaster. + * + * @param paymasterInput The input data for the paymaster. + */ +export function getGeneralPaymasterInput(paymasterInput: GeneralPaymasterInput) { + return PAYMASTER_FLOW_ABI.methods.general(paymasterInput.innerInput).encodeABI(); +} + +/** + * Returns a correctly-formed {@link PaymasterParams|paymasterParams} object for common paymaster flows. + * + * @param paymasterAddress The non-zero paymaster address. + * @param paymasterInput The input data for the paymaster. + * + * @example Create general-based parameters. + * + * const paymasterAddress = "0x0a67078A35745947A37A552174aFe724D8180c25"; + * const paymasterParams = utils.getPaymasterParams(paymasterAddress, { + * type: "General", + * innerInput: new Uint8Array(), + * }); + * + * @example Create approval-based parameters. + * + * const result = utils.getPaymasterParams("0x0a67078A35745947A37A552174aFe724D8180c25", { + * type: "ApprovalBased", + * token: "0x65C899B5fb8Eb9ae4da51D67E1fc417c7CB7e964", + * minimalAllowance: BigInt(1), + * innerInput: new Uint8Array(), + * }); + */ +export function getPaymasterParams( + paymasterAddress: Address, + paymasterInput: PaymasterInput, +): PaymasterParams { + if (paymasterInput.type === 'General') { + return { + paymaster: paymasterAddress, + paymasterInput: getGeneralPaymasterInput(paymasterInput), + }; + } else { + return { + paymaster: paymasterAddress, + paymasterInput: getApprovalBasedPaymasterInput(paymasterInput), + }; + } +} diff --git a/src/plugin.ts b/src/plugin.ts index 774ad1e..a90a3e3 100644 --- a/src/plugin.ts +++ b/src/plugin.ts @@ -1,4 +1,5 @@ -import type { Web3Context, Web3RequestManager } from 'web3-core'; +import type { Web3Context, Web3ContextInitOptions, Web3RequestManager } from 'web3-core'; +import type * as web3Types from 'web3-types'; import type { Address } from 'web3-types'; import { Contract } from 'web3-eth-contract'; import { Web3PluginBase } from 'web3-core'; @@ -6,7 +7,7 @@ import { Web3PluginBase } from 'web3-core'; import { TransactionFactory } from 'web3-eth-accounts'; import { IERC20ABI } from './contracts/IERC20'; import { RpcMethods } from './rpc.methods'; -import { EIP712_TX_TYPE, ETH_ADDRESS, ZERO_ADDRESS } from './constants'; +import * as constants from './constants'; import { IL2BridgeABI } from './contracts/IL2Bridge'; import { IZkSyncABI } from './contracts/IZkSyncStateTransition'; import { IBridgehubABI } from './contracts/IBridgehub'; @@ -16,17 +17,27 @@ import { IERC1271ABI } from './contracts/IERC1271'; import { IL1BridgeABI } from './contracts/IL1ERC20Bridge'; import { INonceHolderABI } from './contracts/INonceHolder'; import { EIP712Transaction } from './Eip712'; +import { ZKSyncWallet } from './zksync-wallet'; +import { Web3ZkSyncL2 } from './web3zksync-l2'; +import { Web3ZkSyncL1 } from './web3zksync-l1'; +import type { ContractsAddresses } from './types'; -export class ZkSyncPlugin extends Web3PluginBase { - public pluginNamespace = 'zkSync'; - public erc20BridgeL1: string; - public erc20BridgeL2: string; - public wethBridgeL1: string; - public wethBridgeL2: string; - public _rpc?: RpcMethods; - public _l2BridgeContracts: Record>; - public _erc20Contracts: Record>; - public Contracts: { +interface ZKSyncWalletConstructor { + new (privateKey: string): ZKSyncWallet; +} + +export type ZKSyncContractsCollection = { + Generic: { + /** + * The web3.js Contract instance for the `IERC20` interface, which is utilized for interacting with ERC20 tokens. + */ + IERC20Contract: Contract; + /** + * The web3.js Contract instance for the `IERC1271` interface, which is utilized for signature validation by contracts. + */ + IERC1271Contract: Contract; + }; + L1: { /** * The web3.js Contract instance for the `ZkSync` interface. */ @@ -35,6 +46,12 @@ export class ZkSyncPlugin extends Web3PluginBase { * The ABI of the `Bridgehub` interface. */ BridgehubContract: Contract; + /** + * The web3.js Contract instance for the `IL1Bridge` interface, which is utilized for transferring ERC20 tokens from L1 to L2. + */ + L1BridgeContract: Contract; + }; + L2: { /** * The web3.js Contract instance for the `IContractDeployer` interface, which is utilized for deploying smart contracts. */ @@ -43,79 +60,176 @@ export class ZkSyncPlugin extends Web3PluginBase { * The web3.js Contract instance for the `IL1Messenger` interface, which is utilized for sending messages from the L2 to L1. */ L1MessengerContract: Contract; - /** - * The web3.js Contract instance for the `IERC20` interface, which is utilized for interacting with ERC20 tokens. - */ - IERC20Contract: Contract; - /** - * The web3.js Contract instance for the `IERC1271` interface, which is utilized for signature validation by contracts. - */ - IERC1271Contract: Contract; - /** - * The web3.js Contract instance for the `IL1Bridge` interface, which is utilized for transferring ERC20 tokens from L1 to L2. - */ - L1BridgeContract: Contract; /** * The web3.js Contract instance for the `IL2Bridge` interface, which is utilized for transferring ERC20 tokens from L2 to L1. */ L2BridgeContract: Contract; + /** * The web3.js Contract instance for the `INonceHolder` interface, which is utilized for managing deployment nonces. */ NonceHolderContract: Contract; }; +}; + +export class ZkSyncPlugin extends Web3PluginBase { + public L1: Web3ZkSyncL1 | undefined; + public L2: Web3ZkSyncL2; + public pluginNamespace = 'zkSync'; + public _rpc?: RpcMethods; + public _l2BridgeContracts: Record>; + public _erc20Contracts: Record>; - constructor() { - super(); + private contracts: ZKSyncContractsCollection | undefined; + public get Contracts(): Promise { + if (this.contracts) { + return Promise.resolve(this.contracts); + } + return this.initContracts(); + } + + contractsAddresses: Promise; + public get ContractsAddresses(): Promise { + if (this.contractsAddresses) { + return Promise.resolve(this.contractsAddresses); + } + return this.initContractsAddresses(); + } + + // the wallet type in this class is different from the parent class. So, they should have different names. + ZkWallet: ZKSyncWalletConstructor; + + /** + * Constructor + * @param providerOrContextL2 - The provider or context for the L2 network + */ + constructor( + providerOrContextL2: + | string + | web3Types.SupportedProviders + | Web3ContextInitOptions + | Web3ZkSyncL2, + ) { + super( + providerOrContextL2 as + | string + | web3Types.SupportedProviders + | Web3ContextInitOptions, + ); + if (providerOrContextL2 instanceof Web3ZkSyncL2) { + this.L2 = providerOrContextL2; + } else { + this.L2 = new Web3ZkSyncL2(providerOrContextL2); + } // @ts-ignore-next-line - TransactionFactory.registerTransactionType(EIP712_TX_TYPE, EIP712Transaction); - this.erc20BridgeL1 = ''; - this.erc20BridgeL2 = ''; - this.wethBridgeL1 = ''; - this.wethBridgeL2 = ''; + TransactionFactory.registerTransactionType(constants.EIP712_TX_TYPE, EIP712Transaction); + this._l2BridgeContracts = {}; this._erc20Contracts = {}; - this.Contracts = { - ZkSyncMainContract: new Contract(IZkSyncABI, ''), - BridgehubContract: new Contract(IBridgehubABI, ''), - ContractDeployerContract: new Contract(IContractDeployerABI, ''), - L1MessengerContract: new Contract(IL1MessengerABI, ''), - IERC20Contract: new Contract(IERC20ABI, ''), - IERC1271Contract: new Contract(IERC1271ABI, ''), - L1BridgeContract: new Contract(IL1BridgeABI, ''), - L2BridgeContract: new Contract(IL2BridgeABI, ''), - NonceHolderContract: new Contract(INonceHolderABI, ''), + + this.contractsAddresses = this.initContractsAddresses(); + + const self = this; + class ZKSyncWalletWithFullContext extends ZKSyncWallet { + constructor(privateKey: string) { + super(privateKey, self.L2, self.L1); + } + } + + this.ZkWallet = ZKSyncWalletWithFullContext; + this.initWallet(); + } + + public async initContracts() { + if (!this.L1 || !this.L2) { + throw new Error( + 'Contracts cannot be initialized because a Web3 instance is not yet linked to ZkSync plugin', + ); + } + + const { + mainContract, + bridgehubContractAddress, + // l1Erc20DefaultBridge, + // l2Erc20DefaultBridge, + // l1WethBridge, + // l2WethBridge, + l1SharedDefaultBridge, + l2SharedDefaultBridge, + } = await this.contractsAddresses; + + const contractsCollection: ZKSyncContractsCollection = { + Generic: { + IERC20Contract: new Contract(IERC20ABI), + IERC1271Contract: new Contract(IERC1271ABI), + }, + L1: { + ZkSyncMainContract: new Contract(IZkSyncABI, mainContract, this.L1), + BridgehubContract: new Contract(IBridgehubABI, bridgehubContractAddress, this.L1), + L1BridgeContract: new Contract(IL1BridgeABI, l1SharedDefaultBridge, this.L1), + }, + L2: { + ContractDeployerContract: new Contract( + IContractDeployerABI, + constants.CONTRACT_DEPLOYER_ADDRESS, + this.L2, + ), + L1MessengerContract: new Contract( + IL1MessengerABI, + constants.L1_MESSENGER_ADDRESS, + this.L2, + ), + NonceHolderContract: new Contract( + INonceHolderABI, + constants.NONCE_HOLDER_ADDRESS, + this.L2, + ), + L2BridgeContract: new Contract(IL2BridgeABI, l2SharedDefaultBridge, this.L2), + }, }; - this.fillContractsAddresses(); + this.contracts = contractsCollection; + return contractsCollection; } - private async fillContractsAddresses() { - // TODO: optionally fetch and set the contract addresses from the Adapter, - // nonce methods like getBridgehubContract and getDefaultBridgeAddresses are implemented. - // this.Contracts.ZkSyncMainContract.options.address = - // this.Contracts.BridgehubContract.options.address = - // this.Contracts.ContractDeployerContract.options.address = - // this.Contracts.L1MessengerContract.options.address = - // this.Contracts.IERC20Contract.options.address = - // this.Contracts.IERC1271Contract.options.address = - // this.Contracts.L1BridgeContract.options.address = - // this.Contracts.L2BridgeContract.options.address = - // this.Contracts.NonceHolderContract.options.address = + /** + * Try to fill the contract addresses + * @returns True if the contract addresses were successfully filled, false otherwise + */ + public async initContractsAddresses() { + const [mainContract, bridgehubContractAddress, bridgeContracts] = await Promise.all([ + this.rpc.getMainContract(), + this.rpc.getBridgehubContractAddress(), + this.rpc.getBridgeContracts(), + ]); + this.contractsAddresses = Promise.resolve({ + mainContract, + bridgehubContractAddress, + ...bridgeContracts, + }); + + return this.contractsAddresses; } public link(parentContext: Web3Context): void { super.link(parentContext); - this.Contracts.ZkSyncMainContract.link(parentContext); - this.Contracts.BridgehubContract.link(parentContext); - this.Contracts.ContractDeployerContract.link(parentContext); - this.Contracts.L1MessengerContract.link(parentContext); - this.Contracts.IERC20Contract.link(parentContext); - this.Contracts.IERC1271Contract.link(parentContext); - this.Contracts.L1BridgeContract.link(parentContext); - this.Contracts.L2BridgeContract.link(parentContext); - this.Contracts.NonceHolderContract.link(parentContext); + this.L1 = new Web3ZkSyncL1(parentContext); + + this.initContracts(); + + this.initWallet(); + } + + private initWallet() { + const self = this; + class ZKSyncWalletWithFullContext extends ZKSyncWallet { + constructor(privateKey: string) { + super(privateKey, self.L2, self.L1); + } + } + + this.ZkWallet = ZKSyncWalletWithFullContext; } /** @@ -124,12 +238,39 @@ export class ZkSyncPlugin extends Web3PluginBase { get rpc(): RpcMethods { if (!this._rpc) { this._rpc = new RpcMethods( - this.requestManager as unknown as Web3RequestManager, + this.L2.requestManager as unknown as Web3RequestManager, ); } return this._rpc; } + /** + * Update the providers + * @param contextL1 - The context for the L1 network + * @param contextL2 - The context for the L2 network + * + * @remarks This method is used to update the providers for the L1 and L2 networks. + * It is very important to call it if one of the providers is changed to a different network. + * For example, if the L1 or L2 providers were changed from testnet to mainnet, this method should be called. + */ + public updateProviders( + contextL1: + | Web3ZkSyncL1 + | web3Types.SupportedProviders + | Web3ContextInitOptions + | string, + contextL2: + | Web3ZkSyncL2 + | web3Types.SupportedProviders + | Web3ContextInitOptions + | string, + ) { + this.L1 = contextL1 instanceof Web3ZkSyncL1 ? contextL1 : new Web3ZkSyncL1(contextL1); + this.L2 = contextL2 instanceof Web3ZkSyncL2 ? contextL2 : new Web3ZkSyncL2(contextL2); + this.initContractsAddresses(); + this.initContracts(); + } + /** * Get L2 bridge contract instance * @param address - Contract address @@ -137,7 +278,7 @@ export class ZkSyncPlugin extends Web3PluginBase { getL2BridgeContract(address: Address): Contract { if (!this._l2BridgeContracts[address]) { this._l2BridgeContracts[address] = new Contract(IL2BridgeABI, address); - this._l2BridgeContracts[address].link(this); + this._l2BridgeContracts[address].link(this.L2); } return this._l2BridgeContracts[address]; } @@ -149,49 +290,25 @@ export class ZkSyncPlugin extends Web3PluginBase { erc20(address: string): Contract { if (!this._erc20Contracts[address]) { this._erc20Contracts[address] = new Contract(IERC20ABI, address); - this._erc20Contracts[address].link(this); + this._erc20Contracts[address].link(this.L2); } return this._erc20Contracts[address]; } - /** - * Get the default bridge addresses - */ - async getDefaultBridgeAddresses(): Promise<{ - erc20L1: Address; - erc20L2: Address; - wethL1: Address; - wethL2: Address; - }> { - if (!this.erc20BridgeL1) { - const addresses = await this.rpc.getBridgeContracts(); - this.erc20BridgeL1 = addresses.l1Erc20DefaultBridge; - this.erc20BridgeL2 = addresses.l2Erc20DefaultBridge; - this.wethBridgeL1 = addresses.l1WethBridge; - this.wethBridgeL2 = addresses.l2WethBridge; - } - return { - erc20L1: this.erc20BridgeL1, - erc20L2: this.erc20BridgeL2, - wethL1: this.wethBridgeL1, - wethL2: this.wethBridgeL2, - }; - } - /** * Get the L1 address of a token * @param token - The address of the token */ async getL1Address(token: Address): Promise
{ - if (token == ETH_ADDRESS) { - return ETH_ADDRESS; + if (token == constants.ETH_ADDRESS) { + return constants.ETH_ADDRESS; } else { - const bridgeAddresses = await this.getDefaultBridgeAddresses(); - if (bridgeAddresses.wethL2 !== ZERO_ADDRESS) { + const bridgeAddresses = await this.L2.getDefaultBridgeAddresses(); + if (bridgeAddresses.wethL2 !== constants.ZERO_ADDRESS) { const l2Bridge = this.getL2BridgeContract(bridgeAddresses.wethL2); try { const l1Token = await l2Bridge.methods.l1TokenAddress(token).call(); - if (l1Token !== ZERO_ADDRESS) { + if (l1Token !== constants.ZERO_ADDRESS) { return l1Token; } } catch (e) { @@ -211,15 +328,15 @@ export class ZkSyncPlugin extends Web3PluginBase { * @param token - The address of the token */ async getL2Address(token: Address): Promise { - if (token == ETH_ADDRESS) { - return ETH_ADDRESS; + if (token == constants.ETH_ADDRESS) { + return constants.ETH_ADDRESS; } else { - const bridgeAddresses = await this.getDefaultBridgeAddresses(); - if (bridgeAddresses.wethL2 !== ZERO_ADDRESS) { + const bridgeAddresses = await this.L2.getDefaultBridgeAddresses(); + if (bridgeAddresses.wethL2 !== constants.ZERO_ADDRESS) { const l2Bridge = this.getL2BridgeContract(bridgeAddresses.wethL2); try { const l2WethToken = await l2Bridge.methods.l2TokenAddress(token).call(); - if (l2WethToken !== ZERO_ADDRESS) { + if (l2WethToken !== constants.ZERO_ADDRESS) { return l2WethToken; } } catch (e) { diff --git a/src/rpc.methods.ts b/src/rpc.methods.ts index a98b6f9..1868b28 100644 --- a/src/rpc.methods.ts +++ b/src/rpc.methods.ts @@ -1,11 +1,7 @@ import type { Web3RequestManager } from 'web3-core'; import * as web3Utils from 'web3-utils'; import type * as web3Types from 'web3-types'; -import * as web3Accounts from 'web3-eth-accounts'; -import { - DEFAULT_RETURN_FORMAT, - // Web3BaseProvider -} from 'web3'; +import { DEFAULT_RETURN_FORMAT } from 'web3'; import type { DataFormat } from 'web3-types/src/data_format_types'; import type { BatchDetails, @@ -18,7 +14,7 @@ import type { TransactionDetails, WalletBalances, TransactionRequest, - TransactionOverrides, + Address, } from './types'; import { AddressSchema, @@ -35,12 +31,6 @@ import { TransactionDetailsSchema, UintSchema, } from './schemas'; -import { - ETH_ADDRESS_IN_CONTRACTS, - L2_BASE_TOKEN_ADDRESS, - REQUIRED_L1_TO_L2_GAS_PER_PUBDATA_LIMIT, -} from './constants'; -import { isAddressEq } from './utils'; // The ZkSync methods described here https://docs.zksync.io/build/api.html @@ -48,36 +38,8 @@ import { isAddressEq } from './utils'; export class RpcMethods { requestManager: Web3RequestManager; - protected _contractAddresses: { - bridgehubContract?: web3Types.Address; - mainContract?: web3Types.Address; - erc20BridgeL1?: web3Types.Address; - erc20BridgeL2?: web3Types.Address; - wethBridgeL1?: web3Types.Address; - wethBridgeL2?: web3Types.Address; - sharedBridgeL1?: web3Types.Address; - sharedBridgeL2?: web3Types.Address; - baseToken?: web3Types.Address; - }; - - protected contractAddresses(): { - bridgehubContract?: web3Types.Address; - mainContract?: web3Types.Address; - erc20BridgeL1?: web3Types.Address; - erc20BridgeL2?: web3Types.Address; - wethBridgeL1?: web3Types.Address; - wethBridgeL2?: web3Types.Address; - sharedBridgeL1?: web3Types.Address; - sharedBridgeL2?: web3Types.Address; - baseToken?: web3Types.Address; - } { - return this._contractAddresses; - } - constructor(requestManager: Web3RequestManager) { this.requestManager = requestManager; - - this._contractAddresses = {}; } private async _send(method: string, params: unknown[]): Promise { @@ -236,31 +198,13 @@ export class RpcMethods { /** * Returns the L1 base token address. */ - async getBaseTokenContractAddress(): Promise { - if (!this.contractAddresses().baseToken) { - this.contractAddresses().baseToken = (await this._send( - 'zks_getBaseTokenL1Address', - [], - )) as string; - } - return web3Utils.toChecksumAddress(this.contractAddresses().baseToken!); - } + async getBaseTokenL1Address(): Promise { + const baseTokenL1Address = (await this._send( + 'zks_getBaseTokenL1Address', + [], + )) as web3Types.Address; - /** - * Returns whether the chain is ETH-based. - */ - async isEthBasedChain(): Promise { - return isAddressEq(await this.getBaseTokenContractAddress(), ETH_ADDRESS_IN_CONTRACTS); - } - - /** - * Returns whether the `token` is the base token. - */ - async isBaseToken(token: web3Types.Address): Promise { - return ( - isAddressEq(token, await this.getBaseTokenContractAddress()) || - isAddressEq(token, L2_BASE_TOKEN_ADDRESS) - ); + return web3Utils.toChecksumAddress(baseTokenL1Address); } /** @@ -275,46 +219,6 @@ export class RpcMethods { return (await this._send('zks_getTestnetPaymaster', [])) as web3Types.Address | null; } - /** - * Returns the addresses of the default zkSync Era bridge contracts on both L1 and L2. - * - * Calls the {@link https://docs.zksync.io/build/api.html#zks-getbridgecontracts zks_getBridgeContracts} JSON-RPC method. - */ - async getDefaultBridgeAddresses(): Promise<{ - erc20L1: string; - erc20L2: string; - wethL1: string; - wethL2: string; - sharedL1: string; - sharedL2: string; - }> { - if (!this.contractAddresses().erc20BridgeL1) { - const addresses: { - l1Erc20DefaultBridge: string; - l2Erc20DefaultBridge: string; - l1WethBridge: string; - l2WethBridge: string; - l1SharedDefaultBridge: string; - l2SharedDefaultBridge: string; - } = (await this._send('zks_getBridgeContracts', [])) as any; - - this.contractAddresses().erc20BridgeL1 = addresses.l1Erc20DefaultBridge; - this.contractAddresses().erc20BridgeL2 = addresses.l2Erc20DefaultBridge; - this.contractAddresses().wethBridgeL1 = addresses.l1WethBridge; - this.contractAddresses().wethBridgeL2 = addresses.l2WethBridge; - this.contractAddresses().sharedBridgeL1 = addresses.l1SharedDefaultBridge; - this.contractAddresses().sharedBridgeL2 = addresses.l2SharedDefaultBridge; - } - return { - erc20L1: this.contractAddresses().erc20BridgeL1!, - erc20L2: this.contractAddresses().erc20BridgeL2!, - wethL1: this.contractAddresses().wethBridgeL1!, - wethL2: this.contractAddresses().wethBridgeL2!, - sharedL1: this.contractAddresses().sharedBridgeL1!, - sharedL2: this.contractAddresses().sharedBridgeL2!, - }; - } - /** * Returns an estimate of the gas required for a L1 to L2 transaction. * @@ -430,21 +334,6 @@ export class RpcMethods { return result; } - /** - * Returns the address of the testnet paymaster: the paymaster that is available on testnets and enables paying fees in ERC-20 compatible tokens. - * - * @param returnFormat - The format of the return value. - */ - public async getTestnetPaymaster( - returnFormat: DataFormat = DEFAULT_RETURN_FORMAT, - ): Promise { - return web3Utils.format( - AddressSchema, - await this._send('zks_getTestnetPaymaster', []), - returnFormat, - ) as web3Types.Address; - } - /** * Given a transaction hash, and an index of the L2 to L1 log produced within the transaction, it returns the proof for the corresponding L2 to L1 log. * @@ -490,53 +379,17 @@ export class RpcMethods { } /** - * Returns gas estimation for an L1 to L2 execute operation. + * Retrieves the bridge hub contract address * - * @param transaction The transaction details. - * @param transaction.contractAddress The address of the contract. - * @param transaction.calldata The transaction call data. - * @param [transaction.caller] The caller's address. - * @param [transaction.l2Value] The current L2 gas value. - * @param [transaction.factoryDeps] An array of bytes containing contract bytecode. - * @param [transaction.gasPerPubdataByte] The current gas per byte value. - * @param [transaction.overrides] Transaction overrides including `gasLimit`, `gasPrice`, and `value`. + * @param returnFormat - The format of the return value. */ - // TODO (EVM-3): support refundRecipient for fee estimation - async estimateL1ToL2Execute( - transaction: { - contractAddress: web3Types.Address; - calldata: string; - caller?: web3Types.Address; - l2Value?: web3Types.Numbers; - factoryDeps?: web3Types.Bytes[]; - gasPerPubdataByte?: web3Types.Numbers; - overrides?: TransactionOverrides; - }, + public async getBridgehubContractAddress( returnFormat: DataFormat = DEFAULT_RETURN_FORMAT, - ): Promise { - transaction.gasPerPubdataByte ??= REQUIRED_L1_TO_L2_GAS_PER_PUBDATA_LIMIT; - - // 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. - transaction.caller ??= web3Accounts.create().address; - - const customData = { - gasPerPubdata: transaction.gasPerPubdataByte, - }; - if (transaction.factoryDeps) { - Object.assign(customData, { factoryDeps: transaction.factoryDeps }); - } - - return await this.estimateGasL1ToL2( - { - from: transaction.caller, - data: transaction.calldata, - to: transaction.contractAddress, - value: transaction.l2Value ? web3Utils.toHex(transaction.l2Value) : undefined, - customData, - }, + ): Promise
{ + return web3Utils.format( + AddressSchema, + await this._send('zks_getBridgehubContract', []), returnFormat, - ); + ) as Address; } } diff --git a/src/schemas.ts b/src/schemas.ts index 118624d..8e2c4de 100644 --- a/src/schemas.ts +++ b/src/schemas.ts @@ -1,3 +1,5 @@ +import { transactionReceiptSchema } from 'web3-eth'; + export const AddressSchema = { format: 'address' }; export const IntSchema = { format: 'int' }; export const UintSchema = { format: 'uint' }; @@ -171,6 +173,8 @@ export const BridgeAddressesSchema = { l2Erc20DefaultBridge: { format: 'address' }, l1WethBridge: { format: 'address' }, l2WethBridge: { format: 'address' }, + l2SharedDefaultBridge: { format: 'address' }, + l1SharedDefaultBridge: { format: 'address' }, }, }; @@ -214,3 +218,42 @@ export const EstimateFeeSchema = { gas_per_pubdata_limit: { format: 'uint' }, }, }; + +export const ZKTransactionReceiptSchema = { + type: 'object', + properties: { + ...transactionReceiptSchema.properties, + l1BatchNumber: { format: 'uint' }, + l1BatchTxIndex: { format: 'uint' }, + logs: { + type: 'array', + items: { + type: 'object', + properties: { + ...transactionReceiptSchema.properties.logs.items.properties, + l1BatchNumber: { format: 'string' }, + }, + }, + }, + l2ToL1Logs: { + type: 'array', + + items: { + type: 'object', + properties: { + blockNumber: { format: 'uint' }, + blockHash: { format: 'string' }, + l1BatchNumber: { format: 'string' }, + transactionIndex: { format: 'uint' }, + shardId: { format: 'uint' }, + isService: { format: 'string' }, + sender: { format: 'address' }, + key: { format: 'string' }, + value: { format: 'bytes' }, + transactionHash: { format: 'string' }, + logIndex: { format: 'string' }, + }, + }, + }, + }, +}; diff --git a/src/types.ts b/src/types.ts index 243c60a..10f5dcf 100644 --- a/src/types.ts +++ b/src/types.ts @@ -1,8 +1,5 @@ // import { FMT_BYTES, FMT_NUMBER, TransactionReceipt, Web3Eth } from 'web3'; import type { FeeMarketEIP1559TxData } from 'web3-eth-accounts'; -// // TODO: // is it needed to be re-exported from web3 -// import { watchTransactionForConfirmations } from 'web3-eth/lib/types/utils/watch_transaction_for_confirmations.js'; - import type { Bytes, HexString, @@ -15,10 +12,14 @@ import type { import type { RpcMethods } from './rpc.methods'; +export type DeepWriteable = { -readonly [P in keyof T]: DeepWriteable }; + export type { Bytes, HexString, Numbers } from 'web3-types'; export interface TransactionOverrides extends Omit {} export const ZeroAddress: Address = '0x0000000000000000000000000000000000000000'; +export const ZeroHash: string = + '0x0000000000000000000000000000000000000000000000000000000000000000'; /** 0x-prefixed, hex encoded, ethereum account address. */ export type Address = string; @@ -521,24 +522,33 @@ export interface L2ToL1Log { * A `TransactionRequest` is an extension of {@link ethers.TransactionRequest} with additional features for interacting * with zkSync Era. */ -export declare type TransactionRequest = TransactionWithSenderAPI & { - /** The custom data for EIP712 transaction metadata. */ - customData?: null | Eip712Meta; -}; +export declare type TransactionRequest = DeepWriteable< + TransactionWithSenderAPI & { + /** The custom data for EIP712 transaction metadata. */ + customData?: null | Eip712Meta; + type?: TransactionWithSenderAPI['type'] & Numbers; + } +>; /** * Interface representation of priority op response that extends {@link ethers.TransactionResponse} and adds a function * that waits to commit a L1 transaction, including when given on optional confirmation number. */ -export interface PriorityOpResponse extends Transaction { +export interface PriorityL1OpResponse { /** * Waits for the L1 transaction to be committed, including waiting for the specified number of confirmations. * @param confirmation The number of confirmations to wait for. Defaults to 1. * @returns A promise that resolves to the transaction receipt once committed. */ waitL1Commit(confirmation?: number): Promise; + wait(confirmation?: number): Promise; + waitFinalize(confirmation?: number): Promise; } - +export interface PriorityL2OpResponse { + wait(confirmation?: number): Promise; + waitFinalize(confirmation?: number): Promise; +} +export type PriorityOpResponse = PriorityL1OpResponse | PriorityL2OpResponse; /** A map containing accounts and their balances. */ export type BalancesMap = { [key: string]: bigint }; @@ -803,6 +813,13 @@ export interface BridgeAddresses { l2Erc20DefaultBridge: Address; l1WethBridge: Address; l2WethBridge: Address; + l1SharedDefaultBridge: Address; + l2SharedDefaultBridge: Address; +} + +export interface ContractsAddresses extends BridgeAddresses { + mainContract: string; + bridgehubContractAddress: string; } export interface L2ToL1Proof { @@ -860,17 +877,22 @@ export interface TypedDataField { type: string; } -export type Eip712TxData = FeeMarketEIP1559TxData & { +export type Eip712TxData = Omit & { /** The custom data for EIP712 transaction metadata. */ customData?: null | Eip712Meta; from?: Address; hash?: string; signature?: string; + /** + * The transaction's gas price. To be used if maxPriorityFeePerGas and maxFeePerGas were not provided + */ + gasPrice?: Numbers | Uint8Array | null; }; + export type Eip712SignedInput = FeeMarketEIP1559TxData & { customData?: null | Eip712Meta; data: Bytes; - value: Bytes; + value: Numbers; nonce: Numbers; gasLimit: Numbers; maxFeePerGas: Numbers; @@ -883,3 +905,15 @@ export type Eip712SignedInput = FeeMarketEIP1559TxData & { paymasterInput: Bytes; [key: string]: unknown; }; + +export type ZKTransactionReceipt = TransactionReceipt & { + l1BatchNumber: Numbers; + l1BatchTxIndex: Numbers; + l2ToL1Logs: L2ToL1Log[]; + logs: TransactionReceipt['logs'] & { + l1BatchNumber: Numbers; + }; +}; + +export interface OverridesReadOnly extends Omit {} +export type Overrides = DeepWriteable; diff --git a/src/utils.ts b/src/utils.ts index 67af0d0..bcd8b5e 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -1,28 +1,29 @@ -// import { AbiCoder, BigNumberish, Bytes, ethers, SignatureLike } from 'ethers'; - import { sha256 } from 'ethereum-cryptography/sha256.js'; -// import { secp256k1 } from '@noble/curves/secp256k1'; -// import { keccak256 } from '@ethersproject/keccak256'; - import * as web3 from 'web3'; - import * as web3Utils from 'web3-utils'; import * as web3Accounts from 'web3-eth-accounts'; import * as web3Types from 'web3-types'; import * as web3Abi from 'web3-eth-abi'; -import * as web3Contract from 'web3-eth-contract'; -import type { Bytes } from 'web3-types'; +import type { + AbiEventFragment, + BlockNumberOrTag, + Bytes, + LogsInput, + TransactionHash, + TransactionReceipt, +} from 'web3-types'; import { toUint8Array } from 'web3-eth-accounts'; -import type { DeploymentInfo, EthereumSignature } from './types'; -import { - // PaymasterParams, - PriorityOpTree, - PriorityQueueType, - // Transaction, - // TransactionRequest, +import type { Web3Eth } from 'web3-eth'; +import { ALL_EVENTS_ABI, decodeEventABI } from 'web3-eth'; +import { keccak256, toBigInt } from 'web3-utils'; +import { encodeEventSignature, jsonInterfaceMethodToString } from 'web3-eth-abi'; +import { PriorityOpTree, PriorityQueueType } from './types'; +import type { + DeploymentInfo, + EthereumSignature, + PriorityL2OpResponse, + PriorityOpResponse, } from './types'; -// import { EIP712Signer } from './signer'; -// import { IERC20__factory } from './typechain'; import { IZkSyncABI } from './contracts/IZkSyncStateTransition'; import { IBridgehubABI } from './contracts/IBridgehub'; import { IContractDeployerABI } from './contracts/IContractDeployer'; @@ -47,13 +48,10 @@ import { // DEFAULT_GAS_PER_PUBDATA_LIMIT, } from './constants'; -import type { RpcMethods } from './rpc.methods'; +import type { Web3ZkSyncL2 } from './web3zksync-l2'; +import { Web3ZkSyncL1 } from './web3zksync-l1'; -export * from './Eip712'; - -// export * from './paymaster-utils'; -// export * from './smart-account-utils'; -// export { EIP712_TYPES } from './signer'; +export * from './Eip712'; // to be used instead of the one at zksync-ethers: Provider from ./provider /** * The web3.js Contract instance for the `ZkSync` interface. @@ -114,12 +112,17 @@ export const NonceHolderContract = new web3.Contract(INonceHolderABI); * consider adding the next few functions to web3.js: * */ -export const toBytes = (number: web3Types.Numbers | Uint8Array) => - web3Utils.hexToBytes( +export const evenHex = (hex: string) => { + return hex.length % 2 === 0 ? hex : `0x0${hex.slice(2)}`; +}; +export const toBytes = (number: web3Types.Numbers | Uint8Array) => { + const hex = typeof number === 'number' || typeof number === 'bigint' ? web3Utils.numberToHex(number) - : web3Utils.bytesToHex(number), - ); + : web3Utils.bytesToHex(number); + + return web3Utils.hexToBytes(evenHex(hex)); +}; export function concat(bytes: web3Types.Bytes[]): string { return '0x' + bytes.map(d => web3Utils.toHex(d).substring(2)).join(''); @@ -202,6 +205,7 @@ export class SignatureObject { this.v = BigInt(signature.v!); } } + static getNormalizedV(v: number): 27 | 28 { if (v === 0 || v === 27) { return 27; @@ -213,15 +217,19 @@ export class SignatureObject { // Otherwise, EIP-155 v means odd is 27 and even is 28 return v & 1 ? 27 : 28; } + concat(datas: ReadonlyArray): string { return '0x' + datas.map(d => web3Utils.toHex(d).substring(2)).join(''); } + get yParity(): 0 | 1 { return this.v === 27n ? 0 : 1; } + public get serialized(): string { return this.concat([this.r, this.s, this.yParity ? '0x1c' : '0x1b']); } + public toString() { return `${this.r}${this.s.slice(2)}${web3Utils.toHex(this.v).slice(2)}`; } @@ -515,6 +523,19 @@ export function hashBytecode(bytecode: web3Types.Bytes): Uint8Array { * * @example */ + +let ZkSyncABIEvents: Array | null = null; + +const getZkSyncEvents = () => { + if (ZkSyncABIEvents === null) { + ZkSyncABIEvents = IZkSyncABI.filter(e => e.type === 'event').map(e => ({ + ...e, + signature: encodeEventSignature(jsonInterfaceMethodToString(e)), + })); + } + return ZkSyncABIEvents; +}; + export function getL2HashFromPriorityOp( txReceipt: web3Types.TransactionReceipt, zkSyncAddress: web3.Address, @@ -526,14 +547,9 @@ export function getL2HashFromPriorityOp( } try { - // TODO: implement at web3.js Contract the parsing of the logs similar to new ethers.Interface(ABI).parseLog(...) - // @ts-ignore - const priorityQueueLog = ZkSyncMainContract.parseLog({ - topics: log.topics as string[], - data: log.data, - }); - if (priorityQueueLog && priorityQueueLog.args.txHash !== null) { - txHash = priorityQueueLog.args.txHash; + const decoded = decodeEventABI(ALL_EVENTS_ABI, log as LogsInput, getZkSyncEvents()); + if (decoded && decoded.returnValues && decoded.returnValues.txHash !== null) { + txHash = decoded.returnValues.txHash ? String(decoded.returnValues.txHash) : null; } } catch { // skip @@ -608,13 +624,12 @@ export function undoL1ToL2Alias(address: string): string { */ export async function getERC20DefaultBridgeData( l1TokenAddress: string, - context: web3.Web3Context, // or maybe use RpcMethods? + context: web3.Web3, // or maybe use RpcMethods? ): Promise { if (isAddressEq(l1TokenAddress, LEGACY_ETH_ADDRESS)) { l1TokenAddress = ETH_ADDRESS_IN_CONTRACTS; } - const token = new web3Contract.Contract(IERC20ABI, l1TokenAddress, context); - + const token = new context.eth.Contract(IERC20ABI, l1TokenAddress); const name = isAddressEq(l1TokenAddress, ETH_ADDRESS_IN_CONTRACTS) ? 'Ether' : await token.methods.name().call(); @@ -625,7 +640,10 @@ export async function getERC20DefaultBridgeData( ? 18 : await token.methods.decimals().call(); - return web3Abi.encodeParameters(['string', 'string', 'uint256'], [name, symbol, decimals]); + return web3Abi.encodeParameters( + ['string', 'string', 'uint256'], + [name, symbol, Number(decimals)], + ); } /** @@ -785,11 +803,11 @@ async function isSignatureCorrect( * const message = "Hello, world!"; * const signature = await new Wallet(PRIVATE_KEY).signMessage(message); * const isValidSignature = await utils.isMessageSignatureCorrect( - * web3, - * ADDRESS, - * message, - * signature, - * ); + * web3, + * ADDRESS, + * message, + * signature, + * ); * // isValidSignature = true */ export async function isMessageSignatureCorrect( @@ -874,8 +892,8 @@ export async function isTypedDataSignatureCorrect( * */ export async function estimateDefaultBridgeDepositL2Gas( - providerL1: web3.Web3Eth, - providerL2: RpcMethods, + providerL1: web3.Web3, + providerL2: Web3ZkSyncL2, token: web3.Address, amount: web3Types.Numbers, to: web3.Address, @@ -902,7 +920,7 @@ export async function estimateDefaultBridgeDepositL2Gas( const l2BridgeAddress = bridgeAddresses.sharedL2; const bridgeData = await getERC20DefaultBridgeData(token, providerL1); - return await estimateCustomBridgeDepositL2Gas( + return estimateCustomBridgeDepositL2Gas( providerL2, l1BridgeAddress, l2BridgeAddress, @@ -959,7 +977,7 @@ export function scaleGasLimit(gasLimit: bigint): bigint { * */ export async function estimateCustomBridgeDepositL2Gas( - providerL2: RpcMethods, + providerL2: Web3ZkSyncL2, l1BridgeAddress: web3.Address, l2BridgeAddress: web3.Address, token: web3.Address, @@ -971,7 +989,7 @@ export async function estimateCustomBridgeDepositL2Gas( l2Value?: web3Types.Numbers, ): Promise { const calldata = await getERC20BridgeCalldata(token, from, to, amount, bridgeData); - return await providerL2.estimateL1ToL2Execute({ + return providerL2.estimateL1ToL2Execute({ caller: applyL1ToL2Alias(l1BridgeAddress), contractAddress: l2BridgeAddress, gasPerPubdataByte: gasPerPubdataByte, @@ -1009,3 +1027,127 @@ export function toJSON(object: any): string { export function isAddressEq(a: web3.Address, b: web3.Address): boolean { return a.toLowerCase() === b.toLowerCase(); } + +export async function waitTxReceipt(web3Eth: Web3Eth, txHash: string): Promise { + while (true) { + try { + const receipt = await web3Eth.getTransactionReceipt(txHash); + if (receipt && receipt.blockNumber) { + return receipt; + } + } catch {} + await sleep(500); + } +} +export async function waitTxByHashConfirmation( + web3Eth: Web3Eth, + txHash: TransactionHash, + waitConfirmations = 1, +): Promise { + const receipt = await waitTxReceipt(web3Eth, txHash); + while (true) { + const blockNumber = await web3Eth.getBlockNumber(); + if (toBigInt(blockNumber) - toBigInt(receipt.blockNumber) + 1n >= waitConfirmations) { + return receipt; + } + await sleep(500); + } +} + +export const getPriorityOpResponse = ( + context: Web3ZkSyncL1 | Web3ZkSyncL2, + l1TxPromise: Promise, + contextL2?: Web3ZkSyncL2, +): PriorityOpResponse => { + if (context instanceof Web3ZkSyncL1) { + return getPriorityOpL1Response(context, l1TxPromise, contextL2); + } else { + return getPriorityOpL2Response(context, l1TxPromise); + } +}; + +export const getPriorityOpL1Response = ( + context: Web3ZkSyncL1, + l1TxPromise: Promise, + contextL2?: Web3ZkSyncL2, +): PriorityOpResponse => { + return { + waitL1Commit: async () => { + const hash = await l1TxPromise; + return waitTxByHashConfirmation(context.eth, hash, 1); + }, + wait: async () => { + const hash = await l1TxPromise; + return waitTxByHashConfirmation(context.eth, hash, 1); + }, + waitFinalize: async () => { + const hash = await l1TxPromise; + const receipt = await waitTxReceipt(context.eth, hash); + const l2TxHash = await (contextL2 as Web3ZkSyncL2).getL2TransactionFromPriorityOp( + receipt, + ); + + if (!contextL2) { + return { + transactionHash: l2TxHash, + } as TransactionReceipt; + } + + return await waitTxByHashConfirmationFinalized(contextL2.eth, l2TxHash, 1); + }, + }; +}; + +export const getPriorityOpL2Response = ( + context: Web3ZkSyncL2, + txPromise: Promise, +): PriorityL2OpResponse => { + return { + wait: async () => { + const hash = await txPromise; + return waitTxByHashConfirmation(context.eth, hash, 1); + }, + waitFinalize: async () => { + const hash = await txPromise; + + return await waitTxByHashConfirmationFinalized(context.eth, hash, 1, 'finalized'); + }, + }; +}; + +export async function waitTxByHashConfirmationFinalized( + web3Eth: Web3Eth, + txHash: TransactionHash, + waitConfirmations = 1, + blogTag?: BlockNumberOrTag, +): Promise { + const receipt = await waitTxReceipt(web3Eth, txHash); + while (true) { + const block = await web3Eth.getBlock(blogTag ?? 'latest'); + if (toBigInt(block.number) - toBigInt(receipt.blockNumber) + 1n >= waitConfirmations) { + return receipt; + } + await sleep(500); + // 3298012n // block.number + // 3303874n + } +} + +/** + * A simple hashing function which operates on UTF-8 strings to compute an 32-byte identifier. + * This simply computes the UTF-8 bytes and computes the [[keccak256]]. + * @param value + */ +export function id(value: string): string { + return keccak256(value); +} + +export function dataSlice(data: Bytes, start?: number, end?: number): string { + const bytes = toBytes(data); + if (end != null && end > bytes.length) { + throw new Error('cannot slice beyond data bounds'); + } + return web3Utils.toHex( + bytes.slice(start == null ? 0 : start, end == null ? bytes.length : end), + ); +} diff --git a/src/web3zksync-l1.ts b/src/web3zksync-l1.ts new file mode 100644 index 0000000..57f8516 --- /dev/null +++ b/src/web3zksync-l1.ts @@ -0,0 +1,6 @@ +import { Web3ZkSync } from './web3zksync'; + +// Equivalent to both L1Provider and L1Signer in sksync-ethers +export class Web3ZkSyncL1 extends Web3ZkSync { + // TODO: Add and possibly move some of the methods from `Web3ZkSync` class. +} diff --git a/src/web3zksync-l2.ts b/src/web3zksync-l2.ts new file mode 100644 index 0000000..d80f39f --- /dev/null +++ b/src/web3zksync-l2.ts @@ -0,0 +1,397 @@ +// import type { Web3ContextInitOptions } from 'web3-core'; +// import { Web3Eth } from 'web3-eth'; +// import * as web3Utils from 'web3-utils'; +// import type { Address, HexString } from 'web3'; + +import type { Block } from 'web3'; +import { DEFAULT_RETURN_FORMAT } from 'web3-types'; +import type { + BlockNumberOrTag, + Bytes, + DataFormat, + Numbers, + Transaction, + TransactionReceipt, +} from 'web3-types'; +import { format, toHex } from 'web3-utils'; +import { ethRpcMethods } from 'web3-rpc-methods'; +import { isNullish } from 'web3-validator'; +import { getL2HashFromPriorityOp, isAddressEq, isETH, sleep } from './utils'; +import { Network as ZkSyncNetwork, TransactionStatus } from './types'; +import type { Address, TransactionOverrides, PaymasterParams, ZKTransactionReceipt } from './types'; +import { Web3ZkSync } from './web3zksync'; +import { ZKTransactionReceiptSchema } from './schemas'; +import { Abi as IEthTokenAbi } from './contracts/IEthToken'; +import { + BOOTLOADER_FORMAL_ADDRESS, + EIP712_TX_TYPE, + ETH_ADDRESS_IN_CONTRACTS, + L2_BASE_TOKEN_ADDRESS, + LEGACY_ETH_ADDRESS, +} from './constants'; +import { IL2BridgeABI } from './contracts/IL2Bridge'; +import { IERC20ABI } from './contracts/IERC20'; + +// Equivalent to both Provider and Signer in zksync-ethers +export class Web3ZkSyncL2 extends Web3ZkSync { + // protected _contractAddresses: { + // mainContract?: Address; + // erc20BridgeL1?: Address; + // erc20BridgeL2?: Address; + // wethBridgeL1?: Address; + // wethBridgeL2?: Address; + // }; + + // override contractAddresses(): { + // mainContract?: Address; + // erc20BridgeL1?: Address; + // erc20BridgeL2?: Address; + // wethBridgeL1?: Address; + // wethBridgeL2?: Address; + // } { + // return this._contractAddresses; + // } + + // /** + // * Creates a new `Provider` instance for connecting to an L2 network. + // * Caching is disabled for local networks. + // * @param [url] The network RPC URL. Defaults to the local network. + // * @param [network] The network name, chain ID, or object with network details. + // * @param [options] Additional options for the provider. + // */ + // constructor(url?: ethers.FetchRequest | string, network?: Networkish, options?: any) { + // if (!url) { + // url = 'http://localhost:3050'; + // } + + // const isLocalNetwork = + // typeof url === 'string' + // ? url.includes('localhost') || url.includes('127.0.0.1') + // : url.url.includes('localhost') || url.url.includes('127.0.0.1'); + + // const optionsWithDisabledCache = isLocalNetwork ? { ...options, cacheTimeout: -1 } : options; + + // super(url, network, optionsWithDisabledCache); + // typeof url === 'string' + // ? (this.#connect = new FetchRequest(url)) + // : (this.#connect = url.clone()); + // this.pollingInterval = 500; + // this._contractAddresses = {}; + // } + + // override async _send( + // payload: JsonRpcPayload | Array, + // ): Promise> { + // const request = this._getConnection(); + // request.body = JSON.stringify(payload); + // request.setHeader('content-type', 'application/json'); + + // const response = await request.send(); + // response.assertOk(); + + // let resp = response.bodyJson; + // if (!Array.isArray(resp)) { + // resp = [resp]; + // } + + // return resp; + // } + + async getZKTransactionReceipt( + transactionHash: Bytes, + returnFormat: ReturnFormat = DEFAULT_RETURN_FORMAT as ReturnFormat, + ) { + const transactionHashFormatted = format( + { format: 'bytes32' }, + transactionHash, + DEFAULT_RETURN_FORMAT, + ); + const response = await ethRpcMethods.getTransactionReceipt( + this.requestManager, + transactionHashFormatted, + ); + + return isNullish(response) + ? response + : format( + ZKTransactionReceiptSchema, + response as unknown as ZKTransactionReceipt, + returnFormat ?? this.defaultReturnFormat, + ); + } + + async _getPriorityOpConfirmationL2ToL1Log(txHash: string, index = 0) { + const hash = toHex(txHash); + const receipt = await this.getZKTransactionReceipt(hash); + if (!receipt) { + throw new Error('Transaction is not mined!'); + } + const messages = Array.from(receipt.l2ToL1Logs.entries()).filter(([, log]) => + isAddressEq(log.sender, BOOTLOADER_FORMAL_ADDRESS), + ); + const [l2ToL1LogIndex, l2ToL1Log] = messages[index]; + + return { + l2ToL1LogIndex, + l2ToL1Log, + l1BatchTxId: receipt.l1BatchTxIndex, + }; + } + + /** + * Returns the transaction confirmation data that is part of `L2->L1` message. + * + * @param txHash The hash of the L2 transaction where the message was initiated. + * @param [index=0] In case there were multiple transactions in one message, you may pass an index of the + * transaction which confirmation data should be fetched. + * @throws {Error} If log proof can not be found. + */ + async getPriorityOpConfirmation(txHash: string, index = 0) { + const { l2ToL1LogIndex, l2ToL1Log, l1BatchTxId } = + await this._getPriorityOpConfirmationL2ToL1Log(txHash, index); + const proof = await this._rpc.getL2ToL1LogProof(txHash, l2ToL1LogIndex); + return { + l1BatchNumber: l2ToL1Log.l1BatchNumber, + l2MessageIndex: proof.id, + l2TxNumberInBlock: l1BatchTxId, + proof: proof.proof, + }; + } + + /** + * Returns a L2 transaction response from L1 transaction response. + * + * @param context + * @param txHash + */ + async getL2TransactionFromPriorityOp(receipt: TransactionReceipt) { + const l2Hash = getL2HashFromPriorityOp(receipt, await this.getMainContractAddress()); + + let status = null; + do { + status = await this.getTransactionStatus(l2Hash); + await sleep(this.transactionPollingInterval); + } while (status === TransactionStatus.NotFound); + + return l2Hash; + } + + /** + * Returns the status of a specified transaction. + * + * @param txHash The hash of the transaction. + */ + // This is inefficient. Status should probably be indicated in the transaction receipt. + async getTransactionStatus(txHash: string): Promise { + let tx; + try { + tx = await this.eth.getTransaction(txHash); + } catch {} + + if (!tx) { + return TransactionStatus.NotFound; + } + if (!tx.blockNumber) { + return TransactionStatus.Processing; + } + const verifiedBlock = (await this.eth.getBlock('finalized')) as Block; + if (tx.blockNumber <= verifiedBlock.number) { + return TransactionStatus.Finalized; + } + return TransactionStatus.Committed; + } + /** + * Returns the populated withdrawal transaction. + * + * @param transaction The transaction details. + * @param transaction.token The token address. + * @param transaction.amount The amount of token. + * @param [transaction.from] The sender's address. + * @param [transaction.to] The recipient's address. + * @param [transaction.bridgeAddress] The bridge address. + * @param [transaction.paymasterParams] Paymaster parameters. + * @param [transaction.overrides] Transaction overrides including `gasLimit`, `gasPrice`, and `value`. + */ + async getWithdrawTx(transaction: { + token: Address; + amount: Numbers; + from?: Address; + to?: Address; + bridgeAddress?: Address; + paymasterParams?: PaymasterParams; + overrides?: TransactionOverrides; + }) { + const { ...tx } = transaction; + const isEthBasedChain = await this.isEthBasedChain(); + + // In case of Ether on non Ether based chain it should get l2 Ether address, + // and in case of base token it should use L2_BASE_TOKEN_ADDRESS + if (isAddressEq(tx.token, LEGACY_ETH_ADDRESS) && !isEthBasedChain) { + tx.token = await this.l2TokenAddress(ETH_ADDRESS_IN_CONTRACTS); + } else if (await this.isBaseToken(tx.token)) { + tx.token = L2_BASE_TOKEN_ADDRESS; + } + + if ( + (tx.to === null || tx.to === undefined) && + (tx.from === null || tx.from === undefined) + ) { + throw new Error('Withdrawal target address is undefined!'); + } + + tx.to ??= tx.from; + tx.overrides ??= {} as TransactionOverrides; + tx.overrides.from ??= tx.from as Address; + + if (isETH(tx.token)) { + if (!tx.overrides?.value) { + tx.overrides.value = toHex(tx.amount); + } + const passedValue = BigInt(tx.overrides?.value ?? 0); + + if (passedValue !== BigInt(tx.amount)) { + // To avoid users shooting themselves into the foot, we will always use the amount to withdraw + // as the value + + throw new Error('The tx.value is not equal to the value withdrawn!'); + } + + const ethL2Token = new this.eth.Contract(IEthTokenAbi, L2_BASE_TOKEN_ADDRESS); + const populatedTx = ethL2Token.methods + .withdraw(tx.to) + // @ts-ignore + .populateTransaction(tx.overrides); + if (tx.paymasterParams) { + return { + ...populatedTx, + customData: { + paymasterParams: tx.paymasterParams, + }, + }; + } + return populatedTx; + } + + if (!tx.bridgeAddress) { + const bridgeAddresses = await this.getDefaultBridgeAddresses(); + tx.bridgeAddress = bridgeAddresses.sharedL2; + } + const bridge = new this.eth.Contract(IL2BridgeABI, tx.bridgeAddress); + + const populatedTx = bridge.methods + .withdraw(tx.to, tx.token, tx.amount) + // @ts-ignore + .populateTransaction(tx.overrides); + + if (tx.paymasterParams) { + return { + ...populatedTx, + customData: { + paymasterParams: tx.paymasterParams, + }, + }; + } + return populatedTx; + } + + /** + * Returns the populated transfer transaction. + * + * @param transaction Transfer transaction request. + * @param transaction.to The address of the recipient. + * @param transaction.amount The amount of the token to transfer. + * @param [transaction.token] The address of the token. Defaults to ETH. + * @param [transaction.paymasterParams] Paymaster parameters. + * @param [transaction.overrides] Transaction's overrides which may be used to pass L2 `gasLimit`, `gasPrice`, `value`, etc. + */ + async getTransferTx(transaction: { + to: Address; + amount: Numbers; + from?: Address; + token?: Address; + paymasterParams?: PaymasterParams; + overrides?: TransactionOverrides; + }) { + const { ...tx } = transaction; + const isEthBasedChain = await this.isEthBasedChain(); + + // In case of Ether on non Ether based chain it should get l2 Ether address, + // and in case of base token it should use L2_BASE_TOKEN_ADDRESS + if (tx.token && isAddressEq(tx.token, LEGACY_ETH_ADDRESS) && !isEthBasedChain) { + tx.token = await this.l2TokenAddress(ETH_ADDRESS_IN_CONTRACTS); + } else if (!tx.token || (await this.isBaseToken(tx.token))) { + tx.token = L2_BASE_TOKEN_ADDRESS; + } + + tx.overrides ??= {} as TransactionOverrides; + tx.overrides.from ??= tx.from as Address; + + if (isETH(tx.token)) { + if (tx.paymasterParams) { + return { + ...tx.overrides, + type: EIP712_TX_TYPE, + to: tx.to, + value: tx.amount, + customData: { + paymasterParams: tx.paymasterParams, + } as Transaction, + }; + } + + return { + ...tx.overrides, + to: tx.to, + value: tx.amount, + } as Transaction; + } else { + const token = new this.eth.Contract(IERC20ABI, tx.token); + const populatedTx = token.methods + .transfer(tx.to, tx.amount) + // @ts-ignore + .populateTransaction(tx.overrides); + + if (tx.paymasterParams) { + return { + ...populatedTx, + customData: { + paymasterParams: tx.paymasterParams, + }, + }; + } + return populatedTx as Transaction; + } + } + + /** + * Creates a new `Provider` from provided URL or network name. + * + * @param zksyncNetwork The type of zkSync network. + * + * @example + * + * import { initWithDefaultProvider, types } from "web3-plugin-zksync"; + * + * const provider = ZkSyncNetwork.initWithDefaultProvider(types.Network.Sepolia); + */ + static initWithDefaultProvider( + zksyncNetwork: ZkSyncNetwork = ZkSyncNetwork.Localhost, + ): Web3ZkSyncL2 { + switch (zksyncNetwork) { + case ZkSyncNetwork.Localhost: + return new Web3ZkSyncL2('http://localhost:3050'); + case ZkSyncNetwork.Sepolia: + return new Web3ZkSyncL2('https://sepolia.era.zksync.dev'); + case ZkSyncNetwork.Mainnet: + return new Web3ZkSyncL2('https://mainnet.era.zksync.io'); + case ZkSyncNetwork.EraTestNode: + return new Web3ZkSyncL2('http://localhost:8011'); + default: + return new Web3ZkSyncL2('http://localhost:3050'); + } + } + + getBalance(address: Address, blockNumber: BlockNumberOrTag = this.defaultBlock) { + return this.eth.getBalance(address, blockNumber); + } +} diff --git a/src/web3zksync.ts b/src/web3zksync.ts new file mode 100644 index 0000000..7c0b471 --- /dev/null +++ b/src/web3zksync.ts @@ -0,0 +1,630 @@ +import type { Web3ContextInitOptions } from 'web3-core'; +import * as web3Utils from 'web3-utils'; +import type * as web3Types from 'web3-types'; +import * as web3Accounts from 'web3-eth-accounts'; +import { DEFAULT_RETURN_FORMAT } from 'web3'; +import { estimateGas, getGasPrice, transactionBuilder, transactionSchema } from 'web3-eth'; +import * as Web3 from 'web3'; +import type { Transaction } from 'web3-types'; +import { toHex } from 'web3-utils'; +import { ethRpcMethods } from 'web3-rpc-methods'; +import type { + BatchDetails, + BlockDetails, + BridgeAddresses, + EstimateFee, + L2ToL1Proof, + StorageProof, + RawBlockTransaction, + TransactionDetails, + WalletBalances, + TransactionRequest, + Address, + TransactionOverrides, + Eip712TxData, + Eip712Meta, +} from './types'; +import { + DEFAULT_GAS_PER_PUBDATA_LIMIT, + EIP712_TX_TYPE, + ETH_ADDRESS_IN_CONTRACTS, + L2_BASE_TOKEN_ADDRESS, + LEGACY_ETH_ADDRESS, + REQUIRED_L1_TO_L2_GAS_PER_PUBDATA_LIMIT, +} from './constants'; +import { EIP712, type EIP712Signer, isAddressEq } from './utils'; +import { RpcMethods } from './rpc.methods'; +import { IL2BridgeABI } from './contracts/IL2Bridge'; +import { IERC20ABI } from './contracts/IERC20'; + +/** + * The base class for interacting with zkSync Era. + * It extends the `Web3Eth` class and provides additional methods for interacting with zkSync Era. + * It is the base class for the `Web3ZkSyncL1` and `Web3ZkSyncL2`. + */ +// Note: Code logic here is similar to JsonRpcApiProvider class in zksync-ethers +export class Web3ZkSync extends Web3.Web3 { + protected _rpc: RpcMethods; + + protected _contractAddresses: { + bridgehubContract?: web3Types.Address; + mainContract?: web3Types.Address; + erc20BridgeL1?: web3Types.Address; + erc20BridgeL2?: web3Types.Address; + wethBridgeL1?: web3Types.Address; + wethBridgeL2?: web3Types.Address; + sharedBridgeL1?: web3Types.Address; + sharedBridgeL2?: web3Types.Address; + baseToken?: web3Types.Address; + }; + + protected contractAddresses(): { + bridgehubContract?: web3Types.Address; + mainContract?: web3Types.Address; + erc20BridgeL1?: web3Types.Address; + erc20BridgeL2?: web3Types.Address; + wethBridgeL1?: web3Types.Address; + wethBridgeL2?: web3Types.Address; + sharedBridgeL1?: web3Types.Address; + sharedBridgeL2?: web3Types.Address; + baseToken?: web3Types.Address; + } { + return this._contractAddresses; + } + + constructor( + providerOrContext?: web3Types.SupportedProviders | Web3ContextInitOptions | string, + ) { + // @ts-ignore + super(providerOrContext as web3Types.SupportedProviders); + // @ts-ignore + this._rpc = new RpcMethods(this.requestManager); + + this._contractAddresses = {}; + } + + /** + * Returns the chain id of the underlying L1. + * + * @param returnFormat - The format of the return value. + */ + public async l1ChainId( + returnFormat: web3Types.DataFormat = DEFAULT_RETURN_FORMAT, + ): Promise { + return this._rpc.l1ChainId(returnFormat); + } + async _eip712Signer(): Promise { + throw new Error('Must be implemented by the derived class!'); + } + /** + * Returns the latest L1 batch number. + * + * @param returnFormat - The format of the return value. + */ + public async getL1BatchNumber( + returnFormat: web3Types.DataFormat = DEFAULT_RETURN_FORMAT, + ): Promise { + return this._rpc.getL1BatchNumber(returnFormat); + } + + /** + * Returns data pertaining to a given batch. + * + * @param number - The layer 1 batch number. + * @param returnFormat - The format of the return value. + */ + public async getL1BatchDetails( + number: web3Types.Numbers, + returnFormat: web3Types.DataFormat = DEFAULT_RETURN_FORMAT, + ): Promise { + return this._rpc.getL1BatchDetails(number, returnFormat); + } + + /** + * Returns additional zkSync-specific information about the L2 block. + * + * committed: The batch is closed and the state transition it creates exists on layer 1. + * proven: The batch proof has been created, submitted, and accepted on layer 1. + * executed: The batch state transition has been executed on L1; meaning the root state has been updated. + * + * @param number - The number of the block. + * @param returnFormat - The format of the return value. + */ + public async getBlockDetails( + number: web3Types.Numbers, + returnFormat: web3Types.DataFormat = DEFAULT_RETURN_FORMAT, + ): Promise { + return this._rpc.getBlockDetails(number, returnFormat); + } + + /** + * Returns data from a specific transaction given by the transaction hash. + * + * @param txHash - Transaction hash as string. + * @param returnFormat - The format of the return value. + */ + public async getTransactionDetails( + txHash: web3Types.Bytes, + returnFormat: web3Types.DataFormat = DEFAULT_RETURN_FORMAT, + ): Promise { + return this._rpc.getTransactionDetails(txHash, returnFormat); + } + + /** + * Returns bytecode of a transaction given by its hash. + * + * @param bytecodeHash - Bytecode hash as string. + * @param returnFormat - The format of the return value. + */ + public async getBytecodeByHash( + bytecodeHash: web3Types.Bytes, + returnFormat: web3Types.DataFormat = DEFAULT_RETURN_FORMAT, + ): Promise { + return this._rpc.getBytecodeByHash(bytecodeHash, returnFormat); + } + + /** + * Returns data of transactions in a block. + * + * @param number - Block number. + * @param returnFormat - The format of the return value. + */ + public async getRawBlockTransactions( + number: web3Types.Numbers, + returnFormat: web3Types.DataFormat = DEFAULT_RETURN_FORMAT, + ): Promise { + return this._rpc.getRawBlockTransactions(number, returnFormat); + } + + /** + * Returns the fee for the transaction. + * + * @param transaction - Transaction object. + * @param returnFormat - The format of the return value. + */ + public async estimateFee( + transaction: Partial, + returnFormat: web3Types.DataFormat = DEFAULT_RETURN_FORMAT, + ): Promise { + return this._rpc.estimateFee(transaction, returnFormat); + } + + /** + * Returns the L1 base token address. + */ + async getBaseTokenContractAddress(): Promise { + if (!this.contractAddresses().baseToken) { + this.contractAddresses().baseToken = await this._rpc.getBaseTokenL1Address(); + } + return this.contractAddresses().baseToken!; + } + + /** + * Returns whether the chain is ETH-based. + */ + async isEthBasedChain(): Promise { + return isAddressEq(await this.getBaseTokenContractAddress(), ETH_ADDRESS_IN_CONTRACTS); + } + + /** + * Returns whether the `token` is the base token. + */ + async isBaseToken(token: web3Types.Address): Promise { + return ( + isAddressEq(token, await this.getBaseTokenContractAddress()) || + isAddressEq(token, L2_BASE_TOKEN_ADDRESS) + ); + } + + /** + * Returns the testnet {@link https://docs.zksync.io/build/developer-reference/account-abstraction.html#paymasters paymaster address} + * if available, or `null`. + * + * Calls the {@link https://docs.zksync.io/build/api.html#zks-gettestnetpaymaster zks_getTestnetPaymaster} JSON-RPC method. + */ + async getTestnetPaymasterAddress(): Promise { + // Unlike contract's addresses, the testnet paymaster is not cached, since it can be trivially changed + // on the fly by the server and should not be relied on to be constant + return this._rpc.getTestnetPaymasterAddress(); + } + + /** + * Returns the addresses of the default zkSync Era bridge contracts on both L1 and L2. + * + * Calls the {@link https://docs.zksync.io/build/api.html#zks-getbridgecontracts zks_getBridgeContracts} JSON-RPC method. + */ + async getDefaultBridgeAddresses(): Promise<{ + erc20L1: string; + erc20L2: string; + wethL1: string; + wethL2: string; + sharedL1: string; + sharedL2: string; + }> { + if (!this.contractAddresses().erc20BridgeL1) { + const addresses = await this._rpc.getBridgeContracts(); + + this.contractAddresses().erc20BridgeL1 = addresses.l1Erc20DefaultBridge; + this.contractAddresses().erc20BridgeL2 = addresses.l2Erc20DefaultBridge; + this.contractAddresses().wethBridgeL1 = addresses.l1WethBridge; + this.contractAddresses().wethBridgeL2 = addresses.l2WethBridge; + this.contractAddresses().sharedBridgeL1 = addresses.l1SharedDefaultBridge; + this.contractAddresses().sharedBridgeL2 = addresses.l2SharedDefaultBridge; + } + return { + erc20L1: this.contractAddresses().erc20BridgeL1!, + erc20L2: this.contractAddresses().erc20BridgeL2!, + wethL1: this.contractAddresses().wethBridgeL1!, + wethL2: this.contractAddresses().wethBridgeL2!, + sharedL1: this.contractAddresses().sharedBridgeL1!, + sharedL2: this.contractAddresses().sharedBridgeL2!, + }; + } + + /** + * Returns an estimate of the gas required for a L1 to L2 transaction. + * + * @param transaction - Transaction object. + * @param returnFormat - The format of the return value. + */ + public async estimateGasL1ToL2( + transaction: Partial, + returnFormat: web3Types.DataFormat = DEFAULT_RETURN_FORMAT, + ): Promise { + return this._rpc.estimateGasL1ToL2(transaction, returnFormat); + } + + /** + * Returns all balances for confirmed tokens given by an account address. + * + * @param address - The account address. + * @param returnFormat - The format of the return value. + */ + public async getAllAccountBalances( + address: web3Types.Address, + returnFormat: web3Types.DataFormat = DEFAULT_RETURN_FORMAT, + ): Promise { + return this._rpc.getAllAccountBalances(address, returnFormat); + } + + /** + * Returns the address of the zkSync Era contract. + * + * @param returnFormat - The format of the return value. + */ + public async getMainContract( + returnFormat: web3Types.DataFormat = DEFAULT_RETURN_FORMAT, + ): Promise { + return this._rpc.getMainContract(returnFormat); + } + + /** + * Returns the range of blocks contained within a batch given by batch number. + * The range is given by beginning/end block numbers in hexadecimal. + * + * @param number The layer 1 batch number. + * @param returnFormat - The format of the return value. + */ + public async getL1BatchBlockRange( + number: web3Types.Numbers, + returnFormat: web3Types.DataFormat = DEFAULT_RETURN_FORMAT, + ): Promise { + return this._rpc.getL1BatchBlockRange(number, returnFormat); + } + + /** + * Returns Merkle proofs for one or more storage values at the specified account along with a Merkle proof of their authenticity. This allows to verify that the values have not been tampered with. + * More details: https://docs.zksync.io/build/api.html#zks-getproof + * + * @param address - The account to fetch storage values and proofs for. + * @param keys - Vector of storage keys in the account. + * @param l1BatchNumber - Number of the L1 batch specifying the point in time at which the requested values are returned. + * @param returnFormat - The format of the return value. + */ + // @ts-ignore + public async getProof( + address: web3Types.Address, + keys: string[], + l1BatchNumber: web3Types.Numbers, + returnFormat: web3Types.DataFormat = DEFAULT_RETURN_FORMAT, + ): Promise { + return this._rpc.getProof(address, keys, l1BatchNumber, returnFormat); + } + + /** + * Given a transaction hash, and an index of the L2 to L1 log produced within the transaction, it returns the proof for the corresponding L2 to L1 log. + * + * The index of the log that can be obtained from the transaction receipt (it includes a list of every log produced by the transaction) + * + * @param txHash - Hash of the L2 transaction the L2 to L1 log was produced within. + * @param l2ToL1LogIndex - The index of the L2 to L1 log in the transaction (optional). + * @param returnFormat - The format of the return value. + */ + public async getL2ToL1LogProof( + txHash: web3Types.HexString32Bytes, + l2ToL1LogIndex?: web3Types.Numbers, + returnFormat: web3Types.DataFormat = DEFAULT_RETURN_FORMAT, + ): Promise { + return this._rpc.getL2ToL1LogProof(txHash, l2ToL1LogIndex, returnFormat); + } + + /** + * Returns L1/L2 addresses of default bridges. + * + * @param returnFormat - The format of the return value. + */ + public async getBridgeContracts( + returnFormat: web3Types.DataFormat = DEFAULT_RETURN_FORMAT, + ): Promise { + return this._rpc.getBridgeContracts(returnFormat); + } + + /** + * Returns the address of the BridgeHub contract. + * + * @param returnFormat - The format of the return value. + */ + public async getBridgehubContractAddress( + returnFormat: web3Types.DataFormat = DEFAULT_RETURN_FORMAT, + ): Promise
{ + if (!this.contractAddresses().bridgehubContract) { + this.contractAddresses().bridgehubContract = + await this._rpc.getBridgehubContractAddress(returnFormat); + } + return this.contractAddresses().bridgehubContract!; + } + + /** + * Returns gas estimation for an L1 to L2 execute operation. + * + * @param transaction The transaction details. + * @param transaction.contractAddress The address of the contract. + * @param transaction.calldata The transaction call data. + * @param [transaction.caller] The caller's address. + * @param [transaction.l2Value] The current L2 gas value. + * @param [transaction.factoryDeps] An array of bytes containing contract bytecode. + * @param [transaction.gasPerPubdataByte] The current gas per byte value. + * @param [transaction.overrides] Transaction overrides including `gasLimit`, `gasPrice`, and `value`. + */ + // TODO (EVM-3): support refundRecipient for fee estimation + async estimateL1ToL2Execute( + transaction: { + contractAddress: web3Types.Address; + calldata: string; + caller?: web3Types.Address; + l2Value?: web3Types.Numbers; + factoryDeps?: web3Types.Bytes[]; + gasPerPubdataByte?: web3Types.Numbers; + overrides?: TransactionOverrides; + }, + returnFormat: web3Types.DataFormat = DEFAULT_RETURN_FORMAT, + ): Promise { + transaction.gasPerPubdataByte ??= REQUIRED_L1_TO_L2_GAS_PER_PUBDATA_LIMIT; + + // 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. + transaction.caller ??= web3Accounts.create().address; + + const customData = { + gasPerPubdata: transaction.gasPerPubdataByte, + }; + if (transaction.factoryDeps) { + Object.assign(customData, { factoryDeps: transaction.factoryDeps }); + } + + return this.estimateGasL1ToL2( + { + from: transaction.caller, + data: transaction.calldata, + to: transaction.contractAddress, + value: transaction.l2Value ? web3Utils.toHex(transaction.l2Value) : undefined, + customData, + }, + returnFormat, + ); + } + + /** + * Returns the L2 token address equivalent for a L1 token address as they are not equal. + * ETH address is set to zero address. + * + * @remarks Only works for tokens bridged on default zkSync Era bridges. + * + * @param token The address of the token on L1. + */ + async l2TokenAddress(token: Address): Promise { + if (isAddressEq(token, LEGACY_ETH_ADDRESS)) { + token = ETH_ADDRESS_IN_CONTRACTS; + } + + const baseToken = await this.getBaseTokenContractAddress(); + if (isAddressEq(token, baseToken)) { + return L2_BASE_TOKEN_ADDRESS; + } + + const bridgeAddresses = await this.getDefaultBridgeAddresses(); + + const contract = new Web3.Contract(IL2BridgeABI, bridgeAddresses.sharedL2); + contract.setProvider(this.provider); + return await contract.methods.l2TokenAddress(token).call(); + } + + /** + * Returns the main zkSync Era smart contract address. + * + * Calls the {@link https://docs.zksync.io/build/api.html#zks-getmaincontract zks_getMainContract} JSON-RPC method. + */ + async getMainContractAddress(): Promise
{ + if (!this.contractAddresses().mainContract) { + this.contractAddresses().mainContract = await this._rpc.getMainContract(); + } + return this.contractAddresses().mainContract!; + } + // /** + // * Returns `tx` as a normalized JSON-RPC transaction request, which has all values `hexlified` and any numeric + // * values converted to Quantity values. + // * @param tx The transaction request that should be normalized. + // */ + // override getRpcTransaction(tx: TransactionRequest): JsonRpcTransactionRequest { + // const result: any = super.getRpcTransaction(tx); + // if (!tx.customData) { + // return result; + // } + // result.type = ethers.toBeHex(EIP712_TX_TYPE); + // result.eip712Meta = { + // gasPerPubdata: ethers.toBeHex(tx.customData.gasPerPubdata ?? 0), + // } as any; + // if (tx.customData.factoryDeps) { + // result.eip712Meta.factoryDeps = tx.customData.factoryDeps.map((dep: ethers.BytesLike) => + // // TODO (SMA-1605): we arraify instead of hexlifying because server expects Vec. + // // We should change deserialization there. + // Array.from(ethers.getBytes(dep)), + // ); + // } + // if (tx.customData.paymasterParams) { + // result.eip712Meta.paymasterParams = { + // paymaster: ethers.hexlify(tx.customData.paymasterParams.paymaster), + // paymasterInput: Array.from(ethers.getBytes(tx.customData.paymasterParams.paymasterInput)), + // }; + // } + // return result; + // } + async getTokenBalance(token: Address, walletAddress: Address): Promise { + const erc20 = new this.eth.Contract(IERC20ABI, token); + + return await erc20.methods.balanceOf(walletAddress).call(); + } + + fillCustomData(data: Eip712Meta): Eip712Meta { + const customData = { ...data }; + customData.gasPerPubdata ??= DEFAULT_GAS_PER_PUBDATA_LIMIT; + customData.factoryDeps ??= []; + return customData; + } + + private async populateTransactionAndGasPrice(transaction: Transaction): Promise { + transaction.nonce = + transaction.nonce ?? (await this.eth.getTransactionCount(transaction.from!, 'pending')); + + const populated = await transactionBuilder({ + transaction, + web3Context: this, + }); + + const formatted = web3Utils.format(transactionSchema, populated); + + delete formatted.input; + delete formatted.chain; + delete formatted.hardfork; + delete formatted.networkId; + if ( + formatted.accessList && + Array.isArray(formatted.accessList) && + formatted.accessList.length === 0 + ) { + delete formatted.accessList; + } + + formatted.gasLimit = + formatted.gasLimit ?? + (await estimateGas(this, formatted as Transaction, 'latest', DEFAULT_RETURN_FORMAT)); + + if (formatted.type === 0n) { + formatted.gasPrice = + formatted.gasPrice ?? (await getGasPrice(this, DEFAULT_RETURN_FORMAT)); + return formatted; + } + if (formatted.type === 2n && formatted.gasPrice) { + formatted.maxFeePerGas = formatted.maxFeePerGas ?? formatted.gasPrice; + formatted.maxPriorityFeePerGas = formatted.maxPriorityFeePerGas ?? formatted.gasPrice; + return formatted; + } + if (formatted.maxPriorityFeePerGas && formatted.maxFeePerGas) { + return formatted; + } + + const gasFees = await this.eth.calculateFeeData(); + if (gasFees.maxFeePerGas && gasFees.maxPriorityFeePerGas) { + formatted.maxFeePerGas = + formatted.maxFeePerGas ?? web3Utils.toBigInt(gasFees.maxFeePerGas); + formatted.maxPriorityFeePerGas = + formatted.maxPriorityFeePerGas ?? + (web3Utils.toBigInt(formatted.maxFeePerGas) > + web3Utils.toBigInt(gasFees.maxPriorityFeePerGas) + ? formatted.maxFeePerGas + : gasFees.maxPriorityFeePerGas); + } else { + formatted.maxFeePerGas = formatted.maxFeePerGas ?? formatted.gasPrice; + formatted.maxPriorityFeePerGas = formatted.maxPriorityFeePerGas ?? formatted.gasPrice; + } + + return formatted; + } + + async populateTransaction(transaction: Transaction) { + if ( + (!transaction.type || + (transaction.type && toHex(transaction.type) !== toHex(EIP712_TX_TYPE))) && + !(transaction as Eip712TxData).customData + ) { + return this.populateTransactionAndGasPrice(transaction); + } + const populated = (await this.populateTransactionAndGasPrice(transaction)) as Eip712TxData; + populated.type = BigInt(EIP712_TX_TYPE); + populated.value ??= 0; + populated.data ??= '0x'; + + populated.customData = this.fillCustomData( + (transaction as Eip712TxData).customData as Eip712Meta, + ); + return populated; + } + + async signTransaction(tx: Transaction): Promise { + if (tx.type && toHex(tx.type) === toHex(EIP712_TX_TYPE)) { + const data = EIP712.getSignInput(tx as Eip712TxData); + // @ts-ignore + delete data.txType; + data.type = EIP712_TX_TYPE; + const signer = await this._eip712Signer(); + data.chainId = signer.getDomain().chainId; + data.customData = { + customSignature: signer.sign(data)?.serialized, + }; + return EIP712.serialize(data); + } + + const account = this.eth.accounts.wallet.get(tx.from!); + + if (!account) { + throw new Error('Account not found'); + } + + const res = await this.eth.accounts.signTransaction(tx, account?.privateKey); + return res.rawTransaction; + } + async sendRawTransaction(signedTx: string) { + // tx.chainId = await this._contextL2().eth.getChainId(); + // const { gasPrice, ...other } = await this._contextL2().eth.calculateFeeData(); + // const data = { ...tx, ...other }; + // @ts-ignore + // return this._contextL2().eth.sendTransaction(data); + + // + // const populated = await this.populateTransaction(tx); + + // const signedTx = await this.signTransaction(populated); + + // data.nonce = await this._contextL2().eth.getTransactionCount(this.getAddress(), 'pending'); + // const { gasPrice, ...other } = await this._contextL2().eth.calculateFeeData(); + // const dataWithGas = { ...data, ...other, gasLimit: 5000000n }; + // + // dataWithGas.customData = { + // customSignature: signer.sign(dataWithGas)?.serialized, + // }; + // + // const serialized = EIP712.serialize(dataWithGas); + return ethRpcMethods.sendRawTransaction(this.requestManager, signedTx); + } +} diff --git a/src/zksync-wallet.ts b/src/zksync-wallet.ts new file mode 100644 index 0000000..4c781a3 --- /dev/null +++ b/src/zksync-wallet.ts @@ -0,0 +1,219 @@ +import type { Web3Account } from 'web3-eth-accounts'; +import { privateKeyToAccount, create as createAccount } from 'web3-eth-accounts'; +import type * as web3Types from 'web3-types'; +import type { Transaction } from 'web3-types'; +import type { Web3ZkSyncL2 } from './web3zksync-l2'; +import type { Web3ZkSyncL1 } from './web3zksync-l1'; +import * as utils from './utils'; +import { AdapterL1, AdapterL2 } from './adapters'; +import type { Address, Eip712TxData, PaymasterParams, TransactionOverrides } from './types'; +import type { EIP712Signer } from './utils'; +import { getPriorityOpResponse, isAddressEq } from './utils'; + +class Adapters extends AdapterL1 { + adapterL2: AdapterL2; + constructor() { + super(); + this.adapterL2 = new AdapterL2(); + this.adapterL2.getAddress = this.getAddress.bind(this); + this.adapterL2._contextL2 = this._contextL2.bind(this); + this.adapterL2._eip712Signer = this._eip712Signer; + } + getBalance(token?: Address, blockTag: web3Types.BlockNumberOrTag = 'committed') { + return this.adapterL2.getBalance(token, blockTag); + } + getAllBalances() { + return this.adapterL2.getAllBalances(); + } + + async populateTransaction( + tx: web3Types.Transaction, + ): Promise { + return this.adapterL2.populateTransaction(tx); + } + + getDeploymentNonce() { + return this.adapterL2.getDeploymentNonce(); + } + getL2BridgeContracts() { + return this.adapterL2.getL2BridgeContracts(); + } + protected async _eip712Signer(): Promise { + throw new Error('Must be implemented by the derived class!'); + } + + withdraw(transaction: { + token: Address; + amount: web3Types.Numbers; + to?: Address; + bridgeAddress?: Address; + paymasterParams?: PaymasterParams; + overrides?: TransactionOverrides; + }) { + return this.adapterL2.withdraw(transaction); + } + async transfer(transaction: { + to: Address; + amount: web3Types.Numbers; + token?: Address; + paymasterParams?: PaymasterParams; + overrides?: TransactionOverrides; + }) { + return this.signAndSend(await this.adapterL2.transfer(transaction), this._contextL2()); + } +} + +export class ZKSyncWallet extends Adapters { + provider?: Web3ZkSyncL2; + providerL1?: Web3ZkSyncL1; + protected eip712!: utils.EIP712Signer; + public account: Web3Account; + + /** + * + * @param privateKey The private key of the account. + * @param providerL2 The provider instance for connecting to a L2 network. + * @param providerL1 The provider instance for connecting to a L1 network. + * + * @example + * + * import { Wallet, Provider, types } from "zksync-ethers"; + * import { ethers } from "ethers"; + * + * const PRIVATE_KEY = ""; + * + * const provider = Provider.getDefaultProvider(types.Network.Sepolia); + * const ethProvider = ethers.getDefaultProvider("sepolia"); + * const wallet = new Wallet(PRIVATE_KEY, provider, ethProvider); + */ + constructor(privateKey: string, providerL2?: Web3ZkSyncL2, providerL1?: Web3ZkSyncL1) { + super(); + + this.account = privateKeyToAccount(privateKey); + if (providerL2) { + this.connect(providerL2); + } + if (providerL1) { + this.connectToL1(providerL1); + } + } + public connect(provider: Web3ZkSyncL2) { + if (!provider.eth.accounts.wallet.get(this.account.address)) { + provider.eth.accounts.wallet.add( + provider.eth.accounts.privateKeyToAccount(this.account.privateKey), + ); + } + this.provider = provider; + this.provider._eip712Signer = this._eip712Signer.bind(this); + return this; + } + public connectToL1(provider: Web3ZkSyncL1) { + if (!provider.eth.accounts.wallet.get(this.account.address)) { + provider.eth.accounts.wallet.add( + provider.eth.accounts.privateKeyToAccount(this.account.privateKey), + ); + } + this.providerL1 = provider; + return this; + } + + protected async _eip712Signer(): Promise { + if (!this.eip712) { + this.eip712 = new utils.EIP712Signer( + this.account, + Number(await this.provider!.eth.getChainId()), + ); + } + + return this.eip712!; + } + protected _contextL1() { + return this.providerL1!; + } + protected _contextL2() { + return this.provider!; + } + + getBalanceL1(token?: Address, blockTag?: web3Types.BlockNumberOrTag) { + return super.getBalanceL1(token, blockTag); + } + getBalance(token?: Address, blockTag: web3Types.BlockNumberOrTag = 'committed') { + return super.getBalance(token, blockTag); + } + getAddress(): any { + return this.account.address; + } + deposit(transaction: { + token: Address; + amount: web3Types.Numbers; + to?: Address; + operatorTip?: web3Types.Numbers; + bridgeAddress?: Address; + approveERC20?: boolean; + approveBaseERC20?: boolean; + l2GasLimit?: web3Types.Numbers; + gasPerPubdataByte?: web3Types.Numbers; + refundRecipient?: Address; + overrides?: TransactionOverrides; + approveOverrides?: TransactionOverrides; + approveBaseOverrides?: TransactionOverrides; + customBridgeData?: web3Types.Bytes; + }) { + return super.deposit(transaction); + } + getNonce(blockNumber?: web3Types.BlockNumberOrTag) { + return this.provider?.eth.getTransactionCount(this.account.address, blockNumber); + } + static createRandom(provider?: Web3ZkSyncL2, providerL1?: Web3ZkSyncL1) { + const acc = createAccount(); + return new ZKSyncWallet(acc.privateKey, provider, providerL1); + } + async signTransaction(transaction: web3Types.Transaction): Promise { + const populated = (await this.populateTransaction(transaction)) as Transaction; + if (!isAddressEq(populated.from!, this.getAddress())) { + throw new Error('Transaction from mismatch'); + } + return this._contextL2().signTransaction(populated); + } + sendRawTransaction(signedTx: string) { + return this._contextL2().sendRawTransaction(signedTx); + } + + /** + * Designed for users who prefer a simplified approach by providing only the necessary data to create a valid transaction. + * The only required fields are `transaction.to` and either `transaction.data` or `transaction.value` (or both, if the method is payable). + * Any other fields that are not set will be prepared by this method. + * + * @param tx The transaction request that needs to be populated. + * + * @example + * + * import { Wallet, Provider, types, utils } from "zksync-ethers"; + * import { ethers } from "ethers"; + * + * const PRIVATE_KEY = ""; + * + * const provider = Provider.getDefaultProvider(types.Network.Sepolia); + * const ethProvider = ethers.getDefaultProvider("sepolia"); + * const wallet = new Wallet(PRIVATE_KEY, provider, ethProvider); + * + * const populatedTx = await wallet.populateTransaction({ + * type: utils.EIP712_TX_TYPE, + * to: RECEIVER, + * value: 7_000_000_000n, + * }); + */ + async populateTransaction(tx: web3Types.Transaction) { + tx.from = tx.from ?? this.getAddress(); + return this._contextL2().populateTransaction(tx); + } + + async getBridgehubContractAddress() { + return this._contextL2().getBridgehubContractAddress(); + } + + async sendTransaction(transaction: web3Types.Transaction) { + const signed = await this.signTransaction(transaction); + return getPriorityOpResponse(this._contextL2(), this.sendRawTransaction(signed)); + } +} diff --git a/test/fixtures.ts b/test/fixtures.ts index 942aa8d..f6f15f7 100644 --- a/test/fixtures.ts +++ b/test/fixtures.ts @@ -75,6 +75,7 @@ export const getRawBlockTransactionsData = { calldata: '0xc9807539000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000001a00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000fd69e45d6f51e482ac4f8f2e14f2155200005d8b0600010203000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000038c58633c000000000000000000000000000000000000000000000000000000038c58633c000000000000000000000000000000000000000000000000000000038c58633c000000000000000000000000000000000000000000000000000000038c58633c00000000000000000000000000000000000000000000000000000000000000002dd5b728d0f7335e173e9df0ec28a1b98e49f549043f6a34a111ba1f7057e1e01f36286dabf8ba11ba7791babe75ef8ce49c39c8ef6a4c6cd4e43bcc193d2d12e00000000000000000000000000000000000000000000000000000000000000020138cc5cd56da9e72c4b82131e4ff8324e7730a1ce77184a04be7d5a17650d5318e78a4684390b7c536507e840880ebb34e0dc6808d7302ccfcb9026e77457d0', value: BigInt(0), + factoryDeps: [], }, received_timestamp_ms: 1706539720746, raw_bytes: @@ -118,8 +119,10 @@ export const getBlockDetailsData = { export const getBridgeContractsData = { output: { l1Erc20DefaultBridge: '0x2ae09702f77a4940621572fbcdae2382d44a2cba', + l1SharedDefaultBridge: '0x3e8b2fe58675126ed30d0d12dea2a9bda72d18ae', l1WethBridge: '0x0000000000000000000000000000000000000000', l2Erc20DefaultBridge: '0x681a1afdc2e06776816386500d2d461a6c96cb45', + l2SharedDefaultBridge: '0x681a1afdc2e06776816386500d2d461a6c96cb45', l2WethBridge: '0x0000000000000000000000000000000000000000', }, }; diff --git a/test/integration/_wallet.test.ts b/test/integration/_wallet.test.ts new file mode 100644 index 0000000..35c14ce --- /dev/null +++ b/test/integration/_wallet.test.ts @@ -0,0 +1,52 @@ +import * as web3Accounts from 'web3-eth-accounts'; +import { TransactionFactory } from 'web3-eth-accounts'; +import { Network as ZkSyncNetwork } from '../../src/types'; +import { Web3ZkSyncL2, Web3ZkSyncL1, ZKSyncWallet } from '../../src'; +import { EIP712_TX_TYPE, ETH_ADDRESS } from '../../src/constants'; +import * as utils from '../../src/utils'; + +// TODO: This test needs to setup local dev nodes for L1 and L2 +// and also needs to have a private key with funds in the L1 +// to be able to run the test. +// Additionally, the test needs to be fixed as `wallet.deposit` throws an internal exception. + +jest.setTimeout(50000); +describe('wallet', () => { + // @ts-ignore + TransactionFactory.registerTransactionType(EIP712_TX_TYPE, utils.EIP712Transaction); + const l1Provider = new Web3ZkSyncL1( + 'https://eth-sepolia.g.alchemy.com/v2/VCOFgnRGJF_vdAY2ZjgSksL6-6pYvRkz', + ); + const l2Provider = Web3ZkSyncL2.initWithDefaultProvider(ZkSyncNetwork.Sepolia); + const PRIVATE_KEY = (process.env.PRIVATE_KEY as string) || web3Accounts.create().privateKey; + const wallet = new ZKSyncWallet(PRIVATE_KEY, l2Provider, l1Provider); + console.log('acc-test', String(process.env.PRIVATE_KEY).slice(0, 10)); + it('should deposit', async () => { + const tx = await wallet.deposit({ + token: ETH_ADDRESS, + to: wallet.getAddress(), + amount: 10000_000000_000000n, + refundRecipient: wallet.getAddress(), + }); + const receipt = await tx.wait(); + + console.log(`Tx: ${receipt.transactionHash}`); + + expect(receipt.status).toBe(1n); + expect(receipt.transactionHash).toBeDefined(); + }); + + it('should withdraw eth', async () => { + const tx = await wallet.withdraw({ + token: ETH_ADDRESS, + to: wallet.getAddress(), + amount: 10n, + }); + const receipt = await tx.wait(); + + console.log(`Tx: ${receipt.transactionHash}`); + + expect(receipt.status).toBe(1n); + expect(receipt.transactionHash).toBeDefined(); + }); +}); diff --git a/test/integration/mainnet.test.ts b/test/integration/mainnet.test.ts index be37604..276ec58 100644 --- a/test/integration/mainnet.test.ts +++ b/test/integration/mainnet.test.ts @@ -12,8 +12,8 @@ describe('ZkSyncPlugin rpc mainnet tests', () => { let web3: Web3; beforeAll(() => { - web3 = new Web3('https://mainnet.era.zksync.io'); - web3.registerPlugin(new ZkSyncPlugin()); + web3 = new Web3(); + web3.registerPlugin(new ZkSyncPlugin('https://mainnet.era.zksync.io')); }); it('should get L1 token address', async () => { diff --git a/test/integration/rpc.mainnet.test.ts b/test/integration/rpc.mainnet.test.ts index 559e422..d0bfbbb 100644 --- a/test/integration/rpc.mainnet.test.ts +++ b/test/integration/rpc.mainnet.test.ts @@ -1,4 +1,5 @@ import { Web3 } from 'web3'; +import { ethRpcMethods } from 'web3-rpc-methods'; import { ZkSyncPlugin } from '../../src'; import { estimateData, @@ -11,12 +12,12 @@ describe('ZkSyncPlugin rpc mainnet tests', () => { let web3: Web3; beforeAll(() => { - web3 = new Web3('https://mainnet.era.zksync.io'); - web3.registerPlugin(new ZkSyncPlugin()); + web3 = new Web3(); + web3.registerPlugin(new ZkSyncPlugin('https://mainnet.era.zksync.io')); }); it('should get bridge addresses', async () => { - const res = await web3.zkSync.getDefaultBridgeAddresses(); + const res = await web3.zkSync.L2.getDefaultBridgeAddresses(); expect(res.erc20L1).toBe('0x57891966931eb4bb6fb81430e6ce0a03aabde063'); expect(res.erc20L2).toBe('0x11f943b2c77b743ab90f4a0ae7d5a4e7fca3e102'); @@ -54,4 +55,11 @@ describe('ZkSyncPlugin rpc mainnet tests', () => { const blockDetails = await web3.zkSync.rpc.getBlockDetails(latestBatchDetails.number); expect(blockDetails.number).toBeDefined(); }); + it.skip('transactionReceipt', async () => { + const res = await ethRpcMethods.getTransactionReceipt( + web3.requestManager, + '0x8cee929f16b4738a8ae6cf4bd4f43d5be0e5f028e4db45a0bde6203ef242c30e', + ); + console.log('res', res); + }); }); diff --git a/test/integration/rpc.test.ts b/test/integration/rpc.test.ts index d370053..e456d86 100644 --- a/test/integration/rpc.test.ts +++ b/test/integration/rpc.test.ts @@ -7,13 +7,12 @@ import { getTransactionDetailsData, } from '../fixtures'; - describe('ZkSyncPlugin rpc tests', () => { let web3: Web3; beforeAll(() => { - web3 = new Web3('https://sepolia.era.zksync.dev'); - web3.registerPlugin(new ZkSyncPlugin()); + web3 = new Web3(); + web3.registerPlugin(new ZkSyncPlugin('https://sepolia.era.zksync.dev')); }); it('l1ChainId', async () => { @@ -52,10 +51,10 @@ describe('ZkSyncPlugin rpc tests', () => { const res = await web3.zkSync.rpc.getL1BatchBlockRange(1); expect(res).toEqual(['0x1', '0x2']); }); - it('getTestnetPaymaster', async () => { - const res = await web3.zkSync.rpc.getTestnetPaymaster(); - expect(res).toBe('0x3cb2b87d10ac01736a65688f3e0fb1b070b3eea3'); - }); + // it('getTestnetPaymaster', async () => { + // const res = await web3.zkSync.rpc.getTestnetPaymaster(); + // expect(res).toBe('0x3cb2b87d10ac01736a65688f3e0fb1b070b3eea3'); + // }); it('getBridgeContracts', async () => { const res = await web3.zkSync.rpc.getBridgeContracts(); expect(res).toEqual(getBridgeContractsData.output); @@ -68,4 +67,9 @@ describe('ZkSyncPlugin rpc tests', () => { }); expect(res).toBeGreaterThan(BigInt(0)); }); + + it('getBridgeHubContract', async () => { + const res = await web3.zkSync.rpc.getBridgehubContractAddress(); + expect(res).toEqual('0x35a54c8c757806eb6820629bc82d90e056394c92'); // @todo: set bridge hub contract address + }); }); diff --git a/test/integration/wallet.test.ts b/test/integration/wallet.test.ts new file mode 100644 index 0000000..b154a15 --- /dev/null +++ b/test/integration/wallet.test.ts @@ -0,0 +1,1777 @@ +import * as ethAccounts from 'web3-eth-accounts'; +import * as web3Utils from 'web3-utils'; +import { toBigInt } from 'web3-utils'; +import type { Transaction } from 'web3-types'; +import type { Address } from 'web3'; +import { privateKeyToAccount } from 'web3-eth-accounts'; +import { utils, Web3ZkSyncL2, ZKSyncWallet, Web3ZkSyncL1 } from '../../src'; +import { + IS_ETH_BASED, + ADDRESS1, + PRIVATE_KEY1, + ADDRESS2, + DAI_L1, + USDC_L1, + APPROVAL_TOKEN, + PAYMASTER, + deepEqualExcluding, +} from '../utils'; +import type { Eip712TxData } from '../../src/types'; +import { Network as ZkSyncNetwork } from '../../src/types'; +import { + ETH_ADDRESS, + ETH_ADDRESS_IN_CONTRACTS, + L2_BASE_TOKEN_ADDRESS, + LEGACY_ETH_ADDRESS, + DEFAULT_GAS_PER_PUBDATA_LIMIT, + EIP712_TX_TYPE, +} from '../../src/constants'; +import { IERC20ABI } from '../../src/contracts/IERC20'; +import { getPaymasterParams } from '../../src/paymaster-utils'; + +jest.setTimeout(5 * 60000); + +describe('Wallet', () => { + const provider = Web3ZkSyncL2.initWithDefaultProvider(ZkSyncNetwork.Sepolia); + const ethProvider = new Web3ZkSyncL1( + 'https://eth-sepolia.g.alchemy.com/v2/VCOFgnRGJF_vdAY2ZjgSksL6-6pYvRkz', + ); + const PRIVATE_KEY = (process.env.PRIVATE_KEY as string) || PRIVATE_KEY1; + const wallet = new ZKSyncWallet(PRIVATE_KEY, provider, ethProvider); + const walletAddress = privateKeyToAccount(PRIVATE_KEY).address; + describe('#constructor()', () => { + it('`Wallet(privateKey, provider)` should return a `Wallet` with L2 provider', async () => { + const wallet = new ZKSyncWallet(PRIVATE_KEY, provider); + + expect(wallet.account.privateKey).toEqual(PRIVATE_KEY); + expect(wallet.provider).toEqual(provider); + }); + + it('`Wallet(privateKey, provider, ethProvider)` should return a `Wallet` with L1 and L2 provider', async () => { + const wallet = new ZKSyncWallet(PRIVATE_KEY, provider, ethProvider); + + expect(wallet.account.privateKey).toEqual(PRIVATE_KEY); + expect(wallet.provider).toEqual(provider); + expect(wallet.providerL1).toEqual(ethProvider); + }); + }); + + describe('#getMainContract()', () => { + it('should return the main contract', async () => { + const result = await wallet.getMainContract(); + expect(result).not.toBeNull(); + }); + }); + + describe('#getBridgehubContract()', () => { + it('should return the bridgehub contract', async () => { + const result = await wallet.getBridgehubContractAddress(); + expect(result).not.toBeNull(); + }); + }); + + describe('#getL1BridgeContracts()', () => { + it('should return the L1 bridge contracts', async () => { + const result = await wallet.getL1BridgeContracts(); + expect(result).not.toBeNull(); + }); + }); + + describe('#isETHBasedChain()', () => { + it('should return whether the chain is ETH-based or not', async () => { + const result = await wallet.isETHBasedChain(); + expect(result).toEqual(IS_ETH_BASED); + }); + }); + + describe('#getBaseToken()', () => { + it('should return base token', async () => { + const result = await wallet.getBaseToken(); + IS_ETH_BASED + ? expect(result).toEqual(ETH_ADDRESS_IN_CONTRACTS) + : expect(result).not.toBeNull(); + }); + }); + + describe('#getBalanceL1()', () => { + it('should return a L1 balance', async () => { + const result = await wallet.getBalanceL1(); + expect(result > 0n).toEqual(true); + }); + }); + + describe('#getAllowanceL1()', () => { + it('should return an allowance of L1 token', async () => { + const result = await wallet.getAllowanceL1(DAI_L1); + expect(result >= 0n).toEqual(true); + }); + }); + + describe('#l2TokenAddress()', () => { + it('should return the L2 base address', async () => { + const baseToken = await provider.getBaseTokenContractAddress(); + const result = await wallet.l2TokenAddress(baseToken); + expect(result).toEqual(L2_BASE_TOKEN_ADDRESS); + }); + + it('should return the L2 ETH address', async () => { + if (!IS_ETH_BASED) { + const result = await wallet.l2TokenAddress(LEGACY_ETH_ADDRESS); + expect(result).not.toBeNull(); + } + }); + + it('should return the L2 DAI address', async () => { + const result = await wallet.l2TokenAddress(DAI_L1); + expect(result).not.toBeNull(); + }); + }); + + describe('#approveERC20()', () => { + it('should approve a L1 token', async () => { + const result = await wallet.approveERC20(DAI_L1, 5); + expect(result).not.toBeNull(); + }); + + it('should throw an error when approving ETH token', async () => { + try { + await wallet.approveERC20(LEGACY_ETH_ADDRESS, 5); + } catch (e) { + expect((e as Error).message).toEqual( + "ETH token can't be approved! The address of the token does not exist on L1.", + ); + } + }); + }); + + describe('#getBaseCost()', () => { + it('should return a base cost of L1 transaction', async () => { + const result = await wallet.getBaseCost({ gasLimit: 100_000 }); + expect(result).not.toBeNull(); + }); + }); + + describe('#getBalance()', () => { + it('should return a `Wallet` balance', async () => { + const result = await wallet.getBalance(); + expect(result > 0n).toEqual(true); + }); + }); + + describe('#getAllBalances()', () => { + it('should return the all balances', async () => { + const result = await wallet.getAllBalances(); + const expected = IS_ETH_BASED ? 2 : 3; + expect(Object.keys(result)).toHaveLength(expected); + }); + }); + + describe('#getL2BridgeContracts()', () => { + it('should return a L2 bridge contracts', async () => { + const result = await wallet.getL2BridgeContracts(); + expect(result).not.toBeNull(); + }); + }); + + describe('#getAddress()', () => { + it('should return the `Wallet` address', async () => { + const result = wallet.getAddress(); + expect(result).toEqual(walletAddress); + }); + }); + + // describe('#ethWallet()', () => { + // it('should return a L1 `Wallet`', async () => { + // const wallet = new ZKSyncWallet(PRIVATE_KEY, provider, ethProvider); + // const ethWallet = wallet.ethWallet(); + // expect(ethWallet.signingKey.privateKey).toEqual(PRIVATE_KEY); + // expect(ethWallet.provider).toEqual(ethProvider); + // }); + // + // it('should throw an error when L1 `Provider` is not specified in constructor', async () => { + // const wallet = new Wallet(PRIVATE_KEY, provider); + // try { + // wallet.ethWallet(); + // } catch (e) { + // expect((e as Error).message).toEqual( + // 'L1 provider is missing! Specify an L1 provider using `Wallet.connectToL1()`.', + // ); + // } + // }); + // }); + + describe('#connect()', () => { + it('should return a `Wallet` with provided `provider` as L2 provider', async () => { + const w = new ZKSyncWallet(PRIVATE_KEY); + w.connect(provider); + expect(w.account.privateKey).toEqual(PRIVATE_KEY); + expect(w.provider).toEqual(provider); + }); + }); + + describe('#connectL1()', () => { + it('should return a `Wallet` with provided `provider` as L1 provider', async () => { + const w = new ZKSyncWallet(PRIVATE_KEY); + w.connectToL1(ethProvider); + expect(w.account.privateKey).toEqual(PRIVATE_KEY); + expect(w.providerL1).toEqual(ethProvider); + }); + }); + + describe('#getDeploymentNonce()', () => { + it('should return a deployment nonce', async () => { + const result = await wallet.getDeploymentNonce(); + expect(result).not.toBeNull(); + }); + }); + + describe('#populateTransaction()', () => { + it('should return a populated transaction', async () => { + const tx = { + to: ADDRESS2, + value: 7_000_000_000n, + type: BigInt(EIP712_TX_TYPE), + from: wallet.getAddress(), + nonce: await wallet.getNonce('pending'), + gasLimit: 154_379n, + chainId: 300n, + data: '0x', + customData: { gasPerPubdata: 50_000, factoryDeps: [] }, + gasPrice: 100_000_000n, + }; + + // @ts-ignore + const result = await wallet.populateTransaction({ + type: web3Utils.toHex(EIP712_TX_TYPE), + to: ADDRESS2, + value: web3Utils.toHex(7_000_000_000), + }); + deepEqualExcluding(result, tx, [ + 'gasLimit', + 'gasPrice', + 'maxFeePerGas', + 'maxPriorityFeePerGas', + ]); + expect(toBigInt(result.gasLimit) > 0n).toEqual(true); + expect( + toBigInt(result.maxPriorityFeePerGas) > 0n || + toBigInt(result.maxFeePerGas) > 0n || + toBigInt(result.gasPrice) > 0n, + ).toEqual(true); + }); + it('should return a populated transaction with default values if are omitted', async () => { + const tx = { + to: ADDRESS2, + value: 7_000_000n, + type: 2n, + from: wallet.getAddress(), + nonce: await wallet.getNonce('pending'), + chainId: 300n, + maxFeePerGas: 1_200_000_000n, + maxPriorityFeePerGas: 1_000_000_000n, + }; + // @ts-ignore + const result = await wallet.populateTransaction({ + to: ADDRESS2, + value: web3Utils.toHex(7_000_000), + }); + deepEqualExcluding(result, tx, ['gasLimit', 'maxFeePerGas', 'maxPriorityFeePerGas']); + expect(toBigInt(result.gasLimit) > 0n).toEqual(true); + expect(toBigInt(result.maxFeePerGas) > 0n).toEqual(true); + expect(toBigInt(result.maxPriorityFeePerGas) > 0n).toEqual(true); + }); + it('should return populated transaction when `maxFeePerGas` and `maxPriorityFeePerGas` and `customData` are provided', async () => { + const tx = { + to: ADDRESS2, + value: 7_000_000n, + type: BigInt(EIP712_TX_TYPE), + from: wallet.getAddress(), + nonce: await wallet.getNonce('pending'), + data: '0x', + chainId: 300n, + maxFeePerGas: 3_500_000_000n, + maxPriorityFeePerGas: 2_000_000_000n, + customData: { + gasPerPubdata: DEFAULT_GAS_PER_PUBDATA_LIMIT, + factoryDeps: [], + }, + }; + // @ts-ignore + const result = await wallet.populateTransaction({ + to: ADDRESS2, + value: web3Utils.toHex(7_000_000), + maxFeePerGas: web3Utils.toHex(3_500_000_000n), + maxPriorityFeePerGas: web3Utils.toHex(2_000_000_000n), + customData: { + gasPerPubdata: DEFAULT_GAS_PER_PUBDATA_LIMIT, + factoryDeps: [], + }, + } as Eip712TxData); + deepEqualExcluding(tx, result, ['gasLimit']); + expect(toBigInt(result.gasLimit) > 0n).toEqual(true); + }); + + it('should return populated transaction when `maxPriorityFeePerGas` and `customData` are provided', async () => { + const tx = { + to: ADDRESS2, + value: 7_000_000n, + type: BigInt(EIP712_TX_TYPE), + from: wallet.getAddress(), + nonce: await wallet.getNonce('pending'), + data: '0x', + chainId: 300n, + maxPriorityFeePerGas: 2_000_000_000n, + customData: { + gasPerPubdata: DEFAULT_GAS_PER_PUBDATA_LIMIT, + factoryDeps: [], + }, + }; + // @ts-ignore + const result = await wallet.populateTransaction({ + to: ADDRESS2, + value: web3Utils.toHex(7_000_000), + maxPriorityFeePerGas: web3Utils.toHex(2_000_000_000n), + customData: { + gasPerPubdata: DEFAULT_GAS_PER_PUBDATA_LIMIT, + }, + } as Eip712TxData); + deepEqualExcluding(result, tx, ['gasLimit', 'maxFeePerGas']); + expect(toBigInt(result.gasLimit) > 0n).toEqual(true); + }); + + it('should return populated transaction when `maxFeePerGas` and `customData` are provided', async () => { + const tx = { + to: ADDRESS2, + value: 7_000_000n, + type: BigInt(EIP712_TX_TYPE), + from: wallet.getAddress(), + nonce: await wallet.getNonce('pending'), + data: '0x', + chainId: 300n, + maxFeePerGas: 3_500_000_000n, + customData: { + gasPerPubdata: DEFAULT_GAS_PER_PUBDATA_LIMIT, + factoryDeps: [], + }, + }; + // @ts-ignore + const result = await wallet.populateTransaction({ + to: ADDRESS2, + value: web3Utils.toHex(7_000_000), + maxFeePerGas: web3Utils.toHex(3_500_000_000n), + customData: { + gasPerPubdata: DEFAULT_GAS_PER_PUBDATA_LIMIT, + }, + } as Eip712TxData); + deepEqualExcluding(tx, result, ['gasLimit']); + expect(toBigInt(result.gasLimit) > 0n).toEqual(true); + }); + + it('should return populated EIP1559 transaction when `maxFeePerGas` and `maxPriorityFeePerGas` are provided', async () => { + const tx = { + to: ADDRESS2, + value: 7_000_000n, + type: 2n, + from: wallet.getAddress(), + nonce: await wallet.getNonce('pending'), + chainId: 300n, + maxFeePerGas: 3_500_000_000n, + maxPriorityFeePerGas: 2_000_000_000n, + }; + // @ts-ignore + const result = await wallet.populateTransaction({ + to: ADDRESS2, + value: web3Utils.toHex(7_000_000), + maxFeePerGas: web3Utils.toHex(3_500_000_000n), + maxPriorityFeePerGas: web3Utils.toHex(2_000_000_000n), + }); + deepEqualExcluding(tx, result, ['gasLimit']); + expect(toBigInt(result.gasLimit!) > 0n).toEqual(true); + }); + + it('should return populated EIP1559 transaction with `maxFeePerGas` and `maxPriorityFeePerGas` same as provided `gasPrice`', async () => { + const tx = { + to: ADDRESS2, + value: 7_000_000n, + type: 2, + from: wallet.getAddress(), + nonce: await wallet.getNonce('pending'), + chainId: 300n, + maxFeePerGas: 3_500_000_000n, + maxPriorityFeePerGas: 3_500_000_000n, + }; + // @ts-ignore + const result = await wallet.populateTransaction({ + to: ADDRESS2, + value: web3Utils.toHex(7_000_000), + gasPrice: web3Utils.toHex(3_500_000_000n), + }); + deepEqualExcluding(result, tx, ['gasLimit', 'type', 'gasPrice']); + expect(toBigInt(result.gasLimit!) > 0n).toEqual(true); + }); + + it('should return populated legacy transaction when `type = 0`', async () => { + const tx = { + to: ADDRESS2, + value: 7_000_000n, + type: 0n, + from: wallet.getAddress(), + nonce: await wallet.getNonce('pending'), + chainId: 300n, + gasPrice: 100_000_000n, + }; + // @ts-ignore + const result = await wallet.populateTransaction({ + type: web3Utils.toHex(0), + to: ADDRESS2, + value: web3Utils.toHex(7_000_000), + }); + deepEqualExcluding(tx, result, ['gasLimit', 'gasPrice']); + expect(toBigInt(result.gasLimit!) > 0n).toEqual(true); + expect(toBigInt(result.gasPrice!) > 0n).toEqual(true); + }); + }); + + describe('#sendTransaction()', () => { + it('should send already populated transaction with provided `maxFeePerGas` and `maxPriorityFeePerGas` and `customData` fields', async () => { + const populatedTx = (await wallet.populateTransaction({ + to: ADDRESS2 as Address, + value: web3Utils.toHex(7_000_000), + maxFeePerGas: web3Utils.toHex(3_500_000_000n), + maxPriorityFeePerGas: web3Utils.toHex(2_000_000_000n), + // @ts-ignore + customData: { + gasPerPubdata: DEFAULT_GAS_PER_PUBDATA_LIMIT, + }, + })) as unknown as Transaction; + const tx = await wallet.sendTransaction(populatedTx); + const result = await tx.wait(); + expect(result).not.toBeNull(); + }); + + it('should send EIP1559 transaction when `maxFeePerGas` and `maxPriorityFeePerGas` are provided', async () => { + const tx = await wallet.sendTransaction({ + to: ADDRESS2, + value: 7_000_000, + maxFeePerGas: 3_500_000_000n, + maxPriorityFeePerGas: 2_000_000_000n, + }); + const result = await tx.wait(); + expect(result).not.toBeNull(); + expect(result.type).toEqual(2n); + }); + + it('should send already populated EIP1559 transaction with `maxFeePerGas` and `maxPriorityFeePerGas`', async () => { + const populatedTx = await wallet.populateTransaction({ + to: ADDRESS2, + value: web3Utils.toHex(7_000_000), + maxFeePerGas: web3Utils.toHex(3_500_000_000n), + maxPriorityFeePerGas: web3Utils.toHex(2_000_000_000n), + }); + + const tx = await wallet.sendTransaction(populatedTx as Transaction); + const result = await tx.wait(); + expect(result).not.toBeNull(); + expect(result.type).toEqual(2n); + }); + + it('should send EIP1559 transaction with `maxFeePerGas` and `maxPriorityFeePerGas` same as provided `gasPrice`', async () => { + const tx = await wallet.sendTransaction({ + to: ADDRESS2, + value: 7_000_000, + gasPrice: 3_500_000_000n, + }); + const result = await tx.wait(); + expect(result).not.toBeNull(); + expect(result.type).toEqual(0n); + }); + + it('should send legacy transaction when `type = 0`', async () => { + const tx = await wallet.sendTransaction({ + type: 0, + to: ADDRESS2, + value: 7_000_000, + }); + const result = await tx.wait(); + expect(result).not.toBeNull(); + expect(result.type).toEqual(0n); + }); + }); + + // describe('#fromMnemonic()', () => { + // it('should return a `Wallet` with the `provider` as L1 provider and a private key that is built from the `mnemonic` passphrase', async () => { + // const wallet = Web3ZkSyncL2.fromMnemonic(MNEMONIC1, ethProvider); + // expect(wallet.signingKey.privateKey).toEqual(PRIVATE_KEY); + // expect(wallet.providerL1).toEqual(ethProvider); + // }); + // }); + + // describe('#fromEncryptedJson()', () => { + // it('should return a `Wallet` from encrypted `json` file using provided `password`', async () => { + // const wallet = await Wallet.fromEncryptedJson( + // fs.readFileSync('tests/files/wallet.json', 'utf8'), + // 'password', + // ); + // expect(wallet.signingKey.privateKey).toEqual(PRIVATE_KEY); + // }) + // }); + + // describe('#fromEncryptedJsonSync()', () => { + // it('should return a `Wallet` from encrypted `json` file using provided `password`', async () => { + // const wallet = Wallet.fromEncryptedJsonSync( + // fs.readFileSync('tests/files/wallet.json', 'utf8'), + // 'password', + // ); + // expect(wallet.signingKey.privateKey).toEqual(PRIVATE_KEY); + // }) + // }); + + describe('#createRandom()', () => { + it('should return a random `Wallet` with L2 provider', async () => { + const wallet = ZKSyncWallet.createRandom(provider); + expect(wallet.account.privateKey).not.toBeNull(); + expect(wallet.provider).toEqual(provider); + }); + }); + + describe('#getDepositTx()', () => { + if (IS_ETH_BASED) { + it('should return ETH deposit transaction', async () => { + const tx = { + contractAddress: wallet.getAddress(), + calldata: '0x', + l2Value: 7_000_000, + l2GasLimit: 415_035n, + mintValue: 111_540_663_250_000n, + token: ETH_ADDRESS_IN_CONTRACTS, + to: wallet.getAddress(), + amount: 7_000_000, + refundRecipient: wallet.getAddress(), + operatorTip: 0, + overrides: { + from: wallet.getAddress(), + maxFeePerGas: 1_000_000_001n, + maxPriorityFeePerGas: 1_000_000_000n, + value: 111_540_663_250_000n, + }, + gasPerPubdataByte: 800, + }; + const result = await wallet.getDepositTx({ + token: LEGACY_ETH_ADDRESS, + to: wallet.getAddress(), + amount: 7_000_000, + refundRecipient: wallet.getAddress(), + }); + deepEqualExcluding(result, tx, ['l2GasLimit', 'mintValue', 'overrides']); + expect(result.l2GasLimit > 0n).toEqual(true); + expect(result.mintValue > 0n).toEqual(true); + expect(utils.isAddressEq(result.overrides.from, wallet.getAddress())).toEqual(true); + expect(result.overrides.maxFeePerGas > 0n).toEqual(true); + expect(result.overrides.maxPriorityFeePerGas > 0n).toEqual(true); + expect(result.overrides.value > 0n).toEqual(true); + }); + + it('should return a deposit transaction with `tx.to == Wallet.getAddress()` when `tx.to` is not specified', async () => { + const tx = { + contractAddress: wallet.getAddress(), + calldata: '0x', + l2Value: 7_000_000, + l2GasLimit: 415_035n, + mintValue: 111_540_663_250_000n, + token: ETH_ADDRESS_IN_CONTRACTS, + amount: 7_000_000, + refundRecipient: wallet.getAddress(), + operatorTip: 0, + overrides: { + from: wallet.getAddress(), + maxFeePerGas: 1_000_000_001n, + maxPriorityFeePerGas: 1_000_000_000n, + value: 111_540_663_250_000n, + }, + gasPerPubdataByte: 800, + }; + const result = await wallet.getDepositTx({ + token: LEGACY_ETH_ADDRESS, + amount: 7_000_000, + refundRecipient: wallet.getAddress(), + }); + deepEqualExcluding(tx, result, ['l2GasLimit', 'mintValue', 'overrides']); + expect(result.l2GasLimit > 0n).toEqual(true); + expect(result.mintValue > 0n).toEqual(true); + expect(utils.isAddressEq(result.overrides.from, wallet.getAddress())).toEqual(true); + expect(result.overrides.maxFeePerGas > 0n).toEqual(true); + expect(result.overrides.maxPriorityFeePerGas > 0n).toEqual(true); + expect(result.overrides.value > 0n).toEqual(true); + }); + + it('should return USDC deposit transaction', async () => { + const transaction = { + maxFeePerGas: 1_000_000_001n, + maxPriorityFeePerGas: 1_000_000_000n, + value: 105_100_275_000_000n, + from: wallet.getAddress(), + to: await provider.getBridgehubContractAddress(), + }; + const result = await wallet.getDepositTx({ + token: USDC_L1, + to: wallet.getAddress(), + amount: 5, + refundRecipient: wallet.getAddress(), + }); + const tx = (await wallet.populateTransaction( + result.tx.populateTransaction(result.overrides), + )) as Transaction; + + expect(tx.from!.toLowerCase()).toBe(transaction.from.toLowerCase()); + expect(tx.to!.toLowerCase()).toBe(transaction.to.toLowerCase()); + expect(tx.maxFeePerGas).toBeGreaterThan(0n); + expect(tx.maxFeePerGas).toBeGreaterThan(0n); + expect(tx.maxPriorityFeePerGas).toBeGreaterThan(0n); + expect(tx.value).toBeGreaterThan(0n); + }); + } else { + it('should return ETH deposit transaction', async () => { + const tx = { + from: ADDRESS1, + to: (await provider.getBridgehubContractAddress()).toLowerCase(), + value: 7_000_000n, + data: '0x24fd57fb0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000010e0000000000000000000000000000000000000000000000000000bf1aaa17ee7000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000062e3d000000000000000000000000000000000000000000000000000000000000032000000000000000000000000036615cf349d7f6344891b1e7ca7c72883f5dc049000000000000000000000000842deab39809094bf5e4b77a7f97ae308adc5e5500000000000000000000000000000000000000000000000000000000006acfc0000000000000000000000000000000000000000000000000000000000000012000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000036615cf349d7f6344891b1e7ca7c72883f5dc049', + maxFeePerGas: 1_000_000_001n, + maxPriorityFeePerGas: 1_000_000_000n, + }; + const result = await wallet.getDepositTx({ + token: LEGACY_ETH_ADDRESS, + to: wallet.getAddress(), + amount: 7_000_000, + refundRecipient: wallet.getAddress(), + }); + result.to = result.to.toLowerCase(); + const { data: a, maxFeePerGas: b, maxPriorityFeePerGas: c, ...otherTx } = tx; + const { + data: d, + maxFeePerGas: e, + maxPriorityFeePerGas: f, + ...otherResult + } = result; + + expect(otherResult).toEqual(otherTx); + expect(toBigInt(result.maxPriorityFeePerGas) > 0n).toEqual(true); + expect(toBigInt(result.maxFeePerGas) > 0n).toEqual(true); + }); + + it('should return a deposit transaction with `tx.to == Wallet.getAddress()` when `tx.to` is not specified', async () => { + const tx = { + from: ADDRESS1, + to: (await provider.getBridgehubContractAddress()).toLowerCase(), + value: 7_000_000n, + maxFeePerGas: 1_000_000_001n, + maxPriorityFeePerGas: 1000_000_000n, + }; + const result = await wallet.getDepositTx({ + token: LEGACY_ETH_ADDRESS, + amount: 7_000_000, + refundRecipient: wallet.getAddress(), + }); + result.to = result.to.toLowerCase(); + deepEqualExcluding(tx, result, ['data', 'maxFeePerGas', 'maxPriorityFeePerGas']); + expect(toBigInt(result.maxPriorityFeePerGas) > 0n).toEqual(true); + expect(toBigInt(result.maxFeePerGas) > 0n).toEqual(true); + }); + + it('should return DAI deposit transaction', async () => { + const tx = { + maxFeePerGas: 1_000_000_001n, + maxPriorityFeePerGas: 1_000_000_000n, + value: 0n, + from: ADDRESS1, + to: (await provider.getBridgehubContractAddress()).toLowerCase(), + }; + const result = await wallet.getDepositTx({ + token: DAI_L1, + to: wallet.getAddress(), + amount: 5, + refundRecipient: wallet.getAddress(), + }); + result.to = result.to.toLowerCase(); + deepEqualExcluding(tx, result, ['data', 'maxFeePerGas', 'maxPriorityFeePerGas']); + expect(toBigInt(result.maxPriorityFeePerGas) > 0n).toEqual(true); + expect(toBigInt(result.maxFeePerGas) > 0n).toEqual(true); + }); + } + }); + + describe('#estimateGasDeposit()', () => { + if (IS_ETH_BASED) { + it('should return a gas estimation for the ETH deposit transaction', async () => { + const result = await wallet.estimateGasDeposit({ + token: LEGACY_ETH_ADDRESS, + to: wallet.getAddress(), + amount: 5, + refundRecipient: wallet.getAddress(), + }); + expect(result > 0n).toEqual(true); + }); + + it('should return a gas estimation for the USDC deposit transaction', async () => { + const result = await wallet.estimateGasDeposit({ + token: USDC_L1, + to: wallet.getAddress(), + amount: 5, + refundRecipient: wallet.getAddress(), + }); + expect(result > 0n).toEqual(true); + }); + } else { + it('should throw an error for insufficient allowance when estimating gas for ETH deposit transaction', async () => { + try { + await wallet.estimateGasDeposit({ + token: LEGACY_ETH_ADDRESS, + to: wallet.getAddress(), + amount: 5, + refundRecipient: wallet.getAddress(), + }); + } catch (e: any) { + expect(e.reason).toMatch('ERC20: insufficient allowance'); + } + }); + + it('should return gas estimation for ETH deposit transaction', async () => { + const token = LEGACY_ETH_ADDRESS; + const amount = 5; + const approveParams = await wallet.getDepositAllowanceParams(token, amount); + + await wallet.approveERC20(approveParams[0].token, approveParams[0].allowance); + + const result = await wallet.estimateGasDeposit({ + token: token, + to: wallet.getAddress(), + amount: amount, + refundRecipient: wallet.getAddress(), + }); + expect(result > 0n).toEqual(true); + }); + + it('should return gas estimation for base token deposit transaction', async () => { + const token = await wallet.getBaseToken(); + const amount = 5; + const approveParams = await wallet.getDepositAllowanceParams(token, amount); + + await wallet.approveERC20(approveParams[0].token, approveParams[0].allowance); + + const result = await wallet.estimateGasDeposit({ + token: token, + to: wallet.getAddress(), + amount: amount, + refundRecipient: wallet.getAddress(), + }); + expect(result > 0n).toEqual(true); + }); + + it('should return gas estimation for DAI deposit transaction', async () => { + const token = DAI_L1; + const amount = 5; + const approveParams = await wallet.getDepositAllowanceParams(token, amount); + + await wallet.approveERC20(approveParams[0].token, approveParams[0].allowance); + await wallet.approveERC20(approveParams[1].token, approveParams[1].allowance); + + const result = await wallet.estimateGasDeposit({ + token: token, + to: wallet.getAddress(), + amount: amount, + refundRecipient: wallet.getAddress(), + }); + expect(result > 0n).toEqual(true); + }); + } + }); + + describe('#deposit()', () => { + if (IS_ETH_BASED) { + it('should deposit ETH to L2 network', async () => { + const amount = 7_000_000_000; + const l2BalanceBeforeDeposit = await wallet.getBalance(); + const l1BalanceBeforeDeposit = await wallet.getBalanceL1(); + const tx = await wallet.deposit({ + token: LEGACY_ETH_ADDRESS, + to: wallet.getAddress(), + amount, + refundRecipient: wallet.getAddress(), + }); + console.log('tx', tx); + const result = await tx.wait(); + console.log('result', result); + const l2BalanceAfterDeposit = await wallet.getBalance(); + const l1BalanceAfterDeposit = await wallet.getBalanceL1(); + expect(result).not.toBeNull(); + expect(l2BalanceAfterDeposit - l2BalanceBeforeDeposit >= 0).toBe(true); + expect(l1BalanceBeforeDeposit - l1BalanceAfterDeposit >= 0).toBe(true); + }); + + it('should deposit USDC to L2 network', async () => { + const amount = 5; + const l2USDC = await provider.l2TokenAddress(USDC_L1); + const l2BalanceBeforeDeposit = await wallet.getBalance(l2USDC); + const l1BalanceBeforeDeposit = await wallet.getBalanceL1(USDC_L1); + const tx = await wallet.deposit({ + token: USDC_L1, + to: wallet.getAddress(), + amount: amount, + approveERC20: true, + refundRecipient: wallet.getAddress(), + }); + const result = await tx.wait(); + const l2BalanceAfterDeposit = await wallet.getBalance(l2USDC); + const l1BalanceAfterDeposit = await wallet.getBalanceL1(USDC_L1); + expect(result).not.toBeNull(); + expect(l2BalanceAfterDeposit - l2BalanceBeforeDeposit >= 0).toEqual(true); + expect(l1BalanceBeforeDeposit - l1BalanceAfterDeposit >= 0).toEqual(true); + }); + + it('should deposit USDC to the L2 network with approve transaction for allowance', async () => { + const amount = 7; + const l2USDC = await provider.l2TokenAddress(USDC_L1); + const l2BalanceBeforeDeposit = await wallet.getBalance(l2USDC); + const l1BalanceBeforeDeposit = await wallet.getBalanceL1(USDC_L1); + const tx = await wallet.deposit({ + token: USDC_L1, + to: wallet.getAddress(), + amount, + approveERC20: true, + refundRecipient: wallet.getAddress(), + }); + const result = await tx.wait(); + await tx.waitFinalize(); + const l2BalanceAfterDeposit = await wallet.getBalance(l2USDC); + const l1BalanceAfterDeposit = await wallet.getBalanceL1(USDC_L1); + expect(result).not.toBeNull(); + expect(l2BalanceAfterDeposit - l2BalanceBeforeDeposit >= amount).toEqual(true); + expect(l1BalanceBeforeDeposit - l1BalanceAfterDeposit >= amount).toEqual(true); + }); + } else { + it('should deposit ETH to L2 network', async () => { + const amount = 7_000_000_000; + const l2EthAddress = await wallet.l2TokenAddress(ETH_ADDRESS_IN_CONTRACTS); + const l2BalanceBeforeDeposit = await wallet.getBalance(l2EthAddress); + const l1BalanceBeforeDeposit = await wallet.getBalanceL1(); + const tx = await wallet.deposit({ + token: ETH_ADDRESS, + to: wallet.getAddress(), + amount: amount, + approveBaseERC20: true, + refundRecipient: wallet.getAddress(), + }); + const result = await tx.wait(); + const l2BalanceAfterDeposit = await wallet.getBalance(l2EthAddress); + const l1BalanceAfterDeposit = await wallet.getBalanceL1(); + expect(result).not.toBeNull(); + expect(l2BalanceAfterDeposit - l2BalanceBeforeDeposit >= amount).toEqual(true); + expect(l1BalanceBeforeDeposit - l1BalanceAfterDeposit >= amount).toEqual(true); + }); + + it('should deposit base token to L2 network', async () => { + const amount = 5; + const baseTokenL1 = await wallet.getBaseToken(); + const l2BalanceBeforeDeposit = await wallet.getBalance(); + const l1BalanceBeforeDeposit = await wallet.getBalanceL1(baseTokenL1); + const tx = await wallet.deposit({ + token: baseTokenL1, + to: wallet.getAddress(), + amount: amount, + approveERC20: true, + refundRecipient: wallet.getAddress(), + }); + const result = await tx.wait(); + const l2BalanceAfterDeposit = await wallet.getBalance(); + const l1BalanceAfterDeposit = await wallet.getBalanceL1(baseTokenL1); + expect(result).not.toBeNull(); + expect(l2BalanceAfterDeposit - l2BalanceBeforeDeposit >= amount).toEqual(true); + expect(l1BalanceBeforeDeposit - l1BalanceAfterDeposit >= 0n).toEqual(true); + }); + + it('should deposit DAI to L2 network', async () => { + const amount = 5; + const l2DAI = await provider.l2TokenAddress(DAI_L1); + const l2BalanceBeforeDeposit = await wallet.getBalance(l2DAI); + const l1BalanceBeforeDeposit = await wallet.getBalanceL1(DAI_L1); + const tx = await wallet.deposit({ + token: DAI_L1, + to: wallet.getAddress(), + amount: amount, + approveERC20: true, + approveBaseERC20: true, + refundRecipient: wallet.getAddress(), + }); + const result = await tx.wait(); + const l2BalanceAfterDeposit = await wallet.getBalance(l2DAI); + const l1BalanceAfterDeposit = await wallet.getBalanceL1(DAI_L1); + expect(result).not.toBeNull(); + expect(l2BalanceAfterDeposit - l2BalanceBeforeDeposit >= amount).toEqual(true); + expect(l1BalanceBeforeDeposit - l1BalanceAfterDeposit >= amount).toEqual(true); + }); + } + }); + + describe('#claimFailedDeposit()', () => { + if (IS_ETH_BASED) { + it('should claim failed deposit', async () => { + const response = await wallet.deposit({ + token: USDC_L1, + to: wallet.getAddress(), + amount: 5, + approveERC20: true, + refundRecipient: wallet.getAddress(), + l2GasLimit: 300_000, // make it fail because of low gas + }); + const receipt = await response.wait(); + expect(receipt.transactionHash).toBeDefined(); + // console.log('rr', rr); + // try { + // await response.waitFinalize(); + // } catch (error) { + // const hash = '0x229f99f63a6fd5e90154546797c56348e8f6260808bf63769ea22e842d09750f'; + // const blockNumber = (await wallet.provider!.eth.getTransaction(hash)).blockNumber!; + // // Now wait for block number to be executed. + // let blockDetails: BlockDetails; + // do { + // // still not executed. + // await utils.sleep(500); + // blockDetails = await wallet.provider!.getBlockDetails(blockNumber); + // } while (!blockDetails || !blockDetails.executeTxHash); + // const result = await wallet.claimFailedDeposit(hash); + // expect(result?.blockHash).not.toBeNull(); + // } + }); + + it('should throw an error when trying to claim successful deposit', async () => { + const response = await wallet.deposit({ + token: LEGACY_ETH_ADDRESS, + to: wallet.getAddress(), + amount: 7_000_000_000, + refundRecipient: wallet.getAddress(), + }); + const tx = await response.waitFinalize(); + try { + await wallet.claimFailedDeposit(tx.transactionHash); + } catch (e) { + expect((e as Error).message).toEqual('Cannot claim successful deposit!'); + } + }); + } else { + it('should throw an error when trying to claim successful deposit', async () => { + const response = await wallet.deposit({ + token: await wallet.getBaseToken(), + to: wallet.getAddress(), + amount: 5, + approveERC20: true, + refundRecipient: wallet.getAddress(), + }); + const tx = await response.waitFinalize(); + try { + await wallet.claimFailedDeposit(tx.transactionHash); + } catch (e) { + expect((e as Error).message).toEqual('Cannot claim successful deposit!'); + } + }); + } + }); + + describe('#getFullRequiredDepositFee()', () => { + if (IS_ETH_BASED) { + it('should return a fee for ETH token deposit', async () => { + const result = await wallet.getFullRequiredDepositFee({ + token: LEGACY_ETH_ADDRESS, + to: wallet.getAddress(), + }); + expect(result.baseCost > 0n).toEqual(true); + expect(result.l1GasLimit > 0n).toEqual(true); + expect(result.l2GasLimit > 0n).toEqual(true); + expect(result.maxPriorityFeePerGas! > 0n).toEqual(true); + expect(result.maxFeePerGas! > 0n).toEqual(true); + }); + + it('should throw an error when there is not enough allowance to cover the deposit', async () => { + try { + await wallet.getFullRequiredDepositFee({ + token: USDC_L1, + to: wallet.getAddress(), + }); + } catch (e) { + expect((e as Error).message).toEqual( + 'Not enough allowance to cover the deposit!', + ); + } + }); + + it('should return a fee for DAI token deposit', async () => { + await wallet.approveERC20(USDC_L1, 5); + + const result = await wallet.getFullRequiredDepositFee({ + token: USDC_L1, + to: wallet.getAddress(), + }); + expect(result.baseCost > 0n).toEqual(true); + expect(result.l1GasLimit > 0n).toEqual(true); + expect(result.l2GasLimit > 0n).toEqual(true); + expect(result.maxPriorityFeePerGas! > 0n).toEqual(true); + expect(result.maxFeePerGas! > 0n).toEqual(true); + }); + + it('should throw Not enough balance for deposit!', async () => { + //Not enough allowance to cover the deposit! + expect(async () => { + const randomWallet = new ZKSyncWallet( + wallet.providerL1!.eth.accounts.create().privateKey, + provider, + ethProvider, + ); + + await randomWallet.getFullRequiredDepositFee({ + token: USDC_L1, + to: wallet.getAddress(), + }); + }).rejects.toThrow('Not enough balance for deposit!'); + }); + } else { + it('should throw an error when there is not enough base token allowance to cover the deposit', async () => { + try { + await new ZKSyncWallet( + ethAccounts.create().privateKey, + provider, + ethProvider, + ).getFullRequiredDepositFee({ + token: LEGACY_ETH_ADDRESS, + to: wallet.getAddress(), + }); + } catch (e) { + expect((e as Error).message).toEqual( + 'Not enough base token allowance to cover the deposit!', + ); + } + }); + + it('should return fee for ETH token deposit', async () => { + const token = LEGACY_ETH_ADDRESS; + const approveParams = await wallet.getDepositAllowanceParams(token, 1); + + await wallet.approveERC20(approveParams[0].token, approveParams[0].allowance); + + const result = await wallet.getFullRequiredDepositFee({ + token: token, + to: wallet.getAddress(), + }); + expect(result.baseCost > 0n).toEqual(true); + expect(result.l1GasLimit > 0n).toEqual(true); + expect(result.l2GasLimit > 0n).toEqual(true); + expect(result.maxPriorityFeePerGas! > 0n).toEqual(true); + expect(result.maxFeePerGas! > 0n).toEqual(true); + }); + + it('should return fee for base token deposit', async () => { + const token = await wallet.getBaseToken(); + const approveParams = await wallet.getDepositAllowanceParams(token, 1); + + await wallet.approveERC20(approveParams[0].token, approveParams[0].allowance); + + const result = await wallet.getFullRequiredDepositFee({ + token: token, + to: wallet.getAddress(), + }); + expect(result).not.toBeNull(); + }); + + it('should return fee for DAI token deposit', async () => { + const token = DAI_L1; + const approveParams = await wallet.getDepositAllowanceParams(token, 1); + + await wallet.approveERC20(approveParams[0].token, approveParams[0].allowance); + await wallet.approveERC20(approveParams[1].token, approveParams[1].allowance); + + const result = await wallet.getFullRequiredDepositFee({ + token: token, + to: wallet.getAddress(), + }); + expect(result.baseCost > 0n).toEqual(true); + expect(result.l1GasLimit > 0n).toEqual(true); + expect(result.l2GasLimit > 0n).toEqual(true); + expect(result.maxPriorityFeePerGas! > 0n).toEqual(true); + expect(result.maxFeePerGas! > 0n).toEqual(true); + }); + + it('should throw an error when there is not enough token allowance to cover the deposit', async () => { + const token = DAI_L1; + const randomWallet = new ZKSyncWallet( + ethAccounts.create().privateKey, + provider, + ethProvider, + ); + + // mint base token to random wallet + const baseToken = new ethProvider.eth.Contract( + IERC20ABI, + await wallet.getBaseToken(), + ); + + await baseToken.methods + .mint(randomWallet.getAddress(), web3Utils.toWei('0.5', 'ether')) + .send({ from: wallet.getAddress() }); + + // transfer ETH to random wallet so that base token approval tx can be performed + await ethProvider.eth.sendTransaction({ + from: wallet.getAddress(), + to: randomWallet.getAddress(), + value: web3Utils.toWei('0.1', 'ether'), + }); + + const approveParams = await randomWallet.getDepositAllowanceParams(token, 1); + // only approve base token + await randomWallet.approveERC20(approveParams[0].token, approveParams[0].allowance); + try { + await randomWallet.getFullRequiredDepositFee({ + token: token, + to: wallet.getAddress(), + }); + } catch (e) { + expect((e as Error).message).toEqual( + 'Not enough token allowance to cover the deposit!', + ); + } + }); + } + }); + + describe('#withdraw()', () => { + if (IS_ETH_BASED) { + it('should withdraw ETH to the L1 network', async () => { + const amount = 7n; + // const l2BalanceBeforeWithdrawal = await wallet.getBalance(); + const withdrawTx = await wallet.withdraw({ + token: LEGACY_ETH_ADDRESS, + to: wallet.getAddress(), + amount: amount, + }); + const receipt = await withdrawTx.wait(); + expect(receipt.transactionHash).toBeDefined(); + // works but in sepolia we need to wait few hours to finalize block + // const txHash = '0xd638355396984ae5f94660ad5b803890f952e036e846b5ea23213d8335e8aef6'; + // expect(await wallet.isWithdrawalFinalized(txHash)).toEqual(false); + // + // const result = await wallet.finalizeWithdrawal(txHash); + // // const l2BalanceAfterWithdrawal = await wallet.getBalance(); + // expect(result).not.toBeNull(); + // expect(l2BalanceBeforeWithdrawal - l2BalanceAfterWithdrawal >= amount).toEqual( + // true, + // ); + }); + + it.skip('should withdraw ETH to the L1 network using paymaster to cover fee', async () => { + const amount = 7n; + const minimalAllowance = 1n; + // const paymasterBalanceBeforeWithdrawal = await provider.eth.getBalance(PAYMASTER); + // const paymasterTokenBalanceBeforeWithdrawal = await provider.getTokenBalance( + // APPROVAL_TOKEN, + // PAYMASTER, + // ); + // const l2BalanceBeforeWithdrawal = await wallet.getBalance(); + // const l2ApprovalTokenBalanceBeforeWithdrawal = + // await wallet.getBalance(APPROVAL_TOKEN); + + const tx = await wallet.withdraw({ + token: ETH_ADDRESS_IN_CONTRACTS, + to: wallet.getAddress(), + amount: amount, + paymasterParams: getPaymasterParams(PAYMASTER, { + type: 'ApprovalBased', + token: APPROVAL_TOKEN, + minimalAllowance, + innerInput: new Uint8Array(), + }), + }); + const receipt = await tx.wait(); + + expect(receipt).toBeDefined(); + // const withdrawTx = await tx.waitFinalize(); + // expect(await wallet.isWithdrawalFinalized(withdrawTx.transactionHash)).toEqual( + // false, + // ); + // + // const result = await wallet.finalizeWithdrawal(withdrawTx.transactionHash); + // + // const paymasterBalanceAfterWithdrawal = await provider.eth.getBalance(PAYMASTER); + // const paymasterTokenBalanceAfterWithdrawal = await provider.getTokenBalance( + // APPROVAL_TOKEN, + // PAYMASTER, + // ); + // const l2BalanceAfterWithdrawal = await wallet.getBalance(); + // const l2ApprovalTokenBalanceAfterWithdrawal = + // await wallet.getBalance(APPROVAL_TOKEN); + // + // expect( + // paymasterBalanceBeforeWithdrawal - paymasterBalanceAfterWithdrawal >= 0n, + // ).toEqual(true); + // expect( + // paymasterTokenBalanceAfterWithdrawal - paymasterTokenBalanceBeforeWithdrawal, + // ).toEqual(minimalAllowance); + // + // expect(l2BalanceBeforeWithdrawal - l2BalanceAfterWithdrawal).toEqual(amount); + // expect( + // l2ApprovalTokenBalanceAfterWithdrawal === + // l2ApprovalTokenBalanceBeforeWithdrawal - minimalAllowance, + // ).toEqual(true); + // + // expect(result).not.toBeNull(); + }); + } else { + it('should withdraw ETH to the L1 network', async () => { + const amount = 7_000_000_000n; + const token = await wallet.l2TokenAddress(ETH_ADDRESS_IN_CONTRACTS); + const l2BalanceBeforeWithdrawal = await wallet.getBalance(token); + const tx = await wallet.withdraw({ + token: token, + to: wallet.getAddress(), + amount: amount, + }); + const withdrawTx = await tx.waitFinalize(); + expect(await wallet.isWithdrawalFinalized(withdrawTx.transactionHash)).toEqual( + false, + ); + + const result = await wallet.finalizeWithdrawal(withdrawTx.transactionHash); + const l2BalanceAfterWithdrawal = await wallet.getBalance(token); + expect(result).not.toBeNull(); + expect(l2BalanceBeforeWithdrawal - l2BalanceAfterWithdrawal >= amount).toEqual( + true, + ); + }); + + it('should withdraw base token to the L1 network', async () => { + const amount = 7_000_000_000n; + const baseToken = await wallet.getBaseToken(); + const l2BalanceBeforeWithdrawal = await wallet.getBalance(); + const tx = await wallet.withdraw({ + token: baseToken, + to: wallet.getAddress(), + amount: amount, + }); + const withdrawTx = await tx.waitFinalize(); + expect(await wallet.isWithdrawalFinalized(withdrawTx.transactionHash)).toEqual( + false, + ); + + const result = await wallet.finalizeWithdrawal(withdrawTx.transactionHash); + const l2BalanceAfterWithdrawal = await wallet.getBalance(); + expect(result).not.toBeNull(); + expect(l2BalanceBeforeWithdrawal - l2BalanceAfterWithdrawal >= amount).toEqual( + true, + ); + }); + + it('should withdraw ETH to the L1 network using paymaster to cover fee', async () => { + const amount = 7_000_000_000n; + const minimalAllowance = 1n; + + const token = await wallet.l2TokenAddress(ETH_ADDRESS_IN_CONTRACTS); + const paymasterBalanceBeforeWithdrawal = await provider.eth.getBalance(PAYMASTER); + const paymasterTokenBalanceBeforeWithdrawal = await provider.getTokenBalance( + APPROVAL_TOKEN, + PAYMASTER, + ); + const l2BalanceBeforeWithdrawal = await wallet.getBalance(token); + const l2ApprovalTokenBalanceBeforeWithdrawal = + await wallet.getBalance(APPROVAL_TOKEN); + + const tx = await wallet.withdraw({ + token: token, + to: wallet.getAddress(), + amount: amount, + paymasterParams: getPaymasterParams(PAYMASTER, { + type: 'ApprovalBased', + token: APPROVAL_TOKEN, + minimalAllowance: minimalAllowance, + innerInput: new Uint8Array(), + }), + }); + const withdrawTx = await tx.waitFinalize(); + expect(await wallet.isWithdrawalFinalized(withdrawTx.transactionHash)).toEqual( + false, + ); + + const result = await wallet.finalizeWithdrawal(withdrawTx.transactionHash); + + const paymasterBalanceAfterWithdrawal = await provider.getBalance(PAYMASTER); + const paymasterTokenBalanceAfterWithdrawal = await provider.getTokenBalance( + APPROVAL_TOKEN, + PAYMASTER, + ); + const l2BalanceAfterWithdrawal = await wallet.getBalance(token); + const l2ApprovalTokenBalanceAfterWithdrawal = + await wallet.getBalance(APPROVAL_TOKEN); + + expect( + paymasterBalanceBeforeWithdrawal - paymasterBalanceAfterWithdrawal >= 0n, + ).toEqual(true); + expect( + paymasterTokenBalanceAfterWithdrawal - paymasterTokenBalanceBeforeWithdrawal, + ).toEqual(minimalAllowance); + + expect(l2BalanceBeforeWithdrawal - l2BalanceAfterWithdrawal).toEqual(amount); + expect( + l2ApprovalTokenBalanceAfterWithdrawal === + l2ApprovalTokenBalanceBeforeWithdrawal - minimalAllowance, + ).toEqual(true); + + expect(result).not.toBeNull(); + }); + } + + it('should withdraw USDC to the L1 network', async () => { + const amount = 5n; + const l2USDC = await provider.l2TokenAddress(USDC_L1); + const l2BalanceBeforeWithdrawal = await wallet.getBalance(l2USDC); + const l1BalanceBeforeWithdrawal = await wallet.getBalanceL1(USDC_L1); + expect(l2BalanceBeforeWithdrawal).toBeGreaterThan(0n); + expect(l1BalanceBeforeWithdrawal).toBeDefined(); + const tx = await wallet.withdraw({ + token: l2USDC, + to: wallet.getAddress(), + amount: amount, + }); + const receipt = await tx.wait(); + expect(receipt.transactionHash).toBeDefined(); + // const withdrawTx = await tx.waitFinalize(); + // expect(await wallet.isWithdrawalFinalized(withdrawTx.transactionHash)).toEqual(false); + // + // const result = await wallet.finalizeWithdrawal(withdrawTx.transactionHash); + // const l2BalanceAfterWithdrawal = await wallet.getBalance(l2USDC); + // const l1BalanceAfterWithdrawal = await wallet.getBalanceL1(DAI_L1); + + // expect(result).not.toBeNull(); + // expect(l2BalanceBeforeWithdrawal - l2BalanceAfterWithdrawal).toEqual(amount); + // expect(l1BalanceAfterWithdrawal - l1BalanceBeforeWithdrawal).toEqual(amount); + }); + + it.skip('should withdraw USDC to the L1 network using paymaster to cover fee', async () => { + const amount = 5n; + const minimalAllowance = 1n; + const l2USDC = await provider.l2TokenAddress(USDC_L1); + + const paymasterBalanceBeforeWithdrawal = await provider.getBalance(PAYMASTER); + const paymasterTokenBalanceBeforeWithdrawal = await provider.getTokenBalance( + APPROVAL_TOKEN, + PAYMASTER, + ); + const l2BalanceBeforeWithdrawal = await wallet.getBalance(l2USDC); + const l1BalanceBeforeWithdrawal = await wallet.getBalanceL1(USDC_L1); + const l2ApprovalTokenBalanceBeforeWithdrawal = await wallet.getBalance(APPROVAL_TOKEN); + + const tx = await wallet.withdraw({ + token: l2USDC, + to: wallet.getAddress(), + amount: amount, + paymasterParams: getPaymasterParams(PAYMASTER, { + type: 'ApprovalBased', + token: APPROVAL_TOKEN, + minimalAllowance: minimalAllowance, + innerInput: new Uint8Array(), + }), + }); + const withdrawTx = await tx.waitFinalize(); + expect(await wallet.isWithdrawalFinalized(withdrawTx.transactionHash)).toEqual(false); + + const result = await wallet.finalizeWithdrawal(withdrawTx.transactionHash); + + const paymasterBalanceAfterWithdrawal = await provider.getBalance(PAYMASTER); + const paymasterTokenBalanceAfterWithdrawal = await provider.getBalance( + APPROVAL_TOKEN, + PAYMASTER, + ); + const l2BalanceAfterWithdrawal = await wallet.getBalance(l2USDC); + const l1BalanceAfterWithdrawal = await wallet.getBalanceL1(USDC_L1); + const l2ApprovalTokenBalanceAfterWithdrawal = await wallet.getBalance(APPROVAL_TOKEN); + + expect( + paymasterBalanceBeforeWithdrawal - paymasterBalanceAfterWithdrawal >= 0n, + ).toEqual(true); + expect( + paymasterTokenBalanceAfterWithdrawal - paymasterTokenBalanceBeforeWithdrawal, + ).toEqual(minimalAllowance); + expect( + l2ApprovalTokenBalanceAfterWithdrawal === + l2ApprovalTokenBalanceBeforeWithdrawal - minimalAllowance, + ).toEqual(true); + + expect(result).not.toBeNull(); + expect(l2BalanceBeforeWithdrawal - l2BalanceAfterWithdrawal).toEqual(amount); + expect(l1BalanceAfterWithdrawal - l1BalanceBeforeWithdrawal).toEqual(amount); + }); + }); + + describe('#getRequestExecuteTx()', () => { + const amount = 7_000_000_000; + if (IS_ETH_BASED) { + it('should return request execute transaction', async () => { + const result = await wallet.getRequestExecuteTx({ + contractAddress: await provider.getBridgehubContractAddress(), + calldata: '0x', + l2Value: amount, + }); + expect(result).not.toBeNull(); + }); + } else { + it('should return request execute transaction', async () => { + const result = await wallet.getRequestExecuteTx({ + contractAddress: wallet.getAddress(), + calldata: '0x', + l2Value: amount, + overrides: { nonce: 0 }, + }); + expect(result).not.toBeNull(); + }); + } + }); + + describe('#estimateGasRequestExecute()', () => { + if (IS_ETH_BASED) { + it('should return gas estimation for request execute transaction', async () => { + const result = await wallet.estimateGasRequestExecute({ + contractAddress: await provider.getBridgehubContractAddress(), + calldata: '0x', + l2Value: 7_000_000_000, + }); + expect(result > 0n).toEqual(true); + }); + } else { + it('should return gas estimation for request execute transaction', async () => { + const tx = { + contractAddress: wallet.getAddress(), + calldata: '0x', + l2Value: 7_000_000_000, + overrides: { value: 0 }, + }; + + const approveParams = await wallet.getRequestExecuteAllowanceParams(tx); + await wallet.approveERC20(approveParams.token, approveParams.allowance); + + const result = await wallet.estimateGasRequestExecute(tx); + expect(result > 0n).toEqual(true); + }); + } + }); + + describe('#requestExecute()', () => { + if (IS_ETH_BASED) { + it('should request transaction execution on L2 network', async () => { + const amount = 7_000_000_000; + const l2BalanceBeforeExecution = await wallet.getBalance(); + const l1BalanceBeforeExecution = await wallet.getBalanceL1(); + const tx = await wallet.requestExecute({ + contractAddress: await provider.getBridgehubContractAddress(), + calldata: '0x', + l2Value: amount, + l2GasLimit: 900_000, + }); + const result = await tx.waitFinalize(); + const l2BalanceAfterExecution = await wallet.getBalance(); + const l1BalanceAfterExecution = await wallet.getBalanceL1(); + expect(result).not.toBeNull(); + expect(l2BalanceAfterExecution - l2BalanceBeforeExecution >= amount).toEqual(true); + expect(l1BalanceBeforeExecution - l1BalanceAfterExecution >= amount).toEqual(true); + }); + } else { + it('should request transaction execution on L2 network', async () => { + const amount = 7_000_000_000; + const request = { + contractAddress: wallet.getAddress(), + calldata: '0x', + l2Value: amount, + l2GasLimit: 1_319_957n, + operatorTip: 0, + gasPerPubdataByte: 800, + refundRecipient: wallet.getAddress(), + overrides: { + maxFeePerGas: 1_000_000_010n, + maxPriorityFeePerGas: 1_000_000_000n, + gasLimit: 238_654n, + value: 0, + }, + }; + + const approveParams = await wallet.getRequestExecuteAllowanceParams(request); + await wallet.approveERC20(approveParams.token, approveParams.allowance); + + const l2BalanceBeforeExecution = await wallet.getBalance(); + const l1BalanceBeforeExecution = await wallet.getBalanceL1(); + + const tx = await wallet.requestExecute(request); + const result = await tx.wait(); + const l2BalanceAfterExecution = await wallet.getBalance(); + const l1BalanceAfterExecution = await wallet.getBalanceL1(); + expect(result).not.toBeNull(); + expect(l2BalanceAfterExecution - l2BalanceBeforeExecution >= amount).toEqual(true); + expect(l1BalanceBeforeExecution - l1BalanceAfterExecution >= amount).toEqual(true); + }); + } + }); + + describe('#transfer()', () => { + it('should transfer ETH or base token depending on chain type', async () => { + const amount = 7n; + const balanceBeforeTransfer = await provider.getBalance(ADDRESS1); + const result = await wallet.transfer({ + token: await wallet.getBaseToken(), + to: ADDRESS1, + amount: amount, + }); + await result.wait(); + const balanceAfterTransfer = await provider.getBalance(ADDRESS1); + expect(result).not.toBeNull(); + expect(balanceAfterTransfer - balanceBeforeTransfer).toEqual(amount); + }); + + it.skip('should transfer ETH or base token depending on chain using paymaster to cover fee', async () => { + const amount = 7_000_000_000n; + const minimalAllowance = 1n; + + const paymasterBalanceBeforeTransfer = await provider.getBalance(PAYMASTER); + const paymasterTokenBalanceBeforeTransfer = await provider.getTokenBalance( + APPROVAL_TOKEN, + PAYMASTER, + ); + const senderBalanceBeforeTransfer = await wallet.getBalance(); + const senderApprovalTokenBalanceBeforeTransfer = + await wallet.getBalance(APPROVAL_TOKEN); + const receiverBalanceBeforeTransfer = await provider.getBalance(ADDRESS2, 'latest'); + + const result = await wallet.transfer({ + to: ADDRESS2, + amount: amount, + paymasterParams: getPaymasterParams(PAYMASTER, { + type: 'ApprovalBased', + token: APPROVAL_TOKEN, + minimalAllowance: minimalAllowance, + innerInput: new Uint8Array(), + }), + }); + + const paymasterBalanceAfterTransfer = await provider.getBalance(PAYMASTER); + const paymasterTokenBalanceAfterTransfer = await provider.getBalance( + APPROVAL_TOKEN, + PAYMASTER, + ); + const senderBalanceAfterTransfer = await wallet.getBalance(); + const senderApprovalTokenBalanceAfterTransfer = await wallet.getBalance(APPROVAL_TOKEN); + const receiverBalanceAfterTransfer = await provider.getBalance(ADDRESS2); + + expect(paymasterBalanceBeforeTransfer - paymasterBalanceAfterTransfer >= 0n).toEqual( + true, + ); + expect( + paymasterTokenBalanceAfterTransfer - paymasterTokenBalanceBeforeTransfer, + ).toEqual(minimalAllowance); + + expect(senderBalanceBeforeTransfer - senderBalanceAfterTransfer).toEqual(amount); + expect( + senderApprovalTokenBalanceAfterTransfer === + senderApprovalTokenBalanceBeforeTransfer - minimalAllowance, + ).toEqual(true); + + expect(result).not.toBeNull(); + expect(receiverBalanceAfterTransfer - receiverBalanceBeforeTransfer).toEqual(amount); + }); + + if (!IS_ETH_BASED) { + it('should transfer ETH on non eth based chain', async () => { + const amount = 7_000_000_000n; + const token = await wallet.l2TokenAddress(ETH_ADDRESS_IN_CONTRACTS); + const balanceBeforeTransfer = await provider.getTokenBalance(token, ADDRESS2); + const result = await wallet.transfer({ + token: LEGACY_ETH_ADDRESS, + to: ADDRESS2, + amount: amount, + }); + const balanceAfterTransfer = await provider.getTokenBalance(token, ADDRESS2); + expect(result).not.toBeNull(); + expect(balanceAfterTransfer - balanceBeforeTransfer).toEqual(amount); + }); + + it('should transfer ETH using paymaster to cover fee', async () => { + const amount = 7_000_000_000n; + const minimalAllowance = 1n; + + const token = await wallet.l2TokenAddress(ETH_ADDRESS_IN_CONTRACTS); + const paymasterBalanceBeforeTransfer = await provider.getBalance(PAYMASTER); + const paymasterTokenBalanceBeforeTransfer = await provider.getTokenBalance( + APPROVAL_TOKEN, + PAYMASTER, + ); + const senderBalanceBeforeTransfer = await wallet.getBalance(token); + const senderApprovalTokenBalanceBeforeTransfer = + await wallet.getBalance(APPROVAL_TOKEN); + const receiverBalanceBeforeTransfer = await provider.getTokenBalance( + token, + ADDRESS2, + ); + + const result = await wallet.transfer({ + token: token, + to: ADDRESS2, + amount: amount, + paymasterParams: getPaymasterParams(PAYMASTER, { + type: 'ApprovalBased', + token: APPROVAL_TOKEN, + minimalAllowance: minimalAllowance, + innerInput: new Uint8Array(), + }), + }); + + const paymasterBalanceAfterTransfer = await provider.getBalance(PAYMASTER); + const paymasterTokenBalanceAfterTransfer = await provider.getTokenBalance( + APPROVAL_TOKEN, + PAYMASTER, + ); + const senderBalanceAfterTransfer = await wallet.getBalance(token); + const senderApprovalTokenBalanceAfterTransfer = + await wallet.getBalance(APPROVAL_TOKEN); + const receiverBalanceAfterTransfer = await provider.getTokenBalance( + token, + ADDRESS2, + ); + + expect( + paymasterBalanceBeforeTransfer - paymasterBalanceAfterTransfer >= 0n, + ).toEqual(true); + expect( + paymasterTokenBalanceAfterTransfer - paymasterTokenBalanceBeforeTransfer, + ).toEqual(minimalAllowance); + + expect(senderBalanceBeforeTransfer - senderBalanceAfterTransfer).toEqual(amount); + expect( + senderApprovalTokenBalanceAfterTransfer === + senderApprovalTokenBalanceBeforeTransfer - minimalAllowance, + ).toEqual(true); + + expect(result).not.toBeNull(); + expect(receiverBalanceAfterTransfer - receiverBalanceBeforeTransfer).toEqual( + amount, + ); + }); + } + + it('should transfer USDC', async () => { + const amount = 5n; + const l2USDC = await provider.l2TokenAddress(USDC_L1); + const balanceBeforeTransfer = await provider.getTokenBalance(l2USDC, ADDRESS2); + const result = await wallet.transfer({ + token: l2USDC, + to: ADDRESS2, + amount: amount, + }); + const receipt = await result.wait(); + console.log('receipt', receipt); + const balanceAfterTransfer = await provider.getTokenBalance(l2USDC, ADDRESS2); + expect(result).not.toBeNull(); + expect(balanceAfterTransfer - balanceBeforeTransfer).toEqual(amount); + }); + + it.skip('should transfer DAI using paymaster to cover fee', async () => { + const amount = 5n; + const minimalAllowance = 1n; + const l2DAI = await provider.l2TokenAddress(DAI_L1); + + const paymasterBalanceBeforeTransfer = await provider.getBalance(PAYMASTER); + const paymasterTokenBalanceBeforeTransfer = await provider.getTokenBalance( + APPROVAL_TOKEN, + PAYMASTER, + ); + const senderBalanceBeforeTransfer = await wallet.getBalance(l2DAI); + const senderApprovalTokenBalanceBeforeTransfer = + await wallet.getBalance(APPROVAL_TOKEN); + const receiverBalanceBeforeTransfer = await provider.getTokenBalance(l2DAI, ADDRESS2); + + const result = await wallet.transfer({ + token: l2DAI, + to: ADDRESS2, + amount: amount, + paymasterParams: getPaymasterParams(PAYMASTER, { + type: 'ApprovalBased', + token: APPROVAL_TOKEN, + minimalAllowance: minimalAllowance, + innerInput: new Uint8Array(), + }), + }); + + const paymasterBalanceAfterTransfer = await provider.getBalance(PAYMASTER); + const paymasterTokenBalanceAfterTransfer = await provider.getTokenBalance( + APPROVAL_TOKEN, + PAYMASTER, + ); + const senderBalanceAfterTransfer = await wallet.getBalance(l2DAI); + const senderApprovalTokenBalanceAfterTransfer = await wallet.getBalance(APPROVAL_TOKEN); + const receiverBalanceAfterTransfer = await provider.getTokenBalance(l2DAI, ADDRESS2); + + expect(paymasterBalanceBeforeTransfer - paymasterBalanceAfterTransfer >= 0n).toEqual( + true, + ); + expect( + paymasterTokenBalanceAfterTransfer - paymasterTokenBalanceBeforeTransfer, + ).toEqual(minimalAllowance); + + expect(senderBalanceBeforeTransfer - senderBalanceAfterTransfer).toEqual(amount); + expect( + senderApprovalTokenBalanceAfterTransfer === + senderApprovalTokenBalanceBeforeTransfer - minimalAllowance, + ).toEqual(true); + + expect(result).not.toBeNull(); + expect(receiverBalanceAfterTransfer - receiverBalanceBeforeTransfer).toEqual(amount); + }); + + if (!IS_ETH_BASED) { + it('should transfer base token', async () => { + const amount = 7_000_000_000n; + const balanceBeforeTransfer = await provider.getBalance(ADDRESS2); + const result = await wallet.transfer({ + token: await wallet.getBaseToken(), + to: ADDRESS2, + amount: amount, + }); + const balanceAfterTransfer = await provider.getBalance(ADDRESS2); + expect(result).not.toBeNull(); + expect(balanceAfterTransfer - balanceBeforeTransfer).toEqual(amount); + }); + } + }); + + describe('#signTransaction()', () => { + it('should return a signed type EIP1559 transaction', async () => { + const result = await wallet.signTransaction({ + type: 2, + to: ADDRESS2, + value: 7_000_000_000n, + }); + expect(result).not.toBeNull(); + }); + + it('should return a signed EIP712 transaction', async () => { + const result = await wallet.signTransaction({ + type: EIP712_TX_TYPE, + to: ADDRESS2, + value: 1, + }); + expect(result).not.toBeNull(); + }); + + it('should throw an error when `tx.from` is mismatched from private key', async () => { + try { + await wallet.signTransaction({ + type: EIP712_TX_TYPE, + from: ADDRESS2, + to: ADDRESS2, + value: 1, + }); + } catch (e) { + expect((e as Error).message).toMatch('Transaction from mismatch'); + } + }); + }); +}); diff --git a/test/integration/zksync.contracts.test.ts b/test/integration/zksync.contracts.test.ts new file mode 100644 index 0000000..e806357 --- /dev/null +++ b/test/integration/zksync.contracts.test.ts @@ -0,0 +1,40 @@ +import { Web3 } from 'web3'; +import { QuickNodeProvider, Network } from 'web3-rpc-providers'; +import type { ZKSyncContractsCollection } from 'src/plugin'; +import { ZkSyncPlugin } from '../../src'; + +describe('ZkSyncPlugin rpc tests', () => { + let web3: Web3; + let zkSync: ZkSyncPlugin; + let contracts: ZKSyncContractsCollection; + + beforeAll(async () => { + web3 = new Web3(new QuickNodeProvider(Network.ETH_SEPOLIA)); + zkSync = new ZkSyncPlugin('https://sepolia.era.zksync.dev'); + }); + + it('contracts only works after registering the plugin', async () => { + expect(zkSync.Contracts).rejects.toThrow(); + web3.registerPlugin(zkSync); + + // after registering the plugin, the Contracts property should be without throwing an error + contracts = await zkSync.Contracts; + expect(contracts).toBeDefined(); + }); + + it('should be able to call contracts methods', async () => { + expect(await contracts.L1.ZkSyncMainContract).toBeDefined(); + + // ZkSyncMainContract is a proxy contract at 0x9A6DE0f62Aa270A8bCB1e2610078650D539B1Ef9 + // that is a proxy to the actual contract at 0x550cf73F4b50aA0DF0257f2D07630D48fA00f73a + // TODO: Need to check how to either get the actual contract address or how to call the proxy with now issues + // current error when calling the proxy is: "ContractExecutionError: Error happened while trying to execute a function inside a smart contract" + // related web3.js issue: https://github.com/web3/web3.js/issues/7143 + contracts.L1.ZkSyncMainContract.options.address = + '0x550cf73F4b50aA0DF0257f2D07630D48fA00f73a'; + + const contractName = await contracts.L1.ZkSyncMainContract.methods.getName().call(); + + expect(contractName).toBe('MailboxFacet'); + }); +}); diff --git a/test/jest.config.js b/test/jest.config.js index 3abcbd9..6231bde 100644 --- a/test/jest.config.js +++ b/test/jest.config.js @@ -1,5 +1,5 @@ /** @type {import('ts-jest').JestConfigWithTsJest} */ module.exports = { - preset: "ts-jest", - testEnvironment: "node", + preset: 'ts-jest', + testEnvironment: 'node', }; diff --git a/test/unit/index.test.ts b/test/unit/index.test.ts index a18b5a4..267b6cc 100644 --- a/test/unit/index.test.ts +++ b/test/unit/index.test.ts @@ -3,8 +3,8 @@ import { ZkSyncPlugin } from '../../src'; describe('ZkSyncPlugin tests', () => { it('should register ZkSync plugin on Web3Context instance', () => { - const web3 = new Web3('http://some-rpc-url.com'); - web3.registerPlugin(new ZkSyncPlugin()); + const web3 = new Web3('https://sepolia.era.zksync.dev'); + web3.registerPlugin(new ZkSyncPlugin('https://sepolia.era.zksync.dev')); expect(web3.zkSync).toBeDefined(); }); }); diff --git a/test/unit/signer.test.ts b/test/unit/signer.test.ts new file mode 100644 index 0000000..fc28770 --- /dev/null +++ b/test/unit/signer.test.ts @@ -0,0 +1,75 @@ +// import '../custom-matchers'; +import { EIP712Signer } from '../../src/Eip712'; +import { ADDRESS1, ADDRESS2 } from '../utils'; +import { ZeroAddress } from '../../src/types'; +import { DEFAULT_GAS_PER_PUBDATA_LIMIT, EIP712_TX_TYPE } from '../../src/constants'; +import { EIP712 } from '../../src/Eip712'; + +describe('EIP712Signer', () => { + describe('#getSignInput()', () => { + it('should return a populated transaction', async () => { + const tx = { + txType: EIP712_TX_TYPE, + from: ADDRESS1, + to: ADDRESS2, + gasLimit: 21_000n, + gasPerPubdataByteLimit: DEFAULT_GAS_PER_PUBDATA_LIMIT, + maxFeePerGas: 250_000_000n, + maxPriorityFeePerGas: 250_000_000n, + paymaster: ZeroAddress, + nonce: 0, + value: 7_000_000n, + data: '0x', + factoryDeps: [], + paymasterInput: '0x', + }; + + const result = EIP712.getSignInput({ + type: EIP712_TX_TYPE, + to: ADDRESS2, + value: 7_000_000n, + from: ADDRESS1, + nonce: 0, + chainId: 270n, + gasPrice: 250_000_000n, + gasLimit: 21_000n, + customData: {}, + }); + expect(result).toEqual(tx); + }); + it('should return a populated transaction with default values', async () => { + const tx = { + txType: EIP712_TX_TYPE, + from: ADDRESS1, + to: ADDRESS2, + gasLimit: 0n, + gasPerPubdataByteLimit: DEFAULT_GAS_PER_PUBDATA_LIMIT, + maxFeePerGas: 0n, + maxPriorityFeePerGas: 0n, + paymaster: ZeroAddress, + nonce: 0, + value: 0n, + data: '0x', + factoryDeps: [], + paymasterInput: '0x', + }; + + const result = EIP712.getSignInput({ + type: EIP712_TX_TYPE, + to: ADDRESS2, + from: ADDRESS1, + }); + expect(result).toEqual(tx); + }); + }); + + describe('#getSignedDigest()', () => { + it('should throw an error when chain ID is not specified', async () => { + try { + EIP712Signer.getSignedDigest({}); + } catch (e) { + expect((e as Error).message).toEqual("Transaction chainId isn't set!"); + } + }); + }); +}); diff --git a/test/unit/utils.test.ts b/test/unit/utils.test.ts index 7a12da7..61c4e84 100644 --- a/test/unit/utils.test.ts +++ b/test/unit/utils.test.ts @@ -1,9 +1,11 @@ import { Web3 } from 'web3'; import * as web3Accounts from 'web3-eth-accounts'; +import type * as web3Types from 'web3-types'; import type { types } from '../../src'; import { utils } from '../../src'; -import { ADDRESS1, ADDRESS2 } from '../utils'; +import { ADDRESS1, ADDRESS3 } from '../utils'; import * as constants from '../../src/constants'; +import { getL2HashFromPriorityOp } from '../../src/utils'; describe('utils', () => { describe('#getHashedL2ToL1Msg()', () => { @@ -162,7 +164,7 @@ describe('utils', () => { { chainId: 270, from: ADDRESS1, - to: ADDRESS2, + to: ADDRESS3, value: 1_000_000, }, '0x73a20167b8d23b610b058c05368174495adf7da3a4ed4a57eb6dbdeb1fafc24aaf87530d663a0d061f69bb564d2c6fb46ae5ae776bbd4bd2a2a4478b9cd1b42a', @@ -225,7 +227,7 @@ describe('utils', () => { maxPriorityFeePerGas: 0n, maxFeePerGas: 0n, gasLimit: 0n, - to: ADDRESS2, + to: ADDRESS3, value: 1_000_000n, data: '0x', chainId: 270n, @@ -252,7 +254,7 @@ describe('utils', () => { maxPriorityFeePerGas: 0n, maxFeePerGas: 0n, gasLimit: 0n, - to: ADDRESS2, + to: ADDRESS3, value: 0n, data: '0x', chainId: 270n, @@ -289,4 +291,131 @@ describe('utils', () => { expect(isValidSignature).toBe(true); }); }); + describe('#isMessageSignatureCorrect()', () => { + it('should return true if signature made by a private key was correct', async () => { + const receipt = { + transactionHash: + '0xd5660cc02410f62bff04fefeefba6217ad31a670063af0c3fed5bd02a8fa9065', + blockHash: '0x9df9bbbfcadf7afb7fecd1133c306a7d37a62c3fc9c8cae4b3480666d79c42cf', + blockNumber: '0x5f6994', + logsBloom: + '0x00040004800000000000020000000000000000001001000000020002000000020000000000000000000200800001000200040000000000000000000000200000000000020000000400000008000000000000000000000000000000081000020000000000000000400004000010000000000000000000000000040010000000000000000000000000000001000200000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000000222000000000000000000000000000000000000000000000001000002000010001000000000000000000002080080020010000000000000000000000000', + gasUsed: '0x420e7', + cumulativeGasUsed: '0x13867f2', + transactionIndex: '0x97', + from: '0x81d3ce1a567389f6cb1178a68eb33aa6f081dc52', + to: '0x35a54c8c757806eb6820629bc82d90e056394c92', + type: '0x2', + effectiveGasPrice: '0x22b3acaea', + logs: [ + { + blockHash: + '0x9df9bbbfcadf7afb7fecd1133c306a7d37a62c3fc9c8cae4b3480666d79c42cf', + address: '0x3e8b2fe58675126ed30d0d12dea2a9bda72d18ae', + logIndex: '0x11a', + data: '0x000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000006ff3c58a20e0', + removed: false, + topics: [ + '0x249bc8a55d0c4a0034b9aaa6be739bec2d4466e5d859bec9566a8553c405c838', + '0x000000000000000000000000000000000000000000000000000000000000012c', + '0x00000000000000000000000081d3ce1a567389f6cb1178a68eb33aa6f081dc52', + ], + blockNumber: '0x5f6994', + transactionIndex: '0x97', + transactionHash: + '0xd5660cc02410f62bff04fefeefba6217ad31a670063af0c3fed5bd02a8fa9065', + }, + { + blockHash: + '0x9df9bbbfcadf7afb7fecd1133c306a7d37a62c3fc9c8cae4b3480666d79c42cf', + address: '0x53844f9577c2334e541aec7df7174ece5df1fcf0', + logIndex: '0x11b', + data: '0x0000000000000000000000000000000000000000000000000000000000000005', + removed: false, + topics: [ + '0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef', + '0x00000000000000000000000081d3ce1a567389f6cb1178a68eb33aa6f081dc52', + '0x0000000000000000000000003e8b2fe58675126ed30d0d12dea2a9bda72d18ae', + ], + blockNumber: '0x5f6994', + transactionIndex: '0x97', + transactionHash: + '0xd5660cc02410f62bff04fefeefba6217ad31a670063af0c3fed5bd02a8fa9065', + }, + { + blockHash: + '0x9df9bbbfcadf7afb7fecd1133c306a7d37a62c3fc9c8cae4b3480666d79c42cf', + address: '0x53844f9577c2334e541aec7df7174ece5df1fcf0', + logIndex: '0x11c', + data: '0x0000000000000000000000000000000000000000000000000000000000000000', + removed: false, + topics: [ + '0x8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925', + '0x00000000000000000000000081d3ce1a567389f6cb1178a68eb33aa6f081dc52', + '0x0000000000000000000000003e8b2fe58675126ed30d0d12dea2a9bda72d18ae', + ], + blockNumber: '0x5f6994', + transactionIndex: '0x97', + transactionHash: + '0xd5660cc02410f62bff04fefeefba6217ad31a670063af0c3fed5bd02a8fa9065', + }, + { + blockHash: + '0x9df9bbbfcadf7afb7fecd1133c306a7d37a62c3fc9c8cae4b3480666d79c42cf', + address: '0x3e8b2fe58675126ed30d0d12dea2a9bda72d18ae', + logIndex: '0x11d', + data: '0x00000000000000000000000081d3ce1a567389f6cb1178a68eb33aa6f081dc5200000000000000000000000053844f9577c2334e541aec7df7174ece5df1fcf00000000000000000000000000000000000000000000000000000000000000005', + removed: false, + topics: [ + '0x8768405a01370685449c74c293804d6c9cc216d170acdbdba50b33ed4144447f', + '0x000000000000000000000000000000000000000000000000000000000000012c', + '0x14ea5757930256307269b33bbf0794af5b5ad979c2e1fffcc9b0f7486416f2e5', + '0x00000000000000000000000081d3ce1a567389f6cb1178a68eb33aa6f081dc52', + ], + blockNumber: '0x5f6994', + transactionIndex: '0x97', + transactionHash: + '0xd5660cc02410f62bff04fefeefba6217ad31a670063af0c3fed5bd02a8fa9065', + }, + { + blockHash: + '0x9df9bbbfcadf7afb7fecd1133c306a7d37a62c3fc9c8cae4b3480666d79c42cf', + address: '0x9a6de0f62aa270a8bcb1e2610078650d539b1ef9', + logIndex: '0x11e', + data: '0x000000000000000000000000000000000000000000000000000000000000b77d801ee033bc4d9df4e224f814b895fcd3b8d37648c563b4105c73d491a11cc64800000000000000000000000000000000000000000000000000000000668857c800000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000062000000000000000000000000000000000000000000000000000000000000000ff0000000000000000000000004f9c2fe58675126ed30d0d12dea2a9bda72d29bf000000000000000000000000681a1afdc2e06776816386500d2d461a6c96cb4500000000000000000000000000000000000000000000000000000000000493e000000000000000000000000000000000000000000000000000000000000003200000000000000000000000000000000000000000000000000000000015d76ea200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000b77d000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006ff3c58a20e000000000000000000000000081d3ce1a567389f6cb1178a68eb33aa6f081dc5200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000026000000000000000000000000000000000000000000000000000000000000005000000000000000000000000000000000000000000000000000000000000000520000000000000000000000000000000000000000000000000000000000000054000000000000000000000000000000000000000000000000000000000000005600000000000000000000000000000000000000000000000000000000000000264cfe7af7c00000000000000000000000081d3ce1a567389f6cb1178a68eb33aa6f081dc5200000000000000000000000081d3ce1a567389f6cb1178a68eb33aa6f081dc5200000000000000000000000053844f9577c2334e541aec7df7174ece5df1fcf0000000000000000000000000000000000000000000000000000000000000000500000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000001a0000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000e00000000000000000000000000000000000000000000000000000000000000160000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000008444149204d6f636b0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000047444414900000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000120000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000', + removed: false, + topics: [ + '0x4531cd5795773d7101c17bdeb9f5ab7f47d7056017506f937083be5d6e77a382', + ], + blockNumber: '0x5f6994', + transactionIndex: '0x97', + transactionHash: + '0xd5660cc02410f62bff04fefeefba6217ad31a670063af0c3fed5bd02a8fa9065', + }, + { + blockHash: + '0x9df9bbbfcadf7afb7fecd1133c306a7d37a62c3fc9c8cae4b3480666d79c42cf', + address: '0x3e8b2fe58675126ed30d0d12dea2a9bda72d18ae', + logIndex: '0x11f', + data: '0x', + removed: false, + topics: [ + '0xe4def01b981193a97a9e81230d7b9f31812ceaf23f864a828a82c687911cb2df', + '0x000000000000000000000000000000000000000000000000000000000000012c', + '0x14ea5757930256307269b33bbf0794af5b5ad979c2e1fffcc9b0f7486416f2e5', + '0x801ee033bc4d9df4e224f814b895fcd3b8d37648c563b4105c73d491a11cc648', + ], + blockNumber: '0x5f6994', + transactionIndex: '0x97', + transactionHash: + '0xd5660cc02410f62bff04fefeefba6217ad31a670063af0c3fed5bd02a8fa9065', + }, + ], + status: '0x1', + } as web3Types.TransactionReceipt; + const addr = '0x9a6de0f62aa270a8bcb1e2610078650d539b1ef9'; + const hash = getL2HashFromPriorityOp(receipt, addr); + expect(hash).toBe('0x801ee033bc4d9df4e224f814b895fcd3b8d37648c563b4105c73d491a11cc648'); + }); + }); }); diff --git a/test/unit/web3zksync-l2.as.provider.test.ts b/test/unit/web3zksync-l2.as.provider.test.ts new file mode 100644 index 0000000..b1c4e5f --- /dev/null +++ b/test/unit/web3zksync-l2.as.provider.test.ts @@ -0,0 +1,59 @@ +import type { Transaction } from 'web3-types'; +import { ethRpcMethods } from 'web3-rpc-methods'; + +import { Web3ZkSyncL2, Web3ZkSyncL1 } from '../../src'; +import { getPriorityOpResponse } from '../../src/utils'; +import type { PriorityL1OpResponse } from '../../src/types'; + +jest.mock('web3-rpc-methods'); + +describe('Web3ZkSyncL2 as a Provider', () => { + it('should correctly initialize and assign function properties in getPriorityOpResponse', async () => { + const web3ZkSyncL2 = new Web3ZkSyncL2('https://mainnet.era.zksync.io'); + const acc = web3ZkSyncL2.eth.accounts.privateKeyToAccount( + '0x1f953dc9b6437fb94fcafa5dabe3faa0c34315b954dd66f41bf53273339c6d26', + ); + web3ZkSyncL2.eth.accounts.wallet.add(acc); + // TODO: remove the commented lines after the test passes + // NOTE: this was an object of TransactionResponse in ethers + const l1Tx: Transaction = { + // hash: '0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef', + from: acc.address, + // blockHash: '0xabcd1234567890abcdef1234567890abcdef1234567890abcdef1234567890', + // blockNumber: 123456, + to: '0xabcdef1234567890abcdef1234567890abcdef12', + type: 0, + nonce: 42, + gasLimit: 2000000n, + // index: 3, + gasPrice: 1000000000n, + // data: '0x', + value: 0n, + chainId: 1337n, + // // not used in tested code, keep the test simple + // signature: null as unknown as Signature, + }; + + // mock ethRpcMethods.sendTransaction + jest.spyOn(ethRpcMethods, 'sendTransaction').mockResolvedValue(''); + // @ts-ignore + jest.spyOn(ethRpcMethods, 'signTransaction').mockResolvedValue({ + // @ts-ignore + raw: '0xabcd1234567890abcdef1234567890abcdef1234567890abcdef1234567890', + }); + + const signed = await web3ZkSyncL2.signTransaction(l1Tx); + + const txPromise = web3ZkSyncL2.sendRawTransaction(signed); + + const priorityOpResponse = await getPriorityOpResponse(new Web3ZkSyncL1(), txPromise); + // 'The waitL1Commit function should be properly initialized' + expect(typeof (priorityOpResponse as PriorityL1OpResponse).waitL1Commit).toEqual( + 'function', + ); + // 'The wait function should be properly initialized' + expect(typeof priorityOpResponse.wait).toBe('function'); + // 'The waitFinalize function should be properly initialized' + expect(typeof priorityOpResponse.waitFinalize).toEqual('function'); + }); +}); diff --git a/test/utils.ts b/test/utils.ts index 525b832..2207e89 100644 --- a/test/utils.ts +++ b/test/utils.ts @@ -1,11 +1,26 @@ export const ADDRESS1 = '0x36615Cf349d7F6344891B1e7CA7C72883F5dc049'; +export const ADDRESS3 = '0xa61464658AfeAf65CccaaFD3a512b69A83B77618'; + export const PRIVATE_KEY1 = '0x7726827caac94a7f9e1b160f7ea819f172f7b6f9d2a97f992c38edeab82d4110'; export const MNEMONIC1 = 'stuff slice staff easily soup parent arm payment cotton trade scatter struggle'; -export const ADDRESS2 = '0xa61464658AfeAf65CccaaFD3a512b69A83B77618'; +export const ADDRESS2 = '0x12b1d9d74d73b1c3a245b19c1c5501c653af1af9'; export const PRIVATE_KEY2 = '0xac1e735be8536c6534bb4f17f06f6afc73b2b5ba84ac2cfb12f7461b20c0bbe3'; -export const DAI_L1 = '0x70a0F165d6f8054d0d0CF8dFd4DD2005f0AF6B55'; -export const APPROVAL_TOKEN = '0x841c43Fa5d8fFfdB9efE3358906f7578d8700Dd4'; // Crown token -export const PAYMASTER = '0xa222f0c183AFA73a8Bc1AFb48D34C88c9Bf7A174'; // Crown token paymaster +export const DAI_L1 = '0x3e622317f8C93f7328350cF0B56d9eD4C620C5d6'; +export const USDC_L1 = '0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238'; +export const APPROVAL_TOKEN = '0x927488F48ffbc32112F1fF721759649A89721F8F'; // Crown token +export const PAYMASTER = '0x6f72f0d7bDba2E2a923beC09fBEE64cD134680F2'; // Crown token paymaster export const IS_ETH_BASED = true; + +export function deepEqualExcluding( + obj1: Record, + expected: Record, + excludeFields: string[], +) { + for (const key in obj1) { + if (!excludeFields.includes(key)) { + expect(obj1[key]).toEqual(expected[key]); + } + } +} diff --git a/yarn.lock b/yarn.lock index 0e64382..f5ff535 100644 --- a/yarn.lock +++ b/yarn.lock @@ -467,6 +467,11 @@ resolved "https://registry.yarnpkg.com/@ethereumjs/rlp/-/rlp-4.0.1.tgz#626fabfd9081baab3d0a3074b0c7ecaf674aaa41" integrity sha512-tqsQiBQDQdmPWE1xkkBq4rlSW5QZpLOUJ5RJh2/9fug+q9tnUhuZoVLk7s0scUIKTOzEtR72DFBXI4WiZcMpvw== +"@ethereumjs/rlp@^5.0.2": + version "5.0.2" + resolved "https://registry.yarnpkg.com/@ethereumjs/rlp/-/rlp-5.0.2.tgz#c89bd82f2f3bec248ab2d517ae25f5bbc4aac842" + integrity sha512-DziebCdg4JpGlEqEdGgXmjqcFoJi+JGulUXwEjsZGAscAQ7MyD/7LE/GVCP29vEQxKc7AAwjT3A2ywHp2xfoCA== + "@ethersproject/abi@5.7.0", "@ethersproject/abi@^5.1.2", "@ethersproject/abi@^5.7.0": version "5.7.0" resolved "https://registry.yarnpkg.com/@ethersproject/abi/-/abi-5.7.0.tgz#b3f3e045bbbeed1af3947335c247ad625a44e449" @@ -5733,29 +5738,47 @@ walker@^1.0.8: dependencies: makeerror "1.0.12" -web3-core@^4.3.0, web3-core@^4.4.0, web3-core@^4.5.0: - version "4.5.0" - resolved "https://registry.yarnpkg.com/web3-core/-/web3-core-4.5.0.tgz#f16e7f5bfa6373c7be45f0ed233aff479fd33079" - integrity sha512-Q8LIAqmF7vkRydBPiU+OC7wI44nEU6JEExolFaOakqrjMtQ1CWFHRUQMNJRDsk5bRirjyShuAsuqLeYByvvXhg== - dependencies: - web3-errors "^1.2.0" - web3-eth-accounts "^4.1.2" - web3-eth-iban "^4.0.7" - web3-providers-http "^4.1.0" - web3-providers-ws "^4.0.7" - web3-types "^1.7.0" - web3-utils "^4.3.0" - web3-validator "^2.0.6" +web3-core@4.5.1-dev.1436228.0, web3-core@4.5.1-dev.1436228.0+1436228: + version "4.5.1-dev.1436228.0" + resolved "https://registry.yarnpkg.com/web3-core/-/web3-core-4.5.1-dev.1436228.0.tgz#6b869109b0456d735a228b268fe1dae918228d3e" + integrity sha512-Ahmb16rwfyRoSAQiYOpMtzXxHPvfZKxfqD2lP77Ox6cJElCj5Y5Sm8La3OTn0PkTjUPmdzeilDpvAwT5zwk9Vg== + dependencies: + web3-errors "1.2.1-dev.1436228.0+1436228" + web3-eth-accounts "4.1.3-dev.1436228.0+1436228" + web3-eth-iban "4.0.8-dev.1436228.0+1436228" + web3-providers-http "4.1.1-dev.1436228.0+1436228" + web3-providers-ws "4.0.8-dev.1436228.0+1436228" + web3-types "1.7.1-dev.1436228.0+1436228" + web3-utils "4.3.1-dev.1436228.0+1436228" + web3-validator "2.0.7-dev.1436228.0+1436228" optionalDependencies: - web3-providers-ipc "^4.0.7" + web3-providers-ipc "4.0.8-dev.1436228.0+1436228" + +web3-errors@1.2.1-dev.1436228.0+1436228: + version "1.2.1-dev.1436228.0" + resolved "https://registry.yarnpkg.com/web3-errors/-/web3-errors-1.2.1-dev.1436228.0.tgz#e865fa4b381daa4359542b621879ce25cc81bb97" + integrity sha512-Cruu3QYpvMMikAVovNG53go/0G7Wq3ZHGZqacAMCuZH41i9SHM0jJWC9CNPbtK1V8qKZyIX6IwyplwDH0iQTxA== + dependencies: + web3-types "1.7.1-dev.1436228.0+1436228" -web3-errors@^1.1.3, web3-errors@^1.1.4, web3-errors@^1.2.0: +web3-errors@^1.1.4, web3-errors@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/web3-errors/-/web3-errors-1.2.0.tgz#441acfd7fd744c9beedf23f277f20759fae92433" integrity sha512-58Kczou5zyjcm9LuSs5Hrm6VrG8t9p2J8X0yGArZrhKNPZL66gMGkOUpPx+EopE944Sk4yE+Q25hKv4H5BH+kA== dependencies: web3-types "^1.6.0" +web3-eth-abi@4.2.3-dev.1436228.0+1436228: + version "4.2.3-dev.1436228.0" + resolved "https://registry.yarnpkg.com/web3-eth-abi/-/web3-eth-abi-4.2.3-dev.1436228.0.tgz#6689269cacfad983bf2435e5edc9ba2d97f39fa4" + integrity sha512-4MfysOUh7YVLeuwyLkKe/PzecFOoKceqvGUlGlwhEQiUbg2jU6h3GZAsDOeLueiWcGQOqnZOR0N9IjeWHDQtjg== + dependencies: + abitype "0.7.1" + web3-errors "1.2.1-dev.1436228.0+1436228" + web3-types "1.7.1-dev.1436228.0+1436228" + web3-utils "4.3.1-dev.1436228.0+1436228" + web3-validator "2.0.7-dev.1436228.0+1436228" + web3-eth-abi@^4.2.2: version "4.2.2" resolved "https://registry.yarnpkg.com/web3-eth-abi/-/web3-eth-abi-4.2.2.tgz#d7592e2cc113fd34da3fb4c933537ddf8639d9b2" @@ -5767,6 +5790,19 @@ web3-eth-abi@^4.2.2: web3-utils "^4.3.0" web3-validator "^2.0.6" +web3-eth-accounts@4.1.3-dev.1436228.0+1436228: + version "4.1.3-dev.1436228.0" + resolved "https://registry.yarnpkg.com/web3-eth-accounts/-/web3-eth-accounts-4.1.3-dev.1436228.0.tgz#588c37eab5e79fa78ca8a9184b82681f61a76182" + integrity sha512-Wg1SVTxKCDZ49iR4xswzmYbXRZ9GsaqTCzpAJn2WCcTMlEit8v0Cr/wzi9orZZqrTQFXLxOjlY8UETsOGiiBGg== + dependencies: + "@ethereumjs/rlp" "^4.0.1" + crc-32 "^1.2.2" + ethereum-cryptography "^2.0.0" + web3-errors "1.2.1-dev.1436228.0+1436228" + web3-types "1.7.1-dev.1436228.0+1436228" + web3-utils "4.3.1-dev.1436228.0+1436228" + web3-validator "2.0.7-dev.1436228.0+1436228" + web3-eth-accounts@^4.1.2: version "4.1.2" resolved "https://registry.yarnpkg.com/web3-eth-accounts/-/web3-eth-accounts-4.1.2.tgz#652d6e3daf4d6cb3fe67cec6a878e768f6e8b8e8" @@ -5780,139 +5816,156 @@ web3-eth-accounts@^4.1.2: web3-utils "^4.2.3" web3-validator "^2.0.5" -web3-eth-contract@^4.5.0: - version "4.5.0" - resolved "https://registry.yarnpkg.com/web3-eth-contract/-/web3-eth-contract-4.5.0.tgz#954c9cf4c055f3f2c33189bdd69093a8e20a569a" - integrity sha512-AX6OiDrIryz/T28k9Xz0gXpUrlOUjcooEgGluu2s5dFDWCPM/zlN5RsUZlXZiXpQyj52VCUy5+bkvu3yDPA4fg== - dependencies: - web3-core "^4.4.0" - web3-errors "^1.2.0" - web3-eth "^4.7.0" - web3-eth-abi "^4.2.2" - web3-types "^1.6.0" - web3-utils "^4.3.0" - web3-validator "^2.0.6" - -web3-eth-ens@^4.4.0: - version "4.4.0" - resolved "https://registry.yarnpkg.com/web3-eth-ens/-/web3-eth-ens-4.4.0.tgz#bc0d11d755cb15ed4b82e38747c5104622d9a4b9" - integrity sha512-DeyVIS060hNV9g8dnTx92syqvgbvPricE3MerCxe/DquNZT3tD8aVgFfq65GATtpCgDDJffO2bVeHp3XBemnSQ== +web3-eth-contract@4.5.1-dev.1436228.0, web3-eth-contract@4.5.1-dev.1436228.0+1436228: + version "4.5.1-dev.1436228.0" + resolved "https://registry.yarnpkg.com/web3-eth-contract/-/web3-eth-contract-4.5.1-dev.1436228.0.tgz#a659bf3ccb594901a2a2a7469fbf9577d056272a" + integrity sha512-eVG5u5NF2f1Fv9tyQLkfevyke2DJB0DYGN8hCuFzUd2B7zEEyQ+ywEsT15u0DXPoXIt23fp90Lw9DIpHlZiL4g== + dependencies: + "@ethereumjs/rlp" "^5.0.2" + web3-core "4.5.1-dev.1436228.0+1436228" + web3-errors "1.2.1-dev.1436228.0+1436228" + web3-eth "4.8.1-dev.1436228.0+1436228" + web3-eth-abi "4.2.3-dev.1436228.0+1436228" + web3-types "1.7.1-dev.1436228.0+1436228" + web3-utils "4.3.1-dev.1436228.0+1436228" + web3-validator "2.0.7-dev.1436228.0+1436228" + +web3-eth-ens@4.4.1-dev.1436228.0+1436228: + version "4.4.1-dev.1436228.0" + resolved "https://registry.yarnpkg.com/web3-eth-ens/-/web3-eth-ens-4.4.1-dev.1436228.0.tgz#ecd42f19c2ea7916f96d16d88fea8a779e4efb24" + integrity sha512-MIw/1WtRMNPiPILNmHjihV0VNop27N8Yq0b20lXjQOv7FHy5boz4jnrg1Vv2qeUUqJ4SEO11Dey66a+gXph12A== dependencies: "@adraffy/ens-normalize" "^1.8.8" - web3-core "^4.5.0" - web3-errors "^1.2.0" - web3-eth "^4.8.0" - web3-eth-contract "^4.5.0" - web3-net "^4.1.0" - web3-types "^1.7.0" - web3-utils "^4.3.0" - web3-validator "^2.0.6" - -web3-eth-iban@^4.0.7: - version "4.0.7" - resolved "https://registry.yarnpkg.com/web3-eth-iban/-/web3-eth-iban-4.0.7.tgz#ee504f845d7b6315f0be78fcf070ccd5d38e4aaf" - integrity sha512-8weKLa9KuKRzibC87vNLdkinpUE30gn0IGY027F8doeJdcPUfsa4IlBgNC4k4HLBembBB2CTU0Kr/HAOqMeYVQ== - dependencies: - web3-errors "^1.1.3" - web3-types "^1.3.0" - web3-utils "^4.0.7" - web3-validator "^2.0.3" - -web3-eth-personal@^4.0.8: - version "4.0.8" - resolved "https://registry.yarnpkg.com/web3-eth-personal/-/web3-eth-personal-4.0.8.tgz#b51628c560de550ca8b354fa784f9556aae6065c" - integrity sha512-sXeyLKJ7ddQdMxz1BZkAwImjqh7OmKxhXoBNF3isDmD4QDpMIwv/t237S3q4Z0sZQamPa/pHebJRWVuvP8jZdw== - dependencies: - web3-core "^4.3.0" - web3-eth "^4.3.1" - web3-rpc-methods "^1.1.3" - web3-types "^1.3.0" - web3-utils "^4.0.7" - web3-validator "^2.0.3" - -web3-eth@^4.3.1, web3-eth@^4.7.0, web3-eth@^4.8.0: - version "4.8.0" - resolved "https://registry.yarnpkg.com/web3-eth/-/web3-eth-4.8.0.tgz#1db0b573d933b69032038e814df730136f4837ba" - integrity sha512-fobkdpwN9SH785/0LSLfxOMH4rZNAD/EvTKIHdpl4ZVz5XdKehX+xPMpSGDGwMlAQ7yXByjZDX3opzoqEQLWxg== + web3-core "4.5.1-dev.1436228.0+1436228" + web3-errors "1.2.1-dev.1436228.0+1436228" + web3-eth "4.8.1-dev.1436228.0+1436228" + web3-eth-contract "4.5.1-dev.1436228.0+1436228" + web3-net "4.1.1-dev.1436228.0+1436228" + web3-types "1.7.1-dev.1436228.0+1436228" + web3-utils "4.3.1-dev.1436228.0+1436228" + web3-validator "2.0.7-dev.1436228.0+1436228" + +web3-eth-iban@4.0.8-dev.1436228.0+1436228: + version "4.0.8-dev.1436228.0" + resolved "https://registry.yarnpkg.com/web3-eth-iban/-/web3-eth-iban-4.0.8-dev.1436228.0.tgz#23e2520f8d692b079a5b4603516994847ece7fc9" + integrity sha512-Q5Vkj87ivRt33oMWdkzssJpeUANmTJdqjmQj0PBDpmCRU6ghP0SETB9NoQlfGkmoC56e1z+detQOH2E2dLCKbQ== + dependencies: + web3-errors "1.2.1-dev.1436228.0+1436228" + web3-types "1.7.1-dev.1436228.0+1436228" + web3-utils "4.3.1-dev.1436228.0+1436228" + web3-validator "2.0.7-dev.1436228.0+1436228" + +web3-eth-personal@4.0.9-dev.1436228.0+1436228: + version "4.0.9-dev.1436228.0" + resolved "https://registry.yarnpkg.com/web3-eth-personal/-/web3-eth-personal-4.0.9-dev.1436228.0.tgz#8e74fa26a4656b1ac28c611ca947229d64d3cf69" + integrity sha512-yZlNIZ3j7a7MIZNB/ztEsEF1Ji9oI8zT0gAiygAnLvYmqOcLBQoRFnSaZ01+AKMl00BCD1xToOtGs8Kd9vpVqQ== + dependencies: + web3-core "4.5.1-dev.1436228.0+1436228" + web3-eth "4.8.1-dev.1436228.0+1436228" + web3-rpc-methods "1.3.1-dev.1436228.0+1436228" + web3-types "1.7.1-dev.1436228.0+1436228" + web3-utils "4.3.1-dev.1436228.0+1436228" + web3-validator "2.0.7-dev.1436228.0+1436228" + +web3-eth@4.8.1-dev.1436228.0+1436228: + version "4.8.1-dev.1436228.0" + resolved "https://registry.yarnpkg.com/web3-eth/-/web3-eth-4.8.1-dev.1436228.0.tgz#74a587d6faca22f8cfc6a42edb5c82661bcec65e" + integrity sha512-T06ETt1gh7oxo7tZqtY+SpPfa8e5DzMMlYRT0wvYEZ7EKZtqMkrZVImuWDP0mtg+AqS+r1mf6DP+DJzIAXVRsA== dependencies: setimmediate "^1.0.5" - web3-core "^4.5.0" - web3-errors "^1.2.0" - web3-eth-abi "^4.2.2" - web3-eth-accounts "^4.1.2" - web3-net "^4.1.0" - web3-providers-ws "^4.0.7" - web3-rpc-methods "^1.3.0" - web3-types "^1.7.0" - web3-utils "^4.3.0" - web3-validator "^2.0.6" - -web3-net@^4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/web3-net/-/web3-net-4.1.0.tgz#db7bde675e58b153339e4f149f29ec0410d6bab2" - integrity sha512-WWmfvHVIXWEoBDWdgKNYKN8rAy6SgluZ0abyRyXOL3ESr7ym7pKWbfP4fjApIHlYTh8tNqkrdPfM4Dyi6CA0SA== - dependencies: - web3-core "^4.4.0" - web3-rpc-methods "^1.3.0" - web3-types "^1.6.0" - web3-utils "^4.3.0" - -web3-providers-http@^4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/web3-providers-http/-/web3-providers-http-4.1.0.tgz#8d7afda67d1d8542ca85b30f60a3d1fe1993b561" - integrity sha512-6qRUGAhJfVQM41E5t+re5IHYmb5hSaLc02BE2MaRQsz2xKA6RjmHpOA5h/+ojJxEpI9NI2CrfDKOAgtJfoUJQg== + web3-core "4.5.1-dev.1436228.0+1436228" + web3-errors "1.2.1-dev.1436228.0+1436228" + web3-eth-abi "4.2.3-dev.1436228.0+1436228" + web3-eth-accounts "4.1.3-dev.1436228.0+1436228" + web3-net "4.1.1-dev.1436228.0+1436228" + web3-providers-ws "4.0.8-dev.1436228.0+1436228" + web3-rpc-methods "1.3.1-dev.1436228.0+1436228" + web3-types "1.7.1-dev.1436228.0+1436228" + web3-utils "4.3.1-dev.1436228.0+1436228" + web3-validator "2.0.7-dev.1436228.0+1436228" + +web3-net@4.1.1-dev.1436228.0+1436228: + version "4.1.1-dev.1436228.0" + resolved "https://registry.yarnpkg.com/web3-net/-/web3-net-4.1.1-dev.1436228.0.tgz#19efe0c67d91ad3592a7549ffdd7be2b74becd16" + integrity sha512-RwZjG/9FIIeZybECh4VDAAE8eoDFxvbzzZrF0aKnQi5xjTB0S3MCcki7bUwomVWAZCU8Fjz62K7dFh0Rxtt98Q== + dependencies: + web3-core "4.5.1-dev.1436228.0+1436228" + web3-rpc-methods "1.3.1-dev.1436228.0+1436228" + web3-types "1.7.1-dev.1436228.0+1436228" + web3-utils "4.3.1-dev.1436228.0+1436228" + +web3-providers-http@4.1.1-dev.1436228.0+1436228: + version "4.1.1-dev.1436228.0" + resolved "https://registry.yarnpkg.com/web3-providers-http/-/web3-providers-http-4.1.1-dev.1436228.0.tgz#22763c49ef9644b11d2d6420c02c30810a73e079" + integrity sha512-wGrnkvDvwWjgGho2ByIDOqfUgDkFqTB7lVy/wTT7aZnjie1NbW4f2UbkFfhcao2dIAGY2rRZI0Mk8uBfpFiJ4w== dependencies: cross-fetch "^4.0.0" - web3-errors "^1.1.3" - web3-types "^1.3.0" - web3-utils "^4.0.7" + web3-errors "1.2.1-dev.1436228.0+1436228" + web3-types "1.7.1-dev.1436228.0+1436228" + web3-utils "4.3.1-dev.1436228.0+1436228" -web3-providers-ipc@^4.0.7: - version "4.0.7" - resolved "https://registry.yarnpkg.com/web3-providers-ipc/-/web3-providers-ipc-4.0.7.tgz#9ec4c8565053af005a5170ba80cddeb40ff3e3d3" - integrity sha512-YbNqY4zUvIaK2MHr1lQFE53/8t/ejHtJchrWn9zVbFMGXlTsOAbNoIoZWROrg1v+hCBvT2c9z8xt7e/+uz5p1g== +web3-providers-ipc@4.0.8-dev.1436228.0+1436228: + version "4.0.8-dev.1436228.0" + resolved "https://registry.yarnpkg.com/web3-providers-ipc/-/web3-providers-ipc-4.0.8-dev.1436228.0.tgz#b35d7bb95345e1625807b22c868c02a8e2abb540" + integrity sha512-pFiheejy19qi7061I6ppZSD5LB2O0++JQ8gZ+S4MWgWX1hdVxojTeiEgj7Cx+j1xuvN2vvLb1zZ/i35100RSrA== dependencies: - web3-errors "^1.1.3" - web3-types "^1.3.0" - web3-utils "^4.0.7" + web3-errors "1.2.1-dev.1436228.0+1436228" + web3-types "1.7.1-dev.1436228.0+1436228" + web3-utils "4.3.1-dev.1436228.0+1436228" -web3-providers-ws@^4.0.7: - version "4.0.7" - resolved "https://registry.yarnpkg.com/web3-providers-ws/-/web3-providers-ws-4.0.7.tgz#7a78a0dcf077e0e802da524fbb37d080b356c14b" - integrity sha512-n4Dal9/rQWjS7d6LjyEPM2R458V8blRm0eLJupDEJOOIBhGYlxw5/4FthZZ/cqB7y/sLVi7K09DdYx2MeRtU5w== +web3-providers-ws@4.0.8-dev.1436228.0+1436228: + version "4.0.8-dev.1436228.0" + resolved "https://registry.yarnpkg.com/web3-providers-ws/-/web3-providers-ws-4.0.8-dev.1436228.0.tgz#3c2609aa7f809fa318c15511dbc504085b2cf9b3" + integrity sha512-/9ECj/OMnMuSS9MCT6ep70iCfm7N9LX3JtBPHttZaalM3P1sVGzXFowW7jtF84ltZX0a6uqIjwsqmPZ/ID26VA== dependencies: "@types/ws" "8.5.3" isomorphic-ws "^5.0.0" - web3-errors "^1.1.3" - web3-types "^1.3.0" - web3-utils "^4.0.7" - ws "^8.8.1" - -web3-rpc-methods@^1.1.3, web3-rpc-methods@^1.3.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/web3-rpc-methods/-/web3-rpc-methods-1.3.0.tgz#d5ee299a69389d63822d354ddee2c6a121a6f670" - integrity sha512-/CHmzGN+IYgdBOme7PdqzF+FNeMleefzqs0LVOduncSaqsppeOEoskLXb2anSpzmQAP3xZJPaTrkQPWSJMORig== - dependencies: - web3-core "^4.4.0" - web3-types "^1.6.0" - web3-validator "^2.0.6" - -web3-rpc-providers@^1.0.0-rc.0: - version "1.0.0-rc.0" - resolved "https://registry.yarnpkg.com/web3-rpc-providers/-/web3-rpc-providers-1.0.0-rc.0.tgz#fa5f4aac5e15707b50d6b72a10c087a58ea2e281" - integrity sha512-lmBOZ4PE+wf5JyptPZJ+GHeGPyTBfnCRbrfOxWLU+Q7g+D6NukgS3fk2xcunEvUsR/b5fp+uXk0TkmhX9/GCKg== - dependencies: - web3-providers-http "^4.1.0" - web3-providers-ws "^4.0.7" - web3-types "^1.7.0" - web3-utils "^4.3.0" - -web3-types@^1.3.0, web3-types@^1.6.0, web3-types@^1.7.0: + web3-errors "1.2.1-dev.1436228.0+1436228" + web3-types "1.7.1-dev.1436228.0+1436228" + web3-utils "4.3.1-dev.1436228.0+1436228" + ws "^8.17.1" + +web3-rpc-methods@1.3.1-dev.1436228.0+1436228: + version "1.3.1-dev.1436228.0" + resolved "https://registry.yarnpkg.com/web3-rpc-methods/-/web3-rpc-methods-1.3.1-dev.1436228.0.tgz#8fb4c78429f1f18404888d8f22f4db1c443bb236" + integrity sha512-Y+sFdRNuE3wIQcCiH0TPU+aRugWk3v+FyF2VkFsFXfSnDYqJTnhm4MTDQhkfruG68xuw3pHo8J6YPTCiJQRdLg== + dependencies: + web3-core "4.5.1-dev.1436228.0+1436228" + web3-types "1.7.1-dev.1436228.0+1436228" + web3-validator "2.0.7-dev.1436228.0+1436228" + +web3-rpc-providers@1.0.0-dev.1436228.0+1436228: + version "1.0.0-dev.1436228.0" + resolved "https://registry.yarnpkg.com/web3-rpc-providers/-/web3-rpc-providers-1.0.0-dev.1436228.0.tgz#ffc61375fa507fceb1cdeeb270f145a7d35e41f1" + integrity sha512-glQblyg9SMLv1SZTwdQ/0T1oU+dmmh/KbOXY5Jw6kG0BxeSle/pgBXj14ps9T+HnDtmI7SlTUAggXQk4ehNJ5w== + dependencies: + web3-providers-http "4.1.1-dev.1436228.0+1436228" + web3-providers-ws "4.0.8-dev.1436228.0+1436228" + web3-types "1.7.1-dev.1436228.0+1436228" + web3-utils "4.3.1-dev.1436228.0+1436228" + +web3-types@1.7.1-dev.1436228.0, web3-types@1.7.1-dev.1436228.0+1436228: + version "1.7.1-dev.1436228.0" + resolved "https://registry.yarnpkg.com/web3-types/-/web3-types-1.7.1-dev.1436228.0.tgz#374f101087431b20823107c2f0c3b096fcdd1d69" + integrity sha512-Lxg6ZpLUC36i/idSQZ8GI/LRBhHyHOwZxcmrNELiAtWe7cQzU+zOGybtWV7XRNwOUUQaZ4NCqrIF6LuM6oJE9Q== + +web3-types@^1.6.0: version "1.7.0" resolved "https://registry.yarnpkg.com/web3-types/-/web3-types-1.7.0.tgz#9945fa644af96b20b1db18564aff9ab8db00df59" integrity sha512-nhXxDJ7a5FesRw9UG5SZdP/C/3Q2EzHGnB39hkAV+YGXDMgwxBXFWebQLfEzZzuArfHnvC0sQqkIHNwSKcVjdA== -web3-utils@^4.0.7, web3-utils@^4.2.3, web3-utils@^4.3.0: +web3-utils@4.3.1-dev.1436228.0, web3-utils@4.3.1-dev.1436228.0+1436228: + version "4.3.1-dev.1436228.0" + resolved "https://registry.yarnpkg.com/web3-utils/-/web3-utils-4.3.1-dev.1436228.0.tgz#482bb705a54007222eccfe88f5a1203ff3d1d672" + integrity sha512-0knKVcFmAK8sVgjn26/pJIiCV1LrEzHiJNGOzg8DiR/pqFOF+K4lsQK6q+wJYfd5h4qpxwg93qouU8ZKxnC8YQ== + dependencies: + ethereum-cryptography "^2.0.0" + eventemitter3 "^5.0.1" + web3-errors "1.2.1-dev.1436228.0+1436228" + web3-types "1.7.1-dev.1436228.0+1436228" + web3-validator "2.0.7-dev.1436228.0+1436228" + +web3-utils@^4.2.3, web3-utils@^4.3.0: version "4.3.0" resolved "https://registry.yarnpkg.com/web3-utils/-/web3-utils-4.3.0.tgz#c18918f0d692f745d622d22172406f6102528860" integrity sha512-fGG2IZr0XB1vEoWZiyJzoy28HpsIfZgz4mgPeQA9aj5rIx8z0o80qUPtIyrCYX/Bo2gYALlV5SWIJWxJNUQn9Q== @@ -5923,7 +5976,18 @@ web3-utils@^4.0.7, web3-utils@^4.2.3, web3-utils@^4.3.0: web3-types "^1.6.0" web3-validator "^2.0.6" -web3-validator@^2.0.3, web3-validator@^2.0.5, web3-validator@^2.0.6: +web3-validator@2.0.7-dev.1436228.0+1436228: + version "2.0.7-dev.1436228.0" + resolved "https://registry.yarnpkg.com/web3-validator/-/web3-validator-2.0.7-dev.1436228.0.tgz#9b4b71501b8731225b9dccd450b080e351830b23" + integrity sha512-qY0dtddh08xbh4owLmelUC1USQtcpvbo8IAjpwo60VrjJ4xIvbgPUNsvd54e2TwiHpTRlwFEi5r3M7WVLLq6ug== + dependencies: + ethereum-cryptography "^2.0.0" + util "^0.12.5" + web3-errors "1.2.1-dev.1436228.0+1436228" + web3-types "1.7.1-dev.1436228.0+1436228" + zod "^3.21.4" + +web3-validator@^2.0.5, web3-validator@^2.0.6: version "2.0.6" resolved "https://registry.yarnpkg.com/web3-validator/-/web3-validator-2.0.6.tgz#a0cdaa39e1d1708ece5fae155b034e29d6a19248" integrity sha512-qn9id0/l1bWmvH4XfnG/JtGKKwut2Vokl6YXP5Kfg424npysmtRLe9DgiNBM9Op7QL/aSiaA0TVXibuIuWcizg== @@ -5934,28 +5998,28 @@ web3-validator@^2.0.3, web3-validator@^2.0.5, web3-validator@^2.0.6: web3-types "^1.6.0" zod "^3.21.4" -web3@^4.10.0: - version "4.10.0" - resolved "https://registry.yarnpkg.com/web3/-/web3-4.10.0.tgz#b20a9b0c510efacfc6f776630a0d1b4e87f41398" - integrity sha512-0A0SEZG4QL5DRtZQtF1pL+LldHn0kAAb4/vUdQua1k4CrZ+hRoDAc3dfFZw94EOV69oXuAFo8fqhITxyWfCHaw== - dependencies: - web3-core "^4.5.0" - web3-errors "^1.2.0" - web3-eth "^4.8.0" - web3-eth-abi "^4.2.2" - web3-eth-accounts "^4.1.2" - web3-eth-contract "^4.5.0" - web3-eth-ens "^4.4.0" - web3-eth-iban "^4.0.7" - web3-eth-personal "^4.0.8" - web3-net "^4.1.0" - web3-providers-http "^4.1.0" - web3-providers-ws "^4.0.7" - web3-rpc-methods "^1.3.0" - web3-rpc-providers "^1.0.0-rc.0" - web3-types "^1.7.0" - web3-utils "^4.3.0" - web3-validator "^2.0.6" +web3@4.10.1-dev.1436228.0: + version "4.10.1-dev.1436228.0" + resolved "https://registry.yarnpkg.com/web3/-/web3-4.10.1-dev.1436228.0.tgz#7e62eaa01c0ea29933a40483c57c04f66d7994a7" + integrity sha512-rn8CMhwTYHiFAccOww//0ZwXagMHZFWxktJvwt1kWBNT4oQ8ggSEi1z2rrkuPC+Oll4hWCPmRPnqcVSwuMnHvA== + dependencies: + web3-core "4.5.1-dev.1436228.0+1436228" + web3-errors "1.2.1-dev.1436228.0+1436228" + web3-eth "4.8.1-dev.1436228.0+1436228" + web3-eth-abi "4.2.3-dev.1436228.0+1436228" + web3-eth-accounts "4.1.3-dev.1436228.0+1436228" + web3-eth-contract "4.5.1-dev.1436228.0+1436228" + web3-eth-ens "4.4.1-dev.1436228.0+1436228" + web3-eth-iban "4.0.8-dev.1436228.0+1436228" + web3-eth-personal "4.0.9-dev.1436228.0+1436228" + web3-net "4.1.1-dev.1436228.0+1436228" + web3-providers-http "4.1.1-dev.1436228.0+1436228" + web3-providers-ws "4.0.8-dev.1436228.0+1436228" + web3-rpc-methods "1.3.1-dev.1436228.0+1436228" + web3-rpc-providers "1.0.0-dev.1436228.0+1436228" + web3-types "1.7.1-dev.1436228.0+1436228" + web3-utils "4.3.1-dev.1436228.0+1436228" + web3-validator "2.0.7-dev.1436228.0+1436228" webidl-conversions@^3.0.0: version "3.0.1" @@ -6036,10 +6100,10 @@ ws@^7.4.6: resolved "https://registry.yarnpkg.com/ws/-/ws-7.5.9.tgz#54fa7db29f4c7cec68b1ddd3a89de099942bb591" integrity sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q== -ws@^8.8.1: - version "8.13.0" - resolved "https://registry.yarnpkg.com/ws/-/ws-8.13.0.tgz#9a9fb92f93cf41512a0735c8f4dd09b8a1211cd0" - integrity sha512-x9vcZYTrFPC7aSIbj7sRCYo7L/Xb8Iy+pW0ng0wt2vCJv7M9HOMy0UoN3rr+IFC7hb7vXoqS+P9ktyLLLhO+LA== +ws@^8.17.1: + version "8.18.0" + resolved "https://registry.yarnpkg.com/ws/-/ws-8.18.0.tgz#0d7505a6eafe2b0e712d232b42279f53bc289bbc" + integrity sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw== y18n@^5.0.5: version "5.0.8"