diff --git a/contracts/oracle/IPyth.sol b/contracts/oracle/IPyth.sol new file mode 100644 index 0000000..f5cec4b --- /dev/null +++ b/contracts/oracle/IPyth.sol @@ -0,0 +1,22 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity ^0.8.0; + +import "./IWormhole.sol"; + +/// @title Consume prices from the Pyth Network (https://pyth.network/). +/// @dev Please refer to the guidance at https://docs.pyth.network/documentation/pythnet-price-feeds/best-practices for how to consume prices safely. +/// @author Pyth Data Association +interface IPyth { + function wormhole() external view returns (IWormhole); + + function singleUpdateFeeInWei() external view returns (uint); + + /// @notice Update price feeds with given update messages. + /// This method requires the caller to pay a fee in wei; the required fee can be computed by calling + /// `getUpdateFee` with the length of the `updateData` array. + /// Prices will be updated if they are more recent than the current stored prices. + /// The call will succeed even if the update is not the most recent. + /// @dev Reverts if the transferred fee is not sufficient or the updateData is invalid. + /// @param updateData Array of price update data. + function updatePriceFeeds(bytes[] calldata updateData) external payable; +} diff --git a/contracts/oracle/IWormhole.sol b/contracts/oracle/IWormhole.sol new file mode 100644 index 0000000..29a0968 --- /dev/null +++ b/contracts/oracle/IWormhole.sol @@ -0,0 +1,142 @@ +// contracts/Messages.sol +// SPDX-License-Identifier: Apache 2 + +pragma solidity ^0.8.0; + +interface IWormhole { + struct GuardianSet { + address[] keys; + uint32 expirationTime; + } + + struct Signature { + bytes32 r; + bytes32 s; + uint8 v; + uint8 guardianIndex; + } + + struct VM { + uint8 version; + uint32 timestamp; + uint32 nonce; + uint16 emitterChainId; + bytes32 emitterAddress; + uint64 sequence; + uint8 consistencyLevel; + bytes payload; + + uint32 guardianSetIndex; + Signature[] signatures; + + bytes32 hash; + } + + struct ContractUpgrade { + bytes32 module; + uint8 action; + uint16 chain; + + address newContract; + } + + struct GuardianSetUpgrade { + bytes32 module; + uint8 action; + uint16 chain; + + GuardianSet newGuardianSet; + uint32 newGuardianSetIndex; + } + + struct SetMessageFee { + bytes32 module; + uint8 action; + uint16 chain; + + uint256 messageFee; + } + + struct TransferFees { + bytes32 module; + uint8 action; + uint16 chain; + + uint256 amount; + bytes32 recipient; + } + + struct RecoverChainId { + bytes32 module; + uint8 action; + + uint256 evmChainId; + uint16 newChainId; + } + + event LogMessagePublished(address indexed sender, uint64 sequence, uint32 nonce, bytes payload, uint8 consistencyLevel); + event ContractUpgraded(address indexed oldContract, address indexed newContract); + event GuardianSetAdded(uint32 indexed index); + + function publishMessage( + uint32 nonce, + bytes memory payload, + uint8 consistencyLevel + ) external payable returns (uint64 sequence); + + function initialize() external; + + function parseAndVerifyVM(bytes calldata encodedVM) external view returns (VM memory vm, bool valid, string memory reason); + + function verifyVM(VM memory vm) external view returns (bool valid, string memory reason); + + function verifySignatures(bytes32 hash, Signature[] memory signatures, GuardianSet memory guardianSet) external pure returns (bool valid, string memory reason); + + function parseVM(bytes memory encodedVM) external pure returns (VM memory vm); + + function quorum(uint numGuardians) external pure returns (uint numSignaturesRequiredForQuorum); + + function getGuardianSet(uint32 index) external view returns (GuardianSet memory); + + function getCurrentGuardianSetIndex() external view returns (uint32); + + function getGuardianSetExpiry() external view returns (uint32); + + function governanceActionIsConsumed(bytes32 hash) external view returns (bool); + + function isInitialized(address impl) external view returns (bool); + + function chainId() external view returns (uint16); + + function isFork() external view returns (bool); + + function governanceChainId() external view returns (uint16); + + function governanceContract() external view returns (bytes32); + + function messageFee() external view returns (uint256); + + function evmChainId() external view returns (uint256); + + function nextSequence(address emitter) external view returns (uint64); + + function parseContractUpgrade(bytes memory encodedUpgrade) external pure returns (ContractUpgrade memory cu); + + function parseGuardianSetUpgrade(bytes memory encodedUpgrade) external pure returns (GuardianSetUpgrade memory gsu); + + function parseSetMessageFee(bytes memory encodedSetMessageFee) external pure returns (SetMessageFee memory smf); + + function parseTransferFees(bytes memory encodedTransferFees) external pure returns (TransferFees memory tf); + + function parseRecoverChainId(bytes memory encodedRecoverChainId) external pure returns (RecoverChainId memory rci); + + function submitContractUpgrade(bytes memory _vm) external; + + function submitSetMessageFee(bytes memory _vm) external; + + function submitNewGuardianSet(bytes memory _vm) external; + + function submitTransferFees(bytes memory _vm) external; + + function submitRecoverChainId(bytes memory _vm) external; +} \ No newline at end of file diff --git a/contracts/oracle/PythVerifier.sol b/contracts/oracle/PythVerifier.sol new file mode 100644 index 0000000..03bdefa --- /dev/null +++ b/contracts/oracle/PythVerifier.sol @@ -0,0 +1,36 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 + +pragma solidity ^0.8.0; + +import "./IPyth.sol"; + +/// @title Verify oracle committed data for pyth +/// @author zk.link +contract PythVerifier { + + /// @notice The pyth contract + IPyth public immutable pyth; + + constructor(IPyth _pyth) { + pyth = _pyth; + } + + function verify(uint32 guardianSetIndex, bytes32 guardianSetAddressHash, uint256 totalNumUpdates) external { + // verify guardian set + IWormhole wormhole = pyth.wormhole(); + IWormhole.GuardianSet memory guardianSet = wormhole.getGuardianSet(guardianSetIndex); + require(guardianSet.keys.length > 0, "Invalid guardian set index"); + require(guardianSetIndex == wormhole.getCurrentGuardianSetIndex() || guardianSet.expirationTime >= block.timestamp, "Guardian set has expired"); + + // check the address set used to verify signature offchain is valid + bytes memory addressHashContent; + for (uint256 i = 0; i < guardianSet.keys.length; ++i) { + addressHashContent = abi.encodePacked(addressHashContent, guardianSet.keys[i]); + } + require(keccak256(addressHashContent) == guardianSetAddressHash, "Invalid guardian set address hash"); + + // calculate fee that need to pay for pyth + uint256 requiredFee = pyth.singleUpdateFeeInWei() * totalNumUpdates; + pyth.updatePriceFeeds{value: requiredFee}(new bytes[](0)); + } +}