Skip to content
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

feat(ethereum): implement TWAP verification method #2187

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
84 changes: 84 additions & 0 deletions target_chains/ethereum/contracts/contracts/pyth/Pyth.sol
Original file line number Diff line number Diff line change
Expand Up @@ -394,4 +394,88 @@ abstract contract Pyth is
function version() public pure returns (string memory) {
return "1.4.3";
}

// TWAP verification and update methods
function updateTwapFeed(
bytes[] calldata updateData,
bytes32 feedId,
uint64 startTime,
uint64 endTime
) external payable {
// Parse and validate both start and end updates
PythStructs.PriceFeed[] memory startFeed = parsePriceFeedUpdatesInternal(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the messages here have a different format. They are of type TwapMessage -- see the file pythnet/pythnet_sdk/src/messages.rs for the spec. You will have to implement a different parsing function to read this data

updateData,
new bytes32[](1),
PythInternalStructs.ParseConfig(startTime, startTime, true)
);

PythStructs.PriceFeed[] memory endFeed = parsePriceFeedUpdatesInternal(
updateData,
new bytes32[](1),
PythInternalStructs.ParseConfig(endTime, endTime, true)
);

// Validate TWAP requirements
validateTwapMessages(startFeed[0], endFeed[0], feedId);

// Calculate and store TWAP
calculateAndStoreTwap(startFeed[0], endFeed[0]);
}

function validateTwapMessages(
PythStructs.PriceFeed memory startFeed,
PythStructs.PriceFeed memory endFeed,
bytes32 expectedFeedId
) internal pure {
// Validate feed IDs match expected ID
if (startFeed.id != expectedFeedId || endFeed.id != expectedFeedId)
revert PythErrors.InvalidArgument();

// Validate feed IDs match each other
if (startFeed.id != endFeed.id)
revert PythErrors.InvalidArgument();

// Validate exponents match
if (startFeed.price.expo != endFeed.price.expo)
revert PythErrors.InvalidArgument();

// Validate time order
if (startFeed.price.publishTime >= endFeed.price.publishTime)
revert PythErrors.InvalidArgument();
}

function calculateAndStoreTwap(
PythStructs.PriceFeed memory startFeed,
PythStructs.PriceFeed memory endFeed
) internal returns (PythInternalStructs.TwapInfo memory) {
// Calculate time-weighted average price
uint64 timeDiff = uint64(endFeed.price.publishTime - startFeed.price.publishTime);
int64 priceDiff = endFeed.price.price - startFeed.price.price;

// Calculate TWAP using linear interpolation
int64 twapPrice = startFeed.price.price + (priceDiff * int64(timeDiff) / int64(timeDiff));

// Create TWAP info
PythInternalStructs.TwapInfo memory twapInfo = PythInternalStructs.TwapInfo({
feedId: startFeed.id,
startTime: uint64(startFeed.price.publishTime),
endTime: uint64(endFeed.price.publishTime),
price: twapPrice,
conf: endFeed.price.conf, // Use the end confidence
exponent: startFeed.price.expo
});

// Store TWAP info in state
_state.latestTwapInfo[startFeed.id] = twapInfo;

return twapInfo;
}

function getTwapFeed(
bytes32 id
) public view returns (PythInternalStructs.TwapInfo memory twapInfo) {
twapInfo = _state.latestTwapInfo[id];
if (twapInfo.startTime == 0) revert PythErrors.PriceFeedNotFound();
return twapInfo;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -30,4 +30,13 @@ contract PythInternalStructs {
uint16 chainId;
bytes32 emitterAddress;
}

struct TwapInfo {
bytes32 feedId;
uint64 startTime;
uint64 endTime;
int64 price;
uint64 conf;
int32 exponent;
}
}
3 changes: 3 additions & 0 deletions target_chains/ethereum/contracts/contracts/pyth/PythState.sol
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,9 @@ contract PythStorage {
// Mapping of cached price information
// priceId => PriceInfo
mapping(bytes32 => PythInternalStructs.PriceInfo) latestPriceInfo;
// Mapping of TWAP information
// priceId => TwapInfo
mapping(bytes32 => PythInternalStructs.TwapInfo) latestTwapInfo;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think it makes sense to store these updates given that there isn't a notion of "latest". Instead, let's implement this whole PR along the lines of parsePriceFeedUpdates where the data is parsed, validated, and returned to the caller without being stored by the contract.

}
}

Expand Down
Loading