GM Points
On-chain points system for GM Lending and Looping activity
The GM Points contract stores tracks users that lend and borrow USDC via the Lending Pool and attributes points for their activity. Users also get points boosts for the number of referrals they acquire. The contract inherits from Solmate's Auth and OpenZeppelin's Pausable contracts.
constructor
At deployment, the constructor sets the deployer (msg.sender
) as the owner (referred to hereafter as Admin). It also converts the _poolContract
address into a LendingPool
contract instance and converts the usdcTokenAddress
into an ERC20 token instance.
Admin Functions
pause
Pauses functions with the whenNotPaused
modifier. Only the Admin can call.
unpause
Unpauses functions with the whenNotPaused
modifier. Only the Admin can call.
setPointsParameters
Sets point accrual ending time, points earned per dollar for both lending and borrowing, point boost per referral, and max referral boost allowed. This can only be called by the Admin.
Parameters:
pointsEndTime
uint256
The time the Points Program ends
_lendingUSDCPPD
uint256
Points per USDC dollar lent per day
_borrowingUSDCPPD
uint256
Points per USDC dollar borrowed per day
_referralBoost
uint256
The point boost per referral
_maxReferralBoost
uint256
The maximum point boost a user can obtain for referrals.
addReferrer
Sets an address as the caller's referrer. The referrer address cannot be the zero address, and the referral address also cannot be its own referrer. For the referral address, the call sets userRefs[msg.sender].hasReferrer
to true (this locks in the referrer, which can only be set once), stores the address in userRefs[msg.sender].referrer
. Then, the referral's address is added to the referrals
array in the referrer's userRefs
struct.
If the referral address calls this function again with a referrer set, it will revert.
Upon success, the call emits a ReferrerAdded
event with the caller's address and the new referrer's address as parameters.
Parameters:
_referrer
address
The proposed referrer address of the caller, e.g. the referral.
updatePoints
This public wrapper only allows the pool contract to call and update users' points. This enables claimPoints()
to call _updatePoints()
internally. See _updatePoints()
for logic description.
Parameters:
_user
address
The user to calculate points for
_activity
PoolActivity
The pool action that called updatePoints()
: Deposit, Withdraw, Borrow, Repay
_amount
uint256
The amount of USDC tokens in the latest transaction
Returns:
newLendPoints
uint256
The lending points earned since lastUpdateTime
newBorrowPoints
uint256
The borrowing points earned since lastUpdateTime
getUserPoints
Retrieves a user's stored points, based off the lastUpdateTime
, which is the last time the user transacted with the pool. Thus, this does not return floating points (not-yet-stored points).
Parameters:
_user
address
The user to query
Returns:
pointsEndTime
uint256
The time the Points Program ends
_lendingUSDCPPD
uint256
Points per USDC dollar lent per day
_borrowingUSDCPPD
uint256
Points per USDC dollar borrowed per day
_referralBoost
uint256
The point boost per referral
_maxReferralBoost
uint256
The maximum point boost a user can obtain for referrals.
getTotalProgramStats
Retrieves program-wide number of users and points.
Returns:
programStats.totalNumUsers
uint256
The total number of users that have points
programStats.totalLendingPoints
uint256
The total points earned by all users lending USDC, including referral boosts.
programStats.totalBorrowingPoints
uint256
The total points earned by all users borrowing USDC, including referral boosts
getUserReferrals
Retrieves an array of a given user's referrals.
Parameters:
_user
address
The user to query
Returns:
referrals
address[] memory
The referrer's address array of referrals.
calculateCurrentBoost
Calculates and returns a user's current points boost up to a predefined maximum. If there are no eligible referrals, then points boost defaults to PRECISION, which is 10_000
. Looping through the user's referrals array, the USDC balance in the Lending Pool is retrieved, and numEligibleRefs
increases by one with each referral that has over the MIN_DEPOSIT_AMOUNT
in the Lending Pool.
The totalBoost
is then calculated by multiplying the referralBoost
by numEligibleRefs
and adding that to PRECISION
. If the resulting number is above maxReferralBoost
, the total Boost is set to the max boost allowed and returned.
A user's boost can fluctuate and is freshly calculated at each updatePoints()
and claimPoints()
call. Note: this function only calculates the user's potential boost. It does not take into account the user's own point eligibility (e.g. min deposit check). That is determined in calculateFloatingPoints()
.
Parameters:
_user
address
The user to calculate the boost for
calculateFloatingPoints
Calculates the not-yet-stored "floating" points for a given user. Users can call this function at any time. As an example, a user can enter their address, 0 for a "deposit" transaction, 0 for the _amount
, and can enter their current boost retrieved from calculateCurrentBoost()
.
First, the days since the lastUpdateTime
are calculated. If it is past the pointsEndTime
, lastUpdateTime
becomes pointsEndTime
. The call then checks if the user's previously lending balance was above the MIN_DEPOSIT_AMOUNT
to be eligible for points. If not, then the function returns (0, 0)
.
When called in the updatePoints()
call chain, balances are retrieved after successful pool balance operations, so the user's USDC pool balance change that began the call is "undone" before calculating the floating points, since the new points should be earned based on the previously existing balance, if any. If the action was a deposit or withdraw, the USDC lending points are based on the user's pre-deposit/withdraw balance. Since no changes were made to the borrow balance, the currentBorrowBalance
is used to calculate borrow points. If the action was a borrow or repay, the USDC borrowing points are based on the user's pre-borrow/repay balance. Since no changes were made to the lend balance, the currentLendBalance
is used to calculate lending points.
Floating Points = (amount [borrowed/lent] * Points Per Day factor * days * total referral boost) / PRECISION.
Parameters:
_user
address
The time the Points Program ends
_activity
uint8
The uint8 representation of a specific PoolActivity Enum element
_amount
uint256
The amount of USDC tokens in the latest transaction
_maxReferralBoost
uint256
The maximum point boost a user can obtain for referrals
Returns:
floatingLendPoints
uint256
Points for lending USDC that have been earned but not yet stored
floatingBorrowPoints
uint256
Points for borrowing USDC that have been earned but not yet stored
claimPoints
Claims all floating points for a user. Since points live on-chain, and there is no automatic way to claim them for users, this function is primarily for calculating users' final floating points, adding them to existing points, and storing them after the Points Program ends. If block.timestamp
is past pointsEndTime
, the function will revert. If the user has no earned points to claim, the function will revert.
The remaining floating points are calculated via an internal call to _updatePoints()
. The user's totalEarnedPts
are then stored in a temporary variable and zeroed out in the user's userPoints
struct to combat reentrancy. The points are then finally stored in the claimedPoints
member of the struct. A Claim
event is emitted with the address of the caller and the totalEarnedPts
as parameters.
Returns:
totalEarnedPts
uint256
The total amount of points a user has earned over the entire program.
_updatePoints
All the main functions and calculations are tied together here. Using the update time, the current boost, and the floating points, user point structs are updated with the latest calculated points.
If userPoints[_user].lastUpdateTime
is zero, then the address is a brand new user and programStats.totalNumUsers
increments by one. Then the lastUpdateTime
is set to the block.timestamp
and (0, 0)
is returned.
The user's total points boost is obtained from calculateCurrentBoost()
and is passed into calculateFloatingPoints()
, which returns the floatingLendPoints
and floatingBorrowPoints
. The user's lastUpdateTime
is updated to either block.timestamp
or pointsEndTime
, whichever time is earlier. The function then updates the following struct members with either floatingLendPoints
, floatingBorrowPoints
, or the sum of both: userPoints[_user].lendingUSDCPoints
, programStats.totalLendingPoints
, userPoints[_user].borrowingUSDCPoints
, programStats.totalBorrowingPoints
, and userPoints[_user].totalEarnedPoints
.
The function then returns the floating points, albeit with different names: newLendPoints
and newBorrowPoints
.
Parameters:
_user
address
The user that will potentially accrue points
_activity
PoolActivity
The pool action that called updatePoints()
: Deposit, Withdraw, Borrow, Repay
_amount
uint256
The amount of USDC tokens in the latest transaction
Returns:
newLendPoints
uint256
The lending points earned since lastUpdateTime
newBorrowPoints
uint256
The borrowing points earned since lastUpdateTime
_updateTime
If after pointsEndTime
, then this function returns the pointsEndTime
. If not, it returns block.timestamp
.
Returns:
unnamed
uint256
block.timestamp
or pointsEndTime
Last updated