Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

M-07, M-13, M-15, L-02, CR-01 (OZ Audit) #27

Merged
merged 7 commits into from
Jan 18, 2024
20,000 changes: 20,000 additions & 0 deletions fuzz_out.txt

Large diffs are not rendered by default.

211 changes: 106 additions & 105 deletions src/IonPool.sol
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,18 @@ import { SpotOracle } from "src/oracles/spot/SpotOracle.sol";
import { RewardModule } from "src/reward/RewardModule.sol";
import { InterestRate } from "src/InterestRate.sol";
import { WadRayMath, RAY } from "src/libraries/math/WadRayMath.sol";
import { IonPausableUpgradeable } from "src/admin/IonPausableUpgradeable.sol";

import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";

import { SafeCast } from "@openzeppelin/contracts/utils/math/SafeCast.sol";
import { EnumerableSet } from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol";
import { Math } from "@openzeppelin/contracts/utils/math/Math.sol";
import { PausableUpgradeable } from "@openzeppelin/contracts-upgradeable/utils/PausableUpgradeable.sol";

contract IonPool is IonPausableUpgradeable, RewardModule {
import { safeconsole as console } from "forge-std/safeconsole.sol";

contract IonPool is PausableUpgradeable, RewardModule {
using SafeERC20 for IERC20;
using SafeCast for *;
using WadRayMath for *;
Expand Down Expand Up @@ -309,46 +311,20 @@ contract IonPool is IonPausableUpgradeable, RewardModule {
}

/**
* @dev Pause actions that put the protocol into a further unsafe state.
* These are actions that take liquidity out of the system (e.g. borrowing,
* withdrawing base)
*/
function pauseUnsafeActions() external onlyRole(ION) {
_pause(Pauses.UNSAFE);
}

/**
* @dev Unpause actions that put the protocol into a further unsafe state.
* @dev Pause actions but accrue interest as well.
*/
function unpauseUnsafeActions() external onlyRole(ION) {
_unpause(Pauses.UNSAFE);
}

/**
* @dev Pause actions that put the protocol into a further safe state.
* These are actions that put liquidity into the system (e.g. repaying,
* depositing base)
*
* Pausing accrual is also necessary with this since disabling repaying
* should not continue to accrue interest.
*
* Also accrues interest before the pause to update all debt at the time the
* pause takes place.
*/
function pauseSafeActions() external onlyRole(ION) {
_pause(Pauses.SAFE);
function pause() external onlyRole(ION) {
_accrueInterest();
_pause();
}

/**
* @dev Unpause actions that put the protocol into a further safe state.
*
* Will also update the `lastRateUpdate` to the unpause transaction
* timestamp. This essentially allows for a pausing and unpausing of the
* accrual of interest.
* @dev Unpause actions but this will also update the `lastRateUpdate` to
* the unpause transaction timestamp. This essentially allows for a pausing
* and unpausing of the accrual of interest.
*/
function unpauseSafeActions() external onlyRole(ION) {
_unpause(Pauses.SAFE);
function unpause() external onlyRole(ION) {
_unpause();
IonPoolStorage storage $ = _getIonPoolStorage();

uint256 ilksLength = $.ilks.length;
Expand All @@ -367,20 +343,14 @@ contract IonPool is IonPausableUpgradeable, RewardModule {
* @dev Updates accumulators for all `ilk`s based on current interest rates.
* @return newTotalDebt the new total debt after interest accrual
*/
function accrueInterest() external whenNotPaused(Pauses.SAFE) returns (uint256) {
function accrueInterest() external whenNotPaused returns (uint256 newTotalDebt) {
return _accrueInterest();
}

function _accrueInterest() internal returns (uint256 newTotalDebt) {
IonPoolStorage storage $ = _getIonPoolStorage();

// Safe actions should really only be paused in conjunction with unsafe
// actions. However, if for some reason only safe actions were paused,
// it would still be possible to accrue interest by withdrawing and/or
// borrowing... so we prevent this outcome; but without reverting the tx
// altogether.
if (paused(Pauses.SAFE)) return ($.debt);
uint256 totalEthSupply = totalSupply();
uint256 totalEthSupply = totalSupplyUnaccrued();

uint256 totalSupplyFactorIncrease;
uint256 totalTreasuryMintAmount;
Expand All @@ -394,7 +364,7 @@ contract IonPool is IonPausableUpgradeable, RewardModule {
uint104 newRateIncrease,
uint256 newDebtIncrease,
uint48 timestampIncrease
) = _calculateRewardAndDebtDistribution(i, totalEthSupply);
) = _calculateRewardAndDebtDistributionForIlk(i, totalEthSupply);

if (timestampIncrease > 0) {
Ilk storage ilk = $.ilks[i];
Expand All @@ -412,33 +382,71 @@ contract IonPool is IonPausableUpgradeable, RewardModule {

newTotalDebt = $.debt + totalDebtIncrease;
$.debt = newTotalDebt;
_setSupplyFactor(supplyFactor() + totalSupplyFactorIncrease);
_setSupplyFactor(supplyFactorUnaccrued() + totalSupplyFactorIncrease);
_mintToTreasury(totalTreasuryMintAmount);
}

function _accrueInterestForIlk(uint8 ilkIndex) internal {
(
uint256 supplyFactorIncrease,
uint256 treasuryMintAmount,
uint104 newRateIncrease,
uint256 newDebtIncrease,
uint48 timestampIncrease
) = _calculateRewardAndDebtDistribution(ilkIndex, totalSupply());

function calculateRewardAndDebtDistribution()
public
view
override
returns (
uint256 totalSupplyFactorIncrease,
uint256 totalTreasuryMintAmount,
uint104[] memory rateIncreases,
uint256 totalDebtIncrease,
uint48[] memory timestampIncreases
)
{
IonPoolStorage storage $ = _getIonPoolStorage();

if (timestampIncrease > 0) {
Ilk storage ilk = $.ilks[ilkIndex];
ilk.rate += newRateIncrease;
ilk.lastRateUpdate += timestampIncrease;
$.debt += newDebtIncrease;
uint256 ilksLength = $.ilks.length;

rateIncreases = new uint104[](ilksLength);
timestampIncreases = new uint48[](ilksLength);

_setSupplyFactor(supplyFactor() + supplyFactorIncrease);
_mintToTreasury(treasuryMintAmount);
uint256 totalEthSupply = totalSupplyUnaccrued();

for (uint8 i = 0; i < ilksLength;) {
(
uint256 supplyFactorIncrease,
uint256 treasuryMintAmount,
uint104 newRateIncrease,
uint256 newDebtIncrease,
uint48 timestampIncrease
) = _calculateRewardAndDebtDistributionForIlk(i, totalEthSupply);

if (timestampIncrease > 0) {
rateIncreases[i] = newRateIncrease;
timestampIncreases[i] = timestampIncrease;
totalDebtIncrease += newDebtIncrease;

totalSupplyFactorIncrease += supplyFactorIncrease;
totalTreasuryMintAmount += treasuryMintAmount;
}

// forgefmt: disable-next-line
unchecked { ++i; }
}
}

function _calculateRewardAndDebtDistribution(
/**
* @notice This is primarily for simulation purposes to see how an
* individual ilk's state will change after an accrual.
* @param ilkIndex index of the collateral.
* @return newRateIncrease the rate increase for the ilk.
* @return timestampIncrease the timestamp increase for the ilk.
*/
function calculateRewardAndDebtDistributionForIlk(uint8 ilkIndex)
public
view
returns (uint104 newRateIncrease, uint48 timestampIncrease)
{
(,, newRateIncrease,, timestampIncrease) =
_calculateRewardAndDebtDistributionForIlk(ilkIndex, totalSupplyUnaccrued());
}

function _calculateRewardAndDebtDistributionForIlk(
uint8 ilkIndex,
uint256 totalEthSupply
)
Expand Down Expand Up @@ -490,7 +498,7 @@ contract IonPool is IonPausableUpgradeable, RewardModule {
newDebtIncrease = _totalNormalizedDebt * newRateIncrease; // [RAD]

// Income distribution
uint256 _normalizedTotalSupply = normalizedTotalSupply(); // [WAD]
uint256 _normalizedTotalSupply = normalizedTotalSupplyUnaccrued(); // [WAD]

// If there is no supply, then nothing is being lent out.
supplyFactorIncrease = _normalizedTotalSupply == 0
Expand All @@ -511,7 +519,7 @@ contract IonPool is IonPausableUpgradeable, RewardModule {
* asset should be sent to.
* @param amount of underlying to reedeem for.
*/
function withdraw(address receiverOfUnderlying, uint256 amount) external whenNotPaused(Pauses.UNSAFE) {
function withdraw(address receiverOfUnderlying, uint256 amount) external whenNotPaused {
uint256 newTotalDebt = _accrueInterest();
IonPoolStorage storage $ = _getIonPoolStorage();

Expand All @@ -536,7 +544,7 @@ contract IonPool is IonPausableUpgradeable, RewardModule {
bytes32[] calldata proof
)
external
whenNotPaused(Pauses.SAFE)
whenNotPaused
onlyWhitelistedLenders(user, proof)
{
uint256 newTotalDebt = _accrueInterest();
Expand Down Expand Up @@ -570,10 +578,10 @@ contract IonPool is IonPausableUpgradeable, RewardModule {
bytes32[] memory proof
)
external
whenNotPaused(Pauses.UNSAFE)
whenNotPaused
onlyWhitelistedBorrowers(ilkIndex, user, proof)
{
_accrueInterestForIlk(ilkIndex);
_accrueInterest();
(uint104 ilkRate, uint256 newDebt) =
_modifyPosition(ilkIndex, user, address(0), recipient, 0, amountOfNormalizedDebt.toInt256());

Expand All @@ -594,9 +602,9 @@ contract IonPool is IonPausableUpgradeable, RewardModule {
uint256 amountOfNormalizedDebt
)
external
whenNotPaused(Pauses.SAFE)
whenNotPaused
{
_accrueInterestForIlk(ilkIndex);
_accrueInterest();
(uint104 ilkRate, uint256 newDebt) =
_modifyPosition(ilkIndex, user, address(0), payer, 0, -(amountOfNormalizedDebt.toInt256()));

Expand All @@ -617,8 +625,9 @@ contract IonPool is IonPausableUpgradeable, RewardModule {
uint256 amount
)
external
whenNotPaused(Pauses.UNSAFE)
whenNotPaused
{
_accrueInterest();
_modifyPosition(ilkIndex, user, recipient, address(0), -(amount.toInt256()), 0);

emit WithdrawCollateral(ilkIndex, user, recipient, amount);
Expand All @@ -640,9 +649,10 @@ contract IonPool is IonPausableUpgradeable, RewardModule {
bytes32[] calldata proof
)
external
whenNotPaused(Pauses.SAFE)
whenNotPaused
onlyWhitelistedBorrowers(ilkIndex, user, proof)
{
_accrueInterest();
_modifyPosition(ilkIndex, user, depositor, address(0), amount.toInt256(), 0);

emit DepositCollateral(ilkIndex, user, depositor, amount);
Expand Down Expand Up @@ -739,7 +749,7 @@ contract IonPool is IonPausableUpgradeable, RewardModule {
* @param user the address that owns the bad debt being paid off
* @param rad amount of debt to be repaid (45 decimals)
*/
function repayBadDebt(address user, uint256 rad) external whenNotPaused(Pauses.SAFE) {
function repayBadDebt(address user, uint256 rad) external whenNotPaused {
IonPoolStorage storage $ = _getIonPoolStorage();

$.unbackedDebt[user] -= rad;
Expand Down Expand Up @@ -801,9 +811,10 @@ contract IonPool is IonPausableUpgradeable, RewardModule {
int256 changeInNormalizedDebt
)
external
whenNotPaused
onlyRole(LIQUIDATOR_ROLE)
{
_accrueInterestForIlk(ilkIndex);
_accrueInterest();

IonPoolStorage storage $ = _getIonPoolStorage();

Expand Down Expand Up @@ -834,14 +845,7 @@ contract IonPool is IonPausableUpgradeable, RewardModule {
* @param usr user
* @param wad amount to add or remove
*/
function mintAndBurnGem(
uint8 ilkIndex,
address usr,
int256 wad
)
external
onlyRole(GEM_JOIN_ROLE)
{
function mintAndBurnGem(uint8 ilkIndex, address usr, int256 wad) external onlyRole(GEM_JOIN_ROLE) {
IonPoolStorage storage $ = _getIonPoolStorage();

$.gem[ilkIndex][usr] = _add($.gem[ilkIndex][usr], wad);
Expand All @@ -856,7 +860,7 @@ contract IonPool is IonPausableUpgradeable, RewardModule {
* @param dst destination of the gem
* @param wad amount of gem
*/
function transferGem(uint8 ilkIndex, address src, address dst, uint256 wad) external whenNotPaused(Pauses.UNSAFE) {
function transferGem(uint8 ilkIndex, address src, address dst, uint256 wad) external whenNotPaused {
if (!isAllowed(src, _msgSender())) revert GemTransferWithoutConsent(ilkIndex, src, _msgSender());

IonPoolStorage storage $ = _getIonPoolStorage();
Expand Down Expand Up @@ -913,12 +917,20 @@ contract IonPool is IonPausableUpgradeable, RewardModule {
return $.ilks[ilkIndex].totalNormalizedDebt;
}

function rateUnaccrued(uint8 ilkIndex) external view returns (uint256) {
IonPoolStorage storage $ = _getIonPoolStorage();
return $.ilks[ilkIndex].rate;
}

/**
* @return The rate (debt accumulator) for collateral with index `ilkIndex`.
*/
function rate(uint8 ilkIndex) external view returns (uint256) {
IonPoolStorage storage $ = _getIonPoolStorage();
return $.ilks[ilkIndex].rate;

(uint256 newRateIncrease,) = calculateRewardAndDebtDistributionForIlk(ilkIndex);

return $.ilks[ilkIndex].rate + newRateIncrease;
}

/**
Expand Down Expand Up @@ -1012,13 +1024,21 @@ contract IonPool is IonPausableUpgradeable, RewardModule {
return either(user == operator, $.isOperator[user][operator] == 1);
}

function debtUnaccrued() external view returns (uint256) {
IonPoolStorage storage $ = _getIonPoolStorage();
return $.debt;
}

/**
* @dev This includes unbacked debt.
* @return The total amount of debt.
*/
function debt() external view returns (uint256) {
IonPoolStorage storage $ = _getIonPoolStorage();
return $.debt;

(,,, uint256 totalDebtIncrease,) = calculateRewardAndDebtDistribution();

return $.debt + totalDebtIncrease;
}

/**
Expand Down Expand Up @@ -1059,7 +1079,7 @@ contract IonPool is IonPausableUpgradeable, RewardModule {
function getCurrentBorrowRate(uint8 ilkIndex) external view returns (uint256 borrowRate, uint256 reserveFactor) {
IonPoolStorage storage $ = _getIonPoolStorage();

uint256 totalEthSupply = totalSupply();
uint256 totalEthSupply = totalSupplyUnaccrued();
uint256 _totalNormalizedDebt = $.ilks[ilkIndex].totalNormalizedDebt;
uint256 _rate = $.ilks[ilkIndex].rate;

Expand All @@ -1069,25 +1089,6 @@ contract IonPool is IonPausableUpgradeable, RewardModule {
borrowRate += RAY;
}

/**
* @dev Calculates the increase in debt and supply factors for a given
* `ilkIndex` should it's interest be accrued.
*
*/
function calculateRewardAndDebtDistribution(uint8 ilkIndex)
external
view
returns (
uint256 supplyFactorIncrease,
uint256 treasuryMintAmount,
uint104 newRateIncrease,
uint256 newDebtIncrease,
uint48 newTimestampIncrease
)
{
return _calculateRewardAndDebtDistribution(ilkIndex, totalSupply());
}

/**
* @dev Address of the implementation. This is stored immutably on the
* implementation so that it can be read by the proxy.
Expand Down