OnChainPriceUtils

Description:

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

Blockchain: Ethereum

Source Code: View Code On The Blockchain

Solidity Source Code:

// File: @openzeppelin/contracts/utils/Context.sol


// 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;
    }
}

// File: @openzeppelin/contracts/access/Ownable.sol


// OpenZeppelin Contracts (last updated v5.0.0) (access/Ownable.sol)

pragma solidity ^0.8.20;


/**
 * @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);
    }
}

// File: OnChainPriceFinder.sol


pragma solidity ^0.8.20;


/// @title OnChainPriceUtils - 链上池子数据查询工具库
/// @author Alpha Catalyst
/// @notice 提供链上DEX池子原始数据查询功能,返回储备量、流动性等信息供前端计算价格。
/// @dev 该合约利用 `staticcall` 来安全地查询外部DEX数据,返回原始数据不进行价格计算。
contract OnChainPriceUtils is Ownable {

    // =============================================================================
    //                                结构体与常量
    // =============================================================================

    /// @notice 池子原始数据结构体
    struct PoolInfo {
        bool isValid;           // 池子是否存在且拥有有效流动性
        uint256 reserve0;       // token0的储备量(原始decimal)
        uint256 reserve1;       // token1的储备量(原始decimal)
        address token0;         // token0地址(较小地址)
        address token1;         // token1地址(较大地址)
        uint160 sqrtPriceX96;   // V3池子的sqrtPrice(仅V3有效)
        uint128 liquidity;      // V3池子的流动性(仅V3有效)
    }

    /// @dev Uniswap V3储备估算用Q96 (2^96)
    uint256 private constant Q96 = 0x1000000000000000000000000;

    // =============================================================================
    //                          原生资产价格配置的状态变量
    // =============================================================================
    
    /// @notice 用于计算原生资产价格的 DEX V2 工厂地址
    address public nativePriceDexFactoryV2;
    /// @notice 用于计算原生资产价格的稳定币地址 (例如 USDT, USDC)
    address public nativePriceStablecoin;
    /// @notice 原生包装资产地址 (例如 WETH, WBNB)
    address public nativeWrappedAsset;
    
    /// @notice 用于计算原生资产价格的稳定币的小数位
    uint8 public nativePriceStablecoinDecimals;
    /// @notice 原生包装资产的小数位
    uint8 public nativeWrappedAssetDecimals;

    // V3 配置状态变量
    /// @notice 用于计算原生资产价格的 DEX V3 工厂地址
    address public nativePriceDexFactoryV3;
    /// @notice V3 池子支持的费率数组,用于寻找最优流动性池子
    uint24[] public nativePriceV3Fees;

    /// @notice 初始化合约,设置部署者为所有者
    constructor() Ownable(msg.sender) {}


    // =============================================================================
    //                                  构造与管理
    // =============================================================================


    /// @notice 设置用于计算原生资产价格的配置,只能由所有者调用
    function setNativePriceConfig(
        address _newDexFactoryV2, 
        address _newStablecoin, 
        uint8 _newStablecoinDecimals,
        address _newWrappedAsset,
        uint8 _newWrappedAssetDecimals
    ) external onlyOwner {
        nativePriceDexFactoryV2 = _newDexFactoryV2;
        nativePriceStablecoin = _newStablecoin;
        nativePriceStablecoinDecimals = _newStablecoinDecimals;
        nativeWrappedAsset = _newWrappedAsset;
        nativeWrappedAssetDecimals = _newWrappedAssetDecimals;
    }

    /// @notice 设置用于计算原生资产价格的 V3 配置,只能由所有者调用
    function setNativePriceConfigV3(
        address _newDexFactoryV3,
        uint24[] calldata _newFees
    ) external onlyOwner {
        nativePriceDexFactoryV3 = _newDexFactoryV3;
        
        // 清空现有费率数组并重新赋值
        delete nativePriceV3Fees;
        for (uint256 i = 0; i < _newFees.length; i++) {
            nativePriceV3Fees.push(_newFees[i]);
        }
    }


    // =============================================================================
    //                          核心视图函数 (状态化与无状态)
    // =============================================================================

    /// @notice 获取预配置的原生资产 V2 池子原始数据
    /// @return pool 包含储备量等原始数据
    function getNativeAssetPoolInfoV2() external view returns (PoolInfo memory pool) {
        pool = _getV2PoolInfoInternal(
            nativePriceDexFactoryV2, 
            nativeWrappedAsset, 
            nativePriceStablecoin
        );
        
        require(pool.isValid, "OnChainPriceUtils: Native price pool is not valid or has no liquidity.");
    }

    /// @notice 智能获取预配置的原生资产池子原始数据,优先使用 V3,降级使用 V2
    /// @return pool 包含储备量等原始数据
    function getNativeAssetPoolInfo() external view returns (PoolInfo memory pool) {
        // 首先检查 V3 配置是否有效
        if (_isV3ConfigValid()) {
            // 尝试使用 V3 获取池子信息,但不抛出异常
            try this.getNativeAssetPoolInfoV3() returns (PoolInfo memory v3Pool) {
                return v3Pool;
            } catch {
                // V3 失败,继续尝试 V2
            }
        }
        
        // V3 无效或失败,尝试使用 V2
        if (_isV2ConfigValid()) {
            return this.getNativeAssetPoolInfoV2();
        }
        
        // 所有配置都无效
        revert("OnChainPriceUtils: No valid native price configuration found (both V2 and V3 unavailable)");
    }

    /// @notice 获取预配置的原生资产 V3 池子原始数据,从多个费率中选择流动性最大的池子
    /// @return pool 包含储备量等原始数据
    function getNativeAssetPoolInfoV3() external view returns (PoolInfo memory pool) {
        require(nativePriceDexFactoryV3 != address(0), "OnChainPriceUtils: V3 factory not configured");
        require(nativePriceV3Fees.length > 0, "OnChainPriceUtils: V3 fees not configured");
        
        uint128 maxLiquidity = 0;
        PoolInfo memory bestPool;
        
        // 遍历所有配置的费率,寻找流动性最大的池子
        for (uint256 i = 0; i < nativePriceV3Fees.length; i++) {
            PoolInfo memory currentPool = this.getV3PoolInfo(
                nativePriceDexFactoryV3,
                nativeWrappedAsset,
                nativePriceStablecoin,
                nativePriceV3Fees[i]
            );
            
            // 如果当前池子有效且流动性更大,则更新最佳池子
            if (currentPool.isValid && currentPool.liquidity > maxLiquidity) {
                maxLiquidity = currentPool.liquidity;
                bestPool = currentPool;
            }
        }
        
        require(bestPool.isValid, "OnChainPriceUtils: No valid V3 native price pool found");
        pool = bestPool;
    }


    /// @notice 获取任意V2风格池子的原始数据
    function getV2PoolInfo(
        address _factory,
        address _tokenA,
        address _tokenB
    ) external view returns (PoolInfo memory) {
        return _getV2PoolInfoInternal(_factory, _tokenA, _tokenB);
    }


    /// @notice 获取任意V3风格池子的原始数据
    function getV3PoolInfo(
        address _factory,
        address _tokenA,
        address _tokenB,
        uint24 _fee
    ) external view returns (PoolInfo memory pool) {
        address poolAddress = _getV3PoolAddress(_factory, _tokenA, _tokenB, _fee);
        if (poolAddress == address(0)) {
            pool.isValid = false;
            return pool;
        }

        (uint160 sqrtPriceX96, uint128 liquidity, bool success) = _getV3PoolData(poolAddress);
        if (!success || liquidity == 0) {
            pool.isValid = false;
            return pool;
        }

        // 设置token0和token1地址顺序
        pool.token0 = _tokenA < _tokenB ? _tokenA : _tokenB;
        pool.token1 = _tokenA < _tokenB ? _tokenB : _tokenA;
        
        // 存储V3特有数据
        pool.sqrtPriceX96 = sqrtPriceX96;
        pool.liquidity = liquidity;
        
        // 直接查询池子合约的代币余额
        uint256 token0Balance = _getTokenBalance(pool.token0, poolAddress);
        uint256 token1Balance = _getTokenBalance(pool.token1, poolAddress);
        
        pool.reserve0 = token0Balance;
        pool.reserve1 = token1Balance;
        pool.isValid = true;
    }

    /// @notice 获取代币在指定地址的余额
    /// @param _token 代币合约地址
    /// @param _account 查询余额的账户地址
    /// @return balance 代币余额
    function _getTokenBalance(address _token, address _account) internal view returns (uint256 balance) {
        bytes memory data = abi.encodeWithSignature("balanceOf(address)", _account);
        (bool success, bytes memory returnData) = _token.staticcall(data);
        if (success && returnData.length >= 32) {
            balance = abi.decode(returnData, (uint256));
        }
    }

    
    // =============================================================================
    //                                内部计算函数
    // =============================================================================

    /// @notice 检查 V2 配置是否有效
    /// @return 如果 V2 配置完整且有效返回 true
    function _isV2ConfigValid() internal view returns (bool) {
        return nativePriceDexFactoryV2 != address(0) 
            && nativePriceStablecoin != address(0) 
            && nativeWrappedAsset != address(0);
    }

    /// @notice 检查 V3 配置是否有效
    /// @return 如果 V3 配置完整且有效返回 true
    function _isV3ConfigValid() internal view returns (bool) {
        return nativePriceDexFactoryV3 != address(0) 
            && nativePriceStablecoin != address(0) 
            && nativeWrappedAsset != address(0) 
            && nativePriceV3Fees.length > 0;
    }

    function _getV2PoolInfoInternal(
        address _factory,
        address _tokenA,
        address _tokenB
    ) internal view returns (PoolInfo memory pool) {
        address pairAddress = _getPairAddress(_factory, _tokenA, _tokenB);
        if (pairAddress == address(0)) {
            pool.isValid = false;
            return pool;
        }

        (uint256 reserve0, uint256 reserve1, bool success) = _getReserves(pairAddress);
        if (!success || reserve0 == 0 || reserve1 == 0) {
            pool.isValid = false;
            return pool;
        }
        
        // 设置token地址顺序和储备量
        pool.token0 = _tokenA < _tokenB ? _tokenA : _tokenB;
        pool.token1 = _tokenA < _tokenB ? _tokenB : _tokenA;
        pool.reserve0 = reserve0;
        pool.reserve1 = reserve1;
        
        // V2池子没有sqrtPriceX96和liquidity,设为0
        pool.sqrtPriceX96 = 0;
        pool.liquidity = 0;
        pool.isValid = true;
    }



    // =============================================================================
    //                                底层静态调用
    // =============================================================================
    
    function _getPairAddress(address _factory, address _tokenA, address _tokenB) internal view returns (address pair) {
        (bool success, bytes memory result) = _factory.staticcall(abi.encodeWithSignature("getPair(address,address)", _tokenA, _tokenB));
        if (success && result.length == 32) pair = abi.decode(result, (address));
    }
    
    function _getReserves(address _pair) internal view returns (uint112 reserve0, uint112 reserve1, bool success) {
        bytes memory result;
        (success, result) = _pair.staticcall(abi.encodeWithSignature("getReserves()"));
        if (success && result.length >= 64) (reserve0, reserve1) = abi.decode(result, (uint112, uint112));
    }
    
    function _getV3PoolAddress(address _factory, address _tokenA, address _tokenB, uint24 _fee) internal view returns (address pool) {
        (bool success, bytes memory result) = _factory.staticcall(abi.encodeWithSignature("getPool(address,address,uint24)", _tokenA, _tokenB, _fee));
        if (success && result.length == 32) pool = abi.decode(result, (address));
    }
    
    function _getV3PoolData(address _pool) internal view returns (uint160 sqrtPriceX96, uint128 liquidity, bool success) {
        (bool slot0Success, bytes memory slot0Result) = _pool.staticcall(abi.encodeWithSignature("slot0()"));
        (bool liquiditySuccess, bytes memory liquidityResult) = _pool.staticcall(abi.encodeWithSignature("liquidity()"));
        
        if (slot0Success && slot0Result.length >= 32) (sqrtPriceX96) = abi.decode(slot0Result, (uint160));
        if (liquiditySuccess && liquidityResult.length == 32) liquidity = abi.decode(liquidityResult, (uint128));
        success = slot0Success && liquiditySuccess;
    }
}

Tags:
Multisig, Liquidity, Multi-Signature, Factory|addr:0x8d19ac8a7ae49369e42aaa316746ba6c3f494b96|verified:true|block:23386564|tx:0x49a85229669ec64460de761478c52f807c22f1a2b16a122a06f47502fd5fef2b|first_check:1758188078

Submitted on: 2025-09-18 11:34:40

Comments

Log in to comment.

No comments yet.