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.
- ✅ 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
-
src/reentrancy/VulnerableVault.solA minimal ETH vault that sends ETH to the user before updating the user's balance, which makes it vulnerable to reentrancy. -
src/reentrancy/ReentrancyAttacker.solAttacker contract that re-enterswithdrawfromreceive()to drain the vault. -
src/reentrancy/FixedVault.solHardened vault using checks-effects-interactions and OpenZeppelin'sReentrancyGuard(defense in depth). -
test/reentrancy/ReentrancyPoC.t.solFoundry PoC test suite:testExploit_DrainsVault— the attack drains a 10 ETH vault with 1 ETH of attacker capitaltestFix_BlocksReentrancy— the same attacker againstFixedVaultreverts and victim funds remain safetestFix_AllowsHonestWithdraw— sanity check that the fix does not break legitimate users
-
reports/01-reentrancy.mdAudit-style writeup: severity, summary, root cause, PoC, recommendation, fixed implementation, and learnings.
-
src/access-control/VulnerableTreasury.solA protocol treasury with anownerfield, butwithdrawandsetOwnerhave no access checks — two independent bugs. -
src/access-control/FixedTreasury.solHardened treasury using OpenZeppelinOwnableandonlyOwneron sensitive functions (no separate attacker contract needed; EOA can exploit the vulnerable version). -
test/access-control/AccessControlPoC.t.solFoundry PoC test suite:testExploit_AnyoneCanDrainTreasury— any account can callwithdrawand drain all ETHtestExploit_AnyoneCanBecomeOwner— any account can callsetOwnerand seize ownershiptestFix_BlocksUnauthorizedWithdraw— attackerwithdrawreverts withOwnableUnauthorizedAccounttestFix_BlocksUnauthorizedSetOwner— attackersetOwnerreverts; owner unchangedtestFix_AllowsOwnerFunctions— legitimate owner can still withdraw and transfer ownership
-
reports/02-access-control.mdAudit-style writeup: two findings (withdraw,setOwner), severity, PoC, recommendation, fixed implementation, and learnings.
-
src/signature-replay/VulnerableAirdrop.solA deliberately vulnerable ETH airdrop that accepts an off-chain signature from a trusted signer, but the signed message only bindsaccountandamount. -
test/signature-replay/SignatureReplayPoC.t.solFoundry PoC test suite:testExploit_SameSignatureClaimsTwice— reuses the exact same signature twice and proves the claimant receives the airdrop twicetestFix_BlocksSignatureReplay— proves the fixed contract consumes the user's nonce and rejects the replayed signaturetestFix_RejectsExpiredSignature— proves expired signatures cannot be used
-
src/signature-replay/FixedAirdrop.solFixed implementation: binds signatures to nonce, deadline, chain id, andaddress(this). -
reports/03-signature-replay.mdAudit-style writeup: severity, replay impact, root cause, PoC, recommendation, fixed implementation, and learnings.
-
src/oracle-manipulation/SimpleAMM.solA deliberately simplified constant-product AMM whose spot price can be moved by changing reserves. -
src/oracle-manipulation/VulnerableLending.solA toy lending market that directly trusts the AMM spot price to calculate borrowing power. -
src/oracle-manipulation/TWAPOracle.solRecords cumulative AMM prices so lending can consult a time-weighted average instead of spot. -
src/oracle-manipulation/FixedLending.solHardened lending market that prices collateral withTWAPOracle.consult()instead ofgetSpotPrice(). -
test/oracle-manipulation/OracleManipulationPoC.t.solFoundry PoC test suite:testExploit_SpotPriceManipulationInflatesBorrowLimit— proves manipulating the AMM spot price inflates the borrow limit and drains pool liquiditytestNormalPriceOnlyAllowsLimitedBorrow— sanity check showing the normal price only allows a much smaller borrowtestFix_BlocksSpotPriceManipulation— proves the same manipulation cannot drain the pool when TWAP pricing is usedtestFix_AllowsHonestBorrow— sanity check that legitimate users can still borrow against the TWAP price
-
reports/04-oracle-manipulation.mdAudit-style writeup: severity, spot-price oracle risk, root cause, PoC, TWAP mitigation, fixed implementation, and learnings.
-
src/upgradeable-proxy/SimpleProxy.solMinimal EIP-1967-style proxy:fallbackforwards calls to the implementation viadelegatecall. -
src/upgradeable-proxy/ImplementationV1.solLogic contract with a deliberately unprotectedinitialize()— anyone can set or overwriteownerin proxy storage. -
src/upgradeable-proxy/FixedImplementationV1.solHardened logic contract using OpenZeppelinInitializable: one-timeinitializerand_disableInitializers()on the implementation. -
test/upgradeable-proxy/ProxyPoC.t.solFoundry PoC test suite:testExploit_UnprotectedInitializeLetsAttackerTakeOwnership— attacker callsinitializethrough the proxy and seizesownertestExploit_AttackerCanReinitializeAndOverwriteOwner— attacker re-initializes after the admin and overwrites ownershiptestFix_BlocksReinitialize— proves replayedinitializecalls revert and ownership stays with the admintestFix_AllowsLegitimateInit— sanity check that legitimate initialization and owner flows still worktestFix_BlocksDirectInitializeOnImplementation— proves the bare implementation contract cannot be initialized directly
-
reports/05-upgradeable-proxy.mdAudit-style writeup: unprotected initializer risk, delegatecall storage model, PoC,Initializablemitigation, and learnings. -
src/upgradeable-proxy/ImplementationV2.solDeliberately unsafe V2 that changes storage order and demonstrates slot reinterpretation after upgrade. -
src/upgradeable-proxy/FixedImplementationV2.solSafe V2 that preserves V1 layout and appends new variables. -
test/upgradeable-proxy/StorageLayoutPoC.t.solFoundry PoC test suite:testExploit_UpgradeToBadLayoutCorruptsOwnerAndValue— proves slot reinterpretation corrupts owner/value semanticstestExploit_AdminLosesPrivilegesAfterBadUpgrade— proves admin loses owner-only access after incompatible upgradetestFix_CompatibleLayoutPreservesOwnerAndValueAcrossUpgrade— proves layout-safe V2 keeps state intacttestFix_OnlyOwnerCanRunV2Initializer— proves V2 initializer is owner-gated and works for legitimate admin
-
reports/06-storage-layout-collision.mdAudit-style writeup: storage collision root cause, upgrade PoC, mitigation, and verification.
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
- 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.
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"
foundryupgit clone --recurse-submodules https://github.com/huichain/smart-contract-security-lab.git
cd smart-contract-security-labIf you already cloned without submodules:
git submodule update --init --recursiveforge buildThe 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 againstFixedVault.testFix_AllowsHonestWithdraw— sanity check that legitimate users still work.
Access Control (test/access-control/):
testExploit_AnyoneCanDrainTreasury— proves anyone can drain the treasury viawithdraw.testExploit_AnyoneCanBecomeOwner— proves anyone can seize ownership viasetOwner.testFix_BlocksUnauthorizedWithdraw— provesFixedTreasuryblocks 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 unprotectedinitialize()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 testRun a single module:
forge test --match-path test/access-control/AccessControlPoC.t.sol -vvNote:
verbosity = 3is set infoundry.toml, soforge testalready shows the same level of detail asforge test -vvv.
| 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 |
Software engineer with C++ / C# background, transitioning into smart contract security and Web3 tooling.