Skip to content

Commit f94dceb

Browse files
authored
[eth] Add Pyth Accumulator (#776)
This PR adds the support WormholeMerkle accumulator message to the ethereum contract while still supporting the old message format. The code is not optimized yet and with more optimizations we can achieve a better gas usage. Currently based on the gas benchmark below it has a 18% improvement with a single price feed. Although the cost of updating 5 feeds in the same batch is higher than the current approach but in reality the chances that all 5 feeds be in the same batch is very low.
1 parent e7b72bf commit f94dceb

File tree

11 files changed

+1155
-47
lines changed

11 files changed

+1155
-47
lines changed

package-lock.json

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
// SPDX-License-Identifier: Apache 2
2+
3+
pragma solidity ^0.8.0;
4+
5+
import "./external/UnsafeBytesLib.sol";
6+
7+
/**
8+
* @dev This library provides methods to construct and verify Merkle Tree proofs efficiently.
9+
*
10+
*/
11+
12+
library MerkleTree {
13+
uint8 constant MERKLE_LEAF_PREFIX = 0;
14+
uint8 constant MERKLE_NODE_PREFIX = 1;
15+
uint8 constant MERKLE_EMPTY_LEAF_PREFIX = 2;
16+
17+
function hash(bytes memory input) internal pure returns (bytes20) {
18+
return bytes20(keccak256(input));
19+
}
20+
21+
function emptyLeafHash() internal pure returns (bytes20) {
22+
return hash(abi.encodePacked(MERKLE_EMPTY_LEAF_PREFIX));
23+
}
24+
25+
function leafHash(bytes memory data) internal pure returns (bytes20) {
26+
return hash(abi.encodePacked(MERKLE_LEAF_PREFIX, data));
27+
}
28+
29+
function nodeHash(
30+
bytes20 childA,
31+
bytes20 childB
32+
) internal pure returns (bytes20) {
33+
if (childA > childB) {
34+
(childA, childB) = (childB, childA);
35+
}
36+
return hash(abi.encodePacked(MERKLE_NODE_PREFIX, childA, childB));
37+
}
38+
39+
/// @notice Verify Merkle Tree proof for given leaf data.
40+
/// @dev To optimize gas usage, this method doesn't take the proof as a bytes array
41+
/// but rather takes the encoded proof and the offset of the proof in the
42+
/// encoded proof array possibly containing multiple proofs. Also, the method
43+
/// does not perform any check on the boundry of the `encodedProof` and the
44+
/// `proofOffset` parameters. It is the caller's responsibility to ensure
45+
/// that the `encodedProof` is long enough to contain the proof and the
46+
/// `proofOffset` is not out of bound.
47+
function isProofValid(
48+
bytes memory encodedProof,
49+
uint proofOffset,
50+
bytes20 root,
51+
bytes memory leafData
52+
) internal pure returns (bool valid, uint endOffset) {
53+
unchecked {
54+
bytes20 currentDigest = MerkleTree.leafHash(leafData);
55+
56+
uint8 proofSize = UnsafeBytesLib.toUint8(encodedProof, proofOffset);
57+
proofOffset += 1;
58+
59+
for (uint i = 0; i < proofSize; i++) {
60+
bytes20 siblingDigest = bytes20(
61+
UnsafeBytesLib.toAddress(encodedProof, proofOffset)
62+
);
63+
proofOffset += 20;
64+
65+
currentDigest = MerkleTree.nodeHash(
66+
currentDigest,
67+
siblingDigest
68+
);
69+
}
70+
71+
valid = currentDigest == root;
72+
endOffset = proofOffset;
73+
}
74+
}
75+
76+
/// @notice Construct Merkle Tree proofs for given list of messages.
77+
/// @dev This function is only used for testing purposes and is not efficient
78+
/// for production use-cases.
79+
///
80+
/// This method creates a merkle tree with leaf size of (2^depth) with the
81+
/// messages as leafs (in the same given order) and returns the root digest
82+
/// and the proofs for each message. If the number of messages is not a power
83+
/// of 2, the tree is padded with empty messages.
84+
function constructProofs(
85+
bytes[] memory messages,
86+
uint8 depth
87+
) internal pure returns (bytes20 root, bytes[] memory proofs) {
88+
require((1 << depth) >= messages.length, "depth too small");
89+
90+
bytes20[] memory tree = new bytes20[]((1 << (depth + 1)));
91+
92+
// The tree is structured as follows:
93+
// 1
94+
// 2 3
95+
// 4 5 6 7
96+
// ...
97+
// In this structure the parent of node x is x//2 and the children
98+
// of node x are x*2 and x*2 + 1. Also, the sibling of the node x
99+
// is x^1. The root is at index 1 and index 0 is not used.
100+
101+
// Filling the leaf hashes
102+
bytes20 cachedEmptyLeafHash = emptyLeafHash();
103+
104+
for (uint i = 0; i < (1 << depth); i++) {
105+
if (i < messages.length) {
106+
tree[(1 << depth) + i] = leafHash(messages[i]);
107+
} else {
108+
tree[(1 << depth) + i] = cachedEmptyLeafHash;
109+
}
110+
}
111+
112+
// Filling the node hashes from bottom to top
113+
for (uint k = depth; k > 0; k--) {
114+
uint level = k - 1;
115+
uint levelNumNodes = (1 << level);
116+
for (uint i = 0; i < levelNumNodes; i++) {
117+
uint id = (1 << level) + i;
118+
tree[id] = nodeHash(tree[id * 2], tree[id * 2 + 1]);
119+
}
120+
}
121+
122+
root = tree[1];
123+
124+
proofs = new bytes[](messages.length);
125+
126+
for (uint i = 0; i < messages.length; i++) {
127+
// depth is the number of sibling nodes in the path from the leaf to the root
128+
proofs[i] = abi.encodePacked(depth);
129+
130+
uint idx = (1 << depth) + i;
131+
132+
// This loop iterates through the leaf and its parents
133+
// and keeps adding the sibling of the current node to the proof.
134+
while (idx > 1) {
135+
proofs[i] = abi.encodePacked(
136+
proofs[i],
137+
tree[idx ^ 1] // Sibling of this node
138+
);
139+
140+
// Jump to parent
141+
idx /= 2;
142+
}
143+
}
144+
}
145+
}

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

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
// contracts/Bridge.sol
21
// SPDX-License-Identifier: Apache 2
32

43
pragma solidity ^0.8.0;
@@ -8,11 +7,17 @@ import "@pythnetwork/pyth-sdk-solidity/AbstractPyth.sol";
87
import "@pythnetwork/pyth-sdk-solidity/PythStructs.sol";
98

109
import "@pythnetwork/pyth-sdk-solidity/PythErrors.sol";
10+
import "./PythAccumulator.sol";
1111
import "./PythGetters.sol";
1212
import "./PythSetters.sol";
1313
import "./PythInternalStructs.sol";
1414

15-
abstract contract Pyth is PythGetters, PythSetters, AbstractPyth {
15+
abstract contract Pyth is
16+
PythGetters,
17+
PythSetters,
18+
AbstractPyth,
19+
PythAccumulator
20+
{
1621
function _initialize(
1722
address wormhole,
1823
uint16[] calldata dataSourceEmitterChainIds,
@@ -66,11 +71,19 @@ abstract contract Pyth is PythGetters, PythSetters, AbstractPyth {
6671
function updatePriceFeeds(
6772
bytes[] calldata updateData
6873
) public payable override {
74+
// TODO: Is this fee model still good for accumulator?
6975
uint requiredFee = getUpdateFee(updateData);
7076
if (msg.value < requiredFee) revert PythErrors.InsufficientFee();
7177

7278
for (uint i = 0; i < updateData.length; ) {
73-
updatePriceBatchFromVm(updateData[i]);
79+
if (
80+
updateData[i].length > 4 &&
81+
UnsafeBytesLib.toUint32(updateData[i], 0) == ACCUMULATOR_MAGIC
82+
) {
83+
updatePricesUsingAccumulator(updateData[i]);
84+
} else {
85+
updatePriceBatchFromVm(updateData[i]);
86+
}
7487

7588
unchecked {
7689
i++;
@@ -536,6 +549,6 @@ abstract contract Pyth is PythGetters, PythSetters, AbstractPyth {
536549
}
537550

538551
function version() public pure returns (string memory) {
539-
return "1.2.0";
552+
return "1.3.0-alpha";
540553
}
541554
}

0 commit comments

Comments
 (0)