Contract Name:
JumpRateModelV2
Contract Source Code:
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (access/IAccessControl.sol)
pragma solidity ^0.8.0;
/**
* @dev External interface of AccessControl declared to support ERC165 detection.
*/
interface IAccessControl {
/**
* @dev Emitted when `newAdminRole` is set as ``role``'s admin role, replacing `previousAdminRole`
*
* `DEFAULT_ADMIN_ROLE` is the starting admin for all roles, despite
* {RoleAdminChanged} not being emitted signaling this.
*
* _Available since v3.1._
*/
event RoleAdminChanged(bytes32 indexed role, bytes32 indexed previousAdminRole, bytes32 indexed newAdminRole);
/**
* @dev Emitted when `account` is granted `role`.
*
* `sender` is the account that originated the contract call, an admin role
* bearer except when using {AccessControl-_setupRole}.
*/
event RoleGranted(bytes32 indexed role, address indexed account, address indexed sender);
/**
* @dev Emitted when `account` is revoked `role`.
*
* `sender` is the account that originated the contract call:
* - if using `revokeRole`, it is the admin role bearer
* - if using `renounceRole`, it is the role bearer (i.e. `account`)
*/
event RoleRevoked(bytes32 indexed role, address indexed account, address indexed sender);
/**
* @dev Returns `true` if `account` has been granted `role`.
*/
function hasRole(bytes32 role, address account) external view returns (bool);
/**
* @dev Returns the admin role that controls `role`. See {grantRole} and
* {revokeRole}.
*
* To change a role's admin, use {AccessControl-_setRoleAdmin}.
*/
function getRoleAdmin(bytes32 role) external view returns (bytes32);
/**
* @dev Grants `role` to `account`.
*
* If `account` had not been already granted `role`, emits a {RoleGranted}
* event.
*
* Requirements:
*
* - the caller must have ``role``'s admin role.
*/
function grantRole(bytes32 role, address account) external;
/**
* @dev Revokes `role` from `account`.
*
* If `account` had been granted `role`, emits a {RoleRevoked} event.
*
* Requirements:
*
* - the caller must have ``role``'s admin role.
*/
function revokeRole(bytes32 role, address account) external;
/**
* @dev Revokes `role` from the calling account.
*
* Roles are often managed via {grantRole} and {revokeRole}: this function's
* purpose is to provide a mechanism for accounts to lose their privileges
* if they are compromised (such as when a trusted device is misplaced).
*
* If the calling account had been granted `role`, emits a {RoleRevoked}
* event.
*
* Requirements:
*
* - the caller must be `account`.
*/
function renounceRole(bytes32 role, address account) external;
}
// SPDX-License-Identifier: BSD-3-Clause
pragma solidity ^0.8.25;
import "@openzeppelin/contracts/access/IAccessControl.sol";
/**
* @title IAccessControlManagerV8
* @author Venus
* @notice Interface implemented by the `AccessControlManagerV8` contract.
*/
interface IAccessControlManagerV8 is IAccessControl {
function giveCallPermission(address contractAddress, string calldata functionSig, address accountToPermit) external;
function revokeCallPermission(
address contractAddress,
string calldata functionSig,
address accountToRevoke
) external;
function isAllowedToCall(address account, string calldata functionSig) external view returns (bool);
function hasPermission(
address account,
address contractAddress,
string calldata functionSig
) external view returns (bool);
}
// SPDX-License-Identifier: BSD-3-Clause
pragma solidity 0.8.25;
import { SECONDS_PER_YEAR } from "./constants.sol";
abstract contract TimeManagerV8 {
/// @notice Stores blocksPerYear if isTimeBased is true else secondsPerYear is stored
/// @custom:oz-upgrades-unsafe-allow state-variable-immutable
uint256 public immutable blocksOrSecondsPerYear;
/// @notice Acknowledges if a contract is time based or not
/// @custom:oz-upgrades-unsafe-allow state-variable-immutable
bool public immutable isTimeBased;
/// @notice Stores the current block timestamp or block number depending on isTimeBased
/// @custom:oz-upgrades-unsafe-allow state-variable-immutable
function() view returns (uint256) private immutable _getCurrentSlot;
/**
* @dev This empty reserved space is put in place to allow future versions to add new
* variables without shifting down storage in the inheritance chain
* See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps
*/
uint256[48] private __gap;
/// @notice Thrown when blocks per year is invalid
error InvalidBlocksPerYear();
/// @notice Thrown when time based but blocks per year is provided
error InvalidTimeBasedConfiguration();
/**
* @param timeBased_ A boolean indicating whether the contract is based on time or block
* If timeBased is true than blocksPerYear_ param is ignored as blocksOrSecondsPerYear is set to SECONDS_PER_YEAR
* @param blocksPerYear_ The number of blocks per year
* @custom:error InvalidBlocksPerYear is thrown if blocksPerYear entered is zero and timeBased is false
* @custom:error InvalidTimeBasedConfiguration is thrown if blocksPerYear entered is non zero and timeBased is true
* @custom:oz-upgrades-unsafe-allow constructor
*/
constructor(bool timeBased_, uint256 blocksPerYear_) {
if (!timeBased_ && blocksPerYear_ == 0) {
revert InvalidBlocksPerYear();
}
if (timeBased_ && blocksPerYear_ != 0) {
revert InvalidTimeBasedConfiguration();
}
isTimeBased = timeBased_;
blocksOrSecondsPerYear = timeBased_ ? SECONDS_PER_YEAR : blocksPerYear_;
_getCurrentSlot = timeBased_ ? _getBlockTimestamp : _getBlockNumber;
}
/**
* @dev Function to simply retrieve block number or block timestamp
* @return Current block number or block timestamp
*/
function getBlockNumberOrTimestamp() public view virtual returns (uint256) {
return _getCurrentSlot();
}
/**
* @dev Returns the current timestamp in seconds
* @return The current timestamp
*/
function _getBlockTimestamp() private view returns (uint256) {
return block.timestamp;
}
/**
* @dev Returns the current block number
* @return The current block number
*/
function _getBlockNumber() private view returns (uint256) {
return block.number;
}
}
// SPDX-License-Identifier: BSD-3-Clause
pragma solidity ^0.8.25;
/// @dev Base unit for computations, usually used in scaling (multiplications, divisions)
uint256 constant EXP_SCALE = 1e18;
/// @dev A unit (literal one) in EXP_SCALE, usually used in additions/subtractions
uint256 constant MANTISSA_ONE = EXP_SCALE;
/// @dev The approximate number of seconds per year
uint256 constant SECONDS_PER_YEAR = 31_536_000;
// SPDX-License-Identifier: BSD-3-Clause
pragma solidity 0.8.25;
/**
* @title Compound's InterestRateModel Interface
* @author Compound
*/
abstract contract InterestRateModel {
/**
* @notice Calculates the current borrow interest rate per slot (block or second)
* @param cash The total amount of cash the market has
* @param borrows The total amount of borrows the market has outstanding
* @param reserves The total amount of reserves the market has
* @param badDebt The amount of badDebt in the market
* @return The borrow rate percentage per slot (block or second) as a mantissa (scaled by EXP_SCALE)
*/
function getBorrowRate(
uint256 cash,
uint256 borrows,
uint256 reserves,
uint256 badDebt
) external view virtual returns (uint256);
/**
* @notice Calculates the current supply interest rate per slot (block or second)
* @param cash The total amount of cash the market has
* @param borrows The total amount of borrows the market has outstanding
* @param reserves The total amount of reserves the market has
* @param reserveFactorMantissa The current reserve factor the market has
* @param badDebt The amount of badDebt in the market
* @return The supply rate percentage per slot (block or second) as a mantissa (scaled by EXP_SCALE)
*/
function getSupplyRate(
uint256 cash,
uint256 borrows,
uint256 reserves,
uint256 reserveFactorMantissa,
uint256 badDebt
) external view virtual returns (uint256);
/**
* @notice Indicator that this is an InterestRateModel contract (for inspection)
* @return Always true
*/
function isInterestRateModel() external pure virtual returns (bool) {
return true;
}
}
// SPDX-License-Identifier: BSD-3-Clause
pragma solidity 0.8.25;
import { IAccessControlManagerV8 } from "@venusprotocol/governance-contracts/contracts/Governance/IAccessControlManagerV8.sol";
import { TimeManagerV8 } from "@venusprotocol/solidity-utilities/contracts/TimeManagerV8.sol";
import { InterestRateModel } from "./InterestRateModel.sol";
import { EXP_SCALE, MANTISSA_ONE } from "./lib/constants.sol";
/**
* @title JumpRateModelV2
* @author Compound (modified by Dharma Labs, Arr00 and Enclabs)
* @notice An interest rate model with a steep increase after a certain utilization threshold called **kink** is reached.
* The parameters of this interest rate model can be adjusted by the owner. Version 2 modifies Version 1 by enabling updateable parameters
*/
contract JumpRateModelV2 is InterestRateModel, TimeManagerV8 {
/**
* @notice The address of the AccessControlManager contract
*/
IAccessControlManagerV8 public accessControlManager;
/**
* @notice The multiplier of utilization rate per block or second that gives the slope of the interest rate
*/
uint256 public multiplierPerBlock;
/**
* @notice The base interest rate per block or second which is the y-intercept when utilization rate is 0
*/
uint256 public baseRatePerBlock;
/**
* @notice The multiplier per block or second after hitting a specified utilization point
*/
uint256 public jumpMultiplierPerBlock;
/**
* @notice The utilization point at which the jump multiplier is applied
*/
uint256 public kink;
event NewInterestParams(
uint256 baseRatePerBlockOrTimestamp,
uint256 multiplierPerBlockOrTimestamp,
uint256 jumpMultiplierPerBlockOrTimestamp,
uint256 kink
);
/**
* @notice Thrown when the action is prohibited by AccessControlManager
*/
error Unauthorized(address sender, address calledContract, string methodSignature);
/**
* @notice Construct an interest rate model
* @param baseRatePerYear_ The approximate target base APR, as a mantissa (scaled by EXP_SCALE)
* @param multiplierPerYear_ The rate of increase in interest rate wrt utilization (scaled by EXP_SCALE)
* @param jumpMultiplierPerYear_ The multiplier after hitting a specified utilization point
* @param kink_ The utilization point at which the jump multiplier is applied
* @param accessControlManager_ The address of the AccessControlManager contract
* @param timeBased_ A boolean indicating whether the contract is based on time or block.
* @param blocksPerYear_ The number of blocks per year
*/
constructor(
uint256 baseRatePerYear_,
uint256 multiplierPerYear_,
uint256 jumpMultiplierPerYear_,
uint256 kink_,
IAccessControlManagerV8 accessControlManager_,
bool timeBased_,
uint256 blocksPerYear_
) TimeManagerV8(timeBased_, blocksPerYear_) {
require(address(accessControlManager_) != address(0), "invalid ACM address");
accessControlManager = accessControlManager_;
_updateJumpRateModel(baseRatePerYear_, multiplierPerYear_, jumpMultiplierPerYear_, kink_);
}
/**
* @notice Update the parameters of the interest rate model
* @param baseRatePerYear The approximate target base APR, as a mantissa (scaled by EXP_SCALE)
* @param multiplierPerYear The rate of increase in interest rate wrt utilization (scaled by EXP_SCALE)
* @param jumpMultiplierPerYear The multiplierPerBlockOrTimestamp after hitting a specified utilization point
* @param kink_ The utilization point at which the jump multiplier is applied
* @custom:error Unauthorized if the sender is not allowed to call this function
* @custom:access Controlled by AccessControlManager
*/
function updateJumpRateModel(
uint256 baseRatePerYear,
uint256 multiplierPerYear,
uint256 jumpMultiplierPerYear,
uint256 kink_
) external virtual {
string memory signature = "updateJumpRateModel(uint256,uint256,uint256,uint256)";
bool isAllowedToCall = accessControlManager.isAllowedToCall(msg.sender, signature);
if (!isAllowedToCall) {
revert Unauthorized(msg.sender, address(this), signature);
}
_updateJumpRateModel(baseRatePerYear, multiplierPerYear, jumpMultiplierPerYear, kink_);
}
/**
* @notice Calculates the current borrow rate per slot (block or second)
* @param cash The amount of cash in the market
* @param borrows The amount of borrows in the market
* @param reserves The amount of reserves in the market
* @param badDebt The amount of badDebt in the market
* @return The borrow rate percentage per slot (block or second) as a mantissa (scaled by 1e18)
*/
function getBorrowRate(
uint256 cash,
uint256 borrows,
uint256 reserves,
uint256 badDebt
) external view override returns (uint256) {
return _getBorrowRate(cash, borrows, reserves, badDebt);
}
/**
* @notice Calculates the current supply rate per slot (block or second)
* @param cash The amount of cash in the market
* @param borrows The amount of borrows in the market
* @param reserves The amount of reserves in the market
* @param reserveFactorMantissa The current reserve factor for the market
* @param badDebt The amount of badDebt in the market
* @return The supply rate percentage per slot (block or second) as a mantissa (scaled by EXP_SCALE)
*/
function getSupplyRate(
uint256 cash,
uint256 borrows,
uint256 reserves,
uint256 reserveFactorMantissa,
uint256 badDebt
) public view virtual override returns (uint256) {
uint256 oneMinusReserveFactor = MANTISSA_ONE - reserveFactorMantissa;
uint256 borrowRate = _getBorrowRate(cash, borrows, reserves, badDebt);
uint256 rateToPool = (borrowRate * oneMinusReserveFactor) / EXP_SCALE;
uint256 incomeToDistribute = borrows * rateToPool;
uint256 supply = cash + borrows + badDebt - reserves;
return incomeToDistribute / supply;
}
/**
* @notice Calculates the utilization rate of the market: `(borrows + badDebt) / (cash + borrows + badDebt - reserves)`
* @param cash The amount of cash in the market
* @param borrows The amount of borrows in the market
* @param reserves The amount of reserves in the market (currently unused)
* @param badDebt The amount of badDebt in the market
* @return The utilization rate as a mantissa between [0, MANTISSA_ONE]
*/
function utilizationRate(
uint256 cash,
uint256 borrows,
uint256 reserves,
uint256 badDebt
) public pure returns (uint256) {
// Utilization rate is 0 when there are no borrows and badDebt
if ((borrows + badDebt) == 0) {
return 0;
}
uint256 rate = ((borrows + badDebt) * EXP_SCALE) / (cash + borrows + badDebt - reserves);
if (rate > EXP_SCALE) {
rate = EXP_SCALE;
}
return rate;
}
/**
* @notice Internal function to update the parameters of the interest rate model
* @param baseRatePerYear The approximate target base APR, as a mantissa (scaled by EXP_SCALE)
* @param multiplierPerYear The rate of increase in interest rate wrt utilization (scaled by EXP_SCALE)
* @param jumpMultiplierPerYear The multiplierPerBlockOrTimestamp after hitting a specified utilization point
* @param kink_ The utilization point at which the jump multiplier is applied
*/
function _updateJumpRateModel(
uint256 baseRatePerYear,
uint256 multiplierPerYear,
uint256 jumpMultiplierPerYear,
uint256 kink_
) internal {
baseRatePerBlock = baseRatePerYear / blocksOrSecondsPerYear;
multiplierPerBlock = multiplierPerYear / blocksOrSecondsPerYear;
jumpMultiplierPerBlock = jumpMultiplierPerYear / blocksOrSecondsPerYear;
kink = kink_;
emit NewInterestParams(baseRatePerBlock, multiplierPerBlock, jumpMultiplierPerBlock, kink);
}
/**
* @notice Calculates the current borrow rate per slot (block or second), with the error code expected by the market
* @param cash The amount of cash in the market
* @param borrows The amount of borrows in the market
* @param reserves The amount of reserves in the market
* @param badDebt The amount of badDebt in the market
* @return The borrow rate percentage per slot (block or second) as a mantissa (scaled by EXP_SCALE)
*/
function _getBorrowRate(
uint256 cash,
uint256 borrows,
uint256 reserves,
uint256 badDebt
) internal view returns (uint256) {
uint256 util = utilizationRate(cash, borrows, reserves, badDebt);
uint256 kink_ = kink;
if (util <= kink_) {
return ((util * multiplierPerBlock) / EXP_SCALE) + baseRatePerBlock;
}
uint256 normalRate = ((kink_ * multiplierPerBlock) / EXP_SCALE) + baseRatePerBlock;
uint256 excessUtil;
unchecked {
excessUtil = util - kink_;
}
return ((excessUtil * jumpMultiplierPerBlock) / EXP_SCALE) + normalRate;
}
}
// SPDX-License-Identifier: BSD-3-Clause
pragma solidity ^0.8.25;
/// @dev The approximate number of seconds per year
uint256 constant SECONDS_PER_YEAR = 31_536_000;
/// @dev Base unit for computations, usually used in scaling (multiplications, divisions)
uint256 constant EXP_SCALE = 1e18;
/// @dev A unit (literal one) in EXP_SCALE, usually used in additions/subtractions
uint256 constant MANTISSA_ONE = EXP_SCALE;