From 2d083597633a270f9bf9f2711ad9af34504ba101 Mon Sep 17 00:00:00 2001 From: Aditya Arora Date: Tue, 26 Mar 2024 11:53:08 -0400 Subject: [PATCH 1/8] Moving convertToUint to utils --- .../oracle_swap/contract/src/OracleSwap.sol | 37 +++++++------------ .../ethereum/sdk/solidity/PythUtils.sol | 33 +++++++++++++++++ 2 files changed, 46 insertions(+), 24 deletions(-) create mode 100644 target_chains/ethereum/sdk/solidity/PythUtils.sol diff --git a/target_chains/ethereum/examples/oracle_swap/contract/src/OracleSwap.sol b/target_chains/ethereum/examples/oracle_swap/contract/src/OracleSwap.sol index d62021f169..8b51ac0a3d 100644 --- a/target_chains/ethereum/examples/oracle_swap/contract/src/OracleSwap.sol +++ b/target_chains/ethereum/examples/oracle_swap/contract/src/OracleSwap.sol @@ -3,6 +3,7 @@ pragma solidity ^0.8.0; import "pyth-sdk-solidity/IPyth.sol"; import "pyth-sdk-solidity/PythStructs.sol"; +import "pyth-sdk-solidity/PythUtils.sol"; import "openzeppelin-contracts/contracts/token/ERC20/ERC20.sol"; // Example oracle AMM powered by Pyth price feeds. @@ -69,8 +70,18 @@ contract OracleSwap { // Note: this code does all arithmetic with 18 decimal points. This approach should be fine for most // price feeds, which typically have ~8 decimals. You can check the exponent on the price feed to ensure // this doesn't lose precision. - uint256 basePrice = convertToUint(currentBasePrice, 18); - uint256 quotePrice = convertToUint(currentQuotePrice, 18); + + uint256 basePrice = PythUtils.convertToUint( + currentBasePrice.price, + currentBasePrice.expo, + 18 + ); + + uint256 quotePrice = PythUtils.convertToUint( + currentQuotePrice.price, + currentQuotePrice.expo, + 18 + ); // This computation loses precision. The infinite-precision result is between [quoteSize, quoteSize + 1] // We need to round this result in favor of the contract. @@ -90,28 +101,6 @@ contract OracleSwap { } } - // TODO: we should probably move something like this into the solidity sdk - function convertToUint( - PythStructs.Price memory price, - uint8 targetDecimals - ) private pure returns (uint256) { - if (price.price < 0 || price.expo > 0 || price.expo < -255) { - revert(); - } - - uint8 priceDecimals = uint8(uint32(-1 * price.expo)); - - if (targetDecimals >= priceDecimals) { - return - uint(uint64(price.price)) * - 10 ** uint32(targetDecimals - priceDecimals); - } else { - return - uint(uint64(price.price)) / - 10 ** uint32(priceDecimals - targetDecimals); - } - } - // Get the number of base tokens in the pool function baseBalance() public view returns (uint256) { return baseToken.balanceOf(address(this)); diff --git a/target_chains/ethereum/sdk/solidity/PythUtils.sol b/target_chains/ethereum/sdk/solidity/PythUtils.sol new file mode 100644 index 0000000000..58bfdbf580 --- /dev/null +++ b/target_chains/ethereum/sdk/solidity/PythUtils.sol @@ -0,0 +1,33 @@ + +// SPDX-License-Identifier: Apache-2.0 +pragma solidity ^0.8.0; + +library PythUtils { + + /// @notice Converts a Pyth price to a uint256 with a target number of decimals + /// @param price The Pyth price + /// @param expo The Pyth price exponent + /// @param targetDecimals The target number of decimals + /// @return The price as a uint256 + function convertToUint( + int64 price, + int32 expo, + uint8 targetDecimals + ) private pure returns (uint256) { + if (price < 0 || expo > 0 || expo < -255) { + revert(); + } + + uint8 priceDecimals = uint8(uint32(-1 * expo)); + + if (targetDecimals >= priceDecimals) { + return + uint(uint64(price)) * + 10 ** uint32(targetDecimals - priceDecimals); + } else { + return + uint(uint64(price)) / + 10 ** uint32(priceDecimals - targetDecimals); + } + } +} \ No newline at end of file From 3456853a08b42b0f1b2abbd0d2ce95e892826416 Mon Sep 17 00:00:00 2001 From: Aditya Arora Date: Tue, 26 Mar 2024 12:20:52 -0400 Subject: [PATCH 2/8] pre-commit fix --- target_chains/ethereum/sdk/solidity/PythUtils.sol | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/target_chains/ethereum/sdk/solidity/PythUtils.sol b/target_chains/ethereum/sdk/solidity/PythUtils.sol index 58bfdbf580..ac125562d0 100644 --- a/target_chains/ethereum/sdk/solidity/PythUtils.sol +++ b/target_chains/ethereum/sdk/solidity/PythUtils.sol @@ -1,11 +1,9 @@ - // SPDX-License-Identifier: Apache-2.0 pragma solidity ^0.8.0; library PythUtils { - /// @notice Converts a Pyth price to a uint256 with a target number of decimals - /// @param price The Pyth price + /// @param price The Pyth price /// @param expo The Pyth price exponent /// @param targetDecimals The target number of decimals /// @return The price as a uint256 @@ -30,4 +28,4 @@ library PythUtils { 10 ** uint32(priceDecimals - targetDecimals); } } -} \ No newline at end of file +} From 11f5132701266d076f4c0b7b995277964afc59f2 Mon Sep 17 00:00:00 2001 From: Aditya Arora Date: Wed, 27 Mar 2024 16:18:02 -0400 Subject: [PATCH 3/8] reversing OracleSwap example --- .../forge-test/utils/PythTestUtils.t.sol | 15 ++++++++ .../oracle_swap/contract/src/OracleSwap.sol | 37 ++++++++++++------- .../ethereum/sdk/solidity/PythUtils.sol | 2 +- .../ethereum/sdk/solidity/package.json | 2 +- 4 files changed, 41 insertions(+), 15 deletions(-) diff --git a/target_chains/ethereum/contracts/forge-test/utils/PythTestUtils.t.sol b/target_chains/ethereum/contracts/forge-test/utils/PythTestUtils.t.sol index db9bf94c48..ccb3e35c9e 100644 --- a/target_chains/ethereum/contracts/forge-test/utils/PythTestUtils.t.sol +++ b/target_chains/ethereum/contracts/forge-test/utils/PythTestUtils.t.sol @@ -12,6 +12,7 @@ import "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol"; import "@pythnetwork/pyth-sdk-solidity/PythStructs.sol"; import "@pythnetwork/pyth-sdk-solidity/IPythEvents.sol"; import "@pythnetwork/pyth-sdk-solidity/IPyth.sol"; +import "@pythnetwork/pyth-sdk-solidity/PythUtils.sol"; import "forge-std/Test.sol"; import "./WormholeTestUtils.t.sol"; @@ -275,3 +276,17 @@ abstract contract PythTestUtils is Test, WormholeTestUtils, RandTestUtils { } } } + +contract PythTestUtilsTest is + Test, + WormholeTestUtils, + PythTestUtils, + IPythEvents +{ + using PythUtils for *; + function testConvertToUnit() public { + + vm.expectRevert(); + uint256 price = PythUtils.convertToUnit(-100, -5,18); + } +} diff --git a/target_chains/ethereum/examples/oracle_swap/contract/src/OracleSwap.sol b/target_chains/ethereum/examples/oracle_swap/contract/src/OracleSwap.sol index 8b51ac0a3d..d62021f169 100644 --- a/target_chains/ethereum/examples/oracle_swap/contract/src/OracleSwap.sol +++ b/target_chains/ethereum/examples/oracle_swap/contract/src/OracleSwap.sol @@ -3,7 +3,6 @@ pragma solidity ^0.8.0; import "pyth-sdk-solidity/IPyth.sol"; import "pyth-sdk-solidity/PythStructs.sol"; -import "pyth-sdk-solidity/PythUtils.sol"; import "openzeppelin-contracts/contracts/token/ERC20/ERC20.sol"; // Example oracle AMM powered by Pyth price feeds. @@ -70,18 +69,8 @@ contract OracleSwap { // Note: this code does all arithmetic with 18 decimal points. This approach should be fine for most // price feeds, which typically have ~8 decimals. You can check the exponent on the price feed to ensure // this doesn't lose precision. - - uint256 basePrice = PythUtils.convertToUint( - currentBasePrice.price, - currentBasePrice.expo, - 18 - ); - - uint256 quotePrice = PythUtils.convertToUint( - currentQuotePrice.price, - currentQuotePrice.expo, - 18 - ); + uint256 basePrice = convertToUint(currentBasePrice, 18); + uint256 quotePrice = convertToUint(currentQuotePrice, 18); // This computation loses precision. The infinite-precision result is between [quoteSize, quoteSize + 1] // We need to round this result in favor of the contract. @@ -101,6 +90,28 @@ contract OracleSwap { } } + // TODO: we should probably move something like this into the solidity sdk + function convertToUint( + PythStructs.Price memory price, + uint8 targetDecimals + ) private pure returns (uint256) { + if (price.price < 0 || price.expo > 0 || price.expo < -255) { + revert(); + } + + uint8 priceDecimals = uint8(uint32(-1 * price.expo)); + + if (targetDecimals >= priceDecimals) { + return + uint(uint64(price.price)) * + 10 ** uint32(targetDecimals - priceDecimals); + } else { + return + uint(uint64(price.price)) / + 10 ** uint32(priceDecimals - targetDecimals); + } + } + // Get the number of base tokens in the pool function baseBalance() public view returns (uint256) { return baseToken.balanceOf(address(this)); diff --git a/target_chains/ethereum/sdk/solidity/PythUtils.sol b/target_chains/ethereum/sdk/solidity/PythUtils.sol index ac125562d0..01fbd15803 100644 --- a/target_chains/ethereum/sdk/solidity/PythUtils.sol +++ b/target_chains/ethereum/sdk/solidity/PythUtils.sol @@ -11,7 +11,7 @@ library PythUtils { int64 price, int32 expo, uint8 targetDecimals - ) private pure returns (uint256) { + ) public pure returns (uint256) { if (price < 0 || expo > 0 || expo < -255) { revert(); } diff --git a/target_chains/ethereum/sdk/solidity/package.json b/target_chains/ethereum/sdk/solidity/package.json index 2865a73ba8..1f13785bf9 100644 --- a/target_chains/ethereum/sdk/solidity/package.json +++ b/target_chains/ethereum/sdk/solidity/package.json @@ -9,7 +9,7 @@ }, "scripts": { "format": "npx prettier --write .", - "generate-abi": "npx generate-abis IPyth IPythEvents AbstractPyth MockPyth PythErrors", + "generate-abi": "npx generate-abis IPyth IPythEvents AbstractPyth MockPyth PythErrors PythUtils", "check-abi": "git diff --exit-code abis", "build": "solcjs --bin MockPyth.sol --base-path . -o build/" }, From 4ecad586d1441c3d67c4661de74e5243db7a5af0 Mon Sep 17 00:00:00 2001 From: Aditya Arora Date: Wed, 27 Mar 2024 16:18:51 -0400 Subject: [PATCH 4/8] pre-commit] --- .../ethereum/contracts/forge-test/utils/PythTestUtils.t.sol | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/target_chains/ethereum/contracts/forge-test/utils/PythTestUtils.t.sol b/target_chains/ethereum/contracts/forge-test/utils/PythTestUtils.t.sol index ccb3e35c9e..d32eb8ca70 100644 --- a/target_chains/ethereum/contracts/forge-test/utils/PythTestUtils.t.sol +++ b/target_chains/ethereum/contracts/forge-test/utils/PythTestUtils.t.sol @@ -284,9 +284,9 @@ contract PythTestUtilsTest is IPythEvents { using PythUtils for *; + function testConvertToUnit() public { - vm.expectRevert(); - uint256 price = PythUtils.convertToUnit(-100, -5,18); + uint256 price = PythUtils.convertToUnit(-100, -5, 18); } } From 0f906a521c0eaf22f7670f64d1f281f435e352b6 Mon Sep 17 00:00:00 2001 From: Aditya Arora Date: Thu, 28 Mar 2024 01:46:19 -0400 Subject: [PATCH 5/8] added test --- .../forge-test/utils/PythTestUtils.t.sol | 29 +++++++++++++++++-- .../ethereum/sdk/solidity/PythUtils.sol | 2 ++ 2 files changed, 28 insertions(+), 3 deletions(-) diff --git a/target_chains/ethereum/contracts/forge-test/utils/PythTestUtils.t.sol b/target_chains/ethereum/contracts/forge-test/utils/PythTestUtils.t.sol index d32eb8ca70..6d00d75557 100644 --- a/target_chains/ethereum/contracts/forge-test/utils/PythTestUtils.t.sol +++ b/target_chains/ethereum/contracts/forge-test/utils/PythTestUtils.t.sol @@ -283,10 +283,33 @@ contract PythTestUtilsTest is PythTestUtils, IPythEvents { - using PythUtils for *; - function testConvertToUnit() public { + // Price can't be negative + vm.expectRevert(); + PythUtils.convertToUint(-100, -5, 18); + + // Exponent can't be positive vm.expectRevert(); - uint256 price = PythUtils.convertToUnit(-100, -5, 18); + PythUtils.convertToUint(100, 5, 18); + + // Price with 18 decimals and exponent -5 + assertEq( + PythUtils.convertToUint(100, -5, 18), + 1000000000000000 // 100 * 10^13 + ); + + // Price with 9 decimals and exponent -2 + assertEq( + PythUtils.convertToUint(100, -2, 9), + 1000000000 // 100 * 10^7 + ); + + // Price with 4 decimals and exponent -5 + assertEq(PythUtils.convertToUint(100, -5, 4), 10); + + // Price with 5 decimals and exponent -2 + // @note: We will lose precision here as price is + // 0.00001 and we are targetDecimals is 2. + assertEq(PythUtils.convertToUint(100, -5, 2), 0); } } diff --git a/target_chains/ethereum/sdk/solidity/PythUtils.sol b/target_chains/ethereum/sdk/solidity/PythUtils.sol index 01fbd15803..3b4b79096f 100644 --- a/target_chains/ethereum/sdk/solidity/PythUtils.sol +++ b/target_chains/ethereum/sdk/solidity/PythUtils.sol @@ -7,6 +7,8 @@ library PythUtils { /// @param expo The Pyth price exponent /// @param targetDecimals The target number of decimals /// @return The price as a uint256 + /// @dev Function will loose precision if targetDecimals is less than the Pyth price decimals + /// e.g. If the price is 0.000123 and the targetDecimals is 2, the result will be 0 function convertToUint( int64 price, int32 expo, From 9d57e8410b78d014087d9a97ad54c011617c6fdc Mon Sep 17 00:00:00 2001 From: Aditya Arora Date: Thu, 28 Mar 2024 09:36:21 -0400 Subject: [PATCH 6/8] abi-gen --- .../ethereum/sdk/solidity/abis/PythUtils.json | 31 +++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 target_chains/ethereum/sdk/solidity/abis/PythUtils.json diff --git a/target_chains/ethereum/sdk/solidity/abis/PythUtils.json b/target_chains/ethereum/sdk/solidity/abis/PythUtils.json new file mode 100644 index 0000000000..c30f138950 --- /dev/null +++ b/target_chains/ethereum/sdk/solidity/abis/PythUtils.json @@ -0,0 +1,31 @@ +[ + { + "inputs": [ + { + "internalType": "int64", + "name": "price", + "type": "int64" + }, + { + "internalType": "int32", + "name": "expo", + "type": "int32" + }, + { + "internalType": "uint8", + "name": "targetDecimals", + "type": "uint8" + } + ], + "name": "convertToUint", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "pure", + "type": "function" + } +] From 1b41cdeacf85cdb4ef2f56d10dfb90a5234e1c0b Mon Sep 17 00:00:00 2001 From: Aditya Arora Date: Thu, 28 Mar 2024 12:25:20 -0400 Subject: [PATCH 7/8] Added solc to sdk --- package-lock.json | 74 ++++++++++++++++++- .../ethereum/sdk/solidity/package.json | 3 +- 2 files changed, 74 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index b3e1df662b..87a4d858a1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -59453,7 +59453,47 @@ "devDependencies": { "abi_generator": "*", "prettier": "^2.7.1", - "prettier-plugin-solidity": "^1.0.0-rc.1" + "prettier-plugin-solidity": "^1.0.0-rc.1", + "solc": "^0.8.25" + } + }, + "target_chains/ethereum/sdk/solidity/node_modules/commander": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz", + "integrity": "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==", + "dev": true, + "engines": { + "node": ">= 12" + } + }, + "target_chains/ethereum/sdk/solidity/node_modules/semver": { + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", + "dev": true, + "bin": { + "semver": "bin/semver" + } + }, + "target_chains/ethereum/sdk/solidity/node_modules/solc": { + "version": "0.8.25", + "resolved": "https://registry.npmjs.org/solc/-/solc-0.8.25.tgz", + "integrity": "sha512-7P0TF8gPeudl1Ko3RGkyY6XVCxe2SdD/qQhtns1vl3yAbK/PDifKDLHGtx1t7mX3LgR7ojV7Fg/Kc6Q9D2T8UQ==", + "dev": true, + "dependencies": { + "command-exists": "^1.2.8", + "commander": "^8.1.0", + "follow-redirects": "^1.12.1", + "js-sha3": "0.8.0", + "memorystream": "^0.3.1", + "semver": "^5.5.0", + "tmp": "0.0.33" + }, + "bin": { + "solcjs": "solc.js" + }, + "engines": { + "node": ">=10.0.0" } }, "target_chains/solana/sdk/js/pyth_solana_receiver": { @@ -71066,7 +71106,37 @@ "requires": { "abi_generator": "*", "prettier": "^2.7.1", - "prettier-plugin-solidity": "^1.0.0-rc.1" + "prettier-plugin-solidity": "^1.0.0-rc.1", + "solc": "*" + }, + "dependencies": { + "commander": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz", + "integrity": "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==", + "dev": true + }, + "semver": { + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", + "dev": true + }, + "solc": { + "version": "0.8.25", + "resolved": "https://registry.npmjs.org/solc/-/solc-0.8.25.tgz", + "integrity": "sha512-7P0TF8gPeudl1Ko3RGkyY6XVCxe2SdD/qQhtns1vl3yAbK/PDifKDLHGtx1t7mX3LgR7ojV7Fg/Kc6Q9D2T8UQ==", + "dev": true, + "requires": { + "command-exists": "^1.2.8", + "commander": "^8.1.0", + "follow-redirects": "^1.12.1", + "js-sha3": "0.8.0", + "memorystream": "^0.3.1", + "semver": "^5.5.0", + "tmp": "0.0.33" + } + } } }, "@pythnetwork/pyth-solana-receiver": { diff --git a/target_chains/ethereum/sdk/solidity/package.json b/target_chains/ethereum/sdk/solidity/package.json index 1f13785bf9..4a9d3e7257 100644 --- a/target_chains/ethereum/sdk/solidity/package.json +++ b/target_chains/ethereum/sdk/solidity/package.json @@ -25,8 +25,9 @@ }, "homepage": "https://github.com/pyth-network/pyth-crosschain/tree/main/target_chains/ethereum/sdk/solidity", "devDependencies": { + "abi_generator": "*", "prettier": "^2.7.1", "prettier-plugin-solidity": "^1.0.0-rc.1", - "abi_generator": "*" + "solc": "^0.8.25" } } From 72f3f56007f2a9c8f2d58737f663e4fa33913f33 Mon Sep 17 00:00:00 2001 From: Aditya Arora Date: Thu, 28 Mar 2024 17:14:20 -0400 Subject: [PATCH 8/8] resolved comments --- .../contracts/forge-test/utils/PythTestUtils.t.sol | 7 +------ target_chains/ethereum/sdk/solidity/PythUtils.sol | 3 ++- 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/target_chains/ethereum/contracts/forge-test/utils/PythTestUtils.t.sol b/target_chains/ethereum/contracts/forge-test/utils/PythTestUtils.t.sol index 6d00d75557..73149425b3 100644 --- a/target_chains/ethereum/contracts/forge-test/utils/PythTestUtils.t.sol +++ b/target_chains/ethereum/contracts/forge-test/utils/PythTestUtils.t.sol @@ -277,12 +277,7 @@ abstract contract PythTestUtils is Test, WormholeTestUtils, RandTestUtils { } } -contract PythTestUtilsTest is - Test, - WormholeTestUtils, - PythTestUtils, - IPythEvents -{ +contract PythUtilsTest is Test, WormholeTestUtils, PythTestUtils, IPythEvents { function testConvertToUnit() public { // Price can't be negative vm.expectRevert(); diff --git a/target_chains/ethereum/sdk/solidity/PythUtils.sol b/target_chains/ethereum/sdk/solidity/PythUtils.sol index 3b4b79096f..04b7f51f34 100644 --- a/target_chains/ethereum/sdk/solidity/PythUtils.sol +++ b/target_chains/ethereum/sdk/solidity/PythUtils.sol @@ -7,7 +7,8 @@ library PythUtils { /// @param expo The Pyth price exponent /// @param targetDecimals The target number of decimals /// @return The price as a uint256 - /// @dev Function will loose precision if targetDecimals is less than the Pyth price decimals + /// @dev Function will lose precision if targetDecimals is less than the Pyth price decimals. + /// This method will truncate any digits that cannot be represented by the targetDecimals. /// e.g. If the price is 0.000123 and the targetDecimals is 2, the result will be 0 function convertToUint( int64 price,