mirror of
https://github.com/morluto/rea.git
synced 2026-10-26 17:35:11 +00:00
refactor(evm): bind offline analysis through a static descriptor
Bind existing EVM CLI/MCP metadata and the legacy service factory through one static descriptor, and register the same selected service that owns cleanup. Preserve original names, schemas, inputs/full defaults, configuration, Evidence and injection identity.
Verified exact SDK catalog/fact parity across six real EVM pairs, 59 actual EVM CLI/MCP cases, six real cross-domain workflows, six actual Codex CLI fact/reference/lifecycle trials and relevant local checks. CI run 38283026233 succeeds for exact head d999d9874b. No token reduction from wiring or fully qualified resource claim; final counters remain unknown. Continues #2016.
Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
This commit is contained in:
1 parent
d96550f2f6
commit
ad08510a11
13 files changed
+590
-48
No files matched your search
@@ -6,7 +6,7 @@ graph TD
|
||||
end
|
||||
|
||||
subgraph "Production Composition"
|
||||
COMPOSITION["src/composition/<br/>fresh typed provider and session factories<br/>one-shot binding and independent optional loading<br/>static WASM descriptor: named contract, factory, retryable lifetime"]
|
||||
COMPOSITION["src/composition/<br/>fresh typed provider and session factories<br/>one-shot binding and independent optional loading<br/>static WASM/EVM descriptors: named contracts, factories, retryable lifetimes"]
|
||||
end
|
||||
|
||||
subgraph "Adapters"
|
||||
|
||||
@@ -11,6 +11,10 @@ Follow-up: [native PE/DOS entry views and real model verification](native-entry-
|
||||
records the post-merge native continuation, including a small-function case where
|
||||
four selected replies cost more bytes than the complete dossier.
|
||||
|
||||
Follow-up: [EVM descriptor and real-template controls](evm-descriptor.md) extends
|
||||
the offline corpus with digest-pinned Uniswap runtime templates and an independent
|
||||
published ABI selector oracle, preserving unknown deployment/immutable metadata.
|
||||
|
||||
The 12 detailed checkpoint receipts were archived byte-for-byte before removal
|
||||
from the final PR tree. The archive `review-receipts-00d51e0fc-20261012` is in the
|
||||
private experiment lab outside the checkout; its `archive-index.json` records
|
||||
@@ -40,8 +44,9 @@ acceptance conditions for every optimization.
|
||||
experiment control; it does not itself establish reduced CPU consumption.
|
||||
|
||||
Migrate domain by domain with focused real-artifact checks and relevant shared
|
||||
contract checks. The domain matrix is a growing roadmap; native PE and DOS, for
|
||||
example, remain unverified in this study. A passed lane does not imply all
|
||||
contract checks. The native follow-up verifies selected PE and DOS MZ entry facts;
|
||||
DOS COM and complete native application behavior remain unverified. The domain
|
||||
matrix is a growing roadmap. A passed lane does not imply all
|
||||
advertised platforms/providers have passed. Newly discovered REA bugs remain
|
||||
in the separate local handoff document for later team repair.
|
||||
|
||||
|
||||
@@ -179,6 +179,62 @@
|
||||
"transport_integrity": "sha512-RJFoomET6FajjG511fmQxeBQfU6M24a0aFZPqpid+ttIxanWf1VGytBG0UmsGjt07qmIPJS8U31D+aecuCucsQ==",
|
||||
"derivation": "selected regular tar member package/wasm/tree-sitter-css.wasm; no target execution"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "uniswap-v3.factory.hex",
|
||||
"file": "UniswapV3Factory.runtime.hex",
|
||||
"bytes": 49073,
|
||||
"sha256": "fb152cc9b597ef4e54e9d72e81dd72c202050aae5e98c63df139864274504276",
|
||||
"decoded_bytes": 24535,
|
||||
"decoded_sha256": "95c34fe1e6b26c79ae42800041a3e2bdb6a978d19297cd74001ac6b316a48d8d",
|
||||
"provenance": {
|
||||
"project": "Uniswap v3-core UniswapV3Factory",
|
||||
"version": "1.0.1",
|
||||
"url": "https://registry.npmjs.org/@uniswap/v3-core/-/v3-core-1.0.1.tgz",
|
||||
"transport_bytes": 86371,
|
||||
"transport_sha256": "141b001e2a376fff867cb24e0713b2834095cb1250c13aae263d9a646446d329",
|
||||
"transport_integrity": "sha512-7pVk4hEm00j9tc71Y9+ssYpO6ytkeI0y7WE9P6UcmNzhxPePwyAxImuhVsTqWK9YFvzgtvzJHi64pBl4jUzKMQ==",
|
||||
"archive_member": "package/artifacts/contracts/UniswapV3Factory.sol/UniswapV3Factory.json",
|
||||
"compiler_artifact_bytes": 104076,
|
||||
"compiler_artifact_sha256": "599479f60ebb056804aff7b2d05bdd0830ddbb1fdfaa0b6c62c02294ca7188b0",
|
||||
"derivation": "Parse the exact published Hardhat deployedBytecode field and preserve its original hex carrier with one trailing LF; preserve template placeholders",
|
||||
"authority": "Published compiler runtime template, not a live deployed instance; original placeholders/immutables preserved; no execution or chain lookup",
|
||||
"published_abi_function_count": 7,
|
||||
"deployed_link_references": {},
|
||||
"immutable_references": {
|
||||
"recorded": false,
|
||||
"value": null
|
||||
},
|
||||
"immutable_scope": "Published Hardhat artifact does not record an immutable-reference map; absence is unknown, not proof of no immutables. Runtime template bytes remain exact."
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "uniswap-v3.pool.hex",
|
||||
"file": "UniswapV3Pool.runtime.hex",
|
||||
"bytes": 44287,
|
||||
"sha256": "84d90218eb125074383287877cdc564eca3765241e3bf6f6afdd64a922fbed2b",
|
||||
"decoded_bytes": 22142,
|
||||
"decoded_sha256": "3ef71385c2a22b945bead7904f24b25db877de9db51d1401f2f9daea07ea5c0c",
|
||||
"provenance": {
|
||||
"project": "Uniswap v3-core UniswapV3Pool",
|
||||
"version": "1.0.1",
|
||||
"url": "https://registry.npmjs.org/@uniswap/v3-core/-/v3-core-1.0.1.tgz",
|
||||
"transport_bytes": 86371,
|
||||
"transport_sha256": "141b001e2a376fff867cb24e0713b2834095cb1250c13aae263d9a646446d329",
|
||||
"transport_integrity": "sha512-7pVk4hEm00j9tc71Y9+ssYpO6ytkeI0y7WE9P6UcmNzhxPePwyAxImuhVsTqWK9YFvzgtvzJHi64pBl4jUzKMQ==",
|
||||
"archive_member": "package/artifacts/contracts/UniswapV3Pool.sol/UniswapV3Pool.json",
|
||||
"compiler_artifact_bytes": 111557,
|
||||
"compiler_artifact_sha256": "204fb2fa7ad1a639b1df5601f035f9924b064b721b97adf60c7d5d4712643e34",
|
||||
"derivation": "Parse the exact published Hardhat deployedBytecode field and preserve its original hex carrier with one trailing LF; preserve template placeholders",
|
||||
"authority": "Published compiler runtime template, not a live deployed instance; original placeholders/immutables preserved; no execution or chain lookup",
|
||||
"published_abi_function_count": 26,
|
||||
"deployed_link_references": {},
|
||||
"immutable_references": {
|
||||
"recorded": false,
|
||||
"value": null
|
||||
},
|
||||
"immutable_scope": "Published Hardhat artifact does not record an immutable-reference map; absence is unknown, not proof of no immutables. Runtime template bytes remain exact."
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,181 @@
|
||||
# EVM descriptor and real-template checkpoint
|
||||
|
||||
Continues [#2016](https://github.com/morluto/rea/issues/2016) after the native
|
||||
checkpoint merged. The internal descriptor binds the existing exact EVM
|
||||
contract, CLI name and typed service lifetime. Existing complete inputs, output,
|
||||
provider selection and cleanup remain the baseline. Wiring alone does not establish
|
||||
token or latency savings.
|
||||
|
||||
The descriptor contains only the named contract, existing CLI name and existing
|
||||
factory. The legacy `createEvmInterfaceService` export remains usable. The MCP
|
||||
host selects one complete service instance, preserves injection identity, passes
|
||||
that same instance to registration and retains it for cleanup/retry. No bound
|
||||
service wrapper, generic registry, automatic dependency acquisition or new pack
|
||||
selection is introduced.
|
||||
|
||||
Five actual in-memory SDK boundary scenarios verify converted schemas, duplicate
|
||||
registration rejection before use, malformed inputs, explicit unavailable or
|
||||
incompatible-output failures with a healthy WASM peer, cleanup retry, and request
|
||||
cancellation followed by recovery. Their injected ports are recording seams, not
|
||||
real EVMole/WABT claims. Nine service and 26 provider-boundary cases also pass;
|
||||
two unsupported-host scenarios are skipped on this Linux host. The existing seven
|
||||
WASM MCP scenarios pass. Full `npm run check` passes; its approximately 3 GiB
|
||||
sampled peak and `memory.high` events are excluded from performance claims.
|
||||
Candidate real-artifact/catalog and model observations are recorded below.
|
||||
|
||||
## Public inputs and independent checks
|
||||
|
||||
[corpus.json](corpus.json) retains the original WETH historical example and adds
|
||||
Uniswap v3-core 1.0.1 Factory/Pool compiler runtime templates from the official npm
|
||||
package. Archive integrity, exact regular member, compiler JSON, original carrier
|
||||
and decoded-byte digests are pinned. The carrier is the original `deployedBytecode`
|
||||
field plus one trailing LF. No placeholder is rewritten and no contract is executed.
|
||||
|
||||
These are compiler templates, not live deployed instances. Their published Hardhat
|
||||
artifacts omit immutable-reference maps; that metadata remains explicitly unknown.
|
||||
The recorded empty link-reference maps do not establish absence of immutables.
|
||||
WETH remains a published historical RPC example, with unknown live authenticity.
|
||||
|
||||
Actual control CLI observations infer 7 Factory and 26 Pool functions. The
|
||||
[independent verifier](verify-evm-template.mjs) hashes each canonical published ABI
|
||||
signature with caller-supplied Ethereum Keccak and checks the complete ABI-dispatch
|
||||
selector multiset against the original REA response. Both sets match. This does not
|
||||
establish inferred argument/mutability equivalence, deployment authenticity or
|
||||
runtime semantics; all producer unknowns remain visible.
|
||||
|
||||
## Latest-main control
|
||||
|
||||
Production control: `1770f38cfc6cbe87491d0de8b8730c305c5cf8ea`, Node 24.18.0,
|
||||
npm 11.16.0, matching locked dependencies and unchanged EVMole 0.9.3.
|
||||
The frozen `dist/`/`bridge/` file-table SHA-256 is
|
||||
`7fd6c2c37b4e166df158ec29d48d31f3712b9b087d9807ee019ee997d06ea144`.
|
||||
Measurement driver SHA-256 is
|
||||
`f767c0c0232be1e11918e70e3197a6919680d16688076bef28d4320662f712f7`.
|
||||
Each public SDK run validates the selected named advertised schema and verifies
|
||||
original carrier and decoded identities, inference/unknown scope and unchanged
|
||||
input bytes. All three runs return the same complete 2,420,709-byte catalog.
|
||||
|
||||
| Question | Inferred functions | SDK tool-result JSON bytes | First workflow seconds |
|
||||
| ------------------------ | -----------------: | -------------------------: | ---------------------: |
|
||||
| WETH historical carrier | 11 | 26,760 | 11.432 |
|
||||
| Uniswap Factory template | 7 | 110,424 | 3.628 |
|
||||
| Uniswap Pool template | 26 | 110,998 | 11.211 |
|
||||
|
||||
These are initial observations, not paired performance conclusions or model-token
|
||||
counts. Each workflow includes connection, complete catalog discovery and one
|
||||
interface query. Full bytecode remains inline. Candidate/control repetitions must
|
||||
use the same source driver, inputs, provider settings and Node/worker limits.
|
||||
|
||||
Preparation against earlier frozen main `b1bf4a70d44191348ca20a91a52fb663da4daed0`
|
||||
also passed all 59 cases of the existing real CLI/MCP EVM verifier with privately
|
||||
supplied solc 0.8.30 and strace 6.1. Those cases cover compiler configurations,
|
||||
raw/hex identity, malformed/unsupported carriers, missing limiter and resource
|
||||
constraints. Its measured scope took 233.1 seconds, sampled peak 1,192,730,624
|
||||
bytes and last-observed CPU 181.018 seconds. This is a control, not candidate proof.
|
||||
|
||||
All heavy runs are serial under one CPU, 4 GiB hard memory/3 GiB early-stop, no swap
|
||||
and bounded tasks. Final cgroup counters disappear at scope teardown and remain
|
||||
unverified; last-observed zero limit events are not substituted for final counters.
|
||||
Raw replies, tools, compiler artifacts and detailed receipts remain outside Git.
|
||||
|
||||
## Reproduction
|
||||
|
||||
Build and freeze each pinned runtime using the repository toolchain, then run
|
||||
`measure.mjs --artifact uniswap-v3.factory.hex` or `uniswap-v3.pool.hex` with the
|
||||
exact derived carrier, `--schemas selected`, explicit runtime/revision and a fresh
|
||||
private output directory. Existing `weth.hex` answer fields and invocation remain
|
||||
unchanged. The same common workflow verifies all three offline carriers.
|
||||
|
||||
Run `verify-evm-template.mjs` with `--artifact` selecting the corpus ID,
|
||||
`--artifact-json` selecting its original published compiler JSON, `--evidence`
|
||||
selecting the recorded CLI Evidence or SDK structured reply, `--keccak-module`
|
||||
selecting the absolute `js-sha3/src/sha3.js` module from the private solc toolchain,
|
||||
and `--output` selecting a new private receipt. It acquires no dependencies and
|
||||
does not overwrite an existing receipt.
|
||||
|
||||
The [template model question](questions/evm-uniswap.txt) requires the same complete
|
||||
inferred interface facts and explicit unknowns. An ABI-count match is not used as
|
||||
a model grade.
|
||||
|
||||
## Candidate compatibility and real-provider verification
|
||||
|
||||
Candidate: `152984da771450048ee9d60db8a410994a125878`, frozen runtime file-table
|
||||
SHA-256 `725563640c27eb50ddb6f977d2dfbe1bdaddf7e05c02897318bceaa36fa602d7`.
|
||||
Two repetitions for each EVM input produce six passing paired fact/settings
|
||||
comparisons with the same driver and explicit limits. Each report preserves both
|
||||
original Evidence reference maps. The complete actual SDK catalog is byte-identical
|
||||
on every pair, including order, names, descriptions, schemas and annotations;
|
||||
catalog SHA-256 is
|
||||
`8664e42fd5225645cfd910f440f731c67ac198d3d53aa85b5c485374c74cf603`.
|
||||
Result byte counts remain unchanged. Candidate Factory/Pool selectors also match
|
||||
the independent published ABI oracle.
|
||||
|
||||
| Question | Control seconds, repetitions 1 / 2 | Candidate seconds, repetitions 1 / 2 |
|
||||
| -------- | ---------------------------------: | -----------------------------------: |
|
||||
| Factory | 3.628 / 3.650 | 3.708 / 3.605 |
|
||||
| Pool | 11.211 / 3.591 | 3.749 / 3.581 |
|
||||
| WETH | 11.432 / 3.694 | 3.572 / 3.677 |
|
||||
|
||||
First controls preceded wiring; each second pair ran candidate then control.
|
||||
Every measurement starts a new MCP process; host cache/page state is not fully
|
||||
controlled. Slower initial controls do not establish a descriptor speedup.
|
||||
Second-pair latency and last-observed CPU are similar. Sampled EVM SDK family peaks
|
||||
range from 510,255,104 to 527,622,144 bytes. No scope abort occurred; final counters
|
||||
remain unknown, so these measurements are descriptive.
|
||||
|
||||
The candidate passes all 59 existing real CLI/MCP verifier cases with solc 0.8.30
|
||||
and unchanged EVMole 0.9.3. Actual syscall traces report target execution and network
|
||||
sockets absent for those scenarios, and the verifier reports no remaining
|
||||
descendants. The scope took 177.456 seconds, sampled peak 1,237,020,672 bytes and
|
||||
last-observed CPU 144.913 seconds. Different totals from the earlier control are
|
||||
not attributed to unchanged provider algorithms.
|
||||
|
||||
Post-descriptor public MCP regression uses the original pinned shipped corpus:
|
||||
|
||||
| Domain / selected question | Actual provider / scope | Calls | Seconds |
|
||||
| ------------------------------------------- | -------------------------------------------------- | ----: | ------: |
|
||||
| SQLite WASM counts / exports | WABT 1.0.42; selected summary and retained read | 2 | 5.516 |
|
||||
| ShareX managed metadata / native boundaries | Static PE/CLI, no assembly execution | 2 | 3.710 |
|
||||
| Obsidian manifest / inventory | Complete inventory retained, no AST claim | 4 | 5.376 |
|
||||
| NewPipe package / MainActivity methods | Real headless JADX 0.7.1 | 3 | 21.669 |
|
||||
| OpenWrt regions | Real firmware signature scan, no device execution | 1 | 3.581 |
|
||||
| jq native entry | Ghidra 12.1.4, default analysis, ephemeral Program | 4 | 270.986 |
|
||||
|
||||
All six workflows pass named SDK output validation and original digest checks;
|
||||
input bytes remain unchanged. Maximum sampled family peak is 1,284,648,960 bytes.
|
||||
They establish those available Linux workflows, not paired performance or
|
||||
Windows/macOS, IDA/Hopper, Android-device, DOS COM, browser or JavaScript AST
|
||||
behavior. Browser/AST were not repeated for this narrow migration.
|
||||
|
||||
## Real model complete-output calibration
|
||||
|
||||
Six serial Codex CLI 0.161.0 trials request `gpt-5.6-terra`, high reasoning, with
|
||||
isolated temporary profiles pointing to the frozen candidate. Resolved server-side
|
||||
model identity is not inferred. Only the EVM inspection tool is enabled, matching
|
||||
the earlier WETH calibration; this is not full-catalog tool selection verification.
|
||||
Every trial makes one successful call and no failed call.
|
||||
|
||||
| Trial | Total input | Uncached input | Output | Seconds |
|
||||
| --------- | ----------: | -------------: | -----: | ------: |
|
||||
| Factory 1 | 66,158 | 36,974 | 1,329 | 43.483 |
|
||||
| Pool 1 | 65,600 | 36,416 | 2,307 | 69.546 |
|
||||
| WETH 1 | 54,700 | 25,516 | 1,144 | 56.372 |
|
||||
| Factory 2 | 65,964 | 36,780 | 1,453 | 26.412 |
|
||||
| Pool 2 | 65,753 | 36,569 | 1,939 | 74.706 |
|
||||
| WETH 2 | 54,502 | 25,318 | 1,208 | 35.137 |
|
||||
|
||||
Every trial reports 29,184 cached input tokens. Selected fact/reference/lifecycle
|
||||
grades all pass: original/decoded identities, every inferred selector, offset,
|
||||
dispatch, argument/mutability string including null, explicit unknowns, no runtime
|
||||
execution, observed Evidence references, unchanged bytes and removed profiles.
|
||||
Raw transcripts and grades remain private. Sampled peaks are
|
||||
594,853,888–776,335,360 bytes; last-observed CPU is 6.181–7.034 seconds. Strict
|
||||
overall grades remain unverified because final resource counters are unknown;
|
||||
they are not relabeled as wholly passing resource-qualified runs.
|
||||
|
||||
This calibrates the current complete path after compatible wiring, not a paired
|
||||
model performance experiment. The next token experiment should answer the same
|
||||
interface question through an explicitly selected precise view, retain full
|
||||
bytecode/original Evidence under the existing lifetime, and preserve complete
|
||||
defaults. Discover support before sending new options to an older server. Include
|
||||
discovery, follow-up, failures and retries in the complete task cost.
|
||||
@@ -350,6 +350,38 @@ const inspectWasmTarget = async () => {
|
||||
};
|
||||
};
|
||||
|
||||
/** Identical complete interface question for pinned offline EVM carriers. */
|
||||
const inspectEvmTarget = async () => {
|
||||
const info = await call("inspect_evm_interface", {
|
||||
path: values.path,
|
||||
encoding: "hex",
|
||||
});
|
||||
const n = normalized(info);
|
||||
if (
|
||||
n.evidence_kind !== "inferred" ||
|
||||
n.discovery_completeness !== "unknown" ||
|
||||
n.runtime_execution !== "not-performed" ||
|
||||
["kind", "hardfork", "deployment_authenticity"].some(
|
||||
(key) => n.bytecode[key] !== "unknown",
|
||||
) ||
|
||||
(artifact.decoded_sha256 !== undefined &&
|
||||
n.bytecode.sha256 !== artifact.decoded_sha256) ||
|
||||
(artifact.decoded_bytes !== undefined &&
|
||||
n.bytecode.bytes !== artifact.decoded_bytes)
|
||||
)
|
||||
throw new Error("EVM interface identity or uncertainty changed");
|
||||
return {
|
||||
artifact_sha256: targetSha(info),
|
||||
artifact: n.artifact,
|
||||
bytecode: n.bytecode,
|
||||
functions: n.functions,
|
||||
discovery_completeness: n.discovery_completeness,
|
||||
runtime_execution: n.runtime_execution,
|
||||
limitations: n.limitations,
|
||||
evidence_ids: [info.evidence_id],
|
||||
};
|
||||
};
|
||||
|
||||
const workflows = {
|
||||
"obsidian.asar": async () => {
|
||||
if (values["javascript-question"] === "selected") {
|
||||
@@ -534,23 +566,9 @@ const workflows = {
|
||||
"sqlite.wasm": inspectWasmTarget,
|
||||
"vscode.tree-sitter.wasm": inspectWasmTarget,
|
||||
"vscode.tree-sitter-css.wasm": inspectWasmTarget,
|
||||
"weth.hex": async () => {
|
||||
const info = await call("inspect_evm_interface", {
|
||||
path: values.path,
|
||||
encoding: "hex",
|
||||
});
|
||||
const n = normalized(info);
|
||||
return {
|
||||
artifact_sha256: targetSha(info),
|
||||
artifact: n.artifact,
|
||||
bytecode: n.bytecode,
|
||||
functions: n.functions,
|
||||
discovery_completeness: n.discovery_completeness,
|
||||
runtime_execution: n.runtime_execution,
|
||||
limitations: n.limitations,
|
||||
evidence_ids: [info.evidence_id],
|
||||
};
|
||||
},
|
||||
"weth.hex": inspectEvmTarget,
|
||||
"uniswap-v3.factory.hex": inspectEvmTarget,
|
||||
"uniswap-v3.pool.hex": inspectEvmTarget,
|
||||
...Object.fromEntries(
|
||||
NATIVE_ARTIFACTS.map((artifact) => [
|
||||
artifact,
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
Statically inspect the published Uniswap v3-core 1.0.1 compiler runtime template at {{artifact_path}} using only REA inspect_evm_interface with encoding hex. Report original carrier SHA-256/byte count, decoded bytecode SHA-256/byte count, every inferred function selector with bytecode_offset, dispatch, inferred_arguments and inferred_state_mutability exactly including null. Preserve evidence_kind, discovery_completeness, bytecode kind/hardfork/deployment_authenticity, runtime_execution, real Evidence IDs and limitations. The unchanged template came from the official published compiler artifact; constructor-dependent values and immutable-reference metadata are not established. Do not infer a live deployed instance, original source signatures or runtime behavior. No RPC, transactions, target execution, shell/file tools or other agents. Final JSON fields: artifact_sha256, artifact_bytes, bytecode_sha256, bytecode_bytes, functions, evidence_kind, discovery_completeness, bytecode_kind, hardfork, deployment_authenticity, runtime_execution, evidence_ids, limitations.
|
||||
@@ -0,0 +1,95 @@
|
||||
import assert from "node:assert/strict";
|
||||
import { createHash } from "node:crypto";
|
||||
import { readFile, writeFile } from "node:fs/promises";
|
||||
import { isAbsolute } from "node:path";
|
||||
import { pathToFileURL } from "node:url";
|
||||
import { parseArgs } from "node:util";
|
||||
|
||||
const { values } = parseArgs({
|
||||
options: Object.fromEntries(
|
||||
["artifact", "artifact-json", "evidence", "keccak-module", "output"].map(
|
||||
(key) => [key, { type: "string" }],
|
||||
),
|
||||
),
|
||||
});
|
||||
assert.ok(values.artifact, "Select the exact corpus artifact ID");
|
||||
for (const key of ["artifact-json", "evidence", "keccak-module", "output"])
|
||||
assert.ok(isAbsolute(values[key] ?? ""), `${key} requires an absolute path`);
|
||||
const corpus = JSON.parse(
|
||||
await readFile(new URL("./corpus.json", import.meta.url), "utf8"),
|
||||
);
|
||||
const selected = corpus.artifacts.find(({ id }) => id === values.artifact);
|
||||
assert.ok(selected?.provenance.compiler_artifact_sha256);
|
||||
const source = await readFile(values["artifact-json"]);
|
||||
const hash = (bytes) => createHash("sha256").update(bytes).digest("hex");
|
||||
assert.equal(hash(source), selected.provenance.compiler_artifact_sha256);
|
||||
assert.equal(source.length, selected.provenance.compiler_artifact_bytes);
|
||||
const artifact = JSON.parse(source);
|
||||
const carrier = Buffer.from(artifact.deployedBytecode + "\n");
|
||||
assert.equal(hash(carrier), selected.sha256);
|
||||
assert.equal(carrier.length, selected.bytes);
|
||||
const { keccak_256 } = (await import(pathToFileURL(values["keccak-module"])))
|
||||
.default;
|
||||
assert.equal(
|
||||
typeof keccak_256,
|
||||
"function",
|
||||
"Supply js-sha3's Ethereum Keccak implementation",
|
||||
);
|
||||
const canonicalType = (input) =>
|
||||
input.type.startsWith("tuple")
|
||||
? `(${input.components.map(canonicalType).join(",")})${input.type.slice(5)}`
|
||||
: input.type;
|
||||
const selectors = artifact.abi
|
||||
.filter((entry) => entry.type === "function")
|
||||
.map((entry) => {
|
||||
const signature = `${entry.name}(${entry.inputs.map(canonicalType).join(",")})`;
|
||||
return { signature, selector: `0x${keccak_256(signature).slice(0, 8)}` };
|
||||
});
|
||||
const reply = JSON.parse(await readFile(values.evidence, "utf8"));
|
||||
const evidence = reply.structuredContent ?? reply;
|
||||
assert.equal(evidence.operation, "inspect_evm_interface");
|
||||
assert.equal(evidence.subject.digest.sha256, selected.sha256);
|
||||
assert.equal(evidence.normalized_result.artifact.sha256, selected.sha256);
|
||||
assert.equal(
|
||||
evidence.normalized_result.bytecode.sha256,
|
||||
selected.decoded_sha256,
|
||||
);
|
||||
assert.equal(
|
||||
hash(Buffer.from(evidence.normalized_result.bytecode.hex, "hex")),
|
||||
selected.decoded_sha256,
|
||||
);
|
||||
assert.equal(evidence.normalized_result.bytecode.bytes, selected.decoded_bytes);
|
||||
assert.equal(evidence.normalized_result.evidence_kind, "inferred");
|
||||
assert.equal(evidence.normalized_result.discovery_completeness, "unknown");
|
||||
const observed = evidence.normalized_result.functions
|
||||
.filter((entry) => entry.dispatch === "abi")
|
||||
.map((entry) => entry.selector);
|
||||
assert.deepEqual(
|
||||
observed.toSorted(),
|
||||
selectors.map((entry) => entry.selector).toSorted(),
|
||||
);
|
||||
await writeFile(
|
||||
values.output,
|
||||
JSON.stringify(
|
||||
{
|
||||
artifact: selected.id,
|
||||
compiler_artifact_sha256: hash(source),
|
||||
keccak_module_sha256: hash(await readFile(values["keccak-module"])),
|
||||
evidence_id: evidence.evidence_id,
|
||||
selector_set_matches_published_abi: true,
|
||||
selectors,
|
||||
scope:
|
||||
"Independent published ABI-derived selector oracle; no deployed authenticity, inferred argument/mutability equivalence or runtime verification is established.",
|
||||
},
|
||||
null,
|
||||
2,
|
||||
) + "\n",
|
||||
{ flag: "wx", mode: 0o600 },
|
||||
);
|
||||
process.stdout.write(
|
||||
JSON.stringify({
|
||||
artifact: selected.id,
|
||||
selector_count: selectors.length,
|
||||
selector_set_matches_published_abi: true,
|
||||
}) + "\n",
|
||||
);
|
||||
@@ -1,7 +1,6 @@
|
||||
import { z } from "incur";
|
||||
import { resolve } from "node:path";
|
||||
import { createEvmInterfaceService } from "../composition/evm.js";
|
||||
import { CLI_COMMANDS } from "../cliCommandNames.js";
|
||||
import { evmInterfaceModule } from "../composition/evm.js";
|
||||
import { logCliCommand } from "../cliLogging.js";
|
||||
import { projectAnalysisError } from "../domain/analysisErrorProjection.js";
|
||||
import type { Logger } from "pino";
|
||||
@@ -14,8 +13,8 @@ export const registerEvmCommands = (
|
||||
logger: Logger,
|
||||
environment: Readonly<NodeJS.ProcessEnv>,
|
||||
): void => {
|
||||
const service = createEvmInterfaceService(environment);
|
||||
cli.command(CLI_COMMANDS.inspectEvmInterface, {
|
||||
const service = evmInterfaceModule.create(environment);
|
||||
cli.command(evmInterfaceModule.cliCommand, {
|
||||
description: "Infer interface candidates from explicit local EVM bytecode",
|
||||
args: z.object({
|
||||
path: z.string().describe("Local bytecode carrier"),
|
||||
@@ -28,7 +27,7 @@ export const registerEvmCommands = (
|
||||
try {
|
||||
return await logCliCommand(
|
||||
logger,
|
||||
CLI_COMMANDS.inspectEvmInterface,
|
||||
evmInterfaceModule.cliCommand,
|
||||
async () => {
|
||||
const result = await service.inspect(
|
||||
{ path: resolve(args.path), encoding: args.encoding },
|
||||
|
||||
@@ -1,8 +1,21 @@
|
||||
import { EvmInterfaceService } from "../application/evm/EvmInterfaceService.js";
|
||||
import { EvmoleInterfaceProvider } from "../evm/EvmoleInterfaceProvider.js";
|
||||
import { CLI_COMMANDS } from "../cliCommandNames.js";
|
||||
import { INSPECT_EVM_INTERFACE_CONTRACT } from "../contracts/evm/evmToolContracts.js";
|
||||
|
||||
/** Compose offline interface analysis without import-time I/O or dependency acquisition. */
|
||||
export const createEvmInterfaceService = (
|
||||
environment: Readonly<NodeJS.ProcessEnv> = process.env,
|
||||
): EvmInterfaceService =>
|
||||
new EvmInterfaceService(new EvmoleInterfaceProvider(environment));
|
||||
|
||||
/**
|
||||
* Static binding of the existing operation, command and fresh service factory.
|
||||
* The host owns injected service identity and its retryable cleanup lifetime.
|
||||
* Construction performs no acquisition, target I/O or import-time execution.
|
||||
*/
|
||||
export const evmInterfaceModule = {
|
||||
contract: INSPECT_EVM_INTERFACE_CONTRACT,
|
||||
cliCommand: CLI_COMMANDS.inspectEvmInterface,
|
||||
create: createEvmInterfaceService,
|
||||
} as const;
|
||||
@@ -7,20 +7,23 @@ import { toolContractMetadata } from "../toolEffects.js";
|
||||
import { evidenceResultOf } from "../toolOutputSchemaPrimitives.js";
|
||||
|
||||
/** Target-free EVM capabilities are independent of archive/Apple artifact providers. */
|
||||
export const INSPECT_EVM_INTERFACE_CONTRACT = {
|
||||
name: "inspect_evm_interface",
|
||||
...toolContractMetadata("inspect_evm_interface"),
|
||||
kind: "artifact-provider",
|
||||
description:
|
||||
"Inspect an explicit local flat EVM bytecode carrier and infer dispatch selectors, argument strings and mutability without executing the target or accessing a chain/RPC endpoint. Choose raw or hex encoding explicitly. Returns original carrier and decoded-byte SHA-256, full bytecode and interface candidates inline as inferred Evidence; bytecode kind, hardfork, deployed authenticity and discovery completeness remain unknown. Initial real-verified Linux x64 profile uses unchanged bundled EVMole 0.9.3/WASM in a bounded owned worker; EOF-style EF00 containers are unsupported. No signature lookup, wallet action or transaction broadcast.",
|
||||
inputSchema: inspectEvmInterfaceInputSchema,
|
||||
outputSchema: evidenceResultOf(evmInterfaceSchema),
|
||||
examples: [
|
||||
{
|
||||
title: "Inspect a local runtime bytecode hex carrier",
|
||||
input: { path: "/artifacts/runtime.hex", encoding: "hex" },
|
||||
},
|
||||
],
|
||||
} as const satisfies ToolContract;
|
||||
|
||||
/** Preserve the default catalog's exact operation and ordering. */
|
||||
export const EVM_TOOL_CONTRACTS = [
|
||||
{
|
||||
name: "inspect_evm_interface",
|
||||
...toolContractMetadata("inspect_evm_interface"),
|
||||
kind: "artifact-provider",
|
||||
description:
|
||||
"Inspect an explicit local flat EVM bytecode carrier and infer dispatch selectors, argument strings and mutability without executing the target or accessing a chain/RPC endpoint. Choose raw or hex encoding explicitly. Returns original carrier and decoded-byte SHA-256, full bytecode and interface candidates inline as inferred Evidence; bytecode kind, hardfork, deployed authenticity and discovery completeness remain unknown. Initial real-verified Linux x64 profile uses unchanged bundled EVMole 0.9.3/WASM in a bounded owned worker; EOF-style EF00 containers are unsupported. No signature lookup, wallet action or transaction broadcast.",
|
||||
inputSchema: inspectEvmInterfaceInputSchema,
|
||||
outputSchema: evidenceResultOf(evmInterfaceSchema),
|
||||
examples: [
|
||||
{
|
||||
title: "Inspect a local runtime bytecode hex carrier",
|
||||
input: { path: "/artifacts/runtime.hex", encoding: "hex" },
|
||||
},
|
||||
],
|
||||
},
|
||||
INSPECT_EVM_INTERFACE_CONTRACT,
|
||||
] as const satisfies readonly ToolContract[];
|
||||
@@ -5,7 +5,7 @@ import { snapshotEnvironment } from "../process/snapshotEnvironment.js";
|
||||
import { createPeResourcesService } from "../composition/binaryDiagnostics.js";
|
||||
import { registerPeResourcesTool } from "./registerPeResourcesTool.js";
|
||||
import type { EvmInterfaceService } from "../application/evm/EvmInterfaceService.js";
|
||||
import { createEvmInterfaceService } from "../composition/evm.js";
|
||||
import { evmInterfaceModule } from "../composition/evm.js";
|
||||
import { registerEvmTools } from "./registerEvmTools.js";
|
||||
import { registerRecordedCrashTools } from "./registerRecordedCrashTools.js";
|
||||
import { createRecordedCrashService } from "../composition/binaryDiagnostics.js";
|
||||
@@ -242,7 +242,7 @@ export const createServer = (
|
||||
const environment = snapshotEnvironment(options.environment ?? process.env);
|
||||
const delivery = selectToolResultDelivery(environment, options.delivery);
|
||||
const evmInterface =
|
||||
options.evmInterface ?? createEvmInterfaceService(environment);
|
||||
options.evmInterface ?? evmInterfaceModule.create(environment);
|
||||
const webSourceLocation =
|
||||
options.webSourceLocation ?? createWebSourceLocationService(environment);
|
||||
const webModuleTrace =
|
||||
@@ -372,6 +372,7 @@ export const createServer = (
|
||||
toolContext,
|
||||
android,
|
||||
wasmArtifact,
|
||||
evmInterface,
|
||||
jeb,
|
||||
firmwareAnalysis,
|
||||
);
|
||||
@@ -449,6 +450,7 @@ const registerConfiguredAnalysisTools = (
|
||||
}: ServerToolContext,
|
||||
android: AndroidAnalysisPort,
|
||||
wasmArtifact: Pick<WasmArtifactService, "inspect" | "close">,
|
||||
evmInterface: EvmInterfaceService,
|
||||
jeb: JebAnalysisPort,
|
||||
firmwareAnalysis: FirmwareAnalysisService,
|
||||
): void => {
|
||||
@@ -505,7 +507,7 @@ const registerConfiguredAnalysisTools = (
|
||||
);
|
||||
registerEvmTools(
|
||||
server,
|
||||
options.evmInterface ?? createEvmInterfaceService(environment),
|
||||
evmInterface,
|
||||
toolLogger,
|
||||
recordEvidence,
|
||||
withAdmittedAnalysis,
|
||||
|
||||
@@ -2,7 +2,7 @@ import type { EvidenceMcpServer } from "./EvidenceMcpServer.js";
|
||||
|
||||
import type { EvmInterfaceService } from "../application/evm/EvmInterfaceService.js";
|
||||
import type { EvidenceWriter } from "../application/investigation/InvestigationRecordPort.js";
|
||||
import { toolContract } from "../contracts/toolContracts.js";
|
||||
import { evmInterfaceModule } from "../composition/evm.js";
|
||||
import type { Logger } from "pino";
|
||||
import type { WithAdmittedAnalysis } from "./analysisAdmission.js";
|
||||
import { createEvidenceToolHandler } from "./contractToolHandler.js";
|
||||
@@ -16,7 +16,7 @@ export const registerEvmTools = (
|
||||
recordEvidence?: EvidenceWriter["recordEvidence"],
|
||||
withAdmittedAnalysis?: WithAdmittedAnalysis,
|
||||
): void => {
|
||||
const contract = toolContract("inspect_evm_interface");
|
||||
const contract = evmInterfaceModule.contract;
|
||||
const handler = createEvidenceToolHandler(
|
||||
server,
|
||||
(input, options) => service.inspect(input, options),
|
||||
|
||||
@@ -1,12 +1,25 @@
|
||||
import { Client, InMemoryTransport } from "@modelcontextprotocol/client";
|
||||
import { Ajv2020 } from "ajv/dist/2020.js";
|
||||
import { expect, it, onTestFinished } from "vitest";
|
||||
import { expect, it, onTestFinished, vi } from "vitest";
|
||||
import { EvmInterfaceService } from "../../../src/application/evm/EvmInterfaceService.js";
|
||||
import { WasmArtifactService } from "../../../src/application/wasm/WasmArtifactService.js";
|
||||
import { toolContract } from "../../../src/contracts/toolContracts.js";
|
||||
import {
|
||||
AnalysisCancelledError,
|
||||
AnalysisCapabilityUnavailableError,
|
||||
} from "../../../src/domain/analysisErrorCore.js";
|
||||
import { parseEvidence } from "../../../src/domain/evidence.js";
|
||||
import { ok } from "../../../src/domain/result.js";
|
||||
import { err, ok } from "../../../src/domain/result.js";
|
||||
import { silentLogger } from "../../../src/logger.js";
|
||||
import { createServer } from "../../../src/server/createServer.js";
|
||||
import { createTestBinarySession } from "../../fixtures/binarySession.js";
|
||||
import { registerEvmTools } from "../../../src/server/registerEvmTools.js";
|
||||
import { createApplicationMcpHarness } from "../../fixtures/applicationMcpHarness.js";
|
||||
import {
|
||||
createDeferred,
|
||||
createTestBinarySession,
|
||||
} from "../../fixtures/binarySession.js";
|
||||
import { parseMcpToolError } from "../../fixtures/mcpToolError.js";
|
||||
import { wasmArtifactExecution } from "../../fixtures/wasm/artifact.js";
|
||||
import {
|
||||
evmInterfaceFixture,
|
||||
evmInterfaceExecution,
|
||||
@@ -54,6 +67,14 @@ it("advertises exact valid schemas and records inline EVM interface evidence wit
|
||||
approval: true,
|
||||
}),
|
||||
).toBe(false);
|
||||
expect(() => registerEvmTools(server, service, silentLogger)).toThrow(
|
||||
/inspect_evm_interface.*already registered/,
|
||||
);
|
||||
expect(
|
||||
(await client.listTools()).tools.filter(
|
||||
(tool) => tool.name === "inspect_evm_interface",
|
||||
),
|
||||
).toHaveLength(1);
|
||||
const response = await client.callTool({
|
||||
name: "inspect_evm_interface",
|
||||
arguments: { path: value.artifact.path, encoding: "hex" },
|
||||
@@ -80,3 +101,151 @@ it("advertises exact valid schemas and records inline EVM interface evidence wit
|
||||
).isError,
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
it.each(["unavailable", "incompatible-output"])(
|
||||
"preserves a healthy WASM peer and explicit EVM failure: %s",
|
||||
async (scenario) => {
|
||||
const value = evmInterfaceFixture();
|
||||
const inspect = vi.fn(() =>
|
||||
Promise.resolve(
|
||||
scenario === "unavailable"
|
||||
? err(
|
||||
new AnalysisCapabilityUnavailableError(
|
||||
"fixture-evm",
|
||||
"inspect_evm_interface",
|
||||
"selected engine unavailable",
|
||||
),
|
||||
)
|
||||
: ok(
|
||||
evmInterfaceExecution({
|
||||
...value,
|
||||
artifact: { ...value.artifact, encoding: "raw" },
|
||||
}),
|
||||
),
|
||||
),
|
||||
);
|
||||
const harness = await createApplicationMcpHarness({
|
||||
serverOptions: {
|
||||
evmInterface: new EvmInterfaceService({ inspect }),
|
||||
wasmArtifact: new WasmArtifactService({
|
||||
close: () => Promise.resolve(),
|
||||
inspect: () => Promise.resolve(ok(wasmArtifactExecution())),
|
||||
}),
|
||||
},
|
||||
});
|
||||
onTestFinished(harness.close);
|
||||
const invalid = await harness.client.callTool({
|
||||
name: "inspect_evm_interface",
|
||||
arguments: { path: value.artifact.path, encoding: "hex", approval: true },
|
||||
});
|
||||
expect(invalid.isError).toBe(true);
|
||||
expect(inspect).not.toHaveBeenCalled();
|
||||
const failure = await harness.client.callTool({
|
||||
name: "inspect_evm_interface",
|
||||
arguments: { path: value.artifact.path, encoding: "hex" },
|
||||
});
|
||||
expect(parseMcpToolError(failure).error.code).toBe(
|
||||
scenario === "unavailable"
|
||||
? "capability_unavailable"
|
||||
: "unreadable_output",
|
||||
);
|
||||
const peer = await harness.client.callTool({
|
||||
name: "inspect_wasm_artifact",
|
||||
arguments: { path: "/artifacts/module.wasm" },
|
||||
});
|
||||
expect(peer.isError).not.toBe(true);
|
||||
const parsed = toolContract("inspect_wasm_artifact").outputSchema.parse(
|
||||
peer.structuredContent,
|
||||
);
|
||||
const evidence = parseEvidence(parsed);
|
||||
expect(harness.session.evidenceById(evidence.evidence_id)).toEqual(
|
||||
evidence,
|
||||
);
|
||||
await harness.client.ping();
|
||||
},
|
||||
);
|
||||
|
||||
it("retains the injected EVM owner and retries failed cleanup on server shutdown", async () => {
|
||||
const close = vi
|
||||
.fn()
|
||||
.mockRejectedValueOnce(new Error("retained EVM worker"))
|
||||
.mockResolvedValue(undefined);
|
||||
const service = new EvmInterfaceService({
|
||||
close,
|
||||
inspect: () => Promise.resolve(ok(evmInterfaceExecution())),
|
||||
});
|
||||
const session = createTestBinarySession(() => {
|
||||
throw new Error("deep provider must not start");
|
||||
});
|
||||
const server = createServer(
|
||||
{ kind: "session", session },
|
||||
{ evmInterface: service },
|
||||
);
|
||||
const client = new Client({ name: "evm-cleanup-retry", version: "1" });
|
||||
const [a, b] = InMemoryTransport.createLinkedPair();
|
||||
try {
|
||||
await server.connect(b);
|
||||
await client.connect(a);
|
||||
const response = await client.callTool({
|
||||
name: "inspect_evm_interface",
|
||||
arguments: { path: "/artifacts/source-owned.hex", encoding: "hex" },
|
||||
});
|
||||
expect(response.isError).not.toBe(true);
|
||||
await expect(server.close()).rejects.toThrow("REA session cleanup failed");
|
||||
await server.close();
|
||||
expect(close).toHaveBeenCalledTimes(2);
|
||||
} finally {
|
||||
await client.close();
|
||||
await server.close();
|
||||
await session.close();
|
||||
}
|
||||
});
|
||||
|
||||
it("settles the admitted EVM operation after SDK cancellation and accepts a subsequent request", async () => {
|
||||
const entered = createDeferred<void>();
|
||||
const stopped = createDeferred<void>();
|
||||
let invocations = 0;
|
||||
const harness = await createApplicationMcpHarness({
|
||||
serverOptions: {
|
||||
evmInterface: new EvmInterfaceService({
|
||||
inspect: async (_input, options) => {
|
||||
if (invocations++ > 0) return ok(evmInterfaceExecution());
|
||||
const signal = options?.signal;
|
||||
if (signal === undefined)
|
||||
throw new Error("MCP request omitted cancellation signal");
|
||||
const aborted = new Promise<void>((resolve) => {
|
||||
if (signal.aborted) resolve();
|
||||
else
|
||||
signal.addEventListener("abort", () => resolve(), { once: true });
|
||||
});
|
||||
entered.resolve();
|
||||
await aborted;
|
||||
stopped.resolve();
|
||||
return err(new AnalysisCancelledError("inspect_evm_interface"));
|
||||
},
|
||||
}),
|
||||
},
|
||||
});
|
||||
onTestFinished(harness.close);
|
||||
const controller = new AbortController();
|
||||
const input = {
|
||||
name: "inspect_evm_interface",
|
||||
arguments: { path: "/artifacts/source-owned.hex", encoding: "hex" },
|
||||
};
|
||||
const cancellation = harness.client
|
||||
.callTool(input, { signal: controller.signal })
|
||||
.then(
|
||||
() => "completed",
|
||||
(error: unknown) => error,
|
||||
);
|
||||
await entered.promise;
|
||||
controller.abort(new Error("SDK EVM cancellation verification"));
|
||||
expect(await cancellation).toMatchObject({
|
||||
message: expect.stringContaining("SDK EVM cancellation verification"),
|
||||
code: "REQUEST_TIMEOUT",
|
||||
});
|
||||
await stopped.promise;
|
||||
await harness.client.ping();
|
||||
expect((await harness.client.callTool(input)).isError).not.toBe(true);
|
||||
expect(invocations).toBe(2);
|
||||
});
|
||||
Reference in new issue
Block a user