Overview
ETH Balance
0 ETH
Eth Value
$0.00Loading...
Loading
Loading...
Loading
Cross-Chain Transactions
Loading...
Loading
Contract Name:
PoolLens
Compiler Version
v0.8.25+commit.b61c2a91
Contract Source Code (Solidity Standard Json-Input format)
// SPDX-License-Identifier: BSD-3-Clause
pragma solidity 0.8.25;
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import { IERC20Metadata } from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol";
import { ResilientOracleInterface } from "@venusprotocol/oracle/contracts/interfaces/OracleInterface.sol";
import { ExponentialNoError } from "../ExponentialNoError.sol";
import { VToken } from "../VToken.sol";
import { Action, ComptrollerInterface, ComptrollerViewInterface } from "../ComptrollerInterface.sol";
import { PoolRegistryInterface } from "../Pool/PoolRegistryInterface.sol";
import { PoolRegistry } from "../Pool/PoolRegistry.sol";
import { RewardsDistributor } from "../Rewards/RewardsDistributor.sol";
import { TimeManagerV8 } from "@venusprotocol/solidity-utilities/contracts/TimeManagerV8.sol";
/**
* @title PoolLens
* @author Venus
* @notice The `PoolLens` contract is designed to retrieve important information for each registered pool. A list of essential information
* for all pools within the lending protocol can be acquired through the function `getAllPools()`. Additionally, the following records can be
* looked up for specific pools and markets:
- the vToken balance of a given user;
- the pool data (oracle address, associated vToken, liquidation incentive, etc) of a pool via its associated comptroller address;
- the vToken address in a pool for a given asset;
- a list of all pools that support an asset;
- the underlying asset price of a vToken;
- the metadata (exchange/borrow/supply rate, total supply, collateral factor, etc) of any vToken.
*/
contract PoolLens is ExponentialNoError, TimeManagerV8 {
/**
* @dev Struct for PoolDetails.
*/
struct PoolData {
string name;
address creator;
address comptroller;
uint256 blockPosted;
uint256 timestampPosted;
string category;
string logoURL;
string description;
address priceOracle;
uint256 closeFactor;
uint256 liquidationIncentive;
uint256 minLiquidatableCollateral;
VTokenMetadata[] vTokens;
}
/**
* @dev Struct for VToken.
*/
struct VTokenMetadata {
address vToken;
uint256 exchangeRateCurrent;
uint256 supplyRatePerBlockOrTimestamp;
uint256 borrowRatePerBlockOrTimestamp;
uint256 reserveFactorMantissa;
uint256 supplyCaps;
uint256 borrowCaps;
uint256 totalBorrows;
uint256 totalReserves;
uint256 totalSupply;
uint256 totalCash;
bool isListed;
uint256 collateralFactorMantissa;
address underlyingAssetAddress;
uint256 vTokenDecimals;
uint256 underlyingDecimals;
uint256 pausedActions;
}
/**
* @dev Struct for VTokenBalance.
*/
struct VTokenBalances {
address vToken;
uint256 balanceOf;
uint256 borrowBalanceCurrent;
uint256 balanceOfUnderlying;
uint256 tokenBalance;
uint256 tokenAllowance;
}
/**
* @dev Struct for underlyingPrice of VToken.
*/
struct VTokenUnderlyingPrice {
address vToken;
uint256 underlyingPrice;
}
/**
* @dev Struct with pending reward info for a market.
*/
struct PendingReward {
address vTokenAddress;
uint256 amount;
}
/**
* @dev Struct with reward distribution totals for a single reward token and distributor.
*/
struct RewardSummary {
address distributorAddress;
address rewardTokenAddress;
uint256 totalRewards;
PendingReward[] pendingRewards;
}
/**
* @dev Struct used in RewardDistributor to save last updated market state.
*/
struct RewardTokenState {
// The market's last updated rewardTokenBorrowIndex or rewardTokenSupplyIndex
uint224 index;
// The block number or timestamp the index was last updated at
uint256 blockOrTimestamp;
// The block number or timestamp at which to stop rewards
uint256 lastRewardingBlockOrTimestamp;
}
/**
* @dev Struct with bad debt of a market denominated
*/
struct BadDebt {
address vTokenAddress;
uint256 badDebtUsd;
}
/**
* @dev Struct with bad debt total denominated in usd for a pool and an array of BadDebt structs for each market
*/
struct BadDebtSummary {
address comptroller;
uint256 totalBadDebtUsd;
BadDebt[] badDebts;
}
/**
* @param timeBased_ A boolean indicating whether the contract is based on time or block.
* @param blocksPerYear_ The number of blocks per year
* @custom:oz-upgrades-unsafe-allow constructor
*/
constructor(bool timeBased_, uint256 blocksPerYear_) TimeManagerV8(timeBased_, blocksPerYear_) {}
/**
* @notice Queries the user's supply/borrow balances in vTokens
* @param vTokens The list of vToken addresses
* @param account The user Account
* @return A list of structs containing balances data
*/
function vTokenBalancesAll(VToken[] calldata vTokens, address account) external returns (VTokenBalances[] memory) {
uint256 vTokenCount = vTokens.length;
VTokenBalances[] memory res = new VTokenBalances[](vTokenCount);
for (uint256 i; i < vTokenCount; ++i) {
res[i] = vTokenBalances(vTokens[i], account);
}
return res;
}
/**
* @notice Queries all pools with addtional details for each of them
* @dev This function is not designed to be called in a transaction: it is too gas-intensive
* @param poolRegistryAddress The address of the PoolRegistry contract
* @return Arrays of all Venus pools' data
*/
function getAllPools(address poolRegistryAddress) external view returns (PoolData[] memory) {
PoolRegistryInterface poolRegistryInterface = PoolRegistryInterface(poolRegistryAddress);
PoolRegistry.VenusPool[] memory venusPools = poolRegistryInterface.getAllPools();
uint256 poolLength = venusPools.length;
PoolData[] memory poolDataItems = new PoolData[](poolLength);
for (uint256 i; i < poolLength; ++i) {
PoolRegistry.VenusPool memory venusPool = venusPools[i];
PoolData memory poolData = getPoolDataFromVenusPool(poolRegistryAddress, venusPool);
poolDataItems[i] = poolData;
}
return poolDataItems;
}
/**
* @notice Queries the details of a pool identified by Comptroller address
* @param poolRegistryAddress The address of the PoolRegistry contract
* @param comptroller The Comptroller implementation address
* @return PoolData structure containing the details of the pool
*/
function getPoolByComptroller(
address poolRegistryAddress,
address comptroller
) external view returns (PoolData memory) {
PoolRegistryInterface poolRegistryInterface = PoolRegistryInterface(poolRegistryAddress);
return getPoolDataFromVenusPool(poolRegistryAddress, poolRegistryInterface.getPoolByComptroller(comptroller));
}
/**
* @notice Returns vToken holding the specified underlying asset in the specified pool
* @param poolRegistryAddress The address of the PoolRegistry contract
* @param comptroller The pool comptroller
* @param asset The underlyingAsset of VToken
* @return Address of the vToken
*/
function getVTokenForAsset(
address poolRegistryAddress,
address comptroller,
address asset
) external view returns (address) {
PoolRegistryInterface poolRegistryInterface = PoolRegistryInterface(poolRegistryAddress);
return poolRegistryInterface.getVTokenForAsset(comptroller, asset);
}
/**
* @notice Returns all pools that support the specified underlying asset
* @param poolRegistryAddress The address of the PoolRegistry contract
* @param asset The underlying asset of vToken
* @return A list of Comptroller contracts
*/
function getPoolsSupportedByAsset(
address poolRegistryAddress,
address asset
) external view returns (address[] memory) {
PoolRegistryInterface poolRegistryInterface = PoolRegistryInterface(poolRegistryAddress);
return poolRegistryInterface.getPoolsSupportedByAsset(asset);
}
/**
* @notice Returns the price data for the underlying assets of the specified vTokens
* @param vTokens The list of vToken addresses
* @return An array containing the price data for each asset
*/
function vTokenUnderlyingPriceAll(
VToken[] calldata vTokens
) external view returns (VTokenUnderlyingPrice[] memory) {
uint256 vTokenCount = vTokens.length;
VTokenUnderlyingPrice[] memory res = new VTokenUnderlyingPrice[](vTokenCount);
for (uint256 i; i < vTokenCount; ++i) {
res[i] = vTokenUnderlyingPrice(vTokens[i]);
}
return res;
}
/**
* @notice Returns the pending rewards for a user for a given pool.
* @param account The user account.
* @param comptrollerAddress address
* @return Pending rewards array
*/
function getPendingRewards(
address account,
address comptrollerAddress
) external view returns (RewardSummary[] memory) {
VToken[] memory markets = ComptrollerInterface(comptrollerAddress).getAllMarkets();
RewardsDistributor[] memory rewardsDistributors = ComptrollerViewInterface(comptrollerAddress)
.getRewardDistributors();
RewardSummary[] memory rewardSummary = new RewardSummary[](rewardsDistributors.length);
for (uint256 i; i < rewardsDistributors.length; ++i) {
RewardSummary memory reward;
reward.distributorAddress = address(rewardsDistributors[i]);
reward.rewardTokenAddress = address(rewardsDistributors[i].rewardToken());
reward.totalRewards = rewardsDistributors[i].rewardTokenAccrued(account);
reward.pendingRewards = _calculateNotDistributedAwards(account, markets, rewardsDistributors[i]);
rewardSummary[i] = reward;
}
return rewardSummary;
}
/**
* @notice Returns a summary of a pool's bad debt broken down by market
*
* @param comptrollerAddress Address of the comptroller
*
* @return badDebtSummary A struct with comptroller address, total bad debut denominated in usd, and
* a break down of bad debt by market
*/
function getPoolBadDebt(address comptrollerAddress) external view returns (BadDebtSummary memory) {
uint256 totalBadDebtUsd;
// Get every market in the pool
ComptrollerViewInterface comptroller = ComptrollerViewInterface(comptrollerAddress);
VToken[] memory markets = comptroller.getAllMarkets();
ResilientOracleInterface priceOracle = comptroller.oracle();
BadDebt[] memory badDebts = new BadDebt[](markets.length);
BadDebtSummary memory badDebtSummary;
badDebtSummary.comptroller = comptrollerAddress;
badDebtSummary.badDebts = badDebts;
// // Calculate the bad debt is USD per market
for (uint256 i; i < markets.length; ++i) {
BadDebt memory badDebt;
badDebt.vTokenAddress = address(markets[i]);
badDebt.badDebtUsd =
(VToken(address(markets[i])).badDebt() * priceOracle.getUnderlyingPrice(address(markets[i]))) /
EXP_SCALE;
badDebtSummary.badDebts[i] = badDebt;
totalBadDebtUsd = totalBadDebtUsd + badDebt.badDebtUsd;
}
badDebtSummary.totalBadDebtUsd = totalBadDebtUsd;
return badDebtSummary;
}
/**
* @notice Queries the user's supply/borrow balances in the specified vToken
* @param vToken vToken address
* @param account The user Account
* @return A struct containing the balances data
*/
function vTokenBalances(VToken vToken, address account) public returns (VTokenBalances memory) {
uint256 balanceOf = vToken.balanceOf(account);
uint256 borrowBalanceCurrent = vToken.borrowBalanceCurrent(account);
uint256 balanceOfUnderlying = vToken.balanceOfUnderlying(account);
uint256 tokenBalance;
uint256 tokenAllowance;
IERC20 underlying = IERC20(vToken.underlying());
tokenBalance = underlying.balanceOf(account);
tokenAllowance = underlying.allowance(account, address(vToken));
return
VTokenBalances({
vToken: address(vToken),
balanceOf: balanceOf,
borrowBalanceCurrent: borrowBalanceCurrent,
balanceOfUnderlying: balanceOfUnderlying,
tokenBalance: tokenBalance,
tokenAllowance: tokenAllowance
});
}
/**
* @notice Queries additional information for the pool
* @param poolRegistryAddress Address of the PoolRegistry
* @param venusPool The VenusPool Object from PoolRegistry
* @return Enriched PoolData
*/
function getPoolDataFromVenusPool(
address poolRegistryAddress,
PoolRegistry.VenusPool memory venusPool
) public view returns (PoolData memory) {
// Get tokens in the Pool
ComptrollerInterface comptrollerInstance = ComptrollerInterface(venusPool.comptroller);
VToken[] memory vTokens = comptrollerInstance.getAllMarkets();
VTokenMetadata[] memory vTokenMetadataItems = vTokenMetadataAll(vTokens);
PoolRegistryInterface poolRegistryInterface = PoolRegistryInterface(poolRegistryAddress);
PoolRegistry.VenusPoolMetaData memory venusPoolMetaData = poolRegistryInterface.getVenusPoolMetadata(
venusPool.comptroller
);
ComptrollerViewInterface comptrollerViewInstance = ComptrollerViewInterface(venusPool.comptroller);
PoolData memory poolData = PoolData({
name: venusPool.name,
creator: venusPool.creator,
comptroller: venusPool.comptroller,
blockPosted: venusPool.blockPosted,
timestampPosted: venusPool.timestampPosted,
category: venusPoolMetaData.category,
logoURL: venusPoolMetaData.logoURL,
description: venusPoolMetaData.description,
vTokens: vTokenMetadataItems,
priceOracle: address(comptrollerViewInstance.oracle()),
closeFactor: comptrollerViewInstance.closeFactorMantissa(),
liquidationIncentive: comptrollerViewInstance.liquidationIncentiveMantissa(),
minLiquidatableCollateral: comptrollerViewInstance.minLiquidatableCollateral()
});
return poolData;
}
/**
* @notice Returns the metadata of VToken
* @param vToken The address of vToken
* @return VTokenMetadata struct
*/
function vTokenMetadata(VToken vToken) public view returns (VTokenMetadata memory) {
uint256 exchangeRateCurrent = vToken.exchangeRateStored();
address comptrollerAddress = address(vToken.comptroller());
ComptrollerViewInterface comptroller = ComptrollerViewInterface(comptrollerAddress);
(bool isListed, uint256 collateralFactorMantissa) = comptroller.markets(address(vToken));
address underlyingAssetAddress = vToken.underlying();
uint256 underlyingDecimals = IERC20Metadata(underlyingAssetAddress).decimals();
uint256 pausedActions;
for (uint8 i; i <= uint8(type(Action).max); ++i) {
uint256 paused = ComptrollerInterface(comptrollerAddress).actionPaused(address(vToken), Action(i)) ? 1 : 0;
pausedActions |= paused << i;
}
uint256 supplyRatePerBlock;
if (vToken.totalSupply() > 0) {
supplyRatePerBlock = vToken.supplyRatePerBlock();
}
return
VTokenMetadata({
vToken: address(vToken),
exchangeRateCurrent: exchangeRateCurrent,
supplyRatePerBlockOrTimestamp: supplyRatePerBlock,
borrowRatePerBlockOrTimestamp: vToken.borrowRatePerBlock(),
reserveFactorMantissa: vToken.reserveFactorMantissa(),
supplyCaps: comptroller.supplyCaps(address(vToken)),
borrowCaps: comptroller.borrowCaps(address(vToken)),
totalBorrows: vToken.totalBorrows(),
totalReserves: vToken.totalReserves(),
totalSupply: vToken.totalSupply(),
totalCash: vToken.getCash(),
isListed: isListed,
collateralFactorMantissa: collateralFactorMantissa,
underlyingAssetAddress: underlyingAssetAddress,
vTokenDecimals: vToken.decimals(),
underlyingDecimals: underlyingDecimals,
pausedActions: pausedActions
});
}
/**
* @notice Returns the metadata of all VTokens
* @param vTokens The list of vToken addresses
* @return An array of VTokenMetadata structs
*/
function vTokenMetadataAll(VToken[] memory vTokens) public view returns (VTokenMetadata[] memory) {
uint256 vTokenCount = vTokens.length;
VTokenMetadata[] memory res = new VTokenMetadata[](vTokenCount);
for (uint256 i; i < vTokenCount; ++i) {
res[i] = vTokenMetadata(vTokens[i]);
}
return res;
}
/**
* @notice Returns the price data for the underlying asset of the specified vToken
* @param vToken vToken address
* @return The price data for each asset
*/
function vTokenUnderlyingPrice(VToken vToken) public view returns (VTokenUnderlyingPrice memory) {
ComptrollerViewInterface comptroller = ComptrollerViewInterface(address(vToken.comptroller()));
ResilientOracleInterface priceOracle = comptroller.oracle();
return
VTokenUnderlyingPrice({
vToken: address(vToken),
underlyingPrice: priceOracle.getUnderlyingPrice(address(vToken))
});
}
function _calculateNotDistributedAwards(
address account,
VToken[] memory markets,
RewardsDistributor rewardsDistributor
) internal view returns (PendingReward[] memory) {
PendingReward[] memory pendingRewards = new PendingReward[](markets.length);
for (uint256 i; i < markets.length; ++i) {
// Market borrow and supply state we will modify update in-memory, in order to not modify storage
RewardTokenState memory borrowState;
RewardTokenState memory supplyState;
if (isTimeBased) {
(
borrowState.index,
borrowState.blockOrTimestamp,
borrowState.lastRewardingBlockOrTimestamp
) = rewardsDistributor.rewardTokenBorrowStateTimeBased(address(markets[i]));
(
supplyState.index,
supplyState.blockOrTimestamp,
supplyState.lastRewardingBlockOrTimestamp
) = rewardsDistributor.rewardTokenSupplyStateTimeBased(address(markets[i]));
} else {
(
borrowState.index,
borrowState.blockOrTimestamp,
borrowState.lastRewardingBlockOrTimestamp
) = rewardsDistributor.rewardTokenBorrowState(address(markets[i]));
(
supplyState.index,
supplyState.blockOrTimestamp,
supplyState.lastRewardingBlockOrTimestamp
) = rewardsDistributor.rewardTokenSupplyState(address(markets[i]));
}
Exp memory marketBorrowIndex = Exp({ mantissa: markets[i].borrowIndex() });
// Update market supply and borrow index in-memory
updateMarketBorrowIndex(address(markets[i]), rewardsDistributor, borrowState, marketBorrowIndex);
updateMarketSupplyIndex(address(markets[i]), rewardsDistributor, supplyState);
// Calculate pending rewards
uint256 borrowReward = calculateBorrowerReward(
address(markets[i]),
rewardsDistributor,
account,
borrowState,
marketBorrowIndex
);
uint256 supplyReward = calculateSupplierReward(
address(markets[i]),
rewardsDistributor,
account,
supplyState
);
PendingReward memory pendingReward;
pendingReward.vTokenAddress = address(markets[i]);
pendingReward.amount = borrowReward + supplyReward;
pendingRewards[i] = pendingReward;
}
return pendingRewards;
}
function updateMarketBorrowIndex(
address vToken,
RewardsDistributor rewardsDistributor,
RewardTokenState memory borrowState,
Exp memory marketBorrowIndex
) internal view {
uint256 borrowSpeed = rewardsDistributor.rewardTokenBorrowSpeeds(vToken);
uint256 blockNumberOrTimestamp = getBlockNumberOrTimestamp();
if (
borrowState.lastRewardingBlockOrTimestamp > 0 &&
blockNumberOrTimestamp > borrowState.lastRewardingBlockOrTimestamp
) {
blockNumberOrTimestamp = borrowState.lastRewardingBlockOrTimestamp;
}
uint256 deltaBlocksOrTimestamp = sub_(blockNumberOrTimestamp, borrowState.blockOrTimestamp);
if (deltaBlocksOrTimestamp > 0 && borrowSpeed > 0) {
// Remove the total earned interest rate since the opening of the market from total borrows
uint256 borrowAmount = div_(VToken(vToken).totalBorrows(), marketBorrowIndex);
uint256 tokensAccrued = mul_(deltaBlocksOrTimestamp, borrowSpeed);
Double memory ratio = borrowAmount > 0 ? fraction(tokensAccrued, borrowAmount) : Double({ mantissa: 0 });
Double memory index = add_(Double({ mantissa: borrowState.index }), ratio);
borrowState.index = safe224(index.mantissa, "new index overflows");
borrowState.blockOrTimestamp = blockNumberOrTimestamp;
} else if (deltaBlocksOrTimestamp > 0) {
borrowState.blockOrTimestamp = blockNumberOrTimestamp;
}
}
function updateMarketSupplyIndex(
address vToken,
RewardsDistributor rewardsDistributor,
RewardTokenState memory supplyState
) internal view {
uint256 supplySpeed = rewardsDistributor.rewardTokenSupplySpeeds(vToken);
uint256 blockNumberOrTimestamp = getBlockNumberOrTimestamp();
if (
supplyState.lastRewardingBlockOrTimestamp > 0 &&
blockNumberOrTimestamp > supplyState.lastRewardingBlockOrTimestamp
) {
blockNumberOrTimestamp = supplyState.lastRewardingBlockOrTimestamp;
}
uint256 deltaBlocksOrTimestamp = sub_(blockNumberOrTimestamp, supplyState.blockOrTimestamp);
if (deltaBlocksOrTimestamp > 0 && supplySpeed > 0) {
uint256 supplyTokens = VToken(vToken).totalSupply();
uint256 tokensAccrued = mul_(deltaBlocksOrTimestamp, supplySpeed);
Double memory ratio = supplyTokens > 0 ? fraction(tokensAccrued, supplyTokens) : Double({ mantissa: 0 });
Double memory index = add_(Double({ mantissa: supplyState.index }), ratio);
supplyState.index = safe224(index.mantissa, "new index overflows");
supplyState.blockOrTimestamp = blockNumberOrTimestamp;
} else if (deltaBlocksOrTimestamp > 0) {
supplyState.blockOrTimestamp = blockNumberOrTimestamp;
}
}
function calculateBorrowerReward(
address vToken,
RewardsDistributor rewardsDistributor,
address borrower,
RewardTokenState memory borrowState,
Exp memory marketBorrowIndex
) internal view returns (uint256) {
Double memory borrowIndex = Double({ mantissa: borrowState.index });
Double memory borrowerIndex = Double({
mantissa: rewardsDistributor.rewardTokenBorrowerIndex(vToken, borrower)
});
if (borrowerIndex.mantissa == 0 && borrowIndex.mantissa >= rewardsDistributor.INITIAL_INDEX()) {
// Covers the case where users borrowed tokens before the market's borrow state index was set
borrowerIndex.mantissa = rewardsDistributor.INITIAL_INDEX();
}
Double memory deltaIndex = sub_(borrowIndex, borrowerIndex);
uint256 borrowerAmount = div_(VToken(vToken).borrowBalanceStored(borrower), marketBorrowIndex);
uint256 borrowerDelta = mul_(borrowerAmount, deltaIndex);
return borrowerDelta;
}
function calculateSupplierReward(
address vToken,
RewardsDistributor rewardsDistributor,
address supplier,
RewardTokenState memory supplyState
) internal view returns (uint256) {
Double memory supplyIndex = Double({ mantissa: supplyState.index });
Double memory supplierIndex = Double({
mantissa: rewardsDistributor.rewardTokenSupplierIndex(vToken, supplier)
});
if (supplierIndex.mantissa == 0 && supplyIndex.mantissa >= rewardsDistributor.INITIAL_INDEX()) {
// Covers the case where users supplied tokens before the market's supply state index was set
supplierIndex.mantissa = rewardsDistributor.INITIAL_INDEX();
}
Double memory deltaIndex = sub_(supplyIndex, supplierIndex);
uint256 supplierTokens = VToken(vToken).balanceOf(supplier);
uint256 supplierDelta = mul_(supplierTokens, deltaIndex);
return supplierDelta;
}
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (access/Ownable2Step.sol)
pragma solidity ^0.8.0;
import "./OwnableUpgradeable.sol";
import {Initializable} from "../proxy/utils/Initializable.sol";
/**
* @dev Contract module which provides access control mechanism, where
* there is an account (an owner) that can be granted exclusive access to
* specific functions.
*
* By default, the owner account will be the one that deploys the contract. This
* can later be changed with {transferOwnership} and {acceptOwnership}.
*
* This module is used through inheritance. It will make available all functions
* from parent (Ownable).
*/
abstract contract Ownable2StepUpgradeable is Initializable, OwnableUpgradeable {
address private _pendingOwner;
event OwnershipTransferStarted(address indexed previousOwner, address indexed newOwner);
function __Ownable2Step_init() internal onlyInitializing {
__Ownable_init_unchained();
}
function __Ownable2Step_init_unchained() internal onlyInitializing {
}
/**
* @dev Returns the address of the pending owner.
*/
function pendingOwner() public view virtual returns (address) {
return _pendingOwner;
}
/**
* @dev Starts the ownership transfer of the contract to a new account. Replaces the pending transfer if there is one.
* Can only be called by the current owner.
*/
function transferOwnership(address newOwner) public virtual override onlyOwner {
_pendingOwner = newOwner;
emit OwnershipTransferStarted(owner(), newOwner);
}
/**
* @dev Transfers ownership of the contract to a new account (`newOwner`) and deletes any pending owner.
* Internal function without access restriction.
*/
function _transferOwnership(address newOwner) internal virtual override {
delete _pendingOwner;
super._transferOwnership(newOwner);
}
/**
* @dev The new owner accepts the ownership transfer.
*/
function acceptOwnership() public virtual {
address sender = _msgSender();
require(pendingOwner() == sender, "Ownable2Step: caller is not the new owner");
_transferOwnership(sender);
}
/**
* @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[49] private __gap;
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (access/Ownable.sol)
pragma solidity ^0.8.0;
import "../utils/ContextUpgradeable.sol";
import {Initializable} from "../proxy/utils/Initializable.sol";
/**
* @dev Contract module which provides a basic access control mechanism, where
* there is an account (an owner) that can be granted exclusive access to
* specific functions.
*
* By default, the owner account will be the one that deploys the contract. This
* can later be changed with {transferOwnership}.
*
* This module is used through inheritance. It will make available the modifier
* `onlyOwner`, which can be applied to your functions to restrict their use to
* the owner.
*/
abstract contract OwnableUpgradeable is Initializable, ContextUpgradeable {
address private _owner;
event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
/**
* @dev Initializes the contract setting the deployer as the initial owner.
*/
function __Ownable_init() internal onlyInitializing {
__Ownable_init_unchained();
}
function __Ownable_init_unchained() internal onlyInitializing {
_transferOwnership(_msgSender());
}
/**
* @dev Throws if called by any account other than the owner.
*/
modifier onlyOwner() {
_checkOwner();
_;
}
/**
* @dev Returns the address of the current owner.
*/
function owner() public view virtual returns (address) {
return _owner;
}
/**
* @dev Throws if the sender is not the owner.
*/
function _checkOwner() internal view virtual {
require(owner() == _msgSender(), "Ownable: caller is not the owner");
}
/**
* @dev Leaves the contract without owner. It will not be possible to call
* `onlyOwner` functions. Can only be called by the current owner.
*
* NOTE: Renouncing ownership will leave the contract without an owner,
* thereby disabling any functionality that is only available to the owner.
*/
function renounceOwnership() public virtual onlyOwner {
_transferOwnership(address(0));
}
/**
* @dev Transfers ownership of the contract to a new account (`newOwner`).
* Can only be called by the current owner.
*/
function transferOwnership(address newOwner) public virtual onlyOwner {
require(newOwner != address(0), "Ownable: new owner is the zero address");
_transferOwnership(newOwner);
}
/**
* @dev Transfers ownership of the contract to a new account (`newOwner`).
* Internal function without access restriction.
*/
function _transferOwnership(address newOwner) internal virtual {
address oldOwner = _owner;
_owner = newOwner;
emit OwnershipTransferred(oldOwner, newOwner);
}
/**
* @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[49] private __gap;
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (proxy/utils/Initializable.sol)
pragma solidity ^0.8.2;
import "../../utils/AddressUpgradeable.sol";
/**
* @dev This is a base contract to aid in writing upgradeable contracts, or any kind of contract that will be deployed
* behind a proxy. Since proxied contracts do not make use of a constructor, it's common to move constructor logic to an
* external initializer function, usually called `initialize`. It then becomes necessary to protect this initializer
* function so it can only be called once. The {initializer} modifier provided by this contract will have this effect.
*
* The initialization functions use a version number. Once a version number is used, it is consumed and cannot be
* reused. This mechanism prevents re-execution of each "step" but allows the creation of new initialization steps in
* case an upgrade adds a module that needs to be initialized.
*
* For example:
*
* [.hljs-theme-light.nopadding]
* ```solidity
* contract MyToken is ERC20Upgradeable {
* function initialize() initializer public {
* __ERC20_init("MyToken", "MTK");
* }
* }
*
* contract MyTokenV2 is MyToken, ERC20PermitUpgradeable {
* function initializeV2() reinitializer(2) public {
* __ERC20Permit_init("MyToken");
* }
* }
* ```
*
* TIP: To avoid leaving the proxy in an uninitialized state, the initializer function should be called as early as
* possible by providing the encoded function call as the `_data` argument to {ERC1967Proxy-constructor}.
*
* CAUTION: When used with inheritance, manual care must be taken to not invoke a parent initializer twice, or to ensure
* that all initializers are idempotent. This is not verified automatically as constructors are by Solidity.
*
* [CAUTION]
* ====
* Avoid leaving a contract uninitialized.
*
* An uninitialized contract can be taken over by an attacker. This applies to both a proxy and its implementation
* contract, which may impact the proxy. To prevent the implementation contract from being used, you should invoke
* the {_disableInitializers} function in the constructor to automatically lock it when it is deployed:
*
* [.hljs-theme-light.nopadding]
* ```
* /// @custom:oz-upgrades-unsafe-allow constructor
* constructor() {
* _disableInitializers();
* }
* ```
* ====
*/
abstract contract Initializable {
/**
* @dev Indicates that the contract has been initialized.
* @custom:oz-retyped-from bool
*/
uint8 private _initialized;
/**
* @dev Indicates that the contract is in the process of being initialized.
*/
bool private _initializing;
/**
* @dev Triggered when the contract has been initialized or reinitialized.
*/
event Initialized(uint8 version);
/**
* @dev A modifier that defines a protected initializer function that can be invoked at most once. In its scope,
* `onlyInitializing` functions can be used to initialize parent contracts.
*
* Similar to `reinitializer(1)`, except that functions marked with `initializer` can be nested in the context of a
* constructor.
*
* Emits an {Initialized} event.
*/
modifier initializer() {
bool isTopLevelCall = !_initializing;
require(
(isTopLevelCall && _initialized < 1) || (!AddressUpgradeable.isContract(address(this)) && _initialized == 1),
"Initializable: contract is already initialized"
);
_initialized = 1;
if (isTopLevelCall) {
_initializing = true;
}
_;
if (isTopLevelCall) {
_initializing = false;
emit Initialized(1);
}
}
/**
* @dev A modifier that defines a protected reinitializer function that can be invoked at most once, and only if the
* contract hasn't been initialized to a greater version before. In its scope, `onlyInitializing` functions can be
* used to initialize parent contracts.
*
* A reinitializer may be used after the original initialization step. This is essential to configure modules that
* are added through upgrades and that require initialization.
*
* When `version` is 1, this modifier is similar to `initializer`, except that functions marked with `reinitializer`
* cannot be nested. If one is invoked in the context of another, execution will revert.
*
* Note that versions can jump in increments greater than 1; this implies that if multiple reinitializers coexist in
* a contract, executing them in the right order is up to the developer or operator.
*
* WARNING: setting the version to 255 will prevent any future reinitialization.
*
* Emits an {Initialized} event.
*/
modifier reinitializer(uint8 version) {
require(!_initializing && _initialized < version, "Initializable: contract is already initialized");
_initialized = version;
_initializing = true;
_;
_initializing = false;
emit Initialized(version);
}
/**
* @dev Modifier to protect an initialization function so that it can only be invoked by functions with the
* {initializer} and {reinitializer} modifiers, directly or indirectly.
*/
modifier onlyInitializing() {
require(_initializing, "Initializable: contract is not initializing");
_;
}
/**
* @dev Locks the contract, preventing any future reinitialization. This cannot be part of an initializer call.
* Calling this in the constructor of a contract will prevent that contract from being initialized or reinitialized
* to any version. It is recommended to use this to lock implementation contracts that are designed to be called
* through proxies.
*
* Emits an {Initialized} event the first time it is successfully executed.
*/
function _disableInitializers() internal virtual {
require(!_initializing, "Initializable: contract is initializing");
if (_initialized != type(uint8).max) {
_initialized = type(uint8).max;
emit Initialized(type(uint8).max);
}
}
/**
* @dev Returns the highest version that has been initialized. See {reinitializer}.
*/
function _getInitializedVersion() internal view returns (uint8) {
return _initialized;
}
/**
* @dev Returns `true` if the contract is currently initializing. See {onlyInitializing}.
*/
function _isInitializing() internal view returns (bool) {
return _initializing;
}
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (security/ReentrancyGuard.sol)
pragma solidity ^0.8.0;
import {Initializable} from "../proxy/utils/Initializable.sol";
/**
* @dev Contract module that helps prevent reentrant calls to a function.
*
* Inheriting from `ReentrancyGuard` will make the {nonReentrant} modifier
* available, which can be applied to functions to make sure there are no nested
* (reentrant) calls to them.
*
* Note that because there is a single `nonReentrant` guard, functions marked as
* `nonReentrant` may not call one another. This can be worked around by making
* those functions `private`, and then adding `external` `nonReentrant` entry
* points to them.
*
* TIP: If you would like to learn more about reentrancy and alternative ways
* to protect against it, check out our blog post
* https://blog.openzeppelin.com/reentrancy-after-istanbul/[Reentrancy After Istanbul].
*/
abstract contract ReentrancyGuardUpgradeable is Initializable {
// Booleans are more expensive than uint256 or any type that takes up a full
// word because each write operation emits an extra SLOAD to first read the
// slot's contents, replace the bits taken up by the boolean, and then write
// back. This is the compiler's defense against contract upgrades and
// pointer aliasing, and it cannot be disabled.
// The values being non-zero value makes deployment a bit more expensive,
// but in exchange the refund on every call to nonReentrant will be lower in
// amount. Since refunds are capped to a percentage of the total
// transaction's gas, it is best to keep them low in cases like this one, to
// increase the likelihood of the full refund coming into effect.
uint256 private constant _NOT_ENTERED = 1;
uint256 private constant _ENTERED = 2;
uint256 private _status;
function __ReentrancyGuard_init() internal onlyInitializing {
__ReentrancyGuard_init_unchained();
}
function __ReentrancyGuard_init_unchained() internal onlyInitializing {
_status = _NOT_ENTERED;
}
/**
* @dev Prevents a contract from calling itself, directly or indirectly.
* Calling a `nonReentrant` function from another `nonReentrant`
* function is not supported. It is possible to prevent this from happening
* by making the `nonReentrant` function external, and making it call a
* `private` function that does the actual work.
*/
modifier nonReentrant() {
_nonReentrantBefore();
_;
_nonReentrantAfter();
}
function _nonReentrantBefore() private {
// On the first call to nonReentrant, _status will be _NOT_ENTERED
require(_status != _ENTERED, "ReentrancyGuard: reentrant call");
// Any calls to nonReentrant after this point will fail
_status = _ENTERED;
}
function _nonReentrantAfter() private {
// By storing the original value once again, a refund is triggered (see
// https://eips.ethereum.org/EIPS/eip-2200)
_status = _NOT_ENTERED;
}
/**
* @dev Returns true if the reentrancy guard is currently set to "entered", which indicates there is a
* `nonReentrant` function in the call stack.
*/
function _reentrancyGuardEntered() internal view returns (bool) {
return _status == _ENTERED;
}
/**
* @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[49] private __gap;
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.4) (token/ERC20/extensions/IERC20Permit.sol)
pragma solidity ^0.8.0;
/**
* @dev Interface of the ERC20 Permit extension allowing approvals to be made via signatures, as defined in
* https://eips.ethereum.org/EIPS/eip-2612[EIP-2612].
*
* Adds the {permit} method, which can be used to change an account's ERC20 allowance (see {IERC20-allowance}) by
* presenting a message signed by the account. By not relying on {IERC20-approve}, the token holder account doesn't
* need to send a transaction, and thus is not required to hold Ether at all.
*
* ==== Security Considerations
*
* There are two important considerations concerning the use of `permit`. The first is that a valid permit signature
* expresses an allowance, and it should not be assumed to convey additional meaning. In particular, it should not be
* considered as an intention to spend the allowance in any specific way. The second is that because permits have
* built-in replay protection and can be submitted by anyone, they can be frontrun. A protocol that uses permits should
* take this into consideration and allow a `permit` call to fail. Combining these two aspects, a pattern that may be
* generally recommended is:
*
* ```solidity
* function doThingWithPermit(..., uint256 value, uint256 deadline, uint8 v, bytes32 r, bytes32 s) public {
* try token.permit(msg.sender, address(this), value, deadline, v, r, s) {} catch {}
* doThing(..., value);
* }
*
* function doThing(..., uint256 value) public {
* token.safeTransferFrom(msg.sender, address(this), value);
* ...
* }
* ```
*
* Observe that: 1) `msg.sender` is used as the owner, leaving no ambiguity as to the signer intent, and 2) the use of
* `try/catch` allows the permit to fail and makes the code tolerant to frontrunning. (See also
* {SafeERC20-safeTransferFrom}).
*
* Additionally, note that smart contract wallets (such as Argent or Safe) are not able to produce permit signatures, so
* contracts should have entry points that don't rely on permit.
*/
interface IERC20PermitUpgradeable {
/**
* @dev Sets `value` as the allowance of `spender` over ``owner``'s tokens,
* given ``owner``'s signed approval.
*
* IMPORTANT: The same issues {IERC20-approve} has related to transaction
* ordering also apply here.
*
* Emits an {Approval} event.
*
* Requirements:
*
* - `spender` cannot be the zero address.
* - `deadline` must be a timestamp in the future.
* - `v`, `r` and `s` must be a valid `secp256k1` signature from `owner`
* over the EIP712-formatted function arguments.
* - the signature must use ``owner``'s current nonce (see {nonces}).
*
* For more information on the signature format, see the
* https://eips.ethereum.org/EIPS/eip-2612#specification[relevant EIP
* section].
*
* CAUTION: See Security Considerations above.
*/
function permit(
address owner,
address spender,
uint256 value,
uint256 deadline,
uint8 v,
bytes32 r,
bytes32 s
) external;
/**
* @dev Returns the current nonce for `owner`. This value must be
* included whenever a signature is generated for {permit}.
*
* Every successful call to {permit} increases ``owner``'s nonce by one. This
* prevents a signature from being used multiple times.
*/
function nonces(address owner) external view returns (uint256);
/**
* @dev Returns the domain separator used in the encoding of the signature for {permit}, as defined by {EIP712}.
*/
// solhint-disable-next-line func-name-mixedcase
function DOMAIN_SEPARATOR() external view returns (bytes32);
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (token/ERC20/IERC20.sol)
pragma solidity ^0.8.0;
/**
* @dev Interface of the ERC20 standard as defined in the EIP.
*/
interface IERC20Upgradeable {
/**
* @dev Emitted when `value` tokens are moved from one account (`from`) to
* another (`to`).
*
* Note that `value` may be zero.
*/
event Transfer(address indexed from, address indexed to, uint256 value);
/**
* @dev Emitted when the allowance of a `spender` for an `owner` is set by
* a call to {approve}. `value` is the new allowance.
*/
event Approval(address indexed owner, address indexed spender, uint256 value);
/**
* @dev Returns the amount of tokens in existence.
*/
function totalSupply() external view returns (uint256);
/**
* @dev Returns the amount of tokens owned by `account`.
*/
function balanceOf(address account) external view returns (uint256);
/**
* @dev Moves `amount` tokens from the caller's account to `to`.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* Emits a {Transfer} event.
*/
function transfer(address to, uint256 amount) external returns (bool);
/**
* @dev Returns the remaining number of tokens that `spender` will be
* allowed to spend on behalf of `owner` through {transferFrom}. This is
* zero by default.
*
* This value changes when {approve} or {transferFrom} are called.
*/
function allowance(address owner, address spender) external view returns (uint256);
/**
* @dev Sets `amount` as the allowance of `spender` over the caller's tokens.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* IMPORTANT: Beware that changing an allowance with this method brings the risk
* that someone may use both the old and the new allowance by unfortunate
* transaction ordering. One possible solution to mitigate this race
* condition is to first reduce the spender's allowance to 0 and set the
* desired value afterwards:
* https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
*
* Emits an {Approval} event.
*/
function approve(address spender, uint256 amount) external returns (bool);
/**
* @dev Moves `amount` tokens from `from` to `to` using the
* allowance mechanism. `amount` is then deducted from the caller's
* allowance.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* Emits a {Transfer} event.
*/
function transferFrom(address from, address to, uint256 amount) external returns (bool);
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.3) (token/ERC20/utils/SafeERC20.sol)
pragma solidity ^0.8.0;
import "../IERC20Upgradeable.sol";
import "../extensions/IERC20PermitUpgradeable.sol";
import "../../../utils/AddressUpgradeable.sol";
/**
* @title SafeERC20
* @dev Wrappers around ERC20 operations that throw on failure (when the token
* contract returns false). Tokens that return no value (and instead revert or
* throw on failure) are also supported, non-reverting calls are assumed to be
* successful.
* To use this library you can add a `using SafeERC20 for IERC20;` statement to your contract,
* which allows you to call the safe operations as `token.safeTransfer(...)`, etc.
*/
library SafeERC20Upgradeable {
using AddressUpgradeable for address;
/**
* @dev Transfer `value` amount of `token` from the calling contract to `to`. If `token` returns no value,
* non-reverting calls are assumed to be successful.
*/
function safeTransfer(IERC20Upgradeable token, address to, uint256 value) internal {
_callOptionalReturn(token, abi.encodeWithSelector(token.transfer.selector, to, value));
}
/**
* @dev Transfer `value` amount of `token` from `from` to `to`, spending the approval given by `from` to the
* calling contract. If `token` returns no value, non-reverting calls are assumed to be successful.
*/
function safeTransferFrom(IERC20Upgradeable token, address from, address to, uint256 value) internal {
_callOptionalReturn(token, abi.encodeWithSelector(token.transferFrom.selector, from, to, value));
}
/**
* @dev Deprecated. This function has issues similar to the ones found in
* {IERC20-approve}, and its usage is discouraged.
*
* Whenever possible, use {safeIncreaseAllowance} and
* {safeDecreaseAllowance} instead.
*/
function safeApprove(IERC20Upgradeable token, address spender, uint256 value) internal {
// safeApprove should only be called when setting an initial allowance,
// or when resetting it to zero. To increase and decrease it, use
// 'safeIncreaseAllowance' and 'safeDecreaseAllowance'
require(
(value == 0) || (token.allowance(address(this), spender) == 0),
"SafeERC20: approve from non-zero to non-zero allowance"
);
_callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, value));
}
/**
* @dev Increase the calling contract's allowance toward `spender` by `value`. If `token` returns no value,
* non-reverting calls are assumed to be successful.
*/
function safeIncreaseAllowance(IERC20Upgradeable token, address spender, uint256 value) internal {
uint256 oldAllowance = token.allowance(address(this), spender);
_callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, oldAllowance + value));
}
/**
* @dev Decrease the calling contract's allowance toward `spender` by `value`. If `token` returns no value,
* non-reverting calls are assumed to be successful.
*/
function safeDecreaseAllowance(IERC20Upgradeable token, address spender, uint256 value) internal {
unchecked {
uint256 oldAllowance = token.allowance(address(this), spender);
require(oldAllowance >= value, "SafeERC20: decreased allowance below zero");
_callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, oldAllowance - value));
}
}
/**
* @dev Set the calling contract's allowance toward `spender` to `value`. If `token` returns no value,
* non-reverting calls are assumed to be successful. Meant to be used with tokens that require the approval
* to be set to zero before setting it to a non-zero value, such as USDT.
*/
function forceApprove(IERC20Upgradeable token, address spender, uint256 value) internal {
bytes memory approvalCall = abi.encodeWithSelector(token.approve.selector, spender, value);
if (!_callOptionalReturnBool(token, approvalCall)) {
_callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, 0));
_callOptionalReturn(token, approvalCall);
}
}
/**
* @dev Use a ERC-2612 signature to set the `owner` approval toward `spender` on `token`.
* Revert on invalid signature.
*/
function safePermit(
IERC20PermitUpgradeable token,
address owner,
address spender,
uint256 value,
uint256 deadline,
uint8 v,
bytes32 r,
bytes32 s
) internal {
uint256 nonceBefore = token.nonces(owner);
token.permit(owner, spender, value, deadline, v, r, s);
uint256 nonceAfter = token.nonces(owner);
require(nonceAfter == nonceBefore + 1, "SafeERC20: permit did not succeed");
}
/**
* @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement
* on the return value: the return value is optional (but if data is returned, it must not be false).
* @param token The token targeted by the call.
* @param data The call data (encoded using abi.encode or one of its variants).
*/
function _callOptionalReturn(IERC20Upgradeable token, bytes memory data) private {
// We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since
// we're implementing it ourselves. We use {Address-functionCall} to perform this call, which verifies that
// the target address contains contract code and also asserts for success in the low-level call.
bytes memory returndata = address(token).functionCall(data, "SafeERC20: low-level call failed");
require(returndata.length == 0 || abi.decode(returndata, (bool)), "SafeERC20: ERC20 operation did not succeed");
}
/**
* @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement
* on the return value: the return value is optional (but if data is returned, it must not be false).
* @param token The token targeted by the call.
* @param data The call data (encoded using abi.encode or one of its variants).
*
* This is a variant of {_callOptionalReturn} that silents catches all reverts and returns a bool instead.
*/
function _callOptionalReturnBool(IERC20Upgradeable token, bytes memory data) private returns (bool) {
// We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since
// we're implementing it ourselves. We cannot use {Address-functionCall} here since this should return false
// and not revert is the subcall reverts.
(bool success, bytes memory returndata) = address(token).call(data);
return
success && (returndata.length == 0 || abi.decode(returndata, (bool))) && AddressUpgradeable.isContract(address(token));
}
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (utils/Address.sol)
pragma solidity ^0.8.1;
/**
* @dev Collection of functions related to the address type
*/
library AddressUpgradeable {
/**
* @dev Returns true if `account` is a contract.
*
* [IMPORTANT]
* ====
* It is unsafe to assume that an address for which this function returns
* false is an externally-owned account (EOA) and not a contract.
*
* Among others, `isContract` will return false for the following
* types of addresses:
*
* - an externally-owned account
* - a contract in construction
* - an address where a contract will be created
* - an address where a contract lived, but was destroyed
*
* Furthermore, `isContract` will also return true if the target contract within
* the same transaction is already scheduled for destruction by `SELFDESTRUCT`,
* which only has an effect at the end of a transaction.
* ====
*
* [IMPORTANT]
* ====
* You shouldn't rely on `isContract` to protect against flash loan attacks!
*
* Preventing calls from contracts is highly discouraged. It breaks composability, breaks support for smart wallets
* like Gnosis Safe, and does not provide security since it can be circumvented by calling from a contract
* constructor.
* ====
*/
function isContract(address account) internal view returns (bool) {
// This method relies on extcodesize/address.code.length, which returns 0
// for contracts in construction, since the code is only stored at the end
// of the constructor execution.
return account.code.length > 0;
}
/**
* @dev Replacement for Solidity's `transfer`: sends `amount` wei to
* `recipient`, forwarding all available gas and reverting on errors.
*
* https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost
* of certain opcodes, possibly making contracts go over the 2300 gas limit
* imposed by `transfer`, making them unable to receive funds via
* `transfer`. {sendValue} removes this limitation.
*
* https://consensys.net/diligence/blog/2019/09/stop-using-soliditys-transfer-now/[Learn more].
*
* IMPORTANT: because control is transferred to `recipient`, care must be
* taken to not create reentrancy vulnerabilities. Consider using
* {ReentrancyGuard} or the
* https://solidity.readthedocs.io/en/v0.8.0/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern].
*/
function sendValue(address payable recipient, uint256 amount) internal {
require(address(this).balance >= amount, "Address: insufficient balance");
(bool success, ) = recipient.call{value: amount}("");
require(success, "Address: unable to send value, recipient may have reverted");
}
/**
* @dev Performs a Solidity function call using a low level `call`. A
* plain `call` is an unsafe replacement for a function call: use this
* function instead.
*
* If `target` reverts with a revert reason, it is bubbled up by this
* function (like regular Solidity function calls).
*
* Returns the raw returned data. To convert to the expected return value,
* use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`].
*
* Requirements:
*
* - `target` must be a contract.
* - calling `target` with `data` must not revert.
*
* _Available since v3.1._
*/
function functionCall(address target, bytes memory data) internal returns (bytes memory) {
return functionCallWithValue(target, data, 0, "Address: low-level call failed");
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], but with
* `errorMessage` as a fallback revert reason when `target` reverts.
*
* _Available since v3.1._
*/
function functionCall(
address target,
bytes memory data,
string memory errorMessage
) internal returns (bytes memory) {
return functionCallWithValue(target, data, 0, errorMessage);
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
* but also transferring `value` wei to `target`.
*
* Requirements:
*
* - the calling contract must have an ETH balance of at least `value`.
* - the called Solidity function must be `payable`.
*
* _Available since v3.1._
*/
function functionCallWithValue(address target, bytes memory data, uint256 value) internal returns (bytes memory) {
return functionCallWithValue(target, data, value, "Address: low-level call with value failed");
}
/**
* @dev Same as {xref-Address-functionCallWithValue-address-bytes-uint256-}[`functionCallWithValue`], but
* with `errorMessage` as a fallback revert reason when `target` reverts.
*
* _Available since v3.1._
*/
function functionCallWithValue(
address target,
bytes memory data,
uint256 value,
string memory errorMessage
) internal returns (bytes memory) {
require(address(this).balance >= value, "Address: insufficient balance for call");
(bool success, bytes memory returndata) = target.call{value: value}(data);
return verifyCallResultFromTarget(target, success, returndata, errorMessage);
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
* but performing a static call.
*
* _Available since v3.3._
*/
function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) {
return functionStaticCall(target, data, "Address: low-level static call failed");
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],
* but performing a static call.
*
* _Available since v3.3._
*/
function functionStaticCall(
address target,
bytes memory data,
string memory errorMessage
) internal view returns (bytes memory) {
(bool success, bytes memory returndata) = target.staticcall(data);
return verifyCallResultFromTarget(target, success, returndata, errorMessage);
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
* but performing a delegate call.
*
* _Available since v3.4._
*/
function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) {
return functionDelegateCall(target, data, "Address: low-level delegate call failed");
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],
* but performing a delegate call.
*
* _Available since v3.4._
*/
function functionDelegateCall(
address target,
bytes memory data,
string memory errorMessage
) internal returns (bytes memory) {
(bool success, bytes memory returndata) = target.delegatecall(data);
return verifyCallResultFromTarget(target, success, returndata, errorMessage);
}
/**
* @dev Tool to verify that a low level call to smart-contract was successful, and revert (either by bubbling
* the revert reason or using the provided one) in case of unsuccessful call or if target was not a contract.
*
* _Available since v4.8._
*/
function verifyCallResultFromTarget(
address target,
bool success,
bytes memory returndata,
string memory errorMessage
) internal view returns (bytes memory) {
if (success) {
if (returndata.length == 0) {
// only check isContract if the call was successful and the return data is empty
// otherwise we already know that it was a contract
require(isContract(target), "Address: call to non-contract");
}
return returndata;
} else {
_revert(returndata, errorMessage);
}
}
/**
* @dev Tool to verify that a low level call was successful, and revert if it wasn't, either by bubbling the
* revert reason or using the provided one.
*
* _Available since v4.3._
*/
function verifyCallResult(
bool success,
bytes memory returndata,
string memory errorMessage
) internal pure returns (bytes memory) {
if (success) {
return returndata;
} else {
_revert(returndata, errorMessage);
}
}
function _revert(bytes memory returndata, string memory errorMessage) private pure {
// Look for revert reason and bubble it up if present
if (returndata.length > 0) {
// The easiest way to bubble the revert reason is using memory via assembly
/// @solidity memory-safe-assembly
assembly {
let returndata_size := mload(returndata)
revert(add(32, returndata), returndata_size)
}
} else {
revert(errorMessage);
}
}
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.4) (utils/Context.sol)
pragma solidity ^0.8.0;
import {Initializable} from "../proxy/utils/Initializable.sol";
/**
* @dev Provides information about the current execution context, including the
* sender of the transaction and its data. While these are generally available
* via msg.sender and msg.data, they should not be accessed in such a direct
* manner, since when dealing with meta-transactions the account sending and
* paying for execution may not be the actual sender (as far as an application
* is concerned).
*
* This contract is only required for intermediate, library-like contracts.
*/
abstract contract ContextUpgradeable is Initializable {
function __Context_init() internal onlyInitializing {
}
function __Context_init_unchained() internal onlyInitializing {
}
function _msgSender() internal view virtual returns (address) {
return msg.sender;
}
function _msgData() internal view virtual returns (bytes calldata) {
return msg.data;
}
function _contextSuffixLength() internal view virtual returns (uint256) {
return 0;
}
/**
* @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[50] private __gap;
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (access/IAccessControl.sol)
pragma solidity ^0.8.0;
/**
* @dev External interface of AccessControl declared to support ERC165 detection.
*/
interface IAccessControl {
/**
* @dev Emitted when `newAdminRole` is set as ``role``'s admin role, replacing `previousAdminRole`
*
* `DEFAULT_ADMIN_ROLE` is the starting admin for all roles, despite
* {RoleAdminChanged} not being emitted signaling this.
*
* _Available since v3.1._
*/
event RoleAdminChanged(bytes32 indexed role, bytes32 indexed previousAdminRole, bytes32 indexed newAdminRole);
/**
* @dev Emitted when `account` is granted `role`.
*
* `sender` is the account that originated the contract call, an admin role
* bearer except when using {AccessControl-_setupRole}.
*/
event RoleGranted(bytes32 indexed role, address indexed account, address indexed sender);
/**
* @dev Emitted when `account` is revoked `role`.
*
* `sender` is the account that originated the contract call:
* - if using `revokeRole`, it is the admin role bearer
* - if using `renounceRole`, it is the role bearer (i.e. `account`)
*/
event RoleRevoked(bytes32 indexed role, address indexed account, address indexed sender);
/**
* @dev Returns `true` if `account` has been granted `role`.
*/
function hasRole(bytes32 role, address account) external view returns (bool);
/**
* @dev Returns the admin role that controls `role`. See {grantRole} and
* {revokeRole}.
*
* To change a role's admin, use {AccessControl-_setRoleAdmin}.
*/
function getRoleAdmin(bytes32 role) external view returns (bytes32);
/**
* @dev Grants `role` to `account`.
*
* If `account` had not been already granted `role`, emits a {RoleGranted}
* event.
*
* Requirements:
*
* - the caller must have ``role``'s admin role.
*/
function grantRole(bytes32 role, address account) external;
/**
* @dev Revokes `role` from `account`.
*
* If `account` had been granted `role`, emits a {RoleRevoked} event.
*
* Requirements:
*
* - the caller must have ``role``'s admin role.
*/
function revokeRole(bytes32 role, address account) external;
/**
* @dev Revokes `role` from the calling account.
*
* Roles are often managed via {grantRole} and {revokeRole}: this function's
* purpose is to provide a mechanism for accounts to lose their privileges
* if they are compromised (such as when a trusted device is misplaced).
*
* If the calling account had been granted `role`, emits a {RoleRevoked}
* event.
*
* Requirements:
*
* - the caller must be `account`.
*/
function renounceRole(bytes32 role, address account) external;
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (token/ERC20/extensions/IERC20Metadata.sol)
pragma solidity ^0.8.0;
import "../IERC20.sol";
/**
* @dev Interface for the optional metadata functions from the ERC20 standard.
*
* _Available since v4.1._
*/
interface IERC20Metadata is IERC20 {
/**
* @dev Returns the name of the token.
*/
function name() external view returns (string memory);
/**
* @dev Returns the symbol of the token.
*/
function symbol() external view returns (string memory);
/**
* @dev Returns the decimals places of the token.
*/
function decimals() external view returns (uint8);
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (token/ERC20/IERC20.sol)
pragma solidity ^0.8.0;
/**
* @dev Interface of the ERC20 standard as defined in the EIP.
*/
interface IERC20 {
/**
* @dev Emitted when `value` tokens are moved from one account (`from`) to
* another (`to`).
*
* Note that `value` may be zero.
*/
event Transfer(address indexed from, address indexed to, uint256 value);
/**
* @dev Emitted when the allowance of a `spender` for an `owner` is set by
* a call to {approve}. `value` is the new allowance.
*/
event Approval(address indexed owner, address indexed spender, uint256 value);
/**
* @dev Returns the amount of tokens in existence.
*/
function totalSupply() external view returns (uint256);
/**
* @dev Returns the amount of tokens owned by `account`.
*/
function balanceOf(address account) external view returns (uint256);
/**
* @dev Moves `amount` tokens from the caller's account to `to`.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* Emits a {Transfer} event.
*/
function transfer(address to, uint256 amount) external returns (bool);
/**
* @dev Returns the remaining number of tokens that `spender` will be
* allowed to spend on behalf of `owner` through {transferFrom}. This is
* zero by default.
*
* This value changes when {approve} or {transferFrom} are called.
*/
function allowance(address owner, address spender) external view returns (uint256);
/**
* @dev Sets `amount` as the allowance of `spender` over the caller's tokens.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* IMPORTANT: Beware that changing an allowance with this method brings the risk
* that someone may use both the old and the new allowance by unfortunate
* transaction ordering. One possible solution to mitigate this race
* condition is to first reduce the spender's allowance to 0 and set the
* desired value afterwards:
* https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
*
* Emits an {Approval} event.
*/
function approve(address spender, uint256 amount) external returns (bool);
/**
* @dev Moves `amount` tokens from `from` to `to` using the
* allowance mechanism. `amount` is then deducted from the caller's
* allowance.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* Emits a {Transfer} event.
*/
function transferFrom(address from, address to, uint256 amount) external returns (bool);
}// SPDX-License-Identifier: BSD-3-Clause
pragma solidity 0.8.25;
import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
import "@openzeppelin/contracts-upgradeable/access/Ownable2StepUpgradeable.sol";
import "./IAccessControlManagerV8.sol";
/**
* @title AccessControlledV8
* @author Venus
* @notice This contract is helper between access control manager and actual contract. This contract further inherited by other contract (using solidity 0.8.13)
* to integrate access controlled mechanism. It provides initialise methods and verifying access methods.
*/
abstract contract AccessControlledV8 is Initializable, Ownable2StepUpgradeable {
/// @notice Access control manager contract
IAccessControlManagerV8 private _accessControlManager;
/**
* @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[49] private __gap;
/// @notice Emitted when access control manager contract address is changed
event NewAccessControlManager(address oldAccessControlManager, address newAccessControlManager);
/// @notice Thrown when the action is prohibited by AccessControlManager
error Unauthorized(address sender, address calledContract, string methodSignature);
function __AccessControlled_init(address accessControlManager_) internal onlyInitializing {
__Ownable2Step_init();
__AccessControlled_init_unchained(accessControlManager_);
}
function __AccessControlled_init_unchained(address accessControlManager_) internal onlyInitializing {
_setAccessControlManager(accessControlManager_);
}
/**
* @notice Sets the address of AccessControlManager
* @dev Admin function to set address of AccessControlManager
* @param accessControlManager_ The new address of the AccessControlManager
* @custom:event Emits NewAccessControlManager event
* @custom:access Only Governance
*/
function setAccessControlManager(address accessControlManager_) external onlyOwner {
_setAccessControlManager(accessControlManager_);
}
/**
* @notice Returns the address of the access control manager contract
*/
function accessControlManager() external view returns (IAccessControlManagerV8) {
return _accessControlManager;
}
/**
* @dev Internal function to set address of AccessControlManager
* @param accessControlManager_ The new address of the AccessControlManager
*/
function _setAccessControlManager(address accessControlManager_) internal {
require(address(accessControlManager_) != address(0), "invalid acess control manager address");
address oldAccessControlManager = address(_accessControlManager);
_accessControlManager = IAccessControlManagerV8(accessControlManager_);
emit NewAccessControlManager(oldAccessControlManager, accessControlManager_);
}
/**
* @notice Reverts if the call is not allowed by AccessControlManager
* @param signature Method signature
*/
function _checkAccessAllowed(string memory signature) internal view {
bool isAllowedToCall = _accessControlManager.isAllowedToCall(msg.sender, signature);
if (!isAllowedToCall) {
revert Unauthorized(msg.sender, address(this), signature);
}
}
}// SPDX-License-Identifier: BSD-3-Clause
pragma solidity ^0.8.25;
import "@openzeppelin/contracts/access/IAccessControl.sol";
/**
* @title IAccessControlManagerV8
* @author Venus
* @notice Interface implemented by the `AccessControlManagerV8` contract.
*/
interface IAccessControlManagerV8 is IAccessControl {
function giveCallPermission(address contractAddress, string calldata functionSig, address accountToPermit) external;
function revokeCallPermission(
address contractAddress,
string calldata functionSig,
address accountToRevoke
) external;
function isAllowedToCall(address account, string calldata functionSig) external view returns (bool);
function hasPermission(
address account,
address contractAddress,
string calldata functionSig
) external view returns (bool);
}// SPDX-License-Identifier: BSD-3-Clause
pragma solidity ^0.8.25;
interface OracleInterface {
function getPrice(address asset) external view returns (uint256);
}
interface ResilientOracleInterface is OracleInterface {
function updatePrice(address vToken) external;
function updateAssetPrice(address asset) external;
function getUnderlyingPrice(address vToken) external view returns (uint256);
}
interface TwapInterface is OracleInterface {
function updateTwap(address asset) external returns (uint256);
}
interface BoundValidatorInterface {
function validatePriceWithAnchorPrice(
address asset,
uint256 reporterPrice,
uint256 anchorPrice
) external view returns (bool);
}// 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
}
function updateAssetsState(
address comptroller,
address asset,
IncomeType incomeType
) external;
}// SPDX-License-Identifier: BSD-3-Clause pragma solidity ^0.8.25; /// @dev Base unit for computations, usually used in scaling (multiplications, divisions) uint256 constant EXP_SCALE = 1e18; /// @dev A unit (literal one) in EXP_SCALE, usually used in additions/subtractions uint256 constant MANTISSA_ONE = EXP_SCALE; /// @dev The approximate number of seconds per year uint256 constant SECONDS_PER_YEAR = 31_536_000;
// SPDX-License-Identifier: BSD-3-Clause
pragma solidity 0.8.25;
import { SECONDS_PER_YEAR } from "./constants.sol";
abstract contract TimeManagerV8 {
/// @notice Stores blocksPerYear if isTimeBased is true else secondsPerYear is stored
/// @custom:oz-upgrades-unsafe-allow state-variable-immutable
uint256 public immutable blocksOrSecondsPerYear;
/// @notice Acknowledges if a contract is time based or not
/// @custom:oz-upgrades-unsafe-allow state-variable-immutable
bool public immutable isTimeBased;
/// @notice Stores the current block timestamp or block number depending on isTimeBased
/// @custom:oz-upgrades-unsafe-allow state-variable-immutable
function() view returns (uint256) private immutable _getCurrentSlot;
/**
* @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;
/// @notice Thrown when blocks per year is invalid
error InvalidBlocksPerYear();
/// @notice Thrown when time based but blocks per year is provided
error InvalidTimeBasedConfiguration();
/**
* @param timeBased_ A boolean indicating whether the contract is based on time or block
* If timeBased is true than blocksPerYear_ param is ignored as blocksOrSecondsPerYear is set to SECONDS_PER_YEAR
* @param blocksPerYear_ The number of blocks per year
* @custom:error InvalidBlocksPerYear is thrown if blocksPerYear entered is zero and timeBased is false
* @custom:error InvalidTimeBasedConfiguration is thrown if blocksPerYear entered is non zero and timeBased is true
* @custom:oz-upgrades-unsafe-allow constructor
*/
constructor(bool timeBased_, uint256 blocksPerYear_) {
if (!timeBased_ && blocksPerYear_ == 0) {
revert InvalidBlocksPerYear();
}
if (timeBased_ && blocksPerYear_ != 0) {
revert InvalidTimeBasedConfiguration();
}
isTimeBased = timeBased_;
blocksOrSecondsPerYear = timeBased_ ? SECONDS_PER_YEAR : blocksPerYear_;
_getCurrentSlot = timeBased_ ? _getBlockTimestamp : _getBlockNumber;
}
/**
* @dev Function to simply retrieve block number or block timestamp
* @return Current block number or block timestamp
*/
function getBlockNumberOrTimestamp() public view virtual returns (uint256) {
return _getCurrentSlot();
}
/**
* @dev Returns the current timestamp in seconds
* @return The current timestamp
*/
function _getBlockTimestamp() private view returns (uint256) {
return block.timestamp;
}
/**
* @dev Returns the current block number
* @return The current block number
*/
function _getBlockNumber() private view returns (uint256) {
return block.number;
}
}// SPDX-License-Identifier: BSD-3-Clause
pragma solidity 0.8.25;
/// @notice Thrown if the supplied address is a zero address where it is not allowed
error ZeroAddressNotAllowed();
/// @notice Thrown if the supplied value is 0 where it is not allowed
error ZeroValueNotAllowed();
/// @notice Checks if the provided address is nonzero, reverts otherwise
/// @param address_ Address to check
/// @custom:error ZeroAddressNotAllowed is thrown if the provided address is a zero address
function ensureNonzeroAddress(address address_) pure {
if (address_ == address(0)) {
revert ZeroAddressNotAllowed();
}
}
/// @notice Checks if the provided value is nonzero, reverts otherwise
/// @param value_ Value to check
/// @custom:error ZeroValueNotAllowed is thrown if the provided value is 0
function ensureNonzeroValue(uint256 value_) pure {
if (value_ == 0) {
revert ZeroValueNotAllowed();
}
}// SPDX-License-Identifier: BSD-3-Clause
pragma solidity ^0.8.25;
import { PrimeStorageV1 } from "../PrimeStorage.sol";
/**
* @title IPrime
* @author Venus
* @notice Interface for Prime Token
*/
interface IPrime {
struct APRInfo {
// supply APR of the user in BPS
uint256 supplyAPR;
// borrow APR of the user in BPS
uint256 borrowAPR;
// total score of the market
uint256 totalScore;
// score of the user
uint256 userScore;
// capped XVS balance of the user
uint256 xvsBalanceForScore;
// capital of the user
uint256 capital;
// capped supply of the user
uint256 cappedSupply;
// capped borrow of the user
uint256 cappedBorrow;
// capped supply of user in USD
uint256 supplyCapUSD;
// capped borrow of user in USD
uint256 borrowCapUSD;
}
struct Capital {
// capital of the user
uint256 capital;
// capped supply of the user
uint256 cappedSupply;
// capped borrow of the user
uint256 cappedBorrow;
// capped supply of user in USD
uint256 supplyCapUSD;
// capped borrow of user in USD
uint256 borrowCapUSD;
}
/**
* @notice Returns boosted pending interest accrued for a user for all markets
* @param user the account for which to get the accrued interests
* @return pendingRewards the number of underlying tokens accrued by the user for all markets
*/
function getPendingRewards(address user) external returns (PrimeStorageV1.PendingReward[] memory pendingRewards);
/**
* @notice Update total score of multiple users and market
* @param users accounts for which we need to update score
*/
function updateScores(address[] memory users) external;
/**
* @notice Update value of alpha
* @param _alphaNumerator numerator of alpha. If alpha is 0.5 then numerator is 1
* @param _alphaDenominator denominator of alpha. If alpha is 0.5 then denominator is 2
*/
function updateAlpha(uint128 _alphaNumerator, uint128 _alphaDenominator) external;
/**
* @notice Update multipliers for a market
* @param market address of the market vToken
* @param supplyMultiplier new supply multiplier for the market, scaled by 1e18
* @param borrowMultiplier new borrow multiplier for the market, scaled by 1e18
*/
function updateMultipliers(address market, uint256 supplyMultiplier, uint256 borrowMultiplier) external;
/**
* @notice Add a market to prime program
* @param comptroller address of the comptroller
* @param market address of the market vToken
* @param supplyMultiplier the multiplier for supply cap. It should be converted to 1e18
* @param borrowMultiplier the multiplier for borrow cap. It should be converted to 1e18
*/
function addMarket(
address comptroller,
address market,
uint256 supplyMultiplier,
uint256 borrowMultiplier
) external;
/**
* @notice Set limits for total tokens that can be minted
* @param _irrevocableLimit total number of irrevocable tokens that can be minted
* @param _revocableLimit total number of revocable tokens that can be minted
*/
function setLimit(uint256 _irrevocableLimit, uint256 _revocableLimit) external;
/**
* @notice Directly issue prime tokens to users
* @param isIrrevocable are the tokens being issued
* @param users list of address to issue tokens to
*/
function issue(bool isIrrevocable, address[] calldata users) external;
/**
* @notice Executed by XVSVault whenever user's XVSVault balance changes
* @param user the account address whose balance was updated
*/
function xvsUpdated(address user) external;
/**
* @notice accrues interest and updates score for an user for a specific market
* @param user the account address for which to accrue interest and update score
* @param market the market for which to accrue interest and update score
*/
function accrueInterestAndUpdateScore(address user, address market) external;
/**
* @notice For claiming prime token when staking period is completed
*/
function claim() external;
/**
* @notice For burning any prime token
* @param user the account address for which the prime token will be burned
*/
function burn(address user) external;
/**
* @notice To pause or unpause claiming of interest
*/
function togglePause() external;
/**
* @notice For user to claim boosted yield
* @param vToken the market for which claim the accrued interest
* @return amount the amount of tokens transferred to the user
*/
function claimInterest(address vToken) external returns (uint256);
/**
* @notice For user to claim boosted yield
* @param vToken the market for which claim the accrued interest
* @param user the user for which to claim the accrued interest
* @return amount the amount of tokens transferred to the user
*/
function claimInterest(address vToken, address user) external returns (uint256);
/**
* @notice Distributes income from market since last distribution
* @param vToken the market for which to distribute the income
*/
function accrueInterest(address vToken) external;
/**
* @notice Returns boosted interest accrued for a user
* @param vToken the market for which to fetch the accrued interest
* @param user the account for which to get the accrued interest
* @return interestAccrued the number of underlying tokens accrued by the user since the last accrual
*/
function getInterestAccrued(address vToken, address user) external returns (uint256);
/**
* @notice Retrieves an array of all available markets
* @return an array of addresses representing all available markets
*/
function getAllMarkets() external view returns (address[] memory);
/**
* @notice fetch the numbers of seconds remaining for staking period to complete
* @param user the account address for which we are checking the remaining time
* @return timeRemaining the number of seconds the user needs to wait to claim prime token
*/
function claimTimeRemaining(address user) external view returns (uint256);
/**
* @notice Returns supply and borrow APR for user for a given market
* @param market the market for which to fetch the APR
* @param user the account for which to get the APR
* @return aprInfo APR information for the user for the given market
*/
function calculateAPR(address market, address user) external view returns (APRInfo memory aprInfo);
/**
* @notice Returns supply and borrow APR for estimated supply, borrow and XVS staked
* @param market the market for which to fetch the APR
* @param user the account for which to get the APR
* @param borrow hypothetical borrow amount
* @param supply hypothetical supply amount
* @param xvsStaked hypothetical staked XVS amount
* @return aprInfo APR information for the user for the given market
*/
function estimateAPR(
address market,
address user,
uint256 borrow,
uint256 supply,
uint256 xvsStaked
) external view returns (APRInfo memory aprInfo);
/**
* @notice the total income that's going to be distributed in a year to prime token holders
* @param vToken the market for which to fetch the total income that's going to distributed in a year
* @return amount the total income
*/
function incomeDistributionYearly(address vToken) external view returns (uint256 amount);
/**
* @notice Returns if user is a prime holder
* @return isPrimeHolder true if user is a prime holder
*/
function isUserPrimeHolder(address user) external view returns (bool);
/**
* @notice Set the limit for the loops can iterate to avoid the DOS
* @param loopsLimit Number of loops limit
*/
function setMaxLoopsLimit(uint256 loopsLimit) external;
/**
* @notice Update staked at timestamp for multiple users
* @param users accounts for which we need to update staked at timestamp
* @param timestamps new staked at timestamp for the users
*/
function setStakedAt(address[] calldata users, uint256[] calldata timestamps) external;
}// SPDX-License-Identifier: BSD-3-Clause
pragma solidity 0.8.25;
import { ResilientOracleInterface } from "@venusprotocol/oracle/contracts/interfaces/OracleInterface.sol";
/**
* @title PrimeStorageV1
* @author Venus
* @notice Storage for Prime Token
*/
contract PrimeStorageV1 {
struct Token {
bool exists;
bool isIrrevocable;
}
struct Market {
uint256 supplyMultiplier;
uint256 borrowMultiplier;
uint256 rewardIndex;
uint256 sumOfMembersScore;
bool exists;
}
struct Interest {
uint256 accrued;
uint256 score;
uint256 rewardIndex;
}
struct PendingReward {
address vToken;
address rewardToken;
uint256 amount;
}
/// @notice Base unit for computations, usually used in scaling (multiplications, divisions)
uint256 internal constant EXP_SCALE = 1e18;
/// @notice maximum BPS = 100%
uint256 internal constant MAXIMUM_BPS = 1e4;
/// @notice Mapping to get prime token's metadata
mapping(address => Token) public tokens;
/// @notice Tracks total irrevocable tokens minted
uint256 public totalIrrevocable;
/// @notice Tracks total revocable tokens minted
uint256 public totalRevocable;
/// @notice Indicates maximum revocable tokens that can be minted
uint256 public revocableLimit;
/// @notice Indicates maximum irrevocable tokens that can be minted
uint256 public irrevocableLimit;
/// @notice Tracks when prime token eligible users started staking for claiming prime token
mapping(address => uint256) public stakedAt;
/// @notice vToken to market configuration
mapping(address => Market) public markets;
/// @notice vToken to user to user index
mapping(address => mapping(address => Interest)) public interests;
/// @notice A list of boosted markets
address[] internal _allMarkets;
/// @notice numerator of alpha. Ex: if alpha is 0.5 then this will be 1
uint128 public alphaNumerator;
/// @notice denominator of alpha. Ex: if alpha is 0.5 then this will be 2
uint128 public alphaDenominator;
/// @notice address of XVS vault
address public xvsVault;
/// @notice address of XVS vault reward token
address public xvsVaultRewardToken;
/// @notice address of XVS vault pool id
uint256 public xvsVaultPoolId;
/// @notice mapping to check if a account's score was updated in the round
mapping(uint256 => mapping(address => bool)) public isScoreUpdated;
/// @notice unique id for next round
uint256 public nextScoreUpdateRoundId;
/// @notice total number of accounts whose score needs to be updated
uint256 public totalScoreUpdatesRequired;
/// @notice total number of accounts whose score is yet to be updated
uint256 public pendingScoreUpdates;
/// @notice mapping used to find if an asset is part of prime markets
mapping(address => address) public vTokenForAsset;
/// @notice Address of core pool comptroller contract
address internal corePoolComptroller;
/// @notice unreleased income from PLP that's already distributed to prime holders
/// @dev mapping of asset address => amount
mapping(address => uint256) public unreleasedPLPIncome;
/// @notice The address of PLP contract
address public primeLiquidityProvider;
/// @notice The address of ResilientOracle contract
ResilientOracleInterface public oracle;
/// @notice The address of PoolRegistry contract
address public poolRegistry;
/// @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.
uint256[26] private __gap;
}// SPDX-License-Identifier: BSD-3-Clause
pragma solidity 0.8.25;
import { Ownable2StepUpgradeable } from "@openzeppelin/contracts-upgradeable/access/Ownable2StepUpgradeable.sol";
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 { ComptrollerInterface, 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";
/**
* @title Comptroller
* @author Venus
* @notice The Comptroller is designed to provide checks for all minting, redeeming, transferring, borrowing, lending, repaying, liquidating,
* and seizing done by the `vToken` contract. Each pool has one `Comptroller` checking these interactions across markets. When a user interacts
* with a given market by one of these main actions, a call is made to a corresponding hook in the associated `Comptroller`, which either allows
* or reverts the transaction. These hooks also update supply and borrow rewards as they are called. The comptroller holds the logic for assessing
* liquidity snapshots of an account via the collateral factor and liquidation threshold. This check determines the collateral needed for a borrow,
* as well as how much of a borrow may be liquidated. A user may borrow a portion of their collateral with the maximum amount determined by the
* markets collateral factor. However, if their borrowed amount exceeds an amount calculated using the market’s corresponding liquidation threshold,
* the borrow is eligible for liquidation.
*
* The `Comptroller` also includes two functions `liquidateAccount()` and `healAccount()`, which are meant to handle accounts that do not exceed
* the `minLiquidatableCollateral` for the `Comptroller`:
*
* - `healAccount()`: This function is called to seize all of a given user’s collateral, requiring the `msg.sender` repay a certain percentage
* of the debt calculated by `collateral/(borrows*liquidationIncentive)`. The function can only be called if the calculated percentage does not exceed
* 100%, because otherwise no `badDebt` would be created and `liquidateAccount()` should be used instead. The difference in the actual amount of debt
* and debt paid off is recorded as `badDebt` for each market, which can then be auctioned off for the risk reserves of the associated pool.
* - `liquidateAccount()`: This function can only be called if the collateral seized will cover all borrows of an account, as well as the liquidation
* incentive. Otherwise, the pool will incur bad debt, in which case the function `healAccount()` should be used instead. This function skips the logic
* verifying that the repay amount does not exceed the close factor.
*/
contract Comptroller is
Ownable2StepUpgradeable,
AccessControlledV8,
ComptrollerStorage,
ComptrollerInterface,
ExponentialNoError,
MaxLoopsLimitHelper
{
// PoolRegistry, immutable to save on gas
/// @custom:oz-upgrades-unsafe-allow state-variable-immutable
address public immutable poolRegistry;
/// @notice Emitted when an account enters a market
event MarketEntered(VToken indexed vToken, address indexed account);
/// @notice Emitted when an account exits a market
event MarketExited(VToken indexed vToken, address indexed account);
/// @notice Emitted when close factor is changed by admin
event NewCloseFactor(uint256 oldCloseFactorMantissa, uint256 newCloseFactorMantissa);
/// @notice Emitted when a collateral factor is changed by admin
event NewCollateralFactor(VToken vToken, uint256 oldCollateralFactorMantissa, uint256 newCollateralFactorMantissa);
/// @notice Emitted when liquidation threshold is changed by admin
event NewLiquidationThreshold(
VToken vToken,
uint256 oldLiquidationThresholdMantissa,
uint256 newLiquidationThresholdMantissa
);
/// @notice Emitted when liquidation incentive is changed by admin
event NewLiquidationIncentive(uint256 oldLiquidationIncentiveMantissa, uint256 newLiquidationIncentiveMantissa);
/// @notice Emitted when price oracle is changed
event NewPriceOracle(ResilientOracleInterface oldPriceOracle, ResilientOracleInterface newPriceOracle);
/// @notice Emitted when an action is paused on a market
event ActionPausedMarket(VToken vToken, Action action, bool pauseState);
/// @notice Emitted when borrow cap for a vToken is changed
event NewBorrowCap(VToken indexed vToken, uint256 newBorrowCap);
/// @notice Emitted when the collateral threshold (in USD) for non-batch liquidations is changed
event NewMinLiquidatableCollateral(uint256 oldMinLiquidatableCollateral, uint256 newMinLiquidatableCollateral);
/// @notice Emitted when supply cap for a vToken is changed
event NewSupplyCap(VToken indexed vToken, uint256 newSupplyCap);
/// @notice Emitted when a rewards distributor is added
event NewRewardsDistributor(address indexed rewardsDistributor, address indexed rewardToken);
/// @notice Emitted when a market is supported
event MarketSupported(VToken vToken);
/// @notice Emitted when prime token contract address is changed
event NewPrimeToken(IPrime oldPrimeToken, IPrime newPrimeToken);
/// @notice Emitted when forced liquidation is enabled or disabled for a market
event IsForcedLiquidationEnabledUpdated(address indexed vToken, bool enable);
/// @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 Thrown when collateral factor exceeds the upper bound
error InvalidCollateralFactor();
/// @notice Thrown when liquidation threshold exceeds the collateral factor
error InvalidLiquidationThreshold();
/// @notice Thrown when the action is only available to specific sender, but the real sender was different
error UnexpectedSender(address expectedSender, address actualSender);
/// @notice Thrown when the oracle returns an invalid price for some asset
error PriceError(address vToken);
/// @notice Thrown if VToken unexpectedly returned a nonzero error code while trying to get account snapshot
error SnapshotError(address vToken, address user);
/// @notice Thrown when the market is not listed
error MarketNotListed(address market);
/// @notice Thrown when a market has an unexpected comptroller
error ComptrollerMismatch();
/// @notice Thrown when user is not member of market
error MarketNotCollateral(address vToken, address user);
/// @notice Thrown when borrow action is not paused
error BorrowActionNotPaused();
/// @notice Thrown when mint action is not paused
error MintActionNotPaused();
/// @notice Thrown when redeem action is not paused
error RedeemActionNotPaused();
/// @notice Thrown when repay action is not paused
error RepayActionNotPaused();
/// @notice Thrown when seize action is not paused
error SeizeActionNotPaused();
/// @notice Thrown when exit market action is not paused
error ExitMarketActionNotPaused();
/// @notice Thrown when transfer action is not paused
error TransferActionNotPaused();
/// @notice Thrown when enter market action is not paused
error EnterMarketActionNotPaused();
/// @notice Thrown when liquidate action is not paused
error LiquidateActionNotPaused();
/// @notice Thrown when borrow cap is not zero
error BorrowCapIsNotZero();
/// @notice Thrown when supply cap is not zero
error SupplyCapIsNotZero();
/// @notice Thrown when collateral factor is not zero
error CollateralFactorIsNotZero();
/**
* @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
* or healAccount) are available.
*/
error MinimalCollateralViolated(uint256 expectedGreaterThan, uint256 actual);
error CollateralExceedsThreshold(uint256 expectedLessThanOrEqualTo, uint256 actual);
error InsufficientCollateral(uint256 collateralToSeize, uint256 availableCollateral);
/// @notice Thrown when the account doesn't have enough liquidity to redeem or borrow
error InsufficientLiquidity();
/// @notice Thrown when trying to liquidate a healthy account
error InsufficientShortfall();
/// @notice Thrown when trying to repay more than allowed by close factor
error TooMuchRepay();
/// @notice Thrown if the user is trying to exit a market in which they have an outstanding debt
error NonzeroBorrowBalance();
/// @notice Thrown when trying to perform an action that is paused
error ActionPaused(address market, Action action);
/// @notice Thrown when trying to add a market that is already listed
error MarketAlreadyListed(address market);
/// @notice Thrown if the supply cap is exceeded
error SupplyCapExceeded(address market, uint256 cap);
/// @notice Thrown if the borrow cap is exceeded
error BorrowCapExceeded(address market, uint256 cap);
/// @notice Thrown if delegate approval status is already set to the requested value
error DelegationStatusUnchanged();
/// @param poolRegistry_ Pool registry address
/// @custom:oz-upgrades-unsafe-allow constructor
/// @custom:error ZeroAddressNotAllowed is thrown when pool registry address is zero
constructor(address poolRegistry_) {
ensureNonzeroAddress(poolRegistry_);
poolRegistry = poolRegistry_;
_disableInitializers();
}
/**
* @param loopLimit Limit for the loops can iterate to avoid the DOS
* @param accessControlManager Access control manager contract address
*/
function initialize(uint256 loopLimit, address accessControlManager) external initializer {
__Ownable2Step_init();
__AccessControlled_init_unchained(accessControlManager);
_setMaxLoopsLimit(loopLimit);
}
/**
* @notice Add assets to be included in account liquidity calculation; enabling them to be used as collateral
* @param vTokens The list of addresses of the vToken markets to be enabled
* @return errors An array of NO_ERROR for compatibility with Venus core tooling
* @custom:event MarketEntered is emitted for each market on success
* @custom:error ActionPaused error is thrown if entering any of the markets is paused
* @custom:error MarketNotListed error is thrown if any of the markets is not listed
* @custom:access Not restricted
*/
function enterMarkets(address[] memory vTokens) external override returns (uint256[] memory) {
uint256 len = vTokens.length;
uint256[] memory results = new uint256[](len);
for (uint256 i; i < len; ++i) {
VToken vToken = VToken(vTokens[i]);
_addToMarket(vToken, msg.sender);
results[i] = NO_ERROR;
}
return results;
}
/**
* @notice Unlist a market by setting isListed to false
* @dev Checks if all actions are paused, borrow/supply caps is set to 0 and collateral factor is to 0.
* @param market The address of the market (token) to unlist
* @return uint256 Always NO_ERROR for compatibility with Venus core tooling
* @custom:event MarketUnlisted is emitted on success
* @custom:error MarketNotListed error is thrown when the market is not listed
* @custom:error BorrowActionNotPaused error is thrown if borrow action is not paused
* @custom:error MintActionNotPaused error is thrown if mint action is not paused
* @custom:error RedeemActionNotPaused error is thrown if redeem action is not paused
* @custom:error RepayActionNotPaused error is thrown if repay action is not paused
* @custom:error EnterMarketActionNotPaused error is thrown if enter market action is not paused
* @custom:error LiquidateActionNotPaused error is thrown if liquidate action is not paused
* @custom:error BorrowCapIsNotZero error is thrown if borrow cap is not zero
* @custom:error SupplyCapIsNotZero error is thrown if supply cap is not zero
* @custom:error CollateralFactorIsNotZero error is thrown if collateral factor is not zero
*/
function unlistMarket(address market) external returns (uint256) {
_checkAccessAllowed("unlistMarket(address)");
Market storage _market = markets[market];
if (!_market.isListed) {
revert MarketNotListed(market);
}
if (!actionPaused(market, Action.BORROW)) {
revert BorrowActionNotPaused();
}
if (!actionPaused(market, Action.MINT)) {
revert MintActionNotPaused();
}
if (!actionPaused(market, Action.REDEEM)) {
revert RedeemActionNotPaused();
}
if (!actionPaused(market, Action.REPAY)) {
revert RepayActionNotPaused();
}
if (!actionPaused(market, Action.SEIZE)) {
revert SeizeActionNotPaused();
}
if (!actionPaused(market, Action.ENTER_MARKET)) {
revert EnterMarketActionNotPaused();
}
if (!actionPaused(market, Action.LIQUIDATE)) {
revert LiquidateActionNotPaused();
}
if (!actionPaused(market, Action.TRANSFER)) {
revert TransferActionNotPaused();
}
if (!actionPaused(market, Action.EXIT_MARKET)) {
revert ExitMarketActionNotPaused();
}
if (borrowCaps[market] != 0) {
revert BorrowCapIsNotZero();
}
if (supplyCaps[market] != 0) {
revert SupplyCapIsNotZero();
}
if (_market.collateralFactorMantissa != 0) {
revert CollateralFactorIsNotZero();
}
_market.isListed = false;
emit MarketUnlisted(market);
return NO_ERROR;
}
/**
* @notice Grants or revokes the borrowing or redeeming delegate rights to / from an account
* If allowed, the delegate will be able to borrow funds on behalf of the sender
* Upon a delegated borrow, the delegate will receive the funds, and the borrower
* will see the debt on their account
* Upon a delegated redeem, the delegate will receive the redeemed amount and the approver
* will see a deduction in his vToken balance
* @param delegate The address to update the rights for
* @param approved Whether to grant (true) or revoke (false) the borrowing or redeeming rights
* @custom:event DelegateUpdated emits on success
* @custom:error ZeroAddressNotAllowed is thrown when delegate address is zero
* @custom:error DelegationStatusUnchanged is thrown if approval status is already set to the requested value
* @custom:access Not restricted
*/
function updateDelegate(address delegate, bool approved) external {
ensureNonzeroAddress(delegate);
if (approvedDelegates[msg.sender][delegate] == approved) {
revert DelegationStatusUnchanged();
}
approvedDelegates[msg.sender][delegate] = approved;
emit DelegateUpdated(msg.sender, delegate, approved);
}
/**
* @notice Removes asset from sender's account liquidity calculation; disabling them as collateral
* @dev Sender must not have an outstanding borrow balance in the asset,
* or be providing necessary collateral for an outstanding borrow.
* @param vTokenAddress The address of the asset to be removed
* @return error Always NO_ERROR for compatibility with Venus core tooling
* @custom:event MarketExited is emitted on success
* @custom:error ActionPaused error is thrown if exiting the market is paused
* @custom:error NonzeroBorrowBalance error is thrown if the user has an outstanding borrow in this market
* @custom:error MarketNotListed error is thrown when the market is not listed
* @custom:error InsufficientLiquidity error is thrown if exiting the market would lead to user's insolvency
* @custom:error SnapshotError is thrown if some vToken fails to return the account's supply and borrows
* @custom:error PriceError is thrown if the oracle returns an incorrect price for some asset
* @custom:access Not restricted
*/
function exitMarket(address vTokenAddress) external override returns (uint256) {
_checkActionPauseState(vTokenAddress, Action.EXIT_MARKET);
VToken vToken = VToken(vTokenAddress);
/* Get sender tokensHeld and amountOwed underlying from the vToken */
(uint256 tokensHeld, uint256 amountOwed, ) = _safeGetAccountSnapshot(vToken, msg.sender);
/* Fail if the sender has a borrow balance */
if (amountOwed != 0) {
revert NonzeroBorrowBalance();
}
/* Fail if the sender is not permitted to redeem all of their tokens */
_checkRedeemAllowed(vTokenAddress, msg.sender, tokensHeld);
Market storage marketToExit = markets[address(vToken)];
/* Return true if the sender is not already ‘in’ the market */
if (!marketToExit.accountMembership[msg.sender]) {
return NO_ERROR;
}
/* Set vToken account membership to false */
delete marketToExit.accountMembership[msg.sender];
/* Delete vToken from the account’s list of assets */
// load into memory for faster iteration
VToken[] memory userAssetList = accountAssets[msg.sender];
uint256 len = userAssetList.length;
uint256 assetIndex = len;
for (uint256 i; i < len; ++i) {
if (userAssetList[i] == vToken) {
assetIndex = i;
break;
}
}
// We *must* have found the asset in the list or our redundant data structure is broken
assert(assetIndex < len);
// copy last item in list to location of item to be removed, reduce length by 1
VToken[] storage storedList = accountAssets[msg.sender];
storedList[assetIndex] = storedList[storedList.length - 1];
storedList.pop();
emit MarketExited(vToken, msg.sender);
return NO_ERROR;
}
/*** Policy Hooks ***/
/**
* @notice Checks if the account should be allowed to mint tokens in the given market
* @param vToken The market to verify the mint against
* @param minter The account which would get the minted tokens
* @param mintAmount The amount of underlying being supplied to the market in exchange for tokens
* @custom:error ActionPaused error is thrown if supplying to this market is paused
* @custom:error MarketNotListed error is thrown when the market is not listed
* @custom:error SupplyCapExceeded error is thrown if the total supply exceeds the cap after minting
* @custom:access Not restricted
*/
function preMintHook(address vToken, address minter, uint256 mintAmount) external override {
_checkActionPauseState(vToken, Action.MINT);
if (!markets[vToken].isListed) {
revert MarketNotListed(address(vToken));
}
uint256 supplyCap = supplyCaps[vToken];
// Skipping the cap check for uncapped coins to save some gas
if (supplyCap != type(uint256).max) {
uint256 vTokenSupply = VToken(vToken).totalSupply();
Exp memory exchangeRate = Exp({ mantissa: VToken(vToken).exchangeRateStored() });
uint256 nextTotalSupply = mul_ScalarTruncateAddUInt(exchangeRate, vTokenSupply, mintAmount);
if (nextTotalSupply > supplyCap) {
revert SupplyCapExceeded(vToken, supplyCap);
}
}
// Keep the flywheel moving
uint256 rewardDistributorsCount = rewardsDistributors.length;
for (uint256 i; i < rewardDistributorsCount; ++i) {
RewardsDistributor rewardsDistributor = rewardsDistributors[i];
rewardsDistributor.updateRewardTokenSupplyIndex(vToken);
rewardsDistributor.distributeSupplierRewardToken(vToken, minter);
}
}
/**
* @notice Validates mint, accrues interest and updates score in prime. Reverts on rejection. May emit logs.
* @param vToken Asset being minted
* @param minter The address minting the tokens
* @param actualMintAmount The amount of the underlying asset being minted
* @param mintTokens The number of tokens being minted
*/
// solhint-disable-next-line no-unused-vars
function mintVerify(address vToken, address minter, uint256 actualMintAmount, uint256 mintTokens) external {
if (address(prime) != address(0)) {
prime.accrueInterestAndUpdateScore(minter, vToken);
}
}
/**
* @notice Checks if the account should be allowed to redeem tokens in the given market
* @param vToken The market to verify the redeem against
* @param redeemer The account which would redeem the tokens
* @param redeemTokens The number of vTokens to exchange for the underlying asset in the market
* @custom:error ActionPaused error is thrown if withdrawals are paused in this market
* @custom:error MarketNotListed error is thrown when the market is not listed
* @custom:error InsufficientLiquidity error is thrown if the withdrawal would lead to user's insolvency
* @custom:error SnapshotError is thrown if some vToken fails to return the account's supply and borrows
* @custom:error PriceError is thrown if the oracle returns an incorrect price for some asset
* @custom:access Not restricted
*/
function preRedeemHook(address vToken, address redeemer, uint256 redeemTokens) external override {
_checkActionPauseState(vToken, Action.REDEEM);
_checkRedeemAllowed(vToken, redeemer, redeemTokens);
// Keep the flywheel moving
uint256 rewardDistributorsCount = rewardsDistributors.length;
for (uint256 i; i < rewardDistributorsCount; ++i) {
RewardsDistributor rewardsDistributor = rewardsDistributors[i];
rewardsDistributor.updateRewardTokenSupplyIndex(vToken);
rewardsDistributor.distributeSupplierRewardToken(vToken, redeemer);
}
}
/**
* @notice Validates redeem, accrues interest and updates score in prime. Reverts on rejection. May emit logs.
* @param vToken Asset being redeemed
* @param redeemer The address redeeming the tokens
* @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 {
if (address(prime) != address(0)) {
prime.accrueInterestAndUpdateScore(redeemer, vToken);
}
}
/**
* @notice Validates repayBorrow, accrues interest and updates score in prime. Reverts on rejection. May emit logs.
* @param vToken Asset being repaid
* @param payer The address repaying the borrow
* @param borrower The address of the borrower
* @param actualRepayAmount The amount of underlying being repaid
*/
function repayBorrowVerify(
address vToken,
address payer, // solhint-disable-line no-unused-vars
address borrower,
uint256 actualRepayAmount, // solhint-disable-line no-unused-vars
uint256 borrowerIndex // solhint-disable-line no-unused-vars
) external {
if (address(prime) != address(0)) {
prime.accrueInterestAndUpdateScore(borrower, vToken);
}
}
/**
* @notice Validates liquidateBorrow, accrues interest and updates score in prime. Reverts on rejection. May emit logs.
* @param vTokenBorrowed Asset which was borrowed by the borrower
* @param vTokenCollateral Asset which was used as collateral and will be seized
* @param liquidator The address repaying the borrow and seizing the collateral
* @param borrower The address of the borrower
* @param actualRepayAmount The amount of underlying being repaid
* @param seizeTokens The amount of collateral token that will be seized
*/
function liquidateBorrowVerify(
address vTokenBorrowed,
address vTokenCollateral, // solhint-disable-line no-unused-vars
address liquidator,
address borrower,
uint256 actualRepayAmount, // solhint-disable-line no-unused-vars
uint256 seizeTokens // solhint-disable-line no-unused-vars
) external {
if (address(prime) != address(0)) {
prime.accrueInterestAndUpdateScore(borrower, vTokenBorrowed);
prime.accrueInterestAndUpdateScore(liquidator, vTokenBorrowed);
}
}
/**
* @notice Validates seize, accrues interest and updates score in prime. Reverts on rejection. May emit logs.
* @param vTokenCollateral Asset which was used as collateral and will be seized
* @param vTokenBorrowed Asset which was borrowed by the borrower
* @param liquidator The address repaying the borrow and seizing the collateral
* @param borrower The address of the borrower
* @param seizeTokens The number of collateral tokens to seize
*/
function seizeVerify(
address vTokenCollateral,
address vTokenBorrowed, // solhint-disable-line no-unused-vars
address liquidator,
address borrower,
uint256 seizeTokens // solhint-disable-line no-unused-vars
) external {
if (address(prime) != address(0)) {
prime.accrueInterestAndUpdateScore(borrower, vTokenCollateral);
prime.accrueInterestAndUpdateScore(liquidator, vTokenCollateral);
}
}
/**
* @notice Validates transfer, accrues interest and updates score in prime. Reverts on rejection. May emit logs.
* @param vToken Asset being transferred
* @param src The account which sources the tokens
* @param dst The account which receives the tokens
* @param transferTokens The number of vTokens to transfer
*/
// solhint-disable-next-line no-unused-vars
function transferVerify(address vToken, address src, address dst, uint256 transferTokens) external {
if (address(prime) != address(0)) {
prime.accrueInterestAndUpdateScore(src, vToken);
prime.accrueInterestAndUpdateScore(dst, vToken);
}
}
/**
* @notice Checks if the account should be allowed to borrow the underlying asset of the given market
* @param vToken The market to verify the borrow against
* @param borrower The account which would borrow the asset
* @param borrowAmount The amount of underlying the account would borrow
* @custom:error ActionPaused error is thrown if borrowing is paused in this market
* @custom:error MarketNotListed error is thrown when the market is not listed
* @custom:error InsufficientLiquidity error is thrown if there is not enough collateral to borrow
* @custom:error BorrowCapExceeded is thrown if the borrow cap will be exceeded should this borrow succeed
* @custom:error SnapshotError is thrown if some vToken fails to return the account's supply and borrows
* @custom:error PriceError is thrown if the oracle returns an incorrect price for some asset
* @custom:access Not restricted if vToken is enabled as collateral, otherwise only vToken
*/
/// disable-eslint
function preBorrowHook(address vToken, address borrower, uint256 borrowAmount) external override {
_checkActionPauseState(vToken, Action.BORROW);
if (!markets[vToken].isListed) {
revert MarketNotListed(address(vToken));
}
if (!markets[vToken].accountMembership[borrower]) {
// only vTokens may call borrowAllowed if borrower not in market
_checkSenderIs(vToken);
// attempt to add borrower to the market or revert
_addToMarket(VToken(msg.sender), borrower);
}
// Update the prices of tokens
updatePrices(borrower);
if (oracle.getUnderlyingPrice(vToken) == 0) {
revert PriceError(address(vToken));
}
uint256 borrowCap = borrowCaps[vToken];
// Skipping the cap check for uncapped coins to save some gas
if (borrowCap != type(uint256).max) {
uint256 totalBorrows = VToken(vToken).totalBorrows();
uint256 badDebt = VToken(vToken).badDebt();
uint256 nextTotalBorrows = totalBorrows + borrowAmount + badDebt;
if (nextTotalBorrows > borrowCap) {
revert BorrowCapExceeded(vToken, borrowCap);
}
}
AccountLiquiditySnapshot memory snapshot = _getHypotheticalLiquiditySnapshot(
borrower,
VToken(vToken),
0,
borrowAmount,
_getCollateralFactor
);
if (snapshot.shortfall > 0) {
revert InsufficientLiquidity();
}
Exp memory borrowIndex = Exp({ mantissa: VToken(vToken).borrowIndex() });
// Keep the flywheel moving
uint256 rewardDistributorsCount = rewardsDistributors.length;
for (uint256 i; i < rewardDistributorsCount; ++i) {
RewardsDistributor rewardsDistributor = rewardsDistributors[i];
rewardsDistributor.updateRewardTokenBorrowIndex(vToken, borrowIndex);
rewardsDistributor.distributeBorrowerRewardToken(vToken, borrower, borrowIndex);
}
}
/**
* @notice Validates borrow, accrues interest and updates score in prime. Reverts on rejection. May emit logs.
* @param vToken Asset whose underlying is being borrowed
* @param borrower The address borrowing the underlying
* @param borrowAmount The amount of the underlying asset requested to borrow
*/
// solhint-disable-next-line no-unused-vars
function borrowVerify(address vToken, address borrower, uint256 borrowAmount) external {
if (address(prime) != address(0)) {
prime.accrueInterestAndUpdateScore(borrower, vToken);
}
}
/**
* @notice Checks if the account should be allowed to repay a borrow in the given market
* @param vToken The market to verify the repay against
* @param borrower The account which would borrowed the asset
* @custom:error ActionPaused error is thrown if repayments are paused in this market
* @custom:error MarketNotListed error is thrown when the market is not listed
* @custom:access Not restricted
*/
function preRepayHook(address vToken, address borrower) external override {
_checkActionPauseState(vToken, Action.REPAY);
oracle.updatePrice(vToken);
if (!markets[vToken].isListed) {
revert MarketNotListed(address(vToken));
}
// Keep the flywheel moving
uint256 rewardDistributorsCount = rewardsDistributors.length;
for (uint256 i; i < rewardDistributorsCount; ++i) {
Exp memory borrowIndex = Exp({ mantissa: VToken(vToken).borrowIndex() });
RewardsDistributor rewardsDistributor = rewardsDistributors[i];
rewardsDistributor.updateRewardTokenBorrowIndex(vToken, borrowIndex);
rewardsDistributor.distributeBorrowerRewardToken(vToken, borrower, borrowIndex);
}
}
/**
* @notice Checks if the liquidation should be allowed to occur
* @param vTokenBorrowed Asset which was borrowed by the borrower
* @param vTokenCollateral Asset which was used as collateral and will be seized
* @param borrower The address of the borrower
* @param repayAmount The amount of underlying being repaid
* @param skipLiquidityCheck Allows the borrow to be liquidated regardless of the account liquidity
* @custom:error ActionPaused error is thrown if liquidations are paused in this market
* @custom:error MarketNotListed error is thrown if either collateral or borrowed token is not listed
* @custom:error TooMuchRepay error is thrown if the liquidator is trying to repay more than allowed by close factor
* @custom:error MinimalCollateralViolated is thrown if the users' total collateral is lower than the threshold for non-batch liquidations
* @custom:error InsufficientShortfall is thrown when trying to liquidate a healthy account
* @custom:error SnapshotError is thrown if some vToken fails to return the account's supply and borrows
* @custom:error PriceError is thrown if the oracle returns an incorrect price for some asset
*/
function preLiquidateHook(
address vTokenBorrowed,
address vTokenCollateral,
address borrower,
uint256 repayAmount,
bool skipLiquidityCheck
) external override {
// Pause Action.LIQUIDATE on BORROWED TOKEN to prevent liquidating it.
// If we want to pause liquidating to vTokenCollateral, we should pause
// Action.SEIZE on it
_checkActionPauseState(vTokenBorrowed, Action.LIQUIDATE);
// Update the prices of tokens
updatePrices(borrower);
if (!markets[vTokenBorrowed].isListed) {
revert MarketNotListed(address(vTokenBorrowed));
}
if (!markets[vTokenCollateral].isListed) {
revert MarketNotListed(address(vTokenCollateral));
}
uint256 borrowBalance = VToken(vTokenBorrowed).borrowBalanceStored(borrower);
/* Allow accounts to be liquidated if it is a forced liquidation */
if (skipLiquidityCheck || isForcedLiquidationEnabled[vTokenBorrowed]) {
if (repayAmount > borrowBalance) {
revert TooMuchRepay();
}
return;
}
/* The borrower must have shortfall and collateral > threshold in order to be liquidatable */
AccountLiquiditySnapshot memory snapshot = _getCurrentLiquiditySnapshot(borrower, _getLiquidationThreshold);
if (snapshot.totalCollateral <= minLiquidatableCollateral) {
/* The liquidator should use either liquidateAccount or healAccount */
revert MinimalCollateralViolated(minLiquidatableCollateral, snapshot.totalCollateral);
}
if (snapshot.shortfall == 0) {
revert InsufficientShortfall();
}
/* The liquidator may not repay more than what is allowed by the closeFactor */
uint256 maxClose = mul_ScalarTruncate(Exp({ mantissa: closeFactorMantissa }), borrowBalance);
if (repayAmount > maxClose) {
revert TooMuchRepay();
}
}
/**
* @notice Checks if the seizing of assets should be allowed to occur
* @param vTokenCollateral Asset which was used as collateral and will be seized
* @param seizerContract Contract that tries to seize the asset (either borrowed vToken or Comptroller)
* @param liquidator The address repaying the borrow and seizing the collateral
* @param borrower The address of the borrower
* @custom:error ActionPaused error is thrown if seizing this type of collateral is paused
* @custom:error MarketNotListed error is thrown if either collateral or borrowed token is not listed
* @custom:error ComptrollerMismatch error is when seizer contract or seized asset belong to different pools
* @custom:access Not restricted
*/
function preSeizeHook(
address vTokenCollateral,
address seizerContract,
address liquidator,
address borrower
) external override {
// Pause Action.SEIZE on COLLATERAL to prevent seizing it.
// If we want to pause liquidating vTokenBorrowed, we should pause
// Action.LIQUIDATE on it
_checkActionPauseState(vTokenCollateral, Action.SEIZE);
Market storage market = markets[vTokenCollateral];
if (!market.isListed) {
revert MarketNotListed(vTokenCollateral);
}
if (seizerContract == address(this)) {
// If Comptroller is the seizer, just check if collateral's comptroller
// is equal to the current address
if (address(VToken(vTokenCollateral).comptroller()) != address(this)) {
revert ComptrollerMismatch();
}
} else {
// If the seizer is not the Comptroller, check that the seizer is a
// listed market, and that the markets' comptrollers match
if (!markets[seizerContract].isListed) {
revert MarketNotListed(seizerContract);
}
if (VToken(vTokenCollateral).comptroller() != VToken(seizerContract).comptroller()) {
revert ComptrollerMismatch();
}
}
if (!market.accountMembership[borrower]) {
revert MarketNotCollateral(vTokenCollateral, borrower);
}
// Keep the flywheel moving
uint256 rewardDistributorsCount = rewardsDistributors.length;
for (uint256 i; i < rewardDistributorsCount; ++i) {
RewardsDistributor rewardsDistributor = rewardsDistributors[i];
rewardsDistributor.updateRewardTokenSupplyIndex(vTokenCollateral);
rewardsDistributor.distributeSupplierRewardToken(vTokenCollateral, borrower);
rewardsDistributor.distributeSupplierRewardToken(vTokenCollateral, liquidator);
}
}
/**
* @notice Checks if the account should be allowed to transfer tokens in the given market
* @param vToken The market to verify the transfer against
* @param src The account which sources the tokens
* @param dst The account which receives the tokens
* @param transferTokens The number of vTokens to transfer
* @custom:error ActionPaused error is thrown if withdrawals are paused in this market
* @custom:error MarketNotListed error is thrown when the market is not listed
* @custom:error InsufficientLiquidity error is thrown if the withdrawal would lead to user's insolvency
* @custom:error SnapshotError is thrown if some vToken fails to return the account's supply and borrows
* @custom:error PriceError is thrown if the oracle returns an incorrect price for some asset
* @custom:access Not restricted
*/
function preTransferHook(address vToken, address src, address dst, uint256 transferTokens) external override {
_checkActionPauseState(vToken, Action.TRANSFER);
// Currently the only consideration is whether or not
// the src is allowed to redeem this many tokens
_checkRedeemAllowed(vToken, src, transferTokens);
// Keep the flywheel moving
uint256 rewardDistributorsCount = rewardsDistributors.length;
for (uint256 i; i < rewardDistributorsCount; ++i) {
RewardsDistributor rewardsDistributor = rewardsDistributors[i];
rewardsDistributor.updateRewardTokenSupplyIndex(vToken);
rewardsDistributor.distributeSupplierRewardToken(vToken, src);
rewardsDistributor.distributeSupplierRewardToken(vToken, dst);
}
}
/*** Pool-level operations ***/
/**
* @notice Seizes all the remaining collateral, makes msg.sender repay the existing
* borrows, and treats the rest of the debt as bad debt (for each market).
* The sender has to repay a certain percentage of the debt, computed as
* collateral / (borrows * liquidationIncentive).
* @param user account to heal
* @custom:error CollateralExceedsThreshold error is thrown when the collateral is too big for healing
* @custom:error SnapshotError is thrown if some vToken fails to return the account's supply and borrows
* @custom:error PriceError is thrown if the oracle returns an incorrect price for some asset
* @custom:access Not restricted
*/
function healAccount(address user) external {
VToken[] memory userAssets = getAssetsIn(user);
uint256 userAssetsCount = userAssets.length;
address liquidator = msg.sender;
{
ResilientOracleInterface oracle_ = oracle;
// We need all user's markets to be fresh for the computations to be correct
for (uint256 i; i < userAssetsCount; ++i) {
userAssets[i].accrueInterest();
oracle_.updatePrice(address(userAssets[i]));
}
}
AccountLiquiditySnapshot memory snapshot = _getCurrentLiquiditySnapshot(user, _getLiquidationThreshold);
if (snapshot.totalCollateral > minLiquidatableCollateral) {
revert CollateralExceedsThreshold(minLiquidatableCollateral, snapshot.totalCollateral);
}
if (snapshot.shortfall == 0) {
revert InsufficientShortfall();
}
// percentage = collateral / (borrows * liquidation incentive)
Exp memory collateral = Exp({ mantissa: snapshot.totalCollateral });
Exp memory scaledBorrows = mul_(
Exp({ mantissa: snapshot.borrows }),
Exp({ mantissa: liquidationIncentiveMantissa })
);
Exp memory percentage = div_(collateral, scaledBorrows);
if (lessThanExp(Exp({ mantissa: MANTISSA_ONE }), percentage)) {
revert CollateralExceedsThreshold(scaledBorrows.mantissa, collateral.mantissa);
}
for (uint256 i; i < userAssetsCount; ++i) {
VToken market = userAssets[i];
(uint256 tokens, uint256 borrowBalance, ) = _safeGetAccountSnapshot(market, user);
uint256 repaymentAmount = mul_ScalarTruncate(percentage, borrowBalance);
// Seize the entire collateral
if (tokens != 0) {
market.seize(liquidator, user, tokens);
}
// Repay a certain percentage of the borrow, forgive the rest
if (borrowBalance != 0) {
market.healBorrow(liquidator, user, repaymentAmount);
}
}
}
/**
* @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
* the collateral is higher than the threshold, use regular liquidations. If the collateral is
* below the threshold, and the account is insolvent, use healAccount.
* @param borrower the borrower address
* @param orders an array of liquidation orders
* @custom:error CollateralExceedsThreshold error is thrown when the collateral is too big for a batch liquidation
* @custom:error InsufficientCollateral error is thrown when there is not enough collateral to cover the debt
* @custom:error SnapshotError is thrown if some vToken fails to return the account's supply and borrows
* @custom:error PriceError is thrown if the oracle returns an incorrect price for some asset
* @custom:access Not restricted
*/
function liquidateAccount(address borrower, LiquidationOrder[] calldata orders) external {
// We will accrue interest and update the oracle prices later during the liquidation
AccountLiquiditySnapshot memory snapshot = _getCurrentLiquiditySnapshot(borrower, _getLiquidationThreshold);
if (snapshot.totalCollateral > minLiquidatableCollateral) {
// You should use the regular vToken.liquidateBorrow(...) call
revert CollateralExceedsThreshold(minLiquidatableCollateral, snapshot.totalCollateral);
}
uint256 collateralToSeize = mul_ScalarTruncate(
Exp({ mantissa: liquidationIncentiveMantissa }),
snapshot.borrows
);
if (collateralToSeize >= snapshot.totalCollateral) {
// There is not enough collateral to seize. Use healBorrow to repay some part of the borrow
// and record bad debt.
revert InsufficientCollateral(collateralToSeize, snapshot.totalCollateral);
}
if (snapshot.shortfall == 0) {
revert InsufficientShortfall();
}
uint256 ordersCount = orders.length;
_ensureMaxLoops(ordersCount / 2);
for (uint256 i; i < ordersCount; ++i) {
if (!markets[address(orders[i].vTokenBorrowed)].isListed) {
revert MarketNotListed(address(orders[i].vTokenBorrowed));
}
if (!markets[address(orders[i].vTokenCollateral)].isListed) {
revert MarketNotListed(address(orders[i].vTokenCollateral));
}
LiquidationOrder calldata order = orders[i];
order.vTokenBorrowed.forceLiquidateBorrow(
msg.sender,
borrower,
order.repayAmount,
order.vTokenCollateral,
true
);
}
VToken[] memory borrowMarkets = getAssetsIn(borrower);
uint256 marketsCount = borrowMarkets.length;
for (uint256 i; i < marketsCount; ++i) {
(, uint256 borrowBalance, ) = _safeGetAccountSnapshot(borrowMarkets[i], borrower);
require(borrowBalance == 0, "Nonzero borrow balance after liquidation");
}
}
/**
* @notice Sets the closeFactor to use when liquidating borrows
* @param newCloseFactorMantissa New close factor, scaled by 1e18
* @custom:event Emits NewCloseFactor on success
* @custom:access Controlled by AccessControlManager
*/
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");
uint256 oldCloseFactorMantissa = closeFactorMantissa;
closeFactorMantissa = newCloseFactorMantissa;
emit NewCloseFactor(oldCloseFactorMantissa, newCloseFactorMantissa);
}
/**
* @notice Sets the collateralFactor for a market
* @dev This function is restricted by the AccessControlManager
* @param vToken The market to set the factor on
* @param newCollateralFactorMantissa The new collateral factor, scaled by 1e18
* @param newLiquidationThresholdMantissa The new liquidation threshold, scaled by 1e18
* @custom:event Emits NewCollateralFactor when collateral factor is updated
* and NewLiquidationThreshold when liquidation threshold is updated
* @custom:error MarketNotListed error is thrown when the market is not listed
* @custom:error InvalidCollateralFactor error is thrown when collateral factor is too high
* @custom:error InvalidLiquidationThreshold error is thrown when liquidation threshold is lower than collateral factor
* @custom:error PriceError is thrown when the oracle returns an invalid price for the asset
* @custom:access Controlled by AccessControlManager
*/
function setCollateralFactor(
VToken vToken,
uint256 newCollateralFactorMantissa,
uint256 newLiquidationThresholdMantissa
) external {
_checkAccessAllowed("setCollateralFactor(address,uint256,uint256)");
// Verify market is listed
Market storage market = markets[address(vToken)];
if (!market.isListed) {
revert MarketNotListed(address(vToken));
}
// Check collateral factor <= 0.9
if (newCollateralFactorMantissa > MAX_COLLATERAL_FACTOR_MANTISSA) {
revert InvalidCollateralFactor();
}
// Ensure that liquidation threshold <= 1
if (newLiquidationThresholdMantissa > MANTISSA_ONE) {
revert InvalidLiquidationThreshold();
}
// Ensure that liquidation threshold >= CF
if (newLiquidationThresholdMantissa < newCollateralFactorMantissa) {
revert InvalidLiquidationThreshold();
}
// If collateral factor != 0, fail if price == 0
if (newCollateralFactorMantissa != 0 && oracle.getUnderlyingPrice(address(vToken)) == 0) {
revert PriceError(address(vToken));
}
uint256 oldCollateralFactorMantissa = market.collateralFactorMantissa;
if (newCollateralFactorMantissa != oldCollateralFactorMantissa) {
market.collateralFactorMantissa = newCollateralFactorMantissa;
emit NewCollateralFactor(vToken, oldCollateralFactorMantissa, newCollateralFactorMantissa);
}
uint256 oldLiquidationThresholdMantissa = market.liquidationThresholdMantissa;
if (newLiquidationThresholdMantissa != oldLiquidationThresholdMantissa) {
market.liquidationThresholdMantissa = newLiquidationThresholdMantissa;
emit NewLiquidationThreshold(vToken, oldLiquidationThresholdMantissa, newLiquidationThresholdMantissa);
}
}
/**
* @notice Sets liquidationIncentive
* @dev This function is restricted by the AccessControlManager
* @param newLiquidationIncentiveMantissa New liquidationIncentive scaled by 1e18
* @custom:event Emits NewLiquidationIncentive on success
* @custom:access Controlled by AccessControlManager
*/
function setLiquidationIncentive(uint256 newLiquidationIncentiveMantissa) external {
require(newLiquidationIncentiveMantissa >= MANTISSA_ONE, "liquidation incentive should be greater than 1e18");
_checkAccessAllowed("setLiquidationIncentive(uint256)");
// Save current value for use in log
uint256 oldLiquidationIncentiveMantissa = liquidationIncentiveMantissa;
// Set liquidation incentive to new incentive
liquidationIncentiveMantissa = newLiquidationIncentiveMantissa;
// Emit event with old incentive, new incentive
emit NewLiquidationIncentive(oldLiquidationIncentiveMantissa, newLiquidationIncentiveMantissa);
}
/**
* @notice Add the market to the markets mapping and set it as listed
* @dev Only callable by the PoolRegistry
* @param vToken The address of the market (token) to list
* @custom:error MarketAlreadyListed is thrown if the market is already listed in this pool
* @custom:access Only PoolRegistry
*/
function supportMarket(VToken vToken) external {
_checkSenderIs(poolRegistry);
if (markets[address(vToken)].isListed) {
revert MarketAlreadyListed(address(vToken));
}
require(vToken.isVToken(), "Comptroller: Invalid vToken"); // Sanity check to make sure its really a VToken
Market storage newMarket = markets[address(vToken)];
newMarket.isListed = true;
newMarket.collateralFactorMantissa = 0;
newMarket.liquidationThresholdMantissa = 0;
_addMarket(address(vToken));
uint256 rewardDistributorsCount = rewardsDistributors.length;
for (uint256 i; i < rewardDistributorsCount; ++i) {
rewardsDistributors[i].initializeMarket(address(vToken));
}
emit MarketSupported(vToken);
}
/**
* @notice Set the given borrow caps for the given vToken markets. Borrowing that brings total borrows to or above borrow cap will revert.
* @dev This function is restricted by the AccessControlManager
* @dev A borrow cap of type(uint256).max corresponds to unlimited borrowing.
* @dev Borrow caps smaller than the current total borrows are accepted. This way, new borrows will not be allowed
until the total borrows amount goes below the new borrow cap
* @param vTokens The addresses of the markets (tokens) to change the borrow caps for
* @param newBorrowCaps The new borrow cap values in underlying to be set. A value of type(uint256).max corresponds to unlimited borrowing.
* @custom:access Controlled by AccessControlManager
*/
function setMarketBorrowCaps(VToken[] calldata vTokens, uint256[] calldata newBorrowCaps) external {
_checkAccessAllowed("setMarketBorrowCaps(address[],uint256[])");
uint256 numMarkets = vTokens.length;
uint256 numBorrowCaps = newBorrowCaps.length;
require(numMarkets != 0 && numMarkets == numBorrowCaps, "invalid input");
_ensureMaxLoops(numMarkets);
for (uint256 i; i < numMarkets; ++i) {
borrowCaps[address(vTokens[i])] = newBorrowCaps[i];
emit NewBorrowCap(vTokens[i], newBorrowCaps[i]);
}
}
/**
* @notice Set the given supply caps for the given vToken markets. Supply that brings total Supply to or above supply cap will revert.
* @dev This function is restricted by the AccessControlManager
* @dev A supply cap of type(uint256).max corresponds to unlimited supply.
* @dev Supply caps smaller than the current total supplies are accepted. This way, new supplies will not be allowed
until the total supplies amount goes below the new supply cap
* @param vTokens The addresses of the markets (tokens) to change the supply caps for
* @param newSupplyCaps The new supply cap values in underlying to be set. A value of type(uint256).max corresponds to unlimited supply.
* @custom:access Controlled by AccessControlManager
*/
function setMarketSupplyCaps(VToken[] calldata vTokens, uint256[] calldata newSupplyCaps) external {
_checkAccessAllowed("setMarketSupplyCaps(address[],uint256[])");
uint256 vTokensCount = vTokens.length;
require(vTokensCount != 0, "invalid number of markets");
require(vTokensCount == newSupplyCaps.length, "invalid number of markets");
_ensureMaxLoops(vTokensCount);
for (uint256 i; i < vTokensCount; ++i) {
supplyCaps[address(vTokens[i])] = newSupplyCaps[i];
emit NewSupplyCap(vTokens[i], newSupplyCaps[i]);
}
}
/**
* @notice Pause/unpause specified actions
* @dev This function is restricted by the AccessControlManager
* @param marketsList Markets to pause/unpause the actions on
* @param actionsList List of action ids to pause/unpause
* @param paused The new paused state (true=paused, false=unpaused)
* @custom:access Controlled by AccessControlManager
*/
function setActionsPaused(VToken[] calldata marketsList, Action[] calldata actionsList, bool paused) external {
_checkAccessAllowed("setActionsPaused(address[],uint256[],bool)");
uint256 marketsCount = marketsList.length;
uint256 actionsCount = actionsList.length;
_ensureMaxLoops(marketsCount * actionsCount);
for (uint256 marketIdx; marketIdx < marketsCount; ++marketIdx) {
for (uint256 actionIdx; actionIdx < actionsCount; ++actionIdx) {
_setActionPaused(address(marketsList[marketIdx]), actionsList[actionIdx], paused);
}
}
}
/**
* @notice Set the given collateral threshold for non-batch liquidations. Regular liquidations
* will fail if the collateral amount is less than this threshold. Liquidators should use batch
* operations like liquidateAccount or healAccount.
* @dev This function is restricted by the AccessControlManager
* @param newMinLiquidatableCollateral The new min liquidatable collateral (in USD).
* @custom:access Controlled by AccessControlManager
*/
function setMinLiquidatableCollateral(uint256 newMinLiquidatableCollateral) external {
_checkAccessAllowed("setMinLiquidatableCollateral(uint256)");
uint256 oldMinLiquidatableCollateral = minLiquidatableCollateral;
minLiquidatableCollateral = newMinLiquidatableCollateral;
emit NewMinLiquidatableCollateral(oldMinLiquidatableCollateral, newMinLiquidatableCollateral);
}
/**
* @notice Add a new RewardsDistributor and initialize it with all markets. We can add several RewardsDistributor
* contracts with the same rewardToken, and there could be overlaping among them considering the last reward slot (block or second)
* @dev Only callable by the admin
* @param _rewardsDistributor Address of the RewardDistributor contract to add
* @custom:access Only Governance
* @custom:event Emits NewRewardsDistributor with distributor address
*/
function addRewardsDistributor(RewardsDistributor _rewardsDistributor) external onlyOwner {
require(!rewardsDistributorExists[address(_rewardsDistributor)], "already exists");
uint256 rewardsDistributorsLen = rewardsDistributors.length;
_ensureMaxLoops(rewardsDistributorsLen + 1);
rewardsDistributors.push(_rewardsDistributor);
rewardsDistributorExists[address(_rewardsDistributor)] = true;
uint256 marketsCount = allMarkets.length;
for (uint256 i; i < marketsCount; ++i) {
_rewardsDistributor.initializeMarket(address(allMarkets[i]));
}
emit NewRewardsDistributor(address(_rewardsDistributor), address(_rewardsDistributor.rewardToken()));
}
/**
* @notice Sets a new price oracle for the Comptroller
* @dev Only callable by the admin
* @param newOracle Address of the new price oracle to set
* @custom:event Emits NewPriceOracle on success
* @custom:error ZeroAddressNotAllowed is thrown when the new oracle address is zero
*/
function setPriceOracle(ResilientOracleInterface newOracle) external onlyOwner {
ensureNonzeroAddress(address(newOracle));
ResilientOracleInterface oldOracle = oracle;
oracle = newOracle;
emit NewPriceOracle(oldOracle, newOracle);
}
/**
* @notice Set the for loop iteration limit to avoid DOS
* @param limit Limit for the max loops can execute at a time
*/
function setMaxLoopsLimit(uint256 limit) external onlyOwner {
_setMaxLoopsLimit(limit);
}
/**
* @notice Sets the prime token contract for the comptroller
* @param _prime Address of the Prime contract
*/
function setPrimeToken(IPrime _prime) external onlyOwner {
ensureNonzeroAddress(address(_prime));
emit NewPrimeToken(prime, _prime);
prime = _prime;
}
/**
* @notice Enables forced liquidations for a market. If forced liquidation is enabled,
* borrows in the market may be liquidated regardless of the account liquidity
* @param vTokenBorrowed Borrowed vToken
* @param enable Whether to enable forced liquidations
*/
function setForcedLiquidation(address vTokenBorrowed, bool enable) external {
_checkAccessAllowed("setForcedLiquidation(address,bool)");
ensureNonzeroAddress(vTokenBorrowed);
if (!markets[vTokenBorrowed].isListed) {
revert MarketNotListed(vTokenBorrowed);
}
isForcedLiquidationEnabled[vTokenBorrowed] = enable;
emit IsForcedLiquidationEnabledUpdated(vTokenBorrowed, enable);
}
/**
* @notice Determine the current account liquidity with respect to liquidation threshold requirements
* @dev The interface of this function is intentionally kept compatible with Compound and Venus Core
* @param account The account get liquidity for
* @return error Always NO_ERROR for compatibility with Venus core tooling
* @return liquidity Account liquidity in excess of liquidation threshold requirements,
* @return shortfall Account shortfall below liquidation threshold requirements
*/
function getAccountLiquidity(
address account
) external view returns (uint256 error, uint256 liquidity, uint256 shortfall) {
AccountLiquiditySnapshot memory snapshot = _getCurrentLiquiditySnapshot(account, _getLiquidationThreshold);
return (NO_ERROR, snapshot.liquidity, snapshot.shortfall);
}
/**
* @notice Determine the current account liquidity with respect to collateral requirements
* @dev The interface of this function is intentionally kept compatible with Compound and Venus Core
* @param account The account get liquidity for
* @return error Always NO_ERROR for compatibility with Venus core tooling
* @return liquidity Account liquidity in excess of collateral requirements,
* @return shortfall Account shortfall below collateral requirements
*/
function getBorrowingPower(
address account
) external view returns (uint256 error, uint256 liquidity, uint256 shortfall) {
AccountLiquiditySnapshot memory snapshot = _getCurrentLiquiditySnapshot(account, _getCollateralFactor);
return (NO_ERROR, snapshot.liquidity, snapshot.shortfall);
}
/**
* @notice Determine what the account liquidity would be if the given amounts were redeemed/borrowed
* @dev The interface of this function is intentionally kept compatible with Compound and Venus Core
* @param vTokenModify The market to hypothetically redeem/borrow in
* @param account The account to determine liquidity for
* @param redeemTokens The number of tokens to hypothetically redeem
* @param borrowAmount The amount of underlying to hypothetically borrow
* @return error Always NO_ERROR for compatibility with Venus core tooling
* @return liquidity Hypothetical account liquidity in excess of collateral requirements,
* @return shortfall Hypothetical account shortfall below collateral requirements
*/
function getHypotheticalAccountLiquidity(
address account,
address vTokenModify,
uint256 redeemTokens,
uint256 borrowAmount
) external view returns (uint256 error, uint256 liquidity, uint256 shortfall) {
AccountLiquiditySnapshot memory snapshot = _getHypotheticalLiquiditySnapshot(
account,
VToken(vTokenModify),
redeemTokens,
borrowAmount,
_getCollateralFactor
);
return (NO_ERROR, snapshot.liquidity, snapshot.shortfall);
}
/**
* @notice Return all of the markets
* @dev The automatic getter may be used to access an individual market.
* @return markets The list of market addresses
*/
function getAllMarkets() external view override returns (VToken[] memory) {
return allMarkets;
}
/**
* @notice Check if a market is marked as listed (active)
* @param vToken vToken Address for the market to check
* @return listed True if listed otherwise false
*/
function isMarketListed(VToken vToken) external view returns (bool) {
return markets[address(vToken)].isListed;
}
/*** Assets You Are In ***/
/**
* @notice Returns whether the given account is entered in a given market
* @param account The address of the account to check
* @param vToken The vToken to check
* @return True if the account is in the market specified, otherwise false.
*/
function checkMembership(address account, VToken vToken) external view returns (bool) {
return markets[address(vToken)].accountMembership[account];
}
/**
* @notice Calculate number of tokens of collateral asset to seize given an underlying amount
* @dev Used in liquidation (called in vToken.liquidateBorrowFresh)
* @param vTokenBorrowed The address of the borrowed vToken
* @param vTokenCollateral The address of the collateral vToken
* @param actualRepayAmount The amount of vTokenBorrowed underlying to convert into vTokenCollateral tokens
* @return error Always NO_ERROR for compatibility with Venus core tooling
* @return tokensToSeize Number of vTokenCollateral tokens to be seized in a liquidation
* @custom:error PriceError if the oracle returns an invalid price
*/
function liquidateCalculateSeizeTokens(
address vTokenBorrowed,
address vTokenCollateral,
uint256 actualRepayAmount
) external view override returns (uint256 error, uint256 tokensToSeize) {
/* Read oracle prices for borrowed and collateral markets */
uint256 priceBorrowedMantissa = _safeGetUnderlyingPrice(VToken(vTokenBorrowed));
uint256 priceCollateralMantissa = _safeGetUnderlyingPrice(VToken(vTokenCollateral));
/*
* Get the exchange rate and calculate the number of collateral tokens to seize:
* seizeAmount = actualRepayAmount * liquidationIncentive * priceBorrowed / priceCollateral
* seizeTokens = seizeAmount / exchangeRate
* = actualRepayAmount * (liquidationIncentive * priceBorrowed) / (priceCollateral * exchangeRate)
*/
uint256 exchangeRateMantissa = VToken(vTokenCollateral).exchangeRateStored(); // Note: reverts on error
uint256 seizeTokens;
Exp memory numerator;
Exp memory denominator;
Exp memory ratio;
numerator = mul_(Exp({ mantissa: liquidationIncentiveMantissa }), Exp({ mantissa: priceBorrowedMantissa }));
denominator = mul_(Exp({ mantissa: priceCollateralMantissa }), Exp({ mantissa: exchangeRateMantissa }));
ratio = div_(numerator, denominator);
seizeTokens = mul_ScalarTruncate(ratio, actualRepayAmount);
return (NO_ERROR, seizeTokens);
}
/**
* @notice Returns reward speed given a vToken
* @param vToken The vToken to get the reward speeds for
* @return rewardSpeeds Array of total supply and borrow speeds and reward token for all reward distributors
*/
function getRewardsByMarket(address vToken) external view returns (RewardSpeeds[] memory rewardSpeeds) {
uint256 rewardsDistributorsLength = rewardsDistributors.length;
rewardSpeeds = new RewardSpeeds[](rewardsDistributorsLength);
for (uint256 i; i < rewardsDistributorsLength; ++i) {
RewardsDistributor rewardsDistributor = rewardsDistributors[i];
address rewardToken = address(rewardsDistributor.rewardToken());
rewardSpeeds[i] = RewardSpeeds({
rewardToken: rewardToken,
supplySpeed: rewardsDistributor.rewardTokenSupplySpeeds(vToken),
borrowSpeed: rewardsDistributor.rewardTokenBorrowSpeeds(vToken)
});
}
return rewardSpeeds;
}
/**
* @notice Return all reward distributors for this pool
* @return Array of RewardDistributor addresses
*/
function getRewardDistributors() external view returns (RewardsDistributor[] memory) {
return rewardsDistributors;
}
/**
* @notice A marker method that returns true for a valid Comptroller contract
* @return Always true
*/
function isComptroller() external pure override returns (bool) {
return true;
}
/**
* @notice Update the prices of all the tokens associated with the provided account
* @param account Address of the account to get associated tokens with
*/
function updatePrices(address account) public {
VToken[] memory vTokens = getAssetsIn(account);
uint256 vTokensCount = vTokens.length;
ResilientOracleInterface oracle_ = oracle;
for (uint256 i; i < vTokensCount; ++i) {
oracle_.updatePrice(address(vTokens[i]));
}
}
/**
* @notice Checks if a certain action is paused on a market
* @param market vToken address
* @param action Action to check
* @return paused True if the action is paused otherwise false
*/
function actionPaused(address market, Action action) public view returns (bool) {
return _actionPaused[market][action];
}
/**
* @notice Returns the assets an account has entered
* @param account The address of the account to pull assets for
* @return A list with the assets the account has entered
*/
function getAssetsIn(address account) public view returns (VToken[] memory) {
uint256 len;
VToken[] memory _accountAssets = accountAssets[account];
uint256 _accountAssetsLength = _accountAssets.length;
VToken[] memory assetsIn = new VToken[](_accountAssetsLength);
for (uint256 i; i < _accountAssetsLength; ++i) {
Market storage market = markets[address(_accountAssets[i])];
if (market.isListed) {
assetsIn[len] = _accountAssets[i];
++len;
}
}
assembly {
mstore(assetsIn, len)
}
return assetsIn;
}
/**
* @notice Add the market to the borrower's "assets in" for liquidity calculations
* @param vToken The market to enter
* @param borrower The address of the account to modify
*/
function _addToMarket(VToken vToken, address borrower) internal {
_checkActionPauseState(address(vToken), Action.ENTER_MARKET);
Market storage marketToJoin = markets[address(vToken)];
if (!marketToJoin.isListed) {
revert MarketNotListed(address(vToken));
}
if (marketToJoin.accountMembership[borrower]) {
// already joined
return;
}
// survived the gauntlet, add to list
// NOTE: we store these somewhat redundantly as a significant optimization
// this avoids having to iterate through the list for the most common use cases
// that is, only when we need to perform liquidity checks
// and not whenever we want to check if an account is in a particular market
marketToJoin.accountMembership[borrower] = true;
accountAssets[borrower].push(vToken);
emit MarketEntered(vToken, borrower);
}
/**
* @notice Internal function to validate that a market hasn't already been added
* and if it hasn't adds it
* @param vToken The market to support
*/
function _addMarket(address vToken) internal {
uint256 marketsCount = allMarkets.length;
for (uint256 i; i < marketsCount; ++i) {
if (allMarkets[i] == VToken(vToken)) {
revert MarketAlreadyListed(vToken);
}
}
allMarkets.push(VToken(vToken));
marketsCount = allMarkets.length;
_ensureMaxLoops(marketsCount);
}
/**
* @dev Pause/unpause an action on a market
* @param market Market to pause/unpause the action on
* @param action Action id to pause/unpause
* @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");
_actionPaused[market][action] = paused;
emit ActionPausedMarket(VToken(market), action, paused);
}
/**
* @dev Internal function to check that vTokens can be safely redeemed for the underlying asset.
* @param vToken Address of the vTokens to redeem
* @param redeemer Account redeeming the tokens
* @param redeemTokens The number of tokens to redeem
*/
function _checkRedeemAllowed(address vToken, address redeemer, uint256 redeemTokens) internal {
Market storage market = markets[vToken];
if (!market.isListed) {
revert MarketNotListed(address(vToken));
}
/* If the redeemer is not 'in' the market, then we can bypass the liquidity check */
if (!market.accountMembership[redeemer]) {
return;
}
// Update the prices of tokens
updatePrices(redeemer);
/* Otherwise, perform a hypothetical liquidity check to guard against shortfall */
AccountLiquiditySnapshot memory snapshot = _getHypotheticalLiquiditySnapshot(
redeemer,
VToken(vToken),
redeemTokens,
0,
_getCollateralFactor
);
if (snapshot.shortfall > 0) {
revert InsufficientLiquidity();
}
}
/**
* @notice Get the total collateral, weighted collateral, borrow balance, liquidity, shortfall
* @param account The account to get the snapshot for
* @param weight The function to compute the weight of the collateral – either collateral factor or
* liquidation threshold. Accepts the address of the vToken and returns the weight as Exp.
* @dev Note that we calculate the exchangeRateStored for each collateral vToken using stored data,
* without calculating accumulated interest.
* @return snapshot Account liquidity snapshot
*/
function _getCurrentLiquiditySnapshot(
address account,
function(VToken) internal view returns (Exp memory) weight
) internal view returns (AccountLiquiditySnapshot memory snapshot) {
return _getHypotheticalLiquiditySnapshot(account, VToken(address(0)), 0, 0, weight);
}
/**
* @notice Determine what the supply/borrow balances would be if the given amounts were redeemed/borrowed
* @param vTokenModify The market to hypothetically redeem/borrow in
* @param account The account to determine liquidity for
* @param redeemTokens The number of tokens to hypothetically redeem
* @param borrowAmount The amount of underlying to hypothetically borrow
* @param weight The function to compute the weight of the collateral – either collateral factor or
liquidation threshold. Accepts the address of the VToken and returns the weight
* @dev Note that we calculate the exchangeRateStored for each collateral vToken using stored data,
* without calculating accumulated interest.
* @return snapshot Account liquidity snapshot
*/
function _getHypotheticalLiquiditySnapshot(
address account,
VToken vTokenModify,
uint256 redeemTokens,
uint256 borrowAmount,
function(VToken) internal view returns (Exp memory) weight
) internal view returns (AccountLiquiditySnapshot memory snapshot) {
// For each asset the account is in
VToken[] memory assets = getAssetsIn(account);
uint256 assetsCount = assets.length;
for (uint256 i; i < assetsCount; ++i) {
VToken asset = assets[i];
// Read the balances and exchange rate from the vToken
(uint256 vTokenBalance, uint256 borrowBalance, uint256 exchangeRateMantissa) = _safeGetAccountSnapshot(
asset,
account
);
// Get the normalized price of the asset
Exp memory oraclePrice = Exp({ mantissa: _safeGetUnderlyingPrice(asset) });
// Pre-compute conversion factors from vTokens -> usd
Exp memory vTokenPrice = mul_(Exp({ mantissa: exchangeRateMantissa }), oraclePrice);
Exp memory weightedVTokenPrice = mul_(weight(asset), vTokenPrice);
// weightedCollateral += weightedVTokenPrice * vTokenBalance
snapshot.weightedCollateral = mul_ScalarTruncateAddUInt(
weightedVTokenPrice,
vTokenBalance,
snapshot.weightedCollateral
);
// totalCollateral += vTokenPrice * vTokenBalance
snapshot.totalCollateral = mul_ScalarTruncateAddUInt(vTokenPrice, vTokenBalance, snapshot.totalCollateral);
// borrows += oraclePrice * borrowBalance
snapshot.borrows = mul_ScalarTruncateAddUInt(oraclePrice, borrowBalance, snapshot.borrows);
// Calculate effects of interacting with vTokenModify
if (asset == vTokenModify) {
// redeem effect
// effects += tokensToDenom * redeemTokens
snapshot.effects = mul_ScalarTruncateAddUInt(weightedVTokenPrice, redeemTokens, snapshot.effects);
// borrow effect
// effects += oraclePrice * borrowAmount
snapshot.effects = mul_ScalarTruncateAddUInt(oraclePrice, borrowAmount, snapshot.effects);
}
}
uint256 borrowPlusEffects = snapshot.borrows + snapshot.effects;
// These are safe, as the underflow condition is checked first
unchecked {
if (snapshot.weightedCollateral > borrowPlusEffects) {
snapshot.liquidity = snapshot.weightedCollateral - borrowPlusEffects;
snapshot.shortfall = 0;
} else {
snapshot.liquidity = 0;
snapshot.shortfall = borrowPlusEffects - snapshot.weightedCollateral;
}
}
return snapshot;
}
/**
* @dev Retrieves price from oracle for an asset and checks it is nonzero
* @param asset Address for asset to query price
* @return Underlying price
*/
function _safeGetUnderlyingPrice(VToken asset) internal view returns (uint256) {
uint256 oraclePriceMantissa = oracle.getUnderlyingPrice(address(asset));
if (oraclePriceMantissa == 0) {
revert PriceError(address(asset));
}
return oraclePriceMantissa;
}
/**
* @dev Return collateral factor for a market
* @param asset Address for asset
* @return Collateral factor as exponential
*/
function _getCollateralFactor(VToken asset) internal view returns (Exp memory) {
return Exp({ mantissa: markets[address(asset)].collateralFactorMantissa });
}
/**
* @dev Retrieves liquidation threshold for a market as an exponential
* @param asset Address for asset to liquidation threshold
* @return Liquidation threshold as exponential
*/
function _getLiquidationThreshold(VToken asset) internal view returns (Exp memory) {
return Exp({ mantissa: markets[address(asset)].liquidationThresholdMantissa });
}
/**
* @dev Returns supply and borrow balances of user in vToken, reverts on failure
* @param vToken Market to query
* @param user Account address
* @return vTokenBalance Balance of vTokens, the same as vToken.balanceOf(user)
* @return borrowBalance Borrowed amount, including the interest
* @return exchangeRateMantissa Stored exchange rate
*/
function _safeGetAccountSnapshot(
VToken vToken,
address user
) internal view returns (uint256 vTokenBalance, uint256 borrowBalance, uint256 exchangeRateMantissa) {
uint256 err;
(err, vTokenBalance, borrowBalance, exchangeRateMantissa) = vToken.getAccountSnapshot(user);
if (err != 0) {
revert SnapshotError(address(vToken), user);
}
return (vTokenBalance, borrowBalance, exchangeRateMantissa);
}
/// @notice Reverts if the call is not from expectedSender
/// @param expectedSender Expected transaction sender
function _checkSenderIs(address expectedSender) internal view {
if (msg.sender != expectedSender) {
revert UnexpectedSender(expectedSender, msg.sender);
}
}
/// @notice Reverts if a certain action is paused on a market
/// @param market Market to check
/// @param action Action to check
function _checkActionPauseState(address market, Action action) private view {
if (actionPaused(market, action)) {
revert ActionPaused(market, action);
}
}
}// SPDX-License-Identifier: BSD-3-Clause
pragma solidity ^0.8.25;
import { ResilientOracleInterface } from "@venusprotocol/oracle/contracts/interfaces/OracleInterface.sol";
import { VToken } from "./VToken.sol";
import { RewardsDistributor } from "./Rewards/RewardsDistributor.sol";
enum Action {
MINT,
REDEEM,
BORROW,
REPAY,
SEIZE,
LIQUIDATE,
TRANSFER,
ENTER_MARKET,
EXIT_MARKET
}
/**
* @title ComptrollerInterface
* @author Venus
* @notice Interface implemented by the `Comptroller` contract.
*/
interface ComptrollerInterface {
/*** Assets You Are In ***/
function enterMarkets(address[] calldata vTokens) external returns (uint256[] memory);
function exitMarket(address vToken) external returns (uint256);
/*** Policy Hooks ***/
function preMintHook(address vToken, address minter, uint256 mintAmount) external;
function preRedeemHook(address vToken, address redeemer, uint256 redeemTokens) external;
function preBorrowHook(address vToken, address borrower, uint256 borrowAmount) external;
function preRepayHook(address vToken, address borrower) external;
function preLiquidateHook(
address vTokenBorrowed,
address vTokenCollateral,
address borrower,
uint256 repayAmount,
bool skipLiquidityCheck
) external;
function preSeizeHook(
address vTokenCollateral,
address vTokenBorrowed,
address liquidator,
address borrower
) external;
function borrowVerify(address vToken, address borrower, uint borrowAmount) external;
function mintVerify(address vToken, address minter, uint mintAmount, uint mintTokens) external;
function redeemVerify(address vToken, address redeemer, uint redeemAmount, uint redeemTokens) external;
function repayBorrowVerify(
address vToken,
address payer,
address borrower,
uint repayAmount,
uint borrowerIndex
) external;
function liquidateBorrowVerify(
address vTokenBorrowed,
address vTokenCollateral,
address liquidator,
address borrower,
uint repayAmount,
uint seizeTokens
) external;
function seizeVerify(
address vTokenCollateral,
address vTokenBorrowed,
address liquidator,
address borrower,
uint seizeTokens
) external;
function transferVerify(address vToken, address src, address dst, uint transferTokens) external;
function preTransferHook(address vToken, address src, address dst, uint256 transferTokens) external;
function isComptroller() external view returns (bool);
/*** Liquidity/Liquidation Calculations ***/
function liquidateCalculateSeizeTokens(
address vTokenBorrowed,
address vTokenCollateral,
uint256 repayAmount
) external view returns (uint256, uint256);
function getAllMarkets() external view returns (VToken[] memory);
function actionPaused(address market, Action action) external view returns (bool);
}
/**
* @title ComptrollerViewInterface
* @author Venus
* @notice Interface implemented by the `Comptroller` contract, including only some util view functions.
*/
interface ComptrollerViewInterface {
function markets(address) external view returns (bool, uint256);
function oracle() external view returns (ResilientOracleInterface);
function getAssetsIn(address) external view returns (VToken[] memory);
function closeFactorMantissa() external view returns (uint256);
function liquidationIncentiveMantissa() external view returns (uint256);
function minLiquidatableCollateral() external view returns (uint256);
function getRewardDistributors() external view returns (RewardsDistributor[] memory);
function getAllMarkets() external view returns (VToken[] memory);
function borrowCaps(address) external view returns (uint256);
function supplyCaps(address) external view returns (uint256);
function approvedDelegates(address user, address delegate) external view returns (bool);
}// SPDX-License-Identifier: BSD-3-Clause
pragma solidity 0.8.25;
import { ResilientOracleInterface } from "@venusprotocol/oracle/contracts/interfaces/OracleInterface.sol";
import { VToken } from "./VToken.sol";
import { RewardsDistributor } from "./Rewards/RewardsDistributor.sol";
import { IPrime } from "@venusprotocol/venus-protocol/contracts/Tokens/Prime/Interfaces/IPrime.sol";
import { Action } from "./ComptrollerInterface.sol";
/**
* @title ComptrollerStorage
* @author Venus
* @notice Storage layout for the `Comptroller` contract.
*/
contract ComptrollerStorage {
struct LiquidationOrder {
VToken vTokenCollateral;
VToken vTokenBorrowed;
uint256 repayAmount;
}
struct AccountLiquiditySnapshot {
uint256 totalCollateral;
uint256 weightedCollateral;
uint256 borrows;
uint256 effects;
uint256 liquidity;
uint256 shortfall;
}
struct RewardSpeeds {
address rewardToken;
uint256 supplySpeed;
uint256 borrowSpeed;
}
struct Market {
// Whether or not this market is listed
bool isListed;
// Multiplier representing the most one can borrow against their collateral in this market.
// For instance, 0.9 to allow borrowing 90% of collateral value.
// Must be between 0 and 1, and stored as a mantissa.
uint256 collateralFactorMantissa;
// Multiplier representing the collateralization after which the borrow is eligible
// for liquidation. For instance, 0.8 liquidate when the borrow is 80% of collateral
// value. Must be between 0 and collateral factor, stored as a mantissa.
uint256 liquidationThresholdMantissa;
// Per-market mapping of "accounts in this asset"
mapping(address => bool) accountMembership;
}
/**
* @notice Oracle which gives the price of any given asset
*/
ResilientOracleInterface public oracle;
/**
* @notice Multiplier used to calculate the maximum repayAmount when liquidating a borrow
*/
uint256 public closeFactorMantissa;
/**
* @notice Multiplier representing the discount on collateral that a liquidator receives
*/
uint256 public liquidationIncentiveMantissa;
/**
* @notice Per-account mapping of "assets you are in"
*/
mapping(address => VToken[]) public accountAssets;
/**
* @notice Official mapping of vTokens -> Market metadata
* @dev Used e.g. to determine if a market is supported
*/
mapping(address => Market) public markets;
/// @notice A list of all markets
VToken[] public allMarkets;
/// @notice Borrow caps enforced by borrowAllowed for each vToken address. Defaults to zero which restricts borrowing.
mapping(address => uint256) public borrowCaps;
/// @notice Minimal collateral required for regular (non-batch) liquidations
uint256 public minLiquidatableCollateral;
/// @notice Supply caps enforced by mintAllowed for each vToken address. Defaults to zero which corresponds to minting not allowed
mapping(address => uint256) public supplyCaps;
/// @notice True if a certain action is paused on a certain market
mapping(address => mapping(Action => bool)) internal _actionPaused;
// List of Reward Distributors added
RewardsDistributor[] internal rewardsDistributors;
// Used to check if rewards distributor is added
mapping(address => bool) internal rewardsDistributorExists;
/// @notice Flag indicating whether forced liquidation enabled for a market
mapping(address => bool) public isForcedLiquidationEnabled;
uint256 internal constant NO_ERROR = 0;
// closeFactorMantissa must be strictly greater than this value
uint256 internal constant MIN_CLOSE_FACTOR_MANTISSA = 0.05e18; // 0.05
// closeFactorMantissa must not exceed this value
uint256 internal constant MAX_CLOSE_FACTOR_MANTISSA = 0.9e18; // 0.9
// No collateralFactorMantissa may exceed this value
uint256 internal constant MAX_COLLATERAL_FACTOR_MANTISSA = 0.95e18; // 0.95
/// Prime token address
IPrime public prime;
/// @notice Whether the delegate is allowed to borrow or redeem on behalf of the user
//mapping(address user => mapping (address delegate => bool approved)) public approvedDelegates;
mapping(address => mapping(address => bool)) public approvedDelegates;
/**
* @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[47] private __gap;
}// SPDX-License-Identifier: BSD-3-Clause
pragma solidity 0.8.25;
/**
* @title TokenErrorReporter
* @author Venus
* @notice Errors that can be thrown by the `VToken` contract.
*/
contract TokenErrorReporter {
uint256 public constant NO_ERROR = 0; // support legacy return codes
error TransferNotAllowed();
error MintFreshnessCheck();
error RedeemFreshnessCheck();
error RedeemTransferOutNotPossible();
error BorrowFreshnessCheck();
error BorrowCashNotAvailable();
error DelegateNotApproved();
error RepayBorrowFreshnessCheck();
error HealBorrowUnauthorized();
error ForceLiquidateBorrowUnauthorized();
error LiquidateFreshnessCheck();
error LiquidateCollateralFreshnessCheck();
error LiquidateAccrueCollateralInterestFailed(uint256 errorCode);
error LiquidateLiquidatorIsBorrower();
error LiquidateCloseAmountIsZero();
error LiquidateCloseAmountIsUintMax();
error LiquidateSeizeLiquidatorIsBorrower();
error ProtocolSeizeShareTooBig();
error SetReserveFactorFreshCheck();
error SetReserveFactorBoundsCheck();
error AddReservesFactorFreshCheck(uint256 actualAddAmount);
error ReduceReservesFreshCheck();
error ReduceReservesCashNotAvailable();
error ReduceReservesCashValidation();
error SetInterestRateModelFreshCheck();
}// SPDX-License-Identifier: BSD-3-Clause
pragma solidity 0.8.25;
import { EXP_SCALE as EXP_SCALE_, MANTISSA_ONE as MANTISSA_ONE_ } from "./lib/constants.sol";
/**
* @title Exponential module for storing fixed-precision decimals
* @author Compound
* @notice Exp is a struct which stores decimals with a fixed precision of 18 decimal places.
* Thus, if we wanted to store the 5.1, mantissa would store 5.1e18. That is:
* `Exp({mantissa: 5100000000000000000})`.
*/
contract ExponentialNoError {
struct Exp {
uint256 mantissa;
}
struct Double {
uint256 mantissa;
}
uint256 internal constant EXP_SCALE = EXP_SCALE_;
uint256 internal constant DOUBLE_SCALE = 1e36;
uint256 internal constant HALF_EXP_SCALE = EXP_SCALE / 2;
uint256 internal constant MANTISSA_ONE = MANTISSA_ONE_;
/**
* @dev Truncates the given exp to a whole number value.
* For example, truncate(Exp{mantissa: 15 * EXP_SCALE}) = 15
*/
function truncate(Exp memory exp) internal pure returns (uint256) {
// Note: We are not using careful math here as we're performing a division that cannot fail
return exp.mantissa / EXP_SCALE;
}
/**
* @dev Multiply an Exp by a scalar, then truncate to return an unsigned integer.
*/
// solhint-disable-next-line func-name-mixedcase
function mul_ScalarTruncate(Exp memory a, uint256 scalar) internal pure returns (uint256) {
Exp memory product = mul_(a, scalar);
return truncate(product);
}
/**
* @dev Multiply an Exp by a scalar, truncate, then add an to an unsigned integer, returning an unsigned integer.
*/
// solhint-disable-next-line func-name-mixedcase
function mul_ScalarTruncateAddUInt(Exp memory a, uint256 scalar, uint256 addend) internal pure returns (uint256) {
Exp memory product = mul_(a, scalar);
return add_(truncate(product), addend);
}
/**
* @dev Checks if first Exp is less than second Exp.
*/
function lessThanExp(Exp memory left, Exp memory right) internal pure returns (bool) {
return left.mantissa < right.mantissa;
}
function safe224(uint256 n, string memory errorMessage) internal pure returns (uint224) {
require(n <= type(uint224).max, errorMessage);
return uint224(n);
}
function safe32(uint256 n, string memory errorMessage) internal pure returns (uint32) {
require(n <= type(uint32).max, errorMessage);
return uint32(n);
}
function add_(Exp memory a, Exp memory b) internal pure returns (Exp memory) {
return Exp({ mantissa: add_(a.mantissa, b.mantissa) });
}
function add_(Double memory a, Double memory b) internal pure returns (Double memory) {
return Double({ mantissa: add_(a.mantissa, b.mantissa) });
}
function add_(uint256 a, uint256 b) internal pure returns (uint256) {
return a + b;
}
function sub_(Exp memory a, Exp memory b) internal pure returns (Exp memory) {
return Exp({ mantissa: sub_(a.mantissa, b.mantissa) });
}
function sub_(Double memory a, Double memory b) internal pure returns (Double memory) {
return Double({ mantissa: sub_(a.mantissa, b.mantissa) });
}
function sub_(uint256 a, uint256 b) internal pure returns (uint256) {
return a - b;
}
function mul_(Exp memory a, Exp memory b) internal pure returns (Exp memory) {
return Exp({ mantissa: mul_(a.mantissa, b.mantissa) / EXP_SCALE });
}
function mul_(Exp memory a, uint256 b) internal pure returns (Exp memory) {
return Exp({ mantissa: mul_(a.mantissa, b) });
}
function mul_(uint256 a, Exp memory b) internal pure returns (uint256) {
return mul_(a, b.mantissa) / EXP_SCALE;
}
function mul_(Double memory a, Double memory b) internal pure returns (Double memory) {
return Double({ mantissa: mul_(a.mantissa, b.mantissa) / DOUBLE_SCALE });
}
function mul_(Double memory a, uint256 b) internal pure returns (Double memory) {
return Double({ mantissa: mul_(a.mantissa, b) });
}
function mul_(uint256 a, Double memory b) internal pure returns (uint256) {
return mul_(a, b.mantissa) / DOUBLE_SCALE;
}
function mul_(uint256 a, uint256 b) internal pure returns (uint256) {
return a * b;
}
function div_(Exp memory a, Exp memory b) internal pure returns (Exp memory) {
return Exp({ mantissa: div_(mul_(a.mantissa, EXP_SCALE), b.mantissa) });
}
function div_(Exp memory a, uint256 b) internal pure returns (Exp memory) {
return Exp({ mantissa: div_(a.mantissa, b) });
}
function div_(uint256 a, Exp memory b) internal pure returns (uint256) {
return div_(mul_(a, EXP_SCALE), b.mantissa);
}
function div_(Double memory a, Double memory b) internal pure returns (Double memory) {
return Double({ mantissa: div_(mul_(a.mantissa, DOUBLE_SCALE), b.mantissa) });
}
function div_(Double memory a, uint256 b) internal pure returns (Double memory) {
return Double({ mantissa: div_(a.mantissa, b) });
}
function div_(uint256 a, Double memory b) internal pure returns (uint256) {
return div_(mul_(a, DOUBLE_SCALE), b.mantissa);
}
function div_(uint256 a, uint256 b) internal pure returns (uint256) {
return a / b;
}
function fraction(uint256 a, uint256 b) internal pure returns (Double memory) {
return Double({ mantissa: div_(mul_(a, DOUBLE_SCALE), b) });
}
}// SPDX-License-Identifier: BSD-3-Clause
pragma solidity 0.8.25;
/**
* @title Compound's InterestRateModel Interface
* @author Compound
*/
abstract contract InterestRateModel {
/**
* @notice Calculates the current borrow interest rate per slot (block or second)
* @param cash The total amount of cash the market has
* @param borrows The total amount of borrows the market has outstanding
* @param reserves The total amount of reserves the market has
* @param badDebt The amount of badDebt in the market
* @return The borrow rate percentage per slot (block or second) as a mantissa (scaled by EXP_SCALE)
*/
function getBorrowRate(
uint256 cash,
uint256 borrows,
uint256 reserves,
uint256 badDebt
) external view virtual returns (uint256);
/**
* @notice Calculates the current supply interest rate per slot (block or second)
* @param cash The total amount of cash the market has
* @param borrows The total amount of borrows the market has outstanding
* @param reserves The total amount of reserves the market has
* @param reserveFactorMantissa The current reserve factor the market has
* @param badDebt The amount of badDebt in the market
* @return The supply rate percentage per slot (block or second) as a mantissa (scaled by EXP_SCALE)
*/
function getSupplyRate(
uint256 cash,
uint256 borrows,
uint256 reserves,
uint256 reserveFactorMantissa,
uint256 badDebt
) external view virtual returns (uint256);
/**
* @notice Indicator that this is an InterestRateModel contract (for inspection)
* @return Always true
*/
function isInterestRateModel() external pure virtual returns (bool) {
return true;
}
}// SPDX-License-Identifier: MIT
pragma solidity 0.8.25;
import { IERC20Upgradeable } from "@openzeppelin/contracts-upgradeable/token/ERC20/IERC20Upgradeable.sol";
library ApproveOrRevert {
/// @notice Thrown if a contract is unable to approve a transfer
error ApproveFailed();
/// @notice Approves a transfer, ensuring that it is successful. This function supports non-compliant
/// tokens like the ones that don't return a boolean value on success. Thus, such approve call supports
/// three different kinds of tokens:
/// * Compliant tokens that revert on failure
/// * Compliant tokens that return false on failure
/// * Non-compliant tokens that don't return a value
/// @param token The contract address of the token which will be transferred
/// @param spender The spender contract address
/// @param amount The value of the transfer
function approveOrRevert(IERC20Upgradeable token, address spender, uint256 amount) internal {
bytes memory callData = abi.encodeCall(token.approve, (spender, amount));
// solhint-disable-next-line avoid-low-level-calls
(bool success, bytes memory result) = address(token).call(callData);
if (!success || (result.length != 0 && !abi.decode(result, (bool)))) {
revert ApproveFailed();
}
}
}// SPDX-License-Identifier: BSD-3-Clause pragma solidity ^0.8.25; /// @dev The approximate number of seconds per year uint256 constant SECONDS_PER_YEAR = 31_536_000; /// @dev Base unit for computations, usually used in scaling (multiplications, divisions) uint256 constant EXP_SCALE = 1e18; /// @dev A unit (literal one) in EXP_SCALE, usually used in additions/subtractions uint256 constant MANTISSA_ONE = EXP_SCALE;
// SPDX-License-Identifier: MIT
pragma solidity 0.8.25;
import { Initializable } from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
import { IERC20Upgradeable } from "@openzeppelin/contracts-upgradeable/token/ERC20/IERC20Upgradeable.sol";
import { SafeERC20Upgradeable } from "@openzeppelin/contracts-upgradeable/token/ERC20/utils/SafeERC20Upgradeable.sol";
/**
* @title TokenDebtTracker
* @author Venus
* @notice TokenDebtTracker is an abstract contract that handles transfers _out_ of the inheriting contract.
* If there is an error transferring out (due to any reason, e.g. the token contract restricted the user from
* receiving incoming transfers), the amount is recorded as a debt that can be claimed later.
* @dev Note that the inheriting contract keeps some amount of users' tokens on its balance, so be careful when
* using balanceOf(address(this))!
*/
abstract contract TokenDebtTracker is Initializable {
using SafeERC20Upgradeable for IERC20Upgradeable;
/**
* @notice Mapping (IERC20Upgradeable token => (address user => uint256 amount)).
* Tracks failed transfers: when a token transfer fails, we record the
* amount of the transfer, so that the user can redeem this debt later.
*/
mapping(IERC20Upgradeable => mapping(address => uint256)) public tokenDebt;
/**
* @notice Mapping (IERC20Upgradeable token => uint256 amount) shows how many
* tokens the contract owes to all users. This is useful for accounting to
* understand how much of balanceOf(address(this)) is already owed to users.
*/
mapping(IERC20Upgradeable => uint256) public totalTokenDebt;
/**
* @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;
/**
* @notice Emitted when the contract's debt to the user is increased due to a failed transfer
* @param token Token address
* @param user User address
* @param amount The amount of debt added
*/
event TokenDebtAdded(address indexed token, address indexed user, uint256 amount);
/**
* @notice Emitted when a user claims tokens that the contract owes them
* @param token Token address
* @param user User address
* @param amount The amount transferred
*/
event TokenDebtClaimed(address indexed token, address indexed user, uint256 amount);
/**
* @notice Thrown if the user tries to claim more tokens than they are owed
* @param token The token the user is trying to claim
* @param owedAmount The amount of tokens the contract owes to the user
* @param amount The amount of tokens the user is trying to claim
*/
error InsufficientDebt(address token, address user, uint256 owedAmount, uint256 amount);
/**
* @notice Thrown if trying to transfer more tokens than the contract currently has
* @param token The token the contract is trying to transfer
* @param recipient The recipient of the transfer
* @param amount The amount of tokens the contract is trying to transfer
* @param availableBalance The amount of tokens the contract currently has
*/
error InsufficientBalance(address token, address recipient, uint256 amount, uint256 availableBalance);
/**
* @notice Transfers the tokens we owe to msg.sender, if any
* @param token The token to claim
* @param amount_ The amount of tokens to claim (or max uint256 to claim all)
* @custom:error InsufficientDebt The contract doesn't have enough debt to the user
*/
function claimTokenDebt(IERC20Upgradeable token, uint256 amount_) external {
uint256 owedAmount = tokenDebt[token][msg.sender];
uint256 amount = (amount_ == type(uint256).max ? owedAmount : amount_);
if (amount > owedAmount) {
revert InsufficientDebt(address(token), msg.sender, owedAmount, amount);
}
unchecked {
// Safe because we revert if amount > owedAmount above
tokenDebt[token][msg.sender] = owedAmount - amount;
}
totalTokenDebt[token] -= amount;
emit TokenDebtClaimed(address(token), msg.sender, amount);
token.safeTransfer(msg.sender, amount);
}
// solhint-disable-next-line func-name-mixedcase
function __TokenDebtTracker_init() internal onlyInitializing {
__TokenDebtTracker_init_unchained();
}
// solhint-disable-next-line func-name-mixedcase, no-empty-blocks
function __TokenDebtTracker_init_unchained() internal onlyInitializing {}
/**
* @dev Transfers tokens to the recipient if the contract has enough balance, or
* records the debt if the transfer fails due to reasons unrelated to the contract's
* balance (e.g. if the token forbids transfers to the recipient).
* @param token The token to transfer
* @param to The recipient of the transfer
* @param amount The amount to transfer
* @custom:error InsufficientBalance The contract doesn't have enough balance to transfer
*/
function _transferOutOrTrackDebt(IERC20Upgradeable token, address to, uint256 amount) internal {
uint256 balance = token.balanceOf(address(this));
if (balance < amount) {
revert InsufficientBalance(address(token), address(this), amount, balance);
}
_transferOutOrTrackDebtSkippingBalanceCheck(token, to, amount);
}
/**
* @dev Transfers tokens to the recipient, or records the debt if the transfer fails
* due to any reason, including insufficient balance.
* @param token The token to transfer
* @param to The recipient of the transfer
* @param amount The amount to transfer
*/
function _transferOutOrTrackDebtSkippingBalanceCheck(IERC20Upgradeable token, address to, uint256 amount) internal {
// We can't use safeTransfer here because we can't try-catch internal calls
bool success = _tryTransferOut(token, to, amount);
if (!success) {
tokenDebt[token][to] += amount;
totalTokenDebt[token] += amount;
emit TokenDebtAdded(address(token), to, amount);
}
}
/**
* @dev Either transfers tokens to the recepient or returns false. Supports tokens
* thet revert or return false to indicate failure, and the non-compliant ones
* that do not return any value.
* @param token The token to transfer
* @param to The recipient of the transfer
* @param amount The amount to transfer
* @return true if the transfer succeeded, false otherwise
*/
function _tryTransferOut(IERC20Upgradeable token, address to, uint256 amount) private returns (bool) {
bytes memory callData = abi.encodeCall(token.transfer, (to, amount));
// solhint-disable-next-line avoid-low-level-calls
(bool success, bytes memory returndata) = address(token).call(callData);
return success && (returndata.length == 0 || abi.decode(returndata, (bool))) && address(token).code.length > 0;
}
}// SPDX-License-Identifier: BSD-3-Clause
pragma solidity 0.8.25;
/// @notice Thrown if the supplied address is a zero address where it is not allowed
error ZeroAddressNotAllowed();
/// @notice Checks if the provided address is nonzero, reverts otherwise
/// @param address_ Address to check
/// @custom:error ZeroAddressNotAllowed is thrown if the provided address is a zero address
function ensureNonzeroAddress(address address_) pure {
if (address_ == address(0)) {
revert ZeroAddressNotAllowed();
}
}// SPDX-License-Identifier: BSD-3-Clause
pragma solidity 0.8.25;
/**
* @title MaxLoopsLimitHelper
* @author Venus
* @notice Abstract contract used to avoid collection with too many items that would generate gas errors and DoS.
*/
abstract contract MaxLoopsLimitHelper {
// Limit for the loops to avoid the DOS
uint256 public maxLoopsLimit;
/**
* @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[49] private __gap;
/// @notice Emitted when max loops limit is set
event MaxLoopsLimitUpdated(uint256 oldMaxLoopsLimit, uint256 newmaxLoopsLimit);
/// @notice Thrown an error on maxLoopsLimit exceeds for any loop
error MaxLoopsLimitExceeded(uint256 loopsLimit, uint256 requiredLoops);
/**
* @notice Set the limit for the loops can iterate to avoid the DOS
* @param limit Limit for the max loops can execute at a time
*/
function _setMaxLoopsLimit(uint256 limit) internal {
require(limit > maxLoopsLimit, "Comptroller: Invalid maxLoopsLimit");
uint256 oldMaxLoopsLimit = maxLoopsLimit;
maxLoopsLimit = limit;
emit MaxLoopsLimitUpdated(oldMaxLoopsLimit, limit);
}
/**
* @notice Compare the maxLoopsLimit with number of the times loop iterate
* @param len Length of the loops iterate
* @custom:error MaxLoopsLimitExceeded error is thrown when loops length exceeds maxLoopsLimit
*/
function _ensureMaxLoops(uint256 len) internal view {
if (len > maxLoopsLimit) {
revert MaxLoopsLimitExceeded(maxLoopsLimit, len);
}
}
}// SPDX-License-Identifier: BSD-3-Clause
pragma solidity 0.8.25;
import { Ownable2StepUpgradeable } from "@openzeppelin/contracts-upgradeable/access/Ownable2StepUpgradeable.sol";
import { SafeERC20Upgradeable } from "@openzeppelin/contracts-upgradeable/token/ERC20/utils/SafeERC20Upgradeable.sol";
import { IERC20Upgradeable } from "@openzeppelin/contracts-upgradeable/token/ERC20/IERC20Upgradeable.sol";
import { AccessControlledV8 } from "@venusprotocol/governance-contracts/contracts/Governance/AccessControlledV8.sol";
import { PoolRegistryInterface } from "./PoolRegistryInterface.sol";
import { Comptroller } from "../Comptroller.sol";
import { VToken } from "../VToken.sol";
import { ensureNonzeroAddress } from "../lib/validators.sol";
/**
* @title PoolRegistry
* @author Venus
* @notice The Isolated Pools architecture centers around the `PoolRegistry` contract. The `PoolRegistry` maintains a directory of isolated lending
* pools and can perform actions like creating and registering new pools, adding new markets to existing pools, setting and updating the pool's required
* metadata, and providing the getter methods to get information on the pools.
*
* Isolated lending has three main components: PoolRegistry, pools, and markets. The PoolRegistry is responsible for managing pools.
* It can create new pools, update pool metadata and manage markets within pools. PoolRegistry contains getter methods to get the details of
* any existing pool like `getVTokenForAsset` and `getPoolsSupportedByAsset`. It also contains methods for updating pool metadata (`updatePoolMetadata`)
* and setting pool name (`setPoolName`).
*
* The directory of pools is managed through two mappings: `_poolByComptroller` which is a hashmap with the comptroller address as the key and `VenusPool` as
* the value and `_poolsByID` which is an array of comptroller addresses. Individual pools can be accessed by calling `getPoolByComptroller` with the pool's
* comptroller address. `_poolsByID` is used to iterate through all of the pools.
*
* PoolRegistry also contains a map of asset addresses called `_supportedPools` that maps to an array of assets suppored by each pool. This array of pools by
* asset is retrieved by calling `getPoolsSupportedByAsset`.
*
* PoolRegistry registers new isolated pools in the directory with the `createRegistryPool` method. Isolated pools are composed of independent markets with
* specific assets and custom risk management configurations according to their markets.
*/
contract PoolRegistry is Ownable2StepUpgradeable, AccessControlledV8, PoolRegistryInterface {
using SafeERC20Upgradeable for IERC20Upgradeable;
struct AddMarketInput {
VToken vToken;
uint256 collateralFactor;
uint256 liquidationThreshold;
uint256 initialSupply;
address vTokenReceiver;
uint256 supplyCap;
uint256 borrowCap;
}
uint256 internal constant MAX_POOL_NAME_LENGTH = 100;
/**
* @notice Maps pool's comptroller address to metadata.
*/
mapping(address => VenusPoolMetaData) public metadata;
/**
* @dev Maps pool ID to pool's comptroller address
*/
mapping(uint256 => address) private _poolsByID;
/**
* @dev Total number of pools created.
*/
uint256 private _numberOfPools;
/**
* @dev Maps comptroller address to Venus pool Index.
*/
mapping(address => VenusPool) private _poolByComptroller;
/**
* @dev Maps pool's comptroller address to asset to vToken.
*/
mapping(address => mapping(address => address)) private _vTokens;
/**
* @dev Maps asset to list of supported pools.
*/
mapping(address => address[]) private _supportedPools;
/**
* @notice Emitted when a new Venus pool is added to the directory.
*/
event PoolRegistered(address indexed comptroller, VenusPool pool);
/**
* @notice Emitted when a pool name is set.
*/
event PoolNameSet(address indexed comptroller, string oldName, string newName);
/**
* @notice Emitted when a pool metadata is updated.
*/
event PoolMetadataUpdated(
address indexed comptroller,
VenusPoolMetaData oldMetadata,
VenusPoolMetaData newMetadata
);
/**
* @notice Emitted when a Market is added to the pool.
*/
event MarketAdded(address indexed comptroller, address indexed vTokenAddress);
/// @custom:oz-upgrades-unsafe-allow constructor
constructor() {
// Note that the contract is upgradeable. Use initialize() or reinitializers
// to set the state variables.
_disableInitializers();
}
/**
* @notice Initializes the deployer to owner
* @param accessControlManager_ AccessControlManager contract address
*/
function initialize(address accessControlManager_) external initializer {
__Ownable2Step_init();
__AccessControlled_init_unchained(accessControlManager_);
}
/**
* @notice Adds a new Venus pool to the directory
* @dev Price oracle must be configured before adding a pool
* @param name The name of the pool
* @param comptroller Pool's Comptroller contract
* @param closeFactor The pool's close factor (scaled by 1e18)
* @param liquidationIncentive The pool's liquidation incentive (scaled by 1e18)
* @param minLiquidatableCollateral Minimal collateral for regular (non-batch) liquidations flow
* @return index The index of the registered Venus pool
* @custom:error ZeroAddressNotAllowed is thrown when Comptroller address is zero
* @custom:error ZeroAddressNotAllowed is thrown when price oracle address is zero
*/
function addPool(
string calldata name,
Comptroller comptroller,
uint256 closeFactor,
uint256 liquidationIncentive,
uint256 minLiquidatableCollateral
) external virtual returns (uint256 index) {
_checkAccessAllowed("addPool(string,address,uint256,uint256,uint256)");
// Input validation
ensureNonzeroAddress(address(comptroller));
ensureNonzeroAddress(address(comptroller.oracle()));
uint256 poolId = _registerPool(name, address(comptroller));
// Set Venus pool parameters
comptroller.setCloseFactor(closeFactor);
comptroller.setLiquidationIncentive(liquidationIncentive);
comptroller.setMinLiquidatableCollateral(minLiquidatableCollateral);
return poolId;
}
/**
* @notice Add a market to an existing pool and then mint to provide initial supply
* @param input The structure describing the parameters for adding a market to a pool
* @custom:error ZeroAddressNotAllowed is thrown when vToken address is zero
* @custom:error ZeroAddressNotAllowed is thrown when vTokenReceiver address is zero
*/
function addMarket(AddMarketInput memory input) external {
_checkAccessAllowed("addMarket(AddMarketInput)");
ensureNonzeroAddress(address(input.vToken));
ensureNonzeroAddress(input.vTokenReceiver);
require(input.initialSupply > 0, "PoolRegistry: initialSupply is zero");
VToken vToken = input.vToken;
address vTokenAddress = address(vToken);
address comptrollerAddress = address(vToken.comptroller());
Comptroller comptroller = Comptroller(comptrollerAddress);
address underlyingAddress = vToken.underlying();
IERC20Upgradeable underlying = IERC20Upgradeable(underlyingAddress);
require(_poolByComptroller[comptrollerAddress].creator != address(0), "PoolRegistry: Pool not registered");
// solhint-disable-next-line reason-string
require(
_vTokens[comptrollerAddress][underlyingAddress] == address(0),
"PoolRegistry: Market already added for asset comptroller combination"
);
comptroller.supportMarket(vToken);
comptroller.setCollateralFactor(vToken, input.collateralFactor, input.liquidationThreshold);
uint256[] memory newSupplyCaps = new uint256[](1);
uint256[] memory newBorrowCaps = new uint256[](1);
VToken[] memory vTokens = new VToken[](1);
newSupplyCaps[0] = input.supplyCap;
newBorrowCaps[0] = input.borrowCap;
vTokens[0] = vToken;
comptroller.setMarketSupplyCaps(vTokens, newSupplyCaps);
comptroller.setMarketBorrowCaps(vTokens, newBorrowCaps);
_vTokens[comptrollerAddress][underlyingAddress] = vTokenAddress;
_supportedPools[underlyingAddress].push(comptrollerAddress);
uint256 amountToSupply = _transferIn(underlying, msg.sender, input.initialSupply);
underlying.forceApprove(vTokenAddress, 0);
underlying.forceApprove(vTokenAddress, amountToSupply);
vToken.mintBehalf(input.vTokenReceiver, amountToSupply);
emit MarketAdded(comptrollerAddress, vTokenAddress);
}
/**
* @notice Modify existing Venus pool name
* @param comptroller Pool's Comptroller
* @param name New pool name
*/
function setPoolName(address comptroller, string calldata name) external {
_checkAccessAllowed("setPoolName(address,string)");
_ensureValidName(name);
VenusPool storage pool = _poolByComptroller[comptroller];
string memory oldName = pool.name;
pool.name = name;
emit PoolNameSet(comptroller, oldName, name);
}
/**
* @notice Update metadata of an existing pool
* @param comptroller Pool's Comptroller
* @param metadata_ New pool metadata
*/
function updatePoolMetadata(address comptroller, VenusPoolMetaData calldata metadata_) external {
_checkAccessAllowed("updatePoolMetadata(address,VenusPoolMetaData)");
VenusPoolMetaData memory oldMetadata = metadata[comptroller];
metadata[comptroller] = metadata_;
emit PoolMetadataUpdated(comptroller, oldMetadata, metadata_);
}
/**
* @notice Returns arrays of all Venus pools' data
* @dev This function is not designed to be called in a transaction: it is too gas-intensive
* @return A list of all pools within PoolRegistry, with details for each pool
*/
function getAllPools() external view override returns (VenusPool[] memory) {
uint256 numberOfPools_ = _numberOfPools; // storage load to save gas
VenusPool[] memory _pools = new VenusPool[](numberOfPools_);
for (uint256 i = 1; i <= numberOfPools_; ++i) {
address comptroller = _poolsByID[i];
_pools[i - 1] = (_poolByComptroller[comptroller]);
}
return _pools;
}
/**
* @param comptroller The comptroller proxy address associated to the pool
* @return Returns Venus pool
*/
function getPoolByComptroller(address comptroller) external view override returns (VenusPool memory) {
return _poolByComptroller[comptroller];
}
/**
* @param comptroller comptroller of Venus pool
* @return Returns Metadata of Venus pool
*/
function getVenusPoolMetadata(address comptroller) external view override returns (VenusPoolMetaData memory) {
return metadata[comptroller];
}
function getVTokenForAsset(address comptroller, address asset) external view override returns (address) {
return _vTokens[comptroller][asset];
}
function getPoolsSupportedByAsset(address asset) external view override returns (address[] memory) {
return _supportedPools[asset];
}
/**
* @dev Adds a new Venus pool to the directory (without checking msg.sender).
* @param name The name of the pool
* @param comptroller The pool's Comptroller proxy contract address
* @return The index of the registered Venus pool
*/
function _registerPool(string calldata name, address comptroller) internal returns (uint256) {
VenusPool storage storedPool = _poolByComptroller[comptroller];
require(storedPool.creator == address(0), "PoolRegistry: Pool already exists in the directory.");
_ensureValidName(name);
++_numberOfPools;
uint256 numberOfPools_ = _numberOfPools; // cache on stack to save storage read gas
VenusPool memory pool = VenusPool(name, msg.sender, comptroller, block.number, block.timestamp);
_poolsByID[numberOfPools_] = comptroller;
_poolByComptroller[comptroller] = pool;
emit PoolRegistered(comptroller, pool);
return numberOfPools_;
}
function _transferIn(IERC20Upgradeable token, address from, uint256 amount) internal returns (uint256) {
uint256 balanceBefore = token.balanceOf(address(this));
token.safeTransferFrom(from, address(this), amount);
uint256 balanceAfter = token.balanceOf(address(this));
return balanceAfter - balanceBefore;
}
function _ensureValidName(string calldata name) internal pure {
require(bytes(name).length <= MAX_POOL_NAME_LENGTH, "Pool's name is too large");
}
}// SPDX-License-Identifier: BSD-3-Clause
pragma solidity ^0.8.25;
/**
* @title PoolRegistryInterface
* @author Venus
* @notice Interface implemented by `PoolRegistry`.
*/
interface PoolRegistryInterface {
/**
* @notice Struct for a Venus interest rate pool.
*/
struct VenusPool {
string name;
address creator;
address comptroller;
uint256 blockPosted;
uint256 timestampPosted;
}
/**
* @notice Struct for a Venus interest rate pool metadata.
*/
struct VenusPoolMetaData {
string category;
string logoURL;
string description;
}
/// @notice Get all pools in PoolRegistry
function getAllPools() external view returns (VenusPool[] memory);
/// @notice Get a pool by comptroller address
function getPoolByComptroller(address comptroller) external view returns (VenusPool memory);
/// @notice Get the address of the VToken contract in the Pool where the underlying token is the provided asset
function getVTokenForAsset(address comptroller, address asset) external view returns (address);
/// @notice Get the addresss of the Pools supported that include a market for the provided asset
function getPoolsSupportedByAsset(address asset) external view returns (address[] memory);
/// @notice Get the metadata of a Pool by comptroller address
function getVenusPoolMetadata(address comptroller) external view returns (VenusPoolMetaData memory);
}// SPDX-License-Identifier: BSD-3-Clause
pragma solidity 0.8.25;
import { Ownable2StepUpgradeable } from "@openzeppelin/contracts-upgradeable/access/Ownable2StepUpgradeable.sol";
import { IERC20Upgradeable } from "@openzeppelin/contracts-upgradeable/token/ERC20/IERC20Upgradeable.sol";
import { SafeERC20Upgradeable } from "@openzeppelin/contracts-upgradeable/token/ERC20/utils/SafeERC20Upgradeable.sol";
import { AccessControlledV8 } from "@venusprotocol/governance-contracts/contracts/Governance/AccessControlledV8.sol";
import { TimeManagerV8 } from "@venusprotocol/solidity-utilities/contracts/TimeManagerV8.sol";
import { ExponentialNoError } from "../ExponentialNoError.sol";
import { VToken } from "../VToken.sol";
import { Comptroller } from "../Comptroller.sol";
import { MaxLoopsLimitHelper } from "../MaxLoopsLimitHelper.sol";
import { RewardsDistributorStorage } from "./RewardsDistributorStorage.sol";
/**
* @title `RewardsDistributor`
* @author Venus
* @notice Contract used to configure, track and distribute rewards to users based on their actions (borrows and supplies) in the protocol.
* Users can receive additional rewards through a `RewardsDistributor`. Each `RewardsDistributor` proxy is initialized with a specific reward
* token and `Comptroller`, which can then distribute the reward token to users that supply or borrow in the associated pool.
* Authorized users can set the reward token borrow and supply speeds for each market in the pool. This sets a fixed amount of reward
* token to be released each slot (block or second) for borrowers and suppliers, which is distributed based on a user’s percentage of the borrows or supplies
* respectively. The owner can also set up reward distributions to contributor addresses (distinct from suppliers and borrowers) by setting
* their contributor reward token speed, which similarly allocates a fixed amount of reward token per slot (block or second).
*
* The owner has the ability to transfer any amount of reward tokens held by the contract to any other address. Rewards are not distributed
* automatically and must be claimed by a user calling `claimRewardToken()`. Users should be aware that it is up to the owner and other centralized
* entities to ensure that the `RewardsDistributor` holds enough tokens to distribute the accumulated rewards of users and contributors.
*/
contract RewardsDistributor is
ExponentialNoError,
Ownable2StepUpgradeable,
AccessControlledV8,
MaxLoopsLimitHelper,
RewardsDistributorStorage,
TimeManagerV8
{
using SafeERC20Upgradeable for IERC20Upgradeable;
/// @notice The initial REWARD TOKEN index for a market
uint224 public constant INITIAL_INDEX = 1e36;
/// @notice Emitted when REWARD TOKEN is distributed to a supplier
event DistributedSupplierRewardToken(
VToken indexed vToken,
address indexed supplier,
uint256 rewardTokenDelta,
uint256 rewardTokenTotal,
uint256 rewardTokenSupplyIndex
);
/// @notice Emitted when REWARD TOKEN is distributed to a borrower
event DistributedBorrowerRewardToken(
VToken indexed vToken,
address indexed borrower,
uint256 rewardTokenDelta,
uint256 rewardTokenTotal,
uint256 rewardTokenBorrowIndex
);
/// @notice Emitted when a new supply-side REWARD TOKEN speed is calculated for a market
event RewardTokenSupplySpeedUpdated(VToken indexed vToken, uint256 newSpeed);
/// @notice Emitted when a new borrow-side REWARD TOKEN speed is calculated for a market
event RewardTokenBorrowSpeedUpdated(VToken indexed vToken, uint256 newSpeed);
/// @notice Emitted when REWARD TOKEN is granted by admin
event RewardTokenGranted(address indexed recipient, uint256 amount);
/// @notice Emitted when a new REWARD TOKEN speed is set for a contributor
event ContributorRewardTokenSpeedUpdated(address indexed contributor, uint256 newSpeed);
/// @notice Emitted when a market is initialized
event MarketInitialized(address indexed vToken);
/// @notice Emitted when a reward token supply index is updated
event RewardTokenSupplyIndexUpdated(address indexed vToken);
/// @notice Emitted when a reward token borrow index is updated
event RewardTokenBorrowIndexUpdated(address indexed vToken, Exp marketBorrowIndex);
/// @notice Emitted when a reward for contributor is updated
event ContributorRewardsUpdated(address indexed contributor, uint256 rewardAccrued);
/// @notice Emitted when a reward token last rewarding block for supply is updated
event SupplyLastRewardingBlockUpdated(address indexed vToken, uint32 newBlock);
/// @notice Emitted when a reward token last rewarding block for borrow is updated
event BorrowLastRewardingBlockUpdated(address indexed vToken, uint32 newBlock);
/// @notice Emitted when a reward token last rewarding timestamp for supply is updated
event SupplyLastRewardingBlockTimestampUpdated(address indexed vToken, uint256 newTimestamp);
/// @notice Emitted when a reward token last rewarding timestamp for borrow is updated
event BorrowLastRewardingBlockTimestampUpdated(address indexed vToken, uint256 newTimestamp);
modifier onlyComptroller() {
require(address(comptroller) == msg.sender, "Only comptroller can call this function");
_;
}
/**
* @param timeBased_ A boolean indicating whether the contract is based on time or block.
* @param blocksPerYear_ The number of blocks per year
* @custom:oz-upgrades-unsafe-allow constructor
*/
constructor(bool timeBased_, uint256 blocksPerYear_) TimeManagerV8(timeBased_, blocksPerYear_) {
// Note that the contract is upgradeable. Use initialize() or reinitializers
// to set the state variables.
_disableInitializers();
}
/**
* @notice RewardsDistributor initializer
* @dev Initializes the deployer to owner
* @param comptroller_ Comptroller to attach the reward distributor to
* @param rewardToken_ Reward token to distribute
* @param loopsLimit_ Maximum number of iterations for the loops in this contract
* @param accessControlManager_ AccessControlManager contract address
*/
function initialize(
Comptroller comptroller_,
IERC20Upgradeable rewardToken_,
uint256 loopsLimit_,
address accessControlManager_
) external initializer {
comptroller = comptroller_;
rewardToken = rewardToken_;
__Ownable2Step_init();
__AccessControlled_init_unchained(accessControlManager_);
_setMaxLoopsLimit(loopsLimit_);
}
/**
* @notice Initializes the market state for a specific vToken
* @param vToken The address of the vToken to be initialized
* @custom:event MarketInitialized emits on success
* @custom:access Only Comptroller
*/
function initializeMarket(address vToken) external onlyComptroller {
uint256 blockNumberOrTimestamp = getBlockNumberOrTimestamp();
isTimeBased
? _initializeMarketTimestampBased(vToken, blockNumberOrTimestamp)
: _initializeMarketBlockBased(vToken, safe32(blockNumberOrTimestamp, "block number exceeds 32 bits"));
emit MarketInitialized(vToken);
}
/*** Reward Token Distribution ***/
/**
* @notice Calculate reward token accrued by a borrower and possibly transfer it to them
* Borrowers will begin to accrue after the first interaction with the protocol.
* @dev This function should only be called when the user has a borrow position in the market
* (e.g. Comptroller.preBorrowHook, and Comptroller.preRepayHook)
* We avoid an external call to check if they are in the market to save gas because this function is called in many places
* @param vToken The market in which the borrower is interacting
* @param borrower The address of the borrower to distribute REWARD TOKEN to
* @param marketBorrowIndex The current global borrow index of vToken
*/
function distributeBorrowerRewardToken(
address vToken,
address borrower,
Exp memory marketBorrowIndex
) external onlyComptroller {
_distributeBorrowerRewardToken(vToken, borrower, marketBorrowIndex);
}
function updateRewardTokenSupplyIndex(address vToken) external onlyComptroller {
_updateRewardTokenSupplyIndex(vToken);
}
/**
* @notice Transfer REWARD TOKEN to the recipient
* @dev Note: If there is not enough REWARD TOKEN, we do not perform the transfer all
* @param recipient The address of the recipient to transfer REWARD TOKEN to
* @param amount The amount of REWARD TOKEN to (possibly) transfer
*/
function grantRewardToken(address recipient, uint256 amount) external onlyOwner {
uint256 amountLeft = _grantRewardToken(recipient, amount);
require(amountLeft == 0, "insufficient rewardToken for grant");
emit RewardTokenGranted(recipient, amount);
}
function updateRewardTokenBorrowIndex(address vToken, Exp memory marketBorrowIndex) external onlyComptroller {
_updateRewardTokenBorrowIndex(vToken, marketBorrowIndex);
}
/**
* @notice Set REWARD TOKEN borrow and supply speeds for the specified markets
* @param vTokens The markets whose REWARD TOKEN speed to update
* @param supplySpeeds New supply-side REWARD TOKEN speed for the corresponding market
* @param borrowSpeeds New borrow-side REWARD TOKEN speed for the corresponding market
*/
function setRewardTokenSpeeds(
VToken[] memory vTokens,
uint256[] memory supplySpeeds,
uint256[] memory borrowSpeeds
) external {
_checkAccessAllowed("setRewardTokenSpeeds(address[],uint256[],uint256[])");
uint256 numTokens = vTokens.length;
require(numTokens == supplySpeeds.length && numTokens == borrowSpeeds.length, "invalid setRewardTokenSpeeds");
for (uint256 i; i < numTokens; ++i) {
_setRewardTokenSpeed(vTokens[i], supplySpeeds[i], borrowSpeeds[i]);
}
}
/**
* @notice Set REWARD TOKEN last rewarding block for the specified markets, used when contract is block based
* @param vTokens The markets whose REWARD TOKEN last rewarding block to update
* @param supplyLastRewardingBlocks New supply-side REWARD TOKEN last rewarding block for the corresponding market
* @param borrowLastRewardingBlocks New borrow-side REWARD TOKEN last rewarding block for the corresponding market
*/
function setLastRewardingBlocks(
VToken[] calldata vTokens,
uint32[] calldata supplyLastRewardingBlocks,
uint32[] calldata borrowLastRewardingBlocks
) external {
_checkAccessAllowed("setLastRewardingBlocks(address[],uint32[],uint32[])");
require(!isTimeBased, "Block-based operation only");
uint256 numTokens = vTokens.length;
require(
numTokens == supplyLastRewardingBlocks.length && numTokens == borrowLastRewardingBlocks.length,
"RewardsDistributor::setLastRewardingBlocks invalid input"
);
for (uint256 i; i < numTokens; ) {
_setLastRewardingBlock(vTokens[i], supplyLastRewardingBlocks[i], borrowLastRewardingBlocks[i]);
unchecked {
++i;
}
}
}
/**
* @notice Set REWARD TOKEN last rewarding block timestamp for the specified markets, used when contract is time based
* @param vTokens The markets whose REWARD TOKEN last rewarding block to update
* @param supplyLastRewardingBlockTimestamps New supply-side REWARD TOKEN last rewarding block timestamp for the corresponding market
* @param borrowLastRewardingBlockTimestamps New borrow-side REWARD TOKEN last rewarding block timestamp for the corresponding market
*/
function setLastRewardingBlockTimestamps(
VToken[] calldata vTokens,
uint256[] calldata supplyLastRewardingBlockTimestamps,
uint256[] calldata borrowLastRewardingBlockTimestamps
) external {
_checkAccessAllowed("setLastRewardingBlockTimestamps(address[],uint256[],uint256[])");
require(isTimeBased, "Time-based operation only");
uint256 numTokens = vTokens.length;
require(
numTokens == supplyLastRewardingBlockTimestamps.length &&
numTokens == borrowLastRewardingBlockTimestamps.length,
"RewardsDistributor::setLastRewardingBlockTimestamps invalid input"
);
for (uint256 i; i < numTokens; ) {
_setLastRewardingBlockTimestamp(
vTokens[i],
supplyLastRewardingBlockTimestamps[i],
borrowLastRewardingBlockTimestamps[i]
);
unchecked {
++i;
}
}
}
/**
* @notice Set REWARD TOKEN speed for a single contributor
* @param contributor The contributor whose REWARD TOKEN speed to update
* @param rewardTokenSpeed New REWARD TOKEN speed for contributor
*/
function setContributorRewardTokenSpeed(address contributor, uint256 rewardTokenSpeed) external onlyOwner {
// note that REWARD TOKEN speed could be set to 0 to halt liquidity rewards for a contributor
updateContributorRewards(contributor);
if (rewardTokenSpeed == 0) {
// release storage
delete lastContributorBlock[contributor];
} else {
lastContributorBlock[contributor] = getBlockNumberOrTimestamp();
}
rewardTokenContributorSpeeds[contributor] = rewardTokenSpeed;
emit ContributorRewardTokenSpeedUpdated(contributor, rewardTokenSpeed);
}
function distributeSupplierRewardToken(address vToken, address supplier) external onlyComptroller {
_distributeSupplierRewardToken(vToken, supplier);
}
/**
* @notice Claim all the rewardToken accrued by holder in all markets
* @param holder The address to claim REWARD TOKEN for
*/
function claimRewardToken(address holder) external {
return claimRewardToken(holder, comptroller.getAllMarkets());
}
/**
* @notice Set the limit for the loops can iterate to avoid the DOS
* @param limit Limit for the max loops can execute at a time
*/
function setMaxLoopsLimit(uint256 limit) external onlyOwner {
_setMaxLoopsLimit(limit);
}
/**
* @notice Calculate additional accrued REWARD TOKEN for a contributor since last accrual
* @param contributor The address to calculate contributor rewards for
*/
function updateContributorRewards(address contributor) public {
uint256 rewardTokenSpeed = rewardTokenContributorSpeeds[contributor];
uint256 blockNumberOrTimestamp = getBlockNumberOrTimestamp();
uint256 deltaBlocksOrTimestamp = sub_(blockNumberOrTimestamp, lastContributorBlock[contributor]);
if (deltaBlocksOrTimestamp > 0 && rewardTokenSpeed > 0) {
uint256 newAccrued = mul_(deltaBlocksOrTimestamp, rewardTokenSpeed);
uint256 contributorAccrued = add_(rewardTokenAccrued[contributor], newAccrued);
rewardTokenAccrued[contributor] = contributorAccrued;
lastContributorBlock[contributor] = blockNumberOrTimestamp;
emit ContributorRewardsUpdated(contributor, rewardTokenAccrued[contributor]);
}
}
/**
* @notice Claim all the rewardToken accrued by holder in the specified markets
* @param holder The address to claim REWARD TOKEN for
* @param vTokens The list of markets to claim REWARD TOKEN in
*/
function claimRewardToken(address holder, VToken[] memory vTokens) public {
uint256 vTokensCount = vTokens.length;
_ensureMaxLoops(vTokensCount);
for (uint256 i; i < vTokensCount; ++i) {
VToken vToken = vTokens[i];
require(comptroller.isMarketListed(vToken), "market must be listed");
Exp memory borrowIndex = Exp({ mantissa: vToken.borrowIndex() });
_updateRewardTokenBorrowIndex(address(vToken), borrowIndex);
_distributeBorrowerRewardToken(address(vToken), holder, borrowIndex);
_updateRewardTokenSupplyIndex(address(vToken));
_distributeSupplierRewardToken(address(vToken), holder);
}
rewardTokenAccrued[holder] = _grantRewardToken(holder, rewardTokenAccrued[holder]);
}
/**
* @notice Set REWARD TOKEN last rewarding block for a single market.
* @param vToken market's whose reward token last rewarding block to be updated
* @param supplyLastRewardingBlock New supply-side REWARD TOKEN last rewarding block for market
* @param borrowLastRewardingBlock New borrow-side REWARD TOKEN last rewarding block for market
*/
function _setLastRewardingBlock(
VToken vToken,
uint32 supplyLastRewardingBlock,
uint32 borrowLastRewardingBlock
) internal {
require(comptroller.isMarketListed(vToken), "rewardToken market is not listed");
uint256 blockNumber = getBlockNumberOrTimestamp();
require(supplyLastRewardingBlock > blockNumber, "setting last rewarding block in the past is not allowed");
require(borrowLastRewardingBlock > blockNumber, "setting last rewarding block in the past is not allowed");
uint32 currentSupplyLastRewardingBlock = rewardTokenSupplyState[address(vToken)].lastRewardingBlock;
uint32 currentBorrowLastRewardingBlock = rewardTokenBorrowState[address(vToken)].lastRewardingBlock;
require(
currentSupplyLastRewardingBlock == 0 || currentSupplyLastRewardingBlock > blockNumber,
"this RewardsDistributor is already locked"
);
require(
currentBorrowLastRewardingBlock == 0 || currentBorrowLastRewardingBlock > blockNumber,
"this RewardsDistributor is already locked"
);
if (currentSupplyLastRewardingBlock != supplyLastRewardingBlock) {
rewardTokenSupplyState[address(vToken)].lastRewardingBlock = supplyLastRewardingBlock;
emit SupplyLastRewardingBlockUpdated(address(vToken), supplyLastRewardingBlock);
}
if (currentBorrowLastRewardingBlock != borrowLastRewardingBlock) {
rewardTokenBorrowState[address(vToken)].lastRewardingBlock = borrowLastRewardingBlock;
emit BorrowLastRewardingBlockUpdated(address(vToken), borrowLastRewardingBlock);
}
}
/**
* @notice Set REWARD TOKEN last rewarding timestamp for a single market.
* @param vToken market's whose reward token last rewarding timestamp to be updated
* @param supplyLastRewardingBlockTimestamp New supply-side REWARD TOKEN last rewarding timestamp for market
* @param borrowLastRewardingBlockTimestamp New borrow-side REWARD TOKEN last rewarding timestamp for market
*/
function _setLastRewardingBlockTimestamp(
VToken vToken,
uint256 supplyLastRewardingBlockTimestamp,
uint256 borrowLastRewardingBlockTimestamp
) internal {
require(comptroller.isMarketListed(vToken), "rewardToken market is not listed");
uint256 blockTimestamp = getBlockNumberOrTimestamp();
require(
supplyLastRewardingBlockTimestamp > blockTimestamp,
"setting last rewarding timestamp in the past is not allowed"
);
require(
borrowLastRewardingBlockTimestamp > blockTimestamp,
"setting last rewarding timestamp in the past is not allowed"
);
uint256 currentSupplyLastRewardingBlockTimestamp = rewardTokenSupplyStateTimeBased[address(vToken)]
.lastRewardingTimestamp;
uint256 currentBorrowLastRewardingBlockTimestamp = rewardTokenBorrowStateTimeBased[address(vToken)]
.lastRewardingTimestamp;
require(
currentSupplyLastRewardingBlockTimestamp == 0 || currentSupplyLastRewardingBlockTimestamp > blockTimestamp,
"this RewardsDistributor is already locked"
);
require(
currentBorrowLastRewardingBlockTimestamp == 0 || currentBorrowLastRewardingBlockTimestamp > blockTimestamp,
"this RewardsDistributor is already locked"
);
if (currentSupplyLastRewardingBlockTimestamp != supplyLastRewardingBlockTimestamp) {
rewardTokenSupplyStateTimeBased[address(vToken)].lastRewardingTimestamp = supplyLastRewardingBlockTimestamp;
emit SupplyLastRewardingBlockTimestampUpdated(address(vToken), supplyLastRewardingBlockTimestamp);
}
if (currentBorrowLastRewardingBlockTimestamp != borrowLastRewardingBlockTimestamp) {
rewardTokenBorrowStateTimeBased[address(vToken)].lastRewardingTimestamp = borrowLastRewardingBlockTimestamp;
emit BorrowLastRewardingBlockTimestampUpdated(address(vToken), borrowLastRewardingBlockTimestamp);
}
}
/**
* @notice Set REWARD TOKEN speed for a single market.
* @param vToken market's whose reward token rate to be updated
* @param supplySpeed New supply-side REWARD TOKEN speed for market
* @param borrowSpeed New borrow-side REWARD TOKEN speed for market
*/
function _setRewardTokenSpeed(VToken vToken, uint256 supplySpeed, uint256 borrowSpeed) internal {
require(comptroller.isMarketListed(vToken), "rewardToken market is not listed");
if (rewardTokenSupplySpeeds[address(vToken)] != supplySpeed) {
// Supply speed updated so let's update supply state to ensure that
// 1. REWARD TOKEN accrued properly for the old speed, and
// 2. REWARD TOKEN accrued at the new speed starts after this block.
_updateRewardTokenSupplyIndex(address(vToken));
// Update speed and emit event
rewardTokenSupplySpeeds[address(vToken)] = supplySpeed;
emit RewardTokenSupplySpeedUpdated(vToken, supplySpeed);
}
if (rewardTokenBorrowSpeeds[address(vToken)] != borrowSpeed) {
// Borrow speed updated so let's update borrow state to ensure that
// 1. REWARD TOKEN accrued properly for the old speed, and
// 2. REWARD TOKEN accrued at the new speed starts after this block.
Exp memory borrowIndex = Exp({ mantissa: vToken.borrowIndex() });
_updateRewardTokenBorrowIndex(address(vToken), borrowIndex);
// Update speed and emit event
rewardTokenBorrowSpeeds[address(vToken)] = borrowSpeed;
emit RewardTokenBorrowSpeedUpdated(vToken, borrowSpeed);
}
}
/**
* @notice Calculate REWARD TOKEN accrued by a supplier and possibly transfer it to them.
* @param vToken The market in which the supplier is interacting
* @param supplier The address of the supplier to distribute REWARD TOKEN to
*/
function _distributeSupplierRewardToken(address vToken, address supplier) internal {
RewardToken storage supplyState = rewardTokenSupplyState[vToken];
TimeBasedRewardToken storage supplyStateTimeBased = rewardTokenSupplyStateTimeBased[vToken];
uint256 supplyIndex = isTimeBased ? supplyStateTimeBased.index : supplyState.index;
uint256 supplierIndex = rewardTokenSupplierIndex[vToken][supplier];
// Update supplier's index to the current index since we are distributing accrued REWARD TOKEN
rewardTokenSupplierIndex[vToken][supplier] = supplyIndex;
if (supplierIndex == 0 && supplyIndex >= INITIAL_INDEX) {
// Covers the case where users supplied tokens before the market's supply state index was set.
// Rewards the user with REWARD TOKEN accrued from the start of when supplier rewards were first
// set for the market.
supplierIndex = INITIAL_INDEX;
}
// Calculate change in the cumulative sum of the REWARD TOKEN per vToken accrued
Double memory deltaIndex = Double({ mantissa: sub_(supplyIndex, supplierIndex) });
uint256 supplierTokens = VToken(vToken).balanceOf(supplier);
// Calculate REWARD TOKEN accrued: vTokenAmount * accruedPerVToken
uint256 supplierDelta = mul_(supplierTokens, deltaIndex);
uint256 supplierAccrued = add_(rewardTokenAccrued[supplier], supplierDelta);
rewardTokenAccrued[supplier] = supplierAccrued;
emit DistributedSupplierRewardToken(VToken(vToken), supplier, supplierDelta, supplierAccrued, supplyIndex);
}
/**
* @notice Calculate reward token accrued by a borrower and possibly transfer it to them.
* @param vToken The market in which the borrower is interacting
* @param borrower The address of the borrower to distribute REWARD TOKEN to
* @param marketBorrowIndex The current global borrow index of vToken
*/
function _distributeBorrowerRewardToken(address vToken, address borrower, Exp memory marketBorrowIndex) internal {
RewardToken storage borrowState = rewardTokenBorrowState[vToken];
TimeBasedRewardToken storage borrowStateTimeBased = rewardTokenBorrowStateTimeBased[vToken];
uint256 borrowIndex = isTimeBased ? borrowStateTimeBased.index : borrowState.index;
uint256 borrowerIndex = rewardTokenBorrowerIndex[vToken][borrower];
// Update borrowers's index to the current index since we are distributing accrued REWARD TOKEN
rewardTokenBorrowerIndex[vToken][borrower] = borrowIndex;
if (borrowerIndex == 0 && borrowIndex >= INITIAL_INDEX) {
// Covers the case where users borrowed tokens before the market's borrow state index was set.
// Rewards the user with REWARD TOKEN accrued from the start of when borrower rewards were first
// set for the market.
borrowerIndex = INITIAL_INDEX;
}
// Calculate change in the cumulative sum of the REWARD TOKEN per borrowed unit accrued
Double memory deltaIndex = Double({ mantissa: sub_(borrowIndex, borrowerIndex) });
uint256 borrowerAmount = div_(VToken(vToken).borrowBalanceStored(borrower), marketBorrowIndex);
// Calculate REWARD TOKEN accrued: vTokenAmount * accruedPerBorrowedUnit
if (borrowerAmount != 0) {
uint256 borrowerDelta = mul_(borrowerAmount, deltaIndex);
uint256 borrowerAccrued = add_(rewardTokenAccrued[borrower], borrowerDelta);
rewardTokenAccrued[borrower] = borrowerAccrued;
emit DistributedBorrowerRewardToken(VToken(vToken), borrower, borrowerDelta, borrowerAccrued, borrowIndex);
}
}
/**
* @notice Transfer REWARD TOKEN to the user.
* @dev Note: If there is not enough REWARD TOKEN, we do not perform the transfer all.
* @param user The address of the user to transfer REWARD TOKEN to
* @param amount The amount of REWARD TOKEN to (possibly) transfer
* @return The amount of REWARD TOKEN which was NOT transferred to the user
*/
function _grantRewardToken(address user, uint256 amount) internal returns (uint256) {
uint256 rewardTokenRemaining = rewardToken.balanceOf(address(this));
if (amount > 0 && amount <= rewardTokenRemaining) {
rewardToken.safeTransfer(user, amount);
return 0;
}
return amount;
}
/**
* @notice Accrue REWARD TOKEN to the market by updating the supply index
* @param vToken The market whose supply index to update
* @dev Index is a cumulative sum of the REWARD TOKEN per vToken accrued
*/
function _updateRewardTokenSupplyIndex(address vToken) internal {
RewardToken storage supplyState = rewardTokenSupplyState[vToken];
TimeBasedRewardToken storage supplyStateTimeBased = rewardTokenSupplyStateTimeBased[vToken];
uint256 supplySpeed = rewardTokenSupplySpeeds[vToken];
uint256 blockNumberOrTimestamp = getBlockNumberOrTimestamp();
if (!isTimeBased) {
safe32(blockNumberOrTimestamp, "block number exceeds 32 bits");
}
uint256 lastRewardingBlockOrTimestamp = isTimeBased
? supplyStateTimeBased.lastRewardingTimestamp
: uint256(supplyState.lastRewardingBlock);
if (lastRewardingBlockOrTimestamp > 0 && blockNumberOrTimestamp > lastRewardingBlockOrTimestamp) {
blockNumberOrTimestamp = lastRewardingBlockOrTimestamp;
}
uint256 deltaBlocksOrTimestamp = sub_(
blockNumberOrTimestamp,
(isTimeBased ? supplyStateTimeBased.timestamp : uint256(supplyState.block))
);
if (deltaBlocksOrTimestamp > 0 && supplySpeed > 0) {
uint256 supplyTokens = VToken(vToken).totalSupply();
uint256 accruedSinceUpdate = mul_(deltaBlocksOrTimestamp, supplySpeed);
Double memory ratio = supplyTokens > 0
? fraction(accruedSinceUpdate, supplyTokens)
: Double({ mantissa: 0 });
uint224 supplyIndex = isTimeBased ? supplyStateTimeBased.index : supplyState.index;
uint224 index = safe224(
add_(Double({ mantissa: supplyIndex }), ratio).mantissa,
"new index exceeds 224 bits"
);
if (isTimeBased) {
supplyStateTimeBased.index = index;
supplyStateTimeBased.timestamp = blockNumberOrTimestamp;
} else {
supplyState.index = index;
supplyState.block = uint32(blockNumberOrTimestamp);
}
} else if (deltaBlocksOrTimestamp > 0) {
isTimeBased ? supplyStateTimeBased.timestamp = blockNumberOrTimestamp : supplyState.block = uint32(
blockNumberOrTimestamp
);
}
emit RewardTokenSupplyIndexUpdated(vToken);
}
/**
* @notice Accrue REWARD TOKEN to the market by updating the borrow index
* @param vToken The market whose borrow index to update
* @param marketBorrowIndex The current global borrow index of vToken
* @dev Index is a cumulative sum of the REWARD TOKEN per vToken accrued
*/
function _updateRewardTokenBorrowIndex(address vToken, Exp memory marketBorrowIndex) internal {
RewardToken storage borrowState = rewardTokenBorrowState[vToken];
TimeBasedRewardToken storage borrowStateTimeBased = rewardTokenBorrowStateTimeBased[vToken];
uint256 borrowSpeed = rewardTokenBorrowSpeeds[vToken];
uint256 blockNumberOrTimestamp = getBlockNumberOrTimestamp();
if (!isTimeBased) {
safe32(blockNumberOrTimestamp, "block number exceeds 32 bits");
}
uint256 lastRewardingBlockOrTimestamp = isTimeBased
? borrowStateTimeBased.lastRewardingTimestamp
: uint256(borrowState.lastRewardingBlock);
if (lastRewardingBlockOrTimestamp > 0 && blockNumberOrTimestamp > lastRewardingBlockOrTimestamp) {
blockNumberOrTimestamp = lastRewardingBlockOrTimestamp;
}
uint256 deltaBlocksOrTimestamp = sub_(
blockNumberOrTimestamp,
(isTimeBased ? borrowStateTimeBased.timestamp : uint256(borrowState.block))
);
if (deltaBlocksOrTimestamp > 0 && borrowSpeed > 0) {
uint256 borrowAmount = div_(VToken(vToken).totalBorrows(), marketBorrowIndex);
uint256 accruedSinceUpdate = mul_(deltaBlocksOrTimestamp, borrowSpeed);
Double memory ratio = borrowAmount > 0
? fraction(accruedSinceUpdate, borrowAmount)
: Double({ mantissa: 0 });
uint224 borrowIndex = isTimeBased ? borrowStateTimeBased.index : borrowState.index;
uint224 index = safe224(
add_(Double({ mantissa: borrowIndex }), ratio).mantissa,
"new index exceeds 224 bits"
);
if (isTimeBased) {
borrowStateTimeBased.index = index;
borrowStateTimeBased.timestamp = blockNumberOrTimestamp;
} else {
borrowState.index = index;
borrowState.block = uint32(blockNumberOrTimestamp);
}
} else if (deltaBlocksOrTimestamp > 0) {
if (isTimeBased) {
borrowStateTimeBased.timestamp = blockNumberOrTimestamp;
} else {
borrowState.block = uint32(blockNumberOrTimestamp);
}
}
emit RewardTokenBorrowIndexUpdated(vToken, marketBorrowIndex);
}
/**
* @notice Initializes the market state for a specific vToken called when contract is block-based
* @param vToken The address of the vToken to be initialized
* @param blockNumber current block number
*/
function _initializeMarketBlockBased(address vToken, uint32 blockNumber) internal {
RewardToken storage supplyState = rewardTokenSupplyState[vToken];
RewardToken storage borrowState = rewardTokenBorrowState[vToken];
/*
* Update market state indices
*/
if (supplyState.index == 0) {
// Initialize supply state index with default value
supplyState.index = INITIAL_INDEX;
}
if (borrowState.index == 0) {
// Initialize borrow state index with default value
borrowState.index = INITIAL_INDEX;
}
/*
* Update market state block numbers
*/
supplyState.block = borrowState.block = blockNumber;
}
/**
* @notice Initializes the market state for a specific vToken called when contract is time-based
* @param vToken The address of the vToken to be initialized
* @param blockTimestamp current block timestamp
*/
function _initializeMarketTimestampBased(address vToken, uint256 blockTimestamp) internal {
TimeBasedRewardToken storage supplyState = rewardTokenSupplyStateTimeBased[vToken];
TimeBasedRewardToken storage borrowState = rewardTokenBorrowStateTimeBased[vToken];
/*
* Update market state indices
*/
if (supplyState.index == 0) {
// Initialize supply state index with default value
supplyState.index = INITIAL_INDEX;
}
if (borrowState.index == 0) {
// Initialize borrow state index with default value
borrowState.index = INITIAL_INDEX;
}
/*
* Update market state block timestamp
*/
supplyState.timestamp = borrowState.timestamp = blockTimestamp;
}
}// SPDX-License-Identifier: BSD-3-Clause
pragma solidity 0.8.25;
import { IERC20Upgradeable } from "@openzeppelin/contracts-upgradeable/token/ERC20/IERC20Upgradeable.sol";
import { Comptroller } from "../Comptroller.sol";
/**
* @title RewardsDistributorStorage
* @author Venus
* @dev Storage for RewardsDistributor
*/
contract RewardsDistributorStorage {
struct RewardToken {
// The market's last updated rewardTokenBorrowIndex or rewardTokenSupplyIndex
uint224 index;
// The block number the index was last updated at
uint32 block;
// The block number at which to stop rewards
uint32 lastRewardingBlock;
}
struct TimeBasedRewardToken {
// The market's last updated rewardTokenBorrowIndex or rewardTokenSupplyIndex
uint224 index;
// The block timestamp the index was last updated at
uint256 timestamp;
// The block timestamp at which to stop rewards
uint256 lastRewardingTimestamp;
}
/// @notice The REWARD TOKEN market supply state for each market
mapping(address => RewardToken) public rewardTokenSupplyState;
/// @notice The REWARD TOKEN borrow index for each market for each supplier as of the last time they accrued REWARD TOKEN
mapping(address => mapping(address => uint256)) public rewardTokenSupplierIndex;
/// @notice The REWARD TOKEN accrued but not yet transferred to each user
mapping(address => uint256) public rewardTokenAccrued;
/// @notice The rate at which rewardToken is distributed to the corresponding borrow market per slot (block or second)
mapping(address => uint256) public rewardTokenBorrowSpeeds;
/// @notice The rate at which rewardToken is distributed to the corresponding supply market per slot (block or second)
mapping(address => uint256) public rewardTokenSupplySpeeds;
/// @notice The REWARD TOKEN market borrow state for each market
mapping(address => RewardToken) public rewardTokenBorrowState;
/// @notice The portion of REWARD TOKEN that each contributor receives per slot (block or second)
mapping(address => uint256) public rewardTokenContributorSpeeds;
/// @notice Last slot (block or second) at which a contributor's REWARD TOKEN rewards have been allocated
mapping(address => uint256) public lastContributorBlock;
/// @notice The REWARD TOKEN borrow index for each market for each borrower as of the last time they accrued REWARD TOKEN
mapping(address => mapping(address => uint256)) public rewardTokenBorrowerIndex;
Comptroller internal comptroller;
IERC20Upgradeable public rewardToken;
/// @notice The REWARD TOKEN market supply state for each market
mapping(address => TimeBasedRewardToken) public rewardTokenSupplyStateTimeBased;
/// @notice The REWARD TOKEN market borrow state for each market
mapping(address => TimeBasedRewardToken) public rewardTokenBorrowStateTimeBased;
/**
* @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[37] private __gap;
}// SPDX-License-Identifier: BSD-3-Clause
pragma solidity ^0.8.25;
/**
* @title IRiskFund
* @author Venus
* @notice Interface implemented by `RiskFund`.
*/
interface IRiskFund {
function transferReserveForAuction(address comptroller, uint256 amount) external returns (uint256);
function convertibleBaseAsset() external view returns (address);
function getPoolsBaseAssetReserves(address comptroller) external view returns (uint256);
}// SPDX-License-Identifier: BSD-3-Clause
pragma solidity 0.8.25;
import { Ownable2StepUpgradeable } from "@openzeppelin/contracts-upgradeable/access/Ownable2StepUpgradeable.sol";
import { IERC20Upgradeable } from "@openzeppelin/contracts-upgradeable/token/ERC20/IERC20Upgradeable.sol";
import { SafeERC20Upgradeable } from "@openzeppelin/contracts-upgradeable/token/ERC20/utils/SafeERC20Upgradeable.sol";
import { ReentrancyGuardUpgradeable } from "@openzeppelin/contracts-upgradeable/security/ReentrancyGuardUpgradeable.sol";
import { ResilientOracleInterface } from "@venusprotocol/oracle/contracts/interfaces/OracleInterface.sol";
import { AccessControlledV8 } from "@venusprotocol/governance-contracts/contracts/Governance/AccessControlledV8.sol";
import { ensureNonzeroAddress, ensureNonzeroValue } from "@venusprotocol/solidity-utilities/contracts/validators.sol";
import { TimeManagerV8 } from "@venusprotocol/solidity-utilities/contracts/TimeManagerV8.sol";
import { VToken } from "../VToken.sol";
import { ComptrollerInterface, ComptrollerViewInterface } from "../ComptrollerInterface.sol";
import { IRiskFund } from "./IRiskFund.sol";
import { PoolRegistry } from "../Pool/PoolRegistry.sol";
import { PoolRegistryInterface } from "../Pool/PoolRegistryInterface.sol";
import { TokenDebtTracker } from "../lib/TokenDebtTracker.sol";
import { ShortfallStorage } from "./ShortfallStorage.sol";
import { EXP_SCALE } from "../lib/constants.sol";
/**
* @title Shortfall
* @author Venus
* @notice Shortfall is an auction contract designed to auction off the `convertibleBaseAsset` accumulated in `RiskFund`. The `convertibleBaseAsset`
* is auctioned in exchange for users paying off the pool's bad debt. An auction can be started by anyone once a pool's bad debt has reached a minimum value.
* This value is set and can be changed by the authorized accounts. If the pool’s bad debt exceeds the risk fund plus a 10% incentive, then the auction winner
* is determined by who will pay off the largest percentage of the pool's bad debt. The auction winner then exchanges for the entire risk fund. Otherwise,
* if the risk fund covers the pool's bad debt plus the 10% incentive, then the auction winner is determined by who will take the smallest percentage of the
* risk fund in exchange for paying off all the pool's bad debt.
*/
contract Shortfall is
Ownable2StepUpgradeable,
AccessControlledV8,
ReentrancyGuardUpgradeable,
TokenDebtTracker,
ShortfallStorage,
TimeManagerV8
{
using SafeERC20Upgradeable for IERC20Upgradeable;
/// @dev Max basis points i.e., 100%
uint256 private constant MAX_BPS = 10000;
// @notice Default incentive basis points (BPS) for the auction participants, set to 10%
uint256 private constant DEFAULT_INCENTIVE_BPS = 1000;
// @notice Default block or timestamp limit for the next bidder to place a bid
/// @custom:oz-upgrades-unsafe-allow state-variable-immutable
uint256 private immutable DEFAULT_NEXT_BIDDER_BLOCK_OR_TIMESTAMP_LIMIT;
// @notice Default number of blocks or seconds to wait for the first bidder before starting the auction
/// @custom:oz-upgrades-unsafe-allow state-variable-immutable
uint256 private immutable DEFAULT_WAIT_FOR_FIRST_BIDDER;
/// @notice Emitted when a auction starts
event AuctionStarted(
address indexed comptroller,
uint256 auctionStartBlockOrTimestamp,
AuctionType auctionType,
VToken[] markets,
uint256[] marketsDebt,
uint256 seizedRiskFund,
uint256 startBidBps
);
/// @notice Emitted when a bid is placed
event BidPlaced(
address indexed comptroller,
uint256 auctionStartBlockOrTimestamp,
uint256 bidBps,
address indexed bidder
);
/// @notice Emitted when a auction is completed
event AuctionClosed(
address indexed comptroller,
uint256 auctionStartBlockOrTimestamp,
address indexed highestBidder,
uint256 highestBidBps,
uint256 seizedRiskFind,
VToken[] markets,
uint256[] marketDebt
);
/// @notice Emitted when a auction is restarted
event AuctionRestarted(address indexed comptroller, uint256 auctionStartBlockOrTimestamp);
/// @notice Emitted when pool registry address is updated
event PoolRegistryUpdated(address indexed oldPoolRegistry, address indexed newPoolRegistry);
/// @notice Emitted when minimum pool bad debt is updated
event MinimumPoolBadDebtUpdated(uint256 oldMinimumPoolBadDebt, uint256 newMinimumPoolBadDebt);
/// @notice Emitted when wait for first bidder block or timestamp count is updated
event WaitForFirstBidderUpdated(uint256 oldWaitForFirstBidder, uint256 newWaitForFirstBidder);
/// @notice Emitted when next bidder block or timestamp limit is updated
event NextBidderBlockLimitUpdated(
uint256 oldNextBidderBlockOrTimestampLimit,
uint256 newNextBidderBlockOrTimestampLimit
);
/// @notice Emitted when incentiveBps is updated
event IncentiveBpsUpdated(uint256 oldIncentiveBps, uint256 newIncentiveBps);
/// @notice Emitted when auctions are paused
event AuctionsPaused(address sender);
/// @notice Emitted when auctions are unpaused
event AuctionsResumed(address sender);
/**
* @param timeBased_ A boolean indicating whether the contract is based on time or block.
* @param blocksPerYear_ The number of blocks per year
* @param nextBidderBlockOrTimestampLimit_ Default block or timestamp limit for the next bidder to place a bid
* @param waitForFirstBidder_ Default number of blocks or seconds to wait for the first bidder before starting the auction
* @custom:oz-upgrades-unsafe-allow constructor
*/
constructor(
bool timeBased_,
uint256 blocksPerYear_,
uint256 nextBidderBlockOrTimestampLimit_,
uint256 waitForFirstBidder_
) TimeManagerV8(timeBased_, blocksPerYear_) {
ensureNonzeroValue(nextBidderBlockOrTimestampLimit_);
ensureNonzeroValue(waitForFirstBidder_);
DEFAULT_NEXT_BIDDER_BLOCK_OR_TIMESTAMP_LIMIT = nextBidderBlockOrTimestampLimit_;
DEFAULT_WAIT_FOR_FIRST_BIDDER = waitForFirstBidder_;
// Note that the contract is upgradeable. Use initialize() or reinitializers
// to set the state variables.
_disableInitializers();
}
/**
* @notice Initialize the shortfall contract
* @param riskFund_ RiskFund contract address
* @param minimumPoolBadDebt_ Minimum bad debt in base asset for a pool to start auction
* @param accessControlManager_ AccessControlManager contract address
* @custom:error ZeroAddressNotAllowed is thrown when convertible base asset address is zero
* @custom:error ZeroAddressNotAllowed is thrown when risk fund address is zero
*/
function initialize(
IRiskFund riskFund_,
uint256 minimumPoolBadDebt_,
address accessControlManager_
) external initializer {
ensureNonzeroAddress(address(riskFund_));
require(minimumPoolBadDebt_ != 0, "invalid minimum pool bad debt");
__Ownable2Step_init();
__AccessControlled_init_unchained(accessControlManager_);
__ReentrancyGuard_init();
__TokenDebtTracker_init();
minimumPoolBadDebt = minimumPoolBadDebt_;
riskFund = riskFund_;
incentiveBps = DEFAULT_INCENTIVE_BPS;
auctionsPaused = false;
waitForFirstBidder = DEFAULT_WAIT_FOR_FIRST_BIDDER;
nextBidderBlockLimit = DEFAULT_NEXT_BIDDER_BLOCK_OR_TIMESTAMP_LIMIT;
}
/**
* @notice Place a bid greater than the previous in an ongoing auction
* @param comptroller Comptroller address of the pool
* @param bidBps The bid percent of the risk fund or bad debt depending on auction type
* @param auctionStartBlockOrTimestamp The block number or timestamp when auction started
* @custom:event Emits BidPlaced event on success
*/
function placeBid(address comptroller, uint256 bidBps, uint256 auctionStartBlockOrTimestamp) external nonReentrant {
Auction storage auction = auctions[comptroller];
require(auction.startBlockOrTimestamp == auctionStartBlockOrTimestamp, "auction has been restarted");
require(_isStarted(auction), "no on-going auction");
require(!_isStale(auction), "auction is stale, restart it");
require(bidBps > 0, "basis points cannot be zero");
require(bidBps <= MAX_BPS, "basis points cannot be more than 10000");
require(
(auction.auctionType == AuctionType.LARGE_POOL_DEBT &&
((auction.highestBidder != address(0) && bidBps > auction.highestBidBps) ||
(auction.highestBidder == address(0) && bidBps >= auction.startBidBps))) ||
(auction.auctionType == AuctionType.LARGE_RISK_FUND &&
((auction.highestBidder != address(0) && bidBps < auction.highestBidBps) ||
(auction.highestBidder == address(0) && bidBps <= auction.startBidBps))),
"your bid is not the highest"
);
uint256 marketsCount = auction.markets.length;
for (uint256 i; i < marketsCount; ++i) {
VToken vToken = VToken(address(auction.markets[i]));
IERC20Upgradeable erc20 = IERC20Upgradeable(address(vToken.underlying()));
if (auction.highestBidder != address(0)) {
_transferOutOrTrackDebt(erc20, auction.highestBidder, auction.bidAmount[auction.markets[i]]);
}
uint256 balanceBefore = erc20.balanceOf(address(this));
if (auction.auctionType == AuctionType.LARGE_POOL_DEBT) {
uint256 currentBidAmount = ((auction.marketDebt[auction.markets[i]] * bidBps) / MAX_BPS);
erc20.safeTransferFrom(msg.sender, address(this), currentBidAmount);
} else {
erc20.safeTransferFrom(msg.sender, address(this), auction.marketDebt[auction.markets[i]]);
}
uint256 balanceAfter = erc20.balanceOf(address(this));
auction.bidAmount[auction.markets[i]] = balanceAfter - balanceBefore;
}
auction.highestBidder = msg.sender;
auction.highestBidBps = bidBps;
auction.highestBidBlockOrTimestamp = getBlockNumberOrTimestamp();
emit BidPlaced(comptroller, auction.startBlockOrTimestamp, bidBps, msg.sender);
}
/**
* @notice Close an auction
* @param comptroller Comptroller address of the pool
* @custom:event Emits AuctionClosed event on successful close
*/
function closeAuction(address comptroller) external nonReentrant {
Auction storage auction = auctions[comptroller];
require(_isStarted(auction), "no on-going auction");
require(
getBlockNumberOrTimestamp() > auction.highestBidBlockOrTimestamp + nextBidderBlockLimit &&
auction.highestBidder != address(0),
"waiting for next bidder. cannot close auction"
);
uint256 marketsCount = auction.markets.length;
uint256[] memory marketsDebt = new uint256[](marketsCount);
auction.status = AuctionStatus.ENDED;
for (uint256 i; i < marketsCount; ++i) {
VToken vToken = VToken(address(auction.markets[i]));
IERC20Upgradeable erc20 = IERC20Upgradeable(address(vToken.underlying()));
uint256 balanceBefore = erc20.balanceOf(address(auction.markets[i]));
erc20.safeTransfer(address(auction.markets[i]), auction.bidAmount[auction.markets[i]]);
uint256 balanceAfter = erc20.balanceOf(address(auction.markets[i]));
marketsDebt[i] = balanceAfter - balanceBefore;
auction.markets[i].badDebtRecovered(marketsDebt[i]);
}
uint256 riskFundBidAmount;
if (auction.auctionType == AuctionType.LARGE_POOL_DEBT) {
riskFundBidAmount = auction.seizedRiskFund;
} else {
riskFundBidAmount = (auction.seizedRiskFund * auction.highestBidBps) / MAX_BPS;
}
address convertibleBaseAsset = riskFund.convertibleBaseAsset();
uint256 transferredAmount = riskFund.transferReserveForAuction(comptroller, riskFundBidAmount);
_transferOutOrTrackDebt(IERC20Upgradeable(convertibleBaseAsset), auction.highestBidder, riskFundBidAmount);
emit AuctionClosed(
comptroller,
auction.startBlockOrTimestamp,
auction.highestBidder,
auction.highestBidBps,
transferredAmount,
auction.markets,
marketsDebt
);
}
/**
* @notice Start a auction when there is not currently one active
* @param comptroller Comptroller address of the pool
* @custom:event Emits AuctionStarted event on success
* @custom:event Errors if auctions are paused
*/
function startAuction(address comptroller) external nonReentrant {
require(!auctionsPaused, "Auctions are paused");
_startAuction(comptroller);
}
/**
* @notice Restart an auction
* @param comptroller Address of the pool
* @custom:event Emits AuctionRestarted event on successful restart
*/
function restartAuction(address comptroller) external nonReentrant {
Auction storage auction = auctions[comptroller];
require(!auctionsPaused, "auctions are paused");
require(_isStarted(auction), "no on-going auction");
require(_isStale(auction), "you need to wait for more time for first bidder");
auction.status = AuctionStatus.ENDED;
emit AuctionRestarted(comptroller, auction.startBlockOrTimestamp);
_startAuction(comptroller);
}
/**
* @notice Update next bidder block or timestamp limit which is used determine when an auction can be closed
* @param nextBidderBlockOrTimestampLimit_ New next bidder slot (block or second) limit
* @custom:event Emits NextBidderBlockLimitUpdated on success
* @custom:access Restricted by ACM
*/
function updateNextBidderBlockLimit(uint256 nextBidderBlockOrTimestampLimit_) external {
_checkAccessAllowed("updateNextBidderBlockLimit(uint256)");
require(nextBidderBlockOrTimestampLimit_ != 0, "nextBidderBlockOrTimestampLimit_ must not be 0");
emit NextBidderBlockLimitUpdated(nextBidderBlockLimit, nextBidderBlockOrTimestampLimit_);
nextBidderBlockLimit = nextBidderBlockOrTimestampLimit_;
}
/**
* @notice Updates the incentive BPS
* @param incentiveBps_ New incentive BPS
* @custom:event Emits IncentiveBpsUpdated on success
* @custom:access Restricted by ACM
*/
function updateIncentiveBps(uint256 incentiveBps_) external {
_checkAccessAllowed("updateIncentiveBps(uint256)");
require(incentiveBps_ != 0, "incentiveBps must not be 0");
uint256 oldIncentiveBps = incentiveBps;
incentiveBps = incentiveBps_;
emit IncentiveBpsUpdated(oldIncentiveBps, incentiveBps_);
}
/**
* @notice Update minimum pool bad debt to start auction
* @param minimumPoolBadDebt_ Minimum bad debt in the base asset for a pool to start auction
* @custom:event Emits MinimumPoolBadDebtUpdated on success
* @custom:access Restricted by ACM
*/
function updateMinimumPoolBadDebt(uint256 minimumPoolBadDebt_) external {
_checkAccessAllowed("updateMinimumPoolBadDebt(uint256)");
uint256 oldMinimumPoolBadDebt = minimumPoolBadDebt;
minimumPoolBadDebt = minimumPoolBadDebt_;
emit MinimumPoolBadDebtUpdated(oldMinimumPoolBadDebt, minimumPoolBadDebt_);
}
/**
* @notice Update wait for first bidder block or timestamp count. If the first bid is not made within this limit, the auction is closed and needs to be restarted
* @param waitForFirstBidder_ New wait for first bidder block or timestamp count
* @custom:event Emits WaitForFirstBidderUpdated on success
* @custom:access Restricted by ACM
*/
function updateWaitForFirstBidder(uint256 waitForFirstBidder_) external {
_checkAccessAllowed("updateWaitForFirstBidder(uint256)");
uint256 oldWaitForFirstBidder = waitForFirstBidder;
waitForFirstBidder = waitForFirstBidder_;
emit WaitForFirstBidderUpdated(oldWaitForFirstBidder, waitForFirstBidder_);
}
/**
* @notice Update the pool registry this shortfall supports
* @dev After Pool Registry is deployed we need to set the pool registry address
* @param poolRegistry_ Address of pool registry contract
* @custom:event Emits PoolRegistryUpdated on success
* @custom:access Restricted to owner
* @custom:error ZeroAddressNotAllowed is thrown when pool registry address is zero
*/
function updatePoolRegistry(address poolRegistry_) external onlyOwner {
ensureNonzeroAddress(poolRegistry_);
address oldPoolRegistry = poolRegistry;
poolRegistry = poolRegistry_;
emit PoolRegistryUpdated(oldPoolRegistry, poolRegistry_);
}
/**
* @notice Pause auctions. This disables starting new auctions but lets the current auction finishes
* @custom:event Emits AuctionsPaused on success
* @custom:error Errors is auctions are paused
* @custom:access Restricted by ACM
*/
function pauseAuctions() external {
_checkAccessAllowed("pauseAuctions()");
require(!auctionsPaused, "Auctions are already paused");
auctionsPaused = true;
emit AuctionsPaused(msg.sender);
}
/**
* @notice Resume paused auctions.
* @custom:event Emits AuctionsResumed on success
* @custom:error Errors is auctions are active
* @custom:access Restricted by ACM
*/
function resumeAuctions() external {
_checkAccessAllowed("resumeAuctions()");
require(auctionsPaused, "Auctions are not paused");
auctionsPaused = false;
emit AuctionsResumed(msg.sender);
}
/**
* @notice Start a auction when there is not currently one active
* @param comptroller Comptroller address of the pool
*/
function _startAuction(address comptroller) internal {
PoolRegistryInterface.VenusPool memory pool = PoolRegistry(poolRegistry).getPoolByComptroller(comptroller);
require(pool.comptroller == comptroller, "comptroller doesn't exist pool registry");
Auction storage auction = auctions[comptroller];
require(
auction.status == AuctionStatus.NOT_STARTED || auction.status == AuctionStatus.ENDED,
"auction is on-going"
);
auction.highestBidBps = 0;
auction.highestBidBlockOrTimestamp = 0;
uint256 marketsCount = auction.markets.length;
for (uint256 i; i < marketsCount; ++i) {
VToken vToken = auction.markets[i];
auction.marketDebt[vToken] = 0;
}
delete auction.markets;
VToken[] memory vTokens = _getAllMarkets(comptroller);
marketsCount = vTokens.length;
ResilientOracleInterface priceOracle = _getPriceOracle(comptroller);
uint256 poolBadDebt;
uint256[] memory marketsDebt = new uint256[](marketsCount);
auction.markets = new VToken[](marketsCount);
for (uint256 i; i < marketsCount; ++i) {
uint256 marketBadDebt = vTokens[i].badDebt();
priceOracle.updatePrice(address(vTokens[i]));
uint256 usdValue = (priceOracle.getUnderlyingPrice(address(vTokens[i])) * marketBadDebt) / EXP_SCALE;
poolBadDebt = poolBadDebt + usdValue;
auction.markets[i] = vTokens[i];
auction.marketDebt[vTokens[i]] = marketBadDebt;
marketsDebt[i] = marketBadDebt;
}
require(poolBadDebt >= minimumPoolBadDebt, "pool bad debt is too low");
priceOracle.updateAssetPrice(riskFund.convertibleBaseAsset());
uint256 riskFundBalance = (priceOracle.getPrice(riskFund.convertibleBaseAsset()) *
riskFund.getPoolsBaseAssetReserves(comptroller)) / EXP_SCALE;
uint256 remainingRiskFundBalance = riskFundBalance;
uint256 badDebtPlusIncentive = poolBadDebt + ((poolBadDebt * incentiveBps) / MAX_BPS);
if (badDebtPlusIncentive >= riskFundBalance) {
auction.startBidBps =
(MAX_BPS * MAX_BPS * remainingRiskFundBalance) /
(poolBadDebt * (MAX_BPS + incentiveBps));
remainingRiskFundBalance = 0;
auction.auctionType = AuctionType.LARGE_POOL_DEBT;
} else {
uint256 maxSeizeableRiskFundBalance = badDebtPlusIncentive;
remainingRiskFundBalance = remainingRiskFundBalance - maxSeizeableRiskFundBalance;
auction.auctionType = AuctionType.LARGE_RISK_FUND;
auction.startBidBps = MAX_BPS;
}
auction.seizedRiskFund = riskFundBalance - remainingRiskFundBalance;
auction.startBlockOrTimestamp = getBlockNumberOrTimestamp();
auction.status = AuctionStatus.STARTED;
auction.highestBidder = address(0);
emit AuctionStarted(
comptroller,
auction.startBlockOrTimestamp,
auction.auctionType,
auction.markets,
marketsDebt,
auction.seizedRiskFund,
auction.startBidBps
);
}
/**
* @dev Returns the price oracle of the pool
* @param comptroller Address of the pool's comptroller
* @return oracle The pool's price oracle
*/
function _getPriceOracle(address comptroller) internal view returns (ResilientOracleInterface) {
return ResilientOracleInterface(ComptrollerViewInterface(comptroller).oracle());
}
/**
* @dev Returns all markets of the pool
* @param comptroller Address of the pool's comptroller
* @return markets The pool's markets as VToken array
*/
function _getAllMarkets(address comptroller) internal view returns (VToken[] memory) {
return ComptrollerInterface(comptroller).getAllMarkets();
}
/**
* @dev Checks if the auction has started
* @param auction The auction to query the status for
* @return True if the auction has started
*/
function _isStarted(Auction storage auction) internal view returns (bool) {
return auction.status == AuctionStatus.STARTED;
}
/**
* @dev Checks if the auction is stale, i.e. there's no bidder and the auction
* was started more than waitForFirstBidder blocks or seconds ago.
* @param auction The auction to query the status for
* @return True if the auction is stale
*/
function _isStale(Auction storage auction) internal view returns (bool) {
bool noBidder = auction.highestBidder == address(0);
return noBidder && (getBlockNumberOrTimestamp() > auction.startBlockOrTimestamp + waitForFirstBidder);
}
}// SPDX-License-Identifier: BSD-3-Clause
pragma solidity 0.8.25;
import { VToken } from "../VToken.sol";
import { IRiskFund } from "../Shortfall/IRiskFund.sol";
/**
* @title ShortfallStorage
* @author Venus
* @dev Storage for Shortfall
*/
contract ShortfallStorage {
/// @notice Type of auction
enum AuctionType {
LARGE_POOL_DEBT,
LARGE_RISK_FUND
}
/// @notice Status of auction
enum AuctionStatus {
NOT_STARTED,
STARTED,
ENDED
}
/// @notice Auction metadata
struct Auction {
/// @notice It holds either the starting block number or timestamp
uint256 startBlockOrTimestamp;
AuctionType auctionType;
AuctionStatus status;
VToken[] markets;
uint256 seizedRiskFund;
address highestBidder;
uint256 highestBidBps;
/// @notice It holds either the highestBid block or timestamp
uint256 highestBidBlockOrTimestamp;
uint256 startBidBps;
mapping(VToken => uint256) marketDebt;
mapping(VToken => uint256) bidAmount;
}
/// @notice Pool registry address
address public poolRegistry;
/// @notice Risk fund address
IRiskFund public riskFund;
/// @notice Minimum USD debt in pool for shortfall to trigger
uint256 public minimumPoolBadDebt;
/// @notice Incentive to auction participants, initial value set to 1000 or 10%
uint256 public incentiveBps;
/// @notice Time to wait for next bidder. Initially waits for DEFAULT_NEXT_BIDDER_BLOCK_OR_TIMESTAMP_LIMIT
uint256 public nextBidderBlockLimit;
/// @notice Boolean of if auctions are paused
bool public auctionsPaused;
/// @notice Time to wait for first bidder. Initially waits for DEFAULT_WAIT_FOR_FIRST_BIDDER
uint256 public waitForFirstBidder;
/// @notice Auctions for each pool
mapping(address => Auction) public auctions;
/**
* @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[42] private __gap;
}// SPDX-License-Identifier: BSD-3-Clause
pragma solidity 0.8.25;
import { Ownable2StepUpgradeable } from "@openzeppelin/contracts-upgradeable/access/Ownable2StepUpgradeable.sol";
import { IERC20Upgradeable } from "@openzeppelin/contracts-upgradeable/token/ERC20/IERC20Upgradeable.sol";
import { SafeERC20Upgradeable } from "@openzeppelin/contracts-upgradeable/token/ERC20/utils/SafeERC20Upgradeable.sol";
import { AccessControlledV8 } from "@venusprotocol/governance-contracts/contracts/Governance/AccessControlledV8.sol";
import { IProtocolShareReserve } from "@venusprotocol/protocol-reserve/contracts/Interfaces/IProtocolShareReserve.sol";
import { VTokenInterface } from "./VTokenInterfaces.sol";
import { ComptrollerInterface, ComptrollerViewInterface } from "./ComptrollerInterface.sol";
import { TokenErrorReporter } from "./ErrorReporter.sol";
import { InterestRateModel } from "./InterestRateModel.sol";
import { ExponentialNoError } from "./ExponentialNoError.sol";
import { TimeManagerV8 } from "@venusprotocol/solidity-utilities/contracts/TimeManagerV8.sol";
import { ensureNonzeroAddress } from "./lib/validators.sol";
/**
* @title VToken
* @author Venus
* @notice Each asset that is supported by a pool is integrated through an instance of the `VToken` contract. As outlined in the protocol overview,
* each isolated pool creates its own `vToken` corresponding to an asset. Within a given pool, each included `vToken` is referred to as a market of
* the pool. The main actions a user regularly interacts with in a market are:
- mint/redeem of vTokens;
- transfer of vTokens;
- borrow/repay a loan on an underlying asset;
- liquidate a borrow or liquidate/heal an account.
* A user supplies the underlying asset to a pool by minting `vTokens`, where the corresponding `vToken` amount is determined by the `exchangeRate`.
* The `exchangeRate` will change over time, dependent on a number of factors, some of which accrue interest. Additionally, once users have minted
* `vToken` in a pool, they can borrow any asset in the isolated pool by using their `vToken` as collateral. In order to borrow an asset or use a `vToken`
* as collateral, the user must be entered into each corresponding market (else, the `vToken` will not be considered collateral for a borrow). Note that
* a user may borrow up to a portion of their collateral determined by the market’s collateral factor. However, if their borrowed amount exceeds an amount
* calculated using the market’s corresponding liquidation threshold, the borrow is eligible for liquidation. When a user repays a borrow, they must also
* pay off interest accrued on the borrow.
*
* The Venus protocol includes unique mechanisms for healing an account and liquidating an account. These actions are performed in the `Comptroller`
* and consider all borrows and collateral for which a given account is entered within a market. These functions may only be called on an account with a
* total collateral amount that is no larger than a universal `minLiquidatableCollateral` value, which is used for all markets within a `Comptroller`.
* Both functions settle all of an account’s borrows, but `healAccount()` may add `badDebt` to a vToken. For more detail, see the description of
* `healAccount()` and `liquidateAccount()` in the `Comptroller` summary section below.
*/
contract VToken is
Ownable2StepUpgradeable,
AccessControlledV8,
VTokenInterface,
ExponentialNoError,
TokenErrorReporter,
TimeManagerV8
{
using SafeERC20Upgradeable for IERC20Upgradeable;
uint256 internal constant DEFAULT_PROTOCOL_SEIZE_SHARE_MANTISSA = 5e16; // 5%
// Maximum fraction of interest that can be set aside for reserves
uint256 internal constant MAX_RESERVE_FACTOR_MANTISSA = 1e18;
// Maximum borrow rate that can ever be applied per slot(block or second)
/// @custom:oz-upgrades-unsafe-allow state-variable-immutable
uint256 internal immutable MAX_BORROW_RATE_MANTISSA;
/**
* Reentrancy Guard **
*/
/**
* @dev Prevents a contract from calling itself, directly or indirectly.
*/
modifier nonReentrant() {
require(_notEntered, "re-entered");
_notEntered = false;
_;
_notEntered = true; // get a gas-refund post-Istanbul
}
/**
* @param timeBased_ A boolean indicating whether the contract is based on time or block.
* @param blocksPerYear_ The number of blocks per year
* @param maxBorrowRateMantissa_ The maximum value of borrowing rate mantissa
* @custom:oz-upgrades-unsafe-allow constructor
*/
constructor(
bool timeBased_,
uint256 blocksPerYear_,
uint256 maxBorrowRateMantissa_
) TimeManagerV8(timeBased_, blocksPerYear_) {
// Note that the contract is upgradeable. Use initialize() or reinitializers
// to set the state variables.
require(maxBorrowRateMantissa_ <= 1e18, "Max borrow rate must be <= 1e18");
MAX_BORROW_RATE_MANTISSA = maxBorrowRateMantissa_;
_disableInitializers();
}
/**
* @notice Construct a new money market
* @param underlying_ The address of the underlying asset
* @param comptroller_ The address of the Comptroller
* @param interestRateModel_ The address of the interest rate model
* @param initialExchangeRateMantissa_ The initial exchange rate, scaled by 1e18
* @param name_ ERC-20 name of this token
* @param symbol_ ERC-20 symbol of this token
* @param decimals_ ERC-20 decimal precision of this token
* @param admin_ Address of the administrator of this token
* @param accessControlManager_ AccessControlManager contract address
* @param riskManagement Addresses of risk & income related contracts
* @param reserveFactorMantissa_ Percentage of borrow interest that goes to reserves (from 0 to 1e18)
* @custom:error ZeroAddressNotAllowed is thrown when admin address is zero
* @custom:error ZeroAddressNotAllowed is thrown when shortfall contract address is zero
* @custom:error ZeroAddressNotAllowed is thrown when protocol share reserve address is zero
*/
function initialize(
address underlying_,
ComptrollerInterface comptroller_,
InterestRateModel interestRateModel_,
uint256 initialExchangeRateMantissa_,
string memory name_,
string memory symbol_,
uint8 decimals_,
address admin_,
address accessControlManager_,
RiskManagementInit memory riskManagement,
uint256 reserveFactorMantissa_
) external initializer {
ensureNonzeroAddress(admin_);
// Initialize the market
_initialize(
underlying_,
comptroller_,
interestRateModel_,
initialExchangeRateMantissa_,
name_,
symbol_,
decimals_,
admin_,
accessControlManager_,
riskManagement,
reserveFactorMantissa_
);
}
/**
* @notice Transfer `amount` tokens from `msg.sender` to `dst`
* @param dst The address of the destination account
* @param amount The number of tokens to transfer
* @return success True if the transfer succeeded, reverts otherwise
* @custom:event Emits Transfer event on success
* @custom:error TransferNotAllowed is thrown if trying to transfer to self
* @custom:access Not restricted
*/
function transfer(address dst, uint256 amount) external override nonReentrant returns (bool) {
_transferTokens(msg.sender, msg.sender, dst, amount);
return true;
}
/**
* @notice Transfer `amount` tokens from `src` to `dst`
* @param src The address of the source account
* @param dst The address of the destination account
* @param amount The number of tokens to transfer
* @return success True if the transfer succeeded, reverts otherwise
* @custom:event Emits Transfer event on success
* @custom:error TransferNotAllowed is thrown if trying to transfer to self
* @custom:access Not restricted
*/
function transferFrom(address src, address dst, uint256 amount) external override nonReentrant returns (bool) {
_transferTokens(msg.sender, src, dst, amount);
return true;
}
/**
* @notice Approve `spender` to transfer up to `amount` from `src`
* @dev This will overwrite the approval amount for `spender`
* and is subject to issues noted [here](https://eips.ethereum.org/EIPS/eip-20#approve)
* @param spender The address of the account which may transfer tokens
* @param amount The number of tokens that are approved (uint256.max means infinite)
* @return success Whether or not the approval succeeded
* @custom:event Emits Approval event
* @custom:access Not restricted
* @custom:error ZeroAddressNotAllowed is thrown when spender address is zero
*/
function approve(address spender, uint256 amount) external override returns (bool) {
ensureNonzeroAddress(spender);
address src = msg.sender;
transferAllowances[src][spender] = amount;
emit Approval(src, spender, amount);
return true;
}
/**
* @notice Increase approval for `spender`
* @param spender The address of the account which may transfer tokens
* @param addedValue The number of additional tokens spender can transfer
* @return success Whether or not the approval succeeded
* @custom:event Emits Approval event
* @custom:access Not restricted
* @custom:error ZeroAddressNotAllowed is thrown when spender address is zero
*/
function increaseAllowance(address spender, uint256 addedValue) external override returns (bool) {
ensureNonzeroAddress(spender);
address src = msg.sender;
uint256 newAllowance = transferAllowances[src][spender];
newAllowance += addedValue;
transferAllowances[src][spender] = newAllowance;
emit Approval(src, spender, newAllowance);
return true;
}
/**
* @notice Decreases approval for `spender`
* @param spender The address of the account which may transfer tokens
* @param subtractedValue The number of tokens to remove from total approval
* @return success Whether or not the approval succeeded
* @custom:event Emits Approval event
* @custom:access Not restricted
* @custom:error ZeroAddressNotAllowed is thrown when spender address is zero
*/
function decreaseAllowance(address spender, uint256 subtractedValue) external override returns (bool) {
ensureNonzeroAddress(spender);
address src = msg.sender;
uint256 currentAllowance = transferAllowances[src][spender];
require(currentAllowance >= subtractedValue, "decreased allowance below zero");
unchecked {
currentAllowance -= subtractedValue;
}
transferAllowances[src][spender] = currentAllowance;
emit Approval(src, spender, currentAllowance);
return true;
}
/**
* @notice Get the underlying balance of the `owner`
* @dev This also accrues interest in a transaction
* @param owner The address of the account to query
* @return amount The amount of underlying owned by `owner`
*/
function balanceOfUnderlying(address owner) external override returns (uint256) {
Exp memory exchangeRate = Exp({ mantissa: exchangeRateCurrent() });
return mul_ScalarTruncate(exchangeRate, accountTokens[owner]);
}
/**
* @notice Returns the current total borrows plus accrued interest
* @return totalBorrows The total borrows with interest
*/
function totalBorrowsCurrent() external override nonReentrant returns (uint256) {
accrueInterest();
return totalBorrows;
}
/**
* @notice Accrue interest to updated borrowIndex and then calculate account's borrow balance using the updated borrowIndex
* @param account The address whose balance should be calculated after updating borrowIndex
* @return borrowBalance The calculated balance
*/
function borrowBalanceCurrent(address account) external override nonReentrant returns (uint256) {
accrueInterest();
return _borrowBalanceStored(account);
}
/**
* @notice Sender supplies assets into the market and receives vTokens in exchange
* @dev Accrues interest whether or not the operation succeeds, unless reverted
* @param mintAmount The amount of the underlying asset to supply
* @return error Always NO_ERROR for compatibility with Venus core tooling
* @custom:event Emits Mint and Transfer events; may emit AccrueInterest
* @custom:access Not restricted
*/
function mint(uint256 mintAmount) external override nonReentrant returns (uint256) {
accrueInterest();
_mintFresh(msg.sender, msg.sender, mintAmount);
return NO_ERROR;
}
/**
* @notice Sender calls on-behalf of minter. minter supplies assets into the market and receives vTokens in exchange
* @dev Accrues interest whether or not the operation succeeds, unless reverted
* @param minter User whom the supply will be attributed to
* @param mintAmount The amount of the underlying asset to supply
* @return error Always NO_ERROR for compatibility with Venus core tooling
* @custom:event Emits Mint and Transfer events; may emit AccrueInterest
* @custom:access Not restricted
* @custom:error ZeroAddressNotAllowed is thrown when minter address is zero
*/
function mintBehalf(address minter, uint256 mintAmount) external override nonReentrant returns (uint256) {
ensureNonzeroAddress(minter);
accrueInterest();
_mintFresh(msg.sender, minter, mintAmount);
return NO_ERROR;
}
/**
* @notice Sender redeems vTokens in exchange for the underlying asset
* @dev Accrues interest whether or not the operation succeeds, unless reverted
* @param redeemTokens The number of vTokens to redeem into underlying
* @return error Always NO_ERROR for compatibility with Venus core tooling
* @custom:event Emits Redeem and Transfer events; may emit AccrueInterest
* @custom:error RedeemTransferOutNotPossible is thrown when the protocol has insufficient cash
* @custom:access Not restricted
*/
function redeem(uint256 redeemTokens) external override nonReentrant returns (uint256) {
accrueInterest();
_redeemFresh(msg.sender, msg.sender, redeemTokens, 0);
return NO_ERROR;
}
/**
* @notice Sender redeems assets on behalf of some other address. This function is only available
* for senders, explicitly marked as delegates of the supplier using `comptroller.updateDelegate`
* @dev Accrues interest whether or not the operation succeeds, unless reverted
* @param redeemer The user on behalf of whom to redeem
* @param redeemTokens The number of vTokens to redeem into underlying
* @return error Always NO_ERROR for compatibility with Venus core tooling
* @custom:error InsufficientRedeemApproval is thrown when sender is not approved by the redeemer for the given amount
* @custom:error RedeemTransferOutNotPossible is thrown when the protocol has insufficient cash
* @custom:event Emits Redeem and Transfer events; may emit AccrueInterest
* @custom:access Not restricted
*/
function redeemBehalf(address redeemer, uint256 redeemTokens) external override nonReentrant returns (uint256) {
_ensureSenderIsDelegateOf(redeemer);
accrueInterest();
_redeemFresh(redeemer, msg.sender, redeemTokens, 0);
return NO_ERROR;
}
/**
* @notice Sender redeems vTokens in exchange for a specified amount of underlying asset
* @dev Accrues interest whether or not the operation succeeds, unless reverted
* @param redeemAmount The amount of underlying to receive from redeeming vTokens
* @return error Always NO_ERROR for compatibility with Venus core tooling
*/
function redeemUnderlying(uint256 redeemAmount) external override nonReentrant returns (uint256) {
accrueInterest();
_redeemFresh(msg.sender, msg.sender, 0, redeemAmount);
return NO_ERROR;
}
/**
* @notice Sender redeems underlying assets on behalf of some other address. This function is only available
* for senders, explicitly marked as delegates of the supplier using `comptroller.updateDelegate`
* @dev Accrues interest whether or not the operation succeeds, unless reverted
* @param redeemer, on behalf of whom to redeem
* @param redeemAmount The amount of underlying to receive from redeeming vTokens
* @return error Always NO_ERROR for compatibility with Venus core tooling
* @custom:error InsufficientRedeemApproval is thrown when sender is not approved by the redeemer for the given amount
* @custom:event Emits Redeem and Transfer events; may emit AccrueInterest
* @custom:access Not restricted
*/
function redeemUnderlyingBehalf(
address redeemer,
uint256 redeemAmount
) external override nonReentrant returns (uint256) {
_ensureSenderIsDelegateOf(redeemer);
accrueInterest();
_redeemFresh(redeemer, msg.sender, 0, redeemAmount);
return NO_ERROR;
}
/**
* @notice Sender borrows assets from the protocol to their own address
* @param borrowAmount The amount of the underlying asset to borrow
* @return error Always NO_ERROR for compatibility with Venus core tooling
* @custom:event Emits Borrow event; may emit AccrueInterest
* @custom:error BorrowCashNotAvailable is thrown when the protocol has insufficient cash
* @custom:access Not restricted
*/
function borrow(uint256 borrowAmount) external override nonReentrant returns (uint256) {
accrueInterest();
_borrowFresh(msg.sender, msg.sender, borrowAmount);
return NO_ERROR;
}
/**
* @notice Sender borrows assets on behalf of some other address. This function is only available
* for senders, explicitly marked as delegates of the borrower using `comptroller.updateDelegate`
* @param borrower The borrower, on behalf of whom to borrow
* @param borrowAmount The amount of the underlying asset to borrow
* @return error Always NO_ERROR for compatibility with Venus core tooling
* @custom:error DelegateNotApproved is thrown if caller is not approved delegate
* @custom:error BorrowCashNotAvailable is thrown when the protocol has insufficient cash
* @custom:event Emits Borrow event; may emit AccrueInterest
* @custom:access Not restricted
*/
function borrowBehalf(address borrower, uint256 borrowAmount) external override returns (uint256) {
_ensureSenderIsDelegateOf(borrower);
accrueInterest();
_borrowFresh(borrower, msg.sender, borrowAmount);
return NO_ERROR;
}
/**
* @notice Sender repays their own borrow
* @param repayAmount The amount to repay, or type(uint256).max for the full outstanding amount
* @return error Always NO_ERROR for compatibility with Venus core tooling
* @custom:event Emits RepayBorrow event; may emit AccrueInterest
* @custom:access Not restricted
*/
function repayBorrow(uint256 repayAmount) external override nonReentrant returns (uint256) {
accrueInterest();
_repayBorrowFresh(msg.sender, msg.sender, repayAmount);
return NO_ERROR;
}
/**
* @notice Sender repays a borrow belonging to borrower
* @param borrower the account with the debt being payed off
* @param repayAmount The amount to repay, or type(uint256).max for the full outstanding amount
* @return error Always NO_ERROR for compatibility with Venus core tooling
* @custom:event Emits RepayBorrow event; may emit AccrueInterest
* @custom:access Not restricted
*/
function repayBorrowBehalf(address borrower, uint256 repayAmount) external override nonReentrant returns (uint256) {
accrueInterest();
_repayBorrowFresh(msg.sender, borrower, repayAmount);
return NO_ERROR;
}
/**
* @notice The sender liquidates the borrowers collateral.
* The collateral seized is transferred to the liquidator.
* @param borrower The borrower of this vToken to be liquidated
* @param repayAmount The amount of the underlying borrowed asset to repay
* @param vTokenCollateral The market in which to seize collateral from the borrower
* @return error Always NO_ERROR for compatibility with Venus core tooling
* @custom:event Emits LiquidateBorrow event; may emit AccrueInterest
* @custom:error LiquidateAccrueCollateralInterestFailed is thrown when it is not possible to accrue interest on the collateral vToken
* @custom:error LiquidateCollateralFreshnessCheck is thrown when interest has not been accrued on the collateral vToken
* @custom:error LiquidateLiquidatorIsBorrower is thrown when trying to liquidate self
* @custom:error LiquidateCloseAmountIsZero is thrown when repayment amount is zero
* @custom:error LiquidateCloseAmountIsUintMax is thrown when repayment amount is UINT_MAX
* @custom:access Not restricted
*/
function liquidateBorrow(
address borrower,
uint256 repayAmount,
VTokenInterface vTokenCollateral
) external override returns (uint256) {
_liquidateBorrow(msg.sender, borrower, repayAmount, vTokenCollateral, false);
return NO_ERROR;
}
/**
* @notice sets protocol share accumulated from liquidations
* @dev must be equal or less than liquidation incentive - 1
* @param newProtocolSeizeShareMantissa_ new protocol share mantissa
* @custom:event Emits NewProtocolSeizeShare event on success
* @custom:error Unauthorized error is thrown when the call is not authorized by AccessControlManager
* @custom:error ProtocolSeizeShareTooBig is thrown when the new seize share is too high
* @custom:access Controlled by AccessControlManager
*/
function setProtocolSeizeShare(uint256 newProtocolSeizeShareMantissa_) external {
_checkAccessAllowed("setProtocolSeizeShare(uint256)");
uint256 liquidationIncentive = ComptrollerViewInterface(address(comptroller)).liquidationIncentiveMantissa();
if (newProtocolSeizeShareMantissa_ + MANTISSA_ONE > liquidationIncentive) {
revert ProtocolSeizeShareTooBig();
}
uint256 oldProtocolSeizeShareMantissa = protocolSeizeShareMantissa;
protocolSeizeShareMantissa = newProtocolSeizeShareMantissa_;
emit NewProtocolSeizeShare(oldProtocolSeizeShareMantissa, newProtocolSeizeShareMantissa_);
}
/**
* @notice accrues interest and sets a new reserve factor for the protocol using _setReserveFactorFresh
* @dev Admin function to accrue interest and set a new reserve factor
* @param newReserveFactorMantissa New reserve factor (from 0 to 1e18)
* @custom:event Emits NewReserveFactor event; may emit AccrueInterest
* @custom:error Unauthorized error is thrown when the call is not authorized by AccessControlManager
* @custom:error SetReserveFactorBoundsCheck is thrown when the new reserve factor is too high
* @custom:access Controlled by AccessControlManager
*/
function setReserveFactor(uint256 newReserveFactorMantissa) external override nonReentrant {
_checkAccessAllowed("setReserveFactor(uint256)");
accrueInterest();
_setReserveFactorFresh(newReserveFactorMantissa);
}
/**
* @notice Accrues interest and reduces reserves by transferring to the protocol reserve contract
* @dev Gracefully return if reserves already reduced in accrueInterest
* @param reduceAmount Amount of reduction to reserves
* @custom:event Emits ReservesReduced event; may emit AccrueInterest
* @custom:error ReduceReservesCashNotAvailable is thrown when the vToken does not have sufficient cash
* @custom:error ReduceReservesCashValidation is thrown when trying to withdraw more cash than the reserves have
* @custom:access Not restricted
*/
function reduceReserves(uint256 reduceAmount) external override nonReentrant {
accrueInterest();
if (reduceReservesBlockNumber == getBlockNumberOrTimestamp()) return;
_reduceReservesFresh(reduceAmount);
}
/**
* @notice The sender adds to reserves.
* @param addAmount The amount of underlying token to add as reserves
* @custom:event Emits ReservesAdded event; may emit AccrueInterest
* @custom:access Not restricted
*/
function addReserves(uint256 addAmount) external override nonReentrant {
accrueInterest();
_addReservesFresh(addAmount);
}
/**
* @notice accrues interest and updates the interest rate model using _setInterestRateModelFresh
* @dev Admin function to accrue interest and update the interest rate model
* @param newInterestRateModel the new interest rate model to use
* @custom:event Emits NewMarketInterestRateModel event; may emit AccrueInterest
* @custom:error Unauthorized error is thrown when the call is not authorized by AccessControlManager
* @custom:access Controlled by AccessControlManager
*/
function setInterestRateModel(InterestRateModel newInterestRateModel) external override {
_checkAccessAllowed("setInterestRateModel(address)");
accrueInterest();
_setInterestRateModelFresh(newInterestRateModel);
}
/**
* @notice Repays a certain amount of debt, treats the rest of the borrow as bad debt, essentially
* "forgiving" the borrower. Healing is a situation that should rarely happen. However, some pools
* may list risky assets or be configured improperly – we want to still handle such cases gracefully.
* We assume that Comptroller does the seizing, so this function is only available to Comptroller.
* @dev This function does not call any Comptroller hooks (like "healAllowed"), because we assume
* the Comptroller does all the necessary checks before calling this function.
* @param payer account who repays the debt
* @param borrower account to heal
* @param repayAmount amount to repay
* @custom:event Emits RepayBorrow, BadDebtIncreased events; may emit AccrueInterest
* @custom:error HealBorrowUnauthorized is thrown when the request does not come from Comptroller
* @custom:access Only Comptroller
*/
function healBorrow(address payer, address borrower, uint256 repayAmount) external override nonReentrant {
if (repayAmount != 0) {
comptroller.preRepayHook(address(this), borrower);
}
if (msg.sender != address(comptroller)) {
revert HealBorrowUnauthorized();
}
uint256 accountBorrowsPrev = _borrowBalanceStored(borrower);
uint256 totalBorrowsNew = totalBorrows;
uint256 actualRepayAmount;
if (repayAmount != 0) {
// _doTransferIn reverts if anything goes wrong, since we can't be sure if side effects occurred.
// We violate checks-effects-interactions here to account for tokens that take transfer fees
actualRepayAmount = _doTransferIn(payer, repayAmount);
totalBorrowsNew = totalBorrowsNew - actualRepayAmount;
emit RepayBorrow(
payer,
borrower,
actualRepayAmount,
accountBorrowsPrev - actualRepayAmount,
totalBorrowsNew
);
}
// The transaction will fail if trying to repay too much
uint256 badDebtDelta = accountBorrowsPrev - actualRepayAmount;
if (badDebtDelta != 0) {
uint256 badDebtOld = badDebt;
uint256 badDebtNew = badDebtOld + badDebtDelta;
totalBorrowsNew = totalBorrowsNew - badDebtDelta;
badDebt = badDebtNew;
// We treat healing as "repayment", where vToken is the payer
emit RepayBorrow(address(this), borrower, badDebtDelta, 0, totalBorrowsNew);
emit BadDebtIncreased(borrower, badDebtDelta, badDebtOld, badDebtNew);
}
accountBorrows[borrower].principal = 0;
accountBorrows[borrower].interestIndex = borrowIndex;
totalBorrows = totalBorrowsNew;
emit HealBorrow(payer, borrower, repayAmount);
}
/**
* @notice The extended version of liquidations, callable only by Comptroller. May skip
* the close factor check. The collateral seized is transferred to the liquidator.
* @param liquidator The address repaying the borrow and seizing collateral
* @param borrower The borrower of this vToken to be liquidated
* @param repayAmount The amount of the underlying borrowed asset to repay
* @param vTokenCollateral The market in which to seize collateral from the borrower
* @param skipLiquidityCheck If set to true, allows to liquidate up to 100% of the borrow
* regardless of the account liquidity
* @custom:event Emits LiquidateBorrow event; may emit AccrueInterest
* @custom:error ForceLiquidateBorrowUnauthorized is thrown when the request does not come from Comptroller
* @custom:error LiquidateAccrueCollateralInterestFailed is thrown when it is not possible to accrue interest on the collateral vToken
* @custom:error LiquidateCollateralFreshnessCheck is thrown when interest has not been accrued on the collateral vToken
* @custom:error LiquidateLiquidatorIsBorrower is thrown when trying to liquidate self
* @custom:error LiquidateCloseAmountIsZero is thrown when repayment amount is zero
* @custom:error LiquidateCloseAmountIsUintMax is thrown when repayment amount is UINT_MAX
* @custom:access Only Comptroller
*/
function forceLiquidateBorrow(
address liquidator,
address borrower,
uint256 repayAmount,
VTokenInterface vTokenCollateral,
bool skipLiquidityCheck
) external override {
if (msg.sender != address(comptroller)) {
revert ForceLiquidateBorrowUnauthorized();
}
_liquidateBorrow(liquidator, borrower, repayAmount, vTokenCollateral, skipLiquidityCheck);
}
/**
* @notice Transfers collateral tokens (this market) to the liquidator.
* @dev Will fail unless called by another vToken during the process of liquidation.
* It's absolutely critical to use msg.sender as the borrowed vToken and not a parameter.
* @param liquidator The account receiving seized collateral
* @param borrower The account having collateral seized
* @param seizeTokens The number of vTokens to seize
* @custom:event Emits Transfer, ReservesAdded events
* @custom:error LiquidateSeizeLiquidatorIsBorrower is thrown when trying to liquidate self
* @custom:access Not restricted
*/
function seize(address liquidator, address borrower, uint256 seizeTokens) external override nonReentrant {
_seize(msg.sender, liquidator, borrower, seizeTokens);
}
/**
* @notice Updates bad debt
* @dev Called only when bad debt is recovered from auction
* @param recoveredAmount_ The amount of bad debt recovered
* @custom:event Emits BadDebtRecovered event
* @custom:access Only Shortfall contract
*/
function badDebtRecovered(uint256 recoveredAmount_) external {
require(msg.sender == shortfall, "only shortfall contract can update bad debt");
require(recoveredAmount_ <= badDebt, "more than bad debt recovered from auction");
uint256 badDebtOld = badDebt;
uint256 badDebtNew = badDebtOld - recoveredAmount_;
badDebt = badDebtNew;
emit BadDebtRecovered(badDebtOld, badDebtNew);
}
/**
* @notice Sets protocol share reserve contract address
* @param protocolShareReserve_ The address of the protocol share reserve contract
* @custom:error ZeroAddressNotAllowed is thrown when protocol share reserve address is zero
* @custom:access Only Governance
*/
function setProtocolShareReserve(address payable protocolShareReserve_) external onlyOwner {
_setProtocolShareReserve(protocolShareReserve_);
}
/**
* @notice Sets shortfall contract address
* @param shortfall_ The address of the shortfall contract
* @custom:error ZeroAddressNotAllowed is thrown when shortfall contract address is zero
* @custom:access Only Governance
*/
function setShortfallContract(address shortfall_) external onlyOwner {
_setShortfallContract(shortfall_);
}
/**
* @notice A public function to sweep accidental ERC-20 transfers to this contract. Tokens are sent to admin (timelock)
* @param token The address of the ERC-20 token to sweep
* @custom:access Only Governance
*/
function sweepToken(IERC20Upgradeable token) external override {
require(msg.sender == owner(), "VToken::sweepToken: only admin can sweep tokens");
require(address(token) != underlying, "VToken::sweepToken: can not sweep underlying token");
uint256 balance = token.balanceOf(address(this));
token.safeTransfer(owner(), balance);
emit SweepToken(address(token));
}
/**
* @notice A public function to set new threshold of slot(block or second) difference after which funds will be sent to the protocol share reserve
* @param _newReduceReservesBlockOrTimestampDelta slot(block or second) difference value
* @custom:access Only Governance
*/
function setReduceReservesBlockDelta(uint256 _newReduceReservesBlockOrTimestampDelta) external {
_checkAccessAllowed("setReduceReservesBlockDelta(uint256)");
require(_newReduceReservesBlockOrTimestampDelta > 0, "Invalid Input");
emit NewReduceReservesBlockDelta(reduceReservesBlockDelta, _newReduceReservesBlockOrTimestampDelta);
reduceReservesBlockDelta = _newReduceReservesBlockOrTimestampDelta;
}
/**
* @notice Get the current allowance from `owner` for `spender`
* @param owner The address of the account which owns the tokens to be spent
* @param spender The address of the account which may transfer tokens
* @return amount The number of tokens allowed to be spent (type(uint256).max means infinite)
*/
function allowance(address owner, address spender) external view override returns (uint256) {
return transferAllowances[owner][spender];
}
/**
* @notice Get the token balance of the `owner`
* @param owner The address of the account to query
* @return amount The number of tokens owned by `owner`
*/
function balanceOf(address owner) external view override returns (uint256) {
return accountTokens[owner];
}
/**
* @notice Get a snapshot of the account's balances, and the cached exchange rate
* @dev This is used by comptroller to more efficiently perform liquidity checks.
* @param account Address of the account to snapshot
* @return error Always NO_ERROR for compatibility with Venus core tooling
* @return vTokenBalance User's balance of vTokens
* @return borrowBalance Amount owed in terms of underlying
* @return exchangeRate Stored exchange rate
*/
function getAccountSnapshot(
address account
)
external
view
override
returns (uint256 error, uint256 vTokenBalance, uint256 borrowBalance, uint256 exchangeRate)
{
return (NO_ERROR, accountTokens[account], _borrowBalanceStored(account), _exchangeRateStored());
}
/**
* @notice Get cash balance of this vToken in the underlying asset
* @return cash The quantity of underlying asset owned by this contract
*/
function getCash() external view override returns (uint256) {
return _getCashPrior();
}
/**
* @notice Returns the current per slot(block or second) borrow interest rate for this vToken
* @return rate The borrow interest rate per slot(block or second), scaled by 1e18
*/
function borrowRatePerBlock() external view override returns (uint256) {
return interestRateModel.getBorrowRate(_getCashPrior(), totalBorrows, totalReserves, badDebt);
}
/**
* @notice Returns the current per-slot(block or second) supply interest rate for this v
* @return rate The supply interest rate per slot(block or second), scaled by 1e18
*/
function supplyRatePerBlock() external view override returns (uint256) {
return
interestRateModel.getSupplyRate(
_getCashPrior(),
totalBorrows,
totalReserves,
reserveFactorMantissa,
badDebt
);
}
/**
* @notice Return the borrow balance of account based on stored data
* @param account The address whose balance should be calculated
* @return borrowBalance The calculated balance
*/
function borrowBalanceStored(address account) external view override returns (uint256) {
return _borrowBalanceStored(account);
}
/**
* @notice Calculates the exchange rate from the underlying to the VToken
* @dev This function does not accrue interest before calculating the exchange rate
* @return exchangeRate Calculated exchange rate scaled by 1e18
*/
function exchangeRateStored() external view override returns (uint256) {
return _exchangeRateStored();
}
/**
* @notice Accrue interest then return the up-to-date exchange rate
* @return exchangeRate Calculated exchange rate scaled by 1e18
*/
function exchangeRateCurrent() public override nonReentrant returns (uint256) {
accrueInterest();
return _exchangeRateStored();
}
/**
* @notice Applies accrued interest to total borrows and reserves
* @dev This calculates interest accrued from the last checkpointed slot(block or second)
* up to the current slot(block or second) and writes new checkpoint to storage and
* reduce spread reserves to protocol share reserve
* if currentSlot - reduceReservesBlockNumber >= slotDelta
* @return Always NO_ERROR
* @custom:event Emits AccrueInterest event on success
* @custom:access Not restricted
*/
function accrueInterest() public virtual override returns (uint256) {
/* Remember the initial block number or timestamp */
uint256 currentSlotNumber = getBlockNumberOrTimestamp();
uint256 accrualSlotNumberPrior = accrualBlockNumber;
/* Short-circuit accumulating 0 interest */
if (accrualSlotNumberPrior == currentSlotNumber) {
return NO_ERROR;
}
/* Read the previous values out of storage */
uint256 cashPrior = _getCashPrior();
uint256 borrowsPrior = totalBorrows;
uint256 reservesPrior = totalReserves;
uint256 borrowIndexPrior = borrowIndex;
/* Calculate the current borrow interest rate */
uint256 borrowRateMantissa = interestRateModel.getBorrowRate(cashPrior, borrowsPrior, reservesPrior, badDebt);
require(borrowRateMantissa <= MAX_BORROW_RATE_MANTISSA, "borrow rate is absurdly high");
/* Calculate the number of slots elapsed since the last accrual */
uint256 slotDelta = currentSlotNumber - accrualSlotNumberPrior;
/*
* Calculate the interest accumulated into borrows and reserves and the new index:
* simpleInterestFactor = borrowRate * slotDelta
* interestAccumulated = simpleInterestFactor * totalBorrows
* totalBorrowsNew = interestAccumulated + totalBorrows
* totalReservesNew = interestAccumulated * reserveFactor + totalReserves
* borrowIndexNew = simpleInterestFactor * borrowIndex + borrowIndex
*/
Exp memory simpleInterestFactor = mul_(Exp({ mantissa: borrowRateMantissa }), slotDelta);
uint256 interestAccumulated = mul_ScalarTruncate(simpleInterestFactor, borrowsPrior);
uint256 totalBorrowsNew = interestAccumulated + borrowsPrior;
uint256 totalReservesNew = mul_ScalarTruncateAddUInt(
Exp({ mantissa: reserveFactorMantissa }),
interestAccumulated,
reservesPrior
);
uint256 borrowIndexNew = mul_ScalarTruncateAddUInt(simpleInterestFactor, borrowIndexPrior, borrowIndexPrior);
/////////////////////////
// EFFECTS & INTERACTIONS
// (No safe failures beyond this point)
/* We write the previously calculated values into storage */
accrualBlockNumber = currentSlotNumber;
borrowIndex = borrowIndexNew;
totalBorrows = totalBorrowsNew;
totalReserves = totalReservesNew;
if (currentSlotNumber - reduceReservesBlockNumber >= reduceReservesBlockDelta) {
reduceReservesBlockNumber = currentSlotNumber;
if (cashPrior < totalReservesNew) {
_reduceReservesFresh(cashPrior);
} else {
_reduceReservesFresh(totalReservesNew);
}
}
/* We emit an AccrueInterest event */
emit AccrueInterest(cashPrior, interestAccumulated, borrowIndexNew, totalBorrowsNew);
return NO_ERROR;
}
/**
* @notice User supplies assets into the market and receives vTokens in exchange
* @dev Assumes interest has already been accrued up to the current block or timestamp
* @param payer The address of the account which is sending the assets for supply
* @param minter The address of the account which is supplying the assets
* @param mintAmount The amount of the underlying asset to supply
*/
function _mintFresh(address payer, address minter, uint256 mintAmount) internal {
/* Fail if mint not allowed */
comptroller.preMintHook(address(this), minter, mintAmount);
/* Verify market's slot(block or second) number equals current slot(block or second) number */
if (accrualBlockNumber != getBlockNumberOrTimestamp()) {
revert MintFreshnessCheck();
}
Exp memory exchangeRate = Exp({ mantissa: _exchangeRateStored() });
/////////////////////////
// EFFECTS & INTERACTIONS
// (No safe failures beyond this point)
/*
* We call `_doTransferIn` for the minter and the mintAmount.
* `_doTransferIn` reverts if anything goes wrong, since we can't be sure if
* side-effects occurred. The function returns the amount actually transferred,
* in case of a fee. On success, the vToken holds an additional `actualMintAmount`
* of cash.
*/
uint256 actualMintAmount = _doTransferIn(payer, mintAmount);
/*
* We get the current exchange rate and calculate the number of vTokens to be minted:
* mintTokens = actualMintAmount / exchangeRate
*/
uint256 mintTokens = div_(actualMintAmount, exchangeRate);
/*
* We calculate the new total supply of vTokens and minter token balance, checking for overflow:
* totalSupplyNew = totalSupply + mintTokens
* accountTokensNew = accountTokens[minter] + mintTokens
* And write them into storage
*/
totalSupply = totalSupply + mintTokens;
uint256 balanceAfter = accountTokens[minter] + mintTokens;
accountTokens[minter] = balanceAfter;
/* We emit a Mint event, and a Transfer event */
emit Mint(minter, actualMintAmount, mintTokens, balanceAfter);
emit Transfer(address(0), minter, mintTokens);
/* We call the defense and prime accrue interest hook */
comptroller.mintVerify(address(this), minter, actualMintAmount, mintTokens);
}
/**
* @notice Redeemer redeems vTokens in exchange for the underlying assets, transferred to the receiver. Redeemer and receiver can be the same
* address, or different addresses if the receiver was previously approved by the redeemer as a valid delegate (see Comptroller.updateDelegate)
* @dev Assumes interest has already been accrued up to the current slot(block or second)
* @param redeemer The address of the account which is redeeming the tokens
* @param receiver The receiver of the underlying tokens
* @param redeemTokensIn The number of vTokens to redeem into underlying (only one of redeemTokensIn or redeemAmountIn may be non-zero)
* @param redeemAmountIn The number of underlying tokens to receive from redeeming vTokens (only one of redeemTokensIn or redeemAmountIn may be non-zero)
*/
function _redeemFresh(address redeemer, address receiver, uint256 redeemTokensIn, uint256 redeemAmountIn) internal {
require(redeemTokensIn == 0 || redeemAmountIn == 0, "one of redeemTokensIn or redeemAmountIn must be zero");
/* Verify market's slot(block or second) number equals current slot(block or second) number */
if (accrualBlockNumber != getBlockNumberOrTimestamp()) {
revert RedeemFreshnessCheck();
}
/* exchangeRate = invoke Exchange Rate Stored() */
Exp memory exchangeRate = Exp({ mantissa: _exchangeRateStored() });
uint256 redeemTokens;
uint256 redeemAmount;
/* If redeemTokensIn > 0: */
if (redeemTokensIn > 0) {
/*
* We calculate the exchange rate and the amount of underlying to be redeemed:
* redeemTokens = redeemTokensIn
*/
redeemTokens = redeemTokensIn;
} else {
/*
* We get the current exchange rate and calculate the amount to be redeemed:
* redeemTokens = redeemAmountIn / exchangeRate
*/
redeemTokens = div_(redeemAmountIn, exchangeRate);
uint256 _redeemAmount = mul_(redeemTokens, exchangeRate);
if (_redeemAmount != 0 && _redeemAmount != redeemAmountIn) redeemTokens++; // round up
}
// redeemAmount = exchangeRate * redeemTokens
redeemAmount = mul_ScalarTruncate(exchangeRate, redeemTokens);
// Revert if amount is zero
if (redeemAmount == 0) {
revert("redeemAmount is zero");
}
/* Fail if redeem not allowed */
comptroller.preRedeemHook(address(this), redeemer, redeemTokens);
/* Fail gracefully if protocol has insufficient cash */
if (_getCashPrior() - totalReserves < redeemAmount) {
revert RedeemTransferOutNotPossible();
}
/////////////////////////
// EFFECTS & INTERACTIONS
// (No safe failures beyond this point)
/*
* We write the previously calculated values into storage.
* Note: Avoid token reentrancy attacks by writing reduced supply before external transfer.
*/
totalSupply = totalSupply - redeemTokens;
uint256 balanceAfter = accountTokens[redeemer] - redeemTokens;
accountTokens[redeemer] = balanceAfter;
/*
* We invoke _doTransferOut for the receiver and the redeemAmount.
* On success, the vToken has redeemAmount less of cash.
* _doTransferOut reverts if anything goes wrong, since we can't be sure if side effects occurred.
*/
_doTransferOut(receiver, redeemAmount);
/* We emit a Transfer event, and a Redeem event */
emit Transfer(redeemer, address(this), redeemTokens);
emit Redeem(redeemer, redeemAmount, redeemTokens, balanceAfter);
/* We call the defense and prime accrue interest hook */
comptroller.redeemVerify(address(this), redeemer, redeemAmount, redeemTokens);
}
/**
* @notice Users or their delegates borrow assets from the protocol
* @param borrower User who borrows the assets
* @param receiver The receiver of the tokens, if called by a delegate
* @param borrowAmount The amount of the underlying asset to borrow
*/
function _borrowFresh(address borrower, address receiver, uint256 borrowAmount) internal {
/* Fail if borrow not allowed */
comptroller.preBorrowHook(address(this), borrower, borrowAmount);
/* Verify market's slot(block or second) number equals current slot(block or second) number */
if (accrualBlockNumber != getBlockNumberOrTimestamp()) {
revert BorrowFreshnessCheck();
}
/* Fail gracefully if protocol has insufficient underlying cash */
if (_getCashPrior() - totalReserves < borrowAmount) {
revert BorrowCashNotAvailable();
}
/*
* We calculate the new borrower and total borrow balances, failing on overflow:
* accountBorrowNew = accountBorrow + borrowAmount
* totalBorrowsNew = totalBorrows + borrowAmount
*/
uint256 accountBorrowsPrev = _borrowBalanceStored(borrower);
uint256 accountBorrowsNew = accountBorrowsPrev + borrowAmount;
uint256 totalBorrowsNew = totalBorrows + borrowAmount;
/////////////////////////
// EFFECTS & INTERACTIONS
// (No safe failures beyond this point)
/*
* We write the previously calculated values into storage.
* Note: Avoid token reentrancy attacks by writing increased borrow before external transfer.
`*/
accountBorrows[borrower].principal = accountBorrowsNew;
accountBorrows[borrower].interestIndex = borrowIndex;
totalBorrows = totalBorrowsNew;
/*
* We invoke _doTransferOut for the receiver and the borrowAmount.
* On success, the vToken borrowAmount less of cash.
* _doTransferOut reverts if anything goes wrong, since we can't be sure if side effects occurred.
*/
_doTransferOut(receiver, borrowAmount);
/* We emit a Borrow event */
emit Borrow(borrower, borrowAmount, accountBorrowsNew, totalBorrowsNew);
/* We call the defense and prime accrue interest hook */
comptroller.borrowVerify(address(this), borrower, borrowAmount);
}
/**
* @notice Borrows are repaid by another user (possibly the borrower).
* @param payer the account paying off the borrow
* @param borrower the account with the debt being payed off
* @param repayAmount the amount of underlying tokens being returned, or type(uint256).max for the full outstanding amount
* @return (uint) the actual repayment amount.
*/
function _repayBorrowFresh(address payer, address borrower, uint256 repayAmount) internal returns (uint256) {
/* Fail if repayBorrow not allowed */
comptroller.preRepayHook(address(this), borrower);
/* Verify market's slot(block or second) number equals current slot(block or second) number */
if (accrualBlockNumber != getBlockNumberOrTimestamp()) {
revert RepayBorrowFreshnessCheck();
}
/* We fetch the amount the borrower owes, with accumulated interest */
uint256 accountBorrowsPrev = _borrowBalanceStored(borrower);
uint256 repayAmountFinal = repayAmount >= accountBorrowsPrev ? accountBorrowsPrev : repayAmount;
/////////////////////////
// EFFECTS & INTERACTIONS
// (No safe failures beyond this point)
/*
* We call _doTransferIn for the payer and the repayAmount
* On success, the vToken holds an additional repayAmount of cash.
* _doTransferIn reverts if anything goes wrong, since we can't be sure if side effects occurred.
* it returns the amount actually transferred, in case of a fee.
*/
uint256 actualRepayAmount = _doTransferIn(payer, repayAmountFinal);
/*
* We calculate the new borrower and total borrow balances, failing on underflow:
* accountBorrowsNew = accountBorrows - actualRepayAmount
* totalBorrowsNew = totalBorrows - actualRepayAmount
*/
uint256 accountBorrowsNew = accountBorrowsPrev - actualRepayAmount;
uint256 totalBorrowsNew = totalBorrows - actualRepayAmount;
/* We write the previously calculated values into storage */
accountBorrows[borrower].principal = accountBorrowsNew;
accountBorrows[borrower].interestIndex = borrowIndex;
totalBorrows = totalBorrowsNew;
/* We emit a RepayBorrow event */
emit RepayBorrow(payer, borrower, actualRepayAmount, accountBorrowsNew, totalBorrowsNew);
/* We call the defense and prime accrue interest hook */
comptroller.repayBorrowVerify(address(this), payer, borrower, actualRepayAmount, borrowIndex);
return actualRepayAmount;
}
/**
* @notice The sender liquidates the borrowers collateral.
* The collateral seized is transferred to the liquidator.
* @param liquidator The address repaying the borrow and seizing collateral
* @param borrower The borrower of this vToken to be liquidated
* @param vTokenCollateral The market in which to seize collateral from the borrower
* @param repayAmount The amount of the underlying borrowed asset to repay
* @param skipLiquidityCheck If set to true, allows to liquidate up to 100% of the borrow
* regardless of the account liquidity
*/
function _liquidateBorrow(
address liquidator,
address borrower,
uint256 repayAmount,
VTokenInterface vTokenCollateral,
bool skipLiquidityCheck
) internal nonReentrant {
accrueInterest();
uint256 error = vTokenCollateral.accrueInterest();
if (error != NO_ERROR) {
// accrueInterest emits logs on errors, but we still want to log the fact that an attempted liquidation failed
revert LiquidateAccrueCollateralInterestFailed(error);
}
_liquidateBorrowFresh(liquidator, borrower, repayAmount, vTokenCollateral, skipLiquidityCheck);
}
/**
* @notice The liquidator liquidates the borrowers collateral.
* The collateral seized is transferred to the liquidator.
* @param liquidator The address repaying the borrow and seizing collateral
* @param borrower The borrower of this vToken to be liquidated
* @param vTokenCollateral The market in which to seize collateral from the borrower
* @param repayAmount The amount of the underlying borrowed asset to repay
* @param skipLiquidityCheck If set to true, allows to liquidate up to 100% of the borrow
* regardless of the account liquidity
*/
function _liquidateBorrowFresh(
address liquidator,
address borrower,
uint256 repayAmount,
VTokenInterface vTokenCollateral,
bool skipLiquidityCheck
) internal {
/* Fail if liquidate not allowed */
comptroller.preLiquidateHook(
address(this),
address(vTokenCollateral),
borrower,
repayAmount,
skipLiquidityCheck
);
/* Verify market's slot(block or second) number equals current slot(block or second) number */
if (accrualBlockNumber != getBlockNumberOrTimestamp()) {
revert LiquidateFreshnessCheck();
}
/* Verify vTokenCollateral market's slot(block or second) number equals current slot(block or second) number */
if (vTokenCollateral.accrualBlockNumber() != getBlockNumberOrTimestamp()) {
revert LiquidateCollateralFreshnessCheck();
}
/* Fail if borrower = liquidator */
if (borrower == liquidator) {
revert LiquidateLiquidatorIsBorrower();
}
/* Fail if repayAmount = 0 */
if (repayAmount == 0) {
revert LiquidateCloseAmountIsZero();
}
/* Fail if repayAmount = type(uint256).max */
if (repayAmount == type(uint256).max) {
revert LiquidateCloseAmountIsUintMax();
}
/* Fail if repayBorrow fails */
uint256 actualRepayAmount = _repayBorrowFresh(liquidator, borrower, repayAmount);
/////////////////////////
// EFFECTS & INTERACTIONS
// (No safe failures beyond this point)
/* We calculate the number of collateral tokens that will be seized */
(uint256 amountSeizeError, uint256 seizeTokens) = comptroller.liquidateCalculateSeizeTokens(
address(this),
address(vTokenCollateral),
actualRepayAmount
);
require(amountSeizeError == NO_ERROR, "LIQUIDATE_COMPTROLLER_CALCULATE_AMOUNT_SEIZE_FAILED");
/* Revert if borrower collateral token balance < seizeTokens */
require(vTokenCollateral.balanceOf(borrower) >= seizeTokens, "LIQUIDATE_SEIZE_TOO_MUCH");
// If this is also the collateral, call _seize internally to avoid re-entrancy, otherwise make an external call
if (address(vTokenCollateral) == address(this)) {
_seize(address(this), liquidator, borrower, seizeTokens);
} else {
vTokenCollateral.seize(liquidator, borrower, seizeTokens);
}
/* We emit a LiquidateBorrow event */
emit LiquidateBorrow(liquidator, borrower, actualRepayAmount, address(vTokenCollateral), seizeTokens);
/* We call the defense and prime accrue interest hook */
comptroller.liquidateBorrowVerify(
address(this),
address(vTokenCollateral),
liquidator,
borrower,
actualRepayAmount,
seizeTokens
);
}
/**
* @notice Transfers collateral tokens (this market) to the liquidator.
* @dev Called only during an in-kind liquidation, or by liquidateBorrow during the liquidation of another VToken.
* It's absolutely critical to use msg.sender as the seizer vToken and not a parameter.
* @param seizerContract The contract seizing the collateral (either borrowed vToken or Comptroller)
* @param liquidator The account receiving seized collateral
* @param borrower The account having collateral seized
* @param seizeTokens The number of vTokens to seize
*/
function _seize(address seizerContract, address liquidator, address borrower, uint256 seizeTokens) internal {
/* Fail if seize not allowed */
comptroller.preSeizeHook(address(this), seizerContract, liquidator, borrower);
/* Fail if borrower = liquidator */
if (borrower == liquidator) {
revert LiquidateSeizeLiquidatorIsBorrower();
}
/*
* We calculate the new borrower and liquidator token balances, failing on underflow/overflow:
* borrowerTokensNew = accountTokens[borrower] - seizeTokens
* liquidatorTokensNew = accountTokens[liquidator] + seizeTokens
*/
uint256 liquidationIncentiveMantissa = ComptrollerViewInterface(address(comptroller))
.liquidationIncentiveMantissa();
uint256 numerator = mul_(seizeTokens, Exp({ mantissa: protocolSeizeShareMantissa }));
uint256 protocolSeizeTokens = div_(numerator, Exp({ mantissa: liquidationIncentiveMantissa }));
uint256 liquidatorSeizeTokens = seizeTokens - protocolSeizeTokens;
Exp memory exchangeRate = Exp({ mantissa: _exchangeRateStored() });
uint256 protocolSeizeAmount = mul_ScalarTruncate(exchangeRate, protocolSeizeTokens);
/////////////////////////
// EFFECTS & INTERACTIONS
// (No safe failures beyond this point)
/* We write the calculated values into storage */
totalSupply = totalSupply - protocolSeizeTokens;
accountTokens[borrower] = accountTokens[borrower] - seizeTokens;
accountTokens[liquidator] = accountTokens[liquidator] + liquidatorSeizeTokens;
// _doTransferOut reverts if anything goes wrong, since we can't be sure if side effects occurred.
// Transferring an underlying asset to the protocolShareReserve contract to channel the funds for different use.
_doTransferOut(protocolShareReserve, protocolSeizeAmount);
// Update the pool asset's state in the protocol share reserve for the above transfer.
IProtocolShareReserve(protocolShareReserve).updateAssetsState(
address(comptroller),
underlying,
IProtocolShareReserve.IncomeType.LIQUIDATION
);
/* Emit a Transfer event */
emit Transfer(borrower, liquidator, liquidatorSeizeTokens);
emit ProtocolSeize(borrower, protocolShareReserve, protocolSeizeAmount);
/* We call the defense and prime accrue interest hook */
comptroller.seizeVerify(address(this), seizerContract, liquidator, borrower, seizeTokens);
}
function _setComptroller(ComptrollerInterface newComptroller) internal {
ComptrollerInterface oldComptroller = comptroller;
// Ensure invoke comptroller.isComptroller() returns true
require(newComptroller.isComptroller(), "marker method returned false");
// Set market's comptroller to newComptroller
comptroller = newComptroller;
// Emit NewComptroller(oldComptroller, newComptroller)
emit NewComptroller(oldComptroller, newComptroller);
}
/**
* @notice Sets a new reserve factor for the protocol (*requires fresh interest accrual)
* @dev Admin function to set a new reserve factor
* @param newReserveFactorMantissa New reserve factor (from 0 to 1e18)
*/
function _setReserveFactorFresh(uint256 newReserveFactorMantissa) internal {
// Verify market's slot(block or second) number equals current slot(block or second) number
if (accrualBlockNumber != getBlockNumberOrTimestamp()) {
revert SetReserveFactorFreshCheck();
}
// Check newReserveFactor ≤ maxReserveFactor
if (newReserveFactorMantissa > MAX_RESERVE_FACTOR_MANTISSA) {
revert SetReserveFactorBoundsCheck();
}
uint256 oldReserveFactorMantissa = reserveFactorMantissa;
reserveFactorMantissa = newReserveFactorMantissa;
emit NewReserveFactor(oldReserveFactorMantissa, newReserveFactorMantissa);
}
/**
* @notice Add reserves by transferring from caller
* @dev Requires fresh interest accrual
* @param addAmount Amount of addition to reserves
* @return actualAddAmount The actual amount added, excluding the potential token fees
*/
function _addReservesFresh(uint256 addAmount) internal returns (uint256) {
// totalReserves + actualAddAmount
uint256 totalReservesNew;
uint256 actualAddAmount;
// We fail gracefully unless market's slot(block or second) number equals current slot(block or second) number
if (accrualBlockNumber != getBlockNumberOrTimestamp()) {
revert AddReservesFactorFreshCheck(actualAddAmount);
}
actualAddAmount = _doTransferIn(msg.sender, addAmount);
totalReservesNew = totalReserves + actualAddAmount;
totalReserves = totalReservesNew;
emit ReservesAdded(msg.sender, actualAddAmount, totalReservesNew);
return actualAddAmount;
}
/**
* @notice Reduces reserves by transferring to the protocol reserve contract
* @dev Requires fresh interest accrual
* @param reduceAmount Amount of reduction to reserves
*/
function _reduceReservesFresh(uint256 reduceAmount) internal {
if (reduceAmount == 0) {
return;
}
// totalReserves - reduceAmount
uint256 totalReservesNew;
// We fail gracefully unless market's slot(block or second) number equals current slot(block or second) number
if (accrualBlockNumber != getBlockNumberOrTimestamp()) {
revert ReduceReservesFreshCheck();
}
// Fail gracefully if protocol has insufficient underlying cash
if (_getCashPrior() < reduceAmount) {
revert ReduceReservesCashNotAvailable();
}
// Check reduceAmount ≤ reserves[n] (totalReserves)
if (reduceAmount > totalReserves) {
revert ReduceReservesCashValidation();
}
/////////////////////////
// EFFECTS & INTERACTIONS
// (No safe failures beyond this point)
totalReservesNew = totalReserves - reduceAmount;
// Store reserves[n+1] = reserves[n] - reduceAmount
totalReserves = totalReservesNew;
// _doTransferOut reverts if anything goes wrong, since we can't be sure if side effects occurred.
// Transferring an underlying asset to the protocolShareReserve contract to channel the funds for different use.
_doTransferOut(protocolShareReserve, reduceAmount);
// Update the pool asset's state in the protocol share reserve for the above transfer.
IProtocolShareReserve(protocolShareReserve).updateAssetsState(
address(comptroller),
underlying,
IProtocolShareReserve.IncomeType.SPREAD
);
emit SpreadReservesReduced(protocolShareReserve, reduceAmount, totalReservesNew);
}
/**
* @notice updates the interest rate model (*requires fresh interest accrual)
* @dev Admin function to update the interest rate model
* @param newInterestRateModel the new interest rate model to use
*/
function _setInterestRateModelFresh(InterestRateModel newInterestRateModel) internal {
// Used to store old model for use in the event that is emitted on success
InterestRateModel oldInterestRateModel;
// We fail gracefully unless market's slot(block or second) number equals current slot(block or second) number
if (accrualBlockNumber != getBlockNumberOrTimestamp()) {
revert SetInterestRateModelFreshCheck();
}
// Track the market's current interest rate model
oldInterestRateModel = interestRateModel;
// Ensure invoke newInterestRateModel.isInterestRateModel() returns true
require(newInterestRateModel.isInterestRateModel(), "marker method returned false");
// Set the interest rate model to newInterestRateModel
interestRateModel = newInterestRateModel;
// Emit NewMarketInterestRateModel(oldInterestRateModel, newInterestRateModel)
emit NewMarketInterestRateModel(oldInterestRateModel, newInterestRateModel);
}
/**
* Safe Token **
*/
/**
* @dev Similar to ERC-20 transfer, but handles tokens that have transfer fees.
* This function returns the actual amount received,
* which may be less than `amount` if there is a fee attached to the transfer.
* @param from Sender of the underlying tokens
* @param amount Amount of underlying to transfer
* @return Actual amount received
*/
function _doTransferIn(address from, uint256 amount) internal virtual returns (uint256) {
IERC20Upgradeable token = IERC20Upgradeable(underlying);
uint256 balanceBefore = token.balanceOf(address(this));
token.safeTransferFrom(from, address(this), amount);
uint256 balanceAfter = token.balanceOf(address(this));
// Return the amount that was *actually* transferred
return balanceAfter - balanceBefore;
}
/**
* @dev Just a regular ERC-20 transfer, reverts on failure
* @param to Receiver of the underlying tokens
* @param amount Amount of underlying to transfer
*/
function _doTransferOut(address to, uint256 amount) internal virtual {
IERC20Upgradeable token = IERC20Upgradeable(underlying);
token.safeTransfer(to, amount);
}
/**
* @notice Transfer `tokens` tokens from `src` to `dst` by `spender`
* @dev Called by both `transfer` and `transferFrom` internally
* @param spender The address of the account performing the transfer
* @param src The address of the source account
* @param dst The address of the destination account
* @param tokens The number of tokens to transfer
*/
function _transferTokens(address spender, address src, address dst, uint256 tokens) internal {
/* Fail if transfer not allowed */
comptroller.preTransferHook(address(this), src, dst, tokens);
/* Do not allow self-transfers */
if (src == dst) {
revert TransferNotAllowed();
}
/* Get the allowance, infinite for the account owner */
uint256 startingAllowance;
if (spender == src) {
startingAllowance = type(uint256).max;
} else {
startingAllowance = transferAllowances[src][spender];
}
/* Do the calculations, checking for {under,over}flow */
uint256 allowanceNew = startingAllowance - tokens;
uint256 srcTokensNew = accountTokens[src] - tokens;
uint256 dstTokensNew = accountTokens[dst] + tokens;
/////////////////////////
// EFFECTS & INTERACTIONS
accountTokens[src] = srcTokensNew;
accountTokens[dst] = dstTokensNew;
/* Eat some of the allowance (if necessary) */
if (startingAllowance != type(uint256).max) {
transferAllowances[src][spender] = allowanceNew;
}
/* We emit a Transfer event */
emit Transfer(src, dst, tokens);
comptroller.transferVerify(address(this), src, dst, tokens);
}
/**
* @notice Initialize the money market
* @param underlying_ The address of the underlying asset
* @param comptroller_ The address of the Comptroller
* @param interestRateModel_ The address of the interest rate model
* @param initialExchangeRateMantissa_ The initial exchange rate, scaled by 1e18
* @param name_ ERC-20 name of this token
* @param symbol_ ERC-20 symbol of this token
* @param decimals_ ERC-20 decimal precision of this token
* @param admin_ Address of the administrator of this token
* @param accessControlManager_ AccessControlManager contract address
* @param riskManagement Addresses of risk & income related contracts
* @param reserveFactorMantissa_ Percentage of borrow interest that goes to reserves (from 0 to 1e18)
*/
function _initialize(
address underlying_,
ComptrollerInterface comptroller_,
InterestRateModel interestRateModel_,
uint256 initialExchangeRateMantissa_,
string memory name_,
string memory symbol_,
uint8 decimals_,
address admin_,
address accessControlManager_,
RiskManagementInit memory riskManagement,
uint256 reserveFactorMantissa_
) internal onlyInitializing {
__Ownable2Step_init();
__AccessControlled_init_unchained(accessControlManager_);
require(accrualBlockNumber == 0 && borrowIndex == 0, "market may only be initialized once");
// Set initial exchange rate
initialExchangeRateMantissa = initialExchangeRateMantissa_;
require(initialExchangeRateMantissa > 0, "initial exchange rate must be greater than zero.");
_setComptroller(comptroller_);
// Initialize slot(block or second) number and borrow index (slot(block or second) number mocks depend on comptroller being set)
accrualBlockNumber = getBlockNumberOrTimestamp();
borrowIndex = MANTISSA_ONE;
// Set the interest rate model (depends on slot(block or second) number / borrow index)
_setInterestRateModelFresh(interestRateModel_);
_setReserveFactorFresh(reserveFactorMantissa_);
name = name_;
symbol = symbol_;
decimals = decimals_;
_setShortfallContract(riskManagement.shortfall);
_setProtocolShareReserve(riskManagement.protocolShareReserve);
protocolSeizeShareMantissa = DEFAULT_PROTOCOL_SEIZE_SHARE_MANTISSA;
// Set underlying and sanity check it
underlying = underlying_;
IERC20Upgradeable(underlying).totalSupply();
// The counter starts true to prevent changing it from zero to non-zero (i.e. smaller cost/refund)
_notEntered = true;
_transferOwnership(admin_);
}
function _setShortfallContract(address shortfall_) internal {
ensureNonzeroAddress(shortfall_);
address oldShortfall = shortfall;
shortfall = shortfall_;
emit NewShortfallContract(oldShortfall, shortfall_);
}
function _setProtocolShareReserve(address payable protocolShareReserve_) internal {
ensureNonzeroAddress(protocolShareReserve_);
address oldProtocolShareReserve = address(protocolShareReserve);
protocolShareReserve = protocolShareReserve_;
emit NewProtocolShareReserve(oldProtocolShareReserve, address(protocolShareReserve_));
}
function _ensureSenderIsDelegateOf(address user) internal view {
if (!ComptrollerViewInterface(address(comptroller)).approvedDelegates(user, msg.sender)) {
revert DelegateNotApproved();
}
}
/**
* @notice Gets balance of this contract in terms of the underlying
* @dev This excludes the value of the current message, if any
* @return The quantity of underlying tokens owned by this contract
*/
function _getCashPrior() internal view virtual returns (uint256) {
return IERC20Upgradeable(underlying).balanceOf(address(this));
}
/**
* @notice Return the borrow balance of account based on stored data
* @param account The address whose balance should be calculated
* @return borrowBalance the calculated balance
*/
function _borrowBalanceStored(address account) internal view returns (uint256) {
/* Get borrowBalance and borrowIndex */
BorrowSnapshot memory borrowSnapshot = accountBorrows[account];
/* If borrowBalance = 0 then borrowIndex is likely also 0.
* Rather than failing the calculation with a division by 0, we immediately return 0 in this case.
*/
if (borrowSnapshot.principal == 0) {
return 0;
}
/* Calculate new borrow balance using the interest index:
* recentBorrowBalance = borrower.borrowBalance * market.borrowIndex / borrower.borrowIndex
*/
uint256 principalTimesIndex = borrowSnapshot.principal * borrowIndex;
return principalTimesIndex / borrowSnapshot.interestIndex;
}
/**
* @notice Calculates the exchange rate from the underlying to the VToken
* @dev This function does not accrue interest before calculating the exchange rate
* @return exchangeRate Calculated exchange rate scaled by 1e18
*/
function _exchangeRateStored() internal view virtual returns (uint256) {
uint256 _totalSupply = totalSupply;
if (_totalSupply == 0) {
/*
* If there are no tokens minted:
* exchangeRate = initialExchangeRate
*/
return initialExchangeRateMantissa;
}
/*
* Otherwise:
* exchangeRate = (totalCash + totalBorrows + badDebt - totalReserves) / totalSupply
*/
uint256 totalCash = _getCashPrior();
uint256 cashPlusBorrowsMinusReserves = totalCash + totalBorrows + badDebt - totalReserves;
uint256 exchangeRate = (cashPlusBorrowsMinusReserves * EXP_SCALE) / _totalSupply;
return exchangeRate;
}
}// SPDX-License-Identifier: BSD-3-Clause
pragma solidity 0.8.25;
import { IERC20Upgradeable } from "@openzeppelin/contracts-upgradeable/token/ERC20/IERC20Upgradeable.sol";
import { ResilientOracleInterface } from "@venusprotocol/oracle/contracts/interfaces/OracleInterface.sol";
import { ComptrollerInterface } from "./ComptrollerInterface.sol";
import { InterestRateModel } from "./InterestRateModel.sol";
/**
* @title VTokenStorage
* @author Venus
* @notice Storage layout used by the `VToken` contract
*/
// solhint-disable-next-line max-states-count
contract VTokenStorage {
/**
* @notice Container for borrow balance information
* @member principal Total balance (with accrued interest), after applying the most recent balance-changing action
* @member interestIndex Global borrowIndex as of the most recent balance-changing action
*/
struct BorrowSnapshot {
uint256 principal;
uint256 interestIndex;
}
/**
* @dev Guard variable for re-entrancy checks
*/
bool internal _notEntered;
/**
* @notice Underlying asset for this VToken
*/
address public underlying;
/**
* @notice EIP-20 token name for this token
*/
string public name;
/**
* @notice EIP-20 token symbol for this token
*/
string public symbol;
/**
* @notice EIP-20 token decimals for this token
*/
uint8 public decimals;
/**
* @notice Protocol share Reserve contract address
*/
address payable public protocolShareReserve;
/**
* @notice Contract which oversees inter-vToken operations
*/
ComptrollerInterface public comptroller;
/**
* @notice Model which tells what the current interest rate should be
*/
InterestRateModel public interestRateModel;
// Initial exchange rate used when minting the first VTokens (used when totalSupply = 0)
uint256 internal initialExchangeRateMantissa;
/**
* @notice Fraction of interest currently set aside for reserves
*/
uint256 public reserveFactorMantissa;
/**
* @notice Slot(block or second) number that interest was last accrued at
*/
uint256 public accrualBlockNumber;
/**
* @notice Accumulator of the total earned interest rate since the opening of the market
*/
uint256 public borrowIndex;
/**
* @notice Total amount of outstanding borrows of the underlying in this market
*/
uint256 public totalBorrows;
/**
* @notice Total amount of reserves of the underlying held in this market
*/
uint256 public totalReserves;
/**
* @notice Total number of tokens in circulation
*/
uint256 public totalSupply;
/**
* @notice Total bad debt of the market
*/
uint256 public badDebt;
// Official record of token balances for each account
mapping(address => uint256) internal accountTokens;
// Approved token transfer amounts on behalf of others
mapping(address => mapping(address => uint256)) internal transferAllowances;
// Mapping of account addresses to outstanding borrow balances
mapping(address => BorrowSnapshot) internal accountBorrows;
/**
* @notice Share of seized collateral that is added to reserves
*/
uint256 public protocolSeizeShareMantissa;
/**
* @notice Storage of Shortfall contract address
*/
address public shortfall;
/**
* @notice delta slot (block or second) after which reserves will be reduced
*/
uint256 public reduceReservesBlockDelta;
/**
* @notice last slot (block or second) number at which reserves were reduced
*/
uint256 public reduceReservesBlockNumber;
/**
* @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;
}
/**
* @title VTokenInterface
* @author Venus
* @notice Interface implemented by the `VToken` contract
*/
abstract contract VTokenInterface is VTokenStorage {
struct RiskManagementInit {
address shortfall;
address payable protocolShareReserve;
}
/*** Market Events ***/
/**
* @notice Event emitted when interest is accrued
*/
event AccrueInterest(uint256 cashPrior, uint256 interestAccumulated, uint256 borrowIndex, uint256 totalBorrows);
/**
* @notice Event emitted when tokens are minted
*/
event Mint(address indexed minter, uint256 mintAmount, uint256 mintTokens, uint256 accountBalance);
/**
* @notice Event emitted when tokens are redeemed
*/
event Redeem(address indexed redeemer, uint256 redeemAmount, uint256 redeemTokens, uint256 accountBalance);
/**
* @notice Event emitted when underlying is borrowed
*/
event Borrow(address indexed borrower, uint256 borrowAmount, uint256 accountBorrows, uint256 totalBorrows);
/**
* @notice Event emitted when a borrow is repaid
*/
event RepayBorrow(
address indexed payer,
address indexed borrower,
uint256 repayAmount,
uint256 accountBorrows,
uint256 totalBorrows
);
/**
* @notice Event emitted when bad debt is accumulated on a market
* @param borrower borrower to "forgive"
* @param badDebtDelta amount of new bad debt recorded
* @param badDebtOld previous bad debt value
* @param badDebtNew new bad debt value
*/
event BadDebtIncreased(address indexed borrower, uint256 badDebtDelta, uint256 badDebtOld, uint256 badDebtNew);
/**
* @notice Event emitted when bad debt is recovered via an auction
* @param badDebtOld previous bad debt value
* @param badDebtNew new bad debt value
*/
event BadDebtRecovered(uint256 badDebtOld, uint256 badDebtNew);
/**
* @notice Event emitted when a borrow is liquidated
*/
event LiquidateBorrow(
address indexed liquidator,
address indexed borrower,
uint256 repayAmount,
address indexed vTokenCollateral,
uint256 seizeTokens
);
/*** Admin Events ***/
/**
* @notice Event emitted when comptroller is changed
*/
event NewComptroller(ComptrollerInterface indexed oldComptroller, ComptrollerInterface indexed newComptroller);
/**
* @notice Event emitted when shortfall contract address is changed
*/
event NewShortfallContract(address indexed oldShortfall, address indexed newShortfall);
/**
* @notice Event emitted when protocol share reserve contract address is changed
*/
event NewProtocolShareReserve(address indexed oldProtocolShareReserve, address indexed newProtocolShareReserve);
/**
* @notice Event emitted when interestRateModel is changed
*/
event NewMarketInterestRateModel(
InterestRateModel indexed oldInterestRateModel,
InterestRateModel indexed newInterestRateModel
);
/**
* @notice Event emitted when protocol seize share is changed
*/
event NewProtocolSeizeShare(uint256 oldProtocolSeizeShareMantissa, uint256 newProtocolSeizeShareMantissa);
/**
* @notice Event emitted when the reserve factor is changed
*/
event NewReserveFactor(uint256 oldReserveFactorMantissa, uint256 newReserveFactorMantissa);
/**
* @notice Event emitted when the reserves are added
*/
event ReservesAdded(address indexed benefactor, uint256 addAmount, uint256 newTotalReserves);
/**
* @notice Event emitted when the spread reserves are reduced
*/
event SpreadReservesReduced(address indexed protocolShareReserve, uint256 reduceAmount, uint256 newTotalReserves);
/**
* @notice EIP20 Transfer event
*/
event Transfer(address indexed from, address indexed to, uint256 amount);
/**
* @notice EIP20 Approval event
*/
event Approval(address indexed owner, address indexed spender, uint256 amount);
/**
* @notice Event emitted when healing the borrow
*/
event HealBorrow(address indexed payer, address indexed borrower, uint256 repayAmount);
/**
* @notice Event emitted when tokens are swept
*/
event SweepToken(address indexed token);
/**
* @notice Event emitted when reduce reserves slot (block or second) delta is changed
*/
event NewReduceReservesBlockDelta(
uint256 oldReduceReservesBlockOrTimestampDelta,
uint256 newReduceReservesBlockOrTimestampDelta
);
/**
* @notice Event emitted when liquidation reserves are reduced
*/
event ProtocolSeize(address indexed from, address indexed to, uint256 amount);
/*** User Interface ***/
function mint(uint256 mintAmount) external virtual returns (uint256);
function mintBehalf(address minter, uint256 mintAllowed) external virtual returns (uint256);
function redeem(uint256 redeemTokens) external virtual returns (uint256);
function redeemBehalf(address redeemer, uint256 redeemTokens) external virtual returns (uint256);
function redeemUnderlying(uint256 redeemAmount) external virtual returns (uint256);
function redeemUnderlyingBehalf(address redeemer, uint256 redeemAmount) external virtual returns (uint256);
function borrow(uint256 borrowAmount) external virtual returns (uint256);
function borrowBehalf(address borrwwer, uint256 borrowAmount) external virtual returns (uint256);
function repayBorrow(uint256 repayAmount) external virtual returns (uint256);
function repayBorrowBehalf(address borrower, uint256 repayAmount) external virtual returns (uint256);
function liquidateBorrow(
address borrower,
uint256 repayAmount,
VTokenInterface vTokenCollateral
) external virtual returns (uint256);
function healBorrow(address payer, address borrower, uint256 repayAmount) external virtual;
function forceLiquidateBorrow(
address liquidator,
address borrower,
uint256 repayAmount,
VTokenInterface vTokenCollateral,
bool skipCloseFactorCheck
) external virtual;
function seize(address liquidator, address borrower, uint256 seizeTokens) external virtual;
function transfer(address dst, uint256 amount) external virtual returns (bool);
function transferFrom(address src, address dst, uint256 amount) external virtual returns (bool);
function accrueInterest() external virtual returns (uint256);
function sweepToken(IERC20Upgradeable token) external virtual;
/*** Admin Functions ***/
function setReserveFactor(uint256 newReserveFactorMantissa) external virtual;
function reduceReserves(uint256 reduceAmount) external virtual;
function exchangeRateCurrent() external virtual returns (uint256);
function borrowBalanceCurrent(address account) external virtual returns (uint256);
function setInterestRateModel(InterestRateModel newInterestRateModel) external virtual;
function addReserves(uint256 addAmount) external virtual;
function totalBorrowsCurrent() external virtual returns (uint256);
function balanceOfUnderlying(address owner) external virtual returns (uint256);
function approve(address spender, uint256 amount) external virtual returns (bool);
function increaseAllowance(address spender, uint256 addedValue) external virtual returns (bool);
function decreaseAllowance(address spender, uint256 subtractedValue) external virtual returns (bool);
function allowance(address owner, address spender) external view virtual returns (uint256);
function balanceOf(address owner) external view virtual returns (uint256);
function getAccountSnapshot(address account) external view virtual returns (uint256, uint256, uint256, uint256);
function borrowRatePerBlock() external view virtual returns (uint256);
function supplyRatePerBlock() external view virtual returns (uint256);
function borrowBalanceStored(address account) external view virtual returns (uint256);
function exchangeRateStored() external view virtual returns (uint256);
function getCash() external view virtual returns (uint256);
/**
* @notice Indicator that this is a VToken contract (for inspection)
* @return Always true
*/
function isVToken() external pure virtual returns (bool) {
return true;
}
}// SPDX-License-Identifier: BSD-3-Clause
pragma solidity 0.8.25;
import { IERC20Upgradeable } from "@openzeppelin/contracts-upgradeable/token/ERC20/IERC20Upgradeable.sol";
import { SafeERC20Upgradeable } from "@openzeppelin/contracts-upgradeable/token/ERC20/utils/SafeERC20Upgradeable.sol";
import { ResilientOracleInterface } from "@venusprotocol/oracle/contracts/interfaces/OracleInterface.sol";
import { Ownable2StepUpgradeable } from "@openzeppelin/contracts-upgradeable/access/Ownable2StepUpgradeable.sol";
import { ApproveOrRevert } from "./lib/ApproveOrRevert.sol";
import { Comptroller } from "./Comptroller.sol";
import { Action } from "./ComptrollerInterface.sol";
import { VToken } from "./VToken.sol";
contract WUSDMLiquidator is Ownable2StepUpgradeable {
using SafeERC20Upgradeable for IERC20Upgradeable;
using ApproveOrRevert for IERC20Upgradeable;
struct OriginalConfig {
uint256 minLiquidatableCollateral;
uint256 closeFactor;
uint256 wUSDMCollateralFactor;
uint256 wUSDMLiquidationThreshold;
uint256 vWUSDMProtocolSeizeShare;
uint256 vWETHProtocolSeizeShare;
uint256 vUSDCEProtocolSeizeShare;
uint256 vUSDTProtocolSeizeShare;
}
OriginalConfig private _originalConfig;
ResilientOracleInterface public constant ORACLE =
ResilientOracleInterface(0xDe564a4C887d5ad315a19a96DC81991c98b12182);
Comptroller public constant COMPTROLLER = Comptroller(0xddE4D098D9995B659724ae6d5E3FB9681Ac941B1);
VToken public constant VWUSDM = VToken(0x183dE3C349fCf546aAe925E1c7F364EA6FB4033c);
VToken public constant VWETH = VToken(0x1Fa916C27c7C2c4602124A14C77Dbb40a5FF1BE8);
VToken public constant VUSDCE = VToken(0x1aF23bD57c62A99C59aD48236553D0Dd11e49D2D);
VToken public constant VUSDT = VToken(0x69cDA960E3b20DFD480866fFfd377Ebe40bd0A46);
uint256 public constant LIQUIDATION_INCENTIVE = 1.1e18;
address public constant A2 = 0x4C0e4B3e6c5756fb31886a0A01079701ffEC0561;
address public constant A3 = 0x924EDEd3D010b3F20009b872183eec48D0111265;
address public constant A4 = 0x2B379d8c90e02016658aD00ba2566F55E814C369;
address public constant A5 = 0xfffAB9120d9Df39EEa07063F6465a0aA45a80C52;
IERC20Upgradeable public immutable WUSDM;
IERC20Upgradeable public immutable WETH;
IERC20Upgradeable public immutable USDCE;
IERC20Upgradeable public immutable USDT;
/// @notice Emitted when token is swept from the contract
event SweepToken(address indexed token, address indexed receiver, uint256 amount);
/**
* @notice Constructor to initialize immutable token references and disable initializers
*/
constructor() {
WUSDM = IERC20Upgradeable(VWUSDM.underlying());
WETH = IERC20Upgradeable(VWETH.underlying());
USDCE = IERC20Upgradeable(VUSDCE.underlying());
USDT = IERC20Upgradeable(VUSDT.underlying());
_disableInitializers();
}
/**
* @notice Initializes the contract and sets up ownership
* @dev This function can only be called once
*/
function initialize() external initializer {
__Ownable2Step_init();
}
/**
* @notice Executes the liquidation and repayment process
* @dev This function performs the following steps:
* 1. Configures the markets by setting collateral factors, close factors, and enabling actions.
* 2. Supplies WUSDM as collateral.
* 3. Borrows WETH and liquidates borrowers with WETH debt.
* 4. Borrows USDCE and liquidates borrowers with USDCE debt.
* 5. Borrows USDT and liquidates borrowers with USDT debt.
* 6. Restores the original market configuration.
*/
function run() external onlyOwner {
uint256 wusdmPrice = ORACLE.getPrice(address(WUSDM));
uint256 vwUSDMExchangeRate = VWUSDM.exchangeRateCurrent();
_configureMarkets();
_supplyCollateral();
_borrowWETHAndLiquidateBorrowers(wusdmPrice, vwUSDMExchangeRate);
_borrowUSDCeAndLiquidateBorrowers(wusdmPrice, vwUSDMExchangeRate);
_borrowUSDTAndLiquidateBorrowers(wusdmPrice, vwUSDMExchangeRate);
_restoreOriginalConfiguration();
}
/**
* @notice Sweeps the input token address tokens from the contract and sends them to the owner
* @param token Address of the token
* @custom:event SweepToken emits on success
* @custom:access Controlled by Governance
*/
function sweepToken(IERC20Upgradeable token) external onlyOwner {
uint256 balance = token.balanceOf(address(this));
if (balance > 0) {
address owner_ = owner();
token.safeTransfer(owner_, balance);
emit SweepToken(address(token), owner_, balance);
}
}
function _configureMarkets() internal {
(, uint256 wUSDMCollateralFactor, uint256 wUSDMLiquidationThreshold) = COMPTROLLER.markets(address(VWUSDM));
_originalConfig = OriginalConfig({
minLiquidatableCollateral: COMPTROLLER.minLiquidatableCollateral(),
closeFactor: COMPTROLLER.closeFactorMantissa(),
wUSDMCollateralFactor: wUSDMCollateralFactor,
wUSDMLiquidationThreshold: wUSDMLiquidationThreshold,
vWUSDMProtocolSeizeShare: VWUSDM.protocolSeizeShareMantissa(),
vWETHProtocolSeizeShare: VWETH.protocolSeizeShareMantissa(),
vUSDCEProtocolSeizeShare: VUSDCE.protocolSeizeShareMantissa(),
vUSDTProtocolSeizeShare: VUSDT.protocolSeizeShareMantissa()
});
COMPTROLLER.setMinLiquidatableCollateral(0);
COMPTROLLER.setCloseFactor(1e18);
COMPTROLLER.setCollateralFactor(VWUSDM, 0.78e18, 0.78e18);
VToken[] memory markets = new VToken[](1);
Action[] memory actions = new Action[](2);
markets[0] = VWUSDM;
actions[0] = Action.MINT;
actions[1] = Action.ENTER_MARKET;
COMPTROLLER.setActionsPaused(markets, actions, false);
VWUSDM.setProtocolSeizeShare(0);
VWETH.setProtocolSeizeShare(0);
VUSDCE.setProtocolSeizeShare(0);
VUSDT.setProtocolSeizeShare(0);
}
function _restoreOriginalConfiguration() internal {
COMPTROLLER.setMinLiquidatableCollateral(_originalConfig.minLiquidatableCollateral);
COMPTROLLER.setCloseFactor(_originalConfig.closeFactor);
COMPTROLLER.setCollateralFactor(
VWUSDM,
_originalConfig.wUSDMCollateralFactor,
_originalConfig.wUSDMLiquidationThreshold
);
VToken[] memory markets = new VToken[](1);
Action[] memory actions = new Action[](2);
markets[0] = VWUSDM;
actions[0] = Action.MINT;
actions[1] = Action.ENTER_MARKET;
COMPTROLLER.setActionsPaused(markets, actions, true);
VWUSDM.setProtocolSeizeShare(_originalConfig.vWUSDMProtocolSeizeShare);
VWETH.setProtocolSeizeShare(_originalConfig.vWETHProtocolSeizeShare);
VUSDCE.setProtocolSeizeShare(_originalConfig.vUSDCEProtocolSeizeShare);
VUSDT.setProtocolSeizeShare(_originalConfig.vUSDTProtocolSeizeShare);
// Get some gas refunds for zeroing out storage
_originalConfig = OriginalConfig(0, 0, 0, 0, 0, 0, 0, 0);
}
function _supplyCollateral() internal {
uint256 amount = WUSDM.balanceOf(address(this));
WUSDM.approveOrRevert(address(VWUSDM), amount);
VWUSDM.mint(amount);
WUSDM.approveOrRevert(address(VWUSDM), 0);
address[] memory markets = new address[](1);
markets[0] = address(VWUSDM);
COMPTROLLER.enterMarkets(markets);
}
function _borrowWETHAndLiquidateBorrowers(uint256 wusdmPrice, uint256 vwUSDMExchangeRate) internal {
uint256 a2Debt = _getDebt(VWETH, A2);
uint256 ratio = _getBorrowedTokensToCollateralVTokensRatio(VWETH, wusdmPrice, vwUSDMExchangeRate);
uint256 amountToRepay = _getAmountToRepayDuringLiquidation(A2, a2Debt, VWUSDM, ratio);
if (amountToRepay > 0) {
_borrow(VWETH, amountToRepay);
WETH.approveOrRevert(address(VWETH), amountToRepay);
VWETH.liquidateBorrow(A2, amountToRepay, VWUSDM);
WETH.approveOrRevert(address(VWETH), 0);
}
}
function _borrowUSDCeAndLiquidateBorrowers(uint256 wusdmPrice, uint256 vwUSDMExchangeRate) internal {
uint256 a3Debt = _getDebt(VUSDCE, A3);
uint256 a4Debt = _getDebt(VUSDCE, A4);
uint256 a5Debt = _getDebt(VUSDCE, A5);
uint256 totalRepayment = a3Debt + a4Debt + a5Debt;
_borrow(VUSDCE, totalRepayment);
USDCE.approveOrRevert(address(VUSDCE), totalRepayment);
uint256 ratio = _getBorrowedTokensToCollateralVTokensRatio(VUSDCE, wusdmPrice, vwUSDMExchangeRate);
a3Debt = _liquidateAsMuchAsPossible(A3, a3Debt, VUSDCE, VWUSDM, ratio);
a4Debt = _liquidateAsMuchAsPossible(A4, a4Debt, VUSDCE, VWUSDM, ratio);
a5Debt = _liquidateAsMuchAsPossible(A5, a5Debt, VUSDCE, VWUSDM, ratio);
_repay(A3, VUSDCE, a3Debt > 1 ? a3Debt - 1 : 0);
_repay(A4, VUSDCE, a4Debt > 1 ? a4Debt - 1 : 0);
_repay(A5, VUSDCE, a5Debt > 1 ? a5Debt - 1 : 0);
USDCE.approveOrRevert(address(VUSDCE), 0);
}
function _borrowUSDTAndLiquidateBorrowers(uint256 wusdmPrice, uint256 vwUSDMExchangeRate) internal {
uint256 a3Debt = _getDebt(VUSDT, A3);
uint256 a4Debt = _getDebt(VUSDT, A4);
uint256 a5Debt = _getDebt(VUSDT, A5);
uint256 totalRepayment = a3Debt + a4Debt + a5Debt;
_borrow(VUSDT, totalRepayment);
USDT.approveOrRevert(address(VUSDT), totalRepayment);
uint256 ratio = _getBorrowedTokensToCollateralVTokensRatio(VUSDT, wusdmPrice, vwUSDMExchangeRate);
a3Debt = _liquidateAsMuchAsPossible(A3, a3Debt, VUSDT, VWUSDM, ratio);
a4Debt = _liquidateAsMuchAsPossible(A4, a4Debt, VUSDT, VWUSDM, ratio);
a5Debt = _liquidateAsMuchAsPossible(A5, a5Debt, VUSDT, VWUSDM, ratio);
_repay(A3, VUSDT, a3Debt > 1 ? a3Debt - 1 : 0);
_repay(A4, VUSDT, a4Debt > 1 ? a4Debt - 1 : 0);
_repay(A5, VUSDT, a5Debt > 1 ? a5Debt - 1 : 0);
USDT.approveOrRevert(address(VUSDT), 0);
}
function _getDebt(VToken market, address account) internal returns (uint256) {
if (!_isUnderwater(account)) {
return 0; // no debt if not underwater
}
return market.borrowBalanceCurrent(account);
}
function _borrow(VToken market, uint256 amount) internal {
market.borrow(amount);
}
function _repay(address account, VToken market, uint256 amount) internal {
if (amount == 0) {
return;
}
market.repayBorrowBehalf(account, amount);
}
function _liquidateAsMuchAsPossible(
address account,
uint256 debt,
VToken market,
VToken collateral,
uint256 ratio
) internal returns (uint256) {
if (debt == 0) {
return 0;
}
uint256 amountToRepay = _getAmountToRepayDuringLiquidation(account, debt, collateral, ratio);
if (amountToRepay > 1) {
market.liquidateBorrow(account, amountToRepay, collateral);
}
return debt - amountToRepay;
}
function _getAmountToRepayDuringLiquidation(
address account,
uint256 debt,
VToken collateral,
uint256 ratio
) internal view returns (uint256) {
if (debt == 0) {
return 0;
}
uint256 seizeableVTokens = collateral.balanceOf(account);
if (seizeableVTokens <= 1) {
return debt;
}
// Ideally, we should round up here so that we repay as much as possible during the liquidation,
// leaving no vTokens supplied, but due to anvil-zksync issues, we want to keep at least 1 wei
// of vTokens supplied so that we're able to test it later
uint256 amountToRepayForEntireCollateral = ((seizeableVTokens - 1) * 1e18) / ratio;
// Keeping 1 wei of debt, for the same purpose
return debt > amountToRepayForEntireCollateral ? amountToRepayForEntireCollateral : (debt - 1);
}
function _getBorrowedTokensToCollateralVTokensRatio(
VToken borrowed,
uint256 collateralPrice,
uint256 collateralExchangeRate
) internal view returns (uint256) {
uint256 borrowedPrice = ORACLE.getUnderlyingPrice(address(borrowed));
return (borrowedPrice * LIQUIDATION_INCENTIVE * 1e18) / (collateralPrice * collateralExchangeRate);
}
function _isUnderwater(address account) internal view returns (bool) {
(, uint256 liquidity, uint256 shortfall) = COMPTROLLER.getAccountLiquidity(account);
return liquidity == 0 && shortfall > 0;
}
}{
"optimizer": {
"enabled": true,
"runs": 200,
"details": {
"yul": true
}
},
"evmVersion": "paris",
"outputSelection": {
"*": {
"*": [
"evm.bytecode",
"evm.deployedBytecode",
"abi"
]
}
},
"metadata": {
"useLiteralContent": true
}
}Contract Security Audit
- No Contract Security Audit Submitted- Submit Audit Here
Contract ABI
API[{"inputs":[{"internalType":"bool","name":"timeBased_","type":"bool"},{"internalType":"uint256","name":"blocksPerYear_","type":"uint256"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[],"name":"InvalidBlocksPerYear","type":"error"},{"inputs":[],"name":"InvalidTimeBasedConfiguration","type":"error"},{"inputs":[],"name":"blocksOrSecondsPerYear","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"poolRegistryAddress","type":"address"}],"name":"getAllPools","outputs":[{"components":[{"internalType":"string","name":"name","type":"string"},{"internalType":"address","name":"creator","type":"address"},{"internalType":"address","name":"comptroller","type":"address"},{"internalType":"uint256","name":"blockPosted","type":"uint256"},{"internalType":"uint256","name":"timestampPosted","type":"uint256"},{"internalType":"string","name":"category","type":"string"},{"internalType":"string","name":"logoURL","type":"string"},{"internalType":"string","name":"description","type":"string"},{"internalType":"address","name":"priceOracle","type":"address"},{"internalType":"uint256","name":"closeFactor","type":"uint256"},{"internalType":"uint256","name":"liquidationIncentive","type":"uint256"},{"internalType":"uint256","name":"minLiquidatableCollateral","type":"uint256"},{"components":[{"internalType":"address","name":"vToken","type":"address"},{"internalType":"uint256","name":"exchangeRateCurrent","type":"uint256"},{"internalType":"uint256","name":"supplyRatePerBlockOrTimestamp","type":"uint256"},{"internalType":"uint256","name":"borrowRatePerBlockOrTimestamp","type":"uint256"},{"internalType":"uint256","name":"reserveFactorMantissa","type":"uint256"},{"internalType":"uint256","name":"supplyCaps","type":"uint256"},{"internalType":"uint256","name":"borrowCaps","type":"uint256"},{"internalType":"uint256","name":"totalBorrows","type":"uint256"},{"internalType":"uint256","name":"totalReserves","type":"uint256"},{"internalType":"uint256","name":"totalSupply","type":"uint256"},{"internalType":"uint256","name":"totalCash","type":"uint256"},{"internalType":"bool","name":"isListed","type":"bool"},{"internalType":"uint256","name":"collateralFactorMantissa","type":"uint256"},{"internalType":"address","name":"underlyingAssetAddress","type":"address"},{"internalType":"uint256","name":"vTokenDecimals","type":"uint256"},{"internalType":"uint256","name":"underlyingDecimals","type":"uint256"},{"internalType":"uint256","name":"pausedActions","type":"uint256"}],"internalType":"struct PoolLens.VTokenMetadata[]","name":"vTokens","type":"tuple[]"}],"internalType":"struct PoolLens.PoolData[]","name":"","type":"tuple[]"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getBlockNumberOrTimestamp","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"account","type":"address"},{"internalType":"address","name":"comptrollerAddress","type":"address"}],"name":"getPendingRewards","outputs":[{"components":[{"internalType":"address","name":"distributorAddress","type":"address"},{"internalType":"address","name":"rewardTokenAddress","type":"address"},{"internalType":"uint256","name":"totalRewards","type":"uint256"},{"components":[{"internalType":"address","name":"vTokenAddress","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"internalType":"struct PoolLens.PendingReward[]","name":"pendingRewards","type":"tuple[]"}],"internalType":"struct PoolLens.RewardSummary[]","name":"","type":"tuple[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"comptrollerAddress","type":"address"}],"name":"getPoolBadDebt","outputs":[{"components":[{"internalType":"address","name":"comptroller","type":"address"},{"internalType":"uint256","name":"totalBadDebtUsd","type":"uint256"},{"components":[{"internalType":"address","name":"vTokenAddress","type":"address"},{"internalType":"uint256","name":"badDebtUsd","type":"uint256"}],"internalType":"struct PoolLens.BadDebt[]","name":"badDebts","type":"tuple[]"}],"internalType":"struct PoolLens.BadDebtSummary","name":"","type":"tuple"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"poolRegistryAddress","type":"address"},{"internalType":"address","name":"comptroller","type":"address"}],"name":"getPoolByComptroller","outputs":[{"components":[{"internalType":"string","name":"name","type":"string"},{"internalType":"address","name":"creator","type":"address"},{"internalType":"address","name":"comptroller","type":"address"},{"internalType":"uint256","name":"blockPosted","type":"uint256"},{"internalType":"uint256","name":"timestampPosted","type":"uint256"},{"internalType":"string","name":"category","type":"string"},{"internalType":"string","name":"logoURL","type":"string"},{"internalType":"string","name":"description","type":"string"},{"internalType":"address","name":"priceOracle","type":"address"},{"internalType":"uint256","name":"closeFactor","type":"uint256"},{"internalType":"uint256","name":"liquidationIncentive","type":"uint256"},{"internalType":"uint256","name":"minLiquidatableCollateral","type":"uint256"},{"components":[{"internalType":"address","name":"vToken","type":"address"},{"internalType":"uint256","name":"exchangeRateCurrent","type":"uint256"},{"internalType":"uint256","name":"supplyRatePerBlockOrTimestamp","type":"uint256"},{"internalType":"uint256","name":"borrowRatePerBlockOrTimestamp","type":"uint256"},{"internalType":"uint256","name":"reserveFactorMantissa","type":"uint256"},{"internalType":"uint256","name":"supplyCaps","type":"uint256"},{"internalType":"uint256","name":"borrowCaps","type":"uint256"},{"internalType":"uint256","name":"totalBorrows","type":"uint256"},{"internalType":"uint256","name":"totalReserves","type":"uint256"},{"internalType":"uint256","name":"totalSupply","type":"uint256"},{"internalType":"uint256","name":"totalCash","type":"uint256"},{"internalType":"bool","name":"isListed","type":"bool"},{"internalType":"uint256","name":"collateralFactorMantissa","type":"uint256"},{"internalType":"address","name":"underlyingAssetAddress","type":"address"},{"internalType":"uint256","name":"vTokenDecimals","type":"uint256"},{"internalType":"uint256","name":"underlyingDecimals","type":"uint256"},{"internalType":"uint256","name":"pausedActions","type":"uint256"}],"internalType":"struct PoolLens.VTokenMetadata[]","name":"vTokens","type":"tuple[]"}],"internalType":"struct PoolLens.PoolData","name":"","type":"tuple"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"poolRegistryAddress","type":"address"},{"components":[{"internalType":"string","name":"name","type":"string"},{"internalType":"address","name":"creator","type":"address"},{"internalType":"address","name":"comptroller","type":"address"},{"internalType":"uint256","name":"blockPosted","type":"uint256"},{"internalType":"uint256","name":"timestampPosted","type":"uint256"}],"internalType":"struct PoolRegistryInterface.VenusPool","name":"venusPool","type":"tuple"}],"name":"getPoolDataFromVenusPool","outputs":[{"components":[{"internalType":"string","name":"name","type":"string"},{"internalType":"address","name":"creator","type":"address"},{"internalType":"address","name":"comptroller","type":"address"},{"internalType":"uint256","name":"blockPosted","type":"uint256"},{"internalType":"uint256","name":"timestampPosted","type":"uint256"},{"internalType":"string","name":"category","type":"string"},{"internalType":"string","name":"logoURL","type":"string"},{"internalType":"string","name":"description","type":"string"},{"internalType":"address","name":"priceOracle","type":"address"},{"internalType":"uint256","name":"closeFactor","type":"uint256"},{"internalType":"uint256","name":"liquidationIncentive","type":"uint256"},{"internalType":"uint256","name":"minLiquidatableCollateral","type":"uint256"},{"components":[{"internalType":"address","name":"vToken","type":"address"},{"internalType":"uint256","name":"exchangeRateCurrent","type":"uint256"},{"internalType":"uint256","name":"supplyRatePerBlockOrTimestamp","type":"uint256"},{"internalType":"uint256","name":"borrowRatePerBlockOrTimestamp","type":"uint256"},{"internalType":"uint256","name":"reserveFactorMantissa","type":"uint256"},{"internalType":"uint256","name":"supplyCaps","type":"uint256"},{"internalType":"uint256","name":"borrowCaps","type":"uint256"},{"internalType":"uint256","name":"totalBorrows","type":"uint256"},{"internalType":"uint256","name":"totalReserves","type":"uint256"},{"internalType":"uint256","name":"totalSupply","type":"uint256"},{"internalType":"uint256","name":"totalCash","type":"uint256"},{"internalType":"bool","name":"isListed","type":"bool"},{"internalType":"uint256","name":"collateralFactorMantissa","type":"uint256"},{"internalType":"address","name":"underlyingAssetAddress","type":"address"},{"internalType":"uint256","name":"vTokenDecimals","type":"uint256"},{"internalType":"uint256","name":"underlyingDecimals","type":"uint256"},{"internalType":"uint256","name":"pausedActions","type":"uint256"}],"internalType":"struct PoolLens.VTokenMetadata[]","name":"vTokens","type":"tuple[]"}],"internalType":"struct PoolLens.PoolData","name":"","type":"tuple"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"poolRegistryAddress","type":"address"},{"internalType":"address","name":"asset","type":"address"}],"name":"getPoolsSupportedByAsset","outputs":[{"internalType":"address[]","name":"","type":"address[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"poolRegistryAddress","type":"address"},{"internalType":"address","name":"comptroller","type":"address"},{"internalType":"address","name":"asset","type":"address"}],"name":"getVTokenForAsset","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"isTimeBased","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"contract VToken","name":"vToken","type":"address"},{"internalType":"address","name":"account","type":"address"}],"name":"vTokenBalances","outputs":[{"components":[{"internalType":"address","name":"vToken","type":"address"},{"internalType":"uint256","name":"balanceOf","type":"uint256"},{"internalType":"uint256","name":"borrowBalanceCurrent","type":"uint256"},{"internalType":"uint256","name":"balanceOfUnderlying","type":"uint256"},{"internalType":"uint256","name":"tokenBalance","type":"uint256"},{"internalType":"uint256","name":"tokenAllowance","type":"uint256"}],"internalType":"struct PoolLens.VTokenBalances","name":"","type":"tuple"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"contract VToken[]","name":"vTokens","type":"address[]"},{"internalType":"address","name":"account","type":"address"}],"name":"vTokenBalancesAll","outputs":[{"components":[{"internalType":"address","name":"vToken","type":"address"},{"internalType":"uint256","name":"balanceOf","type":"uint256"},{"internalType":"uint256","name":"borrowBalanceCurrent","type":"uint256"},{"internalType":"uint256","name":"balanceOfUnderlying","type":"uint256"},{"internalType":"uint256","name":"tokenBalance","type":"uint256"},{"internalType":"uint256","name":"tokenAllowance","type":"uint256"}],"internalType":"struct PoolLens.VTokenBalances[]","name":"","type":"tuple[]"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"contract VToken","name":"vToken","type":"address"}],"name":"vTokenMetadata","outputs":[{"components":[{"internalType":"address","name":"vToken","type":"address"},{"internalType":"uint256","name":"exchangeRateCurrent","type":"uint256"},{"internalType":"uint256","name":"supplyRatePerBlockOrTimestamp","type":"uint256"},{"internalType":"uint256","name":"borrowRatePerBlockOrTimestamp","type":"uint256"},{"internalType":"uint256","name":"reserveFactorMantissa","type":"uint256"},{"internalType":"uint256","name":"supplyCaps","type":"uint256"},{"internalType":"uint256","name":"borrowCaps","type":"uint256"},{"internalType":"uint256","name":"totalBorrows","type":"uint256"},{"internalType":"uint256","name":"totalReserves","type":"uint256"},{"internalType":"uint256","name":"totalSupply","type":"uint256"},{"internalType":"uint256","name":"totalCash","type":"uint256"},{"internalType":"bool","name":"isListed","type":"bool"},{"internalType":"uint256","name":"collateralFactorMantissa","type":"uint256"},{"internalType":"address","name":"underlyingAssetAddress","type":"address"},{"internalType":"uint256","name":"vTokenDecimals","type":"uint256"},{"internalType":"uint256","name":"underlyingDecimals","type":"uint256"},{"internalType":"uint256","name":"pausedActions","type":"uint256"}],"internalType":"struct PoolLens.VTokenMetadata","name":"","type":"tuple"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"contract VToken[]","name":"vTokens","type":"address[]"}],"name":"vTokenMetadataAll","outputs":[{"components":[{"internalType":"address","name":"vToken","type":"address"},{"internalType":"uint256","name":"exchangeRateCurrent","type":"uint256"},{"internalType":"uint256","name":"supplyRatePerBlockOrTimestamp","type":"uint256"},{"internalType":"uint256","name":"borrowRatePerBlockOrTimestamp","type":"uint256"},{"internalType":"uint256","name":"reserveFactorMantissa","type":"uint256"},{"internalType":"uint256","name":"supplyCaps","type":"uint256"},{"internalType":"uint256","name":"borrowCaps","type":"uint256"},{"internalType":"uint256","name":"totalBorrows","type":"uint256"},{"internalType":"uint256","name":"totalReserves","type":"uint256"},{"internalType":"uint256","name":"totalSupply","type":"uint256"},{"internalType":"uint256","name":"totalCash","type":"uint256"},{"internalType":"bool","name":"isListed","type":"bool"},{"internalType":"uint256","name":"collateralFactorMantissa","type":"uint256"},{"internalType":"address","name":"underlyingAssetAddress","type":"address"},{"internalType":"uint256","name":"vTokenDecimals","type":"uint256"},{"internalType":"uint256","name":"underlyingDecimals","type":"uint256"},{"internalType":"uint256","name":"pausedActions","type":"uint256"}],"internalType":"struct PoolLens.VTokenMetadata[]","name":"","type":"tuple[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"contract VToken","name":"vToken","type":"address"}],"name":"vTokenUnderlyingPrice","outputs":[{"components":[{"internalType":"address","name":"vToken","type":"address"},{"internalType":"uint256","name":"underlyingPrice","type":"uint256"}],"internalType":"struct PoolLens.VTokenUnderlyingPrice","name":"","type":"tuple"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"contract VToken[]","name":"vTokens","type":"address[]"}],"name":"vTokenUnderlyingPriceAll","outputs":[{"components":[{"internalType":"address","name":"vToken","type":"address"},{"internalType":"uint256","name":"underlyingPrice","type":"uint256"}],"internalType":"struct PoolLens.VTokenUnderlyingPrice[]","name":"","type":"tuple[]"}],"stateMutability":"view","type":"function"}]Contract Creation Code
60e060405234801561001057600080fd5b5060405161406e38038061406e83398101604081905261002f916100dc565b81818115801561003d575080155b1561005b576040516302723dfb60e21b815260040160405180910390fd5b81801561006757508015155b156100855760405163ae0fcab360e01b815260040160405180910390fd5b81151560a05281610096578061009c565b6301e133805b608052816100b3576100d460201b611dfb176100be565b6100d860201b611dff175b6001600160401b031660c0525061010f92505050565b4390565b4290565b600080604083850312156100ef57600080fd5b825180151581146100ff57600080fd5b6020939093015192949293505050565b60805160a05160c051613f296101456000396000611dcf0152600081816102a60152611ed0015260006101d10152613f296000f3fe608060405234801561001057600080fd5b50600436106101005760003560e01c80637c51b64211610097578063c7ad089511610066578063c7ad0895146102a1578063d77ebf96146102d8578063e0a67f11146102f8578063e1d146fb1461031857600080fd5b80637c51b642146102215780637c84e3b314610241578063aa5dbd2314610261578063b31242391461028157600080fd5b806347d86a81116100d357806347d86a811461018e57806355dd9515146101a15780636857249c146101cc5780637a27db571461020157600080fd5b80630d3ae318146101055780631f884fdf1461012e578063345954dc1461014e5780633e3e399c1461016e575b600080fd5b610118610113366004612f40565b610320565b60405161012591906132ad565b60405180910390f35b61014161013c36600461330b565b610655565b604051610125919061334c565b61016161015c3660046133ac565b61071d565b60405161012591906133c9565b61018161017c3660046133ac565b610850565b604051610125919061342d565b61011861019c3660046134b7565b610bc2565b6101b46101af3660046134f0565b610c49565b6040516001600160a01b039091168152602001610125565b6101f37f000000000000000000000000000000000000000000000000000000000000000081565b604051908152602001610125565b61021461020f3660046134b7565b610cc9565b604051610125919061353b565b61023461022f366004613617565b610fd6565b60405161012591906136a2565b61025461024f3660046133ac565b61108f565b60405161012591906136f0565b61027461026f3660046133ac565b6111f9565b6040516101259190613710565b61029461028f3660046134b7565b6119b9565b604051610125919061371f565b6102c87f000000000000000000000000000000000000000000000000000000000000000081565b6040519015158152602001610125565b6102eb6102e63660046134b7565b611ca0565b604051610125919061372d565b61030b610306366004613791565b611d13565b604051610125919061382a565b6101f3611dc8565b610328612d07565b6000826040015190506000816001600160a01b031663b0772d0b6040518163ffffffff1660e01b8152600401600060405180830381865afa158015610371573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f19168201604052610399919081019061386d565b905060006103a682611d13565b60408681015190516328ebbe8b60e21b81526001600160a01b039182166004820152919250879160009183169063a3aefa2c90602401600060405180830381865afa1580156103f9573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f191682016040526104219190810190613940565b90506000876040015190506000604051806101a001604052808a6000015181526020018a602001516001600160a01b031681526020018a604001516001600160a01b031681526020018a6060015181526020018a608001518152602001846000015181526020018460200151815260200184604001518152602001836001600160a01b0316637dc0d1d06040518163ffffffff1660e01b8152600401602060405180830381865afa1580156104da573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906104fe91906139f4565b6001600160a01b03168152602001836001600160a01b031663e87554466040518163ffffffff1660e01b8152600401602060405180830381865afa15801561054a573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061056e9190613a11565b8152602001836001600160a01b0316634ada90af6040518163ffffffff1660e01b8152600401602060405180830381865afa1580156105b1573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906105d59190613a11565b8152602001836001600160a01b031663db5c65de6040518163ffffffff1660e01b8152600401602060405180830381865afa158015610618573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061063c9190613a11565b8152602001959095525092955050505050505b92915050565b6060816000816001600160401b0381111561067257610672612e89565b6040519080825280602002602001820160405280156106b757816020015b60408051808201909152600080825260208201528152602001906001900390816106905790505b50905060005b82811015610714576106ef8686838181106106da576106da613a2a565b905060200201602081019061024f91906133ac565b82828151811061070157610701613a2a565b60209081029190910101526001016106bd565b50949350505050565b606060008290506000816001600160a01b031663d88ff1f46040518163ffffffff1660e01b8152600401600060405180830381865afa158015610764573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f1916820160405261078c9190810190613ac3565b80519091506000816001600160401b038111156107ab576107ab612e89565b6040519080825280602002602001820160405280156107e457816020015b6107d1612d07565b8152602001906001900390816107c95790505b50905060005b8281101561084657600084828151811061080657610806613a2a565b60200260200101519050600061081c8983610320565b90508084848151811061083157610831613a2a565b602090810291909101015250506001016107ea565b5095945050505050565b604080516060808201835260008083526020830152918101919091526000808390506000816001600160a01b031663b0772d0b6040518163ffffffff1660e01b8152600401600060405180830381865afa1580156108b2573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f191682016040526108da919081019061386d565b90506000826001600160a01b0316637dc0d1d06040518163ffffffff1660e01b8152600401602060405180830381865afa15801561091c573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061094091906139f4565b9050600082516001600160401b0381111561095d5761095d612e89565b6040519080825280602002602001820160405280156109a257816020015b604080518082019091526000808252602082015281526020019060019003908161097b5790505b5090506109d2604051806060016040528060006001600160a01b0316815260200160008152602001606081525090565b6001600160a01b03881681526040810182905260005b8451811015610bae576040805180820190915260008082526020820152858281518110610a1757610a17613a2a565b602002602001015181600001906001600160a01b031690816001600160a01b031681525050670de0b6b3a7640000856001600160a01b031663fc57d4df888581518110610a6657610a66613a2a565b60200260200101516040518263ffffffff1660e01b8152600401610a9991906001600160a01b0391909116815260200190565b602060405180830381865afa158015610ab6573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610ada9190613a11565b878481518110610aec57610aec613a2a565b60200260200101516001600160a01b031663bbcac5576040518163ffffffff1660e01b8152600401602060405180830381865afa158015610b31573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610b559190613a11565b610b5f9190613b89565b610b699190613ba0565b60208201526040830151805182919084908110610b8857610b88613a2a565b6020026020010181905250806020015188610ba39190613bc2565b9750506001016109e8565b506020810195909552509295945050505050565b610bca612d07565b604051637aee632d60e01b81526001600160a01b0383811660048301528491610c4191839190821690637aee632d90602401600060405180830381865afa158015610c19573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f191682016040526101139190810190613bd5565b949350505050565b60405163266e0a7f60e01b81526001600160a01b0383811660048301528281166024830152600091859182169063266e0a7f90604401602060405180830381865afa158015610c9c573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610cc091906139f4565b95945050505050565b60606000826001600160a01b031663b0772d0b6040518163ffffffff1660e01b8152600401600060405180830381865afa158015610d0b573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f19168201604052610d33919081019061386d565b90506000836001600160a01b03166361252fd16040518163ffffffff1660e01b8152600401600060405180830381865afa158015610d75573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f19168201604052610d9d9190810190613c09565b9050600081516001600160401b03811115610dba57610dba612e89565b604051908082528060200260200182016040528015610e0b57816020015b6040805160808101825260008082526020808301829052928201526060808201528252600019909201910181610dd85790505b50905060005b8251811015610846576040805160808101825260008082526020820181905291810191909152606080820152838281518110610e4f57610e4f613a2a565b60209081029190910101516001600160a01b031681528351849083908110610e7957610e79613a2a565b60200260200101516001600160a01b031663f7c618c16040518163ffffffff1660e01b8152600401602060405180830381865afa158015610ebe573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610ee291906139f4565b6001600160a01b031660208201528351849083908110610f0457610f04613a2a565b6020908102919091010151604051631627ee8960e01b81526001600160a01b038a8116600483015290911690631627ee8990602401602060405180830381865afa158015610f56573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610f7a9190613a11565b816040018181525050610fa78886868581518110610f9a57610f9a613a2a565b6020026020010151611e03565b816060018190525080838381518110610fc257610fc2613a2a565b602090810291909101015250600101610e11565b6060826000816001600160401b03811115610ff357610ff3612e89565b60405190808252806020026020018201604052801561102c57816020015b611019612d8a565b8152602001906001900390816110115790505b50905060005b828110156108465761106a87878381811061104f5761104f613a2a565b905060200201602081019061106491906133ac565b866119b9565b82828151811061107c5761107c613a2a565b6020908102919091010152600101611032565b60408051808201909152600080825260208201526000826001600160a01b0316635fe3b5676040518163ffffffff1660e01b8152600401602060405180830381865afa1580156110e3573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061110791906139f4565b90506000816001600160a01b0316637dc0d1d06040518163ffffffff1660e01b8152600401602060405180830381865afa158015611149573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061116d91906139f4565b6040805180820182526001600160a01b03808816808352925163fc57d4df60e01b815260048101939093529293509160208301919084169063fc57d4df90602401602060405180830381865afa1580156111cb573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906111ef9190613a11565b9052949350505050565b611201612dc9565b6000826001600160a01b031663182df0f56040518163ffffffff1660e01b8152600401602060405180830381865afa158015611241573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906112659190613a11565b90506000836001600160a01b0316635fe3b5676040518163ffffffff1660e01b8152600401602060405180830381865afa1580156112a7573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906112cb91906139f4565b604051638e8f294b60e01b81526001600160a01b03868116600483015291925082916000918291841690638e8f294b906024016040805180830381865afa15801561131a573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061133e9190613ca7565b915091506000876001600160a01b0316636f307dc36040518163ffffffff1660e01b8152600401602060405180830381865afa158015611382573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906113a691906139f4565b90506000816001600160a01b031663313ce5676040518163ffffffff1660e01b8152600401602060405180830381865afa1580156113e8573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061140c9190613cd3565b60ff1690506000805b600860ff8216116114d2576000886001600160a01b031663e85a29608d8460ff16600881111561144757611447613cf6565b6040518363ffffffff1660e01b8152600401611464929190613d0c565b602060405180830381865afa158015611481573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906114a59190613d47565b6114b05760006114b3565b60015b60ff9081169083161b9290921791506114cb81613d62565b9050611415565b506000808b6001600160a01b03166318160ddd6040518163ffffffff1660e01b8152600401602060405180830381865afa158015611514573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906115389190613a11565b11156115a3578a6001600160a01b031663ae9d70b06040518163ffffffff1660e01b8152600401602060405180830381865afa15801561157c573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906115a09190613a11565b90505b6040518061022001604052808c6001600160a01b031681526020018a81526020018281526020018c6001600160a01b031663f8f9da286040518163ffffffff1660e01b8152600401602060405180830381865afa158015611608573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061162c9190613a11565b81526020018c6001600160a01b031663173b99046040518163ffffffff1660e01b8152600401602060405180830381865afa15801561166f573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906116939190613a11565b81526040516302c3bcbb60e01b81526001600160a01b038e811660048301526020909201918a16906302c3bcbb90602401602060405180830381865afa1580156116e1573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906117059190613a11565b815260405163252c221960e11b81526001600160a01b038e811660048301526020909201918a1690634a58443290602401602060405180830381865afa158015611753573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906117779190613a11565b81526020018c6001600160a01b03166347bd37186040518163ffffffff1660e01b8152600401602060405180830381865afa1580156117ba573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906117de9190613a11565b81526020018c6001600160a01b0316638f840ddd6040518163ffffffff1660e01b8152600401602060405180830381865afa158015611821573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906118459190613a11565b81526020018c6001600160a01b03166318160ddd6040518163ffffffff1660e01b8152600401602060405180830381865afa158015611888573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906118ac9190613a11565b81526020018c6001600160a01b0316633b1d21a26040518163ffffffff1660e01b8152600401602060405180830381865afa1580156118ef573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906119139190613a11565b81526020018715158152602001868152602001856001600160a01b031681526020018c6001600160a01b031663313ce5676040518163ffffffff1660e01b8152600401602060405180830381865afa158015611973573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906119979190613cd3565b60ff168152602001848152602001838152509950505050505050505050919050565b6119c1612d8a565b6040516370a0823160e01b81526001600160a01b038381166004830152600091908516906370a0823190602401602060405180830381865afa158015611a0b573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611a2f9190613a11565b6040516305eff7ef60e21b81526001600160a01b0385811660048301529192506000918616906317bfdfbc906024016020604051808303816000875af1158015611a7d573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611aa19190613a11565b604051633af9e66960e01b81526001600160a01b038681166004830152919250600091871690633af9e669906024016020604051808303816000875af1158015611aef573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611b139190613a11565b90506000806000886001600160a01b0316636f307dc36040518163ffffffff1660e01b8152600401602060405180830381865afa158015611b58573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611b7c91906139f4565b6040516370a0823160e01b81526001600160a01b038a81166004830152919250908216906370a0823190602401602060405180830381865afa158015611bc6573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611bea9190613a11565b604051636eb1769f60e11b81526001600160a01b038a811660048301528b811660248301529194509082169063dd62ed3e90604401602060405180830381865afa158015611c3c573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611c609190613a11565b6040805160c0810182526001600160a01b038c168152602081019890985287019590955250506060840191909152608083015260a0820152905092915050565b604051631e6db74760e31b81526001600160a01b038281166004830152606091849182169063f36dba3890602401600060405180830381865afa158015611ceb573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f19168201604052610c419190810190613d81565b80516060906000816001600160401b03811115611d3257611d32612e89565b604051908082528060200260200182016040528015611d6b57816020015b611d58612dc9565b815260200190600190039081611d505790505b50905060005b82811015611dc057611d9b858281518110611d8e57611d8e613a2a565b60200260200101516111f9565b828281518110611dad57611dad613a2a565b6020908102919091010152600101611d71565b509392505050565b6000611df67f000000000000000000000000000000000000000000000000000000000000000063ffffffff16565b905090565b4390565b4290565b6060600083516001600160401b03811115611e2057611e20612e89565b604051908082528060200260200182016040528015611e6557816020015b6040805180820190915260008082526020820152815260200190600190039081611e3e5790505b50905060005b845181101561071457611ea1604051806060016040528060006001600160e01b0316815260200160008152602001600081525090565b611ece604051806060016040528060006001600160e01b0316815260200160008152602001600081525090565b7f00000000000000000000000000000000000000000000000000000000000000001561205157856001600160a01b0316638f693ec7888581518110611f1557611f15613a2a565b60200260200101516040518263ffffffff1660e01b8152600401611f4891906001600160a01b0391909116815260200190565b606060405180830381865afa158015611f65573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611f899190613e26565b604085015260208401526001600160e01b0316825286516001600160a01b03871690638181494590899086908110611fc357611fc3613a2a565b60200260200101516040518263ffffffff1660e01b8152600401611ff691906001600160a01b0391909116815260200190565b606060405180830381865afa158015612013573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906120379190613e26565b604084015260208301526001600160e01b031681526121bc565b856001600160a01b0316632c427b5788858151811061207257612072613a2a565b60200260200101516040518263ffffffff1660e01b81526004016120a591906001600160a01b0391909116815260200190565b606060405180830381865afa1580156120c2573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906120e69190613e6f565b63ffffffff90811660408601521660208401526001600160e01b0316825286516001600160a01b038716906392a182359089908690811061212957612129613a2a565b60200260200101516040518263ffffffff1660e01b815260040161215c91906001600160a01b0391909116815260200190565b606060405180830381865afa158015612179573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061219d9190613e6f565b63ffffffff90811660408501521660208301526001600160e01b031681525b600060405180602001604052808986815181106121db576121db613a2a565b60200260200101516001600160a01b031663aa5af0fd6040518163ffffffff1660e01b8152600401602060405180830381865afa158015612220573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906122449190613a11565b815250905061226e88858151811061225e5761225e613a2a565b6020026020010151888584612363565b61229288858151811061228357612283613a2a565b60200260200101518884612564565b60006122ba8986815181106122a9576122a9613a2a565b6020026020010151898c878661275b565b905060006122e38a87815181106122d3576122d3613a2a565b60200260200101518a8d8761298b565b60408051808201909152600080825260208201529091508a878151811061230c5761230c613a2a565b60209081029190910101516001600160a01b0316815261232c8284613bc2565b60208201528751819089908990811061234757612347613a2a565b6020026020010181905250505050505050806001019050611e6b565b604051637c05a7c560e01b81526001600160a01b03858116600483015260009190851690637c05a7c590602401602060405180830381865afa1580156123ad573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906123d19190613a11565b905060006123dd611dc8565b9050600084604001511180156123f65750836040015181115b15612402575060408301515b6000612412828660200151612baf565b90506000811180156124245750600083115b1561254d576000612496886001600160a01b03166347bd37186040518163ffffffff1660e01b8152600401602060405180830381865afa15801561246c573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906124909190613a11565b86612bc2565b905060006124a48386612be0565b905060008083116124c457604051806020016040528060008152506124ce565b6124ce8284612bec565b905060006124f760405180602001604052808b600001516001600160e01b031681525083612c31565b90506125328160000151604051806040016040528060138152602001726e657720696e646578206f766572666c6f777360681b815250612c5d565b6001600160e01b03168952505050506020850182905261255b565b801561255b57602085018290525b50505050505050565b604051631d31307360e21b81526001600160a01b038481166004830152600091908416906374c4c1cc90602401602060405180830381865afa1580156125ae573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906125d29190613a11565b905060006125de611dc8565b9050600083604001511180156125f75750826040015181115b15612603575060408201515b6000612613828560200151612baf565b90506000811180156126255750600083115b15612745576000866001600160a01b03166318160ddd6040518163ffffffff1660e01b8152600401602060405180830381865afa15801561266a573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061268e9190613a11565b9050600061269c8386612be0565b905060008083116126bc57604051806020016040528060008152506126c6565b6126c68284612bec565b905060006126ef60405180602001604052808a600001516001600160e01b031681525083612c31565b905061272a8160000151604051806040016040528060138152602001726e657720696e646578206f766572666c6f777360681b815250612c5d565b6001600160e01b031688525050505060208401829052612753565b801561275357602084018290525b505050505050565b604080516020808201835284516001600160e01b031682528251908101928390526336fe846560e11b9092526001600160a01b0387811660248401528581166044840152600092839181908916636dfd08ca60648301602060405180830381865afa1580156127ce573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906127f29190613a11565b905280519091501580156128745750866001600160a01b031663160c3a036040518163ffffffff1660e01b8152600401602060405180830381865afa15801561283f573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906128639190613eb2565b6001600160e01b0316826000015110155b156128e757866001600160a01b031663160c3a036040518163ffffffff1660e01b8152600401602060405180830381865afa1580156128b7573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906128db9190613eb2565b6001600160e01b031681525b60006128f38383612c99565b6040516395dd919360e01b81526001600160a01b03898116600483015291925060009161296e91908c16906395dd919390602401602060405180830381865afa158015612944573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906129689190613a11565b87612bc2565b9050600061297c8284612cc5565b9b9a5050505050505050505050565b604080516020808201835283516001600160e01b0316825282519081019283905263552c097160e01b9092526001600160a01b038681166024840152848116604484015260009283918190881663552c097160648301602060405180830381865afa1580156129fe573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190612a229190613a11565b90528051909150158015612aa45750856001600160a01b031663160c3a036040518163ffffffff1660e01b8152600401602060405180830381865afa158015612a6f573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190612a939190613eb2565b6001600160e01b0316826000015110155b15612b1757856001600160a01b031663160c3a036040518163ffffffff1660e01b8152600401602060405180830381865afa158015612ae7573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190612b0b9190613eb2565b6001600160e01b031681525b6000612b238383612c99565b6040516370a0823160e01b81526001600160a01b0388811660048301529192506000918a16906370a0823190602401602060405180830381865afa158015612b6f573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190612b939190613a11565b90506000612ba18284612cc5565b9a9950505050505050505050565b6000612bbb8284613ecd565b9392505050565b6000612bbb612bd984670de0b6b3a7640000612be0565b8351612cef565b6000612bbb8284613b89565b6040805160208101909152600081526040518060200160405280612c28612c22866ec097ce7bc90715b34b9f1000000000612be0565b85612cef565b90529392505050565b6040805160208101909152600081526040518060200160405280612c2885600001518560000151612cfb565b6000816001600160e01b03841115612c915760405162461bcd60e51b8152600401612c889190613ee0565b60405180910390fd5b509192915050565b6040805160208101909152600081526040518060200160405280612c2885600001518560000151612baf565b60006ec097ce7bc90715b34b9f1000000000612ce5848460000151612be0565b612bbb9190613ba0565b6000612bbb8284613ba0565b6000612bbb8284613bc2565b604051806101a001604052806060815260200160006001600160a01b0316815260200160006001600160a01b03168152602001600081526020016000815260200160608152602001606081526020016060815260200160006001600160a01b03168152602001600081526020016000815260200160008152602001606081525090565b6040518060c0016040528060006001600160a01b0316815260200160008152602001600081526020016000815260200160008152602001600081525090565b60405180610220016040528060006001600160a01b03168152602001600081526020016000815260200160008152602001600081526020016000815260200160008152602001600081526020016000815260200160008152602001600081526020016000151581526020016000815260200160006001600160a01b031681526020016000815260200160008152602001600081525090565b6001600160a01b0381168114612e7657600080fd5b50565b8035612e8481612e61565b919050565b634e487b7160e01b600052604160045260246000fd5b60405160a081016001600160401b0381118282101715612ec157612ec1612e89565b60405290565b604051606081016001600160401b0381118282101715612ec157612ec1612e89565b604051601f8201601f191681016001600160401b0381118282101715612f1157612f11612e89565b604052919050565b60006001600160401b03821115612f3257612f32612e89565b50601f01601f191660200190565b60008060408385031215612f5357600080fd5b8235612f5e81612e61565b91506020838101356001600160401b0380821115612f7b57600080fd5b9085019060a08288031215612f8f57600080fd5b612f97612e9f565b823582811115612fa657600080fd5b83019150601f82018813612fb957600080fd5b8135612fcc612fc782612f19565b612ee9565b8181528986838601011115612fe057600080fd5b818685018783013760008683830101528083525050613000848401612e79565b8482015261301060408401612e79565b60408201526060830135606082015260808301356080820152809450505050509250929050565b60005b8381101561305257818101518382015260200161303a565b50506000910152565b60008151808452613073816020860160208601613037565b601f01601f19169290920160200192915050565b80516001600160a01b031682526020810151602083015260408101516040830152606081015160608301526080810151608083015260a081015160a083015260c081015160c083015260e081015160e0830152610100808201518184015250610120808201518184015250610140808201518184015250610160808201516131128285018215159052565b505061018081810151908301526101a0808201516001600160a01b0316908301526101c080820151908301526101e0808201519083015261020090810151910152565b60008151808452602080850194506020840160005b838110156131915761317d878351613087565b61022096909601959082019060010161316a565b509495945050505050565b60006101a082518185526131b28286018261305b565b91505060208301516131cf60208601826001600160a01b03169052565b5060408301516131ea60408601826001600160a01b03169052565b50606083015160608501526080830151608085015260a083015184820360a0860152613216828261305b565b91505060c083015184820360c0860152613230828261305b565b91505060e083015184820360e086015261324a828261305b565b91505061010080840151613268828701826001600160a01b03169052565b505061012083810151908501526101408084015190850152610160808401519085015261018080840151858303828701526132a38382613155565b9695505050505050565b602081526000612bbb602083018461319c565b60008083601f8401126132d257600080fd5b5081356001600160401b038111156132e957600080fd5b6020830191508360208260051b850101111561330457600080fd5b9250929050565b6000806020838503121561331e57600080fd5b82356001600160401b0381111561333457600080fd5b613340858286016132c0565b90969095509350505050565b602080825282518282018190526000919060409081850190868401855b8281101561339f5761338f84835180516001600160a01b03168252602090810151910152565b9284019290850190600101613369565b5091979650505050505050565b6000602082840312156133be57600080fd5b8135612bbb81612e61565b600060208083016020845280855180835260408601915060408160051b87010192506020870160005b8281101561342057603f1988860301845261340e85835161319c565b945092850192908501906001016133f2565b5092979650505050505050565b602080825282516001600160a01b03168282015282810151604080840191909152808401516060808501528051608085018190526000939291830191849160a08701905b808410156134ab5761349782865180516001600160a01b03168252602090810151910152565b938501936001939093019290820190613471565b50979650505050505050565b600080604083850312156134ca57600080fd5b82356134d581612e61565b915060208301356134e581612e61565b809150509250929050565b60008060006060848603121561350557600080fd5b833561351081612e61565b9250602084013561352081612e61565b9150604084013561353081612e61565b809150509250925092565b600060208083018184528085518083526040925060408601915060408160051b8701018488016000805b8481101561360857898403603f19018652825180516001600160a01b03908116865289820151168986015287810151888601526060908101516080918601829052805191860182905289019060a086019084905b808210156135f3576135df83855180516001600160a01b03168252602090810151910152565b928b0192918a0191600191909101906135b9565b50509689019694505091870191600101613565565b50919998505050505050505050565b60008060006040848603121561362c57600080fd5b83356001600160401b0381111561364257600080fd5b61364e868287016132c0565b909450925050602084013561353081612e61565b80516001600160a01b031682526020808201519083015260408082015190830152606080820151908301526080808201519083015260a090810151910152565b6020808252825182820181905260009190848201906040850190845b818110156136e4576136d1838551613662565b9284019260c092909201916001016136be565b50909695505050505050565b81516001600160a01b03168152602080830151908201526040810161064f565b610220810161064f8284613087565b60c0810161064f8284613662565b6020808252825182820181905260009190848201906040850190845b818110156136e45783516001600160a01b031683529284019291840191600101613749565b60006001600160401b0382111561378757613787612e89565b5060051b60200190565b600060208083850312156137a457600080fd5b82356001600160401b038111156137ba57600080fd5b8301601f810185136137cb57600080fd5b80356137d9612fc78261376e565b81815260059190911b820183019083810190878311156137f857600080fd5b928401925b8284101561381f57833561381081612e61565b825292840192908401906137fd565b979650505050505050565b6020808252825182820181905260009190848201906040850190845b818110156136e457613859838551613087565b928401926102209290920191600101613846565b6000602080838503121561388057600080fd5b82516001600160401b0381111561389657600080fd5b8301601f810185136138a757600080fd5b80516138b5612fc78261376e565b81815260059190911b820183019083810190878311156138d457600080fd5b928401925b8284101561381f5783516138ec81612e61565b825292840192908401906138d9565b600082601f83011261390c57600080fd5b815161391a612fc782612f19565b81815284602083860101111561392f57600080fd5b610c41826020830160208701613037565b60006020828403121561395257600080fd5b81516001600160401b038082111561396957600080fd5b908301906060828603121561397d57600080fd5b613985612ec7565b82518281111561399457600080fd5b6139a0878286016138fb565b8252506020830151828111156139b557600080fd5b6139c1878286016138fb565b6020830152506040830151828111156139d957600080fd5b6139e5878286016138fb565b60408301525095945050505050565b600060208284031215613a0657600080fd5b8151612bbb81612e61565b600060208284031215613a2357600080fd5b5051919050565b634e487b7160e01b600052603260045260246000fd5b600060a08284031215613a5257600080fd5b613a5a612e9f565b905081516001600160401b03811115613a7257600080fd5b613a7e848285016138fb565b8252506020820151613a8f81612e61565b60208201526040820151613aa281612e61565b80604083015250606082015160608201526080820151608082015292915050565b60006020808385031215613ad657600080fd5b82516001600160401b0380821115613aed57600080fd5b818501915085601f830112613b0157600080fd5b8151613b0f612fc78261376e565b81815260059190911b83018401908481019088831115613b2e57600080fd5b8585015b83811015613b6657805185811115613b4a5760008081fd5b613b588b89838a0101613a40565b845250918601918601613b32565b5098975050505050505050565b634e487b7160e01b600052601160045260246000fd5b808202811582820484141761064f5761064f613b73565b600082613bbd57634e487b7160e01b600052601260045260246000fd5b500490565b8082018082111561064f5761064f613b73565b600060208284031215613be757600080fd5b81516001600160401b03811115613bfd57600080fd5b610c4184828501613a40565b60006020808385031215613c1c57600080fd5b82516001600160401b03811115613c3257600080fd5b8301601f81018513613c4357600080fd5b8051613c51612fc78261376e565b81815260059190911b82018301908381019087831115613c7057600080fd5b928401925b8284101561381f578351613c8881612e61565b82529284019290840190613c75565b80518015158114612e8457600080fd5b60008060408385031215613cba57600080fd5b613cc383613c97565b9150602083015190509250929050565b600060208284031215613ce557600080fd5b815160ff81168114612bbb57600080fd5b634e487b7160e01b600052602160045260246000fd5b6001600160a01b03831681526040810160098310613d3a57634e487b7160e01b600052602160045260246000fd5b8260208301529392505050565b600060208284031215613d5957600080fd5b612bbb82613c97565b600060ff821660ff8103613d7857613d78613b73565b60010192915050565b60006020808385031215613d9457600080fd5b82516001600160401b03811115613daa57600080fd5b8301601f81018513613dbb57600080fd5b8051613dc9612fc78261376e565b81815260059190911b82018301908381019087831115613de857600080fd5b928401925b8284101561381f578351613e0081612e61565b82529284019290840190613ded565b80516001600160e01b0381168114612e8457600080fd5b600080600060608486031215613e3b57600080fd5b613e4484613e0f565b925060208401519150604084015190509250925092565b805163ffffffff81168114612e8457600080fd5b600080600060608486031215613e8457600080fd5b613e8d84613e0f565b9250613e9b60208501613e5b565b9150613ea960408501613e5b565b90509250925092565b600060208284031215613ec457600080fd5b612bbb82613e0f565b8181038181111561064f5761064f613b73565b602081526000612bbb602083018461305b56fea26469706673582212208dc9fbc3de84a5eadf5a8d0d7d030c880d5f4a9665121e956562816524e6c89964736f6c63430008190033000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002819a0
Deployed Bytecode
0x608060405234801561001057600080fd5b50600436106101005760003560e01c80637c51b64211610097578063c7ad089511610066578063c7ad0895146102a1578063d77ebf96146102d8578063e0a67f11146102f8578063e1d146fb1461031857600080fd5b80637c51b642146102215780637c84e3b314610241578063aa5dbd2314610261578063b31242391461028157600080fd5b806347d86a81116100d357806347d86a811461018e57806355dd9515146101a15780636857249c146101cc5780637a27db571461020157600080fd5b80630d3ae318146101055780631f884fdf1461012e578063345954dc1461014e5780633e3e399c1461016e575b600080fd5b610118610113366004612f40565b610320565b60405161012591906132ad565b60405180910390f35b61014161013c36600461330b565b610655565b604051610125919061334c565b61016161015c3660046133ac565b61071d565b60405161012591906133c9565b61018161017c3660046133ac565b610850565b604051610125919061342d565b61011861019c3660046134b7565b610bc2565b6101b46101af3660046134f0565b610c49565b6040516001600160a01b039091168152602001610125565b6101f37f00000000000000000000000000000000000000000000000000000000002819a081565b604051908152602001610125565b61021461020f3660046134b7565b610cc9565b604051610125919061353b565b61023461022f366004613617565b610fd6565b60405161012591906136a2565b61025461024f3660046133ac565b61108f565b60405161012591906136f0565b61027461026f3660046133ac565b6111f9565b6040516101259190613710565b61029461028f3660046134b7565b6119b9565b604051610125919061371f565b6102c87f000000000000000000000000000000000000000000000000000000000000000081565b6040519015158152602001610125565b6102eb6102e63660046134b7565b611ca0565b604051610125919061372d565b61030b610306366004613791565b611d13565b604051610125919061382a565b6101f3611dc8565b610328612d07565b6000826040015190506000816001600160a01b031663b0772d0b6040518163ffffffff1660e01b8152600401600060405180830381865afa158015610371573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f19168201604052610399919081019061386d565b905060006103a682611d13565b60408681015190516328ebbe8b60e21b81526001600160a01b039182166004820152919250879160009183169063a3aefa2c90602401600060405180830381865afa1580156103f9573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f191682016040526104219190810190613940565b90506000876040015190506000604051806101a001604052808a6000015181526020018a602001516001600160a01b031681526020018a604001516001600160a01b031681526020018a6060015181526020018a608001518152602001846000015181526020018460200151815260200184604001518152602001836001600160a01b0316637dc0d1d06040518163ffffffff1660e01b8152600401602060405180830381865afa1580156104da573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906104fe91906139f4565b6001600160a01b03168152602001836001600160a01b031663e87554466040518163ffffffff1660e01b8152600401602060405180830381865afa15801561054a573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061056e9190613a11565b8152602001836001600160a01b0316634ada90af6040518163ffffffff1660e01b8152600401602060405180830381865afa1580156105b1573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906105d59190613a11565b8152602001836001600160a01b031663db5c65de6040518163ffffffff1660e01b8152600401602060405180830381865afa158015610618573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061063c9190613a11565b8152602001959095525092955050505050505b92915050565b6060816000816001600160401b0381111561067257610672612e89565b6040519080825280602002602001820160405280156106b757816020015b60408051808201909152600080825260208201528152602001906001900390816106905790505b50905060005b82811015610714576106ef8686838181106106da576106da613a2a565b905060200201602081019061024f91906133ac565b82828151811061070157610701613a2a565b60209081029190910101526001016106bd565b50949350505050565b606060008290506000816001600160a01b031663d88ff1f46040518163ffffffff1660e01b8152600401600060405180830381865afa158015610764573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f1916820160405261078c9190810190613ac3565b80519091506000816001600160401b038111156107ab576107ab612e89565b6040519080825280602002602001820160405280156107e457816020015b6107d1612d07565b8152602001906001900390816107c95790505b50905060005b8281101561084657600084828151811061080657610806613a2a565b60200260200101519050600061081c8983610320565b90508084848151811061083157610831613a2a565b602090810291909101015250506001016107ea565b5095945050505050565b604080516060808201835260008083526020830152918101919091526000808390506000816001600160a01b031663b0772d0b6040518163ffffffff1660e01b8152600401600060405180830381865afa1580156108b2573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f191682016040526108da919081019061386d565b90506000826001600160a01b0316637dc0d1d06040518163ffffffff1660e01b8152600401602060405180830381865afa15801561091c573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061094091906139f4565b9050600082516001600160401b0381111561095d5761095d612e89565b6040519080825280602002602001820160405280156109a257816020015b604080518082019091526000808252602082015281526020019060019003908161097b5790505b5090506109d2604051806060016040528060006001600160a01b0316815260200160008152602001606081525090565b6001600160a01b03881681526040810182905260005b8451811015610bae576040805180820190915260008082526020820152858281518110610a1757610a17613a2a565b602002602001015181600001906001600160a01b031690816001600160a01b031681525050670de0b6b3a7640000856001600160a01b031663fc57d4df888581518110610a6657610a66613a2a565b60200260200101516040518263ffffffff1660e01b8152600401610a9991906001600160a01b0391909116815260200190565b602060405180830381865afa158015610ab6573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610ada9190613a11565b878481518110610aec57610aec613a2a565b60200260200101516001600160a01b031663bbcac5576040518163ffffffff1660e01b8152600401602060405180830381865afa158015610b31573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610b559190613a11565b610b5f9190613b89565b610b699190613ba0565b60208201526040830151805182919084908110610b8857610b88613a2a565b6020026020010181905250806020015188610ba39190613bc2565b9750506001016109e8565b506020810195909552509295945050505050565b610bca612d07565b604051637aee632d60e01b81526001600160a01b0383811660048301528491610c4191839190821690637aee632d90602401600060405180830381865afa158015610c19573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f191682016040526101139190810190613bd5565b949350505050565b60405163266e0a7f60e01b81526001600160a01b0383811660048301528281166024830152600091859182169063266e0a7f90604401602060405180830381865afa158015610c9c573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610cc091906139f4565b95945050505050565b60606000826001600160a01b031663b0772d0b6040518163ffffffff1660e01b8152600401600060405180830381865afa158015610d0b573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f19168201604052610d33919081019061386d565b90506000836001600160a01b03166361252fd16040518163ffffffff1660e01b8152600401600060405180830381865afa158015610d75573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f19168201604052610d9d9190810190613c09565b9050600081516001600160401b03811115610dba57610dba612e89565b604051908082528060200260200182016040528015610e0b57816020015b6040805160808101825260008082526020808301829052928201526060808201528252600019909201910181610dd85790505b50905060005b8251811015610846576040805160808101825260008082526020820181905291810191909152606080820152838281518110610e4f57610e4f613a2a565b60209081029190910101516001600160a01b031681528351849083908110610e7957610e79613a2a565b60200260200101516001600160a01b031663f7c618c16040518163ffffffff1660e01b8152600401602060405180830381865afa158015610ebe573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610ee291906139f4565b6001600160a01b031660208201528351849083908110610f0457610f04613a2a565b6020908102919091010151604051631627ee8960e01b81526001600160a01b038a8116600483015290911690631627ee8990602401602060405180830381865afa158015610f56573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610f7a9190613a11565b816040018181525050610fa78886868581518110610f9a57610f9a613a2a565b6020026020010151611e03565b816060018190525080838381518110610fc257610fc2613a2a565b602090810291909101015250600101610e11565b6060826000816001600160401b03811115610ff357610ff3612e89565b60405190808252806020026020018201604052801561102c57816020015b611019612d8a565b8152602001906001900390816110115790505b50905060005b828110156108465761106a87878381811061104f5761104f613a2a565b905060200201602081019061106491906133ac565b866119b9565b82828151811061107c5761107c613a2a565b6020908102919091010152600101611032565b60408051808201909152600080825260208201526000826001600160a01b0316635fe3b5676040518163ffffffff1660e01b8152600401602060405180830381865afa1580156110e3573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061110791906139f4565b90506000816001600160a01b0316637dc0d1d06040518163ffffffff1660e01b8152600401602060405180830381865afa158015611149573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061116d91906139f4565b6040805180820182526001600160a01b03808816808352925163fc57d4df60e01b815260048101939093529293509160208301919084169063fc57d4df90602401602060405180830381865afa1580156111cb573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906111ef9190613a11565b9052949350505050565b611201612dc9565b6000826001600160a01b031663182df0f56040518163ffffffff1660e01b8152600401602060405180830381865afa158015611241573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906112659190613a11565b90506000836001600160a01b0316635fe3b5676040518163ffffffff1660e01b8152600401602060405180830381865afa1580156112a7573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906112cb91906139f4565b604051638e8f294b60e01b81526001600160a01b03868116600483015291925082916000918291841690638e8f294b906024016040805180830381865afa15801561131a573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061133e9190613ca7565b915091506000876001600160a01b0316636f307dc36040518163ffffffff1660e01b8152600401602060405180830381865afa158015611382573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906113a691906139f4565b90506000816001600160a01b031663313ce5676040518163ffffffff1660e01b8152600401602060405180830381865afa1580156113e8573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061140c9190613cd3565b60ff1690506000805b600860ff8216116114d2576000886001600160a01b031663e85a29608d8460ff16600881111561144757611447613cf6565b6040518363ffffffff1660e01b8152600401611464929190613d0c565b602060405180830381865afa158015611481573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906114a59190613d47565b6114b05760006114b3565b60015b60ff9081169083161b9290921791506114cb81613d62565b9050611415565b506000808b6001600160a01b03166318160ddd6040518163ffffffff1660e01b8152600401602060405180830381865afa158015611514573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906115389190613a11565b11156115a3578a6001600160a01b031663ae9d70b06040518163ffffffff1660e01b8152600401602060405180830381865afa15801561157c573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906115a09190613a11565b90505b6040518061022001604052808c6001600160a01b031681526020018a81526020018281526020018c6001600160a01b031663f8f9da286040518163ffffffff1660e01b8152600401602060405180830381865afa158015611608573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061162c9190613a11565b81526020018c6001600160a01b031663173b99046040518163ffffffff1660e01b8152600401602060405180830381865afa15801561166f573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906116939190613a11565b81526040516302c3bcbb60e01b81526001600160a01b038e811660048301526020909201918a16906302c3bcbb90602401602060405180830381865afa1580156116e1573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906117059190613a11565b815260405163252c221960e11b81526001600160a01b038e811660048301526020909201918a1690634a58443290602401602060405180830381865afa158015611753573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906117779190613a11565b81526020018c6001600160a01b03166347bd37186040518163ffffffff1660e01b8152600401602060405180830381865afa1580156117ba573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906117de9190613a11565b81526020018c6001600160a01b0316638f840ddd6040518163ffffffff1660e01b8152600401602060405180830381865afa158015611821573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906118459190613a11565b81526020018c6001600160a01b03166318160ddd6040518163ffffffff1660e01b8152600401602060405180830381865afa158015611888573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906118ac9190613a11565b81526020018c6001600160a01b0316633b1d21a26040518163ffffffff1660e01b8152600401602060405180830381865afa1580156118ef573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906119139190613a11565b81526020018715158152602001868152602001856001600160a01b031681526020018c6001600160a01b031663313ce5676040518163ffffffff1660e01b8152600401602060405180830381865afa158015611973573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906119979190613cd3565b60ff168152602001848152602001838152509950505050505050505050919050565b6119c1612d8a565b6040516370a0823160e01b81526001600160a01b038381166004830152600091908516906370a0823190602401602060405180830381865afa158015611a0b573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611a2f9190613a11565b6040516305eff7ef60e21b81526001600160a01b0385811660048301529192506000918616906317bfdfbc906024016020604051808303816000875af1158015611a7d573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611aa19190613a11565b604051633af9e66960e01b81526001600160a01b038681166004830152919250600091871690633af9e669906024016020604051808303816000875af1158015611aef573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611b139190613a11565b90506000806000886001600160a01b0316636f307dc36040518163ffffffff1660e01b8152600401602060405180830381865afa158015611b58573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611b7c91906139f4565b6040516370a0823160e01b81526001600160a01b038a81166004830152919250908216906370a0823190602401602060405180830381865afa158015611bc6573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611bea9190613a11565b604051636eb1769f60e11b81526001600160a01b038a811660048301528b811660248301529194509082169063dd62ed3e90604401602060405180830381865afa158015611c3c573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611c609190613a11565b6040805160c0810182526001600160a01b038c168152602081019890985287019590955250506060840191909152608083015260a0820152905092915050565b604051631e6db74760e31b81526001600160a01b038281166004830152606091849182169063f36dba3890602401600060405180830381865afa158015611ceb573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f19168201604052610c419190810190613d81565b80516060906000816001600160401b03811115611d3257611d32612e89565b604051908082528060200260200182016040528015611d6b57816020015b611d58612dc9565b815260200190600190039081611d505790505b50905060005b82811015611dc057611d9b858281518110611d8e57611d8e613a2a565b60200260200101516111f9565b828281518110611dad57611dad613a2a565b6020908102919091010152600101611d71565b509392505050565b6000611df67f000000000000000000000000000000000000000000000000000000d400001dfb63ffffffff16565b905090565b4390565b4290565b6060600083516001600160401b03811115611e2057611e20612e89565b604051908082528060200260200182016040528015611e6557816020015b6040805180820190915260008082526020820152815260200190600190039081611e3e5790505b50905060005b845181101561071457611ea1604051806060016040528060006001600160e01b0316815260200160008152602001600081525090565b611ece604051806060016040528060006001600160e01b0316815260200160008152602001600081525090565b7f00000000000000000000000000000000000000000000000000000000000000001561205157856001600160a01b0316638f693ec7888581518110611f1557611f15613a2a565b60200260200101516040518263ffffffff1660e01b8152600401611f4891906001600160a01b0391909116815260200190565b606060405180830381865afa158015611f65573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611f899190613e26565b604085015260208401526001600160e01b0316825286516001600160a01b03871690638181494590899086908110611fc357611fc3613a2a565b60200260200101516040518263ffffffff1660e01b8152600401611ff691906001600160a01b0391909116815260200190565b606060405180830381865afa158015612013573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906120379190613e26565b604084015260208301526001600160e01b031681526121bc565b856001600160a01b0316632c427b5788858151811061207257612072613a2a565b60200260200101516040518263ffffffff1660e01b81526004016120a591906001600160a01b0391909116815260200190565b606060405180830381865afa1580156120c2573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906120e69190613e6f565b63ffffffff90811660408601521660208401526001600160e01b0316825286516001600160a01b038716906392a182359089908690811061212957612129613a2a565b60200260200101516040518263ffffffff1660e01b815260040161215c91906001600160a01b0391909116815260200190565b606060405180830381865afa158015612179573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061219d9190613e6f565b63ffffffff90811660408501521660208301526001600160e01b031681525b600060405180602001604052808986815181106121db576121db613a2a565b60200260200101516001600160a01b031663aa5af0fd6040518163ffffffff1660e01b8152600401602060405180830381865afa158015612220573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906122449190613a11565b815250905061226e88858151811061225e5761225e613a2a565b6020026020010151888584612363565b61229288858151811061228357612283613a2a565b60200260200101518884612564565b60006122ba8986815181106122a9576122a9613a2a565b6020026020010151898c878661275b565b905060006122e38a87815181106122d3576122d3613a2a565b60200260200101518a8d8761298b565b60408051808201909152600080825260208201529091508a878151811061230c5761230c613a2a565b60209081029190910101516001600160a01b0316815261232c8284613bc2565b60208201528751819089908990811061234757612347613a2a565b6020026020010181905250505050505050806001019050611e6b565b604051637c05a7c560e01b81526001600160a01b03858116600483015260009190851690637c05a7c590602401602060405180830381865afa1580156123ad573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906123d19190613a11565b905060006123dd611dc8565b9050600084604001511180156123f65750836040015181115b15612402575060408301515b6000612412828660200151612baf565b90506000811180156124245750600083115b1561254d576000612496886001600160a01b03166347bd37186040518163ffffffff1660e01b8152600401602060405180830381865afa15801561246c573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906124909190613a11565b86612bc2565b905060006124a48386612be0565b905060008083116124c457604051806020016040528060008152506124ce565b6124ce8284612bec565b905060006124f760405180602001604052808b600001516001600160e01b031681525083612c31565b90506125328160000151604051806040016040528060138152602001726e657720696e646578206f766572666c6f777360681b815250612c5d565b6001600160e01b03168952505050506020850182905261255b565b801561255b57602085018290525b50505050505050565b604051631d31307360e21b81526001600160a01b038481166004830152600091908416906374c4c1cc90602401602060405180830381865afa1580156125ae573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906125d29190613a11565b905060006125de611dc8565b9050600083604001511180156125f75750826040015181115b15612603575060408201515b6000612613828560200151612baf565b90506000811180156126255750600083115b15612745576000866001600160a01b03166318160ddd6040518163ffffffff1660e01b8152600401602060405180830381865afa15801561266a573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061268e9190613a11565b9050600061269c8386612be0565b905060008083116126bc57604051806020016040528060008152506126c6565b6126c68284612bec565b905060006126ef60405180602001604052808a600001516001600160e01b031681525083612c31565b905061272a8160000151604051806040016040528060138152602001726e657720696e646578206f766572666c6f777360681b815250612c5d565b6001600160e01b031688525050505060208401829052612753565b801561275357602084018290525b505050505050565b604080516020808201835284516001600160e01b031682528251908101928390526336fe846560e11b9092526001600160a01b0387811660248401528581166044840152600092839181908916636dfd08ca60648301602060405180830381865afa1580156127ce573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906127f29190613a11565b905280519091501580156128745750866001600160a01b031663160c3a036040518163ffffffff1660e01b8152600401602060405180830381865afa15801561283f573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906128639190613eb2565b6001600160e01b0316826000015110155b156128e757866001600160a01b031663160c3a036040518163ffffffff1660e01b8152600401602060405180830381865afa1580156128b7573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906128db9190613eb2565b6001600160e01b031681525b60006128f38383612c99565b6040516395dd919360e01b81526001600160a01b03898116600483015291925060009161296e91908c16906395dd919390602401602060405180830381865afa158015612944573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906129689190613a11565b87612bc2565b9050600061297c8284612cc5565b9b9a5050505050505050505050565b604080516020808201835283516001600160e01b0316825282519081019283905263552c097160e01b9092526001600160a01b038681166024840152848116604484015260009283918190881663552c097160648301602060405180830381865afa1580156129fe573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190612a229190613a11565b90528051909150158015612aa45750856001600160a01b031663160c3a036040518163ffffffff1660e01b8152600401602060405180830381865afa158015612a6f573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190612a939190613eb2565b6001600160e01b0316826000015110155b15612b1757856001600160a01b031663160c3a036040518163ffffffff1660e01b8152600401602060405180830381865afa158015612ae7573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190612b0b9190613eb2565b6001600160e01b031681525b6000612b238383612c99565b6040516370a0823160e01b81526001600160a01b0388811660048301529192506000918a16906370a0823190602401602060405180830381865afa158015612b6f573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190612b939190613a11565b90506000612ba18284612cc5565b9a9950505050505050505050565b6000612bbb8284613ecd565b9392505050565b6000612bbb612bd984670de0b6b3a7640000612be0565b8351612cef565b6000612bbb8284613b89565b6040805160208101909152600081526040518060200160405280612c28612c22866ec097ce7bc90715b34b9f1000000000612be0565b85612cef565b90529392505050565b6040805160208101909152600081526040518060200160405280612c2885600001518560000151612cfb565b6000816001600160e01b03841115612c915760405162461bcd60e51b8152600401612c889190613ee0565b60405180910390fd5b509192915050565b6040805160208101909152600081526040518060200160405280612c2885600001518560000151612baf565b60006ec097ce7bc90715b34b9f1000000000612ce5848460000151612be0565b612bbb9190613ba0565b6000612bbb8284613ba0565b6000612bbb8284613bc2565b604051806101a001604052806060815260200160006001600160a01b0316815260200160006001600160a01b03168152602001600081526020016000815260200160608152602001606081526020016060815260200160006001600160a01b03168152602001600081526020016000815260200160008152602001606081525090565b6040518060c0016040528060006001600160a01b0316815260200160008152602001600081526020016000815260200160008152602001600081525090565b60405180610220016040528060006001600160a01b03168152602001600081526020016000815260200160008152602001600081526020016000815260200160008152602001600081526020016000815260200160008152602001600081526020016000151581526020016000815260200160006001600160a01b031681526020016000815260200160008152602001600081525090565b6001600160a01b0381168114612e7657600080fd5b50565b8035612e8481612e61565b919050565b634e487b7160e01b600052604160045260246000fd5b60405160a081016001600160401b0381118282101715612ec157612ec1612e89565b60405290565b604051606081016001600160401b0381118282101715612ec157612ec1612e89565b604051601f8201601f191681016001600160401b0381118282101715612f1157612f11612e89565b604052919050565b60006001600160401b03821115612f3257612f32612e89565b50601f01601f191660200190565b60008060408385031215612f5357600080fd5b8235612f5e81612e61565b91506020838101356001600160401b0380821115612f7b57600080fd5b9085019060a08288031215612f8f57600080fd5b612f97612e9f565b823582811115612fa657600080fd5b83019150601f82018813612fb957600080fd5b8135612fcc612fc782612f19565b612ee9565b8181528986838601011115612fe057600080fd5b818685018783013760008683830101528083525050613000848401612e79565b8482015261301060408401612e79565b60408201526060830135606082015260808301356080820152809450505050509250929050565b60005b8381101561305257818101518382015260200161303a565b50506000910152565b60008151808452613073816020860160208601613037565b601f01601f19169290920160200192915050565b80516001600160a01b031682526020810151602083015260408101516040830152606081015160608301526080810151608083015260a081015160a083015260c081015160c083015260e081015160e0830152610100808201518184015250610120808201518184015250610140808201518184015250610160808201516131128285018215159052565b505061018081810151908301526101a0808201516001600160a01b0316908301526101c080820151908301526101e0808201519083015261020090810151910152565b60008151808452602080850194506020840160005b838110156131915761317d878351613087565b61022096909601959082019060010161316a565b509495945050505050565b60006101a082518185526131b28286018261305b565b91505060208301516131cf60208601826001600160a01b03169052565b5060408301516131ea60408601826001600160a01b03169052565b50606083015160608501526080830151608085015260a083015184820360a0860152613216828261305b565b91505060c083015184820360c0860152613230828261305b565b91505060e083015184820360e086015261324a828261305b565b91505061010080840151613268828701826001600160a01b03169052565b505061012083810151908501526101408084015190850152610160808401519085015261018080840151858303828701526132a38382613155565b9695505050505050565b602081526000612bbb602083018461319c565b60008083601f8401126132d257600080fd5b5081356001600160401b038111156132e957600080fd5b6020830191508360208260051b850101111561330457600080fd5b9250929050565b6000806020838503121561331e57600080fd5b82356001600160401b0381111561333457600080fd5b613340858286016132c0565b90969095509350505050565b602080825282518282018190526000919060409081850190868401855b8281101561339f5761338f84835180516001600160a01b03168252602090810151910152565b9284019290850190600101613369565b5091979650505050505050565b6000602082840312156133be57600080fd5b8135612bbb81612e61565b600060208083016020845280855180835260408601915060408160051b87010192506020870160005b8281101561342057603f1988860301845261340e85835161319c565b945092850192908501906001016133f2565b5092979650505050505050565b602080825282516001600160a01b03168282015282810151604080840191909152808401516060808501528051608085018190526000939291830191849160a08701905b808410156134ab5761349782865180516001600160a01b03168252602090810151910152565b938501936001939093019290820190613471565b50979650505050505050565b600080604083850312156134ca57600080fd5b82356134d581612e61565b915060208301356134e581612e61565b809150509250929050565b60008060006060848603121561350557600080fd5b833561351081612e61565b9250602084013561352081612e61565b9150604084013561353081612e61565b809150509250925092565b600060208083018184528085518083526040925060408601915060408160051b8701018488016000805b8481101561360857898403603f19018652825180516001600160a01b03908116865289820151168986015287810151888601526060908101516080918601829052805191860182905289019060a086019084905b808210156135f3576135df83855180516001600160a01b03168252602090810151910152565b928b0192918a0191600191909101906135b9565b50509689019694505091870191600101613565565b50919998505050505050505050565b60008060006040848603121561362c57600080fd5b83356001600160401b0381111561364257600080fd5b61364e868287016132c0565b909450925050602084013561353081612e61565b80516001600160a01b031682526020808201519083015260408082015190830152606080820151908301526080808201519083015260a090810151910152565b6020808252825182820181905260009190848201906040850190845b818110156136e4576136d1838551613662565b9284019260c092909201916001016136be565b50909695505050505050565b81516001600160a01b03168152602080830151908201526040810161064f565b610220810161064f8284613087565b60c0810161064f8284613662565b6020808252825182820181905260009190848201906040850190845b818110156136e45783516001600160a01b031683529284019291840191600101613749565b60006001600160401b0382111561378757613787612e89565b5060051b60200190565b600060208083850312156137a457600080fd5b82356001600160401b038111156137ba57600080fd5b8301601f810185136137cb57600080fd5b80356137d9612fc78261376e565b81815260059190911b820183019083810190878311156137f857600080fd5b928401925b8284101561381f57833561381081612e61565b825292840192908401906137fd565b979650505050505050565b6020808252825182820181905260009190848201906040850190845b818110156136e457613859838551613087565b928401926102209290920191600101613846565b6000602080838503121561388057600080fd5b82516001600160401b0381111561389657600080fd5b8301601f810185136138a757600080fd5b80516138b5612fc78261376e565b81815260059190911b820183019083810190878311156138d457600080fd5b928401925b8284101561381f5783516138ec81612e61565b825292840192908401906138d9565b600082601f83011261390c57600080fd5b815161391a612fc782612f19565b81815284602083860101111561392f57600080fd5b610c41826020830160208701613037565b60006020828403121561395257600080fd5b81516001600160401b038082111561396957600080fd5b908301906060828603121561397d57600080fd5b613985612ec7565b82518281111561399457600080fd5b6139a0878286016138fb565b8252506020830151828111156139b557600080fd5b6139c1878286016138fb565b6020830152506040830151828111156139d957600080fd5b6139e5878286016138fb565b60408301525095945050505050565b600060208284031215613a0657600080fd5b8151612bbb81612e61565b600060208284031215613a2357600080fd5b5051919050565b634e487b7160e01b600052603260045260246000fd5b600060a08284031215613a5257600080fd5b613a5a612e9f565b905081516001600160401b03811115613a7257600080fd5b613a7e848285016138fb565b8252506020820151613a8f81612e61565b60208201526040820151613aa281612e61565b80604083015250606082015160608201526080820151608082015292915050565b60006020808385031215613ad657600080fd5b82516001600160401b0380821115613aed57600080fd5b818501915085601f830112613b0157600080fd5b8151613b0f612fc78261376e565b81815260059190911b83018401908481019088831115613b2e57600080fd5b8585015b83811015613b6657805185811115613b4a5760008081fd5b613b588b89838a0101613a40565b845250918601918601613b32565b5098975050505050505050565b634e487b7160e01b600052601160045260246000fd5b808202811582820484141761064f5761064f613b73565b600082613bbd57634e487b7160e01b600052601260045260246000fd5b500490565b8082018082111561064f5761064f613b73565b600060208284031215613be757600080fd5b81516001600160401b03811115613bfd57600080fd5b610c4184828501613a40565b60006020808385031215613c1c57600080fd5b82516001600160401b03811115613c3257600080fd5b8301601f81018513613c4357600080fd5b8051613c51612fc78261376e565b81815260059190911b82018301908381019087831115613c7057600080fd5b928401925b8284101561381f578351613c8881612e61565b82529284019290840190613c75565b80518015158114612e8457600080fd5b60008060408385031215613cba57600080fd5b613cc383613c97565b9150602083015190509250929050565b600060208284031215613ce557600080fd5b815160ff81168114612bbb57600080fd5b634e487b7160e01b600052602160045260246000fd5b6001600160a01b03831681526040810160098310613d3a57634e487b7160e01b600052602160045260246000fd5b8260208301529392505050565b600060208284031215613d5957600080fd5b612bbb82613c97565b600060ff821660ff8103613d7857613d78613b73565b60010192915050565b60006020808385031215613d9457600080fd5b82516001600160401b03811115613daa57600080fd5b8301601f81018513613dbb57600080fd5b8051613dc9612fc78261376e565b81815260059190911b82018301908381019087831115613de857600080fd5b928401925b8284101561381f578351613e0081612e61565b82529284019290840190613ded565b80516001600160e01b0381168114612e8457600080fd5b600080600060608486031215613e3b57600080fd5b613e4484613e0f565b925060208401519150604084015190509250925092565b805163ffffffff81168114612e8457600080fd5b600080600060608486031215613e8457600080fd5b613e8d84613e0f565b9250613e9b60208501613e5b565b9150613ea960408501613e5b565b90509250925092565b600060208284031215613ec457600080fd5b612bbb82613e0f565b8181038181111561064f5761064f613b73565b602081526000612bbb602083018461305b56fea26469706673582212208dc9fbc3de84a5eadf5a8d0d7d030c880d5f4a9665121e956562816524e6c89964736f6c63430008190033
Constructor Arguments (ABI-Encoded and is the last bytes of the Contract Creation Code above)
000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002819a0
-----Decoded View---------------
Arg [0] : timeBased_ (bool): False
Arg [1] : blocksPerYear_ (uint256): 2628000
-----Encoded View---------------
2 Constructor Arguments found :
Arg [0] : 0000000000000000000000000000000000000000000000000000000000000000
Arg [1] : 00000000000000000000000000000000000000000000000000000000002819a0
Deployed Bytecode Sourcemap
1626:24273:27:-:0;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;13212:1648;;;;;;:::i;:::-;;:::i;:::-;;;;;;;:::i;:::-;;;;;;;;8673:404;;;;;;:::i;:::-;;:::i;:::-;;;;;;;:::i;5823:702::-;;;;;;:::i;:::-;;:::i;:::-;;;;;;;:::i;10626:1211::-;;;;;;:::i;:::-;;:::i;:::-;;;;;;;:::i;6835:366::-;;;;;;:::i;:::-;;:::i;7523:335::-;;;;;;:::i;:::-;;:::i;:::-;;;-1:-1:-1;;;;;12193:32:43;;;12175:51;;12163:2;12148:18;7523:335:27;12029:203:43;313:47:16;;;;;;;;12383:25:43;;;12371:2;12356:18;313:47:16;12237:177:43;9289:1016:27;;;;;;:::i;:::-;;:::i;:::-;;;;;;;:::i;5133:376::-;;;;;;:::i;:::-;;:::i;:::-;;;;;;;:::i;17739:467::-;;;;;;:::i;:::-;;:::i;:::-;;;;;;;:::i;15008:2016::-;;;;;;:::i;:::-;;:::i;:::-;;;;;;;:::i;12068:905::-;;;;;;:::i;:::-;;:::i;:::-;;;;;;;:::i;497:33:16:-;;;;;;;;17621:14:43;;17614:22;17596:41;;17584:2;17569:18;497:33:16;17456:187:43;8130:316:27;;;;;;:::i;:::-;;:::i;:::-;;;;;;;:::i;17198:351::-;;;;;;:::i;:::-;;:::i;:::-;;;;;;;:::i;2447:116:16:-;;;:::i;13212:1648:27:-;13359:15;;:::i;:::-;13420:40;13484:9;:21;;;13420:86;;13517:23;13543:19;-1:-1:-1;;;;;13543:33:27;;:35;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;-1:-1:-1;;13543:35:27;;;;;;;;;;;;:::i;:::-;13517:61;;13589:43;13635:26;13653:7;13635:17;:26::i;:::-;13885:21;;;;;13829:87;;-1:-1:-1;;;13829:87:27;;-1:-1:-1;;;;;12193:32:43;;;13829:87:27;;;12175:51:43;13589:72:27;;-1:-1:-1;13740:19:27;;13672:43;;13829:42;;;;;12148:18:43;;13829:87:27;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;-1:-1:-1;;13829:87:27;;;;;;;;;;;;:::i;:::-;13771:145;;13927:48;14003:9;:21;;;13927:98;;14036:24;14063:764;;;;;;;;14092:9;:14;;;14063:764;;;;14129:9;:17;;;-1:-1:-1;;;;;14063:764:27;;;;;14173:9;:21;;;-1:-1:-1;;;;;14063:764:27;;;;;14221:9;:21;;;14063:764;;;;14273:9;:25;;;14063:764;;;;14322:17;:26;;;14063:764;;;;14371:17;:25;;;14063:764;;;;14423:17;:29;;;14063:764;;;;14529:23;-1:-1:-1;;;;;14529:30:27;;:32;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;-1:-1:-1;;;;;14063:764:27;;;;;14589:23;-1:-1:-1;;;;;14589:43:27;;:45;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;14063:764;;;;14670:23;-1:-1:-1;;;;;14670:52:27;;:54;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;14063:764;;;;14765:23;-1:-1:-1;;;;;14765:49:27;;:51;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;14063:764;;;;;;;;-1:-1:-1;14036:791:27;;-1:-1:-1;;;;;;13212:1648:27;;;;;:::o;8673:404::-;8771:30;8835:7;8813:19;8835:7;-1:-1:-1;;;;;8896:40:27;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;-1:-1:-1;;;;;;;;;;;;;;;;;8896:40:27;;;;;;;;;;;;;;;;8859:77;;8951:9;8946:105;8966:11;8962:1;:15;8946:105;;;9007:33;9029:7;;9037:1;9029:10;;;;;;;:::i;:::-;;;;;;;;;;;;;;:::i;9007:33::-;8998:3;9002:1;8998:6;;;;;;;;:::i;:::-;;;;;;;;;;:42;8979:3;;8946:105;;;-1:-1:-1;9067:3:27;8673:404;-1:-1:-1;;;;8673:404:27:o;5823:702::-;5896:17;5925:43;5993:19;5925:88;;6023:42;6068:21;-1:-1:-1;;;;;6068:33:27;;:35;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;-1:-1:-1;;6068:35:27;;;;;;;;;;;;:::i;:::-;6134:17;;6023:80;;-1:-1:-1;6113:18:27;6134:17;-1:-1:-1;;;;;6196:26:27;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;6162:60;;6238:9;6233:255;6253:10;6249:1;:14;6233:255;;;6284:39;6326:10;6337:1;6326:13;;;;;;;;:::i;:::-;;;;;;;6284:55;;6353:24;6380:56;6405:19;6426:9;6380:24;:56::i;:::-;6353:83;;6469:8;6450:13;6464:1;6450:16;;;;;;;;:::i;:::-;;;;;;;;;;:27;-1:-1:-1;;6265:3:27;;6233:255;;;-1:-1:-1;6505:13:27;5823:702;-1:-1:-1;;;;;5823:702:27:o;10626:1211::-;-1:-1:-1;;;;;;;;;;;;;;;;;;;;;;;;10734:23:27;10808:36;10872:18;10808:83;;10901:23;10927:11;-1:-1:-1;;;;;10927:25:27;;:27;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;-1:-1:-1;;10927:27:27;;;;;;;;;;;;:::i;:::-;10901:53;;10964:36;11003:11;-1:-1:-1;;;;;11003:18:27;;:20;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;10964:59;;11034:25;11076:7;:14;-1:-1:-1;;;;;11062:29:27;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;-1:-1:-1;;;;;;;;;;;;;;;;;11062:29:27;;;;;;;;;;;;;;;;11034:57;;11102:36;-1:-1:-1;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;11102:36:27;-1:-1:-1;;;;;11148:47:27;;;;11205:23;;;:34;;;11148:26;11305:435;11325:7;:14;11321:1;:18;11305:435;;;-1:-1:-1;;;;;;;;;;;;;;;;;11428:7:27;11436:1;11428:10;;;;;;;;:::i;:::-;;;;;;;11396:7;:21;;:43;-1:-1:-1;;;;;11396:43:27;;;-1:-1:-1;;;;;11396:43:27;;;;;287:4:41;11531:11:27;-1:-1:-1;;;;;11531:30:27;;11570:7;11578:1;11570:10;;;;;;;;:::i;:::-;;;;;;;11531:51;;;;;;;;;;;;;;-1:-1:-1;;;;;12193:32:43;;;;12175:51;;12163:2;12148:18;;12029:203;11531:51:27;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;11506:7;11514:1;11506:10;;;;;;;;:::i;:::-;;;;;;;-1:-1:-1;;;;;11491:35:27;;:37;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;:91;;;;:::i;:::-;11490:121;;;;:::i;:::-;11453:18;;;:158;11625:23;;;;:26;;11453:7;;11625:23;11649:1;;11625:26;;;;;;:::i;:::-;;;;;;:36;;;;11711:7;:18;;;11693:15;:36;;;;:::i;:::-;11675:54;-1:-1:-1;;11341:3:27;;11305:435;;;-1:-1:-1;11750:30:27;;;:48;;;;-1:-1:-1;11750:14:27;;10626:1211;-1:-1:-1;;;;;10626:1211:27:o;6835:366::-;6960:15;;:::i;:::-;7138:55;;-1:-1:-1;;;7138:55:27;;-1:-1:-1;;;;;12193:32:43;;;7138:55:27;;;12175:51:43;7055:19:27;;7092:102;;7055:19;;7138:42;;;;;;12148:18:43;;7138:55:27;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;-1:-1:-1;;7138:55:27;;;;;;;;;;;;:::i;7092:102::-;7085:109;6835:366;-1:-1:-1;;;;6835:366:27:o;7523:335::-;7792:59;;-1:-1:-1;;;7792:59:27;;-1:-1:-1;;;;;26442:15:43;;;7792:59:27;;;26424:34:43;26494:15;;;26474:18;;;26467:43;7668:7:27;;7755:19;;7792:39;;;;;26359:18:43;;7792:59:27;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;7785:66;7523:335;-1:-1:-1;;;;;7523:335:27:o;9289:1016::-;9406:22;9440:23;9487:18;-1:-1:-1;;;;;9466:54:27;;:56;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;-1:-1:-1;;9466:56:27;;;;;;;;;;;;:::i;:::-;9440:82;;9532:47;9607:18;-1:-1:-1;;;;;9582:79:27;;:81;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;-1:-1:-1;;9582:81:27;;;;;;;;;;;;:::i;:::-;9532:131;;9673:36;9732:19;:26;-1:-1:-1;;;;;9712:47:27;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;-1:-1:-1;;;;;;;;;;;;;;;;;;;;;;;;;;;;9712:47:27;;-1:-1:-1;;9712:47:27;;;;;;;;;;;;9673:86;;9774:9;9769:500;9789:19;:26;9785:1;:30;9769:500;;;-1:-1:-1;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;9913:19:27;9933:1;9913:22;;;;;;;;:::i;:::-;;;;;;;;;;;-1:-1:-1;;;;;9877:59:27;;;9986:22;;:19;;10006:1;;9986:22;;;;;;:::i;:::-;;;;;;;-1:-1:-1;;;;;9986:34:27;;:36;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;-1:-1:-1;;;;;9950:73:27;:25;;;:73;10059:22;;:19;;10079:1;;10059:22;;;;;;:::i;:::-;;;;;;;;;;;:50;;-1:-1:-1;;;10059:50:27;;-1:-1:-1;;;;;12193:32:43;;;10059:50:27;;;12175:51:43;10059:41:27;;;;;;12148:18:43;;10059:50:27;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;10037:6;:19;;:72;;;;;10147;10178:7;10187;10196:19;10216:1;10196:22;;;;;;;;:::i;:::-;;;;;;;10147:30;:72::i;:::-;10123:6;:21;;:96;;;;10252:6;10233:13;10247:1;10233:16;;;;;;;;:::i;:::-;;;;;;;;;;:25;-1:-1:-1;9817:3:27;;9769:500;;5133:376;5222:23;5279:7;5257:19;5279:7;-1:-1:-1;;;;;5333:33:27;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;5303:63;;5381:9;5376:107;5396:11;5392:1;:15;5376:107;;;5437:35;5452:7;;5460:1;5452:10;;;;;;;:::i;:::-;;;;;;;;;;;;;;:::i;:::-;5464:7;5437:14;:35::i;:::-;5428:3;5432:1;5428:6;;;;;;;;:::i;:::-;;;;;;;;;;:44;5409:3;;5376:107;;17739:467;-1:-1:-1;;;;;;;;;;;;;;;;;17846:36:27;17918:6;-1:-1:-1;;;;;17918:18:27;;:20;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;17846:94;;17950:36;17989:11;-1:-1:-1;;;;;17989:18:27;;:20;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;18039:160;;;;;;;;-1:-1:-1;;;;;18039:160:27;;;;;;18137:47;;-1:-1:-1;;;18137:47:27;;;;;12175:51:43;;;;17950:59:27;;-1:-1:-1;18039:160:27;;;;;18137:30;;;;;;12148:18:43;;18137:47:27;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;18039:160;;18020:179;17739:467;-1:-1:-1;;;;17739:467:27:o;15008:2016::-;15068:21;;:::i;:::-;15101:27;15131:6;-1:-1:-1;;;;;15131:25:27;;:27;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;15101:57;;15168:26;15205:6;-1:-1:-1;;;;;15205:18:27;;:20;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;15381:36;;-1:-1:-1;;;15381:36:27;;-1:-1:-1;;;;;12193:32:43;;;15381:36:27;;;12175:51:43;15168:58:27;;-1:-1:-1;15168:58:27;;15236:36;;;;15381:19;;;;;12148:18:43;;15381:36:27;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;15329:88;;;;15428:30;15461:6;-1:-1:-1;;;;;15461:17:27;;:19;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;15428:52;;15490:26;15534:22;-1:-1:-1;;;;;15519:47:27;;:49;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;15490:78;;;;15579:21;15615:7;15610:222;15635:16;15624:28;;;;15610:222;;15673:14;15711:18;-1:-1:-1;;;;;15690:53:27;;15752:6;15768:1;15761:9;;;;;;;;;;:::i;:::-;15690:81;;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;:89;;15778:1;15690:89;;;15774:1;15690:89;15673:106;;;;15810:11;;;;15793:28;;;;;-1:-1:-1;15654:3:27;15820:1;15654:3;:::i;:::-;;;15610:222;;;;15842:26;15906:1;15883:6;-1:-1:-1;;;;;15883:18:27;;:20;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;:24;15879:103;;;15944:6;-1:-1:-1;;;;;15944:25:27;;:27;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;15923:48;;15879:103;16011:1006;;;;;;;;16060:6;-1:-1:-1;;;;;16011:1006:27;;;;;16106:19;16011:1006;;;;16174:18;16011:1006;;;;16241:6;-1:-1:-1;;;;;16241:25:27;;:27;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;16011:1006;;;;16309:6;-1:-1:-1;;;;;16309:28:27;;:30;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;16011:1006;;16369:39;;-1:-1:-1;;;16369:39:27;;-1:-1:-1;;;;;12193:32:43;;;16369:39:27;;;12175:51:43;16011:1006:27;;;;;16369:22;;;;;12148:18:43;;16369:39:27;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;16011:1006;;16438:39;;-1:-1:-1;;;16438:39:27;;-1:-1:-1;;;;;12193:32:43;;;16438:39:27;;;12175:51:43;16011:1006:27;;;;;16438:22;;;;;12148:18:43;;16438:39:27;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;16011:1006;;;;16509:6;-1:-1:-1;;;;;16509:19:27;;:21;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;16011:1006;;;;16563:6;-1:-1:-1;;;;;16563:20:27;;:22;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;16011:1006;;;;16616:6;-1:-1:-1;;;;;16616:18:27;;:20;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;16011:1006;;;;16665:6;-1:-1:-1;;;;;16665:14:27;;:16;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;16011:1006;;;;16709:8;16011:1006;;;;;;16761:24;16011:1006;;;;16827:22;-1:-1:-1;;;;;16011:1006:27;;;;;16883:6;-1:-1:-1;;;;;16883:15:27;;:17;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;16011:1006;;;;;;16938:18;16011:1006;;;;16989:13;16011:1006;;;15992:1025;;;;;;;;;;;15008:2016;;;:::o;12068:905::-;12140:21;;:::i;:::-;12193:25;;-1:-1:-1;;;12193:25:27;;-1:-1:-1;;;;;12193:32:43;;;:25:27;;;12175:51:43;12173:17:27;;12193:16;;;;;;12148:18:43;;12193:25:27;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;12259:36;;-1:-1:-1;;;12259:36:27;;-1:-1:-1;;;;;12193:32:43;;;12259:36:27;;;12175:51:43;12173:45:27;;-1:-1:-1;12228:28:27;;12259:27;;;;;12148:18:43;;12259:36:27;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;12335:35;;-1:-1:-1;;;12335:35:27;;-1:-1:-1;;;;;12193:32:43;;;12335:35:27;;;12175:51:43;12228:67:27;;-1:-1:-1;12305:27:27;;12335:26;;;;;12148:18:43;;12335:35:27;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;12305:65;;12380:20;12410:22;12443:17;12470:6;-1:-1:-1;;;;;12470:17:27;;:19;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;12515:29;;-1:-1:-1;;;12515:29:27;;-1:-1:-1;;;;;12193:32:43;;;12515:29:27;;;12175:51:43;12443:47:27;;-1:-1:-1;12515:20:27;;;;;;12148:18:43;;12515:29:27;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;12571:46;;-1:-1:-1;;;12571:46:27;;-1:-1:-1;;;;;26442:15:43;;;12571:46:27;;;26424:34:43;26494:15;;;26474:18;;;26467:43;12500:44:27;;-1:-1:-1;12571:20:27;;;;;;26359:18:43;;12571:46:27;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;12647:319;;;;;;;;-1:-1:-1;;;;;12647:319:27;;;;;;;;;;;;;;;;;-1:-1:-1;;12647:319:27;;;;;;;;;;;;;;;;-1:-1:-1;12068:905:27;;;;:::o;8130:316::-;8386:53;;-1:-1:-1;;;8386:53:27;;-1:-1:-1;;;;;12193:32:43;;;8386:53:27;;;12175:51:43;8253:16:27;;8349:19;;8386:46;;;;;12148:18:43;;8386:53:27;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;-1:-1:-1;;8386:53:27;;;;;;;;;;;;:::i;17198:351::-;17328:14;;17271:23;;17306:19;17328:14;-1:-1:-1;;;;;17382:33:27;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;17352:63;;17430:9;17425:98;17445:11;17441:1;:15;17425:98;;;17486:26;17501:7;17509:1;17501:10;;;;;;;;:::i;:::-;;;;;;;17486:14;:26::i;:::-;17477:3;17481:1;17477:6;;;;;;;;:::i;:::-;;;;;;;;;;:35;17458:3;;17425:98;;;-1:-1:-1;17539:3:27;17198:351;-1:-1:-1;;;17198:351:27:o;2447:116:16:-;2513:7;2539:17;:15;:17;;:::i;:::-;2532:24;;2447:116;:::o;2882:94::-;2957:12;;2882:94::o;2675:100::-;2753:15;;2675:100::o;18212:2744:27:-;18386:22;18420:37;18480:7;:14;-1:-1:-1;;;;;18460:35:27;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;-1:-1:-1;;;;;;;;;;;;;;;;;18460:35:27;;;;;;;;;;;;;;;;18420:75;;18511:9;18506:2413;18526:7;:14;18522:1;:18;18506:2413;;;18671:35;-1:-1:-1;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;18671:35:27;18720;-1:-1:-1;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;18720:35:27;18774:11;18770:1083;;;18978:18;-1:-1:-1;;;;;18978:50:27;;19037:7;19045:1;19037:10;;;;;;;;:::i;:::-;;;;;;;18978:71;;;;;;;;;;;;;;-1:-1:-1;;;;;12193:32:43;;;;12175:51;;12163:2;12148:18;;12029:203;18978:71:27;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;18916:41;;;18805:244;18866:28;;;18805:244;-1:-1:-1;;;;;18805:244:27;;;19299:10;;-1:-1:-1;;;;;19240:50:27;;;;;19299:7;;19307:1;;19299:10;;;;;;:::i;:::-;;;;;;;19240:71;;;;;;;;;;;;;;-1:-1:-1;;;;;12193:32:43;;;;12175:51;;12163:2;12148:18;;12029:203;19240:71:27;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;19178:41;;;19067:244;19128:28;;;19067:244;-1:-1:-1;;;;;19067:244:27;;;18770:1083;;;19523:18;-1:-1:-1;;;;;19523:41:27;;19573:7;19581:1;19573:10;;;;;;;;:::i;:::-;;;;;;;19523:62;;;;;;;;;;;;;;-1:-1:-1;;;;;12193:32:43;;;;12175:51;;12163:2;12148:18;;12029:203;19523:62:27;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;19350:235;;;;19461:41;;;19350:235;;19411:28;;;19350:235;-1:-1:-1;;;;;19350:235:27;;;19826:10;;-1:-1:-1;;;;;19776:41:27;;;;;19826:7;;19834:1;;19826:10;;;;;;:::i;:::-;;;;;;;19776:62;;;;;;;;;;;;;;-1:-1:-1;;;;;12193:32:43;;;;12175:51;;12163:2;12148:18;;12029:203;19776:62:27;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;19603:235;;;;19714:41;;;19603:235;;19664:28;;;19603:235;-1:-1:-1;;;;;19603:235:27;;;18770:1083;19867:28;19898:43;;;;;;;;19914:7;19922:1;19914:10;;;;;;;;:::i;:::-;;;;;;;-1:-1:-1;;;;;19914:22:27;;:24;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;19898:43;;;19867:74;;20019:96;20051:7;20059:1;20051:10;;;;;;;;:::i;:::-;;;;;;;20064:18;20084:11;20097:17;20019:23;:96::i;:::-;20129:77;20161:7;20169:1;20161:10;;;;;;;;:::i;:::-;;;;;;;20174:18;20194:11;20129:23;:77::i;:::-;20262:20;20285:199;20334:7;20342:1;20334:10;;;;;;;;:::i;:::-;;;;;;;20363:18;20399:7;20424:11;20453:17;20285:23;:199::i;:::-;20262:222;;20498:20;20521:164;20570:7;20578:1;20570:10;;;;;;;;:::i;:::-;;;;;;;20599:18;20635:7;20660:11;20521:23;:164::i;:::-;-1:-1:-1;;;;;;;;;;;;;;;;;20498:187:27;;-1:-1:-1;20786:7:27;20794:1;20786:10;;;;;;;;:::i;:::-;;;;;;;;;;;-1:-1:-1;;;;;20748:49:27;;;20834:27;20849:12;20834;:27;:::i;:::-;20811:20;;;:50;20875:17;;20811:13;;20875:14;;20890:1;;20875:17;;;;;;:::i;:::-;;;;;;:33;;;;18547:2372;;;;;;18542:3;;;;;18506:2413;;20962:1538;21201:50;;-1:-1:-1;;;21201:50:27;;-1:-1:-1;;;;;12193:32:43;;;21201:50:27;;;12175:51:43;21179:19:27;;21201:42;;;;;;12148:18:43;;21201:50:27;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;21179:72;;21261:30;21294:27;:25;:27::i;:::-;21261:60;;21393:1;21349:11;:41;;;:45;:127;;;;;21435:11;:41;;;21410:22;:66;21349:127;21332:246;;;-1:-1:-1;21526:41:27;;;;21332:246;21588:30;21621:58;21626:22;21650:11;:28;;;21621:4;:58::i;:::-;21588:91;;21718:1;21693:22;:26;:45;;;;;21737:1;21723:11;:15;21693:45;21689:805;;;21858:20;21881:54;21893:6;-1:-1:-1;;;;;21886:27:27;;:29;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;21917:17;21881:4;:54::i;:::-;21858:77;;21949:21;21973:41;21978:22;22002:11;21973:4;:41::i;:::-;21949:65;;22028:19;22065:1;22050:12;:16;:82;;22109:23;;;;;;;;22128:1;22109:23;;;22050:82;;;22069:37;22078:13;22093:12;22069:8;:37::i;:::-;22028:104;;22146:19;22168:52;22173:39;;;;;;;;22192:11;:17;;;-1:-1:-1;;;;;22173:39:27;;;;22214:5;22168:4;:52::i;:::-;22146:74;;22254:46;22262:5;:14;;;22254:46;;;;;;;;;;;;;-1:-1:-1;;;22254:46:27;;;:7;:46::i;:::-;-1:-1:-1;;;;;22234:66:27;;;-1:-1:-1;;;;22314:28:27;;;:53;;;21689:805;;;22388:26;;22384:110;;22430:28;;;:53;;;22384:110;21169:1331;;;20962:1538;;;;:::o;22506:1370::-;22707:50;;-1:-1:-1;;;22707:50:27;;-1:-1:-1;;;;;12193:32:43;;;22707:50:27;;;12175:51:43;22685:19:27;;22707:42;;;;;;12148:18:43;;22707:50:27;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;22685:72;;22767:30;22800:27;:25;:27::i;:::-;22767:60;;22899:1;22855:11;:41;;;:45;:127;;;;;22941:11;:41;;;22916:22;:66;22855:127;22838:246;;;-1:-1:-1;23032:41:27;;;;22838:246;23094:30;23127:58;23132:22;23156:11;:28;;;23127:4;:58::i;:::-;23094:91;;23224:1;23199:22;:26;:45;;;;;23243:1;23229:11;:15;23199:45;23195:675;;;23260:20;23290:6;-1:-1:-1;;;;;23283:26:27;;:28;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;23260:51;;23325:21;23349:41;23354:22;23378:11;23349:4;:41::i;:::-;23325:65;;23404:19;23441:1;23426:12;:16;:82;;23485:23;;;;;;;;23504:1;23485:23;;;23426:82;;;23445:37;23454:13;23469:12;23445:8;:37::i;:::-;23404:104;;23522:19;23544:52;23549:39;;;;;;;;23568:11;:17;;;-1:-1:-1;;;;;23549:39:27;;;;23590:5;23544:4;:52::i;:::-;23522:74;;23630:46;23638:5;:14;;;23630:46;;;;;;;;;;;;;-1:-1:-1;;;23630:46:27;;;:7;:46::i;:::-;-1:-1:-1;;;;;23610:66:27;;;-1:-1:-1;;;;23690:28:27;;;:53;;;23195:675;;;23764:26;;23760:110;;23806:28;;;:53;;;23760:110;22675:1201;;;22506:1370;;;:::o;23882:1041::-;24171:39;;;;;;;;;24190:17;;-1:-1:-1;;;;;24171:39:27;;;24250:103;;;;;;;;;-1:-1:-1;;;24281:61:27;;;-1:-1:-1;;;;;26442:15:43;;;24281:61:27;;;26424:34:43;26494:15;;;26474:18;;;26467:43;-1:-1:-1;;;;24250:103:27;;24281:43;;;26359:18:43;;;24281:61:27;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;24250:103;;24367:22;;24220:133;;-1:-1:-1;24367:27:27;:89;;;;;24422:18;-1:-1:-1;;;;;24422:32:27;;:34;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;-1:-1:-1;;;;;24398:58:27;:11;:20;;;:58;;24367:89;24363:285;;;24603:18;-1:-1:-1;;;;;24603:32:27;;:34;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;-1:-1:-1;;;;;24578:59:27;;;24363:285;24657:24;24684:32;24689:11;24702:13;24684:4;:32::i;:::-;24756:44;;-1:-1:-1;;;24756:44:27;;-1:-1:-1;;;;;12193:32:43;;;24756:44:27;;;12175:51:43;24657:59:27;;-1:-1:-1;24726:22:27;;24751:69;;24756:34;;;;;;12148:18:43;;24756:44:27;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;24802:17;24751:4;:69::i;:::-;24726:94;;24830:21;24854:32;24859:14;24875:10;24854:4;:32::i;:::-;24830:56;23882:1041;-1:-1:-1;;;;;;;;;;;23882:1041:27:o;24929:968::-;25180:39;;;;;;;;;25199:17;;-1:-1:-1;;;;;25180:39:27;;;25259:103;;;;;;;;;-1:-1:-1;;;25290:61:27;;;-1:-1:-1;;;;;26442:15:43;;;25290:61:27;;;26424:34:43;26494:15;;;26474:18;;;26467:43;-1:-1:-1;;;;25259:103:27;;25290:43;;;26359:18:43;;;25290:61:27;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;25259:103;;25376:22;;25229:133;;-1:-1:-1;25376:27:27;:89;;;;;25431:18;-1:-1:-1;;;;;25431:32:27;;:34;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;-1:-1:-1;;;;;25407:58:27;:11;:20;;;:58;;25376:89;25372:285;;;25612:18;-1:-1:-1;;;;;25612:32:27;;:34;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;-1:-1:-1;;;;;25587:59:27;;;25372:285;25666:24;25693:32;25698:11;25711:13;25693:4;:32::i;:::-;25760:34;;-1:-1:-1;;;25760:34:27;;-1:-1:-1;;;;;12193:32:43;;;25760:34:27;;;12175:51:43;25666:59:27;;-1:-1:-1;25735:22:27;;25760:24;;;;;12148:18:43;;25760:34:27;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;25735:59;;25804:21;25828:32;25833:14;25849:10;25828:4;:32::i;:::-;25804:56;24929:968;-1:-1:-1;;;;;;;;;;24929:968:27:o;3286:97:25:-;3345:7;3371:5;3375:1;3371;:5;:::i;:::-;3364:12;3286:97;-1:-1:-1;;;3286:97:25:o;4715:131::-;4777:7;4803:36;4808:18;4813:1;287:4:41;4808::25;:18::i;:::-;4828:10;;4803:4;:36::i;4299:97::-;4358:7;4384:5;4388:1;4384;:5;:::i;5435:154::-;-1:-1:-1;;;;;;;;;;;;5530:52:25;;;;;;;;5549:30;5554:21;5559:1;718:4;5554;:21::i;:::-;5577:1;5549:4;:30::i;:::-;5530:52;;5523:59;5435:154;-1:-1:-1;;;5435:154:25:o;2697:160::-;-1:-1:-1;;;;;;;;;;;;2800:50:25;;;;;;;;2819:28;2824:1;:10;;;2836:1;:10;;;2819:4;:28::i;2181:177::-;2260:7;2311:12;-1:-1:-1;;;;;2287:22:25;;;2279:45;;;;-1:-1:-1;;;2279:45:25;;;;;;;;:::i;:::-;;;;;;;;;-1:-1:-1;2349:1:25;;2181:177;-1:-1:-1;;2181:177:25:o;3120:160::-;-1:-1:-1;;;;;;;;;;;;3223:50:25;;;;;;;;3242:28;3247:1;:10;;;3259:1;:10;;;3242:4;:28::i;4161:132::-;4226:7;718:4;4252:19;4257:1;4260;:10;;;4252:4;:19::i;:::-;:34;;;;:::i;5332:97::-;5391:7;5417:5;5421:1;5417;:5;:::i;2863:97::-;2922:7;2948:5;2952:1;2948;:5;:::i;-1:-1:-1:-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::o;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::o;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::o;14:131:43:-;-1:-1:-1;;;;;89:31:43;;79:42;;69:70;;135:1;132;125:12;69:70;14:131;:::o;150:134::-;218:20;;247:31;218:20;247:31;:::i;:::-;150:134;;;:::o;289:127::-;350:10;345:3;341:20;338:1;331:31;381:4;378:1;371:15;405:4;402:1;395:15;421:253;493:2;487:9;535:4;523:17;;-1:-1:-1;;;;;555:34:43;;591:22;;;552:62;549:88;;;617:18;;:::i;:::-;653:2;646:22;421:253;:::o;679:::-;751:2;745:9;793:4;781:17;;-1:-1:-1;;;;;813:34:43;;849:22;;;810:62;807:88;;;875:18;;:::i;937:275::-;1008:2;1002:9;1073:2;1054:13;;-1:-1:-1;;1050:27:43;1038:40;;-1:-1:-1;;;;;1093:34:43;;1129:22;;;1090:62;1087:88;;;1155:18;;:::i;:::-;1191:2;1184:22;937:275;;-1:-1:-1;937:275:43:o;1217:187::-;1266:4;-1:-1:-1;;;;;1291:6:43;1288:30;1285:56;;;1321:18;;:::i;:::-;-1:-1:-1;1387:2:43;1366:15;-1:-1:-1;;1362:29:43;1393:4;1358:40;;1217:187::o;1409:1379::-;1505:6;1513;1566:2;1554:9;1545:7;1541:23;1537:32;1534:52;;;1582:1;1579;1572:12;1534:52;1621:9;1608:23;1640:31;1665:5;1640:31;:::i;:::-;1690:5;-1:-1:-1;1714:2:43;1752:18;;;1739:32;-1:-1:-1;;;;;1820:14:43;;;1817:34;;;1847:1;1844;1837:12;1817:34;1870:22;;;;1926:4;1908:16;;;1904:27;1901:47;;;1944:1;1941;1934:12;1901:47;1972:22;;:::i;:::-;2032:2;2019:16;2060:2;2050:8;2047:16;2044:36;;;2076:1;2073;2066:12;2044:36;2099:17;;;-1:-1:-1;2147:4:43;2139:13;;2135:27;-1:-1:-1;2125:55:43;;2176:1;2173;2166:12;2125:55;2212:2;2199:16;2237:49;2253:32;2282:2;2253:32;:::i;:::-;2237:49;:::i;:::-;2309:2;2302:5;2295:17;2349:7;2344:2;2339;2335;2331:11;2327:20;2324:33;2321:53;;;2370:1;2367;2360:12;2321:53;2425:2;2420;2416;2412:11;2407:2;2400:5;2396:14;2383:45;2469:1;2464:2;2459;2452:5;2448:14;2444:23;2437:34;2496:5;2487:7;2480:22;;;2536:31;2563:2;2559;2555:11;2536:31;:::i;:::-;2531:2;2522:7;2518:16;2511:57;2602:31;2629:2;2625;2621:11;2602:31;:::i;:::-;2597:2;2588:7;2584:16;2577:57;2689:2;2685;2681:11;2668:25;2663:2;2654:7;2650:16;2643:51;2750:3;2746:2;2742:12;2729:26;2723:3;2714:7;2710:17;2703:53;2775:7;2765:17;;;;;;1409:1379;;;;;:::o;2793:250::-;2878:1;2888:113;2902:6;2899:1;2896:13;2888:113;;;2978:11;;;2972:18;2959:11;;;2952:39;2924:2;2917:10;2888:113;;;-1:-1:-1;;3035:1:43;3017:16;;3010:27;2793:250::o;3048:271::-;3090:3;3128:5;3122:12;3155:6;3150:3;3143:19;3171:76;3240:6;3233:4;3228:3;3224:14;3217:4;3210:5;3206:16;3171:76;:::i;:::-;3301:2;3280:15;-1:-1:-1;;3276:29:43;3267:39;;;;3308:4;3263:50;;3048:271;-1:-1:-1;;3048:271:43:o;3529:1303::-;3616:12;;-1:-1:-1;;;;;3390:31:43;3378:44;;3683:4;3676:5;3672:16;3666:23;3659:4;3654:3;3650:14;3643:47;3739:4;3732:5;3728:16;3722:23;3715:4;3710:3;3706:14;3699:47;3795:4;3788:5;3784:16;3778:23;3771:4;3766:3;3762:14;3755:47;3851:4;3844:5;3840:16;3834:23;3827:4;3822:3;3818:14;3811:47;3907:4;3900:5;3896:16;3890:23;3883:4;3878:3;3874:14;3867:47;3963:4;3956:5;3952:16;3946:23;3939:4;3934:3;3930:14;3923:47;4019:4;4012:5;4008:16;4002:23;3995:4;3990:3;3986:14;3979:47;4045:6;4098:2;4091:5;4087:14;4081:21;4076:2;4071:3;4067:12;4060:43;;4122:6;4175:2;4168:5;4164:14;4158:21;4153:2;4148:3;4144:12;4137:43;;4199:6;4252:2;4245:5;4241:14;4235:21;4230:2;4225:3;4221:12;4214:43;;4276:6;4328:2;4321:5;4317:14;4311:21;4341:43;4380:2;4375:3;4371:12;4357;3503:13;3496:21;3484:34;;3433:91;4341:43;-1:-1:-1;;4403:6:43;4445:14;;;4439:21;4425:12;;;4418:43;4480:6;4523:14;;;4517:21;-1:-1:-1;;;;;3390:31:43;4582:12;;;3378:44;4614:6;4656:14;;;4650:21;4636:12;;;4629:43;4691:6;4733:14;;;4727:21;4713:12;;;4706:43;4768:6;4810:14;;;4804:21;4790:12;;4783:43;3529:1303::o;4837:483::-;4904:3;4942:5;4936:12;4969:6;4964:3;4957:19;4995:4;5024;5019:3;5015:14;5008:21;;5063:4;5056:5;5052:16;5086:1;5096:199;5110:6;5107:1;5104:13;5096:199;;;5159:52;5207:3;5198:6;5192:13;5159:52;:::i;:::-;5240:6;5231:16;;;;;5270:15;;;;5132:1;5125:9;5096:199;;;-1:-1:-1;5311:3:43;;4837:483;-1:-1:-1;;;;;4837:483:43:o;5325:1628::-;5376:3;5404:6;5445:5;5439:12;5472:2;5467:3;5460:15;5496:45;5537:2;5532:3;5528:12;5514;5496:45;:::i;:::-;5484:57;;;5589:4;5582:5;5578:16;5572:23;5604:50;5648:4;5643:3;5639:14;5623;-1:-1:-1;;;;;3390:31:43;3378:44;;3324:104;5604:50;;5702:4;5695:5;5691:16;5685:23;5717:50;5761:4;5756:3;5752:14;5736;-1:-1:-1;;;;;3390:31:43;3378:44;;3324:104;5717:50;;5816:4;5809:5;5805:16;5799:23;5792:4;5787:3;5783:14;5776:47;5872:4;5865:5;5861:16;5855:23;5848:4;5843:3;5839:14;5832:47;5927:4;5920:5;5916:16;5910:23;5975:3;5969:4;5965:14;5958:4;5953:3;5949:14;5942:38;6003:39;6037:4;6021:14;6003:39;:::i;:::-;5989:53;;;6090:4;6083:5;6079:16;6073:23;6140:3;6132:6;6128:16;6121:4;6116:3;6112:14;6105:40;6168:41;6202:6;6186:14;6168:41;:::i;:::-;6154:55;;;6257:4;6250:5;6246:16;6240:23;6307:3;6299:6;6295:16;6288:4;6283:3;6279:14;6272:40;6335:41;6369:6;6353:14;6335:41;:::i;:::-;6321:55;;;6395:6;6449:2;6442:5;6438:14;6432:21;6462:48;6506:2;6501:3;6497:12;6481:14;-1:-1:-1;;;;;3390:31:43;3378:44;;3324:104;6462:48;-1:-1:-1;;6529:6:43;6571:14;;;6565:21;6551:12;;;6544:43;6606:6;6648:14;;;6642:21;6628:12;;;6621:43;6683:6;6725:14;;;6719:21;6705:12;;;6698:43;6760:6;6803:14;;;6797:21;6848:16;;;6834:12;;;6827:38;6881:66;6852:6;6797:21;6881:66;:::i;:::-;6874:73;5325:1628;-1:-1:-1;;;;;;5325:1628:43:o;6958:261::-;7139:2;7128:9;7121:21;7102:4;7159:54;7209:2;7198:9;7194:18;7186:6;7159:54;:::i;7224:375::-;7295:8;7305:6;7359:3;7352:4;7344:6;7340:17;7336:27;7326:55;;7377:1;7374;7367:12;7326:55;-1:-1:-1;7400:20:43;;-1:-1:-1;;;;;7432:30:43;;7429:50;;;7475:1;7472;7465:12;7429:50;7512:4;7504:6;7500:17;7488:29;;7572:3;7565:4;7555:6;7552:1;7548:14;7540:6;7536:27;7532:38;7529:47;7526:67;;;7589:1;7586;7579:12;7526:67;7224:375;;;;;:::o;7604:461::-;7706:6;7714;7767:2;7755:9;7746:7;7742:23;7738:32;7735:52;;;7783:1;7780;7773:12;7735:52;7823:9;7810:23;-1:-1:-1;;;;;7848:6:43;7845:30;7842:50;;;7888:1;7885;7878:12;7842:50;7927:78;7997:7;7988:6;7977:9;7973:22;7927:78;:::i;:::-;8024:8;;7901:104;;-1:-1:-1;7604:461:43;-1:-1:-1;;;;7604:461:43:o;8263:764::-;8512:2;8564:21;;;8634:13;;8537:18;;;8656:22;;;8483:4;;8512:2;8697;;8715:18;;;;8756:15;;;8483:4;8799:202;8813:6;8810:1;8807:13;8799:202;;;8862:59;8917:3;8908:6;8902:13;8161:12;;-1:-1:-1;;;;;8157:38:43;8145:51;;8245:4;8234:16;;;8228:23;8212:14;;8205:47;8070:188;8862:59;8941:12;;;;8976:15;;;;8835:1;8828:9;8799:202;;;-1:-1:-1;9018:3:43;;8263:764;-1:-1:-1;;;;;;;8263:764:43:o;9032:247::-;9091:6;9144:2;9132:9;9123:7;9119:23;9115:32;9112:52;;;9160:1;9157;9150:12;9112:52;9199:9;9186:23;9218:31;9243:5;9218:31;:::i;9284:844::-;9478:4;9507:2;9547;9536:9;9532:18;9577:2;9566:9;9559:21;9600:6;9635;9629:13;9666:6;9658;9651:22;9704:2;9693:9;9689:18;9682:25;;9766:2;9756:6;9753:1;9749:14;9738:9;9734:30;9730:39;9716:53;;9804:2;9796:6;9792:15;9825:1;9835:264;9849:6;9846:1;9843:13;9835:264;;;9942:2;9938:7;9926:9;9918:6;9914:22;9910:36;9905:3;9898:49;9970;10012:6;10003;9997:13;9970:49;:::i;:::-;9960:59;-1:-1:-1;10077:12:43;;;;10042:15;;;;9871:1;9864:9;9835:264;;;-1:-1:-1;10116:6:43;;9284:844;-1:-1:-1;;;;;;;9284:844:43:o;10133:964::-;10318:2;10329:21;;;10432:13;;-1:-1:-1;;;;;10428:39:43;10408:18;;;10401:67;10493:15;;;10487:22;10528:2;10546:18;;;10539:30;;;;10604:15;;;10598:22;10658:4;10636:20;;;10629:34;10712:19;;10388:3;10373:19;;10740:22;;;10289:4;;10318:2;10820:21;;;;10289:4;;10455:3;10778:19;;;10869:202;10883:6;10880:1;10877:13;10869:202;;;10932:59;10987:3;10978:6;10972:13;8161:12;;-1:-1:-1;;;;;8157:38:43;8145:51;;8245:4;8234:16;;;8228:23;8212:14;;8205:47;8070:188;10932:59;11046:15;;;;10905:1;10898:9;;;;;11011:12;;;;10869:202;;;-1:-1:-1;11088:3:43;10133:964;-1:-1:-1;;;;;;;10133:964:43:o;11102:388::-;11170:6;11178;11231:2;11219:9;11210:7;11206:23;11202:32;11199:52;;;11247:1;11244;11237:12;11199:52;11286:9;11273:23;11305:31;11330:5;11305:31;:::i;:::-;11355:5;-1:-1:-1;11412:2:43;11397:18;;11384:32;11425:33;11384:32;11425:33;:::i;:::-;11477:7;11467:17;;;11102:388;;;;;:::o;11495:529::-;11572:6;11580;11588;11641:2;11629:9;11620:7;11616:23;11612:32;11609:52;;;11657:1;11654;11647:12;11609:52;11696:9;11683:23;11715:31;11740:5;11715:31;:::i;:::-;11765:5;-1:-1:-1;11822:2:43;11807:18;;11794:32;11835:33;11794:32;11835:33;:::i;:::-;11887:7;-1:-1:-1;11946:2:43;11931:18;;11918:32;11959:33;11918:32;11959:33;:::i;:::-;12011:7;12001:17;;;11495:529;;;;;:::o;12419:1776::-;12623:4;12652:2;12692;12681:9;12677:18;12722:2;12711:9;12704:21;12745:6;12780;12774:13;12811:6;12803;12796:22;12837:2;12827:12;;12870:2;12859:9;12855:18;12848:25;;12932:2;12922:6;12919:1;12915:14;12904:9;12900:30;12896:39;12970:2;12962:6;12958:15;12991:1;13012;13022:1144;13038:6;13033:3;13030:15;13022:1144;;;13107:22;;;-1:-1:-1;;13103:36:43;13091:49;;13163:13;;13319:9;;-1:-1:-1;;;;;13315:18:43;;;13300:34;;13381:11;;;13375:18;13371:27;13354:15;;;13347:52;13442:11;;;13436:18;13419:15;;;13412:43;13478:4;13521:11;;;13515:18;13199:4;13553:15;;;13546:27;;;13634:19;;13230:15;;;13666:24;;;13757:21;;;13276:3;13712:16;;;13802:1;;13816:242;13832:8;13827:3;13824:17;13816:242;;;13895:63;13952:5;13941:8;13935:15;8161:12;;-1:-1:-1;;;;;8157:38:43;8145:51;;8245:4;8234:16;;;8228:23;8212:14;;8205:47;8070:188;13895:63;14027:17;;;;13984:14;;;;13860:1;13851:11;;;;;13816:242;;;-1:-1:-1;;14144:12:43;;;;14081:5;-1:-1:-1;;14109:15:43;;;;13064:1;13055:11;13022:1144;;;-1:-1:-1;14183:6:43;;12419:1776;-1:-1:-1;;;;;;;;;12419:1776:43:o;14200:596::-;14311:6;14319;14327;14380:2;14368:9;14359:7;14355:23;14351:32;14348:52;;;14396:1;14393;14386:12;14348:52;14436:9;14423:23;-1:-1:-1;;;;;14461:6:43;14458:30;14455:50;;;14501:1;14498;14491:12;14455:50;14540:78;14610:7;14601:6;14590:9;14586:22;14540:78;:::i;:::-;14637:8;;-1:-1:-1;14514:104:43;-1:-1:-1;;14722:2:43;14707:18;;14694:32;14735:31;14694:32;14735:31;:::i;14801:405::-;14885:12;;-1:-1:-1;;;;;14881:38:43;14869:51;;14969:4;14958:16;;;14952:23;14936:14;;;14929:47;15025:4;15014:16;;;15008:23;14992:14;;;14985:47;15081:4;15070:16;;;15064:23;15048:14;;;15041:47;15137:4;15126:16;;;15120:23;15104:14;;;15097:47;14907:3;15182:16;;;15176:23;15160:14;;15153:47;14801:405::o;15211:724::-;15446:2;15498:21;;;15568:13;;15471:18;;;15590:22;;;15417:4;;15446:2;15669:15;;;;15643:2;15628:18;;;15417:4;15712:197;15726:6;15723:1;15720:13;15712:197;;;15775:52;15823:3;15814:6;15808:13;15775:52;:::i;:::-;15884:15;;;;15856:4;15847:14;;;;;15748:1;15741:9;15712:197;;;-1:-1:-1;15926:3:43;;15211:724;-1:-1:-1;;;;;;15211:724:43:o;16208:288::-;8161:12;;-1:-1:-1;;;;;8157:38:43;8145:51;;8245:4;8234:16;;;8228:23;8212:14;;;8205:47;16420:2;16405:18;;16432:58;8070:188;16501:268;16699:3;16684:19;;16712:51;16688:9;16745:6;16712:51;:::i;17183:268::-;17381:3;17366:19;;17394:51;17370:9;17427:6;17394:51;:::i;17648:658::-;17819:2;17871:21;;;17941:13;;17844:18;;;17963:22;;;17790:4;;17819:2;18042:15;;;;18016:2;18001:18;;;17790:4;18085:195;18099:6;18096:1;18093:13;18085:195;;;18164:13;;-1:-1:-1;;;;;18160:39:43;18148:52;;18255:15;;;;18220:12;;;;18196:1;18114:9;18085:195;;18311:191;18379:4;-1:-1:-1;;;;;18404:6:43;18401:30;18398:56;;;18434:18;;:::i;:::-;-1:-1:-1;18479:1:43;18475:14;18491:4;18471:25;;18311:191::o;18507:990::-;18607:6;18638:2;18681;18669:9;18660:7;18656:23;18652:32;18649:52;;;18697:1;18694;18687:12;18649:52;18737:9;18724:23;-1:-1:-1;;;;;18762:6:43;18759:30;18756:50;;;18802:1;18799;18792:12;18756:50;18825:22;;18878:4;18870:13;;18866:27;-1:-1:-1;18856:55:43;;18907:1;18904;18897:12;18856:55;18943:2;18930:16;18966:68;18982:51;19030:2;18982:51;:::i;18966:68::-;19068:15;;;19150:1;19146:10;;;;19138:19;;19134:28;;;19099:12;;;;19174:19;;;19171:39;;;19206:1;19203;19196:12;19171:39;19230:11;;;;19250:217;19266:6;19261:3;19258:15;19250:217;;;19346:3;19333:17;19363:31;19388:5;19363:31;:::i;:::-;19407:18;;19283:12;;;;19445;;;;19250:217;;;19486:5;18507:990;-1:-1:-1;;;;;;;18507:990:43:o;19502:726::-;19737:2;19789:21;;;19859:13;;19762:18;;;19881:22;;;19708:4;;19737:2;19960:15;;;;19934:2;19919:18;;;19708:4;20003:199;20017:6;20014:1;20011:13;20003:199;;;20066:52;20114:3;20105:6;20099:13;20066:52;:::i;:::-;20177:15;;;;20147:6;20138:16;;;;;20039:1;20032:9;20003:199;;20233:980;20344:6;20375:2;20418;20406:9;20397:7;20393:23;20389:32;20386:52;;;20434:1;20431;20424:12;20386:52;20467:9;20461:16;-1:-1:-1;;;;;20492:6:43;20489:30;20486:50;;;20532:1;20529;20522:12;20486:50;20555:22;;20608:4;20600:13;;20596:27;-1:-1:-1;20586:55:43;;20637:1;20634;20627:12;20586:55;20666:2;20660:9;20689:68;20705:51;20753:2;20705:51;:::i;20689:68::-;20791:15;;;20873:1;20869:10;;;;20861:19;;20857:28;;;20822:12;;;;20897:19;;;20894:39;;;20929:1;20926;20919:12;20894:39;20953:11;;;;20973:210;20989:6;20984:3;20981:15;20973:210;;;21062:3;21056:10;21079:31;21104:5;21079:31;:::i;:::-;21123:18;;21006:12;;;;21161;;;;20973:210;;21218:443;21272:5;21325:3;21318:4;21310:6;21306:17;21302:27;21292:55;;21343:1;21340;21333:12;21292:55;21372:6;21366:13;21403:49;21419:32;21448:2;21419:32;:::i;21403:49::-;21477:2;21468:7;21461:19;21523:3;21516:4;21511:2;21503:6;21499:15;21495:26;21492:35;21489:55;;;21540:1;21537;21530:12;21489:55;21553:77;21627:2;21620:4;21611:7;21607:18;21600:4;21592:6;21588:17;21553:77;:::i;21666:982::-;21772:6;21825:2;21813:9;21804:7;21800:23;21796:32;21793:52;;;21841:1;21838;21831:12;21793:52;21874:9;21868:16;-1:-1:-1;;;;;21944:2:43;21936:6;21933:14;21930:34;;;21960:1;21957;21950:12;21930:34;21983:22;;;;22039:4;22021:16;;;22017:27;22014:47;;;22057:1;22054;22047:12;22014:47;22083:22;;:::i;:::-;22136:2;22130:9;22164:2;22154:8;22151:16;22148:36;;;22180:1;22177;22170:12;22148:36;22207:56;22255:7;22244:8;22240:2;22236:17;22207:56;:::i;:::-;22200:5;22193:71;;22303:2;22299;22295:11;22289:18;22332:2;22322:8;22319:16;22316:36;;;22348:1;22345;22338:12;22316:36;22384:56;22432:7;22421:8;22417:2;22413:17;22384:56;:::i;:::-;22379:2;22372:5;22368:14;22361:80;;22480:2;22476;22472:11;22466:18;22509:2;22499:8;22496:16;22493:36;;;22525:1;22522;22515:12;22493:36;22561:56;22609:7;22598:8;22594:2;22590:17;22561:56;:::i;:::-;22556:2;22545:14;;22538:80;-1:-1:-1;22549:5:43;21666:982;-1:-1:-1;;;;;21666:982:43:o;22653:284::-;22756:6;22809:2;22797:9;22788:7;22784:23;22780:32;22777:52;;;22825:1;22822;22815:12;22777:52;22857:9;22851:16;22876:31;22901:5;22876:31;:::i;22942:184::-;23012:6;23065:2;23053:9;23044:7;23040:23;23036:32;23033:52;;;23081:1;23078;23071:12;23033:52;-1:-1:-1;23104:16:43;;22942:184;-1:-1:-1;22942:184:43:o;23131:127::-;23192:10;23187:3;23183:20;23180:1;23173:31;23223:4;23220:1;23213:15;23247:4;23244:1;23237:15;23263:742;23330:5;23378:4;23366:9;23361:3;23357:19;23353:30;23350:50;;;23396:1;23393;23386:12;23350:50;23418:22;;:::i;:::-;23409:31;;23469:9;23463:16;-1:-1:-1;;;;;23494:6:43;23491:30;23488:50;;;23534:1;23531;23524:12;23488:50;23561:57;23614:3;23605:6;23594:9;23590:22;23561:57;:::i;:::-;23554:5;23547:72;;23664:2;23653:9;23649:18;23643:25;23677:33;23702:7;23677:33;:::i;:::-;23737:2;23726:14;;23719:31;23795:2;23780:18;;23774:25;23808:33;23774:25;23808:33;:::i;:::-;23873:7;23868:2;23861:5;23857:14;23850:31;;23934:2;23923:9;23919:18;23913:25;23908:2;23901:5;23897:14;23890:49;23993:3;23982:9;23978:19;23972:26;23966:3;23959:5;23955:15;23948:51;23263:742;;;;:::o;24010:1170::-;24133:6;24164:2;24207;24195:9;24186:7;24182:23;24178:32;24175:52;;;24223:1;24220;24213:12;24175:52;24256:9;24250:16;-1:-1:-1;;;;;24326:2:43;24318:6;24315:14;24312:34;;;24342:1;24339;24332:12;24312:34;24380:6;24369:9;24365:22;24355:32;;24425:7;24418:4;24414:2;24410:13;24406:27;24396:55;;24447:1;24444;24437:12;24396:55;24476:2;24470:9;24499:68;24515:51;24563:2;24515:51;:::i;24499:68::-;24601:15;;;24683:1;24679:10;;;;24671:19;;24667:28;;;24632:12;;;;24707:19;;;24704:39;;;24739:1;24736;24729:12;24704:39;24771:2;24767;24763:11;24783:367;24799:6;24794:3;24791:15;24783:367;;;24878:3;24872:10;24914:2;24901:11;24898:19;24895:109;;;24958:1;24987:2;24983;24976:14;24895:109;25029:78;25099:7;25094:2;25080:11;25076:2;25072:20;25068:29;25029:78;:::i;:::-;25017:91;;-1:-1:-1;25128:12:43;;;;24816;;24783:367;;;-1:-1:-1;25169:5:43;24010:1170;-1:-1:-1;;;;;;;;24010:1170:43:o;25185:127::-;25246:10;25241:3;25237:20;25234:1;25227:31;25277:4;25274:1;25267:15;25301:4;25298:1;25291:15;25317:168;25390:9;;;25421;;25438:15;;;25432:22;;25418:37;25408:71;;25459:18;;:::i;25490:217::-;25530:1;25556;25546:132;;25600:10;25595:3;25591:20;25588:1;25581:31;25635:4;25632:1;25625:15;25663:4;25660:1;25653:15;25546:132;-1:-1:-1;25692:9:43;;25490:217::o;25712:125::-;25777:9;;;25798:10;;;25795:36;;;25811:18;;:::i;25842:365::-;25940:6;25993:2;25981:9;25972:7;25968:23;25964:32;25961:52;;;26009:1;26006;25999:12;25961:52;26042:9;26036:16;-1:-1:-1;;;;;26067:6:43;26064:30;26061:50;;;26107:1;26104;26097:12;26061:50;26130:71;26193:7;26184:6;26173:9;26169:22;26130:71;:::i;26777:992::-;26900:6;26931:2;26974;26962:9;26953:7;26949:23;26945:32;26942:52;;;26990:1;26987;26980:12;26942:52;27023:9;27017:16;-1:-1:-1;;;;;27048:6:43;27045:30;27042:50;;;27088:1;27085;27078:12;27042:50;27111:22;;27164:4;27156:13;;27152:27;-1:-1:-1;27142:55:43;;27193:1;27190;27183:12;27142:55;27222:2;27216:9;27245:68;27261:51;27309:2;27261:51;:::i;27245:68::-;27347:15;;;27429:1;27425:10;;;;27417:19;;27413:28;;;27378:12;;;;27453:19;;;27450:39;;;27485:1;27482;27475:12;27450:39;27509:11;;;;27529:210;27545:6;27540:3;27537:15;27529:210;;;27618:3;27612:10;27635:31;27660:5;27635:31;:::i;:::-;27679:18;;27562:12;;;;27717;;;;27529:210;;28340:164;28416:13;;28465;;28458:21;28448:32;;28438:60;;28494:1;28491;28484:12;28509:263;28585:6;28593;28646:2;28634:9;28625:7;28621:23;28617:32;28614:52;;;28662:1;28659;28652:12;28614:52;28685:37;28712:9;28685:37;:::i;:::-;28675:47;;28762:2;28751:9;28747:18;28741:25;28731:35;;28509:263;;;;;:::o;28777:273::-;28845:6;28898:2;28886:9;28877:7;28873:23;28869:32;28866:52;;;28914:1;28911;28904:12;28866:52;28946:9;28940:16;28996:4;28989:5;28985:16;28978:5;28975:27;28965:55;;29016:1;29013;29006:12;29055:127;29116:10;29111:3;29107:20;29104:1;29097:31;29147:4;29144:1;29137:15;29171:4;29168:1;29161:15;29187:436;-1:-1:-1;;;;;29388:32:43;;29370:51;;29358:2;29343:18;;29451:1;29440:13;;29430:144;;29496:10;29491:3;29487:20;29484:1;29477:31;29531:4;29528:1;29521:15;29559:4;29556:1;29549:15;29430:144;29610:6;29605:2;29594:9;29590:18;29583:34;29187:436;;;;;:::o;29628:202::-;29695:6;29748:2;29736:9;29727:7;29723:23;29719:32;29716:52;;;29764:1;29761;29754:12;29716:52;29787:37;29814:9;29787:37;:::i;29835:175::-;29872:3;29916:4;29909:5;29905:16;29945:4;29936:7;29933:17;29930:43;;29953:18;;:::i;:::-;30002:1;29989:15;;29835:175;-1:-1:-1;;29835:175:43:o;30015:964::-;30110:6;30141:2;30184;30172:9;30163:7;30159:23;30155:32;30152:52;;;30200:1;30197;30190:12;30152:52;30233:9;30227:16;-1:-1:-1;;;;;30258:6:43;30255:30;30252:50;;;30298:1;30295;30288:12;30252:50;30321:22;;30374:4;30366:13;;30362:27;-1:-1:-1;30352:55:43;;30403:1;30400;30393:12;30352:55;30432:2;30426:9;30455:68;30471:51;30519:2;30471:51;:::i;30455:68::-;30557:15;;;30639:1;30635:10;;;;30627:19;;30623:28;;;30588:12;;;;30663:19;;;30660:39;;;30695:1;30692;30685:12;30660:39;30719:11;;;;30739:210;30755:6;30750:3;30747:15;30739:210;;;30828:3;30822:10;30845:31;30870:5;30845:31;:::i;:::-;30889:18;;30772:12;;;;30927;;;;30739:210;;30984:177;31063:13;;-1:-1:-1;;;;;31105:31:43;;31095:42;;31085:70;;31151:1;31148;31141:12;31166:330;31254:6;31262;31270;31323:2;31311:9;31302:7;31298:23;31294:32;31291:52;;;31339:1;31336;31329:12;31291:52;31362:40;31392:9;31362:40;:::i;:::-;31352:50;;31442:2;31431:9;31427:18;31421:25;31411:35;;31486:2;31475:9;31471:18;31465:25;31455:35;;31166:330;;;;;:::o;31501:167::-;31579:13;;31632:10;31621:22;;31611:33;;31601:61;;31658:1;31655;31648:12;31673:374;31759:6;31767;31775;31828:2;31816:9;31807:7;31803:23;31799:32;31796:52;;;31844:1;31841;31834:12;31796:52;31867:40;31897:9;31867:40;:::i;:::-;31857:50;;31926:48;31970:2;31959:9;31955:18;31926:48;:::i;:::-;31916:58;;31993:48;32037:2;32026:9;32022:18;31993:48;:::i;:::-;31983:58;;31673:374;;;;;:::o;32052:208::-;32122:6;32175:2;32163:9;32154:7;32150:23;32146:32;32143:52;;;32191:1;32188;32181:12;32143:52;32214:40;32244:9;32214:40;:::i;32265:128::-;32332:9;;;32353:11;;;32350:37;;;32367:18;;:::i;32398:220::-;32547:2;32536:9;32529:21;32510:4;32567:45;32608:2;32597:9;32593:18;32585:6;32567:45;:::i
Swarm Source
ipfs://8dc9fbc3de84a5eadf5a8d0d7d030c880d5f4a9665121e956562816524e6c899
Loading...
Loading
Loading...
Loading
Net Worth in USD
$0.00
Net Worth in ETH
0
Multichain Portfolio | 34 Chains
| Chain | Token | Portfolio % | Price | Amount | Value |
|---|
Loading...
Loading
Loading...
Loading
Loading...
Loading
A contract address hosts a smart contract, which is a set of code stored on the blockchain that runs when predetermined conditions are met. Learn more about addresses in our Knowledge Base.