diff --git a/.gitmodules b/.gitmodules index 8cf4bc0..05c2101 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,12 +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.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 +[submodule "lib/pyth-crosschain"] + path = lib/pyth-crosschain + url = https://github.com/pyth-network/pyth-crosschain 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" 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": { diff --git a/foundry.lock b/foundry.lock new file mode 100644 index 0000000..8a5bc96 --- /dev/null +++ b/foundry.lock @@ -0,0 +1,11 @@ +{ + "lib/forge-std": { + "rev": "17a9b2398a7d629931dc66a168a098d051d53dc6" + }, + "lib/pyth-crosschain": { + "rev": "6248709ef4f5c20e4fb2828bc68789eb0164df99" + }, + "lib/rain.interpreter": { + "rev": "6068857887731d910f3f1850c5f27248fce3a5d5" + } +} \ No newline at end of file diff --git a/foundry.toml b/foundry.toml index 94424c5..448d980 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/", @@ -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/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 diff --git a/lib/pyth-crosschain b/lib/pyth-crosschain index 5e356f4..6248709 160000 --- a/lib/pyth-crosschain +++ b/lib/pyth-crosschain @@ -1 +1 @@ -Subproject commit 5e356f425d7e05d2d3bfd50b8780500bd560ed19 +Subproject commit 6248709ef4f5c20e4fb2828bc68789eb0164df99 diff --git a/lib/rain.interpreter b/lib/rain.interpreter index 97927f4..6068857 160000 --- a/lib/rain.interpreter +++ b/lib/rain.interpreter @@ -1 +1 @@ -Subproject commit 97927f4f613cdd4c165f493e5aeb216a6bd731e1 +Subproject commit 6068857887731d910f3f1850c5f27248fce3a5d5 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 diff --git a/src/abstract/PythExtern.sol b/src/abstract/PythExtern.sol index 91a69a1..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, Operand} 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(Operand, uint256[] memory) + function(OperandV2, StackItem[] memory) internal view - returns (uint256[] memory)[] memory fs = new function(Operand, 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; @@ -38,10 +42,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/generated/PythWords.pointers.sol b/src/generated/PythWords.pointers.sol index 908bf27..e26dc8d 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(0x61b6a9bc93d4d671ec1edf90e01875a8f2ca1b9b9963cfd7f2afc0b80b677edc); /// @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"0aca"; /// @dev The function pointers for the integrity check fns. -bytes constant INTEGRITY_FUNCTION_POINTERS = hex"086f"; +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 /// 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 9184433..87e3c01 100644 --- a/src/lib/op/LibOpPythPrice.sol +++ b/src/lib/op/LibOpPythPrice.sol @@ -2,37 +2,39 @@ // 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, 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} 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) { - return (2, 1); + /// Always requires 2 inputs and produces 2 outputs. + function integrity(OperandV2, uint256, uint256) internal pure returns (uint256, uint256) { + return (2, 2); } /// 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, StackItem[] memory inputs) internal view returns (StackItem[] 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); + (Float price, Float conf) = 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(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 e2988df..45d4558 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,18 @@ library LibPyth { } } - function getPriceNoOlderThan(IntOrAString feedSymbol, uint256 staleAfter) internal view returns (uint256) { + 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, staleAfter); + // 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 LibDecimalFloat.toFixedDecimalLossless(priceData.price, priceData.expo, 18); + 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 3f39d37..4394bdf 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,19 +21,22 @@ contract PythWordsPythPriceTest is OpTest { PythWords pythWords = new PythWords(); - uint256[] memory expectedStack = new uint256[](1); - expectedStack[0] = 172.3176e18; + 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\" 10800);" + "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, - "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 723bb3d..d981f77 100644 --- a/test/src/lib/op/LibOpPythPrice.t.sol +++ b/test/src/lib/op/LibOpPythPrice.t.sol @@ -3,26 +3,28 @@ pragma solidity =0.8.25; import {Test} from "forge-std/Test.sol"; -import {LibOpPythPrice, Operand} 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(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); + assertEq(calculatedOutputs, 2); } 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(Operand.wrap(0), inputs); - assertEq(outputs.length, 1); - assertEq(outputs[0], 172.3176e18); + StackItem[] memory outputs = LibOpPythPrice.run(OperandV2.wrap(0), inputs); + 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 34c5535..238bb28 100644 --- a/test/src/lib/pyth/LibPyth.getPriceNoOlderThan.t.sol +++ b/test/src/lib/pyth/LibPyth.getPriceNoOlderThan.t.sol @@ -2,46 +2,153 @@ // 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, 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, 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); + (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 { 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), + LibDecimalFloat.packLossless(2.00302e5, -5) + ); + checkPriceNoOlderThan( + LibIntOrAString.fromString2("Equity.US.AMZN/USD"), + LibDecimalFloat.packLossless(500 hours, 0), + 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(1.91401e5, -5) + ); + checkPriceNoOlderThan( + LibIntOrAString.fromString2("Equity.US.MSFT/USD"), + LibDecimalFloat.packLossless(72 hours, 0), + 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(0.35259e5, -5) + ); + checkPriceNoOlderThan( + LibIntOrAString.fromString2("Equity.US.NVDA/USD"), + LibDecimalFloat.packLossless(1000 hours, 0), + 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(0.72984e5, -5) + ); + checkPriceNoOlderThan( + LibIntOrAString.fromString2("Equity.US.GME/USD"), + LibDecimalFloat.packLossless(8000 hours, 0), + LibDecimalFloat.packLossless(29.3177e5, -5), + LibDecimalFloat.packLossless(0.26199e5, -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), + LibDecimalFloat.packLossless(0.40327e5, -5) + ); + checkPriceNoOlderThan( + LibIntOrAString.fromString2("Equity.US.AMZN/USD"), + LibDecimalFloat.packLossless(24 hours, 0), + 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(0.13478e5, -5) + ); + checkPriceNoOlderThan( + LibIntOrAString.fromString2("Equity.US.MSFT/USD"), + LibDecimalFloat.packLossless(24 hours, 0), + 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(0.28529e5, -5) + ); + checkPriceNoOlderThan( + LibIntOrAString.fromString2("Equity.US.NVDA/USD"), + LibDecimalFloat.packLossless(24 hours, 0), + 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(0.40439e5, -5) + ); + checkPriceNoOlderThan( + LibIntOrAString.fromString2("Equity.US.GME/USD"), + LibDecimalFloat.packLossless(24 hours, 0), + 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(0.32048e5, -5) + ); + checkPriceNoOlderThan( + LibIntOrAString.fromString2("Equity.US.BRK-B/USD"), + LibDecimalFloat.packLossless(24 hours, 0), + 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(0.03281e5, -5) + ); } }