Skip to content

Commit d92749d

Browse files
committed
feat(tests): add tests for contract upgrade functionality in PythGovernance
1 parent fe75b45 commit d92749d

File tree

1 file changed

+136
-0
lines changed

1 file changed

+136
-0
lines changed

target_chains/ethereum/contracts/forge-test/PythGovernance.t.sol

Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
pragma solidity ^0.8.0;
44

55
import "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol";
6+
import "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol";
67
import "forge-std/Test.sol";
78

89
import "@pythnetwork/pyth-sdk-solidity/IPyth.sol";
@@ -24,6 +25,7 @@ import "../contracts/wormhole-receiver/ReceiverGovernanceStructs.sol";
2425
import "../contracts/wormhole-receiver/ReceiverStructs.sol";
2526
import "../contracts/wormhole-receiver/ReceiverGovernance.sol";
2627
import "../contracts/libraries/external/BytesLib.sol";
28+
import "../contracts/pyth/mock/MockUpgradeableProxy.sol";
2729
import "./utils/WormholeTestUtils.t.sol";
2830
import "./utils/PythTestUtils.t.sol";
2931
import "./utils/RandTestUtils.t.sol";
@@ -380,6 +382,140 @@ contract PythGovernanceTest is
380382
PythGovernance(address(pyth)).executeGovernanceInstruction(vaa2);
381383
}
382384

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+
383519
function testSetTransactionFee() public {
384520
// Set transaction fee to 1000 (1000 = 1 * 10^3)
385521
bytes memory setTransactionFeeMessage = abi.encodePacked(

0 commit comments

Comments
 (0)