From 27264888c01d9824b9521ffe8fb4ecb02a933a39 Mon Sep 17 00:00:00 2001 From: Aditya Arora Date: Fri, 13 Dec 2024 15:12:53 -0500 Subject: [PATCH 1/3] initial commit --- .gitmodules | 3 + benchmarks/evm/contracts/lib/forge-std | 1 + price_feeds/evm/morpho_deploy/lib/forge-std | 1 + .../pyth_sample/.github/workflows/test.yml | 34 ++++++ price_feeds/evm/pyth_sample/.gitignore | 14 +++ price_feeds/evm/pyth_sample/README.md | 66 +++++++++++ price_feeds/evm/pyth_sample/foundry.toml | 6 + price_feeds/evm/pyth_sample/lib/forge-std | 1 + price_feeds/evm/pyth_sample/package-lock.json | 22 ++++ price_feeds/evm/pyth_sample/package.json | 19 +++ price_feeds/evm/pyth_sample/remappings.txt | 2 + .../evm/pyth_sample/src/PythSample.sol | 48 ++++++++ .../evm/pyth_sample/test/PythSample.t.sol | 111 ++++++++++++++++++ 13 files changed, 328 insertions(+) create mode 160000 benchmarks/evm/contracts/lib/forge-std create mode 160000 price_feeds/evm/morpho_deploy/lib/forge-std create mode 100644 price_feeds/evm/pyth_sample/.github/workflows/test.yml create mode 100644 price_feeds/evm/pyth_sample/.gitignore create mode 100644 price_feeds/evm/pyth_sample/README.md create mode 100644 price_feeds/evm/pyth_sample/foundry.toml create mode 160000 price_feeds/evm/pyth_sample/lib/forge-std create mode 100644 price_feeds/evm/pyth_sample/package-lock.json create mode 100644 price_feeds/evm/pyth_sample/package.json create mode 100644 price_feeds/evm/pyth_sample/remappings.txt create mode 100644 price_feeds/evm/pyth_sample/src/PythSample.sol create mode 100644 price_feeds/evm/pyth_sample/test/PythSample.t.sol diff --git a/.gitmodules b/.gitmodules index 970ba3b..5ba4828 100644 --- a/.gitmodules +++ b/.gitmodules @@ -7,3 +7,6 @@ [submodule "lazer/evm/lib/openzeppelin-contracts-upgradeable"] path = lazer/evm/lib/openzeppelin-contracts-upgradeable url = https://github.com/OpenZeppelin/openzeppelin-contracts-upgradeable +[submodule "price_feeds/evm/pyth_sample/lib/forge-std"] + path = price_feeds/evm/pyth_sample/lib/forge-std + url = https://github.com/foundry-rs/forge-std diff --git a/benchmarks/evm/contracts/lib/forge-std b/benchmarks/evm/contracts/lib/forge-std new file mode 160000 index 0000000..1eea5ba --- /dev/null +++ b/benchmarks/evm/contracts/lib/forge-std @@ -0,0 +1 @@ +Subproject commit 1eea5bae12ae557d589f9f0f0edae2faa47cb262 diff --git a/price_feeds/evm/morpho_deploy/lib/forge-std b/price_feeds/evm/morpho_deploy/lib/forge-std new file mode 160000 index 0000000..1eea5ba --- /dev/null +++ b/price_feeds/evm/morpho_deploy/lib/forge-std @@ -0,0 +1 @@ +Subproject commit 1eea5bae12ae557d589f9f0f0edae2faa47cb262 diff --git a/price_feeds/evm/pyth_sample/.github/workflows/test.yml b/price_feeds/evm/pyth_sample/.github/workflows/test.yml new file mode 100644 index 0000000..9282e82 --- /dev/null +++ b/price_feeds/evm/pyth_sample/.github/workflows/test.yml @@ -0,0 +1,34 @@ +name: test + +on: workflow_dispatch + +env: + FOUNDRY_PROFILE: ci + +jobs: + check: + strategy: + fail-fast: true + + name: Foundry project + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + submodules: recursive + + - name: Install Foundry + uses: foundry-rs/foundry-toolchain@v1 + with: + version: nightly + + - name: Run Forge build + run: | + forge --version + forge build --sizes + id: build + + - name: Run Forge tests + run: | + forge test -vvv + id: test diff --git a/price_feeds/evm/pyth_sample/.gitignore b/price_feeds/evm/pyth_sample/.gitignore new file mode 100644 index 0000000..85198aa --- /dev/null +++ b/price_feeds/evm/pyth_sample/.gitignore @@ -0,0 +1,14 @@ +# Compiler files +cache/ +out/ + +# Ignores development broadcast logs +!/broadcast +/broadcast/*/31337/ +/broadcast/**/dry-run/ + +# Docs +docs/ + +# Dotenv file +.env diff --git a/price_feeds/evm/pyth_sample/README.md b/price_feeds/evm/pyth_sample/README.md new file mode 100644 index 0000000..9265b45 --- /dev/null +++ b/price_feeds/evm/pyth_sample/README.md @@ -0,0 +1,66 @@ +## Foundry + +**Foundry is a blazing fast, portable and modular toolkit for Ethereum application development written in Rust.** + +Foundry consists of: + +- **Forge**: Ethereum testing framework (like Truffle, Hardhat and DappTools). +- **Cast**: Swiss army knife for interacting with EVM smart contracts, sending transactions and getting chain data. +- **Anvil**: Local Ethereum node, akin to Ganache, Hardhat Network. +- **Chisel**: Fast, utilitarian, and verbose solidity REPL. + +## Documentation + +https://book.getfoundry.sh/ + +## Usage + +### Build + +```shell +$ forge build +``` + +### Test + +```shell +$ forge test +``` + +### Format + +```shell +$ forge fmt +``` + +### Gas Snapshots + +```shell +$ forge snapshot +``` + +### Anvil + +```shell +$ anvil +``` + +### Deploy + +```shell +$ forge script script/Counter.s.sol:CounterScript --rpc-url --private-key +``` + +### Cast + +```shell +$ cast +``` + +### Help + +```shell +$ forge --help +$ anvil --help +$ cast --help +``` diff --git a/price_feeds/evm/pyth_sample/foundry.toml b/price_feeds/evm/pyth_sample/foundry.toml new file mode 100644 index 0000000..25b918f --- /dev/null +++ b/price_feeds/evm/pyth_sample/foundry.toml @@ -0,0 +1,6 @@ +[profile.default] +src = "src" +out = "out" +libs = ["lib"] + +# See more config options https://github.com/foundry-rs/foundry/blob/master/crates/config/README.md#all-options diff --git a/price_feeds/evm/pyth_sample/lib/forge-std b/price_feeds/evm/pyth_sample/lib/forge-std new file mode 160000 index 0000000..1eea5ba --- /dev/null +++ b/price_feeds/evm/pyth_sample/lib/forge-std @@ -0,0 +1 @@ +Subproject commit 1eea5bae12ae557d589f9f0f0edae2faa47cb262 diff --git a/price_feeds/evm/pyth_sample/package-lock.json b/price_feeds/evm/pyth_sample/package-lock.json new file mode 100644 index 0000000..f2a32e6 --- /dev/null +++ b/price_feeds/evm/pyth_sample/package-lock.json @@ -0,0 +1,22 @@ +{ + "name": "pyth_sample", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "pyth_sample", + "version": "1.0.0", + "license": "ISC", + "dependencies": { + "@pythnetwork/pyth-sdk-solidity": "^4.0.0" + } + }, + "node_modules/@pythnetwork/pyth-sdk-solidity": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@pythnetwork/pyth-sdk-solidity/-/pyth-sdk-solidity-4.0.0.tgz", + "integrity": "sha512-Cy2MvSN1Oh5YpIYmZd2In6/gfXbGjnpazmXKioTuq07Drp4Rl2XHcvtqHdgilplCl32IG4pU+XoRafpexID08A==", + "license": "Apache-2.0" + } + } +} diff --git a/price_feeds/evm/pyth_sample/package.json b/price_feeds/evm/pyth_sample/package.json new file mode 100644 index 0000000..bc3e3df --- /dev/null +++ b/price_feeds/evm/pyth_sample/package.json @@ -0,0 +1,19 @@ +{ + "name": "pyth_sample", + "version": "1.0.0", + "description": "**Foundry is a blazing fast, portable and modular toolkit for Ethereum application development written in Rust.**", + "main": "index.js", + "directories": { + "lib": "lib", + "test": "test" + }, + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "keywords": [], + "author": "", + "license": "ISC", + "dependencies": { + "@pythnetwork/pyth-sdk-solidity": "^4.0.0" + } +} diff --git a/price_feeds/evm/pyth_sample/remappings.txt b/price_feeds/evm/pyth_sample/remappings.txt new file mode 100644 index 0000000..18db66a --- /dev/null +++ b/price_feeds/evm/pyth_sample/remappings.txt @@ -0,0 +1,2 @@ +@pythnetwork/pyth-sdk-solidity/=node_modules/@pythnetwork/pyth-sdk-solidity + diff --git a/price_feeds/evm/pyth_sample/src/PythSample.sol b/price_feeds/evm/pyth_sample/src/PythSample.sol new file mode 100644 index 0000000..0dc2b3f --- /dev/null +++ b/price_feeds/evm/pyth_sample/src/PythSample.sol @@ -0,0 +1,48 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.13; + + +import "@pythnetwork/pyth-sdk-solidity/IPyth.sol"; +import "@pythnetwork/pyth-sdk-solidity/PythStructs.sol"; + + +contract PythSample { + + IPyth pyth; + + // @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 + constructor(address _pyth) { + pyth = IPyth(_pyth); + } + + + // @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 + // @param priceUpdate: The encoded data to update the contract with the latest pricecontract-addresses/evm + function getLatestPrice(bytes32 priceId, bytes[] calldata priceUpdate) public payable returns (PythStructs.Price memory) { + + uint updateFee = pyth.getUpdateFee(priceUpdate); + pyth.updatePriceFeeds{ value: updateFee }(priceUpdate); + + return pyth.getPriceNoOlderThan(priceId, 60); + } + + + // @dev: This function is an example method to update multiple price feeds at once. + // @param priceIds: The price ids of the price feeds. + // @param priceUpdates: The encoded data to update the contract with the latest price + function getLatestPrices(bytes32[] calldata priceIds, bytes[] calldata priceUpdates) public payable returns (PythStructs.Price[] memory) { + + // Calculate the update fee for the price feeds + // One can update multiple price feeds by calling the updatePriceFeeds function once. + uint updateFee = pyth.getUpdateFee(priceUpdates); + pyth.updatePriceFeeds{ value: updateFee }(priceUpdates); + + // Get the latest prices for the price feeds + PythStructs.Price[] memory prices = new PythStructs.Price[](priceIds.length); + for (uint i = 0; i < priceIds.length; i++) { + prices[i] = pyth.getPriceNoOlderThan(priceIds[i], 60); + } + return prices; + } + +} diff --git a/price_feeds/evm/pyth_sample/test/PythSample.t.sol b/price_feeds/evm/pyth_sample/test/PythSample.t.sol new file mode 100644 index 0000000..664e070 --- /dev/null +++ b/price_feeds/evm/pyth_sample/test/PythSample.t.sol @@ -0,0 +1,111 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.13; + +import { Test, console2 } from "forge-std/Test.sol"; +import { PythSample } from "../src/PythSample.sol"; +import { MockPyth } from "@pythnetwork/pyth-sdk-solidity/MockPyth.sol"; +import { PythStructs } from "@pythnetwork/pyth-sdk-solidity/PythStructs.sol"; + +contract PythSampleTest is Test { + MockPyth public pyth; + PythSample public app; + + // @dev: Dummy price ids for testing. + bytes32 constant ETH_PRICE_FEED_ID = bytes32(uint256(0x1)); + bytes32 constant BTC_PRICE_FEED_ID = bytes32(uint256(0x2)); + + // @dev: Setup the test environment. + function setUp() public { + pyth = new MockPyth(60, 1); + app = new PythSample(address(pyth)); + } + + // @dev: Creating a dummyprice update for a given price id and price. + function createPriceUpdate( + bytes32 priceId, + int64 price + ) private view returns (bytes[] memory) { + bytes[] memory updateData = new bytes[](1); + updateData[0] = pyth.createPriceFeedUpdateData( + priceId, + price * 100000, // price + 10 * 100000, // confidence + -5, // exponent + price * 100000, // emaPrice + 10 * 100000, // emaConfidence + uint64(block.timestamp), // publishTime + uint64(block.timestamp) // prevPublishTime + ); + return updateData; + } + + // @dev: Testing the getLatestPrice function. + function testGetLatestPrice() public { + bytes[] memory updateData = createPriceUpdate(ETH_PRICE_FEED_ID, 2000); + uint updateFee = pyth.getUpdateFee(updateData); + + vm.deal(address(this), updateFee); + PythStructs.Price memory price = app.getLatestPrice{value: updateFee}( + ETH_PRICE_FEED_ID, + updateData + ); + + assertEq(price.price, 2000 * 100000); + } + + function testGetLatestPrices() public { + bytes32[] memory priceIds = new bytes32[](2); + priceIds[0] = ETH_PRICE_FEED_ID; + priceIds[1] = BTC_PRICE_FEED_ID; + + bytes[] memory updateData = new bytes[](2); + updateData[0] = pyth.createPriceFeedUpdateData( + ETH_PRICE_FEED_ID, + 2000 * 100000, // ETH price + 10 * 100000, + -5, + 2000 * 100000, + 10 * 100000, + uint64(block.timestamp), + uint64(block.timestamp) + ); + updateData[1] = pyth.createPriceFeedUpdateData( + BTC_PRICE_FEED_ID, + 30000 * 100000, // BTC price + 10 * 100000, + -5, + 30000 * 100000, + 10 * 100000, + uint64(block.timestamp), + uint64(block.timestamp) + ); + + uint updateFee = pyth.getUpdateFee(updateData); + vm.deal(address(this), updateFee); + + PythStructs.Price[] memory prices = app.getLatestPrices{value: updateFee}( + priceIds, + updateData + ); + + assertEq(prices.length, 2); + assertEq(prices[0].price, 2000 * 100000); + assertEq(prices[1].price, 30000 * 100000); + } + + function testStalePrice() public { + bytes[] memory updateData = createPriceUpdate(ETH_PRICE_FEED_ID, 2000); + uint updateFee = pyth.getUpdateFee(updateData); + vm.deal(address(this), updateFee); + + // Update price + pyth.updatePriceFeeds{value: updateFee}(updateData); + + // Skip 120 seconds (more than the 60-second staleness threshold) + skip(120); + + // Expect revert when trying to get price + vm.expectRevert(); + app.getLatestPrice{value: updateFee}(ETH_PRICE_FEED_ID, updateData); + } +} \ No newline at end of file From 147d8952ac8aa272a702472676069f4fd1214221 Mon Sep 17 00:00:00 2001 From: Aditya Arora Date: Fri, 13 Dec 2024 15:40:39 -0500 Subject: [PATCH 2/3] update --- benchmarks/evm/contracts/lib/forge-std | 1 - price_feeds/evm/morpho_deploy/lib/forge-std | 1 - price_feeds/evm/pyth_sample/README.md | 71 +++++++------------ .../evm/pyth_sample/src/PythSample.sol | 2 - 4 files changed, 25 insertions(+), 50 deletions(-) delete mode 160000 benchmarks/evm/contracts/lib/forge-std delete mode 160000 price_feeds/evm/morpho_deploy/lib/forge-std diff --git a/benchmarks/evm/contracts/lib/forge-std b/benchmarks/evm/contracts/lib/forge-std deleted file mode 160000 index 1eea5ba..0000000 --- a/benchmarks/evm/contracts/lib/forge-std +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 1eea5bae12ae557d589f9f0f0edae2faa47cb262 diff --git a/price_feeds/evm/morpho_deploy/lib/forge-std b/price_feeds/evm/morpho_deploy/lib/forge-std deleted file mode 160000 index 1eea5ba..0000000 --- a/price_feeds/evm/morpho_deploy/lib/forge-std +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 1eea5bae12ae557d589f9f0f0edae2faa47cb262 diff --git a/price_feeds/evm/pyth_sample/README.md b/price_feeds/evm/pyth_sample/README.md index 9265b45..40f42e4 100644 --- a/price_feeds/evm/pyth_sample/README.md +++ b/price_feeds/evm/pyth_sample/README.md @@ -1,66 +1,45 @@ -## Foundry +# Pyth Oracle sample app -**Foundry is a blazing fast, portable and modular toolkit for Ethereum application development written in Rust.** +This directory contains an example of a smart contract using Pyth Price Feeds. -Foundry consists of: +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. -- **Forge**: Ethereum testing framework (like Truffle, Hardhat and DappTools). -- **Cast**: Swiss army knife for interacting with EVM smart contracts, sending transactions and getting chain data. -- **Anvil**: Local Ethereum node, akin to Ganache, Hardhat Network. -- **Chisel**: Fast, utilitarian, and verbose solidity REPL. +### Building -## Documentation +You need to have [Foundry](https://getfoundry.sh/) and `node` installed to run this example. +Once you have installed these tools, run the following commands from root directory to install forge dependencies: -https://book.getfoundry.sh/ - -## Usage - -### Build - -```shell -$ forge build ``` - -### Test - -```shell -$ forge test +forge install foundry-rs/forge-std@v1.8.0 --no-git --no-commit ``` -### Format +After installing the above dependencies, you need to install pyth-sdk-solidity. -```shell -$ forge fmt ``` - -### Gas Snapshots - -```shell -$ forge snapshot +npm init -y +npm install @pythnetwork/pyth-sdk-solidity ``` -### Anvil +### Testing -```shell -$ anvil -``` +Simply run `forge test` from root directory. -### Deploy -```shell -$ forge script script/Counter.s.sol:CounterScript --rpc-url --private-key -``` +### Resources -### Cast +- [Pyth Price Feeds Documentation](https://docs.pyth.network/price-feeds) +- [Pyth Price Feed IDs](https://www.pyth.network/developers/price-feed-ids) +- [Pyth Price Feeds Contracts](https://docs.pyth.network/price-feeds/contract-addresses/evm) -```shell -$ cast -``` +### Retrieve Price Updates. -### Help +Price updates can be retrieved using Hermes. It provides various ways to retrieve price updates. -```shell -$ forge --help -$ anvil --help -$ cast --help +For example + +``` +curl -X 'GET' \ + 'https://hermes.pyth.network/v2/updates/price/latest?ids%5B%5D=0xe62df6c8b4a85fe1a67db44dc12de5db330f7ac66b72dc658afedf0f4a415b43&ids%5B%5D=0xc96458d393fe9deb7a7d63a0ac41e2898a67a7750dbd166673279e06c868df0a' ``` + +Checkout [How to Fetch Price Updates](https://docs.pyth.network/price-feeds/fetch-price-updates) for more details. \ No newline at end of file diff --git a/price_feeds/evm/pyth_sample/src/PythSample.sol b/price_feeds/evm/pyth_sample/src/PythSample.sol index 0dc2b3f..a40642e 100644 --- a/price_feeds/evm/pyth_sample/src/PythSample.sol +++ b/price_feeds/evm/pyth_sample/src/PythSample.sol @@ -15,7 +15,6 @@ contract PythSample { pyth = IPyth(_pyth); } - // @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 // @param priceUpdate: The encoded data to update the contract with the latest pricecontract-addresses/evm function getLatestPrice(bytes32 priceId, bytes[] calldata priceUpdate) public payable returns (PythStructs.Price memory) { @@ -26,7 +25,6 @@ contract PythSample { return pyth.getPriceNoOlderThan(priceId, 60); } - // @dev: This function is an example method to update multiple price feeds at once. // @param priceIds: The price ids of the price feeds. // @param priceUpdates: The encoded data to update the contract with the latest price From 1402e34882610d49f23bb4798bb478c67928cf43 Mon Sep 17 00:00:00 2001 From: Aditya Arora Date: Tue, 17 Dec 2024 11:55:41 -0500 Subject: [PATCH 3/3] remove workflow --- .../pyth_sample/.github/workflows/test.yml | 34 ------------------- 1 file changed, 34 deletions(-) delete mode 100644 price_feeds/evm/pyth_sample/.github/workflows/test.yml diff --git a/price_feeds/evm/pyth_sample/.github/workflows/test.yml b/price_feeds/evm/pyth_sample/.github/workflows/test.yml deleted file mode 100644 index 9282e82..0000000 --- a/price_feeds/evm/pyth_sample/.github/workflows/test.yml +++ /dev/null @@ -1,34 +0,0 @@ -name: test - -on: workflow_dispatch - -env: - FOUNDRY_PROFILE: ci - -jobs: - check: - strategy: - fail-fast: true - - name: Foundry project - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - with: - submodules: recursive - - - name: Install Foundry - uses: foundry-rs/foundry-toolchain@v1 - with: - version: nightly - - - name: Run Forge build - run: | - forge --version - forge build --sizes - id: build - - - name: Run Forge tests - run: | - forge test -vvv - id: test