Skip to content

Commit d36a6be

Browse files
authored
Pyth Sample EVM Price Feeds (#40)
* initial commit * update * remove workflow
1 parent 7ad9dc4 commit d36a6be

File tree

10 files changed

+269
-0
lines changed

10 files changed

+269
-0
lines changed

.gitmodules

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,3 +7,6 @@
77
[submodule "lazer/evm/lib/openzeppelin-contracts-upgradeable"]
88
path = lazer/evm/lib/openzeppelin-contracts-upgradeable
99
url = https://github.com/OpenZeppelin/openzeppelin-contracts-upgradeable
10+
[submodule "price_feeds/evm/pyth_sample/lib/forge-std"]
11+
path = price_feeds/evm/pyth_sample/lib/forge-std
12+
url = https://github.com/foundry-rs/forge-std
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
# Compiler files
2+
cache/
3+
out/
4+
5+
# Ignores development broadcast logs
6+
!/broadcast
7+
/broadcast/*/31337/
8+
/broadcast/**/dry-run/
9+
10+
# Docs
11+
docs/
12+
13+
# Dotenv file
14+
.env

price_feeds/evm/pyth_sample/README.md

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
# Pyth Oracle sample app
2+
3+
This directory contains an example of a smart contract using Pyth Price Feeds.
4+
5+
Please see the [Pyth documentation](https://docs.pyth.network/documentation/pythnet-price-feeds) for more information about Pyth and how to integrate it into your application.
6+
7+
### Building
8+
9+
You need to have [Foundry](https://getfoundry.sh/) and `node` installed to run this example.
10+
Once you have installed these tools, run the following commands from root directory to install forge dependencies:
11+
12+
```
13+
forge install foundry-rs/forge-std@v1.8.0 --no-git --no-commit
14+
```
15+
16+
After installing the above dependencies, you need to install pyth-sdk-solidity.
17+
18+
```
19+
npm init -y
20+
npm install @pythnetwork/pyth-sdk-solidity
21+
```
22+
23+
### Testing
24+
25+
Simply run `forge test` from root directory.
26+
27+
28+
### Resources
29+
30+
- [Pyth Price Feeds Documentation](https://docs.pyth.network/price-feeds)
31+
- [Pyth Price Feed IDs](https://www.pyth.network/developers/price-feed-ids)
32+
- [Pyth Price Feeds Contracts](https://docs.pyth.network/price-feeds/contract-addresses/evm)
33+
34+
### Retrieve Price Updates.
35+
36+
Price updates can be retrieved using Hermes. It provides various ways to retrieve price updates.
37+
38+
For example
39+
40+
```
41+
curl -X 'GET' \
42+
'https://hermes.pyth.network/v2/updates/price/latest?ids%5B%5D=0xe62df6c8b4a85fe1a67db44dc12de5db330f7ac66b72dc658afedf0f4a415b43&ids%5B%5D=0xc96458d393fe9deb7a7d63a0ac41e2898a67a7750dbd166673279e06c868df0a'
43+
```
44+
45+
Checkout [How to Fetch Price Updates](https://docs.pyth.network/price-feeds/fetch-price-updates) for more details.
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
[profile.default]
2+
src = "src"
3+
out = "out"
4+
libs = ["lib"]
5+
6+
# See more config options https://github.com/foundry-rs/foundry/blob/master/crates/config/README.md#all-options
Submodule forge-std added at 1eea5ba

price_feeds/evm/pyth_sample/package-lock.json

Lines changed: 22 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
{
2+
"name": "pyth_sample",
3+
"version": "1.0.0",
4+
"description": "**Foundry is a blazing fast, portable and modular toolkit for Ethereum application development written in Rust.**",
5+
"main": "index.js",
6+
"directories": {
7+
"lib": "lib",
8+
"test": "test"
9+
},
10+
"scripts": {
11+
"test": "echo \"Error: no test specified\" && exit 1"
12+
},
13+
"keywords": [],
14+
"author": "",
15+
"license": "ISC",
16+
"dependencies": {
17+
"@pythnetwork/pyth-sdk-solidity": "^4.0.0"
18+
}
19+
}
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
@pythnetwork/pyth-sdk-solidity/=node_modules/@pythnetwork/pyth-sdk-solidity
2+
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
// SPDX-License-Identifier: UNLICENSED
2+
pragma solidity ^0.8.13;
3+
4+
5+
import "@pythnetwork/pyth-sdk-solidity/IPyth.sol";
6+
import "@pythnetwork/pyth-sdk-solidity/PythStructs.sol";
7+
8+
9+
contract PythSample {
10+
11+
IPyth pyth;
12+
13+
// @param pyth: The contract address of the Pyth contract. Instantiate it with the Pyth contract address from https://docs.pyth.network/price-feeds/contract-addresses/evm
14+
constructor(address _pyth) {
15+
pyth = IPyth(_pyth);
16+
}
17+
18+
// @param priceId: Each price feed (e.g., ETH/USD) is identified by a price feed ID. The complete list of feed IDs is available at https://pyth.network/developers/price-feed-ids
19+
// @param priceUpdate: The encoded data to update the contract with the latest pricecontract-addresses/evm
20+
function getLatestPrice(bytes32 priceId, bytes[] calldata priceUpdate) public payable returns (PythStructs.Price memory) {
21+
22+
uint updateFee = pyth.getUpdateFee(priceUpdate);
23+
pyth.updatePriceFeeds{ value: updateFee }(priceUpdate);
24+
25+
return pyth.getPriceNoOlderThan(priceId, 60);
26+
}
27+
28+
// @dev: This function is an example method to update multiple price feeds at once.
29+
// @param priceIds: The price ids of the price feeds.
30+
// @param priceUpdates: The encoded data to update the contract with the latest price
31+
function getLatestPrices(bytes32[] calldata priceIds, bytes[] calldata priceUpdates) public payable returns (PythStructs.Price[] memory) {
32+
33+
// Calculate the update fee for the price feeds
34+
// One can update multiple price feeds by calling the updatePriceFeeds function once.
35+
uint updateFee = pyth.getUpdateFee(priceUpdates);
36+
pyth.updatePriceFeeds{ value: updateFee }(priceUpdates);
37+
38+
// Get the latest prices for the price feeds
39+
PythStructs.Price[] memory prices = new PythStructs.Price[](priceIds.length);
40+
for (uint i = 0; i < priceIds.length; i++) {
41+
prices[i] = pyth.getPriceNoOlderThan(priceIds[i], 60);
42+
}
43+
return prices;
44+
}
45+
46+
}
Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
// SPDX-License-Identifier: UNLICENSED
2+
pragma solidity ^0.8.13;
3+
4+
import { Test, console2 } from "forge-std/Test.sol";
5+
import { PythSample } from "../src/PythSample.sol";
6+
import { MockPyth } from "@pythnetwork/pyth-sdk-solidity/MockPyth.sol";
7+
import { PythStructs } from "@pythnetwork/pyth-sdk-solidity/PythStructs.sol";
8+
9+
contract PythSampleTest is Test {
10+
MockPyth public pyth;
11+
PythSample public app;
12+
13+
// @dev: Dummy price ids for testing.
14+
bytes32 constant ETH_PRICE_FEED_ID = bytes32(uint256(0x1));
15+
bytes32 constant BTC_PRICE_FEED_ID = bytes32(uint256(0x2));
16+
17+
// @dev: Setup the test environment.
18+
function setUp() public {
19+
pyth = new MockPyth(60, 1);
20+
app = new PythSample(address(pyth));
21+
}
22+
23+
// @dev: Creating a dummyprice update for a given price id and price.
24+
function createPriceUpdate(
25+
bytes32 priceId,
26+
int64 price
27+
) private view returns (bytes[] memory) {
28+
bytes[] memory updateData = new bytes[](1);
29+
updateData[0] = pyth.createPriceFeedUpdateData(
30+
priceId,
31+
price * 100000, // price
32+
10 * 100000, // confidence
33+
-5, // exponent
34+
price * 100000, // emaPrice
35+
10 * 100000, // emaConfidence
36+
uint64(block.timestamp), // publishTime
37+
uint64(block.timestamp) // prevPublishTime
38+
);
39+
return updateData;
40+
}
41+
42+
// @dev: Testing the getLatestPrice function.
43+
function testGetLatestPrice() public {
44+
bytes[] memory updateData = createPriceUpdate(ETH_PRICE_FEED_ID, 2000);
45+
uint updateFee = pyth.getUpdateFee(updateData);
46+
47+
vm.deal(address(this), updateFee);
48+
PythStructs.Price memory price = app.getLatestPrice{value: updateFee}(
49+
ETH_PRICE_FEED_ID,
50+
updateData
51+
);
52+
53+
assertEq(price.price, 2000 * 100000);
54+
}
55+
56+
function testGetLatestPrices() public {
57+
bytes32[] memory priceIds = new bytes32[](2);
58+
priceIds[0] = ETH_PRICE_FEED_ID;
59+
priceIds[1] = BTC_PRICE_FEED_ID;
60+
61+
bytes[] memory updateData = new bytes[](2);
62+
updateData[0] = pyth.createPriceFeedUpdateData(
63+
ETH_PRICE_FEED_ID,
64+
2000 * 100000, // ETH price
65+
10 * 100000,
66+
-5,
67+
2000 * 100000,
68+
10 * 100000,
69+
uint64(block.timestamp),
70+
uint64(block.timestamp)
71+
);
72+
updateData[1] = pyth.createPriceFeedUpdateData(
73+
BTC_PRICE_FEED_ID,
74+
30000 * 100000, // BTC price
75+
10 * 100000,
76+
-5,
77+
30000 * 100000,
78+
10 * 100000,
79+
uint64(block.timestamp),
80+
uint64(block.timestamp)
81+
);
82+
83+
uint updateFee = pyth.getUpdateFee(updateData);
84+
vm.deal(address(this), updateFee);
85+
86+
PythStructs.Price[] memory prices = app.getLatestPrices{value: updateFee}(
87+
priceIds,
88+
updateData
89+
);
90+
91+
assertEq(prices.length, 2);
92+
assertEq(prices[0].price, 2000 * 100000);
93+
assertEq(prices[1].price, 30000 * 100000);
94+
}
95+
96+
function testStalePrice() public {
97+
bytes[] memory updateData = createPriceUpdate(ETH_PRICE_FEED_ID, 2000);
98+
uint updateFee = pyth.getUpdateFee(updateData);
99+
vm.deal(address(this), updateFee);
100+
101+
// Update price
102+
pyth.updatePriceFeeds{value: updateFee}(updateData);
103+
104+
// Skip 120 seconds (more than the 60-second staleness threshold)
105+
skip(120);
106+
107+
// Expect revert when trying to get price
108+
vm.expectRevert();
109+
app.getLatestPrice{value: updateFee}(ETH_PRICE_FEED_ID, updateData);
110+
}
111+
}

0 commit comments

Comments
 (0)