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;
}
}
Submitted on: 2025-09-18 11:34:40
Comments
Log in to comment.
No comments yet.