{"record":{"id":"80b3b2ade21da812","repo":"nautechsystems/nautilus_trader","slug":"verified-call-trace-contains-an-unreviewed-call-t","errorCode":null,"errorMessage":"Verified call trace contains an unreviewed {call_type} edge {} -> {target} for {purpose}","messagePattern":"Verified call trace contains an unreviewed (.+?) edge (.+?) -> (.+?) for (.+?)","errorType":"validation","errorClass":"anyhow::Error","httpStatus":null,"severity":"critical","filePath":"crates/adapters/blockchain/src/execution/client.rs","lineNumber":4445,"sourceCode":"        let target = call.to.ok_or_else(|| {\n            anyhow::anyhow!(\"Verified call trace contains an operation without a target\")\n        })?;\n        let call_type = match call.call_type {\n            RpcCallType::Call => \"call\",\n            RpcCallType::Callcode => \"callcode\",\n            RpcCallType::Delegatecall => \"delegatecall\",\n            RpcCallType::Staticcall => \"staticcall\",\n            RpcCallType::Create | RpcCallType::Create2 | RpcCallType::Selfdestruct => {\n                anyhow::bail!(\"Verified call trace contains a forbidden state-changing operation\")\n            }\n        };\n        let permitted = manifest.call_edges.iter().any(|edge| {\n            edge.purpose == purpose\n                && edge.call_type.eq_ignore_ascii_case(call_type)\n                && Address::from_str(&edge.caller).ok() == Some(call.from)\n                && Address::from_str(&edge.target).ok() == Some(target)\n        });\n        anyhow::ensure!(\n            permitted,\n            \"Verified call trace contains an unreviewed {call_type} edge {} -> {target} for {purpose}\",\n            call.from\n        );\n        let child_context = match call.call_type {\n            RpcCallType::Call | RpcCallType::Staticcall => target,\n            RpcCallType::Callcode | RpcCallType::Delegatecall => caller_context,\n            RpcCallType::Create | RpcCallType::Create2 | RpcCallType::Selfdestruct => {\n                unreachable!(\"forbidden operations return before child traversal\")\n            }\n        };\n        validate_internal_calls(&call.calls, child_context, purpose, manifest)?;\n    }\n    Ok(())\n}\n\nasync fn verify_wrap_balance_increase(\n    executor: &TransactionExecutor,","sourceCodeStart":4427,"sourceCodeEnd":4463,"githubUrl":"https://github.com/nautechsystems/nautilus_trader/blob/18893faf8b356be3320add8de2f861b0b647cf06/crates/adapters/blockchain/src/execution/client.rs#L4427-L4463","documentation":"This error is thrown by `validate_internal_calls` when an internal call frame in the verified trace corresponds to a caller→target edge (for a given call type) that is not listed in the deployment manifest's reviewed `call_edges` for the execution `purpose`. The deployment manifest is an allowlist of contract-to-contract call edges that have been manually reviewed; any edge observed on chain that is not in the allowlist means the transaction performed an unreviewed interaction, which is rejected as a security control.","triggerScenarios":"`anyhow::ensure!(permitted, ...)` at client.rs:4445 fails when no entry in `manifest.call_edges` matches all of: `edge.purpose == purpose`, `edge.call_type` (case-insensitive) equals the frame's call type (`call`/`callcode`/`delegatecall`/`staticcall`), `edge.caller` parses to the frame's `from` address, and `edge.target` parses to the frame's `to` address. Triggered whenever a traced internal call touches a contract pair not reviewed for that purpose.","commonSituations":"A deployed contract was upgraded or its dependencies changed so it now calls a new contract address not in the reviewed manifest; the manifest was generated before a dependency (e.g. a token or router) was swapped; running an execution with a `purpose` whose edges were never added to the manifest; a mistyped or checksummed-vs-lowercase address in the manifest preventing a match; the trace legitimately follows a deeper edge that reviewers forgot to approve.","solutions":["Review the reported `{call_type} edge {from} -> {target} for {purpose}` in the message; if the interaction is expected and safe, add a matching entry to `call_edges` in the deployment manifest (purpose, call_type, caller, target) and regenerate/redeploy the manifest.","Verify the addresses in the manifest match the on-chain addresses exactly (checksum/lowercase is handled via `Address::from_str`, but wrong or stale addresses will never match); update stale entries after any contract upgrade or dependency swap.","Confirm the `purpose` string passed to execution/verification exactly matches the purpose recorded on the manifest edges (case-sensitive).","If the unexpected edge comes from a contract upgrade you did not intend, pin the expected bytecode/dependencies in the manifest verification and redeploy the correct contract version.","Re-run the trace after updating the manifest; do not bypass or weaken the allowlist check."],"exampleFix":"// before: manifest missing the newly introduced token edge\n[[deployment.call_edges]]\npurpose = \"wrap\"\ncall_type = \"call\"\ncaller = \"0xrouter...\"\ntarget = \"0xoldweth...\"  # router now calls 0xnewweth...\n\n// after: add the reviewed edge the trace observed\n[[deployment.call_edges]]\npurpose = \"wrap\"\ncall_type = \"call\"\ncaller = \"0xrouter...\"\ntarget = \"0xnewweth...\"  # reviewed and approved","handlingStrategy":"validation","validationCode":"// Pre-check that every edge the trace will exercise is in the manifest allowlist.\nfn edges_permitted(calls: &[VerifiedCallTrace], ctx: Address, purpose: &str, manifest: &BlockchainDeploymentManifest) -> bool {\n    calls.iter().all(|c| {\n        let Some(target) = c.to else { return false };\n        let call_type = match c.call_type {\n            RpcCallType::Call => \"call\",\n            RpcCallType::Callcode => \"callcode\",\n            RpcCallType::Delegatecall => \"delegatecall\",\n            RpcCallType::Staticcall => \"staticcall\",\n            _ => return false,\n        };\n        manifest.call_edges.iter().any(|e| {\n            e.purpose == purpose\n                && e.call_type.eq_ignore_ascii_case(call_type)\n                && Address::from_str(&e.caller).ok() == Some(c.from)\n                && Address::from_str(&e.target).ok() == Some(target)\n        })\n    })\n}","typeGuard":"fn edge_is_reviewed(edge: &ManifestCallEdge, purpose: &str, from: Address, target: Address, call_type: &str) -> bool {\n    edge.purpose == purpose\n        && edge.call_type.eq_ignore_ascii_case(call_type)\n        && Address::from_str(&edge.caller).ok() == Some(from)\n        && Address::from_str(&edge.target).ok() == Some(target)\n}","tryCatchPattern":"match result {\n    Err(e) if e.to_string().starts_with(\"Verified call trace contains an unreviewed\") => {\n        // surface the edge for security review; add to call_edges only after human approval\n        request_manifest_review(e.to_string());\n        Err(e)\n    }\n    other => other,\n}","preventionTips":["Regenerate and review the deployment manifest's call_edges after every contract upgrade or dependency change.","Keep address entries canonical (checksummed or lowercase consistently) in the manifest to avoid parse mismatches.","Use exact, stable `purpose` strings shared between execution, manifest, and verification.","Dry-run/simulate executions against a fork first and diff observed edges against the allowlist before live submission.","Never bypass or auto-extend the allowlist in response to this error; route new edges through manual review."],"tags":["blockchain","security","allowlist","call-trace","deployment-manifest"],"backgroundTag":"permission-denied","analyzedSha":"18893faf8b356be3320add8de2f861b0b647cf06","analyzedAt":"2026-09-08T20:49:34.690Z","contentChangedAt":"2026-09-08T20:49:34.690Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}