mirror of
https://github.com/Instadapp/fluid-contracts-public.git
synced 2024-07-29 21:57:37 +00:00
351 lines
15 KiB
Solidity
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
|
|
);
|
|
}
|
|
}
|