wormhole-lending-examples/example-2/evm/src/contracts/lendingHub/HubInterestUtilities.sol

171 lines
7.4 KiB
Solidity

// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.0;
import "../HubSpokeStructs.sol";
import "./HubGetters.sol";
import "./HubSetters.sol";
import "forge-std/console.sol";
contract HubInterestUtilities is HubSpokeStructs, HubGetters, HubSetters {
/*
*
* The following three functions describe the Interest Rate Model of the whole protocol!
* TODO: IMPORTANT! Substitute this function out for whatever desired interest rate model you wish to have
*
*/
/**
* @notice Assets accrue interest over time, so at any given point in time the value of an asset is (amount of asset on day 1) * (the amount of interest that has accrued).
* This function updates both the deposit and borrow interest accrual indices of the asset.
*
* @param assetAddress - The asset to update the interest accrual indices of
*/
function updateAccrualIndices(address assetAddress) internal {
setInterestAccrualIndices(assetAddress, getCurrentAccrualIndices(assetAddress));
setLastActivityBlockTimestamp(assetAddress, block.timestamp);
}
function getCurrentAccrualIndices(address assetAddress) internal view returns (AccrualIndices memory) {
uint256 lastActivityBlockTimestamp = getLastActivityBlockTimestamp(assetAddress);
uint256 secondsElapsed = block.timestamp - lastActivityBlockTimestamp;
uint256 deposited = getTotalAssetsDeposited(assetAddress);
AccrualIndices memory accrualIndices = getInterestAccrualIndices(assetAddress);
if ((secondsElapsed != 0) && (deposited != 0)) {
uint256 borrowed = getTotalAssetsBorrowed(assetAddress);
PiecewiseInterestRateModel memory interestRateModel = getInterestRateModel(assetAddress);
uint256 interestFactor = computeSourceInterestFactor(secondsElapsed, deposited, borrowed, interestRateModel);
AssetInfo memory assetInfo = getAssetInfo(assetAddress);
uint256 reserveFactor = assetInfo.interestRateModel.reserveFactor;
uint256 reservePrecision = assetInfo.interestRateModel.reservePrecision;
accrualIndices.borrowed += interestFactor;
accrualIndices.deposited +=
(interestFactor * (reservePrecision - reserveFactor) * borrowed) / reservePrecision / deposited;
}
return accrualIndices;
}
function computeSourceInterestFactor(
uint256 secondsElapsed,
uint256 deposited,
uint256 borrowed,
PiecewiseInterestRateModel memory interestRateModel
) internal view returns (uint256) {
if (deposited == 0) {
return 0;
}
uint256[] memory kinks = interestRateModel.kinks;
uint256[] memory rates = interestRateModel.rates;
uint i = 0;
uint256 interestRate = 0;
while (borrowed * interestRateModel.ratePrecision > deposited * kinks[i]) {
interestRate = rates[i];
i += 1;
if (i == rates.length) {
return rates[i-1];
}
}
// if zero borrows and nonzero deposits, then set interest rate for period to the rate intercept i.e. first kink; ow linearly interpolate between kinks
if (i==0) {
interestRate = rates[0];
}
else {
interestRate += (rates[i] - rates[i-1]) * ((borrowed - kinks[i-1] * deposited) / deposited) / (kinks[i] - kinks[i-1]);
}
return (getInterestAccrualIndexPrecision() * secondsElapsed * interestRate / interestRateModel.ratePrecision) / 365 / 24 / 60 / 60;
}
/*
*
* End Interest Rate Model
*
*/
/**
* @notice Assets accrue interest over time, so at any given point in time the value of an asset is (amount of asset on day 1) * (the amount of interest that has accrued).
*
* @param denormalizedAmount - The true amount of an asset
* @param interestAccrualIndex - The amount of interest that has accrued, multiplied by getInterestAccrualIndexPrecision().
* So, (interestAccrualIndex/interestAccrualIndexPrecision) represents the interest accrued (this is initialized to 1 at the start of the protocol)
* @return {uint256} The normalized amount of the asset
*/
function normalizeAmount(uint256 denormalizedAmount, uint256 interestAccrualIndex, Round round)
public
view
returns (uint256)
{
return divide(denormalizedAmount * getInterestAccrualIndexPrecision(), interestAccrualIndex, round);
}
/**
* @notice Similar to 'normalizeAmount', takes a normalized value (amount of an asset) and denormalizes it.
*
* @param normalizedAmount - The normalized amount of an asset
* @param interestAccrualIndex - The amount of interest that has accrued, multiplied by getInterestAccrualIndexPrecision().
* @return {uint256} The true amount of the asset
*/
function denormalizeAmount(uint256 normalizedAmount, uint256 interestAccrualIndex, Round round)
public
view
returns (uint256)
{
return divide(normalizedAmount * interestAccrualIndex, getInterestAccrualIndexPrecision(), round);
}
/**
* @notice Get a user's account balance in an asset
*
* @param vaultOwner - the address of the user
* @param assetAddress - the address of the asset
* @return a struct with 'deposited' field and 'borrowed' field for the amount deposited and borrowed of the asset
* multiplied by 10^decimal for that asset. Values are denormalized.
*/
function getUserBalance(address vaultOwner, address assetAddress) public view returns (VaultAmount memory) {
VaultAmount memory normalized = getVaultAmounts(vaultOwner, assetAddress);
AccrualIndices memory interestAccrualIndex = getCurrentAccrualIndices(assetAddress);
return VaultAmount({
deposited: denormalizeAmount(normalized.deposited, interestAccrualIndex.deposited, Round.DOWN),
borrowed: denormalizeAmount(normalized.borrowed, interestAccrualIndex.borrowed, Round.UP)
});
}
/**
* @notice Get the protocol's global balance in an asset
*
* @param assetAddress - the address of the asset
* @return a struct with 'deposited' field and 'borrowed' field for the amount deposited and borrowed of the asset
* multiplied by 10^decimal for that asset. Values are denormalized.
*/
function getGlobalBalance(address assetAddress) public view returns (VaultAmount memory) {
VaultAmount memory normalized = getGlobalAmounts(assetAddress);
AccrualIndices memory interestAccrualIndex = getCurrentAccrualIndices(assetAddress);
return VaultAmount({
deposited: denormalizeAmount(normalized.deposited, interestAccrualIndex.deposited, Round.DOWN),
borrowed: denormalizeAmount(normalized.borrowed, interestAccrualIndex.borrowed, Round.UP)
});
}
/**
* @notice Divide helper function, for rounding
*
* @param dividend - the dividend
* @param divisor - the divisor
* @param round - Whether or not to round up (Round.UP) or round down (Round.DOWN)
* @return dividend/divisor, rounded appropriately
*/
function divide(uint256 dividend, uint256 divisor, Round round) internal pure returns (uint256) {
uint256 modulo = dividend % divisor;
uint256 quotient = dividend / divisor;
if (modulo == 0 || round == Round.DOWN) {
return quotient;
}
return quotient + 1;
}
}