From a810f4826f324401bed140b6945d1c0d9c1d7236 Mon Sep 17 00:00:00 2001 From: Jacob Stenum Czepluch Date: Fri, 23 Jan 2026 16:36:36 +0100 Subject: [PATCH] minor release updates and update backtesting interface and docs --- credible/backtesting-reference.mdx | 144 ++++++++++++----------------- credible/backtesting.mdx | 129 +++++++++++--------------- credible/cheatcodes-reference.mdx | 3 + credible/cli-reference.mdx | 12 +-- credible/credible-std-overview.mdx | 2 +- 5 files changed, 124 insertions(+), 166 deletions(-) diff --git a/credible/backtesting-reference.mdx b/credible/backtesting-reference.mdx index f2cb18a..798a1b4 100644 --- a/credible/backtesting-reference.mdx +++ b/credible/backtesting-reference.mdx @@ -13,7 +13,7 @@ New to backtesting? Start with the [Backtesting Guide](/credible/backtesting) to ### `targetContract` -The address of the contract ([Assertion Adopter](/credible/glossary#assertion-adopter)) to test assertions against. The backtesting tool will find all transactions that interact with this contract (either directly or internally, depending on `useTraceFilter`). +The address of the contract ([Assertion Adopter](/credible/glossary#assertion-adopter)) to test assertions against. The backtesting tool will find all transactions that interact with this contract, including both direct calls and internal calls (e.g., through routers or aggregators). ```solidity targetContract: 0x5fd84259d66Cd46123540766Be93DFE6D43130D7 @@ -65,51 +65,12 @@ rpcUrl: "https://sepolia.optimism.io" rpcUrl: vm.envString("MAINNET_RPC_URL") ``` -### `useTraceFilter` - -Controls how transactions are detected in the block range. - -**`true` (recommended):** Uses the `trace_filter` RPC method -- Fetches all transactions in a single RPC call per batch -- Detects both direct calls and internal calls to the target contract -- **RPC calls for 100 blocks:** ~1-2 calls - -**`false`:** Scans each block individually -- One `eth_getBlockByNumber` call per block -- Only detects direct calls where `tx.to == targetContract` -- Does not detect internal calls (e.g., when another contract calls your target contract through a router or aggregator) -- **RPC calls for 100 blocks:** ~100 calls - -Use `trace_filter` for large block ranges or when you need to detect internal calls. Use block scanning when your RPC provider doesn't support `trace_filter`. - - -Not all RPC providers support `trace_filter`, and some providers only support it on certain networks. Check your provider's documentation to confirm `trace_filter` availability for your target chain. - - -```solidity -useTraceFilter: true // Recommended for most cases -useTraceFilter: false // For RPC providers and endpoints without trace_filter support -``` - ### `forkByTxHash` -Controls how the EVM state is forked for each transaction replay. - -**`false` (recommended):** Forks at the start of the block -- Uses `vm.createSelectFork(rpcUrl, blockNumber)` -- Faster and more RPC-efficient -- Suitable for most backtesting scenarios - -**`true`:** Forks at the exact transaction -- Uses `vm.createSelectFork(rpcUrl, txHash)` -- Replays all prior transactions in the block to get exact state -- Slower but necessary for transactions where transactions earlier in the block change the state of the target contract - -Use `false` for normal backtesting. Use `true` only when debugging specific failures where exact transaction state is needed. +Controls how the EVM state is forked for each transaction replay. This parameter is kept for interface compatibility but is effectively always `true` internally to ensure correct pre-transaction state. ```solidity -forkByTxHash: false // Recommended default -forkByTxHash: true // Only for debugging specific failures +forkByTxHash: true // Default and recommended ``` ### `detailedBlocks` @@ -120,38 +81,6 @@ Controls logging verbosity. Currently reserved for future functionality. detailedBlocks: false // Standard output ``` -## RPC Call Estimates - -Understanding RPC usage helps you plan for rate limits and choose the right RPC provider. - -### With trace_filter (Recommended) - -**Configuration:** -```solidity -useTraceFilter: true -``` - -**100 blocks, 50 matching transactions:** -- **Fetching:** 1-2 calls -- **Validation:** 50 calls (one per transaction) -- **Total:** ~52 RPC calls - -### Without trace_filter - -**Configuration:** -```solidity -useTraceFilter: false -``` - -**100 blocks, 50 matching transactions:** -- **Fetching:** 100 calls (one per block) -- **Validation:** 50 calls (one per transaction) -- **Total:** ~150 RPC calls - - -For large block ranges (>1,000 blocks), use paid RPC providers to avoid rate limiting. The `useTraceFilter` option significantly reduces RPC calls. - - ## API Reference ### executeBacktest @@ -181,12 +110,54 @@ BacktestingTypes.BacktestingResults memory results = executeBacktest( assertionSelector: MyAssertion.assertionInvariant.selector, rpcUrl: "https://sepolia.optimism.io", detailedBlocks: false, - useTraceFilter: true, - forkByTxHash: false + forkByTxHash: true }) ); ``` +### executeBacktestForTransaction + +Executes a backtest for a single transaction specified by its hash. This is useful for: +- Testing assertions against specific known transactions (such as historical exploits) +- Debugging potential false positives reported during staging (see [Testing Strategy](/credible/testing-strategy) for more on the staging workflow) +- Quickly validating assertion behavior on a problematic transaction without specifying a block range + +```solidity +function executeBacktestForTransaction( + bytes32 txHash, + address targetContract, + bytes memory assertionCreationCode, + bytes4 assertionSelector, + string memory rpcUrl +) public returns (BacktestingTypes.BacktestingResults memory results) +``` + +**Parameters:** +- `txHash`: The transaction hash to backtest +- `targetContract`: The target contract address (assertion adopter) +- `assertionCreationCode`: The assertion contract creation code +- `assertionSelector`: The assertion function selector +- `rpcUrl`: The RPC URL to use + +**Returns:** +- `results`: Results struct containing test execution statistics + +**Example:** +```solidity +bytes32 exploitTxHash = 0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef; + +BacktestingTypes.BacktestingResults memory results = executeBacktestForTransaction( + exploitTxHash, + TARGET_CONTRACT, + type(MyAssertion).creationCode, + MyAssertion.assertionInvariant.selector, + vm.envString("MAINNET_RPC_URL") +); + +// Expect the assertion to catch the exploit +assertEq(results.assertionFailures, 1, "Assertion should catch the exploit"); +``` + ### BacktestingConfig Configuration struct for backtesting parameters. @@ -200,7 +171,6 @@ struct BacktestingConfig { bytes4 assertionSelector; // Assertion function selector string rpcUrl; // RPC endpoint bool detailedBlocks; // Detailed logging (reserved) - bool useTraceFilter; // Use trace_filter API bool forkByTxHash; // Fork at exact transaction } ``` @@ -216,8 +186,7 @@ struct BacktestingConfig { | `assertionSelector` | `bytes4` | Function selector of the assertion to execute | | `rpcUrl` | `string` | RPC endpoint URL for blockchain data | | `detailedBlocks` | `bool` | Enable detailed logging (reserved for future use) | -| `useTraceFilter` | `bool` | Use trace_filter API for transaction detection | -| `forkByTxHash` | `bool` | Fork at exact transaction vs. block start | +| `forkByTxHash` | `bool` | Fork at exact transaction (kept for interface compatibility) | ### BacktestingResults @@ -228,8 +197,9 @@ struct BacktestingResults { uint256 totalTransactions; // Total transactions found uint256 processedTransactions; // Transactions validated uint256 successfulValidations; // Passing validations - uint256 failedValidations; // Failing validations + uint256 skippedTransactions; // Transactions where assertion wasn't triggered uint256 assertionFailures; // Protocol violations detected + uint256 replayFailures; // Transactions that failed to replay uint256 unknownErrors; // Unexpected errors } ``` @@ -241,8 +211,9 @@ struct BacktestingResults { | `totalTransactions` | `uint256` | Total number of transactions found in the block range | | `processedTransactions` | `uint256` | Number of transactions that were validated | | `successfulValidations` | `uint256` | Transactions that passed validation | -| `failedValidations` | `uint256` | Transactions that failed validation | -| `assertionFailures` | `uint256` | Number of protocol violations detected | +| `skippedTransactions` | `uint256` | Transactions where the assertion wasn't triggered (function selector didn't match) | +| `assertionFailures` | `uint256` | Number of protocol violations detected (assertion reverted) | +| `replayFailures` | `uint256` | Transactions that failed to replay before assertion could execute | | `unknownErrors` | `uint256` | Unexpected errors during execution | **Interpreting Results:** @@ -259,7 +230,8 @@ if (results.assertionFailures > 0) { // Log detailed results console.log("Total transactions:", results.totalTransactions); console.log("Successful validations:", results.successfulValidations); -console.log("Failed validations:", results.failedValidations); +console.log("Skipped transactions:", results.skippedTransactions); +console.log("Assertion failures:", results.assertionFailures); ``` **Common Causes of Assertion Failures:** @@ -285,8 +257,7 @@ BacktestingTypes.BacktestingConfig({ assertionSelector: MyAssertion.assertionInvariant.selector, rpcUrl: vm.envString("MAINNET_RPC_URL"), detailedBlocks: false, - useTraceFilter: true, // Efficient fetching - forkByTxHash: false // Fast validation + forkByTxHash: true }) ``` @@ -295,6 +266,9 @@ BacktestingTypes.BacktestingConfig({ ## Learn More + + Autogenerated API documentation for backtesting + Learn how to use backtesting to validate assertions diff --git a/credible/backtesting.mdx b/credible/backtesting.mdx index e70cd63..c1f3309 100644 --- a/credible/backtesting.mdx +++ b/credible/backtesting.mdx @@ -13,14 +13,12 @@ If you're looking for the reference documentation on all configuration parameter Backtesting operates in two phases: -1. **Transaction Fetching**: Identifies all transactions to the target contract in the block range +1. **Transaction Fetching**: Identifies all transactions to the target contract in the block range using trace APIs (with automatic fallback) 2. **Transaction Validation**: Replays each transaction with your assertion enabled -The tool fetches transactions using one of two methods: -- **trace_filter API** (recommended): Single RPC call for the entire block range, detects both direct and internal calls (e.g., when Contract A calls your target Contract B through a router or aggregator) -- **Block scanning**: One RPC call per block, detects only direct calls where `tx.to` equals your target contract (does not detect internal calls) +The tool automatically detects both direct calls and internal calls (e.g., when Contract A calls your target Contract B through a router or aggregator) using trace APIs. -For validation, the tool forks the EVM at each transaction to the target contract and replays it against the specified assertion. +For validation, the tool forks the EVM at the exact transaction state and replays it against the specified assertion. ## Example @@ -28,8 +26,8 @@ For validation, the tool forks the EVM at each transaction to the target contrac // SPDX-License-Identifier: MIT pragma solidity ^0.8.20; -import {CredibleTestWithBacktesting} from "../src/backtesting/CredibleTestWithBacktesting.sol"; -import {BacktestingTypes} from "../src/backtesting/BacktestingTypes.sol"; +import {CredibleTestWithBacktesting} from "credible-std/CredibleTestWithBacktesting.sol"; +import {BacktestingTypes} from "credible-std/utils/BacktestingTypes.sol"; import {MyAssertion} from "../assertions/src/MyAssertion.a.sol"; contract MyBacktestingTest is CredibleTestWithBacktesting { @@ -43,13 +41,12 @@ contract MyBacktestingTest is CredibleTestWithBacktesting { assertionSelector: MyAssertion.assertionInvariant.selector, rpcUrl: "https://sepolia.optimism.io", detailedBlocks: false, - useTraceFilter: true, - forkByTxHash: false + forkByTxHash: true }) ); // Check results - assert(results.assertionFailures == 0, "Found protocol violations!"); + assertEq(results.assertionFailures, 0, "Found protocol violations!"); } } ``` @@ -62,7 +59,7 @@ Consider excluding backtesting from your CI/CD pipeline as it can take time to r ## Common Configurations -### Large Block Ranges (100+ blocks) +### Standard Configuration ```solidity BacktestingTypes.BacktestingConfig({ @@ -73,51 +70,11 @@ BacktestingTypes.BacktestingConfig({ assertionSelector: MyAssertion.assertionInvariant.selector, rpcUrl: vm.envString("MAINNET_RPC_URL"), detailedBlocks: false, - useTraceFilter: true, // Fast fetching - forkByTxHash: false // Fast validation -}) -``` - -Expected runtime: ~20 seconds for 100 blocks with 28 transactions - -### Debugging Failures - -```solidity -BacktestingTypes.BacktestingConfig({ - targetContract: TARGET_CONTRACT, - endBlock: FAILING_BLOCK, - blockRange: 5, // Small range around failure - assertionCreationCode: type(MyAssertion).creationCode, - assertionSelector: MyAssertion.assertionInvariant.selector, - rpcUrl: vm.envString("MAINNET_RPC_URL"), - detailedBlocks: false, - useTraceFilter: true, - forkByTxHash: true // Exact state for debugging -}) -``` - -Use this when investigating why a specific transaction failed. The `forkByTxHash: true` setting replays all prior transactions to get the exact state just before execution. -This is disabled by default, since it uses significantly more RPC calls and hence is slower. It is recommended to use this setting when debugging failures for concentrated block intervals. - -### Quick Tests (< 20 blocks) - -```solidity -BacktestingTypes.BacktestingConfig({ - targetContract: TARGET_CONTRACT, - endBlock: RECENT_BLOCK, - blockRange: 10, - assertionCreationCode: type(MyAssertion).creationCode, - assertionSelector: MyAssertion.assertionInvariant.selector, - rpcUrl: vm.envString("MAINNET_RPC_URL"), - detailedBlocks: false, - useTraceFilter: false, // Simple method sufficient - forkByTxHash: false + forkByTxHash: true }) ``` -For small ranges, the simpler block scanning method is adequate. - -### Testing Against a Specific Transaction +### Testing Against a Specific Block When you want to test your assertion against a specific known transaction (such as a historical exploit), set `blockRange: 1` with the block number containing that transaction: @@ -130,7 +87,6 @@ BacktestingTypes.BacktestingConfig({ assertionSelector: BatchSwapDeltaAssertion.assertionBatchSwapRateManipulation.selector, rpcUrl: vm.envString("MAINNET_RPC_URL"), detailedBlocks: false, - useTraceFilter: true, forkByTxHash: true }) ``` @@ -144,6 +100,27 @@ This is particularly useful for: See the [Balancer V2 Rate Manipulation Exploit](/assertions-book/previous-hacks/balancer-v2-stable-rate-exploit) for a real-world example of using backtesting to verify an assertion catches a historical exploit transaction. +### Testing a Single Transaction by Hash + +For debugging false positives or validating against a specific transaction, use `executeBacktestForTransaction`: + +```solidity +bytes32 txHash = 0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef; + +BacktestingTypes.BacktestingResults memory results = executeBacktestForTransaction( + txHash, + TARGET_CONTRACT, + type(MyAssertion).creationCode, + MyAssertion.assertionInvariant.selector, + vm.envString("MAINNET_RPC_URL") +); +``` + +This is particularly useful for: +- Debugging potential false positives reported during staging (see [Testing Strategy](/credible/testing-strategy)) +- Quick validation of assertion behavior on a specific transaction +- Investigating incidents without needing to know the block number + ## Understanding Results When backtesting completes, transactions are categorized into different result types to help you quickly identify issues: @@ -154,18 +131,22 @@ When backtesting completes, transactions are categorized into different result t - Transaction replayed and assertion validated without errors - **Recommended Action:** None - everything is working as expected -**NEEDS_REVIEW** - Transaction requires review -- Transaction either called a function not monitored by your assertion, or failed to replay in the test environment +**SKIP** - Assertion not triggered +- Transaction called a function not monitored by your assertion (function selector didn't match) +- This is normal behavior - your assertion only triggers on specific function calls +- **Recommended Action:** None required, unless you expected this transaction to trigger your assertion +- **Note:** These are NOT assertion violations - they indicate transactions that aren't relevant to your assertion + +**REPLAY_FAIL** - Transaction replay failed +- Transaction failed to replay before the assertion could execute - Common causes: - - **Function mismatch:** Assertion monitors `batch()` but transaction called `call()` - - **State dependencies:** Transaction depends on earlier transactions in the same block - - **Context requirements:** Transaction requires specific block state that isn't present when forking at block level -- **Recommended Action:** - - If many transactions show this status, enable `forkByTxHash: true` to fork at exact transaction state - - Review to ensure your assertion is triggering on the correct function -- **Note:** These are NOT assertion violations - they're informational and indicate transactions that either aren't relevant to your assertion or fail before the assertion is executed due to wrong configuration of the backtest + - **State dependencies:** Transaction depends on specific state that isn't present + - **Context requirements:** Transaction requires specific block context + - **Insufficient funds:** Sender balance changed between original execution and replay +- **Recommended Action:** Check the error message for details; this usually indicates an environmental issue rather than an assertion problem +- **Note:** These are NOT assertion violations -**ASSERTION_FAIL** - Assertion reverted +**FAIL** - Assertion reverted - Your assertion reverted when validating this transaction - **Most Common Causes:** - **False positive:** Your assertion logic incorrectly flags legitimate protocol behavior (most common) @@ -178,7 +159,7 @@ When backtesting completes, transactions are categorized into different result t - Check if the assertion is running out of gas and needs optimization - Verify the transaction on a block explorer to understand what it does -**UNKNOWN_ERROR** - Unexpected failure +**ERROR** - Unexpected failure - An error occurred that doesn't fit other categories - May indicate RPC issues, assertion bugs, or unexpected contract behavior - **Recommended Action:** Check the error message and retry; if persistent, [file a bug report](https://github.com/phylaxsystems/credible-std/issues). @@ -195,13 +176,14 @@ Block Range: 23697580 - 23697590 Total Transactions: 15 Processed Transactions: 15 Successful Validations: 10 -Failed Validations: 5 +Skipped Transactions: 3 +Failed Validations: 2 === ERROR BREAKDOWN === Protocol Violations (Assertion Failures): 1 -Needs Review (Selector Mismatch or Prestate Issues): 4 +Replay Failures (Tx reverted before assertion): 1 -Success Rate: 66% +Success Rate: 83% ================================ ``` @@ -242,12 +224,11 @@ See [CI/CD Integration](/credible/ci-cd-integration#foundry-profile-configuratio ## Best Practices 1. **Start small** - Test with 10-20 blocks first to verify your setup -2. **Use trace_filter** - Set `useTraceFilter: true` if your RPC provider supports it -3. **Keep forkByTxHash false** - Enable if assertions are reverting and might be affected by txs earlier in a block -4. **Use paid RPC providers** - For block ranges over 1,000 -5. **Check assertion failures** - Ensure `results.assertionFailures == 0` -6. **Run before deployment** - Catch false positives on real transactions -7. **Run manually** - Exclude from CI/CD to avoid long test runs +2. **Use paid RPC providers** - For block ranges over 1,000 +3. **Check assertion failures** - Ensure `results.assertionFailures == 0` +4. **Run before deployment** - Catch false positives on real transactions +5. **Run manually** - Exclude from CI/CD to avoid long test runs +6. **Debug with single transactions** - Use `executeBacktestForTransaction` to investigate specific failures **Learn More:** - [Backtesting Reference](/credible/backtesting-reference) - Complete configuration details diff --git a/credible/cheatcodes-reference.mdx b/credible/cheatcodes-reference.mdx index b2bcabb..0c21871 100644 --- a/credible/cheatcodes-reference.mdx +++ b/credible/cheatcodes-reference.mdx @@ -407,6 +407,9 @@ function assertionExample() external { ## Next Steps + + Autogenerated API documentation for credible-std + Learn how to write assertions using cheatcodes diff --git a/credible/cli-reference.mdx b/credible/cli-reference.mdx index abb8855..ddba9a3 100644 --- a/credible/cli-reference.mdx +++ b/credible/cli-reference.mdx @@ -36,7 +36,7 @@ Commands: help Print this message or the help of the given subcommand(s) Options: - -u, --auth-url Base URL for authentication service [env: PCL_AUTH_URL=] [default: https://dapp.internal.phylax.systems] + -u, --auth-url Base URL for authentication service [env: PCL_AUTH_URL=] [default: https://app.phylax.systems] -h, --help Print help ``` @@ -110,7 +110,7 @@ Arguments: Options: -u, --da-url URL of the assertion-DA server [env: PCL_DA_URL=] - [default: https://da.internal.phylax.systems] + [default: https://da.phylax.systems] --root Root directory of the project -h, --help Print help (see more with '--help') ``` @@ -146,7 +146,7 @@ Arguments: [CONSTRUCTOR_ARGS]... Constructor arguments for the assertion Options: - -u, --api-url Base URL for the Credible Layer dApp API [env: PCL_API_URL=] [default: https://dapp.internal.phylax.systems/api/v1] + -u, --api-url Base URL for the Credible Layer dApp API [env: PCL_API_URL=] [default: https://app.phylax.systems/api/v1] -p, --project-name Optional project name to skip interactive selection -a, --assertion Assertion in format 'Name(arg1,arg2)'. Use multiple -a flags for multiple assertions. -h, --help Print help (see more with '--help') @@ -205,7 +205,7 @@ Delete the current configuration file. ### `PCL_AUTH_URL` -Base URL for authentication service. Default: `https://dapp.internal.phylax.systems` +Base URL for authentication service. Default: `https://app.phylax.systems` ```bash export PCL_AUTH_URL=https://custom-auth-url.com @@ -214,7 +214,7 @@ pcl auth login ### `PCL_DA_URL` -URL of the [Assertion DA](/credible/glossary#assertion-da-data-availability) server. Default: `https://da.internal.phylax.systems` +URL of the [Assertion DA](/credible/glossary#assertion-da-data-availability) server. Default: `https://da.phylax.systems` ```bash export PCL_DA_URL=https://custom-da-url.com @@ -223,7 +223,7 @@ pcl store MyAssertion ### `PCL_API_URL` -Base URL for the [Credible Layer dApp](/credible/glossary#credible-dapp) API. Default: `https://dapp.internal.phylax.systems/api/v1` +Base URL for the [Credible Layer dApp](/credible/glossary#credible-dapp) API. Default: `https://app.phylax.systems/api/v1` ```bash export PCL_API_URL=https://custom-api-url.com/api/v1 diff --git a/credible/credible-std-overview.mdx b/credible/credible-std-overview.mdx index 4c75271..90ef42f 100644 --- a/credible/credible-std-overview.mdx +++ b/credible/credible-std-overview.mdx @@ -5,7 +5,7 @@ description: "Overview of the Phylax credible-std Library" `credible-std` is the standard library for implementing assertions in the Credible Layer. It provides the core contracts and interfaces needed to create and manage assertions for smart contract security monitoring. -You can find the repository on [GitHub](https://github.com/phylaxsystems/credible-std). +You can find the repository on [GitHub](https://github.com/phylaxsystems/credible-std) and the [full API reference](https://phylaxsystems.github.io/credible-std/index.html). ## Installation