refactor: move brother_node development artifact to dev/test-nodes subdirectory

Development Artifact Cleanup:
 BROTHER_NODE REORGANIZATION: Moved development test node to appropriate location
- dev/test-nodes/brother_node/: Moved from root directory for better organization
- Contains development configuration, test logs, and test chain data
- No impact on production systems - purely development/testing artifact

 DEVELOPMENT ARTIFACTS IDENTIFIED:
- Chain ID: aitbc-brother-chain (test/development chain)
- Ports: 8010 (P2P) and 8011 (RPC) - different from production
- Environment: .env file with test configuration
- Logs: rpc.log and node.log from development testing session (March 15, 2026)

 ROOT DIRECTORY CLEANUP: Removed development clutter from production directory
- brother_node/ moved to dev/test-nodes/brother_node/
- Root directory now contains only production-ready components
- Development artifacts properly organized in dev/ subdirectory

DIRECTORY STRUCTURE IMPROVEMENT:
📁 dev/test-nodes/: Development and testing node configurations
🏗️ Root Directory: Clean production structure with only essential components
🧪 Development Isolation: Test environments separated from production

BENEFITS:
 Clean Production Directory: No development artifacts in root
 Better Organization: Development nodes grouped in dev/ subdirectory
 Clear Separation: Production vs development environments clearly distinguished
 Maintainability: Easier to identify and manage development components

RESULT: Successfully moved brother_node development artifact to dev/test-nodes/ subdirectory, cleaning up the root directory while preserving development testing environment for future use.
This commit is contained in:
2026-03-30 17:09:06 +02:00
parent bf730dcb4a
commit 816e258d4c
11734 changed files with 2001707 additions and 0 deletions

View File

@@ -0,0 +1,3 @@
# `@nomicfoundation/edr-darwin-arm64`
This is the **aarch64-apple-darwin** binary for `@nomicfoundation/edr`

Binary file not shown.

View File

@@ -0,0 +1,16 @@
{
"name": "@nomicfoundation/edr-darwin-arm64",
"repository": {
"url": "https://github.com/NomicFoundation/edr.git",
"type": "git"
},
"version": "0.12.0-next.24",
"main": "edr.darwin-arm64.node",
"files": [
"edr.darwin-arm64.node"
],
"license": "MIT",
"engines": {
"node": ">= 20"
}
}

View File

@@ -0,0 +1,3 @@
# `@nomicfoundation/edr-darwin-x64`
This is the **x86_64-apple-darwin** binary for `@nomicfoundation/edr`

Binary file not shown.

View File

@@ -0,0 +1,16 @@
{
"name": "@nomicfoundation/edr-darwin-x64",
"repository": {
"url": "https://github.com/NomicFoundation/edr.git",
"type": "git"
},
"version": "0.12.0-next.24",
"main": "edr.darwin-x64.node",
"files": [
"edr.darwin-x64.node"
],
"license": "MIT",
"engines": {
"node": ">= 20"
}
}

View File

@@ -0,0 +1,3 @@
# `@nomicfoundation/edr-linux-arm64-gnu`
This is the **aarch64-unknown-linux-gnu** binary for `@nomicfoundation/edr`

Binary file not shown.

View File

@@ -0,0 +1,16 @@
{
"name": "@nomicfoundation/edr-linux-arm64-gnu",
"repository": {
"url": "https://github.com/NomicFoundation/edr.git",
"type": "git"
},
"version": "0.12.0-next.24",
"main": "edr.linux-arm64-gnu.node",
"files": [
"edr.linux-arm64-gnu.node"
],
"license": "MIT",
"engines": {
"node": ">= 20"
}
}

View File

@@ -0,0 +1,3 @@
# `@nomicfoundation/edr-linux-arm64-musl`
This is the **aarch64-unknown-linux-musl** binary for `@nomicfoundation/edr`

Binary file not shown.

View File

@@ -0,0 +1,16 @@
{
"name": "@nomicfoundation/edr-linux-arm64-musl",
"repository": {
"url": "https://github.com/NomicFoundation/edr.git",
"type": "git"
},
"version": "0.12.0-next.24",
"main": "edr.linux-arm64-musl.node",
"files": [
"edr.linux-arm64-musl.node"
],
"license": "MIT",
"engines": {
"node": ">= 20"
}
}

View File

@@ -0,0 +1,3 @@
# `@nomicfoundation/edr-linux-x64-gnu`
This is the **x86_64-unknown-linux-gnu** binary for `@nomicfoundation/edr`

Binary file not shown.

View File

@@ -0,0 +1,16 @@
{
"name": "@nomicfoundation/edr-linux-x64-gnu",
"repository": {
"url": "https://github.com/NomicFoundation/edr.git",
"type": "git"
},
"version": "0.12.0-next.24",
"main": "edr.linux-x64-gnu.node",
"files": [
"edr.linux-x64-gnu.node"
],
"license": "MIT",
"engines": {
"node": ">= 20"
}
}

View File

@@ -0,0 +1,3 @@
# `@nomicfoundation/edr-linux-x64-musl`
This is the **x86_64-unknown-linux-musl** binary for `@nomicfoundation/edr`

Binary file not shown.

View File

@@ -0,0 +1,16 @@
{
"name": "@nomicfoundation/edr-linux-x64-musl",
"repository": {
"url": "https://github.com/NomicFoundation/edr.git",
"type": "git"
},
"version": "0.12.0-next.24",
"main": "edr.linux-x64-musl.node",
"files": [
"edr.linux-x64-musl.node"
],
"license": "MIT",
"engines": {
"node": ">= 20"
}
}

View File

@@ -0,0 +1,3 @@
# `@nomicfoundation/edr-win32-x64-msvc`
This is the **x86_64-pc-windows-msvc** binary for `@nomicfoundation/edr`

Binary file not shown.

View File

@@ -0,0 +1,16 @@
{
"name": "@nomicfoundation/edr-win32-x64-msvc",
"repository": {
"url": "https://github.com/NomicFoundation/edr.git",
"type": "git"
},
"version": "0.12.0-next.24",
"main": "edr.win32-x64-msvc.node",
"files": [
"edr.win32-x64-msvc.node"
],
"license": "MIT",
"engines": {
"node": ">= 20"
}
}

25
dev/env/node_modules/@nomicfoundation/edr/LICENSE generated vendored Executable file
View File

@@ -0,0 +1,25 @@
MIT License
Copyright (c) 2021 Nomic Foundation. The solidity tests feature of this work
is based on and modifies Foundry, which is licensed under the MIT license,
copyright (c) 2021 Georgios Konstantopoulos. Documentation for the solidity
tests feature is based on Foundry Book which is copyright (c) 2021 Oliver
Nordbjerg.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

5
dev/env/node_modules/@nomicfoundation/edr/README.md generated vendored Executable file
View File

@@ -0,0 +1,5 @@
# EDR - Ethereum Development Runtime
**EDR**, or **Ethereum Development Runtime** in full, is a library for creating developer tooling on top of the Ethereum Virtual Machine (EVM), such as an EVM debugger or state inspector. EDR provides a performant API, written in Rust, with bindings for the Node API (TypeScript).
At the moment, EDR is only meant to be consumed from [Hardhat](https://hardhat.org/), but we plan to have a stable API that can be used from any other tooling. If you are interested in this, please reach out.

1691
dev/env/node_modules/@nomicfoundation/edr/index.d.ts generated vendored Executable file

File diff suppressed because it is too large Load Diff

403
dev/env/node_modules/@nomicfoundation/edr/index.js generated vendored Executable file
View File

@@ -0,0 +1,403 @@
/* tslint:disable */
/* eslint-disable */
/* prettier-ignore */
/* auto-generated by NAPI-RS */
const { existsSync, readFileSync } = require('fs')
const { join } = require('path')
const { platform, arch } = process
let nativeBinding = null
let localFileExisted = false
let loadError = null
function isMusl() {
// For Node 10
if (!process.report || typeof process.report.getReport !== 'function') {
try {
const lddPath = require('child_process').execSync('which ldd').toString().trim()
return readFileSync(lddPath, 'utf8').includes('musl')
} catch (e) {
return true
}
} else {
const { glibcVersionRuntime } = process.report.getReport().header
return !glibcVersionRuntime
}
}
switch (platform) {
case 'android':
switch (arch) {
case 'arm64':
localFileExisted = existsSync(join(__dirname, 'edr.android-arm64.node'))
try {
if (localFileExisted) {
nativeBinding = require('./edr.android-arm64.node')
} else {
nativeBinding = require('@nomicfoundation/edr-android-arm64')
}
} catch (e) {
loadError = e
}
break
case 'arm':
localFileExisted = existsSync(join(__dirname, 'edr.android-arm-eabi.node'))
try {
if (localFileExisted) {
nativeBinding = require('./edr.android-arm-eabi.node')
} else {
nativeBinding = require('@nomicfoundation/edr-android-arm-eabi')
}
} catch (e) {
loadError = e
}
break
default:
throw new Error(`Unsupported architecture on Android ${arch}`)
}
break
case 'win32':
switch (arch) {
case 'x64':
localFileExisted = existsSync(
join(__dirname, 'edr.win32-x64-msvc.node')
)
try {
if (localFileExisted) {
nativeBinding = require('./edr.win32-x64-msvc.node')
} else {
nativeBinding = require('@nomicfoundation/edr-win32-x64-msvc')
}
} catch (e) {
loadError = e
}
break
case 'ia32':
localFileExisted = existsSync(
join(__dirname, 'edr.win32-ia32-msvc.node')
)
try {
if (localFileExisted) {
nativeBinding = require('./edr.win32-ia32-msvc.node')
} else {
nativeBinding = require('@nomicfoundation/edr-win32-ia32-msvc')
}
} catch (e) {
loadError = e
}
break
case 'arm64':
localFileExisted = existsSync(
join(__dirname, 'edr.win32-arm64-msvc.node')
)
try {
if (localFileExisted) {
nativeBinding = require('./edr.win32-arm64-msvc.node')
} else {
nativeBinding = require('@nomicfoundation/edr-win32-arm64-msvc')
}
} catch (e) {
loadError = e
}
break
default:
throw new Error(`Unsupported architecture on Windows: ${arch}`)
}
break
case 'darwin':
localFileExisted = existsSync(join(__dirname, 'edr.darwin-universal.node'))
try {
if (localFileExisted) {
nativeBinding = require('./edr.darwin-universal.node')
} else {
nativeBinding = require('@nomicfoundation/edr-darwin-universal')
}
break
} catch {}
switch (arch) {
case 'x64':
localFileExisted = existsSync(join(__dirname, 'edr.darwin-x64.node'))
try {
if (localFileExisted) {
nativeBinding = require('./edr.darwin-x64.node')
} else {
nativeBinding = require('@nomicfoundation/edr-darwin-x64')
}
} catch (e) {
loadError = e
}
break
case 'arm64':
localFileExisted = existsSync(
join(__dirname, 'edr.darwin-arm64.node')
)
try {
if (localFileExisted) {
nativeBinding = require('./edr.darwin-arm64.node')
} else {
nativeBinding = require('@nomicfoundation/edr-darwin-arm64')
}
} catch (e) {
loadError = e
}
break
default:
throw new Error(`Unsupported architecture on macOS: ${arch}`)
}
break
case 'freebsd':
if (arch !== 'x64') {
throw new Error(`Unsupported architecture on FreeBSD: ${arch}`)
}
localFileExisted = existsSync(join(__dirname, 'edr.freebsd-x64.node'))
try {
if (localFileExisted) {
nativeBinding = require('./edr.freebsd-x64.node')
} else {
nativeBinding = require('@nomicfoundation/edr-freebsd-x64')
}
} catch (e) {
loadError = e
}
break
case 'linux':
switch (arch) {
case 'x64':
if (isMusl()) {
localFileExisted = existsSync(
join(__dirname, 'edr.linux-x64-musl.node')
)
try {
if (localFileExisted) {
nativeBinding = require('./edr.linux-x64-musl.node')
} else {
nativeBinding = require('@nomicfoundation/edr-linux-x64-musl')
}
} catch (e) {
loadError = e
}
} else {
localFileExisted = existsSync(
join(__dirname, 'edr.linux-x64-gnu.node')
)
try {
if (localFileExisted) {
nativeBinding = require('./edr.linux-x64-gnu.node')
} else {
nativeBinding = require('@nomicfoundation/edr-linux-x64-gnu')
}
} catch (e) {
loadError = e
}
}
break
case 'arm64':
if (isMusl()) {
localFileExisted = existsSync(
join(__dirname, 'edr.linux-arm64-musl.node')
)
try {
if (localFileExisted) {
nativeBinding = require('./edr.linux-arm64-musl.node')
} else {
nativeBinding = require('@nomicfoundation/edr-linux-arm64-musl')
}
} catch (e) {
loadError = e
}
} else {
localFileExisted = existsSync(
join(__dirname, 'edr.linux-arm64-gnu.node')
)
try {
if (localFileExisted) {
nativeBinding = require('./edr.linux-arm64-gnu.node')
} else {
nativeBinding = require('@nomicfoundation/edr-linux-arm64-gnu')
}
} catch (e) {
loadError = e
}
}
break
case 'arm':
if (isMusl()) {
localFileExisted = existsSync(
join(__dirname, 'edr.linux-arm-musleabihf.node')
)
try {
if (localFileExisted) {
nativeBinding = require('./edr.linux-arm-musleabihf.node')
} else {
nativeBinding = require('@nomicfoundation/edr-linux-arm-musleabihf')
}
} catch (e) {
loadError = e
}
} else {
localFileExisted = existsSync(
join(__dirname, 'edr.linux-arm-gnueabihf.node')
)
try {
if (localFileExisted) {
nativeBinding = require('./edr.linux-arm-gnueabihf.node')
} else {
nativeBinding = require('@nomicfoundation/edr-linux-arm-gnueabihf')
}
} catch (e) {
loadError = e
}
}
break
case 'riscv64':
if (isMusl()) {
localFileExisted = existsSync(
join(__dirname, 'edr.linux-riscv64-musl.node')
)
try {
if (localFileExisted) {
nativeBinding = require('./edr.linux-riscv64-musl.node')
} else {
nativeBinding = require('@nomicfoundation/edr-linux-riscv64-musl')
}
} catch (e) {
loadError = e
}
} else {
localFileExisted = existsSync(
join(__dirname, 'edr.linux-riscv64-gnu.node')
)
try {
if (localFileExisted) {
nativeBinding = require('./edr.linux-riscv64-gnu.node')
} else {
nativeBinding = require('@nomicfoundation/edr-linux-riscv64-gnu')
}
} catch (e) {
loadError = e
}
}
break
case 's390x':
localFileExisted = existsSync(
join(__dirname, 'edr.linux-s390x-gnu.node')
)
try {
if (localFileExisted) {
nativeBinding = require('./edr.linux-s390x-gnu.node')
} else {
nativeBinding = require('@nomicfoundation/edr-linux-s390x-gnu')
}
} catch (e) {
loadError = e
}
break
default:
throw new Error(`Unsupported architecture on Linux: ${arch}`)
}
break
default:
throw new Error(`Unsupported OS: ${platform}, architecture: ${arch}`)
}
if (!nativeBinding) {
if (loadError) {
throw loadError
}
throw new Error(`Failed to load native binding`)
}
const { GENERIC_CHAIN_TYPE, genericChainProviderFactory, L1_CHAIN_TYPE, l1GenesisState, l1ProviderFactory, SpecId, l1HardforkFromString, l1HardforkToString, l1HardforkLatest, FRONTIER, FRONTIER_THAWING, HOMESTEAD, DAO_FORK, TANGERINE, SPURIOUS_DRAGON, BYZANTIUM, CONSTANTINOPLE, PETERSBURG, ISTANBUL, MUIR_GLACIER, BERLIN, LONDON, ARROW_GLACIER, GRAY_GLACIER, MERGE, SHANGHAI, CANCUN, PRAGUE, OSAKA, OpHardfork, opHardforkFromString, opHardforkToString, opLatestHardfork, OP_CHAIN_TYPE, opGenesisState, opProviderFactory, BEDROCK, REGOLITH, CANYON, ECOTONE, FJORD, GRANITE, HOLOCENE, ISTHMUS, MineOrdering, EdrContext, ContractDecoder, GasReportExecutionStatus, addStatementCoverageInstrumentation, latestSupportedSolidityVersion, Precompile, precompileP256Verify, ProviderFactory, Response, Provider, SuccessReason, ExceptionalHalt, CheatcodeErrorCode, CachedChains, CachedEndpoints, FsAccessPermission, CollectStackTraces, IncludeTraces, SolidityTestRunnerFactory, l1SolidityTestRunnerFactory, opSolidityTestRunnerFactory, SuiteResult, TestResult, TestStatus, CallKind, LogKind, linkHexStringBytecode, printStackTrace, Exit, ExitCode, BytecodeWrapper, ContractFunctionType, ReturnData, StackTraceEntryType, stackTraceEntryTypeToString, FALLBACK_FUNCTION_NAME, RECEIVE_FUNCTION_NAME, CONSTRUCTOR_FUNCTION_NAME, UNRECOGNIZED_FUNCTION_NAME, UNKNOWN_FUNCTION_NAME, PRECOMPILE_FUNCTION_NAME, UNRECOGNIZED_CONTRACT_NAME, RawTrace, getLatestSupportedSolcVersion } = nativeBinding
module.exports.GENERIC_CHAIN_TYPE = GENERIC_CHAIN_TYPE
module.exports.genericChainProviderFactory = genericChainProviderFactory
module.exports.L1_CHAIN_TYPE = L1_CHAIN_TYPE
module.exports.l1GenesisState = l1GenesisState
module.exports.l1ProviderFactory = l1ProviderFactory
module.exports.SpecId = SpecId
module.exports.l1HardforkFromString = l1HardforkFromString
module.exports.l1HardforkToString = l1HardforkToString
module.exports.l1HardforkLatest = l1HardforkLatest
module.exports.FRONTIER = FRONTIER
module.exports.FRONTIER_THAWING = FRONTIER_THAWING
module.exports.HOMESTEAD = HOMESTEAD
module.exports.DAO_FORK = DAO_FORK
module.exports.TANGERINE = TANGERINE
module.exports.SPURIOUS_DRAGON = SPURIOUS_DRAGON
module.exports.BYZANTIUM = BYZANTIUM
module.exports.CONSTANTINOPLE = CONSTANTINOPLE
module.exports.PETERSBURG = PETERSBURG
module.exports.ISTANBUL = ISTANBUL
module.exports.MUIR_GLACIER = MUIR_GLACIER
module.exports.BERLIN = BERLIN
module.exports.LONDON = LONDON
module.exports.ARROW_GLACIER = ARROW_GLACIER
module.exports.GRAY_GLACIER = GRAY_GLACIER
module.exports.MERGE = MERGE
module.exports.SHANGHAI = SHANGHAI
module.exports.CANCUN = CANCUN
module.exports.PRAGUE = PRAGUE
module.exports.OSAKA = OSAKA
module.exports.OpHardfork = OpHardfork
module.exports.opHardforkFromString = opHardforkFromString
module.exports.opHardforkToString = opHardforkToString
module.exports.opLatestHardfork = opLatestHardfork
module.exports.OP_CHAIN_TYPE = OP_CHAIN_TYPE
module.exports.opGenesisState = opGenesisState
module.exports.opProviderFactory = opProviderFactory
module.exports.BEDROCK = BEDROCK
module.exports.REGOLITH = REGOLITH
module.exports.CANYON = CANYON
module.exports.ECOTONE = ECOTONE
module.exports.FJORD = FJORD
module.exports.GRANITE = GRANITE
module.exports.HOLOCENE = HOLOCENE
module.exports.ISTHMUS = ISTHMUS
module.exports.MineOrdering = MineOrdering
module.exports.EdrContext = EdrContext
module.exports.ContractDecoder = ContractDecoder
module.exports.GasReportExecutionStatus = GasReportExecutionStatus
module.exports.addStatementCoverageInstrumentation = addStatementCoverageInstrumentation
module.exports.latestSupportedSolidityVersion = latestSupportedSolidityVersion
module.exports.Precompile = Precompile
module.exports.precompileP256Verify = precompileP256Verify
module.exports.ProviderFactory = ProviderFactory
module.exports.Response = Response
module.exports.Provider = Provider
module.exports.SuccessReason = SuccessReason
module.exports.ExceptionalHalt = ExceptionalHalt
module.exports.CheatcodeErrorCode = CheatcodeErrorCode
module.exports.CachedChains = CachedChains
module.exports.CachedEndpoints = CachedEndpoints
module.exports.FsAccessPermission = FsAccessPermission
module.exports.CollectStackTraces = CollectStackTraces
module.exports.IncludeTraces = IncludeTraces
module.exports.SolidityTestRunnerFactory = SolidityTestRunnerFactory
module.exports.l1SolidityTestRunnerFactory = l1SolidityTestRunnerFactory
module.exports.opSolidityTestRunnerFactory = opSolidityTestRunnerFactory
module.exports.SuiteResult = SuiteResult
module.exports.TestResult = TestResult
module.exports.TestStatus = TestStatus
module.exports.CallKind = CallKind
module.exports.LogKind = LogKind
module.exports.linkHexStringBytecode = linkHexStringBytecode
module.exports.printStackTrace = printStackTrace
module.exports.Exit = Exit
module.exports.ExitCode = ExitCode
module.exports.BytecodeWrapper = BytecodeWrapper
module.exports.ContractFunctionType = ContractFunctionType
module.exports.ReturnData = ReturnData
module.exports.StackTraceEntryType = StackTraceEntryType
module.exports.stackTraceEntryTypeToString = stackTraceEntryTypeToString
module.exports.FALLBACK_FUNCTION_NAME = FALLBACK_FUNCTION_NAME
module.exports.RECEIVE_FUNCTION_NAME = RECEIVE_FUNCTION_NAME
module.exports.CONSTRUCTOR_FUNCTION_NAME = CONSTRUCTOR_FUNCTION_NAME
module.exports.UNRECOGNIZED_FUNCTION_NAME = UNRECOGNIZED_FUNCTION_NAME
module.exports.UNKNOWN_FUNCTION_NAME = UNKNOWN_FUNCTION_NAME
module.exports.PRECOMPILE_FUNCTION_NAME = PRECOMPILE_FUNCTION_NAME
module.exports.UNRECOGNIZED_CONTRACT_NAME = UNRECOGNIZED_CONTRACT_NAME
module.exports.RawTrace = RawTrace
module.exports.getLatestSupportedSolcVersion = getLatestSupportedSolcVersion

89
dev/env/node_modules/@nomicfoundation/edr/package.json generated vendored Executable file
View File

@@ -0,0 +1,89 @@
{
"name": "@nomicfoundation/edr",
"version": "0.12.0-next.24",
"devDependencies": {
"@napi-rs/cli": "^2.18.4",
"@nomicfoundation/ethereumjs-util": "^9.0.4",
"@tsconfig/node20": "^20.1.6",
"@types/chai": "^4.2.0",
"@types/chai-as-promised": "^7.1.8",
"@types/mocha": ">=9.1.0",
"@types/node": "^20.0.0",
"@typescript-eslint/eslint-plugin": "5.61.0",
"@typescript-eslint/parser": "5.61.0",
"chai": "^4.3.6",
"chai-as-promised": "^7.1.1",
"chalk": "^2.4.2",
"eslint": "^8.44.0",
"eslint-config-prettier": "9.1.0",
"eslint-plugin-import": "2.27.5",
"eslint-plugin-mocha": "10.4.1",
"eslint-plugin-prettier": "5.2.1",
"ethers": "^6.1.0",
"json-stream-stringify": "^3.1.4",
"mocha": "^10.0.0",
"prettier": "^3.2.5",
"ts-node": "^10.8.0",
"typescript": "~5.8.2"
},
"engines": {
"node": ">= 20"
},
"exports": {
".": "./index.js",
"./solidity-tests": "./dist/src/ts/solidity_tests.js"
},
"files": [
"index.js",
"index.d.ts",
"src/"
],
"license": "MIT",
"main": "index.js",
"napi": {
"name": "edr",
"triples": {
"defaults": false,
"additional": [
"aarch64-apple-darwin",
"x86_64-apple-darwin",
"aarch64-unknown-linux-gnu",
"aarch64-unknown-linux-musl",
"x86_64-unknown-linux-gnu",
"x86_64-unknown-linux-musl",
"x86_64-pc-windows-msvc"
]
}
},
"repository": "NomicFoundation/edr.git",
"types": "index.d.ts",
"dependencies": {
"@nomicfoundation/edr-darwin-arm64": "0.12.0-next.24",
"@nomicfoundation/edr-darwin-x64": "0.12.0-next.24",
"@nomicfoundation/edr-linux-arm64-gnu": "0.12.0-next.24",
"@nomicfoundation/edr-linux-arm64-musl": "0.12.0-next.24",
"@nomicfoundation/edr-linux-x64-gnu": "0.12.0-next.24",
"@nomicfoundation/edr-linux-x64-musl": "0.12.0-next.24",
"@nomicfoundation/edr-win32-x64-msvc": "0.12.0-next.24"
},
"scripts": {
"artifacts": "napi artifacts",
"build": "pnpm run build:publish",
"build:debug": "bash ../../scripts/build_edr_napi.sh --features op",
"build:dev": "bash ../../scripts/build_edr_napi.sh --release --features op,test-mock",
"build:publish": "bash ../../scripts/build_edr_napi.sh --profile napi-publish --features op",
"build:scenarios": "bash ../../scripts/build_edr_napi.sh --release --features op,scenarios",
"build:tracing": "bash ../../scripts/build_edr_napi.sh --release --features op,tracing",
"build:typingFile": "bash ../../scripts/build_edr_napi.sh --features op",
"clean": "rm -rf @nomicfoundation/edr.node",
"eslint": "eslint 'test/**/*.ts'",
"lint": "pnpm run prettier && pnpm run eslint",
"lint:fix": "pnpm run prettier --write",
"pretest": "pnpm build:dev",
"prettier": "prettier --check \"test/**.ts\"",
"test": "node --max-old-space-size=8192 node_modules/mocha/bin/_mocha --recursive \"test/**/*.ts\"",
"testNoBuild": "node --max-old-space-size=8192 node_modules/mocha/bin/_mocha --recursive \"test/**/{,!(logs|mock)}.ts\"",
"universal": "napi universal",
"version": "napi version"
}
}

124
dev/env/node_modules/@nomicfoundation/edr/src/account.rs generated vendored Executable file
View File

@@ -0,0 +1,124 @@
use derive_more::Debug;
use edr_primitives::{hex, Address, HashMap, U256};
use edr_solidity_tests::{backend::Predeploy, revm::state::AccountInfo};
use edr_state_api::EvmStorageSlot;
use napi::bindgen_prelude::{BigInt, Uint8Array};
use napi_derive::napi;
use crate::{
cast::TryCast,
serde::{
serialize_bigint_as_struct, serialize_optional_bigint_as_struct,
serialize_optional_uint8array_as_hex, serialize_uint8array_as_hex,
},
};
/// Specification of overrides for an account and its storage.
#[napi(object)]
#[derive(Clone, Debug, serde::Serialize)]
pub struct AccountOverride {
/// The account's address
#[debug("{}", hex::encode(address))]
#[serde(serialize_with = "serialize_uint8array_as_hex")]
pub address: Uint8Array,
/// If present, the overwriting balance.
#[serde(serialize_with = "serialize_optional_bigint_as_struct")]
pub balance: Option<BigInt>,
/// If present, the overwriting nonce.
#[serde(serialize_with = "serialize_optional_bigint_as_struct")]
pub nonce: Option<BigInt>,
/// If present, the overwriting code.
#[debug("{:?}", code.as_ref().map(hex::encode))]
#[serde(serialize_with = "serialize_optional_uint8array_as_hex")]
pub code: Option<Uint8Array>,
/// BEWARE: This field is not supported yet. See <https://github.com/NomicFoundation/edr/issues/911>
///
/// If present, the overwriting storage.
pub storage: Option<Vec<StorageSlot>>,
}
impl TryFrom<AccountOverride> for (Address, edr_provider::AccountOverride) {
type Error = napi::Error;
fn try_from(value: AccountOverride) -> Result<Self, Self::Error> {
let AccountOverride {
address,
balance,
nonce,
code,
storage,
} = value;
let storage = storage
.map(|storage| {
storage
.into_iter()
.map(|StorageSlot { index, value }| {
let value = value.try_cast()?;
let slot = EvmStorageSlot::new(value, 0);
let index: U256 = index.try_cast()?;
Ok((index, slot))
})
.collect::<napi::Result<_>>()
})
.transpose()?;
let account_override = edr_provider::AccountOverride {
balance: balance.map(TryCast::try_cast).transpose()?,
nonce: nonce.map(TryCast::try_cast).transpose()?,
code: code.map(TryCast::try_cast).transpose()?,
storage,
};
let address: Address = address.try_cast()?;
Ok((address, account_override))
}
}
impl TryFrom<AccountOverride> for Predeploy {
type Error = napi::Error;
fn try_from(value: AccountOverride) -> Result<Self, Self::Error> {
let (address, account_override) = value.try_into()?;
let storage = account_override.storage.unwrap_or_else(HashMap::default);
let balance = account_override.balance.unwrap_or(U256::ZERO);
let nonce = account_override.nonce.unwrap_or(0);
let code = account_override.code.ok_or_else(|| {
napi::Error::from_reason(format!("Predeploy with address '{address}' must have code"))
})?;
if code.is_empty() {
return Err(napi::Error::from_reason(
"Predeploy with address '{address}' must have non-empty code",
));
}
let code_hash = code.hash_slow();
let account_info = AccountInfo {
balance,
nonce,
code_hash,
code: Some(code),
};
Ok(Self {
address,
account_info,
storage,
})
}
}
/// A description of a storage slot's state.
#[napi(object)]
#[derive(Clone, Debug, serde::Serialize)]
pub struct StorageSlot {
/// The storage slot's index
#[serde(serialize_with = "serialize_bigint_as_struct")]
pub index: BigInt,
/// The storage slot's value
#[serde(serialize_with = "serialize_bigint_as_struct")]
pub value: BigInt,
}

28
dev/env/node_modules/@nomicfoundation/edr/src/block.rs generated vendored Executable file
View File

@@ -0,0 +1,28 @@
use napi::bindgen_prelude::BigInt;
use napi_derive::napi;
use crate::cast::TryCast;
/// Information about the blob gas used in a block.
#[napi(object)]
pub struct BlobGas {
/// The total amount of blob gas consumed by the transactions within the
/// block.
pub gas_used: BigInt,
/// The running total of blob gas consumed in excess of the target, prior to
/// the block. Blocks with above-target blob gas consumption increase this
/// value, blocks with below-target blob gas consumption decrease it
/// (bounded at 0).
pub excess_gas: BigInt,
}
impl TryFrom<BlobGas> for edr_block_header::BlobGas {
type Error = napi::Error;
fn try_from(value: BlobGas) -> Result<Self, Self::Error> {
Ok(Self {
gas_used: BigInt::try_cast(value.gas_used)?,
excess_gas: BigInt::try_cast(value.excess_gas)?,
})
}
}

View File

@@ -0,0 +1,116 @@
use std::sync::mpsc::channel;
use edr_primitives::{Address, Bytes};
use napi::{
bindgen_prelude::{Promise, Uint8Array},
threadsafe_function::{
ErrorStrategy, ThreadSafeCallContext, ThreadsafeFunction, ThreadsafeFunctionCallMode,
},
tokio::runtime,
Env, JsFunction, Status,
};
use napi_derive::napi;
use crate::cast::TryCast;
/// The result of executing a call override.
#[napi(object)]
pub struct CallOverrideResult {
pub result: Uint8Array,
pub should_revert: bool,
}
impl TryCast<Option<edr_provider::CallOverrideResult>> for Option<CallOverrideResult> {
type Error = napi::Error;
fn try_cast(self) -> Result<Option<edr_provider::CallOverrideResult>, Self::Error> {
match self {
None => Ok(None),
Some(result) => Ok(Some(edr_provider::CallOverrideResult {
output: Bytes::copy_from_slice(&result.result),
should_revert: result.should_revert,
})),
}
}
}
struct CallOverrideCall {
contract_address: Address,
data: Bytes,
}
#[derive(Clone)]
pub struct CallOverrideCallback {
call_override_callback_fn: ThreadsafeFunction<CallOverrideCall, ErrorStrategy::Fatal>,
runtime: runtime::Handle,
}
impl CallOverrideCallback {
pub fn new(
env: &Env,
call_override_callback: JsFunction,
runtime: runtime::Handle,
) -> napi::Result<Self> {
let mut call_override_callback_fn = call_override_callback.create_threadsafe_function(
0,
|ctx: ThreadSafeCallContext<CallOverrideCall>| {
let address = ctx
.env
.create_arraybuffer_with_data(ctx.value.contract_address.to_vec())?
.into_raw();
let data = ctx
.env
.create_arraybuffer_with_data(ctx.value.data.to_vec())?
.into_raw();
Ok(vec![address, data])
},
)?;
// Maintain a weak reference to the function to avoid blocking the event loop
// from exiting.
call_override_callback_fn.unref(env)?;
Ok(Self {
call_override_callback_fn,
runtime,
})
}
pub fn call_override(
&self,
contract_address: Address,
data: Bytes,
) -> Option<edr_provider::CallOverrideResult> {
let (sender, receiver) = channel();
let runtime = self.runtime.clone();
let status = self.call_override_callback_fn.call_with_return_value(
CallOverrideCall {
contract_address,
data,
},
ThreadsafeFunctionCallMode::Blocking,
move |result: Promise<Option<CallOverrideResult>>| {
runtime.spawn(async move {
let result = result.await?.try_cast();
sender.send(result).map_err(|_error| {
napi::Error::new(
Status::GenericFailure,
"Failed to send result from call_override_callback",
)
})
});
Ok(())
},
);
assert_eq!(status, Status::Ok, "Call override callback failed");
receiver
.recv()
.unwrap()
.expect("Failed call to call_override_callback")
}
}

165
dev/env/node_modules/@nomicfoundation/edr/src/cast.rs generated vendored Executable file
View File

@@ -0,0 +1,165 @@
use edr_primitives::{Address, Bytecode, Bytes, B256, B64, U256};
use napi::{
bindgen_prelude::{BigInt, Uint8Array},
Status,
};
/// An attempted conversion that consumes `self`, which may or may not be
/// expensive. It is identical to [`TryInto`], but it allows us to implement
/// the trait for external types.
pub trait TryCast<T>: Sized {
/// The type returned in the event of a conversion error.
type Error;
/// Performs the conversion.
fn try_cast(self) -> Result<T, Self::Error>;
}
impl TryCast<Address> for Uint8Array {
type Error = napi::Error;
fn try_cast(self) -> std::result::Result<Address, Self::Error> {
if self.len() != 20 {
return Err(napi::Error::new(
Status::InvalidArg,
"Uint8Array was expected to be 20 bytes.".to_string(),
));
}
Ok(Address::from_slice(&self))
}
}
impl TryCast<B64> for Uint8Array {
type Error = napi::Error;
fn try_cast(self) -> std::result::Result<B64, Self::Error> {
if self.len() != 8 {
return Err(napi::Error::new(
Status::InvalidArg,
"Uint8Array was expected to be 8 bytes.".to_string(),
));
}
Ok(B64::from_slice(&self))
}
}
impl TryCast<B256> for Uint8Array {
type Error = napi::Error;
fn try_cast(self) -> std::result::Result<B256, Self::Error> {
if self.len() != 32 {
return Err(napi::Error::new(
Status::InvalidArg,
"Uint8Array was expected to be 32 bytes.".to_string(),
));
}
Ok(B256::from_slice(&self))
}
}
impl TryCast<Bytecode> for Uint8Array {
type Error = napi::Error;
fn try_cast(self) -> std::result::Result<Bytecode, Self::Error> {
let bytes = Bytes::copy_from_slice(&self);
Bytecode::new_raw_checked(bytes).map_err(|error| {
napi::Error::new(
Status::InvalidArg,
format!("Uint8Array was not valid bytecode: {error}"),
)
})
}
}
impl TryCast<u64> for BigInt {
type Error = napi::Error;
fn try_cast(self) -> std::result::Result<u64, Self::Error> {
let (signed, value, lossless) = self.get_u64();
if signed {
return Err(napi::Error::new(
Status::InvalidArg,
"BigInt was expected to be unsigned.".to_string(),
));
}
if !lossless {
return Err(napi::Error::new(
Status::InvalidArg,
"BigInt was expected to fit within 64 bits.".to_string(),
));
}
Ok(value)
}
}
impl TryCast<u128> for BigInt {
type Error = napi::Error;
fn try_cast(self) -> std::result::Result<u128, Self::Error> {
let (signed, value, lossless) = self.get_u128();
if signed {
return Err(napi::Error::new(
Status::InvalidArg,
"BigInt was expected to be unsigned.".to_string(),
));
}
if !lossless {
return Err(napi::Error::new(
Status::InvalidArg,
"BigInt was expected to fit within 128 bits.".to_string(),
));
}
Ok(value)
}
}
impl TryCast<usize> for BigInt {
type Error = napi::Error;
fn try_cast(self) -> std::result::Result<usize, Self::Error> {
let size: u64 = BigInt::try_cast(self)?;
usize::try_from(size).map_err(|e| napi::Error::new(Status::InvalidArg, e.to_string()))
}
}
impl TryCast<U256> for BigInt {
type Error = napi::Error;
fn try_cast(mut self) -> std::result::Result<U256, Self::Error> {
let num_words = self.words.len();
match num_words.cmp(&4) {
std::cmp::Ordering::Less => self.words.append(&mut vec![0u64; 4 - num_words]),
std::cmp::Ordering::Equal => (),
std::cmp::Ordering::Greater => {
return Err(napi::Error::new(
Status::InvalidArg,
"BigInt cannot have more than 4 words.".to_owned(),
));
}
}
Ok(U256::from_limbs(self.words.try_into().unwrap()))
}
}
impl<T> TryCast<T> for T {
type Error = napi::Error;
fn try_cast(self) -> std::result::Result<T, Self::Error> {
Ok(self)
}
}
impl TryCast<Option<Bytes>> for Option<Uint8Array> {
type Error = napi::Error;
fn try_cast(self) -> Result<Option<Bytes>, Self::Error> {
Ok(self.map(|buffer| Bytes::copy_from_slice(&buffer)))
}
}

7
dev/env/node_modules/@nomicfoundation/edr/src/chains.rs generated vendored Executable file
View File

@@ -0,0 +1,7 @@
/// Types for the generic L1 Ethereum implementation.
pub mod generic;
/// Types for L1 Ethereum implementation.
pub mod l1;
/// Types for OP implementation.
#[cfg(feature = "op")]
pub mod op;

View File

@@ -0,0 +1,58 @@
use std::sync::Arc;
use edr_generic::GenericChainSpec;
use edr_napi_core::{
logger::Logger,
provider::{SyncProvider, SyncProviderFactory},
subscription::subscriber_callback_for_chain_spec,
};
use edr_provider::time::CurrentTime;
use edr_solidity::contract_decoder::ContractDecoder;
use napi::tokio::runtime;
use napi_derive::napi;
use crate::provider::ProviderFactory;
pub struct GenericChainProviderFactory;
impl SyncProviderFactory for GenericChainProviderFactory {
fn create_provider(
&self,
runtime: runtime::Handle,
provider_config: edr_napi_core::provider::Config,
logger_config: edr_napi_core::logger::Config,
subscription_callback: edr_napi_core::subscription::Callback,
contract_decoder: Arc<ContractDecoder>,
) -> napi::Result<Arc<dyn SyncProvider>> {
let logger = Logger::<GenericChainSpec, CurrentTime>::new(
logger_config,
Arc::clone(&contract_decoder),
)?;
let provider_config =
edr_provider::ProviderConfig::<edr_chain_l1::Hardfork>::try_from(provider_config)?;
let provider = edr_provider::Provider::<GenericChainSpec>::new(
runtime.clone(),
Box::new(logger),
subscriber_callback_for_chain_spec::<GenericChainSpec, CurrentTime>(
subscription_callback,
),
provider_config,
contract_decoder,
CurrentTime,
)
.map_err(|error| napi::Error::new(napi::Status::GenericFailure, error.to_string()))?;
Ok(Arc::new(provider))
}
}
#[napi]
pub const GENERIC_CHAIN_TYPE: &str = edr_generic::CHAIN_TYPE;
#[napi(catch_unwind)]
pub fn generic_chain_provider_factory() -> ProviderFactory {
let factory: Arc<dyn SyncProviderFactory> = Arc::new(GenericChainProviderFactory);
factory.into()
}

274
dev/env/node_modules/@nomicfoundation/edr/src/chains/l1.rs generated vendored Executable file
View File

@@ -0,0 +1,274 @@
use std::{str::FromStr, sync::Arc};
use edr_blockchain_fork::eips::{
eip2935::{HISTORY_STORAGE_ADDRESS, HISTORY_STORAGE_UNSUPPORTED_BYTECODE},
eip4788::{BEACON_ROOTS_ADDRESS, BEACON_ROOTS_BYTECODE},
};
use edr_chain_l1::L1ChainSpec;
use edr_napi_core::{
logger::Logger,
provider::{SyncProvider, SyncProviderFactory},
subscription::subscriber_callback_for_chain_spec,
};
use edr_provider::time::CurrentTime;
use edr_solidity::contract_decoder::ContractDecoder;
use napi::{
bindgen_prelude::{BigInt, Uint8Array},
tokio::runtime,
};
use napi_derive::napi;
use crate::{account::AccountOverride, provider::ProviderFactory};
pub struct L1ProviderFactory;
impl SyncProviderFactory for L1ProviderFactory {
fn create_provider(
&self,
runtime: runtime::Handle,
provider_config: edr_napi_core::provider::Config,
logger_config: edr_napi_core::logger::Config,
subscription_callback: edr_napi_core::subscription::Callback,
contract_decoder: Arc<ContractDecoder>,
) -> napi::Result<Arc<dyn SyncProvider>> {
let logger =
Logger::<L1ChainSpec, CurrentTime>::new(logger_config, Arc::clone(&contract_decoder))?;
let provider_config =
edr_provider::ProviderConfig::<edr_chain_l1::Hardfork>::try_from(provider_config)?;
let provider = edr_provider::Provider::<L1ChainSpec>::new(
runtime.clone(),
Box::new(logger),
subscriber_callback_for_chain_spec::<L1ChainSpec, CurrentTime>(subscription_callback),
provider_config,
contract_decoder,
CurrentTime,
)
.map_err(|error| napi::Error::new(napi::Status::GenericFailure, error.to_string()))?;
Ok(Arc::new(provider))
}
}
#[napi]
pub const L1_CHAIN_TYPE: &str = edr_chain_l1::CHAIN_TYPE;
#[napi(catch_unwind)]
pub fn l1_genesis_state(hardfork: SpecId) -> Vec<AccountOverride> {
// Use closures for lazy execution
let beacon_roots_account_constructor = || AccountOverride {
address: Uint8Array::with_data_copied(BEACON_ROOTS_ADDRESS),
balance: Some(BigInt::from(0u64)),
nonce: Some(BigInt::from(0u64)),
code: Some(Uint8Array::with_data_copied(&BEACON_ROOTS_BYTECODE)),
storage: Some(Vec::new()),
};
let history_storage_account_constructor = || AccountOverride {
address: Uint8Array::with_data_copied(HISTORY_STORAGE_ADDRESS),
balance: Some(BigInt::from(0u64)),
nonce: Some(BigInt::from(0u64)),
code: Some(Uint8Array::with_data_copied(
&HISTORY_STORAGE_UNSUPPORTED_BYTECODE,
)),
storage: Some(Vec::new()),
};
if hardfork < SpecId::Cancun {
Vec::new()
} else if hardfork < SpecId::Prague {
vec![beacon_roots_account_constructor()]
} else {
vec![
beacon_roots_account_constructor(),
history_storage_account_constructor(),
]
}
}
#[napi(catch_unwind)]
pub fn l1_provider_factory() -> ProviderFactory {
let factory: Arc<dyn SyncProviderFactory> = Arc::new(L1ProviderFactory);
factory.into()
}
/// Identifier for the Ethereum spec.
#[napi]
#[derive(PartialEq, Eq, PartialOrd, Ord)]
pub enum SpecId {
/// Frontier
Frontier = 0,
/// Frontier Thawing
FrontierThawing = 1,
/// Homestead
Homestead = 2,
/// DAO Fork
DaoFork = 3,
/// Tangerine
Tangerine = 4,
/// Spurious Dragon
SpuriousDragon = 5,
/// Byzantium
Byzantium = 6,
/// Constantinople
Constantinople = 7,
/// Petersburg
Petersburg = 8,
/// Istanbul
Istanbul = 9,
/// Muir Glacier
MuirGlacier = 10,
/// Berlin
Berlin = 11,
/// London
London = 12,
/// Arrow Glacier
ArrowGlacier = 13,
/// Gray Glacier
GrayGlacier = 14,
/// Merge
Merge = 15,
/// Shanghai
Shanghai = 16,
/// Cancun
Cancun = 17,
/// Prague
Prague = 18,
/// Osaka
Osaka = 19,
}
impl FromStr for SpecId {
type Err = napi::Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
edr_chain_l1::chains::name::FRONTIER => Ok(SpecId::Frontier),
edr_chain_l1::chains::name::FRONTIER_THAWING => Ok(SpecId::FrontierThawing),
edr_chain_l1::chains::name::HOMESTEAD => Ok(SpecId::Homestead),
edr_chain_l1::chains::name::DAO_FORK => Ok(SpecId::DaoFork),
edr_chain_l1::chains::name::TANGERINE => Ok(SpecId::Tangerine),
edr_chain_l1::chains::name::SPURIOUS_DRAGON => Ok(SpecId::SpuriousDragon),
edr_chain_l1::chains::name::BYZANTIUM => Ok(SpecId::Byzantium),
edr_chain_l1::chains::name::CONSTANTINOPLE => Ok(SpecId::Constantinople),
edr_chain_l1::chains::name::PETERSBURG => Ok(SpecId::Petersburg),
edr_chain_l1::chains::name::ISTANBUL => Ok(SpecId::Istanbul),
edr_chain_l1::chains::name::MUIR_GLACIER => Ok(SpecId::MuirGlacier),
edr_chain_l1::chains::name::BERLIN => Ok(SpecId::Berlin),
edr_chain_l1::chains::name::LONDON => Ok(SpecId::London),
edr_chain_l1::chains::name::ARROW_GLACIER => Ok(SpecId::ArrowGlacier),
edr_chain_l1::chains::name::GRAY_GLACIER => Ok(SpecId::GrayGlacier),
edr_chain_l1::chains::name::MERGE => Ok(SpecId::Merge),
edr_chain_l1::chains::name::SHANGHAI => Ok(SpecId::Shanghai),
edr_chain_l1::chains::name::CANCUN => Ok(SpecId::Cancun),
edr_chain_l1::chains::name::PRAGUE => Ok(SpecId::Prague),
edr_chain_l1::chains::name::OSAKA => Ok(SpecId::Osaka),
_ => Err(napi::Error::new(
napi::Status::InvalidArg,
format!("The provided hardfork `{s}` is not supported."),
)),
}
}
}
impl From<SpecId> for edr_chain_l1::Hardfork {
fn from(value: SpecId) -> Self {
match value {
SpecId::Frontier => edr_chain_l1::Hardfork::FRONTIER,
SpecId::FrontierThawing => edr_chain_l1::Hardfork::FRONTIER_THAWING,
SpecId::Homestead => edr_chain_l1::Hardfork::HOMESTEAD,
SpecId::DaoFork => edr_chain_l1::Hardfork::DAO_FORK,
SpecId::Tangerine => edr_chain_l1::Hardfork::TANGERINE,
SpecId::SpuriousDragon => edr_chain_l1::Hardfork::SPURIOUS_DRAGON,
SpecId::Byzantium => edr_chain_l1::Hardfork::BYZANTIUM,
SpecId::Constantinople => edr_chain_l1::Hardfork::CONSTANTINOPLE,
SpecId::Petersburg => edr_chain_l1::Hardfork::PETERSBURG,
SpecId::Istanbul => edr_chain_l1::Hardfork::ISTANBUL,
SpecId::MuirGlacier => edr_chain_l1::Hardfork::MUIR_GLACIER,
SpecId::Berlin => edr_chain_l1::Hardfork::BERLIN,
SpecId::London => edr_chain_l1::Hardfork::LONDON,
SpecId::ArrowGlacier => edr_chain_l1::Hardfork::ARROW_GLACIER,
SpecId::GrayGlacier => edr_chain_l1::Hardfork::GRAY_GLACIER,
SpecId::Merge => edr_chain_l1::Hardfork::MERGE,
SpecId::Shanghai => edr_chain_l1::Hardfork::SHANGHAI,
SpecId::Cancun => edr_chain_l1::Hardfork::CANCUN,
SpecId::Prague => edr_chain_l1::Hardfork::PRAGUE,
SpecId::Osaka => edr_chain_l1::Hardfork::OSAKA,
}
}
}
/// Tries to parse the provided string to create a [`SpecId`] instance.
///
/// Returns an error if the string does not match any known hardfork.
#[napi(catch_unwind)]
pub fn l1_hardfork_from_string(hardfork: String) -> napi::Result<SpecId> {
hardfork.parse()
}
#[napi(catch_unwind)]
pub fn l1_hardfork_to_string(harfork: SpecId) -> &'static str {
match harfork {
SpecId::Frontier => edr_chain_l1::chains::name::FRONTIER,
SpecId::FrontierThawing => edr_chain_l1::chains::name::FRONTIER_THAWING,
SpecId::Homestead => edr_chain_l1::chains::name::HOMESTEAD,
SpecId::DaoFork => edr_chain_l1::chains::name::DAO_FORK,
SpecId::Tangerine => edr_chain_l1::chains::name::TANGERINE,
SpecId::SpuriousDragon => edr_chain_l1::chains::name::SPURIOUS_DRAGON,
SpecId::Byzantium => edr_chain_l1::chains::name::BYZANTIUM,
SpecId::Constantinople => edr_chain_l1::chains::name::CONSTANTINOPLE,
SpecId::Petersburg => edr_chain_l1::chains::name::PETERSBURG,
SpecId::Istanbul => edr_chain_l1::chains::name::ISTANBUL,
SpecId::MuirGlacier => edr_chain_l1::chains::name::MUIR_GLACIER,
SpecId::Berlin => edr_chain_l1::chains::name::BERLIN,
SpecId::London => edr_chain_l1::chains::name::LONDON,
SpecId::ArrowGlacier => edr_chain_l1::chains::name::ARROW_GLACIER,
SpecId::GrayGlacier => edr_chain_l1::chains::name::GRAY_GLACIER,
SpecId::Merge => edr_chain_l1::chains::name::MERGE,
SpecId::Shanghai => edr_chain_l1::chains::name::SHANGHAI,
SpecId::Cancun => edr_chain_l1::chains::name::CANCUN,
SpecId::Prague => edr_chain_l1::chains::name::PRAGUE,
SpecId::Osaka => edr_chain_l1::chains::name::OSAKA,
}
}
/// Returns the latest supported OP hardfork.
///
/// The returned value will be updated after each network upgrade.
#[napi]
pub fn l1_hardfork_latest() -> SpecId {
SpecId::Osaka
}
macro_rules! export_spec_id {
($($variant:ident),*) => {
$(
#[napi]
pub const $variant: &str = edr_chain_l1::chains::name::$variant;
)*
};
}
export_spec_id!(
FRONTIER,
FRONTIER_THAWING,
HOMESTEAD,
DAO_FORK,
TANGERINE,
SPURIOUS_DRAGON,
BYZANTIUM,
CONSTANTINOPLE,
PETERSBURG,
ISTANBUL,
MUIR_GLACIER,
BERLIN,
LONDON,
ARROW_GLACIER,
GRAY_GLACIER,
MERGE,
SHANGHAI,
CANCUN,
PRAGUE,
OSAKA
);

450
dev/env/node_modules/@nomicfoundation/edr/src/chains/op.rs generated vendored Executable file
View File

@@ -0,0 +1,450 @@
use std::{str::FromStr, sync::Arc};
use edr_napi_core::{
logger::Logger,
provider::{SyncProvider, SyncProviderFactory},
subscription::subscriber_callback_for_chain_spec,
};
use edr_op::{
predeploys::{
gas_price_oracle_code_ecotone, gas_price_oracle_code_fjord, gas_price_oracle_code_isthmus,
l1_block_code_bedrock, l1_block_code_ecotone, l1_block_code_isthmus,
GAS_PRICE_ORACLE_ADDRESS, L1_BLOCK_PREDEPLOY_ADDRESS,
},
OpChainSpec,
};
use edr_primitives::hex;
use edr_provider::time::CurrentTime;
use edr_solidity::contract_decoder::ContractDecoder;
use napi::{
bindgen_prelude::{BigInt, Uint8Array},
tokio::runtime,
};
use napi_derive::napi;
use crate::{
account::{AccountOverride, StorageSlot},
provider::ProviderFactory,
};
pub struct OpProviderFactory;
impl SyncProviderFactory for OpProviderFactory {
fn create_provider(
&self,
runtime: runtime::Handle,
provider_config: edr_napi_core::provider::Config,
logger_config: edr_napi_core::logger::Config,
subscription_callback: edr_napi_core::subscription::Callback,
contract_decoder: Arc<ContractDecoder>,
) -> napi::Result<Arc<dyn SyncProvider>> {
let logger =
Logger::<OpChainSpec, CurrentTime>::new(logger_config, Arc::clone(&contract_decoder))?;
let provider_config =
edr_provider::ProviderConfig::<edr_op::Hardfork>::try_from(provider_config)?;
let provider = edr_provider::Provider::<OpChainSpec>::new(
runtime.clone(),
Box::new(logger),
subscriber_callback_for_chain_spec::<OpChainSpec, CurrentTime>(subscription_callback),
provider_config,
contract_decoder,
CurrentTime,
)
.map_err(|error| napi::Error::new(napi::Status::GenericFailure, error.to_string()))?;
Ok(Arc::new(provider))
}
}
/// Enumeration of supported OP hardforks.
#[napi]
pub enum OpHardfork {
Bedrock = 100,
Regolith = 101,
Canyon = 102,
Ecotone = 103,
Fjord = 104,
Granite = 105,
Holocene = 106,
Isthmus = 107,
}
impl From<OpHardfork> for edr_op::Hardfork {
fn from(hardfork: OpHardfork) -> Self {
match hardfork {
OpHardfork::Bedrock => edr_op::Hardfork::BEDROCK,
OpHardfork::Regolith => edr_op::Hardfork::REGOLITH,
OpHardfork::Canyon => edr_op::Hardfork::CANYON,
OpHardfork::Ecotone => edr_op::Hardfork::ECOTONE,
OpHardfork::Fjord => edr_op::Hardfork::FJORD,
OpHardfork::Granite => edr_op::Hardfork::GRANITE,
OpHardfork::Holocene => edr_op::Hardfork::HOLOCENE,
OpHardfork::Isthmus => edr_op::Hardfork::ISTHMUS,
}
}
}
impl FromStr for OpHardfork {
type Err = napi::Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
edr_op::hardfork::name::BEDROCK => Ok(OpHardfork::Bedrock),
edr_op::hardfork::name::REGOLITH => Ok(OpHardfork::Regolith),
edr_op::hardfork::name::CANYON => Ok(OpHardfork::Canyon),
edr_op::hardfork::name::ECOTONE => Ok(OpHardfork::Ecotone),
edr_op::hardfork::name::FJORD => Ok(OpHardfork::Fjord),
edr_op::hardfork::name::GRANITE => Ok(OpHardfork::Granite),
edr_op::hardfork::name::HOLOCENE => Ok(OpHardfork::Holocene),
edr_op::hardfork::name::ISTHMUS => Ok(OpHardfork::Isthmus),
_ => Err(napi::Error::new(
napi::Status::InvalidArg,
format!("The provided OP hardfork `{s}` is not supported."),
)),
}
}
}
/// Tries to parse the provided string to create an [`OpHardfork`]
/// instance.
///
/// Returns an error if the string does not match any known hardfork.
#[napi(catch_unwind)]
pub fn op_hardfork_from_string(hardfork: String) -> napi::Result<OpHardfork> {
hardfork.parse()
}
/// Returns the string representation of the provided OP hardfork.
#[napi(catch_unwind)]
pub fn op_hardfork_to_string(hardfork: OpHardfork) -> &'static str {
match hardfork {
OpHardfork::Bedrock => edr_op::hardfork::name::BEDROCK,
OpHardfork::Regolith => edr_op::hardfork::name::REGOLITH,
OpHardfork::Canyon => edr_op::hardfork::name::CANYON,
OpHardfork::Ecotone => edr_op::hardfork::name::ECOTONE,
OpHardfork::Fjord => edr_op::hardfork::name::FJORD,
OpHardfork::Granite => edr_op::hardfork::name::GRANITE,
OpHardfork::Holocene => edr_op::hardfork::name::HOLOCENE,
OpHardfork::Isthmus => edr_op::hardfork::name::ISTHMUS,
}
}
/// Returns the latest supported OP hardfork.
///
/// The returned value will be updated after each network upgrade.
#[napi(catch_unwind)]
pub fn op_latest_hardfork() -> OpHardfork {
OpHardfork::Isthmus
}
#[napi]
pub const OP_CHAIN_TYPE: &str = edr_op::CHAIN_TYPE;
#[napi(catch_unwind)]
pub fn op_genesis_state(hardfork: OpHardfork) -> Vec<AccountOverride> {
let l1_block_code = l1_block_code(hardfork.into());
let l1_block = AccountOverride {
address: Uint8Array::with_data_copied(L1_BLOCK_PREDEPLOY_ADDRESS),
balance: Some(BigInt::from(0u64)),
nonce: Some(BigInt::from(0u64)),
code: Some(l1_block_code),
storage: Some(l1_block_storage(hardfork.into())),
};
/* The rest of the predeploys use a stubbed bytecode that reverts with a
message indicating that the predeploy is not supported. For each of
them, the Solidity code that generates the bytecode is:
// SPDX-License-Identifier: Unlicense
pragma solidity ^0.8.0;
contract NotSupported {
fallback() external payable {
revert("Predeploy <PredeployName> is not supported.");
}
}
*/
let stubbed_predeploys_data = vec![
(
"LegacyMessagePasser",
hex!("4200000000000000000000000000000000000000"),
"0x60806040526040517f08c379a000000000000000000000000000000000000000000000000000000000815260040160349060a1565b60405180910390fd5b60006048602f8360bf565b91507f5072656465706c6f79204c65676163794d65737361676550617373657220697360008301527f206e6f7420737570706f727465642e00000000000000000000000000000000006020830152604082019050919050565b6000602082019050818103600083015260b881603d565b9050919050565b60008282526020820190509291505056fea26469706673582212206ba272e31c33ce6fe2612b534c5aa5ed8905e1bed8a757ff1a74cc06509a17f664736f6c63430008000033",
),
(
"DeployerWhitelist",
hex!("4200000000000000000000000000000000000002"),
"0x60806040526040517f08c379a000000000000000000000000000000000000000000000000000000000815260040160349060a1565b60405180910390fd5b60006048602d8360bf565b91507f5072656465706c6f79204465706c6f79657257686974656c697374206973206e60008301527f6f7420737570706f727465642e000000000000000000000000000000000000006020830152604082019050919050565b6000602082019050818103600083015260b881603d565b9050919050565b60008282526020820190509291505056fea26469706673582212206af5fc0549e5db963a08cb2864cbbf5c4e27efb08219fc0e29bda83f84b121ac64736f6c63430008000033",
),
(
"LegacyERC20ETH",
hex!("DeadDeAddeAddEAddeadDEaDDEAdDeaDDeAD0000"),
"0x60806040526040517f08c379a000000000000000000000000000000000000000000000000000000000815260040160349060a1565b60405180910390fd5b60006048602a8360bf565b91507f5072656465706c6f79204c65676163794552433230455448206973206e6f742060008301527f737570706f727465642e000000000000000000000000000000000000000000006020830152604082019050919050565b6000602082019050818103600083015260b881603d565b9050919050565b60008282526020820190509291505056fea264697066735822122054e7f9d6c12400d5b4b67aed39be8c44a8b1461519e96a0e7764c69417239c7964736f6c63430008000033",
),
(
"WETH9",
hex!("4200000000000000000000000000000000000006"),
"0x60806040526040517f08c379a000000000000000000000000000000000000000000000000000000000815260040160349060a1565b60405180910390fd5b6000604860218360bf565b91507f5072656465706c6f79205745544839206973206e6f7420737570706f7274656460008301527f2e000000000000000000000000000000000000000000000000000000000000006020830152604082019050919050565b6000602082019050818103600083015260b881603d565b9050919050565b60008282526020820190509291505056fea2646970667358221220860ec43d585e1b040780713555b6fc492d748c73586bdb8f2b9af441c4452dbf64736f6c63430008000033",
),
(
"L2CrossDomainMessenger",
hex!("4200000000000000000000000000000000000007"),
"0x60806040526040517f08c379a000000000000000000000000000000000000000000000000000000000815260040160349060a1565b60405180910390fd5b6000604860328360bf565b91507f5072656465706c6f79204c3243726f7373446f6d61696e4d657373656e67657260008301527f206973206e6f7420737570706f727465642e00000000000000000000000000006020830152604082019050919050565b6000602082019050818103600083015260b881603d565b9050919050565b60008282526020820190509291505056fea26469706673582212200fadec69889de49a1a3a14d4e7e477e00921681e12650f510863d0077c16f58864736f6c63430008000033",
),
(
"L2StandardBridge",
hex!("4200000000000000000000000000000000000010"),
"0x60806040526040517f08c379a000000000000000000000000000000000000000000000000000000000815260040160349060a1565b60405180910390fd5b60006048602c8360bf565b91507f5072656465706c6f79204c325374616e64617264427269646765206973206e6f60008301527f7420737570706f727465642e00000000000000000000000000000000000000006020830152604082019050919050565b6000602082019050818103600083015260b881603d565b9050919050565b60008282526020820190509291505056fea2646970667358221220ce5c24ee894b04d974b95cd204ab35f85906430ba6f49d1ea70d3d0c9c204cb764736f6c63430008000033",
),
(
"SequencerFeeVault",
hex!("4200000000000000000000000000000000000011"),
"0x60806040526040517f08c379a000000000000000000000000000000000000000000000000000000000815260040160349060a1565b60405180910390fd5b60006048602d8360bf565b91507f5072656465706c6f792053657175656e6365724665655661756c74206973206e60008301527f6f7420737570706f727465642e000000000000000000000000000000000000006020830152604082019050919050565b6000602082019050818103600083015260b881603d565b9050919050565b60008282526020820190509291505056fea26469706673582212203990ed752a94bb02bd5162fef116c1b62079e8207c5164b3ae5a115f5cf0b31164736f6c63430008000033",
),
(
"OptimismMintableERC20Factory",
hex!("4200000000000000000000000000000000000012"),
"0x60806040526040517f08c379a000000000000000000000000000000000000000000000000000000000815260040160349060a1565b60405180910390fd5b6000604860388360bf565b91507f5072656465706c6f79204f7074696d69736d4d696e7461626c6545524332304660008301527f6163746f7279206973206e6f7420737570706f727465642e00000000000000006020830152604082019050919050565b6000602082019050818103600083015260b881603d565b9050919050565b60008282526020820190509291505056fea2646970667358221220240605543d69b93641a24f1d153969c3969089a04a162fc9f18f95de926b385564736f6c63430008000033",
),
(
"L1BlockNumber",
hex!("4200000000000000000000000000000000000013"),
"0x60806040526040517f08c379a000000000000000000000000000000000000000000000000000000000815260040160349060a1565b60405180910390fd5b6000604860298360bf565b91507f5072656465706c6f79204c31426c6f636b4e756d626572206973206e6f74207360008301527f7570706f727465642e00000000000000000000000000000000000000000000006020830152604082019050919050565b6000602082019050818103600083015260b881603d565b9050919050565b60008282526020820190509291505056fea264697066735822122099ba6da366313d162bab19a497fab2200808ddd24935b9f8be496c3622110b1164736f6c63430008000033",
),
(
"GovernanceToken",
hex!("4200000000000000000000000000000000000042"),
"0x60806040526040517f08c379a000000000000000000000000000000000000000000000000000000000815260040160349060a1565b60405180910390fd5b60006048602b8360bf565b91507f5072656465706c6f7920476f7665726e616e6365546f6b656e206973206e6f7460008301527f20737570706f727465642e0000000000000000000000000000000000000000006020830152604082019050919050565b6000602082019050818103600083015260b881603d565b9050919050565b60008282526020820190509291505056fea26469706673582212205a22322e97c15d3a28eb86abac215ed31bcf6e0cf562e2679ce5fb3495953cfc64736f6c63430008000033",
),
(
"L2ToL1MessagePasser",
hex!("4200000000000000000000000000000000000016"),
"0x60806040526040517f08c379a000000000000000000000000000000000000000000000000000000000815260040160349060a1565b60405180910390fd5b60006048602f8360bf565b91507f5072656465706c6f79204c32546f4c314d65737361676550617373657220697360008301527f206e6f7420737570706f727465642e00000000000000000000000000000000006020830152604082019050919050565b6000602082019050818103600083015260b881603d565b9050919050565b60008282526020820190509291505056fea26469706673582212205b2ed2ecc932d0e4a45e97ae7ac256e58848453ac06733b27890587962871a1864736f6c63430008000033",
),
(
"L2ERC721Bridge",
hex!("4200000000000000000000000000000000000014"),
"0x60806040526040517f08c379a000000000000000000000000000000000000000000000000000000000815260040160349060a1565b60405180910390fd5b60006048602a8360bf565b91507f5072656465706c6f79204c32455243373231427269646765206973206e6f742060008301527f737570706f727465642e000000000000000000000000000000000000000000006020830152604082019050919050565b6000602082019050818103600083015260b881603d565b9050919050565b60008282526020820190509291505056fea26469706673582212203f9de306b34383b29e9dfb174fd424d7e11d31e8859d0e96a2aa3a46609e826c64736f6c63430008000033",
),
(
"OptimismMintableERC721Factory",
hex!("4200000000000000000000000000000000000017"),
"0x60806040526040517f08c379a000000000000000000000000000000000000000000000000000000000815260040160349060a1565b60405180910390fd5b6000604860398360bf565b91507f5072656465706c6f79204f7074696d69736d4d696e7461626c6545524337323160008301527f466163746f7279206973206e6f7420737570706f727465642e000000000000006020830152604082019050919050565b6000602082019050818103600083015260b881603d565b9050919050565b60008282526020820190509291505056fea264697066735822122033131ae0c34f3246f5031971388431bd1dfb1b92d6b08d92a0a905911c1eeeeb64736f6c63430008000033",
),
(
"ProxyAdmin",
hex!("4200000000000000000000000000000000000018"),
"0x60806040526040517f08c379a000000000000000000000000000000000000000000000000000000000815260040160349060a1565b60405180910390fd5b6000604860268360bf565b91507f5072656465706c6f792050726f787941646d696e206973206e6f74207375707060008301527f6f727465642e00000000000000000000000000000000000000000000000000006020830152604082019050919050565b6000602082019050818103600083015260b881603d565b9050919050565b60008282526020820190509291505056fea2646970667358221220c7b191ff1b21c73fb6a26fd1e972d6844631a700b7a316ca2d9e04905af44dbb64736f6c63430008000033",
),
(
"BaseFeeVault",
hex!("4200000000000000000000000000000000000019"),
"0x60806040526040517f08c379a000000000000000000000000000000000000000000000000000000000815260040160349060a1565b60405180910390fd5b6000604860288360bf565b91507f5072656465706c6f7920426173654665655661756c74206973206e6f7420737560008301527f70706f727465642e0000000000000000000000000000000000000000000000006020830152604082019050919050565b6000602082019050818103600083015260b881603d565b9050919050565b60008282526020820190509291505056fea2646970667358221220535ae2b8a6393c0be4de1dce095f5e17fc0c7a46b40ac7793db894328f1799e764736f6c63430008000033",
),
(
"L1FeeVault",
hex!("420000000000000000000000000000000000001a"),
"0x60806040526040517f08c379a000000000000000000000000000000000000000000000000000000000815260040160349060a1565b60405180910390fd5b6000604860268360bf565b91507f5072656465706c6f79204c314665655661756c74206973206e6f74207375707060008301527f6f727465642e00000000000000000000000000000000000000000000000000006020830152604082019050919050565b6000602082019050818103600083015260b881603d565b9050919050565b60008282526020820190509291505056fea2646970667358221220dac6ab093c79da782b6e98ec67c48758f6c1cb80cba58e080c114a9b8c93befc64736f6c63430008000033",
),
(
"SchemaRegistry",
hex!("4200000000000000000000000000000000000020"),
"0x60806040526040517f08c379a000000000000000000000000000000000000000000000000000000000815260040160349060a1565b60405180910390fd5b60006048602a8360bf565b91507f5072656465706c6f7920536368656d615265676973747279206973206e6f742060008301527f737570706f727465642e000000000000000000000000000000000000000000006020830152604082019050919050565b6000602082019050818103600083015260b881603d565b9050919050565b60008282526020820190509291505056fea2646970667358221220b3daf5355920b581943cabb92a7cc67123467fdd1b054cb0c5f0e587c08da1be64736f6c63430008000033",
),
(
"EAS",
hex!("4200000000000000000000000000000000000021"),
"0x60806040526040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401603490607b565b60405180910390fd5b60006048601f836099565b91507f5072656465706c6f7920454153206973206e6f7420737570706f727465642e006000830152602082019050919050565b60006020820190508181036000830152609281603d565b9050919050565b60008282526020820190509291505056fea2646970667358221220afa6c1aa54a8b3f4f979e1297db5838a94353f3b77b5ecc164da19db26ea89f564736f6c63430008000033",
),
(
"OperatorFeeVault",
hex!("0x420000000000000000000000000000000000001b"),
"0x60806040526040517f08c379a000000000000000000000000000000000000000000000000000000000815260040160349060b9565b60405180910390fd5b5f82825260208201905092915050565b7f5072656465706c6f79204f70657261746f724665655661756c74206973206e6f5f8201527f7420737570706f727465642e0000000000000000000000000000000000000000602082015250565b5f60a5602c83603d565b915060ae82604d565b604082019050919050565b5f6020820190508181035f83015260ce81609b565b905091905056fea2646970667358221220dc3131d0ea77326c36012aee5dd9a870b6f07d76e6f55c8029da9d70a83f50c364736f6c634300081e0033",
),
];
let stubbed_predeploys = stubbed_predeploys_data
.iter()
.map(|(name, address, code)| AccountOverride {
address: address.into(),
balance: Some(BigInt::from(0u64)),
nonce: Some(BigInt::from(0u64)),
code: Some(
hex::decode(code)
.unwrap_or_else(|e| panic!("The bytecode for the {name} predeploy should be a valid hex string, got error: {e}"))
.into(),
),
storage: Some(vec![]),
});
let predeploys = vec![gas_price_oracle_override(hardfork.into()), l1_block];
predeploys.into_iter().chain(stubbed_predeploys).collect()
}
#[napi(catch_unwind)]
pub fn op_provider_factory() -> ProviderFactory {
let factory: Arc<dyn SyncProviderFactory> = Arc::new(OpProviderFactory);
factory.into()
}
fn gas_price_oracle_override(hardfork: edr_op::Hardfork) -> AccountOverride {
if hardfork >= edr_op::Hardfork::ISTHMUS {
gas_price_oracle_isthmus()
} else if hardfork >= edr_op::Hardfork::FJORD {
gas_price_oracle_fjord()
} else {
gas_price_oracle_ecotone()
}
}
fn gas_price_oracle_ecotone() -> AccountOverride {
AccountOverride {
address: Uint8Array::with_data_copied(GAS_PRICE_ORACLE_ADDRESS),
balance: None,
nonce: None,
code: Some(gas_price_oracle_code_ecotone().into()),
storage: Some(vec![StorageSlot {
index: BigInt::from(0u64),
// bool isEcotone = true
value: BigInt::from(
0x0000000000000000000000000000000000000000000000000000000000000001u64,
),
}]),
}
}
fn gas_price_oracle_fjord() -> AccountOverride {
AccountOverride {
address: Uint8Array::with_data_copied(GAS_PRICE_ORACLE_ADDRESS),
balance: None,
nonce: None,
code: Some(gas_price_oracle_code_fjord().into()),
storage: Some(vec![StorageSlot {
index: BigInt::from(0u64),
// bool isEcotone = true
// bool isFjord = true
value: BigInt::from(
0x0000000000000000000000000000000000000000000000000000000000000101u64,
),
}]),
}
}
fn gas_price_oracle_isthmus() -> AccountOverride {
AccountOverride {
address: Uint8Array::with_data_copied(GAS_PRICE_ORACLE_ADDRESS),
balance: None,
nonce: None,
code: Some(gas_price_oracle_code_isthmus().into()),
storage: Some(vec![StorageSlot {
index: BigInt::from(0u64),
// bool isEcotone = true
// bool isFjord = true
// bool isIsthmus = true
value: BigInt::from(
0x0000000000000000000000000000000000000000000000000000000000010101u64,
),
}]),
}
}
fn l1_block_storage(hardfork: edr_op::Hardfork) -> Vec<StorageSlot> {
let mut base_storage = vec![
StorageSlot {
index: BigInt::from(0u64),
// uint64 public number = 1
// uint64 public timestamp = 1
value: BigInt {
words: vec![
0x0000000000000001_u64, // least significative
0x0000000000000001_u64,
],
sign_bit: false,
},
},
StorageSlot {
index: BigInt::from(1u64),
// uint256 baseFee = 10 gwei
value: BigInt::from(0x00000002540be400_u64),
},
StorageSlot {
index: BigInt::from(2u64),
// bytes32 hash = 0
value: BigInt::from(0u64),
},
StorageSlot {
index: BigInt::from(3u64),
// uint64 sequenceNumber = 0
// uint32 blobBaseFeeScalar = 1014213
// uint32 baseFeeScalar = 5227
value: BigInt {
words: vec![
0x0000000000000000_u64, // least significative
0x0000000000000000_u64,
0x00000000000f79c5_u64,
0x000000000000146b_u64,
],
sign_bit: false,
},
},
StorageSlot {
index: BigInt::from(4u64),
// bytes32 batcherHash = 0
value: BigInt::from(0u64),
},
StorageSlot {
index: BigInt::from(5u64),
// uint256 l1FeeOverhead = 0
value: BigInt::from(0u64),
},
StorageSlot {
index: BigInt::from(6u64),
// uint256 l1FeeScalar = 0
value: BigInt::from(0u64),
},
StorageSlot {
index: BigInt::from(7u64),
// uint256 blobBaseFee = 10 gwei
value: BigInt::from(0x00000002540be400_u64),
},
];
if hardfork >= edr_op::Hardfork::ISTHMUS {
base_storage.push(StorageSlot {
// Operator fee parameters
index: BigInt::from(8u64),
value: BigInt::from(0u64),
});
}
base_storage
}
fn l1_block_code(hardfork: edr_op::Hardfork) -> Uint8Array {
if hardfork >= edr_op::Hardfork::ISTHMUS {
l1_block_code_isthmus().into()
} else if hardfork >= edr_op::Hardfork::ECOTONE {
l1_block_code_ecotone().into()
} else {
l1_block_code_bedrock().into()
}
}
macro_rules! export_spec_id {
($($variant:ident,)*) => {
$(
#[napi]
pub const $variant: &str = edr_op::hardfork::name::$variant;
)*
};
}
export_spec_id! {
BEDROCK,
REGOLITH,
CANYON,
ECOTONE,
FJORD,
GRANITE,
HOLOCENE,
ISTHMUS,
}

710
dev/env/node_modules/@nomicfoundation/edr/src/config.rs generated vendored Executable file
View File

@@ -0,0 +1,710 @@
use core::fmt::{Debug, Display};
use std::{
num::NonZeroU64,
path::PathBuf,
time::{Duration, SystemTime},
};
use edr_coverage::reporter::SyncOnCollectedCoverageCallback;
use edr_eip1559::{BaseFeeActivation, ConstantBaseFeeParams};
use edr_gas_report::SyncOnCollectedGasReportCallback;
use edr_primitives::{Bytes, HashMap, HashSet};
use edr_signer::{secret_key_from_str, SecretKey};
use napi::{
bindgen_prelude::{BigInt, Promise, Reference, Uint8Array},
threadsafe_function::{
ErrorStrategy, ThreadSafeCallContext, ThreadsafeFunction, ThreadsafeFunctionCallMode,
},
tokio::runtime,
Either, JsFunction, JsString, JsStringUtf8,
};
use napi_derive::napi;
use crate::{
account::AccountOverride, block::BlobGas, cast::TryCast, gas_report::GasReport,
logger::LoggerConfig, precompile::Precompile, subscription::SubscriptionConfig,
};
/// Configuration for EIP-1559 parameters
#[napi(object)]
pub struct BaseFeeParamActivation {
pub activation: Either<BaseFeeActivationByBlockNumber, BaseFeeActivationByHardfork>,
pub max_change_denominator: BigInt,
pub elasticity_multiplier: BigInt,
}
#[napi(object)]
pub struct BaseFeeActivationByBlockNumber {
/// The block number at which the `base_fee_params` is activated
pub block_number: BigInt,
}
#[napi(object)]
pub struct BaseFeeActivationByHardfork {
/// The hardfork at which the `base_fee_params` is activated
pub hardfork: String,
}
impl TryFrom<BaseFeeParamActivation> for (BaseFeeActivation<String>, ConstantBaseFeeParams) {
type Error = napi::Error;
fn try_from(value: BaseFeeParamActivation) -> Result<Self, Self::Error> {
let base_fee_params = ConstantBaseFeeParams {
max_change_denominator: value.max_change_denominator.try_cast()?,
elasticity_multiplier: value.elasticity_multiplier.try_cast()?,
};
match value.activation {
Either::A(BaseFeeActivationByBlockNumber { block_number }) => {
let activation_block_number: u64 = block_number.try_cast()?;
Ok((
BaseFeeActivation::BlockNumber(activation_block_number),
base_fee_params,
))
}
Either::B(BaseFeeActivationByHardfork { hardfork }) => {
Ok((BaseFeeActivation::Hardfork(hardfork), base_fee_params))
}
}
}
}
/// Specification of a chain with possible overrides.
#[napi(object)]
pub struct ChainOverride {
/// The chain ID
pub chain_id: BigInt,
/// The chain's name
pub name: String,
/// If present, overrides for the chain's supported hardforks
pub hardfork_activation_overrides: Option<Vec<HardforkActivation>>,
}
/// Configuration for a code coverage reporter.
#[napi(object)]
pub struct CodeCoverageConfig {
/// The callback to be called when coverage has been collected.
///
/// The callback receives an array of unique coverage hit markers (i.e. no
/// repetition) per transaction.
///
/// Exceptions thrown in the callback will be propagated to the original
/// caller.
#[napi(ts_type = "(coverageHits: Uint8Array[]) => Promise<void>")]
pub on_collected_coverage_callback: JsFunction,
}
#[napi(object)]
pub struct GasReportConfig {
/// Gas reports are collected after a block is mined or `eth_call` is
/// executed.
///
/// Exceptions thrown in the callback will be propagated to the original
/// caller.
#[napi(ts_type = "(gasReport: GasReport) => Promise<void>")]
pub on_collected_gas_report_callback: JsFunction,
}
/// Configuration for forking a blockchain
#[napi(object)]
pub struct ForkConfig {
/// The block number to fork from. If not provided, the latest safe block is
/// used.
pub block_number: Option<BigInt>,
/// The directory to cache remote JSON-RPC responses
pub cache_dir: Option<String>,
/// Overrides for the configuration of chains.
pub chain_overrides: Option<Vec<ChainOverride>>,
/// The HTTP headers to use when making requests to the JSON-RPC endpoint
pub http_headers: Option<Vec<HttpHeader>>,
/// The URL of the JSON-RPC endpoint to fork from
pub url: String,
}
#[napi(object)]
pub struct HttpHeader {
pub name: String,
pub value: String,
}
/// Configuration for a hardfork activation
#[napi(object)]
pub struct HardforkActivation {
/// The condition for the hardfork activation
pub condition: Either<HardforkActivationByBlockNumber, HardforkActivationByTimestamp>,
/// The activated hardfork
pub hardfork: String,
}
#[napi(object)]
pub struct HardforkActivationByBlockNumber {
/// The block number at which the hardfork is activated
pub block_number: BigInt,
}
#[napi(object)]
pub struct HardforkActivationByTimestamp {
/// The timestamp at which the hardfork is activated
pub timestamp: BigInt,
}
#[napi(string_enum)]
#[doc = "The type of ordering to use when selecting blocks to mine."]
pub enum MineOrdering {
#[doc = "Insertion order"]
Fifo,
#[doc = "Effective miner fee"]
Priority,
}
/// Configuration for the provider's mempool.
#[napi(object)]
pub struct MemPoolConfig {
pub order: MineOrdering,
}
#[napi(object)]
pub struct IntervalRange {
pub min: BigInt,
pub max: BigInt,
}
/// Configuration for the provider's miner.
#[napi(object)]
pub struct MiningConfig {
pub auto_mine: bool,
pub interval: Option<Either<BigInt, IntervalRange>>,
pub mem_pool: MemPoolConfig,
}
/// Configuration for runtime observability.
#[napi(object)]
pub struct ObservabilityConfig {
/// If present, configures runtime observability to collect code coverage.
pub code_coverage: Option<CodeCoverageConfig>,
/// If present, configures runtime observability to collect gas reports.
pub gas_report: Option<GasReportConfig>,
}
/// Configuration for a provider
#[napi(object)]
pub struct ProviderConfig {
/// Whether to allow blocks with the same timestamp
pub allow_blocks_with_same_timestamp: bool,
/// Whether to allow unlimited contract size
pub allow_unlimited_contract_size: bool,
/// Whether to return an `Err` when `eth_call` fails
pub bail_on_call_failure: bool,
/// Whether to return an `Err` when a `eth_sendTransaction` fails
pub bail_on_transaction_failure: bool,
/// EIP-1559 base fee parameters activations to be used to calculate the
/// block base fee.
///
/// Provide an ordered list of `base_fee_params` to be
/// used starting from the specified activation point (hardfork or block
/// number).
/// If not provided, the default values from the chain spec
/// will be used.
pub base_fee_config: Option<Vec<BaseFeeParamActivation>>,
/// The gas limit of each block
pub block_gas_limit: BigInt,
/// The chain ID of the blockchain
pub chain_id: BigInt,
/// The address of the coinbase
pub coinbase: Uint8Array,
/// The configuration for forking a blockchain. If not provided, a local
/// blockchain will be created
pub fork: Option<ForkConfig>,
/// The genesis state of the blockchain
pub genesis_state: Vec<AccountOverride>,
/// The hardfork of the blockchain
pub hardfork: String,
/// The initial base fee per gas of the blockchain. Required for EIP-1559
/// transactions and later
pub initial_base_fee_per_gas: Option<BigInt>,
/// The initial blob gas of the blockchain. Required for EIP-4844
pub initial_blob_gas: Option<BlobGas>,
/// The initial date of the blockchain, in seconds since the Unix epoch
pub initial_date: Option<BigInt>,
/// The initial parent beacon block root of the blockchain. Required for
/// EIP-4788
pub initial_parent_beacon_block_root: Option<Uint8Array>,
/// The minimum gas price of the next block.
pub min_gas_price: BigInt,
/// The configuration for the miner
pub mining: MiningConfig,
/// The network ID of the blockchain
pub network_id: BigInt,
/// The configuration for the provider's observability
pub observability: ObservabilityConfig,
// Using JsString here as it doesn't have `Debug`, `Display` and `Serialize` implementation
// which prevents accidentally leaking the secret keys to error messages and logs.
/// Secret keys of owned accounts
pub owned_accounts: Vec<JsString>,
/// Overrides for precompiles
pub precompile_overrides: Vec<Reference<Precompile>>,
/// Transaction gas cap, introduced in [EIP-7825].
///
/// When not set, will default to value defined by the used hardfork
///
/// [EIP-7825]: https://eips.ethereum.org/EIPS/eip-7825
pub transaction_gas_cap: Option<BigInt>,
}
impl TryFrom<ForkConfig> for edr_provider::ForkConfig<String> {
type Error = napi::Error;
fn try_from(value: ForkConfig) -> Result<Self, Self::Error> {
let block_number: Option<u64> = value.block_number.map(TryCast::try_cast).transpose()?;
let cache_dir = PathBuf::from(
value
.cache_dir
.unwrap_or(edr_defaults::CACHE_DIR.to_owned()),
);
let chain_overrides = value
.chain_overrides
.map(|chain_overrides| {
chain_overrides
.into_iter()
.map(
|ChainOverride {
chain_id,
name,
hardfork_activation_overrides,
}| {
let hardfork_activation_overrides =
hardfork_activation_overrides
.map(|hardfork_activations| {
hardfork_activations
.into_iter()
.map(
|HardforkActivation {
condition,
hardfork,
}| {
let condition = match condition {
Either::A(HardforkActivationByBlockNumber {
block_number,
}) => edr_chain_config::ForkCondition::Block(
block_number.try_cast()?,
),
Either::B(HardforkActivationByTimestamp {
timestamp,
}) => edr_chain_config::ForkCondition::Timestamp(
timestamp.try_cast()?,
),
};
Ok(edr_chain_config::HardforkActivation {
condition,
hardfork,
})
},
)
.collect::<napi::Result<Vec<_>>>()
.map(edr_chain_config::HardforkActivations::new)
})
.transpose()?;
let chain_config = edr_chain_config::ChainOverride {
name,
hardfork_activation_overrides,
};
let chain_id = chain_id.try_cast()?;
Ok((chain_id, chain_config))
},
)
.collect::<napi::Result<_>>()
})
.transpose()?;
let http_headers = value.http_headers.map(|http_headers| {
http_headers
.into_iter()
.map(|HttpHeader { name, value }| (name, value))
.collect()
});
Ok(Self {
block_number,
cache_dir,
chain_overrides: chain_overrides.unwrap_or_default(),
http_headers,
url: value.url,
})
}
}
impl From<MemPoolConfig> for edr_provider::MemPoolConfig {
fn from(value: MemPoolConfig) -> Self {
Self {
order: value.order.into(),
}
}
}
impl From<MineOrdering> for edr_block_miner::MineOrdering {
fn from(value: MineOrdering) -> Self {
match value {
MineOrdering::Fifo => Self::Fifo,
MineOrdering::Priority => Self::Priority,
}
}
}
impl TryFrom<MiningConfig> for edr_provider::MiningConfig {
type Error = napi::Error;
fn try_from(value: MiningConfig) -> Result<Self, Self::Error> {
let mem_pool = value.mem_pool.into();
let interval = value
.interval
.map(|interval| {
let interval = match interval {
Either::A(interval) => {
let interval = interval.try_cast()?;
let interval = NonZeroU64::new(interval).ok_or_else(|| {
napi::Error::new(
napi::Status::GenericFailure,
"Interval must be greater than 0",
)
})?;
edr_provider::IntervalConfig::Fixed(interval)
}
Either::B(IntervalRange { min, max }) => edr_provider::IntervalConfig::Range {
min: min.try_cast()?,
max: max.try_cast()?,
},
};
napi::Result::Ok(interval)
})
.transpose()?;
Ok(Self {
auto_mine: value.auto_mine,
interval,
mem_pool,
})
}
}
impl ObservabilityConfig {
/// Resolves the instance, converting it to a
/// [`edr_provider::observability::Config`].
pub fn resolve(
self,
env: &napi::Env,
runtime: runtime::Handle,
) -> napi::Result<edr_provider::observability::Config> {
let on_collected_coverage_fn = self
.code_coverage
.map(
|code_coverage| -> napi::Result<Box<dyn SyncOnCollectedCoverageCallback>> {
let runtime = runtime.clone();
let mut on_collected_coverage_callback: ThreadsafeFunction<
_,
ErrorStrategy::Fatal,
> = code_coverage
.on_collected_coverage_callback
.create_threadsafe_function(
0,
|ctx: ThreadSafeCallContext<HashSet<Bytes>>| {
let hits = ctx
.env
.create_array_with_length(ctx.value.len())
.and_then(|mut hits| {
for (idx, hit) in ctx.value.into_iter().enumerate() {
ctx.env
.create_buffer_with_data(hit.to_vec())
.and_then(|hit| {
let idx = u32::try_from(idx).unwrap_or_else(|_| panic!("Number of hits should not exceed '{}'",
u32::MAX));
hits.set_element(idx, hit.into_raw())
})?;
}
Ok(hits)
})?;
Ok(vec![hits])
},
)?;
// Maintain a weak reference to the function to avoid blocking the event loop
// from exiting.
on_collected_coverage_callback.unref(env)?;
let on_collected_coverage_fn: Box<dyn SyncOnCollectedCoverageCallback> =
Box::new(move |hits| {
let runtime = runtime.clone();
let (sender, receiver) = std::sync::mpsc::channel();
let status = on_collected_coverage_callback
.call_with_return_value(hits, ThreadsafeFunctionCallMode::Blocking, move |result: Promise<()>| {
// We spawn a background task to handle the async callback
runtime.spawn(async move {
let result = result.await;
sender.send(result).map_err(|_error| {
napi::Error::new(
napi::Status::GenericFailure,
"Failed to send result from on_collected_coverage_callback",
)
})
});
Ok(())
});
assert_eq!(status, napi::Status::Ok);
let () = receiver.recv().expect("Receive can only fail if the channel is closed")?;
Ok(())
});
Ok(on_collected_coverage_fn)
},
)
.transpose()?;
let on_collected_gas_report_fn = self.gas_report.map(
|gas_report| -> napi::Result<Box<dyn SyncOnCollectedGasReportCallback>> {
let mut on_collected_gas_report_callback: ThreadsafeFunction<
_,
ErrorStrategy::Fatal,
> = gas_report
.on_collected_gas_report_callback
.create_threadsafe_function(
0,
|ctx: ThreadSafeCallContext<GasReport>| {
let report = ctx.value;
Ok(vec![report])
}
,
)?;
// Maintain a weak reference to the function to avoid blocking the event loop
// from exiting.
on_collected_gas_report_callback.unref(env)?;
let on_collected_gas_report_fn: Box<dyn SyncOnCollectedGasReportCallback> =
Box::new(move |report| {
let runtime = runtime.clone();
let (sender, receiver) = std::sync::mpsc::channel();
// Convert the report to the N-API representation
let status = on_collected_gas_report_callback
.call_with_return_value(GasReport::from(report), ThreadsafeFunctionCallMode::Blocking, move |result: Promise<()>| {
// We spawn a background task to handle the async callback
runtime.spawn(async move {
let result = result.await;
sender.send(result).map_err(|_error| {
napi::Error::new(
napi::Status::GenericFailure,
"Failed to send result from on_collected_gas_report_callback",
)
})
});
Ok(())
});
assert_eq!(status, napi::Status::Ok);
let () = receiver.recv().expect("Receive can only fail if the channel is closed")?;
Ok(())
});
Ok(on_collected_gas_report_fn)
},
).transpose()?;
Ok(edr_provider::observability::Config {
on_collected_coverage_fn,
on_collected_gas_report_fn,
..edr_provider::observability::Config::default()
})
}
}
impl ProviderConfig {
/// Resolves the instance to a [`edr_napi_core::provider::Config`].
pub fn resolve(
self,
env: &napi::Env,
runtime: runtime::Handle,
) -> napi::Result<edr_napi_core::provider::Config> {
let owned_accounts = self
.owned_accounts
.into_iter()
.map(|secret_key| {
// This is the only place in production code where it's allowed to use
// `DangerousSecretKeyStr`.
#[allow(deprecated)]
use edr_signer::DangerousSecretKeyStr;
static_assertions::assert_not_impl_all!(JsString: Debug, Display, serde::Serialize);
static_assertions::assert_not_impl_all!(JsStringUtf8: Debug, Display, serde::Serialize);
// `SecretKey` has `Debug` implementation, but it's opaque (only shows the
// type name)
static_assertions::assert_not_impl_any!(SecretKey: Display, serde::Serialize);
let secret_key = secret_key.into_utf8()?;
// This is the only place in production code where it's allowed to use
// `DangerousSecretKeyStr`.
#[allow(deprecated)]
let secret_key_str = DangerousSecretKeyStr(secret_key.as_str()?);
let secret_key: SecretKey = secret_key_from_str(secret_key_str)
.map_err(|error| napi::Error::new(napi::Status::InvalidArg, error))?;
Ok(secret_key)
})
.collect::<napi::Result<Vec<_>>>()?;
let base_fee_params: Option<Vec<(BaseFeeActivation<String>, ConstantBaseFeeParams)>> = self
.base_fee_config
.map(|vec| vec.into_iter().map(TryInto::try_into).collect())
.transpose()?;
let block_gas_limit =
NonZeroU64::new(self.block_gas_limit.try_cast()?).ok_or_else(|| {
napi::Error::new(
napi::Status::GenericFailure,
"Block gas limit must be greater than 0",
)
})?;
let genesis_state = self
.genesis_state
.into_iter()
.map(TryInto::try_into)
.collect::<napi::Result<HashMap<edr_primitives::Address, edr_provider::AccountOverride>>>()?;
let precompile_overrides = self
.precompile_overrides
.into_iter()
.map(|precompile| precompile.to_tuple())
.collect();
Ok(edr_napi_core::provider::Config {
allow_blocks_with_same_timestamp: self.allow_blocks_with_same_timestamp,
allow_unlimited_contract_size: self.allow_unlimited_contract_size,
bail_on_call_failure: self.bail_on_call_failure,
bail_on_transaction_failure: self.bail_on_transaction_failure,
base_fee_params,
block_gas_limit,
chain_id: self.chain_id.try_cast()?,
coinbase: self.coinbase.try_cast()?,
fork: self.fork.map(TryInto::try_into).transpose()?,
genesis_state,
hardfork: self.hardfork,
initial_base_fee_per_gas: self
.initial_base_fee_per_gas
.map(TryCast::try_cast)
.transpose()?,
initial_blob_gas: self.initial_blob_gas.map(TryInto::try_into).transpose()?,
initial_date: self
.initial_date
.map(|date| {
let elapsed_since_epoch = Duration::from_secs(date.try_cast()?);
napi::Result::Ok(SystemTime::UNIX_EPOCH + elapsed_since_epoch)
})
.transpose()?,
initial_parent_beacon_block_root: self
.initial_parent_beacon_block_root
.map(TryCast::try_cast)
.transpose()?,
mining: self.mining.try_into()?,
min_gas_price: self.min_gas_price.try_cast()?,
network_id: self.network_id.try_cast()?,
observability: self.observability.resolve(env, runtime)?,
owned_accounts,
precompile_overrides,
transaction_gas_cap: self
.transaction_gas_cap
.map(TryCast::try_cast)
.transpose()?,
})
}
}
/// Tracing config for Solidity stack trace generation.
#[napi(object)]
pub struct TracingConfigWithBuffers {
/// Build information to use for decoding contracts. Either a Hardhat v2
/// build info file that contains both input and output or a Hardhat v3
/// build info file that doesn't contain output and a separate output file.
pub build_infos: Option<Either<Vec<Uint8Array>, Vec<BuildInfoAndOutput>>>,
/// Whether to ignore contracts whose name starts with "Ignored".
pub ignore_contracts: Option<bool>,
}
impl From<TracingConfigWithBuffers> for edr_napi_core::solidity::config::TracingConfigWithBuffers {
fn from(value: TracingConfigWithBuffers) -> Self {
edr_napi_core::solidity::config::TracingConfigWithBuffers {
build_infos: value.build_infos.map(|infos| match infos {
Either::A(with_output) => Either::A(with_output),
Either::B(separate_output) => Either::B(
separate_output
.into_iter()
.map(edr_napi_core::solidity::config::BuildInfoAndOutput::from)
.collect(),
),
}),
ignore_contracts: value.ignore_contracts,
}
}
}
/// Hardhat V3 build info where the compiler output is not part of the build
/// info file.
#[napi(object)]
pub struct BuildInfoAndOutput {
/// The build info input file
pub build_info: Uint8Array,
/// The build info output file
pub output: Uint8Array,
}
impl From<BuildInfoAndOutput> for edr_napi_core::solidity::config::BuildInfoAndOutput {
fn from(value: BuildInfoAndOutput) -> Self {
Self {
build_info: value.build_info,
output: value.output,
}
}
}
/// Result of [`resolve_configs`].
pub struct ConfigResolution {
pub logger_config: edr_napi_core::logger::Config,
pub provider_config: edr_napi_core::provider::Config,
pub subscription_callback: edr_napi_core::subscription::Callback,
}
/// Helper function for resolving the provided N-API configs.
pub fn resolve_configs(
env: &napi::Env,
runtime: runtime::Handle,
provider_config: ProviderConfig,
logger_config: LoggerConfig,
subscription_config: SubscriptionConfig,
) -> napi::Result<ConfigResolution> {
let provider_config = provider_config.resolve(env, runtime)?;
let logger_config = logger_config.resolve(env)?;
let subscription_config = edr_napi_core::subscription::Config::from(subscription_config);
let subscription_callback =
edr_napi_core::subscription::Callback::new(env, subscription_config.subscription_callback)?;
Ok(ConfigResolution {
logger_config,
provider_config,
subscription_callback,
})
}

447
dev/env/node_modules/@nomicfoundation/edr/src/context.rs generated vendored Executable file
View File

@@ -0,0 +1,447 @@
use std::sync::Arc;
use edr_napi_core::{provider::SyncProviderFactory, solidity};
use edr_primitives::HashMap;
use edr_solidity_tests::{
decode::RevertDecoder,
multi_runner::{SuiteResultAndArtifactId, TestContract, TestContracts},
TestFilterConfig,
};
use napi::{
threadsafe_function::{
ErrorStrategy, ThreadSafeCallContext, ThreadsafeFunction, ThreadsafeFunctionCallMode,
},
tokio::{runtime, sync::Mutex as AsyncMutex},
Env, JsFunction, JsObject,
};
use napi_derive::napi;
use tracing_subscriber::{prelude::*, EnvFilter, Registry};
use crate::{
config::{resolve_configs, ConfigResolution, ProviderConfig, TracingConfigWithBuffers},
contract_decoder::ContractDecoder,
logger::LoggerConfig,
provider::{Provider, ProviderFactory},
solidity_tests::{
artifact::{Artifact, ArtifactId},
config::SolidityTestRunnerConfigArgs,
factory::SolidityTestRunnerFactory,
test_results::{SolidityTestResult, SuiteResult},
LinkingOutput,
},
subscription::SubscriptionConfig,
};
#[napi]
pub struct EdrContext {
inner: Arc<AsyncMutex<Context>>,
}
#[napi]
impl EdrContext {
/// Creates a new [`EdrContext`] instance. Should only be called once!
#[napi(catch_unwind, constructor)]
pub fn new() -> napi::Result<Self> {
let context = Context::new()?;
Ok(Self {
inner: Arc::new(AsyncMutex::new(context)),
})
}
/// Constructs a new provider with the provided configuration.
#[napi(catch_unwind, ts_return_type = "Promise<Provider>")]
pub fn create_provider(
&self,
env: Env,
chain_type: String,
provider_config: ProviderConfig,
logger_config: LoggerConfig,
subscription_config: SubscriptionConfig,
contract_decoder: &ContractDecoder,
) -> napi::Result<JsObject> {
let (deferred, promise) = env.create_deferred()?;
macro_rules! try_or_reject_promise {
($expr:expr) => {
match $expr {
Ok(value) => value,
Err(error) => {
deferred.reject(error);
return Ok(promise);
}
}
};
}
let runtime = runtime::Handle::current();
let ConfigResolution {
logger_config,
provider_config,
subscription_callback,
} = try_or_reject_promise!(resolve_configs(
&env,
runtime.clone(),
provider_config,
logger_config,
subscription_config,
));
#[cfg(feature = "scenarios")]
let scenario_file =
try_or_reject_promise!(runtime.clone().block_on(crate::scenarios::scenario_file(
chain_type.clone(),
provider_config.clone(),
logger_config.enable,
)));
let factory = {
// TODO: https://github.com/NomicFoundation/edr/issues/760
// TODO: Don't block the JS event loop
let context = runtime.block_on(async { self.inner.lock().await });
try_or_reject_promise!(context.get_provider_factory(&chain_type))
};
let contract_decoder = Arc::clone(contract_decoder.as_inner());
runtime.clone().spawn_blocking(move || {
let result = factory
.create_provider(
runtime.clone(),
provider_config,
logger_config,
subscription_callback,
Arc::clone(&contract_decoder),
)
.map(|provider| {
Provider::new(
provider,
runtime,
contract_decoder,
#[cfg(feature = "scenarios")]
scenario_file,
)
});
deferred.resolve(|_env| result);
});
Ok(promise)
}
/// Registers a new provider factory for the provided chain type.
#[napi(catch_unwind)]
pub async fn register_provider_factory(
&self,
chain_type: String,
factory: &ProviderFactory,
) -> napi::Result<()> {
let mut context = self.inner.lock().await;
context.register_provider_factory(chain_type, factory.as_inner().clone());
Ok(())
}
#[napi(catch_unwind)]
pub async fn register_solidity_test_runner_factory(
&self,
chain_type: String,
factory: &SolidityTestRunnerFactory,
) -> napi::Result<()> {
let mut context = self.inner.lock().await;
context.register_solidity_test_runner(chain_type, factory.as_inner().clone());
Ok(())
}
/// Executes Solidity tests
///
/// The function will return a promise that resolves to a
/// [`SolidityTestResult`].
///
/// Arguments:
/// - `chainType`: the same chain type that was passed to
/// `registerProviderFactory`.
/// - `artifacts`: the project's compilation output artifacts. It's
/// important to include include all artifacts here, otherwise cheatcodes
/// that access artifacts and other functionality (e.g. auto-linking, gas
/// reports) can break.
/// - `testSuites`: the test suite ids that specify which test suites to
/// execute. The test suite artifacts must be present in `artifacts`.
/// - `configArgs`: solidity test runner configuration. See the struct docs
/// for details.
/// - `tracingConfig`: the build infos used for stack trace generation.
/// These are lazily parsed and it's important that they're passed as
/// Uint8 arrays for performance.
/// - `onTestSuiteCompletedCallback`: The progress callback will be called
/// with the results of each test suite as soon as it finished executing.
#[allow(clippy::too_many_arguments)]
#[napi(catch_unwind, ts_return_type = "Promise<SolidityTestResult>")]
pub fn run_solidity_tests(
&self,
env: Env,
chain_type: String,
artifacts: Vec<Artifact>,
test_suites: Vec<ArtifactId>,
config_args: SolidityTestRunnerConfigArgs,
tracing_config: TracingConfigWithBuffers,
#[napi(ts_arg_type = "(result: SuiteResult) => void")]
on_test_suite_completed_callback: JsFunction,
) -> napi::Result<JsObject> {
let (deferred, promise) = env.create_deferred()?;
let on_test_suite_completed_callback: ThreadsafeFunction<_, ErrorStrategy::Fatal> =
match on_test_suite_completed_callback.create_threadsafe_function(
// Unbounded queue size
0,
|ctx: ThreadSafeCallContext<SuiteResult>| Ok(vec![ctx.value]),
) {
Ok(value) => value,
Err(error) => {
deferred.reject(error);
return Ok(promise);
}
};
let test_filter: Arc<TestFilterConfig> =
Arc::new(match config_args.try_get_test_filter() {
Ok(test_filter) => test_filter,
Err(error) => {
deferred.reject(error);
return Ok(promise);
}
});
let runtime = runtime::Handle::current();
let config = match config_args.resolve(&env, runtime.clone()) {
Ok(config) => config,
Err(error) => {
deferred.reject(error);
return Ok(promise);
}
};
let context = self.inner.clone();
runtime.clone().spawn(async move {
macro_rules! try_or_reject_deferred {
($expr:expr) => {
match $expr {
Ok(value) => value,
Err(error) => {
deferred.reject(error);
return;
}
}
};
}
let factory = {
let context = context.lock().await;
try_or_reject_deferred!(context.solidity_test_runner_factory(&chain_type).await)
};
let linking_output =
try_or_reject_deferred!(LinkingOutput::link(&config.project_root, artifacts));
// Build revert decoder from ABIs of all artifacts.
let abis = linking_output
.known_contracts
.iter()
.map(|(_, contract)| &contract.abi);
let revert_decoder = RevertDecoder::new().with_abis(abis);
let test_suites = try_or_reject_deferred!(test_suites
.into_iter()
.map(edr_solidity::artifacts::ArtifactId::try_from)
.collect::<Result<Vec<_>, _>>());
let contracts = try_or_reject_deferred!(test_suites
.iter()
.map(|artifact_id| {
let contract_data = linking_output
.known_contracts
.get(artifact_id)
.ok_or_else(|| {
napi::Error::new(
napi::Status::GenericFailure,
format!("Unknown contract: {}", artifact_id.identifier()),
)
})?;
let bytecode = contract_data.bytecode.clone().ok_or_else(|| {
napi::Error::new(
napi::Status::GenericFailure,
format!(
"No bytecode for test suite contract: {}",
artifact_id.identifier()
),
)
})?;
let test_contract = TestContract {
abi: contract_data.abi.clone(),
bytecode,
};
Ok((artifact_id.clone(), test_contract))
})
.collect::<napi::Result<TestContracts>>());
let include_traces = config.include_traces.into();
let runtime_for_factory = runtime.clone();
let test_runner = try_or_reject_deferred!(runtime
.clone()
.spawn_blocking(move || {
factory.create_test_runner(
runtime_for_factory,
config,
contracts,
linking_output.known_contracts,
linking_output.libs_to_deploy,
revert_decoder,
tracing_config.into(),
)
})
.await
.expect("Failed to join test runner factory thread"));
let runtime_for_runner = runtime.clone();
let test_result = try_or_reject_deferred!(runtime
.clone()
.spawn_blocking(move || {
test_runner.run_tests(
runtime_for_runner,
test_filter,
Arc::new(
move |SuiteResultAndArtifactId {
artifact_id,
result,
}| {
let suite_result =
SuiteResult::new(artifact_id, result, include_traces);
let status = on_test_suite_completed_callback
.call(suite_result, ThreadsafeFunctionCallMode::Blocking);
// This should always succeed since we're using an unbounded queue.
// We add an assertion for
// completeness.
assert_eq!(
status,
napi::Status::Ok,
"Failed to call on_test_suite_completed_callback with status: {status}"
);
},
),
)
})
.await
.expect("Failed to join test runner thread"));
deferred.resolve(move |_env| Ok(SolidityTestResult::from(test_result)));
});
Ok(promise)
}
}
pub struct Context {
provider_factories: HashMap<String, Arc<dyn SyncProviderFactory>>,
solidity_test_runner_factories: HashMap<String, Arc<dyn solidity::SyncTestRunnerFactory>>,
#[cfg(feature = "tracing")]
_tracing_write_guard: tracing_flame::FlushGuard<std::io::BufWriter<std::fs::File>>,
}
impl Context {
/// Creates a new [`Context`] instance. Should only be called once!
pub fn new() -> napi::Result<Self> {
let fmt_layer = tracing_subscriber::fmt::layer()
.with_file(true)
.with_line_number(true)
.with_thread_ids(true)
.with_target(false)
.with_level(true)
.with_filter(EnvFilter::from_default_env());
let subscriber = Registry::default().with(fmt_layer);
#[cfg(feature = "tracing")]
let (flame_layer, guard) = {
let (flame_layer, guard) = tracing_flame::FlameLayer::with_file("tracing.folded")
.map_err(|err| {
napi::Error::new(
napi::Status::GenericFailure,
format!("Failed to create tracing.folded file with error: {err:?}"),
)
})?;
let flame_layer = flame_layer.with_empty_samples(false);
(flame_layer, guard)
};
#[cfg(feature = "tracing")]
let subscriber = subscriber.with(flame_layer);
if let Err(error) = tracing::subscriber::set_global_default(subscriber) {
println!(
"Failed to set global tracing subscriber with error: {error}\n\
Please only initialize EdrContext once per process to avoid this error."
);
}
Ok(Self {
provider_factories: HashMap::default(),
solidity_test_runner_factories: HashMap::default(),
#[cfg(feature = "tracing")]
_tracing_write_guard: guard,
})
}
/// Registers a new provider factory for the provided chain type.
pub fn register_provider_factory(
&mut self,
chain_type: String,
factory: Arc<dyn SyncProviderFactory>,
) {
self.provider_factories.insert(chain_type, factory);
}
pub fn register_solidity_test_runner(
&mut self,
chain_type: String,
factory: Arc<dyn solidity::SyncTestRunnerFactory>,
) {
self.solidity_test_runner_factories
.insert(chain_type, factory);
}
/// Tries to create a new provider for the provided chain type and
/// configuration.
pub fn get_provider_factory(
&self,
chain_type: &str,
) -> napi::Result<Arc<dyn SyncProviderFactory>> {
if let Some(factory) = self.provider_factories.get(chain_type) {
Ok(Arc::clone(factory))
} else {
Err(napi::Error::new(
napi::Status::GenericFailure,
"Provider for provided chain type does not exist",
))
}
}
pub async fn solidity_test_runner_factory(
&self,
chain_type: &str,
) -> napi::Result<Arc<dyn solidity::SyncTestRunnerFactory>> {
if let Some(factory) = self.solidity_test_runner_factories.get(chain_type) {
Ok(Arc::clone(factory))
} else {
Err(napi::Error::new(
napi::Status::GenericFailure,
"Solidity test runner for provided chain type does not exist",
))
}
}
}

View File

@@ -0,0 +1,57 @@
use std::sync::Arc;
use napi_derive::napi;
use crate::config::TracingConfigWithBuffers;
#[napi]
pub struct ContractDecoder {
inner: Arc<edr_solidity::contract_decoder::ContractDecoder>,
}
#[napi]
impl ContractDecoder {
#[doc = "Creates an empty instance."]
#[napi(constructor, catch_unwind)]
// Following TS convention for the constructor without arguments to be `new()`.
#[allow(clippy::new_without_default)]
pub fn new() -> Self {
Self {
inner: Arc::new(edr_solidity::contract_decoder::ContractDecoder::default()),
}
}
#[doc = "Creates a new instance with the provided configuration."]
#[napi(factory, catch_unwind)]
pub fn with_contracts(config: TracingConfigWithBuffers) -> napi::Result<Self> {
let build_info_config = edr_solidity::artifacts::BuildInfoConfig::parse_from_buffers(
(&edr_napi_core::solidity::config::TracingConfigWithBuffers::from(config)).into(),
)
.map_err(|error| napi::Error::from_reason(error.to_string()))?;
let contract_decoder =
edr_solidity::contract_decoder::ContractDecoder::new(&build_info_config).map_or_else(
|error| Err(napi::Error::from_reason(error.to_string())),
|contract_decoder| Ok(Arc::new(contract_decoder)),
)?;
Ok(Self {
inner: contract_decoder,
})
}
}
impl ContractDecoder {
/// Returns a reference to the inner contract decoder.
pub fn as_inner(&self) -> &Arc<edr_solidity::contract_decoder::ContractDecoder> {
&self.inner
}
}
impl From<Arc<edr_solidity::contract_decoder::ContractDecoder>> for ContractDecoder {
fn from(contract_decoder: Arc<edr_solidity::contract_decoder::ContractDecoder>) -> Self {
Self {
inner: contract_decoder,
}
}
}

40
dev/env/node_modules/@nomicfoundation/edr/src/debug_trace.rs generated vendored Executable file
View File

@@ -0,0 +1,40 @@
use std::collections::HashMap;
use napi::bindgen_prelude::{BigInt, Uint8Array};
use napi_derive::napi;
// False positive: imported by HH2
#[allow(dead_code)]
#[napi(object)]
pub struct DebugTraceResult {
pub pass: bool,
pub gas_used: BigInt,
pub output: Option<Uint8Array>,
pub struct_logs: Vec<DebugTraceLogItem>,
}
#[napi(object)]
pub struct DebugTraceLogItem {
/// Program Counter
pub pc: BigInt,
// Op code
pub op: u8,
/// Gas left before executing this operation as hex number.
pub gas: String,
/// Gas cost of this operation as hex number.
pub gas_cost: String,
/// Array of all values (hex numbers) on the stack
pub stack: Option<Vec<String>>,
/// Depth of the call stack
pub depth: BigInt,
/// Size of memory array
pub mem_size: BigInt,
/// Name of the operation
pub op_name: String,
/// Description of an error as a hex string.
pub error: Option<String>,
/// Array of all allocated values as hex strings.
pub memory: Option<Vec<String>>,
/// Map of all stored values with keys and values encoded as hex strings.
pub storage: Option<HashMap<String, String>>,
}

92
dev/env/node_modules/@nomicfoundation/edr/src/gas_report.rs generated vendored Executable file
View File

@@ -0,0 +1,92 @@
use std::collections::HashMap;
use napi::bindgen_prelude::BigInt;
use napi_derive::napi;
#[napi(object)]
pub struct GasReport {
pub contracts: HashMap<String, ContractGasReport>,
}
#[napi(object)]
pub struct ContractGasReport {
pub deployments: Vec<DeploymentGasReport>,
pub functions: HashMap<String, Vec<FunctionGasReport>>,
}
#[napi]
pub enum GasReportExecutionStatus {
Success,
Revert,
Halt,
}
#[napi(object)]
pub struct DeploymentGasReport {
pub gas: BigInt,
pub size: BigInt,
pub status: GasReportExecutionStatus,
}
#[napi(object)]
pub struct FunctionGasReport {
pub gas: BigInt,
pub status: GasReportExecutionStatus,
}
impl From<edr_gas_report::GasReport> for GasReport {
fn from(value: edr_gas_report::GasReport) -> Self {
Self {
contracts: value
.into_inner()
.into_iter()
.map(|(k, v)| (k, v.into()))
.collect(),
}
}
}
impl From<edr_gas_report::ContractGasReport> for ContractGasReport {
fn from(value: edr_gas_report::ContractGasReport) -> Self {
Self {
deployments: value.deployments.into_iter().map(Into::into).collect(),
functions: value
.functions
.into_iter()
.map(|(k, v)| {
let function_reports = v.into_iter().map(FunctionGasReport::from).collect();
(k, function_reports)
})
.collect(),
}
}
}
impl From<edr_gas_report::GasReportExecutionStatus> for GasReportExecutionStatus {
fn from(value: edr_gas_report::GasReportExecutionStatus) -> Self {
match value {
edr_gas_report::GasReportExecutionStatus::Success => Self::Success,
edr_gas_report::GasReportExecutionStatus::Revert => Self::Revert,
edr_gas_report::GasReportExecutionStatus::Halt => Self::Halt,
}
}
}
impl From<edr_gas_report::DeploymentGasReport> for DeploymentGasReport {
fn from(value: edr_gas_report::DeploymentGasReport) -> Self {
Self {
gas: BigInt::from(value.gas),
size: BigInt::from(value.size),
status: value.status.into(),
}
}
}
impl From<edr_gas_report::FunctionGasReport> for FunctionGasReport {
fn from(value: edr_gas_report::FunctionGasReport) -> Self {
Self {
gas: BigInt::from(value.gas),
status: value.status.into(),
}
}
}

115
dev/env/node_modules/@nomicfoundation/edr/src/instrument.rs generated vendored Executable file
View File

@@ -0,0 +1,115 @@
use edr_instrument::coverage::{self, Version};
use napi::bindgen_prelude::Uint8Array;
use napi_derive::napi;
#[napi(object)]
pub struct InstrumentationResult {
/// The generated source code with coverage instrumentation.
#[napi(readonly)]
pub source: String,
/// The metadata for each instrumented code segment.
#[napi(readonly)]
pub metadata: Vec<InstrumentationMetadata>,
}
impl TryFrom<edr_instrument::coverage::InstrumentationResult> for InstrumentationResult {
type Error = usize;
fn try_from(
value: edr_instrument::coverage::InstrumentationResult,
) -> Result<Self, Self::Error> {
let metadata = value
.metadata
.into_iter()
.map(InstrumentationMetadata::try_from)
.collect::<Result<Vec<_>, _>>()?;
Ok(InstrumentationResult {
source: value.source,
metadata,
})
}
}
#[napi(object)]
pub struct InstrumentationMetadata {
/// The tag that identifies the instrumented code. Tags are
/// deterministically generated from the source code, source id, and
/// Solidity version.
#[napi(readonly)]
pub tag: Uint8Array,
/// The kind of instrumented code. Currently, the only supported kind
/// is "statement".
#[napi(readonly)]
pub kind: String,
/// The starting position of the instrumented code - including trivia such
/// as whitespace - in the source code, in UTF-16 code units.
#[napi(readonly)]
pub start_utf16: i64,
/// The ending position of the instrumented code - including trivia such as
/// whitespace - in the source code, in UTF-16 code units.
#[napi(readonly)]
pub end_utf16: i64,
}
impl TryFrom<edr_instrument::coverage::InstrumentationMetadata> for InstrumentationMetadata {
type Error = usize;
fn try_from(
value: edr_instrument::coverage::InstrumentationMetadata,
) -> Result<Self, Self::Error> {
let start_utf16 = value
.start_utf16
.try_into()
.map_err(|_error| value.start_utf16)?;
let end_utf16 = value
.end_utf16
.try_into()
.map_err(|_error| value.end_utf16)?;
Ok(InstrumentationMetadata {
tag: Uint8Array::with_data_copied(value.tag),
kind: value.kind.to_owned(),
start_utf16,
end_utf16,
})
}
}
/// Adds per-statement coverage instrumentation to the given Solidity source
/// code.
#[napi(catch_unwind)]
pub fn add_statement_coverage_instrumentation(
source_code: String,
source_id: String,
solidity_version: String,
coverage_library_path: String,
) -> napi::Result<InstrumentationResult> {
let solidity_version = Version::parse(&solidity_version).map_err(|error| {
napi::Error::new(
napi::Status::InvalidArg,
format!("Invalid Solidity version: {error}"),
)
})?;
let instrumented = coverage::instrument_code(
&source_code,
&source_id,
solidity_version,
&coverage_library_path,
)
.map_err(|error| napi::Error::new(napi::Status::GenericFailure, error))?;
instrumented.try_into().map_err(|location| {
napi::Error::new(
napi::Status::GenericFailure,
format!("Cannot represent source locations in JavaScript: {location}."),
)
})
}
/// Retrieves the latest version of `Solidity` supported for instrumentation.
#[napi(catch_unwind)]
pub fn latest_supported_solidity_version() -> String {
edr_instrument::LATEST_SUPPORTED_SOLIDITY_VERSION.to_string()
}

50
dev/env/node_modules/@nomicfoundation/edr/src/lib.rs generated vendored Executable file
View File

@@ -0,0 +1,50 @@
// #![warn(missing_docs)]
//! NAPI bindings for EDR's core types.
#[global_allocator]
static ALLOC: mimalloc::MiMalloc = mimalloc::MiMalloc;
mod account;
mod block;
/// Types for overriding a call.
pub mod call_override;
/// Types for casting N-API types to Rust types.
pub mod cast;
/// Supported chain types.
pub mod chains;
/// Types for configuration.
pub mod config;
/// Types related to an EDR N-API context.
pub mod context;
/// Types for decoding smart contract data.
pub mod contract_decoder;
mod debug_trace;
pub mod gas_report;
/// Types and functions related to code coverage instrumentation.
pub mod instrument;
/// Types for EVM execution logs.
pub mod log;
/// Types for an RPC request logger.
pub mod logger;
/// Types for mocking provider behavior.
#[cfg(feature = "test-mock")]
pub mod mock;
/// Types for precompiles.
pub mod precompile;
/// Types for Ethereum RPC providers.
pub mod provider;
/// Types for EVM execution results.
pub mod result;
/// Types relating to benchmark scenarios.
#[cfg(feature = "scenarios")]
pub mod scenarios;
mod serde;
/// Solidity test runner.
pub mod solidity_tests;
/// Types for subscribing to events.
pub mod subscription;
/// Types for EVM traces.
pub mod trace;
/// Types related to Ethereum withdrawals.
pub mod withdrawal;

28
dev/env/node_modules/@nomicfoundation/edr/src/log.rs generated vendored Executable file
View File

@@ -0,0 +1,28 @@
use napi::bindgen_prelude::Uint8Array;
use napi_derive::napi;
/// Ethereum execution log.
#[napi(object)]
pub struct ExecutionLog {
pub address: Uint8Array,
pub topics: Vec<Uint8Array>,
pub data: Uint8Array,
}
impl From<&edr_receipt::log::ExecutionLog> for ExecutionLog {
fn from(value: &edr_receipt::log::ExecutionLog) -> Self {
let topics = value
.topics()
.iter()
.map(Uint8Array::with_data_copied)
.collect();
let data = Uint8Array::with_data_copied(&value.data.data);
Self {
address: Uint8Array::with_data_copied(value.address),
topics,
data,
}
}
}

120
dev/env/node_modules/@nomicfoundation/edr/src/logger.rs generated vendored Executable file
View File

@@ -0,0 +1,120 @@
use std::sync::{mpsc::channel, Arc};
use edr_napi_core::logger::LoggerError;
use edr_primitives::Bytes;
use napi::{
threadsafe_function::{
ErrorStrategy, ThreadSafeCallContext, ThreadsafeFunction, ThreadsafeFunctionCallMode,
},
JsFunction, Status,
};
use napi_derive::napi;
#[napi(object)]
pub struct LoggerConfig {
/// Whether to enable the logger.
pub enable: bool,
#[napi(ts_type = "(inputs: ArrayBuffer[]) => string[]")]
pub decode_console_log_inputs_callback: JsFunction,
#[napi(ts_type = "(message: string, replace: boolean) => void")]
pub print_line_callback: JsFunction,
}
impl LoggerConfig {
/// Resolves the logger config, converting it to a
/// `edr_napi_core::logger::Config`.
pub fn resolve(self, env: &napi::Env) -> napi::Result<edr_napi_core::logger::Config> {
let mut decode_console_log_inputs_callback: ThreadsafeFunction<_, ErrorStrategy::Fatal> =
self.decode_console_log_inputs_callback
.create_threadsafe_function(0, |ctx: ThreadSafeCallContext<Vec<Bytes>>| {
let inputs = ctx.env.create_array_with_length(ctx.value.len()).and_then(
|mut inputs| {
for (idx, input) in ctx.value.into_iter().enumerate() {
ctx.env
.create_arraybuffer_with_data(input.to_vec())
.and_then(|input| {
inputs.set_element(idx as u32, input.into_raw())
})?;
}
Ok(inputs)
},
)?;
Ok(vec![inputs])
})?;
// Maintain a weak reference to the function to avoid blocking the event loop
// from exiting.
decode_console_log_inputs_callback.unref(env)?;
let decode_console_log_inputs_fn = Arc::new(move |console_log_inputs| {
let (sender, receiver) = channel();
let status = decode_console_log_inputs_callback.call_with_return_value(
console_log_inputs,
ThreadsafeFunctionCallMode::Blocking,
move |decoded_inputs: Vec<String>| {
sender.send(decoded_inputs).map_err(|_error| {
napi::Error::new(
Status::GenericFailure,
"Failed to send result from decode_console_log_inputs",
)
})
},
);
assert_eq!(status, Status::Ok);
receiver
.recv()
.expect("Receive can only fail if the channel is closed")
});
let mut print_line_callback: ThreadsafeFunction<_, ErrorStrategy::Fatal> = self
.print_line_callback
.create_threadsafe_function(0, |ctx: ThreadSafeCallContext<(String, bool)>| {
// String
let message = ctx.env.create_string_from_std(ctx.value.0)?;
// bool
let replace = ctx.env.get_boolean(ctx.value.1)?;
Ok(vec![message.into_unknown(), replace.into_unknown()])
})?;
// Maintain a weak reference to the function to avoid blocking the event loop
// from exiting.
print_line_callback.unref(env)?;
let print_line_fn = Arc::new(move |message, replace| {
let (sender, receiver) = channel();
let status = print_line_callback.call_with_return_value(
(message, replace),
ThreadsafeFunctionCallMode::Blocking,
move |()| {
sender.send(()).map_err(|_error| {
napi::Error::new(
Status::GenericFailure,
"Failed to send result from decode_console_log_inputs",
)
})
},
);
let () = receiver.recv().unwrap();
if status == napi::Status::Ok {
Ok(())
} else {
Err(LoggerError::PrintLine)
}
});
Ok(edr_napi_core::logger::Config {
enable: self.enable,
decode_console_log_inputs_fn,
print_line_fn,
})
}
}

71
dev/env/node_modules/@nomicfoundation/edr/src/mock.rs generated vendored Executable file
View File

@@ -0,0 +1,71 @@
pub mod time;
use std::sync::Arc;
use edr_chain_spec::EvmHaltReason;
use edr_napi_core::provider::SyncProvider;
use edr_rpc_client::jsonrpc;
use edr_solidity::contract_decoder::ContractDecoder;
use napi::tokio::runtime;
use napi_derive::napi;
use crate::{context::EdrContext, provider::Provider};
/// A mock provider that always returns the given mocked response.
pub struct MockProvider {
mocked_response: serde_json::Value,
}
impl MockProvider {
pub fn new(mocked_response: serde_json::Value) -> Self {
Self { mocked_response }
}
}
impl SyncProvider for MockProvider {
fn handle_request(
&self,
_request: String,
_contract_decoder: Arc<ContractDecoder>,
) -> napi::Result<edr_napi_core::spec::Response<EvmHaltReason>> {
let response = jsonrpc::ResponseData::Success {
result: self.mocked_response.clone(),
};
edr_napi_core::spec::marshal_response_data(response)
.map(|data| edr_napi_core::spec::Response {
solidity_trace: None,
data,
traces: Vec::new(),
})
.map_err(|error| napi::Error::new(napi::Status::GenericFailure, error.to_string()))
}
fn set_call_override_callback(
&self,
_call_override_callback: Arc<dyn edr_provider::SyncCallOverride>,
) {
}
fn set_verbose_tracing(&self, _enabled: bool) {}
}
#[napi]
impl EdrContext {
#[doc = "Creates a mock provider, which always returns the given response."]
#[doc = "For testing purposes."]
#[napi]
pub fn create_mock_provider(
&self,
mocked_response: serde_json::Value,
) -> napi::Result<Provider> {
let provider = Provider::new(
Arc::new(MockProvider::new(mocked_response)),
runtime::Handle::current(),
Arc::new(ContractDecoder::default()),
#[cfg(feature = "scenarios")]
None,
);
Ok(provider)
}
}

134
dev/env/node_modules/@nomicfoundation/edr/src/mock/time.rs generated vendored Executable file
View File

@@ -0,0 +1,134 @@
use std::sync::Arc;
use edr_chain_spec::ChainSpec;
use edr_chain_spec_block::BlockChainSpec;
use edr_chain_spec_rpc::RpcBlockChainSpec;
use edr_generic::GenericChainSpec;
use edr_napi_core::logger::Logger;
use edr_primitives::B256;
use napi::{bindgen_prelude::BigInt, tokio::runtime, Env, JsObject};
use napi_derive::napi;
use crate::{
cast::TryCast as _,
config::{resolve_configs, ConfigResolution, ProviderConfig},
contract_decoder::ContractDecoder,
logger::LoggerConfig,
provider::Provider,
subscription::SubscriptionConfig,
};
#[napi]
pub struct MockTime {
inner: Arc<edr_provider::time::MockTime>,
}
#[napi]
impl MockTime {
#[doc = "Creates a new instance of `MockTime` with the current time."]
#[napi(factory, catch_unwind)]
pub fn now() -> Self {
Self {
inner: Arc::new(edr_provider::time::MockTime::now()),
}
}
#[doc = "Adds the specified number of seconds to the current time."]
#[napi(catch_unwind)]
pub fn add_seconds(&self, seconds: BigInt) -> napi::Result<()> {
let seconds = seconds.try_cast()?;
self.inner.add_seconds(seconds);
Ok(())
}
}
#[doc = "Creates a provider with a mock timer."]
#[doc = "For testing purposes."]
#[napi(catch_unwind, ts_return_type = "Promise<Provider>")]
pub fn create_provider_with_mock_timer(
env: Env,
provider_config: ProviderConfig,
logger_config: LoggerConfig,
subscription_config: SubscriptionConfig,
contract_decoder: &ContractDecoder,
time: &MockTime,
) -> napi::Result<JsObject> {
let (deferred, promise) = env.create_deferred()?;
macro_rules! try_or_reject_promise {
($expr:expr) => {
match $expr {
Ok(value) => value,
Err(error) => {
deferred.reject(error);
return Ok(promise);
}
}
};
}
let runtime = runtime::Handle::current();
let ConfigResolution {
logger_config,
provider_config,
subscription_callback,
} = try_or_reject_promise!(resolve_configs(
&env,
runtime.clone(),
provider_config,
logger_config,
subscription_config,
));
let contract_decoder = Arc::clone(contract_decoder.as_inner());
let timer = Arc::clone(&time.inner);
runtime.clone().spawn_blocking(move || {
// Using a closure to limit the scope, allowing us to use `?` for error
// handling. This is necessary because the result of the closure is used
// to resolve the deferred promise.
let create_provider = move || -> napi::Result<Provider> {
let logger = Logger::<GenericChainSpec, Arc<edr_provider::time::MockTime>>::new(
logger_config,
Arc::clone(&contract_decoder),
)?;
let provider_config =
edr_provider::ProviderConfig::<edr_chain_l1::Hardfork>::try_from(provider_config)?;
let provider =
edr_provider::Provider::<GenericChainSpec, Arc<edr_provider::time::MockTime>>::new(
runtime.clone(),
Box::new(logger),
Box::new(move |event| {
let event = edr_napi_core::subscription::SubscriptionEvent::new::<
<GenericChainSpec as BlockChainSpec>::Block,
<GenericChainSpec as RpcBlockChainSpec>::RpcBlock<B256>,
<GenericChainSpec as ChainSpec>::SignedTransaction,
>(event);
subscription_callback.call(event);
}),
provider_config,
Arc::clone(&contract_decoder),
timer,
)
.map_err(|error| napi::Error::from_reason(error.to_string()))?;
Ok(Provider::new(
Arc::new(provider),
runtime,
contract_decoder,
#[cfg(feature = "scenarios")]
None,
))
};
let result = create_provider();
deferred.resolve(|_env| result);
});
Ok(promise)
}

50
dev/env/node_modules/@nomicfoundation/edr/src/precompile.rs generated vendored Executable file
View File

@@ -0,0 +1,50 @@
use edr_precompile::PrecompileFn;
use edr_primitives::Address;
use napi::bindgen_prelude::Uint8Array;
use napi_derive::napi;
#[napi]
#[derive(Clone)]
pub struct Precompile {
address: Address,
precompile_fn: PrecompileFn,
}
impl Precompile {
pub fn new(address: Address, precompile_fn: PrecompileFn) -> Self {
Self {
address,
precompile_fn,
}
}
/// Returns the address and precompile function as a tuple.
pub fn to_tuple(&self) -> (Address, PrecompileFn) {
(self.address, self.precompile_fn)
}
}
impl From<edr_precompile::Precompile> for Precompile {
fn from(value: edr_precompile::Precompile) -> Self {
Self {
address: *value.address(),
precompile_fn: value.into_precompile(),
}
}
}
#[napi]
impl Precompile {
/// Returns the address of the precompile.
#[napi(catch_unwind, getter)]
pub fn address(&self) -> Uint8Array {
Uint8Array::with_data_copied(self.address)
}
}
/// [RIP-7212](https://github.com/ethereum/RIPs/blob/master/RIPS/rip-7212.md#specification)
/// secp256r1 precompile.
#[napi(catch_unwind)]
pub fn precompile_p256_verify() -> Precompile {
Precompile::from(edr_precompile::secp256r1::P256VERIFY)
}

162
dev/env/node_modules/@nomicfoundation/edr/src/provider.rs generated vendored Executable file
View File

@@ -0,0 +1,162 @@
/// Types related to provider factories.
pub mod factory;
mod response;
use std::sync::Arc;
use edr_napi_core::provider::SyncProvider;
use edr_solidity::compiler::create_models_and_decode_bytecodes;
use napi::{tokio::runtime, Env, JsFunction, JsObject, Status};
use napi_derive::napi;
pub use self::factory::ProviderFactory;
use self::response::Response;
use crate::{call_override::CallOverrideCallback, contract_decoder::ContractDecoder};
/// A JSON-RPC provider for Ethereum.
#[napi]
pub struct Provider {
contract_decoder: Arc<edr_solidity::contract_decoder::ContractDecoder>,
provider: Arc<dyn SyncProvider>,
runtime: runtime::Handle,
#[cfg(feature = "scenarios")]
scenario_file: Option<napi::tokio::sync::Mutex<napi::tokio::fs::File>>,
}
impl Provider {
/// Constructs a new instance.
pub fn new(
provider: Arc<dyn SyncProvider>,
runtime: runtime::Handle,
contract_decoder: Arc<edr_solidity::contract_decoder::ContractDecoder>,
#[cfg(feature = "scenarios")] scenario_file: Option<
napi::tokio::sync::Mutex<napi::tokio::fs::File>,
>,
) -> Self {
Self {
contract_decoder,
provider,
runtime,
#[cfg(feature = "scenarios")]
scenario_file,
}
}
}
#[napi]
impl Provider {
#[doc = "Adds a compilation result to the instance."]
#[doc = ""]
#[doc = "For internal use only. Support for this method may be removed in the future."]
#[napi(catch_unwind)]
pub async fn add_compilation_result(
&self,
solc_version: String,
compiler_input: serde_json::Value,
compiler_output: serde_json::Value,
) -> napi::Result<()> {
let contract_decoder = self.contract_decoder.clone();
self.runtime
.spawn_blocking(move || {
let compiler_input = serde_json::from_value(compiler_input)
.map_err(|error| napi::Error::from_reason(error.to_string()))?;
let compiler_output = serde_json::from_value(compiler_output)
.map_err(|error| napi::Error::from_reason(error.to_string()))?;
let contracts = match create_models_and_decode_bytecodes(
solc_version,
&compiler_input,
&compiler_output,
) {
Ok(contracts) => contracts,
Err(error) => {
return Err(napi::Error::from_reason(format!("Contract decoder failed to be updated. Please report this to help us improve Hardhat.\n{error}")));
}
};
for contract in contracts {
contract_decoder.add_contract_metadata(contract);
}
Ok(())
})
.await
.map_err(|error| napi::Error::new(Status::GenericFailure, error.to_string()))?
}
#[doc = "Retrieves the instance's contract decoder."]
#[napi(catch_unwind)]
pub fn contract_decoder(&self) -> ContractDecoder {
ContractDecoder::from(Arc::clone(&self.contract_decoder))
}
#[doc = "Handles a JSON-RPC request and returns a JSON-RPC response."]
#[napi(catch_unwind)]
pub async fn handle_request(&self, request: String) -> napi::Result<Response> {
let provider = self.provider.clone();
#[cfg(feature = "scenarios")]
if let Some(scenario_file) = &self.scenario_file {
crate::scenarios::write_request(scenario_file, &request).await?;
}
let contract_decoder = Arc::clone(&self.contract_decoder);
self.runtime
.spawn_blocking(move || provider.handle_request(request, contract_decoder))
.await
.map_err(|error| napi::Error::new(Status::GenericFailure, error.to_string()))?
.map(Response::from)
}
#[napi(catch_unwind, ts_return_type = "Promise<void>")]
pub fn set_call_override_callback(
&self,
env: Env,
#[napi(
ts_arg_type = "(contract_address: ArrayBuffer, data: ArrayBuffer) => Promise<CallOverrideResult | undefined>"
)]
call_override_callback: JsFunction,
) -> napi::Result<JsObject> {
let (deferred, promise) = env.create_deferred()?;
let call_override_callback =
match CallOverrideCallback::new(&env, call_override_callback, self.runtime.clone()) {
Ok(callback) => callback,
Err(error) => {
deferred.reject(error);
return Ok(promise);
}
};
let call_override_callback =
Arc::new(move |address, data| call_override_callback.call_override(address, data));
let provider = self.provider.clone();
self.runtime.spawn_blocking(move || {
provider.set_call_override_callback(call_override_callback);
deferred.resolve(|_env| Ok(()));
});
Ok(promise)
}
/// Set to `true` to make the traces returned with `eth_call`,
/// `eth_estimateGas`, `eth_sendRawTransaction`, `eth_sendTransaction`,
/// `evm_mine`, `hardhat_mine` include the full stack and memory. Set to
/// `false` to disable this.
#[napi(catch_unwind)]
pub async fn set_verbose_tracing(&self, verbose_tracing: bool) -> napi::Result<()> {
let provider = self.provider.clone();
self.runtime
.spawn_blocking(move || {
provider.set_verbose_tracing(verbose_tracing);
})
.await
.map_err(|error| napi::Error::new(Status::GenericFailure, error.to_string()))
}
}

View File

@@ -0,0 +1,22 @@
use std::sync::Arc;
use edr_napi_core::provider::SyncProviderFactory;
use napi_derive::napi;
#[napi]
pub struct ProviderFactory {
inner: Arc<dyn SyncProviderFactory>,
}
impl ProviderFactory {
/// Returns a reference to the inner provider factory.
pub fn as_inner(&self) -> &Arc<dyn SyncProviderFactory> {
&self.inner
}
}
impl From<Arc<dyn SyncProviderFactory>> for ProviderFactory {
fn from(inner: Arc<dyn SyncProviderFactory>) -> Self {
Self { inner }
}
}

View File

@@ -0,0 +1,73 @@
use edr_chain_spec::EvmHaltReason;
use edr_napi_core::spec::SolidityTraceData;
use edr_solidity::contract_decoder::NestedTraceDecoder as _;
use napi::Either;
use napi_derive::napi;
use crate::{
cast::TryCast,
trace::{solidity_stack_trace::SolidityStackTrace, RawTrace},
};
#[napi]
pub struct Response {
inner: edr_napi_core::spec::Response<EvmHaltReason>,
}
impl From<edr_napi_core::spec::Response<EvmHaltReason>> for Response {
fn from(value: edr_napi_core::spec::Response<EvmHaltReason>) -> Self {
Self { inner: value }
}
}
#[napi]
impl Response {
#[doc = "Returns the response data as a JSON string or a JSON object."]
#[napi(catch_unwind, getter)]
pub fn data(&self) -> Either<String, serde_json::Value> {
self.inner.data.clone()
}
// Rust port of https://github.com/NomicFoundation/hardhat/blob/c20bf195a6efdc2d74e778b7a4a7799aac224841/packages/hardhat-core/src/internal/hardhat-network/provider/provider.ts#L590
#[doc = "Compute the error stack trace. Return the stack trace if it can be decoded, otherwise returns none. Throws if there was an error computing the stack trace."]
#[napi(catch_unwind)]
pub fn stack_trace(&self) -> napi::Result<Option<SolidityStackTrace>> {
let Some(SolidityTraceData {
trace,
contract_decoder,
}) = &self.inner.solidity_trace
else {
return Ok(None);
};
let nested_trace = edr_solidity::nested_tracer::convert_trace_messages_to_nested_trace(
trace.as_ref().clone(),
)
.map_err(|err| napi::Error::from_reason(err.to_string()))?;
if let Some(vm_trace) = nested_trace {
let decoded_trace = contract_decoder
.try_to_decode_nested_trace(vm_trace)
.map_err(|err| napi::Error::from_reason(err.to_string()))?;
let stack_trace = edr_solidity::solidity_tracer::get_stack_trace(decoded_trace)
.map_err(|err| napi::Error::from_reason(err.to_string()))?;
let stack_trace = stack_trace
.into_iter()
.map(TryCast::try_cast)
.collect::<Result<Vec<_>, _>>()?;
Ok(Some(stack_trace))
} else {
Ok(None)
}
}
#[doc = "Returns the raw traces of executed contracts. This maybe contain zero or more traces."]
#[napi(catch_unwind, getter)]
pub fn traces(&self) -> Vec<RawTrace> {
self.inner
.traces
.iter()
.map(|trace| RawTrace::from(trace.clone()))
.collect()
}
}

216
dev/env/node_modules/@nomicfoundation/edr/src/result.rs generated vendored Executable file
View File

@@ -0,0 +1,216 @@
use edr_chain_spec::EvmHaltReason;
use edr_tracing::AfterMessage;
use napi::{
bindgen_prelude::{BigInt, Either3, Uint8Array},
Either,
};
use napi_derive::napi;
use crate::log::ExecutionLog;
/// The possible reasons for successful termination of the EVM.
#[napi]
pub enum SuccessReason {
/// The opcode `STOP` was called
Stop,
/// The opcode `RETURN` was called
Return,
/// The opcode `SELFDESTRUCT` was called
SelfDestruct,
}
impl From<edr_chain_spec_evm::result::SuccessReason> for SuccessReason {
fn from(eval: edr_chain_spec_evm::result::SuccessReason) -> Self {
match eval {
edr_chain_spec_evm::result::SuccessReason::Stop => Self::Stop,
edr_chain_spec_evm::result::SuccessReason::Return => Self::Return,
edr_chain_spec_evm::result::SuccessReason::SelfDestruct => Self::SelfDestruct,
}
}
}
impl From<SuccessReason> for edr_chain_spec_evm::result::SuccessReason {
fn from(value: SuccessReason) -> Self {
match value {
SuccessReason::Stop => Self::Stop,
SuccessReason::Return => Self::Return,
SuccessReason::SelfDestruct => Self::SelfDestruct,
}
}
}
#[napi(object)]
pub struct CallOutput {
/// Return value
pub return_value: Uint8Array,
}
#[napi(object)]
pub struct CreateOutput {
/// Return value
pub return_value: Uint8Array,
/// Optionally, a 160-bit address
pub address: Option<Uint8Array>,
}
/// The result when the EVM terminates successfully.
#[napi(object)]
pub struct SuccessResult {
/// The reason for termination
pub reason: SuccessReason,
/// The amount of gas used
pub gas_used: BigInt,
/// The amount of gas refunded
pub gas_refunded: BigInt,
/// The logs
pub logs: Vec<ExecutionLog>,
/// The transaction output
pub output: Either<CallOutput, CreateOutput>,
}
/// The result when the EVM terminates due to a revert.
#[napi(object)]
pub struct RevertResult {
/// The amount of gas used
pub gas_used: BigInt,
/// The transaction output
pub output: Uint8Array,
}
/// Indicates that the EVM has experienced an exceptional halt. This causes
/// execution to immediately end with all gas being consumed.
#[napi]
pub enum ExceptionalHalt {
OutOfGas,
OpcodeNotFound,
InvalidFEOpcode,
InvalidJump,
NotActivated,
StackUnderflow,
StackOverflow,
OutOfOffset,
CreateCollision,
PrecompileError,
NonceOverflow,
/// Create init code size exceeds limit (runtime).
CreateContractSizeLimit,
/// Error on created contract that begins with EF
CreateContractStartingWithEF,
/// EIP-3860: Limit and meter initcode. Initcode size limit exceeded.
CreateInitCodeSizeLimit,
}
impl From<EvmHaltReason> for ExceptionalHalt {
fn from(halt: EvmHaltReason) -> Self {
match halt {
EvmHaltReason::OutOfGas(..) => ExceptionalHalt::OutOfGas,
EvmHaltReason::OpcodeNotFound => ExceptionalHalt::OpcodeNotFound,
EvmHaltReason::InvalidFEOpcode => ExceptionalHalt::InvalidFEOpcode,
EvmHaltReason::InvalidJump => ExceptionalHalt::InvalidJump,
EvmHaltReason::NotActivated => ExceptionalHalt::NotActivated,
EvmHaltReason::StackUnderflow => ExceptionalHalt::StackUnderflow,
EvmHaltReason::StackOverflow => ExceptionalHalt::StackOverflow,
EvmHaltReason::OutOfOffset => ExceptionalHalt::OutOfOffset,
EvmHaltReason::CreateCollision => ExceptionalHalt::CreateCollision,
EvmHaltReason::PrecompileError | EvmHaltReason::PrecompileErrorWithContext(_) => {
ExceptionalHalt::PrecompileError
}
EvmHaltReason::NonceOverflow => ExceptionalHalt::NonceOverflow,
EvmHaltReason::CreateContractSizeLimit => ExceptionalHalt::CreateContractSizeLimit,
EvmHaltReason::CreateContractStartingWithEF => {
ExceptionalHalt::CreateContractStartingWithEF
}
EvmHaltReason::CreateInitCodeSizeLimit => ExceptionalHalt::CreateInitCodeSizeLimit,
EvmHaltReason::OverflowPayment
| EvmHaltReason::StateChangeDuringStaticCall
| EvmHaltReason::CallNotAllowedInsideStatic
| EvmHaltReason::OutOfFunds
| EvmHaltReason::CallTooDeep => {
unreachable!("Internal halts that can be only found inside Inspector: {halt:?}")
}
}
}
}
/// The result when the EVM terminates due to an exceptional halt.
#[napi(object)]
pub struct HaltResult {
/// The exceptional halt that occurred
pub reason: ExceptionalHalt,
/// Halting will spend all the gas and will thus be equal to the specified
/// gas limit
pub gas_used: BigInt,
}
/// The result of executing a transaction.
#[napi(object)]
pub struct ExecutionResult {
/// The transaction result
pub result: Either3<SuccessResult, RevertResult, HaltResult>,
/// Optional contract address if the transaction created a new contract.
pub contract_address: Option<Uint8Array>,
}
impl From<&AfterMessage<EvmHaltReason>> for ExecutionResult {
fn from(value: &AfterMessage<EvmHaltReason>) -> Self {
let AfterMessage {
execution_result,
contract_address,
} = value;
let result = match execution_result {
edr_chain_spec_evm::result::ExecutionResult::Success {
reason,
gas_used,
gas_refunded,
logs,
output,
} => {
let logs = logs.iter().map(ExecutionLog::from).collect();
Either3::A(SuccessResult {
reason: SuccessReason::from(*reason),
gas_used: BigInt::from(*gas_used),
gas_refunded: BigInt::from(*gas_refunded),
logs,
output: match output {
edr_chain_spec_evm::result::Output::Call(return_value) => {
let return_value = Uint8Array::with_data_copied(return_value);
Either::A(CallOutput { return_value })
}
edr_chain_spec_evm::result::Output::Create(return_value, address) => {
let return_value = Uint8Array::with_data_copied(return_value);
Either::B(CreateOutput {
return_value,
address: address.as_ref().map(Uint8Array::with_data_copied),
})
}
},
})
}
edr_chain_spec_evm::result::ExecutionResult::Revert { gas_used, output } => {
let output = Uint8Array::with_data_copied(output);
Either3::B(RevertResult {
gas_used: BigInt::from(*gas_used),
output,
})
}
edr_chain_spec_evm::result::ExecutionResult::Halt { reason, gas_used } => {
Either3::C(HaltResult {
reason: ExceptionalHalt::from(reason.clone()),
gas_used: BigInt::from(*gas_used),
})
}
};
let contract_address = contract_address.as_ref().map(Uint8Array::with_data_copied);
Self {
result,
contract_address,
}
}
}

53
dev/env/node_modules/@nomicfoundation/edr/src/scenarios.rs generated vendored Executable file
View File

@@ -0,0 +1,53 @@
use std::time::{SystemTime, UNIX_EPOCH};
use edr_scenarios::ScenarioConfig;
use napi::tokio::{fs::File, io::AsyncWriteExt, sync::Mutex};
use rand::{distributions::Alphanumeric, Rng};
const SCENARIO_FILE_PREFIX: &str = "EDR_SCENARIO_PREFIX";
/// Creates a scenario file with the provided configuration.
pub async fn scenario_file(
chain_type: String,
provider_config: edr_napi_core::provider::Config,
logger_enabled: bool,
) -> napi::Result<Option<Mutex<File>>> {
if let Ok(scenario_prefix) = std::env::var(SCENARIO_FILE_PREFIX) {
let timestamp = SystemTime::now()
.duration_since(UNIX_EPOCH)
.expect("Time went backwards")
.as_secs();
let suffix = rand::thread_rng()
.sample_iter(&Alphanumeric)
.take(4)
.map(char::from)
.collect::<String>();
let mut scenario_file =
File::create(format!("{scenario_prefix}_{timestamp}_{suffix}.json")).await?;
let config = ScenarioConfig {
chain_type: Some(chain_type),
logger_enabled,
provider_config: provider_config.try_into()?,
};
let mut line = serde_json::to_string(&config)?;
line.push('\n');
scenario_file.write_all(line.as_bytes()).await?;
Ok(Some(Mutex::new(scenario_file)))
} else {
Ok(None)
}
}
/// Writes a JSON-RPC request to the scenario file.
pub async fn write_request(scenario_file: &Mutex<File>, request: &str) -> napi::Result<()> {
let mut line = request.to_string();
line.push('\n');
{
let mut scenario_file = scenario_file.lock().await;
scenario_file.write_all(line.as_bytes()).await?;
}
Ok(())
}

57
dev/env/node_modules/@nomicfoundation/edr/src/serde.rs generated vendored Executable file
View File

@@ -0,0 +1,57 @@
use edr_primitives::hex;
use napi::bindgen_prelude::{BigInt, Uint8Array};
use serde::Serializer;
/// Serialize a `Uint8Array` as a 0x-prefixed hex string
pub fn serialize_uint8array_as_hex<S>(buffer: &Uint8Array, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let hex_string = format!("0x{}", hex::encode(buffer));
serializer.serialize_str(&hex_string)
}
/// Serialize an Option<Uint8Array> as a 0x-prefixed hex string or None
pub fn serialize_optional_uint8array_as_hex<S>(
buffer: &Option<Uint8Array>,
serializer: S,
) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
match buffer {
Some(buf) => {
let hex_string = format!("0x{}", hex::encode(buf));
serializer.serialize_str(&hex_string)
}
None => serializer.serialize_none(),
}
}
/// Serialize a `BigInt` as a struct with `sign_bit` and `words` fields
pub fn serialize_bigint_as_struct<S>(value: &BigInt, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
use serde::ser::SerializeStruct;
let mut state = serializer.serialize_struct("BigInt", 2)?;
state.serialize_field("sign_bit", &value.sign_bit)?;
state.serialize_field("words", &value.words)?;
state.end()
}
/// Serialize an Option<BigInt> as a struct with `sign_bit` and `words` fields
/// or None
pub fn serialize_optional_bigint_as_struct<S>(
value: &Option<BigInt>,
serializer: S,
) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
match value {
Some(val) => serialize_bigint_as_struct(val, serializer),
None => serializer.serialize_none(),
}
}

View File

@@ -0,0 +1,57 @@
pub mod artifact;
pub mod cheatcode_errors;
pub mod config;
pub mod factory;
pub mod l1;
#[cfg(feature = "op")]
pub mod op;
pub mod runner;
pub mod test_results;
use std::path::Path;
use edr_primitives::Bytes;
use edr_solidity::linker::{LinkOutput, Linker};
use edr_solidity_tests::{constants::LIBRARY_DEPLOYER, contracts::ContractsByArtifact};
use foundry_compilers::artifacts::Libraries;
use crate::solidity_tests::artifact::Artifact;
pub(crate) struct LinkingOutput {
pub libs_to_deploy: Vec<Bytes>,
pub known_contracts: ContractsByArtifact,
}
impl LinkingOutput {
pub fn link(project_root: &Path, artifacts: Vec<Artifact>) -> napi::Result<Self> {
let artifact_contracts = artifacts
.into_iter()
.map(|artifact| Ok((artifact.id.try_into()?, artifact.contract.try_into()?)))
.collect::<napi::Result<Vec<_>>>()?;
let linker = Linker::new(project_root, artifact_contracts);
let LinkOutput {
libraries,
libs_to_deploy,
} = linker
.link_with_nonce_or_address(
Libraries::default(),
LIBRARY_DEPLOYER,
0,
linker.contracts.keys(),
)
.map_err(|error| napi::Error::from_reason(error.to_string()))?;
let linked_contracts = linker
.get_linked_artifacts(&libraries)
.map_err(|error| napi::Error::from_reason(error.to_string()))?;
let known_contracts = ContractsByArtifact::new(linked_contracts);
Ok(LinkingOutput {
libs_to_deploy,
known_contracts,
})
}
}

View File

@@ -0,0 +1,184 @@
use std::{
borrow::Cow,
collections::{BTreeMap, HashMap},
};
use napi_derive::napi;
/// A compilation artifact.
#[derive(Clone, Debug)]
#[napi(object)]
pub struct Artifact {
/// The identifier of the artifact.
pub id: ArtifactId,
/// The test contract.
pub contract: ContractData,
}
/// The identifier of a Solidity contract.
#[derive(Clone, Debug, serde::Serialize)]
#[napi(object)]
pub struct ArtifactId {
/// The name of the contract.
pub name: String,
/// Original source file path.
pub source: String,
/// The solc semver string.
pub solc_version: String,
}
impl From<edr_solidity::artifacts::ArtifactId> for ArtifactId {
fn from(value: edr_solidity::artifacts::ArtifactId) -> Self {
Self {
name: value.name,
source: value.source.to_string_lossy().to_string(),
solc_version: value.version.to_string(),
}
}
}
impl TryFrom<ArtifactId> for edr_solidity::artifacts::ArtifactId {
type Error = napi::Error;
fn try_from(value: ArtifactId) -> napi::Result<Self> {
Ok(edr_solidity::artifacts::ArtifactId {
name: value.name,
source: value.source.parse().map_err(|_err| {
napi::Error::new(napi::Status::GenericFailure, "Invalid source path")
})?,
version: value.solc_version.parse().map_err(|_err| {
napi::Error::new(napi::Status::GenericFailure, "Invalid solc semver string")
})?,
})
}
}
/// A test contract to execute.
#[derive(Clone, Debug)]
#[napi(object)]
pub struct ContractData {
/// Contract ABI as json string.
pub abi: String,
/// Contract creation code as hex string. It can be missing if the contract
/// is ABI only.
pub bytecode: Option<String>,
/// The link references of the deployment bytecode.
pub link_references: Option<HashMap<String, HashMap<String, Vec<LinkReference>>>>,
/// Contract runtime code as hex string. It can be missing if the contract
/// is ABI only.
pub deployed_bytecode: Option<String>,
/// The link references of the deployed bytecode.
pub deployed_link_references: Option<HashMap<String, HashMap<String, Vec<LinkReference>>>>,
}
impl TryFrom<ContractData> for foundry_compilers::artifacts::CompactContractBytecode {
type Error = napi::Error;
fn try_from(contract: ContractData) -> napi::Result<Self> {
Ok(foundry_compilers::artifacts::CompactContractBytecode {
abi: Some(serde_json::from_str(&contract.abi).map_err(|_err| {
napi::Error::new(napi::Status::GenericFailure, "Invalid JSON ABI")
})?),
bytecode: contract
.bytecode
.map(|bytecode| {
let link_references =
convert_link_references(contract.link_references.unwrap_or_default());
let object = convert_bytecode(bytecode, !link_references.is_empty())?;
Ok::<_, napi::Error>(foundry_compilers::artifacts::CompactBytecode {
object,
source_map: None,
link_references,
})
})
.transpose()?,
deployed_bytecode: contract
.deployed_bytecode
.map(|deployed_bytecode| {
let link_references = convert_link_references(
contract.deployed_link_references.unwrap_or_default(),
);
let object = convert_bytecode(deployed_bytecode, !link_references.is_empty())?;
let compact_bytecode = foundry_compilers::artifacts::CompactBytecode {
object,
source_map: None,
link_references,
};
Ok::<_, napi::Error>(foundry_compilers::artifacts::CompactDeployedBytecode {
bytecode: Some(compact_bytecode),
immutable_references: BTreeMap::default(),
})
})
.transpose()?,
})
}
}
impl TryFrom<ContractData> for foundry_compilers::artifacts::CompactContractBytecodeCow<'static> {
type Error = napi::Error;
fn try_from(contract: ContractData) -> napi::Result<Self> {
let c: foundry_compilers::artifacts::CompactContractBytecode = contract.try_into()?;
Ok(foundry_compilers::artifacts::CompactContractBytecodeCow {
abi: c.abi.map(Cow::Owned),
bytecode: c.bytecode.map(Cow::Owned),
deployed_bytecode: c.deployed_bytecode.map(Cow::Owned),
})
}
}
// The order of link references as supplied through the NAPI interface doesn't
// matter, but the order can matter downstream for deterministic address
// generation.
fn convert_link_references(
link_references: HashMap<String, HashMap<String, Vec<LinkReference>>>,
) -> BTreeMap<String, BTreeMap<String, Vec<foundry_compilers::artifacts::Offsets>>> {
link_references
.into_iter()
.map(|(file, libraries)| {
let lib_map = libraries
.into_iter()
.map(|(library_name, references)| {
let offsets = references.into_iter().map(Into::into).collect();
(library_name, offsets)
})
.collect();
(file, lib_map)
})
.collect()
}
fn convert_bytecode(
bytecode: String,
needs_linking: bool,
) -> napi::Result<foundry_compilers::artifacts::BytecodeObject> {
if needs_linking {
Ok(foundry_compilers::artifacts::BytecodeObject::Unlinked(
bytecode,
))
} else {
let bytes = bytecode.parse().map_err(|err| {
let message = format!("Hex decoding error while parsing bytecode: '{err}'. Maybe forgot to pass link references for a contract that needs linking?");
napi::Error::from_reason(message)
})?;
Ok(foundry_compilers::artifacts::BytecodeObject::Bytecode(
bytes,
))
}
}
#[derive(Clone, Debug)]
#[napi(object)]
pub struct LinkReference {
pub start: u32,
pub length: u32,
}
impl From<LinkReference> for foundry_compilers::artifacts::Offsets {
fn from(value: LinkReference) -> Self {
Self {
start: value.start,
length: value.length,
}
}
}

View File

@@ -0,0 +1,37 @@
use napi_derive::napi;
use serde::Serialize;
#[napi(string_enum)]
#[derive(Serialize)]
#[doc = "Error codes that can be returned by cheatcodes in Solidity tests."]
pub enum CheatcodeErrorCode {
#[doc = "The specified cheatcode is not supported."]
UnsupportedCheatcode,
#[doc = "The specified cheatcode is missing."]
MissingCheatcode,
}
#[napi(object)]
#[derive(Clone, Serialize)]
#[doc = "Error returned by a cheatcode in Solidity tests."]
pub struct CheatcodeErrorDetails {
#[doc = "The error code representing the type of cheatcode error."]
pub code: CheatcodeErrorCode,
#[doc = "The name of the cheatcode that caused the error."]
pub cheatcode: String,
}
impl From<edr_solidity::return_data::CheatcodeErrorCode> for CheatcodeErrorCode {
fn from(value: edr_solidity::return_data::CheatcodeErrorCode) -> Self {
match value {
edr_solidity::return_data::CheatcodeErrorCode::UnsupportedCheatcode => {
CheatcodeErrorCode::UnsupportedCheatcode
}
// __Invalid is generated by alloy_sol_types for invalid encoded values.
edr_solidity::return_data::CheatcodeErrorCode::MissingCheatcode
| edr_solidity::return_data::CheatcodeErrorCode::__Invalid => {
CheatcodeErrorCode::MissingCheatcode
}
}
}
}

View File

@@ -0,0 +1,984 @@
use std::{collections::HashMap, path::PathBuf};
use derive_more::Debug;
use edr_primitives::hex;
use edr_solidity_tests::{
executors::invariant::InvariantConfig,
fuzz::FuzzConfig,
inspectors::cheatcodes::{CheatsConfigOptions, ExecutionContextConfig},
TestFilterConfig,
};
use foundry_cheatcodes::{FsPermissions, RpcEndpointUrl, RpcEndpoints};
use napi::{
bindgen_prelude::{BigInt, Uint8Array},
tokio::runtime,
Either, Status,
};
use napi_derive::napi;
use crate::{
account::AccountOverride,
cast::TryCast,
config::ObservabilityConfig,
serde::{
serialize_optional_bigint_as_struct, serialize_optional_uint8array_as_hex,
serialize_uint8array_as_hex,
},
solidity_tests::artifact::ArtifactId,
};
/// Solidity test runner configuration arguments exposed through the ffi.
/// Docs based on <https://book.getfoundry.sh/reference/config/testing>.
#[napi(object)]
#[derive(Debug, serde::Serialize)]
pub struct SolidityTestRunnerConfigArgs {
/// The absolute path to the project root directory.
/// Relative paths in cheat codes are resolved against this path.
pub project_root: String,
/// Configures the permissions of cheat codes that access the file system.
pub fs_permissions: Option<Vec<PathPermission>>,
/// Address labels for traces. Defaults to none.
pub labels: Option<Vec<AddressLabel>>,
/// Whether to enable isolation of calls. In isolation mode all top-level
/// calls are executed as a separate transaction in a separate EVM
/// context, enabling more precise gas accounting and transaction state
/// changes.
/// Defaults to false.
pub isolate: Option<bool>,
/// Whether or not to enable the ffi cheatcode.
/// Warning: Enabling this cheatcode has security implications, as it allows
/// tests to execute arbitrary programs on your computer.
/// Defaults to false.
pub ffi: Option<bool>,
/// Allow expecting reverts with `expectRevert` at the same callstack depth
/// as the test. Defaults to false.
pub allow_internal_expect_revert: Option<bool>,
/// The value of `msg.sender` in tests as hex string.
/// Defaults to `0x1804c8AB1F12E6bbf3894d4083f33e07309d1f38`.
#[debug("{:?}", sender.as_ref().map(hex::encode))]
#[serde(serialize_with = "serialize_optional_uint8array_as_hex")]
pub sender: Option<Uint8Array>,
/// The value of `tx.origin` in tests as hex string.
/// Defaults to `0x1804c8AB1F12E6bbf3894d4083f33e07309d1f38`.
#[debug("{:?}", tx_origin.as_ref().map(hex::encode))]
#[serde(serialize_with = "serialize_optional_uint8array_as_hex")]
pub tx_origin: Option<Uint8Array>,
/// The initial balance of the sender in tests.
/// Defaults to `0xffffffffffffffffffffffff`.
#[serde(serialize_with = "serialize_optional_bigint_as_struct")]
pub initial_balance: Option<BigInt>,
/// The value of `block.number` in tests.
/// Defaults to `1`.
#[serde(serialize_with = "serialize_optional_bigint_as_struct")]
pub block_number: Option<BigInt>,
/// The value of the `chainid` opcode in tests.
/// Defaults to `31337`.
#[serde(serialize_with = "serialize_optional_bigint_as_struct")]
pub chain_id: Option<BigInt>,
/// The hardfork to use for EVM execution.
pub hardfork: String,
/// The gas limit for each test case.
/// Defaults to `9_223_372_036_854_775_807` (`i64::MAX`).
#[serde(serialize_with = "serialize_optional_bigint_as_struct")]
pub gas_limit: Option<BigInt>,
/// The price of gas (in wei) in tests.
/// Defaults to `0`.
#[serde(serialize_with = "serialize_optional_bigint_as_struct")]
pub gas_price: Option<BigInt>,
/// The base fee per gas (in wei) in tests.
/// Defaults to `0`.
#[serde(serialize_with = "serialize_optional_bigint_as_struct")]
pub block_base_fee_per_gas: Option<BigInt>,
/// The value of `block.coinbase` in tests.
/// Defaults to `0x0000000000000000000000000000000000000000`.
#[serde(serialize_with = "serialize_optional_uint8array_as_hex")]
#[debug("{:?}", block_coinbase.as_ref().map(hex::encode))]
pub block_coinbase: Option<Uint8Array>,
/// The value of `block.timestamp` in tests.
/// Defaults to 1.
#[serde(serialize_with = "serialize_optional_bigint_as_struct")]
pub block_timestamp: Option<BigInt>,
/// The value of `block.difficulty` in tests.
/// Defaults to 0.
#[serde(serialize_with = "serialize_optional_bigint_as_struct")]
pub block_difficulty: Option<BigInt>,
/// The `block.gaslimit` value during EVM execution.
/// Defaults to none.
#[serde(serialize_with = "serialize_optional_bigint_as_struct")]
pub block_gas_limit: Option<BigInt>,
/// Whether to disable the block gas limit.
/// Defaults to false.
pub disable_block_gas_limit: Option<bool>,
/// Whether to enable the EIP-7825 (Osaka) transaction gas limit cap.
/// Defaults to false.
pub enable_tx_gas_limit_cap: Option<bool>,
/// The memory limit of the EVM in bytes.
/// Defaults to `33_554_432` (2^25 = 32MiB).
#[serde(serialize_with = "serialize_optional_bigint_as_struct")]
pub memory_limit: Option<BigInt>,
/// The predeploys applied in local mode. Defaults to no predeploys.
/// These should match the predeploys of the network in fork mode, so they
/// aren't set in fork mode.
/// The code must be set and non-empty. The nonce and the balance default to
/// zero and storage defaults to empty.
pub local_predeploys: Option<Vec<AccountOverride>>,
/// If set, all tests are run in fork mode using this url or remote name.
/// Defaults to none.
pub eth_rpc_url: Option<String>,
/// Pins the block number for the global state fork.
#[serde(serialize_with = "serialize_optional_bigint_as_struct")]
pub fork_block_number: Option<BigInt>,
/// Map of RPC endpoints from chain name to RPC urls for fork cheat codes,
/// e.g. `{ "optimism": "https://optimism.alchemyapi.io/v2/..." }`
pub rpc_endpoints: Option<HashMap<String, String>>,
/// Optional RPC cache path. If this is none, then no RPC calls will be
/// cached, otherwise data is cached to `<rpc_cache_path>/<chain
/// id>/<block number>`. Caching can be disabled for specific chains
/// with `rpc_storage_caching`.
pub rpc_cache_path: Option<String>,
/// What RPC endpoints are cached. Defaults to all.
pub rpc_storage_caching: Option<StorageCachingConfig>,
/// The number of seconds to wait before `vm.prompt` reverts with a timeout.
/// Defaults to 120.
pub prompt_timeout: Option<u32>,
/// Fuzz testing configuration.
pub fuzz: Option<FuzzConfigArgs>,
/// Invariant testing configuration.
/// If an invariant config setting is not set, but a corresponding fuzz
/// config value is set, then the fuzz config value will be used.
pub invariant: Option<InvariantConfigArgs>,
/// Whether to collect stack traces.
pub collect_stack_traces: Option<CollectStackTraces>,
/// Controls which test results should include execution traces. Defaults to
/// None.
pub include_traces: Option<IncludeTraces>,
/// The configuration for the Solidity test runner's observability
#[debug(skip)]
#[serde(skip)]
pub observability: Option<ObservabilityConfig>,
/// A regex pattern to filter tests. If provided, only test methods that
/// match the pattern will be executed and reported as a test result.
pub test_pattern: Option<String>,
/// Controls whether to generate a gas report after running the tests.
/// Enabling this also enables collection of all traces and EVM isolation
/// mode.
/// Defaults to false.
pub generate_gas_report: Option<bool>,
/// Test function level config overrides.
/// Defaults to none.
pub test_function_overrides: Option<Vec<TestFunctionOverride>>,
}
impl SolidityTestRunnerConfigArgs {
/// Resolves the instance, converting it to a
/// [`edr_napi_core::solidity::config::TestRunnerConfig`].
pub fn resolve(
self,
env: &napi::Env,
runtime: runtime::Handle,
) -> napi::Result<edr_napi_core::solidity::config::TestRunnerConfig> {
let SolidityTestRunnerConfigArgs {
project_root,
fs_permissions,
labels,
isolate,
ffi,
allow_internal_expect_revert,
sender,
tx_origin,
initial_balance,
block_number,
chain_id,
hardfork,
gas_limit,
gas_price,
block_base_fee_per_gas,
block_coinbase,
block_timestamp,
block_difficulty,
block_gas_limit,
disable_block_gas_limit,
enable_tx_gas_limit_cap,
memory_limit,
local_predeploys,
eth_rpc_url,
rpc_cache_path,
fork_block_number,
rpc_endpoints,
rpc_storage_caching,
prompt_timeout,
fuzz,
invariant,
collect_stack_traces,
include_traces,
observability,
test_pattern,
generate_gas_report,
test_function_overrides,
} = self;
let test_pattern = TestFilterConfig {
test_pattern: test_pattern
.as_ref()
.map(|p| {
p.parse()
.map_err(|error| napi::Error::new(Status::InvalidArg, error))
})
.transpose()?,
};
let local_predeploys = local_predeploys
.map(|local_predeploys| {
local_predeploys
.into_iter()
.map(TryInto::try_into)
.collect::<Result<Vec<_>, _>>()
})
.transpose()?;
let invariant: InvariantConfig = fuzz
.as_ref()
.map(|f| invariant.clone().unwrap_or_default().defaults_from_fuzz(f))
.or(invariant)
.map(TryFrom::try_from)
.transpose()?
.unwrap_or_default();
let fuzz: FuzzConfig = fuzz.map(TryFrom::try_from).transpose()?.unwrap_or_default();
let cheatcode = CheatsConfigOptions {
// TODO https://github.com/NomicFoundation/edr/issues/657
// If gas reporting or coverage is supported, take that into account here.
execution_context: ExecutionContextConfig::Test,
rpc_endpoints: rpc_endpoints
.map(|endpoints| {
RpcEndpoints::new(
endpoints
.into_iter()
.map(|(chain, url)| (chain, RpcEndpointUrl::new(url))),
)
})
.unwrap_or_default(),
rpc_cache_path: rpc_cache_path.map(PathBuf::from),
rpc_storage_caching: rpc_storage_caching
.map(TryFrom::try_from)
.transpose()?
.unwrap_or_default(),
fs_permissions: FsPermissions::new(
fs_permissions
.unwrap_or_default()
.into_iter()
.map(Into::into),
),
prompt_timeout: prompt_timeout.map_or(120, Into::into),
labels: labels
.unwrap_or_default()
.into_iter()
.map(|AddressLabel { address, label }| Ok((address.try_cast()?, label)))
.collect::<Result<_, napi::Error>>()?,
seed: fuzz.seed,
allow_internal_expect_revert: allow_internal_expect_revert.unwrap_or(false),
functions_internal_expect_revert: test_function_overrides
.as_ref()
.map(|overrides| {
overrides
.iter()
.filter(|override_item| {
override_item
.config
.allow_internal_expect_revert
.unwrap_or_default()
})
.map(|override_item| override_item.identifier.clone().try_into())
.collect::<Result<_, _>>()
})
.transpose()?
.unwrap_or_default(),
};
let on_collected_coverage_fn = observability.map_or_else(
|| Ok(None),
|observability| {
observability
.resolve(env, runtime)
.map(|config| config.on_collected_coverage_fn)
},
)?;
let config = edr_napi_core::solidity::config::TestRunnerConfig {
project_root: project_root.into(),
include_traces: include_traces.unwrap_or_default().into(),
isolate,
ffi,
sender: sender.map(TryCast::try_cast).transpose()?,
tx_origin: tx_origin.map(TryCast::try_cast).transpose()?,
initial_balance: initial_balance.map(TryCast::try_cast).transpose()?,
block_number: block_number.map(TryCast::try_cast).transpose()?,
chain_id: chain_id.map(TryCast::try_cast).transpose()?,
hardfork,
gas_limit: gas_limit.map(TryCast::try_cast).transpose()?,
gas_price: gas_price.map(TryCast::try_cast).transpose()?,
block_base_fee_per_gas: block_base_fee_per_gas.map(TryCast::try_cast).transpose()?,
block_coinbase: block_coinbase.map(TryCast::try_cast).transpose()?,
block_timestamp: block_timestamp.map(TryCast::try_cast).transpose()?,
block_difficulty: block_difficulty.map(TryCast::try_cast).transpose()?,
block_gas_limit: block_gas_limit.map(TryCast::try_cast).transpose()?,
disable_block_gas_limit,
enable_tx_gas_limit_cap,
memory_limit: memory_limit.map(TryCast::try_cast).transpose()?,
local_predeploys,
fork_url: eth_rpc_url,
fork_block_number: fork_block_number.map(TryCast::try_cast).transpose()?,
cheatcode,
fuzz,
invariant,
collect_stack_traces: collect_stack_traces.map_or(
edr_solidity_tests::CollectStackTraces::OnFailure,
edr_solidity_tests::CollectStackTraces::from,
),
on_collected_coverage_fn,
test_pattern,
generate_gas_report,
test_function_overrides: test_function_overrides
.map(|overrides| {
overrides
.into_iter()
.map(|override_item| {
Ok::<_, napi::Error>((
override_item.identifier.try_into()?,
override_item.config.into(),
))
})
.collect::<Result<HashMap<_, _>, _>>()
})
.transpose()?,
};
Ok(config)
}
}
/// Fuzz testing configuration
#[napi(object)]
#[derive(Clone, Debug, Default, serde::Serialize)]
pub struct FuzzConfigArgs {
/// Path where fuzz failures are recorded and replayed if set.
pub failure_persist_dir: Option<String>,
/// Name of the file to record fuzz failures, defaults to `failures`.
pub failure_persist_file: Option<String>,
/// The amount of fuzz runs to perform for each fuzz test case. Higher
/// values gives more confidence in results at the cost of testing
/// speed.
/// Defaults to 256.
pub runs: Option<u32>,
/// The maximum number of combined inputs that may be rejected before the
/// test as a whole aborts. “Global” filters apply to the whole test
/// case. If the test case is rejected, the whole thing is regenerated.
/// Defaults to 65536.
pub max_test_rejects: Option<u32>,
/// Hexadecimal string.
/// Optional seed for the fuzzing RNG algorithm.
/// Defaults to None.
pub seed: Option<String>,
/// Integer between 0 and 100.
/// The weight of the dictionary. A higher dictionary weight will bias the
/// fuzz inputs towards “interesting” values, e.g. boundary values like
/// type(uint256).max or contract addresses from your environment.
/// Defaults to 40.
pub dictionary_weight: Option<u32>,
/// The flag indicating whether to include values from storage.
/// Defaults to true.
pub include_storage: Option<bool>,
/// The flag indicating whether to include push bytes values.
/// Defaults to true.
pub include_push_bytes: Option<bool>,
/// Optional timeout (in seconds) for each property test.
/// Defaults to none (no timeout).
pub timeout: Option<u32>,
}
impl TryFrom<FuzzConfigArgs> for FuzzConfig {
type Error = napi::Error;
fn try_from(value: FuzzConfigArgs) -> Result<Self, Self::Error> {
let FuzzConfigArgs {
failure_persist_dir,
failure_persist_file,
runs,
max_test_rejects,
seed,
dictionary_weight,
include_storage,
include_push_bytes,
timeout,
} = value;
let failure_persist_dir = failure_persist_dir.map(PathBuf::from);
let failure_persist_file = failure_persist_file.unwrap_or_else(|| "failures".to_string());
let seed = seed
.map(|s| {
s.parse().map_err(|_err| {
napi::Error::new(Status::InvalidArg, format!("Invalid seed value: {s}"))
})
})
.transpose()?;
let mut fuzz = FuzzConfig {
seed,
failure_persist_dir,
failure_persist_file,
// TODO https://github.com/NomicFoundation/edr/issues/657
gas_report_samples: 0,
timeout,
..FuzzConfig::default()
};
if let Some(runs) = runs {
fuzz.runs = runs;
}
if let Some(max_test_rejects) = max_test_rejects {
fuzz.max_test_rejects = max_test_rejects;
}
if let Some(dictionary_weight) = dictionary_weight {
fuzz.dictionary.dictionary_weight = dictionary_weight;
}
if let Some(include_storage) = include_storage {
fuzz.dictionary.include_storage = include_storage;
}
if let Some(include_push_bytes) = include_push_bytes {
fuzz.dictionary.include_push_bytes = include_push_bytes;
}
Ok(fuzz)
}
}
impl SolidityTestRunnerConfigArgs {
pub fn try_get_test_filter(&self) -> napi::Result<TestFilterConfig> {
let test_pattern = self
.test_pattern
.as_ref()
.map(|p| {
p.parse()
.map_err(|e| napi::Error::new(Status::InvalidArg, e))
})
.transpose()?;
Ok(TestFilterConfig { test_pattern })
}
}
/// Invariant testing configuration.
#[napi(object)]
#[derive(Clone, Debug, Default, serde::Serialize)]
pub struct InvariantConfigArgs {
/// Path where invariant failures are recorded and replayed if set.
pub failure_persist_dir: Option<String>,
/// The number of runs that must execute for each invariant test group.
/// Defaults to 256.
pub runs: Option<u32>,
/// The number of calls executed to attempt to break invariants in one run.
/// Defaults to 500.
pub depth: Option<u32>,
/// Fails the invariant fuzzing if a revert occurs.
/// Defaults to false.
pub fail_on_revert: Option<bool>,
/// Overrides unsafe external calls when running invariant tests, useful for
/// e.g. performing reentrancy checks.
/// Defaults to false.
pub call_override: Option<bool>,
/// Integer between 0 and 100.
/// The weight of the dictionary. A higher dictionary weight will bias the
/// fuzz inputs towards “interesting” values, e.g. boundary values like
/// type(uint256).max or contract addresses from your environment.
/// Defaults to 40.
pub dictionary_weight: Option<u32>,
/// The flag indicating whether to include values from storage.
/// Defaults to true.
pub include_storage: Option<bool>,
/// The flag indicating whether to include push bytes values.
/// Defaults to true.
pub include_push_bytes: Option<bool>,
/// The maximum number of attempts to shrink a failed the sequence. Shrink
/// process is disabled if set to 0.
/// Defaults to 5000.
pub shrink_run_limit: Option<u32>,
/// The maximum number of rejects via `vm.assume` which can be encountered
/// during a single invariant run.
/// Defaults to 65536.
pub max_assume_rejects: Option<u32>,
/// Optional timeout (in seconds) for each invariant test.
/// Defaults to none (no timeout).
pub timeout: Option<u32>,
}
impl InvariantConfigArgs {
/// Fill in fields from the fuzz config if they are not set.
fn defaults_from_fuzz(mut self, fuzz: &FuzzConfigArgs) -> Self {
let FuzzConfigArgs {
failure_persist_dir,
runs,
dictionary_weight,
include_storage,
include_push_bytes,
// These aren't used in the invariant config.
failure_persist_file: _,
max_test_rejects: _,
seed: _,
timeout,
} = fuzz;
if self.failure_persist_dir.is_none() {
self.failure_persist_dir.clone_from(failure_persist_dir);
}
if self.runs.is_none() {
self.runs = *runs;
}
if self.dictionary_weight.is_none() {
self.dictionary_weight = *dictionary_weight;
}
if self.include_storage.is_none() {
self.include_storage = *include_storage;
}
if self.include_push_bytes.is_none() {
self.include_push_bytes = *include_push_bytes;
}
if self.timeout.is_none() {
self.timeout = *timeout;
}
self
}
}
impl From<InvariantConfigArgs> for InvariantConfig {
fn from(value: InvariantConfigArgs) -> Self {
let InvariantConfigArgs {
failure_persist_dir,
runs,
depth,
fail_on_revert,
call_override,
dictionary_weight,
include_storage,
include_push_bytes,
shrink_run_limit,
max_assume_rejects,
timeout,
} = value;
let failure_persist_dir = failure_persist_dir.map(PathBuf::from);
let mut invariant = InvariantConfig {
failure_persist_dir,
// TODO https://github.com/NomicFoundation/edr/issues/657
gas_report_samples: 0,
timeout,
..InvariantConfig::default()
};
if let Some(runs) = runs {
invariant.runs = runs;
}
if let Some(depth) = depth {
invariant.depth = depth;
}
if let Some(fail_on_revert) = fail_on_revert {
invariant.fail_on_revert = fail_on_revert;
}
if let Some(call_override) = call_override {
invariant.call_override = call_override;
}
if let Some(dictionary_weight) = dictionary_weight {
invariant.dictionary.dictionary_weight = dictionary_weight;
}
if let Some(include_storage) = include_storage {
invariant.dictionary.include_storage = include_storage;
}
if let Some(include_push_bytes) = include_push_bytes {
invariant.dictionary.include_push_bytes = include_push_bytes;
}
if let Some(shrink_run_limit) = shrink_run_limit {
invariant.shrink_run_limit = shrink_run_limit;
}
if let Some(max_assume_rejects) = max_assume_rejects {
invariant.max_assume_rejects = max_assume_rejects;
}
invariant
}
}
/// Settings to configure caching of remote RPC endpoints.
#[napi(object)]
#[derive(Clone, Debug, serde::Serialize)]
pub struct StorageCachingConfig {
/// Chains to cache. Either all or none or a list of chain names, e.g.
/// ["optimism", "mainnet"].
pub chains: Either<CachedChains, Vec<String>>,
/// Endpoints to cache. Either all or remote or a regex.
pub endpoints: Either<CachedEndpoints, String>,
}
impl Default for StorageCachingConfig {
fn default() -> Self {
Self {
chains: Either::A(CachedChains::default()),
endpoints: Either::A(CachedEndpoints::default()),
}
}
}
impl TryFrom<StorageCachingConfig> for foundry_cheatcodes::StorageCachingConfig {
type Error = napi::Error;
fn try_from(value: StorageCachingConfig) -> Result<Self, Self::Error> {
let chains = match value.chains {
Either::A(chains) => chains.into(),
Either::B(chains) => {
let chains = chains
.into_iter()
.map(|c| {
c.parse()
.map_err(|c| napi::Error::new(Status::InvalidArg, c))
})
.collect::<Result<_, _>>()?;
foundry_cheatcodes::CachedChains::Chains(chains)
}
};
let endpoints = match value.endpoints {
Either::A(endpoints) => endpoints.into(),
Either::B(regex) => {
let regex = regex.parse().map_err(|_err| {
napi::Error::new(Status::InvalidArg, format!("Invalid regex: {regex}"))
})?;
foundry_cheatcodes::CachedEndpoints::Pattern(regex)
}
};
Ok(Self { chains, endpoints })
}
}
/// What chains to cache
#[napi]
#[derive(Debug, Default, serde::Serialize)]
pub enum CachedChains {
/// Cache all chains
#[default]
All,
/// Don't cache anything
None,
}
impl From<CachedChains> for foundry_cheatcodes::CachedChains {
fn from(value: CachedChains) -> Self {
match value {
CachedChains::All => foundry_cheatcodes::CachedChains::All,
CachedChains::None => foundry_cheatcodes::CachedChains::None,
}
}
}
/// What endpoints to enable caching for
#[napi]
#[derive(Debug, Default, serde::Serialize)]
pub enum CachedEndpoints {
/// Cache all endpoints
#[default]
All,
/// Only cache non-local host endpoints
Remote,
}
impl From<CachedEndpoints> for foundry_cheatcodes::CachedEndpoints {
fn from(value: CachedEndpoints) -> Self {
match value {
CachedEndpoints::All => foundry_cheatcodes::CachedEndpoints::All,
CachedEndpoints::Remote => foundry_cheatcodes::CachedEndpoints::Remote,
}
}
}
/// Represents an access permission to a single path
#[napi(object)]
#[derive(Clone, Debug, serde::Serialize)]
pub struct PathPermission {
/// Permission level to access the `path`
pub access: FsAccessPermission,
/// The targeted path guarded by the permission
pub path: String,
}
impl From<PathPermission> for foundry_cheatcodes::PathPermission {
fn from(value: PathPermission) -> Self {
let PathPermission { access, path } = value;
Self {
access: access.into(),
path: path.into(),
}
}
}
/**
* Determines the level of file system access for the given path.
*
* Exact path matching is used for file permissions. Prefix matching is used
* for directory permissions.
*
* Giving write access to configuration files, source files or executables
* in a project is considered dangerous, because it can be used by malicious
* Solidity dependencies to escape the EVM sandbox. It is therefore
* recommended to give write access to specific safe files only. If write
* access to a directory is needed, please make sure that it doesn't contain
* configuration files, source files or executables neither in the top level
* directory, nor in any subdirectories.
*/
#[napi]
#[derive(Debug, serde::Serialize)]
pub enum FsAccessPermission {
/// Allows reading and writing the file
ReadWriteFile,
/// Only allows reading the file
ReadFile,
/// Only allows writing the file
WriteFile,
/// Allows reading and writing all files in the directory and its
/// subdirectories
DangerouslyReadWriteDirectory,
/// Allows reading all files in the directory and its subdirectories
ReadDirectory,
/// Allows writing all files in the directory and its subdirectories
DangerouslyWriteDirectory,
}
impl From<FsAccessPermission> for foundry_cheatcodes::FsAccessPermission {
fn from(value: FsAccessPermission) -> Self {
match value {
FsAccessPermission::ReadWriteFile => {
foundry_cheatcodes::FsAccessPermission::ReadWriteFile
}
FsAccessPermission::ReadFile => foundry_cheatcodes::FsAccessPermission::ReadFile,
FsAccessPermission::WriteFile => foundry_cheatcodes::FsAccessPermission::WriteFile,
FsAccessPermission::DangerouslyReadWriteDirectory => {
foundry_cheatcodes::FsAccessPermission::DangerouslyReadWriteDirectory
}
FsAccessPermission::ReadDirectory => {
foundry_cheatcodes::FsAccessPermission::ReadDirectory
}
FsAccessPermission::DangerouslyWriteDirectory => {
foundry_cheatcodes::FsAccessPermission::DangerouslyWriteDirectory
}
}
}
}
#[napi(object)]
#[derive(Clone, Debug, serde::Serialize)]
pub struct AddressLabel {
/// The address to label
#[serde(serialize_with = "serialize_uint8array_as_hex")]
#[debug("{}", hex::encode(address))]
pub address: Uint8Array,
/// The label to assign to the address
pub label: String,
}
/// A type that controls when stack traces are collected.
#[napi]
#[derive(Debug, serde::Serialize)]
pub enum CollectStackTraces {
/// Always collects stack traces, adding performance overhead.
Always,
/// Only collects stack traces upon failure, re-executing the test. This
/// minimizes performance overhead.
///
/// Not all tests can be re-executed since certain cheatcodes contain
/// non-deterministic side-effects.
OnFailure,
}
impl From<CollectStackTraces> for edr_solidity_tests::CollectStackTraces {
fn from(value: CollectStackTraces) -> Self {
match value {
CollectStackTraces::Always => edr_solidity_tests::CollectStackTraces::Always,
CollectStackTraces::OnFailure => edr_solidity_tests::CollectStackTraces::OnFailure,
}
}
}
/// Configuration for [`SolidityTestRunnerConfigArgs::include_traces`] that
/// controls execution trace decoding and inclusion in test results.
#[napi]
#[derive(Debug, Default, PartialEq, Eq, serde::Serialize)]
pub enum IncludeTraces {
/// No traces will be included in any test result.
#[default]
None,
/// Traces will be included only on the results of failed tests.
Failing,
/// Traces will be included in all test results.
All,
}
impl From<IncludeTraces> for edr_solidity_tests::IncludeTraces {
fn from(value: IncludeTraces) -> Self {
match value {
IncludeTraces::None => edr_solidity_tests::IncludeTraces::None,
IncludeTraces::Failing => edr_solidity_tests::IncludeTraces::Failing,
IncludeTraces::All => edr_solidity_tests::IncludeTraces::All,
}
}
}
impl From<edr_solidity_tests::IncludeTraces> for IncludeTraces {
fn from(value: edr_solidity_tests::IncludeTraces) -> Self {
match value {
edr_solidity_tests::IncludeTraces::None => IncludeTraces::None,
edr_solidity_tests::IncludeTraces::Failing => IncludeTraces::Failing,
edr_solidity_tests::IncludeTraces::All => IncludeTraces::All,
}
}
}
/// Test function level config override.
#[napi(object)]
#[derive(Clone, Debug, Default, serde::Serialize)]
pub struct TestFunctionConfigOverride {
/// Allow expecting reverts with `expectRevert` at the same callstack depth
/// as the test.
pub allow_internal_expect_revert: Option<bool>,
/// Configuration override for fuzz testing.
pub fuzz: Option<FuzzConfigOverride>,
/// Configuration override for invariant testing.
pub invariant: Option<InvariantConfigOverride>,
}
impl From<TestFunctionConfigOverride> for edr_solidity_tests::TestFunctionConfigOverride {
fn from(value: TestFunctionConfigOverride) -> Self {
Self {
allow_internal_expect_revert: value.allow_internal_expect_revert,
fuzz: value.fuzz.map(Into::into),
invariant: value.invariant.map(Into::into),
}
}
}
/// Test function override configuration.
#[napi(object)]
#[derive(Clone, Debug, serde::Serialize)]
pub struct TestFunctionOverride {
/// The test function identifier.
pub identifier: TestFunctionIdentifier,
/// The configuration override.
pub config: TestFunctionConfigOverride,
}
/// Test function identifier.
#[napi(object)]
#[derive(Clone, Debug, serde::Serialize)]
pub struct TestFunctionIdentifier {
/// The contract artifact id.
pub contract_artifact: ArtifactId,
/// The function selector as hex string.
pub function_selector: String,
}
impl TryFrom<TestFunctionIdentifier> for foundry_cheatcodes::TestFunctionIdentifier {
type Error = napi::Error;
fn try_from(value: TestFunctionIdentifier) -> napi::Result<Self> {
Ok(foundry_cheatcodes::TestFunctionIdentifier {
contract_artifact: value.contract_artifact.try_into()?,
function_selector: value.function_selector,
})
}
}
/// Timeout configuration.
/// Note: This wrapper is needed to avoid ambiguity with NAPI conversion.
#[napi(object)]
#[derive(Clone, Debug, Default, serde::Serialize)]
pub struct TimeoutConfig {
/// Optional timeout (in seconds).
pub time: Option<u32>,
}
impl From<TimeoutConfig> for edr_solidity_tests::TimeoutConfig {
fn from(value: TimeoutConfig) -> Self {
Self { time: value.time }
}
}
/// Test function or test contract level fuzz config override.
#[napi(object)]
#[derive(Clone, Debug, Default, serde::Serialize)]
pub struct FuzzConfigOverride {
/// The number of test cases that must execute for each property test.
pub runs: Option<u32>,
/// The maximum number of test case rejections allowed by proptest, to be
/// encountered during usage of `vm.assume` cheatcode. This will be used
/// to set the `max_global_rejects` value in proptest test runner config.
/// `max_local_rejects` option isn't exposed here since we're not using
/// `prop_filter`.
pub max_test_rejects: Option<u32>,
/// show `console.log` in fuzz test, defaults to `false`.
pub show_logs: Option<bool>,
/// Optional timeout (in seconds) for each property test.
pub timeout: Option<TimeoutConfig>,
}
impl From<FuzzConfigOverride> for edr_solidity_tests::FuzzConfigOverride {
fn from(value: FuzzConfigOverride) -> Self {
Self {
runs: value.runs,
max_test_rejects: value.max_test_rejects,
show_logs: value.show_logs,
timeout: value.timeout.map(Into::into),
}
}
}
/// Test function or test contract level invariant config override.
#[napi(object)]
#[derive(Clone, Debug, Default, serde::Serialize)]
pub struct InvariantConfigOverride {
/// The number of runs that must execute for each invariant test group.
pub runs: Option<u32>,
/// The number of calls executed to attempt to break invariants in one run.
pub depth: Option<u32>,
/// Fails the invariant fuzzing if a revert occurs.
pub fail_on_revert: Option<bool>,
/// Allows overriding an unsafe external call when running invariant tests.
/// eg. reentrancy checks
pub call_override: Option<bool>,
/// Optional timeout (in seconds) for each invariant test.
pub timeout: Option<TimeoutConfig>,
}
impl From<InvariantConfigOverride> for edr_solidity_tests::InvariantConfigOverride {
fn from(value: InvariantConfigOverride) -> Self {
Self {
runs: value.runs,
depth: value.depth,
fail_on_revert: value.fail_on_revert,
call_override: value.call_override,
timeout: value.timeout.map(Into::into),
}
}
}

View File

@@ -0,0 +1,22 @@
use std::sync::Arc;
use edr_napi_core::solidity::SyncTestRunnerFactory;
use napi_derive::napi;
#[napi]
pub struct SolidityTestRunnerFactory {
inner: Arc<dyn SyncTestRunnerFactory>,
}
impl SolidityTestRunnerFactory {
/// Returns a reference to the inner test runner factory.
pub fn as_inner(&self) -> &Arc<dyn SyncTestRunnerFactory> {
&self.inner
}
}
impl From<Arc<dyn SyncTestRunnerFactory>> for SolidityTestRunnerFactory {
fn from(inner: Arc<dyn SyncTestRunnerFactory>) -> Self {
Self { inner }
}
}

View File

@@ -0,0 +1,72 @@
use std::{collections::BTreeMap, sync::Arc};
use edr_napi_core::solidity::{
config::{TestRunnerConfig, TracingConfigWithBuffers},
SyncTestRunner, SyncTestRunnerFactory,
};
use edr_primitives::Bytes;
use edr_solidity::artifacts::ArtifactId;
use edr_solidity_tests::{
contracts::ContractsByArtifact,
decode::RevertDecoder,
evm_context::L1EvmBuilder,
multi_runner::TestContract,
revm::context::{BlockEnv, TxEnv},
MultiContractRunner,
};
use napi::tokio;
use napi_derive::napi;
use crate::solidity_tests::{factory::SolidityTestRunnerFactory, runner::LazyContractDecoder};
struct L1TestRunnerFactory;
impl SyncTestRunnerFactory for L1TestRunnerFactory {
fn create_test_runner(
&self,
runtime: tokio::runtime::Handle,
config: TestRunnerConfig,
contracts: BTreeMap<ArtifactId, TestContract>,
known_contracts: ContractsByArtifact,
libs_to_deploy: Vec<Bytes>,
revert_decoder: RevertDecoder,
tracing_config: TracingConfigWithBuffers,
) -> napi::Result<Box<dyn SyncTestRunner>> {
let contract_decoder = LazyContractDecoder::new(tracing_config);
let runner = tokio::task::block_in_place(|| {
runtime
.block_on(MultiContractRunner::<
BlockEnv,
(),
L1EvmBuilder,
edr_chain_l1::HaltReason,
edr_chain_l1::Hardfork,
_,
edr_chain_l1::InvalidTransaction,
TxEnv,
>::new(
config.try_into()?,
contracts,
known_contracts,
libs_to_deploy,
contract_decoder,
revert_decoder,
))
.map_err(|err| {
napi::Error::new(
napi::Status::GenericFailure,
format!("Failed to create multi contract runner: {err}"),
)
})
})?;
Ok(Box::new(runner))
}
}
#[napi(catch_unwind)]
pub fn l1_solidity_test_runner_factory() -> SolidityTestRunnerFactory {
let factory: Arc<dyn SyncTestRunnerFactory> = Arc::new(L1TestRunnerFactory);
factory.into()
}

View File

@@ -0,0 +1,72 @@
use std::{collections::BTreeMap, sync::Arc};
use edr_napi_core::solidity::{
config::{TestRunnerConfig, TracingConfigWithBuffers},
SyncTestRunner, SyncTestRunnerFactory,
};
use edr_op::{solidity_tests::OpEvmBuilder, transaction::OpTxEnv};
use edr_primitives::Bytes;
use edr_solidity::artifacts::ArtifactId;
use edr_solidity_tests::{
contracts::ContractsByArtifact,
decode::RevertDecoder,
multi_runner::TestContract,
revm::context::{BlockEnv, TxEnv},
MultiContractRunner,
};
use napi::tokio;
use napi_derive::napi;
use crate::solidity_tests::{factory::SolidityTestRunnerFactory, runner::LazyContractDecoder};
struct OpTestRunnerFactory;
impl SyncTestRunnerFactory for OpTestRunnerFactory {
fn create_test_runner(
&self,
runtime: tokio::runtime::Handle,
config: TestRunnerConfig,
contracts: BTreeMap<ArtifactId, TestContract>,
known_contracts: ContractsByArtifact,
libs_to_deploy: Vec<Bytes>,
revert_decoder: RevertDecoder,
tracing_config: TracingConfigWithBuffers,
) -> napi::Result<Box<dyn SyncTestRunner>> {
let contract_decoder = LazyContractDecoder::new(tracing_config);
let runner = tokio::task::block_in_place(|| {
runtime
.block_on(MultiContractRunner::<
BlockEnv,
_,
OpEvmBuilder,
edr_op::HaltReason,
edr_op::Hardfork,
_,
edr_op::InvalidTransaction,
OpTxEnv<TxEnv>,
>::new(
config.try_into()?,
contracts,
known_contracts,
libs_to_deploy,
contract_decoder,
revert_decoder,
))
.map_err(|err| {
napi::Error::new(
napi::Status::GenericFailure,
format!("Failed to create multi contract runner: {err}"),
)
})
})?;
Ok(Box::new(runner))
}
}
#[napi(catch_unwind)]
pub fn op_solidity_test_runner_factory() -> SolidityTestRunnerFactory {
let factory: Arc<dyn SyncTestRunnerFactory> = Arc::new(OpTestRunnerFactory);
factory.into()
}

View File

@@ -0,0 +1,51 @@
use std::sync::{Mutex, OnceLock};
use edr_chain_spec::HaltReasonTrait;
use edr_napi_core::solidity::config::TracingConfigWithBuffers;
use edr_solidity::{
artifacts::BuildInfoConfigWithBuffers,
contract_decoder::{ContractDecoder, ContractDecoderError, NestedTraceDecoder},
nested_trace::NestedTrace,
};
/// Only parses the tracing config which is very expensive if the contract
/// decoder is used.
#[derive(Debug)]
pub(crate) struct LazyContractDecoder {
// We need the `Mutex`, because `Uint8Array` is not `Sync`
tracing_config: Mutex<TracingConfigWithBuffers>,
// Storing the result so that we can propagate the error
contract_decoder: OnceLock<Result<ContractDecoder, ContractDecoderError>>,
}
impl LazyContractDecoder {
pub fn new(tracing_config: TracingConfigWithBuffers) -> Self {
Self {
tracing_config: Mutex::new(tracing_config),
contract_decoder: OnceLock::new(),
}
}
}
impl<HaltReasonT: HaltReasonTrait> NestedTraceDecoder<HaltReasonT> for LazyContractDecoder {
fn try_to_decode_nested_trace(
&self,
nested_trace: NestedTrace<HaltReasonT>,
) -> Result<NestedTrace<HaltReasonT>, ContractDecoderError> {
self.contract_decoder
.get_or_init(|| {
let tracing_config = self
.tracing_config
.lock()
.expect("Can't get poisoned, because only called once");
edr_solidity::artifacts::BuildInfoConfig::parse_from_buffers(
BuildInfoConfigWithBuffers::from(&*tracing_config),
)
.map_err(|err| ContractDecoderError::Initialization(err.to_string()))
.and_then(|config| ContractDecoder::new(&config))
})
.as_ref()
.map_err(Clone::clone)
.and_then(|decoder| decoder.try_to_decode_nested_trace(nested_trace))
}
}

View File

@@ -0,0 +1,790 @@
use std::{
borrow::Cow,
convert::Infallible,
fmt::{Debug, Formatter},
sync::Arc,
};
use edr_solidity_tests::{
constants::CHEATCODE_ADDRESS,
executors::stack_trace::StackTraceResult,
traces::{self, CallTraceArena, SparsedTraceArena},
};
use napi::{
bindgen_prelude::{BigInt, Either3, Either4, Uint8Array},
Either,
};
use napi_derive::napi;
use crate::{
cast::TryCast,
gas_report::GasReport,
solidity_tests::{artifact::ArtifactId, config::IncludeTraces},
trace::{solidity_stack_trace::SolidityStackTraceEntry, u256_to_bigint},
};
/// A grouping of value snapshot entries for a test.
#[napi(object)]
#[derive(Clone, Debug)]
pub struct ValueSnapshotGroup {
/// The group name.
pub name: String,
/// The entries in the group.
pub entries: Vec<ValueSnapshotEntry>,
}
/// An entry in a value snapshot group.
#[napi(object)]
#[derive(Clone, Debug)]
pub struct ValueSnapshotEntry {
/// The name of the entry.
pub name: String,
/// The value of the entry.
pub value: String,
}
/// See [`edr_solidity_tests::result::SuiteResult`]
#[napi]
#[derive(Clone, Debug)]
pub struct SuiteResult {
/// The artifact id can be used to match input to result in the progress
/// callback
#[napi(readonly)]
pub id: ArtifactId,
/// See [`edr_solidity_tests::result::SuiteResult::duration`]
#[napi(readonly)]
pub duration_ns: BigInt,
/// See [`edr_solidity_tests::result::SuiteResult::test_results`]
#[napi(readonly)]
pub test_results: Vec<TestResult>,
/// See [`edr_solidity_tests::result::SuiteResult::warnings`]
#[napi(readonly)]
pub warnings: Vec<String>,
}
impl SuiteResult {
pub fn new(
id: edr_solidity::artifacts::ArtifactId,
suite_result: edr_solidity_tests::result::SuiteResult<String>,
include_traces: IncludeTraces,
) -> Self {
Self {
id: id.into(),
duration_ns: BigInt::from(suite_result.duration.as_nanos()),
test_results: suite_result
.test_results
.into_iter()
.map(|(name, test_result)| TestResult::new(name, test_result, include_traces))
.collect(),
warnings: suite_result.warnings,
}
}
}
/// See [`edr_solidity_tests::result::TestResult`]
#[napi]
#[derive(Clone, Debug)]
pub struct TestResult {
/// The name of the test.
#[napi(readonly)]
pub name: String,
/// See [`edr_solidity_tests::result::TestResult::status`]
#[napi(readonly)]
pub status: TestStatus,
/// See [`edr_solidity_tests::result::TestResult::reason`]
#[napi(readonly)]
pub reason: Option<String>,
/// See [`edr_solidity_tests::result::TestResult::counterexample`]
#[napi(readonly)]
pub counterexample: Option<Either<BaseCounterExample, CounterExampleSequence>>,
/// See [`edr_solidity_tests::result::TestResult::decoded_logs`]
#[napi(readonly)]
pub decoded_logs: Vec<String>,
/// See [`edr_solidity_tests::result::TestResult::kind`]
#[napi(readonly)]
pub kind: Either3<StandardTestKind, FuzzTestKind, InvariantTestKind>,
/// See [`edr_solidity_tests::result::TestResult::duration`]
#[napi(readonly)]
pub duration_ns: BigInt,
/// Groups of value snapshot entries (incl. gas).
///
/// Only present if the test runner collected scoped snapshots. Currently,
/// this is always the case.
#[napi(readonly)]
pub value_snapshot_groups: Option<Vec<ValueSnapshotGroup>>,
stack_trace_result: Option<Arc<StackTraceResult<String>>>,
call_trace_arenas: Vec<(traces::TraceKind, SparsedTraceArena)>,
}
/// The stack trace result
#[napi(object)]
pub struct StackTrace {
/// Enum tag for JS.
#[napi(ts_type = "\"StackTrace\"")]
pub kind: &'static str,
/// The stack trace entries
pub entries: Vec<SolidityStackTraceEntry>,
}
/// We couldn't generate stack traces, because an unexpected error occurred.
#[napi(object)]
pub struct UnexpectedError {
/// Enum tag for JS.
#[napi(ts_type = "\"UnexpectedError\"")]
pub kind: &'static str,
/// The error message from the unexpected error.
pub error_message: String,
}
/// We couldn't generate stack traces, because the stack trace generation
/// heuristics failed due to an unknown reason.
#[napi(object)]
pub struct HeuristicFailed {
/// Enum tag for JS.
#[napi(ts_type = "\"HeuristicFailed\"")]
pub kind: &'static str,
}
/// We couldn't generate stack traces, because the test execution is unsafe to
/// replay due to indeterminism. This can be caused by either specifying a fork
/// url without a fork block number in the test runner config or using impure
/// cheatcodes.
#[napi(object)]
pub struct UnsafeToReplay {
/// Enum tag for JS.
#[napi(ts_type = "\"UnsafeToReplay\"")]
pub kind: &'static str,
/// Indeterminism due to specifying a fork url without a fork block number
/// in the test runner config.
pub global_fork_latest: bool,
/// The list of executed impure cheatcode signatures. We collect function
/// signatures instead of function names as whether a cheatcode is impure
/// can depend on the arguments it takes (e.g. `createFork` without a second
/// argument means implicitly fork from “latest”). Example signature:
/// `function createSelectFork(string calldata urlOrAlias) external returns
/// (uint256 forkId);`.
pub impure_cheatcodes: Vec<String>,
}
#[napi]
impl TestResult {
/// Compute the error stack trace.
/// The result is either the stack trace or the reason why we couldn't
/// generate the stack trace.
/// Returns null if the test status is succeeded or skipped.
/// Cannot throw.
#[napi]
pub fn stack_trace(
&self,
) -> Option<Either4<StackTrace, UnexpectedError, HeuristicFailed, UnsafeToReplay>> {
self.stack_trace_result.as_ref().map(|stack_trace_result| {
match stack_trace_result.as_ref() {
StackTraceResult::Success(stack_trace) => Either4::A(StackTrace {
kind: "StackTrace",
entries: stack_trace
.iter()
.cloned()
.map(TryCast::try_cast)
.collect::<Result<Vec<_>, Infallible>>()
.expect("infallible"),
}),
StackTraceResult::Error(error) => Either4::B(UnexpectedError {
kind: "UnexpectedError",
error_message: error.to_string(),
}),
StackTraceResult::HeuristicFailed => Either4::C(HeuristicFailed {
kind: "HeuristicFailed",
}),
StackTraceResult::UnsafeToReplay {
global_fork_latest,
impure_cheatcodes,
} => Either4::D(UnsafeToReplay {
kind: "UnsafeToReplay",
global_fork_latest: *global_fork_latest,
// napi-rs would clone `&'static str` under the hood anyway, so no performance
// hit from `Cow::into_owned`.
impure_cheatcodes: impure_cheatcodes
.iter()
.cloned()
.map(Cow::into_owned)
.collect(),
}),
}
})
}
/// Constructs the execution traces for the test. Returns an empty array if
/// traces for this test were not requested according to
/// [`crate::solidity_tests::config::SolidityTestRunnerConfigArgs::include_traces`]. Otherwise, returns
/// an array of the root calls of the trace, which always includes the test
/// call itself and may also include the setup call if there is one
/// (identified by the function name `setUp`).
#[napi]
pub fn call_traces(&self) -> Vec<CallTrace> {
self.call_trace_arenas
.iter()
.filter(|(k, _)| *k != traces::TraceKind::Deployment)
.map(|(_, a)| CallTrace::from_arena_node(&a.resolve_arena(), 0))
.collect()
}
}
impl TestResult {
fn new(
name: String,
test_result: edr_solidity_tests::result::TestResult<String>,
include_traces: IncludeTraces,
) -> Self {
let include_trace = include_traces == IncludeTraces::All
|| (include_traces == IncludeTraces::Failing && test_result.status.is_failure());
Self {
name,
status: test_result.status.into(),
reason: test_result.reason,
counterexample: test_result
.counterexample
.map(|counterexample| match counterexample {
edr_solidity_tests::fuzz::CounterExample::Single(counterexample) => {
Either::A(BaseCounterExample::from(counterexample))
}
edr_solidity_tests::fuzz::CounterExample::Sequence(
original_size,
counterexamples,
) => Either::B(CounterExampleSequence {
original_sequence_size: u64::try_from(original_size)
.expect("usize fits into u64")
.into(),
sequence: counterexamples
.into_iter()
.map(BaseCounterExample::from)
.collect(),
}),
}),
decoded_logs: test_result.decoded_logs,
kind: match test_result.kind {
edr_solidity_tests::result::TestKind::Unit { gas: gas_consumed } => {
Either3::A(StandardTestKind {
consumed_gas: BigInt::from(gas_consumed),
})
}
edr_solidity_tests::result::TestKind::Fuzz {
runs,
mean_gas,
median_gas,
} => Either3::B(FuzzTestKind {
// usize as u64 is always safe
runs: BigInt::from(runs as u64),
mean_gas: BigInt::from(mean_gas),
median_gas: BigInt::from(median_gas),
}),
edr_solidity_tests::result::TestKind::Invariant {
runs,
calls,
reverts,
metrics,
failed_corpus_replays,
} => Either3::C(InvariantTestKind {
// usize as u64 is always safe
runs: BigInt::from(runs as u64),
calls: BigInt::from(calls as u64),
reverts: BigInt::from(reverts as u64),
metrics: metrics
.into_iter()
.map(|(name, metric)| {
(
name,
InvariantMetrics {
calls: BigInt::from(metric.calls as u64),
reverts: BigInt::from(metric.reverts as u64),
discards: BigInt::from(metric.discards as u64),
},
)
})
.collect(),
failed_corpus_replays: BigInt::from(failed_corpus_replays as u64),
}),
},
duration_ns: BigInt::from(test_result.duration.as_nanos()),
value_snapshot_groups: Some(
test_result
.value_snapshots
.into_iter()
.map(|(group_name, entries)| ValueSnapshotGroup {
name: group_name,
entries: entries
.into_iter()
.map(|(name, value)| ValueSnapshotEntry { name, value })
.collect(),
})
.collect(),
),
stack_trace_result: test_result.stack_trace_result.map(Arc::new),
call_trace_arenas: if include_trace {
test_result.traces
} else {
vec![]
},
}
}
}
#[derive(Debug)]
#[napi(string_enum)]
#[doc = "The result of a test execution."]
pub enum TestStatus {
#[doc = "Test success"]
Success,
#[doc = "Test failure"]
Failure,
#[doc = "Test skipped"]
Skipped,
}
impl From<edr_solidity_tests::result::TestStatus> for TestStatus {
fn from(value: edr_solidity_tests::result::TestStatus) -> Self {
match value {
edr_solidity_tests::result::TestStatus::Success => Self::Success,
edr_solidity_tests::result::TestStatus::Failure => Self::Failure,
edr_solidity_tests::result::TestStatus::Skipped => Self::Skipped,
}
}
}
/// See [`edr_solidity_tests::result::TestKind::Unit`]
#[napi(object)]
#[derive(Debug, Clone)]
pub struct StandardTestKind {
/// The gas consumed by the test.
#[napi(readonly)]
pub consumed_gas: BigInt,
}
/// See [`edr_solidity_tests::result::TestKind::Fuzz`]
#[napi(object)]
#[derive(Debug, Clone)]
pub struct FuzzTestKind {
/// See [`edr_solidity_tests::result::TestKind::Fuzz`]
#[napi(readonly)]
pub runs: BigInt,
/// See [`edr_solidity_tests::result::TestKind::Fuzz`]
#[napi(readonly)]
pub mean_gas: BigInt,
/// See [`edr_solidity_tests::result::TestKind::Fuzz`]
#[napi(readonly)]
pub median_gas: BigInt,
}
/// See [`edr_solidity_tests::fuzz::FuzzCase`]
#[napi(object)]
#[derive(Clone)]
pub struct FuzzCase {
/// The calldata used for this fuzz test
#[napi(readonly)]
pub calldata: Uint8Array,
/// Consumed gas
#[napi(readonly)]
pub gas: BigInt,
/// The initial gas stipend for the transaction
#[napi(readonly)]
pub stipend: BigInt,
}
impl Debug for FuzzCase {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
f.debug_struct("FuzzCase")
.field("gas", &self.gas)
.field("stipend", &self.stipend)
.finish()
}
}
/// See [`edr_solidity_tests::result::TestKind::Invariant`]
#[napi(object)]
#[derive(Debug, Clone)]
pub struct InvariantTestKind {
/// See [`edr_solidity_tests::result::TestKind::Invariant`]
#[napi(readonly)]
pub runs: BigInt,
/// See [`edr_solidity_tests::result::TestKind::Invariant`]
#[napi(readonly)]
pub calls: BigInt,
/// See [`edr_solidity_tests::result::TestKind::Invariant`]
#[napi(readonly)]
pub reverts: BigInt,
/// See [`edr_solidity_tests::result::TestKind::Invariant`]
#[napi(readonly)]
pub metrics: std::collections::HashMap<String, InvariantMetrics>,
/// See [`edr_solidity_tests::result::TestKind::Invariant`]
#[napi(readonly)]
pub failed_corpus_replays: BigInt,
}
/// See [`edr_solidity_tests::result::InvariantMetrics`]
#[napi(object)]
#[derive(Debug, Clone)]
pub struct InvariantMetrics {
// Count of fuzzed selector calls.
#[napi(readonly)]
pub calls: BigInt,
// Count of fuzzed selector reverts.
#[napi(readonly)]
pub reverts: BigInt,
// Count of fuzzed selector discards (through assume cheatcodes).
#[napi(readonly)]
pub discards: BigInt,
}
/// Original sequence size and sequence of calls used as a counter example
/// for invariant tests.
#[napi(object)]
#[derive(Clone, Debug)]
pub struct CounterExampleSequence {
/// The original sequence size before shrinking.
pub original_sequence_size: BigInt,
/// The shrunk counterexample sequence.
pub sequence: Vec<BaseCounterExample>,
}
/// See [`edr_solidity_tests::fuzz::BaseCounterExample`]
#[napi(object)]
#[derive(Clone)]
pub struct BaseCounterExample {
/// See [`edr_solidity_tests::fuzz::BaseCounterExample::sender`]
#[napi(readonly)]
pub sender: Option<Uint8Array>,
/// See [`edr_solidity_tests::fuzz::BaseCounterExample::addr`]
#[napi(readonly)]
pub address: Option<Uint8Array>,
/// See [`edr_solidity_tests::fuzz::BaseCounterExample::calldata`]
#[napi(readonly)]
pub calldata: Uint8Array,
/// See [`edr_solidity_tests::fuzz::BaseCounterExample::contract_name`]
#[napi(readonly)]
pub contract_name: Option<String>,
/// See [`edr_solidity_tests::fuzz::BaseCounterExample::signature`]
#[napi(readonly)]
pub signature: Option<String>,
/// See [`edr_solidity_tests::fuzz::BaseCounterExample::args`]
#[napi(readonly)]
pub args: Option<String>,
}
impl Debug for BaseCounterExample {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
f.debug_struct("BaseCounterExample")
.field("contract_name", &self.contract_name)
.field("signature", &self.signature)
.field("args", &self.args)
.finish()
}
}
impl From<edr_solidity_tests::fuzz::BaseCounterExample> for BaseCounterExample {
fn from(value: edr_solidity_tests::fuzz::BaseCounterExample) -> Self {
Self {
sender: value.sender.map(Uint8Array::with_data_copied),
address: value.addr.map(Uint8Array::with_data_copied),
calldata: Uint8Array::with_data_copied(value.calldata),
contract_name: value.contract_name,
signature: value.signature,
args: value.args,
}
}
}
/// Object representing a call in an execution trace, including contract
/// creation.
#[napi(object)]
pub struct CallTrace {
/// The kind of call or contract creation this represents.
pub kind: CallKind,
/// Whether the call succeeded or reverted.
pub success: bool,
/// Whether the call is a cheatcode.
pub is_cheatcode: bool,
/// The amount of gas that was consumed.
pub gas_used: BigInt,
/// The amount of native token that was included with the call.
pub value: BigInt,
/// The target address of the call.
pub address: String,
/// The name of the contract that is the target of the call, if known.
pub contract: Option<String>,
/// The input (calldata) to the call. If it encodes a known function call,
/// it will be decoded into the function name and a list of arguments.
/// For example, `{ name: "ownerOf", arguments: ["1"] }`. Note that the
/// function name may also be any of the special `fallback` and `receive`
/// functions. Otherwise, it will be provided as a raw byte array.
pub inputs: Either<DecodedTraceParameters, Uint8Array>,
/// The output of the call. This will be a decoded human-readable
/// representation of the value if the function is known, otherwise a
/// raw byte array.
pub outputs: Either<String, Uint8Array>,
/// Interleaved subcalls and event logs. Use `kind` to check if each member
/// of the array is a call or log trace.
pub children: Vec<Either<CallTrace, LogTrace>>,
}
/// Object representing an event log in an execution trace.
#[napi(object)]
pub struct LogTrace {
/// A constant to help discriminate the union `CallTrace | LogTrace`.
pub kind: LogKind,
/// If the log is a known event (based on its first topic), it will be
/// decoded into the event name and list of named parameters. For
/// example, `{ name: "Log", arguments: ["value: 1"] }`. Otherwise, it
/// will be provided as an array where all but the last element are the
/// log topics, and the last element is the log data.
pub parameters: Either<DecodedTraceParameters, Vec<Uint8Array>>,
}
/// The various kinds of call frames possible in the EVM.
#[napi]
#[derive(Debug)]
pub enum CallKind {
/// Regular call that may change state.
Call = 0,
/// Variant of `DelegateCall` that doesn't preserve sender or value in the
/// frame.
CallCode = 1,
/// Call that executes the code of the target in the context of the caller.
DelegateCall = 2,
/// Regular call that may not change state.
StaticCall = 3,
/// Contract creation.
Create = 4,
}
/// Kind marker for log traces.
#[napi]
#[derive(Debug)]
pub enum LogKind {
/// Single kind of log.
Log = 5,
// NOTE: The discriminants of LogKind and CallKind must be disjoint.
}
/// Decoded function call or event.
#[napi(object)]
pub struct DecodedTraceParameters {
/// The name of a function or an event.
pub name: String,
/// The arguments of the function call or the event, in their human-readable
/// representations.
pub arguments: Vec<String>,
}
impl CallTrace {
/// Instantiates a `CallTrace` with the details from a node and the supplied
/// children.
fn new(node: &traces::CallTraceNode, children: Vec<Either<CallTrace, LogTrace>>) -> Self {
let contract = node
.trace
.decoded
.as_ref()
.and_then(|decoded| decoded.label.clone());
let address = node.trace.address.to_checksum(None);
let inputs = match &node
.trace
.decoded
.as_ref()
.and_then(|decoded| decoded.call_data.as_ref())
{
Some(traces::DecodedCallData { signature, args }) => {
let name = signature
.split('(')
.next()
.expect("invalid function signature")
.to_string();
let arguments = args.clone();
Either::A(DecodedTraceParameters { name, arguments })
}
None => Either::B(node.trace.data.as_ref().into()),
};
let outputs = match node
.trace
.decoded
.as_ref()
.and_then(|decoded| decoded.return_data.as_ref())
{
Some(outputs) => Either::A(outputs.clone()),
None => {
if node.kind().is_any_create() && node.trace.success {
Either::A(format!("{} bytes of code", node.trace.output.len()))
} else {
Either::B(node.trace.output.as_ref().into())
}
}
};
Self {
kind: node.kind().into(),
success: node.trace.success,
is_cheatcode: node.trace.address == CHEATCODE_ADDRESS,
gas_used: node.trace.gas_used.into(),
value: u256_to_bigint(&node.trace.value),
contract,
address,
inputs,
outputs,
children,
}
}
/// Creates a tree of `CallTrace` rooted at some node in a trace arena.
fn from_arena_node(arena: &CallTraceArena, arena_index: usize) -> Self {
struct StackItem {
visited: bool,
parent_stack_index: Option<usize>,
arena_index: usize,
child_traces: Vec<Option<CallTrace>>,
}
let mut stack = Vec::new();
stack.push(StackItem {
visited: false,
arena_index,
parent_stack_index: None,
child_traces: Vec::new(),
});
loop {
// We will break out of the loop before the stack goes empty.
let mut item = stack.pop().unwrap();
let node = arena
.nodes()
.get(item.arena_index)
.expect("Arena index should be valid");
if item.visited {
let mut logs = node
.logs
.iter()
.map(|log| Some(LogTrace::from(log)))
.collect::<Vec<_>>();
let children = node
.ordering
.iter()
.filter_map(|ord| match *ord {
traces::TraceMemberOrder::Log(i) => {
let log = logs
.get_mut(i)
.expect("Log index should be valid")
.take()
.unwrap();
Some(Either::B(log))
}
traces::TraceMemberOrder::Call(i) => {
let child_trace = item
.child_traces
.get_mut(i)
.expect("Child trace index should be valid")
.take()
.unwrap();
Some(Either::A(child_trace))
}
traces::TraceMemberOrder::Step(_) => None,
})
.collect();
let trace = CallTrace::new(node, children);
if let Some(parent_stack_index) = item.parent_stack_index {
let parent = stack
.get_mut(parent_stack_index)
.expect("Parent stack index should be valid");
parent.child_traces.push(Some(trace));
} else {
return trace;
}
} else {
item.visited = true;
item.child_traces.reserve(node.children.len());
stack.push(item);
let top_index = Some(stack.len() - 1);
// Push children in reverse order to result in linear traversal of the arena for
// cache efficiency, on the assumption that the arena contains a pre-order
// traversal of the trace.
stack.extend(node.children.iter().rev().map(|&arena_index| StackItem {
visited: false,
parent_stack_index: top_index,
arena_index,
child_traces: Vec::new(),
}));
}
}
}
}
impl From<&traces::CallLog> for LogTrace {
fn from(log: &traces::CallLog) -> Self {
let decoded_log = log
.decoded
.as_ref()
.and_then(|decoded| decoded.name.clone().zip(decoded.params.as_ref()));
let parameters = decoded_log.map_or_else(
|| {
let raw_log = &log.raw_log;
let mut params = Vec::with_capacity(raw_log.topics().len() + 1);
params.extend(raw_log.topics().iter().map(|topic| topic.as_slice().into()));
params.push(log.raw_log.data.as_ref().into());
Either::B(params)
},
|(name, params)| {
let arguments = params
.iter()
.map(|(name, value)| format!("{name}: {value}"))
.collect();
Either::A(DecodedTraceParameters { name, arguments })
},
);
Self {
kind: LogKind::Log,
parameters,
}
}
}
impl From<traces::CallKind> for CallKind {
fn from(value: traces::CallKind) -> Self {
match value {
traces::CallKind::Call => CallKind::Call,
traces::CallKind::StaticCall => CallKind::StaticCall,
traces::CallKind::CallCode => CallKind::CallCode,
traces::CallKind::DelegateCall => CallKind::DelegateCall,
traces::CallKind::Create | traces::CallKind::Create2 => CallKind::Create,
// We do not support these EVM features.
traces::CallKind::AuthCall => {
unreachable!("Unsupported EVM features")
}
}
}
}
/// The result of a Solidity test run.
#[napi(object)]
pub struct SolidityTestResult {
/// Gas report, if it was generated.
#[napi(readonly)]
pub gas_report: Option<GasReport>,
}
impl From<edr_solidity_tests::multi_runner::SolidityTestResult> for SolidityTestResult {
fn from(value: edr_solidity_tests::multi_runner::SolidityTestResult) -> Self {
Self {
gas_report: value.gas_report.map(GasReport::from),
}
}
}

View File

@@ -0,0 +1,32 @@
use napi::{bindgen_prelude::BigInt, JsFunction};
use napi_derive::napi;
/// Configuration for subscriptions.
#[napi(object)]
pub struct SubscriptionConfig {
/// Callback to be called when a new event is received.
#[napi(ts_type = "(event: SubscriptionEvent) => void")]
pub subscription_callback: JsFunction,
}
impl From<edr_napi_core::subscription::Config> for SubscriptionConfig {
fn from(config: edr_napi_core::subscription::Config) -> Self {
Self {
subscription_callback: config.subscription_callback,
}
}
}
impl From<SubscriptionConfig> for edr_napi_core::subscription::Config {
fn from(config: SubscriptionConfig) -> Self {
Self {
subscription_callback: config.subscription_callback,
}
}
}
#[napi(object)]
pub struct SubscriptionEvent {
pub filter_id: BigInt,
pub result: serde_json::Value,
}

199
dev/env/node_modules/@nomicfoundation/edr/src/trace.rs generated vendored Executable file
View File

@@ -0,0 +1,199 @@
// In contrast to the functions in the `#[napi] impl XYZ` block,
// the free functions `#[napi] pub fn` are exported by napi-rs but
// are considered dead code in the (lib test) target.
// For now, we silence the relevant warnings, as we need to mimick
// the original API while we rewrite the stack trace refinement to Rust.
#![cfg_attr(test, allow(dead_code))]
use std::sync::Arc;
use edr_chain_spec::EvmHaltReason;
use edr_primitives::bytecode::opcode::OpCode;
use edr_tracing::BeforeMessage;
use napi::bindgen_prelude::{BigInt, Either3, Uint8Array};
use napi_derive::napi;
use crate::result::ExecutionResult;
mod library_utils;
mod debug;
mod exit;
mod model;
mod return_data;
pub mod solidity_stack_trace;
#[napi(object)]
pub struct TracingMessage {
/// Sender address
#[napi(readonly)]
pub caller: Uint8Array,
/// Recipient address. None if it is a Create message.
#[napi(readonly)]
pub to: Option<Uint8Array>,
/// Whether it's a static call
#[napi(readonly)]
pub is_static_call: bool,
/// Transaction gas limit
#[napi(readonly)]
pub gas_limit: BigInt,
/// Depth of the message
#[napi(readonly)]
pub depth: u8,
/// Input data of the message
#[napi(readonly)]
pub data: Uint8Array,
/// Value sent in the message
#[napi(readonly)]
pub value: BigInt,
/// Address of the code that is being executed. Can be different from `to`
/// if a delegate call is being done.
#[napi(readonly)]
pub code_address: Option<Uint8Array>,
/// Code of the contract that is being executed.
#[napi(readonly)]
pub code: Option<Uint8Array>,
}
impl From<&BeforeMessage> for TracingMessage {
fn from(value: &BeforeMessage) -> Self {
// Deconstruct to make sure all fields are handled
let BeforeMessage {
depth,
caller,
to,
is_static_call,
gas_limit,
data,
value,
code_address,
code,
} = value;
let data = Uint8Array::with_data_copied(data);
let code = code
.as_ref()
.map(|code| Uint8Array::with_data_copied(code.original_bytes()));
TracingMessage {
caller: Uint8Array::with_data_copied(caller),
to: to.as_ref().map(Uint8Array::with_data_copied),
gas_limit: BigInt::from(*gas_limit),
is_static_call: *is_static_call,
depth: *depth as u8,
data,
value: BigInt {
sign_bit: false,
words: value.into_limbs().to_vec(),
},
code_address: code_address.as_ref().map(Uint8Array::with_data_copied),
code,
}
}
}
#[napi(object)]
pub struct TracingStep {
/// Call depth
#[napi(readonly)]
pub depth: u8,
/// The program counter
#[napi(readonly)]
pub pc: BigInt,
/// The executed op code
#[napi(readonly)]
pub opcode: String,
/// The entries on the stack. It only contains the top element unless
/// verbose tracing is enabled. The vector is empty if there are no elements
/// on the stack.
#[napi(readonly)]
pub stack: Vec<BigInt>,
/// The memory at the step. None if verbose tracing is disabled.
#[napi(readonly)]
pub memory: Option<Uint8Array>,
}
impl TracingStep {
pub fn new(step: &edr_tracing::Step) -> Self {
let stack = step.stack.full().map_or_else(
|| {
step.stack
.top()
.map(u256_to_bigint)
.map_or_else(Vec::default, |top| vec![top])
},
|stack| stack.iter().map(u256_to_bigint).collect(),
);
let memory = step.memory.as_ref().map(Uint8Array::with_data_copied);
Self {
depth: step.depth as u8,
pc: BigInt::from(u64::from(step.pc)),
opcode: OpCode::name_by_op(step.opcode).to_string(),
stack,
memory,
}
}
}
pub(crate) fn u256_to_bigint(v: &edr_primitives::U256) -> BigInt {
BigInt {
sign_bit: false,
words: v.into_limbs().to_vec(),
}
}
#[napi(object)]
pub struct TracingMessageResult {
/// Execution result
#[napi(readonly)]
pub execution_result: ExecutionResult,
}
#[napi]
#[derive(Clone)]
pub struct RawTrace {
inner: Arc<edr_tracing::Trace<EvmHaltReason>>,
}
impl From<Arc<edr_tracing::Trace<EvmHaltReason>>> for RawTrace {
fn from(value: Arc<edr_tracing::Trace<EvmHaltReason>>) -> Self {
Self { inner: value }
}
}
#[napi]
impl RawTrace {
#[napi(getter)]
pub fn trace(&self) -> Vec<Either3<TracingMessage, TracingStep, TracingMessageResult>> {
self.inner
.messages
.iter()
.map(|message| match message {
edr_tracing::TraceMessage::Before(message) => {
Either3::A(TracingMessage::from(message))
}
edr_tracing::TraceMessage::Step(step) => Either3::B(TracingStep::new(step)),
edr_tracing::TraceMessage::After(message) => Either3::C(TracingMessageResult {
execution_result: ExecutionResult::from(message),
}),
})
.collect()
}
}
#[napi]
/// Returns the latest version of solc that EDR officially
/// supports and is tested against.
pub fn get_latest_supported_solc_version() -> String {
"0.8.28".to_string()
}

61
dev/env/node_modules/@nomicfoundation/edr/src/trace/debug.rs generated vendored Executable file
View File

@@ -0,0 +1,61 @@
//! Port of `hardhat-network/stack-traces/debug.ts` from Hardhat.
use napi::bindgen_prelude::Either25;
use napi_derive::napi;
use super::solidity_stack_trace::{RevertErrorStackTraceEntry, SolidityStackTrace};
use crate::trace::return_data::ReturnData;
#[napi(catch_unwind)]
fn print_stack_trace(trace: SolidityStackTrace) -> napi::Result<()> {
let entry_values = trace
.into_iter()
.map(|entry| match entry {
Either25::A(entry) => serde_json::to_value(entry),
Either25::B(entry) => serde_json::to_value(entry),
Either25::C(entry) => serde_json::to_value(entry),
Either25::D(entry) => serde_json::to_value(entry),
Either25::F(entry) => serde_json::to_value(entry),
Either25::G(entry) => serde_json::to_value(entry),
Either25::H(entry) => serde_json::to_value(entry),
Either25::I(entry) => serde_json::to_value(entry),
Either25::J(entry) => serde_json::to_value(entry),
Either25::K(entry) => serde_json::to_value(entry),
Either25::L(entry) => serde_json::to_value(entry),
Either25::M(entry) => serde_json::to_value(entry),
Either25::N(entry) => serde_json::to_value(entry),
Either25::O(entry) => serde_json::to_value(entry),
Either25::P(entry) => serde_json::to_value(entry),
Either25::Q(entry) => serde_json::to_value(entry),
Either25::R(entry) => serde_json::to_value(entry),
Either25::S(entry) => serde_json::to_value(entry),
Either25::T(entry) => serde_json::to_value(entry),
Either25::U(entry) => serde_json::to_value(entry),
Either25::V(entry) => serde_json::to_value(entry),
Either25::W(entry) => serde_json::to_value(entry),
Either25::X(entry) => serde_json::to_value(entry),
Either25::Y(entry) => serde_json::to_value(entry),
// Decode the error message from the return data
Either25::E(entry @ RevertErrorStackTraceEntry { .. }) => {
use serde::de::Error;
let decoded_error_msg = ReturnData::new(entry.return_data.clone())
.decode_error()
.map_err(|e| {
serde_json::Error::custom(format_args!("Error decoding return data: {e}"))
})?;
let mut value = serde_json::to_value(entry)?;
if let Some(obj) = value.as_object_mut() {
obj.insert("message".to_string(), decoded_error_msg.into());
}
Ok(value)
}
})
.collect::<Result<Vec<_>, _>>()
.map_err(|e| napi::Error::from_reason(format!("Error converting to JSON: {e}")))?;
println!("{}", serde_json::to_string_pretty(&entry_values)?);
Ok(())
}

89
dev/env/node_modules/@nomicfoundation/edr/src/trace/exit.rs generated vendored Executable file
View File

@@ -0,0 +1,89 @@
//! Naive rewrite of `hardhat-network/provider/vm/exit.ts` from Hardhat.
//! Used together with `VmTracer`.
use std::fmt;
use edr_chain_spec::EvmHaltReason;
use napi_derive::napi;
#[napi]
pub struct Exit(pub(crate) ExitCode);
#[napi]
/// Represents the exit code of the EVM.
#[derive(Debug, PartialEq, Eq)]
#[allow(clippy::upper_case_acronyms, non_camel_case_types)] // These are exported and mapped 1:1 to existing JS enum
pub enum ExitCode {
/// Execution was successful.
SUCCESS = 0,
/// Execution was reverted.
REVERT,
/// Execution ran out of gas.
OUT_OF_GAS,
/// Execution encountered an internal error.
INTERNAL_ERROR,
/// Execution encountered an invalid opcode.
INVALID_OPCODE,
/// Execution encountered a stack underflow.
STACK_UNDERFLOW,
/// Create init code size exceeds limit (runtime).
CODESIZE_EXCEEDS_MAXIMUM,
/// Create collision.
CREATE_COLLISION,
/// Unknown halt reason.
UNKNOWN_HALT_REASON,
}
impl fmt::Display for ExitCode {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
ExitCode::SUCCESS => write!(f, "Success"),
ExitCode::REVERT => write!(f, "Reverted"),
ExitCode::OUT_OF_GAS => write!(f, "Out of gas"),
ExitCode::INTERNAL_ERROR => write!(f, "Internal error"),
ExitCode::INVALID_OPCODE => write!(f, "Invalid opcode"),
ExitCode::STACK_UNDERFLOW => write!(f, "Stack underflow"),
ExitCode::CODESIZE_EXCEEDS_MAXIMUM => write!(f, "Codesize exceeds maximum"),
ExitCode::CREATE_COLLISION => write!(f, "Create collision"),
ExitCode::UNKNOWN_HALT_REASON => write!(f, "Unknown halt reason"),
}
}
}
#[allow(clippy::fallible_impl_from)] // naively ported for now
impl From<edr_solidity::exit_code::ExitCode<EvmHaltReason>> for ExitCode {
fn from(code: edr_solidity::exit_code::ExitCode<EvmHaltReason>) -> Self {
use edr_solidity::exit_code::ExitCode;
match code {
ExitCode::Success => Self::SUCCESS,
ExitCode::Revert => Self::REVERT,
ExitCode::Halt(EvmHaltReason::OutOfGas(_)) => Self::OUT_OF_GAS,
ExitCode::Halt(EvmHaltReason::OpcodeNotFound | EvmHaltReason::InvalidFEOpcode
// Returned when an opcode is not implemented for the hardfork
| EvmHaltReason::NotActivated) => Self::INVALID_OPCODE,
ExitCode::Halt(EvmHaltReason::StackUnderflow) => Self::STACK_UNDERFLOW,
ExitCode::Halt(EvmHaltReason::CreateContractSizeLimit) => Self::CODESIZE_EXCEEDS_MAXIMUM,
ExitCode::Halt(EvmHaltReason::CreateCollision) => Self::CREATE_COLLISION,
_ => Self::UNKNOWN_HALT_REASON,
}
}
}
#[napi]
impl Exit {
#[napi(catch_unwind, getter)]
pub fn kind(&self) -> ExitCode {
self.0
}
#[napi(catch_unwind)]
pub fn is_error(&self) -> bool {
!matches!(self.0, ExitCode::SUCCESS)
}
#[napi(catch_unwind)]
pub fn get_reason(&self) -> String {
self.0.to_string()
}
}

View File

@@ -0,0 +1,11 @@
use napi_derive::napi;
#[napi(catch_unwind)]
pub fn link_hex_string_bytecode(
code: String,
address: String,
position: u32,
) -> napi::Result<String> {
edr_solidity::library_utils::link_hex_string_bytecode(code, &address, position)
.map_err(|err| napi::Error::from_reason(err.to_string()))
}

59
dev/env/node_modules/@nomicfoundation/edr/src/trace/model.rs generated vendored Executable file
View File

@@ -0,0 +1,59 @@
use std::rc::Rc;
use edr_solidity::build_model::ContractMetadata;
use napi_derive::napi;
use serde::Serialize;
/// Opaque handle to the `Bytecode` struct.
/// Only used on the JS side by the `VmTraceDecoder` class.
// NOTE: Needed, because we store the resolved `Bytecode` in the MessageTrace
// JS plain objects and those need a dedicated (class) type.
#[napi]
pub struct BytecodeWrapper(pub(crate) Rc<ContractMetadata>);
impl BytecodeWrapper {
pub fn new(bytecode: Rc<ContractMetadata>) -> Self {
Self(bytecode)
}
pub fn inner(&self) -> &Rc<ContractMetadata> {
&self.0
}
}
impl std::ops::Deref for BytecodeWrapper {
type Target = ContractMetadata;
fn deref(&self) -> &Self::Target {
&self.0
}
}
#[derive(Debug, PartialEq, Eq, Serialize)]
#[allow(non_camel_case_types)] // intentionally mimicks the original case in TS
#[allow(clippy::upper_case_acronyms)]
#[napi]
// Mimicks [`edr_solidity::build_model::ContractFunctionType`].
pub enum ContractFunctionType {
CONSTRUCTOR,
FUNCTION,
FALLBACK,
RECEIVE,
GETTER,
MODIFIER,
FREE_FUNCTION,
}
impl From<edr_solidity::build_model::ContractFunctionType> for ContractFunctionType {
fn from(value: edr_solidity::build_model::ContractFunctionType) -> Self {
match value {
edr_solidity::build_model::ContractFunctionType::Constructor => Self::CONSTRUCTOR,
edr_solidity::build_model::ContractFunctionType::Function => Self::FUNCTION,
edr_solidity::build_model::ContractFunctionType::Fallback => Self::FALLBACK,
edr_solidity::build_model::ContractFunctionType::Receive => Self::RECEIVE,
edr_solidity::build_model::ContractFunctionType::Getter => Self::GETTER,
edr_solidity::build_model::ContractFunctionType::Modifier => Self::MODIFIER,
edr_solidity::build_model::ContractFunctionType::FreeFunction => Self::FREE_FUNCTION,
}
}
}

View File

@@ -0,0 +1,96 @@
//! Rewrite of `hardhat-network/provider/return-data.ts` from Hardhat.
use alloy_sol_types::SolError;
use napi::bindgen_prelude::{BigInt, Uint8Array};
use napi_derive::napi;
// Built-in error types
// See <https://docs.soliditylang.org/en/v0.8.26/control-structures.html#error-handling-assert-require-revert-and-exceptions>
alloy_sol_types::sol! {
error Error(string);
error Panic(uint256);
}
#[napi]
pub struct ReturnData {
#[napi(readonly)]
pub value: Uint8Array,
selector: Option<[u8; 4]>,
}
#[napi]
impl ReturnData {
#[napi(catch_unwind, constructor)]
pub fn new(value: Uint8Array) -> Self {
let selector = value
.get(0..4)
.map(|selector| selector.try_into().expect("selector is 4 bytes"));
Self { value, selector }
}
#[napi(catch_unwind)]
pub fn is_empty(&self) -> bool {
self.value.is_empty()
}
pub fn matches_selector(&self, selector: impl AsRef<[u8]>) -> bool {
self.selector
.is_some_and(|value| value == selector.as_ref())
}
#[napi(catch_unwind)]
pub fn is_error_return_data(&self) -> bool {
self.selector == Some(Error::SELECTOR)
}
#[napi(catch_unwind)]
pub fn is_panic_return_data(&self) -> bool {
self.selector == Some(Panic::SELECTOR)
}
#[napi(catch_unwind)]
pub fn decode_error(&self) -> napi::Result<String> {
if self.is_empty() {
return Ok(String::new());
}
if !self.is_error_return_data() {
return Err(napi::Error::new(
napi::Status::InvalidArg,
"VM Exception while processing transaction: Expected return data to be a Error(string)",
));
}
let result = Error::abi_decode(&self.value[..]).map_err(|_err| {
napi::Error::new(
napi::Status::InvalidArg,
"VM Exception while processing transaction: Expected return data to contain a valid string",
)
})?;
Ok(result.0)
}
#[napi(catch_unwind)]
pub fn decode_panic(&self) -> napi::Result<BigInt> {
if !self.is_panic_return_data() {
return Err(napi::Error::new(
napi::Status::InvalidArg,
"VM Exception while processing transaction: Expected return data to be a Panic(uint256)",
));
}
let result = Panic::abi_decode(&self.value[..]).map_err(|_err| {
napi::Error::new(
napi::Status::InvalidArg,
"VM Exception while processing transaction: Expected return data to contain a valid uint256",
)
})?;
Ok(BigInt {
sign_bit: false,
words: result.0.as_limbs().to_vec(),
})
}
}

View File

@@ -0,0 +1,883 @@
//! Naive rewrite of `hardhat-network/stack-traces/solidity-stack-traces.ts`
//! from Hardhat.
use std::convert::Infallible;
use edr_primitives::{hex, U256};
use napi::bindgen_prelude::{BigInt, Either25, FromNapiValue, ToNapiValue, Uint8Array, Undefined};
use napi_derive::napi;
use serde::{Serialize, Serializer};
use super::model::ContractFunctionType;
use crate::{
cast::TryCast, solidity_tests::cheatcode_errors::CheatcodeErrorDetails, trace::u256_to_bigint,
};
#[napi]
#[repr(u8)]
#[allow(non_camel_case_types)] // intentionally mimicks the original case in TS
#[allow(clippy::upper_case_acronyms)]
#[derive(PartialEq, Eq, PartialOrd, Ord, strum::FromRepr, strum::IntoStaticStr, Serialize)]
pub enum StackTraceEntryType {
CALLSTACK_ENTRY = 0,
UNRECOGNIZED_CREATE_CALLSTACK_ENTRY,
UNRECOGNIZED_CONTRACT_CALLSTACK_ENTRY,
PRECOMPILE_ERROR,
REVERT_ERROR,
PANIC_ERROR,
CUSTOM_ERROR,
FUNCTION_NOT_PAYABLE_ERROR,
INVALID_PARAMS_ERROR,
FALLBACK_NOT_PAYABLE_ERROR,
FALLBACK_NOT_PAYABLE_AND_NO_RECEIVE_ERROR,
UNRECOGNIZED_FUNCTION_WITHOUT_FALLBACK_ERROR, /* TODO: Should trying to call a
* private/internal be a special case of
* this? */
MISSING_FALLBACK_OR_RECEIVE_ERROR,
RETURNDATA_SIZE_ERROR,
NONCONTRACT_ACCOUNT_CALLED_ERROR,
CALL_FAILED_ERROR,
DIRECT_LIBRARY_CALL_ERROR,
UNRECOGNIZED_CREATE_ERROR,
UNRECOGNIZED_CONTRACT_ERROR,
OTHER_EXECUTION_ERROR,
// This is a special case to handle a regression introduced in solc 0.6.3
// For more info: https://github.com/ethereum/solidity/issues/9006
UNMAPPED_SOLC_0_6_3_REVERT_ERROR,
CONTRACT_TOO_LARGE_ERROR,
INTERNAL_FUNCTION_CALLSTACK_ENTRY,
CONTRACT_CALL_RUN_OUT_OF_GAS_ERROR,
CHEATCODE_ERROR,
}
#[napi(catch_unwind)]
pub fn stack_trace_entry_type_to_string(val: StackTraceEntryType) -> &'static str {
val.into()
}
#[napi]
pub const FALLBACK_FUNCTION_NAME: &str = "<fallback>";
#[napi]
pub const RECEIVE_FUNCTION_NAME: &str = "<receive>";
#[napi]
pub const CONSTRUCTOR_FUNCTION_NAME: &str = "constructor";
#[napi]
pub const UNRECOGNIZED_FUNCTION_NAME: &str = "<unrecognized-selector>";
#[napi]
pub const UNKNOWN_FUNCTION_NAME: &str = "<unknown>";
#[napi]
pub const PRECOMPILE_FUNCTION_NAME: &str = "<precompile>";
#[napi]
pub const UNRECOGNIZED_CONTRACT_NAME: &str = "<UnrecognizedContract>";
#[napi(object)]
#[derive(Clone, PartialEq, Serialize)]
pub struct SourceReference {
pub source_name: String,
pub source_content: String,
pub contract: Option<String>,
pub function: Option<String>,
pub line: u32,
// [number, number] tuple
pub range: Vec<u32>,
}
impl From<edr_solidity::solidity_stack_trace::SourceReference> for SourceReference {
fn from(value: edr_solidity::solidity_stack_trace::SourceReference) -> Self {
let (range_start, range_end) = value.range;
Self {
source_name: value.source_name,
source_content: value.source_content,
contract: value.contract,
function: value.function,
line: value.line,
range: vec![range_start, range_end],
}
}
}
impl From<edr_solidity::return_data::CheatcodeErrorDetails> for CheatcodeErrorDetails {
fn from(value: edr_solidity::return_data::CheatcodeErrorDetails) -> Self {
Self {
code: value.code.into(),
cheatcode: value.cheatcode,
}
}
}
/// A [`StackTraceEntryType`] constant that is convertible to/from a
/// `napi_value`.
///
/// Since Rust does not allow constants directly as members, we use this wrapper
/// to allow the `StackTraceEntryType` to be used as a member of an interface
/// when defining the N-API bindings.
// NOTE: It's currently not possible to use an enum as const generic parameter,
// so we use the underlying `u8` repr used by the enum.
#[derive(Clone, Copy)]
pub struct StackTraceEntryTypeConst<const ENTRY_TYPE: u8>;
impl<const ENTRY_TYPE: u8> FromNapiValue for StackTraceEntryTypeConst<ENTRY_TYPE> {
unsafe fn from_napi_value(
env: napi::sys::napi_env,
napi_val: napi::sys::napi_value,
) -> napi::Result<Self> {
// SAFETY: The safety concern is propagated in the function signature.
let inner: u8 = unsafe { FromNapiValue::from_napi_value(env, napi_val) }?;
if inner != ENTRY_TYPE {
return Err(napi::Error::new(
napi::Status::InvalidArg,
format!("Expected StackTraceEntryType value: {ENTRY_TYPE}, got: {inner}"),
));
}
Ok(StackTraceEntryTypeConst)
}
}
impl<const ENTRY_TYPE: u8> ToNapiValue for StackTraceEntryTypeConst<ENTRY_TYPE> {
unsafe fn to_napi_value(
env: napi::sys::napi_env,
_val: Self,
) -> napi::Result<napi::sys::napi_value> {
// SAFETY: The safety concern is propagated in the function signature.
unsafe { u8::to_napi_value(env, ENTRY_TYPE) }
}
}
impl<const ENTRY_TYPE: u8> Serialize for StackTraceEntryTypeConst<ENTRY_TYPE> {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
let inner = StackTraceEntryType::from_repr(ENTRY_TYPE).ok_or_else(|| {
serde::ser::Error::custom(format!("Invalid StackTraceEntryType value: {ENTRY_TYPE}"))
})?;
inner.serialize(serializer)
}
}
#[napi(object)]
#[derive(Clone, Serialize)]
pub struct CallstackEntryStackTraceEntry {
#[napi(js_name = "type", ts_type = "StackTraceEntryType.CALLSTACK_ENTRY")]
pub type_: StackTraceEntryTypeConst<{ StackTraceEntryType::CALLSTACK_ENTRY as u8 }>,
pub source_reference: SourceReference,
pub function_type: ContractFunctionType,
}
impl From<CallstackEntryStackTraceEntry> for SolidityStackTraceEntry {
fn from(val: CallstackEntryStackTraceEntry) -> Self {
Either25::A(val)
}
}
#[napi(object)]
#[derive(Clone, Serialize)]
pub struct UnrecognizedCreateCallstackEntryStackTraceEntry {
#[napi(
js_name = "type",
ts_type = "StackTraceEntryType.UNRECOGNIZED_CREATE_CALLSTACK_ENTRY"
)]
pub type_: StackTraceEntryTypeConst<
{ StackTraceEntryType::UNRECOGNIZED_CREATE_CALLSTACK_ENTRY as u8 },
>,
pub source_reference: Option<Undefined>,
}
impl From<UnrecognizedCreateCallstackEntryStackTraceEntry> for SolidityStackTraceEntry {
fn from(val: UnrecognizedCreateCallstackEntryStackTraceEntry) -> Self {
Either25::B(val)
}
}
#[napi(object)]
#[derive(Clone, Serialize)]
pub struct UnrecognizedContractCallstackEntryStackTraceEntry {
#[napi(
js_name = "type",
ts_type = "StackTraceEntryType.UNRECOGNIZED_CONTRACT_CALLSTACK_ENTRY"
)]
pub type_: StackTraceEntryTypeConst<
{ StackTraceEntryType::UNRECOGNIZED_CONTRACT_CALLSTACK_ENTRY as u8 },
>,
#[serde(serialize_with = "serialize_uint8array_to_hex")]
pub address: Uint8Array,
pub source_reference: Option<Undefined>,
}
impl From<UnrecognizedContractCallstackEntryStackTraceEntry> for SolidityStackTraceEntry {
fn from(val: UnrecognizedContractCallstackEntryStackTraceEntry) -> Self {
Either25::C(val)
}
}
#[napi(object)]
#[derive(Clone, Serialize)]
pub struct PrecompileErrorStackTraceEntry {
#[napi(js_name = "type", ts_type = "StackTraceEntryType.PRECOMPILE_ERROR")]
pub type_: StackTraceEntryTypeConst<{ StackTraceEntryType::PRECOMPILE_ERROR as u8 }>,
pub precompile: u32,
pub source_reference: Option<Undefined>,
}
impl From<PrecompileErrorStackTraceEntry> for SolidityStackTraceEntry {
fn from(val: PrecompileErrorStackTraceEntry) -> Self {
Either25::D(val)
}
}
#[napi(object)]
#[derive(Clone, Serialize)]
pub struct RevertErrorStackTraceEntry {
#[napi(js_name = "type", ts_type = "StackTraceEntryType.REVERT_ERROR")]
pub type_: StackTraceEntryTypeConst<{ StackTraceEntryType::REVERT_ERROR as u8 }>,
#[serde(serialize_with = "serialize_uint8array_to_hex")]
pub return_data: Uint8Array,
pub source_reference: SourceReference,
pub is_invalid_opcode_error: bool,
}
impl From<RevertErrorStackTraceEntry> for SolidityStackTraceEntry {
fn from(val: RevertErrorStackTraceEntry) -> Self {
Either25::E(val)
}
}
#[napi(object)]
#[derive(Clone, Serialize)]
pub struct PanicErrorStackTraceEntry {
#[napi(js_name = "type", ts_type = "StackTraceEntryType.PANIC_ERROR")]
pub type_: StackTraceEntryTypeConst<{ StackTraceEntryType::PANIC_ERROR as u8 }>,
#[serde(serialize_with = "serialize_evm_value_bigint_using_u256")]
pub error_code: BigInt,
pub source_reference: Option<SourceReference>,
}
impl From<PanicErrorStackTraceEntry> for SolidityStackTraceEntry {
fn from(val: PanicErrorStackTraceEntry) -> Self {
Either25::F(val)
}
}
#[napi(object)]
#[derive(Clone, Serialize)]
pub struct CustomErrorStackTraceEntry {
#[napi(js_name = "type", ts_type = "StackTraceEntryType.CUSTOM_ERROR")]
pub type_: StackTraceEntryTypeConst<{ StackTraceEntryType::CUSTOM_ERROR as u8 }>,
// unlike RevertErrorStackTraceEntry, this includes the message already parsed
pub message: String,
pub source_reference: SourceReference,
}
impl From<CustomErrorStackTraceEntry> for SolidityStackTraceEntry {
fn from(val: CustomErrorStackTraceEntry) -> Self {
Either25::G(val)
}
}
#[napi(object)]
#[derive(Clone, Serialize)]
pub struct FunctionNotPayableErrorStackTraceEntry {
#[napi(
js_name = "type",
ts_type = "StackTraceEntryType.FUNCTION_NOT_PAYABLE_ERROR"
)]
pub type_: StackTraceEntryTypeConst<{ StackTraceEntryType::FUNCTION_NOT_PAYABLE_ERROR as u8 }>,
#[serde(serialize_with = "serialize_evm_value_bigint_using_u256")]
pub value: BigInt,
pub source_reference: SourceReference,
}
impl From<FunctionNotPayableErrorStackTraceEntry> for SolidityStackTraceEntry {
fn from(val: FunctionNotPayableErrorStackTraceEntry) -> Self {
Either25::H(val)
}
}
#[napi(object)]
#[derive(Clone, Serialize)]
pub struct InvalidParamsErrorStackTraceEntry {
#[napi(js_name = "type", ts_type = "StackTraceEntryType.INVALID_PARAMS_ERROR")]
pub type_: StackTraceEntryTypeConst<{ StackTraceEntryType::INVALID_PARAMS_ERROR as u8 }>,
pub source_reference: SourceReference,
}
impl From<InvalidParamsErrorStackTraceEntry> for SolidityStackTraceEntry {
fn from(val: InvalidParamsErrorStackTraceEntry) -> Self {
Either25::I(val)
}
}
#[napi(object)]
#[derive(Clone, Serialize)]
pub struct FallbackNotPayableErrorStackTraceEntry {
#[napi(
js_name = "type",
ts_type = "StackTraceEntryType.FALLBACK_NOT_PAYABLE_ERROR"
)]
pub type_: StackTraceEntryTypeConst<{ StackTraceEntryType::FALLBACK_NOT_PAYABLE_ERROR as u8 }>,
#[serde(serialize_with = "serialize_evm_value_bigint_using_u256")]
pub value: BigInt,
pub source_reference: SourceReference,
}
impl From<FallbackNotPayableErrorStackTraceEntry> for SolidityStackTraceEntry {
fn from(val: FallbackNotPayableErrorStackTraceEntry) -> Self {
Either25::J(val)
}
}
#[napi(object)]
#[derive(Clone, Serialize)]
pub struct FallbackNotPayableAndNoReceiveErrorStackTraceEntry {
#[napi(
js_name = "type",
ts_type = "StackTraceEntryType.FALLBACK_NOT_PAYABLE_AND_NO_RECEIVE_ERROR"
)]
pub type_: StackTraceEntryTypeConst<
{ StackTraceEntryType::FALLBACK_NOT_PAYABLE_AND_NO_RECEIVE_ERROR as u8 },
>,
#[serde(serialize_with = "serialize_evm_value_bigint_using_u256")]
pub value: BigInt,
pub source_reference: SourceReference,
}
impl From<FallbackNotPayableAndNoReceiveErrorStackTraceEntry> for SolidityStackTraceEntry {
fn from(val: FallbackNotPayableAndNoReceiveErrorStackTraceEntry) -> Self {
Either25::K(val)
}
}
#[napi(object)]
#[derive(Clone, Serialize)]
pub struct UnrecognizedFunctionWithoutFallbackErrorStackTraceEntry {
#[napi(
js_name = "type",
ts_type = "StackTraceEntryType.UNRECOGNIZED_FUNCTION_WITHOUT_FALLBACK_ERROR"
)]
pub type_: StackTraceEntryTypeConst<
{ StackTraceEntryType::UNRECOGNIZED_FUNCTION_WITHOUT_FALLBACK_ERROR as u8 },
>,
pub source_reference: SourceReference,
}
impl From<UnrecognizedFunctionWithoutFallbackErrorStackTraceEntry> for SolidityStackTraceEntry {
fn from(val: UnrecognizedFunctionWithoutFallbackErrorStackTraceEntry) -> Self {
Either25::L(val)
}
}
#[napi(object)]
#[derive(Clone, Serialize)]
pub struct MissingFallbackOrReceiveErrorStackTraceEntry {
#[napi(
js_name = "type",
ts_type = "StackTraceEntryType.MISSING_FALLBACK_OR_RECEIVE_ERROR"
)]
pub type_:
StackTraceEntryTypeConst<{ StackTraceEntryType::MISSING_FALLBACK_OR_RECEIVE_ERROR as u8 }>,
pub source_reference: SourceReference,
}
impl From<MissingFallbackOrReceiveErrorStackTraceEntry> for SolidityStackTraceEntry {
fn from(val: MissingFallbackOrReceiveErrorStackTraceEntry) -> Self {
Either25::M(val)
}
}
#[napi(object)]
#[derive(Clone, Serialize)]
pub struct ReturndataSizeErrorStackTraceEntry {
#[napi(
js_name = "type",
ts_type = "StackTraceEntryType.RETURNDATA_SIZE_ERROR"
)]
pub type_: StackTraceEntryTypeConst<{ StackTraceEntryType::RETURNDATA_SIZE_ERROR as u8 }>,
pub source_reference: SourceReference,
}
impl From<ReturndataSizeErrorStackTraceEntry> for SolidityStackTraceEntry {
fn from(val: ReturndataSizeErrorStackTraceEntry) -> Self {
Either25::N(val)
}
}
#[napi(object)]
#[derive(Clone, Serialize)]
pub struct NonContractAccountCalledErrorStackTraceEntry {
#[napi(
js_name = "type",
ts_type = "StackTraceEntryType.NONCONTRACT_ACCOUNT_CALLED_ERROR"
)]
pub type_:
StackTraceEntryTypeConst<{ StackTraceEntryType::NONCONTRACT_ACCOUNT_CALLED_ERROR as u8 }>,
pub source_reference: SourceReference,
}
impl From<NonContractAccountCalledErrorStackTraceEntry> for SolidityStackTraceEntry {
fn from(val: NonContractAccountCalledErrorStackTraceEntry) -> Self {
Either25::O(val)
}
}
#[napi(object)]
#[derive(Clone, Serialize)]
pub struct CallFailedErrorStackTraceEntry {
#[napi(js_name = "type", ts_type = "StackTraceEntryType.CALL_FAILED_ERROR")]
pub type_: StackTraceEntryTypeConst<{ StackTraceEntryType::CALL_FAILED_ERROR as u8 }>,
pub source_reference: SourceReference,
}
impl From<CallFailedErrorStackTraceEntry> for SolidityStackTraceEntry {
fn from(val: CallFailedErrorStackTraceEntry) -> Self {
Either25::P(val)
}
}
#[napi(object)]
#[derive(Clone, Serialize)]
pub struct DirectLibraryCallErrorStackTraceEntry {
#[napi(
js_name = "type",
ts_type = "StackTraceEntryType.DIRECT_LIBRARY_CALL_ERROR"
)]
pub type_: StackTraceEntryTypeConst<{ StackTraceEntryType::DIRECT_LIBRARY_CALL_ERROR as u8 }>,
pub source_reference: SourceReference,
}
impl From<DirectLibraryCallErrorStackTraceEntry> for SolidityStackTraceEntry {
fn from(val: DirectLibraryCallErrorStackTraceEntry) -> Self {
Either25::Q(val)
}
}
#[napi(object)]
#[derive(Clone, Serialize)]
pub struct UnrecognizedCreateErrorStackTraceEntry {
#[napi(
js_name = "type",
ts_type = "StackTraceEntryType.UNRECOGNIZED_CREATE_ERROR"
)]
pub type_: StackTraceEntryTypeConst<{ StackTraceEntryType::UNRECOGNIZED_CREATE_ERROR as u8 }>,
#[serde(serialize_with = "serialize_uint8array_to_hex")]
pub return_data: Uint8Array,
pub source_reference: Option<Undefined>,
pub is_invalid_opcode_error: bool,
}
impl From<UnrecognizedCreateErrorStackTraceEntry> for SolidityStackTraceEntry {
fn from(val: UnrecognizedCreateErrorStackTraceEntry) -> Self {
Either25::R(val)
}
}
#[napi(object)]
#[derive(Clone, Serialize)]
pub struct UnrecognizedContractErrorStackTraceEntry {
#[napi(
js_name = "type",
ts_type = "StackTraceEntryType.UNRECOGNIZED_CONTRACT_ERROR"
)]
pub type_: StackTraceEntryTypeConst<{ StackTraceEntryType::UNRECOGNIZED_CONTRACT_ERROR as u8 }>,
#[serde(serialize_with = "serialize_uint8array_to_hex")]
pub address: Uint8Array,
#[serde(serialize_with = "serialize_uint8array_to_hex")]
pub return_data: Uint8Array,
pub source_reference: Option<Undefined>,
pub is_invalid_opcode_error: bool,
}
impl From<UnrecognizedContractErrorStackTraceEntry> for SolidityStackTraceEntry {
fn from(val: UnrecognizedContractErrorStackTraceEntry) -> Self {
Either25::S(val)
}
}
#[napi(object)]
#[derive(Clone, Serialize)]
pub struct OtherExecutionErrorStackTraceEntry {
#[napi(
js_name = "type",
ts_type = "StackTraceEntryType.OTHER_EXECUTION_ERROR"
)]
pub type_: StackTraceEntryTypeConst<{ StackTraceEntryType::OTHER_EXECUTION_ERROR as u8 }>,
pub source_reference: Option<SourceReference>,
}
impl From<OtherExecutionErrorStackTraceEntry> for SolidityStackTraceEntry {
fn from(val: OtherExecutionErrorStackTraceEntry) -> Self {
Either25::T(val)
}
}
#[napi(object)]
#[derive(Clone, Serialize)]
pub struct UnmappedSolc063RevertErrorStackTraceEntry {
#[napi(
js_name = "type",
ts_type = "StackTraceEntryType.UNMAPPED_SOLC_0_6_3_REVERT_ERROR"
)]
pub type_:
StackTraceEntryTypeConst<{ StackTraceEntryType::UNMAPPED_SOLC_0_6_3_REVERT_ERROR as u8 }>,
pub source_reference: Option<SourceReference>,
}
impl From<UnmappedSolc063RevertErrorStackTraceEntry> for SolidityStackTraceEntry {
fn from(val: UnmappedSolc063RevertErrorStackTraceEntry) -> Self {
Either25::U(val)
}
}
#[napi(object)]
#[derive(Clone, Serialize)]
pub struct ContractTooLargeErrorStackTraceEntry {
#[napi(
js_name = "type",
ts_type = "StackTraceEntryType.CONTRACT_TOO_LARGE_ERROR"
)]
pub type_: StackTraceEntryTypeConst<{ StackTraceEntryType::CONTRACT_TOO_LARGE_ERROR as u8 }>,
pub source_reference: Option<SourceReference>,
}
impl From<ContractTooLargeErrorStackTraceEntry> for SolidityStackTraceEntry {
fn from(val: ContractTooLargeErrorStackTraceEntry) -> Self {
Either25::V(val)
}
}
#[napi(object)]
#[derive(Clone, Serialize)]
pub struct InternalFunctionCallStackEntry {
#[napi(
js_name = "type",
ts_type = "StackTraceEntryType.INTERNAL_FUNCTION_CALLSTACK_ENTRY"
)]
pub type_:
StackTraceEntryTypeConst<{ StackTraceEntryType::INTERNAL_FUNCTION_CALLSTACK_ENTRY as u8 }>,
pub pc: u32,
pub source_reference: SourceReference,
}
impl From<InternalFunctionCallStackEntry> for SolidityStackTraceEntry {
fn from(val: InternalFunctionCallStackEntry) -> Self {
Either25::W(val)
}
}
#[napi(object)]
#[derive(Clone, Serialize)]
pub struct ContractCallRunOutOfGasError {
#[napi(
js_name = "type",
ts_type = "StackTraceEntryType.CONTRACT_CALL_RUN_OUT_OF_GAS_ERROR"
)]
pub type_:
StackTraceEntryTypeConst<{ StackTraceEntryType::CONTRACT_CALL_RUN_OUT_OF_GAS_ERROR as u8 }>,
pub source_reference: Option<SourceReference>,
}
impl From<ContractCallRunOutOfGasError> for SolidityStackTraceEntry {
fn from(val: ContractCallRunOutOfGasError) -> Self {
Either25::X(val)
}
}
#[napi(object)]
#[derive(Clone, Serialize)]
pub struct CheatcodeErrorStackTraceEntry {
#[napi(js_name = "type", ts_type = "StackTraceEntryType.CHEATCODE_ERROR")]
pub type_: StackTraceEntryTypeConst<{ StackTraceEntryType::CHEATCODE_ERROR as u8 }>,
// The parsed cheatcode error message that can be displayed to the user
pub message: String,
pub source_reference: SourceReference,
pub details: Option<CheatcodeErrorDetails>,
}
impl From<CheatcodeErrorStackTraceEntry> for SolidityStackTraceEntry {
fn from(val: CheatcodeErrorStackTraceEntry) -> Self {
Either25::Y(val)
}
}
#[allow(dead_code)]
// NOTE: This ported directly from JS for completeness and is used in the Rust
// side of the bindings. However, napi-rs does not support exporting Rust type
// aliases to the index.d.ts file, and it does not store the type definitions
// when expanding the macros, so to use it we would have to specify this type
// literally (all 26 lines of it) at every #[napi]-exported function, which is
// not ideal.
// Rather, we just bite the bullet for now and use the type alias directly
// (which falls back to `any` as it's not recognized in the context of the
// index.d.ts file) until we finish the porting work.
pub type SolidityStackTraceEntry = Either25<
CallstackEntryStackTraceEntry,
UnrecognizedCreateCallstackEntryStackTraceEntry,
UnrecognizedContractCallstackEntryStackTraceEntry,
PrecompileErrorStackTraceEntry,
RevertErrorStackTraceEntry,
PanicErrorStackTraceEntry,
CustomErrorStackTraceEntry,
FunctionNotPayableErrorStackTraceEntry,
InvalidParamsErrorStackTraceEntry,
FallbackNotPayableErrorStackTraceEntry,
FallbackNotPayableAndNoReceiveErrorStackTraceEntry,
UnrecognizedFunctionWithoutFallbackErrorStackTraceEntry,
MissingFallbackOrReceiveErrorStackTraceEntry,
ReturndataSizeErrorStackTraceEntry,
NonContractAccountCalledErrorStackTraceEntry,
CallFailedErrorStackTraceEntry,
DirectLibraryCallErrorStackTraceEntry,
UnrecognizedCreateErrorStackTraceEntry,
UnrecognizedContractErrorStackTraceEntry,
OtherExecutionErrorStackTraceEntry,
UnmappedSolc063RevertErrorStackTraceEntry,
ContractTooLargeErrorStackTraceEntry,
InternalFunctionCallStackEntry,
ContractCallRunOutOfGasError,
CheatcodeErrorStackTraceEntry,
>;
impl TryCast<SolidityStackTraceEntry> for edr_solidity::solidity_stack_trace::StackTraceEntry {
type Error = Infallible;
fn try_cast(self) -> Result<SolidityStackTraceEntry, Self::Error> {
use edr_solidity::solidity_stack_trace::StackTraceEntry;
let result = match self {
StackTraceEntry::CallstackEntry {
source_reference,
function_type,
} => CallstackEntryStackTraceEntry {
type_: StackTraceEntryTypeConst,
source_reference: source_reference.into(),
function_type: function_type.into(),
}
.into(),
StackTraceEntry::UnrecognizedCreateCallstackEntry => {
UnrecognizedCreateCallstackEntryStackTraceEntry {
type_: StackTraceEntryTypeConst,
source_reference: None,
}
.into()
}
StackTraceEntry::UnrecognizedContractCallstackEntry { address } => {
UnrecognizedContractCallstackEntryStackTraceEntry {
type_: StackTraceEntryTypeConst,
address: Uint8Array::with_data_copied(address),
source_reference: None,
}
.into()
}
StackTraceEntry::PrecompileError { precompile } => PrecompileErrorStackTraceEntry {
type_: StackTraceEntryTypeConst,
precompile,
source_reference: None,
}
.into(),
StackTraceEntry::RevertError {
return_data,
source_reference,
is_invalid_opcode_error,
} => RevertErrorStackTraceEntry {
type_: StackTraceEntryTypeConst,
return_data: return_data.into(),
source_reference: source_reference.into(),
is_invalid_opcode_error,
}
.into(),
StackTraceEntry::PanicError {
error_code,
source_reference,
} => PanicErrorStackTraceEntry {
type_: StackTraceEntryTypeConst,
error_code: u256_to_bigint(&error_code),
source_reference: source_reference.map(std::convert::Into::into),
}
.into(),
StackTraceEntry::CheatCodeError {
message,
source_reference,
details,
} => CheatcodeErrorStackTraceEntry {
type_: StackTraceEntryTypeConst,
message,
source_reference: source_reference.into(),
details: details.map(std::convert::Into::into),
}
.into(),
StackTraceEntry::CustomError {
message,
source_reference,
} => CustomErrorStackTraceEntry {
type_: StackTraceEntryTypeConst,
message,
source_reference: source_reference.into(),
}
.into(),
StackTraceEntry::FunctionNotPayableError {
value,
source_reference,
} => FunctionNotPayableErrorStackTraceEntry {
type_: StackTraceEntryTypeConst,
value: u256_to_bigint(&value),
source_reference: source_reference.into(),
}
.into(),
StackTraceEntry::InvalidParamsError { source_reference } => {
InvalidParamsErrorStackTraceEntry {
type_: StackTraceEntryTypeConst,
source_reference: source_reference.into(),
}
.into()
}
StackTraceEntry::FallbackNotPayableError {
value,
source_reference,
} => FallbackNotPayableErrorStackTraceEntry {
type_: StackTraceEntryTypeConst,
value: u256_to_bigint(&value),
source_reference: source_reference.into(),
}
.into(),
StackTraceEntry::FallbackNotPayableAndNoReceiveError {
value,
source_reference,
} => FallbackNotPayableAndNoReceiveErrorStackTraceEntry {
type_: StackTraceEntryTypeConst,
value: u256_to_bigint(&value),
source_reference: source_reference.into(),
}
.into(),
StackTraceEntry::UnrecognizedFunctionWithoutFallbackError { source_reference } => {
UnrecognizedFunctionWithoutFallbackErrorStackTraceEntry {
type_: StackTraceEntryTypeConst,
source_reference: source_reference.into(),
}
.into()
}
StackTraceEntry::MissingFallbackOrReceiveError { source_reference } => {
MissingFallbackOrReceiveErrorStackTraceEntry {
type_: StackTraceEntryTypeConst,
source_reference: source_reference.into(),
}
.into()
}
StackTraceEntry::ReturndataSizeError { source_reference } => {
ReturndataSizeErrorStackTraceEntry {
type_: StackTraceEntryTypeConst,
source_reference: source_reference.into(),
}
.into()
}
StackTraceEntry::NoncontractAccountCalledError { source_reference } => {
NonContractAccountCalledErrorStackTraceEntry {
type_: StackTraceEntryTypeConst,
source_reference: source_reference.into(),
}
.into()
}
StackTraceEntry::CallFailedError { source_reference } => {
CallFailedErrorStackTraceEntry {
type_: StackTraceEntryTypeConst,
source_reference: source_reference.into(),
}
.into()
}
StackTraceEntry::DirectLibraryCallError { source_reference } => {
DirectLibraryCallErrorStackTraceEntry {
type_: StackTraceEntryTypeConst,
source_reference: source_reference.into(),
}
.into()
}
StackTraceEntry::UnrecognizedCreateError {
return_data,
is_invalid_opcode_error,
} => UnrecognizedCreateErrorStackTraceEntry {
type_: StackTraceEntryTypeConst,
return_data: return_data.into(),
is_invalid_opcode_error,
source_reference: None,
}
.into(),
StackTraceEntry::UnrecognizedContractError {
address,
return_data,
is_invalid_opcode_error,
} => UnrecognizedContractErrorStackTraceEntry {
type_: StackTraceEntryTypeConst,
address: Uint8Array::with_data_copied(address),
return_data: return_data.into(),
is_invalid_opcode_error,
source_reference: None,
}
.into(),
StackTraceEntry::OtherExecutionError { source_reference } => {
OtherExecutionErrorStackTraceEntry {
type_: StackTraceEntryTypeConst,
source_reference: source_reference.map(std::convert::Into::into),
}
.into()
}
StackTraceEntry::UnmappedSolc0_6_3RevertError { source_reference } => {
UnmappedSolc063RevertErrorStackTraceEntry {
type_: StackTraceEntryTypeConst,
source_reference: source_reference.map(std::convert::Into::into),
}
.into()
}
StackTraceEntry::ContractTooLargeError { source_reference } => {
ContractTooLargeErrorStackTraceEntry {
type_: StackTraceEntryTypeConst,
source_reference: source_reference.map(std::convert::Into::into),
}
.into()
}
StackTraceEntry::InternalFunctionCallstackEntry {
pc,
source_reference,
} => InternalFunctionCallStackEntry {
type_: StackTraceEntryTypeConst,
pc,
source_reference: source_reference.into(),
}
.into(),
StackTraceEntry::ContractCallRunOutOfGasError { source_reference } => {
ContractCallRunOutOfGasError {
type_: StackTraceEntryTypeConst,
source_reference: source_reference.map(std::convert::Into::into),
}
.into()
}
};
Ok(result)
}
}
#[allow(dead_code)]
// Same as above, but for the `SolidityStackTrace` type.
pub type SolidityStackTrace = Vec<SolidityStackTraceEntry>;
const _: () = {
const fn assert_to_from_napi_value<T: FromNapiValue + ToNapiValue>() {}
assert_to_from_napi_value::<SolidityStackTraceEntry>();
};
/// Serializes a [`BigInt`] that represents an EVM value as a
/// [`edr_primitives::U256`].
fn serialize_evm_value_bigint_using_u256<S>(bigint: &BigInt, s: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let val = U256::from_limbs_slice(&bigint.words);
val.serialize(s)
}
fn serialize_uint8array_to_hex<S>(uint8array: &Uint8Array, s: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let hex = hex::encode(uint8array.as_ref());
hex.serialize(s)
}

View File

@@ -0,0 +1,46 @@
import { StandardTestKind, FuzzTestKind, InvariantTestKind } from "../../index";
export enum SortOrder {
Ascending,
Descending,
}
export interface GasUsageFilter {
minThreshold?: bigint;
maxThreshold?: bigint;
}
export function extractGasUsage(
testResults: {
name: string;
kind: StandardTestKind | FuzzTestKind | InvariantTestKind;
}[],
filter?: GasUsageFilter,
ordering?: SortOrder
): { name: string; gas: bigint }[] {
const gasUsage: { name: string; gas: bigint }[] = [];
for (const result of testResults) {
// Default to zero gas for invariant tests
const gas = "consumedGas" in result.kind
? result.kind.consumedGas
: "medianGas" in result.kind
? result.kind.medianGas
: BigInt(0);
if (
(!filter?.minThreshold || gas >= filter.minThreshold) &&
(!filter?.maxThreshold || gas <= filter.maxThreshold)
) {
gasUsage.push({ name: result.name, gas });
}
}
if (ordering === SortOrder.Ascending) {
gasUsage.sort((a, b) => (a.gas < b.gas ? -1 : a.gas > b.gas ? 1 : 0));
} else if (ordering === SortOrder.Descending) {
gasUsage.sort((a, b) => (a.gas > b.gas ? -1 : a.gas < b.gas ? 1 : 0));
}
return gasUsage;
}

49
dev/env/node_modules/@nomicfoundation/edr/src/withdrawal.rs generated vendored Executable file
View File

@@ -0,0 +1,49 @@
use edr_primitives::Address;
use napi::bindgen_prelude::{BigInt, Uint8Array};
use napi_derive::napi;
use crate::cast::TryCast as _;
#[napi(object)]
pub struct Withdrawal {
/// The index of withdrawal
pub index: BigInt,
/// The index of the validator that generated the withdrawal
pub validator_index: BigInt,
/// The recipient address for withdrawal value
pub address: Uint8Array,
/// The value contained in withdrawal
pub amount: BigInt,
}
impl From<edr_block_header::Withdrawal> for Withdrawal {
fn from(withdrawal: edr_block_header::Withdrawal) -> Self {
Self {
index: BigInt::from(withdrawal.index),
validator_index: BigInt::from(withdrawal.validator_index),
address: Uint8Array::with_data_copied(withdrawal.address),
amount: BigInt {
sign_bit: false,
words: vec![withdrawal.amount],
},
}
}
}
impl TryFrom<Withdrawal> for edr_block_header::Withdrawal {
type Error = napi::Error;
fn try_from(value: Withdrawal) -> Result<Self, Self::Error> {
let index: u64 = BigInt::try_cast(value.index)?;
let validator_index: u64 = BigInt::try_cast(value.validator_index)?;
let amount = BigInt::try_cast(value.amount)?;
let address = Address::from_slice(&value.address);
Ok(Self {
index,
validator_index,
address,
amount,
})
}
}

View File

@@ -0,0 +1,53 @@
# @nomicfoundation/hardhat-errors
## 3.0.7
### Patch Changes
- 6674b00: Bump `hardhat-utils` major
- 4cd63e9: Introduce the `@nomicfoundation/hardhat-foundry` plugin for Hardhat 3
- f1e9b05: Added support for `inline actions` in tasks [7851](https://github.com/NomicFoundation/hardhat/pull/7851).
## 3.0.6
### Patch Changes
- 6b2ed9a: Add ability for task options to be hidden from the CLI ([#7426](https://github.com/NomicFoundation/hardhat/issues/7426))
## 3.0.5
### Patch Changes
- 03a4539: Export error descriptors for the website
- 95684ac: Full links to documentation replaced by short links with redirects added to the Hardhat website ([#142](https://github.com/NomicFoundation/hardhat-website/issues/142))
## 3.0.4
### Patch Changes
- ce5c22a: Fail when a file isn't built neither as contract nor test
## 3.0.3
### Patch Changes
- a871e3e: Ported the `@nomicfoundation/hardhat-ledger` plugin to Hardhat 3 ([#5646](https://github.com/NomicFoundation/hardhat/issues/5646))
## 3.0.2
### Patch Changes
- be469d6: Display an error message when attempting to use a global hardhat installation in a local repo ([#5362](https://github.com/NomicFoundation/hardhat/issues/5362))
- 8d3b16c: Support for custom compilers ([#7130](https://github.com/NomicFoundation/hardhat/issues/7130))
## 3.0.1
### Patch Changes
- ddefbff: Added guard to stop multiple simultaneous calls to `ignition.deploy(...)` at once ([#6440](https://github.com/NomicFoundation/hardhat/issues/6440))
## 3.0.0
### Major Changes
- 29cc141: First release of Hardhat 3!

View File

@@ -0,0 +1,9 @@
MIT License
Copyright (c) 2024 Nomic Foundation
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

View File

@@ -0,0 +1,12 @@
# hardhat-errors
> ⚠️ This package is an internal Hardhat component and it's not meant to be used directly.
This packages has the definition of the error classes used by Hardhat, and the list of possible errors.
This module exports:
1. The error class `HardhatError`, which has a static field `ERRORS`, with the different `ErrorDescriptors` that it accepts.
2. The error class `HardhatPluginError`, which is the recommended way to handle errors in Hardhat plugins. For convenience, it is re-exported from `@nomicfoundation/hardhat/plugins`. Plugin developers should import it from this path.
3. The interface `ErrorDescriptor`.
4. The assertion helper `assertHardhatInvariant`.

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1 @@
{"version":3,"file":"descriptors.d.ts","sourceRoot":"","sources":["../../src/descriptors.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,MAAM,WAAW,eAAe;IAC9B;;OAEG;IACH,MAAM,EAAE,MAAM,CAAC;IAEf;;;;;;;OAOG;IACH,eAAe,EAAE,MAAM,CAAC;IAExB;;OAEG;IACH,gBAAgB,CAAC,EAAE,IAAI,CAAC;IAExB;;;OAGG;IACH,YAAY,EAAE,MAAM,CAAC;IAErB;;;OAGG;IACH,kBAAkB,EAAE,MAAM,CAAC;CAC5B;AAED,eAAO,MAAM,gBAAgB,EAAE;IAC7B,CAAC,WAAW,EAAE,MAAM,GAAG;QACrB,GAAG,EAAE,MAAM,CAAC;QACZ,GAAG,EAAE,MAAM,CAAC;QACZ,QAAQ,EAAE,MAAM,GAAG,SAAS,CAAC;QAC7B,YAAY,EAAE,MAAM,CAAC;QACrB,UAAU,EAAE;YACV,CAAC,YAAY,EAAE,MAAM,GAAG;gBACtB,GAAG,EAAE,MAAM,CAAC;gBACZ,GAAG,EAAE,MAAM,CAAC;gBACZ,eAAe,EAAE,MAAM,CAAC;aACzB,CAAC;SACH,CAAC;KACH,CAAC;CAwRH,CAAC;AAEF,eAAO,MAAM,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAskFT,CAAC"}

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,66 @@
import type { ErrorDescriptor } from "./descriptors.js";
import { CustomError } from "@nomicfoundation/hardhat-utils/error";
import { ERRORS } from "./descriptors.js";
export type ErrorMessageTemplateValue = string | number | boolean | bigint | undefined | null | ErrorMessageTemplateValue[] | {
toString(): string;
};
export type MessagetTemplateArguments<MessageTemplateT extends string> = MessageTemplateT extends `${string}{${infer Tag}}${infer Rest}` ? {
[K in Tag | keyof MessagetTemplateArguments<Rest>]: ErrorMessageTemplateValue;
} : {};
export type HardhatErrorConstructorArguments<ErrorDescriptorT extends ErrorDescriptor> = keyof MessagetTemplateArguments<ErrorDescriptorT["messageTemplate"]> extends never ? [ErrorDescriptorT, Error?] : [
ErrorDescriptorT,
MessagetTemplateArguments<ErrorDescriptorT["messageTemplate"]>,
Error?
];
export declare const ERROR_PREFIX = "HHE";
/**
* An error thrown by Hardhat. This error is meant to be thrown by Hardhat
* itself, and internal plugins. For errors thrown by community plugins, see
* `HardhatPluginError`.
*/
export declare class HardhatError<ErrorDescriptorT extends ErrorDescriptor = ErrorDescriptor> extends CustomError {
#private;
static readonly ERRORS: typeof ERRORS;
constructor(...[errorDescriptor, messageArgumentsOrParentError, parentError,]: HardhatErrorConstructorArguments<ErrorDescriptorT>);
static isHardhatError(other: unknown): other is HardhatError<ErrorDescriptor>;
static isHardhatError<ErrorDescriptorT extends ErrorDescriptor>(other: unknown, descriptor?: ErrorDescriptorT): other is HardhatError<ErrorDescriptorT>;
get number(): number;
get pluginId(): string | undefined;
get descriptor(): ErrorDescriptor;
get messageArguments(): MessagetTemplateArguments<ErrorDescriptorT["messageTemplate"]>;
get errorCode(): string;
get formattedMessage(): string;
}
/**
* An error thrown by a Hardhat plugin. This error is meant to be thrown by
* community plugins to signal that something went wrong.
*/
export declare class HardhatPluginError extends CustomError {
readonly pluginId: string;
constructor(pluginId: string, message: string, parentError?: Error);
static isHardhatPluginError(other: unknown): other is HardhatPluginError;
}
/**
* Asserts an internal invariant.
*
* @param invariant The condition to check.
* @param message A message to show if the condition is false.
*/
export declare function assertHardhatInvariant(invariant: boolean, message: string): asserts invariant;
/**
* This function applies error messages templates like this:
*
* - Template is a string which contains a variable tags. A variable tag is a
* a variable name surrounded by %. Eg: %plugin1%
* - A variable name is a string of alphanumeric ascii characters.
* - Every variable tag is replaced by its value.
* - %% is replaced by %.
* - Values can't contain variable tags.
* - If a variable is not present in the template, but present in the values
* object, an error is thrown.
*
* @param template The template string.
* @param values A map of variable names to their values.
*/
export declare function applyErrorMessageTemplate(template: string, values: Record<string, ErrorMessageTemplateValue>): string;
//# sourceMappingURL=errors.d.ts.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../../src/errors.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAC;AAExD,OAAO,EAAE,WAAW,EAAE,MAAM,sCAAsC,CAAC;AAGnE,OAAO,EAAE,MAAM,EAAoB,MAAM,kBAAkB,CAAC;AAE5D,MAAM,MAAM,yBAAyB,GACjC,MAAM,GACN,MAAM,GACN,OAAO,GACP,MAAM,GACN,SAAS,GACT,IAAI,GACJ,yBAAyB,EAAE,GAC3B;IAAE,QAAQ,IAAI,MAAM,CAAA;CAAE,CAAC;AAE3B,MAAM,MAAM,yBAAyB,CAAC,gBAAgB,SAAS,MAAM,IACnE,gBAAgB,SAAS,GAAG,MAAM,IAAI,MAAM,GAAG,IAAI,MAAM,IAAI,EAAE,GAC3D;KACG,CAAC,IACE,GAAG,GACH,MAAM,yBAAyB,CAAC,IAAI,CAAC,GAAG,yBAAyB;CACtE,GACD,EAAE,CAAC;AAET,MAAM,MAAM,gCAAgC,CAC1C,gBAAgB,SAAS,eAAe,IACtC,MAAM,yBAAyB,CACjC,gBAAgB,CAAC,iBAAiB,CAAC,CACpC,SAAS,KAAK,GACX,CAAC,gBAAgB,EAAE,KAAK,CAAC,CAAC,GAC1B;IACE,gBAAgB;IAChB,yBAAyB,CAAC,gBAAgB,CAAC,iBAAiB,CAAC,CAAC;IAC9D,KAAK,CAAC;CACP,CAAC;AAEN,eAAO,MAAM,YAAY,QAAQ,CAAC;AAKlC;;;;GAIG;AACH,qBAAa,YAAY,CACvB,gBAAgB,SAAS,eAAe,GAAG,eAAe,CAC1D,SAAQ,WAAW;;IACnB,gBAAuB,MAAM,EAAE,OAAO,MAAM,CAAU;gBAapD,GAAG,CACD,eAAe,EACf,6BAA6B,EAC7B,WAAW,EACZ,EAAE,gCAAgC,CAAC,gBAAgB,CAAC;WAsDzC,cAAc,CAC1B,KAAK,EAAE,OAAO,GACb,KAAK,IAAI,YAAY,CAAC,eAAe,CAAC;WAC3B,cAAc,CAAC,gBAAgB,SAAS,eAAe,EACnE,KAAK,EAAE,OAAO,EACd,UAAU,CAAC,EAAE,gBAAgB,GAC5B,KAAK,IAAI,YAAY,CAAC,gBAAgB,CAAC;IAuB1C,IAAW,MAAM,IAAI,MAAM,CAE1B;IAED,IAAW,QAAQ,IAAI,MAAM,GAAG,SAAS,CAYxC;IAED,IAAW,UAAU,IAAI,eAAe,CAEvC;IAED,IAAW,gBAAgB,IAAI,yBAAyB,CACtD,gBAAgB,CAAC,iBAAiB,CAAC,CACpC,CAEA;IAED,IAAW,SAAS,IAAI,MAAM,CAE7B;IAED,IAAW,gBAAgB,IAAI,MAAM,CAEpC;CACF;AAED;;;GAGG;AACH,qBAAa,kBAAmB,SAAQ,WAAW;aAE/B,QAAQ,EAAE,MAAM;gBAAhB,QAAQ,EAAE,MAAM,EAChC,OAAO,EAAE,MAAM,EACf,WAAW,CAAC,EAAE,KAAK;WAaP,oBAAoB,CAChC,KAAK,EAAE,OAAO,GACb,KAAK,IAAI,kBAAkB;CAY/B;AAED;;;;;GAKG;AACH,wBAAgB,sBAAsB,CACpC,SAAS,EAAE,OAAO,EAClB,OAAO,EAAE,MAAM,GACd,OAAO,CAAC,SAAS,CAInB;AAMD;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,yBAAyB,CACvC,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,yBAAyB,CAAC,GAChD,MAAM,CAsBR"}

View File

@@ -0,0 +1,164 @@
import { CustomError } from "@nomicfoundation/hardhat-utils/error";
import { isObject } from "@nomicfoundation/hardhat-utils/lang";
import { ERRORS, ERROR_CATEGORIES } from "./descriptors.js";
export const ERROR_PREFIX = "HHE";
const IS_HARDHAT_ERROR_PROPERTY_NAME = "_isHardhatError";
const IS_HARDHAT_PLUGIN_ERROR_PROPERTY_NAME = "_isHardhatPluginError";
/**
* An error thrown by Hardhat. This error is meant to be thrown by Hardhat
* itself, and internal plugins. For errors thrown by community plugins, see
* `HardhatPluginError`.
*/
export class HardhatError extends CustomError {
static ERRORS = ERRORS;
#descriptor;
#arguments;
#errorCode;
#formattedMessage;
constructor(...[errorDescriptor, messageArgumentsOrParentError, parentError,]) {
const errorCode = getErrorCode(errorDescriptor);
const formattedMessage = messageArgumentsOrParentError === undefined ||
messageArgumentsOrParentError instanceof Error
? errorDescriptor.messageTemplate
: applyErrorMessageTemplate(errorDescriptor.messageTemplate, messageArgumentsOrParentError);
super(`${errorCode}: ${formattedMessage}`, parentError instanceof Error
? parentError
: messageArgumentsOrParentError instanceof Error
? messageArgumentsOrParentError
: undefined);
this.#descriptor = errorDescriptor;
this.#errorCode = errorCode;
this.#formattedMessage = formattedMessage;
if (messageArgumentsOrParentError === undefined ||
messageArgumentsOrParentError instanceof Error) {
/* eslint-disable @typescript-eslint/consistent-type-assertions --
Typescript inference get's lost here, but we know that if we didn't get
arguments, it's because the error doesn't have any. */
this.#arguments = {};
}
else {
this.#arguments = messageArgumentsOrParentError;
}
// As this package is going to be used from most of our packages, there's a
// change of users having multiple versions of it. If that happens, they may
// have multiple `HardhatError` classes, so we can't reliably use
// `instanceof` to check if an error is a `HardhatError`. We define a
// pseudo-private field to use it for it. While this is not bulletproof, it
// should be enough for our case, as we won't be changing this class often.
Object.defineProperty(this, IS_HARDHAT_ERROR_PROPERTY_NAME, {
configurable: false,
enumerable: false,
writable: false,
value: true,
});
}
static isHardhatError(other, descriptor) {
if (!isObject(other)) {
return false;
}
const isHardhatErrorProperty = Object.getOwnPropertyDescriptor(other, IS_HARDHAT_ERROR_PROPERTY_NAME);
return (isHardhatErrorProperty?.value === true &&
// If an error descriptor is provided, check if its number matches the Hardhat error number
(descriptor === undefined
? true
: "number" in other && other.number === descriptor.number));
}
get number() {
return this.#descriptor.number;
}
get pluginId() {
for (const category of Object.values(ERROR_CATEGORIES)) {
const isWithinCategoryRange = this.#descriptor.number >= category.min &&
this.#descriptor.number <= category.max;
if (isWithinCategoryRange) {
return category.pluginId;
}
}
return undefined;
}
get descriptor() {
return this.#descriptor;
}
get messageArguments() {
return this.#arguments;
}
get errorCode() {
return this.#errorCode;
}
get formattedMessage() {
return this.#formattedMessage;
}
}
/**
* An error thrown by a Hardhat plugin. This error is meant to be thrown by
* community plugins to signal that something went wrong.
*/
export class HardhatPluginError extends CustomError {
pluginId;
constructor(pluginId, message, parentError) {
super(message, parentError);
this.pluginId = pluginId;
// See `HardhatError` constructor for an explanation of this property.
Object.defineProperty(this, IS_HARDHAT_PLUGIN_ERROR_PROPERTY_NAME, {
configurable: false,
enumerable: false,
writable: false,
value: true,
});
}
static isHardhatPluginError(other) {
if (!isObject(other)) {
return false;
}
const isHardhatPluginErrorProperty = Object.getOwnPropertyDescriptor(other, IS_HARDHAT_PLUGIN_ERROR_PROPERTY_NAME);
return isHardhatPluginErrorProperty?.value === true;
}
}
/**
* Asserts an internal invariant.
*
* @param invariant The condition to check.
* @param message A message to show if the condition is false.
*/
export function assertHardhatInvariant(invariant, message) {
if (!invariant) {
throw new HardhatError(ERRORS.CORE.INTERNAL.ASSERTION_ERROR, { message });
}
}
function getErrorCode(errorDescriptor) {
return `${ERROR_PREFIX}${errorDescriptor.number}`;
}
/**
* This function applies error messages templates like this:
*
* - Template is a string which contains a variable tags. A variable tag is a
* a variable name surrounded by %. Eg: %plugin1%
* - A variable name is a string of alphanumeric ascii characters.
* - Every variable tag is replaced by its value.
* - %% is replaced by %.
* - Values can't contain variable tags.
* - If a variable is not present in the template, but present in the values
* object, an error is thrown.
*
* @param template The template string.
* @param values A map of variable names to their values.
*/
export function applyErrorMessageTemplate(template, values) {
return template.replaceAll(/{(.*?)}/g, (_match, variableName) => {
const rawValue = values[variableName];
if (rawValue === undefined) {
return "undefined";
}
if (rawValue === null) {
return "null";
}
if (typeof rawValue === "bigint") {
return `${rawValue}n`;
}
if (Array.isArray(rawValue)) {
return JSON.stringify(rawValue);
}
return rawValue.toString();
});
}
//# sourceMappingURL=errors.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"errors.js","sourceRoot":"","sources":["../../src/errors.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,WAAW,EAAE,MAAM,sCAAsC,CAAC;AACnE,OAAO,EAAE,QAAQ,EAAE,MAAM,qCAAqC,CAAC;AAE/D,OAAO,EAAE,MAAM,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AAiC5D,MAAM,CAAC,MAAM,YAAY,GAAG,KAAK,CAAC;AAElC,MAAM,8BAA8B,GAAG,iBAAiB,CAAC;AACzD,MAAM,qCAAqC,GAAG,uBAAuB,CAAC;AAEtE;;;;GAIG;AACH,MAAM,OAAO,YAEX,SAAQ,WAAW;IACZ,MAAM,CAAU,MAAM,GAAkB,MAAM,CAAC;IAE7C,WAAW,CAAmB;IAE9B,UAAU,CAEjB;IAEO,UAAU,CAAS;IAEnB,iBAAiB,CAAS;IAEnC,YACE,GAAG,CACD,eAAe,EACf,6BAA6B,EAC7B,WAAW,EACwC;QAErD,MAAM,SAAS,GAAG,YAAY,CAAC,eAAe,CAAC,CAAC;QAEhD,MAAM,gBAAgB,GACpB,6BAA6B,KAAK,SAAS;YAC3C,6BAA6B,YAAY,KAAK;YAC5C,CAAC,CAAC,eAAe,CAAC,eAAe;YACjC,CAAC,CAAC,yBAAyB,CACvB,eAAe,CAAC,eAAe,EAC/B,6BAA6B,CAC9B,CAAC;QAER,KAAK,CACH,GAAG,SAAS,KAAK,gBAAgB,EAAE,EACnC,WAAW,YAAY,KAAK;YAC1B,CAAC,CAAC,WAAW;YACb,CAAC,CAAC,6BAA6B,YAAY,KAAK;gBAC9C,CAAC,CAAC,6BAA6B;gBAC/B,CAAC,CAAC,SAAS,CAChB,CAAC;QAEF,IAAI,CAAC,WAAW,GAAG,eAAe,CAAC;QACnC,IAAI,CAAC,UAAU,GAAG,SAAS,CAAC;QAC5B,IAAI,CAAC,iBAAiB,GAAG,gBAAgB,CAAC;QAE1C,IACE,6BAA6B,KAAK,SAAS;YAC3C,6BAA6B,YAAY,KAAK,EAC9C,CAAC;YACD;;kEAEsD;YACtD,IAAI,CAAC,UAAU,GAAG,EAEjB,CAAC;QACJ,CAAC;aAAM,CAAC;YACN,IAAI,CAAC,UAAU,GAAG,6BAA6B,CAAC;QAClD,CAAC;QAED,2EAA2E;QAC3E,4EAA4E;QAC5E,iEAAiE;QACjE,qEAAqE;QACrE,2EAA2E;QAC3E,2EAA2E;QAC3E,MAAM,CAAC,cAAc,CAAC,IAAI,EAAE,8BAA8B,EAAE;YAC1D,YAAY,EAAE,KAAK;YACnB,UAAU,EAAE,KAAK;YACjB,QAAQ,EAAE,KAAK;YACf,KAAK,EAAE,IAAI;SACZ,CAAC,CAAC;IACL,CAAC;IASM,MAAM,CAAC,cAAc,CAC1B,KAAc,EACd,UAA4B;QAE5B,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC;YACrB,OAAO,KAAK,CAAC;QACf,CAAC;QAED,MAAM,sBAAsB,GAAG,MAAM,CAAC,wBAAwB,CAC5D,KAAK,EACL,8BAA8B,CAC/B,CAAC;QAEF,OAAO,CACL,sBAAsB,EAAE,KAAK,KAAK,IAAI;YACtC,2FAA2F;YAC3F,CAAC,UAAU,KAAK,SAAS;gBACvB,CAAC,CAAC,IAAI;gBACN,CAAC,CAAC,QAAQ,IAAI,KAAK,IAAI,KAAK,CAAC,MAAM,KAAK,UAAU,CAAC,MAAM,CAAC,CAC7D,CAAC;IACJ,CAAC;IAED,IAAW,MAAM;QACf,OAAO,IAAI,CAAC,WAAW,CAAC,MAAM,CAAC;IACjC,CAAC;IAED,IAAW,QAAQ;QACjB,KAAK,MAAM,QAAQ,IAAI,MAAM,CAAC,MAAM,CAAC,gBAAgB,CAAC,EAAE,CAAC;YACvD,MAAM,qBAAqB,GACzB,IAAI,CAAC,WAAW,CAAC,MAAM,IAAI,QAAQ,CAAC,GAAG;gBACvC,IAAI,CAAC,WAAW,CAAC,MAAM,IAAI,QAAQ,CAAC,GAAG,CAAC;YAE1C,IAAI,qBAAqB,EAAE,CAAC;gBAC1B,OAAO,QAAQ,CAAC,QAAQ,CAAC;YAC3B,CAAC;QACH,CAAC;QAED,OAAO,SAAS,CAAC;IACnB,CAAC;IAED,IAAW,UAAU;QACnB,OAAO,IAAI,CAAC,WAAW,CAAC;IAC1B,CAAC;IAED,IAAW,gBAAgB;QAGzB,OAAO,IAAI,CAAC,UAAU,CAAC;IACzB,CAAC;IAED,IAAW,SAAS;QAClB,OAAO,IAAI,CAAC,UAAU,CAAC;IACzB,CAAC;IAED,IAAW,gBAAgB;QACzB,OAAO,IAAI,CAAC,iBAAiB,CAAC;IAChC,CAAC;;AAGH;;;GAGG;AACH,MAAM,OAAO,kBAAmB,SAAQ,WAAW;IAE/B;IADlB,YACkB,QAAgB,EAChC,OAAe,EACf,WAAmB;QAEnB,KAAK,CAAC,OAAO,EAAE,WAAW,CAAC,CAAC;QAJZ,aAAQ,GAAR,QAAQ,CAAQ;QAMhC,sEAAsE;QACtE,MAAM,CAAC,cAAc,CAAC,IAAI,EAAE,qCAAqC,EAAE;YACjE,YAAY,EAAE,KAAK;YACnB,UAAU,EAAE,KAAK;YACjB,QAAQ,EAAE,KAAK;YACf,KAAK,EAAE,IAAI;SACZ,CAAC,CAAC;IACL,CAAC;IAEM,MAAM,CAAC,oBAAoB,CAChC,KAAc;QAEd,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC;YACrB,OAAO,KAAK,CAAC;QACf,CAAC;QAED,MAAM,4BAA4B,GAAG,MAAM,CAAC,wBAAwB,CAClE,KAAK,EACL,qCAAqC,CACtC,CAAC;QAEF,OAAO,4BAA4B,EAAE,KAAK,KAAK,IAAI,CAAC;IACtD,CAAC;CACF;AAED;;;;;GAKG;AACH,MAAM,UAAU,sBAAsB,CACpC,SAAkB,EAClB,OAAe;IAEf,IAAI,CAAC,SAAS,EAAE,CAAC;QACf,MAAM,IAAI,YAAY,CAAC,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC,eAAe,EAAE,EAAE,OAAO,EAAE,CAAC,CAAC;IAC5E,CAAC;AACH,CAAC;AAED,SAAS,YAAY,CAAC,eAAgC;IACpD,OAAO,GAAG,YAAY,GAAG,eAAe,CAAC,MAAM,EAAE,CAAC;AACpD,CAAC;AAED;;;;;;;;;;;;;;GAcG;AACH,MAAM,UAAU,yBAAyB,CACvC,QAAgB,EAChB,MAAiD;IAEjD,OAAO,QAAQ,CAAC,UAAU,CAAC,UAAU,EAAE,CAAC,MAAM,EAAE,YAAY,EAAE,EAAE;QAC9D,MAAM,QAAQ,GAAG,MAAM,CAAC,YAAY,CAAC,CAAC;QAEtC,IAAI,QAAQ,KAAK,SAAS,EAAE,CAAC;YAC3B,OAAO,WAAW,CAAC;QACrB,CAAC;QAED,IAAI,QAAQ,KAAK,IAAI,EAAE,CAAC;YACtB,OAAO,MAAM,CAAC;QAChB,CAAC;QAED,IAAI,OAAO,QAAQ,KAAK,QAAQ,EAAE,CAAC;YACjC,OAAO,GAAG,QAAQ,GAAG,CAAC;QACxB,CAAC;QAED,IAAI,KAAK,CAAC,OAAO,CAAC,QAAQ,CAAC,EAAE,CAAC;YAC5B,OAAO,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,CAAC;QAClC,CAAC;QAED,OAAO,QAAQ,CAAC,QAAQ,EAAE,CAAC;IAC7B,CAAC,CAAC,CAAC;AACL,CAAC"}

View File

@@ -0,0 +1,3 @@
export type { ErrorDescriptor } from "./descriptors.js";
export { HardhatError, HardhatPluginError, assertHardhatInvariant, } from "./errors.js";
//# sourceMappingURL=index.d.ts.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,YAAY,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAC;AACxD,OAAO,EACL,YAAY,EACZ,kBAAkB,EAClB,sBAAsB,GACvB,MAAM,aAAa,CAAC"}

View File

@@ -0,0 +1,2 @@
export { HardhatError, HardhatPluginError, assertHardhatInvariant, } from "./errors.js";
//# sourceMappingURL=index.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EACL,YAAY,EACZ,kBAAkB,EAClB,sBAAsB,GACvB,MAAM,aAAa,CAAC"}

View File

@@ -0,0 +1,58 @@
{
"name": "@nomicfoundation/hardhat-errors",
"version": "3.0.7",
"description": "The different errors that Hardhat can throw",
"homepage": "https://github.com/nomicfoundation/hardhat/tree/v-next/v-next/hardhat-errors",
"repository": {
"type": "git",
"url": "https://github.com/NomicFoundation/hardhat",
"directory": "v-next/hardhat-errors"
},
"author": "Nomic Foundation",
"license": "MIT",
"type": "module",
"types": "dist/src/index.d.ts",
"exports": {
".": "./dist/src/index.js",
"./descriptors": "./dist/src/descriptors.js"
},
"keywords": [
"ethereum",
"smart-contracts",
"hardhat"
],
"files": [
"dist/src/",
"src/",
"CHANGELOG.md",
"LICENSE",
"README.md"
],
"devDependencies": {
"@nomicfoundation/hardhat-node-test-reporter": "^3.0.0",
"@types/node": "^22.0.0",
"c8": "^9.1.0",
"eslint": "9.25.1",
"expect-type": "^1.2.1",
"prettier": "3.2.5",
"rimraf": "^5.0.5",
"tsx": "^4.19.3",
"typescript": "~5.8.0"
},
"dependencies": {
"@nomicfoundation/hardhat-utils": "^4.0.0"
},
"scripts": {
"lint": "pnpm prettier --check && pnpm eslint",
"lint:fix": "pnpm prettier --write && pnpm eslint --fix",
"eslint": "eslint \"src/**/*.ts\" \"test/**/*.ts\"",
"prettier": "prettier \"**/*.{ts,js,md,json}\"",
"test": "node --import tsx/esm --test --test-reporter=@nomicfoundation/hardhat-node-test-reporter \"test/*.ts\" \"test/!(fixture-projects|helpers)/**/*.ts\"",
"test:only": "node --import tsx/esm --test --test-only --test-reporter=@nomicfoundation/hardhat-node-test-reporter \"test/*.ts\" \"test/!(fixture-projects|helpers)/**/*.ts\"",
"test:coverage": "c8 --reporter html --reporter text --all --exclude test --exclude \"src/**/{types,type-extensions}.ts\" --src src node --import tsx/esm --test --test-reporter=@nomicfoundation/hardhat-node-test-reporter \"test/*.ts\" \"test/!(fixture-projects|helpers)/**/*.ts\"",
"pretest": "pnpm build",
"pretest:only": "pnpm build",
"build": "tsc --build .",
"clean": "rimraf dist"
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,285 @@
import type { ErrorDescriptor } from "./descriptors.js";
import { CustomError } from "@nomicfoundation/hardhat-utils/error";
import { isObject } from "@nomicfoundation/hardhat-utils/lang";
import { ERRORS, ERROR_CATEGORIES } from "./descriptors.js";
export type ErrorMessageTemplateValue =
| string
| number
| boolean
| bigint
| undefined
| null
| ErrorMessageTemplateValue[]
| { toString(): string };
export type MessagetTemplateArguments<MessageTemplateT extends string> =
MessageTemplateT extends `${string}{${infer Tag}}${infer Rest}`
? {
[K in
| Tag
| keyof MessagetTemplateArguments<Rest>]: ErrorMessageTemplateValue;
}
: {};
export type HardhatErrorConstructorArguments<
ErrorDescriptorT extends ErrorDescriptor,
> = keyof MessagetTemplateArguments<
ErrorDescriptorT["messageTemplate"]
> extends never
? [ErrorDescriptorT, Error?]
: [
ErrorDescriptorT,
MessagetTemplateArguments<ErrorDescriptorT["messageTemplate"]>,
Error?,
];
export const ERROR_PREFIX = "HHE";
const IS_HARDHAT_ERROR_PROPERTY_NAME = "_isHardhatError";
const IS_HARDHAT_PLUGIN_ERROR_PROPERTY_NAME = "_isHardhatPluginError";
/**
* An error thrown by Hardhat. This error is meant to be thrown by Hardhat
* itself, and internal plugins. For errors thrown by community plugins, see
* `HardhatPluginError`.
*/
export class HardhatError<
ErrorDescriptorT extends ErrorDescriptor = ErrorDescriptor,
> extends CustomError {
public static readonly ERRORS: typeof ERRORS = ERRORS;
readonly #descriptor: ErrorDescriptorT;
readonly #arguments: MessagetTemplateArguments<
ErrorDescriptorT["messageTemplate"]
>;
readonly #errorCode: string;
readonly #formattedMessage: string;
constructor(
...[
errorDescriptor,
messageArgumentsOrParentError,
parentError,
]: HardhatErrorConstructorArguments<ErrorDescriptorT>
) {
const errorCode = getErrorCode(errorDescriptor);
const formattedMessage =
messageArgumentsOrParentError === undefined ||
messageArgumentsOrParentError instanceof Error
? errorDescriptor.messageTemplate
: applyErrorMessageTemplate(
errorDescriptor.messageTemplate,
messageArgumentsOrParentError,
);
super(
`${errorCode}: ${formattedMessage}`,
parentError instanceof Error
? parentError
: messageArgumentsOrParentError instanceof Error
? messageArgumentsOrParentError
: undefined,
);
this.#descriptor = errorDescriptor;
this.#errorCode = errorCode;
this.#formattedMessage = formattedMessage;
if (
messageArgumentsOrParentError === undefined ||
messageArgumentsOrParentError instanceof Error
) {
/* eslint-disable @typescript-eslint/consistent-type-assertions --
Typescript inference get's lost here, but we know that if we didn't get
arguments, it's because the error doesn't have any. */
this.#arguments = {} as MessagetTemplateArguments<
ErrorDescriptorT["messageTemplate"]
>;
} else {
this.#arguments = messageArgumentsOrParentError;
}
// As this package is going to be used from most of our packages, there's a
// change of users having multiple versions of it. If that happens, they may
// have multiple `HardhatError` classes, so we can't reliably use
// `instanceof` to check if an error is a `HardhatError`. We define a
// pseudo-private field to use it for it. While this is not bulletproof, it
// should be enough for our case, as we won't be changing this class often.
Object.defineProperty(this, IS_HARDHAT_ERROR_PROPERTY_NAME, {
configurable: false,
enumerable: false,
writable: false,
value: true,
});
}
public static isHardhatError(
other: unknown,
): other is HardhatError<ErrorDescriptor>;
public static isHardhatError<ErrorDescriptorT extends ErrorDescriptor>(
other: unknown,
descriptor?: ErrorDescriptorT,
): other is HardhatError<ErrorDescriptorT>;
public static isHardhatError(
other: unknown,
descriptor?: ErrorDescriptor,
): other is HardhatError<ErrorDescriptor> {
if (!isObject(other)) {
return false;
}
const isHardhatErrorProperty = Object.getOwnPropertyDescriptor(
other,
IS_HARDHAT_ERROR_PROPERTY_NAME,
);
return (
isHardhatErrorProperty?.value === true &&
// If an error descriptor is provided, check if its number matches the Hardhat error number
(descriptor === undefined
? true
: "number" in other && other.number === descriptor.number)
);
}
public get number(): number {
return this.#descriptor.number;
}
public get pluginId(): string | undefined {
for (const category of Object.values(ERROR_CATEGORIES)) {
const isWithinCategoryRange =
this.#descriptor.number >= category.min &&
this.#descriptor.number <= category.max;
if (isWithinCategoryRange) {
return category.pluginId;
}
}
return undefined;
}
public get descriptor(): ErrorDescriptor {
return this.#descriptor;
}
public get messageArguments(): MessagetTemplateArguments<
ErrorDescriptorT["messageTemplate"]
> {
return this.#arguments;
}
public get errorCode(): string {
return this.#errorCode;
}
public get formattedMessage(): string {
return this.#formattedMessage;
}
}
/**
* An error thrown by a Hardhat plugin. This error is meant to be thrown by
* community plugins to signal that something went wrong.
*/
export class HardhatPluginError extends CustomError {
constructor(
public readonly pluginId: string,
message: string,
parentError?: Error,
) {
super(message, parentError);
// See `HardhatError` constructor for an explanation of this property.
Object.defineProperty(this, IS_HARDHAT_PLUGIN_ERROR_PROPERTY_NAME, {
configurable: false,
enumerable: false,
writable: false,
value: true,
});
}
public static isHardhatPluginError(
other: unknown,
): other is HardhatPluginError {
if (!isObject(other)) {
return false;
}
const isHardhatPluginErrorProperty = Object.getOwnPropertyDescriptor(
other,
IS_HARDHAT_PLUGIN_ERROR_PROPERTY_NAME,
);
return isHardhatPluginErrorProperty?.value === true;
}
}
/**
* Asserts an internal invariant.
*
* @param invariant The condition to check.
* @param message A message to show if the condition is false.
*/
export function assertHardhatInvariant(
invariant: boolean,
message: string,
): asserts invariant {
if (!invariant) {
throw new HardhatError(ERRORS.CORE.INTERNAL.ASSERTION_ERROR, { message });
}
}
function getErrorCode(errorDescriptor: ErrorDescriptor): string {
return `${ERROR_PREFIX}${errorDescriptor.number}`;
}
/**
* This function applies error messages templates like this:
*
* - Template is a string which contains a variable tags. A variable tag is a
* a variable name surrounded by %. Eg: %plugin1%
* - A variable name is a string of alphanumeric ascii characters.
* - Every variable tag is replaced by its value.
* - %% is replaced by %.
* - Values can't contain variable tags.
* - If a variable is not present in the template, but present in the values
* object, an error is thrown.
*
* @param template The template string.
* @param values A map of variable names to their values.
*/
export function applyErrorMessageTemplate(
template: string,
values: Record<string, ErrorMessageTemplateValue>,
): string {
return template.replaceAll(/{(.*?)}/g, (_match, variableName) => {
const rawValue = values[variableName];
if (rawValue === undefined) {
return "undefined";
}
if (rawValue === null) {
return "null";
}
if (typeof rawValue === "bigint") {
return `${rawValue}n`;
}
if (Array.isArray(rawValue)) {
return JSON.stringify(rawValue);
}
return rawValue.toString();
});
}

View File

@@ -0,0 +1,6 @@
export type { ErrorDescriptor } from "./descriptors.js";
export {
HardhatError,
HardhatPluginError,
assertHardhatInvariant,
} from "./errors.js";

View File

@@ -0,0 +1,57 @@
# @nomicfoundation/hardhat-utils
## 4.0.0
### Major Changes
- 87623db: Introduce new inter-process mutex implementation ([7942](https://github.com/NomicFoundation/hardhat/pull/7942)).
- 726ff37: Update the `--coverage` table output to match the style used by `--gas-stats`. Thanks @jose-blockchain! ([#7733](https://github.com/NomicFoundation/hardhat/issues/7733))
### Patch Changes
- 87623db: Fix two issues in the `download` function ([7942](https://github.com/NomicFoundation/hardhat/pull/7942)).
## 3.0.6
### Patch Changes
- 2bc18b2: Bumped `viem` version across all packages [7861](https://github.com/NomicFoundation/hardhat/pull/7861).
## 3.0.5
### Patch Changes
- d45234d: Fixed Etherscan verification failures by removing hardcoded v1 API URLs from chain descriptors ([#7623](https://github.com/NomicFoundation/hardhat/issues/7623)). Also enhanced config resolution to support partial overrides in block explorer configurations for future extensibility.
## 3.0.4
### Patch Changes
- d1969e7: Added support for showing gas statistics after running nodejs tests ([#7472](https://github.com/NomicFoundation/hardhat/issues/7428)).
## 3.0.3
### Patch Changes
- d821a0a: Fix npm artifact cleanup on windows ([#7459](https://github.com/NomicFoundation/hardhat/issues/7459))
- b13620a: Add compilation progress spinner to show build progress ([#7460](https://github.com/NomicFoundation/hardhat/pull/7460))
## 3.0.2
### Patch Changes
- 8c1cb1e: Fixed peer dependencies for Hardhat so `rpc` utils can be loaded ([#7415](https://github.com/NomicFoundation/hardhat/issues/7415))
## 3.0.1
### Patch Changes
- 49cc9ba: Load resolved global options into environment variables during tests ([#7305](https://github.com/NomicFoundation/hardhat/pull/7305))
- 8d3b16c: Support for custom compilers ([#7130](https://github.com/NomicFoundation/hardhat/issues/7130))
- a475780: Added automatic proxy detection for `hardhat-verify` and fixed case-insensitive proxy environment variables for network requests ([#7407](https://github.com/NomicFoundation/hardhat/pull/7407))
## 3.0.0
### Major Changes
- 29cc141: First release of Hardhat 3!

View File

@@ -0,0 +1,9 @@
MIT License
Copyright (c) 2024 Nomic Foundation
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

View File

@@ -0,0 +1,5 @@
# hardhat-utils
> ⚠️ This package is an internal Hardhat component and it's not meant to be used directly.
This package contains utilities used by Hardhat 3 and its plugins.

View File

@@ -0,0 +1,32 @@
/**
* Returns the minimum of two bigints.
*
* @param x The first number to compare.
* @param y The second number to compare.
* @returns The smaller of the two numbers.
*/
export declare function min(x: bigint, y: bigint): bigint;
/**
* Returns the maximum of two bigints.
*
* @param x The first number to compare.
* @param y The second number to compare.
* @returns The larger of the two numbers.
*/
export declare function max(x: bigint, y: bigint): bigint;
/**
* Converts a value to a bigint.
*
* This function supports several types of input:
* - `number`: Must be an integer and a safe integer. If it's not, an error is thrown.
* - `bigint`: Returned as is.
* - `string`: Converted to a bigint using the BigInt constructor.
*
* If the input is of an unsupported type, an error is thrown.
*
* @param value The value to convert to a bigint.
* @returns The input value converted to a bigint.
* @throws InvalidParameterError If the input value cannot be converted to a bigint.
*/
export declare function toBigInt(value: number | string | bigint): bigint;
//# sourceMappingURL=bigint.d.ts.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"bigint.d.ts","sourceRoot":"","sources":["../../src/bigint.ts"],"names":[],"mappings":"AAGA;;;;;;GAMG;AACH,wBAAgB,GAAG,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,MAAM,CAEhD;AAED;;;;;;GAMG;AACH,wBAAgB,GAAG,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,MAAM,CAEhD;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,MAAM,GAAG,MAAM,CAsBhE"}

View File

@@ -0,0 +1,55 @@
import { InvalidParameterError } from "./common-errors.js";
import { unreachable } from "./error.js";
/**
* Returns the minimum of two bigints.
*
* @param x The first number to compare.
* @param y The second number to compare.
* @returns The smaller of the two numbers.
*/
export function min(x, y) {
return x < y ? x : y;
}
/**
* Returns the maximum of two bigints.
*
* @param x The first number to compare.
* @param y The second number to compare.
* @returns The larger of the two numbers.
*/
export function max(x, y) {
return x > y ? x : y;
}
/**
* Converts a value to a bigint.
*
* This function supports several types of input:
* - `number`: Must be an integer and a safe integer. If it's not, an error is thrown.
* - `bigint`: Returned as is.
* - `string`: Converted to a bigint using the BigInt constructor.
*
* If the input is of an unsupported type, an error is thrown.
*
* @param value The value to convert to a bigint.
* @returns The input value converted to a bigint.
* @throws InvalidParameterError If the input value cannot be converted to a bigint.
*/
export function toBigInt(value) {
// eslint-disable-next-line @typescript-eslint/switch-exhaustiveness-check -- The other types will throw an error
switch (typeof value) {
case "number":
if (!Number.isInteger(value)) {
throw new InvalidParameterError(`${value} is not an integer`);
}
if (!Number.isSafeInteger(value)) {
throw new InvalidParameterError(`Integer ${value} is unsafe. Consider using ${value}n instead. For more details, see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/isSafeInteger`);
}
// `break;` intentionally omitted. fallthrough desired.
case "string":
case "bigint":
return BigInt(value);
default:
unreachable(value, new InvalidParameterError(`Unsupported type: ${typeof value}`));
}
}
//# sourceMappingURL=bigint.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"bigint.js","sourceRoot":"","sources":["../../src/bigint.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,qBAAqB,EAAE,MAAM,oBAAoB,CAAC;AAC3D,OAAO,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAEzC;;;;;;GAMG;AACH,MAAM,UAAU,GAAG,CAAC,CAAS,EAAE,CAAS;IACtC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;AACvB,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,GAAG,CAAC,CAAS,EAAE,CAAS;IACtC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;AACvB,CAAC;AAED;;;;;;;;;;;;;GAaG;AACH,MAAM,UAAU,QAAQ,CAAC,KAA+B;IACtD,iHAAiH;IACjH,QAAQ,OAAO,KAAK,EAAE,CAAC;QACrB,KAAK,QAAQ;YACX,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,KAAK,CAAC,EAAE,CAAC;gBAC7B,MAAM,IAAI,qBAAqB,CAAC,GAAG,KAAK,oBAAoB,CAAC,CAAC;YAChE,CAAC;YACD,IAAI,CAAC,MAAM,CAAC,aAAa,CAAC,KAAK,CAAC,EAAE,CAAC;gBACjC,MAAM,IAAI,qBAAqB,CAC7B,WAAW,KAAK,8BAA8B,KAAK,wIAAwI,CAC5L,CAAC;YACJ,CAAC;QACH,uDAAuD;QACvD,KAAK,QAAQ,CAAC;QACd,KAAK,QAAQ;YACX,OAAO,MAAM,CAAC,KAAK,CAAC,CAAC;QACvB;YACE,WAAW,CACT,KAAK,EACL,IAAI,qBAAqB,CAAC,qBAAqB,OAAO,KAAK,EAAE,CAAC,CAC/D,CAAC;IACN,CAAC;AACH,CAAC"}

View File

@@ -0,0 +1,27 @@
import type { PrefixedHexString } from "./hex.js";
import type { Artifact, LibraryAddresses, LibraryLink } from "./internal/bytecode.js";
/**
* Resolves the linked bytecode for a given contract artifact by substituting
* the required library placeholders within the bytecode with the provided
* library addresses.
*
* @param artifact The contract artifact containing the bytecode and link references.
* @param providedLibraries An object containing library names as keys and their addresses as values.
* @returns The linked bytecode with all required libraries correctly linked.
* @throws InvalidLibraryAddressError If any provided library address is invalid.
* @throws AmbiguousLibraryNameError If any provided library name matches multiple needed libraries.
* @throws UnnecessaryLibraryError If any provided library name is not needed by the contract.
* @throws OverlappingLibrariesError If any library is provided more than once.
* @throws MissingLibrariesError If any needed library address is missing.
*/
export declare function resolveLinkedBytecode(artifact: Artifact, providedLibraries: LibraryAddresses): PrefixedHexString;
/**
* Links the bytecode of a contract artifact with the provided library addresses.
* This function does not perform any validation on the provided libraries.
*
* @param artifact The contract artifact containing the bytecode and link references.
* @param libraries An array of LibraryLink objects representing the libraries to be linked.
* @returns The linked bytecode with all provided libraries correctly linked.
*/
export declare function linkBytecode(artifact: Artifact, libraries: LibraryLink[]): PrefixedHexString;
//# sourceMappingURL=bytecode.d.ts.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"bytecode.d.ts","sourceRoot":"","sources":["../../src/bytecode.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,UAAU,CAAC;AAClD,OAAO,KAAK,EACV,QAAQ,EACR,gBAAgB,EAChB,WAAW,EACZ,MAAM,wBAAwB,CAAC;AAUhC;;;;;;;;;;;;;GAaG;AACH,wBAAgB,qBAAqB,CACnC,QAAQ,EAAE,QAAQ,EAClB,iBAAiB,EAAE,gBAAgB,GAClC,iBAAiB,CA0BnB;AAED;;;;;;;GAOG;AACH,wBAAgB,YAAY,CAC1B,QAAQ,EAAE,QAAQ,EAClB,SAAS,EAAE,WAAW,EAAE,GACvB,iBAAiB,CAkBnB"}

View File

@@ -0,0 +1,60 @@
import { getPrefixedHexString, getUnprefixedHexString } from "./hex.js";
import { checkAmbiguousOrUnnecessaryLinks, checkMissingLibraryAddresses, checkOverlappingLibraryNames, checkProvidedLibraryAddresses, } from "./internal/bytecode.js";
/**
* Resolves the linked bytecode for a given contract artifact by substituting
* the required library placeholders within the bytecode with the provided
* library addresses.
*
* @param artifact The contract artifact containing the bytecode and link references.
* @param providedLibraries An object containing library names as keys and their addresses as values.
* @returns The linked bytecode with all required libraries correctly linked.
* @throws InvalidLibraryAddressError If any provided library address is invalid.
* @throws AmbiguousLibraryNameError If any provided library name matches multiple needed libraries.
* @throws UnnecessaryLibraryError If any provided library name is not needed by the contract.
* @throws OverlappingLibrariesError If any library is provided more than once.
* @throws MissingLibrariesError If any needed library address is missing.
*/
export function resolveLinkedBytecode(artifact, providedLibraries) {
checkProvidedLibraryAddresses(providedLibraries);
const neededLibraries = [];
for (const [sourceName, sourceLibraries] of Object.entries(artifact.linkReferences)) {
for (const libraryName of Object.keys(sourceLibraries)) {
const libraryFqn = `${sourceName}:${libraryName}`;
const address = providedLibraries[libraryFqn] ?? providedLibraries[libraryName];
neededLibraries.push({
sourceName,
libraryName,
libraryFqn,
address,
});
}
}
checkAmbiguousOrUnnecessaryLinks(providedLibraries, neededLibraries);
checkOverlappingLibraryNames(providedLibraries, neededLibraries);
checkMissingLibraryAddresses(neededLibraries);
return linkBytecode(artifact, neededLibraries);
}
/**
* Links the bytecode of a contract artifact with the provided library addresses.
* This function does not perform any validation on the provided libraries.
*
* @param artifact The contract artifact containing the bytecode and link references.
* @param libraries An array of LibraryLink objects representing the libraries to be linked.
* @returns The linked bytecode with all provided libraries correctly linked.
*/
export function linkBytecode(artifact, libraries) {
const { bytecode, linkReferences } = artifact;
let linkedBytecode = bytecode;
for (const { sourceName, libraryName, address } of libraries) {
const contractLinkReferences = linkReferences[sourceName]?.[libraryName] ?? [];
const unprefixedAddress = getUnprefixedHexString(address);
for (const { start, length } of contractLinkReferences) {
linkedBytecode =
linkedBytecode.substring(0, 2 + start * 2) +
unprefixedAddress +
linkedBytecode.substring(2 + (start + length) * 2);
}
}
return getPrefixedHexString(linkedBytecode);
}
//# sourceMappingURL=bytecode.js.map

Some files were not shown because too many files have changed in this diff Show More