|
| 1 | +--- |
| 2 | +title: "Challenge #1 - Unstopabble" |
| 3 | +date: 2023-03-25T01:57:29+01:00 |
| 4 | +draft: true |
| 5 | +--- |
| 6 | + |
| 7 | +DvD version 3.0.0 |
| 8 | + |
| 9 | +There’s a tokenized vault with a million DVT tokens deposited. It’s offering flash loans for free, until the grace period ends. |
| 10 | + |
| 11 | +*To pass the challenge, make the vault stop offering flash loans.* |
| 12 | + |
| 13 | +You start with 10 DVT tokens in balance. |
| 14 | + |
| 15 | +- [See the contracts](https://github.com/tinchoabbate/damn-vulnerable-defi/tree/v3.0.0/contracts/unstoppable) |
| 16 | +- [Complete the challenge](https://github.com/tinchoabbate/damn-vulnerable-defi/blob/v3.0.0/test/unstoppable/unstoppable.challenge.js) |
| 17 | + |
| 18 | +## Solution ## |
| 19 | + |
| 20 | +In this challenge we have two contracts: *ReceiverUnstoppable.sol* and *UnstoppableVault.sol*. If we analyze them we will see the first one is just implementing the use of function **flashLoan()** of the second one, so, let's take a look what this function do in UnstoppableVault.sol. |
| 21 | + |
| 22 | +## UnstoppableVault.sol ## |
| 23 | + |
| 24 | +The function *flashLoad()* is defined as |
| 25 | + |
| 26 | +```solidity |
| 27 | +function flashLoan( |
| 28 | + IERC3156FlashBorrower receiver, |
| 29 | + address _token, |
| 30 | + uint256 amount, |
| 31 | + bytes calldata data |
| 32 | +) external returns (bool) { |
| 33 | + if (amount == 0) revert InvalidAmount(0); // fail early |
| 34 | + if (address(asset) != _token) revert UnsupportedCurrency(); // enforce ERC3156 requirement |
| 35 | + uint256 balanceBefore = totalAssets(); |
| 36 | + if (convertToShares(totalSupply) != balanceBefore) revert InvalidBalance(); // enforce ERC4626 requirement |
| 37 | + uint256 fee = flashFee(_token, amount); |
| 38 | + // transfer tokens out + execute callback on receiver |
| 39 | + ERC20(_token).safeTransfer(address(receiver), amount); |
| 40 | + // callback must return magic value, otherwise assume it failed |
| 41 | + if (receiver.onFlashLoan(msg.sender, address(asset), amount, fee, data) != keccak256("IERC3156FlashBorrower.onFlashLoan")) |
| 42 | + revert CallbackFailed(); |
| 43 | + // pull amount + fee from receiver, then pay the fee to the recipient |
| 44 | + ERC20(_token).safeTransferFrom(address(receiver), address(this), amount + fee); |
| 45 | + ERC20(_token).safeTransfer(feeRecipient, fee); |
| 46 | + return true; |
| 47 | +} |
| 48 | +``` |
| 49 | + |
| 50 | +and we can re-write all this as |
| 51 | + |
| 52 | +- checking load amoun is not 0 |
| 53 | +- checking asset address is correct |
| 54 | +- **checking totalSuppy is equat to totalAssets** |
| 55 | +- calculate fee |
| 56 | +- transfer *amount* to receiver |
| 57 | +- call receiver's function onFlashLoad() |
| 58 | +- transfer from receiver back to the contract *amount* + *fee* |
| 59 | +- transfer fee to feeReceiver |
| 60 | + |
| 61 | +Every seems good except for the revert condition checking totalSupply is equal to totalAssets(). To understand this line, lets take a look at contract ERC4626 from which this contract inherits. |
| 62 | + |
| 63 | +```solidity |
| 64 | +... |
| 65 | +ERC20 public immutable asset; |
| 66 | +
|
| 67 | +constructor( |
| 68 | + ERC20 _asset, |
| 69 | + string memory _name, |
| 70 | + string memory _symbol |
| 71 | +) ERC20(_name, _symbol, _asset.decimals()) { |
| 72 | + asset = _asset; |
| 73 | +} |
| 74 | +... |
| 75 | +``` |
0 commit comments