Damn Vulnerable DeFi 第一关
刚开始学习web3,不知道怎么写好,希望有大佬看到能纠错
第一关是关于闪电贷的
先来看合约
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/security/ReentrancyGuard.sol";
interface IReceiver {
function receiveTokens(address tokenAddress, uint256 amount) external;
}
/**
* @title UnstoppableLender
* @author Damn Vulnerable DeFi (https://damnvulnerabledefi.xyz)
*/
contract UnstoppableLender is ReentrancyGuard {
IERC20 public immutable damnValuableToken;
uint256 public poolBalance;
constructor(address tokenAddress) {
require(tokenAddress != address(0), "Token address cannot be zero");
damnValuableToken = IERC20(tokenAddress);
}
function depositTokens(uint256 amount) external nonReentrant {
require(amount > 0, "Must deposit at least one token");
// Transfer token from sender. Sender must have first approved them.
damnValuableToken.transferFrom(msg.sender, address(this), amount);
poolBalance = poolBalance + amount;
}
function flashLoan(uint256 borrowAmount) external nonReentrant {
require(borrowAmount > 0, "Must borrow at least one token");
uint256 balanceBefore = damnValuableToken.balanceOf(address(this));
require(balanceBefore >= borrowAmount, "Not enough tokens in pool");
// Ensured by the protocol via the `depositTokens` function
assert(poolBalance == balanceBefore);
damnValuableToken.transfer(msg.sender, borrowAmount);
IReceiver(msg.sender).receiveTokens(address(damnValuableToken), borrowAmount);
uint256 balanceAfter = damnValuableToken.balanceOf(address(this));
require(balanceAfter >= balanceBefore, "Flash loan hasn't been paid back");
}
}
合约分析如下
1.首先声明合约
2.引入一个openzeppelin跟ERC20一些东西
3.定义一个接口为IReceiver,在solidity里面interface表示定义一个接口,然后在接口里面声明了一个函数,而且,这个函数有两个值就是参数,一个是address tokenaddress 跟 uint 256 amount ,我这里的理解是
address tokenaddress :就是币的地址
uint 256 amount:为数量
4.这里 contract UnstoppableLender is ReentrancyGuard,其中ReentrancyGuard是sol里面的一个库,这样写可以用来防止入重攻击
5.
IERC20 public immutable damnValuableToken;
这一行声明了一个 IERC20 类型,公共状态变量,起了一个名字叫 damnValuableToken
immutable 状态变量的修饰符,它用于标记状态变量的值在合约创建后将不会再被修改
uint256 public poolBalance;
这一行声明了一个 uint256 类型的公共状态变量,叫 poolBalance
6.
1)、 depositTokens 函数,主要用于用户向合约存款 ERC20 代币。使用 nonReentrant 修饰符可以确保在函数执行期间不能再次调用相同的函数,避免重入攻击
2)、要求用户存款的数量必须大于零,否则函数将失败并抛出错误消息
3)、借钱的人从被借钱的账户借到他自己的地址
4)、poolBalance用于记录当前合约中存储的所有 ERC20 代币的总数量,这行代码是在更新合约的状态变量,反映合约中的资金池的余额
7.flashLoan 函数允许用户借入 ERC20 代币。它要求借款数量大于零,检查池中是否有足够的币,然后通过 transfer 函数将借款转移到调用者地址。接着,调用 IReceiver 接口的 receiveTokens 函数,将借款发送给调用者。最后,检查借款是否在函数执行结束后归还,以确保它是一个“闪电贷”
判断闪电贷:
Solidity 中,并没有专门的关键字或标记来直接表示闪电贷。闪电贷是一种模式,而不是一种在语法层面直接可识别的结构。
通常情况下,通过检查函数的逻辑和操作,来推断是否涉及到了闪电贷。以下是一些可能存在闪电贷操作的特征:
- 借款和还款在同一笔交易中: 闪电贷通常涉及在同一笔交易中进行借款和还款操作。在你的合约中,flashLoan 函数中的 damnValuableToken.transfer 和 IReceiver(msg.sender).receiveTokens 就是在同一笔交易中完成的。
- 接口调用或外部合约调用: 闪电贷常涉及调用外部合约或接口,例如 IReceiver(msg.sender).receiveTokens 就是调用了外部接口。
- 必要的前提条件: 闪电贷通常需要确保某些前提条件满足,比如足够的余额,以便在同一笔交易中进行借款和还款。
我的理解是这样:比如说有a,b,c,d这里a是借款账户,然后b,c,d都是被借款账户如果a同时对b,c,d进行借款那是不是就表示在同一笔交易中,
所以通过分析这个合约的关键是flashLoan函数,而整个过程中会通过require(balanceBefore >= borrowAmount, "Not enough tokens in pool")确保有足够余额供以借出并通过target.functionCall(data);调用指定地址的某个函数,最后通过require(balanceAfter >= balanceBefore, "Flash loan hasn't been paid back");验证闪电贷交易的安全
- require(balanceBefore >= borrowAmount, "Not enough tokens in pool"); 是为了保证有足够的钱
- require(balanceAfter >= balanceBefore, "Flash loan hasn't been paid back");还款的钱要大于之前借出去的钱
- require(borrowAmount > 0, "Must borrow at least one token"); 就是借款金额要大于0
- assert(poolBalance == balanceBefore); 我理解的是用来确保借的钱没问题
接下来是被借款的ReceiverUnstoppable合约
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "../unstoppable/UnstoppableLender.sol";
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
/**
* @title ReceiverUnstoppable
* @author Damn Vulnerable DeFi (https://damnvulnerabledefi.xyz)
*/
contract ReceiverUnstoppable {
UnstoppableLender private immutable pool;
address private immutable owner;
constructor(address poolAddress) {
pool = UnstoppableLender(poolAddress);
owner = msg.sender;
}
// Pool will call this function during the flash loan
function receiveTokens(address tokenAddress, uint256 amount) external {
require(msg.sender == address(pool), "Sender must be pool");
// Return all tokens to the pool
require(IERC20(tokenAddress).transfer(msg.sender, amount), "Transfer of tokens failed");
}
function executeFlashLoan(uint256 amount) external {
require(msg.sender == owner, "Only owner can execute flash loan");
pool.flashLoan(amount);
}
}
该合约定义了receiveTokens函数以变将通证归还给闪电贷合约,而executeFlashLoan只允许owner自身发起调用以展开闪电贷交易。
require(msg.sender == address(pool), "Sender must be pool"); 这里就表示msg.sender 是指调用合约函数的地址,address(pool) 将合约变量 pool 转换为一个 address 类型的值, UnstoppableLender 合约的地址。
成功实施攻击
在flashLoan函数里有一句assert(poolBalance == balanceBefore);所以我们只要让他们两不相等就行。

这道题算是理解了闪电贷的原理吧,后面再多看看。如果有错误希望大佬们指正

浙公网安备 33010602011771号