Skip to content

Commit 8e388c6

Browse files
Make echidna exercise-7 solution self contained (#158)
* Make echidna exercise-7 solution self contained * Fixed incorrect quantity in config.yaml for echidna exercise 7 non-etheno solution * Instructions for Echidna Exercise 7 without using Etheno * Renamed contract, fixed exercise description. Added github action for exercise 7
1 parent fd80147 commit 8e388c6

File tree

4 files changed

+40
-66
lines changed

4 files changed

+40
-66
lines changed

.github/workflows/echidna.yml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,14 @@ jobs:
6060
contract: UnstoppableEchidna
6161
outcome: failure
6262
expected: 'echidna_testFlashLoan:\s*failed'
63+
- name: Exercise 7
64+
workdir: dvdefi/
65+
files: .
66+
config: sideentrance.yaml
67+
crytic-args: --hardhat-ignore-compile
68+
contract: SideEntranceEchidna
69+
outcome: failure
70+
expected: 'testPoolBalance():\s*failed'
6371
- name: TestToken
6472
workdir: program-analysis/echidna/example/
6573
files: testtoken.sol

program-analysis/echidna/Exercise-7.md

Lines changed: 14 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,8 @@ Join the team on Slack at: https://empireslacking.herokuapp.com/ #ethereum
1515
- clone the repo via `git clone https://github.com/tinchoabbate/damn-vulnerable-defi -b v2.0.0`, and
1616
- install the dependencies via `yarn install`.
1717
2. To run Echidna on these contracts you must comment out the `dependencyCompiler` section in `hardhat.config.js`. Otherwise, the project will not compile with [`crytic-compile`](https://github.com/crytic/crytic-compile). See the example provided [here](./exercises/exercise7/example.hardhat.config.ts).
18-
3. For this exercise we will be using Etheno to deploy the `SideEntranceLenderPool` contract. You can find more about Etheno [here](./end-to-end-testing.md).
19-
4. Analyze the `before` function in `test/side-entrance/side-entrance.challenge.js` to identify what initial setup needs to be done.
20-
5. Create a contract called `E2E` to be used for the end-to-end testing by Echidna.
18+
3. Analyze the `before` function in `test/side-entrance/side-entrance.challenge.js` to identify what initial setup needs to be done.
19+
4. Create a contract to be used for the property testing by Echidna.
2120

2221
No skeleton will be provided for this exercise.
2322

@@ -39,72 +38,27 @@ This solution can be found in [exercises/exercise7/solution.sol](./exercises/exe
3938
<summary>Solution Explained (spoilers ahead)</summary>
4039

4140
The goal of the side entrance challenge is to realize that the contract's accounting of its ETH balance is misconfigured. `balanceBefore` is used to track the balance of the contract before the flash loan BUT `address(this).balance` is used to track the balance of the contract after the flash loan. Thus, you can use the deposit function to repay your flash loan while still maintaining that the contract's total balance of ETH has not changed (i.e. `address(this).balance >= balanceBefore`). Since the ETH that was deposited is now owned by you, you can now also withdraw it and drain all the funds from the contract.
42-
43-
We instruct Echidna to do a flashloan. Using the `setEnableWithdraw` and `setEnableDeposit` Echidna will search for function(s) to call inside the flashloan callback to try and break the `testPoolBalance` property.
44-
45-
At some point Echidna will identify that if (1) `deposit` is used to pay back the flash loan and (2) `withdraw` is called right after, the `testPoolBalance` property breaks.
46-
47-
To use Etheno, you can use an example deployment script like the one below via Hardhat:
48-
```javascript
49-
const hre = require("hardhat");
50-
const ethers = hre.ethers;
51-
52-
async function main() {
53-
const ETHER_IN_POOL = ethers.utils.parseEther("1000");
54-
55-
[deployer, attacker] = await ethers.getSigners();
56-
57-
const SideEntranceLenderPoolFactory = await ethers.getContractFactory(
58-
"SideEntranceLenderPool",
59-
deployer
60-
);
61-
62-
pool = await SideEntranceLenderPoolFactory.deploy();
63-
await pool.deployed();
64-
console.log(`pool address ${pool.address}`);
6541

66-
await this.pool.deposit({ value: ETHER_IN_POOL });
42+
In order for Echidna to be able to interact with the `SideEntranceLenderPool`, it has to be deployed first. However, deploying and funding it from the contract to be used by Echidna won't work, as the funding transaction's `msg.sender` is the contract itself. This means that the owner of the funds is the Echidna contract and therefore it can remove the funds by calling `withdraw()`, without the need for the exploit.
6743

68-
}
44+
To prevent that issue, a simple factory contract has to be created to deploy the pool without setting the Echidna property testing contract as the owner of the funds. This factory has a public function that deploys a `SideEntranceLenderPool`, funds it with the given amount, and return its address. Now, since the Echidna testing contract is not the owner of the funds, it cannot call `withdraw()` to empty the pool.
6945

70-
main()
71-
.then(() => process.exit(0))
72-
.catch((error) => {
73-
console.error(error);
74-
process.exit(1);
75-
});
76-
```
77-
Make sure to add a localhost network to be able to deploy to Etheno. Example for Hardhat:
78-
```javascript
79-
networks: {
80-
localhost: {
81-
url: "http://127.0.0.1:8545",
82-
},
83-
}
84-
```
85-
86-
To deploy to Etheno run the following command:
87-
```shell
88-
etheno --ganache --ganache-args="--miner.blockGasLimit 10000000" -x init.json
89-
```
90-
In another shell run the following hardhat command:
91-
```shell
92-
npx hardhat run scripts/deploy.js --network localhost
93-
```
46+
Now that the challenge is set up as intended, we proceed to solve it by instructing Echidna to do a flashloan. Using the `setEnableWithdraw` and `setEnableDeposit` Echidna will search for function(s) to call inside the flashloan callback to try and break the `testPoolBalance` property.
9447

95-
And then your shell command works fine.
48+
At some point Echidna will identify that if (1) `deposit` is used to pay back the flash loan and (2) `withdraw` is called right after, the `testPoolBalance` property breaks.
9649

97-
Don't forget to copy the initialization JSON file (`init.json`) from Etheno to your fuzzing environment!
98-
9950
Example Echidna output:
10051
```
101-
$ echidna-test . --contract E2E --config config.yaml
52+
$ echidna-test . --contract EchidnaSideEntranceLenderPool --config config.yaml
10253
...
103-
testPoolBalance(): failed!💥
104-
Call sequence, shrinking (3003/5000):
105-
setEnableDeposit(true,208)
54+
testPoolBalance(): failed!💥
55+
Call sequence:
56+
execute() Value: 0x103
57+
setEnableDeposit(true,256)
10658
flashLoan(1)
107-
withdraw()
59+
setEnableWithdraw(true)
60+
setEnableDeposit(false,0)
61+
execute()
10862
testPoolBalance()
10963
...
11064
```

program-analysis/echidna/exercises/exercise7/config.yaml

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
1-
initialize: init.json
2-
multi-abi: true
31
testMode: assertion
4-
balanceContract: 10000000000000000000000
2+
balanceContract: 1000000000000000000000
53
testLimit: 100000000000
64

75
deployer: "0x10000"

program-analysis/echidna/exercises/exercise7/solution.sol

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,18 @@ pragma solidity ^0.8.4;
33

44
import "./side-entrance/SideEntranceLenderPool.sol";
55

6-
contract E2E is IFlashLoanEtherReceiver {
6+
contract PoolDeployer {
7+
function deployNewPool() public payable returns (SideEntranceLenderPool) {
8+
SideEntranceLenderPool p;
9+
p = new SideEntranceLenderPool();
10+
p.deposit{value: msg.value}();
11+
12+
return p;
13+
}
14+
}
15+
16+
contract SideEntranceEchidna is IFlashLoanEtherReceiver {
717
SideEntranceLenderPool pool;
8-
address ADDRESS_POOL = 0x1dC4c1cEFEF38a777b15aA20260a54E584b16C48;
918

1019
uint256 initialPoolBalance;
1120

@@ -14,7 +23,11 @@ contract E2E is IFlashLoanEtherReceiver {
1423
uint256 depositAmount;
1524

1625
constructor() payable {
17-
pool = SideEntranceLenderPool(ADDRESS_POOL);
26+
require(msg.value == 1000 ether);
27+
28+
PoolDeployer p = new PoolDeployer();
29+
30+
pool = p.deployNewPool{value: 1000 ether}();
1831
initialPoolBalance = address(pool).balance;
1932
}
2033

@@ -45,4 +58,5 @@ contract E2E is IFlashLoanEtherReceiver {
4558
function testPoolBalance() public view {
4659
assert(address(pool).balance >= initialPoolBalance);
4760
}
61+
4862
}

0 commit comments

Comments
 (0)