Damn Vulnerable DeFi- Challenge# 1

In this post we will focus on solving the first challenge of Damn Vulnerbale Defi wargame designed to teach developer , security engineers about blockchain security.
All readers are expected to have backgrounds on blockchain and different components used in the Defi ecosystem like flashloans, ERC20 tokens, DEX,….etc.

Challenge Setup

First clone the repository

sakr@HacKeD0x90:~/study$ git clone https://github.com/OpenZeppelin/damn-vulnerable-defi
  Cloning into 'damn-vulnerable-defi'…
  remote: Enumerating objects: 76, done.
  remote: Counting objects: 100% (14/14), done.
  remote: Compressing objects: 100% (13/13), done.
  remote: Total 76 (delta 3), reused 5 (delta 1), pack-reused 62
  Unpacking objects: 100% (76/76), done.

Install dependencies using npm and node, the preferred version is node 12.20.0

sakr@HacKeD0x90:~/study/damn-vulnerable-defi$ npm install
npm WARN deprecated mkdirp@0.5.1: Legacy versions of mkdirp are no longer supported. Please update to mkdirp 1.x. (Note that the API surface has changed to use Promises in 1.x.)

> scrypt@6.0.3 preinstall /home/sakr/study/damn-vulnerable-defi/node_modules/ganache-core/node_modules/scrypt
> node node-scrypt-preinstall.js

Now let’s read the challenge description, it says that we have a lending pool offering 100000 DVT token, our goal is to stop the flashloan smart contract from working, and our initial attacker balance is 100.

Challenge Description

Now let’s map this to the source code of the challenges, first there is the smart contract of the DVT token, this is a key component in the game and will be used in most of the challenges.
we can see that we have a contract called DamnValuableToken which inherits from ERC20.sol, and therefore, all function available at ERC20.sol will be available here as well.
Next in the constructor it calls the mint function to provide the sender of the message 2^256 -1 amount of tokens.

DVT Token Implementation

Next we have the Flash loan provider implementation, typically flashloans consist of minimum two components, the flashloan provider which is a smart contract used provide loans to users in the form of Tokens without the need to provide any collateral, the catch is that you have to repay it in the same transaction. there are many use cases for flashloans, one of them is called arbitrage,
the second component is the receiver component, and this is the smart contract who is borrowing the tokens from the flashloan providers, do something with the tokens and then pay it back.
Let’s first view the important parts in the flashloan provider source code `UnstoppableLender.sol`
1. first we have an interface to communicate with the receiver smart contract(this interface is used to send him the tokens he borrowed)
2. in the constructor the address of the DVT token is provided.
3. there is a depositTokens function which is used to increase the balance of tokens in the smart contract.

4. we have a flashLoan function , this method should be called by the receiver contract whenever he wish to request a loan from the provider. the function takes note of the original balance before providing the loan (balanceBefore), this is very important because before the transaction end we need to ensure that our pool balance is still the same(the borrower paid the loan).
5. there is an assert statement that checks if the poolBalance is equal to the balance in the smart contract.
assert(poolBalance == balanceBefore);
as per the challenge this should always lead to true as in the deposit function, whenever a token is added to the pool the poolBalance is incremented with the amount added.
6. the flashloan provider will transfer the tokens to the msg.sender and then calls the receiver smart contract method called receiveTokens, in this function the receiver contract will do something with the token and then pay it back.

The receiveTokens function in the receiver smart contract looks like below. we can see it transfers the token back again to the msg.sender( which is the flashloan provider).

Now that we understand the smart contract let’s see the challenge setup, which should deploy the DVT token smart contract, deploy the flashloan provider smart contract and transfer 1M DVT token to it, then transfer 100 DVT token to an attacker address, and also it will deploy the receiver smart contract and request a loan by executing executeFlashLoan function which will then execute the flashLoan method on the flashloan provider smart contract.

Above code exist in test directory, you shouldn’t edit anything here as it’s the challenge setup.
next we can find the part where we should exploit, the test case checks if we can call the executeFlashLoan function from someuser address, if it’s successful then our mission failed. as our mission is to stop the smart contract from offering flashloan.

Let’s run the challenge using the command npm run unstoppable
we can see that the 2nd test fails as it was expecting a revert, while non was received.

An interesting line of code in the flashloan provider is
assert(poolBalance == balanceBefore);
assert is one way of error handling in solidity, like require and revert, however it’s used to make sure that the condition in the assert statement never happens, and if this happens, then something is seriously wrong with the application and it will stop working , so for security auditor whenever you see an assert statement it’s always interesting, because if you can cause the condition in the assert to become false you can cause the smart contract to stop working.
poolBalance variable is incremented everytime someone calls the depositTokens function, while balanceBefore is the balance of the tokens in the smart contract
uint256 balanceBefore = damnValuableToken.balanceOf(address(this));
which should be the same as whenever we add balance to the contract we increment the poolBalance. the developer didn’t take in the consideration that we can also add tokens to the smart contract using the transfer method in ERC20, if we remember the DVT token inherits from ERC20, and therefore all functions is available there.
so simply to exploit this contract we have to transfer the 100 DVT token from our attacker address to the flashloan provider address.
this can be done by writing the below code in the test file.

await this.token.transfer(this.pool.address, INITIAL_ATTACKER_BALANCE, {from: attacker});

Now , let’s try and run the challenge again to see if all of the test cases will pass.

Indeed, we were able to pass the challenge by causing a revert to anyone who will call the flashLoan function in flashloan provider smart contract.

Leave a Reply

Your email address will not be published. Required fields are marked *