Unstoppable

Description

There's a tokenized vault with a million DVT tokens deposited. It’s offering flash loans for free, until the grace period ends.

To pass the challenge, make the vault stop offering flash loans.

You start with 10 DVT tokens in balance.

TL;DR

There is a strict check in the flashLoan() function:

if (convertToShares(totalSupply) != balanceBefore) revert InvalidBalance();

Sending 1 wei to the contract will break this check and cause DoS.

Code Audit

Our objective is to DoS the flash loan functionality, so we should be looking for things like require and revert.

In UnstoppableVault.flashLoan():

uint256 balanceBefore = totalAssets();
if (convertToShares(totalSupply) != balanceBefore) revert InvalidBalance(); // enforce ERC4626 requirement

If we can make convertToShares(totalSupply) and balanceBefore out of sync, then the flashLoan() call will revert. This can be a DoS attack. But how?

totalAssets():

The assembly block tests if the storage slot 0 equals 2, and then moves something like a function selector into memory offset 0 and reverts. This looks like a defense menchanism so we can ignore it. In the end this function returns balanceOf(address(this)).

In flashLoan() we are actually testing if totalSupply is the same as balanceOf(address(this)), but recall that there are two ways of transferring ethers into this contract:

  1. Through the vault.deposit() function -> this is the intended way

  2. Call token.transfer() directly -> this is unintended and will break the if statement.

Here is how Secureum describes this bug:

Dangerous strict equalities: Use of strict equalities with tokens/Ether can accidentally/maliciously cause unexpected behavior. Consider using >= or <= instead of == for such variables depending on the contract logic. (see here)

To fix the code, we can just change != to >=:

Building PoC

Simply transfer 1 wei to the lender pool:

Last updated