Daniel Chew 8 ماه پیش
والد
کامیت
ebcb1aa4d5
1فایلهای تغییر یافته به همراه475 افزوده شده و 0 حذف شده
  1. 475 0
      target_chains/ethereum/contracts/forge-test/PythGovernance.t.sol

+ 475 - 0
target_chains/ethereum/contracts/forge-test/PythGovernance.t.sol

@@ -0,0 +1,475 @@
+// SPDX-License-Identifier: Apache 2
+
+pragma solidity ^0.8.0;
+
+import "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol";
+import "forge-std/Test.sol";
+
+import "@pythnetwork/pyth-sdk-solidity/IPyth.sol";
+import "@pythnetwork/pyth-sdk-solidity/PythErrors.sol";
+import "@pythnetwork/pyth-sdk-solidity/PythStructs.sol";
+import "../contracts/pyth/PythInternalStructs.sol";
+import "../contracts/pyth/PythGovernanceInstructions.sol";
+import "../contracts/pyth/PythUpgradable.sol";
+import "../contracts/pyth/PythGetters.sol";
+import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
+import "../contracts/wormhole/interfaces/IWormhole.sol";
+import "../contracts/wormhole/Implementation.sol";
+import "../contracts/wormhole/Setup.sol";
+import "../contracts/wormhole/Wormhole.sol";
+import "../contracts/wormhole-receiver/WormholeReceiver.sol";
+import "../contracts/wormhole-receiver/ReceiverImplementation.sol";
+import "../contracts/wormhole-receiver/ReceiverSetup.sol";
+import "../contracts/wormhole-receiver/ReceiverGovernanceStructs.sol";
+import "../contracts/wormhole-receiver/ReceiverStructs.sol";
+import "../contracts/wormhole-receiver/ReceiverGovernance.sol";
+import "../contracts/libraries/external/BytesLib.sol";
+import "./utils/WormholeTestUtils.t.sol";
+import "./utils/PythTestUtils.t.sol";
+import "./utils/RandTestUtils.t.sol";
+
+contract PythGovernanceTest is
+    Test,
+    WormholeTestUtils,
+    PythTestUtils,
+    PythGovernanceInstructions
+{
+    using BytesLib for bytes;
+
+    IPyth public pyth;
+    address constant TEST_SIGNER1 = 0xbeFA429d57cD18b7F8A4d91A2da9AB4AF05d0FBe;
+    address constant TEST_SIGNER2 = 0x4ba0C2db9A26208b3bB1a50B01b16941c10D76db;
+    uint16 constant TEST_GOVERNANCE_CHAIN_ID = 1;
+    bytes32 constant TEST_GOVERNANCE_EMITTER =
+        0x0000000000000000000000000000000000000000000000000000000000000011;
+    uint16 constant TEST_PYTH2_WORMHOLE_CHAIN_ID = 1;
+    bytes32 constant TEST_PYTH2_WORMHOLE_EMITTER =
+        0x71f8dcb863d176e2c420ad6610cf687359612b6fb392e0642b0ca6b1f186aa3b;
+    uint16 constant TARGET_CHAIN_ID = 2;
+
+    function setUp() public {
+        pyth = IPyth(setUpPyth(setUpWormholeReceiver(1)));
+    }
+
+    function testNoOwner() public {
+        // Check that the ownership is renounced
+        assertEq(OwnableUpgradeable(address(pyth)).owner(), address(0));
+    }
+
+    function testValidDataSources() public {
+        assertTrue(
+            PythGetters(address(pyth)).isValidDataSource(
+                TEST_PYTH2_WORMHOLE_CHAIN_ID,
+                TEST_PYTH2_WORMHOLE_EMITTER
+            )
+        );
+    }
+
+    function testSetFee() public {
+        // Set fee to 5000 (5000 = 5 * 10^3)
+        bytes memory setFeeMessage = abi.encodePacked(
+            MAGIC,
+            uint8(GovernanceModule.Target),
+            uint8(GovernanceAction.SetFee),
+            TARGET_CHAIN_ID,
+            uint64(5), // value
+            uint64(3) // exponent
+        );
+
+        bytes memory vaa = encodeAndSignMessage(
+            setFeeMessage,
+            TEST_GOVERNANCE_CHAIN_ID,
+            TEST_GOVERNANCE_EMITTER,
+            1
+        );
+
+        uint oldFee = PythGetters(address(pyth)).singleUpdateFeeInWei();
+        vm.expectEmit(true, true, true, true);
+        emit FeeSet(oldFee, 5000);
+
+        PythGovernance(address(pyth)).executeGovernanceInstruction(vaa);
+        assertEq(PythGetters(address(pyth)).singleUpdateFeeInWei(), 5000);
+    }
+
+    function testSetValidPeriod() public {
+        // Create governance VAA to set valid period to 0
+        bytes memory data = abi.encodePacked(
+            MAGIC,
+            uint8(GovernanceModule.Target),
+            uint8(GovernanceAction.SetValidPeriod),
+            TARGET_CHAIN_ID, // Target chain ID
+            uint64(0) // New valid period
+        );
+
+        bytes memory vaa = encodeAndSignMessage(
+            data,
+            TEST_GOVERNANCE_CHAIN_ID,
+            TEST_GOVERNANCE_EMITTER,
+            1
+        );
+
+        uint oldValidPeriod = PythGetters(address(pyth))
+            .validTimePeriodSeconds();
+        vm.expectEmit(true, true, true, true);
+        emit ValidPeriodSet(oldValidPeriod, 0);
+
+        PythGovernance(address(pyth)).executeGovernanceInstruction(vaa);
+        assertEq(PythGetters(address(pyth)).validTimePeriodSeconds(), 0);
+    }
+
+    function testInvalidGovernanceMessage() public {
+        // Test with wrong magic number
+        bytes memory data = abi.encodePacked(
+            bytes4(0x12345678), // Wrong magic
+            uint8(GovernanceModule.Target),
+            uint8(GovernanceAction.SetValidPeriod),
+            uint16(1), // Target chain ID
+            uint64(0)
+        );
+
+        bytes memory vaa = encodeAndSignMessage(
+            data,
+            TEST_GOVERNANCE_CHAIN_ID,
+            TEST_GOVERNANCE_EMITTER,
+            1
+        );
+
+        vm.expectRevert(PythErrors.InvalidGovernanceMessage.selector);
+        PythGovernance(address(pyth)).executeGovernanceInstruction(vaa);
+    }
+
+    function testInvalidGovernanceTarget() public {
+        // Test with wrong chain target
+        bytes memory data = abi.encodePacked(
+            MAGIC,
+            uint8(GovernanceModule.Target),
+            uint8(GovernanceAction.SetValidPeriod),
+            uint16(3), // Different chain ID for testing invalid target
+            uint64(0)
+        );
+
+        bytes memory vaa = encodeAndSignMessage(
+            data,
+            TEST_GOVERNANCE_CHAIN_ID,
+            TEST_GOVERNANCE_EMITTER,
+            1
+        );
+
+        vm.expectRevert(PythErrors.InvalidGovernanceTarget.selector);
+        PythGovernance(address(pyth)).executeGovernanceInstruction(vaa);
+    }
+
+    function testInvalidGovernanceDataSource() public {
+        // Test with wrong emitter
+        bytes memory data = abi.encodePacked(
+            MAGIC,
+            uint8(GovernanceModule.Target),
+            uint8(GovernanceAction.SetValidPeriod),
+            bytes32("ethereum"),
+            uint64(0)
+        );
+
+        bytes memory vaa = encodeAndSignMessage(
+            data,
+            TEST_GOVERNANCE_CHAIN_ID,
+            bytes32(uint256(0x1111)), // Wrong emitter
+            1
+        );
+
+        vm.expectRevert(PythErrors.InvalidGovernanceDataSource.selector);
+        PythGovernance(address(pyth)).executeGovernanceInstruction(vaa);
+    }
+
+    function testSetDataSources() public {
+        // Create governance VAA to set new data sources
+        bytes memory data = abi.encodePacked(
+            MAGIC,
+            uint8(GovernanceModule.Target),
+            uint8(GovernanceAction.SetDataSources),
+            TARGET_CHAIN_ID, // Target chain ID
+            uint8(1), // Number of data sources
+            uint16(1), // Chain ID
+            bytes32(
+                0x0000000000000000000000000000000000000000000000000000000000001111
+            ) // Emitter
+        );
+
+        bytes memory vaa = encodeAndSignMessage(
+            data,
+            TEST_GOVERNANCE_CHAIN_ID,
+            TEST_GOVERNANCE_EMITTER,
+            1
+        );
+
+        PythInternalStructs.DataSource[] memory oldDataSources = PythGetters(
+            address(pyth)
+        ).validDataSources();
+
+        PythInternalStructs.DataSource[]
+            memory newDataSources = new PythInternalStructs.DataSource[](1);
+        newDataSources[0] = PythInternalStructs.DataSource(
+            1,
+            0x0000000000000000000000000000000000000000000000000000000000001111
+        );
+
+        vm.expectEmit(true, true, true, true);
+        emit DataSourcesSet(oldDataSources, newDataSources);
+
+        PythGovernance(address(pyth)).executeGovernanceInstruction(vaa);
+
+        // Verify old data source is no longer valid
+        assertFalse(
+            PythGetters(address(pyth)).isValidDataSource(
+                TEST_PYTH2_WORMHOLE_CHAIN_ID,
+                TEST_PYTH2_WORMHOLE_EMITTER
+            )
+        );
+
+        // Verify new data source is valid
+        assertTrue(
+            PythGetters(address(pyth)).isValidDataSource(
+                1,
+                0x0000000000000000000000000000000000000000000000000000000000001111
+            )
+        );
+    }
+
+    function testSetWormholeAddress() public {
+        // Deploy a new wormhole contract
+        address newWormhole = address(setUpWormholeReceiver(1));
+
+        // Create governance VAA to set new wormhole address
+        bytes memory data = abi.encodePacked(
+            MAGIC,
+            uint8(GovernanceModule.Target),
+            uint8(GovernanceAction.SetWormholeAddress),
+            TARGET_CHAIN_ID, // Target chain ID
+            newWormhole // New wormhole address
+        );
+
+        bytes memory vaa = encodeAndSignMessage(
+            data,
+            TEST_GOVERNANCE_CHAIN_ID,
+            TEST_GOVERNANCE_EMITTER,
+            1
+        );
+
+        address oldWormhole = address(PythGetters(address(pyth)).wormhole());
+        vm.expectEmit(true, true, true, true);
+        emit WormholeAddressSet(oldWormhole, newWormhole);
+
+        PythGovernance(address(pyth)).executeGovernanceInstruction(vaa);
+        assertEq(address(PythGetters(address(pyth)).wormhole()), newWormhole);
+    }
+
+    function testTransferGovernanceDataSource() public {
+        uint16 newEmitterChain = 2;
+        bytes32 newEmitterAddress = 0x0000000000000000000000000000000000000000000000000000000000001111;
+
+        // Create claim VAA from new governance
+        bytes memory claimData = abi.encodePacked(
+            MAGIC,
+            uint8(GovernanceModule.Target),
+            uint8(GovernanceAction.RequestGovernanceDataSourceTransfer),
+            TARGET_CHAIN_ID, // Target chain ID
+            uint32(1) // New governance index
+        );
+
+        bytes memory claimVaa = encodeAndSignMessage(
+            claimData,
+            newEmitterChain,
+            newEmitterAddress,
+            1
+        );
+
+        // Create authorize VAA from current governance
+        bytes memory authData = abi.encodePacked(
+            MAGIC,
+            uint8(GovernanceModule.Target),
+            uint8(GovernanceAction.AuthorizeGovernanceDataSourceTransfer),
+            TARGET_CHAIN_ID, // Target chain ID
+            claimVaa
+        );
+
+        bytes memory authVaa = encodeAndSignMessage(
+            authData,
+            TEST_GOVERNANCE_CHAIN_ID,
+            TEST_GOVERNANCE_EMITTER,
+            1
+        );
+
+        PythInternalStructs.DataSource
+            memory oldDataSource = PythInternalStructs.DataSource(
+                TEST_GOVERNANCE_CHAIN_ID,
+                TEST_GOVERNANCE_EMITTER
+            );
+        PythInternalStructs.DataSource
+            memory newDataSource = PythInternalStructs.DataSource(
+                newEmitterChain,
+                newEmitterAddress
+            );
+
+        vm.expectEmit(true, true, true, true);
+        emit GovernanceDataSourceSet(oldDataSource, newDataSource, 1);
+
+        PythGovernance(address(pyth)).executeGovernanceInstruction(authVaa);
+
+        // Verify old governance can't execute instructions anymore
+        bytes memory invalidData = abi.encodePacked(
+            MAGIC,
+            uint8(GovernanceModule.Target),
+            uint8(GovernanceAction.SetValidPeriod),
+            uint16(1), // Wrong chain ID for testing invalid target
+            uint64(0)
+        );
+
+        bytes memory invalidVaa = encodeAndSignMessage(
+            invalidData,
+            TEST_GOVERNANCE_CHAIN_ID,
+            TEST_GOVERNANCE_EMITTER,
+            2
+        );
+
+        vm.expectRevert(PythErrors.InvalidGovernanceDataSource.selector);
+        PythGovernance(address(pyth)).executeGovernanceInstruction(invalidVaa);
+    }
+
+    function testSequentialGovernanceMessages() public {
+        // First governance message
+        bytes memory data1 = abi.encodePacked(
+            MAGIC,
+            uint8(GovernanceModule.Target),
+            uint8(GovernanceAction.SetValidPeriod),
+            TARGET_CHAIN_ID, // Target chain ID
+            uint64(10)
+        );
+
+        bytes memory vaa1 = encodeAndSignMessage(
+            data1,
+            TEST_GOVERNANCE_CHAIN_ID,
+            TEST_GOVERNANCE_EMITTER,
+            1
+        );
+
+        PythGovernance(address(pyth)).executeGovernanceInstruction(vaa1);
+
+        // Second governance message
+        bytes memory data2 = abi.encodePacked(
+            MAGIC,
+            uint8(GovernanceModule.Target),
+            uint8(GovernanceAction.SetValidPeriod),
+            TARGET_CHAIN_ID, // Target chain ID
+            uint64(20)
+        );
+
+        bytes memory vaa2 = encodeAndSignMessage(
+            data2,
+            TEST_GOVERNANCE_CHAIN_ID,
+            TEST_GOVERNANCE_EMITTER,
+            2
+        );
+
+        PythGovernance(address(pyth)).executeGovernanceInstruction(vaa2);
+
+        // Try to replay first message
+        vm.expectRevert(PythErrors.OldGovernanceMessage.selector);
+        PythGovernance(address(pyth)).executeGovernanceInstruction(vaa1);
+
+        // Try to replay second message
+        vm.expectRevert(PythErrors.OldGovernanceMessage.selector);
+        PythGovernance(address(pyth)).executeGovernanceInstruction(vaa2);
+    }
+
+    function testSetTransactionFee() public {
+        // Set transaction fee to 1000 (1000 = 1 * 10^3)
+        bytes memory setTransactionFeeMessage = abi.encodePacked(
+            MAGIC,
+            uint8(GovernanceModule.Target),
+            uint8(GovernanceAction.SetTransactionFee),
+            TARGET_CHAIN_ID,
+            uint64(1), // value
+            uint64(3) // exponent
+        );
+
+        bytes memory vaa = encodeAndSignMessage(
+            setTransactionFeeMessage,
+            TEST_GOVERNANCE_CHAIN_ID,
+            TEST_GOVERNANCE_EMITTER,
+            1
+        );
+
+        uint oldFee = PythGetters(address(pyth)).transactionFeeInWei();
+        vm.expectEmit(true, true, true, true);
+        emit TransactionFeeSet(oldFee, 1000);
+
+        PythGovernance(address(pyth)).executeGovernanceInstruction(vaa);
+        assertEq(PythGetters(address(pyth)).transactionFeeInWei(), 1000);
+
+        // Test that update fee includes transaction fee
+        bytes[] memory updateData = new bytes[](0);
+        assertEq(pyth.getUpdateFee(updateData), 1000);
+
+        // Test that insufficient fee reverts
+        vm.expectRevert(PythErrors.InsufficientFee.selector);
+        pyth.updatePriceFeeds{value: 999}(updateData);
+
+        // Test that sufficient fee works
+        pyth.updatePriceFeeds{value: 1000}(updateData);
+    }
+
+    function encodeAndSignWormholeMessage(
+        bytes memory data,
+        uint16 emitterChainId,
+        bytes32 emitterAddress,
+        uint64 sequence,
+        uint8 numGuardians
+    ) internal view returns (bytes memory) {
+        return
+            generateVaa(
+                uint32(block.timestamp),
+                emitterChainId,
+                emitterAddress,
+                sequence,
+                data,
+                numGuardians
+            );
+    }
+
+    function encodeAndSignMessage(
+        bytes memory data,
+        uint16 emitterChainId,
+        bytes32 emitterAddress,
+        uint64 sequence
+    ) internal view returns (bytes memory) {
+        return
+            encodeAndSignWormholeMessage(
+                data,
+                emitterChainId,
+                emitterAddress,
+                sequence,
+                1 // Number of guardians
+            );
+    }
+
+    // Events
+    event ContractUpgraded(
+        address oldImplementation,
+        address newImplementation
+    );
+    event GovernanceDataSourceSet(
+        PythInternalStructs.DataSource oldDataSource,
+        PythInternalStructs.DataSource newDataSource,
+        uint64 initialSequence
+    );
+    event DataSourcesSet(
+        PythInternalStructs.DataSource[] oldDataSources,
+        PythInternalStructs.DataSource[] newDataSources
+    );
+    event FeeSet(uint oldFee, uint newFee);
+    event ValidPeriodSet(uint oldValidPeriod, uint newValidPeriod);
+    event WormholeAddressSet(
+        address oldWormholeAddress,
+        address newWormholeAddress
+    );
+    event TransactionFeeSet(uint oldFee, uint newFee);
+}