Overview
S Balance
0 S
S Value
$0.00More Info
Private Name Tags
ContractCreator
Loading...
Loading
Contract Source Code Verified (Exact Match)
Contract Name:
VToken
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 { 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 "./Governance/AccessControlledV8.sol"; import { IProtocolShareReserve } from "./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 "./TimeManagerV8.sol"; import { ensureNonzeroAddress } from "./lib/validators.sol"; /** * @title VToken * @author Enclabs * @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 Enclabs 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 Enclabs 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 Enclabs 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 Enclabs 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 Enclabs 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 Enclabs 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 Enclabs 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 Enclabs 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 Enclabs 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 Enclabs 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 Enclabs 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 Enclabs 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 Enclabs 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: 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) (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.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.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: 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; 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; /// @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 { Ownable2StepUpgradeable } from "@openzeppelin/contracts-upgradeable/access/Ownable2StepUpgradeable.sol"; import { ResilientOracleInterface } from "./Oracle/OracleInterface.sol"; import { AccessControlledV8 } from "./Governance/AccessControlledV8.sol"; import { IPrime } from "./Prime/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 Enclabs * @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 Enclabs 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 Enclabs 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 Enclabs 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 Enclabs Core * @param account The account get liquidity for * @return error Always NO_ERROR for compatibility with Enclabs 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 Enclabs Core * @param account The account get liquidity for * @return error Always NO_ERROR for compatibility with Enclabs 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 Enclabs 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 Enclabs 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 Enclabs 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 "./Oracle/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 Enclabs * @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 Enclabs * @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 "./Oracle/OracleInterface.sol"; import { VToken } from "./VToken.sol"; import { RewardsDistributor } from "./Rewards/RewardsDistributor.sol"; import { IPrime } from "./Prime/IPrime.sol"; import { Action } from "./ComptrollerInterface.sol"; /** * @title ComptrollerStorage * @author Enclabs * @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 Enclabs * @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; import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; import "@openzeppelin/contracts-upgradeable/access/Ownable2StepUpgradeable.sol"; import "./IAccessControlManagerV8.sol"; /** * @title AccessControlledV8 * @author Enclabs * @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 Enclabs * @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; /** * @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: 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; /** * @title MaxLoopsLimitHelper * @author Enclabs * @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; 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; 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 "../Oracle/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 { 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 Enclabs * @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 Enclabs * @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; 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; import { IERC20Upgradeable } from "@openzeppelin/contracts-upgradeable/token/ERC20/IERC20Upgradeable.sol"; import { ResilientOracleInterface } from "./Oracle/OracleInterface.sol"; import { ComptrollerInterface } from "./ComptrollerInterface.sol"; import { InterestRateModel } from "./InterestRateModel.sol"; /** * @title VTokenStorage * @author Enclabs * @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 Enclabs * @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; /// @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; /// @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: 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(); } }
{ "evmVersion": "paris", "libraries": {}, "metadata": { "bytecodeHash": "ipfs", "useLiteralContent": true }, "optimizer": { "enabled": true, "runs": 200 }, "remappings": [], "outputSelection": { "*": { "*": [ "evm.bytecode", "evm.deployedBytecode", "devdoc", "userdoc", "metadata", "abi" ] } } }
Contract Security Audit
- No Contract Security Audit Submitted- Submit Audit Here
[{"inputs":[{"internalType":"bool","name":"timeBased_","type":"bool"},{"internalType":"uint256","name":"blocksPerYear_","type":"uint256"},{"internalType":"uint256","name":"maxBorrowRateMantissa_","type":"uint256"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[{"internalType":"uint256","name":"actualAddAmount","type":"uint256"}],"name":"AddReservesFactorFreshCheck","type":"error"},{"inputs":[],"name":"BorrowCashNotAvailable","type":"error"},{"inputs":[],"name":"BorrowFreshnessCheck","type":"error"},{"inputs":[],"name":"DelegateNotApproved","type":"error"},{"inputs":[],"name":"ForceLiquidateBorrowUnauthorized","type":"error"},{"inputs":[],"name":"HealBorrowUnauthorized","type":"error"},{"inputs":[],"name":"InvalidBlocksPerYear","type":"error"},{"inputs":[],"name":"InvalidTimeBasedConfiguration","type":"error"},{"inputs":[{"internalType":"uint256","name":"errorCode","type":"uint256"}],"name":"LiquidateAccrueCollateralInterestFailed","type":"error"},{"inputs":[],"name":"LiquidateCloseAmountIsUintMax","type":"error"},{"inputs":[],"name":"LiquidateCloseAmountIsZero","type":"error"},{"inputs":[],"name":"LiquidateCollateralFreshnessCheck","type":"error"},{"inputs":[],"name":"LiquidateFreshnessCheck","type":"error"},{"inputs":[],"name":"LiquidateLiquidatorIsBorrower","type":"error"},{"inputs":[],"name":"LiquidateSeizeLiquidatorIsBorrower","type":"error"},{"inputs":[],"name":"MintFreshnessCheck","type":"error"},{"inputs":[],"name":"ProtocolSeizeShareTooBig","type":"error"},{"inputs":[],"name":"RedeemFreshnessCheck","type":"error"},{"inputs":[],"name":"RedeemTransferOutNotPossible","type":"error"},{"inputs":[],"name":"ReduceReservesCashNotAvailable","type":"error"},{"inputs":[],"name":"ReduceReservesCashValidation","type":"error"},{"inputs":[],"name":"ReduceReservesFreshCheck","type":"error"},{"inputs":[],"name":"RepayBorrowFreshnessCheck","type":"error"},{"inputs":[],"name":"SetInterestRateModelFreshCheck","type":"error"},{"inputs":[],"name":"SetReserveFactorBoundsCheck","type":"error"},{"inputs":[],"name":"SetReserveFactorFreshCheck","type":"error"},{"inputs":[],"name":"TransferNotAllowed","type":"error"},{"inputs":[{"internalType":"address","name":"sender","type":"address"},{"internalType":"address","name":"calledContract","type":"address"},{"internalType":"string","name":"methodSignature","type":"string"}],"name":"Unauthorized","type":"error"},{"inputs":[],"name":"ZeroAddressNotAllowed","type":"error"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"cashPrior","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"interestAccumulated","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"borrowIndex","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"totalBorrows","type":"uint256"}],"name":"AccrueInterest","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":true,"internalType":"address","name":"spender","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"Approval","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"borrower","type":"address"},{"indexed":false,"internalType":"uint256","name":"badDebtDelta","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"badDebtOld","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"badDebtNew","type":"uint256"}],"name":"BadDebtIncreased","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"badDebtOld","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"badDebtNew","type":"uint256"}],"name":"BadDebtRecovered","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"borrower","type":"address"},{"indexed":false,"internalType":"uint256","name":"borrowAmount","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"accountBorrows","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"totalBorrows","type":"uint256"}],"name":"Borrow","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"payer","type":"address"},{"indexed":true,"internalType":"address","name":"borrower","type":"address"},{"indexed":false,"internalType":"uint256","name":"repayAmount","type":"uint256"}],"name":"HealBorrow","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint8","name":"version","type":"uint8"}],"name":"Initialized","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"liquidator","type":"address"},{"indexed":true,"internalType":"address","name":"borrower","type":"address"},{"indexed":false,"internalType":"uint256","name":"repayAmount","type":"uint256"},{"indexed":true,"internalType":"address","name":"vTokenCollateral","type":"address"},{"indexed":false,"internalType":"uint256","name":"seizeTokens","type":"uint256"}],"name":"LiquidateBorrow","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"minter","type":"address"},{"indexed":false,"internalType":"uint256","name":"mintAmount","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"mintTokens","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"accountBalance","type":"uint256"}],"name":"Mint","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"oldAccessControlManager","type":"address"},{"indexed":false,"internalType":"address","name":"newAccessControlManager","type":"address"}],"name":"NewAccessControlManager","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"contract ComptrollerInterface","name":"oldComptroller","type":"address"},{"indexed":true,"internalType":"contract ComptrollerInterface","name":"newComptroller","type":"address"}],"name":"NewComptroller","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"contract InterestRateModel","name":"oldInterestRateModel","type":"address"},{"indexed":true,"internalType":"contract InterestRateModel","name":"newInterestRateModel","type":"address"}],"name":"NewMarketInterestRateModel","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"oldProtocolSeizeShareMantissa","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"newProtocolSeizeShareMantissa","type":"uint256"}],"name":"NewProtocolSeizeShare","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"oldProtocolShareReserve","type":"address"},{"indexed":true,"internalType":"address","name":"newProtocolShareReserve","type":"address"}],"name":"NewProtocolShareReserve","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"oldReduceReservesBlockOrTimestampDelta","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"newReduceReservesBlockOrTimestampDelta","type":"uint256"}],"name":"NewReduceReservesBlockDelta","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"oldReserveFactorMantissa","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"newReserveFactorMantissa","type":"uint256"}],"name":"NewReserveFactor","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"oldShortfall","type":"address"},{"indexed":true,"internalType":"address","name":"newShortfall","type":"address"}],"name":"NewShortfallContract","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"previousOwner","type":"address"},{"indexed":true,"internalType":"address","name":"newOwner","type":"address"}],"name":"OwnershipTransferStarted","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"previousOwner","type":"address"},{"indexed":true,"internalType":"address","name":"newOwner","type":"address"}],"name":"OwnershipTransferred","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"from","type":"address"},{"indexed":true,"internalType":"address","name":"to","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"ProtocolSeize","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"redeemer","type":"address"},{"indexed":false,"internalType":"uint256","name":"redeemAmount","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"redeemTokens","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"accountBalance","type":"uint256"}],"name":"Redeem","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"payer","type":"address"},{"indexed":true,"internalType":"address","name":"borrower","type":"address"},{"indexed":false,"internalType":"uint256","name":"repayAmount","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"accountBorrows","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"totalBorrows","type":"uint256"}],"name":"RepayBorrow","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"benefactor","type":"address"},{"indexed":false,"internalType":"uint256","name":"addAmount","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"newTotalReserves","type":"uint256"}],"name":"ReservesAdded","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"protocolShareReserve","type":"address"},{"indexed":false,"internalType":"uint256","name":"reduceAmount","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"newTotalReserves","type":"uint256"}],"name":"SpreadReservesReduced","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"token","type":"address"}],"name":"SweepToken","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"from","type":"address"},{"indexed":true,"internalType":"address","name":"to","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"Transfer","type":"event"},{"inputs":[],"name":"NO_ERROR","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"acceptOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"accessControlManager","outputs":[{"internalType":"contract IAccessControlManagerV8","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"accrualBlockNumber","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"accrueInterest","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"addAmount","type":"uint256"}],"name":"addReserves","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"owner","type":"address"},{"internalType":"address","name":"spender","type":"address"}],"name":"allowance","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"spender","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"approve","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"badDebt","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"recoveredAmount_","type":"uint256"}],"name":"badDebtRecovered","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"owner","type":"address"}],"name":"balanceOf","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"owner","type":"address"}],"name":"balanceOfUnderlying","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"blocksOrSecondsPerYear","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"borrowAmount","type":"uint256"}],"name":"borrow","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"account","type":"address"}],"name":"borrowBalanceCurrent","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"account","type":"address"}],"name":"borrowBalanceStored","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"borrower","type":"address"},{"internalType":"uint256","name":"borrowAmount","type":"uint256"}],"name":"borrowBehalf","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"borrowIndex","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"borrowRatePerBlock","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"comptroller","outputs":[{"internalType":"contract ComptrollerInterface","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"decimals","outputs":[{"internalType":"uint8","name":"","type":"uint8"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"spender","type":"address"},{"internalType":"uint256","name":"subtractedValue","type":"uint256"}],"name":"decreaseAllowance","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"exchangeRateCurrent","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"exchangeRateStored","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"liquidator","type":"address"},{"internalType":"address","name":"borrower","type":"address"},{"internalType":"uint256","name":"repayAmount","type":"uint256"},{"internalType":"contract VTokenInterface","name":"vTokenCollateral","type":"address"},{"internalType":"bool","name":"skipLiquidityCheck","type":"bool"}],"name":"forceLiquidateBorrow","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"account","type":"address"}],"name":"getAccountSnapshot","outputs":[{"internalType":"uint256","name":"error","type":"uint256"},{"internalType":"uint256","name":"vTokenBalance","type":"uint256"},{"internalType":"uint256","name":"borrowBalance","type":"uint256"},{"internalType":"uint256","name":"exchangeRate","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getBlockNumberOrTimestamp","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getCash","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"payer","type":"address"},{"internalType":"address","name":"borrower","type":"address"},{"internalType":"uint256","name":"repayAmount","type":"uint256"}],"name":"healBorrow","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"spender","type":"address"},{"internalType":"uint256","name":"addedValue","type":"uint256"}],"name":"increaseAllowance","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"underlying_","type":"address"},{"internalType":"contract ComptrollerInterface","name":"comptroller_","type":"address"},{"internalType":"contract InterestRateModel","name":"interestRateModel_","type":"address"},{"internalType":"uint256","name":"initialExchangeRateMantissa_","type":"uint256"},{"internalType":"string","name":"name_","type":"string"},{"internalType":"string","name":"symbol_","type":"string"},{"internalType":"uint8","name":"decimals_","type":"uint8"},{"internalType":"address","name":"admin_","type":"address"},{"internalType":"address","name":"accessControlManager_","type":"address"},{"components":[{"internalType":"address","name":"shortfall","type":"address"},{"internalType":"address payable","name":"protocolShareReserve","type":"address"}],"internalType":"struct VTokenInterface.RiskManagementInit","name":"riskManagement","type":"tuple"},{"internalType":"uint256","name":"reserveFactorMantissa_","type":"uint256"}],"name":"initialize","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"interestRateModel","outputs":[{"internalType":"contract InterestRateModel","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"isTimeBased","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"isVToken","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"borrower","type":"address"},{"internalType":"uint256","name":"repayAmount","type":"uint256"},{"internalType":"contract VTokenInterface","name":"vTokenCollateral","type":"address"}],"name":"liquidateBorrow","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"mintAmount","type":"uint256"}],"name":"mint","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"minter","type":"address"},{"internalType":"uint256","name":"mintAmount","type":"uint256"}],"name":"mintBehalf","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"name","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"owner","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"pendingOwner","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"protocolSeizeShareMantissa","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"protocolShareReserve","outputs":[{"internalType":"address payable","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"redeemTokens","type":"uint256"}],"name":"redeem","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"redeemer","type":"address"},{"internalType":"uint256","name":"redeemTokens","type":"uint256"}],"name":"redeemBehalf","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"redeemAmount","type":"uint256"}],"name":"redeemUnderlying","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"redeemer","type":"address"},{"internalType":"uint256","name":"redeemAmount","type":"uint256"}],"name":"redeemUnderlyingBehalf","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"reduceAmount","type":"uint256"}],"name":"reduceReserves","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"reduceReservesBlockDelta","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"reduceReservesBlockNumber","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"renounceOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"repayAmount","type":"uint256"}],"name":"repayBorrow","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"borrower","type":"address"},{"internalType":"uint256","name":"repayAmount","type":"uint256"}],"name":"repayBorrowBehalf","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"reserveFactorMantissa","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"liquidator","type":"address"},{"internalType":"address","name":"borrower","type":"address"},{"internalType":"uint256","name":"seizeTokens","type":"uint256"}],"name":"seize","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"accessControlManager_","type":"address"}],"name":"setAccessControlManager","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"contract InterestRateModel","name":"newInterestRateModel","type":"address"}],"name":"setInterestRateModel","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"newProtocolSeizeShareMantissa_","type":"uint256"}],"name":"setProtocolSeizeShare","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address payable","name":"protocolShareReserve_","type":"address"}],"name":"setProtocolShareReserve","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_newReduceReservesBlockOrTimestampDelta","type":"uint256"}],"name":"setReduceReservesBlockDelta","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"newReserveFactorMantissa","type":"uint256"}],"name":"setReserveFactor","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"shortfall_","type":"address"}],"name":"setShortfallContract","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"shortfall","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"supplyRatePerBlock","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"contract IERC20Upgradeable","name":"token","type":"address"}],"name":"sweepToken","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"symbol","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"totalBorrows","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"totalBorrowsCurrent","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"totalReserves","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"totalSupply","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"dst","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"transfer","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"src","type":"address"},{"internalType":"address","name":"dst","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"transferFrom","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"newOwner","type":"address"}],"name":"transferOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"underlying","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"}]
Contract Creation Code
61010060405234801561001157600080fd5b506040516150cb3803806150cb83398101604081905261003091610202565b82828115801561003e575080155b1561005c576040516302723dfb60e21b815260040160405180910390fd5b81801561006857508015155b156100865760405163ae0fcab360e01b815260040160405180910390fd5b81151560a05281610097578061009d565b6301e133805b608052816100b45761013f60201b6120a5176100bf565b61014360201b6120a9175b6001600160401b031660c0525050670de0b6b3a764000081111561012a5760405162461bcd60e51b815260206004820152601f60248201527f4d617820626f72726f772072617465206d757374206265203c3d20316531380060448201526064015b60405180910390fd5b60e0819052610137610147565b50505061023e565b4390565b4290565b600054610100900460ff16156101af5760405162461bcd60e51b815260206004820152602760248201527f496e697469616c697a61626c653a20636f6e747261637420697320696e697469604482015266616c697a696e6760c81b6064820152608401610121565b60005460ff90811614610200576000805460ff191660ff9081179091556040519081527f7f26b83ff96e1f2b6a682f133852f6798a09c465da95921460cefb38474024989060200160405180910390a15b565b60008060006060848603121561021757600080fd5b8351801515811461022757600080fd5b602085015160409095015190969495509392505050565b60805160a05160c05160e051614e546102776000396000611a3e01526000611e7901526000610877015260006106560152614e546000f3fe608060405234801561001057600080fd5b50600436106104335760003560e01c80637821a51411610236578063b2a02ff11161013b578063dd62ed3e116100c3578063ef60450c11610087578063ef60450c14610932578063f2fde38b14610945578063f3fdb15a14610958578063f5e3c4621461096b578063f8f9da281461097e57600080fd5b8063dd62ed3e146108b5578063df3a516e146108ee578063e1d146fb14610901578063e30c397814610909578063e9a44fd91461091a57600080fd5b8063c37f68e21161010a578063c37f68e21461082c578063c5ebeaec1461085f578063c7ad089514610872578063d1109c2f14610899578063db006a75146108a257600080fd5b8063b2a02ff1146107f7578063b4a0bdf31461080a578063bbcac5571461081b578063bd6d894d1461082457600080fd5b806395d89b41116101be578063a6afed951161018d578063a6afed95146107c2578063a9059cbb146107ca578063aa5af0fd146107dd578063ae96f141146107e6578063ae9d70b0146107ef57600080fd5b806395d89b411461078157806395dd919314610789578063a0712d681461079c578063a457c2d7146107af57600080fd5b80638a42c319116102055780638a42c3191461072e5780638bbdb6db146107415780638bcd4016146107545780638da5cb5b146107675780638f840ddd1461077857600080fd5b80637821a514146106ed57806379ba509714610700578063852a12e314610708578063856e5bb31461071b57600080fd5b8063313ce5671161033c5780636752e702116102c45780636f307dc3116102935780636f307dc31461068957806370a08231146106a1578063715018a6146106ca57806373acee98146106d2578063757212f0146106da57600080fd5b80636752e702146106485780636857249c1461065157806369ab3250146106785780636c540baf1461068057600080fd5b80633d9ea3a11161030b5780633d9ea3a1146105ff57806341f641ee1461060657806344fe6ffe1461061957806347bd37181461062c5780635fe3b5671461063557600080fd5b8063313ce567146105b257806339509351146105d15780633af9e669146105e45780633b1d21a2146105f757600080fd5b8063182df0f5116103bf578063210bc0521161038e578063210bc0521461055357806323323e031461056657806323b872dd146105795780632464176b1461058c5780632608f8181461059f57600080fd5b8063182df0f5146104fa57806319b1faef146105025780631be195601461052d5780631c4469831461054057600080fd5b80630e752702116104065780630e752702146104a1578063107568df146104c2578063173b9904146104d557806317bfdfbc146104de57806318160ddd146104f157600080fd5b806306fdde031461043857806307e2795914610456578063095ea7b31461046b5780630e32cb861461048e575b600080fd5b610440610986565b60405161044d919061468f565b60405180910390f35b6104696104643660046146a2565b610a14565b005b61047e6104793660046146e0565b610a7b565b604051901515815260200161044d565b61046961049c36600461470c565b610aec565b6104b46104af3660046146a2565b610b00565b60405190815260200161044d565b6104696104d036600461470c565b610b5a565b6104b460d05481565b6104b46104ec36600461470c565b610b6b565b6104b460d55481565b6104b4610bc0565b60db54610515906001600160a01b031681565b6040516001600160a01b03909116815260200161044d565b61046961053b36600461470c565b610bcf565b61046961054e3660046146a2565b610d8a565b6104b46105613660046146e0565b610e05565b6104b46105743660046146e0565b610e69565b61047e610587366004614729565b610eb5565b61046961059a3660046146a2565b610f07565b6104b46105ad3660046146e0565b610fa9565b60cc546105bf9060ff1681565b60405160ff909116815260200161044d565b61047e6105df3660046146e0565b611004565b6104b46105f236600461470c565b6110ac565b6104b46110f2565b600161047e565b61046961061436600461470c565b6110fc565b610469610627366004614729565b61110d565b6104b460d35481565b60cd54610515906001600160a01b031681565b6104b460da5481565b6104b47f000000000000000000000000000000000000000000000000000000000000000081565b6104b4600081565b6104b460d15481565b60c9546105159061010090046001600160a01b031681565b6104b46106af36600461470c565b6001600160a01b0316600090815260d7602052604090205490565b6104696113b0565b6104b46113c4565b6104696106e83660046146a2565b611410565b6104696106fb3660046146a2565b611537565b610469611586565b6104b46107163660046146a2565b6115fd565b6104b46107293660046146e0565b611657565b61046961073c366004614881565b61167f565b61046961074f366004614982565b6117af565b61046961076236600461470c565b6117ee565b6033546001600160a01b0316610515565b6104b460d45481565b61044061183e565b6104b461079736600461470c565b61184b565b6104b46107aa3660046146a2565b611856565b61047e6107bd3660046146e0565b611899565b6104b4611976565b61047e6107d83660046146e0565b611bcb565b6104b460d25481565b6104b460dd5481565b6104b4611c1c565b610469610805366004614729565b611cbf565b6097546001600160a01b0316610515565b6104b460d65481565b6104b4611d09565b61083f61083a36600461470c565b611d5b565b60408051948552602085019390935291830152606082015260800161044d565b6104b461086d3660046146a2565b611d9c565b61047e7f000000000000000000000000000000000000000000000000000000000000000081565b6104b460dc5481565b6104b46108b03660046146a2565b611ddf565b6104b46108c33660046149ea565b6001600160a01b03918216600090815260d86020908152604080832093909416825291909152205490565b6104b46108fc3660046146e0565b611e24565b6104b4611e72565b6065546001600160a01b0316610515565b60cc546105159061010090046001600160a01b031681565b6104696109403660046146a2565b611ea0565b61046961095336600461470c565b611fbf565b60ce54610515906001600160a01b031681565b6104b4610979366004614a23565b612030565b6104b461204a565b60ca805461099390614a65565b80601f01602080910402602001604051908101604052809291908181526020018280546109bf90614a65565b8015610a0c5780601f106109e157610100808354040283529160200191610a0c565b820191906000526020600020905b8154815290600101906020018083116109ef57829003601f168201915b505050505081565b60c95460ff16610a3f5760405162461bcd60e51b8152600401610a3690614a9f565b60405180910390fd5b60c9805460ff19169055610a51611976565b50610a5a611e72565b60dd5414610a6b57610a6b816120ad565b5060c9805460ff19166001179055565b6000610a8683612229565b33600081815260d8602090815260408083206001600160a01b038816808552908352928190208690555185815283917f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925910160405180910390a360019150505b92915050565b610af4612250565b610afd816122aa565b50565b60c95460009060ff16610b255760405162461bcd60e51b8152600401610a3690614a9f565b60c9805460ff19169055610b37611976565b50610b43333384612370565b506000905060c9805460ff19166001179055919050565b610b62612250565b610afd8161254b565b60c95460009060ff16610b905760405162461bcd60e51b8152600401610a3690614a9f565b60c9805460ff19169055610ba2611976565b50610bac826125ae565b905060c9805460ff19166001179055919050565b6000610bca61261e565b905090565b6033546001600160a01b03163314610c415760405162461bcd60e51b815260206004820152602f60248201527f56546f6b656e3a3a7377656570546f6b656e3a206f6e6c792061646d696e206360448201526e616e20737765657020746f6b656e7360881b6064820152608401610a36565b60c9546001600160a01b03610100909104811690821603610cbf5760405162461bcd60e51b815260206004820152603260248201527f56546f6b656e3a3a7377656570546f6b656e3a2063616e206e6f74207377656560448201527138103ab73232b9363cb4b733903a37b5b2b760711b6064820152608401610a36565b6040516370a0823160e01b81523060048201526000906001600160a01b038316906370a0823190602401602060405180830381865afa158015610d06573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610d2a9190614ac3565b9050610d52610d416033546001600160a01b031690565b6001600160a01b0384169083612693565b6040516001600160a01b038316907f35ce4c546a473796a8e70ec2d4af4f2031afe357afa7057b6ea7fa340730e1b290600090a25050565b60c95460ff16610dac5760405162461bcd60e51b8152600401610a3690614a9f565b60c9805460ff1916905560408051808201909152601981527f73657452657365727665466163746f722875696e7432353629000000000000006020820152610df3906126fb565b610dfb611976565b50610a6b81612799565b60c95460009060ff16610e2a5760405162461bcd60e51b8152600401610a3690614a9f565b60c9805460ff19169055610e3d83612829565b610e45611976565b50610e5383338460006128ba565b50600060c9805460ff1916600117905592915050565b60c95460009060ff16610e8e5760405162461bcd60e51b8152600401610a3690614a9f565b60c9805460ff19169055610ea183612229565b610ea9611976565b50610e53338484612bf3565b60c95460009060ff16610eda5760405162461bcd60e51b8152600401610a3690614a9f565b60c9805460ff19169055610ef033858585612df8565b50600160c9805460ff191660011790559392505050565b610f28604051806060016040528060248152602001614ddb602491396126fb565b60008111610f685760405162461bcd60e51b815260206004820152600d60248201526c125b9d985b1a5908125b9c1d5d609a1b6044820152606401610a36565b60dc5460408051918252602082018390527fc2ac513cdb57f91eb2bef4db918c285829524f549682b99717c6cb06cc011183910160405180910390a160dc55565b60c95460009060ff16610fce5760405162461bcd60e51b8152600401610a3690614a9f565b60c9805460ff19169055610fe0611976565b50610fec338484612370565b506000905060c9805460ff1916600117905592915050565b600061100f83612229565b33600081815260d8602090815260408083206001600160a01b038816845290915290205461103d8482614af2565b6001600160a01b03838116600081815260d860209081526040808320948b16808452948252918290208590559051848152939450919290917f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b92591015b60405180910390a3506001949350505050565b60008060405180602001604052806110c2611d09565b90526001600160a01b038416600090815260d760205260409020549091506110eb908290613022565b9392505050565b6000610bca61303a565b611104612250565b610afd81613070565b60c95460ff1661112f5760405162461bcd60e51b8152600401610a3690614a9f565b60c9805460ff1916905580156111a55760cd5460405163eade3eed60e01b81523060048201526001600160a01b0384811660248301529091169063eade3eed90604401600060405180830381600087803b15801561118c57600080fd5b505af11580156111a0573d6000803e3d6000fd5b505050505b60cd546001600160a01b031633146111d057604051632c40292560e01b815260040160405180910390fd5b60006111db836125ae565b60d3549091506000831561125b576111f386856130cb565b90506111ff8183614b05565b91506001600160a01b038086169087167f1a2a22cb034d26d1854bdc6666a5b91fe25efbbb5dcad3b0355478d6f5c362a18361123b8188614b05565b604080519283526020830191909152810186905260600160405180910390a35b60006112678285614b05565b9050801561132f5760d654600061127e8383614af2565b905061128a8386614b05565b60d682905560408051858152600060208201529081018290529095506001600160a01b0389169030907f1a2a22cb034d26d1854bdc6666a5b91fe25efbbb5dcad3b0355478d6f5c362a19060600160405180910390a360408051848152602081018490529081018290526001600160a01b038916907f90125ffdb441e57c4f6bf69789206424859f206bea5727f2d81ad2470826ef6a9060600160405180910390a250505b6001600160a01b03808716600081815260d9602052604080822091825560d25460019092019190915560d38690555190918916907f9fe0294717a8efbc6ace1c151b73a4c89982339b2228a27d1ca21394e348986f906113929089815260200190565b60405180910390a3505060c9805460ff191660011790555050505050565b6113b8612250565b6113c260006131d9565b565b60c95460009060ff166113e95760405162461bcd60e51b8152600401610a3690614a9f565b60c9805460ff191690556113fb611976565b505060d35460c9805460ff1916600117905590565b61144e6040518060400160405280601e81526020017f73657450726f746f636f6c5365697a6553686172652875696e743235362900008152506126fb565b60cd5460408051634ada90af60e01b815290516000926001600160a01b031691634ada90af9160048083019260209291908290030181865afa158015611498573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906114bc9190614ac3565b9050806114d1670de0b6b3a764000084614af2565b11156114f05760405163034dd2c160e11b815260040160405180910390fd5b60da80549083905560408051828152602081018590527ff5815f353a60e815cce7553e4f60c533a59d26b1b5504ea4b6db8d60da3e4da291015b60405180910390a1505050565b60c95460ff166115595760405162461bcd60e51b8152600401610a3690614a9f565b60c9805460ff1916905561156b611976565b50611575816131f2565b505060c9805460ff19166001179055565b60655433906001600160a01b031681146115f45760405162461bcd60e51b815260206004820152602960248201527f4f776e61626c6532537465703a2063616c6c6572206973206e6f7420746865206044820152683732bb9037bbb732b960b91b6064820152608401610a36565b610afd816131d9565b60c95460009060ff166116225760405162461bcd60e51b8152600401610a3690614a9f565b60c9805460ff19169055611634611976565b5061164233336000856128ba565b50600060c9805460ff19166001179055919050565b600061166283612829565b61166a611976565b50611676833384613287565b50600092915050565b600054610100900460ff161580801561169f5750600054600160ff909116105b806116b95750303b1580156116b9575060005460ff166001145b61171c5760405162461bcd60e51b815260206004820152602e60248201527f496e697469616c697a61626c653a20636f6e747261637420697320616c72656160448201526d191e481a5b9a5d1a585b1a5e995960921b6064820152608401610a36565b6000805460ff19166001179055801561173f576000805461ff0019166101001790555b61174885612229565b61175b8c8c8c8c8c8c8c8c8c8c8c613465565b80156117a1576000805461ff0019169055604051600181527f7f26b83ff96e1f2b6a682f133852f6798a09c465da95921460cefb38474024989060200160405180910390a15b505050505050505050505050565b60cd546001600160a01b031633146117da57604051635c85a5e760e01b815260040160405180910390fd5b6117e78585858585613692565b5050505050565b61182c6040518060400160405280601d81526020017f736574496e746572657374526174654d6f64656c2861646472657373290000008152506126fb565b611834611976565b50610afd81613773565b60cb805461099390614a65565b6000610ae6826125ae565b60c95460009060ff1661187b5760405162461bcd60e51b8152600401610a3690614a9f565b60c9805460ff1916905561188d611976565b50611642333384612bf3565b60006118a483612229565b33600081815260d8602090815260408083206001600160a01b0388168452909152902054838110156119185760405162461bcd60e51b815260206004820152601e60248201527f64656372656173656420616c6c6f77616e63652062656c6f77207a65726f00006044820152606401610a36565b6001600160a01b03828116600081815260d860209081526040808320948a1680845294825291829020948890039485905590518481527f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b9259101611099565b600080611981611e72565b60d1549091508181036119975760009250505090565b60006119a161303a565b60d35460d45460d25460ce5460d6546040516301cee29d60e21b815260048101879052602481018690526044810185905260648101919091529495509293919290916000916001600160a01b03169063073b8a7490608401602060405180830381865afa158015611a16573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611a3a9190614ac3565b90507f0000000000000000000000000000000000000000000000000000000000000000811115611aac5760405162461bcd60e51b815260206004820152601c60248201527f626f72726f772072617465206973206162737572646c792068696768000000006044820152606401610a36565b6000611ab88789614b05565b90506000611ad4604051806020016040528085815250836138b5565b90506000611ae28288613022565b90506000611af08883614af2565b90506000611b0f604051806020016040528060d054815250848a6138e6565b90506000611b1e85898a6138e6565b60d18e905560d281905560d384905560d483905560dc5460dd5491925090611b46908f614b05565b10611b6f5760dd8d9055818b1015611b6657611b618b6120ad565b611b6f565b611b6f826120ad565b604080518c815260208101869052908101829052606081018490527f4dec04e750ca11537cabcd8a9eab06494de08da3735bc8871cd41250e190bc049060800160405180910390a160009d505050505050505050505050505090565b60c95460009060ff16611bf05760405162461bcd60e51b8152600401610a3690614a9f565b60c9805460ff19169055611c0633808585612df8565b50600160c9805460ff1916600117905592915050565b60ce546000906001600160a01b0316630cde8d1c611c3861303a565b60d35460d45460d05460d6546040516001600160e01b031960e088901b1681526004810195909552602485019390935260448401919091526064830152608482015260a4015b602060405180830381865afa158015611c9b573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610bca9190614ac3565b60c95460ff16611ce15760405162461bcd60e51b8152600401610a3690614a9f565b60c9805460ff19169055611cf733848484613907565b505060c9805460ff1916600117905550565b60c95460009060ff16611d2e5760405162461bcd60e51b8152600401610a3690614a9f565b60c9805460ff19169055611d40611976565b50611d4961261e565b905060c9805460ff1916600117905590565b6001600160a01b038116600090815260d760205260408120548190819081908190611d85876125ae565b611d8d61261e565b93509350935093509193509193565b60c95460009060ff16611dc15760405162461bcd60e51b8152600401610a3690614a9f565b60c9805460ff19169055611dd3611976565b50611642333384613287565b60c95460009060ff16611e045760405162461bcd60e51b8152600401610a3690614a9f565b60c9805460ff19169055611e16611976565b5061164233338460006128ba565b60c95460009060ff16611e495760405162461bcd60e51b8152600401610a3690614a9f565b60c9805460ff19169055611e5c83612829565b611e64611976565b50610e5383336000856128ba565b6000610bca7f000000000000000000000000000000000000000000000000000000000000000063ffffffff16565b60db546001600160a01b03163314611f0e5760405162461bcd60e51b815260206004820152602b60248201527f6f6e6c792073686f727466616c6c20636f6e74726163742063616e207570646160448201526a1d1948189859081919589d60aa1b6064820152608401610a36565b60d654811115611f725760405162461bcd60e51b815260206004820152602960248201527f6d6f7265207468616e206261642064656274207265636f76657265642066726f604482015268369030bab1ba34b7b760b91b6064820152608401610a36565b60d6546000611f818383614b05565b60d681905560408051848152602081018390529192507f9e19ec7d2b8f8a94df8cc0072453ace318d221e3cbb2731d0eaa0baac856520f910161152a565b611fc7612250565b606580546001600160a01b0383166001600160a01b03199091168117909155611ff86033546001600160a01b031690565b6001600160a01b03167f38d16b8cac22d99fc7c124b9cd0de2d3fa1faef420bfe791d8c362d765e2270060405160405180910390a350565b6000612040338585856000613692565b5060009392505050565b60ce546000906001600160a01b031663073b8a7461206661303a565b60d35460d45460d6546040516001600160e01b031960e087901b1681526004810194909452602484019290925260448301526064820152608401611c7e565b4390565b4290565b806000036120b85750565b60006120c2611e72565b60d154146120e357604051630dff50cb60e41b815260040160405180910390fd5b816120ec61303a565b101561210b57604051633345e99960e01b815260040160405180910390fd5b60d45482111561212e576040516378d2980560e11b815260040160405180910390fd5b8160d45461213c9190614b05565b60d481905560cc5490915061215f9061010090046001600160a01b031683613cb1565b60cc5460cd5460c9546040516305bebb3b60e21b81526001600160a01b03610100948590048116946316faecec946121a7949083169391900490911690600090600401614b18565b600060405180830381600087803b1580156121c157600080fd5b505af11580156121d5573d6000803e3d6000fd5b505060cc5460408051868152602081018690526101009092046001600160a01b031693507f9cc63bb4ef37ad6a5f5f657dfaf94865531d4234acbc431cc8ac035468f6272092500160405180910390a25050565b6001600160a01b038116610afd576040516342bcdf7f60e11b815260040160405180910390fd5b6033546001600160a01b031633146113c25760405162461bcd60e51b815260206004820181905260248201527f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e65726044820152606401610a36565b6001600160a01b03811661230e5760405162461bcd60e51b815260206004820152602560248201527f696e76616c696420616365737320636f6e74726f6c206d616e61676572206164604482015264647265737360d81b6064820152608401610a36565b609780546001600160a01b038381166001600160a01b031983168117909355604080519190921680825260208201939093527f66fd58e82f7b31a2a5c30e0888f3093efe4e111b00cd2b0c31fe014601293aa091015b60405180910390a15050565b60cd5460405163eade3eed60e01b81523060048201526001600160a01b038481166024830152600092169063eade3eed90604401600060405180830381600087803b1580156123be57600080fd5b505af11580156123d2573d6000803e3d6000fd5b505050506123de611e72565b60d154146123ff5760405163c9021e2f60e01b815260040160405180910390fd5b600061240a846125ae565b905060008184101561241c578361241e565b815b9050600061242c87836130cb565b9050600061243a8285614b05565b905060008260d35461244c9190614b05565b6001600160a01b03898116600081815260d9602090815260409182902087815560d25460019091015560d3859055815188815290810187905290810184905292935091908b16907f1a2a22cb034d26d1854bdc6666a5b91fe25efbbb5dcad3b0355478d6f5c362a19060600160405180910390a360cd5460d254604051631ededc9160e01b81523060048201526001600160a01b038c811660248301528b81166044830152606482018790526084820192909252911690631ededc919060a401600060405180830381600087803b15801561252657600080fd5b505af115801561253a573d6000803e3d6000fd5b50949b9a5050505050505050505050565b61255481612229565b60cc80546001600160a01b03838116610100818102610100600160a81b031985161790945560405193909204169182907fafec95c8612496c3ecf5dddc71e393528fe29bd145fbaf9c6b496d78d7e2d79b90600090a35050565b6001600160a01b038116600090815260d96020908152604080832081518083019092528054808352600190910154928201929092529082036125f35750600092915050565b60d254815160009161260491614b5c565b90508160200151816126169190614b73565b949350505050565b60d55460009080820361263357505060cf5490565b600061263d61303a565b9050600060d45460d65460d354846126559190614af2565b61265f9190614af2565b6126699190614b05565b9050600083612680670de0b6b3a764000084614b5c565b61268a9190614b73565b95945050505050565b6040516001600160a01b0383166024820152604481018290526126f690849063a9059cbb60e01b906064015b60408051601f198184030181529190526020810180516001600160e01b03166001600160e01b031990931692909217909152613ccd565b505050565b6097546040516318c5e8ab60e01b81526000916001600160a01b0316906318c5e8ab9061272e9033908690600401614b95565b602060405180830381865afa15801561274b573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061276f9190614bb9565b90508061279557333083604051634a3fa29360e01b8152600401610a3693929190614bd6565b5050565b6127a1611e72565b60d154146127c257604051637dfca6b760e11b815260040160405180910390fd5b670de0b6b3a76400008111156127eb5760405163717220f360e11b815260040160405180910390fd5b60d080549082905560408051828152602081018490527faaa68312e2ea9d50e16af5068410ab56e1a1fd06037b1a35664812c30f8214609101612364565b60cd54604051630217306760e31b81526001600160a01b038381166004830152336024830152909116906310b9833890604401602060405180830381865afa158015612879573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061289d9190614bb9565b610afd57604051630cf0b6f560e01b815260040160405180910390fd5b8115806128c5575080155b61292e5760405162461bcd60e51b815260206004820152603460248201527f6f6e65206f662072656465656d546f6b656e73496e206f722072656465656d416044820152736d6f756e74496e206d757374206265207a65726f60601b6064820152608401610a36565b612936611e72565b60d15414612957576040516397b5cfcd60e01b815260040160405180910390fd5b6000604051806020016040528061296c61261e565b905290506000808415612981578491506129bf565b61298b8484613da2565b915060006129998385613dc0565b905080158015906129aa5750848114155b156129bd57826129b981614c02565b9350505b505b6129c98383613022565b905080600003612a125760405162461bcd60e51b815260206004820152601460248201527372656465656d416d6f756e74206973207a65726f60601b6044820152606401610a36565b60cd54604051634732387560e11b81526001600160a01b0390911690638e6470ea90612a469030908b908790600401614c1b565b600060405180830381600087803b158015612a6057600080fd5b505af1158015612a74573d6000803e3d6000fd5b505050508060d454612a8461303a565b612a8e9190614b05565b1015612aad576040516391240a1b60e01b815260040160405180910390fd5b8160d554612abb9190614b05565b60d5556001600160a01b038716600090815260d76020526040812054612ae2908490614b05565b6001600160a01b038916600090815260d7602052604090208190559050612b098783613cb1565b60405183815230906001600160a01b038a1690600080516020614dff8339815191529060200160405180910390a360408051838152602081018590529081018290526001600160a01b038916907fbd5034ffbd47e4e72a94baa2cdb74c6fad73cb3bcdc13036b72ec8306f5a76469060600160405180910390a260cd546040516351dff98960e01b81523060048201526001600160a01b038a811660248301526044820185905260648201869052909116906351dff989906084015b600060405180830381600087803b158015612bdf57600080fd5b505af11580156117a1573d6000803e3d6000fd5b60cd5460405163c0891ba960e01b81526001600160a01b039091169063c0891ba990612c2790309086908690600401614c1b565b600060405180830381600087803b158015612c4157600080fd5b505af1158015612c55573d6000803e3d6000fd5b50505050612c61611e72565b60d15414612c82576040516338d8859760e01b815260040160405180910390fd5b60006040518060200160405280612c9761261e565b905290506000612ca785846130cb565b90506000612cb58284613da2565b90508060d554612cc59190614af2565b60d5556001600160a01b038516600090815260d76020526040812054612cec908390614af2565b6001600160a01b038716600081815260d760209081526040918290208490558151878152908101869052908101839052919250907fb4c03061fb5b7fed76389d5af8f2e0ddb09f8c70d1333abbb62582835e10accb9060600160405180910390a26040518281526001600160a01b03871690600090600080516020614dff8339815191529060200160405180910390a360cd546040516341c728b960e01b81523060048201526001600160a01b0388811660248301526044820186905260648201859052909116906341c728b990608401600060405180830381600087803b158015612dd757600080fd5b505af1158015612deb573d6000803e3d6000fd5b5050505050505050505050565b60cd54604051636d0be88d60e01b81523060048201526001600160a01b03858116602483015284811660448301526064820184905290911690636d0be88d90608401600060405180830381600087803b158015612e5457600080fd5b505af1158015612e68573d6000803e3d6000fd5b50505050816001600160a01b0316836001600160a01b031603612e9e57604051638cd22d1960e01b815260040160405180910390fd5b6000836001600160a01b0316856001600160a01b031603612ec25750600019612eea565b506001600160a01b03808416600090815260d860209081526040808320938816835292905220545b6000612ef68383614b05565b6001600160a01b038616600090815260d7602052604081205491925090612f1e908590614b05565b6001600160a01b038616600090815260d7602052604081205491925090612f46908690614af2565b6001600160a01b03808916600090815260d7602052604080822086905591891681522081905590506000198414612fa0576001600160a01b03808816600090815260d860209081526040808320938c168352929052208390555b856001600160a01b0316876001600160a01b0316600080516020614dff83398151915287604051612fd391815260200190565b60405180910390a360cd5460405163352b4a3f60e11b81523060048201526001600160a01b03898116602483015288811660448301526064820188905290911690636a56947e90608401612bc5565b60008061302f84846138b5565b905061261681613de3565b60c9546040516370a0823160e01b815230600482015260009161010090046001600160a01b0316906370a0823190602401611c7e565b61307981612229565b60db80546001600160a01b038381166001600160a01b0319831681179093556040519116919082907f6dbf1ff28f860de5edafa4c6505e37c0aba213288cc4166c5352b6d3776c79ef90600090a35050565b60c9546040516370a0823160e01b815230600482015260009161010090046001600160a01b031690829082906370a0823190602401602060405180830381865afa15801561311d573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906131419190614ac3565b90506131586001600160a01b038316863087613dfb565b6040516370a0823160e01b81523060048201526000906001600160a01b038416906370a0823190602401602060405180830381865afa15801561319f573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906131c39190614ac3565b90506131cf8282614b05565b9695505050505050565b606580546001600160a01b0319169055610afd81613e22565b60008060006131ff611e72565b60d15414613223576040516338acf79960e01b815260048101829052602401610a36565b61322d33856130cb565b90508060d45461323d9190614af2565b60d4819055604080518381526020810183905291935033917fa91e67c5ea634cd43a12c5a482724b03de01e85ca68702a53d0c2f45cb7c1dc5910160405180910390a29392505050565b60cd5460405163df71403b60e01b81526001600160a01b039091169063df71403b906132bb90309087908690600401614c1b565b600060405180830381600087803b1580156132d557600080fd5b505af11580156132e9573d6000803e3d6000fd5b505050506132f5611e72565b60d1541461331657604051630e8d8c6160e21b815260040160405180910390fd5b8060d45461332261303a565b61332c9190614b05565b101561334b576040516348c2588160e01b815260040160405180910390fd5b6000613356846125ae565b905060006133648383614af2565b905060008360d3546133769190614af2565b6001600160a01b038716600090815260d96020526040902083815560d25460019091015560d381905590506133ab8585613cb1565b60408051858152602081018490529081018290526001600160a01b038716907f13ed6866d4e1ee6da46f845c46d7e54120883d75c5ea9a2dacc1c4ca8984ab809060600160405180910390a260cd54604051635c77860560e01b81526001600160a01b0390911690635c7786059061342b9030908a908990600401614c1b565b600060405180830381600087803b15801561344557600080fd5b505af1158015613459573d6000803e3d6000fd5b50505050505050505050565b600054610100900460ff1661348c5760405162461bcd60e51b8152600401610a3690614c3f565b613494613e74565b61349d83613ea3565b60d1541580156134ad575060d254155b6135055760405162461bcd60e51b815260206004820152602360248201527f6d61726b6574206d6179206f6e6c7920626520696e697469616c697a6564206f6044820152626e636560e81b6064820152608401610a36565b60cf889055876135705760405162461bcd60e51b815260206004820152603060248201527f696e697469616c2065786368616e67652072617465206d75737420626520677260448201526f32b0ba32b9103a3430b7103d32b9379760811b6064820152608401610a36565b6135798a613eca565b613581611e72565b60d155670de0b6b3a764000060d25561359989613773565b6135a281612799565b60ca6135ae8882614cda565b5060cb6135bb8782614cda565b5060cc805460ff191660ff871617905581516135d690613070565b6135e3826020015161254b565b66b1a2bc2ec5000060da5560c98054610100600160a81b0319166101006001600160a01b038e811682029290921792839055604080516318160ddd60e01b8152905191909304909116916318160ddd9160048083019260209291908290030181865afa158015613657573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061367b9190614ac3565b5060c9805460ff19166001179055612deb846131d9565b60c95460ff166136b45760405162461bcd60e51b8152600401610a3690614a9f565b60c9805460ff191690556136c6611976565b506000826001600160a01b031663a6afed956040518163ffffffff1660e01b81526004016020604051808303816000875af1158015613709573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061372d9190614ac3565b9050801561375157604051633eea49b760e11b815260048101829052602401610a36565b61375e8686868686613fd5565b505060c9805460ff1916600117905550505050565b600061377d611e72565b60d1541461379e57604051630be2a5cb60e11b815260040160405180910390fd5b60ce60009054906101000a90046001600160a01b03169050816001600160a01b0316632191f92a6040518163ffffffff1660e01b8152600401602060405180830381865afa1580156137f4573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906138189190614bb9565b6138645760405162461bcd60e51b815260206004820152601c60248201527f6d61726b6572206d6574686f642072657475726e65642066616c7365000000006044820152606401610a36565b60ce80546001600160a01b0319166001600160a01b0384811691821790925560405190918316907fedffc32e068c7c95dfd4bdfd5c4d939a084d6b11c4199eac8436ed234d72f92690600090a35050565b60408051602081019091526000815260405180602001604052806138dd856000015185614463565b90529392505050565b6000806138f385856138b5565b905061268a61390182613de3565b8461446f565b60cd5460405163037883e560e31b81523060048201526001600160a01b0386811660248301528581166044830152848116606483015290911690631bc41f2890608401600060405180830381600087803b15801561396457600080fd5b505af1158015613978573d6000803e3d6000fd5b50505050826001600160a01b0316826001600160a01b0316036139ae57604051633a94626760e11b815260040160405180910390fd5b60cd5460408051634ada90af60e01b815290516000926001600160a01b031691634ada90af9160048083019260209291908290030181865afa1580156139f8573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190613a1c9190614ac3565b90506000613a3a83604051806020016040528060da54815250613dc0565b90506000613a5682604051806020016040528086815250613da2565b90506000613a648286614b05565b905060006040518060200160405280613a7b61261e565b905290506000613a8b8285613022565b90508360d554613a9b9190614b05565b60d5556001600160a01b038816600090815260d76020526040902054613ac2908890614b05565b6001600160a01b03808a16600090815260d7602052604080822093909355908b1681522054613af2908490614af2565b6001600160a01b03808b16600090815260d7602052604090209190915560cc54613b23916101009091041682613cb1565b60cc5460cd5460c9546040516305bebb3b60e21b81526001600160a01b03610100948590048116946316faecec94613b6b949083169391900490911690600190600401614b18565b600060405180830381600087803b158015613b8557600080fd5b505af1158015613b99573d6000803e3d6000fd5b50505050886001600160a01b0316886001600160a01b0316600080516020614dff83398151915285604051613bd091815260200190565b60405180910390a360cc546040516001600160a01b036101009092048216918a16907f3ac0548d62d3fa3c9a817cd33899b9acacd57e8958ebe51bc7d9a79f26a8a5db90613c219085815260200190565b60405180910390a360cd54604051636d35bf9160e01b81523060048201526001600160a01b038c811660248301528b811660448301528a81166064830152608482018a905290911690636d35bf919060a401600060405180830381600087803b158015613c8d57600080fd5b505af1158015613ca1573d6000803e3d6000fd5b5050505050505050505050505050565b60c95461010090046001600160a01b03166126f6818484612693565b6000613d22826040518060400160405280602081526020017f5361666545524332303a206c6f772d6c6576656c2063616c6c206661696c6564815250856001600160a01b031661447b9092919063ffffffff16565b9050805160001480613d43575080806020019051810190613d439190614bb9565b6126f65760405162461bcd60e51b815260206004820152602a60248201527f5361666545524332303a204552433230206f7065726174696f6e20646964206e6044820152691bdd081cdd58d8d9595960b21b6064820152608401610a36565b60006110eb613db984670de0b6b3a7640000614463565b835161448a565b6000670de0b6b3a7640000613dd9848460000151614463565b6110eb9190614b73565b8051600090610ae690670de0b6b3a764000090614b73565b613e1c846323b872dd60e01b8585856040516024016126bf93929190614c1b565b50505050565b603380546001600160a01b038381166001600160a01b0319831681179093556040519116919082907f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e090600090a35050565b600054610100900460ff16613e9b5760405162461bcd60e51b8152600401610a3690614c3f565b6113c2614496565b600054610100900460ff16610af45760405162461bcd60e51b8152600401610a3690614c3f565b60cd5460408051623f1ee960e11b815290516001600160a01b0392831692841691627e3dd29160048083019260209291908290030181865afa158015613f14573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190613f389190614bb9565b613f845760405162461bcd60e51b815260206004820152601c60248201527f6d61726b6572206d6574686f642072657475726e65642066616c7365000000006044820152606401610a36565b60cd80546001600160a01b0319166001600160a01b0384811691821790925560405190918316907f7ac369dbd14fa5ea3f473ed67cc9d598964a77501540ba6751eb0b3decf5870d90600090a35050565b60cd5460405163e89d51ad60e01b81523060048201526001600160a01b03848116602483015286811660448301526064820186905283151560848301529091169063e89d51ad9060a401600060405180830381600087803b15801561403957600080fd5b505af115801561404d573d6000803e3d6000fd5b50505050614059611e72565b60d1541461407a576040516380965b1b60e01b815260040160405180910390fd5b614082611e72565b826001600160a01b0316636c540baf6040518163ffffffff1660e01b8152600401602060405180830381865afa1580156140c0573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906140e49190614ac3565b1461410257604051631046f38d60e31b815260040160405180910390fd5b846001600160a01b0316846001600160a01b03160361413457604051631bd1a62160e21b815260040160405180910390fd5b826000036141555760405163d29da7ef60e01b815260040160405180910390fd5b600019830361417757604051635982c5bb60e11b815260040160405180910390fd5b6000614184868686612370565b60cd5460405163c488847b60e01b815291925060009182916001600160a01b03169063c488847b906141be90309089908890600401614c1b565b6040805180830381865afa1580156141da573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906141fe9190614d9a565b915091506000821461426e5760405162461bcd60e51b815260206004820152603360248201527f4c49515549444154455f434f4d5054524f4c4c45525f43414c43554c4154455f604482015272105353d5539517d4d152569157d19052531151606a1b6064820152608401610a36565b6040516370a0823160e01b81526001600160a01b0388811660048301528291908716906370a0823190602401602060405180830381865afa1580156142b7573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906142db9190614ac3565b10156143295760405162461bcd60e51b815260206004820152601860248201527f4c49515549444154455f5345495a455f544f4f5f4d55434800000000000000006044820152606401610a36565b306001600160a01b0386160361434a5761434530898984613907565b6143ad565b60405163b2a02ff160e01b81526001600160a01b0386169063b2a02ff19061437a908b908b908690600401614c1b565b600060405180830381600087803b15801561439457600080fd5b505af11580156143a8573d6000803e3d6000fd5b505050505b846001600160a01b0316876001600160a01b0316896001600160a01b03167f298637f684da70674f26509b10f07ec2fbc77a335ab1e7d6215a4b2484d8bb528685604051614405929190918252602082015260400190565b60405180910390a460cd546040516347ef3b3b60e01b81523060048201526001600160a01b0387811660248301528a8116604483015289811660648301526084820186905260a48201849052909116906347ef3b3b9060c401612bc5565b60006110eb8284614b5c565b60006110eb8284614af2565b606061261684846000856144c6565b60006110eb8284614b73565b600054610100900460ff166144bd5760405162461bcd60e51b8152600401610a3690614c3f565b6113c2336131d9565b6060824710156145275760405162461bcd60e51b815260206004820152602660248201527f416464726573733a20696e73756666696369656e742062616c616e636520666f6044820152651c8818d85b1b60d21b6064820152608401610a36565b600080866001600160a01b031685876040516145439190614dbe565b60006040518083038185875af1925050503d8060008114614580576040519150601f19603f3d011682016040523d82523d6000602084013e614585565b606091505b5091509150614596878383876145a1565b979650505050505050565b60608315614610578251600003614609576001600160a01b0385163b6146095760405162461bcd60e51b815260206004820152601d60248201527f416464726573733a2063616c6c20746f206e6f6e2d636f6e74726163740000006044820152606401610a36565b5081612616565b61261683838151156146255781518083602001fd5b8060405162461bcd60e51b8152600401610a36919061468f565b60005b8381101561465a578181015183820152602001614642565b50506000910152565b6000815180845261467b81602086016020860161463f565b601f01601f19169290920160200192915050565b6020815260006110eb6020830184614663565b6000602082840312156146b457600080fd5b5035919050565b6001600160a01b0381168114610afd57600080fd5b80356146db816146bb565b919050565b600080604083850312156146f357600080fd5b82356146fe816146bb565b946020939093013593505050565b60006020828403121561471e57600080fd5b81356110eb816146bb565b60008060006060848603121561473e57600080fd5b8335614749816146bb565b92506020840135614759816146bb565b929592945050506040919091013590565b634e487b7160e01b600052604160045260246000fd5b600082601f83011261479157600080fd5b813567ffffffffffffffff808211156147ac576147ac61476a565b604051601f8301601f19908116603f011681019082821181831017156147d4576147d461476a565b816040528381528660208588010111156147ed57600080fd5b836020870160208301376000602085830101528094505050505092915050565b803560ff811681146146db57600080fd5b60006040828403121561483057600080fd5b6040516040810181811067ffffffffffffffff821117156148535761485361476a565b6040529050808235614864816146bb565b81526020830135614874816146bb565b6020919091015292915050565b60008060008060008060008060008060006101808c8e0312156148a357600080fd5b6148ac8c6146d0565b9a506148ba60208d016146d0565b99506148c860408d016146d0565b985060608c0135975067ffffffffffffffff8060808e013511156148eb57600080fd5b6148fb8e60808f01358f01614780565b97508060a08e0135111561490e57600080fd5b5061491f8d60a08e01358e01614780565b955061492d60c08d0161480d565b945061493b60e08d016146d0565b935061494a6101008d016146d0565b925061495a8d6101208e0161481e565b91506101608c013590509295989b509295989b9093969950565b8015158114610afd57600080fd5b600080600080600060a0868803121561499a57600080fd5b85356149a5816146bb565b945060208601356149b5816146bb565b93506040860135925060608601356149cc816146bb565b915060808601356149dc81614974565b809150509295509295909350565b600080604083850312156149fd57600080fd5b8235614a08816146bb565b91506020830135614a18816146bb565b809150509250929050565b600080600060608486031215614a3857600080fd5b8335614a43816146bb565b9250602084013591506040840135614a5a816146bb565b809150509250925092565b600181811c90821680614a7957607f821691505b602082108103614a9957634e487b7160e01b600052602260045260246000fd5b50919050565b6020808252600a90820152691c994b595b9d195c995960b21b604082015260600190565b600060208284031215614ad557600080fd5b5051919050565b634e487b7160e01b600052601160045260246000fd5b80820180821115610ae657610ae6614adc565b81810381811115610ae657610ae6614adc565b6001600160a01b038481168252831660208201526060810160028310614b4e57634e487b7160e01b600052602160045260246000fd5b826040830152949350505050565b8082028115828204841417610ae657610ae6614adc565b600082614b9057634e487b7160e01b600052601260045260246000fd5b500490565b6001600160a01b038316815260406020820181905260009061261690830184614663565b600060208284031215614bcb57600080fd5b81516110eb81614974565b6001600160a01b0384811682528316602082015260606040820181905260009061268a90830184614663565b600060018201614c1457614c14614adc565b5060010190565b6001600160a01b039384168152919092166020820152604081019190915260600190565b6020808252602b908201527f496e697469616c697a61626c653a20636f6e7472616374206973206e6f74206960408201526a6e697469616c697a696e6760a81b606082015260800190565b601f8211156126f6576000816000526020600020601f850160051c81016020861015614cb35750805b601f850160051c820191505b81811015614cd257828155600101614cbf565b505050505050565b815167ffffffffffffffff811115614cf457614cf461476a565b614d0881614d028454614a65565b84614c8a565b602080601f831160018114614d3d5760008415614d255750858301515b600019600386901b1c1916600185901b178555614cd2565b600085815260208120601f198616915b82811015614d6c57888601518255948401946001909101908401614d4d565b5085821015614d8a5787850151600019600388901b60f8161c191681555b5050505050600190811b01905550565b60008060408385031215614dad57600080fd5b505080516020909101519092909150565b60008251614dd081846020870161463f565b919091019291505056fe7365745265647563655265736572766573426c6f636b44656c74612875696e7432353629ddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3efa2646970667358221220b16fa916660c59bb0e52059aec9877b725d7f8debd2a22c6a43f4028203b51de64736f6c6343000819003300000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001840f0fbb00
Deployed Bytecode
0x608060405234801561001057600080fd5b50600436106104335760003560e01c80637821a51411610236578063b2a02ff11161013b578063dd62ed3e116100c3578063ef60450c11610087578063ef60450c14610932578063f2fde38b14610945578063f3fdb15a14610958578063f5e3c4621461096b578063f8f9da281461097e57600080fd5b8063dd62ed3e146108b5578063df3a516e146108ee578063e1d146fb14610901578063e30c397814610909578063e9a44fd91461091a57600080fd5b8063c37f68e21161010a578063c37f68e21461082c578063c5ebeaec1461085f578063c7ad089514610872578063d1109c2f14610899578063db006a75146108a257600080fd5b8063b2a02ff1146107f7578063b4a0bdf31461080a578063bbcac5571461081b578063bd6d894d1461082457600080fd5b806395d89b41116101be578063a6afed951161018d578063a6afed95146107c2578063a9059cbb146107ca578063aa5af0fd146107dd578063ae96f141146107e6578063ae9d70b0146107ef57600080fd5b806395d89b411461078157806395dd919314610789578063a0712d681461079c578063a457c2d7146107af57600080fd5b80638a42c319116102055780638a42c3191461072e5780638bbdb6db146107415780638bcd4016146107545780638da5cb5b146107675780638f840ddd1461077857600080fd5b80637821a514146106ed57806379ba509714610700578063852a12e314610708578063856e5bb31461071b57600080fd5b8063313ce5671161033c5780636752e702116102c45780636f307dc3116102935780636f307dc31461068957806370a08231146106a1578063715018a6146106ca57806373acee98146106d2578063757212f0146106da57600080fd5b80636752e702146106485780636857249c1461065157806369ab3250146106785780636c540baf1461068057600080fd5b80633d9ea3a11161030b5780633d9ea3a1146105ff57806341f641ee1461060657806344fe6ffe1461061957806347bd37181461062c5780635fe3b5671461063557600080fd5b8063313ce567146105b257806339509351146105d15780633af9e669146105e45780633b1d21a2146105f757600080fd5b8063182df0f5116103bf578063210bc0521161038e578063210bc0521461055357806323323e031461056657806323b872dd146105795780632464176b1461058c5780632608f8181461059f57600080fd5b8063182df0f5146104fa57806319b1faef146105025780631be195601461052d5780631c4469831461054057600080fd5b80630e752702116104065780630e752702146104a1578063107568df146104c2578063173b9904146104d557806317bfdfbc146104de57806318160ddd146104f157600080fd5b806306fdde031461043857806307e2795914610456578063095ea7b31461046b5780630e32cb861461048e575b600080fd5b610440610986565b60405161044d919061468f565b60405180910390f35b6104696104643660046146a2565b610a14565b005b61047e6104793660046146e0565b610a7b565b604051901515815260200161044d565b61046961049c36600461470c565b610aec565b6104b46104af3660046146a2565b610b00565b60405190815260200161044d565b6104696104d036600461470c565b610b5a565b6104b460d05481565b6104b46104ec36600461470c565b610b6b565b6104b460d55481565b6104b4610bc0565b60db54610515906001600160a01b031681565b6040516001600160a01b03909116815260200161044d565b61046961053b36600461470c565b610bcf565b61046961054e3660046146a2565b610d8a565b6104b46105613660046146e0565b610e05565b6104b46105743660046146e0565b610e69565b61047e610587366004614729565b610eb5565b61046961059a3660046146a2565b610f07565b6104b46105ad3660046146e0565b610fa9565b60cc546105bf9060ff1681565b60405160ff909116815260200161044d565b61047e6105df3660046146e0565b611004565b6104b46105f236600461470c565b6110ac565b6104b46110f2565b600161047e565b61046961061436600461470c565b6110fc565b610469610627366004614729565b61110d565b6104b460d35481565b60cd54610515906001600160a01b031681565b6104b460da5481565b6104b47f0000000000000000000000000000000000000000000000000000000001e1338081565b6104b4600081565b6104b460d15481565b60c9546105159061010090046001600160a01b031681565b6104b46106af36600461470c565b6001600160a01b0316600090815260d7602052604090205490565b6104696113b0565b6104b46113c4565b6104696106e83660046146a2565b611410565b6104696106fb3660046146a2565b611537565b610469611586565b6104b46107163660046146a2565b6115fd565b6104b46107293660046146e0565b611657565b61046961073c366004614881565b61167f565b61046961074f366004614982565b6117af565b61046961076236600461470c565b6117ee565b6033546001600160a01b0316610515565b6104b460d45481565b61044061183e565b6104b461079736600461470c565b61184b565b6104b46107aa3660046146a2565b611856565b61047e6107bd3660046146e0565b611899565b6104b4611976565b61047e6107d83660046146e0565b611bcb565b6104b460d25481565b6104b460dd5481565b6104b4611c1c565b610469610805366004614729565b611cbf565b6097546001600160a01b0316610515565b6104b460d65481565b6104b4611d09565b61083f61083a36600461470c565b611d5b565b60408051948552602085019390935291830152606082015260800161044d565b6104b461086d3660046146a2565b611d9c565b61047e7f000000000000000000000000000000000000000000000000000000000000000181565b6104b460dc5481565b6104b46108b03660046146a2565b611ddf565b6104b46108c33660046149ea565b6001600160a01b03918216600090815260d86020908152604080832093909416825291909152205490565b6104b46108fc3660046146e0565b611e24565b6104b4611e72565b6065546001600160a01b0316610515565b60cc546105159061010090046001600160a01b031681565b6104696109403660046146a2565b611ea0565b61046961095336600461470c565b611fbf565b60ce54610515906001600160a01b031681565b6104b4610979366004614a23565b612030565b6104b461204a565b60ca805461099390614a65565b80601f01602080910402602001604051908101604052809291908181526020018280546109bf90614a65565b8015610a0c5780601f106109e157610100808354040283529160200191610a0c565b820191906000526020600020905b8154815290600101906020018083116109ef57829003601f168201915b505050505081565b60c95460ff16610a3f5760405162461bcd60e51b8152600401610a3690614a9f565b60405180910390fd5b60c9805460ff19169055610a51611976565b50610a5a611e72565b60dd5414610a6b57610a6b816120ad565b5060c9805460ff19166001179055565b6000610a8683612229565b33600081815260d8602090815260408083206001600160a01b038816808552908352928190208690555185815283917f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925910160405180910390a360019150505b92915050565b610af4612250565b610afd816122aa565b50565b60c95460009060ff16610b255760405162461bcd60e51b8152600401610a3690614a9f565b60c9805460ff19169055610b37611976565b50610b43333384612370565b506000905060c9805460ff19166001179055919050565b610b62612250565b610afd8161254b565b60c95460009060ff16610b905760405162461bcd60e51b8152600401610a3690614a9f565b60c9805460ff19169055610ba2611976565b50610bac826125ae565b905060c9805460ff19166001179055919050565b6000610bca61261e565b905090565b6033546001600160a01b03163314610c415760405162461bcd60e51b815260206004820152602f60248201527f56546f6b656e3a3a7377656570546f6b656e3a206f6e6c792061646d696e206360448201526e616e20737765657020746f6b656e7360881b6064820152608401610a36565b60c9546001600160a01b03610100909104811690821603610cbf5760405162461bcd60e51b815260206004820152603260248201527f56546f6b656e3a3a7377656570546f6b656e3a2063616e206e6f74207377656560448201527138103ab73232b9363cb4b733903a37b5b2b760711b6064820152608401610a36565b6040516370a0823160e01b81523060048201526000906001600160a01b038316906370a0823190602401602060405180830381865afa158015610d06573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610d2a9190614ac3565b9050610d52610d416033546001600160a01b031690565b6001600160a01b0384169083612693565b6040516001600160a01b038316907f35ce4c546a473796a8e70ec2d4af4f2031afe357afa7057b6ea7fa340730e1b290600090a25050565b60c95460ff16610dac5760405162461bcd60e51b8152600401610a3690614a9f565b60c9805460ff1916905560408051808201909152601981527f73657452657365727665466163746f722875696e7432353629000000000000006020820152610df3906126fb565b610dfb611976565b50610a6b81612799565b60c95460009060ff16610e2a5760405162461bcd60e51b8152600401610a3690614a9f565b60c9805460ff19169055610e3d83612829565b610e45611976565b50610e5383338460006128ba565b50600060c9805460ff1916600117905592915050565b60c95460009060ff16610e8e5760405162461bcd60e51b8152600401610a3690614a9f565b60c9805460ff19169055610ea183612229565b610ea9611976565b50610e53338484612bf3565b60c95460009060ff16610eda5760405162461bcd60e51b8152600401610a3690614a9f565b60c9805460ff19169055610ef033858585612df8565b50600160c9805460ff191660011790559392505050565b610f28604051806060016040528060248152602001614ddb602491396126fb565b60008111610f685760405162461bcd60e51b815260206004820152600d60248201526c125b9d985b1a5908125b9c1d5d609a1b6044820152606401610a36565b60dc5460408051918252602082018390527fc2ac513cdb57f91eb2bef4db918c285829524f549682b99717c6cb06cc011183910160405180910390a160dc55565b60c95460009060ff16610fce5760405162461bcd60e51b8152600401610a3690614a9f565b60c9805460ff19169055610fe0611976565b50610fec338484612370565b506000905060c9805460ff1916600117905592915050565b600061100f83612229565b33600081815260d8602090815260408083206001600160a01b038816845290915290205461103d8482614af2565b6001600160a01b03838116600081815260d860209081526040808320948b16808452948252918290208590559051848152939450919290917f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b92591015b60405180910390a3506001949350505050565b60008060405180602001604052806110c2611d09565b90526001600160a01b038416600090815260d760205260409020549091506110eb908290613022565b9392505050565b6000610bca61303a565b611104612250565b610afd81613070565b60c95460ff1661112f5760405162461bcd60e51b8152600401610a3690614a9f565b60c9805460ff1916905580156111a55760cd5460405163eade3eed60e01b81523060048201526001600160a01b0384811660248301529091169063eade3eed90604401600060405180830381600087803b15801561118c57600080fd5b505af11580156111a0573d6000803e3d6000fd5b505050505b60cd546001600160a01b031633146111d057604051632c40292560e01b815260040160405180910390fd5b60006111db836125ae565b60d3549091506000831561125b576111f386856130cb565b90506111ff8183614b05565b91506001600160a01b038086169087167f1a2a22cb034d26d1854bdc6666a5b91fe25efbbb5dcad3b0355478d6f5c362a18361123b8188614b05565b604080519283526020830191909152810186905260600160405180910390a35b60006112678285614b05565b9050801561132f5760d654600061127e8383614af2565b905061128a8386614b05565b60d682905560408051858152600060208201529081018290529095506001600160a01b0389169030907f1a2a22cb034d26d1854bdc6666a5b91fe25efbbb5dcad3b0355478d6f5c362a19060600160405180910390a360408051848152602081018490529081018290526001600160a01b038916907f90125ffdb441e57c4f6bf69789206424859f206bea5727f2d81ad2470826ef6a9060600160405180910390a250505b6001600160a01b03808716600081815260d9602052604080822091825560d25460019092019190915560d38690555190918916907f9fe0294717a8efbc6ace1c151b73a4c89982339b2228a27d1ca21394e348986f906113929089815260200190565b60405180910390a3505060c9805460ff191660011790555050505050565b6113b8612250565b6113c260006131d9565b565b60c95460009060ff166113e95760405162461bcd60e51b8152600401610a3690614a9f565b60c9805460ff191690556113fb611976565b505060d35460c9805460ff1916600117905590565b61144e6040518060400160405280601e81526020017f73657450726f746f636f6c5365697a6553686172652875696e743235362900008152506126fb565b60cd5460408051634ada90af60e01b815290516000926001600160a01b031691634ada90af9160048083019260209291908290030181865afa158015611498573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906114bc9190614ac3565b9050806114d1670de0b6b3a764000084614af2565b11156114f05760405163034dd2c160e11b815260040160405180910390fd5b60da80549083905560408051828152602081018590527ff5815f353a60e815cce7553e4f60c533a59d26b1b5504ea4b6db8d60da3e4da291015b60405180910390a1505050565b60c95460ff166115595760405162461bcd60e51b8152600401610a3690614a9f565b60c9805460ff1916905561156b611976565b50611575816131f2565b505060c9805460ff19166001179055565b60655433906001600160a01b031681146115f45760405162461bcd60e51b815260206004820152602960248201527f4f776e61626c6532537465703a2063616c6c6572206973206e6f7420746865206044820152683732bb9037bbb732b960b91b6064820152608401610a36565b610afd816131d9565b60c95460009060ff166116225760405162461bcd60e51b8152600401610a3690614a9f565b60c9805460ff19169055611634611976565b5061164233336000856128ba565b50600060c9805460ff19166001179055919050565b600061166283612829565b61166a611976565b50611676833384613287565b50600092915050565b600054610100900460ff161580801561169f5750600054600160ff909116105b806116b95750303b1580156116b9575060005460ff166001145b61171c5760405162461bcd60e51b815260206004820152602e60248201527f496e697469616c697a61626c653a20636f6e747261637420697320616c72656160448201526d191e481a5b9a5d1a585b1a5e995960921b6064820152608401610a36565b6000805460ff19166001179055801561173f576000805461ff0019166101001790555b61174885612229565b61175b8c8c8c8c8c8c8c8c8c8c8c613465565b80156117a1576000805461ff0019169055604051600181527f7f26b83ff96e1f2b6a682f133852f6798a09c465da95921460cefb38474024989060200160405180910390a15b505050505050505050505050565b60cd546001600160a01b031633146117da57604051635c85a5e760e01b815260040160405180910390fd5b6117e78585858585613692565b5050505050565b61182c6040518060400160405280601d81526020017f736574496e746572657374526174654d6f64656c2861646472657373290000008152506126fb565b611834611976565b50610afd81613773565b60cb805461099390614a65565b6000610ae6826125ae565b60c95460009060ff1661187b5760405162461bcd60e51b8152600401610a3690614a9f565b60c9805460ff1916905561188d611976565b50611642333384612bf3565b60006118a483612229565b33600081815260d8602090815260408083206001600160a01b0388168452909152902054838110156119185760405162461bcd60e51b815260206004820152601e60248201527f64656372656173656420616c6c6f77616e63652062656c6f77207a65726f00006044820152606401610a36565b6001600160a01b03828116600081815260d860209081526040808320948a1680845294825291829020948890039485905590518481527f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b9259101611099565b600080611981611e72565b60d1549091508181036119975760009250505090565b60006119a161303a565b60d35460d45460d25460ce5460d6546040516301cee29d60e21b815260048101879052602481018690526044810185905260648101919091529495509293919290916000916001600160a01b03169063073b8a7490608401602060405180830381865afa158015611a16573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611a3a9190614ac3565b90507f000000000000000000000000000000000000000000000000000001840f0fbb00811115611aac5760405162461bcd60e51b815260206004820152601c60248201527f626f72726f772072617465206973206162737572646c792068696768000000006044820152606401610a36565b6000611ab88789614b05565b90506000611ad4604051806020016040528085815250836138b5565b90506000611ae28288613022565b90506000611af08883614af2565b90506000611b0f604051806020016040528060d054815250848a6138e6565b90506000611b1e85898a6138e6565b60d18e905560d281905560d384905560d483905560dc5460dd5491925090611b46908f614b05565b10611b6f5760dd8d9055818b1015611b6657611b618b6120ad565b611b6f565b611b6f826120ad565b604080518c815260208101869052908101829052606081018490527f4dec04e750ca11537cabcd8a9eab06494de08da3735bc8871cd41250e190bc049060800160405180910390a160009d505050505050505050505050505090565b60c95460009060ff16611bf05760405162461bcd60e51b8152600401610a3690614a9f565b60c9805460ff19169055611c0633808585612df8565b50600160c9805460ff1916600117905592915050565b60ce546000906001600160a01b0316630cde8d1c611c3861303a565b60d35460d45460d05460d6546040516001600160e01b031960e088901b1681526004810195909552602485019390935260448401919091526064830152608482015260a4015b602060405180830381865afa158015611c9b573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610bca9190614ac3565b60c95460ff16611ce15760405162461bcd60e51b8152600401610a3690614a9f565b60c9805460ff19169055611cf733848484613907565b505060c9805460ff1916600117905550565b60c95460009060ff16611d2e5760405162461bcd60e51b8152600401610a3690614a9f565b60c9805460ff19169055611d40611976565b50611d4961261e565b905060c9805460ff1916600117905590565b6001600160a01b038116600090815260d760205260408120548190819081908190611d85876125ae565b611d8d61261e565b93509350935093509193509193565b60c95460009060ff16611dc15760405162461bcd60e51b8152600401610a3690614a9f565b60c9805460ff19169055611dd3611976565b50611642333384613287565b60c95460009060ff16611e045760405162461bcd60e51b8152600401610a3690614a9f565b60c9805460ff19169055611e16611976565b5061164233338460006128ba565b60c95460009060ff16611e495760405162461bcd60e51b8152600401610a3690614a9f565b60c9805460ff19169055611e5c83612829565b611e64611976565b50610e5383336000856128ba565b6000610bca7f00000000000000000000000000000000000000000000000000000143000020a963ffffffff16565b60db546001600160a01b03163314611f0e5760405162461bcd60e51b815260206004820152602b60248201527f6f6e6c792073686f727466616c6c20636f6e74726163742063616e207570646160448201526a1d1948189859081919589d60aa1b6064820152608401610a36565b60d654811115611f725760405162461bcd60e51b815260206004820152602960248201527f6d6f7265207468616e206261642064656274207265636f76657265642066726f604482015268369030bab1ba34b7b760b91b6064820152608401610a36565b60d6546000611f818383614b05565b60d681905560408051848152602081018390529192507f9e19ec7d2b8f8a94df8cc0072453ace318d221e3cbb2731d0eaa0baac856520f910161152a565b611fc7612250565b606580546001600160a01b0383166001600160a01b03199091168117909155611ff86033546001600160a01b031690565b6001600160a01b03167f38d16b8cac22d99fc7c124b9cd0de2d3fa1faef420bfe791d8c362d765e2270060405160405180910390a350565b6000612040338585856000613692565b5060009392505050565b60ce546000906001600160a01b031663073b8a7461206661303a565b60d35460d45460d6546040516001600160e01b031960e087901b1681526004810194909452602484019290925260448301526064820152608401611c7e565b4390565b4290565b806000036120b85750565b60006120c2611e72565b60d154146120e357604051630dff50cb60e41b815260040160405180910390fd5b816120ec61303a565b101561210b57604051633345e99960e01b815260040160405180910390fd5b60d45482111561212e576040516378d2980560e11b815260040160405180910390fd5b8160d45461213c9190614b05565b60d481905560cc5490915061215f9061010090046001600160a01b031683613cb1565b60cc5460cd5460c9546040516305bebb3b60e21b81526001600160a01b03610100948590048116946316faecec946121a7949083169391900490911690600090600401614b18565b600060405180830381600087803b1580156121c157600080fd5b505af11580156121d5573d6000803e3d6000fd5b505060cc5460408051868152602081018690526101009092046001600160a01b031693507f9cc63bb4ef37ad6a5f5f657dfaf94865531d4234acbc431cc8ac035468f6272092500160405180910390a25050565b6001600160a01b038116610afd576040516342bcdf7f60e11b815260040160405180910390fd5b6033546001600160a01b031633146113c25760405162461bcd60e51b815260206004820181905260248201527f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e65726044820152606401610a36565b6001600160a01b03811661230e5760405162461bcd60e51b815260206004820152602560248201527f696e76616c696420616365737320636f6e74726f6c206d616e61676572206164604482015264647265737360d81b6064820152608401610a36565b609780546001600160a01b038381166001600160a01b031983168117909355604080519190921680825260208201939093527f66fd58e82f7b31a2a5c30e0888f3093efe4e111b00cd2b0c31fe014601293aa091015b60405180910390a15050565b60cd5460405163eade3eed60e01b81523060048201526001600160a01b038481166024830152600092169063eade3eed90604401600060405180830381600087803b1580156123be57600080fd5b505af11580156123d2573d6000803e3d6000fd5b505050506123de611e72565b60d154146123ff5760405163c9021e2f60e01b815260040160405180910390fd5b600061240a846125ae565b905060008184101561241c578361241e565b815b9050600061242c87836130cb565b9050600061243a8285614b05565b905060008260d35461244c9190614b05565b6001600160a01b03898116600081815260d9602090815260409182902087815560d25460019091015560d3859055815188815290810187905290810184905292935091908b16907f1a2a22cb034d26d1854bdc6666a5b91fe25efbbb5dcad3b0355478d6f5c362a19060600160405180910390a360cd5460d254604051631ededc9160e01b81523060048201526001600160a01b038c811660248301528b81166044830152606482018790526084820192909252911690631ededc919060a401600060405180830381600087803b15801561252657600080fd5b505af115801561253a573d6000803e3d6000fd5b50949b9a5050505050505050505050565b61255481612229565b60cc80546001600160a01b03838116610100818102610100600160a81b031985161790945560405193909204169182907fafec95c8612496c3ecf5dddc71e393528fe29bd145fbaf9c6b496d78d7e2d79b90600090a35050565b6001600160a01b038116600090815260d96020908152604080832081518083019092528054808352600190910154928201929092529082036125f35750600092915050565b60d254815160009161260491614b5c565b90508160200151816126169190614b73565b949350505050565b60d55460009080820361263357505060cf5490565b600061263d61303a565b9050600060d45460d65460d354846126559190614af2565b61265f9190614af2565b6126699190614b05565b9050600083612680670de0b6b3a764000084614b5c565b61268a9190614b73565b95945050505050565b6040516001600160a01b0383166024820152604481018290526126f690849063a9059cbb60e01b906064015b60408051601f198184030181529190526020810180516001600160e01b03166001600160e01b031990931692909217909152613ccd565b505050565b6097546040516318c5e8ab60e01b81526000916001600160a01b0316906318c5e8ab9061272e9033908690600401614b95565b602060405180830381865afa15801561274b573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061276f9190614bb9565b90508061279557333083604051634a3fa29360e01b8152600401610a3693929190614bd6565b5050565b6127a1611e72565b60d154146127c257604051637dfca6b760e11b815260040160405180910390fd5b670de0b6b3a76400008111156127eb5760405163717220f360e11b815260040160405180910390fd5b60d080549082905560408051828152602081018490527faaa68312e2ea9d50e16af5068410ab56e1a1fd06037b1a35664812c30f8214609101612364565b60cd54604051630217306760e31b81526001600160a01b038381166004830152336024830152909116906310b9833890604401602060405180830381865afa158015612879573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061289d9190614bb9565b610afd57604051630cf0b6f560e01b815260040160405180910390fd5b8115806128c5575080155b61292e5760405162461bcd60e51b815260206004820152603460248201527f6f6e65206f662072656465656d546f6b656e73496e206f722072656465656d416044820152736d6f756e74496e206d757374206265207a65726f60601b6064820152608401610a36565b612936611e72565b60d15414612957576040516397b5cfcd60e01b815260040160405180910390fd5b6000604051806020016040528061296c61261e565b905290506000808415612981578491506129bf565b61298b8484613da2565b915060006129998385613dc0565b905080158015906129aa5750848114155b156129bd57826129b981614c02565b9350505b505b6129c98383613022565b905080600003612a125760405162461bcd60e51b815260206004820152601460248201527372656465656d416d6f756e74206973207a65726f60601b6044820152606401610a36565b60cd54604051634732387560e11b81526001600160a01b0390911690638e6470ea90612a469030908b908790600401614c1b565b600060405180830381600087803b158015612a6057600080fd5b505af1158015612a74573d6000803e3d6000fd5b505050508060d454612a8461303a565b612a8e9190614b05565b1015612aad576040516391240a1b60e01b815260040160405180910390fd5b8160d554612abb9190614b05565b60d5556001600160a01b038716600090815260d76020526040812054612ae2908490614b05565b6001600160a01b038916600090815260d7602052604090208190559050612b098783613cb1565b60405183815230906001600160a01b038a1690600080516020614dff8339815191529060200160405180910390a360408051838152602081018590529081018290526001600160a01b038916907fbd5034ffbd47e4e72a94baa2cdb74c6fad73cb3bcdc13036b72ec8306f5a76469060600160405180910390a260cd546040516351dff98960e01b81523060048201526001600160a01b038a811660248301526044820185905260648201869052909116906351dff989906084015b600060405180830381600087803b158015612bdf57600080fd5b505af11580156117a1573d6000803e3d6000fd5b60cd5460405163c0891ba960e01b81526001600160a01b039091169063c0891ba990612c2790309086908690600401614c1b565b600060405180830381600087803b158015612c4157600080fd5b505af1158015612c55573d6000803e3d6000fd5b50505050612c61611e72565b60d15414612c82576040516338d8859760e01b815260040160405180910390fd5b60006040518060200160405280612c9761261e565b905290506000612ca785846130cb565b90506000612cb58284613da2565b90508060d554612cc59190614af2565b60d5556001600160a01b038516600090815260d76020526040812054612cec908390614af2565b6001600160a01b038716600081815260d760209081526040918290208490558151878152908101869052908101839052919250907fb4c03061fb5b7fed76389d5af8f2e0ddb09f8c70d1333abbb62582835e10accb9060600160405180910390a26040518281526001600160a01b03871690600090600080516020614dff8339815191529060200160405180910390a360cd546040516341c728b960e01b81523060048201526001600160a01b0388811660248301526044820186905260648201859052909116906341c728b990608401600060405180830381600087803b158015612dd757600080fd5b505af1158015612deb573d6000803e3d6000fd5b5050505050505050505050565b60cd54604051636d0be88d60e01b81523060048201526001600160a01b03858116602483015284811660448301526064820184905290911690636d0be88d90608401600060405180830381600087803b158015612e5457600080fd5b505af1158015612e68573d6000803e3d6000fd5b50505050816001600160a01b0316836001600160a01b031603612e9e57604051638cd22d1960e01b815260040160405180910390fd5b6000836001600160a01b0316856001600160a01b031603612ec25750600019612eea565b506001600160a01b03808416600090815260d860209081526040808320938816835292905220545b6000612ef68383614b05565b6001600160a01b038616600090815260d7602052604081205491925090612f1e908590614b05565b6001600160a01b038616600090815260d7602052604081205491925090612f46908690614af2565b6001600160a01b03808916600090815260d7602052604080822086905591891681522081905590506000198414612fa0576001600160a01b03808816600090815260d860209081526040808320938c168352929052208390555b856001600160a01b0316876001600160a01b0316600080516020614dff83398151915287604051612fd391815260200190565b60405180910390a360cd5460405163352b4a3f60e11b81523060048201526001600160a01b03898116602483015288811660448301526064820188905290911690636a56947e90608401612bc5565b60008061302f84846138b5565b905061261681613de3565b60c9546040516370a0823160e01b815230600482015260009161010090046001600160a01b0316906370a0823190602401611c7e565b61307981612229565b60db80546001600160a01b038381166001600160a01b0319831681179093556040519116919082907f6dbf1ff28f860de5edafa4c6505e37c0aba213288cc4166c5352b6d3776c79ef90600090a35050565b60c9546040516370a0823160e01b815230600482015260009161010090046001600160a01b031690829082906370a0823190602401602060405180830381865afa15801561311d573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906131419190614ac3565b90506131586001600160a01b038316863087613dfb565b6040516370a0823160e01b81523060048201526000906001600160a01b038416906370a0823190602401602060405180830381865afa15801561319f573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906131c39190614ac3565b90506131cf8282614b05565b9695505050505050565b606580546001600160a01b0319169055610afd81613e22565b60008060006131ff611e72565b60d15414613223576040516338acf79960e01b815260048101829052602401610a36565b61322d33856130cb565b90508060d45461323d9190614af2565b60d4819055604080518381526020810183905291935033917fa91e67c5ea634cd43a12c5a482724b03de01e85ca68702a53d0c2f45cb7c1dc5910160405180910390a29392505050565b60cd5460405163df71403b60e01b81526001600160a01b039091169063df71403b906132bb90309087908690600401614c1b565b600060405180830381600087803b1580156132d557600080fd5b505af11580156132e9573d6000803e3d6000fd5b505050506132f5611e72565b60d1541461331657604051630e8d8c6160e21b815260040160405180910390fd5b8060d45461332261303a565b61332c9190614b05565b101561334b576040516348c2588160e01b815260040160405180910390fd5b6000613356846125ae565b905060006133648383614af2565b905060008360d3546133769190614af2565b6001600160a01b038716600090815260d96020526040902083815560d25460019091015560d381905590506133ab8585613cb1565b60408051858152602081018490529081018290526001600160a01b038716907f13ed6866d4e1ee6da46f845c46d7e54120883d75c5ea9a2dacc1c4ca8984ab809060600160405180910390a260cd54604051635c77860560e01b81526001600160a01b0390911690635c7786059061342b9030908a908990600401614c1b565b600060405180830381600087803b15801561344557600080fd5b505af1158015613459573d6000803e3d6000fd5b50505050505050505050565b600054610100900460ff1661348c5760405162461bcd60e51b8152600401610a3690614c3f565b613494613e74565b61349d83613ea3565b60d1541580156134ad575060d254155b6135055760405162461bcd60e51b815260206004820152602360248201527f6d61726b6574206d6179206f6e6c7920626520696e697469616c697a6564206f6044820152626e636560e81b6064820152608401610a36565b60cf889055876135705760405162461bcd60e51b815260206004820152603060248201527f696e697469616c2065786368616e67652072617465206d75737420626520677260448201526f32b0ba32b9103a3430b7103d32b9379760811b6064820152608401610a36565b6135798a613eca565b613581611e72565b60d155670de0b6b3a764000060d25561359989613773565b6135a281612799565b60ca6135ae8882614cda565b5060cb6135bb8782614cda565b5060cc805460ff191660ff871617905581516135d690613070565b6135e3826020015161254b565b66b1a2bc2ec5000060da5560c98054610100600160a81b0319166101006001600160a01b038e811682029290921792839055604080516318160ddd60e01b8152905191909304909116916318160ddd9160048083019260209291908290030181865afa158015613657573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061367b9190614ac3565b5060c9805460ff19166001179055612deb846131d9565b60c95460ff166136b45760405162461bcd60e51b8152600401610a3690614a9f565b60c9805460ff191690556136c6611976565b506000826001600160a01b031663a6afed956040518163ffffffff1660e01b81526004016020604051808303816000875af1158015613709573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061372d9190614ac3565b9050801561375157604051633eea49b760e11b815260048101829052602401610a36565b61375e8686868686613fd5565b505060c9805460ff1916600117905550505050565b600061377d611e72565b60d1541461379e57604051630be2a5cb60e11b815260040160405180910390fd5b60ce60009054906101000a90046001600160a01b03169050816001600160a01b0316632191f92a6040518163ffffffff1660e01b8152600401602060405180830381865afa1580156137f4573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906138189190614bb9565b6138645760405162461bcd60e51b815260206004820152601c60248201527f6d61726b6572206d6574686f642072657475726e65642066616c7365000000006044820152606401610a36565b60ce80546001600160a01b0319166001600160a01b0384811691821790925560405190918316907fedffc32e068c7c95dfd4bdfd5c4d939a084d6b11c4199eac8436ed234d72f92690600090a35050565b60408051602081019091526000815260405180602001604052806138dd856000015185614463565b90529392505050565b6000806138f385856138b5565b905061268a61390182613de3565b8461446f565b60cd5460405163037883e560e31b81523060048201526001600160a01b0386811660248301528581166044830152848116606483015290911690631bc41f2890608401600060405180830381600087803b15801561396457600080fd5b505af1158015613978573d6000803e3d6000fd5b50505050826001600160a01b0316826001600160a01b0316036139ae57604051633a94626760e11b815260040160405180910390fd5b60cd5460408051634ada90af60e01b815290516000926001600160a01b031691634ada90af9160048083019260209291908290030181865afa1580156139f8573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190613a1c9190614ac3565b90506000613a3a83604051806020016040528060da54815250613dc0565b90506000613a5682604051806020016040528086815250613da2565b90506000613a648286614b05565b905060006040518060200160405280613a7b61261e565b905290506000613a8b8285613022565b90508360d554613a9b9190614b05565b60d5556001600160a01b038816600090815260d76020526040902054613ac2908890614b05565b6001600160a01b03808a16600090815260d7602052604080822093909355908b1681522054613af2908490614af2565b6001600160a01b03808b16600090815260d7602052604090209190915560cc54613b23916101009091041682613cb1565b60cc5460cd5460c9546040516305bebb3b60e21b81526001600160a01b03610100948590048116946316faecec94613b6b949083169391900490911690600190600401614b18565b600060405180830381600087803b158015613b8557600080fd5b505af1158015613b99573d6000803e3d6000fd5b50505050886001600160a01b0316886001600160a01b0316600080516020614dff83398151915285604051613bd091815260200190565b60405180910390a360cc546040516001600160a01b036101009092048216918a16907f3ac0548d62d3fa3c9a817cd33899b9acacd57e8958ebe51bc7d9a79f26a8a5db90613c219085815260200190565b60405180910390a360cd54604051636d35bf9160e01b81523060048201526001600160a01b038c811660248301528b811660448301528a81166064830152608482018a905290911690636d35bf919060a401600060405180830381600087803b158015613c8d57600080fd5b505af1158015613ca1573d6000803e3d6000fd5b5050505050505050505050505050565b60c95461010090046001600160a01b03166126f6818484612693565b6000613d22826040518060400160405280602081526020017f5361666545524332303a206c6f772d6c6576656c2063616c6c206661696c6564815250856001600160a01b031661447b9092919063ffffffff16565b9050805160001480613d43575080806020019051810190613d439190614bb9565b6126f65760405162461bcd60e51b815260206004820152602a60248201527f5361666545524332303a204552433230206f7065726174696f6e20646964206e6044820152691bdd081cdd58d8d9595960b21b6064820152608401610a36565b60006110eb613db984670de0b6b3a7640000614463565b835161448a565b6000670de0b6b3a7640000613dd9848460000151614463565b6110eb9190614b73565b8051600090610ae690670de0b6b3a764000090614b73565b613e1c846323b872dd60e01b8585856040516024016126bf93929190614c1b565b50505050565b603380546001600160a01b038381166001600160a01b0319831681179093556040519116919082907f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e090600090a35050565b600054610100900460ff16613e9b5760405162461bcd60e51b8152600401610a3690614c3f565b6113c2614496565b600054610100900460ff16610af45760405162461bcd60e51b8152600401610a3690614c3f565b60cd5460408051623f1ee960e11b815290516001600160a01b0392831692841691627e3dd29160048083019260209291908290030181865afa158015613f14573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190613f389190614bb9565b613f845760405162461bcd60e51b815260206004820152601c60248201527f6d61726b6572206d6574686f642072657475726e65642066616c7365000000006044820152606401610a36565b60cd80546001600160a01b0319166001600160a01b0384811691821790925560405190918316907f7ac369dbd14fa5ea3f473ed67cc9d598964a77501540ba6751eb0b3decf5870d90600090a35050565b60cd5460405163e89d51ad60e01b81523060048201526001600160a01b03848116602483015286811660448301526064820186905283151560848301529091169063e89d51ad9060a401600060405180830381600087803b15801561403957600080fd5b505af115801561404d573d6000803e3d6000fd5b50505050614059611e72565b60d1541461407a576040516380965b1b60e01b815260040160405180910390fd5b614082611e72565b826001600160a01b0316636c540baf6040518163ffffffff1660e01b8152600401602060405180830381865afa1580156140c0573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906140e49190614ac3565b1461410257604051631046f38d60e31b815260040160405180910390fd5b846001600160a01b0316846001600160a01b03160361413457604051631bd1a62160e21b815260040160405180910390fd5b826000036141555760405163d29da7ef60e01b815260040160405180910390fd5b600019830361417757604051635982c5bb60e11b815260040160405180910390fd5b6000614184868686612370565b60cd5460405163c488847b60e01b815291925060009182916001600160a01b03169063c488847b906141be90309089908890600401614c1b565b6040805180830381865afa1580156141da573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906141fe9190614d9a565b915091506000821461426e5760405162461bcd60e51b815260206004820152603360248201527f4c49515549444154455f434f4d5054524f4c4c45525f43414c43554c4154455f604482015272105353d5539517d4d152569157d19052531151606a1b6064820152608401610a36565b6040516370a0823160e01b81526001600160a01b0388811660048301528291908716906370a0823190602401602060405180830381865afa1580156142b7573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906142db9190614ac3565b10156143295760405162461bcd60e51b815260206004820152601860248201527f4c49515549444154455f5345495a455f544f4f5f4d55434800000000000000006044820152606401610a36565b306001600160a01b0386160361434a5761434530898984613907565b6143ad565b60405163b2a02ff160e01b81526001600160a01b0386169063b2a02ff19061437a908b908b908690600401614c1b565b600060405180830381600087803b15801561439457600080fd5b505af11580156143a8573d6000803e3d6000fd5b505050505b846001600160a01b0316876001600160a01b0316896001600160a01b03167f298637f684da70674f26509b10f07ec2fbc77a335ab1e7d6215a4b2484d8bb528685604051614405929190918252602082015260400190565b60405180910390a460cd546040516347ef3b3b60e01b81523060048201526001600160a01b0387811660248301528a8116604483015289811660648301526084820186905260a48201849052909116906347ef3b3b9060c401612bc5565b60006110eb8284614b5c565b60006110eb8284614af2565b606061261684846000856144c6565b60006110eb8284614b73565b600054610100900460ff166144bd5760405162461bcd60e51b8152600401610a3690614c3f565b6113c2336131d9565b6060824710156145275760405162461bcd60e51b815260206004820152602660248201527f416464726573733a20696e73756666696369656e742062616c616e636520666f6044820152651c8818d85b1b60d21b6064820152608401610a36565b600080866001600160a01b031685876040516145439190614dbe565b60006040518083038185875af1925050503d8060008114614580576040519150601f19603f3d011682016040523d82523d6000602084013e614585565b606091505b5091509150614596878383876145a1565b979650505050505050565b60608315614610578251600003614609576001600160a01b0385163b6146095760405162461bcd60e51b815260206004820152601d60248201527f416464726573733a2063616c6c20746f206e6f6e2d636f6e74726163740000006044820152606401610a36565b5081612616565b61261683838151156146255781518083602001fd5b8060405162461bcd60e51b8152600401610a36919061468f565b60005b8381101561465a578181015183820152602001614642565b50506000910152565b6000815180845261467b81602086016020860161463f565b601f01601f19169290920160200192915050565b6020815260006110eb6020830184614663565b6000602082840312156146b457600080fd5b5035919050565b6001600160a01b0381168114610afd57600080fd5b80356146db816146bb565b919050565b600080604083850312156146f357600080fd5b82356146fe816146bb565b946020939093013593505050565b60006020828403121561471e57600080fd5b81356110eb816146bb565b60008060006060848603121561473e57600080fd5b8335614749816146bb565b92506020840135614759816146bb565b929592945050506040919091013590565b634e487b7160e01b600052604160045260246000fd5b600082601f83011261479157600080fd5b813567ffffffffffffffff808211156147ac576147ac61476a565b604051601f8301601f19908116603f011681019082821181831017156147d4576147d461476a565b816040528381528660208588010111156147ed57600080fd5b836020870160208301376000602085830101528094505050505092915050565b803560ff811681146146db57600080fd5b60006040828403121561483057600080fd5b6040516040810181811067ffffffffffffffff821117156148535761485361476a565b6040529050808235614864816146bb565b81526020830135614874816146bb565b6020919091015292915050565b60008060008060008060008060008060006101808c8e0312156148a357600080fd5b6148ac8c6146d0565b9a506148ba60208d016146d0565b99506148c860408d016146d0565b985060608c0135975067ffffffffffffffff8060808e013511156148eb57600080fd5b6148fb8e60808f01358f01614780565b97508060a08e0135111561490e57600080fd5b5061491f8d60a08e01358e01614780565b955061492d60c08d0161480d565b945061493b60e08d016146d0565b935061494a6101008d016146d0565b925061495a8d6101208e0161481e565b91506101608c013590509295989b509295989b9093969950565b8015158114610afd57600080fd5b600080600080600060a0868803121561499a57600080fd5b85356149a5816146bb565b945060208601356149b5816146bb565b93506040860135925060608601356149cc816146bb565b915060808601356149dc81614974565b809150509295509295909350565b600080604083850312156149fd57600080fd5b8235614a08816146bb565b91506020830135614a18816146bb565b809150509250929050565b600080600060608486031215614a3857600080fd5b8335614a43816146bb565b9250602084013591506040840135614a5a816146bb565b809150509250925092565b600181811c90821680614a7957607f821691505b602082108103614a9957634e487b7160e01b600052602260045260246000fd5b50919050565b6020808252600a90820152691c994b595b9d195c995960b21b604082015260600190565b600060208284031215614ad557600080fd5b5051919050565b634e487b7160e01b600052601160045260246000fd5b80820180821115610ae657610ae6614adc565b81810381811115610ae657610ae6614adc565b6001600160a01b038481168252831660208201526060810160028310614b4e57634e487b7160e01b600052602160045260246000fd5b826040830152949350505050565b8082028115828204841417610ae657610ae6614adc565b600082614b9057634e487b7160e01b600052601260045260246000fd5b500490565b6001600160a01b038316815260406020820181905260009061261690830184614663565b600060208284031215614bcb57600080fd5b81516110eb81614974565b6001600160a01b0384811682528316602082015260606040820181905260009061268a90830184614663565b600060018201614c1457614c14614adc565b5060010190565b6001600160a01b039384168152919092166020820152604081019190915260600190565b6020808252602b908201527f496e697469616c697a61626c653a20636f6e7472616374206973206e6f74206960408201526a6e697469616c697a696e6760a81b606082015260800190565b601f8211156126f6576000816000526020600020601f850160051c81016020861015614cb35750805b601f850160051c820191505b81811015614cd257828155600101614cbf565b505050505050565b815167ffffffffffffffff811115614cf457614cf461476a565b614d0881614d028454614a65565b84614c8a565b602080601f831160018114614d3d5760008415614d255750858301515b600019600386901b1c1916600185901b178555614cd2565b600085815260208120601f198616915b82811015614d6c57888601518255948401946001909101908401614d4d565b5085821015614d8a5787850151600019600388901b60f8161c191681555b5050505050600190811b01905550565b60008060408385031215614dad57600080fd5b505080516020909101519092909150565b60008251614dd081846020870161463f565b919091019291505056fe7365745265647563655265736572766573426c6f636b44656c74612875696e7432353629ddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3efa2646970667358221220b16fa916660c59bb0e52059aec9877b725d7f8debd2a22c6a43f4028203b51de64736f6c63430008190033
Constructor Arguments (ABI-Encoded and is the last bytes of the Contract Creation Code above)
00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001840f0fbb00
-----Decoded View---------------
Arg [0] : timeBased_ (bool): True
Arg [1] : blocksPerYear_ (uint256): 0
Arg [2] : maxBorrowRateMantissa_ (uint256): 1666700000000
-----Encoded View---------------
3 Constructor Arguments found :
Arg [0] : 0000000000000000000000000000000000000000000000000000000000000001
Arg [1] : 0000000000000000000000000000000000000000000000000000000000000000
Arg [2] : 000000000000000000000000000000000000000000000000000001840f0fbb00
Loading...
Loading
Loading...
Loading
Multichain Portfolio | 30 Chains
Chain | Token | Portfolio % | Price | Amount | Value |
---|
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.