This projects sole goal is to allow people that want to learn about Smart Contracts Security through solving OpenZeppelin's challenges created by @tinchoabbate https://www.damnvulnerabledefi.xyz/, without worring about also learning Javascript. It was also a good execuse for me to start playing with Brownie, a Python-based development framework for the EVM created by Ben Hauser (iamdefinitelyhuman)
I "ported" Martin's exercises to Brownie. This allowed me to solve them, while at the same time learning about Brownie and its inner workings.
You can use this repository to solve the following challenges:
- Unstoppable
- Naive receiver
- Truster
- Side Entrance
- The Rewarder
- Selfie
- Compromised
- Puppet
- Puppet v2
- Free Rider
- Backdoor
- Climber
To be able to port challenge 11 I had to modify part of the source code of Gnosis Safe project that's saved on Brownie's packages folder (/home/user/.brownie/packages/
), specifically remove the enforcement of Solidity compiler version from the following files:
- packages/safe-global/safe-contracts@1.3.0-libs.0/contracts/interfaces/ViewStorageAccessible.sol comment out Solidity pragma
- packages/safe-global/safe-contracts@1.3.0-libs.0/contracts/test/ERC20Token.sol comment out Solidity pragma
Also I needed to include project mock-contract
into safe-contracts
project folder:
- Download https://github.com/gnosis/mock-contract/tree/master and save it into packages/safe-global/safe-contracts@1.3.0-libs.0/
I tried to change the least amount of things due to the fact that I'm a beginner learning about all of this.
The idea is to leverage Brownie's flexibility in its testing suite and use it to test our solutions for the challenges. Every challenge follows the same idea:
I created a test file called <challenge_name>_test.py
that has a function called
test_solution
. In this test, after setting up the scenario (see section below for more details)
you can write your solution, for example, let's see the test for truster
challenge:
def test_solution():
damn_valuable_token, truster_lender_pool = scenario_setup()
# Solution goes here
# Write your exploit
# :) Good lock
# Attacker is using accounts[1]
#
# SOLUTION GOES HERE
#
assert damn_valuable_token.balanceOf(accounts[1]) == TOKENS_IN_POOL
assert damn_valuable_token.balanceOf(truster_lender_pool) == 0
The first step is to set up the scenario (again see below section for more details), after that you can write your solution within the test_solution() function in the test.py file. the assertions at the end ensure that you have correctly solved the challenge.
For the challenges that I solved I included an exploit.py
under the scripts
folder that contains code used for solving the challenge (this is a spoiler, don't read it before
thinking about the problem). The same applies for contracts named AttackerContract
.
These are part of the solution, don't spoil yourself.
- Install Brownie in a virtual environment and activate it. The best way to do this is to follow this guide: https://iamdefinitelyahuman.medium.com/getting-started-with-brownie-part-1-9b2181f4cb99
Note: that you will need dependencies including ganache-cli through node/npm (that will really be the only piece of JS you'll need) The brownie install docs are very helpful for this as well: https://eth-brownie.readthedocs.io/en/stable/install.html
- Clone this repository. You'll find different folders for each challenge.
- Each of these folders is a different Brownie project.
- Change directory to the challenge you want to solve. For example, truster.
- If you'd like to interact with the contract, run the deploy.py setup scenario script. See section below for more information.
- You can run
brownie test
to run all test. For most of the challenges I kept the same tests that Martin created to check that the scenario is properly setup. Your solution has to be implemented or called from thetest_solution
test.
Note: you can use brownie test -k test_solution
to run only the test with the
solution
- Write your solution in the
test_solution
placeholder that I left. - Test your solution running the tests, with
brownie test -k test_solution
. If the solution is correct, test should pass.
To do this, we'll want to run the deploy.py script contained in the "scripts" folder. The way to do this is as follows:
- Create a "__init__.py" file inside the scripts folder if it is not already there
- Run command "brownie console" (ensure the dependencies are all installed - follow the links above in the Quick Start for more info)
- execute "from scripts.deploy import scenario_setup" (where scenario_setup() is the name of the function)
- Now you can call "scenario_setup()" which returns the contracts
- You can now interact with the contract(s) via the brownie-cli; the contracts will have the same methods and functions to interact with as you see in the code itself
Please note that all of the steps above are to be able to interact with the exercise via the brownie console. If you only want to code your solution and test it you can do as described above:
- Write your solution in the file "test_solution.py" inside "tests"
- Test it running: "brownie test -k test_solution"
You can find my solutions in my Blog, links below: