Skip to content
This repository has been archived by the owner on Aug 26, 2024. It is now read-only.

Feat/adrastia prudentia caps #79

Merged
merged 27 commits into from
Aug 7, 2024
Merged
Show file tree
Hide file tree
Changes from 13 commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
10c35bf
forge install: adrastia-periphery
TylerEther Aug 2, 2023
8dbe644
Add support for Adrastia Prudentia supply and borrow cap controllers
TylerEther Aug 2, 2023
a5bdae4
Merge branch 'development' into feat/adrastia-prudentia-caps
TylerEther Aug 8, 2023
b95552b
Merge branch 'ionicprotocol:development' into feat/adrastia-prudentia…
TylerEther Aug 15, 2023
2e9bd7a
Add decimal shift support for Adrastia Prudentia cap management
TylerEther Aug 17, 2023
0a53f04
Merge branch 'feat/adrastia-prudentia-caps' of github.com:adrastia-or…
TylerEther Aug 17, 2023
a533fef
Merge branch 'development' into feat/adrastia-prudentia-caps
TylerEther Mar 16, 2024
922ae6e
Use underlying token addresses with the Prudentia cap controller
TylerEther Mar 21, 2024
487e185
Merge branch 'development' into feat/adrastia-prudentia-caps
TylerEther Jul 17, 2024
21619f8
Add PrudentiaInterestRateModel
TylerEther Jul 17, 2024
a933e35
Merge branch 'development' into feat/adrastia-prudentia-caps
TylerEther Jul 20, 2024
1818293
Merge branch 'ionicprotocol:development' into feat/adrastia-prudentia…
TylerEther Jul 26, 2024
a8e3312
fix: deploy prudentia
rhlsthrm Jul 26, 2024
21f90e2
fix: add arg to multisig
rhlsthrm Jul 26, 2024
7a5ab5a
feat: deploy prudentia
rhlsthrm Jul 28, 2024
07e6322
Merge branch 'development' into feat/adrastia-prudentia-caps
rhlsthrm Jul 30, 2024
dfea1dc
feat: prudentia scripts
rhlsthrm Jul 30, 2024
2a6abd2
Improve supply and borrow cap visibility
TylerEther Jul 31, 2024
7c6fe26
Merge branch 'feat/adrastia-prudentia-caps' of github.com:ionicprotoc…
TylerEther Jul 31, 2024
eaa1c64
Revert Comptroller deployment back
TylerEther Jul 31, 2024
bb40da1
Add log to prudentia:config
TylerEther Jul 31, 2024
10c3e43
Remove unnecessary SLOAD
TylerEther Aug 1, 2024
bae6f0e
Fix supply and borrow cap visibility
TylerEther Aug 1, 2024
7c4d392
Add scripts to print Prudentia related info
TylerEther Aug 1, 2024
362a58d
merge
rhlsthrm Aug 7, 2024
224352e
fix: irms
rhlsthrm Aug 7, 2024
5bbaaaa
Merge branch 'development' into feat/adrastia-prudentia-caps
rhlsthrm Aug 7, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@
[submodule "lib/ops"]
path = lib/ops
url = https://github.com/gelatodigital/ops
[submodule "lib/adrastia-periphery"]
path = lib/adrastia-periphery
url = https://github.com/adrastia-oracle/adrastia-periphery
[submodule "lib/flywheel-v2"]
path = lib/flywheel-v2
url = https://github.com/ionicprotocol/flywheel-v2
10 changes: 10 additions & 0 deletions contracts/adrastia/PrudentiaLib.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity >=0.8.0;

library PrudentiaLib {
struct PrudentiaConfig {
address controller; // Adrastia Prudentia controller address
uint8 offset; // Offset for delayed rate activation
int8 decimalShift; // Positive values scale the rate up (in powers of 10), negative values scale the rate down
}
}
81 changes: 79 additions & 2 deletions contracts/compound/Comptroller.sol
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,12 @@ import { IFeeDistributor } from "./IFeeDistributor.sol";
import { IIonicFlywheel } from "../ionic/strategies/flywheel/IIonicFlywheel.sol";
import { DiamondExtension, DiamondBase, LibDiamond } from "../ionic/DiamondExtension.sol";
import { ComptrollerExtensionInterface, ComptrollerBase, ComptrollerInterface } from "./ComptrollerInterface.sol";
import { PrudentiaLib } from "../adrastia/PrudentiaLib.sol";

import "@openzeppelin/contracts/utils/structs/EnumerableSet.sol";

import "adrastia-periphery/rates/IHistoricalRates.sol";

/**
* @title Compound's Comptroller Contract
* @author Compound
Expand Down Expand Up @@ -247,7 +250,45 @@ contract Comptroller is ComptrollerBase, ComptrollerInterface, ComptrollerErrorR
}

// Check supply cap
uint256 supplyCap = supplyCaps[cTokenAddress];
PrudentiaLib.PrudentiaConfig memory capConfig = supplyCapConfig;

uint256 supplyCap;

// Check if we're using Adrastia Prudentia for the supply cap
if (capConfig.controller != address(0)) {
// We have a controller, so we're using Adrastia Prudentia

address underlyingToken = ICErc20(cTokenAddress).underlying();

// Get the supply cap from Adrastia Prudentia
supplyCap = IHistoricalRates(capConfig.controller).getRateAt(underlyingToken, capConfig.offset).current;

// Prudentia trims decimal points from amounts while our code requires the mantissa amount, so we
// must scale the supply cap to get the correct amount

int256 scaleByDecimals = 18;
// Not all ERC20s implement decimals(), so we use a staticcall and check the return data
(bool success, bytes memory data) = underlyingToken.staticcall(abi.encodeWithSignature("decimals()"));
if (success && data.length == 32) {
scaleByDecimals = int256(uint256(abi.decode(data, (uint8))));
}

scaleByDecimals += capConfig.decimalShift;

if (scaleByDecimals >= 0) {
// We're scaling up, so we need to multiply
supplyCap *= 10**uint256(scaleByDecimals);
} else {
// We're scaling down, so we need to divide
supplyCap /= 10**uint256(-scaleByDecimals);
}
} else {
// We don't have a controller, so we're using the local supply cap

// Get the supply cap from the local supply cap
supplyCap = supplyCaps[cTokenAddress];
}

// Supply cap of 0 corresponds to unlimited supplying
if (supplyCap != 0 && !supplyCapWhitelist[cTokenAddress].contains(minter)) {
uint256 totalUnderlyingSupply = ICErc20(cTokenAddress).getTotalUnderlyingSupplied();
Expand Down Expand Up @@ -468,7 +509,43 @@ contract Comptroller is ComptrollerBase, ComptrollerInterface, ComptrollerErrorR
}

// Check borrow cap
uint256 borrowCap = borrowCaps[cToken];
PrudentiaLib.PrudentiaConfig memory capConfig = borrowCapConfig;

uint256 borrowCap;

// Check if we're using Adrastia Prudentia for the borrow cap
if (capConfig.controller != address(0)) {
// We have a controller, so we're using Adrastia Prudentia

address underlyingToken = ICErc20(cToken).underlying();

// Get the borrow cap from Adrastia Prudentia
borrowCap = IHistoricalRates(capConfig.controller).getRateAt(underlyingToken, capConfig.offset).current;

// Prudentia trims decimal points from amounts while our code requires the mantissa amount, so we
// must scale the supply cap to get the correct amount

int256 scaleByDecimals = 18;
// Not all ERC20s implement decimals(), so we use a staticcall and check the return data
(bool success, bytes memory data) = underlyingToken.staticcall(abi.encodeWithSignature("decimals()"));
if (success && data.length == 32) {
scaleByDecimals = int256(uint256(abi.decode(data, (uint8))));
}

scaleByDecimals += capConfig.decimalShift;

if (scaleByDecimals >= 0) {
// We're scaling up, so we need to multiply
borrowCap *= 10**uint256(scaleByDecimals);
} else {
// We're scaling down, so we need to divide
borrowCap /= 10**uint256(-scaleByDecimals);
}
} else {
// We don't have a controller, so we're using the local borrow cap
borrowCap = borrowCaps[cToken];
}

// Borrow cap of 0 corresponds to unlimited borrowing
if (borrowCap != 0 && !borrowCapWhitelist[cToken].contains(borrower)) {
uint256 totalBorrows = ICErc20(cToken).totalBorrowsCurrent();
Expand Down
34 changes: 32 additions & 2 deletions contracts/compound/ComptrollerInterface.sol
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@ pragma solidity >=0.8.0;

import { BasePriceOracle } from "../oracles/BasePriceOracle.sol";
import { ICErc20 } from "./CTokenInterfaces.sol";
import { ComptrollerV3Storage } from "../compound/ComptrollerStorage.sol";
import { DiamondExtension } from "../ionic/DiamondExtension.sol";
import { ComptrollerV4Storage } from "../compound/ComptrollerStorage.sol";
import { PrudentiaLib } from "../adrastia/PrudentiaLib.sol";

interface ComptrollerInterface {
function isDeprecated(ICErc20 cToken) external view returns (bool);
Expand Down Expand Up @@ -314,6 +316,34 @@ interface ComptrollerExtensionInterface {
function registerInSFS() external returns (uint256);
}

interface ComptrollerPrudentiaCapsExtInterface {
/**
* @notice Retrieves Adrastia Prudentia borrow cap config from storage.
* @return The config.
*/
function getBorrowCapConfig() external view returns (PrudentiaLib.PrudentiaConfig memory);

/**
* @notice Retrieves Adrastia Prudentia supply cap config from storage.
* @return The config.
*/
function getSupplyCapConfig() external view returns (PrudentiaLib.PrudentiaConfig memory);

/**
* @notice Sets the Adrastia Prudentia supply cap config.
* @dev Specifying a zero address for the `controller` parameter will make the Comptroller use the native supply caps.
* @param newConfig The new config.
*/
function _setSupplyCapConfig(PrudentiaLib.PrudentiaConfig calldata newConfig) external;

/**
* @notice Sets the Adrastia Prudentia supply cap config.
* @dev Specifying a zero address for the `controller` parameter will make the Comptroller use the native borrow caps.
* @param newConfig The new config.
*/
function _setBorrowCapConfig(PrudentiaLib.PrudentiaConfig calldata newConfig) external;
}

interface UnitrollerInterface {
function comptrollerImplementation() external view returns (address);

Expand All @@ -339,7 +369,7 @@ interface IonicComptroller is

}

abstract contract ComptrollerBase is ComptrollerV3Storage {
abstract contract ComptrollerBase is ComptrollerV4Storage {
/// @notice Indicator that this is a Comptroller contract (for inspection)
bool public constant isComptroller = true;
}
69 changes: 69 additions & 0 deletions contracts/compound/ComptrollerPrudentiaCapsExt.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity >=0.8.0;

import { DiamondExtension } from "../ionic/DiamondExtension.sol";
import { ICErc20 } from "./CTokenInterfaces.sol";
import { ComptrollerPrudentiaCapsExtInterface, ComptrollerBase } from "./ComptrollerInterface.sol";
import { PrudentiaLib } from "../adrastia/PrudentiaLib.sol";

/**
* @title ComptrollerPrudentiaCapsExt
* @author Tyler Loewen (TRILEZ SOFTWARE INC. dba. Adrastia)
* @notice A diamond extension that allows the Comptroller to use Adrastia Prudentia to control supply and borrow caps.
*/
contract ComptrollerPrudentiaCapsExt is DiamondExtension, ComptrollerBase, ComptrollerPrudentiaCapsExtInterface {
/**
* @notice Emitted when the Adrastia Prudentia supply cap config is changed.
* @param oldConfig The old config.
* @param newConfig The new config.
*/
event NewSupplyCapConfig(PrudentiaLib.PrudentiaConfig oldConfig, PrudentiaLib.PrudentiaConfig newConfig);

/**
* @notice Emitted when the Adrastia Prudentia borrow cap config is changed.
* @param oldConfig The old config.
* @param newConfig The new config.
*/
event NewBorrowCapConfig(PrudentiaLib.PrudentiaConfig oldConfig, PrudentiaLib.PrudentiaConfig newConfig);

/// @inheritdoc ComptrollerPrudentiaCapsExtInterface
function _setSupplyCapConfig(PrudentiaLib.PrudentiaConfig calldata newConfig) external {
require(msg.sender == admin || msg.sender == borrowCapGuardian, "!admin");

PrudentiaLib.PrudentiaConfig memory oldConfig = supplyCapConfig;
supplyCapConfig = newConfig;

emit NewSupplyCapConfig(oldConfig, newConfig);
}

/// @inheritdoc ComptrollerPrudentiaCapsExtInterface
function _setBorrowCapConfig(PrudentiaLib.PrudentiaConfig calldata newConfig) external {
require(msg.sender == admin || msg.sender == borrowCapGuardian, "!admin");

PrudentiaLib.PrudentiaConfig memory oldConfig = borrowCapConfig;
borrowCapConfig = newConfig;

emit NewBorrowCapConfig(oldConfig, newConfig);
}

/// @inheritdoc ComptrollerPrudentiaCapsExtInterface
function getBorrowCapConfig() external view returns (PrudentiaLib.PrudentiaConfig memory) {
return borrowCapConfig;
}

/// @inheritdoc ComptrollerPrudentiaCapsExtInterface
function getSupplyCapConfig() external view returns (PrudentiaLib.PrudentiaConfig memory) {
return supplyCapConfig;
}

function _getExtensionFunctions() external pure virtual override returns (bytes4[] memory) {
uint8 fnsCount = 4;
bytes4[] memory functionSelectors = new bytes4[](fnsCount);
functionSelectors[--fnsCount] = this._setSupplyCapConfig.selector;
functionSelectors[--fnsCount] = this._setBorrowCapConfig.selector;
functionSelectors[--fnsCount] = this.getBorrowCapConfig.selector;
functionSelectors[--fnsCount] = this.getSupplyCapConfig.selector;
require(fnsCount == 0, "use the correct array length");
return functionSelectors;
}
}
9 changes: 9 additions & 0 deletions contracts/compound/ComptrollerStorage.sol
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ pragma solidity >=0.8.0;
import "./IFeeDistributor.sol";
import "../oracles/BasePriceOracle.sol";
import { ICErc20 } from "./CTokenInterfaces.sol";
import { PrudentiaLib } from "../adrastia/PrudentiaLib.sol";

import "@openzeppelin/contracts/utils/structs/EnumerableSet.sol";

Expand Down Expand Up @@ -175,3 +176,11 @@ contract ComptrollerV3Storage is ComptrollerV2Storage {
/// @dev set of whitelisted accounts that are allowed to bypass the borrow cap
mapping(address => EnumerableSet.AddressSet) internal borrowCapWhitelist;
}

contract ComptrollerV4Storage is ComptrollerV3Storage {
/// @dev Adrastia Prudentia config for controlling borrow caps.
PrudentiaLib.PrudentiaConfig internal borrowCapConfig;

/// @dev Adrastia Prudentia config for controlling supply caps.
PrudentiaLib.PrudentiaConfig internal supplyCapConfig;
}
126 changes: 126 additions & 0 deletions contracts/ionic/irms/PrudentiaInterestRateModel.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity >=0.8.0;

import { InterestRateModel } from "../../compound/InterestRateModel.sol";

import { IRateComputer } from "adrastia-periphery/rates/IRateComputer.sol";

import { Math } from "@openzeppelin/contracts/utils/math/Math.sol";

/**
* @title Adrastia Prudentia Interest Rate Model
* @author TRILEZ SOFTWARE INC.
*/
contract PrudentiaInterestRateModel is InterestRateModel {
using Math for uint256;

/**
* @notice The address of the underlying token for which the interest rate model calculates rates.
*/
address public immutable underlyingToken;

/**
* @notice The address of the Adrastia Prudentia interest rate controller.
*/
IRateComputer public immutable rateController;

/**
* @notice The approximate number of blocks per year that is assumed by the interest rate model.
*/
uint256 public immutable blocksPerYear;

/**
* @notice Construct a new interest rate model that reads from an Adrastia Prudentia interest rate controller.
*
* @param blocksPerYear_ The approximate number of blocks per year that is assumed by the interest rate model.
* @param underlyingToken_ The address of the underlying token for which the interest rate model calculates rates.
* @param rateController_ The address of the Adrastia Prudentia interest rate controller.
*/
constructor(
uint256 blocksPerYear_,
address underlyingToken_,
IRateComputer rateController_
) {
if (underlyingToken_ == address(0)) {
revert("PrudentiaInterestRateModel: underlyingToken is the zero address");
}
if (address(rateController_) == address(0)) {
revert("PrudentiaInterestRateModel: rateController is the zero address");
}

blocksPerYear = blocksPerYear_;
underlyingToken = underlyingToken_;
rateController = rateController_;
}

/**
* @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.
*
* @return The utilization rate as a mantissa between [0, 1e18].
*/
function utilizationRate(
uint256 cash,
uint256 borrows,
uint256 reserves
) public pure returns (uint256) {
uint256 total = cash + borrows - reserves;
if (total == 0) {
// Utilization rate is zero when nothing is available (prevents division by zero)
return 0;
}

return (borrows * 1e18) / total;
}

/**
* @notice Calculates the current borrow rate per block by reading the current rate from the Adrastia Prudentia
* interest rate controller.
*
* @param cash Not used.
* @param borrows Not used.
* @param reserves Not used.
*
* @return The borrow rate percentage per block as a mantissa (scaled by 1e18).
*/
function getBorrowRate(
uint256 cash,
uint256 borrows,
uint256 reserves
) public view override returns (uint256) {
// Silence unused variable warnings
cash;
borrows;
reserves;

uint256 annualRate = rateController.computeRate(underlyingToken);

return annualRate.ceilDiv(blocksPerYear); // Convert the annual rate to a per-block rate, rounding up
}

/**
* @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 1e18).
*/
function getSupplyRate(
uint256 cash,
uint256 borrows,
uint256 reserves,
uint256 reserveFactorMantissa
) public view virtual override returns (uint256) {
uint256 oneMinusReserveFactor = 1e18 - reserveFactorMantissa;
uint256 borrowRate = getBorrowRate(cash, borrows, reserves);
uint256 rateToPool = (borrowRate * oneMinusReserveFactor) / 1e18;

return (utilizationRate(cash, borrows, reserves) * rateToPool) / 1e18;
}
}
Loading
Loading