فهرست منبع

Add signer to pyth header

Danial Mehrjerdi 5 ماه پیش
والد
کامیت
09f25290f5

+ 12 - 14
target_chains/ethereum/contracts/contracts/pyth/Pyth.sol

@@ -104,8 +104,9 @@ abstract contract Pyth is
             ) {
                 (
                     uint offset,
-                    UpdateType updateType
-                ) = extractUpdateTypeFromAccumulatorHeader(updateData[i]);
+                    UpdateType updateType,
+
+                ) = extractAccumulatorHeaderDetails(updateData[i]);
                 if (updateType != UpdateType.WormholeMerkle) {
                     revert PythErrors.InvalidUpdateData();
                 }
@@ -135,8 +136,9 @@ abstract contract Pyth is
         ) {
             (
                 uint offset,
-                UpdateType updateType
-            ) = extractUpdateTypeFromAccumulatorHeader(updateData[0]);
+                UpdateType updateType,
+
+            ) = extractAccumulatorHeaderDetails(updateData[0]);
             if (updateType != UpdateType.WormholeMerkle) {
                 revert PythErrors.InvalidUpdateData();
             }
@@ -273,9 +275,10 @@ abstract contract Pyth is
         }
 
         uint offset;
+        Signer signer;
         {
             UpdateType updateType;
-            (offset, updateType) = extractUpdateTypeFromAccumulatorHeader(
+            (offset, updateType, signer) = extractAccumulatorHeaderDetails(
                 singleUpdateData
             );
 
@@ -293,10 +296,7 @@ abstract contract Pyth is
             merkleData.numUpdates,
             encoded,
             merkleData.slot
-        ) = extractWormholeMerkleHeaderDigestAndNumUpdatesAndEncodedAndSlotFromAccumulatorUpdate(
-            singleUpdateData,
-            offset
-        );
+        ) = extractWormholeMerkleHeader(singleUpdateData, signer, offset);
 
         // Process each update within the Merkle proof
         for (uint j = 0; j < merkleData.numUpdates; j++) {
@@ -435,12 +435,13 @@ abstract contract Pyth is
         )
     {
         UpdateType updateType;
+        Signer signer;
         uint offset;
         bytes20 digest;
         uint8 numUpdates;
         bytes calldata encoded;
         // Extract and validate the header for start data
-        (offset, updateType) = extractUpdateTypeFromAccumulatorHeader(
+        (offset, updateType, signer) = extractAccumulatorHeaderDetails(
             updateData
         );
 
@@ -455,10 +456,7 @@ abstract contract Pyth is
             encoded,
             // slot ignored
 
-        ) = extractWormholeMerkleHeaderDigestAndNumUpdatesAndEncodedAndSlotFromAccumulatorUpdate(
-            updateData,
-            offset
-        );
+        ) = extractWormholeMerkleHeader(updateData, signer, offset);
 
         // Add additional validation before extracting TWAP price info
         if (offset >= updateData.length) {

+ 45 - 28
target_chains/ethereum/contracts/contracts/pyth/PythAccumulator.sol

@@ -29,32 +29,44 @@ abstract contract PythAccumulator is PythGetters, PythSetters, AbstractPyth {
         TwapPriceFeed
     }
 
+    enum Signer {
+        Wormhole,
+        Verifier
+    }
+
     // This method is also used by batch attestation but moved here
     // as the batch attestation will deprecate soon.
     function parseAndVerifyPythVM(
-        bytes calldata encodedVm
+        bytes calldata encodedVm,
+        Signer signer
     ) internal view returns (IWormhole.VM memory vm) {
-        {
-            bool valid;
+        bool valid;
+        if (signer == Signer.Wormhole) {
             (vm, valid, ) = wormhole().parseAndVerifyVM(encodedVm);
-            if (!valid) {
-                if (address(verifier()) == address(0)) {
-                    revert PythErrors.InvalidUpdateData();
-                }
-                (vm, valid, ) = verifier().parseAndVerifyVM(encodedVm);
-                if (!valid) {
-                    revert PythErrors.InvalidUpdateData();
-                }
-            }
+        } else if (signer == Signer.Verifier) {
+            if (address(verifier()) == address(0))
+                revert PythErrors.InvalidUpdateData();
+            (vm, valid, ) = verifier().parseAndVerifyVM(encodedVm);
+        } else {
+            revert PythErrors.InvalidSigner();
         }
 
-        if (!isValidDataSource(vm.emitterChainId, vm.emitterAddress))
+        if (!valid) {
+            revert PythErrors.InvalidUpdateData();
+        }
+
+        if (!isValidDataSource(vm.emitterChainId, vm.emitterAddress)) {
             revert PythErrors.InvalidUpdateDataSource();
+        }
     }
 
-    function extractUpdateTypeFromAccumulatorHeader(
+    function extractAccumulatorHeaderDetails(
         bytes calldata accumulatorUpdate
-    ) internal pure returns (uint offset, UpdateType updateType) {
+    )
+        internal
+        pure
+        returns (uint offset, UpdateType updateType, Signer signer)
+    {
         unchecked {
             offset = 0;
 
@@ -98,13 +110,14 @@ abstract contract PythAccumulator is PythGetters, PythSetters, AbstractPyth {
                 );
                 offset += 1;
 
-                // We use another offset for the trailing header and in the end add the
-                // offset by trailingHeaderSize to skip the future headers.
-                //
-                // An example would be like this:
-                // uint trailingHeaderOffset = offset
-                // uint x = UnsafeBytesLib.ToUint8(accumulatorUpdate, trailingHeaderOffset)
-                // trailingHeaderOffset += 1
+                uint trailingHeaderOffset = offset;
+                signer = Signer(
+                    UnsafeCalldataBytesLib.toUint8(
+                        accumulatorUpdate,
+                        trailingHeaderOffset
+                    )
+                );
+                trailingHeaderOffset += 1;
 
                 offset += trailingHeaderSize;
             }
@@ -120,8 +133,9 @@ abstract contract PythAccumulator is PythGetters, PythSetters, AbstractPyth {
         }
     }
 
-    function extractWormholeMerkleHeaderDigestAndNumUpdatesAndEncodedAndSlotFromAccumulatorUpdate(
+    function extractWormholeMerkleHeader(
         bytes calldata accumulatorUpdate,
+        Signer signer,
         uint encodedOffset
     )
         internal
@@ -156,7 +170,8 @@ abstract contract PythAccumulator is PythGetters, PythSetters, AbstractPyth {
                             encoded,
                             offset,
                             whProofSize
-                        )
+                        ),
+                        signer
                     );
                     offset += whProofSize;
 
@@ -179,7 +194,7 @@ abstract contract PythAccumulator is PythGetters, PythSetters, AbstractPyth {
                     UpdateType updateType = UpdateType(
                         UnsafeBytesLib.toUint8(encodedPayload, payloadOffset)
                     );
-                    ++payloadOffset;
+                    payloadOffset += 1;
 
                     if (updateType != UpdateType.WormholeMerkle)
                         revert PythErrors.InvalidUpdateData();
@@ -468,8 +483,9 @@ abstract contract PythAccumulator is PythGetters, PythSetters, AbstractPyth {
     ) internal returns (uint8 numUpdates) {
         (
             uint encodedOffset,
-            UpdateType updateType
-        ) = extractUpdateTypeFromAccumulatorHeader(accumulatorUpdate);
+            UpdateType updateType,
+            Signer signer
+        ) = extractAccumulatorHeaderDetails(accumulatorUpdate);
 
         if (updateType != UpdateType.WormholeMerkle) {
             revert PythErrors.InvalidUpdateData();
@@ -485,8 +501,9 @@ abstract contract PythAccumulator is PythGetters, PythSetters, AbstractPyth {
             numUpdates,
             encoded,
             slot
-        ) = extractWormholeMerkleHeaderDigestAndNumUpdatesAndEncodedAndSlotFromAccumulatorUpdate(
+        ) = extractWormholeMerkleHeader(
             accumulatorUpdate,
+            signer,
             encodedOffset
         );
 

+ 1 - 1
target_chains/ethereum/contracts/forge-test/Executor.t.sol

@@ -8,7 +8,7 @@ import "../contracts/executor/ExecutorUpgradable.sol";
 import "./utils/WormholeTestUtils.t.sol";
 import "./utils/InvalidMagic.t.sol";
 
-contract ExecutorTest is Test, WormholeTestUtils {
+contract ExecutorTest is Test, AbstractWormholeTestUtils {
     Wormhole public wormhole;
     ExecutorUpgradable public executor;
     ExecutorUpgradable public executor2;

+ 6 - 4
target_chains/ethereum/contracts/forge-test/GasBenchmark.t.sol

@@ -11,7 +11,7 @@ import "@pythnetwork/pyth-sdk-solidity/PythStructs.sol";
 import "./utils/WormholeTestUtils.t.sol";
 import "./utils/PythTestUtils.t.sol";
 
-contract GasBenchmark is Test, WormholeTestUtils, PythTestUtils {
+contract GasBenchmark is Test, PythTestUtils {
     // 19, current mainnet number of guardians, is used to have gas estimates
     // close to our mainnet transactions.
     uint8 constant NUM_GUARDIANS = 19;
@@ -53,9 +53,11 @@ contract GasBenchmark is Test, WormholeTestUtils, PythTestUtils {
     uint randomSeed;
 
     function setUp() public {
-        address wormholeAddr = setUpWormholeReceiver(NUM_GUARDIANS);
-        wormhole = IWormhole(wormholeAddr);
-        pyth = IPyth(setUpPyth(wormholeAddr));
+        WormholeTestUtils wormholeTestUtils = new WormholeTestUtils(
+            NUM_GUARDIANS
+        );
+        wormhole = IWormhole(wormholeTestUtils.getWormholeReceiverAddr());
+        pyth = IPyth(setUpPyth(wormholeTestUtils));
 
         priceIds = new bytes32[](NUM_PRICES);
         priceIds[0] = bytes32(

+ 1 - 1
target_chains/ethereum/contracts/forge-test/Pyth.Aave.t.sol

@@ -24,7 +24,7 @@ contract PythAaveTest is PythWormholeMerkleAccumulatorTest {
     uint constant VALID_TIME_PERIOD_SECS = 60;
 
     function setUp() public override {
-        pyth = IPyth(setUpPyth(setUpWormholeReceiver(1)));
+        pyth = IPyth(setUpPyth(new WormholeTestUtils(1)));
         assets = new address[](NUM_PRICE_FEEDS);
         PriceFeedMessage[]
             memory priceFeedMessages = generateRandomBoundedPriceFeedMessage(

+ 19 - 16
target_chains/ethereum/contracts/forge-test/Pyth.WormholeMerkleAccumulator.t.sol

@@ -8,24 +8,20 @@ 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 "./utils/WormholeTestUtils.t.sol";
 import "./utils/PythTestUtils.t.sol";
 import "./utils/RandTestUtils.t.sol";
 
 import "../contracts/libraries/MerkleTree.sol";
+import "./utils/WormholeTestUtils.t.sol";
 
-contract PythWormholeMerkleAccumulatorTest is
-    Test,
-    WormholeTestUtils,
-    PythTestUtils
-{
+contract PythWormholeMerkleAccumulatorTest is Test, PythTestUtils {
     IPyth public pyth;
 
     // -1 is equal to 0xffffff which is the biggest uint if converted back
     uint64 constant MAX_UINT64 = uint64(int64(-1));
 
     function setUp() public virtual {
-        pyth = IPyth(setUpPyth(setUpWormholeReceiver(1)));
+        pyth = IPyth(setUpPyth(new WormholeTestUtils(1)));
     }
 
     function assertPriceFeedMessageStored(
@@ -530,15 +526,15 @@ contract PythWormholeMerkleAccumulatorTest is
         bytes memory wormholePayload;
         unchecked {
             wormholePayload = abi.encodePacked(
-                isNotMatch(forgeItem, "whMagic")
+                _wormholeTestUtils.isNotMatch(forgeItem, "whMagic")
                     ? uint32(0x41555756)
                     : uint32(0x41555750),
-                isNotMatch(forgeItem, "whUpdateType")
+                _wormholeTestUtils.isNotMatch(forgeItem, "whUpdateType")
                     ? uint8(PythAccumulator.UpdateType.WormholeMerkle)
                     : uint8(PythAccumulator.UpdateType.WormholeMerkle) + 1,
                 uint64(0), // Slot, not used in target networks
                 uint32(0), // Storage index, not used in target networks
-                isNotMatch(forgeItem, "rootDigest")
+                _wormholeTestUtils.isNotMatch(forgeItem, "rootDigest")
                     ? rootDigest
                     : bytes20(uint160(rootDigest) + 1)
             );
@@ -546,26 +542,29 @@ contract PythWormholeMerkleAccumulatorTest is
 
         bytes memory wormholeMerkleVaa = generateVaa(
             0,
-            isNotMatch(forgeItem, "whSourceChain")
+            _wormholeTestUtils.isNotMatch(forgeItem, "whSourceChain")
                 ? SOURCE_EMITTER_CHAIN_ID
                 : SOURCE_EMITTER_CHAIN_ID + 1,
-            isNotMatch(forgeItem, "whSourceAddress")
+            _wormholeTestUtils.isNotMatch(forgeItem, "whSourceAddress")
                 ? SOURCE_EMITTER_ADDRESS
                 : bytes32(
                     0x71f8dcb863d176e2c420ad6610cf687359612b6fb392e0642b0ca6b1f186aa00
                 ),
             0,
             wormholePayload,
-            1 // num signers
+            1, // num signers
+            false
         );
 
         updateData = new bytes[](1);
 
         updateData[0] = abi.encodePacked(
-            isNotMatch(forgeItem, "headerMagic")
+            _wormholeTestUtils.isNotMatch(forgeItem, "headerMagic")
                 ? uint32(0x504e4155)
                 : uint32(0x504e4150), // PythAccumulator.ACCUMULATOR_MAGIC
-            isNotMatch(forgeItem, "headerMajorVersion") ? uint8(1) : uint8(2), // major version
+            _wormholeTestUtils.isNotMatch(forgeItem, "headerMajorVersion")
+                ? uint8(1)
+                : uint8(2), // major version
             uint8(0), // minor version
             uint8(0), // trailing header size
             uint8(PythAccumulator.UpdateType.WormholeMerkle),
@@ -575,11 +574,15 @@ contract PythWormholeMerkleAccumulatorTest is
         );
 
         for (uint i = 0; i < priceFeedMessages.length; i++) {
+            bytes memory proof = proofs[0];
+            if (_wormholeTestUtils.isNotMatch(forgeItem, "proofItem")) {
+                proof = proofs[i];
+            }
             updateData[0] = abi.encodePacked(
                 updateData[0],
                 uint16(encodedPriceFeedMessages[i].length),
                 encodedPriceFeedMessages[i],
-                isNotMatch(forgeItem, "proofItem") ? proofs[i] : proofs[0]
+                proof
             );
         }
 

+ 2 - 2
target_chains/ethereum/contracts/forge-test/Pyth.t.sol

@@ -13,7 +13,7 @@ import "./utils/PythTestUtils.t.sol";
 import "./utils/RandTestUtils.t.sol";
 import "forge-std/console.sol";
 
-contract PythTest is Test, WormholeTestUtils, PythTestUtils {
+contract PythTest is Test, PythTestUtils {
     IPyth public pyth;
 
     // -1 is equal to 0xffffff which is the biggest uint if converted back
@@ -32,7 +32,7 @@ contract PythTest is Test, WormholeTestUtils, PythTestUtils {
     bytes32[2] basePriceIds;
 
     function setUp() public {
-        pyth = IPyth(setUpPyth(setUpWormholeReceiver(NUM_GUARDIAN_SIGNERS)));
+        pyth = IPyth(setUpPyth(new WormholeTestUtils(NUM_GUARDIAN_SIGNERS)));
 
         // Initialize base TWAP messages for two price feeds
         basePriceIds[0] = bytes32(uint256(1));

+ 8 - 10
target_chains/ethereum/contracts/forge-test/PythGovernance.t.sol

@@ -33,12 +33,7 @@ import "./utils/WormholeTestUtils.t.sol";
 import "./utils/PythTestUtils.t.sol";
 import "./utils/RandTestUtils.t.sol";
 
-contract PythGovernanceTest is
-    Test,
-    WormholeTestUtils,
-    PythTestUtils,
-    PythGovernanceInstructions
-{
+contract PythGovernanceTest is Test, PythTestUtils, PythGovernanceInstructions {
     using BytesLib for bytes;
 
     IPyth public pyth;
@@ -53,7 +48,7 @@ contract PythGovernanceTest is
     uint16 constant TARGET_CHAIN_ID = 2;
 
     function setUp() public {
-        pyth = IPyth(setUpPyth(setUpWormholeReceiver(1)));
+        pyth = IPyth(setUpPyth(new WormholeTestUtils(1)));
     }
 
     function testNoOwner() public {
@@ -241,7 +236,8 @@ contract PythGovernanceTest is
 
     function testSetWormholeAddress() public {
         // Deploy a new wormhole contract
-        address newWormhole = address(setUpWormholeReceiver(1));
+        address newWormhole = new WormholeTestUtils(1)
+            .getWormholeReceiverAddr();
 
         // Create governance VAA to set new wormhole address
         bytes memory data = abi.encodePacked(
@@ -269,7 +265,8 @@ contract PythGovernanceTest is
 
     function testSetVerifierAddress() public {
         // Deploy a new verifier contract
-        address newVerifier = address(setUpWormholeReceiver(1));
+        address newVerifier = new WormholeTestUtils(3)
+            .getWormholeReceiverAddr();
 
         // Create governance VAA to set new verifier address
         bytes memory data = abi.encodePacked(
@@ -692,7 +689,8 @@ contract PythGovernanceTest is
                 emitterAddress,
                 sequence,
                 data,
-                numGuardians
+                numGuardians,
+                false
             );
     }
 

+ 11 - 7
target_chains/ethereum/contracts/forge-test/VerificationExperiments.t.sol

@@ -16,7 +16,7 @@ import "./utils/PythTestUtils.t.sol";
 import "./utils/RandTestUtils.t.sol";
 
 // Experiments to measure the gas usage of different ways of verifying prices in the EVM contract.
-contract VerificationExperiments is Test, WormholeTestUtils, PythTestUtils {
+contract VerificationExperiments is Test, PythTestUtils {
     // 19, current mainnet number of guardians, is used to have gas estimates
     // close to our mainnet transactions.
     uint8 constant NUM_GUARDIANS = 19;
@@ -65,8 +65,15 @@ contract VerificationExperiments is Test, WormholeTestUtils, PythTestUtils {
     uint64 sequence;
 
     function setUp() public {
+        WormholeTestUtils wormholeTestUtils = new WormholeTestUtils(
+            NUM_GUARDIAN_SIGNERS
+        );
+
+        // Just set it up for generating the VAA signatures.
+        setUpPyth(wormholeTestUtils);
+
         address payable wormhole = payable(
-            setUpWormholeReceiver(NUM_GUARDIANS)
+            wormholeTestUtils.getWormholeReceiverAddr()
         );
 
         // Deploy experimental contract
@@ -93,7 +100,6 @@ contract VerificationExperiments is Test, WormholeTestUtils, PythTestUtils {
             60, // Valid time period in seconds
             1 // single update fee in wei
         );
-
         priceIds = new bytes32[](NUM_PRICES);
         priceIds[0] = bytes32(
             0x1000000000000000000000000000000000000000000000000000000000000f00
@@ -127,7 +133,6 @@ contract VerificationExperiments is Test, WormholeTestUtils, PythTestUtils {
             );
             freshPricesPublishTimes.push(publishTime);
         }
-
         // Populate the contract with the initial prices
         (
             cachedPricesUpdateData,
@@ -144,7 +149,6 @@ contract VerificationExperiments is Test, WormholeTestUtils, PythTestUtils {
         ) = generateWormholeUpdateDataAndFee(freshPrices);
 
         // Generate the update payloads for the various verification systems
-
         whMerkleUpdateDepth0 = generateSingleWhMerkleUpdate(
             priceIds[0],
             freshPrices[0],
@@ -176,7 +180,6 @@ contract VerificationExperiments is Test, WormholeTestUtils, PythTestUtils {
             freshPrices[0],
             8
         );
-
         thresholdUpdate = generateThresholdUpdate(priceIds[0], freshPrices[0]);
 
         nativeUpdate = generateMessagePayload(priceIds[0], freshPrices[0]);
@@ -265,7 +268,8 @@ contract VerificationExperiments is Test, WormholeTestUtils, PythTestUtils {
             PythTestUtils.SOURCE_EMITTER_ADDRESS,
             sequence,
             bytes.concat(root),
-            NUM_GUARDIAN_SIGNERS
+            NUM_GUARDIAN_SIGNERS,
+            false
         );
 
         ++sequence;

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

@@ -15,10 +15,10 @@ import "@pythnetwork/pyth-sdk-solidity/IPyth.sol";
 import "@pythnetwork/pyth-sdk-solidity/PythUtils.sol";
 
 import "forge-std/Test.sol";
-import "./WormholeTestUtils.t.sol";
 import "./RandTestUtils.t.sol";
+import "./WormholeTestUtils.t.sol";
 
-abstract contract PythTestUtils is Test, WormholeTestUtils, RandTestUtils {
+abstract contract PythTestUtils is Test, RandTestUtils {
     uint16 constant SOURCE_EMITTER_CHAIN_ID = 0x1;
     bytes32 constant SOURCE_EMITTER_ADDRESS =
         0x71f8dcb863d176e2c420ad6610cf687359612b6fb392e0642b0ca6b1f186aa3b;
@@ -28,7 +28,11 @@ abstract contract PythTestUtils is Test, WormholeTestUtils, RandTestUtils {
         0x0000000000000000000000000000000000000000000000000000000000000011;
     uint constant SINGLE_UPDATE_FEE_IN_WEI = 1;
 
-    function setUpPyth(address wormhole) public returns (address) {
+    WormholeTestUtils _wormholeTestUtils;
+
+    function setUpPyth(
+        WormholeTestUtils wormholeTestUtils
+    ) public returns (address) {
         PythUpgradable implementation = new PythUpgradable();
         ERC1967Proxy proxy = new ERC1967Proxy(
             address(implementation),
@@ -43,7 +47,7 @@ abstract contract PythTestUtils is Test, WormholeTestUtils, RandTestUtils {
         emitterAddresses[0] = SOURCE_EMITTER_ADDRESS;
 
         pyth.initialize(
-            wormhole,
+            wormholeTestUtils.getWormholeReceiverAddr(),
             emitterChainIds,
             emitterAddresses,
             GOVERNANCE_EMITTER_CHAIN_ID,
@@ -53,9 +57,33 @@ abstract contract PythTestUtils is Test, WormholeTestUtils, RandTestUtils {
             SINGLE_UPDATE_FEE_IN_WEI // single update fee in wei
         );
 
+        _wormholeTestUtils = wormholeTestUtils;
+
         return address(pyth);
     }
 
+    function generateVaa(
+        uint32 timestamp,
+        uint16 emitterChainId,
+        bytes32 emitterAddress,
+        uint64 sequence,
+        bytes memory payload,
+        uint8 numSigners,
+        bool verifier
+    ) internal view returns (bytes memory vaa) {
+        if (!verifier)
+            return
+                _wormholeTestUtils.generateVaa(
+                    timestamp,
+                    emitterChainId,
+                    emitterAddress,
+                    sequence,
+                    payload,
+                    numSigners
+                );
+        revert PythErrors.InvalidSigner();
+    }
+
     function singleUpdateFeeInWei() public pure returns (uint) {
         return SINGLE_UPDATE_FEE_IN_WEI;
     }
@@ -159,7 +187,8 @@ abstract contract PythTestUtils is Test, WormholeTestUtils, RandTestUtils {
             config.source_emitter_address,
             0,
             wormholePayload,
-            config.numSigners
+            config.numSigners,
+            false
         );
 
         if (config.brokenVaa) {
@@ -218,7 +247,8 @@ abstract contract PythTestUtils is Test, WormholeTestUtils, RandTestUtils {
             config.source_emitter_address,
             0,
             wormholePayload,
-            config.numSigners
+            config.numSigners,
+            false
         );
 
         if (config.brokenVaa) {
@@ -343,7 +373,8 @@ abstract contract PythTestUtils is Test, WormholeTestUtils, RandTestUtils {
                 rootDigest, // this can have bytes past this for future versions
                 futureData
             ),
-            numSigners
+            numSigners,
+            false
         );
     }
 
@@ -369,7 +400,7 @@ abstract contract PythTestUtils is Test, WormholeTestUtils, RandTestUtils {
     }
 }
 
-contract PythUtilsTest is Test, WormholeTestUtils, PythTestUtils, IPythEvents {
+contract PythUtilsTest is Test, PythTestUtils, IPythEvents {
     function testConvertToUnit() public {
         // Price can't be negative
         vm.expectRevert();

+ 11 - 28
target_chains/ethereum/contracts/forge-test/utils/WormholeTestUtils.t.sol

@@ -14,7 +14,7 @@ import "../../contracts/wormhole-receiver/ReceiverGovernanceStructs.sol";
 
 import "forge-std/Test.sol";
 
-abstract contract WormholeTestUtils is Test {
+abstract contract AbstractWormholeTestUtils is Test {
     uint256[] currentSigners;
     address wormholeReceiverAddr;
     uint16 constant CHAIN_ID = 2; // Ethereum
@@ -22,36 +22,13 @@ abstract contract WormholeTestUtils is Test {
     bytes32 constant GOVERNANCE_CONTRACT =
         0x0000000000000000000000000000000000000000000000000000000000000004;
 
-    function setUpWormhole(uint8 numGuardians) public returns (address) {
-        Implementation wormholeImpl = new Implementation();
-        Setup wormholeSetup = new Setup();
-
-        Wormhole wormhole = new Wormhole(address(wormholeSetup), new bytes(0));
-
-        address[] memory initSigners = new address[](numGuardians);
-        currentSigners = new uint256[](numGuardians);
-
-        for (uint256 i = 0; i < numGuardians; ++i) {
-            currentSigners[i] = i + 1;
-            initSigners[i] = vm.addr(currentSigners[i]); // i+1 is the private key for the i-th signer.
-        }
-
-        // These values are the default values used in our tilt test environment
-        // and are not important.
-        Setup(address(wormhole)).setup(
-            address(wormholeImpl),
-            initSigners,
-            CHAIN_ID, // Ethereum chain ID
-            GOVERNANCE_CHAIN_ID, // Governance source chain ID (1 = solana)
-            GOVERNANCE_CONTRACT // Governance source address
-        );
-
-        return address(wormhole);
+    function getWormholeReceiverAddr() public view returns (address) {
+        return wormholeReceiverAddr;
     }
 
     function setUpWormholeReceiver(
         uint8 numGuardians
-    ) public returns (address) {
+    ) internal returns (address) {
         ReceiverImplementation wormholeReceiverImpl = new ReceiverImplementation();
         ReceiverSetup wormholeReceiverSetup = new ReceiverSetup();
 
@@ -228,7 +205,7 @@ abstract contract WormholeTestUtils is Test {
     }
 }
 
-contract WormholeTestUtilsTest is Test, WormholeTestUtils {
+contract WormholeTestUtilsTest is AbstractWormholeTestUtils {
     uint32 constant TEST_VAA_TIMESTAMP = 112;
     uint16 constant TEST_EMITTER_CHAIN_ID = 7;
     bytes32 constant TEST_EMITTER_ADDR =
@@ -487,3 +464,9 @@ contract WormholeTestUtilsTest is Test, WormholeTestUtils {
         assertEq(reason, "VM signature invalid");
     }
 }
+
+contract WormholeTestUtils is AbstractWormholeTestUtils {
+    constructor(uint8 numGuardians) {
+        setUpWormholeReceiver(numGuardians);
+    }
+}

+ 3 - 0
target_chains/ethereum/sdk/solidity/PythErrors.sol

@@ -52,4 +52,7 @@ library PythErrors {
     // The verifier address to set in SetVerifierAddress governance is invalid.
     // Signature: 0xab8af376
     error InvalidVerifierAddressToSet();
+    // The signer of the message is invalid.
+    // Signature: 0x815e1d64
+    error InvalidSigner();
 }

+ 5 - 0
target_chains/ethereum/sdk/solidity/abis/PythErrors.json

@@ -24,6 +24,11 @@
     "name": "InvalidGovernanceTarget",
     "type": "error"
   },
+  {
+    "inputs": [],
+    "name": "InvalidSigner",
+    "type": "error"
+  },
   {
     "inputs": [],
     "name": "InvalidTwapUpdateData",