Skip to content

feat(target_chains/ethereum): add twap #2543

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 30 commits into from
Apr 11, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
b950569
update
aditya520 Feb 12, 2025
e1dcc2f
update
aditya520 Feb 12, 2025
507b5ca
update
aditya520 Feb 14, 2025
a87ff74
update: fix logic & improve code structure and readability
cctdaniel Apr 2, 2025
98ab843
feat: add parseTwapPriceFeedUpdates function to IPyth and MockPyth
cctdaniel Apr 2, 2025
d0ff3a3
feat: add override keyword to TwapPriceFeed function in Pyth contract
cctdaniel Apr 3, 2025
46808d8
feat: refactor TwapPriceFeed function for improved readability and st…
cctdaniel Apr 3, 2025
dc2cd92
feat: add comments
cctdaniel Apr 3, 2025
93195fa
refactor: streamline message extraction and validation logic in PythA…
cctdaniel Apr 3, 2025
c842b9d
chore: rename downSlotRatio to downSlotsRatio
cctdaniel Apr 3, 2025
766ecde
temp
cctdaniel Apr 4, 2025
74217f9
feat: implement TwapPriceFeedMessage struct and related encoding func…
cctdaniel Apr 8, 2025
671c6c3
feat: enhance fee charging logic for TWAP updates to improve user exp…
cctdaniel Apr 8, 2025
a5e15e5
test: add unit tests for TWAP price feed update validation and error …
cctdaniel Apr 8, 2025
46eb28d
chore: update version to 1.4.4-alpha.2 in Pyth contract and package.json
cctdaniel Apr 8, 2025
dac3e31
feat: refactor TWAP price handling by moving TwapPriceInfo struct to …
cctdaniel Apr 9, 2025
f8c8ac2
fix build
cctdaniel Apr 9, 2025
3ab3f0d
docs: clarify TWAP update requirements in Pyth contract
cctdaniel Apr 9, 2025
c88c2f5
feat: update parseTwapPriceFeedUpdates function to accept a single ar…
cctdaniel Apr 9, 2025
c06488e
docs: enhance downSlotsRatio comment to clarify its purpose and usage…
cctdaniel Apr 9, 2025
139d813
revert cargo locks
cctdaniel Apr 9, 2025
371350a
feat: update updateData parameter type to accept a single array in Mo…
cctdaniel Apr 9, 2025
8a6f154
feat: refactor TWAP update logic to decode price feed data and valida…
cctdaniel Apr 9, 2025
1b01a7a
feat: implement TWAP message creation and update data generation in P…
cctdaniel Apr 9, 2025
41a0af9
docs: add return value descriptions for TWAP update function in Pyth …
cctdaniel Apr 9, 2025
e382594
setup twappricefeedmessage and reuse in tests
cctdaniel Apr 10, 2025
5b9a47e
feat: simplify createTwapPriceFeedUpdateData parameters for testing a…
cctdaniel Apr 10, 2025
f19d2c9
refactor: update MockPyth ABI to change cumulativePrice and cumulativ…
cctdaniel Apr 10, 2025
83ad4d6
feat: simplify TWAP price feed processing and update data creation in…
cctdaniel Apr 10, 2025
fd94f73
refactor: change startTime and endTime types to uint64 in MockPyth an…
cctdaniel Apr 10, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion target_chains/cosmwasm/examples/cw-contract/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

176 changes: 174 additions & 2 deletions target_chains/ethereum/contracts/contracts/pyth/Pyth.sol
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,11 @@ import "@pythnetwork/pyth-sdk-solidity/AbstractPyth.sol";
import "@pythnetwork/pyth-sdk-solidity/PythStructs.sol";

import "@pythnetwork/pyth-sdk-solidity/PythErrors.sol";
import "@pythnetwork/pyth-sdk-solidity/PythUtils.sol";
import "./PythAccumulator.sol";
import "./PythGetters.sol";
import "./PythSetters.sol";
import "./PythInternalStructs.sol";

abstract contract Pyth is
PythGetters,
PythSetters,
Expand Down Expand Up @@ -308,6 +308,165 @@ abstract contract Pyth is
);
}

function processSingleTwapUpdate(
bytes calldata updateData
)
private
view
returns (
/// @return newOffset The next position in the update data after processing this TWAP update
/// @return twapPriceInfo The extracted time-weighted average price information
/// @return priceId The unique identifier for this price feed
uint newOffset,
PythStructs.TwapPriceInfo memory twapPriceInfo,
bytes32 priceId
)
{
UpdateType updateType;
uint offset;
bytes20 digest;
uint8 numUpdates;
bytes calldata encoded;
// Extract and validate the header for start data
(offset, updateType) = extractUpdateTypeFromAccumulatorHeader(
updateData
);

if (updateType != UpdateType.WormholeMerkle) {
revert PythErrors.InvalidUpdateData();
}

(
offset,
digest,
numUpdates,
encoded
) = extractWormholeMerkleHeaderDigestAndNumUpdatesAndEncodedFromAccumulatorUpdate(
updateData,
offset
);

// Add additional validation before extracting TWAP price info
if (offset >= updateData.length) {
revert PythErrors.InvalidUpdateData();
}

// Extract start TWAP data with robust error checking
(offset, twapPriceInfo, priceId) = extractTwapPriceInfoFromMerkleProof(
digest,
encoded,
offset
);

if (offset != encoded.length) {
revert PythErrors.InvalidTwapUpdateData();
}
newOffset = offset;
}

function parseTwapPriceFeedUpdates(
bytes[] calldata updateData,
bytes32[] calldata priceIds
)
external
payable
override
returns (PythStructs.TwapPriceFeed[] memory twapPriceFeeds)
{
// TWAP requires exactly 2 updates - one for the start point and one for the end point
// to calculate the time-weighted average price between those two points
if (updateData.length != 2) {
revert PythErrors.InvalidUpdateData();
}

uint requiredFee = getUpdateFee(updateData);
if (msg.value < requiredFee) revert PythErrors.InsufficientFee();

unchecked {
twapPriceFeeds = new PythStructs.TwapPriceFeed[](priceIds.length);
for (uint i = 0; i < updateData.length - 1; i++) {
if (
(updateData[i].length > 4 &&
UnsafeCalldataBytesLib.toUint32(updateData[i], 0) ==
ACCUMULATOR_MAGIC) &&
(updateData[i + 1].length > 4 &&
UnsafeCalldataBytesLib.toUint32(updateData[i + 1], 0) ==
ACCUMULATOR_MAGIC)
) {
uint offsetStart;
uint offsetEnd;
bytes32 priceIdStart;
bytes32 priceIdEnd;
PythStructs.TwapPriceInfo memory twapPriceInfoStart;
PythStructs.TwapPriceInfo memory twapPriceInfoEnd;
(
offsetStart,
twapPriceInfoStart,
priceIdStart
) = processSingleTwapUpdate(updateData[i]);
(
offsetEnd,
twapPriceInfoEnd,
priceIdEnd
) = processSingleTwapUpdate(updateData[i + 1]);

if (priceIdStart != priceIdEnd)
revert PythErrors.InvalidTwapUpdateDataSet();

validateTwapPriceInfo(twapPriceInfoStart, twapPriceInfoEnd);

uint k = findIndexOfPriceId(priceIds, priceIdStart);

// If priceFeed[k].id != 0 then it means that there was a valid
// update for priceIds[k] and we don't need to process this one.
if (k == priceIds.length || twapPriceFeeds[k].id != 0) {
continue;
}

twapPriceFeeds[k] = calculateTwap(
priceIdStart,
twapPriceInfoStart,
twapPriceInfoEnd
);
} else {
revert PythErrors.InvalidUpdateData();
}
}

for (uint k = 0; k < priceIds.length; k++) {
if (twapPriceFeeds[k].id == 0) {
revert PythErrors.PriceFeedNotFoundWithinRange();
}
}
}
}

function validateTwapPriceInfo(
PythStructs.TwapPriceInfo memory twapPriceInfoStart,
PythStructs.TwapPriceInfo memory twapPriceInfoEnd
) private pure {
// First validate each individual price's uniqueness
if (
twapPriceInfoStart.prevPublishTime >= twapPriceInfoStart.publishTime
) {
revert PythErrors.InvalidTwapUpdateData();
}
if (twapPriceInfoEnd.prevPublishTime >= twapPriceInfoEnd.publishTime) {
revert PythErrors.InvalidTwapUpdateData();
}

// Then validate the relationship between the two data points
if (twapPriceInfoStart.expo != twapPriceInfoEnd.expo) {
revert PythErrors.InvalidTwapUpdateDataSet();
}
if (twapPriceInfoStart.publishSlot > twapPriceInfoEnd.publishSlot) {
revert PythErrors.InvalidTwapUpdateDataSet();
}
if (twapPriceInfoStart.publishTime > twapPriceInfoEnd.publishTime) {
revert PythErrors.InvalidTwapUpdateDataSet();
}
}

function parsePriceFeedUpdatesUnique(
bytes[] calldata updateData,
bytes32[] calldata priceIds,
Expand Down Expand Up @@ -397,6 +556,19 @@ abstract contract Pyth is
}

function version() public pure returns (string memory) {
return "1.4.4-alpha.1";
return "1.4.4-alpha.2";
}

function calculateTwap(
bytes32 priceId,
PythStructs.TwapPriceInfo memory twapPriceInfoStart,
PythStructs.TwapPriceInfo memory twapPriceInfoEnd
) private pure returns (PythStructs.TwapPriceFeed memory) {
return
PythUtils.calculateTwap(
priceId,
twapPriceInfoStart,
twapPriceInfoEnd
);
}
}
Loading
Loading