fluid-contracts-public/contracts/periphery/resolvers/liquidity/main.sol
2024-07-11 13:05:09 +00:00

544 lines
24 KiB
Solidity

// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.21;
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import { LiquidityCalcs } from "../../../libraries/liquidityCalcs.sol";
import { BigMathMinified } from "../../../libraries/bigMathMinified.sol";
import { LiquiditySlotsLink } from "../../../libraries/liquiditySlotsLink.sol";
import { IFluidLiquidity } from "../../../liquidity/interfaces/iLiquidity.sol";
import { IFluidLiquidityResolver } from "./iLiquidityResolver.sol";
import { Structs } from "./structs.sol";
import { Variables } from "./variables.sol";
interface TokenInterface {
function balanceOf(address) external view returns (uint);
}
/// @notice Fluid Liquidity resolver
/// Implements various view-only methods to give easy access to Liquidity data.
contract FluidLiquidityResolver is IFluidLiquidityResolver, Variables, Structs {
/// @dev address that is mapped to the chain native token
address internal constant _NATIVE_TOKEN_ADDRESS = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE;
/// @notice thrown if an input param address is zero
error FluidLiquidityResolver__AddressZero();
constructor(IFluidLiquidity liquidity_) Variables(liquidity_) {
if (address(liquidity_) == address(0)) {
revert FluidLiquidityResolver__AddressZero();
}
}
/// @inheritdoc IFluidLiquidityResolver
function getRevenueCollector() public view returns (address) {
return address(uint160(LIQUIDITY.readFromStorage(bytes32(0))));
}
/// @inheritdoc IFluidLiquidityResolver
function getRevenue(address token_) public view returns (uint256 revenueAmount_) {
uint256 liquidityTokenBalance_ = token_ == _NATIVE_TOKEN_ADDRESS
? address(LIQUIDITY).balance
: IERC20(token_).balanceOf(address(LIQUIDITY));
uint256 exchangePricesAndConfig_ = getExchangePricesAndConfig(token_);
if (exchangePricesAndConfig_ == 0) {
return 0;
}
return LiquidityCalcs.calcRevenue(getTotalAmounts(token_), exchangePricesAndConfig_, liquidityTokenBalance_);
}
/// @inheritdoc IFluidLiquidityResolver
function getStatus() public view returns (uint256) {
return LIQUIDITY.readFromStorage(bytes32(LiquiditySlotsLink.LIQUIDITY_STATUS_SLOT));
}
/// @inheritdoc IFluidLiquidityResolver
function isAuth(address auth_) public view returns (uint256) {
return
LIQUIDITY.readFromStorage(
LiquiditySlotsLink.calculateMappingStorageSlot(LiquiditySlotsLink.LIQUIDITY_AUTHS_MAPPING_SLOT, auth_)
);
}
/// @inheritdoc IFluidLiquidityResolver
function isGuardian(address guardian_) public view returns (uint256) {
return
LIQUIDITY.readFromStorage(
LiquiditySlotsLink.calculateMappingStorageSlot(
LiquiditySlotsLink.LIQUIDITY_GUARDIANS_MAPPING_SLOT,
guardian_
)
);
}
/// @inheritdoc IFluidLiquidityResolver
function getUserClass(address user_) public view returns (uint256) {
return
LIQUIDITY.readFromStorage(
LiquiditySlotsLink.calculateMappingStorageSlot(
LiquiditySlotsLink.LIQUIDITY_USER_CLASS_MAPPING_SLOT,
user_
)
);
}
/// @inheritdoc IFluidLiquidityResolver
function getExchangePricesAndConfig(address token_) public view returns (uint256) {
return
LIQUIDITY.readFromStorage(
LiquiditySlotsLink.calculateMappingStorageSlot(
LiquiditySlotsLink.LIQUIDITY_EXCHANGE_PRICES_MAPPING_SLOT,
token_
)
);
}
/// @inheritdoc IFluidLiquidityResolver
function getRateConfig(address token_) public view returns (uint256) {
return
LIQUIDITY.readFromStorage(
LiquiditySlotsLink.calculateMappingStorageSlot(
LiquiditySlotsLink.LIQUIDITY_RATE_DATA_MAPPING_SLOT,
token_
)
);
}
/// @inheritdoc IFluidLiquidityResolver
function getTotalAmounts(address token_) public view returns (uint256) {
return
LIQUIDITY.readFromStorage(
LiquiditySlotsLink.calculateMappingStorageSlot(
LiquiditySlotsLink.LIQUIDITY_TOTAL_AMOUNTS_MAPPING_SLOT,
token_
)
);
}
/// @inheritdoc IFluidLiquidityResolver
function getConfigs2(address token_) public view returns (uint256) {
return
LIQUIDITY.readFromStorage(
LiquiditySlotsLink.calculateMappingStorageSlot(
LiquiditySlotsLink.LIQUIDITY_CONFIGS2_MAPPING_SLOT,
token_
)
);
}
/// @inheritdoc IFluidLiquidityResolver
function getUserSupply(address user_, address token_) public view returns (uint256) {
return
LIQUIDITY.readFromStorage(
LiquiditySlotsLink.calculateDoubleMappingStorageSlot(
LiquiditySlotsLink.LIQUIDITY_USER_SUPPLY_DOUBLE_MAPPING_SLOT,
user_,
token_
)
);
}
/// @inheritdoc IFluidLiquidityResolver
function getUserBorrow(address user_, address token_) public view returns (uint256) {
return
LIQUIDITY.readFromStorage(
LiquiditySlotsLink.calculateDoubleMappingStorageSlot(
LiquiditySlotsLink.LIQUIDITY_USER_BORROW_DOUBLE_MAPPING_SLOT,
user_,
token_
)
);
}
/// @inheritdoc IFluidLiquidityResolver
function listedTokens() public view returns (address[] memory listedTokens_) {
uint256 length_ = LIQUIDITY.readFromStorage(bytes32(LiquiditySlotsLink.LIQUIDITY_LISTED_TOKENS_ARRAY_SLOT));
listedTokens_ = new address[](length_);
uint256 startingSlotForArrayElements_ = uint256(
keccak256(abi.encode(LiquiditySlotsLink.LIQUIDITY_LISTED_TOKENS_ARRAY_SLOT))
);
for (uint256 i; i < length_; i++) {
listedTokens_[i] = address(uint160(LIQUIDITY.readFromStorage(bytes32(startingSlotForArrayElements_ + i))));
}
}
/// @inheritdoc IFluidLiquidityResolver
function getTokenRateData(address token_) public view returns (RateData memory rateData_) {
uint256 rateConfig_ = getRateConfig(token_);
rateData_.version = rateConfig_ & 0xF;
if (rateData_.version == 1) {
rateData_.rateDataV1.rateAtUtilizationZero =
(rateConfig_ >> LiquiditySlotsLink.BITS_RATE_DATA_V1_RATE_AT_UTILIZATION_ZERO) &
X16;
rateData_.rateDataV1.kink = (rateConfig_ >> LiquiditySlotsLink.BITS_RATE_DATA_V1_UTILIZATION_AT_KINK) & X16;
rateData_.rateDataV1.rateAtUtilizationKink =
(rateConfig_ >> LiquiditySlotsLink.BITS_RATE_DATA_V1_RATE_AT_UTILIZATION_KINK) &
X16;
rateData_.rateDataV1.rateAtUtilizationMax =
(rateConfig_ >> LiquiditySlotsLink.BITS_RATE_DATA_V1_RATE_AT_UTILIZATION_MAX) &
X16;
} else if (rateData_.version == 2) {
rateData_.rateDataV2.rateAtUtilizationZero =
(rateConfig_ >> LiquiditySlotsLink.BITS_RATE_DATA_V2_RATE_AT_UTILIZATION_ZERO) &
X16;
rateData_.rateDataV2.kink1 =
(rateConfig_ >> LiquiditySlotsLink.BITS_RATE_DATA_V2_UTILIZATION_AT_KINK1) &
X16;
rateData_.rateDataV2.rateAtUtilizationKink1 =
(rateConfig_ >> LiquiditySlotsLink.BITS_RATE_DATA_V2_RATE_AT_UTILIZATION_KINK1) &
X16;
rateData_.rateDataV2.kink2 =
(rateConfig_ >> LiquiditySlotsLink.BITS_RATE_DATA_V2_UTILIZATION_AT_KINK2) &
X16;
rateData_.rateDataV2.rateAtUtilizationKink2 =
(rateConfig_ >> LiquiditySlotsLink.BITS_RATE_DATA_V2_RATE_AT_UTILIZATION_KINK2) &
X16;
rateData_.rateDataV2.rateAtUtilizationMax =
(rateConfig_ >> LiquiditySlotsLink.BITS_RATE_DATA_V2_RATE_AT_UTILIZATION_MAX) &
X16;
} else if (rateData_.version > 0) {
// when version is 0 -> token not configured yet. do not revert, just return 0 for all values
revert("not-valid-rate-version");
}
}
/// @inheritdoc IFluidLiquidityResolver
function getTokensRateData(address[] calldata tokens_) public view returns (RateData[] memory rateDatas_) {
uint256 length_ = tokens_.length;
rateDatas_ = new RateData[](length_);
for (uint256 i; i < length_; i++) {
rateDatas_[i] = getTokenRateData(tokens_[i]);
}
}
/// @inheritdoc IFluidLiquidityResolver
function getOverallTokenData(
address token_
) public view returns (Structs.OverallTokenData memory overallTokenData_) {
overallTokenData_.rateData = getTokenRateData(token_);
uint256 exchangePriceAndConfig_ = getExchangePricesAndConfig(token_);
if (exchangePriceAndConfig_ > 0) {
uint256 totalAmounts_ = getTotalAmounts(token_);
(overallTokenData_.supplyExchangePrice, overallTokenData_.borrowExchangePrice) = LiquidityCalcs
.calcExchangePrices(exchangePriceAndConfig_);
overallTokenData_.borrowRate = exchangePriceAndConfig_ & X16;
overallTokenData_.fee = (exchangePriceAndConfig_ >> LiquiditySlotsLink.BITS_EXCHANGE_PRICES_FEE) & X14;
overallTokenData_.lastStoredUtilization =
(exchangePriceAndConfig_ >> LiquiditySlotsLink.BITS_EXCHANGE_PRICES_UTILIZATION) &
X14;
overallTokenData_.storageUpdateThreshold =
(exchangePriceAndConfig_ >> LiquiditySlotsLink.BITS_EXCHANGE_PRICES_UPDATE_THRESHOLD) &
X14;
overallTokenData_.lastUpdateTimestamp =
(exchangePriceAndConfig_ >> LiquiditySlotsLink.BITS_EXCHANGE_PRICES_LAST_TIMESTAMP) &
X33;
overallTokenData_.maxUtilization = FOUR_DECIMALS;
if ((exchangePriceAndConfig_ >> LiquiditySlotsLink.BITS_EXCHANGE_PRICES_USES_CONFIGS2) & 1 == 1) {
overallTokenData_.maxUtilization = getConfigs2(token_) & X14;
}
// Extract supply & borrow amounts
uint256 temp_ = totalAmounts_ & X64;
overallTokenData_.supplyRawInterest = (temp_ >> DEFAULT_EXPONENT_SIZE) << (temp_ & DEFAULT_EXPONENT_MASK);
temp_ = (totalAmounts_ >> LiquiditySlotsLink.BITS_TOTAL_AMOUNTS_SUPPLY_INTEREST_FREE) & X64;
overallTokenData_.supplyInterestFree = (temp_ >> DEFAULT_EXPONENT_SIZE) << (temp_ & DEFAULT_EXPONENT_MASK);
temp_ = (totalAmounts_ >> LiquiditySlotsLink.BITS_TOTAL_AMOUNTS_BORROW_WITH_INTEREST) & X64;
overallTokenData_.borrowRawInterest = (temp_ >> DEFAULT_EXPONENT_SIZE) << (temp_ & DEFAULT_EXPONENT_MASK);
// no & mask needed for borrow interest free as it occupies the last bits in the storage slot
temp_ = (totalAmounts_ >> LiquiditySlotsLink.BITS_TOTAL_AMOUNTS_BORROW_INTEREST_FREE);
overallTokenData_.borrowInterestFree = (temp_ >> DEFAULT_EXPONENT_SIZE) << (temp_ & DEFAULT_EXPONENT_MASK);
uint256 supplyWithInterest_;
uint256 borrowWithInterest_;
if (overallTokenData_.supplyRawInterest > 0) {
// use old exchange prices for supply rate to be at same level as borrow rate from storage.
// Note the rate here can be a tiny bit with higher precision because we use borrowWithInterest_ / supplyWithInterest_
// which has higher precision than the utilization used from storage in LiquidityCalcs
supplyWithInterest_ =
(overallTokenData_.supplyRawInterest *
((exchangePriceAndConfig_ >> LiquiditySlotsLink.BITS_EXCHANGE_PRICES_SUPPLY_EXCHANGE_PRICE) &
X64)) /
EXCHANGE_PRICES_PRECISION; // normalized from raw
borrowWithInterest_ =
(overallTokenData_.borrowRawInterest *
((exchangePriceAndConfig_ >> LiquiditySlotsLink.BITS_EXCHANGE_PRICES_BORROW_EXCHANGE_PRICE) &
X64)) /
EXCHANGE_PRICES_PRECISION; // normalized from raw
overallTokenData_.supplyRate = supplyWithInterest_ == 0
? 0
: (overallTokenData_.borrowRate * (FOUR_DECIMALS - overallTokenData_.fee) * borrowWithInterest_) /
(supplyWithInterest_ * FOUR_DECIMALS);
}
supplyWithInterest_ =
(overallTokenData_.supplyRawInterest * overallTokenData_.supplyExchangePrice) /
EXCHANGE_PRICES_PRECISION; // normalized from raw
overallTokenData_.totalSupply = supplyWithInterest_ + overallTokenData_.supplyInterestFree;
borrowWithInterest_ =
(overallTokenData_.borrowRawInterest * overallTokenData_.borrowExchangePrice) /
EXCHANGE_PRICES_PRECISION; // normalized from raw
overallTokenData_.totalBorrow = borrowWithInterest_ + overallTokenData_.borrowInterestFree;
overallTokenData_.revenue = getRevenue(token_);
}
}
/// @inheritdoc IFluidLiquidityResolver
function getOverallTokensData(
address[] memory tokens_
) public view returns (Structs.OverallTokenData[] memory overallTokensData_) {
uint256 length_ = tokens_.length;
overallTokensData_ = new Structs.OverallTokenData[](length_);
for (uint256 i; i < length_; i++) {
overallTokensData_[i] = getOverallTokenData(tokens_[i]);
}
}
/// @inheritdoc IFluidLiquidityResolver
function getAllOverallTokensData() public view returns (Structs.OverallTokenData[] memory overallTokensData_) {
return getOverallTokensData(listedTokens());
}
/// @inheritdoc IFluidLiquidityResolver
function getUserSupplyData(
address user_,
address token_
)
public
view
returns (Structs.UserSupplyData memory userSupplyData_, Structs.OverallTokenData memory overallTokenData_)
{
overallTokenData_ = getOverallTokenData(token_);
uint256 userSupply_ = getUserSupply(user_, token_);
if (userSupply_ > 0) {
// if userSupply_ == 0 -> user not configured yet for token at Liquidity
userSupplyData_.modeWithInterest = userSupply_ & 1 == 1;
userSupplyData_.supply = BigMathMinified.fromBigNumber(
(userSupply_ >> LiquiditySlotsLink.BITS_USER_SUPPLY_AMOUNT) & X64,
DEFAULT_EXPONENT_SIZE,
DEFAULT_EXPONENT_MASK
);
// get updated expanded withdrawal limit
userSupplyData_.withdrawalLimit = LiquidityCalcs.calcWithdrawalLimitBeforeOperate(
userSupply_,
userSupplyData_.supply
);
userSupplyData_.lastUpdateTimestamp =
(userSupply_ >> LiquiditySlotsLink.BITS_USER_SUPPLY_LAST_UPDATE_TIMESTAMP) &
X33;
userSupplyData_.expandPercent = (userSupply_ >> LiquiditySlotsLink.BITS_USER_SUPPLY_EXPAND_PERCENT) & X14;
userSupplyData_.expandDuration = (userSupply_ >> LiquiditySlotsLink.BITS_USER_SUPPLY_EXPAND_DURATION) & X24;
userSupplyData_.baseWithdrawalLimit = BigMathMinified.fromBigNumber(
(userSupply_ >> LiquiditySlotsLink.BITS_USER_SUPPLY_BASE_WITHDRAWAL_LIMIT) & X18,
DEFAULT_EXPONENT_SIZE,
DEFAULT_EXPONENT_MASK
);
if (userSupplyData_.modeWithInterest) {
// convert raw amounts to normal for withInterest mode
userSupplyData_.supply =
(userSupplyData_.supply * overallTokenData_.supplyExchangePrice) /
EXCHANGE_PRICES_PRECISION;
userSupplyData_.withdrawalLimit =
(userSupplyData_.withdrawalLimit * overallTokenData_.supplyExchangePrice) /
EXCHANGE_PRICES_PRECISION;
userSupplyData_.baseWithdrawalLimit =
(userSupplyData_.baseWithdrawalLimit * overallTokenData_.supplyExchangePrice) /
EXCHANGE_PRICES_PRECISION;
}
userSupplyData_.withdrawableUntilLimit = userSupplyData_.supply > userSupplyData_.withdrawalLimit
? userSupplyData_.supply - userSupplyData_.withdrawalLimit
: 0;
uint balanceOf_ = token_ == _NATIVE_TOKEN_ADDRESS
? address(LIQUIDITY).balance
: TokenInterface(token_).balanceOf(address(LIQUIDITY));
userSupplyData_.withdrawable = balanceOf_ > userSupplyData_.withdrawableUntilLimit
? userSupplyData_.withdrawableUntilLimit
: balanceOf_;
}
}
/// @inheritdoc IFluidLiquidityResolver
function getUserMultipleSupplyData(
address user_,
address[] calldata tokens_
)
public
view
returns (
Structs.UserSupplyData[] memory userSuppliesData_,
Structs.OverallTokenData[] memory overallTokensData_
)
{
uint256 length_ = tokens_.length;
userSuppliesData_ = new Structs.UserSupplyData[](length_);
overallTokensData_ = new Structs.OverallTokenData[](length_);
for (uint256 i; i < length_; i++) {
(userSuppliesData_[i], overallTokensData_[i]) = getUserSupplyData(user_, tokens_[i]);
}
}
/// @inheritdoc IFluidLiquidityResolver
function getUserBorrowData(
address user_,
address token_
)
public
view
returns (Structs.UserBorrowData memory userBorrowData_, Structs.OverallTokenData memory overallTokenData_)
{
overallTokenData_ = getOverallTokenData(token_);
uint256 userBorrow_ = getUserBorrow(user_, token_);
if (userBorrow_ > 0) {
// if userBorrow_ == 0 -> user not configured yet for token at Liquidity
userBorrowData_.modeWithInterest = userBorrow_ & 1 == 1;
userBorrowData_.borrow = BigMathMinified.fromBigNumber(
(userBorrow_ >> LiquiditySlotsLink.BITS_USER_BORROW_AMOUNT) & X64,
DEFAULT_EXPONENT_SIZE,
DEFAULT_EXPONENT_MASK
);
// get updated expanded borrow limit
userBorrowData_.borrowLimit = LiquidityCalcs.calcBorrowLimitBeforeOperate(
userBorrow_,
userBorrowData_.borrow
);
userBorrowData_.lastUpdateTimestamp =
(userBorrow_ >> LiquiditySlotsLink.BITS_USER_BORROW_LAST_UPDATE_TIMESTAMP) &
X33;
userBorrowData_.expandPercent = (userBorrow_ >> LiquiditySlotsLink.BITS_USER_BORROW_EXPAND_PERCENT) & X14;
userBorrowData_.expandDuration = (userBorrow_ >> LiquiditySlotsLink.BITS_USER_BORROW_EXPAND_DURATION) & X24;
userBorrowData_.baseBorrowLimit = BigMathMinified.fromBigNumber(
(userBorrow_ >> LiquiditySlotsLink.BITS_USER_BORROW_BASE_BORROW_LIMIT) & X18,
DEFAULT_EXPONENT_SIZE,
DEFAULT_EXPONENT_MASK
);
userBorrowData_.maxBorrowLimit = BigMathMinified.fromBigNumber(
(userBorrow_ >> LiquiditySlotsLink.BITS_USER_BORROW_MAX_BORROW_LIMIT) & X18,
DEFAULT_EXPONENT_SIZE,
DEFAULT_EXPONENT_MASK
);
if (userBorrowData_.modeWithInterest) {
// convert raw amounts to normal for withInterest mode
userBorrowData_.borrow =
(userBorrowData_.borrow * overallTokenData_.borrowExchangePrice) /
EXCHANGE_PRICES_PRECISION;
userBorrowData_.borrowLimit =
(userBorrowData_.borrowLimit * overallTokenData_.borrowExchangePrice) /
EXCHANGE_PRICES_PRECISION;
userBorrowData_.baseBorrowLimit =
(userBorrowData_.baseBorrowLimit * overallTokenData_.borrowExchangePrice) /
EXCHANGE_PRICES_PRECISION;
userBorrowData_.maxBorrowLimit =
(userBorrowData_.maxBorrowLimit * overallTokenData_.borrowExchangePrice) /
EXCHANGE_PRICES_PRECISION;
}
userBorrowData_.borrowLimitUtilization =
(overallTokenData_.maxUtilization * overallTokenData_.totalSupply) /
1e4;
// uncollected revenue is counting towards available balanceOf.
// because of this "borrowable" would be showing an amount that can go above 100% utilization, causing a revert.
// need to take into consideration the borrowable amount until the max utilization limit, which depends on the total
// borrow amount (not user specific)
uint borrowableUntilUtilizationLimit_ = userBorrowData_.borrowLimitUtilization >
overallTokenData_.totalBorrow
? userBorrowData_.borrowLimitUtilization - overallTokenData_.totalBorrow
: 0;
uint borrowableUntilBorrowLimit_ = userBorrowData_.borrowLimit > userBorrowData_.borrow
? userBorrowData_.borrowLimit - userBorrowData_.borrow
: 0;
userBorrowData_.borrowableUntilLimit = borrowableUntilBorrowLimit_ > borrowableUntilUtilizationLimit_
? borrowableUntilUtilizationLimit_
: borrowableUntilBorrowLimit_;
// if available balance at Liquidity is less than the borrowableUntilLimit amount, then the balance is
// the limiting borrowable amount.
uint balanceOf_ = token_ == _NATIVE_TOKEN_ADDRESS
? address(LIQUIDITY).balance
: TokenInterface(token_).balanceOf(address(LIQUIDITY));
userBorrowData_.borrowable = balanceOf_ > userBorrowData_.borrowableUntilLimit
? userBorrowData_.borrowableUntilLimit
: balanceOf_;
}
}
/// @inheritdoc IFluidLiquidityResolver
function getUserMultipleBorrowData(
address user_,
address[] calldata tokens_
)
public
view
returns (
Structs.UserBorrowData[] memory userBorrowingsData_,
Structs.OverallTokenData[] memory overallTokensData_
)
{
uint256 length_ = tokens_.length;
userBorrowingsData_ = new UserBorrowData[](length_);
overallTokensData_ = new Structs.OverallTokenData[](length_);
for (uint256 i; i < length_; i++) {
(userBorrowingsData_[i], overallTokensData_[i]) = getUserBorrowData(user_, tokens_[i]);
}
}
/// @inheritdoc IFluidLiquidityResolver
function getUserMultipleBorrowSupplyData(
address user_,
address[] calldata supplyTokens_,
address[] calldata borrowTokens_
)
public
view
returns (
Structs.UserSupplyData[] memory userSuppliesData_,
Structs.OverallTokenData[] memory overallSupplyTokensData_,
Structs.UserBorrowData[] memory userBorrowingsData_,
Structs.OverallTokenData[] memory overallBorrowTokensData_
)
{
uint256 length_ = supplyTokens_.length;
userSuppliesData_ = new Structs.UserSupplyData[](length_);
overallSupplyTokensData_ = new Structs.OverallTokenData[](length_);
for (uint256 i; i < length_; i++) {
(userSuppliesData_[i], overallSupplyTokensData_[i]) = getUserSupplyData(user_, supplyTokens_[i]);
}
length_ = borrowTokens_.length;
userBorrowingsData_ = new UserBorrowData[](length_);
overallBorrowTokensData_ = new Structs.OverallTokenData[](length_);
for (uint256 i; i < length_; i++) {
(userBorrowingsData_[i], overallBorrowTokensData_[i]) = getUserBorrowData(user_, borrowTokens_[i]);
}
}
}