Skip to content

Commit

Permalink
Merge pull request #654 from ionicprotocol/feat/hypernative-security-…
Browse files Browse the repository at this point in the history
…oracle

Hypernative security oracle
  • Loading branch information
rhlsthrm authored Dec 2, 2024
2 parents 8839224 + ffb1458 commit 445e0ba
Show file tree
Hide file tree
Showing 169 changed files with 103,229 additions and 314 deletions.
25 changes: 14 additions & 11 deletions packages/contracts/contracts/compound/CToken.sol
Original file line number Diff line number Diff line change
Expand Up @@ -9,17 +9,20 @@ import { EIP20Interface } from "./EIP20Interface.sol";
import { InterestRateModel } from "./InterestRateModel.sol";
import { ComptrollerV3Storage } from "./ComptrollerStorage.sol";
import { IFeeDistributor } from "./IFeeDistributor.sol";
import { CTokenOracleProtected } from "./CTokenOracleProtected.sol";

import { DiamondExtension, LibDiamond } from "../ionic/DiamondExtension.sol";
import { PoolLens } from "../PoolLens.sol";
import { IonicUniV3Liquidator } from "../IonicUniV3Liquidator.sol";
import { IHypernativeOracle } from "../external/hypernative/interfaces/IHypernativeOracle.sol";

/**
* @title Compound's CErc20 Contract
* @notice CTokens which wrap an EIP-20 underlying
* @dev This contract should not to be deployed on its own; instead, deploy `CErc20Delegator` (proxy contract) and `CErc20Delegate` (logic/implementation contract).
* @author Compound
*/
abstract contract CErc20 is CTokenSecondExtensionBase, TokenErrorReporter, Exponential, DiamondExtension {
abstract contract CErc20 is CTokenOracleProtected, CTokenSecondExtensionBase, TokenErrorReporter, Exponential, DiamondExtension {
modifier isAuthorized() {
require(
IFeeDistributor(ionicAdmin).canCall(address(comptroller), msg.sender, address(this), msg.sig),
Expand Down Expand Up @@ -69,7 +72,7 @@ abstract contract CErc20 is CTokenSecondExtensionBase, TokenErrorReporter, Expon
* @param mintAmount The amount of the underlying asset to supply
* @return uint 0=success, otherwise a failure (see ErrorReporter.sol for details)
*/
function mint(uint256 mintAmount) external override isAuthorized returns (uint256) {
function mint(uint256 mintAmount) external override isAuthorized onlyOracleApprovedAllowEOA returns (uint256) {
(uint256 err, ) = mintInternal(mintAmount);
return err;
}
Expand All @@ -80,7 +83,7 @@ abstract contract CErc20 is CTokenSecondExtensionBase, TokenErrorReporter, Expon
* @param redeemTokens The number of cTokens to redeem into underlying
* @return uint 0=success, otherwise a failure (see ErrorReporter.sol for details)
*/
function redeem(uint256 redeemTokens) external override isAuthorized returns (uint256) {
function redeem(uint256 redeemTokens) external override isAuthorized onlyOracleApprovedAllowEOA returns (uint256) {
return redeemInternal(redeemTokens);
}

Expand All @@ -90,7 +93,7 @@ abstract contract CErc20 is CTokenSecondExtensionBase, TokenErrorReporter, Expon
* @param redeemAmount The amount of underlying to redeem
* @return uint 0=success, otherwise a failure (see ErrorReporter.sol for details)
*/
function redeemUnderlying(uint256 redeemAmount) external override isAuthorized returns (uint256) {
function redeemUnderlying(uint256 redeemAmount) external override isAuthorized onlyOracleApprovedAllowEOA returns (uint256) {
return redeemUnderlyingInternal(redeemAmount);
}

Expand All @@ -99,7 +102,7 @@ abstract contract CErc20 is CTokenSecondExtensionBase, TokenErrorReporter, Expon
* @param borrowAmount The amount of the underlying asset to borrow
* @return uint 0=success, otherwise a failure (see ErrorReporter.sol for details)
*/
function borrow(uint256 borrowAmount) external override isAuthorized returns (uint256) {
function borrow(uint256 borrowAmount) external override isAuthorized onlyOracleApprovedAllowEOA returns (uint256) {
return borrowInternal(borrowAmount);
}

Expand All @@ -108,7 +111,7 @@ abstract contract CErc20 is CTokenSecondExtensionBase, TokenErrorReporter, Expon
* @param repayAmount The amount to repay
* @return uint 0=success, otherwise a failure (see ErrorReporter.sol for details)
*/
function repayBorrow(uint256 repayAmount) external override isAuthorized returns (uint256) {
function repayBorrow(uint256 repayAmount) external override isAuthorized onlyOracleApprovedAllowEOA returns (uint256) {
(uint256 err, ) = repayBorrowInternal(repayAmount);
return err;
}
Expand All @@ -119,7 +122,7 @@ abstract contract CErc20 is CTokenSecondExtensionBase, TokenErrorReporter, Expon
* @param repayAmount The amount to repay
* @return uint 0=success, otherwise a failure (see ErrorReporter.sol for details)
*/
function repayBorrowBehalf(address borrower, uint256 repayAmount) external override isAuthorized returns (uint256) {
function repayBorrowBehalf(address borrower, uint256 repayAmount) external override isAuthorized onlyOracleApprovedAllowEOA returns (uint256) {
(uint256 err, ) = repayBorrowBehalfInternal(borrower, repayAmount);
return err;
}
Expand All @@ -136,7 +139,7 @@ abstract contract CErc20 is CTokenSecondExtensionBase, TokenErrorReporter, Expon
address borrower,
uint256 repayAmount,
address cTokenCollateral
) external override isAuthorized isMinHFThresholdExceeded(borrower) returns (uint256) {
) external override isAuthorized onlyOracleApprovedAllowEOA isMinHFThresholdExceeded(borrower) returns (uint256) {
(uint256 err, ) = liquidateBorrowInternal(borrower, repayAmount, cTokenCollateral);
return err;
}
Expand All @@ -162,7 +165,7 @@ abstract contract CErc20 is CTokenSecondExtensionBase, TokenErrorReporter, Expon
address liquidator,
address borrower,
uint256 seizeTokens
) external override nonReentrant(true) returns (uint256) {
) external override nonReentrant(true) onlyOracleApprovedAllowEOA returns (uint256) {
return seizeInternal(msg.sender, liquidator, borrower, seizeTokens);
}

Expand All @@ -181,7 +184,7 @@ abstract contract CErc20 is CTokenSecondExtensionBase, TokenErrorReporter, Expon
* @param withdrawAmount Amount of fees to withdraw
* @return uint 0=success, otherwise a failure (see ErrorReporter.sol for details)
*/
function _withdrawIonicFees(uint256 withdrawAmount) external override nonReentrant(false) returns (uint256) {
function _withdrawIonicFees(uint256 withdrawAmount) external override nonReentrant(false) onlyOracleApproved returns (uint256) {
asCTokenExtension().accrueInterest();

if (accrualBlockNumber != block.number) {
Expand Down Expand Up @@ -214,7 +217,7 @@ abstract contract CErc20 is CTokenSecondExtensionBase, TokenErrorReporter, Expon
* @param withdrawAmount Amount of fees to withdraw
* @return uint 0=success, otherwise a failure (see ErrorReporter.sol for details)
*/
function _withdrawAdminFees(uint256 withdrawAmount) external override nonReentrant(false) returns (uint256) {
function _withdrawAdminFees(uint256 withdrawAmount) external override nonReentrant(false) onlyOracleApproved returns (uint256) {
asCTokenExtension().accrueInterest();

if (accrualBlockNumber != block.number) {
Expand Down
27 changes: 20 additions & 7 deletions packages/contracts/contracts/compound/CTokenFirstExtension.sol
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,16 @@ import { TokenErrorReporter } from "./ErrorReporter.sol";
import { Exponential } from "./Exponential.sol";
import { InterestRateModel } from "./InterestRateModel.sol";
import { IFeeDistributor } from "./IFeeDistributor.sol";
import { Multicall } from "../utils/Multicall.sol";
import { CTokenOracleProtected } from "./CTokenOracleProtected.sol";

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

import { Multicall } from "../utils/Multicall.sol";
import { AddressesProvider } from "../ionic/AddressesProvider.sol";
import { IHypernativeOracle } from "../external/hypernative/interfaces/IHypernativeOracle.sol";

contract CTokenFirstExtension is
CTokenOracleProtected,
CErc20FirstExtensionBase,
TokenErrorReporter,
Exponential,
Expand Down Expand Up @@ -147,7 +150,10 @@ contract CTokenFirstExtension is
* @param amount The number of tokens to transfer
* @return Whether or not the transfer succeeded
*/
function transfer(address dst, uint256 amount) public override nonReentrant(false) isAuthorized returns (bool) {
function transfer(
address dst,
uint256 amount
) public override nonReentrant(false) isAuthorized onlyOracleApprovedAllowEOA returns (bool) {
return transferTokens(msg.sender, msg.sender, dst, amount) == uint256(Error.NO_ERROR);
}

Expand All @@ -162,7 +168,7 @@ contract CTokenFirstExtension is
address src,
address dst,
uint256 amount
) public override nonReentrant(false) isAuthorized returns (bool) {
) public override nonReentrant(false) isAuthorized onlyOracleApprovedAllowEOA returns (bool) {
return transferTokens(msg.sender, src, dst, amount) == uint256(Error.NO_ERROR);
}

Expand All @@ -174,7 +180,10 @@ contract CTokenFirstExtension is
* @param amount The number of tokens that are approved (-1 means infinite)
* @return Whether or not the approval succeeded
*/
function approve(address spender, uint256 amount) public override isAuthorized returns (bool) {
function approve(
address spender,
uint256 amount
) public override isAuthorized onlyOracleApprovedAllowEOA returns (bool) {
address src = msg.sender;
transferAllowances[src][spender] = amount;
emit Approval(src, spender, amount);
Expand Down Expand Up @@ -229,7 +238,9 @@ contract CTokenFirstExtension is
* @dev Admin function to accrue interest and set a new reserve factor
* @return uint 0=success, otherwise a failure (see ErrorReporter.sol for details)
*/
function _setReserveFactor(uint256 newReserveFactorMantissa) public override nonReentrant(false) returns (uint256) {
function _setReserveFactor(
uint256 newReserveFactorMantissa
) public override nonReentrant(false) returns (uint256) {
accrueInterest();
// Check caller is admin
if (!hasAdminRights()) {
Expand Down Expand Up @@ -259,7 +270,9 @@ contract CTokenFirstExtension is
* @dev Admin function to accrue interest and set a new admin fee
* @return uint 0=success, otherwise a failure (see ErrorReporter.sol for details)
*/
function _setAdminFee(uint256 newAdminFeeMantissa) public override nonReentrant(false) returns (uint256) {
function _setAdminFee(
uint256 newAdminFeeMantissa
) public override nonReentrant(false) returns (uint256) {
accrueInterest();
// Verify market's block number equals current block number
if (accrualBlockNumber != block.number) {
Expand Down Expand Up @@ -638,7 +651,7 @@ contract CTokenFirstExtension is
return balance;
}

function flash(uint256 amount, bytes calldata data) public override isAuthorized {
function flash(uint256 amount, bytes calldata data) public override isAuthorized onlyOracleApprovedAllowEOA {
accrueInterest();

totalBorrows += amount;
Expand Down
36 changes: 36 additions & 0 deletions packages/contracts/contracts/compound/CTokenOracleProtected.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
// SPDX-License-Identifier: Unlicense
pragma solidity ^0.8.22;

import { CErc20Storage } from "./CTokenInterfaces.sol";
import { IHypernativeOracle } from "../external/hypernative/interfaces/IHypernativeOracle.sol";

contract CTokenOracleProtected is CErc20Storage {
error InteractionNotAllowed();

modifier onlyOracleApproved() {
address oracleAddress = ap.getAddress("HYPERNATIVE_ORACLE");
if (oracleAddress == address(0)) {
_;
return;
}
IHypernativeOracle oracle = IHypernativeOracle(oracleAddress);
if (oracle.isBlacklistedContext(msg.sender, tx.origin) || !oracle.isTimeExceeded(msg.sender)) {
revert InteractionNotAllowed();
}
_;
}

modifier onlyOracleApprovedAllowEOA() {
address oracleAddress = ap.getAddress("HYPERNATIVE_ORACLE");
if (oracleAddress == address(0)) {
_;
return;
}

IHypernativeOracle oracle = IHypernativeOracle(oracleAddress);
if (oracle.isBlacklistedAccount(msg.sender) || msg.sender != tx.origin) {
revert InteractionNotAllowed();
}
_;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.22;

interface IHypernativeOracle {
function register(address account) external;
function registerStrict(address account) external;
function isBlacklistedAccount(address account) external view returns (bool);
function isBlacklistedContext(address sender, address origin) external view returns (bool);
function isTimeExceeded(address account) external view returns (bool);
}
80 changes: 80 additions & 0 deletions packages/contracts/contracts/security/OracleRegistry.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.22;

import { IHypernativeOracle } from "../external/hypernative/interfaces/IHypernativeOracle.sol";
import { Ownable2Step } from "@openzeppelin/contracts/access/Ownable2Step.sol";

contract OracleRegistry is Ownable2Step {
bytes32 private constant HYPERNATIVE_ORACLE_STORAGE_SLOT =
bytes32(uint256(keccak256("eip1967.hypernative.oracle")) - 1);
bytes32 private constant HYPERNATIVE_MODE_STORAGE_SLOT =
bytes32(uint256(keccak256("eip1967.hypernative.is_strict_mode")) - 1);

event OracleAdminChanged(address indexed previousAdmin, address indexed newAdmin);
event OracleAddressChanged(address indexed previousOracle, address indexed newOracle);

constructor() Ownable2Step() {}

function oracleRegister(address _account) public {
address oracleAddress = _getAddressBySlot(HYPERNATIVE_ORACLE_STORAGE_SLOT);
IHypernativeOracle oracle = IHypernativeOracle(oracleAddress);
if (hypernativeOracleIsStrictMode()) {
oracle.registerStrict(_account);
} else {
oracle.register(_account);
}
}

function setOracle(address _oracle) public onlyOwner {
_setOracle(_oracle);
}

function setIsStrictMode(bool _mode) public onlyOwner {
_setIsStrictMode(_mode);
}

function hypernativeOracleIsStrictMode() public view returns (bool) {
return _getValueBySlot(HYPERNATIVE_MODE_STORAGE_SLOT) == 1;
}

function hypernativeOracle() public view returns (address) {
return _getAddressBySlot(HYPERNATIVE_ORACLE_STORAGE_SLOT);
}

/**
* @dev Admin only function, sets new oracle admin. set to address(0) to revoke oracle
*/
function _setOracle(address _oracle) internal {
address oldOracle = hypernativeOracle();
_setAddressBySlot(HYPERNATIVE_ORACLE_STORAGE_SLOT, _oracle);
emit OracleAddressChanged(oldOracle, _oracle);
}

function _setIsStrictMode(bool _mode) internal {
_setValueBySlot(HYPERNATIVE_MODE_STORAGE_SLOT, _mode ? 1 : 0);
}

function _setAddressBySlot(bytes32 slot, address newAddress) internal {
assembly {
sstore(slot, newAddress)
}
}

function _setValueBySlot(bytes32 _slot, uint256 _value) internal {
assembly {
sstore(_slot, _value)
}
}

function _getAddressBySlot(bytes32 slot) internal view returns (address addr) {
assembly {
addr := sload(slot)
}
}

function _getValueBySlot(bytes32 _slot) internal view returns (uint256 _value) {
assembly {
_value := sload(_slot)
}
}
}
Loading

0 comments on commit 445e0ba

Please sign in to comment.