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

351 lines
15 KiB
Solidity

// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.21;
import { IFluidLiquidity } from "../../liquidity/interfaces/iLiquidity.sol";
import { LiquiditySlotsLink } from "../../libraries/liquiditySlotsLink.sol";
import { IFluidReserveContract } from "../../reserve/interfaces/iReserveContract.sol";
import { Error } from "../error.sol";
import { ErrorTypes } from "../errorTypes.sol";
import { FluidConfigHandler } from "../fluidConfigHandler.sol";
import { BigMathMinified } from "../../libraries/bigMathMinified.sol";
import { Structs as AdminModuleStructs } from "../../liquidity/adminModule/structs.sol";
abstract contract Constants {
IFluidReserveContract public immutable RESERVE_CONTRACT;
IFluidLiquidity public immutable LIQUIDITY;
/// @notice supply token at Liquidity which borrow rate is based on
address public immutable SUPPLY_TOKEN;
/// @notice borrow token at Liquidity for which the borrow rate is managed
address public immutable BORROW_TOKEN;
/// @notice buffer at kink1 for the rate. borrow rate = supply rate + buffer. In percent (100 = 1%, 1 = 0.01%)
int256 public immutable RATE_BUFFER_KINK1;
/// @notice buffer at kink2 for the rate. borrow rate = supply rate + buffer. In percent (100 = 1%, 1 = 0.01%)
/// @dev only used if CURRENT borrow rate mode at Liquidity is V2 (with 2 kinks).
int256 public immutable RATE_BUFFER_KINK2;
/// @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_SUPPLY_TOTAL_AMOUNTS_SLOT;
bytes32 internal immutable _LIQUDITY_SUPPLY_EXCHANGE_PRICES_AND_CONFIG_SLOT;
bytes32 internal immutable _LIQUDITY_BORROW_RATE_DATA_SLOT;
uint256 internal constant EXCHANGE_PRICES_PRECISION = 1e12;
uint256 internal constant DEFAULT_EXPONENT_SIZE = 8;
uint256 internal constant DEFAULT_EXPONENT_MASK = 0xff;
uint256 internal constant X14 = 0x3fff;
uint256 internal constant X16 = 0xffff;
uint256 internal constant X64 = 0xffffffffffffffff;
uint256 internal constant FOUR_DECIMALS = 10000;
}
abstract contract Events {
/// @notice emitted when borrow rate for `BORROW_TOKEN` is updated based on
/// supply rate of `SUPPLY_TOKEN` + buffer.
event LogUpdateRate(
uint256 supplyRate,
uint256 oldRateKink1,
uint256 newRateKink1,
uint256 oldRateKink2,
uint256 newRateKink2
);
}
/// @notice Sets borrow rate for `BORROW_TOKEN` at Liquidaty based on supply rate of `SUPPLY_TOKEN` + buffer.
contract FluidBufferRateHandler is Constants, Error, Events, FluidConfigHandler {
/// @dev Validates that an address is not the zero address
modifier validAddress(address value_) {
if (value_ == address(0)) {
revert FluidConfigError(ErrorTypes.BufferRateConfigHandler__AddressZero);
}
_;
}
/// @dev Validates that an address is a rebalancer (taken from reserve contract)
modifier onlyRebalancer() {
if (!RESERVE_CONTRACT.isRebalancer(msg.sender)) {
revert FluidConfigError(ErrorTypes.BufferRateConfigHandler__Unauthorized);
}
_;
}
constructor(
IFluidReserveContract reserveContract_,
IFluidLiquidity liquidity_,
address supplyToken_,
address borrowToken_,
int256 rateBufferKink1_,
int256 rateBufferKink2_,
uint256 minUpdateDiff_
)
validAddress(address(reserveContract_))
validAddress(address(liquidity_))
validAddress(supplyToken_)
validAddress(borrowToken_)
{
if (
minUpdateDiff_ == 0 ||
// rate buffer should be within +100% to - 100%
rateBufferKink1_ > 1e4 ||
rateBufferKink1_ < -int256(1e4) ||
rateBufferKink2_ > 1e4 ||
rateBufferKink2_ < -int256(1e4)
) {
revert FluidConfigError(ErrorTypes.BufferRateConfigHandler__InvalidParams);
}
RESERVE_CONTRACT = reserveContract_;
LIQUIDITY = liquidity_;
SUPPLY_TOKEN = supplyToken_;
BORROW_TOKEN = borrowToken_;
MIN_UPDATE_DIFF = minUpdateDiff_;
RATE_BUFFER_KINK1 = rateBufferKink1_;
RATE_BUFFER_KINK2 = rateBufferKink2_;
_LIQUDITY_SUPPLY_TOTAL_AMOUNTS_SLOT = LiquiditySlotsLink.calculateMappingStorageSlot(
LiquiditySlotsLink.LIQUIDITY_TOTAL_AMOUNTS_MAPPING_SLOT,
supplyToken_
);
_LIQUDITY_SUPPLY_EXCHANGE_PRICES_AND_CONFIG_SLOT = LiquiditySlotsLink.calculateMappingStorageSlot(
LiquiditySlotsLink.LIQUIDITY_EXCHANGE_PRICES_MAPPING_SLOT,
supplyToken_
);
_LIQUDITY_BORROW_RATE_DATA_SLOT = LiquiditySlotsLink.calculateMappingStorageSlot(
LiquiditySlotsLink.LIQUIDITY_RATE_DATA_MAPPING_SLOT,
borrowToken_
);
}
/// @inheritdoc FluidConfigHandler
function configPercentDiff() public view override returns (uint256 configPercentDiff_) {
uint256 rateConfig_ = LIQUIDITY.readFromStorage(_LIQUDITY_BORROW_RATE_DATA_SLOT);
(uint256 newRateKink1_, uint256 newRateKink2_) = _calcBorrowRates(supplyTokenLendingRate(), rateConfig_);
uint256 rateVersion_ = rateConfig_ & 0xF;
if (rateVersion_ == 1) {
uint256 oldRateKink1_ = (rateConfig_ >> LiquiditySlotsLink.BITS_RATE_DATA_V1_RATE_AT_UTILIZATION_KINK) &
X16;
configPercentDiff_ = _percentDiffForValue(oldRateKink1_, newRateKink1_);
} else if (rateVersion_ == 2) {
uint256 oldRateKink1_ = (rateConfig_ >> LiquiditySlotsLink.BITS_RATE_DATA_V2_RATE_AT_UTILIZATION_KINK1) &
X16;
uint256 oldRateKink2_ = (rateConfig_ >> LiquiditySlotsLink.BITS_RATE_DATA_V2_RATE_AT_UTILIZATION_KINK2) &
X16;
configPercentDiff_ = _percentDiffForValue(oldRateKink1_, newRateKink1_);
uint256 rateKink2Diff_ = _percentDiffForValue(oldRateKink2_, newRateKink2_);
// final diff = biggest diff between all config values
configPercentDiff_ = configPercentDiff_ > rateKink2Diff_ ? configPercentDiff_ : rateKink2Diff_;
} else {
revert FluidConfigError(ErrorTypes.BufferRateConfigHandler__RateVersionUnsupported);
}
}
/// @inheritdoc FluidConfigHandler
function rebalance() external override onlyRebalancer {
uint256 supplyLendingRate_ = supplyTokenLendingRate();
uint256 rateConfig_ = LIQUIDITY.readFromStorage(_LIQUDITY_BORROW_RATE_DATA_SLOT);
uint256 rateVersion_ = rateConfig_ & 0xF;
if (rateVersion_ == 1) {
_rebalanceRateV1(supplyLendingRate_, rateConfig_);
} else if (rateVersion_ == 2) {
_rebalanceRateV2(supplyLendingRate_, rateConfig_);
} else {
revert FluidConfigError(ErrorTypes.BufferRateConfigHandler__RateVersionUnsupported);
}
}
/// @notice returns the current calculcated borrow rates at kink1 and kink 2 (for rate data v2).
function calcBorrowRates() public view returns (uint256 rateKink1_, uint256 rateKink2_) {
return _calcBorrowRates(supplyTokenLendingRate(), LIQUIDITY.readFromStorage(_LIQUDITY_BORROW_RATE_DATA_SLOT));
}
/// @notice get current `SUPPLY_TOKEN` lending `rate_` at Liquidity
function supplyTokenLendingRate() public view returns (uint256 rate_) {
// @dev logic here based on Liquidity Resolver .getOverallTokenData()
uint256 totalAmounts_ = LIQUIDITY.readFromStorage(_LIQUDITY_SUPPLY_TOTAL_AMOUNTS_SLOT);
// Extract supply & borrow amounts
uint256 supplyRawInterest_ = totalAmounts_ & X64;
supplyRawInterest_ =
(supplyRawInterest_ >> DEFAULT_EXPONENT_SIZE) <<
(supplyRawInterest_ & DEFAULT_EXPONENT_MASK);
uint256 borrowRawInterest_ = (totalAmounts_ >> LiquiditySlotsLink.BITS_TOTAL_AMOUNTS_BORROW_WITH_INTEREST) &
X64;
borrowRawInterest_ =
(borrowRawInterest_ >> DEFAULT_EXPONENT_SIZE) <<
(borrowRawInterest_ & DEFAULT_EXPONENT_MASK);
if (supplyRawInterest_ > 0) {
uint256 exchangePriceAndConfig_ = LIQUIDITY.readFromStorage(
_LIQUDITY_SUPPLY_EXCHANGE_PRICES_AND_CONFIG_SLOT
);
// use old exchange prices for supply rate to be at same level as borrow rate from storage.
// Note the rate here can be a tiny bit with higher precision because we use borrowWithInterest_ / supplyWithInterest_
// which has higher precision than the utilization used from storage in LiquidityCalcs
uint256 supplyWithInterest_ = (supplyRawInterest_ *
((exchangePriceAndConfig_ >> LiquiditySlotsLink.BITS_EXCHANGE_PRICES_SUPPLY_EXCHANGE_PRICE) & X64)) /
EXCHANGE_PRICES_PRECISION; // normalized from raw
uint256 borrowWithInterest_ = (borrowRawInterest_ *
((exchangePriceAndConfig_ >> LiquiditySlotsLink.BITS_EXCHANGE_PRICES_BORROW_EXCHANGE_PRICE) & X64)) /
EXCHANGE_PRICES_PRECISION; // normalized from raw
uint256 borrowRate_ = exchangePriceAndConfig_ & X16;
uint256 fee_ = (exchangePriceAndConfig_ >> LiquiditySlotsLink.BITS_EXCHANGE_PRICES_FEE) & X14;
rate_ =
(borrowRate_ * (FOUR_DECIMALS - fee_) * borrowWithInterest_) /
(supplyWithInterest_ * FOUR_DECIMALS);
}
}
/// @dev calculates current borrow rates at kinks for supply rate and current rate data
function _calcBorrowRates(
uint256 supplyRate_,
uint256 rateConfig_
) internal view returns (uint256 rateKink1_, uint256 rateKink2_) {
// rate can never be <0, > X16.
rateKink1_ = (int256(supplyRate_) + RATE_BUFFER_KINK1) > 0
? uint256((int256(supplyRate_) + RATE_BUFFER_KINK1))
: 0;
// rate can never be > X16
rateKink1_ = rateKink1_ > X16 ? X16 : rateKink1_;
if ((rateConfig_ & 0xF) == 1) {
// v1: only 1 kink
// rate at last kink must always be <= rate at 100% utilization
uint256 rateAtUtilizationMax_ = (rateConfig_ >>
LiquiditySlotsLink.BITS_RATE_DATA_V1_RATE_AT_UTILIZATION_MAX) & X16;
if (rateKink1_ > rateAtUtilizationMax_) {
rateKink1_ = rateAtUtilizationMax_;
}
} else {
// v2: 2 kinks
// rate can never be <0, > X16.
rateKink2_ = (int256(supplyRate_) + RATE_BUFFER_KINK2) > 0
? uint256(int256(supplyRate_) + RATE_BUFFER_KINK2)
: 0;
// rate can never be > X16
rateKink2_ = rateKink2_ > X16 ? X16 : rateKink2_;
// rate at kink must always be <= rate at 100% utilization
uint256 rateAtUtilizationMax_ = (rateConfig_ >>
LiquiditySlotsLink.BITS_RATE_DATA_V2_RATE_AT_UTILIZATION_MAX) & X16;
if (rateKink1_ > rateAtUtilizationMax_) {
rateKink1_ = rateAtUtilizationMax_;
}
if (rateKink2_ > rateAtUtilizationMax_) {
rateKink2_ = rateAtUtilizationMax_;
}
}
}
/// @dev gets the percentage difference between `oldValue_` and `newValue_` in relation to `oldValue_`
function _percentDiffForValue(
uint256 oldValue_,
uint256 newValue_
) internal pure returns (uint256 configPercentDiff_) {
if (oldValue_ == newValue_) {
return 0;
}
if (oldValue_ > newValue_) {
// % of how much new value would be smaller
configPercentDiff_ = oldValue_ - newValue_;
// e.g. 10 - 8 = 2. 2 * 10000 / 10 -> 2000 (20%)
} else {
// % of how much new value would be bigger
configPercentDiff_ = newValue_ - oldValue_;
// e.g. 10 - 8 = 2. 2 * 10000 / 8 -> 2500 (25%)
}
configPercentDiff_ = (configPercentDiff_ * 1e4) / oldValue_;
}
/// @dev rebalances for a RateV1 config
function _rebalanceRateV1(uint256 supplyRate_, uint256 rateConfig_) internal {
AdminModuleStructs.RateDataV1Params memory rateData_;
uint256 oldRateKink1_ = (rateConfig_ >> LiquiditySlotsLink.BITS_RATE_DATA_V1_RATE_AT_UTILIZATION_KINK) & X16;
(rateData_.rateAtUtilizationKink, ) = _calcBorrowRates(supplyRate_, rateConfig_);
// check if diff is enough to trigger update
if (_percentDiffForValue(oldRateKink1_, rateData_.rateAtUtilizationKink) < MIN_UPDATE_DIFF) {
revert FluidConfigError(ErrorTypes.BufferRateConfigHandler__NoUpdate);
}
rateData_.token = BORROW_TOKEN;
// values that stay the same: kink, rate at 0%, rate at 100%
rateData_.rateAtUtilizationZero =
(rateConfig_ >> LiquiditySlotsLink.BITS_RATE_DATA_V1_RATE_AT_UTILIZATION_ZERO) &
X16;
rateData_.kink = (rateConfig_ >> LiquiditySlotsLink.BITS_RATE_DATA_V1_UTILIZATION_AT_KINK) & X16;
rateData_.rateAtUtilizationMax =
(rateConfig_ >> LiquiditySlotsLink.BITS_RATE_DATA_V1_RATE_AT_UTILIZATION_MAX) &
X16;
// trigger update
AdminModuleStructs.RateDataV1Params[] memory params_ = new AdminModuleStructs.RateDataV1Params[](1);
params_[0] = rateData_;
LIQUIDITY.updateRateDataV1s(params_);
// emit event
emit LogUpdateRate(supplyRate_, oldRateKink1_, rateData_.rateAtUtilizationKink, 0, 0);
}
/// @dev rebalances for a RateV2 config
function _rebalanceRateV2(uint256 supplyRate_, uint256 rateConfig_) internal {
AdminModuleStructs.RateDataV2Params memory rateData_;
uint256 oldRateKink1_ = (rateConfig_ >> LiquiditySlotsLink.BITS_RATE_DATA_V2_RATE_AT_UTILIZATION_KINK1) & X16;
uint256 oldRateKink2_ = (rateConfig_ >> LiquiditySlotsLink.BITS_RATE_DATA_V2_RATE_AT_UTILIZATION_KINK2) & X16;
(rateData_.rateAtUtilizationKink1, rateData_.rateAtUtilizationKink2) = _calcBorrowRates(
supplyRate_,
rateConfig_
);
// check if diff is enough to trigger update
if (
_percentDiffForValue(oldRateKink1_, rateData_.rateAtUtilizationKink1) < MIN_UPDATE_DIFF &&
_percentDiffForValue(oldRateKink2_, rateData_.rateAtUtilizationKink2) < MIN_UPDATE_DIFF
) {
revert FluidConfigError(ErrorTypes.BufferRateConfigHandler__NoUpdate);
}
rateData_.token = BORROW_TOKEN;
// values that stay the same: kink1, kink2, rate at 0%, rate at 100%
rateData_.rateAtUtilizationZero =
(rateConfig_ >> LiquiditySlotsLink.BITS_RATE_DATA_V2_RATE_AT_UTILIZATION_ZERO) &
X16;
rateData_.kink1 = (rateConfig_ >> LiquiditySlotsLink.BITS_RATE_DATA_V2_UTILIZATION_AT_KINK1) & X16;
rateData_.kink2 = (rateConfig_ >> LiquiditySlotsLink.BITS_RATE_DATA_V2_UTILIZATION_AT_KINK2) & X16;
rateData_.rateAtUtilizationMax =
(rateConfig_ >> LiquiditySlotsLink.BITS_RATE_DATA_V2_RATE_AT_UTILIZATION_MAX) &
X16;
// trigger update
AdminModuleStructs.RateDataV2Params[] memory params_ = new AdminModuleStructs.RateDataV2Params[](1);
params_[0] = rateData_;
LIQUIDITY.updateRateDataV2s(params_);
// emit event
emit LogUpdateRate(
supplyRate_,
oldRateKink1_,
rateData_.rateAtUtilizationKink1,
oldRateKink2_,
rateData_.rateAtUtilizationKink2
);
}
}