Description:
Proxy contract enabling upgradeable smart contract patterns. Delegates calls to an implementation contract.
Blockchain: Ethereum
Source Code: View Code On The Blockchain
Solidity Source Code:
{{
"language": "Solidity",
"sources": {
"src/moolah/Moolah.sol": {
"content": "// SPDX-License-Identifier: MIT
pragma solidity 0.8.28;
import { AccessControlEnumerableUpgradeable } from "@openzeppelin/contracts-upgradeable/access/extensions/AccessControlEnumerableUpgradeable.sol";
import { UUPSUpgradeable } from "@openzeppelin/contracts/proxy/utils/UUPSUpgradeable.sol";
import { IERC20Metadata } from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol";
import { PausableUpgradeable } from "@openzeppelin/contracts-upgradeable/utils/PausableUpgradeable.sol";
import { ReentrancyGuardUpgradeable } from "@openzeppelin/contracts-upgradeable/utils/ReentrancyGuardUpgradeable.sol";
import { EnumerableSet } from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol";
import { Id, IMoolahStaticTyping, IMoolahBase, MarketParams, Position, Market, Authorization, Signature } from "./interfaces/IMoolah.sol";
import { IMoolahLiquidateCallback, IMoolahRepayCallback, IMoolahSupplyCallback, IMoolahSupplyCollateralCallback, IMoolahFlashLoanCallback } from "./interfaces/IMoolahCallbacks.sol";
import { IIrm } from "./interfaces/IIrm.sol";
import { IERC20 } from "./interfaces/IERC20.sol";
import { IOracle } from "./interfaces/IOracle.sol";
import "./libraries/ConstantsLib.sol";
import { UtilsLib } from "./libraries/UtilsLib.sol";
import { EventsLib } from "./libraries/EventsLib.sol";
import { ErrorsLib } from "./libraries/ErrorsLib.sol";
import { MathLib, WAD } from "./libraries/MathLib.sol";
import { SharesMathLib } from "./libraries/SharesMathLib.sol";
import { MarketParamsLib } from "./libraries/MarketParamsLib.sol";
import { SafeTransferLib } from "./libraries/SafeTransferLib.sol";
import { ReentrancyGuardUpgradeable } from "@openzeppelin/contracts-upgradeable/utils/ReentrancyGuardUpgradeable.sol";
import { IProvider } from "../provider/interfaces/IProvider.sol";
/// @title Moolah
/// @author Lista DAO
/// @notice The Moolah contract.
contract Moolah is
UUPSUpgradeable,
AccessControlEnumerableUpgradeable,
ReentrancyGuardUpgradeable,
PausableUpgradeable,
IMoolahStaticTyping
{
using MathLib for uint128;
using MathLib for uint256;
using UtilsLib for uint256;
using SharesMathLib for uint256;
using SafeTransferLib for IERC20;
using MarketParamsLib for MarketParams;
using EnumerableSet for EnumerableSet.AddressSet;
/* IMMUTABLES */
/// @inheritdoc IMoolahBase
bytes32 public immutable DOMAIN_SEPARATOR;
/* STORAGE */
/// @inheritdoc IMoolahBase
address public feeRecipient;
/// @inheritdoc IMoolahStaticTyping
mapping(Id => mapping(address => Position)) public position;
/// @inheritdoc IMoolahStaticTyping
mapping(Id => Market) public market;
/// @inheritdoc IMoolahBase
mapping(address => bool) public isIrmEnabled;
/// @inheritdoc IMoolahBase
mapping(uint256 => bool) public isLltvEnabled;
/// @inheritdoc IMoolahBase
mapping(address => mapping(address => bool)) public isAuthorized;
/// @inheritdoc IMoolahBase
mapping(address => uint256) public nonce;
/// @inheritdoc IMoolahStaticTyping
mapping(Id => MarketParams) public idToMarketParams;
/// marketId => liquidation whitelist addresses
mapping(Id => EnumerableSet.AddressSet) private liquidationWhitelist;
/// The minimum loan token position value, using the same precision as the oracle (8 decimals).
uint256 public minLoanValue;
/// @inheritdoc IMoolahBase
mapping(Id => mapping(address => address)) public providers;
/// if whitelist is set, only whitelisted addresses can supply, supply collateral, borrow
mapping(Id => EnumerableSet.AddressSet) private whiteList;
/// default market fee rate
uint256 public defaultMarketFee;
bytes32 public constant MANAGER = keccak256("MANAGER"); // manager role
bytes32 public constant PAUSER = keccak256("PAUSER"); // pauser role
bytes32 public constant OPERATOR = keccak256("OPERATOR"); // operator role
/* CONSTRUCTOR */
/// @custom:oz-upgrades-unsafe-allow constructor
constructor() {
_disableInitializers();
DOMAIN_SEPARATOR = keccak256(abi.encode(DOMAIN_TYPEHASH, block.chainid, address(this)));
}
/// @param admin The new admin of the contract.
/// @param manager The new manager of the contract.
/// @param pauser The new pauser of the contract.
function initialize(address admin, address manager, address pauser, uint256 _minLoanValue) public initializer {
require(admin != address(0), ErrorsLib.ZERO_ADDRESS);
require(manager != address(0), ErrorsLib.ZERO_ADDRESS);
require(pauser != address(0), ErrorsLib.ZERO_ADDRESS);
__Pausable_init();
__AccessControl_init();
__ReentrancyGuard_init();
_grantRole(DEFAULT_ADMIN_ROLE, admin);
_grantRole(MANAGER, manager);
_grantRole(PAUSER, pauser);
minLoanValue = _minLoanValue;
}
/* ONLY MANAGER FUNCTIONS */
/// @inheritdoc IMoolahBase
function enableIrm(address irm) external onlyRole(MANAGER) {
require(!isIrmEnabled[irm], ErrorsLib.ALREADY_SET);
isIrmEnabled[irm] = true;
emit EventsLib.EnableIrm(irm);
}
/// @inheritdoc IMoolahBase
function enableLltv(uint256 lltv) external onlyRole(MANAGER) {
require(!isLltvEnabled[lltv], ErrorsLib.ALREADY_SET);
require(lltv < WAD, ErrorsLib.MAX_LLTV_EXCEEDED);
isLltvEnabled[lltv] = true;
emit EventsLib.EnableLltv(lltv);
}
/// @inheritdoc IMoolahBase
function setFee(MarketParams memory marketParams, uint256 newFee) external onlyRole(MANAGER) {
Id id = marketParams.id();
require(market[id].lastUpdate != 0, ErrorsLib.MARKET_NOT_CREATED);
require(newFee != market[id].fee, ErrorsLib.ALREADY_SET);
require(newFee <= MAX_FEE, ErrorsLib.MAX_FEE_EXCEEDED);
// Accrue interest using the previous fee set before changing it.
_accrueInterest(marketParams, id);
// Safe "unchecked" cast.
market[id].fee = uint128(newFee);
emit EventsLib.SetFee(id, newFee);
}
/// @inheritdoc IMoolahBase
function setDefaultMarketFee(uint256 newFee) external onlyRole(MANAGER) {
require(newFee != defaultMarketFee, ErrorsLib.ALREADY_SET);
require(newFee <= MAX_FEE, ErrorsLib.MAX_FEE_EXCEEDED);
defaultMarketFee = newFee;
emit EventsLib.SetDefaultMarketFee(newFee);
}
/// @inheritdoc IMoolahBase
function setFeeRecipient(address newFeeRecipient) external onlyRole(MANAGER) {
require(newFeeRecipient != feeRecipient, ErrorsLib.ALREADY_SET);
feeRecipient = newFeeRecipient;
emit EventsLib.SetFeeRecipient(newFeeRecipient);
}
/// @inheritdoc IMoolahBase
function setMinLoanValue(uint256 _minLoanValue) external onlyRole(MANAGER) {
require(_minLoanValue != minLoanValue, ErrorsLib.ALREADY_SET);
minLoanValue = _minLoanValue;
emit EventsLib.SetMinLoanValue(_minLoanValue);
}
/// @inheritdoc IMoolahBase
function addLiquidationWhitelist(Id id, address account) public onlyRole(MANAGER) {
require(!liquidationWhitelist[id].contains(account), ErrorsLib.ALREADY_SET);
liquidationWhitelist[id].add(account);
emit EventsLib.AddLiquidationWhitelist(id, account);
}
/// @inheritdoc IMoolahBase
function removeLiquidationWhitelist(Id id, address account) public onlyRole(MANAGER) {
require(liquidationWhitelist[id].contains(account), ErrorsLib.NOT_SET);
liquidationWhitelist[id].remove(account);
emit EventsLib.RemoveLiquidationWhitelist(id, account);
}
/// @inheritdoc IMoolahBase
function batchToggleLiquidationWhitelist(
Id[] memory ids,
address[][] memory accounts,
bool isAddition
) external onlyRole(MANAGER) {
require(ids.length == accounts.length, ErrorsLib.INCONSISTENT_INPUT);
// add/remove accounts from liquidation whitelist for each market
for (uint256 i = 0; i < ids.length; ++i) {
Id id = ids[i];
address[] memory accountList = accounts[i];
for (uint256 j = 0; j < accountList.length; ++j) {
address account = accountList[j];
// add to whitelist
if (isAddition) {
addLiquidationWhitelist(id, account);
} else {
// remove from whitelist
removeLiquidationWhitelist(id, account);
}
}
}
}
/// @inheritdoc IMoolahBase
function addWhiteList(Id id, address account) external onlyRole(MANAGER) {
require(!whiteList[id].contains(account), ErrorsLib.ALREADY_SET);
whiteList[id].add(account);
emit EventsLib.AddWhiteList(id, account);
}
/// @inheritdoc IMoolahBase
function removeWhiteList(Id id, address account) external onlyRole(MANAGER) {
require(whiteList[id].contains(account), ErrorsLib.NOT_SET);
whiteList[id].remove(account);
emit EventsLib.RemoveWhiteList(id, account);
}
function addProvider(Id id, address provider) external onlyRole(MANAGER) {
address token = IProvider(provider).TOKEN();
require(token != address(0), ErrorsLib.ZERO_ADDRESS);
require(market[id].lastUpdate != 0, ErrorsLib.MARKET_NOT_CREATED);
require(provider != address(0), ErrorsLib.ZERO_ADDRESS);
require(providers[id][token] == address(0), ErrorsLib.ALREADY_SET);
providers[id][token] = provider;
emit EventsLib.AddProvider(id, token, provider);
}
function removeProvider(Id id, address token) external onlyRole(MANAGER) {
require(providers[id][token] != address(0), ErrorsLib.NOT_SET);
address provider = providers[id][token];
delete providers[id][token];
emit EventsLib.RemoveProvider(id, token, provider);
}
/* MARKET CREATION */
/// @inheritdoc IMoolahBase
function createMarket(MarketParams memory marketParams) external {
require(getRoleMemberCount(OPERATOR) == 0 || hasRole(OPERATOR, msg.sender), ErrorsLib.UNAUTHORIZED);
Id id = marketParams.id();
require(isIrmEnabled[marketParams.irm], ErrorsLib.IRM_NOT_ENABLED);
require(isLltvEnabled[marketParams.lltv], ErrorsLib.LLTV_NOT_ENABLED);
require(market[id].lastUpdate == 0, ErrorsLib.MARKET_ALREADY_CREATED);
require(marketParams.oracle != address(0), ErrorsLib.ZERO_ADDRESS);
require(marketParams.loanToken != address(0), ErrorsLib.ZERO_ADDRESS);
require(marketParams.collateralToken != address(0), ErrorsLib.ZERO_ADDRESS);
// Safe "unchecked" cast.
market[id].lastUpdate = uint128(block.timestamp);
market[id].fee = uint128(defaultMarketFee);
idToMarketParams[id] = marketParams;
IOracle(marketParams.oracle).peek(marketParams.loanToken);
IOracle(marketParams.oracle).peek(marketParams.collateralToken);
emit EventsLib.CreateMarket(id, marketParams);
// Call to initialize the IRM in case it is stateful.
if (marketParams.irm != address(0)) IIrm(marketParams.irm).borrowRate(marketParams, market[id]);
}
/* SUPPLY MANAGEMENT */
/// @inheritdoc IMoolahBase
function supply(
MarketParams memory marketParams,
uint256 assets,
uint256 shares,
address onBehalf,
bytes calldata data
) external whenNotPaused nonReentrant returns (uint256, uint256) {
Id id = marketParams.id();
require(isWhiteList(id, onBehalf), ErrorsLib.NOT_WHITELIST);
require(market[id].lastUpdate != 0, ErrorsLib.MARKET_NOT_CREATED);
require(UtilsLib.exactlyOneZero(assets, shares), ErrorsLib.INCONSISTENT_INPUT);
require(onBehalf != address(0), ErrorsLib.ZERO_ADDRESS);
_accrueInterest(marketParams, id);
if (assets > 0) shares = assets.toSharesDown(market[id].totalSupplyAssets, market[id].totalSupplyShares);
else assets = shares.toAssetsUp(market[id].totalSupplyAssets, market[id].totalSupplyShares);
position[id][onBehalf].supplyShares += shares;
market[id].totalSupplyShares += shares.toUint128();
market[id].totalSupplyAssets += assets.toUint128();
emit EventsLib.Supply(id, msg.sender, onBehalf, assets, shares);
if (data.length > 0) IMoolahSupplyCallback(msg.sender).onMoolahSupply(assets, data);
IERC20(marketParams.loanToken).safeTransferFrom(msg.sender, address(this), assets);
require(_checkSupplyAssets(marketParams, onBehalf), ErrorsLib.REMAIN_SUPPLY_TOO_LOW);
return (assets, shares);
}
/// @inheritdoc IMoolahBase
function withdraw(
MarketParams memory marketParams,
uint256 assets,
uint256 shares,
address onBehalf,
address receiver
) external whenNotPaused nonReentrant returns (uint256, uint256) {
Id id = marketParams.id();
require(market[id].lastUpdate != 0, ErrorsLib.MARKET_NOT_CREATED);
require(UtilsLib.exactlyOneZero(assets, shares), ErrorsLib.INCONSISTENT_INPUT);
require(receiver != address(0), ErrorsLib.ZERO_ADDRESS);
// No need to verify that onBehalf != address(0) thanks to the following authorization check.
require(_isSenderAuthorized(onBehalf), ErrorsLib.UNAUTHORIZED);
_accrueInterest(marketParams, id);
if (assets > 0) shares = assets.toSharesUp(market[id].totalSupplyAssets, market[id].totalSupplyShares);
else assets = shares.toAssetsDown(market[id].totalSupplyAssets, market[id].totalSupplyShares);
position[id][onBehalf].supplyShares -= shares;
market[id].totalSupplyShares -= shares.toUint128();
market[id].totalSupplyAssets -= assets.toUint128();
require(market[id].totalBorrowAssets <= market[id].totalSupplyAssets, ErrorsLib.INSUFFICIENT_LIQUIDITY);
emit EventsLib.Withdraw(id, msg.sender, onBehalf, receiver, assets, shares);
IERC20(marketParams.loanToken).safeTransfer(receiver, assets);
return (assets, shares);
}
/* BORROW MANAGEMENT */
/// @inheritdoc IMoolahBase
function borrow(
MarketParams memory marketParams,
uint256 assets,
uint256 shares,
address onBehalf,
address receiver
) external whenNotPaused nonReentrant returns (uint256, uint256) {
Id id = marketParams.id();
require(isWhiteList(id, onBehalf), ErrorsLib.NOT_WHITELIST);
require(market[id].lastUpdate != 0, ErrorsLib.MARKET_NOT_CREATED);
require(UtilsLib.exactlyOneZero(assets, shares), ErrorsLib.INCONSISTENT_INPUT);
require(receiver != address(0), ErrorsLib.ZERO_ADDRESS);
// No need to verify that onBehalf != address(0) thanks to the following authorization check.
address provider = providers[id][marketParams.loanToken];
if (provider == msg.sender) {
require(receiver == provider, ErrorsLib.NOT_PROVIDER);
} else {
require(_isSenderAuthorized(onBehalf), ErrorsLib.UNAUTHORIZED);
}
_accrueInterest(marketParams, id);
if (assets > 0) shares = assets.toSharesUp(market[id].totalBorrowAssets, market[id].totalBorrowShares);
else assets = shares.toAssetsDown(market[id].totalBorrowAssets, market[id].totalBorrowShares);
position[id][onBehalf].borrowShares += shares.toUint128();
market[id].totalBorrowShares += shares.toUint128();
market[id].totalBorrowAssets += assets.toUint128();
require(_isHealthy(marketParams, id, onBehalf), ErrorsLib.INSUFFICIENT_COLLATERAL);
require(market[id].totalBorrowAssets <= market[id].totalSupplyAssets, ErrorsLib.INSUFFICIENT_LIQUIDITY);
emit EventsLib.Borrow(id, msg.sender, onBehalf, receiver, assets, shares);
IERC20(marketParams.loanToken).safeTransfer(receiver, assets);
require(_checkBorrowAssets(marketParams, onBehalf), ErrorsLib.REMAIN_BORROW_TOO_LOW);
return (assets, shares);
}
/// @inheritdoc IMoolahBase
function repay(
MarketParams memory marketParams,
uint256 assets,
uint256 shares,
address onBehalf,
bytes calldata data
) external whenNotPaused nonReentrant returns (uint256, uint256) {
Id id = marketParams.id();
require(market[id].lastUpdate != 0, ErrorsLib.MARKET_NOT_CREATED);
require(UtilsLib.exactlyOneZero(assets, shares), ErrorsLib.INCONSISTENT_INPUT);
require(onBehalf != address(0), ErrorsLib.ZERO_ADDRESS);
_accrueInterest(marketParams, id);
if (assets > 0) shares = assets.toSharesDown(market[id].totalBorrowAssets, market[id].totalBorrowShares);
else assets = shares.toAssetsUp(market[id].totalBorrowAssets, market[id].totalBorrowShares);
position[id][onBehalf].borrowShares -= shares.toUint128();
market[id].totalBorrowShares -= shares.toUint128();
market[id].totalBorrowAssets = UtilsLib.zeroFloorSub(market[id].totalBorrowAssets, assets).toUint128();
// `assets` may be greater than `totalBorrowAssets` by 1.
emit EventsLib.Repay(id, msg.sender, onBehalf, assets, shares);
if (data.length > 0) IMoolahRepayCallback(msg.sender).onMoolahRepay(assets, data);
IERC20(marketParams.loanToken).safeTransferFrom(msg.sender, address(this), assets);
require(_checkBorrowAssets(marketParams, onBehalf), ErrorsLib.REMAIN_BORROW_TOO_LOW);
return (assets, shares);
}
/* COLLATERAL MANAGEMENT */
/// @inheritdoc IMoolahBase
function supplyCollateral(
MarketParams memory marketParams,
uint256 assets,
address onBehalf,
bytes calldata data
) external whenNotPaused nonReentrant {
Id id = marketParams.id();
require(isWhiteList(id, onBehalf), ErrorsLib.NOT_WHITELIST);
require(market[id].lastUpdate != 0, ErrorsLib.MARKET_NOT_CREATED);
require(assets != 0, ErrorsLib.ZERO_ASSETS);
require(onBehalf != address(0), ErrorsLib.ZERO_ADDRESS);
address provider = providers[id][marketParams.collateralToken];
if (provider != address(0)) {
require(msg.sender == provider, ErrorsLib.NOT_PROVIDER);
}
// Don't accrue interest because it's not required and it saves gas.
position[id][onBehalf].collateral += assets.toUint128();
emit EventsLib.SupplyCollateral(id, msg.sender, onBehalf, assets);
if (data.length > 0) IMoolahSupplyCollateralCallback(msg.sender).onMoolahSupplyCollateral(assets, data);
IERC20(marketParams.collateralToken).safeTransferFrom(msg.sender, address(this), assets);
}
/// @inheritdoc IMoolahBase
function withdrawCollateral(
MarketParams memory marketParams,
uint256 assets,
address onBehalf,
address receiver
) external whenNotPaused nonReentrant {
Id id = marketParams.id();
require(market[id].lastUpdate != 0, ErrorsLib.MARKET_NOT_CREATED);
require(assets != 0, ErrorsLib.ZERO_ASSETS);
require(receiver != address(0), ErrorsLib.ZERO_ADDRESS);
// No need to verify that onBehalf != address(0) thanks to the following authorization check.
address provider = providers[id][marketParams.collateralToken];
if (provider != address(0)) {
require(msg.sender == provider && receiver == provider, ErrorsLib.NOT_PROVIDER);
} else {
require(_isSenderAuthorized(onBehalf), ErrorsLib.UNAUTHORIZED);
}
_accrueInterest(marketParams, id);
position[id][onBehalf].collateral -= assets.toUint128();
require(_isHealthy(marketParams, id, onBehalf), ErrorsLib.INSUFFICIENT_COLLATERAL);
emit EventsLib.WithdrawCollateral(id, msg.sender, onBehalf, receiver, assets);
IERC20(marketParams.collateralToken).safeTransfer(receiver, assets);
}
/* LIQUIDATION */
/// @inheritdoc IMoolahBase
function liquidate(
MarketParams memory marketParams,
address borrower,
uint256 seizedAssets,
uint256 repaidShares,
bytes calldata data
) external whenNotPaused nonReentrant returns (uint256, uint256) {
Id id = marketParams.id();
require(_checkLiquidationWhiteList(id, msg.sender), ErrorsLib.NOT_LIQUIDATION_WHITELIST);
require(market[id].lastUpdate != 0, ErrorsLib.MARKET_NOT_CREATED);
require(UtilsLib.exactlyOneZero(seizedAssets, repaidShares), ErrorsLib.INCONSISTENT_INPUT);
_accrueInterest(marketParams, id);
{
uint256 collateralPrice = getPrice(marketParams);
require(!_isHealthy(marketParams, id, borrower, collateralPrice), ErrorsLib.HEALTHY_POSITION);
// The liquidation incentive factor is min(maxLiquidationIncentiveFactor, 1/(1 - cursor*(1 - lltv))).
uint256 liquidationIncentiveFactor = UtilsLib.min(
MAX_LIQUIDATION_INCENTIVE_FACTOR,
WAD.wDivDown(WAD - LIQUIDATION_CURSOR.wMulDown(WAD - marketParams.lltv))
);
if (seizedAssets > 0) {
uint256 seizedAssetsQuoted = seizedAssets.mulDivUp(collateralPrice, ORACLE_PRICE_SCALE);
repaidShares = seizedAssetsQuoted.wDivUp(liquidationIncentiveFactor).toSharesUp(
market[id].totalBorrowAssets,
market[id].totalBorrowShares
);
} else {
seizedAssets = repaidShares
.toAssetsDown(market[id].totalBorrowAssets, market[id].totalBorrowShares)
.wMulDown(liquidationIncentiveFactor)
.mulDivDown(ORACLE_PRICE_SCALE, collateralPrice);
}
}
uint256 repaidAssets = repaidShares.toAssetsUp(market[id].totalBorrowAssets, market[id].totalBorrowShares);
position[id][borrower].borrowShares -= repaidShares.toUint128();
market[id].totalBorrowShares -= repaidShares.toUint128();
market[id].totalBorrowAssets = UtilsLib.zeroFloorSub(market[id].totalBorrowAssets, repaidAssets).toUint128();
position[id][borrower].collateral -= seizedAssets.toUint128();
uint256 badDebtShares;
uint256 badDebtAssets;
if (position[id][borrower].collateral == 0) {
badDebtShares = position[id][borrower].borrowShares;
badDebtAssets = UtilsLib.min(
market[id].totalBorrowAssets,
badDebtShares.toAssetsUp(market[id].totalBorrowAssets, market[id].totalBorrowShares)
);
market[id].totalBorrowAssets -= badDebtAssets.toUint128();
market[id].totalSupplyAssets -= badDebtAssets.toUint128();
market[id].totalBorrowShares -= badDebtShares.toUint128();
position[id][borrower].borrowShares = 0;
}
// `repaidAssets` may be greater than `totalBorrowAssets` by 1.
emit EventsLib.Liquidate(
id,
msg.sender,
borrower,
repaidAssets,
repaidShares,
seizedAssets,
badDebtAssets,
badDebtShares
);
IERC20(marketParams.collateralToken).safeTransfer(msg.sender, seizedAssets);
{
address provider = providers[id][marketParams.collateralToken];
if (provider != address(0)) {
IProvider(provider).liquidate(id, borrower);
}
}
if (data.length > 0) IMoolahLiquidateCallback(msg.sender).onMoolahLiquidate(repaidAssets, data);
IERC20(marketParams.loanToken).safeTransferFrom(msg.sender, address(this), repaidAssets);
require(_isHealthyAfterLiquidate(marketParams, borrower), ErrorsLib.UNHEALTHY_POSITION);
return (seizedAssets, repaidAssets);
}
/* FLASH LOANS */
/// @inheritdoc IMoolahBase
function flashLoan(address token, uint256 assets, bytes calldata data) external whenNotPaused {
require(assets != 0, ErrorsLib.ZERO_ASSETS);
emit EventsLib.FlashLoan(msg.sender, token, assets);
IERC20(token).safeTransfer(msg.sender, assets);
IMoolahFlashLoanCallback(msg.sender).onMoolahFlashLoan(assets, data);
IERC20(token).safeTransferFrom(msg.sender, address(this), assets);
}
/* AUTHORIZATION */
/// @inheritdoc IMoolahBase
function setAuthorization(address authorized, bool newIsAuthorized) external {
require(newIsAuthorized != isAuthorized[msg.sender][authorized], ErrorsLib.ALREADY_SET);
isAuthorized[msg.sender][authorized] = newIsAuthorized;
emit EventsLib.SetAuthorization(msg.sender, msg.sender, authorized, newIsAuthorized);
}
/// @inheritdoc IMoolahBase
function setAuthorizationWithSig(Authorization memory authorization, Signature calldata signature) external {
/// Do not check whether authorization is already set because the nonce increment is a desired side effect.
require(block.timestamp <= authorization.deadline, ErrorsLib.SIGNATURE_EXPIRED);
require(authorization.nonce == nonce[authorization.authorizer]++, ErrorsLib.INVALID_NONCE);
bytes32 hashStruct = keccak256(abi.encode(AUTHORIZATION_TYPEHASH, authorization));
bytes32 digest = keccak256(bytes.concat("\x19\x01", DOMAIN_SEPARATOR, hashStruct));
address signatory = ecrecover(digest, signature.v, signature.r, signature.s);
require(signatory != address(0) && authorization.authorizer == signatory, ErrorsLib.INVALID_SIGNATURE);
emit EventsLib.IncrementNonce(msg.sender, authorization.authorizer, authorization.nonce);
isAuthorized[authorization.authorizer][authorization.authorized] = authorization.isAuthorized;
emit EventsLib.SetAuthorization(
msg.sender,
authorization.authorizer,
authorization.authorized,
authorization.isAuthorized
);
}
/// @dev Returns whether the sender is authorized to manage `onBehalf`'s positions.
function _isSenderAuthorized(address onBehalf) internal view returns (bool) {
return msg.sender == onBehalf || isAuthorized[onBehalf][msg.sender];
}
/* INTEREST MANAGEMENT */
/// @inheritdoc IMoolahBase
function accrueInterest(MarketParams memory marketParams) external {
Id id = marketParams.id();
require(market[id].lastUpdate != 0, ErrorsLib.MARKET_NOT_CREATED);
_accrueInterest(marketParams, id);
}
/// @dev Accrues interest for the given market `marketParams`.
/// @dev Assumes that the inputs `marketParams` and `id` match.
function _accrueInterest(MarketParams memory marketParams, Id id) internal {
uint256 elapsed = block.timestamp - market[id].lastUpdate;
if (elapsed == 0) return;
if (marketParams.irm != address(0)) {
uint256 borrowRate = IIrm(marketParams.irm).borrowRate(marketParams, market[id]);
uint256 interest = market[id].totalBorrowAssets.wMulDown(borrowRate.wTaylorCompounded(elapsed));
market[id].totalBorrowAssets += interest.toUint128();
market[id].totalSupplyAssets += interest.toUint128();
uint256 feeShares;
if (market[id].fee != 0) {
uint256 feeAmount = interest.wMulDown(market[id].fee);
// The fee amount is subtracted from the total supply in this calculation to compensate for the fact
// that total supply is already increased by the full interest (including the fee amount).
feeShares = feeAmount.toSharesDown(market[id].totalSupplyAssets - feeAmount, market[id].totalSupplyShares);
position[id][feeRecipient].supplyShares += feeShares;
market[id].totalSupplyShares += feeShares.toUint128();
}
emit EventsLib.AccrueInterest(id, borrowRate, interest, feeShares);
}
// Safe "unchecked" cast.
market[id].lastUpdate = uint128(block.timestamp);
}
/* HEALTH CHECK */
/// @dev Returns whether the position of `borrower` in the given market `marketParams` is healthy.
/// @dev Assumes that the inputs `marketParams` and `id` match.
function _isHealthy(MarketParams memory marketParams, Id id, address borrower) internal view returns (bool) {
if (position[id][borrower].borrowShares == 0) return true;
uint256 collateralPrice = getPrice(marketParams);
return _isHealthy(marketParams, id, borrower, collateralPrice);
}
/// @dev Returns whether the position of `borrower` in the given market `marketParams` is healthy.
/// @dev Assumes that the inputs `marketParams` and `id` match.
function isHealthy(MarketParams memory marketParams, Id id, address borrower) external view returns (bool) {
return _isHealthy(marketParams, id, borrower);
}
/// @dev Returns whether the position of `borrower` in the given market `marketParams` with the given
/// `collateralPrice` is healthy.
/// @dev Assumes that the inputs `marketParams` and `id` match.
/// @dev Rounds in favor of the protocol, so one might not be able to borrow exactly `maxBorrow` but one unit less.
function _isHealthy(
MarketParams memory marketParams,
Id id,
address borrower,
uint256 collateralPrice
) internal view returns (bool) {
uint256 borrowed = uint256(position[id][borrower].borrowShares).toAssetsUp(
market[id].totalBorrowAssets,
market[id].totalBorrowShares
);
uint256 maxBorrow = uint256(position[id][borrower].collateral)
.mulDivDown(collateralPrice, ORACLE_PRICE_SCALE)
.wMulDown(marketParams.lltv);
return maxBorrow >= borrowed;
}
function getPrice(MarketParams memory marketParams) public view returns (uint256) {
IOracle _oracle = IOracle(marketParams.oracle);
uint256 baseTokenDecimals = IERC20Metadata(marketParams.collateralToken).decimals();
uint256 quotaTokenDecimals = IERC20Metadata(marketParams.loanToken).decimals();
uint256 basePrice = _oracle.peek(marketParams.collateralToken);
uint256 quotaPrice = _oracle.peek(marketParams.loanToken);
uint256 scaleFactor = 10 ** (36 + quotaTokenDecimals - baseTokenDecimals);
return scaleFactor.mulDivDown(basePrice, quotaPrice);
}
/// @inheritdoc IMoolahBase
function getWhiteList(Id id) external view returns (address[] memory) {
return whiteList[id].values();
}
/// @inheritdoc IMoolahBase
function isWhiteList(Id id, address account) public view returns (bool) {
return whiteList[id].length() == 0 || whiteList[id].contains(account);
}
/// @inheritdoc IMoolahBase
function getLiquidationWhitelist(Id id) external view returns (address[] memory) {
address[] memory whitelist = new address[](liquidationWhitelist[id].length());
for (uint256 i = 0; i < liquidationWhitelist[id].length(); i++) {
whitelist[i] = liquidationWhitelist[id].at(i);
}
return whitelist;
}
/// @inheritdoc IMoolahBase
function isLiquidationWhitelist(Id id, address account) external view returns (bool) {
return _checkLiquidationWhiteList(id, account);
}
function _checkLiquidationWhiteList(Id id, address account) internal view returns (bool) {
return liquidationWhitelist[id].length() == 0 || liquidationWhitelist[id].contains(account);
}
function _checkSupplyAssets(MarketParams memory marketParams, address account) internal view returns (bool) {
Id id = marketParams.id();
if (position[id][account].supplyShares == 0) {
return true;
}
return
uint256(position[id][account].supplyShares).toAssetsDown(
market[id].totalSupplyAssets,
market[id].totalSupplyShares
) >= minLoan(marketParams);
}
function _checkBorrowAssets(MarketParams memory marketParams, address account) internal view returns (bool) {
Id id = marketParams.id();
if (position[id][account].borrowShares == 0) {
return true;
}
return
uint256(position[id][account].borrowShares).toAssetsDown(
market[id].totalBorrowAssets,
market[id].totalBorrowShares
) >= minLoan(marketParams);
}
function _isHealthyAfterLiquidate(MarketParams memory marketParams, address account) internal view returns (bool) {
Id id = marketParams.id();
if (position[id][account].borrowShares == 0 || position[id][account].collateral == 0) {
return true;
}
uint256 borrowAssets = uint256(position[id][account].borrowShares).toAssetsDown(
market[id].totalBorrowAssets,
market[id].totalBorrowShares
);
if (borrowAssets >= minLoan(marketParams)) {
return true;
}
return _isHealthy(marketParams, marketParams.id(), account, getPrice(marketParams));
}
/// @inheritdoc IMoolahBase
function minLoan(MarketParams memory marketParams) public view returns (uint256) {
uint256 price = IOracle(marketParams.oracle).peek(marketParams.loanToken);
uint8 decimals = IERC20Metadata(marketParams.loanToken).decimals();
return price == 0 ? 0 : minLoanValue.mulDivDown(10 ** decimals, price);
}
/**
* @dev pause contract
*/
function pause() external onlyRole(PAUSER) {
_pause();
}
/**
* @dev unpause contract
*/
function unpause() external onlyRole(MANAGER) {
_unpause();
}
function _authorizeUpgrade(address newImplementation) internal override onlyRole(DEFAULT_ADMIN_ROLE) {}
}
"
},
"lib/openzeppelin-contracts-upgradeable/contracts/access/extensions/AccessControlEnumerableUpgradeable.sol": {
"content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (access/extensions/AccessControlEnumerable.sol)
pragma solidity ^0.8.20;
import {IAccessControlEnumerable} from "@openzeppelin/contracts/access/extensions/IAccessControlEnumerable.sol";
import {AccessControlUpgradeable} from "../AccessControlUpgradeable.sol";
import {EnumerableSet} from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol";
import {Initializable} from "../../proxy/utils/Initializable.sol";
/**
* @dev Extension of {AccessControl} that allows enumerating the members of each role.
*/
abstract contract AccessControlEnumerableUpgradeable is Initializable, IAccessControlEnumerable, AccessControlUpgradeable {
using EnumerableSet for EnumerableSet.AddressSet;
/// @custom:storage-location erc7201:openzeppelin.storage.AccessControlEnumerable
struct AccessControlEnumerableStorage {
mapping(bytes32 role => EnumerableSet.AddressSet) _roleMembers;
}
// keccak256(abi.encode(uint256(keccak256("openzeppelin.storage.AccessControlEnumerable")) - 1)) & ~bytes32(uint256(0xff))
bytes32 private constant AccessControlEnumerableStorageLocation = 0xc1f6fe24621ce81ec5827caf0253cadb74709b061630e6b55e82371705932000;
function _getAccessControlEnumerableStorage() private pure returns (AccessControlEnumerableStorage storage $) {
assembly {
$.slot := AccessControlEnumerableStorageLocation
}
}
function __AccessControlEnumerable_init() internal onlyInitializing {
}
function __AccessControlEnumerable_init_unchained() internal onlyInitializing {
}
/**
* @dev See {IERC165-supportsInterface}.
*/
function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
return interfaceId == type(IAccessControlEnumerable).interfaceId || super.supportsInterface(interfaceId);
}
/**
* @dev Returns one of the accounts that have `role`. `index` must be a
* value between 0 and {getRoleMemberCount}, non-inclusive.
*
* Role bearers are not sorted in any particular way, and their ordering may
* change at any point.
*
* WARNING: When using {getRoleMember} and {getRoleMemberCount}, make sure
* you perform all queries on the same block. See the following
* https://forum.openzeppelin.com/t/iterating-over-elements-on-enumerableset-in-openzeppelin-contracts/2296[forum post]
* for more information.
*/
function getRoleMember(bytes32 role, uint256 index) public view virtual returns (address) {
AccessControlEnumerableStorage storage $ = _getAccessControlEnumerableStorage();
return $._roleMembers[role].at(index);
}
/**
* @dev Returns the number of accounts that have `role`. Can be used
* together with {getRoleMember} to enumerate all bearers of a role.
*/
function getRoleMemberCount(bytes32 role) public view virtual returns (uint256) {
AccessControlEnumerableStorage storage $ = _getAccessControlEnumerableStorage();
return $._roleMembers[role].length();
}
/**
* @dev Return all accounts that have `role`
*
* WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
* to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
* this function has an unbounded cost, and using it as part of a state-changing function may render the function
* uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.
*/
function getRoleMembers(bytes32 role) public view virtual returns (address[] memory) {
AccessControlEnumerableStorage storage $ = _getAccessControlEnumerableStorage();
return $._roleMembers[role].values();
}
/**
* @dev Overload {AccessControl-_grantRole} to track enumerable memberships
*/
function _grantRole(bytes32 role, address account) internal virtual override returns (bool) {
AccessControlEnumerableStorage storage $ = _getAccessControlEnumerableStorage();
bool granted = super._grantRole(role, account);
if (granted) {
$._roleMembers[role].add(account);
}
return granted;
}
/**
* @dev Overload {AccessControl-_revokeRole} to track enumerable memberships
*/
function _revokeRole(bytes32 role, address account) internal virtual override returns (bool) {
AccessControlEnumerableStorage storage $ = _getAccessControlEnumerableStorage();
bool revoked = super._revokeRole(role, account);
if (revoked) {
$._roleMembers[role].remove(account);
}
return revoked;
}
}
"
},
"lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/proxy/utils/UUPSUpgradeable.sol": {
"content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.2.0) (proxy/utils/UUPSUpgradeable.sol)
pragma solidity ^0.8.22;
import {IERC1822Proxiable} from "../../interfaces/draft-IERC1822.sol";
import {ERC1967Utils} from "../ERC1967/ERC1967Utils.sol";
/**
* @dev An upgradeability mechanism designed for UUPS proxies. The functions included here can perform an upgrade of an
* {ERC1967Proxy}, when this contract is set as the implementation behind such a proxy.
*
* A security mechanism ensures that an upgrade does not turn off upgradeability accidentally, although this risk is
* reinstated if the upgrade retains upgradeability but removes the security mechanism, e.g. by replacing
* `UUPSUpgradeable` with a custom implementation of upgrades.
*
* The {_authorizeUpgrade} function must be overridden to include access restriction to the upgrade mechanism.
*/
abstract contract UUPSUpgradeable is IERC1822Proxiable {
/// @custom:oz-upgrades-unsafe-allow state-variable-immutable
address private immutable __self = address(this);
/**
* @dev The version of the upgrade interface of the contract. If this getter is missing, both `upgradeTo(address)`
* and `upgradeToAndCall(address,bytes)` are present, and `upgradeTo` must be used if no function should be called,
* while `upgradeToAndCall` will invoke the `receive` function if the second argument is the empty byte string.
* If the getter returns `"5.0.0"`, only `upgradeToAndCall(address,bytes)` is present, and the second argument must
* be the empty byte string if no function should be called, making it impossible to invoke the `receive` function
* during an upgrade.
*/
string public constant UPGRADE_INTERFACE_VERSION = "5.0.0";
/**
* @dev The call is from an unauthorized context.
*/
error UUPSUnauthorizedCallContext();
/**
* @dev The storage `slot` is unsupported as a UUID.
*/
error UUPSUnsupportedProxiableUUID(bytes32 slot);
/**
* @dev Check that the execution is being performed through a delegatecall call and that the execution context is
* a proxy contract with an implementation (as defined in ERC-1967) pointing to self. This should only be the case
* for UUPS and transparent proxies that are using the current contract as their implementation. Execution of a
* function through ERC-1167 minimal proxies (clones) would not normally pass this test, but is not guaranteed to
* fail.
*/
modifier onlyProxy() {
_checkProxy();
_;
}
/**
* @dev Check that the execution is not being performed through a delegate call. This allows a function to be
* callable on the implementing contract but not through proxies.
*/
modifier notDelegated() {
_checkNotDelegated();
_;
}
/**
* @dev Implementation of the ERC-1822 {proxiableUUID} function. This returns the storage slot used by the
* implementation. It is used to validate the implementation's compatibility when performing an upgrade.
*
* IMPORTANT: A proxy pointing at a proxiable contract should not be considered proxiable itself, because this risks
* bricking a proxy that upgrades to it, by delegating to itself until out of gas. Thus it is critical that this
* function revert if invoked through a proxy. This is guaranteed by the `notDelegated` modifier.
*/
function proxiableUUID() external view virtual notDelegated returns (bytes32) {
return ERC1967Utils.IMPLEMENTATION_SLOT;
}
/**
* @dev Upgrade the implementation of the proxy to `newImplementation`, and subsequently execute the function call
* encoded in `data`.
*
* Calls {_authorizeUpgrade}.
*
* Emits an {Upgraded} event.
*
* @custom:oz-upgrades-unsafe-allow-reachable delegatecall
*/
function upgradeToAndCall(address newImplementation, bytes memory data) public payable virtual onlyProxy {
_authorizeUpgrade(newImplementation);
_upgradeToAndCallUUPS(newImplementation, data);
}
/**
* @dev Reverts if the execution is not performed via delegatecall or the execution
* context is not of a proxy with an ERC-1967 compliant implementation pointing to self.
* See {_onlyProxy}.
*/
function _checkProxy() internal view virtual {
if (
address(this) == __self || // Must be called through delegatecall
ERC1967Utils.getImplementation() != __self // Must be called through an active proxy
) {
revert UUPSUnauthorizedCallContext();
}
}
/**
* @dev Reverts if the execution is performed via delegatecall.
* See {notDelegated}.
*/
function _checkNotDelegated() internal view virtual {
if (address(this) != __self) {
// Must not be called through delegatecall
revert UUPSUnauthorizedCallContext();
}
}
/**
* @dev Function that should revert when `msg.sender` is not authorized to upgrade the contract. Called by
* {upgradeToAndCall}.
*
* Normally, this function will use an xref:access.adoc[access control] modifier such as {Ownable-onlyOwner}.
*
* ```solidity
* function _authorizeUpgrade(address) internal onlyOwner {}
* ```
*/
function _authorizeUpgrade(address newImplementation) internal virtual;
/**
* @dev Performs an implementation upgrade with a security check for UUPS proxies, and additional setup call.
*
* As a security check, {proxiableUUID} is invoked in the new implementation, and the return value
* is expected to be the implementation slot in ERC-1967.
*
* Emits an {IERC1967-Upgraded} event.
*/
function _upgradeToAndCallUUPS(address newImplementation, bytes memory data) private {
try IERC1822Proxiable(newImplementation).proxiableUUID() returns (bytes32 slot) {
if (slot != ERC1967Utils.IMPLEMENTATION_SLOT) {
revert UUPSUnsupportedProxiableUUID(slot);
}
ERC1967Utils.upgradeToAndCall(newImplementation, data);
} catch {
// The implementation is not UUPS
revert ERC1967Utils.ERC1967InvalidImplementation(newImplementation);
}
}
}
"
},
"lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/token/ERC20/extensions/IERC20Metadata.sol": {
"content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (token/ERC20/extensions/IERC20Metadata.sol)
pragma solidity ^0.8.20;
import {IERC20} from "../IERC20.sol";
/**
* @dev Interface for the optional metadata functions from the ERC-20 standard.
*/
interface IERC20Metadata is IERC20 {
/**
* @dev Returns the name of the token.
*/
function name() external view returns (string memory);
/**
* @dev Returns the symbol of the token.
*/
function symbol() external view returns (string memory);
/**
* @dev Returns the decimals places of the token.
*/
function decimals() external view returns (uint8);
}
"
},
"lib/openzeppelin-contracts-upgradeable/contracts/utils/PausableUpgradeable.sol": {
"content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (utils/Pausable.sol)
pragma solidity ^0.8.20;
import {ContextUpgradeable} from "../utils/ContextUpgradeable.sol";
import {Initializable} from "../proxy/utils/Initializable.sol";
/**
* @dev Contract module which allows children to implement an emergency stop
* mechanism that can be triggered by an authorized account.
*
* This module is used through inheritance. It will make available the
* modifiers `whenNotPaused` and `whenPaused`, which can be applied to
* the functions of your contract. Note that they will not be pausable by
* simply including this module, only once the modifiers are put in place.
*/
abstract contract PausableUpgradeable is Initializable, ContextUpgradeable {
/// @custom:storage-location erc7201:openzeppelin.storage.Pausable
struct PausableStorage {
bool _paused;
}
// keccak256(abi.encode(uint256(keccak256("openzeppelin.storage.Pausable")) - 1)) & ~bytes32(uint256(0xff))
bytes32 private constant PausableStorageLocation = 0xcd5ed15c6e187e77e9aee88184c21f4f2182ab5827cb3b7e07fbedcd63f03300;
function _getPausableStorage() private pure returns (PausableStorage storage $) {
assembly {
$.slot := PausableStorageLocation
}
}
/**
* @dev Emitted when the pause is triggered by `account`.
*/
event Paused(address account);
/**
* @dev Emitted when the pause is lifted by `account`.
*/
event Unpaused(address account);
/**
* @dev The operation failed because the contract is paused.
*/
error EnforcedPause();
/**
* @dev The operation failed because the contract is not paused.
*/
error ExpectedPause();
/**
* @dev Initializes the contract in unpaused state.
*/
function __Pausable_init() internal onlyInitializing {
__Pausable_init_unchained();
}
function __Pausable_init_unchained() internal onlyInitializing {
PausableStorage storage $ = _getPausableStorage();
$._paused = false;
}
/**
* @dev Modifier to make a function callable only when the contract is not paused.
*
* Requirements:
*
* - The contract must not be paused.
*/
modifier whenNotPaused() {
_requireNotPaused();
_;
}
/**
* @dev Modifier to make a function callable only when the contract is paused.
*
* Requirements:
*
* - The contract must be paused.
*/
modifier whenPaused() {
_requirePaused();
_;
}
/**
* @dev Returns true if the contract is paused, and false otherwise.
*/
function paused() public view virtual returns (bool) {
PausableStorage storage $ = _getPausableStorage();
return $._paused;
}
/**
* @dev Throws if the contract is paused.
*/
function _requireNotPaused() internal view virtual {
if (paused()) {
revert EnforcedPause();
}
}
/**
* @dev Throws if the contract is not paused.
*/
function _requirePaused() internal view virtual {
if (!paused()) {
revert ExpectedPause();
}
}
/**
* @dev Triggers stopped state.
*
* Requirements:
*
* - The contract must not be paused.
*/
function _pause() internal virtual whenNotPaused {
PausableStorage storage $ = _getPausableStorage();
$._paused = true;
emit Paused(_msgSender());
}
/**
* @dev Returns to normal state.
*
* Requirements:
*
* - The contract must be paused.
*/
function _unpause() internal virtual whenPaused {
PausableStorage storage $ = _getPausableStorage();
$._paused = false;
emit Unpaused(_msgSender());
}
}
"
},
"lib/openzeppelin-contracts-upgradeable/contracts/utils/ReentrancyGuardUpgradeable.sol": {
"content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (utils/ReentrancyGuard.sol)
pragma solidity ^0.8.20;
import {Initializable} from "../proxy/utils/Initializable.sol";
/**
* @dev Contract module that helps prevent reentrant calls to a function.
*
* Inheriting from `ReentrancyGuard` will make the {nonReentrant} modifier
* available, which can be applied to functions to make sure there are no nested
* (reentrant) calls to them.
*
* Note that because there is a single `nonReentrant` guard, functions marked as
* `nonReentrant` may not call one another. This can be worked around by making
* those functions `private`, and then adding `external` `nonReentrant` entry
* points to them.
*
* TIP: If EIP-1153 (transient storage) is available on the chain you're deploying at,
* consider using {ReentrancyGuardTransient} instead.
*
* TIP: If you would like to learn more about reentrancy and alternative ways
* to protect against it, check out our blog post
* https://blog.openzeppelin.com/reentrancy-after-istanbul/[Reentrancy After Istanbul].
*/
abstract contract ReentrancyGuardUpgradeable is Initializable {
// Booleans are more expensive than uint256 or any type that takes up a full
// word because each write operation emits an extra SLOAD to first read the
// slot's contents, replace the bits taken up by the boolean, and then write
// back. This is the compiler's defense against contract upgrades and
// pointer aliasing, and it cannot be disabled.
// The values being non-zero value makes deployment a bit more expensive,
// but in exchange the refund on every call to nonReentrant will be lower in
// amount. Since refunds are capped to a percentage of the total
// transaction's gas, it is best to keep them low in cases like this one, to
// increase the likelihood of the full refund coming into effect.
uint256 private constant NOT_ENTERED = 1;
uint256 private constant ENTERED = 2;
/// @custom:storage-location erc7201:openzeppelin.storage.ReentrancyGuard
struct ReentrancyGuardStorage {
uint256 _status;
}
// keccak256(abi.encode(uint256(keccak256("openzeppelin.storage.ReentrancyGuard")) - 1)) & ~bytes32(uint256(0xff))
bytes32 private constant ReentrancyGuardStorageLocation = 0x9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f00;
function _getReentrancyGuardStorage() private pure returns (ReentrancyGuardStorage storage $) {
assembly {
$.slot := ReentrancyGuardStorageLocation
}
}
/**
* @dev Unauthorized reentrant call.
*/
error ReentrancyGuardReentrantCall();
function __ReentrancyGuard_init() internal onlyInitializing {
__ReentrancyGuard_init_unchained();
}
function __ReentrancyGuard_init_unchained() internal onlyInitializing {
ReentrancyGuardStorage storage $ = _getReentrancyGuardStorage();
$._status = NOT_ENTERED;
}
/**
* @dev Prevents a contract from calling itself, directly or indirectly.
* Calling a `nonReentrant` function from another `nonReentrant`
* function is not supported. It is possible to prevent this from happening
* by making the `nonReentrant` function external, and making it call a
* `private` function that does the actual work.
*/
modifier nonReentrant() {
_nonReentrantBefore();
_;
_nonReentrantAfter();
}
function _nonReentrantBefore() private {
ReentrancyGuardStorage storage $ = _getReentrancyGuardStorage();
// On the first call to nonReentrant, _status will be NOT_ENTERED
if ($._status == ENTERED) {
revert ReentrancyGuardReentrantCall();
}
// Any calls to nonReentrant after this point will fail
$._status = ENTERED;
}
function _nonReentrantAfter() private {
ReentrancyGuardStorage storage $ = _getReentrancyGuardStorage();
// By storing the original value once again, a refund is triggered (see
// https://eips.ethereum.org/EIPS/eip-2200)
$._status = NOT_ENTERED;
}
/**
* @dev Returns true if the reentrancy guard is currently set to "entered", which indicates there is a
* `nonReentrant` function in the call stack.
*/
function _reentrancyGuardEntered() internal view returns (bool) {
ReentrancyGuardStorage storage $ = _getReentrancyGuardStorage();
return $._status == ENTERED;
}
}
"
},
"lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/utils/structs/EnumerableSet.sol": {
"content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (utils/structs/EnumerableSet.sol)
// This file was procedurally generated from scripts/generate/templates/EnumerableSet.js.
pragma solidity ^0.8.20;
/**
* @dev Library for managing
* https://en.wikipedia.org/wiki/Set_(abstract_data_type)[sets] of primitive
* types.
*
* Sets have the following properties:
*
* - Elements are added, removed, and checked for existence in constant time
* (O(1)).
* - Elements are enumerated in O(n). No guarantees are made on the ordering.
*
* ```solidity
* contract Example {
* // Add the library methods
* using EnumerableSet for EnumerableSet.AddressSet;
*
* // Declare a set state variable
* EnumerableSet.AddressSet private mySet;
* }
* ```
*
* As of v3.3.0, sets of type `bytes32` (`Bytes32Set`), `address` (`AddressSet`)
* and `uint256` (`UintSet`) are supported.
*
* [WARNING]
* ====
* Trying to delete such a structure from storage will likely result in data corruption, rendering the structure
* unusable.
* See https://github.com/ethereum/solidity/pull/11843[ethereum/solidity#11843] for more info.
*
* In order to clean an EnumerableSet, you can either remove all elements one by one or create a fresh instance using an
* array of EnumerableSet.
* ====
*/
library EnumerableSet {
// To implement this library for multiple types with as little code
// repetition as possible, we write it in terms of a generic Set type with
// bytes32 values.
// The Set implementation uses private functions, and user-facing
// implementations (such as AddressSet) are just wrappers around the
// underlying Set.
// This means that we can only create new EnumerableSets for types that fit
// in bytes32.
struct Set {
// Storage of set values
bytes32[] _values;
// Position is the index of the value in the `values` array plus 1.
// Position 0 is used to mean a value is not in the set.
mapping(bytes32 value => uint256) _positions;
}
/**
* @dev Add a value to a set. O(1).
*
* Returns true if the value was added to the set, that is if it was not
* already present.
*/
function _add(Set storage set, bytes32 value) private returns (bool) {
if (!_contains(set, value)) {
set._values.push(value);
// The value is stored at length-1, but we add 1 to all indexes
// and use 0 as a sentinel value
set._positions[value] = set._values.length;
return true;
} else {
return false;
}
}
/**
* @dev Removes a value from a set. O(1).
*
* Returns true if the value was removed from the set, that is if it was
* present.
*/
function _remove(Set storage set, bytes32 value) private returns (bool) {
// We cache the value's position to prevent multiple reads from the same storage slot
uint256 position = set._positions[value];
if (position != 0) {
// Equivalent to contains(set, value)
// To delete an element from the _values array in O(1), we swap the element to delete with the last one in
// the array, and then remove the last element (sometimes called as 'swap and pop').
// This modifies the order of the array, as noted in {at}.
uint256 valueIndex = position - 1;
uint256 lastIndex = set._values.length - 1;
if (valueIndex != lastIndex) {
bytes32 lastValue = set._values[lastIndex];
// Move the lastValue to the index where the value to delete is
set._values[valueIndex] = lastValue;
// Update the tracked position of the lastValue (that was just moved)
set._positions[lastValue] = position;
}
// Delete the slot where the moved value was stored
set._values.pop();
// Delete the tracked position for the deleted slot
delete set._positions[value];
return true;
} else {
return false;
}
}
/**
* @dev Returns true if the value is in the set. O(1).
*/
function _contains(Set storage set, bytes32 value) private view returns (bool) {
return set._positions[value] != 0;
}
/**
* @dev Returns the number of values on the set. O(1).
*/
function _length(Set storage set) private view returns (uint256) {
return set._values.length;
}
/**
* @dev Returns the value stored at position `index` in the set. O(1).
*
* Note that there are no guarantees on the ordering of values inside the
* array, and it may change when more values are added or removed.
*
* Requirements:
*
* - `index` must be strictly less than {length}.
*/
function _at(Set storage set, uint256 index) private view returns (bytes32) {
return set._values[index];
}
/**
* @dev Return the entire set in an array
*
* WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
* to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
* this function has an unbounded cost, and using it as part of a state-changing function may render the function
* uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.
*/
function _values(Set storage set) private view returns (bytes32[] memory) {
return set._values;
}
// Bytes32Set
struct Bytes32Set {
Set _inner;
}
/**
* @dev Add a value to a set. O(1).
*
* Returns true if the value was added to the set, that is if it was not
* already present.
*/
function add(Bytes32Set storage set, bytes32 value) internal returns (bool) {
return _add(set._inner, value);
}
/**
* @dev Removes a value from a set. O(1).
*
* Returns true if the value was removed from the set, that is if it was
* present.
*/
function remove(Bytes32Set storage set, bytes32 value) internal returns (bool) {
return _remove(set._inner, value);
}
/**
* @dev Returns true if the value is in the set. O(1).
*/
function contains(Bytes32Set storage set, bytes32 value) internal view returns (bool) {
return _contains(set._inner, value);
}
/**
* @dev Returns the number of values in the set. O(1).
*/
function length(Bytes32Set storage set) internal view returns (uint256) {
return _length(set._inner);
}
/**
* @dev Returns the value stored at position `index` in the set. O(1).
*
* Note that there are no guarantees on the ordering of values inside the
* array, and it may change when more values are added or removed.
*
* Requirements:
*
* - `index` must be strictly less than {length}.
*/
function at(Bytes32Set storage set, uint256 index) internal view returns (bytes32) {
return _at(set._inner, index);
}
/**
* @dev Return the entire set in an array
*
* WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
* to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
* this function has an unbounded cost, and using it as part of a state-changing function may render the function
* uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.
*/
function values(Bytes32Set storage set) internal view returns (bytes32[] memory) {
bytes32[] memory store = _values(set._inner);
bytes32[] memory result;
assembly ("memory-safe") {
result := store
}
return result;
}
// AddressSet
struct AddressSet {
Set _inner;
}
/**
* @dev Add a value to a set. O(1).
*
* Returns true if the value was added to the set, that is if it was not
* already present.
*/
function add(AddressSet storage set, address value) internal returns (bool) {
return _add(set._inner, bytes32(uint256(uint160(value))));
}
/**
* @dev Removes a value from a set. O(1).
*
* Returns true if the value was removed from the set, that is if it was
* present.
*/
function remove(AddressSet storage set, address value) internal returns (bool) {
return _remove(set._inner, bytes32(uint256(uint160(value))));
}
/**
* @dev Returns true if the value is in the set. O(1).
*/
function contains(AddressSet storage set, address value) internal view returns (bool) {
return _contains(set._inner, bytes32(uint256(uint160(value))));
}
/**
* @dev Returns the number of values in the set. O(1).
*/
function length(AddressSet storage set) internal view returns (uint256) {
return _length(set._inner);
}
/**
* @dev Returns the value stored at position `index` in the set. O(1).
*
* Note that there are no guarantees on the ordering of values inside the
* array, and it may change when more values are added or removed.
*
* Requirements:
*
* - `index` must be strictly less than {length}.
*/
function at(AddressSet storage set, uint256 index) internal view returns (address) {
return address(uint160(uint256(_at(set._inner, index))));
}
/**
* @dev Return the entire set in an array
*
* WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
* to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
* this function has an unbounded cost, and using it as part of a state-changing function may render the function
* uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.
*/
function values(AddressSet storage set) internal view returns (address[] memory) {
bytes32[] memory store = _values(set._inner);
address[] memory result;
assembly ("memory-safe") {
result := store
}
return result;
}
// UintSet
struct UintSet {
Set _inner;
}
/**
* @dev Add a value to a set. O(1).
*
* Returns true if the value was added to the set, that is if it was not
* already present.
*/
function add(UintSet storage set, uint256 value) internal returns (bool) {
return _add(set._inner, bytes32(value));
}
/**
* @dev Removes a value from a set. O(1).
*
* Returns true if the value was removed from the set, that is
Submitted on: 2025-09-26 11:19:27
Comments
Log in to comment.
No comments yet.