How To Use Foundry To PoC Bug Leads, Part 1

Immunefi
Immunefi
Published in
4 min readJul 5, 2022

You’ve probably heard about all of the epic disclosures in the Web3 bug bounty space recently and decided that maybe it’s time to pave your way to the Immunefi Hall of Fame. Or maybe you’re a seasoned whitehat using Hardhat to PoC your bug leads and heard about this shiny thing called Foundry, but not sure if it’s worth the switch?

This tutorial will provide a very basic intro to Foundry. As part of this tutorial, we’ll make a simple test PoC and run it. In part 2 of this tutorial series, we’ll dive into a more detailed PoC example using a real-life vulnerability.

This article was written by cergyk.eth.

Check out part 2 of the tutorial here.

Installing Foundry And Forking

First of all, let’s install Foundry with the instructions provided here.

forge init will initialize a bare-bones test project with the following structure:

├── foundry.toml
├── lib
│ └── forge-std
├── script
│ └── Contract.s.sol
├── src
│ └── Contract.sol
└── test
└── Contract.t.sol

We can see that it defined a basic test (Contract.t.sol), and a standard library (forge-std) is also available in the lib folder. (This library will be useful to operate some tricks on the Foundry vm but more on that later).

Let’s set up the details of the chain we want to fork — in our case, the Ethereum mainnet. To do this, change the configuration of the project in the file foundry.toml:

[default]
src = 'src'
out = 'out'
libs = ['lib']
#Add these lines:
chain_id = 1
eth_rpc_url = 'https://eth-mainnet.alchemyapi.io/v2/{your_alchemy_api_key}'
block_number = 14812830
etherscan_api_key = '{your_etherscan_api_key}'

Note that providing an etherscan_api_key greatly improves readability of forge traces when run in verbose mode.

Fetching Project Sources

Now, we have to get the sources of the deployed address we want to test against. Let’s take YOP Finance as an example (check out their program on Immunefi).

I will not go into the details of any particular vulnerabilities on any particular project. A full write-up will be the subject of another article, so stay tuned! Here we will simply set up a script which stakes some YOP tokens in the protocol.

Foundry enables us to write our tests in Solidity, so we may use the code of the deployed contract and compile our test against it! To download the deployed code, I made a command line tool that you can check here: ethereum-sources-downloader

npm i -g ethereum-sources-downloader
# Downloads staking sources in lib
ethereum-sources-downloader etherscan 0x5B705d7c6362A73fD56D5bCedF09f4E40C2d3670 lib

The tool creates the directory lib/StakingV2, containing the sources of the deployed contract and its dependencies.

Let’s import the target contracts in our test Contract.t.sol:

// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.9;
import "forge-std/Test.sol";
import "StakingV2/contracts/staking/StakingV2.sol";
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
contract ContractTest is Test {
IERC20 yopToken = IERC20(0xAE1eaAE3F627AAca434127644371b67B18444051);
StakingV2 staking = StakingV2(0x5B705d7c6362A73fD56D5bCedF09f4E40C2d3670);
address attacker = address(1);
//...
}

At this point, running forge test complains about not finding @openzeppelin files. This is expected, since they are downloaded into the same folder as the project and Foundry only knows one root for dependencies which is lib/.

It is time to learn about another useful feature in Foundry, which is remappings.

forge remappings gives us:

StakingV2/=lib/StakingV2/
ds-test/=lib/forge-std/lib/ds-test/src/
forge-std/=lib/forge-std/src/

Which shows why lib is the root for the libraries we import! Luckily, we can add a new remapping for the @openzeppelin lib imported by the project. We create the file remappings.txt at the root of our project and add the line:

@openzeppelin/=lib/StakingV2/@openzeppelin/

Now forge test runs successfully!

Airdrop Some Tokens, Impersonate, and Stake

One neat feature of Foundry is that we can overwrite the state linked to the view of a contract. Let’s use this feature to give ourselves some tokens. Check out here for detailed explanations!

Let’s give ourselves 500 of the token in the test setup:

// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.9;
import "forge-std/Test.sol";
import "StakingV2/contracts/staking/StakingV2.sol";
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
contract ContractTest is Test {
using stdStorage for StdStorage;
//...
function writeTokenBalance(
address who,
IERC20 token,
uint256 amt
) internal {
stdstore
.target(address(token))
.sig(token.balanceOf.selector)
.with_key(who)
.checked_write(amt);
}
function setUp() public {
writeTokenBalance(attacker, yopToken, 500 ether);
}
//...
}

Now we have to impersonate the address we chose for the attacker and call the protocol. We need to cheat a bit to do that, and Foundry cheat codes come in handy: vm.startPrank(address) lets us impersonate any address!

#Contract.t.sol:// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.9;
import "forge-std/Test.sol";
import "StakingV2/contracts/staking/StakingV2.sol";
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
contract ContractTest is Test {
//...
function testExample() public {
vm.startPrank(attacker);
uint8 lock_duration_months = 1;
yopToken.approve(address(staking), 500 ether);
staking.stake(500 ether, lock_duration_months);
}
}

We run the test again with forge test and voila!

Running 1 test for test/Contract.t.sol:ContractTest
[PASS] testExample() (gas: 314937)
Test result: ok. 1 passed; 0 failed; finished in 9.30s

The complete code for this tutorial is available on GitHub.

Follow me on Twitter for more Web3 security content!

If you’re a Web2 or Web3 developer who is finally thinking about a bug-hunting career in Web3, we got you. Check out our ultimate blockchain hacking guide, and start taking home some of the $135m in rewards available on Immunefi — the leading bug bounty platform for Web3.

--

--

Immunefi
Immunefi

Immunefi is the premier bug bounty platform for smart contracts, where hackers review code, disclose vulnerabilities, get paid, and make crypto safer.