Skip to content

Commit

Permalink
add flash loan erc721
Browse files Browse the repository at this point in the history
  • Loading branch information
thorseldon committed Jan 22, 2024
1 parent 0ea87ee commit 41e1aeb
Show file tree
Hide file tree
Showing 12 changed files with 272 additions and 9 deletions.
22 changes: 22 additions & 0 deletions src/PoolManager.sol
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import './libraries/logic/BorrowLogic.sol';
import './libraries/logic/LiquidationLogic.sol';
import './libraries/logic/IsolateLogic.sol';
import './libraries/logic/YieldLogic.sol';
import './libraries/logic/FlashLoanLogic.sol';

contract PoolManager is PausableUpgradeable, ReentrancyGuardUpgradeable, ERC721HolderUpgradeable {
using EnumerableSetUpgradeable for EnumerableSetUpgradeable.AddressSet;
Expand Down Expand Up @@ -126,6 +127,10 @@ contract PoolManager is PausableUpgradeable, ReentrancyGuardUpgradeable, ERC721H
ConfigureLogic.executeSetAssetBorrowing(poolId, asset, isEnable);
}

function setAssetFlashLoan(uint32 poolId, address asset, bool isEnable) public nonReentrant {
ConfigureLogic.executeSetAssetFlashLoan(poolId, asset, isEnable);
}

function setAssetSupplyCap(uint32 poolId, address asset, uint256 newCap) public nonReentrant {
ConfigureLogic.executeSetAssetSupplyCap(poolId, asset, newCap);
}
Expand Down Expand Up @@ -430,6 +435,23 @@ contract PoolManager is PausableUpgradeable, ReentrancyGuardUpgradeable, ERC721H
/****************************************************************************/
/* Misc Features */
/****************************************************************************/
function flashLoanERC721(
uint32 poolId,
address[] calldata nftAssets,
uint256[] calldata nftTokenIds,
address receiverAddress,
bytes calldata params
) public whenNotPaused nonReentrant {
FlashLoanLogic.executeFlashLoanERC721(
InputTypes.ExecuteFlashLoanERC721Params({
poolId: poolId,
nftAssets: nftAssets,
nftTokenIds: nftTokenIds,
receiverAddress: receiverAddress,
params: params
})
);
}

/****************************************************************************/
/* Pool Query */
Expand Down
3 changes: 3 additions & 0 deletions src/PriceOracle.sol
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ contract PriceOracle is IPriceOracle, Initializable {
return getAssetPriceFromBendNFTOracle(asset);
}

/// @notice Query the price of asset from chainlink oracle
function getAssetPriceFromChainlink(address asset) public view returns (uint256) {
uint256 price;

Expand All @@ -105,8 +106,10 @@ contract PriceOracle is IPriceOracle, Initializable {
return price;
}

/// @notice Query the price of asset from benddao nft oracle
function getAssetPriceFromBendNFTOracle(address asset) public view returns (uint256) {
uint256 nftPriceInNftBase = bendNFTOracle.getAssetPrice(asset);
require(nftPriceInNftBase > 0, Errors.ASSET_PRICE_IS_ZERO);

// nft oracle use the same currency with protocol
if (NFT_BASE_CURRENCY == BASE_CURRENCY) {
Expand Down
25 changes: 25 additions & 0 deletions src/interfaces/IFlashLoanReceiver.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.19;

/**
* @title IFlashLoanReceiver interface
* @notice Interface for the IFlashLoanReceiver.
* @dev implement this interface to develop a flashloan-compatible flashLoanReceiver contract
**/
interface IFlashLoanReceiver {
function executeOperationERC20(
address[] calldata nftAssets,
uint256[] calldata amounts,
address initiator,
address operator,
bytes calldata params
) external returns (bool);

function executeOperationERC721(
address[] calldata nftAssets,
uint256[] calldata tokenIds,
address initiator,
address operator,
bytes calldata params
) external returns (bool);
}
84 changes: 83 additions & 1 deletion src/interfaces/IPoolManager.sol
Original file line number Diff line number Diff line change
@@ -1,4 +1,86 @@
// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.19;

interface IPoolManager {}
interface IPoolManager {
/****************************************************************************/
/* supply */
/****************************************************************************/
function depositERC20(uint32 poolId, address asset, uint256 amount) external;

function withdrawERC20(uint32 poolId, address asset, uint256 amount) external;

function depositERC721(uint32 poolId, address asset, uint256[] calldata tokenIds, uint8 supplyMode) external;

function withdrawERC721(uint32 poolId, address asset, uint256[] calldata tokenIds, uint8 supplyMode) external;

function setERC721SupplyMode(uint32 poolId, address asset, uint256[] calldata tokenIds, uint8 supplyMode) external;

/****************************************************************************/
/* cross lending */
/****************************************************************************/
function crossBorrowERC20(uint32 poolId, address asset, uint8[] calldata groups, uint256[] calldata amounts) external;

function crossRepayERC20(uint32 poolId, address asset, uint8[] calldata groups, uint256[] calldata amounts) external;

function crossLiquidateERC20(
uint32 poolId,
address user,
address collateralAsset,
address debtAsset,
uint256 debtToCover,
bool supplyAsCollateral
) external;

function crossLiquidateERC721(
uint32 poolId,
address user,
address collateralAsset,
uint256[] calldata collateralTokenIds,
address debtAsset,
bool supplyAsCollateral
) external;

/****************************************************************************/
/* isolate lending */
/****************************************************************************/
function isolateBorrow(
uint32 poolId,
address nftAsset,
uint256[] calldata nftTokenIds,
address asset,
uint256[] calldata amounts
) external;

function isolateRepay(
uint32 poolId,
address nftAsset,
uint256[] calldata nftTokenIds,
address asset,
uint256[] calldata amounts
) external;

function isolateAuction(
uint32 poolId,
address nftAsset,
uint256[] calldata nftTokenIds,
address asset,
uint256[] calldata amounts
) external;

function isolateRedeem(uint32 poolId, address nftAsset, uint256[] calldata nftTokenIds, address asset) external;

function isolateLiquidate(
uint32 poolId,
address nftAsset,
uint256[] calldata nftTokenIds,
address asset,
bool supplyAsCollateral
) external;

/****************************************************************************/
/* Yield */
/****************************************************************************/
function yieldBorrowERC20(uint32 poolId, address asset, uint256 amount) external;

function yieldRepayERC20(uint32 poolId, address asset, uint256 amount) external;
}
9 changes: 5 additions & 4 deletions src/libraries/helpers/Errors.sol
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ library Errors {
string public constant INVALID_BORROW_AMOUNT = '116';
string public constant INVALID_TOKEN_OWNER = '117';
string public constant INVALID_YIELD_STAKER = '118';
string public constant INCONSISTENT_PARAMS_LENGH = '119';
string public constant INCONSISTENT_PARAMS_LENGTH = '119';
string public constant INVALID_LOAN_STATUS = '120';

string public constant ENUM_SET_ADD_FAILED = '150';
Expand All @@ -32,9 +32,9 @@ library Errors {
string public constant ACL_MANAGER_CANNOT_BE_ZERO = '201';
string public constant OWNER_CANNOT_BE_ZERO = '202';
string public constant CALLER_NOT_ORACLE_ADMIN = '203';
string public constant INCONSISTENT_PARAMS_LENGTH = '204';
string public constant INVALID_ASSET_PARAMS = '205';
string public constant CALLER_NOT_EMERGENCY_ADMIN = '206';
string public constant INVALID_ASSET_PARAMS = '204';
string public constant CALLER_NOT_EMERGENCY_ADMIN = '205';
string public constant FLASH_LOAN_EXEC_FAILED = '206';

string public constant POOL_ALREADY_EXISTS = '300';
string public constant POOL_NOT_EXISTS = '301';
Expand Down Expand Up @@ -71,6 +71,7 @@ library Errors {
string public constant ASSET_LOCK_FLAG_NOT_EMPTY = '359';
string public constant ASSET_SUPPLY_CAP_EXCEEDED = '360';
string public constant ASSET_BORROW_CAP_EXCEEDED = '361';
string public constant ASSET_IS_FLASHLOAN_DISABLED = '362';

string public constant HEALTH_FACTOR_BELOW_LIQUIDATION_THRESHOLD = '400';
string public constant HEALTH_FACTOR_NOT_BELOW_LIQUIDATION_THRESHOLD = '401';
Expand Down
9 changes: 9 additions & 0 deletions src/libraries/helpers/Events.sol
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ library Events {
event SetAssetFrozen(uint32 indexed poolId, address indexed asset, bool isFrozen);
event SetAssetPause(uint32 indexed poolId, address indexed asset, bool isPause);
event SetAssetBorrowing(uint32 indexed poolId, address indexed asset, bool isEnable);
event SetAssetFlashLoan(uint32 indexed poolId, address indexed asset, bool isEnable);
event SetAssetSupplyCap(uint32 indexed poolId, address indexed asset, uint256 newCap);
event SetAssetBorrowCap(uint32 indexed poolId, address indexed asset, uint256 newCap);
event SetAssetClassGroup(uint32 indexed poolId, address indexed asset, uint8 groupId);
Expand Down Expand Up @@ -150,4 +151,12 @@ library Events {
event YieldBorrowERC20(address indexed sender, uint256 indexed poolId, address indexed asset, uint256 amount);

event YieldRepayERC20(address indexed sender, uint256 indexed poolId, address indexed asset, uint256 amount);

// Misc Events
event FlashLoanERC721(
address indexed sender,
address[] nftAssets,
uint256[] nftTokenIds,
address indexed receiverAddress
);
}
15 changes: 15 additions & 0 deletions src/libraries/logic/ConfigureLogic.sol
Original file line number Diff line number Diff line change
Expand Up @@ -352,6 +352,21 @@ library ConfigureLogic {
emit Events.SetAssetBorrowing(poolId, asset, isEnable);
}

function executeSetAssetFlashLoan(uint32 poolId, address asset, bool isEnable) public {
DataTypes.PoolStorage storage ps = StorageSlot.getPoolStorage();

DataTypes.PoolData storage poolData = ps.poolLookup[poolId];
_validateOwnerAndPool(poolData);

DataTypes.AssetData storage assetData = poolData.assetLookup[asset];
require(assetData.underlyingAsset != address(0), Errors.ASSET_NOT_EXISTS);
require(assetData.assetType == Constants.ASSET_TYPE_ERC20, Errors.ASSET_TYPE_NOT_ERC20);

assetData.isFlashLoanEnabled = isEnable;

emit Events.SetAssetFlashLoan(poolId, asset, isEnable);
}

function executeSetAssetSupplyCap(uint32 poolId, address asset, uint256 newCap) public {
DataTypes.PoolStorage storage ps = StorageSlot.getPoolStorage();

Expand Down
72 changes: 72 additions & 0 deletions src/libraries/logic/FlashLoanLogic.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.19;

import {IFlashLoanReceiver} from '../../interfaces/IFlashLoanReceiver.sol';

import {Constants} from '../helpers/Constants.sol';
import {Errors} from '../helpers/Errors.sol';
import {Events} from '../helpers/Events.sol';

import {PercentageMath} from '../math/PercentageMath.sol';

import {InputTypes} from '../types/InputTypes.sol';
import {DataTypes} from '../types/DataTypes.sol';
import {StorageSlot} from './StorageSlot.sol';

import {VaultLogic} from './VaultLogic.sol';
import {ValidateLogic} from './ValidateLogic.sol';

import 'forge-std/console.sol';

library FlashLoanLogic {
function executeFlashLoanERC721(InputTypes.ExecuteFlashLoanERC721Params memory inputParams) public {
DataTypes.PoolStorage storage ps = StorageSlot.getPoolStorage();
DataTypes.PoolData storage poolData = ps.poolLookup[inputParams.poolId];

uint256 i;
IFlashLoanReceiver receiver = IFlashLoanReceiver(inputParams.receiverAddress);

ValidateLogic.validateFlashLoanERC721Basic(inputParams, poolData);

// only token owner can do flashloan
for (i = 0; i < inputParams.nftTokenIds.length; i++) {
DataTypes.AssetData storage assetData = poolData.assetLookup[inputParams.nftAssets[i]];
ValidateLogic.validateAssetBasic(assetData);
require(assetData.assetType == Constants.ASSET_TYPE_ERC721, Errors.ASSET_TYPE_NOT_ERC721);
require(assetData.isFlashLoanEnabled, Errors.ASSET_IS_FLASHLOAN_DISABLED);

DataTypes.ERC721TokenData storage tokenData = VaultLogic.erc721GetTokenData(
assetData,
inputParams.nftTokenIds[i]
);
require(tokenData.owner == msg.sender, Errors.INVALID_TOKEN_OWNER);
}

// step 1: moving underlying asset forward to receiver contract
VaultLogic.erc721TransferOutOnFlashLoan(
inputParams.receiverAddress,
inputParams.nftAssets,
inputParams.nftTokenIds
);

// setup 2: execute receiver contract, doing something like aidrop
bool execOpRet = receiver.executeOperationERC721(
inputParams.nftAssets,
inputParams.nftTokenIds,
msg.sender,
address(this),
inputParams.params
);
require(execOpRet, Errors.FLASH_LOAN_EXEC_FAILED);

// setup 3: moving underlying asset backward from receiver contract
VaultLogic.erc721TransferInOnFlashLoan(inputParams.receiverAddress, inputParams.nftAssets, inputParams.nftTokenIds);

emit Events.FlashLoanERC721(
msg.sender,
inputParams.nftAssets,
inputParams.nftTokenIds,
inputParams.receiverAddress
);
}
}
20 changes: 16 additions & 4 deletions src/libraries/logic/ValidateLogic.sol
Original file line number Diff line number Diff line change
Expand Up @@ -231,7 +231,7 @@ library ValidateLogic {
require(assetData.assetType == Constants.ASSET_TYPE_ERC20, Errors.ASSET_TYPE_NOT_ERC20);

require(inputParams.groups.length > 0, Errors.GROUP_LIST_IS_EMPTY);
require(inputParams.groups.length == inputParams.amounts.length, Errors.INCONSISTENT_PARAMS_LENGH);
require(inputParams.groups.length == inputParams.amounts.length, Errors.INCONSISTENT_PARAMS_LENGTH);

for (uint256 gidx = 0; gidx < inputParams.groups.length; gidx++) {
require(inputParams.amounts[gidx] > 0, Errors.INVALID_AMOUNT);
Expand Down Expand Up @@ -328,7 +328,7 @@ library ValidateLogic {
require(!nftAssetData.isFrozen, Errors.ASSET_IS_FROZEN);

require(inputParams.nftTokenIds.length > 0, Errors.INVALID_ID_LIST);
require(inputParams.nftTokenIds.length == inputParams.amounts.length, Errors.INCONSISTENT_PARAMS_LENGH);
require(inputParams.nftTokenIds.length == inputParams.amounts.length, Errors.INCONSISTENT_PARAMS_LENGTH);

for (vars.i = 0; vars.i < inputParams.nftTokenIds.length; vars.i++) {
require(inputParams.amounts[vars.i] > 0, Errors.INVALID_AMOUNT);
Expand Down Expand Up @@ -417,7 +417,7 @@ library ValidateLogic {
require(nftAssetData.assetType == Constants.ASSET_TYPE_ERC721, Errors.ASSET_TYPE_NOT_ERC721);

require(inputParams.nftTokenIds.length > 0, Errors.INVALID_ID_LIST);
require(inputParams.nftTokenIds.length == inputParams.amounts.length, Errors.INCONSISTENT_PARAMS_LENGH);
require(inputParams.nftTokenIds.length == inputParams.amounts.length, Errors.INCONSISTENT_PARAMS_LENGTH);

for (uint256 i = 0; i < inputParams.amounts.length; i++) {
require(inputParams.amounts[i] > 0, Errors.INVALID_AMOUNT);
Expand Down Expand Up @@ -450,7 +450,7 @@ library ValidateLogic {
require(nftAssetData.assetType == Constants.ASSET_TYPE_ERC721, Errors.ASSET_TYPE_NOT_ERC721);

require(inputParams.nftTokenIds.length > 0, Errors.INVALID_ID_LIST);
require(inputParams.nftTokenIds.length == inputParams.amounts.length, Errors.INCONSISTENT_PARAMS_LENGH);
require(inputParams.nftTokenIds.length == inputParams.amounts.length, Errors.INCONSISTENT_PARAMS_LENGTH);

for (uint256 i = 0; i < inputParams.amounts.length; i++) {
require(inputParams.amounts[i] > 0, Errors.INVALID_AMOUNT);
Expand Down Expand Up @@ -566,4 +566,16 @@ library ValidateLogic {

require(inputParams.amount > 0, Errors.INVALID_AMOUNT);
}

function validateFlashLoanERC721Basic(
InputTypes.ExecuteFlashLoanERC721Params memory inputParams,
DataTypes.PoolData storage poolData
) internal view {
validatePoolBasic(poolData);

require(inputParams.nftAssets.length == inputParams.nftTokenIds.length, Errors.INCONSISTENT_PARAMS_LENGTH);
require(inputParams.nftTokenIds.length > 0, Errors.INVALID_ID_LIST);

require(inputParams.receiverAddress != address(0), Errors.INVALID_ADDRESS);
}
}
14 changes: 14 additions & 0 deletions src/libraries/logic/VaultLogic.sol
Original file line number Diff line number Diff line change
Expand Up @@ -710,6 +710,20 @@ library VaultLogic {
require(poolSizeBefore == (poolSizeAfter + tokenIds.length), Errors.INVALID_TRANSFER_AMOUNT);
}

function erc721TransferInOnFlashLoan(address from, address[] memory nftAssets, uint256[] memory tokenIds) internal {
for (uint256 i = 0; i < tokenIds.length; i++) {
IERC721Upgradeable(nftAssets[i]).safeTransferFrom(from, address(this), tokenIds[i]);
}
}

function erc721TransferOutOnFlashLoan(address to, address[] memory nftAssets, uint256[] memory tokenIds) internal {
require(to != address(0), Errors.INVALID_TO_ADDRESS);

for (uint256 i = 0; i < tokenIds.length; i++) {
IERC721Upgradeable(nftAssets[i]).safeTransferFrom(address(this), to, tokenIds[i]);
}
}

//////////////////////////////////////////////////////////////////////////////
// Misc methods
//////////////////////////////////////////////////////////////////////////////
Expand Down
Loading

0 comments on commit 41e1aeb

Please sign in to comment.