Skip to content

Commit 05cdc67

Browse files
authored
Merge pull request #2611 from pyth-network/migrate-evm-twap
feat(target_chains/ethereum): implement TWAP calculation directly in Pyth contract
2 parents 4f7d3c5 + c1b60b4 commit 05cdc67

File tree

3 files changed

+44
-210
lines changed

3 files changed

+44
-210
lines changed

target_chains/ethereum/contracts/contracts/pyth/Pyth.sol

Lines changed: 44 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ import "@pythnetwork/pyth-sdk-solidity/AbstractPyth.sol";
77
import "@pythnetwork/pyth-sdk-solidity/PythStructs.sol";
88

99
import "@pythnetwork/pyth-sdk-solidity/PythErrors.sol";
10-
import "@pythnetwork/pyth-sdk-solidity/PythUtils.sol";
1110
import "./PythAccumulator.sol";
1211
import "./PythGetters.sol";
1312
import "./PythSetters.sol";
@@ -628,16 +627,53 @@ abstract contract Pyth is
628627
return "1.4.4-alpha.5";
629628
}
630629

630+
/// @notice Calculates TWAP from two price points
631+
/// @dev The calculation is done by taking the difference of cumulative values and dividing by the time difference
632+
/// @param priceId The price feed ID
633+
/// @param twapPriceInfoStart The starting price point
634+
/// @param twapPriceInfoEnd The ending price point
635+
/// @return twapPriceFeed The calculated TWAP price feed
631636
function calculateTwap(
632637
bytes32 priceId,
633638
PythStructs.TwapPriceInfo memory twapPriceInfoStart,
634639
PythStructs.TwapPriceInfo memory twapPriceInfoEnd
635-
) private pure returns (PythStructs.TwapPriceFeed memory) {
636-
return
637-
PythUtils.calculateTwap(
638-
priceId,
639-
twapPriceInfoStart,
640-
twapPriceInfoEnd
641-
);
640+
) private pure returns (PythStructs.TwapPriceFeed memory twapPriceFeed) {
641+
twapPriceFeed.id = priceId;
642+
twapPriceFeed.startTime = twapPriceInfoStart.publishTime;
643+
twapPriceFeed.endTime = twapPriceInfoEnd.publishTime;
644+
645+
// Calculate differences between start and end points for slots and cumulative values
646+
uint64 slotDiff = twapPriceInfoEnd.publishSlot -
647+
twapPriceInfoStart.publishSlot;
648+
int128 priceDiff = twapPriceInfoEnd.cumulativePrice -
649+
twapPriceInfoStart.cumulativePrice;
650+
uint128 confDiff = twapPriceInfoEnd.cumulativeConf -
651+
twapPriceInfoStart.cumulativeConf;
652+
653+
// Calculate time-weighted average price (TWAP) and confidence by dividing
654+
// the difference in cumulative values by the number of slots between data points
655+
int128 twapPrice = priceDiff / int128(uint128(slotDiff));
656+
uint128 twapConf = confDiff / uint128(slotDiff);
657+
658+
// The conversion from int128 to int64 is safe because:
659+
// 1. Individual prices fit within int64 by protocol design
660+
// 2. TWAP is essentially an average price over time (cumulativePrice₂-cumulativePrice₁)/slotDiff
661+
// 3. This average must be within the range of individual prices that went into the calculation
662+
// We use int128 only as an intermediate type to safely handle cumulative sums
663+
twapPriceFeed.twap.price = int64(twapPrice);
664+
twapPriceFeed.twap.conf = uint64(twapConf);
665+
twapPriceFeed.twap.expo = twapPriceInfoStart.expo;
666+
twapPriceFeed.twap.publishTime = twapPriceInfoEnd.publishTime;
667+
668+
// Calculate downSlotsRatio as a value between 0 and 1,000,000
669+
// 0 means no slots were missed, 1,000,000 means all slots were missed
670+
uint64 totalDownSlots = twapPriceInfoEnd.numDownSlots -
671+
twapPriceInfoStart.numDownSlots;
672+
uint64 downSlotsRatio = (totalDownSlots * 1_000_000) / slotDiff;
673+
674+
// Safely downcast to uint32 (sufficient for value range 0-1,000,000)
675+
twapPriceFeed.downSlotsRatio = uint32(downSlotsRatio);
676+
677+
return twapPriceFeed;
642678
}
643679
}

target_chains/ethereum/sdk/solidity/PythUtils.sol

Lines changed: 0 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -33,54 +33,4 @@ library PythUtils {
3333
10 ** uint32(priceDecimals - targetDecimals);
3434
}
3535
}
36-
37-
/// @notice Calculates TWAP from two price points
38-
/// @dev The calculation is done by taking the difference of cumulative values and dividing by the time difference
39-
/// @param priceId The price feed ID
40-
/// @param twapPriceInfoStart The starting price point
41-
/// @param twapPriceInfoEnd The ending price point
42-
/// @return twapPriceFeed The calculated TWAP price feed
43-
function calculateTwap(
44-
bytes32 priceId,
45-
PythStructs.TwapPriceInfo memory twapPriceInfoStart,
46-
PythStructs.TwapPriceInfo memory twapPriceInfoEnd
47-
) public pure returns (PythStructs.TwapPriceFeed memory twapPriceFeed) {
48-
twapPriceFeed.id = priceId;
49-
twapPriceFeed.startTime = twapPriceInfoStart.publishTime;
50-
twapPriceFeed.endTime = twapPriceInfoEnd.publishTime;
51-
52-
// Calculate differences between start and end points for slots and cumulative values
53-
uint64 slotDiff = twapPriceInfoEnd.publishSlot -
54-
twapPriceInfoStart.publishSlot;
55-
int128 priceDiff = twapPriceInfoEnd.cumulativePrice -
56-
twapPriceInfoStart.cumulativePrice;
57-
uint128 confDiff = twapPriceInfoEnd.cumulativeConf -
58-
twapPriceInfoStart.cumulativeConf;
59-
60-
// Calculate time-weighted average price (TWAP) and confidence by dividing
61-
// the difference in cumulative values by the number of slots between data points
62-
int128 twapPrice = priceDiff / int128(uint128(slotDiff));
63-
uint128 twapConf = confDiff / uint128(slotDiff);
64-
65-
// The conversion from int128 to int64 is safe because:
66-
// 1. Individual prices fit within int64 by protocol design
67-
// 2. TWAP is essentially an average price over time (cumulativePrice₂-cumulativePrice₁)/slotDiff
68-
// 3. This average must be within the range of individual prices that went into the calculation
69-
// We use int128 only as an intermediate type to safely handle cumulative sums
70-
twapPriceFeed.twap.price = int64(twapPrice);
71-
twapPriceFeed.twap.conf = uint64(twapConf);
72-
twapPriceFeed.twap.expo = twapPriceInfoStart.expo;
73-
twapPriceFeed.twap.publishTime = twapPriceInfoEnd.publishTime;
74-
75-
// Calculate downSlotsRatio as a value between 0 and 1,000,000
76-
// 0 means no slots were missed, 1,000,000 means all slots were missed
77-
uint64 totalDownSlots = twapPriceInfoEnd.numDownSlots -
78-
twapPriceInfoStart.numDownSlots;
79-
uint64 downSlotsRatio = (totalDownSlots * 1_000_000) / slotDiff;
80-
81-
// Safely downcast to uint32 (sufficient for value range 0-1,000,000)
82-
twapPriceFeed.downSlotsRatio = uint32(downSlotsRatio);
83-
84-
return twapPriceFeed;
85-
}
8636
}

target_chains/ethereum/sdk/solidity/abis/PythUtils.json

Lines changed: 0 additions & 152 deletions
Original file line numberDiff line numberDiff line change
@@ -1,156 +1,4 @@
11
[
2-
{
3-
"inputs": [
4-
{
5-
"internalType": "bytes32",
6-
"name": "priceId",
7-
"type": "bytes32"
8-
},
9-
{
10-
"components": [
11-
{
12-
"internalType": "int128",
13-
"name": "cumulativePrice",
14-
"type": "int128"
15-
},
16-
{
17-
"internalType": "uint128",
18-
"name": "cumulativeConf",
19-
"type": "uint128"
20-
},
21-
{
22-
"internalType": "uint64",
23-
"name": "numDownSlots",
24-
"type": "uint64"
25-
},
26-
{
27-
"internalType": "uint64",
28-
"name": "publishSlot",
29-
"type": "uint64"
30-
},
31-
{
32-
"internalType": "uint64",
33-
"name": "publishTime",
34-
"type": "uint64"
35-
},
36-
{
37-
"internalType": "uint64",
38-
"name": "prevPublishTime",
39-
"type": "uint64"
40-
},
41-
{
42-
"internalType": "int32",
43-
"name": "expo",
44-
"type": "int32"
45-
}
46-
],
47-
"internalType": "struct PythStructs.TwapPriceInfo",
48-
"name": "twapPriceInfoStart",
49-
"type": "tuple"
50-
},
51-
{
52-
"components": [
53-
{
54-
"internalType": "int128",
55-
"name": "cumulativePrice",
56-
"type": "int128"
57-
},
58-
{
59-
"internalType": "uint128",
60-
"name": "cumulativeConf",
61-
"type": "uint128"
62-
},
63-
{
64-
"internalType": "uint64",
65-
"name": "numDownSlots",
66-
"type": "uint64"
67-
},
68-
{
69-
"internalType": "uint64",
70-
"name": "publishSlot",
71-
"type": "uint64"
72-
},
73-
{
74-
"internalType": "uint64",
75-
"name": "publishTime",
76-
"type": "uint64"
77-
},
78-
{
79-
"internalType": "uint64",
80-
"name": "prevPublishTime",
81-
"type": "uint64"
82-
},
83-
{
84-
"internalType": "int32",
85-
"name": "expo",
86-
"type": "int32"
87-
}
88-
],
89-
"internalType": "struct PythStructs.TwapPriceInfo",
90-
"name": "twapPriceInfoEnd",
91-
"type": "tuple"
92-
}
93-
],
94-
"name": "calculateTwap",
95-
"outputs": [
96-
{
97-
"components": [
98-
{
99-
"internalType": "bytes32",
100-
"name": "id",
101-
"type": "bytes32"
102-
},
103-
{
104-
"internalType": "uint64",
105-
"name": "startTime",
106-
"type": "uint64"
107-
},
108-
{
109-
"internalType": "uint64",
110-
"name": "endTime",
111-
"type": "uint64"
112-
},
113-
{
114-
"components": [
115-
{
116-
"internalType": "int64",
117-
"name": "price",
118-
"type": "int64"
119-
},
120-
{
121-
"internalType": "uint64",
122-
"name": "conf",
123-
"type": "uint64"
124-
},
125-
{
126-
"internalType": "int32",
127-
"name": "expo",
128-
"type": "int32"
129-
},
130-
{
131-
"internalType": "uint256",
132-
"name": "publishTime",
133-
"type": "uint256"
134-
}
135-
],
136-
"internalType": "struct PythStructs.Price",
137-
"name": "twap",
138-
"type": "tuple"
139-
},
140-
{
141-
"internalType": "uint32",
142-
"name": "downSlotsRatio",
143-
"type": "uint32"
144-
}
145-
],
146-
"internalType": "struct PythStructs.TwapPriceFeed",
147-
"name": "twapPriceFeed",
148-
"type": "tuple"
149-
}
150-
],
151-
"stateMutability": "pure",
152-
"type": "function"
153-
},
1542
{
1553
"inputs": [
1564
{

0 commit comments

Comments
 (0)