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