Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Create a Math Library for Combining Pyth Prices on Solidity #1746

Open
wants to merge 10 commits into
base: main
Choose a base branch
from

Conversation

0xHaku
Copy link

@0xHaku 0xHaku commented Jun 29, 2024

Overview

This pull request includes the implementation of the get_collateral_valuation_price, get_borrow_valuation_price, and get_price_in_quote functions in Solidity, along with their supporting functions affine_combination and fraction.
These functions were ported from their original Rust implementations to Solidity to ensure compatibility and functionality within our Solidity-based smart contract environment.

Changes Made

get_collateral_valuation_price

Ported the logic to calculate the valuation of a collateral position based on the amount deposited, using a linear interpolation between initial and final discount rates.
Ensured the function handles the correct scaling and precision of rates and amounts.

get_borrow_valuation_price

Ported the logic to calculate the valuation of a borrow position based on the amount borrowed, using a linear interpolation between initial and final premium rates.
Ensured the function maintains correct scaling and precision for the premiums.

get_price_in_quote

Implemented the function to convert the price of one token into the price of another token (quote).
Ensured the function accurately handles conversions and maintains precision.

Supporting Functions

Implemented affine_combination to perform affine combination of two prices.
Implemented fraction to create fractions of prices with appropriate precision handling.

Documentation

In-code comments were added to explain the logic and assumptions behind each function.

Additional Notes

Ensure that Solidity version compatibility is checked before merging.
Peer review and additional testing are recommended to validate the correctness of the ported functions.

Copy link

vercel bot commented Jun 29, 2024

@0xHaku is attempting to deploy a commit to the pyth-web Team on Vercel.

A member of the Team first needs to authorize it.

* @param discount_exponent The exponent to apply to the discounts
* @return The valuation price of the collateral
*/
function get_collateral_valuation_price(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

camel case

* @return The valuation price of the collateral
*/
function get_collateral_valuation_price(
PythStructs.Price memory self,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

better name than self? maybe price?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

While "price" might be a better fit according to general function naming conventions, using "self" makes sense if we assume the use of the using PriceLib for PythStructs.Price; syntax (as the first argument is omitted).
Which one do you prefer?

int32 midpriceExpo = base.expo - other.expo + PD_EXPO;

uint64 otherConfidencePct = (other.conf * PD_SCALE) / otherPrice;
uint128 conf = ((uint128(base.conf) * PD_SCALE) / otherPrice) +
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i think solidity handles casting uints up, but make sure? generally need some tests to make sure this all works

* @return The resulting price
*/
function cmul(
PythStructs.Price memory self,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

again rename this to something clearer, like price

*/
function mul(
PythStructs.Price memory self,
PythStructs.Price memory other
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

price1, price2

* @param b The second value
* @return The minimum of a and b
*/
function min(uint64 a, uint64 b) internal pure returns (uint64) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

don't use this, use Math.min from openzeppelin

* @param b The second value
* @return The minimum of a and b
*/
function min(uint256 a, uint256 b) internal pure returns (uint256) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same as above

function min(uint256 a, uint256 b) internal pure returns (uint256) {
return a < b ? a : b;
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This generally looks good to me, main thing is it needs a bunch of tests. See here for examples, i'd recommend using forge for unit testing

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@anihamde
I referred to forge-test/PythTestUtils.t.sol for how to write forge-test.
This test code seems to be testing the code stored in the @pythnetwork/pyth-sdk-solidity library.
However, my code is not included in the package, so I cannot use this notation.
Should I import the test target file by traversing the local directory, or should I incorporate the implementation into the package and then import the target file?
Please advise on the correct procedure.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can just write unit tests for this library. I would recommend using a Harness to access the methods of the library. You could write a Harness to inherit PythStructs.Price, and then do using PriceLib for PriceHarness to write all the test cases. Let me know if that makes sense?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@anihamde
I have created a test contract that encompasses the harness and performed a series of tests, so please review it.

Copy link
Contributor

@anihamde anihamde left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks better. I would say it still needs a lot more tests, and there are some existing issues I pointed out. In particular on the point about needing more tests, I would like to see tests around getBorrowValuationPrice, getCollateralValuationPrice, but also would be good to see a lot more tests for the primitive ops like mul, div, etc. See how many tests are here for reference--doesn't need to be that many, but really would be good to check edge cases and make sure behavior is as intended. This shouldn't be too hard, but it is important

uint64 midprice = (basePrice * PD_SCALE) / otherPrice;
int32 midpriceExpo = base.expo - price2.expo + PD_EXPO;

uint64 otherConfidencePct = (price2.conf * PD_SCALE) / otherPrice;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe some comments here to explain the math, similar to what's in the rust sdk? o/w it's close to impossible for a first-time reader of the code to understand what is going on here

uint64 midprice = basePrice * otherPrice;
int32 midpriceExpo = base.expo + price2.expo;

uint64 conf = base.conf * otherPrice + price2.conf * basePrice;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same comment here as in div function: some comments to explain the math would be great

});
PythStructs.Price memory result = harness.mul(price1, price2);
assertEq(result.price, 5000, "Multiplication result should be 5000");
assertEq(result.expo, -16, "Exponents should be added");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

assert the confidence is as expected?

5,
-9
);
assertEq(result.price, 10 ** 7, "Affine combination should be 150");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this assert message is wrong. i think this test case is wrong too? should be 200 for y2, i think?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants