Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
daa3fc2
feat: simple flash loan contracts
Debugger022 Dec 18, 2024
32bd0be
feat: add simple flash loan functionality
Debugger022 Dec 18, 2024
a0c30a6
feat: add flash loan contracts
Debugger022 Dec 18, 2024
eb28e0d
feat: add flash loan functionality for multiple assets
Debugger022 Dec 18, 2024
b0f3b25
test: add tests for flash loan single and multiple assets
Debugger022 Dec 19, 2024
64c01dd
test: refactor tests for the single asset flashloan
Debugger022 Dec 24, 2024
82b0fc8
refactor: flash loan functionality to reduce comptroller size
Debugger022 Dec 26, 2024
008e135
chore: update deployment config and script to allow flashloan in vtokens
Debugger022 Dec 26, 2024
b193870
chore: add flashloan fee mantissa in deployment config for vtokens
Debugger022 Dec 26, 2024
cf97406
test: multiple asset flash loan tests
Debugger022 Dec 26, 2024
b126f3c
test: single asset flash loan fork tests
Debugger022 Dec 27, 2024
a8bebee
test: multiple assets flash loan fork tests
Debugger022 Dec 27, 2024
673612a
refactor: reduce comptroller size
Debugger022 Dec 30, 2024
39b1fdc
refactor: use camel case for flashloan
Debugger022 Jan 6, 2025
59b46cc
chore: fix typo
Debugger022 Jan 6, 2025
cb9912c
Merge branch 'develop' into feat/flash-loan
Debugger022 Jan 14, 2025
c0f036d
refactor: add param arg to the execute operation method
Debugger022 Mar 12, 2025
abe0450
refactor: flash loan functionality
Debugger022 Mar 18, 2025
b03bf68
Merge branch 'develop' into feat/flash-loan
Debugger022 Mar 18, 2025
f4327b0
refactor: transfer protocol fee to PSR for flashloan amount
Debugger022 Apr 1, 2025
7a102bf
refactor: add interface for the PSR
Debugger022 Apr 1, 2025
17dd8e9
refactor: update deployment config file
Debugger022 Apr 1, 2025
e564740
fix: increase node options space size to 8192
Debugger022 Apr 1, 2025
1c2b575
fix: increase node options space size to 8192 for ci.yaml
Debugger022 Apr 1, 2025
e2a2e35
fix: increase node options space size to 12288 for ci.yaml
Debugger022 Apr 2, 2025
88402cd
Merge branch 'develop' into feat/flash-loan
Debugger022 Jun 19, 2025
79f792d
fix: minor fix
Debugger022 Jun 27, 2025
d094b7a
Merge branch 'develop' into feat/flash-loan
Debugger022 Aug 12, 2025
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
2 changes: 1 addition & 1 deletion .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ jobs:
name: Test
runs-on: ubuntu-22.04
env:
NODE_OPTIONS: --max-old-space-size=4096
NODE_OPTIONS: --max-old-space-size=12288
steps:
- uses: actions/checkout@v2

Expand Down
2 changes: 1 addition & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ RUN wget https://github.com/ethereum/solidity/releases/download/v0.8.25/solc-sta
RUN mkdir -p /usr/app
WORKDIR /usr/app

ENV NODE_OPTIONS=--max-old-space-size=4096
ENV NODE_OPTIONS=--max-old-space-size=8192

# First add deps
RUN npm install -g yarn
Expand Down
154 changes: 143 additions & 11 deletions contracts/Comptroller.sol
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,16 @@ import { Ownable2StepUpgradeable } from "@openzeppelin/contracts-upgradeable/acc
import { ResilientOracleInterface } from "@venusprotocol/oracle/contracts/interfaces/OracleInterface.sol";
import { AccessControlledV8 } from "@venusprotocol/governance-contracts/contracts/Governance/AccessControlledV8.sol";
import { IPrime } from "@venusprotocol/venus-protocol/contracts/Tokens/Prime/Interfaces/IPrime.sol";
import { IProtocolShareReserve } from "./Flashloan/interfaces/IProtocolShareReserve.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 +102,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 @@ -163,6 +169,24 @@ contract Comptroller is
/// @notice Thrown when collateral factor is not zero
error CollateralFactorIsNotZero();

/// @notice Thrown when the close factor is invalid
error InvalidCloseFactor();

/// @notice Thrown when the liquidation incentive is invalid
error InvalidLiquidationIncentive();

/// @notice Thrown when the VToken is invalid
error InvalidVToken();

/// @notice Thrown when the input is invalid
error InvalidInput();

/// @notice Thrown when the rewards distributor already exists
error RewardsDistributorAlreadyExists();

/// @notice Thrown when the market does not exist
error MarketNotExist();

/**
* @notice Thrown during the liquidation if user's total collateral amount is lower than
* a predefined threshold. In this case only batch liquidations (either liquidateAccount
Expand Down Expand Up @@ -199,6 +223,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 +955,86 @@ contract Comptroller is
}
}

/**
* @notice Executes a flashLoan operation with the specified vTokens and underlyingAmounts.
* @dev Transfer the specified vTokens 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 vTokens, 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 vTokens The addresses of the vTokens to be loaned.
* @param underlyingAmounts The underlyingAmounts of each asset to be loaned.
* @param param The bytes passed in the executeOperation call.
* @custom:requirements
* - `vTokens.length` must be equal to `underlyingAmounts.length`.
* - `vTokens.length` and `underlyingAmounts.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 vTokens.
* - 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 vTokens,
uint256[] calldata underlyingAmounts,
bytes calldata param
) external override {
ensureNonzeroAddress(receiver);
// Asset and amount length must be equals and not be zero
if (vTokens.length != underlyingAmounts.length || vTokens.length == 0 || receiver == address(0)) {
revert InvalidFlashLoanParams();
}

uint256 len = vTokens.length;
uint256[] memory protocolFees = new uint256[](vTokens.length);
uint256[] memory supplierFees = new uint256[](vTokens.length);
uint256[] memory totalFees = new uint256[](vTokens.length);
uint256[] memory balanceAfterTransfer = new uint256[](len);

for (uint256 j; j < len; ) {
(protocolFees[j], supplierFees[j]) = (vTokens[j]).calculateFlashLoanFee(underlyingAmounts[j]);
totalFees[j] = protocolFees[j] + supplierFees[j]; // Sum protocol and supplier fees
// Transfer the asset
(balanceAfterTransfer[j]) = (vTokens[j]).transferOutUnderlying(receiver, underlyingAmounts[j]);

unchecked {
++j;
}
}

// Call the execute operation on receiver contract
if (!IFlashLoanReceiver(receiver).executeOperation(vTokens, underlyingAmounts, totalFees, msg.sender, param)) {
revert ExecuteFlashLoanFailed();
}

for (uint256 k; k < len; ) {
(vTokens[k]).transferInUnderlyingAndVerify(
receiver,
underlyingAmounts[k],
totalFees[k],
balanceAfterTransfer[k]
);
(vTokens[k]).transferOutUnderlying(vTokens[k].protocolShareReserve(), protocolFees[k]);

// Update protocol share reserve state
IProtocolShareReserve(vTokens[k].protocolShareReserve()).updateAssetsState(
address(vTokens[k].comptroller()),
address(vTokens[k].underlying()),
IProtocolShareReserve.IncomeType.FLASHLOAN
);

unchecked {
++k;
}
}

emit FlashLoanExecuted(receiver, vTokens, underlyingAmounts);
}

/**
* @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 +1099,9 @@ contract Comptroller is

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

Expand All @@ -995,8 +1113,10 @@ contract Comptroller is
*/
function setCloseFactor(uint256 newCloseFactorMantissa) external {
_checkAccessAllowed("setCloseFactor(uint256)");
require(MAX_CLOSE_FACTOR_MANTISSA >= newCloseFactorMantissa, "Close factor greater than maximum close factor");
require(MIN_CLOSE_FACTOR_MANTISSA <= newCloseFactorMantissa, "Close factor smaller than minimum close factor");

if (MAX_CLOSE_FACTOR_MANTISSA < newCloseFactorMantissa || MIN_CLOSE_FACTOR_MANTISSA > newCloseFactorMantissa) {
revert InvalidCloseFactor();
}

uint256 oldCloseFactorMantissa = closeFactorMantissa;
closeFactorMantissa = newCloseFactorMantissa;
Expand Down Expand Up @@ -1071,7 +1191,9 @@ contract Comptroller is
* @custom:access Controlled by AccessControlManager
*/
function setLiquidationIncentive(uint256 newLiquidationIncentiveMantissa) external {
require(newLiquidationIncentiveMantissa >= MANTISSA_ONE, "liquidation incentive should be greater than 1e18");
if (newLiquidationIncentiveMantissa < MANTISSA_ONE) {
revert InvalidLiquidationIncentive();
}

_checkAccessAllowed("setLiquidationIncentive(uint256)");

Expand Down Expand Up @@ -1099,7 +1221,9 @@ contract Comptroller is
revert MarketAlreadyListed(address(vToken));
}

require(vToken.isVToken(), "Comptroller: Invalid vToken"); // Sanity check to make sure its really a VToken
if (!vToken.isVToken()) {
revert InvalidVToken();
}

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

require(numMarkets != 0 && numMarkets == numBorrowCaps, "invalid input");
if (numMarkets == 0 || numMarkets != numBorrowCaps) {
revert InvalidInput();
}

_ensureMaxLoops(numMarkets);

Expand All @@ -1157,8 +1283,9 @@ contract Comptroller is
_checkAccessAllowed("setMarketSupplyCaps(address[],uint256[])");
uint256 vTokensCount = vTokens.length;

require(vTokensCount != 0, "invalid number of markets");
require(vTokensCount == newSupplyCaps.length, "invalid number of markets");
if (vTokensCount == 0 || vTokensCount != newSupplyCaps.length) {
revert InvalidInput();
}

_ensureMaxLoops(vTokensCount);

Expand Down Expand Up @@ -1216,7 +1343,9 @@ contract Comptroller is
* @custom:event Emits NewRewardsDistributor with distributor address
*/
function addRewardsDistributor(RewardsDistributor _rewardsDistributor) external onlyOwner {
require(!rewardsDistributorExists[address(_rewardsDistributor)], "already exists");
if (rewardsDistributorExists[address(_rewardsDistributor)]) {
revert RewardsDistributorAlreadyExists();
}

uint256 rewardsDistributorsLen = rewardsDistributors.length;
_ensureMaxLoops(rewardsDistributorsLen + 1);
Expand Down Expand Up @@ -1554,7 +1683,10 @@ contract Comptroller is
* @param paused The new paused state (true=paused, false=unpaused)
*/
function _setActionPaused(address market, Action action, bool paused) internal {
require(markets[market].isListed, "cannot pause a market that is not listed");
if (!markets[market].isListed) {
revert MarketNotExist();
}

_actionPaused[market][action] = paused;
emit ActionPausedMarket(VToken(market), action, paused);
}
Expand Down
8 changes: 8 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,13 @@ interface ComptrollerInterface {

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

function executeFlashLoan(
address receiver,
VTokenInterface[] calldata assets,
uint256[] calldata amounts,
bytes calldata param
) 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);
}
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 flashLoan 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);
}
13 changes: 13 additions & 0 deletions contracts/Flashloan/interfaces/IProtocolShareReserve.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
// SPDX-License-Identifier: BSD-3-Clause
pragma solidity ^0.8.25;

interface IProtocolShareReserve {
/// @notice it represents the type of vToken income
enum IncomeType {
SPREAD,
LIQUIDATION,
FLASHLOAN
}

function updateAssetsState(address comptroller, address asset, IncomeType incomeType) external;
}
Loading
Loading