Source Code
Overview
ETH Balance
0 ETH
Eth Value
$0.00| Transaction Hash |
Method
|
Block
|
From
|
|
To
|
||||
|---|---|---|---|---|---|---|---|---|---|
Latest 1 internal transaction
Advanced mode:
| Parent Transaction Hash | Method | Block |
From
|
|
To
|
||
|---|---|---|---|---|---|---|---|
| 0x60806040 | 24002353 | 71 days ago | Contract Creation | 0 ETH |
Loading...
Loading
Loading...
Loading
Cross-Chain Transactions
Loading...
Loading
Contract Name:
FluidLiquidityUserModuleMainnet
Compiler Version
v0.8.29+commit.ab55807c
Optimization Enabled:
Yes with 10000000 runs
Other Settings:
cancun EvmVersion
Contract Source Code (Solidity Standard Json-Input format)
// SPDX-License-Identifier: BUSL-1.1
pragma solidity >=0.8.21 <=0.8.29;
import { FluidLiquidityUserModule } from "./main.sol";
import { CommonHelpersMainnet } from "../common/helpersMainnet.sol";
contract FluidLiquidityUserModuleMainnet is FluidLiquidityUserModule, CommonHelpersMainnet {}// SPDX-License-Identifier: MIT // OpenZeppelin Contracts v4.4.1 (interfaces/IERC20.sol) pragma solidity ^0.8.0; import "../token/ERC20/IERC20.sol";
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.6.0) (token/ERC20/IERC20.sol)
pragma solidity ^0.8.0;
/**
* @dev Interface of the ERC20 standard as defined in the EIP.
*/
interface IERC20 {
/**
* @dev Emitted when `value` tokens are moved from one account (`from`) to
* another (`to`).
*
* Note that `value` may be zero.
*/
event Transfer(address indexed from, address indexed to, uint256 value);
/**
* @dev Emitted when the allowance of a `spender` for an `owner` is set by
* a call to {approve}. `value` is the new allowance.
*/
event Approval(address indexed owner, address indexed spender, uint256 value);
/**
* @dev Returns the amount of tokens in existence.
*/
function totalSupply() external view returns (uint256);
/**
* @dev Returns the amount of tokens owned by `account`.
*/
function balanceOf(address account) external view returns (uint256);
/**
* @dev Moves `amount` tokens from the caller's account to `to`.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* Emits a {Transfer} event.
*/
function transfer(address to, uint256 amount) external returns (bool);
/**
* @dev Returns the remaining number of tokens that `spender` will be
* allowed to spend on behalf of `owner` through {transferFrom}. This is
* zero by default.
*
* This value changes when {approve} or {transferFrom} are called.
*/
function allowance(address owner, address spender) external view returns (uint256);
/**
* @dev Sets `amount` as the allowance of `spender` over the caller's tokens.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* IMPORTANT: Beware that changing an allowance with this method brings the risk
* that someone may use both the old and the new allowance by unfortunate
* transaction ordering. One possible solution to mitigate this race
* condition is to first reduce the spender's allowance to 0 and set the
* desired value afterwards:
* https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
*
* Emits an {Approval} event.
*/
function approve(address spender, uint256 amount) external returns (bool);
/**
* @dev Moves `amount` tokens from `from` to `to` using the
* allowance mechanism. `amount` is then deducted from the caller's
* allowance.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* Emits a {Transfer} event.
*/
function transferFrom(
address from,
address to,
uint256 amount
) external returns (bool);
}// SPDX-License-Identifier: BUSL-1.1
pragma solidity >=0.8.21 <=0.8.29;
/// @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.
/// 5035703444687813576399584 = 10000101010010110100000011111011110010100110100000000011100101001101001101011100000
/// 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)
}
}
}
/// @dev gets the least significant bit `firstBit` of a `normal` number (position of rightmost 1 in binary format).
/// e.g.
/// 5035703444687813576399584 = 10000101010010110100000011111011110010100110100000000011100101001101001101011100000
/// firstBit = ^-6--^
function leastSignificantBit(uint256 normal) internal pure returns (uint firstBit) {
assembly {
// If number is 0, revert as there is no least significant bit
if iszero(normal) {
revert(0, 0)
}
// Find first set bit using binary search
let number_ := normal
firstBit := 0
// Check if lower 128 bits are all zero
if iszero(and(number_, 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF)) {
number_ := shr(0x80, number_)
firstBit := 0x80
}
// Check if lower 64 bits are all zero
if iszero(and(number_, 0xFFFFFFFFFFFFFFFF)) {
number_ := shr(0x40, number_)
firstBit := add(firstBit, 0x40)
}
// Check if lower 32 bits are all zero
if iszero(and(number_, 0xFFFFFFFF)) {
number_ := shr(0x20, number_)
firstBit := add(firstBit, 0x20)
}
// Check if lower 16 bits are all zero
if iszero(and(number_, 0xFFFF)) {
number_ := shr(0x10, number_)
firstBit := add(firstBit, 0x10)
}
// Check if lower 8 bits are all zero
if iszero(and(number_, 0xFF)) {
number_ := shr(0x8, number_)
firstBit := add(firstBit, 0x8)
}
// Check if lower 4 bits are all zero
if iszero(and(number_, 0xF)) {
number_ := shr(0x4, number_)
firstBit := add(firstBit, 0x4)
}
// Check if lower 2 bits are all zero
if iszero(and(number_, 0x3)) {
number_ := shr(0x2, number_)
firstBit := add(firstBit, 0x2)
}
// Check if lowest bit is zero
if iszero(and(number_, 0x1)) {
firstBit := add(firstBit, 1)
}
// Add 1 to match the 1-based position counting
firstBit := add(firstBit, 1)
}
}
}// SPDX-License-Identifier: BUSL-1.1
pragma solidity >=0.8.21 <=0.8.29;
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 <=0.8.29;
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% + 25%) = 62,5%
// supplyRate = (40% * (100% - 10%)) * 62,5% = 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 at least 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 at least 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 end up with maxExpansionLimit_ = 0 because userBorrow = 0, so maxExpandedBorrowLimit_ = 0,
// so currentBorrowLimit_ = 0, so it will enter currentBorrowLimit_ < temp_ when checking against base limit as expected.
// 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 at least 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_;
}
// 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 base borrow limit, if current limit is below this, then set base limit as current limit
temp_ = (userBorrowData_ >> LiquiditySlotsLink.BITS_USER_BORROW_BASE_BORROW_LIMIT) & X18;
temp_ = (temp_ >> DEFAULT_EXPONENT_SIZE) << (temp_ & DEFAULT_EXPONENT_MASK);
if (currentBorrowLimit_ < temp_) {
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);
if (currentBorrowLimit_ > temp_) {
return 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_) - int256(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_) - int256(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 <=0.8.29;
/// @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 configs2 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_DECAY_AMOUNT = 218;
uint256 internal constant BITS_USER_SUPPLY_DECAY_DURATION_CHECKPOINTS = 244;
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 <=0.8.29;
import { LibsErrorTypes as ErrorTypes } from "./errorTypes.sol";
/// @notice provides minimalistic methods for safe transfers, e.g. ERC20 safeTransferFrom
library SafeTransfer {
// pass max. 50k gas for native transfers. This ensures support for some realistic
// scenarios like WETH deposits in receive() but as restrictive as possible.
uint256 internal constant MAX_NATIVE_TRANSFER_GAS = 50000;
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 <=0.8.29;
import { Variables } from "./variables.sol";
import { ErrorTypes } from "../errorTypes.sol";
import { Error } from "../error.sol";
/// @dev ReentrancyGuard based on OpenZeppelin implementation.
/// https://github.com/OpenZeppelin/openzeppelin-contracts/blob/release-v4.8/contracts/security/ReentrancyGuard.sol
abstract contract ReentrancyGuard is Variables, Error {
uint8 internal constant REENTRANCY_NOT_ENTERED = 1;
uint8 internal constant REENTRANCY_ENTERED = 2;
constructor() {
// on logic contracts, switch reentrancy to entered so no call is possible (forces delegatecall)
_status = REENTRANCY_ENTERED;
}
/// @dev Prevents a contract from calling itself, directly or indirectly.
/// See OpenZeppelin implementation for more info
modifier reentrancy() {
// On the first call to nonReentrant, _status will be NOT_ENTERED
if (_status == REENTRANCY_ENTERED) {
revert FluidLiquidityError(ErrorTypes.LiquidityHelpers__Reentrancy);
}
// Any calls to nonReentrant after this point will fail
_status = REENTRANCY_ENTERED;
_;
// By storing the original value once again, a refund is triggered (see
// https://eips.ethereum.org/EIPS/eip-2200)
_status = REENTRANCY_NOT_ENTERED;
}
}
abstract contract CommonHelpers is ReentrancyGuard {
/// @dev Returns the current admin (governance).
function _getGovernanceAddr() internal view returns (address governance_) {
assembly {
governance_ := sload(GOVERNANCE_SLOT)
}
}
/// @notice Hook invoked after assets are transferred into the contract. Must be implemented for chain specific implementation version.
/// @dev ATTENTION: THIS IS NOT CALLED FOR NATIVE TOKEN (e.g. ETH).
/// @param token_ The address of the transferred-in token.
/// @param amount_ The amount of tokens transferred in.
function _afterTransferIn(address token_, uint256 amount_) internal virtual;
/// @notice Hook invoked before assets are transferred out of the contract. Must be implemented for chain specific implementation version.
/// @dev ATTENTION: THIS IS NOT CALLED FOR NATIVE TOKEN (e.g. ETH).
/// @param token_ The address of the token to be transferred out.
/// @param amount_ The amount of tokens to be transferred out.
function _preTransferOut(address token_, uint256 amount_) internal virtual;
/// @notice Returns the contract's own balance of the given token currently re-hypothecated or otherwise sitting externally.
/// @param token_ The address of the token to check.
/// @return The total balance of the contract re-hypothecated assets.
function _getExternalBalances(address token_) internal view virtual returns (uint256);
}// SPDX-License-Identifier: BUSL-1.1
pragma solidity >=0.8.21 <=0.8.29;
import { IERC20 } from "@openzeppelin/contracts/interfaces/IERC20.sol";
import { CommonHelpers } from "./helpers.sol";
interface IZtakingPool {
///@notice Stake a specified amount of a particular supported token into the Ztaking Pool
///@param _token The token to deposit/stake in the Ztaking Pool
///@param _for The user to deposit/stake on behalf of
///@param _amount The amount of token to deposit/stake into the Ztaking Pool
function depositFor(address _token, address _for, uint256 _amount) external;
///@notice Withdraw a specified amount of a particular supported token previously staked into the Ztaking Pool
///@param _token The token to withdraw from the Ztaking Pool
///@param _amount The amount of token to withdraw from the Ztaking Pool
function withdraw(address _token, uint256 _amount) external;
function balance(address token_, address staker_) external view returns (uint256);
}
/// @notice Mainnet specific implementation of CommonHelpers.
/// @dev This contract contains chain-specific logic for mainnet. It overrides the virtual methods defined in CommonHelpers (see helpers.sol).
abstract contract CommonHelpersMainnet is CommonHelpers {
address private constant WEETH = 0xCd5fE23C85820F7B72D0926FC9b05b43E359b7ee;
address private constant WEETHS = 0x917ceE801a67f933F2e6b33fC0cD1ED2d5909D88;
IZtakingPool private constant ZIRCUIT = IZtakingPool(0xF047ab4c75cebf0eB9ed34Ae2c186f3611aEAfa6);
/// @notice Hook allowing logic after assets are transferred in
function _afterTransferIn(address token_, uint256 amount_) internal override {
// temporary rehypo addition for weETH & weETHs: if token is weETH or weETHs -> deposit to Zircuit
if (token_ == WEETH) {
if (IERC20(WEETH).allowance(address(this), address(ZIRCUIT)) > 0) {
ZIRCUIT.depositFor(WEETH, address(this), amount_);
}
} else if (token_ == WEETHS) {
if ((IERC20(WEETHS).allowance(address(this), address(ZIRCUIT)) > 0)) {
ZIRCUIT.depositFor(WEETHS, address(this), amount_);
}
}
// temporary code also includes: WEETH, WEETHS & ZIRCUIT constant, IZtakingPool interface
}
/// @notice Hook allowing logic before assets are transferred out
function _preTransferOut(address token_, uint256 amount_) internal override {
// temporary rehypo addition for weETH & weETHs: if token is weETH or weETHs -> withdraw from Zircuit
if (token_ == WEETH) {
if ((IERC20(WEETH).balanceOf(address(this)) < amount_)) {
ZIRCUIT.withdraw(WEETH, amount_);
}
} else if (token_ == WEETHS) {
if ((IERC20(WEETHS).balanceOf(address(this)) < amount_)) {
ZIRCUIT.withdraw(WEETHS, amount_);
}
}
// temporary code also includes: WEETH, WEETHS & ZIRCUIT constant, IZtakingPool interface
}
function _getExternalBalances(address token_) internal view override returns (uint256 balanceOf_) {
if (token_ == WEETH) {
balanceOf_ += ZIRCUIT.balance(WEETH, address(this));
} else if (token_ == WEETHS) {
balanceOf_ += ZIRCUIT.balance(WEETHS, address(this));
}
}
}// SPDX-License-Identifier: BUSL-1.1
pragma solidity >=0.8.21 <=0.8.29;
abstract contract ConstantVariables {
/// @dev Storage slot with the admin of the contract. Logic from "proxy.sol".
/// This is the keccak-256 hash of "eip1967.proxy.admin" subtracted by 1, and is validated in the constructor.
bytes32 internal constant GOVERNANCE_SLOT = 0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103;
uint256 internal constant EXCHANGE_PRICES_PRECISION = 1e12;
/// @dev address that is mapped to the chain native token
address internal constant NATIVE_TOKEN_ADDRESS = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE;
/// @dev decimals for native token
// !! Double check compatibility with all code if this ever changes for a deployment !!
uint8 internal constant NATIVE_TOKEN_DECIMALS = 18;
/// @dev Minimum token decimals for any token that can be listed at Liquidity (inclusive)
uint8 internal constant MIN_TOKEN_DECIMALS = 6;
/// @dev Maximum token decimals for any token that can be listed at Liquidity (inclusive)
uint8 internal constant MAX_TOKEN_DECIMALS = 18;
/// @dev Ignoring leap years
uint256 internal constant SECONDS_PER_YEAR = 365 days;
/// @dev limit any total amount to be half of type(uint128).max (~3.4e38) at type(int128).max (~1.7e38) as safety
/// measure for any potential overflows / unexpected outcomes. This is checked for total borrow / supply.
uint256 internal constant MAX_TOKEN_AMOUNT_CAP = uint256(uint128(type(int128).max));
/// @dev limit for triggering a revert if sent along excess input amount diff is bigger than this percentage (in 1e2)
uint256 internal constant MAX_INPUT_AMOUNT_EXCESS = 100; // 1%
/// @dev if this bytes32 is set in the calldata, then token transfers are skipped as long as Liquidity layer is on the winning side.
bytes32 internal constant SKIP_TRANSFERS = keccak256(bytes("SKIP_TRANSFERS"));
/// @dev if this bytes32 is set in the calldata, then token transfers are only done for net input - output.
bytes32 internal constant NET_TRANSFERS = keccak256(bytes("NET_TRANSFERS"));
int256 internal constant SKIP_TRANSFER_OUT_BELOW_VALUE_SIGNAL = -1;
/// @dev time after which a write to storage of exchangePricesAndConfig will happen always.
uint256 internal constant FORCE_STORAGE_WRITE_AFTER_TIME = 1 days;
/// @dev target duration for new decays on deposit amounts that push the withdrawal limit up. effective duration depends on
/// existing decay amount ratio to new decay amount.
uint256 internal constant TOTAL_DECAY_CHECKPOINTS = 1e3;
uint256 internal constant MIN_DECAY_DURATION_CHECKPOINTS = 80; // 80 checkpoints =4m 48s: minimum decay duration after a new deposit, depending on ratio.
uint256 internal constant DECAY_CHECKPOINT_DURATION_SCALEDX10 = 36; // 3.6s * 10
/// @dev when a new operate amount is > 10_000 * existing total amount AND the amount is bigger than 2 ** 80 -> revert.
/// extra input validation safety check.
uint256 internal constant MAX_NEW_VS_EXISTING_TOTAL_AMOUNT_RATIO_DEPOSIT_BORROW = 10_000;
uint256 internal constant MAX_NEW_VS_EXISTING_TOTAL_AMOUNT_RATIO_WITHDRAW_PAYBACK = 2; // effectively 50% in one go
uint256 internal constant MAX_NEW_AMOUNT_WHEN_RATIO_CHECK = 2 ** 80; // 1.208.925,819614629174706176
/// @dev constants used for BigMath conversion from and to storage
uint256 internal constant DECAY_COEFFICIENT_SIZE = 18;
uint256 internal constant SMALL_COEFFICIENT_SIZE = 10;
uint256 internal constant DEFAULT_COEFFICIENT_SIZE = 56;
uint256 internal constant DEFAULT_EXPONENT_SIZE = 8;
uint256 internal constant DEFAULT_EXPONENT_MASK = 0xFF;
/// @dev constants to increase readability for using bit masks
uint256 internal constant FOUR_DECIMALS = 1e4;
uint256 internal constant TWELVE_DECIMALS = 1e12;
uint256 internal constant X8 = 0xff;
uint256 internal constant X10 = 0x3ff;
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 X26 = 0x3ffffff;
uint256 internal constant X33 = 0x1ffffffff;
uint256 internal constant X64 = 0xffffffffffffffff;
// protocol identifier for dexV2
bytes32 internal constant DEXV2_IDENTIFIER = keccak256(bytes("DEXV2"));
}
abstract contract Variables is ConstantVariables {
/// @dev address of contract that gets sent the revenue. Configurable by governance
address internal _revenueCollector;
// 12 bytes empty
// ----- storage slot 1 ------
/// @dev paused status: status = 1 -> normal. status = 2 -> paused.
/// not tightly packed with revenueCollector address to allow for potential changes later that improve gas more
/// (revenueCollector is only rarely used by admin methods, where optimization is not as important).
/// to be replaced with transient storage once EIP-1153 Transient storage becomes available with dencun upgrade.
uint256 internal _status;
// ----- storage slot 2 ------
/// @dev Auths can set most config values. E.g. contracts that automate certain flows like e.g. adding a new fToken.
/// Governance can add/remove auths.
/// Governance is auth by default
mapping(address => uint256) internal _isAuth;
// ----- storage slot 3 ------
/// @dev Guardians can pause lower class users
/// Governance can add/remove guardians
/// Governance is guardian by default
mapping(address => uint256) internal _isGuardian;
// ----- storage slot 4 ------
/// @dev class defines which protocols can be paused by guardians
/// Currently there are 2 classes: 0 can be paused by guardians. 1 cannot be paused by guardians.
/// New protocols are added as class 0 and will be upgraded to 1 over time.
mapping(address => uint256) internal _userClass;
// ----- storage slot 5 ------
/// @dev exchange prices and token config per token: token -> exchange prices & config
/// First 16 bits => 0- 15 => borrow rate (in 1e2: 100% = 10_000; 1% = 100 -> max value 65535)
/// Next 14 bits => 16- 29 => fee on interest from borrowers to lenders (in 1e2: 100% = 10_000; 1% = 100 -> max value 16_383). configurable.
/// Next 14 bits => 30- 43 => last stored utilization (in 1e2: 100% = 10_000; 1% = 100 -> max value 16_383)
/// Next 14 bits => 44- 57 => update on storage threshold (in 1e2: 100% = 10_000; 1% = 100 -> max value 16_383). configurable.
/// Next 33 bits => 58- 90 => last update timestamp (enough until 16 March 2242 -> max value 8589934591)
/// Next 64 bits => 91-154 => supply exchange price (1e12 -> max value 18_446_744,073709551615)
/// Next 64 bits => 155-218 => borrow exchange price (1e12 -> max value 18_446_744,073709551615)
/// Next 1 bit => 219-219 => if 0 then ratio is supplyInterestFree / supplyWithInterest else ratio is supplyWithInterest / supplyInterestFree
/// Next 14 bits => 220-233 => supplyRatio: supplyInterestFree / supplyWithInterest (in 1e2: 100% = 10_000; 1% = 100 -> max value 16_383)
/// Next 1 bit => 234-234 => if 0 then ratio is borrowInterestFree / borrowWithInterest else ratio is borrowWithInterest / borrowInterestFree
/// Next 14 bits => 235-248 => borrowRatio: borrowInterestFree / borrowWithInterest (in 1e2: 100% = 10_000; 1% = 100 -> max value 16_383)
/// Next 1 bit => 249-249 => flag for token uses config storage slot 2. (signals SLOAD for additional config slot is needed during execution)
/// Last 6 bits => 250-255 => empty for future use
/// if more free bits are needed in the future, update on storage threshold bits could be reduced to 7 bits
/// (can plan to add `MAX_TOKEN_CONFIG_UPDATE_THRESHOLD` but need to adjust more bits)
/// if more bits absolutely needed then we can convert fee, utilization, update on storage threshold,
/// supplyRatio & borrowRatio from 14 bits to 10bits (1023 max number) where 1000 = 100% & 1 = 0.1%
mapping(address => uint256) internal _exchangePricesAndConfig;
// ----- storage slot 6 ------
/// @dev Rate related data per token: token -> rate data
/// READ (SLOAD): all actions; WRITE (SSTORE): only on set config admin actions
/// token => rate related data
/// First 4 bits => 0-3 => rate version
/// rest of the bits are rate dependent:
/// 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 => empty for future use
/// 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 => empty for future use
mapping(address => uint256) internal _rateData;
// ----- storage slot 7 ------
/// @dev total supply / borrow amounts for with / without interest per token: token -> amounts
/// First 64 bits => 0- 63 => total supply with interest in raw (totalSupply = totalSupplyRaw * supplyExchangePrice); BigMath: 56 | 8
/// Next 64 bits => 64-127 => total interest free supply in normal token amount (totalSupply = totalSupply); BigMath: 56 | 8
/// Next 64 bits => 128-191 => total borrow with interest in raw (totalBorrow = totalBorrowRaw * borrowExchangePrice); BigMath: 56 | 8
/// Next 64 bits => 192-255 => total interest free borrow in normal token amount (totalBorrow = totalBorrow); BigMath: 56 | 8
mapping(address => uint256) internal _totalAmounts;
// ----- storage slot 8 ------
/// @dev user supply data per token: user -> token -> data
/// First 1 bit => 0 => mode: user supply with or without interest
/// 0 = without, amounts are in normal (i.e. no need to multiply with exchange price)
/// 1 = with interest, amounts are in raw (i.e. must multiply with exchange price to get actual token amounts)
/// Next 64 bits => 1- 64 => user supply amount (normal or raw depends on 1st bit); BigMath: 56 | 8
/// Next 64 bits => 65-128 => previous user withdrawal limit (normal or raw depends on 1st bit); BigMath: 56 | 8
/// Next 33 bits => 129-161 => last triggered process timestamp (enough until 16 March 2242 -> max value 8589934591)
/// Next 14 bits => 162-175 => expand withdrawal limit percentage (in 1e2: 100% = 10_000; 1% = 100 -> max value 16_383)
/// @dev shrinking is instant, available decay is taken first
/// Next 24 bits => 176-199 => withdrawal limit expand duration in seconds.(Max value 16_777_215; ~4_660 hours, ~194 days)
/// Next 18 bits => 200-217 => base withdrawal limit: below this, 100% withdrawals can be done (normal or raw depends on 1st bit); BigMath: 10 | 8
/// Next 26 bits => 218-243 => decay amount per second; BigMath 18 | 8
/// Next 10 bits => 244-253 => decay duration checkpoints, max 1023. precision = 0.1% so down to 0.1% of 60 minutes = 0.06 minutes = ~3.6 seconds (1 checkpoint)
/// Next 1 bit => 254-254 => empty for future use
/// Last bit => 255-255 => is user paused (1 = paused, 0 = not paused)
mapping(address => mapping(address => uint256)) internal _userSupplyData;
// ----- storage slot 9 ------
/// @dev user borrow data per token: user -> token -> data
/// First 1 bit => 0 => mode: user borrow with or without interest
/// 0 = without, amounts are in normal (i.e. no need to multiply with exchange price)
/// 1 = with interest, amounts are in raw (i.e. must multiply with exchange price to get actual token amounts)
/// Next 64 bits => 1- 64 => user borrow amount (normal or raw depends on 1st bit); BigMath: 56 | 8
/// Next 64 bits => 65-128 => previous user debt ceiling (normal or raw depends on 1st bit); BigMath: 56 | 8
/// Next 33 bits => 129-161 => last triggered process timestamp (enough until 16 March 2242 -> max value 8589934591)
/// Next 14 bits => 162-175 => expand debt ceiling percentage (in 1e2: 100% = 10_000; 1% = 100 -> max value 16_383)
/// @dev shrinking is instant
/// Next 24 bits => 176-199 => debt ceiling expand duration in seconds (Max value 16_777_215; ~4_660 hours, ~194 days)
/// Next 18 bits => 200-217 => base debt ceiling: below this, there's no debt ceiling limits (normal or raw depends on 1st bit); BigMath: 10 | 8
/// Next 18 bits => 218-235 => max debt ceiling: absolute maximum debt ceiling can expand to (normal or raw depends on 1st bit); BigMath: 10 | 8
/// Next 19 bits => 236-254 => empty for future use
/// Last bit => 255-255 => is user paused (1 = paused, 0 = not paused)
mapping(address => mapping(address => uint256)) internal _userBorrowData;
// ----- storage slot 10 ------
/// @dev list of allowed tokens at Liquidity. tokens that are once configured can never be completely removed. so this
/// array is append-only.
address[] internal _listedTokens;
// ----- storage slot 11 ------
/// @dev expanded token configs per token: token -> config data slot 2.
/// Use of this is signaled by `_exchangePricesAndConfig` bit 249.
/// First 14 bits => 0- 13 => max allowed utilization (in 1e2: 100% = 10_000; 1% = 100 -> max value 16_383). configurable.
/// Last 242 bits => 14-255 => empty for future use
mapping(address => uint256) internal _configs2;
}// SPDX-License-Identifier: BUSL-1.1
pragma solidity >=0.8.21 <=0.8.29;
contract Error {
error FluidLiquidityError(uint256 errorId_);
}// SPDX-License-Identifier: BUSL-1.1
pragma solidity >=0.8.21 <=0.8.29;
library ErrorTypes {
/***********************************|
| Admin Module |
|__________________________________*/
/// @notice thrown when an input address is zero
uint256 internal constant AdminModule__AddressZero = 10001;
/// @notice thrown when msg.sender is not governance
uint256 internal constant AdminModule__OnlyGovernance = 10002;
/// @notice thrown when msg.sender is not auth
uint256 internal constant AdminModule__OnlyAuths = 10003;
/// @notice thrown when msg.sender is not guardian
uint256 internal constant AdminModule__OnlyGuardians = 10004;
/// @notice thrown when base withdrawal limit, base debt limit or max withdrawal limit is sent as 0
uint256 internal constant AdminModule__LimitZero = 10005;
/// @notice thrown whenever an invalid input param is given
uint256 internal constant AdminModule__InvalidParams = 10006;
/// @notice thrown if user class 1 is paused (can not be paused)
uint256 internal constant AdminModule__UserNotPausable = 10007;
/// @notice thrown if user is tried to be unpaused but is not paused in the first place
uint256 internal constant AdminModule__UserNotPaused = 10008;
/// @notice thrown if user is not defined yet: Governance didn't yet set any config for this user on a particular token
uint256 internal constant AdminModule__UserNotDefined = 10009;
/// @notice thrown if a token is configured in an invalid order: 1. Set rate config for token 2. Set token config 3. allow any user.
uint256 internal constant AdminModule__InvalidConfigOrder = 10010;
/// @notice thrown if revenue is collected when revenue collector address is not set
uint256 internal constant AdminModule__RevenueCollectorNotSet = 10011;
/// @notice all ValueOverflow errors below are thrown if a certain input param overflows the allowed storage size
uint256 internal constant AdminModule__ValueOverflow__RATE_AT_UTIL_ZERO = 10012;
uint256 internal constant AdminModule__ValueOverflow__RATE_AT_UTIL_KINK = 10013;
uint256 internal constant AdminModule__ValueOverflow__RATE_AT_UTIL_MAX = 10014;
uint256 internal constant AdminModule__ValueOverflow__RATE_AT_UTIL_KINK1 = 10015;
uint256 internal constant AdminModule__ValueOverflow__RATE_AT_UTIL_KINK2 = 10016;
uint256 internal constant AdminModule__ValueOverflow__RATE_AT_UTIL_MAX_V2 = 10017;
uint256 internal constant AdminModule__ValueOverflow__FEE = 10018;
uint256 internal constant AdminModule__ValueOverflow__THRESHOLD = 10019;
uint256 internal constant AdminModule__ValueOverflow__EXPAND_PERCENT = 10020;
uint256 internal constant AdminModule__ValueOverflow__EXPAND_DURATION = 10021;
uint256 internal constant AdminModule__ValueOverflow__EXPAND_PERCENT_BORROW = 10022;
uint256 internal constant AdminModule__ValueOverflow__EXPAND_DURATION_BORROW = 10023;
uint256 internal constant AdminModule__ValueOverflow__EXCHANGE_PRICES = 10024;
uint256 internal constant AdminModule__ValueOverflow__UTILIZATION = 10025;
/// @notice thrown when an address is not a contract
uint256 internal constant AdminModule__AddressNotAContract = 10026;
uint256 internal constant AdminModule__ValueOverflow__MAX_UTILIZATION = 10027;
/// @notice thrown if a token that is being listed has not between 6 and 18 decimals
uint256 internal constant AdminModule__TokenInvalidDecimalsRange = 10028;
/***********************************|
| User Module |
|__________________________________*/
/// @notice thrown when user operations are paused for an interacted token
uint256 internal constant UserModule__UserNotDefined = 11001;
/// @notice thrown when user operations are paused for an interacted token
uint256 internal constant UserModule__UserPaused = 11002;
/// @notice thrown when user's try to withdraw below withdrawal limit
uint256 internal constant UserModule__WithdrawalLimitReached = 11003;
/// @notice thrown when user's try to borrow above borrow limit
uint256 internal constant UserModule__BorrowLimitReached = 11004;
/// @notice thrown when user sent supply/withdraw and borrow/payback both as 0
uint256 internal constant UserModule__OperateAmountsZero = 11005;
/// @notice thrown when user sent supply/withdraw or borrow/payback both as bigger than 2**128
uint256 internal constant UserModule__OperateAmountOutOfBounds = 11006;
/// @notice thrown when the operate amount for supply / withdraw / borrow / payback is below the minimum amount
/// that would cause a storage difference after BigMath & rounding imprecision. Extremely unlikely to ever happen
/// for all normal use-cases.
uint256 internal constant UserModule__OperateAmountInsufficient = 11007;
/// @notice thrown when withdraw or borrow is executed but withdrawTo or borrowTo is the zero address
uint256 internal constant UserModule__ReceiverNotDefined = 11008;
/// @notice thrown when user did send excess or insufficient amount (beyond rounding issues)
uint256 internal constant UserModule__TransferAmountOutOfBounds = 11009;
/// @notice thrown when user sent msg.value along for an operation not for the native token
uint256 internal constant UserModule__MsgValueForNonNativeToken = 11010;
/// @notice thrown when a borrow operation is done when utilization is above 100%
uint256 internal constant UserModule__MaxUtilizationReached = 11011;
/// @notice all ValueOverflow errors below are thrown if a certain input param or calc result overflows the allowed storage size
uint256 internal constant UserModule__ValueOverflow__EXCHANGE_PRICES = 11012;
uint256 internal constant UserModule__ValueOverflow__UTILIZATION = 11013;
uint256 internal constant UserModule__ValueOverflow__TOTAL_SUPPLY = 11014;
uint256 internal constant UserModule__ValueOverflow__TOTAL_BORROW = 11015;
/// @notice thrown when SKIP_TRANSFERS is set but the input params are invalid for skipping transfers
uint256 internal constant UserModule__SkipTransfersInvalid = 11016;
/// @notice thrown when a new operate amount is > 10_000 * existing total amount AND the amount is bigger than 2 ** 80
uint256 internal constant UserModule__OperateAmountRatioExcess = 11017;
/// @notice thrown when NET_TRANSFERS is set but the input params are invalid for net transfers
uint256 internal constant UserModule__NetTransfersInvalid = 11018;
/***********************************|
| LiquidityHelpers |
|__________________________________*/
/// @notice thrown when a reentrancy happens
uint256 internal constant LiquidityHelpers__Reentrancy = 12001;
}// SPDX-License-Identifier: BUSL-1.1
pragma solidity >=0.8.21 <=0.8.29;
contract Events {
/// @notice emitted on any `operate()` execution: deposit / supply / withdraw / borrow.
/// includes info related to the executed operation, new total amounts (packed uint256 of BigMath numbers as in storage)
/// and exchange prices (packed uint256 as in storage).
/// @param user protocol that triggered this operation (e.g. via an fToken or via Vault protocol)
/// @param token token address for which this operation was executed
/// @param supplyAmount supply amount for the operation. if >0 then a deposit happened, if <0 then a withdrawal happened.
/// if 0 then nothing.
/// @param borrowAmount borrow amount for the operation. if >0 then a borrow happened, if <0 then a payback happened.
/// if 0 then nothing.
/// @param withdrawTo address that funds where withdrawn to (if supplyAmount <0)
/// @param borrowTo address that funds where borrowed to (if borrowAmount >0)
/// @param totalAmounts updated total amounts, stacked uint256 as written to storage:
/// First 64 bits => 0- 63 => total supply with interest in raw (totalSupply = totalSupplyRaw * supplyExchangePrice); BigMath: 56 | 8
/// Next 64 bits => 64-127 => total interest free supply in normal token amount (totalSupply = totalSupply); BigMath: 56 | 8
/// Next 64 bits => 128-191 => total borrow with interest in raw (totalBorrow = totalBorrowRaw * borrowExchangePrice); BigMath: 56 | 8
/// Next 64 bits => 192-255 => total interest free borrow in normal token amount (totalBorrow = totalBorrow); BigMath: 56 | 8
/// @param exchangePricesAndConfig updated exchange prices and configs storage slot. Contains updated supply & borrow exchange price:
/// First 16 bits => 0- 15 => borrow rate (in 1e2: 100% = 10_000; 1% = 100 -> max value 65535)
/// Next 14 bits => 16- 29 => fee on interest from borrowers to lenders (in 1e2: 100% = 10_000; 1% = 100 -> max value 16_383). configurable.
/// Next 14 bits => 30- 43 => last stored utilization (in 1e2: 100% = 10_000; 1% = 100 -> max value 16_383)
/// Next 14 bits => 44- 57 => update on storage threshold (in 1e2: 100% = 10_000; 1% = 100 -> max value 16_383). configurable.
/// Next 33 bits => 58- 90 => last update timestamp (enough until 16 March 2242 -> max value 8589934591)
/// Next 64 bits => 91-154 => supply exchange price (1e12 -> max value 18_446_744,073709551615)
/// Next 64 bits => 155-218 => borrow exchange price (1e12 -> max value 18_446_744,073709551615)
/// Next 1 bit => 219-219 => if 0 then ratio is supplyInterestFree / supplyWithInterest else ratio is supplyWithInterest / supplyInterestFree
/// Next 14 bits => 220-233 => supplyRatio: supplyInterestFree / supplyWithInterest (in 1e2: 100% = 10_000; 1% = 100 -> max value 16_383)
/// Next 1 bit => 234-234 => if 0 then ratio is borrowInterestFree / borrowWithInterest else ratio is borrowWithInterest / borrowInterestFree
/// Next 14 bits => 235-248 => borrowRatio: borrowInterestFree / borrowWithInterest (in 1e2: 100% = 10_000; 1% = 100 -> max value 16_383)
event LogOperate(
address indexed user,
address indexed token,
int256 supplyAmount,
int256 borrowAmount,
address withdrawTo,
address borrowTo,
uint256 totalAmounts,
uint256 exchangePricesAndConfig
);
}// SPDX-License-Identifier: BUSL-1.1
pragma solidity >=0.8.21 <=0.8.29;
import { IERC20 } from "@openzeppelin/contracts/interfaces/IERC20.sol";
import { FixedPointMathLib } from "solmate/src/utils/FixedPointMathLib.sol";
import { BigMathMinified } from "../../libraries/bigMathMinified.sol";
import { LiquidityCalcs } from "../../libraries/liquidityCalcs.sol";
import { LiquiditySlotsLink } from "../../libraries/liquiditySlotsLink.sol";
import { SafeTransfer } from "../../libraries/safeTransfer.sol";
import { CommonHelpers } from "../common/helpers.sol";
import { Events } from "./events.sol";
import { ErrorTypes } from "../errorTypes.sol";
import { Error } from "../error.sol";
interface IProtocol {
function liquidityCallback(address token_, uint256 amount_, bytes calldata data_) external;
}
abstract contract CoreInternals is Error, CommonHelpers, Events {
using BigMathMinified for uint256;
/// @dev supply or withdraw for both with interest & interest free.
/// positive `amount_` is deposit, negative `amount_` is withdraw.
function _supplyOrWithdraw(
address token_,
int256 amount_,
uint256 supplyExchangePrice_
) internal returns (int256 newSupplyInterestRaw_, int256 newSupplyInterestFree_) {
uint256 userSupplyData_ = _userSupplyData[msg.sender][token_];
if (userSupplyData_ == 0) {
revert FluidLiquidityError(ErrorTypes.UserModule__UserNotDefined);
}
if ((userSupplyData_ >> LiquiditySlotsLink.BITS_USER_SUPPLY_IS_PAUSED) & 1 == 1) {
revert FluidLiquidityError(ErrorTypes.UserModule__UserPaused);
}
// extract user supply amount
uint256 userSupply_ = (userSupplyData_ >> LiquiditySlotsLink.BITS_USER_SUPPLY_AMOUNT) & X64;
userSupply_ = (userSupply_ >> DEFAULT_EXPONENT_SIZE) << (userSupply_ & DEFAULT_EXPONENT_MASK);
// get current leftover decaying amount
uint256 decayAmount_ = (userSupplyData_ >> LiquiditySlotsLink.BITS_USER_SUPPLY_DECAY_AMOUNT) & X26;
decayAmount_ = (decayAmount_ >> DEFAULT_EXPONENT_SIZE) << (decayAmount_ & DEFAULT_EXPONENT_MASK);
// decay duration is in checkpoints. Also decay duration related constants are in Checkpoints, not in seconds!
uint256 decayDurationCPs_ = (userSupplyData_ >>
LiquiditySlotsLink.BITS_USER_SUPPLY_DECAY_DURATION_CHECKPOINTS) & X10;
if (decayAmount_ > 0) {
unchecked {
// calculate decay check points passed by scaling timestamps x 10
// formula: (block.timestamp * 10 / 36) - (lastUpdateTimestamp * 10 / 36)
// can not underflow as last timestamp can never be > block.timestamp and divisor can not be 0
uint256 decayedCPs_ = ((block.timestamp * 10) / DECAY_CHECKPOINT_DURATION_SCALEDX10) -
((((userSupplyData_ >> LiquiditySlotsLink.BITS_USER_SUPPLY_LAST_UPDATE_TIMESTAMP) & X33) * 10) /
DECAY_CHECKPOINT_DURATION_SCALEDX10);
if (decayedCPs_ < decayDurationCPs_) {
// only partial decay happened, update leftover decay amount
decayAmount_ = decayAmount_ - (decayAmount_ * decayedCPs_) / decayDurationCPs_; // decayDurationCPs_ can not be 0. can not underflow
decayDurationCPs_ = decayDurationCPs_ - decayedCPs_; // decayDurationCPs_ => decay duration checkpoints leftover
} else {
// full decay happened
decayAmount_ = 0;
decayDurationCPs_ = 0;
}
}
}
// calculate current, updated (expanded etc.) withdrawal limit
uint256 withdrawLimitBefore_ = LiquidityCalcs.calcWithdrawalLimitBeforeOperate(userSupplyData_, userSupply_);
// calculate updated user supply amount
if (userSupplyData_ & 1 == 1) {
// mode: with interest
if (amount_ > 0) {
// convert amount from normal to raw (divide by exchange price) -> round down for deposit
newSupplyInterestRaw_ = (amount_ * int256(EXCHANGE_PRICES_PRECISION)) / int256(supplyExchangePrice_);
userSupply_ = userSupply_ + uint256(newSupplyInterestRaw_);
} else {
// convert amount from normal to raw (divide by exchange price) -> round up for withdraw
newSupplyInterestRaw_ = -int256(
FixedPointMathLib.mulDivUp(uint256(-amount_), EXCHANGE_PRICES_PRECISION, supplyExchangePrice_)
);
// if withdrawal is more than user's supply then solidity will throw here
userSupply_ = userSupply_ - uint256(-newSupplyInterestRaw_);
}
} else {
// mode: without interest
newSupplyInterestFree_ = amount_;
if (newSupplyInterestFree_ > 0) {
userSupply_ = userSupply_ + uint256(newSupplyInterestFree_);
} else {
// if withdrawal is more than user's supply then solidity will throw here
userSupply_ = userSupply_ - uint256(-newSupplyInterestFree_);
}
}
bool checkDecayExpansion_;
if (amount_ < 0) {
// withdrawal: check withdraw limit, take from decay if available, push down limit
if (userSupply_ < withdrawLimitBefore_) {
// if withdraw, then check the user supply after withdrawal is above withdrawal limit.
// this check is in place also in case where decay is available as protocols do expect max withdrawable amount at once
// is the fully expanded withdrawal limit (which == withdrawLimitBefore_ in case of decay at last tx)
revert FluidLiquidityError(ErrorTypes.UserModule__WithdrawalLimitReached);
}
if (decayAmount_ > 0) {
// subtract from decaying amount in case of withdrawal. the resulting withdrawal limit after must be pushed down by
// the amount of withdraw amount that is covered by available decay amount.
// withdraw limit after can end up either (see calcWithdrawalLimitAfterOperate()):
// - 0 if supply below base
// - withdraw limit before
// - max expansion if withdraw limit before is < max expansion (can only happen in case of push down from decay or new deposits)
// so, reducing withdrawLimitBefore_ makes it end up either at pushed down target or at max expansion.
unchecked {
uint256 withdrawAmount_ = uint256(-(newSupplyInterestRaw_ + newSupplyInterestFree_)); // only one of either can be set
if (withdrawAmount_ > decayAmount_) {
// withdrawal case A -> push down by full available decaying amount
withdrawLimitBefore_ = withdrawLimitBefore_ > decayAmount_
? withdrawLimitBefore_ - decayAmount_
: 0;
decayAmount_ = 0;
} else {
// withdrawal case B -> push down by withdraw amount taken fully from decaying amount
withdrawLimitBefore_ = withdrawLimitBefore_ > withdrawAmount_
? withdrawLimitBefore_ - withdrawAmount_
: 0;
decayAmount_ = decayAmount_ - withdrawAmount_;
}
}
// Note not full amount taken from decay might be reflected in pushed down limit because of max expansion being hit
// -> handled below Ref #43681765878
checkDecayExpansion_ = true;
}
}
// calculate withdrawal limit to store as previous withdrawal limit in storage
uint256 withdrawLimitAfter_ = LiquidityCalcs.calcWithdrawalLimitAfterOperate(
userSupplyData_,
userSupply_,
withdrawLimitBefore_
);
if (withdrawLimitAfter_ == 0) {
// if after limit is 0 -> no decay needed (below base limit anyway full withdrawal possible)
decayAmount_ = 0;
} else {
// limit after can only ever become 0, == before or > before. see calcWithdrawalLimitAfterOperate().
// case 0 -> handled. case == -> nothing to do for deposit, target hit for decay withdrawal.
if (withdrawLimitBefore_ != withdrawLimitAfter_) {
if (amount_ > 0) {
// add new decaying amount in case of excess deposit
if (withdrawLimitBefore_ == 0) {
// special case: if before was 0 -> use base withdrawal limit as before reference
withdrawLimitBefore_ =
(userSupplyData_ >> LiquiditySlotsLink.BITS_USER_SUPPLY_BASE_WITHDRAWAL_LIMIT) &
X18;
withdrawLimitBefore_ =
(withdrawLimitBefore_ >> DEFAULT_EXPONENT_SIZE) <<
(withdrawLimitBefore_ & DEFAULT_EXPONENT_MASK);
// in this case it is possible that after is < before! when user supply ends up slightly above base then expansion
// of the limit can reach below base withdrwal limit! Ref #412521521521
}
if (withdrawLimitAfter_ > withdrawLimitBefore_) {
uint256 newDecayAmount_;
unchecked {
newDecayAmount_ = withdrawLimitAfter_ - withdrawLimitBefore_;
}
// new decay duration depends on ratio of leftover decay vs new decay, to solve this case:
// current decay of 10M, 60% passed so 4M decay left. New excess deposit of 100k comes in. decay duration would restart
// for the whole amount of 4.1M, stretching out the decay. This could compound.
// With ratio duration instead:
// 4M : 0.1M, so current leftover decay duration of 40% should have a 40x bigger factor than the 100% for the new amount
// duration = (40% * 1 hour * 4M + 100% * 1 hour * 0.1M) / (4M + 0.1M) = 1492s = 24.87 minutes
// decayDurationCPs_ here already is decay duration leftover (in checkpoints)
decayDurationCPs_ =
(decayDurationCPs_ * decayAmount_ + TOTAL_DECAY_CHECKPOINTS * newDecayAmount_) /
(decayAmount_ + newDecayAmount_); // new target decay duration. always <= TOTAL_DECAY_CHECKPOINTS. newDecayAmount_ can not be 0
if (decayDurationCPs_ < MIN_DECAY_DURATION_CHECKPOINTS) {
// decay duration after a new deposit is always between at least 4m48s and max 1 hour
decayDurationCPs_ = MIN_DECAY_DURATION_CHECKPOINTS;
}
decayAmount_ = decayAmount_ + newDecayAmount_;
} else {
// edge case because of base limit see above Ref #412521521521. no decay
decayAmount_ = 0;
decayDurationCPs_ = 0;
}
} else if (checkDecayExpansion_) {
// Ref #43681765878 case of decay withdrawal: limit did not end up at target pushed down withdrawLimitBefore_.
uint256 notPushedDownAmount_;
unchecked {
notPushedDownAmount_ = withdrawLimitAfter_ > withdrawLimitBefore_
? withdrawLimitAfter_ - withdrawLimitBefore_
: 0;
}
decayAmount_ = decayAmount_ + notPushedDownAmount_;
}
}
}
if (decayAmount_ < 10) {
decayAmount_ = 0;
decayDurationCPs_ = 0;
} else {
decayAmount_ = decayAmount_.toBigNumber(
DECAY_COEFFICIENT_SIZE,
DEFAULT_EXPONENT_SIZE,
BigMathMinified.ROUND_DOWN
);
if (decayDurationCPs_ > TOTAL_DECAY_CHECKPOINTS) {
decayDurationCPs_ = TOTAL_DECAY_CHECKPOINTS; // should not be possible but to be extra sure
} else if (decayDurationCPs_ == 0) {
decayDurationCPs_ = 1; // decay duration should at least always be minimum possible of 1 if decay amount exists (for checkpoints = 0.1% ~ 3.6 sec)
}
}
// Converting user's supply into BigNumber
userSupply_ = userSupply_.toBigNumber(
DEFAULT_COEFFICIENT_SIZE,
DEFAULT_EXPONENT_SIZE,
BigMathMinified.ROUND_DOWN
);
if (((userSupplyData_ >> LiquiditySlotsLink.BITS_USER_SUPPLY_AMOUNT) & X64) == userSupply_) {
// make sure that operate amount is not so small that it wouldn't affect storage update. if a difference
// is present then rounding will be in the right direction to avoid any potential manipulation.
revert FluidLiquidityError(ErrorTypes.UserModule__OperateAmountInsufficient);
}
// Converting withdrawal limit into BigNumber
withdrawLimitAfter_ = withdrawLimitAfter_.toBigNumber(
DEFAULT_COEFFICIENT_SIZE,
DEFAULT_EXPONENT_SIZE,
BigMathMinified.ROUND_DOWN
);
_userSupplyData[msg.sender][token_] =
// mask to update bits 1-161 (supply amount, withdrawal limit, timestamp) and 218-253 (decay amount, decay duration percent)
(userSupplyData_ & 0xC000000003FFFFFFFFFFFFFC0000000000000000000000000000000000000001) |
(userSupply_ << LiquiditySlotsLink.BITS_USER_SUPPLY_AMOUNT) | // converted to BigNumber can not overflow
(withdrawLimitAfter_ << LiquiditySlotsLink.BITS_USER_SUPPLY_PREVIOUS_WITHDRAWAL_LIMIT) | // converted to BigNumber can not overflow
(block.timestamp << LiquiditySlotsLink.BITS_USER_SUPPLY_LAST_UPDATE_TIMESTAMP) |
(decayAmount_ << LiquiditySlotsLink.BITS_USER_SUPPLY_DECAY_AMOUNT) | // converted to BigNumber can not overflow
(decayDurationCPs_ << LiquiditySlotsLink.BITS_USER_SUPPLY_DECAY_DURATION_CHECKPOINTS); // can not overflow as can never be > TOTAL_DECAY_CHECKPOINTS
}
/// @dev borrow or payback for both with interest & interest free.
/// positive `amount_` is borrow, negative `amount_` is payback.
function _borrowOrPayback(
address token_,
int256 amount_,
uint256 borrowExchangePrice_
) internal returns (int256 newBorrowInterestRaw_, int256 newBorrowInterestFree_) {
uint256 userBorrowData_ = _userBorrowData[msg.sender][token_];
if (userBorrowData_ == 0) {
revert FluidLiquidityError(ErrorTypes.UserModule__UserNotDefined);
}
if ((userBorrowData_ >> LiquiditySlotsLink.BITS_USER_BORROW_IS_PAUSED) & 1 == 1) {
revert FluidLiquidityError(ErrorTypes.UserModule__UserPaused);
}
// extract user borrow amount
uint256 userBorrow_ = (userBorrowData_ >> LiquiditySlotsLink.BITS_USER_BORROW_AMOUNT) & X64;
userBorrow_ = (userBorrow_ >> DEFAULT_EXPONENT_SIZE) << (userBorrow_ & DEFAULT_EXPONENT_MASK);
// calculate current, updated (expanded etc.) borrow limit
uint256 newBorrowLimit_ = LiquidityCalcs.calcBorrowLimitBeforeOperate(userBorrowData_, userBorrow_);
// calculate updated user borrow amount
if (userBorrowData_ & 1 == 1) {
// with interest
if (amount_ > 0) {
// convert amount normal to raw (divide by exchange price) -> round up for borrow
newBorrowInterestRaw_ = int256(
FixedPointMathLib.mulDivUp(uint256(amount_), EXCHANGE_PRICES_PRECISION, borrowExchangePrice_)
);
userBorrow_ = userBorrow_ + uint256(newBorrowInterestRaw_);
} else {
// convert amount from normal to raw (divide by exchange price) -> round down for payback
newBorrowInterestRaw_ = (amount_ * int256(EXCHANGE_PRICES_PRECISION)) / int256(borrowExchangePrice_);
userBorrow_ = userBorrow_ - uint256(-newBorrowInterestRaw_);
}
} else {
// without interest
newBorrowInterestFree_ = amount_;
if (newBorrowInterestFree_ > 0) {
// borrowing
userBorrow_ = userBorrow_ + uint256(newBorrowInterestFree_);
} else {
// payback
userBorrow_ = userBorrow_ - uint256(-newBorrowInterestFree_);
}
}
if (amount_ > 0 && userBorrow_ > newBorrowLimit_) {
// if borrow, then check the user borrow amount after borrowing is below borrow limit
revert FluidLiquidityError(ErrorTypes.UserModule__BorrowLimitReached);
}
// calculate borrow limit to store as previous borrow limit in storage
newBorrowLimit_ = LiquidityCalcs.calcBorrowLimitAfterOperate(userBorrowData_, userBorrow_, newBorrowLimit_);
// Converting user's borrowings into bignumber
userBorrow_ = userBorrow_.toBigNumber(
DEFAULT_COEFFICIENT_SIZE,
DEFAULT_EXPONENT_SIZE,
BigMathMinified.ROUND_UP
);
if (((userBorrowData_ >> LiquiditySlotsLink.BITS_USER_BORROW_AMOUNT) & X64) == userBorrow_) {
// make sure that operate amount is not so small that it wouldn't affect storage update. if a difference
// is present then rounding will be in the right direction to avoid any potential manipulation.
revert FluidLiquidityError(ErrorTypes.UserModule__OperateAmountInsufficient);
}
// Converting borrow limit into bignumber
newBorrowLimit_ = newBorrowLimit_.toBigNumber(
DEFAULT_COEFFICIENT_SIZE,
DEFAULT_EXPONENT_SIZE,
BigMathMinified.ROUND_DOWN
);
// Updating on storage
_userBorrowData[msg.sender][token_] =
// mask to update bits 1-161 (borrow amount, borrow limit, timestamp)
(userBorrowData_ & 0xfffffffffffffffffffffffc0000000000000000000000000000000000000001) |
(userBorrow_ << LiquiditySlotsLink.BITS_USER_BORROW_AMOUNT) | // converted to BigNumber can not overflow
(newBorrowLimit_ << LiquiditySlotsLink.BITS_USER_BORROW_PREVIOUS_BORROW_LIMIT) | // converted to BigNumber can not overflow
(block.timestamp << LiquiditySlotsLink.BITS_USER_BORROW_LAST_UPDATE_TIMESTAMP);
}
/// @dev checks if `supplyAmount_` & `borrowAmount_` amounts transfers can be skipped (DEX-protocol use-case).
/// - Requirements:
/// - ` callbackData_` MUST be > 63 bytes and 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 _isInOutBalancedOut(
int256 supplyAmount_,
int256 borrowAmount_,
address withdrawTo_,
address borrowTo_,
bytes memory callbackData_
) internal view returns (bool) {
// callbackData_ being at least > 63 in length is already verified before calling this method.
// 1. SKIP_TRANSFERS must be set in callbackData_ 32 bytes before last 32 bytes
bytes32 skipTransfers_;
assembly {
skipTransfers_ := mload(
add(
// add padding for length as present for dynamic arrays in memory
add(callbackData_, 32),
// Load from memory offset of 2 slots (64 bytes): 1 slot: bytes32 skipTransfers_ + 2 slot: address inFrom_
sub(mload(callbackData_), 64)
)
)
}
if (skipTransfers_ != SKIP_TRANSFERS) {
return false;
}
// after here, if invalid, protocol intended to skip transfers, but something is invalid. so we don't just
// NOT skip transfers, we actually revert because there must be something wrong on protocol side.
// 2. amounts must be
// a) equal: supply(+) == borrow(+), withdraw(-) == payback(-) OR
// b) Liquidity must be on the winning side.
// EITHER:
// deposit and borrow, both positive. there must be more borrow than deposit.
// so supply amount must be less, e.g. 80 deposit and 100 borrow.
// OR:
// withdraw and payback, both negative. there must be more withdraw than payback.
// so supplyAmount must be less (e.g. -100 withdraw and -80 payback )
if (
msg.value != 0 || // no msg.value should be sent along when trying to skip transfers.
supplyAmount_ == 0 ||
borrowAmount_ == 0 || // it must be a 2 actions operation, not just e.g. only deposit or only payback.
supplyAmount_ > borrowAmount_ // allow case a) and b): supplyAmount must be <=
) {
revert FluidLiquidityError(ErrorTypes.UserModule__SkipTransfersInvalid);
}
// 3. inFrom_ must be in last 32 bytes and must match receiver
address inFrom_;
assembly {
inFrom_ := mload(
add(
// add padding for length as present for dynamic arrays in memory
add(callbackData_, 32),
// assembly expects address with leading zeros / left padded so need to use 32 as length here
sub(mload(callbackData_), 32)
)
)
}
if (supplyAmount_ > 0) {
// deposit and borrow
if (!(inFrom_ == borrowTo_ && inFrom_ == msg.sender)) {
revert FluidLiquidityError(ErrorTypes.UserModule__SkipTransfersInvalid);
}
} else {
// withdraw and payback
if (!(inFrom_ == withdrawTo_ && inFrom_ == msg.sender)) {
revert FluidLiquidityError(ErrorTypes.UserModule__SkipTransfersInvalid);
}
}
return true;
}
/// @dev checks if net transfers should be done only (DEX-protocol use-case).
/// - Requirements:
/// - ` callbackData_` MUST be > 63 bytes and 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_ in case of net transfer out
/// - `callbackData_` must in addition to the from address as described above include bytes32 NET_TRANSFERS
/// in the slot before (second last slot)
/// - Amounts must be so that it's a 2 action operation, with some input and some output
function _isNetTransfers(
int256 supplyAmount_,
int256 borrowAmount_,
address withdrawTo_,
address borrowTo_,
bytes memory callbackData_
) internal pure returns (bool isNetTransfers_, uint256 operateAmountOut_) {
// 1. NET_TRANSFERS must be set in callbackData_ as the second-to-last 32-byte word
bytes32 netTransfers_;
assembly {
netTransfers_ := mload(
add(
// add padding for length as present for dynamic arrays in memory
add(callbackData_, 32),
// Load from memory offset of 2 slots (64 bytes): 1 slot: bytes32 netTransfers_ + 2 slot: address inFrom_
sub(mload(callbackData_), 64)
)
)
}
if (netTransfers_ != NET_TRANSFERS) {
return (false, 0);
}
// memVar_ => operateAmountOut: borrow + withdraw
operateAmountOut_ =
uint256((borrowAmount_ > 0 ? borrowAmount_ : int256(0))) +
uint256((supplyAmount_ < 0 ? -supplyAmount_ : int256(0)));
if (
// it must be a 2 actions operation, not just e.g. only deposit or only payback.
supplyAmount_ == 0 ||
borrowAmount_ == 0 ||
// must not be deposit and payback or withdraw and borrow (some in, some out)
// The ^ operator is the bitwise XOR in Solidity. For signed integers, (supplyAmount_ ^ borrowAmount_) < 0 checks if the
// two values have opposite signs (one positive, one negative), because XORing numbers with different signs sets the sign bit.
// i.e. equivalent to (supplyAmount_ > 0 && borrowAmount_ < 0) || (supplyAmount_ < 0 && borrowAmount_ > 0)
(supplyAmount_ ^ borrowAmount_) < 0
) {
revert FluidLiquidityError(ErrorTypes.UserModule__NetTransfersInvalid);
}
// inFrom_ must be in last 32 bytes and must match receiver, and only either withdrawTo_ or borrowTo_ must be set, but not both
address inFrom_;
assembly {
inFrom_ := mload(
add(
// add padding for length as present for dynamic arrays in memory
add(callbackData_, 32),
// assembly expects address with leading zeros / left padded so need to use 32 as length here
sub(mload(callbackData_), 32)
)
)
}
if (supplyAmount_ < 0) {
if (inFrom_ != withdrawTo_ || borrowTo_ != address(0)) {
revert FluidLiquidityError(ErrorTypes.UserModule__NetTransfersInvalid);
}
} else if (borrowAmount_ > 0) {
if (inFrom_ != borrowTo_ || withdrawTo_ != address(0)) {
revert FluidLiquidityError(ErrorTypes.UserModule__NetTransfersInvalid);
}
}
return (true, operateAmountOut_);
}
/// @notice Checks and enforces the total input amount for a protocol callback.
/// @dev Supports legacy DexV1 and new protocols (e.g., DexV2) by decoding callbackData accordingly.
/// @param expectedInputAmount_ The expected input amount to be enforced.
/// @param callbackData_ The callback data containing protocol-specific input information.
/// @return The validated or updated input amount to be used.
function _checkEnforceTotalInputAmount(
uint256 expectedInputAmount_,
bytes memory callbackData_
) internal pure returns (uint256) {
// of all live protocols until Sep 2025, only DexV1 fulfills this case.
// all new protocols after that time implement sending PROTOCOL, ACTION bytes32 in the first 2 slots of callbackData_
bytes32 firstSlotBytes32_;
assembly {
// Read the first 32 bytes after the array length (i.e., the first word of data)
firstSlotBytes32_ := mload(add(callbackData_, 32))
}
uint256 slotUint_ = uint256(firstSlotBytes32_);
// Check if first slot value as uint is within +1% of expected input amount
if (
slotUint_ < expectedInputAmount_ ||
slotUint_ > (expectedInputAmount_ * (FOUR_DECIMALS + MAX_INPUT_AMOUNT_EXCESS)) / FOUR_DECIMALS
) {
// first slot must be bytes32. e.g. bytes32 keccak hash for "DEXV2" is d118e12c537365aadb8862ad8af2972cc5a1400f6c9f46f35f384925ff0a4db6
// so there is ~no way this gets hit by chance to be within 1% of any realistic input amount
if (firstSlotBytes32_ == DEXV2_IDENTIFIER) {
// enforce the amount to send should be incl. revenue fee
// DexV2 sends this structure as callbackData:
// bytes32 PROTOCOL, bytes32 ACTION, uint amountToSend_, ...others
// Read the third 32-byte word (slot 3) from callbackData_ and assign as uint to expectedInputAmount_
assembly {
slotUint_ := mload(add(callbackData_, 96))
}
// make sure the value to enforce is within +1% of the current expected input amount
if (
slotUint_ < expectedInputAmount_ ||
slotUint_ > (expectedInputAmount_ * (FOUR_DECIMALS + MAX_INPUT_AMOUNT_EXCESS)) / FOUR_DECIMALS
) {
revert FluidLiquidityError(ErrorTypes.UserModule__TransferAmountOutOfBounds);
}
expectedInputAmount_ = slotUint_;
}
// else -> for all other protocols enforce only the default input amount
} else {
// first slot is an uint and callbackData length >= 96, can only be DexV1. enforce the amount to send should be incl. revenue fee
// DexV1 sends this structure: `(uint amountToSend_, bool isCallback_, address from_) = abi.decode(data_, (uint, bool, address));`
expectedInputAmount_ = slotUint_;
}
return expectedInputAmount_;
}
/// @dev checks `newOperateAmount_` to be within an acceptable valid ratio compared to `existingTotalAmount_`
/// serves as additional input validation and operate effects check.
function _checkMaxOperateAmountRatio(
uint256 newOperateAmount_,
uint256 existingTotalAmount_,
bool isDepositBorrow_
) internal pure {
unchecked {
// existingTotalAmount_ -> existingTotalAmount_ adjusted with max ratio
existingTotalAmount_ = isDepositBorrow_
? (MAX_NEW_VS_EXISTING_TOTAL_AMOUNT_RATIO_DEPOSIT_BORROW * existingTotalAmount_)
: existingTotalAmount_ / MAX_NEW_VS_EXISTING_TOTAL_AMOUNT_RATIO_WITHDRAW_PAYBACK;
if (newOperateAmount_ > MAX_NEW_AMOUNT_WHEN_RATIO_CHECK && newOperateAmount_ > existingTotalAmount_) {
revert FluidLiquidityError(ErrorTypes.UserModule__OperateAmountRatioExcess);
}
}
}
}
/// @title Fluid Liquidity UserModule
/// @notice Fluid Liquidity public facing endpoint logic contract that implements the `operate()` method.
/// operate can be used to deposit, withdraw, borrow & payback funds, given that they have the necessary
/// user config allowance. Interacting users must be allowed via the Fluid Liquidity AdminModule first.
/// Intended users are thus allow-listed protocols, e.g. the Lending protocol (fTokens), Vault protocol etc.
/// @dev For view methods / accessing data, use the "LiquidityResolver" periphery contract.
abstract contract FluidLiquidityUserModule is CoreInternals {
using BigMathMinified for uint256;
/// @dev struct for vars used in operate() that would otherwise cause a Stack too deep error
struct OperateMemoryVars {
int256 netTransfersOut; // when 0 -> normal flow, when -1 -> skip out transfers, when > 0 -> only do net transfer out flow
uint256 supplyExchangePrice;
uint256 borrowExchangePrice;
uint256 supplyRawInterest;
uint256 supplyInterestFree;
uint256 borrowRawInterest;
uint256 borrowInterestFree;
uint256 totalAmounts;
uint256 exchangePricesAndConfig;
}
/// @notice inheritdoc IFluidLiquidity
function operate(
address token_,
int256 supplyAmount_,
int256 borrowAmount_,
address withdrawTo_,
address borrowTo_,
bytes calldata callbackData_
) external payable reentrancy returns (uint256 memVar3_, uint256 memVar4_) {
if (supplyAmount_ == 0 && borrowAmount_ == 0) {
revert FluidLiquidityError(ErrorTypes.UserModule__OperateAmountsZero);
}
if (
supplyAmount_ < type(int128).min ||
supplyAmount_ > type(int128).max ||
borrowAmount_ < type(int128).min ||
borrowAmount_ > type(int128).max
) {
revert FluidLiquidityError(ErrorTypes.UserModule__OperateAmountOutOfBounds);
}
if ((supplyAmount_ < 0 && withdrawTo_ == address(0)) || (borrowAmount_ > 0 && borrowTo_ == address(0))) {
revert FluidLiquidityError(ErrorTypes.UserModule__ReceiverNotDefined);
}
if (token_ != NATIVE_TOKEN_ADDRESS && msg.value > 0) {
// revert: there should not be msg.value if the token is not the native token
revert FluidLiquidityError(ErrorTypes.UserModule__MsgValueForNonNativeToken);
}
OperateMemoryVars memory o_;
// @dev temporary memory variables used as helper in between to avoid assigning new memory variables
uint256 memVar_;
// memVar2_ => operateAmountIn: deposit + payback
uint256 memVar2_ = uint256((supplyAmount_ > 0 ? supplyAmount_ : int256(0))) +
uint256((borrowAmount_ < 0 ? -borrowAmount_ : int256(0)));
memVar3_ = MAX_INPUT_AMOUNT_EXCESS; // max input amount excess gets adjusted * 1000 for net transfers in
if (callbackData_.length > 63) {
// check if token transfers can be skipped. see `_isInOutBalancedOut` for details.
if (_isInOutBalancedOut(supplyAmount_, borrowAmount_, withdrawTo_, borrowTo_, callbackData_)) {
memVar2_ = 0; // set to 0 to skip transfers IN
o_.netTransfersOut = SKIP_TRANSFER_OUT_BELOW_VALUE_SIGNAL; // set to -1 to skip transfers OUT
}
bool isNetTransfers_;
// check if token transfers can be done only for net amounts. see `_isNetTransfers` for details.
(isNetTransfers_, memVar_) = _isNetTransfers(
supplyAmount_,
borrowAmount_,
withdrawTo_,
borrowTo_,
callbackData_
);
if (isNetTransfers_) {
unchecked {
if (memVar_ == memVar2_) {
// should use SKIP transfers instead
revert FluidLiquidityError(ErrorTypes.UserModule__NetTransfersInvalid);
} else if (memVar2_ > memVar_) {
// net transfer in
// total in - total out
memVar2_ = memVar2_ - memVar_;
o_.netTransfersOut = SKIP_TRANSFER_OUT_BELOW_VALUE_SIGNAL; // set to -1 to skip transfers OUT
memVar3_ = memVar3_ * 1e3;
} else {
// total out - total in
o_.netTransfersOut = int256(memVar_ - memVar2_);
// net transfer out
memVar2_ = 0; // set to 0 to skip transfers IN
}
}
}
}
if (token_ == NATIVE_TOKEN_ADDRESS) {
unchecked {
// check supply and payback amount is covered by available sent msg.value and
// protection that msg.value is not unintentionally way more than actually used in operate()
if (memVar2_ > msg.value || msg.value > (memVar2_ * (FOUR_DECIMALS + memVar3_)) / FOUR_DECIMALS) {
revert FluidLiquidityError(ErrorTypes.UserModule__TransferAmountOutOfBounds);
}
}
memVar2_ = 0; // set to 0 to skip transfers IN more gas efficient. No need for native token.
}
// if supply or payback or both -> transfer token amount from sender to here.
// for native token this is already covered by msg.value checks in operate(). memVar2_ is set to 0
// for same amounts in same operate(): supply(+) == borrow(+), withdraw(-) == payback(-). memVar2_ is set to 0
if (memVar2_ > 0) {
if (callbackData_.length > 95) {
// check enforce total input amount with revenue for dexV1 and dexV2
memVar2_ = _checkEnforceTotalInputAmount(memVar2_, callbackData_);
}
// memVar_ => initial token balance of this contract
memVar_ = IERC20(token_).balanceOf(address(this));
// trigger protocol to send token amount and pass callback data
IProtocol(msg.sender).liquidityCallback(token_, memVar2_, callbackData_);
// memVar_ => current token balance of this contract - initial balance
memVar_ = IERC20(token_).balanceOf(address(this)) - memVar_;
unchecked {
if (memVar_ < memVar2_ || memVar_ > (memVar2_ * (FOUR_DECIMALS + memVar3_)) / FOUR_DECIMALS) {
// revert if protocol did not send enough to cover supply / payback
// or if protocol sent more than expected, with 1% tolerance for any potential rounding issues (and for DEX revenue cut)
revert FluidLiquidityError(ErrorTypes.UserModule__TransferAmountOutOfBounds);
}
}
_afterTransferIn(token_, memVar_);
}
o_.exchangePricesAndConfig = _exchangePricesAndConfig[token_];
// calculate updated exchange prices
(o_.supplyExchangePrice, o_.borrowExchangePrice) = LiquidityCalcs.calcExchangePrices(
o_.exchangePricesAndConfig
);
// Extract total supply / borrow amounts for the token
o_.totalAmounts = _totalAmounts[token_];
memVar_ = o_.totalAmounts & X64;
o_.supplyRawInterest = (memVar_ >> DEFAULT_EXPONENT_SIZE) << (memVar_ & DEFAULT_EXPONENT_MASK);
memVar_ = (o_.totalAmounts >> LiquiditySlotsLink.BITS_TOTAL_AMOUNTS_SUPPLY_INTEREST_FREE) & X64;
o_.supplyInterestFree = (memVar_ >> DEFAULT_EXPONENT_SIZE) << (memVar_ & DEFAULT_EXPONENT_MASK);
memVar_ = (o_.totalAmounts >> LiquiditySlotsLink.BITS_TOTAL_AMOUNTS_BORROW_WITH_INTEREST) & X64;
o_.borrowRawInterest = (memVar_ >> DEFAULT_EXPONENT_SIZE) << (memVar_ & DEFAULT_EXPONENT_MASK);
// no & mask needed for borrow interest free as it occupies the last bits in the storage slot
memVar_ = (o_.totalAmounts >> LiquiditySlotsLink.BITS_TOTAL_AMOUNTS_BORROW_INTEREST_FREE);
o_.borrowInterestFree = (memVar_ >> DEFAULT_EXPONENT_SIZE) << (memVar_ & DEFAULT_EXPONENT_MASK);
if (supplyAmount_ != 0) {
// execute supply or withdraw and update total amounts
{
uint256 totalAmountsBefore_ = o_.totalAmounts;
(int256 newSupplyInterestRaw_, int256 newSupplyInterestFree_) = _supplyOrWithdraw(
token_,
supplyAmount_,
o_.supplyExchangePrice
);
// update total amounts. this is done here so that values are only written to storage once
// if a borrow / payback also happens in the same `operate()` call
if (newSupplyInterestFree_ == 0) {
// Note newSupplyInterestFree_ can ONLY be 0 if mode is with interest,
// easy to check as that variable is NOT the result of a dvision etc.
// supply or withdraw with interest -> raw amount
if (newSupplyInterestRaw_ > 0) {
_checkMaxOperateAmountRatio(uint256(newSupplyInterestRaw_), o_.supplyRawInterest, true);
o_.supplyRawInterest += uint256(newSupplyInterestRaw_);
} else {
_checkMaxOperateAmountRatio(uint256(-newSupplyInterestRaw_), o_.supplyRawInterest, false);
unchecked {
o_.supplyRawInterest = o_.supplyRawInterest > uint256(-newSupplyInterestRaw_)
? o_.supplyRawInterest - uint256(-newSupplyInterestRaw_)
: 0; // withdraw amount is > total supply -> withdraw total supply down to 0
// Note no risk here as if the user withdraws more than supplied it would revert already
// earlier. Total amounts can end up < sum of user amounts because of rounding
}
}
// Note check for revert {UserModule}__ValueOverflow__TOTAL_SUPPLY is further down when we anyway
// calculate the normal amount from raw
// Converting the updated total amount into big number for storage
memVar_ = o_.supplyRawInterest.toBigNumber(
DEFAULT_COEFFICIENT_SIZE,
DEFAULT_EXPONENT_SIZE,
BigMathMinified.ROUND_DOWN
);
// update total supply with interest at total amounts in storage (only update changed values)
o_.totalAmounts =
// mask to update bits 0-63
(o_.totalAmounts & 0xffffffffffffffffffffffffffffffffffffffffffffffff0000000000000000) |
memVar_; // converted to BigNumber can not overflow
} else {
// supply or withdraw interest free -> normal amount
if (newSupplyInterestFree_ > 0) {
_checkMaxOperateAmountRatio(uint256(newSupplyInterestFree_), o_.supplyInterestFree, true);
o_.supplyInterestFree += uint256(newSupplyInterestFree_);
} else {
_checkMaxOperateAmountRatio(uint256(-newSupplyInterestFree_), o_.supplyInterestFree, false);
unchecked {
o_.supplyInterestFree = o_.supplyInterestFree > uint256(-newSupplyInterestFree_)
? o_.supplyInterestFree - uint256(-newSupplyInterestFree_)
: 0; // withdraw amount is > total supply -> withdraw total supply down to 0
// Note no risk here as if the user withdraws more than supplied it would revert already
// earlier. Total amounts can end up < sum of user amounts because of rounding
}
}
if (o_.supplyInterestFree > MAX_TOKEN_AMOUNT_CAP) {
// only withdrawals allowed if total supply interest free reaches MAX_TOKEN_AMOUNT_CAP
revert FluidLiquidityError(ErrorTypes.UserModule__ValueOverflow__TOTAL_SUPPLY);
}
// Converting the updated total amount into big number for storage
memVar_ = o_.supplyInterestFree.toBigNumber(
DEFAULT_COEFFICIENT_SIZE,
DEFAULT_EXPONENT_SIZE,
BigMathMinified.ROUND_DOWN
);
// update total supply interest free at total amounts in storage (only update changed values)
o_.totalAmounts =
// mask to update bits 64-127
(o_.totalAmounts & 0xffffffffffffffffffffffffffffffff0000000000000000ffffffffffffffff) |
(memVar_ << LiquiditySlotsLink.BITS_TOTAL_AMOUNTS_SUPPLY_INTEREST_FREE); // converted to BigNumber can not overflow
}
if (totalAmountsBefore_ == o_.totalAmounts) {
// make sure that operate amount is not so small that it wouldn't affect storage update. if a difference
// is present then rounding will be in the right direction to avoid any potential manipulation.
revert FluidLiquidityError(ErrorTypes.UserModule__OperateAmountInsufficient);
}
}
}
if (borrowAmount_ != 0) {
// execute borrow or payback and update total amounts
{
uint256 totalAmountsBefore_ = o_.totalAmounts;
(int256 newBorrowInterestRaw_, int256 newBorrowInterestFree_) = _borrowOrPayback(
token_,
borrowAmount_,
o_.borrowExchangePrice
);
// update total amounts. this is done here so that values are only written to storage once
// if a supply / withdraw also happens in the same `operate()` call
if (newBorrowInterestFree_ == 0) {
// Note newBorrowInterestFree_ can ONLY be 0 if mode is with interest,
// easy to check as that variable is NOT the result of a dvision etc.
// borrow or payback with interest -> raw amount
if (newBorrowInterestRaw_ > 0) {
_checkMaxOperateAmountRatio(uint256(newBorrowInterestRaw_), o_.borrowRawInterest, true);
o_.borrowRawInterest += uint256(newBorrowInterestRaw_);
} else {
_checkMaxOperateAmountRatio(uint256(-newBorrowInterestRaw_), o_.borrowRawInterest, false);
unchecked {
o_.borrowRawInterest = o_.borrowRawInterest > uint256(-newBorrowInterestRaw_)
? o_.borrowRawInterest - uint256(-newBorrowInterestRaw_)
: 0; // payback amount is > total borrow -> payback total borrow down to 0
}
}
// Note check for revert UserModule__ValueOverflow__TOTAL_BORROW is further down when we anyway
// calculate the normal amount from raw
// Converting the updated total amount into big number for storage
memVar_ = o_.borrowRawInterest.toBigNumber(
DEFAULT_COEFFICIENT_SIZE,
DEFAULT_EXPONENT_SIZE,
BigMathMinified.ROUND_UP
);
// update total borrow with interest at total amounts in storage (only update changed values)
o_.totalAmounts =
// mask to update bits 128-191
(o_.totalAmounts & 0xffffffffffffffff0000000000000000ffffffffffffffffffffffffffffffff) |
(memVar_ << LiquiditySlotsLink.BITS_TOTAL_AMOUNTS_BORROW_WITH_INTEREST); // converted to BigNumber can not overflow
} else {
// borrow or payback interest free -> normal amount
if (newBorrowInterestFree_ > 0) {
_checkMaxOperateAmountRatio(uint256(newBorrowInterestFree_), o_.borrowInterestFree, true);
o_.borrowInterestFree += uint256(newBorrowInterestFree_);
} else {
_checkMaxOperateAmountRatio(uint256(-newBorrowInterestFree_), o_.borrowInterestFree, false);
unchecked {
o_.borrowInterestFree = o_.borrowInterestFree > uint256(-newBorrowInterestFree_)
? o_.borrowInterestFree - uint256(-newBorrowInterestFree_)
: 0; // payback amount is > total borrow -> payback total borrow down to 0
}
}
if (o_.borrowInterestFree > MAX_TOKEN_AMOUNT_CAP) {
// only payback allowed if total borrow interest free reaches MAX_TOKEN_AMOUNT_CAP
revert FluidLiquidityError(ErrorTypes.UserModule__ValueOverflow__TOTAL_BORROW);
}
// Converting the updated total amount into big number for storage
memVar_ = o_.borrowInterestFree.toBigNumber(
DEFAULT_COEFFICIENT_SIZE,
DEFAULT_EXPONENT_SIZE,
BigMathMinified.ROUND_UP
);
// update total borrow interest free at total amounts in storage (only update changed values)
o_.totalAmounts =
// mask to update bits 192-255
(o_.totalAmounts & 0x0000000000000000ffffffffffffffffffffffffffffffffffffffffffffffff) |
(memVar_ << LiquiditySlotsLink.BITS_TOTAL_AMOUNTS_BORROW_INTEREST_FREE); // converted to BigNumber can not overflow
}
if (totalAmountsBefore_ == o_.totalAmounts) {
// make sure that operate amount is not so small that it wouldn't affect storage update. if a difference
// is present then rounding will be in the right direction to avoid any potential manipulation.
revert FluidLiquidityError(ErrorTypes.UserModule__OperateAmountInsufficient);
}
}
}
// Updating total amounts on storage
_totalAmounts[token_] = o_.totalAmounts;
{
// update exchange prices / utilization / ratios
// exchangePricesAndConfig is only written to storage if either utilization, supplyRatio or borrowRatio
// change is above the required storageUpdateThreshold config value or if the last write was > 1 day ago.
// 1. calculate new supply ratio, borrow ratio & utilization.
// 2. check if last storage write was > 1 day ago.
// 3. If false -> check if utilization is above update threshold
// 4. If false -> check if supply ratio is above update threshold
// 5. If false -> check if borrow ratio is above update threshold
// 6. If any true, then update on storage
// ########## calculating supply ratio ##########
// supplyWithInterest in normal amount
memVar3_ = ((o_.supplyRawInterest * o_.supplyExchangePrice) / EXCHANGE_PRICES_PRECISION);
if (memVar3_ > MAX_TOKEN_AMOUNT_CAP && supplyAmount_ > 0) {
// only withdrawals allowed if total supply raw reaches MAX_TOKEN_AMOUNT_CAP
revert FluidLiquidityError(ErrorTypes.UserModule__ValueOverflow__TOTAL_SUPPLY);
}
// memVar_ => total supply. set here so supplyWithInterest (memVar3_) is only calculated once. For utilization
memVar_ = o_.supplyInterestFree + memVar3_;
if (memVar3_ > o_.supplyInterestFree) {
// memVar3_ is ratio with 1 bit as 0 as supply interest raw is bigger
memVar3_ = ((o_.supplyInterestFree * FOUR_DECIMALS) / memVar3_) << 1;
// because of checking to divide by bigger amount, ratio can never be > 100%
} else if (memVar3_ < o_.supplyInterestFree) {
// memVar3_ is ratio with 1 bit as 1 as supply interest free is bigger
memVar3_ = (((memVar3_ * FOUR_DECIMALS) / o_.supplyInterestFree) << 1) | 1;
// because of checking to divide by bigger amount, ratio can never be > 100%
} else if (memVar_ > 0) {
// supplies match exactly (memVar3_ == o_.supplyInterestFree) and total supplies are not 0
// -> set ratio to 1 (with first bit set to 0, doesn't matter)
memVar3_ = FOUR_DECIMALS << 1;
} // else if total supply = 0, memVar3_ (supplyRatio) is already 0.
// ########## calculating borrow ratio ##########
// borrowWithInterest in normal amount
memVar4_ = ((o_.borrowRawInterest * o_.borrowExchangePrice) / EXCHANGE_PRICES_PRECISION);
if (memVar4_ > MAX_TOKEN_AMOUNT_CAP && borrowAmount_ > 0) {
// only payback allowed if total borrow raw reaches MAX_TOKEN_AMOUNT_CAP
revert FluidLiquidityError(ErrorTypes.UserModule__ValueOverflow__TOTAL_BORROW);
}
// memVar2_ => total borrow. set here so borrowWithInterest (memVar4_) is only calculated once. For utilization
memVar2_ = o_.borrowInterestFree + memVar4_;
if (memVar4_ > o_.borrowInterestFree) {
// memVar4_ is ratio with 1 bit as 0 as borrow interest raw is bigger
memVar4_ = ((o_.borrowInterestFree * FOUR_DECIMALS) / memVar4_) << 1;
// because of checking to divide by bigger amount, ratio can never be > 100%
} else if (memVar4_ < o_.borrowInterestFree) {
// memVar4_ is ratio with 1 bit as 1 as borrow interest free is bigger
memVar4_ = (((memVar4_ * FOUR_DECIMALS) / o_.borrowInterestFree) << 1) | 1;
// because of checking to divide by bigger amount, ratio can never be > 100%
} else if (memVar2_ > 0) {
// borrows match exactly (memVar4_ == o_.borrowInterestFree) and total borrows are not 0
// -> set ratio to 1 (with first bit set to 0, doesn't matter)
memVar4_ = FOUR_DECIMALS << 1;
} // else if total borrow = 0, memVar4_ (borrowRatio) is already 0.
// calculate utilization. If there is no supply, utilization must be 0 (avoid division by 0)
uint256 utilization_;
if (memVar_ > 0) {
utilization_ = ((memVar2_ * FOUR_DECIMALS) / memVar_);
// for borrow operations, ensure max utilization is not reached
if (borrowAmount_ > 0) {
// memVar_ => max utilization
// if any max utilization other than 100% is set, the flag usesConfigs2 in
// exchangePricesAndConfig is 1. (optimized to avoid SLOAD if not needed).
memVar_ = (o_.exchangePricesAndConfig >> LiquiditySlotsLink.BITS_EXCHANGE_PRICES_USES_CONFIGS2) &
1 ==
1
? (_configs2[token_] & X14) // read configured max utilization
: FOUR_DECIMALS; // default max utilization = 100%
if (utilization_ > memVar_) {
revert FluidLiquidityError(ErrorTypes.UserModule__MaxUtilizationReached);
}
}
}
// check if time difference is big enough (> 1 day)
unchecked {
if (
block.timestamp >
// extract last update timestamp + 1 day
(((o_.exchangePricesAndConfig >> LiquiditySlotsLink.BITS_EXCHANGE_PRICES_LAST_TIMESTAMP) & X33) +
FORCE_STORAGE_WRITE_AFTER_TIME)
) {
memVar_ = 1; // set write to storage flag
} else {
memVar_ = 0;
}
}
if (memVar_ == 0) {
// time difference is not big enough to cause storage write -> check utilization
// memVar_ => extract last utilization
memVar_ = (o_.exchangePricesAndConfig >> LiquiditySlotsLink.BITS_EXCHANGE_PRICES_UTILIZATION) & X14;
// memVar2_ => storage update threshold in percent
memVar2_ =
(o_.exchangePricesAndConfig >> LiquiditySlotsLink.BITS_EXCHANGE_PRICES_UPDATE_THRESHOLD) &
X14;
unchecked {
// set memVar_ to 1 if current utilization to previous utilization difference is > update storage threshold
memVar_ = (utilization_ > memVar_ ? utilization_ - memVar_ : memVar_ - utilization_) > memVar2_
? 1
: 0;
if (memVar_ == 0) {
// utilization & time difference is not big enough -> check supplyRatio difference
// memVar_ => extract last supplyRatio
memVar_ =
(o_.exchangePricesAndConfig >> LiquiditySlotsLink.BITS_EXCHANGE_PRICES_SUPPLY_RATIO) &
X15;
// set memVar_ to 1 if current supplyRatio to previous supplyRatio difference is > update storage threshold
if ((memVar_ & 1) == (memVar3_ & 1)) {
memVar_ = memVar_ >> 1;
memVar_ = (
(memVar3_ >> 1) > memVar_ ? (memVar3_ >> 1) - memVar_ : memVar_ - (memVar3_ >> 1)
) > memVar2_
? 1
: 0; // memVar3_ = supplyRatio, memVar_ = previous supplyRatio, memVar2_ = update storage threshold
} else {
// if inverse bit is changing then always update on storage
memVar_ = 1;
}
if (memVar_ == 0) {
// utilization, time, and supplyRatio difference is not big enough -> check borrowRatio difference
// memVar_ => extract last borrowRatio
memVar_ =
(o_.exchangePricesAndConfig >> LiquiditySlotsLink.BITS_EXCHANGE_PRICES_BORROW_RATIO) &
X15;
// set memVar_ to 1 if current borrowRatio to previous borrowRatio difference is > update storage threshold
if ((memVar_ & 1) == (memVar4_ & 1)) {
memVar_ = memVar_ >> 1;
memVar_ = (
(memVar4_ >> 1) > memVar_ ? (memVar4_ >> 1) - memVar_ : memVar_ - (memVar4_ >> 1)
) > memVar2_
? 1
: 0; // memVar4_ = borrowRatio, memVar_ = previous borrowRatio, memVar2_ = update storage threshold
} else {
// if inverse bit is changing then always update on storage
memVar_ = 1;
}
}
}
}
}
// memVar_ is 1 if either time diff was enough or if
// utilization, supplyRatio or borrowRatio difference was > update storage threshold
if (memVar_ == 1) {
// memVar_ => calculate new borrow rate for utilization. includes value overflow check.
memVar_ = LiquidityCalcs.calcBorrowRateFromUtilization(_rateData[token_], utilization_);
// ensure values written to storage do not exceed the dedicated bit space in packed uint256 slots
if (o_.supplyExchangePrice > X64 || o_.borrowExchangePrice > X64) {
revert FluidLiquidityError(ErrorTypes.UserModule__ValueOverflow__EXCHANGE_PRICES);
}
if (utilization_ > X14) {
revert FluidLiquidityError(ErrorTypes.UserModule__ValueOverflow__UTILIZATION);
}
o_.exchangePricesAndConfig =
(o_.exchangePricesAndConfig &
// mask to update bits: 0-15 (borrow rate), 30-43 (utilization), 58-248 (timestamp, exchange prices, ratios)
0xfe000000000000000000000000000000000000000000000003fff0003fff0000) |
memVar_ | // calcBorrowRateFromUtilization already includes an overflow check
(utilization_ << LiquiditySlotsLink.BITS_EXCHANGE_PRICES_UTILIZATION) |
(block.timestamp << LiquiditySlotsLink.BITS_EXCHANGE_PRICES_LAST_TIMESTAMP) |
(o_.supplyExchangePrice << LiquiditySlotsLink.BITS_EXCHANGE_PRICES_SUPPLY_EXCHANGE_PRICE) |
(o_.borrowExchangePrice << LiquiditySlotsLink.BITS_EXCHANGE_PRICES_BORROW_EXCHANGE_PRICE) |
// ratios can never be > 100%, no overflow check needed
(memVar3_ << LiquiditySlotsLink.BITS_EXCHANGE_PRICES_SUPPLY_RATIO) | // supplyRatio (memVar3_ holds that value)
(memVar4_ << LiquiditySlotsLink.BITS_EXCHANGE_PRICES_BORROW_RATIO); // borrowRatio (memVar4_ holds that value)
// Updating on storage
_exchangePricesAndConfig[token_] = o_.exchangePricesAndConfig;
} else {
// do not update in storage but update o_.exchangePricesAndConfig for updated exchange prices at
// event emit of LogOperate
o_.exchangePricesAndConfig =
(o_.exchangePricesAndConfig &
// mask to update bits: 91-218 (exchange prices)
0xfffffffffc00000000000000000000000000000007ffffffffffffffffffffff) |
(o_.supplyExchangePrice << LiquiditySlotsLink.BITS_EXCHANGE_PRICES_SUPPLY_EXCHANGE_PRICE) |
(o_.borrowExchangePrice << LiquiditySlotsLink.BITS_EXCHANGE_PRICES_BORROW_EXCHANGE_PRICE);
}
}
// sending tokens to user at the end after updating everything
// only transfer to user in case of withdraw or borrow.
// special flow in case of netTransfersOut < 0 -> skip transfers, e.g. when net transfer in only or when
// same amounts in same operate(): supply(+) == borrow(+), withdraw(-) == payback(-). (DEX protocol use-case).
// when net transfers out = 0 -> normal flow
// when net transfers out > 0 -> net transfers out only flow
if ((supplyAmount_ < 0 || borrowAmount_ > 0) && o_.netTransfersOut > SKIP_TRANSFER_OUT_BELOW_VALUE_SIGNAL) {
// sending tokens to user at the end after updating everything
if (o_.netTransfersOut > 0) {
if (token_ == NATIVE_TOKEN_ADDRESS) {
SafeTransfer.safeTransferNative(
withdrawTo_ == address(0) ? borrowTo_ : withdrawTo_, // enforced in isNetTransfers_ that either one is set
uint256(o_.netTransfersOut)
);
} else {
_preTransferOut(token_, uint256(o_.netTransfersOut));
SafeTransfer.safeTransfer(
token_,
withdrawTo_ == address(0) ? borrowTo_ : withdrawTo_, // enforced in isNetTransfers_ that either one is set
uint256(o_.netTransfersOut)
);
}
} else {
// set memVar2_ to borrowAmount (if borrow) or reset memVar2_ var to 0 because
// it is used with > 0 check below to transfer withdraw / borrow / both
memVar2_ = borrowAmount_ > 0 ? uint256(borrowAmount_) : 0;
if (supplyAmount_ < 0) {
unchecked {
memVar_ = uint256(-supplyAmount_);
}
} else {
memVar_ = 0;
}
if (memVar_ > 0 && memVar2_ > 0 && withdrawTo_ == borrowTo_) {
// if user is doing borrow & withdraw together and address for both is the same
// then transfer tokens of borrow & withdraw together to save on gas
if (token_ == NATIVE_TOKEN_ADDRESS) {
SafeTransfer.safeTransferNative(withdrawTo_, memVar_ + memVar2_);
} else {
memVar_ = memVar_ + memVar2_;
_preTransferOut(token_, memVar_);
SafeTransfer.safeTransfer(token_, withdrawTo_, memVar_);
}
} else {
if (token_ == NATIVE_TOKEN_ADDRESS) {
// if withdraw
if (memVar_ > 0) {
SafeTransfer.safeTransferNative(withdrawTo_, memVar_);
}
// if borrow
if (memVar2_ > 0) {
SafeTransfer.safeTransferNative(borrowTo_, memVar2_);
}
} else {
// if withdraw
if (memVar_ > 0) {
_preTransferOut(token_, memVar_);
SafeTransfer.safeTransfer(token_, withdrawTo_, memVar_);
}
// if borrow
if (memVar2_ > 0) {
_preTransferOut(token_, memVar2_);
SafeTransfer.safeTransfer(token_, borrowTo_, memVar2_);
}
}
}
}
}
// emit Operate event
emit LogOperate(
msg.sender,
token_,
supplyAmount_,
borrowAmount_,
withdrawTo_,
borrowTo_,
o_.totalAmounts,
o_.exchangePricesAndConfig
);
// set return values
memVar3_ = o_.supplyExchangePrice;
memVar4_ = o_.borrowExchangePrice;
}
}// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity >=0.8.0;
/// @notice Arithmetic library with operations for fixed-point numbers.
/// @author Solmate (https://github.com/transmissions11/solmate/blob/main/src/utils/FixedPointMathLib.sol)
/// @author Inspired by USM (https://github.com/usmfum/USM/blob/master/contracts/WadMath.sol)
library FixedPointMathLib {
/*//////////////////////////////////////////////////////////////
SIMPLIFIED FIXED POINT OPERATIONS
//////////////////////////////////////////////////////////////*/
uint256 internal constant MAX_UINT256 = 2**256 - 1;
uint256 internal constant WAD = 1e18; // The scalar of ETH and most ERC20s.
function mulWadDown(uint256 x, uint256 y) internal pure returns (uint256) {
return mulDivDown(x, y, WAD); // Equivalent to (x * y) / WAD rounded down.
}
function mulWadUp(uint256 x, uint256 y) internal pure returns (uint256) {
return mulDivUp(x, y, WAD); // Equivalent to (x * y) / WAD rounded up.
}
function divWadDown(uint256 x, uint256 y) internal pure returns (uint256) {
return mulDivDown(x, WAD, y); // Equivalent to (x * WAD) / y rounded down.
}
function divWadUp(uint256 x, uint256 y) internal pure returns (uint256) {
return mulDivUp(x, WAD, y); // Equivalent to (x * WAD) / y rounded up.
}
/*//////////////////////////////////////////////////////////////
LOW LEVEL FIXED POINT OPERATIONS
//////////////////////////////////////////////////////////////*/
function mulDivDown(
uint256 x,
uint256 y,
uint256 denominator
) internal pure returns (uint256 z) {
/// @solidity memory-safe-assembly
assembly {
// Equivalent to require(denominator != 0 && (y == 0 || x <= type(uint256).max / y))
if iszero(mul(denominator, iszero(mul(y, gt(x, div(MAX_UINT256, y)))))) {
revert(0, 0)
}
// Divide x * y by the denominator.
z := div(mul(x, y), denominator)
}
}
function mulDivUp(
uint256 x,
uint256 y,
uint256 denominator
) internal pure returns (uint256 z) {
/// @solidity memory-safe-assembly
assembly {
// Equivalent to require(denominator != 0 && (y == 0 || x <= type(uint256).max / y))
if iszero(mul(denominator, iszero(mul(y, gt(x, div(MAX_UINT256, y)))))) {
revert(0, 0)
}
// If x * y modulo the denominator is strictly greater than 0,
// 1 is added to round up the division of x * y by the denominator.
z := add(gt(mod(mul(x, y), denominator), 0), div(mul(x, y), denominator))
}
}
function rpow(
uint256 x,
uint256 n,
uint256 scalar
) internal pure returns (uint256 z) {
/// @solidity memory-safe-assembly
assembly {
switch x
case 0 {
switch n
case 0 {
// 0 ** 0 = 1
z := scalar
}
default {
// 0 ** n = 0
z := 0
}
}
default {
switch mod(n, 2)
case 0 {
// If n is even, store scalar in z for now.
z := scalar
}
default {
// If n is odd, store x in z for now.
z := x
}
// Shifting right by 1 is like dividing by 2.
let half := shr(1, scalar)
for {
// Shift n right by 1 before looping to halve it.
n := shr(1, n)
} n {
// Shift n right by 1 each iteration to halve it.
n := shr(1, n)
} {
// Revert immediately if x ** 2 would overflow.
// Equivalent to iszero(eq(div(xx, x), x)) here.
if shr(128, x) {
revert(0, 0)
}
// Store x squared.
let xx := mul(x, x)
// Round to the nearest number.
let xxRound := add(xx, half)
// Revert if xx + half overflowed.
if lt(xxRound, xx) {
revert(0, 0)
}
// Set x to scaled xxRound.
x := div(xxRound, scalar)
// If n is even:
if mod(n, 2) {
// Compute z * x.
let zx := mul(z, x)
// If z * x overflowed:
if iszero(eq(div(zx, x), z)) {
// Revert if x is non-zero.
if iszero(iszero(x)) {
revert(0, 0)
}
}
// Round to the nearest number.
let zxRound := add(zx, half)
// Revert if zx + half overflowed.
if lt(zxRound, zx) {
revert(0, 0)
}
// Return properly scaled zxRound.
z := div(zxRound, scalar)
}
}
}
}
}
/*//////////////////////////////////////////////////////////////
GENERAL NUMBER UTILITIES
//////////////////////////////////////////////////////////////*/
function sqrt(uint256 x) internal pure returns (uint256 z) {
/// @solidity memory-safe-assembly
assembly {
let y := x // We start y at x, which will help us make our initial estimate.
z := 181 // The "correct" value is 1, but this saves a multiplication later.
// This segment is to get a reasonable initial estimate for the Babylonian method. With a bad
// start, the correct # of bits increases ~linearly each iteration instead of ~quadratically.
// We check y >= 2^(k + 8) but shift right by k bits
// each branch to ensure that if x >= 256, then y >= 256.
if iszero(lt(y, 0x10000000000000000000000000000000000)) {
y := shr(128, y)
z := shl(64, z)
}
if iszero(lt(y, 0x1000000000000000000)) {
y := shr(64, y)
z := shl(32, z)
}
if iszero(lt(y, 0x10000000000)) {
y := shr(32, y)
z := shl(16, z)
}
if iszero(lt(y, 0x1000000)) {
y := shr(16, y)
z := shl(8, z)
}
// Goal was to get z*z*y within a small factor of x. More iterations could
// get y in a tighter range. Currently, we will have y in [256, 256*2^16).
// We ensured y >= 256 so that the relative difference between y and y+1 is small.
// That's not possible if x < 256 but we can just verify those cases exhaustively.
// Now, z*z*y <= x < z*z*(y+1), and y <= 2^(16+8), and either y >= 256, or x < 256.
// Correctness can be checked exhaustively for x < 256, so we assume y >= 256.
// Then z*sqrt(y) is within sqrt(257)/sqrt(256) of sqrt(x), or about 20bps.
// For s in the range [1/256, 256], the estimate f(s) = (181/1024) * (s+1) is in the range
// (1/2.84 * sqrt(s), 2.84 * sqrt(s)), with largest error when s = 1 and when s = 256 or 1/256.
// Since y is in [256, 256*2^16), let a = y/65536, so that a is in [1/256, 256). Then we can estimate
// sqrt(y) using sqrt(65536) * 181/1024 * (a + 1) = 181/4 * (y + 65536)/65536 = 181 * (y + 65536)/2^18.
// There is no overflow risk here since y < 2^136 after the first branch above.
z := shr(18, mul(z, add(y, 65536))) // A mul() is saved from starting z at 181.
// Given the worst case multiplicative error of 2.84 above, 7 iterations should be enough.
z := shr(1, add(z, div(x, z)))
z := shr(1, add(z, div(x, z)))
z := shr(1, add(z, div(x, z)))
z := shr(1, add(z, div(x, z)))
z := shr(1, add(z, div(x, z)))
z := shr(1, add(z, div(x, z)))
z := shr(1, add(z, div(x, z)))
// If x+1 is a perfect square, the Babylonian method cycles between
// floor(sqrt(x)) and ceil(sqrt(x)). This statement ensures we return floor.
// See: https://en.wikipedia.org/wiki/Integer_square_root#Using_only_integer_division
// Since the ceil is rare, we save gas on the assignment and repeat division in the rare case.
// If you don't care whether the floor or ceil square root is returned, you can remove this statement.
z := sub(z, lt(div(x, z), z))
}
}
function unsafeMod(uint256 x, uint256 y) internal pure returns (uint256 z) {
/// @solidity memory-safe-assembly
assembly {
// Mod x by y. Note this will return
// 0 instead of reverting if y is zero.
z := mod(x, y)
}
}
function unsafeDiv(uint256 x, uint256 y) internal pure returns (uint256 r) {
/// @solidity memory-safe-assembly
assembly {
// Divide x by y. Note this will return
// 0 instead of reverting if y is zero.
r := div(x, y)
}
}
function unsafeDivUp(uint256 x, uint256 y) internal pure returns (uint256 z) {
/// @solidity memory-safe-assembly
assembly {
// Add 1 to x * y if x % y > 0. Note this will
// return 0 instead of reverting if y is zero.
z := add(gt(mod(x, y), 0), div(x, y))
}
}
}{
"optimizer": {
"enabled": true,
"runs": 10000000
},
"evmVersion": "cancun",
"outputSelection": {
"*": {
"*": [
"evm.bytecode",
"evm.deployedBytecode",
"devdoc",
"userdoc",
"metadata",
"abi"
]
}
},
"metadata": {
"useLiteralContent": true
}
}Contract Security Audit
- No Contract Security Audit Submitted- Submit Audit Here
Contract ABI
API[{"inputs":[{"internalType":"uint256","name":"errorId_","type":"uint256"}],"name":"FluidLiquidityCalcsError","type":"error"},{"inputs":[{"internalType":"uint256","name":"errorId_","type":"uint256"}],"name":"FluidLiquidityError","type":"error"},{"inputs":[{"internalType":"uint256","name":"errorId_","type":"uint256"}],"name":"FluidSafeTransferError","type":"error"},{"anonymous":false,"inputs":[],"name":"BorrowRateMaxCap","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"user","type":"address"},{"indexed":true,"internalType":"address","name":"token","type":"address"},{"indexed":false,"internalType":"int256","name":"supplyAmount","type":"int256"},{"indexed":false,"internalType":"int256","name":"borrowAmount","type":"int256"},{"indexed":false,"internalType":"address","name":"withdrawTo","type":"address"},{"indexed":false,"internalType":"address","name":"borrowTo","type":"address"},{"indexed":false,"internalType":"uint256","name":"totalAmounts","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"exchangePricesAndConfig","type":"uint256"}],"name":"LogOperate","type":"event"},{"inputs":[{"internalType":"address","name":"token_","type":"address"},{"internalType":"int256","name":"supplyAmount_","type":"int256"},{"internalType":"int256","name":"borrowAmount_","type":"int256"},{"internalType":"address","name":"withdrawTo_","type":"address"},{"internalType":"address","name":"borrowTo_","type":"address"},{"internalType":"bytes","name":"callbackData_","type":"bytes"}],"name":"operate","outputs":[{"internalType":"uint256","name":"memVar3_","type":"uint256"},{"internalType":"uint256","name":"memVar4_","type":"uint256"}],"stateMutability":"payable","type":"function"}]Contract Creation Code
6080604052348015600e575f5ffd5b5060026001556133dd806100215f395ff3fe60806040526004361061001d575f3560e01c8063ad967e1514610021575b5f5ffd5b61003461002f3660046130dc565b61004d565b6040805192835260208301919091520160405180910390f35b5f5f600260ff1660015403610097576040517fdcab82e2000000000000000000000000000000000000000000000000000000008152612ee160048201526024015b60405180910390fd5b6002600155871580156100a8575086155b156100e3576040517fdcab82e2000000000000000000000000000000000000000000000000000000008152612afd600482015260240161008e565b7fffffffffffffffffffffffffffffffff8000000000000000000000000000000088128061012057506f7fffffffffffffffffffffffffffffff88135b8061014a57507fffffffffffffffffffffffffffffffff8000000000000000000000000000000087125b8061016457506f7fffffffffffffffffffffffffffffff87135b1561019f576040517fdcab82e2000000000000000000000000000000000000000000000000000000008152612afe600482015260240161008e565b5f881280156101c2575073ffffffffffffffffffffffffffffffffffffffff8616155b806101eb57505f871380156101eb575073ffffffffffffffffffffffffffffffffffffffff8516155b15610226576040517fdcab82e2000000000000000000000000000000000000000000000000000000008152612b00600482015260240161008e565b73ffffffffffffffffffffffffffffffffffffffff891673eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee1480159061025f57505f34115b1561029a576040517fdcab82e2000000000000000000000000000000000000000000000000000000008152612b02600482015260240161008e565b6102e06040518061012001604052805f81526020015f81526020015f81526020015f81526020015f81526020015f81526020015f81526020015f81526020015f81525090565b5f5f5f8a126102ef575f6102f8565b6102f88a6131bb565b5f8c13610305575f610307565b8b5b61031191906131f1565b606495509050603f86111561045d576103628b8b8b8b8b8b8080601f0160208091040260200160405190810160405280939291908181526020018383808284375f9201919091525061154992505050565b1561038d57507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff82525f5b5f6103d08c8c8c8c8c8c8080601f0160208091040260200160405190810160405280939291908181526020018383808284375f9201919091525061176792505050565b93509050801561045b57818303610417576040517fdcab82e2000000000000000000000000000000000000000000000000000000008152612b0a600482015260240161008e565b82821115610453577fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff84526103e890950294908290039061045b565b90820383525f905b505b7fffffffffffffffffffffffff111111111111111111111111111111111111111273ffffffffffffffffffffffffffffffffffffffff8d16016104ed57348111806104af575061271085810182020434115b156104ea576040517fdcab82e2000000000000000000000000000000000000000000000000000000008152612b01600482015260240161008e565b505f5b801561073057605f86111561053e5761053b8188888080601f0160208091040260200160405190810160405280939291908181526020018383808284375f920191909152506119be92505050565b90505b6040517f70a0823100000000000000000000000000000000000000000000000000000000815230600482015273ffffffffffffffffffffffffffffffffffffffff8d16906370a0823190602401602060405180830381865afa1580156105a6573d5f5f3e3d5ffd5b505050506040513d601f19601f820116820180604052508101906105ca9190613231565b6040517fad207501000000000000000000000000000000000000000000000000000000008152909250339063ad2075019061060f908f9085908c908c90600401613248565b5f604051808303815f87803b158015610626575f5ffd5b505af1158015610638573d5f5f3e3d5ffd5b50506040517f70a0823100000000000000000000000000000000000000000000000000000000815230600482015284925073ffffffffffffffffffffffffffffffffffffffff8f1691506370a0823190602401602060405180830381865afa1580156106a6573d5f5f3e3d5ffd5b505050506040513d601f19601f820116820180604052508101906106ca9190613231565b6106d491906132b8565b9150808210806106eb575061271085810182020482115b15610726576040517fdcab82e2000000000000000000000000000000000000000000000000000000008152612b01600482015260240161008e565b6107308c83611add565b73ffffffffffffffffffffffffffffffffffffffff8c165f90815260056020526040902054610100840181905261076690611dd1565b60408086019190915260208086019290925273ffffffffffffffffffffffffffffffffffffffff8e165f9081526007909252908190205460e0850181905266ffffffffffffff600882901c811660ff8084169190911b6060880152604883901c82169383901c81169390931b608080880191909152608883901c9091169082901c83161b60a086015260c881901c60c091821c9283161b9085015291508a15610a24575f8360e0015190505f5f6108228f8f8860200151611fdf565b91509150805f036108dd575f82131561085f5761084582876060015160016124b5565b818660600181815161085791906131f1565b905250610897565b61087661086b836131bb565b87606001515f6124b5565b815f03866060015111610889575f610891565b606086015182015b60608701525b60608601516108aa90603860085f612524565b60e0870180517fffffffffffffffffffffffffffffffffffffffffffffffff0000000000000000168217905294506109df565b5f81131561090f576108f581876080015160016124b5565b808660800181815161090791906131f1565b905250610947565b61092661091b826131bb565b87608001515f6124b5565b805f03866080015111610939575f610941565b608086015181015b60808701525b60808601516f7fffffffffffffffffffffffffffffff1015610999576040517fdcab82e2000000000000000000000000000000000000000000000000000000008152612b06600482015260240161008e565b60808601516109ac90603860085f612524565b60e0870180517fffffffffffffffffffffffffffffffff0000000000000000ffffffffffffffff16604083901b17905294505b8560e001518303610a20576040517fdcab82e2000000000000000000000000000000000000000000000000000000008152612aff600482015260240161008e565b5050505b8915610c43575f8360e0015190505f5f610a438f8e886040015161264a565b91509150805f03610b03575f821315610a8057610a66828760a0015160016124b5565b818660a001818151610a7891906131f1565b905250610ab8565b610a97610a8c836131bb565b8760a001515f6124b5565b815f038660a0015111610aaa575f610ab2565b60a086015182015b60a08701525b60a0860151610acc90603860086001612524565b60e0870180517fffffffffffffffff0000000000000000ffffffffffffffffffffffffffffffff16608083901b1790529450610bfe565b5f811315610b3557610b1b818760c0015160016124b5565b808660c001818151610b2d91906131f1565b905250610b6d565b610b4c610b41826131bb565b8760c001515f6124b5565b805f038660c0015111610b5f575f610b67565b60c086015181015b60c08701525b60c08601516f7fffffffffffffffffffffffffffffff1015610bbf576040517fdcab82e2000000000000000000000000000000000000000000000000000000008152612b07600482015260240161008e565b60c0860151610bd390603860086001612524565b60e08701805177ffffffffffffffffffffffffffffffffffffffffffffffff1660c083901b17905294505b8560e001518303610c3f576040517fdcab82e2000000000000000000000000000000000000000000000000000000008152612aff600482015260240161008e565b5050505b60e083015173ffffffffffffffffffffffffffffffffffffffff8d165f90815260076020908152604090912091909155830151606084015164e8d4a5100091610c8b916132cb565b610c9591906132e2565b94506f7fffffffffffffffffffffffffffffff85118015610cb557505f8b135b15610cf0576040517fdcab82e2000000000000000000000000000000000000000000000000000000008152612b06600482015260240161008e565b848360800151610d0091906131f1565b91508260800151851115610d36576001856127108560800151610d2391906132cb565b610d2d91906132e2565b901b9450610d78565b8260800151851015610d6c576080830151600190610d56612710886132cb565b610d6091906132e2565b901b6001179450610d78565b8115610d7857614e2094505b64e8d4a5100083604001518460a00151610d9291906132cb565b610d9c91906132e2565b93506f7fffffffffffffffffffffffffffffff84118015610dbc57505f8a135b15610df7576040517fdcab82e2000000000000000000000000000000000000000000000000000000008152612b07600482015260240161008e565b838360c00151610e0791906131f1565b90508260c00151841115610e3d576001846127108560c00151610e2a91906132cb565b610e3491906132e2565b901b9350610e7f565b8260c00151841015610e735760c0830151600190610e5d612710876132cb565b610e6791906132e2565b901b6001179350610e7f565b8015610e7f57614e2093505b5f8215610f2d5782610e93612710846132cb565b610e9d91906132e2565b90505f8b1315610f2d5760f9846101000151901c600116600114610ec357612710610eed565b73ffffffffffffffffffffffffffffffffffffffff8d165f908152600b6020526040902054613fff165b925082811115610f2d576040517fdcab82e2000000000000000000000000000000000000000000000000000000008152612b03600482015260240161008e565b610100840151603a1c6401ffffffff166201518001421115610f525760019250610f56565b5f92505b825f0361107457613fff601e856101000151901c169250613fff602c856101000151901c16915081838211610f8d57818403610f91565b8382035b11610f9c575f610f9f565b60015b60ff169250825f0361107457617fff60db856101000151901c16925085600116836001160361100757600183901c92508183600188901c11610fe757600187901c8403610fef565b83600188901c035b11610ffa575f610ffd565b60015b60ff16925061100c565b600192505b825f0361107457617fff60ea856101000151901c16925084600116836001160361106f57600183901c92508183600187901c1161104f57600186901c8403611057565b83600187901c035b11611062575f611065565b60015b60ff169250611074565b600192505b826001036111f95773ffffffffffffffffffffffffffffffffffffffff8d165f908152600660205260409020546110ab90826128eb565b925067ffffffffffffffff846020015111806110d2575067ffffffffffffffff8460400151115b1561110d576040517fdcab82e2000000000000000000000000000000000000000000000000000000008152612b04600482015260240161008e565b613fff81111561114d576040517fdcab82e2000000000000000000000000000000000000000000000000000000008152612b05600482015260240161008e565b60ea85901b60db87901b609b8660400151901b605b8760200151901b603a42901b601e86901b888a61010001517ffe000000000000000000000000000000000000000000000003fff0003fff000016171717171717178461010001818152505083610100015160055f8f73ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f2081905550611240565b609b8460400151901b605b8560200151901b8561010001517ffffffffffc00000000000000000000000000000007ffffffffffffffffffffff161717846101000181815250505b505f8b128061124e57505f8a135b801561127a575082517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff125b156114a95782515f1215611334577fffffffffffffffffffffffff111111111111111111111111111111111111111273ffffffffffffffffffffffffffffffffffffffff8d16016112f8576112f373ffffffffffffffffffffffffffffffffffffffff8a16156112ea57896112ec565b885b8451612992565b6114a9565b6113058c845f01516129db565b6112f38c73ffffffffffffffffffffffffffffffffffffffff8b161561132b578a61132d565b895b8551612c4f565b5f8a13611341575f611343565b895b90505f8b1215611357578a5f03915061135b565b5f91505b5f8211801561136957505f81115b80156113a057508773ffffffffffffffffffffffffffffffffffffffff168973ffffffffffffffffffffffffffffffffffffffff16145b15611416577fffffffffffffffffffffffff111111111111111111111111111111111111111273ffffffffffffffffffffffffffffffffffffffff8d16016113f5576112f3896113f083856131f1565b612992565b6113ff81836131f1565b915061140b8c836129db565b6112f38c8a84612c4f565b7fffffffffffffffffffffffff111111111111111111111111111111111111111273ffffffffffffffffffffffffffffffffffffffff8d1601611473578115611463576114638983612992565b80156112f3576112f38882612992565b811561148e576114838c836129db565b61148e8c8a84612c4f565b80156114a95761149e8c826129db565b6114a98c8983612c4f565b60e0830151610100840151604080518e8152602081018e905273ffffffffffffffffffffffffffffffffffffffff8d8116828401528c81166060830152608082019490945260a081019290925251918e169133917f4d93b232a24e82b284ced7461bf4deacffe66759d5c24513e6f29e571ad78d15919081900360c00190a35050602081015160409091015160018055909a909950975050505050505050565b805181017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0015160408051808201909152600e81527f534b49505f5452414e53464552530000000000000000000000000000000000006020909101525f907f520f8ead5ba46586393d3a30de35560ff757e867d80fed46623eb8022af73ba381146115d7575f91505061175e565b341515806115e3575086155b806115ec575085155b806115f657508587135b15611631576040517fdcab82e2000000000000000000000000000000000000000000000000000000008152612b08600482015260240161008e565b82518301515f8813156116cd578473ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff1614801561168e575073ffffffffffffffffffffffffffffffffffffffff811633145b6116c8576040517fdcab82e2000000000000000000000000000000000000000000000000000000008152612b08600482015260240161008e565b611757565b8573ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff1614801561171d575073ffffffffffffffffffffffffffffffffffffffff811633145b611757576040517fdcab82e2000000000000000000000000000000000000000000000000000000008152612b08600482015260240161008e565b6001925050505b95945050505050565b805181017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0015160408051808201909152600d81527f4e45545f5452414e5346455253000000000000000000000000000000000000006020909101525f9081907f1c3af6d2a9b692a8b50895d7ef6c86616579c006b3b26b878c48ef9abe2a7eab81146117fa575f5f92509250506119b4565b5f8812611807575f611810565b611810886131bb565b5f881361181d575f61181f565b875b61182991906131f1565b9150871580611836575086155b8061184257505f878918125b1561187d576040517fdcab82e2000000000000000000000000000000000000000000000000000000008152612b0a600482015260240161008e565b83518401515f89121561191a578673ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff161415806118da575073ffffffffffffffffffffffffffffffffffffffff861615155b15611915576040517fdcab82e2000000000000000000000000000000000000000000000000000000008152612b0a600482015260240161008e565b6119ad565b5f8813156119ad578573ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff16141580611972575073ffffffffffffffffffffffffffffffffffffffff871615155b156119ad576040517fdcab82e2000000000000000000000000000000000000000000000000000000008152612b0a600482015260240161008e565b6001935050505b9550959350505050565b60208101515f9080848110806119f457506127106119dd6064826131f1565b6119e790876132cb565b6119f191906132e2565b81115b15611acd5760408051808201909152600581527f44455856320000000000000000000000000000000000000000000000000000006020909101527f2ee71ed3ac8c9a5524779d52750d68d33a5ebff09360b90ca0c7b6da00f5b24a8201611ac85750606083015184811080611a895750612710611a726064826131f1565b611a7c90876132cb565b611a8691906132e2565b81115b15611ac4576040517fdcab82e2000000000000000000000000000000000000000000000000000000008152612b01600482015260240161008e565b8094505b611ad1565b8094505b84925050505b92915050565b7fffffffffffffffffffffffff32a01dc37a7df0848d2f6d90364fa4bc1ca6481273ffffffffffffffffffffffffffffffffffffffff831601611c65576040517fdd62ed3e00000000000000000000000000000000000000000000000000000000815230600482015273f047ab4c75cebf0eb9ed34ae2c186f3611aeafa660248201525f9073cd5fe23c85820f7b72d0926fc9b05b43e359b7ee9063dd62ed3e90604401602060405180830381865afa158015611b9c573d5f5f3e3d5ffd5b505050506040513d601f19601f82011682018060405250810190611bc09190613231565b1115611c61576040517fb3db428b00000000000000000000000000000000000000000000000000000000815273cd5fe23c85820f7b72d0926fc9b05b43e359b7ee60048201523060248201526044810182905273f047ab4c75cebf0eb9ed34ae2c186f3611aeafa69063b3db428b906064015b5f604051808303815f87803b158015611c4a575f5ffd5b505af1158015611c5c573d5f5f3e3d5ffd5b505050505b5050565b7fffffffffffffffffffffffff6e83117fe59806cc0d194cc03f32e12d2a6f627873ffffffffffffffffffffffffffffffffffffffff831601611c61576040517fdd62ed3e00000000000000000000000000000000000000000000000000000000815230600482015273f047ab4c75cebf0eb9ed34ae2c186f3611aeafa660248201525f9073917cee801a67f933f2e6b33fc0cd1ed2d5909d889063dd62ed3e90604401602060405180830381865afa158015611d24573d5f5f3e3d5ffd5b505050506040513d601f19601f82011682018060405250810190611d489190613231565b1115611c61576040517fb3db428b00000000000000000000000000000000000000000000000000000000815273917cee801a67f933f2e6b33fc0cd1ed2d5909d8860048201523060248201526044810182905273f047ab4c75cebf0eb9ed34ae2c186f3611aeafa69063b3db428b906064015f604051808303815f87803b158015611c4a575f5ffd5b67ffffffffffffffff605b82901c811690609b83901c16811580611df3575080155b15611e2f576040517fd50d751200000000000000000000000000000000000000000000000000000000815262011171600482015260240161008e565b61ffff8316603a84901c6401ffffffff16428181039160ea87901c617fff16911480611e59575082155b80611e645750806001145b15611e7157505050915091565b64496cebb80084840283020484019350617fff60db87901c16925082600103611e9c57505050915091565b82600116600103611ef15760019290921c91826c7e37be2022c0914b268000000081611eca57611eca613204565b049250612710601e87901c613fff166b033b2e3c9fd0803ce8000000850102049250611f1e565b60019290921c916305f5e100601e87901c613fff166127108501026b033b2e3c9fd0803ce8000000020492505b80600116600103611f555760011c61271081016b033b2e3c9fd0803ce8000000820281611f4d57611f4d613204565b049050611f8b565b60011c61271081016b033b2e3c9fd0803ce8000000820281611f7957611f79613204565b046b033b2e3c9fd0803ce80000000390505b760a70c3c40a64e6c51999090b65f67d92400000000000008382026127100261ffff881691900402601087901c613fff16612710030292506801b5a660ea44b8000085840283020485019450505050915091565b335f90815260086020908152604080832073ffffffffffffffffffffffffffffffffffffffff87168452909152812054819080820361204e576040517fdcab82e2000000000000000000000000000000000000000000000000000000008152612af9600482015260240161008e565b60ff81901c600103612090576040517fdcab82e2000000000000000000000000000000000000000000000000000000008152612afa600482015260240161008e565b66ffffffffffffff600982901c1660ff600183901c81169190911b906203ffff60e284901c1660da84901c9091161b60f483901c6103ff16811561211c575f6024600a608187901c6401ffffffff1602046024600a4202040390508181101561211357818184028161210457612104613204565b0483039250808203915061211a565b5f92505f91505b505b5f6121278585612cf5565b90508460011660010361219f575f891315612169578761214c64e8d4a510008b6132f5565b6121569190613340565b965061216287856131f1565b93506121ca565b6121816121758a6131bb565b64e8d4a510008a612d86565b61218a906131bb565b9650612195876131bb565b61216290856132b8565b8895505f8613156121b45761216286856131f1565b6121bd866131bb565b6121c790856132b8565b93505b5f5f8a121561225e5781851015612211576040517fdcab82e2000000000000000000000000000000000000000000000000000000008152612afb600482015260240161008e565b831561225e578787015f038481111561223f57848311612231575f612235565b8483035b92505f9450612258565b80831161224c575f612250565b8083035b925080850394505b50600190505b5f61226a878785612dc8565b9050805f0361227b575f945061233a565b80831461233a575f8b131561231257825f036122a6576103ff60d088901c1660ff60c889901c161b92505b82811115612307578281036122bb81876131f1565b6122c7826103e86132cb565b6122d188886132cb565b6122db91906131f1565b6122e591906132e2565b945060508510156122f557605094505b6122ff81876131f1565b95505061233a565b5f94505f935061233a565b811561233a575f838211612326575f61232a565b8382035b905061233681876131f1565b9550505b600a85101561234e575f94505f935061237e565b61235c85601260085f612524565b94506103e8841115612372576103e8935061237e565b835f0361237e57600193505b61238c86603860085f612524565b95508567ffffffffffffffff600189901c16036123d9576040517fdcab82e2000000000000000000000000000000000000000000000000000000008152612aff600482015260240161008e565b6123e781603860085f612524565b905060f484901b60da86901b608142901b604184901b60018a901b8b7fc000000003fffffffffffffc000000000000000000000000000000000000000116171717171760085f3373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f205f8e73ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f208190555050505050505050935093915050565b806124c357600282046124c9565b81612710025b91506a0100000000000000000000831180156124e457508183115b1561251f576040517fdcab82e2000000000000000000000000000000000000000000000000000000008152612b09600482015260240161008e565b505050565b5f5f856fffffffffffffffffffffffffffffffff8111156125465760809150811c5b67ffffffffffffffff81111561255e576040918201911c5b63ffffffff811115612572576020918201911c5b61ffff811115612584576010918201911c5b60ff811115612595576008918201911c5b600f8111156125a6576004918201911c5b60038111156125b7576002918201911c5b60018111156125c7576001820191505b80156125d4576001820191505b50848110156125e05750835b848103905085811c5f821184161561262e57600181019050806001871b0361262e57506001908101907fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff86011b5b6001851b821061263c575f5ffd5b90931b909201949350505050565b335f90815260096020908152604080832073ffffffffffffffffffffffffffffffffffffffff8716845290915281205481908082036126b9576040517fdcab82e2000000000000000000000000000000000000000000000000000000008152612af9600482015260240161008e565b60ff81901c6001036126fb576040517fdcab82e2000000000000000000000000000000000000000000000000000000008152612afa600482015260240161008e565b66ffffffffffffff600982901c1660ff600183901c161b5f61271d8383612e19565b905082600116600103612784575f871315612755576127428764e8d4a5100088612d86565b945061274e85836131f1565b91506127af565b8561276564e8d4a51000896132f5565b61276f9190613340565b945061277a856131bb565b61274e90836132b8565b8693505f8413156127995761274e84836131f1565b6127a2846131bb565b6127ac90836132b8565b91505b5f871380156127bd57508082115b156127f8576040517fdcab82e2000000000000000000000000000000000000000000000000000000008152612afc600482015260240161008e565b612803838383612ed5565b905061281482603860086001612524565b91508167ffffffffffffffff600185901c1603612861576040517fdcab82e2000000000000000000000000000000000000000000000000000000008152612aff600482015260240161008e565b61286f81603860085f612524565b335f90815260096020908152604080832073ffffffffffffffffffffffffffffffffffffffff9c909c1683529a9052989098207ffffffffffffffffffffffffc000000000000000000000000000000000000000190931660019290921b9190911760419790971b969096174260811b1790559094909350915050565b5f600f8316600181900361290a576129038484612f34565b9150612953565b8060020361291c57612903848461300f565b6040517fd50d751200000000000000000000000000000000000000000000000000000000815262011172600482015260240161008e565b61ffff82111561298b5760405161ffff92507fe41708b2641eb1f7442f6a1e760ae5098fd7ba9f85e0c101513add0fbb27bcd7905f90a15b5092915050565b5f5f5f5f5f858761c350f190508061251f576040517fdee51a8a0000000000000000000000000000000000000000000000000000000081526201155a600482015260240161008e565b7fffffffffffffffffffffffff32a01dc37a7df0848d2f6d90364fa4bc1ca6481273ffffffffffffffffffffffffffffffffffffffff831601612b15576040517f70a08231000000000000000000000000000000000000000000000000000000008152306004820152819073cd5fe23c85820f7b72d0926fc9b05b43e359b7ee906370a0823190602401602060405180830381865afa158015612a80573d5f5f3e3d5ffd5b505050506040513d601f19601f82011682018060405250810190612aa49190613231565b1015611c61576040517ff3fef3a300000000000000000000000000000000000000000000000000000000815273cd5fe23c85820f7b72d0926fc9b05b43e359b7ee60048201526024810182905273f047ab4c75cebf0eb9ed34ae2c186f3611aeafa69063f3fef3a390604401611c33565b7fffffffffffffffffffffffff6e83117fe59806cc0d194cc03f32e12d2a6f627873ffffffffffffffffffffffffffffffffffffffff831601611c61576040517f70a08231000000000000000000000000000000000000000000000000000000008152306004820152819073917cee801a67f933f2e6b33fc0cd1ed2d5909d88906370a0823190602401602060405180830381865afa158015612bba573d5f5f3e3d5ffd5b505050506040513d601f19601f82011682018060405250810190612bde9190613231565b1015611c61576040517ff3fef3a300000000000000000000000000000000000000000000000000000000815273917cee801a67f933f2e6b33fc0cd1ed2d5909d8860048201526024810182905273f047ab4c75cebf0eb9ed34ae2c186f3611aeafa69063f3fef3a390604401611c33565b5f6040517fa9059cbb00000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff8416600482015282602482015260205f6044835f895af13d15601f3d1160015f511416171691505080612cef576040517fdee51a8a0000000000000000000000000000000000000000000000000000000081526201155a600482015260240161008e565b50505050565b5f66ffffffffffffff604984901c1660ff604185901c161b808203612d1d575f915050611ad7565b612710613fff60a286901c168402046401ffffffff608186901c16420362ffffff60b087901c16612d4e82846132cb565b612d5891906132e2565b9050808311612d67575f612d6b565b8083035b93505080840383811115612d7d578093505b50505092915050565b5f827fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0484118302158202612db9575f5ffd5b50910281810615159190040190565b5f6103ff60d085901c1660ff60c886901c161b80841015612dec575f915050612e12565b50612710613fff60a286901c16840204830382811115612e0d579050612e12565b829150505b9392505050565b5f608183901c6401ffffffff164203612710613fff60a286901c16840204808401604986901c66ffffffffffffff16604187901c60ff161b60b087901c62ffffff16612e6585856132cb565b612e6f91906132e2565b612e7991906131f1565b935080841115612e87578093505b6103ff60d087901c1660ff60c888901c161b925082841015612eae57829350505050611ad7565b6103ff60e287901c1660ff60da88901c161b925082841115612d7d57829350505050611ad7565b612710613fff60a285901c16830204820160d084901c6103ff1660c885901c60ff161b80821015612f07579050612e12565b506103ff60e285901c1660ff60da86901c161b80821115612f26578091505b81831115612e0d5750612e12565b5f80808080601487901c61ffff1680871015612f6b5761ffff600489901c16945061ffff602489901c1693505f9250809150612f8a565b61ffff602489901c16945061ffff603489901c16935080925061271091505b5f5f84840364e8d4a510008888030281612fa657612fa6613204565b0585810264e8d4a5100089020392508902820190505f811215612ffa576040517fd50d751200000000000000000000000000000000000000000000000000000000815262011173600482015260240161008e565b64e8d4a5100090049998505050505050505050565b5f80808080601487901c61ffff16808710156130465761ffff600489901c16945061ffff602489901c1693505f9250809150612f8a565b603488901c61ffff16808810156130785761ffff60248a901c16955061ffff60448a901c169450819350809250613097565b61ffff60448a901c16955061ffff60548a901c16945080935061271092505b505f5f84840364e8d4a510008888030281612fa657612fa6613204565b803573ffffffffffffffffffffffffffffffffffffffff811681146130d7575f5ffd5b919050565b5f5f5f5f5f5f5f60c0888a0312156130f2575f5ffd5b6130fb886130b4565b96506020880135955060408801359450613117606089016130b4565b9350613125608089016130b4565b925060a088013567ffffffffffffffff811115613140575f5ffd5b8801601f81018a13613150575f5ffd5b803567ffffffffffffffff811115613166575f5ffd5b8a6020828401011115613177575f5ffd5b602082019350809250505092959891949750929550565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52601160045260245ffd5b5f7f800000000000000000000000000000000000000000000000000000000000000082036131eb576131eb61318e565b505f0390565b80820180821115611ad757611ad761318e565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52601260045260245ffd5b5f60208284031215613241575f5ffd5b5051919050565b73ffffffffffffffffffffffffffffffffffffffff8516815283602082015260606040820152816060820152818360808301375f818301608090810191909152601f9092017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe01601019392505050565b81810381811115611ad757611ad761318e565b8082028115828204841417611ad757611ad761318e565b5f826132f0576132f0613204565b500490565b8082025f82127f80000000000000000000000000000000000000000000000000000000000000008414161561332c5761332c61318e565b8181058314821517611ad757611ad761318e565b5f8261334e5761334e613204565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff83147f8000000000000000000000000000000000000000000000000000000000000000831416156133a2576133a261318e565b50059056fea2646970667358221220a485a525474d247066749e3c247384fe17bc6d01b812c6e1a23dca9aea71e51764736f6c634300081d0033
Deployed Bytecode
0x60806040526004361061001d575f3560e01c8063ad967e1514610021575b5f5ffd5b61003461002f3660046130dc565b61004d565b6040805192835260208301919091520160405180910390f35b5f5f600260ff1660015403610097576040517fdcab82e2000000000000000000000000000000000000000000000000000000008152612ee160048201526024015b60405180910390fd5b6002600155871580156100a8575086155b156100e3576040517fdcab82e2000000000000000000000000000000000000000000000000000000008152612afd600482015260240161008e565b7fffffffffffffffffffffffffffffffff8000000000000000000000000000000088128061012057506f7fffffffffffffffffffffffffffffff88135b8061014a57507fffffffffffffffffffffffffffffffff8000000000000000000000000000000087125b8061016457506f7fffffffffffffffffffffffffffffff87135b1561019f576040517fdcab82e2000000000000000000000000000000000000000000000000000000008152612afe600482015260240161008e565b5f881280156101c2575073ffffffffffffffffffffffffffffffffffffffff8616155b806101eb57505f871380156101eb575073ffffffffffffffffffffffffffffffffffffffff8516155b15610226576040517fdcab82e2000000000000000000000000000000000000000000000000000000008152612b00600482015260240161008e565b73ffffffffffffffffffffffffffffffffffffffff891673eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee1480159061025f57505f34115b1561029a576040517fdcab82e2000000000000000000000000000000000000000000000000000000008152612b02600482015260240161008e565b6102e06040518061012001604052805f81526020015f81526020015f81526020015f81526020015f81526020015f81526020015f81526020015f81526020015f81525090565b5f5f5f8a126102ef575f6102f8565b6102f88a6131bb565b5f8c13610305575f610307565b8b5b61031191906131f1565b606495509050603f86111561045d576103628b8b8b8b8b8b8080601f0160208091040260200160405190810160405280939291908181526020018383808284375f9201919091525061154992505050565b1561038d57507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff82525f5b5f6103d08c8c8c8c8c8c8080601f0160208091040260200160405190810160405280939291908181526020018383808284375f9201919091525061176792505050565b93509050801561045b57818303610417576040517fdcab82e2000000000000000000000000000000000000000000000000000000008152612b0a600482015260240161008e565b82821115610453577fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff84526103e890950294908290039061045b565b90820383525f905b505b7fffffffffffffffffffffffff111111111111111111111111111111111111111273ffffffffffffffffffffffffffffffffffffffff8d16016104ed57348111806104af575061271085810182020434115b156104ea576040517fdcab82e2000000000000000000000000000000000000000000000000000000008152612b01600482015260240161008e565b505f5b801561073057605f86111561053e5761053b8188888080601f0160208091040260200160405190810160405280939291908181526020018383808284375f920191909152506119be92505050565b90505b6040517f70a0823100000000000000000000000000000000000000000000000000000000815230600482015273ffffffffffffffffffffffffffffffffffffffff8d16906370a0823190602401602060405180830381865afa1580156105a6573d5f5f3e3d5ffd5b505050506040513d601f19601f820116820180604052508101906105ca9190613231565b6040517fad207501000000000000000000000000000000000000000000000000000000008152909250339063ad2075019061060f908f9085908c908c90600401613248565b5f604051808303815f87803b158015610626575f5ffd5b505af1158015610638573d5f5f3e3d5ffd5b50506040517f70a0823100000000000000000000000000000000000000000000000000000000815230600482015284925073ffffffffffffffffffffffffffffffffffffffff8f1691506370a0823190602401602060405180830381865afa1580156106a6573d5f5f3e3d5ffd5b505050506040513d601f19601f820116820180604052508101906106ca9190613231565b6106d491906132b8565b9150808210806106eb575061271085810182020482115b15610726576040517fdcab82e2000000000000000000000000000000000000000000000000000000008152612b01600482015260240161008e565b6107308c83611add565b73ffffffffffffffffffffffffffffffffffffffff8c165f90815260056020526040902054610100840181905261076690611dd1565b60408086019190915260208086019290925273ffffffffffffffffffffffffffffffffffffffff8e165f9081526007909252908190205460e0850181905266ffffffffffffff600882901c811660ff8084169190911b6060880152604883901c82169383901c81169390931b608080880191909152608883901c9091169082901c83161b60a086015260c881901c60c091821c9283161b9085015291508a15610a24575f8360e0015190505f5f6108228f8f8860200151611fdf565b91509150805f036108dd575f82131561085f5761084582876060015160016124b5565b818660600181815161085791906131f1565b905250610897565b61087661086b836131bb565b87606001515f6124b5565b815f03866060015111610889575f610891565b606086015182015b60608701525b60608601516108aa90603860085f612524565b60e0870180517fffffffffffffffffffffffffffffffffffffffffffffffff0000000000000000168217905294506109df565b5f81131561090f576108f581876080015160016124b5565b808660800181815161090791906131f1565b905250610947565b61092661091b826131bb565b87608001515f6124b5565b805f03866080015111610939575f610941565b608086015181015b60808701525b60808601516f7fffffffffffffffffffffffffffffff1015610999576040517fdcab82e2000000000000000000000000000000000000000000000000000000008152612b06600482015260240161008e565b60808601516109ac90603860085f612524565b60e0870180517fffffffffffffffffffffffffffffffff0000000000000000ffffffffffffffff16604083901b17905294505b8560e001518303610a20576040517fdcab82e2000000000000000000000000000000000000000000000000000000008152612aff600482015260240161008e565b5050505b8915610c43575f8360e0015190505f5f610a438f8e886040015161264a565b91509150805f03610b03575f821315610a8057610a66828760a0015160016124b5565b818660a001818151610a7891906131f1565b905250610ab8565b610a97610a8c836131bb565b8760a001515f6124b5565b815f038660a0015111610aaa575f610ab2565b60a086015182015b60a08701525b60a0860151610acc90603860086001612524565b60e0870180517fffffffffffffffff0000000000000000ffffffffffffffffffffffffffffffff16608083901b1790529450610bfe565b5f811315610b3557610b1b818760c0015160016124b5565b808660c001818151610b2d91906131f1565b905250610b6d565b610b4c610b41826131bb565b8760c001515f6124b5565b805f038660c0015111610b5f575f610b67565b60c086015181015b60c08701525b60c08601516f7fffffffffffffffffffffffffffffff1015610bbf576040517fdcab82e2000000000000000000000000000000000000000000000000000000008152612b07600482015260240161008e565b60c0860151610bd390603860086001612524565b60e08701805177ffffffffffffffffffffffffffffffffffffffffffffffff1660c083901b17905294505b8560e001518303610c3f576040517fdcab82e2000000000000000000000000000000000000000000000000000000008152612aff600482015260240161008e565b5050505b60e083015173ffffffffffffffffffffffffffffffffffffffff8d165f90815260076020908152604090912091909155830151606084015164e8d4a5100091610c8b916132cb565b610c9591906132e2565b94506f7fffffffffffffffffffffffffffffff85118015610cb557505f8b135b15610cf0576040517fdcab82e2000000000000000000000000000000000000000000000000000000008152612b06600482015260240161008e565b848360800151610d0091906131f1565b91508260800151851115610d36576001856127108560800151610d2391906132cb565b610d2d91906132e2565b901b9450610d78565b8260800151851015610d6c576080830151600190610d56612710886132cb565b610d6091906132e2565b901b6001179450610d78565b8115610d7857614e2094505b64e8d4a5100083604001518460a00151610d9291906132cb565b610d9c91906132e2565b93506f7fffffffffffffffffffffffffffffff84118015610dbc57505f8a135b15610df7576040517fdcab82e2000000000000000000000000000000000000000000000000000000008152612b07600482015260240161008e565b838360c00151610e0791906131f1565b90508260c00151841115610e3d576001846127108560c00151610e2a91906132cb565b610e3491906132e2565b901b9350610e7f565b8260c00151841015610e735760c0830151600190610e5d612710876132cb565b610e6791906132e2565b901b6001179350610e7f565b8015610e7f57614e2093505b5f8215610f2d5782610e93612710846132cb565b610e9d91906132e2565b90505f8b1315610f2d5760f9846101000151901c600116600114610ec357612710610eed565b73ffffffffffffffffffffffffffffffffffffffff8d165f908152600b6020526040902054613fff165b925082811115610f2d576040517fdcab82e2000000000000000000000000000000000000000000000000000000008152612b03600482015260240161008e565b610100840151603a1c6401ffffffff166201518001421115610f525760019250610f56565b5f92505b825f0361107457613fff601e856101000151901c169250613fff602c856101000151901c16915081838211610f8d57818403610f91565b8382035b11610f9c575f610f9f565b60015b60ff169250825f0361107457617fff60db856101000151901c16925085600116836001160361100757600183901c92508183600188901c11610fe757600187901c8403610fef565b83600188901c035b11610ffa575f610ffd565b60015b60ff16925061100c565b600192505b825f0361107457617fff60ea856101000151901c16925084600116836001160361106f57600183901c92508183600187901c1161104f57600186901c8403611057565b83600187901c035b11611062575f611065565b60015b60ff169250611074565b600192505b826001036111f95773ffffffffffffffffffffffffffffffffffffffff8d165f908152600660205260409020546110ab90826128eb565b925067ffffffffffffffff846020015111806110d2575067ffffffffffffffff8460400151115b1561110d576040517fdcab82e2000000000000000000000000000000000000000000000000000000008152612b04600482015260240161008e565b613fff81111561114d576040517fdcab82e2000000000000000000000000000000000000000000000000000000008152612b05600482015260240161008e565b60ea85901b60db87901b609b8660400151901b605b8760200151901b603a42901b601e86901b888a61010001517ffe000000000000000000000000000000000000000000000003fff0003fff000016171717171717178461010001818152505083610100015160055f8f73ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f2081905550611240565b609b8460400151901b605b8560200151901b8561010001517ffffffffffc00000000000000000000000000000007ffffffffffffffffffffff161717846101000181815250505b505f8b128061124e57505f8a135b801561127a575082517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff125b156114a95782515f1215611334577fffffffffffffffffffffffff111111111111111111111111111111111111111273ffffffffffffffffffffffffffffffffffffffff8d16016112f8576112f373ffffffffffffffffffffffffffffffffffffffff8a16156112ea57896112ec565b885b8451612992565b6114a9565b6113058c845f01516129db565b6112f38c73ffffffffffffffffffffffffffffffffffffffff8b161561132b578a61132d565b895b8551612c4f565b5f8a13611341575f611343565b895b90505f8b1215611357578a5f03915061135b565b5f91505b5f8211801561136957505f81115b80156113a057508773ffffffffffffffffffffffffffffffffffffffff168973ffffffffffffffffffffffffffffffffffffffff16145b15611416577fffffffffffffffffffffffff111111111111111111111111111111111111111273ffffffffffffffffffffffffffffffffffffffff8d16016113f5576112f3896113f083856131f1565b612992565b6113ff81836131f1565b915061140b8c836129db565b6112f38c8a84612c4f565b7fffffffffffffffffffffffff111111111111111111111111111111111111111273ffffffffffffffffffffffffffffffffffffffff8d1601611473578115611463576114638983612992565b80156112f3576112f38882612992565b811561148e576114838c836129db565b61148e8c8a84612c4f565b80156114a95761149e8c826129db565b6114a98c8983612c4f565b60e0830151610100840151604080518e8152602081018e905273ffffffffffffffffffffffffffffffffffffffff8d8116828401528c81166060830152608082019490945260a081019290925251918e169133917f4d93b232a24e82b284ced7461bf4deacffe66759d5c24513e6f29e571ad78d15919081900360c00190a35050602081015160409091015160018055909a909950975050505050505050565b805181017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0015160408051808201909152600e81527f534b49505f5452414e53464552530000000000000000000000000000000000006020909101525f907f520f8ead5ba46586393d3a30de35560ff757e867d80fed46623eb8022af73ba381146115d7575f91505061175e565b341515806115e3575086155b806115ec575085155b806115f657508587135b15611631576040517fdcab82e2000000000000000000000000000000000000000000000000000000008152612b08600482015260240161008e565b82518301515f8813156116cd578473ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff1614801561168e575073ffffffffffffffffffffffffffffffffffffffff811633145b6116c8576040517fdcab82e2000000000000000000000000000000000000000000000000000000008152612b08600482015260240161008e565b611757565b8573ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff1614801561171d575073ffffffffffffffffffffffffffffffffffffffff811633145b611757576040517fdcab82e2000000000000000000000000000000000000000000000000000000008152612b08600482015260240161008e565b6001925050505b95945050505050565b805181017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0015160408051808201909152600d81527f4e45545f5452414e5346455253000000000000000000000000000000000000006020909101525f9081907f1c3af6d2a9b692a8b50895d7ef6c86616579c006b3b26b878c48ef9abe2a7eab81146117fa575f5f92509250506119b4565b5f8812611807575f611810565b611810886131bb565b5f881361181d575f61181f565b875b61182991906131f1565b9150871580611836575086155b8061184257505f878918125b1561187d576040517fdcab82e2000000000000000000000000000000000000000000000000000000008152612b0a600482015260240161008e565b83518401515f89121561191a578673ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff161415806118da575073ffffffffffffffffffffffffffffffffffffffff861615155b15611915576040517fdcab82e2000000000000000000000000000000000000000000000000000000008152612b0a600482015260240161008e565b6119ad565b5f8813156119ad578573ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff16141580611972575073ffffffffffffffffffffffffffffffffffffffff871615155b156119ad576040517fdcab82e2000000000000000000000000000000000000000000000000000000008152612b0a600482015260240161008e565b6001935050505b9550959350505050565b60208101515f9080848110806119f457506127106119dd6064826131f1565b6119e790876132cb565b6119f191906132e2565b81115b15611acd5760408051808201909152600581527f44455856320000000000000000000000000000000000000000000000000000006020909101527f2ee71ed3ac8c9a5524779d52750d68d33a5ebff09360b90ca0c7b6da00f5b24a8201611ac85750606083015184811080611a895750612710611a726064826131f1565b611a7c90876132cb565b611a8691906132e2565b81115b15611ac4576040517fdcab82e2000000000000000000000000000000000000000000000000000000008152612b01600482015260240161008e565b8094505b611ad1565b8094505b84925050505b92915050565b7fffffffffffffffffffffffff32a01dc37a7df0848d2f6d90364fa4bc1ca6481273ffffffffffffffffffffffffffffffffffffffff831601611c65576040517fdd62ed3e00000000000000000000000000000000000000000000000000000000815230600482015273f047ab4c75cebf0eb9ed34ae2c186f3611aeafa660248201525f9073cd5fe23c85820f7b72d0926fc9b05b43e359b7ee9063dd62ed3e90604401602060405180830381865afa158015611b9c573d5f5f3e3d5ffd5b505050506040513d601f19601f82011682018060405250810190611bc09190613231565b1115611c61576040517fb3db428b00000000000000000000000000000000000000000000000000000000815273cd5fe23c85820f7b72d0926fc9b05b43e359b7ee60048201523060248201526044810182905273f047ab4c75cebf0eb9ed34ae2c186f3611aeafa69063b3db428b906064015b5f604051808303815f87803b158015611c4a575f5ffd5b505af1158015611c5c573d5f5f3e3d5ffd5b505050505b5050565b7fffffffffffffffffffffffff6e83117fe59806cc0d194cc03f32e12d2a6f627873ffffffffffffffffffffffffffffffffffffffff831601611c61576040517fdd62ed3e00000000000000000000000000000000000000000000000000000000815230600482015273f047ab4c75cebf0eb9ed34ae2c186f3611aeafa660248201525f9073917cee801a67f933f2e6b33fc0cd1ed2d5909d889063dd62ed3e90604401602060405180830381865afa158015611d24573d5f5f3e3d5ffd5b505050506040513d601f19601f82011682018060405250810190611d489190613231565b1115611c61576040517fb3db428b00000000000000000000000000000000000000000000000000000000815273917cee801a67f933f2e6b33fc0cd1ed2d5909d8860048201523060248201526044810182905273f047ab4c75cebf0eb9ed34ae2c186f3611aeafa69063b3db428b906064015f604051808303815f87803b158015611c4a575f5ffd5b67ffffffffffffffff605b82901c811690609b83901c16811580611df3575080155b15611e2f576040517fd50d751200000000000000000000000000000000000000000000000000000000815262011171600482015260240161008e565b61ffff8316603a84901c6401ffffffff16428181039160ea87901c617fff16911480611e59575082155b80611e645750806001145b15611e7157505050915091565b64496cebb80084840283020484019350617fff60db87901c16925082600103611e9c57505050915091565b82600116600103611ef15760019290921c91826c7e37be2022c0914b268000000081611eca57611eca613204565b049250612710601e87901c613fff166b033b2e3c9fd0803ce8000000850102049250611f1e565b60019290921c916305f5e100601e87901c613fff166127108501026b033b2e3c9fd0803ce8000000020492505b80600116600103611f555760011c61271081016b033b2e3c9fd0803ce8000000820281611f4d57611f4d613204565b049050611f8b565b60011c61271081016b033b2e3c9fd0803ce8000000820281611f7957611f79613204565b046b033b2e3c9fd0803ce80000000390505b760a70c3c40a64e6c51999090b65f67d92400000000000008382026127100261ffff881691900402601087901c613fff16612710030292506801b5a660ea44b8000085840283020485019450505050915091565b335f90815260086020908152604080832073ffffffffffffffffffffffffffffffffffffffff87168452909152812054819080820361204e576040517fdcab82e2000000000000000000000000000000000000000000000000000000008152612af9600482015260240161008e565b60ff81901c600103612090576040517fdcab82e2000000000000000000000000000000000000000000000000000000008152612afa600482015260240161008e565b66ffffffffffffff600982901c1660ff600183901c81169190911b906203ffff60e284901c1660da84901c9091161b60f483901c6103ff16811561211c575f6024600a608187901c6401ffffffff1602046024600a4202040390508181101561211357818184028161210457612104613204565b0483039250808203915061211a565b5f92505f91505b505b5f6121278585612cf5565b90508460011660010361219f575f891315612169578761214c64e8d4a510008b6132f5565b6121569190613340565b965061216287856131f1565b93506121ca565b6121816121758a6131bb565b64e8d4a510008a612d86565b61218a906131bb565b9650612195876131bb565b61216290856132b8565b8895505f8613156121b45761216286856131f1565b6121bd866131bb565b6121c790856132b8565b93505b5f5f8a121561225e5781851015612211576040517fdcab82e2000000000000000000000000000000000000000000000000000000008152612afb600482015260240161008e565b831561225e578787015f038481111561223f57848311612231575f612235565b8483035b92505f9450612258565b80831161224c575f612250565b8083035b925080850394505b50600190505b5f61226a878785612dc8565b9050805f0361227b575f945061233a565b80831461233a575f8b131561231257825f036122a6576103ff60d088901c1660ff60c889901c161b92505b82811115612307578281036122bb81876131f1565b6122c7826103e86132cb565b6122d188886132cb565b6122db91906131f1565b6122e591906132e2565b945060508510156122f557605094505b6122ff81876131f1565b95505061233a565b5f94505f935061233a565b811561233a575f838211612326575f61232a565b8382035b905061233681876131f1565b9550505b600a85101561234e575f94505f935061237e565b61235c85601260085f612524565b94506103e8841115612372576103e8935061237e565b835f0361237e57600193505b61238c86603860085f612524565b95508567ffffffffffffffff600189901c16036123d9576040517fdcab82e2000000000000000000000000000000000000000000000000000000008152612aff600482015260240161008e565b6123e781603860085f612524565b905060f484901b60da86901b608142901b604184901b60018a901b8b7fc000000003fffffffffffffc000000000000000000000000000000000000000116171717171760085f3373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f205f8e73ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f208190555050505050505050935093915050565b806124c357600282046124c9565b81612710025b91506a0100000000000000000000831180156124e457508183115b1561251f576040517fdcab82e2000000000000000000000000000000000000000000000000000000008152612b09600482015260240161008e565b505050565b5f5f856fffffffffffffffffffffffffffffffff8111156125465760809150811c5b67ffffffffffffffff81111561255e576040918201911c5b63ffffffff811115612572576020918201911c5b61ffff811115612584576010918201911c5b60ff811115612595576008918201911c5b600f8111156125a6576004918201911c5b60038111156125b7576002918201911c5b60018111156125c7576001820191505b80156125d4576001820191505b50848110156125e05750835b848103905085811c5f821184161561262e57600181019050806001871b0361262e57506001908101907fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff86011b5b6001851b821061263c575f5ffd5b90931b909201949350505050565b335f90815260096020908152604080832073ffffffffffffffffffffffffffffffffffffffff8716845290915281205481908082036126b9576040517fdcab82e2000000000000000000000000000000000000000000000000000000008152612af9600482015260240161008e565b60ff81901c6001036126fb576040517fdcab82e2000000000000000000000000000000000000000000000000000000008152612afa600482015260240161008e565b66ffffffffffffff600982901c1660ff600183901c161b5f61271d8383612e19565b905082600116600103612784575f871315612755576127428764e8d4a5100088612d86565b945061274e85836131f1565b91506127af565b8561276564e8d4a51000896132f5565b61276f9190613340565b945061277a856131bb565b61274e90836132b8565b8693505f8413156127995761274e84836131f1565b6127a2846131bb565b6127ac90836132b8565b91505b5f871380156127bd57508082115b156127f8576040517fdcab82e2000000000000000000000000000000000000000000000000000000008152612afc600482015260240161008e565b612803838383612ed5565b905061281482603860086001612524565b91508167ffffffffffffffff600185901c1603612861576040517fdcab82e2000000000000000000000000000000000000000000000000000000008152612aff600482015260240161008e565b61286f81603860085f612524565b335f90815260096020908152604080832073ffffffffffffffffffffffffffffffffffffffff9c909c1683529a9052989098207ffffffffffffffffffffffffc000000000000000000000000000000000000000190931660019290921b9190911760419790971b969096174260811b1790559094909350915050565b5f600f8316600181900361290a576129038484612f34565b9150612953565b8060020361291c57612903848461300f565b6040517fd50d751200000000000000000000000000000000000000000000000000000000815262011172600482015260240161008e565b61ffff82111561298b5760405161ffff92507fe41708b2641eb1f7442f6a1e760ae5098fd7ba9f85e0c101513add0fbb27bcd7905f90a15b5092915050565b5f5f5f5f5f858761c350f190508061251f576040517fdee51a8a0000000000000000000000000000000000000000000000000000000081526201155a600482015260240161008e565b7fffffffffffffffffffffffff32a01dc37a7df0848d2f6d90364fa4bc1ca6481273ffffffffffffffffffffffffffffffffffffffff831601612b15576040517f70a08231000000000000000000000000000000000000000000000000000000008152306004820152819073cd5fe23c85820f7b72d0926fc9b05b43e359b7ee906370a0823190602401602060405180830381865afa158015612a80573d5f5f3e3d5ffd5b505050506040513d601f19601f82011682018060405250810190612aa49190613231565b1015611c61576040517ff3fef3a300000000000000000000000000000000000000000000000000000000815273cd5fe23c85820f7b72d0926fc9b05b43e359b7ee60048201526024810182905273f047ab4c75cebf0eb9ed34ae2c186f3611aeafa69063f3fef3a390604401611c33565b7fffffffffffffffffffffffff6e83117fe59806cc0d194cc03f32e12d2a6f627873ffffffffffffffffffffffffffffffffffffffff831601611c61576040517f70a08231000000000000000000000000000000000000000000000000000000008152306004820152819073917cee801a67f933f2e6b33fc0cd1ed2d5909d88906370a0823190602401602060405180830381865afa158015612bba573d5f5f3e3d5ffd5b505050506040513d601f19601f82011682018060405250810190612bde9190613231565b1015611c61576040517ff3fef3a300000000000000000000000000000000000000000000000000000000815273917cee801a67f933f2e6b33fc0cd1ed2d5909d8860048201526024810182905273f047ab4c75cebf0eb9ed34ae2c186f3611aeafa69063f3fef3a390604401611c33565b5f6040517fa9059cbb00000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff8416600482015282602482015260205f6044835f895af13d15601f3d1160015f511416171691505080612cef576040517fdee51a8a0000000000000000000000000000000000000000000000000000000081526201155a600482015260240161008e565b50505050565b5f66ffffffffffffff604984901c1660ff604185901c161b808203612d1d575f915050611ad7565b612710613fff60a286901c168402046401ffffffff608186901c16420362ffffff60b087901c16612d4e82846132cb565b612d5891906132e2565b9050808311612d67575f612d6b565b8083035b93505080840383811115612d7d578093505b50505092915050565b5f827fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0484118302158202612db9575f5ffd5b50910281810615159190040190565b5f6103ff60d085901c1660ff60c886901c161b80841015612dec575f915050612e12565b50612710613fff60a286901c16840204830382811115612e0d579050612e12565b829150505b9392505050565b5f608183901c6401ffffffff164203612710613fff60a286901c16840204808401604986901c66ffffffffffffff16604187901c60ff161b60b087901c62ffffff16612e6585856132cb565b612e6f91906132e2565b612e7991906131f1565b935080841115612e87578093505b6103ff60d087901c1660ff60c888901c161b925082841015612eae57829350505050611ad7565b6103ff60e287901c1660ff60da88901c161b925082841115612d7d57829350505050611ad7565b612710613fff60a285901c16830204820160d084901c6103ff1660c885901c60ff161b80821015612f07579050612e12565b506103ff60e285901c1660ff60da86901c161b80821115612f26578091505b81831115612e0d5750612e12565b5f80808080601487901c61ffff1680871015612f6b5761ffff600489901c16945061ffff602489901c1693505f9250809150612f8a565b61ffff602489901c16945061ffff603489901c16935080925061271091505b5f5f84840364e8d4a510008888030281612fa657612fa6613204565b0585810264e8d4a5100089020392508902820190505f811215612ffa576040517fd50d751200000000000000000000000000000000000000000000000000000000815262011173600482015260240161008e565b64e8d4a5100090049998505050505050505050565b5f80808080601487901c61ffff16808710156130465761ffff600489901c16945061ffff602489901c1693505f9250809150612f8a565b603488901c61ffff16808810156130785761ffff60248a901c16955061ffff60448a901c169450819350809250613097565b61ffff60448a901c16955061ffff60548a901c16945080935061271092505b505f5f84840364e8d4a510008888030281612fa657612fa6613204565b803573ffffffffffffffffffffffffffffffffffffffff811681146130d7575f5ffd5b919050565b5f5f5f5f5f5f5f60c0888a0312156130f2575f5ffd5b6130fb886130b4565b96506020880135955060408801359450613117606089016130b4565b9350613125608089016130b4565b925060a088013567ffffffffffffffff811115613140575f5ffd5b8801601f81018a13613150575f5ffd5b803567ffffffffffffffff811115613166575f5ffd5b8a6020828401011115613177575f5ffd5b602082019350809250505092959891949750929550565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52601160045260245ffd5b5f7f800000000000000000000000000000000000000000000000000000000000000082036131eb576131eb61318e565b505f0390565b80820180821115611ad757611ad761318e565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52601260045260245ffd5b5f60208284031215613241575f5ffd5b5051919050565b73ffffffffffffffffffffffffffffffffffffffff8516815283602082015260606040820152816060820152818360808301375f818301608090810191909152601f9092017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe01601019392505050565b81810381811115611ad757611ad761318e565b8082028115828204841417611ad757611ad761318e565b5f826132f0576132f0613204565b500490565b8082025f82127f80000000000000000000000000000000000000000000000000000000000000008414161561332c5761332c61318e565b8181058314821517611ad757611ad761318e565b5f8261334e5761334e613204565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff83147f8000000000000000000000000000000000000000000000000000000000000000831416156133a2576133a261318e565b50059056fea2646970667358221220a485a525474d247066749e3c247384fe17bc6d01b812c6e1a23dca9aea71e51764736f6c634300081d0033
Loading...
Loading
Loading...
Loading
Net Worth in USD
$0.00
Net Worth in ETH
0
Multichain Portfolio | 34 Chains
| Chain | Token | Portfolio % | Price | Amount | Value |
|---|
Loading...
Loading
Loading...
Loading
Loading...
Loading
[ Download: CSV Export ]
A contract address hosts a smart contract, which is a set of code stored on the blockchain that runs when predetermined conditions are met. Learn more about addresses in our Knowledge Base.