Skip to content

Commit 2597596

Browse files
authored
[eth] Gas improvement: Optimize events (#387)
* Update pyth-sdk-solidity version * Add parsePriceFeedUpdates as an empty method To be implemented in the future * Update events * Fix tests * Address Tom review comment * Fix Pyth forge test
1 parent 3b80bda commit 2597596

File tree

5 files changed

+78
-89
lines changed

5 files changed

+78
-89
lines changed

ethereum/contracts/pyth/Pyth.sol

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -40,8 +40,6 @@ abstract contract Pyth is PythGetters, PythSetters, AbstractPyth {
4040

4141
unchecked { i++; }
4242
}
43-
44-
emit UpdatePriceFeeds(msg.sender, updateData.length, requiredFee);
4543
}
4644

4745
/// This method is deprecated, please use the `getUpdateFee(bytes[])` instead.
@@ -120,7 +118,6 @@ abstract contract Pyth is PythGetters, PythSetters, AbstractPyth {
120118

121119
PythInternalStructs.PriceInfo memory info;
122120
bytes32 priceId;
123-
uint freshPrices = 0;
124121

125122
// Deserialize each attestation
126123
for (uint j=0; j < nAttestations; j++) {
@@ -200,22 +197,26 @@ abstract contract Pyth is PythGetters, PythSetters, AbstractPyth {
200197
// Store the attestation
201198
uint64 latestPublishTime = latestPriceInfoPublishTime(priceId);
202199

203-
bool fresh = false;
204200
if(info.publishTime > latestPublishTime) {
205-
freshPrices += 1;
206-
fresh = true;
207201
setLatestPriceInfo(priceId, info);
202+
emit PriceFeedUpdate(priceId, info.publishTime, info.price, info.conf);
208203
}
209-
210-
emit PriceFeedUpdate(priceId, fresh, vm.emitterChainId, vm.sequence, latestPublishTime,
211-
info.publishTime, info.price, info.conf);
212204
}
213205

214-
215-
emit BatchPriceFeedUpdate(vm.emitterChainId, vm.sequence, nAttestations, freshPrices);
206+
emit BatchPriceFeedUpdate(vm.emitterChainId, vm.sequence);
216207
}
217208
}
218209

210+
function parsePriceFeedUpdates(
211+
bytes[] calldata updateData,
212+
bytes32[] calldata priceIds,
213+
uint64 minPublishTime,
214+
uint64 maxPublishTime
215+
) external payable override returns (PythStructs.PriceFeed[] memory priceFeeds) {
216+
// TODO: To be implemented soon.
217+
revert("unimplemented");
218+
}
219+
219220
function queryPriceFeed(bytes32 id) public view override returns (PythStructs.PriceFeed memory priceFeed){
220221
// Look up the latest price info for the given ID
221222
PythInternalStructs.PriceInfo memory info = latestPriceInfo(id);

ethereum/forge-test/utils/PythTestUtils.t.sol

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import "../../contracts/pyth/PythUpgradable.sol";
66
import "../../contracts/pyth/PythInternalStructs.sol";
77
import "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol";
88
import "@pythnetwork/pyth-sdk-solidity/PythStructs.sol";
9+
import "@pythnetwork/pyth-sdk-solidity/IPythEvents.sol";
910
import "@pythnetwork/pyth-sdk-solidity/IPyth.sol";
1011

1112

@@ -130,10 +131,7 @@ abstract contract PythTestUtils is Test, WormholeTestUtils {
130131
}
131132
}
132133

133-
contract PythTestUtilsTest is Test, WormholeTestUtils, PythTestUtils {
134-
// TODO: It is better to have a PythEvents contract that be extendable.
135-
event PriceFeedUpdate(bytes32 indexed id, bool indexed fresh, uint16 chainId, uint64 sequenceNumber, uint lastPublishTime, uint publishTime, int64 price, uint64 conf);
136-
134+
contract PythTestUtilsTest is Test, WormholeTestUtils, PythTestUtils, IPythEvents {
137135
function testGeneratePriceFeedUpdateVAAWorks() public {
138136
IPyth pyth = IPyth(setUpPyth(setUpWormhole(
139137
1 // Number of guardians
@@ -163,7 +161,7 @@ contract PythTestUtilsTest is Test, WormholeTestUtils, PythTestUtils {
163161
uint updateFee = pyth.getUpdateFee(updateData);
164162

165163
vm.expectEmit(true, true, false, true);
166-
emit PriceFeedUpdate(priceIds[0], true, SOURCE_EMITTER_CHAIN_ID, 1, 0, 1, 100, 10);
164+
emit PriceFeedUpdate(priceIds[0], 1, 100, 10);
167165

168166
pyth.updatePriceFeeds{value: updateFee}(updateData);
169167
}

ethereum/package-lock.json

Lines changed: 7 additions & 7 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

ethereum/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@
3232
"@certusone/wormhole-sdk-wasm": "^0.0.1",
3333
"@openzeppelin/contracts": "^4.5.0",
3434
"@openzeppelin/contracts-upgradeable": "^4.5.2",
35-
"@pythnetwork/pyth-sdk-solidity": "^2.0.0",
35+
"@pythnetwork/pyth-sdk-solidity": "^2.1.0",
3636
"@pythnetwork/xc-governance-sdk": "file:../third_party/pyth/xc-governance-sdk-js",
3737
"dotenv": "^10.0.0",
3838
"elliptic": "^6.5.2",

ethereum/test/pyth.js

Lines changed: 55 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -286,7 +286,7 @@ contract("Pyth", function () {
286286
return replaced;
287287
}
288288

289-
it.only("should parse batch price attestation correctly", async function () {
289+
it("should parse batch price attestation correctly", async function () {
290290
let attestationTime = 1647273460; // re-used for publishTime
291291
let publishTime = 1647273465; // re-used for publishTime
292292
let priceVal = 1337;
@@ -300,9 +300,9 @@ contract("Pyth", function () {
300300

301301
const receipt = await updatePriceFeeds(this.pythProxy, [rawBatch]);
302302

303-
expectEvent(receipt, 'PriceFeedUpdate', {
303+
expectEventMultipleTimes(receipt, 'PriceFeedUpdate', {
304304
price: "1337",
305-
});
305+
}, 10);
306306

307307
for (var i = 1; i <= RAW_BATCH_ATTESTATION_COUNT; i++) {
308308
const price_id =
@@ -352,9 +352,6 @@ contract("Pyth", function () {
352352
const receipt = await updatePriceFeeds(this.pythProxy, []);
353353
expectEvent.notEmitted(receipt, 'PriceFeedUpdate');
354354
expectEvent.notEmitted(receipt, 'BatchPriceFeedUpdate');
355-
expectEvent(receipt, 'UpdatePriceFeeds', {
356-
batchCount: '0',
357-
});
358355
});
359356

360357
it("should attest price updates with multiple batches of correct order", async function () {
@@ -363,15 +360,12 @@ contract("Pyth", function () {
363360
let rawBatch2 = generateRawBatchAttestation(ts + 5, ts + 10, 1338);
364361
const receipt = await updatePriceFeeds(this.pythProxy, [rawBatch1, rawBatch2]);
365362
expectEvent(receipt, 'PriceFeedUpdate', {
366-
fresh: true,
367-
});
368-
expectEvent(receipt, 'BatchPriceFeedUpdate', {
369-
batchSize: '10',
370-
freshPricesInBatch: '10',
363+
publishTime: (ts - 5).toString(),
371364
});
372-
expectEvent(receipt, 'UpdatePriceFeeds', {
373-
batchCount: '2',
365+
expectEvent(receipt, 'PriceFeedUpdate', {
366+
publishTime: (ts + 5).toString(),
374367
});
368+
expectEventMultipleTimes(receipt, 'BatchPriceFeedUpdate', {}, 2);
375369
});
376370

377371
it("should attest price updates with multiple batches of wrong order", async function () {
@@ -380,21 +374,11 @@ contract("Pyth", function () {
380374
let rawBatch2 = generateRawBatchAttestation(ts + 5, ts + 10, 1338);
381375
const receipt = await updatePriceFeeds(this.pythProxy, [rawBatch2, rawBatch1]);
382376
expectEvent(receipt, 'PriceFeedUpdate', {
383-
fresh: true,
384-
});
385-
expectEvent(receipt, 'PriceFeedUpdate', {
386-
fresh: false,
387-
});
388-
expectEvent(receipt, 'BatchPriceFeedUpdate', {
389-
batchSize: '10',
390-
freshPricesInBatch: '10',
377+
publishTime: (ts + 5).toString(),
391378
});
392-
expectEvent(receipt, 'BatchPriceFeedUpdate', {
393-
batchSize: '10',
394-
freshPricesInBatch: '0',
395-
});
396-
expectEvent(receipt, 'UpdatePriceFeeds', {
397-
batchCount: '2',
379+
expectEventMultipleTimes(receipt, 'BatchPriceFeedUpdate', {}, 2);
380+
expectEventNotEmittedWithArgs(receipt, 'PriceFeedUpdate', {
381+
publishTime: (ts - 5).toString(),
398382
});
399383
});
400384

@@ -449,10 +433,7 @@ contract("Pyth", function () {
449433
let feeInWei = await this.pythProxy.methods["getUpdateFee(bytes[])"]([rawBatch1, rawBatch2]);
450434
assert.equal(feeInWei, 20);
451435

452-
const receipt = await updatePriceFeeds(this.pythProxy, [rawBatch1, rawBatch2], feeInWei);
453-
expectEvent(receipt, 'UpdatePriceFeeds', {
454-
fee: feeInWei
455-
});
436+
await updatePriceFeeds(this.pythProxy, [rawBatch1, rawBatch2], feeInWei);
456437
const pythBalance = await web3.eth.getBalance(this.pythProxy.address);
457438
assert.equal(pythBalance, feeInWei);
458439
});
@@ -479,10 +460,7 @@ contract("Pyth", function () {
479460
let feeInWei = await this.pythProxy.methods["getUpdateFee(bytes[])"]([rawBatch1, rawBatch2]);
480461
assert.equal(feeInWei, 20);
481462

482-
const receipt = await updatePriceFeeds(this.pythProxy, [rawBatch1, rawBatch2], feeInWei + 10);
483-
expectEvent(receipt, 'UpdatePriceFeeds', {
484-
fee: feeInWei
485-
});
463+
await updatePriceFeeds(this.pythProxy, [rawBatch1, rawBatch2], feeInWei + 10);
486464
const pythBalance = await web3.eth.getBalance(this.pythProxy.address);
487465
assert.equal(pythBalance, feeInWei + 10);
488466
});
@@ -497,15 +475,11 @@ contract("Pyth", function () {
497475
);
498476
let receipt = await updatePriceFeeds(this.pythProxy, [rawBatch]);
499477
expectEvent(receipt, 'PriceFeedUpdate', {
500-
fresh: true,
501-
});
502-
expectEvent(receipt, 'BatchPriceFeedUpdate', {
503-
batchSize: '10',
504-
freshPricesInBatch: '10',
505-
});
506-
expectEvent(receipt, 'UpdatePriceFeeds', {
507-
batchCount: '1',
478+
price: priceVal.toString(),
479+
publishTime: (currentTimestamp - 5).toString()
508480
});
481+
expectEvent(receipt, 'BatchPriceFeedUpdate');
482+
509483

510484
let first_prod_id = "0x" + "01".repeat(32);
511485
let first_price_id = "0x" + "fe".repeat(32);
@@ -529,15 +503,10 @@ contract("Pyth", function () {
529503
);
530504
receipt = await updatePriceFeeds(this.pythProxy, [rawBatch2]);
531505
expectEvent(receipt, 'PriceFeedUpdate', {
532-
fresh: true,
533-
});
534-
expectEvent(receipt, 'BatchPriceFeedUpdate', {
535-
batchSize: '10',
536-
freshPricesInBatch: '10',
537-
});
538-
expectEvent(receipt, 'UpdatePriceFeeds', {
539-
batchCount: '1',
506+
price: (priceVal+5).toString(),
507+
publishTime: (nextTimestamp - 5).toString()
540508
});
509+
expectEvent(receipt, 'BatchPriceFeedUpdate');
541510

542511
first = await this.pythProxy.queryPriceFeed(first_price_id);
543512
assert.equal(first.price.price, priceVal + 5);
@@ -552,16 +521,8 @@ contract("Pyth", function () {
552521
priceVal + 10
553522
);
554523
receipt = await updatePriceFeeds(this.pythProxy, [rawBatch3]);
555-
expectEvent(receipt, 'PriceFeedUpdate', {
556-
fresh: false,
557-
});
558-
expectEvent(receipt, 'BatchPriceFeedUpdate', {
559-
batchSize: '10',
560-
freshPricesInBatch: '0',
561-
});
562-
expectEvent(receipt, 'UpdatePriceFeeds', {
563-
batchCount: '1',
564-
});
524+
expectEvent.notEmitted(receipt, 'PriceFeedUpdate');
525+
expectEvent(receipt, 'BatchPriceFeedUpdate');
565526

566527
first = await this.pythProxy.queryPriceFeed(first_price_id);
567528
assert.equal(first.price.price, priceVal + 5);
@@ -1170,10 +1131,7 @@ contract("Pyth", function () {
11701131
insufficientFeeError
11711132
);
11721133

1173-
const receiptUpdateFeeds = await updatePriceFeeds(this.pythProxy, [rawBatch], 5000);
1174-
expectEvent(receiptUpdateFeeds, 'UpdatePriceFeeds', {
1175-
fee: "5000"
1176-
});
1134+
await updatePriceFeeds(this.pythProxy, [rawBatch], 5000);
11771135
});
11781136

11791137
it("Setting valid period should work", async function () {
@@ -1316,3 +1274,35 @@ async function createVAAFromUint8Array(
13161274
0
13171275
);
13181276
}
1277+
1278+
// There is no way to check event with given args has not emitted with expectEvent
1279+
// or how many times an event was emitted. This function is implemented to count
1280+
// the matching events and is used for the mentioned purposes.
1281+
function getNumMatchingEvents(receipt, eventName, args) {
1282+
let matchCnt = 0;
1283+
for (let log of receipt.logs) {
1284+
if (log.event === eventName) {
1285+
let match = true;
1286+
for (let argKey in args) {
1287+
if (log.args[argKey].toString() !== args[argKey].toString()) {
1288+
match = false;
1289+
break;
1290+
}
1291+
}
1292+
if (match) {
1293+
matchCnt ++;
1294+
}
1295+
}
1296+
}
1297+
return matchCnt;
1298+
}
1299+
1300+
function expectEventNotEmittedWithArgs(receipt, eventName, args) {
1301+
const matches = getNumMatchingEvents(receipt, eventName, args);
1302+
assert(matches === 0, `Expected no matching emitted event. But found ${matches}.`);
1303+
}
1304+
1305+
function expectEventMultipleTimes(receipt, eventName, args, cnt) {
1306+
const matches = getNumMatchingEvents(receipt, eventName, args);
1307+
assert(matches === cnt, `Expected ${cnt} event matches, found ${matches}.`);
1308+
}

0 commit comments

Comments
 (0)