Seismic Book
  • Welcome
  • introduction
    • Why
    • What
    • How
  • onboarding
    • Installation
    • Quickstart
    • Tutorial
      • Setting Up Your Walnut App Project
        • Verify devtool installation
        • Create project structure and monorepo workspace
        • Initialize the contracts subdirectory
        • Initialize the CLI subdirectory
      • Writing, testing and deploying the contract
        • Chapter 1: Making the Kernel
        • Chapter 2: Making the Shell and revealing the Kernel
        • Chapter 3: Reset Mechanism, Rounds, and a more conditional Kernel Reveal
        • Chapter 4: Testing your Walnut contract
        • Deploying your contract
      • Interacting with the contract via a CLI
        • Quick primer: seismic-viem
        • Chapter 1: Defining the constants and utilities
        • Chapter 2: Writing the core app
        • Chapter 3: Bringing it all together
      • Understanding the Walnut contract
  • core
    • Basics
      • suint / sint
      • saddress
      • sbool
    • Collections
    • Clients
  • Appendix
    • Devnet
Powered by GitBook

Contact us

  • Telegram
  • Twitter
  • Email
On this page
  • Getting Started
  • Writing Test Cases
Export as PDF
  1. onboarding
  2. Tutorial
  3. Writing, testing and deploying the contract

Chapter 4: Testing your Walnut contract

In this chapter, you’ll write tests to verify that the Walnut contract behaves as expected under various scenarios. Testing ensures the functionality, fairness, and access control mechanisms of your contract work seamlessly, particularly in multi-round gameplay. Estimated Time: ~15 minutes.

Getting Started

Navigate to the test folder in your Walnut App and open the Walnut.t.sol file located at:

packages/contracts/test/Walnut.t.sol

This file is where you’ll write all the test cases for the Walnut contract. Start with the following base code:

// SPDX-License-Identifier: MIT License
pragma solidity ^0.8.13;

import {Test, console} from "forge-std/Test.sol";
import {Walnut} from "../src/Walnut.sol";

contract WalnutTest is Test {
    Walnut public walnut;

    function setUp() public {
        // Initialize a Walnut with shell strength = 2 and kernel = 0
        walnut = new Walnut(2, suint256(0));
    }
}

The setUp() function initializes the Walnut contract for use in all test cases.

Writing Test Cases

Start off with testing the basic functionalities, hit , shake , look and reset

Core functionalities

  1. Basic hit functionality

Ensures the Walnut’s shell can be cracked by shellStrength number of hits.

function test_Hit() public {
    walnut.hit(); // Decrease shell strength by 1
    walnut.hit(); // Fully crack the shell
    assertEq(walnut.look(), 0); // Kernel should still be 0 since no shakes
}
  1. Basic shake functionality

Validates that shaking the Walnut increments the kernel value.

function test_Shake() public {
    walnut.shake(suint256(10)); // Shake the Walnut, increasing the kernel
    walnut.hit(); // Decrease shell strength by 1
    walnut.hit(); // Fully crack the shell
    assertEq(walnut.look(), 10); // Kernel should be 10 after 10 shakes
}
  1. Reset functionality

function test_Reset() public {
    walnut.hit(); // Decrease shell strength by 1
    walnut.shake(suint256(2)); // Shake the Walnut
    walnut.hit(); // Fully crack the shell
    walnut.reset(); // Reset the Walnut

    assertEq(walnut.getShellStrength(), 2); // Shell strength should reset to initial value
    walnut.hit(); // Start hitting again
    walnut.shake(suint256(5)); // Shake the Walnut again
    walnut.hit(); // Fully crack the shell again
    assertEq(walnut.look(), 5); // Kernel should reflect the shakes in the new round
}

Now, test for the restrictive/conditional nature of these basic functionalities.

Restricting Actions

  1. Preventing hit when shell is cracked

Ensures that hitting a cracked shell is not allowed.

function test_CannotHitWhenCracked() public {
    walnut.hit(); // Decrease shell strength by 1
    walnut.hit(); // Fully crack the shell
    vm.expectRevert("SHELL_ALREADY_CRACKED"); // Expect revert when hitting an already cracked shell
    walnut.hit();
}
  1. Preventing shake when shell is cracked

Ensures that shaking the Walnut after the shell is cracked is not allowed.

function test_CannotShakeWhenCracked() public {
    walnut.hit(); // Decrease shell strength by 1
    walnut.shake(suint256(1)); // Shake the Walnut
    walnut.hit(); // Fully crack the shell
    vm.expectRevert("SHELL_ALREADY_CRACKED"); // Expect revert when shaking an already cracked shell
    walnut.shake(suint256(1));
}
  1. Preventing look when shell is intact

Ensures that the kernel cannot be revealed unless the shell is fully cracked.

function test_CannotLookWhenIntact() public {
    walnut.hit(); // Partially crack the shell
    walnut.shake(suint256(1)); // Shake the Walnut
    vm.expectRevert("SHELL_INTACT"); // Expect revert when trying to look at the kernel with the shell intact
    walnut.look();
}
  1. Preventing reset when shell is intact

Validates that the Walnut cannot be reset unless the shell is fully cracked.

function test_CannotResetWhenIntact() public {
    walnut.hit(); // Partially crack the shell
    walnut.shake(suint256(1)); // Shake the Walnut
    vm.expectRevert("SHELL_INTACT"); // Expect revert when trying to reset without cracking the shell
    walnut.reset();
}

Now, test for more complex scenarios.

Complex scenarios

  1. Sequence of Multiple Actions

Ensures that the Walnut behaves correctly under a sequence of hits and shakes.

function test_ManyActions() public {
    uint256 shakes = 0;
    for (uint256 i = 0; i < 50; i++) {
        if (walnut.getShellStrength() > 0) {
            if (i % 25 == 0) {
                walnut.hit(); // Hit the shell every 25 iterations
            } else {
                uint256 numShakes = (i % 3) + 1; // Random shakes between 1 and 3
                walnut.shake(suint256(numShakes));
                shakes += numShakes;
            }
        }
    }
    assertEq(walnut.look(), shakes); // Kernel should match the total number of shakes
}
  1. Prevent Non-Contributors From Using look()

Ensures that only contributors in the current round can call look() .

function test_RevertWhen_NonContributorTriesToLook() public {
    address nonContributor = address(0xabcd);

    walnut.hit(); // Decrease shell strength by 1
    walnut.shake(suint256(3)); // Shake the Walnut
    walnut.hit(); // Fully crack the shell

    vm.prank(nonContributor); // Impersonate a non-contributor
    vm.expectRevert("NOT_A_CONTRIBUTOR"); // Expect revert when non-contributor calls `look()`
    walnut.look();
}
  1. Contributor Tracking Across Rounds

Validates that contributions are tracked independently for each round. The test has one contributor hit both times and crack the shell in the first round, and a different contributor hit and crack the shell in the second round. We check for the fact the second round contributor cannot see the kernel after the first round and the first round contributor cannot see the kernel after the second.

function test_ContributorInRound2() public {
    address contributorRound2 = address(0xabcd); // Contributor for round 2

    // Round 1: Cracked by address(this)
    walnut.hit(); // Hit 1
    walnut.hit(); // Hit 2
    assertEq(walnut.look(), 0); // Confirm kernel value

    walnut.reset(); // Start Round 2

    // Round 2: ContributorRound2 cracks the Walnut
    vm.prank(contributorRound2);
    walnut.hit();

    vm.prank(contributorRound2);
    walnut.shake(suint256(5)); // Shake kernel 5 times

    vm.prank(contributorRound2);
    walnut.hit();

    vm.prank(contributorRound2);
    assertEq(walnut.look(), 5); // Kernel value is 5 for contributorRound2

    vm.expectRevert("NOT_A_CONTRIBUTOR"); // address(this) cannot look in round 2
    walnut.look();
}

Test out the file by running the following inside the packages/contracts directory:

sforge build
sforge test

The contract has been tested, time to deploy it!

PreviousChapter 3: Reset Mechanism, Rounds, and a more conditional Kernel RevealNextDeploying your contract

Last updated 4 months ago

You can find the entire test file .

here