GM Lending Pool
The core lending contract stores user balances, user debt, price oracles, asset data, and fee data; calculates interest and health factors; and is the executor of all deposits, borrows, repays, withdrawals, and liquidations. The pool contract inherits from Solmate's Auth contract and OpenZeppelin's ReentrancyGuard. The protocol and asset mechanisms are set according to the following functions:
constructor
At deployment, the constructor sets the deployer-provided _owner
as the owner and _authority
as the Authority (both hereafter referred to as Admin). It then stores the pool name
.
Admin Functions
setGloopStakersWallet
Sets or updates the gloopStakersWallet
address, which will receive a portion of the platform fees to be distributed to future Gloop stakers. This can only be changed by the Admin. Upon success, the call emits a GloopStakersWalletUpdated
event with the caller's address and the new wallet address as parameters.
Parameters:
_newWallet
address
The address of the new Gloop Staker rewards wallet
setAddressStore
Sets or updates the Address Store contract. The Address Store can only be changed by the Admin.
Parameters:
newAddressStore
AddressStore
The new AddressStore instance
setOracle
Sets or updates the Pool's Oracle contract. The Oracle can only be updated by the Admin. Upon success, the call emits an OracleUpdated
event with the caller's address and the new oracle contract as parameters.
Parameters:
newOracle
PriceOracle
The Lending Oracle contract instance
setInterestRateModel
Before changing the IRM, interest is accrued using the existing model (if there is one). This helps avoid a stepwise jump in rates of interest accrual, which could result in wonky amounts of yield to users. This can be bypassed in emergency/unforeseen situations like an IRM that has been set but has a bug that causes interest accrual to revert, thereby leaving the Pool in a forever stuck state.
Parameters:
asset
ERC20
The asset to assign the model to
newInterestRateModel
InterestRateModel
The interest model that the asset will use.
accrue
boolean
If true and there is an existing model, accrue interest. If false, do not accrue interest.
configureAsset
The configuration struct contains the lending/borrowing factors and caps for each asset as shown here:
Once the mappings are successfully updated, an AssetConfigured
or AssetConfigurationUpdated
event is emitted with caller's address, asset, vault contract, and configuration.
Parameters:
asset
ERC20
The asset to be configured
vault
ERC4626
The tokenized standard type of the asset's vault
configuration
Configuration
A struct that contains an asset's lend and borrow factors
updateVault
boolean
Updates the asset's vault if true
setGmTokens
Stores an array of GM Token addresses in the gmTokensAddresses
state array. The array can only be updated by the Admin.
Parameters:
newGmTokensAddress
address[] memory
An array of GM tokens
addNewGmToken
Adds a GMX Market (GM) Token to the gmTokensAddresses
state array. Tokens can only be added by the Admin. Upon success, the call emits a NewGMToken
event with the caller's address and the new GM Token address as parameters.
Parameters:
newGmToken
address
The address of the GMX Market Token
setGMPointsContract
Parameters:
newPointsAddress
address
The address of the GM Points contract
updateLiquidationBonus
Sets or updates the liquidationBonus
, which can only be done by the Admin. The bonus cannot be more than the MAX_LIQUIDATION_BONUS
. This is hardcoded to 10%.
Parameters:
_newLiquidationBonus
uint256
The bonus multiplier for liquidations
liquidateBadDebt
Partially or fully repays a borrow position with "bad debt," meaning little or no collateral and the position is at risk of or is fully insolvent. Only the Admin can call this function. Admin does not receive any collateral for liquidating bad debt. Function is only used to avoid bad debt accrual.
Parameters:
borrower
address
The user with poor health factor
repayAmount
uint256
Amount repaid by owner (in USDC)
setGloopStakersYieldFactor
Sets or updates the gloopStakersYieldFactor
. Can only be called by the Admin. The factor cannot be greater than 45%. The factor represents the portion of the platform fees to be distributed to the gloopStakersWallet
. Pool interest is accrued before the factor change/setting. Upon success, the call emits a GloopStakersYieldUpdated
event with the caller's address and the new factor as parameters.
Parameters:
newGSYFactor
uint256
The new Gloop Staker Yield Factor (percentage of lending platform fees)
updateReservesFactor
Sets or updates the reserveFactor
. Can only be called by the Admin. The factor represents the portion of the platform fees that will go to Protocol Reserves. Pool interest is accrued before the factor change/setting.
Parameters:
newReserveFactor
uint256
The new Reserve Factor (percentage of lending platform fees)
withdrawReserves
Transfers an amount
of totalReserves
from the USDC vault to the Admin wallet. The token amount
is subtracted from totalReserves
, then transferred out of the asset's vault to the pool contract, which immediately transfers them to the Admin wallet.
Parameters:
asset
ERC20
The asset to withdraw
amount
uint256
The amount of tokens to withdraw
withdrawStakersYield
Transfers an amount
of gloopStakersYield
from the USDC vault to the Admin wallet. The token amount
is subtracted from gloopStakersYield
, then transferred out of the asset's vault to the pool contract, which immediately transfers them to the gloopStakersWallet
.
Parameters:
asset
ERC20
The asset to withdraw
amount
uint256
The amount of tokens to withdraw
Core External/Public Functions
deposit
This function is the main and first point of entry to the protocol. At present, the only assets allowed for depositing are GM tokens and USDC. GM tokens can only be used as collateral. USDC cannot be used as collateral, but is the only token that can be used for lending. This call will revert if the amount
is zero, if the asset is not a valid token, or if the new deposit puts the Pool's supply of that asset over its supplyCap
.
The token amount
is then transferred to the pool contract, which immediately transfers them to the asset's ERC-4626 vault. If the asset is a GM token, then it is automatically enabled as collateral for the user. If the asset is USDC, it is not. (Currently, USDC is solely used for liquidity provision and borrowing.) Upon successful transfer, the Deposit
event is emitted, including msg.sender
's address, the asset
, and the amount
.
Parameters:
asset
ERC20
The asset to deposit
amount
uint256
The amount of tokens to deposit
withdraw
Transfers an amount
of tokens to the caller. This function is essentially a public wrapper of the internal function _withdraw()
. For convenience, the withdraw logic will be explained here.
_withdraw()
:
The token amount
is then transferred out of the asset's vault to the pool contract, which immediately transfers them to the user. Upon successful transfer, the Withdraw
event is emitted, including receiver
's address, the asset
, and the amount
. If _withdraw()
is called within a liquidation call via liquidateUser()
, the tokens are sent to the receiver
, which is the address that initiated the liquidation.
Parameters:
asset
ERC20
The asset to withdraw
amount
uint256
The amount of tokens to withdraw
borrow
Transfers an amount of borrowed USDC to the caller. Once a user has deposited GM tokens as enabled collateral, the user is able to borrow USDC. (More assets may be available to borrow in the future.)
The tokens are now finally transferred from the USDC vault to the pool contract, which immediately transfers them to the caller/borrower. Upon successful transfer, the Borrow
event is emitted, including msg.sender
's address, the asset
, and the amount
.
Parameters:
asset
ERC20
The asset to borrow (for now, this can only be USDC)
amount
uint256
The amount of USDC to borrow
repay
Transfers an amount of USDC from the caller to the protocol as a repayment of debt. This function is essentially a public wrapper of the internal function _repay()
. For convenience, the repay logic will be explained here.
_repay()
:
The tokens are now finally transferred from the user to the pool contract, which immediately transfers them to the USDC vault. If _repay()
is called within a liquidation call, the tokens are transferred from the liquidator to the pool contract, on behalf of the borrower. Upon successful transfer, the Repay
event is emitted, including the msg.sender
's address, the asset
, and the amount
.
Parameters:
asset
ERC20
The asset to repay (for now, this can only be USDC)
amount
uint256
The amount of USDC to repay
liquidateUser
Allows a liquidator to repay a borrower's debt and seize their collateral if their health factor has dropped below the liquidatable threshold (1e18 or 100%). Before proceeding, the function calls accrueInterest
to update any pending interest for the pool. If the borrower is healthy, the borrower has no collateral value, or the USDC vault is paused or has been recently unpaused and the block.timestamp
has not yet reached the liquidationsResumeTimestamp
, this function will revert.
The liquidator provides a repayment amount, which is used to calculate collValueToSeize
: the repayment amount in USDC multiplied by the price of the borrowed asset and the liquidation bonus factor (which gives a discount to the liquidator). If collValueToSeize
exceeds the borrower's total available collateral, the collValueToSeize
is adjusted to the maximum collateral value.
Parameters:
borrowedAsset
ERC20
The borrowed asset to be repaid
borrower
address
The address of the borrower to liquidate
repayAmount
uint256
The amount the liquidator is paying to cover some or all of the borrower's debt.
Ancillary Functions
isValidGMToken
Searches the gmTokensAddresses
array to verify if the input asset is a valid GM token and returns true if so. This check ensures that only GM tokens can be deposited as collateral in the protocol.
Parameters:
asset
ERC20
The token to validate
Returns:
isValid
bool
If true, the asset is a valid GM token.
checkNotSameBlock
enableAsset
Enables an asset as a user's collateral. The asset must be a valid (active) GM collateral token (notably, it cannot be USDC). The asset is pushed to the userCollateral
and enabledCollateral
mappings. An AssetEnabled
event is then emitted with the caller's address and asset
as event parameters.
Parameters:
asset
ERC20
The token to enable
disableAsset
Disables an asset as a user's collateral. The asset must be an enabled GM token and cannot be USDC. If the asset is the user's sole collateral, then the user must not have an active borrow against it. If the user isn't borrowing, then the collateral will be disabled.
If the user has multiple assets as active collateral, the max borrow value against the asset to be disabled is subtracted from the user's total maxBorrowableValue
. If this value is greater or equal to the user's currentBorrowValue
, then the function will proceed. If not, the function reverts with an error.
The asset is disabled by deleting it from the userCollateral
mapping and switching the enabledCollateral[msg.sender]
boolean to false. An AssetDisabled
event is emitted with the msg.sender
's address and the asset
now disabled.
Parameters:
asset
ERC20
The token to disable
_seizeCollateral
During a call to liquidateUser
, after debt repayment, _seizeCollateral
loops through the borrower's collateral-enabled assets (in the order of utilized
, which is the sequence the assets were initially deposited in by the user), determines each asset's total value, and compares it to the collValueToSeize
. If the borrower's first collateral asset's total value exceeds the collValueToSeize
, then that value of asset tokens is withdrawn to the liquidator's address, allowing the borrower to retain the remaining tokens in that collateral. In that case, the liquidation would end there.
If the first collateral asset's total value is less than the collValueToSeize
, after the first withdraw, the function proceeds to the next collateral asset of the borrower and compares that asset's total value to collValueToSeize
. Tokens from both assets will be withdrawn to the liquidator's address, and the loop moves to the next collateral asset, if necessary. The Liquidation
event is emitted at process completion.
Parameters:
borrower
address
The address to be liquidated
collValueToSeize
uint256
The total value of the borrower's collateral to be seized
getUserCollateralValue
Calculates and sums the values of all assets the borrower currently has enabled as collateral.
Parameters:
borrower
address
The address the collateral belongs to
Returns:
totalCollateralValue
uint256
The total collateral value of the user/borrower
vaultPauseInterestAccrual
Accrues or does not accrue interest based on USDC vault status. During the vault's paused window, interest
should not accrue, so when pause() is called on the USDC Vault, all pending interest is accrued before the actual pause happens. Then, once paused, no interest shall accrue . When the vault is unpaused, lastInterestAccrual
is updated to the current block.timestamp
and interest accrual can resume.
This ensures user positions do not become unhealthy when vault is paused. This call chain begins and
ends within the GMUSDCVault
contract, where the vault is actually paused or unpaused.
This function is only callable by the USDC Vault.
Parameters:
willBePaused
boolean
If true, interest shall be accrued. If false, interest is not accrued and lastInterestAccrual
gets updated to the current block.timestamp
.
calculateHealthFactor
Returns the new Health Factor after a requested borrow by the user
. If zero is input as the amount
, then the user's current Health Factor will be calculated and returned. The calculation occurs in a few steps:
First, the collateral values for each of the user's enabled assets are multiplied by each asset's
lendingFactor
, then summed together to get themaximumBorrowable
value.The
hypotheticalBorrowBalance
is determined by adding the requested borrow amount to the currentborrowBalance
, if any.The
hypotheticalBorrowValue
is then determined by multiplying thehypotheticalBorrowBalance
by the current price of the asset.If the asset to be borrowed has a
borrowFactor
, it is multiplied by themaximumBorrowable
value to obtain theactualBorrowableValue
. USDC'sborrowFactor
is 1 or1e18
.Dividing the
actualBorrowableValue
by thehypotheticalBorrowValue
results in the Health Factor. If the Health Factor is above1e18
, e.g. if theactualBorrowableValue
is greater than or equal to the futurehypotheticalBorrowValue
, then the user can borrow the amount requested.
Parameters:
asset
ERC20
The asset requested to borrow
user
address
The address to calculate the Health Factor for
amount
uint256
The amount requested to borrow
Returns:
unnamed
uint256
The new Health Factor after the requested borrow
calculateHFAfterCollChange
Calculates the Health Factor of a user after a collateral change. Set the collIncrease
boolean argument to true if the change is a deposit or enabling an asset as collateral. Set to false when withdrawing a collateral asset or disabling it as collateral. The calculation occurs in a few steps:
After the maximum borrowable value against the collateral value being adjusted is calculated (this becomesadjustedCollMaxBorrow
), the user's current borrowed value is calculated. If the currentBorrowValue
is zero, then the new Health Factor is infinity.
If the collIncrease
boolean is true (meaning an asset will be enabled as collateral), then the adjustedCollMaxBorrow
is added to the user's maxBorrowableValue()
. If collIncrease
is false (disabling or withdrawing an asset), then adjustedCollMaxBorrow
is subtracted to the user's maxBorrowableValue()
. This is the newMaxBorrowValue
.
The resulting Health Factor after the collateral change is newMaxBorrowValue
/ currentBorrowValue
.
Parameters:
asset
ERC20
The collateral asset
user
address
The address to calculate the Health Factor for
collAmount
uint256
The collateral token amount adjustment
Returns:
hypotheticalHF
uint256
The hypothetical Health Factor after the collateral adjustment
maxBorrowableValue
Calculates and returns the maximumBorrowableValue
for the caller. This function loops through the userCollateral
array and sums the products of the collateral value and the lendingFactor
for each asset. If the asset to be borrowed has a borrowFactor
, it is multiplied by the maximumBorrowableValue
to result in a lesser borrowable value. Since USDC has a borrowFactor
of 1 (or 1e18), currently this step has no effect on the answer.
The resulting maximumBorrowableValue
is the theoretical maximum value allowed to borrow in relation to the user's current collateral.
Returns:
maximumBorrowableValue
uint256
The maximum value the user can borrow
getCollateral
Retrieves the user's ERC20[]
array of collateral assets from the userCollateral
mapping.
Parameters:
user
address
The owner of the collateral
Returns:
unnamed
ERC20[]
An ERC20[]
array of collateral assets
getInternalCachedTotalBorrows
Retrieves the internal cachedTotalBorrows
variable for the asset
.
Parameters:
asset
address
The address of the asset
Returns:
cachedTotalBorrows
uint256
The internal total borrow storage variable, which excludes non-accrued interest.
Accounting Logic Functions
totalUnderlying
Parameters:
asset
ERC20
The underlying asset
Returns:
unnamed
uint256
The total amount of tokens held by and owed to the pool
availableLiquidity
Returns the total amount of tokens held in the token's vault for the Lending Pool that are immediately available. After calling balanceOf
on the asset's vault, the shares are converted to the amount of tokens held by the pool by calling convertToAssets()
on the vault.
A point of distinction is that the gloopStakersYield
and totalReserves
are not considered as the pool's availableLiquidity
for USDC, since when borrow interest is accrued, these portions never touch the pool's internal balances. They are tracked in cachedTotalBorrows
so that they are included in borrow balances and captured in repayments, but then they are sequestered for the Gloop Staker and Reserves wallets, so they are not exposed to further pool fluctuations or bear any additional interest.
Parameters:
asset
ERC20
The underlying asset
Returns:
unnamed
uint256
The total amount of tokens held in the asset's vault.
getEffectiveUSDCBalanceOf
Since gloopStakersYield
and totalReserves
are not considered as the pool's availableLiquidity
for USDC, this getter calculates these pending interest allocations and removes them to get the effectiveTotalUnderlying
tokens. Once this is determined, it is multiplied by the ratio of the user's internalBalances
to the totalInternalBalance
and returns the effective USDC balance of the user.
Parameters:
user
address
The address to get the token balance of
Returns:
unnamed
uint256
The effective token balance of the user, adjusting for pending interest allocations
balanceOf
Parameters:
asset
ERC20
The underlying asset
user
address
The address to get the token balance of
Returns:
unnamed
uint256
The token balance of the user
internalBalanceExchangeRate
Parameters:
asset
ERC20
The underlying asset
Returns:
unnamed
uint256
The exchange rate
borrowBalance
Parameters:
asset
ERC20
The borrowed asset
user
address
The address to calculate the borrow balance for
Returns:
unnamed
uint256
The borrow balance of the user
internalDebtExchangeRate
Parameters:
asset
ERC20
The underlying asset
Returns:
unnamed
uint256
The exchange rate
totalBorrows
Multiplying cachedtotalBorrows
by the interestAccumulator
gives us the total borrows with interest accrual.
Parameters:
asset
ERC20
The underlying asset
Returns:
unnamed
uint256
The total amount of borrowed tokens (including interest)
accrueInterest
Deriving the totalInterestAccumulated
from totalBorrows() - cachedTotalBorrows
, this function calculates the accrued stakersYield
and reserveAmount
and adds them to their namesake state variables. After adding totalInterestAccumulated
to cachedTotalBorrows
, the current block.timestamp
is then stored in lastInterestAccrual
for the asset.
Parameters:
asset
ERC20
The underlying asset (only USDC for now)
Last updated