From 37ff56fdc03a80bf3336c9f256f3458fd80ffa08 Mon Sep 17 00:00:00 2001 From: thedavidmeister Date: Sat, 4 Oct 2025 21:55:30 +0400 Subject: [PATCH 01/14] update forge-std --- lib/forge-std | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/forge-std b/lib/forge-std index 77041d2..17a9b23 160000 --- a/lib/forge-std +++ b/lib/forge-std @@ -1 +1 @@ -Subproject commit 77041d2ce690e692d6e03cc812b57d1ddaa4d505 +Subproject commit 17a9b2398a7d629931dc66a168a098d051d53dc6 From ce2211c7da6415edde780642c97e253e1bb2df40 Mon Sep 17 00:00:00 2001 From: thedavidmeister Date: Sat, 4 Oct 2025 21:55:45 +0400 Subject: [PATCH 02/14] bump nix --- flake.lock | 42 +++++++++++++++++++++--------------------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/flake.lock b/flake.lock index 586e2e3..78eae1c 100644 --- a/flake.lock +++ b/flake.lock @@ -182,11 +182,11 @@ "nixpkgs": "nixpkgs_5" }, "locked": { - "lastModified": 1741023058, - "narHash": "sha256-LSd/8CBlpDLjci5ANFJjP0w+dGdY/mqKsyUfhjGwnfs=", + "lastModified": 1758705030, + "narHash": "sha256-zYM8PiEXANNrtjfyGUc7w37/D/kCynp0cQS+wCQ77GI=", "owner": "shazow", "repo": "foundry.nix", - "rev": "66becfe20b7e688b8f2e5774609c4436cf202ba0", + "rev": "b59a55014050110170023e3e1c277c1d4a2f055b", "type": "github" }, "original": { @@ -272,11 +272,11 @@ }, "nixpkgs_6": { "locked": { - "lastModified": 1747828570, - "narHash": "sha256-tv8R4Z/69GC8zogsb5TNDRj5tkhMeHpyYIzRl1cJigo=", + "lastModified": 1758711836, + "narHash": "sha256-uBqPg7wNX2v6YUdTswH7wWU8wqb60cFZx0tHaWTGF30=", "owner": "nixos", "repo": "nixpkgs", - "rev": "040a62f13f40879a05578a66dd4ae0d284c55a5b", + "rev": "46f97b78e825ae762c0224e3983c47687436a498", "type": "github" }, "original": { @@ -303,11 +303,11 @@ }, "nixpkgs_8": { "locked": { - "lastModified": 1731531548, - "narHash": "sha256-sz8/v17enkYmfpgeeuyzniGJU0QQBfmAjlemAUYhfy8=", + "lastModified": 1748662220, + "narHash": "sha256-7gGa49iB9nCnFk4h/g9zwjlQAyjtpgcFkODjcOQS0Es=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "24f0d4acd634792badd6470134c387a3b039dace", + "rev": "59138c7667b7970d205d6a05a8bfa2d78caa3643", "type": "github" }, "original": { @@ -367,11 +367,11 @@ "solc": "solc_2" }, "locked": { - "lastModified": 1748346550, - "narHash": "sha256-Rlaj/hHHACo0blFKane6/arqIXWUCGe/sCNgqX8VfZ8=", + "lastModified": 1758730752, + "narHash": "sha256-ZQ1INSsEWYgb1NdC5zo6nu1ObLzOCDI+wjiZ4222fQ8=", "owner": "rainprotocol", "repo": "rainix", - "rev": "a1a5c321f356cb5006d16dd2d1a2242c183b7e96", + "rev": "cc7c9bbbd9b817aa3a6a5c994262e14fb0bc920c", "type": "github" }, "original": { @@ -411,11 +411,11 @@ "nixpkgs": "nixpkgs_7" }, "locked": { - "lastModified": 1747795013, - "narHash": "sha256-c7i0xJ+xFhgjO9SWHYu5dF/7lq63RPDvwKAdjc6VCE4=", + "lastModified": 1758681214, + "narHash": "sha256-8cW731vev6kfr58cILO2ZsjHwaPhm88dQ8Q6nTSjP9I=", "owner": "oxalica", "repo": "rust-overlay", - "rev": "6b1cf12374361859242a562e1933a7930649131a", + "rev": "b12ed88d8d33d4f3cbc842bf29fad93bb1437299", "type": "github" }, "original": { @@ -446,13 +446,13 @@ "solc-macos-amd64-list-json": { "flake": false, "locked": { - "narHash": "sha256-U5ckttxwKO13gIKggel6iybG5oTDbSidPR5nH3Gs+kY=", + "narHash": "sha256-AvITkfpNYgCypXuLJyqco0li+unVw39BAfdOZvd/SPE=", "type": "file", - "url": "https://github.com/ethereum/solc-bin/raw/30a3695/macosx-amd64/list.json" + "url": "https://github.com/argotorg/solc-bin/raw/26fc3fd/macosx-amd64/list.json" }, "original": { "type": "file", - "url": "https://github.com/ethereum/solc-bin/raw/30a3695/macosx-amd64/list.json" + "url": "https://github.com/argotorg/solc-bin/raw/26fc3fd/macosx-amd64/list.json" } }, "solc_2": { @@ -462,11 +462,11 @@ "solc-macos-amd64-list-json": "solc-macos-amd64-list-json" }, "locked": { - "lastModified": 1742758229, - "narHash": "sha256-FrU9rhab/0vOjjeFoQF+Ej43zRLv3enUIYjgLrH3Gd8=", + "lastModified": 1756368702, + "narHash": "sha256-cqEHv7uCV0LibmQphyiXZ1+jYtGjMNb9Pae4tfcAcF8=", "owner": "hellwolf", "repo": "solc.nix", - "rev": "6885b61bac89da19a6e3c70b89fdd592e2cef884", + "rev": "d83e90df2fa8359a690f6baabf76099432193c3f", "type": "github" }, "original": { From f4a62ef99f6f613b2f6ed7ce4513da10219e6e5d Mon Sep 17 00:00:00 2001 From: thedavidmeister Date: Sat, 4 Oct 2025 22:06:02 +0400 Subject: [PATCH 03/14] foundry lock --- foundry.lock | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 foundry.lock diff --git a/foundry.lock b/foundry.lock new file mode 100644 index 0000000..6777e54 --- /dev/null +++ b/foundry.lock @@ -0,0 +1,14 @@ +{ + "lib/forge-std": { + "rev": "17a9b2398a7d629931dc66a168a098d051d53dc6" + }, + "lib/pyth-crosschain": { + "rev": "5e356f425d7e05d2d3bfd50b8780500bd560ed19" + }, + "lib/rain.interpreter": { + "rev": "97927f4f613cdd4c165f493e5aeb216a6bd731e1" + }, + "lib/rain.math.float": { + "rev": "b506dd29f7e698c597497b8b337d61f40c883efb" + } +} \ No newline at end of file From a4c95cb05a371931b012c6dd4eaa44d3a5a118fa Mon Sep 17 00:00:00 2001 From: thedavidmeister Date: Sat, 4 Oct 2025 22:07:41 +0400 Subject: [PATCH 04/14] rm deps --- .gitmodules | 6 ------ foundry.lock | 6 ------ lib/rain.interpreter | 1 - lib/rain.math.float | 1 - 4 files changed, 14 deletions(-) delete mode 160000 lib/rain.interpreter delete mode 160000 lib/rain.math.float diff --git a/.gitmodules b/.gitmodules index 8cf4bc0..fffd46c 100644 --- a/.gitmodules +++ b/.gitmodules @@ -4,9 +4,3 @@ [submodule "lib/pyth-crosschain"] path = lib/pyth-crosschain url = https://github.com/pyth-network/pyth-crosschain -[submodule "lib/rain.math.float"] - path = lib/rain.math.float - url = https://github.com/rainlanguage/rain.math.float -[submodule "lib/rain.interpreter"] - path = lib/rain.interpreter - url = https://github.com/rainlanguage/rain.interpreter diff --git a/foundry.lock b/foundry.lock index 6777e54..2490d35 100644 --- a/foundry.lock +++ b/foundry.lock @@ -4,11 +4,5 @@ }, "lib/pyth-crosschain": { "rev": "5e356f425d7e05d2d3bfd50b8780500bd560ed19" - }, - "lib/rain.interpreter": { - "rev": "97927f4f613cdd4c165f493e5aeb216a6bd731e1" - }, - "lib/rain.math.float": { - "rev": "b506dd29f7e698c597497b8b337d61f40c883efb" } } \ No newline at end of file diff --git a/lib/rain.interpreter b/lib/rain.interpreter deleted file mode 160000 index 97927f4..0000000 --- a/lib/rain.interpreter +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 97927f4f613cdd4c165f493e5aeb216a6bd731e1 diff --git a/lib/rain.math.float b/lib/rain.math.float deleted file mode 160000 index b506dd2..0000000 --- a/lib/rain.math.float +++ /dev/null @@ -1 +0,0 @@ -Subproject commit b506dd29f7e698c597497b8b337d61f40c883efb From 956068b71bc53991567f9f4f3a43e18ad7d1d2f4 Mon Sep 17 00:00:00 2001 From: thedavidmeister Date: Sat, 4 Oct 2025 22:35:58 +0400 Subject: [PATCH 05/14] bump i9r --- .gitmodules | 3 +++ lib/rain.interpreter | 1 + 2 files changed, 4 insertions(+) create mode 160000 lib/rain.interpreter diff --git a/.gitmodules b/.gitmodules index fffd46c..868d8a8 100644 --- a/.gitmodules +++ b/.gitmodules @@ -4,3 +4,6 @@ [submodule "lib/pyth-crosschain"] path = lib/pyth-crosschain url = https://github.com/pyth-network/pyth-crosschain +[submodule "lib/rain.interpreter"] + path = lib/rain.interpreter + url = https://github.com/rainlanguage/rain.interpreter diff --git a/lib/rain.interpreter b/lib/rain.interpreter new file mode 160000 index 0000000..821f42a --- /dev/null +++ b/lib/rain.interpreter @@ -0,0 +1 @@ +Subproject commit 821f42ad028126e56f701d5fb18b0b3d039a9909 From 506b5cad5a61e7de7f4159b7e8aa4213f5212f13 Mon Sep 17 00:00:00 2001 From: thedavidmeister Date: Mon, 6 Oct 2025 16:58:57 +0400 Subject: [PATCH 06/14] install i9r --- foundry.lock | 3 +++ foundry.toml | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/foundry.lock b/foundry.lock index 2490d35..f0fbefb 100644 --- a/foundry.lock +++ b/foundry.lock @@ -4,5 +4,8 @@ }, "lib/pyth-crosschain": { "rev": "5e356f425d7e05d2d3bfd50b8780500bd560ed19" + }, + "lib/rain.interpreter": { + "rev": "821f42ad028126e56f701d5fb18b0b3d039a9909" } } \ No newline at end of file diff --git a/foundry.toml b/foundry.toml index 94424c5..3051d83 100644 --- a/foundry.toml +++ b/foundry.toml @@ -4,7 +4,7 @@ solc = "0.8.25" remappings = [ "pyth-sdk/=lib/pyth-crosschain/target_chains/ethereum/sdk/solidity/", - "rain.math.float/=lib/rain.math.float/src/", + "rain.math.float/=lib/rain.interpreter/lib/rain.interpreter.interface/lib/rain.math.float/src/", "rain.interpreter/=lib/rain.interpreter/src/", "rain.metadata/=lib/rain.interpreter/lib/rain.metadata/src/", "rain.sol.codegen/=lib/rain.interpreter/lib/rain.interpreter.interface/lib/rain.sol.codegen/src/", From fa334c1cae88ed7258503b5a9ae23bb079132d59 Mon Sep 17 00:00:00 2001 From: thedavidmeister Date: Mon, 6 Oct 2025 17:02:13 +0400 Subject: [PATCH 07/14] update deps --- .gitmodules | 6 +++--- foundry.lock | 7 +++++-- lib/pyth-crosschain | 2 +- lib/rain.interpreter | 2 +- 4 files changed, 10 insertions(+), 7 deletions(-) diff --git a/.gitmodules b/.gitmodules index 868d8a8..05c2101 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,9 +1,9 @@ [submodule "lib/forge-std"] path = lib/forge-std url = https://github.com/foundry-rs/forge-std -[submodule "lib/pyth-crosschain"] - path = lib/pyth-crosschain - url = https://github.com/pyth-network/pyth-crosschain [submodule "lib/rain.interpreter"] path = lib/rain.interpreter url = https://github.com/rainlanguage/rain.interpreter +[submodule "lib/pyth-crosschain"] + path = lib/pyth-crosschain + url = https://github.com/pyth-network/pyth-crosschain diff --git a/foundry.lock b/foundry.lock index f0fbefb..49c5652 100644 --- a/foundry.lock +++ b/foundry.lock @@ -3,9 +3,12 @@ "rev": "17a9b2398a7d629931dc66a168a098d051d53dc6" }, "lib/pyth-crosschain": { - "rev": "5e356f425d7e05d2d3bfd50b8780500bd560ed19" + "tag": { + "name": "v2.38.0", + "rev": "25ecd01bf7f5ce6e744f6a300b56dc2cdf7e65e6" + } }, "lib/rain.interpreter": { - "rev": "821f42ad028126e56f701d5fb18b0b3d039a9909" + "rev": "e31cdda0815228cc66663104685b4f3509c15daf" } } \ No newline at end of file diff --git a/lib/pyth-crosschain b/lib/pyth-crosschain index 5e356f4..25ecd01 160000 --- a/lib/pyth-crosschain +++ b/lib/pyth-crosschain @@ -1 +1 @@ -Subproject commit 5e356f425d7e05d2d3bfd50b8780500bd560ed19 +Subproject commit 25ecd01bf7f5ce6e744f6a300b56dc2cdf7e65e6 diff --git a/lib/rain.interpreter b/lib/rain.interpreter index 821f42a..e31cdda 160000 --- a/lib/rain.interpreter +++ b/lib/rain.interpreter @@ -1 +1 @@ -Subproject commit 821f42ad028126e56f701d5fb18b0b3d039a9909 +Subproject commit e31cdda0815228cc66663104685b4f3509c15daf From 5c4d0ca132bd6dcd01f77af9a30966b544f30a45 Mon Sep 17 00:00:00 2001 From: thedavidmeister Date: Mon, 6 Oct 2025 17:09:29 +0400 Subject: [PATCH 08/14] set pyth rev --- foundry.lock | 5 +---- lib/pyth-crosschain | 2 +- src/lib/op/LibOpPythPrice.sol | 4 ++-- 3 files changed, 4 insertions(+), 7 deletions(-) diff --git a/foundry.lock b/foundry.lock index 49c5652..213cba8 100644 --- a/foundry.lock +++ b/foundry.lock @@ -3,10 +3,7 @@ "rev": "17a9b2398a7d629931dc66a168a098d051d53dc6" }, "lib/pyth-crosschain": { - "tag": { - "name": "v2.38.0", - "rev": "25ecd01bf7f5ce6e744f6a300b56dc2cdf7e65e6" - } + "rev": "6248709ef4f5c20e4fb2828bc68789eb0164df99" }, "lib/rain.interpreter": { "rev": "e31cdda0815228cc66663104685b4f3509c15daf" diff --git a/lib/pyth-crosschain b/lib/pyth-crosschain index 25ecd01..6248709 160000 --- a/lib/pyth-crosschain +++ b/lib/pyth-crosschain @@ -1 +1 @@ -Subproject commit 25ecd01bf7f5ce6e744f6a300b56dc2cdf7e65e6 +Subproject commit 6248709ef4f5c20e4fb2828bc68789eb0164df99 diff --git a/src/lib/op/LibOpPythPrice.sol b/src/lib/op/LibOpPythPrice.sol index 9184433..0041277 100644 --- a/src/lib/op/LibOpPythPrice.sol +++ b/src/lib/op/LibOpPythPrice.sol @@ -19,13 +19,13 @@ library LibOpPythPrice { /// @param inputs the inputs to the extern. function run(Operand, uint256[] memory inputs) internal view returns (uint256[] memory) { IntOrAString symbol; - uint256 staleAfter; + Float staleAfter; assembly ("memory-safe") { symbol := mload(add(inputs, 0x20)) staleAfter := mload(add(inputs, 0x40)) } - uint256 price18 = LibPyth.getPriceNoOlderThan(symbol, staleAfter); + uint256 price18 = LibPyth.getPriceNoOlderThan(symbol, LibDecimalFloat.toFixedDecimalLossless(staleAfter, 0)); uint256[] memory outputs; assembly ("memory-safe") { From 2b33a70b3d64f47fbd2e2c7f5d1ef25ef047eee2 Mon Sep 17 00:00:00 2001 From: thedavidmeister Date: Mon, 6 Oct 2025 17:21:50 +0400 Subject: [PATCH 09/14] wip on build with new i9r --- src/abstract/PythExtern.sol | 10 +++++----- src/abstract/PythSubParser.sol | 20 ++++++++++---------- src/lib/op/LibOpPythPrice.sol | 7 ++++--- test/src/concrete/PythWords.pythPrice.t.sol | 7 ++++--- test/src/lib/op/LibOpPythPrice.t.sol | 6 +++--- 5 files changed, 26 insertions(+), 24 deletions(-) diff --git a/src/abstract/PythExtern.sol b/src/abstract/PythExtern.sol index 91a69a1..7ed9b97 100644 --- a/src/abstract/PythExtern.sol +++ b/src/abstract/PythExtern.sol @@ -2,7 +2,7 @@ // SPDX-FileCopyrightText: Copyright (c) 2020 Rain Open Source Software Ltd pragma solidity ^0.8.25; -import {BaseRainterpreterExternNPE2, Operand} from "rain.interpreter/abstract/BaseRainterpreterExternNPE2.sol"; +import {BaseRainterpreterExternNPE2, OperandV2} from "rain.interpreter/abstract/BaseRainterpreterExternNPE2.sol"; import {LibOpPythPrice} from "../lib/op/LibOpPythPrice.sol"; import {LibConvert} from "rain.lib.typecast/LibConvert.sol"; import {OPCODE_FUNCTION_POINTERS, INTEGRITY_FUNCTION_POINTERS} from "../generated/PythWords.pointers.sol"; @@ -21,10 +21,10 @@ abstract contract PythExtern is BaseRainterpreterExternNPE2 { } function buildOpcodeFunctionPointers() external pure returns (bytes memory) { - function(Operand, uint256[] memory) + function(OperandV2, uint256[] memory) internal view - returns (uint256[] memory)[] memory fs = new function(Operand, uint256[] memory) + returns (uint256[] memory)[] memory fs = new function(OperandV2, uint256[] memory) internal view returns (uint256[] memory)[](OPCODE_FUNCTION_POINTERS_LENGTH); @@ -38,10 +38,10 @@ abstract contract PythExtern is BaseRainterpreterExternNPE2 { } function buildIntegrityFunctionPointers() external pure returns (bytes memory) { - function(Operand, uint256, uint256) + function(OperandV2, uint256, uint256) internal pure - returns (uint256, uint256)[] memory fs = new function(Operand, uint256, uint256) + returns (uint256, uint256)[] memory fs = new function(OperandV2, uint256, uint256) internal pure returns (uint256, uint256)[](OPCODE_FUNCTION_POINTERS_LENGTH); diff --git a/src/abstract/PythSubParser.sol b/src/abstract/PythSubParser.sol index 9e19ece..03033d8 100644 --- a/src/abstract/PythSubParser.sol +++ b/src/abstract/PythSubParser.sol @@ -3,12 +3,12 @@ pragma solidity ^0.8.25; import {OPCODE_PYTH_PRICE} from "./PythExtern.sol"; -import {Operand, BaseRainterpreterSubParserNPE2} from "rain.interpreter/abstract/BaseRainterpreterSubParserNPE2.sol"; +import {OperandV2, BaseRainterpreterSubParserNPE2} from "rain.interpreter/abstract/BaseRainterpreterSubParserNPE2.sol"; import {LibParseOperand} from "rain.interpreter/lib/parse/LibParseOperand.sol"; import {SUB_PARSER_WORD_PARSERS_LENGTH, SUB_PARSER_WORD_PYTH_PRICE} from "../lib/parse/LibPythSubParser.sol"; import {LibConvert} from "rain.lib.typecast/LibConvert.sol"; import {LibSubParse} from "rain.interpreter/lib/parse/LibSubParse.sol"; -import {IInterpreterExternV3} from "rain.interpreter.interface/interface/IInterpreterExternV3.sol"; +import {IInterpreterExternV4} from "rain.interpreter.interface/interface/unstable/IInterpreterExternV4.sol"; import { OPERAND_HANDLER_FUNCTION_POINTERS as SUB_PARSER_OPERAND_HANDLERS, PARSE_META as SUB_PARSER_PARSE_META, @@ -39,10 +39,10 @@ abstract contract PythSubParser is BaseRainterpreterSubParserNPE2 { } function buildOperandHandlerFunctionPointers() external pure returns (bytes memory) { - function(uint256[] memory) internal pure returns (Operand)[] memory fs = new function(uint256[] memory) + function(bytes32[] memory) internal pure returns (OperandV2)[] memory fs = new function(bytes32[] memory) internal pure - returns (Operand)[](SUB_PARSER_WORD_PARSERS_LENGTH); + returns (OperandV2)[](SUB_PARSER_WORD_PARSERS_LENGTH); fs[SUB_PARSER_WORD_PYTH_PRICE] = LibParseOperand.handleOperandDisallowed; uint256[] memory pointers; @@ -57,13 +57,13 @@ abstract contract PythSubParser is BaseRainterpreterSubParserNPE2 { } function buildSubParserWordParsers() external pure returns (bytes memory) { - function(uint256, uint256, Operand) + function(uint256, uint256, OperandV2) internal view - returns (bool, bytes memory, uint256[] memory)[] memory fs = new function(uint256, uint256, Operand) + returns (bool, bytes memory, bytes32[] memory)[] memory fs = new function(uint256, uint256, OperandV2) internal view - returns (bool, bytes memory, uint256[] memory)[](SUB_PARSER_WORD_PARSERS_LENGTH); + returns (bool, bytes memory, bytes32[] memory)[](SUB_PARSER_WORD_PARSERS_LENGTH); fs[SUB_PARSER_WORD_PYTH_PRICE] = pythPriceSubParser; uint256[] memory pointers; @@ -74,14 +74,14 @@ abstract contract PythSubParser is BaseRainterpreterSubParserNPE2 { } // slither-disable-next-line dead-code - function pythPriceSubParser(uint256 constantsHeight, uint256 ioByte, Operand operand) + function pythPriceSubParser(uint256 constantsHeight, uint256 ioByte, OperandV2 operand) internal view - returns (bool, bytes memory, uint256[] memory) + returns (bool, bytes memory, bytes32[] memory) { // slither-disable-next-line unused-return return LibSubParse.subParserExtern( - IInterpreterExternV3(extern()), constantsHeight, ioByte, operand, OPCODE_PYTH_PRICE + IInterpreterExternV4(extern()), constantsHeight, ioByte, operand, OPCODE_PYTH_PRICE ); } } diff --git a/src/lib/op/LibOpPythPrice.sol b/src/lib/op/LibOpPythPrice.sol index 0041277..63f1339 100644 --- a/src/lib/op/LibOpPythPrice.sol +++ b/src/lib/op/LibOpPythPrice.sol @@ -2,22 +2,23 @@ // SPDX-FileCopyrightText: Copyright (c) 2020 Rain Open Source Software Ltd pragma solidity ^0.8.25; -import {Operand} from "rain.interpreter.interface/interface/deprecated/IInterpreterV2.sol"; +import {OperandV2} from "rain.interpreter.interface/interface/unstable/IInterpreterV4.sol"; import {LibIntOrAString, IntOrAString} from "rain.intorastring/lib/LibIntOrAString.sol"; import {LibPyth} from "../pyth/LibPyth.sol"; +import {Float, LibDecimalFloat} from "rain.math.float/lib/LibDecimalFloat.sol"; library LibOpPythPrice { using LibIntOrAString for IntOrAString; /// Extern integrity for the Pyth price operation. /// Always requires 2 inputs and produces 1 output. - function integrity(Operand, uint256, uint256) internal pure returns (uint256, uint256) { + function integrity(OperandV2, uint256, uint256) internal pure returns (uint256, uint256) { return (2, 1); } /// Runs the Pyth price operation. /// @param inputs the inputs to the extern. - function run(Operand, uint256[] memory inputs) internal view returns (uint256[] memory) { + function run(OperandV2, uint256[] memory inputs) internal view returns (uint256[] memory) { IntOrAString symbol; Float staleAfter; assembly ("memory-safe") { diff --git a/test/src/concrete/PythWords.pythPrice.t.sol b/test/src/concrete/PythWords.pythPrice.t.sol index 3f39d37..5707b55 100644 --- a/test/src/concrete/PythWords.pythPrice.t.sol +++ b/test/src/concrete/PythWords.pythPrice.t.sol @@ -2,11 +2,12 @@ // SPDX-FileCopyrightText: Copyright (c) 2020 Rain Open Source Software Ltd pragma solidity =0.8.25; -import {OpTest} from "rain.interpreter/../test/abstract/OpTest.sol"; +import {OpTest, StackItem} from "rain.interpreter/../test/abstract/OpTest.sol"; import {PythWords} from "src/concrete/PythWords.sol"; import {FORK_RPC_URL_ARBITRUM, FORK_BLOCK_ARBITRUM} from "test/lib/LibFork.sol"; import {Strings} from "openzeppelin-contracts/contracts/utils/Strings.sol"; import {LibPyth} from "src/lib/pyth/LibPyth.sol"; +import {LibDecimalFloat, Float} from "rain.math.float/lib/LibDecimalFloat.sol"; contract PythWordsPythPriceTest is OpTest { using Strings for address; @@ -20,8 +21,8 @@ contract PythWordsPythPriceTest is OpTest { PythWords pythWords = new PythWords(); - uint256[] memory expectedStack = new uint256[](1); - expectedStack[0] = 172.3176e18; + StackItem[] memory expectedStack = new StackItem[](1); + expectedStack[0] = StackItem.wrap(Float.unwrap(LibDecimalFloat.fromFixedDecimalLosslessPacked(172.3176e18, 18))); checkHappy( bytes( diff --git a/test/src/lib/op/LibOpPythPrice.t.sol b/test/src/lib/op/LibOpPythPrice.t.sol index 723bb3d..e9a8cb9 100644 --- a/test/src/lib/op/LibOpPythPrice.t.sol +++ b/test/src/lib/op/LibOpPythPrice.t.sol @@ -3,12 +3,12 @@ pragma solidity =0.8.25; import {Test} from "forge-std/Test.sol"; -import {LibOpPythPrice, Operand} from "src/lib/op/LibOpPythPrice.sol"; +import {LibOpPythPrice, OperandV2} from "src/lib/op/LibOpPythPrice.sol"; import {LibIntOrAString, IntOrAString} from "rain.intorastring/lib/LibIntOrAString.sol"; import {FORK_RPC_URL_ARBITRUM, FORK_BLOCK_ARBITRUM} from "test/lib/LibFork.sol"; contract LibOpPythPriceTest is Test { - function testIntegrity(Operand operand, uint256 inputs, uint256 outputs) external pure { + function testIntegrity(OperandV2 operand, uint256 inputs, uint256 outputs) external pure { (uint256 calculatedInputs, uint256 calculatedOutputs) = LibOpPythPrice.integrity(operand, inputs, outputs); assertEq(calculatedInputs, 2); assertEq(calculatedOutputs, 1); @@ -21,7 +21,7 @@ contract LibOpPythPriceTest is Test { inputs[0] = IntOrAString.unwrap(LibIntOrAString.fromString2("Equity.US.GOOG/USD")); inputs[1] = 72 hours; - uint256[] memory outputs = LibOpPythPrice.run(Operand.wrap(0), inputs); + uint256[] memory outputs = LibOpPythPrice.run(OperandV2.wrap(0), inputs); assertEq(outputs.length, 1); assertEq(outputs[0], 172.3176e18); } From f2bcbbac2d811bcca20e019924d0a919f3df2ede Mon Sep 17 00:00:00 2001 From: thedavidmeister Date: Mon, 6 Oct 2025 20:32:41 +0400 Subject: [PATCH 10/14] update i9r --- foundry.lock | 2 +- lib/rain.interpreter | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/foundry.lock b/foundry.lock index 213cba8..8a5bc96 100644 --- a/foundry.lock +++ b/foundry.lock @@ -6,6 +6,6 @@ "rev": "6248709ef4f5c20e4fb2828bc68789eb0164df99" }, "lib/rain.interpreter": { - "rev": "e31cdda0815228cc66663104685b4f3509c15daf" + "rev": "6068857887731d910f3f1850c5f27248fce3a5d5" } } \ No newline at end of file diff --git a/lib/rain.interpreter b/lib/rain.interpreter index e31cdda..6068857 160000 --- a/lib/rain.interpreter +++ b/lib/rain.interpreter @@ -1 +1 @@ -Subproject commit e31cdda0815228cc66663104685b4f3509c15daf +Subproject commit 6068857887731d910f3f1850c5f27248fce3a5d5 From b6e85d1acbac75d4976841d6753dc229d7bfe6ad Mon Sep 17 00:00:00 2001 From: thedavidmeister Date: Mon, 6 Oct 2025 21:50:05 +0400 Subject: [PATCH 11/14] float for pyth words --- foundry.toml | 2 +- src/abstract/PythExtern.sol | 12 +- src/generated/PythWords.pointers.sol | 18 +-- src/lib/op/LibOpPythPrice.sol | 10 +- src/lib/pyth/LibPyth.sol | 9 +- test/src/concrete/PythWords.pythPrice.t.sol | 6 +- test/src/lib/op/LibOpPythPrice.t.sol | 13 +- .../pyth/LibPyth.getPriceNoOlderThan.t.sol | 127 ++++++++++++++---- 8 files changed, 142 insertions(+), 55 deletions(-) diff --git a/foundry.toml b/foundry.toml index 3051d83..448d980 100644 --- a/foundry.toml +++ b/foundry.toml @@ -19,7 +19,7 @@ fs_permissions = [ evm_version = "paris" optimizer = true -optimizer_runs = 1000000 +optimizer_runs = 1000 bytecode_hash = "none" cbor_metadata = false \ No newline at end of file diff --git a/src/abstract/PythExtern.sol b/src/abstract/PythExtern.sol index 7ed9b97..cb0d7e0 100644 --- a/src/abstract/PythExtern.sol +++ b/src/abstract/PythExtern.sol @@ -2,7 +2,11 @@ // SPDX-FileCopyrightText: Copyright (c) 2020 Rain Open Source Software Ltd pragma solidity ^0.8.25; -import {BaseRainterpreterExternNPE2, OperandV2} from "rain.interpreter/abstract/BaseRainterpreterExternNPE2.sol"; +import { + BaseRainterpreterExternNPE2, + OperandV2, + StackItem +} from "rain.interpreter/abstract/BaseRainterpreterExternNPE2.sol"; import {LibOpPythPrice} from "../lib/op/LibOpPythPrice.sol"; import {LibConvert} from "rain.lib.typecast/LibConvert.sol"; import {OPCODE_FUNCTION_POINTERS, INTEGRITY_FUNCTION_POINTERS} from "../generated/PythWords.pointers.sol"; @@ -21,13 +25,13 @@ abstract contract PythExtern is BaseRainterpreterExternNPE2 { } function buildOpcodeFunctionPointers() external pure returns (bytes memory) { - function(OperandV2, uint256[] memory) + function(OperandV2, StackItem[] memory) internal view - returns (uint256[] memory)[] memory fs = new function(OperandV2, uint256[] memory) + returns (StackItem[] memory)[] memory fs = new function(OperandV2, StackItem[] memory) internal view - returns (uint256[] memory)[](OPCODE_FUNCTION_POINTERS_LENGTH); + returns (StackItem[] memory)[](OPCODE_FUNCTION_POINTERS_LENGTH); fs[OPCODE_PYTH_PRICE] = LibOpPythPrice.run; uint256[] memory pointers; diff --git a/src/generated/PythWords.pointers.sol b/src/generated/PythWords.pointers.sol index 908bf27..2d400ee 100644 --- a/src/generated/PythWords.pointers.sol +++ b/src/generated/PythWords.pointers.sol @@ -1,3 +1,7 @@ +// SPDX-License-Identifier: LicenseRef-DCL-1.0 +// SPDX-FileCopyrightText: Copyright (c) 2020 Rain Open Source Software Ltd +pragma solidity ^0.8.25; + // THIS FILE IS AUTOGENERATED BY ./script/BuildPointers.sol // This file is committed to the repository because there is a circular @@ -5,12 +9,8 @@ // needs the pointers file to exist so that it can compile, and the pointers // file needs the contract to exist so that it can be compiled. -// SPDX-License-Identifier: LicenseRef-DCL-1.0 -// SPDX-FileCopyrightText: Copyright (c) 2020 Rain Open Source Software Ltd -pragma solidity =0.8.25; - /// @dev Hash of the known bytecode. -bytes32 constant BYTECODE_HASH = bytes32(0xc2563d7f113f4586c661be35de1861e2a415f818178ecc8d568dd03a46eb9d92); +bytes32 constant BYTECODE_HASH = bytes32(0x53dbbb78e3f6cc40e9fa84c2fd4fa657649d34c2dcb253e0730315131a589d71); /// @dev The hash of the meta that describes the contract. bytes32 constant DESCRIBED_BY_META_HASH = bytes32(0xe7bb5842b2cf1d25681a9885109fbf8943495bcebb9ec049bc3790e5db57fa80); @@ -42,18 +42,18 @@ uint8 constant PARSE_META_BUILD_DEPTH = 1; /// bytecode that dials back into this contract at eval time, and mapping /// to things that happen entirely on the interpreter such as well known /// constants and references to the context grid. -bytes constant SUB_PARSER_WORD_PARSERS = hex"0782"; +bytes constant SUB_PARSER_WORD_PARSERS = hex"0700"; /// @dev Every two bytes is a function pointer for an operand handler. /// These positional indexes all map to the same indexes looked up in the parse /// meta. -bytes constant OPERAND_HANDLER_FUNCTION_POINTERS = hex"087c"; +bytes constant OPERAND_HANDLER_FUNCTION_POINTERS = hex"0ac6"; /// @dev The function pointers for the integrity check fns. -bytes constant INTEGRITY_FUNCTION_POINTERS = hex"086f"; +bytes constant INTEGRITY_FUNCTION_POINTERS = hex"0aba"; /// @dev The function pointers known to the interpreter for dynamic dispatch. /// By setting these as a constant they can be inlined into the interpreter /// and loaded at eval time for very low gas (~100) due to the compiler /// optimising it to a single `codecopy` to build the in memory bytes array. -bytes constant OPCODE_FUNCTION_POINTERS = hex"0835"; +bytes constant OPCODE_FUNCTION_POINTERS = hex"07b3"; diff --git a/src/lib/op/LibOpPythPrice.sol b/src/lib/op/LibOpPythPrice.sol index 63f1339..03ffab0 100644 --- a/src/lib/op/LibOpPythPrice.sol +++ b/src/lib/op/LibOpPythPrice.sol @@ -2,7 +2,7 @@ // SPDX-FileCopyrightText: Copyright (c) 2020 Rain Open Source Software Ltd pragma solidity ^0.8.25; -import {OperandV2} from "rain.interpreter.interface/interface/unstable/IInterpreterV4.sol"; +import {OperandV2, StackItem} from "rain.interpreter.interface/interface/unstable/IInterpreterV4.sol"; import {LibIntOrAString, IntOrAString} from "rain.intorastring/lib/LibIntOrAString.sol"; import {LibPyth} from "../pyth/LibPyth.sol"; import {Float, LibDecimalFloat} from "rain.math.float/lib/LibDecimalFloat.sol"; @@ -18,7 +18,7 @@ library LibOpPythPrice { /// Runs the Pyth price operation. /// @param inputs the inputs to the extern. - function run(OperandV2, uint256[] memory inputs) internal view returns (uint256[] memory) { + function run(OperandV2, StackItem[] memory inputs) internal view returns (StackItem[] memory) { IntOrAString symbol; Float staleAfter; assembly ("memory-safe") { @@ -26,14 +26,14 @@ library LibOpPythPrice { staleAfter := mload(add(inputs, 0x40)) } - uint256 price18 = LibPyth.getPriceNoOlderThan(symbol, LibDecimalFloat.toFixedDecimalLossless(staleAfter, 0)); + Float price = LibPyth.getPriceNoOlderThan(symbol, staleAfter); - uint256[] memory outputs; + StackItem[] memory outputs; assembly ("memory-safe") { outputs := mload(0x40) mstore(0x40, add(outputs, 0x40)) mstore(outputs, 1) - mstore(add(outputs, 0x20), price18) + mstore(add(outputs, 0x20), price) } return outputs; } diff --git a/src/lib/pyth/LibPyth.sol b/src/lib/pyth/LibPyth.sol index e2988df..67a7450 100644 --- a/src/lib/pyth/LibPyth.sol +++ b/src/lib/pyth/LibPyth.sol @@ -4,7 +4,7 @@ pragma solidity ^0.8.25; import {IPyth} from "pyth-sdk/IPyth.sol"; import {PythStructs} from "pyth-sdk/PythStructs.sol"; -import {LibDecimalFloat} from "rain.math.float/lib/LibDecimalFloat.sol"; +import {LibDecimalFloat, Float} from "rain.math.float/lib/LibDecimalFloat.sol"; import {IntOrAString} from "rain.intorastring/lib/LibIntOrAString.sol"; error UnsupportedChainId(); @@ -129,12 +129,13 @@ library LibPyth { } } - function getPriceNoOlderThan(IntOrAString feedSymbol, uint256 staleAfter) internal view returns (uint256) { + function getPriceNoOlderThan(IntOrAString feedSymbol, Float staleAfter) internal view returns (Float) { + uint256 staleAfterUint = LibDecimalFloat.toFixedDecimalLossless(staleAfter, 0); bytes32 feedId = getPriceFeedId(feedSymbol); IPyth priceFeedContract = getPriceFeedContract(block.chainid); - PythStructs.Price memory priceData = priceFeedContract.getPriceNoOlderThan(feedId, staleAfter); + PythStructs.Price memory priceData = priceFeedContract.getPriceNoOlderThan(feedId, staleAfterUint); - return LibDecimalFloat.toFixedDecimalLossless(priceData.price, priceData.expo, 18); + return LibDecimalFloat.packLossless(priceData.price, priceData.expo); } } diff --git a/test/src/concrete/PythWords.pythPrice.t.sol b/test/src/concrete/PythWords.pythPrice.t.sol index 5707b55..39c3989 100644 --- a/test/src/concrete/PythWords.pythPrice.t.sol +++ b/test/src/concrete/PythWords.pythPrice.t.sol @@ -22,18 +22,18 @@ contract PythWordsPythPriceTest is OpTest { PythWords pythWords = new PythWords(); StackItem[] memory expectedStack = new StackItem[](1); - expectedStack[0] = StackItem.wrap(Float.unwrap(LibDecimalFloat.fromFixedDecimalLosslessPacked(172.3176e18, 18))); + expectedStack[0] = StackItem.wrap(Float.unwrap(LibDecimalFloat.packLossless(172.3176e5, -5))); checkHappy( bytes( string.concat( "using-words-from ", address(pythWords).toHexString(), - " _: pyth-price(\"Equity.US.GOOG/USD\" 10800);" + " _: pyth-price(\"Equity.US.GOOG/USD\" 1080000);" ) ), expectedStack, - "pyth-price(\"Equity.US.GOOG/USD\" 10800)" + "pyth-price(\"Equity.US.GOOG/USD\" 1080000)" ); } } diff --git a/test/src/lib/op/LibOpPythPrice.t.sol b/test/src/lib/op/LibOpPythPrice.t.sol index e9a8cb9..3490d7b 100644 --- a/test/src/lib/op/LibOpPythPrice.t.sol +++ b/test/src/lib/op/LibOpPythPrice.t.sol @@ -3,9 +3,10 @@ pragma solidity =0.8.25; import {Test} from "forge-std/Test.sol"; -import {LibOpPythPrice, OperandV2} from "src/lib/op/LibOpPythPrice.sol"; +import {LibOpPythPrice, OperandV2, StackItem} from "src/lib/op/LibOpPythPrice.sol"; import {LibIntOrAString, IntOrAString} from "rain.intorastring/lib/LibIntOrAString.sol"; import {FORK_RPC_URL_ARBITRUM, FORK_BLOCK_ARBITRUM} from "test/lib/LibFork.sol"; +import {Float, LibDecimalFloat} from "rain.math.float/lib/LibDecimalFloat.sol"; contract LibOpPythPriceTest is Test { function testIntegrity(OperandV2 operand, uint256 inputs, uint256 outputs) external pure { @@ -17,12 +18,12 @@ contract LibOpPythPriceTest is Test { function testRunForkCurrentPriceHappy() external { vm.createSelectFork(FORK_RPC_URL_ARBITRUM, FORK_BLOCK_ARBITRUM); - uint256[] memory inputs = new uint256[](2); - inputs[0] = IntOrAString.unwrap(LibIntOrAString.fromString2("Equity.US.GOOG/USD")); - inputs[1] = 72 hours; + StackItem[] memory inputs = new StackItem[](2); + inputs[0] = StackItem.wrap(bytes32(IntOrAString.unwrap(LibIntOrAString.fromString2("Equity.US.GOOG/USD")))); + inputs[1] = StackItem.wrap(Float.unwrap(LibDecimalFloat.packLossless(72 hours, 0))); - uint256[] memory outputs = LibOpPythPrice.run(OperandV2.wrap(0), inputs); + StackItem[] memory outputs = LibOpPythPrice.run(OperandV2.wrap(0), inputs); assertEq(outputs.length, 1); - assertEq(outputs[0], 172.3176e18); + assertEq(StackItem.unwrap(outputs[0]), Float.unwrap(LibDecimalFloat.packLossless(172.3176e5, -5))); } } diff --git a/test/src/lib/pyth/LibPyth.getPriceNoOlderThan.t.sol b/test/src/lib/pyth/LibPyth.getPriceNoOlderThan.t.sol index 34c5535..d7dd9c2 100644 --- a/test/src/lib/pyth/LibPyth.getPriceNoOlderThan.t.sol +++ b/test/src/lib/pyth/LibPyth.getPriceNoOlderThan.t.sol @@ -2,46 +2,127 @@ // SPDX-FileCopyrightText: Copyright (c) 2020 Rain Open Source Software Ltd pragma solidity ^0.8.25; -import {Test} from "forge-std/Test.sol"; +import {Test, console2} from "forge-std/Test.sol"; import {LibPyth} from "src/lib/pyth/LibPyth.sol"; import {FORK_RPC_URL_ARBITRUM, FORK_RPC_URL_BASE, FORK_BLOCK_ARBITRUM, FORK_BLOCK_BASE} from "test/lib/LibFork.sol"; import {IntOrAString, LibIntOrAString} from "rain.intorastring/lib/LibIntOrAString.sol"; +import {Float, LibDecimalFloat} from "rain.math.float/lib/LibDecimalFloat.sol"; contract LibPythGetPriceNoOlderThanTest is Test { using LibIntOrAString for string; - function getPriceNoOlderThanExternal(IntOrAString symbol, uint256 maxAge) external view returns (uint256) { + function getPriceNoOlderThanExternal(IntOrAString symbol, Float maxAge) external view returns (Float) { return LibPyth.getPriceNoOlderThan(symbol, maxAge); } - function checkPriceNoOlderThan(IntOrAString symbol, uint256 maxAge, uint256 expectedPrice) internal view { - assertEq(LibPyth.getPriceNoOlderThan(symbol, maxAge), expectedPrice); + function checkPriceNoOlderThan(IntOrAString symbol, Float maxAge, Float expectedPrice) internal view { + Float actualPrice = this.getPriceNoOlderThanExternal(symbol, maxAge); + (int256 actualSignedCoefficient, int256 actualExponent) = LibDecimalFloat.unpack(actualPrice); + console2.logInt(actualSignedCoefficient); + console2.logInt(actualExponent); + assertEq(Float.unwrap(LibPyth.getPriceNoOlderThan(symbol, maxAge)), Float.unwrap(expectedPrice)); } function testPriceNoOlderThanArbitrum() external { vm.createSelectFork(FORK_RPC_URL_ARBITRUM, FORK_BLOCK_ARBITRUM); - checkPriceNoOlderThan(LibIntOrAString.fromString2("Equity.US.GOOG/USD"), 72 hours, 172.3176e18); - checkPriceNoOlderThan(LibIntOrAString.fromString2("Equity.US.AMZN/USD"), 500 hours, 205.06198e18); - checkPriceNoOlderThan(LibIntOrAString.fromString2("Equity.US.AAPL/USD"), 72 hours, 202.86002e18); - checkPriceNoOlderThan(LibIntOrAString.fromString2("Equity.US.MSFT/USD"), 72 hours, 469.8035e18); - checkPriceNoOlderThan(LibIntOrAString.fromString2("Equity.US.TSLA/USD"), 300 hours, 360.02978e18); - checkPriceNoOlderThan(LibIntOrAString.fromString2("Equity.US.NVDA/USD"), 1000 hours, 104.5623e18); - checkPriceNoOlderThan(LibIntOrAString.fromString2("Equity.US.META/USD"), 8000 hours, 448.73e18); - checkPriceNoOlderThan(LibIntOrAString.fromString2("Equity.US.GME/USD"), 8000 hours, 29.3177e18); + checkPriceNoOlderThan( + LibIntOrAString.fromString2("Equity.US.GOOG/USD"), + LibDecimalFloat.packLossless(72 hours, 0), + LibDecimalFloat.packLossless(172.3176e5, -5) + ); + checkPriceNoOlderThan( + LibIntOrAString.fromString2("Equity.US.AMZN/USD"), + LibDecimalFloat.packLossless(500 hours, 0), + LibDecimalFloat.packLossless(205.06198e5, -5) + ); + checkPriceNoOlderThan( + LibIntOrAString.fromString2("Equity.US.AAPL/USD"), + LibDecimalFloat.packLossless(72 hours, 0), + LibDecimalFloat.packLossless(202.86002e5, -5) + ); + checkPriceNoOlderThan( + LibIntOrAString.fromString2("Equity.US.MSFT/USD"), + LibDecimalFloat.packLossless(72 hours, 0), + LibDecimalFloat.packLossless(469.8035e5, -5) + ); + checkPriceNoOlderThan( + LibIntOrAString.fromString2("Equity.US.TSLA/USD"), + LibDecimalFloat.packLossless(300 hours, 0), + LibDecimalFloat.packLossless(360.02978e5, -5) + ); + checkPriceNoOlderThan( + LibIntOrAString.fromString2("Equity.US.NVDA/USD"), + LibDecimalFloat.packLossless(1000 hours, 0), + LibDecimalFloat.packLossless(104.5623e5, -5) + ); + checkPriceNoOlderThan( + LibIntOrAString.fromString2("Equity.US.META/USD"), + LibDecimalFloat.packLossless(8000 hours, 0), + LibDecimalFloat.packLossless(448.73e5, -5) + ); + checkPriceNoOlderThan( + LibIntOrAString.fromString2("Equity.US.GME/USD"), + LibDecimalFloat.packLossless(8000 hours, 0), + LibDecimalFloat.packLossless(29.3177e5, -5) + ); } function testPriceNoOlderThanBase() external { vm.createSelectFork(FORK_RPC_URL_BASE, FORK_BLOCK_BASE); - checkPriceNoOlderThan(LibIntOrAString.fromString2("Equity.US.GOOG/USD"), 24 hours, 246.29352e18); - checkPriceNoOlderThan(LibIntOrAString.fromString2("Equity.US.AMZN/USD"), 24 hours, 222.43512e18); - checkPriceNoOlderThan(LibIntOrAString.fromString2("Equity.US.AAPL/USD"), 24 hours, 257.33026e18); - checkPriceNoOlderThan(LibIntOrAString.fromString2("Equity.US.MSFT/USD"), 24 hours, 515.64431e18); - checkPriceNoOlderThan(LibIntOrAString.fromString2("Equity.US.TSLA/USD"), 24 hours, 436.03414e18); - checkPriceNoOlderThan(LibIntOrAString.fromString2("Equity.US.NVDA/USD"), 24 hours, 188.92e18); - checkPriceNoOlderThan(LibIntOrAString.fromString2("Equity.US.META/USD"), 24 hours, 727.0145e18); - checkPriceNoOlderThan(LibIntOrAString.fromString2("Equity.US.GME/USD"), 24 hours, 27.24202e18); - checkPriceNoOlderThan(LibIntOrAString.fromString2("Equity.US.MSTR/USD"), 24 hours, 352.34642e18); - checkPriceNoOlderThan(LibIntOrAString.fromString2("Equity.US.BRK-B/USD"), 24 hours, 496.12003e18); - checkPriceNoOlderThan(LibIntOrAString.fromString2("Equity.US.SPLG/USD"), 24 hours, 78.71367e18); + checkPriceNoOlderThan( + LibIntOrAString.fromString2("Equity.US.GOOG/USD"), + LibDecimalFloat.packLossless(24 hours, 0), + LibDecimalFloat.packLossless(246.29352e5, -5) + ); + checkPriceNoOlderThan( + LibIntOrAString.fromString2("Equity.US.AMZN/USD"), + LibDecimalFloat.packLossless(24 hours, 0), + LibDecimalFloat.packLossless(222.43512e5, -5) + ); + checkPriceNoOlderThan( + LibIntOrAString.fromString2("Equity.US.AAPL/USD"), + LibDecimalFloat.packLossless(24 hours, 0), + LibDecimalFloat.packLossless(257.33026e5, -5) + ); + checkPriceNoOlderThan( + LibIntOrAString.fromString2("Equity.US.MSFT/USD"), + LibDecimalFloat.packLossless(24 hours, 0), + LibDecimalFloat.packLossless(515.64431e5, -5) + ); + checkPriceNoOlderThan( + LibIntOrAString.fromString2("Equity.US.TSLA/USD"), + LibDecimalFloat.packLossless(24 hours, 0), + LibDecimalFloat.packLossless(436.03414e5, -5) + ); + checkPriceNoOlderThan( + LibIntOrAString.fromString2("Equity.US.NVDA/USD"), + LibDecimalFloat.packLossless(24 hours, 0), + LibDecimalFloat.packLossless(188.92e5, -5) + ); + checkPriceNoOlderThan( + LibIntOrAString.fromString2("Equity.US.META/USD"), + LibDecimalFloat.packLossless(24 hours, 0), + LibDecimalFloat.packLossless(727.0145e5, -5) + ); + checkPriceNoOlderThan( + LibIntOrAString.fromString2("Equity.US.GME/USD"), + LibDecimalFloat.packLossless(24 hours, 0), + LibDecimalFloat.packLossless(27.24202e5, -5) + ); + checkPriceNoOlderThan( + LibIntOrAString.fromString2("Equity.US.MSTR/USD"), + LibDecimalFloat.packLossless(24 hours, 0), + LibDecimalFloat.packLossless(352.34642e5, -5) + ); + checkPriceNoOlderThan( + LibIntOrAString.fromString2("Equity.US.BRK-B/USD"), + LibDecimalFloat.packLossless(24 hours, 0), + LibDecimalFloat.packLossless(496.12003e5, -5) + ); + checkPriceNoOlderThan( + LibIntOrAString.fromString2("Equity.US.SPLG/USD"), + LibDecimalFloat.packLossless(24 hours, 0), + LibDecimalFloat.packLossless(78.71367e5, -5) + ); } } From 05f9194410280a9430d3d899fbd15e379a88583c Mon Sep 17 00:00:00 2001 From: thedavidmeister Date: Tue, 7 Oct 2025 19:05:40 +0400 Subject: [PATCH 12/14] fix legal --- REUSE.toml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/REUSE.toml b/REUSE.toml index b0b7a3b..7ac3e11 100644 --- a/REUSE.toml +++ b/REUSE.toml @@ -12,7 +12,8 @@ path = [ "flake.nix", "foundry.toml", "slither.config.json", - "REUSE.toml" + "REUSE.toml", + "foundry.lock", ] SPDX-FileCopyrightText = "Copyright (c) 2020 Rain Open Source Software Ltd" SPDX-License-Identifier = "LicenseRef-DCL-1.0" From 9cef775e4b89f3e4e5ed98d07693b96bfe35f955 Mon Sep 17 00:00:00 2001 From: thedavidmeister Date: Tue, 7 Oct 2025 20:34:03 +0400 Subject: [PATCH 13/14] fix static --- src/generated/PythWords.pointers.sol | 6 +- src/lib/op/LibOpPythPrice.sol | 13 ++-- src/lib/pyth/LibPyth.sol | 7 +- test/src/concrete/PythWords.pythPrice.t.sol | 9 ++- test/src/lib/op/LibOpPythPrice.t.sol | 5 +- .../pyth/LibPyth.getPriceNoOlderThan.t.sol | 72 +++++++++++++------ 6 files changed, 73 insertions(+), 39 deletions(-) diff --git a/src/generated/PythWords.pointers.sol b/src/generated/PythWords.pointers.sol index 2d400ee..e26dc8d 100644 --- a/src/generated/PythWords.pointers.sol +++ b/src/generated/PythWords.pointers.sol @@ -10,7 +10,7 @@ pragma solidity ^0.8.25; // file needs the contract to exist so that it can be compiled. /// @dev Hash of the known bytecode. -bytes32 constant BYTECODE_HASH = bytes32(0x53dbbb78e3f6cc40e9fa84c2fd4fa657649d34c2dcb253e0730315131a589d71); +bytes32 constant BYTECODE_HASH = bytes32(0x61b6a9bc93d4d671ec1edf90e01875a8f2ca1b9b9963cfd7f2afc0b80b677edc); /// @dev The hash of the meta that describes the contract. bytes32 constant DESCRIBED_BY_META_HASH = bytes32(0xe7bb5842b2cf1d25681a9885109fbf8943495bcebb9ec049bc3790e5db57fa80); @@ -47,10 +47,10 @@ bytes constant SUB_PARSER_WORD_PARSERS = hex"0700"; /// @dev Every two bytes is a function pointer for an operand handler. /// These positional indexes all map to the same indexes looked up in the parse /// meta. -bytes constant OPERAND_HANDLER_FUNCTION_POINTERS = hex"0ac6"; +bytes constant OPERAND_HANDLER_FUNCTION_POINTERS = hex"0aca"; /// @dev The function pointers for the integrity check fns. -bytes constant INTEGRITY_FUNCTION_POINTERS = hex"0aba"; +bytes constant INTEGRITY_FUNCTION_POINTERS = hex"0abf"; /// @dev The function pointers known to the interpreter for dynamic dispatch. /// By setting these as a constant they can be inlined into the interpreter diff --git a/src/lib/op/LibOpPythPrice.sol b/src/lib/op/LibOpPythPrice.sol index 03ffab0..87e3c01 100644 --- a/src/lib/op/LibOpPythPrice.sol +++ b/src/lib/op/LibOpPythPrice.sol @@ -5,15 +5,15 @@ pragma solidity ^0.8.25; import {OperandV2, StackItem} from "rain.interpreter.interface/interface/unstable/IInterpreterV4.sol"; import {LibIntOrAString, IntOrAString} from "rain.intorastring/lib/LibIntOrAString.sol"; import {LibPyth} from "../pyth/LibPyth.sol"; -import {Float, LibDecimalFloat} from "rain.math.float/lib/LibDecimalFloat.sol"; +import {Float} from "rain.math.float/lib/LibDecimalFloat.sol"; library LibOpPythPrice { using LibIntOrAString for IntOrAString; /// Extern integrity for the Pyth price operation. - /// Always requires 2 inputs and produces 1 output. + /// Always requires 2 inputs and produces 2 outputs. function integrity(OperandV2, uint256, uint256) internal pure returns (uint256, uint256) { - return (2, 1); + return (2, 2); } /// Runs the Pyth price operation. @@ -26,14 +26,15 @@ library LibOpPythPrice { staleAfter := mload(add(inputs, 0x40)) } - Float price = LibPyth.getPriceNoOlderThan(symbol, staleAfter); + (Float price, Float conf) = LibPyth.getPriceNoOlderThan(symbol, staleAfter); StackItem[] memory outputs; assembly ("memory-safe") { outputs := mload(0x40) - mstore(0x40, add(outputs, 0x40)) - mstore(outputs, 1) + mstore(0x40, add(outputs, 0x60)) + mstore(outputs, 2) mstore(add(outputs, 0x20), price) + mstore(add(outputs, 0x40), conf) } return outputs; } diff --git a/src/lib/pyth/LibPyth.sol b/src/lib/pyth/LibPyth.sol index 67a7450..312dc68 100644 --- a/src/lib/pyth/LibPyth.sol +++ b/src/lib/pyth/LibPyth.sol @@ -129,13 +129,16 @@ library LibPyth { } } - function getPriceNoOlderThan(IntOrAString feedSymbol, Float staleAfter) internal view returns (Float) { + function getPriceNoOlderThan(IntOrAString feedSymbol, Float staleAfter) internal view returns (Float, Float) { uint256 staleAfterUint = LibDecimalFloat.toFixedDecimalLossless(staleAfter, 0); bytes32 feedId = getPriceFeedId(feedSymbol); IPyth priceFeedContract = getPriceFeedContract(block.chainid); PythStructs.Price memory priceData = priceFeedContract.getPriceNoOlderThan(feedId, staleAfterUint); - return LibDecimalFloat.packLossless(priceData.price, priceData.expo); + return ( + LibDecimalFloat.packLossless(priceData.price, priceData.expo), + LibDecimalFloat.packLossless(int256(uint256(priceData.conf)), priceData.expo) + ); } } diff --git a/test/src/concrete/PythWords.pythPrice.t.sol b/test/src/concrete/PythWords.pythPrice.t.sol index 39c3989..4394bdf 100644 --- a/test/src/concrete/PythWords.pythPrice.t.sol +++ b/test/src/concrete/PythWords.pythPrice.t.sol @@ -21,15 +21,18 @@ contract PythWordsPythPriceTest is OpTest { PythWords pythWords = new PythWords(); - StackItem[] memory expectedStack = new StackItem[](1); - expectedStack[0] = StackItem.wrap(Float.unwrap(LibDecimalFloat.packLossless(172.3176e5, -5))); + StackItem[] memory expectedStack = new StackItem[](2); + expectedStack[0] = StackItem.wrap(Float.unwrap(LibDecimalFloat.packLossless(2.00302e5, -5))); + expectedStack[1] = StackItem.wrap(Float.unwrap(LibDecimalFloat.packLossless(172.3176e5, -5))); checkHappy( bytes( string.concat( "using-words-from ", address(pythWords).toHexString(), - " _: pyth-price(\"Equity.US.GOOG/USD\" 1080000);" + "price confidence: pyth-price(\"Equity.US.GOOG/USD\" 1080000),", + ":ensure(equal-to(price 172.3176) \"bad price\"),", + ":ensure(equal-to(confidence 2.00302) \"bad confidence\");" ) ), expectedStack, diff --git a/test/src/lib/op/LibOpPythPrice.t.sol b/test/src/lib/op/LibOpPythPrice.t.sol index 3490d7b..d981f77 100644 --- a/test/src/lib/op/LibOpPythPrice.t.sol +++ b/test/src/lib/op/LibOpPythPrice.t.sol @@ -12,7 +12,7 @@ contract LibOpPythPriceTest is Test { function testIntegrity(OperandV2 operand, uint256 inputs, uint256 outputs) external pure { (uint256 calculatedInputs, uint256 calculatedOutputs) = LibOpPythPrice.integrity(operand, inputs, outputs); assertEq(calculatedInputs, 2); - assertEq(calculatedOutputs, 1); + assertEq(calculatedOutputs, 2); } function testRunForkCurrentPriceHappy() external { @@ -23,7 +23,8 @@ contract LibOpPythPriceTest is Test { inputs[1] = StackItem.wrap(Float.unwrap(LibDecimalFloat.packLossless(72 hours, 0))); StackItem[] memory outputs = LibOpPythPrice.run(OperandV2.wrap(0), inputs); - assertEq(outputs.length, 1); + assertEq(outputs.length, 2); assertEq(StackItem.unwrap(outputs[0]), Float.unwrap(LibDecimalFloat.packLossless(172.3176e5, -5))); + assertEq(StackItem.unwrap(outputs[1]), Float.unwrap(LibDecimalFloat.packLossless(2.00302e5, -5))); } } diff --git a/test/src/lib/pyth/LibPyth.getPriceNoOlderThan.t.sol b/test/src/lib/pyth/LibPyth.getPriceNoOlderThan.t.sol index d7dd9c2..238bb28 100644 --- a/test/src/lib/pyth/LibPyth.getPriceNoOlderThan.t.sol +++ b/test/src/lib/pyth/LibPyth.getPriceNoOlderThan.t.sol @@ -11,16 +11,23 @@ import {Float, LibDecimalFloat} from "rain.math.float/lib/LibDecimalFloat.sol"; contract LibPythGetPriceNoOlderThanTest is Test { using LibIntOrAString for string; - function getPriceNoOlderThanExternal(IntOrAString symbol, Float maxAge) external view returns (Float) { + function getPriceNoOlderThanExternal(IntOrAString symbol, Float maxAge) external view returns (Float, Float) { return LibPyth.getPriceNoOlderThan(symbol, maxAge); } - function checkPriceNoOlderThan(IntOrAString symbol, Float maxAge, Float expectedPrice) internal view { - Float actualPrice = this.getPriceNoOlderThanExternal(symbol, maxAge); + function checkPriceNoOlderThan(IntOrAString symbol, Float maxAge, Float expectedPrice, Float expectedConf) + internal + view + { + (Float actualPrice, Float actualConf) = LibPyth.getPriceNoOlderThan(symbol, maxAge); (int256 actualSignedCoefficient, int256 actualExponent) = LibDecimalFloat.unpack(actualPrice); console2.logInt(actualSignedCoefficient); console2.logInt(actualExponent); - assertEq(Float.unwrap(LibPyth.getPriceNoOlderThan(symbol, maxAge)), Float.unwrap(expectedPrice)); + (actualSignedCoefficient, actualExponent) = LibDecimalFloat.unpack(actualConf); + console2.logInt(actualSignedCoefficient); + console2.logInt(actualExponent); + assertEq(Float.unwrap(actualPrice), Float.unwrap(expectedPrice)); + assertEq(Float.unwrap(actualConf), Float.unwrap(expectedConf)); } function testPriceNoOlderThanArbitrum() external { @@ -28,42 +35,50 @@ contract LibPythGetPriceNoOlderThanTest is Test { checkPriceNoOlderThan( LibIntOrAString.fromString2("Equity.US.GOOG/USD"), LibDecimalFloat.packLossless(72 hours, 0), - LibDecimalFloat.packLossless(172.3176e5, -5) + LibDecimalFloat.packLossless(172.3176e5, -5), + LibDecimalFloat.packLossless(2.00302e5, -5) ); checkPriceNoOlderThan( LibIntOrAString.fromString2("Equity.US.AMZN/USD"), LibDecimalFloat.packLossless(500 hours, 0), - LibDecimalFloat.packLossless(205.06198e5, -5) + LibDecimalFloat.packLossless(205.06198e5, -5), + LibDecimalFloat.packLossless(0.27188e5, -5) ); checkPriceNoOlderThan( LibIntOrAString.fromString2("Equity.US.AAPL/USD"), LibDecimalFloat.packLossless(72 hours, 0), - LibDecimalFloat.packLossless(202.86002e5, -5) + LibDecimalFloat.packLossless(202.86002e5, -5), + LibDecimalFloat.packLossless(1.91401e5, -5) ); checkPriceNoOlderThan( LibIntOrAString.fromString2("Equity.US.MSFT/USD"), LibDecimalFloat.packLossless(72 hours, 0), - LibDecimalFloat.packLossless(469.8035e5, -5) + LibDecimalFloat.packLossless(469.8035e5, -5), + LibDecimalFloat.packLossless(2.02763e5, -5) ); checkPriceNoOlderThan( LibIntOrAString.fromString2("Equity.US.TSLA/USD"), LibDecimalFloat.packLossless(300 hours, 0), - LibDecimalFloat.packLossless(360.02978e5, -5) + LibDecimalFloat.packLossless(360.02978e5, -5), + LibDecimalFloat.packLossless(0.35259e5, -5) ); checkPriceNoOlderThan( LibIntOrAString.fromString2("Equity.US.NVDA/USD"), LibDecimalFloat.packLossless(1000 hours, 0), - LibDecimalFloat.packLossless(104.5623e5, -5) + LibDecimalFloat.packLossless(104.5623e5, -5), + LibDecimalFloat.packLossless(0.1513e5, -5) ); checkPriceNoOlderThan( LibIntOrAString.fromString2("Equity.US.META/USD"), LibDecimalFloat.packLossless(8000 hours, 0), - LibDecimalFloat.packLossless(448.73e5, -5) + LibDecimalFloat.packLossless(448.73e5, -5), + LibDecimalFloat.packLossless(0.72984e5, -5) ); checkPriceNoOlderThan( LibIntOrAString.fromString2("Equity.US.GME/USD"), LibDecimalFloat.packLossless(8000 hours, 0), - LibDecimalFloat.packLossless(29.3177e5, -5) + LibDecimalFloat.packLossless(29.3177e5, -5), + LibDecimalFloat.packLossless(0.26199e5, -5) ); } @@ -72,57 +87,68 @@ contract LibPythGetPriceNoOlderThanTest is Test { checkPriceNoOlderThan( LibIntOrAString.fromString2("Equity.US.GOOG/USD"), LibDecimalFloat.packLossless(24 hours, 0), - LibDecimalFloat.packLossless(246.29352e5, -5) + LibDecimalFloat.packLossless(246.29352e5, -5), + LibDecimalFloat.packLossless(0.40327e5, -5) ); checkPriceNoOlderThan( LibIntOrAString.fromString2("Equity.US.AMZN/USD"), LibDecimalFloat.packLossless(24 hours, 0), - LibDecimalFloat.packLossless(222.43512e5, -5) + LibDecimalFloat.packLossless(222.43512e5, -5), + LibDecimalFloat.packLossless(0.10153e5, -5) ); checkPriceNoOlderThan( LibIntOrAString.fromString2("Equity.US.AAPL/USD"), LibDecimalFloat.packLossless(24 hours, 0), - LibDecimalFloat.packLossless(257.33026e5, -5) + LibDecimalFloat.packLossless(257.33026e5, -5), + LibDecimalFloat.packLossless(0.13478e5, -5) ); checkPriceNoOlderThan( LibIntOrAString.fromString2("Equity.US.MSFT/USD"), LibDecimalFloat.packLossless(24 hours, 0), - LibDecimalFloat.packLossless(515.64431e5, -5) + LibDecimalFloat.packLossless(515.64431e5, -5), + LibDecimalFloat.packLossless(0.21746e5, -5) ); checkPriceNoOlderThan( LibIntOrAString.fromString2("Equity.US.TSLA/USD"), LibDecimalFloat.packLossless(24 hours, 0), - LibDecimalFloat.packLossless(436.03414e5, -5) + LibDecimalFloat.packLossless(436.03414e5, -5), + LibDecimalFloat.packLossless(0.28529e5, -5) ); checkPriceNoOlderThan( LibIntOrAString.fromString2("Equity.US.NVDA/USD"), LibDecimalFloat.packLossless(24 hours, 0), - LibDecimalFloat.packLossless(188.92e5, -5) + LibDecimalFloat.packLossless(188.92e5, -5), + LibDecimalFloat.packLossless(0.13648e5, -5) ); checkPriceNoOlderThan( LibIntOrAString.fromString2("Equity.US.META/USD"), LibDecimalFloat.packLossless(24 hours, 0), - LibDecimalFloat.packLossless(727.0145e5, -5) + LibDecimalFloat.packLossless(727.0145e5, -5), + LibDecimalFloat.packLossless(0.40439e5, -5) ); checkPriceNoOlderThan( LibIntOrAString.fromString2("Equity.US.GME/USD"), LibDecimalFloat.packLossless(24 hours, 0), - LibDecimalFloat.packLossless(27.24202e5, -5) + LibDecimalFloat.packLossless(27.24202e5, -5), + LibDecimalFloat.packLossless(0.05212e5, -5) ); checkPriceNoOlderThan( LibIntOrAString.fromString2("Equity.US.MSTR/USD"), LibDecimalFloat.packLossless(24 hours, 0), - LibDecimalFloat.packLossless(352.34642e5, -5) + LibDecimalFloat.packLossless(352.34642e5, -5), + LibDecimalFloat.packLossless(0.32048e5, -5) ); checkPriceNoOlderThan( LibIntOrAString.fromString2("Equity.US.BRK-B/USD"), LibDecimalFloat.packLossless(24 hours, 0), - LibDecimalFloat.packLossless(496.12003e5, -5) + LibDecimalFloat.packLossless(496.12003e5, -5), + LibDecimalFloat.packLossless(0.1009e5, -5) ); checkPriceNoOlderThan( LibIntOrAString.fromString2("Equity.US.SPLG/USD"), LibDecimalFloat.packLossless(24 hours, 0), - LibDecimalFloat.packLossless(78.71367e5, -5) + LibDecimalFloat.packLossless(78.71367e5, -5), + LibDecimalFloat.packLossless(0.03281e5, -5) ); } } From 6afd2399deceebd987e2277a35a18e5086474784 Mon Sep 17 00:00:00 2001 From: thedavidmeister Date: Tue, 7 Oct 2025 21:01:35 +0400 Subject: [PATCH 14/14] slither false positive --- src/lib/pyth/LibPyth.sol | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/lib/pyth/LibPyth.sol b/src/lib/pyth/LibPyth.sol index 312dc68..45d4558 100644 --- a/src/lib/pyth/LibPyth.sol +++ b/src/lib/pyth/LibPyth.sol @@ -134,6 +134,8 @@ library LibPyth { bytes32 feedId = getPriceFeedId(feedSymbol); IPyth priceFeedContract = getPriceFeedContract(block.chainid); + // Slither false positive because conf is returned for caller to handle. + // slither-disable-next-line pyth-unchecked-confidence PythStructs.Price memory priceData = priceFeedContract.getPriceNoOlderThan(feedId, staleAfterUint); return (