fluid-contracts-public/contracts/config/maxBorrowHandler/main.sol
2024-07-11 13:05:09 +00:00

266 lines
10 KiB
Solidity

// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.21;
import { IFluidLiquidity } from "../../liquidity/interfaces/iLiquidity.sol";
import { IFluidLiquidityResolver } from "../../periphery/resolvers/liquidity/iLiquidityResolver.sol";
import { LiquiditySlotsLink } from "../../libraries/liquiditySlotsLink.sol";
import { BigMathMinified } from "../../libraries/bigMathMinified.sol";
import { LiquidityCalcs } from "../../libraries/liquidityCalcs.sol";
import { IFluidReserveContract } from "../../reserve/interfaces/iReserveContract.sol";
import { Structs as AdminModuleStructs } from "../../liquidity/adminModule/structs.sol";
import { Error } from "../error.sol";
import { ErrorTypes } from "../errorTypes.sol";
abstract contract Constants {
IFluidReserveContract public immutable RESERVE_CONTRACT;
IFluidLiquidity public immutable LIQUIDITY;
IFluidLiquidityResolver public immutable LIQUIDITY_RESOLVER;
address public immutable PROTOCOL;
address public immutable BORROW_TOKEN;
/// @dev max utilization of total supply that will be set as max borrow limit. In percent (100 = 1%, 1 = 0.01%)
uint256 public immutable MAX_UTILIZATION;
/// @dev minimum percent difference to trigger an update. In percent (100 = 1%, 1 = 0.01%)
uint256 public immutable MIN_UPDATE_DIFF;
bytes32 internal immutable _LIQUDITY_PROTOCOL_BORROW_SLOT;
uint256 internal constant MAX_UTILIZATION_PRECISION = 1e4;
uint256 internal constant DEFAULT_EXPONENT_SIZE = 8;
uint256 internal constant DEFAULT_EXPONENT_MASK = 0xff;
uint256 internal constant EXCHANGE_PRICES_PRECISION = 1e12;
uint256 internal constant X14 = 0x3fff;
uint256 internal constant X18 = 0x3ffff;
uint256 internal constant X24 = 0xffffff;
}
abstract contract Events {
/// @notice emitted when borrow max limit is updated
event LogUpdateBorrowMaxDebtCeiling(
uint256 totalSupplyNormal,
uint256 oldMaxDebtCeilingRaw,
uint256 maxDebtCeilingRaw,
uint256 borrowExchangePrice
);
}
/// @notice Sets max borrow limit for a protocol on Liquidity based on utilization of total supply of the same borrow token
contract FluidMaxBorrowConfigHandler is Constants, Error, Events {
/// @dev Validates that an address is not the zero address
modifier validAddress(address value_) {
if (value_ == address(0)) {
revert FluidConfigError(ErrorTypes.MaxBorrowConfigHandler__AddressZero);
}
_;
}
/// @dev Validates that an address is a rebalancer (taken from reserve contract)
modifier onlyRebalancer() {
if (!RESERVE_CONTRACT.isRebalancer(msg.sender)) {
revert FluidConfigError(ErrorTypes.MaxBorrowConfigHandler__Unauthorized);
}
_;
}
constructor(
IFluidReserveContract reserveContract_,
IFluidLiquidity liquidity_,
IFluidLiquidityResolver liquidityResolver_,
address protocol_,
address borrowToken_,
uint256 maxUtilization_,
uint256 minUpdateDiff_
)
validAddress(address(reserveContract_))
validAddress(address(liquidity_))
validAddress(address(liquidityResolver_))
validAddress(protocol_)
validAddress(borrowToken_)
{
RESERVE_CONTRACT = reserveContract_;
LIQUIDITY = liquidity_;
LIQUIDITY_RESOLVER = liquidityResolver_;
PROTOCOL = protocol_;
BORROW_TOKEN = borrowToken_;
if (maxUtilization_ > MAX_UTILIZATION_PRECISION || minUpdateDiff_ == 0) {
revert FluidConfigError(ErrorTypes.MaxBorrowConfigHandler__InvalidParams);
}
_LIQUDITY_PROTOCOL_BORROW_SLOT = LiquiditySlotsLink.calculateDoubleMappingStorageSlot(
LiquiditySlotsLink.LIQUIDITY_USER_BORROW_DOUBLE_MAPPING_SLOT,
protocol_,
borrowToken_
);
MAX_UTILIZATION = maxUtilization_;
MIN_UPDATE_DIFF = minUpdateDiff_;
}
/// @notice returns `BORROW_TOKEN` total supply at Liquidity (in normal).
function getTotalSupply() public view returns (uint256 totalSupplyNormal_) {
(totalSupplyNormal_, ) = _getTotalSupply();
}
/// @notice returns the currently set max debt ceiling (in raw for mode with interest!).
function currentMaxDebtCeiling() public view returns (uint256 maxDebtCeiling_) {
return
BigMathMinified.fromBigNumber(
(LIQUIDITY.readFromStorage(_LIQUDITY_PROTOCOL_BORROW_SLOT) >>
LiquiditySlotsLink.BITS_USER_BORROW_MAX_BORROW_LIMIT) & X18,
DEFAULT_EXPONENT_SIZE,
DEFAULT_EXPONENT_MASK
);
}
/// @notice returns the max debt ceiling that should be set according to current state (in normal).
function calcMaxDebtCeilingNormal() public view returns (uint256 maxDebtCeilingNormal_) {
(uint256 maxDebtCeilingRaw_, , uint256 borrowExchangePrice_, , ) = _calcMaxDebtCeiling();
// convert to normal
maxDebtCeilingNormal_ = (maxDebtCeilingRaw_ * borrowExchangePrice_) / EXCHANGE_PRICES_PRECISION;
}
/// @notice returns the max debt ceiling that should be set according to current state (in raw for mode with interest!).
function calcMaxDebtCeiling() public view returns (uint256 maxDebtCeiling_) {
(maxDebtCeiling_, , , , ) = _calcMaxDebtCeiling();
}
/// @notice returns how much new config would be different from current config in percent (100 = 1%, 1 = 0.01%).
function configPercentDiff() public view returns (uint256 configPercentDiff_) {
(uint256 maxDebtCeilingRaw_, , , uint256 userBorrowData_, ) = _calcMaxDebtCeiling();
(configPercentDiff_, ) = _configPercentDiff(userBorrowData_, maxDebtCeilingRaw_);
}
/// @notice Rebalances the configs for `PROTOCOL` at Fluid Liquidity based on protocol total supply & total borrow.
/// Emits `LogUpdateBorrowMaxDebtCeiling` if update is executed.
/// Reverts if no update is needed.
/// Can only be called by an authorized rebalancer.
function rebalance() external onlyRebalancer {
if (!_updateBorrowLimits()) {
revert FluidConfigError(ErrorTypes.MaxBorrowConfigHandler__NoUpdate);
}
}
/***********************************|
| INTERNALS |
|__________________________________*/
function _getTotalSupply() public view returns (uint256 totalSupplyNormal_, uint256 borrowExchangePrice_) {
uint256 supplyExchangePrice_;
(supplyExchangePrice_, borrowExchangePrice_) = LiquidityCalcs.calcExchangePrices(
LIQUIDITY_RESOLVER.getExchangePricesAndConfig(BORROW_TOKEN)
);
totalSupplyNormal_ = LiquidityCalcs.getTotalSupply(
LIQUIDITY_RESOLVER.getTotalAmounts(BORROW_TOKEN),
supplyExchangePrice_
);
}
function _calcMaxDebtCeiling()
internal
view
returns (
uint256 maxDebtCeilingRaw_,
uint256 totalSupplyNormal_,
uint256 borrowExchangePrice_,
uint256 userBorrowData_,
uint256 baseDebtCeilingRaw_
)
{
(totalSupplyNormal_, borrowExchangePrice_) = _getTotalSupply();
uint256 maxDebtCeilingNormal_ = (MAX_UTILIZATION * totalSupplyNormal_) / MAX_UTILIZATION_PRECISION;
// turn into maxDebtCeiling Raw
maxDebtCeilingRaw_ = (maxDebtCeilingNormal_ * EXCHANGE_PRICES_PRECISION) / borrowExchangePrice_;
userBorrowData_ = LIQUIDITY.readFromStorage(_LIQUDITY_PROTOCOL_BORROW_SLOT); // total storage slot
baseDebtCeilingRaw_ = BigMathMinified.fromBigNumber(
(userBorrowData_ >> LiquiditySlotsLink.BITS_USER_BORROW_BASE_BORROW_LIMIT) & X18,
DEFAULT_EXPONENT_SIZE,
DEFAULT_EXPONENT_MASK
);
if (baseDebtCeilingRaw_ > maxDebtCeilingRaw_) {
// max debt ceiling can never be < base debt ceiling
maxDebtCeilingRaw_ = baseDebtCeilingRaw_;
}
}
function _configPercentDiff(
uint256 userBorrowData_,
uint256 maxDebtCeilingRaw_
) internal pure returns (uint256 configPercentDiff_, uint256 oldMaxDebtCeilingRaw_) {
oldMaxDebtCeilingRaw_ = BigMathMinified.fromBigNumber(
(userBorrowData_ >> LiquiditySlotsLink.BITS_USER_BORROW_MAX_BORROW_LIMIT) & X18,
DEFAULT_EXPONENT_SIZE,
DEFAULT_EXPONENT_MASK
);
if (oldMaxDebtCeilingRaw_ == maxDebtCeilingRaw_) {
return (0, oldMaxDebtCeilingRaw_);
}
if (oldMaxDebtCeilingRaw_ > maxDebtCeilingRaw_) {
// % of how much new max debt ceiling would be smaller
configPercentDiff_ = oldMaxDebtCeilingRaw_ - maxDebtCeilingRaw_;
// e.g. 10 - 8 = 2. 2 * 10000 / 10 -> 2000 (20%)
} else {
// % of how much new max debt ceiling would be bigger
configPercentDiff_ = maxDebtCeilingRaw_ - oldMaxDebtCeilingRaw_;
// e.g. 10 - 8 = 2. 2 * 10000 / 8 -> 2500 (25%)
}
configPercentDiff_ = (configPercentDiff_ * 1e4) / oldMaxDebtCeilingRaw_;
}
function _updateBorrowLimits() internal returns (bool updated_) {
(
uint256 maxDebtCeilingRaw_,
uint256 totalSupplyNormal_,
uint256 borrowExchangePrice_,
uint256 userBorrowData_,
uint256 baseDebtCeilingRaw_
) = _calcMaxDebtCeiling();
(uint256 configPercentDiff_, uint256 oldMaxDebtCeilingRaw_) = _configPercentDiff(
userBorrowData_,
maxDebtCeilingRaw_
);
// check if min config deviation is reached
if (configPercentDiff_ < MIN_UPDATE_DIFF) {
return false;
}
// execute update at Liquidity
AdminModuleStructs.UserBorrowConfig[] memory userBorrowConfigs_ = new AdminModuleStructs.UserBorrowConfig[](1);
userBorrowConfigs_[0] = AdminModuleStructs.UserBorrowConfig({
user: PROTOCOL,
token: BORROW_TOKEN,
mode: uint8(userBorrowData_ & 1), // first bit
expandPercent: (userBorrowData_ >> LiquiditySlotsLink.BITS_USER_BORROW_EXPAND_PERCENT) & X14, // set same as old
expandDuration: (userBorrowData_ >> LiquiditySlotsLink.BITS_USER_BORROW_EXPAND_DURATION) & X24, // set same as old
baseDebtCeiling: baseDebtCeilingRaw_, // set same as old
maxDebtCeiling: maxDebtCeilingRaw_
});
LIQUIDITY.updateUserBorrowConfigs(userBorrowConfigs_);
emit LogUpdateBorrowMaxDebtCeiling(
totalSupplyNormal_,
oldMaxDebtCeilingRaw_,
maxDebtCeilingRaw_,
borrowExchangePrice_
);
return true;
}
}