├── .gitignore ├── foundry.toml ├── readme.txt ├── src ├── AaveV2FlashLoan.sol ├── AaveV3FlashLoan.sol ├── BigFlashLoan.sol ├── RepaimentStorage.sol ├── UniV2FlashSwap.sol └── UniV3FlashSwap.sol └── test ├── AaveV2FlashLoan.t.sol ├── AaveV3FlashLoan.t.sol ├── BigFlashLoan.t.sol ├── Hacker.t.sol ├── SharedSetup.sol ├── UniV2FlashSwap.t.sol └── UniV3FlashSwap.t.sol /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiler files 2 | cache/ 3 | out/ 4 | script/ 5 | lib/ 6 | foundry.toml 7 | .DS_Store 8 | 9 | # Ignores development broadcast logs 10 | !/broadcast 11 | /broadcast/* 12 | /broadcast/*/31337/ 13 | -------------------------------------------------------------------------------- /foundry.toml: -------------------------------------------------------------------------------- 1 | [profile.default] 2 | src = 'src' 3 | out = 'out' 4 | libs = ['lib'] 5 | 6 | # See more config options https://github.com/foundry-rs/foundry/tree/master/config -------------------------------------------------------------------------------- /readme.txt: -------------------------------------------------------------------------------- 1 | FLASHLOANS TOOLBOX 2 | 3 | Suite of smart contracts allowing easy flashLoans on UniswapV2, UniswapV3, AAVEV2 and AAVEV3. 4 | Make your contracts inherit one of those, call flashLoan() (AAVE) or flashSwap (Uni) and override the "onFlashLoan" functions with your own logic. 5 | Contains utils functions to estimate the max amount that can be flashloans as well as the fee that will be due. 6 | 7 | The test folder is usable with foundry. The sandard library is enough to test as all contracts have been flattened for simplicity. 8 | All tests are meant to be run on ETH mainnet fork except: 9 | 10 | UniV2FlashSwap.t.sol: meant to be run on BSC mainnet fork 11 | AaveV3FlashLoan.t.sol: meant to be run on AVAX mainnet fork 12 | 13 | The test Hacker.t.sol show how you can use the BigFlashLoan contract on a Honeypot contract. 14 | 15 | List of contracts: 16 | 17 | - UniV3FlashSwap: 18 | 19 | FlashSwap multiple assets accross multiple pools with the "flashSwap" function. 20 | Write your own logic in the "onUniV3FlashSwap" function. 21 | Repayment of the loan is automatic and managed by the "repayLoans" function. 22 | Override "initializeKnownPools" if you want to use more specific pools than the preset. 23 | Fee differs according to the UNIV3 pool (0.1% -> 1%) 24 | 25 | - UniV2FlashSwap: 26 | 27 | FlashSwap multiple assets accross multiple pairs with the "flashSwap" function. 28 | Write your own logic in the "onUniV2FlashSwap" function. 29 | Repayment of the loan is automatic and managed by the "repayLoans" function. 30 | Override "initializeKnownPools" if you want to use more specific pools than the preset. 31 | Fee: 0,3% 32 | 33 | - AaveV2FlashLoan: 34 | 35 | FlashLoan multiple assets accross multiple reserves of the AAVE V2 protocol. 36 | Write your own logic in the "onAaveV2FlashLoan" function. 37 | Repayment of the loan is automatic and required ERC20 approvals done in the "executeOperation" function. 38 | Fee depends on the reserve. By default: 0.09% (could be less) 39 | 40 | 41 | - AaveV3FlashLoan: 42 | 43 | FlashLoan multiple assets accross multiple reserves of the AAVE V2 protocol. 44 | Write your own logic in the "onAaveV3FlashLoan" function. 45 | Repayment of the loan is automatic and required ERC20 approvals done in the "executeOperation" function. 46 | Fee depends on the reserve. By default: 0.09% (could be less) 47 | 48 | - BigFlashLoan: 49 | 50 | Designed for Ethereum mainnet. 51 | Combine the abilities of AaveV2FlashLoan and UniV3FlashSwap. 52 | Use it if the reserves of AAVE V2 are not enough for your flashloan. 53 | Drain the liquidity of both AAVE V2 reserves and UNIV3 pools for multiple assets. 54 | Write your own logic in the "onflashLoan" function 55 | 56 | contact: @_supercycled 57 | -------------------------------------------------------------------------------- /src/AaveV2FlashLoan.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | // OpenZeppelin Contracts (last updated v4.6.0) (token/ERC20/IERC20.sol) 6 | 7 | /** 8 | * @dev Interface of the ERC20 standard as defined in the EIP. 9 | */ 10 | interface IERC20 { 11 | /** 12 | * @dev Emitted when `value` tokens are moved from one account (`from`) to 13 | * another (`to`). 14 | * 15 | * Note that `value` may be zero. 16 | */ 17 | event Transfer(address indexed from, address indexed to, uint256 value); 18 | 19 | /** 20 | * @dev Emitted when the allowance of a `spender` for an `owner` is set by 21 | * a call to {approve}. `value` is the new allowance. 22 | */ 23 | event Approval(address indexed owner, address indexed spender, uint256 value); 24 | 25 | /** 26 | * @dev Returns the amount of tokens in existence. 27 | */ 28 | function totalSupply() external view returns (uint256); 29 | 30 | /** 31 | * @dev Returns the amount of tokens owned by `account`. 32 | */ 33 | function balanceOf(address account) external view returns (uint256); 34 | 35 | /** 36 | * @dev Moves `amount` tokens from the caller's account to `to`. 37 | * 38 | * Returns a boolean value indicating whether the operation succeeded. 39 | * 40 | * Emits a {Transfer} event. 41 | */ 42 | function transfer(address to, uint256 amount) external returns (bool); 43 | 44 | /** 45 | * @dev Returns the remaining number of tokens that `spender` will be 46 | * allowed to spend on behalf of `owner` through {transferFrom}. This is 47 | * zero by default. 48 | * 49 | * This value changes when {approve} or {transferFrom} are called. 50 | */ 51 | function allowance(address owner, address spender) external view returns (uint256); 52 | 53 | /** 54 | * @dev Sets `amount` as the allowance of `spender` over the caller's tokens. 55 | * 56 | * Returns a boolean value indicating whether the operation succeeded. 57 | * 58 | * IMPORTANT: Beware that changing an allowance with this method brings the risk 59 | * that someone may use both the old and the new allowance by unfortunate 60 | * transaction ordering. One possible solution to mitigate this race 61 | * condition is to first reduce the spender's allowance to 0 and set the 62 | * desired value afterwards: 63 | * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729 64 | * 65 | * Emits an {Approval} event. 66 | */ 67 | function approve(address spender, uint256 amount) external returns (bool); 68 | 69 | /** 70 | * @dev Moves `amount` tokens from `from` to `to` using the 71 | * allowance mechanism. `amount` is then deducted from the caller's 72 | * allowance. 73 | * 74 | * Returns a boolean value indicating whether the operation succeeded. 75 | * 76 | * Emits a {Transfer} event. 77 | */ 78 | function transferFrom( 79 | address from, 80 | address to, 81 | uint256 amount 82 | ) external returns (bool); 83 | } 84 | 85 | library DataTypes { 86 | // refer to the whitepaper, section 1.1 basic concepts for a formal description of these properties. 87 | struct ReserveData { 88 | //stores the reserve configuration 89 | ReserveConfigurationMap configuration; 90 | //the liquidity index. Expressed in ray 91 | uint128 liquidityIndex; 92 | //variable borrow index. Expressed in ray 93 | uint128 variableBorrowIndex; 94 | //the current supply rate. Expressed in ray 95 | uint128 currentLiquidityRate; 96 | //the current variable borrow rate. Expressed in ray 97 | uint128 currentVariableBorrowRate; 98 | //the current stable borrow rate. Expressed in ray 99 | uint128 currentStableBorrowRate; 100 | uint40 lastUpdateTimestamp; 101 | //tokens addresses 102 | address aTokenAddress; 103 | address stableDebtTokenAddress; 104 | address variableDebtTokenAddress; 105 | //address of the interest rate strategy 106 | address interestRateStrategyAddress; 107 | //the id of the reserve. Represents the position in the list of the active reserves 108 | uint8 id; 109 | } 110 | 111 | struct ReserveConfigurationMap { 112 | //bit 0-15: LTV 113 | //bit 16-31: Liq. threshold 114 | //bit 32-47: Liq. bonus 115 | //bit 48-55: Decimals 116 | //bit 56: Reserve is active 117 | //bit 57: reserve is frozen 118 | //bit 58: borrowing is enabled 119 | //bit 59: stable rate borrowing enabled 120 | //bit 60-63: reserved 121 | //bit 64-79: reserve factor 122 | uint256 data; 123 | } 124 | 125 | struct UserConfigurationMap { 126 | uint256 data; 127 | } 128 | 129 | enum InterestRateMode {NONE, STABLE, VARIABLE} 130 | } 131 | 132 | // OpenZeppelin Contracts (last updated v4.7.0) (token/ERC20/utils/SafeERC20.sol) 133 | 134 | // OpenZeppelin Contracts v4.4.1 (token/ERC20/extensions/draft-IERC20Permit.sol) 135 | 136 | /** 137 | * @dev Interface of the ERC20 Permit extension allowing approvals to be made via signatures, as defined in 138 | * https://eips.ethereum.org/EIPS/eip-2612[EIP-2612]. 139 | * 140 | * Adds the {permit} method, which can be used to change an account's ERC20 allowance (see {IERC20-allowance}) by 141 | * presenting a message signed by the account. By not relying on {IERC20-approve}, the token holder account doesn't 142 | * need to send a transaction, and thus is not required to hold Ether at all. 143 | */ 144 | interface IERC20Permit { 145 | /** 146 | * @dev Sets `value` as the allowance of `spender` over ``owner``'s tokens, 147 | * given ``owner``'s signed approval. 148 | * 149 | * IMPORTANT: The same issues {IERC20-approve} has related to transaction 150 | * ordering also apply here. 151 | * 152 | * Emits an {Approval} event. 153 | * 154 | * Requirements: 155 | * 156 | * - `spender` cannot be the zero address. 157 | * - `deadline` must be a timestamp in the future. 158 | * - `v`, `r` and `s` must be a valid `secp256k1` signature from `owner` 159 | * over the EIP712-formatted function arguments. 160 | * - the signature must use ``owner``'s current nonce (see {nonces}). 161 | * 162 | * For more information on the signature format, see the 163 | * https://eips.ethereum.org/EIPS/eip-2612#specification[relevant EIP 164 | * section]. 165 | */ 166 | function permit( 167 | address owner, 168 | address spender, 169 | uint256 value, 170 | uint256 deadline, 171 | uint8 v, 172 | bytes32 r, 173 | bytes32 s 174 | ) external; 175 | 176 | /** 177 | * @dev Returns the current nonce for `owner`. This value must be 178 | * included whenever a signature is generated for {permit}. 179 | * 180 | * Every successful call to {permit} increases ``owner``'s nonce by one. This 181 | * prevents a signature from being used multiple times. 182 | */ 183 | function nonces(address owner) external view returns (uint256); 184 | 185 | /** 186 | * @dev Returns the domain separator used in the encoding of the signature for {permit}, as defined by {EIP712}. 187 | */ 188 | // solhint-disable-next-line func-name-mixedcase 189 | function DOMAIN_SEPARATOR() external view returns (bytes32); 190 | } 191 | 192 | // OpenZeppelin Contracts (last updated v4.7.0) (utils/Address.sol) 193 | 194 | /** 195 | * @dev Collection of functions related to the address type 196 | */ 197 | library Address { 198 | /** 199 | * @dev Returns true if `account` is a contract. 200 | * 201 | * [IMPORTANT] 202 | * ==== 203 | * It is unsafe to assume that an address for which this function returns 204 | * false is an externally-owned account (EOA) and not a contract. 205 | * 206 | * Among others, `isContract` will return false for the following 207 | * types of addresses: 208 | * 209 | * - an externally-owned account 210 | * - a contract in construction 211 | * - an address where a contract will be created 212 | * - an address where a contract lived, but was destroyed 213 | * ==== 214 | * 215 | * [IMPORTANT] 216 | * ==== 217 | * You shouldn't rely on `isContract` to protect against flash loan attacks! 218 | * 219 | * Preventing calls from contracts is highly discouraged. It breaks composability, breaks support for smart wallets 220 | * like Gnosis Safe, and does not provide security since it can be circumvented by calling from a contract 221 | * constructor. 222 | * ==== 223 | */ 224 | function isContract(address account) internal view returns (bool) { 225 | // This method relies on extcodesize/address.code.length, which returns 0 226 | // for contracts in construction, since the code is only stored at the end 227 | // of the constructor execution. 228 | 229 | return account.code.length > 0; 230 | } 231 | 232 | /** 233 | * @dev Replacement for Solidity's `transfer`: sends `amount` wei to 234 | * `recipient`, forwarding all available gas and reverting on errors. 235 | * 236 | * https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost 237 | * of certain opcodes, possibly making contracts go over the 2300 gas limit 238 | * imposed by `transfer`, making them unable to receive funds via 239 | * `transfer`. {sendValue} removes this limitation. 240 | * 241 | * https://diligence.consensys.net/posts/2019/09/stop-using-soliditys-transfer-now/[Learn more]. 242 | * 243 | * IMPORTANT: because control is transferred to `recipient`, care must be 244 | * taken to not create reentrancy vulnerabilities. Consider using 245 | * {ReentrancyGuard} or the 246 | * https://solidity.readthedocs.io/en/v0.5.11/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern]. 247 | */ 248 | function sendValue(address payable recipient, uint256 amount) internal { 249 | require(address(this).balance >= amount, "Address: insufficient balance"); 250 | 251 | (bool success, ) = recipient.call{value: amount}(""); 252 | require(success, "Address: unable to send value, recipient may have reverted"); 253 | } 254 | 255 | /** 256 | * @dev Performs a Solidity function call using a low level `call`. A 257 | * plain `call` is an unsafe replacement for a function call: use this 258 | * function instead. 259 | * 260 | * If `target` reverts with a revert reason, it is bubbled up by this 261 | * function (like regular Solidity function calls). 262 | * 263 | * Returns the raw returned data. To convert to the expected return value, 264 | * use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`]. 265 | * 266 | * Requirements: 267 | * 268 | * - `target` must be a contract. 269 | * - calling `target` with `data` must not revert. 270 | * 271 | * _Available since v3.1._ 272 | */ 273 | function functionCall(address target, bytes memory data) internal returns (bytes memory) { 274 | return functionCallWithValue(target, data, 0, "Address: low-level call failed"); 275 | } 276 | 277 | /** 278 | * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], but with 279 | * `errorMessage` as a fallback revert reason when `target` reverts. 280 | * 281 | * _Available since v3.1._ 282 | */ 283 | function functionCall( 284 | address target, 285 | bytes memory data, 286 | string memory errorMessage 287 | ) internal returns (bytes memory) { 288 | return functionCallWithValue(target, data, 0, errorMessage); 289 | } 290 | 291 | /** 292 | * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], 293 | * but also transferring `value` wei to `target`. 294 | * 295 | * Requirements: 296 | * 297 | * - the calling contract must have an ETH balance of at least `value`. 298 | * - the called Solidity function must be `payable`. 299 | * 300 | * _Available since v3.1._ 301 | */ 302 | function functionCallWithValue( 303 | address target, 304 | bytes memory data, 305 | uint256 value 306 | ) internal returns (bytes memory) { 307 | return functionCallWithValue(target, data, value, "Address: low-level call with value failed"); 308 | } 309 | 310 | /** 311 | * @dev Same as {xref-Address-functionCallWithValue-address-bytes-uint256-}[`functionCallWithValue`], but 312 | * with `errorMessage` as a fallback revert reason when `target` reverts. 313 | * 314 | * _Available since v3.1._ 315 | */ 316 | function functionCallWithValue( 317 | address target, 318 | bytes memory data, 319 | uint256 value, 320 | string memory errorMessage 321 | ) internal returns (bytes memory) { 322 | require(address(this).balance >= value, "Address: insufficient balance for call"); 323 | (bool success, bytes memory returndata) = target.call{value: value}(data); 324 | return verifyCallResultFromTarget(target, success, returndata, errorMessage); 325 | } 326 | 327 | /** 328 | * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], 329 | * but performing a static call. 330 | * 331 | * _Available since v3.3._ 332 | */ 333 | function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) { 334 | return functionStaticCall(target, data, "Address: low-level static call failed"); 335 | } 336 | 337 | /** 338 | * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`], 339 | * but performing a static call. 340 | * 341 | * _Available since v3.3._ 342 | */ 343 | function functionStaticCall( 344 | address target, 345 | bytes memory data, 346 | string memory errorMessage 347 | ) internal view returns (bytes memory) { 348 | (bool success, bytes memory returndata) = target.staticcall(data); 349 | return verifyCallResultFromTarget(target, success, returndata, errorMessage); 350 | } 351 | 352 | /** 353 | * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], 354 | * but performing a delegate call. 355 | * 356 | * _Available since v3.4._ 357 | */ 358 | function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) { 359 | return functionDelegateCall(target, data, "Address: low-level delegate call failed"); 360 | } 361 | 362 | /** 363 | * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`], 364 | * but performing a delegate call. 365 | * 366 | * _Available since v3.4._ 367 | */ 368 | function functionDelegateCall( 369 | address target, 370 | bytes memory data, 371 | string memory errorMessage 372 | ) internal returns (bytes memory) { 373 | (bool success, bytes memory returndata) = target.delegatecall(data); 374 | return verifyCallResultFromTarget(target, success, returndata, errorMessage); 375 | } 376 | 377 | /** 378 | * @dev Tool to verify that a low level call to smart-contract was successful, and revert (either by bubbling 379 | * the revert reason or using the provided one) in case of unsuccessful call or if target was not a contract. 380 | * 381 | * _Available since v4.8._ 382 | */ 383 | function verifyCallResultFromTarget( 384 | address target, 385 | bool success, 386 | bytes memory returndata, 387 | string memory errorMessage 388 | ) internal view returns (bytes memory) { 389 | if (success) { 390 | if (returndata.length == 0) { 391 | // only check isContract if the call was successful and the return data is empty 392 | // otherwise we already know that it was a contract 393 | require(isContract(target), "Address: call to non-contract"); 394 | } 395 | return returndata; 396 | } else { 397 | _revert(returndata, errorMessage); 398 | } 399 | } 400 | 401 | /** 402 | * @dev Tool to verify that a low level call was successful, and revert if it wasn't, either by bubbling the 403 | * revert reason or using the provided one. 404 | * 405 | * _Available since v4.3._ 406 | */ 407 | function verifyCallResult( 408 | bool success, 409 | bytes memory returndata, 410 | string memory errorMessage 411 | ) internal pure returns (bytes memory) { 412 | if (success) { 413 | return returndata; 414 | } else { 415 | _revert(returndata, errorMessage); 416 | } 417 | } 418 | 419 | function _revert(bytes memory returndata, string memory errorMessage) private pure { 420 | // Look for revert reason and bubble it up if present 421 | if (returndata.length > 0) { 422 | // The easiest way to bubble the revert reason is using memory via assembly 423 | /// @solidity memory-safe-assembly 424 | assembly { 425 | let returndata_size := mload(returndata) 426 | revert(add(32, returndata), returndata_size) 427 | } 428 | } else { 429 | revert(errorMessage); 430 | } 431 | } 432 | } 433 | 434 | /** 435 | * @title SafeERC20 436 | * @dev Wrappers around ERC20 operations that throw on failure (when the token 437 | * contract returns false). Tokens that return no value (and instead revert or 438 | * throw on failure) are also supported, non-reverting calls are assumed to be 439 | * successful. 440 | * To use this library you can add a `using SafeERC20 for IERC20;` statement to your contract, 441 | * which allows you to call the safe operations as `token.safeTransfer(...)`, etc. 442 | */ 443 | library SafeERC20 { 444 | using Address for address; 445 | 446 | function safeTransfer( 447 | IERC20 token, 448 | address to, 449 | uint256 value 450 | ) internal { 451 | _callOptionalReturn(token, abi.encodeWithSelector(token.transfer.selector, to, value)); 452 | } 453 | 454 | function safeTransferFrom( 455 | IERC20 token, 456 | address from, 457 | address to, 458 | uint256 value 459 | ) internal { 460 | _callOptionalReturn(token, abi.encodeWithSelector(token.transferFrom.selector, from, to, value)); 461 | } 462 | 463 | /** 464 | * @dev Deprecated. This function has issues similar to the ones found in 465 | * {IERC20-approve}, and its usage is discouraged. 466 | * 467 | * Whenever possible, use {safeIncreaseAllowance} and 468 | * {safeDecreaseAllowance} instead. 469 | */ 470 | function safeApprove( 471 | IERC20 token, 472 | address spender, 473 | uint256 value 474 | ) internal { 475 | // safeApprove should only be called when setting an initial allowance, 476 | // or when resetting it to zero. To increase and decrease it, use 477 | // 'safeIncreaseAllowance' and 'safeDecreaseAllowance' 478 | require( 479 | (value == 0) || (token.allowance(address(this), spender) == 0), 480 | "SafeERC20: approve from non-zero to non-zero allowance" 481 | ); 482 | _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, value)); 483 | } 484 | 485 | function safeIncreaseAllowance( 486 | IERC20 token, 487 | address spender, 488 | uint256 value 489 | ) internal { 490 | uint256 newAllowance = token.allowance(address(this), spender) + value; 491 | _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance)); 492 | } 493 | 494 | function safeDecreaseAllowance( 495 | IERC20 token, 496 | address spender, 497 | uint256 value 498 | ) internal { 499 | unchecked { 500 | uint256 oldAllowance = token.allowance(address(this), spender); 501 | require(oldAllowance >= value, "SafeERC20: decreased allowance below zero"); 502 | uint256 newAllowance = oldAllowance - value; 503 | _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance)); 504 | } 505 | } 506 | 507 | function safePermit( 508 | IERC20Permit token, 509 | address owner, 510 | address spender, 511 | uint256 value, 512 | uint256 deadline, 513 | uint8 v, 514 | bytes32 r, 515 | bytes32 s 516 | ) internal { 517 | uint256 nonceBefore = token.nonces(owner); 518 | token.permit(owner, spender, value, deadline, v, r, s); 519 | uint256 nonceAfter = token.nonces(owner); 520 | require(nonceAfter == nonceBefore + 1, "SafeERC20: permit did not succeed"); 521 | } 522 | 523 | /** 524 | * @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement 525 | * on the return value: the return value is optional (but if data is returned, it must not be false). 526 | * @param token The token targeted by the call. 527 | * @param data The call data (encoded using abi.encode or one of its variants). 528 | */ 529 | function _callOptionalReturn(IERC20 token, bytes memory data) private { 530 | // We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since 531 | // we're implementing it ourselves. We use {Address.functionCall} to perform this call, which verifies that 532 | // the target address contains contract code and also asserts for success in the low-level call. 533 | 534 | bytes memory returndata = address(token).functionCall(data, "SafeERC20: low-level call failed"); 535 | if (returndata.length > 0) { 536 | // Return data is optional 537 | require(abi.decode(returndata, (bool)), "SafeERC20: ERC20 operation did not succeed"); 538 | } 539 | } 540 | } 541 | 542 | 543 | contract RepaimentStorage { 544 | 545 | // loans storage. Erased after each flashSwap call with repayLoans() 546 | mapping(address => Loan[]) loans; 547 | mapping(address => uint256) totalBorrowPerToken; 548 | address[] public borrowedTokens; 549 | 550 | struct Loan { 551 | address pool; 552 | address token; 553 | uint256 amount; 554 | uint256 fee; 555 | } 556 | 557 | // use this function if you want to du multiple flashloans 558 | function cleanupRepaimentStorage() internal { 559 | 560 | if (borrowedTokens.length > 0) { 561 | 562 | for (uint256 i = 0; i < borrowedTokens.length; i++) { 563 | 564 | address token = borrowedTokens[i]; 565 | delete totalBorrowPerToken[token]; 566 | delete loans[token]; 567 | } 568 | } 569 | delete borrowedTokens; 570 | } 571 | } 572 | 573 | 574 | 575 | 576 | interface ILendingPool { 577 | 578 | function flashLoan( 579 | address receiverAddress, 580 | address[] calldata assets, 581 | uint256[] calldata amounts, 582 | uint256[] calldata modes, 583 | address onBehalfOf, 584 | bytes calldata params, 585 | uint16 referralCode 586 | ) external; 587 | 588 | function getReserveData(address asset) external view returns (DataTypes.ReserveData memory); 589 | function FLASHLOAN_PREMIUM_TOTAL() external view returns (uint256); 590 | } 591 | 592 | abstract contract AaveV2FlashLoan is RepaimentStorage{ 593 | using SafeERC20 for IERC20; 594 | 595 | ILendingPool private constant LENDINGPOOL = ILendingPool(0x7d2768dE32b0b80b7a3454c06BdAc94A69DDc7A9); 596 | 597 | // callback function for flashLoan() 598 | function executeOperation( 599 | address[] calldata assets, 600 | uint256[] calldata amounts, 601 | uint256[] calldata premiums, 602 | address initiator, 603 | bytes calldata params 604 | ) external returns (bool){ 605 | 606 | require(msg.sender == address(LENDINGPOOL), "AaveV2FlashLoan: unauthorized POOL"); 607 | require(initiator == address(this), "AaveV2FlashLoan: unothorized initiator"); 608 | 609 | for (uint256 i= 0; i< assets.length; ++i){ 610 | IERC20(assets[i]).safeApprove(address(LENDINGPOOL), amounts[i] + premiums[i]); 611 | if (totalBorrowPerToken[assets[i]] == 0) borrowedTokens.push(assets[i]); 612 | totalBorrowPerToken[assets[i]] += (amounts[i] + premiums[i]); 613 | } 614 | onAaveV2FlashLoan(); 615 | return true; 616 | } 617 | 618 | function verifyFlashLoan(address token, uint256 amount) private view { 619 | uint256 max = maxFlashLoan(token); 620 | require(max > 0, "AaveV2FlashLoan: unsupported token"); 621 | require(max<= amount, "AaveV2FlashLoan: amount beyond maxFlashLoan"); 622 | } 623 | 624 | function maxFlashLoan(address token) public view virtual returns(uint256){ 625 | address aTokenAddress = LENDINGPOOL.getReserveData(token).aTokenAddress; 626 | if (aTokenAddress == address(0)) return 0; 627 | return IERC20(token).balanceOf(aTokenAddress); 628 | } 629 | 630 | function estimateFlashLoanFee(address token, uint256 amount) public view virtual returns(uint256 feesEstimated){ 631 | verifyFlashLoan(token, amount); 632 | return amount * LENDINGPOOL.FLASHLOAN_PREMIUM_TOTAL() / 1e4; 633 | } 634 | 635 | function flashLoan(address token, uint256 amount) internal { 636 | 637 | address[] memory tokens = new address[](1); 638 | uint256[] memory amounts = new uint256[](1); 639 | tokens[0] = token; 640 | amounts[0] = amount; 641 | flashLoan(tokens, amounts); 642 | } 643 | 644 | function flashLoan(address[] memory tokens , uint256[] memory amounts) internal virtual{ 645 | for (uint256 i = 0; i < amounts.length; ++i){ 646 | verifyFlashLoan(tokens[i], amounts[i]); 647 | } 648 | 649 | uint256[] memory modes = new uint256[](amounts.length); 650 | LENDINGPOOL.flashLoan(address(this), tokens, amounts, modes, address(this), "", 0); 651 | } 652 | 653 | // should be overriden with your own logic 654 | function onAaveV2FlashLoan() virtual internal; 655 | } 656 | -------------------------------------------------------------------------------- /src/RepaimentStorage.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | contract RepaimentStorage { 6 | 7 | // loans storage. Erased after each flashSwap call with repayLoans() 8 | mapping(address => Loan[]) loans; 9 | mapping(address => uint256) totalBorrowPerToken; 10 | address[] public borrowedTokens; 11 | 12 | struct Loan { 13 | address pool; 14 | address token; 15 | uint256 amount; 16 | uint256 fee; 17 | } 18 | 19 | // use this function if you want to du multiple flashloans 20 | function cleanupRepaimentStorage() internal { 21 | 22 | if (borrowedTokens.length > 0) { 23 | 24 | for (uint256 i = 0; i < borrowedTokens.length; i++) { 25 | 26 | address token = borrowedTokens[i]; 27 | delete totalBorrowPerToken[token]; 28 | delete loans[token]; 29 | } 30 | } 31 | delete borrowedTokens; 32 | } 33 | } 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /src/UniV2FlashSwap.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | interface IUniswapV2Callee { 6 | function uniswapV2Call(address sender, uint amount0, uint amount1, bytes calldata data) external; 7 | } 8 | 9 | interface IUniswapV2Pair { 10 | event Approval(address indexed owner, address indexed spender, uint value); 11 | event Transfer(address indexed from, address indexed to, uint value); 12 | 13 | function name() external pure returns (string memory); 14 | function symbol() external pure returns (string memory); 15 | function decimals() external pure returns (uint8); 16 | function totalSupply() external view returns (uint); 17 | function balanceOf(address owner) external view returns (uint); 18 | function allowance(address owner, address spender) external view returns (uint); 19 | 20 | function approve(address spender, uint value) external returns (bool); 21 | function transfer(address to, uint value) external returns (bool); 22 | function transferFrom(address from, address to, uint value) external returns (bool); 23 | 24 | function DOMAIN_SEPARATOR() external view returns (bytes32); 25 | function PERMIT_TYPEHASH() external pure returns (bytes32); 26 | function nonces(address owner) external view returns (uint); 27 | 28 | function permit(address owner, address spender, uint value, uint deadline, uint8 v, bytes32 r, bytes32 s) external; 29 | 30 | event Mint(address indexed sender, uint amount0, uint amount1); 31 | event Burn(address indexed sender, uint amount0, uint amount1, address indexed to); 32 | event Swap( 33 | address indexed sender, 34 | uint amount0In, 35 | uint amount1In, 36 | uint amount0Out, 37 | uint amount1Out, 38 | address indexed to 39 | ); 40 | event Sync(uint112 reserve0, uint112 reserve1); 41 | 42 | function MINIMUM_LIQUIDITY() external pure returns (uint); 43 | function factory() external view returns (address); 44 | function token0() external view returns (address); 45 | function token1() external view returns (address); 46 | function getReserves() external view returns (uint112 reserve0, uint112 reserve1, uint32 blockTimestampLast); 47 | function price0CumulativeLast() external view returns (uint); 48 | function price1CumulativeLast() external view returns (uint); 49 | function kLast() external view returns (uint); 50 | 51 | function mint(address to) external returns (uint liquidity); 52 | function burn(address to) external returns (uint amount0, uint amount1); 53 | function swap(uint amount0Out, uint amount1Out, address to, bytes calldata data) external; 54 | function skim(address to) external; 55 | function sync() external; 56 | 57 | function initialize(address, address) external; 58 | } 59 | 60 | // OpenZeppelin Contracts (last updated v4.6.0) (token/ERC20/IERC20.sol) 61 | 62 | /** 63 | * @dev Interface of the ERC20 standard as defined in the EIP. 64 | */ 65 | interface IERC20 { 66 | /** 67 | * @dev Emitted when `value` tokens are moved from one account (`from`) to 68 | * another (`to`). 69 | * 70 | * Note that `value` may be zero. 71 | */ 72 | event Transfer(address indexed from, address indexed to, uint256 value); 73 | 74 | /** 75 | * @dev Emitted when the allowance of a `spender` for an `owner` is set by 76 | * a call to {approve}. `value` is the new allowance. 77 | */ 78 | event Approval(address indexed owner, address indexed spender, uint256 value); 79 | 80 | /** 81 | * @dev Returns the amount of tokens in existence. 82 | */ 83 | function totalSupply() external view returns (uint256); 84 | 85 | /** 86 | * @dev Returns the amount of tokens owned by `account`. 87 | */ 88 | function balanceOf(address account) external view returns (uint256); 89 | 90 | /** 91 | * @dev Moves `amount` tokens from the caller's account to `to`. 92 | * 93 | * Returns a boolean value indicating whether the operation succeeded. 94 | * 95 | * Emits a {Transfer} event. 96 | */ 97 | function transfer(address to, uint256 amount) external returns (bool); 98 | 99 | /** 100 | * @dev Returns the remaining number of tokens that `spender` will be 101 | * allowed to spend on behalf of `owner` through {transferFrom}. This is 102 | * zero by default. 103 | * 104 | * This value changes when {approve} or {transferFrom} are called. 105 | */ 106 | function allowance(address owner, address spender) external view returns (uint256); 107 | 108 | /** 109 | * @dev Sets `amount` as the allowance of `spender` over the caller's tokens. 110 | * 111 | * Returns a boolean value indicating whether the operation succeeded. 112 | * 113 | * IMPORTANT: Beware that changing an allowance with this method brings the risk 114 | * that someone may use both the old and the new allowance by unfortunate 115 | * transaction ordering. One possible solution to mitigate this race 116 | * condition is to first reduce the spender's allowance to 0 and set the 117 | * desired value afterwards: 118 | * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729 119 | * 120 | * Emits an {Approval} event. 121 | */ 122 | function approve(address spender, uint256 amount) external returns (bool); 123 | 124 | /** 125 | * @dev Moves `amount` tokens from `from` to `to` using the 126 | * allowance mechanism. `amount` is then deducted from the caller's 127 | * allowance. 128 | * 129 | * Returns a boolean value indicating whether the operation succeeded. 130 | * 131 | * Emits a {Transfer} event. 132 | */ 133 | function transferFrom( 134 | address from, 135 | address to, 136 | uint256 amount 137 | ) external returns (bool); 138 | } 139 | 140 | // OpenZeppelin Contracts (last updated v4.7.0) (token/ERC20/utils/SafeERC20.sol) 141 | 142 | // OpenZeppelin Contracts v4.4.1 (token/ERC20/extensions/draft-IERC20Permit.sol) 143 | 144 | /** 145 | * @dev Interface of the ERC20 Permit extension allowing approvals to be made via signatures, as defined in 146 | * https://eips.ethereum.org/EIPS/eip-2612[EIP-2612]. 147 | * 148 | * Adds the {permit} method, which can be used to change an account's ERC20 allowance (see {IERC20-allowance}) by 149 | * presenting a message signed by the account. By not relying on {IERC20-approve}, the token holder account doesn't 150 | * need to send a transaction, and thus is not required to hold Ether at all. 151 | */ 152 | interface IERC20Permit { 153 | /** 154 | * @dev Sets `value` as the allowance of `spender` over ``owner``'s tokens, 155 | * given ``owner``'s signed approval. 156 | * 157 | * IMPORTANT: The same issues {IERC20-approve} has related to transaction 158 | * ordering also apply here. 159 | * 160 | * Emits an {Approval} event. 161 | * 162 | * Requirements: 163 | * 164 | * - `spender` cannot be the zero address. 165 | * - `deadline` must be a timestamp in the future. 166 | * - `v`, `r` and `s` must be a valid `secp256k1` signature from `owner` 167 | * over the EIP712-formatted function arguments. 168 | * - the signature must use ``owner``'s current nonce (see {nonces}). 169 | * 170 | * For more information on the signature format, see the 171 | * https://eips.ethereum.org/EIPS/eip-2612#specification[relevant EIP 172 | * section]. 173 | */ 174 | function permit( 175 | address owner, 176 | address spender, 177 | uint256 value, 178 | uint256 deadline, 179 | uint8 v, 180 | bytes32 r, 181 | bytes32 s 182 | ) external; 183 | 184 | /** 185 | * @dev Returns the current nonce for `owner`. This value must be 186 | * included whenever a signature is generated for {permit}. 187 | * 188 | * Every successful call to {permit} increases ``owner``'s nonce by one. This 189 | * prevents a signature from being used multiple times. 190 | */ 191 | function nonces(address owner) external view returns (uint256); 192 | 193 | /** 194 | * @dev Returns the domain separator used in the encoding of the signature for {permit}, as defined by {EIP712}. 195 | */ 196 | // solhint-disable-next-line func-name-mixedcase 197 | function DOMAIN_SEPARATOR() external view returns (bytes32); 198 | } 199 | 200 | // OpenZeppelin Contracts (last updated v4.7.0) (utils/Address.sol) 201 | 202 | /** 203 | * @dev Collection of functions related to the address type 204 | */ 205 | library Address { 206 | /** 207 | * @dev Returns true if `account` is a contract. 208 | * 209 | * [IMPORTANT] 210 | * ==== 211 | * It is unsafe to assume that an address for which this function returns 212 | * false is an externally-owned account (EOA) and not a contract. 213 | * 214 | * Among others, `isContract` will return false for the following 215 | * types of addresses: 216 | * 217 | * - an externally-owned account 218 | * - a contract in construction 219 | * - an address where a contract will be created 220 | * - an address where a contract lived, but was destroyed 221 | * ==== 222 | * 223 | * [IMPORTANT] 224 | * ==== 225 | * You shouldn't rely on `isContract` to protect against flash loan attacks! 226 | * 227 | * Preventing calls from contracts is highly discouraged. It breaks composability, breaks support for smart wallets 228 | * like Gnosis Safe, and does not provide security since it can be circumvented by calling from a contract 229 | * constructor. 230 | * ==== 231 | */ 232 | function isContract(address account) internal view returns (bool) { 233 | // This method relies on extcodesize/address.code.length, which returns 0 234 | // for contracts in construction, since the code is only stored at the end 235 | // of the constructor execution. 236 | 237 | return account.code.length > 0; 238 | } 239 | 240 | /** 241 | * @dev Replacement for Solidity's `transfer`: sends `amount` wei to 242 | * `recipient`, forwarding all available gas and reverting on errors. 243 | * 244 | * https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost 245 | * of certain opcodes, possibly making contracts go over the 2300 gas limit 246 | * imposed by `transfer`, making them unable to receive funds via 247 | * `transfer`. {sendValue} removes this limitation. 248 | * 249 | * https://diligence.consensys.net/posts/2019/09/stop-using-soliditys-transfer-now/[Learn more]. 250 | * 251 | * IMPORTANT: because control is transferred to `recipient`, care must be 252 | * taken to not create reentrancy vulnerabilities. Consider using 253 | * {ReentrancyGuard} or the 254 | * https://solidity.readthedocs.io/en/v0.5.11/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern]. 255 | */ 256 | function sendValue(address payable recipient, uint256 amount) internal { 257 | require(address(this).balance >= amount, "Address: insufficient balance"); 258 | 259 | (bool success, ) = recipient.call{value: amount}(""); 260 | require(success, "Address: unable to send value, recipient may have reverted"); 261 | } 262 | 263 | /** 264 | * @dev Performs a Solidity function call using a low level `call`. A 265 | * plain `call` is an unsafe replacement for a function call: use this 266 | * function instead. 267 | * 268 | * If `target` reverts with a revert reason, it is bubbled up by this 269 | * function (like regular Solidity function calls). 270 | * 271 | * Returns the raw returned data. To convert to the expected return value, 272 | * use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`]. 273 | * 274 | * Requirements: 275 | * 276 | * - `target` must be a contract. 277 | * - calling `target` with `data` must not revert. 278 | * 279 | * _Available since v3.1._ 280 | */ 281 | function functionCall(address target, bytes memory data) internal returns (bytes memory) { 282 | return functionCallWithValue(target, data, 0, "Address: low-level call failed"); 283 | } 284 | 285 | /** 286 | * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], but with 287 | * `errorMessage` as a fallback revert reason when `target` reverts. 288 | * 289 | * _Available since v3.1._ 290 | */ 291 | function functionCall( 292 | address target, 293 | bytes memory data, 294 | string memory errorMessage 295 | ) internal returns (bytes memory) { 296 | return functionCallWithValue(target, data, 0, errorMessage); 297 | } 298 | 299 | /** 300 | * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], 301 | * but also transferring `value` wei to `target`. 302 | * 303 | * Requirements: 304 | * 305 | * - the calling contract must have an ETH balance of at least `value`. 306 | * - the called Solidity function must be `payable`. 307 | * 308 | * _Available since v3.1._ 309 | */ 310 | function functionCallWithValue( 311 | address target, 312 | bytes memory data, 313 | uint256 value 314 | ) internal returns (bytes memory) { 315 | return functionCallWithValue(target, data, value, "Address: low-level call with value failed"); 316 | } 317 | 318 | /** 319 | * @dev Same as {xref-Address-functionCallWithValue-address-bytes-uint256-}[`functionCallWithValue`], but 320 | * with `errorMessage` as a fallback revert reason when `target` reverts. 321 | * 322 | * _Available since v3.1._ 323 | */ 324 | function functionCallWithValue( 325 | address target, 326 | bytes memory data, 327 | uint256 value, 328 | string memory errorMessage 329 | ) internal returns (bytes memory) { 330 | require(address(this).balance >= value, "Address: insufficient balance for call"); 331 | (bool success, bytes memory returndata) = target.call{value: value}(data); 332 | return verifyCallResultFromTarget(target, success, returndata, errorMessage); 333 | } 334 | 335 | /** 336 | * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], 337 | * but performing a static call. 338 | * 339 | * _Available since v3.3._ 340 | */ 341 | function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) { 342 | return functionStaticCall(target, data, "Address: low-level static call failed"); 343 | } 344 | 345 | /** 346 | * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`], 347 | * but performing a static call. 348 | * 349 | * _Available since v3.3._ 350 | */ 351 | function functionStaticCall( 352 | address target, 353 | bytes memory data, 354 | string memory errorMessage 355 | ) internal view returns (bytes memory) { 356 | (bool success, bytes memory returndata) = target.staticcall(data); 357 | return verifyCallResultFromTarget(target, success, returndata, errorMessage); 358 | } 359 | 360 | /** 361 | * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], 362 | * but performing a delegate call. 363 | * 364 | * _Available since v3.4._ 365 | */ 366 | function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) { 367 | return functionDelegateCall(target, data, "Address: low-level delegate call failed"); 368 | } 369 | 370 | /** 371 | * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`], 372 | * but performing a delegate call. 373 | * 374 | * _Available since v3.4._ 375 | */ 376 | function functionDelegateCall( 377 | address target, 378 | bytes memory data, 379 | string memory errorMessage 380 | ) internal returns (bytes memory) { 381 | (bool success, bytes memory returndata) = target.delegatecall(data); 382 | return verifyCallResultFromTarget(target, success, returndata, errorMessage); 383 | } 384 | 385 | /** 386 | * @dev Tool to verify that a low level call to smart-contract was successful, and revert (either by bubbling 387 | * the revert reason or using the provided one) in case of unsuccessful call or if target was not a contract. 388 | * 389 | * _Available since v4.8._ 390 | */ 391 | function verifyCallResultFromTarget( 392 | address target, 393 | bool success, 394 | bytes memory returndata, 395 | string memory errorMessage 396 | ) internal view returns (bytes memory) { 397 | if (success) { 398 | if (returndata.length == 0) { 399 | // only check isContract if the call was successful and the return data is empty 400 | // otherwise we already know that it was a contract 401 | require(isContract(target), "Address: call to non-contract"); 402 | } 403 | return returndata; 404 | } else { 405 | _revert(returndata, errorMessage); 406 | } 407 | } 408 | 409 | /** 410 | * @dev Tool to verify that a low level call was successful, and revert if it wasn't, either by bubbling the 411 | * revert reason or using the provided one. 412 | * 413 | * _Available since v4.3._ 414 | */ 415 | function verifyCallResult( 416 | bool success, 417 | bytes memory returndata, 418 | string memory errorMessage 419 | ) internal pure returns (bytes memory) { 420 | if (success) { 421 | return returndata; 422 | } else { 423 | _revert(returndata, errorMessage); 424 | } 425 | } 426 | 427 | function _revert(bytes memory returndata, string memory errorMessage) private pure { 428 | // Look for revert reason and bubble it up if present 429 | if (returndata.length > 0) { 430 | // The easiest way to bubble the revert reason is using memory via assembly 431 | /// @solidity memory-safe-assembly 432 | assembly { 433 | let returndata_size := mload(returndata) 434 | revert(add(32, returndata), returndata_size) 435 | } 436 | } else { 437 | revert(errorMessage); 438 | } 439 | } 440 | } 441 | 442 | /** 443 | * @title SafeERC20 444 | * @dev Wrappers around ERC20 operations that throw on failure (when the token 445 | * contract returns false). Tokens that return no value (and instead revert or 446 | * throw on failure) are also supported, non-reverting calls are assumed to be 447 | * successful. 448 | * To use this library you can add a `using SafeERC20 for IERC20;` statement to your contract, 449 | * which allows you to call the safe operations as `token.safeTransfer(...)`, etc. 450 | */ 451 | library SafeERC20 { 452 | using Address for address; 453 | 454 | function safeTransfer( 455 | IERC20 token, 456 | address to, 457 | uint256 value 458 | ) internal { 459 | _callOptionalReturn(token, abi.encodeWithSelector(token.transfer.selector, to, value)); 460 | } 461 | 462 | function safeTransferFrom( 463 | IERC20 token, 464 | address from, 465 | address to, 466 | uint256 value 467 | ) internal { 468 | _callOptionalReturn(token, abi.encodeWithSelector(token.transferFrom.selector, from, to, value)); 469 | } 470 | 471 | /** 472 | * @dev Deprecated. This function has issues similar to the ones found in 473 | * {IERC20-approve}, and its usage is discouraged. 474 | * 475 | * Whenever possible, use {safeIncreaseAllowance} and 476 | * {safeDecreaseAllowance} instead. 477 | */ 478 | function safeApprove( 479 | IERC20 token, 480 | address spender, 481 | uint256 value 482 | ) internal { 483 | // safeApprove should only be called when setting an initial allowance, 484 | // or when resetting it to zero. To increase and decrease it, use 485 | // 'safeIncreaseAllowance' and 'safeDecreaseAllowance' 486 | require( 487 | (value == 0) || (token.allowance(address(this), spender) == 0), 488 | "SafeERC20: approve from non-zero to non-zero allowance" 489 | ); 490 | _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, value)); 491 | } 492 | 493 | function safeIncreaseAllowance( 494 | IERC20 token, 495 | address spender, 496 | uint256 value 497 | ) internal { 498 | uint256 newAllowance = token.allowance(address(this), spender) + value; 499 | _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance)); 500 | } 501 | 502 | function safeDecreaseAllowance( 503 | IERC20 token, 504 | address spender, 505 | uint256 value 506 | ) internal { 507 | unchecked { 508 | uint256 oldAllowance = token.allowance(address(this), spender); 509 | require(oldAllowance >= value, "SafeERC20: decreased allowance below zero"); 510 | uint256 newAllowance = oldAllowance - value; 511 | _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance)); 512 | } 513 | } 514 | 515 | function safePermit( 516 | IERC20Permit token, 517 | address owner, 518 | address spender, 519 | uint256 value, 520 | uint256 deadline, 521 | uint8 v, 522 | bytes32 r, 523 | bytes32 s 524 | ) internal { 525 | uint256 nonceBefore = token.nonces(owner); 526 | token.permit(owner, spender, value, deadline, v, r, s); 527 | uint256 nonceAfter = token.nonces(owner); 528 | require(nonceAfter == nonceBefore + 1, "SafeERC20: permit did not succeed"); 529 | } 530 | 531 | /** 532 | * @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement 533 | * on the return value: the return value is optional (but if data is returned, it must not be false). 534 | * @param token The token targeted by the call. 535 | * @param data The call data (encoded using abi.encode or one of its variants). 536 | */ 537 | function _callOptionalReturn(IERC20 token, bytes memory data) private { 538 | // We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since 539 | // we're implementing it ourselves. We use {Address.functionCall} to perform this call, which verifies that 540 | // the target address contains contract code and also asserts for success in the low-level call. 541 | 542 | bytes memory returndata = address(token).functionCall(data, "SafeERC20: low-level call failed"); 543 | if (returndata.length > 0) { 544 | // Return data is optional 545 | require(abi.decode(returndata, (bool)), "SafeERC20: ERC20 operation did not succeed"); 546 | } 547 | } 548 | } 549 | 550 | /* 551 | * @title Solidity Bytes Arrays Utils 552 | * @author Gonçalo Sá 553 | * 554 | * @dev Bytes tightly packed arrays utility library for ethereum contracts written in Solidity. 555 | * The library lets you concatenate, slice and type cast bytes arrays both in memory and storage. 556 | */ 557 | 558 | library BytesLib { 559 | 560 | function slice( 561 | bytes memory _bytes, 562 | uint256 _start, 563 | uint256 _length 564 | ) internal pure returns (bytes memory) { 565 | require(_length + 31 >= _length, 'slice_overflow'); 566 | require(_start + _length >= _start, 'slice_overflow'); 567 | require(_bytes.length >= _start + _length, 'slice_outOfBounds'); 568 | 569 | bytes memory tempBytes; 570 | 571 | assembly { 572 | switch iszero(_length) 573 | case 0 { 574 | // Get a location of some free memory and store it in tempBytes as 575 | // Solidity does for memory variables. 576 | tempBytes := mload(0x40) 577 | 578 | // The first word of the slice result is potentially a partial 579 | // word read from the original array. To read it, we calculate 580 | // the length of that partial word and start copying that many 581 | // bytes into the array. The first word we copy will start with 582 | // data we don't care about, but the last `lengthmod` bytes will 583 | // land at the beginning of the contents of the new array. When 584 | // we're done copying, we overwrite the full first word with 585 | // the actual length of the slice. 586 | let lengthmod := and(_length, 31) 587 | 588 | // cete operation and eqiuivaut à mod(_length, 32)!! 589 | // on obtiens ainsi le bout de _bytes qui va rester une fois qu'on l'aura fetch par block de 32 bytes 590 | 591 | // The multiplication in the next line is necessary 592 | // because when slicing multiples of 32 bytes (lengthmod == 0) 593 | // the following copy loop was copying the origin's length 594 | // and then ending prematurely not copying everything it should. 595 | let mc := add(add(tempBytes, lengthmod), mul(0x20, iszero(lengthmod))) 596 | 597 | // ici, mc est soit == fmp + lengthmod, soit == fmp + 32 598 | 599 | let end := add(mc, _length) 600 | 601 | for { 602 | // The multiplication in the next line has the same exact purpose 603 | // as the one above. 604 | let cc := add(add(add(_bytes, lengthmod), mul(0x20, iszero(lengthmod))), _start) 605 | } lt(mc, end) { 606 | mc := add(mc, 0x20) 607 | cc := add(cc, 0x20) 608 | } { 609 | mstore(mc, mload(cc)) 610 | } 611 | 612 | mstore(tempBytes, _length) 613 | 614 | //update free-memory pointer 615 | //allocating the array padded to 32 bytes like the compiler does now 616 | mstore(0x40, and(add(mc, 31), not(31))) 617 | } 618 | //if we want a zero-length slice let's just return a zero-length array 619 | default { 620 | tempBytes := mload(0x40) 621 | //zero out the 32 bytes slice we are about to return 622 | //we need to do it because Solidity does not garbage collect 623 | mstore(tempBytes, 0) 624 | 625 | mstore(0x40, add(tempBytes, 0x20)) 626 | } 627 | } 628 | 629 | return tempBytes; 630 | } 631 | 632 | function toAddress(bytes memory _bytes, uint256 _start) internal pure returns (address) { 633 | 634 | require(_start + 20 >= _start, 'toAddress_overflow'); 635 | require(_bytes.length >= _start + 20, 'toAddress_outOfBounds'); 636 | address tempAddress; 637 | 638 | assembly { 639 | tempAddress := div(mload(add(add(_bytes, 0x20), _start)), 0x1000000000000000000000000) 640 | } 641 | 642 | return tempAddress; 643 | } 644 | 645 | function toUint24(bytes memory _bytes, uint256 _start) internal pure returns (uint24) { 646 | require(_start + 3 >= _start, 'toUint24_overflow'); 647 | require(_bytes.length >= _start + 3, 'toUint24_outOfBounds'); 648 | uint24 tempUint; 649 | 650 | assembly { 651 | 652 | 653 | tempUint := mload(add(add(_bytes, 0x3), _start)) 654 | // le memory slot qui est load ici peut ressembler a SSSSSSSVVVVVVVVVVVV (size, value) ou on load une partie du size word 655 | // tout ce qu'on sait c'est que les 3 derniers bytes de ce word == notre uint 24 656 | } 657 | 658 | return tempUint; // l'implicit convertion uint256 => uint24 fait le taff en ne gardant que les 3 derniers bytes 659 | } 660 | 661 | function toUint256(bytes memory _bytes, uint256 _start) internal pure returns (uint256 value){ 662 | require(_start + 20 >= _start, 'toUint256_overflow'); 663 | require(_bytes.length >= _start + 20, 'toUint256_outOfBounds'); 664 | 665 | assembly { 666 | value := mload(add(add(_bytes, 0x20), _start)) 667 | } 668 | } 669 | } 670 | 671 | 672 | contract RepaimentStorage { 673 | 674 | // loans storage. Erased after each flashSwap call with repayLoans() 675 | mapping(address => Loan[]) loans; 676 | mapping(address => uint256) totalBorrowPerToken; 677 | address[] public borrowedTokens; 678 | 679 | struct Loan { 680 | address pool; 681 | address token; 682 | uint256 amount; 683 | uint256 fee; 684 | } 685 | 686 | // use this function if you want to du multiple flashloans 687 | function cleanupRepaimentStorage() internal { 688 | 689 | if (borrowedTokens.length > 0) { 690 | 691 | for (uint256 i = 0; i < borrowedTokens.length; i++) { 692 | 693 | address token = borrowedTokens[i]; 694 | delete totalBorrowPerToken[token]; 695 | delete loans[token]; 696 | } 697 | } 698 | delete borrowedTokens; 699 | } 700 | } 701 | 702 | 703 | 704 | 705 | abstract contract UniV2FlashSwap is IUniswapV2Callee, RepaimentStorage { 706 | 707 | using BytesLib for bytes; 708 | using SafeERC20 for IERC20; 709 | 710 | uint256 private constant ONEFLASHSWAPUNIV2 = 84; 711 | address private constant FACTORYV2 = 0x1F98431c8aD98523631AE4a59f267346ea31F984; 712 | 713 | // pool storage 714 | mapping(address => uint256) public numberOfPoolForAsset; 715 | mapping(address => mapping(uint256 => address)) public poolsForAsset; 716 | 717 | event FlashSwap( 718 | address indexed token, 719 | uint256 indexed amount, 720 | uint256 indexed feePaid 721 | ); 722 | 723 | constructor(){ 724 | initializeKnownPools(); 725 | } 726 | 727 | // main pools for BSC 728 | // should be overriden if you want to use other pools or other networks 729 | function initializeKnownPools() internal virtual { 730 | 731 | address WBNB = 0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c; 732 | address BUSD = 0xe9e7CEA3DedcA5984780Bafc599bD69ADd087D56; 733 | address CAKE = 0x0E09FaBB73Bd3Ade0a17ECC321fD13a19e81cE82; 734 | address USDT = 0x55d398326f99059fF775485246999027B3197955; 735 | address WETH = 0x2170Ed0880ac9A755fd29B2688956BD959F933F8; 736 | address BTCB = 0x7130d2A12B9BCbFAe4f2634d864A1Ee1Ce3Ead9c; 737 | 738 | numberOfPoolForAsset[WBNB] = 5; 739 | numberOfPoolForAsset[BUSD] = 3; 740 | numberOfPoolForAsset[USDT] = 3; 741 | numberOfPoolForAsset[BTCB] = 3; 742 | 743 | poolsForAsset[WBNB][1] = 0x16b9a82891338f9bA80E2D6970FddA79D1eb0daE; // USDTWBNB 744 | poolsForAsset[WBNB][2] = 0x58F876857a02D6762E0101bb5C46A8c1ED44Dc16; // WBNBBUSD 745 | poolsForAsset[WBNB][3] = 0x0eD7e52944161450477ee417DE9Cd3a859b14fD0; // CAKEWBNB 746 | poolsForAsset[WBNB][4] = 0x74E4716E431f45807DCF19f284c7aA99F18a4fbc; // WETHWBNB 747 | poolsForAsset[WBNB][5] = 0x61EB789d75A95CAa3fF50ed7E47b96c132fEc082; // BTCBWBNB 748 | 749 | poolsForAsset[BUSD][1] = 0x58F876857a02D6762E0101bb5C46A8c1ED44Dc16; // WBNBBUSD 750 | poolsForAsset[BUSD][2] = 0x7EFaEf62fDdCCa950418312c6C91Aef321375A00; // USDTBUSD 751 | poolsForAsset[BUSD][3] = 0xF45cd219aEF8618A92BAa7aD848364a158a24F33; // BTCBBUSD 752 | 753 | poolsForAsset[USDT][1] = 0x16b9a82891338f9bA80E2D6970FddA79D1eb0daE; // USDTWBNB 754 | poolsForAsset[USDT][2] = 0x7EFaEf62fDdCCa950418312c6C91Aef321375A00; // USDTBUSD 755 | poolsForAsset[USDT][3] = 0xcd9D281a588b69EcDFfA411363661954Aa339C7b; // USDTTAU 756 | 757 | poolsForAsset[BTCB][1] = 0xF45cd219aEF8618A92BAa7aD848364a158a24F33; // BTCBBUSD 758 | poolsForAsset[BTCB][2] = 0x61EB789d75A95CAa3fF50ed7E47b96c132fEc082; // BTCBWBNB 759 | poolsForAsset[BTCB][3] = 0xD171B26E4484402de70e3Ea256bE5A2630d7e88D; // WETHBTCB 760 | } 761 | 762 | function maxFlashSwap(address token) public view virtual returns(uint256 amount){ 763 | if (numberOfPoolForAsset[token] == 0) return 0; 764 | 765 | for (uint256 i = 1; i <= numberOfPoolForAsset[token]; i++){ 766 | 767 | address pair = poolsForAsset[token][i]; 768 | (uint112 reserve0, uint112 reserve1,) = IUniswapV2Pair(pair).getReserves(); 769 | uint256 increment = token == IUniswapV2Pair(pair).token0() ? reserve0 : reserve1; 770 | amount += (increment -1); // amountOut must be < reserve 771 | } 772 | } 773 | 774 | function estimateFlashSwapFee(address token, uint256 amount) public view virtual returns(uint256 feesEstimated){ 775 | 776 | address[] memory tokens = new address[](1); 777 | uint256[] memory amounts = new uint256[](1); 778 | tokens[0] = token; 779 | amounts[0] = amount; 780 | bytes memory flashSwapPath = _buildFlashSwapsArray(tokens, amounts); 781 | 782 | while (flashSwapPath.length > 0){ 783 | (, uint256 amount0, uint256 amount1) = decodeFirstFlashSwap(flashSwapPath); 784 | uint256 _amount = amount0 > 0? amount0 : amount1; 785 | feesEstimated += _amount * 3 / 997 + 1; // 0.3% 786 | flashSwapPath = skipFlashSwap(flashSwapPath); 787 | } 788 | } 789 | 790 | function _updateRepaimentStorage(address token, address pool, uint256 amount,uint256 fee) private { 791 | if (totalBorrowPerToken[token] == 0) borrowedTokens.push(token); 792 | loans[token].push() = Loan(pool, token, amount, fee); 793 | totalBorrowPerToken[token] += (amount + fee); 794 | } 795 | 796 | function pancakeCall(address sender, uint amount0, uint amount1, bytes calldata data) external{ 797 | uniswapV2FlashCallback(sender, amount0, amount1, data); 798 | } 799 | 800 | function uniswapV2Call(address sender, uint amount0, uint amount1, bytes calldata data) external{ 801 | uniswapV2FlashCallback(sender, amount0, amount1, data); 802 | } 803 | 804 | function uniswapV2FlashCallback(address sender, uint amount0, uint amount1, bytes calldata data) internal { 805 | require(sender == address(this), "UniV2FlashSwap: unauthorized"); 806 | bytes memory flashSwapPath = data; 807 | (address pair,,) = decodeFirstFlashSwap(flashSwapPath); 808 | require(msg.sender == pair); 809 | 810 | // edit repaiement storage 811 | if (amount0 > 0){ 812 | address token0 = IUniswapV2Pair(pair).token0(); 813 | _updateRepaimentStorage(token0, pair, amount0, (amount0 * 3 / 997) + 1); 814 | } 815 | if (amount1 > 0){ 816 | address token1 = IUniswapV2Pair(pair).token1(); 817 | _updateRepaimentStorage(token1, pair, amount1, (amount1 * 3 / 997) + 1); 818 | } 819 | 820 | // edit flashSwaps 821 | flashSwapPath = skipFlashSwap(flashSwapPath); 822 | _executeFlashSwaps(flashSwapPath); 823 | } 824 | 825 | function repayLoans() private { 826 | 827 | for (uint256 i = 0; i < borrowedTokens.length; i++){ 828 | 829 | address token = borrowedTokens[i]; 830 | Loan[] memory tokenLoans = loans[token]; 831 | uint256 feeTotal; 832 | uint256 amountTotal; 833 | 834 | // repay the loans 835 | for (uint256 j; j < tokenLoans.length; j++){ 836 | Loan memory loan = tokenLoans[j]; 837 | 838 | require( 839 | IERC20(token).balanceOf(address(this)) >= (loan.amount + loan.fee), 840 | "UniV2FlashSwap: repay loan: not enough balance to repay loan" 841 | ); 842 | feeTotal += loan.fee; 843 | amountTotal += loan.amount; 844 | IERC20(token).safeTransfer(loan.pool, loan.amount + loan.fee); 845 | } 846 | emit FlashSwap(token, amountTotal, feeTotal); 847 | } 848 | } 849 | 850 | function _buildFlashSwapsArray( 851 | 852 | address[] memory tokens, 853 | uint256[] memory amounts) internal view returns(bytes memory flashSwapPath){ 854 | 855 | require(tokens.length == amounts.length, "UniV2FlashSwap: length of amounts and tokens doesnt match"); 856 | 857 | for (uint256 i = 0; i < tokens.length; ++i){ 858 | 859 | address token = tokens[i]; 860 | uint256 amount = amounts[i]; 861 | 862 | require(numberOfPoolForAsset[token] > 0, "UniV2FlashSwap: asset not supported"); 863 | require(amount <= maxFlashSwap(token), "UniV2FlashSwap: amount to fs > maxFlashSwap"); 864 | 865 | uint256 counter = 0; 866 | uint256 tempAmount = amount; 867 | 868 | // build the flashSwaps byte array 869 | // [pair][amount0][amount1] 870 | while (tempAmount > 0) { 871 | 872 | address nextPair = poolsForAsset[token][counter + 1]; 873 | (uint112 reserve0, uint112 reserve1,) = IUniswapV2Pair(nextPair).getReserves(); 874 | uint256 pairReserve = token == IUniswapV2Pair(nextPair).token0() ? reserve0: reserve1; 875 | pairReserve -=1; // amountOut must be < reserve 876 | uint256 increment = pairReserve >= tempAmount? tempAmount: pairReserve; 877 | uint256 amount0 = pairReserve + 1 == reserve0 ? increment : 0; 878 | uint256 amount1 = amount0 > 0? 0 : increment; 879 | 880 | if (flashSwapPath.length == 0){ 881 | flashSwapPath = abi.encodePacked(nextPair, amount0, amount1); 882 | continue; 883 | } 884 | 885 | bool expendPath = true; 886 | 887 | assembly { 888 | 889 | let numberOfFlashSwaps := div(mload(flashSwapPath), ONEFLASHSWAPUNIV2) 890 | let startPtr := add(flashSwapPath, 0x20) 891 | 892 | // check if the pool is already registered in flashSwapPath 893 | for {let j := 0} lt(j, numberOfFlashSwaps) {j:= add(j, 1)}{ 894 | 895 | let swapptr := add(startPtr, mul(j, ONEFLASHSWAPUNIV2)) 896 | let pool := shr(96, mload(swapptr)) 897 | 898 | if eq(pool, nextPair) { 899 | // if amount0 == 0 , store amount1 900 | if iszero(amount0) {mstore(add(swapptr, 52), amount1)} 901 | // if amount1 == 0 , store amount0 902 | if iszero(amount1) {mstore(add(swapptr, 20), amount0)} 903 | expendPath := 0 904 | } 905 | } 906 | } 907 | 908 | if (expendPath) flashSwapPath = abi.encodePacked( 909 | flashSwapPath, 910 | nextPair, 911 | amount0, 912 | amount1); 913 | 914 | tempAmount -= increment; 915 | ++counter; 916 | } 917 | } 918 | } 919 | 920 | function _executeFlashSwaps(bytes memory flashSwapPath) private { 921 | 922 | // actually execute the flashSwaps here. reentrancy is desired 923 | if (flashSwapPath.length > 0) { 924 | (address pool, uint256 amount0, uint256 amount1) = decodeFirstFlashSwap(flashSwapPath); 925 | IUniswapV2Pair(pool).swap(amount0, amount1, address(this), flashSwapPath); 926 | } 927 | // if the length is only 1, it means all the flashswaps are done 928 | else { 929 | onUniV2FlashSwap(); 930 | repayLoans(); 931 | } 932 | } 933 | 934 | function flashSwap(address[] memory tokens, uint256[] memory amounts) internal { 935 | bytes memory flashSwapPath = _buildFlashSwapsArray(tokens, amounts); 936 | _executeFlashSwaps(flashSwapPath); 937 | } 938 | 939 | function flashSwap(address token, uint256 amount) internal { 940 | address[] memory tokens = new address[](1); 941 | uint256[] memory amounts = new uint256[](1); 942 | tokens[0] = token; 943 | amounts[0] = amount; 944 | flashSwap(tokens, amounts); 945 | } 946 | 947 | function decodeFirstFlashSwap(bytes memory flashSwapPath) 948 | private 949 | pure 950 | returns ( 951 | address pool, 952 | uint256 amount0, 953 | uint256 amount1 954 | ) { 955 | pool = flashSwapPath.toAddress(0); 956 | amount0 = flashSwapPath.toUint256(20); 957 | amount1 = flashSwapPath.toUint256(52); 958 | } 959 | 960 | function skipFlashSwap(bytes memory flashSwapPath) private pure returns (bytes memory){ 961 | return flashSwapPath.slice(ONEFLASHSWAPUNIV2, flashSwapPath.length - ONEFLASHSWAPUNIV2); 962 | } 963 | 964 | // should be overriden with your own logic 965 | function onUniV2FlashSwap() internal virtual; 966 | } 967 | -------------------------------------------------------------------------------- /src/UniV3FlashSwap.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | /// @title Pool state that never changes 6 | /// @notice These parameters are fixed for a pool forever, i.e., the methods will always return the same values 7 | interface IUniswapV3PoolImmutables { 8 | /// @notice The contract that deployed the pool, which must adhere to the IUniswapV3Factory interface 9 | /// @return The contract address 10 | function factory() external view returns (address); 11 | 12 | /// @notice The first of the two tokens of the pool, sorted by address 13 | /// @return The token contract address 14 | function token0() external view returns (address); 15 | 16 | /// @notice The second of the two tokens of the pool, sorted by address 17 | /// @return The token contract address 18 | function token1() external view returns (address); 19 | 20 | /// @notice The pool's fee in hundredths of a bip, i.e. 1e-6 21 | /// @return The fee 22 | function fee() external view returns (uint24); 23 | 24 | /// @notice The pool tick spacing 25 | /// @dev Ticks can only be used at multiples of this value, minimum of 1 and always positive 26 | /// e.g.: a tickSpacing of 3 means ticks can be initialized every 3rd tick, i.e., ..., -6, -3, 0, 3, 6, ... 27 | /// This value is an int24 to avoid casting even though it is always positive. 28 | /// @return The tick spacing 29 | function tickSpacing() external view returns (int24); 30 | 31 | /// @notice The maximum amount of position liquidity that can use any tick in the range 32 | /// @dev This parameter is enforced per tick to prevent liquidity from overflowing a uint128 at any point, and 33 | /// also prevents out-of-range liquidity from being used to prevent adding in-range liquidity to a pool 34 | /// @return The max amount of liquidity per tick 35 | function maxLiquidityPerTick() external view returns (uint128); 36 | } 37 | 38 | /// @title Pool state that can change 39 | /// @notice These methods compose the pool's state, and can change with any frequency including multiple times 40 | /// per transaction 41 | interface IUniswapV3PoolState { 42 | /// @notice The 0th storage slot in the pool stores many values, and is exposed as a single method to save gas 43 | /// when accessed externally. 44 | /// @return sqrtPriceX96 The current price of the pool as a sqrt(token1/token0) Q64.96 value 45 | /// tick The current tick of the pool, i.e. according to the last tick transition that was run. 46 | /// This value may not always be equal to SqrtTickMath.getTickAtSqrtRatio(sqrtPriceX96) if the price is on a tick 47 | /// boundary. 48 | /// observationIndex The index of the last oracle observation that was written, 49 | /// observationCardinality The current maximum number of observations stored in the pool, 50 | /// observationCardinalityNext The next maximum number of observations, to be updated when the observation. 51 | /// feeProtocol The protocol fee for both tokens of the pool. 52 | /// Encoded as two 4 bit values, where the protocol fee of token1 is shifted 4 bits and the protocol fee of token0 53 | /// is the lower 4 bits. Used as the denominator of a fraction of the swap fee, e.g. 4 means 1/4th of the swap fee. 54 | /// unlocked Whether the pool is currently locked to reentrancy 55 | function slot0() 56 | external 57 | view 58 | returns ( 59 | uint160 sqrtPriceX96, 60 | int24 tick, 61 | uint16 observationIndex, 62 | uint16 observationCardinality, 63 | uint16 observationCardinalityNext, 64 | uint8 feeProtocol, 65 | bool unlocked 66 | ); 67 | 68 | /// @notice The fee growth as a Q128.128 fees of token0 collected per unit of liquidity for the entire life of the pool 69 | /// @dev This value can overflow the uint256 70 | function feeGrowthGlobal0X128() external view returns (uint256); 71 | 72 | /// @notice The fee growth as a Q128.128 fees of token1 collected per unit of liquidity for the entire life of the pool 73 | /// @dev This value can overflow the uint256 74 | function feeGrowthGlobal1X128() external view returns (uint256); 75 | 76 | /// @notice The amounts of token0 and token1 that are owed to the protocol 77 | /// @dev Protocol fees will never exceed uint128 max in either token 78 | function protocolFees() external view returns (uint128 token0, uint128 token1); 79 | 80 | /// @notice The currently in range liquidity available to the pool 81 | /// @dev This value has no relationship to the total liquidity across all ticks 82 | function liquidity() external view returns (uint128); 83 | 84 | /// @notice Look up information about a specific tick in the pool 85 | /// @param tick The tick to look up 86 | /// @return liquidityGross the total amount of position liquidity that uses the pool either as tick lower or 87 | /// tick upper, 88 | /// liquidityNet how much liquidity changes when the pool price crosses the tick, 89 | /// feeGrowthOutside0X128 the fee growth on the other side of the tick from the current tick in token0, 90 | /// feeGrowthOutside1X128 the fee growth on the other side of the tick from the current tick in token1, 91 | /// tickCumulativeOutside the cumulative tick value on the other side of the tick from the current tick 92 | /// secondsPerLiquidityOutsideX128 the seconds spent per liquidity on the other side of the tick from the current tick, 93 | /// secondsOutside the seconds spent on the other side of the tick from the current tick, 94 | /// initialized Set to true if the tick is initialized, i.e. liquidityGross is greater than 0, otherwise equal to false. 95 | /// Outside values can only be used if the tick is initialized, i.e. if liquidityGross is greater than 0. 96 | /// In addition, these values are only relative and must be used only in comparison to previous snapshots for 97 | /// a specific position. 98 | function ticks(int24 tick) 99 | external 100 | view 101 | returns ( 102 | uint128 liquidityGross, 103 | int128 liquidityNet, 104 | uint256 feeGrowthOutside0X128, 105 | uint256 feeGrowthOutside1X128, 106 | int56 tickCumulativeOutside, 107 | uint160 secondsPerLiquidityOutsideX128, 108 | uint32 secondsOutside, 109 | bool initialized 110 | ); 111 | 112 | /// @notice Returns 256 packed tick initialized boolean values. See TickBitmap for more information 113 | function tickBitmap(int16 wordPosition) external view returns (uint256); 114 | 115 | /// @notice Returns the information about a position by the position's key 116 | /// @param key The position's key is a hash of a preimage composed by the owner, tickLower and tickUpper 117 | /// @return _liquidity The amount of liquidity in the position, 118 | /// Returns feeGrowthInside0LastX128 fee growth of token0 inside the tick range as of the last mint/burn/poke, 119 | /// Returns feeGrowthInside1LastX128 fee growth of token1 inside the tick range as of the last mint/burn/poke, 120 | /// Returns tokensOwed0 the computed amount of token0 owed to the position as of the last mint/burn/poke, 121 | /// Returns tokensOwed1 the computed amount of token1 owed to the position as of the last mint/burn/poke 122 | function positions(bytes32 key) 123 | external 124 | view 125 | returns ( 126 | uint128 _liquidity, 127 | uint256 feeGrowthInside0LastX128, 128 | uint256 feeGrowthInside1LastX128, 129 | uint128 tokensOwed0, 130 | uint128 tokensOwed1 131 | ); 132 | 133 | /// @notice Returns data about a specific observation index 134 | /// @param index The element of the observations array to fetch 135 | /// @dev You most likely want to use #observe() instead of this method to get an observation as of some amount of time 136 | /// ago, rather than at a specific index in the array. 137 | /// @return blockTimestamp The timestamp of the observation, 138 | /// Returns tickCumulative the tick multiplied by seconds elapsed for the life of the pool as of the observation timestamp, 139 | /// Returns secondsPerLiquidityCumulativeX128 the seconds per in range liquidity for the life of the pool as of the observation timestamp, 140 | /// Returns initialized whether the observation has been initialized and the values are safe to use 141 | function observations(uint256 index) 142 | external 143 | view 144 | returns ( 145 | uint32 blockTimestamp, 146 | int56 tickCumulative, 147 | uint160 secondsPerLiquidityCumulativeX128, 148 | bool initialized 149 | ); 150 | } 151 | 152 | /// @title Pool state that is not stored 153 | /// @notice Contains view functions to provide information about the pool that is computed rather than stored on the 154 | /// blockchain. The functions here may have variable gas costs. 155 | interface IUniswapV3PoolDerivedState { 156 | /// @notice Returns the cumulative tick and liquidity as of each timestamp `secondsAgo` from the current block timestamp 157 | /// @dev To get a time weighted average tick or liquidity-in-range, you must call this with two values, one representing 158 | /// the beginning of the period and another for the end of the period. E.g., to get the last hour time-weighted average tick, 159 | /// you must call it with secondsAgos = [3600, 0]. 160 | /// @dev The time weighted average tick represents the geometric time weighted average price of the pool, in 161 | /// log base sqrt(1.0001) of token1 / token0. The TickMath library can be used to go from a tick value to a ratio. 162 | /// @param secondsAgos From how long ago each cumulative tick and liquidity value should be returned 163 | /// @return tickCumulatives Cumulative tick values as of each `secondsAgos` from the current block timestamp 164 | /// @return secondsPerLiquidityCumulativeX128s Cumulative seconds per liquidity-in-range value as of each `secondsAgos` from the current block 165 | /// timestamp 166 | function observe(uint32[] calldata secondsAgos) 167 | external 168 | view 169 | returns (int56[] memory tickCumulatives, uint160[] memory secondsPerLiquidityCumulativeX128s); 170 | 171 | /// @notice Returns a snapshot of the tick cumulative, seconds per liquidity and seconds inside a tick range 172 | /// @dev Snapshots must only be compared to other snapshots, taken over a period for which a position existed. 173 | /// I.e., snapshots cannot be compared if a position is not held for the entire period between when the first 174 | /// snapshot is taken and the second snapshot is taken. 175 | /// @param tickLower The lower tick of the range 176 | /// @param tickUpper The upper tick of the range 177 | /// @return tickCumulativeInside The snapshot of the tick accumulator for the range 178 | /// @return secondsPerLiquidityInsideX128 The snapshot of seconds per liquidity for the range 179 | /// @return secondsInside The snapshot of seconds per liquidity for the range 180 | function snapshotCumulativesInside(int24 tickLower, int24 tickUpper) 181 | external 182 | view 183 | returns ( 184 | int56 tickCumulativeInside, 185 | uint160 secondsPerLiquidityInsideX128, 186 | uint32 secondsInside 187 | ); 188 | } 189 | 190 | /// @title Permissionless pool actions 191 | /// @notice Contains pool methods that can be called by anyone 192 | interface IUniswapV3PoolActions { 193 | /// @notice Sets the initial price for the pool 194 | /// @dev Price is represented as a sqrt(amountToken1/amountToken0) Q64.96 value 195 | /// @param sqrtPriceX96 the initial sqrt price of the pool as a Q64.96 196 | function initialize(uint160 sqrtPriceX96) external; 197 | 198 | /// @notice Adds liquidity for the given recipient/tickLower/tickUpper position 199 | /// @dev The caller of this method receives a callback in the form of IUniswapV3MintCallback#uniswapV3MintCallback 200 | /// in which they must pay any token0 or token1 owed for the liquidity. The amount of token0/token1 due depends 201 | /// on tickLower, tickUpper, the amount of liquidity, and the current price. 202 | /// @param recipient The address for which the liquidity will be created 203 | /// @param tickLower The lower tick of the position in which to add liquidity 204 | /// @param tickUpper The upper tick of the position in which to add liquidity 205 | /// @param amount The amount of liquidity to mint 206 | /// @param data Any data that should be passed through to the callback 207 | /// @return amount0 The amount of token0 that was paid to mint the given amount of liquidity. Matches the value in the callback 208 | /// @return amount1 The amount of token1 that was paid to mint the given amount of liquidity. Matches the value in the callback 209 | function mint( 210 | address recipient, 211 | int24 tickLower, 212 | int24 tickUpper, 213 | uint128 amount, 214 | bytes calldata data 215 | ) external returns (uint256 amount0, uint256 amount1); 216 | 217 | /// @notice Collects tokens owed to a position 218 | /// @dev Does not recompute fees earned, which must be done either via mint or burn of any amount of liquidity. 219 | /// Collect must be called by the position owner. To withdraw only token0 or only token1, amount0Requested or 220 | /// amount1Requested may be set to zero. To withdraw all tokens owed, caller may pass any value greater than the 221 | /// actual tokens owed, e.g. type(uint128).max. Tokens owed may be from accumulated swap fees or burned liquidity. 222 | /// @param recipient The address which should receive the fees collected 223 | /// @param tickLower The lower tick of the position for which to collect fees 224 | /// @param tickUpper The upper tick of the position for which to collect fees 225 | /// @param amount0Requested How much token0 should be withdrawn from the fees owed 226 | /// @param amount1Requested How much token1 should be withdrawn from the fees owed 227 | /// @return amount0 The amount of fees collected in token0 228 | /// @return amount1 The amount of fees collected in token1 229 | function collect( 230 | address recipient, 231 | int24 tickLower, 232 | int24 tickUpper, 233 | uint128 amount0Requested, 234 | uint128 amount1Requested 235 | ) external returns (uint128 amount0, uint128 amount1); 236 | 237 | /// @notice Burn liquidity from the sender and account tokens owed for the liquidity to the position 238 | /// @dev Can be used to trigger a recalculation of fees owed to a position by calling with an amount of 0 239 | /// @dev Fees must be collected separately via a call to #collect 240 | /// @param tickLower The lower tick of the position for which to burn liquidity 241 | /// @param tickUpper The upper tick of the position for which to burn liquidity 242 | /// @param amount How much liquidity to burn 243 | /// @return amount0 The amount of token0 sent to the recipient 244 | /// @return amount1 The amount of token1 sent to the recipient 245 | function burn( 246 | int24 tickLower, 247 | int24 tickUpper, 248 | uint128 amount 249 | ) external returns (uint256 amount0, uint256 amount1); 250 | 251 | /// @notice Swap token0 for token1, or token1 for token0 252 | /// @dev The caller of this method receives a callback in the form of IUniswapV3SwapCallback#uniswapV3SwapCallback 253 | /// @param recipient The address to receive the output of the swap 254 | /// @param zeroForOne The direction of the swap, true for token0 to token1, false for token1 to token0 255 | /// @param amountSpecified The amount of the swap, which implicitly configures the swap as exact input (positive), or exact output (negative) 256 | /// @param sqrtPriceLimitX96 The Q64.96 sqrt price limit. If zero for one, the price cannot be less than this 257 | /// value after the swap. If one for zero, the price cannot be greater than this value after the swap 258 | /// @param data Any data to be passed through to the callback 259 | /// @return amount0 The delta of the balance of token0 of the pool, exact when negative, minimum when positive 260 | /// @return amount1 The delta of the balance of token1 of the pool, exact when negative, minimum when positive 261 | function swap( 262 | address recipient, 263 | bool zeroForOne, 264 | int256 amountSpecified, 265 | uint160 sqrtPriceLimitX96, 266 | bytes calldata data 267 | ) external returns (int256 amount0, int256 amount1); 268 | 269 | /// @notice Receive token0 and/or token1 and pay it back, plus a fee, in the callback 270 | /// @dev The caller of this method receives a callback in the form of IUniswapV3FlashCallback#uniswapV3FlashCallback 271 | /// @dev Can be used to donate underlying tokens pro-rata to currently in-range liquidity providers by calling 272 | /// with 0 amount{0,1} and sending the donation amount(s) from the callback 273 | /// @param recipient The address which will receive the token0 and token1 amounts 274 | /// @param amount0 The amount of token0 to send 275 | /// @param amount1 The amount of token1 to send 276 | /// @param data Any data to be passed through to the callback 277 | function flash( 278 | address recipient, 279 | uint256 amount0, 280 | uint256 amount1, 281 | bytes calldata data 282 | ) external; 283 | 284 | /// @notice Increase the maximum number of price and liquidity observations that this pool will store 285 | /// @dev This method is no-op if the pool already has an observationCardinalityNext greater than or equal to 286 | /// the input observationCardinalityNext. 287 | /// @param observationCardinalityNext The desired minimum number of observations for the pool to store 288 | function increaseObservationCardinalityNext(uint16 observationCardinalityNext) external; 289 | } 290 | 291 | /// @title Permissioned pool actions 292 | /// @notice Contains pool methods that may only be called by the factory owner 293 | interface IUniswapV3PoolOwnerActions { 294 | /// @notice Set the denominator of the protocol's % share of the fees 295 | /// @param feeProtocol0 new protocol fee for token0 of the pool 296 | /// @param feeProtocol1 new protocol fee for token1 of the pool 297 | function setFeeProtocol(uint8 feeProtocol0, uint8 feeProtocol1) external; 298 | 299 | /// @notice Collect the protocol fee accrued to the pool 300 | /// @param recipient The address to which collected protocol fees should be sent 301 | /// @param amount0Requested The maximum amount of token0 to send, can be 0 to collect fees in only token1 302 | /// @param amount1Requested The maximum amount of token1 to send, can be 0 to collect fees in only token0 303 | /// @return amount0 The protocol fee collected in token0 304 | /// @return amount1 The protocol fee collected in token1 305 | function collectProtocol( 306 | address recipient, 307 | uint128 amount0Requested, 308 | uint128 amount1Requested 309 | ) external returns (uint128 amount0, uint128 amount1); 310 | } 311 | 312 | /// @title Events emitted by a pool 313 | /// @notice Contains all events emitted by the pool 314 | interface IUniswapV3PoolEvents { 315 | /// @notice Emitted exactly once by a pool when #initialize is first called on the pool 316 | /// @dev Mint/Burn/Swap cannot be emitted by the pool before Initialize 317 | /// @param sqrtPriceX96 The initial sqrt price of the pool, as a Q64.96 318 | /// @param tick The initial tick of the pool, i.e. log base 1.0001 of the starting price of the pool 319 | event Initialize(uint160 sqrtPriceX96, int24 tick); 320 | 321 | /// @notice Emitted when liquidity is minted for a given position 322 | /// @param sender The address that minted the liquidity 323 | /// @param owner The owner of the position and recipient of any minted liquidity 324 | /// @param tickLower The lower tick of the position 325 | /// @param tickUpper The upper tick of the position 326 | /// @param amount The amount of liquidity minted to the position range 327 | /// @param amount0 How much token0 was required for the minted liquidity 328 | /// @param amount1 How much token1 was required for the minted liquidity 329 | event Mint( 330 | address sender, 331 | address indexed owner, 332 | int24 indexed tickLower, 333 | int24 indexed tickUpper, 334 | uint128 amount, 335 | uint256 amount0, 336 | uint256 amount1 337 | ); 338 | 339 | /// @notice Emitted when fees are collected by the owner of a position 340 | /// @dev Collect events may be emitted with zero amount0 and amount1 when the caller chooses not to collect fees 341 | /// @param owner The owner of the position for which fees are collected 342 | /// @param tickLower The lower tick of the position 343 | /// @param tickUpper The upper tick of the position 344 | /// @param amount0 The amount of token0 fees collected 345 | /// @param amount1 The amount of token1 fees collected 346 | event Collect( 347 | address indexed owner, 348 | address recipient, 349 | int24 indexed tickLower, 350 | int24 indexed tickUpper, 351 | uint128 amount0, 352 | uint128 amount1 353 | ); 354 | 355 | /// @notice Emitted when a position's liquidity is removed 356 | /// @dev Does not withdraw any fees earned by the liquidity position, which must be withdrawn via #collect 357 | /// @param owner The owner of the position for which liquidity is removed 358 | /// @param tickLower The lower tick of the position 359 | /// @param tickUpper The upper tick of the position 360 | /// @param amount The amount of liquidity to remove 361 | /// @param amount0 The amount of token0 withdrawn 362 | /// @param amount1 The amount of token1 withdrawn 363 | event Burn( 364 | address indexed owner, 365 | int24 indexed tickLower, 366 | int24 indexed tickUpper, 367 | uint128 amount, 368 | uint256 amount0, 369 | uint256 amount1 370 | ); 371 | 372 | /// @notice Emitted by the pool for any swaps between token0 and token1 373 | /// @param sender The address that initiated the swap call, and that received the callback 374 | /// @param recipient The address that received the output of the swap 375 | /// @param amount0 The delta of the token0 balance of the pool 376 | /// @param amount1 The delta of the token1 balance of the pool 377 | /// @param sqrtPriceX96 The sqrt(price) of the pool after the swap, as a Q64.96 378 | /// @param liquidity The liquidity of the pool after the swap 379 | /// @param tick The log base 1.0001 of price of the pool after the swap 380 | event Swap( 381 | address indexed sender, 382 | address indexed recipient, 383 | int256 amount0, 384 | int256 amount1, 385 | uint160 sqrtPriceX96, 386 | uint128 liquidity, 387 | int24 tick 388 | ); 389 | 390 | /// @notice Emitted by the pool for any flashes of token0/token1 391 | /// @param sender The address that initiated the swap call, and that received the callback 392 | /// @param recipient The address that received the tokens from flash 393 | /// @param amount0 The amount of token0 that was flashed 394 | /// @param amount1 The amount of token1 that was flashed 395 | /// @param paid0 The amount of token0 paid for the flash, which can exceed the amount0 plus the fee 396 | /// @param paid1 The amount of token1 paid for the flash, which can exceed the amount1 plus the fee 397 | event Flash( 398 | address indexed sender, 399 | address indexed recipient, 400 | uint256 amount0, 401 | uint256 amount1, 402 | uint256 paid0, 403 | uint256 paid1 404 | ); 405 | 406 | /// @notice Emitted by the pool for increases to the number of observations that can be stored 407 | /// @dev observationCardinalityNext is not the observation cardinality until an observation is written at the index 408 | /// just before a mint/swap/burn. 409 | /// @param observationCardinalityNextOld The previous value of the next observation cardinality 410 | /// @param observationCardinalityNextNew The updated value of the next observation cardinality 411 | event IncreaseObservationCardinalityNext( 412 | uint16 observationCardinalityNextOld, 413 | uint16 observationCardinalityNextNew 414 | ); 415 | 416 | /// @notice Emitted when the protocol fee is changed by the pool 417 | /// @param feeProtocol0Old The previous value of the token0 protocol fee 418 | /// @param feeProtocol1Old The previous value of the token1 protocol fee 419 | /// @param feeProtocol0New The updated value of the token0 protocol fee 420 | /// @param feeProtocol1New The updated value of the token1 protocol fee 421 | event SetFeeProtocol(uint8 feeProtocol0Old, uint8 feeProtocol1Old, uint8 feeProtocol0New, uint8 feeProtocol1New); 422 | 423 | /// @notice Emitted when the collected protocol fees are withdrawn by the factory owner 424 | /// @param sender The address that collects the protocol fees 425 | /// @param recipient The address that receives the collected protocol fees 426 | /// @param amount0 The amount of token0 protocol fees that is withdrawn 427 | /// @param amount0 The amount of token1 protocol fees that is withdrawn 428 | event CollectProtocol(address indexed sender, address indexed recipient, uint128 amount0, uint128 amount1); 429 | } 430 | 431 | /// @title The interface for a Uniswap V3 Pool 432 | /// @notice A Uniswap pool facilitates swapping and automated market making between any two assets that strictly conform 433 | /// to the ERC20 specification 434 | /// @dev The pool interface is broken up into many smaller pieces 435 | interface IUniswapV3Pool is 436 | IUniswapV3PoolImmutables, 437 | IUniswapV3PoolState, 438 | IUniswapV3PoolDerivedState, 439 | IUniswapV3PoolActions, 440 | IUniswapV3PoolOwnerActions, 441 | IUniswapV3PoolEvents 442 | { 443 | 444 | } 445 | 446 | /// @title Callback for IUniswapV3PoolActions#flash 447 | /// @notice Any contract that calls IUniswapV3PoolActions#flash must implement this interface 448 | interface IUniswapV3FlashCallback { 449 | /// @notice Called to `msg.sender` after transferring to the recipient from IUniswapV3Pool#flash. 450 | /// @dev In the implementation you must repay the pool the tokens sent by flash plus the computed fee amounts. 451 | /// The caller of this method must be checked to be a UniswapV3Pool deployed by the canonical UniswapV3Factory. 452 | /// @param fee0 The fee amount in token0 due to the pool by the end of the flash 453 | /// @param fee1 The fee amount in token1 due to the pool by the end of the flash 454 | /// @param data Any data passed through by the caller via the IUniswapV3PoolActions#flash call 455 | function uniswapV3FlashCallback( 456 | uint256 fee0, 457 | uint256 fee1, 458 | bytes calldata data 459 | ) external; 460 | } 461 | 462 | // OpenZeppelin Contracts (last updated v4.6.0) (token/ERC20/IERC20.sol) 463 | 464 | /** 465 | * @dev Interface of the ERC20 standard as defined in the EIP. 466 | */ 467 | interface IERC20 { 468 | /** 469 | * @dev Emitted when `value` tokens are moved from one account (`from`) to 470 | * another (`to`). 471 | * 472 | * Note that `value` may be zero. 473 | */ 474 | event Transfer(address indexed from, address indexed to, uint256 value); 475 | 476 | /** 477 | * @dev Emitted when the allowance of a `spender` for an `owner` is set by 478 | * a call to {approve}. `value` is the new allowance. 479 | */ 480 | event Approval(address indexed owner, address indexed spender, uint256 value); 481 | 482 | /** 483 | * @dev Returns the amount of tokens in existence. 484 | */ 485 | function totalSupply() external view returns (uint256); 486 | 487 | /** 488 | * @dev Returns the amount of tokens owned by `account`. 489 | */ 490 | function balanceOf(address account) external view returns (uint256); 491 | 492 | /** 493 | * @dev Moves `amount` tokens from the caller's account to `to`. 494 | * 495 | * Returns a boolean value indicating whether the operation succeeded. 496 | * 497 | * Emits a {Transfer} event. 498 | */ 499 | function transfer(address to, uint256 amount) external returns (bool); 500 | 501 | /** 502 | * @dev Returns the remaining number of tokens that `spender` will be 503 | * allowed to spend on behalf of `owner` through {transferFrom}. This is 504 | * zero by default. 505 | * 506 | * This value changes when {approve} or {transferFrom} are called. 507 | */ 508 | function allowance(address owner, address spender) external view returns (uint256); 509 | 510 | /** 511 | * @dev Sets `amount` as the allowance of `spender` over the caller's tokens. 512 | * 513 | * Returns a boolean value indicating whether the operation succeeded. 514 | * 515 | * IMPORTANT: Beware that changing an allowance with this method brings the risk 516 | * that someone may use both the old and the new allowance by unfortunate 517 | * transaction ordering. One possible solution to mitigate this race 518 | * condition is to first reduce the spender's allowance to 0 and set the 519 | * desired value afterwards: 520 | * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729 521 | * 522 | * Emits an {Approval} event. 523 | */ 524 | function approve(address spender, uint256 amount) external returns (bool); 525 | 526 | /** 527 | * @dev Moves `amount` tokens from `from` to `to` using the 528 | * allowance mechanism. `amount` is then deducted from the caller's 529 | * allowance. 530 | * 531 | * Returns a boolean value indicating whether the operation succeeded. 532 | * 533 | * Emits a {Transfer} event. 534 | */ 535 | function transferFrom( 536 | address from, 537 | address to, 538 | uint256 amount 539 | ) external returns (bool); 540 | } 541 | 542 | // OpenZeppelin Contracts (last updated v4.7.0) (token/ERC20/utils/SafeERC20.sol) 543 | 544 | // OpenZeppelin Contracts v4.4.1 (token/ERC20/extensions/draft-IERC20Permit.sol) 545 | 546 | /** 547 | * @dev Interface of the ERC20 Permit extension allowing approvals to be made via signatures, as defined in 548 | * https://eips.ethereum.org/EIPS/eip-2612[EIP-2612]. 549 | * 550 | * Adds the {permit} method, which can be used to change an account's ERC20 allowance (see {IERC20-allowance}) by 551 | * presenting a message signed by the account. By not relying on {IERC20-approve}, the token holder account doesn't 552 | * need to send a transaction, and thus is not required to hold Ether at all. 553 | */ 554 | interface IERC20Permit { 555 | /** 556 | * @dev Sets `value` as the allowance of `spender` over ``owner``'s tokens, 557 | * given ``owner``'s signed approval. 558 | * 559 | * IMPORTANT: The same issues {IERC20-approve} has related to transaction 560 | * ordering also apply here. 561 | * 562 | * Emits an {Approval} event. 563 | * 564 | * Requirements: 565 | * 566 | * - `spender` cannot be the zero address. 567 | * - `deadline` must be a timestamp in the future. 568 | * - `v`, `r` and `s` must be a valid `secp256k1` signature from `owner` 569 | * over the EIP712-formatted function arguments. 570 | * - the signature must use ``owner``'s current nonce (see {nonces}). 571 | * 572 | * For more information on the signature format, see the 573 | * https://eips.ethereum.org/EIPS/eip-2612#specification[relevant EIP 574 | * section]. 575 | */ 576 | function permit( 577 | address owner, 578 | address spender, 579 | uint256 value, 580 | uint256 deadline, 581 | uint8 v, 582 | bytes32 r, 583 | bytes32 s 584 | ) external; 585 | 586 | /** 587 | * @dev Returns the current nonce for `owner`. This value must be 588 | * included whenever a signature is generated for {permit}. 589 | * 590 | * Every successful call to {permit} increases ``owner``'s nonce by one. This 591 | * prevents a signature from being used multiple times. 592 | */ 593 | function nonces(address owner) external view returns (uint256); 594 | 595 | /** 596 | * @dev Returns the domain separator used in the encoding of the signature for {permit}, as defined by {EIP712}. 597 | */ 598 | // solhint-disable-next-line func-name-mixedcase 599 | function DOMAIN_SEPARATOR() external view returns (bytes32); 600 | } 601 | 602 | // OpenZeppelin Contracts (last updated v4.7.0) (utils/Address.sol) 603 | 604 | /** 605 | * @dev Collection of functions related to the address type 606 | */ 607 | library Address { 608 | /** 609 | * @dev Returns true if `account` is a contract. 610 | * 611 | * [IMPORTANT] 612 | * ==== 613 | * It is unsafe to assume that an address for which this function returns 614 | * false is an externally-owned account (EOA) and not a contract. 615 | * 616 | * Among others, `isContract` will return false for the following 617 | * types of addresses: 618 | * 619 | * - an externally-owned account 620 | * - a contract in construction 621 | * - an address where a contract will be created 622 | * - an address where a contract lived, but was destroyed 623 | * ==== 624 | * 625 | * [IMPORTANT] 626 | * ==== 627 | * You shouldn't rely on `isContract` to protect against flash loan attacks! 628 | * 629 | * Preventing calls from contracts is highly discouraged. It breaks composability, breaks support for smart wallets 630 | * like Gnosis Safe, and does not provide security since it can be circumvented by calling from a contract 631 | * constructor. 632 | * ==== 633 | */ 634 | function isContract(address account) internal view returns (bool) { 635 | // This method relies on extcodesize/address.code.length, which returns 0 636 | // for contracts in construction, since the code is only stored at the end 637 | // of the constructor execution. 638 | 639 | return account.code.length > 0; 640 | } 641 | 642 | /** 643 | * @dev Replacement for Solidity's `transfer`: sends `amount` wei to 644 | * `recipient`, forwarding all available gas and reverting on errors. 645 | * 646 | * https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost 647 | * of certain opcodes, possibly making contracts go over the 2300 gas limit 648 | * imposed by `transfer`, making them unable to receive funds via 649 | * `transfer`. {sendValue} removes this limitation. 650 | * 651 | * https://diligence.consensys.net/posts/2019/09/stop-using-soliditys-transfer-now/[Learn more]. 652 | * 653 | * IMPORTANT: because control is transferred to `recipient`, care must be 654 | * taken to not create reentrancy vulnerabilities. Consider using 655 | * {ReentrancyGuard} or the 656 | * https://solidity.readthedocs.io/en/v0.5.11/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern]. 657 | */ 658 | function sendValue(address payable recipient, uint256 amount) internal { 659 | require(address(this).balance >= amount, "Address: insufficient balance"); 660 | 661 | (bool success, ) = recipient.call{value: amount}(""); 662 | require(success, "Address: unable to send value, recipient may have reverted"); 663 | } 664 | 665 | /** 666 | * @dev Performs a Solidity function call using a low level `call`. A 667 | * plain `call` is an unsafe replacement for a function call: use this 668 | * function instead. 669 | * 670 | * If `target` reverts with a revert reason, it is bubbled up by this 671 | * function (like regular Solidity function calls). 672 | * 673 | * Returns the raw returned data. To convert to the expected return value, 674 | * use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`]. 675 | * 676 | * Requirements: 677 | * 678 | * - `target` must be a contract. 679 | * - calling `target` with `data` must not revert. 680 | * 681 | * _Available since v3.1._ 682 | */ 683 | function functionCall(address target, bytes memory data) internal returns (bytes memory) { 684 | return functionCallWithValue(target, data, 0, "Address: low-level call failed"); 685 | } 686 | 687 | /** 688 | * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], but with 689 | * `errorMessage` as a fallback revert reason when `target` reverts. 690 | * 691 | * _Available since v3.1._ 692 | */ 693 | function functionCall( 694 | address target, 695 | bytes memory data, 696 | string memory errorMessage 697 | ) internal returns (bytes memory) { 698 | return functionCallWithValue(target, data, 0, errorMessage); 699 | } 700 | 701 | /** 702 | * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], 703 | * but also transferring `value` wei to `target`. 704 | * 705 | * Requirements: 706 | * 707 | * - the calling contract must have an ETH balance of at least `value`. 708 | * - the called Solidity function must be `payable`. 709 | * 710 | * _Available since v3.1._ 711 | */ 712 | function functionCallWithValue( 713 | address target, 714 | bytes memory data, 715 | uint256 value 716 | ) internal returns (bytes memory) { 717 | return functionCallWithValue(target, data, value, "Address: low-level call with value failed"); 718 | } 719 | 720 | /** 721 | * @dev Same as {xref-Address-functionCallWithValue-address-bytes-uint256-}[`functionCallWithValue`], but 722 | * with `errorMessage` as a fallback revert reason when `target` reverts. 723 | * 724 | * _Available since v3.1._ 725 | */ 726 | function functionCallWithValue( 727 | address target, 728 | bytes memory data, 729 | uint256 value, 730 | string memory errorMessage 731 | ) internal returns (bytes memory) { 732 | require(address(this).balance >= value, "Address: insufficient balance for call"); 733 | (bool success, bytes memory returndata) = target.call{value: value}(data); 734 | return verifyCallResultFromTarget(target, success, returndata, errorMessage); 735 | } 736 | 737 | /** 738 | * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], 739 | * but performing a static call. 740 | * 741 | * _Available since v3.3._ 742 | */ 743 | function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) { 744 | return functionStaticCall(target, data, "Address: low-level static call failed"); 745 | } 746 | 747 | /** 748 | * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`], 749 | * but performing a static call. 750 | * 751 | * _Available since v3.3._ 752 | */ 753 | function functionStaticCall( 754 | address target, 755 | bytes memory data, 756 | string memory errorMessage 757 | ) internal view returns (bytes memory) { 758 | (bool success, bytes memory returndata) = target.staticcall(data); 759 | return verifyCallResultFromTarget(target, success, returndata, errorMessage); 760 | } 761 | 762 | /** 763 | * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], 764 | * but performing a delegate call. 765 | * 766 | * _Available since v3.4._ 767 | */ 768 | function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) { 769 | return functionDelegateCall(target, data, "Address: low-level delegate call failed"); 770 | } 771 | 772 | /** 773 | * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`], 774 | * but performing a delegate call. 775 | * 776 | * _Available since v3.4._ 777 | */ 778 | function functionDelegateCall( 779 | address target, 780 | bytes memory data, 781 | string memory errorMessage 782 | ) internal returns (bytes memory) { 783 | (bool success, bytes memory returndata) = target.delegatecall(data); 784 | return verifyCallResultFromTarget(target, success, returndata, errorMessage); 785 | } 786 | 787 | /** 788 | * @dev Tool to verify that a low level call to smart-contract was successful, and revert (either by bubbling 789 | * the revert reason or using the provided one) in case of unsuccessful call or if target was not a contract. 790 | * 791 | * _Available since v4.8._ 792 | */ 793 | function verifyCallResultFromTarget( 794 | address target, 795 | bool success, 796 | bytes memory returndata, 797 | string memory errorMessage 798 | ) internal view returns (bytes memory) { 799 | if (success) { 800 | if (returndata.length == 0) { 801 | // only check isContract if the call was successful and the return data is empty 802 | // otherwise we already know that it was a contract 803 | require(isContract(target), "Address: call to non-contract"); 804 | } 805 | return returndata; 806 | } else { 807 | _revert(returndata, errorMessage); 808 | } 809 | } 810 | 811 | /** 812 | * @dev Tool to verify that a low level call was successful, and revert if it wasn't, either by bubbling the 813 | * revert reason or using the provided one. 814 | * 815 | * _Available since v4.3._ 816 | */ 817 | function verifyCallResult( 818 | bool success, 819 | bytes memory returndata, 820 | string memory errorMessage 821 | ) internal pure returns (bytes memory) { 822 | if (success) { 823 | return returndata; 824 | } else { 825 | _revert(returndata, errorMessage); 826 | } 827 | } 828 | 829 | function _revert(bytes memory returndata, string memory errorMessage) private pure { 830 | // Look for revert reason and bubble it up if present 831 | if (returndata.length > 0) { 832 | // The easiest way to bubble the revert reason is using memory via assembly 833 | /// @solidity memory-safe-assembly 834 | assembly { 835 | let returndata_size := mload(returndata) 836 | revert(add(32, returndata), returndata_size) 837 | } 838 | } else { 839 | revert(errorMessage); 840 | } 841 | } 842 | } 843 | 844 | /** 845 | * @title SafeERC20 846 | * @dev Wrappers around ERC20 operations that throw on failure (when the token 847 | * contract returns false). Tokens that return no value (and instead revert or 848 | * throw on failure) are also supported, non-reverting calls are assumed to be 849 | * successful. 850 | * To use this library you can add a `using SafeERC20 for IERC20;` statement to your contract, 851 | * which allows you to call the safe operations as `token.safeTransfer(...)`, etc. 852 | */ 853 | library SafeERC20 { 854 | using Address for address; 855 | 856 | function safeTransfer( 857 | IERC20 token, 858 | address to, 859 | uint256 value 860 | ) internal { 861 | _callOptionalReturn(token, abi.encodeWithSelector(token.transfer.selector, to, value)); 862 | } 863 | 864 | function safeTransferFrom( 865 | IERC20 token, 866 | address from, 867 | address to, 868 | uint256 value 869 | ) internal { 870 | _callOptionalReturn(token, abi.encodeWithSelector(token.transferFrom.selector, from, to, value)); 871 | } 872 | 873 | /** 874 | * @dev Deprecated. This function has issues similar to the ones found in 875 | * {IERC20-approve}, and its usage is discouraged. 876 | * 877 | * Whenever possible, use {safeIncreaseAllowance} and 878 | * {safeDecreaseAllowance} instead. 879 | */ 880 | function safeApprove( 881 | IERC20 token, 882 | address spender, 883 | uint256 value 884 | ) internal { 885 | // safeApprove should only be called when setting an initial allowance, 886 | // or when resetting it to zero. To increase and decrease it, use 887 | // 'safeIncreaseAllowance' and 'safeDecreaseAllowance' 888 | require( 889 | (value == 0) || (token.allowance(address(this), spender) == 0), 890 | "SafeERC20: approve from non-zero to non-zero allowance" 891 | ); 892 | _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, value)); 893 | } 894 | 895 | function safeIncreaseAllowance( 896 | IERC20 token, 897 | address spender, 898 | uint256 value 899 | ) internal { 900 | uint256 newAllowance = token.allowance(address(this), spender) + value; 901 | _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance)); 902 | } 903 | 904 | function safeDecreaseAllowance( 905 | IERC20 token, 906 | address spender, 907 | uint256 value 908 | ) internal { 909 | unchecked { 910 | uint256 oldAllowance = token.allowance(address(this), spender); 911 | require(oldAllowance >= value, "SafeERC20: decreased allowance below zero"); 912 | uint256 newAllowance = oldAllowance - value; 913 | _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance)); 914 | } 915 | } 916 | 917 | function safePermit( 918 | IERC20Permit token, 919 | address owner, 920 | address spender, 921 | uint256 value, 922 | uint256 deadline, 923 | uint8 v, 924 | bytes32 r, 925 | bytes32 s 926 | ) internal { 927 | uint256 nonceBefore = token.nonces(owner); 928 | token.permit(owner, spender, value, deadline, v, r, s); 929 | uint256 nonceAfter = token.nonces(owner); 930 | require(nonceAfter == nonceBefore + 1, "SafeERC20: permit did not succeed"); 931 | } 932 | 933 | /** 934 | * @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement 935 | * on the return value: the return value is optional (but if data is returned, it must not be false). 936 | * @param token The token targeted by the call. 937 | * @param data The call data (encoded using abi.encode or one of its variants). 938 | */ 939 | function _callOptionalReturn(IERC20 token, bytes memory data) private { 940 | // We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since 941 | // we're implementing it ourselves. We use {Address.functionCall} to perform this call, which verifies that 942 | // the target address contains contract code and also asserts for success in the low-level call. 943 | 944 | bytes memory returndata = address(token).functionCall(data, "SafeERC20: low-level call failed"); 945 | if (returndata.length > 0) { 946 | // Return data is optional 947 | require(abi.decode(returndata, (bool)), "SafeERC20: ERC20 operation did not succeed"); 948 | } 949 | } 950 | } 951 | 952 | /* 953 | * @title Solidity Bytes Arrays Utils 954 | * @author Gonçalo Sá 955 | * 956 | * @dev Bytes tightly packed arrays utility library for ethereum contracts written in Solidity. 957 | * The library lets you concatenate, slice and type cast bytes arrays both in memory and storage. 958 | */ 959 | 960 | library BytesLib { 961 | 962 | function slice( 963 | bytes memory _bytes, 964 | uint256 _start, 965 | uint256 _length 966 | ) internal pure returns (bytes memory) { 967 | require(_length + 31 >= _length, 'slice_overflow'); 968 | require(_start + _length >= _start, 'slice_overflow'); 969 | require(_bytes.length >= _start + _length, 'slice_outOfBounds'); 970 | 971 | bytes memory tempBytes; 972 | 973 | assembly { 974 | switch iszero(_length) 975 | case 0 { 976 | // Get a location of some free memory and store it in tempBytes as 977 | // Solidity does for memory variables. 978 | tempBytes := mload(0x40) 979 | 980 | // The first word of the slice result is potentially a partial 981 | // word read from the original array. To read it, we calculate 982 | // the length of that partial word and start copying that many 983 | // bytes into the array. The first word we copy will start with 984 | // data we don't care about, but the last `lengthmod` bytes will 985 | // land at the beginning of the contents of the new array. When 986 | // we're done copying, we overwrite the full first word with 987 | // the actual length of the slice. 988 | let lengthmod := and(_length, 31) 989 | 990 | // cete operation and eqiuivaut à mod(_length, 32)!! 991 | // on obtiens ainsi le bout de _bytes qui va rester une fois qu'on l'aura fetch par block de 32 bytes 992 | 993 | // The multiplication in the next line is necessary 994 | // because when slicing multiples of 32 bytes (lengthmod == 0) 995 | // the following copy loop was copying the origin's length 996 | // and then ending prematurely not copying everything it should. 997 | let mc := add(add(tempBytes, lengthmod), mul(0x20, iszero(lengthmod))) 998 | 999 | // ici, mc est soit == fmp + lengthmod, soit == fmp + 32 1000 | 1001 | let end := add(mc, _length) 1002 | 1003 | for { 1004 | // The multiplication in the next line has the same exact purpose 1005 | // as the one above. 1006 | let cc := add(add(add(_bytes, lengthmod), mul(0x20, iszero(lengthmod))), _start) 1007 | } lt(mc, end) { 1008 | mc := add(mc, 0x20) 1009 | cc := add(cc, 0x20) 1010 | } { 1011 | mstore(mc, mload(cc)) 1012 | } 1013 | 1014 | mstore(tempBytes, _length) 1015 | 1016 | //update free-memory pointer 1017 | //allocating the array padded to 32 bytes like the compiler does now 1018 | mstore(0x40, and(add(mc, 31), not(31))) 1019 | } 1020 | //if we want a zero-length slice let's just return a zero-length array 1021 | default { 1022 | tempBytes := mload(0x40) 1023 | //zero out the 32 bytes slice we are about to return 1024 | //we need to do it because Solidity does not garbage collect 1025 | mstore(tempBytes, 0) 1026 | 1027 | mstore(0x40, add(tempBytes, 0x20)) 1028 | } 1029 | } 1030 | 1031 | return tempBytes; 1032 | } 1033 | 1034 | function toAddress(bytes memory _bytes, uint256 _start) internal pure returns (address) { 1035 | 1036 | require(_start + 20 >= _start, 'toAddress_overflow'); 1037 | require(_bytes.length >= _start + 20, 'toAddress_outOfBounds'); 1038 | address tempAddress; 1039 | 1040 | assembly { 1041 | tempAddress := div(mload(add(add(_bytes, 0x20), _start)), 0x1000000000000000000000000) 1042 | } 1043 | 1044 | return tempAddress; 1045 | } 1046 | 1047 | function toUint24(bytes memory _bytes, uint256 _start) internal pure returns (uint24) { 1048 | require(_start + 3 >= _start, 'toUint24_overflow'); 1049 | require(_bytes.length >= _start + 3, 'toUint24_outOfBounds'); 1050 | uint24 tempUint; 1051 | 1052 | assembly { 1053 | 1054 | 1055 | tempUint := mload(add(add(_bytes, 0x3), _start)) 1056 | // le memory slot qui est load ici peut ressembler a SSSSSSSVVVVVVVVVVVV (size, value) ou on load une partie du size word 1057 | // tout ce qu'on sait c'est que les 3 derniers bytes de ce word == notre uint 24 1058 | } 1059 | 1060 | return tempUint; // l'implicit convertion uint256 => uint24 fait le taff en ne gardant que les 3 derniers bytes 1061 | } 1062 | 1063 | function toUint256(bytes memory _bytes, uint256 _start) internal pure returns (uint256 value){ 1064 | require(_start + 20 >= _start, 'toUint256_overflow'); 1065 | require(_bytes.length >= _start + 20, 'toUint256_outOfBounds'); 1066 | 1067 | assembly { 1068 | value := mload(add(add(_bytes, 0x20), _start)) 1069 | } 1070 | } 1071 | } 1072 | 1073 | 1074 | contract RepaimentStorage { 1075 | 1076 | // loans storage. Erased after each flashSwap call with repayLoans() 1077 | mapping(address => Loan[]) loans; 1078 | mapping(address => uint256) totalBorrowPerToken; 1079 | address[] public borrowedTokens; 1080 | 1081 | struct Loan { 1082 | address pool; 1083 | address token; 1084 | uint256 amount; 1085 | uint256 fee; 1086 | } 1087 | 1088 | // use this function if you want to du multiple flashloans 1089 | function cleanupRepaimentStorage() internal { 1090 | 1091 | if (borrowedTokens.length > 0) { 1092 | 1093 | for (uint256 i = 0; i < borrowedTokens.length; i++) { 1094 | 1095 | address token = borrowedTokens[i]; 1096 | delete totalBorrowPerToken[token]; 1097 | delete loans[token]; 1098 | } 1099 | } 1100 | delete borrowedTokens; 1101 | } 1102 | } 1103 | 1104 | 1105 | 1106 | 1107 | abstract contract UniV3FlashSwap is IUniswapV3FlashCallback, RepaimentStorage{ 1108 | 1109 | using BytesLib for bytes; 1110 | using SafeERC20 for IERC20; 1111 | 1112 | uint256 private constant ONEFLASHSWAPUNIV3 = 84; 1113 | address private constant FACTORYV3 = 0x1F98431c8aD98523631AE4a59f267346ea31F984; 1114 | 1115 | // pool storage 1116 | mapping(address => uint256) public numberOfPoolForAsset; 1117 | mapping(address => mapping(uint256 => address)) public poolsForAsset; 1118 | 1119 | event FlashSwap( 1120 | address indexed token, 1121 | uint256 indexed amount, 1122 | uint256 indexed feePaid 1123 | ); 1124 | 1125 | constructor(){ 1126 | initializeKnownPools(); 1127 | } 1128 | 1129 | // should be overriden if you want to use other pools 1130 | function initializeKnownPools() internal virtual { 1131 | 1132 | address WETH = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2; 1133 | address DAI = 0x6B175474E89094C44Da98b954EedeAC495271d0F; 1134 | address USDC = 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48; 1135 | address USDT = 0xdAC17F958D2ee523a2206206994597C13D831ec7; 1136 | 1137 | numberOfPoolForAsset[WETH] = 5; 1138 | numberOfPoolForAsset[DAI] = 2; 1139 | numberOfPoolForAsset[USDC] = 6; 1140 | numberOfPoolForAsset[USDT] = 3; 1141 | 1142 | poolsForAsset[WETH][1] = 0x88e6A0c2dDD26FEEb64F039a2c41296FcB3f5640; // USDCWETH005 1143 | poolsForAsset[WETH][2] = 0x4585FE77225b41b697C938B018E2Ac67Ac5a20c0; // WBTCWETH005 1144 | poolsForAsset[WETH][3] = 0x8ad599c3A0ff1De082011EFDDc58f1908eb6e6D8; // USDCWETH03 1145 | poolsForAsset[WETH][4] = 0xCBCdF9626bC03E24f779434178A73a0B4bad62eD; // WBTCWETH03 1146 | poolsForAsset[WETH][5] = 0x4e68Ccd3E89f51C3074ca5072bbAC773960dFa36; // WETHUSDT03 1147 | 1148 | poolsForAsset[DAI][1] = 0x5777d92f208679DB4b9778590Fa3CAB3aC9e2168; // DAIUSDC001 1149 | poolsForAsset[DAI][2] = 0x6c6Bc977E13Df9b0de53b251522280BB72383700; // DAIUSDC005 1150 | 1151 | poolsForAsset[USDC][1] = 0x5777d92f208679DB4b9778590Fa3CAB3aC9e2168; // DAIUSDC001 1152 | poolsForAsset[USDC][2] = 0x3416cF6C708Da44DB2624D63ea0AAef7113527C6; // USDCUSDT001 1153 | poolsForAsset[USDC][3] = 0x88e6A0c2dDD26FEEb64F039a2c41296FcB3f5640; // USDCWETH005 1154 | poolsForAsset[USDC][4] = 0x6c6Bc977E13Df9b0de53b251522280BB72383700; // DAIUSDC005 1155 | poolsForAsset[USDC][5] = 0xc63B0708E2F7e69CB8A1df0e1389A98C35A76D52; // FRAXUSDC005 1156 | poolsForAsset[USDC][6] = 0x8ad599c3A0ff1De082011EFDDc58f1908eb6e6D8; // USDCWETH03 1157 | 1158 | poolsForAsset[USDT][1] = 0x3416cF6C708Da44DB2624D63ea0AAef7113527C6; // USDCUSDT001 1159 | poolsForAsset[USDT][2] = 0x11b815efB8f581194ae79006d24E0d814B7697F6; // WETHUSDT005 1160 | poolsForAsset[USDT][3] = 0x4e68Ccd3E89f51C3074ca5072bbAC773960dFa36; // WETHUSDT03 1161 | } 1162 | 1163 | function maxFlashSwap(address token) public view virtual returns(uint256 amount){ 1164 | if (numberOfPoolForAsset[token] == 0) return 0; 1165 | 1166 | for (uint256 i = 1; i <= numberOfPoolForAsset[token]; i++){ 1167 | amount += IERC20(token).balanceOf(poolsForAsset[token][i]); 1168 | } 1169 | } 1170 | 1171 | function estimateFlashSwapFee(address token, uint256 amount) public view virtual returns(uint256 feesEstimated){ 1172 | 1173 | address[] memory tokens = new address[](1); 1174 | uint256[] memory amounts = new uint256[](1); 1175 | tokens[0] = token; 1176 | amounts[0] = amount; 1177 | bytes memory flashSwapPath = _buildFlashSwapsArray(tokens, amounts); 1178 | 1179 | while (flashSwapPath.length > 0){ 1180 | (address pool, uint256 amount0, uint256 amount1) = decodeFirstFlashSwap(flashSwapPath); 1181 | uint256 _amount = amount0 > 0? amount0 : amount1; 1182 | uint256 fee = IUniswapV3Pool(pool).fee(); 1183 | feesEstimated += _amount * fee / 1e6 + 1; 1184 | flashSwapPath = skipFlashSwap(flashSwapPath); 1185 | } 1186 | } 1187 | 1188 | function _updateRepaimentStorage(address token, address pool, uint256 amount,uint256 fee) private { 1189 | if (totalBorrowPerToken[token] == 0) borrowedTokens.push(token); 1190 | loans[token].push() = Loan(pool, token, amount, fee); 1191 | totalBorrowPerToken[token] += (amount + fee); 1192 | } 1193 | 1194 | function uniswapV3FlashCallback( 1195 | uint256 fee0, 1196 | uint256 fee1, 1197 | bytes calldata data) external virtual override { 1198 | bytes memory flashSwapPath = data; 1199 | 1200 | (address pool, uint256 amount0, uint256 amount1) = decodeFirstFlashSwap(flashSwapPath); 1201 | require(msg.sender == pool); 1202 | 1203 | // edit repaiement storage 1204 | if (fee0 > 0){ 1205 | address token0 = IUniswapV3Pool(pool).token0(); 1206 | _updateRepaimentStorage(token0, pool, amount0, fee0); 1207 | } 1208 | if (fee1 > 0){ 1209 | address token1 = IUniswapV3Pool(pool).token1(); 1210 | _updateRepaimentStorage(token1, pool, amount1, fee1); 1211 | } 1212 | 1213 | // edit flashSwaps 1214 | flashSwapPath = skipFlashSwap(flashSwapPath); 1215 | _executeFlashSwaps(flashSwapPath); 1216 | } 1217 | 1218 | function repayLoans() private { 1219 | 1220 | for (uint256 i = 0; i < borrowedTokens.length; i++){ 1221 | 1222 | address token = borrowedTokens[i]; 1223 | Loan[] memory tokenLoans = loans[token]; 1224 | uint256 feeTotal; 1225 | uint256 amountTotal; 1226 | 1227 | // repay the loans 1228 | for (uint256 j; j < tokenLoans.length; j++){ 1229 | 1230 | Loan memory loan = tokenLoans[j]; 1231 | require( 1232 | IERC20(token).balanceOf(address(this)) >= (loan.amount + loan.fee), 1233 | "UniV3FlashSwap: repay loan: not enough balance to repay loan" 1234 | ); 1235 | feeTotal += loan.fee; 1236 | amountTotal += loan.amount; 1237 | IERC20(token).safeTransfer(loan.pool, loan.amount + loan.fee); 1238 | } 1239 | emit FlashSwap(token, amountTotal, feeTotal); 1240 | } 1241 | } 1242 | 1243 | function _buildFlashSwapsArray( 1244 | 1245 | address[] memory tokens, 1246 | uint256[] memory amounts) internal view returns(bytes memory flashSwapPath){ 1247 | 1248 | require(tokens.length == amounts.length, "UniV3FlashSwap: length of amounts and tokens doesnt match"); 1249 | 1250 | for (uint256 i = 0; i < tokens.length; i++){ 1251 | 1252 | address token = tokens[i]; 1253 | uint256 amount = amounts[i]; 1254 | 1255 | require(numberOfPoolForAsset[token] > 0, "UniV3FlashSwap: asset not supported"); 1256 | require(amount <= maxFlashSwap(token), "UniV3FlashSwap: amount to fs > maxFlashSwap"); 1257 | 1258 | uint256 counter = 0; 1259 | uint256 tempAmount = amount; 1260 | 1261 | // build the flashSwaps byte array 1262 | // [pair][amount0][amount1] 1263 | while (tempAmount > 0) { 1264 | 1265 | address nextPool = poolsForAsset[token][counter + 1]; 1266 | uint256 nextPoolBalance = IERC20(token).balanceOf(nextPool); 1267 | uint256 increment = nextPoolBalance >= tempAmount? tempAmount: nextPoolBalance; 1268 | uint256 amount0 = IUniswapV3Pool(nextPool).token0() == token? increment : 0; 1269 | uint256 amount1 = amount0 > 0? 0 : increment; 1270 | 1271 | if (flashSwapPath.length == 0){ 1272 | flashSwapPath = abi.encodePacked(nextPool, amount0, amount1); 1273 | continue; 1274 | } 1275 | 1276 | bool expendPath = true; 1277 | 1278 | assembly { 1279 | 1280 | let numberOfFlashSwaps := div(mload(flashSwapPath), ONEFLASHSWAPUNIV3) 1281 | let startPtr := add(flashSwapPath, 0x20) 1282 | 1283 | // check if the pool is already registered in flashSwapPath 1284 | for {let j := 0} lt(j, numberOfFlashSwaps) {j:= add(j, 1)}{ 1285 | let swapptr := add(startPtr, mul(j, ONEFLASHSWAPUNIV3)) 1286 | let pool := shr(96, mload(swapptr)) 1287 | 1288 | if eq(pool, nextPool) { 1289 | // if amount0 == 0 , store amount1 1290 | if iszero(amount0) {mstore(add(swapptr, 52), amount1)} 1291 | // if amount1 == 0 , store amount0 1292 | if iszero(amount1) {mstore(add(swapptr, 20), amount0)} 1293 | expendPath := 0 1294 | } 1295 | } 1296 | } 1297 | 1298 | if (expendPath) flashSwapPath = abi.encodePacked( 1299 | flashSwapPath, 1300 | nextPool, 1301 | amount0, 1302 | amount1); 1303 | 1304 | tempAmount -= increment; 1305 | ++counter; 1306 | } 1307 | } 1308 | } 1309 | 1310 | function _executeFlashSwaps(bytes memory flashSwapPath) private { 1311 | 1312 | // actually execute the flashSwaps here. reentrancy is desired 1313 | if (flashSwapPath.length > 0) { 1314 | (address pool, uint256 amount0, uint256 amount1) = decodeFirstFlashSwap(flashSwapPath); 1315 | IUniswapV3Pool(pool).flash(address(this), amount0, amount1, flashSwapPath); 1316 | } 1317 | // if the length is only 1, it means all the flashswaps are done 1318 | else { 1319 | onUniV3FlashSwap(); 1320 | repayLoans(); 1321 | } 1322 | } 1323 | 1324 | function flashSwap(address[] memory tokens, uint256[] memory amounts) internal { 1325 | bytes memory flashSwapPath = _buildFlashSwapsArray(tokens, amounts); 1326 | _executeFlashSwaps(flashSwapPath); 1327 | } 1328 | 1329 | function flashSwap(address token, uint256 amount) internal { 1330 | address[] memory tokens = new address[](1); 1331 | uint256[] memory amounts = new uint256[](1); 1332 | tokens[0] = token; 1333 | amounts[0] = amount; 1334 | flashSwap(tokens, amounts); 1335 | } 1336 | 1337 | function decodeFirstFlashSwap(bytes memory flashSwapPath) 1338 | private 1339 | pure 1340 | returns ( 1341 | address pool, 1342 | uint256 amount0, 1343 | uint256 amount1 1344 | ) { 1345 | pool = flashSwapPath.toAddress(0); 1346 | amount0 = flashSwapPath.toUint256(20); 1347 | amount1 = flashSwapPath.toUint256(52); 1348 | } 1349 | 1350 | function skipFlashSwap(bytes memory flashSwapPath) private pure returns (bytes memory){ 1351 | return flashSwapPath.slice(ONEFLASHSWAPUNIV3, flashSwapPath.length - ONEFLASHSWAPUNIV3); 1352 | } 1353 | 1354 | // should be overriden with your own logic 1355 | function onUniV3FlashSwap() internal virtual; 1356 | } 1357 | -------------------------------------------------------------------------------- /test/AaveV2FlashLoan.t.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.8.0; 2 | 3 | import "forge-std/Test.sol"; 4 | import "../src/AaveV2FlashLoan.sol"; 5 | import "forge-std/console2.sol"; 6 | import {SharedSetupMainnet} from "./SharedSetup.sol"; 7 | 8 | // tests done on mainnet 9 | 10 | contract Foo is AaveV2FlashLoan, SharedSetupMainnet { 11 | 12 | function maxFlashWeth() external { 13 | uint256 amountToLoan = maxFlashLoan(WETH); 14 | flashLoan(WETH, amountToLoan); 15 | } 16 | 17 | function maxFlashAll() external{ 18 | address[] memory tokens = new address[](4); 19 | uint256[] memory amounts = new uint256[](4); 20 | 21 | tokens[0] = DAI; 22 | tokens[1] = USDC; 23 | tokens[2] = WETH; 24 | tokens[3] = USDT; 25 | 26 | amounts[0] = maxFlashLoan(DAI); 27 | amounts[1] = maxFlashLoan(USDC); 28 | amounts[2] = maxFlashLoan(WETH); 29 | amounts[3] = maxFlashLoan(USDT); 30 | 31 | flashLoan(tokens, amounts); 32 | } 33 | 34 | function onAaveV2FlashLoan() override internal { 35 | console.log("=> borrowed DAI balance:", IERC20(DAI).balanceOf(address(this))); 36 | console.log("=> borrowed USDC balance:", IERC20(USDC).balanceOf(address(this))); 37 | console.log("=> borrowed WETH balance:", IERC20(WETH).balanceOf(address(this))); 38 | } 39 | } 40 | 41 | contract AaveV2FlashLoanTest is Test , SharedSetupMainnet{ 42 | using SafeERC20 for IERC20; 43 | 44 | Foo foo; 45 | 46 | function setUp() public { 47 | foo = new Foo(); 48 | vm.deal(address(foo), 0); 49 | } 50 | 51 | function testmaxFlashWeth() public { 52 | 53 | vm.startPrank(wethProvider); 54 | IERC20(WETH).safeTransfer(address(foo), IERC20(WETH).balanceOf(wethProvider)); 55 | vm.stopPrank(); 56 | 57 | uint256 maxAmount = foo.maxFlashLoan(WETH); 58 | console2.log("=> amount expected to borrow:", maxAmount); 59 | console2.log("=> estimated amount of fees:", foo.estimateFlashLoanFee(WETH, maxAmount)); 60 | 61 | foo.maxFlashWeth(); 62 | } 63 | 64 | function testmaxFlashAll() public { 65 | 66 | vm.startPrank(wethProvider); 67 | IERC20(WETH).safeTransfer(address(foo), IERC20(WETH).balanceOf(wethProvider)); 68 | vm.stopPrank(); 69 | 70 | vm.startPrank(usdcProvider); 71 | IERC20(USDC).safeTransfer(address(foo), IERC20(USDC).balanceOf(usdcProvider)); 72 | vm.stopPrank(); 73 | 74 | vm.startPrank(daiProvider); 75 | IERC20(DAI).safeTransfer(address(foo), IERC20(DAI).balanceOf(daiProvider)); 76 | vm.stopPrank(); 77 | 78 | vm.startPrank(usdtProvider); 79 | IERC20(USDT).safeTransfer(address(foo), IERC20(USDT).balanceOf(usdtProvider)); 80 | vm.stopPrank(); 81 | 82 | foo.maxFlashAll(); 83 | } 84 | } -------------------------------------------------------------------------------- /test/AaveV3FlashLoan.t.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.8.0; 2 | 3 | import "forge-std/Test.sol"; 4 | import "../src/AaveV3FlashLoan.sol"; 5 | import "forge-std/console2.sol"; 6 | import {SharedSetupAvax} from "./SharedSetup.sol"; 7 | 8 | // tests done on avax-main network 9 | // rpc: https://api.avax.network/ext/bc/C/rpc 10 | 11 | contract Foo is AaveV3FlashLoan , SharedSetupAvax{ 12 | 13 | function maxFlashWeth() external { 14 | uint256 amountToLoan = maxFlashLoan(WETH); 15 | flashLoan(WETH, amountToLoan); 16 | } 17 | 18 | function maxFlashAll() external{ 19 | address[] memory tokens = new address[](4); 20 | uint256[] memory amounts = new uint256[](4); 21 | tokens[0] = DAI; 22 | tokens[1] = USDC; 23 | tokens[2] = WETH; 24 | tokens[3] = USDT; 25 | 26 | amounts[0] = maxFlashLoan(DAI); 27 | amounts[1] = maxFlashLoan(USDC); 28 | amounts[2] = maxFlashLoan(WETH); 29 | amounts[3] = maxFlashLoan(USDT); 30 | 31 | flashLoan(tokens, amounts); 32 | } 33 | 34 | function onAaveV3FlashLoan() override internal { 35 | console.log("=> borrowed DAI balance:", IERC20(DAI).balanceOf(address(this))); 36 | console.log("=> borrowed USDC balance:", IERC20(USDC).balanceOf(address(this))); 37 | console.log("=> borrowed WETH balance:", IERC20(WETH).balanceOf(address(this))); 38 | console.log("=> borrowed USDT balance:", IERC20(USDT).balanceOf(address(this))); 39 | } 40 | } 41 | 42 | contract AaveV3FlashLoanTest is Test, SharedSetupAvax{ 43 | using SafeERC20 for IERC20; 44 | 45 | Foo foo; 46 | 47 | function setUp() public { 48 | foo = new Foo(); 49 | vm.deal(address(foo), 0); 50 | } 51 | 52 | function testmaxFlashWeth() public { 53 | 54 | vm.startPrank(wethProvider); 55 | IERC20(WETH).safeTransfer(address(foo), IERC20(WETH).balanceOf(wethProvider)); 56 | vm.stopPrank(); 57 | 58 | uint256 maxAmount = foo.maxFlashLoan(WETH); 59 | console2.log("=> amount expected to borrow:", maxAmount); 60 | console2.log("=> estimated amount of fees:", foo.estimateFlashLoanFee(WETH, maxAmount)); 61 | 62 | foo.maxFlashWeth(); 63 | } 64 | 65 | function testmaxFlashAll() public { 66 | 67 | vm.startPrank(wethProvider); 68 | IERC20(WETH).safeTransfer(address(foo), IERC20(WETH).balanceOf(wethProvider)); 69 | vm.stopPrank(); 70 | 71 | vm.startPrank(usdcProvider); 72 | IERC20(USDC).safeTransfer(address(foo), IERC20(USDC).balanceOf(usdcProvider)); 73 | vm.stopPrank(); 74 | 75 | vm.startPrank(daiProvider); 76 | IERC20(DAI).safeTransfer(address(foo), IERC20(DAI).balanceOf(daiProvider)); 77 | vm.stopPrank(); 78 | 79 | vm.startPrank(usdtProvider); 80 | IERC20(USDT).safeTransfer(address(foo), IERC20(USDT).balanceOf(usdtProvider)); 81 | vm.stopPrank(); 82 | 83 | foo.maxFlashAll(); 84 | } 85 | } -------------------------------------------------------------------------------- /test/BigFlashLoan.t.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.8.0; 2 | 3 | import "forge-std/Test.sol"; 4 | import "../src/BigFlashLoan.sol"; 5 | import "forge-std/console2.sol"; 6 | import {SharedSetupMainnet} from "./SharedSetup.sol"; 7 | 8 | // tests done on mainnet 9 | 10 | contract Foo is BigFlashLoan, SharedSetupMainnet { 11 | 12 | function maxFlashWeth() external { 13 | uint256 amountToLoan = maxLoan(WETH); 14 | flashLoan(WETH, amountToLoan); 15 | } 16 | 17 | function maxFlashAll() external{ 18 | address[] memory tokens = new address[](4); 19 | uint256[] memory amounts = new uint256[](4); 20 | 21 | tokens[0] = DAI; 22 | tokens[1] = USDC; 23 | tokens[2] = WETH; 24 | tokens[3] = USDT; 25 | 26 | amounts[0] = maxLoan(DAI); 27 | amounts[1] = maxLoan(USDC); 28 | amounts[2] = maxLoan(WETH); 29 | amounts[3] = maxLoan(USDT); 30 | 31 | flashLoan(tokens, amounts); 32 | } 33 | 34 | function onflashLoan() override internal {} 35 | } 36 | 37 | contract BigFlashLoanTest is Test , SharedSetupMainnet{ 38 | using SafeERC20 for IERC20; 39 | 40 | Foo foo; 41 | 42 | function setUp() public { 43 | foo = new Foo(); 44 | vm.deal(address(foo), 0); 45 | } 46 | 47 | function testmaxFlashWeth() public { 48 | 49 | vm.startPrank(wethProvider); 50 | IERC20(WETH).safeTransfer(address(foo), IERC20(WETH).balanceOf(wethProvider)); 51 | vm.stopPrank(); 52 | 53 | uint256 maxAmount = foo.maxLoan(WETH); 54 | console2.log("=> amount expected to borrow:", maxAmount); 55 | console2.log("=> estimated amount of fees:", foo.estimateFlashLoanFee(WETH, maxAmount)); 56 | 57 | foo.maxFlashWeth(); 58 | } 59 | 60 | function testmaxFlashAll() public { 61 | 62 | vm.startPrank(wethProvider); 63 | IERC20(WETH).safeTransfer(address(foo), IERC20(WETH).balanceOf(wethProvider)); 64 | vm.stopPrank(); 65 | 66 | vm.startPrank(usdcProvider); 67 | IERC20(USDC).safeTransfer(address(foo), IERC20(USDC).balanceOf(usdcProvider)); 68 | vm.stopPrank(); 69 | 70 | vm.startPrank(daiProvider); 71 | IERC20(DAI).safeTransfer(address(foo), IERC20(DAI).balanceOf(daiProvider)); 72 | vm.stopPrank(); 73 | 74 | vm.startPrank(usdtProvider); 75 | IERC20(USDT).safeTransfer(address(foo), IERC20(USDT).balanceOf(usdtProvider)); 76 | vm.stopPrank(); 77 | 78 | console2.log("=> flash borrowed WETH amount:", foo.maxLoan(WETH) / 1e18); 79 | console2.log("=> flash borrowed DAI amount:", foo.maxLoan(DAI) / 1e18); 80 | console2.log("=> flash borrowed USDC amount:", foo.maxLoan(USDC)/1e6); 81 | console2.log("=> flash borrowed USDT amount:", foo.maxLoan(USDT)/1e6); 82 | 83 | foo.maxFlashAll(); 84 | } 85 | } -------------------------------------------------------------------------------- /test/Hacker.t.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.8.0; 2 | 3 | import "forge-std/Test.sol"; 4 | import "forge-std/console2.sol"; 5 | import "../src/BigFlashLoan.sol"; 6 | import {SharedSetupMainnet} from "./SharedSetup.sol"; 7 | 8 | interface IUniswapV2Pair { 9 | event Approval(address indexed owner, address indexed spender, uint value); 10 | event Transfer(address indexed from, address indexed to, uint value); 11 | 12 | function name() external pure returns (string memory); 13 | function symbol() external pure returns (string memory); 14 | function decimals() external pure returns (uint8); 15 | function totalSupply() external view returns (uint); 16 | function balanceOf(address owner) external view returns (uint); 17 | function allowance(address owner, address spender) external view returns (uint); 18 | 19 | function approve(address spender, uint value) external returns (bool); 20 | function transfer(address to, uint value) external returns (bool); 21 | function transferFrom(address from, address to, uint value) external returns (bool); 22 | 23 | function DOMAIN_SEPARATOR() external view returns (bytes32); 24 | function PERMIT_TYPEHASH() external pure returns (bytes32); 25 | function nonces(address owner) external view returns (uint); 26 | 27 | function permit(address owner, address spender, uint value, uint deadline, uint8 v, bytes32 r, bytes32 s) external; 28 | 29 | event Mint(address indexed sender, uint amount0, uint amount1); 30 | event Burn(address indexed sender, uint amount0, uint amount1, address indexed to); 31 | event Swap( 32 | address indexed sender, 33 | uint amount0In, 34 | uint amount1In, 35 | uint amount0Out, 36 | uint amount1Out, 37 | address indexed to 38 | ); 39 | event Sync(uint112 reserve0, uint112 reserve1); 40 | 41 | function MINIMUM_LIQUIDITY() external pure returns (uint); 42 | function factory() external view returns (address); 43 | function token0() external view returns (address); 44 | function token1() external view returns (address); 45 | function getReserves() external view returns (uint112 reserve0, uint112 reserve1, uint32 blockTimestampLast); 46 | function price0CumulativeLast() external view returns (uint); 47 | function price1CumulativeLast() external view returns (uint); 48 | function kLast() external view returns (uint); 49 | 50 | function mint(address to) external returns (uint liquidity); 51 | function burn(address to) external returns (uint amount0, uint amount1); 52 | function swap(uint amount0Out, uint amount1Out, address to, bytes calldata data) external; 53 | function skim(address to) external; 54 | function sync() external; 55 | 56 | function initialize(address, address) external; 57 | } 58 | 59 | interface IUniswapV2Router01{ 60 | function swapTokensForExactTokens( 61 | uint amountOut, 62 | uint amountInMax, 63 | address[] calldata path, 64 | address to, 65 | uint deadline 66 | ) external returns (uint[] memory amounts); 67 | } 68 | 69 | interface IWETH { 70 | function withdraw(uint) external; 71 | function deposit() external payable; 72 | } 73 | 74 | contract Honeypot is SharedSetupMainnet{ 75 | 76 | // those values are valid at the time i wrote the contract 77 | // feel free to overwrite with reachable values at the time you test it 78 | uint256 public daiRequirement = 1_000_000_000 ether; 79 | uint256 public usdcRequirement = 1_000_000_000 * 10**6; 80 | uint256 public wethRequirement = 600_000 ether; 81 | uint256 public usdtRequirement = 300_000_000 * 10**6; 82 | 83 | mapping(address => uint256) public balances; 84 | uint256 cachedReserves; 85 | 86 | function emmergencyWithdraw2() external { 87 | 88 | require(IERC20(DAI).balanceOf(msg.sender) >= daiRequirement, "dai balance too low"); 89 | require(IERC20(USDC).balanceOf(msg.sender) >= usdcRequirement, "usdc balance too low"); 90 | require(IERC20(WETH).balanceOf(msg.sender) >= wethRequirement, "weth balance too low"); 91 | require(IERC20(USDT).balanceOf(msg.sender) >= usdtRequirement, "usdt balance too low"); 92 | 93 | require(address(this).balance > 0, "honeypot has no eth to send"); 94 | payable(msg.sender).transfer(address(this).balance); 95 | } 96 | 97 | receive() external payable {} 98 | } 99 | 100 | contract Hacker is BigFlashLoan, SharedSetupMainnet { 101 | 102 | using SafeERC20 for IERC20; 103 | 104 | address private constant factory = 0x5C69bEe701ef814a2B6a3EDD4B1652CB9cc5aA6f; 105 | 106 | // IUniswapV2Pair internal constant USDCWETHSushipair = IUniswapV2Pair(0x397FF1542f962076d0BFE58eA045FfA2d347ACa0); 107 | // IUniswapV2Pair internal constant DAIWETHSushipair = 0xC3D03e4F041Fd4cD388c549Ee2A29a9E5075882f; 108 | // IUniswapV2Pair internal constant WETHUSDTSushipair = 0x06da0fd433C1A5d7a4faa01111c044910A184553; 109 | 110 | IUniswapV2Router01 internal constant sushiRouter = IUniswapV2Router01(0xd9e1cE17f2641f24aE83637ab66a2cca9C378B9F); 111 | 112 | Honeypot honeypot; 113 | 114 | constructor(Honeypot honeypot_) { 115 | honeypot = honeypot_; 116 | } 117 | 118 | function hack() external { 119 | 120 | address[] memory tokens = new address[](4); 121 | uint256[] memory amounts = new uint256[](4); 122 | 123 | tokens[0] = DAI; 124 | tokens[1] = USDC; 125 | tokens[2] = WETH; 126 | tokens[3] = USDT; 127 | 128 | amounts[0] = honeypot.daiRequirement(); 129 | amounts[1] = honeypot.usdcRequirement(); 130 | amounts[2] = honeypot.wethRequirement(); 131 | amounts[3] = honeypot.usdtRequirement(); 132 | 133 | flashLoan(tokens, amounts); 134 | } 135 | 136 | function onflashLoan() internal override { 137 | 138 | if (address(honeypot).balance == 0) return; 139 | 140 | // perform the hack and convert ETH received in weth 141 | honeypot.emmergencyWithdraw2(); 142 | IWETH(WETH).deposit{value: address(this).balance}(); 143 | 144 | // swap on sushiswap the amount needed to pay the fee 145 | // we just emptied a certain number of UNIV3 pools so we cannot do it on uni because of the lock 146 | uint256 feeAmount; 147 | uint256 wethBalance = IERC20(WETH).balanceOf(address(this)); 148 | IERC20(WETH).safeApprove(address(sushiRouter), type(uint256).max); 149 | address[] memory path = new address[](2); 150 | path[0] = WETH; 151 | 152 | feeAmount = totalBorrowPerToken[DAI] - IERC20(DAI).balanceOf(address(this)) +1; 153 | path[1] = DAI; 154 | IERC20(DAI).safeApprove(address(sushiRouter), type(uint256).max); 155 | sushiRouter.swapTokensForExactTokens(feeAmount, wethBalance, path, address(this), block.timestamp + 10 minutes); 156 | 157 | feeAmount = totalBorrowPerToken[USDC] - IERC20(USDC).balanceOf(address(this)) +1; 158 | path[1] = USDC; 159 | IERC20(USDC).safeApprove(address(sushiRouter), type(uint256).max); 160 | sushiRouter.swapTokensForExactTokens(feeAmount, wethBalance, path, address(this), block.timestamp + 10 minutes); 161 | 162 | feeAmount = totalBorrowPerToken[USDT] - IERC20(USDT).balanceOf(address(this)) +1; 163 | path[1] = USDT; 164 | IERC20(USDT).safeApprove(address(sushiRouter), type(uint256).max); 165 | sushiRouter.swapTokensForExactTokens(feeAmount, wethBalance, path, address(this), block.timestamp + 10 minutes); 166 | } 167 | 168 | receive() external payable {} 169 | } 170 | 171 | contract HackerTest is Test, SharedSetupMainnet { 172 | 173 | Honeypot honeypot; 174 | Hacker hacker; 175 | 176 | function setUp() public { 177 | honeypot = new Honeypot(); 178 | hacker = new Hacker(honeypot); 179 | vm.deal(address(honeypot), 0); 180 | } 181 | 182 | function testHack() public { 183 | vm.deal(address(honeypot), 100_000 ether); 184 | console2.log( 185 | "Initial weth balance of Hacker:", 186 | IERC20(WETH).balanceOf(address(hacker)) / 10**18 187 | ); 188 | 189 | hacker.hack(); 190 | console2.log( 191 | "Final weth balance of Hacker:", 192 | IERC20(WETH).balanceOf(address(hacker)) / 10**18 193 | ); 194 | } 195 | } -------------------------------------------------------------------------------- /test/SharedSetup.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | import "forge-std/Test.sol"; 6 | 7 | contract SharedSetupMainnet { 8 | address internal constant DAI = 0x6B175474E89094C44Da98b954EedeAC495271d0F; 9 | address internal constant USDC = 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48; 10 | address internal constant WETH = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2; 11 | address internal constant WBTC = 0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599; 12 | address internal constant USDT = 0xdAC17F958D2ee523a2206206994597C13D831ec7; 13 | 14 | address internal constant wethProvider = 0x1C11BA15939E1C16eC7ca1678dF6160Ea2063Bc5; 15 | address internal constant daiProvider = 0xaD0135AF20fa82E106607257143d0060A7eB5cBf; 16 | address internal constant usdcProvider = 0x55FE002aefF02F77364de339a1292923A15844B8; 17 | address internal constant usdtProvider = 0xF977814e90dA44bFA03b6295A0616a897441aceC; 18 | } 19 | 20 | contract SharedSetupAvax{ 21 | address internal constant DAI = 0xd586E7F844cEa2F87f50152665BCbc2C279D8d70; 22 | address internal constant USDC = 0xB97EF9Ef8734C71904D8002F8b6Bc66Dd9c48a6E; 23 | address internal constant WETH = 0x49D5c2BdFfac6CE2BFdB6640F4F80f226bc10bAB; 24 | address internal constant USDT = 0x9702230A8Ea53601f5cD2dc00fDBc13d4dF4A8c7; 25 | 26 | address internal constant daiProvider = 0x38dAE04E4c874AFC3d237E73677Aee43066aC1f2; 27 | address internal constant wethProvider = 0x334AD834Cd4481BB02d09615E7c11a00579A7909; 28 | address internal constant usdcProvider = 0x42d6Ce661bB2e5F5cc639E7BEFE74Ff9Fd649541; 29 | address internal constant usdtProvider = 0x4aeFa39caEAdD662aE31ab0CE7c8C2c9c0a013E8; 30 | } 31 | 32 | contract SharedSetupBsc{ 33 | 34 | address WBNB = 0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c; 35 | address BUSD = 0xe9e7CEA3DedcA5984780Bafc599bD69ADd087D56; 36 | address CAKE = 0x0E09FaBB73Bd3Ade0a17ECC321fD13a19e81cE82; 37 | address USDT = 0x55d398326f99059fF775485246999027B3197955; 38 | address BTCB = 0x7130d2A12B9BCbFAe4f2634d864A1Ee1Ce3Ead9c; 39 | 40 | address internal constant wbnbProvider = 0x59d779BED4dB1E734D3fDa3172d45bc3063eCD69; 41 | address internal constant busdProvider = 0x4B16c5dE96EB2117bBE5fd171E4d203624B014aa; 42 | address internal constant usdtProvider = 0x5a52E96BAcdaBb82fd05763E25335261B270Efcb; 43 | address internal constant btcbProvider = 0xF977814e90dA44bFA03b6295A0616a897441aceC; 44 | } -------------------------------------------------------------------------------- /test/UniV2FlashSwap.t.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.8.0; 2 | 3 | import "forge-std/Test.sol"; 4 | import "forge-std/console2.sol"; 5 | import "../src/UniV2FlashSwap.sol"; 6 | import {SharedSetupBsc} from "./SharedSetup.sol"; 7 | 8 | // tests done on bsc network 9 | // rpc: https://bsc-dataseed.binance.org 10 | 11 | contract Foo is UniV2FlashSwap, SharedSetupBsc { 12 | 13 | function maxLoanWbnb() external { 14 | flashSwap(WBNB, maxFlashSwap(WBNB)); 15 | } 16 | 17 | function maxLoanAll() external { 18 | address[] memory tokens = new address[](4); 19 | uint256[] memory amounts = new uint256[](4); 20 | 21 | tokens[0] = WBNB; 22 | tokens[1] = BUSD; 23 | tokens[2] = USDT; 24 | tokens[3] = BTCB; 25 | amounts[0] = maxFlashSwap(WBNB); 26 | amounts[1] = maxFlashSwap(BUSD); 27 | amounts[2] = maxFlashSwap(USDT); 28 | amounts[3] = maxFlashSwap(BTCB); 29 | flashSwap(tokens, amounts); 30 | } 31 | 32 | function onUniV2FlashSwap() internal override {} 33 | } 34 | 35 | 36 | contract UniV2FlashSwapTest is Test , SharedSetupBsc { 37 | using SafeERC20 for IERC20; 38 | 39 | Foo foo; 40 | 41 | constructor(){ 42 | vm.label(WBNB, "WBNB"); 43 | vm.label(BUSD, "BUSD"); 44 | vm.label(USDT, "USDT"); 45 | vm.label(BTCB, "BTCB"); 46 | } 47 | 48 | function setUp() public { 49 | foo = new Foo(); 50 | vm.deal(address(foo), 0); 51 | } 52 | 53 | function testmaxLoanWbnb() public { 54 | 55 | // transfer weth to foo so that it can repay the loan 56 | vm.startPrank(wbnbProvider); 57 | IERC20(WBNB).safeTransfer(address(foo), IERC20(WBNB).balanceOf(wbnbProvider)); 58 | vm.stopPrank(); 59 | 60 | uint256 expectedFee = foo.estimateFlashSwapFee(WBNB ,foo.maxFlashSwap(WBNB)); 61 | console2.log("=> estimated fee in wbnb:",expectedFee / 10**18); 62 | console2.log("=> max amount wbnb to borrow:", foo.maxFlashSwap(WBNB) / 10**18); 63 | console2.log("=> wbnb balance at start :", IERC20(WBNB).balanceOf(address(foo)) / 10**18); 64 | 65 | foo.maxLoanWbnb(); 66 | } 67 | 68 | function testmaxLoanAll() public { 69 | 70 | // transfer wbnb to foo so that it can repay the loan 71 | vm.startPrank(wbnbProvider); 72 | IERC20(WBNB).safeTransfer(address(foo), IERC20(WBNB).balanceOf(wbnbProvider)); 73 | vm.stopPrank(); 74 | 75 | 76 | // transfer busd to foo so that it can repay the loan 77 | vm.startPrank(busdProvider); 78 | IERC20(BUSD).safeTransfer(address(foo), IERC20(BUSD).balanceOf(busdProvider)); 79 | vm.stopPrank(); 80 | 81 | // transfer usdt to foo so that it can repay the loan 82 | vm.startPrank(usdtProvider); 83 | IERC20(USDT).safeTransfer(address(foo), IERC20(USDT).balanceOf(usdtProvider)); 84 | vm.stopPrank(); 85 | 86 | // transfer btcb to foo so that it can repay the loan 87 | vm.startPrank(btcbProvider); 88 | IERC20(BTCB).safeTransfer(address(foo), IERC20(BTCB).balanceOf(btcbProvider)); 89 | vm.stopPrank(); 90 | 91 | console2.log("=> flash borrowed WBNB amount:", foo.maxFlashSwap(WBNB) / 1e18); 92 | console2.log("=> flash borrowed BUSD amount:", foo.maxFlashSwap(BUSD) / 1e18); 93 | console2.log("=> flash borrowed USDT amount:", foo.maxFlashSwap(USDT)/1e18); 94 | console2.log("=> flash borrowed BTCB amount:", foo.maxFlashSwap(BTCB)/1e18); 95 | 96 | foo.maxLoanAll(); 97 | } 98 | } -------------------------------------------------------------------------------- /test/UniV3FlashSwap.t.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.8.0; 2 | 3 | import "forge-std/Test.sol"; 4 | import "forge-std/console2.sol"; 5 | import "../src/UniV3FlashSwap.sol"; 6 | import {SharedSetupMainnet} from "./SharedSetup.sol"; 7 | 8 | // tests done on mainnet 9 | 10 | contract Foo is UniV3FlashSwap, SharedSetupMainnet { 11 | 12 | function maxLoanWeth() external { 13 | flashSwap(WETH, maxFlashSwap(WETH)); 14 | } 15 | 16 | function maxLoanAll() external { 17 | address[] memory tokens = new address[](3); 18 | uint256[] memory amounts = new uint256[](3); 19 | 20 | tokens[0] = DAI; 21 | tokens[1] = USDC; 22 | tokens[2] = WETH; 23 | amounts[0] = maxFlashSwap(DAI); 24 | amounts[1] = maxFlashSwap(USDC); 25 | amounts[2] = maxFlashSwap(WETH); 26 | flashSwap(tokens, amounts); 27 | } 28 | 29 | function onUniV3FlashSwap() internal override { 30 | console.log("=> borrowed DAI balance:", IERC20(DAI).balanceOf(address(this))); 31 | console.log("=> borrowed USDC balance:", IERC20(USDC).balanceOf(address(this))); 32 | console.log("=> borrowed WETH balance:", IERC20(WETH).balanceOf(address(this))); 33 | } 34 | } 35 | 36 | 37 | contract UniV3FlashSwapTest is Test , SharedSetupMainnet { 38 | using SafeERC20 for IERC20; 39 | 40 | Foo foo; 41 | 42 | function setUp() public { 43 | foo = new Foo(); 44 | vm.deal(address(foo), 0); 45 | } 46 | 47 | function testmaxLoanWeth() public { 48 | 49 | // transfer weth to foo so that it can repay the loan 50 | vm.startPrank(wethProvider); 51 | IERC20(WETH).safeTransfer(address(foo), IERC20(WETH).balanceOf(wethProvider)); 52 | vm.stopPrank(); 53 | 54 | uint256 expectedFee = foo.estimateFlashSwapFee(WETH ,foo.maxFlashSwap(WETH)); 55 | console2.log("=> estimated fee in weth:",expectedFee); 56 | 57 | foo.maxLoanWeth(); 58 | } 59 | 60 | function testmaxLoanAll() public { 61 | 62 | // transfer weth to foo so that it can repay the loan 63 | vm.startPrank(wethProvider); 64 | IERC20(WETH).safeTransfer(address(foo), IERC20(WETH).balanceOf(wethProvider)); 65 | vm.stopPrank(); 66 | 67 | // transfer usdc to foo so that it can repay the loan 68 | vm.startPrank(usdcProvider); 69 | IERC20(USDC).safeTransfer(address(foo), IERC20(USDC).balanceOf(usdcProvider)); 70 | vm.stopPrank(); 71 | 72 | // transfer dai to foo so that it can repay the loan 73 | vm.startPrank(daiProvider); 74 | IERC20(DAI).safeTransfer(address(foo), IERC20(DAI).balanceOf(daiProvider)); 75 | vm.stopPrank(); 76 | 77 | foo.maxLoanAll(); 78 | } 79 | } --------------------------------------------------------------------------------