| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246 |
- // contracts/Bridge.sol
- // SPDX-License-Identifier: Apache 2
- pragma solidity ^0.8.0;
- import "../libraries/external/BytesLib.sol";
- import "@pythnetwork/pyth-sdk-solidity/AbstractPyth.sol";
- import "@pythnetwork/pyth-sdk-solidity/PythStructs.sol";
- import "./PythGetters.sol";
- import "./PythSetters.sol";
- import "./PythInternalStructs.sol";
- abstract contract Pyth is PythGetters, PythSetters, AbstractPyth {
- using BytesLib for bytes;
- function _initialize(
- address wormhole,
- uint16 pyth2WormholeChainId,
- bytes32 pyth2WormholeEmitter
- ) internal {
- setWormhole(wormhole);
- setPyth2WormholeChainId(pyth2WormholeChainId);
- setPyth2WormholeEmitter(pyth2WormholeEmitter);
- }
- function updatePriceBatchFromVm(bytes calldata encodedVm) private returns (PythInternalStructs.BatchPriceAttestation memory bpa) {
- (IWormhole.VM memory vm, bool valid, string memory reason) = wormhole().parseAndVerifyVM(encodedVm);
- require(valid, reason);
- require(verifyPythVM(vm), "invalid data source chain/emitter ID");
- PythInternalStructs.BatchPriceAttestation memory batch = parseBatchPriceAttestation(vm.payload);
- uint freshPrices = 0;
- for (uint i = 0; i < batch.attestations.length; i++) {
- PythInternalStructs.PriceAttestation memory attestation = batch.attestations[i];
-
- PythInternalStructs.PriceInfo memory newPriceInfo = createNewPriceInfo(attestation);
- PythInternalStructs.PriceInfo memory latestPrice = latestPriceInfo(attestation.priceId);
- bool fresh = false;
- if(newPriceInfo.priceFeed.price.publishTime > latestPrice.priceFeed.price.publishTime) {
- freshPrices += 1;
- fresh = true;
- setLatestPriceInfo(attestation.priceId, newPriceInfo);
- }
- emit PriceFeedUpdate(attestation.priceId, fresh, vm.emitterChainId, vm.sequence, latestPrice.priceFeed.price.publishTime,
- newPriceInfo.priceFeed.price.publishTime, newPriceInfo.priceFeed.price.price, newPriceInfo.priceFeed.price.conf);
- }
- emit BatchPriceFeedUpdate(vm.emitterChainId, vm.sequence, batch.attestations.length, freshPrices);
- return batch;
- }
- function updatePriceFeeds(bytes[] calldata updateData) public override payable {
- uint requiredFee = getUpdateFee(updateData);
- require(msg.value >= requiredFee, "insufficient paid fee amount");
-
- for(uint i = 0; i < updateData.length; i++) {
- updatePriceBatchFromVm(updateData[i]);
- }
- emit UpdatePriceFeeds(msg.sender, updateData.length, requiredFee);
- }
- /// This method is deprecated, please use the `getUpdateFee(bytes[])` instead.
- function getUpdateFee(uint updateDataSize) public view returns (uint feeAmount) {
- return singleUpdateFeeInWei() * updateDataSize;
- }
- function getUpdateFee(bytes[] calldata updateData) public override view returns (uint feeAmount) {
- return singleUpdateFeeInWei() * updateData.length;
- }
- function createNewPriceInfo(PythInternalStructs.PriceAttestation memory pa) private view returns (PythInternalStructs.PriceInfo memory info) {
- info.attestationTime = pa.attestationTime;
- info.arrivalTime = block.timestamp;
- info.arrivalBlock = block.number;
- info.priceFeed.id = pa.priceId;
- PythInternalStructs.PriceAttestationStatus status = PythInternalStructs.PriceAttestationStatus(pa.status);
- if (status == PythInternalStructs.PriceAttestationStatus.TRADING) {
- info.priceFeed.price.price = pa.price;
- info.priceFeed.price.conf = pa.conf;
- info.priceFeed.price.publishTime = pa.publishTime;
- info.priceFeed.emaPrice.publishTime = pa.publishTime;
- } else {
- info.priceFeed.price.price = pa.prevPrice;
- info.priceFeed.price.conf = pa.prevConf;
- info.priceFeed.price.publishTime = pa.prevPublishTime;
- // The EMA is last updated when the aggregate had trading status,
- // so, we use prev_publish_time (the time when the aggregate last had trading status).
- info.priceFeed.emaPrice.publishTime = pa.prevPublishTime;
- }
- info.priceFeed.price.expo = pa.expo;
- info.priceFeed.emaPrice.price = pa.emaPrice;
- info.priceFeed.emaPrice.conf = pa.emaConf;
- info.priceFeed.emaPrice.expo = pa.expo;
- return info;
- }
- function verifyPythVM(IWormhole.VM memory vm) private view returns (bool valid) {
- return isValidDataSource(vm.emitterChainId, vm.emitterAddress);
- }
- function parseBatchPriceAttestation(bytes memory encoded) public pure returns (PythInternalStructs.BatchPriceAttestation memory bpa) {
- uint index = 0;
- // Check header
- bpa.header.magic = encoded.toUint32(index);
- index += 4;
- require(bpa.header.magic == 0x50325748, "invalid magic value");
- bpa.header.versionMajor = encoded.toUint16(index);
- index += 2;
- require(bpa.header.versionMajor == 3, "invalid version major, expected 3");
- bpa.header.versionMinor = encoded.toUint16(index);
- index += 2;
- require(bpa.header.versionMinor >= 0, "invalid version minor, expected 0 or more");
- bpa.header.hdrSize = encoded.toUint16(index);
- index += 2;
- // NOTE(2022-04-19): Currently, only payloadId comes after
- // hdrSize. Future extra header fields must be read using a
- // separate offset to respect hdrSize, i.e.:
- //
- // uint hdrIndex = 0;
- // bpa.header.payloadId = encoded.toUint8(index + hdrIndex);
- // hdrIndex += 1;
- //
- // bpa.header.someNewField = encoded.toUint32(index + hdrIndex);
- // hdrIndex += 4;
- //
- // // Skip remaining unknown header bytes
- // index += bpa.header.hdrSize;
- bpa.header.payloadId = encoded.toUint8(index);
- // Skip remaining unknown header bytes
- index += bpa.header.hdrSize;
- // Payload ID of 2 required for batch headerBa
- require(bpa.header.payloadId == 2, "invalid payload ID, expected 2 for BatchPriceAttestation");
- // Parse the number of attestations
- bpa.nAttestations = encoded.toUint16(index);
- index += 2;
- // Parse the attestation size
- bpa.attestationSize = encoded.toUint16(index);
- index += 2;
- require(encoded.length == (index + (bpa.attestationSize * bpa.nAttestations)), "invalid BatchPriceAttestation size");
- bpa.attestations = new PythInternalStructs.PriceAttestation[](bpa.nAttestations);
- // Deserialize each attestation
- for (uint j=0; j < bpa.nAttestations; j++) {
- // NOTE: We don't advance the global index immediately.
- // attestationIndex is an attestation-local offset used
- // for readability and easier debugging.
- uint attestationIndex = 0;
- // Attestation
- bpa.attestations[j].productId = encoded.toBytes32(index + attestationIndex);
- attestationIndex += 32;
- bpa.attestations[j].priceId = encoded.toBytes32(index + attestationIndex);
- attestationIndex += 32;
- bpa.attestations[j].price = int64(encoded.toUint64(index + attestationIndex));
- attestationIndex += 8;
- bpa.attestations[j].conf = encoded.toUint64(index + attestationIndex);
- attestationIndex += 8;
- bpa.attestations[j].expo = int32(encoded.toUint32(index + attestationIndex));
- attestationIndex += 4;
- bpa.attestations[j].emaPrice = int64(encoded.toUint64(index + attestationIndex));
- attestationIndex += 8;
- bpa.attestations[j].emaConf = encoded.toUint64(index + attestationIndex);
- attestationIndex += 8;
- bpa.attestations[j].status = encoded.toUint8(index + attestationIndex);
- attestationIndex += 1;
- bpa.attestations[j].numPublishers = encoded.toUint32(index + attestationIndex);
- attestationIndex += 4;
- bpa.attestations[j].maxNumPublishers = encoded.toUint32(index + attestationIndex);
- attestationIndex += 4;
- bpa.attestations[j].attestationTime = encoded.toUint64(index + attestationIndex);
- attestationIndex += 8;
- bpa.attestations[j].publishTime = encoded.toUint64(index + attestationIndex);
- attestationIndex += 8;
- bpa.attestations[j].prevPublishTime = encoded.toUint64(index + attestationIndex);
- attestationIndex += 8;
- bpa.attestations[j].prevPrice = int64(encoded.toUint64(index + attestationIndex));
- attestationIndex += 8;
- bpa.attestations[j].prevConf = encoded.toUint64(index + attestationIndex);
- attestationIndex += 8;
- require(attestationIndex <= bpa.attestationSize, "INTERNAL: Consumed more than `attestationSize` bytes");
- // Respect specified attestation size for forward-compat
- index += bpa.attestationSize;
- }
- }
- function queryPriceFeed(bytes32 id) public view override returns (PythStructs.PriceFeed memory priceFeed){
- // Look up the latest price info for the given ID
- PythInternalStructs.PriceInfo memory info = latestPriceInfo(id);
- require(info.priceFeed.id != 0, "price feed for the given id is not pushed or does not exist");
- return info.priceFeed;
- }
- function priceFeedExists(bytes32 id) public override view returns (bool) {
- PythInternalStructs.PriceInfo memory info = latestPriceInfo(id);
- return (info.priceFeed.id != 0);
- }
- function getValidTimePeriod() public override view returns (uint) {
- return validTimePeriodSeconds();
- }
- function version() public pure returns (string memory) {
- return "1.1.0";
- }
- }
|