Description:
Multi-signature wallet contract requiring multiple confirmations for transaction execution.
Blockchain: Ethereum
Source Code: View Code On The Blockchain
Solidity Source Code:
// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.20;
// src/core/interfaces/IAggregatorV3Interface.sol
interface IAggregatorV3Interface {
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);
}
// src/oracles/base/IPythMinimumOracle.sol
interface IPythMinimumOracle {
// A price with a degree of uncertainty, represented as a price +- a confidence interval.
//
// The confidence interval roughly corresponds to the standard error of a normal distribution.
// Both the price and confidence are stored in a fixed-point numeric representation,
// `x * (10^expo)`, where `expo` is the exponent.
//
// Please refer to the documentation at https://docs.pyth.network/documentation/pythnet-price-feeds/best-practices for how
// to how this price safely.
struct Price {
// Price
int64 price;
// Confidence interval around the price
uint64 conf;
// Price exponent
int32 expo;
// Unix timestamp describing when the price was published
uint publishTime;
}
// PriceFeed represents a current aggregate price from pyth publisher feeds.
struct PriceFeed {
// The price ID.
bytes32 id;
// Latest available price
Price price;
// Latest available exponentially-weighted moving average price
Price emaPrice;
}
/// @notice Returns the price feed with the ID specified.
/// @dev Reverts if the price does not exist.
/// @param id The Pyth Price Feed ID of which to fetch the PriceFeed.
function queryPriceFeed(bytes32 id) external view returns (PriceFeed memory priceFeed);
/// @notice Returns the price feed with the ID specified.
/// @dev Reverts if the price does not exist.
/// @param id The Pyth Price Feed ID of which to fetch the PriceFeed.
function getPrice(bytes32 id) external view returns (PriceFeed memory priceFeed);
function getPriceNoOlderThan(bytes32 id, uint256 age) external view returns (PriceFeed memory priceFeed);
function priceFeedExists(bytes32 id) external view returns (bool);
function getValidTimePeriod() external view returns (uint256);
}
// src/core/OwnableGuarded.sol
abstract contract OwnableGuarded {
// ----------------------------------------------------------------------------------------------------
// Constants
// ----------------------------------------------------------------------------------------------------
// Booleans are more expensive than uint256 or any type that takes up a full
// word because each write operation emits an extra SLOAD to first read the
// slot's contents, replace the bits taken up by the boolean, and then write
// back. This is the compiler's defense against contract upgrades and
// pointer aliasing, and it cannot be disabled.
// The values being non-zero value makes deployment a bit more expensive,
// but in exchange the refund on every call to nonReentrant will be lower in
// amount. Since refunds are capped to a percentage of the total
// transaction's gas, it is best to keep them low in cases like this one, to
// increase the likelihood of the full refund coming into effect.
uint256 private constant NOT_ENTERED = 1;
uint256 private constant ENTERED = 2;
// ----------------------------------------------------------------------------------------------------
// Errors
// ----------------------------------------------------------------------------------------------------
error OwnerOnly();
error OwnerAddressRequired();
error ReentrancyGuardReentrantCall();
// ----------------------------------------------------------------------------------------------------
// Storage layout
// ----------------------------------------------------------------------------------------------------
uint256 private _status;
address internal _owner;
// ----------------------------------------------------------------------------------------------------
// Events
// ----------------------------------------------------------------------------------------------------
/**
* @notice Triggers when contract ownership changes.
* @param previousOwner The previous owner of the contract.
* @param newOwner The new owner of the contract.
*/
event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
// ----------------------------------------------------------------------------------------------------
// Modifiers
// ----------------------------------------------------------------------------------------------------
/**
* @dev Prevents a contract from calling itself, directly or indirectly.
* Calling a `nonReentrant` function from another `nonReentrant`
* function is not supported. It is possible to prevent this from happening
* by making the `nonReentrant` function external, and making it call a
* `private` function that does the actual work.
*/
modifier nonReentrant() {
// On the first call to nonReentrant, _status will be NOT_ENTERED
if (_status == ENTERED) {
revert ReentrancyGuardReentrantCall();
}
// Any calls to nonReentrant after this point will fail
_status = ENTERED;
_;
// By storing the original value once again, a refund is triggered (see https://eips.ethereum.org/EIPS/eip-2200)
_status = NOT_ENTERED;
}
/**
* @dev Throws if called by any account other than the owner.
*/
modifier onlyOwner() {
if (msg.sender != _owner) revert OwnerOnly();
_;
}
// ----------------------------------------------------------------------------------------------------
// Functions
// ----------------------------------------------------------------------------------------------------
/**
* @notice Transfers ownership of the contract to the account specified.
* @param newOwner The address of the new owner.
*/
function transferOwnership(address newOwner) external virtual nonReentrant onlyOwner {
_transferOwnership(newOwner);
}
function _transferOwnership(address newOwner) internal virtual {
if (newOwner == address(0)) revert OwnerAddressRequired();
address oldOwner = _owner;
_owner = newOwner;
emit OwnershipTransferred(oldOwner, newOwner);
}
/**
* @notice Gets the owner of the contract.
* @return address The address who owns the contract.
*/
function owner() external view virtual returns (address) {
return _owner;
}
}
// src/oracles/PythOracleAdapter.sol
/**
* @title Oracle adapter for Pyth/ChainLink.
* @dev The contract validates the staleness of the Pyth feed by default.
*/
contract PythOracleAdapter is IAggregatorV3Interface, OwnableGuarded {
// ----------------------------------------------------------------------------
// Constants
// ----------------------------------------------------------------------------
/// @notice Strict mode. Check for staleness.
uint8 constant public FETCH_STRATEGY_STRICT = 0;
/// @notice Relaxed mode. No staleness.
uint8 constant public FETCH_STRATEGY_RELAXED = 1;
/// @notice Medium mode. Check for staleness within a limit expressed in seconds.
uint8 constant public FETCH_STRATEGY_WITH_LIMIT = 2;
// ----------------------------------------------------------------------------
// Errors
// ----------------------------------------------------------------------------
error NegativePrice();
error DecimalPlacesNotSupported();
error InvalidFetchStrategy();
error OracleDecimalsMismatch();
error InputRequired();
error InvalidThreshold();
// ----------------------------------------------------------------------------
// Tightly-packed storage layout
// ----------------------------------------------------------------------------
/// @notice The ID of the price feed reported by Pyth.
bytes32 public priceFeedId;
/// @notice The staleness threshold (in seconds) to apply on Pyth.
uint256 public stalenessThreshold;
/// @notice The network-wide Pyth contract address applicable to this network.
address public priceFeedContractAddress;
/// @notice The fetch strategy to use when getting price feeds from Pyth.
uint8 public fetchStrategy;
/// @dev The number of decimals reported by the Pyth oracle.
uint8 internal _pythDecimals;
uint8 internal _oracleDecimals;
uint256 internal _oracleVersion;
string internal _oracleDescription;
// ----------------------------------------------------------------------------
// Events
// ----------------------------------------------------------------------------
event FetchStrategyUpdated();
event StalenessThresholdUpdated();
// ----------------------------------------------------------------------------
// Constructor
// ----------------------------------------------------------------------------
constructor(
uint8 newOracleDecimals,
uint256 newOracleVersion,
string memory newOracleDescription,
bytes32 newPriceFeedId,
address priceFeedContractAddr
) {
_owner = msg.sender;
_initMe(newOracleDecimals, newOracleVersion, newOracleDescription, newPriceFeedId, priceFeedContractAddr);
}
// ----------------------------------------------------------------------------
// Functions
// ----------------------------------------------------------------------------
/**
* @notice Updates the fetch strategy for getting prices from Pyth.
* @dev Valid values are: 0 = strict, 1 = Relaxed, 2 = With a limit
* @param newFetchStrategy The new strategy.
*/
function updateFetchStrategy(uint8 newFetchStrategy) external nonReentrant onlyOwner {
if (
(newFetchStrategy != FETCH_STRATEGY_STRICT) &&
(newFetchStrategy != FETCH_STRATEGY_RELAXED) &&
(newFetchStrategy != FETCH_STRATEGY_WITH_LIMIT)
) revert InvalidFetchStrategy();
fetchStrategy = newFetchStrategy;
emit FetchStrategyUpdated();
}
/**
* @notice Updates the staleness threshold accepted by this oracle.
* @param newStalenessThreshold The new threshold, expressed in seconds.
*/
function updateStalenessThreshold(uint256 newStalenessThreshold) external nonReentrant onlyOwner {
if (newStalenessThreshold < 1) revert InvalidThreshold();
stalenessThreshold = newStalenessThreshold;
emit StalenessThresholdUpdated();
}
/// @dev Initializes the oracle
function _initMe(
uint8 newOracleDecimals,
uint256 newOracleVersion,
string memory newOracleDescription,
bytes32 newPriceFeedId,
address priceFeedContractAddr
) private {
if (newPriceFeedId == bytes32(0)) revert InputRequired();
if (newOracleDecimals < 6) revert DecimalPlacesNotSupported();
_oracleDecimals = newOracleDecimals;
_oracleVersion = newOracleVersion;
_oracleDescription = newOracleDescription;
// The network-wide Pyth contract address applicable to this chain, as reported at https://docs.pyth.network/price-feeds/core/contract-addresses/evm
// In Pyth, the contract address below tracks all of the price feeds for the entire network.
priceFeedContractAddress = priceFeedContractAddr;
// The ID of the price feed, as reported by Pyth at https://docs.pyth.network/price-feeds/core/price-feeds/price-feed-ids
priceFeedId = newPriceFeedId;
stalenessThreshold = IPythMinimumOracle(priceFeedContractAddress).getValidTimePeriod();
// Validate the Pyth contract address and Feed identifier. This call will revert in case of error.
IPythMinimumOracle.PriceFeed memory priceFeed = IPythMinimumOracle(priceFeedContractAddress).queryPriceFeed(priceFeedId);
// Notice that the exponent reported by the Pyth oracle can be negative.
uint256 decsAbsolute = (priceFeed.price.expo < 0) ? uint256(-1 * int256(priceFeed.price.expo)) : uint256(int256(priceFeed.price.expo));
// Cache the number of decimals reported by the Pyth oracle. The oracle should never change their decimal places.
_pythDecimals = uint8(decsAbsolute);
}
function decimals() external view override returns (uint8) {
return _oracleDecimals;
}
function description() external view override returns (string memory) {
return _oracleDescription;
}
function version() external view override returns (uint256) {
return _oracleVersion;
}
function latestRoundData() external view override returns (
uint80 roundId,
int256 answer,
uint256 startedAt,
uint256 updatedAt,
uint80 answeredInRound
) {
return _queryLatestRound();
}
function getRoundData(uint80) external view override returns (
uint80 roundId,
int256 answer,
uint256 startedAt,
uint256 updatedAt,
uint80 answeredInRound
) {
return _queryLatestRound();
}
/// @dev Reads a feed from Pyth using the strategy defined by the owner of this contract.
function _readFeed() private view returns (IPythMinimumOracle.PriceFeed memory priceFeed) {
// Price lookup
if (fetchStrategy == FETCH_STRATEGY_STRICT) {
// Strict. Checks for staleness.
priceFeed = IPythMinimumOracle(priceFeedContractAddress).getPrice(priceFeedId);
} else if (fetchStrategy == FETCH_STRATEGY_RELAXED) {
// Relaxed. Does not check for staleness
priceFeed = IPythMinimumOracle(priceFeedContractAddress).queryPriceFeed(priceFeedId);
} else if (fetchStrategy == FETCH_STRATEGY_WITH_LIMIT) {
// Get the feed. Throw if the feed is older than the staleness threshold.
priceFeed = IPythMinimumOracle(priceFeedContractAddress).getPriceNoOlderThan(priceFeedId, stalenessThreshold);
}
// The price reported by the Pyth oracle cannot be negative or zero, no matter what.
if (priceFeed.price.price < 1) revert NegativePrice();
// The exponent reported by the Pyth oracle should not change.
uint256 decs = (priceFeed.price.expo < 0) ? uint256(-1 * int256(priceFeed.price.expo)) : uint256(int256(priceFeed.price.expo));
if (uint8(decs) != _pythDecimals) revert OracleDecimalsMismatch();
}
/// @dev Grabs the price from Pyth and converts the result into a ChainLink-compatible output
function _queryLatestRound() private view returns (
uint80 roundId,
int256 answer,
uint256 startedAt,
uint256 updatedAt,
uint80 answeredInRound
) {
// Read the feed from Pyth
IPythMinimumOracle.PriceFeed memory priceFeed = _readFeed();
// Get the rough price reported by the Pyth oracle.
int256 price = int256(priceFeed.price.price);
// Scale down the price reported by Pyth, if applicable.
answer = (_oracleDecimals < _pythDecimals) ? int256(price) / int256(10 ** (_pythDecimals - _oracleDecimals)) : price;
// Pyth does not work with ChainLink rounds, so we always return "1" as the round ID
roundId = uint80(1);
answeredInRound = uint80(1);
startedAt = priceFeed.price.publishTime;
updatedAt = priceFeed.price.publishTime;
}
}
Submitted on: 2025-11-07 15:21:08
Comments
Log in to comment.
No comments yet.