5/5
## Running JavaScript Scripts in Foundry Tests for Off-Chain Data Generation This lesson details how to execute JavaScript or TypeScript scripts from within your Foundry smart contract tests. This technique is invaluable when your Solidity test functions require data, such as cryptographic proofs generated by off-chain systems (e.g., zero-knowledge proof frameworks like Noir), that cannot be easily produced within Solidity itself. ### The Challenge: Requiring External Data in Solidity Tests Consider a scenario where you're testing a Solidity smart contract, `Panagram.sol`. Your test suite, specifically `Panagram.t.sol`, includes a test for a function that requires a `bytes memory proof` argument. Let's look at the initial structure of a relevant test function, `testCorrectGuessPasses`, within `Panagram.t.sol`: ```solidity // contracts/test/Panagram.t.sol // ... (imports and contract definition) contract PanagramTest is Test { HonkVerifier public verifier; Panagram public panagram; // ... address user = makeAddr("user"); // Define a user // ... (setUp function) // The goal is to test this scenario: // // 1. Test someone recieves NFT 0 if they guess correctly first function testCorrectGuessPasses() public { // vm.prank to simulate the call from the 'user' vm.prank(user); panagram.makeGuess(proof); // 'proof' is the missing piece // ... (assertions would follow) } } ``` The primary issue here is that the `proof` variable is undefined. This proof, essential for the `panagram.makeGuess()` function, is expected to be generated by an external process, likely a JavaScript script interacting with a ZK-proof system. A previous, commented-out approach might have hinted at this: ```solidity // // Create a valid proof for the answer "triangles" // bytes memory validProof = verifier.createProof(ANSWER); // This is likely what the JS script will replace ``` This comment suggests that proof generation is complex and not suitable for direct implementation in Solidity. ### The Solution: Foundry's Foreign Function Interface (FFI) Foundry provides a powerful cheatcode, the Foreign Function Interface (FFI), to call external scripts directly from your Solidity tests. This allows you to generate the necessary `proof` off-chain and then feed it into your test. According to the official Foundry documentation (accessible at `getfoundry.sh/reference/cheatcodes/ffi/`), the FFI cheatcode is defined as: ```solidity function ffi(string[] calldata args) external returns (bytes memory output); ``` Key characteristics of FFI include: * It executes an arbitrary command specified in the `args` array, provided FFI is enabled in your Foundry configuration. * The external script is executed from the **top-level directory of your project**, not from within the `contracts/test` directory or where the Solidity test file resides. * The `output` returned by the script is typically a hex-encoded byte string, which can then be decoded and used within your Solidity test. ### Setting Up the JavaScript/TypeScript Script Environment To generate the proof, we'll create a dedicated script. 1. **Create a Script Directory:** Inside your project, create a new folder, for example, `js-scripts`. While you might initially think of placing it within `contracts`, it's often better at the project root or a dedicated top-level scripts folder, especially when considering `npm` package management. 2. **Create the Script File:** Within the `js-scripts` folder, create your script file. We'll use TypeScript for its type safety and improved developer experience, naming the file `generateProof.ts`. The purpose of this script will be to produce the `bytes memory proof` required by `panagram.makeGuess()`. ### Installing Dependencies for the Proof Generation Script Generating proofs, especially those related to Noir and Barretenberg, requires specific JavaScript libraries. **Crucial: Version Matching with CLI Tools** A critical aspect of working with `noir-js` (the JavaScript library for Noir) and `bb.js` (the JavaScript/Wasm binding for Barretenberg) is ensuring their versions **precisely match** the versions of the command-line interface (CLI) tools (`nargo` for Noir, `bb` for Barretenberg) used to compile your Noir circuits and generate the Solidity verifier contract. Mismatches can lead to subtle and hard-to-debug compatibility issues. Before installing the libraries, verify your CLI tool versions: * **Barretenberg (`bb`) CLI version:** Open your terminal and run: ```bash bb --version ``` In the context of this lesson, the example output was `v0.82.2`. * **Noir (`nargo`) CLI version:** Run: ```bash nargo --version ``` The example output was `nargo version = 1.0.0-beta.3`, `noirc version = 1.0.0-beta.3`. **Installing Node.js Packages** 1. **Navigate to Project Root:** Ensure your terminal is in the project's root directory before running `npm install` commands. This is standard practice to ensure `package.json`, `package-lock.json`, and the `node_modules` folder are correctly placed at the project level. If you were inside the `contracts` directory, you would navigate out using `cd ../`. 2. **Install Version-Specific Libraries:** Install `noir-js` and `bb.js`, pinning them to the versions identified above: ```bash npm install @noir-lang/noir_js@1.0.0-beta.3 @aztec/bb.js@0.82.2 ``` *(Note: Pay close attention to typos when entering commands; for instance, "intall" should be "install".)* 3. **Install Other Useful Libraries:** The `ethers` library is also often useful for various Ethereum-related utilities, though its direct role in proof generation might vary depending on the script's exact needs. ```bash npm install ethers ``` ### Key Concepts and Considerations * **Foundry Testing & Cheatcodes:** Foundry's testing framework, enhanced by cheatcodes like `vm.prank`, `makeAddr`, and `ffi`, allows for powerful and flexible smart contract testing. * **Off-Chain Computation:** Proof generation is computationally intensive and is best performed off-chain. The JavaScript script serves this purpose. * **Zero-Knowledge Proofs (Implied Context):** The mention of Noir, Barretenberg, and "proof" generation strongly implies the use of Zero-Knowledge proofs, where the `Panagram` contract likely verifies a user's guess without revealing the guess itself, using the provided proof. * **Noir & Barretenberg:** * **Noir:** A domain-specific language for writing ZK circuits. `noir-js` facilitates interaction with these circuits from JavaScript. * **Barretenberg (`bb` / `bb.js`):** The underlying cryptographic backend used by Noir for proof generation and verification. * **Dependency Management (`npm`):** The Node Package Manager (`npm`) is essential for installing and managing the JavaScript libraries your script depends on. * **Version Pinning:** Re-emphasizing the importance: strict version alignment between JS libraries (`noir-js`, `bb.js`) and their corresponding CLI tools (`nargo`, `bb`) is paramount for compatibility. * **FFI Script Execution Path:** Remember that scripts called via `ffi` are executed from your project's root directory, which influences how you specify file paths within the script or its arguments. * **TypeScript Benefits:** Using TypeScript (`.ts`) offers advantages like static typing, leading to more robust code and a better development experience compared to plain JavaScript (`.js`). ### Summary and Next Steps At this point, we have: 1. Identified the need for an externally generated `proof` within a Foundry Solidity test. 2. Introduced Foundry's FFI cheatcode as the mechanism to integrate external script execution. 3. Set up the basic structure for our external script (`js-scripts/generateProof.ts`). 4. Installed the necessary JavaScript dependencies (`@noir-lang/noir_js`, `@aztec/bb.js`, `ethers`), critically ensuring their versions align with the project's Noir and Barretenberg CLI tools. The subsequent steps involve writing the TypeScript code within `generateProof.ts` to actually generate the required cryptographic proof and then modifying the `Panagram.t.sol` test file to use the `ffi` cheatcode to execute this script, capture its output, and use the returned proof in the `panagram.makeGuess()` function call.
A practical walkthrough of Running JavaScript Scripts in Foundry Tests for Off-Chain Data Generation - Learn to integrate off-chain data generation into your Foundry smart contract tests by calling JavaScript/TypeScript scripts using the FFI cheatcode. This lesson covers setting up your script environment, managing dependencies like `noir-js` and `bb.js`, and the vital step of version matching with CLI tools.
Previous lesson
Previous
Next lesson
Next
Give us feedback
Course Overview
About the course
Noir syntax
Create a witness, a proof, and Solidity verifier contracts
Use the Poseidon commitment scheme
Create ZK circuits and build a full ZK protocol
ZK Merkle trees and hashing in Noir
Verify signatures without revealing the signer
Build the backend for a full-stack ZK application with noir.js and bb.js
How to create proofs and verify them in a front-end
Last updated on June 12, 2025
Duration: 6min
Duration: 1h 11min
Duration: 2h 12min
Duration: 3h 19min
Course Overview
About the course
Noir syntax
Create a witness, a proof, and Solidity verifier contracts
Use the Poseidon commitment scheme
Create ZK circuits and build a full ZK protocol
ZK Merkle trees and hashing in Noir
Verify signatures without revealing the signer
Build the backend for a full-stack ZK application with noir.js and bb.js
How to create proofs and verify them in a front-end
Last updated on June 12, 2025