Skip to content

huichain/smart-contract-security-lab

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

27 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Smart Contract Security Lab

A Foundry-based lab to learn smart contract security by reproducing real vulnerabilities, writing PoCs, and proposing fixes.

This repo is built day by day, not all at once.
Each day adds one small, working piece: a vulnerable contract, an attacker, a fix, or a report.

Status

  • Reentrancy module complete — vulnerable contract, attacker, fix, 3 passing tests, audit-style writeup
  • Access Control module complete — vulnerable + fixed contracts, 5 passing tests, audit-style writeup
  • Signature Replay module complete — vulnerable airdrop, fixed implementation, 3 passing tests, audit-style writeup
  • Oracle Manipulation module complete — vulnerable + fixed lending, TWAP oracle, 4 passing tests, audit-style writeup
  • Upgradeable Proxy module complete — initializer PoC + storage-layout collision PoC, fixed implementations, 9 passing tests, audit-style writeups

Reentrancy — Vulnerable Vault, Exploit PoC, Fix, and Writeup

  • src/reentrancy/VulnerableVault.sol A minimal ETH vault that sends ETH to the user before updating the user's balance, which makes it vulnerable to reentrancy.
  • src/reentrancy/ReentrancyAttacker.sol Attacker contract that re-enters withdraw from receive() to drain the vault.
  • src/reentrancy/FixedVault.sol Hardened vault using checks-effects-interactions and OpenZeppelin's ReentrancyGuard (defense in depth).
  • test/reentrancy/ReentrancyPoC.t.sol Foundry PoC test suite:
    • testExploit_DrainsVault — the attack drains a 10 ETH vault with 1 ETH of attacker capital
    • testFix_BlocksReentrancy — the same attacker against FixedVault reverts and victim funds remain safe
    • testFix_AllowsHonestWithdraw — sanity check that the fix does not break legitimate users
  • reports/01-reentrancy.md Audit-style writeup: severity, summary, root cause, PoC, recommendation, fixed implementation, and learnings.

Access Control — Vulnerable Treasury, Exploit PoC, Fix

  • src/access-control/VulnerableTreasury.sol A protocol treasury with an owner field, but withdraw and setOwner have no access checks — two independent bugs.
  • src/access-control/FixedTreasury.sol Hardened treasury using OpenZeppelin Ownable and onlyOwner on sensitive functions (no separate attacker contract needed; EOA can exploit the vulnerable version).
  • test/access-control/AccessControlPoC.t.sol Foundry PoC test suite:
    • testExploit_AnyoneCanDrainTreasury — any account can call withdraw and drain all ETH
    • testExploit_AnyoneCanBecomeOwner — any account can call setOwner and seize ownership
    • testFix_BlocksUnauthorizedWithdraw — attacker withdraw reverts with OwnableUnauthorizedAccount
    • testFix_BlocksUnauthorizedSetOwner — attacker setOwner reverts; owner unchanged
    • testFix_AllowsOwnerFunctions — legitimate owner can still withdraw and transfer ownership
  • reports/02-access-control.md Audit-style writeup: two findings (withdraw, setOwner), severity, PoC, recommendation, fixed implementation, and learnings.

Signature Replay — Vulnerable Airdrop, Replay PoC, Fix, and Writeup

  • src/signature-replay/VulnerableAirdrop.sol A deliberately vulnerable ETH airdrop that accepts an off-chain signature from a trusted signer, but the signed message only binds account and amount.
  • test/signature-replay/SignatureReplayPoC.t.sol Foundry PoC test suite:
    • testExploit_SameSignatureClaimsTwice — reuses the exact same signature twice and proves the claimant receives the airdrop twice
    • testFix_BlocksSignatureReplay — proves the fixed contract consumes the user's nonce and rejects the replayed signature
    • testFix_RejectsExpiredSignature — proves expired signatures cannot be used
  • src/signature-replay/FixedAirdrop.sol Fixed implementation: binds signatures to nonce, deadline, chain id, and address(this).
  • reports/03-signature-replay.md Audit-style writeup: severity, replay impact, root cause, PoC, recommendation, fixed implementation, and learnings.

Oracle Manipulation — AMM Spot Price PoC

  • src/oracle-manipulation/SimpleAMM.sol A deliberately simplified constant-product AMM whose spot price can be moved by changing reserves.
  • src/oracle-manipulation/VulnerableLending.sol A toy lending market that directly trusts the AMM spot price to calculate borrowing power.
  • src/oracle-manipulation/TWAPOracle.sol Records cumulative AMM prices so lending can consult a time-weighted average instead of spot.
  • src/oracle-manipulation/FixedLending.sol Hardened lending market that prices collateral with TWAPOracle.consult() instead of getSpotPrice().
  • test/oracle-manipulation/OracleManipulationPoC.t.sol Foundry PoC test suite:
    • testExploit_SpotPriceManipulationInflatesBorrowLimit — proves manipulating the AMM spot price inflates the borrow limit and drains pool liquidity
    • testNormalPriceOnlyAllowsLimitedBorrow — sanity check showing the normal price only allows a much smaller borrow
    • testFix_BlocksSpotPriceManipulation — proves the same manipulation cannot drain the pool when TWAP pricing is used
    • testFix_AllowsHonestBorrow — sanity check that legitimate users can still borrow against the TWAP price
  • reports/04-oracle-manipulation.md Audit-style writeup: severity, spot-price oracle risk, root cause, PoC, TWAP mitigation, fixed implementation, and learnings.

Upgradeable Proxy — Minimal Proxy, Initialize PoC, Fix

  • src/upgradeable-proxy/SimpleProxy.sol Minimal EIP-1967-style proxy: fallback forwards calls to the implementation via delegatecall.
  • src/upgradeable-proxy/ImplementationV1.sol Logic contract with a deliberately unprotected initialize() — anyone can set or overwrite owner in proxy storage.
  • src/upgradeable-proxy/FixedImplementationV1.sol Hardened logic contract using OpenZeppelin Initializable: one-time initializer and _disableInitializers() on the implementation.
  • test/upgradeable-proxy/ProxyPoC.t.sol Foundry PoC test suite:
    • testExploit_UnprotectedInitializeLetsAttackerTakeOwnership — attacker calls initialize through the proxy and seizes owner
    • testExploit_AttackerCanReinitializeAndOverwriteOwner — attacker re-initializes after the admin and overwrites ownership
    • testFix_BlocksReinitialize — proves replayed initialize calls revert and ownership stays with the admin
    • testFix_AllowsLegitimateInit — sanity check that legitimate initialization and owner flows still work
    • testFix_BlocksDirectInitializeOnImplementation — proves the bare implementation contract cannot be initialized directly
  • reports/05-upgradeable-proxy.md Audit-style writeup: unprotected initializer risk, delegatecall storage model, PoC, Initializable mitigation, and learnings.
  • src/upgradeable-proxy/ImplementationV2.sol Deliberately unsafe V2 that changes storage order and demonstrates slot reinterpretation after upgrade.
  • src/upgradeable-proxy/FixedImplementationV2.sol Safe V2 that preserves V1 layout and appends new variables.
  • test/upgradeable-proxy/StorageLayoutPoC.t.sol Foundry PoC test suite:
    • testExploit_UpgradeToBadLayoutCorruptsOwnerAndValue — proves slot reinterpretation corrupts owner/value semantics
    • testExploit_AdminLosesPrivilegesAfterBadUpgrade — proves admin loses owner-only access after incompatible upgrade
    • testFix_CompatibleLayoutPreservesOwnerAndValueAcrossUpgrade — proves layout-safe V2 keeps state intact
    • testFix_OnlyOwnerCanRunV2Initializer — proves V2 initializer is owner-gated and works for legitimate admin
  • reports/06-storage-layout-collision.md Audit-style writeup: storage collision root cause, upgrade PoC, mitigation, and verification.

Project Structure

smart-contract-security-lab/
├─ foundry.toml         # Foundry config + remappings
├─ foundry.lock         # Locked dependency versions
├─ .gitmodules          # Git submodules (forge-std, openzeppelin-contracts)
├─ .gitignore
├─ remappings.txt       # IDE-friendly remappings (mirrors foundry.toml)
├─ README.md
├─ lib/
│  ├─ forge-std/             # Foundry standard testing library (submodule)
│  └─ openzeppelin-contracts/ # OpenZeppelin Solidity library (submodule)
├─ src/
│  ├─ reentrancy/
│  │  ├─ VulnerableVault.sol
│  │  ├─ ReentrancyAttacker.sol
│  │  └─ FixedVault.sol
│  ├─ access-control/
│  │  ├─ VulnerableTreasury.sol
│  │  └─ FixedTreasury.sol
│  ├─ oracle-manipulation/
│  │  ├─ SimpleAMM.sol
│  │  ├─ VulnerableLending.sol
│  │  ├─ TWAPOracle.sol
│  │  └─ FixedLending.sol
│  ├─ signature-replay/
│  │  ├─ VulnerableAirdrop.sol
│  │  └─ FixedAirdrop.sol
│  └─ upgradeable-proxy/
│     ├─ SimpleProxy.sol
│     ├─ ImplementationV1.sol
│     ├─ FixedImplementationV1.sol
│     ├─ ImplementationV2.sol
│     └─ FixedImplementationV2.sol
├─ test/
│  ├─ reentrancy/
│  │  └─ ReentrancyPoC.t.sol
│  ├─ access-control/
│  │  └─ AccessControlPoC.t.sol
│  ├─ oracle-manipulation/
│  │  └─ OracleManipulationPoC.t.sol
│  ├─ signature-replay/
│  │  └─ SignatureReplayPoC.t.sol
│  └─ upgradeable-proxy/
│     ├─ ProxyPoC.t.sol
│     └─ StorageLayoutPoC.t.sol
└─ reports/
   ├─ 01-reentrancy.md
   ├─ 02-access-control.md
   ├─ 03-signature-replay.md
   ├─ 04-oracle-manipulation.md
   ├─ 05-upgradeable-proxy.md
   └─ 06-storage-layout-collision.md

Dependencies

  • Foundry
  • forge-std v1.16.1 — Foundry standard testing library
  • OpenZeppelin Contracts v5.6.1 — battle-tested Solidity components used in fixed versions (Ownable, ReentrancyGuard, ECDSA, etc.)

All dependencies are installed as git submodules under lib/ and locked in foundry.lock.

Getting Started

1. Install Foundry

Follow the official installation guide for your OS: https://book.getfoundry.sh/getting-started/installation

Quick reference:

# macOS / Linux / WSL
curl -L https://foundry.paradigm.xyz | bash
foundryup
# Windows (PowerShell)
powershell -c "irm https://foundry.paradigm.xyz/install.ps1 | iex"
foundryup

2. Clone with submodules

git clone --recurse-submodules https://github.com/huichain/smart-contract-security-lab.git
cd smart-contract-security-lab

If you already cloned without submodules:

git submodule update --init --recursive

3. Build

forge build

4. Test

The lab currently ships with 24 passing tests across five vulnerability modules.

Reentrancy (test/reentrancy/):

  • testExploit_DrainsVault — proves the attacker drains a 10 ETH vault with 1 ETH of capital.
  • testFix_BlocksReentrancy — proves the same exploit reverts against FixedVault.
  • testFix_AllowsHonestWithdraw — sanity check that legitimate users still work.

Access Control (test/access-control/):

  • testExploit_AnyoneCanDrainTreasury — proves anyone can drain the treasury via withdraw.
  • testExploit_AnyoneCanBecomeOwner — proves anyone can seize ownership via setOwner.
  • testFix_BlocksUnauthorizedWithdraw — proves FixedTreasury blocks unauthorized withdrawals.
  • testFix_BlocksUnauthorizedSetOwner — proves unauthorized ownership transfer reverts.
  • testFix_AllowsOwnerFunctions — sanity check that the owner can still operate the treasury.

Signature Replay (test/signature-replay/):

  • testExploit_SameSignatureClaimsTwice — proves the same signature can be replayed to claim twice.
  • testFix_BlocksSignatureReplay — proves nonce consumption blocks replaying the same signature.
  • testFix_RejectsExpiredSignature — proves signatures cannot be used after their deadline.

Oracle Manipulation (test/oracle-manipulation/):

  • testExploit_SpotPriceManipulationInflatesBorrowLimit — proves AMM spot-price manipulation inflates borrowing power.
  • testNormalPriceOnlyAllowsLimitedBorrow — proves the unmanipulated price enforces the expected lower borrow limit.
  • testFix_BlocksSpotPriceManipulation — proves TWAP pricing blocks the same spot-price manipulation attack.
  • testFix_AllowsHonestBorrow — proves legitimate users can still borrow against the TWAP price.

Upgradeable Proxy (test/upgradeable-proxy/):

  • testExploit_UnprotectedInitializeLetsAttackerTakeOwnership — proves an unprotected initialize() lets the attacker seize proxy ownership.
  • testExploit_AttackerCanReinitializeAndOverwriteOwner — proves a public initializer can overwrite an existing owner.
  • testFix_BlocksReinitialize — proves the fixed implementation blocks replayed initialization.
  • testFix_AllowsLegitimateInit — proves legitimate admin initialization still works.
  • testFix_BlocksDirectInitializeOnImplementation — proves the logic contract address cannot be initialized directly.
  • testExploit_UpgradeToBadLayoutCorruptsOwnerAndValue — proves an incompatible V2 storage layout reinterprets existing proxy state.
  • testExploit_AdminLosesPrivilegesAfterBadUpgrade — proves admin loses owner-only privileges after bad upgrade.
  • testFix_CompatibleLayoutPreservesOwnerAndValueAcrossUpgrade — proves compatible V2 preserves owner/value across upgrade.
  • testFix_OnlyOwnerCanRunV2Initializer — proves only owner can run V2 reinitializer.

Run all tests:

forge test

Run a single module:

forge test --match-path test/access-control/AccessControlPoC.t.sol -vv

Note: verbosity = 3 is set in foundry.toml, so forge test already shows the same level of detail as forge test -vvv.

Roadmap (high-level)

Vulnerability Status
Reentrancy ✅ Done — vulnerable + attacker + fix + tests + writeup
Access Control ✅ Done — vulnerable + fix + tests + writeup
Signature Replay ✅ Done — vulnerable + fixed airdrop + tests + writeup
Oracle Manipulation ✅ Done — vulnerable + TWAP fix + tests + writeup
Upgradeable Proxy ✅ Done — initializer + storage-layout PoCs, fixed V1/V2, 9 tests, 2 writeups

About the Author

Software engineer with C++ / C# background, transitioning into smart contract security and Web3 tooling.

About

Foundry-based smart contract security lab: vulnerable contracts, exploit PoCs, and fixes.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Packages

 
 
 

Contributors