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

function pause() external requiresAuth

Pauses functions with the whenNotPaused modifier. Only the Admin can call.

unpause

function unpause() external requiresAuth

Unpauses functions with the whenNotPaused modifier. Only the Admin can call.

setPointsParameters

function setPointsParameters(address _newWallet) external requiresAuth 

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:

NameTypeDescription

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

function addReferrer(address _newWallet) external 

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:

NameTypeDescription

_referrer

address

The proposed referrer address of the caller, e.g. the referral.

updatePoints

function updatePoints(address _newWallet) public onlyPool returns (uint256, uint256)

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:

NameTypeDescription

_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:

NameTypeDescription

newLendPoints

uint256

The lending points earned since lastUpdateTime

newBorrowPoints

uint256

The borrowing points earned since lastUpdateTime

getUserPoints

function getUserPoints(address _user) external view returns (uint256, uint256, uint256, uint256, uint256)

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:

NameTypeDescription

_user

address

The user to query

Returns:

NameTypeDescription

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

function getTotalProgramStats() external view returns (uint256, uint256, uint256)

Retrieves program-wide number of users and points.

Returns:

NameTypeDescription

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

function getUserReferrals(address _user) external view returns (address[] memory)

Retrieves an array of a given user's referrals.

Parameters:

NameTypeDescription

_user

address

The user to query

Returns:

NameTypeDescription

referrals

address[] memory

The referrer's address array of referrals.

calculateCurrentBoost

function calculateCurrentBoost(address _user) public view returns (uint256)

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:

NameTypeDescription

_user

address

The user to calculate the boost for

calculateFloatingPoints

function calculateFloatingPoints(address _user, uint8 _activity, uint256 _amount, uint256 _totalBoost) public view returns (uint256, uint256)

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:

NameTypeDescription

_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:

NameTypeDescription

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

function claimPoints() external returns (uint256)

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:

NameTypeDescription

totalEarnedPts

uint256

The total amount of points a user has earned over the entire program.

_updatePoints

function _updatePoints(address _user, PoolActivity _activity, uint256 _amount) internal whenNotPaused returns (uint256, uint256]

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:

NameTypeDescription

_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:

NameTypeDescription

newLendPoints

uint256

The lending points earned since lastUpdateTime

newBorrowPoints

uint256

The borrowing points earned since lastUpdateTime

_updateTime

function _updateTime() internal view returns (uint256)

If after pointsEndTime, then this function returns the pointsEndTime. If not, it returns block.timestamp.

Returns:

NameTypeDescription

unnamed

uint256

block.timestamp or pointsEndTime

Last updated