3
3
pragma solidity ^ 0.8.0 ;
4
4
5
5
import "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol " ;
6
+ import "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol " ;
6
7
import "forge-std/Test.sol " ;
7
8
8
9
import "@pythnetwork/pyth-sdk-solidity/IPyth.sol " ;
@@ -24,6 +25,7 @@ import "../contracts/wormhole-receiver/ReceiverGovernanceStructs.sol";
24
25
import "../contracts/wormhole-receiver/ReceiverStructs.sol " ;
25
26
import "../contracts/wormhole-receiver/ReceiverGovernance.sol " ;
26
27
import "../contracts/libraries/external/BytesLib.sol " ;
28
+ import "../contracts/pyth/mock/MockUpgradeableProxy.sol " ;
27
29
import "./utils/WormholeTestUtils.t.sol " ;
28
30
import "./utils/PythTestUtils.t.sol " ;
29
31
import "./utils/RandTestUtils.t.sol " ;
@@ -380,6 +382,140 @@ contract PythGovernanceTest is
380
382
PythGovernance (address (pyth)).executeGovernanceInstruction (vaa2);
381
383
}
382
384
385
+ function testUpgradeContractWithChainIdZeroIsInvalid () public {
386
+ // Deploy a new PythUpgradable contract
387
+ PythUpgradable newImplementation = new PythUpgradable ();
388
+
389
+ // Create governance VAA with chain ID 0 (unset)
390
+ bytes memory data = abi.encodePacked (
391
+ MAGIC,
392
+ uint8 (GovernanceModule.Target),
393
+ uint8 (GovernanceAction.UpgradeContract),
394
+ uint16 (0 ), // Chain ID 0 (unset)
395
+ address (newImplementation) // New implementation address
396
+ );
397
+
398
+ bytes memory vaa = encodeAndSignMessage (
399
+ data,
400
+ TEST_GOVERNANCE_CHAIN_ID,
401
+ TEST_GOVERNANCE_EMITTER,
402
+ 1
403
+ );
404
+
405
+ // Should revert with InvalidGovernanceTarget
406
+ vm.expectRevert (PythErrors.InvalidGovernanceTarget.selector );
407
+ PythGovernance (address (pyth)).executeGovernanceInstruction (vaa);
408
+ }
409
+
410
+ // Helper function to get the second address from event data
411
+ function getSecondAddressFromEventData (
412
+ bytes memory data
413
+ ) internal pure returns (address ) {
414
+ (, address secondAddr ) = abi.decode (data, (address , address ));
415
+ return secondAddr;
416
+ }
417
+
418
+ function testUpgradeContractShouldWork () public {
419
+ // Deploy a new PythUpgradable contract
420
+ PythUpgradable newImplementation = new PythUpgradable ();
421
+
422
+ // Create governance VAA to upgrade the contract
423
+ bytes memory data = abi.encodePacked (
424
+ MAGIC,
425
+ uint8 (GovernanceModule.Target),
426
+ uint8 (GovernanceAction.UpgradeContract),
427
+ TARGET_CHAIN_ID, // Valid target chain ID
428
+ address (newImplementation) // New implementation address
429
+ );
430
+
431
+ bytes memory vaa = encodeAndSignMessage (
432
+ data,
433
+ TEST_GOVERNANCE_CHAIN_ID,
434
+ TEST_GOVERNANCE_EMITTER,
435
+ 1
436
+ );
437
+
438
+ // Create a custom event checker for ContractUpgraded event
439
+ // Since we only care about the newImplementation parameter
440
+ vm.recordLogs ();
441
+
442
+ // Execute the governance instruction
443
+ PythGovernance (address (pyth)).executeGovernanceInstruction (vaa);
444
+
445
+ // Get emitted logs and check the event parameters
446
+ Vm.Log[] memory entries = vm.getRecordedLogs ();
447
+ bool foundUpgradeEvent = false ;
448
+
449
+ for (uint i = 0 ; i < entries.length ; i++ ) {
450
+ // The event signature for ContractUpgraded
451
+ bytes32 eventSignature = keccak256 (
452
+ "ContractUpgraded(address,address) "
453
+ );
454
+
455
+ if (entries[i].topics[0 ] == eventSignature) {
456
+ // This is a ContractUpgraded event
457
+ // Get just the new implementation address using our helper
458
+ address recordedNewImplementation = getSecondAddressFromEventData (
459
+ entries[i].data
460
+ );
461
+
462
+ // Check newImplementation
463
+ assertEq (recordedNewImplementation, address (newImplementation));
464
+ foundUpgradeEvent = true ;
465
+ break ;
466
+ }
467
+ }
468
+
469
+ // Make sure we found the event
470
+ assertTrue (foundUpgradeEvent, "ContractUpgraded event not found " );
471
+
472
+ // Verify the upgrade worked by checking the magic number
473
+ assertEq (
474
+ PythUpgradable (address (pyth)).pythUpgradableMagic (),
475
+ 0x97a6f304
476
+ );
477
+
478
+ // Verify the implementation was upgraded to our new implementation
479
+ // Access implementation using the ERC1967 storage slot
480
+ address implAddr = address (
481
+ uint160 (
482
+ uint256 (
483
+ vm.load (
484
+ address (pyth),
485
+ 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc // ERC1967 implementation slot
486
+ )
487
+ )
488
+ )
489
+ );
490
+ assertEq (implAddr, address (newImplementation));
491
+ }
492
+
493
+ function testUpgradeContractToNonPythContractWontWork () public {
494
+ // Deploy a mock upgradeable proxy that isn't a proper Pyth implementation
495
+ MockUpgradeableProxy newImplementation = new MockUpgradeableProxy ();
496
+
497
+ // Create governance VAA to upgrade to an invalid implementation
498
+ bytes memory data = abi.encodePacked (
499
+ MAGIC,
500
+ uint8 (GovernanceModule.Target),
501
+ uint8 (GovernanceAction.UpgradeContract),
502
+ TARGET_CHAIN_ID, // Valid target chain ID
503
+ address (newImplementation) // Invalid implementation address
504
+ );
505
+
506
+ bytes memory vaa = encodeAndSignMessage (
507
+ data,
508
+ TEST_GOVERNANCE_CHAIN_ID,
509
+ TEST_GOVERNANCE_EMITTER,
510
+ 1
511
+ );
512
+
513
+ // Should revert with no specific error message because the mock implementation
514
+ // doesn't have the pythUpgradableMagic method
515
+ vm.expectRevert ();
516
+ PythGovernance (address (pyth)).executeGovernanceInstruction (vaa);
517
+ }
518
+
383
519
function testSetTransactionFee () public {
384
520
// Set transaction fee to 1000 (1000 = 1 * 10^3)
385
521
bytes memory setTransactionFeeMessage = abi.encodePacked (
0 commit comments