Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[WIP] Flash loan implementation #460

Draft
wants to merge 4 commits into
base: develop
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 { 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 @@ -47,7 +48,7 @@
{
// PoolRegistry, immutable to save on gas
/// @custom:oz-upgrades-unsafe-allow state-variable-immutable
address public immutable poolRegistry;

Check warning on line 51 in contracts/Comptroller.sol

View workflow job for this annotation

GitHub Actions / Lint

Immutable variables name are set to be in capitalized SNAKE_CASE

/// @notice Emitted when an account enters a market
event MarketEntered(VToken indexed vToken, address indexed account);
Expand Down Expand Up @@ -100,9 +101,13 @@

/// @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 @@
/// @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 @@ -500,7 +517,7 @@
* @param redeemAmount The amount of the underlying asset being redeemed
* @param redeemTokens The number of tokens being redeemed
*/
function redeemVerify(address vToken, address redeemer, uint256 redeemAmount, uint256 redeemTokens) external {

Check warning on line 520 in contracts/Comptroller.sol

View workflow job for this annotation

GitHub Actions / Lint

Variable "redeemAmount" is unused

Check warning on line 520 in contracts/Comptroller.sol

View workflow job for this annotation

GitHub Actions / Lint

Variable "redeemTokens" is unused
if (address(prime) != address(0)) {
prime.accrueInterestAndUpdateScore(redeemer, vToken);
}
Expand Down Expand Up @@ -919,6 +936,82 @@
}
}

/**
* @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 Expand Up @@ -983,7 +1076,7 @@

for (uint256 i; i < marketsCount; ++i) {
(, uint256 borrowBalance, ) = _safeGetAccountSnapshot(borrowMarkets[i], borrower);
require(borrowBalance == 0, "Nonzero borrow balance after liquidation");

Check warning on line 1079 in contracts/Comptroller.sol

View workflow job for this annotation

GitHub Actions / Lint

Use Custom Errors instead of require statements
}
}

Expand All @@ -995,8 +1088,8 @@
*/
function setCloseFactor(uint256 newCloseFactorMantissa) external {
_checkAccessAllowed("setCloseFactor(uint256)");
require(MAX_CLOSE_FACTOR_MANTISSA >= newCloseFactorMantissa, "Close factor greater than maximum close factor");

Check warning on line 1091 in contracts/Comptroller.sol

View workflow job for this annotation

GitHub Actions / Lint

Use Custom Errors instead of require statements
require(MIN_CLOSE_FACTOR_MANTISSA <= newCloseFactorMantissa, "Close factor smaller than minimum close factor");

Check warning on line 1092 in contracts/Comptroller.sol

View workflow job for this annotation

GitHub Actions / Lint

Use Custom Errors instead of require statements

uint256 oldCloseFactorMantissa = closeFactorMantissa;
closeFactorMantissa = newCloseFactorMantissa;
Expand Down Expand Up @@ -1071,7 +1164,7 @@
* @custom:access Controlled by AccessControlManager
*/
function setLiquidationIncentive(uint256 newLiquidationIncentiveMantissa) external {
require(newLiquidationIncentiveMantissa >= MANTISSA_ONE, "liquidation incentive should be greater than 1e18");

Check warning on line 1167 in contracts/Comptroller.sol

View workflow job for this annotation

GitHub Actions / Lint

Use Custom Errors instead of require statements

_checkAccessAllowed("setLiquidationIncentive(uint256)");

Expand Down Expand Up @@ -1099,7 +1192,7 @@
revert MarketAlreadyListed(address(vToken));
}

require(vToken.isVToken(), "Comptroller: Invalid vToken"); // Sanity check to make sure its really a VToken

Check warning on line 1195 in contracts/Comptroller.sol

View workflow job for this annotation

GitHub Actions / Lint

Use Custom Errors instead of require statements

Market storage newMarket = markets[address(vToken)];
newMarket.isListed = true;
Expand Down Expand Up @@ -1133,7 +1226,7 @@
uint256 numMarkets = vTokens.length;
uint256 numBorrowCaps = newBorrowCaps.length;

require(numMarkets != 0 && numMarkets == numBorrowCaps, "invalid input");

Check warning on line 1229 in contracts/Comptroller.sol

View workflow job for this annotation

GitHub Actions / Lint

Use Custom Errors instead of require statements

_ensureMaxLoops(numMarkets);

Expand All @@ -1157,7 +1250,7 @@
_checkAccessAllowed("setMarketSupplyCaps(address[],uint256[])");
uint256 vTokensCount = vTokens.length;

require(vTokensCount != 0, "invalid number of markets");

Check warning on line 1253 in contracts/Comptroller.sol

View workflow job for this annotation

GitHub Actions / Lint

Use Custom Errors instead of require statements
require(vTokensCount == newSupplyCaps.length, "invalid number of markets");

_ensureMaxLoops(vTokensCount);
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);
}
23 changes: 23 additions & 0 deletions contracts/Flashloan/base/FlashloanReceiverBase.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
// SPDX-License-Identifier: BSD-3-Clause
pragma solidity 0.8.25;

import { IFlashloanReceiver } from "../interfaces/IFlashloanReceiver.sol";
import { ComptrollerInterface } from "../../ComptrollerInterface.sol";

/// @title FlashloanReceiverBase
/// @notice A base contract for implementing flashloan receiver logic.
/// @dev This abstract contract provides the necessary structure for inheriting contracts to implement the `IFlashloanReceiver` interface.
/// It stores a reference to the Comptroller contract, which manages various aspects of the protocol.
abstract contract FlashloanReceiverBase is IFlashloanReceiver {
/// @notice The Comptroller contract that governs the protocol.
/// @dev This immutable variable stores the address of the Comptroller contract, which cannot be changed after deployment.
ComptrollerInterface public immutable COMPTROLLER;

/**
* @notice Constructor to initialize the base contract with the Comptroller address.
* @param comptroller_ The address of the Comptroller contract that oversees the protocol.
*/
constructor(ComptrollerInterface comptroller_) {
COMPTROLLER = comptroller_;
}
}
24 changes: 24 additions & 0 deletions contracts/Flashloan/base/FlashloanSimpleReceiverBase.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
// SPDX-License-Identifier: BSD-3-Clause
pragma solidity 0.8.25;

import { IFlashloanSimpleReceiver } from "../interfaces/IFlashloanSimpleReceiver.sol";
import { VTokenInterface } from "../../VTokenInterfaces.sol";

/**
* @title FlashloanSimpleReceiverBase
* @author Venus
* @notice Base contract to develop a flashloan-receiver contract.
* @dev This contract serves as a foundational contract for implementing custom flash loan receiver logic.
* Inheritors of this contract need to implement the `executeOperation` function defined in the `IFlashloanSimpleReceiver` interface.
*/
abstract contract FlashloanSimpleReceiverBase is IFlashloanSimpleReceiver {
/// @notice The VToken contract used to initiate and handle flash loan
/// @dev This is an immutable reference to the VTokenInterface, which enables the flash loan functionality.
VTokenInterface public immutable VTOKEN;

/// @notice Initializes the base contract by setting the VToken address
/// @param vToken_ The address of the VToken contract that supports flash loan
constructor(VTokenInterface vToken_) {
VTOKEN = vToken_;
}
}
28 changes: 28 additions & 0 deletions contracts/Flashloan/interfaces/IFlashloanReceiver.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
// SPDX-License-Identifier: BSD-3-Clause
pragma solidity ^0.8.25;

import { VTokenInterface } from "../../VTokenInterfaces.sol";

/// @title IFlashloanReceiver
/// @notice Interface for flashloan receiver contract, which execute custom logic with flash-borrowed assets.
/// @dev This interface defines the method that must be implemented by any contract wishing to interact with the flashloan system.
/// Contracts must ensure they have the means to repay both the flashloaned amount and the associated premium (fee).
interface IFlashloanReceiver {
/**
* @notice Executes an operation after receiving the flash-borrowed assets.
* @dev Implementation of this function must ensure the borrowed amount plus the premium (fee) is repaid within the same transaction.
* @param assets The addresses of the assets that were flash-borrowed.
* @param amounts The amounts of each of the flash-borrowed assets.
* @param premiums The premiums (fees) associated with each flash-borrowed asset.
* @param initiator The address that initiated the flashloan operation.
* @param param Additional parameters encoded as bytes. These can be used to pass custom data to the receiver contract.
* @return True if the operation succeeds and the borrowed amount plus the premium is repaid, false otherwise.
*/
function executeOperation(
VTokenInterface[] calldata assets,
uint256[] calldata amounts,
uint256[] calldata premiums,
address initiator,
bytes calldata param
) external returns (bool);
}
23 changes: 23 additions & 0 deletions contracts/Flashloan/interfaces/IFlashloanSimpleReceiver.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
// SPDX-License-Identifier: BSD-3-Clause
pragma solidity ^0.8.25;

interface IFlashloanSimpleReceiver {
/**
* @notice Executes an operation after receiving the flash-borrowed asset
* @dev Ensure that the contract can return the debt + premium, e.g., has
* enough funds to repay and has to transfer the debt + premium to the VToken
* @param asset The address of the flash-borrowed asset
* @param amount The amount of the flash-borrowed asset
* @param premium The premium (fee) associated with flash-borrowed asset.
* @param initiator The address that initiated the flashloan operation
* @param param The byte-encoded param passed when initiating the flashloan
* @return True if the execution of the operation succeeds, false otherwise
*/
function executeOperation(
address asset,
uint256 amount,
uint256 premium,
address initiator,
bytes calldata param
) external returns (bool);
}
Loading
Loading