diff --git a/packages/sdk/wallets/wallet-base/src/signing-utils.test.ts b/packages/sdk/wallets/wallet-base/src/signing-utils.test.ts index 8ef470a82ce..af15401a4c4 100644 --- a/packages/sdk/wallets/wallet-base/src/signing-utils.test.ts +++ b/packages/sdk/wallets/wallet-base/src/signing-utils.test.ts @@ -369,6 +369,32 @@ describe('recoverTransaction', () => { ] `) }) + it('handles cip64 transactions', () => { + const cip64TX = + '0x7bf88282ad5a8063630a94588e4b68193001e4d10928660ab4165b813717c0880de0b6b3a764000083abcdefc094cd2a3d9f938e13cd947ec05abc7fe734df8dd82680a091b5504a59e529e7efa42dbb97fbc3311a91d035c873a94ab0789441fc989f84a02e8254d6b3101b63417e5d496833bc84f4832d4a8bf8a2b83e291d8f38c0f62d' + expect(recoverTransaction(cip64TX)).toMatchInlineSnapshot(` + [ + { + "accessList": [], + "chainId": 44378, + "data": "0xabcdef", + "feeCurrency": "0xcd2a3d9f938e13cd947ec05abc7fe734df8dd826", + "gas": 10, + "maxFeePerGas": 99, + "maxPriorityFeePerGas": 99, + "nonce": 0, + "r": "0x91b5504a59e529e7efa42dbb97fbc3311a91d035c873a94ab0789441fc989f84", + "s": "0x2e8254d6b3101b63417e5d496833bc84f4832d4a8bf8a2b83e291d8f38c0f62d", + "to": "0x588e4b68193001e4d10928660ab4165b813717c0", + "type": "cip64", + "v": 27, + "value": 1000000000000000000, + "yParity": 0, + }, + "0xb2a81DC4204cF3E96488dFa71a633ae5B336b3fE", + ] + `) + }) it('handles cip42 transactions', () => { const cip42TX = '0x7cf89a82ad5a8063630a94cd2a3d9f938e13cd947ec05abc7fe734df8dd826941be31a94361a391bbafb2a4ccd704f57dc04d4bb82567894588e4b68193001e4d10928660ab4165b813717c0880de0b6b3a764000083abcdefc01ba0c610507b2ac3cff80dd7017419021196807d605efce0970c18cde48db33c27d1a01799477e0f601f554f0ee6f7ac21490602124801e9f7a99d9605249b90f03112' diff --git a/packages/sdk/wallets/wallet-base/src/signing-utils.ts b/packages/sdk/wallets/wallet-base/src/signing-utils.ts index 58d1399c435..1c0f139caa5 100644 --- a/packages/sdk/wallets/wallet-base/src/signing-utils.ts +++ b/packages/sdk/wallets/wallet-base/src/signing-utils.ts @@ -387,10 +387,13 @@ function prefixAwareRLPDecode(rlpEncode: string, type: TransactionTypes): string return type === 'celo-legacy' ? RLP.decode(rlpEncode) : RLP.decode(`0x${rlpEncode.slice(4)}`) } -function correctLengthWithSignatureOf(type: TransactionTypes) { +function correctLengthOf(type: TransactionTypes, hasSignature: boolean = true) { switch (type) { + case 'cip64': { + return hasSignature ? 13 : 10 + } case 'cip42': - return 15 + return hasSignature ? 15 : 12 case 'celo-legacy': case 'eip1559': return 12 @@ -401,9 +404,9 @@ export function extractSignature(rawTx: string) { const type = determineTXType(rawTx) const rawValues = prefixAwareRLPDecode(rawTx, type) const length = rawValues.length - if (correctLengthWithSignatureOf(type) !== length) { + if (correctLengthOf(type) !== length) { throw new Error( - `@extractSignature: provided transaction has ${length} elements but ${type} txs with a signature have ${correctLengthWithSignatureOf( + `@extractSignature: provided transaction has ${length} elements but ${type} txs with a signature have ${correctLengthOf( type )} ${JSON.stringify(rawValues)}` ) @@ -473,9 +476,9 @@ export function recoverTransaction(rawTx: string): [CeloTx, string] { } // inspired by @ethereumjs/tx -function getPublicKeyofSignerFromTx(transactionArray: string[]) { - // TODO this needs to be 10 for cip64 - const base = transactionArray.slice(0, 12) // 12 is length of cip42 without vrs fields +function getPublicKeyofSignerFromTx(transactionArray: string[], type: TransactionTypes) { + // this needs to be 10 for cip64, 12 for cip42 and eip1559 + const base = transactionArray.slice(0, correctLengthOf(type, false)) const message = concatHex([TxTypeToPrefix.cip42, RLP.encode(base).slice(2)]) const msgHash = keccak256(hexToBytes(message)) @@ -494,7 +497,10 @@ function getPublicKeyofSignerFromTx(transactionArray: string[]) { export function getSignerFromTxEIP2718TX(serializedTransaction: string): string { const transactionArray: any[] = RLP.decode(`0x${serializedTransaction.slice(4)}`) - const signer = getPublicKeyofSignerFromTx(transactionArray) + const signer = getPublicKeyofSignerFromTx( + transactionArray, + determineTXType(serializedTransaction) + ) return toChecksumAddress(Address.fromPublicKey(signer).toString()) } diff --git a/packages/sdk/wallets/wallet-local/src/local-wallet.test.ts b/packages/sdk/wallets/wallet-local/src/local-wallet.test.ts index 7f447b18fd8..692c7c9a277 100644 --- a/packages/sdk/wallets/wallet-local/src/local-wallet.test.ts +++ b/packages/sdk/wallets/wallet-local/src/local-wallet.test.ts @@ -1,3 +1,4 @@ +import { StrongAddress } from '@celo/base/lib/address' import { CeloTx, EncodedTransaction, Hex } from '@celo/connect' import { normalizeAddressWith0x, @@ -12,7 +13,6 @@ import { TransactionSerializableEIP1559, parseTransaction } from 'viem' import { privateKeyToAccount } from 'viem/accounts' import Web3 from 'web3' import { LocalWallet } from './local-wallet' -import { StrongAddress } from '@celo/base/lib/address' const CHAIN_ID = 44378 @@ -272,6 +272,35 @@ describe('Local wallet class', () => { ) expect(signedTransaction.raw).toEqual(viemSignedTransaction) }) + test('succeeds with cip64', async () => { + const recoverTransactionCIP64 = { + ...celoTransactionWithGasPrice, + gasPrice: undefined, + gatewayFee: undefined, + gatewayFeeRecipient: undefined, + maxFeePerGas: '99', + maxPriorityFeePerGas: '99', + feeCurrency: '0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826', + } + await expect(wallet.signTransaction(recoverTransactionCIP64)).resolves + .toMatchInlineSnapshot(` + { + "raw": "0x7bf88282ad5a8063630a94588e4b68193001e4d10928660ab4165b813717c0880de0b6b3a764000083abcdefc094cd2a3d9f938e13cd947ec05abc7fe734df8dd82680a091b5504a59e529e7efa42dbb97fbc3311a91d035c873a94ab0789441fc989f84a02e8254d6b3101b63417e5d496833bc84f4832d4a8bf8a2b83e291d8f38c0f62d", + "tx": { + "gas": "0x0a", + "hash": "0x645afc1d19fe805c0c0956e70d5415487bf073741d7b297ccb7e7040c6ce5df6", + "input": "0xabcdef", + "nonce": "0", + "r": "0x91b5504a59e529e7efa42dbb97fbc3311a91d035c873a94ab0789441fc989f84", + "s": "0x2e8254d6b3101b63417e5d496833bc84f4832d4a8bf8a2b83e291d8f38c0f62d", + "to": "0x588e4b68193001e4d10928660ab4165b813717c0", + "v": "0x", + "value": "0x0de0b6b3a7640000", + }, + "type": "cip64", + } + `) + }) test('succeeds with cip42', async () => { const transaction42 = { @@ -279,6 +308,7 @@ describe('Local wallet class', () => { gasPrice: undefined, maxFeePerGas: '99', maxPriorityFeePerGas: '99', + gatewayFee: '0x5678', feeCurrency: '0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826', } await expect(wallet.signTransaction(transaction42)).resolves.toMatchInlineSnapshot(` @@ -343,7 +373,7 @@ describe('Local wallet class', () => { ) }) }) - describe('when using signTransaction with type CIP42', () => { + describe('when using signTransaction with type CIP42/64', () => { let celoTransactionBase: CeloTx let feeCurrency = '0x10c892a6ec43a53e45d0b916b4b7d383b1b78c0f' let maxFeePerGas = '0x100000000' @@ -360,11 +390,26 @@ describe('Local wallet class', () => { data: '0xabcdef', } }) - - describe('when feeCurrency and maxPriorityFeePerGas and maxFeePerGas are set', () => { + describe('when feeCurrency and maxPriorityFeePerGas and maxFeePerGas are set but no gatewayfees', () => { + it('signs as a CIP64 tx', async () => { + const transaction: CeloTx = { + ...celoTransactionBase, + gatewayFee: undefined, + gatewayFeeRecipient: undefined, + feeCurrency, + maxFeePerGas, + maxPriorityFeePerGas, + } + const signedTx: EncodedTransaction = await wallet.signTransaction(transaction) + expect(signedTx.raw).toMatch(/^0x7b/) + }) + }) + describe('when feeCurrency and gatewayFee and maxPriorityFeePerGas and maxFeePerGas are set', () => { it('signs as a CIP42 tx', async () => { const transaction: CeloTx = { ...celoTransactionBase, + gatewayFee: '0x1331', + gatewayFeeRecipient: FEE_ADDRESS, feeCurrency, maxFeePerGas, maxPriorityFeePerGas, diff --git a/packages/sdk/wallets/wallet-local/src/signing.test.ts b/packages/sdk/wallets/wallet-local/src/signing.test.ts index 0fa9af00ffa..ccd53ceb861 100644 --- a/packages/sdk/wallets/wallet-local/src/signing.test.ts +++ b/packages/sdk/wallets/wallet-local/src/signing.test.ts @@ -182,11 +182,12 @@ describe('Transaction Utils', () => { if (celoTransaction.gasPrice != undefined) { description.push(`Testing Legacy with gas price ${celoTransaction.gasPrice}`) } else if ( - celoTransaction.feeCurrency != undefined || celoTransaction.gatewayFeeRecipient !== undefined || celoTransaction.gatewayFee !== undefined ) { description.push('Testing CIP42 with') + } else if (celoTransaction.feeCurrency != undefined) { + description.push('Testing CIP64 with') } else { description.push(`Testing EIP1559 with maxFeePerGas ${celoTransaction.maxFeePerGas}`) }