diff --git a/contracts/BaseJumpRateModelV3.sol b/contracts/BaseJumpRateModelV3.sol new file mode 100644 index 000000000..8b5adc25a --- /dev/null +++ b/contracts/BaseJumpRateModelV3.sol @@ -0,0 +1,139 @@ +// SPDX-License-Identifier: BSD-3-Clause +pragma solidity ^0.8.10; + +import "./InterestRateModel.sol"; + +/** + * @title Logic for Compound's JumpRateModel Contract V2. + * @author Compound (modified by Dharma Labs, refactored by Arr00) + * @notice Version 2 modifies Version 1 by enabling updateable parameters. + */ +abstract contract BaseJumpRateModelV3 is InterestRateModel { + event NewInterestParams(uint baseRatePerBlock, uint multiplierPerBlock, uint jumpMultiplierPerBlock, uint kink); + + uint256 private constant BASE = 1e18; + + /** + * @notice The address of the owner, i.e. the Timelock contract, which can update parameters directly + */ + address public owner; + + /** + * @notice The approximate number of blocks per year that is assumed by the interest rate model + * @dev This was calculated accounting for 12-second block times + */ + uint public constant blocksPerYear = 2629800; + + /** + * @notice The multiplier of utilization rate that gives the slope of the interest rate + */ + uint public multiplierPerBlock; + + /** + * @notice The base interest rate which is the y-intercept when utilization rate is 0 + */ + uint public baseRatePerBlock; + + /** + * @notice The multiplierPerBlock after hitting a specified utilization point + */ + uint public jumpMultiplierPerBlock; + + /** + * @notice The utilization point at which the jump multiplier is applied + */ + uint public kink; + + /** + * @notice Construct an interest rate model + * @param baseRatePerYear The approximate target base APR, as a mantissa (scaled by BASE) + * @param multiplierPerYear The rate of increase in interest rate wrt utilization (scaled by BASE) + * @param jumpMultiplierPerYear The multiplierPerBlock after hitting a specified utilization point + * @param kink_ The utilization point at which the jump multiplier is applied + * @param owner_ The address of the owner, i.e. the Timelock contract (which has the ability to update parameters directly) + */ + constructor(uint baseRatePerYear, uint multiplierPerYear, uint jumpMultiplierPerYear, uint kink_, address owner_) internal { + owner = owner_; + + updateJumpRateModelInternal(baseRatePerYear, multiplierPerYear, jumpMultiplierPerYear, kink_); + } + + /** + * @notice Update the parameters of the interest rate model (only callable by owner, i.e. Timelock) + * @param baseRatePerYear The approximate target base APR, as a mantissa (scaled by BASE) + * @param multiplierPerYear The rate of increase in interest rate wrt utilization (scaled by BASE) + * @param jumpMultiplierPerYear The multiplierPerBlock after hitting a specified utilization point + * @param kink_ The utilization point at which the jump multiplier is applied + */ + function updateJumpRateModel(uint baseRatePerYear, uint multiplierPerYear, uint jumpMultiplierPerYear, uint kink_) virtual external { + require(msg.sender == owner, "only the owner may call this function."); + + updateJumpRateModelInternal(baseRatePerYear, multiplierPerYear, jumpMultiplierPerYear, kink_); + } + + /** + * @notice Calculates the utilization rate of the market: `borrows / (cash + borrows - 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) + * @return The utilization rate as a mantissa between [0, BASE] + */ + function utilizationRate(uint cash, uint borrows, uint reserves) public pure returns (uint) { + // Utilization rate is 0 when there are no borrows + if (borrows == 0) { + return 0; + } + + return borrows * BASE / (cash + borrows - reserves); + } + + /** + * @notice Calculates the current borrow rate per block, 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 + * @return The borrow rate percentage per block as a mantissa (scaled by BASE) + */ + function getBorrowRateInternal(uint cash, uint borrows, uint reserves) internal view returns (uint) { + uint util = utilizationRate(cash, borrows, reserves); + + if (util <= kink) { + return ((util * multiplierPerBlock) / BASE) + baseRatePerBlock; + } else { + uint normalRate = ((kink * multiplierPerBlock) / BASE) + baseRatePerBlock; + uint excessUtil = util - kink; + return ((excessUtil * jumpMultiplierPerBlock) / BASE) + normalRate; + } + } + + /** + * @notice Calculates the current supply rate per block + * @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 + * @return The supply rate percentage per block as a mantissa (scaled by BASE) + */ + function getSupplyRate(uint cash, uint borrows, uint reserves, uint reserveFactorMantissa) virtual override public view returns (uint) { + uint oneMinusReserveFactor = BASE - reserveFactorMantissa; + uint borrowRate = getBorrowRateInternal(cash, borrows, reserves); + uint rateToPool = borrowRate * oneMinusReserveFactor / BASE; + return utilizationRate(cash, borrows, reserves) * rateToPool / BASE; + } + + /** + * @notice Internal function to update the parameters of the interest rate model + * @param baseRatePerYear The approximate target base APR, as a mantissa (scaled by BASE) + * @param multiplierPerYear The rate of increase in interest rate wrt utilization (scaled by BASE) + * @param jumpMultiplierPerYear The multiplierPerBlock after hitting a specified utilization point + * @param kink_ The utilization point at which the jump multiplier is applied + */ + function updateJumpRateModelInternal(uint baseRatePerYear, uint multiplierPerYear, uint jumpMultiplierPerYear, uint kink_) internal { + baseRatePerBlock = baseRatePerYear / blocksPerYear; + multiplierPerBlock = (multiplierPerYear * BASE) / (blocksPerYear * kink_); + jumpMultiplierPerBlock = jumpMultiplierPerYear / blocksPerYear; + kink = kink_; + + emit NewInterestParams(baseRatePerBlock, multiplierPerBlock, jumpMultiplierPerBlock, kink); + } +} diff --git a/contracts/DAIInterestRateModelV4.sol b/contracts/DAIInterestRateModelV4.sol index c275fd01d..4c93783da 100644 --- a/contracts/DAIInterestRateModelV4.sol +++ b/contracts/DAIInterestRateModelV4.sol @@ -1,6 +1,6 @@ pragma solidity ^0.8.10; -import "./JumpRateModelV2.sol"; +import "./JumpRateModelV3.sol"; /** * @title Compound's DAIInterestRateModel Contract (version 4) @@ -8,7 +8,7 @@ import "./JumpRateModelV2.sol"; * @notice Version 4 modifies the number of seconds per block to 12, * and takes the stability fee of ETH-B as a reference. */ -contract DAIInterestRateModelV4 is JumpRateModelV2 { +contract DAIInterestRateModelV4 is JumpRateModelV3 { uint256 private constant BASE = 1e18; uint256 private constant RAY_BASE = 1e27; uint256 private constant RAY_TO_BASE_SCALE = 1e9;