diff --git a/README.md b/README.md index 808d270..a5a20c7 100644 --- a/README.md +++ b/README.md @@ -45,8 +45,7 @@ Tests are configured via environment variables: - `START_ETH_RPC=1` - Automatically start revive eth-rpc server - `ETH_RPC_PATH` - Path to the eth-rpc binary (default: to ~/polkadot-sdk/target/debug/eth-rpc) -- `USE_REVIVE=evm|pvm` - Whether to run tests against revive with evm or pvm bytecode, default to `evm` if not specified +- `USE_REVIVE=evm|pvm` - Whether to run tests against revive with evm or pvm bytecode, default to `evm` if not specified - `START_REVIVE_DEV_NODE=1` - Start Revive dev node - `REVIVE_DEV_NODE_PATH` - Path to the Revive dev node binary (default: to ~/polkadot-sdk/target/debug/revive-dev-node) - diff --git a/contracts/ReturnDataTester.sol b/contracts/ReturnDataTester.sol new file mode 100644 index 0000000..461cccd --- /dev/null +++ b/contracts/ReturnDataTester.sol @@ -0,0 +1,22 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; +import "./contracts/Errors.sol"; + +// Parent contract that creates a child and captures return data +contract ReturnDataTester { + uint public returndatasize; + + function createChildContract() external { + new Errors(); + uint size; + assembly { + size := returndatasize() + } + returndatasize = size; + } + + // Read-only function to return the captured return data size + function getCapturedReturnDataSize() external view returns (uint) { + return returndatasize; + } +} \ No newline at end of file diff --git a/src/others.test.ts b/src/others.test.ts index fa00cc0..c7e3d16 100644 --- a/src/others.test.ts +++ b/src/others.test.ts @@ -9,6 +9,7 @@ import { expect } from '@std/expect' import { decodeEventLog, encodeFunctionData, parseEther } from 'viem' import { ErrorsAbi } from '../codegen/abi/Errors.ts' import { EventExampleAbi } from '../codegen/abi/EventExample.ts' +import { ReturnDataTesterAbi } from '../codegen/abi/ReturnDataTester.ts' // Initialize test environment const env = await getEnv() @@ -31,6 +32,15 @@ const getEventExampleAddr = memoizedDeploy( }), ) +const getReturnDataTesterAddr = memoizedDeploy( + env, + () => + env.serverWallet.deployContract({ + abi: ReturnDataTesterAbi, + bytecode: getByteCode('ReturnDataTester', env.evm), + }), +) + Deno.test('eth_call with insufficient funds', opts, async () => { try { await env.emptyWallet.simulateContract({ @@ -44,18 +54,14 @@ Deno.test('eth_call with insufficient funds', opts, async () => { } catch (_) { const lastJsonRpcError = jsonRpcErrors.pop() expect(lastJsonRpcError?.code).toBe(-32000) - expect(lastJsonRpcError?.message).toContain( - 'insufficient funds', - ) + expect(lastJsonRpcError?.message).toContain('insufficient funds') expect(lastJsonRpcError?.data).toBeUndefined() } }) Deno.test('eth_call transfer with insufficient funds', opts, async () => { const value = parseEther('10') - const balance = await env.emptyWallet.getBalance( - env.emptyWallet.account, - ) + const balance = await env.emptyWallet.getBalance(env.emptyWallet.account) if (balance >= value) { throw new Error('Balance should be less than 10') } @@ -68,9 +74,7 @@ Deno.test('eth_call transfer with insufficient funds', opts, async () => { } catch (_) { const lastJsonRpcError = jsonRpcErrors.pop() expect(lastJsonRpcError?.code).toBe(-32000) - expect(lastJsonRpcError?.message).toContain( - 'insufficient funds', - ) + expect(lastJsonRpcError?.message).toContain('insufficient funds') expect(lastJsonRpcError?.data).toBeUndefined() } }) @@ -88,9 +92,7 @@ Deno.test('eth_estimate with insufficient funds', opts, async () => { } catch (_err) { const lastJsonRpcError = jsonRpcErrors.pop() expect(lastJsonRpcError?.code).toBe(-32000) - expect(lastJsonRpcError?.message).toContain( - 'insufficient funds', - ) + expect(lastJsonRpcError?.message).toContain('insufficient funds') expect(lastJsonRpcError?.data).toBeUndefined() } }) @@ -111,9 +113,7 @@ Deno.test( } catch (_err) { const lastJsonRpcError = jsonRpcErrors.pop() expect(lastJsonRpcError?.code).toBe(-32000) - expect(lastJsonRpcError?.message).toContain( - 'insufficient funds', - ) + expect(lastJsonRpcError?.message).toContain('insufficient funds') expect(lastJsonRpcError?.data).toBeUndefined() } }, @@ -168,18 +168,14 @@ Deno.test( } catch (_err) { const lastJsonRpcError = jsonRpcErrors.pop() expect(lastJsonRpcError?.code).toBe(-32000) - expect(lastJsonRpcError?.message).toContain( - 'insufficient funds', - ) + expect(lastJsonRpcError?.message).toContain('insufficient funds') expect(lastJsonRpcError?.data).toBeUndefined() } }, ) Deno.test('eth_estimate with no gas specified', opts, async () => { - const balance = await env.serverWallet.getBalance( - env.emptyWallet.account, - ) + const balance = await env.serverWallet.getBalance(env.emptyWallet.account) expect(balance).toBe(0n) const data = encodeFunctionData({ @@ -209,9 +205,7 @@ Deno.test('logs', opts, async () => { }) const hash = await env.serverWallet.writeContract(request) - const receipt = await env.serverWallet.waitForTransactionReceipt( - hash, - ) + const receipt = await env.serverWallet.waitForTransactionReceipt(hash) const logs = await env.serverWallet.getLogs({ address, blockHash: receipt.blockHash, @@ -239,3 +233,57 @@ Deno.test('logs', opts, async () => { }, }) }) + +Deno.test('returndata_works', opts, async () => { + // 1. deploy ReturnDataTester contract and get its address + const address = await getReturnDataTesterAddr() + + // 2. Make child contract code available + await getErrorTesterAddr() + + // 3. call createChildContract to create a child contract + const { request } = await env.serverWallet.simulateContract({ + address, + abi: ReturnDataTesterAbi, + functionName: 'createChildContract', + }) + const hash = await env.serverWallet.writeContract(request) + const receipt = await env.serverWallet.waitForTransactionReceipt(hash) + expect(receipt.status).toEqual('success') + + // 4. call getCapturedReturnDataSize to get the recorded return data size + const dataSize = await env.emptyWallet.readContract({ + address: address, + abi: ReturnDataTesterAbi, + functionName: 'getCapturedReturnDataSize', + args: [], + }) + + expect(dataSize).toBe(0n) +}) + +Deno.test('eth_call_deployment_returns_bytecode', opts, async () => { + const result = await env.serverWallet.call({ + data: getByteCode('Errors', env.evm), + }) + + expect(typeof result).toBe('object') + if (env.evm) { + expect(result).not.toBeNull() + expect('data' in result).toBe(true) + expect(typeof result.data).toBe('string') + const data = result['data'] + if (typeof data !== 'string') { + throw new Error( + `expected result.data to be string, got ${typeof data}`, + ) + } + + // hex string; '0xDDDD...' + expect(data.startsWith('0x')).toBe(true) + expect(data.length).toBeGreaterThan(2) + } else { + // PVM does not return runtime bytecode for contract deployment calls + expect(result.data).toBeUndefined() + } +}) diff --git a/src/util.ts b/src/util.ts index 50de3dc..6957f93 100644 --- a/src/util.ts +++ b/src/util.ts @@ -340,7 +340,7 @@ export function waitForHealth(url: string) { resolve() } catch (_err) { const elapsed = Date.now() - start - if (elapsed > 30_000) { + if (elapsed > 60_000) { clearInterval(interval) reject(new Error('hit timeout')) }