From 405e003788d8cb01c79ef82b54e28f056f507b3e Mon Sep 17 00:00:00 2001 From: pgherveou Date: Thu, 3 Apr 2025 15:12:37 +0200 Subject: [PATCH 1/4] Update eth-rpc tests Set --no-file-parallelism as otherwise vitest will fire all deployment at once, and that will mess up the nonce count --- eth-rpc/package.json | 4 +- eth-rpc/src/geth-diff.test.ts | 111 ++++++++++++---------------------- eth-rpc/src/redstone.test.ts | 20 +++--- eth-rpc/src/test-setup.ts | 4 ++ eth-rpc/src/util.ts | 17 ++++++ 5 files changed, 70 insertions(+), 86 deletions(-) diff --git a/eth-rpc/package.json b/eth-rpc/package.json index de7f694..e0b50c2 100644 --- a/eth-rpc/package.json +++ b/eth-rpc/package.json @@ -5,8 +5,8 @@ "type": "module", "scripts": { "build": "npx tsx src/build-contracts.ts", - "test": "vitest --test-timeout=30000", - "test-init": "vitest run --test-timeout=30000", + "test": "vitest --test-timeout=30000 --no-file-parallelism", + "test-init": "vitest run --test-timeout=30000 --no-file-parallelism", "prettier": "prettier --write .", "solhint": "solhint \"contracts/**/*.sol\"" }, diff --git a/eth-rpc/src/geth-diff.test.ts b/eth-rpc/src/geth-diff.test.ts index e809a01..2279448 100644 --- a/eth-rpc/src/geth-diff.test.ts +++ b/eth-rpc/src/geth-diff.test.ts @@ -1,9 +1,15 @@ -import { jsonRpcErrors, getByteCode, visit, createEnv } from './util.ts' +import { + jsonRpcErrors, + getByteCode, + visit, + createEnv, + deployFactory, +} from './util.ts' import { afterEach, describe, expect, inject, test } from 'vitest' import fs from 'node:fs' import { fail } from 'node:assert' -import { encodeFunctionData, Hex, parseEther, decodeEventLog } from 'viem' +import { encodeFunctionData, parseEther, decodeEventLog } from 'viem' import { ErrorsAbi } from '../abi/Errors.ts' import { EventExampleAbi } from '../abi/EventExample.ts' import { TracingCallerAbi } from '../abi/TracingCaller.ts' @@ -15,78 +21,36 @@ afterEach(() => { const envs = await Promise.all(inject('envs').map(createEnv)) for (const env of envs) { - describe(`${env.serverWallet.chain.name}`, () => { - const getErrorTesterAddr = (() => { - let contractAddress: Hex = '0x' - return async () => { - if (contractAddress !== '0x') { - return contractAddress - } - const hash = await env.serverWallet.deployContract({ - abi: ErrorsAbi, - bytecode: getByteCode('Errors', env.evm), - }) - const deployReceipt = - await env.serverWallet.waitForTransactionReceipt({ hash }) - contractAddress = deployReceipt.contractAddress! - return contractAddress - } - })() + const getErrorTesterAddr = deployFactory(env, () => + env.serverWallet.deployContract({ + abi: ErrorsAbi, + bytecode: getByteCode('Errors', env.evm), + }) + ) - const getEventExampleAddr = (() => { - let contractAddress: Hex = '0x' - return async () => { - if (contractAddress !== '0x') { - return contractAddress - } - const hash = await env.serverWallet.deployContract({ - abi: EventExampleAbi, - bytecode: getByteCode('EventExample', env.evm), - }) - const deployReceipt = - await env.serverWallet.waitForTransactionReceipt({ hash }) - contractAddress = deployReceipt.contractAddress! - return contractAddress - } - })() - - const getTracingExampleAddrs = (() => { - let callerAddr: Hex = '0x' - let calleeAddr: Hex = '0x' - return async () => { - if (callerAddr !== '0x') { - return [callerAddr, calleeAddr] - } - calleeAddr = await (async () => { - const hash = await env.serverWallet.deployContract({ - abi: TracingCalleeAbi, - bytecode: getByteCode('TracingCallee', env.evm), - }) - const receipt = - await env.serverWallet.waitForTransactionReceipt({ - hash, - }) - return receipt.contractAddress! - })() - - callerAddr = await (async () => { - const hash = await env.serverWallet.deployContract({ - abi: TracingCallerAbi, - args: [calleeAddr], - bytecode: getByteCode('TracingCaller', env.evm), - value: parseEther('10'), - }) - const receipt = - await env.serverWallet.waitForTransactionReceipt({ - hash, - }) - return receipt.contractAddress! - })() - - return [callerAddr, calleeAddr] - } - })() + const getEventExampleAddr = deployFactory(env, async () => + env.serverWallet.deployContract({ + abi: EventExampleAbi, + bytecode: getByteCode('EventExample', env.evm), + }) + ) + const getTracingCalleeAddr = deployFactory(env, async () => + env.serverWallet.deployContract({ + abi: TracingCalleeAbi, + bytecode: getByteCode('TracingCallee', env.evm), + }) + ) + + const getTracingCallerAddr = deployFactory(env, async () => + env.serverWallet.deployContract({ + abi: TracingCallerAbi, + args: [await getTracingCalleeAddr()], + bytecode: getByteCode('TracingCaller', env.evm), + value: parseEther('10'), + }) + ) + describe(`${env.serverWallet.chain.name}`, () => { test('triggerAssertError', async () => { try { await env.accountWallet.readContract({ @@ -390,7 +354,8 @@ for (const env of envs) { }) test('tracing', async () => { - let [callerAddr, calleeAddr] = await getTracingExampleAddrs() + const calleeAddr = await getTracingCalleeAddr() + const callerAddr = await getTracingCallerAddr() const receipt = await (async () => { const { request } = await env.serverWallet.simulateContract({ diff --git a/eth-rpc/src/redstone.test.ts b/eth-rpc/src/redstone.test.ts index 269c991..ec26a71 100644 --- a/eth-rpc/src/redstone.test.ts +++ b/eth-rpc/src/redstone.test.ts @@ -1,5 +1,5 @@ import { ExampleRedstoneShowroomAbi } from '../abi/ExampleRedstoneShowroom.ts' -import { createEnv, getByteCode } from './util.ts' +import { createEnv, deployFactory, getByteCode } from './util.ts' import { Contract, providers } from 'ethers' import { WrapperBuilder } from '@redstone-finance/evm-connector' import { describe, expect, inject, test } from 'vitest' @@ -7,15 +7,6 @@ import { describe, expect, inject, test } from 'vitest' const envs = await Promise.all(inject('envs').map(createEnv)) for (const env of envs) { - const hash = await env.serverWallet.deployContract({ - abi: ExampleRedstoneShowroomAbi, - bytecode: getByteCode('ExampleRedstoneShowroom', env.evm), - }) - const deployReceipt = await env.serverWallet.waitForTransactionReceipt({ - hash, - }) - const contractAddress = deployReceipt.contractAddress! - const provider = new providers.JsonRpcProvider( env.chain.rpcUrls.default.http[0], { @@ -24,10 +15,17 @@ for (const env of envs) { } ) + const getContractAddr = deployFactory(env, () => + env.serverWallet.deployContract({ + abi: ExampleRedstoneShowroomAbi, + bytecode: getByteCode('ExampleRedstoneShowroom', env.evm), + }) + ) + describe(`${env.serverWallet.chain.name}`, () => { test('getTokensPrices works', async () => { const contract = new Contract( - contractAddress, + await getContractAddr(), ExampleRedstoneShowroomAbi as any, provider ) diff --git a/eth-rpc/src/test-setup.ts b/eth-rpc/src/test-setup.ts index 44aa000..668fde8 100644 --- a/eth-rpc/src/test-setup.ts +++ b/eth-rpc/src/test-setup.ts @@ -68,6 +68,10 @@ export default async function setup(project: TestProject) { ...(useEthRpc ? ['eth-rpc' as const] : []), ] + if (envs.length === 0) { + throw new Error('No environment started. Set USE_GETH or USE_ETH_RPC') + } + project.provide('envs', envs) return () => { diff --git a/eth-rpc/src/util.ts b/eth-rpc/src/util.ts index 48be314..69a894d 100644 --- a/eth-rpc/src/util.ts +++ b/eth-rpc/src/util.ts @@ -43,6 +43,7 @@ export function killProcessOnPort(port: number) { } export let jsonRpcErrors: JsonRpcError[] = [] +export type ChainEnv = Awaited> export async function createEnv(name: 'geth' | 'eth-rpc') { const gethPort = process.env.GETH_PORT ?? '8546' const ethRpcPort = process.env.ETH_RPC_PORT ?? '8545' @@ -233,3 +234,19 @@ export function visit( return obj } } + +export function deployFactory(env: ChainEnv, deploy: () => Promise) { + return (() => { + let contractAddress: Hex = '0x' + return async () => { + if (contractAddress !== '0x') { + return contractAddress + } + const hash = await deploy() + const deployReceipt = + await env.serverWallet.waitForTransactionReceipt({ hash }) + contractAddress = deployReceipt.contractAddress! + return contractAddress + } + })() +} From 5a3ec6fd05c9caec095ce782aeeca67749d45629 Mon Sep 17 00:00:00 2001 From: pgherveou Date: Fri, 4 Apr 2025 17:38:11 +0200 Subject: [PATCH 2/4] Update tests --- eth-rpc/contracts/Flipper.sol | 6 +- eth-rpc/contracts/Tester.sol | 26 ++++ eth-rpc/src/geth-diff.test.ts | 8 +- eth-rpc/src/methods.test.ts | 241 ++++++++++++++++++++++++++++++++++ eth-rpc/src/redstone.test.ts | 2 +- eth-rpc/src/util.ts | 43 ++++-- 6 files changed, 312 insertions(+), 14 deletions(-) create mode 100644 eth-rpc/contracts/Tester.sol create mode 100644 eth-rpc/src/methods.test.ts diff --git a/eth-rpc/contracts/Flipper.sol b/eth-rpc/contracts/Flipper.sol index 51aaafc..3dc8e12 100644 --- a/eth-rpc/contracts/Flipper.sol +++ b/eth-rpc/contracts/Flipper.sol @@ -5,8 +5,12 @@ pragma solidity ^0.8.0; contract Flipper { bool public value; + constructor() { + value = true; + } + function flip() external { - value = !value; + value = !value; } function getValue() external view returns (bool) { diff --git a/eth-rpc/contracts/Tester.sol b/eth-rpc/contracts/Tester.sol new file mode 100644 index 0000000..c0e5062 --- /dev/null +++ b/eth-rpc/contracts/Tester.sol @@ -0,0 +1,26 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +// Flipper - Stores and toggles a boolean value +contract Tester { + uint public value; + string public name; + + event TesterDeployed(address indexed creator); + + constructor() payable { + emit TesterDeployed(msg.sender); + value = 42; + name = "Hello world"; + } + + function setValue(uint v) external { + value = v; + } + + function setName(string memory v) external { + name = v; + } + +} + diff --git a/eth-rpc/src/geth-diff.test.ts b/eth-rpc/src/geth-diff.test.ts index 2279448..19991dc 100644 --- a/eth-rpc/src/geth-diff.test.ts +++ b/eth-rpc/src/geth-diff.test.ts @@ -21,28 +21,28 @@ afterEach(() => { const envs = await Promise.all(inject('envs').map(createEnv)) for (const env of envs) { - const getErrorTesterAddr = deployFactory(env, () => + const [getErrorTesterAddr] = deployFactory(env, () => env.serverWallet.deployContract({ abi: ErrorsAbi, bytecode: getByteCode('Errors', env.evm), }) ) - const getEventExampleAddr = deployFactory(env, async () => + const [getEventExampleAddr] = deployFactory(env, async () => env.serverWallet.deployContract({ abi: EventExampleAbi, bytecode: getByteCode('EventExample', env.evm), }) ) - const getTracingCalleeAddr = deployFactory(env, async () => + const [getTracingCalleeAddr] = deployFactory(env, async () => env.serverWallet.deployContract({ abi: TracingCalleeAbi, bytecode: getByteCode('TracingCallee', env.evm), }) ) - const getTracingCallerAddr = deployFactory(env, async () => + const [getTracingCallerAddr] = deployFactory(env, async () => env.serverWallet.deployContract({ abi: TracingCallerAbi, args: [await getTracingCalleeAddr()], diff --git a/eth-rpc/src/methods.test.ts b/eth-rpc/src/methods.test.ts new file mode 100644 index 0000000..c9b5d30 --- /dev/null +++ b/eth-rpc/src/methods.test.ts @@ -0,0 +1,241 @@ +import { + encodeFunctionData, + Hex, + hexToBigInt, + hexToNumber, + parseEther, +} from 'viem' +import { createEnv, deployFactory, getByteCode } from './util.ts' +import { describe, expect, inject, test } from 'vitest' +import { TesterAbi } from '../abi/Tester.ts' + +const envs = await Promise.all(inject('envs').map(createEnv)) + +for (const env of envs) { + const [getTesterAddr, getTesterReceipt] = deployFactory(env, async () => + env.serverWallet.deployContract({ + abi: TesterAbi, + bytecode: getByteCode('Tester', env.evm), + value: parseEther('2'), + }) + ) + describe(`${env.serverWallet.chain.name}`, () => { + test('eth_accounts works', async () => { + const addresses = await env.debugClient.request({ + method: 'eth_accounts', + }) + expect(addresses).toHaveLength(1) + }) + + test('eth_blockNumber works', async () => { + await getTesterReceipt() + const res = await env.debugClient.request({ + method: 'eth_blockNumber', + }) + expect(hexToBigInt(res)).toBeTruthy() + }) + + test('eth_call works', async () => { + const value = await env.serverWallet.readContract({ + address: await getTesterAddr(), + abi: TesterAbi, + functionName: 'value', + }) + expect(value).toEqual(42n) + }) + + test('eth_chainId works', async () => { + const res = await env.debugClient.request({ + method: 'eth_chainId', + }) + expect(hexToNumber(res)).toEqual(env.chain.id) + }) + + test('eth_estimateGas works', async () => { + const res = await env.serverWallet.estimateContractGas({ + address: await getTesterAddr(), + abi: TesterAbi, + functionName: 'setValue', + args: [43n], + }) + + expect(res).toBeTruthy() + }) + + test('eth_gasPrice works', async () => { + const res = await env.debugClient.request({ + method: 'eth_gasPrice', + }) + expect(hexToNumber(res)).toBeTruthy() + }) + + test('eth_getBalance works', async () => { + const balance = await env.serverWallet.getBalance({ + address: await getTesterAddr(), + }) + expect(balance).toEqual(parseEther('2')) + }) + + test('eth_getBlockByHash and eth_getBlockByNumber works', async () => { + const { blockNumber, blockHash } = await getTesterReceipt() + const by_number = await env.serverWallet.getBlock({ + blockNumber, + }) + expect(by_number).toBeTruthy() + + const by_hash = await env.serverWallet.getBlock({ + blockHash, + }) + expect(by_hash).toEqual(by_number) + }) + + test('eth_getBlockTransactionCountByHash and eth_getBlockTransactionCountByNumber works', async () => { + const { blockNumber, blockHash } = await getTesterReceipt() + const byNumber = await env.serverWallet.getBlockTransactionCount({ + blockNumber, + }) + + const byHash = await env.serverWallet.getBlockTransactionCount({ + blockHash, + }) + + expect(byNumber).toEqual(byHash) + expect(byNumber).toBeGreaterThanOrEqual(1) + }) + + test('eth_getCode works', async () => { + const address = await getTesterAddr() + const code = await env.serverWallet.getCode({ + address, + }) + + if (env.evm) { + expect(code).toBeTruthy() + } else { + expect(code).toEqual(getByteCode('Tester', env.evm)) + } + }) + + test('eth_getLogs works', async () => { + const { blockHash } = await getTesterReceipt() + const logs = await env.serverWallet.getLogs({ + blockHash, + }) + + expect(logs).toHaveLength(1) + }) + + test('eth_getStorageAt works', async () => { + const address = await getTesterAddr() + const storage = await env.serverWallet.getStorageAt({ + address, + slot: '0x01', + }) + + // revive store value as little endian. When this change in the compiler, or the runtime API, we can amend this test + if (env.evm) { + expect(storage).toEqual( + '0x48656c6c6f20776f726c64000000000000000000000000000000000000000016' + ) + } else { + expect(storage).toEqual( + '0x160000000000000000000000000000000000000000646c726f77206f6c6c6548' + ) + } + }) + + test('get_transaction_by_block_hash_and_index, eth_getTransactionByBlockNumberAndIndex and eth_getTransactionByHash works', async () => { + const { + transactionHash: hash, + blockHash, + transactionIndex: index, + blockNumber, + } = await getTesterReceipt() + const byTxHash = await env.serverWallet.getTransaction({ hash }) + expect(byTxHash).toBeTruthy() + const byBlockHash = await env.serverWallet.getTransaction({ + blockHash, + index, + }) + expect(byBlockHash).toEqual(byTxHash) + const byBlockNumber = await env.serverWallet.getTransaction({ + blockNumber, + index, + }) + expect(byBlockNumber).toEqual(byTxHash) + }) + + test('eth_getTransactionCount works', async () => { + const count = await env.serverWallet.getTransactionCount( + env.serverWallet.account + ) + expect(count).toBeGreaterThanOrEqual(1) + }) + + test('eth_getTransactionReceipt works', async () => { + const { transactionHash: hash } = await getTesterReceipt() + const receipt = await env.serverWallet.waitForTransactionReceipt({ + hash, + }) + expect(receipt).toBeTruthy() + }) + + test('eth_maxPriorityFeePerGas works', async () => { + const res: Hex = await env.serverWallet.request({ + method: 'eth_maxPriorityFeePerGas' as any, + }) + expect(hexToBigInt(res)).toBeTruthy() + }) + + test('eth_sendRawTransaction works', async () => { + const { request } = await env.accountWallet.simulateContract({ + address: await getTesterAddr(), + abi: TesterAbi, + functionName: 'setValue', + args: [42n], + }) + const hash = await env.accountWallet.writeContract(request) + let receipt = await env.serverWallet.waitForTransactionReceipt({ + hash, + }) + expect(receipt.status, 'success') + }) + + test('eth_sendTransaction works', async () => { + const hash = await env.serverWallet.sendTransaction({ + to: await getTesterAddr(), + data: encodeFunctionData({ + abi: TesterAbi, + functionName: 'setValue', + args: [42n], + }), + }) + let receipt = await env.serverWallet.waitForTransactionReceipt({ + hash, + }) + expect(receipt.status, 'success') + }) + + test('eth_syncing works', async () => { + const res = await env.serverWallet.request({ + method: 'eth_syncing', + }) + + expect(res).toEqual(false) + }) + + test('net_version works', async () => { + const res = await env.serverWallet.request({ + method: 'net_version' as any, + }) + expect(res).toBeTruthy() + }) + + test('web3_clientVersion works', async () => { + const res = await env.serverWallet.request({ + method: 'web3_clientVersion' as any, + }) + expect(res).toBeTruthy() + }) + }) +} diff --git a/eth-rpc/src/redstone.test.ts b/eth-rpc/src/redstone.test.ts index ec26a71..54a21fb 100644 --- a/eth-rpc/src/redstone.test.ts +++ b/eth-rpc/src/redstone.test.ts @@ -15,7 +15,7 @@ for (const env of envs) { } ) - const getContractAddr = deployFactory(env, () => + const [getContractAddr] = deployFactory(env, () => env.serverWallet.deployContract({ abi: ExampleRedstoneShowroomAbi, bytecode: getByteCode('ExampleRedstoneShowroom', env.evm), diff --git a/eth-rpc/src/util.ts b/eth-rpc/src/util.ts index 69a894d..8be7612 100644 --- a/eth-rpc/src/util.ts +++ b/eth-rpc/src/util.ts @@ -9,7 +9,9 @@ import { type Hex, hexToNumber, http, + parseEther, publicActions, + TransactionReceipt, } from 'viem' import { privateKeyToAccount, nonceManager } from 'viem/accounts' @@ -111,6 +113,20 @@ export async function createEnv(name: 'geth' | 'eth-rpc') { chain, }).extend(publicActions) + // On geth let's endow the account wallet with some funds, to match the eth-rpc setup + if (name == 'geth') { + const endowment = parseEther('1000') + const balance = await serverWallet.getBalance(accountWallet.account) + if (balance < endowment / 2n) { + const hash = await serverWallet.sendTransaction({ + account: serverWallet.account, + to: accountWallet.account.address, + value: endowment, + }) + await serverWallet.waitForTransactionReceipt({ hash }) + } + } + const emptyWallet = createWalletClient({ account: privateKeyToAccount( '0x4450c571bae82da0528ecf76fcf7079e12ecc46dc873c9cacb6db8b75ed22f41', @@ -237,16 +253,27 @@ export function visit( export function deployFactory(env: ChainEnv, deploy: () => Promise) { return (() => { - let contractAddress: Hex = '0x' - return async () => { - if (contractAddress !== '0x') { - return contractAddress + let address: Hex | null = null + let receipt: TransactionReceipt | null = null + async function getAddress() { + if (address) { + return address } const hash = await deploy() - const deployReceipt = - await env.serverWallet.waitForTransactionReceipt({ hash }) - contractAddress = deployReceipt.contractAddress! - return contractAddress + receipt = await env.serverWallet.waitForTransactionReceipt({ + hash, + }) + address = receipt.contractAddress! + return address + } + async function getReceipt() { + if (receipt) { + return receipt + } + await getAddress() + return receipt! } + + return [getAddress, getReceipt] as const })() } From a835ae64f4d0ee30cfd299aa132196a8a651d3bc Mon Sep 17 00:00:00 2001 From: pgherveou Date: Fri, 4 Apr 2025 17:40:53 +0200 Subject: [PATCH 3/4] solhint --- eth-rpc/contracts/Tester.sol | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/eth-rpc/contracts/Tester.sol b/eth-rpc/contracts/Tester.sol index c0e5062..48ded9b 100644 --- a/eth-rpc/contracts/Tester.sol +++ b/eth-rpc/contracts/Tester.sol @@ -3,7 +3,7 @@ pragma solidity ^0.8.0; // Flipper - Stores and toggles a boolean value contract Tester { - uint public value; + uint256 public value; string public name; event TesterDeployed(address indexed creator); @@ -14,7 +14,7 @@ contract Tester { name = "Hello world"; } - function setValue(uint v) external { + function setValue(uint256 v) external { value = v; } From 10cacf5aa0f0caaa73a72c32ac8181120a9e4d47 Mon Sep 17 00:00:00 2001 From: pgherveou Date: Mon, 7 Apr 2025 11:15:51 +0200 Subject: [PATCH 4/4] fix tests --- eth-rpc/src/methods.test.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/eth-rpc/src/methods.test.ts b/eth-rpc/src/methods.test.ts index c9b5d30..c746a11 100644 --- a/eth-rpc/src/methods.test.ts +++ b/eth-rpc/src/methods.test.ts @@ -198,7 +198,7 @@ for (const env of envs) { let receipt = await env.serverWallet.waitForTransactionReceipt({ hash, }) - expect(receipt.status, 'success') + expect(receipt.status).toEqual('success') }) test('eth_sendTransaction works', async () => { @@ -213,7 +213,7 @@ for (const env of envs) { let receipt = await env.serverWallet.waitForTransactionReceipt({ hash, }) - expect(receipt.status, 'success') + expect(receipt.status).toEqual('success') }) test('eth_syncing works', async () => {