diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 14b0c80..1494ed8 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -20,4 +20,4 @@ jobs: - run: yarn build - run: export PRIVATE_KEY=${{secrets.PRIVATE_KEY}} - run: yarn test:unit - - run: yarn test:integration + - run: yarn test test/integration/rpc test/integration/mainnet test/integration/zksync test/integration/utils diff --git a/src/Eip712.ts b/src/Eip712.ts index 6535254..23f9683 100644 --- a/src/Eip712.ts +++ b/src/Eip712.ts @@ -355,102 +355,3 @@ export class EIP712Signer { return this.eip712Domain; } } -// export class EIP712Transaction extends BaseTransaction { -// private txData: Eip712TxData; -// private signature?: SignatureObject; -// constructor(txData: Eip712TxData) { -// super(txData, {} as web3Accounts.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()})`; -// } -// -// public static fromTxData(txData: Eip712TxData, _ = {}) { -// return new EIP712Transaction(txData); -// } -// -// 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)); -// } -// raw(): web3Accounts.TxValuesArray { -// return EIP712.raw(this.txData) as unknown as web3Accounts.TxValuesArray; -// } -// -// serialize(): Uint8Array { -// return toUint8Array(EIP712.serialize(this.txData)); -// } -// -// toJSON(): web3Accounts.JsonTx { -// const data = EIP712.getSignInput(this.txData); -// return { -// to: data.to && toHex(data.to), -// gasLimit: toHex(data.gasLimit), -// // @ts-ignore-next-line -// gasPerPubdataByteLimit: data.gasPerPubdataByteLimit, -// customData: data.customData, -// maxFeePerGas: toHex(data.maxFeePerGas), -// maxPriorityFeePerGas: toHex(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/smart-account-utils.ts b/src/smart-account-utils.ts index 7f119c5..89dbff3 100644 --- a/src/smart-account-utils.ts +++ b/src/smart-account-utils.ts @@ -4,7 +4,6 @@ import { privateKeyToAccount, signMessageWithPrivateKey, Web3Account } from 'web // import { DEFAULT_GAS_PER_PUBDATA_LIMIT, EIP712_TX_TYPE } from './constants'; import { Web3ZKsyncL2 } from './web3zksync-l2'; import type * as web3Types from 'web3-types'; -import * as utils from './utils'; import { DEFAULT_GAS_PER_PUBDATA_LIMIT, EIP712_TX_TYPE } from './constants'; import { Transaction } from 'web3-types'; import { Address } from 'web3'; @@ -157,15 +156,11 @@ export const populateTransactionECDSA: TransactionBuilder = async ( typeof secret === 'object' && secret.privateKey ? (secret as Web3Account) : privateKeyToAccount(secret as string); - provider._eip712Signer = async () => { - if (!provider.eip712) { - provider.eip712 = new utils.EIP712Signer( - account, - Number(await provider.eth.getChainId()), - ); - } - return provider.eip712; - }; + + if (!provider.eth.accounts.wallet.get(account.address)) { + provider.eth.accounts.wallet.add(account); + } + tx.type = EIP712_TX_TYPE; tx.chainId = format({ format: 'uint' }, tx.chainId ?? (await provider.eth.getChainId())); tx.value = format({ format: 'uint' }, tx.value ? tx.value : 0n); diff --git a/src/smart-account.ts b/src/smart-account.ts index 0cdcfaf..18de66d 100644 --- a/src/smart-account.ts +++ b/src/smart-account.ts @@ -81,6 +81,14 @@ export class SmartAccount extends AdapterL2 { this._address = signer.address; this.payloadSigner = signer.payloadSigner || signPayloadWithECDSA; this.transactionBuilder = signer.transactionBuilder || populateTransactionECDSA; + if (this._provider) { + const accounts = Array.isArray(this._account) ? this._account : [this._account]; + for (const account of accounts) { + if (!this._provider.eth.accounts.wallet.get(account.address)) { + this._provider.eth.accounts.wallet.add(account); + } + } + } } _contextL2(): Web3ZKsyncL2 { return this._provider!; diff --git a/src/web3zksync-l2.ts b/src/web3zksync-l2.ts index 7a14a39..6b5d920 100644 --- a/src/web3zksync-l2.ts +++ b/src/web3zksync-l2.ts @@ -24,11 +24,9 @@ import { } from './constants'; import { IL2BridgeABI } from './contracts/IL2Bridge'; import { IERC20ABI } from './contracts/IERC20'; -import * as utils from './utils'; // Equivalent to both Provider and Signer in zksync-ethers export class Web3ZKsyncL2 extends Web3ZkSync { - eip712!: utils.EIP712Signer; async getZKTransactionReceipt( transactionHash: Bytes, returnFormat: ReturnFormat = DEFAULT_RETURN_FORMAT as ReturnFormat, diff --git a/src/web3zksync.ts b/src/web3zksync.ts index 9bac47e..e1493ce 100644 --- a/src/web3zksync.ts +++ b/src/web3zksync.ts @@ -33,12 +33,13 @@ import { REQUIRED_L1_TO_L2_GAS_PER_PUBDATA_LIMIT, ZERO_ADDRESS, } from './constants'; -import { EIP712, type EIP712Signer, isAddressEq, isETH } from './utils'; +import { EIP712, isAddressEq, isETH } from './utils'; import { RpcMethods } from './rpc.methods'; import { IL2BridgeABI } from './contracts/IL2Bridge'; import { IERC20ABI } from './contracts/IERC20'; import { EIP712TransactionSchema } from './schemas'; -import { toUint8Array } from 'web3-eth-accounts'; +import { toUint8Array, Web3Account } from 'web3-eth-accounts'; +import * as utils from './utils'; /** * The base class for interacting with ZKsync Era. @@ -96,9 +97,6 @@ export class Web3ZkSync extends Web3.Web3 { ): 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. * @@ -647,8 +645,11 @@ export class Web3ZkSync extends Web3.Web3 { async signTransaction(tx: Transaction): Promise { if (tx.type && toHex(tx.type) === toHex(EIP712_TX_TYPE)) { - const signer = await this._eip712Signer(); - tx.chainId = signer.getDomain().chainId; + const signer = new utils.EIP712Signer( + this.eth.accounts.wallet.get(tx.from!) as Web3Account, + Number(tx.chainId), + ); + // tx.chainId = signer.getDomain().chainId; // @ts-ignore tx.customData = { // @ts-ignore diff --git a/src/zksync-wallet.ts b/src/zksync-wallet.ts index ace07a4..803fe07 100644 --- a/src/zksync-wallet.ts +++ b/src/zksync-wallet.ts @@ -4,10 +4,8 @@ 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 { @@ -17,8 +15,8 @@ class Adapters extends AdapterL1 { 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 = 'latest') { return this.adapterL2.getBalance(token, blockTag); } @@ -38,9 +36,6 @@ class Adapters extends AdapterL1 { getL2BridgeContracts() { return this.adapterL2.getL2BridgeContracts(); } - protected async _eip712Signer(): Promise { - throw new Error('Must be implemented by the derived class!'); - } /** * Initiates the withdrawal process which withdraws ETH or any ERC20 token @@ -84,7 +79,6 @@ class Adapters extends AdapterL1 { export class ZKsyncWallet extends Adapters { provider?: Web3ZKsyncL2; providerL1?: Web3ZKsyncL1; - protected eip712!: utils.EIP712Signer; public account: Web3Account; /** * @@ -138,7 +132,6 @@ export class ZKsyncWallet extends Adapters { // errno: 'ECONNREFUSED', // code: 'ECONNREFUSED' // } - this.provider._eip712Signer = this._eip712Signer.bind(this); return this; } @@ -152,16 +145,6 @@ export class ZKsyncWallet extends Adapters { 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!; } diff --git a/test/local/provider.test.ts b/test/local/provider.test.ts new file mode 100644 index 0000000..2d3d075 --- /dev/null +++ b/test/local/provider.test.ts @@ -0,0 +1,82 @@ +import { Web3ZKsyncL2, ZKsyncWallet, SmartAccount } from '../../src'; +import { L2Provider } from './fixtures'; +import { ADDRESS2, PRIVATE_KEY1, PRIVATE_KEY2 } from '../utils'; +import { EIP712_TX_TYPE } from '../../src/constants'; +import { privateKeyToAccount } from 'web3-eth-accounts'; +describe('EIP712Signer', () => { + it('should different wallets be able to use different accounts, even when using the same EIP712Signer', async () => { + const l2Provider = new Web3ZKsyncL2(L2Provider); + const w1 = new ZKsyncWallet(PRIVATE_KEY1, l2Provider); + + const s1 = await w1.provider?.signTransaction({ + type: EIP712_TX_TYPE, + to: ADDRESS2, + value: 7_000_000n, + from: w1.address, + chainId: 1, + }); + const w2 = new ZKsyncWallet(PRIVATE_KEY2, l2Provider); + await w2.provider?.signTransaction({ + type: EIP712_TX_TYPE, + to: ADDRESS2, + value: 7_000_000n, + from: w2.address, + chainId: 1, + }); + + const s11 = await w1.provider?.signTransaction({ + type: EIP712_TX_TYPE, + to: ADDRESS2, + value: 7_000_000n, + from: w1.address, + chainId: 1, + }); + expect(s11).toBe(s1); + expect(w1.getAddress()).not.toBe(w2.getAddress()); + }); + + it('should different smart accounts be able to use different accounts, even when using the same EIP712Signer', async () => { + const l2Provider = new Web3ZKsyncL2(L2Provider); + const acc = privateKeyToAccount(PRIVATE_KEY1); + const acc2 = privateKeyToAccount(PRIVATE_KEY2); + const sa1 = new SmartAccount( + { + secret: acc.privateKey, + address: acc.address, + }, + l2Provider, + ); + + const s1 = await sa1.provider?.signTransaction({ + type: EIP712_TX_TYPE, + to: ADDRESS2, + value: 7_000_000n, + from: acc.address, + chainId: 1, + }); + const sa2 = new SmartAccount( + { + secret: acc2.privateKey, + address: acc2.address, + }, + l2Provider, + ); + await sa2.provider?.signTransaction({ + type: EIP712_TX_TYPE, + to: ADDRESS2, + value: 7_000_000n, + from: acc2.address, + chainId: 1, + }); + + const s11 = await sa1.provider?.signTransaction({ + type: EIP712_TX_TYPE, + to: ADDRESS2, + value: 7_000_000n, + from: acc.address, + chainId: 1, + }); + expect(s11).toBe(s1); + expect(sa1.getAddress()).not.toBe(sa2.getAddress()); + }); +}); diff --git a/test/unit/signer.test.ts b/test/unit/signer.test.ts index fc28770..7ac427a 100644 --- a/test/unit/signer.test.ts +++ b/test/unit/signer.test.ts @@ -1,9 +1,8 @@ // import '../custom-matchers'; -import { EIP712Signer } from '../../src/Eip712'; +import { EIP712Signer, EIP712 } 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()', () => {