浏览代码

Implement the new IPyth events and methods (#241)

* Implement the new IPyth events and methods

* Check contracts gets the fees in tests

* Fix the fee variable position to avoid conflict

* Add migration files.

* Add events to migration names and docs

* Fix pyth contract address in tests

Very strangely after adding migration steps the old address
used in evm relay became wormhole implementation address
I looked at eth-0/tests container logs and saw the
address is entirely different (before and after the change)

* Fix previous migration bug.

* rename instance to proxy to be more clear

* Update Deploying.md
Ali Behjati 3 年之前
父节点
当前提交
5320c58cf6

+ 1 - 1
devnet/p2w-evm-relay.yaml

@@ -64,7 +64,7 @@ spec:
             - name: EVM_HDWALLET_PATH
               value: "m/44'/60'/0'/0/1" # Use account with idx 1
             - name: EVM_PYTH_CONTRACT_ADDRESS
-              value: "0xDb56f2e9369E0D7bD191099125a3f6C370F8ed15"
+              value: "0xe982E462b094850F12AF94d21D470e21bE9D0E9C"
             - name: EVM_VERIFY_PRICE_FEEDS
               value: "yes"
             - name: REST_PORT

+ 1 - 1
devnet/pyth-evm-watcher.yaml

@@ -24,4 +24,4 @@ spec:
             - name: WS_ENDPOINT
               value: 'ws://eth-devnet:8545'
             - name: PYTH_CONTRACT
-              value: '0xDb56f2e9369E0D7bD191099125a3f6C370F8ed15'
+              value: '0xe982E462b094850F12AF94d21D470e21bE9D0E9C'

+ 3 - 3
ethereum/Deploying.md

@@ -48,7 +48,7 @@ for more information.
 Changes to the files in this directory should be commited as well.
 
 # Upgrading the contract
-To upgrade the contract you should add a new migration file in the `migrations/prod` directory increasing the migration number.
+To upgrade the contract you should add a new migration file in the `migrations/*` directories increasing the migration number.
 
 It looks like so:
 
@@ -63,8 +63,8 @@ const { upgradeProxy } = require("@openzeppelin/truffle-upgrades");
  * Briefly describe the changelog here.
  */
 module.exports = async function (deployer) {
-    const instance = await PythUpgradable.deployed();
-    await upgradeProxy(instance.address, PythUpgradable, { deployer });
+    const proxy = await PythUpgradable.deployed();
+    await upgradeProxy(proxy.address, PythUpgradable, { deployer });
 }
 ```
 

+ 22 - 3
ethereum/contracts/pyth/Pyth.sol

@@ -24,7 +24,7 @@ abstract contract Pyth is PythGetters, PythSetters, AbstractPyth {
         setPyth2WormholeEmitter(pyth2WormholeEmitter);
     }
 
-    function updatePriceBatchFromVm(bytes memory encodedVm) public returns (PythInternalStructs.BatchPriceAttestation memory bpa) {
+    function updatePriceBatchFromVm(bytes memory encodedVm) private returns (PythInternalStructs.BatchPriceAttestation memory bpa) {
         (IWormhole.VM memory vm, bool valid, string memory reason) = wormhole().parseAndVerifyVM(encodedVm);
 
         require(valid, reason);
@@ -32,24 +32,43 @@ abstract contract Pyth is PythGetters, PythSetters, AbstractPyth {
 
         PythInternalStructs.BatchPriceAttestation memory batch = parseBatchPriceAttestation(vm.payload);
 
+        uint freshPrices = 0;
+
         for (uint i = 0; i < batch.attestations.length; i++) {
             PythInternalStructs.PriceAttestation memory attestation = batch.attestations[i];
 
             PythInternalStructs.PriceInfo memory latestPrice = latestPriceInfo(attestation.priceId);
 
+            bool fresh = false;
             if(attestation.attestationTime > latestPrice.attestationTime) {
+                freshPrices += 1;
+                fresh = true;
                 setLatestPriceInfo(attestation.priceId, newPriceInfo(attestation));
-                emit PriceUpdate(attestation.priceId, attestation.publishTime);
             }
+
+            emit PriceFeedUpdate(attestation.priceId, fresh, vm.emitterChainId, vm.sequence, latestPrice.priceFeed.publishTime,
+                attestation.publishTime, attestation.price, attestation.conf);
         }
 
+        emit BatchPriceFeedUpdate(vm.emitterChainId, vm.sequence, batch.attestations.length, freshPrices);
+
         return batch;
     }
 
-    function updatePriceFeeds(bytes[] calldata updateData) public override {
+    function updatePriceFeeds(bytes[] calldata updateData) public override payable {
+        uint requiredFee = getUpdateFee(updateData.length);
+        require(msg.value >= requiredFee, "Insufficient paid fee amount");
+        payable(msg.sender).transfer(msg.value - requiredFee);
+ 
         for(uint i = 0; i < updateData.length; i++) {
             updatePriceBatchFromVm(updateData[i]);
         }
+
+        emit UpdatePriceFeeds(msg.sender, updateData.length, requiredFee);
+    }
+
+    function getUpdateFee(uint updateDataSize) public override view returns (uint feeAmount) {
+        return singleUpdateFeeInWei() * updateDataSize;
     }
 
     function newPriceInfo(PythInternalStructs.PriceAttestation memory pa) private view returns (PythInternalStructs.PriceInfo memory info) {

+ 5 - 0
ethereum/contracts/pyth/PythGetters.sol

@@ -34,4 +34,9 @@ contract PythGetters is PythState {
     function validDataSources() public view returns (PythInternalStructs.DataSource[] memory) {
         return _state.validDataSources;
     }
+
+
+    function singleUpdateFeeInWei() public view returns (uint) {
+        return _state.singleUpdateFeeInWei;
+    }
 }

+ 4 - 0
ethereum/contracts/pyth/PythSetters.sol

@@ -21,4 +21,8 @@ contract PythSetters is PythState {
     function setLatestPriceInfo(bytes32 priceId, PythInternalStructs.PriceInfo memory info) internal {
         _state.latestPriceInfo[priceId] = info;
     }
+
+    function setSingleUpdateFeeInWei(uint fee) internal {
+        _state.singleUpdateFeeInWei = fee;
+    }
 }

+ 2 - 0
ethereum/contracts/pyth/PythState.sol

@@ -21,6 +21,8 @@ contract PythStorage {
         // (chainId, emitterAddress) => isValid; takes advantage of
         // constant-time mapping lookup for VM verification
         mapping(bytes32 => bool) isValidDataSource;
+
+        uint singleUpdateFeeInWei;
     }
 }
 

+ 5 - 0
ethereum/contracts/pyth/PythUpgradable.sol

@@ -55,6 +55,11 @@ contract PythUpgradable is Initializable, OwnableUpgradeable, UUPSUpgradeable, P
         }
     }
 
+    /// Privileged function to update the price update fee
+    function updateSingleUpdateFeeInWei(uint newFee) onlyOwner public {
+        PythSetters.setSingleUpdateFeeInWei(newFee);
+    }
+
     /// Ensures the contract cannot be uninitialized and taken over.
     /// @custom:oz-upgrades-unsafe-allow constructor
     constructor() initializer {}

+ 5 - 4
ethereum/migrations/prod-receiver/4_pyth_multiple_emitters.js

@@ -8,9 +8,10 @@ const { upgradeProxy } = require("@openzeppelin/truffle-upgrades");
  * Adds multiple emitter/chain ID support
  */
 module.exports = async function (deployer) {
-    const instance = await PythUpgradable.deployed();
-    await instance.addDataSource(
-        instance.pyth2WormholeChainId(),
-        instance.pyth2WormholeEmitter()
+    const proxy = await PythUpgradable.deployed();
+    await upgradeProxy(proxy.address, PythUpgradable, { deployer });
+    await proxy.addDataSource(
+        proxy.pyth2WormholeChainId(),
+        proxy.pyth2WormholeEmitter()
     );
 };

+ 16 - 0
ethereum/migrations/prod-receiver/5_pyth_add_events_and_update_fee.js

@@ -0,0 +1,16 @@
+require('dotenv').config({ path: "../.env" });
+
+const PythUpgradable = artifacts.require("PythUpgradable");
+
+const { upgradeProxy } = require("@openzeppelin/truffle-upgrades");
+
+/**
+ * This change:
+ * - Adds fee for updating prices. Default value in Ethereum is 0, so the value after upgrade will be 0.
+ * - Emits multiple events when a price gets updated. This can be used by off-chain applications to monitor
+ *   the contract activity.
+ */
+module.exports = async function (deployer) {
+    const proxy = await PythUpgradable.deployed();
+    await upgradeProxy(proxy.address, PythUpgradable, { deployer });
+}

+ 5 - 4
ethereum/migrations/prod/3_pyth_multiple_emitters.js

@@ -8,9 +8,10 @@ const { upgradeProxy } = require("@openzeppelin/truffle-upgrades");
  * Adds multiple emitter/chain ID support
  */
 module.exports = async function (deployer) {
-    const instance = await PythUpgradable.deployed();
-    await instance.addDataSource(
-        instance.pyth2WormholeChainId(),
-        instance.pyth2WormholeEmitter()
+    const proxy = await PythUpgradable.deployed();
+    await upgradeProxy(proxy.address, PythUpgradable, { deployer });
+    await proxy.addDataSource(
+        proxy.pyth2WormholeChainId(),
+        proxy.pyth2WormholeEmitter()
     );
 };

+ 16 - 0
ethereum/migrations/prod/4_pyth_add_events_and_update_fee.js

@@ -0,0 +1,16 @@
+require('dotenv').config({ path: "../.env" });
+
+const PythUpgradable = artifacts.require("PythUpgradable");
+
+const { upgradeProxy } = require("@openzeppelin/truffle-upgrades");
+
+/**
+ * This change:
+ * - Adds fee for updating prices. Default value in Ethereum is 0, so the value after upgrade will be 0.
+ * - Emits multiple events when a price gets updated. This can be used by off-chain applications to monitor
+ *   the contract activity.
+ */
+module.exports = async function (deployer) {
+    const instance = await PythUpgradable.deployed();
+    await upgradeProxy(instance.address, PythUpgradable, { deployer });
+}

+ 5 - 4
ethereum/migrations/test/4_pyth_multiple_emitters.js

@@ -8,9 +8,10 @@ const { upgradeProxy } = require("@openzeppelin/truffle-upgrades");
  * Adds multiple emitter/chain ID support
  */
 module.exports = async function (deployer) {
-    const instance = await PythUpgradable.deployed();
-    await instance.addDataSource(
-        await instance.pyth2WormholeChainId(),
-        await instance.pyth2WormholeEmitter()
+    const proxy = await PythUpgradable.deployed();
+    await upgradeProxy(proxy.address, PythUpgradable, { deployer });
+    await proxy.addDataSource(
+        await proxy.pyth2WormholeChainId(),
+        await proxy.pyth2WormholeEmitter()
     );
 };

+ 16 - 0
ethereum/migrations/test/5_pyth_add_events_and_update_fee.js

@@ -0,0 +1,16 @@
+require('dotenv').config({ path: "../.env" });
+
+const PythUpgradable = artifacts.require("PythUpgradable");
+
+const { upgradeProxy } = require("@openzeppelin/truffle-upgrades");
+
+/**
+ * This change:
+ * - Adds fee for updating prices. Default value in Ethereum is 0, so the value after upgrade will be 0.
+ * - Emits multiple events when a price gets updated. This can be used by off-chain applications to monitor
+ *   the contract activity.
+ */
+module.exports = async function (deployer) {
+    const proxy = await PythUpgradable.deployed();
+    await upgradeProxy(proxy.address, PythUpgradable, { deployer });
+}

+ 7 - 7
ethereum/package-lock.json

@@ -11,7 +11,7 @@
       "dependencies": {
         "@openzeppelin/contracts": "^4.5.0",
         "@openzeppelin/contracts-upgradeable": "^4.5.2",
-        "@pythnetwork/pyth-sdk-solidity": "^0.2.0",
+        "@pythnetwork/pyth-sdk-solidity": "^0.4.0",
         "dotenv": "^10.0.0",
         "elliptic": "^6.5.2",
         "ganache-cli": "^6.12.1",
@@ -3674,9 +3674,9 @@
       "dev": true
     },
     "node_modules/@pythnetwork/pyth-sdk-solidity": {
-      "version": "0.2.0",
-      "resolved": "https://registry.npmjs.org/@pythnetwork/pyth-sdk-solidity/-/pyth-sdk-solidity-0.2.0.tgz",
-      "integrity": "sha512-kb3pun9wRVRtRBwKakaaMdtIVHw45xbvBH9j1Fq7yJco52AdBGNHK78c7cJv5Q2JZtE0x/6yZT1p75b98M4F9w=="
+      "version": "0.4.0",
+      "resolved": "https://registry.npmjs.org/@pythnetwork/pyth-sdk-solidity/-/pyth-sdk-solidity-0.4.0.tgz",
+      "integrity": "sha512-5WTLIajhYGLVxbwxWEp0BoklBVZ5y3+dooc+L2ipilDw632z7cHu79Cn8gGGNN3UstxuYEVNkQuJ+HGaxFiEwQ=="
     },
     "node_modules/@redux-saga/core": {
       "version": "1.1.3",
@@ -39998,9 +39998,9 @@
       "dev": true
     },
     "@pythnetwork/pyth-sdk-solidity": {
-      "version": "0.2.0",
-      "resolved": "https://registry.npmjs.org/@pythnetwork/pyth-sdk-solidity/-/pyth-sdk-solidity-0.2.0.tgz",
-      "integrity": "sha512-kb3pun9wRVRtRBwKakaaMdtIVHw45xbvBH9j1Fq7yJco52AdBGNHK78c7cJv5Q2JZtE0x/6yZT1p75b98M4F9w=="
+      "version": "0.4.0",
+      "resolved": "https://registry.npmjs.org/@pythnetwork/pyth-sdk-solidity/-/pyth-sdk-solidity-0.4.0.tgz",
+      "integrity": "sha512-5WTLIajhYGLVxbwxWEp0BoklBVZ5y3+dooc+L2ipilDw632z7cHu79Cn8gGGNN3UstxuYEVNkQuJ+HGaxFiEwQ=="
     },
     "@redux-saga/core": {
       "version": "1.1.3",

+ 1 - 1
ethereum/package.json

@@ -30,7 +30,7 @@
   "dependencies": {
     "@openzeppelin/contracts": "^4.5.0",
     "@openzeppelin/contracts-upgradeable": "^4.5.2",
-    "@pythnetwork/pyth-sdk-solidity": "^0.2.0",
+    "@pythnetwork/pyth-sdk-solidity": "^0.4.0",
     "dotenv": "^10.0.0",
     "elliptic": "^6.5.2",
     "ganache-cli": "^6.12.1",

+ 188 - 8
ethereum/test/pyth.js

@@ -6,6 +6,7 @@ const PythStructs = artifacts.require("PythStructs");
 
 const { deployProxy, upgradeProxy } = require("@openzeppelin/truffle-upgrades");
 const { expectRevert, expectEvent } = require("@openzeppelin/test-helpers");
+const { assert } = require("chai");
 
 // Use "WormholeReceiver" if you are testing with Wormhole Receiver
 const Wormhole = artifacts.require("Wormhole");
@@ -29,6 +30,8 @@ contract("Pyth", function () {
         "0x71f8dcb863d176e2c420ad6610cf687359612b6fb392e0642b0ca6b1f186aa3b";
     const notOwnerError =
         "Ownable: caller is not the owner -- Reason given: Ownable: caller is not the owner.";
+    const insufficientFeeError = 
+        "Insufficient paid fee amount";
 
     beforeEach(async function () {
         this.pythProxy = await deployProxy(PythUpgradable, [
@@ -132,6 +135,38 @@ contract("Pyth", function () {
         );
     });
 
+    it("should allow updating singleUpdateFeeInWei by owner", async function () {
+        // Check that the owner is the default account Truffle
+        // has configured for the network.
+        const accounts = await web3.eth.getAccounts();
+        const defaultAccount = accounts[0];
+        assert.equal(await this.pythProxy.owner(), defaultAccount);
+
+        // Check initial fee is zero
+        assert.equal(await this.pythProxy.singleUpdateFeeInWei(), 0);
+
+        // Set fee 
+        await this.pythProxy.updateSingleUpdateFeeInWei(10);
+        assert.equal(await this.pythProxy.singleUpdateFeeInWei(), 10);
+    });
+
+    it("should not allow updating singleUpdateFeeInWei by another account", async function () {
+        // Check that the owner is the default account Truffle
+        // has configured for the network.
+        const accounts = await web3.eth.getAccounts();
+        const defaultAccount = accounts[0];
+        assert.equal(await this.pythProxy.owner(), defaultAccount);
+
+        // Check initial fee is zero
+        assert.equal(await this.pythProxy.singleUpdateFeeInWei(), 0);
+
+        // Checks setting fee using another account reverts.
+        await expectRevert(
+            this.pythProxy.updateSingleUpdateFeeInWei(10, {from: accounts[1]}),
+            notOwnerError,
+        );
+    });
+
     // NOTE(2022-05-02): Raw hex payload obtained from format serialization unit tests in `p2w-sdk/rust`
     // Latest known addition: wire format v3
     //
@@ -233,7 +268,7 @@ contract("Pyth", function () {
         }
     });
 
-    async function updatePriceFeeds(contract, batches) {
+    async function updatePriceFeeds(contract, batches, valueInWei) {
         let updateData = [];
         for (let data of batches) {
             const vm = await signAndEncodeVM(
@@ -249,7 +284,7 @@ contract("Pyth", function () {
             );
             updateData.push("0x" + vm);
         }
-        return await contract.updatePriceFeeds(updateData);
+        return await contract.updatePriceFeeds(updateData, {value: valueInWei});
     }
 
     it("should attest price updates over wormhole", async function () {
@@ -260,7 +295,11 @@ contract("Pyth", function () {
 
     it("should attest price updates empty", async function () {
         const receipt = await updatePriceFeeds(this.pythProxy, []);
-        expectEvent.notEmitted(receipt, 'PriceUpdate');
+        expectEvent.notEmitted(receipt, 'PriceFeedUpdate');
+        expectEvent.notEmitted(receipt, 'BatchPriceFeedUpdate');
+        expectEvent(receipt, 'UpdatePriceFeeds', {
+            batchCount: '0',
+        });
     });
 
     it("should attest price updates with multiple batches of correct order", async function () {
@@ -268,7 +307,16 @@ contract("Pyth", function () {
         let rawBatch1 = generateRawBatchAttestation(ts - 5, ts, 1337);
         let rawBatch2 = generateRawBatchAttestation(ts + 5, ts + 10, 1338);
         const receipt = await updatePriceFeeds(this.pythProxy, [rawBatch1, rawBatch2]);
-        expectEvent(receipt, 'PriceUpdate');
+        expectEvent(receipt, 'PriceFeedUpdate', {
+            fresh: true,
+        });
+        expectEvent(receipt, 'BatchPriceFeedUpdate', {
+            batchSize: '10',
+            freshPricesInBatch: '10',
+        });
+        expectEvent(receipt, 'UpdatePriceFeeds', {
+            batchCount: '2',
+        });
     });
 
     it("should attest price updates with multiple batches of wrong order", async function () {
@@ -276,7 +324,112 @@ contract("Pyth", function () {
         let rawBatch1 = generateRawBatchAttestation(ts - 5, ts, 1337);
         let rawBatch2 = generateRawBatchAttestation(ts + 5, ts + 10, 1338);
         const receipt = await updatePriceFeeds(this.pythProxy, [rawBatch2, rawBatch1]);
-        expectEvent(receipt, 'PriceUpdate');
+        expectEvent(receipt, 'PriceFeedUpdate', {
+            fresh: true,
+        });
+        expectEvent(receipt, 'PriceFeedUpdate', {
+            fresh: false,
+        });
+        expectEvent(receipt, 'BatchPriceFeedUpdate', {
+            batchSize: '10',
+            freshPricesInBatch: '10',
+        });
+        expectEvent(receipt, 'BatchPriceFeedUpdate', {
+            batchSize: '10',
+            freshPricesInBatch: '0',
+        });
+        expectEvent(receipt, 'UpdatePriceFeeds', {
+            batchCount: '2',
+        });
+    });
+
+    it("should not attest price updates with when required fee is not given", async function () {
+        // Check that the owner is the default account Truffle
+        // has configured for the network.
+        const accounts = await web3.eth.getAccounts();
+        const defaultAccount = accounts[0];
+        assert.equal(await this.pythProxy.owner(), defaultAccount);
+
+        // Check initial fee is zero
+        assert.equal(await this.pythProxy.singleUpdateFeeInWei(), 0);
+
+        // Set fee 
+        await this.pythProxy.updateSingleUpdateFeeInWei(10);
+        assert.equal(await this.pythProxy.singleUpdateFeeInWei(), 10);
+
+        let ts = 1647273460;
+        let rawBatch1 = generateRawBatchAttestation(ts - 5, ts, 1337);
+        let rawBatch2 = generateRawBatchAttestation(ts + 5, ts + 10, 1338);
+
+        // Getting the fee from the contract
+        let feeInWei = await this.pythProxy.getUpdateFee(2);
+        assert.equal(feeInWei, 20);
+
+        // When a smaller fee is payed it reverts
+        await expectRevert(
+            updatePriceFeeds(this.pythProxy, [rawBatch1, rawBatch2], feeInWei - 1), 
+            insufficientFeeError
+        );
+    });
+
+    it("should attest price updates with when required fee is given", async function () {
+        // Check that the owner is the default account Truffle
+        // has configured for the network.
+        const accounts = await web3.eth.getAccounts();
+        const defaultAccount = accounts[0];
+        assert.equal(await this.pythProxy.owner(), defaultAccount);
+
+        // Check initial fee is zero
+        assert.equal(await this.pythProxy.singleUpdateFeeInWei(), 0);
+
+        // Set fee 
+        await this.pythProxy.updateSingleUpdateFeeInWei(10);
+        assert.equal(await this.pythProxy.singleUpdateFeeInWei(), 10);
+
+        let ts = 1647273460;
+        let rawBatch1 = generateRawBatchAttestation(ts - 5, ts, 1337);
+        let rawBatch2 = generateRawBatchAttestation(ts + 5, ts + 10, 1338);
+
+        // Getting the fee from the contract
+        let feeInWei = await this.pythProxy.getUpdateFee(2);
+        assert.equal(feeInWei, 20);
+
+        const receipt = await updatePriceFeeds(this.pythProxy, [rawBatch1, rawBatch2], feeInWei);
+        expectEvent(receipt, 'UpdatePriceFeeds', {
+            fee: feeInWei
+        });
+        const pythBalance = await web3.eth.getBalance(this.pythProxy.address);
+        assert.equal(pythBalance, feeInWei);
+    });
+
+    it("should attest price updates with required fee even if more fee is given", async function () {
+        // Check that the owner is the default account Truffle
+        // has configured for the network.
+        const accounts = await web3.eth.getAccounts();
+        const defaultAccount = accounts[0];
+        assert.equal(await this.pythProxy.owner(), defaultAccount);
+
+        // Check initial fee is zero
+        assert.equal(await this.pythProxy.singleUpdateFeeInWei(), 0);
+
+        // Set fee 
+        await this.pythProxy.updateSingleUpdateFeeInWei(10);
+        assert.equal(await this.pythProxy.singleUpdateFeeInWei(), 10);
+
+        let ts = 1647273460;
+        let rawBatch1 = generateRawBatchAttestation(ts - 5, ts, 1337);
+        let rawBatch2 = generateRawBatchAttestation(ts + 5, ts + 10, 1338);
+
+        // Getting the fee from the contract
+        let feeInWei = await this.pythProxy.getUpdateFee(2);
+        assert.equal(feeInWei, 20);
+
+        const receipt = await updatePriceFeeds(this.pythProxy, [rawBatch1, rawBatch2], feeInWei + 10);
+        expectEvent(receipt, 'UpdatePriceFeeds', {
+            fee: feeInWei
+        });
+        const pythBalance = await web3.eth.getBalance(this.pythProxy.address);
+        assert.equal(pythBalance, feeInWei);
     });
 
     it("should cache price updates", async function () {
@@ -288,7 +441,16 @@ contract("Pyth", function () {
             priceVal
         );
         let receipt = await updatePriceFeeds(this.pythProxy, [rawBatch]);
-        expectEvent(receipt, 'PriceUpdate');
+        expectEvent(receipt, 'PriceFeedUpdate', {
+            fresh: true,
+        });
+        expectEvent(receipt, 'BatchPriceFeedUpdate', {
+            batchSize: '10',
+            freshPricesInBatch: '10',
+        });
+        expectEvent(receipt, 'UpdatePriceFeeds', {
+            batchCount: '1',
+        });
 
         let first_prod_id = "0x" + "01".repeat(32);
         let first_price_id = "0x" + "fe".repeat(32);
@@ -311,7 +473,16 @@ contract("Pyth", function () {
             priceVal + 5
         );
         receipt = await updatePriceFeeds(this.pythProxy, [rawBatch2]);
-        expectEvent(receipt, 'PriceUpdate');
+        expectEvent(receipt, 'PriceFeedUpdate', {
+            fresh: true,
+        });
+        expectEvent(receipt, 'BatchPriceFeedUpdate', {
+            batchSize: '10',
+            freshPricesInBatch: '10',
+        });
+        expectEvent(receipt, 'UpdatePriceFeeds', {
+            batchCount: '1',
+        });
 
         first = await this.pythProxy.queryPriceFeed(first_price_id);
         assert.equal(first.price, priceVal + 5);
@@ -326,7 +497,16 @@ contract("Pyth", function () {
             priceVal + 10
         );
         receipt = await updatePriceFeeds(this.pythProxy, [rawBatch3]);
-        expectEvent.notEmitted(receipt, 'PriceUpdate');
+        expectEvent(receipt, 'PriceFeedUpdate', {
+            fresh: false,
+        });
+        expectEvent(receipt, 'BatchPriceFeedUpdate', {
+            batchSize: '10',
+            freshPricesInBatch: '0',
+        });
+        expectEvent(receipt, 'UpdatePriceFeeds', {
+            batchCount: '1',
+        });
 
         first = await this.pythProxy.queryPriceFeed(first_price_id);
         assert.equal(first.price, priceVal + 5);