@@ -371,21 +371,27 @@ abstract contract PythTestUtils is Test, WormholeTestUtils, RandTestUtils {
371
371
372
372
contract PythUtilsTest is Test , WormholeTestUtils , PythTestUtils , IPythEvents {
373
373
function assertCrossRateEquals (
374
- int64 price1 ,
375
- int32 expo1 ,
376
- int64 price2 ,
374
+ int64 price1 ,
375
+ int32 expo1 ,
376
+ int64 price2 ,
377
377
int32 expo2 ,
378
- int32 targetExpo ,
378
+ int32 targetExpo ,
379
379
uint256 expectedPrice
380
380
) internal {
381
- uint256 price = PythUtils.deriveCrossRate (price1, expo1, price2, expo2, targetExpo);
381
+ uint256 price = PythUtils.deriveCrossRate (
382
+ price1,
383
+ expo1,
384
+ price2,
385
+ expo2,
386
+ targetExpo
387
+ );
382
388
assertEq (price, expectedPrice);
383
389
}
384
390
385
391
function assertCrossRateReverts (
386
- int64 price1 ,
387
- int32 expo1 ,
388
- int64 price2 ,
392
+ int64 price1 ,
393
+ int32 expo1 ,
394
+ int64 price2 ,
389
395
int32 expo2 ,
390
396
int32 targetExpo ,
391
397
bytes4 expectedError
@@ -395,7 +401,6 @@ contract PythUtilsTest is Test, WormholeTestUtils, PythTestUtils, IPythEvents {
395
401
}
396
402
397
403
function testConvertToUnit () public {
398
-
399
404
// Test 1: Price can't be negative
400
405
vm.expectRevert (PythErrors.NegativeInputPrice.selector );
401
406
PythUtils.convertToUint (- 100 , - 5 , 18 );
@@ -438,13 +443,15 @@ contract PythUtilsTest is Test, WormholeTestUtils, PythTestUtils, IPythEvents {
438
443
439
444
// Test 8: Positive Exponent Tests
440
445
// Price with 18 decimals and exponent 5
441
- assertEq (PythUtils.convertToUint (100 , 5 , 18 ), 100_00_000_000_000_000_000_000_000 ); // 100 with 23 zeros
446
+ assertEq (
447
+ PythUtils.convertToUint (100 , 5 , 18 ),
448
+ 100_00_000_000_000_000_000_000_000
449
+ ); // 100 with 23 zeros
442
450
// Test 9: Price with 9 decimals and exponent 2
443
451
assertEq (PythUtils.convertToUint (100 , 2 , 9 ), 100_00_000_000_000 ); // 100 with 11 zeros
444
452
445
453
// Test 10: Price with 2 decimals and exponent 1
446
- assertEq (PythUtils.convertToUint (100 , 1 , 2 ), 100_000 ); // 100 with 3 zeros
447
-
454
+ assertEq (PythUtils.convertToUint (100 , 1 , 2 ), 100_000 ); // 100 with 3 zeros
448
455
449
456
// Special Cases
450
457
// Test 11: price = 0, any expo/decimals returns 0
@@ -464,7 +471,7 @@ contract PythUtilsTest is Test, WormholeTestUtils, PythTestUtils, IPythEvents {
464
471
// Test 14: deltaExponent > 0 (should shift price up)
465
472
assertEq (PythUtils.convertToUint (123456 , 5 , 0 ), 12345600000 );
466
473
assertEq (PythUtils.convertToUint (123456 , 5 , 2 ), 1234560000000 );
467
-
474
+
468
475
// Test 15: deltaExponent < 0 (should shift price down)
469
476
assertEq (PythUtils.convertToUint (123456 , - 5 , 0 ), 1 );
470
477
assertEq (PythUtils.convertToUint (123456 , - 5 , 2 ), 123 );
@@ -476,22 +483,25 @@ contract PythUtilsTest is Test, WormholeTestUtils, PythTestUtils, IPythEvents {
476
483
477
484
// Test 17: Big price and scaling, but outside of bounds
478
485
vm.expectRevert (PythErrors.ExponentOverflow.selector );
479
- assertEq (PythUtils.convertToUint (100_000_000 , 10 , 50 ),0 );
486
+ assertEq (PythUtils.convertToUint (100_000_000 , 10 , 50 ), 0 );
480
487
481
488
// Test 18: Big price and scaling
482
- assertEq (PythUtils.convertToUint (100_000_000 , - 50 , 10 ),0 ); // -50 + 10 = -40 > -58
483
- vm.expectRevert (PythErrors.ExponentOverflow.selector );
489
+ assertEq (PythUtils.convertToUint (100_000_000 , - 50 , 10 ), 0 ); // -50 + 10 = -40 > -58
490
+ vm.expectRevert (PythErrors.ExponentOverflow.selector );
484
491
assertEq (PythUtils.convertToUint (100_000_000 , 10 , 50 ), 0 ); // 10 + 50 = 60 > 58
485
-
492
+
486
493
// Test 19: Decimals just save from truncation
487
494
assertEq (PythUtils.convertToUint (5 , - 1 , 1 ), 5 ); // 5/10*10 = 5
488
495
assertEq (PythUtils.convertToUint (5 , - 1 , 2 ), 50 ); // 5/10*100 = 50
489
496
490
497
// 10. Test: Big price and scaling, should be inside the bounds
491
498
// We have to convert int64 -> int256 -> uint256 before multiplying by 10 ** 58
492
- assertEq (PythUtils.convertToUint (type (int64 ).max, 50 , 8 ), uint256 (int256 (type (int64 ).max)) * 10 ** 58 ); // 50 + 8 = 58
499
+ assertEq (
500
+ PythUtils.convertToUint (type (int64 ).max, 50 , 8 ),
501
+ uint256 (int256 (type (int64 ).max)) * 10 ** 58
502
+ ); // 50 + 8 = 58
493
503
vm.expectRevert (PythErrors.ExponentOverflow.selector );
494
- assertEq (PythUtils.convertToUint (type (int64 ).max, 50 , 9 ), 0 );
504
+ assertEq (PythUtils.convertToUint (type (int64 ).max, 50 , 9 ), 0 );
495
505
assertEq (PythUtils.convertToUint (type (int64 ).max, - 64 , 8 ), 0 ); // -64 + 8 = -56 > -58
496
506
assertEq (PythUtils.convertToUint (type (int64 ).max, - 50 , 1 ), 0 ); // -50 + 1 = -49 > -58
497
507
@@ -500,77 +510,191 @@ contract PythUtilsTest is Test, WormholeTestUtils, PythTestUtils, IPythEvents {
500
510
assertEq (PythUtils.convertToUint (type (int64 ).max, 50 , 9 ), 0 ); // 50 + 9 = 59 > 58
501
511
vm.expectRevert (PythErrors.ExponentOverflow.selector );
502
512
assertEq (PythUtils.convertToUint (type (int64 ).max, - 60 , 1 ), 0 ); // -60 + 1 = -59 < -58
503
-
504
513
}
505
514
506
515
function testDeriveCrossRate () public {
507
-
508
516
// Test 1: Prices can't be negative
509
- assertCrossRateReverts (- 100 , - 2 , 100 , - 2 , 5 , PythErrors.NegativeInputPrice.selector );
510
- assertCrossRateReverts (100 , - 2 , - 100 , - 2 , 5 , PythErrors.NegativeInputPrice.selector );
511
- assertCrossRateReverts (- 100 , - 2 , - 100 , - 2 , 5 , PythErrors.NegativeInputPrice.selector );
517
+ assertCrossRateReverts (
518
+ - 100 ,
519
+ - 2 ,
520
+ 100 ,
521
+ - 2 ,
522
+ 5 ,
523
+ PythErrors.NegativeInputPrice.selector
524
+ );
525
+ assertCrossRateReverts (
526
+ 100 ,
527
+ - 2 ,
528
+ - 100 ,
529
+ - 2 ,
530
+ 5 ,
531
+ PythErrors.NegativeInputPrice.selector
532
+ );
533
+ assertCrossRateReverts (
534
+ - 100 ,
535
+ - 2 ,
536
+ - 100 ,
537
+ - 2 ,
538
+ 5 ,
539
+ PythErrors.NegativeInputPrice.selector
540
+ );
512
541
513
542
// Test 2: Exponent can't be less than -255
514
- assertCrossRateReverts (100 , - 256 , 100 , - 2 , 5 , PythErrors.InvalidInputExpo.selector );
515
- assertCrossRateReverts (100 , - 2 , 100 , - 256 , 5 , PythErrors.InvalidInputExpo.selector );
516
- assertCrossRateReverts (100 , - 256 , 100 , - 256 , 5 , PythErrors.InvalidInputExpo.selector );
543
+ assertCrossRateReverts (
544
+ 100 ,
545
+ - 256 ,
546
+ 100 ,
547
+ - 2 ,
548
+ 5 ,
549
+ PythErrors.InvalidInputExpo.selector
550
+ );
551
+ assertCrossRateReverts (
552
+ 100 ,
553
+ - 2 ,
554
+ 100 ,
555
+ - 256 ,
556
+ 5 ,
557
+ PythErrors.InvalidInputExpo.selector
558
+ );
559
+ assertCrossRateReverts (
560
+ 100 ,
561
+ - 256 ,
562
+ 100 ,
563
+ - 256 ,
564
+ 5 ,
565
+ PythErrors.InvalidInputExpo.selector
566
+ );
517
567
// Target exponent can't be less than -255
518
- assertCrossRateReverts (100 , - 2 , 100 , - 2 , - 256 , PythErrors.InvalidInputExpo.selector );
568
+ assertCrossRateReverts (
569
+ 100 ,
570
+ - 2 ,
571
+ 100 ,
572
+ - 2 ,
573
+ - 256 ,
574
+ PythErrors.InvalidInputExpo.selector
575
+ );
519
576
520
- // Test 3: Basic Tests with negative exponents
521
- assertCrossRateEquals (500 , - 8 , 500 , - 8 , - 5 , 100000 );
577
+ // Test 3: Basic Tests with negative exponents
578
+ assertCrossRateEquals (500 , - 8 , 500 , - 8 , - 5 , 100000 );
522
579
assertCrossRateEquals (10_000 , - 8 , 100 , - 2 , - 5 , 10 );
523
580
assertCrossRateEquals (10_000 , - 2 , 100 , - 8 , - 5 , 100_00_000_000_000 );
524
581
525
582
// Test 4: Basic Tests with positive exponents
526
583
assertCrossRateEquals (100 , 2 , 100 , 2 , - 5 , 100000 ); // 100 * 10^2 / 100 * 10^2 = 10000 / 10000 = 1 == 100000 * 10^-5
527
584
// We will loose preistion as the the target exponent is 5 making the price 0.00001
528
- assertCrossRateEquals (100 , 8 , 100 , 8 , 5 , 0 );
585
+ assertCrossRateEquals (100 , 8 , 100 , 8 , 5 , 0 );
529
586
530
587
// Test 5: Different Exponent Tests
531
588
assertCrossRateEquals (10_000 , - 2 , 100 , - 4 , 0 , 10_000 ); // 10_000 / 100 = 100 * 10(-2 - -4) = 10_000 with 0 decimals = 10_000
532
589
assertCrossRateEquals (10_000 , - 2 , 100 , - 4 , 5 , 0 ); // 10_000 / 100 = 100 * 10(-2 - -4) = 10_000 with 5 decimals = 0
533
590
assertCrossRateEquals (10_000 , - 2 , 10_000 , - 1 , 5 , 0 ); // It will truncate to 0
534
591
assertCrossRateEquals (10_000 , - 10 , 10_000 , - 2 , 0 , 0 ); // It will truncate to 0
535
- assertCrossRateEquals (100_000_000 , - 2 , 100 , - 8 , - 8 , 100_000_000_000_000_000_000 ); // 100_000_000 / 100 = 1_000_000 * 10(-2 - -8) = 1000000 * 10^6 = 1000000000000
592
+ assertCrossRateEquals (
593
+ 100_000_000 ,
594
+ - 2 ,
595
+ 100 ,
596
+ - 8 ,
597
+ - 8 ,
598
+ 100_000_000_000_000_000_000
599
+ ); // 100_000_000 / 100 = 1_000_000 * 10(-2 - -8) = 1000000 * 10^6 = 1000000000000
536
600
537
601
// Test 6: Exponent Edge Tests
538
- assertCrossRateEquals (10_000 , 0 , 100 , 0 , 0 , 100 );
539
- assertCrossRateReverts (10_000 , 0 , 100 , 0 , - 255 , PythErrors.ExponentOverflow.selector );
540
- assertCrossRateReverts (10_000 , 0 , 100 , - 255 , - 255 , PythErrors.ExponentOverflow.selector );
541
- assertCrossRateReverts (10_000 , - 255 , 100 , 0 , 0 , PythErrors.ExponentOverflow.selector );
542
- assertCrossRateReverts (10_000 , - 255 , 100 , - 178 , - 5 , PythErrors.ExponentOverflow.selector );
543
-
602
+ assertCrossRateEquals (10_000 , 0 , 100 , 0 , 0 , 100 );
603
+ assertCrossRateReverts (
604
+ 10_000 ,
605
+ 0 ,
606
+ 100 ,
607
+ 0 ,
608
+ - 255 ,
609
+ PythErrors.ExponentOverflow.selector
610
+ );
611
+ assertCrossRateReverts (
612
+ 10_000 ,
613
+ 0 ,
614
+ 100 ,
615
+ - 255 ,
616
+ - 255 ,
617
+ PythErrors.ExponentOverflow.selector
618
+ );
619
+ assertCrossRateReverts (
620
+ 10_000 ,
621
+ - 255 ,
622
+ 100 ,
623
+ 0 ,
624
+ 0 ,
625
+ PythErrors.ExponentOverflow.selector
626
+ );
627
+ assertCrossRateReverts (
628
+ 10_000 ,
629
+ - 255 ,
630
+ 100 ,
631
+ - 178 ,
632
+ - 5 ,
633
+ PythErrors.ExponentOverflow.selector
634
+ );
544
635
545
636
// Test 7: Max int64 price and scaling
546
- assertCrossRateEquals (type (int64 ).max, 0 , 1 , 0 , 0 , uint256 (int256 (type (int64 ).max)));
637
+ assertCrossRateEquals (
638
+ type (int64 ).max,
639
+ 0 ,
640
+ 1 ,
641
+ 0 ,
642
+ 0 ,
643
+ uint256 (int256 (type (int64 ).max))
644
+ );
547
645
assertCrossRateEquals (1 , 0 , type (int64 ).max, 0 , 0 , 0 );
548
646
assertCrossRateEquals (type (int64 ).max, 0 , type (int64 ).max, 0 , 0 , 1 );
549
647
// type(int64).max is approx 9.223e18
550
648
assertCrossRateEquals (type (int64 ).max, 0 , 1 , 0 , 18 , 9 );
551
649
// 1 / type(int64).max is approx 1.085e-19
552
650
assertCrossRateEquals (1 , 0 , type (int64 ).max, 0 , - 19 , 1 );
553
651
// type(int64).max * 10 ** 58 / 1
554
- assertCrossRateEquals (type (int64 ).max, 50 , 1 , - 8 , 0 , uint256 (int256 (type (int64 ).max)) * 10 ** 58 );
652
+ assertCrossRateEquals (
653
+ type (int64 ).max,
654
+ 50 ,
655
+ 1 ,
656
+ - 8 ,
657
+ 0 ,
658
+ uint256 (int256 (type (int64 ).max)) * 10 ** 58
659
+ );
555
660
// 1 / (type(int64).max * 10 ** 58)
556
661
assertCrossRateEquals (1 , 0 , type (int64 ).max, 50 , 8 , 0 );
557
662
558
663
// type(int64).max * 10 ** 59 / 1
559
- assertCrossRateReverts (type (int64 ).max, 50 , 1 , - 9 , 0 , PythErrors.ExponentOverflow.selector );
664
+ assertCrossRateReverts (
665
+ type (int64 ).max,
666
+ 50 ,
667
+ 1 ,
668
+ - 9 ,
669
+ 0 ,
670
+ PythErrors.ExponentOverflow.selector
671
+ );
560
672
// 1 / (type(int64).max * 10 ** 59)
561
- assertCrossRateReverts (1 , 0 , type (int64 ).max, 50 , 9 , PythErrors.ExponentOverflow.selector );
562
-
673
+ assertCrossRateReverts (
674
+ 1 ,
675
+ 0 ,
676
+ type (int64 ).max,
677
+ 50 ,
678
+ 9 ,
679
+ PythErrors.ExponentOverflow.selector
680
+ );
563
681
564
682
// Realistic Tests
565
683
// Test case 1: (StEth/Eth / Eth/USD = ETH/BTC)
566
- uint256 price = PythUtils.deriveCrossRate (206487956502 , - 8 , 206741615681 , - 8 , - 8 );
684
+ uint256 price = PythUtils.deriveCrossRate (
685
+ 206487956502 ,
686
+ - 8 ,
687
+ 206741615681 ,
688
+ - 8 ,
689
+ - 8
690
+ );
567
691
assertApproxEqRel (price, 100000000 , 9e17 ); // $1
568
692
569
- // Test case 2:
693
+ // Test case 2:
570
694
price = PythUtils.deriveCrossRate (520010 , - 8 , 38591 , - 8 , - 8 );
571
695
assertApproxEqRel (price, 1347490347 , 9e17 ); // $13.47
572
696
573
- // Test case 3:
697
+ // Test case 3:
574
698
price = PythUtils.deriveCrossRate (520010 , - 8 , 38591 , - 8 , - 12 );
575
699
assertApproxEqRel (price, 13474903475432 , 9e17 ); // $13.47
576
700
}
0 commit comments