MSigOracleFeed

Description:

Smart contract deployed on Ethereum with Factory, Oracle features.

Blockchain: Ethereum

Source Code: View Code On The Blockchain

Solidity Source Code:

{{
  "language": "Solidity",
  "sources": {
    "src/MSigOracleFeed.sol": {
      "content": "// SPDX-License-Identifier: MIT

pragma solidity ^0.8.20;

import {AggregatorV3Interface} from "@morpho-blue-oracles/morpho-chainlink/interfaces/AggregatorV3Interface.sol";

/**
 * @title MSigOracleFeed
 * @notice A Chainlink-compatible oracle controlled by a single address (EOA or multisig)
 * @dev Implements AggregatorV3Interface with price deviation protection
 */
contract MSigOracleFeed is AggregatorV3Interface {

    /// @notice The address authorized to update the price
    address public immutable controller;

    /// @notice The number of decimals in the returned price
    uint8 public immutable decimals;

    /// @notice Human-readable description of the price feed
    string public description;

    /// @notice Maximum allowed deviation from current price (scaled by 1e18, e.g., 0.1e18 for 10%)
    uint256 public immutable maxSafeDeviation;

    /// @notice Current price value
    int256 private _price;

    /// @notice Current round ID
    uint80 private _roundId;

    /// @notice Timestamp of last update
    uint256 private _updatedAt;

    /// @notice Version of the price feed implementation
    uint256 public constant version = 1;

    // Events
    event PriceUpdated(uint80 indexed roundId, int256 price, address indexed updatedBy);
    event PriceForceUpdated(uint80 indexed roundId, int256 price, address indexed updatedBy);

    // Errors
    error Unauthorized();
    error InvalidPrice();
    error DeviationTooHigh(int256 oldPrice, int256 newPrice, uint256 deviation, uint256 maxDeviation);

    /**
     * @notice Constructs a new MSigOracleFeed
     * @param _controller The address authorized to update prices
     * @param _initialPrice The initial price value
     * @param _maxSafeDeviation Maximum allowed price deviation (scaled by 1e18)
     * @param _decimals The number of decimals for the oracle output
     * @param _description Human-readable description
     */
    constructor(
        address _controller,
        int256 _initialPrice,
        uint256 _maxSafeDeviation,
        uint8 _decimals,
        string memory _description
    ) {
        require(_controller != address(0), "Invalid controller");
        require(_initialPrice > 0, "Invalid initial price");

        controller = _controller;
        _price = _initialPrice;
        maxSafeDeviation = _maxSafeDeviation;
        decimals = _decimals;
        description = _description;

        _roundId = 1;
        _updatedAt = block.timestamp;

        emit PriceUpdated(1, _initialPrice, msg.sender);
    }

    /**
     * @notice Updates the price with deviation check
     * @dev Reverts if the new price deviates more than maxSafeDeviation from current price
     * @param newPrice The new price to set
     */
    function setPrice(int256 newPrice) external {
        if (msg.sender != controller) revert Unauthorized();
        if (newPrice <= 0) revert InvalidPrice();

        // Check deviation
        uint256 deviation = _calculateDeviation(_price, newPrice);
        if (deviation > maxSafeDeviation) {
            revert DeviationTooHigh(_price, newPrice, deviation, maxSafeDeviation);
        }

        _updatePrice(newPrice);
        emit PriceUpdated(_roundId, newPrice, msg.sender);
    }

    /**
     * @notice Updates the price without deviation check
     * @dev Should be used carefully, only when large price movements are legitimate
     * @param newPrice The new price to set
     */
    function setPriceForce(int256 newPrice) external {
        if (msg.sender != controller) revert Unauthorized();
        if (newPrice <= 0) revert InvalidPrice();

        _updatePrice(newPrice);
        emit PriceForceUpdated(_roundId, newPrice, msg.sender);
    }

    /**
     * @notice Internal function to update price and round data
     * @param newPrice The new price value
     */
    function _updatePrice(int256 newPrice) private {
        _price = newPrice;
        _roundId++;
        _updatedAt = block.timestamp;
    }

    /**
     * @notice Calculates the relative deviation between two prices
     * @dev Returns deviation scaled by 1e18 (e.g., 0.1e18 = 10%)
     * @param oldPrice The original price
     * @param newPrice The new price
     * @return The deviation scaled by 1e18
     */
    function _calculateDeviation(int256 oldPrice, int256 newPrice) private pure returns (uint256) {
        if (oldPrice == newPrice) return 0;

        // Calculate absolute difference
        uint256 diff;
        if (newPrice > oldPrice) {
            diff = uint256(newPrice - oldPrice);
        } else {
            diff = uint256(oldPrice - newPrice);
        }

        // Calculate relative deviation: (diff / oldPrice) * 1e18
        return (diff * 1e18) / uint256(oldPrice);
    }

    /**
     * @notice Get the latest round data (implements AggregatorV3Interface)
     * @return roundId The round ID
     * @return answer The current price
     * @return startedAt The timestamp when the round started
     * @return updatedAt The timestamp when the round was updated
     * @return answeredInRound The round ID in which the answer was computed
     */
    function latestRoundData()
        external
        view
        returns (
            uint80 roundId,
            int256 answer,
            uint256 startedAt,
            uint256 updatedAt,
            uint80 answeredInRound
        )
    {
        return (_roundId, _price, _updatedAt, _updatedAt, _roundId);
    }

    /**
     * @notice Get data from a specific round (implements AggregatorV3Interface)
     * @dev This contract does not maintain historical data, so it always returns the latest data
     * @param _requestedRoundId The round ID (ignored)
     * @return roundId The current round ID
     * @return answer The current price
     * @return startedAt The timestamp when the round started
     * @return updatedAt The timestamp when the round was updated
     * @return answeredInRound The round ID in which the answer was computed
     */
    function getRoundData(uint80 _requestedRoundId)
        external
        view
        returns (
            uint80 roundId,
            int256 answer,
            uint256 startedAt,
            uint256 updatedAt,
            uint80 answeredInRound
        )
    {
        // For simplicity, always return latest data regardless of requested round
        return (_roundId, _price, _updatedAt, _updatedAt, _roundId);
    }
}
"
    },
    "lib/morpho-blue-oracles/src/morpho-chainlink/interfaces/AggregatorV3Interface.sol": {
      "content": "// SPDX-License-Identifier: MIT
pragma solidity >=0.5.0;

/// @dev From
/// https://github.com/smartcontractkit/chainlink/blob/master/contracts/src/v0.8/shared/interfaces/AggregatorV3Interface.sol
interface AggregatorV3Interface {
    function decimals() external view returns (uint8);

    function description() external view returns (string memory);

    function version() external view returns (uint256);

    function getRoundData(uint80 _roundId)
        external
        view
        returns (uint80 roundId, int256 answer, uint256 startedAt, uint256 updatedAt, uint80 answeredInRound);

    function latestRoundData()
        external
        view
        returns (uint80 roundId, int256 answer, uint256 startedAt, uint256 updatedAt, uint80 answeredInRound);
}
"
    }
  },
  "settings": {
    "remappings": [
      "@openzeppelin/contracts/=lib/openzeppelin-contracts/contracts/",
      "ds-test/=lib/morpho-blue-oracles/lib/forge-std/lib/ds-test/src/",
      "erc4626-tests/=lib/openzeppelin-contracts/lib/erc4626-tests/",
      "forge-std/=lib/forge-std/src/",
      "halmos-cheatcodes/=lib/openzeppelin-contracts/lib/halmos-cheatcodes/src/",
      "@morpho-blue-oracles/=lib/morpho-blue-oracles/src/",
      "morpho-blue/=lib/morpho-blue-oracles/lib/morpho-blue/",
      "openzeppelin-contracts/=lib/openzeppelin-contracts/",
      "morpho-blue-oracles/=lib/morpho-blue-oracles/src/"
    ],
    "optimizer": {
      "enabled": true,
      "runs": 10000
    },
    "metadata": {
      "useLiteralContent": false,
      "bytecodeHash": "ipfs",
      "appendCBOR": true
    },
    "outputSelection": {
      "*": {
        "*": [
          "evm.bytecode",
          "evm.deployedBytecode",
          "devdoc",
          "userdoc",
          "metadata",
          "abi"
        ]
      }
    },
    "evmVersion": "prague",
    "viaIR": false
  }
}}

Tags:
Factory, Oracle|addr:0x47eb22709685d7b36e938540b0baa26a8dc62b87|verified:true|block:23746383|tx:0x18e643312be8a24bf9d4854f04f994e068af1eb376beb78e551265134334a4fc|first_check:1762518882

Submitted on: 2025-11-07 13:34:43

Comments

Log in to comment.

No comments yet.