diff --git a/substrate/frame/revive/fixtures/contracts/dummy_2.rs b/substrate/frame/revive/fixtures/contracts/dummy_2.rs new file mode 100644 index 000000000000..e4020460534a --- /dev/null +++ b/substrate/frame/revive/fixtures/contracts/dummy_2.rs @@ -0,0 +1,33 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +#![no_std] +#![no_main] + +extern crate common; +use uapi::{HostFn, HostFnImpl as api, StorageFlags}; + +#[no_mangle] +#[polkavm_derive::polkavm_export] +pub extern "C" fn deploy() {} + +#[no_mangle] +#[polkavm_derive::polkavm_export] +pub extern "C" fn call() { + // Burn some PoV, clear_storage consumes some PoV as in order to clear the storage we need to we + // need to read its size first. + api::clear_storage(StorageFlags::empty(), b""); +} diff --git a/substrate/frame/revive/rpc/examples/js/abi/Callee.json b/substrate/frame/revive/rpc/examples/js/abi/Callee.json new file mode 100644 index 000000000000..89e61845b15b --- /dev/null +++ b/substrate/frame/revive/rpc/examples/js/abi/Callee.json @@ -0,0 +1,15 @@ +[ + { + "inputs": [ + { + "internalType": "uint256", + "name": "iterations", + "type": "uint256" + } + ], + "name": "consumeGas", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + } +] \ No newline at end of file diff --git a/substrate/frame/revive/rpc/examples/js/abi/Callee.ts b/substrate/frame/revive/rpc/examples/js/abi/Callee.ts new file mode 100644 index 000000000000..e77b1c233d64 --- /dev/null +++ b/substrate/frame/revive/rpc/examples/js/abi/Callee.ts @@ -0,0 +1,15 @@ +export const CalleeAbi = [ + { + inputs: [ + { + internalType: "uint256", + name: "iterations", + type: "uint256", + }, + ], + name: "consumeGas", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, +] as const; diff --git a/substrate/frame/revive/rpc/examples/js/abi/Caller.json b/substrate/frame/revive/rpc/examples/js/abi/Caller.json new file mode 100644 index 000000000000..a9f5bbdc7870 --- /dev/null +++ b/substrate/frame/revive/rpc/examples/js/abi/Caller.json @@ -0,0 +1,57 @@ +[ + { + "inputs": [ + { + "internalType": "uint256", + "name": "_initialCounter", + "type": "uint256" + }, + { + "internalType": "address", + "name": "_callee", + "type": "address" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "inputs": [], + "name": "callee", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "counter", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "gasToConsume", + "type": "uint256" + } + ], + "name": "start", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + } +] \ No newline at end of file diff --git a/substrate/frame/revive/rpc/examples/js/abi/Caller.ts b/substrate/frame/revive/rpc/examples/js/abi/Caller.ts new file mode 100644 index 000000000000..e6dd2ad36340 --- /dev/null +++ b/substrate/frame/revive/rpc/examples/js/abi/Caller.ts @@ -0,0 +1,57 @@ +export const CallerAbi = [ + { + inputs: [ + { + internalType: "uint256", + name: "_initialCounter", + type: "uint256", + }, + { + internalType: "address", + name: "_callee", + type: "address", + }, + ], + stateMutability: "nonpayable", + type: "constructor", + }, + { + inputs: [], + name: "callee", + outputs: [ + { + internalType: "address", + name: "", + type: "address", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "counter", + outputs: [ + { + internalType: "uint256", + name: "", + type: "uint256", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { + internalType: "uint256", + name: "gasToConsume", + type: "uint256", + }, + ], + name: "start", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, +] as const; diff --git a/substrate/frame/revive/rpc/examples/js/abi/PiggyBank.ts b/substrate/frame/revive/rpc/examples/js/abi/PiggyBank.ts new file mode 100644 index 000000000000..a6b8c1b0be56 --- /dev/null +++ b/substrate/frame/revive/rpc/examples/js/abi/PiggyBank.ts @@ -0,0 +1,65 @@ +export const PiggyBankAbi = [ + { + inputs: [], + stateMutability: "nonpayable", + type: "constructor", + }, + { + inputs: [], + name: "deposit", + outputs: [ + { + internalType: "uint256", + name: "", + type: "uint256", + }, + ], + stateMutability: "payable", + type: "function", + }, + { + inputs: [], + name: "getDeposit", + outputs: [ + { + internalType: "uint256", + name: "", + type: "uint256", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "owner", + outputs: [ + { + internalType: "address", + name: "", + type: "address", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { + internalType: "uint256", + name: "withdrawAmount", + type: "uint256", + }, + ], + name: "withdraw", + outputs: [ + { + internalType: "uint256", + name: "remainingBal", + type: "uint256", + }, + ], + stateMutability: "nonpayable", + type: "function", + }, +] as const; diff --git a/substrate/frame/revive/rpc/examples/js/abi/TracingCallee.json b/substrate/frame/revive/rpc/examples/js/abi/TracingCallee.json new file mode 100644 index 000000000000..de641c7c9606 --- /dev/null +++ b/substrate/frame/revive/rpc/examples/js/abi/TracingCallee.json @@ -0,0 +1,29 @@ +[ + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "caller", + "type": "address" + } + ], + "name": "GasConsumed", + "type": "event" + }, + { + "inputs": [], + "name": "consumeGas", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "failingFunction", + "outputs": [], + "stateMutability": "pure", + "type": "function" + } +] \ No newline at end of file diff --git a/substrate/frame/revive/rpc/examples/js/abi/TracingCallee.ts b/substrate/frame/revive/rpc/examples/js/abi/TracingCallee.ts new file mode 100644 index 000000000000..630cd79b7d99 --- /dev/null +++ b/substrate/frame/revive/rpc/examples/js/abi/TracingCallee.ts @@ -0,0 +1,29 @@ +export const TracingCalleeAbi = [ + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: "address", + name: "caller", + type: "address", + }, + ], + name: "GasConsumed", + type: "event", + }, + { + inputs: [], + name: "consumeGas", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [], + name: "failingFunction", + outputs: [], + stateMutability: "pure", + type: "function", + }, +] as const; diff --git a/substrate/frame/revive/rpc/examples/js/abi/TracingCaller.json b/substrate/frame/revive/rpc/examples/js/abi/TracingCaller.json new file mode 100644 index 000000000000..fe9b2b713bc3 --- /dev/null +++ b/substrate/frame/revive/rpc/examples/js/abi/TracingCaller.json @@ -0,0 +1,39 @@ +[ + { + "inputs": [ + { + "internalType": "address", + "name": "_callee", + "type": "address" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "inputs": [], + "name": "callee", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "counter", + "type": "uint256" + } + ], + "name": "start", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + } +] \ No newline at end of file diff --git a/substrate/frame/revive/rpc/examples/js/abi/TracingCaller.ts b/substrate/frame/revive/rpc/examples/js/abi/TracingCaller.ts new file mode 100644 index 000000000000..d83994aa0c75 --- /dev/null +++ b/substrate/frame/revive/rpc/examples/js/abi/TracingCaller.ts @@ -0,0 +1,39 @@ +export const TracingCallerAbi = [ + { + inputs: [ + { + internalType: "address", + name: "_callee", + type: "address", + }, + ], + stateMutability: "nonpayable", + type: "constructor", + }, + { + inputs: [], + name: "callee", + outputs: [ + { + internalType: "address", + name: "", + type: "address", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { + internalType: "uint256", + name: "counter", + type: "uint256", + }, + ], + name: "start", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, +] as const; diff --git a/substrate/frame/revive/rpc/examples/js/contracts/Tracing.sol b/substrate/frame/revive/rpc/examples/js/contracts/Tracing.sol new file mode 100644 index 000000000000..9eb5469e010d --- /dev/null +++ b/substrate/frame/revive/rpc/examples/js/contracts/Tracing.sol @@ -0,0 +1,47 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +contract TracingCaller { + address public callee; + + constructor(address _callee) { + require(_callee != address(0), "Callee address cannot be zero"); + callee = _callee; + } + + function start(uint256 counter) external { + if (counter == 0) { + return; + } + + TracingCallee(callee).consumeGas(); + + try TracingCallee(callee).failingFunction() { + } catch { + } + + try TracingCallee(callee).consumeGas{gas: 100}() { + } catch { + } + + this.start(counter - 1); + } +} + +contract TracingCallee { + event GasConsumed(address indexed caller); + + function consumeGas() external { + // burn some gas + for (uint256 i = 0; i < 10; i++) { + uint256(keccak256(abi.encodePacked(i))); + } + + emit GasConsumed(msg.sender); + } + + function failingFunction() external pure { + require(false, "This function always fails"); + } +} + diff --git a/substrate/frame/revive/rpc/examples/js/src/build-contracts.ts b/substrate/frame/revive/rpc/examples/js/src/build-contracts.ts index 446a629b123f..8f1f8a3fdde8 100644 --- a/substrate/frame/revive/rpc/examples/js/src/build-contracts.ts +++ b/substrate/frame/revive/rpc/examples/js/src/build-contracts.ts @@ -1,4 +1,4 @@ -import { compile } from '@parity/revive' +import { compile, SolcOutput } from '@parity/revive' import { format } from 'prettier' import { parseArgs } from 'node:util' import solc from 'solc' @@ -8,7 +8,7 @@ import { basename, join } from 'path' type CompileInput = Parameters[0] const { - values: { filter }, + values: { filter, solcOnly }, } = parseArgs({ args: process.argv.slice(2), options: { @@ -16,6 +16,10 @@ const { type: 'string', short: 'f', }, + solcOnly: { + type: 'boolean', + short: 's', + }, }, }) @@ -54,32 +58,23 @@ for (const file of input) { [name]: { content: readFileSync(join(contractsDir, file), 'utf8') }, } - console.log('Compiling with revive...') - const reviveOut = await compile(input, { wasm: !!process.env.WASM }) - - for (const contracts of Object.values(reviveOut.contracts)) { - for (const [name, contract] of Object.entries(contracts)) { - console.log(`📜 Add PVM contract ${name}`) - const abi = contract.abi - const abiName = `${name}Abi` - writeFileSync(join(abiDir, `${name}.json`), JSON.stringify(abi, null, 2)) - - writeFileSync( - join(abiDir, `${name}.ts`), - await format(`export const ${abiName} = ${JSON.stringify(abi, null, 2)} as const`, { - parser: 'typescript', - }) - ) + if (!solcOnly) { + console.log('Compiling with revive...') + const reviveOut = await compile(input, { wasm: !!process.env.WASM }) - writeFileSync( - join(pvmDir, `${name}.polkavm`), - Buffer.from(contract.evm.bytecode.object, 'hex') - ) + for (const contracts of Object.values(reviveOut.contracts)) { + for (const [name, contract] of Object.entries(contracts)) { + console.log(`📜 Add PVM contract ${name}`) + writeFileSync( + join(pvmDir, `${name}.polkavm`), + Buffer.from(contract.evm.bytecode.object, 'hex') + ) + } } } console.log(`Compile with solc ${file}`) - const evmOut = JSON.parse(evmCompile(input)) as typeof reviveOut + const evmOut = JSON.parse(evmCompile(input)) as SolcOutput for (const contracts of Object.values(evmOut.contracts)) { for (const [name, contract] of Object.entries(contracts)) { @@ -88,6 +83,17 @@ for (const file of input) { join(evmDir, `${name}.bin`), Buffer.from(contract.evm.bytecode.object, 'hex') ) + + const abi = contract.abi + const abiName = `${name}Abi` + writeFileSync(join(abiDir, `${name}.json`), JSON.stringify(abi, null, 2)) + + writeFileSync( + join(abiDir, `${name}.ts`), + await format(`export const ${abiName} = ${JSON.stringify(abi, null, 2)} as const`, { + parser: 'typescript', + }) + ) } } } diff --git a/substrate/frame/revive/rpc/examples/js/src/geth-diff.test.ts b/substrate/frame/revive/rpc/examples/js/src/geth-diff.test.ts index 815ae7dde0b6..84d2b7e69359 100644 --- a/substrate/frame/revive/rpc/examples/js/src/geth-diff.test.ts +++ b/substrate/frame/revive/rpc/examples/js/src/geth-diff.test.ts @@ -6,68 +6,76 @@ import { waitForHealth, polkadotSdkPath, } from './util.ts' -import { afterAll, afterEach, beforeAll, describe, expect, test } from 'bun:test' +import { afterAll, afterEach, describe, expect, test } from 'bun:test' import { encodeFunctionData, Hex, parseEther } from 'viem' import { ErrorTesterAbi } from '../abi/ErrorTester' -import { FlipperCallerAbi } from '../abi/FlipperCaller' -import { FlipperAbi } from '../abi/Flipper' +import { TracingCallerAbi } from '../abi/TracingCaller.ts' +import { TracingCalleeAbi } from '../abi/TracingCallee.ts' import { Subprocess, spawn } from 'bun' const procs: Subprocess[] = [] -beforeAll(async () => { - if (!process.env.USE_LIVE_SERVERS) { - procs.push( - // Run geth on port 8546 - await (async () => { - killProcessOnPort(8546) - const proc = spawn( - 'geth --http --http.api web3,eth,debug,personal,net --http.port 8546 --dev --verbosity 0'.split( - ' ' - ), - { stdout: Bun.file('/tmp/geth.out.log'), stderr: Bun.file('/tmp/geth.err.log') } - ) +if (process.env.START_GETH) { + procs.push( + // Run geth on port 8546 + await (async () => { + killProcessOnPort(8546) + const proc = spawn( + 'geth --http --http.api web3,eth,debug,personal,net --http.port 8546 --dev --verbosity 0'.split( + ' ' + ), + { stdout: Bun.file('/tmp/geth.out.log'), stderr: Bun.file('/tmp/geth.err.log') } + ) - await waitForHealth('http://localhost:8546').catch() - return proc - })(), - //Run the substate node - (() => { - killProcessOnPort(9944) - return spawn( - [ - './target/debug/substrate-node', - '--dev', - '-l=error,evm=debug,sc_rpc_server=info,runtime::revive=debug', - ], - { - stdout: Bun.file('/tmp/kitchensink.out.log'), - stderr: Bun.file('/tmp/kitchensink.err.log'), - cwd: polkadotSdkPath, - } - ) - })(), - // Run eth-rpc on 8545 - await (async () => { - killProcessOnPort(8545) - const proc = spawn( - [ - './target/debug/eth-rpc', - '--dev', - '--node-rpc-url=ws://localhost:9944', - '-l=rpc-metrics=debug,eth-rpc=debug', - ], - { - stdout: Bun.file('/tmp/eth-rpc.out.log'), - stderr: Bun.file('/tmp/eth-rpc.err.log'), - cwd: polkadotSdkPath, - } - ) - await waitForHealth('http://localhost:8545').catch() - return proc - })() - ) - } -}) + await waitForHealth('http://localhost:8546').catch() + return proc + })() + ) +} + +if (process.env.START_SUBSTRATE_NODE) { + procs.push( + //Run the substate node + (() => { + killProcessOnPort(9944) + return spawn( + [ + './target/debug/substrate-node', + '--dev', + '-l=error,evm=debug,sc_rpc_server=info,runtime::revive=debug', + ], + { + stdout: Bun.file('/tmp/kitchensink.out.log'), + stderr: Bun.file('/tmp/kitchensink.err.log'), + cwd: polkadotSdkPath, + } + ) + })() + ) +} + +if (process.env.START_ETH_RPC) { + // Run eth-rpc on 8545 + procs.push( + await (async () => { + killProcessOnPort(8545) + const proc = spawn( + [ + './target/debug/eth-rpc', + '--dev', + '--node-rpc-url=ws://localhost:9944', + '-l=rpc-metrics=debug,eth-rpc=debug', + ], + { + stdout: Bun.file('/tmp/eth-rpc.out.log'), + stderr: Bun.file('/tmp/eth-rpc.err.log'), + cwd: polkadotSdkPath, + } + ) + await waitForHealth('http://localhost:8545').catch() + return proc + })() + ) +} afterEach(() => { jsonRpcErrors.length = 0 @@ -77,54 +85,36 @@ afterAll(async () => { procs.forEach((proc) => proc.kill()) }) -const envs = await Promise.all([createEnv('geth'), createEnv('kitchensink')]) +const envs = await Promise.all([ + ...(process.env.USE_GETH ? [createEnv('geth')] : []), + ...(process.env.USE_KITCHENSINK ? [createEnv('kitchensink')] : []), +]) for (const env of envs) { describe(env.serverWallet.chain.name, () => { - let errorTesterAddr: Hex = '0x' - let flipperAddr: Hex = '0x' - let flipperCallerAddr: Hex = '0x' - beforeAll(async () => { - { + const getErrorTesterAddr = (() => { + let contractAddress: Hex = '0x' + return async () => { + if (contractAddress !== '0x') { + return contractAddress + } const hash = await env.serverWallet.deployContract({ abi: ErrorTesterAbi, - bytecode: getByteCode('errorTester', env.evm), - }) - const deployReceipt = await env.serverWallet.waitForTransactionReceipt({ hash }) - if (!deployReceipt.contractAddress) - throw new Error('Contract address should be set') - errorTesterAddr = deployReceipt.contractAddress - } - - { - const hash = await env.serverWallet.deployContract({ - abi: FlipperAbi, - bytecode: getByteCode('flipper', env.evm), + bytecode: getByteCode('ErrorTester', env.evm), }) const deployReceipt = await env.serverWallet.waitForTransactionReceipt({ hash }) if (!deployReceipt.contractAddress) throw new Error('Contract address should be set') - flipperAddr = deployReceipt.contractAddress + contractAddress = deployReceipt.contractAddress + return contractAddress } - - { - const hash = await env.serverWallet.deployContract({ - abi: FlipperCallerAbi, - args: [flipperAddr], - bytecode: getByteCode('flipperCaller', env.evm), - }) - const deployReceipt = await env.serverWallet.waitForTransactionReceipt({ hash }) - if (!deployReceipt.contractAddress) - throw new Error('Contract address should be set') - flipperCallerAddr = deployReceipt.contractAddress - } - }) + })() test('triggerAssertError', async () => { expect.assertions(3) try { await env.accountWallet.readContract({ - address: errorTesterAddr, + address: await getErrorTesterAddr(), abi: ErrorTesterAbi, functionName: 'triggerAssertError', }) @@ -142,7 +132,7 @@ for (const env of envs) { expect.assertions(3) try { await env.accountWallet.readContract({ - address: errorTesterAddr, + address: await getErrorTesterAddr(), abi: ErrorTesterAbi, functionName: 'triggerRevertError', }) @@ -160,7 +150,7 @@ for (const env of envs) { expect.assertions(3) try { await env.accountWallet.readContract({ - address: errorTesterAddr, + address: await getErrorTesterAddr(), abi: ErrorTesterAbi, functionName: 'triggerDivisionByZero', }) @@ -180,7 +170,7 @@ for (const env of envs) { expect.assertions(3) try { await env.accountWallet.readContract({ - address: errorTesterAddr, + address: await getErrorTesterAddr(), abi: ErrorTesterAbi, functionName: 'triggerOutOfBoundsError', }) @@ -200,7 +190,7 @@ for (const env of envs) { expect.assertions(3) try { await env.accountWallet.readContract({ - address: errorTesterAddr, + address: await getErrorTesterAddr(), abi: ErrorTesterAbi, functionName: 'triggerCustomError', }) @@ -218,7 +208,7 @@ for (const env of envs) { expect.assertions(3) try { await env.accountWallet.simulateContract({ - address: errorTesterAddr, + address: await getErrorTesterAddr(), abi: ErrorTesterAbi, functionName: 'valueMatch', value: parseEther('10'), @@ -251,7 +241,7 @@ for (const env of envs) { expect.assertions(3) try { await env.accountWallet.estimateContractGas({ - address: errorTesterAddr, + address: await getErrorTesterAddr(), abi: ErrorTesterAbi, functionName: 'valueMatch', value: parseEther('10'), @@ -269,7 +259,7 @@ for (const env of envs) { expect.assertions(3) try { await env.accountWallet.estimateContractGas({ - address: errorTesterAddr, + address: await getErrorTesterAddr(), abi: ErrorTesterAbi, functionName: 'valueMatch', value: parseEther('10'), @@ -287,7 +277,7 @@ for (const env of envs) { expect.assertions(3) try { await env.serverWallet.estimateContractGas({ - address: errorTesterAddr, + address: await getErrorTesterAddr(), abi: ErrorTesterAbi, functionName: 'valueMatch', value: parseEther('11'), @@ -319,7 +309,7 @@ for (const env of envs) { expect(balance).toBe(0n) await env.accountWallet.estimateContractGas({ - address: errorTesterAddr, + address: await getErrorTesterAddr(), abi: ErrorTesterAbi, functionName: 'setState', args: [true], @@ -348,10 +338,56 @@ for (const env of envs) { { data, from: env.accountWallet.account.address, - to: errorTesterAddr, + to: await getErrorTesterAddr(), }, ], }) }) + + test.only('tracing', async () => { + const calleeAddress = 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! + })() + + console.error('Callee address:', calleeAddress) + + const callerAddress = await (async () => { + const hash = await env.serverWallet.deployContract({ + abi: TracingCallerAbi, + args: [calleeAddress], + bytecode: getByteCode('TracingCaller', env.evm), + }) + const receipt = await env.serverWallet.waitForTransactionReceipt({ + hash, + }) + return receipt.contractAddress! + })() + + console.error('Caller address:', callerAddress) + const txHash = await (async () => { + const { request } = await env.serverWallet.simulateContract({ + address: callerAddress, + abi: TracingCallerAbi, + functionName: 'start', + args: [2n], + }) + + const hash = await env.serverWallet.writeContract(request) + await env.serverWallet.waitForTransactionReceipt({ hash }) + + return hash + })() + + console.error('Transaction hash:', txHash) + const res = await env.debugClient.traceTransaction(txHash) + console.error(res) + }) }) } diff --git a/substrate/frame/revive/rpc/examples/js/src/lib.ts b/substrate/frame/revive/rpc/examples/js/src/lib.ts index e1f0e780d95b..e3c794e05121 100644 --- a/substrate/frame/revive/rpc/examples/js/src/lib.ts +++ b/substrate/frame/revive/rpc/examples/js/src/lib.ts @@ -126,3 +126,17 @@ export function assert(condition: any, message: string): asserts condition { throw new Error(message) } } + +const debugClient = createClient({ + chain: mainnet, + transport: http(), +}).extend((client) => ({ + // ... + async traceCall(args: CallParameters) { + return client.request({ + method: 'debug_traceCall', + params: [formatTransactionRequest(args), 'latest', {}], + }) + }, + // ... +})) diff --git a/substrate/frame/revive/rpc/examples/js/src/util.ts b/substrate/frame/revive/rpc/examples/js/src/util.ts index bdc64eea1ef5..11c618e754c6 100644 --- a/substrate/frame/revive/rpc/examples/js/src/util.ts +++ b/substrate/frame/revive/rpc/examples/js/src/util.ts @@ -1,7 +1,7 @@ import { spawnSync } from 'bun' import { resolve } from 'path' import { readFileSync } from 'fs' -import { createWalletClient, defineChain, Hex, http, publicActions } from 'viem' +import { createClient, createWalletClient, defineChain, type Hex, http, publicActions } from 'viem' import { privateKeyToAccount, nonceManager } from 'viem/accounts' export function getByteCode(name: string, evm: boolean = false): Hex { @@ -85,9 +85,24 @@ export async function createEnv(name: 'geth' | 'kitchensink') { chain, }).extend(publicActions) - return { serverWallet, accountWallet, evm: name == 'geth' } + const debugClient = createClient({ + chain, + transport, + }).extend((client) => ({ + async traceTransaction(txHash: Hex) { + return client.request({ + method: 'debug_traceTransaction' as any, + params: [txHash, { tracer: 'callTracer' } as any], + }) + }, + // ... + })) + + return { debugClient, serverWallet, accountWallet, evm: name == 'geth' } } +export type Env = Awaited> + export function wait(ms: number) { return new Promise((resolve) => setTimeout(resolve, ms)) } diff --git a/substrate/frame/revive/src/debug.rs b/substrate/frame/revive/src/debug.rs index e63a6cef8ebc..309c68921b71 100644 --- a/substrate/frame/revive/src/debug.rs +++ b/substrate/frame/revive/src/debug.rs @@ -49,7 +49,6 @@ pub trait Tracing: Default { value: &U256, input: &[u8], gas_meter: &GasMeter, - nested_gas_meter: &GasMeter, ); /// Called after a contract call is executed @@ -92,7 +91,6 @@ where value: &U256, input: &[u8], gas_meter: &GasMeter, - nested_gas_meter: &GasMeter, ) { match self { Tracer::CallTracer(tracer) => { @@ -105,7 +103,6 @@ where value, input, gas_meter, - nested_gas_meter, ); }, Tracer::Disabled => { @@ -148,7 +145,6 @@ where value: &U256, input: &[u8], gas_meter: &GasMeter, - nested_gas_meter: &GasMeter, ) { let call_type = if is_read_only { CallType::StaticCall @@ -164,8 +160,7 @@ where value: (*value).into(), call_type, input: input.to_vec(), - gas: nested_gas_meter.gas_left(), - gas_used: gas_meter.gas_left(), + gas: gas_meter.gas_left(), ..Default::default() }); @@ -177,7 +172,7 @@ where let current_index = self.current_stack.pop().unwrap(); let trace = &mut self.traces[current_index]; trace.output = output.data.clone(); - trace.gas_used = trace.gas_used.saturating_sub(gas_meter.gas_left()); + trace.gas_used = gas_meter.gas_consumed(); // move the current trace into its parent if let Some(parent_index) = self.current_stack.last() { diff --git a/substrate/frame/revive/src/evm/api/debug_rpc_types.rs b/substrate/frame/revive/src/evm/api/debug_rpc_types.rs index 3bcad5213062..e2cbe76d19fa 100644 --- a/substrate/frame/revive/src/evm/api/debug_rpc_types.rs +++ b/substrate/frame/revive/src/evm/api/debug_rpc_types.rs @@ -26,6 +26,7 @@ pub enum CallType { /// The traces of a transaction. #[derive(TypeInfo, Encode, Decode, Serialize, Deserialize, Clone, Debug, Eq, PartialEq)] +#[serde(untagged)] pub enum Traces { CallTraces(Vec>), } diff --git a/substrate/frame/revive/src/exec.rs b/substrate/frame/revive/src/exec.rs index 08e0ed6b187c..675e973876cc 100644 --- a/substrate/frame/revive/src/exec.rs +++ b/substrate/frame/revive/src/exec.rs @@ -1100,7 +1100,6 @@ where read_only, &value_transferred, &input_data, - &self.gas_meter, &frame.nested_gas, ); @@ -1108,7 +1107,11 @@ where .unwrap_or_else(|| executable.execute(self, entry_point, input_data)) .map_err(|e| ExecError { error: e.error, origin: ErrorOrigin::Callee })?; - >::exit_child_span(self.tracer, &output, &self.gas_meter); + >::exit_child_span( + self.tracer, + &output, + &top_frame_mut!(self).nested_gas, + ); // Avoid useless work that would be reverted anyways. if output.did_revert() { diff --git a/substrate/frame/revive/src/tests.rs b/substrate/frame/revive/src/tests.rs index 0833ffb5309d..47b583ef3258 100644 --- a/substrate/frame/revive/src/tests.rs +++ b/substrate/frame/revive/src/tests.rs @@ -4832,7 +4832,7 @@ fn tracing_works() { use crate::evm::*; use CallType::*; let (code, _code_hash) = compile_module("tracing").unwrap(); - let (wasm_callee, _) = compile_module("dummy").unwrap(); + let (wasm_callee, _) = compile_module("dummy_2").unwrap(); ExtBuilder::default().existential_deposit(200).build().execute_with(|| { let _ = ::Currency::set_balance(&ALICE, 1_000_000); @@ -4843,9 +4843,12 @@ fn tracing_works() { builder::bare_instantiate(Code::Upload(code)).build_and_unwrap_contract(); let result = builder::bare_call(addr).data((3u32, addr_callee).encode()).build(); + println!("{}", serde_json::to_string_pretty(&result.traces).unwrap()); + + let traces = result.traces.map(|_| Weight::default()); assert_eq!( - result.traces.map(|_| Weight::default()), + traces, Traces::CallTraces(vec![CallTrace { from: ALICE_ADDR, to: addr, diff --git a/substrate/frame/revive/src/wasm/mod.rs b/substrate/frame/revive/src/wasm/mod.rs index b24de61314f9..cfe67a1fd6e7 100644 --- a/substrate/frame/revive/src/wasm/mod.rs +++ b/substrate/frame/revive/src/wasm/mod.rs @@ -287,7 +287,7 @@ impl WasmBlob { } let engine = polkavm::Engine::new(&config).expect( "on-chain (no_std) use of interpreter is hard coded. - interpreter is available on all plattforms; qed", + interpreter is available on all platforms; qed", ); let mut module_config = polkavm::ModuleConfig::new();