diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 1babc7378..3003e531e 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -33,6 +33,7 @@ jobs: version: nightly - name: Start Anvil in background run: anvil --fork-url https://nodes.sequence.app/arbitrum & + - run: pnpm build - run: pnpm test # NOTE: if you'd like to see example of how to run diff --git a/packages/wallet/core/test/constants.ts b/packages/wallet/core/test/constants.ts index 0622a2db9..493bf6bf2 100644 --- a/packages/wallet/core/test/constants.ts +++ b/packages/wallet/core/test/constants.ts @@ -17,5 +17,3 @@ export const USDC_ADDRESS: Address.Address = '0xaf88d065e77c8cc2239327c5edb3a432 // Environment variables export const LOCAL_RPC_URL = process.env.LOCAL_RPC_URL || 'http://localhost:8545' -export const { RPC_URL, PRIVATE_KEY } = process.env -export const CAN_RUN_LIVE = !!RPC_URL && !!PRIVATE_KEY diff --git a/packages/wallet/core/test/session-manager.test.ts b/packages/wallet/core/test/session-manager.test.ts index bfbc28b17..9e524fd13 100644 --- a/packages/wallet/core/test/session-manager.test.ts +++ b/packages/wallet/core/test/session-manager.test.ts @@ -1,20 +1,17 @@ +import { Extensions } from '@0xsequence/wallet-primitives' import { AbiEvent, AbiFunction, Address, Bytes, Hex, Provider, RpcTransport, Secp256k1 } from 'ox' import { describe, expect, it } from 'vitest' - import { Attestation, GenericTree, Payload, Permission, SessionConfig } from '../../primitives/src/index.js' import { Envelope, Signers, State, Utils, Wallet } from '../src/index.js' - +import { ExplicitSessionConfig } from '../src/utils/session/types.js' import { - EMITTER_FUNCTIONS, EMITTER_ADDRESS1, EMITTER_ADDRESS2, EMITTER_EVENT_TOPICS, + EMITTER_FUNCTIONS, LOCAL_RPC_URL, USDC_ADDRESS, } from './constants' -import { Extensions } from '@0xsequence/wallet-primitives' -import { ExplicitSessionConfig } from '../src/utils/session/types.js' - const { PermissionBuilder, ERC20PermissionBuilder } = Utils function randomAddress(): Address.Address { @@ -605,10 +602,26 @@ for (const extension of ALL_EXTENSIONS) { transaction: { to: Address.Address; data: Hex.Hex }, expectedEventTopic?: Hex.Hex, ) => { + // Generate and use a random sender address to prevent race conditions + const senderAddress = Address.fromPublicKey(Secp256k1.getPublicKey({ privateKey: Secp256k1.randomPrivateKey() })) + await provider.request({ + method: 'anvil_setBalance', + params: [senderAddress, Hex.fromNumber(1000000000000000000n)], + }) + await provider.request({ + method: 'anvil_impersonateAccount', + params: [senderAddress], + }) + console.log('Simulating transaction', transaction) const txHash = await provider.request({ method: 'eth_sendTransaction', - params: [transaction], + params: [ + { + ...transaction, + from: senderAddress, + }, + ], }) console.log('Transaction hash:', txHash) diff --git a/packages/wallet/core/vitest.config.ts b/packages/wallet/core/vitest.config.ts deleted file mode 100644 index 0b2f7c6c7..000000000 --- a/packages/wallet/core/vitest.config.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { defineConfig } from 'vitest/config' - -export default defineConfig({ - test: { - poolOptions: { - singleThread: true, - }, - }, -}) diff --git a/packages/wallet/wdk/.env.test b/packages/wallet/wdk/.env.test deleted file mode 100644 index 84a53e8c0..000000000 --- a/packages/wallet/wdk/.env.test +++ /dev/null @@ -1,5 +0,0 @@ -PRIVATE_KEY= -# When using an RPC, use cors-anywhere. docker run -d -p 8080:8080 redocly/cors-anywhere. http://localhost:8080/https... -RPC_URL= -RELAYER_PK= - diff --git a/packages/wallet/wdk/test/constants.ts b/packages/wallet/wdk/test/constants.ts index 4ace92092..14b099614 100644 --- a/packages/wallet/wdk/test/constants.ts +++ b/packages/wallet/wdk/test/constants.ts @@ -13,8 +13,6 @@ export const EMITTER_ADDRESS: Address.Address = '0xb7bE532959236170064cf099e1a33 export const EMITTER_ABI = Abi.from(['function explicitEmit()', 'function implicitEmit()']) // Environment variables -export const { RPC_URL, PRIVATE_KEY } = process.env -export const CAN_RUN_LIVE = !!RPC_URL && !!PRIVATE_KEY export const LOCAL_RPC_URL = process.env.LOCAL_RPC_URL || 'http://localhost:8545' let testIdCounter = 0 diff --git a/packages/wallet/wdk/test/sessions.test.ts b/packages/wallet/wdk/test/sessions.test.ts index 67e081fb0..e942f7479 100644 --- a/packages/wallet/wdk/test/sessions.test.ts +++ b/packages/wallet/wdk/test/sessions.test.ts @@ -1,10 +1,10 @@ -import { AbiFunction, Address, Bytes, Hex, Mnemonic, Provider, RpcTransport } from 'ox' -import { beforeEach, describe, expect, it, vi } from 'vitest' +import { AbiFunction, Address, Bytes, Hex, Mnemonic, Provider, RpcTransport, Secp256k1 } from 'ox' +import { beforeEach, describe, expect, it } from 'vitest' import { Signers as CoreSigners, Wallet as CoreWallet, Envelope, State } from '../../core/src/index.js' -import { Attestation, Constants, Extensions, Network, Payload, Permission } from '../../primitives/src/index.js' -import { Sequence } from '../src/index.js' -import { CAN_RUN_LIVE, EMITTER_ABI, EMITTER_ADDRESS, PRIVATE_KEY, RPC_URL } from './constants' import { ExplicitSession } from '../../core/src/utils/session/types.js' +import { Extensions, Network, Payload, Permission } from '../../primitives/src/index.js' +import { Sequence } from '../src/index.js' +import { EMITTER_ABI, EMITTER_ADDRESS, LOCAL_RPC_URL } from './constants' const ALL_EXTENSIONS = [ { @@ -67,26 +67,17 @@ for (const extension of ALL_EXTENSIONS) { } beforeEach(async () => { - // Create provider or use arbitrum sepolia - if (RPC_URL) { - provider = Provider.from( - RpcTransport.fromHttp(RPC_URL, { - fetchOptions: { - headers: { - 'x-requested-with': 'XMLHttpRequest', - }, + // Create provider using LOCAL_RPC_URL + provider = Provider.from( + RpcTransport.fromHttp(LOCAL_RPC_URL, { + fetchOptions: { + headers: { + 'x-requested-with': 'XMLHttpRequest', }, - }), - ) - chainId = Number(await provider.request({ method: 'eth_chainId' })) - } else { - provider = vi.mocked({ - request: vi.fn(), - on: vi.fn(), - removeListener: vi.fn(), - }) - chainId = Network.ChainId.MAINNET - } + }, + }), + ) + chainId = Number(await provider.request({ method: 'eth_chainId' })) // Create state provider stateProvider = new State.Local.Provider() @@ -99,7 +90,7 @@ for (const extension of ALL_EXTENSIONS) { { chainId, type: Network.NetworkType.MAINNET, - rpcUrl: RPC_URL ?? 'XXX', + rpcUrl: LOCAL_RPC_URL, name: 'XXX', blockExplorer: { url: 'XXX' }, nativeCurrency: { @@ -185,18 +176,32 @@ for (const extension of ALL_EXTENSIONS) { const transaction = await dapp.wallet.buildTransaction(provider, signedEnvelope) console.log('tx', transaction) + // Generate and use a random sender address to prevent race conditions + const senderAddress = Address.fromPublicKey(Secp256k1.getPublicKey({ privateKey: Secp256k1.randomPrivateKey() })) + await provider.request({ + method: 'anvil_setBalance', + params: [senderAddress, Hex.fromNumber(1000000000000000000n)], + }) + await provider.request({ + method: 'anvil_impersonateAccount', + params: [senderAddress], + }) + // Send the transaction - if (CAN_RUN_LIVE && PRIVATE_KEY) { - // Load the sender - const senderPk = Hex.from(PRIVATE_KEY as `0x${string}`) - const pkRelayer = new Relayer.Standard.PkRelayer(senderPk, provider) - const tx = await pkRelayer.relay(transaction.to, transaction.data, chainId, undefined) - console.log('Transaction sent', tx) - await new Promise((resolve) => setTimeout(resolve, 3000)) - const receipt = await provider.request({ method: 'eth_getTransactionReceipt', params: [tx.opHash] }) - console.log('Transaction receipt', receipt) - return tx.opHash - } + const txHash = await provider.request({ + method: 'eth_sendTransaction', + params: [ + { + ...transaction, + from: senderAddress, + }, + ], + }) + console.log('Transaction sent', txHash) + await new Promise((resolve) => setTimeout(resolve, 3000)) + const receipt = await provider.request({ method: 'eth_getTransactionReceipt', params: [txHash] }) + console.log('Transaction receipt', receipt) + return txHash } it( @@ -243,6 +248,7 @@ for (const extension of ALL_EXTENSIONS) { throw new Error('Failed to create pk store') } const explicitSession: ExplicitSession = { + type: 'explicit', sessionAddress: e.address, chainId, valueLimit: 0n, @@ -271,34 +277,10 @@ for (const extension of ALL_EXTENSIONS) { behaviorOnError: 'revert', } - if (!RPC_URL) { - // Configure mock provider - ;(provider as any).request.mockImplementation(({ method, params }) => { - if (method === 'eth_chainId') { - return Promise.resolve(chainId.toString()) - } - if (method === 'eth_call' && params[0].data === AbiFunction.encodeData(Constants.GET_IMPLEMENTATION)) { - // Undeployed wallet - return Promise.resolve('0x') - } - if (method === 'eth_call' && params[0].data === AbiFunction.encodeData(Constants.READ_NONCE, [0n])) { - // Nonce is 0 - return Promise.resolve('0x00') - } - if ( - method === 'eth_call' && - params[0].data?.startsWith(AbiFunction.getSelector(Constants.GET_LIMIT_USAGE)) - ) { - // Return 0 for usage limit (no usage yet) - return Promise.resolve('0x0000000000000000000000000000000000000000000000000000000000000000') - } - }) - } - // Sign and send the transaction await signAndSend(call) }, - PRIVATE_KEY || RPC_URL ? { timeout: 60000 } : undefined, + { timeout: 60000 }, ) it( @@ -311,6 +293,7 @@ for (const extension of ALL_EXTENSIONS) { throw new Error('Failed to create pk store') } const explicitSession: ExplicitSession = { + type: 'explicit', sessionAddress: e.address, chainId, valueLimit: 0n, @@ -348,34 +331,10 @@ for (const extension of ALL_EXTENSIONS) { behaviorOnError: 'revert', } - if (!RPC_URL) { - // Configure mock provider - ;(provider as any).request.mockImplementation(({ method, params }) => { - if (method === 'eth_chainId') { - return Promise.resolve(chainId.toString()) - } - if (method === 'eth_call' && params[0].data === AbiFunction.encodeData(Constants.GET_IMPLEMENTATION)) { - // Undeployed wallet - return Promise.resolve('0x') - } - if (method === 'eth_call' && params[0].data === AbiFunction.encodeData(Constants.READ_NONCE, [0n])) { - // Nonce is 0 - return Promise.resolve('0x00') - } - if ( - method === 'eth_call' && - params[0].data?.startsWith(AbiFunction.getSelector(Constants.GET_LIMIT_USAGE)) - ) { - // Return 0 for usage limit (no usage yet) - return Promise.resolve('0x0000000000000000000000000000000000000000000000000000000000000000') - } - }) - } - // Sign and send the transaction await signAndSend(call) }, - PRIVATE_KEY || RPC_URL ? { timeout: 60000 } : undefined, + { timeout: 60000 }, ) it( @@ -389,6 +348,7 @@ for (const extension of ALL_EXTENSIONS) { } // Create the initial permissions let explicitSession: ExplicitSession = { + type: 'explicit', sessionAddress: e.address, chainId, valueLimit: 0n, @@ -426,30 +386,6 @@ for (const extension of ALL_EXTENSIONS) { behaviorOnError: 'revert', } - if (!RPC_URL) { - // Configure mock provider - ;(provider as any).request.mockImplementation(({ method, params }) => { - if (method === 'eth_chainId') { - return Promise.resolve(chainId.toString()) - } - if (method === 'eth_call' && params[0].data === AbiFunction.encodeData(Constants.GET_IMPLEMENTATION)) { - // Undeployed wallet - return Promise.resolve('0x') - } - if (method === 'eth_call' && params[0].data === AbiFunction.encodeData(Constants.READ_NONCE, [0n])) { - // Nonce is 0 - return Promise.resolve('0x00') - } - if ( - method === 'eth_call' && - params[0].data?.startsWith(AbiFunction.getSelector(Constants.GET_LIMIT_USAGE)) - ) { - // Return 0 for usage limit (no usage yet) - return Promise.resolve('0x0000000000000000000000000000000000000000000000000000000000000000') - } - }) - } - // Sign and send the transaction await signAndSend(call) @@ -463,7 +399,7 @@ for (const extension of ALL_EXTENSIONS) { // Should fail with 'No signer supported for call' await expect(signAndSend(call)).rejects.toThrow('No signer supported for call') }, - PRIVATE_KEY || RPC_URL ? { timeout: 60000 } : undefined, + { timeout: 60000 }, ) it( @@ -516,38 +452,10 @@ for (const extension of ALL_EXTENSIONS) { behaviorOnError: 'revert', } - if (!RPC_URL) { - // Configure mock provider - ;(provider as any).request.mockImplementation(({ method, params }) => { - if (method === 'eth_chainId') { - return Promise.resolve(chainId.toString()) - } - if (method === 'eth_call' && params[0].data === AbiFunction.encodeData(Constants.GET_IMPLEMENTATION)) { - // Undeployed wallet - return Promise.resolve('0x') - } - if (method === 'eth_call' && params[0].data === AbiFunction.encodeData(Constants.READ_NONCE, [0n])) { - // Nonce is 0 - return Promise.resolve('0x00') - } - if ( - method === 'eth_call' && - Address.isEqual(params[0].from, dapp.sessionManager.address) && - Address.isEqual(params[0].to, call.to) - ) { - // Implicit request simulation result - const expectedResult = Bytes.toHex( - Attestation.generateImplicitRequestMagic(attestation, dapp.wallet.address), - ) - return Promise.resolve(expectedResult) - } - }) - } - // Sign and send the transaction await signAndSend(call) }, - PRIVATE_KEY || RPC_URL ? { timeout: 60000 } : undefined, + { timeout: 60000 }, ) }) } diff --git a/packages/wallet/wdk/vitest.config.ts b/packages/wallet/wdk/vitest.config.ts index 813dc499d..9c2092c0c 100644 --- a/packages/wallet/wdk/vitest.config.ts +++ b/packages/wallet/wdk/vitest.config.ts @@ -1,4 +1,3 @@ -import { BrowserNavigationCrossOriginPolicyEnum } from 'happy-dom' import { defineConfig } from 'vitest/config' export default defineConfig({