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, leverage position, and liquidations. The pool contract is deployed by the Lending Pool Factory and inherits from Solmate's Auth contract. The protocol and asset mechanisms are set according to the following functions:

constructor

At deployment, the constructor sets the deployer (msg.sender of the lendingPoolFactory.deployLendingPool() call) as the owner and as the Authority (both hereafter referred to as Admin). It then stores its own pool name, which is provided by the Lending Pool Factory. Lastly, the msg.sender is stored as the gloopStakersWallet.

Admin Functions

setGloopStakersWallet

function setGloopStakersWallet(address _newWallet) external requiresAuth 

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 an GloopStakerswalletUpdated event with the caller's address and the new wallet address as parameters.

Parameters:

NameTypeDescription

_newWallet

address

The address of the new Gloop Staker rewards wallet

setAddressStore

function setAddressStore(AddressStore newAddressStore) external requiresAuth 

Sets or updates the Address Store contract. The Address Store can only be changed by the Admin. Upon success, the call emits an AddressStoreUpdated event with the caller's address and the new contract as parameters.

Parameters:

NameTypeDescription

newAddressStore

AddressStore

The new AddressStore instance

setOracle

function setOracle(PriceOracle newOracle) external requiresAuth 

Sets or updates the GM Lending 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:

NameTypeDescription

newOracle

PriceOracle

The Lending Oracle contract instance

setInterestRateModel

function setInterestRateModel(ERC20 asset, InterestRateModel newInterestRateModel) external requiresAuth 

Sets a new or updates the existing interest rate model for a specific asset. Models can only be set or updated by the Admin. Upon success, the call emits an InterestRateModelUpdated event with the caller's address, asset, and model contract. See the Interest Rate Model section for details.

Parameters:

NameTypeDescription

asset

ERC20

The asset to assign the model to

newInterestRateModel

InterestRateModel

The interest model that the asset will use.

configureAsset

function configureAsset(ERC20 asset, ERC4626 vault, Configuration memory configuration, bool updateVault)
        external
        requiresAuth

Configures/adds USDC or a GM token for the Lending Pool, so that users are able to utilize and transfer the tokens. Tokens can only be added or configured by the Admin. If the asset is new to the pool, the vault, configuration struct, and decimals are set for the asset in three separate mappings. The vault contract is the ERC-4626 Vault that has been deployed for the particular GM token. (See GM Vaults section for more.) The decimals are obtained by calling decimals() on the ERC-20 token.

The configuration struct contains the lending and borrowing factor for each asset as shown here:

struct Configuration {
        uint256 lendFactor; // max lend amount in terms of token amount.
        uint256 borrowFactor; // max borrow amount in terms of token amount.
    }

If the asset is not new to the pool, the call updates the lending and borrow factors, and, if the updateVault boolean is marked as true, this function will assign the asset to a new vault/address. Note that this does not move any tokens to the new vault. Each vault has a migrate() function that moves the tokens.

Once the mappings are successfully updated, an AssetConfigured or AssetConfigurationUpdated event is emitted with caller's address, asset, vault contract, and configuration.

Parameters:

NameTypeDescription

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

function setGmTokens(address[] memory newGmTokenAddress) external requiresAuth 

Stores an array of GM Token addresses in the gmTokensAddresses state array. The array can only be updated by the Admin. Upon success, the call emits a NewGMTokens event with the caller's address and the new address array as parameters.

Parameters:

NameTypeDescription

newGmTokensAddress

address[] memory

An array of GM tokens

addNewGmToken

function addNewGmToken(address newGmToken) external requiresAuth 

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:

NameTypeDescription

newGmToken

address

The address of the GMX Market Token

setGMPointsContract

function setGMPointsContract(address newPointsAddress) external requiresAuth 

Sets the GM Points contract for the pool. The contract can only be set by the Admin. Upon success, the call emits an GMPointsContractUpdated event with the caller's address, and the new contract address. See the GM Points section for details.

Parameters:

NameTypeDescription

newPointsAddress

address

The address of the GM Points contract

updateLiquidationBonus

function updateLiquidationBonus(uint256 _newLiquidationBonus) external requiresAuth 

Sets or updates the liquidationBonus, which can only be done by the Admin. Upon success, the call emits a LiquidationBonusUpdated event with the caller's address and the new liquidation bonus multiplier as parameters.

Parameters:

NameTypeDescription

_newLiquidationBonus

uint256

The bonus multiplier for the liquidator

updateMaxHealthFactor

function updateMaxHealthFactor(uint256 _newMaxHealthFactor) external requiresAuth 

Sets or updates the MaxHealthFactor, which can only be done by the Admin. Upon success, the call emits a MaxHealthFactorUpdated event with the caller's address and the new maximum health factor as parameters.

Parameters:

NameTypeDescription

_newMaxHealthFactor

uint256

The max Health Factor that forms a limit on how much a liquidator can repay for the borrower.

setGloopStakersYield

function setGloopStakersYield(address _newGSYRatio) external requiresAuth 

Sets or updates the gloopStakersYieldRatio. Can only be called by the Admin. The ratio cannot be greater than 25%. The ratio represents the portion of the platform fees to be distributed to Gloop stakers. Upon success, the call emits an GloopStakersYieldUpdated event with the caller's address and the new ratio as parameters.

Parameters:

NameTypeDescription

_newGSYRatio

uint256

The new Gloop Staker yield (percentage of lending platform fees)

withdrawReserves (to be completed)

Core External/Public Functions

deposit

function deposit(ERC20 asset, uint256 amount) external updatePoints(asset, GMPoints.PoolActivity.Deposit, amount)

This function is the main and first point of entry to the protocol. Depositing is the same as "lending" an asset to the pool. At present, the only assets allowed for depositing are GM tokens and USDC.

When a user deposits, since the protocol uses ERC-4626 tokenized vaults, the amount of tokens to transfer are converted into "shares" using the internalBalanceExchangeRate(). The internalBalances and totalInternalBalances mappings of the asset are then increased by shares amount for the user and the protocol. The token amount is then transferred briefly 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.) Upon successful transfer, the Deposit event is emitted, including msg.sender's address, the asset, and the amount.

If block.timestamp is not past the gmPointsContract.pointsEndTime() (e.g. the Points program is still going on), then the caller's points are updated via updatePoints() in the GM Points contract. See GM Points for more on the point system.

Parameters:

NameTypeDescription

asset

ERC20

The asset to deposit

amount

uint256

The amount of tokens to deposit

withdraw

function withdraw(ERC20 asset, uint256 amount) external updatePoints(asset, GMPoints.PoolActivity.Withdraw, amount)

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():

Withdraws an amount of the caller's tokens from the pool. The asset can be GM or USDC tokens. As during depositing, the amount of tokens to transfer are converted into "shares" using the internalBalanceExchangeRate(). If the shares requested are less than the user's balance or zero, the call with revert. Similar to disableAsset(), if the withdrawal request would bring the user's health factor below 1, the call with revert. If the health factor remains above 1, then the internalBalances and totalInternalBalances mappings of the asset are decreased by shares amount for the user and the protocol. 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 msg.sender's address, the asset, and the amount.

If block.timestamp is not past the gmPointsContract.pointsEndTime() (e.g. the Points program is still going on), then the caller's points are updated via updatePoints() in the GM Points contract. See GM Points for more on the point system.

Parameters:

NameTypeDescription

asset

ERC20

The asset to withdraw

amount

uint256

The amount of tokens to withdraw

borrow

function borrow(ERC20 asset, uint256 amount) external updatePoints(asset, GMPoints.PoolActivity.Borrow, amount)

Transfers an amount of borrowed USDC to the caller. This function is essentially a public wrapper of the internal function internalBorrow(). For convenience, the borrow logic will be explained here.

internalBorrow():

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.) If the user has an existing loan, the unaccrued interest will be calculated for and added to the position via accrueInterest(). The user's health factor is then calculated based on the requested borrow (see calculateHealthFactor() for details). If the health factor remains above 1, then the user may borrow. If not, the function will revert.

Similar to depositing, the requested amount of tokens is converted to debtUnits by the internalDebtExchangeRate(). The debtUnits are then added to the internalDebt and totalInternalDebt mappings for the user and the protocol, respectively. The token amount is also added to the cachedTotalBorrows mapping for the borrowed asset (USDC).

The tokens are now finally transferred from USDC's 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.

If block.timestamp is not past the gmPointsContract.pointsEndTime() (e.g. the Points program is still going on), then the caller's points are updated via updatePoints() in the GM Points contract. See GM Points for more on the point system.

Parameters:

NameTypeDescription

asset

ERC20

The asset to borrow (for now, this can only be USDC)

amount

uint256

The amount of USDC to borrow

repay

function repay(ERC20 asset, uint256 amount) external updatePoints(asset, GMPoints.PoolActivity.Repay, amount)

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():

Similar to borrowing, the repayment amount of tokens is converted to debtUnits by the internalDebtExchangeRate(), with the function reverting if the debtUnits are greater than the internalDebt of the user. The debtUnits are then subtracted from the internalDebt and totalInternalDebt mappings for the user and the protocol, respectively.

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.) If the user has an existing loan, then interest will be calculated for their position via accrueInterest(). The token amount is also subtracted from the cachedTotalBorrows mapping for the borrowed asset (USDC).

The tokens are now finally transferred from the user to the pool contract, which immediately transfers them to the USDC vault. Upon successful transfer, the Repay event is emitted, including the msg.sender's address, the asset, and the amount.

If block.timestamp is not past the gmPointsContract.pointsEndTime() (e.g. the Points program is still going on), then the caller's points are updated via updatePoints() in the GM Points contract. See GM Points for more on the point system.

Parameters:

NameTypeDescription

asset

ERC20

The asset to repay (for now, this can only be USDC)

amount

uint256

The amount of USDC to repay

liquidateUser

function liquidateUser(ERC20 borrowedAsset, address borrower, uint256 repayAmount) external

To be Completed

Parameters:

NameTypeDescription

borrowedAsset

ERC20

The borrowed asset to be repaid

borrower

address

The address with poor health factor to liquidate

repayAmount

uint256

The amount the liquidator needs to repay to bring the borrower's position back into good health.

Ancillary Functions

isValidGmToken

function isValidGmToken(ERC20 asset) public view returns (bool isValid)

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:

NameTypeDescription

asset

ERC20

The token to validate

Returns:

NameTypeDescription

isValid

bool

If true, the asset is a valid GM token.

enableAsset

function enableAsset(ERC20 asset) public

Enables an asset as a user's collateral. The asset 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:

NameTypeDescription

asset

ERC20

The token to enable

disableAsset

function disableAsset(ERC20 asset) public

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 or employing leverage, then the collateral will be disabled.

If the user has multiple assets as active collateral, the value of the asset to be disabled is subtracted from the total value of the user's assets. If the theoretical health factor based on the new collateral value remains above 1e18, 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:

NameTypeDescription

asset

ERC20

The token to disable

userLiquidatable

function userLiquidatable(ERC20 borrowedAsset, address user) public view returns (bool)

Returns true if canBorrow(borrowedAsset, user, 0) returns false, which means the user's health factor is below 1e18. If false, the user cannot be liquidated at the time of the call.

Parameters:

NameTypeDescription

asset

ERC20

The asset currently borrowed

user

address

The address to check

Returns:

NameTypeDescription

unnamed

bool

If true, the user can be liquidated. If false, user cannot.

canBorrow

function canBorrow(ERC20 asset, address user, uint256 amount) internal view returns (bool)

Returns true if calculateHealthFactor() is greater than or equal to 1e18. If true, the user can borrow the input amount.

Parameters:

NameTypeDescription

asset

ERC20

The asset requested to borrow

user

address

The address to calculate the borrow balance for

amount

uint256

The amount requested to borrow

Returns:

NameTypeDescription

unnamed

bool

If true, the user can borrow. If false, user cannot.

_seizeCollateral

function (ERC20 asset, address user, uint256 amount) internal view returns (bool)

To be completed

Parameters:

NameTypeDescription

asset

ERC20

The asset requested to borrow

user

address

The address to calculate the borrow balance for

amount

uint256

The amount requested to borrow

getUserCollateralValue

function getUserCollateralValue(address borrower) public view returns (uint256)

Calculates and sums the value of each asset the borrower currently has enabled as collateral.

Parameters:

NameTypeDescription

borrower

address

The address the collateral belongs to

Returns:

NameTypeDescription

unnamed

uint256

The total collateral value of the user/borrower

calculateHealthFactor

function calculateHealthFactor(ERC20 asset, address user, uint256 amount) public view returns (uint256)

Returns the new health factor after a requested borrow by the user. 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 the maximumBorrowable value.

  • The hypotheticalBorrowBalance is determined by adding the requested borrow amount to the current borrowBalance, if any.

  • The borrowBalance value is then determined by multiplying the hypotheticalBorrowBalance by the current price of the asset.

  • If the asset to be borrowed has a borrowFactor, it is multiplied by the maximumBorrowable value to obtain the actualBorrowable value.

  • Dividing the actualBorrowable value by the borrowBalance results in the Health Factor. If the Health Factor is above 1e18, e.g. if actualBorrowable is greater than or equal to the future borrowBalance, then the user can borrow the amount requested.

Parameters:

NameTypeDescription

asset

ERC20

The asset requested to borrow

user

address

The address to calculate the borrow balance for

amount

uint256

The amount requested to borrow

Returns:

NameTypeDescription

unnamed

uint256

The new Health Factor after the requested borrow

maxBorrowableValue

function maxBorrowableValue() public view returns (uint256 maximumBorrowable)

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. However, since USDC has a borrowFactor of 1, so 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:

NameTypeDescription

maximumBorrowableValue

uint256

The maximum value the caller can borrow

getCollateral

function getCollateral(address user) external view returns (ERC20[] memory)

Retrieves the user's ERC20[] array of collateral assets from the userCollateral mapping.

Parameters:

NameTypeDescription

user

address

The owner of the collateral

Returns:

NameTypeDescription

unnamed

ERC20[]

An ERC20[] array of collateral assets

Accounting Logic Functions

totalUnderlying

function totalUnderlying(ERC20 asset) public view returns (uint256)

Returns the total amount of underlying tokens both held by and owed to the pool. It does so by adding the totalBorrows() to the availableLiquidity() of the asset.

Parameters:

NameTypeDescription

asset

ERC20

The underlying asset

Returns:

NameTypeDescription

unnamed

uint256

The total amount of tokens held by and owed to the pool

availableLiquidity

function availableLiquidity(ERC20 asset) public view returns (uint256)

Returns the total amount of underlying tokens held in the token's vault for the Lending pool. After calling balanceOf on the asset's vault, the shares are converted to the returned token amount by calling convertToAssets() on the vault.

Parameters:

NameTypeDescription

asset

ERC20

The underlying asset

Returns:

NameTypeDescription

unnamed

uint256

The total amount of tokens held in the asset's vault.

balanceOf

function balanceOf(ERC20 asset, address user) public view returns (uint256)

Calculates the token balance of the user by multiplying the user's internal balance units by the internalBalanceExchangeRate().

Parameters:

NameTypeDescription

asset

ERC20

The underlying asset

user

address

The address to get the token balance of

Returns:

NameTypeDescription

unnamed

uint256

The token balance of the user

internalBalanceExchangeRate

function internalBalanceExchangeRate(ERC20 asset) internal view returns (uint256)

Calculates the exchange rate between underlying tokens and internal balance units. By dividing the totalUnderlying() shares by the totalInternalBalance units for the asset, this function converts ERC4626 shares into tokens. If the totalInternalBalance is zero, then the exchange rate is 1.

Parameters:

NameTypeDescription

asset

ERC20

The underlying asset

Returns:

NameTypeDescription

unnamed

uint256

The exchange rate

borrowBalance

function borrowBalance(ERC20 asset, address user) public view returns (uint256)

Calculates and returns the borrow balance of an address by multiplying the user's internal debt units by the internalDebtExchangeRate().

Parameters:

NameTypeDescription

asset

ERC20

The borrowed asset

user

address

The address to calculate the borrow balance for

Returns:

NameTypeDescription

unnamed

uint256

The borrow balance of the user

internalDebtExchangeRate

function internalDebtExchangeRate(ERC20 asset) internal view returns (uint256)

Calculates the exchange rate between underlying tokens and internal debt units. By dividing the totalBorrows() shares by the totalInternalDebt units for the asset, this function converts ERC4626 shares into tokens. If the totalInternalDebt is zero, then the exchange rate is 1.

Parameters:

NameTypeDescription

asset

ERC20

The underlying asset

Returns:

NameTypeDescription

unnamed

uint256

The exchange rate

totalBorrows

function totalBorrows(ERC20 asset) public view returns (uint256)

Calculates and returns the total amount of underlying tokens being loaned out to borrowers. To calculate the accrued interest on the total underlying tokens and cachedTotalBorrows for the asset are passed as arguments into the getBorrowRate() function called on the Interest Rate Model set for the asset (see Interest Rate Model section for details). Using the time or blockDelta since the lastInterestAccrual, we can calculate the interestAccumulator:

Multiplying cachedtotalBorrows by the interestAccumulator gives us the total borrows with the interest accrual.

Parameters:

NameTypeDescription

asset

ERC20

The underlying asset

Returns:

NameTypeDescription

unnamed

uint256

The total amount of borrowed tokens

accrueInterest

function accrueInterest(ERC20 asset) internal

Deriving the totalInterestAccumulated from totalBorrows() , this function calculates the gloopStakersYield and passes it into _updateStakersYield(). It then adds the interest accumulates (less the staker yield) to the cachedTotalBorrows internal mapping for the given asset. The current block.number is then stored in lastInterestAccrual for the asset.

Parameters:

NameTypeDescription

asset

ERC20

The underlying asset (only USDC for now)

_updateStakersYield

function _updateStakersYield(uint256 _stakersYield) internal

Converts the incoming _stakersYield interest amount to shares via the internalBalanceExchangeRate, These shares are added both to the internalBalances for the gloopStakersWallet and the totalInternalBalances for the asset.

Parameters:

NameTypeDescription

_stakersYield

uint256

The interest accumulated from lending fees that goes to Gloop stakers.

_updateTotalReserves (To Be Completed)

Last updated