PriceOracle

Description:

Multi-signature wallet contract requiring multiple confirmations for transaction execution.

Blockchain: Ethereum

Source Code: View Code On The Blockchain

Solidity Source Code:

{{
  "language": "Solidity",
  "sources": {
    "@openzeppelin/contracts/access/Ownable.sol": {
      "content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (access/Ownable.sol)

pragma solidity ^0.8.20;

import {Context} from "../utils/Context.sol";

/**
 * @dev Contract module which provides a basic access control mechanism, where
 * there is an account (an owner) that can be granted exclusive access to
 * specific functions.
 *
 * The initial owner is set to the address provided by the deployer. This can
 * later be changed with {transferOwnership}.
 *
 * This module is used through inheritance. It will make available the modifier
 * `onlyOwner`, which can be applied to your functions to restrict their use to
 * the owner.
 */
abstract contract Ownable is Context {
    address private _owner;

    /**
     * @dev The caller account is not authorized to perform an operation.
     */
    error OwnableUnauthorizedAccount(address account);

    /**
     * @dev The owner is not a valid owner account. (eg. `address(0)`)
     */
    error OwnableInvalidOwner(address owner);

    event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);

    /**
     * @dev Initializes the contract setting the address provided by the deployer as the initial owner.
     */
    constructor(address initialOwner) {
        if (initialOwner == address(0)) {
            revert OwnableInvalidOwner(address(0));
        }
        _transferOwnership(initialOwner);
    }

    /**
     * @dev Throws if called by any account other than the owner.
     */
    modifier onlyOwner() {
        _checkOwner();
        _;
    }

    /**
     * @dev Returns the address of the current owner.
     */
    function owner() public view virtual returns (address) {
        return _owner;
    }

    /**
     * @dev Throws if the sender is not the owner.
     */
    function _checkOwner() internal view virtual {
        if (owner() != _msgSender()) {
            revert OwnableUnauthorizedAccount(_msgSender());
        }
    }

    /**
     * @dev Leaves the contract without owner. It will not be possible to call
     * `onlyOwner` functions. Can only be called by the current owner.
     *
     * NOTE: Renouncing ownership will leave the contract without an owner,
     * thereby disabling any functionality that is only available to the owner.
     */
    function renounceOwnership() public virtual onlyOwner {
        _transferOwnership(address(0));
    }

    /**
     * @dev Transfers ownership of the contract to a new account (`newOwner`).
     * Can only be called by the current owner.
     */
    function transferOwnership(address newOwner) public virtual onlyOwner {
        if (newOwner == address(0)) {
            revert OwnableInvalidOwner(address(0));
        }
        _transferOwnership(newOwner);
    }

    /**
     * @dev Transfers ownership of the contract to a new account (`newOwner`).
     * Internal function without access restriction.
     */
    function _transferOwnership(address newOwner) internal virtual {
        address oldOwner = _owner;
        _owner = newOwner;
        emit OwnershipTransferred(oldOwner, newOwner);
    }
}
"
    },
    "@openzeppelin/contracts/utils/Context.sol": {
      "content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.1) (utils/Context.sol)

pragma solidity ^0.8.20;

/**
 * @dev Provides information about the current execution context, including the
 * sender of the transaction and its data. While these are generally available
 * via msg.sender and msg.data, they should not be accessed in such a direct
 * manner, since when dealing with meta-transactions the account sending and
 * paying for execution may not be the actual sender (as far as an application
 * is concerned).
 *
 * This contract is only required for intermediate, library-like contracts.
 */
abstract contract Context {
    function _msgSender() internal view virtual returns (address) {
        return msg.sender;
    }

    function _msgData() internal view virtual returns (bytes calldata) {
        return msg.data;
    }

    function _contextSuffixLength() internal view virtual returns (uint256) {
        return 0;
    }
}
"
    },
    "contracts/core/PriceOracle.sol": {
      "content": "// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.20;

import "@openzeppelin/contracts/access/Ownable.sol";
import "../interfaces/IPriceOracle.sol";

interface IUniswapV2Pair {
    function getReserves() external view returns (uint112 reserve0, uint112 reserve1, uint32 blockTimestampLast);
    function token0() external view returns (address);
    function token1() external view returns (address);
}

interface IUniswapV3Pool {
    function slot0() external view returns (
        uint160 sqrtPriceX96,
        int24 tick,
        uint16 observationIndex,
        uint16 observationCardinality,
        uint16 observationCardinalityNext,
        uint8 feeProtocol,
        bool unlocked
    );
    function token0() external view returns (address);
    function token1() external view returns (address);
    function observe(uint32[] calldata secondsAgos) external view returns (
        int56[] memory tickCumulatives,
        uint160[] memory secondsPerLiquidityCumulativeX128s
    );
}

/**
 * @title PriceOracle
 * @dev Multi-source price oracle for BWS token
 * @notice Combines Chainlink, Uniswap V2, and Uniswap V3 for robust pricing
 */
contract PriceOracle is IPriceOracle, Ownable {
    // Price sources
    AggregatorV3Interface public chainlinkPriceFeed;
    IUniswapV2Pair public uniswapV2Pair;
    IUniswapV3Pool public uniswapV3Pool;

    // Token addresses
    address public immutable bwsToken;
    address public immutable wethToken;

    // Price configuration
    uint256 public constant PRICE_STALENESS_THRESHOLD = 3600; // 1 hour
    uint256 public constant TWAP_PERIOD = 600; // 10 minutes for TWAP

    // Fallback price (last known good price)
    uint256 public fallbackPrice;
    uint256 public fallbackPriceTimestamp;

    // Price source weights (out of 100)
    uint8 public chainlinkWeight = 50;
    uint8 public uniswapV2Weight = 25;
    uint8 public uniswapV3Weight = 25;

    // Events
    event PriceSourceUpdated(string source, address indexed newAddress);
    event PriceWeightsUpdated(uint8 chainlink, uint8 v2, uint8 v3);
    event FallbackPriceUpdated(uint256 price, uint256 timestamp);

    constructor(
        address _bwsToken,
        address _wethToken,
        address _owner
    ) Ownable(_owner) {
        require(_bwsToken != address(0), "Invalid BWS token");
        require(_wethToken != address(0), "Invalid WETH token");

        bwsToken = _bwsToken;
        wethToken = _wethToken;

        // Initialize with a default fallback price (0.001 ETH)
        fallbackPrice = 1e15;
        fallbackPriceTimestamp = block.timestamp;
    }

    /**
     * @dev Set Chainlink price feed address
     */
    function setChainlinkPriceFeed(address _priceFeed) external onlyOwner {
        chainlinkPriceFeed = AggregatorV3Interface(_priceFeed);
        emit PriceSourceUpdated("Chainlink", _priceFeed);
    }

    /**
     * @dev Set Uniswap V2 pair address
     */
    function setUniswapV2Pair(address _pair) external onlyOwner {
        uniswapV2Pair = IUniswapV2Pair(_pair);
        emit PriceSourceUpdated("UniswapV2", _pair);
    }

    /**
     * @dev Set Uniswap V3 pool address
     */
    function setUniswapV3Pool(address _pool) external onlyOwner {
        uniswapV3Pool = IUniswapV3Pool(_pool);
        emit PriceSourceUpdated("UniswapV3", _pool);
    }

    /**
     * @dev Update price source weights
     */
    function setPriceWeights(
        uint8 _chainlinkWeight,
        uint8 _uniswapV2Weight,
        uint8 _uniswapV3Weight
    ) external onlyOwner {
        require(
            _chainlinkWeight + _uniswapV2Weight + _uniswapV3Weight == 100,
            "Weights must sum to 100"
        );

        chainlinkWeight = _chainlinkWeight;
        uniswapV2Weight = _uniswapV2Weight;
        uniswapV3Weight = _uniswapV3Weight;

        emit PriceWeightsUpdated(_chainlinkWeight, _uniswapV2Weight, _uniswapV3Weight);
    }

    /**
     * @dev Get BWS price in ETH from all available sources
     */
    function getBWSPriceInETH() external view override returns (uint256) {
        uint256 weightedPrice = 0;
        uint256 totalWeight = 0;

        // Try to get price from each source
        (uint256 chainlinkPrice, bool chainlinkValid) = _getChainlinkPrice();
        (uint256 v2Price, bool v2Valid) = _getUniswapV2Price();
        (uint256 v3Price, bool v3Valid) = _getUniswapV3Price();

        // Calculate weighted average from valid sources
        if (chainlinkValid) {
            weightedPrice += chainlinkPrice * chainlinkWeight;
            totalWeight += chainlinkWeight;
        }

        if (v2Valid) {
            weightedPrice += v2Price * uniswapV2Weight;
            totalWeight += uniswapV2Weight;
        }

        if (v3Valid) {
            weightedPrice += v3Price * uniswapV3Weight;
            totalWeight += uniswapV3Weight;
        }

        // If we have at least one valid price source
        if (totalWeight > 0) {
            return weightedPrice / totalWeight;
        }

        // If all sources fail, use fallback price
        require(fallbackPrice > 0, "No valid price available");
        return fallbackPrice;
    }

    /**
     * @dev Get latest price data with metadata
     */
    function getLatestPriceData() external view override returns (
        uint256 price,
        uint256 timestamp,
        uint8 decimals
    ) {
        price = this.getBWSPriceInETH();
        timestamp = block.timestamp;
        decimals = 18; // ETH has 18 decimals
    }

    /**
     * @dev Check if price is stale
     */
    function isPriceStale(uint256 maxAge) external view override returns (bool) {
        // Check if we have fresh data from any source
        if (address(chainlinkPriceFeed) != address(0)) {
            (, , , uint256 updatedAt, ) = chainlinkPriceFeed.latestRoundData();
            if (block.timestamp - updatedAt <= maxAge) {
                return false;
            }
        }

        // For Uniswap, we consider it fresh if a trade happened recently
        if (address(uniswapV2Pair) != address(0)) {
            (, , uint32 blockTimestampLast) = uniswapV2Pair.getReserves();
            if (block.timestamp - blockTimestampLast <= maxAge) {
                return false;
            }
        }

        // Check fallback price age
        return block.timestamp - fallbackPriceTimestamp > maxAge;
    }

    /**
     * @dev Get price from Chainlink
     */
    function _getChainlinkPrice() private view returns (uint256 price, bool valid) {
        if (address(chainlinkPriceFeed) == address(0)) {
            return (0, false);
        }

        try chainlinkPriceFeed.latestRoundData() returns (
            uint80,
            int256 _price,
            uint256,
            uint256 updatedAt,
            uint80
        ) {
            // Check if price is stale
            if (block.timestamp - updatedAt > PRICE_STALENESS_THRESHOLD) {
                return (0, false);
            }

            // Chainlink returns price with 8 decimals, convert to 18
            if (_price > 0) {
                return (uint256(_price) * 1e10, true);
            }
        } catch {
            // Chainlink call failed
        }

        return (0, false);
    }

    /**
     * @dev Get price from Uniswap V2
     */
    function _getUniswapV2Price() private view returns (uint256 price, bool valid) {
        if (address(uniswapV2Pair) == address(0)) {
            return (0, false);
        }

        try uniswapV2Pair.getReserves() returns (
            uint112 reserve0,
            uint112 reserve1,
            uint32 blockTimestampLast
        ) {
            // Check if data is fresh
            if (block.timestamp - blockTimestampLast > PRICE_STALENESS_THRESHOLD) {
                return (0, false);
            }

            address token0 = uniswapV2Pair.token0();

            // Determine which reserve is BWS and which is WETH
            uint256 bwsReserve;
            uint256 wethReserve;

            if (token0 == bwsToken) {
                bwsReserve = reserve0;
                wethReserve = reserve1;
            } else {
                bwsReserve = reserve1;
                wethReserve = reserve0;
            }

            if (bwsReserve > 0) {
                // Price = WETH per BWS
                price = (wethReserve * 1e18) / bwsReserve;
                return (price, true);
            }
        } catch {
            // V2 call failed
        }

        return (0, false);
    }

    /**
     * @dev Get price from Uniswap V3 (using TWAP)
     */
    function _getUniswapV3Price() private view returns (uint256 price, bool valid) {
        if (address(uniswapV3Pool) == address(0)) {
            return (0, false);
        }

        try uniswapV3Pool.slot0() returns (
            uint160 sqrtPriceX96,
            int24,
            uint16,
            uint16 observationCardinality,
            uint16,
            uint8,
            bool
        ) {
            if (observationCardinality == 0) {
                return (0, false);
            }

            // Calculate price from sqrtPriceX96
            // price = (sqrtPriceX96 / 2^96)^2
            uint256 priceX192 = uint256(sqrtPriceX96) * uint256(sqrtPriceX96);

            address token0 = uniswapV3Pool.token0();

            // Adjust for token order
            if (token0 == bwsToken) {
                // Price is WETH/BWS
                price = (priceX192 * 1e18) >> 192;
            } else {
                // Price is BWS/WETH, need to invert
                // Use safe math to avoid overflow
                if (priceX192 > 0) {
                    price = (1e18 * 1e18) / ((priceX192 * 1e18) >> 192);
                }
            }

            return (price, true);
        } catch {
            // V3 call failed
        }

        return (0, false);
    }

    /**
     * @dev Update fallback price (emergency use only)
     */
    function updateFallbackPrice(uint256 _price) external onlyOwner {
        require(_price > 0, "Invalid price");
        fallbackPrice = _price;
        fallbackPriceTimestamp = block.timestamp;
        emit FallbackPriceUpdated(_price, block.timestamp);
    }

    /**
     * @dev Get all price sources for debugging
     */
    function getAllPrices() external view returns (
        uint256 chainlinkPrice,
        bool chainlinkValid,
        uint256 v2Price,
        bool v2Valid,
        uint256 v3Price,
        bool v3Valid,
        uint256 weightedAverage
    ) {
        (chainlinkPrice, chainlinkValid) = _getChainlinkPrice();
        (v2Price, v2Valid) = _getUniswapV2Price();
        (v3Price, v3Valid) = _getUniswapV3Price();
        weightedAverage = this.getBWSPriceInETH();
    }
}"
    },
    "contracts/interfaces/IPriceOracle.sol": {
      "content": "// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.20;

/**
 * @title IPriceOracle
 * @dev Interface for price oracle implementations
 */
interface IPriceOracle {
    /**
     * @dev Get the latest price of BWS token in ETH
     * @return price The price of 1 BWS token in wei
     */
    function getBWSPriceInETH() external view returns (uint256 price);

    /**
     * @dev Get the latest price with additional metadata
     * @return price The price of 1 BWS token in wei
     * @return timestamp The timestamp of the price update
     * @return decimals The number of decimals in the price
     */
    function getLatestPriceData() external view returns (
        uint256 price,
        uint256 timestamp,
        uint8 decimals
    );

    /**
     * @dev Check if the price feed is stale
     * @param maxAge Maximum age in seconds for the price to be considered fresh
     * @return isStale True if the price is older than maxAge
     */
    function isPriceStale(uint256 maxAge) external view returns (bool isStale);
}

/**
 * @title AggregatorV3Interface
 * @dev Chainlink Price Feed Interface
 */
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 price,
        uint256 startedAt,
        uint256 updatedAt,
        uint80 answeredInRound
    );
}"
    }
  },
  "settings": {
    "optimizer": {
      "enabled": true,
      "runs": 200
    },
    "evmVersion": "paris",
    "outputSelection": {
      "*": {
        "*": [
          "evm.bytecode",
          "evm.deployedBytecode",
          "devdoc",
          "userdoc",
          "metadata",
          "abi"
        ]
      }
    }
  }
}}

Tags:
Multisig, Multi-Signature, Factory, Oracle|addr:0x36cb99096e8442417cadcbaa1d780c72fe393c7a|verified:true|block:23411889|tx:0x6aae95fa7cdeb472c36d435ab0f6da62cf9c29a6cd816e0b4bd42c69c4d83320|first_check:1758464535

Submitted on: 2025-09-21 16:22:16

Comments

Log in to comment.

No comments yet.