Damn Vulnerable Defi Walkthrough Part One: Challenge 1–6.

Balázs Kocsis
9 min readDec 25, 2021

You can find part two here and the repository here.

I started learning solidity in June by participating in Chainlink’s Smart Contract Developer Bootcamp. I liked it a lot so started digging further in the rabbit hole.
Later this year in September I got selected to participate in Secureum’s Smart Contract Security Bootcamp. From the first RACE (Readiness Assessment for CARE) phase with 1024 initial participants, I made it to the top 128 that progressed to the Audit Readiness Phase, where I had the privilege to create the first CARE (Comprehensive Audit Readiness Evaluation)audit report on my own. You can read about that in my next blog post.
During the learning phase, I had quite a lot of fun and learned new audit techniques on a daily basis but the biggest impact was the hands-on capture the flag challenges from @tinchoabbate the Damn Vulnerable Defi.

While I was solving the challenges I was constantly torn between the I want to solve it all immediately and the let’s keep some for tomorrow otherwise I will finish it too quickly feelings. So to preserve something from this enjoyment I would like to present my solutions and the thoughts behind them.

If you would like to go straight to the code here is the repository.

Challenge #1 — Unstoppable

There’s a lending pool UnstoppableLenderwith a million DamnValuableToken (DVT) tokens in balance, offering flash loans for free.
If only there was a way to attack and stop the pool from offering flash loans …You start with 100 DVT tokens in balance.

So all we have to do is to break the pool. Cool, I was always good at breaking things :)

To understand better I always started the challenges by actually using the service the particular smart contract offers. Once that works I tried to solve the challenge itself.
ReceiverUnstoppable is an example contract showing how to take a flash loan. We will use this functionality many times in other challenges so worth taking a look at it :

Okay, we can take a flash loan and we can pay it back but how will we stop the pool from offering it? After examining the loaner contract’s flashLoan function one thing should immediately catch our attention: the strict equality check between two integers wrapped in the assert statement.

assert(poolBalance == balanceBefore);

Secureum has a pretty nice collection of pitfalls and best practices where the following item can be found:

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)

So the internal accounting checks thatpoolBalance == balanceBefore .
poolBalance is calculated only in thedepositToken function by adding the deposited amount to the old balance. And the balanceBefore is the current balance of the contract before the flash loan. So adding tokens to the contract’s balance by not using the deposit function will break the internal accounting and next time someone executes the flashLoan function the equality check will fail and finally we get an answer to the decade-old question: What happens when an unstoppable force meets an immovable object?

The solution is the following:
1. Transfer some DVT tokens from the attacker to the pool.

Challenge #2 — Naive receiver

There’s a lending pool NaiveReceiverLenderPooloffering quite expensive (1 ETH) flash loans of Ether, which has 1000 ETH in balance.
You also see that a user has deployed a contract FlashLoanReceiverwith 10 ETH in balance, capable of interacting with the lending pool and receiveing flash loans of ETH.
Drain all ETH funds from the user’s contract. Doing it in a single transaction is a big plus ;)

Examining the pool contract’s flashLoan function we can immediately see that the loanee’s address borrowerpassed as a parameter and the transaction can be initiated by anyone since there is no restriction on that in the function body. Combine this with the outrageous 1 ETH fee which is deducted from the borrowerand the solution is self-explanatory:
1. Create a contract that calls the pool’s flashLoan function passing FlashLoanReceiver ‘s address as the borrower.
2. Do it ten times and the borrower will run out of funds.

The NaiveReceiverAttacker:

Deploy and call the attack function:

Challenge #3 — Truster

More and more lending pools are offering flash loans. In this case, a new pool TrusterLenderPoolhas launched that is offering flash loans of DVT tokens for free.
Currently the pool has 1 million DVT tokens in balance. And you have nothing.
But don’t worry, you might be able to take them all from the pool. In a single transaction.

The lender pool’s flashLoan function takes four parameters: borrowAmount the amount of the flash loan, borrower the one whom the amount will be transferred, target an address to perform a function call into with a payload defined as the data parameter. From the function body, it turns out there is no pre-defined function that the loanee contract must implement to repay the loan. Rather the lending pool trusts the loanee contract to provide an arbitrary contract address whose arbitrary function will be blindly called by the lending pool with arbitrary parameters hoping that it does what is expected: repays the loan. Well no luck with us. How about passing in the ERC20’s approve function with the attacker’s address as the spenderand the total pool supply as the amountand thanks to the functionCall the pool will be the derived owner. And what about the loan itself? How it will be paid back? Well if there is no loan it should not be paid back.

The solution is the following:
1. Create a contract that calls the pool’s flashLoan function
2. Pass theamount as 0, borrower as the current contract, target as the DVT token address, data as the encoded approve function where the attacker is the spender and the pool’s balance is the amount
3. Transfer the approved allowance from the pool to the attacker.

The TrusterAttacker:

Deploy it, call the attack function and transfer the tokens:

Challenge #4 — Side entrance

A surprisingly simple lending pool SideEntranceLenderPoolallows anyone to deposit ETH, and withdraw it at any point in time.
This very simple lending pool has 1000 ETH in balance already, and is offering free flash loans using the deposited ETH to promote their system.
You must take all ETH from the lending pool.

To take a flash loan the loanee contract must implement the execute payable function which will be called by the pool sending the requested loan amount as the msg.value. Okay, let’s say we implemented the callback, called the pool’sflashLoanfunction the loan is sent and now it’s our turn to repay it…OR dodge somehow the following check :

require(address(this).balance >= balanceBefore, "Flash loan hasn't been paid back");

Wish there would be some side entrance besides paying the loan back to modify the lender pool’s balance… You guessed it right it is the deposit function.

Having this the receipt is easy:
1. Take a flash loan
2. Deposit back into the pool
3. Pass the balance check, withdraw the money and upon receive -ing, transfer it to the attacker’s address and live a happy life.
The SideEntranceAttacker:

Deploy and attack:

Challenge #5 — The rewarder

There’s a pool TheRewarderPooloffering rewards in tokens RewardTokenevery 5 days for those who deposit their DVT tokens into it.
Alice, Bob, Charlie and David have already deposited some DVT tokens, and have won their rewards!
You don’t have any DVT tokens. But in the upcoming round, you must claim most rewards for yourself.
Oh, by the way, rumours say a new pool FlashLoanerPoolhas just landed on mainnet. Isn’t it offering DVT tokens in flash loans?

Things are getting a bit complex from here, but the more complex is the logic and the infrastructure the more frequent is the likeliness of security vulnerabilities. And again, lots of hints in the description. Most likely we will need the flash loan so let’s implement that. Calling FlashLoanerPool ‘s flashLoan function will provide us the desired loan and receiveFlashLoan call back function must be implemented by our contract to repay the loan. Having that we have access now to 1 million DVT tokens for free even just for a timespan of a transaction.
We can only deposit DVT tokens to RewarderPool . The AccountingToken is an ERC20Snapshot token used to track individual deposited DVT balances by minting upon deposit and burning upon withdraw.
Let’s focus on the RewardToken since the goal is to claim the most in the next round. There is only one code block whereRewardTokenis minted and that is in the distributeRewards function:

if(rewards > 0 && !_hasRetrievedReward(msg.sender)) {
rewardToken.mint(msg.sender, rewards);
lastRewardTimestamps[msg.sender] = block.timestamp;
}

Let’s try to pass the statements: rewards is greater than zero if the caller has a positive balance of AccountingToken at a given snapshot in time. To record such a snapshot we have to pass:

if(isNewRewardsRound()) {
_recordSnapshot();
}

isNewRewardsRound evaluates true if at least 5 days passed since the last recorded snapshot.
_hasRetrievedReward will evaluate false (which we need because of the ! negation) if we never received a reward before which is true in our case. If these two statements pass RewardToken will be minted for the caller matching the amount of the rewards .
So all we have to do is hold a lot of money and pose for a quick photo, and they will believe we are rich.

It could be someone else’s money borrowed just for a second…
You guessed it by now.

The solution is the following:
1. Wait 5 days and take a flash loan of DVT from the FlashLoanerPool,
2. Deposit to the RewarderPool,
3. Call distributeRewards to get RewardToken,
4. Withdraw DVT
5. Send RewardToken to the attacker
6. Repay the loan.
The RewarderAttacker:

Deploy the attacker contract wait 5 days and execute the attack:

Challenge #6 — Selfie

A new cool lending pool SelfiePool has launched! It’s now offering flash loans of DVT tokens.
Wow, and it even includes a really fancy governance SimpleGovernance mechanism to control it.What could go wrong, right ?
You start with no DVT tokens in balance, and the pool has 1.5 million. Your objective: take them all.

Another pool with flash loans to be rekt. To take a loan our contract has to implement the pool’s receiveTokens callback function. The loaned token DamnValueableTokenSnapshot is an ERC20Snapshottoken introduced in the previous challenge. The pool also has a drainAllFunds function that transfers all funds to the address defined as a parameter. This one looks promising. The function’s onlyGovernance modifier only permits executions coming from the governance address which points to the SimpleGovernance contract. We can most likely find a way to execute transactions on behalf of SimpleGovernance because that’s what governance contracts for to create proposals, vote for them, and execute them. After examining the governance contract’s executeAction function we find exactly this execute functionality.

GovernanceAction storage actionToExecute = actions[actionId];
actionToExecute.executedAt = block.timestamp;

actionToExecute.receiver.functionCallWithValue(
actionToExecute.data,
actionToExecute.weiAmount
);

To reach this block we have to pass the _canBeExecuted function in the require statement that checks if the action was never executed before and the action was registered at least 2 days ago. Okay, how to register a GovernanceAction ? The queAction function does that for us. We have to pass two require statements here:
1. the receiver (the called contract) cannot be the governance itself, that is a pass since our target is the pool.
2. the caller must pass the _hasEnoughVotes check which we can if the caller address owns more than half of the supply of the governanceToken at the latest snapshot of the token. Yes, SimpleGovernance contract uses DamnValueableTokenSnapshot as a governance token and SelfiePool offers flash loans in the same token while usingSimpleGovernance as its governor.

And this is where the circle closes and the pool takes a selfie in the mirror revealing some unwanted details.

The solution is the following:
1. Take the max amount of flash loan from the pool.
2. Take a token snapshot and take over the governance.
3. Queue an action that drains all funds from the pool.
4. Repay the flash loan.
5. Advance two days in time and execute the action.

The SelfieAttacker:

Deploy it, execute theattack , read the actionId , advance 2 days in time and execute the action by the actionId.

Thanks for reading along continue with part two here.
Cheers and happy (responsible) hacking!

--

--

Balázs Kocsis

Full Stack Engineer | Smart Contract Security Enthusiast