The borrow rate in the semi-logarithmic MonetaryPolicy contract is intricately linked to the utilization ratio of the lending markets. This ratio plays a crucial role in determining the cost of borrowing, with its value ranging between 0 and 1. At a utilization rate of 0, indicating no borrowed assets, the borrowing rate aligns with the minimum threshold, min_rate. Conversely, a utilization rate of 1, where all available assets are borrowed, escalates the borrowing rate to its maximum limit, max_rate.
GitHub
The source code of the SemilogMonetaryPolicy.vy contract can be found on GitHub.
The function for the rate is as simple as:
Variable
Description
MonetaryPolicy.min_rate()
MonetaryPolicy.max_rate()
Utilization of the lending market. What ratio of the provided assets are borrowed?
A graph to display the interplay between min_rate, max_rate, and market utilization:1
The rate values are based on 1e18 and NOT annualized.
To calculate the Borrow APR:
Rate calculations occur within the MonetaryPolicy contract. The rate is regularly updated by the internal _save_rate method in the Controller. This happens whenever a new loan is initiated (_create_loan), collateral is either added (add_collateral) or removed (remove_collateral), additional debt is incurred (borrow_more and borrow_more_extended), debt is repaid (repay, repay_extended), or a loan undergoes liquidation (_liquidate).
Source Code
@internaldef_save_rate():""" @notice Save current rate """rate:uint256=min(self.monetary_policy.rate_write(),MAX_RATE)AMM.set_rate(rate)
log_min_rate:public(int256)log_max_rate:public(int256)@internal@externaldefrate_write(_for:address=msg.sender)->uint256:returnself.calculate_rate(_for,0,0)@internal@viewdefcalculate_rate(_for:address,d_reserves:int256,d_debt:int256)->uint256:total_debt:int256=convert(Controller(_for).total_debt(),int256)total_reserves:int256=convert(BORROWED_TOKEN.balanceOf(_for),int256)+total_debt+d_reservestotal_debt+=d_debtasserttotal_debt>=0,"Negative debt"asserttotal_reserves>=total_debt,"Reserves too small"iftotal_debt==0:returnself.min_rateelse:log_min_rate:int256=self.log_min_ratelog_max_rate:int256=self.log_max_ratereturnself.exp(total_debt*(log_max_rate-log_min_rate)/total_reserves+log_min_rate)
@external@nonreentrant('lock')defset_rate(rate:uint256)->uint256:""" @notice Set interest rate. That affects the dependence of AMM base price over time @param rate New rate in units of int(fraction * 1e18) per second @return rate_mul multiplier (e.g. 1.0 + integral(rate, dt)) """assertmsg.sender==self.adminrate_mul:uint256=self._rate_mul()self.rate_mul=rate_mulself.rate_time=block.timestampself.rate=ratelogSetRate(rate,rate_mul,block.timestamp)returnrate_mul
Getter for the borrow rate for a specific lending market.
Returns: rate (uint256).
Input
Type
Description
_for
address
Controller contract; Defaults to msg.sender, because the caller of the function is usually the Controller.
Source code
@view@externaldefrate(_for:address=msg.sender)->uint256:returnself.calculate_rate(_for,0,0)@internal@viewdefcalculate_rate(_for:address,d_reserves:int256,d_debt:int256)->uint256:total_debt:int256=convert(Controller(_for).total_debt(),int256)total_reserves:int256=convert(BORROWED_TOKEN.balanceOf(_for),int256)+total_debt+d_reservestotal_debt+=d_debtasserttotal_debt>=0,"Negative debt"asserttotal_reserves>=total_debt,"Reserves too small"iftotal_debt==0:returnself.min_rateelse:log_min_rate:int256=self.log_min_ratelog_max_rate:int256=self.log_max_ratereturnself.exp(total_debt*(log_max_rate-log_min_rate)/total_reserves+log_min_rate)
@externaldefrate_write(_for:address=msg.sender)->uint256:returnself.calculate_rate(_for,0,0)@internal@viewdefcalculate_rate(_for:address,d_reserves:int256,d_debt:int256)->uint256:total_debt:int256=convert(Controller(_for).total_debt(),int256)total_reserves:int256=convert(BORROWED_TOKEN.balanceOf(_for),int256)+total_debt+d_reservestotal_debt+=d_debtasserttotal_debt>=0,"Negative debt"asserttotal_reserves>=total_debt,"Reserves too small"iftotal_debt==0:returnself.min_rateelse:log_min_rate:int256=self.log_min_ratelog_max_rate:int256=self.log_max_ratereturnself.exp(total_debt*(log_max_rate-log_min_rate)/total_reserves+log_min_rate)
This function can only be called by the admin of FACTORY.
Function to set new values for min_rate and max_rate, and consequently log_min_rate and log_max_rate as well. New rate values can be chosen quite deliberately, but need to be within the bounds of MIN_RATE and MAX_RATE:
Getter for the current minimum borrow rate. This value is set to the input given for min_default_borrow_rate when creating a new market. The rate is charged when utilization is 0 and can be changed by the admin of the lending factory.
Getter for the current maximum borrow rate. This value is set to the input given for max_default_borrow_rate when creating a new market. The rate is charged when utilization is 1 and can be changed by the admin of the lending factory.
Getter for the logarithm ln() function of min_rate, based on log2.
Returns: semi-log minimum rate (int256).
Source code
log_min_rate:public(int256)@externaldef__init__(borrowed_token:ERC20,min_rate:uint256,max_rate:uint256):assertmin_rate>=MIN_RATEandmax_rate<=MAX_RATEandmin_rate<=max_rate,"Wrong rates"BORROWED_TOKEN=borrowed_tokenself.min_rate=min_rateself.max_rate=max_rateself.log_min_rate=self.ln_int(min_rate)self.log_max_rate=self.ln_int(max_rate)FACTORY=Factory(msg.sender)@internal@puredefln_int(_x:uint256)->int256:""" @notice Logarithm ln() function based on log2. Not very gas-efficient but brief """# adapted from: https://medium.com/coinmonks/9aef8515136e# and vyper log implementation# This can be much more optimal but that's not important herex:uint256=_xif_x<10**18:x=10**36/_xres:uint256=0foriinrange(8):t:uint256=2**(7-i)p:uint256=2**tifx>=p*10**18:x/=pres+=t*10**18d:uint256=10**18foriinrange(59):# 18 decimals: math.log2(10**18) == 59.7if(x>=2*10**18):res+=dx/=2x=x*x/10**18d/=2# Now res = log2(x)# ln(x) = log2(x) / log2(e)result:int256=convert(res*10**18/1442695040888963328,int256)if_x>=10**18:returnresultelse:return-result
Getter for the logarithm ln() function of max_rate, based on log2.
Returns: semi-log maximum rate (int256).
Source code
log_max_rate:public(int256)@externaldef__init__(borrowed_token:ERC20,min_rate:uint256,max_rate:uint256):assertmin_rate>=MIN_RATEandmax_rate<=MAX_RATEandmin_rate<=max_rate,"Wrong rates"BORROWED_TOKEN=borrowed_tokenself.min_rate=min_rateself.max_rate=max_rateself.log_min_rate=self.ln_int(min_rate)self.log_max_rate=self.ln_int(max_rate)FACTORY=Factory(msg.sender)@internal@puredefln_int(_x:uint256)->int256:""" @notice Logarithm ln() function based on log2. Not very gas-efficient but brief """# adapted from: https://medium.com/coinmonks/9aef8515136e# and vyper log implementation# This can be much more optimal but that's not important herex:uint256=_xif_x<10**18:x=10**36/_xres:uint256=0foriinrange(8):t:uint256=2**(7-i)p:uint256=2**tifx>=p*10**18:x/=pres+=t*10**18d:uint256=10**18foriinrange(59):# 18 decimals: math.log2(10**18) == 59.7if(x>=2*10**18):res+=dx/=2x=x*x/10**18d/=2# Now res = log2(x)# ln(x) = log2(x) / log2(e)result:int256=convert(res*10**18/1442695040888963328,int256)if_x>=10**18:returnresultelse:return-result
Getter for the lowest possible rate for the MonetaryPolicy. When setting new rates via set_rates(), MIN_RATE is the lowest possible value. This variable is a constant and therefore cannot be changed.
Getter for the highest possible rate for the MonetaryPolicy. When setting new rates via set_rates(), MAX_RATE is the highest possible value. This variable is a constant and therefore cannot be changed.