5/5
## Setting Up Your Panagram Smart Contract Test Environment This lesson guides you through the initial setup of a test file for the `Panagram` smart contract using the Foundry testing framework. We'll cover creating the test file, importing necessary dependencies, defining the test contract structure, and crucially, implementing the `setUp` function. This function will deploy our contracts and initialize the game state, with a special focus on preparing the "answer" hash to be compatible with the Zero-Knowledge (ZK) SNARK circuit. ### Test File Creation and Initial Boilerplate First, we need to create our test file. Within your project's `test` directory, create a new file named `Panagram.t.sol`. Every Solidity file should start with an SPDX license identifier and a pragma defining the compiler version. Add the following standard boilerplate to your `Panagram.t.sol`: ```solidity // SPDX-License-Identifier: MIT pragma solidity ^0.8.24; ``` ### Importing Essential Dependencies To write effective tests with Foundry and interact with our smart contracts, we need to import several components. 1. **Foundry Test Utilities:** Foundry provides a standard library (`forge-std`) with helpful utilities for testing. We'll import `Test` (the base contract our test contract will inherit from) and `console` (for logging values during tests). ```solidity import {Test, console} from "forge-std/Test.sol"; ``` 2. **Contract Under Test (`Panagram`):** Naturally, we need to import the `Panagram` smart contract that we intend to test. ```solidity import {Panagram} from "../src/Panagram.sol"; ``` 3. **Verifier Contract (`HonkVerifier`):** The `Panagram` contract likely depends on a verifier contract to validate ZK proofs. In this case, it's the `HonkVerifier`. It's important to import the concrete `HonkVerifier` contract, not just its interface (e.g., `IVerifier`), because we will need to deploy an actual instance of it in our `setUp` function. ```solidity import {HonkVerifier} from "../src/Verifier.sol"; ``` ### Defining the Test Contract Structure Now, let's define the structure for our test contract. We'll create a new contract, `PanagramTest`, which inherits from Foundry's `Test` contract. This inheritance provides access to various testing assertions and utilities. ```solidity contract PanagramTest is Test { // Test contract state variables and functions will go here } ``` ### Declaring State Variables for the Test Contract Inside our `PanagramTest` contract, we'll declare state variables to hold instances of our deployed contracts and other important values needed across multiple tests. * **Contract Instances:** We need variables to store the deployed instances of `HonkVerifier` and `Panagram`. Making them `public` allows easy inspection if needed. * **`FIELD_MODULUS`:** This constant is critical for ensuring compatibility with the ZK-SNARK circuit. The Noir circuit operates over a finite field, and numerical inputs must respect this field's modulus. This value is typically defined in the verifier contract (e.g., as `MODULUS` in `Verifier.sol`) or a shared constants file. For this lesson, we'll copy it directly. * **`ANSWER`:** A `bytes32` state variable to store the game's answer, processed to be ZK-circuit compatible. Here's how these state variables are declared within `PanagramTest`: ```solidity contract PanagramTest is Test { HonkVerifier public verifier; Panagram public panagram; // The field modulus used in the ZK-SNARKs (copied from Verifier.sol or a shared constants file) uint256 constant FIELD_MODULUS = 21888242871839275222246405745257275088548364400416034343698204186575808495617; bytes32 public ANSWER; // Will store the processed answer for the round // setUp function and tests will go here } ``` ### Implementing the `setUp` Function The `setUp()` function is a special function in Foundry tests. It is executed before each individual test function (those prefixed with `test...`). This is the ideal place to deploy contracts and set up any initial state required by your tests. Our `setUp` function will perform the following steps: 1. Deploy the `HonkVerifier` contract. 2. Deploy the `Panagram` contract, providing the address of the deployed verifier to its constructor. 3. Create and process the "answer" for the Panagram game, ensuring it's ZK-compatible. 4. Start a new round in the `Panagram` contract using this processed answer. Let's implement these steps: 1. **Deploying the Verifier:** We instantiate `HonkVerifier` and assign it to our state variable. ```solidity // Inside setUp() verifier = new HonkVerifier(); ``` 2. **Deploying the Panagram Contract:** Next, we deploy `Panagram`, passing the address of the `verifier` instance we just created. ```solidity // Inside setUp() panagram = new Panagram(address(verifier)); // Pass the deployed verifier instance's address ``` 3. **Creating the ZK-Compatible Answer (A Key Concept):** This is a critical step when integrating Solidity contracts with ZK-SNARK circuits, such as those written in Noir. * **Noir Circuit Expectation:** The Noir circuit for Panagram (likely in `src/main.nr`) expects the `answer_hash` as a `Field` element. * **Solidity Hashing:** `keccak256` in Solidity produces a 256-bit hash (a `bytes32` value). * **Field Modulus Constraint:** A `Field` element in Noir (specifically for the BN254 curve, commonly used with Aztec tooling) is constrained by a prime field modulus. This `FIELD_MODULUS` (which we defined as `21888242871839275222246405745257275088548364400416034343698204186575808495617`) is smaller than the maximum value representable by a 256-bit integer. * **The Solution: Modular Arithmetic:** To make the Solidity hash compatible, we must reduce it modulo the `FIELD_MODULUS`. This ensures the hash value fits within the range expected by the Noir circuit. The process is: 1. Choose a secret answer string (e.g., "triangles"). 2. Hash this string using `keccak256`. It's best practice to hash the byte representation: `keccak256(bytes("triangles"))`. 3. Convert the resulting `bytes32` hash to a `uint256`. 4. Apply the modulo operation: `uint256_hash % FIELD_MODULUS`. 5. Convert this result back to `bytes32` and store it in our `ANSWER` state variable. ```solidity // Inside setUp() - after deploying panagram // Create the answer (string "triangles" is used as an example) bytes32 hashedAnswer = keccak256(bytes("triangles")); // Hash the secret ANSWER = bytes32(uint256(hashedAnswer) % FIELD_MODULUS); // Reduce modulo the field prime ``` 4. **Starting the New Round:** Finally, we call the `newRound` function on our deployed `panagram` contract, passing the ZK-compatible `ANSWER`. ```solidity // Inside setUp() - after creating ANSWER panagram.newRound(ANSWER); ``` **Completed `setUp` Function:** Putting it all together, the `setUp` function looks like this: ```solidity function setUp() public { // deploy the verifier verifier = new HonkVerifier(); // deploy the panagram contract panagram = new Panagram(address(verifier)); // create the answer // Example answer: "triangles" ANSWER = bytes32(uint256(keccak256(bytes("triangles"))) % FIELD_MODULUS); // start the round panagram.newRound(ANSWER); } ``` ### Next Steps: Outlining Test Cases While we won't implement them in this specific lesson segment, it's good practice to outline the tests we intend to write. This helps structure our testing efforts. For the `Panagram` contract, we might consider tests such as: * `// 1. Test someone receives NFT 0 if they guess correctly first` * `// 2. Test someone receives NFT 1 if they guess correctly second` * `// 3. Test we can start a new round` (e.g., after the current round's time has elapsed or conditions are met) ### Compilation and Initial Verification With the `setUp` function implemented, we can try to compile our test file. Open your terminal in the project root and run: ```bash forge test ``` You might encounter some initial typos or errors, which is a normal part of development. For example: * Forgetting to declare `ANSWER` as a state variable. * Typing `keccak` instead of `keccak256`. * Misspelling contract or variable names (e.g., `panargam` instead of `panagram`). After correcting any such errors, `forge test` should compile successfully. Since we haven't written any actual test functions (functions prefixed with `test`), Foundry will report "No tests found in project!" This is expected at this stage and indicates our `setUp` logic is syntactically correct. ### Key Concepts Recap This setup process has touched upon several important concepts: * **Foundry Testing:** Utilizing `.t.sol` files, inheriting from the `Test` contract, and leveraging the `setUp()` function for pre-test state configuration. * **ZK-SNARK Circuit Compatibility:** The absolute necessity of ensuring data passed from Solidity to a Noir (or other ZK language) circuit adheres to the circuit's data type expectations and constraints. This is especially true for numerical values like hashes, which must often conform to `Field` element representations. * **Modular Arithmetic for Hashes:** The technique of using the modulo operator (`%`) with the `FIELD_MODULUS` to bring a standard cryptographic hash (like `keccak256`) within the valid range for a `Field` element in a ZK circuit. * **Field Modulus (`FIELD_MODULUS`):** This large prime number defines the finite field over which the ZK proof system's arithmetic operates. All calculations within the ZK circuit are performed modulo this prime. * **Solidity Hashing Best Practices:** Using `keccak256(bytes("string"))` or `keccak256(abi.encodePacked("string"))` for hashing strings, rather than relying on potential implicit conversions from string literals. This lesson has successfully established the foundational test setup for the `Panagram` smart contract. The critical step of correctly preparing the answer hash for ZK-SNARK compatibility ensures that our future tests will accurately reflect how the `Panagram` contract interacts with its associated Noir circuit. With this groundwork laid, we are now ready to write specific unit tests.
A crucial blueprint for Setting Up Your Panagram Smart Contract Test Environment - Learn to construct the initial test environment for the `Panagram` smart contract using Foundry, from creating the test file and importing dependencies to defining the test contract structure. This lesson details the implementation of the `setUp` function, with a core focus on deploying contracts and preparing the game's answer hash for ZK-SNARK circuit compatibility.
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