diff --git a/ethereum/contracts/pyth/Pyth.sol b/ethereum/contracts/pyth/Pyth.sol index 7182033c..1cd0098c 100644 --- a/ethereum/contracts/pyth/Pyth.sol +++ b/ethereum/contracts/pyth/Pyth.sol @@ -40,8 +40,6 @@ abstract contract Pyth is PythGetters, PythSetters, AbstractPyth { unchecked { i++; } } - - emit UpdatePriceFeeds(msg.sender, updateData.length, requiredFee); } /// This method is deprecated, please use the `getUpdateFee(bytes[])` instead. @@ -120,7 +118,6 @@ abstract contract Pyth is PythGetters, PythSetters, AbstractPyth { PythInternalStructs.PriceInfo memory info; bytes32 priceId; - uint freshPrices = 0; // Deserialize each attestation for (uint j=0; j < nAttestations; j++) { @@ -200,22 +197,26 @@ abstract contract Pyth is PythGetters, PythSetters, AbstractPyth { // Store the attestation uint64 latestPublishTime = latestPriceInfoPublishTime(priceId); - bool fresh = false; if(info.publishTime > latestPublishTime) { - freshPrices += 1; - fresh = true; setLatestPriceInfo(priceId, info); + emit PriceFeedUpdate(priceId, info.publishTime, info.price, info.conf); } - - emit PriceFeedUpdate(priceId, fresh, vm.emitterChainId, vm.sequence, latestPublishTime, - info.publishTime, info.price, info.conf); } - - emit BatchPriceFeedUpdate(vm.emitterChainId, vm.sequence, nAttestations, freshPrices); + emit BatchPriceFeedUpdate(vm.emitterChainId, vm.sequence); } } + function parsePriceFeedUpdates( + bytes[] calldata updateData, + bytes32[] calldata priceIds, + uint64 minPublishTime, + uint64 maxPublishTime + ) external payable override returns (PythStructs.PriceFeed[] memory priceFeeds) { + // TODO: To be implemented soon. + revert("unimplemented"); + } + function queryPriceFeed(bytes32 id) public view override returns (PythStructs.PriceFeed memory priceFeed){ // Look up the latest price info for the given ID PythInternalStructs.PriceInfo memory info = latestPriceInfo(id); diff --git a/ethereum/forge-test/utils/PythTestUtils.t.sol b/ethereum/forge-test/utils/PythTestUtils.t.sol index 2e241ace..ba60edee 100644 --- a/ethereum/forge-test/utils/PythTestUtils.t.sol +++ b/ethereum/forge-test/utils/PythTestUtils.t.sol @@ -6,6 +6,7 @@ import "../../contracts/pyth/PythUpgradable.sol"; import "../../contracts/pyth/PythInternalStructs.sol"; import "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol"; import "@pythnetwork/pyth-sdk-solidity/PythStructs.sol"; +import "@pythnetwork/pyth-sdk-solidity/IPythEvents.sol"; import "@pythnetwork/pyth-sdk-solidity/IPyth.sol"; @@ -130,10 +131,7 @@ abstract contract PythTestUtils is Test, WormholeTestUtils { } } -contract PythTestUtilsTest is Test, WormholeTestUtils, PythTestUtils { - // TODO: It is better to have a PythEvents contract that be extendable. - event PriceFeedUpdate(bytes32 indexed id, bool indexed fresh, uint16 chainId, uint64 sequenceNumber, uint lastPublishTime, uint publishTime, int64 price, uint64 conf); - +contract PythTestUtilsTest is Test, WormholeTestUtils, PythTestUtils, IPythEvents { function testGeneratePriceFeedUpdateVAAWorks() public { IPyth pyth = IPyth(setUpPyth(setUpWormhole( 1 // Number of guardians @@ -163,7 +161,7 @@ contract PythTestUtilsTest is Test, WormholeTestUtils, PythTestUtils { uint updateFee = pyth.getUpdateFee(updateData); vm.expectEmit(true, true, false, true); - emit PriceFeedUpdate(priceIds[0], true, SOURCE_EMITTER_CHAIN_ID, 1, 0, 1, 100, 10); + emit PriceFeedUpdate(priceIds[0], 1, 100, 10); pyth.updatePriceFeeds{value: updateFee}(updateData); } diff --git a/ethereum/package-lock.json b/ethereum/package-lock.json index f4bdf69c..3eb6f712 100644 --- a/ethereum/package-lock.json +++ b/ethereum/package-lock.json @@ -13,7 +13,7 @@ "@certusone/wormhole-sdk-wasm": "^0.0.1", "@openzeppelin/contracts": "^4.5.0", "@openzeppelin/contracts-upgradeable": "^4.5.2", - "@pythnetwork/pyth-sdk-solidity": "^2.0.0", + "@pythnetwork/pyth-sdk-solidity": "^2.1.0", "@pythnetwork/xc-governance-sdk": "file:../third_party/pyth/xc-governance-sdk-js", "dotenv": "^10.0.0", "elliptic": "^6.5.2", @@ -5641,9 +5641,9 @@ "integrity": "sha1-p3c2C1s5oaLlEG+OhY8v0tBgxXA=" }, "node_modules/@pythnetwork/pyth-sdk-solidity": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@pythnetwork/pyth-sdk-solidity/-/pyth-sdk-solidity-2.0.0.tgz", - "integrity": "sha512-ogWpnI23Ofz1D5AmglkRxr+M/up/y15CBvXuxDcdq0Q6DvW3ksfPWP0DwCV2s7xbeSKYdpr97O+4NRmDCGeDsg==" + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@pythnetwork/pyth-sdk-solidity/-/pyth-sdk-solidity-2.1.0.tgz", + "integrity": "sha512-jHzqw+BHaCOAYwRNCgAUhcbNZrB5f3Arly3PaYN3/Tg7/5RQ95a9FD15XvJB1DB3yymUPIkmLYMur7Sh+e1G4A==" }, "node_modules/@pythnetwork/xc-governance-sdk": { "resolved": "../third_party/pyth/xc-governance-sdk-js", @@ -44827,9 +44827,9 @@ "integrity": "sha1-p3c2C1s5oaLlEG+OhY8v0tBgxXA=" }, "@pythnetwork/pyth-sdk-solidity": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@pythnetwork/pyth-sdk-solidity/-/pyth-sdk-solidity-2.0.0.tgz", - "integrity": "sha512-ogWpnI23Ofz1D5AmglkRxr+M/up/y15CBvXuxDcdq0Q6DvW3ksfPWP0DwCV2s7xbeSKYdpr97O+4NRmDCGeDsg==" + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@pythnetwork/pyth-sdk-solidity/-/pyth-sdk-solidity-2.1.0.tgz", + "integrity": "sha512-jHzqw+BHaCOAYwRNCgAUhcbNZrB5f3Arly3PaYN3/Tg7/5RQ95a9FD15XvJB1DB3yymUPIkmLYMur7Sh+e1G4A==" }, "@pythnetwork/xc-governance-sdk": { "version": "file:../third_party/pyth/xc-governance-sdk-js", diff --git a/ethereum/package.json b/ethereum/package.json index 117d5607..655d850d 100644 --- a/ethereum/package.json +++ b/ethereum/package.json @@ -32,7 +32,7 @@ "@certusone/wormhole-sdk-wasm": "^0.0.1", "@openzeppelin/contracts": "^4.5.0", "@openzeppelin/contracts-upgradeable": "^4.5.2", - "@pythnetwork/pyth-sdk-solidity": "^2.0.0", + "@pythnetwork/pyth-sdk-solidity": "^2.1.0", "@pythnetwork/xc-governance-sdk": "file:../third_party/pyth/xc-governance-sdk-js", "dotenv": "^10.0.0", "elliptic": "^6.5.2", diff --git a/ethereum/test/pyth.js b/ethereum/test/pyth.js index 8a76489f..e680e782 100644 --- a/ethereum/test/pyth.js +++ b/ethereum/test/pyth.js @@ -286,7 +286,7 @@ contract("Pyth", function () { return replaced; } - it.only("should parse batch price attestation correctly", async function () { + it("should parse batch price attestation correctly", async function () { let attestationTime = 1647273460; // re-used for publishTime let publishTime = 1647273465; // re-used for publishTime let priceVal = 1337; @@ -300,9 +300,9 @@ contract("Pyth", function () { const receipt = await updatePriceFeeds(this.pythProxy, [rawBatch]); - expectEvent(receipt, 'PriceFeedUpdate', { + expectEventMultipleTimes(receipt, 'PriceFeedUpdate', { price: "1337", - }); + }, 10); for (var i = 1; i <= RAW_BATCH_ATTESTATION_COUNT; i++) { const price_id = @@ -352,9 +352,6 @@ contract("Pyth", function () { const receipt = await updatePriceFeeds(this.pythProxy, []); 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 () { @@ -363,15 +360,12 @@ contract("Pyth", function () { let rawBatch2 = generateRawBatchAttestation(ts + 5, ts + 10, 1338); const receipt = await updatePriceFeeds(this.pythProxy, [rawBatch1, rawBatch2]); expectEvent(receipt, 'PriceFeedUpdate', { - fresh: true, + publishTime: (ts - 5).toString(), }); - expectEvent(receipt, 'BatchPriceFeedUpdate', { - batchSize: '10', - freshPricesInBatch: '10', - }); - expectEvent(receipt, 'UpdatePriceFeeds', { - batchCount: '2', + expectEvent(receipt, 'PriceFeedUpdate', { + publishTime: (ts + 5).toString(), }); + expectEventMultipleTimes(receipt, 'BatchPriceFeedUpdate', {}, 2); }); it("should attest price updates with multiple batches of wrong order", async function () { @@ -380,21 +374,11 @@ contract("Pyth", function () { let rawBatch2 = generateRawBatchAttestation(ts + 5, ts + 10, 1338); const receipt = await updatePriceFeeds(this.pythProxy, [rawBatch2, rawBatch1]); expectEvent(receipt, 'PriceFeedUpdate', { - fresh: true, + publishTime: (ts + 5).toString(), }); - expectEvent(receipt, 'PriceFeedUpdate', { - fresh: false, - }); - expectEvent(receipt, 'BatchPriceFeedUpdate', { - batchSize: '10', - freshPricesInBatch: '10', - }); - expectEvent(receipt, 'BatchPriceFeedUpdate', { - batchSize: '10', - freshPricesInBatch: '0', - }); - expectEvent(receipt, 'UpdatePriceFeeds', { - batchCount: '2', + expectEventMultipleTimes(receipt, 'BatchPriceFeedUpdate', {}, 2); + expectEventNotEmittedWithArgs(receipt, 'PriceFeedUpdate', { + publishTime: (ts - 5).toString(), }); }); @@ -449,10 +433,7 @@ contract("Pyth", function () { let feeInWei = await this.pythProxy.methods["getUpdateFee(bytes[])"]([rawBatch1, rawBatch2]); assert.equal(feeInWei, 20); - const receipt = await updatePriceFeeds(this.pythProxy, [rawBatch1, rawBatch2], feeInWei); - expectEvent(receipt, 'UpdatePriceFeeds', { - fee: feeInWei - }); + await updatePriceFeeds(this.pythProxy, [rawBatch1, rawBatch2], feeInWei); const pythBalance = await web3.eth.getBalance(this.pythProxy.address); assert.equal(pythBalance, feeInWei); }); @@ -479,10 +460,7 @@ contract("Pyth", function () { let feeInWei = await this.pythProxy.methods["getUpdateFee(bytes[])"]([rawBatch1, rawBatch2]); assert.equal(feeInWei, 20); - const receipt = await updatePriceFeeds(this.pythProxy, [rawBatch1, rawBatch2], feeInWei + 10); - expectEvent(receipt, 'UpdatePriceFeeds', { - fee: feeInWei - }); + await updatePriceFeeds(this.pythProxy, [rawBatch1, rawBatch2], feeInWei + 10); const pythBalance = await web3.eth.getBalance(this.pythProxy.address); assert.equal(pythBalance, feeInWei + 10); }); @@ -497,15 +475,11 @@ contract("Pyth", function () { ); let receipt = await updatePriceFeeds(this.pythProxy, [rawBatch]); expectEvent(receipt, 'PriceFeedUpdate', { - fresh: true, - }); - expectEvent(receipt, 'BatchPriceFeedUpdate', { - batchSize: '10', - freshPricesInBatch: '10', - }); - expectEvent(receipt, 'UpdatePriceFeeds', { - batchCount: '1', + price: priceVal.toString(), + publishTime: (currentTimestamp - 5).toString() }); + expectEvent(receipt, 'BatchPriceFeedUpdate'); + let first_prod_id = "0x" + "01".repeat(32); let first_price_id = "0x" + "fe".repeat(32); @@ -529,15 +503,10 @@ contract("Pyth", function () { ); receipt = await updatePriceFeeds(this.pythProxy, [rawBatch2]); expectEvent(receipt, 'PriceFeedUpdate', { - fresh: true, - }); - expectEvent(receipt, 'BatchPriceFeedUpdate', { - batchSize: '10', - freshPricesInBatch: '10', - }); - expectEvent(receipt, 'UpdatePriceFeeds', { - batchCount: '1', + price: (priceVal+5).toString(), + publishTime: (nextTimestamp - 5).toString() }); + expectEvent(receipt, 'BatchPriceFeedUpdate'); first = await this.pythProxy.queryPriceFeed(first_price_id); assert.equal(first.price.price, priceVal + 5); @@ -552,16 +521,8 @@ contract("Pyth", function () { priceVal + 10 ); receipt = await updatePriceFeeds(this.pythProxy, [rawBatch3]); - expectEvent(receipt, 'PriceFeedUpdate', { - fresh: false, - }); - expectEvent(receipt, 'BatchPriceFeedUpdate', { - batchSize: '10', - freshPricesInBatch: '0', - }); - expectEvent(receipt, 'UpdatePriceFeeds', { - batchCount: '1', - }); + expectEvent.notEmitted(receipt, 'PriceFeedUpdate'); + expectEvent(receipt, 'BatchPriceFeedUpdate'); first = await this.pythProxy.queryPriceFeed(first_price_id); assert.equal(first.price.price, priceVal + 5); @@ -1170,10 +1131,7 @@ contract("Pyth", function () { insufficientFeeError ); - const receiptUpdateFeeds = await updatePriceFeeds(this.pythProxy, [rawBatch], 5000); - expectEvent(receiptUpdateFeeds, 'UpdatePriceFeeds', { - fee: "5000" - }); + await updatePriceFeeds(this.pythProxy, [rawBatch], 5000); }); it("Setting valid period should work", async function () { @@ -1316,3 +1274,35 @@ async function createVAAFromUint8Array( 0 ); } + +// There is no way to check event with given args has not emitted with expectEvent +// or how many times an event was emitted. This function is implemented to count +// the matching events and is used for the mentioned purposes. +function getNumMatchingEvents(receipt, eventName, args) { + let matchCnt = 0; + for (let log of receipt.logs) { + if (log.event === eventName) { + let match = true; + for (let argKey in args) { + if (log.args[argKey].toString() !== args[argKey].toString()) { + match = false; + break; + } + } + if (match) { + matchCnt ++; + } + } + } + return matchCnt; +} + +function expectEventNotEmittedWithArgs(receipt, eventName, args) { + const matches = getNumMatchingEvents(receipt, eventName, args); + assert(matches === 0, `Expected no matching emitted event. But found ${matches}.`); +} + +function expectEventMultipleTimes(receipt, eventName, args, cnt) { + const matches = getNumMatchingEvents(receipt, eventName, args); + assert(matches === cnt, `Expected ${cnt} event matches, found ${matches}.`); +}