Skip to content

Commit 45099bc

Browse files
committed
fix
1 parent 5b5f0b4 commit 45099bc

File tree

2 files changed

+51
-61
lines changed

2 files changed

+51
-61
lines changed

target_chains/ethereum/contracts/forge-test/utils/PythTestUtils.t.sol

Lines changed: 44 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -370,6 +370,17 @@ abstract contract PythTestUtils is Test, WormholeTestUtils, RandTestUtils {
370370
}
371371

372372
contract PythUtilsTest is Test, WormholeTestUtils, PythTestUtils, IPythEvents {
373+
function successTest(int64 price1, int32 expo1, int64 price2, int32 expo2, int64 expectedPrice, int32 expectedExpo) internal pure {
374+
(int64 price, int32 expo) = PythUtils.deriveCrossRate(price1, expo1, price2, expo2);
375+
assertEq(price, expectedPrice);
376+
assertEq(expo, expectedExpo);
377+
}
378+
379+
function revertTest(int64 price1, int32 expo1, int64 price2, int32 expo2) internal {
380+
vm.expectRevert();
381+
PythUtils.deriveCrossRate(price1, expo1, price2, expo2);
382+
}
383+
373384
function testConvertToUnit() public {
374385
// Price can't be negative
375386
vm.expectRevert();
@@ -401,64 +412,42 @@ contract PythUtilsTest is Test, WormholeTestUtils, PythTestUtils, IPythEvents {
401412
}
402413

403414
function testCombinePrices() public {
404-
// Test case 1: Basic price combination (StEth/Eth / Eth/USD = ETH/BTC)
405-
PythStructs.Price memory stEthEth = PythStructs.Price({
406-
price: 206487956502,
407-
conf: 10,
408-
expo: -8,
409-
publishTime: block.timestamp
410-
});
415+
416+
// Basic Tests
417+
successTest(100, -2, 100, -2, 100, -2);
418+
successTest(10000, -2, 100, -2, 10000, -2);
419+
successTest(1_000_000, -2, 10_000, -2, 10_000, -2);
420+
421+
// Negative Price Tests
422+
revertTest(-100, -2, 100, -2);
423+
revertTest(100, -2, -100, -2);
424+
revertTest(-100, -2, -100, -2);
425+
426+
// Positive Exponent Tests
427+
revertTest(100, 2, 100, -2);
428+
revertTest(100, -2, 100, 2);
429+
revertTest(100, 2, 100, 2);
430+
431+
// Different Exponent Tests
432+
successTest(10_000, -2, 100, -4, 100_000_000, -4);
433+
successTest(10_000, -2, 10_000, 0, 1, -2);
434+
successTest(10_000, 0, 10_000, 0, 1, 0);
435+
436+
// End Range Tests
437+
successTest(int64(type(int64).max), 0, int64(type(int64).max), 0, 1, 0);
438+
successTest(int64(type(int64).max), 0, 1, 0, int64(type(int64).max), 0);
439+
successTest(1, 0, int64(type(int64).max), 0, 1 / int64(type(int64).max), 0);
440+
revertTest(10_000, -2, 10_000, -256);
411441

412-
PythStructs.Price memory ethUsd = PythStructs.Price({
413-
price: 206741615681,
414-
conf: 100,
415-
expo: -8,
416-
publishTime: block.timestamp
417-
});
418-
419-
(int64 price, int32 expo) = PythUtils.combinePrices(stEthEth.price, stEthEth.expo, ethUsd.price, ethUsd.expo);
442+
// More Realistic Tests
443+
// Test case 1: (StEth/Eth / Eth/USD = ETH/BTC)
444+
(int64 price, int32 expo) = PythUtils.deriveCrossRate(206487956502, -8, 206741615681, -8);
420445
assertApproxEqRel(price, 100000000, 9e17); // $1
421446
assertEq(expo, -8);
422-
423-
// Test case 2: Different exponents
424-
PythStructs.Price memory smallPrice = PythStructs.Price({
425-
price: 100, // $0.01
426-
conf: 1,
427-
expo: -4, // 4 decimals
428-
publishTime: block.timestamp
429-
});
430-
431-
PythStructs.Price memory largePrice = PythStructs.Price({
432-
price: 1000, // $10
433-
conf: 10,
434-
expo: -2, // 2 decimals
435-
publishTime: block.timestamp
436-
});
437-
438-
(price, expo) = PythUtils.combinePrices(smallPrice.price, smallPrice.expo, largePrice.price, largePrice.expo);
439-
assertEq(price, 10); // 0.001
440-
assertEq(expo, -4);
441-
442-
// Test case 3: Revert on negative prices
443-
PythStructs.Price memory negativePrice = PythStructs.Price({
444-
price: -100,
445-
conf: 10,
446-
expo: -2,
447-
publishTime: block.timestamp
448-
});
449-
450-
vm.expectRevert();
451-
PythUtils.combinePrices(negativePrice.price, negativePrice.expo, ethUsd.price, ethUsd.expo);
452447

453-
// Test case 4: Revert on positive exponents
454-
PythStructs.Price memory invalidExpo = PythStructs.Price({
455-
price: 100,
456-
conf: 10,
457-
expo: 2,
458-
publishTime: block.timestamp
459-
});
460-
461-
vm.expectRevert();
462-
PythUtils.combinePrices(ethUsd.price, ethUsd.expo, invalidExpo.price, invalidExpo.expo);
448+
// Test case 2:
449+
(price, expo) = PythUtils.deriveCrossRate(520010, -8, 38591, -8);
450+
assertApproxEqRel(price, 1347490347, 9e17); // $1
451+
assertEq(expo, -8);
463452
}
464453
}

target_chains/ethereum/sdk/solidity/PythUtils.sol

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -37,15 +37,17 @@ library PythUtils {
3737
/// @notice Combines two prices to get a cross-rate
3838
/// @param price1 The first price (a/b)
3939
/// @param price2 The second price (c/b)
40-
/// @return combinedPrice The combined price (a/c)
41-
/// @return expo The exponent of the combined price
42-
/// @dev This function will revert if either price is negative or if the exponents are invalid
43-
function combinePrices(
40+
/// @return crossRate The cross-rate (a/c)
41+
/// @return expo The exponent of the cross-rate
42+
/// @dev This function will revert if either price is negative or if the exponents are invalid.
43+
/// @dev This function will also revert if the cross-rate is greater than int64.max
44+
/// @notice This function doesn't return the combined confidence interval.
45+
function deriveCrossRate(
4446
int64 price1,
4547
int32 expo1,
4648
int64 price2,
4749
int32 expo2
48-
) public pure returns (int64 combinedPrice, int32 expo) {
50+
) public pure returns (int64 crossRate, int32 expo) {
4951
if (price1 < 0 || price2 < 0 || expo1 > 0 || expo2 > 0 || expo1 < -255 || expo2 < -255) {
5052
revert();
5153
}
@@ -58,7 +60,6 @@ library PythUtils {
5860
// Calculate the combined price with precision
5961
uint256 combined = (p1 * 10**18) / p2;
6062
combined = combined / 10 ** (18 - maxDecimals);
61-
6263
// Check if the combined price fits in int64
6364
if (combined > uint256(uint64(type(int64).max))) {
6465
revert();

0 commit comments

Comments
 (0)