Skip to content

Commit

Permalink
feat: add flash loan functionality for multiple assets
Browse files Browse the repository at this point in the history
  • Loading branch information
Debugger022 committed Dec 18, 2024
1 parent a0c30a6 commit eb28e0d
Show file tree
Hide file tree
Showing 6 changed files with 148 additions and 4 deletions.
95 changes: 94 additions & 1 deletion contracts/Comptroller.sol
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,14 @@ import { ResilientOracleInterface } from "@venusprotocol/oracle/contracts/interf
import { AccessControlledV8 } from "@venusprotocol/governance-contracts/contracts/Governance/AccessControlledV8.sol";
import { IPrime } from "@venusprotocol/venus-protocol/contracts/Tokens/Prime/Interfaces/IPrime.sol";

import { ComptrollerInterface, Action } from "./ComptrollerInterface.sol";
import { ComptrollerInterface, VTokenInterface, Action } from "./ComptrollerInterface.sol";
import { ComptrollerStorage } from "./ComptrollerStorage.sol";
import { ExponentialNoError } from "./ExponentialNoError.sol";
import { VToken } from "./VToken.sol";
import { RewardsDistributor } from "./Rewards/RewardsDistributor.sol";
import { MaxLoopsLimitHelper } from "./MaxLoopsLimitHelper.sol";
import { ensureNonzeroAddress } from "./lib/validators.sol";
import { IFlashloanReceiver } from "./Flashloan/interfaces/IFlashloanReceiver.sol";

/**
* @title Comptroller
Expand Down Expand Up @@ -100,9 +101,13 @@ contract Comptroller is

/// @notice Emitted when a market is unlisted
event MarketUnlisted(address indexed vToken);

/// @notice Emitted when the borrowing or redeeming delegate rights are updated for an account
event DelegateUpdated(address indexed approver, address indexed delegate, bool approved);

/// @notice Emitted When the flash loan is successfully executed
event FlashloanExecuted(address receiver, VTokenInterface[] assets, uint256[] amounts);

/// @notice Thrown when collateral factor exceeds the upper bound
error InvalidCollateralFactor();

Expand Down Expand Up @@ -199,6 +204,18 @@ contract Comptroller is
/// @notice Thrown if delegate approval status is already set to the requested value
error DelegationStatusUnchanged();

/// @notice Thrown if invalid flashloan params passed
error InvalidFlashloanParams();

///@notice Thrown if the flashloan is not enabled for a particular market
error FlashLoanNotEnabled(address market);

///@notice Thrown if repayment amount is insufficient
error InsufficientReypaymentBalance(address tokenAddress);

///@notice Thrown if executeOperation failed
error ExecuteFlashloanFailed();

/// @param poolRegistry_ Pool registry address
/// @custom:oz-upgrades-unsafe-allow constructor
/// @custom:error ZeroAddressNotAllowed is thrown when pool registry address is zero
Expand Down Expand Up @@ -919,6 +936,82 @@ contract Comptroller is
}
}

/**
* @notice Executes a flashloan operation with the specified assets and amounts.
* @dev Transfer the specified assets to the receiver contract and ensures that the total repayment (amount + fee)
* is returned by the receiver contract after the operation for each asset. The function performs checks to ensure the validity
* of parameters, that flashloans are enabled for the given assets, and that the total repayment is sufficient.
* Reverts on invalid parameters, disabled flashloans, or insufficient repayment.
* @param receiver The address of the contract that will receive the flashloan and execute the operation.
* @param assets The addresses of the assets to be loaned.
* @param amounts The amounts of each asset to be loaned.
* @custom:requirements
* - `assets.length` must be equal to `amounts.length`.
* - `assets.length` and `amounts.length` must not be zero.
* - The `receiver` address must not be the zero address.
* - Flashloans must be enabled for each asset.
* - The `receiver` contract must repay the loan with the appropriate fee.
* @custom:reverts
* - Reverts with `InvalidFlashloanParams()` if parameter checks fail.
* - Reverts with `FlashLoanNotEnabled(asset)` if flashloans are disabled for any of the requested assets.
* - Reverts with `ExecuteFlashloanFailed` if the receiver contract fails to execute the operation.
* - Reverts with `InsufficientReypaymentBalance(asset)` if the repayment (amount + fee) is insufficient after the operation.
*/
function executeFlashloan(
address receiver,
VTokenInterface[] calldata assets,
uint256[] calldata amounts
) external override {
// Asset and amount length must be equals and not be zero
if (assets.length != amounts.length || assets.length == 0 || receiver == address(0)) {
revert InvalidFlashloanParams();
}

IFlashloanReceiver receiverContract = IFlashloanReceiver(receiver);
uint256 len = assets.length;
uint256[] memory fees = new uint256[](len);
uint256[] memory balanceBefore = new uint256[](len);

for (uint256 j; j < len; ) {
// Revert if flashloan is not enabled
if (!(assets[j]).isFlashloanEnabled()) {
revert FlashLoanNotEnabled(address(assets[j]));
}

uint256 assetFlashloanFee = (assets[j]).flashloanFeeMantissa();
fees[j] = (amounts[j] * assetFlashloanFee) / MANTISSA_ONE;

// Transfer the asset
(assets[j]).transferUnderlying(receiver, amounts[j]);

balanceBefore[j] = (assets[j]).getCash();

unchecked {
++j;
}
}

// Call the execute operation on receiver contract
if (!receiverContract.executeOperation(assets, amounts, fees, receiver, "")) {
revert ExecuteFlashloanFailed();
}

for (uint256 k; k < len; ) {
uint256 balanceAfter = (assets[k]).getCash();

// balanceAfter should be greater than the fee calculated
if ((balanceAfter - balanceBefore[k]) < (amounts[k] + fees[k])) {
revert InsufficientReypaymentBalance(address(assets[k]));
}

unchecked {
++k;
}
}

emit FlashloanExecuted(receiver, assets, amounts);
}

/**
* @notice Liquidates all borrows of the borrower. Callable only if the collateral is less than
* a predefined threshold, and the account collateral can be seized to cover all borrows. If
Expand Down
3 changes: 3 additions & 0 deletions contracts/ComptrollerInterface.sol
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { ResilientOracleInterface } from "@venusprotocol/oracle/contracts/interf

import { VToken } from "./VToken.sol";
import { RewardsDistributor } from "./Rewards/RewardsDistributor.sol";
import { VTokenInterface } from "./VTokenInterfaces.sol";

enum Action {
MINT,
Expand Down Expand Up @@ -90,6 +91,8 @@ interface ComptrollerInterface {

function preTransferHook(address vToken, address src, address dst, uint256 transferTokens) external;

function executeFlashloan(address receiver, VTokenInterface[] calldata assets, uint256[] calldata amounts) external;

function isComptroller() external view returns (bool);

/*** Liquidity/Liquidation Calculations ***/
Expand Down
5 changes: 5 additions & 0 deletions contracts/ErrorReporter.sol
Original file line number Diff line number Diff line change
Expand Up @@ -46,4 +46,9 @@ contract TokenErrorReporter {
error ReduceReservesCashValidation();

error SetInterestRateModelFreshCheck();

error FlashLoanNotEnabled(address);
error ExecuteFlashloanFailed();
error InvalidComptroller(address comptroller);
error InsufficientReypaymentBalance(address tokenAddress);
}
40 changes: 39 additions & 1 deletion contracts/VTokenInterfaces.sol
Original file line number Diff line number Diff line change
Expand Up @@ -131,12 +131,22 @@ contract VTokenStorage {
*/
uint256 public reduceReservesBlockNumber;

/**
* @notice flashloan is enabled for this market or not
*/
bool public isFlashloanEnabled;

/**
* @notice fee percentage collected by protocol on flashloan
*/
uint256 public flashloanFeeMantissa;

/**
* @dev This empty reserved space is put in place to allow future versions to add new
* variables without shifting down storage in the inheritance chain.
* See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps
*/
uint256[48] private __gap;
uint256[47] private __gap;
}

/**
Expand Down Expand Up @@ -288,6 +298,26 @@ abstract contract VTokenInterface is VTokenStorage {
*/
event ProtocolSeize(address indexed from, address indexed to, uint256 amount);

/**
* @notice Event emitted when flashloanEnabled status is changed
*/
event ToggleFlashloanEnabled(bool oldEnabled, bool enabled);

/**
* @notice Event emitted when flashloan is executed
*/
event FlashloanExecuted(address receiver, address underlying, uint256 amount);

/**
* @notice Event emitted when asset is transferred to receiver
*/
event FlashloanAmountTransferred(address asset, address receiver, uint256 amount);

/**
* @notice Event emitted when flashloan fee mantissa is updated
*/
event FLashloanFeeUpdated(uint256 oldFee, uint256 fee);

/*** User Interface ***/

function mint(uint256 mintAmount) external virtual returns (uint256);
Expand Down Expand Up @@ -336,6 +366,10 @@ abstract contract VTokenInterface is VTokenStorage {

function sweepToken(IERC20Upgradeable token) external virtual;

function transferUnderlying(address receiver, uint256 amount) external virtual;

function executeFlashloan(address receiver, uint256 amount) external virtual returns (uint256);

/*** Admin Functions ***/

function setReserveFactor(uint256 newReserveFactorMantissa) external virtual;
Expand All @@ -350,6 +384,10 @@ abstract contract VTokenInterface is VTokenStorage {

function addReserves(uint256 addAmount) external virtual;

function toggleFlashloan() external virtual;

function setFlashloanFeeMantissa(uint256 fee) external virtual;

function totalBorrowsCurrent() external virtual returns (uint256);

function balanceOfUnderlying(address owner) external virtual returns (uint256);
Expand Down
8 changes: 6 additions & 2 deletions contracts/test/UpgradedVToken.sol
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,9 @@ contract UpgradedVToken is VToken {
address payable admin_,
address accessControlManager_,
RiskManagementInit memory riskManagement,
uint256 reserveFactorMantissa_
uint256 reserveFactorMantissa_,
bool isFlashloanEnabled_,
uint256 flashloanFeeMantissa_
) public reinitializer(2) {
super._initialize(
underlying_,
Expand All @@ -70,7 +72,9 @@ contract UpgradedVToken is VToken {
admin_,
accessControlManager_,
riskManagement,
reserveFactorMantissa_
reserveFactorMantissa_,
isFlashloanEnabled_,
flashloanFeeMantissa_
);
}

Expand Down
1 change: 1 addition & 0 deletions hardhat.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,7 @@ const config: HardhatUserConfig = {
yul: !process.env.CI,
},
},
viaIR: true,
evmVersion: "paris",
outputSelection: {
"*": {
Expand Down

0 comments on commit eb28e0d

Please sign in to comment.