From 61651a10f202c8e02ed9d1f1f27ddf36e763661d Mon Sep 17 00:00:00 2001 From: Ali Behjati Date: Fri, 4 Nov 2022 11:41:01 +0100 Subject: [PATCH] Modify PriceInfo struct for gas optimization (#373) This PR removes unneeded PriceInfo fields and rearranges the struct in a way that it takes less storage. --- ethereum/contracts/pyth/Pyth.sol | 52 ++++++----- .../contracts/pyth/PythDeprecatedStructs.sol | 89 +++++++++++++++++++ .../contracts/pyth/PythInternalStructs.sol | 16 +++- ethereum/contracts/pyth/PythState.sol | 9 +- 4 files changed, 136 insertions(+), 30 deletions(-) create mode 100644 ethereum/contracts/pyth/PythDeprecatedStructs.sol diff --git a/ethereum/contracts/pyth/Pyth.sol b/ethereum/contracts/pyth/Pyth.sol index cab82bf1..8de63781 100644 --- a/ethereum/contracts/pyth/Pyth.sol +++ b/ethereum/contracts/pyth/Pyth.sol @@ -41,14 +41,14 @@ abstract contract Pyth is PythGetters, PythSetters, AbstractPyth { PythInternalStructs.PriceInfo memory latestPrice = latestPriceInfo(attestation.priceId); bool fresh = false; - if(newPriceInfo.priceFeed.price.publishTime > latestPrice.priceFeed.price.publishTime) { + if(newPriceInfo.price.publishTime > latestPrice.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 PriceFeedUpdate(attestation.priceId, fresh, vm.emitterChainId, vm.sequence, latestPrice.price.publishTime, + newPriceInfo.price.publishTime, newPriceInfo.price.price, newPriceInfo.price.conf); } emit BatchPriceFeedUpdate(vm.emitterChainId, vm.sequence, batch.attestations.length, freshPrices); @@ -76,32 +76,27 @@ abstract contract Pyth is PythGetters, PythSetters, AbstractPyth { 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; - + function createNewPriceInfo(PythInternalStructs.PriceAttestation memory pa) private pure returns (PythInternalStructs.PriceInfo memory info) { 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; + info.price.price = pa.price; + info.price.conf = pa.conf; + info.price.publishTime = pa.publishTime; + info.emaPrice.publishTime = pa.publishTime; } else { - info.priceFeed.price.price = pa.prevPrice; - info.priceFeed.price.conf = pa.prevConf; - info.priceFeed.price.publishTime = pa.prevPublishTime; + info.price.price = pa.prevPrice; + info.price.conf = pa.prevConf; + info.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.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; + info.price.expo = pa.expo; + info.emaPrice.price = pa.emaPrice; + info.emaPrice.conf = pa.emaConf; + info.emaPrice.expo = pa.expo; return info; } @@ -226,14 +221,23 @@ abstract contract Pyth is PythGetters, PythSetters, AbstractPyth { 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"); + require(info.price.publishTime != 0, "price feed for the given id is not pushed or does not exist"); - return info.priceFeed; + priceFeed.id = id; + priceFeed.price.price = info.price.price; + priceFeed.price.conf = info.price.conf; + priceFeed.price.expo = info.price.expo; + priceFeed.price.publishTime = uint(info.price.publishTime); + + priceFeed.emaPrice.price = info.emaPrice.price; + priceFeed.emaPrice.conf = info.emaPrice.conf; + priceFeed.emaPrice.expo = info.emaPrice.expo; + priceFeed.emaPrice.publishTime = uint(info.emaPrice.publishTime); } function priceFeedExists(bytes32 id) public override view returns (bool) { PythInternalStructs.PriceInfo memory info = latestPriceInfo(id); - return (info.priceFeed.id != 0); + return (info.price.publishTime != 0); } function getValidTimePeriod() public override view returns (uint) { diff --git a/ethereum/contracts/pyth/PythDeprecatedStructs.sol b/ethereum/contracts/pyth/PythDeprecatedStructs.sol new file mode 100644 index 00000000..fcbca437 --- /dev/null +++ b/ethereum/contracts/pyth/PythDeprecatedStructs.sol @@ -0,0 +1,89 @@ + +// contracts/PythDeprecatedStructs.sol +// SPDX-License-Identifier: Apache 2 + +pragma solidity ^0.8.0; + +// This contract contains self contained structs of all our deprecated structs. +// When deprecating the structs, make sure that there be no dependency to +// the sdk as the sdk might change. +// +// By storing these structs, we keep deprecated fields definitions correctly. Then, +// in the future, we can use them to cleanup their storage and redeem some gas back. +contract PythDeprecatedStructs { + // Structs related to the _deprecatedLatestPriceInfoV1 + enum DeprecatedPriceStatusV1 { + UNKNOWN, + TRADING, + HALTED, + AUCTION + } + + struct DeprecatedPriceFeedV1 { + // The price ID. + bytes32 id; + // Product account key. + bytes32 productId; + // The current price. + int64 price; + // Confidence interval around the price. + uint64 conf; + // Price exponent. + int32 expo; + // Status of price. + DeprecatedPriceStatusV1 status; + // Maximum number of allowed publishers that can contribute to a price. + uint32 maxNumPublishers; + // Number of publishers that made up current aggregate. + uint32 numPublishers; + // Exponentially moving average price. + int64 emaPrice; + // Exponentially moving average confidence interval. + uint64 emaConf; + // Unix timestamp describing when the price was published + uint64 publishTime; + // Price of previous price with TRADING status + int64 prevPrice; + // Confidence interval of previous price with TRADING status + uint64 prevConf; + // Unix timestamp describing when the previous price with TRADING status was published + uint64 prevPublishTime; + } + + struct DeprecatedPriceInfoV1 { + uint256 attestationTime; + uint256 arrivalTime; + uint256 arrivalBlock; + DeprecatedPriceFeedV1 priceFeed; + } + + // Structs related to the _deprecatedLatestPriceInfoV2 + struct DeprecatedPriceV2 { + // 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 DeprecatedPriceFeedV2 { + // The price ID. + bytes32 id; + // Latest available price + DeprecatedPriceV2 price; + // Latest available exponentially-weighted moving average price + DeprecatedPriceV2 emaPrice; + } + + + struct DeprecatedPriceInfoV2 { + uint256 attestationTime; + uint256 arrivalTime; + uint256 arrivalBlock; + DeprecatedPriceFeedV2 priceFeed; + } +} diff --git a/ethereum/contracts/pyth/PythInternalStructs.sol b/ethereum/contracts/pyth/PythInternalStructs.sol index af5d39bd..8d1c6713 100644 --- a/ethereum/contracts/pyth/PythInternalStructs.sol +++ b/ethereum/contracts/pyth/PythInternalStructs.sol @@ -43,11 +43,19 @@ contract PythInternalStructs { uint64 prevConf; } + struct InternalPrice { + int64 price; + uint64 conf; + uint64 publishTime; + int32 expo; + } + struct PriceInfo { - uint256 attestationTime; - uint256 arrivalTime; - uint256 arrivalBlock; - PythStructs.PriceFeed priceFeed; + // slot 1 + InternalPrice price; + + // slot 2 + InternalPrice emaPrice; } struct DataSource { diff --git a/ethereum/contracts/pyth/PythState.sol b/ethereum/contracts/pyth/PythState.sol index 9482ab57..56e34469 100644 --- a/ethereum/contracts/pyth/PythState.sol +++ b/ethereum/contracts/pyth/PythState.sol @@ -4,6 +4,7 @@ pragma solidity ^0.8.0; import "./PythInternalStructs.sol"; +import "./PythDeprecatedStructs.sol"; contract PythStorage { struct State { @@ -12,7 +13,7 @@ contract PythStorage { bytes32 _deprecatedPyth2WormholeEmitter; // Ditto // After a backward-incompatible change in PriceFeed this mapping got deprecated. - mapping(bytes32 => PythInternalStructs.PriceInfo) _deprecatedLatestPriceInfo; + mapping(bytes32 => PythDeprecatedStructs.DeprecatedPriceInfoV1) _deprecatedLatestPriceInfoV1; // For tracking all active emitter/chain ID pairs PythInternalStructs.DataSource[] validDataSources; @@ -38,11 +39,15 @@ contract PythStorage { // Mapping of cached price information // priceId => PriceInfo - mapping(bytes32 => PythInternalStructs.PriceInfo) latestPriceInfo; + mapping(bytes32 => PythDeprecatedStructs.DeprecatedPriceInfoV2) _deprecatedLatestPriceInfoV2; // Index of the governance data source, increased each time the governance data source // changes. uint32 governanceDataSourceIndex; + + // Mapping of cached price information + // priceId => PriceInfo + mapping(bytes32 => PythInternalStructs.PriceInfo) latestPriceInfo; } }