forked from OpenZeppelin/damn-vulnerable-defi
-
Notifications
You must be signed in to change notification settings - Fork 938
/
UnstoppableVault.sol
134 lines (111 loc) · 4.45 KB
/
UnstoppableVault.sol
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
// 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();
}
}