Table of contents:
Join the team on Slack at: https://slack.empirehacking.nyc/ #ethereum
- Clone the repository:
git clone https://github.com/crytic/damn-vulnerable-defi-echidna
- Install dependencies using
- Analyze the
test/side-entrance/side-entrance.challenge.jsto determine the initial setup requirements.
- Create a contract to be used for property testing with Echidna.
No skeleton will be provided for this exercise.
- Set up the testing environment with appropriate contracts and necessary balances.
- Add a property to check if the balance of the
SideEntranceLenderPoolcontract has changed.
- Create a
config.yamlwith the required configuration option(s).
- After Echidna discovers the bug, fix the issue and test your property with Echidna again.
Hint: To become familiar with the workings of the target contract, try manually executing a flash loan.
The solution can be found in solution.sol.
Solution Explained (spoilers ahead)
The goal of the side entrance challenge is to realize that the contract's ETH balance accounting is misconfigured. The
balanceBefore variable tracks the contract's balance before the flash loan, while
address(this).balance tracks the balance after the flash loan. As a result, you can use the deposit function to repay your flash loan while maintaining the notion that the contract's total ETH balance hasn't changed (i.e.,
address(this).balance >= balanceBefore). However, since you now own the deposited ETH, you can also withdraw it and drain all the funds from the contract.
For Echidna to interact with the
SideEntranceLenderPool, it must be deployed first. Deploying and funding the pool from the Echidna property testing contract won't work, as the funding transaction's
msg.sender will be the contract itself. This means that the Echidna contract will own the funds, allowing it to remove them by calling
withdraw() without exploiting the vulnerability.
To avoid the above issue, create a simple factory contract that deploys the pool without setting the Echidna property testing contract as the owner of the funds. This factory will have a public function that deploys a
SideEntranceLenderPool, funds it with the given amount, and returns its address. Since the Echidna testing contract does not own the funds, it cannot call
withdraw() to empty the pool.
With the challenge properly set up, instruct Echidna to execute a flash loan. By using the
setEnableDeposit, Echidna will search for functions to call within the flash loan callback to attempt to break the
Echidna will eventually discover that if (1)
deposit is used to repay the flash loan and (2)
withdraw is called immediately afterward, the
testPoolBalance property fails.
Example Echidna output:
echidna . --contract EchidnaSideEntranceLenderPool --config config.yaml ... testPoolBalance(): failed!💥 Call sequence: execute() Value: 0x103 setEnableDeposit(true,256) flashLoan(1) setEnableWithdraw(true) setEnableDeposit(false,0) execute() testPoolBalance() ...