200 lines
7.4 KiB
Solidity
200 lines
7.4 KiB
Solidity
// 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";
|
|
|
|
contract Pyth is PythGetters, PythSetters, AbstractPyth {
|
|
using BytesLib for bytes;
|
|
|
|
function initialize(
|
|
address wormhole,
|
|
uint16 pyth2WormholeChainId,
|
|
bytes32 pyth2WormholeEmitter
|
|
) virtual public {
|
|
setWormhole(wormhole);
|
|
setPyth2WormholeChainId(pyth2WormholeChainId);
|
|
setPyth2WormholeEmitter(pyth2WormholeEmitter);
|
|
}
|
|
|
|
function updatePriceBatchFromVm(bytes memory encodedVm) public returns (PythInternalStructs.BatchPriceAttestation memory bpa) {
|
|
(IWormhole.VM memory vm, bool valid, string memory reason) = wormhole().parseAndVerifyVM(encodedVm);
|
|
|
|
require(valid, reason);
|
|
require(verifyPythVM(vm), "invalid emitter");
|
|
|
|
PythInternalStructs.BatchPriceAttestation memory batch = parseBatchPriceAttestation(vm.payload);
|
|
|
|
for (uint i = 0; i < batch.attestations.length; i++) {
|
|
PythInternalStructs.PriceAttestation memory attestation = batch.attestations[i];
|
|
|
|
PythInternalStructs.PriceInfo memory latestPrice = latestPriceInfo(attestation.priceId);
|
|
|
|
if(attestation.timestamp > latestPrice.attestationTime) {
|
|
setLatestPriceInfo(attestation.priceId, newPriceInfo(attestation));
|
|
}
|
|
}
|
|
|
|
return batch;
|
|
}
|
|
|
|
function newPriceInfo(PythInternalStructs.PriceAttestation memory pa) private view returns (PythInternalStructs.PriceInfo memory info) {
|
|
info.attestationTime = pa.timestamp;
|
|
info.arrivalTime = block.timestamp;
|
|
info.arrivalBlock = block.number;
|
|
|
|
info.priceFeed.id = pa.priceId;
|
|
info.priceFeed.price = pa.price;
|
|
info.priceFeed.conf = pa.confidenceInterval;
|
|
info.priceFeed.status = PythStructs.PriceStatus(pa.status);
|
|
info.priceFeed.expo = pa.exponent;
|
|
info.priceFeed.emaPrice = pa.emaPrice.value;
|
|
info.priceFeed.emaConf = uint64(pa.emaConf.value);
|
|
info.priceFeed.productId = pa.productId;
|
|
|
|
// These aren't sent in the wire format yet
|
|
info.priceFeed.numPublishers = 0;
|
|
info.priceFeed.maxNumPublishers = 0;
|
|
return info;
|
|
}
|
|
|
|
function verifyPythVM(IWormhole.VM memory vm) private view returns (bool valid) {
|
|
if (vm.emitterChainId != pyth2WormholeChainId()) {
|
|
return false;
|
|
}
|
|
if (vm.emitterAddress != pyth2WormholeEmitter()) {
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
|
|
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.version = encoded.toUint16(index);
|
|
index += 2;
|
|
require(bpa.header.version == 2, "invalid version");
|
|
|
|
bpa.header.payloadId = encoded.toUint8(index);
|
|
index += 1;
|
|
// Payload ID of 2 required for batch header
|
|
require(bpa.header.payloadId == 2, "invalid payload ID");
|
|
|
|
// 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++) {
|
|
// Header
|
|
bpa.attestations[j].header.magic = encoded.toUint32(index);
|
|
index += 4;
|
|
require(bpa.attestations[j].header.magic == 0x50325748, "invalid magic value");
|
|
|
|
bpa.attestations[j].header.version = encoded.toUint16(index);
|
|
index += 2;
|
|
require(bpa.attestations[j].header.version == 2, "invalid version");
|
|
|
|
bpa.attestations[j].header.payloadId = encoded.toUint8(index);
|
|
index += 1;
|
|
// Payload ID of 1 required for individual attestation
|
|
require(bpa.attestations[j].header.payloadId == 1, "invalid payload ID");
|
|
|
|
// Attestation
|
|
bpa.attestations[j].productId = encoded.toBytes32(index);
|
|
index += 32;
|
|
|
|
bpa.attestations[j].priceId = encoded.toBytes32(index);
|
|
index += 32;
|
|
bpa.attestations[j].priceType = encoded.toUint8(index);
|
|
index += 1;
|
|
|
|
bpa.attestations[j].price = int64(encoded.toUint64(index));
|
|
index += 8;
|
|
|
|
bpa.attestations[j].exponent = int32(encoded.toUint32(index));
|
|
index += 4;
|
|
|
|
bpa.attestations[j].emaPrice.value = int64(encoded.toUint64(index));
|
|
index += 8;
|
|
bpa.attestations[j].emaPrice.numerator = int64(encoded.toUint64(index));
|
|
index += 8;
|
|
bpa.attestations[j].emaPrice.denominator = int64(encoded.toUint64(index));
|
|
index += 8;
|
|
|
|
bpa.attestations[j].emaConf.value = int64(encoded.toUint64(index));
|
|
index += 8;
|
|
bpa.attestations[j].emaConf.numerator = int64(encoded.toUint64(index));
|
|
index += 8;
|
|
bpa.attestations[j].emaConf.denominator = int64(encoded.toUint64(index));
|
|
index += 8;
|
|
|
|
bpa.attestations[j].confidenceInterval = encoded.toUint64(index);
|
|
index += 8;
|
|
|
|
bpa.attestations[j].status = encoded.toUint8(index);
|
|
index += 1;
|
|
bpa.attestations[j].corpAct = encoded.toUint8(index);
|
|
index += 1;
|
|
|
|
bpa.attestations[j].timestamp = encoded.toUint64(index);
|
|
index += 8;
|
|
|
|
bpa.attestations[j].num_publishers = encoded.toUint32(index);
|
|
index += 4;
|
|
|
|
bpa.attestations[j].max_num_publishers = encoded.toUint32(index);
|
|
index += 4;
|
|
}
|
|
}
|
|
|
|
/// Maximum acceptable time period before price is considered to be stale.
|
|
///
|
|
/// This includes attestation delay which currently might up to a minute.
|
|
uint private constant VALID_TIME_PERIOD_SECS = 180;
|
|
|
|
function queryPriceFeed(bytes32 id) public 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, "no price feed found for the given price id");
|
|
|
|
// Check that there is not a significant difference between this chain's time
|
|
// and the attestation time. This is a last-resort safety net, and this check
|
|
// will be iterated on in the future.
|
|
if (diff(block.timestamp, info.attestationTime) > VALID_TIME_PERIOD_SECS) {
|
|
info.priceFeed.status = PythStructs.PriceStatus.UNKNOWN;
|
|
}
|
|
|
|
return info.priceFeed;
|
|
}
|
|
|
|
function diff(uint x, uint y) private pure returns (uint) {
|
|
if (x > y) {
|
|
return x - y;
|
|
} else {
|
|
return y - x;
|
|
}
|
|
}
|
|
}
|