Foundrynaut icon indicating copy to clipboard operation
Foundrynaut copied to clipboard

Ethernaut challenges solved with foundry / forge solidity scripts

test

Foundrynaut

Solution to all Ethernaut problems implemented as foundry solidity scripts

Setup

  1. Install foundry
  2. Set environment variables for
    • FORK_URL(url of rinkeby network rpc from a provider such as alchemy)
    • PRIVATE_KEY private key of account you want to use to submit solutions. (Needs to have sufficient rinkeby eth - NEVER SHARE OR COMMIT THIS TO ANY REPO)

Run Solutions

  • Run a single solution: sh run_solution.sh 00_HelloEthernaut (to actually submit your solution on chain add --broadcast option)
  • Run all solutions sh run_all_solutions.sh

(both commands need to be run from the root of this repository)

By default the solutions are run against the ethernaut contracts deployed on "goerli" however you can run any of the other supported networks by specifying the respective deployment/address file from the deployments folder. In that case you will have to also adjust your FORK_URL to point to an RPC node on the respective network.

For example to run the solutions againt sepolia simply run: DEPLOYMENT_FILE="deployments/sepolia.json" sh run_all_solutions.sh

Foundry Scripting issues / painpoints

One purpose of this project was to evaluate foundry's solidity scripts as an alternative to Javascript scripts. Despite the many advantages there were also a few issues / lack of functionality compared to my JS setup (using ethers.js and hardhat). This description refers to forge 0.2.0 (f016135 2022-07-04T00:15:02.930499Z), and might contain issues that are fixed in later versions. (In which case feel welcome to create an issue / pr so that I can update this list as well as my implementations)

No transaction specific gas limits

Problem

There is no way to specify a gas limit for a specific transaction / contract call in forge solidity scripts.

Workaround

I had to deploy separate contracts so that I could use contract.method{gas: gas}() syntax to specify gas limits. (see GatekeeperOne solution) However the gas estimation would ignore these values and the script would then try to run the respective call with a gasLimit lower than the one specified in the nested call. The only workaround I found for that was to manually waste enough gas during simulation. (see here)

On-chain simulation always simulates all tx's on the same block

Problem

Simulation of on-chain transactions behave as if all of them were included in the same block, even when running with --slow (which should ensure that this is not actually the case when broadcasting). vm.roll calls are also ignored during that simulation.

Workaround

This was a problem when implementing the CoinFlip solution which requires each call to be on a separate block. To solve it in this case I had to add very ugly logic skipping parts of the implementation in the on-chain simulation step. (see here)

No top level revertion handling

Problem

Cannot catch / ignore revertions thrown in the script implementation. Revertions that are thrown in the ethernaut script directly always lead to the script aborting. I tried the following approaches none of which did the trick:

  • Handle revertion with try / catch
  • Ignore revertion with (bool success, bytes memory data) = address(contract).call(encodedCallData) syntax.
  • Expect revertiotn with vm.expectRevert

Workaround

I had to create a separate contract instance (even though its deployment will not be actually broadcasted) inside which I handled the revrtion. (see here)

selfdestruct has no effect during script runtime

Problem

No way to test / assert the results of a selfdestruct call. (see related issue here).

Workaround

I didn't find a workaround for this one. I had to disable validating the solution for the Motorbike solution here.