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

Openzeppelin synth fixes #1

Merged
merged 34 commits into from
Jul 30, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
200ed48
restrict function visibility
MickdeGraaf Jun 4, 2024
cb446be
Named parameters in minters mapping
MickdeGraaf Jun 5, 2024
4884946
Use named imports
MickdeGraaf Jun 5, 2024
d6b6548
gas + code improvements
MickdeGraaf Jun 5, 2024
b3d074f
typo fix
MickdeGraaf Jun 5, 2024
9b79936
Emit events in state changing functions
MickdeGraaf Jun 5, 2024
807ecb7
Only gulp when sufficient shares are minted
MickdeGraaf Jun 6, 2024
4504852
Test gulp threshold
MickdeGraaf Jun 6, 2024
b10e73c
Allow withdraws and redeems when a controller is set
MickdeGraaf Jun 6, 2024
ddbd121
Add missing docstrings
MickdeGraaf Jun 4, 2024
0ffc50a
remove _withdraw override and throw original errors on redeem and wit…
MickdeGraaf Jun 6, 2024
7c56d35
Merge pull request #14 from euler-xyz/fix-OZ-Synth-L-03-final
MickdeGraaf Jul 2, 2024
afd1d41
Merge pull request #13 from euler-xyz/fix/OZ-Synths-CR-02
MickdeGraaf Jul 2, 2024
6f23ba7
Merge branch 'openzeppelin-synth-fixes' into fix/OZ-Synths-CR-01
MickdeGraaf Jul 2, 2024
0b43778
Merge pull request #12 from euler-xyz/fix/OZ-Synths-CR-01
MickdeGraaf Jul 2, 2024
e4d7c1d
Merge pull request #11 from euler-xyz/fix/OZ-Synths-N-11
MickdeGraaf Jul 2, 2024
fb336db
Merge pull request #10 from euler-xyz/fix/OZ-Synths-N-09
MickdeGraaf Jul 2, 2024
2ffe51c
Merge pull request #9 from euler-xyz/fix/OZ-Synths-N-08
MickdeGraaf Jul 2, 2024
141e528
Merge pull request #8 from euler-xyz/fix/OZ-Synth-N-06
MickdeGraaf Jul 2, 2024
9df0b7c
Merge branch 'openzeppelin-synth-fixes' into fix/OZ-Synth-N-05
MickdeGraaf Jul 2, 2024
8bcb032
Merge pull request #7 from euler-xyz/fix/OZ-Synth-N-05
MickdeGraaf Jul 2, 2024
561af00
Switch to named imports
MickdeGraaf Jun 5, 2024
818e1b2
Merge pull request #6 from euler-xyz/fix/OZ-Synth-N-03
MickdeGraaf Jul 2, 2024
06c82e7
Merge branch 'openzeppelin-synth-fixes' into fix/OZ-Synth-N-01-final
MickdeGraaf Jul 2, 2024
842c53d
Merge pull request #5 from euler-xyz/fix/OZ-Synth-N-01-final
MickdeGraaf Jul 2, 2024
7bd80ef
docstring fixes
MickdeGraaf Jun 4, 2024
439896f
formatting fix
MickdeGraaf Jul 2, 2024
d137421
Merge pull request #1 from euler-xyz/fix/OZ-Synth-L-02
MickdeGraaf Jul 2, 2024
d5ca0a2
Merge branch 'openzeppelin-synth-fixes' of https://github.com/euler-x…
kasperpawlowski Jul 2, 2024
c2779f2
chore: formatting
kasperpawlowski Jul 2, 2024
ca5fca5
fix: OZ L-04 (continuation)
kasperpawlowski Jul 3, 2024
52b1fb0
fix: formatting
kasperpawlowski Jul 3, 2024
3c22922
test: fix testFuzz_gulp_under_uint168
kasperpawlowski Jul 3, 2024
d57b84e
fix: formatting
kasperpawlowski Jul 3, 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
24 changes: 15 additions & 9 deletions src/Synths/ESynth.sol
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,14 @@ contract ESynth is ERC20Collateral, Ownable {
uint128 minted;
}

/// @notice contains the minting capacity and minted amount for each minter.
mapping(address => MinterData) public minters;
/// @notice contains the list of addresses to ignore for the total supply.
EnumerableSet.AddressSet internal ignoredForTotalSupply;

/// @notice Emitted when the minting capacity for a minter is set.
/// @param minter The address of the minter.
/// @param capacity The capacity set for the minter.
event MinterCapacitySet(address indexed minter, uint256 capacity);

error E_CapacityReached();
Expand Down Expand Up @@ -121,8 +126,8 @@ contract ESynth is ERC20Collateral, Ownable {
/// @dev Overriden due to the conflict with the Context definition.
/// @dev This function returns the account on behalf of which the current operation is being performed, which is
/// either msg.sender or the account authenticated by the EVC.
/// @return The address of the message sender.
function _msgSender() internal view virtual override (ERC20Collateral, Context) returns (address) {
/// @return msgSender The address of the message sender.
function _msgSender() internal view virtual override (ERC20Collateral, Context) returns (address msgSender) {
return ERC20Collateral._msgSender();
}

Expand All @@ -144,24 +149,25 @@ contract ESynth is ERC20Collateral, Ownable {

/// @notice Checks if an account is ignored for the total supply.
/// @param account The account to check.
function isIgnoredForTotalSupply(address account) public view returns (bool) {
/// @return isIgnored True if the account is ignored for the total supply. False otherwise.
function isIgnoredForTotalSupply(address account) external view returns (bool isIgnored) {
return ignoredForTotalSupply.contains(account);
}

/// @notice Retrieves all the accounts ignored for the total supply.
/// @return The list of accounts ignored for the total supply.
function getAllIgnoredForTotalSupply() public view returns (address[] memory) {
/// @return accounts List of accounts ignored for the total supply.
function getAllIgnoredForTotalSupply() external view returns (address[] memory accounts) {
return ignoredForTotalSupply.values();
}

/// @notice Retrieves the total supply of the token.
/// @dev Overriden to exclude the ignored accounts from the total supply.
/// @return The total supply of the token.
function totalSupply() public view override returns (uint256) {
uint256 total = super.totalSupply();
/// @return total Total supply of the token.
function totalSupply() public view override returns (uint256 total) {
total = super.totalSupply();

uint256 ignoredLength = ignoredForTotalSupply.length(); // cache for efficiency
for (uint256 i = 0; i < ignoredLength; i++) {
for (uint256 i = 0; i < ignoredLength; ++i) {
total -= balanceOf(ignoredForTotalSupply.at(i));
}
return total;
Expand Down
74 changes: 60 additions & 14 deletions src/Synths/EulerSavingsRate.sol
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,11 @@ contract EulerSavingsRate is EVCUtil, ERC4626 {
uint8 internal constant UNLOCKED = 1;
uint8 internal constant LOCKED = 2;

/// @notice The virtual amount added to total shares and total assets.
uint256 internal constant VIRTUAL_AMOUNT = 1e6;
/// @notice At least 10 times the virtual amount of shares should exist for gulp to be enabled
uint256 internal constant MIN_SHARES_FOR_GULP = VIRTUAL_AMOUNT * 10;

uint256 public constant INTEREST_SMEAR = 2 weeks;

struct ESRSlot {
Expand All @@ -34,11 +38,16 @@ contract EulerSavingsRate is EVCUtil, ERC4626 {
uint8 locked;
}

/// @notice Multiple state variables stored in a single storage slot.
ESRSlot internal esrSlot;
/// @notice The total assets accounted for in the vault.
uint256 internal _totalAssets;

error Reentrancy();

event Gulped(uint256 gulped, uint256 interestLeft);
event InterestUpdated(uint256 interestAccrued, uint256 interestLeft);

/// @notice Modifier to require an account status check on the EVC.
/// @dev Calls `requireAccountStatusCheck` function from EVC for the specified account after the function body.
/// @param account The address of the account to check.
Expand Down Expand Up @@ -69,6 +78,11 @@ contract EulerSavingsRate is EVCUtil, ERC4626 {
return _totalAssets + interestAccrued();
}

/// @notice Returns the maximum amount of shares that can be redeemed by the specified address.
/// @dev If the account has a controller set it's possible the withdrawal will be reverted by the controller, thus
/// we return 0.
/// @param owner The account owner.
/// @return The maximum amount of shares that can be redeemed.
function maxRedeem(address owner) public view override returns (uint256) {
// If account has borrows, withdrawal might be reverted by the controller during account status checks.
// The vault has no way to verify or enforce the behaviour of the controller, which the account owner
Expand All @@ -83,6 +97,11 @@ contract EulerSavingsRate is EVCUtil, ERC4626 {
return super.maxRedeem(owner);
}

/// @notice Returns the maximum amount of assets that can be withdrawn by the specified address.
/// @dev If the account has a controller set it's possible the withdrawal will be reverted by the controller, thus
/// we return 0.
/// @param owner The account owner.
/// @return The maximum amount of assets that can be withdrawn.
function maxWithdraw(address owner) public view override returns (uint256) {
// If account has borrows, withdrawal might be reverted by the controller during account status checks.
// The vault has no way to verify or enforce the behaviour of the controller, which the account owner
Expand Down Expand Up @@ -137,14 +156,17 @@ contract EulerSavingsRate is EVCUtil, ERC4626 {
/// @notice Mints a certain amount of shares to the account.
/// @param shares The amount of assets to mint.
/// @param receiver The account to mint the shares to.
/// @return The amount of assets spend.
/// @return The amount of assets spent.
function mint(uint256 shares, address receiver) public override nonReentrant returns (uint256) {
return super.mint(shares, receiver);
}

/// @notice Deposits a certain amount of assets to the vault.
/// @param assets The amount of assets to deposit.
/// @notice Withdraws a certain amount of assets to the vault.
/// @dev Overwritten to not call maxWithdraw which would return 0 if there is a controller set, update the accrued
/// interest and update _totalAssets.
/// @param assets The amount of assets to withdraw.
/// @param receiver The recipient of the shares.
/// @param owner The account from which the assets are withdrawn
/// @return The amount of shares minted.
function withdraw(uint256 assets, address receiver, address owner)
public
Expand All @@ -155,12 +177,25 @@ contract EulerSavingsRate is EVCUtil, ERC4626 {
{
// Move interest to totalAssets
updateInterestAndReturnESRSlotCache();
return super.withdraw(assets, receiver, owner);

uint256 maxAssets = _convertToAssets(balanceOf(owner), Math.Rounding.Floor);
if (maxAssets < assets) {
revert ERC4626ExceededMaxWithdraw(owner, assets, maxAssets);
}

uint256 shares = previewWithdraw(assets);
_withdraw(_msgSender(), receiver, owner, assets, shares);
_totalAssets = _totalAssets - assets;
Comment on lines +187 to +188
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't _deposit() also be updated to match the pattern here?

function _deposit(address caller, address receiver, uint256 assets, uint256 shares) internal override {
(as suggested also in the OZ audit report)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it seems that OZ missed that when reviewing. this was Mick's PR for the issue: https://github.com/euler-xyz/openzeppelin-synth-fixes-private/pull/13/files

Copy link
Member

@haythemsellami haythemsellami Jul 3, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah I noticed that, I guess we either do the full fix or they should mark it as partially fixed in the report.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed here: ca5fca5


return shares;
}

/// @notice Redeems a certain amount of shares for assets.
/// @dev Overwritten to not call maxRedeem which would return 0 if there is a controller set, update the accrued
/// interest and update _totalAssets.
/// @param shares The amount of shares to redeem.
/// @param receiver The recipient of the assets.
/// @param owner The account from which the shares are redeemed.
/// @return The amount of assets redeemed.
function redeem(uint256 shares, address receiver, address owner)
public
Expand All @@ -171,7 +206,17 @@ contract EulerSavingsRate is EVCUtil, ERC4626 {
{
// Move interest to totalAssets
updateInterestAndReturnESRSlotCache();
return super.redeem(shares, receiver, owner);

uint256 maxShares = balanceOf(owner);
if (maxShares < shares) {
revert ERC4626ExceededMaxRedeem(owner, shares, maxShares);
}

uint256 assets = previewRedeem(shares);
_withdraw(_msgSender(), receiver, owner, assets, shares);
_totalAssets = _totalAssets - assets;

return assets;
}

function _convertToShares(uint256 assets, Math.Rounding rounding) internal view override returns (uint256) {
Expand All @@ -183,22 +228,17 @@ contract EulerSavingsRate is EVCUtil, ERC4626 {
}

function _deposit(address caller, address receiver, uint256 assets, uint256 shares) internal override {
_totalAssets = _totalAssets + assets;
super._deposit(caller, receiver, assets, shares);
}

function _withdraw(address caller, address receiver, address owner, uint256 assets, uint256 shares)
internal
override
{
_totalAssets = _totalAssets - assets;
super._withdraw(caller, receiver, owner, assets, shares);
_totalAssets = _totalAssets + assets;
}

/// @notice Smears any donations to this vault as interest.
function gulp() public nonReentrant {
ESRSlot memory esrSlotCache = updateInterestAndReturnESRSlotCache();

// Do not gulp if total supply is too low
if (totalSupply() < MIN_SHARES_FOR_GULP) return;

uint256 assetBalance = IERC20(asset()).balanceOf(address(this));
uint256 toGulp = assetBalance - _totalAssets - esrSlotCache.interestLeft;

Expand All @@ -210,6 +250,8 @@ contract EulerSavingsRate is EVCUtil, ERC4626 {

// write esrSlotCache back to storage in a single SSTORE
esrSlot = esrSlotCache;

emit Gulped(toGulp, esrSlotCache.interestLeft);
}

/// @notice Updates the interest and returns the ESR storage slot cache.
Expand All @@ -226,10 +268,13 @@ contract EulerSavingsRate is EVCUtil, ERC4626 {
// Move interest accrued to totalAssets
_totalAssets = _totalAssets + accruedInterest;

emit InterestUpdated(accruedInterest, esrSlotCache.interestLeft);

return esrSlotCache;
}

/// @notice Returns the amount of interest accrued.
/// @return The amount of interest accrued.
function interestAccrued() public view returns (uint256) {
return interestAccruedFromCache(esrSlot);
}
Expand All @@ -253,6 +298,7 @@ contract EulerSavingsRate is EVCUtil, ERC4626 {
}

/// @notice Returns the ESR storage slot as a struct.
/// @return The ESR storage slot as a struct.
function getESRSlot() public view returns (ESRSlot memory) {
return esrSlot;
}
Expand Down
26 changes: 20 additions & 6 deletions src/Synths/IRMSynth.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@

pragma solidity ^0.8.0;

import "../InterestRateModels/IIRM.sol";
import "../interfaces/IPriceOracle.sol";
import {IIRM} from "../InterestRateModels/IIRM.sol";
import {IPriceOracle} from "../interfaces/IPriceOracle.sol";
import {IERC20} from "../EVault/IEVault.sol";

/// @title IRMSynth
Expand All @@ -20,10 +20,15 @@ contract IRMSynth is IIRM {
uint216 public constant ADJUST_ONE = 1.0e18;
uint216 public constant ADJUST_INTERVAL = 1 hours;

/// @notice The address of the synthetic asset.
address public immutable synth;
/// @notice The address of the reference asset.
address public immutable referenceAsset;
/// @notice The address of the oracle.
IPriceOracle public immutable oracle;
/// @notice The target quote which the IRM will try to maintain.
uint256 public immutable targetQuote;
/// @notice The amount of the quote asset to use for the quote.
uint256 public immutable quoteAmount;

struct IRMData {
Expand All @@ -36,6 +41,8 @@ contract IRMSynth is IIRM {
error E_ZeroAddress();
error E_InvalidQuote();

event InterestUpdated(uint256 rate);

constructor(address synth_, address referenceAsset_, address oracle_, uint256 targetQuoute_) {
if (synth_ == address(0) || referenceAsset_ == address(0) || oracle_ == address(0)) {
revert E_ZeroAddress();
Expand All @@ -54,21 +61,26 @@ contract IRMSynth is IIRM {
}

irmStorage = IRMData({lastUpdated: uint40(block.timestamp), lastRate: BASE_RATE});

emit InterestUpdated(BASE_RATE);
}

/// @notice Computes the interest rate and updates the storage if necessary.
/// @return The interest rate.
function computeInterestRate(address, uint256, uint256) external override returns (uint256) {
IRMData memory irmCache = irmStorage;
(uint216 rate, bool updated) = _computeRate(irmCache);
(uint216 rate, bool updated) = _computeRate(irmStorage);

if (updated) {
irmStorage = IRMData({lastUpdated: uint40(block.timestamp), lastRate: rate});
emit InterestUpdated(rate);
}

return rate;
}

function computeInterestRateView(address, uint256, uint256) external view override returns (uint256) {
(uint216 rate,) = _computeRate(irmStorage);
/// @return rate The new interest rate
function computeInterestRateView(address, uint256, uint256) external view override returns (uint256 rate) {
(rate,) = _computeRate(irmStorage);
return rate;
}

Expand Down Expand Up @@ -103,6 +115,8 @@ contract IRMSynth is IIRM {
return (rate, updated);
}

/// @notice Retrieves the packed IRM data as a struct.
/// @return The IRM data.
function getIRMData() external view returns (IRMData memory) {
return irmStorage;
}
Expand Down
5 changes: 5 additions & 0 deletions src/Synths/PegStabilityModule.sol
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,16 @@ contract PegStabilityModule is EVCUtil {
uint256 public constant BPS_SCALE = 100_00;
uint256 public constant PRICE_SCALE = 1e18;

/// @notice The synthetic asset.
ESynth public immutable synth;
/// @notice The underlying asset.
IERC20 public immutable underlying;
/// @notice The conversion price between the synthetic and underlying asset.
uint256 public immutable conversionPrice; // 1e18 = 1 SYNTH == 1 UNDERLYING, 0.01e18 = 1 SYNTH == 0.01 UNDERLYING

/// @notice The fee for swapping to the underlying asset in basis points.
uint256 public immutable TO_UNDERLYING_FEE;
/// @notice The fee for swapping to the synthetic asset in basis points.
uint256 public immutable TO_SYNTH_FEE;

error E_ZeroAddress();
Expand Down
11 changes: 8 additions & 3 deletions test/unit/esr/ESR.Fuzz.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ contract ESRFuzzTest is ESRTest {

// this tests shows that when you have a very small deposit and a very large interestAmount minted to the contract
function testFuzz_gulp_under_uint168(uint256 interestAmount, uint256 depositAmount) public {
uint256 MIN_SHARES_FOR_GULP = 10 * 1e6;
depositAmount = bound(depositAmount, 0, type(uint112).max);
interestAmount = bound(interestAmount, 0, type(uint256).max - depositAmount); // this makes sure that the mint
// won't cause overflow
Expand All @@ -49,10 +50,14 @@ contract ESRFuzzTest is ESRTest {

EulerSavingsRate.ESRSlot memory esrSlot = esr.updateInterestAndReturnESRSlotCache();

if (interestAmount <= type(uint168).max) {
assertEq(esrSlot.interestLeft, interestAmount);
if (depositAmount >= MIN_SHARES_FOR_GULP) {
if (interestAmount <= type(uint168).max) {
assertEq(esrSlot.interestLeft, interestAmount);
} else {
assertEq(esrSlot.interestLeft, type(uint168).max);
}
} else {
assertEq(esrSlot.interestLeft, type(uint168).max);
assertEq(esrSlot.interestLeft, 0);
}
}

Expand Down
Loading
Loading