Builder Rootcamp – Week 3: Testing Smart Contracts on Rootstock

Week 3 introduced a shift that felt inevitable after Week 2.
If Week 2 was about understanding the machinery that runs your contracts, Week 3 was about ensuring the logic running on that machinery behaves correctly.
Writing Solidity is productive.
Testing Solidity is disciplined.
On Rootstock, where contracts execute in a Bitcoin-merged-mined environment, correctness isn’t aspirational it’s structural.
Why Testing Suddenly Matters
One thing became clear early in the session:
Passing tests is not the goal.
Defining guarantees is.
The distinction between implementation and testing framed the week:
Implementation → The Solidity code
Specification → The expected behavior
Test Runner → The tool executing and reporting results
The loop is simple:
Run tests → Analyze failures → Fix implementation or refine specification → Repeat
What stood out to me here was realizing that tests aren’t verifying logic after the fact they are formalizing what the system promises. That changes how you write both functions and assertions.
Smart Contracts as State Machines
Concept | What It Means in a Smart Contract | Why It Matters for Testing |
|---|---|---|
State | Current contract data (balances, roles, mappings, flags) | Defines the system’s current truth |
Transition | A function call that changes state | Must behave deterministically |
Valid Transition | Expected state change | Should succeed |
Invalid Transition | Unauthorized or incorrect state change | Must revert |
Invariant | Rule that must always hold (e.g., total supply constant) | Must never break |
This was one of the moments that genuinely clicked for me.
Instead of thinking “Does this function work?”, I started thinking, “What transitions should never be allowed?”That question alone improves how you design contracts.
Types of Testing
The module organized testing into layers:
Testing Type | What It Tests | Example | Why It Matters |
|---|---|---|---|
Unit Testing | Individual functions in isolation | Does | Ensures basic logic correctness |
Integration Testing | Interaction between contracts or components | ERC20 approvals + transfers, cross-contract calls | Reveals edge cases when systems connect |
Acceptance Testing | End-to-end user-level behavior | Full user flow: deploy → approve → transfer | Validates real-world usage |
Code Coverage: Useful but Not Absolute
We explored coverage metrics:
% statements executed% branches tested% functions covered
Coverage tools help identify blind spots.
However, something that stood out is that high coverage does not automatically mean correctness. You can execute every line of code and still misunderstand its economic or logical implications.
Coverage tells you where you’ve looked.
It does not tell you whether you understood what you saw.
Black Box vs White Box Testing
Another distinction was:
Black Box Testing → Testing external behavior
White Box Testing → Testing internal logic paths
What I found particularly interesting here is how often we default to black box testing in dApp development. White box testing forces you to confront your internal logic more rigorously.
Hardhat vs Foundry
Tooling also shapes thinking.
Dimension | Hardhat | Foundry |
|---|---|---|
Testing Language | JavaScript / TypeScript | Pure Solidity |
Testing Style | Application-level | Protocol-level |
Assertion System | Mocha + Chai | Solidity-based assertions |
Fuzz Testing | Plugin-based | Built-in native fuzzing |
Gas Insights | External plugins | Built-in gas reporting |
Abstraction Level | Higher abstraction | Closer to EVM execution |
Fuzz Testing & Invariants
Fuzz testing introduced property-based testing.
Instead of writing:
“Test transfer of 100 tokens”
You define invariants:
Total supply remains constant
Balances never become negative
Invalid operations revert
Then randomized inputs are generated automatically.
This was one of the most impactful parts of the week for me. Fuzzing challenges assumptions you didn’t even realize you were making. It introduces a layer of humility into development.
The Correctness Quadrants
A key framework discussed was the test correctness matrix:
Correct implementation + correct tests → Ideal
Incorrect implementation + correct tests → Caught early
Correct implementation + incorrect tests → False failures
Incorrect implementation + incorrect tests → False confidence
The last case is dangerous. Because passing tests can create a false sense of security.
That idea reinforced something important:
Testing is only as strong as the reasoning behind it.
Why This Matters on Rootstock
Rootstock combines:
Bitcoin’s merged mining security
Deterministic EVM execution
Irreversible state transitions
Once deployed, contract behavior is not easily changed. Testing becomes part of respecting that permanence.
From a learner’s perspective, Week 3 elevated testing from quality control to architectural responsibility.
What I’m Taking Forward
Three shifts stood out this week:
Tests define guarantees, not just outcomes.
Integration assumptions must be explicit.
Invariants should be designed before complexity is introduced.
Testing on Rootstock is not about paranoia. It is about discipline.
What’s Next
After understanding infrastructure (Week 2) and testing discipline (Week 3), the focus now moves deeper into smart contract patterns and security considerations.
The progression is becoming clear:
Understand the system.
Write the logic.
Prove the guarantees.
Connect with me on this journey:
GitHub: tusharshah21
X (Twitter): Sharing Rootstock + Web3 learnings in real time
Question for Fellow Rootcampers
Did writing tests expose any assumption in your contract logic that you hadn’t noticed before?
Ready to start building on Rootstock? Here’s how you can get involved today:
📖 Explore the Docs – Get started with Rootstock development.
🏆 Check out the Hacker Hub – Learn about past hackathons and upcoming opportunities.
💰 Contribute and Earn with Hacktivator – Build or create content and earn up to $1,000 per contribution.
💬 Join the Community – Connect with other builders on Discord.





