[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
This commit is contained in:
Ali Behjati 2022-11-17 17:44:21 +01:00 committed by GitHub
parent 3b80bda833
commit 2597596022
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 78 additions and 89 deletions

View File

@ -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);

View File

@ -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);
}

View File

@ -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",

View File

@ -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",

View File

@ -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}.`);
}