From 44ad4cc0c27cacdcc8a282bed5f8759b6f24abcd Mon Sep 17 00:00:00 2001 From: Ramesh Sachan <43104299+holps-7@users.noreply.github.com> Date: Fri, 21 Jun 2024 05:24:14 +0530 Subject: [PATCH 1/3] Math Library for Combining Pyth Prices on Solidity --- .../ethereum/sdk/solidity/PriceCombine.sol | 130 ++++++++++++++++++ 1 file changed, 130 insertions(+) create mode 100644 target_chains/ethereum/sdk/solidity/PriceCombine.sol diff --git a/target_chains/ethereum/sdk/solidity/PriceCombine.sol b/target_chains/ethereum/sdk/solidity/PriceCombine.sol new file mode 100644 index 0000000000..4292159bb9 --- /dev/null +++ b/target_chains/ethereum/sdk/solidity/PriceCombine.sol @@ -0,0 +1,130 @@ +// SPDX-License-Identifier: Apache 2 +pragma solidity ^0.8.0; + +import "./PythStructs.sol"; + +// Constants for working with pyth's number representation +int32 constant PD_EXPO = -9; +uint64 constant PD_SCALE = 1_000_000_000; +uint64 constant MAX_PD_V_U64 = (1 << 28) - 1; + +library PriceCombine { + function getPriceInQuote(PythStructs.Price memory self, PythStructs.Price memory quote, int32 resultExpo) public pure returns (PythStructs.Price memory) { + PythStructs.Price memory result = div(self, quote); + //return zero price so the error can be gracefully handeded by the caller + if (result.price == 0 && result.conf == 0) { + return PythStructs.Price({ + price: 0, + conf: 0, + expo: 0, + publishTime: 0 + }); + } + return scaleToExponent(result, resultExpo); + } + + function div(PythStructs.Price memory self, PythStructs.Price memory other) public pure returns (PythStructs.Price memory) { + PythStructs.Price memory base = normalize(self); + other = normalize(other); + + //if other.price == 0 then return zero + if (other.price == 0) { + return PythStructs.Price({ + price: 0, + conf: 0, + expo: 0, + publishTime: 0 + }); + } + + // Convert prices to unsigned and sign + (uint64 basePrice, int64 baseSign) = toUnsigned(base.price); + (uint64 otherPrice, int64 otherSign) = toUnsigned(other.price); + + // Compute the midprice + uint64 midprice = basePrice * PD_SCALE / otherPrice; + int32 midpriceExpo = base.expo - other.expo + PD_EXPO; + + // Compute the confidence interval + uint64 otherConfidencePct = other.conf * PD_SCALE / otherPrice; + uint128 conf = (base.conf * PD_SCALE / otherPrice) + (otherConfidencePct * midprice) / PD_SCALE; + + if (conf < type(uint64).max) { + return PythStructs.Price( + int64(int64(midprice) * baseSign * otherSign), + uint64(conf), + midpriceExpo, + self.publishTime < other.publishTime ? self.publishTime : other.publishTime + ); + } else { + //return zero price so the error can be gracefully handeded by the caller + return PythStructs.Price({ + price: 0, + conf: 0, + expo: 0, + publishTime: 0 + }); + } + } + + // Helper functions + function normalize(PythStructs.Price memory self) public pure returns (PythStructs.Price memory) { + (uint64 price, int64 sign) = toUnsigned(self.price); + uint64 conf = self.conf; + int32 expo = self.expo; + + while (price > MAX_PD_V_U64 || conf > MAX_PD_V_U64) { + price = price / 10; + conf = conf / 10; + expo = expo + 1; + } + + return PythStructs.Price({ + price: int64(price) * sign, + conf: conf, + expo: expo, + publishTime: self.publishTime + }); + } + + function scaleToExponent(PythStructs.Price memory self, int32 targetExpo) public pure returns (PythStructs.Price memory) { + int32 delta = targetExpo - self.expo; + int64 price = self.price; + uint64 conf = self.conf; + if (delta >= 0) { + while (delta > 0 && (price != 0 || conf != 0)) { + price = price / 10; + conf = conf / 10; + delta = delta - 1; + } + return PythStructs.Price({ + price: price, + conf: conf, + expo: targetExpo, + publishTime: self.publishTime + }); + } else { + while (delta < 0) { + price = price * 10; + conf = conf * 10; + delta = delta + 1; + } + return PythStructs.Price({ + price: price, + conf: conf, + expo: targetExpo, + publishTime: self.publishTime + }); + } + } + + function toUnsigned(int64 x) public pure returns (uint64, int64) { + if (x == type(int64).min) { + return (uint64(type(int64).max) + 1, -1); + } else if (x < 0) { + return (uint64(-x), -1); + } else { + return (uint64(x), 1); + } + } +} \ No newline at end of file From fdacfa2bc6b31d9b1a183028e99e92399284d52c Mon Sep 17 00:00:00 2001 From: Ramesh Sachan <43104299+holps-7@users.noreply.github.com> Date: Fri, 21 Jun 2024 05:35:45 +0530 Subject: [PATCH 2/3] add comments --- target_chains/ethereum/sdk/solidity/PriceCombine.sol | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/target_chains/ethereum/sdk/solidity/PriceCombine.sol b/target_chains/ethereum/sdk/solidity/PriceCombine.sol index 4292159bb9..dabddaa3c2 100644 --- a/target_chains/ethereum/sdk/solidity/PriceCombine.sol +++ b/target_chains/ethereum/sdk/solidity/PriceCombine.sol @@ -67,7 +67,8 @@ library PriceCombine { } } - // Helper functions + /// Get a copy of this struct where the price and confidence + /// have been normalized to be between `MIN_PD_V_I64` and `MAX_PD_V_I64`. function normalize(PythStructs.Price memory self) public pure returns (PythStructs.Price memory) { (uint64 price, int64 sign) = toUnsigned(self.price); uint64 conf = self.conf; @@ -87,6 +88,13 @@ library PriceCombine { }); } + /// Scale this price/confidence so that its exponent is `target_expo`. + /// + /// Return `Zero` if this number is outside the range of numbers representable in `target_expo`, + /// which will happen if `target_expo` is too small. + /// + /// Warning: if `target_expo` is significantly larger than the current exponent, this + /// function will return 0 +- 0. function scaleToExponent(PythStructs.Price memory self, int32 targetExpo) public pure returns (PythStructs.Price memory) { int32 delta = targetExpo - self.expo; int64 price = self.price; @@ -118,6 +126,8 @@ library PriceCombine { } } + /// Helper function to convert signed integers to unsigned and a sign bit, which simplifies + /// some of the computations above. function toUnsigned(int64 x) public pure returns (uint64, int64) { if (x == type(int64).min) { return (uint64(type(int64).max) + 1, -1); From 8aa17e815c7740dd8096545304bb0ecc97db57df Mon Sep 17 00:00:00 2001 From: Ramesh Sachan <43104299+holps-7@users.noreply.github.com> Date: Sat, 22 Jun 2024 05:37:59 +0530 Subject: [PATCH 3/3] update state mutability --- target_chains/ethereum/sdk/solidity/PriceCombine.sol | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/target_chains/ethereum/sdk/solidity/PriceCombine.sol b/target_chains/ethereum/sdk/solidity/PriceCombine.sol index dabddaa3c2..5ca1bcd7ed 100644 --- a/target_chains/ethereum/sdk/solidity/PriceCombine.sol +++ b/target_chains/ethereum/sdk/solidity/PriceCombine.sol @@ -9,7 +9,7 @@ uint64 constant PD_SCALE = 1_000_000_000; uint64 constant MAX_PD_V_U64 = (1 << 28) - 1; library PriceCombine { - function getPriceInQuote(PythStructs.Price memory self, PythStructs.Price memory quote, int32 resultExpo) public pure returns (PythStructs.Price memory) { + function getPriceInQuote(PythStructs.Price memory self, PythStructs.Price memory quote, int32 resultExpo) public view returns (PythStructs.Price memory) { PythStructs.Price memory result = div(self, quote); //return zero price so the error can be gracefully handeded by the caller if (result.price == 0 && result.conf == 0) { @@ -23,7 +23,7 @@ library PriceCombine { return scaleToExponent(result, resultExpo); } - function div(PythStructs.Price memory self, PythStructs.Price memory other) public pure returns (PythStructs.Price memory) { + function div(PythStructs.Price memory self, PythStructs.Price memory other) public view returns (PythStructs.Price memory) { PythStructs.Price memory base = normalize(self); other = normalize(other); @@ -69,7 +69,7 @@ library PriceCombine { /// Get a copy of this struct where the price and confidence /// have been normalized to be between `MIN_PD_V_I64` and `MAX_PD_V_I64`. - function normalize(PythStructs.Price memory self) public pure returns (PythStructs.Price memory) { + function normalize(PythStructs.Price memory self) public view returns (PythStructs.Price memory) { (uint64 price, int64 sign) = toUnsigned(self.price); uint64 conf = self.conf; int32 expo = self.expo;