Damn-vulnerable-defi-V4-solution(Unstoppable)
一、Unstoppable
// UnstoppableMonitor.sol
// SPDX-License-Identifier: MIT
// Damn Vulnerable DeFi v4 (https://damnvulnerabledefi.xyz)
pragma solidity =0.8.25;
import {IERC3156FlashBorrower} from "@openzeppelin/contracts/interfaces/IERC3156FlashBorrower.sol";
import {Owned} from "solmate/auth/Owned.sol";
import {UnstoppableVault, ERC20} from "../unstoppable/UnstoppableVault.sol";
/**
* @notice Permissioned contract for on-chain monitoring of the vault's flashloan feature.
*/
contract UnstoppableMonitor is Owned, IERC3156FlashBorrower {
UnstoppableVault private immutable vault;
error UnexpectedFlashLoan();
event FlashLoanStatus(bool success);
constructor(address _vault) Owned(msg.sender) {
vault = UnstoppableVault(_vault);
}
function onFlashLoan(address initiator, address token, uint256 amount, uint256 fee, bytes calldata)
external
returns (bytes32)
{
if (initiator != address(this) || msg.sender != address(vault) || token != address(vault.asset()) || fee != 0) {
revert UnexpectedFlashLoan();
}
ERC20(token).approve(address(vault), amount);
return keccak256("IERC3156FlashBorrower.onFlashLoan");
}
function checkFlashLoan(uint256 amount) external onlyOwner {
require(amount > 0);
address asset = address(vault.asset());
try vault.flashLoan(this, asset, amount, bytes("")) {
emit FlashLoanStatus(true);
} catch {
// Something bad happened
emit FlashLoanStatus(false);
// Pause the vault
vault.setPause(true);
// Transfer ownership to allow review & fixes
vault.transferOwnership(owner);
}
}
}
// UnstoppableVault.sol
// SPDX-License-Identifier: MIT
// Damn Vulnerable DeFi v4 (https://damnvulnerabledefi.xyz)
pragma solidity =0.8.25;
import {ReentrancyGuard} from "solady/utils/ReentrancyGuard.sol";
import {FixedPointMathLib} from "solmate/utils/FixedPointMathLib.sol";
import {Owned} from "solmate/auth/Owned.sol";
import {SafeTransferLib, ERC4626, ERC20} from "solmate/tokens/ERC4626.sol";
import {Pausable} from "@openzeppelin/contracts/utils/Pausable.sol";
import {IERC3156FlashBorrower, IERC3156FlashLender} from "@openzeppelin/contracts/interfaces/IERC3156.sol";
/**
* An ERC4626-compliant tokenized vault offering flashloans for a fee.
* An owner can pause the contract and execute arbitrary changes.
*/
contract UnstoppableVault is IERC3156FlashLender, ReentrancyGuard, Owned, ERC4626, Pausable {
using SafeTransferLib for ERC20;
using FixedPointMathLib for uint256;
uint256 public constant FEE_FACTOR = 0.05 ether;
uint64 public constant GRACE_PERIOD = 30 days;
uint64 public immutable end = uint64(block.timestamp) + GRACE_PERIOD;
address public feeRecipient;
error InvalidAmount(uint256 amount);
error InvalidBalance();
error CallbackFailed();
error UnsupportedCurrency();
event FeeRecipientUpdated(address indexed newFeeRecipient);
constructor(ERC20 _token, address _owner, address _feeRecipient)
ERC4626(_token, "Too Damn Valuable Token", "tDVT")
Owned(_owner)
{
feeRecipient = _feeRecipient;
emit FeeRecipientUpdated(_feeRecipient);
}
/**
* @inheritdoc IERC3156FlashLender
*/
function maxFlashLoan(address _token) public view nonReadReentrant returns (uint256) {
if (address(asset) != _token) {
return 0;
}
return totalAssets();
}
/**
* @inheritdoc IERC3156FlashLender
*/
function flashFee(address _token, uint256 _amount) public view returns (uint256 fee) {
if (address(asset) != _token) {
revert UnsupportedCurrency();
}
if (block.timestamp < end && _amount < maxFlashLoan(_token)) {
return 0;
} else {
return _amount.mulWadUp(FEE_FACTOR);
}
}
/**
* @inheritdoc ERC4626
*/
function totalAssets() public view override nonReadReentrant returns (uint256) {
return asset.balanceOf(address(this));
}
/**
* @inheritdoc IERC3156FlashLender
*/
function flashLoan(IERC3156FlashBorrower receiver, address _token, uint256 amount, bytes calldata data)
external
returns (bool)
{
if (amount == 0) revert InvalidAmount(0); // fail early
if (address(asset) != _token) revert UnsupportedCurrency(); // enforce ERC3156 requirement
uint256 balanceBefore = totalAssets();
if (convertToShares(totalSupply) != balanceBefore) revert InvalidBalance(); // enforce ERC4626 requirement
// transfer tokens out + execute callback on receiver
ERC20(_token).safeTransfer(address(receiver), amount);
// callback must return magic value, otherwise assume it failed
uint256 fee = flashFee(_token, amount);
if (
receiver.onFlashLoan(msg.sender, address(asset), amount, fee, data)
!= keccak256("IERC3156FlashBorrower.onFlashLoan")
) {
revert CallbackFailed();
}
// pull amount + fee from receiver, then pay the fee to the recipient
ERC20(_token).safeTransferFrom(address(receiver), address(this), amount + fee);
ERC20(_token).safeTransfer(feeRecipient, fee);
return true;
}
/**
* @inheritdoc ERC4626
*/
function beforeWithdraw(uint256 assets, uint256 shares) internal override nonReentrant {}
/**
* @inheritdoc ERC4626
*/
function afterDeposit(uint256 assets, uint256 shares) internal override nonReentrant whenNotPaused {}
function setFeeRecipient(address _feeRecipient) external onlyOwner {
if (_feeRecipient != address(this)) {
feeRecipient = _feeRecipient;
emit FeeRecipientUpdated(_feeRecipient);
}
}
// Allow owner to execute arbitrary changes when paused
function execute(address target, bytes memory data) external onlyOwner whenPaused {
(bool success,) = target.delegatecall(data);
require(success);
}
// Allow owner pausing/unpausing this contract
function setPause(bool flag) external onlyOwner {
if (flag) _pause();
else _unpause();
}
}
为了让 monitor 将 vault 的状态修改为 stop,需要在monitor 调用 flashLoan 的时候触发 revert,flashLoan 中要触发 revert,比较好触发的就是这行
if (convertToShares(totalSupply) != balanceBefore) revert InvalidBalance();这块代码有一些问题,这个检查试图确保Vault的资产余额与份额总供应量转换后的值一致,但因为它比较的是两种不同的单位(份额和资产数量),而且当有人直接向Vault转账token时(而不是通过deposit函数),资产余额会增加但份额不变,导致检查失败,从而使flashLoan功能被禁用。
修复之后
/**
* @inheritdoc IERC3156FlashLender
*/
function flashLoan(IERC3156FlashBorrower receiver, address _token, uint256 amount, bytes calldata data)
external
returns (bool)
{
if (amount == 0) revert InvalidAmount(0); // fail early
if (address(asset) != _token) revert UnsupportedCurrency(); // enforce ERC3156 requirement
uint256 balanceBefore = totalAssets();
// 注释了这里的检查
// if (convertToShares(totalSupply) != balanceBefore) revert InvalidBalance(); // enforce ERC4626 requirement
// transfer tokens out + execute callback on receiver
ERC20(_token).safeTransfer(address(receiver), amount);
// callback must return magic value, otherwise assume it failed
uint256 fee = flashFee(_token, amount);
if (
receiver.onFlashLoan(msg.sender, address(asset), amount, fee, data)
!= keccak256("IERC3156FlashBorrower.onFlashLoan")
) {
revert CallbackFailed();
}
// pull amount + fee from receiver, then pay the fee to the recipient
ERC20(_token).safeTransferFrom(address(receiver), address(this), amount + fee);
ERC20(_token).safeTransfer(feeRecipient, fee);
// 此处增加了借贷之前和之后的资产余额比较 确保Vault的资产余额没有减少
if (totalAssets() < balanceBefore) revert InvalidBalance();
return true;
} 当前页面是本站的「Google AMP」版。查看和发表评论请点击:完整版 »