Contract Name:
FluidVaultT1Secondary
Contract Source Code:
// SPDX-License-Identifier: MIT
pragma solidity 0.8.21;
interface IProxy {
function setAdmin(address newAdmin_) external;
function setDummyImplementation(address newDummyImplementation_) external;
function addImplementation(address implementation_, bytes4[] calldata sigs_) external;
function removeImplementation(address implementation_) external;
function getAdmin() external view returns (address);
function getDummyImplementation() external view returns (address);
function getImplementationSigs(address impl_) external view returns (bytes4[] memory);
function getSigsImplementation(bytes4 sig_) external view returns (address);
function readFromStorage(bytes32 slot_) external view returns (uint256 result_);
}
// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.21;
/// @title library that represents a number in BigNumber(coefficient and exponent) format to store in smaller bits.
/// @notice the number is divided into two parts: a coefficient and an exponent. This comes at a cost of losing some precision
/// at the end of the number because the exponent simply fills it with zeroes. This precision is oftentimes negligible and can
/// result in significant gas cost reduction due to storage space reduction.
/// Also note, a valid big number is as follows: if the exponent is > 0, then coefficient last bits should be occupied to have max precision.
/// @dev roundUp is more like a increase 1, which happens everytime for the same number.
/// roundDown simply sets trailing digits after coefficientSize to zero (floor), only once for the same number.
library BigMathMinified {
/// @dev constants to use for `roundUp` input param to increase readability
bool internal constant ROUND_DOWN = false;
bool internal constant ROUND_UP = true;
/// @dev converts `normal` number to BigNumber with `exponent` and `coefficient` (or precision).
/// e.g.:
/// 5035703444687813576399599 (normal) = (coefficient[32bits], exponent[8bits])[40bits]
/// 5035703444687813576399599 (decimal) => 10000101010010110100000011111011110010100110100000000011100101001101001101011101111 (binary)
/// => 10000101010010110100000011111011000000000000000000000000000000000000000000000000000
/// ^-------------------- 51(exponent) -------------- ^
/// coefficient = 1000,0101,0100,1011,0100,0000,1111,1011 (2236301563)
/// exponent = 0011,0011 (51)
/// bigNumber = 1000,0101,0100,1011,0100,0000,1111,1011,0011,0011 (572493200179)
///
/// @param normal number which needs to be converted into Big Number
/// @param coefficientSize at max how many bits of precision there should be (64 = uint64 (64 bits precision))
/// @param exponentSize at max how many bits of exponent there should be (8 = uint8 (8 bits exponent))
/// @param roundUp signals if result should be rounded down or up
/// @return bigNumber converted bigNumber (coefficient << exponent)
function toBigNumber(
uint256 normal,
uint256 coefficientSize,
uint256 exponentSize,
bool roundUp
) internal pure returns (uint256 bigNumber) {
assembly {
let lastBit_
let number_ := normal
if gt(number_, 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF) {
number_ := shr(0x80, number_)
lastBit_ := 0x80
}
if gt(number_, 0xFFFFFFFFFFFFFFFF) {
number_ := shr(0x40, number_)
lastBit_ := add(lastBit_, 0x40)
}
if gt(number_, 0xFFFFFFFF) {
number_ := shr(0x20, number_)
lastBit_ := add(lastBit_, 0x20)
}
if gt(number_, 0xFFFF) {
number_ := shr(0x10, number_)
lastBit_ := add(lastBit_, 0x10)
}
if gt(number_, 0xFF) {
number_ := shr(0x8, number_)
lastBit_ := add(lastBit_, 0x8)
}
if gt(number_, 0xF) {
number_ := shr(0x4, number_)
lastBit_ := add(lastBit_, 0x4)
}
if gt(number_, 0x3) {
number_ := shr(0x2, number_)
lastBit_ := add(lastBit_, 0x2)
}
if gt(number_, 0x1) {
lastBit_ := add(lastBit_, 1)
}
if gt(number_, 0) {
lastBit_ := add(lastBit_, 1)
}
if lt(lastBit_, coefficientSize) {
// for throw exception
lastBit_ := coefficientSize
}
let exponent := sub(lastBit_, coefficientSize)
let coefficient := shr(exponent, normal)
if and(roundUp, gt(exponent, 0)) {
// rounding up is only needed if exponent is > 0, as otherwise the coefficient fully holds the original number
coefficient := add(coefficient, 1)
if eq(shl(coefficientSize, 1), coefficient) {
// case were coefficient was e.g. 111, with adding 1 it became 1000 (in binary) and coefficientSize 3 bits
// final coefficient would exceed it's size. -> reduce coefficent to 100 and increase exponent by 1.
coefficient := shl(sub(coefficientSize, 1), 1)
exponent := add(exponent, 1)
}
}
if iszero(lt(exponent, shl(exponentSize, 1))) {
// if exponent is >= exponentSize, the normal number is too big to fit within
// BigNumber with too small sizes for coefficient and exponent
revert(0, 0)
}
bigNumber := shl(exponentSize, coefficient)
bigNumber := add(bigNumber, exponent)
}
}
/// @dev get `normal` number from `bigNumber`, `exponentSize` and `exponentMask`
function fromBigNumber(
uint256 bigNumber,
uint256 exponentSize,
uint256 exponentMask
) internal pure returns (uint256 normal) {
assembly {
let coefficient := shr(exponentSize, bigNumber)
let exponent := and(bigNumber, exponentMask)
normal := shl(exponent, coefficient)
}
}
/// @dev gets the most significant bit `lastBit` of a `normal` number (length of given number of binary format).
/// e.g.
/// 5035703444687813576399599 = 10000101010010110100000011111011110010100110100000000011100101001101001101011101111
/// lastBit = ^--------------------------------- 83 ----------------------------------------^
function mostSignificantBit(uint256 normal) internal pure returns (uint lastBit) {
assembly {
let number_ := normal
if gt(normal, 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF) {
number_ := shr(0x80, number_)
lastBit := 0x80
}
if gt(number_, 0xFFFFFFFFFFFFFFFF) {
number_ := shr(0x40, number_)
lastBit := add(lastBit, 0x40)
}
if gt(number_, 0xFFFFFFFF) {
number_ := shr(0x20, number_)
lastBit := add(lastBit, 0x20)
}
if gt(number_, 0xFFFF) {
number_ := shr(0x10, number_)
lastBit := add(lastBit, 0x10)
}
if gt(number_, 0xFF) {
number_ := shr(0x8, number_)
lastBit := add(lastBit, 0x8)
}
if gt(number_, 0xF) {
number_ := shr(0x4, number_)
lastBit := add(lastBit, 0x4)
}
if gt(number_, 0x3) {
number_ := shr(0x2, number_)
lastBit := add(lastBit, 0x2)
}
if gt(number_, 0x1) {
lastBit := add(lastBit, 1)
}
if gt(number_, 0) {
lastBit := add(lastBit, 1)
}
}
}
}
// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.21;
library LibsErrorTypes {
/***********************************|
| LiquidityCalcs |
|__________________________________*/
/// @notice thrown when supply or borrow exchange price is zero at calc token data (token not configured yet)
uint256 internal constant LiquidityCalcs__ExchangePriceZero = 70001;
/// @notice thrown when rate data is set to a version that is not implemented
uint256 internal constant LiquidityCalcs__UnsupportedRateVersion = 70002;
/// @notice thrown when the calculated borrow rate turns negative. This should never happen.
uint256 internal constant LiquidityCalcs__BorrowRateNegative = 70003;
/***********************************|
| SafeTransfer |
|__________________________________*/
/// @notice thrown when safe transfer from for an ERC20 fails
uint256 internal constant SafeTransfer__TransferFromFailed = 71001;
/// @notice thrown when safe transfer for an ERC20 fails
uint256 internal constant SafeTransfer__TransferFailed = 71002;
/***********************************|
| SafeApprove |
|__________________________________*/
/// @notice thrown when safe approve from for an ERC20 fails
uint256 internal constant SafeApprove__ApproveFailed = 81001;
}
// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.21;
import { LibsErrorTypes as ErrorTypes } from "./errorTypes.sol";
import { LiquiditySlotsLink } from "./liquiditySlotsLink.sol";
import { BigMathMinified } from "./bigMathMinified.sol";
/// @notice implements calculation methods used for Fluid liquidity such as updated exchange prices,
/// borrow rate, withdrawal / borrow limits, revenue amount.
library LiquidityCalcs {
error FluidLiquidityCalcsError(uint256 errorId_);
/// @notice emitted if the calculated borrow rate surpassed max borrow rate (16 bits) and was capped at maximum value 65535
event BorrowRateMaxCap();
/// @dev constants as from Liquidity variables.sol
uint256 internal constant EXCHANGE_PRICES_PRECISION = 1e12;
/// @dev Ignoring leap years
uint256 internal constant SECONDS_PER_YEAR = 365 days;
// constants used for BigMath conversion from and to storage
uint256 internal constant DEFAULT_EXPONENT_SIZE = 8;
uint256 internal constant DEFAULT_EXPONENT_MASK = 0xFF;
uint256 internal constant FOUR_DECIMALS = 1e4;
uint256 internal constant TWELVE_DECIMALS = 1e12;
uint256 internal constant X14 = 0x3fff;
uint256 internal constant X15 = 0x7fff;
uint256 internal constant X16 = 0xffff;
uint256 internal constant X18 = 0x3ffff;
uint256 internal constant X24 = 0xffffff;
uint256 internal constant X33 = 0x1ffffffff;
uint256 internal constant X64 = 0xffffffffffffffff;
///////////////////////////////////////////////////////////////////////////
////////// CALC EXCHANGE PRICES /////////
///////////////////////////////////////////////////////////////////////////
/// @dev calculates interest (exchange prices) for a token given its' exchangePricesAndConfig from storage.
/// @param exchangePricesAndConfig_ exchange prices and config packed uint256 read from storage
/// @return supplyExchangePrice_ updated supplyExchangePrice
/// @return borrowExchangePrice_ updated borrowExchangePrice
function calcExchangePrices(
uint256 exchangePricesAndConfig_
) internal view returns (uint256 supplyExchangePrice_, uint256 borrowExchangePrice_) {
// Extracting exchange prices
supplyExchangePrice_ =
(exchangePricesAndConfig_ >> LiquiditySlotsLink.BITS_EXCHANGE_PRICES_SUPPLY_EXCHANGE_PRICE) &
X64;
borrowExchangePrice_ =
(exchangePricesAndConfig_ >> LiquiditySlotsLink.BITS_EXCHANGE_PRICES_BORROW_EXCHANGE_PRICE) &
X64;
if (supplyExchangePrice_ == 0 || borrowExchangePrice_ == 0) {
revert FluidLiquidityCalcsError(ErrorTypes.LiquidityCalcs__ExchangePriceZero);
}
uint256 temp_ = exchangePricesAndConfig_ & X16; // temp_ = borrowRate
unchecked {
// last timestamp can not be > current timestamp
uint256 secondsSinceLastUpdate_ = block.timestamp -
((exchangePricesAndConfig_ >> LiquiditySlotsLink.BITS_EXCHANGE_PRICES_LAST_TIMESTAMP) & X33);
uint256 borrowRatio_ = (exchangePricesAndConfig_ >> LiquiditySlotsLink.BITS_EXCHANGE_PRICES_BORROW_RATIO) &
X15;
if (secondsSinceLastUpdate_ == 0 || temp_ == 0 || borrowRatio_ == 1) {
// if no time passed, borrow rate is 0, or no raw borrowings: no exchange price update needed
// (if borrowRatio_ == 1 means there is only borrowInterestFree, as first bit is 1 and rest is 0)
return (supplyExchangePrice_, borrowExchangePrice_);
}
// calculate new borrow exchange price.
// formula borrowExchangePriceIncrease: previous price * borrow rate * secondsSinceLastUpdate_.
// nominator is max uint112 (uint64 * uint16 * uint32). Divisor can not be 0.
borrowExchangePrice_ +=
(borrowExchangePrice_ * temp_ * secondsSinceLastUpdate_) /
(SECONDS_PER_YEAR * FOUR_DECIMALS);
// FOR SUPPLY EXCHANGE PRICE:
// all yield paid by borrowers (in mode with interest) goes to suppliers in mode with interest.
// formula: previous price * supply rate * secondsSinceLastUpdate_.
// where supply rate = (borrow rate - revenueFee%) * ratioSupplyYield. And
// ratioSupplyYield = utilization * supplyRatio * borrowRatio
//
// Example:
// supplyRawInterest is 80, supplyInterestFree is 20. totalSupply is 100. BorrowedRawInterest is 50.
// BorrowInterestFree is 10. TotalBorrow is 60. borrow rate 40%, revenueFee 10%.
// yield is 10 (so half a year must have passed).
// supplyRawInterest must become worth 89. totalSupply must become 109. BorrowedRawInterest must become 60.
// borrowInterestFree must still be 10. supplyInterestFree still 20. totalBorrow 70.
// supplyExchangePrice would have to go from 1 to 1,125 (+ 0.125). borrowExchangePrice from 1 to 1,2 (+0.2).
// utilization is 60%. supplyRatio = 20 / 80 = 25% (only 80% of lenders receiving yield).
// borrowRatio = 10 / 50 = 20% (only 83,333% of borrowers paying yield):
// x of borrowers paying yield = 100% - (20 / (100 + 20)) = 100% - 16.6666666% = 83,333%.
// ratioSupplyYield = 60% * 83,33333% * (100% + 20%) = 62,5%
// supplyRate = (40% * (100% - 10%)) * = 36% * 62,5% = 22.5%
// increase in supplyExchangePrice, assuming 100 as previous price.
// 100 * 22,5% * 1/2 (half a year) = 0,1125.
// cross-check supplyRawInterest worth = 80 * 1.1125 = 89. totalSupply worth = 89 + 20.
// -------------- 1. calculate ratioSupplyYield --------------------------------
// step1: utilization * supplyRatio (or actually part of lenders receiving yield)
// temp_ => supplyRatio (in 1e2: 100% = 10_000; 1% = 100 -> max value 16_383)
// if first bit 0 then ratio is supplyInterestFree / supplyWithInterest (supplyWithInterest is bigger)
// else ratio is supplyWithInterest / supplyInterestFree (supplyInterestFree is bigger)
temp_ = (exchangePricesAndConfig_ >> LiquiditySlotsLink.BITS_EXCHANGE_PRICES_SUPPLY_RATIO) & X15;
if (temp_ == 1) {
// if no raw supply: no exchange price update needed
// (if supplyRatio_ == 1 means there is only supplyInterestFree, as first bit is 1 and rest is 0)
return (supplyExchangePrice_, borrowExchangePrice_);
}
// ratioSupplyYield precision is 1e27 as 100% for increased precision when supplyInterestFree > supplyWithInterest
if (temp_ & 1 == 1) {
// ratio is supplyWithInterest / supplyInterestFree (supplyInterestFree is bigger)
temp_ = temp_ >> 1;
// Note: case where temp_ == 0 (only supplyInterestFree, no yield) already covered by early return
// in the if statement a little above.
// based on above example but supplyRawInterest is 20, supplyInterestFree is 80. no fee.
// supplyRawInterest must become worth 30. totalSupply must become 110.
// supplyExchangePrice would have to go from 1 to 1,5. borrowExchangePrice from 1 to 1,2.
// so ratioSupplyYield must come out as 2.5 (250%).
// supplyRatio would be (20 * 10_000 / 80) = 2500. but must be inverted.
temp_ = (1e27 * FOUR_DECIMALS) / temp_; // e.g. 1e31 / 2500 = 4e27. (* 1e27 for precision)
// e.g. 5_000 * (1e27 + 4e27) / 1e27 = 25_000 (=250%).
temp_ =
// utilization * (100% + 100% / supplyRatio)
(((exchangePricesAndConfig_ >> LiquiditySlotsLink.BITS_EXCHANGE_PRICES_UTILIZATION) & X14) *
(1e27 + temp_)) / // extract utilization (max 16_383 so there is no way this can overflow).
(FOUR_DECIMALS);
// max possible value of temp_ here is 16383 * (1e27 + 1e31) / 1e4 = ~1.64e31
} else {
// ratio is supplyInterestFree / supplyWithInterest (supplyWithInterest is bigger)
temp_ = temp_ >> 1;
// if temp_ == 0 then only supplyWithInterest => full yield. temp_ is already 0
// e.g. 5_000 * 10_000 + (20 * 10_000 / 80) / 10_000 = 5000 * 12500 / 10000 = 6250 (=62.5%).
temp_ =
// 1e27 * utilization * (100% + supplyRatio) / 100%
(1e27 *
((exchangePricesAndConfig_ >> LiquiditySlotsLink.BITS_EXCHANGE_PRICES_UTILIZATION) & X14) * // extract utilization (max 16_383 so there is no way this can overflow).
(FOUR_DECIMALS + temp_)) /
(FOUR_DECIMALS * FOUR_DECIMALS);
// max possible temp_ value: 1e27 * 16383 * 2e4 / 1e8 = 3.2766e27
}
// from here temp_ => ratioSupplyYield (utilization * supplyRatio part) scaled by 1e27. max possible value ~1.64e31
// step2 of ratioSupplyYield: add borrowRatio (only x% of borrowers paying yield)
if (borrowRatio_ & 1 == 1) {
// ratio is borrowWithInterest / borrowInterestFree (borrowInterestFree is bigger)
borrowRatio_ = borrowRatio_ >> 1;
// borrowRatio_ => x of total bororwers paying yield. scale to 1e27.
// Note: case where borrowRatio_ == 0 (only borrowInterestFree, no yield) already covered
// at the beginning of the method by early return if `borrowRatio_ == 1`.
// based on above example but borrowRawInterest is 10, borrowInterestFree is 50. no fee. borrowRatio = 20%.
// so only 16.66% of borrowers are paying yield. so the 100% - part of the formula is not needed.
// x of borrowers paying yield = (borrowRatio / (100 + borrowRatio)) = 16.6666666%
// borrowRatio_ => x of total bororwers paying yield. scale to 1e27.
borrowRatio_ = (borrowRatio_ * 1e27) / (FOUR_DECIMALS + borrowRatio_);
// max value here for borrowRatio_ is (1e31 / (1e4 + 1e4))= 5e26 (= 50% of borrowers paying yield).
} else {
// ratio is borrowInterestFree / borrowWithInterest (borrowWithInterest is bigger)
borrowRatio_ = borrowRatio_ >> 1;
// borrowRatio_ => x of total bororwers paying yield. scale to 1e27.
// x of borrowers paying yield = 100% - (borrowRatio / (100 + borrowRatio)) = 100% - 16.6666666% = 83,333%.
borrowRatio_ = (1e27 - ((borrowRatio_ * 1e27) / (FOUR_DECIMALS + borrowRatio_)));
// borrowRatio can never be > 100%. so max subtraction can be 100% - 100% / 200%.
// or if borrowRatio_ is 0 -> 100% - 0. or if borrowRatio_ is 1 -> 100% - 1 / 101.
// max value here for borrowRatio_ is 1e27 - 0 = 1e27 (= 100% of borrowers paying yield).
}
// temp_ => ratioSupplyYield. scaled down from 1e25 = 1% each to normal percent precision 1e2 = 1%.
// max nominator value is ~1.64e31 * 1e27 = 1.64e58. max result = 1.64e8
temp_ = (FOUR_DECIMALS * temp_ * borrowRatio_) / 1e54;
// 2. calculate supply rate
// temp_ => supply rate (borrow rate - revenueFee%) * ratioSupplyYield.
// division part is done in next step to increase precision. (divided by 2x FOUR_DECIMALS, fee + borrowRate)
// Note that all calculation divisions for supplyExchangePrice are rounded down.
// Note supply rate can be bigger than the borrowRate, e.g. if there are only few lenders with interest
// but more suppliers not earning interest.
temp_ = ((exchangePricesAndConfig_ & X16) * // borrow rate
temp_ * // ratioSupplyYield
(FOUR_DECIMALS - ((exchangePricesAndConfig_ >> LiquiditySlotsLink.BITS_EXCHANGE_PRICES_FEE) & X14))); // revenueFee
// fee can not be > 100%. max possible = 65535 * ~1.64e8 * 1e4 =~1.074774e17.
// 3. calculate increase in supply exchange price
supplyExchangePrice_ += ((supplyExchangePrice_ * temp_ * secondsSinceLastUpdate_) /
(SECONDS_PER_YEAR * FOUR_DECIMALS * FOUR_DECIMALS * FOUR_DECIMALS));
// max possible nominator = max uint 64 * 1.074774e17 * max uint32 = ~8.52e45. Denominator can not be 0.
}
}
///////////////////////////////////////////////////////////////////////////
////////// CALC REVENUE /////////
///////////////////////////////////////////////////////////////////////////
/// @dev gets the `revenueAmount_` for a token given its' totalAmounts and exchangePricesAndConfig from storage
/// and the current balance of the Fluid liquidity contract for the token.
/// @param totalAmounts_ total amounts packed uint256 read from storage
/// @param exchangePricesAndConfig_ exchange prices and config packed uint256 read from storage
/// @param liquidityTokenBalance_ current balance of Liquidity contract (IERC20(token_).balanceOf(address(this)))
/// @return revenueAmount_ collectable revenue amount
function calcRevenue(
uint256 totalAmounts_,
uint256 exchangePricesAndConfig_,
uint256 liquidityTokenBalance_
) internal view returns (uint256 revenueAmount_) {
// @dev no need to super-optimize this method as it is only used by admin
// calculate the new exchange prices based on earned interest
(uint256 supplyExchangePrice_, uint256 borrowExchangePrice_) = calcExchangePrices(exchangePricesAndConfig_);
// total supply = interest free + with interest converted from raw
uint256 totalSupply_ = getTotalSupply(totalAmounts_, supplyExchangePrice_);
if (totalSupply_ > 0) {
// available revenue: balanceOf(token) + totalBorrowings - totalLendings.
revenueAmount_ = liquidityTokenBalance_ + getTotalBorrow(totalAmounts_, borrowExchangePrice_);
// ensure there is no possible case because of rounding etc. where this would revert,
// explicitly check if >
revenueAmount_ = revenueAmount_ > totalSupply_ ? revenueAmount_ - totalSupply_ : 0;
// Note: if utilization > 100% (totalSupply < totalBorrow), then all the amount above 100% utilization
// can only be revenue.
} else {
// if supply is 0, then rest of balance can be withdrawn as revenue so that no amounts get stuck
revenueAmount_ = liquidityTokenBalance_;
}
}
///////////////////////////////////////////////////////////////////////////
////////// CALC LIMITS /////////
///////////////////////////////////////////////////////////////////////////
/// @dev calculates withdrawal limit before an operate execution:
/// amount of user supply that must stay supplied (not amount that can be withdrawn).
/// i.e. if user has supplied 100m and can withdraw 5M, this method returns the 95M, not the withdrawable amount 5M
/// @param userSupplyData_ user supply data packed uint256 from storage
/// @param userSupply_ current user supply amount already extracted from `userSupplyData_` and converted from BigMath
/// @return currentWithdrawalLimit_ current withdrawal limit updated for expansion since last interaction.
/// returned value is in raw for with interest mode, normal amount for interest free mode!
function calcWithdrawalLimitBeforeOperate(
uint256 userSupplyData_,
uint256 userSupply_
) internal view returns (uint256 currentWithdrawalLimit_) {
// @dev must support handling the case where timestamp is 0 (config is set but no interactions yet).
// first tx where timestamp is 0 will enter `if (lastWithdrawalLimit_ == 0)` because lastWithdrawalLimit_ is not set yet.
// returning max withdrawal allowed, which is not exactly right but doesn't matter because the first interaction must be
// a deposit anyway. Important is that it would not revert.
// Note the first time a deposit brings the user supply amount to above the base withdrawal limit, the active limit
// is the fully expanded limit immediately.
// extract last set withdrawal limit
uint256 lastWithdrawalLimit_ = (userSupplyData_ >>
LiquiditySlotsLink.BITS_USER_SUPPLY_PREVIOUS_WITHDRAWAL_LIMIT) & X64;
lastWithdrawalLimit_ =
(lastWithdrawalLimit_ >> DEFAULT_EXPONENT_SIZE) <<
(lastWithdrawalLimit_ & DEFAULT_EXPONENT_MASK);
if (lastWithdrawalLimit_ == 0) {
// withdrawal limit is not activated. Max withdrawal allowed
return 0;
}
uint256 maxWithdrawableLimit_;
uint256 temp_;
unchecked {
// extract max withdrawable percent of user supply and
// calculate maximum withdrawable amount expandPercentage of user supply at full expansion duration elapsed
// e.g.: if 10% expandPercentage, meaning 10% is withdrawable after full expandDuration has elapsed.
// userSupply_ needs to be atleast 1e73 to overflow max limit of ~1e77 in uint256 (no token in existence where this is possible).
maxWithdrawableLimit_ =
(((userSupplyData_ >> LiquiditySlotsLink.BITS_USER_SUPPLY_EXPAND_PERCENT) & X14) * userSupply_) /
FOUR_DECIMALS;
// time elapsed since last withdrawal limit was set (in seconds)
// @dev last process timestamp is guaranteed to exist for withdrawal, as a supply must have happened before.
// last timestamp can not be > current timestamp
temp_ =
block.timestamp -
((userSupplyData_ >> LiquiditySlotsLink.BITS_USER_SUPPLY_LAST_UPDATE_TIMESTAMP) & X33);
}
// calculate withdrawable amount of expandPercent that is elapsed of expandDuration.
// e.g. if 60% of expandDuration has elapsed, then user should be able to withdraw 6% of user supply, down to 94%.
// Note: no explicit check for this needed, it is covered by setting minWithdrawalLimit_ if needed.
temp_ =
(maxWithdrawableLimit_ * temp_) /
// extract expand duration: After this, decrement won't happen (user can withdraw 100% of withdraw limit)
((userSupplyData_ >> LiquiditySlotsLink.BITS_USER_SUPPLY_EXPAND_DURATION) & X24); // expand duration can never be 0
// calculate expanded withdrawal limit: last withdrawal limit - withdrawable amount.
// Note: withdrawable amount here can grow bigger than userSupply if timeElapsed is a lot bigger than expandDuration,
// which would cause the subtraction `lastWithdrawalLimit_ - withdrawableAmount_` to revert. In that case, set 0
// which will cause minimum (fully expanded) withdrawal limit to be set in lines below.
unchecked {
// underflow explicitly checked & handled
currentWithdrawalLimit_ = lastWithdrawalLimit_ > temp_ ? lastWithdrawalLimit_ - temp_ : 0;
// calculate minimum withdrawal limit: minimum amount of user supply that must stay supplied at full expansion.
// subtraction can not underflow as maxWithdrawableLimit_ is a percentage amount (<=100%) of userSupply_
temp_ = userSupply_ - maxWithdrawableLimit_;
}
// if withdrawal limit is decreased below minimum then set minimum
// (e.g. when more than expandDuration time has elapsed)
if (temp_ > currentWithdrawalLimit_) {
currentWithdrawalLimit_ = temp_;
}
}
/// @dev calculates withdrawal limit after an operate execution:
/// amount of user supply that must stay supplied (not amount that can be withdrawn).
/// i.e. if user has supplied 100m and can withdraw 5M, this method returns the 95M, not the withdrawable amount 5M
/// @param userSupplyData_ user supply data packed uint256 from storage
/// @param userSupply_ current user supply amount already extracted from `userSupplyData_` and added / subtracted with the executed operate amount
/// @param newWithdrawalLimit_ current withdrawal limit updated for expansion since last interaction, result from `calcWithdrawalLimitBeforeOperate`
/// @return withdrawalLimit_ updated withdrawal limit that should be written to storage. returned value is in
/// raw for with interest mode, normal amount for interest free mode!
function calcWithdrawalLimitAfterOperate(
uint256 userSupplyData_,
uint256 userSupply_,
uint256 newWithdrawalLimit_
) internal pure returns (uint256) {
// temp_ => base withdrawal limit. below this, maximum withdrawals are allowed
uint256 temp_ = (userSupplyData_ >> LiquiditySlotsLink.BITS_USER_SUPPLY_BASE_WITHDRAWAL_LIMIT) & X18;
temp_ = (temp_ >> DEFAULT_EXPONENT_SIZE) << (temp_ & DEFAULT_EXPONENT_MASK);
// if user supply is below base limit then max withdrawals are allowed
if (userSupply_ < temp_) {
return 0;
}
// temp_ => withdrawal limit expandPercent (is in 1e2 decimals)
temp_ = (userSupplyData_ >> LiquiditySlotsLink.BITS_USER_SUPPLY_EXPAND_PERCENT) & X14;
unchecked {
// temp_ => minimum withdrawal limit: userSupply - max withdrawable limit (userSupply * expandPercent))
// userSupply_ needs to be atleast 1e73 to overflow max limit of ~1e77 in uint256 (no token in existence where this is possible).
// subtraction can not underflow as maxWithdrawableLimit_ is a percentage amount (<=100%) of userSupply_
temp_ = userSupply_ - ((userSupply_ * temp_) / FOUR_DECIMALS);
}
// if new (before operation) withdrawal limit is less than minimum limit then set minimum limit.
// e.g. can happen on new deposits. withdrawal limit is instantly fully expanded in a scenario where
// increased deposit amount outpaces withrawals.
if (temp_ > newWithdrawalLimit_) {
return temp_;
}
return newWithdrawalLimit_;
}
/// @dev calculates borrow limit before an operate execution:
/// total amount user borrow can reach (not borrowable amount in current operation).
/// i.e. if user has borrowed 50M and can still borrow 5M, this method returns the total 55M, not the borrowable amount 5M
/// @param userBorrowData_ user borrow data packed uint256 from storage
/// @param userBorrow_ current user borrow amount already extracted from `userBorrowData_`
/// @return currentBorrowLimit_ current borrow limit updated for expansion since last interaction. returned value is in
/// raw for with interest mode, normal amount for interest free mode!
function calcBorrowLimitBeforeOperate(
uint256 userBorrowData_,
uint256 userBorrow_
) internal view returns (uint256 currentBorrowLimit_) {
// @dev must support handling the case where timestamp is 0 (config is set but no interactions yet) -> base limit.
// first tx where timestamp is 0 will enter `if (maxExpandedBorrowLimit_ < baseBorrowLimit_)` because `userBorrow_` and thus
// `maxExpansionLimit_` and thus `maxExpandedBorrowLimit_` is 0 and `baseBorrowLimit_` can not be 0.
// temp_ = extract borrow expand percent (is in 1e2 decimals)
uint256 temp_ = (userBorrowData_ >> LiquiditySlotsLink.BITS_USER_BORROW_EXPAND_PERCENT) & X14;
uint256 maxExpansionLimit_;
uint256 maxExpandedBorrowLimit_;
unchecked {
// calculate max expansion limit: Max amount limit can expand to since last interaction
// userBorrow_ needs to be atleast 1e73 to overflow max limit of ~1e77 in uint256 (no token in existence where this is possible).
maxExpansionLimit_ = ((userBorrow_ * temp_) / FOUR_DECIMALS);
// calculate max borrow limit: Max point limit can increase to since last interaction
maxExpandedBorrowLimit_ = userBorrow_ + maxExpansionLimit_;
}
// currentBorrowLimit_ = extract base borrow limit
currentBorrowLimit_ = (userBorrowData_ >> LiquiditySlotsLink.BITS_USER_BORROW_BASE_BORROW_LIMIT) & X18;
currentBorrowLimit_ =
(currentBorrowLimit_ >> DEFAULT_EXPONENT_SIZE) <<
(currentBorrowLimit_ & DEFAULT_EXPONENT_MASK);
if (maxExpandedBorrowLimit_ < currentBorrowLimit_) {
return currentBorrowLimit_;
}
// time elapsed since last borrow limit was set (in seconds)
unchecked {
// temp_ = timeElapsed_ (last timestamp can not be > current timestamp)
temp_ =
block.timestamp -
((userBorrowData_ >> LiquiditySlotsLink.BITS_USER_BORROW_LAST_UPDATE_TIMESTAMP) & X33); // extract last update timestamp
}
// currentBorrowLimit_ = expandedBorrowableAmount + extract last set borrow limit
currentBorrowLimit_ =
// calculate borrow limit expansion since last interaction for `expandPercent` that is elapsed of `expandDuration`.
// divisor is extract expand duration (after this, full expansion to expandPercentage happened).
((maxExpansionLimit_ * temp_) /
((userBorrowData_ >> LiquiditySlotsLink.BITS_USER_BORROW_EXPAND_DURATION) & X24)) + // expand duration can never be 0
// extract last set borrow limit
BigMathMinified.fromBigNumber(
(userBorrowData_ >> LiquiditySlotsLink.BITS_USER_BORROW_PREVIOUS_BORROW_LIMIT) & X64,
DEFAULT_EXPONENT_SIZE,
DEFAULT_EXPONENT_MASK
);
// if timeElapsed is bigger than expandDuration, new borrow limit would be > max expansion,
// so set to `maxExpandedBorrowLimit_` in that case.
// also covers the case where last process timestamp = 0 (timeElapsed would simply be very big)
if (currentBorrowLimit_ > maxExpandedBorrowLimit_) {
currentBorrowLimit_ = maxExpandedBorrowLimit_;
}
// temp_ = extract hard max borrow limit. Above this user can never borrow (not expandable above)
temp_ = (userBorrowData_ >> LiquiditySlotsLink.BITS_USER_BORROW_MAX_BORROW_LIMIT) & X18;
temp_ = (temp_ >> DEFAULT_EXPONENT_SIZE) << (temp_ & DEFAULT_EXPONENT_MASK);
if (currentBorrowLimit_ > temp_) {
currentBorrowLimit_ = temp_;
}
}
/// @dev calculates borrow limit after an operate execution:
/// total amount user borrow can reach (not borrowable amount in current operation).
/// i.e. if user has borrowed 50M and can still borrow 5M, this method returns the total 55M, not the borrowable amount 5M
/// @param userBorrowData_ user borrow data packed uint256 from storage
/// @param userBorrow_ current user borrow amount already extracted from `userBorrowData_` and added / subtracted with the executed operate amount
/// @param newBorrowLimit_ current borrow limit updated for expansion since last interaction, result from `calcBorrowLimitBeforeOperate`
/// @return borrowLimit_ updated borrow limit that should be written to storage.
/// returned value is in raw for with interest mode, normal amount for interest free mode!
function calcBorrowLimitAfterOperate(
uint256 userBorrowData_,
uint256 userBorrow_,
uint256 newBorrowLimit_
) internal pure returns (uint256 borrowLimit_) {
// temp_ = extract borrow expand percent
uint256 temp_ = (userBorrowData_ >> LiquiditySlotsLink.BITS_USER_BORROW_EXPAND_PERCENT) & X14; // (is in 1e2 decimals)
unchecked {
// borrowLimit_ = calculate maximum borrow limit at full expansion.
// userBorrow_ needs to be at least 1e73 to overflow max limit of ~1e77 in uint256 (no token in existence where this is possible).
borrowLimit_ = userBorrow_ + ((userBorrow_ * temp_) / FOUR_DECIMALS);
}
// temp_ = extract base borrow limit
temp_ = (userBorrowData_ >> LiquiditySlotsLink.BITS_USER_BORROW_BASE_BORROW_LIMIT) & X18;
temp_ = (temp_ >> DEFAULT_EXPONENT_SIZE) << (temp_ & DEFAULT_EXPONENT_MASK);
if (borrowLimit_ < temp_) {
// below base limit, borrow limit is always base limit
return temp_;
}
// temp_ = extract hard max borrow limit. Above this user can never borrow (not expandable above)
temp_ = (userBorrowData_ >> LiquiditySlotsLink.BITS_USER_BORROW_MAX_BORROW_LIMIT) & X18;
temp_ = (temp_ >> DEFAULT_EXPONENT_SIZE) << (temp_ & DEFAULT_EXPONENT_MASK);
// make sure fully expanded borrow limit is not above hard max borrow limit
if (borrowLimit_ > temp_) {
borrowLimit_ = temp_;
}
// if new borrow limit (from before operate) is > max borrow limit, set max borrow limit.
// (e.g. on a repay shrinking instantly to fully expanded borrow limit from new borrow amount. shrinking is instant)
if (newBorrowLimit_ > borrowLimit_) {
return borrowLimit_;
}
return newBorrowLimit_;
}
///////////////////////////////////////////////////////////////////////////
////////// CALC RATES /////////
///////////////////////////////////////////////////////////////////////////
/// @dev Calculates new borrow rate from utilization for a token
/// @param rateData_ rate data packed uint256 from storage for the token
/// @param utilization_ totalBorrow / totalSupply. 1e4 = 100% utilization
/// @return rate_ rate for that particular token in 1e2 precision (e.g. 5% rate = 500)
function calcBorrowRateFromUtilization(uint256 rateData_, uint256 utilization_) internal returns (uint256 rate_) {
// extract rate version: 4 bits (0xF) starting from bit 0
uint256 rateVersion_ = (rateData_ & 0xF);
if (rateVersion_ == 1) {
rate_ = calcRateV1(rateData_, utilization_);
} else if (rateVersion_ == 2) {
rate_ = calcRateV2(rateData_, utilization_);
} else {
revert FluidLiquidityCalcsError(ErrorTypes.LiquidityCalcs__UnsupportedRateVersion);
}
if (rate_ > X16) {
// hard cap for borrow rate at maximum value 16 bits (65535) to make sure it does not overflow storage space.
// this is unlikely to ever happen if configs stay within expected levels.
rate_ = X16;
// emit event to more easily become aware
emit BorrowRateMaxCap();
}
}
/// @dev calculates the borrow rate based on utilization for rate data version 1 (with one kink) in 1e2 precision
/// @param rateData_ rate data packed uint256 from storage for the token
/// @param utilization_ in 1e2 (100% = 1e4)
/// @return rate_ rate in 1e2 precision
function calcRateV1(uint256 rateData_, uint256 utilization_) internal pure returns (uint256 rate_) {
/// For rate v1 (one kink) ------------------------------------------------------
/// Next 16 bits => 4 - 19 => Rate at utilization 0% (in 1e2: 100% = 10_000; 1% = 100 -> max value 65535)
/// Next 16 bits => 20- 35 => Utilization at kink1 (in 1e2: 100% = 10_000; 1% = 100 -> max value 65535)
/// Next 16 bits => 36- 51 => Rate at utilization kink1 (in 1e2: 100% = 10_000; 1% = 100 -> max value 65535)
/// Next 16 bits => 52- 67 => Rate at utilization 100% (in 1e2: 100% = 10_000; 1% = 100 -> max value 65535)
/// Last 188 bits => 68-255 => blank, might come in use in future
// y = mx + c.
// y is borrow rate
// x is utilization
// m = slope (m can also be negative for declining rates)
// c is constant (c can be negative)
uint256 y1_;
uint256 y2_;
uint256 x1_;
uint256 x2_;
// extract kink1: 16 bits (0xFFFF) starting from bit 20
// kink is in 1e2, same as utilization, so no conversion needed for direct comparison of the two
uint256 kink1_ = (rateData_ >> LiquiditySlotsLink.BITS_RATE_DATA_V1_UTILIZATION_AT_KINK) & X16;
if (utilization_ < kink1_) {
// if utilization is less than kink
y1_ = (rateData_ >> LiquiditySlotsLink.BITS_RATE_DATA_V1_RATE_AT_UTILIZATION_ZERO) & X16;
y2_ = (rateData_ >> LiquiditySlotsLink.BITS_RATE_DATA_V1_RATE_AT_UTILIZATION_KINK) & X16;
x1_ = 0; // 0%
x2_ = kink1_;
} else {
// else utilization is greater than kink
y1_ = (rateData_ >> LiquiditySlotsLink.BITS_RATE_DATA_V1_RATE_AT_UTILIZATION_KINK) & X16;
y2_ = (rateData_ >> LiquiditySlotsLink.BITS_RATE_DATA_V1_RATE_AT_UTILIZATION_MAX) & X16;
x1_ = kink1_;
x2_ = FOUR_DECIMALS; // 100%
}
int256 constant_;
int256 slope_;
unchecked {
// calculating slope with twelve decimal precision. m = (y2 - y1) / (x2 - x1).
// utilization of x2 can not be <= utilization of x1 (so no underflow or 0 divisor)
// y is in 1e2 so can not overflow when multiplied with TWELVE_DECIMALS
slope_ = (int256(y2_ - y1_) * int256(TWELVE_DECIMALS)) / int256((x2_ - x1_));
// calculating constant at 12 decimal precision. slope is already in 12 decimal hence only multiple with y1. c = y - mx.
// maximum y1_ value is 65535. 65535 * 1e12 can not overflow int256
// maximum slope is 65535 - 0 * TWELVE_DECIMALS / 1 = 65535 * 1e12;
// maximum x1_ is 100% (9_999 actually) => slope_ * x1_ can not overflow int256
// subtraction most extreme case would be 0 - max value slope_ * x1_ => can not underflow int256
constant_ = int256(y1_ * TWELVE_DECIMALS) - (slope_ * int256(x1_));
// calculating new borrow rate
// - slope_ max value is 65535 * 1e12,
// - utilization max value is let's say 500% (extreme case where borrow rate increases borrow amount without new supply)
// - constant max value is 65535 * 1e12
// so max values are 65535 * 1e12 * 50_000 + 65535 * 1e12 -> 3.2768*10^21, which easily fits int256
// divisor TWELVE_DECIMALS can not be 0
slope_ = (slope_ * int256(utilization_)) + constant_; // reusing `slope_` as variable for gas savings
if (slope_ < 0) {
revert FluidLiquidityCalcsError(ErrorTypes.LiquidityCalcs__BorrowRateNegative);
}
rate_ = uint256(slope_) / TWELVE_DECIMALS;
}
}
/// @dev calculates the borrow rate based on utilization for rate data version 2 (with two kinks) in 1e4 precision
/// @param rateData_ rate data packed uint256 from storage for the token
/// @param utilization_ in 1e2 (100% = 1e4)
/// @return rate_ rate in 1e4 precision
function calcRateV2(uint256 rateData_, uint256 utilization_) internal pure returns (uint256 rate_) {
/// For rate v2 (two kinks) -----------------------------------------------------
/// Next 16 bits => 4 - 19 => Rate at utilization 0% (in 1e2: 100% = 10_000; 1% = 100 -> max value 65535)
/// Next 16 bits => 20- 35 => Utilization at kink1 (in 1e2: 100% = 10_000; 1% = 100 -> max value 65535)
/// Next 16 bits => 36- 51 => Rate at utilization kink1 (in 1e2: 100% = 10_000; 1% = 100 -> max value 65535)
/// Next 16 bits => 52- 67 => Utilization at kink2 (in 1e2: 100% = 10_000; 1% = 100 -> max value 65535)
/// Next 16 bits => 68- 83 => Rate at utilization kink2 (in 1e2: 100% = 10_000; 1% = 100 -> max value 65535)
/// Next 16 bits => 84- 99 => Rate at utilization 100% (in 1e2: 100% = 10_000; 1% = 100 -> max value 65535)
/// Last 156 bits => 100-255 => blank, might come in use in future
// y = mx + c.
// y is borrow rate
// x is utilization
// m = slope (m can also be negative for declining rates)
// c is constant (c can be negative)
uint256 y1_;
uint256 y2_;
uint256 x1_;
uint256 x2_;
// extract kink1: 16 bits (0xFFFF) starting from bit 20
// kink is in 1e2, same as utilization, so no conversion needed for direct comparison of the two
uint256 kink1_ = (rateData_ >> LiquiditySlotsLink.BITS_RATE_DATA_V2_UTILIZATION_AT_KINK1) & X16;
if (utilization_ < kink1_) {
// if utilization is less than kink1
y1_ = (rateData_ >> LiquiditySlotsLink.BITS_RATE_DATA_V2_RATE_AT_UTILIZATION_ZERO) & X16;
y2_ = (rateData_ >> LiquiditySlotsLink.BITS_RATE_DATA_V2_RATE_AT_UTILIZATION_KINK1) & X16;
x1_ = 0; // 0%
x2_ = kink1_;
} else {
// extract kink2: 16 bits (0xFFFF) starting from bit 52
uint256 kink2_ = (rateData_ >> LiquiditySlotsLink.BITS_RATE_DATA_V2_UTILIZATION_AT_KINK2) & X16;
if (utilization_ < kink2_) {
// if utilization is less than kink2
y1_ = (rateData_ >> LiquiditySlotsLink.BITS_RATE_DATA_V2_RATE_AT_UTILIZATION_KINK1) & X16;
y2_ = (rateData_ >> LiquiditySlotsLink.BITS_RATE_DATA_V2_RATE_AT_UTILIZATION_KINK2) & X16;
x1_ = kink1_;
x2_ = kink2_;
} else {
// else utilization is greater than kink2
y1_ = (rateData_ >> LiquiditySlotsLink.BITS_RATE_DATA_V2_RATE_AT_UTILIZATION_KINK2) & X16;
y2_ = (rateData_ >> LiquiditySlotsLink.BITS_RATE_DATA_V2_RATE_AT_UTILIZATION_MAX) & X16;
x1_ = kink2_;
x2_ = FOUR_DECIMALS;
}
}
int256 constant_;
int256 slope_;
unchecked {
// calculating slope with twelve decimal precision. m = (y2 - y1) / (x2 - x1).
// utilization of x2 can not be <= utilization of x1 (so no underflow or 0 divisor)
// y is in 1e2 so can not overflow when multiplied with TWELVE_DECIMALS
slope_ = (int256(y2_ - y1_) * int256(TWELVE_DECIMALS)) / int256((x2_ - x1_));
// calculating constant at 12 decimal precision. slope is already in 12 decimal hence only multiple with y1. c = y - mx.
// maximum y1_ value is 65535. 65535 * 1e12 can not overflow int256
// maximum slope is 65535 - 0 * TWELVE_DECIMALS / 1 = 65535 * 1e12;
// maximum x1_ is 100% (9_999 actually) => slope_ * x1_ can not overflow int256
// subtraction most extreme case would be 0 - max value slope_ * x1_ => can not underflow int256
constant_ = int256(y1_ * TWELVE_DECIMALS) - (slope_ * int256(x1_));
// calculating new borrow rate
// - slope_ max value is 65535 * 1e12,
// - utilization max value is let's say 500% (extreme case where borrow rate increases borrow amount without new supply)
// - constant max value is 65535 * 1e12
// so max values are 65535 * 1e12 * 50_000 + 65535 * 1e12 -> 3.2768*10^21, which easily fits int256
// divisor TWELVE_DECIMALS can not be 0
slope_ = (slope_ * int256(utilization_)) + constant_; // reusing `slope_` as variable for gas savings
if (slope_ < 0) {
revert FluidLiquidityCalcsError(ErrorTypes.LiquidityCalcs__BorrowRateNegative);
}
rate_ = uint256(slope_) / TWELVE_DECIMALS;
}
}
/// @dev reads the total supply out of Liquidity packed storage `totalAmounts_` for `supplyExchangePrice_`
function getTotalSupply(
uint256 totalAmounts_,
uint256 supplyExchangePrice_
) internal pure returns (uint256 totalSupply_) {
// totalSupply_ => supplyInterestFree
totalSupply_ = (totalAmounts_ >> LiquiditySlotsLink.BITS_TOTAL_AMOUNTS_SUPPLY_INTEREST_FREE) & X64;
totalSupply_ = (totalSupply_ >> DEFAULT_EXPONENT_SIZE) << (totalSupply_ & DEFAULT_EXPONENT_MASK);
uint256 totalSupplyRaw_ = totalAmounts_ & X64; // no shifting as supplyRaw is first 64 bits
totalSupplyRaw_ = (totalSupplyRaw_ >> DEFAULT_EXPONENT_SIZE) << (totalSupplyRaw_ & DEFAULT_EXPONENT_MASK);
// totalSupply = supplyInterestFree + supplyRawInterest normalized from raw
totalSupply_ += ((totalSupplyRaw_ * supplyExchangePrice_) / EXCHANGE_PRICES_PRECISION);
}
/// @dev reads the total borrow out of Liquidity packed storage `totalAmounts_` for `borrowExchangePrice_`
function getTotalBorrow(
uint256 totalAmounts_,
uint256 borrowExchangePrice_
) internal pure returns (uint256 totalBorrow_) {
// totalBorrow_ => borrowInterestFree
// no & mask needed for borrow interest free as it occupies the last bits in the storage slot
totalBorrow_ = (totalAmounts_ >> LiquiditySlotsLink.BITS_TOTAL_AMOUNTS_BORROW_INTEREST_FREE);
totalBorrow_ = (totalBorrow_ >> DEFAULT_EXPONENT_SIZE) << (totalBorrow_ & DEFAULT_EXPONENT_MASK);
uint256 totalBorrowRaw_ = (totalAmounts_ >> LiquiditySlotsLink.BITS_TOTAL_AMOUNTS_BORROW_WITH_INTEREST) & X64;
totalBorrowRaw_ = (totalBorrowRaw_ >> DEFAULT_EXPONENT_SIZE) << (totalBorrowRaw_ & DEFAULT_EXPONENT_MASK);
// totalBorrow = borrowInterestFree + borrowRawInterest normalized from raw
totalBorrow_ += ((totalBorrowRaw_ * borrowExchangePrice_) / EXCHANGE_PRICES_PRECISION);
}
}
// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.21;
/// @notice library that helps in reading / working with storage slot data of Fluid Liquidity.
/// @dev as all data for Fluid Liquidity is internal, any data must be fetched directly through manual
/// slot reading through this library or, if gas usage is less important, through the FluidLiquidityResolver.
library LiquiditySlotsLink {
/// @dev storage slot for status at Liquidity
uint256 internal constant LIQUIDITY_STATUS_SLOT = 1;
/// @dev storage slot for auths mapping at Liquidity
uint256 internal constant LIQUIDITY_AUTHS_MAPPING_SLOT = 2;
/// @dev storage slot for guardians mapping at Liquidity
uint256 internal constant LIQUIDITY_GUARDIANS_MAPPING_SLOT = 3;
/// @dev storage slot for user class mapping at Liquidity
uint256 internal constant LIQUIDITY_USER_CLASS_MAPPING_SLOT = 4;
/// @dev storage slot for exchangePricesAndConfig mapping at Liquidity
uint256 internal constant LIQUIDITY_EXCHANGE_PRICES_MAPPING_SLOT = 5;
/// @dev storage slot for rateData mapping at Liquidity
uint256 internal constant LIQUIDITY_RATE_DATA_MAPPING_SLOT = 6;
/// @dev storage slot for totalAmounts mapping at Liquidity
uint256 internal constant LIQUIDITY_TOTAL_AMOUNTS_MAPPING_SLOT = 7;
/// @dev storage slot for user supply double mapping at Liquidity
uint256 internal constant LIQUIDITY_USER_SUPPLY_DOUBLE_MAPPING_SLOT = 8;
/// @dev storage slot for user borrow double mapping at Liquidity
uint256 internal constant LIQUIDITY_USER_BORROW_DOUBLE_MAPPING_SLOT = 9;
/// @dev storage slot for listed tokens array at Liquidity
uint256 internal constant LIQUIDITY_LISTED_TOKENS_ARRAY_SLOT = 10;
/// @dev storage slot for listed tokens array at Liquidity
uint256 internal constant LIQUIDITY_CONFIGS2_MAPPING_SLOT = 11;
// --------------------------------
// @dev stacked uint256 storage slots bits position data for each:
// ExchangePricesAndConfig
uint256 internal constant BITS_EXCHANGE_PRICES_BORROW_RATE = 0;
uint256 internal constant BITS_EXCHANGE_PRICES_FEE = 16;
uint256 internal constant BITS_EXCHANGE_PRICES_UTILIZATION = 30;
uint256 internal constant BITS_EXCHANGE_PRICES_UPDATE_THRESHOLD = 44;
uint256 internal constant BITS_EXCHANGE_PRICES_LAST_TIMESTAMP = 58;
uint256 internal constant BITS_EXCHANGE_PRICES_SUPPLY_EXCHANGE_PRICE = 91;
uint256 internal constant BITS_EXCHANGE_PRICES_BORROW_EXCHANGE_PRICE = 155;
uint256 internal constant BITS_EXCHANGE_PRICES_SUPPLY_RATIO = 219;
uint256 internal constant BITS_EXCHANGE_PRICES_BORROW_RATIO = 234;
uint256 internal constant BITS_EXCHANGE_PRICES_USES_CONFIGS2 = 249;
// RateData:
uint256 internal constant BITS_RATE_DATA_VERSION = 0;
// RateData: V1
uint256 internal constant BITS_RATE_DATA_V1_RATE_AT_UTILIZATION_ZERO = 4;
uint256 internal constant BITS_RATE_DATA_V1_UTILIZATION_AT_KINK = 20;
uint256 internal constant BITS_RATE_DATA_V1_RATE_AT_UTILIZATION_KINK = 36;
uint256 internal constant BITS_RATE_DATA_V1_RATE_AT_UTILIZATION_MAX = 52;
// RateData: V2
uint256 internal constant BITS_RATE_DATA_V2_RATE_AT_UTILIZATION_ZERO = 4;
uint256 internal constant BITS_RATE_DATA_V2_UTILIZATION_AT_KINK1 = 20;
uint256 internal constant BITS_RATE_DATA_V2_RATE_AT_UTILIZATION_KINK1 = 36;
uint256 internal constant BITS_RATE_DATA_V2_UTILIZATION_AT_KINK2 = 52;
uint256 internal constant BITS_RATE_DATA_V2_RATE_AT_UTILIZATION_KINK2 = 68;
uint256 internal constant BITS_RATE_DATA_V2_RATE_AT_UTILIZATION_MAX = 84;
// TotalAmounts
uint256 internal constant BITS_TOTAL_AMOUNTS_SUPPLY_WITH_INTEREST = 0;
uint256 internal constant BITS_TOTAL_AMOUNTS_SUPPLY_INTEREST_FREE = 64;
uint256 internal constant BITS_TOTAL_AMOUNTS_BORROW_WITH_INTEREST = 128;
uint256 internal constant BITS_TOTAL_AMOUNTS_BORROW_INTEREST_FREE = 192;
// UserSupplyData
uint256 internal constant BITS_USER_SUPPLY_MODE = 0;
uint256 internal constant BITS_USER_SUPPLY_AMOUNT = 1;
uint256 internal constant BITS_USER_SUPPLY_PREVIOUS_WITHDRAWAL_LIMIT = 65;
uint256 internal constant BITS_USER_SUPPLY_LAST_UPDATE_TIMESTAMP = 129;
uint256 internal constant BITS_USER_SUPPLY_EXPAND_PERCENT = 162;
uint256 internal constant BITS_USER_SUPPLY_EXPAND_DURATION = 176;
uint256 internal constant BITS_USER_SUPPLY_BASE_WITHDRAWAL_LIMIT = 200;
uint256 internal constant BITS_USER_SUPPLY_IS_PAUSED = 255;
// UserBorrowData
uint256 internal constant BITS_USER_BORROW_MODE = 0;
uint256 internal constant BITS_USER_BORROW_AMOUNT = 1;
uint256 internal constant BITS_USER_BORROW_PREVIOUS_BORROW_LIMIT = 65;
uint256 internal constant BITS_USER_BORROW_LAST_UPDATE_TIMESTAMP = 129;
uint256 internal constant BITS_USER_BORROW_EXPAND_PERCENT = 162;
uint256 internal constant BITS_USER_BORROW_EXPAND_DURATION = 176;
uint256 internal constant BITS_USER_BORROW_BASE_BORROW_LIMIT = 200;
uint256 internal constant BITS_USER_BORROW_MAX_BORROW_LIMIT = 218;
uint256 internal constant BITS_USER_BORROW_IS_PAUSED = 255;
// Configs2
uint256 internal constant BITS_CONFIGS2_MAX_UTILIZATION = 0;
// --------------------------------
/// @notice Calculating the slot ID for Liquidity contract for single mapping at `slot_` for `key_`
function calculateMappingStorageSlot(uint256 slot_, address key_) internal pure returns (bytes32) {
return keccak256(abi.encode(key_, slot_));
}
/// @notice Calculating the slot ID for Liquidity contract for double mapping at `slot_` for `key1_` and `key2_`
function calculateDoubleMappingStorageSlot(
uint256 slot_,
address key1_,
address key2_
) internal pure returns (bytes32) {
bytes32 intermediateSlot_ = keccak256(abi.encode(key1_, slot_));
return keccak256(abi.encode(key2_, intermediateSlot_));
}
}
// SPDX-License-Identifier: MIT OR Apache-2.0
pragma solidity 0.8.21;
import { LibsErrorTypes as ErrorTypes } from "./errorTypes.sol";
/// @notice provides minimalistic methods for safe transfers, e.g. ERC20 safeTransferFrom
library SafeTransfer {
uint256 internal constant MAX_NATIVE_TRANSFER_GAS = 20000; // pass max. 20k gas for native transfers
error FluidSafeTransferError(uint256 errorId_);
/// @dev Transfer `amount_` of `token_` from `from_` to `to_`, spending the approval given by `from_` to the
/// calling contract. If `token_` returns no value, non-reverting calls are assumed to be successful.
/// Minimally modified from Solmate SafeTransferLib (address as input param for token, Custom Error):
/// https://github.com/transmissions11/solmate/blob/50e15bb566f98b7174da9b0066126a4c3e75e0fd/src/utils/SafeTransferLib.sol#L31-L63
function safeTransferFrom(address token_, address from_, address to_, uint256 amount_) internal {
bool success_;
/// @solidity memory-safe-assembly
assembly {
// Get a pointer to some free memory.
let freeMemoryPointer := mload(0x40)
// Write the abi-encoded calldata into memory, beginning with the function selector.
mstore(freeMemoryPointer, 0x23b872dd00000000000000000000000000000000000000000000000000000000)
mstore(add(freeMemoryPointer, 4), and(from_, 0xffffffffffffffffffffffffffffffffffffffff)) // Append and mask the "from_" argument.
mstore(add(freeMemoryPointer, 36), and(to_, 0xffffffffffffffffffffffffffffffffffffffff)) // Append and mask the "to_" argument.
mstore(add(freeMemoryPointer, 68), amount_) // Append the "amount_" argument. Masking not required as it's a full 32 byte type.
success_ := and(
// Set success to whether the call reverted, if not we check it either
// returned exactly 1 (can't just be non-zero data), or had no return data.
or(and(eq(mload(0), 1), gt(returndatasize(), 31)), iszero(returndatasize())),
// We use 100 because the length of our calldata totals up like so: 4 + 32 * 3.
// We use 0 and 32 to copy up to 32 bytes of return data into the scratch space.
// Counterintuitively, this call must be positioned second to the or() call in the
// surrounding and() call or else returndatasize() will be zero during the computation.
call(gas(), token_, 0, freeMemoryPointer, 100, 0, 32)
)
}
if (!success_) {
revert FluidSafeTransferError(ErrorTypes.SafeTransfer__TransferFromFailed);
}
}
/// @dev Transfer `amount_` of `token_` to `to_`.
/// If `token_` returns no value, non-reverting calls are assumed to be successful.
/// Minimally modified from Solmate SafeTransferLib (address as input param for token, Custom Error):
/// https://github.com/transmissions11/solmate/blob/50e15bb566f98b7174da9b0066126a4c3e75e0fd/src/utils/SafeTransferLib.sol#L65-L95
function safeTransfer(address token_, address to_, uint256 amount_) internal {
bool success_;
/// @solidity memory-safe-assembly
assembly {
// Get a pointer to some free memory.
let freeMemoryPointer := mload(0x40)
// Write the abi-encoded calldata into memory, beginning with the function selector.
mstore(freeMemoryPointer, 0xa9059cbb00000000000000000000000000000000000000000000000000000000)
mstore(add(freeMemoryPointer, 4), and(to_, 0xffffffffffffffffffffffffffffffffffffffff)) // Append and mask the "to_" argument.
mstore(add(freeMemoryPointer, 36), amount_) // Append the "amount_" argument. Masking not required as it's a full 32 byte type.
success_ := and(
// Set success to whether the call reverted, if not we check it either
// returned exactly 1 (can't just be non-zero data), or had no return data.
or(and(eq(mload(0), 1), gt(returndatasize(), 31)), iszero(returndatasize())),
// We use 68 because the length of our calldata totals up like so: 4 + 32 * 2.
// We use 0 and 32 to copy up to 32 bytes of return data into the scratch space.
// Counterintuitively, this call must be positioned second to the or() call in the
// surrounding and() call or else returndatasize() will be zero during the computation.
call(gas(), token_, 0, freeMemoryPointer, 68, 0, 32)
)
}
if (!success_) {
revert FluidSafeTransferError(ErrorTypes.SafeTransfer__TransferFailed);
}
}
/// @dev Transfer `amount_` of ` native token to `to_`.
/// Minimally modified from Solmate SafeTransferLib (Custom Error):
/// https://github.com/transmissions11/solmate/blob/50e15bb566f98b7174da9b0066126a4c3e75e0fd/src/utils/SafeTransferLib.sol#L15-L25
function safeTransferNative(address to_, uint256 amount_) internal {
bool success_;
/// @solidity memory-safe-assembly
assembly {
// Transfer the ETH and store if it succeeded or not. Pass limited gas
success_ := call(MAX_NATIVE_TRANSFER_GAS, to_, amount_, 0, 0, 0, 0)
}
if (!success_) {
revert FluidSafeTransferError(ErrorTypes.SafeTransfer__TransferFailed);
}
}
}
// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.21;
/// @title library that calculates number "tick" and "ratioX96" from this: ratioX96 = (1.0015^tick) * 2^96
/// @notice this library is used in Fluid Vault protocol for optimiziation.
/// @dev "tick" supports between -32767 and 32767. "ratioX96" supports between 37075072 and 169307877264527972847801929085841449095838922544595
library TickMath {
/// The minimum tick that can be passed in getRatioAtTick. 1.0015**-32767
int24 internal constant MIN_TICK = -32767;
/// The maximum tick that can be passed in getRatioAtTick. 1.0015**32767
int24 internal constant MAX_TICK = 32767;
uint256 internal constant FACTOR00 = 0x100000000000000000000000000000000;
uint256 internal constant FACTOR01 = 0xff9dd7de423466c20352b1246ce4856f; // 2^128/1.0015**1 = 339772707859149738855091969477551883631
uint256 internal constant FACTOR02 = 0xff3bd55f4488ad277531fa1c725a66d0; // 2^128/1.0015**2 = 339263812140938331358054887146831636176
uint256 internal constant FACTOR03 = 0xfe78410fd6498b73cb96a6917f853259; // 2^128/1.0015**4 = 338248306163758188337119769319392490073
uint256 internal constant FACTOR04 = 0xfcf2d9987c9be178ad5bfeffaa123273; // 2^128/1.0015**8 = 336226404141693512316971918999264834163
uint256 internal constant FACTOR05 = 0xf9ef02c4529258b057769680fc6601b3; // 2^128/1.0015**16 = 332218786018727629051611634067491389875
uint256 internal constant FACTOR06 = 0xf402d288133a85a17784a411f7aba082; // 2^128/1.0015**32 = 324346285652234375371948336458280706178
uint256 internal constant FACTOR07 = 0xe895615b5beb6386553757b0352bda90; // 2^128/1.0015**64 = 309156521885964218294057947947195947664
uint256 internal constant FACTOR08 = 0xd34f17a00ffa00a8309940a15930391a; // 2^128/1.0015**128 = 280877777739312896540849703637713172762
uint256 internal constant FACTOR09 = 0xae6b7961714e20548d88ea5123f9a0ff; // 2^128/1.0015**256 = 231843708922198649176471782639349113087
uint256 internal constant FACTOR10 = 0x76d6461f27082d74e0feed3b388c0ca1; // 2^128/1.0015**512 = 157961477267171621126394973980180876449
uint256 internal constant FACTOR11 = 0x372a3bfe0745d8b6b19d985d9a8b85bb; // 2^128/1.0015**1024 = 73326833024599564193373530205717235131
uint256 internal constant FACTOR12 = 0x0be32cbee48979763cf7247dd7bb539d; // 2^128/1.0015**2048 = 15801066890623697521348224657638773661
uint256 internal constant FACTOR13 = 0x8d4f70c9ff4924dac37612d1e2921e; // 2^128/1.0015**4096 = 733725103481409245883800626999235102
uint256 internal constant FACTOR14 = 0x4e009ae5519380809a02ca7aec77; // 2^128/1.0015**8192 = 1582075887005588088019997442108535
uint256 internal constant FACTOR15 = 0x17c45e641b6e95dee056ff10; // 2^128/1.0015**16384 = 7355550435635883087458926352
/// The minimum value that can be returned from getRatioAtTick. Equivalent to getRatioAtTick(MIN_TICK). ~ Equivalent to `(1 << 96) * (1.0015**-32767)`
uint256 internal constant MIN_RATIOX96 = 37075072;
/// The maximum value that can be returned from getRatioAtTick. Equivalent to getRatioAtTick(MAX_TICK).
/// ~ Equivalent to `(1 << 96) * (1.0015**32767)`, rounding etc. leading to minor difference
uint256 internal constant MAX_RATIOX96 = 169307877264527972847801929085841449095838922544595;
uint256 internal constant ZERO_TICK_SCALED_RATIO = 0x1000000000000000000000000; // 1 << 96 // 79228162514264337593543950336
uint256 internal constant _1E26 = 1e26;
/// @notice ratioX96 = (1.0015^tick) * 2^96
/// @dev Throws if |tick| > max tick
/// @param tick The input tick for the above formula
/// @return ratioX96 ratio = (debt amount/collateral amount)
function getRatioAtTick(int tick) internal pure returns (uint256 ratioX96) {
assembly {
let absTick_ := sub(xor(tick, sar(255, tick)), sar(255, tick))
if gt(absTick_, MAX_TICK) {
revert(0, 0)
}
let factor_ := FACTOR00
if and(absTick_, 0x1) {
factor_ := FACTOR01
}
if and(absTick_, 0x2) {
factor_ := shr(128, mul(factor_, FACTOR02))
}
if and(absTick_, 0x4) {
factor_ := shr(128, mul(factor_, FACTOR03))
}
if and(absTick_, 0x8) {
factor_ := shr(128, mul(factor_, FACTOR04))
}
if and(absTick_, 0x10) {
factor_ := shr(128, mul(factor_, FACTOR05))
}
if and(absTick_, 0x20) {
factor_ := shr(128, mul(factor_, FACTOR06))
}
if and(absTick_, 0x40) {
factor_ := shr(128, mul(factor_, FACTOR07))
}
if and(absTick_, 0x80) {
factor_ := shr(128, mul(factor_, FACTOR08))
}
if and(absTick_, 0x100) {
factor_ := shr(128, mul(factor_, FACTOR09))
}
if and(absTick_, 0x200) {
factor_ := shr(128, mul(factor_, FACTOR10))
}
if and(absTick_, 0x400) {
factor_ := shr(128, mul(factor_, FACTOR11))
}
if and(absTick_, 0x800) {
factor_ := shr(128, mul(factor_, FACTOR12))
}
if and(absTick_, 0x1000) {
factor_ := shr(128, mul(factor_, FACTOR13))
}
if and(absTick_, 0x2000) {
factor_ := shr(128, mul(factor_, FACTOR14))
}
if and(absTick_, 0x4000) {
factor_ := shr(128, mul(factor_, FACTOR15))
}
let precision_ := 0
if iszero(and(tick, 0x8000000000000000000000000000000000000000000000000000000000000000)) {
factor_ := div(0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff, factor_)
// we round up in the division so getTickAtRatio of the output price is always consistent
if mod(factor_, 0x100000000) {
precision_ := 1
}
}
ratioX96 := add(shr(32, factor_), precision_)
}
}
/// @notice ratioX96 = (1.0015^tick) * 2^96
/// @dev Throws if ratioX96 > max ratio || ratioX96 < min ratio
/// @param ratioX96 The input ratio; ratio = (debt amount/collateral amount)
/// @return tick The output tick for the above formula. Returns in round down form. if tick is 123.23 then 123, if tick is -123.23 then returns -124
/// @return perfectRatioX96 perfect ratio for the above tick
function getTickAtRatio(uint256 ratioX96) internal pure returns (int tick, uint perfectRatioX96) {
assembly {
if or(gt(ratioX96, MAX_RATIOX96), lt(ratioX96, MIN_RATIOX96)) {
revert(0, 0)
}
let cond := lt(ratioX96, ZERO_TICK_SCALED_RATIO)
let factor_
if iszero(cond) {
// if ratioX96 >= ZERO_TICK_SCALED_RATIO
factor_ := div(mul(ratioX96, _1E26), ZERO_TICK_SCALED_RATIO)
}
if cond {
// ratioX96 < ZERO_TICK_SCALED_RATIO
factor_ := div(mul(ZERO_TICK_SCALED_RATIO, _1E26), ratioX96)
}
// put in https://www.wolframalpha.com/ whole equation: (1.0015^tick) * 2^96 * 10^26 / 79228162514264337593543950336
// for tick = 16384
// ratioX96 = (1.0015^16384) * 2^96 = 3665252098134783297721995888537077351735
// 3665252098134783297721995888537077351735 * 10^26 / 79228162514264337593543950336 =
// 4626198540796508716348404308345255985.06131964639489434655721
if iszero(lt(factor_, 4626198540796508716348404308345255985)) {
tick := or(tick, 0x4000)
factor_ := div(mul(factor_, _1E26), 4626198540796508716348404308345255985)
}
// for tick = 8192
// ratioX96 = (1.0015^8192) * 2^96 = 17040868196391020479062776466509865
// 17040868196391020479062776466509865 * 10^26 / 79228162514264337593543950336 =
// 21508599537851153911767490449162.3037648642153898377655505172
if iszero(lt(factor_, 21508599537851153911767490449162)) {
tick := or(tick, 0x2000)
factor_ := div(mul(factor_, _1E26), 21508599537851153911767490449162)
}
// for tick = 4096
// ratioX96 = (1.0015^4096) * 2^96 = 36743933851015821532611831851150
// 36743933851015821532611831851150 * 10^26 / 79228162514264337593543950336 =
// 46377364670549310883002866648.9777607649742626173648716941385
if iszero(lt(factor_, 46377364670549310883002866649)) {
tick := or(tick, 0x1000)
factor_ := div(mul(factor_, _1E26), 46377364670549310883002866649)
}
// for tick = 2048
// ratioX96 = (1.0015^2048) * 2^96 = 1706210527034005899209104452335
// 1706210527034005899209104452335 * 10^26 / 79228162514264337593543950336 =
// 2153540449365864845468344760.06357108484096046743300420319322
if iszero(lt(factor_, 2153540449365864845468344760)) {
tick := or(tick, 0x800)
factor_ := div(mul(factor_, _1E26), 2153540449365864845468344760)
}
// for tick = 1024
// ratioX96 = (1.0015^1024) * 2^96 = 367668226692760093024536487236
// 367668226692760093024536487236 * 10^26 / 79228162514264337593543950336 =
// 464062544207767844008185024.950588990554136265212906454481127
if iszero(lt(factor_, 464062544207767844008185025)) {
tick := or(tick, 0x400)
factor_ := div(mul(factor_, _1E26), 464062544207767844008185025)
}
// for tick = 512
// ratioX96 = (1.0015^512) * 2^96 = 170674186729409605620119663668
// 170674186729409605620119663668 * 10^26 / 79228162514264337593543950336 =
// 215421109505955298802281577.031879604792139232258508172947569
if iszero(lt(factor_, 215421109505955298802281577)) {
tick := or(tick, 0x200)
factor_ := div(mul(factor_, _1E26), 215421109505955298802281577)
}
// for tick = 256
// ratioX96 = (1.0015^256) * 2^96 = 116285004205991934861656513301
// 116285004205991934861656513301 * 10^26 / 79228162514264337593543950336 =
// 146772309890508740607270614.667650899656438875541505058062410
if iszero(lt(factor_, 146772309890508740607270615)) {
tick := or(tick, 0x100)
factor_ := div(mul(factor_, _1E26), 146772309890508740607270615)
}
// for tick = 128
// ratioX96 = (1.0015^128) * 2^96 = 95984619659632141743747099590
// 95984619659632141743747099590 * 10^26 / 79228162514264337593543950336 =
// 121149622323187099817270416.157248837742741760456796835775887
if iszero(lt(factor_, 121149622323187099817270416)) {
tick := or(tick, 0x80)
factor_ := div(mul(factor_, _1E26), 121149622323187099817270416)
}
// for tick = 64
// ratioX96 = (1.0015^64) * 2^96 = 87204845308406958006717891124
// 87204845308406958006717891124 * 10^26 / 79228162514264337593543950336 =
// 110067989135437147685980801.568068573422377364214113968609839
if iszero(lt(factor_, 110067989135437147685980801)) {
tick := or(tick, 0x40)
factor_ := div(mul(factor_, _1E26), 110067989135437147685980801)
}
// for tick = 32
// ratioX96 = (1.0015^32) * 2^96 = 83120873769022354029916374475
// 83120873769022354029916374475 * 10^26 / 79228162514264337593543950336 =
// 104913292358707887270979599.831816586773651266562785765558183
if iszero(lt(factor_, 104913292358707887270979600)) {
tick := or(tick, 0x20)
factor_ := div(mul(factor_, _1E26), 104913292358707887270979600)
}
// for tick = 16
// ratioX96 = (1.0015^16) * 2^96 = 81151180492336368327184716176
// 81151180492336368327184716176 * 10^26 / 79228162514264337593543950336 =
// 102427189924701091191840927.762844039579442328381455567932128
if iszero(lt(factor_, 102427189924701091191840928)) {
tick := or(tick, 0x10)
factor_ := div(mul(factor_, _1E26), 102427189924701091191840928)
}
// for tick = 8
// ratioX96 = (1.0015^8) * 2^96 = 80183906840906820640659903620
// 80183906840906820640659903620 * 10^26 / 79228162514264337593543950336 =
// 101206318935480056907421312.890625
if iszero(lt(factor_, 101206318935480056907421313)) {
tick := or(tick, 0x8)
factor_ := div(mul(factor_, _1E26), 101206318935480056907421313)
}
// for tick = 4
// ratioX96 = (1.0015^4) * 2^96 = 79704602139525152702959747603
// 79704602139525152702959747603 * 10^26 / 79228162514264337593543950336 =
// 100601351350506250000000000
if iszero(lt(factor_, 100601351350506250000000000)) {
tick := or(tick, 0x4)
factor_ := div(mul(factor_, _1E26), 100601351350506250000000000)
}
// for tick = 2
// ratioX96 = (1.0015^2) * 2^96 = 79466025265172787701084167660
// 79466025265172787701084167660 * 10^26 / 79228162514264337593543950336 =
// 100300225000000000000000000
if iszero(lt(factor_, 100300225000000000000000000)) {
tick := or(tick, 0x2)
factor_ := div(mul(factor_, _1E26), 100300225000000000000000000)
}
// for tick = 1
// ratioX96 = (1.0015^1) * 2^96 = 79347004758035734099934266261
// 79347004758035734099934266261 * 10^26 / 79228162514264337593543950336 =
// 100150000000000000000000000
if iszero(lt(factor_, 100150000000000000000000000)) {
tick := or(tick, 0x1)
factor_ := div(mul(factor_, _1E26), 100150000000000000000000000)
}
if iszero(cond) {
// if ratioX96 >= ZERO_TICK_SCALED_RATIO
perfectRatioX96 := div(mul(ratioX96, _1E26), factor_)
}
if cond {
// ratioX96 < ZERO_TICK_SCALED_RATIO
tick := not(tick)
perfectRatioX96 := div(mul(ratioX96, factor_), 100150000000000000000000000)
}
// perfect ratio should always be <= ratioX96
// not sure if it can ever be bigger but better to have extra checks
if gt(perfectRatioX96, ratioX96) {
revert(0, 0)
}
}
}
}
// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.21;
abstract contract Structs {
struct AddressBool {
address addr;
bool value;
}
struct AddressUint256 {
address addr;
uint256 value;
}
/// @notice struct to set borrow rate data for version 1
struct RateDataV1Params {
///
/// @param token for rate data
address token;
///
/// @param kink in borrow rate. in 1e2: 100% = 10_000; 1% = 100
/// utilization below kink usually means slow increase in rate, once utilization is above kink borrow rate increases fast
uint256 kink;
///
/// @param rateAtUtilizationZero desired borrow rate when utilization is zero. in 1e2: 100% = 10_000; 1% = 100
/// i.e. constant minimum borrow rate
/// e.g. at utilization = 0.01% rate could still be at least 4% (rateAtUtilizationZero would be 400 then)
uint256 rateAtUtilizationZero;
///
/// @param rateAtUtilizationKink borrow rate when utilization is at kink. in 1e2: 100% = 10_000; 1% = 100
/// e.g. when rate should be 7% at kink then rateAtUtilizationKink would be 700
uint256 rateAtUtilizationKink;
///
/// @param rateAtUtilizationMax borrow rate when utilization is maximum at 100%. in 1e2: 100% = 10_000; 1% = 100
/// e.g. when rate should be 125% at 100% then rateAtUtilizationMax would be 12_500
uint256 rateAtUtilizationMax;
}
/// @notice struct to set borrow rate data for version 2
struct RateDataV2Params {
///
/// @param token for rate data
address token;
///
/// @param kink1 first kink in borrow rate. in 1e2: 100% = 10_000; 1% = 100
/// utilization below kink 1 usually means slow increase in rate, once utilization is above kink 1 borrow rate increases faster
uint256 kink1;
///
/// @param kink2 second kink in borrow rate. in 1e2: 100% = 10_000; 1% = 100
/// utilization below kink 2 usually means slow / medium increase in rate, once utilization is above kink 2 borrow rate increases fast
uint256 kink2;
///
/// @param rateAtUtilizationZero desired borrow rate when utilization is zero. in 1e2: 100% = 10_000; 1% = 100
/// i.e. constant minimum borrow rate
/// e.g. at utilization = 0.01% rate could still be at least 4% (rateAtUtilizationZero would be 400 then)
uint256 rateAtUtilizationZero;
///
/// @param rateAtUtilizationKink1 desired borrow rate when utilization is at first kink. in 1e2: 100% = 10_000; 1% = 100
/// e.g. when rate should be 7% at first kink then rateAtUtilizationKink would be 700
uint256 rateAtUtilizationKink1;
///
/// @param rateAtUtilizationKink2 desired borrow rate when utilization is at second kink. in 1e2: 100% = 10_000; 1% = 100
/// e.g. when rate should be 7% at second kink then rateAtUtilizationKink would be 1_200
uint256 rateAtUtilizationKink2;
///
/// @param rateAtUtilizationMax desired borrow rate when utilization is maximum at 100%. in 1e2: 100% = 10_000; 1% = 100
/// e.g. when rate should be 125% at 100% then rateAtUtilizationMax would be 12_500
uint256 rateAtUtilizationMax;
}
/// @notice struct to set token config
struct TokenConfig {
///
/// @param token address
address token;
///
/// @param fee charges on borrower's interest. in 1e2: 100% = 10_000; 1% = 100
uint256 fee;
///
/// @param threshold on when to update the storage slot. in 1e2: 100% = 10_000; 1% = 100
uint256 threshold;
///
/// @param maxUtilization maximum allowed utilization. in 1e2: 100% = 10_000; 1% = 100
/// set to 100% to disable and have default limit of 100% (avoiding SLOAD).
uint256 maxUtilization;
}
/// @notice struct to set user supply & withdrawal config
struct UserSupplyConfig {
///
/// @param user address
address user;
///
/// @param token address
address token;
///
/// @param mode: 0 = without interest. 1 = with interest
uint8 mode;
///
/// @param expandPercent withdrawal limit expand percent. in 1e2: 100% = 10_000; 1% = 100
/// Also used to calculate rate at which withdrawal limit should decrease (instant).
uint256 expandPercent;
///
/// @param expandDuration withdrawal limit expand duration in seconds.
/// used to calculate rate together with expandPercent
uint256 expandDuration;
///
/// @param baseWithdrawalLimit base limit, below this, user can withdraw the entire amount.
/// amount in raw (to be multiplied with exchange price) or normal depends on configured mode in user config for the token:
/// with interest -> raw, without interest -> normal
uint256 baseWithdrawalLimit;
}
/// @notice struct to set user borrow & payback config
struct UserBorrowConfig {
///
/// @param user address
address user;
///
/// @param token address
address token;
///
/// @param mode: 0 = without interest. 1 = with interest
uint8 mode;
///
/// @param expandPercent debt limit expand percent. in 1e2: 100% = 10_000; 1% = 100
/// Also used to calculate rate at which debt limit should decrease (instant).
uint256 expandPercent;
///
/// @param expandDuration debt limit expand duration in seconds.
/// used to calculate rate together with expandPercent
uint256 expandDuration;
///
/// @param baseDebtCeiling base borrow limit. until here, borrow limit remains as baseDebtCeiling
/// (user can borrow until this point at once without stepped expansion). Above this, automated limit comes in place.
/// amount in raw (to be multiplied with exchange price) or normal depends on configured mode in user config for the token:
/// with interest -> raw, without interest -> normal
uint256 baseDebtCeiling;
///
/// @param maxDebtCeiling max borrow ceiling, maximum amount the user can borrow.
/// amount in raw (to be multiplied with exchange price) or normal depends on configured mode in user config for the token:
/// with interest -> raw, without interest -> normal
uint256 maxDebtCeiling;
}
}
//SPDX-License-Identifier: MIT
pragma solidity 0.8.21;
import { IProxy } from "../../infiniteProxy/interfaces/iProxy.sol";
import { Structs as AdminModuleStructs } from "../adminModule/structs.sol";
interface IFluidLiquidityAdmin {
/// @notice adds/removes auths. Auths generally could be contracts which can have restricted actions defined on contract.
/// auths can be helpful in reducing governance overhead where it's not needed.
/// @param authsStatus_ array of structs setting allowed status for an address.
/// status true => add auth, false => remove auth
function updateAuths(AdminModuleStructs.AddressBool[] calldata authsStatus_) external;
/// @notice adds/removes guardians. Only callable by Governance.
/// @param guardiansStatus_ array of structs setting allowed status for an address.
/// status true => add guardian, false => remove guardian
function updateGuardians(AdminModuleStructs.AddressBool[] calldata guardiansStatus_) external;
/// @notice changes the revenue collector address (contract that is sent revenue). Only callable by Governance.
/// @param revenueCollector_ new revenue collector address
function updateRevenueCollector(address revenueCollector_) external;
/// @notice changes current status, e.g. for pausing or unpausing all user operations. Only callable by Auths.
/// @param newStatus_ new status
/// status = 2 -> pause, status = 1 -> resume.
function changeStatus(uint256 newStatus_) external;
/// @notice update tokens rate data version 1. Only callable by Auths.
/// @param tokensRateData_ array of RateDataV1Params with rate data to set for each token
function updateRateDataV1s(AdminModuleStructs.RateDataV1Params[] calldata tokensRateData_) external;
/// @notice update tokens rate data version 2. Only callable by Auths.
/// @param tokensRateData_ array of RateDataV2Params with rate data to set for each token
function updateRateDataV2s(AdminModuleStructs.RateDataV2Params[] calldata tokensRateData_) external;
/// @notice updates token configs: fee charge on borrowers interest & storage update utilization threshold.
/// Only callable by Auths.
/// @param tokenConfigs_ contains token address, fee & utilization threshold
function updateTokenConfigs(AdminModuleStructs.TokenConfig[] calldata tokenConfigs_) external;
/// @notice updates user classes: 0 is for new protocols, 1 is for established protocols.
/// Only callable by Auths.
/// @param userClasses_ struct array of uint256 value to assign for each user address
function updateUserClasses(AdminModuleStructs.AddressUint256[] calldata userClasses_) external;
/// @notice sets user supply configs per token basis. Eg: with interest or interest-free and automated limits.
/// Only callable by Auths.
/// @param userSupplyConfigs_ struct array containing user supply config, see `UserSupplyConfig` struct for more info
function updateUserSupplyConfigs(AdminModuleStructs.UserSupplyConfig[] memory userSupplyConfigs_) external;
/// @notice sets a new withdrawal limit as the current limit for a certain user
/// @param user_ user address for which to update the withdrawal limit
/// @param token_ token address for which to update the withdrawal limit
/// @param newLimit_ new limit until which user supply can decrease to.
/// Important: input in raw. Must account for exchange price in input param calculation.
/// Note any limit that is < max expansion or > current user supply will set max expansion limit or
/// current user supply as limit respectively.
/// - set 0 to make maximum possible withdrawable: instant full expansion, and if that goes
/// below base limit then fully down to 0.
/// - set type(uint256).max to make current withdrawable 0 (sets current user supply as limit).
function updateUserWithdrawalLimit(address user_, address token_, uint256 newLimit_) external;
/// @notice setting user borrow configs per token basis. Eg: with interest or interest-free and automated limits.
/// Only callable by Auths.
/// @param userBorrowConfigs_ struct array containing user borrow config, see `UserBorrowConfig` struct for more info
function updateUserBorrowConfigs(AdminModuleStructs.UserBorrowConfig[] memory userBorrowConfigs_) external;
/// @notice pause operations for a particular user in class 0 (class 1 users can't be paused by guardians).
/// Only callable by Guardians.
/// @param user_ address of user to pause operations for
/// @param supplyTokens_ token addresses to pause withdrawals for
/// @param borrowTokens_ token addresses to pause borrowings for
function pauseUser(address user_, address[] calldata supplyTokens_, address[] calldata borrowTokens_) external;
/// @notice unpause operations for a particular user in class 0 (class 1 users can't be paused by guardians).
/// Only callable by Guardians.
/// @param user_ address of user to unpause operations for
/// @param supplyTokens_ token addresses to unpause withdrawals for
/// @param borrowTokens_ token addresses to unpause borrowings for
function unpauseUser(address user_, address[] calldata supplyTokens_, address[] calldata borrowTokens_) external;
/// @notice collects revenue for tokens to configured revenueCollector address.
/// @param tokens_ array of tokens to collect revenue for
/// @dev Note that this can revert if token balance is < revenueAmount (utilization > 100%)
function collectRevenue(address[] calldata tokens_) external;
/// @notice gets the current updated exchange prices for n tokens and updates all prices, rates related data in storage.
/// @param tokens_ tokens to update exchange prices for
/// @return supplyExchangePrices_ new supply rates of overall system for each token
/// @return borrowExchangePrices_ new borrow rates of overall system for each token
function updateExchangePrices(
address[] calldata tokens_
) external returns (uint256[] memory supplyExchangePrices_, uint256[] memory borrowExchangePrices_);
}
interface IFluidLiquidityLogic is IFluidLiquidityAdmin {
/// @notice Single function which handles supply, withdraw, borrow & payback
/// @param token_ address of token (0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE for native)
/// @param supplyAmount_ if +ve then supply, if -ve then withdraw, if 0 then nothing
/// @param borrowAmount_ if +ve then borrow, if -ve then payback, if 0 then nothing
/// @param withdrawTo_ if withdrawal then to which address
/// @param borrowTo_ if borrow then to which address
/// @param callbackData_ callback data passed to `liquidityCallback` method of protocol
/// @return memVar3_ updated supplyExchangePrice
/// @return memVar4_ updated borrowExchangePrice
/// @dev to trigger skipping in / out transfers (gas optimization):
/// - ` callbackData_` MUST be encoded so that "from" address is the last 20 bytes in the last 32 bytes slot,
/// also for native token operations where liquidityCallback is not triggered!
/// from address must come at last position if there is more data. I.e. encode like:
/// abi.encode(otherVar1, otherVar2, FROM_ADDRESS). Note dynamic types used with abi.encode come at the end
/// so if dynamic types are needed, you must use abi.encodePacked to ensure the from address is at the end.
/// - this "from" address must match withdrawTo_ or borrowTo_ and must be == `msg.sender`
/// - `callbackData_` must in addition to the from address as described above include bytes32 SKIP_TRANSFERS
/// in the slot before (bytes 32 to 63)
/// - `msg.value` must be 0.
/// - Amounts must be either:
/// - supply(+) == borrow(+), withdraw(-) == payback(-).
/// - Liquidity must be on the winning side (deposit < borrow OR payback < withdraw).
function operate(
address token_,
int256 supplyAmount_,
int256 borrowAmount_,
address withdrawTo_,
address borrowTo_,
bytes calldata callbackData_
) external payable returns (uint256 memVar3_, uint256 memVar4_);
}
interface IFluidLiquidity is IProxy, IFluidLiquidityLogic {}
// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.21;
contract Error {
error FluidOracleError(uint256 errorId_);
}
// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.21;
library ErrorTypes {
/***********************************|
| FluidOracleL2 |
|__________________________________*/
/// @notice thrown when sequencer on a L2 has an outage and grace period has not yet passed.
uint256 internal constant FluidOracleL2__SequencerOutage = 60000;
/***********************************|
| UniV3CheckCLRSOracle |
|__________________________________*/
/// @notice thrown when the delta between main price source and check rate source is exceeding the allowed delta
uint256 internal constant UniV3CheckCLRSOracle__InvalidPrice = 60001;
/// @notice thrown when an invalid parameter is passed to a method
uint256 internal constant UniV3CheckCLRSOracle__InvalidParams = 60002;
/// @notice thrown when the exchange rate is zero, even after all possible fallbacks depending on config
uint256 internal constant UniV3CheckCLRSOracle__ExchangeRateZero = 60003;
/***********************************|
| FluidOracle |
|__________________________________*/
/// @notice thrown when an invalid info name is passed into a fluid oracle (e.g. not set or too long)
uint256 internal constant FluidOracle__InvalidInfoName = 60010;
/***********************************|
| sUSDe Oracle |
|__________________________________*/
/// @notice thrown when an invalid parameter is passed to a method
uint256 internal constant SUSDeOracle__InvalidParams = 60102;
/***********************************|
| Pendle Oracle |
|__________________________________*/
/// @notice thrown when an invalid parameter is passed to a method
uint256 internal constant PendleOracle__InvalidParams = 60201;
/// @notice thrown when the Pendle market Oracle has not been initialized yet
uint256 internal constant PendleOracle__MarketNotInitialized = 60202;
/// @notice thrown when the Pendle market does not have 18 decimals
uint256 internal constant PendleOracle__MarketInvalidDecimals = 60203;
/// @notice thrown when the Pendle market returns an unexpected price
uint256 internal constant PendleOracle__InvalidPrice = 60204;
/***********************************|
| CLRS2UniV3CheckCLRSOracleL2 |
|__________________________________*/
/// @notice thrown when the exchange rate is zero, even after all possible fallbacks depending on config
uint256 internal constant CLRS2UniV3CheckCLRSOracleL2__ExchangeRateZero = 60301;
/***********************************|
| Ratio2xFallbackCLRSOracleL2 |
|__________________________________*/
/// @notice thrown when the exchange rate is zero, even after all possible fallbacks depending on config
uint256 internal constant Ratio2xFallbackCLRSOracleL2__ExchangeRateZero = 60311;
/***********************************|
| WeETHsOracle |
|__________________________________*/
/// @notice thrown when an invalid parameter is passed to a method
uint256 internal constant WeETHsOracle__InvalidParams = 60321;
/***********************************|
| DexSmartColOracle |
|__________________________________*/
/// @notice thrown when an invalid parameter is passed to a method
uint256 internal constant DexSmartColOracle__InvalidParams = 60331;
/// @notice thrown when smart col is not enabled
uint256 internal constant DexSmartColOracle__SmartColNotEnabled = 60332;
/***********************************|
| DexSmartDebtOracle |
|__________________________________*/
/// @notice thrown when an invalid parameter is passed to a method
uint256 internal constant DexSmartDebtOracle__InvalidParams = 60341;
/// @notice thrown when smart debt is not enabled
uint256 internal constant DexSmartDebtOracle__SmartDebtNotEnabled = 60342;
/***********************************|
| ContractRate |
|__________________________________*/
/// @notice thrown when an invalid parameter is passed to a method
uint256 internal constant ContractRate__InvalidParams = 60351;
/// @notice thrown when caller is not authorized
uint256 internal constant ContractRate__Unauthorized = 60352;
/// @notice thrown when minimum diff for triggering update on the stared rate is not reached
uint256 internal constant ContractRate__MinUpdateDiffNotReached = 60353;
/***********************************|
| sUSDs Oracle |
|__________________________________*/
/// @notice thrown when an invalid parameter is passed to a method
uint256 internal constant SUSDsOracle__InvalidParams = 60361;
/***********************************|
| Peg Oracle |
|__________________________________*/
/// @notice thrown when an invalid parameter is passed to a method
uint256 internal constant PegOracle__InvalidParams = 60371;
/***********************************|
| Chainlink Oracle |
|__________________________________*/
/// @notice thrown when an invalid parameter is passed to a method
uint256 internal constant ChainlinkOracle__InvalidParams = 61001;
/***********************************|
| UniswapV3 Oracle |
|__________________________________*/
/// @notice thrown when an invalid parameter is passed to a method
uint256 internal constant UniV3Oracle__InvalidParams = 62001;
/// @notice thrown when constructor is called with invalid ordered seconds agos values
uint256 internal constant UniV3Oracle__InvalidSecondsAgos = 62002;
/// @notice thrown when constructor is called with invalid delta values > 100%
uint256 internal constant UniV3Oracle__InvalidDeltas = 62003;
/***********************************|
| WstETh Oracle |
|__________________________________*/
/// @notice thrown when an invalid parameter is passed to a method
uint256 internal constant WstETHOracle__InvalidParams = 63001;
/***********************************|
| Redstone Oracle |
|__________________________________*/
/// @notice thrown when an invalid parameter is passed to a method
uint256 internal constant RedstoneOracle__InvalidParams = 64001;
/***********************************|
| Fallback Oracle |
|__________________________________*/
/// @notice thrown when an invalid parameter is passed to a method
uint256 internal constant FallbackOracle__InvalidParams = 65001;
/***********************************|
| FallbackCLRSOracle |
|__________________________________*/
/// @notice thrown when the exchange rate is zero, even for the fallback oracle source (if enabled)
uint256 internal constant FallbackCLRSOracle__ExchangeRateZero = 66001;
/***********************************|
| WstETHCLRSOracle |
|__________________________________*/
/// @notice thrown when the exchange rate is zero, even for the fallback oracle source (if enabled)
uint256 internal constant WstETHCLRSOracle__ExchangeRateZero = 67001;
/***********************************|
| CLFallbackUniV3Oracle |
|__________________________________*/
/// @notice thrown when the exchange rate is zero, even for the uniV3 rate
uint256 internal constant CLFallbackUniV3Oracle__ExchangeRateZero = 68001;
/***********************************|
| WstETHCLRS2UniV3CheckCLRSOracle |
|__________________________________*/
/// @notice thrown when the exchange rate is zero, even for the uniV3 rate
uint256 internal constant WstETHCLRS2UniV3CheckCLRSOracle__ExchangeRateZero = 69001;
/***********************************|
| WeETh Oracle |
|__________________________________*/
/// @notice thrown when an invalid parameter is passed to a method
uint256 internal constant WeETHOracle__InvalidParams = 70001;
}
// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.21;
import { IFluidOracle } from "./interfaces/iFluidOracle.sol";
import { ErrorTypes } from "./errorTypes.sol";
import { Error as OracleError } from "./error.sol";
/// @title FluidOracle
/// @notice Base contract that any Fluid Oracle must implement
abstract contract FluidOracle is IFluidOracle, OracleError {
/// @dev short helper string to easily identify the oracle. E.g. token symbols
//
// using a bytes32 because string can not be immutable.
bytes32 private immutable _infoName;
constructor(string memory infoName_) {
if (bytes(infoName_).length > 32 || bytes(infoName_).length == 0) {
revert FluidOracleError(ErrorTypes.FluidOracle__InvalidInfoName);
}
// convert string to bytes32
bytes32 infoNameBytes32_;
assembly {
infoNameBytes32_ := mload(add(infoName_, 32))
}
_infoName = infoNameBytes32_;
}
/// @inheritdoc IFluidOracle
function infoName() external view returns (string memory) {
// convert bytes32 to string
uint256 length_;
while (length_ < 32 && _infoName[length_] != 0) {
length_++;
}
bytes memory infoNameBytes_ = new bytes(length_);
for (uint256 i; i < length_; i++) {
infoNameBytes_[i] = _infoName[i];
}
return string(infoNameBytes_);
}
/// @inheritdoc IFluidOracle
function getExchangeRate() external view virtual returns (uint256 exchangeRate_);
/// @inheritdoc IFluidOracle
function getExchangeRateOperate() external view virtual returns (uint256 exchangeRate_);
/// @inheritdoc IFluidOracle
function getExchangeRateLiquidate() external view virtual returns (uint256 exchangeRate_);
}
// SPDX-License-Identifier: MIT
pragma solidity 0.8.21;
interface IFluidOracle {
/// @dev Deprecated. Use `getExchangeRateOperate()` and `getExchangeRateLiquidate()` instead. Only implemented for
/// backwards compatibility.
function getExchangeRate() external view returns (uint256 exchangeRate_);
/// @notice Get the `exchangeRate_` between the underlying asset and the peg asset in 1e27 for operates
function getExchangeRateOperate() external view returns (uint256 exchangeRate_);
/// @notice Get the `exchangeRate_` between the underlying asset and the peg asset in 1e27 for liquidations
function getExchangeRateLiquidate() external view returns (uint256 exchangeRate_);
/// @notice helper string to easily identify the oracle. E.g. token symbols
function infoName() external view returns (string memory);
}
// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.21;
abstract contract Error {
error FluidVaultError(uint256 errorId_);
/// @notice used to simulate liquidation to find the maximum liquidatable amounts
error FluidLiquidateResult(uint256 colLiquidated, uint256 debtLiquidated);
}
// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.21;
library ErrorTypes {
/***********************************|
| Vault Factory |
|__________________________________*/
uint256 internal constant VaultFactory__InvalidOperation = 30001;
uint256 internal constant VaultFactory__Unauthorized = 30002;
uint256 internal constant VaultFactory__SameTokenNotAllowed = 30003;
uint256 internal constant VaultFactory__InvalidParams = 30004;
uint256 internal constant VaultFactory__InvalidVault = 30005;
uint256 internal constant VaultFactory__InvalidVaultAddress = 30006;
uint256 internal constant VaultFactory__OnlyDelegateCallAllowed = 30007;
/***********************************|
| Vault |
|__________________________________*/
/// @notice thrown at reentrancy
uint256 internal constant Vault__AlreadyEntered = 31001;
/// @notice thrown when user sends deposit & borrow amount as 0
uint256 internal constant Vault__InvalidOperateAmount = 31002;
/// @notice thrown when msg.value is not in sync with native token deposit or payback
uint256 internal constant Vault__InvalidMsgValueOperate = 31003;
/// @notice thrown when msg.sender is not the owner of the vault
uint256 internal constant Vault__NotAnOwner = 31004;
/// @notice thrown when user's position does not exist. Sending the wrong index from the frontend
uint256 internal constant Vault__TickIsEmpty = 31005;
/// @notice thrown when the user's position is above CF and the user tries to make it more risky by trying to withdraw or borrow
uint256 internal constant Vault__PositionAboveCF = 31006;
/// @notice thrown when the top tick is not initialized. Happens if the vault is totally new or all the user's left
uint256 internal constant Vault__TopTickDoesNotExist = 31007;
/// @notice thrown when msg.value in liquidate is not in sync payback
uint256 internal constant Vault__InvalidMsgValueLiquidate = 31008;
/// @notice thrown when slippage is more on liquidation than what the liquidator sent
uint256 internal constant Vault__ExcessSlippageLiquidation = 31009;
/// @notice thrown when msg.sender is not the rebalancer/reserve contract
uint256 internal constant Vault__NotRebalancer = 31010;
/// @notice thrown when NFT of one vault interacts with the NFT of other vault
uint256 internal constant Vault__NftNotOfThisVault = 31011;
/// @notice thrown when the token is not initialized on the liquidity contract
uint256 internal constant Vault__TokenNotInitialized = 31012;
/// @notice thrown when admin updates fallback if a non-auth calls vault
uint256 internal constant Vault__NotAnAuth = 31013;
/// @notice thrown in operate when user tries to witdhraw more collateral than deposited
uint256 internal constant Vault__ExcessCollateralWithdrawal = 31014;
/// @notice thrown in operate when user tries to payback more debt than borrowed
uint256 internal constant Vault__ExcessDebtPayback = 31015;
/// @notice thrown when user try to withdrawal more than operate's withdrawal limit
uint256 internal constant Vault__WithdrawMoreThanOperateLimit = 31016;
/// @notice thrown when caller of liquidityCallback is not Liquidity
uint256 internal constant Vault__InvalidLiquidityCallbackAddress = 31017;
/// @notice thrown when reentrancy is not already on
uint256 internal constant Vault__NotEntered = 31018;
/// @notice thrown when someone directly calls operate or secondary implementation contract
uint256 internal constant Vault__OnlyDelegateCallAllowed = 31019;
/// @notice thrown when the safeTransferFrom for a token amount failed
uint256 internal constant Vault__TransferFromFailed = 31020;
/// @notice thrown when exchange price overflows while updating on storage
uint256 internal constant Vault__ExchangePriceOverFlow = 31021;
/// @notice thrown when debt to liquidate amt is sent wrong
uint256 internal constant Vault__InvalidLiquidationAmt = 31022;
/// @notice thrown when user debt or collateral goes above 2**128 or below -2**128
uint256 internal constant Vault__UserCollateralDebtExceed = 31023;
/// @notice thrown if on liquidation branch debt becomes lower than 100
uint256 internal constant Vault__BranchDebtTooLow = 31024;
/// @notice thrown when tick's debt is less than 10000
uint256 internal constant Vault__TickDebtTooLow = 31025;
/// @notice thrown when the received new liquidity exchange price is of unexpected value (< than the old one)
uint256 internal constant Vault__LiquidityExchangePriceUnexpected = 31026;
/// @notice thrown when user's debt is less than 10000
uint256 internal constant Vault__UserDebtTooLow = 31027;
/// @notice thrown when on only payback and only deposit the ratio of position increases
uint256 internal constant Vault__InvalidPaybackOrDeposit = 31028;
/// @notice thrown when liquidation just happens of a single partial or when there's nothing to liquidate
uint256 internal constant Vault__InvalidLiquidation = 31029;
/// @notice thrown when msg.value is sent wrong in rebalance
uint256 internal constant Vault__InvalidMsgValueInRebalance = 31030;
/// @notice thrown when nothing rebalanced
uint256 internal constant Vault__NothingToRebalance = 31031;
/// @notice thrown on unforseen liquidation scenarios. Might never come in use.
uint256 internal constant Vault__LiquidationReverts = 31032;
/// @notice thrown when oracle price is > 1e54
uint256 internal constant Vault__InvalidOraclePrice = 31033;
/// @notice thrown when constants are not set properly via contructor
uint256 internal constant Vault__ImproperConstantsSetup = 31034;
/// @notice thrown when externally calling fetchLatestPosition function
uint256 internal constant Vault__FetchLatestPositionFailed = 31035;
/// @notice thrown when dex callback is not from dex
uint256 internal constant Vault__InvalidDexCallbackAddress = 31036;
/// @notice thrown when dex callback is already set
uint256 internal constant Vault__DexFromAddressAlreadySet = 31037;
/// @notice thrown when an invalid min / max amounts config is passed to rebalance()
uint256 internal constant Vault__InvalidMinMaxInRebalance = 31038;
/***********************************|
| ERC721 |
|__________________________________*/
uint256 internal constant ERC721__InvalidParams = 32001;
uint256 internal constant ERC721__Unauthorized = 32002;
uint256 internal constant ERC721__InvalidOperation = 32003;
uint256 internal constant ERC721__UnsafeRecipient = 32004;
uint256 internal constant ERC721__OutOfBoundsIndex = 32005;
/***********************************|
| Vault Admin |
|__________________________________*/
/// @notice thrown when admin tries to setup invalid value which are crossing limits
uint256 internal constant VaultAdmin__ValueAboveLimit = 33001;
/// @notice when someone directly calls admin implementation contract
uint256 internal constant VaultAdmin__OnlyDelegateCallAllowed = 33002;
/// @notice thrown when auth sends NFT ID as 0 while collecting dust debt
uint256 internal constant VaultAdmin__NftIdShouldBeNonZero = 33003;
/// @notice thrown when trying to collect dust debt of NFT which is not of this vault
uint256 internal constant VaultAdmin__NftNotOfThisVault = 33004;
/// @notice thrown when dust debt of NFT is 0, meaning nothing to collect
uint256 internal constant VaultAdmin__DustDebtIsZero = 33005;
/// @notice thrown when final debt after liquidation is not 0, meaning position 100% liquidated
uint256 internal constant VaultAdmin__FinalDebtShouldBeZero = 33006;
/// @notice thrown when NFT is not liquidated state
uint256 internal constant VaultAdmin__NftNotLiquidated = 33007;
/// @notice thrown when total absorbed dust debt is 0
uint256 internal constant VaultAdmin__AbsorbedDustDebtIsZero = 33008;
/// @notice thrown when address is set as 0
uint256 internal constant VaultAdmin__AddressZeroNotAllowed = 33009;
/***********************************|
| Vault Rewards |
|__________________________________*/
uint256 internal constant VaultRewards__Unauthorized = 34001;
uint256 internal constant VaultRewards__AddressZero = 34002;
uint256 internal constant VaultRewards__InvalidParams = 34003;
uint256 internal constant VaultRewards__NewMagnifierSameAsOldMagnifier = 34004;
uint256 internal constant VaultRewards__NotTheInitiator = 34005;
uint256 internal constant VaultRewards__NotTheGovernance = 34006;
uint256 internal constant VaultRewards__AlreadyStarted = 34007;
uint256 internal constant VaultRewards__RewardsNotStartedOrEnded = 34008;
uint256 internal constant VaultRewards__InvalidStartTime = 34009;
uint256 internal constant VaultRewards__AlreadyEnded = 34010;
/***********************************|
| Vault DEX Types |
|__________________________________*/
uint256 internal constant VaultDex__InvalidOperateAmount = 35001;
uint256 internal constant VaultDex__DebtSharesPaidMoreThanAvailableLiquidation = 35002;
/***********************************|
| Vault Borrow Rewards |
|__________________________________*/
uint256 internal constant VaultBorrowRewards__Unauthorized = 36001;
uint256 internal constant VaultBorrowRewards__AddressZero = 36002;
uint256 internal constant VaultBorrowRewards__InvalidParams = 36003;
uint256 internal constant VaultBorrowRewards__NewMagnifierSameAsOldMagnifier = 36004;
uint256 internal constant VaultBorrowRewards__NotTheInitiator = 36005;
uint256 internal constant VaultBorrowRewards__NotTheGovernance = 36006;
uint256 internal constant VaultBorrowRewards__AlreadyStarted = 36007;
uint256 internal constant VaultBorrowRewards__RewardsNotStartedOrEnded = 36008;
uint256 internal constant VaultBorrowRewards__InvalidStartTime = 36009;
uint256 internal constant VaultBorrowRewards__AlreadyEnded = 36010;
}
//SPDX-License-Identifier: MIT
pragma solidity 0.8.21;
interface IFluidVaultT1 {
/// @notice returns the vault id
function VAULT_ID() external view returns (uint256);
/// @notice reads uint256 data `result_` from storage at a bytes32 storage `slot_` key.
function readFromStorage(bytes32 slot_) external view returns (uint256 result_);
struct ConstantViews {
address liquidity;
address factory;
address adminImplementation;
address secondaryImplementation;
address supplyToken;
address borrowToken;
uint8 supplyDecimals;
uint8 borrowDecimals;
uint vaultId;
bytes32 liquiditySupplyExchangePriceSlot;
bytes32 liquidityBorrowExchangePriceSlot;
bytes32 liquidityUserSupplySlot;
bytes32 liquidityUserBorrowSlot;
}
/// @notice returns all Vault constants
function constantsView() external view returns (ConstantViews memory constantsView_);
/// @notice fetches the latest user position after a liquidation
function fetchLatestPosition(
int256 positionTick_,
uint256 positionTickId_,
uint256 positionRawDebt_,
uint256 tickData_
)
external
view
returns (
int256, // tick
uint256, // raw debt
uint256, // raw collateral
uint256, // branchID_
uint256 // branchData_
);
/// @notice calculates the updated vault exchange prices
function updateExchangePrices(
uint256 vaultVariables2_
)
external
view
returns (
uint256 liqSupplyExPrice_,
uint256 liqBorrowExPrice_,
uint256 vaultSupplyExPrice_,
uint256 vaultBorrowExPrice_
);
/// @notice calculates the updated vault exchange prices and writes them to storage
function updateExchangePricesOnStorage()
external
returns (
uint256 liqSupplyExPrice_,
uint256 liqBorrowExPrice_,
uint256 vaultSupplyExPrice_,
uint256 vaultBorrowExPrice_
);
/// @notice returns the liquidity contract address
function LIQUIDITY() external view returns (address);
function operate(
uint256 nftId_, // if 0 then new position
int256 newCol_, // if negative then withdraw
int256 newDebt_, // if negative then payback
address to_ // address at which the borrow & withdraw amount should go to. If address(0) then it'll go to msg.sender
)
external
payable
returns (
uint256, // nftId_
int256, // final supply amount. if - then withdraw
int256 // final borrow amount. if - then payback
);
function liquidate(
uint256 debtAmt_,
uint256 colPerUnitDebt_, // min collateral needed per unit of debt in 1e18
address to_,
bool absorb_
) external payable returns (uint actualDebtAmt_, uint actualColAmt_);
function absorb() external;
function rebalance() external payable returns (int supplyAmt_, int borrowAmt_);
error FluidLiquidateResult(uint256 colLiquidated, uint256 debtLiquidated);
}
// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.21;
contract Variables {
/***********************************|
| Storage Variables |
|__________________________________*/
/// note: in all variables. For tick >= 0 are represented with bit as 1, tick < 0 are represented with bit as 0
/// note: read all the variables through storageRead.sol
/// note: vaultVariables contains vault variables which need regular updates through transactions
/// First 1 bit => 0 => re-entrancy. If 0 then allow transaction to go, else throw.
/// Next 1 bit => 1 => Is the current active branch liquidated? If true then check the branch's minima tick before creating a new position
/// If the new tick is greater than minima tick then initialize a new branch, make that as current branch & do proper linking
/// Next 1 bit => 2 => sign of topmost tick (0 -> negative; 1 -> positive)
/// Next 19 bits => 3-21 => absolute value of topmost tick
/// Next 30 bits => 22-51 => current branch ID
/// Next 30 bits => 52-81 => total branch ID
/// Next 64 bits => 82-145 => Total supply
/// Next 64 bits => 146-209 => Total borrow
/// Next 32 bits => 210-241 => Total positions
uint256 internal vaultVariables;
/// note: vaultVariables2 contains variables which do not update on every transaction. So mainly admin/auth set amount
/// First 16 bits => 0-15 => supply rate magnifier; 10000 = 1x (Here 16 bits should be more than enough)
/// Next 16 bits => 16-31 => borrow rate magnifier; 10000 = 1x (Here 16 bits should be more than enough)
/// Next 10 bits => 32-41 => collateral factor. 800 = 0.8 = 80% (max precision of 0.1%)
/// Next 10 bits => 42-51 => liquidation Threshold. 900 = 0.9 = 90% (max precision of 0.1%)
/// Next 10 bits => 52-61 => liquidation Max Limit. 950 = 0.95 = 95% (max precision of 0.1%) (above this 100% liquidation can happen)
/// Next 10 bits => 62-71 => withdraw gap. 100 = 0.1 = 10%. (max precision of 0.1%) (max 7 bits can also suffice for the requirement here of 0.1% to 10%). Needed to save some limits on withdrawals so liquidate can work seamlessly.
/// Next 10 bits => 72-81 => liquidation penalty. 100 = 0.01 = 1%. (max precision of 0.01%) (max liquidation penantly can be 10.23%). Applies when tick is in between liquidation Threshold & liquidation Max Limit.
/// Next 10 bits => 82-91 => borrow fee. 100 = 0.01 = 1%. (max precision of 0.01%) (max borrow fee can be 10.23%). Fees on borrow.
/// Next 4 bits => 92-95 => empty
/// Next 160 bits => 96-255 => Oracle address
uint256 internal vaultVariables2;
/// note: stores absorbed liquidity
/// First 128 bits raw debt amount
/// last 128 bits raw col amount
uint256 internal absorbedLiquidity;
/// position index => position data uint
/// if the entire variable is 0 (meaning not initialized) at the start that means no position at all
/// First 1 bit => 0 => position type (0 => borrow position; 1 => supply position)
/// Next 1 bit => 1 => sign of user's tick (0 => negative; 1 => positive)
/// Next 19 bits => 2-20 => absolute value of user's tick
/// Next 24 bits => 21-44 => user's tick's id
/// Below we are storing user's collateral & not debt, because the position can also be only collateral with no tick but it can never be only debt
/// Next 64 bits => 45-108 => user's supply amount. Debt will be calculated through supply & ratio.
/// Next 64 bits => 109-172 => user's dust debt amount. User's net debt = total debt - dust amount. Total debt is calculated through supply & ratio
/// User won't pay any extra interest on dust debt & hence we will not show it as a debt on UI. For user's there's no dust.
mapping(uint256 => uint256) internal positionData;
/// Tick has debt only keeps data of non liquidated positions. liquidated tick's data stays in branch itself
/// tick parent => uint (represents bool for 256 children)
/// parent of (i)th tick:-
/// if (i>=0) (i / 256);
/// else ((i + 1) / 256) - 1
/// first bit of the variable is the smallest tick & last bit is the biggest tick of that slot
mapping(int256 => uint256) internal tickHasDebt;
/// mapping tickId => tickData
/// Tick related data. Total debt & other things
/// First bit => 0 => If 1 then liquidated else not liquidated
/// Next 24 bits => 1-24 => Total IDs. ID should start from 1.
/// If not liquidated:
/// Next 64 bits => 25-88 => raw debt
/// If liquidated
/// The below 3 things are of last ID. This is to be updated when user creates a new position
/// Next 1 bit => 25 => Is 100% liquidated? If this is 1 meaning it was above max tick when it got liquidated (100% liquidated)
/// Next 30 bits => 26-55 => branch ID where this tick got liquidated
/// Next 50 bits => 56-105 => debt factor 50 bits (35 bits coefficient | 15 bits expansion)
mapping(int256 => uint256) internal tickData;
/// tick id => previous tick id liquidation data. ID starts from 1
/// One tick ID contains 3 IDs of 80 bits in it, holding liquidation data of previously active but liquidated ticks
/// 81 bits data below
/// #### First 85 bits ####
/// 1st bit => 0 => Is 100% liquidated? If this is 1 meaning it was above max tick when it got liquidated
/// Next 30 bits => 1-30 => branch ID where this tick got liquidated
/// Next 50 bits => 31-80 => debt factor 50 bits (35 bits coefficient | 15 bits expansion)
/// #### Second 85 bits ####
/// 85th bit => 85 => Is 100% liquidated? If this is 1 meaning it was above max tick when it got liquidated
/// Next 30 bits => 86-115 => branch ID where this tick got liquidated
/// Next 50 bits => 116-165 => debt factor 50 bits (35 bits coefficient | 15 bits expansion)
/// #### Third 85 bits ####
/// 170th bit => 170 => Is 100% liquidated? If this is 1 meaning it was above max tick when it got liquidated
/// Next 30 bits => 171-200 => branch ID where this tick got liquidated
/// Next 50 bits => 201-250 => debt factor 50 bits (35 bits coefficient | 15 bits expansion)
mapping(int256 => mapping(uint256 => uint256)) internal tickId;
/// mapping branchId => branchData
/// First 2 bits => 0-1 => if 0 then not liquidated, if 1 then liquidated, if 2 then merged, if 3 then closed
/// merged means the branch is merged into it's base branch
/// closed means all the users are 100% liquidated
/// Next 1 bit => 2 => minima tick sign of this branch. Will only be there if any liquidation happened.
/// Next 19 bits => 3-21 => minima tick of this branch. Will only be there if any liquidation happened.
/// Next 30 bits => 22-51 => Partials of minima tick of branch this is connected to. 0 if master branch.
/// Next 64 bits => 52-115 Debt liquidity at this branch. Similar to last's top tick data. Remaining debt will move here from tickData after first liquidation
/// If not merged
/// Next 50 bits => 116-165 => Debt factor or of this branch. (35 bits coefficient | 15 bits expansion)
/// If merged
/// Next 50 bits => 116-165 => Connection/adjustment debt factor of this branch with the next branch.
/// If closed
/// Next 50 bits => 116-165 => Debt factor as 0. As all the user's positions are now fully gone
/// following values are present always again (merged / not merged / closed)
/// Next 30 bits => 166-195 => Branch's ID with which this branch is connected. If 0 then that means this is the master branch
/// Next 1 bit => 196 => sign of minima tick of branch this is connected to. 0 if master branch.
/// Next 19 bits => 197-215 => minima tick of branch this is connected to. 0 if master branch.
mapping(uint256 => uint256) internal branchData;
/// Exchange prices are in 1e12
/// First 64 bits => 0-63 => Liquidity's collateral token supply exchange price
/// First 64 bits => 64-127 => Liquidity's debt token borrow exchange price
/// First 64 bits => 128-191 => Vault's collateral token supply exchange price
/// First 64 bits => 192-255 => Vault's debt token borrow exchange price
uint256 internal rates;
/// address of rebalancer
address internal rebalancer;
uint256 internal absorbedDustDebt;
}
// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.21;
contract Events {
/// @notice emitted when an operate() method is executed that changes collateral (`colAmt_`) / debt (debtAmt_`)
/// amount for a `user_` position with `nftId_`. Receiver of any funds is the address `to_`.
event LogOperate(address user_, uint256 nftId_, int256 colAmt_, int256 debtAmt_, address to_);
/// @notice emitted when the exchange prices are updated in storage.
event LogUpdateExchangePrice(uint256 supplyExPrice_, uint256 borrowExPrice_);
/// @notice emitted when a liquidation has been executed.
event LogLiquidate(address liquidator_, uint256 colAmt_, uint256 debtAmt_, address to_);
/// @notice emitted when `absorb()` was executed to absorb bad debt.
event LogAbsorb(uint colAbsorbedRaw_, uint debtAbsorbedRaw_);
/// @notice emitted when a `rebalance()` has been executed, balancing out total supply / borrow between Vault
/// and Fluid Liquidity pools.
/// if `colAmt_` is positive then loss, meaning transfer from rebalancer address to vault and deposit.
/// if `colAmt_` is negative then profit, meaning withdrawn from vault and sent to rebalancer address.
/// if `debtAmt_` is positive then profit, meaning borrow from vault and sent to rebalancer address.
/// if `debtAmt_` is negative then loss, meaning transfer from rebalancer address to vault and payback.
event LogRebalance(int colAmt_, int debtAmt_);
}
// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.21;
import { Variables } from "../common/variables.sol";
import { IFluidOracle } from "../../../../oracle/fluidOracle.sol";
import { TickMath } from "../../../../libraries/tickMath.sol";
import { BigMathMinified } from "../../../../libraries/bigMathMinified.sol";
import { Error } from "../../error.sol";
import { ErrorTypes } from "../../errorTypes.sol";
import { IFluidVaultT1 } from "../../interfaces/iVaultT1.sol";
import { Structs } from "./structs.sol";
import { Events } from "./events.sol";
import { LiquiditySlotsLink } from "../../../../libraries/liquiditySlotsLink.sol";
import { LiquidityCalcs } from "../../../../libraries/liquidityCalcs.sol";
import { IFluidLiquidity } from "../../../../liquidity/interfaces/iLiquidity.sol";
import { SafeTransfer } from "../../../../libraries/safeTransfer.sol";
/// @notice Fluid Vault protocol secondary methods contract.
/// Implements `absorb()` and `rebalance()` methods, extracted from main contract due to contract size limits.
/// Methods are limited to be called via delegateCall only (as done by Vault CoreModule "VaultT1" contract).
contract FluidVaultT1Secondary is Variables, Error, Structs, Events {
using BigMathMinified for uint;
address internal constant NATIVE_TOKEN = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE;
// 30 bits (used for partials mainly)
uint internal constant X8 = 0xff;
uint internal constant X10 = 0x3ff;
uint internal constant X16 = 0xffff;
uint internal constant X19 = 0x7ffff;
uint internal constant X20 = 0xfffff;
uint internal constant X24 = 0xffffff;
uint internal constant X25 = 0x1ffffff;
uint internal constant X30 = 0x3fffffff;
uint internal constant X35 = 0x7ffffffff;
uint internal constant X50 = 0x3ffffffffffff;
uint internal constant X64 = 0xffffffffffffffff;
uint internal constant X96 = 0xffffffffffffffffffffffff;
uint internal constant X128 = 0xffffffffffffffffffffffffffffffff;
address private immutable addressThis;
constructor() {
addressThis = address(this);
}
modifier _verifyCaller() {
if (address(this) == addressThis) {
revert FluidVaultError(ErrorTypes.Vault__OnlyDelegateCallAllowed);
}
_;
}
/// @dev absorb function absorbs the bad debt if the bad debt is above max limit. The main use of it is
/// if the bad debt didn't got liquidated in time maybe due to sudden price drop or bad debt was extremely small to liquidate
/// and the bad debt goes above 100% ratio then there's no incentive for anyone to liquidate now
/// hence absorb functions absorbs that bad debt to allow newer bad debt to liquidate seamlessly.
/// if absorbing were to happen after this it's on governance on how to deal with it
/// although it can still be removed through liquidate via liquidator if the price goes back up and liquidation becomes beneficial
/// upon absorbed user position gets 100% liquidated.
function absorb(uint vaultVariables_, int maxTick_) public _verifyCaller returns (uint) {
AbsorbMemoryVariables memory a_;
// Temporary holder variables, used many times for different small few liner things
uint temp_;
uint temp2_;
TickHasDebt memory tickHasDebt_;
{
// liquidating ticks above max ratio
// temp_ -> top tick
temp_ = ((vaultVariables_ >> 2) & X20);
// increasing startingTick_ by 1 so the current tick comes into looping equation
a_.startingTick = (temp_ & 1) == 1 ? (int(temp_ >> 1) + 1) : (-int(temp_ >> 1) + 1);
tickHasDebt_.mapId = a_.startingTick < 0 ? ((a_.startingTick + 1) / 256) - 1 : a_.startingTick / 256;
tickHasDebt_.tickHasDebt = tickHasDebt[tickHasDebt_.mapId];
{
// For last user remaining in vault there could be a lot of while loop.
// Chances of this to happen is extremely low (like ~0%)
tickHasDebt_.nextTick = TickMath.MAX_TICK;
while (true) {
if (tickHasDebt_.tickHasDebt > 0) {
a_.mostSigBit = tickHasDebt_.tickHasDebt.mostSignificantBit();
tickHasDebt_.nextTick = tickHasDebt_.mapId * 256 + int(a_.mostSigBit) - 1;
while (tickHasDebt_.nextTick > maxTick_) {
// storing tickData into temp_
temp_ = tickData[tickHasDebt_.nextTick];
// temp2_ -> tick's debt
temp2_ = (temp_ >> 25) & X64;
// converting big number into normal number
temp2_ = (temp2_ >> 8) << (temp2_ & X8);
// Absorbing tick's debt & collateral
a_.debtAbsorbed += temp2_;
// calculating collateral from debt & ratio and adding to a_.colAbsorbed
a_.colAbsorbed += ((temp2_ * TickMath.ZERO_TICK_SCALED_RATIO) /
TickMath.getRatioAtTick(int24(tickHasDebt_.nextTick)));
// Update tick data on storage. Making tick as 100% liquidated
tickData[tickHasDebt_.nextTick] = 1 | (temp_ & 0x1fffffe) | (1 << 25); // set as 100% liquidated
// temp_ = bits to remove
temp_ = 257 - a_.mostSigBit;
tickHasDebt_.tickHasDebt = (tickHasDebt_.tickHasDebt << temp_) >> temp_;
if (tickHasDebt_.tickHasDebt == 0) break;
a_.mostSigBit = tickHasDebt_.tickHasDebt.mostSignificantBit();
tickHasDebt_.nextTick = tickHasDebt_.mapId * 256 + int(a_.mostSigBit) - 1;
}
// updating tickHasDebt on storage
tickHasDebt[tickHasDebt_.mapId] = tickHasDebt_.tickHasDebt;
}
// tickHasDebt_.tickHasDebt == 0 from here.
if (tickHasDebt_.nextTick <= maxTick_) {
break;
}
if (tickHasDebt_.mapId < -129) {
tickHasDebt_.nextTick = type(int).min;
break;
}
// Fetching next tickHasDebt by decreasing tickHasDebt_.mapId first
tickHasDebt_.tickHasDebt = tickHasDebt[--tickHasDebt_.mapId];
}
}
}
// After the above loop we will get nextTick stored in tickHasDebt_ which we will use to compare & set things in the end
{
TickData memory tickInfo_;
BranchData memory branch_;
// if this remains 0 that means create a new branch over the end
uint newBranchId_;
{
// Liquidate branches in a loop and store the end branch
branch_.id = (vaultVariables_ >> 22) & X30;
branch_.data = branchData[branch_.id];
// Checking if current branch is liquidated
if ((vaultVariables_ & 2) == 0) {
// current branch is not liquidated hence it can be used as a new branch if needed
newBranchId_ = branch_.id;
// Checking the base branch minima tick. temp_ = base branch minima tick
temp_ = (branch_.data >> 196) & X20;
if (temp_ > 0) {
// Setting the base branch as current liquidatable branch
branch_.id = (branch_.data >> 166) & X30;
branch_.data = branchData[branch_.id];
branch_.minimaTick = (temp_ & 1) == 1 ? int(temp_ >> 1) : -int(temp_ >> 1);
} else {
// the current branch is base branch, hence need to setup a new base branch
branch_.id = 0;
branch_.data = 0;
branch_.minimaTick = type(int).min;
}
} else {
// current branch is liquidated
temp_ = (branch_.data >> 2) & X20;
branch_.minimaTick = (temp_ & 1) == 1 ? int(temp_ >> 1) : -int(temp_ >> 1);
}
while (branch_.minimaTick > maxTick_) {
// Check base branch, if exists then check if minima tick is above max tick then liquidate it.
tickInfo_.ratio = TickMath.getRatioAtTick(int24(branch_.minimaTick));
tickInfo_.ratioOneLess = (tickInfo_.ratio * 10000) / 10015;
tickInfo_.length = tickInfo_.ratio - tickInfo_.ratioOneLess;
// partials
tickInfo_.partials = (branch_.data >> 22) & X30;
tickInfo_.currentRatio = tickInfo_.ratioOneLess + ((tickInfo_.length * tickInfo_.partials) / X30);
// debt in branch
temp2_ = (branch_.data >> 52) & X64;
// converting big number into normal number
temp2_ = (temp2_ >> 8) << (temp2_ & X8);
// Absorbing branch's debt & collateral
a_.debtAbsorbed += temp2_;
// calculating branch's collateral using debt & ratio and adding it to a_.colAbsorbed
a_.colAbsorbed += (temp2_ * TickMath.ZERO_TICK_SCALED_RATIO) / tickInfo_.currentRatio;
// Closing branch
branchData[branch_.id] = branch_.data | 3;
// Setting new branch
temp_ = (branch_.data >> 196) & X20; // temp_ -> minima tick of connected branch
if (temp_ > 0) {
// Setting the base branch as current liquidatable branch
branch_.id = (branch_.data >> 166) & X30;
branch_.data = branchData[branch_.id];
branch_.minimaTick = (temp_ & 1) == 1 ? int(temp_ >> 1) : -int(temp_ >> 1);
} else {
// the current branch is base branch, hence need to setup a new base branch
branch_.id = 0;
branch_.data = 0;
branch_.minimaTick = type(int).min;
}
}
}
if (tickHasDebt_.nextTick >= branch_.minimaTick) {
// new top tick is not liquidated
// temp2_ = tick to insert
if (tickHasDebt_.nextTick > type(int).min) {
temp2_ = tickHasDebt_.nextTick < 0
? (uint(-tickHasDebt_.nextTick) << 1)
: ((uint(tickHasDebt_.nextTick) << 1) | 1);
} else {
temp2_ = 0;
}
if (newBranchId_ == 0) {
// initializing a new branch
// newBranchId_ = total current branches + 1
unchecked {
newBranchId_ = ((vaultVariables_ >> 52) & X30) + 1;
}
vaultVariables_ =
((vaultVariables_ >> 82) << 82) |
(temp2_ << 2) |
(newBranchId_ << 22) |
(newBranchId_ << 52);
} else {
// using already initialized non liquidated branch
vaultVariables_ = ((vaultVariables_ >> 22) << 22) | (temp2_ << 2);
}
if (branch_.minimaTick > type(int).min) {
temp2_ = branch_.minimaTick < 0
? (uint(-branch_.minimaTick) << 1)
: ((uint(branch_.minimaTick) << 1) | 1);
// set base branch id and minima tick
branchData[newBranchId_] = (branch_.id << 166) | (temp2_ << 196);
} else {
// new base branch does not have any connected branch
branchData[newBranchId_] = 0;
}
} else {
// new top tick is liquidated
temp2_ = branch_.minimaTick < 0
? (uint(-branch_.minimaTick) << 1)
: ((uint(branch_.minimaTick) << 1) | 1);
if (newBranchId_ == 0) {
vaultVariables_ = ((vaultVariables_ >> 52) << 52) | 2 | (temp2_ << 2) | (branch_.id << 22);
} else {
// uninitializing the non liquidated branch
vaultVariables_ =
((vaultVariables_ >> 82) << 82) |
2 |
(temp2_ << 2) |
(branch_.id << 22) |
((newBranchId_ - 1) << 52); // decreasing total branch by 1
branchData[newBranchId_] = 0;
}
}
}
// updating absorbed liquidity on storage
absorbedLiquidity = absorbedLiquidity + a_.debtAbsorbed + (a_.colAbsorbed << 128);
emit LogAbsorb(a_.colAbsorbed, a_.debtAbsorbed);
// returning updated vault variables
return vaultVariables_;
}
/// @dev Checks total supply of vault's in Liquidity Layer & Vault contract and rebalance it accordingly
/// if vault supply is more than Liquidity Layer then deposit difference through reserve/rebalance contract
/// if vault supply is less than Liquidity Layer then withdraw difference to reserve/rebalance contract
/// if vault borrow is more than Liquidity Layer then borrow difference to reserve/rebalance contract
/// if vault borrow is less than Liquidity Layer then payback difference through reserve/rebalance contract
function rebalance() external payable _verifyCaller returns (int supplyAmt_, int borrowAmt_) {
if (msg.sender != rebalancer) {
revert FluidVaultError(ErrorTypes.Vault__NotRebalancer);
}
uint vaultVariables_ = vaultVariables;
// ############# turning re-entrancy bit on #############
if (vaultVariables_ & 1 == 0) {
// Updating on storage
vaultVariables = vaultVariables_ | 1;
} else {
revert FluidVaultError(ErrorTypes.Vault__AlreadyEntered);
}
IFluidVaultT1.ConstantViews memory constants_ = IFluidVaultT1(address(this)).constantsView();
if (msg.value > 0 && !(constants_.supplyToken == NATIVE_TOKEN || constants_.borrowToken == NATIVE_TOKEN)) {
revert FluidVaultError(ErrorTypes.Vault__InvalidMsgValueInRebalance);
}
IFluidLiquidity liquidity_ = IFluidLiquidity(constants_.liquidity);
RebalanceMemoryVariables memory r_;
(r_.liqSupplyExPrice, r_.liqBorrowExPrice, r_.vaultSupplyExPrice, r_.vaultBorrowExPrice) = IFluidVaultT1(
address(this)
).updateExchangePrices(vaultVariables2);
// extract vault supply at Liquidity -> 64 bits starting from bit 1 (first bit is interest mode)
uint totalSupplyLiquidity_ = (liquidity_.readFromStorage(constants_.liquidityUserSupplySlot) >>
LiquiditySlotsLink.BITS_USER_SUPPLY_AMOUNT) & X64;
totalSupplyLiquidity_ = (totalSupplyLiquidity_ >> 8) << (totalSupplyLiquidity_ & X8);
totalSupplyLiquidity_ =
(totalSupplyLiquidity_ * r_.liqSupplyExPrice) /
LiquidityCalcs.EXCHANGE_PRICES_PRECISION;
// extract vault borrowings at Liquidity -> 64 bits starting from bit 1 (first bit is interest mode)
uint totalBorrowLiquidity_ = (liquidity_.readFromStorage(constants_.liquidityUserBorrowSlot) >>
LiquiditySlotsLink.BITS_USER_BORROW_AMOUNT) & X64;
totalBorrowLiquidity_ = (totalBorrowLiquidity_ >> 8) << (totalBorrowLiquidity_ & X8);
totalBorrowLiquidity_ =
(totalBorrowLiquidity_ * r_.liqBorrowExPrice) /
LiquidityCalcs.EXCHANGE_PRICES_PRECISION;
uint totalSupplyVault_ = (vaultVariables_ >> 82) & X64;
totalSupplyVault_ = (totalSupplyVault_ >> 8) << (totalSupplyVault_ & X8);
totalSupplyVault_ = (totalSupplyVault_ * r_.vaultSupplyExPrice) / LiquidityCalcs.EXCHANGE_PRICES_PRECISION;
uint totalBorrowVault_ = (vaultVariables_ >> 146) & X64;
totalBorrowVault_ = (totalBorrowVault_ >> 8) << (totalBorrowVault_ & X8);
totalBorrowVault_ = (totalBorrowVault_ * r_.vaultBorrowExPrice) / LiquidityCalcs.EXCHANGE_PRICES_PRECISION;
uint value_;
if (totalSupplyVault_ > totalSupplyLiquidity_) {
// Fetch tokens from revenue/rebalance contract and supply in liquidity contract
// This is the scenario when the supply rewards are going in vault, hence
// the vault total supply is increasing at a higher pace than Liquidity contract.
// We are not transferring rewards right when we set the rewards to keep things clean.
// Also, this can also happen in case when supply rate magnifier is greater than 1.
supplyAmt_ = int(totalSupplyVault_) - int(totalSupplyLiquidity_);
if (constants_.supplyToken == NATIVE_TOKEN) {
if (msg.value > uint(supplyAmt_)) {
value_ = uint(supplyAmt_);
SafeTransfer.safeTransferNative(msg.sender, msg.value - value_); // sending back excess ETH
} else {
value_ = msg.value; // setting amount as msg.value
}
supplyAmt_ = int(value_);
} else {
value_ = 0;
}
try liquidity_.operate{ value: value_ }(
constants_.supplyToken,
supplyAmt_,
0,
address(0),
address(0),
abi.encode(rebalancer)
) {
// if success then do nothing
} catch {
supplyAmt_ = 0;
}
} else if (totalSupplyLiquidity_ > totalSupplyVault_) {
if (constants_.supplyToken == NATIVE_TOKEN && msg.value > 0) {
revert FluidVaultError(ErrorTypes.Vault__InvalidMsgValueInRebalance);
}
// Withdraw from Liquidity contract and send it to revenue contract.
// This is the scenario when the vault user's are getting less ETH APR than what's going on Liquidity contract.
// When supply rate magnifier is less than 1.
supplyAmt_ = int(totalSupplyVault_) - int(totalSupplyLiquidity_);
try liquidity_.operate(constants_.supplyToken, supplyAmt_, 0, rebalancer, address(0), new bytes(0)) {
// if success then do nothing
} catch {
supplyAmt_ = 0;
}
}
if (totalBorrowVault_ > totalBorrowLiquidity_) {
if (constants_.borrowToken == NATIVE_TOKEN && msg.value > 0) {
revert FluidVaultError(ErrorTypes.Vault__InvalidMsgValueInRebalance);
}
// Borrow from Liquidity contract and send to revenue/rebalance contract
// This is the scenario when the vault is charging more borrow to user than the Liquidity contract.
// When borrow rate magnifier is greater than 1.
borrowAmt_ = int(totalBorrowVault_) - int(totalBorrowLiquidity_);
try liquidity_.operate(constants_.borrowToken, 0, borrowAmt_, address(0), rebalancer, new bytes(0)) {
// if success then do nothing
} catch {
borrowAmt_ = 0;
}
} else if (totalBorrowLiquidity_ > totalBorrowVault_) {
// Transfer from revenue/rebalance contract and payback on Liquidity contract
// This is the scenario when vault protocol is earning rewards so effective borrow rate for users is low.
// Or the case where borrow rate magnifier is less than 1
borrowAmt_ = int(totalBorrowLiquidity_) - int(totalBorrowVault_);
if (constants_.borrowToken == NATIVE_TOKEN) {
if (msg.value > uint(borrowAmt_)) {
value_ = uint(borrowAmt_);
SafeTransfer.safeTransferNative(msg.sender, msg.value - value_);
} else {
value_ = msg.value; // setting amount as msg.value
}
borrowAmt_ = int(value_);
} else {
value_ = 0;
}
borrowAmt_ = -borrowAmt_;
try liquidity_.operate{ value: value_ }(
constants_.borrowToken,
0,
borrowAmt_,
address(0),
address(0),
abi.encode(rebalancer)
) {
// if success then do nothing
} catch {
borrowAmt_ = 0;
}
}
if (supplyAmt_ == 0 && borrowAmt_ == 0) {
revert FluidVaultError(ErrorTypes.Vault__NothingToRebalance);
}
// Updating vault variable on storage to turn off the reentrancy bit
vaultVariables = vaultVariables_;
emit LogRebalance(supplyAmt_, borrowAmt_);
}
}
// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.21;
contract Structs {
// structs are used to mitigate Stack too deep errors
struct OperateMemoryVars {
// ## User's position before update ##
uint oldColRaw;
uint oldNetDebtRaw; // total debt - dust debt
int oldTick;
// ## User's position after update ##
uint colRaw;
uint debtRaw;
uint dustDebtRaw;
int tick;
uint tickId;
// others
uint256 vaultVariables2;
uint256 branchId;
int256 topTick;
uint liquidityExPrice;
uint supplyExPrice;
uint borrowExPrice;
uint branchData;
// user's supply slot data in liquidity
uint userSupplyLiquidityData;
}
struct BranchData {
uint id;
uint data;
uint ratio;
uint debtFactor;
int minimaTick;
uint baseBranchData;
}
struct TickData {
int tick;
uint data;
uint ratio;
uint ratioOneLess;
uint length;
uint currentRatio; // current tick is ratio with partials.
uint partials;
}
// note: All the below token amounts are in raw form.
struct CurrentLiquidity {
uint256 debtRemaining; // Debt remaining to liquidate
uint256 debt; // Current liquidatable debt before reaching next check point
uint256 col; // Calculate using debt & ratioCurrent
uint256 colPerDebt; // How much collateral to liquidate per unit of Debt
uint256 totalDebtLiq; // Total debt liquidated till now
uint256 totalColLiq; // Total collateral liquidated till now
int tick; // Current tick to liquidate
uint ratio; // Current ratio to liquidate
uint tickStatus; // if 1 then it's a perfect tick, if 2 that means it's a liquidated tick
int refTick; // ref tick to liquidate
uint refRatio; // ratio at ref tick
uint refTickStatus; // if 1 then it's a perfect tick, if 2 that means it's a liquidated tick, if 3 that means it's a liquidation threshold
}
struct TickHasDebt {
int tick; // current tick
int nextTick; // next tick with liquidity
int mapId; // mapping ID of tickHasDebt
uint bitsToRemove; // liquidity to remove till tick_ so we can search for next tick
uint tickHasDebt; // getting tickHasDebt_ from tickHasDebt[mapId_]
uint mostSigBit; // most significant bit in tickHasDebt_ to get the next tick
}
struct LiquidateMemoryVars {
uint256 vaultVariables2;
int liquidationTick;
int maxTick;
uint256 supplyExPrice;
uint256 borrowExPrice;
}
struct AbsorbMemoryVariables {
uint256 debtAbsorbed;
uint256 colAbsorbed;
int256 startingTick;
uint256 mostSigBit;
}
struct ConstantViews {
address liquidity;
address factory;
address adminImplementation;
address secondaryImplementation;
address supplyToken;
address borrowToken;
uint8 supplyDecimals;
uint8 borrowDecimals;
uint vaultId;
bytes32 liquiditySupplyExchangePriceSlot;
bytes32 liquidityBorrowExchangePriceSlot;
bytes32 liquidityUserSupplySlot;
bytes32 liquidityUserBorrowSlot;
}
struct RebalanceMemoryVariables {
uint256 liqSupplyExPrice;
uint256 liqBorrowExPrice;
uint256 vaultSupplyExPrice;
uint256 vaultBorrowExPrice;
}
}